@openora/core 0.4.1-canary.113 → 0.4.1-canary.115
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/dist/.tsbuildinfo +1 -1
- package/dist/admin-console/plugin.d.ts +11 -0
- package/dist/admin-console/plugin.d.ts.map +1 -1
- package/dist/analytics/plugin.d.ts +11 -0
- package/dist/analytics/plugin.d.ts.map +1 -1
- package/dist/audit/__tests__/map-event.test.js +34 -0
- package/dist/audit/__tests__/map-event.test.js.map +1 -1
- package/dist/audit/plugin.d.ts +11 -0
- package/dist/audit/plugin.d.ts.map +1 -1
- package/dist/audit/plugin.js +63 -7
- package/dist/audit/plugin.js.map +1 -1
- package/dist/casino/gaming/plugin.d.ts +11 -0
- package/dist/casino/gaming/plugin.d.ts.map +1 -1
- package/dist/casino/lobby/plugin.d.ts +11 -0
- package/dist/casino/lobby/plugin.d.ts.map +1 -1
- package/dist/cms/plugin.d.ts +11 -0
- package/dist/cms/plugin.d.ts.map +1 -1
- package/dist/compliance/plugin.d.ts +11 -0
- package/dist/compliance/plugin.d.ts.map +1 -1
- package/dist/contracts/adapters/admin-security.d.ts +27 -0
- package/dist/contracts/adapters/admin-security.d.ts.map +1 -0
- package/dist/contracts/adapters/admin-security.js +9 -0
- package/dist/contracts/adapters/admin-security.js.map +1 -0
- package/dist/contracts/adapters/index.d.ts +3 -1
- package/dist/contracts/adapters/index.d.ts.map +1 -1
- package/dist/contracts/adapters/index.js +1 -0
- package/dist/contracts/adapters/index.js.map +1 -1
- package/dist/contracts/adapters/realtime.d.ts +36 -3
- package/dist/contracts/adapters/realtime.d.ts.map +1 -1
- package/dist/contracts/adapters/realtime.js.map +1 -1
- package/dist/contracts/schemas/common.d.ts +3 -1
- package/dist/contracts/schemas/common.d.ts.map +1 -1
- package/dist/contracts/schemas/common.js +2 -0
- package/dist/contracts/schemas/common.js.map +1 -1
- package/dist/contracts/schemas/events.d.ts +114 -0
- package/dist/contracts/schemas/events.d.ts.map +1 -1
- package/dist/contracts/schemas/events.js +77 -0
- package/dist/contracts/schemas/events.js.map +1 -1
- package/dist/contracts/schemas/identity.d.ts +26 -0
- package/dist/contracts/schemas/identity.d.ts.map +1 -1
- package/dist/contracts/schemas/identity.js +32 -1
- package/dist/contracts/schemas/identity.js.map +1 -1
- package/dist/contracts/schemas/platform-config.d.ts +39 -0
- package/dist/contracts/schemas/platform-config.d.ts.map +1 -1
- package/dist/contracts/schemas/platform-config.js +43 -0
- package/dist/contracts/schemas/platform-config.js.map +1 -1
- package/dist/engagement/chat/__tests__/chat.router.test.d.ts +2 -0
- package/dist/engagement/chat/__tests__/chat.router.test.d.ts.map +1 -0
- package/dist/engagement/chat/__tests__/chat.router.test.js +44 -0
- package/dist/engagement/chat/__tests__/chat.router.test.js.map +1 -0
- package/dist/engagement/chat/__tests__/chat.service.int.test.js +590 -6
- package/dist/engagement/chat/__tests__/chat.service.int.test.js.map +1 -1
- package/dist/engagement/chat/contract/constants.d.ts +2 -0
- package/dist/engagement/chat/contract/constants.d.ts.map +1 -1
- package/dist/engagement/chat/contract/constants.js +7 -0
- package/dist/engagement/chat/contract/constants.js.map +1 -1
- package/dist/engagement/chat/contract/index.d.ts +39 -0
- package/dist/engagement/chat/contract/index.d.ts.map +1 -1
- package/dist/engagement/chat/contract/index.js +25 -1
- package/dist/engagement/chat/contract/index.js.map +1 -1
- package/dist/engagement/chat/drizzle/migrations/0009_massive_bill_hollister.sql +1 -0
- package/dist/engagement/chat/drizzle/migrations/0010_backfill_role_assigned_at.sql +9 -0
- package/dist/engagement/chat/drizzle/migrations/meta/0009_snapshot.json +1468 -0
- package/dist/engagement/chat/drizzle/migrations/meta/0010_snapshot.json +1468 -0
- package/dist/engagement/chat/drizzle/migrations/meta/_journal.json +14 -0
- package/dist/engagement/chat/plugin.d.ts +11 -0
- package/dist/engagement/chat/plugin.d.ts.map +1 -1
- package/dist/engagement/chat/router/index.d.ts +19 -0
- package/dist/engagement/chat/router/index.d.ts.map +1 -1
- package/dist/engagement/chat/router/index.js +31 -2
- package/dist/engagement/chat/router/index.js.map +1 -1
- package/dist/engagement/chat/schema/index.d.ts +17 -0
- package/dist/engagement/chat/schema/index.d.ts.map +1 -1
- package/dist/engagement/chat/schema/index.js +3 -0
- package/dist/engagement/chat/schema/index.js.map +1 -1
- package/dist/engagement/chat/service/chat-ban.service.d.ts.map +1 -1
- package/dist/engagement/chat/service/chat-ban.service.js +6 -6
- package/dist/engagement/chat/service/chat-ban.service.js.map +1 -1
- package/dist/engagement/chat/service/chat-room-ban.service.js +1 -1
- package/dist/engagement/chat/service/chat-room-ban.service.js.map +1 -1
- package/dist/engagement/chat/service/chat-room-membership.service.d.ts +15 -0
- package/dist/engagement/chat/service/chat-room-membership.service.d.ts.map +1 -1
- package/dist/engagement/chat/service/chat-room-membership.service.js +96 -4
- package/dist/engagement/chat/service/chat-room-membership.service.js.map +1 -1
- package/dist/engagement/chat/service/chat.service.d.ts +3 -2
- package/dist/engagement/chat/service/chat.service.d.ts.map +1 -1
- package/dist/engagement/chat/service/chat.service.js +73 -24
- package/dist/engagement/chat/service/chat.service.js.map +1 -1
- package/dist/engagement/chat/service/errors/chat-moderation.errors.d.ts +1 -0
- package/dist/engagement/chat/service/errors/chat-moderation.errors.d.ts.map +1 -1
- package/dist/engagement/chat/service/errors/chat-moderation.errors.js +1 -0
- package/dist/engagement/chat/service/errors/chat-moderation.errors.js.map +1 -1
- package/dist/engagement/chat-commands/plugin.d.ts +11 -0
- package/dist/engagement/chat-commands/plugin.d.ts.map +1 -1
- package/dist/engagement/notifications/plugin.d.ts +11 -0
- package/dist/engagement/notifications/plugin.d.ts.map +1 -1
- package/dist/engagement/social/plugin.d.ts +11 -0
- package/dist/engagement/social/plugin.d.ts.map +1 -1
- package/dist/iam/plugin.d.ts +11 -0
- package/dist/iam/plugin.d.ts.map +1 -1
- package/dist/pam/identity/__tests__/admin-security.int.test.d.ts +2 -0
- package/dist/pam/identity/__tests__/admin-security.int.test.d.ts.map +1 -0
- package/dist/pam/identity/__tests__/admin-security.int.test.js +137 -0
- package/dist/pam/identity/__tests__/admin-security.int.test.js.map +1 -0
- package/dist/pam/identity/__tests__/device-fingerprint.test.d.ts +2 -0
- package/dist/pam/identity/__tests__/device-fingerprint.test.d.ts.map +1 -0
- package/dist/pam/identity/__tests__/device-fingerprint.test.js +105 -0
- package/dist/pam/identity/__tests__/device-fingerprint.test.js.map +1 -0
- package/dist/pam/identity/__tests__/identity.rate-limit.int.test.js +3 -3
- package/dist/pam/identity/__tests__/identity.rate-limit.int.test.js.map +1 -1
- package/dist/pam/identity/__tests__/identity.service.int.test.js +221 -1
- package/dist/pam/identity/__tests__/identity.service.int.test.js.map +1 -1
- package/dist/pam/identity/contract/index.d.ts +144 -0
- package/dist/pam/identity/contract/index.d.ts.map +1 -1
- package/dist/pam/identity/contract/index.js +84 -1
- package/dist/pam/identity/contract/index.js.map +1 -1
- package/dist/pam/identity/drizzle/migrations/0008_pink_bucky.sql +23 -0
- package/dist/pam/identity/drizzle/migrations/0009_hard_sauron.sql +2 -0
- package/dist/pam/identity/drizzle/migrations/meta/0008_snapshot.json +913 -0
- package/dist/pam/identity/drizzle/migrations/meta/0009_snapshot.json +926 -0
- package/dist/pam/identity/drizzle/migrations/meta/_journal.json +14 -0
- package/dist/pam/identity/plugin.d.ts +11 -0
- package/dist/pam/identity/plugin.d.ts.map +1 -1
- package/dist/pam/identity/plugin.js +81 -26
- package/dist/pam/identity/plugin.js.map +1 -1
- package/dist/pam/identity/react/account.d.ts +5 -0
- package/dist/pam/identity/react/account.d.ts.map +1 -1
- package/dist/pam/identity/react/auth.d.ts +2 -0
- package/dist/pam/identity/react/auth.d.ts.map +1 -1
- package/dist/pam/identity/router/index.d.ts +105 -1
- package/dist/pam/identity/router/index.d.ts.map +1 -1
- package/dist/pam/identity/router/index.js +44 -3
- package/dist/pam/identity/router/index.js.map +1 -1
- package/dist/pam/identity/schema/index.d.ts +314 -0
- package/dist/pam/identity/schema/index.d.ts.map +1 -1
- package/dist/pam/identity/schema/index.js +38 -0
- package/dist/pam/identity/schema/index.js.map +1 -1
- package/dist/pam/identity/service/admin-security.service.d.ts +82 -0
- package/dist/pam/identity/service/admin-security.service.d.ts.map +1 -0
- package/dist/pam/identity/service/admin-security.service.js +277 -0
- package/dist/pam/identity/service/admin-security.service.js.map +1 -0
- package/dist/pam/identity/service/device-fingerprint.service.d.ts +32 -0
- package/dist/pam/identity/service/device-fingerprint.service.d.ts.map +1 -0
- package/dist/pam/identity/service/device-fingerprint.service.js +79 -0
- package/dist/pam/identity/service/device-fingerprint.service.js.map +1 -0
- package/dist/pam/identity/service/identity.service.d.ts +37 -2
- package/dist/pam/identity/service/identity.service.d.ts.map +1 -1
- package/dist/pam/identity/service/identity.service.js +233 -19
- package/dist/pam/identity/service/identity.service.js.map +1 -1
- package/dist/pam/identity/service/phone-login.service.d.ts +1 -0
- package/dist/pam/identity/service/phone-login.service.d.ts.map +1 -1
- package/dist/pam/identity/service/phone-login.service.js +2 -0
- package/dist/pam/identity/service/phone-login.service.js.map +1 -1
- package/dist/pam/identity/service/session.service.d.ts +45 -1
- package/dist/pam/identity/service/session.service.d.ts.map +1 -1
- package/dist/pam/identity/service/session.service.js +81 -12
- package/dist/pam/identity/service/session.service.js.map +1 -1
- package/dist/pam/identity/service/trusted-device.service.d.ts +54 -0
- package/dist/pam/identity/service/trusted-device.service.d.ts.map +1 -0
- package/dist/pam/identity/service/trusted-device.service.js +157 -0
- package/dist/pam/identity/service/trusted-device.service.js.map +1 -0
- package/dist/pam/identity/service/two-factor-lockout.service.d.ts +25 -0
- package/dist/pam/identity/service/two-factor-lockout.service.d.ts.map +1 -0
- package/dist/pam/identity/service/two-factor-lockout.service.js +121 -0
- package/dist/pam/identity/service/two-factor-lockout.service.js.map +1 -0
- package/dist/pam/player-management/plugin.d.ts +11 -0
- package/dist/pam/player-management/plugin.d.ts.map +1 -1
- package/dist/pam/player-note/plugin.d.ts +11 -0
- package/dist/pam/player-note/plugin.d.ts.map +1 -1
- package/dist/pam/profile/plugin.d.ts +11 -0
- package/dist/pam/profile/plugin.d.ts.map +1 -1
- package/dist/pam/tag/plugin.d.ts +11 -0
- package/dist/pam/tag/plugin.d.ts.map +1 -1
- package/dist/react/context/__tests__/realtime-client.test.d.ts +2 -0
- package/dist/react/context/__tests__/realtime-client.test.d.ts.map +1 -0
- package/dist/react/context/__tests__/realtime-client.test.js +75 -0
- package/dist/react/context/__tests__/realtime-client.test.js.map +1 -0
- package/dist/react/context/realtime-client.d.ts +14 -0
- package/dist/react/context/realtime-client.d.ts.map +1 -1
- package/dist/react/context/realtime-client.js.map +1 -1
- package/dist/server/auth/__tests__/admin-guard.int.test.js +3 -1
- package/dist/server/auth/__tests__/admin-guard.int.test.js.map +1 -1
- package/dist/server/auth/admin-guard.d.ts +3 -2
- package/dist/server/auth/admin-guard.d.ts.map +1 -1
- package/dist/server/auth/admin-guard.js +18 -2
- package/dist/server/auth/admin-guard.js.map +1 -1
- package/dist/server/auth/auth.d.ts +14 -0
- package/dist/server/auth/auth.d.ts.map +1 -1
- package/dist/server/auth/auth.js +18 -1
- package/dist/server/auth/auth.js.map +1 -1
- package/dist/server/kernel/realtime-transport.d.ts +11 -3
- package/dist/server/kernel/realtime-transport.d.ts.map +1 -1
- package/dist/server/kernel/realtime-transport.js +53 -10
- package/dist/server/kernel/realtime-transport.js.map +1 -1
- package/dist/server/runtime/core-token-catalog.d.ts +11 -0
- package/dist/server/runtime/core-token-catalog.d.ts.map +1 -1
- package/dist/server/runtime/core-token-catalog.js +2 -1
- package/dist/server/runtime/core-token-catalog.js.map +1 -1
- package/dist/server/runtime/create-app.d.ts.map +1 -1
- package/dist/server/runtime/create-app.js +2 -2
- package/dist/server/runtime/create-app.js.map +1 -1
- package/dist/testing/seed-player.d.ts +10 -2
- package/dist/testing/seed-player.d.ts.map +1 -1
- package/dist/wallet/plugin.d.ts +11 -0
- package/dist/wallet/plugin.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-fingerprint.test.d.ts","sourceRoot":"","sources":["../../../../src/pam/identity/__tests__/device-fingerprint.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { describeDevice, deviceHash, isSameDevice, isSuspiciousIpChange, normalizeUserAgent, trustedDeviceExpiry, } from '../service/device-fingerprint.service.js';
|
|
3
|
+
const CHROME_MAC = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.86 Safari/537.36';
|
|
4
|
+
const CHROME_MAC_NEXT_BUILD = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.140 Safari/537.36';
|
|
5
|
+
const CHROME_MAC_NEXT_MAJOR = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.83 Safari/537.36';
|
|
6
|
+
const FIREFOX_WINDOWS = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0';
|
|
7
|
+
describe('normalizeUserAgent', () => {
|
|
8
|
+
it('keeps the major version and drops the rest', () => {
|
|
9
|
+
expect(normalizeUserAgent('Chrome/131.0.6778.86')).toBe('chrome/131');
|
|
10
|
+
});
|
|
11
|
+
it('maps a missing user agent to an empty fingerprint', () => {
|
|
12
|
+
expect(normalizeUserAgent(null)).toBe('');
|
|
13
|
+
expect(normalizeUserAgent(undefined)).toBe('');
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
describe('isSameDevice', () => {
|
|
17
|
+
it('survives a patch-level browser auto-update', () => {
|
|
18
|
+
expect(isSameDevice(CHROME_MAC, CHROME_MAC_NEXT_BUILD)).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
it('treats a different browser as a different device', () => {
|
|
21
|
+
expect(isSameDevice(CHROME_MAC, FIREFOX_WINDOWS)).toBe(false);
|
|
22
|
+
});
|
|
23
|
+
it('treats a major-version jump as a different device', () => {
|
|
24
|
+
expect(isSameDevice(CHROME_MAC, CHROME_MAC_NEXT_MAJOR)).toBe(false);
|
|
25
|
+
});
|
|
26
|
+
it('matches two sessions that both carry no user agent', () => {
|
|
27
|
+
expect(isSameDevice(null, null)).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
describe('deviceHash', () => {
|
|
31
|
+
it('is stable across a patch-level update and differs across browsers', () => {
|
|
32
|
+
expect(deviceHash(CHROME_MAC)).toBe(deviceHash(CHROME_MAC_NEXT_BUILD));
|
|
33
|
+
expect(deviceHash(CHROME_MAC)).not.toBe(deviceHash(FIREFOX_WINDOWS));
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
describe('describeDevice', () => {
|
|
37
|
+
it('renders browser and operating system', () => {
|
|
38
|
+
expect(describeDevice(CHROME_MAC)).toEqual({
|
|
39
|
+
label: 'Chrome on macOS',
|
|
40
|
+
browser: 'Chrome',
|
|
41
|
+
os: 'macOS',
|
|
42
|
+
});
|
|
43
|
+
expect(describeDevice(FIREFOX_WINDOWS)).toEqual({
|
|
44
|
+
label: 'Firefox on Windows',
|
|
45
|
+
browser: 'Firefox',
|
|
46
|
+
os: 'Windows',
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
it('does not report Chrome for an Edge user agent', () => {
|
|
50
|
+
const edge = `${CHROME_MAC} Edg/131.0.2903.86`;
|
|
51
|
+
expect(describeDevice(edge).browser).toBe('Edge');
|
|
52
|
+
});
|
|
53
|
+
it('falls back to a placeholder when nothing is recognisable', () => {
|
|
54
|
+
expect(describeDevice(null)).toEqual({ label: 'Unknown device', browser: null, os: null });
|
|
55
|
+
expect(describeDevice('curl/8.4.0')).toEqual({
|
|
56
|
+
label: 'Unknown device',
|
|
57
|
+
browser: null,
|
|
58
|
+
os: null,
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
describe('isSuspiciousIpChange', () => {
|
|
63
|
+
const base = { sessionIp: '10.0.0.1', requestIp: '10.0.0.2' };
|
|
64
|
+
it('ignores every change while the policy is off', () => {
|
|
65
|
+
expect(isSuspiciousIpChange({ ...base, policy: 'off' })).toBe(false);
|
|
66
|
+
});
|
|
67
|
+
it('flags any change under the any policy', () => {
|
|
68
|
+
expect(isSuspiciousIpChange({ ...base, policy: 'any' })).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
it('ignores an unchanged address under every policy', () => {
|
|
71
|
+
expect(isSuspiciousIpChange({ policy: 'any', sessionIp: '10.0.0.1', requestIp: '10.0.0.1' })).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
it('flags a country hop and tolerates a same-country hop', () => {
|
|
74
|
+
expect(isSuspiciousIpChange({
|
|
75
|
+
...base,
|
|
76
|
+
policy: 'country',
|
|
77
|
+
sessionCountry: 'PL',
|
|
78
|
+
requestCountry: 'RU',
|
|
79
|
+
})).toBe(true);
|
|
80
|
+
expect(isSuspiciousIpChange({
|
|
81
|
+
...base,
|
|
82
|
+
policy: 'country',
|
|
83
|
+
sessionCountry: 'PL',
|
|
84
|
+
requestCountry: 'pl',
|
|
85
|
+
})).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
it('does not end a session when the country cannot be resolved', () => {
|
|
88
|
+
expect(isSuspiciousIpChange({
|
|
89
|
+
...base,
|
|
90
|
+
policy: 'country',
|
|
91
|
+
sessionCountry: null,
|
|
92
|
+
requestCountry: 'RU',
|
|
93
|
+
})).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
it('does not end a session when either address is unknown', () => {
|
|
96
|
+
expect(isSuspiciousIpChange({ policy: 'any', sessionIp: null, requestIp: '10.0.0.2' })).toBe(false);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
describe('trustedDeviceExpiry', () => {
|
|
100
|
+
it('adds the configured number of days to the grant date', () => {
|
|
101
|
+
const now = new Date('2026-08-25T00:00:00.000Z');
|
|
102
|
+
expect(trustedDeviceExpiry(30, now).toISOString()).toBe('2026-09-24T00:00:00.000Z');
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
//# sourceMappingURL=device-fingerprint.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-fingerprint.test.js","sourceRoot":"","sources":["../../../../src/pam/identity/__tests__/device-fingerprint.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EACL,cAAc,EACd,UAAU,EACV,YAAY,EACZ,oBAAoB,EACpB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,0CAA0C,CAAC;AAElD,MAAM,UAAU,GACd,2HAA2H,CAAC;AAC9H,MAAM,qBAAqB,GACzB,4HAA4H,CAAC;AAC/H,MAAM,qBAAqB,GACzB,2HAA2H,CAAC;AAC9H,MAAM,eAAe,GACnB,kFAAkF,CAAC;AAErF,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;IAClC,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,CAAC,kBAAkB,CAAC,sBAAsB,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC1C,MAAM,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,CAAC,YAAY,CAAC,UAAU,EAAE,qBAAqB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,CAAC,YAAY,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,CAAC,YAAY,CAAC,UAAU,EAAE,qBAAqB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACtE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,EAAE,CAAC,mEAAmE,EAAE,GAAG,EAAE;QAC3E,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,qBAAqB,CAAC,CAAC,CAAC;QACvE,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC,CAAC;IACvE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC;YACzC,KAAK,EAAE,iBAAiB;YACxB,OAAO,EAAE,QAAQ;YACjB,EAAE,EAAE,OAAO;SACZ,CAAC,CAAC;QACH,MAAM,CAAC,cAAc,CAAC,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC;YAC9C,KAAK,EAAE,oBAAoB;YAC3B,OAAO,EAAE,SAAS;YAClB,EAAE,EAAE,SAAS;SACd,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,IAAI,GAAG,GAAG,UAAU,oBAAoB,CAAC;QAC/C,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3F,MAAM,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC;YAC3C,KAAK,EAAE,gBAAgB;YACvB,OAAO,EAAE,IAAI;YACb,EAAE,EAAE,IAAI;SACT,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,MAAM,IAAI,GAAG,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC;IAE9D,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,CAAC,oBAAoB,CAAC,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACvE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,CAAC,oBAAoB,CAAC,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,CACJ,oBAAoB,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC,CACtF,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAC9D,MAAM,CACJ,oBAAoB,CAAC;YACnB,GAAG,IAAI;YACP,MAAM,EAAE,SAAS;YACjB,cAAc,EAAE,IAAI;YACpB,cAAc,EAAE,IAAI;SACrB,CAAC,CACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACb,MAAM,CACJ,oBAAoB,CAAC;YACnB,GAAG,IAAI;YACP,MAAM,EAAE,SAAS;YACjB,cAAc,EAAE,IAAI;YACpB,cAAc,EAAE,IAAI;SACrB,CAAC,CACH,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4DAA4D,EAAE,GAAG,EAAE;QACpE,MAAM,CACJ,oBAAoB,CAAC;YACnB,GAAG,IAAI;YACP,MAAM,EAAE,SAAS;YACjB,cAAc,EAAE,IAAI;YACpB,cAAc,EAAE,IAAI;SACrB,CAAC,CACH,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,MAAM,CAAC,oBAAoB,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,IAAI,CAC1F,KAAK,CACN,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAC9D,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,0BAA0B,CAAC,CAAC;QACjD,MAAM,CAAC,mBAAmB,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;IACtF,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -97,7 +97,7 @@ describe('IdentityService - verify2fa rate-limit key stability (ABC-208 finding
|
|
|
97
97
|
const svc = withTemplateRenderer({ drizzle, events, limiter });
|
|
98
98
|
// Same two_factor cookie + different junk pairs must still hit the one exhausted bucket.
|
|
99
99
|
for (let i = 0; i < 3; i++) {
|
|
100
|
-
await expect(svc.verifyTwoFactor({ code: '123456' }, { cookie: `better-auth.two_factor=${twoFactorIdentifier}; junk${i}=${i}` }, new Headers())).rejects.toMatchObject({ code: 'TOO_MANY_REQUESTS' });
|
|
100
|
+
await expect(svc.verifyTwoFactor({ code: '123456', method: 'totp', trustDevice: false }, { cookie: `better-auth.two_factor=${twoFactorIdentifier}; junk${i}=${i}` }, new Headers())).rejects.toMatchObject({ code: 'TOO_MANY_REQUESTS' });
|
|
101
101
|
}
|
|
102
102
|
});
|
|
103
103
|
});
|
|
@@ -143,7 +143,7 @@ describe('IdentityService - rate limiting on secret-guessing routes (ABC-208 fin
|
|
|
143
143
|
await limiter.consume('disable2fa:anonymous', { limit: 5, windowMs: 5 * 60 * 1000 });
|
|
144
144
|
}
|
|
145
145
|
const svc = withTemplateRenderer({ drizzle, events, limiter });
|
|
146
|
-
await expect(svc.disableTwoFactor({ password: 'currentpw1' }, {}, new Headers())).rejects.toMatchObject({ code: 'TOO_MANY_REQUESTS' });
|
|
146
|
+
await expect(svc.disableTwoFactor({ password: 'currentpw1', code: '123456' }, {}, new Headers())).rejects.toMatchObject({ code: 'TOO_MANY_REQUESTS' });
|
|
147
147
|
});
|
|
148
148
|
});
|
|
149
149
|
describe('IdentityService - fail-closed limiter policy for credential-guessing keys', () => {
|
|
@@ -164,7 +164,7 @@ describe('IdentityService - fail-closed limiter policy for credential-guessing k
|
|
|
164
164
|
it('passes onUnavailable: deny on the verify2fa: key', async () => {
|
|
165
165
|
const { limiter, consume } = denyingLimiter();
|
|
166
166
|
const svc = withTemplateRenderer({ drizzle, events, limiter });
|
|
167
|
-
await expect(svc.verifyTwoFactor({ code: '123456' }, {}, new Headers())).rejects.toMatchObject({
|
|
167
|
+
await expect(svc.verifyTwoFactor({ code: '123456', method: 'totp', trustDevice: false }, {}, new Headers())).rejects.toMatchObject({
|
|
168
168
|
code: 'TOO_MANY_REQUESTS',
|
|
169
169
|
});
|
|
170
170
|
expect(consume).toHaveBeenCalledWith(expect.stringMatching(/^verify2fa:/), expect.objectContaining({ onUnavailable: 'deny' }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"identity.rate-limit.int.test.js","sourceRoot":"","sources":["../../../../src/pam/identity/__tests__/identity.rate-limit.int.test.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"identity.rate-limit.int.test.js","sourceRoot":"","sources":["../../../../src/pam/identity/__tests__/identity.rate-limit.int.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,9 +9,15 @@ import { migrate as migrateProfile } from '@openora/core/pam/migrate/profile';
|
|
|
9
9
|
import { IdentityService, SESSION_DURATION_IN_SECONDS, } from '../service/identity.service.js';
|
|
10
10
|
import { UnsupportedLanguageError } from '../../shared/language.js';
|
|
11
11
|
import { user, session } from '../schema/index.js';
|
|
12
|
+
import { TrustedDeviceService } from '../service/trusted-device.service.js';
|
|
13
|
+
import { SessionService } from '../service/session.service.js';
|
|
12
14
|
import { makeIdentityReader, mock, makeEventBus } from '../../../testing/mock.js';
|
|
13
|
-
const { signInEmailMock, getSessionMock, updateUserMock, requestPasswordResetEmailOTPMock, checkVerificationOTPMock, resetPasswordEmailOTPMock, capturedAuthOptions, } = vi.hoisted(() => ({
|
|
15
|
+
const { signInEmailMock, verifyTotpMock, verifyBackupCodeMock, generateBackupCodesMock, disableTwoFactorMock, getSessionMock, updateUserMock, requestPasswordResetEmailOTPMock, checkVerificationOTPMock, resetPasswordEmailOTPMock, capturedAuthOptions, } = vi.hoisted(() => ({
|
|
14
16
|
signInEmailMock: vi.fn(),
|
|
17
|
+
verifyTotpMock: vi.fn(),
|
|
18
|
+
verifyBackupCodeMock: vi.fn(),
|
|
19
|
+
generateBackupCodesMock: vi.fn(),
|
|
20
|
+
disableTwoFactorMock: vi.fn(),
|
|
15
21
|
getSessionMock: vi.fn().mockResolvedValue(null),
|
|
16
22
|
updateUserMock: vi.fn(),
|
|
17
23
|
requestPasswordResetEmailOTPMock: vi.fn(),
|
|
@@ -35,6 +41,10 @@ vi.mock('@openora/core/server', async (importOriginal) => ({
|
|
|
35
41
|
getSession: getSessionMock,
|
|
36
42
|
signUpEmail: vi.fn(),
|
|
37
43
|
signInEmail: signInEmailMock,
|
|
44
|
+
verifyTOTP: verifyTotpMock,
|
|
45
|
+
verifyBackupCode: verifyBackupCodeMock,
|
|
46
|
+
generateBackupCodes: generateBackupCodesMock,
|
|
47
|
+
disableTwoFactor: disableTwoFactorMock,
|
|
38
48
|
signOut: vi.fn(),
|
|
39
49
|
updateUser: updateUserMock,
|
|
40
50
|
requestPasswordResetEmailOTP: requestPasswordResetEmailOTPMock,
|
|
@@ -423,6 +433,52 @@ describe('IdentityService - player-status login gate (real PG)', () => {
|
|
|
423
433
|
expect(events.emit).not.toHaveBeenCalledWith('player.login_blocked', expect.anything());
|
|
424
434
|
});
|
|
425
435
|
});
|
|
436
|
+
describe('IdentityService - trusted device login (real PG)', () => {
|
|
437
|
+
const TRUSTED_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
|
438
|
+
const trustedHeaders = {
|
|
439
|
+
cookie: 'better-auth.session_token=tok; better-auth.trust_device=signed-trust-value',
|
|
440
|
+
'user-agent': TRUSTED_UA,
|
|
441
|
+
};
|
|
442
|
+
const honourTrustCookie = (userId) => signInEmailMock.mockImplementation(async ({ headers }) => headers.get('cookie')?.includes('.trust_device')
|
|
443
|
+
? signInSuccess(userId)
|
|
444
|
+
: jsonResponse({ twoFactorRedirect: true }, 200));
|
|
445
|
+
const makeTrustedDevices = () => new TrustedDeviceService({
|
|
446
|
+
drizzle: db.drizzle,
|
|
447
|
+
events: makeEventBus(),
|
|
448
|
+
trustedDeviceDays: 30,
|
|
449
|
+
});
|
|
450
|
+
it('signs in without a second factor while the trusted-device row is live', async () => {
|
|
451
|
+
const account = await seedUser();
|
|
452
|
+
const trustedDevices = makeTrustedDevices();
|
|
453
|
+
await trustedDevices.trust(account.id, { ip: null, userAgent: TRUSTED_UA });
|
|
454
|
+
honourTrustCookie(account.id);
|
|
455
|
+
const svc = buildService({ trustedDevices });
|
|
456
|
+
const result = await svc.login({ email: EMAIL, password: 'rightpass1' }, trustedHeaders, new Headers());
|
|
457
|
+
expect(result).toMatchObject({ session: { token: 'tok' } });
|
|
458
|
+
});
|
|
459
|
+
it('challenges again once the trusted device is revoked', async () => {
|
|
460
|
+
const account = await seedUser();
|
|
461
|
+
const trustedDevices = makeTrustedDevices();
|
|
462
|
+
const device = await trustedDevices.trust(account.id, { ip: null, userAgent: TRUSTED_UA });
|
|
463
|
+
if (!device) {
|
|
464
|
+
throw new Error('trust() stored no device');
|
|
465
|
+
}
|
|
466
|
+
await trustedDevices.revoke(account.id, device.id, account.id);
|
|
467
|
+
honourTrustCookie(account.id);
|
|
468
|
+
const svc = buildService({ trustedDevices });
|
|
469
|
+
const result = await svc.login({ email: EMAIL, password: 'rightpass1' }, trustedHeaders, new Headers());
|
|
470
|
+
expect(result).toEqual({ twoFactorRedirect: true });
|
|
471
|
+
});
|
|
472
|
+
it('challenges a device whose cookie was replayed from another browser', async () => {
|
|
473
|
+
const account = await seedUser();
|
|
474
|
+
const trustedDevices = makeTrustedDevices();
|
|
475
|
+
await trustedDevices.trust(account.id, { ip: null, userAgent: TRUSTED_UA });
|
|
476
|
+
honourTrustCookie(account.id);
|
|
477
|
+
const svc = buildService({ trustedDevices });
|
|
478
|
+
const result = await svc.login({ email: EMAIL, password: 'rightpass1' }, { ...trustedHeaders, 'user-agent': 'Mozilla/5.0 (Macintosh) Safari/605.1.15' }, new Headers());
|
|
479
|
+
expect(result).toEqual({ twoFactorRedirect: true });
|
|
480
|
+
});
|
|
481
|
+
});
|
|
426
482
|
describe('IdentityService.unlockUser (real PG)', () => {
|
|
427
483
|
it('clears the lockout row and emits unlocked with the prior state', async () => {
|
|
428
484
|
const lockedUntil = new Date('2020-01-01T00:00:00.000Z');
|
|
@@ -583,4 +639,168 @@ describe('IdentityService.updateProfile language validation', () => {
|
|
|
583
639
|
expect(result.user.language).toBe('fr');
|
|
584
640
|
});
|
|
585
641
|
});
|
|
642
|
+
describe('IdentityService.verifyTwoFactor', () => {
|
|
643
|
+
const okResponse = () => jsonResponse({ status: true }, 200);
|
|
644
|
+
const pendingChallenge = (userId) => mock({
|
|
645
|
+
resolvePendingUserId: vi.fn(async () => userId),
|
|
646
|
+
assertNotLocked: vi.fn(async () => undefined),
|
|
647
|
+
recordFailure: vi.fn(async () => undefined),
|
|
648
|
+
reset: vi.fn(async () => undefined),
|
|
649
|
+
});
|
|
650
|
+
it('spends a backup code through better-auth instead of the authenticator', async () => {
|
|
651
|
+
const account = await seedUser();
|
|
652
|
+
verifyBackupCodeMock.mockResolvedValue(okResponse());
|
|
653
|
+
const events = makeEventBus();
|
|
654
|
+
const svc = buildService({ events, twoFactorLockout: pendingChallenge(account.id) });
|
|
655
|
+
await svc.verifyTwoFactor({ code: 'lH2MN-bvPJb', method: 'backup_code', trustDevice: false }, {}, new Headers());
|
|
656
|
+
expect(verifyBackupCodeMock).toHaveBeenCalledWith(expect.objectContaining({ body: { code: 'lH2MN-bvPJb', trustDevice: false } }));
|
|
657
|
+
expect(verifyTotpMock).not.toHaveBeenCalled();
|
|
658
|
+
expect(events.emit).toHaveBeenCalledWith('identity.2fa.verified', expect.objectContaining({ userId: account.id, method: 'backup_code' }));
|
|
659
|
+
});
|
|
660
|
+
it('counts a rejected backup code against the lockout', async () => {
|
|
661
|
+
const account = await seedUser();
|
|
662
|
+
verifyBackupCodeMock.mockResolvedValue(jsonResponse({ message: 'Invalid' }, 401));
|
|
663
|
+
const lockout = pendingChallenge(account.id);
|
|
664
|
+
const svc = buildService({ twoFactorLockout: lockout });
|
|
665
|
+
await expect(svc.verifyTwoFactor({ code: 'lH2MN-bvPJb', method: 'backup_code', trustDevice: false }, {}, new Headers())).rejects.toMatchObject({ code: 'UNAUTHORIZED' });
|
|
666
|
+
expect(lockout.recordFailure).toHaveBeenCalledWith(account.id, expect.anything());
|
|
667
|
+
});
|
|
668
|
+
it('announces enrolment only for the challenge answered from a live session', async () => {
|
|
669
|
+
const account = await seedUser();
|
|
670
|
+
verifyTotpMock.mockResolvedValue(okResponse());
|
|
671
|
+
getSessionMock.mockResolvedValue({ user: { ...betterAuthUser, id: account.id } });
|
|
672
|
+
const enrolment = makeEventBus();
|
|
673
|
+
await buildService({ events: enrolment }).verifyTwoFactor({ code: '123456', method: 'totp', trustDevice: false }, {}, new Headers());
|
|
674
|
+
expect(enrolment.emit).toHaveBeenCalledWith('identity.2fa.enabled', expect.objectContaining({ userId: account.id }));
|
|
675
|
+
getSessionMock.mockResolvedValue(null);
|
|
676
|
+
const signIn = makeEventBus();
|
|
677
|
+
await buildService({
|
|
678
|
+
events: signIn,
|
|
679
|
+
twoFactorLockout: pendingChallenge(account.id),
|
|
680
|
+
}).verifyTwoFactor({ code: '123456', method: 'totp', trustDevice: false }, {}, new Headers());
|
|
681
|
+
expect(signIn.emit).toHaveBeenCalledWith('identity.2fa.verified', expect.objectContaining({ userId: account.id }));
|
|
682
|
+
expect(signIn.emit).not.toHaveBeenCalledWith('identity.2fa.enabled', expect.anything());
|
|
683
|
+
});
|
|
684
|
+
});
|
|
685
|
+
describe('IdentityService 2fa step-up teardown', () => {
|
|
686
|
+
const BROWSER_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
|
687
|
+
const okResponse = () => jsonResponse({ status: true }, 200);
|
|
688
|
+
const seedSession = (userId) => db.drizzle.db
|
|
689
|
+
.insert(session)
|
|
690
|
+
.values({
|
|
691
|
+
userId,
|
|
692
|
+
token: randomUUID(),
|
|
693
|
+
userAgent: BROWSER_UA,
|
|
694
|
+
expiresAt: new Date(Date.now() + 86_400_000),
|
|
695
|
+
updatedAt: new Date(),
|
|
696
|
+
})
|
|
697
|
+
.returning({ id: session.id })
|
|
698
|
+
.then(([row]) => row?.id ?? '');
|
|
699
|
+
const buildTeardownDeps = (userId) => {
|
|
700
|
+
const events = makeEventBus();
|
|
701
|
+
const trustedDevices = new TrustedDeviceService({
|
|
702
|
+
drizzle: db.drizzle,
|
|
703
|
+
events: makeEventBus(),
|
|
704
|
+
trustedDeviceDays: 30,
|
|
705
|
+
});
|
|
706
|
+
const sessions = new SessionService({
|
|
707
|
+
drizzle: db.drizzle,
|
|
708
|
+
events: makeEventBus(),
|
|
709
|
+
identityReader: makeIdentityReader(),
|
|
710
|
+
});
|
|
711
|
+
getSessionMock.mockResolvedValue({ user: { ...betterAuthUser, id: userId } });
|
|
712
|
+
return { events, trustedDevices, sessions };
|
|
713
|
+
};
|
|
714
|
+
it('does not buy the trust window for a spent backup code', async () => {
|
|
715
|
+
const account = await seedUser();
|
|
716
|
+
verifyBackupCodeMock.mockResolvedValue(okResponse());
|
|
717
|
+
const { events, trustedDevices } = buildTeardownDeps(account.id);
|
|
718
|
+
getSessionMock.mockResolvedValue(null);
|
|
719
|
+
const lockout = mock({
|
|
720
|
+
resolvePendingUserId: vi.fn(async () => account.id),
|
|
721
|
+
assertNotLocked: vi.fn(async () => undefined),
|
|
722
|
+
recordFailure: vi.fn(async () => undefined),
|
|
723
|
+
reset: vi.fn(async () => undefined),
|
|
724
|
+
});
|
|
725
|
+
await buildService({ events, trustedDevices, twoFactorLockout: lockout }).verifyTwoFactor({ code: 'lH2MN-bvPJb', method: 'backup_code', trustDevice: true }, { 'user-agent': BROWSER_UA }, new Headers());
|
|
726
|
+
expect(verifyBackupCodeMock).toHaveBeenCalledWith(expect.objectContaining({ body: { code: 'lH2MN-bvPJb', trustDevice: false } }));
|
|
727
|
+
expect(await trustedDevices.isTrusted(account.id, BROWSER_UA)).toBe(false);
|
|
728
|
+
});
|
|
729
|
+
it('disableTwoFactor takes a fresh authenticator code, then drops trust and sessions', async () => {
|
|
730
|
+
const account = await seedUser({ twoFactorEnabled: true });
|
|
731
|
+
const { events, trustedDevices, sessions } = buildTeardownDeps(account.id);
|
|
732
|
+
await trustedDevices.trust(account.id, { ip: null, userAgent: BROWSER_UA });
|
|
733
|
+
const liveSession = await seedSession(account.id);
|
|
734
|
+
verifyTotpMock.mockResolvedValue(okResponse());
|
|
735
|
+
disableTwoFactorMock.mockResolvedValue(okResponse());
|
|
736
|
+
const svc = buildService({ events, trustedDevices, sessions });
|
|
737
|
+
await svc.disableTwoFactor({ password: 'rightpass1', code: '123456' }, { cookie: 'better-auth.session_token=live', 'user-agent': BROWSER_UA }, new Headers());
|
|
738
|
+
expect(verifyTotpMock).toHaveBeenCalledWith(expect.objectContaining({ body: { code: '123456', trustDevice: false } }));
|
|
739
|
+
expect(await trustedDevices.isTrusted(account.id, BROWSER_UA)).toBe(false);
|
|
740
|
+
const [row] = await db.drizzle.db
|
|
741
|
+
.select({ expiresAt: session.expiresAt })
|
|
742
|
+
.from(session)
|
|
743
|
+
.where(eq(session.id, liveSession));
|
|
744
|
+
expect(row && row.expiresAt.getTime() > Date.now()).toBe(false);
|
|
745
|
+
expect(events.emit).toHaveBeenCalledWith('identity.2fa.disabled', expect.anything());
|
|
746
|
+
});
|
|
747
|
+
it('rejects disableTwoFactor when the authenticator code is wrong, before better-auth is called', async () => {
|
|
748
|
+
const account = await seedUser({ twoFactorEnabled: true });
|
|
749
|
+
const { events, trustedDevices, sessions } = buildTeardownDeps(account.id);
|
|
750
|
+
verifyTotpMock.mockResolvedValue(jsonResponse({ message: 'Invalid' }, 401));
|
|
751
|
+
const svc = buildService({ events, trustedDevices, sessions });
|
|
752
|
+
await expect(svc.disableTwoFactor({ password: 'rightpass1', code: '000000' }, { cookie: 'better-auth.session_token=live' }, new Headers())).rejects.toMatchObject({ code: 'UNAUTHORIZED' });
|
|
753
|
+
expect(disableTwoFactorMock).not.toHaveBeenCalled();
|
|
754
|
+
});
|
|
755
|
+
it('regenerateBackupCodes takes a fresh authenticator code and revokes old trust', async () => {
|
|
756
|
+
const account = await seedUser({ twoFactorEnabled: true });
|
|
757
|
+
const { events, trustedDevices, sessions } = buildTeardownDeps(account.id);
|
|
758
|
+
await trustedDevices.trust(account.id, { ip: null, userAgent: BROWSER_UA });
|
|
759
|
+
verifyTotpMock.mockResolvedValue(okResponse());
|
|
760
|
+
generateBackupCodesMock.mockResolvedValue(jsonResponse({ backupCodes: ['aaaaa-bbbbb'] }, 200));
|
|
761
|
+
const svc = buildService({ events, trustedDevices, sessions });
|
|
762
|
+
const result = await svc.regenerateBackupCodes({ password: 'rightpass1', code: '123456' }, { cookie: 'better-auth.session_token=live', 'user-agent': BROWSER_UA }, new Headers());
|
|
763
|
+
expect(result.backupCodes).toEqual(['aaaaa-bbbbb']);
|
|
764
|
+
expect(verifyTotpMock).toHaveBeenCalled();
|
|
765
|
+
expect(await trustedDevices.isTrusted(account.id, BROWSER_UA)).toBe(false);
|
|
766
|
+
});
|
|
767
|
+
});
|
|
768
|
+
describe('IdentityService.trustCurrentDevice', () => {
|
|
769
|
+
const BROWSER_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
|
770
|
+
const challengeResponse = () => {
|
|
771
|
+
const res = jsonResponse({ twoFactorRedirect: true }, 200);
|
|
772
|
+
res.headers.append('set-cookie', 'better-auth.two_factor=pending-value; Path=/; HttpOnly');
|
|
773
|
+
return res;
|
|
774
|
+
};
|
|
775
|
+
const makeTrustedDevices = () => new TrustedDeviceService({
|
|
776
|
+
drizzle: db.drizzle,
|
|
777
|
+
events: makeEventBus(),
|
|
778
|
+
trustedDeviceDays: 30,
|
|
779
|
+
});
|
|
780
|
+
it('replays the sign-in leg so better-auth issues its trust cookie', async () => {
|
|
781
|
+
const account = await seedUser();
|
|
782
|
+
getSessionMock.mockResolvedValue({ user: { ...betterAuthUser, id: account.id } });
|
|
783
|
+
signInEmailMock.mockResolvedValue(challengeResponse());
|
|
784
|
+
const trusted = jsonResponse({ status: true }, 200);
|
|
785
|
+
trusted.headers.append('set-cookie', 'better-auth.trust_device=granted; Path=/; HttpOnly');
|
|
786
|
+
verifyTotpMock.mockResolvedValue(trusted);
|
|
787
|
+
const trustedDevices = makeTrustedDevices();
|
|
788
|
+
const resHeaders = new Headers();
|
|
789
|
+
await buildService({ trustedDevices }).trustCurrentDevice({ password: 'rightpass1', code: '123456' }, { cookie: 'better-auth.session_token=live', 'user-agent': BROWSER_UA }, resHeaders);
|
|
790
|
+
const [call] = verifyTotpMock.mock.calls;
|
|
791
|
+
expect(call?.[0].body).toEqual({ code: '123456', trustDevice: true });
|
|
792
|
+
expect(call?.[0].headers.get('cookie')).toBe('better-auth.two_factor=pending-value');
|
|
793
|
+
expect(resHeaders.getSetCookie().join(';')).toContain('trust_device=granted');
|
|
794
|
+
expect(await trustedDevices.isTrusted(account.id, BROWSER_UA)).toBe(true);
|
|
795
|
+
});
|
|
796
|
+
it('refuses an account that has no second factor to trust', async () => {
|
|
797
|
+
const account = await seedUser();
|
|
798
|
+
getSessionMock.mockResolvedValue({ user: { ...betterAuthUser, id: account.id } });
|
|
799
|
+
signInEmailMock.mockResolvedValue(signInSuccess(account.id));
|
|
800
|
+
const trustedDevices = makeTrustedDevices();
|
|
801
|
+
await expect(buildService({ trustedDevices }).trustCurrentDevice({ password: 'rightpass1', code: '123456' }, { 'user-agent': BROWSER_UA }, new Headers())).rejects.toMatchObject({ code: 'BAD_REQUEST' });
|
|
802
|
+
expect(verifyTotpMock).not.toHaveBeenCalled();
|
|
803
|
+
expect(await trustedDevices.isTrusted(account.id, BROWSER_UA)).toBe(false);
|
|
804
|
+
});
|
|
805
|
+
});
|
|
586
806
|
//# sourceMappingURL=identity.service.int.test.js.map
|