@bhooai/nexus-core 2.0.15 → 2.0.16
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/package.json +2 -1
- package/src/app/createNexusApp.ts +25 -0
- package/src/errors.ts +22 -1
- package/vitest.config.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bhooai/nexus-core",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.16",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@bhooai/nexus-ai-client": "^2.0.1",
|
|
22
22
|
"@bhooai/nexus-auth": "^2.0.1",
|
|
23
|
+
"@bhooai/nexus-crypto": "^2.0.15",
|
|
23
24
|
"@bhooai/nexus-data": "^2.0.1",
|
|
24
25
|
"selfsigned": "^5.5.0",
|
|
25
26
|
"zod": "^3.23.8"
|
|
@@ -40,6 +40,7 @@ import { createLazyDb, registerAdminRoutes, RequestLogBuffer } from './adminModu
|
|
|
40
40
|
import { registerAuthRoutes } from './authModule.js';
|
|
41
41
|
import { issueCsrfToken, getCsrfToken, csrf, authToken, requireRole } from '@bhooai/nexus-auth';
|
|
42
42
|
import { connect } from '@bhooai/nexus-data';
|
|
43
|
+
import { ensureLicense } from '@bhooai/nexus-crypto';
|
|
43
44
|
import { createGateway, createFederatedGateway, graphqlHttpHandler, getExplorerHtml, helloSubgraph, SubscriptionServer } from '@bhooai/nexus-graphql';
|
|
44
45
|
import type { Subgraph, GraphQLContext } from '@bhooai/nexus-graphql';
|
|
45
46
|
|
|
@@ -103,6 +104,19 @@ export async function createNexusApp(opts: CreateNexusAppOptions = {}): Promise<
|
|
|
103
104
|
const projectRoot = resolve(opts.projectRoot ?? dirname(srcRoot));
|
|
104
105
|
let config = opts.config ?? (await loadConfigAuto({ root: projectRoot }));
|
|
105
106
|
|
|
107
|
+
// License check at project start — verify the token (signature via fetched
|
|
108
|
+
// JWKS + online status) with the authority. On failure the app still boots,
|
|
109
|
+
// but every request is answered with 402 LICENSE_REQUIRED (middleware below)
|
|
110
|
+
// instead of crashing — a crashed backend surfaces as an opaque HTTP 500
|
|
111
|
+
// through dev proxies.
|
|
112
|
+
let licenseError: string | null = null;
|
|
113
|
+
try {
|
|
114
|
+
await ensureLicense({ projectDir: projectRoot });
|
|
115
|
+
} catch (err) {
|
|
116
|
+
licenseError = (err as Error).message;
|
|
117
|
+
console.error(`[${name}] license check failed: ${licenseError}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
106
120
|
// ------------------------------------------------------------------
|
|
107
121
|
// Per-app ports: nexus.config.ts `apps[]` is authoritative. The app's own
|
|
108
122
|
// entry (matched by `opts.name`) wins over `server.port`. NEXUS_PORT (set
|
|
@@ -516,6 +530,17 @@ export async function createNexusApp(opts: CreateNexusAppOptions = {}): Promise<
|
|
|
516
530
|
},
|
|
517
531
|
});
|
|
518
532
|
|
|
533
|
+
// License gate — when the project-start check failed, answer every request
|
|
534
|
+
// (except /health) with 402 so the UI can show the license error rather than
|
|
535
|
+
// an opaque 500 from a crashed backend.
|
|
536
|
+
if (licenseError) {
|
|
537
|
+
const message = licenseError;
|
|
538
|
+
server.use(async (ctx, next) => {
|
|
539
|
+
if (ctx.path === '/health') return next();
|
|
540
|
+
ctx.json({ error: { code: 'LICENSE_REQUIRED', message } }, 402);
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
|
|
519
544
|
// ------------------------------------------------------------------
|
|
520
545
|
// Realtime WS server — mount discovered ws/*.room.ts handlers (chat lobby)
|
|
521
546
|
// React stack only — future uses live canvas
|
package/src/errors.ts
CHANGED
|
@@ -54,9 +54,30 @@ export class PaymentError extends NexusError {
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
/**
|
|
58
|
+
* A license failure surfaced by `@bhooai/nexus-crypto` (a plain Error carrying
|
|
59
|
+
* `code: 'LICENSE_REQUIRED'`). Kept shape-based so core need not import crypto.
|
|
60
|
+
*/
|
|
61
|
+
export class LicenseRequiredError extends NexusError {
|
|
62
|
+
constructor(message: string, details?: unknown) {
|
|
63
|
+
super(message, { code: 'LICENSE_REQUIRED', statusCode: 402, details });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function licenseCodeOf(err: Error): string | undefined {
|
|
68
|
+
const code = (err as { code?: unknown }).code;
|
|
69
|
+
return typeof code === 'string' ? code : undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
57
72
|
/** Normalize an unknown thrown value into a NexusError. */
|
|
58
73
|
export function toNexusError(err: unknown): NexusError {
|
|
59
74
|
if (err instanceof NexusError) return err;
|
|
60
|
-
if (err instanceof Error)
|
|
75
|
+
if (err instanceof Error) {
|
|
76
|
+
if (licenseCodeOf(err) === 'LICENSE_REQUIRED') {
|
|
77
|
+
const hint = (err as { hint?: unknown }).hint;
|
|
78
|
+
return new LicenseRequiredError(err.message, typeof hint === 'string' ? hint : undefined);
|
|
79
|
+
}
|
|
80
|
+
return new NexusError(err.message, { code: 'NEXUS_ERROR' });
|
|
81
|
+
}
|
|
61
82
|
return new NexusError(String(err));
|
|
62
83
|
}
|