@commonpub/layer 0.3.20 → 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 +5 -5
- package/pages/admin/federation.vue +64 -1
- package/server/api/admin/federation/trusted-instances.delete.ts +17 -0
- package/server/api/admin/federation/trusted-instances.get.ts +16 -0
- package/server/api/admin/federation/trusted-instances.post.ts +17 -0
- package/server/api/auth/federated/login.post.ts +4 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@commonpub/layer",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.21",
|
|
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/auth": "0.5.0",
|
|
47
48
|
"@commonpub/config": "0.7.0",
|
|
48
49
|
"@commonpub/docs": "0.5.2",
|
|
49
|
-
"@commonpub/auth": "0.5.0",
|
|
50
50
|
"@commonpub/editor": "0.5.0",
|
|
51
|
-
"@commonpub/protocol": "0.9.4",
|
|
52
51
|
"@commonpub/learning": "0.5.0",
|
|
52
|
+
"@commonpub/protocol": "0.9.4",
|
|
53
|
+
"@commonpub/server": "2.13.1",
|
|
53
54
|
"@commonpub/schema": "0.8.12",
|
|
54
|
-
"@commonpub/ui": "0.7.1"
|
|
55
|
-
"@commonpub/server": "2.13.0"
|
|
55
|
+
"@commonpub/ui": "0.7.1"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {}
|
|
58
58
|
}
|
|
@@ -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
|
|
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
|
-
|
|
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`,
|