@openora/core 0.4.1-canary.18 → 0.4.1-canary.20
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/audit/plugin.d.ts.map +1 -1
- package/dist/audit/plugin.js +37 -0
- package/dist/audit/plugin.js.map +1 -1
- package/dist/contracts/adapters/__tests__/rate-limit.test.d.ts +2 -0
- package/dist/contracts/adapters/__tests__/rate-limit.test.d.ts.map +1 -0
- package/dist/contracts/adapters/__tests__/rate-limit.test.js +13 -0
- package/dist/contracts/adapters/__tests__/rate-limit.test.js.map +1 -0
- package/dist/contracts/adapters/email-template.d.ts +28 -0
- package/dist/contracts/adapters/email-template.d.ts.map +1 -0
- package/dist/contracts/adapters/email-template.js +19 -0
- package/dist/contracts/adapters/email-template.js.map +1 -0
- package/dist/contracts/adapters/index.d.ts +5 -1
- package/dist/contracts/adapters/index.d.ts.map +1 -1
- package/dist/contracts/adapters/index.js +3 -1
- package/dist/contracts/adapters/index.js.map +1 -1
- package/dist/contracts/adapters/rate-limit.d.ts +2 -0
- package/dist/contracts/adapters/rate-limit.d.ts.map +1 -1
- package/dist/contracts/adapters/rate-limit.js +4 -0
- package/dist/contracts/adapters/rate-limit.js.map +1 -1
- package/dist/contracts/adapters/sms.d.ts +9 -0
- package/dist/contracts/adapters/sms.d.ts.map +1 -0
- package/dist/contracts/adapters/sms.js +7 -0
- package/dist/contracts/adapters/sms.js.map +1 -0
- package/dist/contracts/schemas/__tests__/identity-schemas.test.js +6 -1
- package/dist/contracts/schemas/__tests__/identity-schemas.test.js.map +1 -1
- package/dist/contracts/schemas/events.d.ts +17 -0
- package/dist/contracts/schemas/events.d.ts.map +1 -1
- package/dist/contracts/schemas/events.js +10 -0
- package/dist/contracts/schemas/events.js.map +1 -1
- package/dist/contracts/schemas/identity.d.ts +29 -1
- package/dist/contracts/schemas/identity.d.ts.map +1 -1
- package/dist/contracts/schemas/identity.js +26 -2
- package/dist/contracts/schemas/identity.js.map +1 -1
- package/dist/iam/service/iam.service.d.ts +2 -2
- package/dist/pam/identity/__tests__/identity.rate-limit.test.js +34 -12
- package/dist/pam/identity/__tests__/identity.rate-limit.test.js.map +1 -1
- package/dist/pam/identity/__tests__/identity.service.test.js +188 -35
- package/dist/pam/identity/__tests__/identity.service.test.js.map +1 -1
- package/dist/pam/identity/__tests__/phone-login.service.test.d.ts +2 -0
- package/dist/pam/identity/__tests__/phone-login.service.test.d.ts.map +1 -0
- package/dist/pam/identity/__tests__/phone-login.service.test.js +198 -0
- package/dist/pam/identity/__tests__/phone-login.service.test.js.map +1 -0
- package/dist/pam/identity/adapters/__tests__/default-email-template-renderer.test.d.ts +2 -0
- package/dist/pam/identity/adapters/__tests__/default-email-template-renderer.test.d.ts.map +1 -0
- package/dist/pam/identity/adapters/__tests__/default-email-template-renderer.test.js +25 -0
- package/dist/pam/identity/adapters/__tests__/default-email-template-renderer.test.js.map +1 -0
- package/dist/pam/identity/adapters/default-email-template-renderer.d.ts +8 -0
- package/dist/pam/identity/adapters/default-email-template-renderer.d.ts.map +1 -0
- package/dist/pam/identity/adapters/default-email-template-renderer.js +7 -0
- package/dist/pam/identity/adapters/default-email-template-renderer.js.map +1 -0
- package/dist/pam/identity/adapters/mock/mock-sms-adapter.d.ts +8 -0
- package/dist/pam/identity/adapters/mock/mock-sms-adapter.d.ts.map +1 -0
- package/dist/pam/identity/adapters/mock/mock-sms-adapter.js +7 -0
- package/dist/pam/identity/adapters/mock/mock-sms-adapter.js.map +1 -0
- package/dist/pam/identity/contract/index.d.ts +54 -1
- package/dist/pam/identity/contract/index.d.ts.map +1 -1
- package/dist/pam/identity/contract/index.js +14 -2
- package/dist/pam/identity/contract/index.js.map +1 -1
- package/dist/pam/identity/drizzle/migrations/0001_rainy_peter_quill.sql +18 -0
- package/dist/pam/identity/drizzle/migrations/0002_outstanding_patriot.sql +1 -0
- package/dist/pam/identity/drizzle/migrations/0003_open_shotgun.sql +1 -0
- package/dist/pam/identity/drizzle/migrations/0004_round_giant_girl.sql +1 -0
- package/dist/pam/identity/drizzle/migrations/meta/0001_snapshot.json +684 -0
- package/dist/pam/identity/drizzle/migrations/meta/0002_snapshot.json +690 -0
- package/dist/pam/identity/drizzle/migrations/meta/0003_snapshot.json +695 -0
- package/dist/pam/identity/drizzle/migrations/meta/0004_snapshot.json +680 -0
- package/dist/pam/identity/drizzle/migrations/meta/_journal.json +28 -0
- package/dist/pam/identity/plugin.d.ts.map +1 -1
- package/dist/pam/identity/plugin.js +15 -2
- package/dist/pam/identity/plugin.js.map +1 -1
- package/dist/pam/identity/react/account.d.ts +2 -0
- package/dist/pam/identity/react/account.d.ts.map +1 -1
- package/dist/pam/identity/react/auth.d.ts +9 -1
- package/dist/pam/identity/react/auth.d.ts.map +1 -1
- package/dist/pam/identity/router/index.d.ts +52 -2
- package/dist/pam/identity/router/index.d.ts.map +1 -1
- package/dist/pam/identity/router/index.js +28 -1
- package/dist/pam/identity/router/index.js.map +1 -1
- package/dist/pam/identity/schema/index.d.ts +212 -0
- package/dist/pam/identity/schema/index.d.ts.map +1 -1
- package/dist/pam/identity/schema/index.js +23 -0
- package/dist/pam/identity/schema/index.js.map +1 -1
- package/dist/pam/identity/service/identity.service.d.ts +9 -2
- package/dist/pam/identity/service/identity.service.d.ts.map +1 -1
- package/dist/pam/identity/service/identity.service.js +113 -30
- package/dist/pam/identity/service/identity.service.js.map +1 -1
- package/dist/pam/identity/service/phone-login.service.d.ts +53 -0
- package/dist/pam/identity/service/phone-login.service.d.ts.map +1 -0
- package/dist/pam/identity/service/phone-login.service.js +235 -0
- package/dist/pam/identity/service/phone-login.service.js.map +1 -0
- package/dist/pam/identity/service/rg-guard.service.d.ts +5 -0
- package/dist/pam/identity/service/rg-guard.service.d.ts.map +1 -0
- package/dist/pam/identity/service/rg-guard.service.js +4 -0
- package/dist/pam/identity/service/rg-guard.service.js.map +1 -0
- package/dist/server/auth/__tests__/auth.test.d.ts +2 -0
- package/dist/server/auth/__tests__/auth.test.d.ts.map +1 -0
- package/dist/server/auth/__tests__/auth.test.js +74 -0
- package/dist/server/auth/__tests__/auth.test.js.map +1 -0
- package/dist/server/auth/auth.d.ts +7 -0
- package/dist/server/auth/auth.d.ts.map +1 -1
- package/dist/server/auth/auth.js +37 -14
- package/dist/server/auth/auth.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../../../src/contracts/schemas/identity.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../../../src/contracts/schemas/identity.ts"],"names":[],"mappings":"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"}
|
|
@@ -171,9 +171,9 @@ export declare class IamService {
|
|
|
171
171
|
status: "pending" | "accepted" | "revoked";
|
|
172
172
|
id: string;
|
|
173
173
|
createdAt: Date;
|
|
174
|
+
expiresAt: Date;
|
|
174
175
|
token: string;
|
|
175
176
|
roleId: string;
|
|
176
|
-
expiresAt: Date;
|
|
177
177
|
acceptedAt: Date | null;
|
|
178
178
|
}, "createdAt" | "expiresAt" | "acceptedAt">[];
|
|
179
179
|
total: number;
|
|
@@ -189,9 +189,9 @@ export declare class IamService {
|
|
|
189
189
|
status: "pending" | "accepted" | "revoked";
|
|
190
190
|
id: string;
|
|
191
191
|
createdAt: Date;
|
|
192
|
+
expiresAt: Date;
|
|
192
193
|
token: string;
|
|
193
194
|
roleId: string;
|
|
194
|
-
expiresAt: Date;
|
|
195
195
|
acceptedAt: Date | null;
|
|
196
196
|
}, "createdAt" | "expiresAt" | "acceptedAt">>;
|
|
197
197
|
acceptInvitation(token: string): Promise<{
|
|
@@ -2,6 +2,12 @@ import { describe, it, expect, vi } from 'vitest';
|
|
|
2
2
|
import { InProcessRateLimiter } from '@openora/core/server';
|
|
3
3
|
import { mock, mockDb } from '../../../testing/mock.js';
|
|
4
4
|
import { IdentityService } from '../service/identity.service.js';
|
|
5
|
+
const testTemplateRenderer = {
|
|
6
|
+
render: () => ({ subject: 'subject', body: 'body' }),
|
|
7
|
+
};
|
|
8
|
+
function withTemplateRenderer(deps) {
|
|
9
|
+
return new IdentityService({ templateRenderer: testTemplateRenderer, ...deps });
|
|
10
|
+
}
|
|
5
11
|
// Keep the real @openora/core/server (so assertRateLimit + InProcessRateLimiter
|
|
6
12
|
// are real); only stub createAuth so the constructor doesn't touch a real DB.
|
|
7
13
|
vi.mock('@openora/core/server', async (importOriginal) => {
|
|
@@ -23,7 +29,7 @@ describe('IdentityService - rate limiting', () => {
|
|
|
23
29
|
for (let i = 0; i < 5; i++) {
|
|
24
30
|
await limiter.consume(`register:${email}`, { limit: 5, windowMs: 15 * 60 * 1000 });
|
|
25
31
|
}
|
|
26
|
-
const svc =
|
|
32
|
+
const svc = withTemplateRenderer({ drizzle, events, limiter });
|
|
27
33
|
await expect(svc.register({ email, password: 'password123', name: 'A' }, {}, new Headers())).rejects.toMatchObject({
|
|
28
34
|
code: 'TOO_MANY_REQUESTS',
|
|
29
35
|
data: { retryAfterMs: expect.any(Number) },
|
|
@@ -31,7 +37,7 @@ describe('IdentityService - rate limiting', () => {
|
|
|
31
37
|
limiter.close();
|
|
32
38
|
});
|
|
33
39
|
it('allows register when no limiter is bound (test/no-auth edition)', async () => {
|
|
34
|
-
const svc =
|
|
40
|
+
const svc = withTemplateRenderer({ drizzle, events });
|
|
35
41
|
// signUpEmail is a bare vi.fn returning undefined; a throw here would be a 429, not the
|
|
36
42
|
// downstream .json() failure we expect, so assert we got past the guard.
|
|
37
43
|
await expect(svc.register({ email: 'ok@x.dev', password: 'password123', name: 'A' }, {}, new Headers())).rejects.not.toMatchObject({ code: 'TOO_MANY_REQUESTS' });
|
|
@@ -47,7 +53,7 @@ describe('IdentityService - verify2fa rate-limit key stability (ABC-208 finding
|
|
|
47
53
|
windowMs: 5 * 60 * 1000,
|
|
48
54
|
});
|
|
49
55
|
}
|
|
50
|
-
const svc =
|
|
56
|
+
const svc = withTemplateRenderer({ drizzle, events, limiter });
|
|
51
57
|
// Same two_factor cookie + different junk pairs must still hit the one exhausted bucket.
|
|
52
58
|
for (let i = 0; i < 3; i++) {
|
|
53
59
|
await expect(svc.verifyTwoFactor({ code: '123456' }, { cookie: `better-auth.two_factor=${twoFactorIdentifier}; junk${i}=${i}` }, new Headers())).rejects.toMatchObject({ code: 'TOO_MANY_REQUESTS' });
|
|
@@ -61,7 +67,7 @@ describe('IdentityService - rate limiting on secret-guessing routes (ABC-208 fin
|
|
|
61
67
|
for (let i = 0; i < 5; i++) {
|
|
62
68
|
await limiter.consume('change-password:anonymous', { limit: 5, windowMs: 15 * 60 * 1000 });
|
|
63
69
|
}
|
|
64
|
-
const svc =
|
|
70
|
+
const svc = withTemplateRenderer({ drizzle, events, limiter });
|
|
65
71
|
await expect(svc.changePassword({ currentPassword: 'currentpw1', newPassword: 'newpassword1' }, {}, new Headers())).rejects.toMatchObject({ code: 'TOO_MANY_REQUESTS' });
|
|
66
72
|
limiter.close();
|
|
67
73
|
});
|
|
@@ -70,7 +76,7 @@ describe('IdentityService - rate limiting on secret-guessing routes (ABC-208 fin
|
|
|
70
76
|
for (let i = 0; i < 5; i++) {
|
|
71
77
|
await limiter.consume('verify-email:anonymous', { limit: 5, windowMs: 15 * 60 * 1000 });
|
|
72
78
|
}
|
|
73
|
-
const svc =
|
|
79
|
+
const svc = withTemplateRenderer({ drizzle, events, limiter });
|
|
74
80
|
await expect(svc.verifyEmail({ token: 'sometoken' }, {})).rejects.toMatchObject({
|
|
75
81
|
code: 'TOO_MANY_REQUESTS',
|
|
76
82
|
});
|
|
@@ -81,7 +87,7 @@ describe('IdentityService - rate limiting on secret-guessing routes (ABC-208 fin
|
|
|
81
87
|
for (let i = 0; i < 5; i++) {
|
|
82
88
|
await limiter.consume('enable2fa:anonymous', { limit: 5, windowMs: 5 * 60 * 1000 });
|
|
83
89
|
}
|
|
84
|
-
const svc =
|
|
90
|
+
const svc = withTemplateRenderer({ drizzle, events, limiter });
|
|
85
91
|
await expect(svc.enableTwoFactor({ password: 'currentpw1' }, {}, new Headers())).rejects.toMatchObject({ code: 'TOO_MANY_REQUESTS' });
|
|
86
92
|
limiter.close();
|
|
87
93
|
});
|
|
@@ -90,7 +96,7 @@ describe('IdentityService - rate limiting on secret-guessing routes (ABC-208 fin
|
|
|
90
96
|
for (let i = 0; i < 5; i++) {
|
|
91
97
|
await limiter.consume('disable2fa:anonymous', { limit: 5, windowMs: 5 * 60 * 1000 });
|
|
92
98
|
}
|
|
93
|
-
const svc =
|
|
99
|
+
const svc = withTemplateRenderer({ drizzle, events, limiter });
|
|
94
100
|
await expect(svc.disableTwoFactor({ password: 'currentpw1' }, {}, new Headers())).rejects.toMatchObject({ code: 'TOO_MANY_REQUESTS' });
|
|
95
101
|
limiter.close();
|
|
96
102
|
});
|
|
@@ -105,13 +111,13 @@ describe('IdentityService - fail-closed limiter policy for credential-guessing k
|
|
|
105
111
|
}
|
|
106
112
|
it('passes onUnavailable: deny on the login: key', async () => {
|
|
107
113
|
const { limiter, consume } = denyingLimiter();
|
|
108
|
-
const svc =
|
|
114
|
+
const svc = withTemplateRenderer({ drizzle, events, limiter });
|
|
109
115
|
await expect(svc.login({ email: 'User@X.dev', password: 'password123' }, {}, new Headers())).rejects.toMatchObject({ code: 'TOO_MANY_REQUESTS' });
|
|
110
116
|
expect(consume).toHaveBeenCalledWith('login:user@x.dev', expect.objectContaining({ onUnavailable: 'deny' }));
|
|
111
117
|
});
|
|
112
118
|
it('passes onUnavailable: deny on the verify2fa: key', async () => {
|
|
113
119
|
const { limiter, consume } = denyingLimiter();
|
|
114
|
-
const svc =
|
|
120
|
+
const svc = withTemplateRenderer({ drizzle, events, limiter });
|
|
115
121
|
await expect(svc.verifyTwoFactor({ code: '123456' }, {}, new Headers())).rejects.toMatchObject({
|
|
116
122
|
code: 'TOO_MANY_REQUESTS',
|
|
117
123
|
});
|
|
@@ -119,9 +125,25 @@ describe('IdentityService - fail-closed limiter policy for credential-guessing k
|
|
|
119
125
|
});
|
|
120
126
|
it('passes onUnavailable: deny on the pwreset: key', async () => {
|
|
121
127
|
const { limiter, consume } = denyingLimiter();
|
|
122
|
-
const svc =
|
|
123
|
-
await expect(svc.resetPassword({
|
|
124
|
-
expect(consume).toHaveBeenCalledWith('pwreset:
|
|
128
|
+
const svc = withTemplateRenderer({ drizzle, events, limiter });
|
|
129
|
+
await expect(svc.resetPassword({ email: 'user@x.dev', otp: '123456', newPassword: 'newpassword1' })).rejects.toMatchObject({ code: 'TOO_MANY_REQUESTS' });
|
|
130
|
+
expect(consume).toHaveBeenCalledWith('pwreset:user@x.dev', expect.objectContaining({ onUnavailable: 'deny' }));
|
|
131
|
+
});
|
|
132
|
+
it('passes onUnavailable: deny on the pwreset-verify: key', async () => {
|
|
133
|
+
const { limiter, consume } = denyingLimiter();
|
|
134
|
+
const svc = withTemplateRenderer({ drizzle, events, limiter });
|
|
135
|
+
await expect(svc.verifyPasswordResetOtp({ email: 'user@x.dev', otp: '123456' })).rejects.toMatchObject({ code: 'TOO_MANY_REQUESTS' });
|
|
136
|
+
expect(consume).toHaveBeenCalledWith('pwreset-verify:user@x.dev', expect.objectContaining({ onUnavailable: 'deny' }));
|
|
137
|
+
});
|
|
138
|
+
it('throttles requestPasswordReset on the pwreset-req: key', async () => {
|
|
139
|
+
const { limiter, consume } = denyingLimiter();
|
|
140
|
+
const svc = withTemplateRenderer({ drizzle, events, limiter });
|
|
141
|
+
// The throttle is asserted before the anti-enumeration try/catch, so a denied
|
|
142
|
+
// request key surfaces as a 429 (only the better-auth call is swallowed).
|
|
143
|
+
await expect(svc.requestPasswordReset({ email: 'User@X.dev' })).rejects.toMatchObject({
|
|
144
|
+
code: 'TOO_MANY_REQUESTS',
|
|
145
|
+
});
|
|
146
|
+
expect(consume).toHaveBeenCalledWith('pwreset-req:user@x.dev', expect.any(Object));
|
|
125
147
|
});
|
|
126
148
|
});
|
|
127
149
|
//# sourceMappingURL=identity.rate-limit.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"identity.rate-limit.test.js","sourceRoot":"","sources":["../../../../src/pam/identity/__tests__/identity.rate-limit.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAClD,OAAO,EAAE,oBAAoB,EAAiB,MAAM,sBAAsB,CAAC;AAE3E,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,eAAe,
|
|
1
|
+
{"version":3,"file":"identity.rate-limit.test.js","sourceRoot":"","sources":["../../../../src/pam/identity/__tests__/identity.rate-limit.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,28 +1,41 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { IdentityService, SESSION_DURATION_IN_SECONDS } from '../service/identity.service.js';
|
|
2
|
+
import { IdentityService, SESSION_DURATION_IN_SECONDS, } from '../service/identity.service.js';
|
|
3
3
|
import { UnsupportedLanguageError } from '../../shared/language.js';
|
|
4
4
|
import { ORPCError } from '@orpc/server';
|
|
5
|
-
|
|
5
|
+
import { mock } from '../../../testing/mock.js';
|
|
6
|
+
const { signInEmailMock, getSessionMock, updateUserMock, requestPasswordResetEmailOTPMock, checkVerificationOTPMock, resetPasswordEmailOTPMock, capturedAuthOptions, } = vi.hoisted(() => ({
|
|
6
7
|
signInEmailMock: vi.fn(),
|
|
7
8
|
getSessionMock: vi.fn().mockResolvedValue(null),
|
|
8
9
|
updateUserMock: vi.fn(),
|
|
10
|
+
requestPasswordResetEmailOTPMock: vi.fn(),
|
|
11
|
+
checkVerificationOTPMock: vi.fn(),
|
|
12
|
+
resetPasswordEmailOTPMock: vi.fn(),
|
|
13
|
+
capturedAuthOptions: {
|
|
14
|
+
current: undefined,
|
|
15
|
+
},
|
|
9
16
|
}));
|
|
10
17
|
vi.mock('@openora/core/server', async (importOriginal) => ({
|
|
11
18
|
...(await importOriginal()),
|
|
12
|
-
createAuth: vi.fn(() =>
|
|
13
|
-
options
|
|
14
|
-
|
|
15
|
-
|
|
19
|
+
createAuth: vi.fn((options) => {
|
|
20
|
+
capturedAuthOptions.current = options;
|
|
21
|
+
return {
|
|
22
|
+
options: {
|
|
23
|
+
session: {
|
|
24
|
+
expiresIn: SESSION_DURATION_IN_SECONDS,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
api: {
|
|
28
|
+
getSession: getSessionMock,
|
|
29
|
+
signUpEmail: vi.fn(),
|
|
30
|
+
signInEmail: signInEmailMock,
|
|
31
|
+
signOut: vi.fn(),
|
|
32
|
+
updateUser: updateUserMock,
|
|
33
|
+
requestPasswordResetEmailOTP: requestPasswordResetEmailOTPMock,
|
|
34
|
+
checkVerificationOTP: checkVerificationOTPMock,
|
|
35
|
+
resetPasswordEmailOTP: resetPasswordEmailOTPMock,
|
|
16
36
|
},
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
getSession: getSessionMock,
|
|
20
|
-
signUpEmail: vi.fn(),
|
|
21
|
-
signInEmail: signInEmailMock,
|
|
22
|
-
signOut: vi.fn(),
|
|
23
|
-
updateUser: updateUserMock,
|
|
24
|
-
},
|
|
25
|
-
})),
|
|
37
|
+
};
|
|
38
|
+
}),
|
|
26
39
|
}));
|
|
27
40
|
function makeDrizzle({ selectRows = [], updateReturning = [] } = {}) {
|
|
28
41
|
const selectQueue = [...selectRows];
|
|
@@ -37,10 +50,22 @@ function makeDrizzle({ selectRows = [], updateReturning = [] } = {}) {
|
|
|
37
50
|
const select = vi.fn(() => ({
|
|
38
51
|
from: () => ({ where: () => ({ limit: () => Promise.resolve(selectQueue.shift() ?? []) }) }),
|
|
39
52
|
}));
|
|
40
|
-
return {
|
|
53
|
+
return mock({
|
|
54
|
+
db: { select, update },
|
|
55
|
+
update,
|
|
56
|
+
select,
|
|
57
|
+
});
|
|
41
58
|
}
|
|
42
59
|
function makeEvents() {
|
|
43
|
-
return { emit: vi.fn(), on: vi.fn() };
|
|
60
|
+
return mock({ emit: vi.fn(), on: vi.fn() });
|
|
61
|
+
}
|
|
62
|
+
// createAuth() is mocked below, so the renderer is never actually invoked - this fake only
|
|
63
|
+
// satisfies IdentityService's required `templateRenderer` dependency at construction time.
|
|
64
|
+
const testTemplateRenderer = {
|
|
65
|
+
render: () => ({ subject: 'subject', body: 'body' }),
|
|
66
|
+
};
|
|
67
|
+
function withTemplateRenderer(deps) {
|
|
68
|
+
return new IdentityService({ templateRenderer: testTemplateRenderer, ...deps });
|
|
44
69
|
}
|
|
45
70
|
function jsonResponse(body, status) {
|
|
46
71
|
return new Response(JSON.stringify(body), {
|
|
@@ -62,7 +87,7 @@ beforeEach(() => {
|
|
|
62
87
|
});
|
|
63
88
|
describe('IdentityService - SendEmailPort seam', () => {
|
|
64
89
|
it('constructs without a SendEmailPort (email delivery silently skipped)', () => {
|
|
65
|
-
const svc =
|
|
90
|
+
const svc = withTemplateRenderer({
|
|
66
91
|
drizzle: makeDrizzle(),
|
|
67
92
|
events: makeEvents(),
|
|
68
93
|
});
|
|
@@ -70,7 +95,7 @@ describe('IdentityService - SendEmailPort seam', () => {
|
|
|
70
95
|
});
|
|
71
96
|
it('constructs with a stub SendEmailPort and exposes it for use', () => {
|
|
72
97
|
const stub = { send: vi.fn().mockResolvedValue(undefined) };
|
|
73
|
-
const svc =
|
|
98
|
+
const svc = withTemplateRenderer({
|
|
74
99
|
drizzle: makeDrizzle(),
|
|
75
100
|
events: makeEvents(),
|
|
76
101
|
email: stub,
|
|
@@ -78,7 +103,7 @@ describe('IdentityService - SendEmailPort seam', () => {
|
|
|
78
103
|
expect(svc).toBeInstanceOf(IdentityService);
|
|
79
104
|
});
|
|
80
105
|
it('me returns null when no session exists', async () => {
|
|
81
|
-
const svc =
|
|
106
|
+
const svc = withTemplateRenderer({
|
|
82
107
|
drizzle: makeDrizzle(),
|
|
83
108
|
events: makeEvents(),
|
|
84
109
|
});
|
|
@@ -94,7 +119,7 @@ describe('IdentityService - login lockout', () => {
|
|
|
94
119
|
});
|
|
95
120
|
const events = makeEvents();
|
|
96
121
|
signInEmailMock.mockResolvedValue(jsonResponse({ message: 'Invalid' }, 401));
|
|
97
|
-
const svc =
|
|
122
|
+
const svc = withTemplateRenderer({ drizzle: drizzle, events: events });
|
|
98
123
|
await expect(svc.login({ email: 'A@B.dev', password: 'wrongpass1' }, {}, new Headers())).rejects.toThrow();
|
|
99
124
|
expect(events.emit).toHaveBeenCalledWith('identity.user.lockout.triggered', expect.objectContaining({ userId: 'u1', email: 'a@b.dev' }));
|
|
100
125
|
});
|
|
@@ -105,7 +130,7 @@ describe('IdentityService - login lockout', () => {
|
|
|
105
130
|
});
|
|
106
131
|
const events = makeEvents();
|
|
107
132
|
signInEmailMock.mockResolvedValue(jsonResponse({ message: 'Invalid' }, 401));
|
|
108
|
-
const svc =
|
|
133
|
+
const svc = withTemplateRenderer({ drizzle: drizzle, events: events });
|
|
109
134
|
await expect(svc.login({ email: 'a@b.dev', password: 'wrongpass1' }, {}, new Headers())).rejects.toThrow();
|
|
110
135
|
expect(events.emit).toHaveBeenCalledWith('identity.user.login.failed', expect.objectContaining({ email: 'a@b.dev', reason: 'invalid_credentials' }));
|
|
111
136
|
expect(events.emit).not.toHaveBeenCalledWith('identity.user.lockout.triggered', expect.anything());
|
|
@@ -115,7 +140,7 @@ describe('IdentityService - login lockout', () => {
|
|
|
115
140
|
const drizzle = makeDrizzle({
|
|
116
141
|
selectRows: [[{ id: 'u1', failedLoginAttempts: 5, lockoutUntil: future }]],
|
|
117
142
|
});
|
|
118
|
-
const svc =
|
|
143
|
+
const svc = withTemplateRenderer({ drizzle: drizzle, events: makeEvents() });
|
|
119
144
|
const promise = svc.login({ email: 'a@b.dev', password: 'whatever1' }, {}, new Headers());
|
|
120
145
|
await expect(promise).rejects.toThrow(ORPCError);
|
|
121
146
|
await expect(promise).rejects.toMatchObject({
|
|
@@ -134,7 +159,7 @@ describe('IdentityService - login lockout', () => {
|
|
|
134
159
|
});
|
|
135
160
|
const events = makeEvents();
|
|
136
161
|
signInEmailMock.mockResolvedValue(jsonResponse({ user: betterAuthUser, token: 'tok', session: { expiresAt: '2020-02-01T00:00:00.000Z' } }, 200));
|
|
137
|
-
const svc =
|
|
162
|
+
const svc = withTemplateRenderer({ drizzle: drizzle, events: events });
|
|
138
163
|
const result = await svc.login({ email: 'a@b.dev', password: 'rightpass1' }, {}, new Headers());
|
|
139
164
|
expect(result).toMatchObject({ session: { token: 'tok' } });
|
|
140
165
|
expect(events.emit).toHaveBeenCalledWith('identity.user.login', expect.objectContaining({ userId: 'u1' }));
|
|
@@ -146,7 +171,7 @@ describe('IdentityService - login lockout', () => {
|
|
|
146
171
|
});
|
|
147
172
|
const events = makeEvents();
|
|
148
173
|
signInEmailMock.mockResolvedValue(jsonResponse({ message: 'Invalid' }, 401));
|
|
149
|
-
const svc =
|
|
174
|
+
const svc = withTemplateRenderer({
|
|
150
175
|
drizzle: drizzle,
|
|
151
176
|
events: events,
|
|
152
177
|
options: {
|
|
@@ -171,7 +196,7 @@ describe('IdentityService - login lockout', () => {
|
|
|
171
196
|
});
|
|
172
197
|
const events = makeEvents();
|
|
173
198
|
signInEmailMock.mockResolvedValue(jsonResponse({ message: 'Invalid' }, 401));
|
|
174
|
-
const svc =
|
|
199
|
+
const svc = withTemplateRenderer({
|
|
175
200
|
drizzle: drizzle,
|
|
176
201
|
events: events,
|
|
177
202
|
options: {
|
|
@@ -188,6 +213,29 @@ describe('IdentityService - login lockout', () => {
|
|
|
188
213
|
expect(events.emit).toHaveBeenCalledWith('identity.user.lockout.triggered', expect.objectContaining({ userId: 'u1', email: 'support@b.dev' }));
|
|
189
214
|
});
|
|
190
215
|
});
|
|
216
|
+
describe('IdentityService - login banned-user 403 (not the RG block)', () => {
|
|
217
|
+
it('surfaces a banned-user 403 from signInEmail as FORBIDDEN and still emits login.failed', async () => {
|
|
218
|
+
const drizzle = makeDrizzle({
|
|
219
|
+
selectRows: [
|
|
220
|
+
[
|
|
221
|
+
{
|
|
222
|
+
id: 'u1',
|
|
223
|
+
failedLoginAttempts: 0,
|
|
224
|
+
lockoutUntil: null,
|
|
225
|
+
rgBlocked: false,
|
|
226
|
+
rgBlockedUntil: null,
|
|
227
|
+
},
|
|
228
|
+
],
|
|
229
|
+
],
|
|
230
|
+
});
|
|
231
|
+
const events = makeEvents();
|
|
232
|
+
signInEmailMock.mockResolvedValue(jsonResponse({ message: 'BANNED_USER' }, 403));
|
|
233
|
+
const svc = withTemplateRenderer({ drizzle: drizzle, events: events });
|
|
234
|
+
await expect(svc.login({ email: 'a@b.dev', password: 'whatever1' }, {}, new Headers())).rejects.toMatchObject({ code: 'FORBIDDEN' });
|
|
235
|
+
expect(events.emit).toHaveBeenCalledWith('identity.user.login.failed', expect.objectContaining({ email: 'a@b.dev', reason: 'error' }));
|
|
236
|
+
expect(events.emit).not.toHaveBeenCalledWith('rg.exclusion.login_blocked', expect.anything());
|
|
237
|
+
});
|
|
238
|
+
});
|
|
191
239
|
describe('IdentityService - RG login gate', () => {
|
|
192
240
|
it('blocks a self-excluded login AFTER credentials verify and emits rg.exclusion.login_blocked', async () => {
|
|
193
241
|
const drizzle = makeDrizzle({
|
|
@@ -206,7 +254,7 @@ describe('IdentityService - RG login gate', () => {
|
|
|
206
254
|
const events = makeEvents();
|
|
207
255
|
// Valid credentials (200) - the gate must still block.
|
|
208
256
|
signInEmailMock.mockResolvedValue(jsonResponse({ user: betterAuthUser, token: 'tok', session: {} }, 200));
|
|
209
|
-
const svc =
|
|
257
|
+
const svc = withTemplateRenderer({ drizzle: drizzle, events: events });
|
|
210
258
|
await expect(svc.login({ email: 'a@b.dev', password: 'rightpass1' }, {}, new Headers())).rejects.toMatchObject({ code: 'FORBIDDEN' });
|
|
211
259
|
expect(signInEmailMock).toHaveBeenCalled();
|
|
212
260
|
expect(events.emit).toHaveBeenCalledWith('rg.exclusion.login_blocked', expect.objectContaining({ userId: 'u1' }));
|
|
@@ -232,7 +280,7 @@ describe('IdentityService - RG login gate', () => {
|
|
|
232
280
|
});
|
|
233
281
|
const events = makeEvents();
|
|
234
282
|
signInEmailMock.mockResolvedValue(jsonResponse({ user: betterAuthUser, token: 'tok', session: {} }, 200));
|
|
235
|
-
const svc =
|
|
283
|
+
const svc = withTemplateRenderer({ drizzle: drizzle, events: events });
|
|
236
284
|
const result = await svc.login({ email: 'a@b.dev', password: 'rightpass1' }, {}, new Headers());
|
|
237
285
|
expect(result).toMatchObject({ session: { token: 'tok' } });
|
|
238
286
|
expect(events.emit).toHaveBeenCalledWith('identity.user.login', expect.objectContaining({ userId: 'u1' }));
|
|
@@ -254,7 +302,7 @@ describe('IdentityService - unlockUser', () => {
|
|
|
254
302
|
],
|
|
255
303
|
});
|
|
256
304
|
const events = makeEvents();
|
|
257
|
-
const svc =
|
|
305
|
+
const svc = withTemplateRenderer({ drizzle: drizzle, events: events });
|
|
258
306
|
const res = await svc.unlockUser('u1', 'admin1');
|
|
259
307
|
expect(res).toEqual({ success: true });
|
|
260
308
|
expect(drizzle.update).toHaveBeenCalled();
|
|
@@ -267,7 +315,7 @@ describe('IdentityService - unlockUser', () => {
|
|
|
267
315
|
});
|
|
268
316
|
it('throws when the user does not exist', async () => {
|
|
269
317
|
const drizzle = makeDrizzle({ selectRows: [[]] });
|
|
270
|
-
const svc =
|
|
318
|
+
const svc = withTemplateRenderer({ drizzle: drizzle, events: makeEvents() });
|
|
271
319
|
await expect(svc.unlockUser('missing', 'admin1')).rejects.toThrow();
|
|
272
320
|
});
|
|
273
321
|
it('resets the login rate-limit window for the unlocked user', async () => {
|
|
@@ -288,7 +336,7 @@ describe('IdentityService - unlockUser', () => {
|
|
|
288
336
|
consume: vi.fn(),
|
|
289
337
|
reset: resetMock,
|
|
290
338
|
};
|
|
291
|
-
const svc =
|
|
339
|
+
const svc = withTemplateRenderer({
|
|
292
340
|
drizzle: drizzle,
|
|
293
341
|
events: makeEvents(),
|
|
294
342
|
limiter,
|
|
@@ -297,12 +345,117 @@ describe('IdentityService - unlockUser', () => {
|
|
|
297
345
|
expect(resetMock).toHaveBeenCalledWith('login:a@b.dev');
|
|
298
346
|
});
|
|
299
347
|
});
|
|
348
|
+
describe('IdentityService.requestPasswordReset', () => {
|
|
349
|
+
it('calls the non-deprecated requestPasswordResetEmailOTP endpoint and returns SUCCESS', async () => {
|
|
350
|
+
requestPasswordResetEmailOTPMock.mockResolvedValue(jsonResponse({ success: true }, 200));
|
|
351
|
+
const svc = withTemplateRenderer({
|
|
352
|
+
drizzle: makeDrizzle(),
|
|
353
|
+
events: makeEvents(),
|
|
354
|
+
});
|
|
355
|
+
const result = await svc.requestPasswordReset({ email: 'a@b.dev' });
|
|
356
|
+
expect(result).toEqual({ success: true });
|
|
357
|
+
expect(requestPasswordResetEmailOTPMock).toHaveBeenCalledWith(expect.objectContaining({ body: { email: 'a@b.dev' } }));
|
|
358
|
+
});
|
|
359
|
+
it('swallows a failure and still returns SUCCESS (anti-enumeration)', async () => {
|
|
360
|
+
requestPasswordResetEmailOTPMock.mockRejectedValue(new Error('boom'));
|
|
361
|
+
const svc = withTemplateRenderer({
|
|
362
|
+
drizzle: makeDrizzle(),
|
|
363
|
+
events: makeEvents(),
|
|
364
|
+
});
|
|
365
|
+
await expect(svc.requestPasswordReset({ email: 'unregistered@x.dev' })).resolves.toEqual({
|
|
366
|
+
success: true,
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
describe('IdentityService.verifyPasswordResetOtp', () => {
|
|
371
|
+
it('calls checkVerificationOTP with type forget-password and returns SUCCESS', async () => {
|
|
372
|
+
checkVerificationOTPMock.mockResolvedValue(jsonResponse({ success: true }, 200));
|
|
373
|
+
const svc = withTemplateRenderer({
|
|
374
|
+
drizzle: makeDrizzle(),
|
|
375
|
+
events: makeEvents(),
|
|
376
|
+
});
|
|
377
|
+
const result = await svc.verifyPasswordResetOtp({ email: 'a@b.dev', otp: '123456' });
|
|
378
|
+
expect(result).toEqual({ success: true });
|
|
379
|
+
expect(checkVerificationOTPMock).toHaveBeenCalledWith(expect.objectContaining({
|
|
380
|
+
body: { email: 'a@b.dev', type: 'forget-password', otp: '123456' },
|
|
381
|
+
}));
|
|
382
|
+
});
|
|
383
|
+
it('surfaces an invalid OTP (400) response as ORPCError BAD_REQUEST with a generic message', async () => {
|
|
384
|
+
checkVerificationOTPMock.mockResolvedValue(jsonResponse({ message: 'INVALID_OTP' }, 400));
|
|
385
|
+
const svc = withTemplateRenderer({
|
|
386
|
+
drizzle: makeDrizzle(),
|
|
387
|
+
events: makeEvents(),
|
|
388
|
+
});
|
|
389
|
+
await expect(svc.verifyPasswordResetOtp({ email: 'a@b.dev', otp: '000000' })).rejects.toMatchObject({
|
|
390
|
+
code: 'BAD_REQUEST',
|
|
391
|
+
message: 'Invalid or expired verification code',
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
it('does not leak better-auth USER_NOT_FOUND message - forces the same generic message as a wrong OTP (anti-enumeration)', async () => {
|
|
395
|
+
// better-auth's checkVerificationOTP checks findUserByEmail BEFORE the OTP row, so an
|
|
396
|
+
// unregistered email produces a distinct raw message ("User not found") that must never
|
|
397
|
+
// reach the caller - it would let an unauthenticated caller learn whether an email exists.
|
|
398
|
+
checkVerificationOTPMock.mockResolvedValue(jsonResponse({ message: 'User not found' }, 400));
|
|
399
|
+
const svc = withTemplateRenderer({
|
|
400
|
+
drizzle: makeDrizzle(),
|
|
401
|
+
events: makeEvents(),
|
|
402
|
+
});
|
|
403
|
+
await expect(svc.verifyPasswordResetOtp({ email: 'unregistered@x.dev', otp: '000000' })).rejects.toMatchObject({
|
|
404
|
+
code: 'BAD_REQUEST',
|
|
405
|
+
message: 'Invalid or expired verification code',
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
it('surfaces a TOO_MANY_ATTEMPTS (403) response as ORPCError BAD_REQUEST (masked)', async () => {
|
|
409
|
+
checkVerificationOTPMock.mockResolvedValue(jsonResponse({ message: 'TOO_MANY_ATTEMPTS' }, 403));
|
|
410
|
+
const svc = withTemplateRenderer({
|
|
411
|
+
drizzle: makeDrizzle(),
|
|
412
|
+
events: makeEvents(),
|
|
413
|
+
});
|
|
414
|
+
await expect(svc.verifyPasswordResetOtp({ email: 'a@b.dev', otp: '000000' })).rejects.toMatchObject({ code: 'BAD_REQUEST' });
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
describe('IdentityService.resetPassword', () => {
|
|
418
|
+
it('calls resetPasswordEmailOTP with the right body and returns SUCCESS', async () => {
|
|
419
|
+
resetPasswordEmailOTPMock.mockResolvedValue(jsonResponse({ success: true }, 200));
|
|
420
|
+
const svc = withTemplateRenderer({
|
|
421
|
+
drizzle: makeDrizzle(),
|
|
422
|
+
events: makeEvents(),
|
|
423
|
+
});
|
|
424
|
+
const result = await svc.resetPassword({
|
|
425
|
+
email: 'a@b.dev',
|
|
426
|
+
otp: '123456',
|
|
427
|
+
newPassword: 'newpassword1',
|
|
428
|
+
});
|
|
429
|
+
expect(result).toEqual({ success: true });
|
|
430
|
+
expect(resetPasswordEmailOTPMock).toHaveBeenCalledWith(expect.objectContaining({
|
|
431
|
+
body: { email: 'a@b.dev', otp: '123456', password: 'newpassword1' },
|
|
432
|
+
}));
|
|
433
|
+
});
|
|
434
|
+
it('surfaces a TOO_MANY_ATTEMPTS (403) response as ORPCError BAD_REQUEST (masked)', async () => {
|
|
435
|
+
resetPasswordEmailOTPMock.mockResolvedValue(jsonResponse({ message: 'TOO_MANY_ATTEMPTS' }, 403));
|
|
436
|
+
const svc = withTemplateRenderer({
|
|
437
|
+
drizzle: makeDrizzle(),
|
|
438
|
+
events: makeEvents(),
|
|
439
|
+
});
|
|
440
|
+
await expect(svc.resetPassword({ email: 'a@b.dev', otp: '000000', newPassword: 'newpassword1' })).rejects.toMatchObject({ code: 'BAD_REQUEST' });
|
|
441
|
+
});
|
|
442
|
+
});
|
|
443
|
+
describe('IdentityService onPasswordReset hook (wired via createAuth)', () => {
|
|
444
|
+
it('clears the lockout and emits identity.password.reset when better-auth invokes the hook', async () => {
|
|
445
|
+
const drizzle = makeDrizzle();
|
|
446
|
+
const events = makeEvents();
|
|
447
|
+
withTemplateRenderer({ drizzle: drizzle, events: events });
|
|
448
|
+
await capturedAuthOptions.current?.onPasswordReset?.({ id: 'u1', email: 'a@b.dev' });
|
|
449
|
+
expect(events.emit).toHaveBeenCalledWith('identity.password.reset', expect.objectContaining({ userId: 'u1' }));
|
|
450
|
+
expect(drizzle.update).toHaveBeenCalled();
|
|
451
|
+
});
|
|
452
|
+
});
|
|
300
453
|
describe('IdentityService.updateProfile language validation', () => {
|
|
301
454
|
it('rejects an unsupported language before calling updateUser', async () => {
|
|
302
|
-
const svc =
|
|
455
|
+
const svc = withTemplateRenderer({
|
|
303
456
|
drizzle: makeDrizzle(),
|
|
304
457
|
events: makeEvents(),
|
|
305
|
-
platformConfig: { supportedLanguages: ['en', 'fr'] },
|
|
458
|
+
platformConfig: mock({ supportedLanguages: ['en', 'fr'] }),
|
|
306
459
|
});
|
|
307
460
|
await expect(svc.updateProfile({ language: 'de' }, {}, new Headers())).rejects.toThrow(UnsupportedLanguageError);
|
|
308
461
|
expect(updateUserMock).not.toHaveBeenCalled();
|
|
@@ -310,10 +463,10 @@ describe('IdentityService.updateProfile language validation', () => {
|
|
|
310
463
|
it('accepts a supported language and forwards it to updateUser', async () => {
|
|
311
464
|
updateUserMock.mockResolvedValue(jsonResponse({ status: true }, 200));
|
|
312
465
|
getSessionMock.mockResolvedValueOnce({ user: { ...betterAuthUser, language: 'fr' } });
|
|
313
|
-
const svc =
|
|
466
|
+
const svc = withTemplateRenderer({
|
|
314
467
|
drizzle: makeDrizzle(),
|
|
315
468
|
events: makeEvents(),
|
|
316
|
-
platformConfig: { supportedLanguages: ['en', 'fr'] },
|
|
469
|
+
platformConfig: mock({ supportedLanguages: ['en', 'fr'] }),
|
|
317
470
|
});
|
|
318
471
|
const result = await svc.updateProfile({ language: 'fr' }, {}, new Headers());
|
|
319
472
|
expect(updateUserMock).toHaveBeenCalledWith(expect.objectContaining({ body: expect.objectContaining({ language: 'fr' }) }));
|