Migliorata la struttura della libreria che gestisce le sessioni di autenticazione

This commit is contained in:
2022-03-20 22:12:31 +01:00
parent a970cea91a
commit 1495a5e45b
4 changed files with 145 additions and 130 deletions
+55 -64
View File
@@ -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
}