@authyon/auth 0.2.0-beta.2 → 0.2.0-beta.21

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/dist/index.d.ts CHANGED
@@ -1,18 +1,5 @@
1
- import { T as TokenStorage, H as HttpAdapter, a as HttpLoggerOptions, A as AuthyonClient } from './ability-g6nBpOQM.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, R as RegisterInput, z as Role, S as Session, B as SessionControllerOptions, D as SessionInfo, E as SessionSnapshot, G as SessionSnapshotListener, J as SessionStatus, K as SsoProvider, M as TwoFactorChallenge, N as TwoFactorMethod, Q as TwoFactorStatus, U as User, V as ValidateResult, W as VerifyTwoFactorInput, X as WebAuthnAssertion, Y as WebAuthnCeremonyStart, Z as WebAuthnCredential, _ as createAuthyonAbility, $ as createAuthyonRules, a0 as createClient, a1 as hasPermission } from './ability-g6nBpOQM.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
- withTimeout(timeoutMs: number): this;
12
- withHttpAdapter(httpAdapter: HttpAdapter): this;
13
- withHttpLogger(httpLogger: HttpLoggerOptions): this;
14
- build(): AuthyonClient;
15
- }
1
+ import { T as TokenStorage, H as HttpAdapter, a as HttpLoggerOptions, A as AuthyonClient } from './ability-BTcayvkM.js';
2
+ export { b as AbilityConditions, c as AbilityEvent, d as AbilityListener, e as AbilityRule, f as AbilitySubject, g as AcceptOrganizationInviteInput, h as Activity, i as Approval, j as ApprovalAssurance, k as ApprovalPayload, l as ApprovalStatus, m as ApprovalWebAuthnOptions, n as AuthEvent, o as AuthState, p as AuthStateListener, q as AuthenticatorSetup, r as AuthyonAbility, s as AuthyonAbilityBuilder, t as AuthyonAbilityOptions, u as AuthyonClientOptions, v as AuthyonPermissionSource, w as AuthyonSessionController, C as ConfirmApprovalInput, x as CreateOrganizationInput, F as FetchHttpAdapter, y as HttpAdapterRequest, z as HttpLogEvent, B as HttpLogger, I as IntrospectResult, D as InviteMemberInput, L as LoggingHttpAdapter, E as LoginInput, G as LoginResult, O as Organization, J as OrganizationInvite, K as OrganizationInviteAccepted, M as OrganizationInviteInput, N as OrganizationInviteIssued, P as OrganizationInvitePreview, Q as OrganizationInviteStatus, R as OrganizationMember, S as Paged, U as PaginationOptions, V as PasswordResetCompleted, W as PasswordResetPreview, X as PasswordResetRequestOptions, Y as PermissionGroup, Z as RegisterInput, _ as Role, $ as SecurityMethods, a0 as Session, a1 as SessionControllerOptions, a2 as SessionInfo, a3 as SessionSnapshot, a4 as SessionSnapshotListener, a5 as SessionStatus, a6 as SsoProvider, a7 as SubjectApprovals, a8 as SubjectSecurity, a9 as TwoFactorChallenge, aa as TwoFactorMethod, ab as TwoFactorStatus, ac as User, ad as ValidateResult, ae as VerifyTwoFactorInput, af as WebAuthnAssertion, ag as WebAuthnCeremonyStart, ah as WebAuthnCredential, ai as createAuthyonAbility, aj as createAuthyonRules, ak as createClient, al as hasPermission, am as hasPermissionGroup } from './ability-BTcayvkM.js';
16
3
 
17
4
  declare const ErrorCodes: {
18
5
  readonly Unknown: "unknown";
@@ -26,6 +13,22 @@ declare const ErrorCodes: {
26
13
  readonly PasswordWeak: "user.password_weak";
27
14
  readonly PasswordPwned: "user.password_pwned";
28
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 CredentialThrottled: "credential_throttled";
29
+ readonly ApprovalNotFound: "authorization_not_found";
30
+ readonly SessionRevoked: "session_revoked";
31
+ readonly TwoFactorTooManyFailures: "user.2fa.too_many_failures";
29
32
  };
30
33
  type KnownErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];
31
34
  type AuthyonErrorCategory = "network" | "timeout" | "authentication" | "authorization" | "validation" | "not_found" | "conflict" | "rate_limit" | "server" | "unknown";
@@ -37,6 +40,14 @@ interface AuthyonErrorInterpretation {
37
40
  retryAfter?: number;
38
41
  requestId?: string;
39
42
  }
43
+ /** Error body as sent by the API: RFC 7807, or OAuth-style `error`/`error_description`. */
44
+ type AuthyonErrorBody = Partial<{
45
+ title: string;
46
+ detail: string;
47
+ code: string;
48
+ error: string;
49
+ error_description: string;
50
+ }> & Record<string, unknown>;
40
51
  /** Shared RFC 7807 error implementation used by both public SDKs. */
41
52
  declare class AuthyonError extends Error {
42
53
  readonly status: number;
@@ -46,11 +57,13 @@ declare class AuthyonError extends Error {
46
57
  readonly requestId?: string;
47
58
  readonly retryAfter?: number;
48
59
  readonly cause?: unknown;
49
- constructor(status: number, body: Partial<{
50
- title: string;
51
- detail: string;
52
- code: string;
53
- }>, options?: {
60
+ /**
61
+ * Extra fields from the error body beyond code/title/detail — e.g.
62
+ * `attemptsRemaining` on `invalid_code` or `requiredMethods` on
63
+ * `step_up_required`. Not included in {@link toJSON}.
64
+ */
65
+ readonly extensions: Readonly<Record<string, unknown>>;
66
+ constructor(status: number, body: AuthyonErrorBody, options?: {
54
67
  requestId?: string;
55
68
  retryAfter?: number;
56
69
  cause?: unknown;
@@ -66,10 +79,24 @@ declare class AuthyonError extends Error {
66
79
  toJSON(): Record<string, unknown>;
67
80
  }
68
81
 
82
+ /** Builds an Authyon browser client through explicit, progressive configuration. */
83
+ declare class AuthyonClientBuilder {
84
+ private readonly options;
85
+ constructor(envKey: string);
86
+ withBaseUrl(baseUrl: string, allowInsecureHttp?: boolean): this;
87
+ withStorage(storage: TokenStorage): this;
88
+ withAutomaticRefresh(enabled?: boolean): this;
89
+ withClientIp(clientIp: string): this;
90
+ withTimeout(timeoutMs: number): this;
91
+ withHttpAdapter(httpAdapter: HttpAdapter): this;
92
+ withHttpLogger(httpLogger: HttpLoggerOptions): this;
93
+ build(): AuthyonClient;
94
+ }
95
+
69
96
  /** Keeps the session in memory only (lost on page reload). */
70
97
  declare function createMemoryStorage(): TokenStorage;
71
98
  /** Persists the session in `localStorage` under a namespaced key. */
72
99
  declare function createLocalStorage(key?: string): TokenStorage;
73
100
  declare function createDefaultStorage(): TokenStorage;
74
101
 
75
- export { AuthyonClient, AuthyonClientBuilder, AuthyonError, type AuthyonErrorAction, type AuthyonErrorCategory, type AuthyonErrorInterpretation, ErrorCodes, HttpAdapter, HttpLoggerOptions, type KnownErrorCode, TokenStorage, createDefaultStorage, createLocalStorage, createMemoryStorage };
102
+ 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
@@ -4,8 +4,40 @@ import {
4
4
  AuthyonSessionController,
5
5
  createAuthyonAbility,
6
6
  createAuthyonRules,
7
- hasPermission
8
- } from "./chunk-EHZEUM47.js";
7
+ hasPermission,
8
+ hasPermissionGroup
9
+ } from "./chunk-3XJY6BCI.js";
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
+ var NO_METHODS = {
18
+ password: false,
19
+ authenticator: false,
20
+ emailCode: false,
21
+ passkey: false,
22
+ passkeyCount: 0,
23
+ recoveryCodes: 0,
24
+ sso: [],
25
+ twoFactor: false
26
+ };
27
+ function subjectSecurity(request) {
28
+ return {
29
+ methods: async () => {
30
+ const me = await request("/auth/me");
31
+ return me.security ?? NO_METHODS;
32
+ },
33
+ approvals: {
34
+ get: (id) => request(path(id)),
35
+ webauthnOptions: (id) => request(path(id, "/webauthn/options"), { method: "POST" }),
36
+ confirm: (id, input) => request(path(id, "/confirm"), { method: "POST", body: input }),
37
+ reject: (id) => request(path(id, "/reject"), { method: "POST" })
38
+ }
39
+ };
40
+ }
9
41
 
10
42
  // ../../internal/core/errors/authyonError.ts
11
43
  var ErrorCodes = {
@@ -19,16 +51,45 @@ var ErrorCodes = {
19
51
  EmailTaken: "user.email_taken",
20
52
  PasswordWeak: "user.password_weak",
21
53
  PasswordPwned: "user.password_pwned",
22
- RateLimited: "rate_limited"
54
+ RateLimited: "rate_limited",
55
+ StepUpRequired: "step_up_required",
56
+ InvalidSecondFactorCode: "invalid_code",
57
+ VerificationAttemptsExhausted: "verification_attempts_exhausted",
58
+ MethodNotEnrolled: "method_not_enrolled",
59
+ InvalidApprovalState: "invalid_authorization_state",
60
+ PayloadMismatch: "payload_mismatch",
61
+ ApprovalNotConsumable: "authorization_not_consumable",
62
+ IdempotencyConflict: "idempotency_conflict",
63
+ UserDisabled: "user_disabled",
64
+ UserNotFound: "user_not_found",
65
+ TemporarilyUnavailable: "temporarily_unavailable",
66
+ InsufficientScope: "insufficient_scope",
67
+ CredentialThrottled: "credential_throttled",
68
+ ApprovalNotFound: "authorization_not_found",
69
+ SessionRevoked: "session_revoked",
70
+ TwoFactorTooManyFailures: "user.2fa.too_many_failures"
23
71
  };
72
+ var STANDARD_ERROR_FIELDS = /* @__PURE__ */ new Set([
73
+ "type",
74
+ "status",
75
+ "title",
76
+ "detail",
77
+ "code",
78
+ "error",
79
+ "error_description"
80
+ ]);
24
81
  var AuthyonError = class extends Error {
25
82
  constructor(status, body, options = {}) {
26
- super(body.detail ?? body.title ?? `Authyon request failed with status ${status}`);
83
+ const detail = body.detail ?? body.error_description;
84
+ super(detail ?? body.title ?? `Authyon request failed with status ${status}`);
27
85
  this.name = "AuthyonError";
28
86
  this.status = status;
29
- this.code = body.code ?? "unknown";
87
+ this.code = body.code ?? body.error ?? "unknown";
30
88
  this.title = body.title ?? "Error";
31
- this.detail = body.detail;
89
+ this.detail = detail;
90
+ this.extensions = Object.fromEntries(
91
+ Object.entries(body).filter(([key]) => !STANDARD_ERROR_FIELDS.has(key))
92
+ );
32
93
  this.requestId = options.requestId;
33
94
  this.retryAfter = options.retryAfter;
34
95
  this.cause = options.cause;
@@ -78,6 +139,7 @@ var AuthyonError = class extends Error {
78
139
  };
79
140
  function classifyError(status, code) {
80
141
  if (code === ErrorCodes.NetworkError) return "network";
142
+ if (code === ErrorCodes.StepUpRequired) return "authentication";
81
143
  if (code === ErrorCodes.Timeout || status === 408) return "timeout";
82
144
  if (code === ErrorCodes.RateLimited || status === 429) return "rate_limit";
83
145
  if (code === ErrorCodes.EmailTaken || status === 409) return "conflict";
@@ -171,14 +233,21 @@ var DEFAULT_BASE_URL = "https://api.authyon.com";
171
233
  var DEFAULT_EXPIRY_SKEW_MS = 3e4;
172
234
 
173
235
  // ../../internal/core/http/query.ts
174
- function appendQuery(path, params) {
175
- if (!params) return path;
236
+ function appendQuery(path2, params) {
237
+ if (!params) return path2;
176
238
  const query = new URLSearchParams();
177
239
  for (const [key, value2] of Object.entries(params)) {
178
- if (value2 !== void 0) query.set(key, String(value2));
240
+ if (value2 === void 0) continue;
241
+ if (Array.isArray(value2)) {
242
+ for (const item of value2) {
243
+ if (item !== void 0) query.append(key, String(item));
244
+ }
245
+ continue;
246
+ }
247
+ query.set(key, String(value2));
179
248
  }
180
249
  const value = query.toString();
181
- return value ? `${path}${path.includes("?") ? "&" : "?"}${value}` : path;
250
+ return value ? `${path2}${path2.includes("?") ? "&" : "?"}${value}` : path2;
182
251
  }
183
252
 
184
253
  // ../../internal/core/http/httpAdapter.ts
@@ -191,7 +260,8 @@ var FetchHttpAdapter = class {
191
260
  method: request.method,
192
261
  headers: request.headers,
193
262
  body: request.body,
194
- signal: request.signal
263
+ signal: request.signal,
264
+ redirect: "error"
195
265
  });
196
266
  }
197
267
  };
@@ -243,6 +313,9 @@ function defaultHttpLogger(event) {
243
313
  }
244
314
  function sanitizeUrl(value) {
245
315
  const url = new URL(value);
316
+ url.username = "";
317
+ url.password = "";
318
+ url.hash = "";
246
319
  const queryKeys = /* @__PURE__ */ new Set();
247
320
  url.searchParams.forEach((_value, key) => queryKeys.add(key));
248
321
  url.search = queryKeys.size > 0 ? [...queryKeys].map((key) => `${encodeURIComponent(key)}=REDACTED`).join("&") : "";
@@ -272,12 +345,12 @@ function createSharedTransport(options) {
272
345
  const httpAdapter = options.httpLogger ? new LoggingHttpAdapter(baseAdapter, options.httpLogger) : baseAdapter;
273
346
  return {
274
347
  baseUrl,
275
- async request(path, init) {
348
+ async request(path2, init) {
276
349
  const controller = new AbortController();
277
350
  const timeout = timeoutMs > 0 ? setTimeout(() => controller.abort(), timeoutMs) : void 0;
278
351
  try {
279
352
  return await httpAdapter.request({
280
- url: `${baseUrl}${path}`,
353
+ url: `${baseUrl}${path2}`,
281
354
  method: init.method ?? "GET",
282
355
  headers: normalizeHeaders(init.headers),
283
356
  body: init.body,
@@ -335,11 +408,11 @@ var JsonHttpClient = class {
335
408
  constructor(transport) {
336
409
  this.transport = transport;
337
410
  }
338
- async send(path, options = {}) {
411
+ async send(path2, options = {}) {
339
412
  const headers = { ...options.headers };
340
413
  if (options.body !== void 0) headers["Content-Type"] = "application/json";
341
414
  try {
342
- return await this.transport.request(appendQuery(path, options.query), {
415
+ return await this.transport.request(appendQuery(path2, options.query), {
343
416
  method: options.method ?? "GET",
344
417
  headers,
345
418
  body: options.body !== void 0 ? JSON.stringify(options.body) : void 0
@@ -353,7 +426,9 @@ var JsonHttpClient = class {
353
426
  if (!response.ok) {
354
427
  let body = {};
355
428
  try {
356
- body = await response.json();
429
+ const parsed = await response.json();
430
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
431
+ body = parsed;
357
432
  } catch {
358
433
  }
359
434
  throw new AuthyonError(response.status, body, responseMetadata(response));
@@ -361,11 +436,33 @@ var JsonHttpClient = class {
361
436
  if (response.status === 204) return void 0;
362
437
  return await response.json();
363
438
  }
364
- async request(path, options = {}) {
365
- return this.parse(await this.send(path, options));
439
+ async request(path2, options = {}) {
440
+ return this.parse(await this.send(path2, options));
366
441
  }
367
442
  };
368
443
 
444
+ // ../../internal/core/http/clientIp.ts
445
+ function clientIpHeaders(context) {
446
+ if (!context?.clientIp) return {};
447
+ return { "X-Forwarded-For": normalizeClientIp(context.clientIp) };
448
+ }
449
+ function normalizeClientIp(value) {
450
+ let ip = value.trim();
451
+ if (ip.startsWith("[") && ip.endsWith("]")) ip = ip.slice(1, -1);
452
+ if (!ip || ip.length > 45 || ip.includes(",") || /[\r\n\s]/.test(ip) || !/^[0-9a-f:.]+$/i.test(ip) || !ip.includes(".") && !ip.includes(":")) {
453
+ throw invalidClientIp();
454
+ }
455
+ try {
456
+ new URL(ip.includes(":") ? `http://[${ip}]` : `http://${ip}`);
457
+ } catch {
458
+ throw invalidClientIp();
459
+ }
460
+ return ip;
461
+ }
462
+ function invalidClientIp() {
463
+ return new Error("Authyon: `clientIp` must be a single valid IPv4 or IPv6 address");
464
+ }
465
+
369
466
  // src/client/authyonClient.ts
370
467
  var FALLBACK_EXPIRES_IN = 1800;
371
468
  function readTokens(raw) {
@@ -443,18 +540,41 @@ var AuthyonClient = class {
443
540
  method: "DELETE",
444
541
  bearer: true
445
542
  }),
446
- /** POST /auth/password-reset/request — always resolves (no account enumeration). */
447
- requestPasswordReset: (email) => this.request("/auth/password-reset/request", { method: "POST", body: { email } }),
448
- /** POST /auth/password-reset/confirm — sets a new password and revokes all refresh tokens. */
449
- confirmPasswordReset: (token, newPassword) => this.request("/auth/password-reset/confirm", {
543
+ /**
544
+ * POST /auth/password-reset/request — resolves whether or not the address
545
+ * has an account (no account enumeration). `redirectUri` is where the
546
+ * person lands after changing the password; it must be one of the
547
+ * environment's registered redirect URIs, or the call rejects with
548
+ * `user.password_reset.redirect_uri.not_allowed`.
549
+ */
550
+ requestPasswordReset: (email, options = {}) => this.request("/auth/password-reset/request", {
450
551
  method: "POST",
451
- body: { token, newPassword }
452
- })
552
+ body: { email, ...options }
553
+ }),
554
+ /**
555
+ * POST /auth/password-reset/validate — checks a reset token without
556
+ * spending it, so the reset page can refuse a dead link before asking for
557
+ * a password. Rejects with `user.password_reset.invalid` (401) when the
558
+ * token is unknown, used or expired.
559
+ */
560
+ validatePasswordReset: (token) => this.request("/auth/password-reset/validate", { method: "POST", body: { token } }),
561
+ /**
562
+ * POST /auth/password-reset/confirm — sets a new password and revokes all
563
+ * refresh tokens. Resolves with the `redirectUri` the reset was requested
564
+ * with (`null` when there was none); following it is up to the caller.
565
+ */
566
+ confirmPasswordReset: async (token, newPassword) => {
567
+ const done = await this.request(
568
+ "/auth/password-reset/confirm",
569
+ { method: "POST", body: { token, newPassword } }
570
+ );
571
+ return { redirectUri: done?.redirectUri ?? null };
572
+ }
453
573
  };
454
574
  // ── Organization ─────────────────────────────────────────────────────────
455
575
  this.organization = {
456
- /** GET /auth/tenants — all organization memberships. */
457
- list: () => this.request("/auth/tenants", { bearer: true }),
576
+ /** GET /auth/tenants — filterable list of organization memberships. */
577
+ list: (params = {}) => this.request(appendQuery("/auth/tenants", params), { bearer: true }),
458
578
  /**
459
579
  * POST /auth/tenants — creates an organization owned by the signed-in
460
580
  * user (only available when self-service organization creation is
@@ -491,7 +611,11 @@ var AuthyonClient = class {
491
611
  appendQuery(`/auth/tenants/${encodeURIComponent(organizationId)}/members`, params),
492
612
  { bearer: true }
493
613
  ),
494
- /** POST /auth/tenants/{organizationId}/members — invite a member by e-mail. */
614
+ /**
615
+ * POST /auth/tenants/{organizationId}/members — add an existing
616
+ * environment user by e-mail (no e-mail is sent). To invite an address
617
+ * that may not have an account yet, use `organization.invites.create`.
618
+ */
495
619
  invite: (organizationId, params) => this.request(`/auth/tenants/${encodeURIComponent(organizationId)}/members`, {
496
620
  method: "POST",
497
621
  bearer: true,
@@ -503,6 +627,59 @@ var AuthyonClient = class {
503
627
  { method: "DELETE", bearer: true }
504
628
  )
505
629
  },
630
+ /**
631
+ * Invites by e-mail or by link. Managing them requires the
632
+ * `tenants:members:invite` permission on the organization; `preview` and
633
+ * `accept` are public — the token in the link is the credential.
634
+ */
635
+ invites: {
636
+ /** GET /auth/tenants/{organizationId}/invites — every invite with its status, newest first. */
637
+ list: (organizationId) => this.request(`/auth/tenants/${encodeURIComponent(organizationId)}/invites`, {
638
+ bearer: true
639
+ }),
640
+ /**
641
+ * POST /auth/tenants/{organizationId}/invites — invite an address.
642
+ * Always returns `acceptUrl`; with `sendEmail: false` Authyon does not
643
+ * mail it and you deliver the link yourself. The link is valid for 7
644
+ * days, once, and can't be read again later.
645
+ */
646
+ create: (organizationId, params) => this.request(`/auth/tenants/${encodeURIComponent(organizationId)}/invites`, {
647
+ method: "POST",
648
+ bearer: true,
649
+ body: {
650
+ email: params.email,
651
+ roles: params.roles,
652
+ sendEmail: params.sendEmail ?? true,
653
+ redirectUri: params.redirectUri
654
+ }
655
+ }),
656
+ /**
657
+ * POST /auth/tenants/{organizationId}/invites/{inviteId}/resend — issue
658
+ * a new link (the previous one stops working), mailing it unless
659
+ * `sendEmail` is `false`.
660
+ */
661
+ resend: (organizationId, inviteId, params = {}) => this.request(
662
+ `/auth/tenants/${encodeURIComponent(organizationId)}/invites/${encodeURIComponent(inviteId)}/resend`,
663
+ { method: "POST", bearer: true, body: { sendEmail: params.sendEmail ?? true } }
664
+ ),
665
+ /** DELETE /auth/tenants/{organizationId}/invites/{inviteId} — revoke a pending invite. */
666
+ revoke: (organizationId, inviteId) => this.request(
667
+ `/auth/tenants/${encodeURIComponent(organizationId)}/invites/${encodeURIComponent(inviteId)}`,
668
+ { method: "DELETE", bearer: true }
669
+ ),
670
+ /**
671
+ * POST /auth/tenant-invites/preview — public. Read the `token` query
672
+ * parameter of the invite link and pick the form: one click when
673
+ * `accountExists`, sign-up otherwise.
674
+ */
675
+ preview: (token) => this.request("/auth/tenant-invites/preview", { method: "POST", body: { token } }),
676
+ /**
677
+ * POST /auth/tenant-invites/accept — public. Joins the organization; for
678
+ * an address with no account, also creates it (password required) with
679
+ * the e-mail already confirmed. Does not sign the user in.
680
+ */
681
+ accept: (params) => this.request("/auth/tenant-invites/accept", { method: "POST", body: params })
682
+ },
506
683
  roles: {
507
684
  /** GET /auth/tenants/{organizationId}/roles — roles available in the organization. */
508
685
  list: (organizationId) => this.request(`/auth/tenants/${encodeURIComponent(organizationId)}/roles`, {
@@ -578,9 +755,20 @@ var AuthyonClient = class {
578
755
  })
579
756
  }
580
757
  };
758
+ // ── Security (customer side) ─────────────────────────────────────────────
759
+ /**
760
+ * `security.approvals`: approve or reject what your backend asked the
761
+ * customer to confirm (`@authyon/server` → `security.approvals.create`).
762
+ * Show the payload with `get()`, then `confirm()` with the customer's
763
+ * authenticator code or passkey, or `reject()`.
764
+ */
765
+ this.security = subjectSecurity(
766
+ (path2, options) => this.request(path2, { ...options, bearer: true })
767
+ );
581
768
  if (!options.envKey)
582
769
  throw new Error("Authyon: `envKey` is required (pk_live_... / pk_test_...)");
583
770
  this.envKey = options.envKey;
771
+ this.clientIp = options.clientIp;
584
772
  this.transport = createSharedTransport({
585
773
  baseUrl: options.baseUrl ?? DEFAULT_BASE_URL,
586
774
  allowInsecureHttp: options.allowInsecureHttp,
@@ -690,9 +878,10 @@ var AuthyonClient = class {
690
878
  }
691
879
  }
692
880
  // ── HTTP core ────────────────────────────────────────────────────────────
693
- async request(path, options = {}, isRetry = false) {
881
+ async request(path2, options = {}, isRetry = false) {
694
882
  const headers = {
695
883
  "X-Authyon-Environment": this.envKey,
884
+ ...clientIpHeaders({ clientIp: this.clientIp }),
696
885
  ...options.headers
697
886
  };
698
887
  if (options.bearer) {
@@ -704,7 +893,7 @@ var AuthyonClient = class {
704
893
  });
705
894
  headers.Authorization = `Bearer ${token}`;
706
895
  }
707
- const response = await this.http.send(path, { ...options, headers });
896
+ const response = await this.http.send(path2, { ...options, headers });
708
897
  if (response.status === 401 && options.bearer && this.autoRefresh && !isRetry && this.getSession()) {
709
898
  try {
710
899
  await this.refresh();
@@ -712,7 +901,7 @@ var AuthyonClient = class {
712
901
  this.clearSession();
713
902
  return this.http.parse(response);
714
903
  }
715
- return this.request(path, options, true);
904
+ return this.request(path2, options, true);
716
905
  }
717
906
  return this.http.parse(response);
718
907
  }
@@ -857,6 +1046,10 @@ var AuthyonClientBuilder = class {
857
1046
  this.options.autoRefresh = enabled;
858
1047
  return this;
859
1048
  }
1049
+ withClientIp(clientIp) {
1050
+ this.options.clientIp = clientIp;
1051
+ return this;
1052
+ }
860
1053
  withTimeout(timeoutMs) {
861
1054
  this.options.timeoutMs = timeoutMs;
862
1055
  return this;
@@ -889,5 +1082,6 @@ export {
889
1082
  createDefaultStorage,
890
1083
  createLocalStorage,
891
1084
  createMemoryStorage,
892
- hasPermission
1085
+ hasPermission,
1086
+ hasPermissionGroup
893
1087
  };
@@ -1,7 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
- import { U as User, B as SessionControllerOptions, A as AuthyonClient, E as SessionSnapshot, S as Session, f as AbilitySubject, n as AuthyonAbilityOptions, l as AuthyonAbility } from '../ability-g6nBpOQM.cjs';
4
- export { q as AuthyonSessionController, G as SessionSnapshotListener, J as SessionStatus } from '../ability-g6nBpOQM.cjs';
3
+ import { ac as User, a1 as SessionControllerOptions, A as AuthyonClient, a3 as SessionSnapshot, a0 as Session, f as AbilitySubject, t as AuthyonAbilityOptions, r as AuthyonAbility } from '../ability-BTcayvkM.cjs';
4
+ export { w as AuthyonSessionController, a4 as SessionSnapshotListener, a5 as SessionStatus } from '../ability-BTcayvkM.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 { U as User, B as SessionControllerOptions, A as AuthyonClient, E as SessionSnapshot, S as Session, f as AbilitySubject, n as AuthyonAbilityOptions, l as AuthyonAbility } from '../ability-g6nBpOQM.js';
4
- export { q as AuthyonSessionController, G as SessionSnapshotListener, J as SessionStatus } from '../ability-g6nBpOQM.js';
3
+ import { ac as User, a1 as SessionControllerOptions, A as AuthyonClient, a3 as SessionSnapshot, a0 as Session, f as AbilitySubject, t as AuthyonAbilityOptions, r as AuthyonAbility } from '../ability-BTcayvkM.js';
4
+ export { w as AuthyonSessionController, a4 as SessionSnapshotListener, a5 as SessionStatus } from '../ability-BTcayvkM.js';
5
5
 
6
6
  interface AuthyonProviderProps<TUser extends User = User> extends SessionControllerOptions {
7
7
  client: AuthyonClient;
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  AuthyonSessionController,
4
4
  createAuthyonAbility
5
- } from "../chunk-EHZEUM47.js";
5
+ } from "../chunk-3XJY6BCI.js";
6
6
 
7
7
  // src/react/index.tsx
8
8
  import {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@authyon/auth",
3
- "version": "0.2.0-beta.2",
3
+ "version": "0.2.0-beta.21",
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",