@aithos/sdk 0.1.0-alpha.4 → 0.1.0-alpha.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +211 -7
  2. package/dist/src/apps.d.ts +155 -0
  3. package/dist/src/apps.js +288 -0
  4. package/dist/src/assets.d.ts +207 -0
  5. package/dist/src/assets.js +533 -0
  6. package/dist/src/auth-api.d.ts +138 -0
  7. package/dist/src/auth-api.js +168 -0
  8. package/dist/src/auth.d.ts +536 -119
  9. package/dist/src/auth.js +1207 -152
  10. package/dist/src/compute.d.ts +251 -9
  11. package/dist/src/compute.js +293 -16
  12. package/dist/src/data-schema-contacts-v1.d.ts +14 -0
  13. package/dist/src/data-schema-contacts-v1.js +28 -0
  14. package/dist/src/data.d.ts +153 -0
  15. package/dist/src/data.js +670 -0
  16. package/dist/src/endpoints.d.ts +9 -0
  17. package/dist/src/endpoints.js +5 -0
  18. package/dist/src/ethos.d.ts +202 -1
  19. package/dist/src/ethos.js +821 -16
  20. package/dist/src/index.d.ts +18 -6
  21. package/dist/src/index.js +39 -6
  22. package/dist/src/internal/delegate-bundle.d.ts +18 -0
  23. package/dist/src/internal/delegate-bundle.js +94 -0
  24. package/dist/src/internal/delegate-state.d.ts +45 -0
  25. package/dist/src/internal/delegate-state.js +120 -0
  26. package/dist/src/internal/envelope.d.ts +77 -0
  27. package/dist/src/internal/envelope.js +154 -0
  28. package/dist/src/internal/owner-signers.d.ts +78 -0
  29. package/dist/src/internal/owner-signers.js +179 -0
  30. package/dist/src/internal/protocol-client-bridge.d.ts +8 -0
  31. package/dist/src/internal/protocol-client-bridge.js +20 -0
  32. package/dist/src/internal/recovery-file.d.ts +29 -0
  33. package/dist/src/internal/recovery-file.js +98 -0
  34. package/dist/src/internal/signer.d.ts +59 -0
  35. package/dist/src/internal/signer.js +86 -0
  36. package/dist/src/key-store.d.ts +128 -0
  37. package/dist/src/key-store.js +244 -0
  38. package/dist/src/mandates.d.ts +163 -1
  39. package/dist/src/mandates.js +286 -8
  40. package/dist/src/react/AithosAsset.d.ts +66 -0
  41. package/dist/src/react/AithosAsset.js +67 -0
  42. package/dist/src/react/context.d.ts +29 -0
  43. package/dist/src/react/context.js +31 -0
  44. package/dist/src/react/index.d.ts +28 -0
  45. package/dist/src/react/index.js +30 -0
  46. package/dist/src/react/use-aithos-asset.d.ts +39 -0
  47. package/dist/src/react/use-aithos-asset.js +118 -0
  48. package/dist/src/sdk.d.ts +46 -3
  49. package/dist/src/sdk.js +49 -23
  50. package/dist/src/wallet.d.ts +4 -6
  51. package/dist/src/wallet.js +18 -8
  52. package/dist/src/web.d.ts +279 -0
  53. package/dist/src/web.js +186 -0
  54. package/dist/test/auth-j3.test.d.ts +2 -0
  55. package/dist/test/auth-j3.test.js +391 -0
  56. package/dist/test/compute-delegate-path.test.d.ts +2 -0
  57. package/dist/test/compute-delegate-path.test.js +183 -0
  58. package/dist/test/compute.test.js +26 -11
  59. package/dist/test/endpoints.test.js +20 -1
  60. package/dist/test/envelope.test.d.ts +2 -0
  61. package/dist/test/envelope.test.js +318 -0
  62. package/dist/test/ethos-first-edition.test.d.ts +2 -0
  63. package/dist/test/ethos-first-edition.test.js +248 -0
  64. package/dist/test/ethos.test.d.ts +2 -0
  65. package/dist/test/ethos.test.js +219 -0
  66. package/dist/test/key-store.test.d.ts +2 -0
  67. package/dist/test/key-store.test.js +161 -0
  68. package/dist/test/mandates-compute.test.d.ts +2 -0
  69. package/dist/test/mandates-compute.test.js +256 -0
  70. package/dist/test/mandates.test.d.ts +2 -0
  71. package/dist/test/mandates.test.js +93 -0
  72. package/dist/test/sdk.test.js +70 -30
  73. package/dist/test/signer.test.d.ts +2 -0
  74. package/dist/test/signer.test.js +117 -0
  75. package/dist/test/signup-bootstrap.test.d.ts +2 -0
  76. package/dist/test/signup-bootstrap.test.js +311 -0
  77. package/dist/test/wallet.test.js +20 -9
  78. package/dist/test/web.test.d.ts +2 -0
  79. package/dist/test/web.test.js +270 -0
  80. package/package.json +18 -3
@@ -1,197 +1,614 @@
1
1
  import { type AithosSessionStore } from "./session-store.js";
2
+ import { type AithosKeyStore } from "./key-store.js";
3
+ import { DelegateActor } from "./internal/delegate-state.js";
4
+ import { type SignedEnvelope } from "./internal/envelope.js";
5
+ import { OwnerSigners } from "./internal/owner-signers.js";
2
6
  /** Default URL of the Aithos auth backend. */
3
7
  export declare const DEFAULT_AUTH_BASE_URL = "https://auth.aithos.be";
4
- /**
5
- * Construction options for {@link AithosAuth}.
6
- */
8
+ /** Default URL of the Aithos primitives API (publish_identity, publish_ethos_edition, etc.). */
9
+ export declare const DEFAULT_API_BASE_URL = "https://api.aithos.be";
7
10
  export interface AithosAuthConfig {
8
- /**
9
- * Base URL of the Aithos auth backend. Defaults to
10
- * {@link DEFAULT_AUTH_BASE_URL}. Override for staging or self-hosted
11
- * deployments.
12
- */
13
11
  readonly authBaseUrl?: string;
14
12
  /**
15
- * Optional `fetch` implementation. Defaults to `globalThis.fetch`. Used
16
- * by tests to inject a mock without monkeypatching globals.
13
+ * Base URL of the Aithos primitives API (`api.aithos.be`). Used by
14
+ * {@link AithosAuth.signUp} to bootstrap the user's Ethos via
15
+ * `aithos.publish_identity` after the auth account is created. Override
16
+ * for staging or self-hosted deployments. Defaults to
17
+ * {@link DEFAULT_API_BASE_URL}.
17
18
  */
19
+ readonly apiBaseUrl?: string;
18
20
  readonly fetch?: typeof fetch;
19
- /**
20
- * Optional `window`-like object. Defaults to `globalThis.window` when
21
- * available. Provided so node-side tests can assert redirect URLs without
22
- * shimming jsdom.
23
- */
24
21
  readonly window?: Pick<Window, "location" | "history">;
22
+ /** Pluggable JWT-session storage. Defaults to {@link defaultSessionStore}. */
23
+ readonly sessionStore?: AithosSessionStore;
24
+ /** Pluggable key persistence. Defaults to {@link defaultKeyStore}. */
25
+ readonly keyStore?: AithosKeyStore;
25
26
  /**
26
- * Pluggable session storage. Defaults to {@link sessionStorageStore}
27
- * in browser environments, {@link noopStore} elsewhere. See
28
- * `./session-store.ts` for built-in alternatives or to roll your own.
27
+ * Public client key issued by Aithos for browser callers
28
+ * (`pk_<env>_<…>`). When set, the custodial endpoints (`signUpCustodial`,
29
+ * `verifyEmail`, `resendVerificationEmail`) authenticate as this app
30
+ * by default — the caller no longer has to repeat the key on every
31
+ * call. The corresponding `allowed_origins` allowlist must include the
32
+ * current page's origin.
33
+ *
34
+ * Safe to ship in the browser bundle: it grants nothing beyond what
35
+ * a visitor of the app can already do, is gated by Origin on every
36
+ * call, and is rate-limited per IP by the backend.
37
+ *
38
+ * If your app authenticates with a SECRET Bearer API key from a
39
+ * backend instead, leave this unset and pass `apiKey` per call.
29
40
  */
30
- readonly sessionStore?: AithosSessionStore;
41
+ readonly publicKey?: string;
31
42
  }
32
43
  /**
33
- * Active Aithos session. Persisted in the configured session store and
34
- * surfaced by {@link AithosAuth.getCurrentSession}.
35
- *
36
- * Wire-compatible with the auth Lambda's `SsoExchangeResponse` and
37
- * register/verify payloads ; field names are kept snake_case to match
38
- * the backend.
44
+ * Active Aithos session. Returned by JWT-backed entry points
45
+ * (`signIn`, `signUp`, `handleCallback`). Recovery-file and mandate
46
+ * sign-ins do NOT return an `AithosSession` — they yield the lighter
47
+ * {@link OwnerInfo} / {@link DelegateInfo}.
39
48
  */
40
49
  export interface AithosSession {
41
- /** HS256 JWT — send in `Authorization: Bearer <session>` to auth/* and
42
- * app endpoints that consume it. */
43
50
  readonly session: string;
44
- /** JWT expiry, Unix seconds. */
45
51
  readonly exp: number;
46
- /** Aithos DID — `did:aithos:z…`. Stable across all the user's devices. */
47
52
  readonly did: string;
48
- /** User-visible handle (rendered as `@handle`). */
49
53
  readonly handle: string;
50
- /** Encrypted vault blob, base64. Empty string on first Google sign-in. */
51
54
  readonly blob_b64: string;
52
- /** AES-GCM nonce for the blob, base64 (12 bytes). Empty on first Google sign-in. */
53
55
  readonly blob_nonce_b64: string;
54
- /** Monotonic blob version. 0 on first Google sign-in. */
55
56
  readonly blob_version: number;
56
- /** 32-byte vault key, base64. Decrypts {@link blob_b64} via AES-GCM-256.
57
- * Returned by Google SSO ; absent (empty string) for password sign-in
58
- * where the key stays in browser memory only. */
59
57
  readonly enc_key_b64: string;
60
- /** True the first time this user signs in (Google flow only). */
61
58
  readonly is_first_login: boolean;
62
59
  }
63
- /** Options for {@link AithosAuth.signInWithGoogle}. */
60
+ /**
61
+ * Public information about the loaded owner identity. Available after
62
+ * any owner-side sign-in (password, Google, recovery), regardless of
63
+ * whether a JWT is also present.
64
+ */
65
+ export interface OwnerInfo {
66
+ readonly did: string;
67
+ readonly handle: string;
68
+ readonly displayName: string;
69
+ }
70
+ /**
71
+ * Public information about a delegate session held by the SDK. Returned
72
+ * by `importMandate` and `getDelegates`.
73
+ */
74
+ export interface DelegateInfo {
75
+ readonly mandateId: string;
76
+ readonly subjectDid: string;
77
+ readonly granteeId: string;
78
+ readonly scopes: readonly string[];
79
+ /** ISO-8601, or null when the mandate has no `not_after`. */
80
+ readonly expiresAt: string | null;
81
+ readonly label?: string;
82
+ }
64
83
  export interface SignInWithGoogleOptions {
65
84
  /**
66
- * Opaque deep-link state preserved across the OAuth round-trip and
67
- * surfaced back to the app via `?app_state=…` on the callback URL. Use
68
- * to remember "the user clicked sign-in from /settings/billing" so you
69
- * can restore that route after the redirect chain.
70
- *
71
- * Maximum 1024 characters.
85
+ * Opaque state the consumer app wants to recover after the OAuth
86
+ * round-trip (e.g. a deep-link to resume on). Echoed back as
87
+ * `?app_state=` on the final redirect.
72
88
  */
73
89
  readonly appState?: string;
90
+ /**
91
+ * App id registered in the Aithos `aithos-auth-apps` table. When set
92
+ * together with {@link returnTo}, the auth backend redirects the
93
+ * browser to {@link returnTo} (post Google + Aithos sign-in) instead
94
+ * of the legacy hard-coded `app.aithos.be/auth/callback`.
95
+ *
96
+ * The pair is required together: the backend rejects half-presence
97
+ * with `sso_app_redirect_pair_required`. Use it for any consumer app
98
+ * other than the canonical `app.aithos.be` (typically your own
99
+ * domain in prod, `http://localhost:<port>/auth/callback` in dev).
100
+ */
101
+ readonly appId?: string;
102
+ /**
103
+ * Where the auth backend should 302 the browser back to after a
104
+ * successful Google sign-in. MUST be on the app's
105
+ * `allowed_redirect_uris` allowlist (registered with Aithos out of
106
+ * band; see {@link appId}). Exact-match — wildcards rejected.
107
+ */
108
+ readonly returnTo?: string;
74
109
  }
75
- /** Options for {@link AithosAuth.signIn}. */
76
110
  export interface SignInInput {
77
111
  readonly email: string;
78
112
  readonly password: string;
79
113
  }
80
- /** Options for {@link AithosAuth.signUp}. */
81
114
  export interface SignUpInput {
82
115
  readonly email: string;
83
116
  readonly password: string;
84
- /** Aithos handle (the @-name). 1–63 alphanumeric chars + `_`/`-`. */
85
117
  readonly handle: string;
86
- /** Optional human-readable name. Defaults to the handle. */
87
118
  readonly displayName?: string;
88
119
  }
89
- /** Result of {@link AithosAuth.signUp}. */
90
120
  export interface SignUpResult {
91
121
  readonly session: AithosSession;
92
- /**
93
- * Recovery file containing the user's seed material, plaintext at the
94
- * V1 spec ; the app should offer this to the user as a download. Lose
95
- * this file AND forget the password = lose the ethos.
96
- *
97
- * Format : JSON, content-type `application/json`. Filename suggestion
98
- * exposed as {@link recoveryFilename} for direct use with `<a download>`.
99
- */
100
122
  readonly recoveryFile: Blob;
101
- /** Suggested filename for {@link recoveryFile}. */
102
123
  readonly recoveryFilename: string;
103
124
  }
104
125
  /**
105
- * Authenticator for the Aithos identity service. One instance per app
106
- * is the recommended pattern (the constructor is cheap).
126
+ * Input to {@link AithosAuth.completeSsoFirstLogin}. The handle is
127
+ * required (the auth backend pre-generated one from the user's email
128
+ * local-part, available on the session payload — we re-confirm it
129
+ * here so the user can edit before commit).
130
+ */
131
+ export interface CompleteSsoFirstLoginInput {
132
+ readonly handle: string;
133
+ readonly displayName?: string;
134
+ }
135
+ /**
136
+ * Result of {@link AithosAuth.completeSsoFirstLogin}. Returns a recovery
137
+ * file just like signUp — even though the user authenticated via Google,
138
+ * the freshly-generated Ed25519 seeds are the only material that can
139
+ * sign Aithos artifacts; without the recovery file, losing access to
140
+ * the Google account means losing the ethos forever.
141
+ */
142
+ export interface CompleteSsoFirstLoginResult {
143
+ readonly session: AithosSession;
144
+ readonly recoveryFile: Blob;
145
+ readonly recoveryFilename: string;
146
+ }
147
+ export interface SignInWithRecoveryInput {
148
+ /** Recovery file as a Blob (browser File input) or already-decoded JSON string. */
149
+ readonly file: Blob | string;
150
+ }
151
+ export interface ImportMandateInput {
152
+ /** Delegate bundle as a Blob or already-decoded JSON string. */
153
+ readonly bundle: Blob | string;
154
+ }
155
+ /**
156
+ * Input to {@link AithosAuth.signUpCustodial}.
157
+ *
158
+ * The caller authenticates as an app via ONE of:
159
+ * - `apiKey` : server-only secret Bearer. Pass from your backend
160
+ * only — never ship it in browser code.
161
+ * - `publicKey` : browser-safe public client key. Safe to embed in
162
+ * the bundle; the backend gates it by Origin and
163
+ * rate-limits by IP.
107
164
  *
108
- * The class is **stateful** in one specific way : it owns a session store
109
- * that gets written on every successful auth call and read by
110
- * {@link getCurrentSession}. Pass a custom store at construction time
111
- * if you need different persistence (localStorage, IndexedDB, no-op).
165
+ * If you set `publicKey` on the {@link AithosAuth} constructor, omit
166
+ * both fields here the default credential is used.
167
+ *
168
+ * The `password` is always required and is chosen by the user (sign-up
169
+ * no longer auto-generates one). The created account starts as
170
+ * **pending** (`email_verified=false`); the user must click the
171
+ * confirmation link sent by SES before {@link signInCustodial} works.
112
172
  */
173
+ export interface CustodialSignUpInput {
174
+ /** Server-only Bearer secret. Mutually exclusive with `publicKey`. */
175
+ readonly apiKey?: string;
176
+ /** Browser-safe public client key. Mutually exclusive with `apiKey`.
177
+ * Overrides the constructor's default `publicKey` when provided. */
178
+ readonly publicKey?: string;
179
+ /** Email address of the new user. Will receive the verification mail. */
180
+ readonly email: string;
181
+ /** Raw password the user chose. ≥ 10 chars, mix of letters with
182
+ * ≥ 1 digit or symbol. Enforced server-side. */
183
+ readonly password: string;
184
+ /** Optional display name. Capped at 200 chars by the backend. */
185
+ readonly displayName?: string;
186
+ /** Optional handle hint. Backend may sanitise or replace. */
187
+ readonly handleHint?: string;
188
+ }
189
+ /**
190
+ * Result of {@link AithosAuth.signUpCustodial}. Always carries
191
+ * `status: "pending_verification"` — the user must click the link in
192
+ * their inbox before sign-in works. If `mailSent` is false the row
193
+ * exists in DDB anyway; trigger {@link AithosAuth.resendVerificationEmail}
194
+ * to retry the SES send.
195
+ */
196
+ export interface CustodialSignUpResult {
197
+ readonly status: "pending_verification";
198
+ readonly email: string;
199
+ readonly mailSent: boolean;
200
+ readonly mailMessageId?: string;
201
+ }
202
+ /** Input to {@link AithosAuth.verifyEmail}. Both fields come straight
203
+ * out of the `?email=&token=` query string of the confirmation URL. */
204
+ export interface VerifyEmailInput {
205
+ readonly email: string;
206
+ readonly token: string;
207
+ }
208
+ /**
209
+ * Result of {@link AithosAuth.verifyEmail}. Discriminated by `status`.
210
+ *
211
+ * - `"signed_in"` (magic-link mode): the user has been authenticated
212
+ * in this call. A JWT session is persisted to the session store and
213
+ * the local keystore is hydrated with the unwrapped seed bundle.
214
+ * The caller can navigate the user straight to a logged-in area.
215
+ * - `"already_verified"`: the verification link had already been
216
+ * consumed on a previous click. No session is minted (the token is
217
+ * spent). The caller should route the user to the sign-in form.
218
+ */
219
+ export type VerifyEmailResult = {
220
+ readonly status: "signed_in";
221
+ readonly session: AithosSession;
222
+ readonly passwordMustChange: false;
223
+ } | {
224
+ readonly status: "already_verified";
225
+ readonly email: string;
226
+ };
227
+ /** Input to {@link AithosAuth.resendVerificationEmail}. The `email` is
228
+ * required; credential overrides follow the same rules as
229
+ * {@link CustodialSignUpInput}. */
230
+ export interface ResendVerificationInput {
231
+ readonly email: string;
232
+ readonly apiKey?: string;
233
+ readonly publicKey?: string;
234
+ }
235
+ export interface CustodialSignInInput {
236
+ readonly email: string;
237
+ readonly password: string;
238
+ }
239
+ /**
240
+ * Active custodial session. Same JWT-backed shape as {@link AithosSession}
241
+ * but adds a `passwordMustChange` flag the UI can honour to nudge the
242
+ * user toward a `requestPasswordReset` on first login.
243
+ */
244
+ export interface CustodialSignInResult {
245
+ readonly session: AithosSession;
246
+ readonly passwordMustChange: boolean;
247
+ }
248
+ export interface RequestPasswordResetInput {
249
+ readonly email: string;
250
+ }
251
+ /**
252
+ * Input to {@link AithosAuth.applyPasswordReset}. Finalises a password
253
+ * reset started by {@link AithosAuth.requestPasswordReset}. The `email`
254
+ * and `token` come straight from the magic-link URL that landed in the
255
+ * user's inbox (`?email=…&token=…`); the `newPassword` is what the user
256
+ * just typed in the reset page.
257
+ */
258
+ export interface ApplyPasswordResetInput {
259
+ /** Email address whose password is being reset. */
260
+ readonly email: string;
261
+ /** Raw reset token extracted from the magic-link URL query string. */
262
+ readonly token: string;
263
+ /** New password — must satisfy the backend's policy (≥ 10 chars). */
264
+ readonly newPassword: string;
265
+ }
266
+ /**
267
+ * Result of {@link AithosAuth.applyPasswordReset}. Carries a fresh JWT
268
+ * session so the UI can either redirect to a "you're now signed in"
269
+ * landing or prompt the user to sign in explicitly with their new
270
+ * credentials — same {@link CustodialSignInResult} shape as a normal
271
+ * sign-in.
272
+ *
273
+ * Note: unlike {@link signInCustodial}, this DOES NOT hydrate the local
274
+ * keystore. The reset path on the auth Lambda re-wraps the seed bundle
275
+ * with KMS but doesn't return it (the user just typed a password — they
276
+ * still need to sign in once to materialise the seeds locally). The
277
+ * {@link AithosSession} returned here lets the app store the JWT and
278
+ * call {@link signInCustodial} to complete hydration.
279
+ */
280
+ export interface ApplyPasswordResetResult {
281
+ readonly session: AithosSession;
282
+ }
113
283
  export declare class AithosAuth {
114
- /** Resolved auth base URL with a trailing slash trimmed. */
284
+ #private;
115
285
  readonly authBaseUrl: string;
116
- private readonly fetchImpl;
117
- private readonly win;
118
- private readonly store;
286
+ readonly apiBaseUrl: string;
119
287
  constructor(config?: AithosAuthConfig);
120
288
  /**
121
- * Sign in to an existing Aithos account. Two-round-trip flow under the
122
- * hood :
289
+ * Reload signing material and JWT session from the configured stores.
290
+ * Must be called once at app boot before relying on
291
+ * {@link getCurrentSession} / {@link getOwnerInfo} / {@link canSignAsOwner}
292
+ * — until then they reflect only what's been done in-memory in the
293
+ * current tab.
123
294
  *
124
- * 1. POST /auth/login/challenge server returns the salts + KDF params
125
- * 2. derive auth_key from password + salt (Argon2id)
126
- * 3. POST /auth/login/verify server checks auth_key, returns JWT + blob
295
+ * Strict consistency: if the JWT and the stored owner disagree about
296
+ * who's signed in, both are wiped and the user re-auths. JWT-less
297
+ * owner state (loaded from keyStore but no JWT) is a valid resumed
298
+ * state — the user signed in via recovery or imported a mandate at
299
+ * some earlier moment and never went through the JWT flow.
300
+ */
301
+ resume(): Promise<void>;
302
+ /** JWT-backed session. Null when signed in via recovery / mandate / not at all. */
303
+ getCurrentSession(): AithosSession | null;
304
+ /** Loaded owner identity. Independent of JWT presence. */
305
+ getOwnerInfo(): OwnerInfo | null;
306
+ getDelegates(): readonly DelegateInfo[];
307
+ canSignAsOwner(): boolean;
308
+ /**
309
+ * Sign an envelope (spec §11.2) as the active owner, to authenticate
310
+ * a call to a third-party Aithos-aware backend.
311
+ *
312
+ * Same primitive that SDK namespaces (`sdk.data`, `sdk.ethos`,
313
+ * `sdk.mandates`, ...) use internally to sign their own writes to
314
+ * `api.aithos.be`. Exposed here so apps can sign envelopes for their
315
+ * own backends — any service that verifies a `SignedEnvelope` per
316
+ * spec §11.2 (typically using `@aithos/protocol-core/envelope`'s
317
+ * `verifyEnvelope`) accepts the resulting object.
318
+ *
319
+ * The envelope binds the signature to `(iss, aud, method,
320
+ * params_hash, nonce, iat, exp)`, so it cannot be replayed against a
321
+ * different endpoint, method, or payload, and expires after
322
+ * `ttlSeconds` (default 60s, server-side typically caps at 300s).
127
323
  *
128
- * The returned `enc_key_b64` is empty by design : password sign-in
129
- * doesn't release the vault key over the wire (it's derived locally
130
- * but discarded after the call). Apps that need the seeds — most
131
- * apps don't — should use the (forthcoming) `loadEthos` helper
132
- * separately with the password still in hand.
324
+ * Usage:
133
325
  *
134
- * Persists the session in the configured store before returning.
326
+ * ```ts
327
+ * const envelope = await sdk.auth.signEnvelope({
328
+ * aud: "https://api.example.com/v1/widgets",
329
+ * method: "myapp.widgets.create",
330
+ * params: { name: "Widget #1" },
331
+ * });
332
+ * await fetch("https://api.example.com/v1/widgets", {
333
+ * method: "POST",
334
+ * headers: { "content-type": "application/json" },
335
+ * body: JSON.stringify({ ...payload, _envelope: envelope }),
336
+ * });
337
+ * ```
338
+ *
339
+ * @throws {AithosSDKError} `auth_not_signed_in` if no owner identity
340
+ * is loaded (call `signIn` / `signUp` / `signInCustodial` first).
341
+ * @throws {AithosSDKError} `auth_invalid_sphere` if `sphere` is not
342
+ * one of `"root" | "public" | "circle" | "self"`.
135
343
  */
136
- signIn(input: SignInInput): Promise<AithosSession>;
344
+ signEnvelope(args: {
345
+ /**
346
+ * Absolute URL of the target endpoint (scheme + host + path, no
347
+ * query, no fragment). The receiving server rejects the envelope if
348
+ * `aud` does not match the actual request URL.
349
+ */
350
+ readonly aud: string;
351
+ /** Fully-qualified JSON-RPC method name. */
352
+ readonly method: string;
353
+ /**
354
+ * Tool payload — what `params_hash` commits to. Will be
355
+ * JCS-canonicalized (RFC 8785 subset) before hashing, so JS object
356
+ * key order does not affect the result.
357
+ */
358
+ readonly params: unknown;
359
+ /**
360
+ * Which of the owner's four sphere keys signs. Default: `"public"`,
361
+ * which matches what SDK namespaces use for everyday writes.
362
+ * Choose `"root"`, `"circle"`, or `"self"` only if the receiving
363
+ * server specifically expects one of those (rare).
364
+ */
365
+ readonly sphere?: "root" | "public" | "circle" | "self";
366
+ /**
367
+ * Envelope lifetime in seconds. Default 60. Aithos servers cap
368
+ * at 300; third-party servers may apply their own cap.
369
+ */
370
+ readonly ttlSeconds?: number;
371
+ }): Promise<SignedEnvelope>;
372
+ canSignAsDelegateFor(did: string): boolean;
137
373
  /**
138
- * Create a new Aithos account end-to-end :
374
+ * Internal accessor used by sibling SDK namespaces (compute, wallet,
375
+ * ethos) when they need to sign on behalf of the owner. Returns null
376
+ * if no owner is loaded.
377
+ *
378
+ * @internal
379
+ */
380
+ _getOwnerSigners(): OwnerSigners | null;
381
+ /**
382
+ * Internal accessor — looks up an active delegate by mandate id.
383
+ * @internal
384
+ */
385
+ _getDelegateActor(mandateId: string): DelegateActor | undefined;
386
+ /**
387
+ * Internal accessor — finds the first active delegate whose subject
388
+ * matches `did`. Used by `sdk.ethos.of(did)` when the user holds a
389
+ * mandate for that subject.
390
+ * @internal
391
+ */
392
+ _findDelegateForSubject(did: string): DelegateActor | undefined;
393
+ /**
394
+ * Sign in with email + password, dispatching automatically between
395
+ * the legacy zero-knowledge flow ({@link signIn}) and the custodial
396
+ * flow ({@link signInCustodial}) based on which mode the account
397
+ * was provisioned with.
398
+ *
399
+ * Use this in apps that want a single sign-in form for users who
400
+ * may have been created under either mode (e.g. an app that's
401
+ * migrating from zk to custodial — pre-existing users stay zk
402
+ * forever, new ones go custodial, the SDK figures it out).
403
+ *
404
+ * Strategy: try {@link signInCustodial} first (the modern path).
405
+ * If the backend reports `auth_invalid_credentials` — which it
406
+ * uniformly returns for "wrong password", "unknown user", AND
407
+ * "user exists but not in custodial mode" (anti-enum) — fall
408
+ * back to {@link signIn} (zk).
139
409
  *
140
- * 1. Generate a fresh `BrowserIdentity` (4 Ed25519/X25519 seeds)
141
- * 2. Build the recovery file (plaintext JSON, the user must save it)
142
- * 3. Derive auth_key + enc_key from the password (Argon2id, fresh salts)
143
- * 4. Encrypt the seeds in a vault blob (AES-GCM-256)
144
- * 5. POST /auth/register with everything JWT
145
- * 6. Persist the session and return it + the recovery Blob
410
+ * Other failure modes from the custodial path are NOT swallowed:
411
+ * - `auth_email_not_verified` propagate (user is custodial but
412
+ * hasn't clicked the confirmation link yet; the app should
413
+ * surface a "resend mail" CTA rather than retrying as zk,
414
+ * which would also fail and mask the real cause)
415
+ * - server / network errors propagate (don't double the
416
+ * incident by retrying through the other flow)
146
417
  *
147
- * The seeds are NOT published as an Aithos ethos here : the user's
148
- * profile on `app.aithos.be` won't appear until they (or another app)
149
- * publishes their first edition. This matches `aithos/app`'s design,
150
- * where the vault is the source of truth for keys and the published
151
- * edition is a separate concern.
418
+ * Latency profile:
419
+ * - Pure custodial (success or wrong pwd) : 1 round-trip
420
+ * - Pure zk (any outcome) : 1 custodial probe + 2 zk
421
+ * - Unknown email : same as zk worst case
422
+ *
423
+ * Anti-enum note: timing slightly leaks the mode (custodial path is
424
+ * faster than zk). Acceptable for V1 — rate limiting + strong
425
+ * passwords are the real defenses. A future strict-anti-enum mode
426
+ * could race both paths in parallel and accept the 2x backend load.
152
427
  */
428
+ signInAuto(input: SignInInput): Promise<AithosSession>;
429
+ signIn(input: SignInInput): Promise<AithosSession>;
153
430
  signUp(input: SignUpInput): Promise<SignUpResult>;
154
431
  /**
155
- * Redirect the browser to Google's OAuth consent screen. Must be called
156
- * synchronously in response to a user gesture (button click) most
157
- * browsers block top-level navigation triggered from idle code.
432
+ * Sign in by uploading a recovery file. Hydrates the owner signers
433
+ * locally no JWT is obtained on this path because the recovery
434
+ * file alone doesn't authenticate against the auth backend (no
435
+ * password, no Google session). Apps that need compute/wallet
436
+ * access should follow up with an email+password sign-in or with
437
+ * Google SSO.
158
438
  *
159
- * Does not return : navigation tears the JS context down. The `never`
160
- * return type tells callers any code after the call is unreachable.
439
+ * The recovery file is ALWAYS the file produced by `signUp` (or the
440
+ * equivalent one emitted by `protocol-client`'s `runOnboarding`).
441
+ * Both shapes are accepted.
442
+ */
443
+ signInWithRecovery(input: SignInWithRecoveryInput): Promise<OwnerInfo>;
444
+ /**
445
+ * Import a delegate bundle (`.aithos-delegate.json`). Works in any
446
+ * state: with no owner loaded (delegate-only session), or alongside
447
+ * an existing owner (the user holds mandates for other people's
448
+ * ethoses while also being an owner themselves).
161
449
  */
450
+ importMandate(input: ImportMandateInput): Promise<DelegateInfo>;
451
+ removeMandate(mandateId: string): Promise<void>;
162
452
  signInWithGoogle(opts?: SignInWithGoogleOptions): never;
163
453
  /**
164
- * Inspect the current URL for an `aithos_code` query parameter. If it's
165
- * present, exchange it at the backend, persist the session, and return
166
- * it. The query params are stripped from the URL via
167
- * `history.replaceState` so a page refresh doesn't replay the redeem
168
- * (which would 410 anyway).
169
- *
170
- * Returns `null` when there's no code in the URL safe to call on every
171
- * page load. Throws {@link AithosSDKError} on backend errors or when
172
- * the URL carries `aithos_error=…`.
454
+ * Public entrypoint dedupes concurrent calls (React StrictMode).
455
+ * The first call kicks off the actual exchange; subsequent calls
456
+ * before that promise resolves return the SAME promise so they all
457
+ * receive the same `AithosSession | null`. Otherwise StrictMode's
458
+ * second invocation would race against the URL clean done by the
459
+ * first call and resolve to `null`, robbing the AuthCallback page
460
+ * of the session it actually obtained.
173
461
  */
174
462
  handleCallback(): Promise<AithosSession | null>;
463
+ exchange(aithosCode: string): Promise<AithosSession>;
175
464
  /**
176
- * Programmatically redeem an `aithos_code` for a session. `handleCallback`
177
- * calls this for you ; expose it directly for callers that already pulled
178
- * the code out of the URL via their own router.
465
+ * Finish the first-time Google SSO bootstrap. After
466
+ * `signInWithGoogle()` + `handleCallback()`, a brand-new SSO user has
467
+ * a session JWT and an `enc_key` released by the auth backend, but
468
+ * NO Aithos identity yet (no Ed25519 seeds, no published did.json,
469
+ * no blob in the auth vault). This method closes that gap:
470
+ *
471
+ * 1. Generates a fresh {@link BrowserIdentity} client-side (4
472
+ * Ed25519 keypairs, derived DID).
473
+ * 2. Calls `aithos.publish_identity` on api.aithos.be so reads
474
+ * and writes against the Aithos primitives have an ethos to
475
+ * anchor to.
476
+ * 3. AES-GCM-encrypts the seeds with the session's `enc_key`,
477
+ * PUTs the result to `/auth/blob`. From now on, every Google
478
+ * sign-in for this user will receive the encrypted blob and
479
+ * hydrate locally.
480
+ * 4. Hydrates `ownerSigners` + `keyStore` so `canSignAsOwner()`
481
+ * flips to true.
482
+ * 5. Returns a recovery-file Blob — the only material that can
483
+ * restore this ethos if Google access is lost.
484
+ *
485
+ * Preconditions:
486
+ * - `getCurrentSession()` returns a non-null session (caller went
487
+ * through `handleCallback()` already).
488
+ * - The session's `blob_version` is 0 (i.e. no blob yet).
489
+ * - The session's `enc_key_b64` is non-empty.
179
490
  *
180
- * Note : this method does NOT persist the session — it's the lower-level
181
- * primitive. Use `handleCallback` for the full pipe.
491
+ * Throws `AithosSDKError("auth_sso_no_pending_first_login", …)` if
492
+ * preconditions don't hold (e.g. blob_version > 0 means the user has
493
+ * already completed setup; nothing to do).
182
494
  */
183
- exchange(aithosCode: string): Promise<AithosSession>;
495
+ completeSsoFirstLogin(input: CompleteSsoFirstLoginInput): Promise<CompleteSsoFirstLoginResult>;
184
496
  /**
185
- * Read the active session from the configured store. Returns null if
186
- * the user is signed out, or if the JWT has expired (the store
187
- * auto-evicts expired entries — see ./session-store.ts).
497
+ * Provision a custodial-mode account on behalf of a registered app.
498
+ *
499
+ * Two integration patterns:
500
+ * - **Frontend-only** apps : set `publicKey` on the constructor
501
+ * (or on this call). Safe to ship in browser bundles — the
502
+ * backend gates each request by Origin + IP rate limit.
503
+ * - **Backend-fronted** apps : the backend passes `apiKey` (secret
504
+ * Bearer); the browser never sees the credential.
505
+ *
506
+ * The created account is in a *pending* state — sign-in stays blocked
507
+ * until the user clicks the confirmation link sent to their inbox.
508
+ * Call {@link verifyEmail} from the page mounted on
509
+ * `app.verify_base_url` to consume the token; afterwards
510
+ * {@link signInCustodial} works.
511
+ *
512
+ * Errors map to `AithosSDKError` codes:
513
+ * - `auth_missing_api_key` (no credential provided)
514
+ * - `auth_invalid_api_key` (Bearer rejected by backend)
515
+ * - `auth_invalid_public_key` (public key rejected by backend)
516
+ * - `auth_api_key_revoked` / `auth_public_key_revoked`
517
+ * - `auth_origin_not_allowed` (public key + Origin not in allowlist)
518
+ * - `auth_password_too_weak` (400 — server-side strength check)
519
+ * - `auth_email_exists` (409 — email already registered)
520
+ * - `auth_email_invalid` (400 — bad email format)
521
+ * - `auth_mail_send_failed` (502 — DDB row exists but SES failed)
522
+ * - `auth_custodial_signup_failed` (catch-all)
188
523
  */
189
- getCurrentSession(): AithosSession | null;
524
+ signUpCustodial(input: CustodialSignUpInput): Promise<CustodialSignUpResult>;
190
525
  /**
191
- * Stateless sign-out the Aithos backend doesn't track sessions, so
192
- * there's nothing to revoke server-side ; this method clears the
193
- * configured session store and resolves.
526
+ * Magic-link auto-signin: consume the verification token from the
527
+ * confirmation link, KMS-unwrap the seed bundle server-side, and
528
+ * hydrate the local session + keystore in one round-trip.
529
+ *
530
+ * Outcome depends on the link's state:
531
+ * - First click on a fresh link → returns
532
+ * `{ status: "signed_in", session, … }`. The session store is
533
+ * populated, the owner signers are loaded — the user is signed
534
+ * in. The caller should navigate them to a logged-in route.
535
+ * - Click of an already-consumed link → returns
536
+ * `{ status: "already_verified", email }`. No session is minted;
537
+ * the user must sign in via {@link signInCustodial}.
538
+ *
539
+ * Mount this on the page declared as `verify_base_url` in your app's
540
+ * registration. Read `email` + `token` from `window.location.search`,
541
+ * call this, branch on `result.status`.
542
+ *
543
+ * Throws `auth_token_invalid_or_expired` if the token is wrong or
544
+ * past its 1h TTL — surface a "request a fresh link" CTA in that case.
545
+ */
546
+ verifyEmail(input: VerifyEmailInput): Promise<VerifyEmailResult>;
547
+ /**
548
+ * Re-send the verification mail for a pending account. Use when the
549
+ * user reports never having received the welcome mail, or when their
550
+ * verification token expired (24h TTL).
551
+ *
552
+ * The backend is anti-enumeration (always 200) and rate-limited
553
+ * 1/h/account, so it's safe to call even when the state of `email`
554
+ * is unknown. Accepts the same credential families as
555
+ * {@link signUpCustodial}; falls back to the constructor's
556
+ * `publicKey` when neither override is set.
557
+ */
558
+ resendVerificationEmail(input: ResendVerificationInput): Promise<void>;
559
+ /**
560
+ * Authenticate a custodial-mode user with email + password. Single
561
+ * round-trip: returns a fresh JWT session AND hydrates the local
562
+ * KeyStore with the user's 4 Ed25519 seeds (KMS-unwrapped server-side
563
+ * after Argon2id verify).
564
+ *
565
+ * After this returns, the SDK is ready to publish ethos editions,
566
+ * invoke compute, mint mandates, etc. — exactly as if the user had
567
+ * signed in via {@link signIn} (zk) or {@link handleCallback} (SSO).
568
+ *
569
+ * Errors map to `AithosSDKError` codes:
570
+ * - `auth_invalid_input` (your code passed empty fields)
571
+ * - `auth_invalid_credentials` (401 — wrong email / wrong password)
572
+ * - `auth_wrong_auth_mode` (403 — user exists in another flow)
573
+ */
574
+ signInCustodial(input: CustodialSignInInput): Promise<CustodialSignInResult>;
575
+ /**
576
+ * Trigger a password-reset email to the given address. Backend ALWAYS
577
+ * resolves silently (no enumeration) — caller cannot tell whether the
578
+ * email is registered or not. The mail itself, if sent, contains a
579
+ * magic-link URL of shape `<resetBaseUrl>?token=<raw>&email=<email>`.
580
+ *
581
+ * Per-email rate limits apply server-side (5 mails/day, 5 min cooldown
582
+ * between consecutive requests). Calls during cooldown silently no-op
583
+ * the mail send while still returning success here.
584
+ */
585
+ requestPasswordReset(input: RequestPasswordResetInput): Promise<void>;
586
+ /**
587
+ * Finalise a password reset using the magic-link token sent to the
588
+ * user's inbox by {@link requestPasswordReset}.
589
+ *
590
+ * Typical use site: the page mounted on the reset URL declared in
591
+ * `aithos-auth-apps.reset_base_url`. The page reads `email` and
592
+ * `token` from `window.location.search`, prompts the user for a new
593
+ * password, then calls this method.
594
+ *
595
+ * On success, the returned {@link AithosSession} is persisted to the
596
+ * session store but the local keystore is NOT hydrated — the backend
597
+ * does not return the seed bundle on this endpoint. To get a fully
598
+ * usable session (one that can sign envelopes), follow up with
599
+ * {@link signInCustodial} using the email + new password. The two
600
+ * round-trips can be hidden inside a single UI action: reset → auto
601
+ * sign-in → redirect to dashboard.
602
+ *
603
+ * Errors map to `AithosSDKError` codes:
604
+ * - `auth_invalid_input` (your code passed empty fields)
605
+ * - `auth_reset_token_invalid` (400 — token forged / wrong email)
606
+ * - `auth_reset_token_expired` (410 — token TTL elapsed)
607
+ * - `auth_reset_token_consumed` (409 — already used)
608
+ * - `auth_password_too_short` (400 — < 10 chars)
609
+ * - `auth_custodial_reset_failed` (catch-all)
194
610
  */
611
+ applyPasswordReset(input: ApplyPasswordResetInput): Promise<ApplyPasswordResetResult>;
195
612
  signOut(): Promise<void>;
196
613
  }
197
614
  //# sourceMappingURL=auth.d.ts.map