@aithos/sdk 0.1.0-alpha.28 → 0.1.0-alpha.30
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.
- package/README.md +78 -0
- package/dist/src/auth-api.d.ts +55 -0
- package/dist/src/auth-api.js +76 -0
- package/dist/src/auth.d.ts +154 -0
- package/dist/src/auth.js +183 -1
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -100,6 +100,84 @@ network — they fail fast with a precise `AithosSDKError`:
|
|
|
100
100
|
set — useful for agents that only consume tokens (e.g. creative
|
|
101
101
|
assistants) without seeing any of your data.
|
|
102
102
|
|
|
103
|
+
## Custodial auth — onboarding users without a recovery file
|
|
104
|
+
|
|
105
|
+
Three new methods on `AithosAuth` let an app create and authenticate
|
|
106
|
+
its end-users via a server-managed custody flow — the user only needs
|
|
107
|
+
an email address and a password sent by mail. No recovery file, no
|
|
108
|
+
Google account, no client-side cryptography to handle.
|
|
109
|
+
|
|
110
|
+
The model is honest custody: Aithos KMS-wraps the user's Ed25519
|
|
111
|
+
identity seeds, and unwraps them on every sign-in after password
|
|
112
|
+
verification. Equivalent to how Coinbase or any hosted SaaS keeps your
|
|
113
|
+
private key. Annunciated to the user in the welcome email.
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import { AithosSDK } from "@aithos/sdk";
|
|
117
|
+
|
|
118
|
+
// ─── Server-side: sign-up ───────────────────────────────────────────
|
|
119
|
+
// MUST run on your backend. The API key is a server secret —
|
|
120
|
+
// provisioned by Aithos via the operator runbook.
|
|
121
|
+
const sdk = new AithosSDK({ identity });
|
|
122
|
+
const result = await sdk.auth.signUpCustodial({
|
|
123
|
+
apiKey: process.env.AITHOS_API_KEY!,
|
|
124
|
+
email: "alice@example.com",
|
|
125
|
+
displayName: "Alice",
|
|
126
|
+
});
|
|
127
|
+
// → { userId, did, handle, email, mailSent }
|
|
128
|
+
// The user receives an email with their password and a sign-in link.
|
|
129
|
+
|
|
130
|
+
// ─── Browser-side: sign-in ──────────────────────────────────────────
|
|
131
|
+
// User pastes the password from their mail into your sign-in form,
|
|
132
|
+
// then your frontend calls this. No API key needed — the password
|
|
133
|
+
// is the credential.
|
|
134
|
+
const { session, passwordMustChange } = await sdk.auth.signInCustodial({
|
|
135
|
+
email: "alice@example.com",
|
|
136
|
+
password: "MyTempPass32chars",
|
|
137
|
+
});
|
|
138
|
+
// Local KeyStore is now hydrated with the 4 Ed25519 sphere seeds —
|
|
139
|
+
// the user can publish ethos editions, mint mandates, invoke compute,
|
|
140
|
+
// exactly as if they had signed in via a recovery file or Google SSO.
|
|
141
|
+
if (passwordMustChange) {
|
|
142
|
+
// Optional: nudge the user to set their own password via the
|
|
143
|
+
// standard reset flow.
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ─── Browser-side: request password reset ───────────────────────────
|
|
147
|
+
// The backend always returns silently (anti-enumeration). If the email
|
|
148
|
+
// is registered AND in custodial mode AND not in cooldown AND under the
|
|
149
|
+
// daily cap, a magic-link email is sent to the address.
|
|
150
|
+
await sdk.auth.requestPasswordReset({ email: "alice@example.com" });
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The reset finalization (collecting the new password from the user) is
|
|
154
|
+
done on a small web page hosted by Aithos at `https://app.aithos.be/reset`
|
|
155
|
+
(or your app's own `reset_base_url` if you've registered one — see the
|
|
156
|
+
operator runbook). The page POSTs to `/auth/custodial/reset/finalize`
|
|
157
|
+
and returns the user to your sign-in page on success.
|
|
158
|
+
|
|
159
|
+
### Getting an API key
|
|
160
|
+
|
|
161
|
+
API keys are provisioned out-of-band by Aithos. Contact the maintainer
|
|
162
|
+
(or use the self-service console at `aithos.be/console` when it ships
|
|
163
|
+
in V2). The pattern is `aithos_<env>_<32 chars b58>`. Keep it in your
|
|
164
|
+
backend's secrets manager — never in browser code.
|
|
165
|
+
|
|
166
|
+
### Trade-offs vs. the zk and Google SSO flows
|
|
167
|
+
|
|
168
|
+
| | zk (recovery file) | Google SSO (KMS) | **Custodial** |
|
|
169
|
+
|----------------|----------------------------|----------------------|---------------|
|
|
170
|
+
| User burden | downloads `recovery.json` | Google consent | email only |
|
|
171
|
+
| Password reset | requires recovery file | re-auth via Google | magic-link mail |
|
|
172
|
+
| Trust model | zero-knowledge (you only) | Aithos + Google | Aithos only |
|
|
173
|
+
| Multi-device | re-import recovery | re-Google | email + password |
|
|
174
|
+
| SDK signing capability | full | full | full |
|
|
175
|
+
|
|
176
|
+
Custodial is the right default for SDK-integrated apps that want
|
|
177
|
+
SaaS-grade UX. zk is the right default for power users who want
|
|
178
|
+
sovereign custody. SSO is the right default for users already invested
|
|
179
|
+
in the Google ecosystem.
|
|
180
|
+
|
|
103
181
|
## Extracting webpages without an LLM
|
|
104
182
|
|
|
105
183
|
`sdk.web` is a token-priced primitive that lets your agent read a
|
package/dist/src/auth-api.d.ts
CHANGED
|
@@ -46,5 +46,60 @@ export interface LoginVerifyResponse {
|
|
|
46
46
|
readonly blobVersion: number;
|
|
47
47
|
}
|
|
48
48
|
export declare function loginVerify(http: HttpClient, email: string, authKey: Uint8Array): Promise<LoginVerifyResponse>;
|
|
49
|
+
export interface CustodialSignUpApiInput {
|
|
50
|
+
readonly apiKey: string;
|
|
51
|
+
readonly email: string;
|
|
52
|
+
readonly displayName?: string;
|
|
53
|
+
readonly handleHint?: string;
|
|
54
|
+
}
|
|
55
|
+
export interface CustodialSignUpApiResponse {
|
|
56
|
+
readonly userId: string;
|
|
57
|
+
readonly did: string;
|
|
58
|
+
readonly handle: string;
|
|
59
|
+
readonly email: string;
|
|
60
|
+
readonly mailSent: boolean;
|
|
61
|
+
readonly mailMessageId?: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Provision a custodial-mode account on behalf of a registered app.
|
|
65
|
+
* Server-only — the API key MUST be kept off the browser (it grants the
|
|
66
|
+
* ability to create accounts under your app's name). Typical use site is
|
|
67
|
+
* the app's backend in response to a sign-up form submission.
|
|
68
|
+
*/
|
|
69
|
+
export declare function custodialSignUp(http: HttpClient, input: CustodialSignUpApiInput): Promise<CustodialSignUpApiResponse>;
|
|
70
|
+
export interface CustodialSignInApiInput {
|
|
71
|
+
readonly email: string;
|
|
72
|
+
readonly password: string;
|
|
73
|
+
}
|
|
74
|
+
export interface CustodialSignInApiResponse {
|
|
75
|
+
readonly session: string;
|
|
76
|
+
readonly exp: number;
|
|
77
|
+
readonly did: string;
|
|
78
|
+
readonly handle: string;
|
|
79
|
+
readonly displayName: string;
|
|
80
|
+
/** Raw 32-byte Ed25519 seed — caller MUST hydrate its keystore and
|
|
81
|
+
* zeroize this buffer. */
|
|
82
|
+
readonly seed: Uint8Array;
|
|
83
|
+
/** Raw 32-byte vault encryption key — same lifecycle. */
|
|
84
|
+
readonly encKey: Uint8Array;
|
|
85
|
+
readonly blob: Uint8Array;
|
|
86
|
+
readonly blobNonce: Uint8Array;
|
|
87
|
+
readonly blobVersion: number;
|
|
88
|
+
readonly passwordMustChange: boolean;
|
|
89
|
+
}
|
|
90
|
+
export declare function custodialSignIn(http: HttpClient, input: CustodialSignInApiInput): Promise<CustodialSignInApiResponse>;
|
|
91
|
+
export declare function custodialResetRequest(http: HttpClient, email: string): Promise<void>;
|
|
92
|
+
export interface CustodialResetFinalizeApiInput {
|
|
93
|
+
readonly email: string;
|
|
94
|
+
readonly token: string;
|
|
95
|
+
readonly newPassword: string;
|
|
96
|
+
}
|
|
97
|
+
export interface CustodialResetFinalizeApiResponse {
|
|
98
|
+
readonly session: string;
|
|
99
|
+
readonly exp: number;
|
|
100
|
+
readonly did: string;
|
|
101
|
+
readonly handle: string;
|
|
102
|
+
}
|
|
103
|
+
export declare function custodialResetFinalize(http: HttpClient, input: CustodialResetFinalizeApiInput): Promise<CustodialResetFinalizeApiResponse>;
|
|
49
104
|
export {};
|
|
50
105
|
//# sourceMappingURL=auth-api.d.ts.map
|
package/dist/src/auth-api.js
CHANGED
|
@@ -99,4 +99,80 @@ export async function loginVerify(http, email, authKey) {
|
|
|
99
99
|
blobVersion: wire.blob_version,
|
|
100
100
|
};
|
|
101
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Provision a custodial-mode account on behalf of a registered app.
|
|
104
|
+
* Server-only — the API key MUST be kept off the browser (it grants the
|
|
105
|
+
* ability to create accounts under your app's name). Typical use site is
|
|
106
|
+
* the app's backend in response to a sign-up form submission.
|
|
107
|
+
*/
|
|
108
|
+
export async function custodialSignUp(http, input) {
|
|
109
|
+
const res = await http.fetchImpl(`${http.authBaseUrl}/auth/custodial/sign-up`, {
|
|
110
|
+
method: "POST",
|
|
111
|
+
headers: {
|
|
112
|
+
"content-type": "application/json",
|
|
113
|
+
authorization: `Bearer ${input.apiKey}`,
|
|
114
|
+
},
|
|
115
|
+
body: JSON.stringify({
|
|
116
|
+
email: input.email,
|
|
117
|
+
...(input.displayName ? { display_name: input.displayName } : {}),
|
|
118
|
+
...(input.handleHint ? { handle_hint: input.handleHint } : {}),
|
|
119
|
+
}),
|
|
120
|
+
});
|
|
121
|
+
if (!res.ok)
|
|
122
|
+
throw await readError(res, "custodial_signup_failed");
|
|
123
|
+
const wire = (await res.json());
|
|
124
|
+
return {
|
|
125
|
+
userId: wire.user_id,
|
|
126
|
+
did: wire.did,
|
|
127
|
+
handle: wire.handle,
|
|
128
|
+
email: wire.email,
|
|
129
|
+
mailSent: wire.mail_sent,
|
|
130
|
+
...(wire.mail_message_id !== undefined
|
|
131
|
+
? { mailMessageId: wire.mail_message_id }
|
|
132
|
+
: {}),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
export async function custodialSignIn(http, input) {
|
|
136
|
+
const wire = await postJson(http, "/auth/custodial/sign-in", { email: input.email, password: input.password });
|
|
137
|
+
return {
|
|
138
|
+
session: wire.session,
|
|
139
|
+
exp: wire.exp,
|
|
140
|
+
did: wire.did,
|
|
141
|
+
handle: wire.handle,
|
|
142
|
+
displayName: wire.display_name,
|
|
143
|
+
seed: b64ToBytes(wire.seed_b64),
|
|
144
|
+
encKey: b64ToBytes(wire.enc_key_b64),
|
|
145
|
+
blob: wire.blob_b64 ? b64ToBytes(wire.blob_b64) : new Uint8Array(0),
|
|
146
|
+
blobNonce: wire.blob_nonce_b64
|
|
147
|
+
? b64ToBytes(wire.blob_nonce_b64)
|
|
148
|
+
: new Uint8Array(0),
|
|
149
|
+
blobVersion: wire.blob_version,
|
|
150
|
+
passwordMustChange: wire.password_must_change,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/* ---- POST /auth/custodial/reset/request --------------------------------- */
|
|
154
|
+
export async function custodialResetRequest(http, email) {
|
|
155
|
+
// Backend always returns 200 { ok: true } regardless. We accept any
|
|
156
|
+
// 2xx body, even non-JSON, to be defensive.
|
|
157
|
+
const res = await http.fetchImpl(`${http.authBaseUrl}/auth/custodial/reset/request`, {
|
|
158
|
+
method: "POST",
|
|
159
|
+
headers: { "content-type": "application/json" },
|
|
160
|
+
body: JSON.stringify({ email }),
|
|
161
|
+
});
|
|
162
|
+
if (!res.ok)
|
|
163
|
+
throw await readError(res, "custodial_reset_request_failed");
|
|
164
|
+
}
|
|
165
|
+
export async function custodialResetFinalize(http, input) {
|
|
166
|
+
const wire = await postJson(http, "/auth/custodial/reset/finalize", {
|
|
167
|
+
email: input.email,
|
|
168
|
+
token: input.token,
|
|
169
|
+
new_password: input.newPassword,
|
|
170
|
+
});
|
|
171
|
+
return {
|
|
172
|
+
session: wire.session,
|
|
173
|
+
exp: wire.exp,
|
|
174
|
+
did: wire.did,
|
|
175
|
+
handle: wire.handle,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
102
178
|
//# sourceMappingURL=auth-api.js.map
|
package/dist/src/auth.d.ts
CHANGED
|
@@ -135,6 +135,85 @@ export interface ImportMandateInput {
|
|
|
135
135
|
/** Delegate bundle as a Blob or already-decoded JSON string. */
|
|
136
136
|
readonly bundle: Blob | string;
|
|
137
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Input to {@link AithosAuth.signUpCustodial}. Server-side only — the
|
|
140
|
+
* API key MUST stay off the browser (it grants account-creation
|
|
141
|
+
* authority under your app's name). Typical use site: your app's
|
|
142
|
+
* backend in response to a sign-up form submission.
|
|
143
|
+
*/
|
|
144
|
+
export interface CustodialSignUpInput {
|
|
145
|
+
/** Bearer API key issued to your app (`aithos_<env>_<32b58>`). */
|
|
146
|
+
readonly apiKey: string;
|
|
147
|
+
/** Email address of the new user. Will receive the welcome mail. */
|
|
148
|
+
readonly email: string;
|
|
149
|
+
/** Optional display name. Capped at 200 chars by the backend. */
|
|
150
|
+
readonly displayName?: string;
|
|
151
|
+
/** Optional handle hint. Backend may sanitise or replace. */
|
|
152
|
+
readonly handleHint?: string;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Result of {@link AithosAuth.signUpCustodial}. The raw password is
|
|
156
|
+
* NEVER returned in this response — it lives only in the welcome email
|
|
157
|
+
* sent to the user via SES. `mailSent: false` means the account row
|
|
158
|
+
* was created but the email handoff to SES failed; the operator can
|
|
159
|
+
* trigger a manual resend.
|
|
160
|
+
*/
|
|
161
|
+
export interface CustodialSignUpResult {
|
|
162
|
+
readonly userId: string;
|
|
163
|
+
readonly did: string;
|
|
164
|
+
readonly handle: string;
|
|
165
|
+
readonly email: string;
|
|
166
|
+
readonly mailSent: boolean;
|
|
167
|
+
readonly mailMessageId?: string;
|
|
168
|
+
}
|
|
169
|
+
export interface CustodialSignInInput {
|
|
170
|
+
readonly email: string;
|
|
171
|
+
readonly password: string;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Active custodial session. Same JWT-backed shape as {@link AithosSession}
|
|
175
|
+
* but adds a `passwordMustChange` flag the UI can honour to nudge the
|
|
176
|
+
* user toward a `requestPasswordReset` on first login.
|
|
177
|
+
*/
|
|
178
|
+
export interface CustodialSignInResult {
|
|
179
|
+
readonly session: AithosSession;
|
|
180
|
+
readonly passwordMustChange: boolean;
|
|
181
|
+
}
|
|
182
|
+
export interface RequestPasswordResetInput {
|
|
183
|
+
readonly email: string;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Input to {@link AithosAuth.applyPasswordReset}. Finalises a password
|
|
187
|
+
* reset started by {@link AithosAuth.requestPasswordReset}. The `email`
|
|
188
|
+
* and `token` come straight from the magic-link URL that landed in the
|
|
189
|
+
* user's inbox (`?email=…&token=…`); the `newPassword` is what the user
|
|
190
|
+
* just typed in the reset page.
|
|
191
|
+
*/
|
|
192
|
+
export interface ApplyPasswordResetInput {
|
|
193
|
+
/** Email address whose password is being reset. */
|
|
194
|
+
readonly email: string;
|
|
195
|
+
/** Raw reset token extracted from the magic-link URL query string. */
|
|
196
|
+
readonly token: string;
|
|
197
|
+
/** New password — must satisfy the backend's policy (≥ 10 chars). */
|
|
198
|
+
readonly newPassword: string;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Result of {@link AithosAuth.applyPasswordReset}. Carries a fresh JWT
|
|
202
|
+
* session so the UI can either redirect to a "you're now signed in"
|
|
203
|
+
* landing or prompt the user to sign in explicitly with their new
|
|
204
|
+
* credentials — same {@link CustodialSignInResult} shape as a normal
|
|
205
|
+
* sign-in.
|
|
206
|
+
*
|
|
207
|
+
* Note: unlike {@link signInCustodial}, this DOES NOT hydrate the local
|
|
208
|
+
* keystore. The reset path on the auth Lambda re-wraps the seed bundle
|
|
209
|
+
* with KMS but doesn't return it (the user just typed a password — they
|
|
210
|
+
* still need to sign in once to materialise the seeds locally). The
|
|
211
|
+
* {@link AithosSession} returned here lets the app store the JWT and
|
|
212
|
+
* call {@link signInCustodial} to complete hydration.
|
|
213
|
+
*/
|
|
214
|
+
export interface ApplyPasswordResetResult {
|
|
215
|
+
readonly session: AithosSession;
|
|
216
|
+
}
|
|
138
217
|
export declare class AithosAuth {
|
|
139
218
|
#private;
|
|
140
219
|
readonly authBaseUrl: string;
|
|
@@ -248,6 +327,81 @@ export declare class AithosAuth {
|
|
|
248
327
|
* already completed setup; nothing to do).
|
|
249
328
|
*/
|
|
250
329
|
completeSsoFirstLogin(input: CompleteSsoFirstLoginInput): Promise<CompleteSsoFirstLoginResult>;
|
|
330
|
+
/**
|
|
331
|
+
* Provision a custodial-mode account on behalf of a registered app.
|
|
332
|
+
*
|
|
333
|
+
* SERVER-ONLY — the API key MUST stay off the browser. The raw user
|
|
334
|
+
* password is generated server-side and sent to the user via the
|
|
335
|
+
* Aithos welcome email; it is NEVER returned in this response.
|
|
336
|
+
*
|
|
337
|
+
* Typical use site: your app's backend in response to a sign-up form
|
|
338
|
+
* submission. The frontend never sees the API key, only the resulting
|
|
339
|
+
* `{ userId, did, handle, email, mailSent }` it can show to the user
|
|
340
|
+
* ("we just sent you a mail with your credentials").
|
|
341
|
+
*
|
|
342
|
+
* Errors map to `AithosSDKError` codes:
|
|
343
|
+
* - `auth_missing_api_key` (your code passed empty apiKey)
|
|
344
|
+
* - `auth_invalid_api_key` (Bearer rejected by backend)
|
|
345
|
+
* - `auth_api_key_revoked` (backend marked the key revoked)
|
|
346
|
+
* - `auth_email_exists` (409 — email already registered)
|
|
347
|
+
* - `auth_email_invalid` (400 — bad email format)
|
|
348
|
+
* - `auth_mail_send_failed` (502 — DDB row exists but SES failed)
|
|
349
|
+
* - `auth_custodial_signup_failed` (catch-all)
|
|
350
|
+
*/
|
|
351
|
+
signUpCustodial(input: CustodialSignUpInput): Promise<CustodialSignUpResult>;
|
|
352
|
+
/**
|
|
353
|
+
* Authenticate a custodial-mode user with email + password. Single
|
|
354
|
+
* round-trip: returns a fresh JWT session AND hydrates the local
|
|
355
|
+
* KeyStore with the user's 4 Ed25519 seeds (KMS-unwrapped server-side
|
|
356
|
+
* after Argon2id verify).
|
|
357
|
+
*
|
|
358
|
+
* After this returns, the SDK is ready to publish ethos editions,
|
|
359
|
+
* invoke compute, mint mandates, etc. — exactly as if the user had
|
|
360
|
+
* signed in via {@link signIn} (zk) or {@link handleCallback} (SSO).
|
|
361
|
+
*
|
|
362
|
+
* Errors map to `AithosSDKError` codes:
|
|
363
|
+
* - `auth_invalid_input` (your code passed empty fields)
|
|
364
|
+
* - `auth_invalid_credentials` (401 — wrong email / wrong password)
|
|
365
|
+
* - `auth_wrong_auth_mode` (403 — user exists in another flow)
|
|
366
|
+
*/
|
|
367
|
+
signInCustodial(input: CustodialSignInInput): Promise<CustodialSignInResult>;
|
|
368
|
+
/**
|
|
369
|
+
* Trigger a password-reset email to the given address. Backend ALWAYS
|
|
370
|
+
* resolves silently (no enumeration) — caller cannot tell whether the
|
|
371
|
+
* email is registered or not. The mail itself, if sent, contains a
|
|
372
|
+
* magic-link URL of shape `<resetBaseUrl>?token=<raw>&email=<email>`.
|
|
373
|
+
*
|
|
374
|
+
* Per-email rate limits apply server-side (5 mails/day, 5 min cooldown
|
|
375
|
+
* between consecutive requests). Calls during cooldown silently no-op
|
|
376
|
+
* the mail send while still returning success here.
|
|
377
|
+
*/
|
|
378
|
+
requestPasswordReset(input: RequestPasswordResetInput): Promise<void>;
|
|
379
|
+
/**
|
|
380
|
+
* Finalise a password reset using the magic-link token sent to the
|
|
381
|
+
* user's inbox by {@link requestPasswordReset}.
|
|
382
|
+
*
|
|
383
|
+
* Typical use site: the page mounted on the reset URL declared in
|
|
384
|
+
* `aithos-auth-apps.reset_base_url`. The page reads `email` and
|
|
385
|
+
* `token` from `window.location.search`, prompts the user for a new
|
|
386
|
+
* password, then calls this method.
|
|
387
|
+
*
|
|
388
|
+
* On success, the returned {@link AithosSession} is persisted to the
|
|
389
|
+
* session store but the local keystore is NOT hydrated — the backend
|
|
390
|
+
* does not return the seed bundle on this endpoint. To get a fully
|
|
391
|
+
* usable session (one that can sign envelopes), follow up with
|
|
392
|
+
* {@link signInCustodial} using the email + new password. The two
|
|
393
|
+
* round-trips can be hidden inside a single UI action: reset → auto
|
|
394
|
+
* sign-in → redirect to dashboard.
|
|
395
|
+
*
|
|
396
|
+
* Errors map to `AithosSDKError` codes:
|
|
397
|
+
* - `auth_invalid_input` (your code passed empty fields)
|
|
398
|
+
* - `auth_reset_token_invalid` (400 — token forged / wrong email)
|
|
399
|
+
* - `auth_reset_token_expired` (410 — token TTL elapsed)
|
|
400
|
+
* - `auth_reset_token_consumed` (409 — already used)
|
|
401
|
+
* - `auth_password_too_short` (400 — < 10 chars)
|
|
402
|
+
* - `auth_custodial_reset_failed` (catch-all)
|
|
403
|
+
*/
|
|
404
|
+
applyPasswordReset(input: ApplyPasswordResetInput): Promise<ApplyPasswordResetResult>;
|
|
251
405
|
signOut(): Promise<void>;
|
|
252
406
|
}
|
|
253
407
|
//# sourceMappingURL=auth.d.ts.map
|
package/dist/src/auth.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
// keyStore is the source of truth for "is the user signed in", the
|
|
22
22
|
// JWT is auxiliary for compute/wallet.
|
|
23
23
|
import { buildBlobPlaintext, buildSignedEnvelope, createBrowserIdentity, decryptBlob, DEFAULT_KDF, deriveAuthAndEncKeys, encryptBlob, parseBlob, randomNonce, randomSalt, serializeBlob, signedDidDocument, zeroize, } from "@aithos/protocol-client";
|
|
24
|
-
import { loginChallenge, loginVerify, putBlob, registerAccount, } from "./auth-api.js";
|
|
24
|
+
import { custodialResetFinalize, custodialResetRequest, custodialSignIn, custodialSignUp, loginChallenge, loginVerify, putBlob, registerAccount, } from "./auth-api.js";
|
|
25
25
|
import { defaultSessionStore, } from "./session-store.js";
|
|
26
26
|
import { defaultKeyStore, } from "./key-store.js";
|
|
27
27
|
import { parseDelegateBundle, readDelegateBundleText, } from "./internal/delegate-bundle.js";
|
|
@@ -771,6 +771,188 @@ export class AithosAuth {
|
|
|
771
771
|
};
|
|
772
772
|
}
|
|
773
773
|
/* ------------------------------------------------------------------------ */
|
|
774
|
+
/* Custodial flow (V2 — see PLATFORM-AUTH-PASSWORD-V2-PLAN.md) */
|
|
775
|
+
/* ------------------------------------------------------------------------ */
|
|
776
|
+
/**
|
|
777
|
+
* Provision a custodial-mode account on behalf of a registered app.
|
|
778
|
+
*
|
|
779
|
+
* SERVER-ONLY — the API key MUST stay off the browser. The raw user
|
|
780
|
+
* password is generated server-side and sent to the user via the
|
|
781
|
+
* Aithos welcome email; it is NEVER returned in this response.
|
|
782
|
+
*
|
|
783
|
+
* Typical use site: your app's backend in response to a sign-up form
|
|
784
|
+
* submission. The frontend never sees the API key, only the resulting
|
|
785
|
+
* `{ userId, did, handle, email, mailSent }` it can show to the user
|
|
786
|
+
* ("we just sent you a mail with your credentials").
|
|
787
|
+
*
|
|
788
|
+
* Errors map to `AithosSDKError` codes:
|
|
789
|
+
* - `auth_missing_api_key` (your code passed empty apiKey)
|
|
790
|
+
* - `auth_invalid_api_key` (Bearer rejected by backend)
|
|
791
|
+
* - `auth_api_key_revoked` (backend marked the key revoked)
|
|
792
|
+
* - `auth_email_exists` (409 — email already registered)
|
|
793
|
+
* - `auth_email_invalid` (400 — bad email format)
|
|
794
|
+
* - `auth_mail_send_failed` (502 — DDB row exists but SES failed)
|
|
795
|
+
* - `auth_custodial_signup_failed` (catch-all)
|
|
796
|
+
*/
|
|
797
|
+
async signUpCustodial(input) {
|
|
798
|
+
if (!input.apiKey) {
|
|
799
|
+
throw new AithosSDKError("auth_missing_api_key", "signUpCustodial: apiKey is required");
|
|
800
|
+
}
|
|
801
|
+
if (!input.email) {
|
|
802
|
+
throw new AithosSDKError("auth_invalid_input", "signUpCustodial: email is required");
|
|
803
|
+
}
|
|
804
|
+
return custodialSignUp({ fetchImpl: this.#fetchImpl, authBaseUrl: this.authBaseUrl }, input);
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Authenticate a custodial-mode user with email + password. Single
|
|
808
|
+
* round-trip: returns a fresh JWT session AND hydrates the local
|
|
809
|
+
* KeyStore with the user's 4 Ed25519 seeds (KMS-unwrapped server-side
|
|
810
|
+
* after Argon2id verify).
|
|
811
|
+
*
|
|
812
|
+
* After this returns, the SDK is ready to publish ethos editions,
|
|
813
|
+
* invoke compute, mint mandates, etc. — exactly as if the user had
|
|
814
|
+
* signed in via {@link signIn} (zk) or {@link handleCallback} (SSO).
|
|
815
|
+
*
|
|
816
|
+
* Errors map to `AithosSDKError` codes:
|
|
817
|
+
* - `auth_invalid_input` (your code passed empty fields)
|
|
818
|
+
* - `auth_invalid_credentials` (401 — wrong email / wrong password)
|
|
819
|
+
* - `auth_wrong_auth_mode` (403 — user exists in another flow)
|
|
820
|
+
*/
|
|
821
|
+
async signInCustodial(input) {
|
|
822
|
+
if (!input.email || !input.password) {
|
|
823
|
+
throw new AithosSDKError("auth_invalid_input", "signInCustodial: email and password are required");
|
|
824
|
+
}
|
|
825
|
+
const resp = await custodialSignIn({ fetchImpl: this.#fetchImpl, authBaseUrl: this.authBaseUrl }, input);
|
|
826
|
+
// Split the 128-byte seed bundle into the four sphere seeds. The
|
|
827
|
+
// backend lays them out in the canonical order
|
|
828
|
+
// [root || public || circle || self] (cf. seed-wrapper.ts).
|
|
829
|
+
if (resp.seed.byteLength !== 128) {
|
|
830
|
+
// Legacy 32-byte rows shouldn't happen in production (we wiped the
|
|
831
|
+
// single test row before redeploying with the 4-seed bundle), but
|
|
832
|
+
// we surface a clear error rather than silently corrupting the
|
|
833
|
+
// identity.
|
|
834
|
+
zeroize(resp.seed);
|
|
835
|
+
zeroize(resp.encKey);
|
|
836
|
+
throw new AithosSDKError("auth_custodial_seed_format", `signInCustodial: expected 128-byte seed bundle, got ${resp.seed.byteLength}`);
|
|
837
|
+
}
|
|
838
|
+
const seedRoot = resp.seed.slice(0, 32);
|
|
839
|
+
const seedPublic = resp.seed.slice(32, 64);
|
|
840
|
+
const seedCircle = resp.seed.slice(64, 96);
|
|
841
|
+
const seedSelf = resp.seed.slice(96, 128);
|
|
842
|
+
// Stored shape uses hex strings; round-trip through bytesToHex
|
|
843
|
+
// so the keyStore record is identical to what signUp(zk) writes.
|
|
844
|
+
const stored = {
|
|
845
|
+
version: "0.1.0-hex",
|
|
846
|
+
did: resp.did,
|
|
847
|
+
handle: resp.handle,
|
|
848
|
+
displayName: resp.displayName,
|
|
849
|
+
seedsHex: {
|
|
850
|
+
root: bytesToHex(seedRoot),
|
|
851
|
+
public: bytesToHex(seedPublic),
|
|
852
|
+
circle: bytesToHex(seedCircle),
|
|
853
|
+
self: bytesToHex(seedSelf),
|
|
854
|
+
},
|
|
855
|
+
savedAt: new Date().toISOString(),
|
|
856
|
+
};
|
|
857
|
+
// Zeroize the raw bundle + the split copies now that they've been
|
|
858
|
+
// serialised into the keyStore record (hex strings live in the
|
|
859
|
+
// record; the original bytes can go).
|
|
860
|
+
zeroize(resp.seed);
|
|
861
|
+
zeroize(seedRoot);
|
|
862
|
+
zeroize(seedPublic);
|
|
863
|
+
zeroize(seedCircle);
|
|
864
|
+
zeroize(seedSelf);
|
|
865
|
+
// The enc_key is informational here — the custodial blob is empty
|
|
866
|
+
// at first login. We still don't keep it in memory.
|
|
867
|
+
zeroize(resp.encKey);
|
|
868
|
+
// Hydrate in-memory owner signers from the freshly-stored material.
|
|
869
|
+
if (this.#ownerSigners)
|
|
870
|
+
this.#ownerSigners.destroy();
|
|
871
|
+
this.#ownerSigners = OwnerSigners.fromStoredOwnerKeys(stored);
|
|
872
|
+
await this.#keyStore.saveOwner(stored);
|
|
873
|
+
const session = {
|
|
874
|
+
session: resp.session,
|
|
875
|
+
exp: resp.exp,
|
|
876
|
+
did: resp.did,
|
|
877
|
+
handle: resp.handle,
|
|
878
|
+
blob_b64: bytesToB64Public(resp.blob),
|
|
879
|
+
blob_nonce_b64: bytesToB64Public(resp.blobNonce),
|
|
880
|
+
blob_version: resp.blobVersion,
|
|
881
|
+
enc_key_b64: "",
|
|
882
|
+
is_first_login: resp.passwordMustChange,
|
|
883
|
+
};
|
|
884
|
+
this.#sessionStore.set(session);
|
|
885
|
+
return { session, passwordMustChange: resp.passwordMustChange };
|
|
886
|
+
}
|
|
887
|
+
/**
|
|
888
|
+
* Trigger a password-reset email to the given address. Backend ALWAYS
|
|
889
|
+
* resolves silently (no enumeration) — caller cannot tell whether the
|
|
890
|
+
* email is registered or not. The mail itself, if sent, contains a
|
|
891
|
+
* magic-link URL of shape `<resetBaseUrl>?token=<raw>&email=<email>`.
|
|
892
|
+
*
|
|
893
|
+
* Per-email rate limits apply server-side (5 mails/day, 5 min cooldown
|
|
894
|
+
* between consecutive requests). Calls during cooldown silently no-op
|
|
895
|
+
* the mail send while still returning success here.
|
|
896
|
+
*/
|
|
897
|
+
async requestPasswordReset(input) {
|
|
898
|
+
if (!input.email) {
|
|
899
|
+
throw new AithosSDKError("auth_invalid_input", "requestPasswordReset: email is required");
|
|
900
|
+
}
|
|
901
|
+
await custodialResetRequest({ fetchImpl: this.#fetchImpl, authBaseUrl: this.authBaseUrl }, input.email);
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Finalise a password reset using the magic-link token sent to the
|
|
905
|
+
* user's inbox by {@link requestPasswordReset}.
|
|
906
|
+
*
|
|
907
|
+
* Typical use site: the page mounted on the reset URL declared in
|
|
908
|
+
* `aithos-auth-apps.reset_base_url`. The page reads `email` and
|
|
909
|
+
* `token` from `window.location.search`, prompts the user for a new
|
|
910
|
+
* password, then calls this method.
|
|
911
|
+
*
|
|
912
|
+
* On success, the returned {@link AithosSession} is persisted to the
|
|
913
|
+
* session store but the local keystore is NOT hydrated — the backend
|
|
914
|
+
* does not return the seed bundle on this endpoint. To get a fully
|
|
915
|
+
* usable session (one that can sign envelopes), follow up with
|
|
916
|
+
* {@link signInCustodial} using the email + new password. The two
|
|
917
|
+
* round-trips can be hidden inside a single UI action: reset → auto
|
|
918
|
+
* sign-in → redirect to dashboard.
|
|
919
|
+
*
|
|
920
|
+
* Errors map to `AithosSDKError` codes:
|
|
921
|
+
* - `auth_invalid_input` (your code passed empty fields)
|
|
922
|
+
* - `auth_reset_token_invalid` (400 — token forged / wrong email)
|
|
923
|
+
* - `auth_reset_token_expired` (410 — token TTL elapsed)
|
|
924
|
+
* - `auth_reset_token_consumed` (409 — already used)
|
|
925
|
+
* - `auth_password_too_short` (400 — < 10 chars)
|
|
926
|
+
* - `auth_custodial_reset_failed` (catch-all)
|
|
927
|
+
*/
|
|
928
|
+
async applyPasswordReset(input) {
|
|
929
|
+
if (!input.email || !input.token || !input.newPassword) {
|
|
930
|
+
throw new AithosSDKError("auth_invalid_input", "applyPasswordReset: email, token and newPassword are required");
|
|
931
|
+
}
|
|
932
|
+
const resp = await custodialResetFinalize({ fetchImpl: this.#fetchImpl, authBaseUrl: this.authBaseUrl }, input);
|
|
933
|
+
// The reset endpoint mints a JWT but doesn't ship the seed bundle —
|
|
934
|
+
// the caller still has to signInCustodial() to materialise the keys
|
|
935
|
+
// locally. We persist the session anyway so any code that reads
|
|
936
|
+
// `getCurrentSession()` between the reset and the follow-up sign-in
|
|
937
|
+
// sees the new JWT (e.g. an analytics hook).
|
|
938
|
+
const session = {
|
|
939
|
+
session: resp.session,
|
|
940
|
+
exp: resp.exp,
|
|
941
|
+
did: resp.did,
|
|
942
|
+
handle: resp.handle,
|
|
943
|
+
// No blob / enc_key on this path — the reset endpoint doesn't
|
|
944
|
+
// re-issue the vault. Leave the blob slots empty; the follow-up
|
|
945
|
+
// signInCustodial() will populate them.
|
|
946
|
+
blob_b64: "",
|
|
947
|
+
blob_nonce_b64: "",
|
|
948
|
+
blob_version: 0,
|
|
949
|
+
enc_key_b64: "",
|
|
950
|
+
is_first_login: false,
|
|
951
|
+
};
|
|
952
|
+
this.#sessionStore.set(session);
|
|
953
|
+
return { session };
|
|
954
|
+
}
|
|
955
|
+
/* ------------------------------------------------------------------------ */
|
|
774
956
|
/* Sign-out */
|
|
775
957
|
/* ------------------------------------------------------------------------ */
|
|
776
958
|
async signOut() {
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const VERSION = "0.1.0-alpha.
|
|
1
|
+
export declare const VERSION = "0.1.0-alpha.30";
|
|
2
2
|
export { AithosSDK } from "./sdk.js";
|
|
3
3
|
export type { AithosSDKConfig } from "./types.js";
|
|
4
4
|
export { AithosSDKError } from "./types.js";
|
|
@@ -12,7 +12,7 @@ export { WalletNamespace } from "./wallet.js";
|
|
|
12
12
|
export type { ComponentStyle, ExtractArgs, ExtractContent, ExtractData, ExtractForm, ExtractFormField, ExtractHeading, ExtractIconDeclaration, ExtractImage, ExtractLink, ExtractLogo, ExtractMeta, ExtractResult, ExtractSection, ExtractStructure, ExtractStyles, FetchAssetArgs, FetchAssetResult, PaletteEntry, VisualSignature, WebNamespaceDeps, } from "./web.js";
|
|
13
13
|
export { WebNamespace, WEB_EXTRACT_SCOPE } from "./web.js";
|
|
14
14
|
export { AithosAuth, DEFAULT_API_BASE_URL, DEFAULT_AUTH_BASE_URL, } from "./auth.js";
|
|
15
|
-
export type { AithosAuthConfig, AithosSession, CompleteSsoFirstLoginInput, CompleteSsoFirstLoginResult, DelegateInfo, ImportMandateInput, OwnerInfo, SignInInput, SignInWithGoogleOptions, SignInWithRecoveryInput, SignUpInput, SignUpResult, } from "./auth.js";
|
|
15
|
+
export type { AithosAuthConfig, AithosSession, ApplyPasswordResetInput, ApplyPasswordResetResult, CompleteSsoFirstLoginInput, CompleteSsoFirstLoginResult, CustodialSignInInput, CustodialSignInResult, CustodialSignUpInput, CustodialSignUpResult, DelegateInfo, ImportMandateInput, OwnerInfo, RequestPasswordResetInput, SignInInput, SignInWithGoogleOptions, SignInWithRecoveryInput, SignUpInput, SignUpResult, } from "./auth.js";
|
|
16
16
|
export { DEFAULT_SESSION_STORAGE_KEY, defaultSessionStore, localStorageStore, noopStore, sessionStorageStore, type AithosSessionStore, } from "./session-store.js";
|
|
17
17
|
export { DEFAULT_KEYSTORE_DB_NAME, defaultKeyStore, indexedDbKeyStore, memoryKeyStore, type AithosKeyStore, type StoredDelegateKeys, type StoredOwnerKeys, } from "./key-store.js";
|
|
18
18
|
export { EthosClient, EthosNamespace, EthosZone, ZONE_NAMES, } from "./ethos.js";
|
package/dist/src/index.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// Public types specific to the SDK (`AithosSDKConfig`, `AithosSDKError`)
|
|
18
18
|
// are exported from here. Endpoint config (`AithosSdkEndpoints`,
|
|
19
19
|
// `DEFAULT_SDK_ENDPOINTS`) likewise.
|
|
20
|
-
export const VERSION = "0.1.0-alpha.
|
|
20
|
+
export const VERSION = "0.1.0-alpha.30";
|
|
21
21
|
export { AithosSDK } from "./sdk.js";
|
|
22
22
|
export { AithosSDKError } from "./types.js";
|
|
23
23
|
// Re-export protocol-client's JSON-RPC error type so consumers can
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aithos/sdk",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.30",
|
|
4
4
|
"description": "Aithos SDK \u2014 high-level TypeScript developer kit for building agentic apps on the Aithos protocol. Wraps @aithos/protocol-client and exposes the Aithos compute proxy and wallet (Stripe top-up) endpoints.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"aithos",
|