feat(dashboard): add MeDto, AdminDto, apiKeys + role/password helpers

Extends api.ts with the Phase 3.5 wire types (InstanceRole, Scope,
MeDto, AdminDto, ApiKeyDto, MintApiKey*) and the matching apiKeys
namespace. AdminUser in auth.ts now carries instance_role and email,
so layout/store consumers see the role without a separate fetch.

Adds two tiny lib helpers used by the upcoming profile/users pages:
RoleChip.svelte for the colored owner/admin/member pill, and
password-gen.ts for crypto.getRandomValues-backed temporary
passwords used in user-invite + reset-password reveals.

AdminUserRecord stays as a deprecated alias until /admins is
retired in a follow-up commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-05-27 08:00:06 +02:00
parent 3688c26cb4
commit df691038d7
4 changed files with 157 additions and 9 deletions

View File

@@ -8,7 +8,9 @@
import { goto } from '$app/navigation';
import { base } from '$app/paths';
import { browser } from '$app/environment';
import { clearSession, getToken, setSession, type AdminUser } from './auth';
import { clearSession, getToken, setSession, type InstanceRole } from './auth';
export type { InstanceRole };
export interface ScriptSandbox {
max_operations?: number;
@@ -232,27 +234,88 @@ function safeJson(text: string): unknown {
}
}
export interface AdminUserRecord {
export type Scope =
| 'script:read'
| 'script:write'
| 'route:write'
| 'domain:manage'
| 'log:read'
| 'app:admin'
| 'instance:admin';
export const ALL_SCOPES: readonly Scope[] = [
'script:read',
'script:write',
'route:write',
'domain:manage',
'log:read',
'app:admin',
'instance:admin'
] as const;
export function isInstanceScope(s: Scope): boolean {
return s.startsWith('instance:');
}
export interface MeDto {
id: string;
username: string;
instance_role: InstanceRole;
email: string | null;
}
export interface AdminDto {
id: string;
username: string;
is_active: boolean;
instance_role: InstanceRole;
email: string | null;
created_at: string;
last_login_at: string | null;
}
/** @deprecated use AdminDto. Kept until the /admins route is retired. */
export type AdminUserRecord = AdminDto;
export interface CreateAdminInput {
username: string;
password: string;
instance_role?: InstanceRole;
email?: string | null;
}
export interface PatchAdminInput {
username?: string;
password?: string;
is_active?: boolean;
instance_role?: InstanceRole;
email?: string | null;
}
export interface ApiKeyDto {
id: string;
prefix: string;
name: string;
scopes: Scope[];
app_id: string | null;
expires_at: string | null;
last_used_at: string | null;
created_at: string;
}
export interface MintApiKeyInput {
name: string;
scopes: Scope[];
app_id?: string | null;
expires_at?: string | null;
}
export interface MintApiKeyResponse extends ApiKeyDto {
raw_token: string;
}
interface LoginResponse {
user: AdminUser;
user: MeDto;
token: string;
expires_at: string;
}
@@ -263,7 +326,7 @@ export const api = {
version: () => adminRequest<VersionInfo>('/version'),
auth: {
login: async (username: string, password: string): Promise<AdminUser> => {
login: async (username: string, password: string): Promise<MeDto> => {
const r = await adminRequest<LoginResponse>('/api/v1/admin/auth/login', {
method: 'POST',
body: JSON.stringify({ username, password })
@@ -282,19 +345,19 @@ export const api = {
clearSession();
}
},
me: () => adminRequest<AdminUser>('/api/v1/admin/auth/me')
me: () => adminRequest<MeDto>('/api/v1/admin/auth/me')
},
admins: {
list: () => adminRequest<AdminUserRecord[]>('/api/v1/admin/admins'),
get: (id: string) => adminRequest<AdminUserRecord>(`/api/v1/admin/admins/${id}`),
list: () => adminRequest<AdminDto[]>('/api/v1/admin/admins'),
get: (id: string) => adminRequest<AdminDto>(`/api/v1/admin/admins/${id}`),
create: (input: CreateAdminInput) =>
adminRequest<AdminUserRecord>('/api/v1/admin/admins', {
adminRequest<AdminDto>('/api/v1/admin/admins', {
method: 'POST',
body: JSON.stringify(input)
}),
update: (id: string, input: PatchAdminInput) =>
adminRequest<AdminUserRecord>(`/api/v1/admin/admins/${id}`, {
adminRequest<AdminDto>(`/api/v1/admin/admins/${id}`, {
method: 'PATCH',
body: JSON.stringify(input)
}),
@@ -302,6 +365,17 @@ export const api = {
adminRequest<null>(`/api/v1/admin/admins/${id}`, { method: 'DELETE' })
},
apiKeys: {
list: () => adminRequest<ApiKeyDto[]>('/api/v1/admin/api-keys'),
mint: (input: MintApiKeyInput) =>
adminRequest<MintApiKeyResponse>('/api/v1/admin/api-keys', {
method: 'POST',
body: JSON.stringify(input)
}),
revoke: (id: string) =>
adminRequest<null>(`/api/v1/admin/api-keys/${id}`, { method: 'DELETE' })
},
routes: {
listForScript: (scriptId: string) =>
adminRequest<Route[]>(`/api/v1/admin/scripts/${scriptId}/routes`),