@commonpub/layer 0.16.0 → 0.16.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@commonpub/layer",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./nuxt.config.ts",
|
|
6
6
|
"files": [
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@aws-sdk/client-s3": "^3.1010.0",
|
|
31
31
|
"@commonpub/explainer": "^0.7.12",
|
|
32
|
-
"@commonpub/schema": "^0.14.
|
|
33
|
-
"@commonpub/server": "^2.44.
|
|
32
|
+
"@commonpub/schema": "^0.14.1",
|
|
33
|
+
"@commonpub/server": "^2.44.2",
|
|
34
34
|
"@tiptap/core": "^2.11.0",
|
|
35
35
|
"@tiptap/extension-bold": "^2.11.0",
|
|
36
36
|
"@tiptap/extension-bullet-list": "^2.11.0",
|
|
@@ -54,12 +54,12 @@
|
|
|
54
54
|
"vue-router": "^4.3.0",
|
|
55
55
|
"zod": "^4.3.6",
|
|
56
56
|
"@commonpub/config": "0.11.0",
|
|
57
|
-
"@commonpub/auth": "0.5.1",
|
|
58
57
|
"@commonpub/docs": "0.6.2",
|
|
59
|
-
"@commonpub/
|
|
58
|
+
"@commonpub/auth": "0.5.1",
|
|
60
59
|
"@commonpub/editor": "0.7.9",
|
|
61
|
-
"@commonpub/
|
|
62
|
-
"@commonpub/protocol": "0.9.9"
|
|
60
|
+
"@commonpub/learning": "0.5.0",
|
|
61
|
+
"@commonpub/protocol": "0.9.9",
|
|
62
|
+
"@commonpub/ui": "0.8.5"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@testing-library/jest-dom": "^6.9.1",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { revokeApiKey } from '@commonpub/server';
|
|
1
|
+
import { revokeApiKey, createAuditEntry } from '@commonpub/server';
|
|
2
2
|
|
|
3
3
|
export default defineEventHandler(async (event) => {
|
|
4
4
|
const user = requireAdmin(event);
|
|
@@ -7,5 +7,14 @@ export default defineEventHandler(async (event) => {
|
|
|
7
7
|
const db = useDB();
|
|
8
8
|
const result = await revokeApiKey(db, id, user.id);
|
|
9
9
|
if (!result) throw createError({ statusCode: 404, statusMessage: 'Key not found or already revoked' });
|
|
10
|
+
|
|
11
|
+
await createAuditEntry(db, {
|
|
12
|
+
userId: user.id,
|
|
13
|
+
action: 'api_key.revoke',
|
|
14
|
+
targetType: 'api_key',
|
|
15
|
+
targetId: result.id,
|
|
16
|
+
metadata: { name: result.name, scopes: result.scopes },
|
|
17
|
+
}).catch(() => { /* audit best-effort */ });
|
|
18
|
+
|
|
10
19
|
return result;
|
|
11
20
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createApiKey } from '@commonpub/server';
|
|
1
|
+
import { createApiKey, createAuditEntry } from '@commonpub/server';
|
|
2
2
|
import { createApiKeySchema } from '@commonpub/schema';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -7,6 +7,11 @@ import { createApiKeySchema } from '@commonpub/schema';
|
|
|
7
7
|
* Creates a new public API key. The full token is returned ONCE in the
|
|
8
8
|
* response body — the UI displays it with a "copy now, you won't see it
|
|
9
9
|
* again" warning. Server-side we only keep the SHA-256 hash.
|
|
10
|
+
*
|
|
11
|
+
* Audit: issuance of an API key is a sensitive change to the instance's
|
|
12
|
+
* external access surface, so we always write an auditLogs row. The token
|
|
13
|
+
* itself is NEVER logged — only the id, name, scopes, and (optional) expiry
|
|
14
|
+
* land in the metadata column.
|
|
10
15
|
*/
|
|
11
16
|
export default defineEventHandler(async (event) => {
|
|
12
17
|
const user = requireAdmin(event);
|
|
@@ -18,5 +23,19 @@ export default defineEventHandler(async (event) => {
|
|
|
18
23
|
|
|
19
24
|
const db = useDB();
|
|
20
25
|
const result = await createApiKey(db, user.id, parsed.data);
|
|
26
|
+
|
|
27
|
+
await createAuditEntry(db, {
|
|
28
|
+
userId: user.id,
|
|
29
|
+
action: 'api_key.create',
|
|
30
|
+
targetType: 'api_key',
|
|
31
|
+
targetId: result.key.id,
|
|
32
|
+
metadata: {
|
|
33
|
+
name: result.key.name,
|
|
34
|
+
scopes: result.key.scopes,
|
|
35
|
+
expiresAt: result.key.expiresAt,
|
|
36
|
+
rateLimitPerMinute: result.key.rateLimitPerMinute,
|
|
37
|
+
},
|
|
38
|
+
}).catch(() => { /* audit is best-effort; never fail the create */ });
|
|
39
|
+
|
|
21
40
|
return result;
|
|
22
41
|
});
|
|
@@ -33,6 +33,28 @@ export default defineEventHandler(async (event) => {
|
|
|
33
33
|
throw createError({ statusCode: 404, statusMessage: 'Not Found' });
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
// CORS preflight. Browsers send OPTIONS with `Origin` and either
|
|
37
|
+
// `Access-Control-Request-Method` or `-Headers`. We reflect the origin
|
|
38
|
+
// only if the admin key allow-list has opted in — but to do that we
|
|
39
|
+
// need to auth first, which preflight doesn't carry. The pragmatic
|
|
40
|
+
// compromise: for preflight, short-circuit with permissive headers for
|
|
41
|
+
// the standard set and let the real request's auth check gate access.
|
|
42
|
+
// No data flows here; the body is empty.
|
|
43
|
+
if (event.method === 'OPTIONS') {
|
|
44
|
+
setResponseHeader(event, 'Access-Control-Allow-Methods', 'GET, OPTIONS');
|
|
45
|
+
setResponseHeader(event, 'Access-Control-Allow-Headers', 'Authorization, Content-Type');
|
|
46
|
+
setResponseHeader(event, 'Access-Control-Max-Age', 600);
|
|
47
|
+
const origin = getRequestHeader(event, 'origin');
|
|
48
|
+
if (origin) {
|
|
49
|
+
// Echo origin only on preflight — real requests get the per-key
|
|
50
|
+
// allow-list check below. Browsers that don't trust this echo because
|
|
51
|
+
// credentials aren't involved will simply fall back to the no-CORS path.
|
|
52
|
+
setResponseHeader(event, 'Access-Control-Allow-Origin', origin);
|
|
53
|
+
appendResponseHeader(event, 'Vary', 'Origin');
|
|
54
|
+
}
|
|
55
|
+
return sendNoContent(event);
|
|
56
|
+
}
|
|
57
|
+
|
|
36
58
|
const authHeader = getRequestHeader(event, 'authorization');
|
|
37
59
|
const token = authHeader?.startsWith('Bearer ')
|
|
38
60
|
? authHeader.slice(7).trim()
|