Now server/auth is completely generic on the user type
This commit is contained in:
+8
-3
@@ -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
|
||||
|
||||
+26
-20
@@ -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"`
|
||||
|
||||
+27
-41
@@ -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)
|
||||
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 {
|
||||
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)
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user