@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.0",
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.0",
33
- "@commonpub/server": "^2.44.1",
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/learning": "0.5.0",
58
+ "@commonpub/auth": "0.5.1",
60
59
  "@commonpub/editor": "0.7.9",
61
- "@commonpub/ui": "0.8.5",
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()