feat(server): W2-A login gate, secrets, settings, instances, audit, engine panel API and PG-backed SSE bus
- Admin login gate (docs/design/auth-reference.md): admin_user/admin_session/login_attempt
(migration 0015), one-time setup token logged once + conditional UPDATE, argon2id via
hash-wasm (WASM; compiled binary has no --allow-ffi), sha256-stored sessions in HttpOnly
SameSite=Lax cookie umt_session (fixed 12h TTL, soft revoke), HMAC-derived double-submit
CSRF, IP (5) + account (15) failure limits per 15 min, password change revokes other
sessions, optional TOTP with replay counter, AuditAction enum.
- Unified auth middleware: cookie session or Bearer API key -> c.var.actor
{type: admin|api_key, id, name, scopes}; requireScope works for both; requireAdminSession.
- ctx.secrets: AES-256-GCM, per-secret HKDF-SHA256 key from SECRET_MASTER_KEY, AAD bound
to (id, purpose); ctx.settings: 30s cache invalidated by settings.changed events.
- Endpoints: /auth/*, /settings, /api-keys, /secrets, /audit-events, library-roots,
downloader/media-server instances (+test), path-mappings (+verify),
PUT /sites/{id}/credentials, /engine/runs|stats|cancel (read-only openworkflow schema).
- SSE: PgEventBus (sse_event table, migration 0016, LISTEN/NOTIFY umt_events, catch-up by
seq, pruning, resync); EventBusPort is now async.
- Tests: login/rate limit/revocation/CSRF/TOTP, secrets round trip and no log leakage,
admin APIs against mock qB/Emby, SSE across two Deno subprocesses.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
07db0dac5b
commit
ff230e5da7
34 files changed
+4363
-78
No files matched your search
@@ -234,6 +234,35 @@ export interface AcquisitionSlotTable {
|
||||
reserved_plan_id: string | null;
|
||||
}
|
||||
|
||||
export interface AdminSessionTable {
|
||||
id: Generated<string>;
|
||||
admin_user_id: string;
|
||||
session_hash: string;
|
||||
ip: string | null;
|
||||
user_agent: string | null;
|
||||
last_seen_at: GeneratedTimestamptz;
|
||||
expires_at: Timestamptz;
|
||||
revoked_at: Timestamptz | null;
|
||||
revoke_reason: string | null;
|
||||
created_at: GeneratedTimestamptz;
|
||||
updated_at: GeneratedTimestamptz;
|
||||
}
|
||||
|
||||
export interface AdminUserTable {
|
||||
id: Generated<string>;
|
||||
singleton: Generated<boolean>;
|
||||
username: string | null;
|
||||
password_hash: string | null;
|
||||
setup_token_hash: string | null;
|
||||
password_changed_at: Timestamptz | null;
|
||||
totp_secret_ref: string | null;
|
||||
totp_pending_secret_ref: string | null;
|
||||
totp_enabled_at: Timestamptz | null;
|
||||
totp_last_counter: number | null;
|
||||
created_at: GeneratedTimestamptz;
|
||||
updated_at: GeneratedTimestamptz;
|
||||
}
|
||||
|
||||
export interface AgentRunTable {
|
||||
id: Generated<string>;
|
||||
task_id: string | null;
|
||||
@@ -576,6 +605,15 @@ export interface LibraryRootTable {
|
||||
updated_at: GeneratedTimestamptz;
|
||||
}
|
||||
|
||||
export interface LoginAttemptTable {
|
||||
id: Generated<number>;
|
||||
at: GeneratedTimestamptz;
|
||||
ip: string;
|
||||
username: string;
|
||||
success: boolean;
|
||||
reason: string | null;
|
||||
}
|
||||
|
||||
export interface MediaAssetTable {
|
||||
id: Generated<string>;
|
||||
media_entity_id: string | null;
|
||||
@@ -923,6 +961,13 @@ export interface SourceReleaseTable {
|
||||
updated_at: GeneratedTimestamptz;
|
||||
}
|
||||
|
||||
export interface SseEventTable {
|
||||
seq: Generated<number>;
|
||||
type: string;
|
||||
at: GeneratedTimestamptz;
|
||||
payload: Generated<Json>;
|
||||
}
|
||||
|
||||
export interface SubscriptionTable {
|
||||
id: Generated<string>;
|
||||
media_entity_id: string | null;
|
||||
@@ -1019,6 +1064,8 @@ export interface TitleAliasTable {
|
||||
export interface DB {
|
||||
acquisition_plan: AcquisitionPlanTable;
|
||||
acquisition_slot: AcquisitionSlotTable;
|
||||
admin_session: AdminSessionTable;
|
||||
admin_user: AdminUserTable;
|
||||
agent_run: AgentRunTable;
|
||||
agent_run_event: AgentRunEventTable;
|
||||
api_key: ApiKeyTable;
|
||||
@@ -1037,6 +1084,7 @@ export interface DB {
|
||||
feed_source: FeedSourceTable;
|
||||
identity_revision: IdentityRevisionTable;
|
||||
library_root: LibraryRootTable;
|
||||
login_attempt: LoginAttemptTable;
|
||||
media_asset: MediaAssetTable;
|
||||
media_entity: MediaEntityTable;
|
||||
media_server_instance: MediaServerInstanceTable;
|
||||
@@ -1056,6 +1104,7 @@ export interface DB {
|
||||
site: SiteTable;
|
||||
site_account: SiteAccountTable;
|
||||
source_release: SourceReleaseTable;
|
||||
sse_event: SseEventTable;
|
||||
subscription: SubscriptionTable;
|
||||
task: TaskTable;
|
||||
tasks_fact: TasksFactTable;
|
||||
@@ -1070,6 +1119,12 @@ export type AcquisitionPlanUpdate = Updateable<AcquisitionPlanTable>;
|
||||
export type AcquisitionSlotRow = Selectable<AcquisitionSlotTable>;
|
||||
export type NewAcquisitionSlot = Insertable<AcquisitionSlotTable>;
|
||||
export type AcquisitionSlotUpdate = Updateable<AcquisitionSlotTable>;
|
||||
export type AdminSessionRow = Selectable<AdminSessionTable>;
|
||||
export type NewAdminSession = Insertable<AdminSessionTable>;
|
||||
export type AdminSessionUpdate = Updateable<AdminSessionTable>;
|
||||
export type AdminUserRow = Selectable<AdminUserTable>;
|
||||
export type NewAdminUser = Insertable<AdminUserTable>;
|
||||
export type AdminUserUpdate = Updateable<AdminUserTable>;
|
||||
export type AgentRunRow = Selectable<AgentRunTable>;
|
||||
export type NewAgentRun = Insertable<AgentRunTable>;
|
||||
export type AgentRunUpdate = Updateable<AgentRunTable>;
|
||||
@@ -1124,6 +1179,9 @@ export type IdentityRevisionUpdate = Updateable<IdentityRevisionTable>;
|
||||
export type LibraryRootRow = Selectable<LibraryRootTable>;
|
||||
export type NewLibraryRoot = Insertable<LibraryRootTable>;
|
||||
export type LibraryRootUpdate = Updateable<LibraryRootTable>;
|
||||
export type LoginAttemptRow = Selectable<LoginAttemptTable>;
|
||||
export type NewLoginAttempt = Insertable<LoginAttemptTable>;
|
||||
export type LoginAttemptUpdate = Updateable<LoginAttemptTable>;
|
||||
export type MediaAssetRow = Selectable<MediaAssetTable>;
|
||||
export type NewMediaAsset = Insertable<MediaAssetTable>;
|
||||
export type MediaAssetUpdate = Updateable<MediaAssetTable>;
|
||||
@@ -1181,6 +1239,9 @@ export type SiteAccountUpdate = Updateable<SiteAccountTable>;
|
||||
export type SourceReleaseRow = Selectable<SourceReleaseTable>;
|
||||
export type NewSourceRelease = Insertable<SourceReleaseTable>;
|
||||
export type SourceReleaseUpdate = Updateable<SourceReleaseTable>;
|
||||
export type SseEventRow = Selectable<SseEventTable>;
|
||||
export type NewSseEvent = Insertable<SseEventTable>;
|
||||
export type SseEventUpdate = Updateable<SseEventTable>;
|
||||
export type SubscriptionRow = Selectable<SubscriptionTable>;
|
||||
export type NewSubscription = Insertable<SubscriptionTable>;
|
||||
export type SubscriptionUpdate = Updateable<SubscriptionTable>;
|
||||
|
||||
Reference in new issue
Block a user