feat: add API token authentication

This commit is contained in:
2026-05-19 02:46:47 +02:00
parent ccfb469299
commit 237b5f3004
10 changed files with 310 additions and 14 deletions

View File

@@ -37,6 +37,16 @@ type AnimeRelation struct {
RelationType string `json:"relation_type"`
}
type ApiToken struct {
ID string `json:"id"`
UserID string `json:"user_id"`
TokenHash string `json:"token_hash"`
Name string `json:"name"`
CreatedAt time.Time `json:"created_at"`
LastUsedAt sql.NullTime `json:"last_used_at"`
RevokedAt sql.NullTime `json:"revoked_at"`
}
type ContinueWatchingEntry struct {
ID string `json:"id"`
UserID string `json:"user_id"`

View File

@@ -10,6 +10,7 @@ import (
type Querier interface {
CountPendingAnimeFetchRetries(ctx context.Context) (int64, error)
CreateAPIToken(ctx context.Context, arg CreateAPITokenParams) (ApiToken, error)
CreateSession(ctx context.Context, arg CreateSessionParams) (Session, error)
DeleteAnimeFetchRetry(ctx context.Context, animeID int64) error
DeleteContinueWatchingEntry(ctx context.Context, arg DeleteContinueWatchingEntryParams) error
@@ -17,6 +18,7 @@ type Querier interface {
DeleteSession(ctx context.Context, id string) error
DeleteWatchListEntry(ctx context.Context, arg DeleteWatchListEntryParams) error
EnqueueAnimeFetchRetry(ctx context.Context, arg EnqueueAnimeFetchRetryParams) error
GetAPITokenByHash(ctx context.Context, tokenHash string) (ApiToken, error)
GetAnime(ctx context.Context, id int64) (Anime, error)
GetAnimeNeedingRelationSync(ctx context.Context) ([]GetAnimeNeedingRelationSyncRow, error)
GetContinueWatchingEntries(ctx context.Context, userID string) ([]GetContinueWatchingEntriesRow, error)
@@ -37,8 +39,10 @@ type Querier interface {
MarkAnimeFetchRetryFailed(ctx context.Context, arg MarkAnimeFetchRetryFailedParams) error
MarkEpisodeAvailabilityRefreshFailed(ctx context.Context, arg MarkEpisodeAvailabilityRefreshFailedParams) error
MarkRelationsSynced(ctx context.Context, id int64) error
RevokeAllAPITokensForUser(ctx context.Context, userID string) error
SaveWatchProgress(ctx context.Context, arg SaveWatchProgressParams) error
SetJikanCache(ctx context.Context, arg SetJikanCacheParams) error
TouchAPITokenLastUsedAt(ctx context.Context, id string) error
UpdateAnimeStatus(ctx context.Context, arg UpdateAnimeStatusParams) error
UpsertAnime(ctx context.Context, arg UpsertAnimeParams) (Anime, error)
UpsertAnimeRelation(ctx context.Context, arg UpsertAnimeRelationParams) error

View File

@@ -15,6 +15,26 @@ SELECT * FROM session WHERE id = ? LIMIT 1;
-- name: DeleteSession :exec
DELETE FROM session WHERE id = ?;
-- name: CreateAPIToken :one
INSERT INTO api_token (id, user_id, token_hash, name)
VALUES (?, ?, ?, ?)
RETURNING *;
-- name: GetAPITokenByHash :one
SELECT * FROM api_token
WHERE token_hash = ? AND revoked_at IS NULL
LIMIT 1;
-- name: TouchAPITokenLastUsedAt :exec
UPDATE api_token
SET last_used_at = CURRENT_TIMESTAMP
WHERE id = ?;
-- name: RevokeAllAPITokensForUser :exec
UPDATE api_token
SET revoked_at = CURRENT_TIMESTAMP
WHERE user_id = ? AND revoked_at IS NULL;
-- name: UpsertAnime :one
INSERT INTO anime (id, title_original, title_english, title_japanese, image_url, airing, duration_seconds)
VALUES (?, ?, ?, ?, ?, ?, ?)

View File

@@ -24,6 +24,39 @@ func (q *Queries) CountPendingAnimeFetchRetries(ctx context.Context) (int64, err
return count, err
}
const createAPIToken = `-- name: CreateAPIToken :one
INSERT INTO api_token (id, user_id, token_hash, name)
VALUES (?, ?, ?, ?)
RETURNING id, user_id, token_hash, name, created_at, last_used_at, revoked_at
`
type CreateAPITokenParams struct {
ID string `json:"id"`
UserID string `json:"user_id"`
TokenHash string `json:"token_hash"`
Name string `json:"name"`
}
func (q *Queries) CreateAPIToken(ctx context.Context, arg CreateAPITokenParams) (ApiToken, error) {
row := q.db.QueryRowContext(ctx, createAPIToken,
arg.ID,
arg.UserID,
arg.TokenHash,
arg.Name,
)
var i ApiToken
err := row.Scan(
&i.ID,
&i.UserID,
&i.TokenHash,
&i.Name,
&i.CreatedAt,
&i.LastUsedAt,
&i.RevokedAt,
)
return i, err
}
const createSession = `-- name: CreateSession :one
INSERT INTO session (id, user_id, expires_at)
VALUES (?, ?, ?)
@@ -128,6 +161,27 @@ func (q *Queries) EnqueueAnimeFetchRetry(ctx context.Context, arg EnqueueAnimeFe
return err
}
const getAPITokenByHash = `-- name: GetAPITokenByHash :one
SELECT id, user_id, token_hash, name, created_at, last_used_at, revoked_at FROM api_token
WHERE token_hash = ? AND revoked_at IS NULL
LIMIT 1
`
func (q *Queries) GetAPITokenByHash(ctx context.Context, tokenHash string) (ApiToken, error) {
row := q.db.QueryRowContext(ctx, getAPITokenByHash, tokenHash)
var i ApiToken
err := row.Scan(
&i.ID,
&i.UserID,
&i.TokenHash,
&i.Name,
&i.CreatedAt,
&i.LastUsedAt,
&i.RevokedAt,
)
return i, err
}
const getAnime = `-- name: GetAnime :one
SELECT id, title_original, image_url, created_at, title_english, title_japanese, airing, status, relations_synced_at, duration_seconds FROM anime WHERE id = ? LIMIT 1
`
@@ -820,6 +874,17 @@ func (q *Queries) MarkRelationsSynced(ctx context.Context, id int64) error {
return err
}
const revokeAllAPITokensForUser = `-- name: RevokeAllAPITokensForUser :exec
UPDATE api_token
SET revoked_at = CURRENT_TIMESTAMP
WHERE user_id = ? AND revoked_at IS NULL
`
func (q *Queries) RevokeAllAPITokensForUser(ctx context.Context, userID string) error {
_, err := q.db.ExecContext(ctx, revokeAllAPITokensForUser, userID)
return err
}
const saveWatchProgress = `-- name: SaveWatchProgress :exec
UPDATE watch_list_entry
SET current_episode = ?,
@@ -864,6 +929,17 @@ func (q *Queries) SetJikanCache(ctx context.Context, arg SetJikanCacheParams) er
return err
}
const touchAPITokenLastUsedAt = `-- name: TouchAPITokenLastUsedAt :exec
UPDATE api_token
SET last_used_at = CURRENT_TIMESTAMP
WHERE id = ?
`
func (q *Queries) TouchAPITokenLastUsedAt(ctx context.Context, id string) error {
_, err := q.db.ExecContext(ctx, touchAPITokenLastUsedAt, id)
return err
}
const updateAnimeStatus = `-- name: UpdateAnimeStatus :exec
UPDATE anime SET status = ? WHERE id = ?
`