@rakomi/node 0.0.0 → 0.1.0

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 (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +57 -1
  3. package/SECURITY.md +206 -0
  4. package/dist/agents.d.ts +90 -0
  5. package/dist/agents.js +203 -0
  6. package/dist/anonymous.d.ts +50 -0
  7. package/dist/anonymous.js +105 -0
  8. package/dist/ciba.d.ts +97 -0
  9. package/dist/ciba.js +282 -0
  10. package/dist/client.d.ts +93 -0
  11. package/dist/client.js +202 -0
  12. package/dist/credentials.d.ts +87 -0
  13. package/dist/credentials.js +104 -0
  14. package/dist/device.d.ts +76 -0
  15. package/dist/device.js +244 -0
  16. package/dist/doctor.d.ts +11 -0
  17. package/dist/doctor.js +135 -0
  18. package/dist/dpop-session.d.ts +90 -0
  19. package/dist/dpop-session.js +127 -0
  20. package/dist/dpop.d.ts +24 -0
  21. package/dist/dpop.js +51 -0
  22. package/dist/env-detect.d.ts +11 -0
  23. package/dist/env-detect.js +26 -0
  24. package/dist/errors.d.ts +307 -0
  25. package/dist/errors.js +385 -0
  26. package/dist/eudi.d.ts +23 -0
  27. package/dist/eudi.js +27 -0
  28. package/dist/flags.d.ts +50 -0
  29. package/dist/flags.js +173 -0
  30. package/dist/guards.d.ts +16 -0
  31. package/dist/guards.js +104 -0
  32. package/dist/index.d.ts +30 -0
  33. package/dist/index.js +18 -0
  34. package/dist/internal/canonical-url.d.ts +13 -0
  35. package/dist/internal/canonical-url.js +52 -0
  36. package/dist/internal/shared-constants.d.ts +3 -0
  37. package/dist/internal/shared-constants.js +3 -0
  38. package/dist/jwks-cache.d.ts +31 -0
  39. package/dist/jwks-cache.js +135 -0
  40. package/dist/link.d.ts +73 -0
  41. package/dist/link.js +262 -0
  42. package/dist/middleware.d.ts +21 -0
  43. package/dist/middleware.js +84 -0
  44. package/dist/oauth.d.ts +46 -0
  45. package/dist/oauth.js +457 -0
  46. package/dist/rbac.d.ts +12 -0
  47. package/dist/rbac.js +20 -0
  48. package/dist/token-exchange.d.ts +65 -0
  49. package/dist/token-exchange.js +163 -0
  50. package/dist/types.d.ts +436 -0
  51. package/dist/types.js +1 -0
  52. package/dist/verify-publisher-webhook.d.ts +25 -0
  53. package/dist/verify-publisher-webhook.js +47 -0
  54. package/dist/verify-token.d.ts +3 -0
  55. package/dist/verify-token.js +148 -0
  56. package/dist/verify-webhook.d.ts +7 -0
  57. package/dist/verify-webhook.js +101 -0
  58. package/package.json +61 -5
  59. package/sbom.cdx.json +52 -0
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Per-request environment detection based on hostname.
3
+ * Safe default: production (never accidentally expose verbose errors).
4
+ */
5
+ import type { SdkEnvironment } from './types.js';
6
+ /**
7
+ * Detect environment from request hostname.
8
+ * Returns 'development' for localhost, 127.0.0.1, ::1, *.local.
9
+ * Returns 'production' for everything else (safe default).
10
+ */
11
+ export declare function detectEnvironment(hostname: string): SdkEnvironment;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Per-request environment detection based on hostname.
3
+ * Safe default: production (never accidentally expose verbose errors).
4
+ */
5
+ const DEV_HOSTNAMES = new Set(['localhost', '127.0.0.1', '::1', '[::1]']);
6
+ /**
7
+ * Detect environment from request hostname.
8
+ * Returns 'development' for localhost, 127.0.0.1, ::1, *.local.
9
+ * Returns 'production' for everything else (safe default).
10
+ */
11
+ export function detectEnvironment(hostname) {
12
+ if (!hostname)
13
+ return 'production';
14
+ if (DEV_HOSTNAMES.has(hostname))
15
+ return 'development';
16
+ const host = hostname.includes(']:')
17
+ ? hostname.slice(1, hostname.indexOf(']'))
18
+ : hostname.lastIndexOf(':') > hostname.indexOf(':')
19
+ ? hostname
20
+ : hostname.split(':')[0];
21
+ if (DEV_HOSTNAMES.has(host))
22
+ return 'development';
23
+ if (host.endsWith('.local'))
24
+ return 'development';
25
+ return 'production';
26
+ }
@@ -0,0 +1,307 @@
1
+ import type { SdkError } from './types.js';
2
+ /**
3
+ * Error class thrown by Rakomi constructor for configuration errors.
4
+ * Extends Error so `instanceof Error` works and stack traces are available.
5
+ */
6
+ export declare class RakomiError extends Error {
7
+ readonly code: string;
8
+ readonly suggestion: string;
9
+ readonly docs_url: string;
10
+ readonly fix_command?: string;
11
+ constructor(sdkError: SdkError);
12
+ }
13
+ export declare const ERROR_CODES: {
14
+ readonly TOKEN_REVOKED: "token/revoked";
15
+ readonly TOKEN_EXPIRED: "token/expired";
16
+ readonly TOKEN_INVALID_SIGNATURE: "token/invalid_signature";
17
+ readonly TOKEN_MALFORMED: "token/malformed";
18
+ readonly TOKEN_INVALID_ALGORITHM: "token/invalid_algorithm";
19
+ readonly TOKEN_MISSING_CLAIMS: "token/missing_claims";
20
+ readonly TOKEN_INVALID_ISSUER: "token/invalid_issuer";
21
+ readonly TOKEN_INVALID_AUDIENCE: "token/invalid_audience";
22
+ readonly TOKEN_NOT_YET_VALID: "token/not_yet_valid";
23
+ readonly AUTH_ENVIRONMENT_MISMATCH: "auth/environment_mismatch";
24
+ readonly AUTH_DPOP_PROVER_UNAVAILABLE: "auth/dpop_prover_unavailable";
25
+ readonly AUTH_INVALID_DPOP_PROOF: "auth/invalid_dpop_proof";
26
+ readonly AUTH_INVALID_REFRESH_TOKEN: "auth/invalid_refresh_token";
27
+ readonly AUTH_DPOP_ROTATION_NOOP: "auth/dpop_rotation_noop";
28
+ readonly AUTH_DPOP_ROTATION_DID_NOT_TAKE: "auth/dpop_rotation_did_not_take";
29
+ readonly AUTH_REFRESH_SUPERSEDED_BY_ROTATION: "auth/refresh_superseded_by_rotation";
30
+ readonly JWKS_FETCH_FAILED: "jwks/fetch_failed";
31
+ readonly JWKS_NO_MATCHING_KEY: "jwks/no_matching_key";
32
+ readonly JWKS_INVALID_RESPONSE: "jwks/invalid_response";
33
+ readonly WEBHOOK_TIMESTAMP_TOO_OLD: "webhook/timestamp_too_old";
34
+ readonly WEBHOOK_TIMESTAMP_TOO_NEW: "webhook/timestamp_too_new";
35
+ readonly WEBHOOK_INVALID_SIGNATURE: "webhook/invalid_signature";
36
+ readonly WEBHOOK_INVALID_SECRET: "webhook/invalid_secret";
37
+ readonly WEBHOOK_MISSING_HEADER: "webhook/missing_header";
38
+ readonly WEBHOOK_INVALID_BODY: "webhook/invalid_body";
39
+ };
40
+ export type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];
41
+ export declare const TOKEN_REVOKED: () => SdkError;
42
+ export declare const TOKEN_EXPIRED: () => SdkError;
43
+ export declare const TOKEN_INVALID_SIGNATURE: () => SdkError;
44
+ export declare const TOKEN_MALFORMED: () => SdkError;
45
+ export declare const TOKEN_INVALID_ALGORITHM: () => SdkError;
46
+ export declare const TOKEN_MISSING_CLAIMS: () => SdkError;
47
+ export declare const TOKEN_INVALID_ISSUER: () => SdkError;
48
+ export declare const TOKEN_INVALID_AUDIENCE: () => SdkError;
49
+ export declare const TOKEN_NOT_YET_VALID: () => SdkError;
50
+ export declare const AUTH_ENVIRONMENT_MISMATCH: () => SdkError;
51
+ /**
52
+ * Client-side: this SDK/platform could not produce a DPoP proof here (no native
53
+ * prover, or the signer threw / returned falsy). NOT a security event — the SDK
54
+ * deliberately refused to send an empty/malformed `DPoP` header rather than
55
+ * silently downgrade to Bearer. App action: report a bug / re-login.
56
+ */
57
+ export declare const AUTH_DPOP_PROVER_UNAVAILABLE: (detail?: string) => SdkError;
58
+ /**
59
+ * Server rejected the DPoP proof on the refresh request (signature / `htu` /
60
+ * `htm` / replayed `jti` / cold-start key mismatch). App action: re-attach a
61
+ * fresh proof, check device clock skew, or re-authenticate to re-bind the key.
62
+ */
63
+ export declare const AUTH_INVALID_DPOP_PROOF: (detail?: string) => SdkError;
64
+ /**
65
+ * The refresh token itself is invalid, expired, or has been revoked (the
66
+ * genuine end-of-session class — RFC 6749 `invalid_grant` on the refresh
67
+ * operation). App action: start a full re-authentication (login) flow.
68
+ */
69
+ export declare const AUTH_INVALID_REFRESH_TOKEN: (detail?: string) => SdkError;
70
+ /**
71
+ * Client-side rotation bug: the SDK attempted to rotate to a key the server
72
+ * already has bound (server `400 invalid_request` reason `rotation_noop`). NOT a
73
+ * security event and NO re-login — the session is unchanged. A fresh keypair
74
+ * makes this practically unreachable from the SDK; it surfaces only if a caller
75
+ * forces the new key to equal the old. App action: drop the rotation, keep using
76
+ * the current session.
77
+ */
78
+ export declare const AUTH_DPOP_ROTATION_NOOP: (detail?: string) => SdkError;
79
+ /**
80
+ * The rotation did not take AND no usable refreshed token was obtained. This is the
81
+ * FAILURE arm — distinct from the common rotation-unaware-server case where the
82
+ * refresh itself succeeds (that returns `{ ok: true, data: { ...tokens, rotated: false } }` — a
83
+ * {@link RotationTokenResponse} the caller MUST persist). This error fires
84
+ * only when there is nothing to persist:
85
+ * - a rotation that lost the token-keyed gate to a concurrent ordinary refresh of
86
+ * the same one-time-use token (fail-SAFE, NO network call, OLD key still bound);
87
+ * - the unreachable-by-construction local pre-swap invariant refusing a swap the
88
+ * server DID confirm (a new-key-bound token the SDK has no prover for).
89
+ * The OLD key is STILL bound (never a half-swap). App action: retry the rotation, or
90
+ * re-authenticate if it keeps not taking.
91
+ */
92
+ export declare const AUTH_DPOP_ROTATION_DID_NOT_TAKE: (detail?: string) => SdkError;
93
+ /**
94
+ * A refresh and a key-rotation were attempted concurrently on the SAME
95
+ * refresh_token. The server one-time-uses + rotates the refresh token on
96
+ * every success, so the two operations can never both spend the same token value
97
+ * without tripping server refresh-reuse detection (which revokes ALL session
98
+ * tokens). The SDK serializes every refresh_token-consuming operation through one
99
+ * token-keyed choke point: a key rotation already owns this token, so THIS
100
+ * ordinary refresh was NOT sent (fail-SAFE — no network call, no double-spend).
101
+ * The session/binding are unchanged. App action: use the rotation's result (it
102
+ * also delivers fresh tokens), or retry the refresh with the rotated
103
+ * refresh_token once the rotation settles.
104
+ */
105
+ export declare const AUTH_REFRESH_SUPERSEDED_BY_ROTATION: (detail?: string) => SdkError;
106
+ export declare const JWKS_FETCH_FAILED: (detail?: string) => SdkError;
107
+ export declare const JWKS_NO_MATCHING_KEY: () => SdkError;
108
+ export declare const JWKS_INVALID_RESPONSE: () => SdkError;
109
+ export declare const WEBHOOK_TIMESTAMP_TOO_OLD: (tolerance: number) => SdkError;
110
+ export declare const WEBHOOK_TIMESTAMP_TOO_NEW: (tolerance: number) => SdkError;
111
+ /** @deprecated Use WEBHOOK_TIMESTAMP_TOO_OLD or WEBHOOK_TIMESTAMP_TOO_NEW */
112
+ export declare const WEBHOOK_TIMESTAMP_EXPIRED: () => SdkError;
113
+ export declare const WEBHOOK_INVALID_SIGNATURE: () => SdkError;
114
+ export declare const WEBHOOK_INVALID_SECRET: () => SdkError;
115
+ export declare const WEBHOOK_MISSING_HEADER: () => SdkError;
116
+ export declare const WEBHOOK_INVALID_BODY: () => SdkError;
117
+ export declare const CONFIG_MISSING_API_KEY: () => SdkError;
118
+ export declare const CONFIG_INVALID_BASE_URL: () => SdkError;
119
+ export declare const CONFIG_MISSING_WEBHOOK_SECRET: () => SdkError;
120
+ export declare const OAUTH_INVALID_GRANT: (detail?: string) => SdkError;
121
+ export declare const OAUTH_INVALID_CLIENT: (detail?: string) => SdkError;
122
+ export declare const OAUTH_INVALID_REQUEST: (detail?: string) => SdkError;
123
+ export declare const OAUTH_UNSUPPORTED_GRANT_TYPE: (detail?: string) => SdkError;
124
+ export declare const OAUTH_NETWORK_ERROR: (detail?: string) => SdkError;
125
+ export declare const OAUTH_MISSING_CLIENT_ID: () => SdkError;
126
+ export declare const OAUTH_MISSING_CLIENT_SECRET: () => SdkError;
127
+ export declare const DEVICE_AUTHORIZATION_PENDING: (detail?: string) => SdkError;
128
+ export declare const DEVICE_AUTHORIZATION_SLOW_DOWN: (detail?: string) => SdkError;
129
+ export declare const DEVICE_AUTHORIZATION_DENIED: (detail?: string) => SdkError;
130
+ export declare const DEVICE_AUTHORIZATION_EXPIRED: (detail?: string) => SdkError;
131
+ export declare const DEVICE_AUTHORIZATION_TIMEOUT: (detail?: string) => SdkError;
132
+ export declare const DEVICE_AUTHORIZATION_RATE_LIMITED: (detail?: string) => SdkError;
133
+ export declare const ANONYMOUS_DISABLED: () => SdkError;
134
+ export declare const ANONYMOUS_RATE_LIMITED: (retryAfterSeconds?: number) => SdkError;
135
+ export declare const ANONYMOUS_MAU_EXHAUSTED: () => SdkError;
136
+ export declare const ANONYMOUS_NETWORK_ERROR: (detail?: string) => SdkError;
137
+ /**
138
+ * Thrown by the auto-refresh path when a refresh returns 401 AND the prior
139
+ * access token was marked `is_anonymous: true`. Distinguishes "the tenant
140
+ * purged your anon user" from generic auth failures so apps can prompt a
141
+ * fresh `client.anonymous` rather than sending the user to a login screen.
142
+ *
143
+ * Carries `suggestedAction: 'call_anonymous'` as a stable hint for DX-level
144
+ * UX routing (DX thread).
145
+ */
146
+ export declare class AnonymousSessionExpiredError extends Error {
147
+ readonly code = "anonymous/session_expired";
148
+ readonly suggestedAction: "call_anonymous()";
149
+ readonly suggestion: string;
150
+ readonly docs_url: string;
151
+ constructor(message?: string);
152
+ }
153
+ export declare const ACCOUNT_LINKING_NETWORK_ERROR: (detail?: string) => SdkError;
154
+ export declare const ACCOUNT_LINKING_RATE_LIMITED: (retryAfterSeconds?: number) => SdkError;
155
+ export declare const ACCOUNT_LINKING_IDENTITY_NOT_FOUND: () => SdkError;
156
+ /**
157
+ * Thrown when POST /v1/users/me/link/{provider} returns 403 —
158
+ * tenant has disabled explicit self-service linking.
159
+ */
160
+ export declare class AccountLinkingDisabledError extends Error {
161
+ readonly code = "account_linking/disabled_for_tenant";
162
+ readonly suggestion: string;
163
+ readonly docs_url: string;
164
+ readonly retryAfterSeconds?: number;
165
+ constructor(message?: string);
166
+ }
167
+ /**
168
+ * Thrown when a link attempt finds the identity already owned by a different user.
169
+ * Never include details about the other user — anti-enumeration invariant.
170
+ */
171
+ export declare class IdentityOwnedByOtherUserError extends Error {
172
+ readonly code = "account_linking/identity_owned_by_other_user";
173
+ readonly suggestion: string;
174
+ readonly docs_url: string;
175
+ constructor(message?: string);
176
+ }
177
+ /**
178
+ * Thrown when DELETE /v1/users/me/link/{provider} would remove the user's last
179
+ * sign-in method. Includes the remaining method kinds to inform UX.
180
+ */
181
+ export declare class CannotUnlinkLastMethodError extends Error {
182
+ readonly code = "account_linking/cannot_unlink_last_method";
183
+ readonly suggestion: string;
184
+ readonly docs_url: string;
185
+ readonly remaining_methods: readonly string[];
186
+ constructor(remainingMethods?: readonly string[], message?: string);
187
+ }
188
+ /**
189
+ * Thrown when a high-risk operation is attempted within the 1-hour post-link
190
+ * cooldown window. Semantically distinct from rate-limit
191
+ * 429s: clients should surface a localized "try again at {time}" hint rather
192
+ * than "too many requests".
193
+ *
194
+ * Exposes both `unlockAt: Date` and `unlockAtIso: string` so consumers can
195
+ * format the timestamp without re-parsing.
196
+ */
197
+ export declare class CooldownActiveError extends Error {
198
+ readonly code = "account_linking/cooldown_active";
199
+ readonly suggestion: string;
200
+ readonly docs_url: string;
201
+ readonly unlockAt: Date;
202
+ readonly unlockAtIso: string;
203
+ /**
204
+ * User-facing discriminator so consumer UX can render a distinct copy path
205
+ * ("you recently linked an account") vs. the generic rate-limit message.
206
+ * Populated from the server-side `details.reason` when present.
207
+ */
208
+ readonly reason: 'account_recently_linked' | 'unknown';
209
+ constructor(unlockAtIso: string, reason?: 'account_recently_linked' | 'unknown', message?: string);
210
+ }
211
+ /**
212
+ * Thrown when POST /v1/users/me/link/{provider} returns 401 with
213
+ * `code: 'account_linking/mfa_required'`.
214
+ *
215
+ * @see https://datatracker.ietf.org/doc/html/rfc9470 (Step-Up Authentication
216
+ * Challenge Protocol — conceptual reference; the JSON-body discriminator here
217
+ * mirrors RFC 9470's Bearer `WWW-Authenticate` step-up signal for cookie-session
218
+ * APIs.)
219
+ *
220
+ * In SDK 0.11.0 the canonical post-401 discriminator is `next_action: 'verify_mfa'`
221
+ * (mirrors Stripe `payment_intent.next_action` / AWS Cognito `ChallengeName`).
222
+ * The `mfa_challenge_token` getter is RETAINED for backward compatibility with
223
+ * 0.10.x consumers but will be removed in 0.12.0 — switch to `next_action`.
224
+ *
225
+ * Calling code: guide the user through `POST /v1/auth/step-up/password`, then
226
+ * retry the link initiate with the resulting `X-Step-Up-Token` header.
227
+ *
228
+ * Note: this error is ONLY thrown for users who CAN satisfy step-up (i.e. have
229
+ * a password set). Passwordless users (magic-link / OTP / passkey-only) get the
230
+ * complementary {@link MfaStepUpUnavailableError} instead.
231
+ */
232
+ export declare class MfaStepUpRequiredError extends Error {
233
+ readonly code = "account_linking/mfa_required";
234
+ readonly suggestion: string;
235
+ readonly docs_url: string;
236
+ /**
237
+ * Canonical post-401 discriminator. Always equals the literal `'verify_mfa'`
238
+ * in SDK 0.11.x. Reserved future literals: `'verify_passkey'`,
239
+ * `'verify_magic_link'`, `'verify_otp'`, `'verify_eudi_wallet'`.
240
+ */
241
+ readonly next_action: "verify_mfa";
242
+ /**
243
+ * The set of step-up issuance routes the current user can satisfy. The
244
+ * client SHOULD pick one and call the corresponding `/v1/auth/step-up/*`
245
+ * route. Order is a HINT, not a guarantee — the server may personalize
246
+ * ordering by recent success rate or admin policy.
247
+ *
248
+ * Open union: backend-additive values (e.g. `ciba_push`, `eudi_wallet`)
249
+ * MUST NOT trigger a SemVer-major bump for SDK consumers — the
250
+ * `(string & {})` escape hatch keeps the literal-aware autocomplete
251
+ * experience while accepting unknown strings at runtime.
252
+ *
253
+ * Optional: older API responses (servers older than 2026-04-26)
254
+ * lack this field; consumers MUST handle `undefined` by falling through to
255
+ * the legacy `next_action === 'verify_mfa'` flow.
256
+ */
257
+ readonly availableMethods?: ReadonlyArray<'password' | 'passkey' | 'magic_link' | 'email_otp' | (string & {})>;
258
+ private readonly _mfaChallengeToken;
259
+ /**
260
+ * @deprecated Will be removed in 0.12.0+ once the dual-emit window expires
261
+ * (2026-07-24). Switch to `next_action === 'verify_mfa'`. In 0.10.x this field carried the
262
+ * literal string `'mfa_step_up_required'` (a routing marker, never a nonce);
263
+ * the value is preserved here so existing consumers continue working through
264
+ * the dual-emit window.
265
+ */
266
+ get mfa_challenge_token(): string;
267
+ constructor(mfaChallengeToken?: string, message?: string, availableMethods?: ReadonlyArray<'password' | 'passkey' | 'magic_link' | 'email_otp' | (string & {})>);
268
+ }
269
+ /**
270
+ * Thrown when POST /v1/users/me/link/{provider} returns 401 with
271
+ * `code: 'account_linking/mfa_step_up_unavailable'` — a passwordless user
272
+ * (magic-link / email-OTP / passkey-only) has MFA enabled but no step-up
273
+ * issuance route exists for their authenticator class today (only
274
+ * `POST /v1/auth/step-up/password` ships in 0.11.x).
275
+ *
276
+ * @see https://datatracker.ietf.org/doc/html/rfc9470
277
+ *
278
+ * The complement of {@link MfaStepUpRequiredError}: together the two classes
279
+ * partition the 401-with-MFA-enabled space. UX should surface a "complete
280
+ * account setup (set a password)" affordance — NOT a promise of a passwordless
281
+ * step-up flow (none exists in 0.11.x).
282
+ */
283
+ export declare class MfaStepUpUnavailableError extends Error {
284
+ readonly code = "account_linking/mfa_step_up_unavailable";
285
+ readonly suggestion: string;
286
+ readonly docs_url: string;
287
+ /**
288
+ * Extensible enum — additional reasons reserved for future passwordless
289
+ * step-up flows. The recognised reasons include
290
+ * `'no_step_up_authenticator_available'`, emitted when the server finds
291
+ * zero satisfiable methods (defensive tripwire).
292
+ */
293
+ readonly reason: string;
294
+ constructor(reason?: string, message?: string);
295
+ }
296
+ /**
297
+ * Thrown when an OAuth callback rejects a state row that expired or was
298
+ * already used. The SDK typically surfaces this via the
299
+ * link-list read (consumers infer the failure from a `?error=link_state_expired`
300
+ * query string). Direct API consumers get the typed class.
301
+ */
302
+ export declare class LinkStateExpiredError extends Error {
303
+ readonly code = "account_linking/link_state_expired_or_missing";
304
+ readonly suggestion: string;
305
+ readonly docs_url: string;
306
+ constructor(message?: string);
307
+ }