Now server/auth is completely generic on the user type

This commit is contained in:
2022-03-20 23:02:36 +01:00
parent ff0e0a5dcc
commit 10704d946f
5 changed files with 70 additions and 64 deletions
+26 -20
View File
@@ -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
}