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

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
@@ -9,7 +9,7 @@ Para gestão de organização/membros (secret key) e verificação de token no b
9
9
  ## Instalação
10
10
 
11
11
  ```bash
12
- npm install --save-exact @authyon/auth@0.2.0-beta.1
12
+ npm install --save-exact @authyon/auth@0.2.0-beta.4
13
13
  ```
14
14
 
15
15
  Durante a beta, `npm install @authyon/auth@beta` acompanha o prerelease mais recente. Fixar a versão exata é recomendado para builds reproduzíveis.
@@ -57,6 +57,26 @@ Use o entrypoint dedicado `@authyon/auth/react`:
57
57
 
58
58
  O provider faz refresh automático, valida a sessão por `GET /auth/me`, atualiza usuário e permissions, revalida quando a aba volta ao foco e não libera guards enquanto a validação inicial estiver pendente. Use `useAuthyon()`, `useAuthyonAbility()` e `useCan()` para fluxos programáticos.
59
59
 
60
+ Use `transformUser` para expor campos derivados sem alterar a sessão mantida pela lib:
61
+
62
+ ```tsx
63
+ type ApplicationUser = User & { fullName: string };
64
+
65
+ <AuthyonProvider<ApplicationUser>
66
+ client={authyon}
67
+ transformUser={(user) => ({
68
+ ...user,
69
+ fullName: [user.firstName, user.lastName].filter(Boolean).join(" "),
70
+ })}
71
+ >
72
+ <App />
73
+ </AuthyonProvider>;
74
+
75
+ const { user, session } = useAuthyon<ApplicationUser>();
76
+ ```
77
+
78
+ O usuário transformado é retornado em `user` e em `session.user` após validação, refresh, login e troca de organização. O transformador deve ser puro e só derivar dados do perfil do Authyon; dados externos pertencem ao contexto da aplicação.
79
+
60
80
  Para configuração progressiva, use o builder:
61
81
 
62
82
  ```ts
@@ -209,7 +229,7 @@ Métodos de sessão/auth ficam soltos no client; os que giram em torno de um rec
209
229
 
210
230
  | Método | Endpoint |
211
231
  | ----------------------------------------------------- | ---------------------------------------------- |
212
- | `organization.list()` | `GET /auth/tenants` |
232
+ | `organization.list({ search?, skip?, take? })` | `GET /auth/tenants` |
213
233
  | `organization.create(params?)` | `POST /auth/tenants` |
214
234
  | `organization.get(organizationId)` | `GET /auth/tenants/{id}` |
215
235
  | `organization.rename(organizationId, name)` | `PATCH /auth/tenants/{id}` |
@@ -93,12 +93,17 @@ interface Organization {
93
93
  name?: string;
94
94
  description?: string;
95
95
  roles?: string[];
96
+ /** JSON-encoded values for the organization's configured custom fields. */
97
+ customFields?: string;
98
+ /** JSON-encoded metadata visible to members of the organization. */
99
+ publicMetadata?: string;
96
100
  }
97
101
  /** POST /auth/tenants — creates an organization owned by the signed-in user. */
98
102
  interface CreateOrganizationInput {
99
103
  name?: string;
100
104
  slug?: string;
101
105
  description?: string;
106
+ customFields?: Record<string, unknown>;
102
107
  }
103
108
  /** GET /auth/tenants/{organizationId}/members — confirmed against the live API. */
104
109
  interface OrganizationMember {
@@ -157,6 +162,8 @@ interface RegisterInput {
157
162
  email: string;
158
163
  username?: string;
159
164
  password: string;
165
+ /** Values for custom user fields configured in the environment. */
166
+ customFields?: Record<string, unknown>;
160
167
  }
161
168
  interface LoginInput {
162
169
  /** Provide `email` or `username`. */
@@ -289,6 +296,12 @@ type AuthState = "signed_out" | "authenticated" | "expired";
289
296
  interface AuthyonClientOptions {
290
297
  /** Publishable environment key (`pk_live_...` / `pk_test_...`). */
291
298
  envKey: string;
299
+ /**
300
+ * Originating client IP to forward on every API call as `X-Forwarded-For`.
301
+ * Browsers cannot reliably discover their public IP by themselves; set this
302
+ * only when your application receives it from trusted infrastructure.
303
+ */
304
+ clientIp?: string;
292
305
  /** API origin. Defaults to `https://api.authyon.com`; HTTPS is required outside loopback. */
293
306
  baseUrl?: string;
294
307
  /** Allow an HTTP `baseUrl`. Intended only for explicitly trusted local development. */
@@ -312,6 +325,7 @@ interface AuthyonClientOptions {
312
325
 
313
326
  declare class AuthyonClient {
314
327
  private readonly envKey;
328
+ private readonly clientIp?;
315
329
  private readonly baseUrl;
316
330
  private readonly storage;
317
331
  private readonly autoRefresh;
@@ -421,8 +435,10 @@ declare class AuthyonClient {
421
435
  confirmPasswordReset: (token: string, newPassword: string) => Promise<void>;
422
436
  };
423
437
  readonly organization: {
424
- /** GET /auth/tenants — all organization memberships. */
425
- list: () => Promise<Organization[]>;
438
+ /** GET /auth/tenants — filterable list of organization memberships. */
439
+ list: (params?: {
440
+ search?: string;
441
+ } & PaginationOptions) => Promise<Organization[]>;
426
442
  /**
427
443
  * POST /auth/tenants — creates an organization owned by the signed-in
428
444
  * user (only available when self-service organization creation is
@@ -578,6 +594,13 @@ interface AuthyonPermissionSource {
578
594
  /** OAuth scopes are also accepted and interpreted as Authyon permissions. */
579
595
  scope?: string | null;
580
596
  }
597
+ /** A declarative group of permissions evaluated against an Authyon permission source. */
598
+ interface PermissionGroup {
599
+ /** Permissions that must all be granted. */
600
+ allOf?: readonly string[];
601
+ /** Permissions where at least one must be granted. */
602
+ anyOf?: readonly string[];
603
+ }
581
604
  interface AuthyonAbilityOptions {
582
605
  rules?: readonly AbilityRule[];
583
606
  /** Rules added when the source contains a matching Authyon role. */
@@ -609,7 +632,14 @@ declare class AuthyonAbilityBuilder {
609
632
  }
610
633
  /** Creates an ability from Authyon permissions, OAuth scope and optional role rules. */
611
634
  declare function createAuthyonAbility(source?: AuthyonPermissionSource, options?: AuthyonAbilityOptions): AuthyonAbility;
635
+ /** Checks an Authyon permission string using the same wildcard rules as an ability. */
636
+ declare function hasPermission(source: AuthyonPermissionSource | readonly string[], requiredPermission: string): boolean;
637
+ /**
638
+ * Checks a permission group using Authyon's standard permission and wildcard rules.
639
+ * Empty or omitted groups do not add authorization requirements.
640
+ */
641
+ declare function hasPermissionGroup(source: AuthyonPermissionSource | readonly string[], group: PermissionGroup): boolean;
612
642
  /** Converts Authyon's `subject:action` permission strings to authorization rules. */
613
643
  declare function createAuthyonRules(source?: AuthyonPermissionSource, options?: AuthyonAbilityOptions): AbilityRule[];
614
644
 
615
- export { createAuthyonRules as $, AuthyonClient as A, type SessionControllerOptions as B, type CreateOrganizationInput as C, type SessionInfo as D, type SessionSnapshot as E, FetchHttpAdapter as F, type SessionSnapshotListener as G, type HttpAdapter as H, type IntrospectResult as I, type SessionStatus as J, type SsoProvider as K, LoggingHttpAdapter as L, type TwoFactorChallenge as M, type TwoFactorMethod as N, type Organization as O, type Paged as P, type TwoFactorStatus as Q, type RegisterInput as R, type Session as S, type TokenStorage as T, type User as U, type ValidateResult as V, type VerifyTwoFactorInput as W, type WebAuthnAssertion as X, type WebAuthnCeremonyStart as Y, type WebAuthnCredential as Z, createAuthyonAbility as _, type HttpLoggerOptions as a, createClient as a0, 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 Role as z };
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 };
@@ -93,12 +93,17 @@ interface Organization {
93
93
  name?: string;
94
94
  description?: string;
95
95
  roles?: string[];
96
+ /** JSON-encoded values for the organization's configured custom fields. */
97
+ customFields?: string;
98
+ /** JSON-encoded metadata visible to members of the organization. */
99
+ publicMetadata?: string;
96
100
  }
97
101
  /** POST /auth/tenants — creates an organization owned by the signed-in user. */
98
102
  interface CreateOrganizationInput {
99
103
  name?: string;
100
104
  slug?: string;
101
105
  description?: string;
106
+ customFields?: Record<string, unknown>;
102
107
  }
103
108
  /** GET /auth/tenants/{organizationId}/members — confirmed against the live API. */
104
109
  interface OrganizationMember {
@@ -157,6 +162,8 @@ interface RegisterInput {
157
162
  email: string;
158
163
  username?: string;
159
164
  password: string;
165
+ /** Values for custom user fields configured in the environment. */
166
+ customFields?: Record<string, unknown>;
160
167
  }
161
168
  interface LoginInput {
162
169
  /** Provide `email` or `username`. */
@@ -289,6 +296,12 @@ type AuthState = "signed_out" | "authenticated" | "expired";
289
296
  interface AuthyonClientOptions {
290
297
  /** Publishable environment key (`pk_live_...` / `pk_test_...`). */
291
298
  envKey: string;
299
+ /**
300
+ * Originating client IP to forward on every API call as `X-Forwarded-For`.
301
+ * Browsers cannot reliably discover their public IP by themselves; set this
302
+ * only when your application receives it from trusted infrastructure.
303
+ */
304
+ clientIp?: string;
292
305
  /** API origin. Defaults to `https://api.authyon.com`; HTTPS is required outside loopback. */
293
306
  baseUrl?: string;
294
307
  /** Allow an HTTP `baseUrl`. Intended only for explicitly trusted local development. */
@@ -312,6 +325,7 @@ interface AuthyonClientOptions {
312
325
 
313
326
  declare class AuthyonClient {
314
327
  private readonly envKey;
328
+ private readonly clientIp?;
315
329
  private readonly baseUrl;
316
330
  private readonly storage;
317
331
  private readonly autoRefresh;
@@ -421,8 +435,10 @@ declare class AuthyonClient {
421
435
  confirmPasswordReset: (token: string, newPassword: string) => Promise<void>;
422
436
  };
423
437
  readonly organization: {
424
- /** GET /auth/tenants — all organization memberships. */
425
- list: () => Promise<Organization[]>;
438
+ /** GET /auth/tenants — filterable list of organization memberships. */
439
+ list: (params?: {
440
+ search?: string;
441
+ } & PaginationOptions) => Promise<Organization[]>;
426
442
  /**
427
443
  * POST /auth/tenants — creates an organization owned by the signed-in
428
444
  * user (only available when self-service organization creation is
@@ -578,6 +594,13 @@ interface AuthyonPermissionSource {
578
594
  /** OAuth scopes are also accepted and interpreted as Authyon permissions. */
579
595
  scope?: string | null;
580
596
  }
597
+ /** A declarative group of permissions evaluated against an Authyon permission source. */
598
+ interface PermissionGroup {
599
+ /** Permissions that must all be granted. */
600
+ allOf?: readonly string[];
601
+ /** Permissions where at least one must be granted. */
602
+ anyOf?: readonly string[];
603
+ }
581
604
  interface AuthyonAbilityOptions {
582
605
  rules?: readonly AbilityRule[];
583
606
  /** Rules added when the source contains a matching Authyon role. */
@@ -609,7 +632,14 @@ declare class AuthyonAbilityBuilder {
609
632
  }
610
633
  /** Creates an ability from Authyon permissions, OAuth scope and optional role rules. */
611
634
  declare function createAuthyonAbility(source?: AuthyonPermissionSource, options?: AuthyonAbilityOptions): AuthyonAbility;
635
+ /** Checks an Authyon permission string using the same wildcard rules as an ability. */
636
+ declare function hasPermission(source: AuthyonPermissionSource | readonly string[], requiredPermission: string): boolean;
637
+ /**
638
+ * Checks a permission group using Authyon's standard permission and wildcard rules.
639
+ * Empty or omitted groups do not add authorization requirements.
640
+ */
641
+ declare function hasPermissionGroup(source: AuthyonPermissionSource | readonly string[], group: PermissionGroup): boolean;
612
642
  /** Converts Authyon's `subject:action` permission strings to authorization rules. */
613
643
  declare function createAuthyonRules(source?: AuthyonPermissionSource, options?: AuthyonAbilityOptions): AbilityRule[];
614
644
 
615
- export { createAuthyonRules as $, AuthyonClient as A, type SessionControllerOptions as B, type CreateOrganizationInput as C, type SessionInfo as D, type SessionSnapshot as E, FetchHttpAdapter as F, type SessionSnapshotListener as G, type HttpAdapter as H, type IntrospectResult as I, type SessionStatus as J, type SsoProvider as K, LoggingHttpAdapter as L, type TwoFactorChallenge as M, type TwoFactorMethod as N, type Organization as O, type Paged as P, type TwoFactorStatus as Q, type RegisterInput as R, type Session as S, type TokenStorage as T, type User as U, type ValidateResult as V, type VerifyTwoFactorInput as W, type WebAuthnAssertion as X, type WebAuthnCeremonyStart as Y, type WebAuthnCredential as Z, createAuthyonAbility as _, type HttpLoggerOptions as a, createClient as a0, 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 Role as z };
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 };
@@ -1,6 +1,9 @@
1
1
  // src/session/sessionController.ts
2
2
  var SERVER_SNAPSHOT = {
3
- status: "unauthenticated",
3
+ // The server cannot inspect browser storage. Reporting unauthenticated here
4
+ // makes guards redirect during hydration before a persisted session can be
5
+ // restored and validated on the client.
6
+ status: "validating",
4
7
  session: null,
5
8
  user: null,
6
9
  error: null
@@ -190,6 +193,22 @@ var AuthyonAbilityBuilder = class {
190
193
  function createAuthyonAbility(source = {}, options = {}) {
191
194
  return new AuthyonAbility(createAuthyonRules(source, options), options.detectSubjectType);
192
195
  }
196
+ function hasPermission(source, requiredPermission) {
197
+ const requirement = permissionToRule(requiredPermission);
198
+ if (!requirement || typeof requirement.action !== "string" || typeof requirement.subject !== "string") {
199
+ return false;
200
+ }
201
+ const permissionSource = isPermissionList(source) ? { permissions: source } : source;
202
+ return createAuthyonAbility(permissionSource).can(requirement.action, requirement.subject);
203
+ }
204
+ function hasPermissionGroup(source, group) {
205
+ const hasAll = (group.allOf ?? []).every((permission) => hasPermission(source, permission));
206
+ const hasAny = !group.anyOf?.length || group.anyOf.some((permission) => hasPermission(source, permission));
207
+ return hasAll && hasAny;
208
+ }
209
+ function isPermissionList(source) {
210
+ return Array.isArray(source);
211
+ }
193
212
  function createAuthyonRules(source = {}, options = {}) {
194
213
  const permissions = /* @__PURE__ */ new Set([
195
214
  ...source.permissions ?? [],
@@ -219,9 +238,14 @@ function permissionToRule(permission) {
219
238
  }
220
239
  function matchesToken(value, expected, wildcard) {
221
240
  return (Array.isArray(value) ? value : [value]).some(
222
- (candidate) => candidate === expected || candidate === wildcard || candidate === "*"
241
+ (candidate) => candidate === expected || candidate === wildcard || candidate === "*" || matchesSegments(candidate, expected)
223
242
  );
224
243
  }
244
+ function matchesSegments(pattern, value) {
245
+ const patternSegments = pattern.split(":");
246
+ const valueSegments = value.split(":");
247
+ return patternSegments.length === valueSegments.length && patternSegments.every((segment, index) => segment === "*" || segment === valueSegments[index]);
248
+ }
225
249
  function matchesField(pattern, field) {
226
250
  if (pattern === "*" || pattern === field) return true;
227
251
  return pattern.endsWith(".*") && field.startsWith(pattern.slice(0, -1));
@@ -299,5 +323,7 @@ export {
299
323
  AuthyonAbility,
300
324
  AuthyonAbilityBuilder,
301
325
  createAuthyonAbility,
326
+ hasPermission,
327
+ hasPermissionGroup,
302
328
  createAuthyonRules
303
329
  };
package/dist/index.cjs CHANGED
@@ -34,7 +34,9 @@ __export(index_exports, {
34
34
  createClient: () => createClient,
35
35
  createDefaultStorage: () => createDefaultStorage,
36
36
  createLocalStorage: () => createLocalStorage,
37
- createMemoryStorage: () => createMemoryStorage
37
+ createMemoryStorage: () => createMemoryStorage,
38
+ hasPermission: () => hasPermission,
39
+ hasPermissionGroup: () => hasPermissionGroup
38
40
  });
39
41
  module.exports = __toCommonJS(index_exports);
40
42
 
@@ -222,7 +224,8 @@ var FetchHttpAdapter = class {
222
224
  method: request.method,
223
225
  headers: request.headers,
224
226
  body: request.body,
225
- signal: request.signal
227
+ signal: request.signal,
228
+ redirect: "error"
226
229
  });
227
230
  }
228
231
  };
@@ -274,6 +277,10 @@ function defaultHttpLogger(event) {
274
277
  }
275
278
  function sanitizeUrl(value) {
276
279
  const url = new URL(value);
280
+ url.username = "";
281
+ url.password = "";
282
+ url.hash = "";
283
+ url.pathname = url.pathname.replace(/(\/platform\/workspace-invites\/)[^/]+/u, "$1REDACTED");
277
284
  const queryKeys = /* @__PURE__ */ new Set();
278
285
  url.searchParams.forEach((_value, key) => queryKeys.add(key));
279
286
  url.search = queryKeys.size > 0 ? [...queryKeys].map((key) => `${encodeURIComponent(key)}=REDACTED`).join("&") : "";
@@ -397,6 +404,28 @@ var JsonHttpClient = class {
397
404
  }
398
405
  };
399
406
 
407
+ // ../../internal/core/http/clientIp.ts
408
+ function clientIpHeaders(context) {
409
+ if (!context?.clientIp) return {};
410
+ return { "X-Forwarded-For": normalizeClientIp(context.clientIp) };
411
+ }
412
+ function normalizeClientIp(value) {
413
+ let ip = value.trim();
414
+ if (ip.startsWith("[") && ip.endsWith("]")) ip = ip.slice(1, -1);
415
+ if (!ip || ip.length > 45 || ip.includes(",") || /[\r\n\s]/.test(ip) || !/^[0-9a-f:.]+$/i.test(ip) || !ip.includes(".") && !ip.includes(":")) {
416
+ throw invalidClientIp();
417
+ }
418
+ try {
419
+ new URL(ip.includes(":") ? `http://[${ip}]` : `http://${ip}`);
420
+ } catch {
421
+ throw invalidClientIp();
422
+ }
423
+ return ip;
424
+ }
425
+ function invalidClientIp() {
426
+ return new Error("Authyon: `clientIp` must be a single valid IPv4 or IPv6 address");
427
+ }
428
+
400
429
  // src/client/authyonClient.ts
401
430
  var FALLBACK_EXPIRES_IN = 1800;
402
431
  function readTokens(raw) {
@@ -484,8 +513,8 @@ var AuthyonClient = class {
484
513
  };
485
514
  // ── Organization ─────────────────────────────────────────────────────────
486
515
  this.organization = {
487
- /** GET /auth/tenants — all organization memberships. */
488
- list: () => this.request("/auth/tenants", { bearer: true }),
516
+ /** GET /auth/tenants — filterable list of organization memberships. */
517
+ list: (params = {}) => this.request(appendQuery("/auth/tenants", params), { bearer: true }),
489
518
  /**
490
519
  * POST /auth/tenants — creates an organization owned by the signed-in
491
520
  * user (only available when self-service organization creation is
@@ -612,6 +641,7 @@ var AuthyonClient = class {
612
641
  if (!options.envKey)
613
642
  throw new Error("Authyon: `envKey` is required (pk_live_... / pk_test_...)");
614
643
  this.envKey = options.envKey;
644
+ this.clientIp = options.clientIp;
615
645
  this.transport = createSharedTransport({
616
646
  baseUrl: options.baseUrl ?? DEFAULT_BASE_URL,
617
647
  allowInsecureHttp: options.allowInsecureHttp,
@@ -724,6 +754,7 @@ var AuthyonClient = class {
724
754
  async request(path, options = {}, isRetry = false) {
725
755
  const headers = {
726
756
  "X-Authyon-Environment": this.envKey,
757
+ ...clientIpHeaders({ clientIp: this.clientIp }),
727
758
  ...options.headers
728
759
  };
729
760
  if (options.bearer) {
@@ -888,6 +919,10 @@ var AuthyonClientBuilder = class {
888
919
  this.options.autoRefresh = enabled;
889
920
  return this;
890
921
  }
922
+ withClientIp(clientIp) {
923
+ this.options.clientIp = clientIp;
924
+ return this;
925
+ }
891
926
  withTimeout(timeoutMs) {
892
927
  this.options.timeoutMs = timeoutMs;
893
928
  return this;
@@ -907,7 +942,10 @@ var AuthyonClientBuilder = class {
907
942
 
908
943
  // src/session/sessionController.ts
909
944
  var SERVER_SNAPSHOT = {
910
- status: "unauthenticated",
945
+ // The server cannot inspect browser storage. Reporting unauthenticated here
946
+ // makes guards redirect during hydration before a persisted session can be
947
+ // restored and validated on the client.
948
+ status: "validating",
911
949
  session: null,
912
950
  user: null,
913
951
  error: null
@@ -1097,6 +1135,22 @@ var AuthyonAbilityBuilder = class {
1097
1135
  function createAuthyonAbility(source = {}, options = {}) {
1098
1136
  return new AuthyonAbility(createAuthyonRules(source, options), options.detectSubjectType);
1099
1137
  }
1138
+ function hasPermission(source, requiredPermission) {
1139
+ const requirement = permissionToRule(requiredPermission);
1140
+ if (!requirement || typeof requirement.action !== "string" || typeof requirement.subject !== "string") {
1141
+ return false;
1142
+ }
1143
+ const permissionSource = isPermissionList(source) ? { permissions: source } : source;
1144
+ return createAuthyonAbility(permissionSource).can(requirement.action, requirement.subject);
1145
+ }
1146
+ function hasPermissionGroup(source, group) {
1147
+ const hasAll = (group.allOf ?? []).every((permission) => hasPermission(source, permission));
1148
+ const hasAny = !group.anyOf?.length || group.anyOf.some((permission) => hasPermission(source, permission));
1149
+ return hasAll && hasAny;
1150
+ }
1151
+ function isPermissionList(source) {
1152
+ return Array.isArray(source);
1153
+ }
1100
1154
  function createAuthyonRules(source = {}, options = {}) {
1101
1155
  const permissions = /* @__PURE__ */ new Set([
1102
1156
  ...source.permissions ?? [],
@@ -1126,9 +1180,14 @@ function permissionToRule(permission) {
1126
1180
  }
1127
1181
  function matchesToken(value, expected, wildcard) {
1128
1182
  return (Array.isArray(value) ? value : [value]).some(
1129
- (candidate) => candidate === expected || candidate === wildcard || candidate === "*"
1183
+ (candidate) => candidate === expected || candidate === wildcard || candidate === "*" || matchesSegments(candidate, expected)
1130
1184
  );
1131
1185
  }
1186
+ function matchesSegments(pattern, value) {
1187
+ const patternSegments = pattern.split(":");
1188
+ const valueSegments = value.split(":");
1189
+ return patternSegments.length === valueSegments.length && patternSegments.every((segment, index) => segment === "*" || segment === valueSegments[index]);
1190
+ }
1132
1191
  function matchesField(pattern, field) {
1133
1192
  if (pattern === "*" || pattern === field) return true;
1134
1193
  return pattern.endsWith(".*") && field.startsWith(pattern.slice(0, -1));
@@ -1216,5 +1275,7 @@ function isAbilityRule(value) {
1216
1275
  createClient,
1217
1276
  createDefaultStorage,
1218
1277
  createLocalStorage,
1219
- createMemoryStorage
1278
+ createMemoryStorage,
1279
+ hasPermission,
1280
+ hasPermissionGroup
1220
1281
  });
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { T as TokenStorage, H as HttpAdapter, a as HttpLoggerOptions, A as AuthyonClient } from './ability-CVplsuzO.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, 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 } from './ability-CVplsuzO.cjs';
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
3
 
4
4
  /** Builds an Authyon browser client through explicit, progressive configuration. */
5
5
  declare class AuthyonClientBuilder {
@@ -8,6 +8,7 @@ declare class AuthyonClientBuilder {
8
8
  withBaseUrl(baseUrl: string, allowInsecureHttp?: boolean): this;
9
9
  withStorage(storage: TokenStorage): this;
10
10
  withAutomaticRefresh(enabled?: boolean): this;
11
+ withClientIp(clientIp: string): this;
11
12
  withTimeout(timeoutMs: number): this;
12
13
  withHttpAdapter(httpAdapter: HttpAdapter): this;
13
14
  withHttpLogger(httpLogger: HttpLoggerOptions): this;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { T as TokenStorage, H as HttpAdapter, a as HttpLoggerOptions, A as AuthyonClient } from './ability-CVplsuzO.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 } from './ability-CVplsuzO.js';
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
3
 
4
4
  /** Builds an Authyon browser client through explicit, progressive configuration. */
5
5
  declare class AuthyonClientBuilder {
@@ -8,6 +8,7 @@ declare class AuthyonClientBuilder {
8
8
  withBaseUrl(baseUrl: string, allowInsecureHttp?: boolean): this;
9
9
  withStorage(storage: TokenStorage): this;
10
10
  withAutomaticRefresh(enabled?: boolean): this;
11
+ withClientIp(clientIp: string): this;
11
12
  withTimeout(timeoutMs: number): this;
12
13
  withHttpAdapter(httpAdapter: HttpAdapter): this;
13
14
  withHttpLogger(httpLogger: HttpLoggerOptions): this;
package/dist/index.js CHANGED
@@ -3,8 +3,10 @@ import {
3
3
  AuthyonAbilityBuilder,
4
4
  AuthyonSessionController,
5
5
  createAuthyonAbility,
6
- createAuthyonRules
7
- } from "./chunk-OKPSF4LZ.js";
6
+ createAuthyonRules,
7
+ hasPermission,
8
+ hasPermissionGroup
9
+ } from "./chunk-3XJY6BCI.js";
8
10
 
9
11
  // ../../internal/core/errors/authyonError.ts
10
12
  var ErrorCodes = {
@@ -190,7 +192,8 @@ var FetchHttpAdapter = class {
190
192
  method: request.method,
191
193
  headers: request.headers,
192
194
  body: request.body,
193
- signal: request.signal
195
+ signal: request.signal,
196
+ redirect: "error"
194
197
  });
195
198
  }
196
199
  };
@@ -242,6 +245,10 @@ function defaultHttpLogger(event) {
242
245
  }
243
246
  function sanitizeUrl(value) {
244
247
  const url = new URL(value);
248
+ url.username = "";
249
+ url.password = "";
250
+ url.hash = "";
251
+ url.pathname = url.pathname.replace(/(\/platform\/workspace-invites\/)[^/]+/u, "$1REDACTED");
245
252
  const queryKeys = /* @__PURE__ */ new Set();
246
253
  url.searchParams.forEach((_value, key) => queryKeys.add(key));
247
254
  url.search = queryKeys.size > 0 ? [...queryKeys].map((key) => `${encodeURIComponent(key)}=REDACTED`).join("&") : "";
@@ -365,6 +372,28 @@ var JsonHttpClient = class {
365
372
  }
366
373
  };
367
374
 
375
+ // ../../internal/core/http/clientIp.ts
376
+ function clientIpHeaders(context) {
377
+ if (!context?.clientIp) return {};
378
+ return { "X-Forwarded-For": normalizeClientIp(context.clientIp) };
379
+ }
380
+ function normalizeClientIp(value) {
381
+ let ip = value.trim();
382
+ if (ip.startsWith("[") && ip.endsWith("]")) ip = ip.slice(1, -1);
383
+ if (!ip || ip.length > 45 || ip.includes(",") || /[\r\n\s]/.test(ip) || !/^[0-9a-f:.]+$/i.test(ip) || !ip.includes(".") && !ip.includes(":")) {
384
+ throw invalidClientIp();
385
+ }
386
+ try {
387
+ new URL(ip.includes(":") ? `http://[${ip}]` : `http://${ip}`);
388
+ } catch {
389
+ throw invalidClientIp();
390
+ }
391
+ return ip;
392
+ }
393
+ function invalidClientIp() {
394
+ return new Error("Authyon: `clientIp` must be a single valid IPv4 or IPv6 address");
395
+ }
396
+
368
397
  // src/client/authyonClient.ts
369
398
  var FALLBACK_EXPIRES_IN = 1800;
370
399
  function readTokens(raw) {
@@ -452,8 +481,8 @@ var AuthyonClient = class {
452
481
  };
453
482
  // ── Organization ─────────────────────────────────────────────────────────
454
483
  this.organization = {
455
- /** GET /auth/tenants — all organization memberships. */
456
- list: () => this.request("/auth/tenants", { bearer: true }),
484
+ /** GET /auth/tenants — filterable list of organization memberships. */
485
+ list: (params = {}) => this.request(appendQuery("/auth/tenants", params), { bearer: true }),
457
486
  /**
458
487
  * POST /auth/tenants — creates an organization owned by the signed-in
459
488
  * user (only available when self-service organization creation is
@@ -580,6 +609,7 @@ var AuthyonClient = class {
580
609
  if (!options.envKey)
581
610
  throw new Error("Authyon: `envKey` is required (pk_live_... / pk_test_...)");
582
611
  this.envKey = options.envKey;
612
+ this.clientIp = options.clientIp;
583
613
  this.transport = createSharedTransport({
584
614
  baseUrl: options.baseUrl ?? DEFAULT_BASE_URL,
585
615
  allowInsecureHttp: options.allowInsecureHttp,
@@ -692,6 +722,7 @@ var AuthyonClient = class {
692
722
  async request(path, options = {}, isRetry = false) {
693
723
  const headers = {
694
724
  "X-Authyon-Environment": this.envKey,
725
+ ...clientIpHeaders({ clientIp: this.clientIp }),
695
726
  ...options.headers
696
727
  };
697
728
  if (options.bearer) {
@@ -856,6 +887,10 @@ var AuthyonClientBuilder = class {
856
887
  this.options.autoRefresh = enabled;
857
888
  return this;
858
889
  }
890
+ withClientIp(clientIp) {
891
+ this.options.clientIp = clientIp;
892
+ return this;
893
+ }
859
894
  withTimeout(timeoutMs) {
860
895
  this.options.timeoutMs = timeoutMs;
861
896
  return this;
@@ -887,5 +922,7 @@ export {
887
922
  createClient,
888
923
  createDefaultStorage,
889
924
  createLocalStorage,
890
- createMemoryStorage
925
+ createMemoryStorage,
926
+ hasPermission,
927
+ hasPermissionGroup
891
928
  };
@@ -107,9 +107,14 @@ function permissionToRule(permission) {
107
107
  }
108
108
  function matchesToken(value, expected, wildcard) {
109
109
  return (Array.isArray(value) ? value : [value]).some(
110
- (candidate) => candidate === expected || candidate === wildcard || candidate === "*"
110
+ (candidate) => candidate === expected || candidate === wildcard || candidate === "*" || matchesSegments(candidate, expected)
111
111
  );
112
112
  }
113
+ function matchesSegments(pattern, value) {
114
+ const patternSegments = pattern.split(":");
115
+ const valueSegments = value.split(":");
116
+ return patternSegments.length === valueSegments.length && patternSegments.every((segment, index) => segment === "*" || segment === valueSegments[index]);
117
+ }
113
118
  function matchesField(pattern, field) {
114
119
  if (pattern === "*" || pattern === field) return true;
115
120
  return pattern.endsWith(".*") && field.startsWith(pattern.slice(0, -1));
@@ -184,7 +189,10 @@ function isAbilityRule(value) {
184
189
 
185
190
  // src/session/sessionController.ts
186
191
  var SERVER_SNAPSHOT = {
187
- status: "unauthenticated",
192
+ // The server cannot inspect browser storage. Reporting unauthenticated here
193
+ // makes guards redirect during hydration before a persisted session can be
194
+ // restored and validated on the client.
195
+ status: "validating",
188
196
  session: null,
189
197
  user: null,
190
198
  error: null
@@ -318,7 +326,8 @@ function AuthyonProvider({
318
326
  client,
319
327
  children,
320
328
  refreshAheadMs,
321
- validateOnFocus = true
329
+ validateOnFocus = true,
330
+ transformUser
322
331
  }) {
323
332
  const controllerRef = (0, import_react.useRef)(null);
324
333
  if (!controllerRef.current || controllerRef.current.client !== client) {
@@ -339,14 +348,18 @@ function AuthyonProvider({
339
348
  document.addEventListener("visibilitychange", validateWhenVisible);
340
349
  return () => document.removeEventListener("visibilitychange", validateWhenVisible);
341
350
  }, [controller, validateOnFocus]);
351
+ const exposedSnapshot = (0, import_react.useMemo)(
352
+ () => transformSnapshot(snapshot, transformUser),
353
+ [snapshot, transformUser]
354
+ );
342
355
  const value = (0, import_react.useMemo)(
343
356
  () => ({
344
- ...snapshot,
357
+ ...exposedSnapshot,
345
358
  client,
346
- validateSession: () => controller.validate(),
347
- refreshSession: () => controller.refreshNow()
359
+ validateSession: async () => transformSnapshot(await controller.validate(), transformUser),
360
+ refreshSession: async () => transformSnapshot(await controller.refreshNow(), transformUser)
348
361
  }),
349
- [client, controller, snapshot]
362
+ [client, controller, exposedSnapshot, transformUser]
350
363
  );
351
364
  return (0, import_react.createElement)(AuthyonReactContext.Provider, { value }, children);
352
365
  }
@@ -355,6 +368,15 @@ function useAuthyon() {
355
368
  if (!context) throw new Error("Authyon: `useAuthyon` must be used inside `AuthyonProvider`");
356
369
  return context;
357
370
  }
371
+ function transformSnapshot(snapshot, transformUser) {
372
+ const mapUser = (user2) => transformUser ? transformUser(user2) : user2;
373
+ const user = snapshot.user ? mapUser(snapshot.user) : null;
374
+ const session = snapshot.session ? {
375
+ ...snapshot.session,
376
+ user: snapshot.session.user ? snapshot.session.user === snapshot.user && user ? user : mapUser(snapshot.session.user) : void 0
377
+ } : null;
378
+ return { ...snapshot, session, user };
379
+ }
358
380
  function useAuthyonAbility(options = {}) {
359
381
  const { user } = useAuthyon();
360
382
  return (0, import_react.useMemo)(() => createAuthyonAbility(user ?? {}, options), [user, options]);
@@ -1,21 +1,30 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
- import { B as SessionControllerOptions, A as AuthyonClient, E as SessionSnapshot, f as AbilitySubject, n as AuthyonAbilityOptions, l as AuthyonAbility } from '../ability-CVplsuzO.cjs';
4
- export { q as AuthyonSessionController, G as SessionSnapshotListener, J as SessionStatus } from '../ability-CVplsuzO.cjs';
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';
5
5
 
6
- interface AuthyonProviderProps extends SessionControllerOptions {
6
+ interface AuthyonProviderProps<TUser extends User = User> extends SessionControllerOptions {
7
7
  client: AuthyonClient;
8
8
  children: ReactNode;
9
9
  /** Revalidate with `/auth/me` when the tab becomes visible. Defaults to true. */
10
10
  validateOnFocus?: boolean;
11
+ /** Derives the user exposed by `useAuthyon` without mutating the stored Authyon session. */
12
+ transformUser?: (user: User) => TUser;
11
13
  }
12
- interface AuthyonReactContextValue extends SessionSnapshot {
14
+ type AuthyonSession<TUser extends User = User> = Omit<Session, "user"> & {
15
+ user?: TUser;
16
+ };
17
+ type AuthyonSessionSnapshot<TUser extends User = User> = Omit<SessionSnapshot, "session" | "user"> & {
18
+ session: AuthyonSession<TUser> | null;
19
+ user: TUser | null;
20
+ };
21
+ interface AuthyonReactContextValue<TUser extends User = User> extends AuthyonSessionSnapshot<TUser> {
13
22
  client: AuthyonClient;
14
- validateSession(): Promise<SessionSnapshot>;
15
- refreshSession(): Promise<SessionSnapshot>;
23
+ validateSession(): Promise<AuthyonSessionSnapshot<TUser>>;
24
+ refreshSession(): Promise<AuthyonSessionSnapshot<TUser>>;
16
25
  }
17
- declare function AuthyonProvider({ client, children, refreshAheadMs, validateOnFocus, }: AuthyonProviderProps): react.FunctionComponentElement<react.ProviderProps<AuthyonReactContextValue | null>>;
18
- declare function useAuthyon(): AuthyonReactContextValue;
26
+ declare function AuthyonProvider<TUser extends User = User>({ client, children, refreshAheadMs, validateOnFocus, transformUser, }: AuthyonProviderProps<TUser>): react.FunctionComponentElement<react.ProviderProps<AuthyonReactContextValue<User> | null>>;
27
+ declare function useAuthyon<TUser extends User = User>(): AuthyonReactContextValue<TUser>;
19
28
  declare function useAuthyonAbility(options?: AuthyonAbilityOptions): AuthyonAbility;
20
29
  declare function useCan(action: string, subject: AbilitySubject, field?: string, options?: AuthyonAbilityOptions): boolean;
21
30
  interface SessionGuardProps {
@@ -35,4 +44,4 @@ interface PermissionGuardProps extends SessionGuardProps {
35
44
  }
36
45
  declare function PermissionGuard({ action, subject, field, forbiddenFallback, abilityOptions, children, ...sessionProps }: PermissionGuardProps): react.FunctionComponentElement<SessionGuardProps>;
37
46
 
38
- export { AuthyonProvider, type AuthyonProviderProps, PermissionGuard, type PermissionGuardProps, SessionControllerOptions, SessionGuard, type SessionGuardProps, SessionSnapshot, useAuthyon, useAuthyonAbility, useCan };
47
+ export { AuthyonProvider, type AuthyonProviderProps, type AuthyonReactContextValue, type AuthyonSession, type AuthyonSessionSnapshot, PermissionGuard, type PermissionGuardProps, SessionControllerOptions, SessionGuard, type SessionGuardProps, SessionSnapshot, useAuthyon, useAuthyonAbility, useCan };
@@ -1,21 +1,30 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
- import { B as SessionControllerOptions, A as AuthyonClient, E as SessionSnapshot, f as AbilitySubject, n as AuthyonAbilityOptions, l as AuthyonAbility } from '../ability-CVplsuzO.js';
4
- export { q as AuthyonSessionController, G as SessionSnapshotListener, J as SessionStatus } from '../ability-CVplsuzO.js';
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';
5
5
 
6
- interface AuthyonProviderProps extends SessionControllerOptions {
6
+ interface AuthyonProviderProps<TUser extends User = User> extends SessionControllerOptions {
7
7
  client: AuthyonClient;
8
8
  children: ReactNode;
9
9
  /** Revalidate with `/auth/me` when the tab becomes visible. Defaults to true. */
10
10
  validateOnFocus?: boolean;
11
+ /** Derives the user exposed by `useAuthyon` without mutating the stored Authyon session. */
12
+ transformUser?: (user: User) => TUser;
11
13
  }
12
- interface AuthyonReactContextValue extends SessionSnapshot {
14
+ type AuthyonSession<TUser extends User = User> = Omit<Session, "user"> & {
15
+ user?: TUser;
16
+ };
17
+ type AuthyonSessionSnapshot<TUser extends User = User> = Omit<SessionSnapshot, "session" | "user"> & {
18
+ session: AuthyonSession<TUser> | null;
19
+ user: TUser | null;
20
+ };
21
+ interface AuthyonReactContextValue<TUser extends User = User> extends AuthyonSessionSnapshot<TUser> {
13
22
  client: AuthyonClient;
14
- validateSession(): Promise<SessionSnapshot>;
15
- refreshSession(): Promise<SessionSnapshot>;
23
+ validateSession(): Promise<AuthyonSessionSnapshot<TUser>>;
24
+ refreshSession(): Promise<AuthyonSessionSnapshot<TUser>>;
16
25
  }
17
- declare function AuthyonProvider({ client, children, refreshAheadMs, validateOnFocus, }: AuthyonProviderProps): react.FunctionComponentElement<react.ProviderProps<AuthyonReactContextValue | null>>;
18
- declare function useAuthyon(): AuthyonReactContextValue;
26
+ declare function AuthyonProvider<TUser extends User = User>({ client, children, refreshAheadMs, validateOnFocus, transformUser, }: AuthyonProviderProps<TUser>): react.FunctionComponentElement<react.ProviderProps<AuthyonReactContextValue<User> | null>>;
27
+ declare function useAuthyon<TUser extends User = User>(): AuthyonReactContextValue<TUser>;
19
28
  declare function useAuthyonAbility(options?: AuthyonAbilityOptions): AuthyonAbility;
20
29
  declare function useCan(action: string, subject: AbilitySubject, field?: string, options?: AuthyonAbilityOptions): boolean;
21
30
  interface SessionGuardProps {
@@ -35,4 +44,4 @@ interface PermissionGuardProps extends SessionGuardProps {
35
44
  }
36
45
  declare function PermissionGuard({ action, subject, field, forbiddenFallback, abilityOptions, children, ...sessionProps }: PermissionGuardProps): react.FunctionComponentElement<SessionGuardProps>;
37
46
 
38
- export { AuthyonProvider, type AuthyonProviderProps, PermissionGuard, type PermissionGuardProps, SessionControllerOptions, SessionGuard, type SessionGuardProps, SessionSnapshot, useAuthyon, useAuthyonAbility, useCan };
47
+ export { AuthyonProvider, type AuthyonProviderProps, type AuthyonReactContextValue, type AuthyonSession, type AuthyonSessionSnapshot, PermissionGuard, type PermissionGuardProps, SessionControllerOptions, SessionGuard, type SessionGuardProps, SessionSnapshot, useAuthyon, useAuthyonAbility, useCan };
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  AuthyonSessionController,
4
4
  createAuthyonAbility
5
- } from "../chunk-OKPSF4LZ.js";
5
+ } from "../chunk-3XJY6BCI.js";
6
6
 
7
7
  // src/react/index.tsx
8
8
  import {
@@ -19,7 +19,8 @@ function AuthyonProvider({
19
19
  client,
20
20
  children,
21
21
  refreshAheadMs,
22
- validateOnFocus = true
22
+ validateOnFocus = true,
23
+ transformUser
23
24
  }) {
24
25
  const controllerRef = useRef(null);
25
26
  if (!controllerRef.current || controllerRef.current.client !== client) {
@@ -40,14 +41,18 @@ function AuthyonProvider({
40
41
  document.addEventListener("visibilitychange", validateWhenVisible);
41
42
  return () => document.removeEventListener("visibilitychange", validateWhenVisible);
42
43
  }, [controller, validateOnFocus]);
44
+ const exposedSnapshot = useMemo(
45
+ () => transformSnapshot(snapshot, transformUser),
46
+ [snapshot, transformUser]
47
+ );
43
48
  const value = useMemo(
44
49
  () => ({
45
- ...snapshot,
50
+ ...exposedSnapshot,
46
51
  client,
47
- validateSession: () => controller.validate(),
48
- refreshSession: () => controller.refreshNow()
52
+ validateSession: async () => transformSnapshot(await controller.validate(), transformUser),
53
+ refreshSession: async () => transformSnapshot(await controller.refreshNow(), transformUser)
49
54
  }),
50
- [client, controller, snapshot]
55
+ [client, controller, exposedSnapshot, transformUser]
51
56
  );
52
57
  return createElement(AuthyonReactContext.Provider, { value }, children);
53
58
  }
@@ -56,6 +61,15 @@ function useAuthyon() {
56
61
  if (!context) throw new Error("Authyon: `useAuthyon` must be used inside `AuthyonProvider`");
57
62
  return context;
58
63
  }
64
+ function transformSnapshot(snapshot, transformUser) {
65
+ const mapUser = (user2) => transformUser ? transformUser(user2) : user2;
66
+ const user = snapshot.user ? mapUser(snapshot.user) : null;
67
+ const session = snapshot.session ? {
68
+ ...snapshot.session,
69
+ user: snapshot.session.user ? snapshot.session.user === snapshot.user && user ? user : mapUser(snapshot.session.user) : void 0
70
+ } : null;
71
+ return { ...snapshot, session, user };
72
+ }
59
73
  function useAuthyonAbility(options = {}) {
60
74
  const { user } = useAuthyon();
61
75
  return useMemo(() => createAuthyonAbility(user ?? {}, options), [user, options]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@authyon/auth",
3
- "version": "0.2.0-beta.1",
3
+ "version": "0.2.0-beta.12",
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",