@ -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 correspon ing 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 * AuthSession Service) 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 effectiv ly logging out the user for future requests
// Logout clears the session cookie from a request effectiv e ly logging out the user for future requests
func ( auth * Auth Service) Logout ( w http . ResponseWriter ) {
func ( service * AuthSession Service) 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 * Auth Service) Middleware ( config * Auth MiddlewareConfig) func ( http . Handler ) http . Handler {
func ( service * AuthSession Service) 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 . With Permissions {
for _ , perm := range config . Need Permissions {
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 * Auth Service) LoggedMiddleware ( ) func ( http . Handler ) http . Handler {
func ( service * AuthSession Service) LoggedMiddleware ( ) func ( http . Handler ) http . Handler {
return auth . Middleware ( & Auth MiddlewareConfig{
return service . Middleware ( & MiddlewareConfig{
RequireLogged : true ,
RequireLogged : true ,
With Permissions: [ ] string { } ,
Need Permissions: [ ] 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 * Auth Service) RequestUser ( r * http . Request ) ( string , error ) {
func ( service * AuthSession Service) 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
}
}