Add /jwt route

This commit is contained in:
Zoe Roux 2024-09-02 16:24:03 +02:00
parent 95da0184a0
commit e197062f64
No known key found for this signature in database
7 changed files with 64 additions and 18 deletions

View File

@ -4,6 +4,7 @@ import (
"context"
"crypto/rand"
"encoding/base64"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/zoriya/kyoo/keibi/dbc"
@ -13,6 +14,7 @@ type Configuration struct {
JwtSecret []byte
Issuer string
DefaultClaims jwt.MapClaims
ExpirationDelay time.Duration
}
const (

View File

@ -126,6 +126,10 @@ type Handler struct {
// @host kyoo.zoriya.dev
// @BasePath /auth
// @securityDefinitions.apiKey Token
// @in header
// @name Authorization
func main() {
e := echo.New()
e.Use(middleware.Logger())
@ -151,6 +155,7 @@ func main() {
e.GET("/users", h.ListUsers)
e.POST("/users", h.Register)
e.GET("/jwt", h.CreateJwt)
e.POST("/session", h.Login)
e.GET("/swagger/*", echoSwagger.WrapHandler)

View File

@ -7,6 +7,7 @@ import (
"encoding/base64"
"maps"
"net/http"
"strings"
"time"
"github.com/alexedwards/argon2id"
@ -17,7 +18,7 @@ import (
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"`
}
@ -82,7 +83,7 @@ func (h *Handler) createSession(c echo.Context, user *User) error {
session, err := h.db.CreateSession(ctx, dbc.CreateSessionParams{
Token: base64.StdEncoding.EncodeToString(id),
UserID: user.Id,
UserId: user.Id,
Device: device,
})
if err != nil {
@ -91,20 +92,43 @@ func (h *Handler) createSession(c echo.Context, user *User) error {
return c.JSON(201, session)
}
type Jwt struct {
// The jwt token you can use for all authorized call to either keibi or other services.
Token string `json:"token"`
}
// @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"
// @Security Token
// @Success 200 {object} Jwt
// @Failure 401 {object} problem.Problem "Missing session token"
// @Failure 403 {object} problem.Problem "Invalid session token (or expired)"
// @Router /jwt [get]
func (h *Handler) CreateJwt(c echo.Context, user *User) error {
claims := maps.Clone(user.Claims)
claims["sub"] = user.Id.String()
func (h *Handler) CreateJwt(c echo.Context) error {
auth := c.Request().Header.Get("Authorization")
if !strings.HasPrefix(auth, "Bearer ") {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing session token")
}
token := auth[len("Bearer "):]
session, err := h.db.GetUserFromToken(context.Background(), token)
if err != nil {
return echo.NewHTTPError(http.StatusForbidden, "Invalid token")
}
if session.LastUsed.Add(h.config.ExpirationDelay).Compare(time.Now().UTC()) < 0 {
return echo.NewHTTPError(http.StatusForbidden, "Token has expired")
}
go func() {
h.db.TouchSession(context.Background(), session.Id)
h.db.TouchUser(context.Background(), session.User.Id)
}()
claims := maps.Clone(session.User.Claims)
claims["sub"] = session.User.Id.String()
claims["iss"] = h.config.Issuer
claims["exp"] = &jwt.NumericDate{
Time: time.Now().UTC().Add(time.Hour),
@ -112,12 +136,12 @@ func (h *Handler) CreateJwt(c echo.Context, user *User) error {
claims["iss"] = &jwt.NumericDate{
Time: time.Now().UTC(),
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
t, err := token.SignedString(h.config.JwtSecret)
jwt := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
t, err := jwt.SignedString(h.config.JwtSecret)
if err != nil {
return err
}
return c.JSON(http.StatusOK, echo.Map{
"token": t,
return c.JSON(http.StatusOK, Jwt{
Token: t,
})
}

View File

@ -1,9 +1,9 @@
-- name: GetUserFromToken :one
select
u.*
s.id, s.last_used, sqlc.embed(u)
from
users as u
left join sessions as s on u.id = s.user_id
inner join sessions as s on u.id = s.user_id
where
s.token = $1
limit 1;

View File

@ -38,6 +38,14 @@ where
or username = sqlc.arg(login)
limit 1;
-- name: TouchUser :exec
update
users
set
last_used = now()::timestamptz
where
id = $1;
-- name: CreateUser :one
insert into users(username, email, password, claims)
values ($1, $2, $3, $4)

View File

@ -10,11 +10,18 @@ sql:
out: "dbc"
emit_pointers_for_null_types: true
emit_json_tags: true
initialisms: []
overrides:
- db_type: "timestamptz"
go_type:
import: "time"
type: "Time"
- db_type: "timestamptz"
nullable: true
go_type:
import: "time"
type: "Time"
pointer: true
- db_type: "uuid"
go_type:
import: "github.com/google/uuid"

View File

@ -49,7 +49,7 @@ type RegisterDto struct {
func MapDbUser(user *dbc.User) User {
return User{
Id: user.ID,
Id: user.Id,
Username: user.Username,
Email: user.Email,
CreatedDate: user.CreatedDate,
@ -84,7 +84,7 @@ func (h *Handler) ListUsers(c echo.Context) error {
}
users, err = h.db.GetAllUsersAfter(ctx, dbc.GetAllUsersAfterParams{
Limit: limit,
AfterID: uid,
AfterId: uid,
})
}