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 func(error) 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(err).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(err).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(err).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 }