feat(api): sign-in, refresh rotation and bootstrap admin (M1)
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 1m15s
ci / api / api (push) Successful in 1m31s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 47s
ci / ts / ts (packages + web) (push) Successful in 39s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 26s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 1m15s
ci / api / api (push) Successful in 1m31s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 47s
ci / ts / ts (packages + web) (push) Successful in 39s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 26s
- users, refresh_tokens and audit_log schema (migration 00002) - argon2id passwords with rehash on login, password policy, lockout with exponential back-off and a per-IP rate limit in Redis - Ed25519 JWT access tokens with kid-based key rotation; rotating refresh tokens with reuse detection (cookie for web, body for Android) - bootstrap admin on first start; must_change_credentials and must_change_password gate every other endpoint - /auth/login, /auth/refresh, /auth/logout(-all), /me, /me/password, /me/bootstrap-credentials, /me/sessions; server-info reports bootstrap_required - role policy from docs/auth-and-roles.md with table tests - problem+json errors with field errors; ru/en translations of the codes - install.sh and update.sh generate JWT_PRIVATE_KEY Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b6def39e29
commit
ed4a492dba
37 files changed
+5826
-49
No files matched your search
@@ -0,0 +1,105 @@
|
||||
// Package auth — пользователи, вход, токены, политика прав (docs/auth-and-roles.md).
|
||||
package auth
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Role — роль пользователя.
|
||||
type Role string
|
||||
|
||||
// Роли по возрастанию прав.
|
||||
const (
|
||||
RoleUser Role = "user"
|
||||
RoleModerator Role = "moderator"
|
||||
RoleAdmin Role = "admin"
|
||||
)
|
||||
|
||||
// Valid сообщает, известна ли роль.
|
||||
func (r Role) Valid() bool { return r == RoleUser || r == RoleModerator || r == RoleAdmin }
|
||||
|
||||
// ClientType — тип клиента, от него зависит доставка refresh-токена.
|
||||
type ClientType string
|
||||
|
||||
// Типы клиентов.
|
||||
const (
|
||||
ClientWeb ClientType = "web"
|
||||
ClientAndroid ClientType = "android"
|
||||
)
|
||||
|
||||
// Valid сообщает, известен ли тип клиента.
|
||||
func (c ClientType) Valid() bool { return c == ClientWeb || c == ClientAndroid }
|
||||
|
||||
// UIPrefs — настройки интерфейса пользователя (users.ui_prefs). Пустые поля — значения по умолчанию клиента.
|
||||
type UIPrefs struct {
|
||||
Theme *string `json:"theme,omitempty"`
|
||||
WaveVariant *string `json:"wave_variant,omitempty"`
|
||||
WebPlayer *string `json:"web_player,omitempty"`
|
||||
MobilePlayer *string `json:"mobile_player,omitempty"`
|
||||
Ambient *bool `json:"ambient,omitempty"`
|
||||
CrossfadeS *int `json:"crossfade_s,omitempty"`
|
||||
Normalize *bool `json:"normalize,omitempty"`
|
||||
}
|
||||
|
||||
// User — учётная запись.
|
||||
type User struct {
|
||||
ID uuid.UUID
|
||||
Username string
|
||||
DisplayName *string
|
||||
PasswordHash string
|
||||
Role Role
|
||||
Locale string
|
||||
UIPrefs UIPrefs
|
||||
MustChangePassword bool
|
||||
MustChangeCredentials bool
|
||||
IsBlocked bool
|
||||
TokenVersion int
|
||||
FailedLoginCount int
|
||||
LockedUntil *time.Time
|
||||
CreatedAt time.Time
|
||||
LastLoginAt *time.Time
|
||||
}
|
||||
|
||||
// RefreshToken — строка refresh_tokens. Сам токен в БД не хранится, только SHA-256.
|
||||
type RefreshToken struct {
|
||||
ID uuid.UUID
|
||||
UserID uuid.UUID
|
||||
FamilyID uuid.UUID
|
||||
FamilyStartedAt time.Time
|
||||
TokenHash []byte
|
||||
DeviceName *string
|
||||
ClientType ClientType
|
||||
CreatedAt time.Time
|
||||
ExpiresAt time.Time
|
||||
UsedAt *time.Time
|
||||
RevokedAt *time.Time
|
||||
}
|
||||
|
||||
// Session — активная семья refresh-токенов (одно устройство).
|
||||
type Session struct {
|
||||
ID uuid.UUID // family_id
|
||||
DeviceName *string
|
||||
ClientType ClientType
|
||||
CreatedAt time.Time // начало семьи (вход)
|
||||
LastUsedAt time.Time // последняя ротация
|
||||
}
|
||||
|
||||
// Principal — аутентифицированный пользователь запроса (из access-токена и актуальной строки users).
|
||||
type Principal struct {
|
||||
UserID uuid.UUID
|
||||
Role Role
|
||||
SessionID uuid.UUID
|
||||
MustChangePassword bool
|
||||
MustChangeCredentials bool
|
||||
}
|
||||
|
||||
// AuditEntry — запись audit_log.
|
||||
type AuditEntry struct {
|
||||
ActorID *uuid.UUID
|
||||
Action string
|
||||
TargetType string
|
||||
TargetID *uuid.UUID
|
||||
Payload map[string]any
|
||||
}
|
||||
Reference in new issue
Block a user