/** * Model provider credentials domain — tenant-scoped OAuth tokens and API keys * for model providers (Anthropic/Claude Max, OpenAI Codex, GitHub Copilot, * xAI, plus plain API-key providers). * * Tenancy is two-level: * - **user-scoped** rows (`userId` present): personal plan OAuth tokens and * personal API keys. OAuth plan tokens are ONLY ever user-scoped — they are * personal subscriptions, and sharing one would bill/rate-limit a single * account for the whole org. * - **org-scoped** rows (`userId` absent): shared API keys set by an org * admin, inherited by every member. * * Resolution order at model-call time is user > org (the caller layers server * env vars underneath as a final fallback). * * Secrets posture: credentials are stored server-side only and never returned * to the client (same as `linear_connections.access_token`). Encryption at * rest is out of scope, matching the existing posture. * * The domain also owns `oauth_login_sessions`: pending web login flows * (paste-code PKCE verifiers, device-code poll state) persisted server-side so * any replica can complete or poll a flow started on another. */ import type { AuthCredential, OAuthCredential } from '@mastra/code-sdk/auth/types'; import { FactoryStorageDomain } from '@mastra/core/storage'; import type { CollectionSchema } from '@mastra/core/storage'; import type { FactorySecretEncryption } from '../../../secret-encryption.js'; /** Owning tenant of a credential row. `userId` absent = org-scoped row. */ export interface CredentialTenant { orgId: string; userId?: string; } /** Which tenancy level a credential row lives at. */ export type CredentialScope = 'user' | 'org'; /** One stored credential, annotated with its scope. */ export interface CredentialRecord { provider: string; scope: CredentialScope; credential: AuthCredential; updatedAt: Date; } /** Result of per-caller resolution: the winning credential and its scope. */ export interface ResolvedCredential { provider: string; scope: CredentialScope; credential: AuthCredential; } /** Kind of pending web login flow a session row tracks. */ export type LoginSessionKind = 'paste-code' | 'device-code'; /** Scope the completed credential should be written to. */ export type LoginCredentialScope = 'user' | 'org'; /** One pending OAuth login flow, persisted so any replica can continue it. */ export interface LoginSessionRow { sessionId: string; orgId: string; userId: string; provider: string; kind: LoginSessionKind; /** Where the completed credential lands; absent rows default to `'user'`. */ credentialScope?: LoginCredentialScope; /** Serialized flow state (PKCE verifier, device-code pending state, ...). */ pending: Record; expiresAt: Date; /** Earliest time the next upstream poll is allowed (device-code flows). */ nextPollAt: Date | null; createdAt: Date; } export interface CreateLoginSessionInput { sessionId: string; orgId: string; userId: string; provider: string; kind: LoginSessionKind; credentialScope?: LoginCredentialScope; pending: Record; expiresAt: Date; nextPollAt?: Date | null; } /** Mirror of `AuthStorage.getApiKey()`'s expiry check. */ export declare function isOAuthCredentialExpired(credential: OAuthCredential, now?: number): boolean; export declare const MODEL_CREDENTIALS_SCHEMA: CollectionSchema; export declare const OAUTH_LOGIN_SESSIONS_SCHEMA: CollectionSchema; /** * Model-credentials storage, written once against the generic * `FactoryStorageOps` surface. `refreshOAuth()` and `claimLoginSession()` * ride `updateAtomic` so concurrent replicas serialize instead of * invalidating each other's rotating tokens / double-claiming a flow. */ export declare class ModelCredentialsStorage extends FactoryStorageDomain { #private; private readonly encryption; constructor(encryption?: FactorySecretEncryption); init(): Promise; dangerouslyClearAll(): Promise; /** Read the tenant's credential for a provider at exactly that scope. */ getCredential(tenant: CredentialTenant, provider: string): Promise; /** Upsert the tenant's credential (`created_at` is preserved on update). */ setCredential(tenant: CredentialTenant, provider: string, credential: AuthCredential): Promise; /** Delete the tenant's credential at exactly that scope. True when a row was removed. */ removeCredential(tenant: CredentialTenant, provider: string): Promise; /** All credentials visible to a user: their own rows plus the org's shared rows. */ listCredentials(orgId: string, userId: string): Promise; /** * Resolve the credential a caller should use for a provider. By default the * user's own row wins over the org's shared row; `precedence: 'org'` * (automated factory runs) checks the org's shared row first. */ resolveCredential(orgId: string, userId: string, provider: string, precedence?: CredentialScope): Promise; /** * Refresh the tenant's OAuth credential under the backend's atomic * read-modify-write so concurrent replicas don't invalidate each other's * rotating refresh tokens. The expiry is re-checked under the lock — * another replica may have refreshed already, in which case `refreshFn` is * skipped and the fresh credential is returned. Returns `undefined` when no * OAuth row exists for the tenant. */ refreshOAuth(tenant: CredentialTenant, provider: string, refreshFn: (current: OAuthCredential) => Promise): Promise; /** Persist a pending login flow started by a web route. */ createLoginSession(input: CreateLoginSessionInput): Promise; /** * Read a pending login flow. An expired session is deleted on read (TTL * cleanup) and reported as absent. */ getLoginSession(sessionId: string): Promise; /** * Atomically claim a due login session for one upstream completion/poll attempt. * Returns undefined when the session is missing, expired, owned by another * tenant/provider, or already claimed by a concurrent request. */ claimLoginSession(sessionId: string, owner: Pick): Promise; /** Update a pending flow's serialized state and/or next allowed poll time. */ touchLoginSession(sessionId: string, updates: { pending?: Record; nextPollAt?: Date | null; }): Promise; /** Remove a pending login flow (completed, cancelled, or failed). */ deleteLoginSession(sessionId: string): Promise; } //# sourceMappingURL=base.d.ts.map