webpods
Version:
Append-only log service with OAuth authentication
164 lines • 5.38 kB
JavaScript
/**
* Permission checking domain logic
*/
import { createLogger } from "../logger.js";
const logger = createLogger("webpods:domain:permissions");
/**
* Parse permission string into components
*/
export function parsePermission(permission) {
if (permission === "public" || permission === "private") {
return { type: "basic" };
}
if (permission.startsWith("/")) {
return { type: "stream", stream: permission.substring(1) };
}
return { type: "basic" };
}
/**
* Check if user exists in permission stream
*/
async function checkPermissionStream(db, podId, streamId, userId, action) {
try {
logger.debug("Checking permission stream", {
podId,
streamId,
userId,
action,
});
// First check if the stream exists
const stream = await db.oneOrNone(`SELECT s.*
FROM stream s
JOIN pod p ON p.id = s.pod_id
WHERE p.pod_id = $(podId)
AND s.stream_id = $(streamId)`, { podId, streamId });
if (!stream) {
logger.warn("Permission stream not found", { podId, streamId });
return false;
}
logger.info("Permission stream found", {
streamId: stream.stream_id,
id: stream.id,
});
// Get ALL records from the permission stream
const records = await db.manyOrNone(`SELECT * FROM record
WHERE stream_id = $(streamId)
ORDER BY index ASC`, { streamId: stream.id });
// Process records in memory to find the latest permission for this user
let userPermission = null;
for (const record of records) {
try {
const content = typeof record.content === "string"
? JSON.parse(record.content)
: record.content;
// Check if this record is for our user
if (content.id === userId) {
// Last record wins
userPermission = content;
}
}
catch {
// Skip records that aren't valid JSON or don't have the right structure
logger.debug("Skipping non-permission record", { recordId: record.id });
}
}
logger.info("Permission check result", {
found: !!userPermission,
userId,
streamId,
permission: userPermission,
});
if (!userPermission) {
return false;
}
// Check if action is allowed
const allowed = userPermission[action] === true;
logger.debug("Permission check result", {
userId,
action,
allowed,
userPermission,
});
return allowed;
}
catch (error) {
logger.error("Failed to check permission stream", {
error,
podId,
streamId,
userId,
});
return false;
}
}
/**
* Check if user can read from stream
*/
export async function canRead(db, stream, userId) {
logger.info("canRead check", {
streamId: stream.stream_id,
accessPermission: stream.access_permission,
userId,
creatorId: stream.creator_id,
});
// Creator always has access
if (userId && userId === stream.creator_id) {
return true;
}
// Public read access - anyone can read
if (stream.access_permission === "public") {
return true;
}
// Private access - only creator
if (stream.access_permission === "private") {
return userId === stream.creator_id;
}
// No auth means no access for non-public
if (!userId) {
return false;
}
// Parse permission
const perm = parsePermission(stream.access_permission);
logger.debug("Parsed permission", {
perm,
accessPermission: stream.access_permission,
});
if (perm.type === "stream" && perm.stream) {
// Get pod for this stream
const pod = await db.oneOrNone(`SELECT * FROM pod WHERE id = $(podId)`, { podId: stream.pod_id });
if (!pod)
return false;
// Check if user has read permission in the permission stream
return await checkPermissionStream(db, pod.pod_id, perm.stream, userId, "read");
}
return false;
}
/**
* Check if user can write to stream
*/
export async function canWrite(db, stream, userId) {
// Creator always has access
if (userId === stream.creator_id) {
return true;
}
// Public write access - authenticated users can write
if (stream.access_permission === "public") {
return true;
}
// Private access - only creator
if (stream.access_permission === "private") {
return userId === stream.creator_id;
}
// Parse permission
const perm = parsePermission(stream.access_permission);
if (perm.type === "stream" && perm.stream) {
// Get pod for this stream
const pod = await db.oneOrNone(`SELECT * FROM pod WHERE id = $(podId)`, { podId: stream.pod_id });
if (!pod)
return false;
// Check if user has write permission in the permission stream
return await checkPermissionStream(db, pod.pod_id, perm.stream, userId, "write");
}
return false;
}
//# sourceMappingURL=permissions.js.map