Skip to content

Commit 18b3e84

Browse files
Copilothotlong
andcommitted
fix: address all review comments — browser-safe UUID, selective error handling, fix Studio build, update docs
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent cefcee2 commit 18b3e84

7 files changed

Lines changed: 54 additions & 38 deletions

File tree

apps/studio/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
"@objectstack/driver-memory": "workspace:*",
2323
"@objectstack/metadata": "workspace:*",
2424
"@objectstack/objectql": "workspace:*",
25-
"@objectstack/plugin-auth": "workspace:*",
2625
"@objectstack/plugin-msw": "workspace:*",
2726
"@objectstack/runtime": "workspace:*",
2827
"@objectstack/spec": "workspace:*",

apps/studio/src/mocks/createKernel.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { ObjectKernel, DriverPlugin, AppPlugin } from '@objectstack/runtime';
44
import { ObjectQLPlugin, SchemaRegistry } from '@objectstack/objectql';
55
import { InMemoryDriver } from '@objectstack/driver-memory';
66
import { MSWPlugin } from '@objectstack/plugin-msw';
7-
import { AuthPlugin } from '@objectstack/plugin-auth';
87

98
export interface KernelOptions {
109
appConfigs?: any[]; // Multiple app configs
@@ -43,13 +42,6 @@ export async function createKernel(options: KernelOptions) {
4342
// ObjectQLPlugin's ctx.registerService('protocol', ...) during bootstrap.
4443
console.log('[KernelFactory] Protocol service will be registered by ObjectQLPlugin');
4544

46-
// Register AuthPlugin for MSW/mock mode (gracefully skips HTTP route registration)
47-
// WARNING: This secret is for local development only — never use in production
48-
await kernel.use(new AuthPlugin({
49-
secret: 'INSECURE_DEV_ONLY_mock_secret_do_not_use_in_production',
50-
baseUrl: 'http://localhost:5173',
51-
}));
52-
5345
// --- BROKER SHIM (MUST be registered BEFORE MSWPlugin) ---
5446
// HttpDispatcher requires a broker to function. We inject a shim.
5547
(kernel as any).broker = {

content/docs/guides/authentication.mdx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -626,7 +626,7 @@ await kernel.bootstrap();
626626
627627
### Mock Fallback Endpoints
628628

629-
When no auth service handler or broker is available, `HttpDispatcher.handleAuth()` automatically provides mock responses for:
629+
When no auth service handler is registered and the legacy broker login is unavailable, `HttpDispatcher.handleAuth()` automatically provides mock responses for:
630630

631631
| Endpoint | Method | Description |
632632
|:---|:---:|:---|
@@ -639,17 +639,21 @@ When no auth service handler or broker is available, `HttpDispatcher.handleAuth(
639639

640640
This ensures that registration and sign-in flows do not return 404 errors in MSW/browser-only environments.
641641

642+
> **Note:** In server mode with AuthPlugin loaded, the auth service handler takes priority and the mock fallback is never reached. The mock fallback only activates when AuthPlugin is not loaded (e.g. browser-only Studio builds where `better-auth` is unavailable).
643+
642644
### Studio Kernel Factory
643645

644-
The Studio app's `createKernel()` factory includes AuthPlugin by default:
646+
The Studio app runs in the browser, where the Node-only `better-auth` library cannot be bundled. Instead of loading `AuthPlugin` directly, Studio relies on `HttpDispatcher`'s built-in mock fallback to handle auth endpoints in MSW mode:
645647

646648
```typescript
647649
// apps/studio/src/mocks/createKernel.ts
648-
// WARNING: This secret is for local development only
649-
await kernel.use(new AuthPlugin({
650-
secret: 'INSECURE_DEV_ONLY_mock_secret_do_not_use_in_production',
651-
baseUrl: 'http://localhost:5173',
652-
}));
650+
// No AuthPlugin needed — HttpDispatcher provides mock auth endpoints automatically
651+
const kernel = new ObjectKernel();
652+
await kernel.use(new ObjectQLPlugin());
653+
await kernel.use(new DriverPlugin(driver, 'memory'));
654+
// ...
655+
await kernel.use(new MSWPlugin({ /* ... */ }));
656+
await kernel.bootstrap();
653657
```
654658

655659
---

packages/plugins/plugin-auth/src/auth-plugin.test.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,7 @@ describe('AuthPlugin', () => {
169169
await authPlugin.start(mockContext);
170170

171171
expect(mockContext.logger.warn).toHaveBeenCalledWith(
172-
expect.stringContaining('HTTP server not available'),
173-
expect.any(String)
172+
expect.stringContaining('No HTTP server available')
174173
);
175174
// Should NOT throw
176175
});

packages/plugins/plugin-auth/src/auth-plugin.ts

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -101,21 +101,22 @@ export class AuthPlugin implements Plugin {
101101

102102
// Register HTTP routes if enabled and HTTP server is available
103103
if (this.options.registerRoutes) {
104+
let httpServer: IHttpServer | null = null;
104105
try {
105-
const httpServer = ctx.getService<IHttpServer>('http-server');
106-
if (httpServer) {
107-
this.registerAuthRoutes(httpServer, ctx);
108-
ctx.logger.info(`Auth routes registered at ${this.options.basePath}`);
109-
} else {
110-
ctx.logger.warn(
111-
'No HTTP server available — auth routes not registered. ' +
112-
'Auth service is still available for MSW/mock environments via HttpDispatcher.'
113-
);
114-
}
115-
} catch (error) {
116-
// Gracefully handle missing HTTP server (e.g. MSW/mock mode)
117-
const err = error instanceof Error ? error : new Error(String(error));
118-
ctx.logger.warn('HTTP server not available, skipping auth route registration', { error: err.message });
106+
httpServer = ctx.getService<IHttpServer>('http-server');
107+
} catch {
108+
// Service not found — expected in MSW/mock mode
109+
}
110+
111+
if (httpServer) {
112+
// Route registration errors should propagate (server misconfiguration)
113+
this.registerAuthRoutes(httpServer, ctx);
114+
ctx.logger.info(`Auth routes registered at ${this.options.basePath}`);
115+
} else {
116+
ctx.logger.warn(
117+
'No HTTP server available — auth routes not registered. ' +
118+
'Auth service is still available for MSW/mock environments via HttpDispatcher.'
119+
);
119120
}
120121
}
121122

packages/runtime/src/http-dispatcher.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,19 @@
22

33
import { ObjectKernel, getEnv } from '@objectstack/core';
44
import { CoreServiceName } from '@objectstack/spec/system';
5-
import { randomUUID } from 'crypto';
5+
6+
/** Browser-safe UUID generator — prefers Web Crypto, falls back to RFC 4122 v4 */
7+
function randomUUID(): string {
8+
const cryptoObj = typeof globalThis !== 'undefined' ? (globalThis as any).crypto : undefined;
9+
if (cryptoObj && typeof cryptoObj.randomUUID === 'function') {
10+
return cryptoObj.randomUUID();
11+
}
12+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
13+
const r = (Math.random() * 16) | 0;
14+
const v = c === 'x' ? r : (r & 0x3) | 0x8;
15+
return v.toString(16);
16+
});
17+
}
618

719
export interface HttpProtocolContext {
820
request: any;
@@ -187,8 +199,20 @@ export class HttpDispatcher {
187199
const broker = this.ensureBroker();
188200
const data = await broker.call('auth.login', body, { request: context.request });
189201
return { handled: true, response: { status: 200, body: data } };
190-
} catch {
191-
// Broker not available — fall through to mock fallback
202+
} catch (error: any) {
203+
// Only fall through to mock when the broker is truly unavailable.
204+
const msg = error?.message || '';
205+
const statusCode = error?.statusCode ?? error?.status;
206+
const isBrokerUnavailable =
207+
msg.includes('not available') ||
208+
msg.includes('not found') ||
209+
msg.includes('not registered') ||
210+
statusCode === 503;
211+
212+
if (!isBrokerUnavailable) {
213+
// Propagate real auth failures so callers see the correct error
214+
throw error;
215+
}
192216
}
193217
}
194218

pnpm-lock.yaml

Lines changed: 0 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)