firebase-admin-edge 1.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -171
- package/dist/auth/auth-error-codes.d.ts +12 -0
- package/dist/auth/auth-error-codes.d.ts.map +1 -1
- package/dist/auth/auth-error-codes.js +12 -0
- package/dist/auth/auth-error-codes.js.map +1 -1
- package/dist/auth/email-action-request.d.ts +40 -0
- package/dist/auth/email-action-request.d.ts.map +1 -0
- package/dist/auth/email-action-request.js +80 -0
- package/dist/auth/email-action-request.js.map +1 -0
- package/dist/auth/email-action-request.test.d.ts +2 -0
- package/dist/auth/email-action-request.test.d.ts.map +1 -0
- package/dist/auth/email-action-request.test.js +128 -0
- package/dist/auth/email-action-request.test.js.map +1 -0
- package/dist/auth/firebase-admin-auth.d.ts +31 -8
- package/dist/auth/firebase-admin-auth.d.ts.map +1 -1
- package/dist/auth/firebase-admin-auth.js +272 -14
- package/dist/auth/firebase-admin-auth.js.map +1 -1
- package/dist/auth/firebase-admin-auth.test.js +625 -6
- package/dist/auth/firebase-admin-auth.test.js.map +1 -1
- package/dist/auth/firebase-auth-endpoints.d.ts +31 -0
- package/dist/auth/firebase-auth-endpoints.d.ts.map +1 -1
- package/dist/auth/firebase-auth-endpoints.js +55 -0
- package/dist/auth/firebase-auth-endpoints.js.map +1 -1
- package/dist/auth/firebase-auth-endpoints.test.js +126 -1
- package/dist/auth/firebase-auth-endpoints.test.js.map +1 -1
- package/dist/auth/user-batch.d.ts +21 -0
- package/dist/auth/user-batch.d.ts.map +1 -0
- package/dist/auth/user-batch.js +49 -0
- package/dist/auth/user-batch.js.map +1 -0
- package/dist/auth/user-batch.test.d.ts +2 -0
- package/dist/auth/user-batch.test.d.ts.map +1 -0
- package/dist/auth/user-batch.test.js +73 -0
- package/dist/auth/user-batch.test.js.map +1 -0
- package/dist/auth/user-import.d.ts +55 -0
- package/dist/auth/user-import.d.ts.map +1 -0
- package/dist/auth/user-import.js +208 -0
- package/dist/auth/user-import.js.map +1 -0
- package/dist/auth/user-import.test.d.ts +2 -0
- package/dist/auth/user-import.test.d.ts.map +1 -0
- package/dist/auth/user-import.test.js +217 -0
- package/dist/auth/user-import.test.js.map +1 -0
- package/dist/auth/user-request.d.ts +10 -0
- package/dist/auth/user-request.d.ts.map +1 -1
- package/dist/auth/user-request.js +56 -0
- package/dist/auth/user-request.js.map +1 -1
- package/dist/auth/user-request.test.js +59 -1
- package/dist/auth/user-request.test.js.map +1 -1
- package/dist/firebase-edge-server.d.ts.map +1 -1
- package/dist/firebase-edge-server.js +6 -2
- package/dist/firebase-edge-server.js.map +1 -1
- package/dist/firebase-edge-server.test.js +29 -0
- package/dist/firebase-edge-server.test.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
2
|
import { FirebaseAdminAuth } from './firebase-admin-auth.js';
|
|
3
|
-
import { getAccountsInfo, createAccountAdmin, updateAccountAdmin, deleteAccountAdmin, downloadAccount, getAccountInfo, createSessionCookie as createSessionCookieEndpoint } from './firebase-auth-endpoints.js';
|
|
3
|
+
import { generateEmailActionLink, deleteAccountsAdmin, importAccountsAdmin, getAccountsInfo, createAccountAdmin, updateAccountAdmin, deleteAccountAdmin, downloadAccount, getAccountInfo, createSessionCookie as createSessionCookieEndpoint } from './firebase-auth-endpoints.js';
|
|
4
4
|
import { getToken } from './google-oauth.js';
|
|
5
5
|
import { signJWTCustomToken, verifyJWT, verifySessionJWT } from './firebase-jwt.js';
|
|
6
6
|
import { FirebaseEdgeError, FirebaseAdminAuthErrorInfo, FirebaseEndpointErrorInfo, JWTErrorInfo } from './errors.js';
|
|
7
7
|
vi.mock('./firebase-auth-endpoints.js', () => ({
|
|
8
|
+
generateEmailActionLink: vi.fn(),
|
|
9
|
+
deleteAccountsAdmin: vi.fn(),
|
|
10
|
+
importAccountsAdmin: vi.fn(),
|
|
8
11
|
getAccountsInfo: vi.fn(),
|
|
9
12
|
createAccountAdmin: vi.fn(),
|
|
10
13
|
updateAccountAdmin: vi.fn(),
|
|
@@ -81,6 +84,399 @@ describe('FirebaseAdminAuth', () => {
|
|
|
81
84
|
afterEach(() => {
|
|
82
85
|
vi.resetAllMocks();
|
|
83
86
|
});
|
|
87
|
+
describe('getUserByProviderUid', () => {
|
|
88
|
+
it.each([
|
|
89
|
+
[
|
|
90
|
+
'google.com',
|
|
91
|
+
'google-123',
|
|
92
|
+
{
|
|
93
|
+
federatedUserId: [
|
|
94
|
+
{ providerId: 'google.com', rawId: 'google-123' }
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
],
|
|
98
|
+
['email', 'test@example.com', { email: ['test@example.com'] }],
|
|
99
|
+
['phone', '+15551234567', { phoneNumber: ['+15551234567'] }]
|
|
100
|
+
])('looks up %s using the existing batch helper', async (providerId, uid, request) => {
|
|
101
|
+
const fetchFn = vi.fn();
|
|
102
|
+
const admin = new FirebaseAdminAuth(serviceAccountKey, 'tenant', fetchFn);
|
|
103
|
+
mockedGetToken.mockResolvedValue({
|
|
104
|
+
data: mockGoogleTokenResponse,
|
|
105
|
+
error: null
|
|
106
|
+
});
|
|
107
|
+
vi.mocked(getAccountsInfo).mockResolvedValue({
|
|
108
|
+
data: {
|
|
109
|
+
users: [
|
|
110
|
+
{
|
|
111
|
+
...mockUserRecord,
|
|
112
|
+
phoneNumber: '+15551234567',
|
|
113
|
+
providerUserInfo: [
|
|
114
|
+
{
|
|
115
|
+
providerId: 'google.com',
|
|
116
|
+
rawId: 'google-123'
|
|
117
|
+
}
|
|
118
|
+
]
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
},
|
|
122
|
+
error: null
|
|
123
|
+
});
|
|
124
|
+
const result = await admin.getUserByProviderUid(providerId, uid);
|
|
125
|
+
expect(result.error).toBeNull();
|
|
126
|
+
expect(result.data?.uid).toBe('uid-1');
|
|
127
|
+
expect(result.data?.toJSON()).toMatchObject({ uid: 'uid-1' });
|
|
128
|
+
expect(getAccountsInfo).toHaveBeenCalledWith(request, 'test-access-token', 'test-project', 'tenant', fetchFn);
|
|
129
|
+
});
|
|
130
|
+
it('returns user-not-found for an unmatched provider UID', async () => {
|
|
131
|
+
vi.spyOn(auth, 'getUsers').mockResolvedValue({
|
|
132
|
+
data: {
|
|
133
|
+
users: [],
|
|
134
|
+
notFound: [
|
|
135
|
+
{ providerId: 'google.com', providerUid: 'missing' }
|
|
136
|
+
]
|
|
137
|
+
},
|
|
138
|
+
error: null
|
|
139
|
+
});
|
|
140
|
+
expect((await auth.getUserByProviderUid('google.com', 'missing')).error
|
|
141
|
+
?.code).toBe(FirebaseAdminAuthErrorInfo.ADMIN_USER_NOT_FOUND.code);
|
|
142
|
+
});
|
|
143
|
+
it.each([
|
|
144
|
+
['', 'uid'],
|
|
145
|
+
['google.com', ''],
|
|
146
|
+
[null, 'uid'],
|
|
147
|
+
['google.com', null],
|
|
148
|
+
['email', 'invalid'],
|
|
149
|
+
['phone', 'invalid']
|
|
150
|
+
])('rejects invalid identifiers %s/%s before authentication', async (providerId, uid) => {
|
|
151
|
+
expect((await auth.getUserByProviderUid(providerId, uid)).error).toBeInstanceOf(FirebaseEdgeError);
|
|
152
|
+
expect(mockedGetToken).not.toHaveBeenCalled();
|
|
153
|
+
});
|
|
154
|
+
it('preserves lookup failures', async () => {
|
|
155
|
+
const error = new FirebaseEdgeError(FirebaseAdminAuthErrorInfo.ADMIN_USER_LOOKUP_FAILED);
|
|
156
|
+
vi.spyOn(auth, 'getUsers').mockResolvedValue({ data: null, error });
|
|
157
|
+
expect(await auth.getUserByProviderUid('google.com', 'uid')).toEqual({ data: null, error });
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
describe('generateVerifyAndChangeEmailLink', () => {
|
|
161
|
+
it.each([undefined, { url: 'https://example.com/account' }])('generates a link with settings %j', async (settings) => {
|
|
162
|
+
const fetchFn = vi.fn();
|
|
163
|
+
const admin = new FirebaseAdminAuth(serviceAccountKey, 'tenant', fetchFn);
|
|
164
|
+
mockedGetToken.mockResolvedValue({
|
|
165
|
+
data: mockGoogleTokenResponse,
|
|
166
|
+
error: null
|
|
167
|
+
});
|
|
168
|
+
vi.mocked(generateEmailActionLink).mockResolvedValue({
|
|
169
|
+
data: 'https://example.com/action',
|
|
170
|
+
error: null
|
|
171
|
+
});
|
|
172
|
+
expect(await admin.generateVerifyAndChangeEmailLink('old@example.com', 'new@example.com', settings)).toEqual({ data: 'https://example.com/action', error: null });
|
|
173
|
+
expect(generateEmailActionLink).toHaveBeenCalledWith('test-project', {
|
|
174
|
+
requestType: 'VERIFY_AND_CHANGE_EMAIL',
|
|
175
|
+
email: 'old@example.com',
|
|
176
|
+
newEmail: 'new@example.com',
|
|
177
|
+
returnOobLink: true,
|
|
178
|
+
...(settings
|
|
179
|
+
? {
|
|
180
|
+
continueUrl: settings.url,
|
|
181
|
+
canHandleCodeInApp: false
|
|
182
|
+
}
|
|
183
|
+
: {})
|
|
184
|
+
}, 'test-access-token', fetchFn, 'tenant');
|
|
185
|
+
});
|
|
186
|
+
it.each([
|
|
187
|
+
['invalid', 'new@example.com'],
|
|
188
|
+
['old@example.com', 'invalid'],
|
|
189
|
+
['old@example.com', undefined]
|
|
190
|
+
])('rejects invalid addresses %s/%s', async (email, newEmail) => {
|
|
191
|
+
const result = await auth.generateVerifyAndChangeEmailLink(email, newEmail);
|
|
192
|
+
expect(result.error?.code).toBe(FirebaseAdminAuthErrorInfo.ADMIN_API_INVALID_ARGUMENT.code);
|
|
193
|
+
expect(mockedGetToken).not.toHaveBeenCalled();
|
|
194
|
+
});
|
|
195
|
+
it('returns credential errors without requesting a link', async () => {
|
|
196
|
+
mockedGetToken.mockResolvedValue({
|
|
197
|
+
data: null,
|
|
198
|
+
error: new Error('credentials')
|
|
199
|
+
});
|
|
200
|
+
expect((await auth.generateVerifyAndChangeEmailLink('old@example.com', 'new@example.com')).error?.code).toBe(FirebaseAdminAuthErrorInfo.ADMIN_SERVICE_ACCOUNT_TOKEN_FAILED
|
|
201
|
+
.code);
|
|
202
|
+
expect(generateEmailActionLink).not.toHaveBeenCalled();
|
|
203
|
+
});
|
|
204
|
+
it('wraps endpoint failures with their cause', async () => {
|
|
205
|
+
const cause = new Error('email already exists');
|
|
206
|
+
mockedGetToken.mockResolvedValue({
|
|
207
|
+
data: mockGoogleTokenResponse,
|
|
208
|
+
error: null
|
|
209
|
+
});
|
|
210
|
+
vi.mocked(generateEmailActionLink).mockResolvedValue({
|
|
211
|
+
data: null,
|
|
212
|
+
error: cause
|
|
213
|
+
});
|
|
214
|
+
const result = await auth.generateVerifyAndChangeEmailLink('old@example.com', 'new@example.com');
|
|
215
|
+
expect(result.error?.code).toBe(FirebaseAdminAuthErrorInfo.ADMIN_EMAIL_ACTION_LINK_FAILED.code);
|
|
216
|
+
expect(result.error?.cause).toBe(cause);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
describe('setCustomUserClaims', () => {
|
|
220
|
+
beforeEach(() => {
|
|
221
|
+
mockedGetToken.mockResolvedValue({
|
|
222
|
+
data: mockGoogleTokenResponse,
|
|
223
|
+
error: null
|
|
224
|
+
});
|
|
225
|
+
vi.mocked(updateAccountAdmin).mockResolvedValue({
|
|
226
|
+
data: { localId: 'uid' },
|
|
227
|
+
error: null
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
it('uses the existing update helper with tenant and custom fetch', async () => {
|
|
231
|
+
const customFetch = vi.fn();
|
|
232
|
+
const instance = new FirebaseAdminAuth(serviceAccountKey, 'tenant', customFetch);
|
|
233
|
+
expect(await instance.setCustomUserClaims('uid', { role: 'editor' })).toEqual({ data: undefined, error: null });
|
|
234
|
+
expect(updateAccountAdmin).toHaveBeenCalledExactlyOnceWith('test-project', 'uid', { customAttributes: '{"role":"editor"}' }, 'test-access-token', customFetch, 'tenant');
|
|
235
|
+
expect(getAccountInfo).not.toHaveBeenCalled();
|
|
236
|
+
});
|
|
237
|
+
it('clears claims with null', async () => {
|
|
238
|
+
expect((await auth.setCustomUserClaims('uid', null)).error).toBeNull();
|
|
239
|
+
expect(updateAccountAdmin).toHaveBeenCalledWith('test-project', 'uid', { customAttributes: '{}' }, 'test-access-token', undefined, undefined);
|
|
240
|
+
});
|
|
241
|
+
it('validates UID and claims before authentication', async () => {
|
|
242
|
+
expect((await auth.setCustomUserClaims('', {})).error).not.toBeNull();
|
|
243
|
+
expect((await auth.setCustomUserClaims('a'.repeat(129), {})).error).not.toBeNull();
|
|
244
|
+
expect((await auth.setCustomUserClaims('uid', { sub: 'reserved' }))
|
|
245
|
+
.error).not.toBeNull();
|
|
246
|
+
expect(mockedGetToken).not.toHaveBeenCalled();
|
|
247
|
+
expect(updateAccountAdmin).not.toHaveBeenCalled();
|
|
248
|
+
});
|
|
249
|
+
it('uses cached credentials', async () => {
|
|
250
|
+
const cache = {
|
|
251
|
+
getCache: vi.fn().mockResolvedValue(mockGoogleTokenResponse),
|
|
252
|
+
setCache: vi.fn()
|
|
253
|
+
};
|
|
254
|
+
const instance = new FirebaseAdminAuth(serviceAccountKey, undefined, undefined, cache);
|
|
255
|
+
expect((await instance.setCustomUserClaims('uid', {})).error).toBeNull();
|
|
256
|
+
expect(mockedGetToken).not.toHaveBeenCalled();
|
|
257
|
+
});
|
|
258
|
+
it('returns credential failures without updating', async () => {
|
|
259
|
+
const error = new FirebaseEdgeError(FirebaseAdminAuthErrorInfo.ADMIN_SERVICE_ACCOUNT_TOKEN_FAILED);
|
|
260
|
+
mockedGetToken.mockResolvedValue({ data: null, error });
|
|
261
|
+
expect(await auth.setCustomUserClaims('uid', {})).toEqual({
|
|
262
|
+
data: null,
|
|
263
|
+
error
|
|
264
|
+
});
|
|
265
|
+
expect(updateAccountAdmin).not.toHaveBeenCalled();
|
|
266
|
+
});
|
|
267
|
+
it('guards against missing access tokens', async () => {
|
|
268
|
+
mockedGetToken.mockResolvedValue({
|
|
269
|
+
data: {},
|
|
270
|
+
error: null
|
|
271
|
+
});
|
|
272
|
+
expect((await auth.setCustomUserClaims('uid', {})).error?.code).toBe('auth/admin-no-token-returned');
|
|
273
|
+
expect(updateAccountAdmin).not.toHaveBeenCalled();
|
|
274
|
+
});
|
|
275
|
+
it('preserves endpoint errors including user-not-found', async () => {
|
|
276
|
+
const cause = new FirebaseEdgeError(FirebaseEndpointErrorInfo.ENDPOINT_USER_NOT_FOUND);
|
|
277
|
+
vi.mocked(updateAccountAdmin).mockResolvedValue({
|
|
278
|
+
data: null,
|
|
279
|
+
error: cause
|
|
280
|
+
});
|
|
281
|
+
const result = await auth.setCustomUserClaims('missing', {});
|
|
282
|
+
expect(result.data).toBeNull();
|
|
283
|
+
expect(result.error?.code).toBe('auth/admin-set-custom-claims-failed');
|
|
284
|
+
expect(result.error?.cause).toBe(cause);
|
|
285
|
+
});
|
|
286
|
+
it('rejects missing confirmation data', async () => {
|
|
287
|
+
vi.mocked(updateAccountAdmin).mockResolvedValue({
|
|
288
|
+
data: { localId: '' },
|
|
289
|
+
error: null
|
|
290
|
+
});
|
|
291
|
+
expect((await auth.setCustomUserClaims('uid', {})).error?.code).toBe('auth/admin-set-custom-claims-failed');
|
|
292
|
+
});
|
|
293
|
+
it('returns network and credential exceptions instead of rejecting', async () => {
|
|
294
|
+
const cause = new Error('offline');
|
|
295
|
+
vi.mocked(updateAccountAdmin).mockRejectedValue(cause);
|
|
296
|
+
expect((await auth.setCustomUserClaims('uid', {})).error?.cause).toBe(cause);
|
|
297
|
+
mockedGetToken.mockRejectedValue('token failure');
|
|
298
|
+
expect((await auth.setCustomUserClaims('uid', {})).error?.cause).toBeInstanceOf(Error);
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
describe('phone lookup and bulk user management', () => {
|
|
302
|
+
beforeEach(() => {
|
|
303
|
+
mockedGetToken.mockResolvedValue({
|
|
304
|
+
data: mockGoogleTokenResponse,
|
|
305
|
+
error: null
|
|
306
|
+
});
|
|
307
|
+
mockedGetAccountInfo.mockResolvedValue({
|
|
308
|
+
data: mockUserRecord,
|
|
309
|
+
error: null
|
|
310
|
+
});
|
|
311
|
+
vi.mocked(deleteAccountsAdmin).mockResolvedValue({
|
|
312
|
+
data: {},
|
|
313
|
+
error: null
|
|
314
|
+
});
|
|
315
|
+
vi.mocked(importAccountsAdmin).mockResolvedValue({
|
|
316
|
+
data: {},
|
|
317
|
+
error: null
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
it('looks up a phone number and returns a converted user record', async () => {
|
|
321
|
+
const customFetch = vi.fn();
|
|
322
|
+
const instance = new FirebaseAdminAuth(serviceAccountKey, 'tenant', customFetch);
|
|
323
|
+
const result = await instance.getUserByPhoneNumber('+15555550100');
|
|
324
|
+
expect(result.error).toBeNull();
|
|
325
|
+
expect(result.data?.uid).toBe('uid-1');
|
|
326
|
+
expect(result.data?.toJSON()).toMatchObject({ uid: 'uid-1' });
|
|
327
|
+
expect(getAccountInfo).toHaveBeenCalledWith({ phoneNumber: '+15555550100' }, 'test-access-token', 'test-project', 'tenant', customFetch);
|
|
328
|
+
});
|
|
329
|
+
it('returns user-not-found when no phone match exists', async () => {
|
|
330
|
+
mockedGetAccountInfo.mockResolvedValue({ data: null, error: null });
|
|
331
|
+
expect((await auth.getUserByPhoneNumber('+15555550100')).error?.code).toBe('auth/admin-user-not-found');
|
|
332
|
+
});
|
|
333
|
+
it.each(['', '555', null, 1])('rejects invalid phone number %s before authentication', async (phone) => {
|
|
334
|
+
expect((await auth.getUserByPhoneNumber(phone)).error).toBeInstanceOf(FirebaseEdgeError);
|
|
335
|
+
expect(mockedGetToken).not.toHaveBeenCalled();
|
|
336
|
+
});
|
|
337
|
+
it('deletes a batch with tenant, custom fetch, counts, and indexed errors', async () => {
|
|
338
|
+
const customFetch = vi.fn();
|
|
339
|
+
const instance = new FirebaseAdminAuth(serviceAccountKey, 'tenant', customFetch);
|
|
340
|
+
vi.mocked(deleteAccountsAdmin).mockResolvedValue({
|
|
341
|
+
data: { errors: [{ index: 1, message: 'failure' }] },
|
|
342
|
+
error: null
|
|
343
|
+
});
|
|
344
|
+
const result = await instance.deleteUsers([
|
|
345
|
+
'one',
|
|
346
|
+
'two',
|
|
347
|
+
'missing'
|
|
348
|
+
]);
|
|
349
|
+
expect(result.error).toBeNull();
|
|
350
|
+
expect(result.data).toMatchObject({
|
|
351
|
+
successCount: 2,
|
|
352
|
+
failureCount: 1,
|
|
353
|
+
errors: [{ index: 1, error: { message: 'failure' } }]
|
|
354
|
+
});
|
|
355
|
+
expect(deleteAccountsAdmin).toHaveBeenCalledWith('test-project', ['one', 'two', 'missing'], 'test-access-token', customFetch, 'tenant');
|
|
356
|
+
});
|
|
357
|
+
it('returns empty delete and import results without network requests', async () => {
|
|
358
|
+
const result = {
|
|
359
|
+
data: { successCount: 0, failureCount: 0, errors: [] },
|
|
360
|
+
error: null
|
|
361
|
+
};
|
|
362
|
+
expect(await auth.deleteUsers([])).toEqual(result);
|
|
363
|
+
expect(await auth.importUsers([])).toEqual(result);
|
|
364
|
+
expect(mockedGetToken).not.toHaveBeenCalled();
|
|
365
|
+
});
|
|
366
|
+
it('rejects invalid delete batches and oversized imports before authentication', async () => {
|
|
367
|
+
expect((await auth.deleteUsers(['one', ''])).error).not.toBeNull();
|
|
368
|
+
expect((await auth.deleteUsers(Array(1001).fill('uid'))).error).not.toBeNull();
|
|
369
|
+
expect((await auth.importUsers(Array(1001).fill({ uid: 'uid' }))).error).not.toBeNull();
|
|
370
|
+
expect(mockedGetToken).not.toHaveBeenCalled();
|
|
371
|
+
});
|
|
372
|
+
it('merges local and server import failures using original indices', async () => {
|
|
373
|
+
const customFetch = vi.fn();
|
|
374
|
+
const instance = new FirebaseAdminAuth(serviceAccountKey, 'tenant', customFetch);
|
|
375
|
+
vi.mocked(importAccountsAdmin).mockResolvedValue({
|
|
376
|
+
data: { error: [{ index: 1, message: 'DUPLICATE_LOCAL_ID' }] },
|
|
377
|
+
error: null
|
|
378
|
+
});
|
|
379
|
+
const result = await instance.importUsers([
|
|
380
|
+
{ uid: '' },
|
|
381
|
+
{ uid: 'one' },
|
|
382
|
+
{ uid: 'two' }
|
|
383
|
+
]);
|
|
384
|
+
expect(result.error).toBeNull();
|
|
385
|
+
expect(result.data).toMatchObject({
|
|
386
|
+
successCount: 1,
|
|
387
|
+
failureCount: 2,
|
|
388
|
+
errors: [{ index: 0 }, { index: 2 }]
|
|
389
|
+
});
|
|
390
|
+
expect(importAccountsAdmin).toHaveBeenCalledWith('test-project', { users: [{ localId: 'one' }, { localId: 'two' }] }, 'test-access-token', customFetch, 'tenant');
|
|
391
|
+
});
|
|
392
|
+
it('returns all-invalid imports locally and rejects missing password settings', async () => {
|
|
393
|
+
expect(await auth.importUsers([{ uid: '' }])).toMatchObject({
|
|
394
|
+
data: { successCount: 0, failureCount: 1 },
|
|
395
|
+
error: null
|
|
396
|
+
});
|
|
397
|
+
expect((await auth.importUsers([
|
|
398
|
+
{ uid: 'uid', passwordHash: new Uint8Array([1]) }
|
|
399
|
+
])).error).not.toBeNull();
|
|
400
|
+
expect(mockedGetToken).not.toHaveBeenCalled();
|
|
401
|
+
expect(importAccountsAdmin).not.toHaveBeenCalled();
|
|
402
|
+
});
|
|
403
|
+
it('sends the selected hashing configuration with password imports', async () => {
|
|
404
|
+
await auth.importUsers([{ uid: 'uid', passwordHash: new Uint8Array([1]) }], { hash: { algorithm: 'BCRYPT' } });
|
|
405
|
+
expect(importAccountsAdmin).toHaveBeenCalledWith('test-project', {
|
|
406
|
+
users: [{ localId: 'uid', passwordHash: 'AQ==' }],
|
|
407
|
+
hashAlgorithm: 'BCRYPT'
|
|
408
|
+
}, 'test-access-token', undefined, undefined);
|
|
409
|
+
});
|
|
410
|
+
it('uses cached credentials for all three methods', async () => {
|
|
411
|
+
const cache = {
|
|
412
|
+
getCache: vi.fn().mockResolvedValue(mockGoogleTokenResponse),
|
|
413
|
+
setCache: vi.fn()
|
|
414
|
+
};
|
|
415
|
+
const instance = new FirebaseAdminAuth(serviceAccountKey, undefined, undefined, cache);
|
|
416
|
+
expect((await instance.getUserByPhoneNumber('+15555550100')).error).toBeNull();
|
|
417
|
+
expect((await instance.deleteUsers(['uid'])).error).toBeNull();
|
|
418
|
+
expect((await instance.importUsers([{ uid: 'uid' }])).error).toBeNull();
|
|
419
|
+
expect(mockedGetToken).not.toHaveBeenCalled();
|
|
420
|
+
});
|
|
421
|
+
const operations = [
|
|
422
|
+
{
|
|
423
|
+
name: 'phone',
|
|
424
|
+
run: () => auth.getUserByPhoneNumber('+15555550100'),
|
|
425
|
+
endpoint: vi.mocked(getAccountInfo)
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
name: 'delete',
|
|
429
|
+
run: () => auth.deleteUsers(['uid']),
|
|
430
|
+
endpoint: vi.mocked(deleteAccountsAdmin)
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
name: 'import',
|
|
434
|
+
run: () => auth.importUsers([{ uid: 'uid' }]),
|
|
435
|
+
endpoint: vi.mocked(importAccountsAdmin)
|
|
436
|
+
}
|
|
437
|
+
];
|
|
438
|
+
describe.each(operations)('$name failures', ({ run, endpoint }) => {
|
|
439
|
+
it('propagates token failures without calling the endpoint', async () => {
|
|
440
|
+
const error = new FirebaseEdgeError(FirebaseAdminAuthErrorInfo.ADMIN_SERVICE_ACCOUNT_TOKEN_FAILED);
|
|
441
|
+
mockedGetToken.mockResolvedValue({ data: null, error });
|
|
442
|
+
expect(await run()).toEqual({ data: null, error });
|
|
443
|
+
expect(endpoint).not.toHaveBeenCalled();
|
|
444
|
+
});
|
|
445
|
+
it('guards against a missing access token', async () => {
|
|
446
|
+
mockedGetToken.mockResolvedValue({
|
|
447
|
+
data: {},
|
|
448
|
+
error: null
|
|
449
|
+
});
|
|
450
|
+
expect((await run()).error?.code).toBe('auth/admin-no-token-returned');
|
|
451
|
+
expect(endpoint).not.toHaveBeenCalled();
|
|
452
|
+
});
|
|
453
|
+
it('returns endpoint errors and network exceptions', async () => {
|
|
454
|
+
const error = new FirebaseEdgeError(FirebaseEndpointErrorInfo.ENDPOINT_PERMISSION_DENIED);
|
|
455
|
+
endpoint.mockResolvedValue({ data: null, error });
|
|
456
|
+
expect(await run()).toMatchObject({
|
|
457
|
+
data: null,
|
|
458
|
+
error: expect.any(FirebaseEdgeError)
|
|
459
|
+
});
|
|
460
|
+
endpoint.mockRejectedValue(new Error('offline'));
|
|
461
|
+
expect(await run()).toMatchObject({
|
|
462
|
+
data: null,
|
|
463
|
+
error: expect.any(FirebaseEdgeError)
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
it('returns errors for corrupt bulk responses instead of misleading counts', async () => {
|
|
468
|
+
vi.mocked(deleteAccountsAdmin).mockResolvedValue({
|
|
469
|
+
data: { errors: [{ index: 5 }] },
|
|
470
|
+
error: null
|
|
471
|
+
});
|
|
472
|
+
vi.mocked(importAccountsAdmin).mockResolvedValue({
|
|
473
|
+
data: { error: [{ index: 5 }] },
|
|
474
|
+
error: null
|
|
475
|
+
});
|
|
476
|
+
expect((await auth.deleteUsers(['uid'])).error).not.toBeNull();
|
|
477
|
+
expect((await auth.importUsers([{ uid: 'uid' }])).error).not.toBeNull();
|
|
478
|
+
});
|
|
479
|
+
});
|
|
84
480
|
describe('getUsers', () => {
|
|
85
481
|
beforeEach(() => {
|
|
86
482
|
mockedGetToken.mockResolvedValue({
|
|
@@ -688,13 +1084,186 @@ describe('FirebaseAdminAuth', () => {
|
|
|
688
1084
|
expect(result.error).toBeNull();
|
|
689
1085
|
});
|
|
690
1086
|
});
|
|
1087
|
+
describe.each([
|
|
1088
|
+
['generatePasswordResetLink', 'PASSWORD_RESET'],
|
|
1089
|
+
['generateEmailVerificationLink', 'VERIFY_EMAIL'],
|
|
1090
|
+
['generateSignInWithEmailLink', 'EMAIL_SIGNIN']
|
|
1091
|
+
])('%s', (method, requestType) => {
|
|
1092
|
+
const settings = {
|
|
1093
|
+
url: 'https://example.com/finish',
|
|
1094
|
+
handleCodeInApp: true
|
|
1095
|
+
};
|
|
1096
|
+
it('returns the generated link and forwards credentials, settings, tenant and fetch', async () => {
|
|
1097
|
+
const fetchFn = vi.fn();
|
|
1098
|
+
const admin = new FirebaseAdminAuth(serviceAccountKey, 'tenant', fetchFn);
|
|
1099
|
+
mockedGetToken.mockResolvedValue({
|
|
1100
|
+
data: mockGoogleTokenResponse,
|
|
1101
|
+
error: null
|
|
1102
|
+
});
|
|
1103
|
+
vi.mocked(generateEmailActionLink).mockResolvedValue({
|
|
1104
|
+
data: 'https://example.com/action',
|
|
1105
|
+
error: null
|
|
1106
|
+
});
|
|
1107
|
+
expect(await admin[method]('person@example.com', settings)).toEqual({ data: 'https://example.com/action', error: null });
|
|
1108
|
+
expect(generateEmailActionLink).toHaveBeenCalledWith('test-project', {
|
|
1109
|
+
requestType,
|
|
1110
|
+
email: 'person@example.com',
|
|
1111
|
+
returnOobLink: true,
|
|
1112
|
+
continueUrl: settings.url,
|
|
1113
|
+
canHandleCodeInApp: true
|
|
1114
|
+
}, 'test-access-token', fetchFn, 'tenant');
|
|
1115
|
+
});
|
|
1116
|
+
it('rejects invalid input before obtaining credentials', async () => {
|
|
1117
|
+
expect((await auth[method]('invalid', settings)).error?.code).toBe(FirebaseAdminAuthErrorInfo.ADMIN_API_INVALID_ARGUMENT.code);
|
|
1118
|
+
expect(mockedGetToken).not.toHaveBeenCalled();
|
|
1119
|
+
expect(generateEmailActionLink).not.toHaveBeenCalled();
|
|
1120
|
+
});
|
|
1121
|
+
it('returns authentication errors without calling the endpoint', async () => {
|
|
1122
|
+
mockedGetToken.mockResolvedValue({
|
|
1123
|
+
data: null,
|
|
1124
|
+
error: new Error('credentials')
|
|
1125
|
+
});
|
|
1126
|
+
expect((await auth[method]('person@example.com', settings)).error?.code).toBe(FirebaseAdminAuthErrorInfo.ADMIN_SERVICE_ACCOUNT_TOKEN_FAILED
|
|
1127
|
+
.code);
|
|
1128
|
+
expect(generateEmailActionLink).not.toHaveBeenCalled();
|
|
1129
|
+
});
|
|
1130
|
+
it('wraps endpoint errors and preserves the cause', async () => {
|
|
1131
|
+
const cause = new Error('email not found');
|
|
1132
|
+
mockedGetToken.mockResolvedValue({
|
|
1133
|
+
data: mockGoogleTokenResponse,
|
|
1134
|
+
error: null
|
|
1135
|
+
});
|
|
1136
|
+
vi.mocked(generateEmailActionLink).mockResolvedValue({
|
|
1137
|
+
data: null,
|
|
1138
|
+
error: cause
|
|
1139
|
+
});
|
|
1140
|
+
const result = await auth[method]('person@example.com', settings);
|
|
1141
|
+
expect(result.error?.code).toBe(FirebaseAdminAuthErrorInfo.ADMIN_EMAIL_ACTION_LINK_FAILED.code);
|
|
1142
|
+
expect(result.error?.cause).toBe(cause);
|
|
1143
|
+
});
|
|
1144
|
+
it('returns unexpected failures using the package error convention', async () => {
|
|
1145
|
+
mockedGetToken.mockResolvedValue({
|
|
1146
|
+
data: mockGoogleTokenResponse,
|
|
1147
|
+
error: null
|
|
1148
|
+
});
|
|
1149
|
+
vi.mocked(generateEmailActionLink).mockRejectedValue(new Error('network'));
|
|
1150
|
+
expect((await auth[method]('person@example.com', settings)).error?.code).toBe(FirebaseAdminAuthErrorInfo.ADMIN_EMAIL_ACTION_LINK_FAILED.code);
|
|
1151
|
+
});
|
|
1152
|
+
});
|
|
1153
|
+
it.each([
|
|
1154
|
+
'generatePasswordResetLink',
|
|
1155
|
+
'generateEmailVerificationLink'
|
|
1156
|
+
])('%s allows omitted settings', async (method) => {
|
|
1157
|
+
mockedGetToken.mockResolvedValue({
|
|
1158
|
+
data: mockGoogleTokenResponse,
|
|
1159
|
+
error: null
|
|
1160
|
+
});
|
|
1161
|
+
vi.mocked(generateEmailActionLink).mockResolvedValue({
|
|
1162
|
+
data: 'https://example.com/action',
|
|
1163
|
+
error: null
|
|
1164
|
+
});
|
|
1165
|
+
expect((await auth[method]('person@example.com')).error).toBeNull();
|
|
1166
|
+
expect(generateEmailActionLink).toHaveBeenCalledWith('test-project', expect.not.objectContaining({ continueUrl: expect.anything() }), 'test-access-token', undefined, undefined);
|
|
1167
|
+
});
|
|
1168
|
+
it('requires settings when generating an email sign-in link', async () => {
|
|
1169
|
+
const result =
|
|
1170
|
+
// @ts-expect-error Settings are required for email sign-in.
|
|
1171
|
+
await auth.generateSignInWithEmailLink('person@example.com');
|
|
1172
|
+
expect(result.error?.code).toBe(FirebaseAdminAuthErrorInfo.ADMIN_API_INVALID_ARGUMENT.code);
|
|
1173
|
+
expect(mockedGetToken).not.toHaveBeenCalled();
|
|
1174
|
+
});
|
|
1175
|
+
it.each([
|
|
1176
|
+
'generatePasswordResetLink',
|
|
1177
|
+
'generateEmailVerificationLink',
|
|
1178
|
+
'generateSignInWithEmailLink',
|
|
1179
|
+
'createSessionCookie'
|
|
1180
|
+
])('%s rejects missing access tokens', async (method) => {
|
|
1181
|
+
mockedGetToken.mockResolvedValue({
|
|
1182
|
+
data: { ...mockGoogleTokenResponse, access_token: '' },
|
|
1183
|
+
error: null
|
|
1184
|
+
});
|
|
1185
|
+
const result = method === 'createSessionCookie'
|
|
1186
|
+
? await auth.createSessionCookie('id-token', {
|
|
1187
|
+
expiresIn: 300000
|
|
1188
|
+
})
|
|
1189
|
+
: await auth[method]('person@example.com', {
|
|
1190
|
+
url: 'https://example.com'
|
|
1191
|
+
});
|
|
1192
|
+
expect(result.error?.code).toBe(FirebaseAdminAuthErrorInfo.ADMIN_NO_TOKEN_RETURNED.code);
|
|
1193
|
+
expect(generateEmailActionLink).not.toHaveBeenCalled();
|
|
1194
|
+
expect(mockedCreateSessionCookieEndpoint).not.toHaveBeenCalled();
|
|
1195
|
+
});
|
|
691
1196
|
describe('createSessionCookie', () => {
|
|
1197
|
+
it.each([
|
|
1198
|
+
undefined,
|
|
1199
|
+
null,
|
|
1200
|
+
3600000,
|
|
1201
|
+
{},
|
|
1202
|
+
{ expiresIn: '3600000' },
|
|
1203
|
+
{ expiresIn: NaN },
|
|
1204
|
+
{ expiresIn: Infinity },
|
|
1205
|
+
{ expiresIn: 299999 },
|
|
1206
|
+
{ expiresIn: 1209600001 }
|
|
1207
|
+
])('rejects invalid options %j before authentication', async (options) => {
|
|
1208
|
+
const result = await auth.createSessionCookie('id-token', options);
|
|
1209
|
+
expect(result.error?.code).toBe(FirebaseAdminAuthErrorInfo
|
|
1210
|
+
.ADMIN_SESSION_COOKIE_DURATION_INVALID.code);
|
|
1211
|
+
expect(mockedGetToken).not.toHaveBeenCalled();
|
|
1212
|
+
});
|
|
1213
|
+
it.each(['', null, 1])('rejects invalid ID token %s', async (idToken) => {
|
|
1214
|
+
const result = await auth.createSessionCookie(idToken, { expiresIn: 300000 });
|
|
1215
|
+
expect(result.error?.code).toBe(FirebaseAdminAuthErrorInfo.ADMIN_ID_TOKEN_INVALID.code);
|
|
1216
|
+
expect(mockedGetToken).not.toHaveBeenCalled();
|
|
1217
|
+
});
|
|
1218
|
+
it.each([300000, 1209600000])('accepts boundary lifetime %s and forwards tenant and fetch', async (expiresIn) => {
|
|
1219
|
+
const fetchFn = vi.fn();
|
|
1220
|
+
const admin = new FirebaseAdminAuth(serviceAccountKey, 'tenant', fetchFn);
|
|
1221
|
+
mockedGetToken.mockResolvedValue({
|
|
1222
|
+
data: mockGoogleTokenResponse,
|
|
1223
|
+
error: null
|
|
1224
|
+
});
|
|
1225
|
+
mockedCreateSessionCookieEndpoint.mockResolvedValue({
|
|
1226
|
+
data: 'cookie',
|
|
1227
|
+
error: null
|
|
1228
|
+
});
|
|
1229
|
+
expect(await admin.createSessionCookie('id-token', { expiresIn })).toEqual({ data: 'cookie', error: null });
|
|
1230
|
+
expect(mockedCreateSessionCookieEndpoint).toHaveBeenCalledWith('id-token', 'test-access-token', 'test-project', expiresIn, 'tenant', fetchFn);
|
|
1231
|
+
});
|
|
1232
|
+
it('returns an error for an empty cookie response', async () => {
|
|
1233
|
+
mockedGetToken.mockResolvedValue({
|
|
1234
|
+
data: mockGoogleTokenResponse,
|
|
1235
|
+
error: null
|
|
1236
|
+
});
|
|
1237
|
+
mockedCreateSessionCookieEndpoint.mockResolvedValue({
|
|
1238
|
+
data: null,
|
|
1239
|
+
error: null
|
|
1240
|
+
});
|
|
1241
|
+
const result = await auth.createSessionCookie('id-token', {
|
|
1242
|
+
expiresIn: 300000
|
|
1243
|
+
});
|
|
1244
|
+
expect(result.error?.code).toBe(FirebaseAdminAuthErrorInfo.ADMIN_SESSION_COOKIE_CREATE_FAILED
|
|
1245
|
+
.code);
|
|
1246
|
+
});
|
|
1247
|
+
it('wraps unexpected endpoint failures', async () => {
|
|
1248
|
+
mockedGetToken.mockResolvedValue({
|
|
1249
|
+
data: mockGoogleTokenResponse,
|
|
1250
|
+
error: null
|
|
1251
|
+
});
|
|
1252
|
+
mockedCreateSessionCookieEndpoint.mockRejectedValue(new Error('network'));
|
|
1253
|
+
const result = await auth.createSessionCookie('id-token', {
|
|
1254
|
+
expiresIn: 300000
|
|
1255
|
+
});
|
|
1256
|
+
expect(result.error?.code).toBe(FirebaseAdminAuthErrorInfo.ADMIN_SESSION_COOKIE_CREATE_FAILED
|
|
1257
|
+
.code);
|
|
1258
|
+
});
|
|
692
1259
|
it('returns error when getToken fails', async () => {
|
|
693
1260
|
mockedGetToken.mockResolvedValueOnce({
|
|
694
1261
|
data: null,
|
|
695
1262
|
error: new Error('unauthorized')
|
|
696
1263
|
});
|
|
697
|
-
const result = await auth.createSessionCookie('id-token',
|
|
1264
|
+
const result = await auth.createSessionCookie('id-token', {
|
|
1265
|
+
expiresIn: 3600000
|
|
1266
|
+
});
|
|
698
1267
|
expect(result.data).toBeNull();
|
|
699
1268
|
expect(result.error).toBeInstanceOf(FirebaseEdgeError);
|
|
700
1269
|
expect(result.error.code).toBe(FirebaseAdminAuthErrorInfo.ADMIN_SERVICE_ACCOUNT_TOKEN_FAILED
|
|
@@ -705,7 +1274,9 @@ describe('FirebaseAdminAuth', () => {
|
|
|
705
1274
|
data: null,
|
|
706
1275
|
error: new FirebaseEdgeError(FirebaseAdminAuthErrorInfo.ADMIN_SERVICE_ACCOUNT_TOKEN_FAILED)
|
|
707
1276
|
});
|
|
708
|
-
const result = await auth.createSessionCookie('id-token',
|
|
1277
|
+
const result = await auth.createSessionCookie('id-token', {
|
|
1278
|
+
expiresIn: 3600000
|
|
1279
|
+
});
|
|
709
1280
|
expect(result.data).toBeNull();
|
|
710
1281
|
expect(result.error).toBeInstanceOf(FirebaseEdgeError);
|
|
711
1282
|
expect(result.error.code).toBe(FirebaseAdminAuthErrorInfo.ADMIN_SERVICE_ACCOUNT_TOKEN_FAILED
|
|
@@ -721,8 +1292,10 @@ describe('FirebaseAdminAuth', () => {
|
|
|
721
1292
|
data: null,
|
|
722
1293
|
error: endpointError
|
|
723
1294
|
});
|
|
724
|
-
const result = await auth.createSessionCookie('id-token',
|
|
725
|
-
|
|
1295
|
+
const result = await auth.createSessionCookie('id-token', {
|
|
1296
|
+
expiresIn: 3600000
|
|
1297
|
+
});
|
|
1298
|
+
expect(mockedCreateSessionCookieEndpoint).toHaveBeenCalledWith('id-token', 'test-access-token', 'test-project', 3600000, undefined, undefined);
|
|
726
1299
|
expect(result.data).toBeNull();
|
|
727
1300
|
expect(result.error).toBeInstanceOf(FirebaseEdgeError);
|
|
728
1301
|
expect(result.error.code).toBe(FirebaseAdminAuthErrorInfo.ADMIN_SESSION_COOKIE_CREATE_FAILED
|
|
@@ -738,12 +1311,58 @@ describe('FirebaseAdminAuth', () => {
|
|
|
738
1311
|
data: cookieData,
|
|
739
1312
|
error: null
|
|
740
1313
|
});
|
|
741
|
-
const result = await auth.createSessionCookie('id-token',
|
|
1314
|
+
const result = await auth.createSessionCookie('id-token', {
|
|
1315
|
+
expiresIn: 3600000
|
|
1316
|
+
});
|
|
742
1317
|
expect(result.data).toEqual(cookieData);
|
|
743
1318
|
expect(result.error).toBeNull();
|
|
744
1319
|
});
|
|
745
1320
|
});
|
|
746
1321
|
describe('verifySessionCookie', () => {
|
|
1322
|
+
it('rejects disabled users when revocation checking is enabled', async () => {
|
|
1323
|
+
mockedVerifySessionJWT.mockResolvedValue({
|
|
1324
|
+
data: mockFirebasePayload,
|
|
1325
|
+
error: null
|
|
1326
|
+
});
|
|
1327
|
+
vi.spyOn(auth, 'getUser').mockResolvedValue({
|
|
1328
|
+
data: { ...mockUserRecord, disabled: true },
|
|
1329
|
+
error: null
|
|
1330
|
+
});
|
|
1331
|
+
expect((await auth.verifySessionCookie('cookie', true)).error?.code).toBe(FirebaseAdminAuthErrorInfo.ADMIN_USER_DISABLED.code);
|
|
1332
|
+
});
|
|
1333
|
+
it.each([
|
|
1334
|
+
[
|
|
1335
|
+
'1001',
|
|
1336
|
+
FirebaseAdminAuthErrorInfo.ADMIN_SESSION_COOKIE_REVOKED.code
|
|
1337
|
+
],
|
|
1338
|
+
['1000', undefined],
|
|
1339
|
+
['999', undefined],
|
|
1340
|
+
[undefined, undefined]
|
|
1341
|
+
])('checks auth_time against validSince %s, not issuance time', async (validSince, expectedCode) => {
|
|
1342
|
+
mockedVerifySessionJWT.mockResolvedValue({
|
|
1343
|
+
data: { ...mockFirebasePayload, iat: 1500 },
|
|
1344
|
+
error: null
|
|
1345
|
+
});
|
|
1346
|
+
vi.spyOn(auth, 'getUser').mockResolvedValue({
|
|
1347
|
+
data: { ...mockUserRecord, validSince },
|
|
1348
|
+
error: null
|
|
1349
|
+
});
|
|
1350
|
+
const result = await auth.verifySessionCookie('cookie', true);
|
|
1351
|
+
expect(result.error?.code).toBe(expectedCode);
|
|
1352
|
+
expect(result.data === null).toBe(Boolean(expectedCode));
|
|
1353
|
+
});
|
|
1354
|
+
it('skips the user lookup by default', async () => {
|
|
1355
|
+
mockedVerifySessionJWT.mockResolvedValue({
|
|
1356
|
+
data: mockFirebasePayload,
|
|
1357
|
+
error: null
|
|
1358
|
+
});
|
|
1359
|
+
const lookup = vi.spyOn(auth, 'getUser');
|
|
1360
|
+
expect(await auth.verifySessionCookie('cookie')).toEqual({
|
|
1361
|
+
data: mockFirebasePayload,
|
|
1362
|
+
error: null
|
|
1363
|
+
});
|
|
1364
|
+
expect(lookup).not.toHaveBeenCalled();
|
|
1365
|
+
});
|
|
747
1366
|
it('returns error when verifySessionJWT fails', async () => {
|
|
748
1367
|
mockedVerifySessionJWT.mockResolvedValueOnce({
|
|
749
1368
|
data: null,
|