feat(storage): add configurable storage access policies
Replace hardcoded `users/` path-based access control with a declarative `storageAccessPolicies` system defined per module via `defineModule()`. - Add `storageAccessPolicies` field to `defineModule()` defaults with support for `owner` and `admin` policy types - Expose `getAllStorageAccessPolicies()` from the modules/storage layer - Refactor `handleGetFile` in `storage/api.js` to resolve access control dynamically from registered policies instead of hardcoded path checks - Add `ZEN_STORAGE_ENDPOINT` env var and update `.env.example` to support S3-compatible backends (Cloudflare R2, Backblaze B2) - Document the env/doc sync convention in `DEV.md`
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
/**
|
||||
* Module Storage Registry (Server-Side)
|
||||
*
|
||||
* Aggregates storage public prefixes declared by each module via defineModule().
|
||||
* A prefix listed here is served without authentication by the storage handler.
|
||||
* Aggregates storage public prefixes and private access policies declared by
|
||||
* each module via defineModule().
|
||||
*
|
||||
* Internal modules declare `storagePublicPrefixes` in their defineModule() config.
|
||||
* External modules registered at runtime are also included automatically.
|
||||
* Public prefixes are served without authentication.
|
||||
* Access policies control auth requirements for private paths.
|
||||
*
|
||||
* Usage:
|
||||
* import { getAllStoragePublicPrefixes } from '@zen/core/modules/storage';
|
||||
* import { getAllStoragePublicPrefixes, getAllStorageAccessPolicies } from '@zen/core/modules/storage';
|
||||
*/
|
||||
|
||||
import { getModule, getEnabledModules } from '@zen/core/core/modules';
|
||||
@@ -51,3 +51,38 @@ export function getAllStoragePublicPrefixes() {
|
||||
|
||||
return [...prefixes];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all storage access policies from every enabled module.
|
||||
*
|
||||
* Policies for built-in features (auth, posts) are included directly.
|
||||
* External modules contribute via their `storageAccessPolicies` defineModule field.
|
||||
*
|
||||
* Policy shape: { prefix: string, type: 'owner' | 'admin' }
|
||||
* 'owner' — pathParts[1] must match session.user.id, or role is 'admin'
|
||||
* 'admin' — session.user.role must be 'admin'
|
||||
*
|
||||
* @returns {{ prefix: string, type: string }[]}
|
||||
*/
|
||||
export function getAllStorageAccessPolicies() {
|
||||
const policies = [
|
||||
// Built-in auth feature — user files are owner-scoped
|
||||
{ prefix: 'users', type: 'owner' },
|
||||
];
|
||||
|
||||
// Posts module — non-public post paths require admin
|
||||
if (process.env.ZEN_MODULE_POSTS === 'true') {
|
||||
policies.push({ prefix: 'posts', type: 'admin' });
|
||||
}
|
||||
|
||||
// External modules
|
||||
for (const mod of getEnabledModules()) {
|
||||
if (!mod.external) continue;
|
||||
const runtimeConfig = getModule(mod.name);
|
||||
for (const policy of runtimeConfig?.storageAccessPolicies ?? []) {
|
||||
policies.push(policy);
|
||||
}
|
||||
}
|
||||
|
||||
return policies;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import {
|
||||
import {
|
||||
uploadImage,
|
||||
deleteFile,
|
||||
generatePostFilePath,
|
||||
generateUniqueFilename,
|
||||
validateUpload,
|
||||
getFileExtension,
|
||||
@@ -32,6 +31,8 @@ import {
|
||||
FILE_SIZE_LIMITS
|
||||
} from '@zen/core/storage';
|
||||
|
||||
const generatePostFilePath = (typeKey, postIdOrSlug, filename) => `posts/${typeKey}/${postIdOrSlug}/${filename}`;
|
||||
|
||||
/**
|
||||
* Extension → MIME type map derived from the validated file extension.
|
||||
* The client-supplied file.type is NEVER trusted — it is an attacker-controlled
|
||||
|
||||
Reference in New Issue
Block a user