@cogenta/cli 0.2.2 → 0.4.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/dist/admin-assets/assets/index-C9a7O_Xs.css +1 -0
- package/dist/admin-assets/assets/index-DXvMgWvn.js +74 -0
- package/dist/admin-assets/fonts/ibm-plex-mono-400.woff2 +0 -0
- package/dist/admin-assets/fonts/ibm-plex-mono-500.woff2 +0 -0
- package/dist/admin-assets/fonts/ibm-plex-mono-600.woff2 +0 -0
- package/dist/admin-assets/fonts/ibm-plex-mono-700.woff2 +0 -0
- package/dist/admin-assets/fonts/ibm-plex-sans-var.woff2 +0 -0
- package/dist/admin-assets/index.html +2 -2
- package/dist/commands/assistant.d.ts +65 -0
- package/dist/commands/assistant.d.ts.map +1 -0
- package/dist/commands/assistant.js +207 -0
- package/dist/commands/assistant.js.map +1 -0
- package/dist/commands/content-webhooks.d.ts +41 -0
- package/dist/commands/content-webhooks.d.ts.map +1 -0
- package/dist/commands/content-webhooks.js +61 -0
- package/dist/commands/content-webhooks.js.map +1 -0
- package/dist/commands/http-security.d.ts +39 -0
- package/dist/commands/http-security.d.ts.map +1 -0
- package/dist/commands/http-security.js +153 -0
- package/dist/commands/http-security.js.map +1 -0
- package/dist/commands/links.d.ts +24 -0
- package/dist/commands/links.d.ts.map +1 -0
- package/dist/commands/links.js +109 -0
- package/dist/commands/links.js.map +1 -0
- package/dist/commands/media-images.d.ts +67 -0
- package/dist/commands/media-images.d.ts.map +1 -0
- package/dist/commands/media-images.js +107 -0
- package/dist/commands/media-images.js.map +1 -0
- package/dist/commands/search-page.d.ts +40 -0
- package/dist/commands/search-page.d.ts.map +1 -0
- package/dist/commands/search-page.js +104 -0
- package/dist/commands/search-page.js.map +1 -0
- package/dist/commands/security-alerts.d.ts +24 -0
- package/dist/commands/security-alerts.d.ts.map +1 -0
- package/dist/commands/security-alerts.js +82 -0
- package/dist/commands/security-alerts.js.map +1 -0
- package/dist/commands/seo.d.ts +88 -0
- package/dist/commands/seo.d.ts.map +1 -0
- package/dist/commands/seo.js +155 -0
- package/dist/commands/seo.js.map +1 -0
- package/dist/commands/serve.d.ts +138 -4
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/commands/serve.js +1145 -43
- package/dist/commands/serve.js.map +1 -1
- package/dist/commands/site-plan.d.ts +86 -0
- package/dist/commands/site-plan.d.ts.map +1 -0
- package/dist/commands/site-plan.js +235 -0
- package/dist/commands/site-plan.js.map +1 -0
- package/dist/commands/theme-css.d.ts +54 -0
- package/dist/commands/theme-css.d.ts.map +1 -0
- package/dist/commands/theme-css.js +121 -0
- package/dist/commands/theme-css.js.map +1 -0
- package/dist/commands/theme-render.d.ts +115 -4
- package/dist/commands/theme-render.d.ts.map +1 -1
- package/dist/commands/theme-render.js +260 -17
- package/dist/commands/theme-render.js.map +1 -1
- package/dist/commands/users.d.ts +7 -1
- package/dist/commands/users.d.ts.map +1 -1
- package/dist/commands/users.js +129 -15
- package/dist/commands/users.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -1
- package/dist/reset-mail.d.ts +39 -0
- package/dist/reset-mail.d.ts.map +1 -0
- package/dist/reset-mail.js +45 -0
- package/dist/reset-mail.js.map +1 -0
- package/package.json +15 -10
- package/dist/admin-assets/assets/index-21ZcDkDC.css +0 -1
- package/dist/admin-assets/assets/index-BXVsXHD2.js +0 -23
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<title>Cogenta</title>
|
|
7
|
-
<script type="module" crossorigin src="/admin/assets/index-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/admin/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/admin/assets/index-DXvMgWvn.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/admin/assets/index-C9a7O_Xs.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="root"></div>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type AssistToolset, type EmbeddingProvider, type SemanticSearch, type VectorStore } from '@cogenta/agents';
|
|
2
|
+
import type { CogentaConfig, DatabaseHandle, Logger } from '@cogenta/core';
|
|
3
|
+
import type { CollectionDefinition, ContentStore, SearchDriver } from '@cogenta/schema';
|
|
4
|
+
/**
|
|
5
|
+
* Where L18 is actually wired into a running site.
|
|
6
|
+
*
|
|
7
|
+
* This module is the only place in the repository that knows both
|
|
8
|
+
* `@cogenta/agents` (the tools) and `@cogenta/schema` (the content), which is
|
|
9
|
+
* why the bridge between a written entry and a stored embedding lives here
|
|
10
|
+
* rather than in either of them: `@cogenta/schema` may not depend on the agent
|
|
11
|
+
* runtime, and `@cogenta/agents` may not depend on the content engine.
|
|
12
|
+
*
|
|
13
|
+
* **Everything here is optional and fails soft.** A site with no AI provider
|
|
14
|
+
* gets an unavailable toolset and no route change; a site whose embeddings
|
|
15
|
+
* provider has no adapter yet gets no semantic search and a warning in the log,
|
|
16
|
+
* not a refusal to start. Nothing in this file can prevent `cogenta serve` from
|
|
17
|
+
* serving a site (R2).
|
|
18
|
+
*/
|
|
19
|
+
export interface AssistantAssembly {
|
|
20
|
+
readonly toolset: AssistToolset;
|
|
21
|
+
/** Absent when semantic search is not available on this site. */
|
|
22
|
+
readonly search?: SemanticSearch;
|
|
23
|
+
/** Absent for the same reason. Used to keep the index in step with the content. */
|
|
24
|
+
readonly vectors?: {
|
|
25
|
+
readonly store: VectorStore;
|
|
26
|
+
readonly embeddings: EmbeddingProvider;
|
|
27
|
+
};
|
|
28
|
+
/** What `cogenta serve` prints on startup. Always truthful about what is off. */
|
|
29
|
+
readonly summary: string;
|
|
30
|
+
dispose(): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
export interface BuildAssistantOptions {
|
|
33
|
+
readonly config: CogentaConfig;
|
|
34
|
+
readonly db: DatabaseHandle;
|
|
35
|
+
readonly logger: Logger;
|
|
36
|
+
/** L10's full-text index, fused with the vector half rather than replaced by it. */
|
|
37
|
+
readonly fullText?: SearchDriver;
|
|
38
|
+
}
|
|
39
|
+
export declare function buildAssistant(options: BuildAssistantOptions): Promise<AssistantAssembly>;
|
|
40
|
+
/**
|
|
41
|
+
* Keeps the vector index in step with the content, by wrapping the store.
|
|
42
|
+
*
|
|
43
|
+
* Exactly the shape `withSearchIndexing` (L10 task 3) already established, and
|
|
44
|
+
* for the same reason: `serve.ts` hands the *same* store instances to REST and
|
|
45
|
+
* to GraphQL, so one wrap covers both transports and neither can bypass it.
|
|
46
|
+
*
|
|
47
|
+
* Only the **published** face is indexed. A chat answer is quotable, and the
|
|
48
|
+
* one thing that must never happen is a visitor being told something from a
|
|
49
|
+
* draft nobody approved — so an entry with no published face is removed from
|
|
50
|
+
* the index rather than indexed under its draft status.
|
|
51
|
+
*
|
|
52
|
+
* A failed index write never fails the content write that triggered it: the
|
|
53
|
+
* embedding is derived data that can be rebuilt from the content at any time,
|
|
54
|
+
* and losing an editor's save because an embedding could not be stored would be
|
|
55
|
+
* the wrong trade.
|
|
56
|
+
*/
|
|
57
|
+
export interface VectorIndexingOptions {
|
|
58
|
+
readonly collection: CollectionDefinition;
|
|
59
|
+
readonly siteId: string;
|
|
60
|
+
readonly store: VectorStore;
|
|
61
|
+
readonly embeddings: EmbeddingProvider;
|
|
62
|
+
readonly onError?: (error: unknown) => void;
|
|
63
|
+
}
|
|
64
|
+
export declare function withVectorIndexing(store: ContentStore, options: VectorIndexingOptions): ContentStore;
|
|
65
|
+
//# sourceMappingURL=assistant.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assistant.d.ts","sourceRoot":"","sources":["../../src/commands/assistant.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,aAAa,EAOlB,KAAK,iBAAiB,EAKtB,KAAK,cAAc,EAEnB,KAAK,WAAW,EACjB,MAAM,iBAAiB,CAAA;AACxB,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAC1E,OAAO,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAGvF;;;;;;;;;;;;;;GAcG;AAEH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAA;IAC/B,iEAAiE;IACjE,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAA;IAChC,mFAAmF;IACnF,QAAQ,CAAC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;QAAC,QAAQ,CAAC,UAAU,EAAE,iBAAiB,CAAA;KAAE,CAAA;IAC1F,iFAAiF;IACjF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACzB;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;IAC9B,QAAQ,CAAC,EAAE,EAAE,cAAc,CAAA;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,oFAAoF;IACpF,QAAQ,CAAC,QAAQ,CAAC,EAAE,YAAY,CAAA;CACjC;AAwFD,wBAAsB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA0D/F;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,UAAU,EAAE,oBAAoB,CAAA;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAA;IAC3B,QAAQ,CAAC,UAAU,EAAE,iBAAiB,CAAA;IACtC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;CAC5C;AAyCD,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,qBAAqB,GAC7B,YAAY,CAyCd"}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { createAssistToolset, createHashingEmbeddingProvider, createImageProviderRegistry, createProviderRegistry, createSemanticSearch, createVectorRegistry, PROVIDER_NAMES, } from '@cogenta/agents';
|
|
2
|
+
import { searchDocumentFor } from '@cogenta/schema';
|
|
3
|
+
function isProviderName(value) {
|
|
4
|
+
return PROVIDER_NAMES.includes(value);
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* The text client, or nothing.
|
|
8
|
+
*
|
|
9
|
+
* A configured provider whose name this build does not know, or whose API key
|
|
10
|
+
* is missing, produces `undefined` and a warning — never a throw. An operator
|
|
11
|
+
* who mistyped a provider name should get a site that works with the assistant
|
|
12
|
+
* off, plus a log line saying exactly that.
|
|
13
|
+
*/
|
|
14
|
+
function textProvider(options) {
|
|
15
|
+
const llm = options.config.llm;
|
|
16
|
+
if (llm === undefined)
|
|
17
|
+
return undefined;
|
|
18
|
+
if (!isProviderName(llm.provider)) {
|
|
19
|
+
options.logger.warn('unknown LLM provider, the writing assistant stays off', {
|
|
20
|
+
provider: llm.provider,
|
|
21
|
+
known: PROVIDER_NAMES,
|
|
22
|
+
});
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
if (llm.apiKey === undefined) {
|
|
26
|
+
options.logger.warn('LLM provider configured with no API key, the writing assistant stays off', {
|
|
27
|
+
provider: llm.provider,
|
|
28
|
+
variable: 'COGENTA_LLM_API_KEY',
|
|
29
|
+
});
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
const registry = createProviderRegistry({
|
|
33
|
+
[llm.provider]: {
|
|
34
|
+
apiKey: llm.apiKey,
|
|
35
|
+
model: llm.model,
|
|
36
|
+
...(llm.baseUrl === undefined ? {} : { baseUrl: llm.baseUrl }),
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
return registry.get(llm.provider);
|
|
40
|
+
}
|
|
41
|
+
function imageProvider(options) {
|
|
42
|
+
const images = options.config.imageGeneration;
|
|
43
|
+
if (images === undefined)
|
|
44
|
+
return undefined;
|
|
45
|
+
if (images.apiKey === undefined) {
|
|
46
|
+
options.logger.warn('image provider configured with no API key, image generation stays off', {
|
|
47
|
+
provider: images.provider,
|
|
48
|
+
variable: 'COGENTA_IMAGE_API_KEY',
|
|
49
|
+
});
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
const registry = createImageProviderRegistry({
|
|
53
|
+
[images.provider]: {
|
|
54
|
+
apiKey: images.apiKey,
|
|
55
|
+
model: images.model,
|
|
56
|
+
...(images.baseUrl === undefined ? {} : { baseUrl: images.baseUrl }),
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
return registry.get(images.provider);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The embedder, or nothing.
|
|
63
|
+
*
|
|
64
|
+
* Only `local` has an adapter today — the hashing provider, which needs no key,
|
|
65
|
+
* no service and no model download. `openai` is a valid configuration value
|
|
66
|
+
* that nothing implements yet, and the honest answer to it is to switch the
|
|
67
|
+
* feature off with a log line naming what is missing, not to quietly substitute
|
|
68
|
+
* a different embedding space (which would rank nonsense) and not to refuse to
|
|
69
|
+
* boot over a feature the site may not even use.
|
|
70
|
+
*/
|
|
71
|
+
function embeddingProvider(options) {
|
|
72
|
+
const { provider, dimensions } = options.config.embeddings;
|
|
73
|
+
if (provider === 'local')
|
|
74
|
+
return createHashingEmbeddingProvider({ dimensions });
|
|
75
|
+
options.logger.warn('no adapter for this embeddings provider yet, semantic search and duplicate detection stay off', { provider, implemented: ['local'] });
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
export async function buildAssistant(options) {
|
|
79
|
+
const { config, logger } = options;
|
|
80
|
+
const provider = textProvider(options);
|
|
81
|
+
const images = imageProvider(options);
|
|
82
|
+
const embeddings = embeddingProvider(options);
|
|
83
|
+
let store;
|
|
84
|
+
let disposeVectors;
|
|
85
|
+
let vectorDriver = 'none';
|
|
86
|
+
if (embeddings !== undefined) {
|
|
87
|
+
try {
|
|
88
|
+
const selection = await createVectorRegistry({ db: options.db, logger }).select({
|
|
89
|
+
driver: config.vector.driver,
|
|
90
|
+
dimensions: embeddings.dimensions,
|
|
91
|
+
path: config.vector.path,
|
|
92
|
+
table: config.vector.table,
|
|
93
|
+
});
|
|
94
|
+
store = selection.instance;
|
|
95
|
+
vectorDriver = selection.driver;
|
|
96
|
+
disposeVectors = () => selection.dispose();
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
// Same posture as everywhere else here: a vector store that will not
|
|
100
|
+
// start takes semantic search with it and nothing else.
|
|
101
|
+
logger.warn('no vector store available, semantic search stays off', { error: String(error) });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const search = store === undefined || embeddings === undefined
|
|
105
|
+
? undefined
|
|
106
|
+
: createSemanticSearch({
|
|
107
|
+
store,
|
|
108
|
+
embeddings,
|
|
109
|
+
...(options.fullText === undefined ? {} : { fullText: options.fullText }),
|
|
110
|
+
});
|
|
111
|
+
const toolset = createAssistToolset({
|
|
112
|
+
site: { name: config.site.name, locales: config.site.locales },
|
|
113
|
+
...(provider === undefined ? {} : { provider }),
|
|
114
|
+
...(images === undefined ? {} : { imageProvider: images }),
|
|
115
|
+
...(search === undefined ? {} : { search }),
|
|
116
|
+
...(store === undefined || embeddings === undefined ? {} : { vectors: { store, embeddings } }),
|
|
117
|
+
});
|
|
118
|
+
const summary = toolset.available
|
|
119
|
+
? `assistant: ${toolset.tools.length} tool(s), text provider: ${provider?.name ?? 'none'}, image provider: ${images?.name ?? 'none'}, vector driver: ${vectorDriver}`
|
|
120
|
+
: 'assistant: off (no AI provider configured)';
|
|
121
|
+
return {
|
|
122
|
+
toolset,
|
|
123
|
+
...(search === undefined ? {} : { search }),
|
|
124
|
+
...(store === undefined || embeddings === undefined ? {} : { vectors: { store, embeddings } }),
|
|
125
|
+
summary,
|
|
126
|
+
dispose: async () => {
|
|
127
|
+
await disposeVectors?.();
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/** The chunk id an entry's single document occupies. One chunk per entry, for now — see the note in `reindexEntry`. */
|
|
132
|
+
function chunkIdFor(collection, id) {
|
|
133
|
+
return `${collection}:${id}:0`;
|
|
134
|
+
}
|
|
135
|
+
async function recordFor(options, document) {
|
|
136
|
+
const text = [document.title, document.body].filter((part) => part.length > 0).join('\n\n');
|
|
137
|
+
if (text.trim().length === 0)
|
|
138
|
+
return null;
|
|
139
|
+
const [vector] = await options.embeddings.embed([text]);
|
|
140
|
+
if (vector === undefined)
|
|
141
|
+
return null;
|
|
142
|
+
return {
|
|
143
|
+
siteId: options.siteId,
|
|
144
|
+
collection: options.collection.name,
|
|
145
|
+
entryId: document.id,
|
|
146
|
+
locale: document.locale,
|
|
147
|
+
status: document.status,
|
|
148
|
+
chunk: {
|
|
149
|
+
// One chunk per entry rather than `chunkDocument`'s real chunking.
|
|
150
|
+
// `chunkDocument` (L4) wants a block list with heading flags, which
|
|
151
|
+
// `searchDocumentFor` has already flattened away — feeding it the
|
|
152
|
+
// flattened string would produce chunk boundaries in the wrong places.
|
|
153
|
+
// Wiring real chunking means reading the entry's blocks directly, which
|
|
154
|
+
// is a piece of work of its own; one chunk per entry retrieves the right
|
|
155
|
+
// entries today and simply quotes more of them than it needs to.
|
|
156
|
+
id: chunkIdFor(options.collection.name, document.id),
|
|
157
|
+
documentId: document.id,
|
|
158
|
+
blockIds: [],
|
|
159
|
+
text,
|
|
160
|
+
hash: document.id,
|
|
161
|
+
},
|
|
162
|
+
vector,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
export function withVectorIndexing(store, options) {
|
|
166
|
+
async function reindex(id) {
|
|
167
|
+
try {
|
|
168
|
+
const published = await store.read(id, { state: 'published' });
|
|
169
|
+
if (published === null) {
|
|
170
|
+
await options.store.remove([chunkIdFor(options.collection.name, id)]);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const record = await recordFor(options, searchDocumentFor(options.collection, published));
|
|
174
|
+
if (record === null) {
|
|
175
|
+
await options.store.remove([chunkIdFor(options.collection.name, id)]);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
await options.store.upsert([record]);
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
options.onError?.(error);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
async function after(entry) {
|
|
185
|
+
await reindex(entry.id);
|
|
186
|
+
return entry;
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
...store,
|
|
190
|
+
create: async (input) => after(await store.create(input)),
|
|
191
|
+
update: async (id, input) => after(await store.update(id, input)),
|
|
192
|
+
publish: async (id, input) => after(await store.publish(id, input)),
|
|
193
|
+
unpublish: async (id, input) => after(await store.unpublish(id, input)),
|
|
194
|
+
restore: async (id, version, input) => after(await store.restore(id, version, input)),
|
|
195
|
+
delete: async (id) => {
|
|
196
|
+
const removed = await store.delete(id);
|
|
197
|
+
try {
|
|
198
|
+
await options.store.remove([chunkIdFor(options.collection.name, id)]);
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
options.onError?.(error);
|
|
202
|
+
}
|
|
203
|
+
return removed;
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
//# sourceMappingURL=assistant.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assistant.js","sourceRoot":"","sources":["../../src/commands/assistant.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type WebhookEventSender } from '@cogenta/channels';
|
|
2
|
+
import type { CogentaConfig, Logger } from '@cogenta/core';
|
|
3
|
+
import type { ContentLifecycleEvent } from '@cogenta/schema';
|
|
4
|
+
/**
|
|
5
|
+
* Turns the site's configuration into the one thing `assembleSite` needs: a
|
|
6
|
+
* function that publishes a content lifecycle event to the outside world
|
|
7
|
+
* (L14 task 1).
|
|
8
|
+
*
|
|
9
|
+
* The signed webhook channel has existed since L6 and nothing ever called it.
|
|
10
|
+
* This is the missing half — and deliberately only the wiring: the signature,
|
|
11
|
+
* the headers and the freshness semantics are `@cogenta/channels`'s, unchanged.
|
|
12
|
+
*/
|
|
13
|
+
export interface ContentWebhookOptions {
|
|
14
|
+
readonly webhooks: CogentaConfig['webhooks'];
|
|
15
|
+
/** The site's public base URL, used to turn an entry's path into an absolute URL. */
|
|
16
|
+
readonly siteUrl: string;
|
|
17
|
+
readonly logger: Logger;
|
|
18
|
+
/** Overridable for tests; defaults to the sender built from `webhooks`. */
|
|
19
|
+
readonly sender?: WebhookEventSender;
|
|
20
|
+
}
|
|
21
|
+
export interface ContentWebhookEmitter {
|
|
22
|
+
/** `null` when the site sends nothing — no endpoint, or no secret. */
|
|
23
|
+
readonly emit: ((event: ContentLifecycleEvent) => Promise<void>) | null;
|
|
24
|
+
/**
|
|
25
|
+
* The same signed channel, for the events that are not about content — the
|
|
26
|
+
* suspicious-activity alert of L14 task 4. Deliberately the *same* sender, so
|
|
27
|
+
* a site configures one set of endpoints and one secret, and there is never a
|
|
28
|
+
* second signing path to review.
|
|
29
|
+
*/
|
|
30
|
+
readonly send: ((event: string, data: Readonly<Record<string, unknown>>) => Promise<void>) | null;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Both halves are required, and the missing-secret case is a refusal rather
|
|
34
|
+
* than a downgrade: sending a *signed* webhook to nowhere is harmless, but
|
|
35
|
+
* sending an *unsigned* one is an unauthenticated instruction arriving at
|
|
36
|
+
* somebody else's server, which they have no way to tell from an attacker's.
|
|
37
|
+
* A site that configured endpoints without a secret is told so once, at
|
|
38
|
+
* startup, instead of silently never delivering.
|
|
39
|
+
*/
|
|
40
|
+
export declare function createContentWebhookEmitter(options: ContentWebhookOptions): ContentWebhookEmitter;
|
|
41
|
+
//# sourceMappingURL=content-webhooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-webhooks.d.ts","sourceRoot":"","sources":["../../src/commands/content-webhooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AACrF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAC1D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAA;AAE5D;;;;;;;;GAQG;AAEH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,UAAU,CAAC,CAAA;IAC5C,qFAAqF;IACrF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,2EAA2E;IAC3E,QAAQ,CAAC,MAAM,CAAC,EAAE,kBAAkB,CAAA;CACrC;AAED,MAAM,WAAW,qBAAqB;IACpC,sEAAsE;IACtE,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,EAAE,qBAAqB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAA;IACvE;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAA;CAClG;AAED;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,qBAAqB,GAAG,qBAAqB,CAqDjG"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { createWebhookEventSender } from '@cogenta/channels';
|
|
2
|
+
/**
|
|
3
|
+
* Both halves are required, and the missing-secret case is a refusal rather
|
|
4
|
+
* than a downgrade: sending a *signed* webhook to nowhere is harmless, but
|
|
5
|
+
* sending an *unsigned* one is an unauthenticated instruction arriving at
|
|
6
|
+
* somebody else's server, which they have no way to tell from an attacker's.
|
|
7
|
+
* A site that configured endpoints without a secret is told so once, at
|
|
8
|
+
* startup, instead of silently never delivering.
|
|
9
|
+
*/
|
|
10
|
+
export function createContentWebhookEmitter(options) {
|
|
11
|
+
const { endpoints, secret } = options.webhooks;
|
|
12
|
+
if (options.sender === undefined && endpoints.length === 0)
|
|
13
|
+
return { emit: null, send: null };
|
|
14
|
+
if (options.sender === undefined && secret === undefined) {
|
|
15
|
+
options.logger.warn('outbound webhooks are configured but disabled', {
|
|
16
|
+
endpoints: endpoints.length,
|
|
17
|
+
reason: 'COGENTA_WEBHOOK_SECRET is not set',
|
|
18
|
+
});
|
|
19
|
+
return { emit: null, send: null };
|
|
20
|
+
}
|
|
21
|
+
const sender = options.sender ?? createWebhookEventSender({ endpoints, secret: secret ?? '' });
|
|
22
|
+
/**
|
|
23
|
+
* Structured, and never silent: an outbound side effect that failed is
|
|
24
|
+
* exactly the thing an operator needs in the log, and there is no retry to
|
|
25
|
+
* make it right later (rule R1 — this deployment guarantees no durable
|
|
26
|
+
* worker).
|
|
27
|
+
*/
|
|
28
|
+
const send = async (event, data) => {
|
|
29
|
+
for (const result of await sender.send(event, data)) {
|
|
30
|
+
if (result.delivered) {
|
|
31
|
+
options.logger.info('webhook delivered', {
|
|
32
|
+
event,
|
|
33
|
+
url: result.url,
|
|
34
|
+
status: result.status,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
options.logger.error('webhook delivery failed', {
|
|
39
|
+
event,
|
|
40
|
+
url: result.url,
|
|
41
|
+
status: result.status,
|
|
42
|
+
error: result.error?.code ?? 'UNKNOWN',
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
return {
|
|
48
|
+
send,
|
|
49
|
+
emit: async (event) => {
|
|
50
|
+
const { path, ...rest } = event;
|
|
51
|
+
await send(event.event, {
|
|
52
|
+
...rest,
|
|
53
|
+
path,
|
|
54
|
+
// An absolute URL costs the receiver one less thing to know. Built from
|
|
55
|
+
// the same `site.url` the canonical tag and the sitemap already use.
|
|
56
|
+
url: path === null ? null : new URL(path, options.siteUrl).toString(),
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=content-webhooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-webhooks.js","sourceRoot":"","sources":["../../src/commands/content-webhooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAA2B,MAAM,mBAAmB,CAAA;AAmCrF;;;;;;;GAOG;AACH,MAAM,UAAU,2BAA2B,CAAC,OAA8B;IACxE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAA;IAE9C,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;IAE7F,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzD,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,+CAA+C,EAAE;YACnE,SAAS,EAAE,SAAS,CAAC,MAAM;YAC3B,MAAM,EAAE,mCAAmC;SAC5C,CAAC,CAAA;QACF,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;IACnC,CAAC;IAED,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,wBAAwB,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,IAAI,EAAE,EAAE,CAAC,CAAA;IAE9F;;;;;OAKG;IACH,MAAM,IAAI,GAAG,KAAK,EAAE,KAAa,EAAE,IAAuC,EAAiB,EAAE;QAC3F,KAAK,MAAM,MAAM,IAAI,MAAM,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC;YACpD,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;gBACrB,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,mBAAmB,EAAE;oBACvC,KAAK;oBACL,GAAG,EAAE,MAAM,CAAC,GAAG;oBACf,MAAM,EAAE,MAAM,CAAC,MAAM;iBACtB,CAAC,CAAA;YACJ,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,yBAAyB,EAAE;oBAC9C,KAAK;oBACL,GAAG,EAAE,MAAM,CAAC,GAAG;oBACf,MAAM,EAAE,MAAM,CAAC,MAAM;oBACrB,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,IAAI,IAAI,SAAS;iBACvC,CAAC,CAAA;YACJ,CAAC;QACH,CAAC;IACH,CAAC,CAAA;IAED,OAAO;QACL,IAAI;QACJ,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;YACpB,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,CAAA;YAC/B,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE;gBACtB,GAAG,IAAI;gBACP,IAAI;gBACJ,wEAAwE;gBACxE,qEAAqE;gBACrE,GAAG,EAAE,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE;aACtE,CAAC,CAAA;QACJ,CAAC;KACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import type { CogentaConfig } from '@cogenta/core';
|
|
3
|
+
/**
|
|
4
|
+
* CORS, security headers and cache-control for `cogenta serve` (L10 task 6).
|
|
5
|
+
*
|
|
6
|
+
* All three are applied in one place, before any route runs, for the reason
|
|
7
|
+
* that makes them worth having at all: a header added per route is a header
|
|
8
|
+
* some route forgets. `writeHead` is patched once per request so that every
|
|
9
|
+
* existing `res.writeHead(...)` call — and there are a dozen — picks them up
|
|
10
|
+
* without being rewritten, and so that a route added later cannot opt out by
|
|
11
|
+
* omission.
|
|
12
|
+
*/
|
|
13
|
+
export type SecurityConfig = CogentaConfig['security'];
|
|
14
|
+
/** Which cache-control a path class gets. */
|
|
15
|
+
export declare function cacheControlFor(pathname: string, security: SecurityConfig): string | null;
|
|
16
|
+
/**
|
|
17
|
+
* The CORS headers this request earns, or an empty object.
|
|
18
|
+
*
|
|
19
|
+
* The origin is echoed rather than reflected blindly: only an exact match in
|
|
20
|
+
* the configured list, or `*` when the list holds it. `Vary: Origin` goes out
|
|
21
|
+
* with any match, because a shared cache that stores one origin's response
|
|
22
|
+
* and serves it to another is the whole class of CORS cache-poisoning bugs.
|
|
23
|
+
*/
|
|
24
|
+
export declare function corsHeadersFor(origin: string | undefined, security: SecurityConfig): Readonly<Record<string, string>>;
|
|
25
|
+
/** The extra headers a preflight needs on top of the ones above. */
|
|
26
|
+
export declare function preflightHeadersFor(security: SecurityConfig): Readonly<Record<string, string>>;
|
|
27
|
+
/**
|
|
28
|
+
* Applies the headers to every response of this request, and answers a CORS
|
|
29
|
+
* preflight outright.
|
|
30
|
+
*
|
|
31
|
+
* Returns `true` when the request is finished — the caller must then do
|
|
32
|
+
* nothing else. Patching `writeHead` rather than calling `setHeader` up front
|
|
33
|
+
* is what makes this survive the routes that pass a header object of their
|
|
34
|
+
* own: `res.writeHead(200, { 'content-type': … })` replaces every header
|
|
35
|
+
* previously set, so setting them early would silently lose them on most of
|
|
36
|
+
* the routes in this file.
|
|
37
|
+
*/
|
|
38
|
+
export declare function applySecurity(req: IncomingMessage, res: ServerResponse, pathname: string, security: SecurityConfig): boolean;
|
|
39
|
+
//# sourceMappingURL=http-security.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-security.d.ts","sourceRoot":"","sources":["../../src/commands/http-security.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAuB,cAAc,EAAE,MAAM,WAAW,CAAA;AACrF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAElD;;;;;;;;;GASG;AAEH,MAAM,MAAM,cAAc,GAAG,aAAa,CAAC,UAAU,CAAC,CAAA;AAkBtD,6CAA6C;AAC7C,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,MAAM,GAAG,IAAI,CAkBzF;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,QAAQ,EAAE,cAAc,GACvB,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAYlC;AAED,oEAAoE;AACpE,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAM9F;AAyDD;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,cAAc,GACvB,OAAO,CAsCT"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Headers every response carries, whatever it is.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately short. `X-Content-Type-Options` stops a browser guessing that
|
|
5
|
+
* an uploaded file is a script; `X-Frame-Options` and `Referrer-Policy` are
|
|
6
|
+
* the two others with no plausible downside and a real failure mode without
|
|
7
|
+
* them. Anything beyond that (`Permissions-Policy`, COEP/COOP) breaks real
|
|
8
|
+
* pages depending on what a site embeds, so it belongs in the site's own
|
|
9
|
+
* `csp` string rather than in a default nobody chose.
|
|
10
|
+
*/
|
|
11
|
+
const ALWAYS = Object.freeze({
|
|
12
|
+
'x-content-type-options': 'nosniff',
|
|
13
|
+
'x-frame-options': 'SAMEORIGIN',
|
|
14
|
+
'referrer-policy': 'strict-origin-when-cross-origin',
|
|
15
|
+
});
|
|
16
|
+
/** Which cache-control a path class gets. */
|
|
17
|
+
export function cacheControlFor(pathname, security) {
|
|
18
|
+
// Never store an API response: they are per-actor by construction — the same
|
|
19
|
+
// URL answers differently for an editor and for a stranger — and a shared
|
|
20
|
+
// cache that keeps one is a permission leak, not a performance win.
|
|
21
|
+
if (pathname.startsWith('/api/'))
|
|
22
|
+
return 'no-store';
|
|
23
|
+
// The admin shell is a signed-in application. Its hashed assets could be
|
|
24
|
+
// cached forever, but `index.html` must not be, and telling them apart here
|
|
25
|
+
// would duplicate Vite's naming convention in a second place.
|
|
26
|
+
if (pathname === '/admin' || pathname.startsWith('/admin/'))
|
|
27
|
+
return 'no-store';
|
|
28
|
+
// Images and sitemaps set their own, longer, values at their route.
|
|
29
|
+
if (pathname === '/_image' || pathname === '/robots.txt')
|
|
30
|
+
return null;
|
|
31
|
+
if (pathname === '/search')
|
|
32
|
+
return null;
|
|
33
|
+
// A public page: cacheable, briefly. `must-revalidate` rather than a long
|
|
34
|
+
// max-age because publishing is supposed to be visible immediately, and a
|
|
35
|
+
// CMS whose edits appear ten minutes later is a CMS people stop trusting.
|
|
36
|
+
return security.pageMaxAge === 0
|
|
37
|
+
? 'no-store'
|
|
38
|
+
: `public, max-age=0, s-maxage=${security.pageMaxAge}, must-revalidate`;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The CORS headers this request earns, or an empty object.
|
|
42
|
+
*
|
|
43
|
+
* The origin is echoed rather than reflected blindly: only an exact match in
|
|
44
|
+
* the configured list, or `*` when the list holds it. `Vary: Origin` goes out
|
|
45
|
+
* with any match, because a shared cache that stores one origin's response
|
|
46
|
+
* and serves it to another is the whole class of CORS cache-poisoning bugs.
|
|
47
|
+
*/
|
|
48
|
+
export function corsHeadersFor(origin, security) {
|
|
49
|
+
const { cors } = security;
|
|
50
|
+
if (cors.origins.length === 0 || origin === undefined)
|
|
51
|
+
return {};
|
|
52
|
+
const wildcard = cors.origins.includes('*');
|
|
53
|
+
if (!wildcard && !cors.origins.includes(origin))
|
|
54
|
+
return {};
|
|
55
|
+
return {
|
|
56
|
+
'access-control-allow-origin': wildcard && !cors.credentials ? '*' : origin,
|
|
57
|
+
vary: 'Origin',
|
|
58
|
+
...(cors.credentials ? { 'access-control-allow-credentials': 'true' } : {}),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** The extra headers a preflight needs on top of the ones above. */
|
|
62
|
+
export function preflightHeadersFor(security) {
|
|
63
|
+
return {
|
|
64
|
+
'access-control-allow-methods': security.cors.methods.join(', '),
|
|
65
|
+
'access-control-allow-headers': security.cors.headers.join(', '),
|
|
66
|
+
'access-control-max-age': String(security.cors.maxAge),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function baseHeadersFor(req, pathname, security) {
|
|
70
|
+
const headers = { ...ALWAYS };
|
|
71
|
+
if (security.csp !== undefined && security.csp !== false) {
|
|
72
|
+
headers['content-security-policy'] = security.csp;
|
|
73
|
+
}
|
|
74
|
+
// Only over TLS. Sent on a plain HTTP response the header is ignored by
|
|
75
|
+
// browsers anyway, and sending it locally is how somebody pins
|
|
76
|
+
// `localhost` to HTTPS and cannot develop for the next year.
|
|
77
|
+
if (security.hstsMaxAge > 0 && isSecure(req)) {
|
|
78
|
+
headers['strict-transport-security'] = `max-age=${security.hstsMaxAge}${security.hstsIncludeSubDomains ? '; includeSubDomains' : ''}`;
|
|
79
|
+
}
|
|
80
|
+
const origin = headerOf(req, 'origin');
|
|
81
|
+
Object.assign(headers, corsHeadersFor(origin, security));
|
|
82
|
+
// A page render is per-actor: `renderRequestedPage` passes the requesting
|
|
83
|
+
// actor's context down, so an editor's `GET /blog/embargoed` returns the
|
|
84
|
+
// draft. `public, s-maxage=…` is precisely the pair RFC 9111 §3.5 says
|
|
85
|
+
// re-authorises a *shared* cache to store a response to a request carrying
|
|
86
|
+
// `Authorization` — a CDN in front of the site would then serve that draft
|
|
87
|
+
// to everyone for a minute. Anything sent with credentials is private and
|
|
88
|
+
// unstorable, whatever class its path belongs to. Found by the security
|
|
89
|
+
// review of L10 task 6.
|
|
90
|
+
const cacheControl = hasCredentials(req)
|
|
91
|
+
? 'private, no-store'
|
|
92
|
+
: cacheControlFor(pathname, security);
|
|
93
|
+
if (cacheControl !== null)
|
|
94
|
+
headers['cache-control'] = cacheControl;
|
|
95
|
+
return headers;
|
|
96
|
+
}
|
|
97
|
+
/** True when the request carries something that makes its answer actor-specific. */
|
|
98
|
+
function hasCredentials(req) {
|
|
99
|
+
return headerOf(req, 'authorization') !== undefined || headerOf(req, 'cookie') !== undefined;
|
|
100
|
+
}
|
|
101
|
+
function headerOf(req, name) {
|
|
102
|
+
const value = req.headers[name];
|
|
103
|
+
return Array.isArray(value) ? value[0] : value;
|
|
104
|
+
}
|
|
105
|
+
/** True behind a TLS socket, or behind a proxy that says it terminated one. */
|
|
106
|
+
function isSecure(req) {
|
|
107
|
+
if ('encrypted' in req.socket && req.socket.encrypted === true)
|
|
108
|
+
return true;
|
|
109
|
+
return headerOf(req, 'x-forwarded-proto')?.split(',')[0]?.trim() === 'https';
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Applies the headers to every response of this request, and answers a CORS
|
|
113
|
+
* preflight outright.
|
|
114
|
+
*
|
|
115
|
+
* Returns `true` when the request is finished — the caller must then do
|
|
116
|
+
* nothing else. Patching `writeHead` rather than calling `setHeader` up front
|
|
117
|
+
* is what makes this survive the routes that pass a header object of their
|
|
118
|
+
* own: `res.writeHead(200, { 'content-type': … })` replaces every header
|
|
119
|
+
* previously set, so setting them early would silently lose them on most of
|
|
120
|
+
* the routes in this file.
|
|
121
|
+
*/
|
|
122
|
+
export function applySecurity(req, res, pathname, security) {
|
|
123
|
+
const base = baseHeadersFor(req, pathname, security);
|
|
124
|
+
const original = res.writeHead.bind(res);
|
|
125
|
+
// Both of Node's overloads are accepted, so no existing call site changes.
|
|
126
|
+
// An array-of-raw-headers form exists too and is not used anywhere here; it
|
|
127
|
+
// is passed through untouched rather than half-merged.
|
|
128
|
+
res.writeHead = ((status, reasonOrHeaders, maybeHeaders) => {
|
|
129
|
+
const supplied = typeof reasonOrHeaders === 'string' ? maybeHeaders : (reasonOrHeaders ?? maybeHeaders);
|
|
130
|
+
if (Array.isArray(supplied)) {
|
|
131
|
+
return typeof reasonOrHeaders === 'string'
|
|
132
|
+
? original(status, reasonOrHeaders, supplied)
|
|
133
|
+
: original(status, supplied);
|
|
134
|
+
}
|
|
135
|
+
const merged = { ...base, ...(supplied ?? {}) };
|
|
136
|
+
return typeof reasonOrHeaders === 'string'
|
|
137
|
+
? original(status, reasonOrHeaders, merged)
|
|
138
|
+
: original(status, merged);
|
|
139
|
+
});
|
|
140
|
+
if (req.method === 'OPTIONS') {
|
|
141
|
+
const origin = headerOf(req, 'origin');
|
|
142
|
+
const allowed = corsHeadersFor(origin, security);
|
|
143
|
+
// A preflight for an origin that is not allowed gets a plain 204 with no
|
|
144
|
+
// CORS headers: the browser then refuses the real request on its own. A
|
|
145
|
+
// 403 would be no more secure and would look like a server fault in the
|
|
146
|
+
// console instead of the policy decision it is.
|
|
147
|
+
res.writeHead(204, Object.keys(allowed).length === 0 ? {} : preflightHeadersFor(security));
|
|
148
|
+
res.end();
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=http-security.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-security.js","sourceRoot":"","sources":["../../src/commands/http-security.ts"],"names":[],"mappings":"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"}
|