Cleanup return codes and add docs comment for swagger

This commit is contained in:
Zoe Roux 2024-09-02 14:55:22 +02:00
parent 7b08afde06
commit 95da0184a0
No known key found for this signature in database
3 changed files with 48 additions and 15 deletions

View File

@ -16,7 +16,9 @@ import (
) )
type LoginDto struct { type LoginDto struct {
// Either the email or the username.
Login string `json:"login" validate:"required"` Login string `json:"login" validate:"required"`
// Password of the account.
Password string `json:"password" validate:"required"` Password string `json:"password" validate:"required"`
} }
@ -25,12 +27,13 @@ type LoginDto struct {
// @Tags sessions // @Tags sessions
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param device query uuid false "The device the created session will be used on" // @Param device query string false "The device the created session will be used on"
// @Param user body LoginDto false "Account informations" // @Param login body LoginDto false "Account informations"
// @Success 201 {object} dbc.Session // @Success 201 {object} dbc.Session
// @Failure 400 {object} problem.Problem "Invalid login body" // @Failure 400 {object} problem.Problem "Invalid login body"
// @Failure 400 {object} problem.Problem "Invalid password" // @Failure 403 {object} problem.Problem "Invalid password"
// @Failure 404 {object} problem.Problem "Account does not exists" // @Failure 404 {object} problem.Problem "Account does not exists"
// @Failure 422 {object} problem.Problem "User does not have a password (registered via oidc, please login via oidc)"
// @Router /sessions [post] // @Router /sessions [post]
func (h *Handler) Login(c echo.Context) error { func (h *Handler) Login(c echo.Context) error {
var req LoginDto var req LoginDto
@ -47,7 +50,7 @@ func (h *Handler) Login(c echo.Context) error {
return echo.NewHTTPError(http.StatusNotFound, "No account exists with the specified email or username.") return echo.NewHTTPError(http.StatusNotFound, "No account exists with the specified email or username.")
} }
if dbuser.Password == nil { if dbuser.Password == nil {
return echo.NewHTTPError(http.StatusBadRequest, "Can't login with password, this account was created with OIDC.") return echo.NewHTTPError(http.StatusUnprocessableEntity, "Can't login with password, this account was created with OIDC.")
} }
match, err := argon2id.ComparePasswordAndHash(req.Password, *dbuser.Password) match, err := argon2id.ComparePasswordAndHash(req.Password, *dbuser.Password)
@ -55,7 +58,7 @@ func (h *Handler) Login(c echo.Context) error {
return err return err
} }
if !match { if !match {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid password") return echo.NewHTTPError(http.StatusForbidden, "Invalid password")
} }
user := MapDbUser(&dbuser) user := MapDbUser(&dbuser)
@ -88,9 +91,20 @@ func (h *Handler) createSession(c echo.Context, user *User) error {
return c.JSON(201, session) return c.JSON(201, session)
} }
// @Summary Get JWT
// @Description Convert a session token to a short lived JWT.
// @Tags sessions
// @Accept json
// @Produce json
// @Param user body LoginDto false "Account informations"
// @Success 200 {object} dbc.Session
// @Failure 400 {object} problem.Problem "Invalid login body"
// @Failure 400 {object} problem.Problem "Invalid password"
// @Failure 404 {object} problem.Problem "Account does not exists"
// @Router /jwt [get]
func (h *Handler) CreateJwt(c echo.Context, user *User) error { func (h *Handler) CreateJwt(c echo.Context, user *User) error {
claims := maps.Clone(user.Claims) claims := maps.Clone(user.Claims)
claims["sub"] = user.ID.String() claims["sub"] = user.Id.String()
claims["iss"] = h.config.Issuer claims["iss"] = h.config.Issuer
claims["exp"] = &jwt.NumericDate{ claims["exp"] = &jwt.NumericDate{
Time: time.Now().UTC().Add(time.Hour), Time: time.Now().UTC().Add(time.Hour),

View File

@ -1,11 +1,11 @@
-- name: GetUserFromSession :one -- name: GetUserFromToken :one
select select
u.* u.*
from from
users as u users as u
left join sessions as s on u.id = s.user_id left join sessions as s on u.id = s.user_id
where where
s.id = $1 s.token = $1
limit 1; limit 1;
-- name: TouchSession :exec -- name: TouchSession :exec
@ -27,7 +27,7 @@ order by
last_used; last_used;
-- name: CreateSession :one -- name: CreateSession :one
insert into sessions(id, user_id, device) insert into sessions(token, user_id, device)
values ($1, $2, $3) values ($1, $2, $3)
returning returning
*; *;
@ -38,3 +38,9 @@ where id = $1
returning returning
*; *;
-- name: DeleteSessionByToken :one
delete from sessions
where token = $1
returning
*;

View File

@ -13,30 +13,43 @@ import (
) )
type User struct { type User struct {
ID uuid.UUID `json:"id"` // Id of the user.
Id uuid.UUID `json:"id"`
// Username of the user. Can be used as a login.
Username string `json:"username"` Username string `json:"username"`
// Email of the user. Can be used as a login.
Email string `json:"email" format:"email"` Email string `json:"email" format:"email"`
// When was this account created?
CreatedDate time.Time `json:"createdDate"` CreatedDate time.Time `json:"createdDate"`
// When was the last time this account made any authorized request?
LastSeen time.Time `json:"lastSeen"` LastSeen time.Time `json:"lastSeen"`
// List of custom claims JWT created via get /jwt will have
Claims jwt.MapClaims `json:"claims"` Claims jwt.MapClaims `json:"claims"`
// List of other login method available for this user. Access tokens wont be returned here.
Oidc map[string]OidcHandle `json:"oidc,omitempty"` Oidc map[string]OidcHandle `json:"oidc,omitempty"`
} }
type OidcHandle struct { type OidcHandle struct {
// Id of this oidc handle.
Id string `json:"id"` Id string `json:"id"`
// Username of the user on the external service.
Username string `json:"username"` Username string `json:"username"`
// Link to the profile of the user on the external service. Null if unknown or irrelevant.
ProfileUrl *string `json:"profileUrl" format:"url"` ProfileUrl *string `json:"profileUrl" format:"url"`
} }
type RegisterDto struct { type RegisterDto struct {
// Username of the new account, can't contain @ signs. Can be used for login.
Username string `json:"username" validate:"required,excludes=@"` Username string `json:"username" validate:"required,excludes=@"`
// Valid email that could be used for forgotten password requests. Can be used for login.
Email string `json:"email" validate:"required,email" format:"email"` Email string `json:"email" validate:"required,email" format:"email"`
// Password to use.
Password string `json:"password" validate:"required"` Password string `json:"password" validate:"required"`
} }
func MapDbUser(user *dbc.User) User { func MapDbUser(user *dbc.User) User {
return User{ return User{
ID: user.ID, Id: user.ID,
Username: user.Username, Username: user.Username,
Email: user.Email, Email: user.Email,
CreatedDate: user.CreatedDate, CreatedDate: user.CreatedDate,
@ -53,7 +66,7 @@ func MapDbUser(user *dbc.User) User {
// @Produce json // @Produce json
// @Param afterId query string false "used for pagination." Format(uuid) // @Param afterId query string false "used for pagination." Format(uuid)
// @Success 200 {object} User[] // @Success 200 {object} User[]
// @Failure 400 {object} problem.Problem // @Failure 400 {object} problem.Problem "Invalid after id"
// @Router /users [get] // @Router /users [get]
func (h *Handler) ListUsers(c echo.Context) error { func (h *Handler) ListUsers(c echo.Context) error {
ctx := context.Background() ctx := context.Background()