Added some documentation
This commit is contained in:
+40
-29
@@ -1,18 +1,21 @@
|
||||
// This package provides a utility for managing session cookies.
|
||||
//
|
||||
// The main struct is "AuthService" that provides a pluggable system for login and logout of users, creating and storing their session tokens and middlewares for accepting only logged users or ones with some specified permissions.
|
||||
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
|
||||
|
||||
// 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
|
||||
@@ -21,52 +24,52 @@ type AuthMiddlewareConfig struct {
|
||||
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)
|
||||
// // 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
|
||||
// // 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)
|
||||
}
|
||||
// // RequestUser returns the userID for this cookie session token
|
||||
// RequestUser(r *http.Request) (string, error)
|
||||
// }
|
||||
|
||||
var _ Authenticator = &AuthService{}
|
||||
// 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
|
||||
// CheckUserPassword is called to login a user and create a corresponding session, see also "SessionTokenFromUser"
|
||||
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 returns a (new) 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) {
|
||||
// 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 {
|
||||
if err := auth.CheckUserPassword(userID, password); err != nil {
|
||||
auth.AuthenticationFailed(err).ServeHTTP(w, r)
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
token, err := auth.SessionTokenFromUser(userID)
|
||||
if err != nil {
|
||||
auth.OtherError(err).ServeHTTP(w, r)
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
@@ -76,9 +79,10 @@ func (auth *AuthService) Login(w http.ResponseWriter, r *http.Request, userID, p
|
||||
Expires: time.Now().Add(7 * 24 * time.Hour), // TODO: Make configurable
|
||||
})
|
||||
|
||||
httputil.WriteJSON(w, "ok")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Logout clears the session cookie from a request effectivly logging out the user for future requests
|
||||
func (auth *AuthService) Logout(w http.ResponseWriter) {
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: SessionCookieName,
|
||||
@@ -86,11 +90,9 @@ func (auth *AuthService) Logout(w http.ResponseWriter) {
|
||||
Value: "",
|
||||
Expires: time.Now(),
|
||||
})
|
||||
|
||||
httputil.WriteJSON(w, "ok")
|
||||
}
|
||||
|
||||
// Middleware checks if the user is logged or not and if it has all fo 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 *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) {
|
||||
@@ -160,7 +162,11 @@ func (auth *AuthService) Middleware(config *AuthMiddlewareConfig) func(http.Hand
|
||||
}
|
||||
}
|
||||
|
||||
// LoggedMiddleware is a shortcut for "Middleware(*AuthMiddlewareConfig)" that checks if a user is logged, no extra permissions are checked
|
||||
// LoggedMiddleware is a shortcut for
|
||||
//
|
||||
// Middleware(*AuthMiddlewareConfig)
|
||||
//
|
||||
// that checks if a user is logged, no extra permissions are checked
|
||||
func (auth *AuthService) LoggedMiddleware() func(http.Handler) http.Handler {
|
||||
return auth.Middleware(&AuthMiddlewareConfig{
|
||||
RequireLogged: true,
|
||||
@@ -168,7 +174,12 @@ func (auth *AuthService) LoggedMiddleware() func(http.Handler) http.Handler {
|
||||
})
|
||||
}
|
||||
|
||||
// RequestUser retrives the "userID" from the given request based on the cookie session token
|
||||
// RequestUser retrives the "userID" from the given request based on the cookie session token.
|
||||
// When generics arrive this will become something like
|
||||
//
|
||||
// func (auth *AuthService[U]) RequestUser(r *http.Request) *U
|
||||
//
|
||||
// and will just return nil if no user is present.
|
||||
func (auth *AuthService) RequestUser(r *http.Request) (string, error) {
|
||||
cookie, err := r.Cookie(SessionCookieName)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Loads ".env" file and provides environment variables as global values.
|
||||
package config
|
||||
|
||||
import (
|
||||
@@ -8,11 +9,16 @@ import (
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
// Mode represents the MODE environment variable
|
||||
var Mode string
|
||||
|
||||
// Host represents the HOST environment variable
|
||||
var Host string
|
||||
|
||||
// Url represents the URL environment variable
|
||||
var Url string
|
||||
|
||||
// Email represents the EMAIL environment variable
|
||||
var Email string
|
||||
|
||||
func loadEnv(target *string, name, defaultValue string) {
|
||||
@@ -25,6 +31,7 @@ func loadEnv(target *string, name, defaultValue string) {
|
||||
log.Printf("%s = %v", name, *target)
|
||||
}
|
||||
|
||||
// Load calls "godotenv.Load()" and sets all the above exported variables
|
||||
func Load() {
|
||||
godotenv.Load()
|
||||
|
||||
|
||||
+46
-1
@@ -1,3 +1,6 @@
|
||||
// This package contains the database interface and an in memory implementation.
|
||||
//
|
||||
// For the in-memory implementation see the "NewInMemoryStore()" function.
|
||||
package db
|
||||
|
||||
import (
|
||||
@@ -5,16 +8,29 @@ import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ErrAlreadyExists is the error thrown from Store functions when an entity already exists.
|
||||
var ErrAlreadyExists = errors.New(`object already exists in database`)
|
||||
|
||||
// ErrDoesntExist is the error thrown from Store functions when an entity doesn't exist.
|
||||
var ErrDoesntExist = errors.New(`object doesn't exist in database`)
|
||||
|
||||
// Entities
|
||||
|
||||
// type FrontendUser struct {
|
||||
// Anonymous bool `json:"anonymous,omitempty"`
|
||||
// ID string `json:"id,omitempty"`
|
||||
// Permissions []string `json:"permissions,omitempty"`
|
||||
// }
|
||||
|
||||
// User represents a user in the database.
|
||||
type User struct {
|
||||
ID string `json:"id"`
|
||||
Permissions []string `json:"permissions"`
|
||||
|
||||
// passwordSaltHash string
|
||||
}
|
||||
|
||||
// HasPermission checks if the user has a specific permission.
|
||||
func (user User) HasPermission(neededPerm string) bool {
|
||||
for _, perm := range user.Permissions {
|
||||
if perm == neededPerm {
|
||||
@@ -25,6 +41,7 @@ func (user User) HasPermission(neededPerm string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Room represents a room in the database, a room has an id, a name and a collection of seatIDs of this room.
|
||||
type Room struct {
|
||||
ID string `json:"id"`
|
||||
SeatIDs []string `json:"seatIds"`
|
||||
@@ -32,6 +49,7 @@ type Room struct {
|
||||
// gridRows, gridCols int
|
||||
}
|
||||
|
||||
// Seat represents a seat in the database, it belongs to a single room and can be free or occupied by some user (referenced by userID).
|
||||
type Seat struct {
|
||||
ID string `json:"id"`
|
||||
RoomID string `json:"roomId"`
|
||||
@@ -42,8 +60,26 @@ type Seat struct {
|
||||
// x, y, w, h int
|
||||
}
|
||||
|
||||
// Database Interfaces
|
||||
// type FrontendSeat struct {
|
||||
// ID string `json:"id"`
|
||||
// RoomID string `json:"roomId"`
|
||||
|
||||
// // OccupiedBy is an empty list or a singleton of a userID
|
||||
// OccupiedBy *FrontendUser `json:"occupiedBy"`
|
||||
|
||||
// DiagramRect struct {
|
||||
// X int `json:"x"`
|
||||
// Y int `json:"y"`
|
||||
// Width int `json:"width"`
|
||||
// Height int `json:"height"`
|
||||
// } `json:"diagramRect"`
|
||||
// }
|
||||
|
||||
//
|
||||
// Database Interfaces
|
||||
//
|
||||
|
||||
// Store is the main interface for interacting with database implementations, for now the only implementation is "memDB".
|
||||
type Store interface {
|
||||
CreateUser(user *User) error
|
||||
CreateRoom(room *Room) error
|
||||
@@ -70,12 +106,17 @@ type Store interface {
|
||||
|
||||
// TODO: Create an SQLite implementation
|
||||
|
||||
// memDB is the first Store implementation used for testing.
|
||||
type memDB struct {
|
||||
// FIXME: Giusto per la cronaca fare le modifiche in questo modo alle mappe non è per niente thread safe, servirebbe come minimo usare un mutex per quando si scrive su una di queste variabili
|
||||
// mutex *sync.Mutex
|
||||
|
||||
users map[string]*User
|
||||
rooms map[string]*Room
|
||||
seats map[string]*Seat
|
||||
}
|
||||
|
||||
// NewInMemoryStore creates an instance of memDB hidden behind the Store interface.
|
||||
func NewInMemoryStore() Store {
|
||||
db := &memDB{
|
||||
make(map[string]*User),
|
||||
@@ -115,6 +156,10 @@ func NewInMemoryStore() Store {
|
||||
return db
|
||||
}
|
||||
|
||||
//
|
||||
// memDB Implementation
|
||||
//
|
||||
|
||||
func (db *memDB) CreateUser(user *User) error {
|
||||
if _, present := db.users[user.ID]; present {
|
||||
return ErrAlreadyExists
|
||||
|
||||
+8
-1
@@ -115,12 +115,19 @@ func (server *Server) setupRoutes() {
|
||||
return
|
||||
}
|
||||
|
||||
auth.Login(w, r, credentials.Username, credentials.Password)
|
||||
err := auth.Login(w, r, credentials.Username, credentials.Password)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.WriteJSON(w, "ok")
|
||||
})
|
||||
|
||||
api.With(auth.LoggedMiddleware()).
|
||||
Post("/logout", func(w http.ResponseWriter, r *http.Request) {
|
||||
auth.Logout(w)
|
||||
httputil.WriteJSON(w, "ok")
|
||||
})
|
||||
|
||||
api.Get("/user", func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Reference in New Issue
Block a user