diff --git a/auth/auth.go b/auth/auth.go new file mode 100644 index 00000000..ef521bd2 --- /dev/null +++ b/auth/auth.go @@ -0,0 +1,36 @@ +package auth + +import "errors" + +// auth is a generic interface to implement password-based authentication and authorization +type Auth interface { + Authenticate(user, pass string) (*User, error) + Authorize(user *User, topic string, perm Permission) error +} + +type User struct { + Name string + Role Role +} + +type Permission int + +const ( + PermissionRead = Permission(1) + PermissionWrite = Permission(2) +) + +type Role string + +const ( + RoleAdmin = Role("admin") + RoleUser = Role("user") + RoleNone = Role("none") +) + +var Everyone = &User{ + Name: "", + Role: RoleNone, +} + +var ErrUnauthorized = errors.New("unauthorized") diff --git a/server/auth_sqlite.go b/auth/auth_sqlite.go similarity index 85% rename from server/auth_sqlite.go rename to auth/auth_sqlite.go index 0493c297..cbaa0e44 100644 --- a/server/auth_sqlite.go +++ b/auth/auth_sqlite.go @@ -1,4 +1,4 @@ -package server +package auth import ( "database/sql" @@ -69,15 +69,15 @@ const ( ` ) -type sqliteAuth struct { +type SQLiteAuth struct { db *sql.DB defaultRead bool defaultWrite bool } -var _ auth = (*sqliteAuth)(nil) +var _ Auth = (*SQLiteAuth)(nil) -func newSqliteAuth(filename string, defaultRead, defaultWrite bool) (*sqliteAuth, error) { +func NewSQLiteAuth(filename string, defaultRead, defaultWrite bool) (*SQLiteAuth, error) { db, err := sql.Open("sqlite3", filename) if err != nil { return nil, err @@ -85,7 +85,7 @@ func newSqliteAuth(filename string, defaultRead, defaultWrite bool) (*sqliteAuth if err := setupNewAuthDB(db); err != nil { return nil, err } - return &sqliteAuth{ + return &SQLiteAuth{ db: db, defaultRead: defaultRead, defaultWrite: defaultWrite, @@ -100,7 +100,7 @@ func setupNewAuthDB(db *sql.DB) error { return nil } -func (a *sqliteAuth) Authenticate(username, password string) (*user, error) { +func (a *SQLiteAuth) Authenticate(username, password string) (*User, error) { rows, err := a.db.Query(selectUserQuery, username) if err != nil { return nil, err @@ -117,14 +117,14 @@ func (a *sqliteAuth) Authenticate(username, password string) (*user, error) { if err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)); err != nil { return nil, err } - return &user{ + return &User{ Name: username, - Role: role, + Role: Role(role), }, nil } -func (a *sqliteAuth) Authorize(user *user, topic string, perm int) error { - if user.Role == roleAdmin { +func (a *SQLiteAuth) Authorize(user *User, topic string, perm Permission) error { + if user.Role == RoleAdmin { return nil // Admin can do everything } // Select the read/write permissions for this user/topic combo. The query may return two @@ -147,11 +147,11 @@ func (a *sqliteAuth) Authorize(user *user, topic string, perm int) error { return a.resolvePerms(read, write, perm) } -func (a *sqliteAuth) resolvePerms(read, write bool, perm int) error { - if perm == permRead && read { +func (a *SQLiteAuth) resolvePerms(read, write bool, perm Permission) error { + if perm == PermissionRead && read { return nil - } else if perm == permWrite && write { + } else if perm == PermissionWrite && write { return nil } - return errHTTPUnauthorized + return ErrUnauthorized } diff --git a/server/auth.go b/server/auth.go deleted file mode 100644 index d4a3110d..00000000 --- a/server/auth.go +++ /dev/null @@ -1,28 +0,0 @@ -package server - -// auth is a generic interface to implement password-based authentication and authorization -type auth interface { - Authenticate(user, pass string) (*user, error) - Authorize(user *user, topic string, perm int) error -} - -type user struct { - Name string - Role string -} - -const ( - permRead = 1 - permWrite = 2 -) - -const ( - roleAdmin = "admin" - roleUser = "user" - roleNone = "none" -) - -var everyone = &user{ - Name: "", - Role: roleNone, -} diff --git a/server/server.go b/server/server.go index 3c1daed5..ea06c447 100644 --- a/server/server.go +++ b/server/server.go @@ -14,6 +14,7 @@ import ( "github.com/gorilla/websocket" "golang.org/x/sync/errgroup" "google.golang.org/api/option" + "heckel.io/ntfy/auth" "heckel.io/ntfy/util" "html/template" "io" @@ -46,7 +47,7 @@ type Server struct { firebase subscriber mailer mailer messages int64 - auth auth + auth auth.Auth cache cache fileCache *fileCache closeChan chan bool @@ -141,9 +142,9 @@ func New(conf *Config) (*Server, error) { return nil, err } } - var auth auth + var auther auth.Auth if conf.AuthFile != "" { - auth, err = newSqliteAuth(conf.AuthFile, conf.AuthDefaultRead, conf.AuthDefaultWrite) + auther, err = auth.NewSQLiteAuth(conf.AuthFile, conf.AuthDefaultRead, conf.AuthDefaultWrite) if err != nil { return nil, err } @@ -155,7 +156,7 @@ func New(conf *Config) (*Server, error) { firebase: firebaseSubscriber, mailer: mailer, topics: topics, - auth: auth, + auth: auther, visitors: make(map[string]*visitor), }, nil } @@ -1117,14 +1118,14 @@ func (s *Server) limitRequests(next handleFunc) handleFunc { } func (s *Server) authWrite(next handleFunc) handleFunc { - return s.withAuth(next, permWrite) + return s.withAuth(next, auth.PermissionWrite) } func (s *Server) authRead(next handleFunc) handleFunc { - return s.withAuth(next, permRead) + return s.withAuth(next, auth.PermissionRead) } -func (s *Server) withAuth(next handleFunc, perm int) handleFunc { +func (s *Server) withAuth(next handleFunc, perm auth.Permission) handleFunc { return func(w http.ResponseWriter, r *http.Request, v *visitor) error { if s.auth == nil { return next(w, r, v) @@ -1133,7 +1134,7 @@ func (s *Server) withAuth(next handleFunc, perm int) handleFunc { if err != nil { return err } - user := everyone + user := auth.Everyone username, password, ok := r.BasicAuth() if ok { if user, err = s.auth.Authenticate(username, password); err != nil {