diff --git a/auth/auth.go b/auth/auth.go index eba4280..edee71a 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -1,54 +1,32 @@ package auth -import "fmt" +import ( + "fmt" -// ErrInvalidSession is thrown when an AuthenticatorService is given a missing or invalid session token -var ErrInvalidSession = fmt.Errorf(`invalid session token`) - -// User represents a user returned from AuthenticatorService -type User struct { - Username string `json:"username"` - Name string `json:"name"` - Surname string `json:"surname"` - FullName string `json:"fullName"` - Email string `json:"email"` -} - -// WithDefaultFullName is a utility that returns a copy of the given user with the full name set to the concatenation of the name and surname of the user. -func (u User) WithDefaultFullName() User { - return User{ - Username: u.Username, - Name: u.Name, - Surname: u.Surname, - Email: u.Email, - - FullName: u.Username + " " + u.Surname, - } -} + "git.phc.dm.unipi.it/phc/website/model" +) -// Session represents a session returned from AuthenticatorService -type Session struct { - Username string `json:"username"` - Token string `json:"token"` -} +// ErrInvalidSession is thrown when an AuthenticatorService is given a missing +// or invalid session token +var ErrInvalidSession = fmt.Errorf(`invalid session token`) -// AuthenticatorService can login users using a separate http service or a temporary in memory store. When a user logs in the auth service returns a session token that can be used to read and modify user properties without having to re-send the user password. (TODO: Not yet implemented, this token has to be renewed every so often otherwise it lasts just a couple of days) -type AuthenticatorService interface { +// Service is an authentication service abstraction. When a user is logged in a +// new session token is returned, this can be used to read and modify user +// properties without having to re-send the user password. (TODO: implement +// token renewal) +type Service interface { // GetUser retrieves the user info given the username - GetUser(username string) (*User, error) - + GetUser(username string) (*model.User, error) // GetUsers retrieves the full user list from the authentication service - GetUsers() ([]*User, error) - + GetUsers() ([]*model.User, error) // GetSession retrieves the user session associated to a session token - GetSession(token string) (*Session, error) - + GetSession(token string) (*model.Session, error) // Login tries to log in a user given username and password and if successful returns a new user session - Login(username, password string) (*Session, error) + Login(username, password string) (*model.Session, error) } -// UserForSession returns the user (object) linked to a session token, this is just a shortcut for calling [AuthenticatorService.GetSession] and then [AuthenticatorService.GetUser] -func UserForSession(as AuthenticatorService, token string) (*User, error) { +// UserForSession returns the user linked to the given session token, this is just a shortcut for calling [AuthenticatorService.GetSession] and then [AuthenticatorService.GetUser] +func UserForSession(as Service, token string) (*model.User, error) { session, err := as.GetSession(token) if err != nil { return nil, err @@ -62,8 +40,10 @@ func UserForSession(as AuthenticatorService, token string) (*User, error) { return user, nil } -// New create an AuthenticatorService given an "host" string, if ":memory:" then this just returns an example AuthenticatorService using the [auth.Memory] implementation, otherwise for now this defaults to [auth.LDAPAuthService] -func New(host string) AuthenticatorService { +// NewDefaultService create an AuthenticatorService given an "host" string, +// If host is ":memory:" then this uses the [auth.Memory] implementation, +// otherwise for now this defaults to [auth.LDAPAuthService] +func NewDefaultService(host string) Service { if host == ":memory:" { return exampleMemoryUsers } diff --git a/auth/ldap.go b/auth/ldap.go index 75e8c5d..7678a1a 100644 --- a/auth/ldap.go +++ b/auth/ldap.go @@ -7,6 +7,8 @@ import ( "log" "net/http" "path" + + "git.phc.dm.unipi.it/phc/website/model" ) // ldapUser represents an LDAP User, most fields are inherited from [auth.User] @@ -21,8 +23,8 @@ type ldapUser struct { } // AsUser converts an [ldapUser] to an instance of [auth.User] -func (u ldapUser) AsUser() *User { - return &User{ +func (u ldapUser) AsUser() *model.User { + return &model.User{ Username: u.Uid, Name: u.Name, Surname: u.Surname, @@ -36,6 +38,10 @@ type LDAPAuthService struct { Host string } +func NewLDAPAuthService(host string) Service { + return &LDAPAuthService{host} +} + // doGetRequest is a utility to make HTTP GET requests func (a *LDAPAuthService) doGetRequest(url string, response interface{}) error { req, err := http.NewRequest( @@ -83,7 +89,7 @@ func (a *LDAPAuthService) doPostRequest(url string, request interface{}, respons return json.NewDecoder(res.Body).Decode(response) } -func (a *LDAPAuthService) GetUser(username string) (*User, error) { +func (a *LDAPAuthService) GetUser(username string) (*model.User, error) { var user ldapUser if err := a.doGetRequest(fmt.Sprintf("/user/%s", username), &user); err != nil { return nil, err @@ -92,13 +98,13 @@ func (a *LDAPAuthService) GetUser(username string) (*User, error) { return user.AsUser(), nil } -func (a *LDAPAuthService) GetUsers() ([]*User, error) { +func (a *LDAPAuthService) GetUsers() ([]*model.User, error) { ldapUsers := []*ldapUser{} if err := a.doGetRequest("/users", &ldapUsers); err != nil { return nil, err } - users := make([]*User, len(ldapUsers)) + users := make([]*model.User, len(ldapUsers)) for i, u := range ldapUsers { users[i] = u.AsUser() } @@ -106,8 +112,8 @@ func (a *LDAPAuthService) GetUsers() ([]*User, error) { return users, nil } -func (a *LDAPAuthService) GetSession(token string) (*Session, error) { - var response Session +func (a *LDAPAuthService) GetSession(token string) (*model.Session, error) { + var response model.Session if err := a.doGetRequest(fmt.Sprintf("/session/%s", token), &response); err != nil { return nil, err } @@ -115,13 +121,13 @@ func (a *LDAPAuthService) GetSession(token string) (*Session, error) { return &response, nil } -func (a *LDAPAuthService) Login(username, password string) (*Session, error) { +func (a *LDAPAuthService) Login(username, password string) (*model.Session, error) { reqBody := map[string]interface{}{ "username": username, "password": password, } - var response Session + var response model.Session if err := a.doPostRequest("/login", reqBody, &response); err != nil { return nil, err } diff --git a/auth/memory.go b/auth/memory.go index f0e060b..b838ee3 100644 --- a/auth/memory.go +++ b/auth/memory.go @@ -3,13 +3,14 @@ package auth import ( "fmt" + "git.phc.dm.unipi.it/phc/website/model" "git.phc.dm.unipi.it/phc/website/util" ) var exampleMemoryUsers = &Memory{ Users: map[string]*memoryUser{ "aziis98": { - User: User{ + User: model.User{ Username: "aziis98", Name: "Antonio", Surname: "De Lucreziis", @@ -18,7 +19,7 @@ var exampleMemoryUsers = &Memory{ Password: "123", }, "bachoseven": { - User: User{ + User: model.User{ Username: "bachoseven", Name: "Francesco", Surname: "Minnocci", @@ -31,12 +32,12 @@ var exampleMemoryUsers = &Memory{ } type memoryUser struct { - User + model.User Password string `json:"-"` } -func (u memoryUser) AsUser() *User { - return &User{ +func (u memoryUser) AsUser() *model.User { + return &model.User{ Username: u.Username, Name: u.Name, Surname: u.Surname, @@ -50,8 +51,8 @@ type memorySession struct { Token string } -func (s memorySession) AsSession() *Session { - return &Session{ +func (s memorySession) AsSession() *model.Session { + return &model.Session{ Username: s.Username, Token: s.Token, } @@ -62,7 +63,7 @@ type Memory struct { Sessions map[string]*memorySession } -func (m *Memory) GetUser(username string) (*User, error) { +func (m *Memory) GetUser(username string) (*model.User, error) { user, ok := m.Users[username] if !ok { return nil, fmt.Errorf(`no user with that username`) @@ -71,8 +72,8 @@ func (m *Memory) GetUser(username string) (*User, error) { return user.AsUser(), nil } -func (m *Memory) GetUsers() ([]*User, error) { - users := make([]*User, len(m.Users)) +func (m *Memory) GetUsers() ([]*model.User, error) { + users := make([]*model.User, len(m.Users)) i := 0 for _, u := range m.Users { users[i] = u.AsUser() @@ -82,7 +83,7 @@ func (m *Memory) GetUsers() ([]*User, error) { return users, nil } -func (m *Memory) GetSession(token string) (*Session, error) { +func (m *Memory) GetSession(token string) (*model.Session, error) { session, ok := m.Sessions[token] if !ok { return nil, ErrInvalidSession @@ -91,7 +92,7 @@ func (m *Memory) GetSession(token string) (*Session, error) { return session.AsSession(), nil } -func (m *Memory) Login(username string, password string) (*Session, error) { +func (m *Memory) Login(username string, password string) (*model.Session, error) { user, ok := m.Users[username] if !ok { return nil, fmt.Errorf(`no user with that username`) diff --git a/main.go b/main.go index 78d66b9..87214f7 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,7 @@ import ( "git.phc.dm.unipi.it/phc/website/articles" "git.phc.dm.unipi.it/phc/website/auth" "git.phc.dm.unipi.it/phc/website/config" + "git.phc.dm.unipi.it/phc/website/model" "git.phc.dm.unipi.it/phc/website/templates" "git.phc.dm.unipi.it/phc/website/util" "github.com/gofiber/fiber/v2" @@ -17,7 +18,7 @@ import ( "github.com/gofiber/redirect/v2" ) -func UserMiddleware(as auth.AuthenticatorService) fiber.Handler { +func UserMiddleware(as auth.Service) fiber.Handler { return func(c *fiber.Ctx) error { token := c.Cookies("session-token") user, _ := auth.UserForSession(as, token) @@ -44,7 +45,7 @@ func main() { // Serve content statically from "./public", mounted on the "/public/" route app.Static("/public/", "./public") - authService := auth.New(config.AuthServiceHost) + authService := auth.NewDefaultService(config.AuthServiceHost) app.Use(UserMiddleware(authService)) // Templates & Renderer @@ -157,7 +158,7 @@ func main() { }) app.Get("/profilo", func(c *fiber.Ctx) error { - user, ok := c.Locals("user").(*auth.User) + user, ok := c.Locals("user").(*model.User) if !ok || user == nil { return fmt.Errorf(`user not logged in`) } diff --git a/model/user.go b/model/user.go new file mode 100644 index 0000000..8aee0fb --- /dev/null +++ b/model/user.go @@ -0,0 +1,30 @@ +package model + +// User represents a user returned from AuthenticatorService +type User struct { + Username string `json:"username"` + Name string `json:"name"` + Surname string `json:"surname"` + // FullName is a separate field from Name and Surname because for example + // ldap stores them all as separate fields. + FullName string `json:"fullName"` + Email string `json:"email"` +} + +// WithDefaultFullName is a utility that returns a copy of the given user with the full name set to the concatenation of the name and surname of the user. +func (u User) WithDefaultFullName() User { + return User{ + Username: u.Username, + Name: u.Name, + Surname: u.Surname, + Email: u.Email, + + FullName: u.Username + " " + u.Surname, + } +} + +// Session represents a session returned from AuthenticatorService +type Session struct { + Username string `json:"username"` + Token string `json:"token"` +}