Migliorata la struttura della libreria che gestisce le sessioni di autenticazione

dev
Antonio De Lucreziis 3 years ago
parent a970cea91a
commit 1495a5e45b

@ -0,0 +1,78 @@
package main
import (
"fmt"
"log"
"net/http"
"git.phc.dm.unipi.it/aziis98/posti-dm/server/auth"
"git.phc.dm.unipi.it/aziis98/posti-dm/server/db"
"git.phc.dm.unipi.it/aziis98/posti-dm/server/util"
)
// simpleAuthenticator holds an in memory map of session tokens and a reference to the main database interface
type simpleAuthenticator struct {
// sessions is a map from a sessionToken to userID
sessions map[string]string
database db.Database
}
func (service *simpleAuthenticator) CheckUserPassword(userID, password string) error {
if password != "phc" {
return fmt.Errorf(`invalid password`)
}
// FIXME: al momento quando la password è giusta creiamo tutti gli account necessari
err := service.database.CreateUser(&db.User{
ID: userID,
Permissions: make(util.StringSet),
})
if err != nil {
log.Printf(`got "%v" while trying to log as @%s`, err, userID)
return nil
}
return nil
}
func (service *simpleAuthenticator) UserPermissions(userID string) ([]string, error) {
user, err := service.database.GetUser(userID)
if err != nil {
return nil, err
}
return user.Permissions.Elements(), nil
}
func (service *simpleAuthenticator) SessionTokenFromUser(userID string) (string, error) {
user, err := service.database.GetUser(userID)
if err != nil {
return "", err
}
token := util.RandomHash(20)
service.sessions[token] = user.ID
return token, nil
}
func (service *simpleAuthenticator) UserFromSessionToken(session string) (string, error) {
user, present := service.sessions[session]
if !present {
return "", auth.ErrNoUserForSession
}
return user, nil
}
func (service *simpleAuthenticator) AuthenticationFailed(err error) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusUnauthorized)
})
}
func (service *simpleAuthenticator) OtherError(err error) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusInternalServerError)
})
}

@ -13,67 +13,58 @@ var (
ErrNoUserForSession = errors.New(`no user for session token`) ErrNoUserForSession = errors.New(`no user for session token`)
) )
var SessionCookieName = "session" // TODO: Make configurable // Authenticator handles cookies, authentication and authorization of http routes by providing middlewares, logint/logout methods, user sessions and retriving the userID of an authenticated request.
type Authenticator interface {
// AuthMiddlewareConfig configures the middleware to only accept logged users (if "RequireLogged" is true) and with certain permissions (user must have all permissions inside "WithPermissions")
type AuthMiddlewareConfig struct {
// RequireLogged rejects not logged users if true
RequireLogged bool
// WithPermissions is the list of permissions the user should have to pass the middleware
WithPermissions []string
}
// // Authenticator is the spec of this library
// type Authenticator interface {
// // Login checks user credentials and adds a session cookie to the user if successfull
// Login(w http.ResponseWriter, r *http.Request, userID, password string)
// // Logout clears the user session cookies (by setting the session cookie timeout to 0)
// Logout(w http.ResponseWriter)
// // Middleware is a configurable middleware to authenticate http routes based on logged status and permissions
// Middleware(*AuthMiddlewareConfig) func(http.Handler) http.Handler
// // LoggedMiddleware accepts all logged users without checking for specific permissions
// LoggedMiddleware() func(http.Handler) http.Handler
// // RequestUser returns the userID for this cookie session token
// RequestUser(r *http.Request) (string, error)
// }
// var _ Authenticator = &AuthService{}
// AuthService handles cookies, authentication and authorization of http routes by providing middlewares, logint/logout methods, user sessions and retriving the userID of an authenticated request.
type AuthService struct {
// 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 func(userID string, password string) error CheckUserPassword(userID string, password string) error
// GetUserPermissions gets the list of permissions for this user // GetUserPermissions gets the list of permissions for this user
UserPermissions func(userID string) ([]string, error) UserPermissions(userID string) ([]string, error)
// SessionTokenFromUser returns a (new) session token that represents this user // SessionTokenFromUser returns a (new) session token that represents this user
SessionTokenFromUser func(userID string) (string, error) SessionTokenFromUser(userID string) (string, error)
// UserFromSessionToken returns the corresponing user for this session token or "auth.ErrNoUserForSession" // UserFromSessionToken returns the corresponding user for this session token or "service.ErrNoUserForSession"
UserFromSessionToken func(session string) (string, error) UserFromSessionToken(session string) (string, error)
// AuthenticationFailed handles failed authentications // AuthenticationFailed handles failed authentications
AuthenticationFailed func(error) http.Handler AuthenticationFailed(error) http.Handler
// OtherError handles other errors // OtherError handles other errors
OtherError func(error) http.Handler OtherError(error) http.Handler
}
// MiddlewareConfig configures the middleware to only accept logged users (if "RequireLogged" is true) and with certain permissions (user must have all permissions inside "WithPermissions")
type MiddlewareConfig struct {
// RequireLogged rejects not logged users if true
RequireLogged bool
// NeedPermissions is the list of permissions the user should have to pass the middleware
NeedPermissions []string
}
// AuthService is the spec of this library
type AuthSessionService struct {
SessionCookieName string
Authenticator
}
// NewAuthSessionService creates a new *AuthSessionService with a default session cookie name
func NewAuthSessionService(auth Authenticator) *AuthSessionService {
return &AuthSessionService{"session", auth}
} }
// Login tries to login a user given its id and password // Login tries to login a user given its id and password
func (auth *AuthService) Login(w http.ResponseWriter, r *http.Request, userID, password string) error { func (service *AuthSessionService) Login(w http.ResponseWriter, userID, password string) error {
if err := auth.CheckUserPassword(userID, password); err != nil { if err := service.Authenticator.CheckUserPassword(userID, password); err != nil {
return err return err
} }
token, err := auth.SessionTokenFromUser(userID) token, err := service.Authenticator.SessionTokenFromUser(userID)
if err != nil { if err != nil {
return err return err
} }
http.SetCookie(w, &http.Cookie{ http.SetCookie(w, &http.Cookie{
Name: SessionCookieName, Name: service.SessionCookieName,
Path: "/", // TODO: Make configurable Path: "/", // TODO: Make configurable
Value: token, Value: token,
Expires: time.Now().Add(7 * 24 * time.Hour), // TODO: Make configurable Expires: time.Now().Add(7 * 24 * time.Hour), // TODO: Make configurable
@ -82,10 +73,10 @@ func (auth *AuthService) Login(w http.ResponseWriter, r *http.Request, userID, p
return nil return nil
} }
// Logout clears the session cookie from a request effectivly logging out the user for future requests // Logout clears the session cookie from a request effectively logging out the user for future requests
func (auth *AuthService) Logout(w http.ResponseWriter) { func (service *AuthSessionService) Logout(w http.ResponseWriter) {
http.SetCookie(w, &http.Cookie{ http.SetCookie(w, &http.Cookie{
Name: SessionCookieName, Name: service.SessionCookieName,
Path: "/", Path: "/",
Value: "", Value: "",
Expires: time.Now(), Expires: time.Now(),
@ -93,39 +84,39 @@ func (auth *AuthService) Logout(w http.ResponseWriter) {
} }
// Middleware checks if the user is logged or not and if the user has all the permissions set in "config.WithPermissions" // Middleware checks if the user is logged or not and if the user has all the permissions set in "config.WithPermissions"
func (auth *AuthService) Middleware(config *AuthMiddlewareConfig) func(http.Handler) http.Handler { func (service *AuthSessionService) 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(SessionCookieName) cookie, err := r.Cookie(service.SessionCookieName)
if err == http.ErrNoCookie { if err == http.ErrNoCookie {
if !config.RequireLogged { // Login not required if !config.RequireLogged { // Login not required
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
return return
} }
auth.AuthenticationFailed(err).ServeHTTP(w, r) service.Authenticator.AuthenticationFailed(err).ServeHTTP(w, r)
return return
} }
if err != nil { if err != nil {
auth.OtherError(err).ServeHTTP(w, r) service.Authenticator.OtherError(err).ServeHTTP(w, r)
return return
} }
userID, err := auth.UserFromSessionToken(cookie.Value) userID, err := service.Authenticator.UserFromSessionToken(cookie.Value)
if err == ErrNoUserForSession { if err == ErrNoUserForSession {
auth.Logout(w) service.Logout(w)
w.WriteHeader(http.StatusUnauthorized) w.WriteHeader(http.StatusUnauthorized)
return return
} }
if err != nil { if err != nil {
auth.OtherError(err).ServeHTTP(w, r) service.Authenticator.OtherError(err).ServeHTTP(w, r)
return return
} }
if config.RequireLogged { if config.RequireLogged {
userPerms, err := auth.UserPermissions(userID) userPerms, err := service.Authenticator.UserPermissions(userID)
if err != nil { if err != nil {
auth.OtherError(err).ServeHTTP(w, r) service.Authenticator.OtherError(err).ServeHTTP(w, r)
return return
} }
@ -136,7 +127,7 @@ func (auth *AuthService) Middleware(config *AuthMiddlewareConfig) func(http.Hand
// check the user has all the permissions to access the route // check the user has all the permissions to access the route
hasAll := true hasAll := true
for _, perm := range config.WithPermissions { for _, perm := range config.NeedPermissions {
if _, present := userPermsMap[perm]; !present { if _, present := userPermsMap[perm]; !present {
hasAll = false hasAll = false
break break
@ -144,14 +135,14 @@ func (auth *AuthService) Middleware(config *AuthMiddlewareConfig) func(http.Hand
} }
if !hasAll { if !hasAll {
auth.AuthenticationFailed(err).ServeHTTP(w, r) service.Authenticator.AuthenticationFailed(err).ServeHTTP(w, r)
return return
} }
} }
// Refresh session cookie expiration // Refresh session cookie expiration
http.SetCookie(w, &http.Cookie{ http.SetCookie(w, &http.Cookie{
Name: SessionCookieName, Name: service.SessionCookieName,
Path: "/", Path: "/",
Value: cookie.Value, Value: cookie.Value,
Expires: time.Now().Add(7 * 24 * time.Hour), Expires: time.Now().Add(7 * 24 * time.Hour),
@ -167,10 +158,10 @@ func (auth *AuthService) Middleware(config *AuthMiddlewareConfig) func(http.Hand
// Middleware(*AuthMiddlewareConfig) // Middleware(*AuthMiddlewareConfig)
// //
// that checks if a user is logged, no extra permissions are checked // that checks if a user is logged, no extra permissions are checked
func (auth *AuthService) LoggedMiddleware() func(http.Handler) http.Handler { func (service *AuthSessionService) LoggedMiddleware() func(http.Handler) http.Handler {
return auth.Middleware(&AuthMiddlewareConfig{ return service.Middleware(&MiddlewareConfig{
RequireLogged: true, RequireLogged: true,
WithPermissions: []string{}, NeedPermissions: []string{},
}) })
} }
@ -180,13 +171,13 @@ func (auth *AuthService) LoggedMiddleware() func(http.Handler) http.Handler {
// 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 (auth *AuthService) RequestUser(r *http.Request) (string, error) { func (service *AuthSessionService) RequestUser(r *http.Request) (string, error) {
cookie, err := r.Cookie(SessionCookieName) cookie, err := r.Cookie(service.SessionCookieName)
if err != nil { if err != nil {
return "", err return "", err
} }
userID, err := auth.UserFromSessionToken(cookie.Value) userID, err := service.Authenticator.UserFromSessionToken(cookie.Value)
if err == ErrNoUserForSession { if err == ErrNoUserForSession {
return "", err return "", err
} }

@ -96,8 +96,8 @@ type Seat struct {
// Database Interfaces // Database Interfaces
// //
// Store is the main interface for interacting with database implementations, for now the only implementation is "memDB". // Database is the main interface for interacting with database implementations, for now the only implementation is "memDB".
type Store interface { type Database interface {
BeginTransaction() BeginTransaction()
EndTransaction() EndTransaction()
@ -127,7 +127,7 @@ type Store interface {
// TODO: Create an SQLite implementation // TODO: Create an SQLite implementation
// NewInMemoryStore creates an instance of memDB hidden behind the Store interface. // NewInMemoryStore creates an instance of memDB hidden behind the Store interface.
func NewInMemoryStore() Store { func NewInMemoryStore() Database {
db := &memDB{ db := &memDB{
&sync.Mutex{}, &sync.Mutex{},

@ -1,89 +1,35 @@
package main package main
import ( import (
"fmt"
"log"
"net/http" "net/http"
"git.phc.dm.unipi.it/aziis98/posti-dm/server/auth" "git.phc.dm.unipi.it/aziis98/posti-dm/server/auth"
"git.phc.dm.unipi.it/aziis98/posti-dm/server/db" "git.phc.dm.unipi.it/aziis98/posti-dm/server/db"
"git.phc.dm.unipi.it/aziis98/posti-dm/server/httputil" "git.phc.dm.unipi.it/aziis98/posti-dm/server/httputil"
"git.phc.dm.unipi.it/aziis98/posti-dm/server/httputil/serverevents" "git.phc.dm.unipi.it/aziis98/posti-dm/server/httputil/serverevents"
"git.phc.dm.unipi.it/aziis98/posti-dm/server/util"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
) )
type Server struct { type Server struct {
authService *auth.AuthService auth *auth.AuthSessionService
roomServerEventStreams map[string]serverevents.Handler roomServerEventStreams map[string]serverevents.Handler
Database db.Store Database db.Database
ApiRoute chi.Router ApiRoute chi.Router
} }
func NewServer() *Server { func NewServer() *Server {
sessions := make(map[string]*db.User)
database := db.NewInMemoryStore() database := db.NewInMemoryStore()
server := &Server{ server := &Server{
Database: database, Database: database,
authService: &auth.AuthService{ auth: auth.NewAuthSessionService(
CheckUserPassword: func(userID, password string) error { &simpleAuthenticator{
if password != "phc" { make(map[string]string),
return fmt.Errorf(`invalid password`) database,
}
// FIXME: al momento quando la password è giusta creiamo tutti gli account necessari
err := database.CreateUser(&db.User{
ID: userID,
Permissions: make(util.StringSet),
})
if err != nil {
log.Printf(`got "%v" while trying to log as @%s`, err, userID)
return nil
}
return nil
},
UserPermissions: func(userID string) ([]string, error) {
user, err := database.GetUser(userID)
if err != nil {
return nil, err
}
return user.Permissions.Elements(), nil
},
SessionTokenFromUser: func(userID string) (string, error) {
user, err := database.GetUser(userID)
if err != nil {
return "", err
}
token := util.RandomHash(20)
sessions[token] = user
return token, nil
},
UserFromSessionToken: func(session string) (string, error) {
user, present := sessions[session]
if !present {
return "", auth.ErrNoUserForSession
}
return user.ID, nil
},
AuthenticationFailed: func(err error) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusUnauthorized)
})
},
OtherError: func(err error) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusInternalServerError)
})
}, },
}, ),
roomServerEventStreams: make(map[string]serverevents.Handler), roomServerEventStreams: make(map[string]serverevents.Handler),
@ -103,7 +49,7 @@ func NewServer() *Server {
func (server *Server) setupRoutes() { func (server *Server) setupRoutes() {
api := server.ApiRoute api := server.ApiRoute
database := server.Database database := server.Database
auth := server.authService auth := server.auth
// FIXME: in realtà tutte le routes che interagiscono con il db dovrebbero essere in transazione con // FIXME: in realtà tutte le routes che interagiscono con il db dovrebbero essere in transazione con
// database.BeginTransaction() // database.BeginTransaction()
@ -122,7 +68,7 @@ func (server *Server) setupRoutes() {
return return
} }
err := auth.Login(w, r, credentials.Username, credentials.Password) err := auth.Login(w, credentials.Username, credentials.Password)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized) http.Error(w, err.Error(), http.StatusUnauthorized)
return return

Loading…
Cancel
Save