package auth import ( "fmt" "net/http" "time" ) var SessionCookieName = "session" // TODO: Make configurable type AuthMiddlewareConfig struct { IsLogged bool WithPermissions []string } type Authenticator interface { // Login adds a session cookie to the user Login(w http.ResponseWriter, userID 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) bool // GetUserPermissions gets the list of permissions for this user UserPermissions func(userID string) []string // SessionTokenFromUser returns a session token that represents this user SessionTokenFromUser func(userID string) string // UserFromSessionToken returns the corresponing user for this session token UserFromSessionToken func(session string) (string, bool) // AuthenticationFailed handles failed authentications AuthenticationFailed http.Handler } func (auth *AuthService) Login(w http.ResponseWriter, userID string) { http.SetCookie(w, &http.Cookie{ Name: SessionCookieName, Value: auth.SessionTokenFromUser(userID), Expires: time.Now().Add(10 * time.Minute), // TODO: Make configurable }) } func (auth *AuthService) Logout(w http.ResponseWriter) { http.SetCookie(w, &http.Cookie{ Name: SessionCookieName, Value: "", Expires: time.Now(), }) } 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 && !config.IsLogged { next.ServeHTTP(w, r) return } if err != nil { auth.AuthenticationFailed.ServeHTTP(w, r) return } userID, exists := auth.UserFromSessionToken(cookie.Value) if !exists { auth.Logout(w) w.WriteHeader(http.StatusBadRequest) return } if config.IsLogged { userPerms := map[string]bool{} for _, perm := range auth.UserPermissions(userID) { userPerms[perm] = true } // check the user has all the permissions to access the route hasAll := true for _, perm := range config.WithPermissions { if _, present := userPerms[perm]; !present { hasAll = false break } } if !hasAll { auth.AuthenticationFailed.ServeHTTP(w, r) return } } next.ServeHTTP(w, r) }) } } func (auth *AuthService) LoggedMiddleware() func(http.Handler) http.Handler { return auth.Middleware(&AuthMiddlewareConfig{ IsLogged: true, WithPermissions: []string{}, }) } func (auth *AuthService) UserFromSession(r http.Request) (string, error) { cookie, err := r.Cookie(SessionCookieName) if err != nil { return "", err } userID, exists := auth.UserFromSessionToken(cookie.Value) if !exists { return "", fmt.Errorf(`no user for this session token`) } return userID, nil }