nucleus-core-ts 0.10.35 → 0.10.37

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/.build-ok CHANGED
@@ -1 +1 @@
1
- 0.10.35
1
+ 0.10.37
@@ -1,2 +1,24 @@
1
1
  import type { AuthActions, AuthState } from './types';
2
+ export declare const GODMIN_ROLE_NAME = "godmin";
3
+ export declare function hasGodminRole(roles: {
4
+ name: string;
5
+ }[]): boolean;
6
+ /**
7
+ * The one place that decides whether a claim lookup is even reached.
8
+ *
9
+ * `allow` — the actor bypasses claims entirely (matches the backend).
10
+ * `deny` — the hydrated state failed its integrity check; answer nothing.
11
+ * `claims` — no bypass; the caller must consult the claim list.
12
+ *
13
+ * Order matters and is asserted in the tests: `is_god` short-circuits before
14
+ * integrity (it always has), while the godmin ROLE is honoured only after
15
+ * integrity passes, because `roles` is part of the integrity hash and a
16
+ * tampered roles array must not buy a bypass.
17
+ */
18
+ export declare function resolveClaimGate(actor: {
19
+ isGod?: boolean | null;
20
+ roles: {
21
+ name: string;
22
+ }[];
23
+ }, integrityOk: boolean): 'allow' | 'deny' | 'claims';
2
24
  export declare const useAuthGuardStore: () => import("h-state").StoreType<AuthState, AuthActions>;
@@ -16,6 +16,36 @@ function computeIntegrityHash(roles, claims) {
16
16
  }
17
17
  return hash.toString(36);
18
18
  }
19
+ // ─── Godmin ─────────────────────────────────────────────────────────────────
20
+ // The backend treats the `godmin` ROLE as a full bypass
21
+ // (Services/Authorization/Middleware: `isGodmin` -> `authorized: true`), and it
22
+ // does so for the role alone — `users.is_god` is a separate, additional flag.
23
+ // The claim helpers below must agree with that, or a user the API authorises is
24
+ // shown an empty panel: every menu entry and card is hidden by `hasClaim`, and
25
+ // only the accounts that happen to carry `is_god` can see anything. Measured on
26
+ // DijitalKt: the `godmin` role owns zero rows in `role_claims`, so the whole
27
+ // admin panel rendered empty for it.
28
+ export const GODMIN_ROLE_NAME = 'godmin';
29
+ export function hasGodminRole(roles) {
30
+ return roles.some((r)=>r.name === GODMIN_ROLE_NAME);
31
+ }
32
+ /**
33
+ * The one place that decides whether a claim lookup is even reached.
34
+ *
35
+ * `allow` — the actor bypasses claims entirely (matches the backend).
36
+ * `deny` — the hydrated state failed its integrity check; answer nothing.
37
+ * `claims` — no bypass; the caller must consult the claim list.
38
+ *
39
+ * Order matters and is asserted in the tests: `is_god` short-circuits before
40
+ * integrity (it always has), while the godmin ROLE is honoured only after
41
+ * integrity passes, because `roles` is part of the integrity hash and a
42
+ * tampered roles array must not buy a bypass.
43
+ */ export function resolveClaimGate(actor, integrityOk) {
44
+ if (actor.isGod) return 'allow';
45
+ if (!integrityOk) return 'deny';
46
+ if (hasGodminRole(actor.roles)) return 'allow';
47
+ return 'claims';
48
+ }
19
49
  // ─── Claim Matching ─────────────────────────────────────────────────────────
20
50
  // Mirrors nucleus-core backend's claimMatches logic:
21
51
  // "get.users" matches "get.users.firstName" (prefix/hierarchical match)
@@ -99,7 +129,7 @@ export const { useStore: useAuthGuardStore } = createStore({
99
129
  },
100
130
  isGodmin: (store)=>()=>{
101
131
  if (store.user?.isGod) return true;
102
- return store.roles.some((r)=>r.name === 'godmin');
132
+ return hasGodminRole(store.roles);
103
133
  },
104
134
  hasRole: (store)=>(roleName)=>{
105
135
  if (store.user?.isGod) return true;
@@ -114,23 +144,35 @@ export const { useStore: useAuthGuardStore } = createStore({
114
144
  return roleNames.every((name)=>store.roles.some((r)=>r.name === name));
115
145
  },
116
146
  hasClaim: (store)=>(claimAction, scope)=>{
117
- if (store.user?.isGod) return true;
118
- if (!store.verifyIntegrity()) return false;
147
+ const gate = resolveClaimGate({
148
+ isGod: store.user?.isGod,
149
+ roles: store.roles
150
+ }, store.verifyIntegrity());
151
+ if (gate !== 'claims') return gate === 'allow';
119
152
  return userHasClaim(store.claims, claimAction, scope);
120
153
  },
121
154
  hasAnyClaim: (store)=>(claimActions, scope)=>{
122
- if (store.user?.isGod) return true;
123
- if (!store.verifyIntegrity()) return false;
155
+ const gate = resolveClaimGate({
156
+ isGod: store.user?.isGod,
157
+ roles: store.roles
158
+ }, store.verifyIntegrity());
159
+ if (gate !== 'claims') return gate === 'allow';
124
160
  return claimActions.some((action)=>userHasClaim(store.claims, action, scope));
125
161
  },
126
162
  hasAllClaims: (store)=>(claimActions, scope)=>{
127
- if (store.user?.isGod) return true;
128
- if (!store.verifyIntegrity()) return false;
163
+ const gate = resolveClaimGate({
164
+ isGod: store.user?.isGod,
165
+ roles: store.roles
166
+ }, store.verifyIntegrity());
167
+ if (gate !== 'claims') return gate === 'allow';
129
168
  return claimActions.every((action)=>userHasClaim(store.claims, action, scope));
130
169
  },
131
170
  checkPermission: (store)=>(roles, claims, options)=>{
132
- if (store.user?.isGod) return true;
133
- if (!store.verifyIntegrity()) return false;
171
+ const gate = resolveClaimGate({
172
+ isGod: store.user?.isGod,
173
+ roles: store.roles
174
+ }, store.verifyIntegrity());
175
+ if (gate !== 'claims') return gate === 'allow';
134
176
  const mode = options?.mode ?? 'any';
135
177
  const scope = options?.scope;
136
178
  const rolePass = roles.length === 0 ? false : roles.some((name)=>store.roles.some((r)=>r.name === name));