@authyon/auth 0.2.0-beta.12 → 0.2.0-beta.15

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 CHANGED
@@ -273,6 +273,29 @@ Métodos de sessão/auth ficam soltos no client; os que giram em torno de um rec
273
273
  | `sso.startUrl(provider, params)` | monta a URL de `GET /auth/sso/{provider}/start` (não faz a chamada — navegue até ela) |
274
274
  | `sso.exchange(code)` | `POST /auth/sso/exchange` |
275
275
 
276
+ ### `authyon.security` (aprovação com OTP/passkey)
277
+
278
+ `security.approvals`: o cliente aprova o que o seu backend pediu
279
+ (`@authyon/server` → `security.approvals.create`, com um `payload` livre) usando o código
280
+ do app autenticador ou a passkey. Para só validar um código, use `security.otp.check` no
281
+ `@authyon/server`. Guia completo: [`docs/security-otp-approvals.md`](../../docs/security-otp-approvals.md).
282
+
283
+ ```ts
284
+ const pending = await authyon.security.approvals.get(approvalId);
285
+ // mostre pending.payload ao cliente
286
+ await authyon.security.approvals.confirm(approvalId, {
287
+ method: "authenticator",
288
+ code: "123456",
289
+ });
290
+ ```
291
+
292
+ | Método | Endpoint |
293
+ | ---------------------------------------- | ------------------------------------------------- |
294
+ | `security.approvals.get(id)` | `GET /auth/authorizations/{id}` |
295
+ | `security.approvals.webauthnOptions(id)` | `POST /auth/authorizations/{id}/webauthn/options` |
296
+ | `security.approvals.confirm(id, input?)` | `POST /auth/authorizations/{id}/confirm` |
297
+ | `security.approvals.reject(id)` | `POST /auth/authorizations/{id}/reject` |
298
+
276
299
  ## Invalidação de token
277
300
 
278
301
  - **Sessão atual**: `logout()` revoga o refresh token atual; `logout({ everywhere: true })` revoga todos os refresh tokens do usuário.
@@ -1,3 +1,58 @@
1
+ /** Lifecycle of an approval. */
2
+ type ApprovalStatus = "pending" | "approved" | "denied" | "consumed" | "expired";
3
+ /**
4
+ * Free-form JSON object describing what the customer approves (up to 16 KiB).
5
+ * The API stores it, shows it back to the customer and binds its canonical
6
+ * hash (keys sorted) to the approval.
7
+ */
8
+ type ApprovalPayload = Record<string, unknown>;
9
+ /** Evidence of how the customer proved their identity when approving. */
10
+ interface ApprovalAssurance {
11
+ /** `urn:authyon:loa:3` (passkey) or `urn:authyon:loa:2` (authenticator code). */
12
+ acr: string;
13
+ /** Authentication methods, e.g. `["pwd", "otp"]` or `["pwd", "webauthn"]`. */
14
+ amr: string[];
15
+ /** When the second factor was verified (ISO 8601). */
16
+ authTime: string;
17
+ }
18
+ /** A request for the customer to approve something with a second factor. */
19
+ interface Approval {
20
+ id: string;
21
+ status: ApprovalStatus;
22
+ subjectId: string;
23
+ tenantId?: string | null;
24
+ /** The payload given to `create()`, with keys in canonical order. */
25
+ payload: ApprovalPayload;
26
+ /** SHA-256 (hex) of the canonical payload. */
27
+ payloadHash: string;
28
+ createdAt: string;
29
+ expiresAt: string;
30
+ decidedAt?: string | null;
31
+ consumedAt?: string | null;
32
+ /** Present once approved. */
33
+ assurance?: ApprovalAssurance | null;
34
+ }
35
+ /** Second factor sent with `confirm()`. */
36
+ type ConfirmApprovalInput = {
37
+ method: "authenticator";
38
+ /** Six-digit code from the customer's authenticator app. */
39
+ code: string;
40
+ } | {
41
+ method: "webauthn";
42
+ /** Assertion for the options returned by `webauthnOptions()`. */
43
+ webAuthn: {
44
+ ceremonyToken: string;
45
+ /** JSON-serialized `PublicKeyCredential` from `navigator.credentials.get()`. */
46
+ assertionJson: string;
47
+ };
48
+ };
49
+ /** Passkey assertion options for approving one approval. */
50
+ interface ApprovalWebAuthnOptions {
51
+ ceremonyToken: string;
52
+ /** JSON `PublicKeyCredentialRequestOptions` to pass to `navigator.credentials.get()`. */
53
+ optionsJson: string;
54
+ }
55
+
1
56
  interface HttpAdapterRequest {
2
57
  url: string;
3
58
  method: string;
@@ -55,6 +110,30 @@ declare class LoggingHttpAdapter implements HttpAdapter {
55
110
  private log;
56
111
  }
57
112
 
113
+ /** Customer-side operations on an approval (`/auth/authorizations`). */
114
+ interface SubjectApprovals {
115
+ /** GET /auth/authorizations/{id} — the pending payload, to show before asking for the code. */
116
+ get(id: string): Promise<Approval>;
117
+ /** POST /auth/authorizations/{id}/webauthn/options — passkey options for approving it. */
118
+ webauthnOptions(id: string): Promise<ApprovalWebAuthnOptions>;
119
+ /**
120
+ * POST /auth/authorizations/{id}/confirm — approves it.
121
+ *
122
+ * Pass the authenticator code or passkey assertion to verify the second
123
+ * factor inline (recommended). Without `input`, the session itself must
124
+ * have used a passkey or authenticator code within the last 5 minutes,
125
+ * otherwise the API answers `step_up_required`.
126
+ */
127
+ confirm(id: string, input?: ConfirmApprovalInput): Promise<Approval>;
128
+ /** POST /auth/authorizations/{id}/reject — the customer declines it. */
129
+ reject(id: string): Promise<Approval>;
130
+ }
131
+ /** The `security` namespace an end-user client exposes. */
132
+ interface SubjectSecurity {
133
+ /** Approve or reject what your backend asked the customer to confirm. */
134
+ readonly approvals: SubjectApprovals;
135
+ }
136
+
58
137
  interface PaginationOptions {
59
138
  skip?: number;
60
139
  take?: number;
@@ -518,6 +597,13 @@ declare class AuthyonClient {
518
597
  assertionStart: (challengeToken: string) => Promise<WebAuthnCeremonyStart>;
519
598
  };
520
599
  };
600
+ /**
601
+ * `security.approvals`: approve or reject what your backend asked the
602
+ * customer to confirm (`@authyon/server` → `security.approvals.create`).
603
+ * Show the payload with `get()`, then `confirm()` with the customer's
604
+ * authenticator code or passkey, or `reject()`.
605
+ */
606
+ readonly security: SubjectSecurity;
521
607
  /**
522
608
  * POST /auth/introspect — lightweight token introspection (RFC 7662).
523
609
  *
@@ -642,4 +728,4 @@ declare function hasPermissionGroup(source: AuthyonPermissionSource | readonly s
642
728
  /** Converts Authyon's `subject:action` permission strings to authorization rules. */
643
729
  declare function createAuthyonRules(source?: AuthyonPermissionSource, options?: AuthyonAbilityOptions): AbilityRule[];
644
730
 
645
- export { createAuthyonAbility as $, AuthyonClient as A, type Role as B, type CreateOrganizationInput as C, type SessionControllerOptions as D, type SessionInfo as E, FetchHttpAdapter as F, type SessionSnapshot as G, type HttpAdapter as H, type IntrospectResult as I, type SessionSnapshotListener as J, type SessionStatus as K, LoggingHttpAdapter as L, type SsoProvider as M, type TwoFactorChallenge as N, type Organization as O, type Paged as P, type TwoFactorMethod as Q, type RegisterInput as R, type Session as S, type TokenStorage as T, type TwoFactorStatus as U, type User as V, type ValidateResult as W, type VerifyTwoFactorInput as X, type WebAuthnAssertion as Y, type WebAuthnCeremonyStart as Z, type WebAuthnCredential as _, type HttpLoggerOptions as a, createAuthyonRules as a0, createClient as a1, hasPermission as a2, hasPermissionGroup as a3, type AbilityConditions as b, type AbilityEvent as c, type AbilityListener as d, type AbilityRule as e, type AbilitySubject as f, type Activity as g, type AuthEvent as h, type AuthState as i, type AuthStateListener as j, type AuthenticatorSetup as k, AuthyonAbility as l, AuthyonAbilityBuilder as m, type AuthyonAbilityOptions as n, type AuthyonClientOptions as o, type AuthyonPermissionSource as p, AuthyonSessionController as q, type HttpAdapterRequest as r, type HttpLogEvent as s, type HttpLogger as t, type InviteMemberInput as u, type LoginInput as v, type LoginResult as w, type OrganizationMember as x, type PaginationOptions as y, type PermissionGroup as z };
731
+ export { type TwoFactorMethod as $, AuthyonClient as A, type InviteMemberInput as B, type ConfirmApprovalInput as C, type LoginInput as D, type LoginResult as E, FetchHttpAdapter as F, type OrganizationMember as G, type HttpAdapter as H, type IntrospectResult as I, type PaginationOptions as J, type PermissionGroup as K, LoggingHttpAdapter as L, type Role as M, type SessionControllerOptions as N, type Organization as O, type Paged as P, type SessionInfo as Q, type RegisterInput as R, type Session as S, type TokenStorage as T, type SessionSnapshot as U, type SessionSnapshotListener as V, type SessionStatus as W, type SsoProvider as X, type SubjectApprovals as Y, type SubjectSecurity as Z, type TwoFactorChallenge as _, type HttpLoggerOptions as a, type TwoFactorStatus as a0, type User as a1, type ValidateResult as a2, type VerifyTwoFactorInput as a3, type WebAuthnAssertion as a4, type WebAuthnCeremonyStart as a5, type WebAuthnCredential as a6, createAuthyonAbility as a7, createAuthyonRules as a8, createClient as a9, hasPermission as aa, hasPermissionGroup as ab, type AbilityConditions as b, type AbilityEvent as c, type AbilityListener as d, type AbilityRule as e, type AbilitySubject as f, type Activity as g, type Approval as h, type ApprovalAssurance as i, type ApprovalPayload as j, type ApprovalStatus as k, type ApprovalWebAuthnOptions as l, type AuthEvent as m, type AuthState as n, type AuthStateListener as o, type AuthenticatorSetup as p, AuthyonAbility as q, AuthyonAbilityBuilder as r, type AuthyonAbilityOptions as s, type AuthyonClientOptions as t, type AuthyonPermissionSource as u, AuthyonSessionController as v, type CreateOrganizationInput as w, type HttpAdapterRequest as x, type HttpLogEvent as y, type HttpLogger as z };
@@ -1,3 +1,58 @@
1
+ /** Lifecycle of an approval. */
2
+ type ApprovalStatus = "pending" | "approved" | "denied" | "consumed" | "expired";
3
+ /**
4
+ * Free-form JSON object describing what the customer approves (up to 16 KiB).
5
+ * The API stores it, shows it back to the customer and binds its canonical
6
+ * hash (keys sorted) to the approval.
7
+ */
8
+ type ApprovalPayload = Record<string, unknown>;
9
+ /** Evidence of how the customer proved their identity when approving. */
10
+ interface ApprovalAssurance {
11
+ /** `urn:authyon:loa:3` (passkey) or `urn:authyon:loa:2` (authenticator code). */
12
+ acr: string;
13
+ /** Authentication methods, e.g. `["pwd", "otp"]` or `["pwd", "webauthn"]`. */
14
+ amr: string[];
15
+ /** When the second factor was verified (ISO 8601). */
16
+ authTime: string;
17
+ }
18
+ /** A request for the customer to approve something with a second factor. */
19
+ interface Approval {
20
+ id: string;
21
+ status: ApprovalStatus;
22
+ subjectId: string;
23
+ tenantId?: string | null;
24
+ /** The payload given to `create()`, with keys in canonical order. */
25
+ payload: ApprovalPayload;
26
+ /** SHA-256 (hex) of the canonical payload. */
27
+ payloadHash: string;
28
+ createdAt: string;
29
+ expiresAt: string;
30
+ decidedAt?: string | null;
31
+ consumedAt?: string | null;
32
+ /** Present once approved. */
33
+ assurance?: ApprovalAssurance | null;
34
+ }
35
+ /** Second factor sent with `confirm()`. */
36
+ type ConfirmApprovalInput = {
37
+ method: "authenticator";
38
+ /** Six-digit code from the customer's authenticator app. */
39
+ code: string;
40
+ } | {
41
+ method: "webauthn";
42
+ /** Assertion for the options returned by `webauthnOptions()`. */
43
+ webAuthn: {
44
+ ceremonyToken: string;
45
+ /** JSON-serialized `PublicKeyCredential` from `navigator.credentials.get()`. */
46
+ assertionJson: string;
47
+ };
48
+ };
49
+ /** Passkey assertion options for approving one approval. */
50
+ interface ApprovalWebAuthnOptions {
51
+ ceremonyToken: string;
52
+ /** JSON `PublicKeyCredentialRequestOptions` to pass to `navigator.credentials.get()`. */
53
+ optionsJson: string;
54
+ }
55
+
1
56
  interface HttpAdapterRequest {
2
57
  url: string;
3
58
  method: string;
@@ -55,6 +110,30 @@ declare class LoggingHttpAdapter implements HttpAdapter {
55
110
  private log;
56
111
  }
57
112
 
113
+ /** Customer-side operations on an approval (`/auth/authorizations`). */
114
+ interface SubjectApprovals {
115
+ /** GET /auth/authorizations/{id} — the pending payload, to show before asking for the code. */
116
+ get(id: string): Promise<Approval>;
117
+ /** POST /auth/authorizations/{id}/webauthn/options — passkey options for approving it. */
118
+ webauthnOptions(id: string): Promise<ApprovalWebAuthnOptions>;
119
+ /**
120
+ * POST /auth/authorizations/{id}/confirm — approves it.
121
+ *
122
+ * Pass the authenticator code or passkey assertion to verify the second
123
+ * factor inline (recommended). Without `input`, the session itself must
124
+ * have used a passkey or authenticator code within the last 5 minutes,
125
+ * otherwise the API answers `step_up_required`.
126
+ */
127
+ confirm(id: string, input?: ConfirmApprovalInput): Promise<Approval>;
128
+ /** POST /auth/authorizations/{id}/reject — the customer declines it. */
129
+ reject(id: string): Promise<Approval>;
130
+ }
131
+ /** The `security` namespace an end-user client exposes. */
132
+ interface SubjectSecurity {
133
+ /** Approve or reject what your backend asked the customer to confirm. */
134
+ readonly approvals: SubjectApprovals;
135
+ }
136
+
58
137
  interface PaginationOptions {
59
138
  skip?: number;
60
139
  take?: number;
@@ -518,6 +597,13 @@ declare class AuthyonClient {
518
597
  assertionStart: (challengeToken: string) => Promise<WebAuthnCeremonyStart>;
519
598
  };
520
599
  };
600
+ /**
601
+ * `security.approvals`: approve or reject what your backend asked the
602
+ * customer to confirm (`@authyon/server` → `security.approvals.create`).
603
+ * Show the payload with `get()`, then `confirm()` with the customer's
604
+ * authenticator code or passkey, or `reject()`.
605
+ */
606
+ readonly security: SubjectSecurity;
521
607
  /**
522
608
  * POST /auth/introspect — lightweight token introspection (RFC 7662).
523
609
  *
@@ -642,4 +728,4 @@ declare function hasPermissionGroup(source: AuthyonPermissionSource | readonly s
642
728
  /** Converts Authyon's `subject:action` permission strings to authorization rules. */
643
729
  declare function createAuthyonRules(source?: AuthyonPermissionSource, options?: AuthyonAbilityOptions): AbilityRule[];
644
730
 
645
- export { createAuthyonAbility as $, AuthyonClient as A, type Role as B, type CreateOrganizationInput as C, type SessionControllerOptions as D, type SessionInfo as E, FetchHttpAdapter as F, type SessionSnapshot as G, type HttpAdapter as H, type IntrospectResult as I, type SessionSnapshotListener as J, type SessionStatus as K, LoggingHttpAdapter as L, type SsoProvider as M, type TwoFactorChallenge as N, type Organization as O, type Paged as P, type TwoFactorMethod as Q, type RegisterInput as R, type Session as S, type TokenStorage as T, type TwoFactorStatus as U, type User as V, type ValidateResult as W, type VerifyTwoFactorInput as X, type WebAuthnAssertion as Y, type WebAuthnCeremonyStart as Z, type WebAuthnCredential as _, type HttpLoggerOptions as a, createAuthyonRules as a0, createClient as a1, hasPermission as a2, hasPermissionGroup as a3, type AbilityConditions as b, type AbilityEvent as c, type AbilityListener as d, type AbilityRule as e, type AbilitySubject as f, type Activity as g, type AuthEvent as h, type AuthState as i, type AuthStateListener as j, type AuthenticatorSetup as k, AuthyonAbility as l, AuthyonAbilityBuilder as m, type AuthyonAbilityOptions as n, type AuthyonClientOptions as o, type AuthyonPermissionSource as p, AuthyonSessionController as q, type HttpAdapterRequest as r, type HttpLogEvent as s, type HttpLogger as t, type InviteMemberInput as u, type LoginInput as v, type LoginResult as w, type OrganizationMember as x, type PaginationOptions as y, type PermissionGroup as z };
731
+ export { type TwoFactorMethod as $, AuthyonClient as A, type InviteMemberInput as B, type ConfirmApprovalInput as C, type LoginInput as D, type LoginResult as E, FetchHttpAdapter as F, type OrganizationMember as G, type HttpAdapter as H, type IntrospectResult as I, type PaginationOptions as J, type PermissionGroup as K, LoggingHttpAdapter as L, type Role as M, type SessionControllerOptions as N, type Organization as O, type Paged as P, type SessionInfo as Q, type RegisterInput as R, type Session as S, type TokenStorage as T, type SessionSnapshot as U, type SessionSnapshotListener as V, type SessionStatus as W, type SsoProvider as X, type SubjectApprovals as Y, type SubjectSecurity as Z, type TwoFactorChallenge as _, type HttpLoggerOptions as a, type TwoFactorStatus as a0, type User as a1, type ValidateResult as a2, type VerifyTwoFactorInput as a3, type WebAuthnAssertion as a4, type WebAuthnCeremonyStart as a5, type WebAuthnCredential as a6, createAuthyonAbility as a7, createAuthyonRules as a8, createClient as a9, hasPermission as aa, hasPermissionGroup as ab, type AbilityConditions as b, type AbilityEvent as c, type AbilityListener as d, type AbilityRule as e, type AbilitySubject as f, type Activity as g, type Approval as h, type ApprovalAssurance as i, type ApprovalPayload as j, type ApprovalStatus as k, type ApprovalWebAuthnOptions as l, type AuthEvent as m, type AuthState as n, type AuthStateListener as o, type AuthenticatorSetup as p, AuthyonAbility as q, AuthyonAbilityBuilder as r, type AuthyonAbilityOptions as s, type AuthyonClientOptions as t, type AuthyonPermissionSource as u, AuthyonSessionController as v, type CreateOrganizationInput as w, type HttpAdapterRequest as x, type HttpLogEvent as y, type HttpLogger as z };
package/dist/index.cjs CHANGED
@@ -40,6 +40,23 @@ __export(index_exports, {
40
40
  });
41
41
  module.exports = __toCommonJS(index_exports);
42
42
 
43
+ // ../../internal/core/security/approvals.ts
44
+ function path(id, suffix = "") {
45
+ if (typeof id !== "string" || !id.trim())
46
+ throw new TypeError("Authyon: an approval id is required.");
47
+ return `/auth/authorizations/${encodeURIComponent(id)}${suffix}`;
48
+ }
49
+ function subjectSecurity(request) {
50
+ return {
51
+ approvals: {
52
+ get: (id) => request(path(id)),
53
+ webauthnOptions: (id) => request(path(id, "/webauthn/options"), { method: "POST" }),
54
+ confirm: (id, input) => request(path(id, "/confirm"), { method: "POST", body: input }),
55
+ reject: (id) => request(path(id, "/reject"), { method: "POST" })
56
+ }
57
+ };
58
+ }
59
+
43
60
  // ../../internal/core/errors/authyonError.ts
44
61
  var ErrorCodes = {
45
62
  Unknown: "unknown",
@@ -52,16 +69,44 @@ var ErrorCodes = {
52
69
  EmailTaken: "user.email_taken",
53
70
  PasswordWeak: "user.password_weak",
54
71
  PasswordPwned: "user.password_pwned",
55
- RateLimited: "rate_limited"
72
+ RateLimited: "rate_limited",
73
+ StepUpRequired: "step_up_required",
74
+ InvalidSecondFactorCode: "invalid_code",
75
+ VerificationAttemptsExhausted: "verification_attempts_exhausted",
76
+ MethodNotEnrolled: "method_not_enrolled",
77
+ InvalidApprovalState: "invalid_authorization_state",
78
+ PayloadMismatch: "payload_mismatch",
79
+ ApprovalNotConsumable: "authorization_not_consumable",
80
+ IdempotencyConflict: "idempotency_conflict",
81
+ UserDisabled: "user_disabled",
82
+ UserNotFound: "user_not_found",
83
+ TemporarilyUnavailable: "temporarily_unavailable",
84
+ InsufficientScope: "insufficient_scope",
85
+ ApprovalNotFound: "authorization_not_found",
86
+ SessionRevoked: "session_revoked",
87
+ TwoFactorTooManyFailures: "user.2fa.too_many_failures"
56
88
  };
89
+ var STANDARD_ERROR_FIELDS = /* @__PURE__ */ new Set([
90
+ "type",
91
+ "status",
92
+ "title",
93
+ "detail",
94
+ "code",
95
+ "error",
96
+ "error_description"
97
+ ]);
57
98
  var AuthyonError = class extends Error {
58
99
  constructor(status, body, options = {}) {
59
- super(body.detail ?? body.title ?? `Authyon request failed with status ${status}`);
100
+ const detail = body.detail ?? body.error_description;
101
+ super(detail ?? body.title ?? `Authyon request failed with status ${status}`);
60
102
  this.name = "AuthyonError";
61
103
  this.status = status;
62
- this.code = body.code ?? "unknown";
104
+ this.code = body.code ?? body.error ?? "unknown";
63
105
  this.title = body.title ?? "Error";
64
- this.detail = body.detail;
106
+ this.detail = detail;
107
+ this.extensions = Object.fromEntries(
108
+ Object.entries(body).filter(([key]) => !STANDARD_ERROR_FIELDS.has(key))
109
+ );
65
110
  this.requestId = options.requestId;
66
111
  this.retryAfter = options.retryAfter;
67
112
  this.cause = options.cause;
@@ -111,6 +156,7 @@ var AuthyonError = class extends Error {
111
156
  };
112
157
  function classifyError(status, code) {
113
158
  if (code === ErrorCodes.NetworkError) return "network";
159
+ if (code === ErrorCodes.StepUpRequired) return "authentication";
114
160
  if (code === ErrorCodes.Timeout || status === 408) return "timeout";
115
161
  if (code === ErrorCodes.RateLimited || status === 429) return "rate_limit";
116
162
  if (code === ErrorCodes.EmailTaken || status === 409) return "conflict";
@@ -204,14 +250,14 @@ var DEFAULT_BASE_URL = "https://api.authyon.com";
204
250
  var DEFAULT_EXPIRY_SKEW_MS = 3e4;
205
251
 
206
252
  // ../../internal/core/http/query.ts
207
- function appendQuery(path, params) {
208
- if (!params) return path;
253
+ function appendQuery(path2, params) {
254
+ if (!params) return path2;
209
255
  const query = new URLSearchParams();
210
256
  for (const [key, value2] of Object.entries(params)) {
211
257
  if (value2 !== void 0) query.set(key, String(value2));
212
258
  }
213
259
  const value = query.toString();
214
- return value ? `${path}${path.includes("?") ? "&" : "?"}${value}` : path;
260
+ return value ? `${path2}${path2.includes("?") ? "&" : "?"}${value}` : path2;
215
261
  }
216
262
 
217
263
  // ../../internal/core/http/httpAdapter.ts
@@ -280,7 +326,6 @@ function sanitizeUrl(value) {
280
326
  url.username = "";
281
327
  url.password = "";
282
328
  url.hash = "";
283
- url.pathname = url.pathname.replace(/(\/platform\/workspace-invites\/)[^/]+/u, "$1REDACTED");
284
329
  const queryKeys = /* @__PURE__ */ new Set();
285
330
  url.searchParams.forEach((_value, key) => queryKeys.add(key));
286
331
  url.search = queryKeys.size > 0 ? [...queryKeys].map((key) => `${encodeURIComponent(key)}=REDACTED`).join("&") : "";
@@ -310,12 +355,12 @@ function createSharedTransport(options) {
310
355
  const httpAdapter = options.httpLogger ? new LoggingHttpAdapter(baseAdapter, options.httpLogger) : baseAdapter;
311
356
  return {
312
357
  baseUrl,
313
- async request(path, init) {
358
+ async request(path2, init) {
314
359
  const controller = new AbortController();
315
360
  const timeout = timeoutMs > 0 ? setTimeout(() => controller.abort(), timeoutMs) : void 0;
316
361
  try {
317
362
  return await httpAdapter.request({
318
- url: `${baseUrl}${path}`,
363
+ url: `${baseUrl}${path2}`,
319
364
  method: init.method ?? "GET",
320
365
  headers: normalizeHeaders(init.headers),
321
366
  body: init.body,
@@ -373,11 +418,11 @@ var JsonHttpClient = class {
373
418
  constructor(transport) {
374
419
  this.transport = transport;
375
420
  }
376
- async send(path, options = {}) {
421
+ async send(path2, options = {}) {
377
422
  const headers = { ...options.headers };
378
423
  if (options.body !== void 0) headers["Content-Type"] = "application/json";
379
424
  try {
380
- return await this.transport.request(appendQuery(path, options.query), {
425
+ return await this.transport.request(appendQuery(path2, options.query), {
381
426
  method: options.method ?? "GET",
382
427
  headers,
383
428
  body: options.body !== void 0 ? JSON.stringify(options.body) : void 0
@@ -391,7 +436,9 @@ var JsonHttpClient = class {
391
436
  if (!response.ok) {
392
437
  let body = {};
393
438
  try {
394
- body = await response.json();
439
+ const parsed = await response.json();
440
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
441
+ body = parsed;
395
442
  } catch {
396
443
  }
397
444
  throw new AuthyonError(response.status, body, responseMetadata(response));
@@ -399,8 +446,8 @@ var JsonHttpClient = class {
399
446
  if (response.status === 204) return void 0;
400
447
  return await response.json();
401
448
  }
402
- async request(path, options = {}) {
403
- return this.parse(await this.send(path, options));
449
+ async request(path2, options = {}) {
450
+ return this.parse(await this.send(path2, options));
404
451
  }
405
452
  };
406
453
 
@@ -638,6 +685,16 @@ var AuthyonClient = class {
638
685
  })
639
686
  }
640
687
  };
688
+ // ── Security (customer side) ─────────────────────────────────────────────
689
+ /**
690
+ * `security.approvals`: approve or reject what your backend asked the
691
+ * customer to confirm (`@authyon/server` → `security.approvals.create`).
692
+ * Show the payload with `get()`, then `confirm()` with the customer's
693
+ * authenticator code or passkey, or `reject()`.
694
+ */
695
+ this.security = subjectSecurity(
696
+ (path2, options) => this.request(path2, { ...options, bearer: true })
697
+ );
641
698
  if (!options.envKey)
642
699
  throw new Error("Authyon: `envKey` is required (pk_live_... / pk_test_...)");
643
700
  this.envKey = options.envKey;
@@ -751,7 +808,7 @@ var AuthyonClient = class {
751
808
  }
752
809
  }
753
810
  // ── HTTP core ────────────────────────────────────────────────────────────
754
- async request(path, options = {}, isRetry = false) {
811
+ async request(path2, options = {}, isRetry = false) {
755
812
  const headers = {
756
813
  "X-Authyon-Environment": this.envKey,
757
814
  ...clientIpHeaders({ clientIp: this.clientIp }),
@@ -766,7 +823,7 @@ var AuthyonClient = class {
766
823
  });
767
824
  headers.Authorization = `Bearer ${token}`;
768
825
  }
769
- const response = await this.http.send(path, { ...options, headers });
826
+ const response = await this.http.send(path2, { ...options, headers });
770
827
  if (response.status === 401 && options.bearer && this.autoRefresh && !isRetry && this.getSession()) {
771
828
  try {
772
829
  await this.refresh();
@@ -774,7 +831,7 @@ var AuthyonClient = class {
774
831
  this.clearSession();
775
832
  return this.http.parse(response);
776
833
  }
777
- return this.request(path, options, true);
834
+ return this.request(path2, options, true);
778
835
  }
779
836
  return this.http.parse(response);
780
837
  }
@@ -1193,18 +1250,18 @@ function matchesField(pattern, field) {
1193
1250
  return pattern.endsWith(".*") && field.startsWith(pattern.slice(0, -1));
1194
1251
  }
1195
1252
  function matchesConditions(subject, conditions) {
1196
- return Object.entries(conditions).every(([path, expected]) => {
1197
- if (path === "$and" && Array.isArray(expected)) {
1253
+ return Object.entries(conditions).every(([path2, expected]) => {
1254
+ if (path2 === "$and" && Array.isArray(expected)) {
1198
1255
  return expected.every(
1199
1256
  (condition) => matchesConditions(subject, condition)
1200
1257
  );
1201
1258
  }
1202
- if (path === "$or" && Array.isArray(expected)) {
1259
+ if (path2 === "$or" && Array.isArray(expected)) {
1203
1260
  return expected.some(
1204
1261
  (condition) => matchesConditions(subject, condition)
1205
1262
  );
1206
1263
  }
1207
- return matchesValue(readPath(subject, path), expected);
1264
+ return matchesValue(readPath(subject, path2), expected);
1208
1265
  });
1209
1266
  }
1210
1267
  function matchesValue(actual, expected) {
@@ -1236,8 +1293,8 @@ function matchesValue(actual, expected) {
1236
1293
  }
1237
1294
  });
1238
1295
  }
1239
- function readPath(value, path) {
1240
- return path.split(".").reduce((current, part) => isRecord(current) ? current[part] : void 0, value);
1296
+ function readPath(value, path2) {
1297
+ return path2.split(".").reduce((current, part) => isRecord(current) ? current[part] : void 0, value);
1241
1298
  }
1242
1299
  function defaultSubjectType(subject) {
1243
1300
  const explicit = subject.__type ?? subject.type ?? subject.kind;
package/dist/index.d.cts CHANGED
@@ -1,19 +1,5 @@
1
- import { T as TokenStorage, H as HttpAdapter, a as HttpLoggerOptions, A as AuthyonClient } from './ability-B1TiHFWa.cjs';
2
- export { b as AbilityConditions, c as AbilityEvent, d as AbilityListener, e as AbilityRule, f as AbilitySubject, g as Activity, h as AuthEvent, i as AuthState, j as AuthStateListener, k as AuthenticatorSetup, l as AuthyonAbility, m as AuthyonAbilityBuilder, n as AuthyonAbilityOptions, o as AuthyonClientOptions, p as AuthyonPermissionSource, q as AuthyonSessionController, C as CreateOrganizationInput, F as FetchHttpAdapter, r as HttpAdapterRequest, s as HttpLogEvent, t as HttpLogger, I as IntrospectResult, u as InviteMemberInput, L as LoggingHttpAdapter, v as LoginInput, w as LoginResult, O as Organization, x as OrganizationMember, P as Paged, y as PaginationOptions, z as PermissionGroup, R as RegisterInput, B as Role, S as Session, D as SessionControllerOptions, E as SessionInfo, G as SessionSnapshot, J as SessionSnapshotListener, K as SessionStatus, M as SsoProvider, N as TwoFactorChallenge, Q as TwoFactorMethod, U as TwoFactorStatus, V as User, W as ValidateResult, X as VerifyTwoFactorInput, Y as WebAuthnAssertion, Z as WebAuthnCeremonyStart, _ as WebAuthnCredential, $ as createAuthyonAbility, a0 as createAuthyonRules, a1 as createClient, a2 as hasPermission, a3 as hasPermissionGroup } from './ability-B1TiHFWa.cjs';
3
-
4
- /** Builds an Authyon browser client through explicit, progressive configuration. */
5
- declare class AuthyonClientBuilder {
6
- private readonly options;
7
- constructor(envKey: string);
8
- withBaseUrl(baseUrl: string, allowInsecureHttp?: boolean): this;
9
- withStorage(storage: TokenStorage): this;
10
- withAutomaticRefresh(enabled?: boolean): this;
11
- withClientIp(clientIp: string): this;
12
- withTimeout(timeoutMs: number): this;
13
- withHttpAdapter(httpAdapter: HttpAdapter): this;
14
- withHttpLogger(httpLogger: HttpLoggerOptions): this;
15
- build(): AuthyonClient;
16
- }
1
+ import { T as TokenStorage, H as HttpAdapter, a as HttpLoggerOptions, A as AuthyonClient } from './ability-5_xXXbDF.cjs';
2
+ export { b as AbilityConditions, c as AbilityEvent, d as AbilityListener, e as AbilityRule, f as AbilitySubject, g as Activity, h as Approval, i as ApprovalAssurance, j as ApprovalPayload, k as ApprovalStatus, l as ApprovalWebAuthnOptions, m as AuthEvent, n as AuthState, o as AuthStateListener, p as AuthenticatorSetup, q as AuthyonAbility, r as AuthyonAbilityBuilder, s as AuthyonAbilityOptions, t as AuthyonClientOptions, u as AuthyonPermissionSource, v as AuthyonSessionController, C as ConfirmApprovalInput, w as CreateOrganizationInput, F as FetchHttpAdapter, x as HttpAdapterRequest, y as HttpLogEvent, z as HttpLogger, I as IntrospectResult, B as InviteMemberInput, L as LoggingHttpAdapter, D as LoginInput, E as LoginResult, O as Organization, G as OrganizationMember, P as Paged, J as PaginationOptions, K as PermissionGroup, R as RegisterInput, M as Role, S as Session, N as SessionControllerOptions, Q as SessionInfo, U as SessionSnapshot, V as SessionSnapshotListener, W as SessionStatus, X as SsoProvider, Y as SubjectApprovals, Z as SubjectSecurity, _ as TwoFactorChallenge, $ as TwoFactorMethod, a0 as TwoFactorStatus, a1 as User, a2 as ValidateResult, a3 as VerifyTwoFactorInput, a4 as WebAuthnAssertion, a5 as WebAuthnCeremonyStart, a6 as WebAuthnCredential, a7 as createAuthyonAbility, a8 as createAuthyonRules, a9 as createClient, aa as hasPermission, ab as hasPermissionGroup } from './ability-5_xXXbDF.cjs';
17
3
 
18
4
  declare const ErrorCodes: {
19
5
  readonly Unknown: "unknown";
@@ -27,6 +13,21 @@ declare const ErrorCodes: {
27
13
  readonly PasswordWeak: "user.password_weak";
28
14
  readonly PasswordPwned: "user.password_pwned";
29
15
  readonly RateLimited: "rate_limited";
16
+ readonly StepUpRequired: "step_up_required";
17
+ readonly InvalidSecondFactorCode: "invalid_code";
18
+ readonly VerificationAttemptsExhausted: "verification_attempts_exhausted";
19
+ readonly MethodNotEnrolled: "method_not_enrolled";
20
+ readonly InvalidApprovalState: "invalid_authorization_state";
21
+ readonly PayloadMismatch: "payload_mismatch";
22
+ readonly ApprovalNotConsumable: "authorization_not_consumable";
23
+ readonly IdempotencyConflict: "idempotency_conflict";
24
+ readonly UserDisabled: "user_disabled";
25
+ readonly UserNotFound: "user_not_found";
26
+ readonly TemporarilyUnavailable: "temporarily_unavailable";
27
+ readonly InsufficientScope: "insufficient_scope";
28
+ readonly ApprovalNotFound: "authorization_not_found";
29
+ readonly SessionRevoked: "session_revoked";
30
+ readonly TwoFactorTooManyFailures: "user.2fa.too_many_failures";
30
31
  };
31
32
  type KnownErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];
32
33
  type AuthyonErrorCategory = "network" | "timeout" | "authentication" | "authorization" | "validation" | "not_found" | "conflict" | "rate_limit" | "server" | "unknown";
@@ -38,6 +39,14 @@ interface AuthyonErrorInterpretation {
38
39
  retryAfter?: number;
39
40
  requestId?: string;
40
41
  }
42
+ /** Error body as sent by the API: RFC 7807, or OAuth-style `error`/`error_description`. */
43
+ type AuthyonErrorBody = Partial<{
44
+ title: string;
45
+ detail: string;
46
+ code: string;
47
+ error: string;
48
+ error_description: string;
49
+ }> & Record<string, unknown>;
41
50
  /** Shared RFC 7807 error implementation used by both public SDKs. */
42
51
  declare class AuthyonError extends Error {
43
52
  readonly status: number;
@@ -47,11 +56,13 @@ declare class AuthyonError extends Error {
47
56
  readonly requestId?: string;
48
57
  readonly retryAfter?: number;
49
58
  readonly cause?: unknown;
50
- constructor(status: number, body: Partial<{
51
- title: string;
52
- detail: string;
53
- code: string;
54
- }>, options?: {
59
+ /**
60
+ * Extra fields from the error body beyond code/title/detail — e.g.
61
+ * `attemptsRemaining` on `invalid_code` or `requiredMethods` on
62
+ * `step_up_required`. Not included in {@link toJSON}.
63
+ */
64
+ readonly extensions: Readonly<Record<string, unknown>>;
65
+ constructor(status: number, body: AuthyonErrorBody, options?: {
55
66
  requestId?: string;
56
67
  retryAfter?: number;
57
68
  cause?: unknown;
@@ -67,10 +78,24 @@ declare class AuthyonError extends Error {
67
78
  toJSON(): Record<string, unknown>;
68
79
  }
69
80
 
81
+ /** Builds an Authyon browser client through explicit, progressive configuration. */
82
+ declare class AuthyonClientBuilder {
83
+ private readonly options;
84
+ constructor(envKey: string);
85
+ withBaseUrl(baseUrl: string, allowInsecureHttp?: boolean): this;
86
+ withStorage(storage: TokenStorage): this;
87
+ withAutomaticRefresh(enabled?: boolean): this;
88
+ withClientIp(clientIp: string): this;
89
+ withTimeout(timeoutMs: number): this;
90
+ withHttpAdapter(httpAdapter: HttpAdapter): this;
91
+ withHttpLogger(httpLogger: HttpLoggerOptions): this;
92
+ build(): AuthyonClient;
93
+ }
94
+
70
95
  /** Keeps the session in memory only (lost on page reload). */
71
96
  declare function createMemoryStorage(): TokenStorage;
72
97
  /** Persists the session in `localStorage` under a namespaced key. */
73
98
  declare function createLocalStorage(key?: string): TokenStorage;
74
99
  declare function createDefaultStorage(): TokenStorage;
75
100
 
76
- export { AuthyonClient, AuthyonClientBuilder, AuthyonError, type AuthyonErrorAction, type AuthyonErrorCategory, type AuthyonErrorInterpretation, ErrorCodes, HttpAdapter, HttpLoggerOptions, type KnownErrorCode, TokenStorage, createDefaultStorage, createLocalStorage, createMemoryStorage };
101
+ export { AuthyonClient, AuthyonClientBuilder, AuthyonError, type AuthyonErrorAction, type AuthyonErrorBody, type AuthyonErrorCategory, type AuthyonErrorInterpretation, ErrorCodes, HttpAdapter, HttpLoggerOptions, type KnownErrorCode, TokenStorage, createDefaultStorage, createLocalStorage, createMemoryStorage };
package/dist/index.d.ts CHANGED
@@ -1,19 +1,5 @@
1
- import { T as TokenStorage, H as HttpAdapter, a as HttpLoggerOptions, A as AuthyonClient } from './ability-B1TiHFWa.js';
2
- export { b as AbilityConditions, c as AbilityEvent, d as AbilityListener, e as AbilityRule, f as AbilitySubject, g as Activity, h as AuthEvent, i as AuthState, j as AuthStateListener, k as AuthenticatorSetup, l as AuthyonAbility, m as AuthyonAbilityBuilder, n as AuthyonAbilityOptions, o as AuthyonClientOptions, p as AuthyonPermissionSource, q as AuthyonSessionController, C as CreateOrganizationInput, F as FetchHttpAdapter, r as HttpAdapterRequest, s as HttpLogEvent, t as HttpLogger, I as IntrospectResult, u as InviteMemberInput, L as LoggingHttpAdapter, v as LoginInput, w as LoginResult, O as Organization, x as OrganizationMember, P as Paged, y as PaginationOptions, z as PermissionGroup, R as RegisterInput, B as Role, S as Session, D as SessionControllerOptions, E as SessionInfo, G as SessionSnapshot, J as SessionSnapshotListener, K as SessionStatus, M as SsoProvider, N as TwoFactorChallenge, Q as TwoFactorMethod, U as TwoFactorStatus, V as User, W as ValidateResult, X as VerifyTwoFactorInput, Y as WebAuthnAssertion, Z as WebAuthnCeremonyStart, _ as WebAuthnCredential, $ as createAuthyonAbility, a0 as createAuthyonRules, a1 as createClient, a2 as hasPermission, a3 as hasPermissionGroup } from './ability-B1TiHFWa.js';
3
-
4
- /** Builds an Authyon browser client through explicit, progressive configuration. */
5
- declare class AuthyonClientBuilder {
6
- private readonly options;
7
- constructor(envKey: string);
8
- withBaseUrl(baseUrl: string, allowInsecureHttp?: boolean): this;
9
- withStorage(storage: TokenStorage): this;
10
- withAutomaticRefresh(enabled?: boolean): this;
11
- withClientIp(clientIp: string): this;
12
- withTimeout(timeoutMs: number): this;
13
- withHttpAdapter(httpAdapter: HttpAdapter): this;
14
- withHttpLogger(httpLogger: HttpLoggerOptions): this;
15
- build(): AuthyonClient;
16
- }
1
+ import { T as TokenStorage, H as HttpAdapter, a as HttpLoggerOptions, A as AuthyonClient } from './ability-5_xXXbDF.js';
2
+ export { b as AbilityConditions, c as AbilityEvent, d as AbilityListener, e as AbilityRule, f as AbilitySubject, g as Activity, h as Approval, i as ApprovalAssurance, j as ApprovalPayload, k as ApprovalStatus, l as ApprovalWebAuthnOptions, m as AuthEvent, n as AuthState, o as AuthStateListener, p as AuthenticatorSetup, q as AuthyonAbility, r as AuthyonAbilityBuilder, s as AuthyonAbilityOptions, t as AuthyonClientOptions, u as AuthyonPermissionSource, v as AuthyonSessionController, C as ConfirmApprovalInput, w as CreateOrganizationInput, F as FetchHttpAdapter, x as HttpAdapterRequest, y as HttpLogEvent, z as HttpLogger, I as IntrospectResult, B as InviteMemberInput, L as LoggingHttpAdapter, D as LoginInput, E as LoginResult, O as Organization, G as OrganizationMember, P as Paged, J as PaginationOptions, K as PermissionGroup, R as RegisterInput, M as Role, S as Session, N as SessionControllerOptions, Q as SessionInfo, U as SessionSnapshot, V as SessionSnapshotListener, W as SessionStatus, X as SsoProvider, Y as SubjectApprovals, Z as SubjectSecurity, _ as TwoFactorChallenge, $ as TwoFactorMethod, a0 as TwoFactorStatus, a1 as User, a2 as ValidateResult, a3 as VerifyTwoFactorInput, a4 as WebAuthnAssertion, a5 as WebAuthnCeremonyStart, a6 as WebAuthnCredential, a7 as createAuthyonAbility, a8 as createAuthyonRules, a9 as createClient, aa as hasPermission, ab as hasPermissionGroup } from './ability-5_xXXbDF.js';
17
3
 
18
4
  declare const ErrorCodes: {
19
5
  readonly Unknown: "unknown";
@@ -27,6 +13,21 @@ declare const ErrorCodes: {
27
13
  readonly PasswordWeak: "user.password_weak";
28
14
  readonly PasswordPwned: "user.password_pwned";
29
15
  readonly RateLimited: "rate_limited";
16
+ readonly StepUpRequired: "step_up_required";
17
+ readonly InvalidSecondFactorCode: "invalid_code";
18
+ readonly VerificationAttemptsExhausted: "verification_attempts_exhausted";
19
+ readonly MethodNotEnrolled: "method_not_enrolled";
20
+ readonly InvalidApprovalState: "invalid_authorization_state";
21
+ readonly PayloadMismatch: "payload_mismatch";
22
+ readonly ApprovalNotConsumable: "authorization_not_consumable";
23
+ readonly IdempotencyConflict: "idempotency_conflict";
24
+ readonly UserDisabled: "user_disabled";
25
+ readonly UserNotFound: "user_not_found";
26
+ readonly TemporarilyUnavailable: "temporarily_unavailable";
27
+ readonly InsufficientScope: "insufficient_scope";
28
+ readonly ApprovalNotFound: "authorization_not_found";
29
+ readonly SessionRevoked: "session_revoked";
30
+ readonly TwoFactorTooManyFailures: "user.2fa.too_many_failures";
30
31
  };
31
32
  type KnownErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];
32
33
  type AuthyonErrorCategory = "network" | "timeout" | "authentication" | "authorization" | "validation" | "not_found" | "conflict" | "rate_limit" | "server" | "unknown";
@@ -38,6 +39,14 @@ interface AuthyonErrorInterpretation {
38
39
  retryAfter?: number;
39
40
  requestId?: string;
40
41
  }
42
+ /** Error body as sent by the API: RFC 7807, or OAuth-style `error`/`error_description`. */
43
+ type AuthyonErrorBody = Partial<{
44
+ title: string;
45
+ detail: string;
46
+ code: string;
47
+ error: string;
48
+ error_description: string;
49
+ }> & Record<string, unknown>;
41
50
  /** Shared RFC 7807 error implementation used by both public SDKs. */
42
51
  declare class AuthyonError extends Error {
43
52
  readonly status: number;
@@ -47,11 +56,13 @@ declare class AuthyonError extends Error {
47
56
  readonly requestId?: string;
48
57
  readonly retryAfter?: number;
49
58
  readonly cause?: unknown;
50
- constructor(status: number, body: Partial<{
51
- title: string;
52
- detail: string;
53
- code: string;
54
- }>, options?: {
59
+ /**
60
+ * Extra fields from the error body beyond code/title/detail — e.g.
61
+ * `attemptsRemaining` on `invalid_code` or `requiredMethods` on
62
+ * `step_up_required`. Not included in {@link toJSON}.
63
+ */
64
+ readonly extensions: Readonly<Record<string, unknown>>;
65
+ constructor(status: number, body: AuthyonErrorBody, options?: {
55
66
  requestId?: string;
56
67
  retryAfter?: number;
57
68
  cause?: unknown;
@@ -67,10 +78,24 @@ declare class AuthyonError extends Error {
67
78
  toJSON(): Record<string, unknown>;
68
79
  }
69
80
 
81
+ /** Builds an Authyon browser client through explicit, progressive configuration. */
82
+ declare class AuthyonClientBuilder {
83
+ private readonly options;
84
+ constructor(envKey: string);
85
+ withBaseUrl(baseUrl: string, allowInsecureHttp?: boolean): this;
86
+ withStorage(storage: TokenStorage): this;
87
+ withAutomaticRefresh(enabled?: boolean): this;
88
+ withClientIp(clientIp: string): this;
89
+ withTimeout(timeoutMs: number): this;
90
+ withHttpAdapter(httpAdapter: HttpAdapter): this;
91
+ withHttpLogger(httpLogger: HttpLoggerOptions): this;
92
+ build(): AuthyonClient;
93
+ }
94
+
70
95
  /** Keeps the session in memory only (lost on page reload). */
71
96
  declare function createMemoryStorage(): TokenStorage;
72
97
  /** Persists the session in `localStorage` under a namespaced key. */
73
98
  declare function createLocalStorage(key?: string): TokenStorage;
74
99
  declare function createDefaultStorage(): TokenStorage;
75
100
 
76
- export { AuthyonClient, AuthyonClientBuilder, AuthyonError, type AuthyonErrorAction, type AuthyonErrorCategory, type AuthyonErrorInterpretation, ErrorCodes, HttpAdapter, HttpLoggerOptions, type KnownErrorCode, TokenStorage, createDefaultStorage, createLocalStorage, createMemoryStorage };
101
+ export { AuthyonClient, AuthyonClientBuilder, AuthyonError, type AuthyonErrorAction, type AuthyonErrorBody, type AuthyonErrorCategory, type AuthyonErrorInterpretation, ErrorCodes, HttpAdapter, HttpLoggerOptions, type KnownErrorCode, TokenStorage, createDefaultStorage, createLocalStorage, createMemoryStorage };
package/dist/index.js CHANGED
@@ -8,6 +8,23 @@ import {
8
8
  hasPermissionGroup
9
9
  } from "./chunk-3XJY6BCI.js";
10
10
 
11
+ // ../../internal/core/security/approvals.ts
12
+ function path(id, suffix = "") {
13
+ if (typeof id !== "string" || !id.trim())
14
+ throw new TypeError("Authyon: an approval id is required.");
15
+ return `/auth/authorizations/${encodeURIComponent(id)}${suffix}`;
16
+ }
17
+ function subjectSecurity(request) {
18
+ return {
19
+ approvals: {
20
+ get: (id) => request(path(id)),
21
+ webauthnOptions: (id) => request(path(id, "/webauthn/options"), { method: "POST" }),
22
+ confirm: (id, input) => request(path(id, "/confirm"), { method: "POST", body: input }),
23
+ reject: (id) => request(path(id, "/reject"), { method: "POST" })
24
+ }
25
+ };
26
+ }
27
+
11
28
  // ../../internal/core/errors/authyonError.ts
12
29
  var ErrorCodes = {
13
30
  Unknown: "unknown",
@@ -20,16 +37,44 @@ var ErrorCodes = {
20
37
  EmailTaken: "user.email_taken",
21
38
  PasswordWeak: "user.password_weak",
22
39
  PasswordPwned: "user.password_pwned",
23
- RateLimited: "rate_limited"
40
+ RateLimited: "rate_limited",
41
+ StepUpRequired: "step_up_required",
42
+ InvalidSecondFactorCode: "invalid_code",
43
+ VerificationAttemptsExhausted: "verification_attempts_exhausted",
44
+ MethodNotEnrolled: "method_not_enrolled",
45
+ InvalidApprovalState: "invalid_authorization_state",
46
+ PayloadMismatch: "payload_mismatch",
47
+ ApprovalNotConsumable: "authorization_not_consumable",
48
+ IdempotencyConflict: "idempotency_conflict",
49
+ UserDisabled: "user_disabled",
50
+ UserNotFound: "user_not_found",
51
+ TemporarilyUnavailable: "temporarily_unavailable",
52
+ InsufficientScope: "insufficient_scope",
53
+ ApprovalNotFound: "authorization_not_found",
54
+ SessionRevoked: "session_revoked",
55
+ TwoFactorTooManyFailures: "user.2fa.too_many_failures"
24
56
  };
57
+ var STANDARD_ERROR_FIELDS = /* @__PURE__ */ new Set([
58
+ "type",
59
+ "status",
60
+ "title",
61
+ "detail",
62
+ "code",
63
+ "error",
64
+ "error_description"
65
+ ]);
25
66
  var AuthyonError = class extends Error {
26
67
  constructor(status, body, options = {}) {
27
- super(body.detail ?? body.title ?? `Authyon request failed with status ${status}`);
68
+ const detail = body.detail ?? body.error_description;
69
+ super(detail ?? body.title ?? `Authyon request failed with status ${status}`);
28
70
  this.name = "AuthyonError";
29
71
  this.status = status;
30
- this.code = body.code ?? "unknown";
72
+ this.code = body.code ?? body.error ?? "unknown";
31
73
  this.title = body.title ?? "Error";
32
- this.detail = body.detail;
74
+ this.detail = detail;
75
+ this.extensions = Object.fromEntries(
76
+ Object.entries(body).filter(([key]) => !STANDARD_ERROR_FIELDS.has(key))
77
+ );
33
78
  this.requestId = options.requestId;
34
79
  this.retryAfter = options.retryAfter;
35
80
  this.cause = options.cause;
@@ -79,6 +124,7 @@ var AuthyonError = class extends Error {
79
124
  };
80
125
  function classifyError(status, code) {
81
126
  if (code === ErrorCodes.NetworkError) return "network";
127
+ if (code === ErrorCodes.StepUpRequired) return "authentication";
82
128
  if (code === ErrorCodes.Timeout || status === 408) return "timeout";
83
129
  if (code === ErrorCodes.RateLimited || status === 429) return "rate_limit";
84
130
  if (code === ErrorCodes.EmailTaken || status === 409) return "conflict";
@@ -172,14 +218,14 @@ var DEFAULT_BASE_URL = "https://api.authyon.com";
172
218
  var DEFAULT_EXPIRY_SKEW_MS = 3e4;
173
219
 
174
220
  // ../../internal/core/http/query.ts
175
- function appendQuery(path, params) {
176
- if (!params) return path;
221
+ function appendQuery(path2, params) {
222
+ if (!params) return path2;
177
223
  const query = new URLSearchParams();
178
224
  for (const [key, value2] of Object.entries(params)) {
179
225
  if (value2 !== void 0) query.set(key, String(value2));
180
226
  }
181
227
  const value = query.toString();
182
- return value ? `${path}${path.includes("?") ? "&" : "?"}${value}` : path;
228
+ return value ? `${path2}${path2.includes("?") ? "&" : "?"}${value}` : path2;
183
229
  }
184
230
 
185
231
  // ../../internal/core/http/httpAdapter.ts
@@ -248,7 +294,6 @@ function sanitizeUrl(value) {
248
294
  url.username = "";
249
295
  url.password = "";
250
296
  url.hash = "";
251
- url.pathname = url.pathname.replace(/(\/platform\/workspace-invites\/)[^/]+/u, "$1REDACTED");
252
297
  const queryKeys = /* @__PURE__ */ new Set();
253
298
  url.searchParams.forEach((_value, key) => queryKeys.add(key));
254
299
  url.search = queryKeys.size > 0 ? [...queryKeys].map((key) => `${encodeURIComponent(key)}=REDACTED`).join("&") : "";
@@ -278,12 +323,12 @@ function createSharedTransport(options) {
278
323
  const httpAdapter = options.httpLogger ? new LoggingHttpAdapter(baseAdapter, options.httpLogger) : baseAdapter;
279
324
  return {
280
325
  baseUrl,
281
- async request(path, init) {
326
+ async request(path2, init) {
282
327
  const controller = new AbortController();
283
328
  const timeout = timeoutMs > 0 ? setTimeout(() => controller.abort(), timeoutMs) : void 0;
284
329
  try {
285
330
  return await httpAdapter.request({
286
- url: `${baseUrl}${path}`,
331
+ url: `${baseUrl}${path2}`,
287
332
  method: init.method ?? "GET",
288
333
  headers: normalizeHeaders(init.headers),
289
334
  body: init.body,
@@ -341,11 +386,11 @@ var JsonHttpClient = class {
341
386
  constructor(transport) {
342
387
  this.transport = transport;
343
388
  }
344
- async send(path, options = {}) {
389
+ async send(path2, options = {}) {
345
390
  const headers = { ...options.headers };
346
391
  if (options.body !== void 0) headers["Content-Type"] = "application/json";
347
392
  try {
348
- return await this.transport.request(appendQuery(path, options.query), {
393
+ return await this.transport.request(appendQuery(path2, options.query), {
349
394
  method: options.method ?? "GET",
350
395
  headers,
351
396
  body: options.body !== void 0 ? JSON.stringify(options.body) : void 0
@@ -359,7 +404,9 @@ var JsonHttpClient = class {
359
404
  if (!response.ok) {
360
405
  let body = {};
361
406
  try {
362
- body = await response.json();
407
+ const parsed = await response.json();
408
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
409
+ body = parsed;
363
410
  } catch {
364
411
  }
365
412
  throw new AuthyonError(response.status, body, responseMetadata(response));
@@ -367,8 +414,8 @@ var JsonHttpClient = class {
367
414
  if (response.status === 204) return void 0;
368
415
  return await response.json();
369
416
  }
370
- async request(path, options = {}) {
371
- return this.parse(await this.send(path, options));
417
+ async request(path2, options = {}) {
418
+ return this.parse(await this.send(path2, options));
372
419
  }
373
420
  };
374
421
 
@@ -606,6 +653,16 @@ var AuthyonClient = class {
606
653
  })
607
654
  }
608
655
  };
656
+ // ── Security (customer side) ─────────────────────────────────────────────
657
+ /**
658
+ * `security.approvals`: approve or reject what your backend asked the
659
+ * customer to confirm (`@authyon/server` → `security.approvals.create`).
660
+ * Show the payload with `get()`, then `confirm()` with the customer's
661
+ * authenticator code or passkey, or `reject()`.
662
+ */
663
+ this.security = subjectSecurity(
664
+ (path2, options) => this.request(path2, { ...options, bearer: true })
665
+ );
609
666
  if (!options.envKey)
610
667
  throw new Error("Authyon: `envKey` is required (pk_live_... / pk_test_...)");
611
668
  this.envKey = options.envKey;
@@ -719,7 +776,7 @@ var AuthyonClient = class {
719
776
  }
720
777
  }
721
778
  // ── HTTP core ────────────────────────────────────────────────────────────
722
- async request(path, options = {}, isRetry = false) {
779
+ async request(path2, options = {}, isRetry = false) {
723
780
  const headers = {
724
781
  "X-Authyon-Environment": this.envKey,
725
782
  ...clientIpHeaders({ clientIp: this.clientIp }),
@@ -734,7 +791,7 @@ var AuthyonClient = class {
734
791
  });
735
792
  headers.Authorization = `Bearer ${token}`;
736
793
  }
737
- const response = await this.http.send(path, { ...options, headers });
794
+ const response = await this.http.send(path2, { ...options, headers });
738
795
  if (response.status === 401 && options.bearer && this.autoRefresh && !isRetry && this.getSession()) {
739
796
  try {
740
797
  await this.refresh();
@@ -742,7 +799,7 @@ var AuthyonClient = class {
742
799
  this.clearSession();
743
800
  return this.http.parse(response);
744
801
  }
745
- return this.request(path, options, true);
802
+ return this.request(path2, options, true);
746
803
  }
747
804
  return this.http.parse(response);
748
805
  }
@@ -1,7 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
- import { V as User, D as SessionControllerOptions, A as AuthyonClient, G as SessionSnapshot, S as Session, f as AbilitySubject, n as AuthyonAbilityOptions, l as AuthyonAbility } from '../ability-B1TiHFWa.cjs';
4
- export { q as AuthyonSessionController, J as SessionSnapshotListener, K as SessionStatus } from '../ability-B1TiHFWa.cjs';
3
+ import { a1 as User, N as SessionControllerOptions, A as AuthyonClient, U as SessionSnapshot, S as Session, f as AbilitySubject, s as AuthyonAbilityOptions, q as AuthyonAbility } from '../ability-5_xXXbDF.cjs';
4
+ export { v as AuthyonSessionController, V as SessionSnapshotListener, W as SessionStatus } from '../ability-5_xXXbDF.cjs';
5
5
 
6
6
  interface AuthyonProviderProps<TUser extends User = User> extends SessionControllerOptions {
7
7
  client: AuthyonClient;
@@ -1,7 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
- import { V as User, D as SessionControllerOptions, A as AuthyonClient, G as SessionSnapshot, S as Session, f as AbilitySubject, n as AuthyonAbilityOptions, l as AuthyonAbility } from '../ability-B1TiHFWa.js';
4
- export { q as AuthyonSessionController, J as SessionSnapshotListener, K as SessionStatus } from '../ability-B1TiHFWa.js';
3
+ import { a1 as User, N as SessionControllerOptions, A as AuthyonClient, U as SessionSnapshot, S as Session, f as AbilitySubject, s as AuthyonAbilityOptions, q as AuthyonAbility } from '../ability-5_xXXbDF.js';
4
+ export { v as AuthyonSessionController, V as SessionSnapshotListener, W as SessionStatus } from '../ability-5_xXXbDF.js';
5
5
 
6
6
  interface AuthyonProviderProps<TUser extends User = User> extends SessionControllerOptions {
7
7
  client: AuthyonClient;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@authyon/auth",
3
- "version": "0.2.0-beta.12",
3
+ "version": "0.2.0-beta.15",
4
4
  "description": "Authyon SDK for browsers — auth, sessions, multi-tenant and 2FA for vanilla JS/TS.",
5
5
  "license": "MIT",
6
6
  "type": "module",