Now server/auth is completely generic on the user type

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

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

@ -7,24 +7,30 @@ import (
"errors"
"net/http"
"time"
"git.phc.dm.unipi.it/aziis98/posti-dm/server/util"
)
var (
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
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(userID string, password string) error
CheckUserPassword(user UserID, password string) error
// 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(userID string) (string, error)
SessionTokenFromUser(user UserID) (string, error)
// 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(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
type AuthSessionService struct {
type AuthSessionService[UserID any, U User[UserID]] struct {
SessionCookieName string
SessionCookiePath string
SessionCookieDuration time.Duration
Authenticator
Authenticator Authenticator[UserID, U]
}
// 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
return &AuthSessionService{
return &AuthSessionService[UserID, U]{
"session",
"/",
oneWeek,
@ -63,7 +69,7 @@ func NewAuthSessionService(auth Authenticator) *AuthSessionService {
}
// 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 {
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
func (service *AuthSessionService) Logout(w http.ResponseWriter) {
func (service *AuthSessionService[UserID, U]) Logout(w http.ResponseWriter) {
http.SetCookie(w, &http.Cookie{
Name: service.SessionCookieName,
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
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 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie(service.SessionCookieName)
@ -112,7 +118,7 @@ func (service *AuthSessionService) Middleware(config *MiddlewareConfig) func(htt
return
}
userID, err := service.Authenticator.UserFromSessionToken(cookie.Value)
user, err := service.Authenticator.UserFromSessionToken(cookie.Value)
if err == ErrNoUserForSession {
service.Logout(w)
w.WriteHeader(http.StatusUnauthorized)
@ -124,7 +130,7 @@ func (service *AuthSessionService) Middleware(config *MiddlewareConfig) func(htt
}
if config.RequireLogged {
userPerms, err := service.Authenticator.UserPermissions(userID)
userPerms, err := service.Authenticator.UserPermissions(user.UID())
if err != nil {
service.Authenticator.OtherError(err).ServeHTTP(w, r)
return
@ -168,7 +174,7 @@ func (service *AuthSessionService) Middleware(config *MiddlewareConfig) func(htt
// Middleware(*AuthMiddlewareConfig)
//
// 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{
RequireLogged: true,
NeedPermissions: []string{},
@ -181,16 +187,16 @@ func (service *AuthSessionService) LoggedMiddleware() func(http.Handler) http.Ha
// func (auth *AuthService[U]) RequestUser(r *http.Request) *U
//
// 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)
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 {
return "", err
return util.Zero[U](), err
}
return userID, nil
return user, nil
}

@ -38,6 +38,10 @@ type User struct {
// 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.
type Room struct {
ID string `json:"id"`

@ -1,6 +1,7 @@
package main
import (
"fmt"
"net/http"
"git.phc.dm.unipi.it/aziis98/posti-dm/server/auth"
@ -11,7 +12,7 @@ import (
)
type Server struct {
auth *auth.AuthSessionService
auth *auth.AuthSessionService[string, *db.User]
roomServerEventStreams map[string]serverevents.Handler
@ -24,7 +25,7 @@ func NewServer() *Server {
server := &Server{
Database: database,
auth: auth.NewAuthSessionService(
auth: auth.NewAuthSessionService[string, *db.User](
&simpleAuthenticator{
make(map[string]string),
database,
@ -84,17 +85,12 @@ func (server *Server) setupRoutes() {
})
api.Get("/user", func(w http.ResponseWriter, r *http.Request) {
userID, err := auth.RequestUser(r)
user, err := auth.RequestUser(r)
if err != nil {
httputil.WriteJSON(w, nil)
return
}
user, err := database.GetUser(userID)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
}
httputil.WriteJSON(w, user)
})
@ -134,38 +130,40 @@ func (server *Server) setupRoutes() {
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()).
Post("/seat/occupy", func(w http.ResponseWriter, r *http.Request) {
database.BeginTransaction()
defer database.EndTransaction()
userID, err := auth.RequestUser(r)
user, err := auth.RequestUser(r)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
user, err := database.GetUser(userID)
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])
seat, err := requestSeat(r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
http.Error(w, err.Error(), http.StatusBadRequest)
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
if len(seat.OccupiedBy) == 0 {
userSeats, err := database.GetUserSeats(userID)
userSeats, err := database.GetUserSeats(user.ID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@ -176,7 +174,7 @@ func (server *Server) setupRoutes() {
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)
return
}
@ -190,34 +188,22 @@ func (server *Server) setupRoutes() {
api.With(auth.LoggedMiddleware()).
Post("/seat/leave", func(w http.ResponseWriter, r *http.Request) {
userID, err := auth.RequestUser(r)
user, err := auth.RequestUser(r)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
user, err := database.GetUser(userID)
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])
seat, err := requestSeat(r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// 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 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)
return
}

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