auth: add recovery and account security

This commit is contained in:
2026-04-11 18:05:51 +02:00
parent 810a50c606
commit 6b83f6bde6
11 changed files with 424 additions and 48 deletions

View File

@@ -10,10 +10,10 @@ import (
)
type DBTX interface {
ExecContext(context.Context, string, ...any) (sql.Result, error)
ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
PrepareContext(context.Context, string) (*sql.Stmt, error)
QueryContext(context.Context, string, ...any) (*sql.Rows, error)
QueryRowContext(context.Context, string, ...any) *sql.Row
QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
QueryRowContext(context.Context, string, ...interface{}) *sql.Row
}
func New(db DBTX) *Queries {

View File

@@ -57,10 +57,11 @@ type Session struct {
}
type User struct {
ID string `json:"id"`
Username string `json:"username"`
PasswordHash string `json:"password_hash"`
CreatedAt time.Time `json:"created_at"`
ID string `json:"id"`
Username string `json:"username"`
PasswordHash string `json:"password_hash"`
CreatedAt time.Time `json:"created_at"`
RecoveryKeyHash string `json:"recovery_key_hash"`
}
type WatchListEntry struct {

View File

@@ -22,12 +22,14 @@ type Querier interface {
GetUpcomingSeasons(ctx context.Context, userID string) ([]GetUpcomingSeasonsRow, error)
GetUser(ctx context.Context, id string) (User, error)
GetUserByUsername(ctx context.Context, username string) (User, error)
GetUserByUsernameAndRecoveryKeyHash(ctx context.Context, arg GetUserByUsernameAndRecoveryKeyHashParams) (User, error)
GetUserWatchList(ctx context.Context, userID string) ([]GetUserWatchListRow, error)
GetWatchListEntry(ctx context.Context, arg GetWatchListEntryParams) (WatchListEntry, error)
GetWatchingAnime(ctx context.Context, userID string) ([]GetWatchingAnimeRow, error)
MarkRelationsSynced(ctx context.Context, id int64) error
SetJikanCache(ctx context.Context, arg SetJikanCacheParams) error
UpdateAnimeStatus(ctx context.Context, arg UpdateAnimeStatusParams) error
UpdateUserPasswordAndRecoveryKeyHash(ctx context.Context, arg UpdateUserPasswordAndRecoveryKeyHashParams) error
UpsertAnime(ctx context.Context, arg UpsertAnimeParams) (Anime, error)
UpsertAnimeRelation(ctx context.Context, arg UpsertAnimeRelationParams) error
UpsertWatchListEntry(ctx context.Context, arg UpsertWatchListEntryParams) (WatchListEntry, error)

View File

@@ -5,10 +5,18 @@ SELECT * FROM user WHERE id = ? LIMIT 1;
SELECT * FROM user WHERE username = ? LIMIT 1;
-- name: CreateUser :one
INSERT INTO user (id, username, password_hash)
VALUES (?, ?, ?)
INSERT INTO user (id, username, password_hash, recovery_key_hash)
VALUES (?, ?, ?, ?)
RETURNING *;
-- name: GetUserByUsernameAndRecoveryKeyHash :one
SELECT * FROM user WHERE username = ? AND recovery_key_hash = ? LIMIT 1;
-- name: UpdateUserPasswordAndRecoveryKeyHash :exec
UPDATE user
SET password_hash = ?, recovery_key_hash = ?
WHERE id = ?;
-- name: CreateSession :one
INSERT INTO session (id, user_id, expires_at)
VALUES (?, ?, ?)

View File

@@ -36,25 +36,32 @@ func (q *Queries) CreateSession(ctx context.Context, arg CreateSessionParams) (S
}
const createUser = `-- name: CreateUser :one
INSERT INTO user (id, username, password_hash)
VALUES (?, ?, ?)
RETURNING id, username, password_hash, created_at
INSERT INTO user (id, username, password_hash, recovery_key_hash)
VALUES (?, ?, ?, ?)
RETURNING id, username, password_hash, created_at, recovery_key_hash
`
type CreateUserParams struct {
ID string `json:"id"`
Username string `json:"username"`
PasswordHash string `json:"password_hash"`
ID string `json:"id"`
Username string `json:"username"`
PasswordHash string `json:"password_hash"`
RecoveryKeyHash string `json:"recovery_key_hash"`
}
func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, error) {
row := q.db.QueryRowContext(ctx, createUser, arg.ID, arg.Username, arg.PasswordHash)
row := q.db.QueryRowContext(ctx, createUser,
arg.ID,
arg.Username,
arg.PasswordHash,
arg.RecoveryKeyHash,
)
var i User
err := row.Scan(
&i.ID,
&i.Username,
&i.PasswordHash,
&i.CreatedAt,
&i.RecoveryKeyHash,
)
return i, err
}
@@ -289,7 +296,7 @@ func (q *Queries) GetUpcomingSeasons(ctx context.Context, userID string) ([]GetU
}
const getUser = `-- name: GetUser :one
SELECT id, username, password_hash, created_at FROM user WHERE id = ? LIMIT 1
SELECT id, username, password_hash, created_at, recovery_key_hash FROM user WHERE id = ? LIMIT 1
`
func (q *Queries) GetUser(ctx context.Context, id string) (User, error) {
@@ -300,12 +307,13 @@ func (q *Queries) GetUser(ctx context.Context, id string) (User, error) {
&i.Username,
&i.PasswordHash,
&i.CreatedAt,
&i.RecoveryKeyHash,
)
return i, err
}
const getUserByUsername = `-- name: GetUserByUsername :one
SELECT id, username, password_hash, created_at FROM user WHERE username = ? LIMIT 1
SELECT id, username, password_hash, created_at, recovery_key_hash FROM user WHERE username = ? LIMIT 1
`
func (q *Queries) GetUserByUsername(ctx context.Context, username string) (User, error) {
@@ -316,6 +324,29 @@ func (q *Queries) GetUserByUsername(ctx context.Context, username string) (User,
&i.Username,
&i.PasswordHash,
&i.CreatedAt,
&i.RecoveryKeyHash,
)
return i, err
}
const getUserByUsernameAndRecoveryKeyHash = `-- name: GetUserByUsernameAndRecoveryKeyHash :one
SELECT id, username, password_hash, created_at, recovery_key_hash FROM user WHERE username = ? AND recovery_key_hash = ? LIMIT 1
`
type GetUserByUsernameAndRecoveryKeyHashParams struct {
Username string `json:"username"`
RecoveryKeyHash string `json:"recovery_key_hash"`
}
func (q *Queries) GetUserByUsernameAndRecoveryKeyHash(ctx context.Context, arg GetUserByUsernameAndRecoveryKeyHashParams) (User, error) {
row := q.db.QueryRowContext(ctx, getUserByUsernameAndRecoveryKeyHash, arg.Username, arg.RecoveryKeyHash)
var i User
err := row.Scan(
&i.ID,
&i.Username,
&i.PasswordHash,
&i.CreatedAt,
&i.RecoveryKeyHash,
)
return i, err
}
@@ -523,6 +554,23 @@ func (q *Queries) UpdateAnimeStatus(ctx context.Context, arg UpdateAnimeStatusPa
return err
}
const updateUserPasswordAndRecoveryKeyHash = `-- name: UpdateUserPasswordAndRecoveryKeyHash :exec
UPDATE user
SET password_hash = ?, recovery_key_hash = ?
WHERE id = ?
`
type UpdateUserPasswordAndRecoveryKeyHashParams struct {
PasswordHash string `json:"password_hash"`
RecoveryKeyHash string `json:"recovery_key_hash"`
ID string `json:"id"`
}
func (q *Queries) UpdateUserPasswordAndRecoveryKeyHash(ctx context.Context, arg UpdateUserPasswordAndRecoveryKeyHashParams) error {
_, err := q.db.ExecContext(ctx, updateUserPasswordAndRecoveryKeyHash, arg.PasswordHash, arg.RecoveryKeyHash, arg.ID)
return err
}
const upsertAnime = `-- name: UpsertAnime :one
INSERT INTO anime (id, title_original, title_english, title_japanese, image_url, airing)
VALUES (?, ?, ?, ?, ?, ?)