@filelayer/core 0.3.0
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.
- package/CHANGELOG.md +338 -0
- package/LICENSE +202 -0
- package/MIGRATIONS.md +328 -0
- package/NOTICE +37 -0
- package/README.md +343 -0
- package/SEMANTICS.md +729 -0
- package/dist/authz.d.ts +524 -0
- package/dist/authz.d.ts.map +1 -0
- package/dist/authz.js +889 -0
- package/dist/authz.js.map +1 -0
- package/dist/db.d.ts +145 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +217 -0
- package/dist/db.js.map +1 -0
- package/dist/delivery.d.ts +293 -0
- package/dist/delivery.d.ts.map +1 -0
- package/dist/delivery.js +519 -0
- package/dist/delivery.js.map +1 -0
- package/dist/errors.d.ts +16 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +21 -0
- package/dist/errors.js.map +1 -0
- package/dist/filelayer.d.ts +542 -0
- package/dist/filelayer.d.ts.map +1 -0
- package/dist/filelayer.js +1360 -0
- package/dist/filelayer.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/simple.d.ts +297 -0
- package/dist/simple.d.ts.map +1 -0
- package/dist/simple.js +492 -0
- package/dist/simple.js.map +1 -0
- package/dist/storage.d.ts +269 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +700 -0
- package/dist/storage.js.map +1 -0
- package/dist/store.d.ts +432 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +862 -0
- package/dist/store.js.map +1 -0
- package/package.json +77 -0
- package/schema.sql +1190 -0
- package/src/authz.ts +1398 -0
- package/src/db.ts +271 -0
- package/src/delivery.ts +737 -0
- package/src/errors.ts +24 -0
- package/src/filelayer.ts +1836 -0
- package/src/index.ts +7 -0
- package/src/simple.ts +666 -0
- package/src/storage.ts +917 -0
- package/src/store.ts +1072 -0
- package/test/delivery.test.ts +0 -0
- package/test/group-subjects.test.ts +1072 -0
- package/test/helpers.ts +65 -0
- package/test/listing.test.ts +689 -0
- package/test/local-s3.d.mts +33 -0
- package/test/local-s3.mjs +400 -0
- package/test/persistence.test.ts +953 -0
- package/test/regression.test.ts +619 -0
- package/test/s3-live.test.ts +322 -0
- package/test/security.test.ts +1652 -0
- package/test/semantics.test.ts +888 -0
- package/test/storage.test.ts +437 -0
- package/test/tiers.test.ts +432 -0
- package/test/vault-example.test.ts +302 -0
- package/tsconfig.build.json +29 -0
- package/tsconfig.json +19 -0
package/dist/simple.js
ADDED
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PROGRESSIVE DISCLOSURE -- the tiered surface.
|
|
3
|
+
*
|
|
4
|
+
* This file exists to answer one question:
|
|
5
|
+
*
|
|
6
|
+
* "A developer who only needs a public avatar should be able to use an
|
|
7
|
+
* extremely simple version. A developer who needs a multi-tenant document
|
|
8
|
+
* system should be able to turn on the advanced capabilities."
|
|
9
|
+
*
|
|
10
|
+
* WHAT THIS FILE IS
|
|
11
|
+
* -----------------
|
|
12
|
+
* A thin, additive facade over `Filelayer`. It provisions the concepts the
|
|
13
|
+
* developer did not ask for (a workspace, a service identity, a membership) so
|
|
14
|
+
* that the trivial case costs one line, and it names the concepts the developer
|
|
15
|
+
* DID ask for (an owner, an org, a role) so that the advanced case is reachable
|
|
16
|
+
* by adding an option rather than by rewriting.
|
|
17
|
+
*
|
|
18
|
+
* WHAT THIS FILE IS NOT
|
|
19
|
+
* ---------------------
|
|
20
|
+
* It contains **no authorization logic**. Every call below goes through the same
|
|
21
|
+
* `authz.ts` engine as the full API, with the same audit guarantee. Grep it: the
|
|
22
|
+
* only `if` statements are about which identifier to resolve, never about
|
|
23
|
+
* whether access is permitted.
|
|
24
|
+
*
|
|
25
|
+
* THE FOUR SECURITY PROPERTIES, AND HOW EACH SURVIVES THE ERGONOMICS
|
|
26
|
+
* -----------------------------------------------------------------
|
|
27
|
+
* P1 (deny by default, no public boolean).
|
|
28
|
+
* `{ public: true }` does NOT set a flag. It creates an `anonymous` grant
|
|
29
|
+
* row -- the same row `share({subject:{type:'anonymous'}})` creates -- which
|
|
30
|
+
* is explicit, listable, revocable and audited. `unpublish()` revokes it.
|
|
31
|
+
* There is still no `file.public` column and there never will be.
|
|
32
|
+
*
|
|
33
|
+
* P2 (no ambient authority).
|
|
34
|
+
* Auto-provisioning creates IDENTITIES, never PERMISSIONS beyond the file
|
|
35
|
+
* the caller is creating. A user auto-registered by `put({owner})` gets the
|
|
36
|
+
* `member` role, and `member` + the default `private` visibility means they
|
|
37
|
+
* can read exactly their own files and nothing else. Reads never
|
|
38
|
+
* auto-provision: an unknown `as:` is a 404, not a silent downgrade to
|
|
39
|
+
* anonymous. That last rule is the one that would have been easy to get
|
|
40
|
+
* wrong and is tested explicitly.
|
|
41
|
+
*
|
|
42
|
+
* P3 (tenant isolation is structural).
|
|
43
|
+
* Untouched. Every file still has a real `org_id`. The "no org" experience
|
|
44
|
+
* is a DEFAULT org, not a NULL one -- see ARCHITECTURE-PROGRESSIVE.md for
|
|
45
|
+
* the measurement showing that a nullable `org_id` silently disables the
|
|
46
|
+
* composite foreign key that P3 rests on.
|
|
47
|
+
*
|
|
48
|
+
* P4 (a URL never outlives its permission).
|
|
49
|
+
* The public URL is `/f/:id` served by our own delivery path, which
|
|
50
|
+
* re-authorizes on every request. Revoke the anonymous grant and the URL is
|
|
51
|
+
* dead on the next request. This is the property a public S3 bucket and
|
|
52
|
+
* Supabase's `getPublicUrl` cannot offer at any price. It is ALSO the
|
|
53
|
+
* property that CDN caching would weaken -- see `delivery.ts`, which
|
|
54
|
+
* defaults to `no-store` for that reason.
|
|
55
|
+
*/
|
|
56
|
+
import { FilelayerError, } from "./filelayer.js";
|
|
57
|
+
import { DEFAULT_PROJECT_ID } from "./store.js";
|
|
58
|
+
/**
|
|
59
|
+
* The org every file lands in when the developer never mentioned an org.
|
|
60
|
+
*
|
|
61
|
+
* A reserved `external_id`, not a magic NULL. Tier 3 is reached by naming a
|
|
62
|
+
* different org, not by turning a boundary on.
|
|
63
|
+
*/
|
|
64
|
+
export const DEFAULT_WORKSPACE = '__filelayer_workspace__';
|
|
65
|
+
/**
|
|
66
|
+
* The identity that owns files uploaded with no `owner:`.
|
|
67
|
+
*
|
|
68
|
+
* It is the `owner` of the default workspace (so tier 1 works with no user
|
|
69
|
+
* model at all) and a plain `member` of any org the developer names (so it does
|
|
70
|
+
* NOT acquire admin read over a real tenant's documents as a side effect of
|
|
71
|
+
* being used for an unowned upload).
|
|
72
|
+
*/
|
|
73
|
+
export const SYSTEM_ACTOR = '__filelayer_system__';
|
|
74
|
+
// -----------------------------------------------------------------------------
|
|
75
|
+
// Content sniffing
|
|
76
|
+
// -----------------------------------------------------------------------------
|
|
77
|
+
//
|
|
78
|
+
// Deliberately a short, conservative list of formats with unambiguous magic
|
|
79
|
+
// bytes. Everything else is `application/octet-stream`, which -- combined with
|
|
80
|
+
// the `X-Content-Type-Options: nosniff` and sandbox CSP headers in delivery.ts
|
|
81
|
+
// -- means an unrecognised upload is downloaded rather than executed. SVG is
|
|
82
|
+
// absent on purpose: it has no reliable magic number and it is a script
|
|
83
|
+
// execution context. Pass `contentType` explicitly if you need one.
|
|
84
|
+
const MAGIC = [
|
|
85
|
+
[[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a], 'image/png'],
|
|
86
|
+
[[0xff, 0xd8, 0xff], 'image/jpeg'],
|
|
87
|
+
[[0x47, 0x49, 0x46, 0x38], 'image/gif'],
|
|
88
|
+
[[0x25, 0x50, 0x44, 0x46], 'application/pdf'],
|
|
89
|
+
];
|
|
90
|
+
export function sniffContentType(body) {
|
|
91
|
+
for (const [sig, type] of MAGIC) {
|
|
92
|
+
if (sig.every((b, i) => body[i] === b))
|
|
93
|
+
return type;
|
|
94
|
+
}
|
|
95
|
+
// RIFF....WEBP
|
|
96
|
+
if (body[0] === 0x52 && body[1] === 0x49 && body[2] === 0x46 && body[3] === 0x46 &&
|
|
97
|
+
body[8] === 0x57 && body[9] === 0x45 && body[10] === 0x42 && body[11] === 0x50) {
|
|
98
|
+
return 'image/webp';
|
|
99
|
+
}
|
|
100
|
+
return 'application/octet-stream';
|
|
101
|
+
}
|
|
102
|
+
class Identities {
|
|
103
|
+
fl;
|
|
104
|
+
constructor(fl) {
|
|
105
|
+
this.fl = fl;
|
|
106
|
+
}
|
|
107
|
+
/** The project this facade resolves identities in. See P8 in schema.sql. */
|
|
108
|
+
get project() {
|
|
109
|
+
return this.fl.projectId ?? DEFAULT_PROJECT_ID;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Get-or-create an org by the customer's own id.
|
|
113
|
+
*
|
|
114
|
+
* `ON CONFLICT (project_id, external_id)` rather than a read-then-write, so
|
|
115
|
+
* two cold requests racing to publish the first avatar cannot produce a
|
|
116
|
+
* duplicate-key error in the developer's face.
|
|
117
|
+
*
|
|
118
|
+
* P8 (THE CUSTOMER'S ID SPACE IS THE CUSTOMER'S), AND THIS IS THE SHARPEST
|
|
119
|
+
* EDGE OF IT. When `external_id` was globally
|
|
120
|
+
* unique, this exact statement was a cross-tenant compromise: customer B
|
|
121
|
+
* calling `put({ org: 'acme' })` did not get an error, it got customer A's
|
|
122
|
+
* org id -- and `membership()` below then added B's user to A's tenant. The
|
|
123
|
+
* conflict target is now the (project, external_id) pair, so "acme" in one
|
|
124
|
+
* customer's application and "acme" in another are different rows that can
|
|
125
|
+
* never resolve to each other.
|
|
126
|
+
*/
|
|
127
|
+
async org(externalId, opts = {}) {
|
|
128
|
+
const { rows } = await this.fl.store.db.query(`INSERT INTO org (project_id, external_id, name) VALUES ($3, $1, $2)
|
|
129
|
+
ON CONFLICT (project_id, external_id) DO UPDATE SET external_id = EXCLUDED.external_id
|
|
130
|
+
RETURNING id`, [externalId, opts.name ?? externalId, this.project]);
|
|
131
|
+
const id = rows[0].id;
|
|
132
|
+
if (opts.ownerActorId)
|
|
133
|
+
await this.membership(id, opts.ownerActorId, 'owner', true);
|
|
134
|
+
return id;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Get-or-create an identity by the customer's own id, within this project.
|
|
138
|
+
*
|
|
139
|
+
* FAILS CLOSED ON A DELETED IDENTITY. Auto-provisioning must never resurrect
|
|
140
|
+
* someone who has been deleted: `ON CONFLICT DO UPDATE` would otherwise hand
|
|
141
|
+
* back a soft-deleted actor's id, and the caller would go on to create a file
|
|
142
|
+
* owned by a person the system has been told no longer exists. Access would
|
|
143
|
+
* still be denied downstream (P7 kills their role and their grants), so the
|
|
144
|
+
* failure is fail-closed either way -- but silently owning rows to a deleted
|
|
145
|
+
* identity is a data-integrity mess, not a security decision we should be
|
|
146
|
+
* making by accident.
|
|
147
|
+
*/
|
|
148
|
+
async actor(externalId) {
|
|
149
|
+
const { rows } = await this.fl.store.db.query(`INSERT INTO actor (project_id, external_id) VALUES ($2, $1)
|
|
150
|
+
ON CONFLICT (project_id, external_id) DO UPDATE SET external_id = EXCLUDED.external_id
|
|
151
|
+
RETURNING id, deleted_at`, [externalId, this.project]);
|
|
152
|
+
const row = rows[0];
|
|
153
|
+
if (row.deleted_at !== null) {
|
|
154
|
+
throw new FilelayerError(404, 'not_found', 'deleted_actor');
|
|
155
|
+
}
|
|
156
|
+
return row.id;
|
|
157
|
+
}
|
|
158
|
+
/** Read-only lookup. Returns null for an unknown id -- callers must fail closed. */
|
|
159
|
+
async findActor(externalId) {
|
|
160
|
+
const { rows } = await this.fl.store.db.query(`SELECT id FROM actor
|
|
161
|
+
WHERE external_id = $1 AND project_id = $2 AND deleted_at IS NULL`, [externalId, this.project]);
|
|
162
|
+
return rows[0]?.id ?? null;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Ensure a membership exists, WITHOUT ever downgrading an existing one.
|
|
166
|
+
*
|
|
167
|
+
* `DO NOTHING` and not `DO UPDATE`: auto-provisioning must never be able to
|
|
168
|
+
* demote an admin a developer deliberately promoted, nor promote a member.
|
|
169
|
+
* Deliberate role changes go through `orgs.setRole`, which is authorized.
|
|
170
|
+
*/
|
|
171
|
+
async membership(orgId, actorId, role, bootstrap = false) {
|
|
172
|
+
const { rows } = await this.fl.store.db.query(`INSERT INTO membership (org_id, actor_id, role) VALUES ($1,$2,$3)
|
|
173
|
+
ON CONFLICT (org_id, actor_id) DO NOTHING
|
|
174
|
+
RETURNING true AS inserted`, [orgId, actorId, role]);
|
|
175
|
+
// P5: an identity acquiring standing in an org is a privilege change and is
|
|
176
|
+
// audited like any other, even though it was implicit.
|
|
177
|
+
if (rows.length > 0) {
|
|
178
|
+
await this.fl.store.audit({
|
|
179
|
+
orgId,
|
|
180
|
+
action: bootstrap ? 'member.bootstrap' : 'member.add',
|
|
181
|
+
decision: 'allow',
|
|
182
|
+
actorId,
|
|
183
|
+
fileId: null,
|
|
184
|
+
context: { targetActorId: actorId, toRole: role, via: 'auto_provision' },
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// -----------------------------------------------------------------------------
|
|
190
|
+
// files.*
|
|
191
|
+
// -----------------------------------------------------------------------------
|
|
192
|
+
export class FilesApi {
|
|
193
|
+
fl;
|
|
194
|
+
ids;
|
|
195
|
+
constructor(fl) {
|
|
196
|
+
this.fl = fl;
|
|
197
|
+
this.ids = new Identities(fl);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* TIER 1: `put(bytes, { public: true })` -> a URL, no other concepts.
|
|
201
|
+
* TIER 2: `put(bytes, { owner: userId })` -> adds an owner.
|
|
202
|
+
* TIER 3: `put(bytes, { org, owner })` -> adds a tenant.
|
|
203
|
+
* TIER 4: `+ expiresIn / retainFor / metadata`
|
|
204
|
+
*/
|
|
205
|
+
async put(body, opts = {}) {
|
|
206
|
+
const contentType = opts.contentType ?? sniffContentType(body);
|
|
207
|
+
const name = opts.name ?? 'file';
|
|
208
|
+
// Resolve the tenant. Naming an org is what promotes you from tier 2 to
|
|
209
|
+
// tier 3; not naming one puts you in the default workspace, which is a real
|
|
210
|
+
// org with a real id, not a hole in the model.
|
|
211
|
+
const isDefaultWorkspace = opts.org === undefined;
|
|
212
|
+
const system = await this.ids.actor(SYSTEM_ACTOR);
|
|
213
|
+
const orgId = isDefaultWorkspace
|
|
214
|
+
? await this.ids.org(DEFAULT_WORKSPACE, { name: 'workspace', ownerActorId: system })
|
|
215
|
+
: await this.ids.org(opts.org);
|
|
216
|
+
// Resolve the owner. `owner:` auto-registers, because the developer is
|
|
217
|
+
// asserting the identity exists in their system; we are not inventing it.
|
|
218
|
+
let ownerId;
|
|
219
|
+
if (opts.owner !== undefined) {
|
|
220
|
+
ownerId = await this.ids.actor(opts.owner);
|
|
221
|
+
await this.ids.membership(orgId, ownerId, 'member');
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
ownerId = system;
|
|
225
|
+
// In a NAMED org the service identity is only a `member`, so an unowned
|
|
226
|
+
// upload into a customer tenant does not hand us admin read over that
|
|
227
|
+
// tenant's documents. In the default workspace it is the owner, because
|
|
228
|
+
// somebody has to be.
|
|
229
|
+
if (!isDefaultWorkspace)
|
|
230
|
+
await this.ids.membership(orgId, system, 'member');
|
|
231
|
+
}
|
|
232
|
+
// The uploader is a Principal, not a bare id: ownership is derived from the
|
|
233
|
+
// authorized identity, never supplied beside it. See the note on the
|
|
234
|
+
// signature of `upload()` in filelayer.ts.
|
|
235
|
+
const file = await this.fl.upload({ actorId: ownerId }, orgId, {
|
|
236
|
+
name,
|
|
237
|
+
contentType,
|
|
238
|
+
body,
|
|
239
|
+
...(opts.visibility ? { visibility: opts.visibility } : {}),
|
|
240
|
+
...(opts.expiresIn !== undefined ? { expiresIn: opts.expiresIn } : {}),
|
|
241
|
+
...(opts.retainFor !== undefined ? { retainFor: opts.retainFor } : {}),
|
|
242
|
+
...(opts.metadata ? { metadata: opts.metadata } : {}),
|
|
243
|
+
});
|
|
244
|
+
let url;
|
|
245
|
+
if (opts.public) {
|
|
246
|
+
// P1 intact: this is a grant row, not a flag. The uploader mints it, so
|
|
247
|
+
// it goes through `authorizeShare` and is audited as `grant.create`.
|
|
248
|
+
await this.fl.share({ actorId: ownerId }, file.id, { subject: { type: 'anonymous' } });
|
|
249
|
+
url = this.publicUrl(file.id);
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
id: file.id,
|
|
253
|
+
name: file.name,
|
|
254
|
+
contentType: file.contentType,
|
|
255
|
+
size: file.sizeBytes ?? body.byteLength,
|
|
256
|
+
...(url ? { url } : {}),
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Read a file as a user (`{ as }`), or anonymously (no options).
|
|
261
|
+
*
|
|
262
|
+
* `headers` is carried through from the delivery layer so that the simple
|
|
263
|
+
* tier is exactly as safe as the full one. A tier-2 app that writes
|
|
264
|
+
* `res.writeHead(200, { 'content-type': f.contentType })` re-opens the
|
|
265
|
+
* stored-XSS path; `res.writeHead(200, f.headers)` does not.
|
|
266
|
+
*/
|
|
267
|
+
async get(fileId, opts = {}) {
|
|
268
|
+
const principal = await this.principal(opts);
|
|
269
|
+
const { file, body, headers } = await this.fl.read(principal, fileId);
|
|
270
|
+
return { id: file.id, name: file.name, contentType: file.contentType, body, headers };
|
|
271
|
+
}
|
|
272
|
+
async delete(fileId, opts = {}) {
|
|
273
|
+
await this.fl.delete(await this.principal(opts), fileId);
|
|
274
|
+
}
|
|
275
|
+
/** Make an existing file publicly readable. Same grant row as `put({public:true})`. */
|
|
276
|
+
async publish(fileId, opts = {}) {
|
|
277
|
+
const principal = await this.principal(opts, { orSystem: true });
|
|
278
|
+
const g = await this.fl.share(principal, fileId, { subject: { type: 'anonymous' } });
|
|
279
|
+
return { url: this.publicUrl(fileId), grantId: g.grantId };
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Withdraw public access. Revokes every live anonymous grant on the file.
|
|
283
|
+
*
|
|
284
|
+
* This is the operation a public bucket cannot perform: after it returns, a
|
|
285
|
+
* URL that has been printed, indexed and shared stops working on the very
|
|
286
|
+
* next request, because delivery re-authorizes every time.
|
|
287
|
+
*/
|
|
288
|
+
async unpublish(fileId, opts = {}) {
|
|
289
|
+
const principal = await this.principal(opts, { orSystem: true });
|
|
290
|
+
const grants = await this.fl.listGrants(principal, fileId);
|
|
291
|
+
let revoked = 0;
|
|
292
|
+
for (const g of grants) {
|
|
293
|
+
if (g.subjectType !== 'anonymous' || !g.live)
|
|
294
|
+
continue;
|
|
295
|
+
await this.fl.revoke(principal, g.id);
|
|
296
|
+
revoked++;
|
|
297
|
+
}
|
|
298
|
+
return { revoked };
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* The delivery URL for a public file. Meaningless without an anonymous grant.
|
|
302
|
+
*
|
|
303
|
+
* IT ALWAYS BUILDS ON `/f`, so whatever serves it has to be mounted there.
|
|
304
|
+
* `deliveryHandler(fl)` is -- its `filePrefix` defaults to `/f`. But
|
|
305
|
+
* `fileDownloadRoute(fl, { principal })` mounted on its own defaults to a
|
|
306
|
+
* `prefix` of `/files`, and a URL from here would 404 against it. Either
|
|
307
|
+
* mount `deliveryHandler`, or pass `prefix: '/f'` to `fileDownloadRoute`.
|
|
308
|
+
*/
|
|
309
|
+
publicUrl(fileId) {
|
|
310
|
+
return `${this.fl.baseUrl ?? ''}/f/${fileId}`;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Metadata only, no bytes, authorized identically to `get`.
|
|
314
|
+
*
|
|
315
|
+
* This used to call `read()` (fetching the whole object) and then
|
|
316
|
+
* `getFileRecord()` (unauthorized, and now private). It calls the authorized
|
|
317
|
+
* `stat()` instead, which means a metadata lookup no longer moves the bytes
|
|
318
|
+
* and -- since the cap counts bytes leaving (P6) -- no longer spends a
|
|
319
|
+
* download from a capped grant.
|
|
320
|
+
*/
|
|
321
|
+
async stat(fileId, opts = {}) {
|
|
322
|
+
return this.fl.stat(await this.principal(opts), fileId);
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* External user id -> Principal.
|
|
326
|
+
*
|
|
327
|
+
* FAILS CLOSED. An `as:` we cannot resolve is 404, never a silent demotion to
|
|
328
|
+
* an anonymous principal -- which would turn a typo in a user id into a read
|
|
329
|
+
* of every public file, and, worse, would make the audit log attribute it to
|
|
330
|
+
* nobody.
|
|
331
|
+
*/
|
|
332
|
+
async principal(opts, cfg = {}) {
|
|
333
|
+
if (opts.as === undefined) {
|
|
334
|
+
if (!cfg.orSystem)
|
|
335
|
+
return { actorId: null };
|
|
336
|
+
// publish/unpublish with no `as` is a server-side administrative action in
|
|
337
|
+
// the default workspace, performed by the service identity that owns it.
|
|
338
|
+
return { actorId: await this.ids.actor(SYSTEM_ACTOR) };
|
|
339
|
+
}
|
|
340
|
+
const actorId = await this.ids.findActor(opts.as);
|
|
341
|
+
if (!actorId)
|
|
342
|
+
throw new FilelayerError(404, 'not_found', 'unknown_actor');
|
|
343
|
+
return { actorId };
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
// -----------------------------------------------------------------------------
|
|
347
|
+
// orgs.* -- tier 3
|
|
348
|
+
// -----------------------------------------------------------------------------
|
|
349
|
+
export class OrgsApi {
|
|
350
|
+
fl;
|
|
351
|
+
ids;
|
|
352
|
+
constructor(fl) {
|
|
353
|
+
this.fl = fl;
|
|
354
|
+
this.ids = new Identities(fl);
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Create a tenant with its first owner. Idempotent.
|
|
358
|
+
*
|
|
359
|
+
* The owner is created WITH the org for the same reason `createOrg` does it:
|
|
360
|
+
* there is never a memberless org for someone to walk into.
|
|
361
|
+
*/
|
|
362
|
+
async create(externalId, opts) {
|
|
363
|
+
const ownerActorId = await this.ids.actor(opts.owner);
|
|
364
|
+
const id = await this.ids.org(externalId, {
|
|
365
|
+
...(opts.name ? { name: opts.name } : {}),
|
|
366
|
+
ownerActorId,
|
|
367
|
+
});
|
|
368
|
+
return { id };
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Add or change a member's role.
|
|
372
|
+
*
|
|
373
|
+
* `as` is REQUIRED and is not defaulted. Membership is the privilege that
|
|
374
|
+
* confers every other privilege; there is no convenience worth an unauthorized
|
|
375
|
+
* path to it. This is the one place where the tiered API is deliberately no
|
|
376
|
+
* shorter than the full API.
|
|
377
|
+
*/
|
|
378
|
+
async setRole(org, user, role, opts) {
|
|
379
|
+
const { orgId, actorId, principal } = await this.trio(org, user, opts.as);
|
|
380
|
+
await this.fl.addMember(principal, orgId, actorId, role);
|
|
381
|
+
}
|
|
382
|
+
async removeMember(org, user, opts) {
|
|
383
|
+
const { orgId, actorId, principal } = await this.trio(org, user, opts.as);
|
|
384
|
+
await this.fl.removeMember(principal, orgId, actorId);
|
|
385
|
+
}
|
|
386
|
+
/** Audit trail for a tenant. Requires `read_audit`, i.e. admin or owner. */
|
|
387
|
+
async audit(org, opts) {
|
|
388
|
+
const orgId = await this.requireOrg(org);
|
|
389
|
+
const principal = await this.requirePrincipal(opts.as);
|
|
390
|
+
return this.fl.auditLog(principal, orgId, {
|
|
391
|
+
...(opts.decision ? { decision: opts.decision } : {}),
|
|
392
|
+
...(opts.limit ? { limit: opts.limit } : {}),
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
async verifyAudit(org, opts) {
|
|
396
|
+
const orgId = await this.requireOrg(org);
|
|
397
|
+
return this.fl.verifyAuditChain(await this.requirePrincipal(opts.as), orgId);
|
|
398
|
+
}
|
|
399
|
+
async trio(org, user, as) {
|
|
400
|
+
const orgId = await this.requireOrg(org);
|
|
401
|
+
const actorId = await this.ids.actor(user); // the TARGET may be new
|
|
402
|
+
const principal = await this.requirePrincipal(as); // the CALLER may not
|
|
403
|
+
return { orgId, actorId, principal };
|
|
404
|
+
}
|
|
405
|
+
async requireOrg(externalId) {
|
|
406
|
+
const { rows } = await this.fl.store.db.query(`SELECT o.id FROM org o
|
|
407
|
+
JOIN project p ON p.id = o.project_id
|
|
408
|
+
WHERE o.external_id = $1
|
|
409
|
+
AND o.project_id = $2
|
|
410
|
+
AND o.deleted_at IS NULL
|
|
411
|
+
AND p.deleted_at IS NULL`, [externalId, this.fl.projectId ?? DEFAULT_PROJECT_ID]);
|
|
412
|
+
if (!rows[0])
|
|
413
|
+
throw new FilelayerError(404, 'not_found', 'unknown_org');
|
|
414
|
+
return rows[0].id;
|
|
415
|
+
}
|
|
416
|
+
async requirePrincipal(as) {
|
|
417
|
+
const actorId = await this.ids.findActor(as);
|
|
418
|
+
if (!actorId)
|
|
419
|
+
throw new FilelayerError(404, 'not_found', 'unknown_actor');
|
|
420
|
+
return { actorId };
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
export class SharesApi {
|
|
424
|
+
fl;
|
|
425
|
+
ids;
|
|
426
|
+
constructor(fl) {
|
|
427
|
+
this.fl = fl;
|
|
428
|
+
this.ids = new Identities(fl);
|
|
429
|
+
}
|
|
430
|
+
async create(fileId, opts) {
|
|
431
|
+
const actorId = await this.ids.findActor(opts.as);
|
|
432
|
+
if (!actorId)
|
|
433
|
+
throw new FilelayerError(404, 'not_found', 'unknown_actor');
|
|
434
|
+
if (opts.withUser && opts.withOrg) {
|
|
435
|
+
throw new FilelayerError(400, 'ambiguous_subject', 'withUser_and_withOrg');
|
|
436
|
+
}
|
|
437
|
+
if (opts.minRole && !opts.withOrg) {
|
|
438
|
+
throw new FilelayerError(400, 'ambiguous_subject', 'minRole_without_withOrg');
|
|
439
|
+
}
|
|
440
|
+
const subject = opts.withUser
|
|
441
|
+
? { type: 'actor', actorId: await this.ids.actor(opts.withUser) }
|
|
442
|
+
: opts.withOrg
|
|
443
|
+
? // The subject org must ALREADY exist. `withOrg` is not a
|
|
444
|
+
// get-or-create: auto-provisioning a tenant here would mean a typo in
|
|
445
|
+
// an org name silently mints a grant to an empty organization that
|
|
446
|
+
// anybody could later be added to, which is a permission granted to a
|
|
447
|
+
// population nobody has audited. Unknown org is 404, like `as:`.
|
|
448
|
+
opts.minRole
|
|
449
|
+
? { type: 'role', orgId: await this.requireOrgId(opts.withOrg), minRole: opts.minRole }
|
|
450
|
+
: { type: 'org', orgId: await this.requireOrgId(opts.withOrg) }
|
|
451
|
+
: { type: 'link' };
|
|
452
|
+
return this.fl.share({ actorId }, fileId, {
|
|
453
|
+
subject,
|
|
454
|
+
...(opts.capabilities ? { capabilities: opts.capabilities } : {}),
|
|
455
|
+
...(opts.expiresIn !== undefined ? { expiresIn: opts.expiresIn } : {}),
|
|
456
|
+
...(opts.maxDownloads !== undefined ? { maxDownloads: opts.maxDownloads } : {}),
|
|
457
|
+
...(opts.password !== undefined ? { password: opts.password } : {}),
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
async revoke(grantId, opts) {
|
|
461
|
+
const actorId = await this.ids.findActor(opts.as);
|
|
462
|
+
if (!actorId)
|
|
463
|
+
throw new FilelayerError(404, 'not_found', 'unknown_actor');
|
|
464
|
+
return this.fl.revoke({ actorId }, grantId);
|
|
465
|
+
}
|
|
466
|
+
async list(fileId, opts) {
|
|
467
|
+
const actorId = await this.ids.findActor(opts.as);
|
|
468
|
+
if (!actorId)
|
|
469
|
+
throw new FilelayerError(404, 'not_found', 'unknown_actor');
|
|
470
|
+
return this.fl.listGrants({ actorId }, fileId);
|
|
471
|
+
}
|
|
472
|
+
/** Redeem a share link. No identity required -- the secret is the credential. */
|
|
473
|
+
async redeem(secret, opts = {}) {
|
|
474
|
+
return this.fl.redeem(secret, opts);
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* External org id -> internal id, within this project. Read-only and
|
|
478
|
+
* fail-closed, exactly like `Identities.findActor`.
|
|
479
|
+
*/
|
|
480
|
+
async requireOrgId(externalId) {
|
|
481
|
+
const { rows } = await this.fl.store.db.query(`SELECT o.id FROM org o
|
|
482
|
+
JOIN project p ON p.id = o.project_id
|
|
483
|
+
WHERE o.external_id = $1
|
|
484
|
+
AND o.project_id = $2
|
|
485
|
+
AND o.deleted_at IS NULL
|
|
486
|
+
AND p.deleted_at IS NULL`, [externalId, this.fl.projectId ?? DEFAULT_PROJECT_ID]);
|
|
487
|
+
if (!rows[0])
|
|
488
|
+
throw new FilelayerError(404, 'not_found', 'unknown_org');
|
|
489
|
+
return rows[0].id;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
//# sourceMappingURL=simple.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"simple.js","sourceRoot":"","sources":["../src/simple.ts"],"names":[],"mappings":"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"}
|