feat(web): public read-only file browser — GET API public, writes require admin
- /api/v1/* GET (list buckets/objects, download) no longer requires auth - POST/DELETE/PUT stay behind requireAuth (upload, create/delete bucket, copy, delete object) - FE drops blocking login screen: visitors browse + download freely - Admin-only UI (create bucket, upload dropzone, delete, S3 creds) hidden in read-only mode - Login button in topbar to unlock admin actions
This commit is contained in:
+58
-26
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -109,8 +109,11 @@ export const routes = {
|
|||||||
'/api/v1/auth/me': {
|
'/api/v1/auth/me': {
|
||||||
GET: handleMe,
|
GET: handleMe,
|
||||||
},
|
},
|
||||||
|
// Read endpoints (GET) are public — anyone can list buckets/objects and
|
||||||
|
// download files. Write endpoints (POST/DELETE/PUT) require admin auth so
|
||||||
|
// visitors cannot upload, edit, copy, or delete.
|
||||||
'/api/v1/*': {
|
'/api/v1/*': {
|
||||||
GET: requireAuth(handleWebApiV1),
|
GET: handleWebApiV1,
|
||||||
POST: requireAuth(handleWebApiV1),
|
POST: requireAuth(handleWebApiV1),
|
||||||
DELETE: requireAuth(handleWebApiV1),
|
DELETE: requireAuth(handleWebApiV1),
|
||||||
PUT: requireAuth(handleWebApiV1),
|
PUT: requireAuth(handleWebApiV1),
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { describe, expect, it } from 'bun:test';
|
||||||
|
|
||||||
|
const defaultEnv = (key: string, value: string) => {
|
||||||
|
process.env[key] ||= value;
|
||||||
|
};
|
||||||
|
const setEnv = (key: string, value: string) => {
|
||||||
|
process.env[key] = value;
|
||||||
|
};
|
||||||
|
|
||||||
|
defaultEnv('BOT_TOKENS', '123456:ABC-DEF');
|
||||||
|
defaultEnv('STORAGE_CHANNEL_ID', '-1001234567890');
|
||||||
|
defaultEnv('BASE_URL', 'https://example.com');
|
||||||
|
defaultEnv('DATABASE_URL', 'postgresql://user:***@localhost:5432/test');
|
||||||
|
defaultEnv('PORT', '3000');
|
||||||
|
defaultEnv('NODE_ENV', 'test');
|
||||||
|
setEnv('ADMIN_API_TOKEN', 'route-secret-token');
|
||||||
|
setEnv('SESSION_COOKIE_NAME', 'route_session');
|
||||||
|
setEnv('SESSION_COOKIE_MAX_AGE_SECONDS', '3600');
|
||||||
|
|
||||||
|
const { routes } = await import('../src/interfaces/http/routes/index');
|
||||||
|
const { handleWebApiV1 } = await import('../src/interfaces/http/controllers/web-api-controller');
|
||||||
|
|
||||||
|
describe('public read-only API routing', () => {
|
||||||
|
it('serves GET /api/v1/* without auth (read endpoints are public)', () => {
|
||||||
|
// The GET handler must be the raw web API handler — NOT requireAuth-wrapped.
|
||||||
|
expect(routes['/api/v1/*'].GET).toBe(handleWebApiV1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('protects write endpoints with auth (POST/DELETE/PUT)', () => {
|
||||||
|
// requireAuth wraps the handler into a new function, so these must NOT be
|
||||||
|
// the raw handler — they are auth-guarded.
|
||||||
|
expect(routes['/api/v1/*'].POST).not.toBe(handleWebApiV1);
|
||||||
|
expect(routes['/api/v1/*'].DELETE).not.toBe(handleWebApiV1);
|
||||||
|
expect(routes['/api/v1/*'].PUT).not.toBe(handleWebApiV1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps the auth/me endpoint accessible (frontend uses it to detect admin state)', () => {
|
||||||
|
expect(routes['/api/v1/auth/me']).toBeDefined();
|
||||||
|
expect(typeof routes['/api/v1/auth/me'].GET).toBe('function');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user