@we8/cloudflare 0.1.2 → 0.2.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/README.md +99 -6
- package/dist/access.d.ts +129 -0
- package/dist/access.d.ts.map +1 -0
- package/dist/access.js +211 -0
- package/dist/access.js.map +1 -0
- package/dist/admin-auth.d.ts +42 -0
- package/dist/admin-auth.d.ts.map +1 -0
- package/dist/admin-auth.js +19 -0
- package/dist/admin-auth.js.map +1 -0
- package/dist/cli-args.d.ts +8 -2
- package/dist/cli-args.d.ts.map +1 -1
- package/dist/cli-args.js +28 -4
- package/dist/cli-args.js.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +89 -6
- package/dist/cli.js.map +1 -1
- package/dist/doctor.d.ts +79 -3
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +250 -15
- package/dist/doctor.js.map +1 -1
- package/dist/index.d.ts +21 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -9
- package/dist/index.js.map +1 -1
- package/dist/jwt.d.ts +103 -0
- package/dist/jwt.d.ts.map +1 -0
- package/dist/jwt.js +265 -0
- package/dist/jwt.js.map +1 -0
- package/dist/perimeter.d.ts +49 -0
- package/dist/perimeter.d.ts.map +1 -0
- package/dist/perimeter.js +54 -0
- package/dist/perimeter.js.map +1 -0
- package/dist/project.d.ts +7 -0
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +31 -0
- package/dist/project.js.map +1 -1
- package/dist/skill-command.d.ts +48 -0
- package/dist/skill-command.d.ts.map +1 -0
- package/dist/skill-command.js +122 -0
- package/dist/skill-command.js.map +1 -0
- package/dist/skill.d.ts +51 -0
- package/dist/skill.d.ts.map +1 -0
- package/dist/skill.js +229 -0
- package/dist/skill.js.map +1 -0
- package/dist/wrangler-config.d.ts +64 -1
- package/dist/wrangler-config.d.ts.map +1 -1
- package/dist/wrangler-config.js +133 -21
- package/dist/wrangler-config.js.map +1 -1
- package/package.json +2 -2
package/dist/jwt.js
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RS256 verification for a Cloudflare Access assertion, on WebCrypto.
|
|
3
|
+
*
|
|
4
|
+
* Access signs its assertion with the team's own key and publishes the public
|
|
5
|
+
* half as a JWKS at `https://<team>.cloudflareaccess.com/cdn-cgi/access/certs`.
|
|
6
|
+
* Verifying it is four checks: the signature against that key, the issuer, the
|
|
7
|
+
* audience, and the clock. All four have to pass, and a token that fails any of
|
|
8
|
+
* them is refused rather than downgraded.
|
|
9
|
+
*
|
|
10
|
+
* Written against WebCrypto rather than pulled from a JWT library on purpose.
|
|
11
|
+
* `@we8/cloudflare` has no runtime dependencies, this runs on Workers where
|
|
12
|
+
* `crypto.subtle` is the platform, and the whole verification is small enough
|
|
13
|
+
* to read in one sitting, which is the property that matters most in the file
|
|
14
|
+
* that decides who administers a site.
|
|
15
|
+
*
|
|
16
|
+
* Everything here fails CLOSED. An unreadable token, an unknown key, an
|
|
17
|
+
* unreachable JWKS endpoint, and an algorithm this file does not implement all
|
|
18
|
+
* produce a refusal, never an admission.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* The one signature algorithm accepted.
|
|
22
|
+
*
|
|
23
|
+
* Pinned rather than read from the token's own header, because "which
|
|
24
|
+
* algorithm" being a field the attacker controls is the classic JWT forgery:
|
|
25
|
+
* `alg: none` and an HS256 token signed with the public key are both defeated
|
|
26
|
+
* by refusing to look at anything but RS256.
|
|
27
|
+
*/
|
|
28
|
+
export const ACCESS_JWT_ALGORITHM = 'RS256';
|
|
29
|
+
/**
|
|
30
|
+
* How long a fetched key set is used before it is fetched again. Access
|
|
31
|
+
* rotates keys, and an hour is short enough to pick a rotation up quickly and
|
|
32
|
+
* long enough that the endpoint is not in the path of every admin request.
|
|
33
|
+
*/
|
|
34
|
+
export const JWKS_TTL_MS = 60 * 60 * 1000;
|
|
35
|
+
/**
|
|
36
|
+
* The shortest gap between two fetches provoked by an unknown key id. Without
|
|
37
|
+
* it, a stream of forged tokens carrying invented `kid` values would turn into
|
|
38
|
+
* a stream of requests to Cloudflare.
|
|
39
|
+
*/
|
|
40
|
+
export const JWKS_REFETCH_FLOOR_MS = 60 * 1000;
|
|
41
|
+
/** A few seconds of tolerance, because two machines never agree exactly. */
|
|
42
|
+
export const CLOCK_SKEW_SECONDS = 60;
|
|
43
|
+
/**
|
|
44
|
+
* Module scope, deliberately: a Worker isolate serves many requests, and the
|
|
45
|
+
* key set is public, immutable while it lives, and identical for every request
|
|
46
|
+
* against the same team. This is the cache the doc comment above promises.
|
|
47
|
+
*/
|
|
48
|
+
const jwksCache = new Map();
|
|
49
|
+
/** Drops every cached key set. Tests use it; nothing in the request path does. */
|
|
50
|
+
export function clearJwksCache() {
|
|
51
|
+
jwksCache.clear();
|
|
52
|
+
}
|
|
53
|
+
/** True for a segment that is legal base64url. `atob` is laxer than that. */
|
|
54
|
+
function isBase64Url(value) {
|
|
55
|
+
return value.length > 0 && /^[A-Za-z0-9_-]+$/.test(value);
|
|
56
|
+
}
|
|
57
|
+
/** base64url to bytes, or null when the input is not base64url at all. */
|
|
58
|
+
export function base64UrlToBytes(value) {
|
|
59
|
+
if (!isBase64Url(value))
|
|
60
|
+
return null;
|
|
61
|
+
const padded = value.replace(/-/g, '+').replace(/_/g, '/');
|
|
62
|
+
const remainder = padded.length % 4;
|
|
63
|
+
if (remainder === 1)
|
|
64
|
+
return null;
|
|
65
|
+
const full = remainder === 0 ? padded : padded + '='.repeat(4 - remainder);
|
|
66
|
+
try {
|
|
67
|
+
const binary = atob(full);
|
|
68
|
+
const bytes = new Uint8Array(binary.length);
|
|
69
|
+
for (let i = 0; i < binary.length; i += 1)
|
|
70
|
+
bytes[i] = binary.charCodeAt(i);
|
|
71
|
+
return bytes;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** A base64url JSON segment as an object, or null when it is neither. */
|
|
78
|
+
function decodeJsonSegment(segment) {
|
|
79
|
+
const bytes = base64UrlToBytes(segment);
|
|
80
|
+
if (!bytes)
|
|
81
|
+
return null;
|
|
82
|
+
try {
|
|
83
|
+
const parsed = JSON.parse(new TextDecoder().decode(bytes));
|
|
84
|
+
return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)
|
|
85
|
+
? parsed
|
|
86
|
+
: null;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Imports the RSA keys out of a JWKS document. A key this file cannot use (a
|
|
94
|
+
* different type, a different algorithm, a malformed modulus) is skipped
|
|
95
|
+
* rather than fatal: a key set is allowed to carry more than we understand.
|
|
96
|
+
*/
|
|
97
|
+
async function importJwks(document) {
|
|
98
|
+
const keys = new Map();
|
|
99
|
+
const list = typeof document === 'object' && document !== null
|
|
100
|
+
? document.keys
|
|
101
|
+
: undefined;
|
|
102
|
+
if (!Array.isArray(list))
|
|
103
|
+
return keys;
|
|
104
|
+
for (const candidate of list) {
|
|
105
|
+
if (typeof candidate !== 'object' || candidate === null)
|
|
106
|
+
continue;
|
|
107
|
+
const { kid, kty, alg, n, e } = candidate;
|
|
108
|
+
if (typeof kid !== 'string' || kid.length === 0)
|
|
109
|
+
continue;
|
|
110
|
+
if (kty !== 'RSA')
|
|
111
|
+
continue;
|
|
112
|
+
if (alg !== undefined && alg !== ACCESS_JWT_ALGORITHM)
|
|
113
|
+
continue;
|
|
114
|
+
if (typeof n !== 'string' || typeof e !== 'string')
|
|
115
|
+
continue;
|
|
116
|
+
try {
|
|
117
|
+
const key = await crypto.subtle.importKey('jwk', { kty: 'RSA', alg: ACCESS_JWT_ALGORITHM, use: 'sig', n, e, ext: true }, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, false, ['verify']);
|
|
118
|
+
keys.set(kid, key);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
// A key that will not import cannot verify anything. Skip it.
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return keys;
|
|
125
|
+
}
|
|
126
|
+
async function fetchJwks(entry, certsUrl, fetchImpl, now) {
|
|
127
|
+
entry.attemptedAt = now;
|
|
128
|
+
try {
|
|
129
|
+
const response = await fetchImpl(certsUrl, { headers: { Accept: 'application/json' } });
|
|
130
|
+
if (!response.ok) {
|
|
131
|
+
console.error(`[access] ${certsUrl} answered ${String(response.status)}`);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const document = await response.json();
|
|
135
|
+
const keys = await importJwks(document);
|
|
136
|
+
if (keys.size === 0) {
|
|
137
|
+
console.error(`[access] ${certsUrl} carried no usable RS256 keys`);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
entry.keys = keys;
|
|
141
|
+
entry.fetchedAt = now;
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
console.error(`[access] could not read ${certsUrl}: ${error instanceof Error ? error.message : String(error)}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* One fetch of the key set, with concurrent callers waiting on the same round
|
|
149
|
+
* trip rather than each opening their own.
|
|
150
|
+
*/
|
|
151
|
+
async function loadJwks(certsUrl, fetchImpl, now) {
|
|
152
|
+
const existing = jwksCache.get(certsUrl);
|
|
153
|
+
if (existing?.inflight) {
|
|
154
|
+
await existing.inflight;
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const entry = existing ?? {
|
|
158
|
+
keys: new Map(),
|
|
159
|
+
fetchedAt: 0,
|
|
160
|
+
attemptedAt: 0,
|
|
161
|
+
inflight: null,
|
|
162
|
+
};
|
|
163
|
+
jwksCache.set(certsUrl, entry);
|
|
164
|
+
const inflight = fetchJwks(entry, certsUrl, fetchImpl, now);
|
|
165
|
+
entry.inflight = inflight;
|
|
166
|
+
try {
|
|
167
|
+
await inflight;
|
|
168
|
+
}
|
|
169
|
+
finally {
|
|
170
|
+
entry.inflight = null;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* The signing key for one key id, fetching the team's key set when the cached
|
|
175
|
+
* one cannot answer.
|
|
176
|
+
*
|
|
177
|
+
* A cached key stays usable while the set is fresh. Past that, or for a key id
|
|
178
|
+
* the set does not carry (which is what a rotation looks like), the endpoint is
|
|
179
|
+
* read again; a fetch that fails falls back to whatever was cached, because a
|
|
180
|
+
* transient outage at Cloudflare should not lock an operator out of their own
|
|
181
|
+
* admin, and refuses outright when there is nothing to fall back to.
|
|
182
|
+
*/
|
|
183
|
+
export async function accessSigningKey(certsUrl, kid, now, fetchImpl = fetch) {
|
|
184
|
+
const cached = jwksCache.get(certsUrl);
|
|
185
|
+
const fresh = cached !== undefined && now - cached.fetchedAt < JWKS_TTL_MS;
|
|
186
|
+
if (fresh && cached.keys.has(kid))
|
|
187
|
+
return cached.keys.get(kid);
|
|
188
|
+
// An unknown kid against a fresh set is either a rotation or a forgery. One
|
|
189
|
+
// refetch per floor tells the two apart without letting the second one
|
|
190
|
+
// become an outbound request per request.
|
|
191
|
+
if (cached && cached.inflight === null && now - cached.attemptedAt < JWKS_REFETCH_FLOOR_MS) {
|
|
192
|
+
return cached.keys.get(kid) ?? null;
|
|
193
|
+
}
|
|
194
|
+
await loadJwks(certsUrl, fetchImpl, now);
|
|
195
|
+
return jwksCache.get(certsUrl)?.keys.get(kid) ?? null;
|
|
196
|
+
}
|
|
197
|
+
/** True when `aud` (a string or an array, per the spec) contains `audience`. */
|
|
198
|
+
export function audienceMatches(aud, audience) {
|
|
199
|
+
if (typeof aud === 'string')
|
|
200
|
+
return aud === audience;
|
|
201
|
+
if (Array.isArray(aud))
|
|
202
|
+
return aud.some((entry) => entry === audience);
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Verifies an Access assertion end to end.
|
|
207
|
+
*
|
|
208
|
+
* Order matters only for what a log line says; every check is required, and the
|
|
209
|
+
* first failure is the reported one.
|
|
210
|
+
*/
|
|
211
|
+
export async function verifyAccessJwt(options) {
|
|
212
|
+
const { token, certsUrl, issuer, audience } = options;
|
|
213
|
+
const nowSeconds = options.now ?? Math.floor(Date.now() / 1000);
|
|
214
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
215
|
+
const parts = token.split('.');
|
|
216
|
+
if (parts.length !== 3)
|
|
217
|
+
return { ok: false, reason: 'not a three-part JWT' };
|
|
218
|
+
const [encodedHeader, encodedPayload, encodedSignature] = parts;
|
|
219
|
+
const header = decodeJsonSegment(encodedHeader);
|
|
220
|
+
if (!header)
|
|
221
|
+
return { ok: false, reason: 'the header is not base64url JSON' };
|
|
222
|
+
if (header['alg'] !== ACCESS_JWT_ALGORITHM) {
|
|
223
|
+
return { ok: false, reason: `alg is ${JSON.stringify(header['alg'])}, not ${ACCESS_JWT_ALGORITHM}` };
|
|
224
|
+
}
|
|
225
|
+
const kid = header['kid'];
|
|
226
|
+
if (typeof kid !== 'string' || kid.length === 0) {
|
|
227
|
+
return { ok: false, reason: 'the header names no key id' };
|
|
228
|
+
}
|
|
229
|
+
const payload = decodeJsonSegment(encodedPayload);
|
|
230
|
+
if (!payload)
|
|
231
|
+
return { ok: false, reason: 'the payload is not base64url JSON' };
|
|
232
|
+
const signature = base64UrlToBytes(encodedSignature);
|
|
233
|
+
if (!signature)
|
|
234
|
+
return { ok: false, reason: 'the signature is not base64url' };
|
|
235
|
+
const key = await accessSigningKey(certsUrl, kid, nowSeconds * 1000, fetchImpl);
|
|
236
|
+
if (!key)
|
|
237
|
+
return { ok: false, reason: `no published key for kid ${kid}` };
|
|
238
|
+
const signed = new TextEncoder().encode(`${encodedHeader}.${encodedPayload}`);
|
|
239
|
+
let verified = false;
|
|
240
|
+
try {
|
|
241
|
+
verified = await crypto.subtle.verify({ name: 'RSASSA-PKCS1-v1_5' }, key, signature, signed);
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
return { ok: false, reason: 'the signature could not be checked' };
|
|
245
|
+
}
|
|
246
|
+
if (!verified)
|
|
247
|
+
return { ok: false, reason: 'the signature does not match' };
|
|
248
|
+
if (payload['iss'] !== issuer) {
|
|
249
|
+
return { ok: false, reason: `iss is ${JSON.stringify(payload['iss'])}, not ${issuer}` };
|
|
250
|
+
}
|
|
251
|
+
if (!audienceMatches(payload['aud'], audience)) {
|
|
252
|
+
return { ok: false, reason: 'aud does not carry this application' };
|
|
253
|
+
}
|
|
254
|
+
const exp = payload['exp'];
|
|
255
|
+
if (typeof exp !== 'number')
|
|
256
|
+
return { ok: false, reason: 'no exp claim' };
|
|
257
|
+
if (exp + CLOCK_SKEW_SECONDS < nowSeconds)
|
|
258
|
+
return { ok: false, reason: 'expired' };
|
|
259
|
+
const nbf = payload['nbf'];
|
|
260
|
+
if (typeof nbf === 'number' && nbf - CLOCK_SKEW_SECONDS > nowSeconds) {
|
|
261
|
+
return { ok: false, reason: 'not valid yet' };
|
|
262
|
+
}
|
|
263
|
+
return { ok: true, claims: payload };
|
|
264
|
+
}
|
|
265
|
+
//# sourceMappingURL=jwt.js.map
|
package/dist/jwt.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwt.js","sourceRoot":"","sources":["../src/jwt.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Perimeter mode: this worker checks nothing, and whatever reached it owns the
|
|
3
|
+
* site.
|
|
4
|
+
*
|
|
5
|
+
* DANGEROUS BY DESIGN, and only correct when something in front of the worker
|
|
6
|
+
* is doing the authenticating: a Cloudflare Access application with no role
|
|
7
|
+
* mapping to give this worker, a VPN, a company gateway, an on-premises proxy.
|
|
8
|
+
* Every request that arrives at `/v1/admin/*` resolves to one owner identity,
|
|
9
|
+
* so if the perimeter ever opens (an Access policy edited, a hostname exposed
|
|
10
|
+
* outside the tunnel, a route pointed straight at the Worker's workers.dev
|
|
11
|
+
* subdomain), the whole admin API opens with it: content, settings, API keys,
|
|
12
|
+
* and the form inbox, which is other people's personal data.
|
|
13
|
+
*
|
|
14
|
+
* import cms, { registerAdminAuth } from '@we8/cms';
|
|
15
|
+
* import { perimeterAdminAuth } from '@we8/cloudflare';
|
|
16
|
+
*
|
|
17
|
+
* registerAdminAuth(perimeterAdminAuth({ acknowledged: true }));
|
|
18
|
+
* export default cms;
|
|
19
|
+
*
|
|
20
|
+
* `acknowledged: true` is a deliberate typing exercise, not a feature flag. The
|
|
21
|
+
* doctor fails `--remote` without it, so nobody deploys this mode by copying a
|
|
22
|
+
* snippet without reading what it does, and the acknowledgement sits in the
|
|
23
|
+
* same line of code as the choice, where a reviewer sees both at once.
|
|
24
|
+
*
|
|
25
|
+
* A Worker is reachable at `<name>.<subdomain>.workers.dev` unless that route
|
|
26
|
+
* is disabled. In perimeter mode, disabling it is part of the perimeter.
|
|
27
|
+
*/
|
|
28
|
+
import type { AdminAuthProvider } from './admin-auth.js';
|
|
29
|
+
/** What `GET /v1/auth/mode` reports this provider as. */
|
|
30
|
+
export declare const PERIMETER_PROVIDER_NAME = "perimeter";
|
|
31
|
+
/**
|
|
32
|
+
* The identity every request gets. A fixed, obviously synthetic id rather than
|
|
33
|
+
* an invented per-request one: publish history in this mode says "the
|
|
34
|
+
* perimeter" and means it, instead of implying a person nobody can name.
|
|
35
|
+
*/
|
|
36
|
+
export declare const PERIMETER_USER_ID = "perimeter-admin";
|
|
37
|
+
export interface PerimeterOptions {
|
|
38
|
+
/**
|
|
39
|
+
* Confirms the operator knows every request that reaches this worker is the
|
|
40
|
+
* owner. `we8-cloudflare doctor --remote` fails without it.
|
|
41
|
+
*/
|
|
42
|
+
acknowledged?: boolean;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The provider, ready to register. Resolving is unconditional: there is
|
|
46
|
+
* nothing to check, which is the entire point and the entire risk.
|
|
47
|
+
*/
|
|
48
|
+
export declare function perimeterAdminAuth(options?: PerimeterOptions): AdminAuthProvider;
|
|
49
|
+
//# sourceMappingURL=perimeter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"perimeter.d.ts","sourceRoot":"","sources":["../src/perimeter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAiB,MAAM,iBAAiB,CAAC;AAExE,yDAAyD;AACzD,eAAO,MAAM,uBAAuB,cAAc,CAAC;AAEnD;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,oBAAoB,CAAC;AAEnD,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,gBAAqB,GAAG,iBAAiB,CAkBpF"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Perimeter mode: this worker checks nothing, and whatever reached it owns the
|
|
3
|
+
* site.
|
|
4
|
+
*
|
|
5
|
+
* DANGEROUS BY DESIGN, and only correct when something in front of the worker
|
|
6
|
+
* is doing the authenticating: a Cloudflare Access application with no role
|
|
7
|
+
* mapping to give this worker, a VPN, a company gateway, an on-premises proxy.
|
|
8
|
+
* Every request that arrives at `/v1/admin/*` resolves to one owner identity,
|
|
9
|
+
* so if the perimeter ever opens (an Access policy edited, a hostname exposed
|
|
10
|
+
* outside the tunnel, a route pointed straight at the Worker's workers.dev
|
|
11
|
+
* subdomain), the whole admin API opens with it: content, settings, API keys,
|
|
12
|
+
* and the form inbox, which is other people's personal data.
|
|
13
|
+
*
|
|
14
|
+
* import cms, { registerAdminAuth } from '@we8/cms';
|
|
15
|
+
* import { perimeterAdminAuth } from '@we8/cloudflare';
|
|
16
|
+
*
|
|
17
|
+
* registerAdminAuth(perimeterAdminAuth({ acknowledged: true }));
|
|
18
|
+
* export default cms;
|
|
19
|
+
*
|
|
20
|
+
* `acknowledged: true` is a deliberate typing exercise, not a feature flag. The
|
|
21
|
+
* doctor fails `--remote` without it, so nobody deploys this mode by copying a
|
|
22
|
+
* snippet without reading what it does, and the acknowledgement sits in the
|
|
23
|
+
* same line of code as the choice, where a reviewer sees both at once.
|
|
24
|
+
*
|
|
25
|
+
* A Worker is reachable at `<name>.<subdomain>.workers.dev` unless that route
|
|
26
|
+
* is disabled. In perimeter mode, disabling it is part of the perimeter.
|
|
27
|
+
*/
|
|
28
|
+
/** What `GET /v1/auth/mode` reports this provider as. */
|
|
29
|
+
export const PERIMETER_PROVIDER_NAME = 'perimeter';
|
|
30
|
+
/**
|
|
31
|
+
* The identity every request gets. A fixed, obviously synthetic id rather than
|
|
32
|
+
* an invented per-request one: publish history in this mode says "the
|
|
33
|
+
* perimeter" and means it, instead of implying a person nobody can name.
|
|
34
|
+
*/
|
|
35
|
+
export const PERIMETER_USER_ID = 'perimeter-admin';
|
|
36
|
+
/**
|
|
37
|
+
* The provider, ready to register. Resolving is unconditional: there is
|
|
38
|
+
* nothing to check, which is the entire point and the entire risk.
|
|
39
|
+
*/
|
|
40
|
+
export function perimeterAdminAuth(options = {}) {
|
|
41
|
+
if (options.acknowledged !== true) {
|
|
42
|
+
console.warn('[perimeter] admin auth is perimeter mode and it is NOT acknowledged: every request that reaches this worker is the owner. Register perimeterAdminAuth({ acknowledged: true }) once that is what you mean.');
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
console.warn('[perimeter] admin auth is perimeter mode: every request that reaches this worker is the owner.');
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
name: PERIMETER_PROVIDER_NAME,
|
|
49
|
+
resolve(_request, _env) {
|
|
50
|
+
return Promise.resolve({ userId: PERIMETER_USER_ID, role: 'owner', email: null });
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=perimeter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"perimeter.js","sourceRoot":"","sources":["../src/perimeter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAIH,yDAAyD;AACzD,MAAM,CAAC,MAAM,uBAAuB,GAAG,WAAW,CAAC;AAEnD;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,iBAAiB,CAAC;AAUnD;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,UAA4B,EAAE;IAC/D,IAAI,OAAO,CAAC,YAAY,KAAK,IAAI,EAAE,CAAC;QAClC,OAAO,CAAC,IAAI,CACV,2MAA2M,CAC5M,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,IAAI,CACV,gGAAgG,CACjG,CAAC;IACJ,CAAC;IAED,OAAO;QACL,IAAI,EAAE,uBAAuB;QAE7B,OAAO,CAAC,QAAiB,EAAE,IAAa;YACtC,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,iBAAiB,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACpF,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/dist/project.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ import { type DoctorFacts, type DoctorTarget } from './doctor.js';
|
|
|
8
8
|
export declare const CONFIG_FILENAMES: readonly ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
|
|
9
9
|
/** Where the seed lives inside an installed project. */
|
|
10
10
|
export declare const SEED_RELATIVE_PATH = "node_modules/@we8/cms/seed/local.sql";
|
|
11
|
+
/** Where an installed `@we8/auth` announces itself. */
|
|
12
|
+
export declare const AUTH_PACKAGE_RELATIVE_PATH = "node_modules/@we8/auth/package.json";
|
|
11
13
|
/**
|
|
12
14
|
* Parses `.dev.vars` for the names it sets. Only names: a doctor that printed
|
|
13
15
|
* the value of a secret to prove it exists would be the bug it is looking for.
|
|
@@ -21,6 +23,11 @@ export declare function findConfigPath(projectDir: string): string | null;
|
|
|
21
23
|
* deliberately never touches.
|
|
22
24
|
*/
|
|
23
25
|
export declare function gatherFacts(projectDir: string, target: DoctorTarget, remoteSecrets?: string[] | null): DoctorFacts;
|
|
26
|
+
/**
|
|
27
|
+
* The source of the worker entry named by `main`, or null when there is no
|
|
28
|
+
* readable one. The doctor reads the composed admin auth provider out of it.
|
|
29
|
+
*/
|
|
30
|
+
export declare function readWorkerEntry(projectDir: string, config: unknown): string | null;
|
|
24
31
|
/** The seed file inside the project's installed `@we8/cms`, or null when absent. */
|
|
25
32
|
export declare function findSeedFile(projectDir: string): string | null;
|
|
26
33
|
//# sourceMappingURL=project.d.ts.map
|
package/dist/project.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,OAAO,EAAqB,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,OAAO,EAAqB,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAIrF,oFAAoF;AACpF,eAAO,MAAM,gBAAgB,+DAAgE,CAAC;AAE9F,wDAAwD;AACxD,eAAO,MAAM,kBAAkB,yCAAyC,CAAC;AAEzE,uDAAuD;AACvD,eAAO,MAAM,0BAA0B,wCAAwC,CAAC;AAEhF;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAa3D;AAED,sEAAsE;AACtE,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMhE;AAoBD;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,YAAY,EACpB,aAAa,GAAE,MAAM,EAAE,GAAG,IAAW,GACpC,WAAW,CAkDb;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAalF;AAED,oFAAoF;AACpF,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG9D"}
|
package/dist/project.js
CHANGED
|
@@ -7,10 +7,14 @@ import { readdirSync, readFileSync, existsSync } from 'node:fs';
|
|
|
7
7
|
import { isAbsolute, join, resolve } from 'node:path';
|
|
8
8
|
import { parseJsonc } from './jsonc.js';
|
|
9
9
|
import { readMigrationsDir } from './doctor.js';
|
|
10
|
+
import { SKILL_RELATIVE_PATH } from './skill.js';
|
|
11
|
+
import { AUTH_MIGRATIONS_DIR } from './wrangler-config.js';
|
|
10
12
|
/** The config filenames wrangler itself accepts, in the order it looks for them. */
|
|
11
13
|
export const CONFIG_FILENAMES = ['wrangler.jsonc', 'wrangler.json', 'wrangler.toml'];
|
|
12
14
|
/** Where the seed lives inside an installed project. */
|
|
13
15
|
export const SEED_RELATIVE_PATH = 'node_modules/@we8/cms/seed/local.sql';
|
|
16
|
+
/** Where an installed `@we8/auth` announces itself. */
|
|
17
|
+
export const AUTH_PACKAGE_RELATIVE_PATH = 'node_modules/@we8/auth/package.json';
|
|
14
18
|
/**
|
|
15
19
|
* Parses `.dev.vars` for the names it sets. Only names: a doctor that printed
|
|
16
20
|
* the value of a secret to prove it exists would be the bug it is looking for.
|
|
@@ -94,6 +98,11 @@ export function gatherFacts(projectDir, target, remoteSecrets = null) {
|
|
|
94
98
|
migrations = readdirSync(absolute).filter((name) => name.endsWith('.sql'));
|
|
95
99
|
}
|
|
96
100
|
}
|
|
101
|
+
const authPackageInstalled = existsSync(resolve(projectDir, AUTH_PACKAGE_RELATIVE_PATH));
|
|
102
|
+
const authMigrationsPath = resolve(projectDir, AUTH_MIGRATIONS_DIR);
|
|
103
|
+
const authMigrations = authPackageInstalled && existsSync(authMigrationsPath)
|
|
104
|
+
? readdirSync(authMigrationsPath).filter((name) => name.endsWith('.sql'))
|
|
105
|
+
: null;
|
|
97
106
|
return {
|
|
98
107
|
target,
|
|
99
108
|
config,
|
|
@@ -102,8 +111,30 @@ export function gatherFacts(projectDir, target, remoteSecrets = null) {
|
|
|
102
111
|
remoteSecrets,
|
|
103
112
|
migrations,
|
|
104
113
|
packageScripts: readScripts(projectDir),
|
|
114
|
+
workerEntry: readWorkerEntry(projectDir, config),
|
|
115
|
+
authPackageInstalled,
|
|
116
|
+
authMigrations,
|
|
117
|
+
skillPresent: existsSync(resolve(projectDir, SKILL_RELATIVE_PATH)),
|
|
105
118
|
};
|
|
106
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* The source of the worker entry named by `main`, or null when there is no
|
|
122
|
+
* readable one. The doctor reads the composed admin auth provider out of it.
|
|
123
|
+
*/
|
|
124
|
+
export function readWorkerEntry(projectDir, config) {
|
|
125
|
+
const main = typeof config === 'object' && config !== null
|
|
126
|
+
? config.main
|
|
127
|
+
: undefined;
|
|
128
|
+
if (typeof main !== 'string' || main.length === 0)
|
|
129
|
+
return null;
|
|
130
|
+
const absolute = isAbsolute(main) ? main : resolve(projectDir, main);
|
|
131
|
+
try {
|
|
132
|
+
return readFileSync(absolute, 'utf8');
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
107
138
|
/** The seed file inside the project's installed `@we8/cms`, or null when absent. */
|
|
108
139
|
export function findSeedFile(projectDir) {
|
|
109
140
|
const candidate = resolve(projectDir, SEED_RELATIVE_PATH);
|
package/dist/project.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project.js","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEtD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,iBAAiB,EAAuC,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"project.js","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `we8-cloudflare skill`: write or refresh the project skill in place.
|
|
3
|
+
*
|
|
4
|
+
* The scaffolder writes the skill once, at `npm create we8`. Everything after
|
|
5
|
+
* that is this command's job: a project that predates the skill adopts one, a
|
|
6
|
+
* project whose auth registration changed refreshes it, and CI checks that the
|
|
7
|
+
* file in the repository is the one this version of the pack would write.
|
|
8
|
+
*
|
|
9
|
+
* The state it generates from is derived from the project itself, never asked
|
|
10
|
+
* for. The mode comes out of the worker entry with its comments stripped (the
|
|
11
|
+
* same read the doctor does, for the same reason: the generated entries
|
|
12
|
+
* explain the modes they are not in), the site workspace is a directory that
|
|
13
|
+
* either exists or does not, and the perimeter acknowledgement is a fact about
|
|
14
|
+
* one line of code. Nothing here can produce a skill that describes a project
|
|
15
|
+
* other than this one.
|
|
16
|
+
*/
|
|
17
|
+
import { type AdminAuthMode } from './doctor.js';
|
|
18
|
+
/**
|
|
19
|
+
* What the command did, or would have done.
|
|
20
|
+
*
|
|
21
|
+
* `unchanged` and `current` are separate from `written` because they answer
|
|
22
|
+
* different questions: one says the write was a no-op, the other says the
|
|
23
|
+
* check passed. Both exit zero; `stale`, `missing`, and `not-a-project` do not.
|
|
24
|
+
*/
|
|
25
|
+
export type SkillCommandStatus = 'written' | 'unchanged' | 'current' | 'stale' | 'missing' | 'not-a-project';
|
|
26
|
+
export interface SkillCommandResult {
|
|
27
|
+
status: SkillCommandStatus;
|
|
28
|
+
/** The absolute path the skill lives at, even when nothing was written. */
|
|
29
|
+
path: string;
|
|
30
|
+
/** The mode the skill was generated for, or null when the project was refused. */
|
|
31
|
+
mode: AdminAuthMode | null;
|
|
32
|
+
/** One line for a terminal. */
|
|
33
|
+
message: string;
|
|
34
|
+
/** A command that resolves a non-zero status. */
|
|
35
|
+
fix?: string;
|
|
36
|
+
}
|
|
37
|
+
/** Whether a directory carries the Astro starter the template scaffolds. */
|
|
38
|
+
export declare function hasSiteWorkspace(projectDir: string): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Writes the skill, or checks it. `check` never touches the filesystem beyond
|
|
41
|
+
* reading, so it is safe to run anywhere, CI included.
|
|
42
|
+
*/
|
|
43
|
+
export declare function runSkillCommand(projectDir: string, options: {
|
|
44
|
+
check: boolean;
|
|
45
|
+
}): SkillCommandResult;
|
|
46
|
+
/** Zero when the project is in the state the command asked for, one otherwise. */
|
|
47
|
+
export declare function skillExitCode(status: SkillCommandStatus): number;
|
|
48
|
+
//# sourceMappingURL=skill-command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-command.d.ts","sourceRoot":"","sources":["../src/skill-command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,OAAO,EAIL,KAAK,aAAa,EACnB,MAAM,aAAa,CAAC;AAKrB;;;;;;GAMG;AACH,MAAM,MAAM,kBAAkB,GAC1B,SAAS,GACT,WAAW,GACX,SAAS,GACT,OAAO,GACP,SAAS,GACT,eAAe,CAAC;AAEpB,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb,kFAAkF;IAClF,IAAI,EAAE,aAAa,GAAG,IAAI,CAAC;IAC3B,+BAA+B;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,iDAAiD;IACjD,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,4EAA4E;AAC5E,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAE5D;AA4CD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG,kBAAkB,CAqDnG;AAED,kFAAkF;AAClF,wBAAgB,aAAa,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAEhE"}
|