@proteinjs/db-file 1.7.3 → 1.7.5
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 +22 -0
- package/dist/generated/index.js +1 -1
- package/dist/generated/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/src/FileReachabilityResolver.d.ts +27 -0
- package/dist/src/FileReachabilityResolver.d.ts.map +1 -0
- package/dist/src/FileReachabilityResolver.js +9 -0
- package/dist/src/FileReachabilityResolver.js.map +1 -0
- package/dist/src/FileStorage.d.ts +28 -3
- package/dist/src/FileStorage.d.ts.map +1 -1
- package/dist/src/FileStorage.js +72 -11
- package/dist/src/FileStorage.js.map +1 -1
- package/dist/src/routes/getFile.d.ts +4 -1
- package/dist/src/routes/getFile.d.ts.map +1 -1
- package/dist/src/routes/getFile.js +4 -1
- package/dist/src/routes/getFile.js.map +1 -1
- package/dist/test/FileByteAccess.test.d.ts +2 -0
- package/dist/test/FileByteAccess.test.d.ts.map +1 -0
- package/dist/test/FileByteAccess.test.js +252 -0
- package/dist/test/FileByteAccess.test.js.map +1 -0
- package/dist/test/FileSharedReachability.test.d.ts +2 -0
- package/dist/test/FileSharedReachability.test.d.ts.map +1 -0
- package/dist/test/FileSharedReachability.test.js +358 -0
- package/dist/test/FileSharedReachability.test.js.map +1 -0
- package/dist/test/FileTestEnvironment.d.ts.map +1 -1
- package/dist/test/FileTestEnvironment.js +4 -0
- package/dist/test/FileTestEnvironment.js.map +1 -1
- package/generated/index.ts +1 -1
- package/index.ts +1 -0
- package/package.json +2 -2
- package/src/FileReachabilityResolver.ts +29 -0
- package/src/FileStorage.ts +53 -4
- package/src/routes/getFile.ts +5 -2
- package/test/FileByteAccess.test.ts +143 -0
- package/test/FileSharedReachability.test.ts +229 -0
- package/test/FileTestEnvironment.ts +4 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
12
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
13
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
14
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
|
+
function step(op) {
|
|
16
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
18
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
19
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
|
+
switch (op[0]) {
|
|
21
|
+
case 0: case 1: t = op; break;
|
|
22
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
23
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
24
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
25
|
+
default:
|
|
26
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
27
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
28
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
29
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
30
|
+
if (t[2]) _.ops.pop();
|
|
31
|
+
_.trys.pop(); continue;
|
|
32
|
+
}
|
|
33
|
+
op = body.call(thisArg, _);
|
|
34
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
35
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
var reflection_1 = require("@proteinjs/reflection");
|
|
40
|
+
var FileStorage_1 = require("../src/FileStorage");
|
|
41
|
+
var DbFileStorageDriver_1 = require("../src/DbFileStorageDriver");
|
|
42
|
+
var FileTestEnvironment_1 = require("./FileTestEnvironment");
|
|
43
|
+
/** Every byte value once — a payload that corrupts under any text/base64 mix-up. */
|
|
44
|
+
var rawBytes = Buffer.from(Array.from({ length: 256 }, function (_, i) { return i; }));
|
|
45
|
+
var rawBase64 = rawBytes.toString('base64');
|
|
46
|
+
/** A same-length replacement payload, byte-distinct at every position — the poisoning attempt. */
|
|
47
|
+
var poisonBytes = Buffer.from(Array.from({ length: 256 }, function (_, i) { return 255 - i; }));
|
|
48
|
+
var poisonBase64 = poisonBytes.toString('base64');
|
|
49
|
+
/**
|
|
50
|
+
* The byte-access gates on the browser-facing service (FileStorageService is `allUsers`, so
|
|
51
|
+
* these methods are every logged-in user's to call with any file id they can guess or leak):
|
|
52
|
+
*
|
|
53
|
+
* - **Reads** (`getFileData`): gated on the {@link FileStorage.getFile} row read — the same
|
|
54
|
+
* two-legged access decision every serving path derives from (the caller's SCOPED read, else
|
|
55
|
+
* the FileReachabilityResolver shared-content leg). Bytes are exactly as reachable as the row
|
|
56
|
+
* that names them. Ungated, a foreign file id read another user's bytes outright (the driver
|
|
57
|
+
* byte ops are deliberately unscoped — the access wall lives at this service boundary).
|
|
58
|
+
* - **Writes** (`updateFileData`): gated on the caller's SCOPED row read — owner scope is the
|
|
59
|
+
* write grant on ScopedRecords. Stricter than reads: the resolver leg widens READS only, so a
|
|
60
|
+
* share recipient who can read bytes still cannot write them. Ungated, a foreign file id let
|
|
61
|
+
* any caller poison another user's bytes (with the DbFileStorageDriver: caller-scoped chunk
|
|
62
|
+
* inserts landing beside the owner's — this suite runs that real driver so the intact-bytes
|
|
63
|
+
* assertions prove no chunk ever landed).
|
|
64
|
+
*
|
|
65
|
+
* The resolver is stubbed at the seam (content packages sit above db-file); denial is the named
|
|
66
|
+
* `File not found` — existence is not leaked to unauthorized callers.
|
|
67
|
+
*/
|
|
68
|
+
var testEnv = new FileTestEnvironment_1.FileTestEnvironment();
|
|
69
|
+
var objectCache = function () { return reflection_1.SourceRepository.get().objectCache; };
|
|
70
|
+
var owner;
|
|
71
|
+
var recipient;
|
|
72
|
+
var stranger;
|
|
73
|
+
/** File ids the stub resolver reports as reachable for the current user. */
|
|
74
|
+
var reachableFileIds = new Set();
|
|
75
|
+
/** How many times the seam was consulted — the owner's scoped leg must never pay for it. */
|
|
76
|
+
var resolverConsultations = 0;
|
|
77
|
+
beforeAll(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
78
|
+
return __generator(this, function (_a) {
|
|
79
|
+
switch (_a.label) {
|
|
80
|
+
case 0: return [4 /*yield*/, testEnv.beforeAll()];
|
|
81
|
+
case 1:
|
|
82
|
+
_a.sent();
|
|
83
|
+
// The seam getFile consults on a scoped-read miss, stubbed (the real thought-media resolver
|
|
84
|
+
// lives above this package).
|
|
85
|
+
objectCache()['@proteinjs/db-file/FileReachabilityResolver'] = [
|
|
86
|
+
{
|
|
87
|
+
canReadViaReference: function (fileId) { return __awaiter(void 0, void 0, void 0, function () {
|
|
88
|
+
return __generator(this, function (_a) {
|
|
89
|
+
resolverConsultations++;
|
|
90
|
+
return [2 /*return*/, reachableFileIds.has(fileId)];
|
|
91
|
+
});
|
|
92
|
+
}); },
|
|
93
|
+
},
|
|
94
|
+
];
|
|
95
|
+
// The real chunked driver: write-gate bites are proven at the chunk-row level, not a mock.
|
|
96
|
+
testEnv.setDriver(new DbFileStorageDriver_1.DbFileStorageDriver());
|
|
97
|
+
return [4 /*yield*/, testEnv.createUser({ name: 'File owner', email: 'byte-owner@test.local' })];
|
|
98
|
+
case 2:
|
|
99
|
+
owner = _a.sent();
|
|
100
|
+
return [4 /*yield*/, testEnv.createUser({ name: 'Share recipient', email: 'byte-recipient@test.local' })];
|
|
101
|
+
case 3:
|
|
102
|
+
recipient = _a.sent();
|
|
103
|
+
return [4 /*yield*/, testEnv.createUser({ name: 'Stranger', email: 'byte-stranger@test.local' })];
|
|
104
|
+
case 4:
|
|
105
|
+
stranger = _a.sent();
|
|
106
|
+
testEnv.actAs(owner);
|
|
107
|
+
return [2 /*return*/];
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}); });
|
|
111
|
+
afterAll(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
112
|
+
return __generator(this, function (_a) {
|
|
113
|
+
switch (_a.label) {
|
|
114
|
+
case 0:
|
|
115
|
+
delete objectCache()['@proteinjs/db-file/FileReachabilityResolver'];
|
|
116
|
+
return [4 /*yield*/, testEnv.afterAll()];
|
|
117
|
+
case 1:
|
|
118
|
+
_a.sent();
|
|
119
|
+
return [2 /*return*/];
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}); });
|
|
123
|
+
beforeEach(function () {
|
|
124
|
+
reachableFileIds.clear();
|
|
125
|
+
resolverConsultations = 0;
|
|
126
|
+
testEnv.actAs(owner);
|
|
127
|
+
});
|
|
128
|
+
var createOwnerFile = function (name) { return __awaiter(void 0, void 0, void 0, function () {
|
|
129
|
+
return __generator(this, function (_a) {
|
|
130
|
+
switch (_a.label) {
|
|
131
|
+
case 0: return [4 /*yield*/, new FileStorage_1.FileStorage().createFile({ name: name, type: 'application/octet-stream', size: rawBytes.length }, rawBase64)];
|
|
132
|
+
case 1: return [2 /*return*/, _a.sent()];
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}); };
|
|
136
|
+
describe('byte reads gate on the file-row read (the getFile decision)', function () {
|
|
137
|
+
it('the owner reads their bytes through the scoped leg alone — resolvers are never consulted', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
138
|
+
var file, fileDataBase64;
|
|
139
|
+
return __generator(this, function (_a) {
|
|
140
|
+
switch (_a.label) {
|
|
141
|
+
case 0: return [4 /*yield*/, createOwnerFile('own-bytes.bin')];
|
|
142
|
+
case 1:
|
|
143
|
+
file = _a.sent();
|
|
144
|
+
return [4 /*yield*/, new FileStorage_1.FileStorage().getFileData(file.id)];
|
|
145
|
+
case 2:
|
|
146
|
+
fileDataBase64 = _a.sent();
|
|
147
|
+
expect(fileDataBase64).toEqual(rawBase64);
|
|
148
|
+
expect(resolverConsultations).toEqual(0);
|
|
149
|
+
return [2 /*return*/];
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}); });
|
|
153
|
+
it("THE VULN (read): a stranger holding only the file id is DENIED another user's bytes", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
154
|
+
var file;
|
|
155
|
+
return __generator(this, function (_a) {
|
|
156
|
+
switch (_a.label) {
|
|
157
|
+
case 0: return [4 /*yield*/, createOwnerFile('private-bytes.bin')];
|
|
158
|
+
case 1:
|
|
159
|
+
file = _a.sent();
|
|
160
|
+
testEnv.actAs(stranger);
|
|
161
|
+
return [4 /*yield*/, expect(new FileStorage_1.FileStorage().getFileData(file.id)).rejects.toThrow("File not found: ".concat(file.id))];
|
|
162
|
+
case 2:
|
|
163
|
+
_a.sent();
|
|
164
|
+
return [2 /*return*/];
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}); });
|
|
168
|
+
it('a shared-content recipient (resolver-vouched) reads the bytes byte-identical', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
169
|
+
var file, fileDataBase64;
|
|
170
|
+
return __generator(this, function (_a) {
|
|
171
|
+
switch (_a.label) {
|
|
172
|
+
case 0: return [4 /*yield*/, createOwnerFile('shared-bytes.bin')];
|
|
173
|
+
case 1:
|
|
174
|
+
file = _a.sent();
|
|
175
|
+
reachableFileIds.add(file.id);
|
|
176
|
+
testEnv.actAs(recipient);
|
|
177
|
+
return [4 /*yield*/, new FileStorage_1.FileStorage().getFileData(file.id)];
|
|
178
|
+
case 2:
|
|
179
|
+
fileDataBase64 = _a.sent();
|
|
180
|
+
expect(fileDataBase64).toEqual(rawBase64);
|
|
181
|
+
return [2 /*return*/];
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
}); });
|
|
185
|
+
});
|
|
186
|
+
describe('byte writes gate on the caller WRITING the row (owner scope; read-reachability never grants it)', function () {
|
|
187
|
+
it("THE VULN (write): a stranger holding only the file id cannot poison another user's bytes", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
188
|
+
var file, _a;
|
|
189
|
+
return __generator(this, function (_b) {
|
|
190
|
+
switch (_b.label) {
|
|
191
|
+
case 0: return [4 /*yield*/, createOwnerFile('unpoisonable.bin')];
|
|
192
|
+
case 1:
|
|
193
|
+
file = _b.sent();
|
|
194
|
+
testEnv.actAs(stranger);
|
|
195
|
+
return [4 /*yield*/, expect(new FileStorage_1.FileStorage().updateFileData(file.id, poisonBase64)).rejects.toThrow("File not found: ".concat(file.id))];
|
|
196
|
+
case 2:
|
|
197
|
+
_b.sent();
|
|
198
|
+
// The outcome that matters: no chunk landed — the owner's bytes read back untouched.
|
|
199
|
+
testEnv.actAs(owner);
|
|
200
|
+
_a = expect;
|
|
201
|
+
return [4 /*yield*/, new FileStorage_1.FileStorage().getFileData(file.id)];
|
|
202
|
+
case 3:
|
|
203
|
+
_a.apply(void 0, [_b.sent()]).toEqual(rawBase64);
|
|
204
|
+
return [2 /*return*/];
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
}); });
|
|
208
|
+
it("read-reachability does NOT grant write: a vouched recipient's byte write is denied, bytes intact", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
209
|
+
var file, _a, _b;
|
|
210
|
+
return __generator(this, function (_c) {
|
|
211
|
+
switch (_c.label) {
|
|
212
|
+
case 0: return [4 /*yield*/, createOwnerFile('read-only-share.bin')];
|
|
213
|
+
case 1:
|
|
214
|
+
file = _c.sent();
|
|
215
|
+
reachableFileIds.add(file.id);
|
|
216
|
+
testEnv.actAs(recipient);
|
|
217
|
+
_a = expect;
|
|
218
|
+
return [4 /*yield*/, new FileStorage_1.FileStorage().getFileData(file.id)];
|
|
219
|
+
case 2:
|
|
220
|
+
_a.apply(void 0, [_c.sent()]).toEqual(rawBase64);
|
|
221
|
+
return [4 /*yield*/, expect(new FileStorage_1.FileStorage().updateFileData(file.id, poisonBase64)).rejects.toThrow("File not found: ".concat(file.id))];
|
|
222
|
+
case 3:
|
|
223
|
+
_c.sent();
|
|
224
|
+
testEnv.actAs(owner);
|
|
225
|
+
_b = expect;
|
|
226
|
+
return [4 /*yield*/, new FileStorage_1.FileStorage().getFileData(file.id)];
|
|
227
|
+
case 4:
|
|
228
|
+
_b.apply(void 0, [_c.sent()]).toEqual(rawBase64);
|
|
229
|
+
return [2 /*return*/];
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
}); });
|
|
233
|
+
it('the owner replaces their bytes wholesale', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
234
|
+
var file, _a;
|
|
235
|
+
return __generator(this, function (_b) {
|
|
236
|
+
switch (_b.label) {
|
|
237
|
+
case 0: return [4 /*yield*/, createOwnerFile('rewritable.bin')];
|
|
238
|
+
case 1:
|
|
239
|
+
file = _b.sent();
|
|
240
|
+
return [4 /*yield*/, new FileStorage_1.FileStorage().updateFileData(file.id, poisonBase64)];
|
|
241
|
+
case 2:
|
|
242
|
+
_b.sent();
|
|
243
|
+
_a = expect;
|
|
244
|
+
return [4 /*yield*/, new FileStorage_1.FileStorage().getFileData(file.id)];
|
|
245
|
+
case 3:
|
|
246
|
+
_a.apply(void 0, [_b.sent()]).toEqual(poisonBase64);
|
|
247
|
+
return [2 /*return*/];
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
}); });
|
|
251
|
+
});
|
|
252
|
+
//# sourceMappingURL=FileByteAccess.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FileByteAccess.test.js","sourceRoot":"","sources":["../../test/FileByteAccess.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FileSharedReachability.test.d.ts","sourceRoot":"","sources":["../../test/FileSharedReachability.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __extends = (this && this.__extends) || (function () {
|
|
3
|
+
var extendStatics = function (d, b) {
|
|
4
|
+
extendStatics = Object.setPrototypeOf ||
|
|
5
|
+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
6
|
+
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
|
7
|
+
return extendStatics(d, b);
|
|
8
|
+
};
|
|
9
|
+
return function (d, b) {
|
|
10
|
+
if (typeof b !== "function" && b !== null)
|
|
11
|
+
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
|
12
|
+
extendStatics(d, b);
|
|
13
|
+
function __() { this.constructor = d; }
|
|
14
|
+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
15
|
+
};
|
|
16
|
+
})();
|
|
17
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
18
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
19
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
20
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
21
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
22
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
23
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
27
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
28
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
29
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
30
|
+
function step(op) {
|
|
31
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
32
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
33
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
34
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
35
|
+
switch (op[0]) {
|
|
36
|
+
case 0: case 1: t = op; break;
|
|
37
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
38
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
39
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
40
|
+
default:
|
|
41
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
42
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
43
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
44
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
45
|
+
if (t[2]) _.ops.pop();
|
|
46
|
+
_.trys.pop(); continue;
|
|
47
|
+
}
|
|
48
|
+
op = body.call(thisArg, _);
|
|
49
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
50
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
54
|
+
var reflection_1 = require("@proteinjs/reflection");
|
|
55
|
+
var user_1 = require("@proteinjs/user");
|
|
56
|
+
var FileStorage_1 = require("../src/FileStorage");
|
|
57
|
+
var getFile_1 = require("../src/routes/getFile");
|
|
58
|
+
var FileTestEnvironment_1 = require("./FileTestEnvironment");
|
|
59
|
+
/** Every byte value once — a payload that corrupts under any text/base64 mix-up. */
|
|
60
|
+
var rawBytes = Buffer.from(Array.from({ length: 256 }, function (_, i) { return i; }));
|
|
61
|
+
/**
|
|
62
|
+
* The shared-content file leg (SHARING_EXPANSION §0 found defect / §1 fix shape): `/file/:id`
|
|
63
|
+
* used the caller's SCOPED row read as its whole access check, so a file embedded in content
|
|
64
|
+
* shared WITH the caller — a shared thought's media — 404'd for every share recipient. The fix
|
|
65
|
+
* is the FileReachabilityResolver seam: after the scoped read misses, content packages answer
|
|
66
|
+
* "can the current user read a row that references this file?" through their own grant-filtered
|
|
67
|
+
* reads, and only then is the row served via system read.
|
|
68
|
+
*
|
|
69
|
+
* This suite proves the SEAM CONTRACT at the route with a stub resolver (content packages sit
|
|
70
|
+
* above db-file — the real thought-media resolver is integration-tested in thought-server):
|
|
71
|
+
* a reachable file serves byte-identical to a non-owner, an unreachable one still 404s (the fix
|
|
72
|
+
* must not over-open), the owner's scoped leg never consults resolvers, and writes stay
|
|
73
|
+
* owner-scoped.
|
|
74
|
+
*/
|
|
75
|
+
/** In-memory proxy-shape driver (no signed URLs) — the DbFileStorageDriver serving shape. */
|
|
76
|
+
var ProxyDriver = /** @class */ (function () {
|
|
77
|
+
function ProxyDriver() {
|
|
78
|
+
this.store = new Map();
|
|
79
|
+
}
|
|
80
|
+
ProxyDriver.prototype.createFile = function (file, fileData) {
|
|
81
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
82
|
+
return __generator(this, function (_a) {
|
|
83
|
+
this.store.set(file.id, Buffer.from(fileData, 'base64'));
|
|
84
|
+
return [2 /*return*/];
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
ProxyDriver.prototype.getFileData = function (fileId) {
|
|
89
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
90
|
+
var data;
|
|
91
|
+
return __generator(this, function (_a) {
|
|
92
|
+
data = this.store.get(fileId);
|
|
93
|
+
if (data === undefined) {
|
|
94
|
+
throw new Error("No such object: ".concat(fileId));
|
|
95
|
+
}
|
|
96
|
+
return [2 /*return*/, data.toString('base64')];
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
ProxyDriver.prototype.updateFileData = function (fileId, data) {
|
|
101
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
102
|
+
return __generator(this, function (_a) {
|
|
103
|
+
this.store.set(fileId, Buffer.from(data, 'base64'));
|
|
104
|
+
return [2 /*return*/];
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
ProxyDriver.prototype.deleteFile = function (fileId) {
|
|
109
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
110
|
+
return __generator(this, function (_a) {
|
|
111
|
+
this.store.delete(fileId);
|
|
112
|
+
return [2 /*return*/];
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
return ProxyDriver;
|
|
117
|
+
}());
|
|
118
|
+
/** Proxy driver plus a counting signed-URL mint — the GCS serving shape, minting observable. */
|
|
119
|
+
var SignedUrlDriver = /** @class */ (function (_super) {
|
|
120
|
+
__extends(SignedUrlDriver, _super);
|
|
121
|
+
function SignedUrlDriver() {
|
|
122
|
+
var _this = _super !== null && _super.apply(this, arguments) || this;
|
|
123
|
+
_this.mintCount = 0;
|
|
124
|
+
return _this;
|
|
125
|
+
}
|
|
126
|
+
SignedUrlDriver.prototype.getSignedUrl = function (fileId) {
|
|
127
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
128
|
+
return __generator(this, function (_a) {
|
|
129
|
+
this.mintCount++;
|
|
130
|
+
return [2 /*return*/, "https://signed.test/blob/".concat(fileId, "?sig=test")];
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
return SignedUrlDriver;
|
|
135
|
+
}(ProxyDriver));
|
|
136
|
+
/** Minimal express Response recorder — the route only touches these members. */
|
|
137
|
+
var ResponseRecorder = /** @class */ (function () {
|
|
138
|
+
function ResponseRecorder() {
|
|
139
|
+
this.headers = {};
|
|
140
|
+
}
|
|
141
|
+
ResponseRecorder.prototype.status = function (code) {
|
|
142
|
+
this.statusCode = code;
|
|
143
|
+
return this;
|
|
144
|
+
};
|
|
145
|
+
ResponseRecorder.prototype.send = function (body) {
|
|
146
|
+
this.body = body;
|
|
147
|
+
return this;
|
|
148
|
+
};
|
|
149
|
+
ResponseRecorder.prototype.setHeader = function (name, value) {
|
|
150
|
+
this.headers[name.toLowerCase()] = value;
|
|
151
|
+
};
|
|
152
|
+
ResponseRecorder.prototype.redirect = function (status, url) {
|
|
153
|
+
this.statusCode = status;
|
|
154
|
+
this.redirectUrl = url;
|
|
155
|
+
};
|
|
156
|
+
return ResponseRecorder;
|
|
157
|
+
}());
|
|
158
|
+
var invokeRoute = function (fileId) { return __awaiter(void 0, void 0, void 0, function () {
|
|
159
|
+
var response;
|
|
160
|
+
return __generator(this, function (_a) {
|
|
161
|
+
switch (_a.label) {
|
|
162
|
+
case 0:
|
|
163
|
+
response = new ResponseRecorder();
|
|
164
|
+
return [4 /*yield*/, getFile_1.getFile.onRequest({ params: { id: fileId }, headers: {} }, response)];
|
|
165
|
+
case 1:
|
|
166
|
+
_a.sent();
|
|
167
|
+
return [2 /*return*/, response];
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}); };
|
|
171
|
+
var testEnv = new FileTestEnvironment_1.FileTestEnvironment();
|
|
172
|
+
var objectCache = function () { return reflection_1.SourceRepository.get().objectCache; };
|
|
173
|
+
var owner;
|
|
174
|
+
var recipient;
|
|
175
|
+
/** File ids the stub resolver reports as reachable for the current user. */
|
|
176
|
+
var reachableFileIds = new Set();
|
|
177
|
+
/** How many times the seam was consulted — the owner's scoped leg must never pay for it. */
|
|
178
|
+
var resolverConsultations = 0;
|
|
179
|
+
beforeAll(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
180
|
+
return __generator(this, function (_a) {
|
|
181
|
+
switch (_a.label) {
|
|
182
|
+
case 0: return [4 /*yield*/, testEnv.beforeAll()];
|
|
183
|
+
case 1:
|
|
184
|
+
_a.sent();
|
|
185
|
+
// The route gates on UserAuth.isLoggedIn(), which resolves its user repo from the source
|
|
186
|
+
// repository — seed it (tests don't load the generated source graph) and reset the static cache.
|
|
187
|
+
objectCache()['@proteinjs/user-auth/AuthenticatedUserRepo'] = [new user_1.UserRepo()];
|
|
188
|
+
user_1.UserAuth.userRepo = undefined;
|
|
189
|
+
// The seam under test, stubbed (the real thought-media resolver lives above this package).
|
|
190
|
+
objectCache()['@proteinjs/db-file/FileReachabilityResolver'] = [
|
|
191
|
+
{
|
|
192
|
+
canReadViaReference: function (fileId) { return __awaiter(void 0, void 0, void 0, function () {
|
|
193
|
+
return __generator(this, function (_a) {
|
|
194
|
+
resolverConsultations++;
|
|
195
|
+
return [2 /*return*/, reachableFileIds.has(fileId)];
|
|
196
|
+
});
|
|
197
|
+
}); },
|
|
198
|
+
},
|
|
199
|
+
];
|
|
200
|
+
return [4 /*yield*/, testEnv.createUser({ name: 'File owner', email: 'reachability-owner@test.local' })];
|
|
201
|
+
case 2:
|
|
202
|
+
owner = _a.sent();
|
|
203
|
+
return [4 /*yield*/, testEnv.createUser({ name: 'Share recipient', email: 'reachability-recipient@test.local' })];
|
|
204
|
+
case 3:
|
|
205
|
+
recipient = _a.sent();
|
|
206
|
+
testEnv.actAs(owner);
|
|
207
|
+
return [2 /*return*/];
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}); });
|
|
211
|
+
afterAll(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
212
|
+
return __generator(this, function (_a) {
|
|
213
|
+
switch (_a.label) {
|
|
214
|
+
case 0:
|
|
215
|
+
delete objectCache()['@proteinjs/db-file/FileReachabilityResolver'];
|
|
216
|
+
user_1.UserAuth.userRepo = undefined;
|
|
217
|
+
return [4 /*yield*/, testEnv.afterAll()];
|
|
218
|
+
case 1:
|
|
219
|
+
_a.sent();
|
|
220
|
+
return [2 /*return*/];
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}); });
|
|
224
|
+
beforeEach(function () {
|
|
225
|
+
reachableFileIds.clear();
|
|
226
|
+
resolverConsultations = 0;
|
|
227
|
+
testEnv.actAs(owner);
|
|
228
|
+
});
|
|
229
|
+
var createOwnerFile = function (name, type) { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
230
|
+
switch (_a.label) {
|
|
231
|
+
case 0: return [4 /*yield*/, new FileStorage_1.FileStorage().createFile({ name: name, type: type, size: rawBytes.length }, rawBytes.toString('base64'))];
|
|
232
|
+
case 1: return [2 /*return*/, _a.sent()];
|
|
233
|
+
}
|
|
234
|
+
}); }); };
|
|
235
|
+
describe('shared-content reachability — proxy serving', function () {
|
|
236
|
+
var driver = new ProxyDriver();
|
|
237
|
+
beforeAll(function () {
|
|
238
|
+
testEnv.setDriver(driver);
|
|
239
|
+
});
|
|
240
|
+
it("THE DEFECT: a non-owner whom a resolver vouches for is served the owner's file byte-identical", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
241
|
+
var file, response;
|
|
242
|
+
return __generator(this, function (_a) {
|
|
243
|
+
switch (_a.label) {
|
|
244
|
+
case 0: return [4 /*yield*/, createOwnerFile('shared-shot.png', 'image/png')];
|
|
245
|
+
case 1:
|
|
246
|
+
file = _a.sent();
|
|
247
|
+
reachableFileIds.add(file.id);
|
|
248
|
+
testEnv.actAs(recipient);
|
|
249
|
+
return [4 /*yield*/, invokeRoute(file.id)];
|
|
250
|
+
case 2:
|
|
251
|
+
response = _a.sent();
|
|
252
|
+
expect(response.statusCode).not.toEqual(404);
|
|
253
|
+
expect(response.headers['content-type']).toEqual('image/png');
|
|
254
|
+
expect(Buffer.isBuffer(response.body)).toBe(true);
|
|
255
|
+
expect(Buffer.compare(response.body, rawBytes)).toBe(0);
|
|
256
|
+
return [2 /*return*/];
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
}); });
|
|
260
|
+
it('THE OTHER DIRECTION: a non-owner with no reachability still 404s — the fix must not over-open', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
261
|
+
var file, response;
|
|
262
|
+
return __generator(this, function (_a) {
|
|
263
|
+
switch (_a.label) {
|
|
264
|
+
case 0: return [4 /*yield*/, createOwnerFile('private-shot.png', 'image/png')];
|
|
265
|
+
case 1:
|
|
266
|
+
file = _a.sent();
|
|
267
|
+
testEnv.actAs(recipient);
|
|
268
|
+
return [4 /*yield*/, invokeRoute(file.id)];
|
|
269
|
+
case 2:
|
|
270
|
+
response = _a.sent();
|
|
271
|
+
expect(response.statusCode).toEqual(404);
|
|
272
|
+
expect(response.body).toEqual('File not found');
|
|
273
|
+
return [2 /*return*/];
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
}); });
|
|
277
|
+
it('the owner is served by their scoped read alone — resolvers are never consulted', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
278
|
+
var file, response;
|
|
279
|
+
return __generator(this, function (_a) {
|
|
280
|
+
switch (_a.label) {
|
|
281
|
+
case 0: return [4 /*yield*/, createOwnerFile('own-shot.png', 'image/png')];
|
|
282
|
+
case 1:
|
|
283
|
+
file = _a.sent();
|
|
284
|
+
return [4 /*yield*/, invokeRoute(file.id)];
|
|
285
|
+
case 2:
|
|
286
|
+
response = _a.sent();
|
|
287
|
+
expect(Buffer.compare(response.body, rawBytes)).toBe(0);
|
|
288
|
+
expect(resolverConsultations).toEqual(0);
|
|
289
|
+
return [2 /*return*/];
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
}); });
|
|
293
|
+
it("reachability widens READS only: a vouched-for non-owner's delete leaves the owner's file intact", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
294
|
+
var file, response;
|
|
295
|
+
return __generator(this, function (_a) {
|
|
296
|
+
switch (_a.label) {
|
|
297
|
+
case 0: return [4 /*yield*/, createOwnerFile('undeletable.png', 'image/png')];
|
|
298
|
+
case 1:
|
|
299
|
+
file = _a.sent();
|
|
300
|
+
reachableFileIds.add(file.id);
|
|
301
|
+
testEnv.actAs(recipient);
|
|
302
|
+
return [4 /*yield*/, new FileStorage_1.FileStorage().deleteFile(file.id)];
|
|
303
|
+
case 2:
|
|
304
|
+
_a.sent();
|
|
305
|
+
testEnv.actAs(owner);
|
|
306
|
+
return [4 /*yield*/, invokeRoute(file.id)];
|
|
307
|
+
case 3:
|
|
308
|
+
response = _a.sent();
|
|
309
|
+
expect(Buffer.compare(response.body, rawBytes)).toBe(0);
|
|
310
|
+
return [2 /*return*/];
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
}); });
|
|
314
|
+
});
|
|
315
|
+
describe('shared-content reachability — signed-URL serving', function () {
|
|
316
|
+
var driver = new SignedUrlDriver();
|
|
317
|
+
beforeAll(function () {
|
|
318
|
+
testEnv.setDriver(driver);
|
|
319
|
+
});
|
|
320
|
+
it('302-redirects a vouched-for non-owner to a signed URL (the GCS prod shape)', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
321
|
+
var file, response;
|
|
322
|
+
return __generator(this, function (_a) {
|
|
323
|
+
switch (_a.label) {
|
|
324
|
+
case 0: return [4 /*yield*/, createOwnerFile('shared-clip.mp4', 'video/mp4')];
|
|
325
|
+
case 1:
|
|
326
|
+
file = _a.sent();
|
|
327
|
+
reachableFileIds.add(file.id);
|
|
328
|
+
testEnv.actAs(recipient);
|
|
329
|
+
return [4 /*yield*/, invokeRoute(file.id)];
|
|
330
|
+
case 2:
|
|
331
|
+
response = _a.sent();
|
|
332
|
+
expect(response.statusCode).toEqual(302);
|
|
333
|
+
expect(response.redirectUrl).toEqual("https://signed.test/blob/".concat(file.id, "?sig=test"));
|
|
334
|
+
return [2 /*return*/];
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
}); });
|
|
338
|
+
it('404s an unreachable non-owner WITHOUT minting a signed URL (a signed URL is a bearer capability)', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
339
|
+
var file, mintsBefore, response;
|
|
340
|
+
return __generator(this, function (_a) {
|
|
341
|
+
switch (_a.label) {
|
|
342
|
+
case 0: return [4 /*yield*/, createOwnerFile('private-clip.mp4', 'video/mp4')];
|
|
343
|
+
case 1:
|
|
344
|
+
file = _a.sent();
|
|
345
|
+
mintsBefore = driver.mintCount;
|
|
346
|
+
testEnv.actAs(recipient);
|
|
347
|
+
return [4 /*yield*/, invokeRoute(file.id)];
|
|
348
|
+
case 2:
|
|
349
|
+
response = _a.sent();
|
|
350
|
+
expect(response.statusCode).toEqual(404);
|
|
351
|
+
expect(response.redirectUrl).toBeUndefined();
|
|
352
|
+
expect(driver.mintCount).toEqual(mintsBefore);
|
|
353
|
+
return [2 /*return*/];
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
}); });
|
|
357
|
+
});
|
|
358
|
+
//# sourceMappingURL=FileSharedReachability.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FileSharedReachability.test.js","sourceRoot":"","sources":["../../test/FileSharedReachability.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileTestEnvironment.d.ts","sourceRoot":"","sources":["../../test/FileTestEnvironment.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAI7D,OAAO,EAAwB,IAAI,EAAY,MAAM,iBAAiB,CAAC;AAGvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAqB7D;;;;;GAKG;AACH,qBAAa,mBAAmB;IAC9B,QAAQ,CAAC,aAAa,gBAInB;IACH,OAAO,CAAC,aAAa,CAAwC;IAC7D,OAAO,CAAC,QAAQ,CAAkB;IAE5B,SAAS;
|
|
1
|
+
{"version":3,"file":"FileTestEnvironment.d.ts","sourceRoot":"","sources":["../../test/FileTestEnvironment.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAI7D,OAAO,EAAwB,IAAI,EAAY,MAAM,iBAAiB,CAAC;AAGvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAqB7D;;;;;GAKG;AACH,qBAAa,mBAAmB;IAC9B,QAAQ,CAAC,aAAa,gBAInB;IACH,OAAO,CAAC,aAAa,CAAwC;IAC7D,OAAO,CAAC,QAAQ,CAAkB;IAE5B,SAAS;IA8BT,QAAQ;IAMd;;;OAGG;IACH,SAAS,CAAC,MAAM,EAAE,iBAAiB;IAKnC,mDAAmD;IAC7C,UAAU,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAUxF,mGAAmG;IACnG,KAAK,CAAC,IAAI,EAAE,IAAI;IAIhB,OAAO,CAAC,WAAW;YAIL,UAAU;YAOV,UAAU;CAKzB"}
|