feat: permissions, a console for everyone who manages something, and catalog management
Phase 1 of docs/console.md: permissions as resource:action strings in role bundles at a scope; the console for anyone controlling a catalog, product or room, with a tagged audit log scoped by permission and filters; catalog settings (rating ceiling, upload policy), key rotation, operators, membership (approval, invitations, bans) enforced when getting and wearing products, delisting, suspension, and the catalogs' changes feed. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
76497c9b41
commit
370ae0646a
29 files changed
+5657
-181
No files matched your search
@@ -121,8 +121,15 @@ export type RealtimeDeps = {
|
||||
hostedVisit?: (userId: string, n: number, imvuId: number) => void;
|
||||
/** The IMVU username of a linked account, from its verified link ("" if unknown). */
|
||||
accountName?: (imvuId: number) => Promise<string>;
|
||||
/**
|
||||
* Of these hub product IDs, the ones a person may wear (available to them: docs/products.md, "Member"). Outfit
|
||||
* items with other hub IDs aren't relayed; items that aren't hub IDs (local packs) are.
|
||||
*/
|
||||
wearable?: (userId: string, productIds: string[]) => Promise<Set<string>>;
|
||||
};
|
||||
|
||||
const hubProductID = /^[a-z2-7]{10}$/;
|
||||
|
||||
const maxChats = 16;
|
||||
const chatPattern = /^[0-9]{1,20}$/;
|
||||
const roomPattern = /^[0-9]{1,12}-[0-9]{1,12}$/;
|
||||
@@ -179,6 +186,7 @@ export class Realtime {
|
||||
hostedBoot: async () => {},
|
||||
hostedVisit: () => {},
|
||||
accountName: async () => "",
|
||||
wearable: async (_, ids) => new Set(ids),
|
||||
...d,
|
||||
};
|
||||
}
|
||||
@@ -644,10 +652,16 @@ export class Realtime {
|
||||
const items = m.items ?? [];
|
||||
if (!Array.isArray(items)) return { type: "error", message: "items must be a list" };
|
||||
if (items.length > 64) return { type: "error", message: "too many items" };
|
||||
s.outfit = items
|
||||
let outfit = items
|
||||
.filter((it): it is Item => typeof it?.id === "string" && typeof it?.name === "string")
|
||||
.filter((it) => it.id && it.id.length <= 128 && it.name.length <= 128)
|
||||
.map((it) => ({ id: it.id, name: it.name, adult: it.adult === true }));
|
||||
const hubIds = outfit.filter((it) => hubProductID.test(it.id)).map((it) => it.id);
|
||||
if (hubIds.length > 0) {
|
||||
const ok = await this.deps.wearable(s.userId, hubIds);
|
||||
outfit = outfit.filter((it) => !hubProductID.test(it.id) || ok.has(it.id));
|
||||
}
|
||||
s.outfit = outfit;
|
||||
this.changed(s.chats.keys());
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user