SPB Git

Repository Public

dto-auth.ts 3.8 KB 100 lines typescript

1/**2 * Auth / notification / audit DTOs of the browser API. They are NOT part of `@aos/shared`: the3 * wire shapes are byte-for-byte those of Claude Remote, so the copied auth pages keep working.4 * The web workstream imports this file by relative path (or copies it): it only depends on zod5 * and on `Branding` / `Id` from `@aos/shared`.6 */7import { z } from 'zod';8import { Branding, Id } from '@aos/shared';910const iso = z.string();1112/** The single brand of this product (`GET /auth/status` → `branding`). */13export const BRANDING: z.infer<typeof Branding> = { agent: 'aos', productName: 'Agent OS', agentName: 'Agent' };14export const PRODUCT_NAME = BRANDING.productName;1516// ---------- auth ----------17export const AuthStatus = z.object({18  setupRequired: z.boolean(), // no administrator exists yet -> setup flow with one-time token19  authenticated: z.boolean(),20  user: z.object({ id: Id, username: z.string() }).nullable(),21  methods: z.object({ password: z.boolean(), passkeys: z.number(), totp: z.boolean() }),22  csrfToken: z.string().nullable(), // required as X-CSRF-Token on every mutating request23  sessionExpiresAt: iso.nullable(),24  branding: Branding.optional(),25});26export type AuthStatus = z.infer<typeof AuthStatus>;2728export const SetupRequest = z.object({29  setupToken: z.string().min(16),30  username: z.string().min(3).max(32).regex(/^[a-z0-9_.-]+$/i),31  password: z.string().min(12).max(256),32});33export type SetupRequest = z.infer<typeof SetupRequest>;34export const LoginPasswordRequest = z.object({35  username: z.string().max(64),36  password: z.string().max(256),37  totp: z.string().regex(/^\d{6}$/).optional(),38});39export type LoginPasswordRequest = z.infer<typeof LoginPasswordRequest>;40/** Password OK but second factor still needed. */41export const LoginStep = z.object({42  ok: z.boolean(),43  next: z.enum(['done', 'totp', 'passkey', 'enroll_second_factor']),44  retryAfterSec: z.number().optional(),45});46export type LoginStep = z.infer<typeof LoginStep>;47export const TotpEnrollStart = z.object({ secret: z.string(), otpauthUrl: z.string() });48export type TotpEnrollStart = z.infer<typeof TotpEnrollStart>;49export const TotpEnrollFinish = z.object({ code: z.string().regex(/^\d{6}$/) });50export type TotpEnrollFinish = z.infer<typeof TotpEnrollFinish>;51export const Device = z.object({52  id: Id,53  kind: z.enum(['passkey', 'session']),54  label: z.string(),55  createdAt: iso,56  lastUsedAt: iso.nullable(),57  current: z.boolean(),58  userAgent: z.string().nullable(),59  ip: z.string().nullable(),60});61export type Device = z.infer<typeof Device>;6263// ---------- notifications / audit / errors ----------64/** Agent OS kinds (the Claude Remote kinds made no sense here); the rest of the shape is unchanged. */65export const NotificationKind = z.enum(['wait.approval', 'wait.question', 'mission.finished', 'system']);66export type NotificationKind = z.infer<typeof NotificationKind>;67export const Notification = z.object({68  id: Id,69  kind: NotificationKind,70  title: z.string(),71  body: z.string(), // generic by default; detail level is a user setting72  link: z.string().nullable(),73  readAt: iso.nullable(),74  createdAt: iso,75});76export type Notification = z.infer<typeof Notification>;77export const AuditEvent = z.object({78  id: z.string(),79  at: iso,80  actor: z.string(),81  action: z.string(),82  target: z.string().nullable(),83  ip: z.string().nullable(),84  ok: z.boolean(),85  detail: z.record(z.string(), z.unknown()),86});87export type AuditEvent = z.infer<typeof AuditEvent>;8889export const ApiError = z.object({90  error: z.object({ code: z.string(), message: z.string(), details: z.unknown().optional() }),91});92export type ApiError = z.infer<typeof ApiError>;9394/** WebSocket close codes used by `/api/v1/events`. */95export const WS_CLOSE = {96  UNAUTHORIZED: 4401,97  FORBIDDEN: 4403,98  BACKPRESSURE: 4429,99} as const;100
Files main
Menu

Theme