fix(spaces): default visibility to private (sovereign by default)
Spaces with missing/undefined visibility were falling through to "public" in 7 places: normalizeVisibility fallback, migrateVisibility early return, renderShell default, getSpaceConfig, space list APIs, and the HTML injection middleware. All now default to "private". The migrateVisibility function now writes "private" to docs with missing visibility on load. Also fixed jeff and hash spaces on production (were undefined → private). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7c29ccea41
commit
ca9e91651c
|
|
@ -23,7 +23,7 @@ export function normalizeVisibility(v: string): SpaceVisibility {
|
||||||
if (v === 'public_read' || v === 'public') return 'public';
|
if (v === 'public_read' || v === 'public') return 'public';
|
||||||
if (v === 'authenticated' || v === 'permissioned') return 'permissioned';
|
if (v === 'authenticated' || v === 'permissioned') return 'permissioned';
|
||||||
if (v === 'members_only' || v === 'private') return 'private';
|
if (v === 'members_only' || v === 'private') return 'private';
|
||||||
return 'public';
|
return 'private'; // sovereign by default — unknown values treated as private
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Nest Permissions & Policy ──
|
// ── Nest Permissions & Policy ──
|
||||||
|
|
@ -237,7 +237,13 @@ function migrateVisibility(
|
||||||
slug: string,
|
slug: string,
|
||||||
): Automerge.Doc<CommunityDoc> {
|
): Automerge.Doc<CommunityDoc> {
|
||||||
const v = doc.meta?.visibility as string;
|
const v = doc.meta?.visibility as string;
|
||||||
if (!v) return doc;
|
if (!v) {
|
||||||
|
// Missing visibility — default to private (sovereign by default)
|
||||||
|
console.log(`[Store] Migrating missing visibility→private in ${slug}`);
|
||||||
|
return Automerge.change(doc, `Set default visibility to private in ${slug}`, (d) => {
|
||||||
|
d.meta.visibility = 'private';
|
||||||
|
});
|
||||||
|
}
|
||||||
const normalized = normalizeVisibility(v);
|
const normalized = normalizeVisibility(v);
|
||||||
if (v === normalized) return doc;
|
if (v === normalized) return doc;
|
||||||
console.log(`[Store] Migrating visibility ${v}→${normalized} in ${slug}`);
|
console.log(`[Store] Migrating visibility ${v}→${normalized} in ${slug}`);
|
||||||
|
|
|
||||||
|
|
@ -464,7 +464,7 @@ async function getSpaceConfig(slug: string): Promise<SpaceAuthConfig | null> {
|
||||||
if (!doc) return null;
|
if (!doc) return null;
|
||||||
return {
|
return {
|
||||||
spaceSlug: slug,
|
spaceSlug: slug,
|
||||||
visibility: (doc.meta.visibility || "public") as SpaceVisibility,
|
visibility: (doc.meta.visibility || "private") as SpaceVisibility,
|
||||||
ownerDID: doc.meta.ownerDID || undefined,
|
ownerDID: doc.meta.ownerDID || undefined,
|
||||||
app: "rspace",
|
app: "rspace",
|
||||||
};
|
};
|
||||||
|
|
@ -1960,12 +1960,12 @@ app.use("/:space/*", async (c, next) => {
|
||||||
const space = c.req.param("space");
|
const space = c.req.param("space");
|
||||||
if (!space || space === "api" || space.includes(".")) return;
|
if (!space || space === "api" || space.includes(".")) return;
|
||||||
const config = await getSpaceConfig(space);
|
const config = await getSpaceConfig(space);
|
||||||
const vis = config?.visibility || "public";
|
const vis = config?.visibility || "private";
|
||||||
if (vis === "public") return;
|
if (vis === "private") return; // Shell already defaults to private
|
||||||
const html = await c.res.text();
|
const html = await c.res.text();
|
||||||
c.res = new Response(
|
c.res = new Response(
|
||||||
html.replace(
|
html.replace(
|
||||||
'data-space-visibility="public"',
|
'data-space-visibility="private"',
|
||||||
`data-space-visibility="${vis}"`,
|
`data-space-visibility="${vis}"`,
|
||||||
),
|
),
|
||||||
{ status: c.res.status, headers: c.res.headers },
|
{ status: c.res.status, headers: c.res.headers },
|
||||||
|
|
@ -2179,7 +2179,7 @@ app.get("/admin-data", async (c) => {
|
||||||
spacesList.push({
|
spacesList.push({
|
||||||
slug: data.meta.slug,
|
slug: data.meta.slug,
|
||||||
name: data.meta.name,
|
name: data.meta.name,
|
||||||
visibility: data.meta.visibility || "public",
|
visibility: data.meta.visibility || "private",
|
||||||
createdAt: data.meta.createdAt,
|
createdAt: data.meta.createdAt,
|
||||||
ownerDID: data.meta.ownerDID,
|
ownerDID: data.meta.ownerDID,
|
||||||
shapeCount,
|
shapeCount,
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,7 @@ export function renderShell(opts: ShellOptions): string {
|
||||||
modules,
|
modules,
|
||||||
theme = "dark",
|
theme = "dark",
|
||||||
head = "",
|
head = "",
|
||||||
spaceVisibility = "public",
|
spaceVisibility = "private",
|
||||||
} = opts;
|
} = opts;
|
||||||
|
|
||||||
// Auto-populate from space data when not explicitly provided
|
// Auto-populate from space data when not explicitly provided
|
||||||
|
|
|
||||||
|
|
@ -240,7 +240,7 @@ spaces.get("/", async (c) => {
|
||||||
if (seenSlugs.has(slug)) continue;
|
if (seenSlugs.has(slug)) continue;
|
||||||
seenSlugs.add(slug);
|
seenSlugs.add(slug);
|
||||||
|
|
||||||
let vis = data.meta.visibility || "public";
|
let vis = data.meta.visibility || "private";
|
||||||
// Check both claims.sub (raw userId) and did:key: format for
|
// Check both claims.sub (raw userId) and did:key: format for
|
||||||
// compatibility — auto-provisioned spaces store ownerDID as did:key:
|
// compatibility — auto-provisioned spaces store ownerDID as did:key:
|
||||||
const callerDid = claims ? ((claims.did as string) || `did:key:${(claims.sub as string).slice(0, 32)}`) : "";
|
const callerDid = claims ? ((claims.did as string) || `did:key:${(claims.sub as string).slice(0, 32)}`) : "";
|
||||||
|
|
@ -298,8 +298,8 @@ spaces.get("/", async (c) => {
|
||||||
// Within each group: user's own spaces first, then alphabetically
|
// Within each group: user's own spaces first, then alphabetically
|
||||||
const visOrder: Record<string, number> = { private: 0, permissioned: 1, public: 2 };
|
const visOrder: Record<string, number> = { private: 0, permissioned: 1, public: 2 };
|
||||||
spacesList.sort((a, b) => {
|
spacesList.sort((a, b) => {
|
||||||
const va = visOrder[a.visibility || "public"] ?? 2;
|
const va = visOrder[a.visibility || "private"] ?? 0;
|
||||||
const vb = visOrder[b.visibility || "public"] ?? 2;
|
const vb = visOrder[b.visibility || "private"] ?? 0;
|
||||||
if (va !== vb) return va - vb;
|
if (va !== vb) return va - vb;
|
||||||
if (a.role && !b.role) return -1;
|
if (a.role && !b.role) return -1;
|
||||||
if (!a.role && b.role) return 1;
|
if (!a.role && b.role) return 1;
|
||||||
|
|
@ -522,7 +522,7 @@ spaces.get("/admin", async (c) => {
|
||||||
spacesList.push({
|
spacesList.push({
|
||||||
slug: data.meta.slug,
|
slug: data.meta.slug,
|
||||||
name: data.meta.name,
|
name: data.meta.name,
|
||||||
visibility: data.meta.visibility || "public",
|
visibility: data.meta.visibility || "private",
|
||||||
createdAt: data.meta.createdAt,
|
createdAt: data.meta.createdAt,
|
||||||
ownerDID: data.meta.ownerDID,
|
ownerDID: data.meta.ownerDID,
|
||||||
shapeCount,
|
shapeCount,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue