@antelopejs/dms 0.4.6 → 0.4.8
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 +40 -0
- package/dist/crons/index.d.ts +1 -0
- package/dist/crons/index.js +7 -1
- package/dist/crons/index.js.map +1 -1
- package/dist/crons/sweep-expired-sessions.d.ts +4 -0
- package/dist/crons/sweep-expired-sessions.js +27 -0
- package/dist/crons/sweep-expired-sessions.js.map +1 -0
- package/dist/db/models/userNotificationPreferences.model.js +17 -11
- package/dist/db/models/userNotificationPreferences.model.js.map +1 -1
- package/dist/db/tables/userNotifications.table.js +3 -0
- package/dist/db/tables/userNotifications.table.js.map +1 -1
- package/dist/pages/settings/users/profile-helpers.d.ts +2 -1
- package/dist/pages/settings/users/profile-helpers.js +9 -0
- package/dist/pages/settings/users/profile-helpers.js.map +1 -1
- package/dist/pages/settings/users/profile.js +2 -2
- package/dist/pages/settings/users/profile.js.map +1 -1
- package/dist/routes/auth/refresh.d.ts +1 -1
- package/dist/routes/auth/refresh.js +2 -1
- package/dist/routes/auth/refresh.js.map +1 -1
- package/dist/routes/auth/switch-tenant.d.ts +1 -1
- package/dist/routes/auth/switch-tenant.js +2 -1
- package/dist/routes/auth/switch-tenant.js.map +1 -1
- package/dist/routes/onboarding.js +1 -1
- package/dist/routes/onboarding.js.map +1 -1
- package/dist/test/antelope.test.js +1 -1
- package/dist/test/helpers/db.d.ts +1 -0
- package/dist/test/helpers/db.js +14 -0
- package/dist/test/helpers/db.js.map +1 -1
- package/dist/test/unit/db/email-normalization.test.d.ts +1 -0
- package/dist/test/unit/db/email-normalization.test.js +59 -0
- package/dist/test/unit/db/email-normalization.test.js.map +1 -0
- package/dist/test/unit/db/notification-feed-index.test.d.ts +1 -0
- package/dist/test/unit/db/notification-feed-index.test.js +35 -0
- package/dist/test/unit/db/notification-feed-index.test.js.map +1 -0
- package/dist/test/unit/db/notification-preferences.test.d.ts +1 -0
- package/dist/test/unit/db/notification-preferences.test.js +21 -0
- package/dist/test/unit/db/notification-preferences.test.js.map +1 -0
- package/dist/test/unit/db/session-refresh-token.test.d.ts +1 -0
- package/dist/test/unit/db/session-refresh-token.test.js +52 -0
- package/dist/test/unit/db/session-refresh-token.test.js.map +1 -0
- package/dist/test/unit/db/session-sweep.test.d.ts +1 -0
- package/dist/test/unit/db/session-sweep.test.js +44 -0
- package/dist/test/unit/db/session-sweep.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms/password-policy.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms/password-policy.test.js +43 -0
- package/dist/test/unit/interfaces/dms/password-policy.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms-auth/refresh-token-rotation.test.js +74 -9
- package/dist/test/unit/interfaces/dms-auth/refresh-token-rotation.test.js.map +1 -1
- package/dist/test/unit/pages/settings/profile-email-change.test.d.ts +1 -0
- package/dist/test/unit/pages/settings/profile-email-change.test.js +50 -0
- package/dist/test/unit/pages/settings/profile-email-change.test.js.map +1 -0
- package/dist/validation/password.d.ts +0 -1
- package/dist/validation/password.js +6 -3
- package/dist/validation/password.js.map +1 -1
- package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +4 -0
- package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +4 -0
- package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/tree/Tree.vue +10 -9
- package/frontend-vue/layers/dms-ui/app/composables/table-view/shortcuts/index.ts +17 -4
- package/frontend-vue/layers/dms-ui/app/composables/table-view/shortcuts/tableViewEscape.ts +22 -0
- package/frontend-vue/layers/dms-ui/app/composables/tree/shortcuts/index.ts +19 -4
- package/frontend-vue/layers/dms-ui/app/composables/tree/shortcuts/treeEscape.ts +21 -0
- package/frontend-vue/layers/dms-ui/app/utils/overlayFocus.ts +16 -0
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +4 -2
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +4 -2
- package/frontend-vue/package.json +1 -1
- package/frontend-vue/pnpm-lock.yaml +5 -5
- package/frontend-vue/tests/table-view-escape-shortcut.test.ts +141 -0
- package/frontend-vue/tests/tree-escape-shortcut.test.ts +138 -0
- package/package.json +4 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notification-feed-index.test.js","sourceRoot":"","sources":["../../../../src/test/unit/db/notification-feed-index.test.ts"],"names":[],"mappings":";;AAAA,+BAA8B;AAC9B,+CAAgE;AAChE,yCAAkD;AAIlD,MAAM,eAAe,GAAG,qBAAqB,CAAC;AAG9C,MAAM,uBAAuB,GAAG,MAAM,CAAC;AACvC,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAE7B,KAAK,UAAU,YAAY;IACzB,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,uBAAuB,CAAC;IACtD,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;QAC7B,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,CAAC,MAAM,IAAA,mBAAc,EAAC,mCAA0B,CAAC,CAAC,CAC5D,eAAe,CAChB,CAAC;YACF,IAAI,GAAG;gBAAE,OAAO,GAAG,CAAC;QACtB,CAAC;QAAC,MAAM,CAAC;QAET,CAAC;QACD,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC;IACxE,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,QAAQ,CAAC,kEAAkE,EAAE,GAAG,EAAE;IAChF,EAAE,CAAC,sEAAsE,EAAE,KAAK;QAC9E,IAAI,CAAC,OAAO,CAAC,uBAAuB,GAAG,gBAAgB,CAAC,CAAC;QACzD,MAAM,GAAG,GAAG,MAAM,YAAY,EAAE,CAAC;QAEjC,IAAA,aAAM,EAAC,GAAG,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC;QACrE,IAAA,aAAM,EAAC,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC;YAC3C,WAAW;YACX,QAAQ;YACR,WAAW;SACZ,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const interface_database_decorators_1 = require("@antelopejs/interface-database-decorators");
|
|
4
|
+
const chai_1 = require("chai");
|
|
5
|
+
const userNotificationPreferences_model_1 = require("../../../db/models/userNotificationPreferences.model");
|
|
6
|
+
const USER_ID = "notification-preferences-concurrent-user";
|
|
7
|
+
const CONCURRENT_READS = 5;
|
|
8
|
+
describe("[unit] notification preferences — one row per user", () => {
|
|
9
|
+
const model = (0, interface_database_decorators_1.GetModel)(userNotificationPreferences_model_1.UserNotificationPreferencesModel);
|
|
10
|
+
after(async () => {
|
|
11
|
+
await model.table.getAll(USER_ID, "userId").delete().run();
|
|
12
|
+
});
|
|
13
|
+
it("creates a single row under concurrent first reads, and every read gets it", async () => {
|
|
14
|
+
const results = await Promise.all(Array.from({ length: CONCURRENT_READS }, () => model.getOrCreatePreferences(USER_ID)));
|
|
15
|
+
const rows = await model.table.getAll(USER_ID, "userId").run();
|
|
16
|
+
(0, chai_1.expect)(rows.map((row) => row._id)).to.deep.equal([USER_ID]);
|
|
17
|
+
for (const result of results)
|
|
18
|
+
(0, chai_1.expect)(result._id).to.equal(USER_ID);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
//# sourceMappingURL=notification-preferences.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notification-preferences.test.js","sourceRoot":"","sources":["../../../../src/test/unit/db/notification-preferences.test.ts"],"names":[],"mappings":";;AAAA,6FAAqE;AACrE,+BAA8B;AAC9B,4GAAwG;AAExG,MAAM,OAAO,GAAG,0CAA0C,CAAC;AAC3D,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAE3B,QAAQ,CAAC,oDAAoD,EAAE,GAAG,EAAE;IAClE,MAAM,KAAK,GAAG,IAAA,wCAAQ,EAAC,oEAAgC,CAAC,CAAC;IAEzD,KAAK,CAAC,KAAK,IAAI,EAAE;QACf,MAAM,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2EAA2E,EAAE,KAAK,IAAI,EAAE;QACzF,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,gBAAgB,EAAE,EAAE,GAAG,EAAE,CAC5C,KAAK,CAAC,sBAAsB,CAAC,OAAO,CAAC,CACtC,CACF,CAAC;QAEF,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,GAAG,EAAE,CAAC;QAC/D,IAAA,aAAM,EAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;QAC5D,KAAK,MAAM,MAAM,IAAI,OAAO;YAAE,IAAA,aAAM,EAAC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const interface_database_decorators_1 = require("@antelopejs/interface-database-decorators");
|
|
4
|
+
const chai_1 = require("chai");
|
|
5
|
+
const db_1 = require("@antelopejs/interface-dms/auth/db");
|
|
6
|
+
const USER_ID = "session-refresh-token-user";
|
|
7
|
+
const LEGACY_SESSION_ID = "session-refresh-token-legacy";
|
|
8
|
+
const HASHED_SESSION_ID = "session-refresh-token-hashed";
|
|
9
|
+
const ONE_MINUTE_MS = 60_000;
|
|
10
|
+
describe("[unit] session refresh tokens — stored hashed, rotated against the database", () => {
|
|
11
|
+
const model = (0, interface_database_decorators_1.GetModel)(db_1.SessionModel);
|
|
12
|
+
function insertSession(id, refreshToken) {
|
|
13
|
+
const now = new Date();
|
|
14
|
+
return model.insert({
|
|
15
|
+
_id: id,
|
|
16
|
+
userId: USER_ID,
|
|
17
|
+
refreshToken,
|
|
18
|
+
userAgent: "",
|
|
19
|
+
ip: "",
|
|
20
|
+
browser: "",
|
|
21
|
+
os: "",
|
|
22
|
+
deviceType: "",
|
|
23
|
+
location: "",
|
|
24
|
+
createdAt: now,
|
|
25
|
+
lastActiveAt: now,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
after(async () => {
|
|
29
|
+
await model.deleteByUserId(USER_ID);
|
|
30
|
+
});
|
|
31
|
+
it("rotates a legacy plaintext row once and leaves no plaintext behind", async () => {
|
|
32
|
+
await insertSession(LEGACY_SESSION_ID, "legacy-T0");
|
|
33
|
+
const rotatedAt = new Date();
|
|
34
|
+
(0, chai_1.expect)(await model.rotateRefreshToken(LEGACY_SESSION_ID, "legacy-T0", "legacy-T1", rotatedAt)).to.equal("legacy-T1");
|
|
35
|
+
const session = await model.get(LEGACY_SESSION_ID);
|
|
36
|
+
(0, chai_1.expect)(session?.refreshToken).to.equal("");
|
|
37
|
+
(0, chai_1.expect)((0, db_1.isCurrentRefreshToken)(session, "legacy-T1")).to.equal(true);
|
|
38
|
+
(0, chai_1.expect)((0, db_1.isCurrentRefreshToken)(session, "legacy-T0")).to.equal(false);
|
|
39
|
+
});
|
|
40
|
+
it("rotates a hashed row and hands the successor to a late predecessor holder", async () => {
|
|
41
|
+
await insertSession(HASHED_SESSION_ID, "");
|
|
42
|
+
await model.replaceRefreshToken(HASHED_SESSION_ID, "T1");
|
|
43
|
+
const rotatedAt = new Date();
|
|
44
|
+
(0, chai_1.expect)(await model.rotateRefreshToken(HASHED_SESSION_ID, "T1", "T2", rotatedAt)).to.equal("T2");
|
|
45
|
+
(0, chai_1.expect)(await model.rotateRefreshToken(HASHED_SESSION_ID, "T1", "loser", rotatedAt)).to.equal("T2");
|
|
46
|
+
(0, chai_1.expect)(await model.rotateRefreshToken(HASHED_SESSION_ID, "T1", "late", new Date(rotatedAt.getTime() + ONE_MINUTE_MS))).to.equal(null);
|
|
47
|
+
const session = await model.get(HASHED_SESSION_ID);
|
|
48
|
+
(0, chai_1.expect)(session?.refreshToken).to.equal("");
|
|
49
|
+
(0, chai_1.expect)((0, db_1.isCurrentRefreshToken)(session, "T2")).to.equal(true);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
//# sourceMappingURL=session-refresh-token.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-refresh-token.test.js","sourceRoot":"","sources":["../../../../src/test/unit/db/session-refresh-token.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const interface_database_decorators_1 = require("@antelopejs/interface-database-decorators");
|
|
4
|
+
const chai_1 = require("chai");
|
|
5
|
+
const db_1 = require("@antelopejs/interface-dms/auth/db");
|
|
6
|
+
const config_1 = require("../../../config");
|
|
7
|
+
const sweep_expired_sessions_1 = require("../../../crons/sweep-expired-sessions");
|
|
8
|
+
const USER_ID = "session-sweep-user";
|
|
9
|
+
const STALE_SESSION_ID = "session-sweep-stale";
|
|
10
|
+
const ACTIVE_SESSION_ID = "session-sweep-active";
|
|
11
|
+
const ONE_MINUTE_MS = 60_000;
|
|
12
|
+
describe("[unit] session sweep — idle sessions past the refresh token lifetime", () => {
|
|
13
|
+
const model = (0, interface_database_decorators_1.GetModel)(db_1.SessionModel);
|
|
14
|
+
const now = new Date();
|
|
15
|
+
const lifetime = (0, config_1.getAuthConfig)().refreshTokenLifetime;
|
|
16
|
+
function insertSession(id, lastActiveAt) {
|
|
17
|
+
return model.insert({
|
|
18
|
+
_id: id,
|
|
19
|
+
userId: USER_ID,
|
|
20
|
+
refreshToken: "",
|
|
21
|
+
userAgent: "",
|
|
22
|
+
ip: "",
|
|
23
|
+
browser: "",
|
|
24
|
+
os: "",
|
|
25
|
+
deviceType: "",
|
|
26
|
+
location: "",
|
|
27
|
+
createdAt: lastActiveAt,
|
|
28
|
+
lastActiveAt,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
before(async () => {
|
|
32
|
+
await insertSession(STALE_SESSION_ID, new Date(now.getTime() - lifetime - ONE_MINUTE_MS));
|
|
33
|
+
await insertSession(ACTIVE_SESSION_ID, new Date(now.getTime() - lifetime + ONE_MINUTE_MS));
|
|
34
|
+
});
|
|
35
|
+
after(async () => {
|
|
36
|
+
await model.deleteByUserId(USER_ID);
|
|
37
|
+
});
|
|
38
|
+
it("deletes a session idle for longer than a refresh token lives, and keeps the others", async () => {
|
|
39
|
+
await (0, sweep_expired_sessions_1.runSweepExpiredSessions)(now);
|
|
40
|
+
(0, chai_1.expect)(await model.get(STALE_SESSION_ID)).to.equal(undefined);
|
|
41
|
+
(0, chai_1.expect)((await model.get(ACTIVE_SESSION_ID))?._id).to.equal(ACTIVE_SESSION_ID);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
//# sourceMappingURL=session-sweep.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-sweep.test.js","sourceRoot":"","sources":["../../../../src/test/unit/db/session-sweep.test.ts"],"names":[],"mappings":";;AAAA,6FAAqE;AACrE,+BAA8B;AAC9B,0DAAiE;AACjE,4CAAgD;AAChD,kFAAgF;AAEhF,MAAM,OAAO,GAAG,oBAAoB,CAAC;AACrC,MAAM,gBAAgB,GAAG,qBAAqB,CAAC;AAC/C,MAAM,iBAAiB,GAAG,sBAAsB,CAAC;AACjD,MAAM,aAAa,GAAG,MAAM,CAAC;AAE7B,QAAQ,CAAC,sEAAsE,EAAE,GAAG,EAAE;IACpF,MAAM,KAAK,GAAG,IAAA,wCAAQ,EAAC,iBAAY,CAAC,CAAC;IACrC,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IACvB,MAAM,QAAQ,GAAG,IAAA,sBAAa,GAAE,CAAC,oBAAoB,CAAC;IAEtD,SAAS,aAAa,CAAC,EAAU,EAAE,YAAkB;QACnD,OAAO,KAAK,CAAC,MAAM,CAAC;YAClB,GAAG,EAAE,EAAE;YACP,MAAM,EAAE,OAAO;YACf,YAAY,EAAE,EAAE;YAChB,SAAS,EAAE,EAAE;YACb,EAAE,EAAE,EAAE;YACN,OAAO,EAAE,EAAE;YACX,EAAE,EAAE,EAAE;YACN,UAAU,EAAE,EAAE;YACd,QAAQ,EAAE,EAAE;YACZ,SAAS,EAAE,YAAY;YACvB,YAAY;SACb,CAAC,CAAC;IACL,CAAC;IAED,MAAM,CAAC,KAAK,IAAI,EAAE;QAChB,MAAM,aAAa,CACjB,gBAAgB,EAChB,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,QAAQ,GAAG,aAAa,CAAC,CACnD,CAAC;QACF,MAAM,aAAa,CACjB,iBAAiB,EACjB,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,QAAQ,GAAG,aAAa,CAAC,CACnD,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,KAAK,CAAC,KAAK,IAAI,EAAE;QACf,MAAM,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oFAAoF,EAAE,KAAK,IAAI,EAAE;QAClG,MAAM,IAAA,gDAAuB,EAAC,GAAG,CAAC,CAAC;QAEnC,IAAA,aAAM,EAAC,MAAM,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAC9D,IAAA,aAAM,EAAC,CAAC,MAAM,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,CACxD,iBAAiB,CAClB,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const chai_1 = require("chai");
|
|
4
|
+
const password_1 = require("@antelopejs/interface-dms/auth/password");
|
|
5
|
+
const COMPLIANT_PASSWORD = "StrongP@ss1";
|
|
6
|
+
const ALLOWED_SPECIAL_CHARS = "@$!%*?&";
|
|
7
|
+
describe("[unit] auth/password policy", () => {
|
|
8
|
+
it("requires at least 8 characters", () => {
|
|
9
|
+
(0, chai_1.expect)(password_1.PASSWORD_MIN_LENGTH).to.equal(8);
|
|
10
|
+
});
|
|
11
|
+
it("accepts a password meeting every rule", () => {
|
|
12
|
+
(0, chai_1.expect)((0, password_1.isPasswordCompliant)(COMPLIANT_PASSWORD)).to.equal(true);
|
|
13
|
+
});
|
|
14
|
+
it("accepts a password of exactly the minimum length", () => {
|
|
15
|
+
(0, chai_1.expect)((0, password_1.isPasswordCompliant)("Abcdef1@")).to.equal(true);
|
|
16
|
+
});
|
|
17
|
+
it("accepts every allowed special character", () => {
|
|
18
|
+
for (const special of ALLOWED_SPECIAL_CHARS) {
|
|
19
|
+
(0, chai_1.expect)((0, password_1.isPasswordCompliant)(`Abcdef1${special}`), special).to.equal(true);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
it("rejects a password shorter than the minimum length", () => {
|
|
23
|
+
(0, chai_1.expect)((0, password_1.isPasswordCompliant)("Abcde1@")).to.equal(false);
|
|
24
|
+
});
|
|
25
|
+
it("rejects a password without an uppercase letter", () => {
|
|
26
|
+
(0, chai_1.expect)((0, password_1.isPasswordCompliant)("weakp@ss1")).to.equal(false);
|
|
27
|
+
});
|
|
28
|
+
it("rejects a password without a digit", () => {
|
|
29
|
+
(0, chai_1.expect)((0, password_1.isPasswordCompliant)("Weakpass@")).to.equal(false);
|
|
30
|
+
});
|
|
31
|
+
it("rejects a password without a special character", () => {
|
|
32
|
+
(0, chai_1.expect)((0, password_1.isPasswordCompliant)("Weakpass1")).to.equal(false);
|
|
33
|
+
});
|
|
34
|
+
it("rejects a character outside the allowed set", () => {
|
|
35
|
+
(0, chai_1.expect)((0, password_1.isPasswordCompliant)("StrongP@ss1#")).to.equal(false);
|
|
36
|
+
(0, chai_1.expect)((0, password_1.isPasswordCompliant)("StrongP@ss1 ")).to.equal(false);
|
|
37
|
+
(0, chai_1.expect)((0, password_1.isPasswordCompliant)("StrongP@ss1é")).to.equal(false);
|
|
38
|
+
});
|
|
39
|
+
it("rejects an empty password", () => {
|
|
40
|
+
(0, chai_1.expect)((0, password_1.isPasswordCompliant)("")).to.equal(false);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
//# sourceMappingURL=password-policy.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"password-policy.test.js","sourceRoot":"","sources":["../../../../../src/test/unit/interfaces/dms/password-policy.test.ts"],"names":[],"mappings":";;AAAA,+BAA8B;AAC9B,sEAGiD;AAEjD,MAAM,kBAAkB,GAAG,aAAa,CAAC;AACzC,MAAM,qBAAqB,GAAG,SAAS,CAAC;AAExC,QAAQ,CAAC,6BAA6B,EAAE,GAAG,EAAE;IAC3C,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;QACxC,IAAA,aAAM,EAAC,8BAAmB,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,IAAA,aAAM,EAAC,IAAA,8BAAmB,EAAC,kBAAkB,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,IAAA,aAAM,EAAC,IAAA,8BAAmB,EAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,KAAK,MAAM,OAAO,IAAI,qBAAqB,EAAE,CAAC;YAC5C,IAAA,aAAM,EAAC,IAAA,8BAAmB,EAAC,UAAU,OAAO,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC3E,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,IAAA,aAAM,EAAC,IAAA,8BAAmB,EAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,IAAA,aAAM,EAAC,IAAA,8BAAmB,EAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,IAAA,aAAM,EAAC,IAAA,8BAAmB,EAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,IAAA,aAAM,EAAC,IAAA,8BAAmB,EAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,IAAA,aAAM,EAAC,IAAA,8BAAmB,EAAC,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC5D,IAAA,aAAM,EAAC,IAAA,8BAAmB,EAAC,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC5D,IAAA,aAAM,EAAC,IAAA,8BAAmB,EAAC,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACnC,IAAA,aAAM,EAAC,IAAA,8BAAmB,EAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const node_crypto_1 = require("node:crypto");
|
|
3
4
|
const chai_1 = require("chai");
|
|
4
5
|
const sessions_model_1 = require("@antelopejs/interface-dms/auth/db/models/sessions.model");
|
|
5
|
-
function
|
|
6
|
+
function sha256(token) {
|
|
7
|
+
return (0, node_crypto_1.createHash)("sha256").update(token).digest("hex");
|
|
8
|
+
}
|
|
9
|
+
function createLegacySession(refreshToken) {
|
|
6
10
|
return { _id: "session", refreshToken };
|
|
7
11
|
}
|
|
12
|
+
function matchesPresented(session, presentedHash) {
|
|
13
|
+
if (session.refreshTokenHash) {
|
|
14
|
+
return session.refreshTokenHash === presentedHash;
|
|
15
|
+
}
|
|
16
|
+
return (!!session.refreshToken && sha256(session.refreshToken) === presentedHash);
|
|
17
|
+
}
|
|
8
18
|
function createModel() {
|
|
9
19
|
const model = Object.create(sessions_model_1.SessionModel.prototype);
|
|
10
20
|
let update;
|
|
@@ -18,9 +28,11 @@ function createModel() {
|
|
|
18
28
|
return query;
|
|
19
29
|
},
|
|
20
30
|
run: async () => {
|
|
21
|
-
|
|
31
|
+
const session = model.currentSession;
|
|
32
|
+
if (!session ||
|
|
33
|
+
!matchesPresented(session, update.previousRefreshTokenHash))
|
|
22
34
|
return 0;
|
|
23
|
-
Object.assign(
|
|
35
|
+
Object.assign(session, update);
|
|
24
36
|
return 1;
|
|
25
37
|
},
|
|
26
38
|
};
|
|
@@ -34,20 +46,20 @@ describe("refresh token rotation", () => {
|
|
|
34
46
|
const committedAt = new Date("2026-08-28T12:00:00.000Z");
|
|
35
47
|
it("returns the canonical winner to a concurrent CAS loser", async () => {
|
|
36
48
|
const model = createModel();
|
|
37
|
-
model.currentSession =
|
|
49
|
+
model.currentSession = createLegacySession("T0");
|
|
38
50
|
(0, chai_1.expect)(await model.rotateRefreshToken("session", "T0", "T1", committedAt)).to.equal("T1");
|
|
39
51
|
(0, chai_1.expect)(await model.rotateRefreshToken("session", "T0", "loser", committedAt)).to.equal("T1");
|
|
40
52
|
});
|
|
41
53
|
it("accepts a loser that began just before the winning commit", async () => {
|
|
42
54
|
const model = createModel();
|
|
43
|
-
model.currentSession =
|
|
55
|
+
model.currentSession = createLegacySession("T0");
|
|
44
56
|
const loserStartedAt = new Date(committedAt.getTime() - 1);
|
|
45
57
|
await model.rotateRefreshToken("session", "T0", "T1", committedAt);
|
|
46
58
|
(0, chai_1.expect)(await model.rotateRefreshToken("session", "T0", "loser", loserStartedAt)).to.equal("T1");
|
|
47
59
|
});
|
|
48
60
|
it("recovers a late duplicate and response-loss retry", async () => {
|
|
49
61
|
const model = createModel();
|
|
50
|
-
model.currentSession =
|
|
62
|
+
model.currentSession = createLegacySession("T0");
|
|
51
63
|
await model.rotateRefreshToken("session", "T0", "T1", committedAt);
|
|
52
64
|
const retryAt = new Date(committedAt.getTime() + 10_000);
|
|
53
65
|
(0, chai_1.expect)(await model.rotateRefreshToken("session", "T0", "discarded", retryAt)).to.equal("T1");
|
|
@@ -55,7 +67,7 @@ describe("refresh token rotation", () => {
|
|
|
55
67
|
});
|
|
56
68
|
it("does not slide predecessor expiry", async () => {
|
|
57
69
|
const model = createModel();
|
|
58
|
-
model.currentSession =
|
|
70
|
+
model.currentSession = createLegacySession("T0");
|
|
59
71
|
await model.rotateRefreshToken("session", "T0", "T1", committedAt);
|
|
60
72
|
await model.rotateRefreshToken("session", "T0", "discarded", new Date(committedAt.getTime() + 10_000));
|
|
61
73
|
const expiredAt = new Date(committedAt.getTime() + sessions_model_1.REFRESH_TOKEN_PREDECESSOR_GRACE_MS + 1);
|
|
@@ -63,7 +75,7 @@ describe("refresh token rotation", () => {
|
|
|
63
75
|
});
|
|
64
76
|
it("rejects other tokens and deleted sessions", async () => {
|
|
65
77
|
const model = createModel();
|
|
66
|
-
model.currentSession =
|
|
78
|
+
model.currentSession = createLegacySession("T0");
|
|
67
79
|
await model.rotateRefreshToken("session", "T0", "T1", committedAt);
|
|
68
80
|
(0, chai_1.expect)(await model.rotateRefreshToken("session", "other", "discarded", committedAt)).to.equal(null);
|
|
69
81
|
model.currentSession = undefined;
|
|
@@ -71,7 +83,7 @@ describe("refresh token rotation", () => {
|
|
|
71
83
|
});
|
|
72
84
|
it("clears predecessor state on replacement", async () => {
|
|
73
85
|
const model = createModel();
|
|
74
|
-
model.currentSession =
|
|
86
|
+
model.currentSession = createLegacySession("T0");
|
|
75
87
|
await model.rotateRefreshToken("session", "T0", "T1", committedAt);
|
|
76
88
|
Object.defineProperty(model, "update", {
|
|
77
89
|
value: async (_id, update) => {
|
|
@@ -84,5 +96,58 @@ describe("refresh token rotation", () => {
|
|
|
84
96
|
await model.replaceRefreshToken("session", "replacement");
|
|
85
97
|
(0, chai_1.expect)(await model.rotateRefreshToken("session", "T0", "discarded", committedAt)).to.equal(null);
|
|
86
98
|
});
|
|
99
|
+
it("keeps no refresh token in plaintext once rotated", async () => {
|
|
100
|
+
const model = createModel();
|
|
101
|
+
model.currentSession = createLegacySession("T0");
|
|
102
|
+
await model.rotateRefreshToken("session", "T0", "T1", committedAt);
|
|
103
|
+
const session = model.currentSession;
|
|
104
|
+
(0, chai_1.expect)(session.refreshToken).to.equal("");
|
|
105
|
+
(0, chai_1.expect)(session.refreshTokenHash).to.equal(sha256("T1"));
|
|
106
|
+
(0, chai_1.expect)(session.sealedRefreshToken).to.be.a("string");
|
|
107
|
+
(0, chai_1.expect)(session.sealedRefreshToken).to.not.include("T1");
|
|
108
|
+
(0, chai_1.expect)((0, sessions_model_1.isCurrentRefreshToken)(session, "T1")).to.equal(true);
|
|
109
|
+
(0, chai_1.expect)((0, sessions_model_1.isCurrentRefreshToken)(session, "T0")).to.equal(false);
|
|
110
|
+
});
|
|
111
|
+
it("accepts a legacy plaintext token once, then only its successor", async () => {
|
|
112
|
+
const model = createModel();
|
|
113
|
+
model.currentSession = createLegacySession("T0");
|
|
114
|
+
(0, chai_1.expect)((0, sessions_model_1.isCurrentRefreshToken)(model.currentSession, "T0")).to.equal(true);
|
|
115
|
+
await model.rotateRefreshToken("session", "T0", "T1", committedAt);
|
|
116
|
+
const afterGrace = new Date(committedAt.getTime() + sessions_model_1.REFRESH_TOKEN_PREDECESSOR_GRACE_MS + 1);
|
|
117
|
+
(0, chai_1.expect)(await model.rotateRefreshToken("session", "T0", "discarded", afterGrace)).to.equal(null);
|
|
118
|
+
(0, chai_1.expect)(await model.rotateRefreshToken("session", "T1", "T2", afterGrace)).to.equal("T2");
|
|
119
|
+
});
|
|
120
|
+
it("hands the sealed successor back to a holder of the predecessor", async () => {
|
|
121
|
+
const model = createModel();
|
|
122
|
+
model.currentSession = createLegacySession("T0");
|
|
123
|
+
await model.rotateRefreshToken("session", "T0", "T1", committedAt);
|
|
124
|
+
const secondRotation = new Date(committedAt.getTime() + 60_000);
|
|
125
|
+
await model.rotateRefreshToken("session", "T1", "T2", secondRotation);
|
|
126
|
+
(0, chai_1.expect)(await model.rotateRefreshToken("session", "T1", "loser", secondRotation)).to.equal("T2");
|
|
127
|
+
});
|
|
128
|
+
it("opens nothing for a predecessor the successor was not sealed under", async () => {
|
|
129
|
+
const model = createModel();
|
|
130
|
+
model.currentSession = createLegacySession("T0");
|
|
131
|
+
await model.rotateRefreshToken("session", "T0", "T1", committedAt);
|
|
132
|
+
model.currentSession.previousRefreshTokenHash =
|
|
133
|
+
sha256("forged");
|
|
134
|
+
(0, chai_1.expect)(await model.rotateRefreshToken("session", "forged", "loser", committedAt)).to.equal(null);
|
|
135
|
+
});
|
|
136
|
+
it("stores only the hash on replacement", async () => {
|
|
137
|
+
const model = createModel();
|
|
138
|
+
model.currentSession = createLegacySession("T0");
|
|
139
|
+
Object.defineProperty(model, "update", {
|
|
140
|
+
value: async (_id, update) => {
|
|
141
|
+
Object.assign(model.currentSession, update);
|
|
142
|
+
return 1;
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
await model.replaceRefreshToken("session", "replacement");
|
|
146
|
+
const session = model.currentSession;
|
|
147
|
+
(0, chai_1.expect)(session.refreshToken).to.equal("");
|
|
148
|
+
(0, chai_1.expect)(session.sealedRefreshToken).to.equal(null);
|
|
149
|
+
(0, chai_1.expect)((0, sessions_model_1.isCurrentRefreshToken)(session, "replacement")).to.equal(true);
|
|
150
|
+
(0, chai_1.expect)((0, sessions_model_1.isCurrentRefreshToken)(session, "T0")).to.equal(false);
|
|
151
|
+
});
|
|
87
152
|
});
|
|
88
153
|
//# sourceMappingURL=refresh-token-rotation.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"refresh-token-rotation.test.js","sourceRoot":"","sources":["../../../../../src/test/unit/interfaces/dms-auth/refresh-token-rotation.test.ts"],"names":[],"mappings":";;AAAA,+BAA8B;AAC9B,
|
|
1
|
+
{"version":3,"file":"refresh-token-rotation.test.js","sourceRoot":"","sources":["../../../../../src/test/unit/interfaces/dms-auth/refresh-token-rotation.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const interface_api_1 = require("@antelopejs/interface-api");
|
|
4
|
+
const chai_1 = require("chai");
|
|
5
|
+
const profile_helpers_1 = require("../../../../pages/settings/users/profile-helpers");
|
|
6
|
+
const HTTP_CONFLICT = 409;
|
|
7
|
+
const EMAIL_TAKEN = "error.email_already_used";
|
|
8
|
+
const CURRENT_USER_ID = "profile-email-current";
|
|
9
|
+
const OTHER_USER_ID = "profile-email-other";
|
|
10
|
+
const OTHER_EMAIL = "holder@acme.dev";
|
|
11
|
+
const FREE_EMAIL = "free@acme.dev";
|
|
12
|
+
const OWN_EMAIL = "mine@acme.dev";
|
|
13
|
+
function modelHolding(accounts) {
|
|
14
|
+
return {
|
|
15
|
+
getByEmail: async (lookup) => {
|
|
16
|
+
const id = accounts[lookup.trim().toLowerCase()];
|
|
17
|
+
return id ? { _id: id } : undefined;
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
async function attempt(userModel, email) {
|
|
22
|
+
try {
|
|
23
|
+
return await (0, profile_helpers_1.assertEmailAvailable)(userModel, email, CURRENT_USER_ID);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (error instanceof interface_api_1.HTTPResult)
|
|
27
|
+
return error;
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
describe("[unit] profile — e-mail change is refused for an address another account holds", () => {
|
|
32
|
+
const userModel = modelHolding({
|
|
33
|
+
[OTHER_EMAIL]: OTHER_USER_ID,
|
|
34
|
+
[OWN_EMAIL]: CURRENT_USER_ID,
|
|
35
|
+
});
|
|
36
|
+
it("refuses another account's address, whatever its casing", async () => {
|
|
37
|
+
const result = await attempt(userModel, " Holder@ACME.dev ");
|
|
38
|
+
(0, chai_1.expect)(result).to.be.instanceOf(interface_api_1.HTTPResult);
|
|
39
|
+
const refusal = result;
|
|
40
|
+
(0, chai_1.expect)(refusal.getStatus()).to.equal(HTTP_CONFLICT);
|
|
41
|
+
(0, chai_1.expect)(refusal.getBody()).to.equal(EMAIL_TAKEN);
|
|
42
|
+
});
|
|
43
|
+
it("accepts a free address, normalized", async () => {
|
|
44
|
+
(0, chai_1.expect)(await attempt(userModel, "Free@Acme.dev")).to.equal(FREE_EMAIL);
|
|
45
|
+
});
|
|
46
|
+
it("accepts the user's own address", async () => {
|
|
47
|
+
(0, chai_1.expect)(await attempt(userModel, "Mine@acme.dev")).to.equal(OWN_EMAIL);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
//# sourceMappingURL=profile-email-change.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profile-email-change.test.js","sourceRoot":"","sources":["../../../../../src/test/unit/pages/settings/profile-email-change.test.ts"],"names":[],"mappings":";;AAAA,6DAAuD;AACvD,+BAA8B;AAE9B,sFAAwF;AAExF,MAAM,aAAa,GAAG,GAAG,CAAC;AAC1B,MAAM,WAAW,GAAG,0BAA0B,CAAC;AAC/C,MAAM,eAAe,GAAG,uBAAuB,CAAC;AAChD,MAAM,aAAa,GAAG,qBAAqB,CAAC;AAC5C,MAAM,WAAW,GAAG,iBAAiB,CAAC;AACtC,MAAM,UAAU,GAAG,eAAe,CAAC;AACnC,MAAM,SAAS,GAAG,eAAe,CAAC;AAElC,SAAS,YAAY,CAAC,QAAgC;IACpD,OAAO;QAEL,UAAU,EAAE,KAAK,EAAE,MAAc,EAAE,EAAE;YACnC,MAAM,EAAE,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;YACjD,OAAO,EAAE,CAAC,CAAC,CAAE,EAAE,GAAG,EAAE,EAAE,EAAsB,CAAC,CAAC,CAAC,SAAS,CAAC;QAC3D,CAAC;KACsB,CAAC;AAC5B,CAAC;AAED,KAAK,UAAU,OAAO,CACpB,SAAoB,EACpB,KAAa;IAEb,IAAI,CAAC;QACH,OAAO,MAAM,IAAA,sCAAoB,EAAC,SAAS,EAAE,KAAK,EAAE,eAAe,CAAC,CAAC;IACvE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,0BAAU;YAAE,OAAO,KAAK,CAAC;QAC9C,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED,QAAQ,CAAC,gFAAgF,EAAE,GAAG,EAAE;IAC9F,MAAM,SAAS,GAAG,YAAY,CAAC;QAC7B,CAAC,WAAW,CAAC,EAAE,aAAa;QAC5B,CAAC,SAAS,CAAC,EAAE,eAAe;KAC7B,CAAC,CAAC;IAEH,EAAE,CAAC,wDAAwD,EAAE,KAAK,IAAI,EAAE;QACtE,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,SAAS,EAAE,mBAAmB,CAAC,CAAC;QAC7D,IAAA,aAAM,EAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,UAAU,CAAC,0BAAU,CAAC,CAAC;QAC5C,MAAM,OAAO,GAAG,MAAoB,CAAC;QACrC,IAAA,aAAM,EAAC,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QACpD,IAAA,aAAM,EAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,KAAK,IAAI,EAAE;QAClD,IAAA,aAAM,EAAC,MAAM,OAAO,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gCAAgC,EAAE,KAAK,IAAI,EAAE;QAC9C,IAAA,aAAM,EAAC,MAAM,OAAO,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -33,8 +33,11 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.passwordSchema =
|
|
36
|
+
exports.passwordSchema = void 0;
|
|
37
37
|
const z = __importStar(require("zod"));
|
|
38
|
-
|
|
39
|
-
exports.passwordSchema = z
|
|
38
|
+
const password_1 = require("@antelopejs/interface-dms/auth/password");
|
|
39
|
+
exports.passwordSchema = z
|
|
40
|
+
.string()
|
|
41
|
+
.min(password_1.PASSWORD_MIN_LENGTH)
|
|
42
|
+
.regex(password_1.PASSWORD_PATTERN);
|
|
40
43
|
//# sourceMappingURL=password.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"password.js","sourceRoot":"","sources":["../../src/validation/password.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,uCAAyB;
|
|
1
|
+
{"version":3,"file":"password.js","sourceRoot":"","sources":["../../src/validation/password.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,uCAAyB;AACzB,sEAGiD;AAEpC,QAAA,cAAc,GAAG,CAAC;KAC5B,MAAM,EAAE;KACR,GAAG,CAAC,8BAAmB,CAAC;KACxB,KAAK,CAAC,2BAAgB,CAAC,CAAC"}
|
|
@@ -44,6 +44,10 @@
|
|
|
44
44
|
"title": "Restoring your session...",
|
|
45
45
|
"description": "Your session has expired. Refreshing your access and reloading the page."
|
|
46
46
|
},
|
|
47
|
+
"network": {
|
|
48
|
+
"title": "Connection lost",
|
|
49
|
+
"description": "The server could not be reached. Check your connection and try again."
|
|
50
|
+
},
|
|
47
51
|
"switch_account_failed": "Failed to switch account",
|
|
48
52
|
"email_already_used": "This email address is already used.",
|
|
49
53
|
"user_not_found": "No user found with this email address.",
|
|
@@ -44,6 +44,10 @@
|
|
|
44
44
|
"title": "Restauration de votre session...",
|
|
45
45
|
"description": "Votre session a expiré. Renouvellement de l'accès et rechargement de la page."
|
|
46
46
|
},
|
|
47
|
+
"network": {
|
|
48
|
+
"title": "Connexion perdue",
|
|
49
|
+
"description": "Le serveur est injoignable. Vérifiez votre connexion et réessayez."
|
|
50
|
+
},
|
|
47
51
|
"switch_account_failed": "Échec du changement de compte",
|
|
48
52
|
"email_already_requested": "Cette adresse e-mail a déjà été demandée. Veuillez attendre avant de réessayer.",
|
|
49
53
|
"email_already_used": "Cette adresse e-mail est déjà utilisée.",
|
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
Table,
|
|
8
8
|
VisibilityState,
|
|
9
9
|
} from "@tanstack/vue-table";
|
|
10
|
-
import { refDebounced, watchDebounced } from "@vueuse/core";
|
|
10
|
+
import { refDebounced, useActiveElement, watchDebounced } from "@vueuse/core";
|
|
11
11
|
import {
|
|
12
12
|
QUICK_ACTION_BUTTON_KEY,
|
|
13
13
|
QUICK_ACTION_COMPONENT_KEY,
|
|
@@ -1010,6 +1010,7 @@ defineShortcuts(
|
|
|
1010
1010
|
globalFilter,
|
|
1011
1011
|
rowSelect,
|
|
1012
1012
|
data: shownData,
|
|
1013
|
+
activeElement: useActiveElement(),
|
|
1013
1014
|
}),
|
|
1014
1015
|
);
|
|
1015
1016
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { useActiveElement } from "@vueuse/core";
|
|
2
3
|
import type { TreeNode, TreeProps } from "../../composables/tree/types";
|
|
3
4
|
import {
|
|
4
5
|
TreeSelectionBehavior,
|
|
@@ -120,15 +121,15 @@ function navigateTree(direction: TreeNavigationDirection) {
|
|
|
120
121
|
navigationHandlers[direction]();
|
|
121
122
|
}
|
|
122
123
|
|
|
123
|
-
|
|
124
|
-
buildTreeShortcuts
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
})
|
|
131
|
-
|
|
124
|
+
defineShortcuts(
|
|
125
|
+
buildTreeShortcuts({
|
|
126
|
+
navigateTree,
|
|
127
|
+
props,
|
|
128
|
+
items,
|
|
129
|
+
selected,
|
|
130
|
+
activeElement: useActiveElement(),
|
|
131
|
+
}),
|
|
132
|
+
);
|
|
132
133
|
</script>
|
|
133
134
|
|
|
134
135
|
<template>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { computed, type ComputedRef, type ShallowRef } from "vue";
|
|
2
2
|
import type { RowSelectionState } from "@tanstack/vue-table";
|
|
3
3
|
import type {
|
|
4
4
|
TableRowActionOptions,
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
import {
|
|
16
16
|
TABLE_VIEW_ESCAPE_METADATA,
|
|
17
17
|
createTableViewEscapeShortcut,
|
|
18
|
+
isTableViewEscapeActive,
|
|
18
19
|
} from "./tableViewEscape";
|
|
19
20
|
import {
|
|
20
21
|
TABLE_VIEW_SHIFT_A_METADATA,
|
|
@@ -42,8 +43,11 @@ interface BuildTableViewShortcutsParams<T extends Data> {
|
|
|
42
43
|
globalFilter: ShallowRef<string | undefined>;
|
|
43
44
|
rowSelect: Ref<RowSelectionState>;
|
|
44
45
|
data: Ref<{ results: T[] } | null | undefined>;
|
|
46
|
+
activeElement: Ref<Element | null | undefined>;
|
|
45
47
|
}
|
|
46
48
|
|
|
49
|
+
type TableViewShortcuts = Record<string, (() => void) | undefined>;
|
|
50
|
+
|
|
47
51
|
export function buildTableViewShortcuts<T extends Data>({
|
|
48
52
|
refresh,
|
|
49
53
|
tableProps,
|
|
@@ -51,11 +55,20 @@ export function buildTableViewShortcuts<T extends Data>({
|
|
|
51
55
|
globalFilter,
|
|
52
56
|
rowSelect,
|
|
53
57
|
data,
|
|
54
|
-
|
|
55
|
-
|
|
58
|
+
activeElement,
|
|
59
|
+
}: BuildTableViewShortcutsParams<T>): ComputedRef<TableViewShortcuts> {
|
|
60
|
+
const shortcuts = {
|
|
56
61
|
meta_r: createTableViewMetaRShortcut(refresh),
|
|
57
62
|
shift_n: createTableViewShiftNShortcut(tableProps, newRow),
|
|
58
|
-
escape: createTableViewEscapeShortcut(globalFilter, rowSelect),
|
|
59
63
|
shift_a: createTableViewShiftAShortcut(tableProps, data, rowSelect),
|
|
60
64
|
};
|
|
65
|
+
const escape = createTableViewEscapeShortcut(globalFilter, rowSelect);
|
|
66
|
+
// An unregistered key is left untouched by `defineShortcuts`, so Escape
|
|
67
|
+
// reaches overlays whenever the table has nothing to clear.
|
|
68
|
+
return computed(() => ({
|
|
69
|
+
...shortcuts,
|
|
70
|
+
escape: isTableViewEscapeActive(globalFilter, rowSelect, activeElement)
|
|
71
|
+
? escape
|
|
72
|
+
: undefined,
|
|
73
|
+
}));
|
|
61
74
|
}
|
|
@@ -1,12 +1,34 @@
|
|
|
1
1
|
import type { ShallowRef } from "vue";
|
|
2
2
|
import type { RowSelectionState } from "@tanstack/vue-table";
|
|
3
|
+
import { isFocusInsideOverlay } from "../../../utils/overlayFocus";
|
|
3
4
|
|
|
4
5
|
export const TABLE_VIEW_ESCAPE_METADATA: ShortcutMetadata = {
|
|
5
6
|
key: ["$keyboard.escape"],
|
|
6
7
|
descriptionKey: "$dms.shortcuts.tableview.escape.description",
|
|
7
8
|
component: "$dms.components.tableview",
|
|
9
|
+
condition: {
|
|
10
|
+
descriptionKey: "$dms.shortcuts.tableview.escape.condition",
|
|
11
|
+
},
|
|
8
12
|
};
|
|
9
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Nuxt UI's `defineShortcuts` calls `preventDefault()` on every key it
|
|
16
|
+
* handles, and reka-ui's DismissableLayer ignores a prevented Escape. The
|
|
17
|
+
* shortcut is therefore only registered when there is a search or a
|
|
18
|
+
* selection to clear, and never while focus sits inside an overlay, so the
|
|
19
|
+
* overlay above the table keeps closing on Escape.
|
|
20
|
+
*/
|
|
21
|
+
export function isTableViewEscapeActive(
|
|
22
|
+
globalFilter: ShallowRef<string | undefined>,
|
|
23
|
+
rowSelect: Ref<RowSelectionState>,
|
|
24
|
+
activeElement: Ref<Element | null | undefined>,
|
|
25
|
+
): boolean {
|
|
26
|
+
if (isFocusInsideOverlay(activeElement.value)) return false;
|
|
27
|
+
return (
|
|
28
|
+
globalFilter.value !== undefined || Object.keys(rowSelect.value).length > 0
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
10
32
|
export function createTableViewEscapeShortcut(
|
|
11
33
|
globalFilter: ShallowRef<string | undefined>,
|
|
12
34
|
rowSelect: Ref<RowSelectionState>,
|