You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
180 lines
4.7 KiB
Go
180 lines
4.7 KiB
Go
package auth
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"time"
|
|
|
|
"git.phc.dm.unipi.it/aziis98/posti-dm/server/httputil"
|
|
)
|
|
|
|
var (
|
|
ErrNoUserForSession = errors.New(`no user for session token`)
|
|
)
|
|
var SessionCookieName = "session" // TODO: Make configurable
|
|
|
|
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
|
|
}
|
|
|
|
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
|
|
|
|
// UserFromSession returns the userID for this cookie session token
|
|
UserFromSession(r *http.Request) (string, error)
|
|
}
|
|
|
|
type AuthService struct {
|
|
// CheckUserPassword
|
|
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 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 http.Handler
|
|
|
|
// OtherError handles other errors
|
|
OtherError func(error) http.Handler
|
|
}
|
|
|
|
func (auth *AuthService) Login(w http.ResponseWriter, r *http.Request, userID, password string) {
|
|
if err := auth.CheckUserPassword(userID, password); err != nil {
|
|
auth.AuthenticationFailed.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
token, err := auth.SessionTokenFromUser(userID)
|
|
if err != nil {
|
|
auth.OtherError(err).ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
http.SetCookie(w, &http.Cookie{
|
|
Name: SessionCookieName,
|
|
Path: "/", // TODO: Make configurable
|
|
Value: token,
|
|
Expires: time.Now().Add(7 * 24 * time.Hour), // TODO: Make configurable
|
|
})
|
|
|
|
httputil.WriteJSON(w, "ok")
|
|
}
|
|
|
|
func (auth *AuthService) Logout(w http.ResponseWriter) {
|
|
http.SetCookie(w, &http.Cookie{
|
|
Name: SessionCookieName,
|
|
Path: "/",
|
|
Value: "",
|
|
Expires: time.Now(),
|
|
})
|
|
|
|
httputil.WriteJSON(w, "ok")
|
|
}
|
|
|
|
func (auth *AuthService) Middleware(config *AuthMiddlewareConfig) 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)
|
|
if err == http.ErrNoCookie {
|
|
if !config.RequireLogged { // Login not required
|
|
next.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
auth.AuthenticationFailed.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
if err != nil {
|
|
auth.OtherError(err).ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
userID, err := auth.UserFromSessionToken(cookie.Value)
|
|
if err == ErrNoUserForSession {
|
|
auth.Logout(w)
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
return
|
|
}
|
|
if err != nil {
|
|
auth.OtherError(err).ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
if config.RequireLogged {
|
|
userPerms, err := auth.UserPermissions(userID)
|
|
if err != nil {
|
|
auth.OtherError(err).ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
userPermsMap := map[string]bool{}
|
|
for _, perm := range userPerms {
|
|
userPermsMap[perm] = true
|
|
}
|
|
|
|
// check the user has all the permissions to access the route
|
|
hasAll := true
|
|
for _, perm := range config.WithPermissions {
|
|
if _, present := userPermsMap[perm]; !present {
|
|
hasAll = false
|
|
break
|
|
}
|
|
}
|
|
|
|
if !hasAll {
|
|
auth.AuthenticationFailed.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Refresh session cookie expiration
|
|
http.SetCookie(w, &http.Cookie{
|
|
Name: SessionCookieName,
|
|
Path: "/",
|
|
Value: cookie.Value,
|
|
Expires: time.Now().Add(7 * 24 * time.Hour),
|
|
})
|
|
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|
|
|
|
func (auth *AuthService) LoggedMiddleware() func(http.Handler) http.Handler {
|
|
return auth.Middleware(&AuthMiddlewareConfig{
|
|
RequireLogged: true,
|
|
WithPermissions: []string{},
|
|
})
|
|
}
|
|
|
|
func (auth *AuthService) UserFromSession(r *http.Request) (string, error) {
|
|
cookie, err := r.Cookie(SessionCookieName)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
userID, err := auth.UserFromSessionToken(cookie.Value)
|
|
if err == ErrNoUserForSession {
|
|
return "", err
|
|
}
|
|
|
|
return userID, nil
|
|
}
|