feat: add space subdomain routing and ownership support

- Traefik wildcard HostRegexp for <space>.r*.online subdomains
- Middleware subdomain extraction and path rewriting
- Provision endpoint with owner_did acknowledgement
- Registry enforces space ownership via EncryptID JWT

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-02-25 13:20:01 -08:00
parent 1fce7c9385
commit 5cc17e05e7
1 changed files with 6 additions and 2 deletions

View File

@ -5,6 +5,9 @@ import { prisma } from "@/lib/prisma";
* Internal provision endpoint called by rSpace Registry when activating * Internal provision endpoint called by rSpace Registry when activating
* this app for a space. No auth required (only reachable from Docker network). * this app for a space. No auth required (only reachable from Docker network).
* *
* Payload: { space, description, admin_email, public, owner_did }
* The owner_did identifies who registered the space via the registry.
*
* Creates a default Notebook scoped to the workspace slug + a system collaborator. * Creates a default Notebook scoped to the workspace slug + a system collaborator.
*/ */
export async function POST(request: Request) { export async function POST(request: Request) {
@ -13,13 +16,14 @@ export async function POST(request: Request) {
if (!space) { if (!space) {
return NextResponse.json({ error: "Missing space name" }, { status: 400 }); return NextResponse.json({ error: "Missing space name" }, { status: 400 });
} }
const ownerDid: string = body.owner_did || "";
// Check if a notebook already exists for this workspace // Check if a notebook already exists for this workspace
const existing = await prisma.notebook.findFirst({ const existing = await prisma.notebook.findFirst({
where: { workspaceSlug: space }, where: { workspaceSlug: space },
}); });
if (existing) { if (existing) {
return NextResponse.json({ status: "exists", id: existing.id, slug: existing.slug }); return NextResponse.json({ status: "exists", id: existing.id, slug: existing.slug, owner_did: ownerDid });
} }
const systemDid = `did:system:${space}`; const systemDid = `did:system:${space}`;
@ -42,5 +46,5 @@ export async function POST(request: Request) {
}, },
}); });
return NextResponse.json({ status: "created", id: notebook.id, slug: notebook.slug }, { status: 201 }); return NextResponse.json({ status: "created", id: notebook.id, slug: notebook.slug, owner_did: ownerDid }, { status: 201 });
} }