Migliorata la struttura della libreria che gestisce le sessioni di autenticazione
This commit is contained in:
@@ -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)
|
||||
})
|
||||
}
|
||||
+55
-64
@@ -13,67 +13,58 @@ var (
|
||||
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 {
|
||||
// CheckUserPassword is called to login a user and create a corresponding session, see also "SessionTokenFromUser"
|
||||
CheckUserPassword(userID string, password string) error
|
||||
|
||||
// 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 {
|
||||
// GetUserPermissions gets the list of permissions for this user
|
||||
UserPermissions(userID string) ([]string, error)
|
||||
|
||||
// SessionTokenFromUser returns a (new) session token that represents this user
|
||||
SessionTokenFromUser(userID string) (string, error)
|
||||
// UserFromSessionToken returns the corresponding user for this session token or "service.ErrNoUserForSession"
|
||||
UserFromSessionToken(session string) (string, error)
|
||||
|
||||
// AuthenticationFailed handles failed authentications
|
||||
AuthenticationFailed(error) http.Handler
|
||||
// OtherError handles other errors
|
||||
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
|
||||
|
||||
// WithPermissions is the list of permissions the user should have to pass the middleware
|
||||
WithPermissions []string
|
||||
// NeedPermissions is the list of permissions the user should have to pass the middleware
|
||||
NeedPermissions []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)
|
||||
// AuthService is the spec of this library
|
||||
type AuthSessionService struct {
|
||||
SessionCookieName string
|
||||
Authenticator
|
||||
}
|
||||
|
||||
// // 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 func(userID string, password string) error
|
||||
|
||||
// GetUserPermissions gets the list of permissions for this user
|
||||
UserPermissions func(userID string) ([]string, error)
|
||||
|
||||
// SessionTokenFromUser returns a (new) session token that represents this user
|
||||
SessionTokenFromUser func(userID string) (string, error)
|
||||
// UserFromSessionToken returns the corresponing user for this session token or "auth.ErrNoUserForSession"
|
||||
UserFromSessionToken func(session string) (string, error)
|
||||
|
||||
// AuthenticationFailed handles failed authentications
|
||||
AuthenticationFailed func(error) http.Handler
|
||||
// OtherError handles other errors
|
||||
OtherError func(error) http.Handler
|
||||
// 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
|
||||
func (auth *AuthService) Login(w http.ResponseWriter, r *http.Request, userID, password string) error {
|
||||
if err := auth.CheckUserPassword(userID, password); err != nil {
|
||||
func (service *AuthSessionService) Login(w http.ResponseWriter, userID, password string) error {
|
||||
if err := service.Authenticator.CheckUserPassword(userID, password); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
token, err := auth.SessionTokenFromUser(userID)
|
||||
token, err := service.Authenticator.SessionTokenFromUser(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: SessionCookieName,
|
||||
Name: service.SessionCookieName,
|
||||
Path: "/", // TODO: Make configurable
|
||||
Value: token,
|
||||
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
|
||||
}
|
||||
|
||||
// Logout clears the session cookie from a request effectivly logging out the user for future requests
|
||||
func (auth *AuthService) Logout(w http.ResponseWriter) {
|
||||
// Logout clears the session cookie from a request effectively logging out the user for future requests
|
||||
func (service *AuthSessionService) Logout(w http.ResponseWriter) {
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: SessionCookieName,
|
||||
Name: service.SessionCookieName,
|
||||
Path: "/",
|
||||
Value: "",
|
||||
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"
|
||||
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 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 !config.RequireLogged { // Login not required
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
auth.AuthenticationFailed(err).ServeHTTP(w, r)
|
||||
service.Authenticator.AuthenticationFailed(err).ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
auth.OtherError(err).ServeHTTP(w, r)
|
||||
service.Authenticator.OtherError(err).ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
userID, err := auth.UserFromSessionToken(cookie.Value)
|
||||
userID, err := service.Authenticator.UserFromSessionToken(cookie.Value)
|
||||
if err == ErrNoUserForSession {
|
||||
auth.Logout(w)
|
||||
service.Logout(w)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
auth.OtherError(err).ServeHTTP(w, r)
|
||||
service.Authenticator.OtherError(err).ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if config.RequireLogged {
|
||||
userPerms, err := auth.UserPermissions(userID)
|
||||
userPerms, err := service.Authenticator.UserPermissions(userID)
|
||||
if err != nil {
|
||||
auth.OtherError(err).ServeHTTP(w, r)
|
||||
service.Authenticator.OtherError(err).ServeHTTP(w, r)
|
||||
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
|
||||
hasAll := true
|
||||
for _, perm := range config.WithPermissions {
|
||||
for _, perm := range config.NeedPermissions {
|
||||
if _, present := userPermsMap[perm]; !present {
|
||||
hasAll = false
|
||||
break
|
||||
@@ -144,14 +135,14 @@ func (auth *AuthService) Middleware(config *AuthMiddlewareConfig) func(http.Hand
|
||||
}
|
||||
|
||||
if !hasAll {
|
||||
auth.AuthenticationFailed(err).ServeHTTP(w, r)
|
||||
service.Authenticator.AuthenticationFailed(err).ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh session cookie expiration
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: SessionCookieName,
|
||||
Name: service.SessionCookieName,
|
||||
Path: "/",
|
||||
Value: cookie.Value,
|
||||
Expires: time.Now().Add(7 * 24 * time.Hour),
|
||||
@@ -167,10 +158,10 @@ func (auth *AuthService) Middleware(config *AuthMiddlewareConfig) func(http.Hand
|
||||
// Middleware(*AuthMiddlewareConfig)
|
||||
//
|
||||
// that checks if a user is logged, no extra permissions are checked
|
||||
func (auth *AuthService) LoggedMiddleware() func(http.Handler) http.Handler {
|
||||
return auth.Middleware(&AuthMiddlewareConfig{
|
||||
func (service *AuthSessionService) LoggedMiddleware() func(http.Handler) http.Handler {
|
||||
return service.Middleware(&MiddlewareConfig{
|
||||
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
|
||||
//
|
||||
// and will just return nil if no user is present.
|
||||
func (auth *AuthService) RequestUser(r *http.Request) (string, error) {
|
||||
cookie, err := r.Cookie(SessionCookieName)
|
||||
func (service *AuthSessionService) RequestUser(r *http.Request) (string, error) {
|
||||
cookie, err := r.Cookie(service.SessionCookieName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
userID, err := auth.UserFromSessionToken(cookie.Value)
|
||||
userID, err := service.Authenticator.UserFromSessionToken(cookie.Value)
|
||||
if err == ErrNoUserForSession {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -96,8 +96,8 @@ type Seat struct {
|
||||
// Database Interfaces
|
||||
//
|
||||
|
||||
// Store is the main interface for interacting with database implementations, for now the only implementation is "memDB".
|
||||
type Store interface {
|
||||
// Database is the main interface for interacting with database implementations, for now the only implementation is "memDB".
|
||||
type Database interface {
|
||||
BeginTransaction()
|
||||
EndTransaction()
|
||||
|
||||
@@ -127,7 +127,7 @@ type Store interface {
|
||||
// TODO: Create an SQLite implementation
|
||||
|
||||
// NewInMemoryStore creates an instance of memDB hidden behind the Store interface.
|
||||
func NewInMemoryStore() Store {
|
||||
func NewInMemoryStore() Database {
|
||||
db := &memDB{
|
||||
&sync.Mutex{},
|
||||
|
||||
|
||||
+9
-63
@@ -1,89 +1,35 @@
|
||||
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/httputil"
|
||||
"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"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
authService *auth.AuthService
|
||||
auth *auth.AuthSessionService
|
||||
|
||||
roomServerEventStreams map[string]serverevents.Handler
|
||||
|
||||
Database db.Store
|
||||
Database db.Database
|
||||
ApiRoute chi.Router
|
||||
}
|
||||
|
||||
func NewServer() *Server {
|
||||
sessions := make(map[string]*db.User)
|
||||
database := db.NewInMemoryStore()
|
||||
|
||||
server := &Server{
|
||||
Database: database,
|
||||
authService: &auth.AuthService{
|
||||
CheckUserPassword: func(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 := 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
|
||||
auth: auth.NewAuthSessionService(
|
||||
&simpleAuthenticator{
|
||||
make(map[string]string),
|
||||
database,
|
||||
},
|
||||
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),
|
||||
|
||||
@@ -103,7 +49,7 @@ func NewServer() *Server {
|
||||
func (server *Server) setupRoutes() {
|
||||
api := server.ApiRoute
|
||||
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
|
||||
// database.BeginTransaction()
|
||||
@@ -122,7 +68,7 @@ func (server *Server) setupRoutes() {
|
||||
return
|
||||
}
|
||||
|
||||
err := auth.Login(w, r, credentials.Username, credentials.Password)
|
||||
err := auth.Login(w, credentials.Username, credentials.Password)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusUnauthorized)
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user