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

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,5 +1,5 @@
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';
1
+ import { T as TokenStorage, H as HttpAdapter, a as HttpLoggerOptions, A as AuthyonClient } from './ability-DjEMCVjx.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-DjEMCVjx.js';
3
3
 
4
4
  declare const ErrorCodes: {
5
5
  readonly Unknown: "unknown";
@@ -99,4 +99,54 @@ declare function createMemoryStorage(): TokenStorage;
99
99
  declare function createLocalStorage(key?: string): TokenStorage;
100
100
  declare function createDefaultStorage(): TokenStorage;
101
101
 
102
- export { AuthyonClient, AuthyonClientBuilder, AuthyonError, type AuthyonErrorAction, type AuthyonErrorBody, type AuthyonErrorCategory, type AuthyonErrorInterpretation, ErrorCodes, HttpAdapter, HttpLoggerOptions, type KnownErrorCode, TokenStorage, createDefaultStorage, createLocalStorage, createMemoryStorage };
102
+ /**
103
+ * The permission values the tenant surface checks, as constants.
104
+ *
105
+ * They are strings on the wire and nothing forces you through here — the point
106
+ * is that a rename shows up as a type error instead of as a 403 nobody can
107
+ * explain. `tenants:settings:manage` is exactly that case: it used to be
108
+ * written `tenants:manage`, a two-segment value the API could not even store,
109
+ * so the endpoint requiring it refused every caller.
110
+ */
111
+ declare const TenantPermissions: {
112
+ /** Rename the tenant and change its settings. */
113
+ readonly SettingsManage: "tenants:settings:manage";
114
+ /** Invite an address to the tenant. */
115
+ readonly MembersInvite: "tenants:members:invite";
116
+ /** Remove a member from the tenant. */
117
+ readonly MembersRemove: "tenants:members:remove";
118
+ };
119
+ /**
120
+ * What lets a member of one tenant reach the tenants beneath it.
121
+ *
122
+ * A separate domain, not a suffix on `tenants:…`, and the reason is worth
123
+ * knowing before you grant one: permissions match per segment, so naming these
124
+ * `tenants:tree:manage` would have handed every role already holding
125
+ * `tenants:*:*` power over the whole subtree the day the hierarchy shipped — a
126
+ * widening that appears in no diff. `subtenants:…` is only covered by `*:*:*`,
127
+ * which is explicit superuser.
128
+ *
129
+ * Reaching down is opt-in at every level: holding `tenants:settings:manage` on
130
+ * a parent grants nothing below it, and each of these is granted at the node
131
+ * the caller belongs to, never at the target.
132
+ */
133
+ declare const SubtenantPermissions: {
134
+ /** Read any tenant below one you belong to. */
135
+ readonly TreeRead: "subtenants:tree:read";
136
+ /** Rename a tenant below yours, and add or remove its members. */
137
+ readonly TreeManage: "subtenants:tree:manage";
138
+ /** Create a tenant underneath one you belong to. */
139
+ readonly TreeCreate: "subtenants:tree:create";
140
+ /**
141
+ * Make a tenant below yours the ACTIVE tenant of the session — acting inside
142
+ * it rather than on it from outside. Deliberately not the same permission as
143
+ * {@link SubtenantPermissions.TreeManage}: administering a sub-tenant through
144
+ * the management endpoints and operating as that sub-tenant are different
145
+ * powers, and granting the first should not hand over the second.
146
+ */
147
+ readonly TreeSwitch: "subtenants:tree:switch";
148
+ };
149
+ type TenantPermission = (typeof TenantPermissions)[keyof typeof TenantPermissions];
150
+ type SubtenantPermission = (typeof SubtenantPermissions)[keyof typeof SubtenantPermissions];
151
+
152
+ export { AuthyonClient, AuthyonClientBuilder, AuthyonError, type AuthyonErrorAction, type AuthyonErrorBody, type AuthyonErrorCategory, type AuthyonErrorInterpretation, ErrorCodes, HttpAdapter, HttpLoggerOptions, type KnownErrorCode, type SubtenantPermission, SubtenantPermissions, type TenantPermission, TenantPermissions, TokenStorage, createDefaultStorage, createLocalStorage, createMemoryStorage };
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  createAuthyonRules,
7
7
  hasPermission,
8
8
  hasPermissionGroup
9
- } from "./chunk-3XJY6BCI.js";
9
+ } from "./chunk-IX6U6A72.js";
10
10
 
11
11
  // ../../internal/core/security/approvals.ts
12
12
  function path(id, suffix = "") {
@@ -442,9 +442,11 @@ var JsonHttpClient = class {
442
442
  };
443
443
 
444
444
  // ../../internal/core/http/clientIp.ts
445
+ var CLIENT_IP_HEADER = "X-Authyon-Client-Ip";
445
446
  function clientIpHeaders(context) {
446
447
  if (!context?.clientIp) return {};
447
- return { "X-Forwarded-For": normalizeClientIp(context.clientIp) };
448
+ const ip = normalizeClientIp(context.clientIp);
449
+ return { "X-Forwarded-For": ip, [CLIENT_IP_HEADER]: ip };
448
450
  }
449
451
  function normalizeClientIp(value) {
450
452
  let ip = value.trim();
@@ -573,26 +575,59 @@ var AuthyonClient = class {
573
575
  };
574
576
  // ── Organization ─────────────────────────────────────────────────────────
575
577
  this.organization = {
576
- /** GET /auth/tenants — filterable list of organization memberships. */
578
+ /**
579
+ * GET /auth/tenants — filterable list of organization memberships.
580
+ *
581
+ * Also lists the organizations below them that the user may switch into
582
+ * without being a member, marked `inherited` — only the ones that actually
583
+ * carry `subtenants:tree:switch`, so the switcher never offers an
584
+ * organization the switch would then refuse. `depth` indents the list into
585
+ * the tree it describes.
586
+ */
577
587
  list: (params = {}) => this.request(appendQuery("/auth/tenants", params), { bearer: true }),
578
588
  /**
579
589
  * POST /auth/tenants — creates an organization owned by the signed-in
580
590
  * user (only available when self-service organization creation is
581
591
  * enabled for the environment).
592
+ *
593
+ * `parentTenantId` nests it under one the user already belongs to, which
594
+ * needs `subtenants:tree:create` there. The creator lands as the effective
595
+ * owner either way, with `tenants:settings:manage`,
596
+ * `tenants:members:invite` and `tenants:members:remove` granted directly
597
+ * inside the new organization.
582
598
  */
583
599
  create: (params = {}) => this.request("/auth/tenants", { method: "POST", bearer: true, body: params }),
584
600
  /** GET /auth/tenants/{organizationId} — fetch one of the user's organizations by id. */
585
601
  get: (organizationId) => this.request(`/auth/tenants/${encodeURIComponent(organizationId)}`, { bearer: true }),
586
602
  /**
587
603
  * PATCH /auth/tenants/{organizationId} — renames the organization.
588
- * Requires the `tenants:manage` custom permission on it.
604
+ *
605
+ * Requires the `tenants:settings:manage` custom permission on it, or
606
+ * `subtenants:tree:manage` on an organization above it. The value was
607
+ * spelled `tenants:manage` before 2026-10-31 — two segments, which the API
608
+ * could not store, so the endpoint refused everyone who had been granted
609
+ * it.
589
610
  */
590
611
  rename: (organizationId, name) => this.request(`/auth/tenants/${encodeURIComponent(organizationId)}`, {
591
612
  method: "PATCH",
592
613
  bearer: true,
593
614
  body: { name }
594
615
  }),
595
- /** POST /auth/switch-tenant — issues a fresh token scoped to the new organization. */
616
+ /**
617
+ * POST /auth/switch-tenant — issues a fresh token scoped to the new
618
+ * organization.
619
+ *
620
+ * Membership is enough for one of the user's own organizations. Descending
621
+ * into one below it takes `subtenants:tree:switch` at the organization they
622
+ * belong to — deliberately not the same permission as administering a
623
+ * sub-organization from the outside, since operating AS it is the larger of
624
+ * the two powers. The grants then travel from the node the user belongs to,
625
+ * and the token says so in its `tenant.via`.
626
+ *
627
+ * Refusals: `tenant.outside_scope` (no path down to it),
628
+ * `tenant.ancestor_disabled`, and `tenant.path_stale` when the tree moved
629
+ * under a session — sign-in again rather than retrying.
630
+ */
596
631
  switch: (organizationSlug) => this.request("/auth/switch-tenant", {
597
632
  method: "POST",
598
633
  bearer: true,
@@ -906,7 +941,21 @@ var AuthyonClient = class {
906
941
  return this.http.parse(response);
907
942
  }
908
943
  // ── Auth flows ───────────────────────────────────────────────────────────
909
- /** POST /auth/register — creates a new user (rate-limited to 20/hour per IP). */
944
+ /**
945
+ * POST /auth/register — creates a new user (rate-limited to 20/hour per IP).
946
+ *
947
+ * An environment may close public sign-up, and then this route answers as
948
+ * though it were not mapped: **404 with no body**, so the error carries
949
+ * status 404 and the code `unknown`. The SDK does not relabel it, because a
950
+ * wrong `baseUrl` looks exactly the same from here and guessing would send
951
+ * you after the wrong bug — check the environment's setting in the console
952
+ * and the URL you configured. Accounts in a closed environment are created
953
+ * by your backend instead, through `environment.users.create()` in
954
+ * `@authyon/server`, which already needs the `users:write` scope.
955
+ *
956
+ * It gates creation only. Everyone who already has an account signs in as
957
+ * before, and magic link and social sign-up keep their own switches.
958
+ */
910
959
  async register(params) {
911
960
  return this.request("/auth/register", { method: "POST", body: params });
912
961
  }
@@ -1066,6 +1115,32 @@ var AuthyonClientBuilder = class {
1066
1115
  return new AuthyonClient({ ...this.options });
1067
1116
  }
1068
1117
  };
1118
+
1119
+ // ../../internal/core/authorization/tenantPermissions.ts
1120
+ var TenantPermissions = {
1121
+ /** Rename the tenant and change its settings. */
1122
+ SettingsManage: "tenants:settings:manage",
1123
+ /** Invite an address to the tenant. */
1124
+ MembersInvite: "tenants:members:invite",
1125
+ /** Remove a member from the tenant. */
1126
+ MembersRemove: "tenants:members:remove"
1127
+ };
1128
+ var SubtenantPermissions = {
1129
+ /** Read any tenant below one you belong to. */
1130
+ TreeRead: "subtenants:tree:read",
1131
+ /** Rename a tenant below yours, and add or remove its members. */
1132
+ TreeManage: "subtenants:tree:manage",
1133
+ /** Create a tenant underneath one you belong to. */
1134
+ TreeCreate: "subtenants:tree:create",
1135
+ /**
1136
+ * Make a tenant below yours the ACTIVE tenant of the session — acting inside
1137
+ * it rather than on it from outside. Deliberately not the same permission as
1138
+ * {@link SubtenantPermissions.TreeManage}: administering a sub-tenant through
1139
+ * the management endpoints and operating as that sub-tenant are different
1140
+ * powers, and granting the first should not hand over the second.
1141
+ */
1142
+ TreeSwitch: "subtenants:tree:switch"
1143
+ };
1069
1144
  export {
1070
1145
  AuthyonAbility,
1071
1146
  AuthyonAbilityBuilder,
@@ -1076,6 +1151,8 @@ export {
1076
1151
  ErrorCodes,
1077
1152
  FetchHttpAdapter,
1078
1153
  LoggingHttpAdapter,
1154
+ SubtenantPermissions,
1155
+ TenantPermissions,
1079
1156
  createAuthyonAbility,
1080
1157
  createAuthyonRules,
1081
1158
  createClient,
@@ -83,13 +83,23 @@ function createAuthyonRules(source = {}, options = {}) {
83
83
  ...source.permissions ?? [],
84
84
  ...source.scope?.split(/\s+/).filter(Boolean) ?? []
85
85
  ]);
86
- const rules = [...permissions].map(permissionToRule).filter(isAbilityRule);
86
+ const rules = [...permissions].map(grantToRule).filter(isAbilityRule);
87
87
  for (const role of /* @__PURE__ */ new Set([...source.roles ?? [], ...options.roles ?? []])) {
88
88
  rules.push(...(options.roleRules?.[role] ?? []).map(cloneRule));
89
89
  }
90
90
  rules.push(...(options.rules ?? []).map(cloneRule));
91
91
  return rules;
92
92
  }
93
+ function grantToRule(permission) {
94
+ const normalized = permission.trim();
95
+ if (normalized && isFullWildcard(normalized)) {
96
+ return { action: "manage", subject: "all" };
97
+ }
98
+ return permissionToRule(normalized);
99
+ }
100
+ function isFullWildcard(permission) {
101
+ return permission.split(":").every((segment) => segment === "*");
102
+ }
93
103
  function permissionToRule(permission) {
94
104
  const normalized = permission.trim();
95
105
  if (!normalized) return null;
@@ -1,7 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
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';
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-DjEMCVjx.cjs';
4
+ export { w as AuthyonSessionController, a4 as SessionSnapshotListener, a5 as SessionStatus } from '../ability-DjEMCVjx.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 { 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';
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-DjEMCVjx.js';
4
+ export { w as AuthyonSessionController, a4 as SessionSnapshotListener, a5 as SessionStatus } from '../ability-DjEMCVjx.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-3XJY6BCI.js";
5
+ } from "../chunk-IX6U6A72.js";
6
6
 
7
7
  // src/react/index.tsx
8
8
  import {
package/package.json CHANGED
@@ -1,71 +1,71 @@
1
- {
2
- "name": "@authyon/auth",
3
- "version": "0.2.0-beta.21",
4
- "description": "Authyon SDK for browsers — auth, sessions, multi-tenant and 2FA for vanilla JS/TS.",
5
- "license": "MIT",
6
- "type": "module",
7
- "engines": {
8
- "node": ">=18"
9
- },
10
- "repository": {
11
- "type": "git",
12
- "url": "git+https://github.com/Synkestra/package-authyon.git",
13
- "directory": "packages/auth"
14
- },
15
- "homepage": "https://authyon.com/docs",
16
- "bugs": {
17
- "url": "https://github.com/Synkestra/package-authyon/issues"
18
- },
19
- "main": "./dist/index.cjs",
20
- "module": "./dist/index.js",
21
- "types": "./dist/index.d.ts",
22
- "exports": {
23
- ".": {
24
- "types": "./dist/index.d.ts",
25
- "import": "./dist/index.js",
26
- "require": "./dist/index.cjs",
27
- "default": "./dist/index.js"
28
- },
29
- "./react": {
30
- "types": "./dist/react/index.d.ts",
31
- "import": "./dist/react/index.js",
32
- "require": "./dist/react/index.cjs",
33
- "default": "./dist/react/index.js"
34
- }
35
- },
36
- "files": [
37
- "dist"
38
- ],
39
- "sideEffects": false,
40
- "publishConfig": {
41
- "access": "public",
42
- "provenance": true
43
- },
44
- "scripts": {
45
- "build": "tsup src/index.ts src/react/index.tsx --format esm,cjs --dts --clean",
46
- "typecheck": "tsc --noEmit",
47
- "prepack": "npm run build"
48
- },
49
- "keywords": [
50
- "authyon",
51
- "auth",
52
- "authentication",
53
- "jwt",
54
- "2fa",
55
- "multi-tenant"
56
- ],
57
- "devDependencies": {
58
- "@types/react": "^19.2.18",
59
- "react": "^19.2.8",
60
- "tsup": "^8.0.0",
61
- "typescript": "^5.5.0"
62
- },
63
- "peerDependencies": {
64
- "react": ">=18.2.0"
65
- },
66
- "peerDependenciesMeta": {
67
- "react": {
68
- "optional": true
69
- }
70
- }
71
- }
1
+ {
2
+ "name": "@authyon/auth",
3
+ "version": "0.2.0-beta.22",
4
+ "description": "Authyon SDK for browsers — auth, sessions, multi-tenant and 2FA for vanilla JS/TS.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "engines": {
8
+ "node": ">=18"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/Synkestra/package-authyon.git",
13
+ "directory": "packages/auth"
14
+ },
15
+ "homepage": "https://authyon.com/docs",
16
+ "bugs": {
17
+ "url": "https://github.com/Synkestra/package-authyon/issues"
18
+ },
19
+ "main": "./dist/index.cjs",
20
+ "module": "./dist/index.js",
21
+ "types": "./dist/index.d.ts",
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/index.d.ts",
25
+ "import": "./dist/index.js",
26
+ "require": "./dist/index.cjs",
27
+ "default": "./dist/index.js"
28
+ },
29
+ "./react": {
30
+ "types": "./dist/react/index.d.ts",
31
+ "import": "./dist/react/index.js",
32
+ "require": "./dist/react/index.cjs",
33
+ "default": "./dist/react/index.js"
34
+ }
35
+ },
36
+ "files": [
37
+ "dist"
38
+ ],
39
+ "sideEffects": false,
40
+ "publishConfig": {
41
+ "access": "public",
42
+ "provenance": true
43
+ },
44
+ "scripts": {
45
+ "build": "tsup src/index.ts src/react/index.tsx --format esm,cjs --dts --clean",
46
+ "typecheck": "tsc --noEmit",
47
+ "prepack": "npm run build"
48
+ },
49
+ "keywords": [
50
+ "authyon",
51
+ "auth",
52
+ "authentication",
53
+ "jwt",
54
+ "2fa",
55
+ "multi-tenant"
56
+ ],
57
+ "devDependencies": {
58
+ "@types/react": "^19.2.18",
59
+ "react": "^19.2.8",
60
+ "tsup": "^8.0.0",
61
+ "typescript": "^5.5.0"
62
+ },
63
+ "peerDependencies": {
64
+ "react": ">=18.2.0"
65
+ },
66
+ "peerDependenciesMeta": {
67
+ "react": {
68
+ "optional": true
69
+ }
70
+ }
71
+ }