import type * as plugins from './plugins.js'; import type { IControllerRuntimeId } from '../ts_interfaces/index.js'; import type { IControllerRuntimeConfig } from './interfaces.config.js'; export type TControllerAuthLifecycleState = 'setupRequired' | 'ready'; export type TWebAuthnCeremonyKind = 'setup' | 'authentication'; export type TWebAuthnCeremonyState = 'pending' | 'consumed'; /** * Single source of the audit event vocabulary: the persisted-document * assertion consumes this list at runtime, so a type added here is * automatically storable — the two can no longer drift apart. */ export const controllerAuditEventTypes = [ 'controller.started', 'controller.stopped', 'setup.finish', 'authentication.finish', 'authentication.logout', 'temppassword.create', 'temppassword.login', 'auth.resume', 'session.list', 'conversation.list', 'conversation.search', 'conversation.open', 'conversation.archive', 'conversation.reopen', 'session.read', 'session.auxiliary.read', 'session.model.update', 'session.mode.update', 'session.scratchpad.save', 'session.intelligence.ask', 'session.messages.page', 'session.message.read', 'session.create', 'session.delete', 'session.discard-empty', 'session.send', 'session.abort', 'session.rename', 'session.archive', 'session.yolo', 'session.builtin', 'slash.list', 'slash.execute', 'permission.reply', 'question.reply', 'project.list', 'project.create', 'project.remove', 'project.removal.retry', 'model.list', 'provider.list', 'provider.connection.list', 'provider.login.begin', 'authswitch.request', 'provider.login.read', 'provider.login.cancel', 'provider.connection.logout', 'provider.connection.activate-opencode', 'provider.connection.ratelimits.read', 'provider.model.refresh.begin', 'provider.model.refresh.read', 'command.list', 'session.command', 'settings.read', 'settings.update', 'sessiongroups.read', 'sessiongroups.update', 'terminal.list', 'terminal.create', 'terminal.rename', 'terminal.remove', 'terminal.attach', 'resource.list', 'resource.create', 'resource.rename', 'resource.attach', 'resource.detach', 'resource.start', 'resource.stop', 'resource.retire', 'resource.browser.operation', 'resource.browser.view.open', 'resource.browser.view.close', 'resource.browser.view.operate', ] as const; export type TControllerAuditEventType = (typeof controllerAuditEventTypes)[number]; export type TControllerAuditOutcome = 'attempted' | 'succeeded' | 'denied' | 'failed'; export type TAuthErrorCode = | 'not_initialized' | 'config_mismatch' | 'setup_unavailable' | 'setup_invalid' | 'setup_expired' | 'rate_limited' | 'ceremony_limit' | 'ceremony_invalid' | 'ceremony_replayed' | 'ceremony_expired' | 'origin_mismatch' | 'peer_mismatch' | 'verification_failed' | 'credential_not_found' | 'concurrent_change' | 'ambiguous_write' | 'counter_conflict' | 'invalid_project' | 'invalid_resource' | 'temp_password_invalid' | 'temp_password_limit'; export class AuthError extends Error { public readonly code: TAuthErrorCode; constructor(codeArg: TAuthErrorCode, messageArg: string, optionsArg?: ErrorOptions) { super(messageArg, optionsArg); this.name = 'AuthError'; this.code = codeArg; } } export interface IStoredPasskeyCredential { id: string; publicKey: string; counter: number; transports: plugins.simplewebauthnServer.AuthenticatorTransportFuture[]; deviceType: plugins.simplewebauthnServer.CredentialDeviceType; backedUp: boolean; createdAt: Date; counterUpdateId: string; } export interface ISetupAuthorityState { generation: string; hash: string; expiresAt: Date; } export interface IControllerAuthDocument { id: string; config: IControllerRuntimeConfig; authState: TControllerAuthLifecycleState; setup: ISetupAuthorityState | null; webauthnUserId: string; credentials: IStoredPasskeyCredential[]; } export interface IWebAuthnCeremonyDocument { id: string; kind: TWebAuthnCeremonyKind; state: TWebAuthnCeremonyState; peerId: string; challenge: string; origin: string; rpId: string; userId: string; createdAt: Date; expiresAt: Date; consumedAt?: Date; consumedNonce?: string; setupGeneration?: string; setupHash?: string; } export interface IWebAuthnSetupCeremonyDocument extends IWebAuthnCeremonyDocument { kind: 'setup'; setupGeneration: string; setupHash: string; } export interface IWebAuthnAuthenticationCeremonyDocument extends IWebAuthnCeremonyDocument { kind: 'authentication'; setupGeneration?: never; setupHash?: never; } export interface ISetupAuthority { setupCode: string; generation: string; expiresAt: Date; } /** * The conversation an audited action acted as. Normally an AGL runtime id. A browser operation * driven by the Claude Code conversation that owns an attached terminal names that conversation * instead: AGL does not manage it and has no runtime id for it, so it is recorded exactly as * BrowserRuntime qualifies it. */ export type TControllerAuditSessionId = | IControllerRuntimeId | plugins.browserRuntime.IBrowserClaudeSessionId; export interface IControllerAuditEvent { id: string; controllerId: string; timestamp: Date; type: TControllerAuditEventType; outcome: TControllerAuditOutcome; operationId?: string; peerId?: string; credentialId?: string; sessionId?: TControllerAuditSessionId; requestId?: IControllerRuntimeId; } export interface IControllerTempPasswordDocument { id: string; controllerId: string; /** base64url SHA-256 of the plaintext; the plaintext is never stored. */ secretHash: string; createdAt: Date; expiresAt: Date; } export interface IMintedTempPassword { /** Plaintext credential, shown once by the CLI and never persisted. */ password: string; credentialId: string; expiresAt: Date; } export interface IRecordAuditEventInput { timestamp?: Date; type: TControllerAuditEventType; outcome: TControllerAuditOutcome; operationId?: string; peerId?: string; credentialId?: string; sessionId?: TControllerAuditSessionId; requestId?: IControllerRuntimeId; } export interface IConsumeCeremonyOptions { ceremonyId: string; kind: TWebAuthnCeremonyKind; peerId: string; origin: string; rpId: string; userId: string; now: Date; } export interface IAuthStore { init(): Promise; close(): Promise; getRuntimeConfig(controllerPortArg: number): Promise; resolveOrCreateRuntimeConfig( runtimeConfigArg: IControllerRuntimeConfig, ): Promise; rotateSetupAuthority(): Promise; getState(): Promise; assertSetupAuthority( setupCodeArg: string, ceremonyArg?: IWebAuthnSetupCeremonyDocument, nowArg?: Date, ): Promise; countActiveCeremonies(kindArg: TWebAuthnCeremonyKind, nowArg: Date): Promise; createCeremony( ceremonyArg: IWebAuthnCeremonyDocument, ): Promise; assertPendingCeremony( optionsArg: IConsumeCeremonyOptions, ): Promise; consumeCeremony(optionsArg: IConsumeCeremonyOptions): Promise; commitFirstCredential( ceremonyArg: IWebAuthnSetupCeremonyDocument, credentialArg: IStoredPasskeyCredential, ): Promise; updateCounter( credentialIdArg: string, expectedCounterArg: number, newCounterArg: number, ): Promise; recordAuditEvent(eventArg: IRecordAuditEventInput): Promise; } export interface IWebAuthnServerImplementation { generateRegistrationOptions: typeof plugins.simplewebauthnServer.generateRegistrationOptions; verifyRegistrationResponse: typeof plugins.simplewebauthnServer.verifyRegistrationResponse; generateAuthenticationOptions: typeof plugins.simplewebauthnServer.generateAuthenticationOptions; verifyAuthenticationResponse: typeof plugins.simplewebauthnServer.verifyAuthenticationResponse; } export interface IPasskeyManagerOptions { store: IAuthStore; relyingPartyName?: string; webAuthn?: IWebAuthnServerImplementation; now?: () => Date; setupAttemptLimit?: number; setupAttemptWindowMs?: number; authenticationAttemptLimitPerPeer?: number; authenticationAttemptWindowMs?: number; maxActiveSetupCeremonies?: number; maxActiveAuthenticationCeremonies?: number; } export interface ISetupBeginOptions { peerId: string; origin: string; setupCode: string; } export interface ISetupFinishOptions extends ISetupBeginOptions { ceremonyId: string; response: plugins.simplewebauthnServer.RegistrationResponseJSON; } export interface IAuthenticationBeginOptions { peerId: string; origin: string; } export interface IAuthenticationFinishOptions extends IAuthenticationBeginOptions { ceremonyId: string; response: plugins.simplewebauthnServer.AuthenticationResponseJSON; } export interface ISetupBeginResult { ceremonyId: string; options: plugins.simplewebauthnServer.PublicKeyCredentialCreationOptionsJSON; } export interface IAuthenticationBeginResult { ceremonyId: string; options: plugins.simplewebauthnServer.PublicKeyCredentialRequestOptionsJSON; } export interface IAuthenticationResult { authenticated: true; credentialId: string; }