@antelopejs/interface-database-decorators 0.1.11 → 0.1.12
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/README.md +9 -7
- package/dist/database.js +18 -2
- package/dist/database.js.map +1 -1
- package/dist/model.js +26 -10
- package/dist/model.js.map +1 -1
- package/dist/modifiers/common.js +15 -19
- package/dist/modifiers/common.js.map +1 -1
- package/dist/modifiers/hash.d.ts +46 -4
- package/dist/modifiers/hash.js +172 -14
- package/dist/modifiers/hash.js.map +1 -1
- package/dist/modifiers/internal/kinds.d.ts +1 -0
- package/dist/modifiers/internal/kinds.js +4 -0
- package/dist/modifiers/internal/kinds.js.map +1 -1
- package/dist/modifiers/internal/records.d.ts +3 -1
- package/dist/modifiers/internal/records.js +14 -0
- package/dist/modifiers/internal/records.js.map +1 -1
- package/dist/modifiers/localization.js +3 -5
- package/dist/modifiers/localization.js.map +1 -1
- package/dist/table.js +2 -1
- package/dist/table.js.map +1 -1
- package/dist/tests/database.test.js +32 -0
- package/dist/tests/database.test.js.map +1 -1
- package/dist/tests/hash_upgrade.test.d.ts +1 -0
- package/dist/tests/hash_upgrade.test.js +132 -0
- package/dist/tests/hash_upgrade.test.js.map +1 -0
- package/dist/tests/helpers/hash-rows.d.ts +40 -0
- package/dist/tests/helpers/hash-rows.js +41 -0
- package/dist/tests/helpers/hash-rows.js.map +1 -0
- package/dist/tests/modifiers/hash.test.js +295 -0
- package/dist/tests/modifiers/hash.test.js.map +1 -1
- package/dist/tests/partial_update.test.d.ts +1 -0
- package/dist/tests/partial_update.test.js +55 -0
- package/dist/tests/partial_update.test.js.map +1 -0
- package/docs/3.table-modifiers.md +36 -15
- package/package.json +2 -2
- package/skills/database-decorators-interface/SKILL.md +4 -2
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export declare const PASSWORD = "correct horse battery staple";
|
|
2
|
+
/**
|
|
3
|
+
* `describe` for tests that spend several scrypt runs. One run takes hundreds of
|
|
4
|
+
* milliseconds on a busy machine, which passes mocha's 2 s limit in a few runs.
|
|
5
|
+
*/
|
|
6
|
+
export declare function scryptSuite(title: string, body: () => void): void;
|
|
7
|
+
/** A row as `@Hashed()` stored it in the published 0.1.11: a bare salted SHA-256. */
|
|
8
|
+
export declare const ROW_FROM_0_1_11: {
|
|
9
|
+
_id: string;
|
|
10
|
+
email: string;
|
|
11
|
+
name: string;
|
|
12
|
+
_internal: {
|
|
13
|
+
meta: {
|
|
14
|
+
HashModifier: {
|
|
15
|
+
salt: string;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
data: {
|
|
19
|
+
password: string;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* A row as the published 0.1.11 stored `@Hashed({ algorithm: "sha256" })` on
|
|
25
|
+
* `password` and `@Hashed({ algorithm: "md5" })` on `checksum`, set to `{ a: 1 }`.
|
|
26
|
+
*/
|
|
27
|
+
export declare const FINGERPRINT_ROW_FROM_0_1_11: {
|
|
28
|
+
_id: string;
|
|
29
|
+
_internal: {
|
|
30
|
+
meta: {
|
|
31
|
+
HashModifier: {
|
|
32
|
+
salt: string;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
data: {
|
|
36
|
+
password: string;
|
|
37
|
+
checksum: string;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FINGERPRINT_ROW_FROM_0_1_11 = exports.ROW_FROM_0_1_11 = exports.PASSWORD = void 0;
|
|
4
|
+
exports.scryptSuite = scryptSuite;
|
|
5
|
+
exports.PASSWORD = "correct horse battery staple";
|
|
6
|
+
const SCRYPT_SUITE_TIMEOUT_MS = 30_000;
|
|
7
|
+
/**
|
|
8
|
+
* `describe` for tests that spend several scrypt runs. One run takes hundreds of
|
|
9
|
+
* milliseconds on a busy machine, which passes mocha's 2 s limit in a few runs.
|
|
10
|
+
*/
|
|
11
|
+
function scryptSuite(title, body) {
|
|
12
|
+
describe(title, function () {
|
|
13
|
+
this.timeout(SCRYPT_SUITE_TIMEOUT_MS);
|
|
14
|
+
body();
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
/** A row as `@Hashed()` stored it in the published 0.1.11: a bare salted SHA-256. */
|
|
18
|
+
exports.ROW_FROM_0_1_11 = {
|
|
19
|
+
_id: "row-from-0.1.11",
|
|
20
|
+
email: "legacy@example.test",
|
|
21
|
+
name: "Legacy",
|
|
22
|
+
_internal: {
|
|
23
|
+
meta: { HashModifier: { salt: "BMNyCHwpLNt2tkqkyB5rwOj1q2wfDIo6" } },
|
|
24
|
+
data: { password: "nPamXXaKC8N+8W7xqxTSo8r+pS9jXMkjJu2XMZtUR+A=" },
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* A row as the published 0.1.11 stored `@Hashed({ algorithm: "sha256" })` on
|
|
29
|
+
* `password` and `@Hashed({ algorithm: "md5" })` on `checksum`, set to `{ a: 1 }`.
|
|
30
|
+
*/
|
|
31
|
+
exports.FINGERPRINT_ROW_FROM_0_1_11 = {
|
|
32
|
+
_id: "fingerprint-from-0.1.11",
|
|
33
|
+
_internal: {
|
|
34
|
+
meta: { HashModifier: { salt: "lGSUI2bFDjUE9tTMDmgi4epfFNLsU0xY" } },
|
|
35
|
+
data: {
|
|
36
|
+
password: "UsoNNNsm8q4tIZffzzw9QX58b98ksdLC6JN+a1FN93g=",
|
|
37
|
+
checksum: "NbsXtgSCNhwxQV/eBQ+LKA==",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=hash-rows.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash-rows.js","sourceRoot":"","sources":["../../../src/tests/helpers/hash-rows.ts"],"names":[],"mappings":";;;AAQA,kCAKC;AAbY,QAAA,QAAQ,GAAG,8BAA8B,CAAC;AAEvD,MAAM,uBAAuB,GAAG,MAAM,CAAC;AAEvC;;;GAGG;AACH,SAAgB,WAAW,CAAC,KAAa,EAAE,IAAgB;IACzD,QAAQ,CAAC,KAAK,EAAE;QACd,IAAI,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC;QACtC,IAAI,EAAE,CAAC;IACT,CAAC,CAAC,CAAC;AACL,CAAC;AAED,qFAAqF;AACxE,QAAA,eAAe,GAAG;IAC7B,GAAG,EAAE,iBAAiB;IACtB,KAAK,EAAE,qBAAqB;IAC5B,IAAI,EAAE,QAAQ;IACd,SAAS,EAAE;QACT,IAAI,EAAE,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,kCAAkC,EAAE,EAAE;QACpE,IAAI,EAAE,EAAE,QAAQ,EAAE,8CAA8C,EAAE;KACnE;CACF,CAAC;AAEF;;;GAGG;AACU,QAAA,2BAA2B,GAAG;IACzC,GAAG,EAAE,yBAAyB;IAC9B,SAAS,EAAE;QACT,IAAI,EAAE,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,kCAAkC,EAAE,EAAE;QACpE,IAAI,EAAE;YACJ,QAAQ,EAAE,8CAA8C;YACxD,QAAQ,EAAE,0BAA0B;SACrC;KACF;CACF,CAAC"}
|
|
@@ -9,9 +9,76 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
9
9
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const node_crypto_1 = require("node:crypto");
|
|
12
13
|
const chai_1 = require("chai");
|
|
13
14
|
const table_1 = require("../../table");
|
|
15
|
+
const common_1 = require("../../modifiers/common");
|
|
14
16
|
const hash_1 = require("../../modifiers/hash");
|
|
17
|
+
const hash_rows_1 = require("../helpers/hash-rows");
|
|
18
|
+
const WRONG_PASSWORD = "not the password";
|
|
19
|
+
const SALT = "c2FsdHNhbHRzYWx0c2FsdA";
|
|
20
|
+
const KEY = "a2V5a2V5a2V5a2V5a2V5a2V5a2V5a2V5a2V5a2V5a2U";
|
|
21
|
+
class Account extends table_1.Table.with(hash_1.HashModifier) {
|
|
22
|
+
}
|
|
23
|
+
__decorate([
|
|
24
|
+
(0, hash_1.Hashed)(),
|
|
25
|
+
(0, table_1.Field)("string"),
|
|
26
|
+
__metadata("design:type", String)
|
|
27
|
+
], Account.prototype, "password", void 0);
|
|
28
|
+
__decorate([
|
|
29
|
+
(0, table_1.Field)("string"),
|
|
30
|
+
__metadata("design:type", String)
|
|
31
|
+
], Account.prototype, "email", void 0);
|
|
32
|
+
class Fingerprint extends table_1.Table.with(hash_1.HashModifier) {
|
|
33
|
+
}
|
|
34
|
+
__decorate([
|
|
35
|
+
(0, hash_1.Hashed)({ algorithm: "sha256" }),
|
|
36
|
+
(0, table_1.Field)("string"),
|
|
37
|
+
__metadata("design:type", String)
|
|
38
|
+
], Fingerprint.prototype, "password", void 0);
|
|
39
|
+
__decorate([
|
|
40
|
+
(0, hash_1.Hashed)({ algorithm: "md5" }),
|
|
41
|
+
(0, table_1.Field)("any"),
|
|
42
|
+
__metadata("design:type", Object)
|
|
43
|
+
], Fingerprint.prototype, "checksum", void 0);
|
|
44
|
+
function accountFrom(row) {
|
|
45
|
+
return (0, common_1.fromDatabase)(structuredClone(row), Account);
|
|
46
|
+
}
|
|
47
|
+
function accountStoring(stored) {
|
|
48
|
+
return accountFrom({ _internal: { meta: {}, data: { password: stored } } });
|
|
49
|
+
}
|
|
50
|
+
function newAccount(password = hash_rows_1.PASSWORD) {
|
|
51
|
+
const account = new Account();
|
|
52
|
+
account.password = password;
|
|
53
|
+
return account;
|
|
54
|
+
}
|
|
55
|
+
function scryptRow(ln, password = hash_rows_1.PASSWORD) {
|
|
56
|
+
const salt = Buffer.alloc(16, 7);
|
|
57
|
+
const key = (0, node_crypto_1.scryptSync)(JSON.stringify(password), salt, 32, {
|
|
58
|
+
N: 2 ** ln,
|
|
59
|
+
r: 8,
|
|
60
|
+
p: 1,
|
|
61
|
+
maxmem: 2 ** 30,
|
|
62
|
+
});
|
|
63
|
+
const encoded = [salt, key].map((part) => part.toString("base64").replace(/=+$/, ""));
|
|
64
|
+
return `$scrypt$ln=${ln},r=8,p=1$${encoded[0]}$${encoded[1]}`;
|
|
65
|
+
}
|
|
66
|
+
async function expectVerifies(account, password = hash_rows_1.PASSWORD) {
|
|
67
|
+
(0, chai_1.expect)(account.testHash("password", password)).to.equal(true);
|
|
68
|
+
(0, chai_1.expect)(await account.testHashAsync("password", password)).to.equal(true);
|
|
69
|
+
}
|
|
70
|
+
async function expectRefuses(account, password = WRONG_PASSWORD) {
|
|
71
|
+
(0, chai_1.expect)(account.testHash("password", password)).to.equal(false);
|
|
72
|
+
(0, chai_1.expect)(await account.testHashAsync("password", password)).to.equal(false);
|
|
73
|
+
}
|
|
74
|
+
function fastestMs(work) {
|
|
75
|
+
const runs = [0, 1, 2].map(() => {
|
|
76
|
+
const started = performance.now();
|
|
77
|
+
work();
|
|
78
|
+
return performance.now() - started;
|
|
79
|
+
});
|
|
80
|
+
return Math.min(...runs);
|
|
81
|
+
}
|
|
15
82
|
describe("Modifiers - hash", () => {
|
|
16
83
|
it("hashes string values", async () => {
|
|
17
84
|
const modifier = new TestableHashModifier();
|
|
@@ -103,6 +170,234 @@ describe("Modifiers - hash", () => {
|
|
|
103
170
|
(0, chai_1.expect)(isNotMatch).to.equal(false);
|
|
104
171
|
});
|
|
105
172
|
});
|
|
173
|
+
(0, hash_rows_1.scryptSuite)("Modifiers - hash, default scrypt format", () => {
|
|
174
|
+
it("stores a string that describes the hash it holds, at N >= 2^15", () => {
|
|
175
|
+
const stored = newAccount().password;
|
|
176
|
+
const match = /^\$scrypt\$ln=(\d+),r=(\d+),p=(\d+)\$([^$]+)\$([^$]+)$/.exec(stored);
|
|
177
|
+
if (!match)
|
|
178
|
+
throw new Error(`Unexpected stored form: ${stored}`);
|
|
179
|
+
const [ln, r, p] = match.slice(1, 4).map(Number);
|
|
180
|
+
(0, chai_1.expect)(ln).to.be.at.least(15);
|
|
181
|
+
const key = (0, node_crypto_1.scryptSync)(JSON.stringify(hash_rows_1.PASSWORD), Buffer.from(match[4], "base64"), Buffer.from(match[5], "base64").length, { N: 2 ** ln, r, p, maxmem: 2 ** 30 });
|
|
182
|
+
(0, chai_1.expect)(key.toString("base64").replace(/=+$/, "")).to.equal(match[5]);
|
|
183
|
+
});
|
|
184
|
+
it("gives each write its own salt", () => {
|
|
185
|
+
(0, chai_1.expect)(newAccount().password).to.not.equal(newAccount().password);
|
|
186
|
+
});
|
|
187
|
+
it("tests a value against the stored hash, sync and async", async () => {
|
|
188
|
+
const account = newAccount();
|
|
189
|
+
await expectVerifies(account);
|
|
190
|
+
await expectRefuses(account);
|
|
191
|
+
});
|
|
192
|
+
it("does not flag a hash it just wrote", () => {
|
|
193
|
+
(0, chai_1.expect)(newAccount().isHashOutdated("password")).to.equal(false);
|
|
194
|
+
});
|
|
195
|
+
it("does not flag a field with nothing stored", () => {
|
|
196
|
+
(0, chai_1.expect)(new Account().isHashOutdated("password")).to.equal(false);
|
|
197
|
+
});
|
|
198
|
+
it("throws when asked about a field that is not hashed", async () => {
|
|
199
|
+
const account = newAccount();
|
|
200
|
+
(0, chai_1.expect)(() => account.isHashOutdated("email")).to.throw("email");
|
|
201
|
+
let failure;
|
|
202
|
+
await account.testHashAsync("email", hash_rows_1.PASSWORD).catch((error) => {
|
|
203
|
+
failure = error;
|
|
204
|
+
});
|
|
205
|
+
(0, chai_1.expect)(failure).to.be.instanceOf(Error);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
(0, hash_rows_1.scryptSuite)("Modifiers - hash, upgrade of stored hashes", () => {
|
|
209
|
+
it("verifies a row written by 0.1.11 and flags it", async () => {
|
|
210
|
+
const account = accountFrom(hash_rows_1.ROW_FROM_0_1_11);
|
|
211
|
+
await expectVerifies(account);
|
|
212
|
+
await expectRefuses(account);
|
|
213
|
+
(0, chai_1.expect)(account.isHashOutdated("password")).to.equal(true);
|
|
214
|
+
});
|
|
215
|
+
it("after a re-hash, verifies and no longer flags that row", async () => {
|
|
216
|
+
const account = accountFrom(hash_rows_1.ROW_FROM_0_1_11);
|
|
217
|
+
account.password = hash_rows_1.PASSWORD;
|
|
218
|
+
(0, chai_1.expect)(account.password).to.match(/^\$scrypt\$/);
|
|
219
|
+
await expectVerifies(account);
|
|
220
|
+
await expectRefuses(account);
|
|
221
|
+
(0, chai_1.expect)(account.isHashOutdated("password")).to.equal(false);
|
|
222
|
+
});
|
|
223
|
+
it("verifies a hash of a lower cost by its own parameters and flags it", async () => {
|
|
224
|
+
const account = accountStoring(scryptRow(14));
|
|
225
|
+
await expectVerifies(account);
|
|
226
|
+
await expectRefuses(account);
|
|
227
|
+
(0, chai_1.expect)(account.isHashOutdated("password")).to.equal(true);
|
|
228
|
+
});
|
|
229
|
+
it("keeps the salt of each row while two async tests overlap", async () => {
|
|
230
|
+
const first = accountFrom(hash_rows_1.ROW_FROM_0_1_11);
|
|
231
|
+
const second = accountFrom({
|
|
232
|
+
_internal: {
|
|
233
|
+
meta: { HashModifier: { salt: "another-salt" } },
|
|
234
|
+
data: {
|
|
235
|
+
password: (0, node_crypto_1.createHash)("sha256")
|
|
236
|
+
.update("another-salt")
|
|
237
|
+
.update(JSON.stringify(WRONG_PASSWORD))
|
|
238
|
+
.digest("base64"),
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
const results = await Promise.all([
|
|
243
|
+
first.testHashAsync("password", hash_rows_1.PASSWORD),
|
|
244
|
+
second.testHashAsync("password", WRONG_PASSWORD),
|
|
245
|
+
first.testHashAsync("password", WRONG_PASSWORD),
|
|
246
|
+
second.testHashAsync("password", hash_rows_1.PASSWORD),
|
|
247
|
+
]);
|
|
248
|
+
(0, chai_1.expect)(results).to.deep.equal([true, true, false, false]);
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
(0, hash_rows_1.scryptSuite)("Modifiers - hash, assigning the stored hash back", () => {
|
|
252
|
+
const rows = {
|
|
253
|
+
"from 0.1.11": () => accountFrom(hash_rows_1.ROW_FROM_0_1_11),
|
|
254
|
+
current: () => newAccount(),
|
|
255
|
+
};
|
|
256
|
+
for (const [name, build] of Object.entries(rows)) {
|
|
257
|
+
it(`lock() on a ${name} row keeps the hash`, async () => {
|
|
258
|
+
const account = build();
|
|
259
|
+
const stored = account.password;
|
|
260
|
+
(0, common_1.lock)(account, hash_1.HashModifier, undefined);
|
|
261
|
+
(0, common_1.lock)(account, hash_1.HashModifier, ["password"]);
|
|
262
|
+
(0, chai_1.expect)(account.password).to.equal(stored);
|
|
263
|
+
await expectVerifies(account);
|
|
264
|
+
});
|
|
265
|
+
it(`assigning the hash of a ${name} row keeps it`, async () => {
|
|
266
|
+
const account = build();
|
|
267
|
+
const stored = account.password;
|
|
268
|
+
account.password = stored;
|
|
269
|
+
(0, chai_1.expect)(account.password).to.equal(stored);
|
|
270
|
+
await expectVerifies(account);
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
(0, hash_rows_1.scryptSuite)("Modifiers - hash, explicit algorithm", () => {
|
|
275
|
+
function fingerprintFrom(row) {
|
|
276
|
+
return (0, common_1.fromDatabase)(structuredClone(row), Fingerprint);
|
|
277
|
+
}
|
|
278
|
+
it("still verifies rows written by 0.1.11 and never flags them", async () => {
|
|
279
|
+
const row = fingerprintFrom(hash_rows_1.FINGERPRINT_ROW_FROM_0_1_11);
|
|
280
|
+
(0, chai_1.expect)(row.testHash("password", hash_rows_1.PASSWORD)).to.equal(true);
|
|
281
|
+
(0, chai_1.expect)(await row.testHashAsync("password", hash_rows_1.PASSWORD)).to.equal(true);
|
|
282
|
+
(0, chai_1.expect)(row.testHash("password", WRONG_PASSWORD)).to.equal(false);
|
|
283
|
+
(0, chai_1.expect)(row.testHash("checksum", { a: 1 })).to.equal(true);
|
|
284
|
+
(0, chai_1.expect)(row.testHash("checksum", { a: 2 })).to.equal(false);
|
|
285
|
+
(0, chai_1.expect)(row.isHashOutdated("password")).to.equal(false);
|
|
286
|
+
(0, chai_1.expect)(row.isHashOutdated("checksum")).to.equal(false);
|
|
287
|
+
});
|
|
288
|
+
it("still writes the same salted digest as 0.1.11", () => {
|
|
289
|
+
const row = fingerprintFrom(hash_rows_1.FINGERPRINT_ROW_FROM_0_1_11);
|
|
290
|
+
row.password = "another value";
|
|
291
|
+
(0, chai_1.expect)(row.password).to.equal((0, node_crypto_1.createHash)("sha256")
|
|
292
|
+
.update(hash_rows_1.FINGERPRINT_ROW_FROM_0_1_11._internal.meta.HashModifier.salt)
|
|
293
|
+
.update(JSON.stringify("another value"))
|
|
294
|
+
.digest("base64"));
|
|
295
|
+
});
|
|
296
|
+
it("verifies a scrypt hash and does not flag it, so it is not weakened", async () => {
|
|
297
|
+
const row = (0, common_1.fromDatabase)({ _internal: { meta: {}, data: { password: scryptRow(15) } } }, Fingerprint);
|
|
298
|
+
(0, chai_1.expect)(await row.testHashAsync("password", hash_rows_1.PASSWORD)).to.equal(true);
|
|
299
|
+
(0, chai_1.expect)(row.testHash("password", WRONG_PASSWORD)).to.equal(false);
|
|
300
|
+
(0, chai_1.expect)(row.isHashOutdated("password")).to.equal(false);
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
(0, hash_rows_1.scryptSuite)("Modifiers - hash, values that cannot verify", () => {
|
|
304
|
+
const unusable = [
|
|
305
|
+
["no value", undefined],
|
|
306
|
+
["a null value", null],
|
|
307
|
+
["a number", 42],
|
|
308
|
+
["a digest with no salt", "nPamXXaKC8N+8W7xqxTSo8r+pS9jXMkjJu2XMZtUR+A="],
|
|
309
|
+
["a bare prefix", "$scrypt$"],
|
|
310
|
+
["a key too short to compare", `$scrypt$ln=15,r=8,p=1$${SALT}$A`],
|
|
311
|
+
["an empty key", `$scrypt$ln=15,r=8,p=1$${SALT}$`],
|
|
312
|
+
["a salt too short", `$scrypt$ln=15,r=8,p=1$AAAA$${KEY}`],
|
|
313
|
+
["a cost over the memory limit", `$scrypt$ln=30,r=8,p=1$${SALT}$${KEY}`],
|
|
314
|
+
["a zero p", `$scrypt$ln=15,r=8,p=0$${SALT}$${KEY}`],
|
|
315
|
+
["a zero r", `$scrypt$ln=15,r=0,p=1$${SALT}$${KEY}`],
|
|
316
|
+
["a zero ln", `$scrypt$ln=0,r=8,p=1$${SALT}$${KEY}`],
|
|
317
|
+
["an N that r cannot hold", `$scrypt$ln=16,r=1,p=1$${SALT}$${KEY}`],
|
|
318
|
+
];
|
|
319
|
+
for (const [name, stored] of unusable) {
|
|
320
|
+
it(`refuses a row storing ${name} without throwing`, async () => {
|
|
321
|
+
await expectRefuses(accountStoring(stored), hash_rows_1.PASSWORD);
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
it("refuses an undefined candidate on a row with nothing stored", async () => {
|
|
325
|
+
await expectRefuses(new Account(), new Account().email);
|
|
326
|
+
});
|
|
327
|
+
it("refuses an undefined candidate on a row with a hash", async () => {
|
|
328
|
+
await expectRefuses(newAccount(), new Account().email);
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
(0, hash_rows_1.scryptSuite)("Modifiers - hash, limits of a stored string", () => {
|
|
332
|
+
function rowFor({ ln = 1, r = 1, p = 1, saltBytes = 16, keyBytes = 32 }) {
|
|
333
|
+
const salt = Buffer.alloc(saltBytes, 7);
|
|
334
|
+
const key = (0, node_crypto_1.scryptSync)(JSON.stringify(hash_rows_1.PASSWORD), salt, keyBytes, {
|
|
335
|
+
N: 2 ** ln,
|
|
336
|
+
r,
|
|
337
|
+
p,
|
|
338
|
+
maxmem: 2 ** 30,
|
|
339
|
+
});
|
|
340
|
+
const [encodedSalt, encodedKey] = [salt, key].map((part) => part.toString("base64").replace(/=+$/, ""));
|
|
341
|
+
return `$scrypt$ln=${ln},r=${r},p=${p}$${encodedSalt}$${encodedKey}`;
|
|
342
|
+
}
|
|
343
|
+
const overLimit = [
|
|
344
|
+
{ name: "a p of 17", cost: { p: 17 } },
|
|
345
|
+
{ name: "an r of 100", cost: { r: 100 } },
|
|
346
|
+
{ name: "memory above 128 MiB", cost: { ln: 17, r: 9 } },
|
|
347
|
+
{ name: "a salt of 65 bytes", cost: { saltBytes: 65 } },
|
|
348
|
+
{ name: "a salt of 15 bytes", cost: { saltBytes: 15 } },
|
|
349
|
+
{ name: "a key of 65 bytes", cost: { keyBytes: 65 } },
|
|
350
|
+
{ name: "a key of 15 bytes", cost: { keyBytes: 15 } },
|
|
351
|
+
];
|
|
352
|
+
for (const { name, cost } of overLimit) {
|
|
353
|
+
it(`refuses the right password on a row asking for ${name}`, async () => {
|
|
354
|
+
const account = accountStoring(rowFor(cost));
|
|
355
|
+
await expectRefuses(account, hash_rows_1.PASSWORD);
|
|
356
|
+
(0, chai_1.expect)(account.isHashOutdated("password")).to.equal(true);
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
const atLimit = [
|
|
360
|
+
{ name: "a p of 16", cost: { p: 16 } },
|
|
361
|
+
{ name: "128 MiB of memory", cost: { ln: 17, r: 8 } },
|
|
362
|
+
{ name: "64-byte salt and key", cost: { saltBytes: 64, keyBytes: 64 } },
|
|
363
|
+
];
|
|
364
|
+
for (const { name, cost } of atLimit) {
|
|
365
|
+
it(`verifies the right password on a row asking for ${name}`, () => {
|
|
366
|
+
const account = accountStoring(rowFor(cost));
|
|
367
|
+
(0, chai_1.expect)(account.testHash("password", hash_rows_1.PASSWORD)).to.equal(true);
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
(0, hash_rows_1.scryptSuite)("Modifiers - hash, cost of a test", () => {
|
|
372
|
+
it("testHashAsync lets the event loop turn while it runs", async () => {
|
|
373
|
+
const account = newAccount();
|
|
374
|
+
let turns = 0;
|
|
375
|
+
const timer = setInterval(() => {
|
|
376
|
+
turns += 1;
|
|
377
|
+
}, 1);
|
|
378
|
+
await account.testHashAsync("password", hash_rows_1.PASSWORD);
|
|
379
|
+
clearInterval(timer);
|
|
380
|
+
(0, chai_1.expect)(turns).to.be.greaterThan(0);
|
|
381
|
+
});
|
|
382
|
+
it("a refusal costs a scrypt run whatever the row holds", () => {
|
|
383
|
+
const current = newAccount();
|
|
384
|
+
const legacy = accountFrom(hash_rows_1.ROW_FROM_0_1_11);
|
|
385
|
+
const empty = new Account();
|
|
386
|
+
const full = fastestMs(() => current.testHash("password", WRONG_PASSWORD));
|
|
387
|
+
for (const account of [legacy, empty]) {
|
|
388
|
+
const refused = fastestMs(() => account.testHash("password", WRONG_PASSWORD));
|
|
389
|
+
(0, chai_1.expect)(refused).to.be.greaterThan(full / 4);
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
it("a field with an explicit digest does not pay for scrypt", () => {
|
|
393
|
+
const row = new Fingerprint();
|
|
394
|
+
row.password = hash_rows_1.PASSWORD;
|
|
395
|
+
const digest = fastestMs(() => row.testHash("password", WRONG_PASSWORD));
|
|
396
|
+
const current = newAccount();
|
|
397
|
+
const full = fastestMs(() => current.testHash("password", WRONG_PASSWORD));
|
|
398
|
+
(0, chai_1.expect)(digest).to.be.lessThan(full / 4);
|
|
399
|
+
});
|
|
400
|
+
});
|
|
106
401
|
class TestableHashModifier extends hash_1.HashModifier {
|
|
107
402
|
setOptions(options) {
|
|
108
403
|
this.options = options;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hash.test.js","sourceRoot":"","sources":["../../../src/tests/modifiers/hash.test.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA,+BAA8B;AAC9B,uCAA2C;AAC3C,+CAA4D;AAE5D,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE;QACpC,MAAM,QAAQ,GAAG,IAAI,oBAAoB,EAAE,CAAC;QAC5C,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC7C,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAErB,MAAM,aAAa,GAAG,aAAa,CAAC;QACpC,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;QAEvD,IAAA,aAAM,EAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QACjC,IAAA,aAAM,EAAC,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QAC3C,IAAA,aAAM,EAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE;QACpC,MAAM,QAAQ,GAAG,IAAI,oBAAoB,EAAE,CAAC;QAC5C,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC7C,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAErB,MAAM,aAAa,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC;QAChD,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;QAEvD,IAAA,aAAM,EAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QACjC,IAAA,aAAM,EAAC,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,0BAA0B,EAAE,KAAK,IAAI,EAAE;QACxC,MAAM,QAAQ,GAAG,IAAI,oBAAoB,EAAE,CAAC;QAC5C,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC7C,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAErB,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QAEnD,IAAA,aAAM,EAAC,MAAM,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,4BAA4B,EAAE,KAAK,IAAI,EAAE;QAC1C,MAAM,QAAQ,GAAG,IAAI,oBAAoB,EAAE,CAAC;QAC5C,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;QAC1C,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAErB,MAAM,aAAa,GAAG,WAAW,CAAC;QAClC,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;QAEvD,IAAA,aAAM,EAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QACjC,IAAA,aAAM,EAAC,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QAC3C,IAAA,aAAM,EAAC,QAAQ,CAAC,UAAU,EAAE,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;QACpD,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;QAC7C,SAAS,CAAC,UAAU,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC9C,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAEtB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;QAC7C,SAAS,CAAC,UAAU,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC9C,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAEtB,MAAM,KAAK,GAAG,WAAW,CAAC;QAC1B,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QACjD,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAEjD,IAAA,aAAM,EAAC,OAAO,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACtC,IAAA,aAAM,EAAC,SAAS,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC;IAC1E,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,wBAAwB,EAAE,KAAK,IAAI,EAAE;QACtC,MAAM,SAAU,SAAQ,aAAK,CAAC,IAAI,CAAC,mBAAY,CAAC;SAI/C;QADS;YAFP,IAAA,aAAM,EAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;YAC/B,IAAA,aAAK,EAAC,QAAQ,CAAC;;mDACS;QAG3B,MAAM,QAAQ,GAAG,IAAI,SAAS,EAAE,CAAC;QACjC,QAAQ,CAAC,QAAQ,GAAG,eAAe,CAAC;QAEpC,IAAA,aAAM,EAAC,QAAQ,CAAC,QAAQ,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACtE,IAAA,aAAM,EAAC,QAAQ,CAAC,QAAQ,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,6BAA6B,EAAE,KAAK,IAAI,EAAE;QAC3C,MAAM,QAAQ,GAAG,IAAI,oBAAoB,EAAE,CAAC;QAC5C,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC7C,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAErB,MAAM,aAAa,GAAG,WAAW,CAAC;QAClC,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;QAEvD,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;QACrD,MAAM,UAAU,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;QAEvD,IAAA,aAAM,EAAC,OAAO,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAA,aAAM,EAAC,UAAU,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;QACtD,MAAM,SAAU,SAAQ,aAAK,CAAC,IAAI,CAAC,mBAAY,CAAC;SAI/C;QADS;YAFP,IAAA,aAAM,EAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;YAC/B,IAAA,aAAK,EAAC,QAAQ,CAAC;;mDACS;QAG3B,MAAM,QAAQ,GAAG,IAAI,SAAS,EAAE,CAAC;QACjC,QAAQ,CAAC,QAAQ,GAAG,eAAe,CAAC;QAEpC,IAAA,aAAM,EAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;QAEpE,MAAM,OAAO,GAAG,QAAQ,CAAC,QAAQ,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;QAC/D,MAAM,UAAU,GAAG,QAAQ,CAAC,QAAQ,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;QAElE,IAAA,aAAM,EAAC,OAAO,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAA,aAAM,EAAC,UAAU,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAUH,MAAM,oBAAqB,SAAQ,mBAAY;IACtC,UAAU,CAAC,OAAoB;QACpC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAEM,UAAU;QACf,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAEM,OAAO;QACZ,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;IAEM,OAAO,CAAC,IAAc;QAC3B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF"}
|
|
1
|
+
{"version":3,"file":"hash.test.js","sourceRoot":"","sources":["../../../src/tests/modifiers/hash.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const chai_1 = require("chai");
|
|
13
|
+
const interface_database_1 = require("@antelopejs/interface-database");
|
|
14
|
+
const database_1 = require("../database");
|
|
15
|
+
const model_1 = require("../model");
|
|
16
|
+
const autodate_1 = require("../modifiers/autodate");
|
|
17
|
+
const encryption_1 = require("../modifiers/encryption");
|
|
18
|
+
const schema_1 = require("../schema");
|
|
19
|
+
const table_1 = require("../table");
|
|
20
|
+
const SCHEMA_ID = "partial-update-schema";
|
|
21
|
+
let Note = class Note extends table_1.Table.with(encryption_1.EncryptionModifier) {
|
|
22
|
+
};
|
|
23
|
+
__decorate([
|
|
24
|
+
(0, table_1.Field)("string"),
|
|
25
|
+
__metadata("design:type", String)
|
|
26
|
+
], Note.prototype, "title", void 0);
|
|
27
|
+
__decorate([
|
|
28
|
+
(0, autodate_1.UpdateTime)(),
|
|
29
|
+
(0, table_1.Field)("date"),
|
|
30
|
+
__metadata("design:type", Date)
|
|
31
|
+
], Note.prototype, "updatedAt", void 0);
|
|
32
|
+
__decorate([
|
|
33
|
+
(0, encryption_1.Encrypted)({ secretKey: "0123456789abcdef0123456789abcdef" }),
|
|
34
|
+
(0, table_1.Field)("string"),
|
|
35
|
+
__metadata("design:type", String)
|
|
36
|
+
], Note.prototype, "secret", void 0);
|
|
37
|
+
Note = __decorate([
|
|
38
|
+
(0, schema_1.RegisterTable)("partial_update_notes", SCHEMA_ID)
|
|
39
|
+
], Note);
|
|
40
|
+
const NoteModel = (0, model_1.BasicDataModel)(Note, "partial_update_notes");
|
|
41
|
+
describe("Integration - partial update", () => {
|
|
42
|
+
it("keeps modifier data on an update of another field", async () => {
|
|
43
|
+
await (0, database_1.RegisterSchema)(SCHEMA_ID);
|
|
44
|
+
const instance = interface_database_1.Schema.get(SCHEMA_ID)?.instance();
|
|
45
|
+
if (!instance)
|
|
46
|
+
throw new Error("Partial update schema not found");
|
|
47
|
+
const model = new NoteModel(instance);
|
|
48
|
+
const [id] = await model.insert({ title: "First", secret: "kept" });
|
|
49
|
+
await model.update(id, { title: "Second" });
|
|
50
|
+
const note = await model.get(id);
|
|
51
|
+
(0, chai_1.expect)(note?.title).to.equal("Second");
|
|
52
|
+
(0, chai_1.expect)(note?.secret).to.equal("kept");
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
//# sourceMappingURL=partial_update.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"partial_update.test.js","sourceRoot":"","sources":["../../src/tests/partial_update.test.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA,+BAA8B;AAC9B,uEAAwD;AACxD,0CAA6C;AAC7C,oCAA0C;AAC1C,oDAAmD;AACnD,wDAAwE;AACxE,sCAA0C;AAC1C,oCAAwC;AAExC,MAAM,SAAS,GAAG,uBAAuB,CAAC;AAG1C,IAAM,IAAI,GAAV,MAAM,IAAK,SAAQ,aAAK,CAAC,IAAI,CAAC,+BAAkB,CAAC;CAWhD,CAAA;AATS;IADP,IAAA,aAAK,EAAC,QAAQ,CAAC;;mCACM;AAId;IAFP,IAAA,qBAAU,GAAE;IACZ,IAAA,aAAK,EAAC,MAAM,CAAC;8BACK,IAAI;uCAAC;AAIhB;IAFP,IAAA,sBAAS,EAAC,EAAE,SAAS,EAAE,kCAAkC,EAAE,CAAC;IAC5D,IAAA,aAAK,EAAC,QAAQ,CAAC;;oCACO;AAVnB,IAAI;IADT,IAAA,sBAAa,EAAC,sBAAsB,EAAE,SAAS,CAAC;GAC3C,IAAI,CAWT;AAED,MAAM,SAAS,GAAG,IAAA,sBAAc,EAAC,IAAI,EAAE,sBAAsB,CAAC,CAAC;AAE/D,QAAQ,CAAC,8BAA8B,EAAE,GAAG,EAAE;IAC5C,EAAE,CAAC,mDAAmD,EAAE,KAAK,IAAI,EAAE;QACjE,MAAM,IAAA,yBAAc,EAAC,SAAS,CAAC,CAAC;QAChC,MAAM,QAAQ,GAAG,2BAAM,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;QACnD,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;QAClE,MAAM,KAAK,GAAG,IAAI,SAAS,CAAC,QAAQ,CAAC,CAAC;QACtC,MAAM,CAAC,EAAE,CAAC,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QAEpE,MAAM,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QAE5C,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACjC,IAAA,aAAM,EAAC,IAAI,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACvC,IAAA,aAAM,EAAC,IAAI,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -59,7 +59,7 @@ class UserCredentials extends Table.with(EncryptionModifier) {
|
|
|
59
59
|
|
|
60
60
|
### HashModifier
|
|
61
61
|
|
|
62
|
-
The `HashModifier` stores a salted one-way hash of field values. Unlike encryption, hashing is irreversible -- you cannot retrieve the original value.
|
|
62
|
+
The `HashModifier` stores a salted one-way hash of field values. Unlike encryption, hashing is irreversible -- you cannot retrieve the original value. By default it stores a scrypt hash, a memory-hard function fit for passwords.
|
|
63
63
|
|
|
64
64
|
```typescript
|
|
65
65
|
import {
|
|
@@ -73,39 +73,60 @@ class User extends Table.with(HashModifier) {
|
|
|
73
73
|
@Field("string")
|
|
74
74
|
declare email: string;
|
|
75
75
|
|
|
76
|
-
@Hashed(
|
|
76
|
+
@Hashed()
|
|
77
77
|
@Field("string")
|
|
78
78
|
declare password: string;
|
|
79
79
|
|
|
80
|
-
verifyPassword(plainPassword: string): boolean {
|
|
81
|
-
return this.
|
|
80
|
+
verifyPassword(plainPassword: string): Promise<boolean> {
|
|
81
|
+
return this.testHashAsync("password", plainPassword);
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
```
|
|
85
85
|
|
|
86
86
|
#### How It Works
|
|
87
87
|
|
|
88
|
-
-
|
|
89
|
-
-
|
|
90
|
-
-
|
|
88
|
+
- Each write generates a random salt and stores `$scrypt$ln=15,r=8,p=3$<salt>$<key>` in the field. The string holds the algorithm, the cost, the salt and the hash, so the cost can change later.
|
|
89
|
+
- Assigning the stored hash back to the field, or calling `lock` on a loaded row, keeps the hash as it is.
|
|
90
|
+
- `testHash` and `testHashAsync` read the cost and the salt from the stored string, hash the candidate and compare in constant time.
|
|
91
|
+
- A test spends one scrypt run whatever the row holds, so the time of a refusal does not tell whether a hash exists.
|
|
92
|
+
- A write is synchronous, because a property setter cannot wait. Assigning a value blocks the event loop for one scrypt run, about 150 ms and 32 MiB.
|
|
91
93
|
|
|
92
94
|
#### Options
|
|
93
95
|
|
|
94
|
-
| Option | Type | Default
|
|
95
|
-
| ----------- | -------- |
|
|
96
|
-
| `algorithm` | `string` | `
|
|
97
|
-
|
|
98
|
-
`LocalizationModifier.fallbackLocaleOf(TableClass, field)` returns a field's `fallbackLocale`, or `undefined` when the field is not localized or declares none. Generated forms use it to require a value in that locale.
|
|
96
|
+
| Option | Type | Default | Description |
|
|
97
|
+
| ----------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
98
|
+
| `algorithm` | `string` | none | Digest to store instead of scrypt, any name `createHash` accepts. A digest is fast to guess: use it only for non-secret fingerprints, never for credentials. Rows stay in the bare digest format. |
|
|
99
99
|
|
|
100
|
-
#### Mixin
|
|
100
|
+
#### Mixin Methods
|
|
101
101
|
|
|
102
|
-
The `HashModifier` mixin adds
|
|
102
|
+
The `HashModifier` mixin adds these methods to table instances:
|
|
103
103
|
|
|
104
104
|
```typescript
|
|
105
105
|
testHash(field: string, value: unknown): boolean
|
|
106
|
+
testHashAsync(field: string, value: unknown): Promise<boolean>
|
|
107
|
+
isHashOutdated(field: string): boolean
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
- `testHashAsync` returns whether `value` matches the stored hash. It runs scrypt on the thread pool, so use it on a server.
|
|
111
|
+
- `testHash` returns the same result and blocks the event loop while scrypt runs.
|
|
112
|
+
- `isHashOutdated` returns whether the stored hash differs from what the field writes today. It is always `false` on a field with an `algorithm`, and when nothing is stored.
|
|
113
|
+
|
|
114
|
+
#### Upgrade Stored Hashes
|
|
115
|
+
|
|
116
|
+
A row written before scrypt became the default holds a bare salted SHA-256. It still verifies, and `isHashOutdated` returns `true` for it. The same applies to a hash of an earlier cost. To upgrade a row, assign the value after a successful test, because that is the only moment the plaintext is known:
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
if (await user.testHashAsync("password", password)) {
|
|
120
|
+
if (user.isHashOutdated("password")) {
|
|
121
|
+
user.password = password;
|
|
122
|
+
await userModel.update(user);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
106
125
|
```
|
|
107
126
|
|
|
108
|
-
|
|
127
|
+
To move a credential off `@Hashed({ algorithm: "sha256" })`, remove the option. Its rows keep verifying and are flagged for upgrade. A row written with another digest verifies only while the field declares that digest.
|
|
128
|
+
|
|
129
|
+
`LocalizationModifier.fallbackLocaleOf(TableClass, field)` returns a field's `fallbackLocale`, or `undefined` when the field is not localized or declares none. Generated forms use it to require a value in that locale.
|
|
109
130
|
|
|
110
131
|
### LocalizationModifier
|
|
111
132
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antelopejs/interface-database-decorators",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"description": "AntelopeJS decorators for database tables and typed data models, with encrypted, hashed and localized fields",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"antelopejs",
|
|
@@ -148,7 +148,7 @@
|
|
|
148
148
|
"@antelopejs/interface-api": "^0.0.15",
|
|
149
149
|
"@antelopejs/interface-core": "^0.2.4",
|
|
150
150
|
"@antelopejs/interface-database": "^0.1.11",
|
|
151
|
-
"@antelopejs/tooling-configs": "^0.2.
|
|
151
|
+
"@antelopejs/tooling-configs": "^0.2.17",
|
|
152
152
|
"@types/chai": "^4.3.20",
|
|
153
153
|
"@types/mocha": "^10.0.10",
|
|
154
154
|
"@types/node": "^22.19.15",
|
|
@@ -110,7 +110,7 @@ class Chef extends Table.with(
|
|
|
110
110
|
EncryptionModifier,
|
|
111
111
|
LocalizationModifier,
|
|
112
112
|
) {
|
|
113
|
-
@Hashed() declare passphrase: string; // one-way; test via instance.
|
|
113
|
+
@Hashed() declare passphrase: string; // one-way scrypt; test via await instance.testHashAsync("passphrase", value)
|
|
114
114
|
@Encrypted({ secretKey }) declare supplierCode: string; // transparent encrypt/decrypt (autolock + autounlock)
|
|
115
115
|
@Localized() declare motto: string; // read AND write via instance.localize("en", ["motto"])
|
|
116
116
|
@CreationTime() declare hiredAt: Date; // set on insert, stripped from updates
|
|
@@ -124,7 +124,9 @@ class Chef extends Table.with(
|
|
|
124
124
|
is loaded once before decorators run (e.g. `import "@antelopejs/interface-database-decorators"`).
|
|
125
125
|
- `Hashed`, `Encrypted`, and `Localized` require the matching modifier class in `Table.with(...)`; `CreationTime`/`UpdateTime` are event-only and need no mixin.
|
|
126
126
|
- Modifier ordering matters: `attachModifier` throws if you stack a modifier after a one-way modifier (e.g. anything after `@Hashed` on the same field).
|
|
127
|
-
- Hashed fields cannot be read back or queried directly — only equality-tested with `testHash
|
|
127
|
+
- Hashed fields cannot be read back or queried directly — only equality-tested with `testHashAsync`, or `testHash`, which blocks the event loop for one scrypt run.
|
|
128
|
+
- A row can hold an older hash that still verifies. After a successful test, when `instance.isHashOutdated(field)` is true, assign the tested value and save the row.
|
|
129
|
+
- `@Hashed({ algorithm })` stores a fast digest instead of scrypt: use it only for non-secret fingerprints, never for credentials.
|
|
128
130
|
- Localized fields must be written through a localized instance (`instance.localize(locale).field = ...`);
|
|
129
131
|
values assigned before `localize()` are held in floating state and silently dropped on insert/update.
|
|
130
132
|
- A localized value `""` is stored as no value: reads and queries in that locale get the `fallbackLocale` value.
|