@authyon/server 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.
@@ -1,7 +1,9 @@
1
1
  // ../../internal/core/http/clientIp.ts
2
+ var CLIENT_IP_HEADER = "X-Authyon-Client-Ip";
2
3
  function clientIpHeaders(context) {
3
4
  if (!context?.clientIp) return {};
4
- return { "X-Forwarded-For": normalizeClientIp(context.clientIp) };
5
+ const ip = normalizeClientIp(context.clientIp);
6
+ return { "X-Forwarded-For": ip, [CLIENT_IP_HEADER]: ip };
5
7
  }
6
8
  function normalizeClientIp(value) {
7
9
  let ip = value.trim();
@@ -222,12 +224,29 @@ function createAuthyonAbility(source = {}, options = {}) {
222
224
  return new AuthyonAbility(createAuthyonRules(source, options), options.detectSubjectType);
223
225
  }
224
226
  function hasPermission(source, requiredPermission) {
225
- const requirement = permissionToRule(requiredPermission);
226
- if (!requirement || typeof requirement.action !== "string" || typeof requirement.subject !== "string") {
227
- return false;
228
- }
227
+ const required = requiredPermission?.trim() ?? "";
228
+ if (!isWellFormedPermission(required)) return false;
229
229
  const permissionSource = isPermissionList(source) ? { permissions: source } : source;
230
- return createAuthyonAbility(permissionSource).can(requirement.action, requirement.subject);
230
+ const granted = [
231
+ ...permissionSource.permissions ?? [],
232
+ ...permissionSource.scope?.split(/\s+/).filter(Boolean) ?? []
233
+ ];
234
+ return granted.some((permission) => grants(permission.trim(), required));
235
+ }
236
+ function grants(granted, required) {
237
+ if (!isWellFormedPermission(granted)) return false;
238
+ if (isFullWildcard(granted)) return true;
239
+ const grantedSegments = granted.split(":");
240
+ const requiredSegments = required.split(":");
241
+ if (grantedSegments.length !== requiredSegments.length) return false;
242
+ return grantedSegments.every(
243
+ (segment, index) => segment === "*" || segment === requiredSegments[index]
244
+ );
245
+ }
246
+ function isWellFormedPermission(permission) {
247
+ if (!permission) return false;
248
+ const segments = permission.split(":");
249
+ return segments.length >= 2 && segments.every((segment) => segment.length > 0);
231
250
  }
232
251
  function hasPermissionGroup(source, group) {
233
252
  const hasAll = (group.allOf ?? []).every((permission) => hasPermission(source, permission));
@@ -242,13 +261,23 @@ function createAuthyonRules(source = {}, options = {}) {
242
261
  ...source.permissions ?? [],
243
262
  ...source.scope?.split(/\s+/).filter(Boolean) ?? []
244
263
  ]);
245
- const rules = [...permissions].map(permissionToRule).filter(isAbilityRule);
264
+ const rules = [...permissions].map(grantToRule).filter(isAbilityRule);
246
265
  for (const role of /* @__PURE__ */ new Set([...source.roles ?? [], ...options.roles ?? []])) {
247
266
  rules.push(...(options.roleRules?.[role] ?? []).map(cloneRule));
248
267
  }
249
268
  rules.push(...(options.rules ?? []).map(cloneRule));
250
269
  return rules;
251
270
  }
271
+ function grantToRule(permission) {
272
+ const normalized = permission.trim();
273
+ if (normalized && isFullWildcard(normalized)) {
274
+ return { action: "manage", subject: "all" };
275
+ }
276
+ return permissionToRule(normalized);
277
+ }
278
+ function isFullWildcard(permission) {
279
+ return permission.split(":").every((segment) => segment === "*");
280
+ }
252
281
  function permissionToRule(permission) {
253
282
  const normalized = permission.trim();
254
283
  if (!normalized) return null;
@@ -467,6 +496,7 @@ function unauthenticated() {
467
496
  }
468
497
 
469
498
  export {
499
+ CLIENT_IP_HEADER,
470
500
  clientIpHeaders,
471
501
  normalizeClientIp,
472
502
  TokenVerificationError,
package/dist/index.cjs CHANGED
@@ -33,13 +33,16 @@ __export(index_exports, {
33
33
  AuthyonError: () => AuthyonError,
34
34
  AuthyonServerClient: () => AuthyonServerClient,
35
35
  AuthyonServerClientBuilder: () => AuthyonServerClientBuilder,
36
+ CLIENT_IP_HEADER: () => CLIENT_IP_HEADER,
36
37
  ErrorCodes: () => ErrorCodes,
37
38
  FetchHttpAdapter: () => FetchHttpAdapter,
38
39
  JwksTokenVerifier: () => JwksTokenVerifier,
39
40
  LoggingHttpAdapter: () => LoggingHttpAdapter,
41
+ SubtenantPermissions: () => SubtenantPermissions,
40
42
  TENANT_FILTER_MAX_DEPTH: () => TENANT_FILTER_MAX_DEPTH,
41
43
  TENANT_FILTER_MAX_VALUES: () => TENANT_FILTER_MAX_VALUES,
42
44
  TENANT_FILTER_MAX_VALUE_LENGTH: () => TENANT_FILTER_MAX_VALUE_LENGTH,
45
+ TenantPermissions: () => TenantPermissions,
43
46
  TenantScopedClient: () => TenantScopedClient,
44
47
  TokenVerificationError: () => TokenVerificationError,
45
48
  UserScopedClient: () => UserScopedClient,
@@ -645,9 +648,11 @@ var JsonHttpClient = class {
645
648
  };
646
649
 
647
650
  // ../../internal/core/http/clientIp.ts
651
+ var CLIENT_IP_HEADER = "X-Authyon-Client-Ip";
648
652
  function clientIpHeaders(context) {
649
653
  if (!context?.clientIp) return {};
650
- return { "X-Forwarded-For": normalizeClientIp(context.clientIp) };
654
+ const ip = normalizeClientIp(context.clientIp);
655
+ return { "X-Forwarded-For": ip, [CLIENT_IP_HEADER]: ip };
651
656
  }
652
657
  function normalizeClientIp(value) {
653
658
  let ip = value.trim();
@@ -802,6 +807,11 @@ function isRecord(value) {
802
807
  }
803
808
 
804
809
  // src/client/authyonServerClient.ts
810
+ var ROOT_PARENT_ID = "00000000-0000-0000-0000-000000000000";
811
+ function tenantListQuery({ parentId, ...rest }) {
812
+ if (parentId === void 0) return rest;
813
+ return { ...rest, parentId: parentId === null ? ROOT_PARENT_ID : parentId };
814
+ }
805
815
  var _clientSecret;
806
816
  var AuthyonServerClient = class {
807
817
  constructor(options = {}) {
@@ -1044,15 +1054,47 @@ var AuthyonServerClient = class {
1044
1054
  * });
1045
1055
  * ```
1046
1056
  *
1057
+ * `parentId` narrows the page to one level of the tree — `children()`
1058
+ * and `roots()` below are the same call with that one option set.
1059
+ *
1047
1060
  * Against an Authyon API older than 2026-10-21 this route ignored
1048
1061
  * skip/take/search and answered with a bare array; one older than
1049
- * 2026-10-22 ignores `filter`, which reads as a page that is too wide
1050
- * rather than an error.
1062
+ * 2026-10-22 ignores `filter` and one older than 2026-10-31 ignores
1063
+ * `parentId`, which read as a page that is too wide rather than an error.
1064
+ */
1065
+ list: (params = {}) => this.request("/env/tenants", { envBearer: true, query: tenantListQuery(params) }),
1066
+ /**
1067
+ * One page of a tenant's direct sub-tenants — `list()` with `parentId`.
1068
+ *
1069
+ * Only one level: a node's own children carry `childCount`, so a tree
1070
+ * view asks again where that is above zero instead of reading the whole
1071
+ * subtree to draw the first level of it.
1072
+ */
1073
+ children: (parentTenantId, params = {}) => this.environment.tenants.list({ ...params, parentId: parentTenantId }),
1074
+ /**
1075
+ * One page of the environment's root tenants — `list()` with no parent.
1076
+ *
1077
+ * This is the first level of the tree, and the difference from `list()`
1078
+ * is worth keeping straight: `list()` returns every tenant at every
1079
+ * depth, which is what a flat picker wants and what a tree view must not
1080
+ * start from.
1081
+ */
1082
+ roots: (params = {}) => this.environment.tenants.list({ ...params, parentId: null }),
1083
+ /**
1084
+ * POST /env/tenants — create a tenant in the environment.
1085
+ *
1086
+ * `parentTenantId` nests it under an existing tenant. The environment's
1087
+ * `maxTenantDepth` has to allow the level — it ships as `1`, roots only,
1088
+ * so this answers `tenant.depth.not_allowed` until an operator raises it
1089
+ * in the console.
1051
1090
  */
1052
- list: (params = {}) => this.request("/env/tenants", { envBearer: true, query: params }),
1053
- /** POST /env/tenants — create a tenant in the environment. */
1054
1091
  create: (input) => this.request("/env/tenants", { method: "POST", envBearer: true, body: input }),
1055
- /** GET /env/tenants/{tenantId} — fetch a tenant by id. */
1092
+ /**
1093
+ * GET /env/tenants/{tenantId} — fetch a tenant by id.
1094
+ *
1095
+ * Returns `ancestors` as well, the chain above it root first, so a
1096
+ * breadcrumb costs this one call rather than one per level.
1097
+ */
1056
1098
  get: (tenantId) => this.request(`/env/tenants/${encodeURIComponent(tenantId)}`, { envBearer: true }),
1057
1099
  /** PUT /env/tenants/{tenantId} — update a tenant. */
1058
1100
  update: (tenantId, input) => this.request(`/env/tenants/${encodeURIComponent(tenantId)}`, {
@@ -1066,12 +1108,23 @@ var AuthyonServerClient = class {
1066
1108
  envBearer: true,
1067
1109
  body: input
1068
1110
  }),
1069
- /** POST /env/tenants/{tenantId}/disable — disable without removing tenant data. */
1111
+ /**
1112
+ * POST /env/tenants/{tenantId}/disable — disable without removing tenant
1113
+ * data.
1114
+ *
1115
+ * Disables the branch below it too. A sub-tenant left live under a
1116
+ * disabled parent would go on authenticating its users, which is the one
1117
+ * thing switching a tenant off is there to stop — so check `childCount`
1118
+ * before calling this on a node with children.
1119
+ */
1070
1120
  disable: (tenantId) => this.request(`/env/tenants/${encodeURIComponent(tenantId)}/disable`, {
1071
1121
  method: "POST",
1072
1122
  envBearer: true
1073
1123
  }),
1074
- /** DELETE /env/tenants/{tenantId} — logically delete (disable) a tenant. */
1124
+ /**
1125
+ * DELETE /env/tenants/{tenantId} — logically delete (disable) a tenant,
1126
+ * and the branch below it. Same handler as `disable()`.
1127
+ */
1075
1128
  delete: (tenantId) => this.request(`/env/tenants/${encodeURIComponent(tenantId)}`, {
1076
1129
  method: "DELETE",
1077
1130
  envBearer: true
@@ -1712,12 +1765,29 @@ function createAuthyonAbility(source = {}, options = {}) {
1712
1765
  return new AuthyonAbility(createAuthyonRules(source, options), options.detectSubjectType);
1713
1766
  }
1714
1767
  function hasPermission(source, requiredPermission) {
1715
- const requirement = permissionToRule(requiredPermission);
1716
- if (!requirement || typeof requirement.action !== "string" || typeof requirement.subject !== "string") {
1717
- return false;
1718
- }
1768
+ const required = requiredPermission?.trim() ?? "";
1769
+ if (!isWellFormedPermission(required)) return false;
1719
1770
  const permissionSource = isPermissionList(source) ? { permissions: source } : source;
1720
- return createAuthyonAbility(permissionSource).can(requirement.action, requirement.subject);
1771
+ const granted = [
1772
+ ...permissionSource.permissions ?? [],
1773
+ ...permissionSource.scope?.split(/\s+/).filter(Boolean) ?? []
1774
+ ];
1775
+ return granted.some((permission) => grants(permission.trim(), required));
1776
+ }
1777
+ function grants(granted, required) {
1778
+ if (!isWellFormedPermission(granted)) return false;
1779
+ if (isFullWildcard(granted)) return true;
1780
+ const grantedSegments = granted.split(":");
1781
+ const requiredSegments = required.split(":");
1782
+ if (grantedSegments.length !== requiredSegments.length) return false;
1783
+ return grantedSegments.every(
1784
+ (segment2, index) => segment2 === "*" || segment2 === requiredSegments[index]
1785
+ );
1786
+ }
1787
+ function isWellFormedPermission(permission) {
1788
+ if (!permission) return false;
1789
+ const segments = permission.split(":");
1790
+ return segments.length >= 2 && segments.every((segment2) => segment2.length > 0);
1721
1791
  }
1722
1792
  function hasPermissionGroup(source, group) {
1723
1793
  const hasAll = (group.allOf ?? []).every((permission) => hasPermission(source, permission));
@@ -1732,13 +1802,23 @@ function createAuthyonRules(source = {}, options = {}) {
1732
1802
  ...source.permissions ?? [],
1733
1803
  ...source.scope?.split(/\s+/).filter(Boolean) ?? []
1734
1804
  ]);
1735
- const rules = [...permissions].map(permissionToRule).filter(isAbilityRule);
1805
+ const rules = [...permissions].map(grantToRule).filter(isAbilityRule);
1736
1806
  for (const role of /* @__PURE__ */ new Set([...source.roles ?? [], ...options.roles ?? []])) {
1737
1807
  rules.push(...(options.roleRules?.[role] ?? []).map(cloneRule));
1738
1808
  }
1739
1809
  rules.push(...(options.rules ?? []).map(cloneRule));
1740
1810
  return rules;
1741
1811
  }
1812
+ function grantToRule(permission) {
1813
+ const normalized = permission.trim();
1814
+ if (normalized && isFullWildcard(normalized)) {
1815
+ return { action: "manage", subject: "all" };
1816
+ }
1817
+ return permissionToRule(normalized);
1818
+ }
1819
+ function isFullWildcard(permission) {
1820
+ return permission.split(":").every((segment2) => segment2 === "*");
1821
+ }
1742
1822
  function permissionToRule(permission) {
1743
1823
  const normalized = permission.trim();
1744
1824
  if (!normalized) return null;
@@ -1955,6 +2035,32 @@ function unauthenticated() {
1955
2035
  "A valid bearer token is required"
1956
2036
  );
1957
2037
  }
2038
+
2039
+ // ../../internal/core/authorization/tenantPermissions.ts
2040
+ var TenantPermissions = {
2041
+ /** Rename the tenant and change its settings. */
2042
+ SettingsManage: "tenants:settings:manage",
2043
+ /** Invite an address to the tenant. */
2044
+ MembersInvite: "tenants:members:invite",
2045
+ /** Remove a member from the tenant. */
2046
+ MembersRemove: "tenants:members:remove"
2047
+ };
2048
+ var SubtenantPermissions = {
2049
+ /** Read any tenant below one you belong to. */
2050
+ TreeRead: "subtenants:tree:read",
2051
+ /** Rename a tenant below yours, and add or remove its members. */
2052
+ TreeManage: "subtenants:tree:manage",
2053
+ /** Create a tenant underneath one you belong to. */
2054
+ TreeCreate: "subtenants:tree:create",
2055
+ /**
2056
+ * Make a tenant below yours the ACTIVE tenant of the session — acting inside
2057
+ * it rather than on it from outside. Deliberately not the same permission as
2058
+ * {@link SubtenantPermissions.TreeManage}: administering a sub-tenant through
2059
+ * the management endpoints and operating as that sub-tenant are different
2060
+ * powers, and granting the first should not hand over the second.
2061
+ */
2062
+ TreeSwitch: "subtenants:tree:switch"
2063
+ };
1958
2064
  // Annotate the CommonJS export names for ESM import in node:
1959
2065
  0 && (module.exports = {
1960
2066
  AuthorizationError,
@@ -1963,13 +2069,16 @@ function unauthenticated() {
1963
2069
  AuthyonError,
1964
2070
  AuthyonServerClient,
1965
2071
  AuthyonServerClientBuilder,
2072
+ CLIENT_IP_HEADER,
1966
2073
  ErrorCodes,
1967
2074
  FetchHttpAdapter,
1968
2075
  JwksTokenVerifier,
1969
2076
  LoggingHttpAdapter,
2077
+ SubtenantPermissions,
1970
2078
  TENANT_FILTER_MAX_DEPTH,
1971
2079
  TENANT_FILTER_MAX_VALUES,
1972
2080
  TENANT_FILTER_MAX_VALUE_LENGTH,
2081
+ TenantPermissions,
1973
2082
  TenantScopedClient,
1974
2083
  TokenVerificationError,
1975
2084
  UserScopedClient,
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
- import { H as HttpAdapter, a as HttpLoggerOptions, A as AuthyonServerClient } from './authyonServerClient-COrfN4o_.cjs';
2
- export { b as AcceptTenantInviteInput, c as AccessTokenSource, d as Approval, e as ApprovalAssurance, f as ApprovalPayload, g as ApprovalStatus, h as ApprovalWebAuthnOptions, i as AuditEvent, j as AuthyonJwksDiscoveryOptions, k as AuthyonJwtClaims, l as AuthyonServerClientOptions, C as ClientCredentials, m as ClientRequestContext, n as ConfirmApprovalInput, o as ConsumedApproval, p as CreateApprovalInput, q as CreateCredentialInput, r as CreateOrganizationInput, s as CreatePermissionInput, t as CreateTenantCredentialInput, u as CreateUserInput, v as CredentialCreator, w as CredentialDetail, x as CredentialListOptions, y as CredentialPermissionsInput, E as EnvironmentUser, F as FetchHttpAdapter, z as HttpAdapterRequest, B as HttpLogEvent, D as HttpLogger, I as IntrospectResult, G as InviteTenantMemberInput, J as IssuePasswordResetInput, K as JsonWebKeySet, L as JwksTokenVerifier, M as JwksTokenVerifierOptions, N as LoggingHttpAdapter, O as LoginActivity, P as OpenIdConfiguration, Q as Organization, R as OtpCheckResult, S as Paged, T as PaginationOptions, U as PasswordResetCompleted, V as PasswordResetIssued, W as PasswordResetPreview, X as Permission, Y as PermissionsByRole, Z as ReservedPermissions, _ as Role, $ as RoleGrant, a0 as RoleInput, a1 as RoleList, a2 as RolePermissions, a3 as SafeJwtAlgorithm, a4 as SecurityMethods, a5 as SubjectApprovals, a6 as SubjectSecurity, a7 as TENANT_FILTER_MAX_DEPTH, a8 as TENANT_FILTER_MAX_VALUES, a9 as TENANT_FILTER_MAX_VALUE_LENGTH, aa as TenantClientValidationResult, ab as TenantCredentialIssued, ac as TenantCredentialSummary, ad as TenantFilterExpression, ae as TenantFilterValue, af as TenantInvite, ag as TenantInviteAccepted, ah as TenantInviteInput, ai as TenantInviteIssued, aj as TenantInvitePreview, ak as TenantInviteStatus, al as TenantListOptions, am as TenantMember, an as TenantMemberPage, ao as TenantMembership, ap as TenantScopedClient, aq as TokenResult, ar as TokenVerificationError, as as TokenVerificationErrorCode, at as UpdateOrganizationInput, au as User, av as UserScopedClient, aw as ValidateResult, ax as VerifiedAccessToken, ay as createClient, az as normalizeClientIp, aA as tenantFilter } from './authyonServerClient-COrfN4o_.cjs';
3
- export { b as AbilityConditions, c as AbilityEvent, d as AbilityListener, e as AbilityRequirement, f as AbilityRule, A as AbilitySubject, g as AuthorizationContext, a as AuthorizationError, h as AuthorizationOptions, i as AuthyonAbility, j as AuthyonAbilityBuilder, k as AuthyonAbilityOptions, l as AuthyonPermissionSource, E as ExpressAuthorizationMiddlewareOptions, m as ExpressNext, n as ExpressRequestLike, o as ExpressResponseLike, P as PermissionGroup, R as RequestAuthorizationOptions, p as authorizeRequest, q as authorizeToken, r as createAuthorizationErrorResponse, s as createAuthyonAbility, t as createAuthyonRules, u as createExpressAuthorizationMiddleware, v as hasPermission, w as hasPermissionGroup } from './authorization-Soblu-A5.cjs';
1
+ import { H as HttpAdapter, a as HttpLoggerOptions, A as AuthyonServerClient } from './authyonServerClient-CW46xIRD.cjs';
2
+ export { b as AcceptTenantInviteInput, c as AccessTokenSource, d as Approval, e as ApprovalAssurance, f as ApprovalPayload, g as ApprovalStatus, h as ApprovalWebAuthnOptions, i as AuditEvent, j as AuthyonJwksDiscoveryOptions, k as AuthyonJwtClaims, l as AuthyonServerClientOptions, m as AuthyonTenantClaim, C as CLIENT_IP_HEADER, n as ClientCredentials, o as ClientRequestContext, p as ConfirmApprovalInput, q as ConsumedApproval, r as CreateApprovalInput, s as CreateCredentialInput, t as CreateOrganizationInput, u as CreatePermissionInput, v as CreateTenantCredentialInput, w as CreateUserInput, x as CredentialCreator, y as CredentialDetail, z as CredentialListOptions, B as CredentialPermissionsInput, E as EnvironmentUser, F as FetchHttpAdapter, D as HttpAdapterRequest, G as HttpLogEvent, I as HttpLogger, K as IntrospectResult, L as InviteTenantMemberInput, M as IssuePasswordResetInput, N as JsonWebKeySet, J as JwksTokenVerifier, O as JwksTokenVerifierOptions, P as LoggingHttpAdapter, Q as LoginActivity, R as OpenIdConfiguration, S as Organization, T as OtpCheckResult, U as Paged, V as PaginationOptions, W as PasswordResetCompleted, X as PasswordResetIssued, Y as PasswordResetPreview, Z as Permission, _ as PermissionsByRole, $ as ReservedPermissions, a0 as Role, a1 as RoleGrant, a2 as RoleInput, a3 as RoleList, a4 as RolePermissions, a5 as SafeJwtAlgorithm, a6 as SecurityMethods, a7 as SubjectApprovals, a8 as SubjectSecurity, a9 as TENANT_FILTER_MAX_DEPTH, aa as TENANT_FILTER_MAX_VALUES, ab as TENANT_FILTER_MAX_VALUE_LENGTH, ac as TenantAncestor, ad as TenantClientValidationResult, ae as TenantCredentialIssued, af as TenantCredentialSummary, ag as TenantFilterExpression, ah as TenantFilterValue, ai as TenantInvite, aj as TenantInviteAccepted, ak as TenantInviteInput, al as TenantInviteIssued, am as TenantInvitePreview, an as TenantInviteStatus, ao as TenantListOptions, ap as TenantMember, aq as TenantMemberPage, ar as TenantMembership, as as TenantScopedClient, at as TokenResult, au as TokenVerificationError, av as TokenVerificationErrorCode, aw as UpdateOrganizationInput, ax as User, ay as UserScopedClient, az as ValidateResult, aA as VerifiedAccessToken, aB as createClient, aC as normalizeClientIp, aD as tenantFilter } from './authyonServerClient-CW46xIRD.cjs';
3
+ export { b as AbilityConditions, c as AbilityEvent, d as AbilityListener, e as AbilityRequirement, f as AbilityRule, A as AbilitySubject, g as AuthorizationContext, a as AuthorizationError, h as AuthorizationOptions, i as AuthyonAbility, j as AuthyonAbilityBuilder, k as AuthyonAbilityOptions, l as AuthyonPermissionSource, E as ExpressAuthorizationMiddlewareOptions, m as ExpressNext, n as ExpressRequestLike, o as ExpressResponseLike, P as PermissionGroup, R as RequestAuthorizationOptions, p as authorizeRequest, q as authorizeToken, r as createAuthorizationErrorResponse, s as createAuthyonAbility, t as createAuthyonRules, u as createExpressAuthorizationMiddleware, v as hasPermission, w as hasPermissionGroup } from './authorization-BLENxur8.cjs';
4
4
  import 'jose';
5
5
 
6
6
  declare const ErrorCodes: {
@@ -94,4 +94,54 @@ declare class AuthyonServerClientBuilder {
94
94
  build(): AuthyonServerClient;
95
95
  }
96
96
 
97
- export { AuthyonError, type AuthyonErrorAction, type AuthyonErrorBody, type AuthyonErrorCategory, type AuthyonErrorInterpretation, AuthyonServerClient, AuthyonServerClientBuilder, ErrorCodes, HttpAdapter, HttpLoggerOptions, type KnownErrorCode };
97
+ /**
98
+ * The permission values the tenant surface checks, as constants.
99
+ *
100
+ * They are strings on the wire and nothing forces you through here — the point
101
+ * is that a rename shows up as a type error instead of as a 403 nobody can
102
+ * explain. `tenants:settings:manage` is exactly that case: it used to be
103
+ * written `tenants:manage`, a two-segment value the API could not even store,
104
+ * so the endpoint requiring it refused every caller.
105
+ */
106
+ declare const TenantPermissions: {
107
+ /** Rename the tenant and change its settings. */
108
+ readonly SettingsManage: "tenants:settings:manage";
109
+ /** Invite an address to the tenant. */
110
+ readonly MembersInvite: "tenants:members:invite";
111
+ /** Remove a member from the tenant. */
112
+ readonly MembersRemove: "tenants:members:remove";
113
+ };
114
+ /**
115
+ * What lets a member of one tenant reach the tenants beneath it.
116
+ *
117
+ * A separate domain, not a suffix on `tenants:…`, and the reason is worth
118
+ * knowing before you grant one: permissions match per segment, so naming these
119
+ * `tenants:tree:manage` would have handed every role already holding
120
+ * `tenants:*:*` power over the whole subtree the day the hierarchy shipped — a
121
+ * widening that appears in no diff. `subtenants:…` is only covered by `*:*:*`,
122
+ * which is explicit superuser.
123
+ *
124
+ * Reaching down is opt-in at every level: holding `tenants:settings:manage` on
125
+ * a parent grants nothing below it, and each of these is granted at the node
126
+ * the caller belongs to, never at the target.
127
+ */
128
+ declare const SubtenantPermissions: {
129
+ /** Read any tenant below one you belong to. */
130
+ readonly TreeRead: "subtenants:tree:read";
131
+ /** Rename a tenant below yours, and add or remove its members. */
132
+ readonly TreeManage: "subtenants:tree:manage";
133
+ /** Create a tenant underneath one you belong to. */
134
+ readonly TreeCreate: "subtenants:tree:create";
135
+ /**
136
+ * Make a tenant below yours the ACTIVE tenant of the session — acting inside
137
+ * it rather than on it from outside. Deliberately not the same permission as
138
+ * {@link SubtenantPermissions.TreeManage}: administering a sub-tenant through
139
+ * the management endpoints and operating as that sub-tenant are different
140
+ * powers, and granting the first should not hand over the second.
141
+ */
142
+ readonly TreeSwitch: "subtenants:tree:switch";
143
+ };
144
+ type TenantPermission = (typeof TenantPermissions)[keyof typeof TenantPermissions];
145
+ type SubtenantPermission = (typeof SubtenantPermissions)[keyof typeof SubtenantPermissions];
146
+
147
+ export { AuthyonError, type AuthyonErrorAction, type AuthyonErrorBody, type AuthyonErrorCategory, type AuthyonErrorInterpretation, AuthyonServerClient, AuthyonServerClientBuilder, ErrorCodes, HttpAdapter, HttpLoggerOptions, type KnownErrorCode, type SubtenantPermission, SubtenantPermissions, type TenantPermission, TenantPermissions };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { H as HttpAdapter, a as HttpLoggerOptions, A as AuthyonServerClient } from './authyonServerClient-COrfN4o_.js';
2
- export { b as AcceptTenantInviteInput, c as AccessTokenSource, d as Approval, e as ApprovalAssurance, f as ApprovalPayload, g as ApprovalStatus, h as ApprovalWebAuthnOptions, i as AuditEvent, j as AuthyonJwksDiscoveryOptions, k as AuthyonJwtClaims, l as AuthyonServerClientOptions, C as ClientCredentials, m as ClientRequestContext, n as ConfirmApprovalInput, o as ConsumedApproval, p as CreateApprovalInput, q as CreateCredentialInput, r as CreateOrganizationInput, s as CreatePermissionInput, t as CreateTenantCredentialInput, u as CreateUserInput, v as CredentialCreator, w as CredentialDetail, x as CredentialListOptions, y as CredentialPermissionsInput, E as EnvironmentUser, F as FetchHttpAdapter, z as HttpAdapterRequest, B as HttpLogEvent, D as HttpLogger, I as IntrospectResult, G as InviteTenantMemberInput, J as IssuePasswordResetInput, K as JsonWebKeySet, L as JwksTokenVerifier, M as JwksTokenVerifierOptions, N as LoggingHttpAdapter, O as LoginActivity, P as OpenIdConfiguration, Q as Organization, R as OtpCheckResult, S as Paged, T as PaginationOptions, U as PasswordResetCompleted, V as PasswordResetIssued, W as PasswordResetPreview, X as Permission, Y as PermissionsByRole, Z as ReservedPermissions, _ as Role, $ as RoleGrant, a0 as RoleInput, a1 as RoleList, a2 as RolePermissions, a3 as SafeJwtAlgorithm, a4 as SecurityMethods, a5 as SubjectApprovals, a6 as SubjectSecurity, a7 as TENANT_FILTER_MAX_DEPTH, a8 as TENANT_FILTER_MAX_VALUES, a9 as TENANT_FILTER_MAX_VALUE_LENGTH, aa as TenantClientValidationResult, ab as TenantCredentialIssued, ac as TenantCredentialSummary, ad as TenantFilterExpression, ae as TenantFilterValue, af as TenantInvite, ag as TenantInviteAccepted, ah as TenantInviteInput, ai as TenantInviteIssued, aj as TenantInvitePreview, ak as TenantInviteStatus, al as TenantListOptions, am as TenantMember, an as TenantMemberPage, ao as TenantMembership, ap as TenantScopedClient, aq as TokenResult, ar as TokenVerificationError, as as TokenVerificationErrorCode, at as UpdateOrganizationInput, au as User, av as UserScopedClient, aw as ValidateResult, ax as VerifiedAccessToken, ay as createClient, az as normalizeClientIp, aA as tenantFilter } from './authyonServerClient-COrfN4o_.js';
3
- export { b as AbilityConditions, c as AbilityEvent, d as AbilityListener, e as AbilityRequirement, f as AbilityRule, A as AbilitySubject, g as AuthorizationContext, a as AuthorizationError, h as AuthorizationOptions, i as AuthyonAbility, j as AuthyonAbilityBuilder, k as AuthyonAbilityOptions, l as AuthyonPermissionSource, E as ExpressAuthorizationMiddlewareOptions, m as ExpressNext, n as ExpressRequestLike, o as ExpressResponseLike, P as PermissionGroup, R as RequestAuthorizationOptions, p as authorizeRequest, q as authorizeToken, r as createAuthorizationErrorResponse, s as createAuthyonAbility, t as createAuthyonRules, u as createExpressAuthorizationMiddleware, v as hasPermission, w as hasPermissionGroup } from './authorization-D8tznR96.js';
1
+ import { H as HttpAdapter, a as HttpLoggerOptions, A as AuthyonServerClient } from './authyonServerClient-CW46xIRD.js';
2
+ export { b as AcceptTenantInviteInput, c as AccessTokenSource, d as Approval, e as ApprovalAssurance, f as ApprovalPayload, g as ApprovalStatus, h as ApprovalWebAuthnOptions, i as AuditEvent, j as AuthyonJwksDiscoveryOptions, k as AuthyonJwtClaims, l as AuthyonServerClientOptions, m as AuthyonTenantClaim, C as CLIENT_IP_HEADER, n as ClientCredentials, o as ClientRequestContext, p as ConfirmApprovalInput, q as ConsumedApproval, r as CreateApprovalInput, s as CreateCredentialInput, t as CreateOrganizationInput, u as CreatePermissionInput, v as CreateTenantCredentialInput, w as CreateUserInput, x as CredentialCreator, y as CredentialDetail, z as CredentialListOptions, B as CredentialPermissionsInput, E as EnvironmentUser, F as FetchHttpAdapter, D as HttpAdapterRequest, G as HttpLogEvent, I as HttpLogger, K as IntrospectResult, L as InviteTenantMemberInput, M as IssuePasswordResetInput, N as JsonWebKeySet, J as JwksTokenVerifier, O as JwksTokenVerifierOptions, P as LoggingHttpAdapter, Q as LoginActivity, R as OpenIdConfiguration, S as Organization, T as OtpCheckResult, U as Paged, V as PaginationOptions, W as PasswordResetCompleted, X as PasswordResetIssued, Y as PasswordResetPreview, Z as Permission, _ as PermissionsByRole, $ as ReservedPermissions, a0 as Role, a1 as RoleGrant, a2 as RoleInput, a3 as RoleList, a4 as RolePermissions, a5 as SafeJwtAlgorithm, a6 as SecurityMethods, a7 as SubjectApprovals, a8 as SubjectSecurity, a9 as TENANT_FILTER_MAX_DEPTH, aa as TENANT_FILTER_MAX_VALUES, ab as TENANT_FILTER_MAX_VALUE_LENGTH, ac as TenantAncestor, ad as TenantClientValidationResult, ae as TenantCredentialIssued, af as TenantCredentialSummary, ag as TenantFilterExpression, ah as TenantFilterValue, ai as TenantInvite, aj as TenantInviteAccepted, ak as TenantInviteInput, al as TenantInviteIssued, am as TenantInvitePreview, an as TenantInviteStatus, ao as TenantListOptions, ap as TenantMember, aq as TenantMemberPage, ar as TenantMembership, as as TenantScopedClient, at as TokenResult, au as TokenVerificationError, av as TokenVerificationErrorCode, aw as UpdateOrganizationInput, ax as User, ay as UserScopedClient, az as ValidateResult, aA as VerifiedAccessToken, aB as createClient, aC as normalizeClientIp, aD as tenantFilter } from './authyonServerClient-CW46xIRD.js';
3
+ export { b as AbilityConditions, c as AbilityEvent, d as AbilityListener, e as AbilityRequirement, f as AbilityRule, A as AbilitySubject, g as AuthorizationContext, a as AuthorizationError, h as AuthorizationOptions, i as AuthyonAbility, j as AuthyonAbilityBuilder, k as AuthyonAbilityOptions, l as AuthyonPermissionSource, E as ExpressAuthorizationMiddlewareOptions, m as ExpressNext, n as ExpressRequestLike, o as ExpressResponseLike, P as PermissionGroup, R as RequestAuthorizationOptions, p as authorizeRequest, q as authorizeToken, r as createAuthorizationErrorResponse, s as createAuthyonAbility, t as createAuthyonRules, u as createExpressAuthorizationMiddleware, v as hasPermission, w as hasPermissionGroup } from './authorization-Clf0Jx-U.js';
4
4
  import 'jose';
5
5
 
6
6
  declare const ErrorCodes: {
@@ -94,4 +94,54 @@ declare class AuthyonServerClientBuilder {
94
94
  build(): AuthyonServerClient;
95
95
  }
96
96
 
97
- export { AuthyonError, type AuthyonErrorAction, type AuthyonErrorBody, type AuthyonErrorCategory, type AuthyonErrorInterpretation, AuthyonServerClient, AuthyonServerClientBuilder, ErrorCodes, HttpAdapter, HttpLoggerOptions, type KnownErrorCode };
97
+ /**
98
+ * The permission values the tenant surface checks, as constants.
99
+ *
100
+ * They are strings on the wire and nothing forces you through here — the point
101
+ * is that a rename shows up as a type error instead of as a 403 nobody can
102
+ * explain. `tenants:settings:manage` is exactly that case: it used to be
103
+ * written `tenants:manage`, a two-segment value the API could not even store,
104
+ * so the endpoint requiring it refused every caller.
105
+ */
106
+ declare const TenantPermissions: {
107
+ /** Rename the tenant and change its settings. */
108
+ readonly SettingsManage: "tenants:settings:manage";
109
+ /** Invite an address to the tenant. */
110
+ readonly MembersInvite: "tenants:members:invite";
111
+ /** Remove a member from the tenant. */
112
+ readonly MembersRemove: "tenants:members:remove";
113
+ };
114
+ /**
115
+ * What lets a member of one tenant reach the tenants beneath it.
116
+ *
117
+ * A separate domain, not a suffix on `tenants:…`, and the reason is worth
118
+ * knowing before you grant one: permissions match per segment, so naming these
119
+ * `tenants:tree:manage` would have handed every role already holding
120
+ * `tenants:*:*` power over the whole subtree the day the hierarchy shipped — a
121
+ * widening that appears in no diff. `subtenants:…` is only covered by `*:*:*`,
122
+ * which is explicit superuser.
123
+ *
124
+ * Reaching down is opt-in at every level: holding `tenants:settings:manage` on
125
+ * a parent grants nothing below it, and each of these is granted at the node
126
+ * the caller belongs to, never at the target.
127
+ */
128
+ declare const SubtenantPermissions: {
129
+ /** Read any tenant below one you belong to. */
130
+ readonly TreeRead: "subtenants:tree:read";
131
+ /** Rename a tenant below yours, and add or remove its members. */
132
+ readonly TreeManage: "subtenants:tree:manage";
133
+ /** Create a tenant underneath one you belong to. */
134
+ readonly TreeCreate: "subtenants:tree:create";
135
+ /**
136
+ * Make a tenant below yours the ACTIVE tenant of the session — acting inside
137
+ * it rather than on it from outside. Deliberately not the same permission as
138
+ * {@link SubtenantPermissions.TreeManage}: administering a sub-tenant through
139
+ * the management endpoints and operating as that sub-tenant are different
140
+ * powers, and granting the first should not hand over the second.
141
+ */
142
+ readonly TreeSwitch: "subtenants:tree:switch";
143
+ };
144
+ type TenantPermission = (typeof TenantPermissions)[keyof typeof TenantPermissions];
145
+ type SubtenantPermission = (typeof SubtenantPermissions)[keyof typeof SubtenantPermissions];
146
+
147
+ export { AuthyonError, type AuthyonErrorAction, type AuthyonErrorBody, type AuthyonErrorCategory, type AuthyonErrorInterpretation, AuthyonServerClient, AuthyonServerClientBuilder, ErrorCodes, HttpAdapter, HttpLoggerOptions, type KnownErrorCode, type SubtenantPermission, SubtenantPermissions, type TenantPermission, TenantPermissions };
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@ import {
12
12
  AuthorizationError,
13
13
  AuthyonAbility,
14
14
  AuthyonAbilityBuilder,
15
+ CLIENT_IP_HEADER,
15
16
  JwksTokenVerifier,
16
17
  TokenVerificationError,
17
18
  authorizeRequest,
@@ -24,7 +25,7 @@ import {
24
25
  hasPermission,
25
26
  hasPermissionGroup,
26
27
  normalizeClientIp
27
- } from "./chunk-NKWQMYB3.js";
28
+ } from "./chunk-WF26WYSX.js";
28
29
  import {
29
30
  __privateAdd,
30
31
  __privateGet,
@@ -268,6 +269,11 @@ _token = new WeakMap();
268
269
  _inFlight = new WeakMap();
269
270
 
270
271
  // src/client/authyonServerClient.ts
272
+ var ROOT_PARENT_ID = "00000000-0000-0000-0000-000000000000";
273
+ function tenantListQuery({ parentId, ...rest }) {
274
+ if (parentId === void 0) return rest;
275
+ return { ...rest, parentId: parentId === null ? ROOT_PARENT_ID : parentId };
276
+ }
271
277
  var _clientSecret;
272
278
  var AuthyonServerClient = class {
273
279
  constructor(options = {}) {
@@ -510,15 +516,47 @@ var AuthyonServerClient = class {
510
516
  * });
511
517
  * ```
512
518
  *
519
+ * `parentId` narrows the page to one level of the tree — `children()`
520
+ * and `roots()` below are the same call with that one option set.
521
+ *
513
522
  * Against an Authyon API older than 2026-10-21 this route ignored
514
523
  * skip/take/search and answered with a bare array; one older than
515
- * 2026-10-22 ignores `filter`, which reads as a page that is too wide
516
- * rather than an error.
524
+ * 2026-10-22 ignores `filter` and one older than 2026-10-31 ignores
525
+ * `parentId`, which read as a page that is too wide rather than an error.
526
+ */
527
+ list: (params = {}) => this.request("/env/tenants", { envBearer: true, query: tenantListQuery(params) }),
528
+ /**
529
+ * One page of a tenant's direct sub-tenants — `list()` with `parentId`.
530
+ *
531
+ * Only one level: a node's own children carry `childCount`, so a tree
532
+ * view asks again where that is above zero instead of reading the whole
533
+ * subtree to draw the first level of it.
534
+ */
535
+ children: (parentTenantId, params = {}) => this.environment.tenants.list({ ...params, parentId: parentTenantId }),
536
+ /**
537
+ * One page of the environment's root tenants — `list()` with no parent.
538
+ *
539
+ * This is the first level of the tree, and the difference from `list()`
540
+ * is worth keeping straight: `list()` returns every tenant at every
541
+ * depth, which is what a flat picker wants and what a tree view must not
542
+ * start from.
543
+ */
544
+ roots: (params = {}) => this.environment.tenants.list({ ...params, parentId: null }),
545
+ /**
546
+ * POST /env/tenants — create a tenant in the environment.
547
+ *
548
+ * `parentTenantId` nests it under an existing tenant. The environment's
549
+ * `maxTenantDepth` has to allow the level — it ships as `1`, roots only,
550
+ * so this answers `tenant.depth.not_allowed` until an operator raises it
551
+ * in the console.
517
552
  */
518
- list: (params = {}) => this.request("/env/tenants", { envBearer: true, query: params }),
519
- /** POST /env/tenants — create a tenant in the environment. */
520
553
  create: (input) => this.request("/env/tenants", { method: "POST", envBearer: true, body: input }),
521
- /** GET /env/tenants/{tenantId} — fetch a tenant by id. */
554
+ /**
555
+ * GET /env/tenants/{tenantId} — fetch a tenant by id.
556
+ *
557
+ * Returns `ancestors` as well, the chain above it root first, so a
558
+ * breadcrumb costs this one call rather than one per level.
559
+ */
522
560
  get: (tenantId) => this.request(`/env/tenants/${encodeURIComponent(tenantId)}`, { envBearer: true }),
523
561
  /** PUT /env/tenants/{tenantId} — update a tenant. */
524
562
  update: (tenantId, input) => this.request(`/env/tenants/${encodeURIComponent(tenantId)}`, {
@@ -532,12 +570,23 @@ var AuthyonServerClient = class {
532
570
  envBearer: true,
533
571
  body: input
534
572
  }),
535
- /** POST /env/tenants/{tenantId}/disable — disable without removing tenant data. */
573
+ /**
574
+ * POST /env/tenants/{tenantId}/disable — disable without removing tenant
575
+ * data.
576
+ *
577
+ * Disables the branch below it too. A sub-tenant left live under a
578
+ * disabled parent would go on authenticating its users, which is the one
579
+ * thing switching a tenant off is there to stop — so check `childCount`
580
+ * before calling this on a node with children.
581
+ */
536
582
  disable: (tenantId) => this.request(`/env/tenants/${encodeURIComponent(tenantId)}/disable`, {
537
583
  method: "POST",
538
584
  envBearer: true
539
585
  }),
540
- /** DELETE /env/tenants/{tenantId} — logically delete (disable) a tenant. */
586
+ /**
587
+ * DELETE /env/tenants/{tenantId} — logically delete (disable) a tenant,
588
+ * and the branch below it. Same handler as `disable()`.
589
+ */
541
590
  delete: (tenantId) => this.request(`/env/tenants/${encodeURIComponent(tenantId)}`, {
542
591
  method: "DELETE",
543
592
  envBearer: true
@@ -1114,6 +1163,32 @@ var tenantFilter = {
1114
1163
  return `${assertPath(path2)}:exists`;
1115
1164
  }
1116
1165
  };
1166
+
1167
+ // ../../internal/core/authorization/tenantPermissions.ts
1168
+ var TenantPermissions = {
1169
+ /** Rename the tenant and change its settings. */
1170
+ SettingsManage: "tenants:settings:manage",
1171
+ /** Invite an address to the tenant. */
1172
+ MembersInvite: "tenants:members:invite",
1173
+ /** Remove a member from the tenant. */
1174
+ MembersRemove: "tenants:members:remove"
1175
+ };
1176
+ var SubtenantPermissions = {
1177
+ /** Read any tenant below one you belong to. */
1178
+ TreeRead: "subtenants:tree:read",
1179
+ /** Rename a tenant below yours, and add or remove its members. */
1180
+ TreeManage: "subtenants:tree:manage",
1181
+ /** Create a tenant underneath one you belong to. */
1182
+ TreeCreate: "subtenants:tree:create",
1183
+ /**
1184
+ * Make a tenant below yours the ACTIVE tenant of the session — acting inside
1185
+ * it rather than on it from outside. Deliberately not the same permission as
1186
+ * {@link SubtenantPermissions.TreeManage}: administering a sub-tenant through
1187
+ * the management endpoints and operating as that sub-tenant are different
1188
+ * powers, and granting the first should not hand over the second.
1189
+ */
1190
+ TreeSwitch: "subtenants:tree:switch"
1191
+ };
1117
1192
  export {
1118
1193
  AuthorizationError,
1119
1194
  AuthyonAbility,
@@ -1121,13 +1196,16 @@ export {
1121
1196
  AuthyonError,
1122
1197
  AuthyonServerClient,
1123
1198
  AuthyonServerClientBuilder,
1199
+ CLIENT_IP_HEADER,
1124
1200
  ErrorCodes,
1125
1201
  FetchHttpAdapter,
1126
1202
  JwksTokenVerifier,
1127
1203
  LoggingHttpAdapter,
1204
+ SubtenantPermissions,
1128
1205
  TENANT_FILTER_MAX_DEPTH,
1129
1206
  TENANT_FILTER_MAX_VALUES,
1130
1207
  TENANT_FILTER_MAX_VALUE_LENGTH,
1208
+ TenantPermissions,
1131
1209
  TenantScopedClient,
1132
1210
  TokenVerificationError,
1133
1211
  UserScopedClient,
package/dist/next.cjs CHANGED
@@ -75,13 +75,23 @@ function createAuthyonRules(source = {}, options = {}) {
75
75
  ...source.permissions ?? [],
76
76
  ...source.scope?.split(/\s+/).filter(Boolean) ?? []
77
77
  ]);
78
- const rules = [...permissions].map(permissionToRule).filter(isAbilityRule);
78
+ const rules = [...permissions].map(grantToRule).filter(isAbilityRule);
79
79
  for (const role of /* @__PURE__ */ new Set([...source.roles ?? [], ...options.roles ?? []])) {
80
80
  rules.push(...(options.roleRules?.[role] ?? []).map(cloneRule));
81
81
  }
82
82
  rules.push(...(options.rules ?? []).map(cloneRule));
83
83
  return rules;
84
84
  }
85
+ function grantToRule(permission) {
86
+ const normalized = permission.trim();
87
+ if (normalized && isFullWildcard(normalized)) {
88
+ return { action: "manage", subject: "all" };
89
+ }
90
+ return permissionToRule(normalized);
91
+ }
92
+ function isFullWildcard(permission) {
93
+ return permission.split(":").every((segment) => segment === "*");
94
+ }
85
95
  function permissionToRule(permission) {
86
96
  const normalized = permission.trim();
87
97
  if (!normalized) return null;
package/dist/next.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ReactNode } from 'react';
2
- import { R as RequestAuthorizationOptions, A as AbilitySubject, a as AuthorizationError } from './authorization-Soblu-A5.cjs';
3
- import { A as AuthyonServerClient } from './authyonServerClient-COrfN4o_.cjs';
2
+ import { R as RequestAuthorizationOptions, A as AbilitySubject, a as AuthorizationError } from './authorization-BLENxur8.cjs';
3
+ import { A as AuthyonServerClient } from './authyonServerClient-CW46xIRD.cjs';
4
4
  import 'jose';
5
5
 
6
6
  interface ServerPermissionGuardFallbackContext {