@rebasepro/server 0.9.1-canary.f2f61da → 0.9.1-canary.faee831

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,6 +3,13 @@
3
3
  */
4
4
  import { StorageController, LocalStorageConfig } from "./types";
5
5
  import { UploadFileProps, UploadFileResult, DownloadConfig, StorageListResult } from "@rebasepro/types";
6
+ /**
7
+ * Bucket used when a call names none.
8
+ *
9
+ * Every method resolves through this, so put/get/delete/list agree on where a
10
+ * bare key lives.
11
+ */
12
+ export declare const DEFAULT_BUCKET = "default";
6
13
  /**
7
14
  * Local filesystem storage implementation
8
15
  * Stores files in a directory structure: {basePath}/{bucket}/{path}
@@ -17,8 +24,18 @@ export declare class LocalStorageController implements StorageController {
17
24
  */
18
25
  private ensureDir;
19
26
  /**
20
- * Get the full filesystem path for a storage path.
21
- * Includes a path traversal guard to prevent escaping the base directory.
27
+ * Get the full filesystem path for a storage path, with a traversal guard
28
+ * that keeps the result inside the bucket directory.
29
+ *
30
+ * Defaults the bucket the way `putObject` does.
31
+ *
32
+ * `putObject` has always written into `default` when given no bucket, while
33
+ * the read side resolved a bare key against the storage root — where
34
+ * nothing is. The two disagreed silently: `getObject` returned null (reads
35
+ * as "file missing"), `deleteObject` deleted nothing (404s are swallowed by
36
+ * design), and `listObjects` returned an empty page. So the obvious
37
+ * `putObject({ key })` → `getObject(key)` did not round-trip and nothing
38
+ * said why. One default, applied everywhere, removes the whole class.
22
39
  */
23
40
  private getFullPath;
24
41
  /**
@@ -7,7 +7,7 @@
7
7
  * is used.
8
8
  */
9
9
  import { Hono } from "hono";
10
- import { StorageController } from "./types";
10
+ import { StorageController, type StorageAuthorize } from "./types";
11
11
  import type { StorageRegistry } from "./storage-registry";
12
12
  import { type StorageSourceDefinition, type AuthAdapter } from "@rebasepro/types";
13
13
  import { HonoEnv } from "../api/types";
@@ -45,6 +45,13 @@ export interface StorageRoutesConfig {
45
45
  * configured" crash when `configureJwt()` was never called.
46
46
  */
47
47
  authAdapter?: AuthAdapter;
48
+ /**
49
+ * Per-object access control, consulted after authentication on every
50
+ * storage route. See `StorageAuthorize`.
51
+ *
52
+ * Omitted, storage behaves as before: authenticated means allowed.
53
+ */
54
+ authorize?: StorageAuthorize;
48
55
  }
49
56
  /**
50
57
  * Extract the wildcard portion of a route path from the full request path.
@@ -20,10 +20,30 @@ import type { StorageRegistry } from "./storage-registry";
20
20
  export declare class TusHandler {
21
21
  private storageController?;
22
22
  private storageRegistry?;
23
+ /**
24
+ * Per-object authorization, applied to the resumable path too.
25
+ *
26
+ * TUS is a second way to write an object, so a hook enforced only on
27
+ * `POST /upload` would leave the door it was added to close standing
28
+ * open. The target key lives in the `Upload-Metadata` header, which
29
+ * only this class parses — hence the injection rather than a check in
30
+ * the route. Rejects by throwing.
31
+ */
32
+ private authorizeUpload?;
23
33
  private uploads;
24
34
  private tusDir;
25
35
  private cleanupTimer?;
26
- constructor(storageBaseDir: string, storageController?: StorageController | undefined, storageRegistry?: StorageRegistry | undefined);
36
+ constructor(storageBaseDir: string, storageController?: StorageController | undefined, storageRegistry?: StorageRegistry | undefined,
37
+ /**
38
+ * Per-object authorization, applied to the resumable path too.
39
+ *
40
+ * TUS is a second way to write an object, so a hook enforced only on
41
+ * `POST /upload` would leave the door it was added to close standing
42
+ * open. The target key lives in the `Upload-Metadata` header, which
43
+ * only this class parses — hence the injection rather than a check in
44
+ * the route. Rejects by throwing.
45
+ */
46
+ authorizeUpload?: ((c: Context, key: string, bucket: string) => Promise<void>) | undefined);
27
47
  /** Ensure the temp directory exists. */
28
48
  private ensureDir;
29
49
  /** Start periodic cleanup of stale uploads. */
@@ -78,6 +78,39 @@ export interface GCSStorageConfig {
78
78
  * interface and pass the instance directly to the `storage` config.
79
79
  */
80
80
  export type BackendStorageConfig = LocalStorageConfig | S3StorageConfig | GCSStorageConfig;
81
+ /** What a storage request is trying to do to an object. */
82
+ export type StorageOperation = "read" | "write" | "delete" | "list";
83
+ /** The caller, as resolved by whichever auth middleware ran. */
84
+ export interface StorageAuthorizeUser {
85
+ uid: string;
86
+ email?: string;
87
+ roles?: string[];
88
+ }
89
+ export interface StorageAuthorizeContext {
90
+ /** Object key, bucket prefix stripped and traversal already sanitized. */
91
+ key: string;
92
+ bucket: string;
93
+ operation: StorageOperation;
94
+ /** Null when the route allows unauthenticated access. */
95
+ user: StorageAuthorizeUser | null;
96
+ /** Named backend the request targeted, when one was given. */
97
+ storageId?: string;
98
+ }
99
+ /**
100
+ * Per-object access control for storage, the analogue of an RLS policy on a
101
+ * collection.
102
+ *
103
+ * Without it, storage routes authenticate but do not authorize: `requireAuth`
104
+ * and `publicRead` are global switches, so any signed-in user could read any
105
+ * key they could name. That is fine for a single-tenant app and useless for a
106
+ * multi-tenant one, where the only thing separating two tenants' files would be
107
+ * key unguessability — not an access-control model. Multi-tenant apps were
108
+ * left to route every byte through a custom function to get an ownership check
109
+ * in, and each of them had to invent it.
110
+ *
111
+ * Return false (or throw) to deny. Denials surface as 403.
112
+ */
113
+ export type StorageAuthorize = (ctx: StorageAuthorizeContext) => boolean | Promise<boolean>;
81
114
  /**
82
115
  * Storage controller interface for backend implementations
83
116
  */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rebasepro/server",
3
3
  "type": "module",
4
- "version": "0.9.1-canary.f2f61da",
4
+ "version": "0.9.1-canary.faee831",
5
5
  "description": "Database-Agnostic Backend Core for Rebase",
6
6
  "funding": {
7
7
  "url": "https://github.com/sponsors/rebaseco"
@@ -44,13 +44,13 @@
44
44
  "jsonwebtoken": "^9.0.3",
45
45
  "ws": "^8.21.0",
46
46
  "zod": "^4.4.3",
47
- "@rebasepro/client": "0.9.1-canary.f2f61da",
48
- "@rebasepro/common": "0.9.1-canary.f2f61da",
49
- "@rebasepro/types": "0.9.1-canary.f2f61da",
50
- "@rebasepro/utils": "0.9.1-canary.f2f61da"
47
+ "@rebasepro/common": "0.9.1-canary.faee831",
48
+ "@rebasepro/types": "0.9.1-canary.faee831",
49
+ "@rebasepro/utils": "0.9.1-canary.faee831",
50
+ "@rebasepro/client": "0.9.1-canary.faee831"
51
51
  },
52
52
  "devDependencies": {
53
- "hono": "^4.12.25",
53
+ "@jest/globals": "^30.4.1",
54
54
  "@types/jest": "^30.0.0",
55
55
  "@types/jsonwebtoken": "^9.0.10",
56
56
  "@types/node": "^25.9.3",
@@ -60,6 +60,7 @@
60
60
  "@types/ws": "^8.18.1",
61
61
  "@vitejs/plugin-react": "^6.0.2",
62
62
  "cross-env": "^10.1.0",
63
+ "hono": "^4.12.25",
63
64
  "jest": "^30.4.2",
64
65
  "ts-jest": "29.4.11",
65
66
  "typescript": "^6.0.3",