Added some documentation

main
Antonio De Lucreziis 3 years ago
parent 3732ba245e
commit 0f22ca3437

@ -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()

@ -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
}
// 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

@ -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) {

Loading…
Cancel
Save