Now server/auth is completely generic on the user type

dev
Antonio De Lucreziis 4 years ago
parent ff0e0a5dcc
commit 10704d946f

@ -56,10 +56,15 @@ func (service *simpleAuthenticator) SessionTokenFromUser(userID string) (string,
return token, nil return token, nil
} }
func (service *simpleAuthenticator) UserFromSessionToken(session string) (string, error) { func (service *simpleAuthenticator) UserFromSessionToken(session string) (*db.User, error) {
user, present := service.sessions[session] userID, present := service.sessions[session]
if !present { if !present {
return "", auth.ErrNoUserForSession return nil, auth.ErrNoUserForSession
}
user, err := service.database.GetUser(userID)
if err != nil {
return nil, err
} }
return user, nil return user, nil

@ -7,24 +7,30 @@ import (
"errors" "errors"
"net/http" "net/http"
"time" "time"
"git.phc.dm.unipi.it/aziis98/posti-dm/server/util"
) )
var ( var (
ErrNoUserForSession = errors.New(`no user for session token`) ErrNoUserForSession = errors.New(`no user for session token`)
) )
type User[IdType any] interface {
UID() IdType
}
// Authenticator should be used by clients to provide authentication functions and mapping of session tokens to users // Authenticator should be used by clients to provide authentication functions and mapping of session tokens to users
type Authenticator interface { type Authenticator[UserID any, U User[UserID]] interface {
// CheckUserPassword is called to login a user and create a corresponding session, see also "SessionTokenFromUser" // CheckUserPassword is called to login a user and create a corresponding session, see also "SessionTokenFromUser"
CheckUserPassword(userID string, password string) error CheckUserPassword(user UserID, password string) error
// GetUserPermissions gets the list of permissions for this user // GetUserPermissions gets the list of permissions for this user
UserPermissions(userID string) ([]string, error) UserPermissions(user UserID) ([]string, error)
// SessionTokenFromUser returns a (new) session token that represents this user // SessionTokenFromUser returns a (new) session token that represents this user
SessionTokenFromUser(userID string) (string, error) SessionTokenFromUser(user UserID) (string, error)
// UserFromSessionToken returns the corresponding user for this session token or "service.ErrNoUserForSession" // UserFromSessionToken returns the corresponding user for this session token or "service.ErrNoUserForSession"
UserFromSessionToken(session string) (string, error) UserFromSessionToken(session string) (U, error)
// AuthenticationFailed handles failed authentications // AuthenticationFailed handles failed authentications
AuthenticationFailed(error) http.Handler AuthenticationFailed(error) http.Handler
@ -42,19 +48,19 @@ type MiddlewareConfig struct {
} }
// AuthSessionService given an Authenticator provides functions to login and logout users and an http.Handler middleware that accept users based on permissions and login status // AuthSessionService given an Authenticator provides functions to login and logout users and an http.Handler middleware that accept users based on permissions and login status
type AuthSessionService struct { type AuthSessionService[UserID any, U User[UserID]] struct {
SessionCookieName string SessionCookieName string
SessionCookiePath string SessionCookiePath string
SessionCookieDuration time.Duration SessionCookieDuration time.Duration
Authenticator Authenticator Authenticator[UserID, U]
} }
// NewAuthSessionService creates a new "*AuthSessionService" with a default session cookie name and path // NewAuthSessionService creates a new "*AuthSessionService" with a default session cookie name and path
func NewAuthSessionService(auth Authenticator) *AuthSessionService { func NewAuthSessionService[UserID any, U User[UserID]](auth Authenticator[UserID, U]) *AuthSessionService[UserID, U] {
oneWeek := 7 * 24 * time.Hour oneWeek := 7 * 24 * time.Hour
return &AuthSessionService{ return &AuthSessionService[UserID, U]{
"session", "session",
"/", "/",
oneWeek, oneWeek,
@ -63,7 +69,7 @@ func NewAuthSessionService(auth Authenticator) *AuthSessionService {
} }
// Login tries to login a user given its id and password // Login tries to login a user given its id and password
func (service *AuthSessionService) Login(w http.ResponseWriter, userID, password string) error { func (service *AuthSessionService[UserID, U]) Login(w http.ResponseWriter, userID UserID, password string) error {
if err := service.Authenticator.CheckUserPassword(userID, password); err != nil { if err := service.Authenticator.CheckUserPassword(userID, password); err != nil {
return err return err
} }
@ -84,7 +90,7 @@ func (service *AuthSessionService) Login(w http.ResponseWriter, userID, password
} }
// Logout clears the session cookie from a request effectively logging out the user for future requests // Logout clears the session cookie from a request effectively logging out the user for future requests
func (service *AuthSessionService) Logout(w http.ResponseWriter) { func (service *AuthSessionService[UserID, U]) Logout(w http.ResponseWriter) {
http.SetCookie(w, &http.Cookie{ http.SetCookie(w, &http.Cookie{
Name: service.SessionCookieName, Name: service.SessionCookieName,
Path: service.SessionCookiePath, Path: service.SessionCookiePath,
@ -94,7 +100,7 @@ func (service *AuthSessionService) Logout(w http.ResponseWriter) {
} }
// Middleware returns an http middleware that accepts users based on login status and permissions // Middleware returns an http middleware that accepts users based on login status and permissions
func (service *AuthSessionService) Middleware(config *MiddlewareConfig) func(http.Handler) http.Handler { func (service *AuthSessionService[UserID, U]) Middleware(config *MiddlewareConfig) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie(service.SessionCookieName) cookie, err := r.Cookie(service.SessionCookieName)
@ -112,7 +118,7 @@ func (service *AuthSessionService) Middleware(config *MiddlewareConfig) func(htt
return return
} }
userID, err := service.Authenticator.UserFromSessionToken(cookie.Value) user, err := service.Authenticator.UserFromSessionToken(cookie.Value)
if err == ErrNoUserForSession { if err == ErrNoUserForSession {
service.Logout(w) service.Logout(w)
w.WriteHeader(http.StatusUnauthorized) w.WriteHeader(http.StatusUnauthorized)
@ -124,7 +130,7 @@ func (service *AuthSessionService) Middleware(config *MiddlewareConfig) func(htt
} }
if config.RequireLogged { if config.RequireLogged {
userPerms, err := service.Authenticator.UserPermissions(userID) userPerms, err := service.Authenticator.UserPermissions(user.UID())
if err != nil { if err != nil {
service.Authenticator.OtherError(err).ServeHTTP(w, r) service.Authenticator.OtherError(err).ServeHTTP(w, r)
return return
@ -168,7 +174,7 @@ func (service *AuthSessionService) Middleware(config *MiddlewareConfig) func(htt
// Middleware(*AuthMiddlewareConfig) // Middleware(*AuthMiddlewareConfig)
// //
// that only accepts logged in users, no special permissions are checked // that only accepts logged in users, no special permissions are checked
func (service *AuthSessionService) LoggedMiddleware() func(http.Handler) http.Handler { func (service *AuthSessionService[UserID, U]) LoggedMiddleware() func(http.Handler) http.Handler {
return service.Middleware(&MiddlewareConfig{ return service.Middleware(&MiddlewareConfig{
RequireLogged: true, RequireLogged: true,
NeedPermissions: []string{}, NeedPermissions: []string{},
@ -181,16 +187,16 @@ func (service *AuthSessionService) LoggedMiddleware() func(http.Handler) http.Ha
// func (auth *AuthService[U]) RequestUser(r *http.Request) *U // func (auth *AuthService[U]) RequestUser(r *http.Request) *U
// //
// and will just return nil if no user is present. // and will just return nil if no user is present.
func (service *AuthSessionService) RequestUser(r *http.Request) (string, error) { func (service *AuthSessionService[UserID, U]) RequestUser(r *http.Request) (U, error) {
cookie, err := r.Cookie(service.SessionCookieName) cookie, err := r.Cookie(service.SessionCookieName)
if err != nil { if err != nil {
return "", err return util.Zero[U](), err
} }
userID, err := service.Authenticator.UserFromSessionToken(cookie.Value) user, err := service.Authenticator.UserFromSessionToken(cookie.Value)
if err == ErrNoUserForSession { if err == ErrNoUserForSession {
return "", err return util.Zero[U](), err
} }
return userID, nil return user, nil
} }

@ -38,6 +38,10 @@ type User struct {
// passwordSaltHash string // passwordSaltHash string
} }
func (user User) UID() string {
return user.ID
}
// Room represents a room in the database, a room has an id, a name and a collection of seatIDs of this room. // Room represents a room in the database, a room has an id, a name and a collection of seatIDs of this room.
type Room struct { type Room struct {
ID string `json:"id"` ID string `json:"id"`

@ -1,6 +1,7 @@
package main package main
import ( import (
"fmt"
"net/http" "net/http"
"git.phc.dm.unipi.it/aziis98/posti-dm/server/auth" "git.phc.dm.unipi.it/aziis98/posti-dm/server/auth"
@ -11,7 +12,7 @@ import (
) )
type Server struct { type Server struct {
auth *auth.AuthSessionService auth *auth.AuthSessionService[string, *db.User]
roomServerEventStreams map[string]serverevents.Handler roomServerEventStreams map[string]serverevents.Handler
@ -24,7 +25,7 @@ func NewServer() *Server {
server := &Server{ server := &Server{
Database: database, Database: database,
auth: auth.NewAuthSessionService( auth: auth.NewAuthSessionService[string, *db.User](
&simpleAuthenticator{ &simpleAuthenticator{
make(map[string]string), make(map[string]string),
database, database,
@ -84,17 +85,12 @@ func (server *Server) setupRoutes() {
}) })
api.Get("/user", func(w http.ResponseWriter, r *http.Request) { api.Get("/user", func(w http.ResponseWriter, r *http.Request) {
userID, err := auth.RequestUser(r) user, err := auth.RequestUser(r)
if err != nil { if err != nil {
httputil.WriteJSON(w, nil) httputil.WriteJSON(w, nil)
return return
} }
user, err := database.GetUser(userID)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
}
httputil.WriteJSON(w, user) httputil.WriteJSON(w, user)
}) })
@ -134,38 +130,40 @@ func (server *Server) setupRoutes() {
server.roomServerEventStreams[roomID[0]].ServeHTTP(w, r) server.roomServerEventStreams[roomID[0]].ServeHTTP(w, r)
}) })
requestSeat := func(r *http.Request) (*db.Seat, error) {
seatID, ok := r.URL.Query()["id"]
if !ok {
return nil, fmt.Errorf(`missing seat id`)
}
seat, err := database.GetSeat(seatID[0])
if err != nil {
return nil, err
}
return seat, nil
}
api.With(auth.LoggedMiddleware()). api.With(auth.LoggedMiddleware()).
Post("/seat/occupy", func(w http.ResponseWriter, r *http.Request) { Post("/seat/occupy", func(w http.ResponseWriter, r *http.Request) {
database.BeginTransaction() database.BeginTransaction()
defer database.EndTransaction() defer database.EndTransaction()
userID, err := auth.RequestUser(r) user, err := auth.RequestUser(r)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized) http.Error(w, err.Error(), http.StatusUnauthorized)
return return
} }
user, err := database.GetUser(userID) seat, err := requestSeat(r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
seatID, ok := r.URL.Query()["id"]
if !ok {
http.Error(w, `missing seat id`, http.StatusBadRequest)
return
}
seat, err := database.GetSeat(seatID[0])
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusBadRequest)
return return
} }
// a seat can be occupied only if empty; simple users can occupy a seat only if they have occupied no other seat; admins and moderator (for now) can occupy even more than one seat (as occupied by an anonymous?) to fix the free seat count of the room // a seat can be occupied only if empty; simple users can occupy a seat only if they have occupied no other seat; admins and moderator (for now) can occupy even more than one seat (as occupied by an anonymous?) to fix the free seat count of the room
if len(seat.OccupiedBy) == 0 { if len(seat.OccupiedBy) == 0 {
userSeats, err := database.GetUserSeats(userID) userSeats, err := database.GetUserSeats(user.ID)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -176,7 +174,7 @@ func (server *Server) setupRoutes() {
return return
} }
if err := database.OccupySeat(seatID[0], userID); err != nil { if err := database.OccupySeat(seat.ID, user.ID); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -190,34 +188,22 @@ func (server *Server) setupRoutes() {
api.With(auth.LoggedMiddleware()). api.With(auth.LoggedMiddleware()).
Post("/seat/leave", func(w http.ResponseWriter, r *http.Request) { Post("/seat/leave", func(w http.ResponseWriter, r *http.Request) {
userID, err := auth.RequestUser(r) user, err := auth.RequestUser(r)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized) http.Error(w, err.Error(), http.StatusUnauthorized)
return return
} }
user, err := database.GetUser(userID) seat, err := requestSeat(r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
seatID, ok := r.URL.Query()["id"]
if !ok {
http.Error(w, `missing seat id`, http.StatusBadRequest)
return
}
seat, err := database.GetSeat(seatID[0])
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusBadRequest)
return return
} }
// Check permissions, if the place is occupied then only its owner, a moderator or an admin can clear it // Check permissions, if the place is occupied then only its owner, a moderator or an admin can clear it
if len(seat.OccupiedBy) > 0 { if len(seat.OccupiedBy) > 0 {
if user.ID == seat.OccupiedBy[0] || user.Permissions.HasAny(db.PermissionAdmin, db.PermissionModerator) { if user.ID == seat.OccupiedBy[0] || user.Permissions.HasAny(db.PermissionAdmin, db.PermissionModerator) {
if err := database.FreeSeat(seatID[0]); err != nil { if err := database.FreeSeat(seat.ID); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }

@ -0,0 +1,5 @@
package util
func Zero[T any]() T {
return *new(T)
}
Loading…
Cancel
Save