@selvajs/server 1.5.0-beta.9 → 2.0.0-beta.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access/guards.d.ts +0 -10
- package/dist/access/guards.d.ts.map +1 -1
- package/dist/access/guards.js +23 -26
- package/dist/access/guards.js.map +1 -1
- package/dist/api/bodies.d.ts +1 -1
- package/dist/api/bodies.d.ts.map +1 -1
- package/dist/api/bodies.js +2 -1
- package/dist/api/bodies.js.map +1 -1
- package/dist/api/idempotency.d.ts +10 -3
- package/dist/api/idempotency.d.ts.map +1 -1
- package/dist/api/idempotency.js +32 -9
- package/dist/api/idempotency.js.map +1 -1
- package/dist/api/index.d.ts +2 -2
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +2 -2
- package/dist/api/index.js.map +1 -1
- package/dist/api/request.d.ts +12 -4
- package/dist/api/request.d.ts.map +1 -1
- package/dist/api/request.js +32 -5
- package/dist/api/request.js.map +1 -1
- package/dist/api/respond.d.ts.map +1 -1
- package/dist/api/respond.js +11 -7
- package/dist/api/respond.js.map +1 -1
- package/dist/api/scope.d.ts +7 -0
- package/dist/api/scope.d.ts.map +1 -1
- package/dist/api/scope.js +15 -1
- package/dist/api/scope.js.map +1 -1
- package/dist/compute/idempotency.d.ts +8 -1
- package/dist/compute/idempotency.d.ts.map +1 -1
- package/dist/compute/idempotency.js +35 -5
- package/dist/compute/idempotency.js.map +1 -1
- package/dist/compute/index.d.ts +1 -1
- package/dist/compute/index.d.ts.map +1 -1
- package/dist/compute/index.js +1 -1
- package/dist/compute/index.js.map +1 -1
- package/dist/compute/limits.d.ts +0 -7
- package/dist/compute/limits.d.ts.map +1 -1
- package/dist/compute/limits.js +0 -1
- package/dist/compute/limits.js.map +1 -1
- package/dist/compute/server-config-write.d.ts +11 -0
- package/dist/compute/server-config-write.d.ts.map +1 -1
- package/dist/compute/server-config-write.js +34 -0
- package/dist/compute/server-config-write.js.map +1 -1
- package/dist/definitions/definition-service.d.ts.map +1 -1
- package/dist/definitions/definition-service.js +3 -2
- package/dist/definitions/definition-service.js.map +1 -1
- package/dist/definitions/index.d.ts +1 -1
- package/dist/definitions/index.d.ts.map +1 -1
- package/dist/definitions/index.js +1 -1
- package/dist/definitions/index.js.map +1 -1
- package/dist/definitions/load-for-render.d.ts +9 -0
- package/dist/definitions/load-for-render.d.ts.map +1 -1
- package/dist/definitions/load-for-render.js +1 -1
- package/dist/definitions/load-for-render.js.map +1 -1
- package/dist/definitions/visibility.d.ts.map +1 -1
- package/dist/definitions/visibility.js +7 -12
- package/dist/definitions/visibility.js.map +1 -1
- package/dist/handlers/apiTokens.d.ts.map +1 -1
- package/dist/handlers/apiTokens.js +3 -1
- package/dist/handlers/apiTokens.js.map +1 -1
- package/dist/handlers/definitionVersions.d.ts.map +1 -1
- package/dist/handlers/definitionVersions.js +2 -2
- package/dist/handlers/definitionVersions.js.map +1 -1
- package/dist/handlers/definitions.d.ts.map +1 -1
- package/dist/handlers/definitions.js +27 -15
- package/dist/handlers/definitions.js.map +1 -1
- package/dist/handlers/index.d.ts +1 -1
- package/dist/handlers/index.d.ts.map +1 -1
- package/dist/handlers/index.js +1 -1
- package/dist/handlers/index.js.map +1 -1
- package/dist/handlers/invite-lookup.d.ts.map +1 -1
- package/dist/handlers/invite-lookup.js +3 -5
- package/dist/handlers/invite-lookup.js.map +1 -1
- package/dist/handlers/invites.d.ts +4 -3
- package/dist/handlers/invites.d.ts.map +1 -1
- package/dist/handlers/invites.js +17 -3
- package/dist/handlers/invites.js.map +1 -1
- package/dist/handlers/oauthConsent.d.ts +25 -13
- package/dist/handlers/oauthConsent.d.ts.map +1 -1
- package/dist/handlers/oauthConsent.js +29 -13
- package/dist/handlers/oauthConsent.js.map +1 -1
- package/dist/handlers/orgAssets.d.ts.map +1 -1
- package/dist/handlers/orgAssets.js +2 -2
- package/dist/handlers/orgAssets.js.map +1 -1
- package/dist/handlers/orgCompute.d.ts.map +1 -1
- package/dist/handlers/orgCompute.js +4 -2
- package/dist/handlers/orgCompute.js.map +1 -1
- package/dist/handlers/orgMembers.d.ts.map +1 -1
- package/dist/handlers/orgMembers.js +76 -56
- package/dist/handlers/orgMembers.js.map +1 -1
- package/dist/handlers/ownerChanges.d.ts +8 -0
- package/dist/handlers/ownerChanges.d.ts.map +1 -0
- package/dist/handlers/ownerChanges.js +19 -0
- package/dist/handlers/ownerChanges.js.map +1 -0
- package/dist/handlers/projectMembers.d.ts.map +1 -1
- package/dist/handlers/projectMembers.js +20 -15
- package/dist/handlers/projectMembers.js.map +1 -1
- package/dist/handlers/projects.d.ts.map +1 -1
- package/dist/handlers/projects.js +10 -3
- package/dist/handlers/projects.js.map +1 -1
- package/dist/http/api-token.d.ts +17 -0
- package/dist/http/api-token.d.ts.map +1 -1
- package/dist/http/api-token.js +49 -14
- package/dist/http/api-token.js.map +1 -1
- package/dist/http/bounded-ttl-cache.d.ts +17 -0
- package/dist/http/bounded-ttl-cache.d.ts.map +1 -0
- package/dist/http/bounded-ttl-cache.js +58 -0
- package/dist/http/bounded-ttl-cache.js.map +1 -0
- package/dist/http/csrf.d.ts.map +1 -1
- package/dist/http/csrf.js +4 -2
- package/dist/http/csrf.js.map +1 -1
- package/dist/http/index.d.ts +3 -1
- package/dist/http/index.d.ts.map +1 -1
- package/dist/http/index.js +3 -1
- package/dist/http/index.js.map +1 -1
- package/dist/http/redirect.d.ts.map +1 -1
- package/dist/http/redirect.js +5 -0
- package/dist/http/redirect.js.map +1 -1
- package/dist/http/session-refresher.d.ts +14 -0
- package/dist/http/session-refresher.d.ts.map +1 -0
- package/dist/http/session-refresher.js +34 -0
- package/dist/http/session-refresher.js.map +1 -0
- package/dist/notifications/smtp-provider.d.ts.map +1 -1
- package/dist/notifications/smtp-provider.js +13 -6
- package/dist/notifications/smtp-provider.js.map +1 -1
- package/dist/oauth/index.d.ts +3 -0
- package/dist/oauth/index.d.ts.map +1 -1
- package/dist/oauth/index.js +14 -8
- package/dist/oauth/index.js.map +1 -1
- package/dist/organizations/org-asset-service.d.ts +2 -2
- package/dist/organizations/org-asset-service.js +2 -2
- package/dist/organizations/user-memberships.d.ts.map +1 -1
- package/dist/organizations/user-memberships.js +3 -7
- package/dist/organizations/user-memberships.js.map +1 -1
- package/dist/projects/delete-with-blobs.d.ts +17 -0
- package/dist/projects/delete-with-blobs.d.ts.map +1 -0
- package/dist/projects/delete-with-blobs.js +36 -0
- package/dist/projects/delete-with-blobs.js.map +1 -0
- package/dist/projects/index.d.ts +1 -0
- package/dist/projects/index.d.ts.map +1 -1
- package/dist/projects/index.js +1 -0
- package/dist/projects/index.js.map +1 -1
- package/package.json +11 -10
|
@@ -11,7 +11,7 @@ import { apiError, ApiErrorCode, collection, created, noContent, parseBody, pars
|
|
|
11
11
|
import { canChangeVisibilityToPublic, canCreateDefinition, canEdit, canSolve, canView, hasPermission, slugify, SYSTEM_CONTEXT, outsideTokenOrg, scopeAllows, validateProjectFlags, withAdminBypass } from '@selvajs/platform';
|
|
12
12
|
import { resolveAccessibleProjects } from '../definitions/index.js';
|
|
13
13
|
import { projectAccessInputFromRowsWith, requireCanCreateProject, requireCanEditProjectSettings, requireCanManage } from '../access/index.js';
|
|
14
|
-
import { createProjectWithUniqueSlug } from '../projects/index.js';
|
|
14
|
+
import { createProjectWithUniqueSlug, deleteProjectWithBlobs } from '../projects/index.js';
|
|
15
15
|
/**
|
|
16
16
|
* Projects the caller can view. `canCreateDefinitions` says whether
|
|
17
17
|
* `POST /definitions` would take this project from this caller: the same rule
|
|
@@ -160,7 +160,10 @@ export const updateProject = async (req) => {
|
|
|
160
160
|
// A renamed project gets a re-derived slug; the two must not drift apart.
|
|
161
161
|
if (input.name !== undefined) {
|
|
162
162
|
patch.name = input.name;
|
|
163
|
-
|
|
163
|
+
// A name with no usable letters yields '', which keeps the current slug.
|
|
164
|
+
const slug = slugify(input.name);
|
|
165
|
+
if (slug)
|
|
166
|
+
patch.slug = slug;
|
|
164
167
|
}
|
|
165
168
|
if (input.description !== undefined)
|
|
166
169
|
patch.description = input.description ?? undefined;
|
|
@@ -175,7 +178,11 @@ export const deleteProject = async (req) => {
|
|
|
175
178
|
const { id } = requireParams(req.params, 'id');
|
|
176
179
|
const { ctx } = requireCaller(req);
|
|
177
180
|
await requireCanManage(req, id);
|
|
178
|
-
await
|
|
181
|
+
await deleteProjectWithBlobs({
|
|
182
|
+
projects: req.deps.projects,
|
|
183
|
+
definitions: req.deps.definitionMeta,
|
|
184
|
+
storage: req.deps.storage
|
|
185
|
+
}, ctx, id);
|
|
179
186
|
return noContent();
|
|
180
187
|
};
|
|
181
188
|
//# sourceMappingURL=projects.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"projects.js","sourceRoot":"","sources":["../../src/handlers/projects.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EACN,QAAQ,EACR,YAAY,EACZ,UAAU,EACV,OAAO,EACP,SAAS,EACT,SAAS,EACT,gBAAgB,EAChB,aAAa,EACb,aAAa,EACb,uBAAuB,EACvB,uBAAuB,EAEvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACN,2BAA2B,EAC3B,mBAAmB,EACnB,OAAO,EACP,QAAQ,EACR,OAAO,EACP,aAAa,EACb,OAAO,EACP,cAAc,EACd,eAAe,EACf,WAAW,EACX,oBAAoB,EACpB,eAAe,EAEf,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EACN,8BAA8B,EAC9B,uBAAuB,EACvB,6BAA6B,EAC7B,gBAAgB,EAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,2BAA2B,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"projects.js","sourceRoot":"","sources":["../../src/handlers/projects.ts"],"names":[],"mappings":"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"}
|
package/dist/http/api-token.d.ts
CHANGED
|
@@ -31,6 +31,11 @@ export interface ResolveApiTokenDeps {
|
|
|
31
31
|
* rule's project scan. Absent: every flag off, which only ever refuses more.
|
|
32
32
|
*/
|
|
33
33
|
flag?: (name: keyof SelvaFlags) => boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Skips the holder check for a token that passed it within the cache's TTL.
|
|
36
|
+
* Absent: checked on every request.
|
|
37
|
+
*/
|
|
38
|
+
holderVerdicts?: HolderVerdictCache;
|
|
34
39
|
/** Receives background failures (the `lastUsedAt` write). Logs the token id only. */
|
|
35
40
|
log?: ILogger;
|
|
36
41
|
now?: () => number;
|
|
@@ -52,4 +57,16 @@ export interface ResolveApiTokenOptions {
|
|
|
52
57
|
* narrowed to the token's scopes.
|
|
53
58
|
*/
|
|
54
59
|
export declare function resolveApiToken(request: Request, deps: ResolveApiTokenDeps, { pathPrefix }?: ResolveApiTokenOptions): Promise<ApiTokenResolution>;
|
|
60
|
+
export interface HolderVerdictCache {
|
|
61
|
+
passed(tokenId: string, now: number): boolean;
|
|
62
|
+
remember(tokenId: string, now: number): void;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Remembers passed holder checks for `ttlMs`, so a busy upload key doesn't
|
|
66
|
+
* re-scan its org's projects on every request. A withdrawn right takes effect
|
|
67
|
+
* within the TTL; a disabled owner or a removed member is still refused at once.
|
|
68
|
+
*/
|
|
69
|
+
export declare function createHolderVerdictCache(ttlMs?: number, maxEntries?: number): HolderVerdictCache;
|
|
70
|
+
/** The bearer credential, or null. CSRF's exemption reads it here too: the two must agree. */
|
|
71
|
+
export declare function bearerOf(request: Request): string | null;
|
|
55
72
|
//# sourceMappingURL=api-token.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-token.d.ts","sourceRoot":"","sources":["../../src/http/api-token.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,QAAQ,EACR,QAAQ,EACR,aAAa,EACb,aAAa,EACb,OAAO,EACP,cAAc,EACd,UAAU,EACV,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAGN,KAAK,aAAa,EAClB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAoB,KAAK,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAG5F,MAAM,MAAM,kBAAkB;AAC7B,2EAA2E;AACzE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,GAAG,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,QAAQ,CAAA;CAAE;AACtE,0EAA0E;GACxE;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7E,MAAM,WAAW,mBAAmB;IACnC,8EAA8E;IAC9E,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,IAAI,EAAE,IAAI,CAAC,aAAa,EAAE,SAAS,GAAG,kBAAkB,CAAC,CAAC;IAC1D,IAAI,EAAE,IAAI,CACT,aAAa,EACb,WAAW,GAAG,MAAM,GAAG,aAAa,GAAG,UAAU,GAAG,uBAAuB,GAAG,aAAa,CAC3F,CAAC;IACF,qHAAqH;IACrH,gBAAgB,CAAC,EAAE,oBAAoB,CAAC;IACxC;;;OAGG;IACH,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,UAAU,KAAK,OAAO,CAAC;IAC3C,qFAAqF;IACrF,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,sBAAsB;IACtC,mEAAmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AA0BD;;;;;;;;;;;GAWG;AACH,wBAAsB,eAAe,CACpC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,mBAAmB,EACzB,EAAE,UAAuB,EAAE,GAAE,sBAA2B,GACtD,OAAO,CAAC,kBAAkB,CAAC,
|
|
1
|
+
{"version":3,"file":"api-token.d.ts","sourceRoot":"","sources":["../../src/http/api-token.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,QAAQ,EACR,QAAQ,EACR,aAAa,EACb,aAAa,EACb,OAAO,EACP,cAAc,EACd,UAAU,EACV,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAGN,KAAK,aAAa,EAClB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAoB,KAAK,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAG5F,MAAM,MAAM,kBAAkB;AAC7B,2EAA2E;AACzE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,GAAG,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,QAAQ,CAAA;CAAE;AACtE,0EAA0E;GACxE;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7E,MAAM,WAAW,mBAAmB;IACnC,8EAA8E;IAC9E,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,IAAI,EAAE,IAAI,CAAC,aAAa,EAAE,SAAS,GAAG,kBAAkB,CAAC,CAAC;IAC1D,IAAI,EAAE,IAAI,CACT,aAAa,EACb,WAAW,GAAG,MAAM,GAAG,aAAa,GAAG,UAAU,GAAG,uBAAuB,GAAG,aAAa,CAC3F,CAAC;IACF,qHAAqH;IACrH,gBAAgB,CAAC,EAAE,oBAAoB,CAAC;IACxC;;;OAGG;IACH,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,UAAU,KAAK,OAAO,CAAC;IAC3C;;;OAGG;IACH,cAAc,CAAC,EAAE,kBAAkB,CAAC;IACpC,qFAAqF;IACrF,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,sBAAsB;IACtC,mEAAmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AA0BD;;;;;;;;;;;GAWG;AACH,wBAAsB,eAAe,CACpC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,mBAAmB,EACzB,EAAE,UAAuB,EAAE,GAAE,sBAA2B,GACtD,OAAO,CAAC,kBAAkB,CAAC,CAoF7B;AAED,MAAM,WAAW,kBAAkB;IAClC,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9C,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7C;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,SAAS,EAAE,UAAU,SAAS,GAAG,kBAAkB,CAoBhG;AAED,8FAA8F;AAC9F,wBAAgB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAKxD"}
|
package/dist/http/api-token.js
CHANGED
|
@@ -79,18 +79,22 @@ export async function resolveApiToken(request, deps, { pathPrefix = '/api/v1/' }
|
|
|
79
79
|
if (!user || user.disabled || !member)
|
|
80
80
|
return UNAUTHORIZED;
|
|
81
81
|
const live = await buildRequestContext({ user, platformPermissions, membership: member, pinOrg: true }, { orgs: deps.data.orgs });
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
82
|
+
const now = deps.now?.() ?? Date.now();
|
|
83
|
+
if (!deps.holderVerdicts?.passed(token.id, now)) {
|
|
84
|
+
const holds = await canHoldApiTokens(live, token.scopes, {
|
|
85
|
+
policy: deps.mayHoldApiTokens,
|
|
86
|
+
deps: {
|
|
87
|
+
orgs: deps.data.orgs,
|
|
88
|
+
projects: deps.data.projects,
|
|
89
|
+
platformProjectGrants: deps.data.platformProjectGrants,
|
|
90
|
+
definitionMeta: deps.data.definitions,
|
|
91
|
+
flag: deps.flag ?? (() => false)
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
if (!holds)
|
|
95
|
+
return HOLDER_REFUSED;
|
|
96
|
+
deps.holderVerdicts?.remember(token.id, now);
|
|
97
|
+
}
|
|
94
98
|
let ctx = narrowApiTokenContext(live, token);
|
|
95
99
|
// Signed only after the user check above: a minted session can't be
|
|
96
100
|
// revoked, and row security doesn't look at bans.
|
|
@@ -98,7 +102,6 @@ export async function resolveApiToken(request, deps, { pathPrefix = '/api/v1/' }
|
|
|
98
102
|
const sessionToken = await delegated.mint(user.id, { orgId: token.orgId });
|
|
99
103
|
ctx = { ...ctx, adapterContext: { sessionToken } };
|
|
100
104
|
}
|
|
101
|
-
const now = deps.now?.() ?? Date.now();
|
|
102
105
|
if (!token.lastUsedAt || now - Date.parse(token.lastUsedAt) >= LAST_USED_WRITE_INTERVAL_MS) {
|
|
103
106
|
store.touchLastUsed(SYSTEM_CONTEXT, token.id, new Date(now).toISOString()).catch((err) => {
|
|
104
107
|
deps.log?.warn('Failed to record API token use', {
|
|
@@ -110,7 +113,39 @@ export async function resolveApiToken(request, deps, { pathPrefix = '/api/v1/' }
|
|
|
110
113
|
}
|
|
111
114
|
return { kind: 'ok', user, ctx, token };
|
|
112
115
|
}
|
|
113
|
-
|
|
116
|
+
/**
|
|
117
|
+
* Remembers passed holder checks for `ttlMs`, so a busy upload key doesn't
|
|
118
|
+
* re-scan its org's projects on every request. A withdrawn right takes effect
|
|
119
|
+
* within the TTL; a disabled owner or a removed member is still refused at once.
|
|
120
|
+
*/
|
|
121
|
+
export function createHolderVerdictCache(ttlMs = 30_000, maxEntries = 10_000) {
|
|
122
|
+
const until = new Map();
|
|
123
|
+
return {
|
|
124
|
+
passed(tokenId, now) {
|
|
125
|
+
const expiry = until.get(tokenId);
|
|
126
|
+
if (expiry === undefined)
|
|
127
|
+
return false;
|
|
128
|
+
if (expiry > now)
|
|
129
|
+
return true;
|
|
130
|
+
until.delete(tokenId);
|
|
131
|
+
return false;
|
|
132
|
+
},
|
|
133
|
+
remember(tokenId, now) {
|
|
134
|
+
if (until.size >= maxEntries) {
|
|
135
|
+
for (const [id, expiry] of until)
|
|
136
|
+
if (expiry <= now)
|
|
137
|
+
until.delete(id);
|
|
138
|
+
// Still full of live entries: drop the oldest insert.
|
|
139
|
+
if (until.size >= maxEntries)
|
|
140
|
+
until.delete(until.keys().next().value);
|
|
141
|
+
}
|
|
142
|
+
until.delete(tokenId);
|
|
143
|
+
until.set(tokenId, now + ttlMs);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/** The bearer credential, or null. CSRF's exemption reads it here too: the two must agree. */
|
|
148
|
+
export function bearerOf(request) {
|
|
114
149
|
const header = request.headers.get('authorization');
|
|
115
150
|
if (!header)
|
|
116
151
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-token.js","sourceRoot":"","sources":["../../src/http/api-token.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EACN,gBAAgB,EAChB,iBAAiB,EAEjB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,gBAAgB,EAA6B,MAAM,+BAA+B,CAAC;AAC5F,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"api-token.js","sourceRoot":"","sources":["../../src/http/api-token.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface BoundedTtlCacheConfig {
|
|
2
|
+
/** Max retained entries. Once full, expired entries go first, then oldest-inserted. */
|
|
3
|
+
maxEntries: number;
|
|
4
|
+
}
|
|
5
|
+
export interface BoundedTtlCache<T> {
|
|
6
|
+
/** The live value, or `undefined` when absent or expired. Expired entries are dropped on read. */
|
|
7
|
+
get(key: string): T | undefined;
|
|
8
|
+
/** Store `value` under `key`, expiring `ttlMs` from now. Re-inserting a key refreshes its insertion order. */
|
|
9
|
+
set(key: string, value: T, ttlMs: number): void;
|
|
10
|
+
delete(key: string): void;
|
|
11
|
+
/** Test seam and force-refresh gestures. */
|
|
12
|
+
clear(): void;
|
|
13
|
+
/** Retained entries, including any expired but not yet swept. For tests/observability. */
|
|
14
|
+
size(): number;
|
|
15
|
+
}
|
|
16
|
+
export declare function createBoundedTtlCache<T>(config: BoundedTtlCacheConfig): BoundedTtlCache<T>;
|
|
17
|
+
//# sourceMappingURL=bounded-ttl-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bounded-ttl-cache.d.ts","sourceRoot":"","sources":["../../src/http/bounded-ttl-cache.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,qBAAqB;IACrC,uFAAuF;IACvF,UAAU,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe,CAAC,CAAC;IACjC,kGAAkG;IAClG,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC;IAChC,8GAA8G;IAC9G,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAChD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,4CAA4C;IAC5C,KAAK,IAAI,IAAI,CAAC;IACd,0FAA0F;IAC1F,IAAI,IAAI,MAAM,CAAC;CACf;AAOD,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,MAAM,EAAE,qBAAqB,GAAG,eAAe,CAAC,CAAC,CAAC,CA6C1F"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Process-local TTL cache with a hard entry cap.
|
|
2
|
+
//
|
|
3
|
+
// Sweeping is amortized onto `set` rather than driven by a timer: a timer would
|
|
4
|
+
// keep the event loop alive and give this module a lifecycle its callers — plain
|
|
5
|
+
// module-level singletons — do not have.
|
|
6
|
+
//
|
|
7
|
+
// Values are stored as-is. A caller that caches failures alongside successes
|
|
8
|
+
// encodes both in ONE value type and gives each its own TTL at the `set` call,
|
|
9
|
+
// rather than this holding a success/error union it would have to interpret.
|
|
10
|
+
export function createBoundedTtlCache(config) {
|
|
11
|
+
const maxEntries = Math.max(1, Math.floor(config.maxEntries));
|
|
12
|
+
const entries = new Map();
|
|
13
|
+
function evictIfFull(now) {
|
|
14
|
+
if (entries.size < maxEntries)
|
|
15
|
+
return;
|
|
16
|
+
for (const [key, entry] of entries) {
|
|
17
|
+
if (entry.expiresAt <= now)
|
|
18
|
+
entries.delete(key);
|
|
19
|
+
}
|
|
20
|
+
// Map preserves insertion order, so the first key is the oldest-inserted.
|
|
21
|
+
while (entries.size >= maxEntries) {
|
|
22
|
+
const oldest = entries.keys().next().value;
|
|
23
|
+
if (oldest === undefined)
|
|
24
|
+
break;
|
|
25
|
+
entries.delete(oldest);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
get(key) {
|
|
30
|
+
const entry = entries.get(key);
|
|
31
|
+
if (!entry)
|
|
32
|
+
return undefined;
|
|
33
|
+
if (entry.expiresAt <= Date.now()) {
|
|
34
|
+
entries.delete(key);
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
return entry.value;
|
|
38
|
+
},
|
|
39
|
+
set(key, value, ttlMs) {
|
|
40
|
+
// Delete first so a re-insert moves the key to the end of the insertion
|
|
41
|
+
// order — otherwise a hot key keeps its original position and is evicted
|
|
42
|
+
// while colder, newer keys survive.
|
|
43
|
+
entries.delete(key);
|
|
44
|
+
evictIfFull(Date.now());
|
|
45
|
+
entries.set(key, { value, expiresAt: Date.now() + ttlMs });
|
|
46
|
+
},
|
|
47
|
+
delete(key) {
|
|
48
|
+
entries.delete(key);
|
|
49
|
+
},
|
|
50
|
+
clear() {
|
|
51
|
+
entries.clear();
|
|
52
|
+
},
|
|
53
|
+
size() {
|
|
54
|
+
return entries.size;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=bounded-ttl-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bounded-ttl-cache.js","sourceRoot":"","sources":["../../src/http/bounded-ttl-cache.ts"],"names":[],"mappings":"AAAA,iDAAiD;AACjD,EAAE;AACF,gFAAgF;AAChF,iFAAiF;AACjF,yCAAyC;AACzC,EAAE;AACF,6EAA6E;AAC7E,+EAA+E;AAC/E,6EAA6E;AAwB7E,MAAM,UAAU,qBAAqB,CAAI,MAA6B;IACrE,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;IAC9D,MAAM,OAAO,GAAG,IAAI,GAAG,EAAoB,CAAC;IAE5C,SAAS,WAAW,CAAC,GAAW;QAC/B,IAAI,OAAO,CAAC,IAAI,GAAG,UAAU;YAAE,OAAO;QACtC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,OAAO,EAAE,CAAC;YACpC,IAAI,KAAK,CAAC,SAAS,IAAI,GAAG;gBAAE,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjD,CAAC;QACD,0EAA0E;QAC1E,OAAO,OAAO,CAAC,IAAI,IAAI,UAAU,EAAE,CAAC;YACnC,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;YAC3C,IAAI,MAAM,KAAK,SAAS;gBAAE,MAAM;YAChC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACxB,CAAC;IACF,CAAC;IAED,OAAO;QACN,GAAG,CAAC,GAAG;YACN,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC/B,IAAI,CAAC,KAAK;gBAAE,OAAO,SAAS,CAAC;YAC7B,IAAI,KAAK,CAAC,SAAS,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;gBACnC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACpB,OAAO,SAAS,CAAC;YAClB,CAAC;YACD,OAAO,KAAK,CAAC,KAAK,CAAC;QACpB,CAAC;QACD,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK;YACpB,wEAAwE;YACxE,yEAAyE;YACzE,oCAAoC;YACpC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACpB,WAAW,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;YACxB,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;QAC5D,CAAC;QACD,MAAM,CAAC,GAAG;YACT,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACrB,CAAC;QACD,KAAK;YACJ,OAAO,CAAC,KAAK,EAAE,CAAC;QACjB,CAAC;QACD,IAAI;YACH,OAAO,OAAO,CAAC,IAAI,CAAC;QACrB,CAAC;KACD,CAAC;AACH,CAAC"}
|
package/dist/http/csrf.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"csrf.d.ts","sourceRoot":"","sources":["../../src/http/csrf.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;
|
|
1
|
+
{"version":3,"file":"csrf.d.ts","sourceRoot":"","sources":["../../src/http/csrf.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAeH,MAAM,WAAW,WAAW;IAC3B,sDAAsD;IACtD,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAChC;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAU9F"}
|
package/dist/http/csrf.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* `Origin` (the OAuth token endpoint, per RFC 6749).
|
|
12
12
|
*/
|
|
13
13
|
import { API_TOKEN_PREFIX } from '../tokens/index.js';
|
|
14
|
+
import { bearerOf } from './api-token.js';
|
|
14
15
|
const UNSAFE_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);
|
|
15
16
|
// The content types a browser form can send without a CORS preflight, plus
|
|
16
17
|
// SvelteKit's own binary form encoding.
|
|
@@ -26,8 +27,9 @@ export function isCrossSiteFormSubmission(request, options = {}) {
|
|
|
26
27
|
const type = (request.headers.get('content-type') ?? '').split(';')[0].trim().toLowerCase();
|
|
27
28
|
if (!FORM_TYPES.includes(type))
|
|
28
29
|
return false;
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
// The same parse `resolveApiToken` uses: a header one read as a key and the
|
|
31
|
+
// other didn't would skip this check and still run as the cookie session.
|
|
32
|
+
if (bearerOf(request)?.startsWith(API_TOKEN_PREFIX))
|
|
31
33
|
return false;
|
|
32
34
|
const url = new URL(request.url);
|
|
33
35
|
if (options.exemptPaths?.includes(url.pathname))
|
package/dist/http/csrf.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"csrf.js","sourceRoot":"","sources":["../../src/http/csrf.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"csrf.js","sourceRoot":"","sources":["../../src/http/csrf.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;AACnE,2EAA2E;AAC3E,wCAAwC;AACxC,MAAM,UAAU,GAAG;IAClB,mCAAmC;IACnC,qBAAqB;IACrB,YAAY;IACZ,kCAAkC;CAClC,CAAC;AAOF,MAAM,UAAU,yBAAyB,CAAC,OAAgB,EAAE,UAAuB,EAAE;IACpF,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAC;IACtD,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC5F,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7C,4EAA4E;IAC5E,0EAA0E;IAC1E,IAAI,QAAQ,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC,gBAAgB,CAAC;QAAE,OAAO,KAAK,CAAC;IAClE,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,OAAO,CAAC,WAAW,EAAE,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9D,OAAO,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,GAAG,CAAC,MAAM,CAAC;AACrD,CAAC"}
|
package/dist/http/index.d.ts
CHANGED
|
@@ -5,5 +5,7 @@ export { applySecurityHeaders, type SecurityHeaderOptions } from './security-hea
|
|
|
5
5
|
export { createRouteClassifier, type RouteClassifier, type RouteClassifierConfig } from './route-classifier.js';
|
|
6
6
|
export { createLoginRateLimiter, addressKeysCollapsed, type LoginRateLimiter, type LoginRateLimiterConfig } from './login-rate-limit.js';
|
|
7
7
|
export { buildRequestContext, type BuildRequestContextInput } from './request-context.js';
|
|
8
|
-
export {
|
|
8
|
+
export { createBoundedTtlCache, type BoundedTtlCache, type BoundedTtlCacheConfig } from './bounded-ttl-cache.js';
|
|
9
|
+
export { createSessionRefresher, type SessionRefresher } from './session-refresher.js';
|
|
10
|
+
export { createHolderVerdictCache, resolveApiToken, type ApiTokenResolution, type HolderVerdictCache, type ResolveApiTokenDeps, type ResolveApiTokenOptions } from './api-token.js';
|
|
9
11
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/http/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/http/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,yBAAyB,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AACxE,OAAO,EAAE,uBAAuB,EAAE,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC3E,OAAO,EAAE,oBAAoB,EAAE,KAAK,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AACzF,OAAO,EACN,qBAAqB,EACrB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACN,sBAAsB,EACtB,oBAAoB,EACpB,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,mBAAmB,EAAE,KAAK,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAC1F,OAAO,EACN,eAAe,EACf,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,MAAM,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/http/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,yBAAyB,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AACxE,OAAO,EAAE,uBAAuB,EAAE,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC3E,OAAO,EAAE,oBAAoB,EAAE,KAAK,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AACzF,OAAO,EACN,qBAAqB,EACrB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACN,sBAAsB,EACtB,oBAAoB,EACpB,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,mBAAmB,EAAE,KAAK,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAC1F,OAAO,EACN,qBAAqB,EACrB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,sBAAsB,EAAE,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AACvF,OAAO,EACN,wBAAwB,EACxB,eAAe,EACf,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,MAAM,gBAAgB,CAAC"}
|
package/dist/http/index.js
CHANGED
|
@@ -6,5 +6,7 @@ export { applySecurityHeaders } from './security-headers.js';
|
|
|
6
6
|
export { createRouteClassifier } from './route-classifier.js';
|
|
7
7
|
export { createLoginRateLimiter, addressKeysCollapsed } from './login-rate-limit.js';
|
|
8
8
|
export { buildRequestContext } from './request-context.js';
|
|
9
|
-
export {
|
|
9
|
+
export { createBoundedTtlCache } from './bounded-ttl-cache.js';
|
|
10
|
+
export { createSessionRefresher } from './session-refresher.js';
|
|
11
|
+
export { createHolderVerdictCache, resolveApiToken } from './api-token.js';
|
|
10
12
|
//# sourceMappingURL=index.js.map
|
package/dist/http/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/http/index.ts"],"names":[],"mappings":"AAAA,uEAAuE;AAEvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,yBAAyB,EAAoB,MAAM,WAAW,CAAC;AACxE,OAAO,EAAE,uBAAuB,EAAoB,MAAM,gBAAgB,CAAC;AAC3E,OAAO,EAAE,oBAAoB,EAA8B,MAAM,uBAAuB,CAAC;AACzF,OAAO,EACN,qBAAqB,EAGrB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACN,sBAAsB,EACtB,oBAAoB,EAGpB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,mBAAmB,EAAiC,MAAM,sBAAsB,CAAC;AAC1F,OAAO,EACN,eAAe,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/http/index.ts"],"names":[],"mappings":"AAAA,uEAAuE;AAEvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,yBAAyB,EAAoB,MAAM,WAAW,CAAC;AACxE,OAAO,EAAE,uBAAuB,EAAoB,MAAM,gBAAgB,CAAC;AAC3E,OAAO,EAAE,oBAAoB,EAA8B,MAAM,uBAAuB,CAAC;AACzF,OAAO,EACN,qBAAqB,EAGrB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACN,sBAAsB,EACtB,oBAAoB,EAGpB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,mBAAmB,EAAiC,MAAM,sBAAsB,CAAC;AAC1F,OAAO,EACN,qBAAqB,EAGrB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,sBAAsB,EAAyB,MAAM,wBAAwB,CAAC;AACvF,OAAO,EACN,wBAAwB,EACxB,eAAe,EAKf,MAAM,gBAAgB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redirect.d.ts","sourceRoot":"","sources":["../../src/http/redirect.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"redirect.d.ts","sourceRoot":"","sources":["../../src/http/redirect.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAc3F"}
|
package/dist/http/redirect.js
CHANGED
|
@@ -11,6 +11,11 @@ export function safeRedirectTarget(raw, fallback) {
|
|
|
11
11
|
// Absolute URLs (`https://evil.com`) leave this origin.
|
|
12
12
|
if (raw[0] !== '/')
|
|
13
13
|
return fallback;
|
|
14
|
+
// Browsers drop tab, CR and LF anywhere in a URL, so `/\t/evil.com` lands
|
|
15
|
+
// as `//evil.com` after the check below has passed it.
|
|
16
|
+
// eslint-disable-next-line no-control-regex -- matching control characters is the point
|
|
17
|
+
if (/[\u0000-\u001f\u007f]/.test(raw))
|
|
18
|
+
return fallback;
|
|
14
19
|
// `//evil.com` is protocol-relative — a browser treats it as cross-origin —
|
|
15
20
|
// and some browsers normalize `/\evil.com` into the same thing.
|
|
16
21
|
if (raw[1] === '/' || raw[1] === '\\')
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redirect.js","sourceRoot":"","sources":["../../src/http/redirect.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAA8B,EAAE,QAAgB;IAClF,4EAA4E;IAC5E,gDAAgD;IAChD,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,QAAQ,CAAC;IAC/D,wDAAwD;IACxD,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG;QAAE,OAAO,QAAQ,CAAC;IACpC,4EAA4E;IAC5E,gEAAgE;IAChE,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI;QAAE,OAAO,QAAQ,CAAC;IACvD,OAAO,GAAG,CAAC;AACZ,CAAC"}
|
|
1
|
+
{"version":3,"file":"redirect.js","sourceRoot":"","sources":["../../src/http/redirect.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAA8B,EAAE,QAAgB;IAClF,4EAA4E;IAC5E,gDAAgD;IAChD,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,QAAQ,CAAC;IAC/D,wDAAwD;IACxD,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG;QAAE,OAAO,QAAQ,CAAC;IACpC,0EAA0E;IAC1E,uDAAuD;IACvD,wFAAwF;IACxF,IAAI,uBAAuB,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,QAAQ,CAAC;IACvD,4EAA4E;IAC5E,gEAAgE;IAChE,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI;QAAE,OAAO,QAAQ,CAAC;IACvD,OAAO,GAAG,CAAC;AACZ,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ISessionRefresh } from '@selvajs/platform';
|
|
2
|
+
type SessionPair = NonNullable<Awaited<ReturnType<ISessionRefresh['refreshSession']>>>;
|
|
3
|
+
export type SessionRefresher = (refreshToken: string) => Promise<SessionPair | null>;
|
|
4
|
+
/**
|
|
5
|
+
* A page's parallel requests all arrive with the same expired session and the same refresh token,
|
|
6
|
+
* and a provider may rotate that token on first use. Each request exchanging it alone would see
|
|
7
|
+
* every one after the first refused, read that as a dead session, and clear the cookie: a signed-in
|
|
8
|
+
* user logged out by loading a page. So they share one exchange and its answer.
|
|
9
|
+
*
|
|
10
|
+
* `getRefresh` is read per exchange so a host can swap providers. One per process.
|
|
11
|
+
*/
|
|
12
|
+
export declare function createSessionRefresher(getRefresh: () => ISessionRefresh | undefined): SessionRefresher;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=session-refresher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-refresher.d.ts","sourceRoot":"","sources":["../../src/http/session-refresher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAGzD,KAAK,WAAW,GAAG,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC;AAMvF,MAAM,MAAM,gBAAgB,GAAG,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;AAErF;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACrC,UAAU,EAAE,MAAM,eAAe,GAAG,SAAS,GAC3C,gBAAgB,CAoBlB"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createBoundedTtlCache } from './bounded-ttl-cache.js';
|
|
2
|
+
/** Long enough for the requests of one page load that left before the new cookie landed. */
|
|
3
|
+
const RECENT_PAIR_TTL_MS = 10_000;
|
|
4
|
+
const RECENT_PAIR_MAX = 1_000;
|
|
5
|
+
/**
|
|
6
|
+
* A page's parallel requests all arrive with the same expired session and the same refresh token,
|
|
7
|
+
* and a provider may rotate that token on first use. Each request exchanging it alone would see
|
|
8
|
+
* every one after the first refused, read that as a dead session, and clear the cookie: a signed-in
|
|
9
|
+
* user logged out by loading a page. So they share one exchange and its answer.
|
|
10
|
+
*
|
|
11
|
+
* `getRefresh` is read per exchange so a host can swap providers. One per process.
|
|
12
|
+
*/
|
|
13
|
+
export function createSessionRefresher(getRefresh) {
|
|
14
|
+
const inflight = new Map();
|
|
15
|
+
const recent = createBoundedTtlCache({ maxEntries: RECENT_PAIR_MAX });
|
|
16
|
+
async function exchange(refreshToken) {
|
|
17
|
+
const pair = (await getRefresh()?.refreshSession(refreshToken)) ?? null;
|
|
18
|
+
if (pair)
|
|
19
|
+
recent.set(refreshToken, pair, RECENT_PAIR_TTL_MS);
|
|
20
|
+
return pair;
|
|
21
|
+
}
|
|
22
|
+
return (refreshToken) => {
|
|
23
|
+
const done = recent.get(refreshToken);
|
|
24
|
+
if (done)
|
|
25
|
+
return Promise.resolve(done);
|
|
26
|
+
let pending = inflight.get(refreshToken);
|
|
27
|
+
if (!pending) {
|
|
28
|
+
pending = exchange(refreshToken).finally(() => inflight.delete(refreshToken));
|
|
29
|
+
inflight.set(refreshToken, pending);
|
|
30
|
+
}
|
|
31
|
+
return pending;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=session-refresher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-refresher.js","sourceRoot":"","sources":["../../src/http/session-refresher.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAI/D,4FAA4F;AAC5F,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAClC,MAAM,eAAe,GAAG,KAAK,CAAC;AAI9B;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CACrC,UAA6C;IAE7C,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAuC,CAAC;IAChE,MAAM,MAAM,GAAG,qBAAqB,CAAc,EAAE,UAAU,EAAE,eAAe,EAAE,CAAC,CAAC;IAEnF,KAAK,UAAU,QAAQ,CAAC,YAAoB;QAC3C,MAAM,IAAI,GAAG,CAAC,MAAM,UAAU,EAAE,EAAE,cAAc,CAAC,YAAY,CAAC,CAAC,IAAI,IAAI,CAAC;QACxE,IAAI,IAAI;YAAE,MAAM,CAAC,GAAG,CAAC,YAAY,EAAE,IAAI,EAAE,kBAAkB,CAAC,CAAC;QAC7D,OAAO,IAAI,CAAC;IACb,CAAC;IAED,OAAO,CAAC,YAAY,EAAE,EAAE;QACvB,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QACtC,IAAI,IAAI;YAAE,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QACzC,IAAI,CAAC,OAAO,EAAE,CAAC;YACd,OAAO,GAAG,QAAQ,CAAC,YAAY,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC;YAC9E,QAAQ,CAAC,GAAG,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QACrC,CAAC;QACD,OAAO,OAAO,CAAC;IAChB,CAAC,CAAC;AACH,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"smtp-provider.d.ts","sourceRoot":"","sources":["../../src/notifications/smtp-provider.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"smtp-provider.d.ts","sourceRoot":"","sources":["../../src/notifications/smtp-provider.ts"],"names":[],"mappings":"AACA,OAAO,EAEN,KAAK,OAAO,EACZ,KAAK,qBAAqB,EAC1B,KAAK,eAAe,EACpB,KAAK,UAAU,EACf,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAkB,KAAK,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAShE;;;;;;;GAOG;AACH,qBAAa,wBAAyB,YAAW,qBAAqB;IAGzD,OAAO,CAAC,QAAQ,CAAC,GAAG;IAFhC,QAAQ,CAAC,IAAI,UAAU;gBAEM,GAAG,EAAE,OAAO;IAEnC,IAAI,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,GAAE,OAA0B,GAAG,OAAO,CAAC,UAAU,CAAC;IA8C1F,YAAY,IAAI,OAAO;CAOvB"}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
+
import { createHmac, randomBytes } from 'node:crypto';
|
|
1
2
|
import { NoopLogger } from '@selvajs/platform';
|
|
2
3
|
import { readSmtpConfig } from './smtp-config.js';
|
|
4
|
+
// Per process: the hash only has to match up within one run of the logs.
|
|
5
|
+
const RECIPIENT_HASH_KEY = randomBytes(32);
|
|
6
|
+
function recipientHash(to) {
|
|
7
|
+
return createHmac('sha256', RECIPIENT_HASH_KEY).update(to).digest('hex').slice(0, 16);
|
|
8
|
+
}
|
|
3
9
|
/**
|
|
4
10
|
* SMTP transport. Any server works — a transactional provider, an internal
|
|
5
11
|
* relay, or a mailbox with an app password.
|
|
@@ -47,16 +53,17 @@ export class SmtpNotificationProvider {
|
|
|
47
53
|
return { status: 'sent' };
|
|
48
54
|
}
|
|
49
55
|
catch (err) {
|
|
50
|
-
|
|
51
|
-
//
|
|
52
|
-
|
|
56
|
+
// Never the raw message: an SMTP rejection quotes the address, and erasure
|
|
57
|
+
// can't reach stdout. The keyed hash still matches up failures to one person.
|
|
58
|
+
const { code, responseCode } = err;
|
|
53
59
|
log.warn('Failed to send mail', {
|
|
54
60
|
component: 'SmtpNotificationProvider',
|
|
55
|
-
|
|
61
|
+
recipient: recipientHash(message.to),
|
|
56
62
|
kind: message.kind,
|
|
57
|
-
|
|
63
|
+
code,
|
|
64
|
+
responseCode
|
|
58
65
|
});
|
|
59
|
-
return { status: 'failed', reason };
|
|
66
|
+
return { status: 'failed', reason: code ?? 'send-failed' };
|
|
60
67
|
}
|
|
61
68
|
}
|
|
62
69
|
isConfigured() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"smtp-provider.js","sourceRoot":"","sources":["../../src/notifications/smtp-provider.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,UAAU,EAKV,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAgB,MAAM,kBAAkB,CAAC;AAEhE;;;;;;;GAOG;AACH,MAAM,OAAO,wBAAwB;IAGP;IAFpB,IAAI,GAAG,MAAM,CAAC;IAEvB,YAA6B,GAAY;QAAZ,QAAG,GAAH,GAAG,CAAS;IAAG,CAAC;IAE7C,KAAK,CAAC,IAAI,CAAC,OAAwB,EAAE,MAAe,IAAI,UAAU,EAAE;QACnE,IAAI,MAAM,CAAC;QACX,IAAI,CAAC;YACJ,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACnC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,GAAG,CAAC,KAAK,CAAC,uBAAuB,EAAE;gBAClC,SAAS,EAAE,0BAA0B;gBACrC,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;aACvD,CAAC,CAAC;YACH,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;QACtD,CAAC;QACD,IAAI,CAAC,MAAM;YAAE,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC;QAEjD,IAAI,CAAC;YACJ,qEAAqE;YACrE,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;YACvD,MAAM,SAAS,GAAG,eAAe,CAAC;gBACjC,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,IAAI,EAAE,MAAM,CAAC,IAAI;aACjB,CAAC,CAAC;YAEH,MAAM,SAAS,CAAC,QAAQ,CAAC;gBACxB,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,EAAE,EAAE,OAAO,CAAC,EAAE;gBACd,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,IAAI,EAAE,OAAO,CAAC,IAAI;aAClB,CAAC,CAAC;YACH,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAC3B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,
|
|
1
|
+
{"version":3,"file":"smtp-provider.js","sourceRoot":"","sources":["../../src/notifications/smtp-provider.ts"],"names":[],"mappings":"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"}
|
package/dist/oauth/index.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ export declare const REGISTERED_CLIENT_SCOPES: readonly ["read", "solve"];
|
|
|
25
25
|
/** The Grasshopper plugin's client id. Built in, so it needs no registration and works air-gapped. */
|
|
26
26
|
export declare const GRASSHOPPER_CLIENT_ID = "selva-grasshopper";
|
|
27
27
|
export declare const MCP_PATH = "/mcp";
|
|
28
|
+
export declare const CODE_TTL_MS = 60000;
|
|
28
29
|
export declare class OAuthError extends Error {
|
|
29
30
|
/** RFC 6749 / 7591 error code. */
|
|
30
31
|
readonly error: string;
|
|
@@ -78,6 +79,8 @@ export interface OAuthServerOptions {
|
|
|
78
79
|
* rebuilt server (a new origin, a rotated secret) still refuses a replay.
|
|
79
80
|
*/
|
|
80
81
|
redeemedCodes?: Map<string, number>;
|
|
82
|
+
/** Called once a code is exchanged, with the token its grant carries. */
|
|
83
|
+
onRedeemed?: (tokenId: string) => void;
|
|
81
84
|
now?: () => number;
|
|
82
85
|
}
|
|
83
86
|
interface HttpResult {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/oauth/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAYH,yEAAyE;AACzE,eAAO,MAAM,YAAY,sCAAuC,CAAC;AACjE,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvD;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,4BAA6D,CAAC;AAEnG,sGAAsG;AACtG,eAAO,MAAM,qBAAqB,sBAAsB,CAAC;AAYzD,eAAO,MAAM,QAAQ,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/oauth/index.ts"],"names":[],"mappings":"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"}
|
package/dist/oauth/index.js
CHANGED
|
@@ -34,7 +34,7 @@ const GRASSHOPPER_CLIENT = {
|
|
|
34
34
|
scopes: ['read', 'upload']
|
|
35
35
|
};
|
|
36
36
|
export const MCP_PATH = '/mcp';
|
|
37
|
-
const CODE_TTL_MS = 60_000;
|
|
37
|
+
export const CODE_TTL_MS = 60_000;
|
|
38
38
|
const CLIENT_NAME_MAX = 100;
|
|
39
39
|
const REDIRECT_URIS_MAX = 10;
|
|
40
40
|
const CIMD_TTL_MS = 60 * 60_000;
|
|
@@ -267,6 +267,7 @@ export function createOAuthServer(options) {
|
|
|
267
267
|
throw new OAuthError('invalid_grant', 'The code was already used.');
|
|
268
268
|
}
|
|
269
269
|
const { g } = sealed;
|
|
270
|
+
options.onRedeemed?.(g.tokenId);
|
|
270
271
|
return {
|
|
271
272
|
status: 200,
|
|
272
273
|
body: {
|
|
@@ -340,16 +341,18 @@ async function readMetadata(url, fetchClientMetadata) {
|
|
|
340
341
|
/**
|
|
341
342
|
* What the request asks for, within what its client may have. A scope outside
|
|
342
343
|
* that is dropped, not refused, so a client that asks for every advertised
|
|
343
|
-
* scope still signs in with what it may have.
|
|
344
|
-
*
|
|
344
|
+
* scope still signs in with what it may have. No `scope` at all means all of
|
|
345
|
+
* them. Asking only for scopes it may not have gets `read`, never more than it
|
|
346
|
+
* asked for. `read` rides along with `solve`, which the consent page offers as
|
|
345
347
|
* "read and run solves".
|
|
346
348
|
*/
|
|
347
349
|
function parseScopes(raw, allowed) {
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
.filter((s) => allowed.includes(s));
|
|
351
|
-
if (!asked.length)
|
|
350
|
+
const requested = (raw ?? '').split(' ').filter(Boolean);
|
|
351
|
+
if (!requested.length)
|
|
352
352
|
return [...allowed];
|
|
353
|
+
const asked = requested.filter((s) => allowed.includes(s));
|
|
354
|
+
if (!asked.length)
|
|
355
|
+
return allowed.includes('read') ? ['read'] : [];
|
|
353
356
|
return allowed.filter((s) => asked.includes(s) || (s === 'read' && asked.includes('solve')));
|
|
354
357
|
}
|
|
355
358
|
/**
|
|
@@ -442,7 +445,10 @@ function seal(key, value) {
|
|
|
442
445
|
function open(key, sealed) {
|
|
443
446
|
try {
|
|
444
447
|
const raw = Buffer.from(sealed, 'base64url');
|
|
445
|
-
|
|
448
|
+
// Without the length, a short code is checked against however many tag bytes it has.
|
|
449
|
+
const decipher = createDecipheriv('aes-256-gcm', key, raw.subarray(0, 12), {
|
|
450
|
+
authTagLength: 16
|
|
451
|
+
});
|
|
446
452
|
decipher.setAuthTag(raw.subarray(12, 28));
|
|
447
453
|
const body = Buffer.concat([decipher.update(raw.subarray(28)), decipher.final()]);
|
|
448
454
|
return JSON.parse(body.toString('utf8'));
|