@commonpub/layer 0.3.19 → 0.3.21

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.3.19",
3
+ "version": "0.3.21",
4
4
  "type": "module",
5
5
  "main": "./nuxt.config.ts",
6
6
  "files": [
@@ -45,12 +45,12 @@
45
45
  "vue-router": "^4.3.0",
46
46
  "zod": "^4.3.6",
47
47
  "@commonpub/auth": "0.5.0",
48
- "@commonpub/docs": "0.5.2",
49
48
  "@commonpub/config": "0.7.0",
49
+ "@commonpub/docs": "0.5.2",
50
50
  "@commonpub/editor": "0.5.0",
51
51
  "@commonpub/learning": "0.5.0",
52
52
  "@commonpub/protocol": "0.9.4",
53
- "@commonpub/server": "2.13.0",
53
+ "@commonpub/server": "2.13.1",
54
54
  "@commonpub/schema": "0.8.12",
55
55
  "@commonpub/ui": "0.7.1"
56
56
  },
@@ -2,7 +2,7 @@
2
2
  definePageMeta({ layout: 'admin', middleware: 'auth' });
3
3
  useSeoMeta({ title: `Federation — Admin — ${useSiteName()}` });
4
4
 
5
- const activeTab = ref<'activity' | 'mirrors' | 'clients' | 'tools'>('activity');
5
+ const activeTab = ref<'activity' | 'mirrors' | 'clients' | 'trusted' | 'tools'>('activity');
6
6
 
7
7
  const { data: statsData } = await useFetch('/api/admin/federation/stats', {
8
8
  default: () => ({ inbound: 0, outbound: 0, pending: 0, failed: 0, followers: 0, following: 0 }),
@@ -21,6 +21,38 @@ const { data: clientsData } = await useFetch<any[]>('/api/admin/federation/clien
21
21
  default: () => [],
22
22
  });
23
23
 
24
+ // Trusted instances
25
+ const { data: trustedData, refresh: refreshTrusted } = await useFetch<{ configDomains: string[]; storedDomains: string[] }>('/api/admin/federation/trusted-instances', {
26
+ default: () => ({ configDomains: [], storedDomains: [] }),
27
+ });
28
+
29
+ const newTrustedDomain = ref('');
30
+ const trustedAdding = ref(false);
31
+
32
+ async function addTrusted(): Promise<void> {
33
+ const domain = newTrustedDomain.value.trim().toLowerCase();
34
+ if (!domain) return;
35
+ trustedAdding.value = true;
36
+ try {
37
+ await $fetch('/api/admin/federation/trusted-instances', {
38
+ method: 'POST',
39
+ body: { domain },
40
+ });
41
+ newTrustedDomain.value = '';
42
+ await refreshTrusted();
43
+ } finally {
44
+ trustedAdding.value = false;
45
+ }
46
+ }
47
+
48
+ async function removeTrusted(domain: string): Promise<void> {
49
+ await $fetch('/api/admin/federation/trusted-instances', {
50
+ method: 'DELETE',
51
+ body: { domain },
52
+ });
53
+ await refreshTrusted();
54
+ }
55
+
24
56
  // Mirror creation
25
57
  const newMirrorDomain = ref('');
26
58
  const newMirrorActorUri = ref('');
@@ -179,6 +211,7 @@ async function refederate(): Promise<void> {
179
211
  <button :class="{ active: activeTab === 'activity' }" @click="activeTab = 'activity'">Activity</button>
180
212
  <button :class="{ active: activeTab === 'mirrors' }" @click="activeTab = 'mirrors'">Mirrors</button>
181
213
  <button :class="{ active: activeTab === 'clients' }" @click="activeTab = 'clients'">OAuth Clients</button>
214
+ <button :class="{ active: activeTab === 'trusted' }" @click="activeTab = 'trusted'">Trusted Instances</button>
182
215
  <button :class="{ active: activeTab === 'tools' }" @click="activeTab = 'tools'">Tools</button>
183
216
  </div>
184
217
 
@@ -282,6 +315,36 @@ async function refederate(): Promise<void> {
282
315
  </p>
283
316
  </div>
284
317
 
318
+ <!-- Trusted Instances Tab -->
319
+ <div v-if="activeTab === 'trusted'">
320
+ <p class="cpub-fed-info-text" style="margin-bottom: 12px;">
321
+ Trusted instances can use cross-instance SSO to authenticate users on this instance.
322
+ Domains from the config file cannot be removed here.
323
+ </p>
324
+
325
+ <div class="cpub-fed-form">
326
+ <input v-model="newTrustedDomain" placeholder="instance.example.com" class="cpub-fed-input" @keydown.enter.prevent="addTrusted" />
327
+ <button :disabled="trustedAdding || !newTrustedDomain.trim()" class="cpub-fed-btn" @click="addTrusted">
328
+ {{ trustedAdding ? 'Adding...' : 'Add Instance' }}
329
+ </button>
330
+ </div>
331
+
332
+ <div class="cpub-fed-activity-list">
333
+ <div v-if="!trustedData.configDomains.length && !trustedData.storedDomains.length" class="cpub-fed-empty">No trusted instances configured.</div>
334
+
335
+ <div v-for="domain in trustedData.configDomains" :key="'config-' + domain" class="cpub-fed-activity-row">
336
+ <span class="cpub-fed-type">{{ domain }}</span>
337
+ <span class="cpub-fed-status processed">config</span>
338
+ </div>
339
+
340
+ <div v-for="domain in trustedData.storedDomains" :key="'stored-' + domain" class="cpub-fed-activity-row">
341
+ <span class="cpub-fed-type">{{ domain }}</span>
342
+ <span class="cpub-fed-status pending">admin</span>
343
+ <button class="cpub-fed-btn-sm cpub-fed-btn-danger" @click="removeTrusted(domain)">Remove</button>
344
+ </div>
345
+ </div>
346
+ </div>
347
+
285
348
  <!-- Tools Tab -->
286
349
  <div v-if="activeTab === 'tools'">
287
350
  <div class="cpub-fed-tools">
@@ -0,0 +1,17 @@
1
+ import { removeTrustedInstance } from '@commonpub/server';
2
+ import { z } from 'zod';
3
+
4
+ const removeSchema = z.object({
5
+ domain: z.string().min(3).max(255),
6
+ });
7
+
8
+ export default defineEventHandler(async (event) => {
9
+ requireFeature('admin');
10
+ requireAdmin(event);
11
+ const db = useDB();
12
+ const { domain } = await parseBody(event, removeSchema);
13
+
14
+ await removeTrustedInstance(db, domain.toLowerCase());
15
+
16
+ return { success: true };
17
+ });
@@ -0,0 +1,16 @@
1
+ import { getStoredTrustedInstances } from '@commonpub/server';
2
+
3
+ export default defineEventHandler(async (event) => {
4
+ requireFeature('admin');
5
+ requireAdmin(event);
6
+ const db = useDB();
7
+ const config = useConfig();
8
+
9
+ const stored = await getStoredTrustedInstances(db);
10
+ const configDomains = config.auth.trustedInstances ?? [];
11
+
12
+ return {
13
+ configDomains,
14
+ storedDomains: stored,
15
+ };
16
+ });
@@ -0,0 +1,17 @@
1
+ import { addTrustedInstance } from '@commonpub/server';
2
+ import { z } from 'zod';
3
+
4
+ const addSchema = z.object({
5
+ domain: z.string().min(3).max(255).regex(/^[a-zA-Z0-9][a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/, 'Invalid domain format'),
6
+ });
7
+
8
+ export default defineEventHandler(async (event) => {
9
+ requireFeature('admin');
10
+ requireAdmin(event);
11
+ const db = useDB();
12
+ const { domain } = await parseBody(event, addSchema);
13
+
14
+ await addTrustedInstance(db, domain.toLowerCase());
15
+
16
+ return { success: true, domain: domain.toLowerCase() };
17
+ });
@@ -1,5 +1,5 @@
1
- import { discoverOAuthEndpoint, isTrustedInstance } from '@commonpub/auth';
2
- import { storeOAuthState } from '@commonpub/server';
1
+ import { discoverOAuthEndpoint } from '@commonpub/auth';
2
+ import { storeOAuthState, isDomainTrusted } from '@commonpub/server';
3
3
  import { z } from 'zod';
4
4
 
5
5
  const loginSchema = z.object({
@@ -19,7 +19,8 @@ export default defineEventHandler(async (event) => {
19
19
  const db = useDB();
20
20
  const { instanceDomain, clientId, clientSecret } = await parseBody(event, loginSchema);
21
21
 
22
- if (!isTrustedInstance(config, instanceDomain)) {
22
+ const trusted = await isDomainTrusted(db, config, instanceDomain);
23
+ if (!trusted) {
23
24
  throw createError({
24
25
  statusCode: 403,
25
26
  statusMessage: `Instance ${instanceDomain} is not in the trusted instances list`,
@@ -145,8 +145,12 @@ export default defineEventHandler(async (event) => {
145
145
  return;
146
146
  }
147
147
 
148
- // Handle auth API routes
149
- if (pathname.startsWith('/api/auth')) {
148
+ // Handle auth API routes — skip our custom federated/oauth2 routes (Nitro handles those)
149
+ const isBetterAuthRoute = pathname.startsWith('/api/auth')
150
+ && !pathname.startsWith('/api/auth/federated/')
151
+ && !pathname.startsWith('/api/auth/oauth2/');
152
+
153
+ if (isBetterAuthRoute) {
150
154
  try {
151
155
  const response = await middleware.handleAuthRoute(
152
156
  toWebRequest(event),