@aithos/sdk 0.1.0-alpha.3 → 0.1.0-alpha.5

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 (46) hide show
  1. package/dist/src/auth-api.d.ts +41 -0
  2. package/dist/src/auth-api.js +82 -0
  3. package/dist/src/auth.d.ts +114 -75
  4. package/dist/src/auth.js +553 -73
  5. package/dist/src/compute.d.ts +8 -6
  6. package/dist/src/compute.js +19 -11
  7. package/dist/src/ethos.d.ts +117 -1
  8. package/dist/src/ethos.js +417 -16
  9. package/dist/src/index.d.ts +8 -4
  10. package/dist/src/index.js +26 -8
  11. package/dist/src/internal/delegate-bundle.d.ts +18 -0
  12. package/dist/src/internal/delegate-bundle.js +89 -0
  13. package/dist/src/internal/delegate-state.d.ts +45 -0
  14. package/dist/src/internal/delegate-state.js +120 -0
  15. package/dist/src/internal/owner-signers.d.ts +78 -0
  16. package/dist/src/internal/owner-signers.js +179 -0
  17. package/dist/src/internal/protocol-client-bridge.d.ts +8 -0
  18. package/dist/src/internal/protocol-client-bridge.js +20 -0
  19. package/dist/src/internal/recovery-file.d.ts +29 -0
  20. package/dist/src/internal/recovery-file.js +98 -0
  21. package/dist/src/internal/signer.d.ts +59 -0
  22. package/dist/src/internal/signer.js +86 -0
  23. package/dist/src/key-store.d.ts +128 -0
  24. package/dist/src/key-store.js +244 -0
  25. package/dist/src/mandates.d.ts +88 -1
  26. package/dist/src/mandates.js +185 -8
  27. package/dist/src/sdk.d.ts +36 -3
  28. package/dist/src/sdk.js +27 -23
  29. package/dist/src/session-store.d.ts +58 -0
  30. package/dist/src/session-store.js +158 -0
  31. package/dist/src/wallet.d.ts +4 -6
  32. package/dist/src/wallet.js +18 -8
  33. package/dist/test/auth-j3.test.d.ts +2 -0
  34. package/dist/test/auth-j3.test.js +360 -0
  35. package/dist/test/compute.test.js +22 -11
  36. package/dist/test/ethos.test.d.ts +2 -0
  37. package/dist/test/ethos.test.js +219 -0
  38. package/dist/test/key-store.test.d.ts +2 -0
  39. package/dist/test/key-store.test.js +161 -0
  40. package/dist/test/mandates.test.d.ts +2 -0
  41. package/dist/test/mandates.test.js +93 -0
  42. package/dist/test/sdk.test.js +64 -30
  43. package/dist/test/signer.test.d.ts +2 -0
  44. package/dist/test/signer.test.js +117 -0
  45. package/dist/test/wallet.test.js +20 -9
  46. package/package.json +4 -3
@@ -0,0 +1,41 @@
1
+ import { type KdfParams } from "@aithos/protocol-client";
2
+ interface HttpClient {
3
+ readonly fetchImpl: typeof fetch;
4
+ readonly authBaseUrl: string;
5
+ }
6
+ export interface RegisterApiInput {
7
+ readonly email: string;
8
+ readonly handle: string;
9
+ readonly displayName: string;
10
+ readonly did: string;
11
+ readonly authKey: Uint8Array;
12
+ readonly authSalt: Uint8Array;
13
+ readonly encSalt: Uint8Array;
14
+ readonly kdf: KdfParams;
15
+ readonly blob: Uint8Array;
16
+ readonly blobNonce: Uint8Array;
17
+ readonly blobVersion: number;
18
+ }
19
+ export interface RegisterApiResponse {
20
+ readonly session: string;
21
+ readonly exp: number;
22
+ }
23
+ export declare function registerAccount(http: HttpClient, input: RegisterApiInput): Promise<RegisterApiResponse>;
24
+ export interface LoginChallengeResponse {
25
+ readonly authSalt: Uint8Array;
26
+ readonly encSalt: Uint8Array;
27
+ readonly kdf: KdfParams;
28
+ }
29
+ export declare function loginChallenge(http: HttpClient, email: string): Promise<LoginChallengeResponse>;
30
+ export interface LoginVerifyResponse {
31
+ readonly session: string;
32
+ readonly exp: number;
33
+ readonly did: string;
34
+ readonly handle: string;
35
+ readonly blob: Uint8Array;
36
+ readonly blobNonce: Uint8Array;
37
+ readonly blobVersion: number;
38
+ }
39
+ export declare function loginVerify(http: HttpClient, email: string, authKey: Uint8Array): Promise<LoginVerifyResponse>;
40
+ export {};
41
+ //# sourceMappingURL=auth-api.d.ts.map
@@ -0,0 +1,82 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright 2026 Mathieu Colla
3
+ // Thin HTTP client over the Aithos auth Lambda.
4
+ //
5
+ // Internal — not exported from the package's public surface. The
6
+ // {@link AithosAuth} class composes these calls with the crypto
7
+ // primitives in `@aithos/protocol-client` and the session store in
8
+ // {@link ./session-store.ts} to expose a high-level
9
+ // signIn / signUp / signInWithGoogle API.
10
+ //
11
+ // Wire format mirrors `aithos/auth/API.md` exactly. All `*_b64` fields
12
+ // are standard-base64 (not URL-safe), padding stripped — see
13
+ // `bytesToB64` / `b64ToBytes` in `@aithos/protocol-client`.
14
+ import { bytesToB64, b64ToBytes, } from "@aithos/protocol-client";
15
+ import { AithosSDKError } from "./types.js";
16
+ async function readError(res, defaultCode) {
17
+ let body = null;
18
+ try {
19
+ body = (await res.json());
20
+ }
21
+ catch {
22
+ /* body not JSON */
23
+ }
24
+ const code = typeof body?.code === "string" ? `auth_${body.code}` : `auth_${defaultCode}`;
25
+ const message = body?.error ?? `${res.status} ${res.statusText || "request failed"}`;
26
+ return new AithosSDKError(code, message, {
27
+ status: res.status,
28
+ ...(body !== null ? { data: body } : {}),
29
+ });
30
+ }
31
+ async function postJson(http, path, body, jwt) {
32
+ const res = await http.fetchImpl(`${http.authBaseUrl}${path}`, {
33
+ method: "POST",
34
+ headers: {
35
+ "content-type": "application/json",
36
+ ...(jwt ? { authorization: `Bearer ${jwt}` } : {}),
37
+ },
38
+ body: JSON.stringify(body),
39
+ });
40
+ if (!res.ok)
41
+ throw await readError(res, "request_failed");
42
+ return (await res.json());
43
+ }
44
+ export async function registerAccount(http, input) {
45
+ return postJson(http, "/auth/register", {
46
+ email: input.email,
47
+ handle: input.handle,
48
+ display_name: input.displayName,
49
+ did: input.did,
50
+ auth_key_b64: bytesToB64(input.authKey),
51
+ auth_salt_b64: bytesToB64(input.authSalt),
52
+ enc_salt_b64: bytesToB64(input.encSalt),
53
+ kdf: input.kdf,
54
+ blob_b64: bytesToB64(input.blob),
55
+ blob_nonce_b64: bytesToB64(input.blobNonce),
56
+ blob_version: input.blobVersion,
57
+ });
58
+ }
59
+ export async function loginChallenge(http, email) {
60
+ const wire = await postJson(http, "/auth/login/challenge", { email });
61
+ return {
62
+ authSalt: b64ToBytes(wire.auth_salt_b64),
63
+ encSalt: b64ToBytes(wire.enc_salt_b64),
64
+ kdf: wire.kdf,
65
+ };
66
+ }
67
+ export async function loginVerify(http, email, authKey) {
68
+ const wire = await postJson(http, "/auth/login/verify", {
69
+ email,
70
+ auth_key_b64: bytesToB64(authKey),
71
+ });
72
+ return {
73
+ session: wire.session,
74
+ exp: wire.exp,
75
+ did: wire.did,
76
+ handle: wire.handle,
77
+ blob: b64ToBytes(wire.blob_b64),
78
+ blobNonce: b64ToBytes(wire.blob_nonce_b64),
79
+ blobVersion: wire.blob_version,
80
+ };
81
+ }
82
+ //# sourceMappingURL=auth-api.js.map
@@ -1,116 +1,155 @@
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 { OwnerSigners } from "./internal/owner-signers.js";
1
5
  /** Default URL of the Aithos auth backend. */
2
6
  export declare const DEFAULT_AUTH_BASE_URL = "https://auth.aithos.be";
3
- /**
4
- * Construction options for {@link AithosAuth}.
5
- */
6
7
  export interface AithosAuthConfig {
7
- /**
8
- * Base URL of the Aithos auth backend. Defaults to
9
- * {@link DEFAULT_AUTH_BASE_URL}. Override for staging or self-hosted
10
- * deployments.
11
- */
12
8
  readonly authBaseUrl?: string;
13
- /**
14
- * Optional `fetch` implementation. Defaults to `globalThis.fetch`. Used
15
- * by tests to inject a mock without monkeypatching globals.
16
- */
17
9
  readonly fetch?: typeof fetch;
18
- /**
19
- * Optional `window`-like object. Defaults to `globalThis.window` when
20
- * available. Provided so node-side tests can assert redirect URLs without
21
- * shimming jsdom.
22
- */
23
10
  readonly window?: Pick<Window, "location" | "history">;
11
+ /** Pluggable JWT-session storage. Defaults to {@link defaultSessionStore}. */
12
+ readonly sessionStore?: AithosSessionStore;
13
+ /** Pluggable key persistence. Defaults to {@link defaultKeyStore}. */
14
+ readonly keyStore?: AithosKeyStore;
24
15
  }
25
16
  /**
26
- * Payload returned by a successful Google sign-in.
27
- *
28
- * Wire-compatible with the auth Lambda's `SsoExchangeResponse`. Field names
29
- * are kept snake_case to match the backend; rationale: avoids an extra
30
- * mapping layer and keeps the SDK transparent if the user opens the
31
- * Network panel.
17
+ * Active Aithos session. Returned by JWT-backed entry points
18
+ * (`signIn`, `signUp`, `handleCallback`). Recovery-file and mandate
19
+ * sign-ins do NOT return an `AithosSession` they yield the lighter
20
+ * {@link OwnerInfo} / {@link DelegateInfo}.
32
21
  */
33
22
  export interface AithosSession {
34
- /** HS256 JWT — send in `Authorization: Bearer <session>` to auth/* and
35
- * app endpoints that consume it. */
36
23
  readonly session: string;
37
- /** JWT expiry, Unix seconds. */
38
24
  readonly exp: number;
39
- /** Aithos DID — `did:aithos:z…`. Stable across all the user's devices. */
40
25
  readonly did: string;
41
- /** User-visible handle (rendered as `@handle`). */
42
26
  readonly handle: string;
43
- /** Encrypted vault, base64. Empty string + version 0 on first sign-in. */
44
27
  readonly blob_b64: string;
45
- /** AES-GCM nonce for the blob, base64 (12 bytes). Empty on first sign-in. */
46
28
  readonly blob_nonce_b64: string;
47
- /** Monotonic blob version. Bumped on every PUT /auth/blob. */
48
29
  readonly blob_version: number;
49
- /** 32-byte vault key, base64. Decrypts {@link blob_b64} via AES-GCM-256. */
50
30
  readonly enc_key_b64: string;
51
- /** True the first time this user signs in. The app should run its
52
- * onboarding flow rather than mounting an empty blob. */
53
31
  readonly is_first_login: boolean;
54
32
  }
55
33
  /**
56
- * Options for {@link AithosAuth.signInWithGoogle}.
34
+ * Public information about the loaded owner identity. Available after
35
+ * any owner-side sign-in (password, Google, recovery), regardless of
36
+ * whether a JWT is also present.
57
37
  */
58
- export interface SignInWithGoogleOptions {
59
- /**
60
- * Opaque deep-link state preserved across the OAuth round-trip and
61
- * surfaced back to the app via `?app_state=…` on the callback URL. Use
62
- * to remember "the user clicked sign-in from /settings/billing" so you
63
- * can restore that route after the redirect chain.
64
- *
65
- * Maximum 1024 characters.
66
- */
67
- readonly appState?: string;
38
+ export interface OwnerInfo {
39
+ readonly did: string;
40
+ readonly handle: string;
41
+ readonly displayName: string;
68
42
  }
69
43
  /**
70
- * Authenticator for the Aithos identity service. One instance per app
71
- * is the recommended pattern (the constructor is cheap; it just trims the
72
- * URL). All methods are pure — no module-global state.
44
+ * Public information about a delegate session held by the SDK. Returned
45
+ * by `importMandate` and `getDelegates`.
73
46
  */
47
+ export interface DelegateInfo {
48
+ readonly mandateId: string;
49
+ readonly subjectDid: string;
50
+ readonly granteeId: string;
51
+ readonly scopes: readonly string[];
52
+ /** ISO-8601, or null when the mandate has no `not_after`. */
53
+ readonly expiresAt: string | null;
54
+ readonly label?: string;
55
+ }
56
+ export interface SignInWithGoogleOptions {
57
+ readonly appState?: string;
58
+ }
59
+ export interface SignInInput {
60
+ readonly email: string;
61
+ readonly password: string;
62
+ }
63
+ export interface SignUpInput {
64
+ readonly email: string;
65
+ readonly password: string;
66
+ readonly handle: string;
67
+ readonly displayName?: string;
68
+ }
69
+ export interface SignUpResult {
70
+ readonly session: AithosSession;
71
+ readonly recoveryFile: Blob;
72
+ readonly recoveryFilename: string;
73
+ }
74
+ export interface SignInWithRecoveryInput {
75
+ /** Recovery file as a Blob (browser File input) or already-decoded JSON string. */
76
+ readonly file: Blob | string;
77
+ }
78
+ export interface ImportMandateInput {
79
+ /** Delegate bundle as a Blob or already-decoded JSON string. */
80
+ readonly bundle: Blob | string;
81
+ }
74
82
  export declare class AithosAuth {
75
- /** Resolved auth base URL with a trailing slash trimmed. */
83
+ #private;
76
84
  readonly authBaseUrl: string;
77
- private readonly fetchImpl;
78
- private readonly win;
79
85
  constructor(config?: AithosAuthConfig);
80
86
  /**
81
- * Redirect the browser to Google's OAuth consent screen. Must be called
82
- * synchronously in response to a user gesture (button click) — most
83
- * browsers block top-level navigation triggered from idle code.
87
+ * Reload signing material and JWT session from the configured stores.
88
+ * Must be called once at app boot before relying on
89
+ * {@link getCurrentSession} / {@link getOwnerInfo} / {@link canSignAsOwner}
90
+ * — until then they reflect only what's been done in-memory in the
91
+ * current tab.
84
92
  *
85
- * Does not return: navigation tears the JS context down. The `never`
86
- * return type tells callers any code after the call is unreachable.
93
+ * Strict consistency: if the JWT and the stored owner disagree about
94
+ * who's signed in, both are wiped and the user re-auths. JWT-less
95
+ * owner state (loaded from keyStore but no JWT) is a valid resumed
96
+ * state — the user signed in via recovery or imported a mandate at
97
+ * some earlier moment and never went through the JWT flow.
87
98
  */
88
- signInWithGoogle(opts?: SignInWithGoogleOptions): never;
99
+ resume(): Promise<void>;
100
+ /** JWT-backed session. Null when signed in via recovery / mandate / not at all. */
101
+ getCurrentSession(): AithosSession | null;
102
+ /** Loaded owner identity. Independent of JWT presence. */
103
+ getOwnerInfo(): OwnerInfo | null;
104
+ getDelegates(): readonly DelegateInfo[];
105
+ canSignAsOwner(): boolean;
106
+ canSignAsDelegateFor(did: string): boolean;
89
107
  /**
90
- * Inspect the current URL for an `aithos_code` query parameter. If it's
91
- * present, exchange it at the backend and return the resulting
92
- * {@link AithosSession}. The query params are stripped from the URL via
93
- * `history.replaceState` so a page refresh doesn't replay the redeem
94
- * (which would 410 anyway).
108
+ * Internal accessor used by sibling SDK namespaces (compute, wallet,
109
+ * ethos) when they need to sign on behalf of the owner. Returns null
110
+ * if no owner is loaded.
95
111
  *
96
- * Returns `null` when there's no code in the URL — safe to call on every
97
- * page load. Throws {@link AithosSDKError} on backend errors or when
98
- * the URL carries `aithos_error=…` (Google denial, token-exchange
99
- * failure, etc.).
112
+ * @internal
100
113
  */
101
- handleCallback(): Promise<AithosSession | null>;
114
+ _getOwnerSigners(): OwnerSigners | null;
102
115
  /**
103
- * Programmatically redeem an `aithos_code` for a session. `handleCallback`
104
- * calls this for you; expose it directly for callers that already pulled
105
- * the code out of the URL via their own router.
116
+ * Internal accessor — looks up an active delegate by mandate id.
117
+ * @internal
106
118
  */
107
- exchange(aithosCode: string): Promise<AithosSession>;
119
+ _getDelegateActor(mandateId: string): DelegateActor | undefined;
108
120
  /**
109
- * Stateless sign-out. The Aithos backend doesn't track sessions, so
110
- * there's nothing to revoke server-side; this method exists so the app
111
- * has a symmetric API surface and to remind callers to clear their
112
- * own storage. The Promise always resolves.
121
+ * Internal accessor finds the first active delegate whose subject
122
+ * matches `did`. Used by `sdk.ethos.of(did)` when the user holds a
123
+ * mandate for that subject.
124
+ * @internal
113
125
  */
126
+ _findDelegateForSubject(did: string): DelegateActor | undefined;
127
+ signIn(input: SignInInput): Promise<AithosSession>;
128
+ signUp(input: SignUpInput): Promise<SignUpResult>;
129
+ /**
130
+ * Sign in by uploading a recovery file. Hydrates the owner signers
131
+ * locally — no JWT is obtained on this path because the recovery
132
+ * file alone doesn't authenticate against the auth backend (no
133
+ * password, no Google session). Apps that need compute/wallet
134
+ * access should follow up with an email+password sign-in or with
135
+ * Google SSO.
136
+ *
137
+ * The recovery file is ALWAYS the file produced by `signUp` (or the
138
+ * equivalent one emitted by `protocol-client`'s `runOnboarding`).
139
+ * Both shapes are accepted.
140
+ */
141
+ signInWithRecovery(input: SignInWithRecoveryInput): Promise<OwnerInfo>;
142
+ /**
143
+ * Import a delegate bundle (`.aithos-delegate.json`). Works in any
144
+ * state: with no owner loaded (delegate-only session), or alongside
145
+ * an existing owner (the user holds mandates for other people's
146
+ * ethoses while also being an owner themselves).
147
+ */
148
+ importMandate(input: ImportMandateInput): Promise<DelegateInfo>;
149
+ removeMandate(mandateId: string): Promise<void>;
150
+ signInWithGoogle(opts?: SignInWithGoogleOptions): never;
151
+ handleCallback(): Promise<AithosSession | null>;
152
+ exchange(aithosCode: string): Promise<AithosSession>;
114
153
  signOut(): Promise<void>;
115
154
  }
116
155
  //# sourceMappingURL=auth.d.ts.map