mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Add apikey support to /jwt
This commit is contained in:
parent
85186a74c8
commit
a72ecdb21b
@ -41,7 +41,7 @@ func MapDbKey(key *dbc.Apikey) ApiKeyWToken {
|
||||
CreatedAt: key.CreatedAt,
|
||||
LastUsed: key.LastUsed,
|
||||
},
|
||||
Token: key.Token,
|
||||
Token: fmt.Sprintf("%s-%s", key.Name, key.Token),
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,7 +74,7 @@ func (h *Handler) CreateApiKey(c echo.Context) error {
|
||||
|
||||
dbkey, err := h.db.CreateApiKey(context.Background(), dbc.CreateApiKeyParams{
|
||||
Name: req.Name,
|
||||
Token: fmt.Sprintf("%s-%s", req.Name, base64.RawURLEncoding.EncodeToString(id)),
|
||||
Token: base64.RawURLEncoding.EncodeToString(id),
|
||||
Claims: req.Claims,
|
||||
})
|
||||
if ErrIs(err, pgerrcode.UniqueViolation) {
|
||||
|
@ -64,6 +64,37 @@ func (q *Queries) DeleteApiKey(ctx context.Context, id uuid.UUID) (Apikey, error
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getApiKey = `-- name: GetApiKey :one
|
||||
select
|
||||
pk, id, name, token, claims, created_by, created_at, last_used
|
||||
from
|
||||
apikeys
|
||||
where
|
||||
name = $1
|
||||
and token = $2
|
||||
`
|
||||
|
||||
type GetApiKeyParams struct {
|
||||
Name string `json:"name"`
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetApiKey(ctx context.Context, arg GetApiKeyParams) (Apikey, error) {
|
||||
row := q.db.QueryRow(ctx, getApiKey, arg.Name, arg.Token)
|
||||
var i Apikey
|
||||
err := row.Scan(
|
||||
&i.Pk,
|
||||
&i.Id,
|
||||
&i.Name,
|
||||
&i.Token,
|
||||
&i.Claims,
|
||||
&i.CreatedBy,
|
||||
&i.CreatedAt,
|
||||
&i.LastUsed,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const listApiKeys = `-- name: ListApiKeys :many
|
||||
select
|
||||
pk, id, name, token, claims, created_by, created_at, last_used
|
||||
@ -101,3 +132,17 @@ func (q *Queries) ListApiKeys(ctx context.Context) ([]Apikey, error) {
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const touchApiKey = `-- name: TouchApiKey :exec
|
||||
update
|
||||
apikeys
|
||||
set
|
||||
last_used = now()::timestamptz
|
||||
where
|
||||
pk = $1
|
||||
`
|
||||
|
||||
func (q *Queries) TouchApiKey(ctx context.Context, pk int32) error {
|
||||
_, err := q.db.Exec(ctx, touchApiKey, pk)
|
||||
return err
|
||||
}
|
||||
|
@ -88,6 +88,7 @@ func (q *Queries) DeleteSession(ctx context.Context, arg DeleteSessionParams) (S
|
||||
|
||||
const getUserFromToken = `-- name: GetUserFromToken :one
|
||||
select
|
||||
s.pk,
|
||||
s.id,
|
||||
s.last_used,
|
||||
u.pk, u.id, u.username, u.email, u.password, u.claims, u.created_date, u.last_seen
|
||||
@ -100,6 +101,7 @@ limit 1
|
||||
`
|
||||
|
||||
type GetUserFromTokenRow struct {
|
||||
Pk int32 `json:"pk"`
|
||||
Id uuid.UUID `json:"id"`
|
||||
LastUsed time.Time `json:"lastUsed"`
|
||||
User User `json:"user"`
|
||||
@ -109,6 +111,7 @@ func (q *Queries) GetUserFromToken(ctx context.Context, token string) (GetUserFr
|
||||
row := q.db.QueryRow(ctx, getUserFromToken, token)
|
||||
var i GetUserFromTokenRow
|
||||
err := row.Scan(
|
||||
&i.Pk,
|
||||
&i.Id,
|
||||
&i.LastUsed,
|
||||
&i.User.Pk,
|
||||
@ -169,10 +172,10 @@ update
|
||||
set
|
||||
last_used = now()::timestamptz
|
||||
where
|
||||
id = $1
|
||||
pk = $1
|
||||
`
|
||||
|
||||
func (q *Queries) TouchSession(ctx context.Context, id uuid.UUID) error {
|
||||
_, err := q.db.Exec(ctx, touchSession, id)
|
||||
func (q *Queries) TouchSession(ctx context.Context, pk int32) error {
|
||||
_, err := q.db.Exec(ctx, touchSession, pk)
|
||||
return err
|
||||
}
|
||||
|
@ -261,11 +261,11 @@ update
|
||||
set
|
||||
last_used = now()::timestamptz
|
||||
where
|
||||
id = $1
|
||||
pk = $1
|
||||
`
|
||||
|
||||
func (q *Queries) TouchUser(ctx context.Context, id uuid.UUID) error {
|
||||
_, err := q.db.Exec(ctx, touchUser, id)
|
||||
func (q *Queries) TouchUser(ctx context.Context, pk int32) error {
|
||||
_, err := q.db.Exec(ctx, touchUser, pk)
|
||||
return err
|
||||
}
|
||||
|
||||
|
58
auth/jwt.go
58
auth/jwt.go
@ -9,8 +9,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/lestrrat-go/jwx/jwk"
|
||||
"github.com/zoriya/kyoo/keibi/dbc"
|
||||
)
|
||||
|
||||
type Jwt struct {
|
||||
@ -19,7 +21,7 @@ type Jwt struct {
|
||||
}
|
||||
|
||||
// @Summary Get JWT
|
||||
// @Description Convert a session token to a short lived JWT.
|
||||
// @Description Convert a session token or an API key to a short lived JWT.
|
||||
// @Tags jwt
|
||||
// @Produce json
|
||||
// @Security Token
|
||||
@ -28,6 +30,17 @@ type Jwt struct {
|
||||
// @Header 200 {string} Authorization "Jwt (same value as the returned token)"
|
||||
// @Router /jwt [get]
|
||||
func (h *Handler) CreateJwt(c echo.Context) error {
|
||||
apikey := c.Request().Header.Get("X-Api-Key")
|
||||
if apikey != "" {
|
||||
token, err := h.createApiJwt(apikey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.JSON(http.StatusOK, Jwt{
|
||||
Token: &token,
|
||||
})
|
||||
}
|
||||
|
||||
auth := c.Request().Header.Get("Authorization")
|
||||
var jwt *string
|
||||
|
||||
@ -85,8 +98,8 @@ func (h *Handler) createJwt(token string) (string, error) {
|
||||
}
|
||||
|
||||
go func() {
|
||||
h.db.TouchSession(context.Background(), session.Id)
|
||||
h.db.TouchUser(context.Background(), session.User.Id)
|
||||
h.db.TouchSession(context.Background(), session.Pk)
|
||||
h.db.TouchUser(context.Background(), session.User.Pk)
|
||||
}()
|
||||
|
||||
claims := maps.Clone(session.User.Claims)
|
||||
@ -108,6 +121,45 @@ func (h *Handler) createJwt(token string) (string, error) {
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (h *Handler) createApiJwt(apikey string) (string, error) {
|
||||
info := strings.Split(apikey, "-")
|
||||
if len(info) != 2 {
|
||||
return "", echo.NewHTTPError(http.StatusForbidden, "Invalid api key format")
|
||||
}
|
||||
|
||||
key, err := h.db.GetApiKey(context.Background(), dbc.GetApiKeyParams{
|
||||
Name: info[0],
|
||||
Token: info[1],
|
||||
})
|
||||
if err == pgx.ErrNoRows {
|
||||
return "", echo.NewHTTPError(http.StatusForbidden, "Invalid api key")
|
||||
} else if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
go func() {
|
||||
h.db.TouchApiKey(context.Background(), key.Pk)
|
||||
}()
|
||||
|
||||
claims := maps.Clone(key.Claims)
|
||||
claims["username"] = key.Name
|
||||
claims["sub"] = key.Id
|
||||
claims["sid"] = key.Id
|
||||
claims["iss"] = h.config.PublicUrl
|
||||
claims["iat"] = &jwt.NumericDate{
|
||||
Time: time.Now().UTC(),
|
||||
}
|
||||
claims["exp"] = &jwt.NumericDate{
|
||||
Time: time.Now().UTC().Add(time.Hour),
|
||||
}
|
||||
jwt := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
|
||||
t, err := jwt.SignedString(h.config.JwtPrivateKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// only used for the swagger doc
|
||||
type JwkSet struct {
|
||||
Keys []struct {
|
||||
|
36
auth/main.go
36
auth/main.go
@ -131,24 +131,34 @@ type Handler struct {
|
||||
|
||||
func (h *Handler) TokenToJwt(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
auth := c.Request().Header.Get("Authorization")
|
||||
var jwt *string
|
||||
|
||||
if auth == "" || !strings.HasPrefix(auth, "Bearer ") {
|
||||
jwt = h.createGuestJwt()
|
||||
} else {
|
||||
token := auth[len("Bearer "):]
|
||||
// this is only used to check if it is a session token or a jwt
|
||||
_, err := base64.RawURLEncoding.DecodeString(token)
|
||||
if err != nil {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
tkn, err := h.createJwt(token)
|
||||
apikey := c.Request().Header.Get("X-Api-Key")
|
||||
if apikey != "" {
|
||||
token, err := h.createApiJwt(apikey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jwt = &tkn
|
||||
jwt = &token
|
||||
} else {
|
||||
auth := c.Request().Header.Get("Authorization")
|
||||
|
||||
if auth == "" || !strings.HasPrefix(auth, "Bearer ") {
|
||||
jwt = h.createGuestJwt()
|
||||
} else {
|
||||
token := auth[len("Bearer "):]
|
||||
// this is only used to check if it is a session token or a jwt
|
||||
_, err := base64.RawURLEncoding.DecodeString(token)
|
||||
if err != nil {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
tkn, err := h.createJwt(token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jwt = &tkn
|
||||
}
|
||||
}
|
||||
|
||||
if jwt != nil {
|
||||
|
@ -1,3 +1,20 @@
|
||||
-- name: GetApiKey :one
|
||||
select
|
||||
*
|
||||
from
|
||||
apikeys
|
||||
where
|
||||
name = $1
|
||||
and token = $2;
|
||||
|
||||
-- name: TouchApiKey :exec
|
||||
update
|
||||
apikeys
|
||||
set
|
||||
last_used = now()::timestamptz
|
||||
where
|
||||
pk = $1;
|
||||
|
||||
-- name: ListApiKeys :many
|
||||
select
|
||||
*
|
||||
|
@ -1,5 +1,6 @@
|
||||
-- name: GetUserFromToken :one
|
||||
select
|
||||
s.pk,
|
||||
s.id,
|
||||
s.last_used,
|
||||
sqlc.embed(u)
|
||||
@ -16,7 +17,7 @@ update
|
||||
set
|
||||
last_used = now()::timestamptz
|
||||
where
|
||||
id = $1;
|
||||
pk = $1;
|
||||
|
||||
-- name: GetUserSessions :many
|
||||
select
|
||||
|
@ -49,7 +49,7 @@ update
|
||||
set
|
||||
last_used = now()::timestamptz
|
||||
where
|
||||
id = $1;
|
||||
pk = $1;
|
||||
|
||||
-- name: CreateUser :one
|
||||
insert into users(username, email, password, claims)
|
||||
|
Loading…
x
Reference in New Issue
Block a user