@commonpub/layer 0.3.23 → 0.3.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/package.json +6 -6
- package/server/api/files/upload-from-url.post.ts +16 -9
- package/server/api/image-proxy.get.ts +15 -8
- package/server/api/realtime/stream.get.ts +6 -2
- package/server/plugins/federation-hub-sync.ts +5 -1
- package/server/plugins/notification-email.ts +5 -1
- package/server/routes/.well-known/webfinger.ts +2 -2
package/README.md
CHANGED
|
@@ -47,13 +47,13 @@ Create `server/utils/config.ts` to load the config on the server side. See `apps
|
|
|
47
47
|
|
|
48
48
|
## What's Included
|
|
49
49
|
|
|
50
|
-
### Pages (
|
|
50
|
+
### Pages (70+ routes)
|
|
51
51
|
|
|
52
52
|
Content CRUD, hub feeds, learning paths, docs sites, admin panel, federation management, user profiles, messaging, notifications, search, and more.
|
|
53
53
|
|
|
54
|
-
### Components (
|
|
54
|
+
### Components (100+)
|
|
55
55
|
|
|
56
|
-
Content editor (`CpubEditor`), content cards, author rows, comment sections, engagement bars, federation UI, notification items, message threads, and more.
|
|
56
|
+
Content editor (`CpubEditor`), content cards, author rows, comment sections, engagement bars, federation UI, notification items, message threads, block renderers, and more.
|
|
57
57
|
|
|
58
58
|
### Composables (19)
|
|
59
59
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@commonpub/layer",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.25",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./nuxt.config.ts",
|
|
6
6
|
"files": [
|
|
@@ -44,15 +44,15 @@
|
|
|
44
44
|
"vue": "^3.4.0",
|
|
45
45
|
"vue-router": "^4.3.0",
|
|
46
46
|
"zod": "^4.3.6",
|
|
47
|
-
"@commonpub/config": "0.7.1",
|
|
48
47
|
"@commonpub/auth": "0.5.0",
|
|
48
|
+
"@commonpub/config": "0.7.1",
|
|
49
49
|
"@commonpub/editor": "0.5.0",
|
|
50
|
-
"@commonpub/protocol": "0.9.5",
|
|
51
50
|
"@commonpub/docs": "0.5.2",
|
|
52
|
-
"@commonpub/
|
|
53
|
-
"@commonpub/ui": "0.7.1",
|
|
51
|
+
"@commonpub/protocol": "0.9.5",
|
|
54
52
|
"@commonpub/learning": "0.5.0",
|
|
55
|
-
"@commonpub/server": "2.15.0"
|
|
53
|
+
"@commonpub/server": "2.15.0",
|
|
54
|
+
"@commonpub/ui": "0.7.1",
|
|
55
|
+
"@commonpub/schema": "0.8.12"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {}
|
|
58
58
|
}
|
|
@@ -10,17 +10,24 @@ export default defineEventHandler(async (event) => {
|
|
|
10
10
|
const user = requireAuth(event);
|
|
11
11
|
const { url, purpose } = await parseBody(event, schema);
|
|
12
12
|
|
|
13
|
-
// SSRF protection — block private IPs
|
|
13
|
+
// SSRF protection — block private/internal IPs
|
|
14
14
|
const parsed = new URL(url);
|
|
15
|
-
const hostname = parsed.hostname;
|
|
15
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
16
|
+
const h = hostname.replace(/^\[|\]$/g, '');
|
|
16
17
|
if (
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
h === 'localhost' ||
|
|
19
|
+
h === 'localhost.localdomain' ||
|
|
20
|
+
h === 'metadata.google.internal' ||
|
|
21
|
+
h.endsWith('.local') ||
|
|
22
|
+
/^127\./.test(h) ||
|
|
23
|
+
/^10\./.test(h) ||
|
|
24
|
+
/^172\.(1[6-9]|2\d|3[01])\./.test(h) ||
|
|
25
|
+
/^192\.168\./.test(h) ||
|
|
26
|
+
/^169\.254\./.test(h) ||
|
|
27
|
+
/^0\./.test(h) ||
|
|
28
|
+
h === '::1' ||
|
|
29
|
+
/^f[cd]/i.test(h) ||
|
|
30
|
+
/^fe80/i.test(h)
|
|
24
31
|
) {
|
|
25
32
|
throw createError({ statusCode: 400, statusMessage: 'Cannot fetch from private/local addresses' });
|
|
26
33
|
}
|
|
@@ -29,15 +29,22 @@ export default defineEventHandler(async (event) => {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
// Block localhost/private IPs (SSRF prevention)
|
|
32
|
-
const hostname = parsed.hostname;
|
|
32
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
33
|
+
const h = hostname.replace(/^\[|\]$/g, ''); // strip IPv6 brackets
|
|
33
34
|
if (
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
h === 'localhost' ||
|
|
36
|
+
h === 'localhost.localdomain' ||
|
|
37
|
+
h === 'metadata.google.internal' ||
|
|
38
|
+
h.endsWith('.local') ||
|
|
39
|
+
/^127\./.test(h) ||
|
|
40
|
+
/^10\./.test(h) ||
|
|
41
|
+
/^172\.(1[6-9]|2\d|3[01])\./.test(h) ||
|
|
42
|
+
/^192\.168\./.test(h) ||
|
|
43
|
+
/^169\.254\./.test(h) ||
|
|
44
|
+
/^0\./.test(h) ||
|
|
45
|
+
h === '::1' ||
|
|
46
|
+
/^f[cd]/i.test(h) ||
|
|
47
|
+
/^fe80/i.test(h)
|
|
41
48
|
) {
|
|
42
49
|
throw createError({ statusCode: 403, statusMessage: 'Private addresses not allowed' });
|
|
43
50
|
}
|
|
@@ -30,8 +30,12 @@ export default defineEventHandler(async (event) => {
|
|
|
30
30
|
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'counts', notifications, messages })}\n\n`));
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
// Send initial counts
|
|
34
|
-
|
|
33
|
+
// Send initial counts — if DB is unavailable, send zeros and let polling retry
|
|
34
|
+
try {
|
|
35
|
+
await sendCounts();
|
|
36
|
+
} catch {
|
|
37
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'counts', notifications: 0, messages: 0 })}\n\n`));
|
|
38
|
+
}
|
|
35
39
|
|
|
36
40
|
// Poll every 10 seconds
|
|
37
41
|
const interval = setInterval(async () => {
|
|
@@ -39,7 +39,11 @@ export default defineNitroPlugin((nitro) => {
|
|
|
39
39
|
// Run first sync after a brief delay to avoid startup contention
|
|
40
40
|
runSync(domain, intervalMs, backfillOnSync);
|
|
41
41
|
|
|
42
|
-
interval = setInterval(() =>
|
|
42
|
+
interval = setInterval(() => {
|
|
43
|
+
runSync(domain, intervalMs, backfillOnSync).catch((err) => {
|
|
44
|
+
console.error('[hub-sync] Sync worker unexpected error:', err instanceof Error ? err.message : err);
|
|
45
|
+
});
|
|
46
|
+
}, intervalMs);
|
|
43
47
|
} catch (err) {
|
|
44
48
|
console.error('[hub-sync] Failed to start:', err instanceof Error ? err.message : err);
|
|
45
49
|
}
|
|
@@ -64,7 +64,11 @@ export default defineNitroPlugin((nitro) => {
|
|
|
64
64
|
|
|
65
65
|
// Digest scheduler — runs every hour, sends digests for users whose digest window has elapsed
|
|
66
66
|
const DIGEST_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
|
|
67
|
-
digestInterval = setInterval(() =>
|
|
67
|
+
digestInterval = setInterval(() => {
|
|
68
|
+
runDigest(siteUrl, siteName).catch((err) => {
|
|
69
|
+
console.error('[notification-email] Digest scheduler unexpected error:', err instanceof Error ? err.message : err);
|
|
70
|
+
});
|
|
71
|
+
}, DIGEST_INTERVAL_MS);
|
|
68
72
|
|
|
69
73
|
console.log('[notification-email] Digest scheduler started (interval: 1h)');
|
|
70
74
|
} catch (err) {
|
|
@@ -36,8 +36,8 @@ export default defineEventHandler(async (event) => {
|
|
|
36
36
|
// CORS — WebFinger must be accessible from browser-based AP clients
|
|
37
37
|
setResponseHeader(event, 'access-control-allow-origin', '*');
|
|
38
38
|
|
|
39
|
-
// Instance actor lookup: acct:domain@domain → /actor Service
|
|
40
|
-
if (parsed.username === instanceDomain || parsed.username === config.instance.domain) {
|
|
39
|
+
// Instance actor lookup: acct:domain@domain or acct:instance@domain → /actor Service
|
|
40
|
+
if (parsed.username === instanceDomain || parsed.username === config.instance.domain || parsed.username === 'instance') {
|
|
41
41
|
const actorUri = `https://${instanceDomain}/actor`;
|
|
42
42
|
setResponseHeader(event, 'content-type', 'application/jrd+json');
|
|
43
43
|
return buildWebFingerResponse({
|