@3sln/trove 0.0.11 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@3sln/trove",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Trove — a self-hostable, plugin-extensible Google Drive. Semantic search, pluggable storage (S3 / filesystem / NAS), and a VS Code-style contribution system with sandboxed plugins.",
|
|
6
6
|
"repository": {
|
|
@@ -35,6 +35,18 @@
|
|
|
35
35
|
// Evaluate URL https://<your drive>/api/access/evaluate
|
|
36
36
|
// Keys URL https://<your drive>/api/access/keys
|
|
37
37
|
//
|
|
38
|
+
// 4. AND let Access reach them. If the drive sits behind Access — which is the whole
|
|
39
|
+
// point — then by default these two paths do as well, and Access would have to
|
|
40
|
+
// authenticate to itself to call them. It cannot, so it fails with nothing useful to
|
|
41
|
+
// say. Add a Bypass policy for `/api/access/*`.
|
|
42
|
+
//
|
|
43
|
+
// Bypassing is safe here rather than a concession, and that is by construction: the
|
|
44
|
+
// keys endpoint serves a public key, and the evaluate endpoint authenticates its
|
|
45
|
+
// caller itself by verifying the assertion. Neither ever depended on Access for its
|
|
46
|
+
// own protection. They are also `public: true`, so this drive's OWN identity
|
|
47
|
+
// requirement does not apply to them either — Access holds no Trove session and
|
|
48
|
+
// never will.
|
|
49
|
+
//
|
|
38
50
|
// WHY IT REFUSES TO RUN UNVERIFIED
|
|
39
51
|
//
|
|
40
52
|
// This endpoint answers "does this email have access to this drive". That is a question
|
|
@@ -116,6 +128,10 @@ export function externalEvaluation({ privateJwk, team, kid = 'trove-access', jwk
|
|
|
116
128
|
method: 'POST',
|
|
117
129
|
path: '/api/access/evaluate',
|
|
118
130
|
deps: ['collections'],
|
|
131
|
+
// No Trove identity: the caller is Cloudflare, not a user of this drive, and it
|
|
132
|
+
// authenticates by signing the assertion — which `verifyJwt` below checks. A
|
|
133
|
+
// session requirement here would be asking Access to log in as somebody.
|
|
134
|
+
public: true,
|
|
119
135
|
async handler(ctx) {
|
|
120
136
|
const token = await parseAssertion(ctx.req);
|
|
121
137
|
if (!token) throw TroveError.invalid('No access assertion in the request');
|
|
@@ -141,6 +157,7 @@ export function externalEvaluation({ privateJwk, team, kid = 'trove-access', jwk
|
|
|
141
157
|
method: 'GET',
|
|
142
158
|
path: '/api/access/keys',
|
|
143
159
|
deps: [],
|
|
160
|
+
public: true,
|
|
144
161
|
// Public by design: it is a public key, and Cloudflare fetches it unauthenticated.
|
|
145
162
|
handler() {
|
|
146
163
|
return { keys: [publicJwkOf(privateJwk, { kid })] };
|
|
@@ -330,11 +330,18 @@ export async function createServer(config = {}) {
|
|
|
330
330
|
// exist. A drive that has not configured one has no `/api/access/*` at all, rather than
|
|
331
331
|
// endpoints that exist to answer "no" — which is the difference between a feature that is
|
|
332
332
|
// off and a feature that is broken.
|
|
333
|
+
// Paths a contributed component declared as needing no Trove identity. Exact matches
|
|
334
|
+
// only, and that is deliberate: the check below runs BEFORE routing, so it cannot know
|
|
335
|
+
// which parameterised route would have matched. A component that wants a public route
|
|
336
|
+
// gives it a fixed path.
|
|
337
|
+
const publicPaths = new Set();
|
|
338
|
+
|
|
333
339
|
const accessPolicy = config.accessEvaluation
|
|
334
340
|
? externalEvaluation({ ...config.accessEvaluation, team: config.accessEvaluation.team || config.identity?.access?.team })
|
|
335
341
|
: null;
|
|
336
342
|
for (const route of accessPolicy?.routes?.(routeHelpers) || []) {
|
|
337
343
|
router.add(route.method, route.path, route.deps || [], route.handler);
|
|
344
|
+
if (route.public) publicPaths.add(route.path);
|
|
338
345
|
}
|
|
339
346
|
|
|
340
347
|
// Said at boot, because that is when someone is looking and can still fix it. The
|
|
@@ -409,8 +416,14 @@ export async function createServer(config = {}) {
|
|
|
409
416
|
let principal = null;
|
|
410
417
|
let grant = null;
|
|
411
418
|
try {
|
|
412
|
-
|
|
413
|
-
|
|
419
|
+
// A public route answers to something other than this drive's identity — the
|
|
420
|
+
// external policy endpoints verify a Cloudflare-signed assertion themselves, and
|
|
421
|
+
// the keys endpoint serves a public key. Requiring a session on those is asking
|
|
422
|
+
// the caller to authenticate as a user it is not and does not have.
|
|
423
|
+
if (!publicPaths.has(url.pathname)) {
|
|
424
|
+
grant = await capabilities.resolve(req);
|
|
425
|
+
if (!grant) principal = await identity.authenticate(req);
|
|
426
|
+
}
|
|
414
427
|
} catch (err) {
|
|
415
428
|
const e = err instanceof TroveError ? err : TroveError.unauthorized('Authentication failed');
|
|
416
429
|
return withChallenge(new Response(JSON.stringify(e.toJSON()), { status: e.status, headers: { 'content-type': 'application/json', 'x-content-type-options': 'nosniff' } }), req);
|