@karmaniverous/jeeves-server 3.0.0-0
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/.env.local +13 -0
- package/.env.local.template +13 -0
- package/.tsbuildinfo +1 -0
- package/CHANGELOG.md +450 -0
- package/about.md +82 -0
- package/client/README.md +73 -0
- package/client/eslint.config.js +23 -0
- package/client/index.html +14 -0
- package/client/package-lock.json +5181 -0
- package/client/package.json +60 -0
- package/client/public/vite.svg +1 -0
- package/client/src/App.tsx +22 -0
- package/client/src/components/AccountMenu.tsx +167 -0
- package/client/src/components/ActionDropdown.tsx +120 -0
- package/client/src/components/CodeEditor.tsx +143 -0
- package/client/src/components/CodeViewer.tsx +113 -0
- package/client/src/components/ConfirmDialog.tsx +32 -0
- package/client/src/components/DirectoryRow.tsx +62 -0
- package/client/src/components/DirectoryTable.tsx +42 -0
- package/client/src/components/DownloadDropdown.tsx +116 -0
- package/client/src/components/DriveList.tsx +54 -0
- package/client/src/components/EmbeddedDiagramPanzoom.ts +28 -0
- package/client/src/components/FileContentView.tsx +155 -0
- package/client/src/components/InlineSvgPanzoom.ts +60 -0
- package/client/src/components/LazyDiagram.ts +93 -0
- package/client/src/components/LinkDropdown.tsx +134 -0
- package/client/src/components/MarkdownView.tsx +115 -0
- package/client/src/components/MermaidViewer.tsx +21 -0
- package/client/src/components/PlantUmlViewer.tsx +21 -0
- package/client/src/components/SearchModal.tsx +424 -0
- package/client/src/components/SvgViewer.tsx +107 -0
- package/client/src/components/TabBar.tsx +96 -0
- package/client/src/components/layout/Header.tsx +270 -0
- package/client/src/components/panzoom.ts +203 -0
- package/client/src/components/renderableUtils.ts +15 -0
- package/client/src/components/runner/JobTable.tsx +153 -0
- package/client/src/components/runner/RunHistory.tsx +140 -0
- package/client/src/components/runner/StatsBar.tsx +43 -0
- package/client/src/components/runner/StatusPill.tsx +27 -0
- package/client/src/components/runner/jobTableUtils.ts +65 -0
- package/client/src/components/scrollUtils.ts +39 -0
- package/client/src/components/ui/alert-dialog.tsx +107 -0
- package/client/src/components/ui/button.tsx +40 -0
- package/client/src/components/ui/dropdown-menu.tsx +79 -0
- package/client/src/components/ui/input.tsx +26 -0
- package/client/src/components/useActionState.ts +43 -0
- package/client/src/hooks/useFileBrowser.ts +102 -0
- package/client/src/hooks/useFileData.ts +78 -0
- package/client/src/hooks/useScrollAnchor.ts +70 -0
- package/client/src/hooks/useShareSettings.ts +22 -0
- package/client/src/hooks/useTopBar.ts +27 -0
- package/client/src/index.css +281 -0
- package/client/src/lib/AuthContext.ts +27 -0
- package/client/src/lib/api.ts +239 -0
- package/client/src/lib/auth.tsx +50 -0
- package/client/src/lib/codeBlockCm6.ts +129 -0
- package/client/src/lib/codeBlockCopy.ts +43 -0
- package/client/src/lib/codemirror.ts +77 -0
- package/client/src/lib/runner-api.ts +172 -0
- package/client/src/lib/svg.ts +50 -0
- package/client/src/lib/theme.ts +34 -0
- package/client/src/lib/utils.ts +6 -0
- package/client/src/main.tsx +11 -0
- package/client/src/pages/FileBrowser.tsx +135 -0
- package/client/src/pages/Home.tsx +46 -0
- package/client/src/pages/Runner.tsx +151 -0
- package/client/src/pages/RunnerJob.tsx +170 -0
- package/client/tsconfig.app.json +32 -0
- package/client/tsconfig.json +7 -0
- package/client/tsconfig.node.json +26 -0
- package/client/vite.config.ts +35 -0
- package/content/privacy.md +61 -0
- package/content/terms.md +41 -0
- package/dist/client/assets/CodeEditor-0XHVI8Nu.js +1 -0
- package/dist/client/assets/CodeViewer-CykMVsfX.js +1 -0
- package/dist/client/assets/index--MBieNJA.js +1 -0
- package/dist/client/assets/index-BENeXQI_.js +1 -0
- package/dist/client/assets/index-BbBpoOxz.js +1 -0
- package/dist/client/assets/index-BdV9g5AM.js +6 -0
- package/dist/client/assets/index-BjAilRri.js +2 -0
- package/dist/client/assets/index-BqbhWo2I.js +3 -0
- package/dist/client/assets/index-CVbycZ0H.js +1 -0
- package/dist/client/assets/index-Cs5oz2oJ.js +5 -0
- package/dist/client/assets/index-D8KZVveX.js +1 -0
- package/dist/client/assets/index-DC4HMHxY.js +13 -0
- package/dist/client/assets/index-DbMebkkd.css +1 -0
- package/dist/client/assets/index-DcY2RXqX.js +1 -0
- package/dist/client/assets/index-Duy-tZYV.js +1 -0
- package/dist/client/assets/index-Dw7rDFmE.js +7 -0
- package/dist/client/assets/index-FlCUvrjv.js +2 -0
- package/dist/client/assets/index-K6OVmfhg.js +1 -0
- package/dist/client/assets/index-LjwgzZ7F.js +62 -0
- package/dist/client/assets/index-MLwyFRN0.js +1 -0
- package/dist/client/assets/index-OpqBpSjn.js +1 -0
- package/dist/client/assets/index-SsHei0HE.js +1 -0
- package/dist/client/assets/index-uQa2yckk.js +1 -0
- package/dist/client/assets/index-udkXoIER.js +1 -0
- package/dist/client/index.html +15 -0
- package/dist/client/vite.svg +1 -0
- package/dist/src/auth/google.js +57 -0
- package/dist/src/auth/keys.js +185 -0
- package/dist/src/auth/resolve.js +102 -0
- package/dist/src/auth/session.js +57 -0
- package/dist/src/cli/commands/config.js +100 -0
- package/dist/src/cli/commands/config.test.js +84 -0
- package/dist/src/cli/commands/service.js +93 -0
- package/dist/src/cli/commands/start.js +24 -0
- package/dist/src/cli/index.js +20 -0
- package/dist/src/config/index.js +90 -0
- package/dist/src/config/loadConfig.test.js +127 -0
- package/dist/src/config/resolve.js +134 -0
- package/dist/src/config/resolve.test.js +148 -0
- package/dist/src/config/schema.js +159 -0
- package/dist/src/config/substituteEnvVars.js +45 -0
- package/dist/src/config/substituteEnvVars.test.js +51 -0
- package/dist/src/config/types.js +5 -0
- package/dist/src/routes/api/auth-status.js +56 -0
- package/dist/src/routes/api/diagrams.js +35 -0
- package/dist/src/routes/api/directory.js +93 -0
- package/dist/src/routes/api/drives.js +15 -0
- package/dist/src/routes/api/export.js +218 -0
- package/dist/src/routes/api/fileContent.js +286 -0
- package/dist/src/routes/api/index.js +33 -0
- package/dist/src/routes/api/linkInfo.js +71 -0
- package/dist/src/routes/api/linkInfo.test.js +104 -0
- package/dist/src/routes/api/middleware.js +117 -0
- package/dist/src/routes/api/raw.js +38 -0
- package/dist/src/routes/api/runner.js +59 -0
- package/dist/src/routes/api/search.js +236 -0
- package/dist/src/routes/api/sharing.js +203 -0
- package/dist/src/routes/api/status.js +68 -0
- package/dist/src/routes/api/status.test.js +62 -0
- package/dist/src/routes/auth.js +99 -0
- package/dist/src/routes/event.js +77 -0
- package/dist/src/routes/event.test.js +206 -0
- package/dist/src/routes/health.js +10 -0
- package/dist/src/routes/keys.js +129 -0
- package/dist/src/routes/path/index.js +17 -0
- package/dist/src/routes/static.js +30 -0
- package/dist/src/server.js +90 -0
- package/dist/src/services/deepShareLinks.js +163 -0
- package/dist/src/services/diagramCache.js +104 -0
- package/dist/src/services/embeddedDiagrams.js +136 -0
- package/dist/src/services/eventLog.js +55 -0
- package/dist/src/services/eventLog.test.js +113 -0
- package/dist/src/services/eventQueue.js +154 -0
- package/dist/src/services/eventQueue.test.js +104 -0
- package/dist/src/services/export.js +220 -0
- package/dist/src/services/exportCache.js +196 -0
- package/dist/src/services/markdown.js +147 -0
- package/dist/src/services/mermaid.js +97 -0
- package/dist/src/services/plantuml.js +145 -0
- package/dist/src/services/puppeteer.js +156 -0
- package/dist/src/util/breadcrumbs.js +22 -0
- package/dist/src/util/crypto.js +56 -0
- package/dist/src/util/crypto.test.js +99 -0
- package/dist/src/util/fileDetection.js +66 -0
- package/dist/src/util/fileDetection.test.js +89 -0
- package/dist/src/util/formatters.js +43 -0
- package/dist/src/util/formatters.test.js +83 -0
- package/dist/src/util/packageVersion.js +25 -0
- package/dist/src/util/platform.js +148 -0
- package/dist/src/util/state.js +46 -0
- package/dist/vitest.config.js +12 -0
- package/favicon.svg +3 -0
- package/guides/access-decision-flow.mmd +24 -0
- package/guides/access-decision-flow.svg +1 -0
- package/guides/api-integration.md +236 -0
- package/guides/deployment.md +287 -0
- package/guides/event-gateway.md +204 -0
- package/guides/event-gateway.mmd +17 -0
- package/guides/event-gateway.svg +1 -0
- package/guides/exports.md +239 -0
- package/guides/setup.md +313 -0
- package/guides/sharing.md +204 -0
- package/jeeves-server.config.template.json +25 -0
- package/package.json +124 -0
- package/scripts/download-plantuml.js +70 -0
- package/src/auth/google.ts +93 -0
- package/src/auth/keys.ts +252 -0
- package/src/auth/resolve.ts +157 -0
- package/src/auth/session.ts +77 -0
- package/src/cli/commands/config.test.ts +107 -0
- package/src/cli/commands/config.ts +113 -0
- package/src/cli/commands/service.ts +129 -0
- package/src/cli/commands/start.ts +27 -0
- package/src/cli/index.ts +25 -0
- package/src/config/index.ts +113 -0
- package/src/config/loadConfig.test.ts +155 -0
- package/src/config/resolve.test.ts +192 -0
- package/src/config/resolve.ts +173 -0
- package/src/config/schema.ts +179 -0
- package/src/config/substituteEnvVars.test.ts +64 -0
- package/src/config/substituteEnvVars.ts +52 -0
- package/src/config/types.ts +129 -0
- package/src/routes/api/auth-status.ts +85 -0
- package/src/routes/api/diagrams.ts +53 -0
- package/src/routes/api/directory.ts +123 -0
- package/src/routes/api/drives.ts +23 -0
- package/src/routes/api/export.ts +314 -0
- package/src/routes/api/fileContent.ts +414 -0
- package/src/routes/api/index.ts +37 -0
- package/src/routes/api/linkInfo.test.ts +132 -0
- package/src/routes/api/linkInfo.ts +83 -0
- package/src/routes/api/middleware.ts +156 -0
- package/src/routes/api/raw.ts +54 -0
- package/src/routes/api/runner.ts +107 -0
- package/src/routes/api/search.ts +321 -0
- package/src/routes/api/sharing.ts +259 -0
- package/src/routes/api/status.test.ts +72 -0
- package/src/routes/api/status.ts +82 -0
- package/src/routes/auth.ts +143 -0
- package/src/routes/event.test.ts +248 -0
- package/src/routes/event.ts +109 -0
- package/src/routes/health.ts +13 -0
- package/src/routes/keys.ts +192 -0
- package/src/routes/path/index.ts +24 -0
- package/src/routes/static.ts +54 -0
- package/src/server.ts +104 -0
- package/src/services/deepShareLinks.ts +203 -0
- package/src/services/diagramCache.ts +128 -0
- package/src/services/embeddedDiagrams.ts +168 -0
- package/src/services/eventLog.test.ts +144 -0
- package/src/services/eventLog.ts +68 -0
- package/src/services/eventQueue.test.ts +127 -0
- package/src/services/eventQueue.ts +196 -0
- package/src/services/export.ts +267 -0
- package/src/services/exportCache.ts +216 -0
- package/src/services/markdown.ts +189 -0
- package/src/services/mermaid.ts +113 -0
- package/src/services/plantuml.ts +172 -0
- package/src/services/puppeteer.ts +188 -0
- package/src/types/fastify.d.ts +13 -0
- package/src/types/jsonmap.d.ts +10 -0
- package/src/types/plantuml-encoder.d.ts +4 -0
- package/src/util/breadcrumbs.ts +33 -0
- package/src/util/crypto.test.ts +132 -0
- package/src/util/crypto.ts +79 -0
- package/src/util/fileDetection.test.ts +115 -0
- package/src/util/fileDetection.ts +70 -0
- package/src/util/formatters.test.ts +105 -0
- package/src/util/formatters.ts +44 -0
- package/src/util/packageVersion.ts +30 -0
- package/src/util/platform.ts +178 -0
- package/src/util/state.ts +55 -0
- package/test-docs/diagram-retry-test.md +18 -0
- package/test-docs/embedded-diagrams.md +52 -0
- package/test-docs/lazy-diagrams-test.md +333 -0
- package/test-docs/page-a.md +7 -0
- package/test-docs/page-b.md +7 -0
- package/test-docs/page-c.md +7 -0
- package/test-docs/sub/page-d.md +7 -0
- package/test-docs/test-diagram.puml +13 -0
- package/test-docs/validate-deep-share.js +318 -0
- package/tsconfig.json +37 -0
- package/tsdoc.json +13 -0
- package/vendor/.plantuml-version +1 -0
- package/vendor/plantuml.jar +0 -0
- package/vitest.config.js +12 -0
- package/vitest.config.ts +13 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config resolution — transforms raw validated config into runtime types.
|
|
3
|
+
*
|
|
4
|
+
* Handles: key resolution, insider merging with state, PlantUML server defaults,
|
|
5
|
+
* scope normalization, internal key derivation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
|
|
11
|
+
import { computeInsiderKey } from '../util/crypto.js';
|
|
12
|
+
import type { JeevesConfig } from './schema.js';
|
|
13
|
+
import type {
|
|
14
|
+
NormalizedScopes,
|
|
15
|
+
ResolvedInsider,
|
|
16
|
+
ResolvedKey,
|
|
17
|
+
RuntimeConfig,
|
|
18
|
+
ServerState,
|
|
19
|
+
} from './types.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Normalize any scopes format to \{ allow, deny \}.
|
|
23
|
+
* - undefined/null → null (unrestricted)
|
|
24
|
+
* - string → \{ allow: [string], deny: [] \}
|
|
25
|
+
* - string[] → \{ allow: string[], deny: [] \}
|
|
26
|
+
* - \{ allow?, deny? \} → \{ allow: allow ?? ['/**'], deny: deny ?? [] \}
|
|
27
|
+
*/
|
|
28
|
+
export function normalizeScopes(raw: unknown): NormalizedScopes | null {
|
|
29
|
+
if (raw === undefined || raw === null) return null;
|
|
30
|
+
if (typeof raw === 'string') return { allow: [raw], deny: [] };
|
|
31
|
+
if (Array.isArray(raw)) return { allow: raw as string[], deny: [] };
|
|
32
|
+
if (typeof raw === 'object') {
|
|
33
|
+
const obj = raw as { allow?: string[]; deny?: string[] };
|
|
34
|
+
return {
|
|
35
|
+
allow: obj.allow ?? ['/**'],
|
|
36
|
+
deny: obj.deny ?? [],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Resolve raw key entries to ResolvedKey[].
|
|
44
|
+
*/
|
|
45
|
+
export function resolveKeys(
|
|
46
|
+
keys: Record<string, string | { key: string; scopes?: unknown }>,
|
|
47
|
+
): ResolvedKey[] {
|
|
48
|
+
return Object.entries(keys).map(([name, entry]) => {
|
|
49
|
+
if (typeof entry === 'string') {
|
|
50
|
+
return { name, seed: entry, scopes: null };
|
|
51
|
+
}
|
|
52
|
+
return { name, seed: entry.key, scopes: normalizeScopes(entry.scopes) };
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Resolve insider entries by merging config (identity + scopes) with state (keys).
|
|
58
|
+
*/
|
|
59
|
+
export function resolveInsiders(
|
|
60
|
+
insiders: Record<string, { scopes?: unknown }>,
|
|
61
|
+
stateFile: string,
|
|
62
|
+
): ResolvedInsider[] {
|
|
63
|
+
let serverState: ServerState = {};
|
|
64
|
+
try {
|
|
65
|
+
if (fs.existsSync(stateFile)) {
|
|
66
|
+
serverState = JSON.parse(
|
|
67
|
+
fs.readFileSync(stateFile, 'utf8'),
|
|
68
|
+
) as ServerState;
|
|
69
|
+
}
|
|
70
|
+
} catch {
|
|
71
|
+
/* empty state */
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return Object.entries(insiders).map(([rawEmail, entry]) => {
|
|
75
|
+
const email = rawEmail.toLowerCase();
|
|
76
|
+
const scopes = normalizeScopes(entry.scopes);
|
|
77
|
+
const stateKey = serverState.insiderKeys?.[email];
|
|
78
|
+
return {
|
|
79
|
+
email,
|
|
80
|
+
seed: stateKey?.seed ?? '',
|
|
81
|
+
scopes,
|
|
82
|
+
keyCreatedAt: stateKey?.createdAt ?? null,
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Resolve PlantUML config with auto-discovery and community server fallback.
|
|
89
|
+
*
|
|
90
|
+
* If no jarPath is configured, checks for a bundled jar at vendor/plantuml.jar
|
|
91
|
+
* (downloaded by the postinstall script).
|
|
92
|
+
*/
|
|
93
|
+
export function resolvePlantuml(
|
|
94
|
+
config?: {
|
|
95
|
+
jarPath?: string;
|
|
96
|
+
javaPath?: string;
|
|
97
|
+
servers?: string[];
|
|
98
|
+
},
|
|
99
|
+
rootDir?: string,
|
|
100
|
+
): { jarPath?: string; javaPath?: string; servers: string[] } {
|
|
101
|
+
const COMMUNITY = 'https://www.plantuml.com/plantuml';
|
|
102
|
+
const servers = config?.servers ? [...config.servers] : [];
|
|
103
|
+
if (!servers.includes(COMMUNITY)) servers.push(COMMUNITY);
|
|
104
|
+
|
|
105
|
+
let jarPath = config?.jarPath;
|
|
106
|
+
if (!jarPath && rootDir) {
|
|
107
|
+
const vendorJar = path.join(rootDir, 'vendor', 'plantuml.jar');
|
|
108
|
+
if (fs.existsSync(vendorJar)) {
|
|
109
|
+
jarPath = vendorJar;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return { jarPath, javaPath: config?.javaPath, servers };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Derive the internal insider key from resolved keys.
|
|
118
|
+
*/
|
|
119
|
+
export function deriveInternalKey(resolvedKeys: ResolvedKey[]): string | null {
|
|
120
|
+
const internalKey = resolvedKeys.find((k) => k.name === '_internal');
|
|
121
|
+
return internalKey ? computeInsiderKey(internalKey.seed) : null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Build the full RuntimeConfig from validated config, resolved runtime values, and paths.
|
|
126
|
+
*
|
|
127
|
+
* Centralizes the mapping from parsed config + resolved values → RuntimeConfig,
|
|
128
|
+
* keeping loadConfig focused on loading/validation and this module focused on resolution.
|
|
129
|
+
*/
|
|
130
|
+
export function buildRuntimeConfig(
|
|
131
|
+
config: JeevesConfig,
|
|
132
|
+
rootDir: string,
|
|
133
|
+
configPath: string,
|
|
134
|
+
): RuntimeConfig {
|
|
135
|
+
const stateFile = path.join(rootDir, 'state.json');
|
|
136
|
+
|
|
137
|
+
const resolvedKeys = resolveKeys(
|
|
138
|
+
config.keys as Record<string, string | { key: string; scopes?: unknown }>,
|
|
139
|
+
);
|
|
140
|
+
const resolvedInsiders = resolveInsiders(
|
|
141
|
+
config.insiders as Record<string, { scopes?: unknown }>,
|
|
142
|
+
stateFile,
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
port: config.port,
|
|
147
|
+
eventTimeoutMs: config.eventTimeoutMs,
|
|
148
|
+
eventLogPurgeMs: config.eventLogPurgeMs,
|
|
149
|
+
maxZipSizeMb: config.maxZipSizeMb,
|
|
150
|
+
chromePath: config.chromePath,
|
|
151
|
+
roots: config.roots,
|
|
152
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
153
|
+
mermaidCliPath: config.mermaidCliPath,
|
|
154
|
+
plantuml: resolvePlantuml(config.plantuml, rootDir),
|
|
155
|
+
outsiderPolicy: normalizeScopes(config.outsiderPolicy) ?? null,
|
|
156
|
+
events: config.events,
|
|
157
|
+
authModes: config.auth.modes,
|
|
158
|
+
resolvedKeys,
|
|
159
|
+
resolvedInsiders,
|
|
160
|
+
googleAuth: config.auth.google ?? null,
|
|
161
|
+
sessionSecret: config.auth.sessionSecret ?? null,
|
|
162
|
+
internalInsiderKey: deriveInternalKey(resolvedKeys),
|
|
163
|
+
runnerUrl: config.runnerUrl,
|
|
164
|
+
watcherUrl: config.watcherUrl,
|
|
165
|
+
diagramCachePath: config.diagramCachePath,
|
|
166
|
+
configPath,
|
|
167
|
+
eventsLog: path.join(rootDir, 'logs', 'webhook-events.jsonl'),
|
|
168
|
+
stateFile,
|
|
169
|
+
eventQueuePath: path.join(rootDir, 'logs', 'event-queue.jsonl'),
|
|
170
|
+
eventQueueCursorPath: path.join(rootDir, 'logs', 'event-queue.cursor'),
|
|
171
|
+
eventLogPath: path.join(rootDir, 'logs', 'event-log.jsonl'),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/** Supported authentication methods */
|
|
4
|
+
export const authModeSchema = z.enum(['google', 'keys']);
|
|
5
|
+
|
|
6
|
+
/** Google OAuth configuration */
|
|
7
|
+
export const googleAuthSchema = z.object({
|
|
8
|
+
clientId: z.string().min(1),
|
|
9
|
+
clientSecret: z.string().min(1),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
/** Auth configuration */
|
|
13
|
+
export const authSchema = z.object({
|
|
14
|
+
/** Active authentication methods. Order determines priority. */
|
|
15
|
+
modes: z
|
|
16
|
+
.array(authModeSchema)
|
|
17
|
+
.min(1, { message: 'At least one auth mode is required' }),
|
|
18
|
+
/** Google OAuth config. Required if modes includes "google". */
|
|
19
|
+
google: googleAuthSchema.optional(),
|
|
20
|
+
/** Session cookie signing secret. Required if modes includes "google". */
|
|
21
|
+
sessionSecret: z.string().min(1).optional(),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
/** Event webhook configuration */
|
|
25
|
+
export const eventConfigSchema = z.object({
|
|
26
|
+
schema: z.record(z.string(), z.unknown()),
|
|
27
|
+
cmd: z.string(),
|
|
28
|
+
map: z.record(z.string(), z.unknown()).optional(),
|
|
29
|
+
timeoutMs: z.number().positive().optional(),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Scopes configuration â€" controls which paths a user/key can access.
|
|
34
|
+
*
|
|
35
|
+
* Three forms:
|
|
36
|
+
* - `string` â€" single allow pattern (e.g. '/d/*')
|
|
37
|
+
* - `string[]` — array of allow patterns (shorthand for \{ allow: [...] \})
|
|
38
|
+
* - `\{ allow?: string[], deny?: string[] \}` — explicit allow/deny rules
|
|
39
|
+
*
|
|
40
|
+
* Semantics:
|
|
41
|
+
* - Path must match at least one allow rule AND NOT match any deny rule
|
|
42
|
+
* - Omitting `allow` = implicit ['/*'] (allow everything)
|
|
43
|
+
* - Omitting `deny` = no exclusions
|
|
44
|
+
* - Omitting scopes entirely = unrestricted access
|
|
45
|
+
*/
|
|
46
|
+
export const scopesObjectSchema = z.object({
|
|
47
|
+
allow: z.array(z.string()).optional(),
|
|
48
|
+
deny: z.array(z.string()).optional(),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export const scopesSchema = z.union([
|
|
52
|
+
z.string(),
|
|
53
|
+
z.array(z.string()),
|
|
54
|
+
scopesObjectSchema,
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
/** Insider entry (identity + scopes only; keys are in state.json) */
|
|
58
|
+
export const insiderEntrySchema = z.object({
|
|
59
|
+
scopes: scopesSchema.optional(),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
/** Key entry â€" plain string (seed, no scopes) or object with key + optional scopes */
|
|
63
|
+
export const keyEntrySchema = z.union([
|
|
64
|
+
z.string().min(1),
|
|
65
|
+
z.object({
|
|
66
|
+
key: z.string().min(1),
|
|
67
|
+
scopes: scopesSchema.optional(),
|
|
68
|
+
}),
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
/** Top-level Jeeves Server configuration */
|
|
72
|
+
export const jeevesConfigSchema = z
|
|
73
|
+
.object({
|
|
74
|
+
port: z.number().int().positive().default(1934),
|
|
75
|
+
chromePath: z.string().min(1),
|
|
76
|
+
auth: authSchema,
|
|
77
|
+
insiders: z.record(z.email(), insiderEntrySchema).default({}),
|
|
78
|
+
keys: z.record(z.string(), keyEntrySchema).default({}),
|
|
79
|
+
events: z.record(z.string(), eventConfigSchema).default({}),
|
|
80
|
+
eventTimeoutMs: z.number().positive().default(30_000),
|
|
81
|
+
eventLogPurgeMs: z.number().positive().default(2_592_000_000),
|
|
82
|
+
/** Maximum directory size in MB for ZIP export. Directories exceeding this are refused. */
|
|
83
|
+
maxZipSizeMb: z.number().positive().default(100),
|
|
84
|
+
/**
|
|
85
|
+
* Filesystem roots for the file browser (Linux only).
|
|
86
|
+
* Map of id â†' filesystem path. On Windows this is ignored (drives are auto-discovered).
|
|
87
|
+
* Example: \{ home: '/home/user', projects: '/opt/projects' \}
|
|
88
|
+
* Default: \{ root: '/' \}
|
|
89
|
+
*/
|
|
90
|
+
roots: z.record(z.string(), z.string()).optional(),
|
|
91
|
+
/**
|
|
92
|
+
* URL of the jeeves-runner API for process dashboard proxy.
|
|
93
|
+
* Default: 'http://127.0.0.1:3100'
|
|
94
|
+
*/
|
|
95
|
+
runnerUrl: z.url().optional(),
|
|
96
|
+
/** @deprecated Mermaid is now bundled. This field is ignored but kept for backward compatibility. */
|
|
97
|
+
mermaidCliPath: z.string().optional(),
|
|
98
|
+
/**
|
|
99
|
+
* PlantUML rendering configuration.
|
|
100
|
+
* - jarPath: local PlantUML jar (requires Java). Tried first â€" supports !include.
|
|
101
|
+
* - servers: fallback PlantUML server URLs, tried in order.
|
|
102
|
+
* The public community server (https://www.plantuml.com/plantuml) is always
|
|
103
|
+
* appended as the last resort unless explicitly listed.
|
|
104
|
+
* If omitted entirely, only the community server is used.
|
|
105
|
+
*/
|
|
106
|
+
plantuml: z
|
|
107
|
+
.object({
|
|
108
|
+
jarPath: z.string().optional(),
|
|
109
|
+
javaPath: z.string().optional(),
|
|
110
|
+
servers: z.array(z.url()).optional(),
|
|
111
|
+
})
|
|
112
|
+
.optional(),
|
|
113
|
+
/**
|
|
114
|
+
* Directory for cached rendered diagrams (content-addressed by source hash).
|
|
115
|
+
* Defaults to `.diagram-cache` in the server working directory.
|
|
116
|
+
*/
|
|
117
|
+
diagramCachePath: z.string().optional(),
|
|
118
|
+
/**
|
|
119
|
+
* URL of the jeeves-watcher API for semantic search.
|
|
120
|
+
* When set, the search UI appears in the header. Example: 'http://localhost:3458'
|
|
121
|
+
*/
|
|
122
|
+
watcherUrl: z.url().optional(),
|
|
123
|
+
/**
|
|
124
|
+
* Global outsider policy â€" constrains which paths are eligible for outsider sharing.
|
|
125
|
+
* Uses the same allow/deny model as insider scopes.
|
|
126
|
+
* If omitted, all paths are shareable with outsiders.
|
|
127
|
+
*/
|
|
128
|
+
outsiderPolicy: scopesObjectSchema.optional(),
|
|
129
|
+
})
|
|
130
|
+
.superRefine((config, ctx) => {
|
|
131
|
+
// Google auth mode requires google config + sessionSecret
|
|
132
|
+
if (config.auth.modes.includes('google')) {
|
|
133
|
+
if (!config.auth.google) {
|
|
134
|
+
ctx.addIssue({
|
|
135
|
+
code: 'custom',
|
|
136
|
+
message: 'auth.google is required when auth.modes includes "google"',
|
|
137
|
+
path: ['auth', 'google'],
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
if (!config.auth.sessionSecret) {
|
|
141
|
+
ctx.addIssue({
|
|
142
|
+
code: 'custom',
|
|
143
|
+
message:
|
|
144
|
+
'auth.sessionSecret is required when auth.modes includes "google"',
|
|
145
|
+
path: ['auth', 'sessionSecret'],
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Keys auth mode requires at least one key
|
|
151
|
+
if (
|
|
152
|
+
config.auth.modes.includes('keys') &&
|
|
153
|
+
Object.keys(config.keys).length === 0
|
|
154
|
+
) {
|
|
155
|
+
ctx.addIssue({
|
|
156
|
+
code: 'custom',
|
|
157
|
+
message: 'At least one key is required when auth.modes includes "keys"',
|
|
158
|
+
path: ['keys'],
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// _internal and _plugin keys must not have scopes
|
|
163
|
+
for (const reserved of ['_internal', '_plugin'] as const) {
|
|
164
|
+
const key = config.keys[reserved];
|
|
165
|
+
if (key && typeof key === 'object' && 'scopes' in key && key.scopes) {
|
|
166
|
+
ctx.addIssue({
|
|
167
|
+
code: 'custom',
|
|
168
|
+
message: `${reserved} key must not have scopes (it is always unscoped)`,
|
|
169
|
+
path: ['keys', reserved],
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
/** Inferred config type */
|
|
176
|
+
export type JeevesConfig = z.infer<typeof jeevesConfigSchema>;
|
|
177
|
+
|
|
178
|
+
/** Auth mode type */
|
|
179
|
+
export type AuthMode = z.infer<typeof authModeSchema>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { substituteEnvVars } from './substituteEnvVars.js';
|
|
4
|
+
|
|
5
|
+
describe('substituteEnvVars', () => {
|
|
6
|
+
const ORIGINAL_ENV = process.env;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
process.env = { ...ORIGINAL_ENV };
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
process.env = ORIGINAL_ENV;
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('replaces a single env var in a string', () => {
|
|
17
|
+
process.env['TEST_VAR'] = 'hello';
|
|
18
|
+
expect(substituteEnvVars('${TEST_VAR}')).toBe('hello');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('replaces multiple env vars in a string', () => {
|
|
22
|
+
process.env['HOST'] = 'localhost';
|
|
23
|
+
process.env['PORT'] = '3456';
|
|
24
|
+
expect(substituteEnvVars('http://${HOST}:${PORT}')).toBe(
|
|
25
|
+
'http://localhost:3456',
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('leaves unresolvable expressions untouched', () => {
|
|
30
|
+
expect(substituteEnvVars('${MISSING_VAR}')).toBe('${MISSING_VAR}');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('handles non-string primitives unchanged', () => {
|
|
34
|
+
expect(substituteEnvVars(42)).toBe(42);
|
|
35
|
+
expect(substituteEnvVars(true)).toBe(true);
|
|
36
|
+
expect(substituteEnvVars(null)).toBe(null);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('deep-walks objects', () => {
|
|
40
|
+
process.env['SECRET'] = 'abc123';
|
|
41
|
+
const input = { nested: { key: '${SECRET}' }, plain: 'no-sub' };
|
|
42
|
+
expect(substituteEnvVars(input)).toEqual({
|
|
43
|
+
nested: { key: 'abc123' },
|
|
44
|
+
plain: 'no-sub',
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('deep-walks arrays', () => {
|
|
49
|
+
process.env['ITEM'] = 'resolved';
|
|
50
|
+
expect(substituteEnvVars(['${ITEM}', 'literal'])).toEqual([
|
|
51
|
+
'resolved',
|
|
52
|
+
'literal',
|
|
53
|
+
]);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('handles mixed nested structures', () => {
|
|
57
|
+
process.env['A'] = 'alpha';
|
|
58
|
+
const input = { list: [{ val: '${A}' }, '${A}'], num: 5 };
|
|
59
|
+
expect(substituteEnvVars(input)).toEqual({
|
|
60
|
+
list: [{ val: 'alpha' }, 'alpha'],
|
|
61
|
+
num: 5,
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
*
|
|
4
|
+
* Deep-walks config objects and replaces `${VAR_NAME}` patterns with environment variable values.
|
|
5
|
+
* Ported from jeeves-watcher — candidate for hoisting to shared `@karmaniverous/jeeves-config`.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const ENV_PATTERN = /\$\{([^}]+)\}/g;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Replace `${VAR_NAME}` patterns in a string with `process.env.VAR_NAME`.
|
|
12
|
+
*
|
|
13
|
+
* @param value - The string to process.
|
|
14
|
+
* @returns The string with resolved env vars; unresolvable expressions left untouched.
|
|
15
|
+
*/
|
|
16
|
+
function substituteString(value: string): string {
|
|
17
|
+
return value.replace(ENV_PATTERN, (match, varName: string) => {
|
|
18
|
+
const envValue = process.env[varName];
|
|
19
|
+
if (envValue === undefined) return match;
|
|
20
|
+
return envValue;
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Deep-walk a value and substitute `${VAR_NAME}` patterns in all string values.
|
|
26
|
+
*
|
|
27
|
+
* @param value - The value to walk (object, array, or primitive).
|
|
28
|
+
* @returns A new value with all env var references resolved.
|
|
29
|
+
*/
|
|
30
|
+
export function substituteEnvVars<T>(value: T): T {
|
|
31
|
+
if (typeof value === 'string') {
|
|
32
|
+
return substituteString(value) as T;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (Array.isArray(value)) {
|
|
36
|
+
return value.map((item: unknown) => substituteEnvVars(item)) as T;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (
|
|
40
|
+
value !== null &&
|
|
41
|
+
typeof value === 'object' &&
|
|
42
|
+
Object.getPrototypeOf(value) === Object.prototype
|
|
43
|
+
) {
|
|
44
|
+
const result: Record<string, unknown> = {};
|
|
45
|
+
for (const [key, val] of Object.entries(value)) {
|
|
46
|
+
result[key] = substituteEnvVars(val);
|
|
47
|
+
}
|
|
48
|
+
return result as T;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime and internal types for Jeeves Server.
|
|
3
|
+
* Config types are derived from Zod schema in schema.ts.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { AuthMode, JeevesConfig } from './schema.js';
|
|
7
|
+
|
|
8
|
+
// Re-export config types from schema
|
|
9
|
+
export type { AuthMode, JeevesConfig };
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Normalized scopes — always resolved to \{ allow, deny \} form.
|
|
13
|
+
* null = unrestricted access.
|
|
14
|
+
*/
|
|
15
|
+
export interface NormalizedScopes {
|
|
16
|
+
allow: string[];
|
|
17
|
+
deny: string[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Resolved key seed with normalized scopes
|
|
22
|
+
*/
|
|
23
|
+
export interface ResolvedKey {
|
|
24
|
+
name: string;
|
|
25
|
+
seed: string;
|
|
26
|
+
scopes: NormalizedScopes | null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Resolved insider with normalized scopes
|
|
31
|
+
*/
|
|
32
|
+
export interface ResolvedInsider {
|
|
33
|
+
email: string;
|
|
34
|
+
seed: string;
|
|
35
|
+
scopes: NormalizedScopes | null;
|
|
36
|
+
keyCreatedAt: string | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Combined runtime configuration (post-resolution)
|
|
41
|
+
*/
|
|
42
|
+
export interface RuntimeConfig {
|
|
43
|
+
port: number;
|
|
44
|
+
eventTimeoutMs: number;
|
|
45
|
+
eventLogPurgeMs: number;
|
|
46
|
+
maxZipSizeMb: number;
|
|
47
|
+
chromePath: string;
|
|
48
|
+
roots?: Record<string, string>;
|
|
49
|
+
mermaidCliPath?: string;
|
|
50
|
+
plantuml: {
|
|
51
|
+
jarPath?: string;
|
|
52
|
+
javaPath?: string;
|
|
53
|
+
servers: string[];
|
|
54
|
+
};
|
|
55
|
+
diagramCachePath?: string;
|
|
56
|
+
runnerUrl?: string;
|
|
57
|
+
watcherUrl?: string;
|
|
58
|
+
outsiderPolicy: NormalizedScopes | null;
|
|
59
|
+
events: JeevesConfig['events'];
|
|
60
|
+
authModes: AuthMode[];
|
|
61
|
+
resolvedKeys: ResolvedKey[];
|
|
62
|
+
resolvedInsiders: ResolvedInsider[];
|
|
63
|
+
googleAuth: { clientId: string; clientSecret: string } | null;
|
|
64
|
+
sessionSecret: string | null;
|
|
65
|
+
internalInsiderKey: string | null;
|
|
66
|
+
configPath: string;
|
|
67
|
+
eventsLog: string;
|
|
68
|
+
stateFile: string;
|
|
69
|
+
eventQueuePath: string;
|
|
70
|
+
eventQueueCursorPath: string;
|
|
71
|
+
eventLogPath: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Insider key state (auto-generated, persisted in state.json)
|
|
76
|
+
*/
|
|
77
|
+
export interface InsiderKeyState {
|
|
78
|
+
seed: string;
|
|
79
|
+
createdAt: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* State file structure (mutable runtime state, separate from config)
|
|
84
|
+
*/
|
|
85
|
+
export interface ServerState {
|
|
86
|
+
keyRotatedAt?: string;
|
|
87
|
+
/** Auto-generated insider keys, keyed by email */
|
|
88
|
+
insiderKeys?: Record<string, InsiderKeyState>;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Access mode for authenticated requests
|
|
93
|
+
*/
|
|
94
|
+
export type AccessMode = 'insider' | 'outsider';
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Key verification result
|
|
98
|
+
*/
|
|
99
|
+
export interface KeyVerificationResult {
|
|
100
|
+
valid: boolean;
|
|
101
|
+
mode: AccessMode | null;
|
|
102
|
+
keyName: string | null;
|
|
103
|
+
seed: string | null;
|
|
104
|
+
/** For directory outsider links, the ancestor path the key matched against */
|
|
105
|
+
matchedPath: string | null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Queue entry for event processing
|
|
110
|
+
*/
|
|
111
|
+
export interface QueueEntry {
|
|
112
|
+
ts: string;
|
|
113
|
+
event: string;
|
|
114
|
+
cmd: string;
|
|
115
|
+
body: Record<string, unknown>;
|
|
116
|
+
timeoutMs: number;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Event log entry
|
|
121
|
+
*/
|
|
122
|
+
export interface EventLogEntry {
|
|
123
|
+
ts: string;
|
|
124
|
+
event: string | null;
|
|
125
|
+
matched: boolean;
|
|
126
|
+
exitCode?: number;
|
|
127
|
+
durationMs?: number;
|
|
128
|
+
bodyPreview?: string;
|
|
129
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth status API route.
|
|
3
|
+
*
|
|
4
|
+
* Handles: /api/auth/status
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { FastifyPluginAsync, FastifyReply, FastifyRequest } from 'fastify';
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
findInsider,
|
|
11
|
+
resolveKeyAuth,
|
|
12
|
+
resolveSessionAuth,
|
|
13
|
+
} from '../../auth/resolve.js';
|
|
14
|
+
import { getConfig } from '../../config/index.js';
|
|
15
|
+
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
17
|
+
export const authStatusRoutes: FastifyPluginAsync = async (fastify) => {
|
|
18
|
+
fastify.get(
|
|
19
|
+
'/api/auth/status',
|
|
20
|
+
async (request: FastifyRequest, reply: FastifyReply) => {
|
|
21
|
+
const config = getConfig();
|
|
22
|
+
|
|
23
|
+
// Try session cookie first
|
|
24
|
+
const sessionResult = resolveSessionAuth(config, request);
|
|
25
|
+
if (sessionResult.valid) {
|
|
26
|
+
const insider = findInsider(
|
|
27
|
+
config.resolvedInsiders,
|
|
28
|
+
sessionResult.email!,
|
|
29
|
+
);
|
|
30
|
+
// Get picture from session cookie directly
|
|
31
|
+
const cookieValue = (
|
|
32
|
+
request.cookies as Record<string, string> | undefined
|
|
33
|
+
)?.['jeeves_session'];
|
|
34
|
+
let picture: string | undefined;
|
|
35
|
+
if (cookieValue) {
|
|
36
|
+
try {
|
|
37
|
+
const b64 = cookieValue.slice(0, cookieValue.lastIndexOf('.'));
|
|
38
|
+
const payload = JSON.parse(
|
|
39
|
+
Buffer.from(b64, 'base64url').toString(),
|
|
40
|
+
) as { picture?: string };
|
|
41
|
+
picture = payload.picture;
|
|
42
|
+
} catch {
|
|
43
|
+
/* ignore */
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return reply.send({
|
|
48
|
+
authenticated: true,
|
|
49
|
+
email: sessionResult.email,
|
|
50
|
+
picture,
|
|
51
|
+
isInsider: !!insider?.seed,
|
|
52
|
+
keyCreatedAt: insider?.keyCreatedAt ?? null,
|
|
53
|
+
searchEnabled: !!config.watcherUrl,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Try key-based auth
|
|
58
|
+
if (config.authModes.includes('keys')) {
|
|
59
|
+
const query = request.query as Record<string, string | undefined>;
|
|
60
|
+
const deepParams =
|
|
61
|
+
query.d !== undefined && query.s !== undefined
|
|
62
|
+
? { d: query.d, dirs: query.dirs ?? '0', s: query.s }
|
|
63
|
+
: undefined;
|
|
64
|
+
|
|
65
|
+
const keyResult = resolveKeyAuth(
|
|
66
|
+
config,
|
|
67
|
+
query.path ?? '/',
|
|
68
|
+
query.key,
|
|
69
|
+
query.exp,
|
|
70
|
+
deepParams,
|
|
71
|
+
);
|
|
72
|
+
if (keyResult.valid) {
|
|
73
|
+
return reply.send({
|
|
74
|
+
authenticated: true,
|
|
75
|
+
email: `key:${String(keyResult.keyName)}`,
|
|
76
|
+
isInsider: keyResult.mode === 'insider',
|
|
77
|
+
keyCreatedAt: null,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return reply.send({ authenticated: false, isInsider: false });
|
|
83
|
+
},
|
|
84
|
+
);
|
|
85
|
+
};
|