Files
TX/artifacts/api-server/src/lib/objectAcl.ts
T
riyadhafraa 8df5e76d29 Add ability to upload and manage service images
Integrates Uppy.js for file uploads, adds new API endpoints for requesting upload URLs, and updates UI components to support image uploads.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 804c1330-3360-45df-814d-221ee0d46866
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/JyUisd3
Replit-Helium-Checkpoint-Created: true
2026-04-20 10:55:11 +00:00

138 lines
3.3 KiB
TypeScript

import { File } from "@google-cloud/storage";
const ACL_POLICY_METADATA_KEY = "custom:aclPolicy";
// Can be flexibly defined according to the use case.
//
// Examples:
// - USER_LIST: the users from a list stored in the database;
// - EMAIL_DOMAIN: the users whose email is in a specific domain;
// - GROUP_MEMBER: the users who are members of a specific group;
// - SUBSCRIBER: the users who are subscribers of a specific service / content
// creator.
export enum ObjectAccessGroupType {}
export interface ObjectAccessGroup {
type: ObjectAccessGroupType;
// The logic id that identifies qualified group members. Format depends on the
// ObjectAccessGroupType — e.g. a user-list DB id, an email domain, a group id.
id: string;
}
export enum ObjectPermission {
READ = "read",
WRITE = "write",
}
export interface ObjectAclRule {
group: ObjectAccessGroup;
permission: ObjectPermission;
}
// Stored as object custom metadata under "custom:aclPolicy" (JSON string).
export interface ObjectAclPolicy {
owner: string;
visibility: "public" | "private";
aclRules?: Array<ObjectAclRule>;
}
function isPermissionAllowed(
requested: ObjectPermission,
granted: ObjectPermission,
): boolean {
if (requested === ObjectPermission.READ) {
return [ObjectPermission.READ, ObjectPermission.WRITE].includes(granted);
}
return granted === ObjectPermission.WRITE;
}
abstract class BaseObjectAccessGroup implements ObjectAccessGroup {
constructor(
public readonly type: ObjectAccessGroupType,
public readonly id: string,
) {}
public abstract hasMember(userId: string): Promise<boolean>;
}
function createObjectAccessGroup(
group: ObjectAccessGroup,
): BaseObjectAccessGroup {
switch (group.type) {
// Implement per access group type, e.g.:
// case "USER_LIST":
// return new UserListAccessGroup(group.id);
default:
throw new Error(`Unknown access group type: ${group.type}`);
}
}
export async function setObjectAclPolicy(
objectFile: File,
aclPolicy: ObjectAclPolicy,
): Promise<void> {
const [exists] = await objectFile.exists();
if (!exists) {
throw new Error(`Object not found: ${objectFile.name}`);
}
await objectFile.setMetadata({
metadata: {
[ACL_POLICY_METADATA_KEY]: JSON.stringify(aclPolicy),
},
});
}
export async function getObjectAclPolicy(
objectFile: File,
): Promise<ObjectAclPolicy | null> {
const [metadata] = await objectFile.getMetadata();
const aclPolicy = metadata?.metadata?.[ACL_POLICY_METADATA_KEY];
if (!aclPolicy) {
return null;
}
return JSON.parse(aclPolicy as string);
}
export async function canAccessObject({
userId,
objectFile,
requestedPermission,
}: {
userId?: string;
objectFile: File;
requestedPermission: ObjectPermission;
}): Promise<boolean> {
const aclPolicy = await getObjectAclPolicy(objectFile);
if (!aclPolicy) {
return false;
}
if (
aclPolicy.visibility === "public" &&
requestedPermission === ObjectPermission.READ
) {
return true;
}
if (!userId) {
return false;
}
if (aclPolicy.owner === userId) {
return true;
}
for (const rule of aclPolicy.aclRules || []) {
const accessGroup = createObjectAccessGroup(rule.group);
if (
(await accessGroup.hasMember(userId)) &&
isPermissionAllowed(requestedPermission, rule.permission)
) {
return true;
}
}
return false;
}