Login technically works

This commit is contained in:
2022-03-04 01:56:15 +01:00
parent b5aecea322
commit 3aade31bd6
13 changed files with 769 additions and 130 deletions
+84 -33
View File
@@ -1,21 +1,29 @@
package auth
import (
"fmt"
"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 {
IsLogged bool
// 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 adds a session cookie to the user
Login(w http.ResponseWriter, userID string)
// 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)
@@ -25,71 +33,106 @@ type Authenticator interface {
LoggedMiddleware() func(http.Handler) http.Handler
// UserFromSession returns the userID for this cookie session token
UserFromSession(r http.Request) (string, error)
UserFromSession(r *http.Request) (string, error)
}
type AuthService struct {
// CheckUserPassword
CheckUserPassword func(userID string, password string) bool
CheckUserPassword func(userID string, password string) error
// GetUserPermissions gets the list of permissions for this user
UserPermissions func(userID string) []string
UserPermissions func(userID string) ([]string, error)
// 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)
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, userID string) {
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,
Value: auth.SessionTokenFromUser(userID),
Expires: time.Now().Add(10 * time.Minute), // TODO: Make configurable
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 && !config.IsLogged {
next.ServeHTTP(w, r)
return
}
if err != nil {
if err == http.ErrNoCookie {
if !config.RequireLogged { // Login not required
next.ServeHTTP(w, r)
return
}
auth.AuthenticationFailed.ServeHTTP(w, r)
return
}
userID, exists := auth.UserFromSessionToken(cookie.Value)
if !exists {
auth.Logout(w)
w.WriteHeader(http.StatusBadRequest)
if err != nil {
auth.OtherError(err).ServeHTTP(w, r)
return
}
if config.IsLogged {
userPerms := map[string]bool{}
for _, perm := range auth.UserPermissions(userID) {
userPerms[perm] = true
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 := userPerms[perm]; !present {
if _, present := userPermsMap[perm]; !present {
hasAll = false
break
}
@@ -101,6 +144,14 @@ func (auth *AuthService) Middleware(config *AuthMiddlewareConfig) func(http.Hand
}
}
// 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)
})
}
@@ -108,20 +159,20 @@ func (auth *AuthService) Middleware(config *AuthMiddlewareConfig) func(http.Hand
func (auth *AuthService) LoggedMiddleware() func(http.Handler) http.Handler {
return auth.Middleware(&AuthMiddlewareConfig{
IsLogged: true,
RequireLogged: true,
WithPermissions: []string{},
})
}
func (auth *AuthService) UserFromSession(r http.Request) (string, error) {
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`)
userID, err := auth.UserFromSessionToken(cookie.Value)
if err == ErrNoUserForSession {
return "", err
}
return userID, nil