Remove name prefix in apikeys (#1167)

This commit is contained in:
Zoe Roux 2025-11-19 23:29:31 +01:00 committed by GitHub
parent a115c83cba
commit 18b2ae2c5f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 46 additions and 51 deletions

View File

@ -4,10 +4,9 @@ import (
"context" "context"
"crypto/rand" "crypto/rand"
"encoding/base64" "encoding/base64"
"fmt"
"maps" "maps"
"net/http" "net/http"
"strings" "slices"
"time" "time"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
@ -45,7 +44,7 @@ func MapDbKey(key *dbc.Apikey) ApiKeyWToken {
CreatedAt: key.CreatedAt, CreatedAt: key.CreatedAt,
LastUsed: key.LastUsed, LastUsed: key.LastUsed,
}, },
Token: fmt.Sprintf("%s-%s", key.Name, key.Token), Token: key.Token,
} }
} }
@ -75,7 +74,10 @@ func (h *Handler) CreateApiKey(c echo.Context) error {
return err return err
} }
if _, conflict := h.config.EnvApiKeys[req.Name]; conflict { conflict := slices.ContainsFunc(h.config.EnvApiKeys, func(k ApiKeyWToken) bool {
return k.Name == req.Name
})
if conflict {
return echo.NewHTTPError(409, "An env apikey is already defined with the same name") return echo.NewHTTPError(409, "An env apikey is already defined with the same name")
} }
@ -174,17 +176,15 @@ func (h *Handler) ListApiKey(c echo.Context) error {
} }
func (h *Handler) createApiJwt(apikey string) (string, error) { func (h *Handler) createApiJwt(apikey string) (string, error) {
info := strings.SplitN(apikey, "-", 2) var key *ApiKeyWToken
if len(info) != 2 { for _, k := range h.config.EnvApiKeys {
return "", echo.NewHTTPError(http.StatusForbidden, "Invalid api key format") if k.Token == apikey {
key = &k
break
}
} }
if key == nil {
key, fromEnv := h.config.EnvApiKeys[info[0]] dbKey, err := h.db.GetApiKey(context.Background(), apikey)
if !fromEnv {
dbKey, err := h.db.GetApiKey(context.Background(), dbc.GetApiKeyParams{
Name: info[0],
Token: info[1],
})
if err == pgx.ErrNoRows { if err == pgx.ErrNoRows {
return "", echo.NewHTTPError(http.StatusForbidden, "Invalid api key") return "", echo.NewHTTPError(http.StatusForbidden, "Invalid api key")
} else if err != nil { } else if err != nil {
@ -195,7 +195,8 @@ func (h *Handler) createApiJwt(apikey string) (string, error) {
h.db.TouchApiKey(context.Background(), dbKey.Pk) h.db.TouchApiKey(context.Background(), dbKey.Pk)
}() }()
key = MapDbKey(&dbKey) found := MapDbKey(&dbKey)
key = &found
} }
claims := maps.Clone(key.Claims) claims := maps.Clone(key.Claims)

View File

@ -12,6 +12,7 @@ import (
"fmt" "fmt"
"maps" "maps"
"os" "os"
"slices"
"strings" "strings"
"time" "time"
@ -31,7 +32,7 @@ type Configuration struct {
GuestClaims jwt.MapClaims GuestClaims jwt.MapClaims
ProtectedClaims []string ProtectedClaims []string
ExpirationDelay time.Duration ExpirationDelay time.Duration
EnvApiKeys map[string]ApiKeyWToken EnvApiKeys []ApiKeyWToken
} }
var DefaultConfig = Configuration{ var DefaultConfig = Configuration{
@ -39,7 +40,7 @@ var DefaultConfig = Configuration{
FirstUserClaims: make(jwt.MapClaims), FirstUserClaims: make(jwt.MapClaims),
ProtectedClaims: []string{"permissions"}, ProtectedClaims: []string{"permissions"},
ExpirationDelay: 30 * 24 * time.Hour, ExpirationDelay: 30 * 24 * time.Hour,
EnvApiKeys: make(map[string]ApiKeyWToken), EnvApiKeys: make([]ApiKeyWToken, 0),
} }
func LoadConfiguration(db *dbc.Queries) (*Configuration, error) { func LoadConfiguration(db *dbc.Queries) (*Configuration, error) {
@ -137,14 +138,14 @@ func LoadConfiguration(db *dbc.Queries) (*Configuration, error) {
} }
name = strings.ToLower(name) name = strings.ToLower(name)
ret.EnvApiKeys[name] = ApiKeyWToken{ ret.EnvApiKeys = append(ret.EnvApiKeys, ApiKeyWToken{
ApiKey: ApiKey{ ApiKey: ApiKey{
Id: uuid.New(), Id: uuid.New(),
Name: name, Name: name,
Claims: claims, Claims: claims,
}, },
Token: v[1], Token: v[1],
} })
} }
apikeys, err := db.ListApiKeys(context.Background()) apikeys, err := db.ListApiKeys(context.Background())
@ -152,7 +153,10 @@ func LoadConfiguration(db *dbc.Queries) (*Configuration, error) {
return nil, err return nil, err
} }
for _, key := range apikeys { for _, key := range apikeys {
if _, defined := ret.EnvApiKeys[key.Name]; defined { dup := slices.ContainsFunc(ret.EnvApiKeys, func(k ApiKeyWToken) bool {
return k.Name == key.Name
})
if dup {
return nil, fmt.Errorf( return nil, fmt.Errorf(
"an api key with the name %s is already defined in database. Can't specify a new one via env var", "an api key with the name %s is already defined in database. Can't specify a new one via env var",
key.Name, key.Name,

View File

@ -76,17 +76,11 @@ select
from from
keibi.apikeys keibi.apikeys
where where
name = $1 token = $1
and token = $2
` `
type GetApiKeyParams struct { func (q *Queries) GetApiKey(ctx context.Context, token string) (Apikey, error) {
Name string `json:"name"` row := q.db.QueryRow(ctx, getApiKey, token)
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 var i Apikey
err := row.Scan( err := row.Scan(
&i.Pk, &i.Pk,

View File

@ -4,8 +4,7 @@ select
from from
keibi.apikeys keibi.apikeys
where where
name = $1 token = $1;
and token = $2;
-- name: TouchApiKey :exec -- name: TouchApiKey :exec
update update

View File

@ -11,7 +11,7 @@ HTTP 401
POST {{host}}/keys POST {{host}}/keys
# this is created from the gh workflow file's env var # this is created from the gh workflow file's env var
X-API-KEY: hurl-1234apikey X-API-KEY: 1234apikey
{ {
"name": "dryflower", "name": "dryflower",
"claims": { "claims": {
@ -32,7 +32,7 @@ jwt: jsonpath "$.token"
# Duplicates email # Duplicates email
POST {{host}}/keys POST {{host}}/keys
X-API-KEY: hurl-1234apikey X-API-KEY: 1234apikey
{ {
"name": "dryflower", "name": "dryflower",
"claims": { "claims": {
@ -57,5 +57,5 @@ Authorization: Bearer {{jwt}}
HTTP 403 HTTP 403
DELETE {{host}}/keys/{{id}} DELETE {{host}}/keys/{{id}}
X-API-KEY: hurl-1234apikey X-API-KEY: 1234apikey
HTTP 200 HTTP 200

View File

@ -1,6 +1,6 @@
POST {{host}}/keys POST {{host}}/keys
# this is created from the gh workflow file's env var # this is created from the gh workflow file's env var
X-API-KEY: hurl-1234apikey X-API-KEY: 1234apikey
{ {
"name": "dryflower", "name": "dryflower",
"claims": { "claims": {
@ -32,5 +32,5 @@ jsonpath "$.items[0].claims.permissions" contains "apikeys.read"
# Clean api key # Clean api key
DELETE {{host}}/keys/{{id}} DELETE {{host}}/keys/{{id}}
X-API-KEY: hurl-1234apikey X-API-KEY: 1234apikey
HTTP 200 HTTP 200

View File

@ -62,13 +62,11 @@ spec:
value: "http://{{ include "kyoo.auth.fullname" . }}:4568/.well-known/jwks.json" value: "http://{{ include "kyoo.auth.fullname" . }}:4568/.well-known/jwks.json"
- name: JWT_ISSUER - name: JWT_ISSUER
value: {{ .Values.kyoo.address | quote }} value: {{ .Values.kyoo.address | quote }}
- name: HELPERVAR_APIKEY - name: KYOO_APIKEY
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
key: {{ .Values.kyoo.auth.apikeys.scanner.apikeyKey }} key: {{ .Values.kyoo.auth.apikeys.scanner.apikeyKey }}
name: {{ .Values.kyoo.auth.apikeys.scanner.existingSecret }} name: {{ .Values.kyoo.auth.apikeys.scanner.existingSecret }}
- name: KYOO_APIKEY
value: "scanner-$(HELPERVAR_APIKEY)"
- name: THEMOVIEDB_API_ACCESS_TOKEN - name: THEMOVIEDB_API_ACCESS_TOKEN
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:

View File

@ -115,7 +115,6 @@ kyoo:
extra: [] extra: []
# - name: example # - name: example
# existingSecret: bigsecret # existingSecret: bigsecret
## value of the apieky should be $name-$apikey
# apikeyKey: example_apikey # apikeyKey: example_apikey
# claims: '{"permissions": ["core.read"]}' # claims: '{"permissions": ["core.read"]}'

View File

@ -121,7 +121,7 @@ services:
# Use this env var once we use mTLS for auth # Use this env var once we use mTLS for auth
# - KYOO_URL=${KYOO_URL:-http://api:3567/api} # - KYOO_URL=${KYOO_URL:-http://api:3567/api}
- KYOO_URL=${KYOO_URL:-http://traefik:8901/api} - KYOO_URL=${KYOO_URL:-http://traefik:8901/api}
- KYOO_APIKEY=scanner-$KEIBI_APIKEY_SCANNER - KYOO_APIKEY=$KEIBI_APIKEY_SCANNER
- JWKS_URL=http://auth:4568/.well-known/jwks.json - JWKS_URL=http://auth:4568/.well-known/jwks.json
- JWT_ISSUER=${PUBLIC_URL} - JWT_ISSUER=${PUBLIC_URL}
volumes: volumes:

View File

@ -78,7 +78,7 @@ services:
# Use this env var once we use mTLS for auth # Use this env var once we use mTLS for auth
# - KYOO_URL=${KYOO_URL:-http://api:3567/api} # - KYOO_URL=${KYOO_URL:-http://api:3567/api}
- KYOO_URL=${KYOO_URL:-http://traefik:8901/api} - KYOO_URL=${KYOO_URL:-http://traefik:8901/api}
- KYOO_APIKEY=scanner-$KEIBI_APIKEY_SCANNER - KYOO_APIKEY=$KEIBI_APIKEY_SCANNER
- JWKS_URL=http://auth:4568/.well-known/jwks.json - JWKS_URL=http://auth:4568/.well-known/jwks.json
- JWT_ISSUER=${PUBLIC_URL} - JWT_ISSUER=${PUBLIC_URL}
volumes: volumes:

View File

@ -11,7 +11,7 @@ LIBRARY_IGNORE_PATTERN=".*/[dD]ownloads?/.*"
THEMOVIEDB_API_ACCESS_TOKEN="" THEMOVIEDB_API_ACCESS_TOKEN=""
KYOO_URL="http://api:3567/api" KYOO_URL="http://api:3567/api"
KYOO_APIKEY=scanner-$KEIBI_APIKEY_SCANNER KYOO_APIKEY=$KEIBI_APIKEY_SCANNER
JWKS_URL="http://auth:4568/.well-known/jwks.json" JWKS_URL="http://auth:4568/.well-known/jwks.json"
JWT_ISSUER=$PUBLIC_URL JWT_ISSUER=$PUBLIC_URL