@aphexcms/cms-core 9.8.0 → 9.9.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/api/organizations.d.ts +2 -1
- package/dist/api/organizations.d.ts.map +1 -1
- package/dist/api/organizations.js +2 -1
- package/dist/api/schemas/jobs.d.ts +15 -0
- package/dist/api/schemas/jobs.d.ts.map +1 -0
- package/dist/api/schemas/jobs.js +17 -0
- package/dist/api/schemas/user.d.ts.map +1 -1
- package/dist/api/schemas/user.js +6 -2
- package/dist/auth/account-deletion.d.ts +35 -0
- package/dist/auth/account-deletion.d.ts.map +1 -0
- package/dist/auth/account-deletion.js +65 -0
- package/dist/auth/bootstrap.d.ts +104 -0
- package/dist/auth/bootstrap.d.ts.map +1 -0
- package/dist/auth/bootstrap.js +164 -0
- package/dist/auth/instance-state.d.ts +13 -0
- package/dist/auth/instance-state.d.ts.map +1 -0
- package/dist/auth/instance-state.js +30 -0
- package/dist/auth/invitation-status.d.ts +25 -0
- package/dist/auth/invitation-status.d.ts.map +1 -0
- package/dist/auth/invitation-status.js +41 -0
- package/dist/auth/sign-up-policy.d.ts +62 -0
- package/dist/auth/sign-up-policy.d.ts.map +1 -0
- package/dist/auth/sign-up-policy.js +120 -0
- package/dist/components/admin/ActivityView.svelte +263 -13
- package/dist/components/admin/ActivityView.svelte.d.ts +10 -1
- package/dist/components/admin/ActivityView.svelte.d.ts.map +1 -1
- package/dist/db/interfaces/events.d.ts +29 -2
- package/dist/db/interfaces/events.d.ts.map +1 -1
- package/dist/db/interfaces/index.d.ts +1 -0
- package/dist/db/interfaces/index.d.ts.map +1 -1
- package/dist/db/interfaces/index.js +1 -0
- package/dist/db/interfaces/instance.d.ts +29 -0
- package/dist/db/interfaces/instance.d.ts.map +1 -1
- package/dist/db/interfaces/instance.js +8 -1
- package/dist/events/built-in-consumers.d.ts +20 -0
- package/dist/events/built-in-consumers.d.ts.map +1 -0
- package/dist/events/built-in-consumers.js +49 -0
- package/dist/events/catalog.d.ts +16 -0
- package/dist/events/catalog.d.ts.map +1 -1
- package/dist/events/catalog.js +17 -0
- package/dist/events/consumer.d.ts +14 -0
- package/dist/events/consumer.d.ts.map +1 -1
- package/dist/events/consumer.js +1 -0
- package/dist/events/emit.d.ts +23 -0
- package/dist/events/emit.d.ts.map +1 -1
- package/dist/events/emit.js +32 -1
- package/dist/events/index.d.ts +2 -0
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js +2 -0
- package/dist/field-validation/utils.d.ts +27 -4
- package/dist/field-validation/utils.d.ts.map +1 -1
- package/dist/field-validation/utils.js +78 -10
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/jobs/run-batch.d.ts +7 -1
- package/dist/jobs/run-batch.d.ts.map +1 -1
- package/dist/jobs/run-batch.js +3 -1
- package/dist/lib/api/organizations.d.ts +2 -1
- package/dist/lib/api/organizations.d.ts.map +1 -1
- package/dist/lib/api/organizations.js +2 -1
- package/dist/lib/api/organizations.js.map +1 -1
- package/dist/lib/api/schemas/api-keys.d.ts +4 -4
- package/dist/lib/api/schemas/jobs.d.ts +15 -0
- package/dist/lib/api/schemas/jobs.d.ts.map +1 -0
- package/dist/lib/api/schemas/jobs.js +18 -0
- package/dist/lib/api/schemas/jobs.js.map +1 -0
- package/dist/lib/api/schemas/user.d.ts.map +1 -1
- package/dist/lib/api/schemas/user.js +6 -2
- package/dist/lib/api/schemas/user.js.map +1 -1
- package/dist/lib/auth/account-deletion.d.ts +35 -0
- package/dist/lib/auth/account-deletion.d.ts.map +1 -0
- package/dist/lib/auth/account-deletion.js +66 -0
- package/dist/lib/auth/account-deletion.js.map +1 -0
- package/dist/lib/auth/bootstrap.d.ts +104 -0
- package/dist/lib/auth/bootstrap.d.ts.map +1 -0
- package/dist/lib/auth/bootstrap.js +165 -0
- package/dist/lib/auth/bootstrap.js.map +1 -0
- package/dist/lib/auth/instance-state.d.ts +13 -0
- package/dist/lib/auth/instance-state.d.ts.map +1 -0
- package/dist/lib/auth/instance-state.js +31 -0
- package/dist/lib/auth/instance-state.js.map +1 -0
- package/dist/lib/auth/invitation-status.d.ts +25 -0
- package/dist/lib/auth/invitation-status.d.ts.map +1 -0
- package/dist/lib/auth/invitation-status.js +42 -0
- package/dist/lib/auth/invitation-status.js.map +1 -0
- package/dist/lib/auth/sign-up-policy.d.ts +62 -0
- package/dist/lib/auth/sign-up-policy.d.ts.map +1 -0
- package/dist/lib/auth/sign-up-policy.js +121 -0
- package/dist/lib/auth/sign-up-policy.js.map +1 -0
- package/dist/lib/db/interfaces/events.d.ts +29 -2
- package/dist/lib/db/interfaces/events.d.ts.map +1 -1
- package/dist/lib/db/interfaces/index.d.ts +1 -0
- package/dist/lib/db/interfaces/index.d.ts.map +1 -1
- package/dist/lib/db/interfaces/index.js +1 -0
- package/dist/lib/db/interfaces/index.js.map +1 -1
- package/dist/lib/db/interfaces/instance.d.ts +29 -0
- package/dist/lib/db/interfaces/instance.d.ts.map +1 -1
- package/dist/lib/db/interfaces/instance.js +8 -1
- package/dist/lib/db/interfaces/instance.js.map +1 -1
- package/dist/lib/events/built-in-consumers.d.ts +20 -0
- package/dist/lib/events/built-in-consumers.d.ts.map +1 -0
- package/dist/lib/events/built-in-consumers.js +50 -0
- package/dist/lib/events/built-in-consumers.js.map +1 -0
- package/dist/lib/events/catalog.d.ts +16 -0
- package/dist/lib/events/catalog.d.ts.map +1 -1
- package/dist/lib/events/catalog.js +17 -0
- package/dist/lib/events/catalog.js.map +1 -1
- package/dist/lib/events/consumer.d.ts +14 -0
- package/dist/lib/events/consumer.d.ts.map +1 -1
- package/dist/lib/events/consumer.js +1 -0
- package/dist/lib/events/consumer.js.map +1 -1
- package/dist/lib/events/emit.d.ts +23 -0
- package/dist/lib/events/emit.d.ts.map +1 -1
- package/dist/lib/events/emit.js +32 -1
- package/dist/lib/events/emit.js.map +1 -1
- package/dist/lib/events/index.d.ts +2 -0
- package/dist/lib/events/index.d.ts.map +1 -1
- package/dist/lib/events/index.js +2 -0
- package/dist/lib/events/index.js.map +1 -1
- package/dist/lib/field-validation/utils.d.ts +27 -4
- package/dist/lib/field-validation/utils.d.ts.map +1 -1
- package/dist/lib/field-validation/utils.js +78 -10
- package/dist/lib/field-validation/utils.js.map +1 -1
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +3 -0
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/jobs/run-batch.d.ts +7 -1
- package/dist/lib/jobs/run-batch.d.ts.map +1 -1
- package/dist/lib/jobs/run-batch.js +3 -1
- package/dist/lib/jobs/run-batch.js.map +1 -1
- package/dist/lib/local-api/collection-api.d.ts +42 -1
- package/dist/lib/local-api/collection-api.d.ts.map +1 -1
- package/dist/lib/local-api/collection-api.js +68 -8
- package/dist/lib/local-api/collection-api.js.map +1 -1
- package/dist/lib/local-api/index.d.ts +1 -1
- package/dist/lib/local-api/index.d.ts.map +1 -1
- package/dist/lib/local-api/index.js +1 -1
- package/dist/lib/local-api/index.js.map +1 -1
- package/dist/lib/plugins/resolver.d.ts.map +1 -1
- package/dist/lib/plugins/resolver.js +7 -1
- package/dist/lib/plugins/resolver.js.map +1 -1
- package/dist/lib/server/api/rate-limit.d.ts +38 -0
- package/dist/lib/server/api/rate-limit.d.ts.map +1 -0
- package/dist/lib/server/api/rate-limit.js +71 -0
- package/dist/lib/server/api/rate-limit.js.map +1 -0
- package/dist/lib/server/api/routes/agent-change-sets.js +1 -1
- package/dist/lib/server/api/routes/agent-change-sets.js.map +1 -1
- package/dist/lib/server/api/routes/document-versions.js +1 -1
- package/dist/lib/server/api/routes/document-versions.js.map +1 -1
- package/dist/lib/server/api/routes/documents-by-id.d.ts.map +1 -1
- package/dist/lib/server/api/routes/documents-by-id.js +11 -2
- package/dist/lib/server/api/routes/documents-by-id.js.map +1 -1
- package/dist/lib/server/api/routes/documents-publish.d.ts.map +1 -1
- package/dist/lib/server/api/routes/documents-publish.js +11 -1
- package/dist/lib/server/api/routes/documents-publish.js.map +1 -1
- package/dist/lib/server/api/routes/documents.d.ts.map +1 -1
- package/dist/lib/server/api/routes/documents.js +10 -0
- package/dist/lib/server/api/routes/documents.js.map +1 -1
- package/dist/lib/server/api/routes/jobs.d.ts.map +1 -1
- package/dist/lib/server/api/routes/jobs.js +174 -20
- package/dist/lib/server/api/routes/jobs.js.map +1 -1
- package/dist/lib/server/api/routes/organizations-by-id.d.ts.map +1 -1
- package/dist/lib/server/api/routes/organizations-by-id.js +61 -3
- package/dist/lib/server/api/routes/organizations-by-id.js.map +1 -1
- package/dist/lib/server/api/routes/organizations-invitations.d.ts.map +1 -1
- package/dist/lib/server/api/routes/organizations-invitations.js +31 -2
- package/dist/lib/server/api/routes/organizations-invitations.js.map +1 -1
- package/dist/lib/server/api/routes/resolve-organization-names.d.ts +17 -0
- package/dist/lib/server/api/routes/resolve-organization-names.d.ts.map +1 -0
- package/dist/lib/server/api/routes/resolve-organization-names.js +26 -0
- package/dist/lib/server/api/routes/resolve-organization-names.js.map +1 -0
- package/dist/lib/server/api/routes/user.d.ts +0 -8
- package/dist/lib/server/api/routes/user.d.ts.map +1 -1
- package/dist/lib/server/api/routes/user.js +144 -0
- package/dist/lib/server/api/routes/user.js.map +1 -1
- package/dist/lib/server/index.d.ts +6 -1
- package/dist/lib/server/index.d.ts.map +1 -1
- package/dist/lib/server/index.js +9 -1
- package/dist/lib/server/index.js.map +1 -1
- package/dist/lib/services/references-service.d.ts +1 -1
- package/dist/lib/services/references-service.d.ts.map +1 -1
- package/dist/lib/types/capabilities.d.ts +14 -0
- package/dist/lib/types/capabilities.d.ts.map +1 -1
- package/dist/lib/types/capabilities.js +37 -18
- package/dist/lib/types/capabilities.js.map +1 -1
- package/dist/lib/types/events.d.ts +27 -4
- package/dist/lib/types/events.d.ts.map +1 -1
- package/dist/lib/utils/fetch-remote-file.d.ts.map +1 -1
- package/dist/lib/utils/fetch-remote-file.js +32 -3
- package/dist/lib/utils/fetch-remote-file.js.map +1 -1
- package/dist/local-api/collection-api.d.ts +42 -1
- package/dist/local-api/collection-api.d.ts.map +1 -1
- package/dist/local-api/collection-api.js +68 -8
- package/dist/local-api/index.d.ts +1 -1
- package/dist/local-api/index.d.ts.map +1 -1
- package/dist/local-api/index.js +1 -1
- package/dist/plugins/resolver.d.ts.map +1 -1
- package/dist/plugins/resolver.js +7 -1
- package/dist/server/api/rate-limit.d.ts +38 -0
- package/dist/server/api/rate-limit.d.ts.map +1 -0
- package/dist/server/api/rate-limit.js +70 -0
- package/dist/server/api/routes/agent-change-sets.js +1 -1
- package/dist/server/api/routes/document-versions.js +1 -1
- package/dist/server/api/routes/documents-by-id.d.ts.map +1 -1
- package/dist/server/api/routes/documents-by-id.js +11 -2
- package/dist/server/api/routes/documents-publish.d.ts.map +1 -1
- package/dist/server/api/routes/documents-publish.js +11 -1
- package/dist/server/api/routes/documents.d.ts.map +1 -1
- package/dist/server/api/routes/documents.js +10 -0
- package/dist/server/api/routes/jobs.d.ts.map +1 -1
- package/dist/server/api/routes/jobs.js +174 -20
- package/dist/server/api/routes/organizations-by-id.d.ts.map +1 -1
- package/dist/server/api/routes/organizations-by-id.js +61 -3
- package/dist/server/api/routes/organizations-invitations.d.ts.map +1 -1
- package/dist/server/api/routes/organizations-invitations.js +31 -2
- package/dist/server/api/routes/resolve-organization-names.d.ts +17 -0
- package/dist/server/api/routes/resolve-organization-names.d.ts.map +1 -0
- package/dist/server/api/routes/resolve-organization-names.js +25 -0
- package/dist/server/api/routes/user.d.ts +0 -8
- package/dist/server/api/routes/user.d.ts.map +1 -1
- package/dist/server/api/routes/user.js +144 -0
- package/dist/server/index.d.ts +6 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +9 -1
- package/dist/services/references-service.d.ts +1 -1
- package/dist/services/references-service.d.ts.map +1 -1
- package/dist/types/capabilities.d.ts +14 -0
- package/dist/types/capabilities.d.ts.map +1 -1
- package/dist/types/capabilities.js +37 -18
- package/dist/types/events.d.ts +27 -4
- package/dist/types/events.d.ts.map +1 -1
- package/dist/utils/fetch-remote-file.d.ts.map +1 -1
- package/dist/utils/fetch-remote-file.js +32 -3
- package/package.json +6 -6
|
@@ -2,6 +2,47 @@ import { Hono } from 'hono';
|
|
|
2
2
|
import { zValidator } from '@hono/zod-validator';
|
|
3
3
|
import { cmsLogger } from '../../../utils/logger.js';
|
|
4
4
|
import { updateUserRequest, requestPasswordResetRequest, resetPasswordRequest } from '../../../api/schemas/user.js';
|
|
5
|
+
import { RateLimiter, clientAddress } from '../rate-limit.js';
|
|
6
|
+
// Throttles for the two unauthenticated facades below. Both call the auth provider
|
|
7
|
+
// *server-side*, which bypasses the provider's own HTTP rate limiter entirely (it only
|
|
8
|
+
// engages for calls carrying a real request) — so without these they are unlimited.
|
|
9
|
+
//
|
|
10
|
+
// Two buckets per endpoint, because either one alone is porous: the address bucket stops
|
|
11
|
+
// one host hammering many mailboxes, and the email/token bucket still holds when the
|
|
12
|
+
// address is forged, which `x-forwarded-for` always permits.
|
|
13
|
+
const RESET_REQUEST_WINDOW_MS = 60_000;
|
|
14
|
+
const resetRequestByAddress = new RateLimiter({ windowMs: RESET_REQUEST_WINDOW_MS, max: 5 });
|
|
15
|
+
// Tighter than the address bucket: nobody legitimately asks for the same mailbox's reset
|
|
16
|
+
// link three times a minute, and this is the half an attacker can't sidestep.
|
|
17
|
+
const resetRequestByEmail = new RateLimiter({ windowMs: RESET_REQUEST_WINDOW_MS, max: 2 });
|
|
18
|
+
// The redemption side is a token-guessing surface rather than an email one, so it's keyed
|
|
19
|
+
// on the address and on the token itself — a single token being retried is the signature of
|
|
20
|
+
// a brute-force attempt, not of a user clicking their link again.
|
|
21
|
+
const RESET_SUBMIT_WINDOW_MS = 60_000;
|
|
22
|
+
const resetSubmitByAddress = new RateLimiter({ windowMs: RESET_SUBMIT_WINDOW_MS, max: 10 });
|
|
23
|
+
const resetSubmitByToken = new RateLimiter({ windowMs: RESET_SUBMIT_WINDOW_MS, max: 5 });
|
|
24
|
+
/**
|
|
25
|
+
* Apply every bucket and return a 429 response if any of them is exhausted.
|
|
26
|
+
*
|
|
27
|
+
* All buckets are consumed, not short-circuited: a request that trips the first limit must
|
|
28
|
+
* still count against the others, or an attacker could keep a secondary bucket permanently
|
|
29
|
+
* fresh by deliberately tripping the primary one.
|
|
30
|
+
*/
|
|
31
|
+
function enforceRateLimits(c, checks) {
|
|
32
|
+
let retryAfter = 0;
|
|
33
|
+
for (const { limiter, key } of checks) {
|
|
34
|
+
const result = limiter.check(key);
|
|
35
|
+
if (!result.allowed)
|
|
36
|
+
retryAfter = Math.max(retryAfter, result.retryAfterSeconds);
|
|
37
|
+
}
|
|
38
|
+
if (retryAfter === 0)
|
|
39
|
+
return null;
|
|
40
|
+
return c.json({
|
|
41
|
+
success: false,
|
|
42
|
+
error: 'Too many requests',
|
|
43
|
+
message: `Too many attempts. Try again in ${retryAfter} second${retryAfter === 1 ? '' : 's'}.`
|
|
44
|
+
}, 429, { 'Retry-After': String(retryAfter) });
|
|
45
|
+
}
|
|
5
46
|
/**
|
|
6
47
|
* User account routes that delegate to the configured AuthProvider.
|
|
7
48
|
*
|
|
@@ -10,6 +51,72 @@ import { updateUserRequest, requestPasswordResetRequest, resetPasswordRequest }
|
|
|
10
51
|
* (e.g. the password-reset email) are owned by the AuthProvider impl —
|
|
11
52
|
* cms-core's role is just to expose them over HTTP.
|
|
12
53
|
*/
|
|
54
|
+
/**
|
|
55
|
+
* Avatars are stored as the CDN path `/media/<assetId>/<filename>`, which is what
|
|
56
|
+
* makes the underlying asset recoverable from the profile field. Anything else —
|
|
57
|
+
* an absolute storage URL from before this format, or an external provider's
|
|
58
|
+
* avatar — has no asset of ours behind it and is left alone.
|
|
59
|
+
*/
|
|
60
|
+
function avatarAssetId(image) {
|
|
61
|
+
return /^\/media\/([^/]+)\//.exec(image)?.[1] ?? null;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Is `assetId` an asset this user uploaded as their own avatar?
|
|
65
|
+
*
|
|
66
|
+
* The gate on both pointing the profile field at an asset and deleting the one
|
|
67
|
+
* it used to point at. Both checks read *server-written* facts only:
|
|
68
|
+
*
|
|
69
|
+
* - `createdBy` is stamped from the session by the upload route, so it can't be
|
|
70
|
+
* forged onto somebody else's asset.
|
|
71
|
+
* - `metadata.system` marks the asset as hidden infrastructure rather than
|
|
72
|
+
* content, which is what an avatar is.
|
|
73
|
+
*
|
|
74
|
+
* Without this, `image` was simply a string the client chose: an attacker could
|
|
75
|
+
* point their avatar at any asset in the organization and then change it again,
|
|
76
|
+
* and the replace-cleanup below would delete that asset for them — a delete with
|
|
77
|
+
* none of the `asset.delete` capability checks the assets API enforces, usable
|
|
78
|
+
* by any authenticated user including a viewer.
|
|
79
|
+
*/
|
|
80
|
+
async function isOwnAvatarAsset(assetService, organizationId, userId, assetId) {
|
|
81
|
+
const asset = await assetService.findAssetById(organizationId, assetId);
|
|
82
|
+
if (!asset || asset.createdBy !== userId)
|
|
83
|
+
return false;
|
|
84
|
+
const metadata = asset.metadata;
|
|
85
|
+
return (typeof metadata === 'object' &&
|
|
86
|
+
metadata !== null &&
|
|
87
|
+
metadata.system === true);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Delete the asset behind a superseded avatar.
|
|
91
|
+
*
|
|
92
|
+
* Server-side rather than in the client for two reasons. The `asset.delete`
|
|
93
|
+
* capability gates the assets API, so a viewer replacing their own avatar could
|
|
94
|
+
* never clean up after themselves — their old pictures would accumulate forever.
|
|
95
|
+
* And doing it here means every caller of this route inherits the behaviour
|
|
96
|
+
* instead of each one remembering to orchestrate it.
|
|
97
|
+
*
|
|
98
|
+
* Re-checks ownership rather than trusting that the stored value passed the
|
|
99
|
+
* inbound check: rows written before that check existed are still out there.
|
|
100
|
+
*
|
|
101
|
+
* Best-effort by design: the profile field has already moved, so a failure here
|
|
102
|
+
* leaks a file but never leaves a broken avatar. Failing the request would
|
|
103
|
+
* report the leak by making the user's save look broken, which is worse.
|
|
104
|
+
*/
|
|
105
|
+
async function discardAvatarAsset(assetService, organizationId, userId, image) {
|
|
106
|
+
const id = avatarAssetId(image);
|
|
107
|
+
if (!id)
|
|
108
|
+
return;
|
|
109
|
+
try {
|
|
110
|
+
if (!(await isOwnAvatarAsset(assetService, organizationId, userId, id))) {
|
|
111
|
+
cmsLogger.warn('[User API] Refusing to delete superseded avatar asset that is not the user’s own:', id);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
await assetService.deleteAsset(organizationId, id);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
cmsLogger.warn('[User API] Could not delete superseded avatar asset:', error);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
13
120
|
export const userRouter = new Hono()
|
|
14
121
|
.patch('/', zValidator('json', updateUserRequest, (result, c) => {
|
|
15
122
|
if (!result.success) {
|
|
@@ -48,7 +155,31 @@ export const userRouter = new Hono()
|
|
|
48
155
|
error: 'Auth provider does not support profile image updates'
|
|
49
156
|
}, 500);
|
|
50
157
|
}
|
|
158
|
+
// An avatar may only point at an asset this user uploaded as an avatar.
|
|
159
|
+
// `image` is a free-form string from the client, and the replace
|
|
160
|
+
// cleanup below deletes whatever the *previous* value pointed at — so
|
|
161
|
+
// without this, setting the field to another asset's path and then
|
|
162
|
+
// changing it again is an unauthenticated-by-capability delete of any
|
|
163
|
+
// asset in the organization. Non-asset values (an external provider's
|
|
164
|
+
// URL, or the absolute storage URLs avatars used to use) have no asset
|
|
165
|
+
// of ours behind them and pass through.
|
|
166
|
+
const incomingAssetId = image === null ? null : avatarAssetId(image);
|
|
167
|
+
if (incomingAssetId &&
|
|
168
|
+
!(await isOwnAvatarAsset(c.var.aphexCMS.assetService, auth.organizationId, auth.user.id, incomingAssetId))) {
|
|
169
|
+
return c.json({
|
|
170
|
+
success: false,
|
|
171
|
+
error: 'Forbidden',
|
|
172
|
+
message: 'That image is not one of your uploaded avatars'
|
|
173
|
+
}, 403);
|
|
174
|
+
}
|
|
175
|
+
// Read the outgoing avatar before overwriting the field — once it's
|
|
176
|
+
// gone the asset is unreachable, and an avatar nothing points at is
|
|
177
|
+
// personal data with no way left to find or erase it.
|
|
178
|
+
const current = await provider.getUserById(auth.user.id);
|
|
51
179
|
await provider.changeUserImage(auth.user.id, image);
|
|
180
|
+
if (current?.image && current.image !== image) {
|
|
181
|
+
await discardAvatarAsset(c.var.aphexCMS.assetService, auth.organizationId, auth.user.id, current.image);
|
|
182
|
+
}
|
|
52
183
|
}
|
|
53
184
|
return c.json({ success: true, message: 'User updated successfully' });
|
|
54
185
|
}
|
|
@@ -77,6 +208,13 @@ export const userRouter = new Hono()
|
|
|
77
208
|
return c.json({ success: false, error: 'Auth provider not configured' }, 500);
|
|
78
209
|
}
|
|
79
210
|
const { email, redirectTo } = c.req.valid('json');
|
|
211
|
+
// Keyed on the normalized address so casing/whitespace can't mint a fresh bucket.
|
|
212
|
+
const limited = enforceRateLimits(c, [
|
|
213
|
+
{ limiter: resetRequestByAddress, key: clientAddress(c.req.raw.headers) },
|
|
214
|
+
{ limiter: resetRequestByEmail, key: email.trim().toLowerCase() }
|
|
215
|
+
]);
|
|
216
|
+
if (limited)
|
|
217
|
+
return limited;
|
|
80
218
|
await provider.requestPasswordReset(email, redirectTo);
|
|
81
219
|
// Constant response shape regardless of whether the email exists,
|
|
82
220
|
// to avoid account enumeration via the API.
|
|
@@ -110,6 +248,12 @@ export const userRouter = new Hono()
|
|
|
110
248
|
return c.json({ success: false, error: 'Auth provider not configured' }, 500);
|
|
111
249
|
}
|
|
112
250
|
const { token, newPassword } = c.req.valid('json');
|
|
251
|
+
const limited = enforceRateLimits(c, [
|
|
252
|
+
{ limiter: resetSubmitByAddress, key: clientAddress(c.req.raw.headers) },
|
|
253
|
+
{ limiter: resetSubmitByToken, key: token }
|
|
254
|
+
]);
|
|
255
|
+
if (limited)
|
|
256
|
+
return limited;
|
|
113
257
|
await provider.resetPassword(token, newPassword);
|
|
114
258
|
return c.json({ success: true, message: 'Password reset successfully' });
|
|
115
259
|
}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -4,6 +4,10 @@ export * from '../cache/index.js';
|
|
|
4
4
|
export * from '../email/index.js';
|
|
5
5
|
export * from '../ai/index.js';
|
|
6
6
|
export { AuthError, type AuthErrorCode } from '../auth/auth-errors.js';
|
|
7
|
+
export { isInstanceEmpty, canDetermineInstanceEmptiness } from '../auth/instance-state.js';
|
|
8
|
+
export * from '../auth/bootstrap.js';
|
|
9
|
+
export * from '../auth/sign-up-policy.js';
|
|
10
|
+
export * from '../auth/account-deletion.js';
|
|
7
11
|
export { createCMSConfig } from '../config.js';
|
|
8
12
|
export { cmsLogger, setLogger, type Logger } from '../utils/logger.js';
|
|
9
13
|
export { CMSEngine } from '../engine.js';
|
|
@@ -17,11 +21,12 @@ export { AssetService } from '../services/asset-service.js';
|
|
|
17
21
|
export { RolesService } from '../services/roles-service.js';
|
|
18
22
|
export { GET as serveAssetCDN } from '../routes/assets-cdn.js';
|
|
19
23
|
export { createAphexApi, mountAphexBuiltins, toHonoHandler, type AphexEnv } from './api/index.js';
|
|
24
|
+
export { RateLimiter, clientAddress, type RateLimitRule, type RateLimitResult } from './api/rate-limit.js';
|
|
20
25
|
export * from '../jobs/index.js';
|
|
21
26
|
export * from '../schema-utils/index.js';
|
|
22
27
|
export { createHashForPublishing } from '../utils/content-hash.js';
|
|
23
28
|
export { getPreviewPerspective, type PreviewPerspective } from '../preview/perspective.js';
|
|
24
29
|
export { createGraphQLHandler, type GraphQLConfig, type GraphQLSettings, type GraphQLHandlerResult } from '../graphql/index.js';
|
|
25
|
-
export { LocalAPI, createLocalAPI, getLocalAPI, CollectionAPI, SingletonOperationError, PermissionChecker, PermissionError, authToContext, requireAuth, systemContext, type Collections, type SingletonCollection, type LocalAPIContext, type CreateOptions, type UpdateOptions } from '../local-api/index.js';
|
|
30
|
+
export { LocalAPI, createLocalAPI, getLocalAPI, CollectionAPI, SingletonOperationError, DocumentValidationError, PermissionChecker, PermissionError, authToContext, requireAuth, systemContext, type Collections, type SingletonCollection, type LocalAPIContext, type CreateOptions, type UpdateOptions } from '../local-api/index.js';
|
|
26
31
|
export { buildContentTools, type McpTool, type McpToolResult, type McpToolDeps } from '../mcp/tools.js';
|
|
27
32
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/server/index.ts"],"names":[],"mappings":"AAIA,cAAc,gBAAgB,CAAC;AAE/B,cAAc,kBAAkB,CAAC;AAEjC,cAAc,gBAAgB,CAAC;AAE/B,cAAc,gBAAgB,CAAC;AAE/B,cAAc,aAAa,CAAC;AAG5B,OAAO,EAAE,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/server/index.ts"],"names":[],"mappings":"AAIA,cAAc,gBAAgB,CAAC;AAE/B,cAAc,kBAAkB,CAAC;AAEjC,cAAc,gBAAgB,CAAC;AAE/B,cAAc,gBAAgB,CAAC;AAE/B,cAAc,aAAa,CAAC;AAG5B,OAAO,EAAE,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,6BAA6B,EAAE,MAAM,wBAAwB,CAAC;AACxF,cAAc,mBAAmB,CAAC;AAClC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AAGzC,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAG5C,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAGpE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAGtC,OAAO,EAAE,aAAa,EAAE,0BAA0B,EAAE,KAAK,YAAY,EAAE,MAAM,UAAU,CAAC;AAGxF,cAAc,wBAAwB,CAAC;AAGvC,cAAc,kBAAkB,CAAC;AACjC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAG7C,cAAc,mBAAmB,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAKzD,OAAO,EAAE,GAAG,IAAI,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAI5D,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,aAAa,EAAE,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAK/F,OAAO,EACN,WAAW,EACX,aAAa,EACb,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,MAAM,kBAAkB,CAAC;AAG1B,cAAc,eAAe,CAAC;AAG9B,cAAc,uBAAuB,CAAC;AAGtC,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAGhE,OAAO,EAAE,qBAAqB,EAAE,KAAK,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAGxF,OAAO,EACN,oBAAoB,EACpB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACN,QAAQ,EACR,cAAc,EACd,WAAW,EACX,aAAa,EACb,uBAAuB,EACvB,uBAAuB,EACvB,iBAAiB,EACjB,eAAe,EACf,aAAa,EACb,WAAW,EACX,aAAa,EACb,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EACN,iBAAiB,EACjB,KAAK,OAAO,EACZ,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,MAAM,cAAc,CAAC"}
|
package/dist/server/index.js
CHANGED
|
@@ -8,6 +8,10 @@ export * from '../email/index.js';
|
|
|
8
8
|
export * from '../ai/index.js';
|
|
9
9
|
// Authentication errors
|
|
10
10
|
export { AuthError } from '../auth/auth-errors.js';
|
|
11
|
+
export { isInstanceEmpty, canDetermineInstanceEmptiness } from '../auth/instance-state.js';
|
|
12
|
+
export * from '../auth/bootstrap.js';
|
|
13
|
+
export * from '../auth/sign-up-policy.js';
|
|
14
|
+
export * from '../auth/account-deletion.js';
|
|
11
15
|
// Configuration system
|
|
12
16
|
export { createCMSConfig } from '../config.js';
|
|
13
17
|
// Logger
|
|
@@ -33,6 +37,10 @@ export { GET as serveAssetCDN } from '../routes/assets-cdn.js';
|
|
|
33
37
|
// Hono API app — exposed so user apps (and tests) can construct or extend
|
|
34
38
|
// the same router the SK catch-all forwards to.
|
|
35
39
|
export { createAphexApi, mountAphexBuiltins, toHonoHandler } from './api/index.js';
|
|
40
|
+
// Fixed-window throttle for unauthenticated endpoints. Exposed because an app adding its own
|
|
41
|
+
// public route needs the same guard the password-reset facades use — and because the caveat
|
|
42
|
+
// travels with it: this is per-process memory, so N instances give N× the configured limit.
|
|
43
|
+
export { RateLimiter, clientAddress } from './api/rate-limit.js';
|
|
36
44
|
// Job execution — the DB-backed job runner (claim → run handler → complete/retry/fail).
|
|
37
45
|
export * from '../jobs/index.js';
|
|
38
46
|
// Schema utilities
|
|
@@ -44,7 +52,7 @@ export { getPreviewPerspective } from '../preview/perspective.js';
|
|
|
44
52
|
// GraphQL (built-in, enabled by default)
|
|
45
53
|
export { createGraphQLHandler } from '../graphql/index.js';
|
|
46
54
|
// Local API (unified operations layer)
|
|
47
|
-
export { LocalAPI, createLocalAPI, getLocalAPI, CollectionAPI, SingletonOperationError, PermissionChecker, PermissionError, authToContext, requireAuth, systemContext } from '../local-api/index.js';
|
|
55
|
+
export { LocalAPI, createLocalAPI, getLocalAPI, CollectionAPI, SingletonOperationError, DocumentValidationError, PermissionChecker, PermissionError, authToContext, requireAuth, systemContext } from '../local-api/index.js';
|
|
48
56
|
// MCP — transport-agnostic tool registry (the MCP route + a future AI panel
|
|
49
57
|
// both consume these). The SvelteKit route handler is at ./routes/mcp.
|
|
50
58
|
export { buildContentTools } from '../mcp/tools.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"references-service.d.ts","sourceRoot":"","sources":["../../src/lib/services/references-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"references-service.d.ts","sourceRoot":"","sources":["../../src/lib/services/references-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAInD;;;;;;;;;GASG;AACH,qBAAa,iBAAiB;IACjB,OAAO,CAAC,eAAe;gBAAf,eAAe,EAAE,eAAe;IAEpD;;;OAGG;IACG,iBAAiB,CACtB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,UAAU,GAAG,IAAI,EACzB,QAAQ,EAAE,UAAU,EAAE,GACpB,OAAO,CAAC,IAAI,CAAC;IAShB;;;;;;;OAOG;IACG,eAAe,CACpB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,UAAU,EAAE,EACrB,gBAAgB,EAAE,MAAM,OAAO,CAAC,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC,GACjF,OAAO,CAAC,IAAI,CAAC;CAwBhB"}
|
|
@@ -111,6 +111,20 @@ export declare function isInstanceRole(auth: Auth): boolean;
|
|
|
111
111
|
* seed for the org role so behavior remains safe.
|
|
112
112
|
*/
|
|
113
113
|
export declare function hasCapability(auth: Auth, capability: Capability | (string & {})): boolean;
|
|
114
|
+
/**
|
|
115
|
+
* Expand an API key's coarse `read`/`write` scopes into capabilities.
|
|
116
|
+
*
|
|
117
|
+
* The compatibility path for keys issued before the capability model existed,
|
|
118
|
+
* and the default for keys created without an explicit allowlist.
|
|
119
|
+
*
|
|
120
|
+
* **This expansion is not itself a permission check.** It says what the scopes
|
|
121
|
+
* mean, not what the key's owner may actually do — the caller is responsible for
|
|
122
|
+
* intersecting the result with the owner's grantable set before trusting it, or
|
|
123
|
+
* a `write` key would confer `document.delete` to an owner whose role never had
|
|
124
|
+
* it. `AuthService.validateApiKey` does that clamp; anything else deriving
|
|
125
|
+
* capabilities from scopes must too.
|
|
126
|
+
*/
|
|
127
|
+
export declare function coarseApiKeyCapabilities(permissions: readonly string[]): string[];
|
|
114
128
|
/**
|
|
115
129
|
* Resolve the effective capability set for an Auth.
|
|
116
130
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../../src/lib/types/capabilities.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,MAAM,UAAU,GAEnB,eAAe,GACf,iBAAiB,GACjB,iBAAiB,GACjB,iBAAiB,GACjB,kBAAkB,GAClB,oBAAoB,GAEpB,YAAY,GACZ,cAAc,GACd,cAAc,GAEd,eAAe,GACf,eAAe,GACf,mBAAmB,GACnB,eAAe,GACf,aAAa,GACb,cAAc,GAEd,wBAAwB,CAAC;AAM5B;;GAEG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAS,UAAU,EAiBxC,CAAC;AAEX;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACpC,2EAA2E;IAC3E,EAAE,EAAE,MAAM,CAAC;IACX,yCAAyC;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC/B,EAAE,EAAE,MAAM,EACV,IAAI,GAAE,OAAO,CAAC,IAAI,CAAC,oBAAoB,EAAE,IAAI,CAAC,CAAM,GAClD,oBAAoB,CAOtB;AAED,gGAAgG;AAChG,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAIvD;AAED,4EAA4E;AAC5E,eAAO,MAAM,uBAAuB,EAAE,SAAS,oBAAoB,EAiGlE,CAAC;AAEF;;;GAGG;AACH,wBAAgB,sBAAsB,CACrC,KAAK,GAAE,SAAS,oBAAoB,EAAO,GACzC,oBAAoB,EAAE,CAMxB;AAED;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAAS,gBAAgB,EAKzD,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,iBAAiB,EAAE,MAAM,CACrC,gBAAgB,EAChB;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,SAAS,UAAU,EAAE,CAAA;CAAE,CA6C5D,CAAC;AAeF;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAKvE;AAED;;GAEG;AACH,MAAM,WAAW,IAAI;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAI3B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CAChB;AAED,MAAM,WAAW,OAAO;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,EAAE,CAQtE;AAWD,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAElD;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,GAAG,OAAO,CAEzF;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,IAAI,GAAG,WAAW,CAAC,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../../src/lib/types/capabilities.ts"],"names":[],"mappings":"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"}
|
|
@@ -298,6 +298,37 @@ export function isInstanceRole(auth) {
|
|
|
298
298
|
export function hasCapability(auth, capability) {
|
|
299
299
|
return resolveCapabilities(auth).has(capability);
|
|
300
300
|
}
|
|
301
|
+
/** What the coarse `read` scope buys a key. */
|
|
302
|
+
const API_KEY_READ_CAPABILITIES = ['document.read', 'asset.read'];
|
|
303
|
+
/** What the coarse `write` scope adds on top. */
|
|
304
|
+
const API_KEY_WRITE_CAPABILITIES = [
|
|
305
|
+
'document.create',
|
|
306
|
+
'document.update',
|
|
307
|
+
'document.delete',
|
|
308
|
+
'document.publish',
|
|
309
|
+
'document.unpublish',
|
|
310
|
+
'asset.upload',
|
|
311
|
+
'asset.delete'
|
|
312
|
+
];
|
|
313
|
+
/**
|
|
314
|
+
* Expand an API key's coarse `read`/`write` scopes into capabilities.
|
|
315
|
+
*
|
|
316
|
+
* The compatibility path for keys issued before the capability model existed,
|
|
317
|
+
* and the default for keys created without an explicit allowlist.
|
|
318
|
+
*
|
|
319
|
+
* **This expansion is not itself a permission check.** It says what the scopes
|
|
320
|
+
* mean, not what the key's owner may actually do — the caller is responsible for
|
|
321
|
+
* intersecting the result with the owner's grantable set before trusting it, or
|
|
322
|
+
* a `write` key would confer `document.delete` to an owner whose role never had
|
|
323
|
+
* it. `AuthService.validateApiKey` does that clamp; anything else deriving
|
|
324
|
+
* capabilities from scopes must too.
|
|
325
|
+
*/
|
|
326
|
+
export function coarseApiKeyCapabilities(permissions) {
|
|
327
|
+
const caps = [...API_KEY_READ_CAPABILITIES];
|
|
328
|
+
if (permissions.includes('write'))
|
|
329
|
+
caps.push(...API_KEY_WRITE_CAPABILITIES);
|
|
330
|
+
return caps;
|
|
331
|
+
}
|
|
301
332
|
/**
|
|
302
333
|
* Resolve the effective capability set for an Auth.
|
|
303
334
|
*
|
|
@@ -318,25 +349,13 @@ export function resolveCapabilities(auth) {
|
|
|
318
349
|
if (auth.type === 'session' && INSTANCE_ROLE_OVERRIDES.has(auth.user.role)) {
|
|
319
350
|
return new Set(ALL_CAPABILITIES);
|
|
320
351
|
}
|
|
352
|
+
// Reached only when the key carries no capability list at all — an explicit
|
|
353
|
+
// one, including an empty one, is authoritative and was returned above.
|
|
354
|
+
// "Empty" must mean *nothing*, never "fall back to the coarse set": an
|
|
355
|
+
// allowlist that a clamp stripped to nothing would otherwise resolve to more
|
|
356
|
+
// than it asked for.
|
|
321
357
|
if (auth.type === 'api_key') {
|
|
322
|
-
|
|
323
|
-
// to exactly what they need (e.g. a key that can only publish one
|
|
324
|
-
// collection). Falls back to the coarse read/write mapping otherwise
|
|
325
|
-
// for keys issued before the capability model existed.
|
|
326
|
-
if (Array.isArray(auth.capabilities) && auth.capabilities.length > 0) {
|
|
327
|
-
return new Set(auth.capabilities);
|
|
328
|
-
}
|
|
329
|
-
const caps = new Set(['document.read', 'asset.read']);
|
|
330
|
-
if (auth.permissions.includes('write')) {
|
|
331
|
-
caps.add('document.create');
|
|
332
|
-
caps.add('document.update');
|
|
333
|
-
caps.add('document.delete');
|
|
334
|
-
caps.add('document.publish');
|
|
335
|
-
caps.add('document.unpublish');
|
|
336
|
-
caps.add('asset.upload');
|
|
337
|
-
caps.add('asset.delete');
|
|
338
|
-
}
|
|
339
|
-
return caps;
|
|
358
|
+
return new Set(coarseApiKeyCapabilities(auth.permissions));
|
|
340
359
|
}
|
|
341
360
|
// Session without pre-resolved capabilities → seed fallback.
|
|
342
361
|
const builtin = BUILTIN_ROLE_SEED[auth.organizationRole];
|
package/dist/types/events.d.ts
CHANGED
|
@@ -113,17 +113,23 @@ export interface Page<T> {
|
|
|
113
113
|
limit: number;
|
|
114
114
|
offset: number;
|
|
115
115
|
}
|
|
116
|
-
/**
|
|
116
|
+
/**
|
|
117
|
+
* Query for the read-only event history. Newest first.
|
|
118
|
+
*
|
|
119
|
+
* Omit `organizationId` to read across every organization — the instance-wide view.
|
|
120
|
+
* Only two callers may do that: the worker (which has override access by construction)
|
|
121
|
+
* and a super admin in the admin history view. Anything request-scoped must pass an id.
|
|
122
|
+
*/
|
|
117
123
|
export interface ListEventsOptions {
|
|
118
|
-
organizationId
|
|
124
|
+
organizationId?: string;
|
|
119
125
|
/** Filter by exact event type (e.g. `document.published`). */
|
|
120
126
|
type?: string;
|
|
121
127
|
limit?: number;
|
|
122
128
|
offset?: number;
|
|
123
129
|
}
|
|
124
|
-
/** Query for the read-only job history. Newest first. */
|
|
130
|
+
/** Query for the read-only job history. Newest first. Omitting `organizationId` reads instance-wide — see `ListEventsOptions`. */
|
|
125
131
|
export interface ListJobsOptions {
|
|
126
|
-
organizationId
|
|
132
|
+
organizationId?: string;
|
|
127
133
|
/** Filter by one status or several (e.g. only `failed`). */
|
|
128
134
|
status?: JobStatus | JobStatus[];
|
|
129
135
|
/** Filter by exact job type. */
|
|
@@ -131,4 +137,21 @@ export interface ListJobsOptions {
|
|
|
131
137
|
limit?: number;
|
|
132
138
|
offset?: number;
|
|
133
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* The relay's backlog, as a health signal.
|
|
142
|
+
*
|
|
143
|
+
* This is the one number that distinguishes "nothing happened" from "nothing is being
|
|
144
|
+
* processed". Jobs and events both look quiet-but-healthy when no worker is calling
|
|
145
|
+
* `POST /api/internal/workers/run`; a growing outbox backlog is the only place that
|
|
146
|
+
* shows up, because emission still writes rows that nobody is draining.
|
|
147
|
+
*
|
|
148
|
+
* `oldestPendingAt` matters more than `pending`: a big count that is seconds old is a
|
|
149
|
+
* busy instance, while a single row an hour old means the relay has stopped.
|
|
150
|
+
*/
|
|
151
|
+
export interface OutboxHealth {
|
|
152
|
+
/** Rows with `processedAt IS NULL`. */
|
|
153
|
+
pending: number;
|
|
154
|
+
/** `createdAt` of the oldest unprocessed row, or null when the backlog is empty. */
|
|
155
|
+
oldestPendingAt: Date | null;
|
|
156
|
+
}
|
|
134
157
|
//# sourceMappingURL=events.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/lib/types/events.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/lib/types/events.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch-remote-file.d.ts","sourceRoot":"","sources":["../../src/lib/utils/fetch-remote-file.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"fetch-remote-file.d.ts","sourceRoot":"","sources":["../../src/lib/utils/fetch-remote-file.ts"],"names":[],"mappings":"AA8CA,eAAO,MAAM,qBAAqB,QAAmB,CAAC;AA6EtD,MAAM,WAAW,UAAU;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;;sBAGsB;AACtB,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CA0EtE"}
|
|
@@ -11,7 +11,35 @@
|
|
|
11
11
|
// that gets validated and the address that gets connected to must be the exact same one.
|
|
12
12
|
import { lookup } from 'node:dns/promises';
|
|
13
13
|
import net from 'node:net';
|
|
14
|
-
|
|
14
|
+
// Loading `undici` is deferred, and Node's own dispatcher is forced into place first. That
|
|
15
|
+
// ordering is the whole point of this function, and getting it wrong breaks something far away:
|
|
16
|
+
//
|
|
17
|
+
// Node's built-in fetch and the userland `undici` package share one slot —
|
|
18
|
+
// `globalThis[Symbol.for('undici.globalDispatcher.1')]` — and whoever writes it first wins.
|
|
19
|
+
// Importing `undici` claims that slot if it's still empty, and on Node 24+ the *built-in* fetch
|
|
20
|
+
// then routes through this bundled userland copy (as a `Dispatcher1Wrapper`). Its stricter
|
|
21
|
+
// `processHeader` rejects an explicit `content-length`, which the S3 client sets on every PUT — so
|
|
22
|
+
// merely importing this module killed every S3/R2 upload in the process with "invalid
|
|
23
|
+
// content-length header".
|
|
24
|
+
//
|
|
25
|
+
// The slot is non-configurable once set, and Node caches its wrapper internally, so this can't be
|
|
26
|
+
// undone after the fact: writing the symbol back (even to `undefined`) leaves fetch broken. The
|
|
27
|
+
// only fix is to not lose the race. A `data:` fetch resolves entirely in-process — no socket, no
|
|
28
|
+
// DNS — and is enough to make Node install its dispatcher, after which undici's import leaves the
|
|
29
|
+
// slot alone and both work.
|
|
30
|
+
//
|
|
31
|
+
// This module hangs off the `/server` barrel (server/index.ts → mcp/tools → here), so with a
|
|
32
|
+
// static `import` every Aphex app broke its own uploads at boot. It only showed in production:
|
|
33
|
+
// `vite dev` loads SSR modules lazily, so a real request usually primed Node's dispatcher first,
|
|
34
|
+
// while a production build imports the graph eagerly at startup and undici gets there first.
|
|
35
|
+
let undiciPromise;
|
|
36
|
+
function loadUndici() {
|
|
37
|
+
undiciPromise ??= (async () => {
|
|
38
|
+
await fetch('data:text/plain,0').catch(() => undefined);
|
|
39
|
+
return import('undici');
|
|
40
|
+
})();
|
|
41
|
+
return undiciPromise;
|
|
42
|
+
}
|
|
15
43
|
export const MAX_REMOTE_FILE_BYTES = 10 * 1024 * 1024; // matches the API's global JSON body cap
|
|
16
44
|
const FETCH_TIMEOUT_MS = 10_000;
|
|
17
45
|
const MAX_REDIRECTS = 5;
|
|
@@ -79,7 +107,7 @@ async function resolvePinnedHost(hostname) {
|
|
|
79
107
|
* whatever hostname undici asks it to resolve, it hands back this exact IP, so the socket that
|
|
80
108
|
* actually opens is guaranteed to be the one that passed `isPrivateOrReservedIp`. The hostname
|
|
81
109
|
* itself still flows through normally for the Host header and TLS SNI/cert validation. */
|
|
82
|
-
function pinnedDispatcher(host) {
|
|
110
|
+
function pinnedDispatcher(Agent, host) {
|
|
83
111
|
return new Agent({
|
|
84
112
|
connect: {
|
|
85
113
|
lookup: (_hostname, options, callback) => {
|
|
@@ -94,6 +122,7 @@ function pinnedDispatcher(host) {
|
|
|
94
122
|
* hop), a request timeout, and a streamed size cap (not just a Content-Length check, since that
|
|
95
123
|
* header can lie). */
|
|
96
124
|
export async function fetchRemoteFile(url) {
|
|
125
|
+
const { Agent, fetch: undiciFetch } = await loadUndici();
|
|
97
126
|
let current = url;
|
|
98
127
|
for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
|
|
99
128
|
const parsed = new URL(current);
|
|
@@ -112,7 +141,7 @@ export async function fetchRemoteFile(url) {
|
|
|
112
141
|
res = await undiciFetch(current, {
|
|
113
142
|
redirect: 'manual',
|
|
114
143
|
signal: controller.signal,
|
|
115
|
-
dispatcher: pinnedDispatcher(pinnedHost),
|
|
144
|
+
dispatcher: pinnedDispatcher(Agent, pinnedHost),
|
|
116
145
|
headers: REQUEST_HEADERS
|
|
117
146
|
});
|
|
118
147
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aphexcms/cms-core",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.9.0",
|
|
4
4
|
"description": "Aphex CMS Core - A Sanity-style CMS with ports and adapters architecture",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"svelte": "^5.44.0",
|
|
83
83
|
"tailwind-merge": "^3.4.0",
|
|
84
84
|
"tailwind-variants": "^3.2.2",
|
|
85
|
-
"@aphexcms/ui": "^0.8.
|
|
85
|
+
"@aphexcms/ui": "^0.8.5"
|
|
86
86
|
},
|
|
87
87
|
"peerDependenciesMeta": {
|
|
88
88
|
"graphql": {
|
|
@@ -115,10 +115,10 @@
|
|
|
115
115
|
"dayjs": "^1.11.19",
|
|
116
116
|
"dompurify": "^3.4.12",
|
|
117
117
|
"esbuild": "^0.27.0",
|
|
118
|
-
"hono": "^4.12.
|
|
118
|
+
"hono": "^4.12.34",
|
|
119
119
|
"marked": "^18.0.7",
|
|
120
120
|
"picocolors": "^1.1.1",
|
|
121
|
-
"sharp": "^0.
|
|
121
|
+
"sharp": "^0.35.3",
|
|
122
122
|
"svelte-sonner": "^1.0.7",
|
|
123
123
|
"tsx": "^4.20.6",
|
|
124
124
|
"undici": "^8.9.0",
|
|
@@ -129,7 +129,7 @@
|
|
|
129
129
|
"@eslint/js": "^9.39.1",
|
|
130
130
|
"@internationalized/date": "^3.10.0",
|
|
131
131
|
"@sveltejs/adapter-auto": "^7.0.0",
|
|
132
|
-
"@sveltejs/kit": "^2.
|
|
132
|
+
"@sveltejs/kit": "^2.70.2",
|
|
133
133
|
"@sveltejs/package": "^2.5.6",
|
|
134
134
|
"@sveltejs/vite-plugin-svelte": "^7.0.0",
|
|
135
135
|
"@tailwindcss/vite": "^4.3.0",
|
|
@@ -147,7 +147,7 @@
|
|
|
147
147
|
"typescript": "^5.9.3",
|
|
148
148
|
"typescript-eslint": "^8.48.0",
|
|
149
149
|
"vite": "^8.1.5",
|
|
150
|
-
"vitest": "^4.
|
|
150
|
+
"vitest": "^4.1.10"
|
|
151
151
|
},
|
|
152
152
|
"scripts": {
|
|
153
153
|
"build": "svelte-package && tsc && node ../../scripts/fix-imports.js ./dist",
|