@eintrek/erp-shell 0.1.24 → 0.1.25
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/auth/permission-provider.d.ts +6 -7
- package/dist/auth/permission-utils.d.ts +24 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.esm.js +67 -43
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +67 -42
- package/dist/index.js.map +1 -1
- package/dist/navigation/navigation-data-helpers.d.ts +6 -2
- package/dist/providers/permission-rules-context.d.ts +2 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -623,12 +623,19 @@ SidebarMenuSubButton.displayName = "SidebarMenuSubButton";
|
|
|
623
623
|
|
|
624
624
|
/**
|
|
625
625
|
* True when the user's child-group segments include at least one of the
|
|
626
|
-
* groups required for `permissionRouteKey`.
|
|
627
|
-
*
|
|
626
|
+
* groups required for `permissionRouteKey`.
|
|
627
|
+
*
|
|
628
|
+
* Fail-closed:
|
|
629
|
+
* - Nav item without `permissionRouteKey` → deny (must declare a key)
|
|
630
|
+
* - Key absent from `rulesMap` → deny (unknown route is not public)
|
|
631
|
+
* - Key present with `required_child_groups: []` → allow (explicit public)
|
|
628
632
|
*/
|
|
629
633
|
function canAccessNavPermissionRoute(permissionRouteKey, rulesMap, userChildSegments) {
|
|
630
634
|
if (!permissionRouteKey)
|
|
631
|
-
return
|
|
635
|
+
return false;
|
|
636
|
+
if (!Object.prototype.hasOwnProperty.call(rulesMap, permissionRouteKey)) {
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
632
639
|
const required = rulesMap[permissionRouteKey] ?? [];
|
|
633
640
|
if (required.length === 0)
|
|
634
641
|
return true;
|
|
@@ -1216,8 +1223,8 @@ function PermissionRulesProvider({ children, data, isLoading = false, isError =
|
|
|
1216
1223
|
return (jsxRuntime.jsx(PermissionRulesContext.Provider, { value: value, children: children }));
|
|
1217
1224
|
}
|
|
1218
1225
|
/**
|
|
1219
|
-
* Read the permission rules.
|
|
1220
|
-
*
|
|
1226
|
+
* Read the permission rules. Outside the provider, report loading so
|
|
1227
|
+
* fail-closed consumers (nav / gates) do not treat empty maps as "allow all".
|
|
1221
1228
|
*/
|
|
1222
1229
|
function usePermissionRules() {
|
|
1223
1230
|
const ctx = React.useContext(PermissionRulesContext);
|
|
@@ -1225,7 +1232,7 @@ function usePermissionRules() {
|
|
|
1225
1232
|
return {
|
|
1226
1233
|
rulesMap: {},
|
|
1227
1234
|
policiesMap: {},
|
|
1228
|
-
isLoading:
|
|
1235
|
+
isLoading: true,
|
|
1229
1236
|
isError: false,
|
|
1230
1237
|
updatedAt: null,
|
|
1231
1238
|
};
|
|
@@ -1247,20 +1254,22 @@ function normalizePathnameForPermissionRules(pathname) {
|
|
|
1247
1254
|
const path = parts.length <= 1 ? "/" : "/" + parts.slice(1).join("/");
|
|
1248
1255
|
return path.replace(/\/user-role\/(purchase-doc|sales-doc)$/, "/user-role/members");
|
|
1249
1256
|
}
|
|
1257
|
+
function hasRule(rulesMap, routeKey) {
|
|
1258
|
+
return Object.prototype.hasOwnProperty.call(rulesMap, routeKey);
|
|
1259
|
+
}
|
|
1250
1260
|
/**
|
|
1251
|
-
*
|
|
1252
|
-
* JWT must contain at least one matching group). An exact match on the
|
|
1253
|
-
* normalized path wins; otherwise the function falls back to a segment-by-
|
|
1254
|
-
* segment pattern match where `[param]` placeholders accept any non-empty
|
|
1255
|
-
* value. Returns an empty array when no rule covers the path.
|
|
1261
|
+
* Resolve UI route permissions for a pathname.
|
|
1256
1262
|
*
|
|
1257
|
-
*
|
|
1258
|
-
*
|
|
1263
|
+
* - Exact / `[param]` pattern match → matched
|
|
1264
|
+
* - Parent-prefix inherit only when the parent rule has a **non-empty**
|
|
1265
|
+
* role list (restrictive inherit). Explicitly public parents (`[]`) do
|
|
1266
|
+
* not open all children.
|
|
1267
|
+
* - No match → `{ matched: false }` (fail closed — never treat as public)
|
|
1259
1268
|
*/
|
|
1260
|
-
function
|
|
1269
|
+
function resolveUiRoutePermission(pathname, rulesMap) {
|
|
1261
1270
|
const normalized = normalizePathnameForPermissionRules(pathname);
|
|
1262
|
-
if (rulesMap
|
|
1263
|
-
return rulesMap[normalized];
|
|
1271
|
+
if (hasRule(rulesMap, normalized)) {
|
|
1272
|
+
return { matched: true, roles: rulesMap[normalized] ?? [] };
|
|
1264
1273
|
}
|
|
1265
1274
|
const pathSegments = normalized.split("/").filter(Boolean);
|
|
1266
1275
|
for (const [pattern, roles] of Object.entries(rulesMap)) {
|
|
@@ -1283,24 +1292,43 @@ function getRequiredChildGroups(pathname, rulesMap) {
|
|
|
1283
1292
|
}
|
|
1284
1293
|
}
|
|
1285
1294
|
if (matches) {
|
|
1286
|
-
return roles;
|
|
1295
|
+
return { matched: true, roles: roles ?? [] };
|
|
1287
1296
|
}
|
|
1288
1297
|
}
|
|
1289
|
-
|
|
1298
|
+
// Parent-prefix: /leave/types/abc → /leave/types (non-empty roles only)
|
|
1299
|
+
for (let len = pathSegments.length - 1; len >= 1; len--) {
|
|
1300
|
+
const parent = "/" + pathSegments.slice(0, len).join("/");
|
|
1301
|
+
if (hasRule(rulesMap, parent) && (rulesMap[parent]?.length ?? 0) > 0) {
|
|
1302
|
+
return { matched: true, roles: rulesMap[parent] ?? [] };
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
return { matched: false, roles: [] };
|
|
1306
|
+
}
|
|
1307
|
+
/**
|
|
1308
|
+
* Returns the required child-group segments for a page pathname.
|
|
1309
|
+
*
|
|
1310
|
+
* Prefer {@link resolveUiRoutePermission} when you need to distinguish
|
|
1311
|
+
* "explicitly public (`[]`)" from "no rule (deny)". This helper returns
|
|
1312
|
+
* `[]` for both cases and is kept for backward compatibility.
|
|
1313
|
+
*/
|
|
1314
|
+
function getRequiredChildGroups(pathname, rulesMap) {
|
|
1315
|
+
return resolveUiRoutePermission(pathname, rulesMap).roles;
|
|
1290
1316
|
}
|
|
1291
1317
|
|
|
1318
|
+
function DeniedCard() {
|
|
1319
|
+
return (jsxRuntime.jsx(erpTheme.Card, { className: "p-6", children: jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center gap-3 text-center", children: [jsxRuntime.jsx(lucideReact.AlertCircle, { className: "h-8 w-8 text-destructive" }), jsxRuntime.jsxs("div", { className: "space-y-1", children: [jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-destructive", children: "\u0E44\u0E21\u0E48\u0E21\u0E35\u0E2A\u0E34\u0E17\u0E18\u0E34\u0E4C\u0E40\u0E02\u0E49\u0E32\u0E16\u0E36\u0E07" }), jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: "\u0E04\u0E38\u0E13\u0E44\u0E21\u0E48\u0E21\u0E35\u0E2A\u0E34\u0E17\u0E18\u0E34\u0E4C\u0E43\u0E19\u0E01\u0E32\u0E23\u0E40\u0E02\u0E49\u0E32\u0E16\u0E36\u0E07\u0E2B\u0E19\u0E49\u0E32\u0E19\u0E35\u0E49 \u0E2B\u0E32\u0E01\u0E04\u0E34\u0E14\u0E27\u0E48\u0E32\u0E04\u0E27\u0E23\u0E40\u0E02\u0E49\u0E32\u0E16\u0E36\u0E07\u0E44\u0E14\u0E49 \u0E01\u0E23\u0E38\u0E13\u0E32\u0E15\u0E34\u0E14\u0E15\u0E48\u0E2D\u0E1C\u0E39\u0E49\u0E14\u0E39\u0E41\u0E25\u0E23\u0E30\u0E1A\u0E1A\u0E02\u0E2D\u0E07\u0E2D\u0E07\u0E04\u0E4C\u0E01\u0E23" })] })] }) }));
|
|
1320
|
+
}
|
|
1292
1321
|
/**
|
|
1293
1322
|
* Permission gate for a page subtree. Looks up the current pathname in the
|
|
1294
1323
|
* permission rules map (live rules from `PermissionRulesProvider`, layered
|
|
1295
1324
|
* over the optional `fallbackRulesMap`), passes the resolved required roles
|
|
1296
|
-
* into the host's `usePermission` probe, and renders one of
|
|
1325
|
+
* into the host's `usePermission` probe, and renders one of:
|
|
1297
1326
|
*
|
|
1298
|
-
* 1.
|
|
1299
|
-
* 2.
|
|
1300
|
-
* 3.
|
|
1301
|
-
*
|
|
1302
|
-
*
|
|
1303
|
-
* internal identifiers like `fin_controller` and confuse end users).
|
|
1327
|
+
* 1. rules / probe still loading → `null` (let Suspense / loading.tsx show)
|
|
1328
|
+
* 2. no rule covers this path → deny (fail closed)
|
|
1329
|
+
* 3. rule is explicit public (`[]`) → children
|
|
1330
|
+
* 4. probe says no access → deny card
|
|
1331
|
+
* 5. otherwise → children
|
|
1304
1332
|
*/
|
|
1305
1333
|
function PermissionProvider({ children, usePermission, fallbackRulesMap, }) {
|
|
1306
1334
|
const pathname = navigation.usePathname();
|
|
@@ -1311,31 +1339,27 @@ function PermissionProvider({ children, usePermission, fallbackRulesMap, }) {
|
|
|
1311
1339
|
// live rules win over fallback when both define the same key
|
|
1312
1340
|
return { ...fallbackRulesMap, ...rulesMap };
|
|
1313
1341
|
}, [rulesMap, fallbackRulesMap]);
|
|
1314
|
-
const
|
|
1315
|
-
// Hook must run unconditionally (rules of hooks)
|
|
1316
|
-
// result on the fast path below.
|
|
1342
|
+
const lookup = React.useMemo(() => resolveUiRoutePermission(pathname, mergedMap), [pathname, mergedMap]);
|
|
1343
|
+
// Hook must run unconditionally (rules of hooks).
|
|
1317
1344
|
const { hasPermission, isLoading } = usePermission({
|
|
1318
|
-
requiredRoles,
|
|
1345
|
+
requiredRoles: lookup.roles,
|
|
1319
1346
|
});
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1347
|
+
if (rulesLoading) {
|
|
1348
|
+
return null;
|
|
1349
|
+
}
|
|
1350
|
+
// Unknown route → deny. Only explicit `required_child_groups: []` is public.
|
|
1351
|
+
if (!lookup.matched) {
|
|
1352
|
+
return jsxRuntime.jsx(DeniedCard, {});
|
|
1353
|
+
}
|
|
1354
|
+
if (lookup.roles.length === 0) {
|
|
1324
1355
|
return jsxRuntime.jsx(jsxRuntime.Fragment, { children: children });
|
|
1325
1356
|
}
|
|
1326
|
-
|
|
1327
|
-
// Next.js loading.tsx / Suspense fallback show. The old behaviour wrapped
|
|
1328
|
-
// a big "กำลังตรวจสอบสิทธิ์..." card over the top, which both duplicated
|
|
1329
|
-
// the app's own loader and — worse — caused a "ไม่มีสิทธิ์" flash on
|
|
1330
|
-
// fast loads because the probe transiently returned `hasPermission:false,
|
|
1331
|
-
// isLoading:false` before its dependencies (org context, role-segments)
|
|
1332
|
-
// arrived.
|
|
1333
|
-
const isLoadingCombined = rulesLoading || isLoading;
|
|
1357
|
+
const isLoadingCombined = isLoading;
|
|
1334
1358
|
if (isLoadingCombined) {
|
|
1335
1359
|
return null;
|
|
1336
1360
|
}
|
|
1337
1361
|
if (!hasPermission) {
|
|
1338
|
-
return
|
|
1362
|
+
return jsxRuntime.jsx(DeniedCard, {});
|
|
1339
1363
|
}
|
|
1340
1364
|
return jsxRuntime.jsx(jsxRuntime.Fragment, { children: children });
|
|
1341
1365
|
}
|
|
@@ -1877,6 +1901,7 @@ exports.getRequiredChildGroups = getRequiredChildGroups;
|
|
|
1877
1901
|
exports.isLikelyTransientNetworkError = isLikelyTransientNetworkError;
|
|
1878
1902
|
exports.isPathActive = isPathActive;
|
|
1879
1903
|
exports.normalizePathnameForPermissionRules = normalizePathnameForPermissionRules;
|
|
1904
|
+
exports.resolveUiRoutePermission = resolveUiRoutePermission;
|
|
1880
1905
|
exports.runGlobalRequestPrecheck = runGlobalRequestPrecheck;
|
|
1881
1906
|
exports.runWithTransientRetry = runWithTransientRetry;
|
|
1882
1907
|
exports.setOrganizationId = setOrganizationId;
|