@proteinjs/user-server 1.22.0 → 1.22.1
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 +11 -0
- package/dist/generated/index.js +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/src/authentication/PasswordResetToken.d.ts +43 -12
- package/dist/src/authentication/PasswordResetToken.d.ts.map +1 -1
- package/dist/src/authentication/PasswordResetToken.js +86 -21
- package/dist/src/authentication/PasswordResetToken.js.map +1 -1
- package/dist/src/routes/devLogin.d.ts +1 -1
- package/dist/src/routes/devLogin.js +1 -1
- package/dist/src/routes/executePasswordReset.d.ts +2 -1
- package/dist/src/routes/executePasswordReset.d.ts.map +1 -1
- package/dist/src/routes/executePasswordReset.js +9 -7
- package/dist/src/routes/executePasswordReset.js.map +1 -1
- package/dist/src/routes/initiatePasswordReset.d.ts +3 -2
- package/dist/src/routes/initiatePasswordReset.d.ts.map +1 -1
- package/dist/src/routes/initiatePasswordReset.js +30 -27
- package/dist/src/routes/initiatePasswordReset.js.map +1 -1
- package/dist/test/DbTraffic.d.ts +29 -0
- package/dist/test/DbTraffic.d.ts.map +1 -0
- package/dist/test/DbTraffic.js +106 -0
- package/dist/test/DbTraffic.js.map +1 -0
- package/dist/test/DevLoginBootstrapAdmin.test.js +3 -4
- package/dist/test/DevLoginBootstrapAdmin.test.js.map +1 -1
- package/dist/test/DevLoginStaleCookieFirstHit.test.js +1 -1
- package/dist/test/DevLoginStaleCookieFirstHit.test.js.map +1 -1
- package/dist/test/ExecutePasswordReset.test.js +513 -124
- package/dist/test/ExecutePasswordReset.test.js.map +1 -1
- package/dist/test/InitiatePasswordReset.test.d.ts +2 -0
- package/dist/test/InitiatePasswordReset.test.d.ts.map +1 -0
- package/dist/test/InitiatePasswordReset.test.js +415 -0
- package/dist/test/InitiatePasswordReset.test.js.map +1 -0
- package/dist/test/LogCapture.d.ts +22 -0
- package/dist/test/LogCapture.d.ts.map +1 -0
- package/dist/test/LogCapture.js +127 -0
- package/dist/test/LogCapture.js.map +1 -0
- package/dist/test/MalformedResetTokens.d.ts +11 -0
- package/dist/test/MalformedResetTokens.d.ts.map +1 -0
- package/dist/test/MalformedResetTokens.js +35 -0
- package/dist/test/MalformedResetTokens.js.map +1 -0
- package/dist/test/ValidateResetToken.test.js +156 -33
- package/dist/test/ValidateResetToken.test.js.map +1 -1
- package/dist/test/emulatorLock.test.d.ts +2 -0
- package/dist/test/emulatorLock.test.d.ts.map +1 -0
- package/dist/test/emulatorLock.test.js +32 -0
- package/dist/test/emulatorLock.test.js.map +1 -0
- package/dist/test/emulatorLockName.test.d.ts +2 -0
- package/dist/test/emulatorLockName.test.d.ts.map +1 -0
- package/dist/test/emulatorLockName.test.js +36 -0
- package/dist/test/emulatorLockName.test.js.map +1 -0
- package/generated/index.ts +1 -1
- package/index.ts +1 -1
- package/jest.config.js +2 -2
- package/package.json +3 -3
- package/src/authentication/PasswordResetToken.ts +74 -20
- package/src/routes/devLogin.ts +1 -1
- package/src/routes/executePasswordReset.ts +3 -2
- package/src/routes/initiatePasswordReset.ts +20 -17
- package/test/DbTraffic.ts +43 -0
- package/test/DevLoginBootstrapAdmin.test.ts +3 -4
- package/test/DevLoginStaleCookieFirstHit.test.ts +1 -1
- package/test/ExecutePasswordReset.test.ts +308 -70
- package/test/InitiatePasswordReset.test.ts +250 -0
- package/test/LogCapture.ts +54 -0
- package/test/MalformedResetTokens.ts +27 -0
- package/test/ValidateResetToken.test.ts +108 -24
- package/test/emulatorLock.js +5 -6
- package/test/emulatorLock.test.ts +32 -0
- package/test/emulatorLockName.test.ts +33 -0
|
@@ -0,0 +1,127 @@
|
|
|
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
|
+
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
39
|
+
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
40
|
+
if (ar || !(i in from)) {
|
|
41
|
+
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
42
|
+
ar[i] = from[i];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return to.concat(ar || Array.prototype.slice.call(from));
|
|
46
|
+
};
|
|
47
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
|
+
exports.LogCapture = void 0;
|
|
49
|
+
var crypto_1 = require("crypto");
|
|
50
|
+
/**
|
|
51
|
+
* Everything the process wrote while a run executed, as a log reader would see it: every
|
|
52
|
+
* `console` call at every level (where the default log writer sends each entry, whichever logger
|
|
53
|
+
* produced it) and every direct stdout/stderr write, one string per write. Nothing is filtered by
|
|
54
|
+
* logger name or level, so the capture holds exactly what the default configuration emits.
|
|
55
|
+
*/
|
|
56
|
+
var LogCapture = /** @class */ (function () {
|
|
57
|
+
function LogCapture(lines) {
|
|
58
|
+
this.lines = lines;
|
|
59
|
+
}
|
|
60
|
+
LogCapture.during = function (run) {
|
|
61
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
62
|
+
var lines, consoleSpies, streamSpies;
|
|
63
|
+
return __generator(this, function (_a) {
|
|
64
|
+
switch (_a.label) {
|
|
65
|
+
case 0:
|
|
66
|
+
lines = [];
|
|
67
|
+
consoleSpies = ['log', 'info', 'warn', 'error', 'debug'].map(function (level) {
|
|
68
|
+
return jest.spyOn(console, level).mockImplementation(function () {
|
|
69
|
+
var parts = [];
|
|
70
|
+
for (var _i = 0; _i < arguments.length; _i++) {
|
|
71
|
+
parts[_i] = arguments[_i];
|
|
72
|
+
}
|
|
73
|
+
lines.push(parts.map(function (part) { return (typeof part === 'string' ? part : JSON.stringify(part)); }).join(' '));
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
streamSpies = [process.stdout, process.stderr].map(function (stream) {
|
|
77
|
+
var write = stream.write.bind(stream);
|
|
78
|
+
return jest.spyOn(stream, 'write').mockImplementation((function () {
|
|
79
|
+
var args = [];
|
|
80
|
+
for (var _i = 0; _i < arguments.length; _i++) {
|
|
81
|
+
args[_i] = arguments[_i];
|
|
82
|
+
}
|
|
83
|
+
lines.push(String(args[0]));
|
|
84
|
+
return write.apply(void 0, args);
|
|
85
|
+
}));
|
|
86
|
+
});
|
|
87
|
+
_a.label = 1;
|
|
88
|
+
case 1:
|
|
89
|
+
_a.trys.push([1, , 3, 4]);
|
|
90
|
+
return [4 /*yield*/, run()];
|
|
91
|
+
case 2:
|
|
92
|
+
_a.sent();
|
|
93
|
+
return [3 /*break*/, 4];
|
|
94
|
+
case 3:
|
|
95
|
+
__spreadArray(__spreadArray([], consoleSpies, true), streamSpies, true).forEach(function (spy) { return spy.mockRestore(); });
|
|
96
|
+
return [7 /*endfinally*/];
|
|
97
|
+
case 4: return [2 /*return*/, new LogCapture(lines)];
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
Object.defineProperty(LogCapture.prototype, "text", {
|
|
103
|
+
get: function () {
|
|
104
|
+
return this.lines.join('\n');
|
|
105
|
+
},
|
|
106
|
+
enumerable: false,
|
|
107
|
+
configurable: true
|
|
108
|
+
});
|
|
109
|
+
/**
|
|
110
|
+
* Fails unless the log is free of `token` in every form that would let a log reader act on it
|
|
111
|
+
* or tie a line to a row: the token, the SHA-256 digest a row stores for it, and the token's
|
|
112
|
+
* own leading characters — a log reference to a token is cut from its digest, never from the
|
|
113
|
+
* token. Letter case is ignored.
|
|
114
|
+
*/
|
|
115
|
+
LogCapture.prototype.expectFreeOf = function (token) {
|
|
116
|
+
var text = this.text.toLowerCase();
|
|
117
|
+
var digest = (0, crypto_1.createHash)('sha256').update(token).digest('hex');
|
|
118
|
+
expect(text).not.toContain(token.toLowerCase());
|
|
119
|
+
expect(text).not.toContain(digest);
|
|
120
|
+
expect(text).not.toContain(token.slice(0, LogCapture.TOKEN_PREFIX_LENGTH).toLowerCase());
|
|
121
|
+
};
|
|
122
|
+
/** How many leading characters of a token count as the token showing up in a log. */
|
|
123
|
+
LogCapture.TOKEN_PREFIX_LENGTH = 12;
|
|
124
|
+
return LogCapture;
|
|
125
|
+
}());
|
|
126
|
+
exports.LogCapture = LogCapture;
|
|
127
|
+
//# sourceMappingURL=LogCapture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LogCapture.js","sourceRoot":"","sources":["../../test/LogCapture.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,iCAAoC;AAEpC;;;;;GAKG;AACH;IAIE,oBAA6B,KAAe;QAAf,UAAK,GAAL,KAAK,CAAU;IAAG,CAAC;IAEnC,iBAAM,GAAnB,UAAoB,GAAwB;;;;;;wBACpC,KAAK,GAAa,EAAE,CAAC;wBACrB,YAAY,GAAI,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAW,CAAC,GAAG,CAAC,UAAC,KAAK;4BAClF,OAAA,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,kBAAkB,CAAC;gCAAC,eAAmB;qCAAnB,UAAmB,EAAnB,qBAAmB,EAAnB,IAAmB;oCAAnB,0BAAmB;;gCAChE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,UAAC,IAAI,IAAK,OAAA,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,EAAxD,CAAwD,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;4BACtG,CAAC,CAAC;wBAFF,CAEE,CACH,CAAC;wBACI,WAAW,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,UAAC,MAAM;4BAC9D,IAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAoC,CAAC;4BAC3E,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,CAAC;gCAAC,cAAkB;qCAAlB,UAAkB,EAAlB,qBAAkB,EAAlB,IAAkB;oCAAlB,yBAAkB;;gCACxE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gCAC5B,OAAO,KAAK,eAAI,IAAI,EAAE;4BACxB,CAAC,CAAU,CAAC,CAAC;wBACf,CAAC,CAAC,CAAC;;;;wBAED,qBAAM,GAAG,EAAE,EAAA;;wBAAX,SAAW,CAAC;;;wBAEZ,gCAAI,YAAY,SAAK,WAAW,QAAE,OAAO,CAAC,UAAC,GAAG,IAAK,OAAA,GAAG,CAAC,WAAW,EAAE,EAAjB,CAAiB,CAAC,CAAC;;4BAExE,sBAAO,IAAI,UAAU,CAAC,KAAK,CAAC,EAAC;;;;KAC9B;IAED,sBAAI,4BAAI;aAAR;YACE,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/B,CAAC;;;OAAA;IAED;;;;;OAKG;IACH,iCAAY,GAAZ,UAAa,KAAa;QACxB,IAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;QACrC,IAAM,MAAM,GAAG,IAAA,mBAAU,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAChE,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;QAChD,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QACnC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,mBAAmB,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;IAC3F,CAAC;IA3CD,qFAAqF;IAC7D,8BAAmB,GAAG,EAAE,CAAC;IA2CnD,iBAAC;CAAA,AA7CD,IA6CC;AA7CY,gCAAU"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `token` field of a request that does not carry a well-formed reset token: every type a
|
|
3
|
+
* parsed body or query string can deliver, and every near-miss of the token's shape. The
|
|
4
|
+
* near-misses are cut from a LIVE token, so a shape check that loosens (a lost anchor, a wider
|
|
5
|
+
* length, a case-insensitive class, a missing type check) would be looking up something derived
|
|
6
|
+
* from a real credential — and the suites that use this table assert that no lookup is built.
|
|
7
|
+
*/
|
|
8
|
+
export declare class MalformedResetTokens {
|
|
9
|
+
static readonly CASES: [label: string, tokenField: (liveToken: string) => Record<string, unknown>][];
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=MalformedResetTokens.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MalformedResetTokens.d.ts","sourceRoot":"","sources":["../../test/MalformedResetTokens.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,qBAAa,oBAAoB;IAC/B,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAAE,CAiBlG;CACH"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MalformedResetTokens = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* The `token` field of a request that does not carry a well-formed reset token: every type a
|
|
6
|
+
* parsed body or query string can deliver, and every near-miss of the token's shape. The
|
|
7
|
+
* near-misses are cut from a LIVE token, so a shape check that loosens (a lost anchor, a wider
|
|
8
|
+
* length, a case-insensitive class, a missing type check) would be looking up something derived
|
|
9
|
+
* from a real credential — and the suites that use this table assert that no lookup is built.
|
|
10
|
+
*/
|
|
11
|
+
var MalformedResetTokens = /** @class */ (function () {
|
|
12
|
+
function MalformedResetTokens() {
|
|
13
|
+
}
|
|
14
|
+
MalformedResetTokens.CASES = [
|
|
15
|
+
['absent', function () { return ({}); }],
|
|
16
|
+
['undefined', function () { return ({ token: undefined }); }],
|
|
17
|
+
['null', function () { return ({ token: null }); }],
|
|
18
|
+
['empty', function () { return ({ token: '' }); }],
|
|
19
|
+
['a string of another shape', function () { return ({ token: 'tok-valid-1' }); }],
|
|
20
|
+
['a number', function () { return ({ token: 123 }); }],
|
|
21
|
+
['true', function () { return ({ token: true }); }],
|
|
22
|
+
['an array', function () { return ({ token: ['a', 'b'] }); }],
|
|
23
|
+
['an array holding the live token', function (liveToken) { return ({ token: [liveToken] }); }],
|
|
24
|
+
['an object', function () { return ({ token: { passwordResetToken: null } }); }],
|
|
25
|
+
['63 hex characters of the live token', function (liveToken) { return ({ token: liveToken.slice(0, 63) }); }],
|
|
26
|
+
['the live token and one more hex character', function (liveToken) { return ({ token: "".concat(liveToken, "a") }); }],
|
|
27
|
+
['the live token in uppercase hex', function (liveToken) { return ({ token: liveToken.toUpperCase() }); }],
|
|
28
|
+
['the live token and a trailing newline', function (liveToken) { return ({ token: "".concat(liveToken, "\n") }); }],
|
|
29
|
+
['the live token after a leading space', function (liveToken) { return ({ token: " ".concat(liveToken) }); }],
|
|
30
|
+
['64 characters that are not hex', function () { return ({ token: 'g'.repeat(64) }); }],
|
|
31
|
+
];
|
|
32
|
+
return MalformedResetTokens;
|
|
33
|
+
}());
|
|
34
|
+
exports.MalformedResetTokens = MalformedResetTokens;
|
|
35
|
+
//# sourceMappingURL=MalformedResetTokens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MalformedResetTokens.js","sourceRoot":"","sources":["../../test/MalformedResetTokens.ts"],"names":[],"mappings":";;;AAAA;;;;;;GAMG;AACH;IAAA;IAmBA,CAAC;IAlBiB,0BAAK,GAAkF;QACrG,CAAC,QAAQ,EAAE,cAAM,OAAA,CAAC,EAAE,CAAC,EAAJ,CAAI,CAAC;QACtB,CAAC,WAAW,EAAE,cAAM,OAAA,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,EAAtB,CAAsB,CAAC;QAC3C,CAAC,MAAM,EAAE,cAAM,OAAA,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAjB,CAAiB,CAAC;QACjC,CAAC,OAAO,EAAE,cAAM,OAAA,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,EAAf,CAAe,CAAC;QAChC,CAAC,2BAA2B,EAAE,cAAM,OAAA,CAAC,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC,EAA1B,CAA0B,CAAC;QAC/D,CAAC,UAAU,EAAE,cAAM,OAAA,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,EAAhB,CAAgB,CAAC;QACpC,CAAC,MAAM,EAAE,cAAM,OAAA,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAjB,CAAiB,CAAC;QACjC,CAAC,UAAU,EAAE,cAAM,OAAA,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC,EAAvB,CAAuB,CAAC;QAC3C,CAAC,iCAAiC,EAAE,UAAC,SAAS,IAAK,OAAA,CAAC,EAAE,KAAK,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,EAAxB,CAAwB,CAAC;QAC5E,CAAC,WAAW,EAAE,cAAM,OAAA,CAAC,EAAE,KAAK,EAAE,EAAE,kBAAkB,EAAE,IAAI,EAAE,EAAE,CAAC,EAAzC,CAAyC,CAAC;QAC9D,CAAC,qCAAqC,EAAE,UAAC,SAAS,IAAK,OAAA,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,EAAnC,CAAmC,CAAC;QAC3F,CAAC,2CAA2C,EAAE,UAAC,SAAS,IAAK,OAAA,CAAC,EAAE,KAAK,EAAE,UAAG,SAAS,MAAG,EAAE,CAAC,EAA5B,CAA4B,CAAC;QAC1F,CAAC,iCAAiC,EAAE,UAAC,SAAS,IAAK,OAAA,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,WAAW,EAAE,EAAE,CAAC,EAApC,CAAoC,CAAC;QACxF,CAAC,uCAAuC,EAAE,UAAC,SAAS,IAAK,OAAA,CAAC,EAAE,KAAK,EAAE,UAAG,SAAS,OAAI,EAAE,CAAC,EAA7B,CAA6B,CAAC;QACvF,CAAC,sCAAsC,EAAE,UAAC,SAAS,IAAK,OAAA,CAAC,EAAE,KAAK,EAAE,WAAI,SAAS,CAAE,EAAE,CAAC,EAA5B,CAA4B,CAAC;QACrF,CAAC,gCAAgC,EAAE,cAAM,OAAA,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,EAA3B,CAA2B,CAAC;KACtE,CAAC;IACJ,2BAAC;CAAA,AAnBD,IAmBC;AAnBY,oDAAoB"}
|
|
@@ -39,12 +39,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
39
39
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
40
40
|
};
|
|
41
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
var crypto_1 = require("crypto");
|
|
42
43
|
var moment_1 = __importDefault(require("moment"));
|
|
43
44
|
var db_1 = require("@proteinjs/db");
|
|
44
45
|
var user_1 = require("@proteinjs/user");
|
|
45
46
|
var validateResetPasswordToken_1 = require("../src/routes/validateResetPasswordToken");
|
|
46
47
|
var PasswordResetToken_1 = require("../src/authentication/PasswordResetToken");
|
|
47
48
|
var UserServerTestEnvironment_1 = require("./UserServerTestEnvironment");
|
|
49
|
+
var DbTraffic_1 = require("./DbTraffic");
|
|
50
|
+
var LogCapture_1 = require("./LogCapture");
|
|
51
|
+
var MalformedResetTokens_1 = require("./MalformedResetTokens");
|
|
48
52
|
var testEnv = new UserServerTestEnvironment_1.UserServerTestEnvironment();
|
|
49
53
|
var invokeValidate = function (query) { return __awaiter(void 0, void 0, void 0, function () {
|
|
50
54
|
var outcome, response;
|
|
@@ -68,22 +72,26 @@ var invokeValidate = function (query) { return __awaiter(void 0, void 0, void 0,
|
|
|
68
72
|
}
|
|
69
73
|
});
|
|
70
74
|
}); };
|
|
71
|
-
var
|
|
72
|
-
|
|
73
|
-
|
|
75
|
+
var sha256Hex = function (value) { return (0, crypto_1.createHash)('sha256').update(value).digest('hex'); };
|
|
76
|
+
/** A well-formed token that was never issued to anyone. */
|
|
77
|
+
var unissuedToken = function () { return sha256Hex("never issued ".concat(Math.random())); };
|
|
78
|
+
/** A user with a token minted by the owner; `expiration` (when given) replaces the expiry the mint stored. */
|
|
79
|
+
var armResetToken = function (email, expiration) { return __awaiter(void 0, void 0, void 0, function () {
|
|
80
|
+
var user, token;
|
|
74
81
|
return __generator(this, function (_a) {
|
|
75
82
|
switch (_a.label) {
|
|
76
83
|
case 0: return [4 /*yield*/, testEnv.createUser({ name: 'Reset User', email: email })];
|
|
77
84
|
case 1:
|
|
78
85
|
user = _a.sent();
|
|
79
|
-
return [4 /*yield*/,
|
|
80
|
-
id: user.id,
|
|
81
|
-
passwordResetToken: token,
|
|
82
|
-
passwordResetTokenExpiration: expiration,
|
|
83
|
-
})];
|
|
86
|
+
return [4 /*yield*/, new PasswordResetToken_1.PasswordResetToken().mint(user)];
|
|
84
87
|
case 2:
|
|
88
|
+
token = _a.sent();
|
|
89
|
+
if (!(expiration !== undefined)) return [3 /*break*/, 4];
|
|
90
|
+
return [4 /*yield*/, (0, db_1.getDbAsSystem)().update(user_1.tables.User, { id: user.id, passwordResetTokenExpiration: expiration })];
|
|
91
|
+
case 3:
|
|
85
92
|
_a.sent();
|
|
86
|
-
|
|
93
|
+
_a.label = 4;
|
|
94
|
+
case 4: return [2 /*return*/, token];
|
|
87
95
|
}
|
|
88
96
|
});
|
|
89
97
|
}); };
|
|
@@ -112,11 +120,9 @@ describe('validateResetPasswordToken route', function () {
|
|
|
112
120
|
var token, outcome;
|
|
113
121
|
return __generator(this, function (_a) {
|
|
114
122
|
switch (_a.label) {
|
|
115
|
-
case 0:
|
|
116
|
-
token = mintToken();
|
|
117
|
-
return [4 /*yield*/, armResetToken('reset-valid@test.local', token, (0, moment_1.default)().add(1, 'hour'))];
|
|
123
|
+
case 0: return [4 /*yield*/, armResetToken('reset-valid@test.local')];
|
|
118
124
|
case 1:
|
|
119
|
-
_a.sent();
|
|
125
|
+
token = _a.sent();
|
|
120
126
|
return [4 /*yield*/, invokeValidate({ token: token })];
|
|
121
127
|
case 2:
|
|
122
128
|
outcome = _a.sent();
|
|
@@ -130,11 +136,9 @@ describe('validateResetPasswordToken route', function () {
|
|
|
130
136
|
var token, outcome;
|
|
131
137
|
return __generator(this, function (_a) {
|
|
132
138
|
switch (_a.label) {
|
|
133
|
-
case 0:
|
|
134
|
-
token = mintToken();
|
|
135
|
-
return [4 /*yield*/, armResetToken('reset-expired@test.local', token, (0, moment_1.default)().subtract(1, 'minute'))];
|
|
139
|
+
case 0: return [4 /*yield*/, armResetToken('reset-expired@test.local', (0, moment_1.default)().subtract(1, 'minute'))];
|
|
136
140
|
case 1:
|
|
137
|
-
_a.sent();
|
|
141
|
+
token = _a.sent();
|
|
138
142
|
return [4 /*yield*/, invokeValidate({ token: token })];
|
|
139
143
|
case 2:
|
|
140
144
|
outcome = _a.sent();
|
|
@@ -149,7 +153,7 @@ describe('validateResetPasswordToken route', function () {
|
|
|
149
153
|
var outcome;
|
|
150
154
|
return __generator(this, function (_a) {
|
|
151
155
|
switch (_a.label) {
|
|
152
|
-
case 0: return [4 /*yield*/, invokeValidate({ token:
|
|
156
|
+
case 0: return [4 /*yield*/, invokeValidate({ token: unissuedToken() })];
|
|
153
157
|
case 1:
|
|
154
158
|
outcome = _a.sent();
|
|
155
159
|
expect(outcome.status).toBe(200);
|
|
@@ -159,32 +163,151 @@ describe('validateResetPasswordToken route', function () {
|
|
|
159
163
|
}
|
|
160
164
|
});
|
|
161
165
|
}); });
|
|
162
|
-
it
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
var outcome;
|
|
168
|
-
return __generator(this, function (_a) {
|
|
169
|
-
switch (_a.label) {
|
|
170
|
-
case 0: return [4 /*yield*/, invokeValidate({ token: token })];
|
|
166
|
+
it('the stored digest presented as a token resolves invalid — the row holds nothing that can be presented', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
167
|
+
var token, row, _a, outcome, traffic;
|
|
168
|
+
return __generator(this, function (_b) {
|
|
169
|
+
switch (_b.label) {
|
|
170
|
+
case 0: return [4 /*yield*/, armResetToken('reset-digest@test.local')];
|
|
171
171
|
case 1:
|
|
172
|
-
|
|
172
|
+
token = _b.sent();
|
|
173
|
+
return [4 /*yield*/, (0, db_1.getDbAsSystem)().get(user_1.tables.User, { email: 'reset-digest@test.local' })];
|
|
174
|
+
case 2:
|
|
175
|
+
row = _b.sent();
|
|
176
|
+
expect(row.passwordResetToken).toBe(sha256Hex(token));
|
|
177
|
+
return [4 /*yield*/, DbTraffic_1.DbTraffic.during(testEnv.spannerDriver, function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
178
|
+
switch (_a.label) {
|
|
179
|
+
case 0: return [4 /*yield*/, invokeValidate({ token: row.passwordResetToken })];
|
|
180
|
+
case 1: return [2 /*return*/, _a.sent()];
|
|
181
|
+
}
|
|
182
|
+
}); }); })];
|
|
183
|
+
case 3:
|
|
184
|
+
_a = _b.sent(), outcome = _a.result, traffic = _a.traffic;
|
|
173
185
|
expect(outcome.status).toBe(200);
|
|
174
186
|
expect(outcome.body).toEqual({ isValid: false, message: 'Invalid token' });
|
|
187
|
+
expect(traffic.lookups).toEqual([{ passwordResetToken: sha256Hex(sha256Hex(token)) }]);
|
|
188
|
+
expect(traffic.statements).toBe(1);
|
|
189
|
+
expect(traffic.writes).toBe(0);
|
|
175
190
|
return [2 /*return*/];
|
|
176
191
|
}
|
|
177
192
|
});
|
|
178
193
|
}); });
|
|
179
|
-
it('a
|
|
180
|
-
var outcome;
|
|
194
|
+
it('a token an earlier release stored in clear resolves invalid — no migration, the person asks again', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
195
|
+
var clearToken, user, outcome;
|
|
181
196
|
return __generator(this, function (_a) {
|
|
182
197
|
switch (_a.label) {
|
|
183
|
-
case 0:
|
|
198
|
+
case 0:
|
|
199
|
+
clearToken = unissuedToken();
|
|
200
|
+
return [4 /*yield*/, testEnv.createUser({ name: 'Reset User', email: 'reset-stored-in-clear@test.local' })];
|
|
184
201
|
case 1:
|
|
202
|
+
user = _a.sent();
|
|
203
|
+
return [4 /*yield*/, (0, db_1.getDbAsSystem)().update(user_1.tables.User, {
|
|
204
|
+
id: user.id,
|
|
205
|
+
passwordResetToken: clearToken,
|
|
206
|
+
passwordResetTokenExpiration: (0, moment_1.default)().add(1, 'hour'),
|
|
207
|
+
})];
|
|
208
|
+
case 2:
|
|
209
|
+
_a.sent();
|
|
210
|
+
return [4 /*yield*/, invokeValidate({ token: clearToken })];
|
|
211
|
+
case 3:
|
|
185
212
|
outcome = _a.sent();
|
|
186
|
-
expect(outcome.status).toBe(
|
|
187
|
-
expect(outcome.body
|
|
213
|
+
expect(outcome.status).toBe(200);
|
|
214
|
+
expect(outcome.body).toEqual({ isValid: false, message: 'Invalid token' });
|
|
215
|
+
return [2 /*return*/];
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
}); });
|
|
219
|
+
describe('a query without a well-formed token is invalid without a lookup', function () {
|
|
220
|
+
var liveToken;
|
|
221
|
+
beforeAll(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
222
|
+
return __generator(this, function (_a) {
|
|
223
|
+
switch (_a.label) {
|
|
224
|
+
case 0: return [4 /*yield*/, armResetToken('reset-shapes@test.local')];
|
|
225
|
+
case 1:
|
|
226
|
+
liveToken = _a.sent();
|
|
227
|
+
return [2 /*return*/];
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
}); });
|
|
231
|
+
it.each(MalformedResetTokens_1.MalformedResetTokens.CASES)('%s: invalid, no email, and nothing reaches the database', function (_label, tokenField) { return __awaiter(void 0, void 0, void 0, function () {
|
|
232
|
+
var query, _a, outcome, traffic;
|
|
233
|
+
return __generator(this, function (_b) {
|
|
234
|
+
switch (_b.label) {
|
|
235
|
+
case 0:
|
|
236
|
+
query = tokenField(liveToken);
|
|
237
|
+
return [4 /*yield*/, DbTraffic_1.DbTraffic.during(testEnv.spannerDriver, function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
238
|
+
switch (_a.label) {
|
|
239
|
+
case 0: return [4 /*yield*/, invokeValidate(query)];
|
|
240
|
+
case 1: return [2 /*return*/, _a.sent()];
|
|
241
|
+
}
|
|
242
|
+
}); }); })];
|
|
243
|
+
case 1:
|
|
244
|
+
_a = _b.sent(), outcome = _a.result, traffic = _a.traffic;
|
|
245
|
+
// Nothing usable in the query is a 400; anything else that is not a token is an invalid token.
|
|
246
|
+
expect(outcome).toEqual(query.token
|
|
247
|
+
? { status: 200, body: { isValid: false, message: 'Invalid token' } }
|
|
248
|
+
: { status: 400, body: { isValid: false, message: 'No token provided' } });
|
|
249
|
+
expect(traffic).toEqual(DbTraffic_1.DbTraffic.NONE);
|
|
250
|
+
return [2 /*return*/];
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
}); });
|
|
254
|
+
it('after every refusal the live token is still valid', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
255
|
+
var outcome;
|
|
256
|
+
return __generator(this, function (_a) {
|
|
257
|
+
switch (_a.label) {
|
|
258
|
+
case 0: return [4 /*yield*/, invokeValidate({ token: liveToken })];
|
|
259
|
+
case 1:
|
|
260
|
+
outcome = _a.sent();
|
|
261
|
+
expect(outcome.body).toEqual({ isValid: true, email: 'reset-shapes@test.local' });
|
|
262
|
+
return [2 /*return*/];
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
}); });
|
|
266
|
+
});
|
|
267
|
+
it('no token, no leading characters of one and no full digest reach the log — every verdict', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
268
|
+
var live, expired, unissued, log, _i, _a, token;
|
|
269
|
+
return __generator(this, function (_b) {
|
|
270
|
+
switch (_b.label) {
|
|
271
|
+
case 0: return [4 /*yield*/, armResetToken('reset-log-live@test.local')];
|
|
272
|
+
case 1:
|
|
273
|
+
live = _b.sent();
|
|
274
|
+
return [4 /*yield*/, armResetToken('reset-log-expired@test.local', (0, moment_1.default)().subtract(1, 'minute'))];
|
|
275
|
+
case 2:
|
|
276
|
+
expired = _b.sent();
|
|
277
|
+
unissued = unissuedToken();
|
|
278
|
+
return [4 /*yield*/, LogCapture_1.LogCapture.during(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
279
|
+
return __generator(this, function (_a) {
|
|
280
|
+
switch (_a.label) {
|
|
281
|
+
case 0: return [4 /*yield*/, invokeValidate({ token: unissued })];
|
|
282
|
+
case 1:
|
|
283
|
+
_a.sent();
|
|
284
|
+
return [4 /*yield*/, invokeValidate({ token: live.toUpperCase() })];
|
|
285
|
+
case 2:
|
|
286
|
+
_a.sent();
|
|
287
|
+
return [4 /*yield*/, invokeValidate({ token: sha256Hex(live) })];
|
|
288
|
+
case 3:
|
|
289
|
+
_a.sent();
|
|
290
|
+
return [4 /*yield*/, invokeValidate({ token: expired })];
|
|
291
|
+
case 4:
|
|
292
|
+
_a.sent();
|
|
293
|
+
return [4 /*yield*/, invokeValidate({ token: live })];
|
|
294
|
+
case 5:
|
|
295
|
+
_a.sent();
|
|
296
|
+
return [2 /*return*/];
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
}); })];
|
|
300
|
+
case 3:
|
|
301
|
+
log = _b.sent();
|
|
302
|
+
// The capture saw the route's lines: the refusals above logged.
|
|
303
|
+
expect(log.text).toContain('Invalid reset token used');
|
|
304
|
+
expect(log.text).toContain('Expired reset token used');
|
|
305
|
+
expect(log.lines.length).toBeGreaterThanOrEqual(4);
|
|
306
|
+
// The digest presented as a token is covered by `live`: its full digest is what was presented.
|
|
307
|
+
for (_i = 0, _a = [unissued, live, expired]; _i < _a.length; _i++) {
|
|
308
|
+
token = _a[_i];
|
|
309
|
+
log.expectFreeOf(token);
|
|
310
|
+
}
|
|
188
311
|
return [2 /*return*/];
|
|
189
312
|
}
|
|
190
313
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ValidateResetToken.test.js","sourceRoot":"","sources":["../../test/ValidateResetToken.test.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,kDAA4B;AAC5B,oCAA8C;AAC9C,wCAAyC;AACzC,uFAAsF;AACtF,+EAA8E;AAC9E,yEAAwE;
|
|
1
|
+
{"version":3,"file":"ValidateResetToken.test.js","sourceRoot":"","sources":["../../test/ValidateResetToken.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emulatorLock.test.d.ts","sourceRoot":"","sources":["../../test/emulatorLock.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
var crypto_1 = __importDefault(require("crypto"));
|
|
7
|
+
var os_1 = __importDefault(require("os"));
|
|
8
|
+
var path_1 = __importDefault(require("path"));
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
10
|
+
var lockFilePathFor = require('./emulatorLock').lockFilePathFor;
|
|
11
|
+
/**
|
|
12
|
+
* The lockfile path is a CONTRACT between every copy of the emulator lock: two jest runs only
|
|
13
|
+
* serialize when they compute the same file for the same emulator host. The formula is pinned
|
|
14
|
+
* here byte for byte — a neutral `spanner-emulator-` name (no application's name belongs in a
|
|
15
|
+
* shared lock), the sanitized host slug, and the first 8 hex characters of the host's sha1.
|
|
16
|
+
*/
|
|
17
|
+
describe('emulatorLock.lockFilePathFor', function () {
|
|
18
|
+
var expectedFor = function (host, slug) {
|
|
19
|
+
return path_1.default.join(os_1.default.tmpdir(), "spanner-emulator-".concat(slug, "-").concat(crypto_1.default.createHash('sha1').update(host).digest('hex').slice(0, 8), ".lock"));
|
|
20
|
+
};
|
|
21
|
+
it('names the lock by the emulator alone: spanner-emulator-<slug>-<sha1[0..8]>.lock in os.tmpdir()', function () {
|
|
22
|
+
expect(lockFilePathFor('localhost:9010')).toBe(expectedFor('localhost:9010', 'localhost-9010'));
|
|
23
|
+
expect(lockFilePathFor('127.0.0.1:9710')).toBe(expectedFor('127.0.0.1:9710', '127-0-0-1-9710'));
|
|
24
|
+
});
|
|
25
|
+
it('gives two emulators two files, and one emulator the same file every time', function () {
|
|
26
|
+
expect(lockFilePathFor('localhost:9010')).toBe(lockFilePathFor('localhost:9010'));
|
|
27
|
+
expect(lockFilePathFor('localhost:9010')).not.toBe(lockFilePathFor('localhost:9011'));
|
|
28
|
+
// The hash keeps hosts apart even when their slugs collide.
|
|
29
|
+
expect(lockFilePathFor('localhost:9010')).not.toBe(lockFilePathFor('localhost-9010'));
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
//# sourceMappingURL=emulatorLock.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emulatorLock.test.js","sourceRoot":"","sources":["../../test/emulatorLock.test.ts"],"names":[],"mappings":";;;;;AAAA,kDAA4B;AAC5B,0CAAoB;AACpB,8CAAwB;AAExB,8DAA8D;AACtD,IAAA,eAAe,GAAK,OAAO,CAAC,gBAAgB,CAAkD,gBAA/E,CAAgF;AAEvG;;;;;GAKG;AACH,QAAQ,CAAC,8BAA8B,EAAE;IACvC,IAAM,WAAW,GAAG,UAAC,IAAY,EAAE,IAAY;QAC7C,OAAA,cAAI,CAAC,IAAI,CACP,YAAE,CAAC,MAAM,EAAE,EACX,2BAAoB,IAAI,cAAI,gBAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,UAAO,CACpG;IAHD,CAGC,CAAC;IAEJ,EAAE,CAAC,gGAAgG,EAAE;QACnG,MAAM,CAAC,eAAe,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,CAAC,CAAC;QAChG,MAAM,CAAC,eAAe,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,CAAC,CAAC;IAClG,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0EAA0E,EAAE;QAC7E,MAAM,CAAC,eAAe,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,gBAAgB,CAAC,CAAC,CAAC;QAClF,MAAM,CAAC,eAAe,CAAC,gBAAgB,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,eAAe,CAAC,gBAAgB,CAAC,CAAC,CAAC;QACtF,4DAA4D;QAC5D,MAAM,CAAC,eAAe,CAAC,gBAAgB,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,eAAe,CAAC,gBAAgB,CAAC,CAAC,CAAC;IACxF,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emulatorLockName.test.d.ts","sourceRoot":"","sources":["../../test/emulatorLockName.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
var os_1 = __importDefault(require("os"));
|
|
7
|
+
var path_1 = __importDefault(require("path"));
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
9
|
+
var lockFilePathFor = require('./emulatorLock').lockFilePathFor;
|
|
10
|
+
/**
|
|
11
|
+
* The emulator lock's FILE NAME is a contract between every copy of the lock: two jest runs
|
|
12
|
+
* serialize on a shared emulator only when they compute the same file for the same host. No
|
|
13
|
+
* shared package carries the lock yet, so the contract is pinned by value — this table is the
|
|
14
|
+
* same, row for row, in every repository that keeps a copy. A change to a row here is a change
|
|
15
|
+
* to all of them, made in the same window; a copy that drifts stops contending, silently.
|
|
16
|
+
*/
|
|
17
|
+
// [emulator host, lock file name, what the row pins]
|
|
18
|
+
var LOCK_FILE_NAMES = [
|
|
19
|
+
['localhost:9010', 'spanner-emulator-localhost-9010-0091e79a.lock', 'the default shared emulator'],
|
|
20
|
+
[
|
|
21
|
+
'localhost-9010',
|
|
22
|
+
'spanner-emulator-localhost-9010-493b66d1.lock',
|
|
23
|
+
'the same slug as the row above: the hash alone keeps two hosts apart',
|
|
24
|
+
],
|
|
25
|
+
[
|
|
26
|
+
'[emulator.a-host-name-long-enough-to-run-past-the-sixty-four-cap.example.test]:9010',
|
|
27
|
+
'spanner-emulator-emulator-a-host-name-long-enough-to-run-past-the-sixty-four-cap--fe958a1a.lock',
|
|
28
|
+
'edge dashes trimmed first, the slug capped at 64 second (the cap lands on a dash, and it stays)',
|
|
29
|
+
],
|
|
30
|
+
];
|
|
31
|
+
describe('the emulator lock file name (the contract every copy of the lock shares)', function () {
|
|
32
|
+
it.each(LOCK_FILE_NAMES)('%s locks on %s (%s)', function (host, fileName) {
|
|
33
|
+
expect(lockFilePathFor(host)).toBe(path_1.default.join(os_1.default.tmpdir(), fileName));
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
//# sourceMappingURL=emulatorLockName.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emulatorLockName.test.js","sourceRoot":"","sources":["../../test/emulatorLockName.test.ts"],"names":[],"mappings":";;;;;AAAA,0CAAoB;AACpB,8CAAwB;AAExB,8DAA8D;AACtD,IAAA,eAAe,GAAK,OAAO,CAAC,gBAAgB,CAAkD,gBAA/E,CAAgF;AAEvG;;;;;;GAMG;AACH,qDAAqD;AACrD,IAAM,eAAe,GAA+B;IAClD,CAAC,gBAAgB,EAAE,+CAA+C,EAAE,6BAA6B,CAAC;IAClG;QACE,gBAAgB;QAChB,+CAA+C;QAC/C,sEAAsE;KACvE;IACD;QACE,qFAAqF;QACrF,iGAAiG;QACjG,iGAAiG;KAClG;CACF,CAAC;AAEF,QAAQ,CAAC,0EAA0E,EAAE;IACnF,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,qBAAqB,EAAE,UAAC,IAAI,EAAE,QAAQ;QAC7D,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,cAAI,CAAC,IAAI,CAAC,YAAE,CAAC,MAAM,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAC;IACvE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|