Skip to content

Commit a3a5abf

Browse files
feat(identity): align client user-context with ADR-0068 (#1990)
Mirror the framework's unified EvalUser contract on the client so a predicate evaluates identically across client, server-formula and server-RLS. - ExpressionProvider: expose the canonical `current_user` plus the back-compat `user` alias and the server-RLS-parity `ctx.user` alias, all pointing at the same user object (predicate scope + evaluator). - useIsWorkspaceAdmin: accept the canonical org_owner/org_admin role names emitted into user.roles[] by the server customSession, so admin detection survives the removal of the `user.role='admin'` overwrite. No new @objectstack/spec npm exports are consumed, so this is not publish-gated on the framework release. Verified: app-shell build + 766 tests, auth build + 107 tests, console build 33/33, Playwright chromium e2e 10/10 (real-browser render, no JS errors). Co-authored-by: zhuangjianguo <zhuangjianguo@steedos.com>
1 parent 8a3b351 commit a3a5abf

2 files changed

Lines changed: 18 additions & 3 deletions

File tree

packages/app-shell/src/providers/ExpressionProvider.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,23 @@ interface ExpressionProviderProps {
5050

5151
export function ExpressionProvider({ children, user = {}, app = {}, data = {}, features = {} }: ExpressionProviderProps) {
5252
const value = useMemo(() => {
53-
const context = { user, app, data, features };
53+
// ADR-0068: expose the SAME user object under the canonical `current_user`
54+
// plus the back-compat `user` alias and the server-RLS-parity `ctx.user`
55+
// alias, so a predicate authored against any one form evaluates identically
56+
// on client, server-formula, and server-RLS.
57+
const context = { current_user: user, user, ctx: { user }, app, data, features };
5458
const evaluator = new ExpressionEvaluator(context);
5559
return { user, app, data, features, evaluator };
5660
}, [user, app, data, features]);
5761

5862
// Also feed the predicate scope used by useCondition/useExpression in
5963
// @object-ui/react so action visibility predicates (e.g. on toolbar
6064
// buttons) can see deployment-level flags like features.multiOrgEnabled.
61-
const scope = useMemo(() => ({ user, app, data, features }), [user, app, data, features]);
65+
// Mirror the canonical `current_user`/`user`/`ctx.user` aliases here too.
66+
const scope = useMemo(
67+
() => ({ current_user: user, user, ctx: { user }, app, data, features }),
68+
[user, app, data, features],
69+
);
6270

6371
return (
6472
<ExprCtx.Provider value={value}>
@@ -76,7 +84,8 @@ export function useExpressionContext(): ExpressionContextValue {
7684
if (!ctx) {
7785
// Return a safe default so components can be used outside the provider
7886
const fallback = { user: {}, app: {}, data: {}, features: {} };
79-
return { ...fallback, evaluator: new ExpressionEvaluator(fallback) };
87+
const evalContext = { current_user: {}, ctx: { user: {} }, ...fallback };
88+
return { ...fallback, evaluator: new ExpressionEvaluator(evalContext) };
8089
}
8190
return ctx;
8291
}

packages/auth/src/useIsWorkspaceAdmin.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ const ADMIN_ROLES = new Set([
1515
'superadmin',
1616
'platform_admin',
1717
'system_admin',
18+
// ADR-0068 canonical role names emitted into `user.roles[]` by the server
19+
// customSession (raw better-auth owner/admin are normalized to org_owner/
20+
// org_admin). Accept both raw and canonical so admin detection survives the
21+
// removal of the `user.role = 'admin'` overwrite footgun.
22+
'org_owner',
23+
'org_admin',
1824
]);
1925

2026
function isAdminRole(role: unknown): boolean {

0 commit comments

Comments
 (0)