Skip to content

Commit 980a8ae

Browse files
hotlongCopilot
andcommitted
fix(auth-proxy): short-circuit /auth/config and /auth/bootstrap-status
The AuthProxyPlugin forwards every /api/v1/auth/* request straight to the per-project better-auth handler, but better-auth has no /config or /bootstrap-status endpoints. That hid the platform-SSO 'Continue with ObjectStack' button on per-project subdomains (the Account SPA's SocialSignInButtons probes /auth/config and falls back to no providers on 404). Intercept those two paths inside the proxy and answer them from the project AuthManager directly: - /config → authManager.getPublicConfig() - /bootstrap-status → dataEngine.count('sys_user') > 0 Revert the equivalent hack in http-dispatcher.handleAuth that touched the wrong code path (the host kernel, not the project kernel). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 360e7da commit 980a8ae

11 files changed

Lines changed: 464 additions & 391 deletions

File tree

CHANGELOG.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added — M10.30b Setup app audit + listViews batch 🎯
11+
Followup to M10.30a. Walked every Setup menu in a real tenant; produced verdict matrix; shipped the safe fixes plus structural cleanup.
12+
13+
- **`packages/platform-objects/src/apps/setup.app.ts`** — sidebar trimmed from 7 groups / 41 menus → 6 groups / 26 menus with no loss of capability:
14+
- Removed 5 M:N join-table menus (Department Members, Team Members, Org Members, User Permission Sets, Role Permission Sets) — these are tabs on parent records, not entry points.
15+
- Removed 3 marketplace-only menus (Apps, Packages, Installations) which only render when `@objectstack/service-tenant` is loaded; in single-project runtimes they 404'd.
16+
- Removed 3 OAuth satellite menus (Access Tokens, Refresh Tokens, Consents); they live under their parent OAuth Application detail.
17+
- Removed Activity + Comments from Diagnostics; both are CRM operational data, not platform admin surfaces.
18+
- Demoted "All Metadata" from a top-level Platform group to Advanced/debug. Platform group is now empty and removed.
19+
- Renamed "Linked Accounts" → "Identity Links" to disambiguate from sys_user / org members.
20+
- **listViews extension** — mirroring the M10.30a pattern, the following sys_* schemas now ship curated, segmented views:
21+
- `sys_user`: All Users / Unverified / 2FA Enabled
22+
- `sys_role`: Active / Default / Custom / All
23+
- `sys_permission_set`: Active / Inactive / All
24+
- `sys_invitation`: Pending / Accepted / Expired-Canceled / All
25+
- `sys_session`: My Sessions / All
26+
- `sys_sharing_rule`: Active / Inactive / By Object / All
27+
- `sys_department`: Active / Inactive / By Kind / All
28+
- `sys_team`: By Organization / All
29+
- `sys_organization`: All (curated columns)
30+
- **Classification fixes** (correctness):
31+
- `sys_metadata``managedBy: 'config'``'system'`. The metadata table backs every typed config object; writing raw rows here bypasses Zod validation. The list page now correctly hides the New button (Export-only).
32+
- `sys_user_preference``managedBy: 'platform'``'system'`. Per-user state authored from the user's own settings page; the admin list is a support/diagnostic surface only. New + Import buttons are now correctly hidden.
33+
1034
### Added — M10.30 Built-in list views for system objects 🎯
1135
- **`packages/spec/src/data/object.zod.ts`** — added optional `listViews?: Record<string, ListViewSchema>` to `ObjectSchemaBase`. Already consumed by the console's `ObjectView`; previously dead because no schema declared it (Zod was stripping unknown keys). Authors can now bundle curated, segmented list views directly with the object definition.
1236
- **`packages/platform-objects/src/audit/sys-approval-request.object.ts`** — ships 4 views: `my_pending` (`status=pending AND pending_approvers contains {current_user_id}`, sort `updated_at desc`), `submitted_by_me` (`submitter_id={current_user_id}`), `completed` (`status in approved/rejected/recalled`, sort `completed_at desc`), `all_requests`.

apps/cloud/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
},
5252
"devDependencies": {
5353
"@cloudflare/containers": "^0.3.4",
54-
"@cloudflare/workers-types": "^4.20260518.1",
54+
"@cloudflare/workers-types": "^4.20260519.1",
5555
"@types/pg": "^8.20.0",
5656
"esbuild": "^0.28.0",
5757
"ts-node": "^10.9.2",

apps/objectos/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
},
5656
"devDependencies": {
5757
"@cloudflare/containers": "^0.3.4",
58-
"@cloudflare/workers-types": "^4.20260518.1",
58+
"@cloudflare/workers-types": "^4.20260519.1",
5959
"esbuild": "^0.28.0",
6060
"ts-node": "^10.9.2",
6161
"tsup": "^8.5.1",

apps/studio/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@
2323
},
2424
"dependencies": {
2525
"@ai-sdk/anthropic": "^3.0.78",
26-
"@ai-sdk/gateway": "^3.0.115",
26+
"@ai-sdk/gateway": "^3.0.116",
2727
"@ai-sdk/google": "^3.0.75",
2828
"@ai-sdk/openai": "^3.0.64",
29-
"@ai-sdk/react": "^3.0.186",
29+
"@ai-sdk/react": "^3.0.187",
3030
"@hono/node-server": "^2.0.3",
3131
"@objectstack/client": "workspace:*",
3232
"@objectstack/client-react": "workspace:*",
@@ -63,7 +63,7 @@
6363
"@radix-ui/react-toast": "^1.2.15",
6464
"@radix-ui/react-tooltip": "^1.2.8",
6565
"@tanstack/react-router": "^1.170.4",
66-
"ai": "^6.0.184",
66+
"ai": "^6.0.185",
6767
"class-variance-authority": "^0.7.1",
6868
"clsx": "^2.1.1",
6969
"hono": "^4.12.19",

packages/cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
"topicSeparator": " "
4040
},
4141
"dependencies": {
42-
"@ai-sdk/gateway": "^3.0.115",
42+
"@ai-sdk/gateway": "^3.0.116",
4343
"@objectstack/client": "workspace:*",
4444
"@objectstack/core": "workspace:*",
4545
"@objectstack/driver-memory": "workspace:^",

packages/platform-objects/src/security/sys-sharing-rule.object.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,48 @@ export const SysSharingRule = ObjectSchema.create({
3737
titleFormat: '{label}',
3838
compactLayout: ['name', 'object_name', 'recipient_type', 'recipient_id', 'access_level', 'active'],
3939

40+
listViews: {
41+
active: {
42+
type: 'grid',
43+
name: 'active',
44+
label: 'Active',
45+
data: { provider: 'object', object: 'sys_sharing_rule' },
46+
columns: ['label', 'object_name', 'recipient_type', 'recipient_id', 'access_level', 'updated_at'],
47+
filter: [{ field: 'active', operator: 'equals', value: true }],
48+
sort: [{ field: 'object_name', order: 'asc' }, { field: 'label', order: 'asc' }],
49+
pagination: { pageSize: 50 },
50+
},
51+
inactive: {
52+
type: 'grid',
53+
name: 'inactive',
54+
label: 'Inactive',
55+
data: { provider: 'object', object: 'sys_sharing_rule' },
56+
columns: ['label', 'object_name', 'recipient_type', 'recipient_id', 'updated_at'],
57+
filter: [{ field: 'active', operator: 'equals', value: false }],
58+
sort: [{ field: 'label', order: 'asc' }],
59+
pagination: { pageSize: 50 },
60+
},
61+
by_object: {
62+
type: 'grid',
63+
name: 'by_object',
64+
label: 'By Object',
65+
data: { provider: 'object', object: 'sys_sharing_rule' },
66+
columns: ['object_name', 'label', 'recipient_type', 'access_level', 'active'],
67+
sort: [{ field: 'object_name', order: 'asc' }, { field: 'label', order: 'asc' }],
68+
grouping: { fields: [{ field: 'object_name', order: 'asc', collapsed: false }] },
69+
pagination: { pageSize: 100 },
70+
},
71+
all_rules: {
72+
type: 'grid',
73+
name: 'all_rules',
74+
label: 'All',
75+
data: { provider: 'object', object: 'sys_sharing_rule' },
76+
columns: ['label', 'object_name', 'recipient_type', 'recipient_id', 'access_level', 'active', 'updated_at'],
77+
sort: [{ field: 'label', order: 'asc' }],
78+
pagination: { pageSize: 50 },
79+
},
80+
},
81+
4082
fields: {
4183
id: Field.text({ label: 'Rule ID', required: true, readonly: true, group: 'System' }),
4284

packages/runtime/src/cloud/auth-proxy-plugin.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,44 @@ export class AuthProxyPlugin implements Plugin {
112112
try { authSvc = (projectKernel as any).getService?.('auth'); } catch { /* ignore */ }
113113
}
114114

115+
// Custom non-better-auth endpoints. better-auth has no
116+
// /config or /bootstrap-status route, so without these
117+
// short-circuits the request would fall through to the
118+
// better-auth handler and 404. The Account SPA needs
119+
// /config to render the "Continue with ObjectStack"
120+
// platform SSO button via SocialSignInButtons.
121+
const subPath = url.pathname.startsWith(AUTH_PREFIX + '/')
122+
? url.pathname.substring(AUTH_PREFIX.length + 1)
123+
: '';
124+
if (c.req.method === 'GET' && (subPath === 'config' || subPath === 'bootstrap-status')) {
125+
if (subPath === 'config') {
126+
try {
127+
const config = typeof authSvc?.getPublicConfig === 'function'
128+
? authSvc.getPublicConfig()
129+
: null;
130+
if (config) {
131+
return c.json({ success: true, data: config });
132+
}
133+
return c.json({ success: false, error: { code: 'auth_config_unavailable', message: 'AuthManager has no getPublicConfig()' } }, 503);
134+
} catch (e: any) {
135+
return c.json({ success: false, error: { code: 'auth_config_error', message: String(e?.message ?? e) } }, 500);
136+
}
137+
}
138+
// bootstrap-status
139+
try {
140+
const dataEngine = typeof authSvc?.getDataEngine === 'function'
141+
? authSvc.getDataEngine()
142+
: null;
143+
if (!dataEngine || typeof dataEngine.count !== 'function') {
144+
return c.json({ hasOwner: true });
145+
}
146+
const count = await dataEngine.count('sys_user', {});
147+
return c.json({ hasOwner: (count ?? 0) > 0 });
148+
} catch {
149+
return c.json({ hasOwner: true });
150+
}
151+
}
152+
115153
const fn = await resolveAuthHandler(authSvc);
116154
if (!fn) {
117155
return c.json({ error: 'auth_service_unavailable', projectId }, 503);

packages/runtime/src/http-dispatcher.ts

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -695,39 +695,8 @@ export class HttpDispatcher {
695695
* path: sub-path after /auth/
696696
*/
697697
async handleAuth(path: string, method: string, body: any, context: HttpProtocolContext): Promise<HttpDispatcherResult> {
698-
// 0. Custom non-better-auth endpoints. Per-project AuthPlugins run
699-
// with registerRoutes:false (the project kernel has no HTTP server
700-
// of its own — the host dispatcher routes inbound requests in), so
701-
// the `/config` and `/bootstrap-status` routes that AuthPlugin
702-
// normally wires onto the raw Hono app are unreachable. Mirror them
703-
// here so the per-project Account SPA can render the "Continue with
704-
// ObjectStack" SSO button and detect first-run setup.
705-
const normalizedShort = path.replace(/^\/+/, '');
706-
const authService: any = await this.getService(CoreServiceName.enum.auth);
707-
if (authService && method.toUpperCase() === 'GET') {
708-
if (normalizedShort === 'config' && typeof authService.getPublicConfig === 'function') {
709-
try {
710-
const config = authService.getPublicConfig();
711-
return { handled: true, response: { status: 200, body: { success: true, data: config } } };
712-
} catch (e: any) {
713-
return { handled: true, response: { status: 500, body: { success: false, error: { code: 'auth_config_error', message: String(e?.message ?? e) } } } };
714-
}
715-
}
716-
if (normalizedShort === 'bootstrap-status') {
717-
try {
718-
const dataEngine = typeof authService.getDataEngine === 'function' ? authService.getDataEngine() : null;
719-
if (!dataEngine || typeof dataEngine.count !== 'function') {
720-
return { handled: true, response: { status: 200, body: { hasOwner: true } } };
721-
}
722-
const count = await dataEngine.count('sys_user', {});
723-
return { handled: true, response: { status: 200, body: { hasOwner: (count ?? 0) > 0 } } };
724-
} catch {
725-
return { handled: true, response: { status: 200, body: { hasOwner: true } } };
726-
}
727-
}
728-
}
729-
730698
// 1. Try generic Auth Service
699+
const authService = await this.getService(CoreServiceName.enum.auth);
731700
if (authService && typeof authService.handler === 'function') {
732701
const response = await authService.handler(context.request, context.response);
733702
return { handled: true, result: response };

packages/services/service-ai/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"@ai-sdk/provider": "^3.0.10",
2222
"@objectstack/core": "workspace:*",
2323
"@objectstack/spec": "workspace:*",
24-
"ai": "^6.0.184"
24+
"ai": "^6.0.185"
2525
},
2626
"peerDependencies": {
2727
"@ai-sdk/anthropic": "^3.0.0",

packages/spec/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@
119119
"devDependencies": {
120120
"@types/node": "^25.9.0",
121121
"@vitest/coverage-v8": "^4.1.6",
122-
"ai": "^6.0.184",
122+
"ai": "^6.0.185",
123123
"tsx": "^4.22.2",
124124
"typescript": "^6.0.3",
125125
"vitest": "^4.1.6"

0 commit comments

Comments
 (0)