@quatrain/auth-firebase 1.1.13 → 1.1.14
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/LICENSE.md +15 -0
- package/README.md +94 -0
- package/lib/FirebaseAuthAdapter.test.d.ts +1 -0
- package/lib/FirebaseAuthAdapter.test.js +303 -0
- package/package.json +42 -41
- package/src/FirebaseAuthAdapter.test.ts +357 -0
- package/src/FirebaseAuthAdapter.ts +99 -0
- package/src/index.ts +3 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# LICENSE UPDATE NOTICE
|
|
2
|
+
|
|
3
|
+
As of 01/01/2026, Quatrain Core is licensed under the **GNU Affero General Public License v3.0 (AGPL v3)**.
|
|
4
|
+
Previous versions remain under the MIT License.
|
|
5
|
+
|
|
6
|
+
## Why AGPL?
|
|
7
|
+
|
|
8
|
+
We believe in open collaboration for the development ecosystem. The AGPL ensures that any modification or deployment of this BaaS stack, including over a network, benefits the entire community.
|
|
9
|
+
|
|
10
|
+
## Commercial Services & Enterprise Usage
|
|
11
|
+
|
|
12
|
+
We provide official deployment services, technical training, and certification for Quatrain Core.
|
|
13
|
+
For organizations requiring a non-copyleft license (commercial license) or custom proprietary integrations, please contact the copyright holder: **Quatrain Technologies**.
|
|
14
|
+
|
|
15
|
+
Copyright © 2024-2026 Quatrain Technologies. All Rights Reserved.
|
package/README.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# @quatrain/auth-firebase
|
|
2
|
+
|
|
3
|
+
An authentication adapter for Firebase Authentication. This package integrates Quatrain's auth system with Google's managed authentication service.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Implements the `@quatrain/auth` abstract adapter.
|
|
8
|
+
- Supports email/password, social logins (Google, Facebook, etc.), and custom tokens.
|
|
9
|
+
- Integrates with Firebase security rules.
|
|
10
|
+
- Uses the `firebase-admin` SDK for server-side operations.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @quatrain/auth-firebase firebase-admin
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
### Setup
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { Auth } from '@quatrain/auth'
|
|
24
|
+
import { FirebaseAuthAdapter } from '@quatrain/auth-firebase'
|
|
25
|
+
|
|
26
|
+
const adapter = new FirebaseAuthAdapter({
|
|
27
|
+
config: {
|
|
28
|
+
projectId: 'your-project-id',
|
|
29
|
+
// Other Firebase Admin SDK config
|
|
30
|
+
apiKey: 'your-api-key', // Required for token refresh
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
Auth.addProvider(adapter, 'default', true)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Register a New User
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import { User } from '@quatrain/backend'
|
|
40
|
+
|
|
41
|
+
const user = new User({
|
|
42
|
+
uid: 'unique-user-id',
|
|
43
|
+
_: {
|
|
44
|
+
name: 'John Doe',
|
|
45
|
+
email: 'john@example.com',
|
|
46
|
+
phone: '+1234567890',
|
|
47
|
+
},
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
const userId = await adapter.register(user, 'password123')
|
|
51
|
+
console.log('User registered with ID:', userId)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Verify Auth Token
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
const decodedToken = await adapter.getAuthToken('firebase-id-token')
|
|
58
|
+
console.log('Token verified for user:', decodedToken.uid)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Update User Profile
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
await adapter.update(user, {
|
|
65
|
+
email: 'newemail@example.com',
|
|
66
|
+
displayName: 'Jane Doe',
|
|
67
|
+
phoneNumber: '+0987654321',
|
|
68
|
+
})
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Delete User
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
await adapter.delete(user)
|
|
75
|
+
console.log('User deleted')
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Refresh Token
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
const newTokens = await adapter.refreshToken(refreshToken)
|
|
82
|
+
console.log('New access token:', newTokens.access_token)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
> [!WARNING] > **Incomplete Implementation**
|
|
86
|
+
>
|
|
87
|
+
> The following methods are currently not implemented (empty stubs):
|
|
88
|
+
>
|
|
89
|
+
> - `signup()` - Client-side sign in
|
|
90
|
+
> - `signout()` - Sign out user
|
|
91
|
+
> - `revokeAuthToken()` - Revoke tokens
|
|
92
|
+
> - `setCustomUserClaims()` - Set custom claims
|
|
93
|
+
>
|
|
94
|
+
> These methods exist to satisfy the interface but do not perform any operations.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
28
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
29
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
30
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
31
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
|
+
const FirebaseAuthAdapter_1 = require("./FirebaseAuthAdapter");
|
|
36
|
+
const auth_1 = require("@quatrain/auth");
|
|
37
|
+
const auth_2 = require("firebase-admin/auth");
|
|
38
|
+
const app_1 = require("firebase-admin/app");
|
|
39
|
+
const nativeFetch = __importStar(require("node-fetch-native"));
|
|
40
|
+
// Mock the dependencies
|
|
41
|
+
jest.mock('firebase-admin/auth');
|
|
42
|
+
jest.mock('firebase-admin/app');
|
|
43
|
+
jest.mock('node-fetch-native');
|
|
44
|
+
describe('FirebaseAuthAdapter', () => {
|
|
45
|
+
let adapter;
|
|
46
|
+
let mockAuth;
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
// Clear all mocks before each test
|
|
49
|
+
jest.clearAllMocks();
|
|
50
|
+
// Mock Firebase Admin SDK
|
|
51
|
+
mockAuth = {
|
|
52
|
+
createUser: jest.fn(),
|
|
53
|
+
verifyIdToken: jest.fn(),
|
|
54
|
+
updateUser: jest.fn(),
|
|
55
|
+
deleteUser: jest.fn(),
|
|
56
|
+
};
|
|
57
|
+
auth_2.getAuth.mockReturnValue(mockAuth);
|
|
58
|
+
app_1.getApps.mockReturnValue([]);
|
|
59
|
+
// Create adapter instance
|
|
60
|
+
adapter = new FirebaseAuthAdapter_1.FirebaseAuthAdapter({
|
|
61
|
+
config: {
|
|
62
|
+
projectId: 'test-project',
|
|
63
|
+
apiKey: 'test-api-key',
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
afterEach(() => {
|
|
68
|
+
jest.restoreAllMocks();
|
|
69
|
+
});
|
|
70
|
+
describe('Constructor', () => {
|
|
71
|
+
it('should initialize Firebase app when no apps exist', () => {
|
|
72
|
+
expect(app_1.getApps).toHaveBeenCalled();
|
|
73
|
+
expect(app_1.initializeApp).toHaveBeenCalledWith({
|
|
74
|
+
projectId: 'test-project',
|
|
75
|
+
apiKey: 'test-api-key',
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
it('should not initialize Firebase app when app already exists', () => {
|
|
79
|
+
;
|
|
80
|
+
app_1.getApps.mockReturnValue([{ name: 'existing-app' }]);
|
|
81
|
+
new FirebaseAuthAdapter_1.FirebaseAuthAdapter({
|
|
82
|
+
config: {
|
|
83
|
+
projectId: 'test-project',
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
// initializeApp should still be called once from the first adapter creation
|
|
87
|
+
expect(app_1.initializeApp).toHaveBeenCalledTimes(1);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
describe('register()', () => {
|
|
91
|
+
const mockUser = {
|
|
92
|
+
uid: 'user-123',
|
|
93
|
+
_: {
|
|
94
|
+
name: 'John Doe',
|
|
95
|
+
email: 'john@example.com',
|
|
96
|
+
phone: '+1234567890',
|
|
97
|
+
password: 'hashed-password',
|
|
98
|
+
disabled: false,
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
it('should successfully register a new user', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
102
|
+
const mockUserRecord = {
|
|
103
|
+
uid: 'user-123',
|
|
104
|
+
email: 'john@example.com',
|
|
105
|
+
};
|
|
106
|
+
mockAuth.createUser.mockResolvedValue(mockUserRecord);
|
|
107
|
+
const result = yield adapter.register(mockUser, 'clearPassword123');
|
|
108
|
+
expect(mockAuth.createUser).toHaveBeenCalledWith({
|
|
109
|
+
uid: 'user-123',
|
|
110
|
+
email: 'john@example.com',
|
|
111
|
+
phoneNumber: '+1234567890',
|
|
112
|
+
password: 'clearPassword123',
|
|
113
|
+
disabled: false,
|
|
114
|
+
displayName: 'John Doe',
|
|
115
|
+
});
|
|
116
|
+
expect(result).toBe('user-123');
|
|
117
|
+
}));
|
|
118
|
+
it('should use hashed password when clearPassword is not provided', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
119
|
+
const mockUserRecord = {
|
|
120
|
+
uid: 'user-123',
|
|
121
|
+
email: 'john@example.com',
|
|
122
|
+
};
|
|
123
|
+
mockAuth.createUser.mockResolvedValue(mockUserRecord);
|
|
124
|
+
yield adapter.register(mockUser);
|
|
125
|
+
expect(mockAuth.createUser).toHaveBeenCalledWith({
|
|
126
|
+
uid: 'user-123',
|
|
127
|
+
email: 'john@example.com',
|
|
128
|
+
phoneNumber: '+1234567890',
|
|
129
|
+
password: 'hashed-password',
|
|
130
|
+
disabled: false,
|
|
131
|
+
displayName: 'John Doe',
|
|
132
|
+
});
|
|
133
|
+
}));
|
|
134
|
+
it('should handle user with default disabled value', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
135
|
+
const userWithoutDisabled = {
|
|
136
|
+
uid: 'user-456',
|
|
137
|
+
_: {
|
|
138
|
+
name: 'Jane Doe',
|
|
139
|
+
email: 'jane@example.com',
|
|
140
|
+
phone: '+0987654321',
|
|
141
|
+
password: 'password456',
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
mockAuth.createUser.mockResolvedValue({ uid: 'user-456' });
|
|
145
|
+
yield adapter.register(userWithoutDisabled, 'password');
|
|
146
|
+
const createUserCall = mockAuth.createUser.mock.calls[0][0];
|
|
147
|
+
expect(createUserCall.disabled).toBe(false);
|
|
148
|
+
}));
|
|
149
|
+
it('should throw AuthenticationError on Firebase error', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
150
|
+
mockAuth.createUser.mockRejectedValue(new Error('Email already exists'));
|
|
151
|
+
yield expect(adapter.register(mockUser, 'password')).rejects.toThrow(auth_1.AuthenticationError);
|
|
152
|
+
yield expect(adapter.register(mockUser, 'password')).rejects.toThrow('Email already exists');
|
|
153
|
+
}));
|
|
154
|
+
});
|
|
155
|
+
describe('getAuthToken()', () => {
|
|
156
|
+
it('should successfully verify auth token', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
157
|
+
const mockDecodedToken = {
|
|
158
|
+
uid: 'user-123',
|
|
159
|
+
email: 'test@example.com',
|
|
160
|
+
iat: Date.now(),
|
|
161
|
+
};
|
|
162
|
+
mockAuth.verifyIdToken.mockResolvedValue(mockDecodedToken);
|
|
163
|
+
const result = yield adapter.getAuthToken('bearer-token-123');
|
|
164
|
+
expect(mockAuth.verifyIdToken).toHaveBeenCalledWith('bearer-token-123');
|
|
165
|
+
expect(result).toEqual(mockDecodedToken);
|
|
166
|
+
}));
|
|
167
|
+
it('should throw error when token is invalid', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
168
|
+
mockAuth.verifyIdToken.mockRejectedValue(new Error('Invalid token'));
|
|
169
|
+
yield expect(adapter.getAuthToken('invalid-token')).rejects.toThrow('Invalid token');
|
|
170
|
+
}));
|
|
171
|
+
});
|
|
172
|
+
describe('update()', () => {
|
|
173
|
+
const mockUser = {
|
|
174
|
+
uid: 'user-123',
|
|
175
|
+
_: {
|
|
176
|
+
email: 'test@example.com',
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
it('should successfully update user', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
180
|
+
const updatable = {
|
|
181
|
+
email: 'newemail@example.com',
|
|
182
|
+
displayName: 'New Name',
|
|
183
|
+
};
|
|
184
|
+
mockAuth.updateUser.mockResolvedValue({});
|
|
185
|
+
yield adapter.update(mockUser, updatable);
|
|
186
|
+
expect(mockAuth.updateUser).toHaveBeenCalledWith('user-123', updatable);
|
|
187
|
+
}));
|
|
188
|
+
it('should skip update when updatable is empty', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
189
|
+
yield adapter.update(mockUser, {});
|
|
190
|
+
expect(mockAuth.updateUser).not.toHaveBeenCalled();
|
|
191
|
+
}));
|
|
192
|
+
it('should silently handle errors during update', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
193
|
+
const updatable = { displayName: 'New Name' };
|
|
194
|
+
mockAuth.updateUser.mockRejectedValue(new Error('Update failed'));
|
|
195
|
+
// Should not throw
|
|
196
|
+
yield expect(adapter.update(mockUser, updatable)).resolves.toBeUndefined();
|
|
197
|
+
expect(mockAuth.updateUser).toHaveBeenCalledWith('user-123', updatable);
|
|
198
|
+
}));
|
|
199
|
+
});
|
|
200
|
+
describe('delete()', () => {
|
|
201
|
+
const mockUser = {
|
|
202
|
+
uid: 'user-123',
|
|
203
|
+
};
|
|
204
|
+
it('should successfully delete user', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
205
|
+
mockAuth.deleteUser.mockResolvedValue(undefined);
|
|
206
|
+
yield adapter.delete(mockUser);
|
|
207
|
+
expect(mockAuth.deleteUser).toHaveBeenCalledWith('user-123');
|
|
208
|
+
}));
|
|
209
|
+
it('should throw error on deletion failure', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
210
|
+
mockAuth.deleteUser.mockRejectedValue(new Error('User not found'));
|
|
211
|
+
yield expect(adapter.delete(mockUser)).rejects.toThrow('User not found');
|
|
212
|
+
}));
|
|
213
|
+
});
|
|
214
|
+
describe('refreshToken()', () => {
|
|
215
|
+
it('should successfully refresh token', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
216
|
+
const mockResponse = {
|
|
217
|
+
access_token: 'new-access-token',
|
|
218
|
+
refresh_token: 'new-refresh-token',
|
|
219
|
+
expires_in: 3600,
|
|
220
|
+
};
|
|
221
|
+
const mockFetchResponse = {
|
|
222
|
+
json: jest.fn().mockResolvedValue(mockResponse),
|
|
223
|
+
};
|
|
224
|
+
nativeFetch.fetch.mockResolvedValue(mockFetchResponse);
|
|
225
|
+
const result = yield adapter.refreshToken('old-refresh-token');
|
|
226
|
+
expect(nativeFetch.fetch).toHaveBeenCalledWith('https://securetoken.googleapis.com/v1/token?key=test-api-key', {
|
|
227
|
+
method: 'POST',
|
|
228
|
+
body: JSON.stringify({
|
|
229
|
+
grant_type: 'refresh_token',
|
|
230
|
+
refresh_token: 'old-refresh-token',
|
|
231
|
+
}),
|
|
232
|
+
headers: { 'Content-Type': 'application/json' },
|
|
233
|
+
});
|
|
234
|
+
// Note: Implementation doesn't await json(), so result is a promise
|
|
235
|
+
const resultValue = yield result;
|
|
236
|
+
expect(resultValue).toEqual(mockResponse);
|
|
237
|
+
}));
|
|
238
|
+
it('should return empty object when API key is missing', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
239
|
+
const adapterWithoutKey = new FirebaseAuthAdapter_1.FirebaseAuthAdapter({
|
|
240
|
+
config: {
|
|
241
|
+
projectId: 'test-project',
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
const result = yield adapterWithoutKey.refreshToken('refresh-token');
|
|
245
|
+
expect(result).toEqual({});
|
|
246
|
+
expect(nativeFetch.fetch).not.toHaveBeenCalled();
|
|
247
|
+
}));
|
|
248
|
+
it('should construct correct URL with API key', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
249
|
+
const mockFetchResponse = {
|
|
250
|
+
json: jest.fn().mockResolvedValue({}),
|
|
251
|
+
};
|
|
252
|
+
nativeFetch.fetch.mockResolvedValue(mockFetchResponse);
|
|
253
|
+
yield adapter.refreshToken('refresh-token');
|
|
254
|
+
const callArgs = nativeFetch.fetch.mock.calls[0];
|
|
255
|
+
expect(callArgs[0]).toBe('https://securetoken.googleapis.com/v1/token?key=test-api-key');
|
|
256
|
+
}));
|
|
257
|
+
});
|
|
258
|
+
describe('Unimplemented Methods (Stubs)', () => {
|
|
259
|
+
describe('signup()', () => {
|
|
260
|
+
it('should have signup method defined', () => {
|
|
261
|
+
expect(adapter.signup).toBeDefined();
|
|
262
|
+
expect(typeof adapter.signup).toBe('function');
|
|
263
|
+
});
|
|
264
|
+
it('should return undefined (not implemented)', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
265
|
+
const result = yield adapter.signup('test@example.com', 'password');
|
|
266
|
+
expect(result).toBeUndefined();
|
|
267
|
+
}));
|
|
268
|
+
});
|
|
269
|
+
describe('signout()', () => {
|
|
270
|
+
it('should have signout method defined', () => {
|
|
271
|
+
expect(adapter.signout).toBeDefined();
|
|
272
|
+
expect(typeof adapter.signout).toBe('function');
|
|
273
|
+
});
|
|
274
|
+
it('should return undefined (not implemented)', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
275
|
+
const mockUser = { uid: 'user-123' };
|
|
276
|
+
const result = yield adapter.signout(mockUser);
|
|
277
|
+
expect(result).toBeUndefined();
|
|
278
|
+
}));
|
|
279
|
+
});
|
|
280
|
+
describe('revokeAuthToken()', () => {
|
|
281
|
+
it('should have revokeAuthToken method defined', () => {
|
|
282
|
+
expect(adapter.revokeAuthToken).toBeDefined();
|
|
283
|
+
expect(typeof adapter.revokeAuthToken).toBe('function');
|
|
284
|
+
});
|
|
285
|
+
it('should return undefined (not implemented)', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
286
|
+
const result = yield adapter.revokeAuthToken('token-123');
|
|
287
|
+
expect(result).toBeUndefined();
|
|
288
|
+
}));
|
|
289
|
+
});
|
|
290
|
+
describe('setCustomUserClaims()', () => {
|
|
291
|
+
it('should have setCustomUserClaims method defined', () => {
|
|
292
|
+
expect(adapter.setCustomUserClaims).toBeDefined();
|
|
293
|
+
expect(typeof adapter.setCustomUserClaims).toBe('function');
|
|
294
|
+
});
|
|
295
|
+
it('should return undefined (not implemented)', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
296
|
+
const result = yield adapter.setCustomUserClaims('user-123', {
|
|
297
|
+
role: 'admin',
|
|
298
|
+
});
|
|
299
|
+
expect(result).toBeUndefined();
|
|
300
|
+
}));
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
});
|
package/package.json
CHANGED
|
@@ -1,42 +1,43 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
2
|
+
"name": "@quatrain/auth-firebase",
|
|
3
|
+
"version": "1.1.14",
|
|
4
|
+
"license": "AGPL-3.0-only",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"types": "lib/index.d.ts",
|
|
7
|
+
"bun": "src/index.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"LICENSE.md",
|
|
10
|
+
"src/",
|
|
11
|
+
"lib/",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"author": "Quatrain Développement SAS <developers@quatrain.com>",
|
|
15
|
+
"peerDependencies": {
|
|
16
|
+
"@quatrain/core": "^1.1.42"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@quatrain/auth": "^1.1.19",
|
|
20
|
+
"@quatrain/backend": "^1.1.25",
|
|
21
|
+
"@quatrain/core": "^1.1.42",
|
|
22
|
+
"firebase-admin": "^13.0.1",
|
|
23
|
+
"node-fetch-native": "^1.6.4"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@tsconfig/recommended": "^1.0.1",
|
|
27
|
+
"@types/jest": "^30.0.0",
|
|
28
|
+
"@types/node": "^22.10.1",
|
|
29
|
+
"jest": "^30.2.0",
|
|
30
|
+
"jest-node-exports-resolver": "^1.1.6",
|
|
31
|
+
"jest-serial-runner": "^1.2.1",
|
|
32
|
+
"trace-unhandled": "^2.0.1",
|
|
33
|
+
"ts-jest": "^29.4.1",
|
|
34
|
+
"ts-node": "^10.9.1",
|
|
35
|
+
"typescript": "^5.2.2"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"test-ci": "jest --runInBand",
|
|
39
|
+
"build": "tsc",
|
|
40
|
+
"wbuild": "tsc --watch",
|
|
41
|
+
"bump-to": "yarn version"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import { FirebaseAuthAdapter } from './FirebaseAuthAdapter'
|
|
2
|
+
import { User } from '@quatrain/backend'
|
|
3
|
+
import { AuthenticationError } from '@quatrain/auth'
|
|
4
|
+
import { getAuth } from 'firebase-admin/auth'
|
|
5
|
+
import { getApps, initializeApp } from 'firebase-admin/app'
|
|
6
|
+
import * as nativeFetch from 'node-fetch-native'
|
|
7
|
+
|
|
8
|
+
// Mock the dependencies
|
|
9
|
+
jest.mock('firebase-admin/auth')
|
|
10
|
+
jest.mock('firebase-admin/app')
|
|
11
|
+
jest.mock('node-fetch-native')
|
|
12
|
+
|
|
13
|
+
describe('FirebaseAuthAdapter', () => {
|
|
14
|
+
let adapter: FirebaseAuthAdapter
|
|
15
|
+
let mockAuth: any
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
// Clear all mocks before each test
|
|
19
|
+
jest.clearAllMocks()
|
|
20
|
+
|
|
21
|
+
// Mock Firebase Admin SDK
|
|
22
|
+
mockAuth = {
|
|
23
|
+
createUser: jest.fn(),
|
|
24
|
+
verifyIdToken: jest.fn(),
|
|
25
|
+
updateUser: jest.fn(),
|
|
26
|
+
deleteUser: jest.fn(),
|
|
27
|
+
}
|
|
28
|
+
;(getAuth as jest.Mock).mockReturnValue(mockAuth)
|
|
29
|
+
;(getApps as jest.Mock).mockReturnValue([])
|
|
30
|
+
|
|
31
|
+
// Create adapter instance
|
|
32
|
+
adapter = new FirebaseAuthAdapter({
|
|
33
|
+
config: {
|
|
34
|
+
projectId: 'test-project',
|
|
35
|
+
apiKey: 'test-api-key',
|
|
36
|
+
},
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
afterEach(() => {
|
|
41
|
+
jest.restoreAllMocks()
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
describe('Constructor', () => {
|
|
45
|
+
it('should initialize Firebase app when no apps exist', () => {
|
|
46
|
+
expect(getApps).toHaveBeenCalled()
|
|
47
|
+
expect(initializeApp).toHaveBeenCalledWith({
|
|
48
|
+
projectId: 'test-project',
|
|
49
|
+
apiKey: 'test-api-key',
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('should not initialize Firebase app when app already exists', () => {
|
|
54
|
+
;(getApps as jest.Mock).mockReturnValue([{ name: 'existing-app' }])
|
|
55
|
+
|
|
56
|
+
new FirebaseAuthAdapter({
|
|
57
|
+
config: {
|
|
58
|
+
projectId: 'test-project',
|
|
59
|
+
},
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
// initializeApp should still be called once from the first adapter creation
|
|
63
|
+
expect(initializeApp).toHaveBeenCalledTimes(1)
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
describe('register()', () => {
|
|
68
|
+
const mockUser = {
|
|
69
|
+
uid: 'user-123',
|
|
70
|
+
_: {
|
|
71
|
+
name: 'John Doe',
|
|
72
|
+
email: 'john@example.com',
|
|
73
|
+
phone: '+1234567890',
|
|
74
|
+
password: 'hashed-password',
|
|
75
|
+
disabled: false,
|
|
76
|
+
},
|
|
77
|
+
} as unknown as User
|
|
78
|
+
|
|
79
|
+
it('should successfully register a new user', async () => {
|
|
80
|
+
const mockUserRecord = {
|
|
81
|
+
uid: 'user-123',
|
|
82
|
+
email: 'john@example.com',
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
mockAuth.createUser.mockResolvedValue(mockUserRecord)
|
|
86
|
+
|
|
87
|
+
const result = await adapter.register(mockUser, 'clearPassword123')
|
|
88
|
+
|
|
89
|
+
expect(mockAuth.createUser).toHaveBeenCalledWith({
|
|
90
|
+
uid: 'user-123',
|
|
91
|
+
email: 'john@example.com',
|
|
92
|
+
phoneNumber: '+1234567890',
|
|
93
|
+
password: 'clearPassword123',
|
|
94
|
+
disabled: false,
|
|
95
|
+
displayName: 'John Doe',
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
expect(result).toBe('user-123')
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('should use hashed password when clearPassword is not provided', async () => {
|
|
102
|
+
const mockUserRecord = {
|
|
103
|
+
uid: 'user-123',
|
|
104
|
+
email: 'john@example.com',
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
mockAuth.createUser.mockResolvedValue(mockUserRecord)
|
|
108
|
+
|
|
109
|
+
await adapter.register(mockUser)
|
|
110
|
+
|
|
111
|
+
expect(mockAuth.createUser).toHaveBeenCalledWith({
|
|
112
|
+
uid: 'user-123',
|
|
113
|
+
email: 'john@example.com',
|
|
114
|
+
phoneNumber: '+1234567890',
|
|
115
|
+
password: 'hashed-password',
|
|
116
|
+
disabled: false,
|
|
117
|
+
displayName: 'John Doe',
|
|
118
|
+
})
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('should handle user with default disabled value', async () => {
|
|
122
|
+
const userWithoutDisabled = {
|
|
123
|
+
uid: 'user-456',
|
|
124
|
+
_: {
|
|
125
|
+
name: 'Jane Doe',
|
|
126
|
+
email: 'jane@example.com',
|
|
127
|
+
phone: '+0987654321',
|
|
128
|
+
password: 'password456',
|
|
129
|
+
},
|
|
130
|
+
} as unknown as User
|
|
131
|
+
|
|
132
|
+
mockAuth.createUser.mockResolvedValue({ uid: 'user-456' })
|
|
133
|
+
|
|
134
|
+
await adapter.register(userWithoutDisabled, 'password')
|
|
135
|
+
|
|
136
|
+
const createUserCall = mockAuth.createUser.mock.calls[0][0]
|
|
137
|
+
expect(createUserCall.disabled).toBe(false)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('should throw AuthenticationError on Firebase error', async () => {
|
|
141
|
+
mockAuth.createUser.mockRejectedValue(
|
|
142
|
+
new Error('Email already exists')
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
await expect(adapter.register(mockUser, 'password')).rejects.toThrow(
|
|
146
|
+
AuthenticationError
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
await expect(adapter.register(mockUser, 'password')).rejects.toThrow(
|
|
150
|
+
'Email already exists'
|
|
151
|
+
)
|
|
152
|
+
})
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
describe('getAuthToken()', () => {
|
|
156
|
+
it('should successfully verify auth token', async () => {
|
|
157
|
+
const mockDecodedToken = {
|
|
158
|
+
uid: 'user-123',
|
|
159
|
+
email: 'test@example.com',
|
|
160
|
+
iat: Date.now(),
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
mockAuth.verifyIdToken.mockResolvedValue(mockDecodedToken)
|
|
164
|
+
|
|
165
|
+
const result = await adapter.getAuthToken('bearer-token-123')
|
|
166
|
+
|
|
167
|
+
expect(mockAuth.verifyIdToken).toHaveBeenCalledWith('bearer-token-123')
|
|
168
|
+
expect(result).toEqual(mockDecodedToken)
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('should throw error when token is invalid', async () => {
|
|
172
|
+
mockAuth.verifyIdToken.mockRejectedValue(new Error('Invalid token'))
|
|
173
|
+
|
|
174
|
+
await expect(adapter.getAuthToken('invalid-token')).rejects.toThrow(
|
|
175
|
+
'Invalid token'
|
|
176
|
+
)
|
|
177
|
+
})
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
describe('update()', () => {
|
|
181
|
+
const mockUser = {
|
|
182
|
+
uid: 'user-123',
|
|
183
|
+
_: {
|
|
184
|
+
email: 'test@example.com',
|
|
185
|
+
},
|
|
186
|
+
} as unknown as User
|
|
187
|
+
|
|
188
|
+
it('should successfully update user', async () => {
|
|
189
|
+
const updatable = {
|
|
190
|
+
email: 'newemail@example.com',
|
|
191
|
+
displayName: 'New Name',
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
mockAuth.updateUser.mockResolvedValue({})
|
|
195
|
+
|
|
196
|
+
await adapter.update(mockUser, updatable)
|
|
197
|
+
|
|
198
|
+
expect(mockAuth.updateUser).toHaveBeenCalledWith('user-123', updatable)
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
it('should skip update when updatable is empty', async () => {
|
|
202
|
+
await adapter.update(mockUser, {})
|
|
203
|
+
|
|
204
|
+
expect(mockAuth.updateUser).not.toHaveBeenCalled()
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
it('should silently handle errors during update', async () => {
|
|
208
|
+
const updatable = { displayName: 'New Name' }
|
|
209
|
+
|
|
210
|
+
mockAuth.updateUser.mockRejectedValue(new Error('Update failed'))
|
|
211
|
+
|
|
212
|
+
// Should not throw
|
|
213
|
+
await expect(
|
|
214
|
+
adapter.update(mockUser, updatable)
|
|
215
|
+
).resolves.toBeUndefined()
|
|
216
|
+
|
|
217
|
+
expect(mockAuth.updateUser).toHaveBeenCalledWith('user-123', updatable)
|
|
218
|
+
})
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
describe('delete()', () => {
|
|
222
|
+
const mockUser = {
|
|
223
|
+
uid: 'user-123',
|
|
224
|
+
} as unknown as User
|
|
225
|
+
|
|
226
|
+
it('should successfully delete user', async () => {
|
|
227
|
+
mockAuth.deleteUser.mockResolvedValue(undefined)
|
|
228
|
+
|
|
229
|
+
await adapter.delete(mockUser)
|
|
230
|
+
|
|
231
|
+
expect(mockAuth.deleteUser).toHaveBeenCalledWith('user-123')
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
it('should throw error on deletion failure', async () => {
|
|
235
|
+
mockAuth.deleteUser.mockRejectedValue(new Error('User not found'))
|
|
236
|
+
|
|
237
|
+
await expect(adapter.delete(mockUser)).rejects.toThrow(
|
|
238
|
+
'User not found'
|
|
239
|
+
)
|
|
240
|
+
})
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
describe('refreshToken()', () => {
|
|
244
|
+
it('should successfully refresh token', async () => {
|
|
245
|
+
const mockResponse = {
|
|
246
|
+
access_token: 'new-access-token',
|
|
247
|
+
refresh_token: 'new-refresh-token',
|
|
248
|
+
expires_in: 3600,
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const mockFetchResponse = {
|
|
252
|
+
json: jest.fn().mockResolvedValue(mockResponse),
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
;(nativeFetch.fetch as jest.Mock).mockResolvedValue(mockFetchResponse)
|
|
256
|
+
|
|
257
|
+
const result = await adapter.refreshToken('old-refresh-token')
|
|
258
|
+
|
|
259
|
+
expect(nativeFetch.fetch).toHaveBeenCalledWith(
|
|
260
|
+
'https://securetoken.googleapis.com/v1/token?key=test-api-key',
|
|
261
|
+
{
|
|
262
|
+
method: 'POST',
|
|
263
|
+
body: JSON.stringify({
|
|
264
|
+
grant_type: 'refresh_token',
|
|
265
|
+
refresh_token: 'old-refresh-token',
|
|
266
|
+
}),
|
|
267
|
+
headers: { 'Content-Type': 'application/json' },
|
|
268
|
+
}
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
// Note: Implementation doesn't await json(), so result is a promise
|
|
272
|
+
const resultValue = await result
|
|
273
|
+
expect(resultValue).toEqual(mockResponse)
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
it('should return empty object when API key is missing', async () => {
|
|
277
|
+
const adapterWithoutKey = new FirebaseAuthAdapter({
|
|
278
|
+
config: {
|
|
279
|
+
projectId: 'test-project',
|
|
280
|
+
},
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
const result = await adapterWithoutKey.refreshToken('refresh-token')
|
|
284
|
+
|
|
285
|
+
expect(result).toEqual({})
|
|
286
|
+
expect(nativeFetch.fetch).not.toHaveBeenCalled()
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
it('should construct correct URL with API key', async () => {
|
|
290
|
+
const mockFetchResponse = {
|
|
291
|
+
json: jest.fn().mockResolvedValue({}),
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
;(nativeFetch.fetch as jest.Mock).mockResolvedValue(mockFetchResponse)
|
|
295
|
+
|
|
296
|
+
await adapter.refreshToken('refresh-token')
|
|
297
|
+
|
|
298
|
+
const callArgs = (nativeFetch.fetch as jest.Mock).mock.calls[0]
|
|
299
|
+
expect(callArgs[0]).toBe(
|
|
300
|
+
'https://securetoken.googleapis.com/v1/token?key=test-api-key'
|
|
301
|
+
)
|
|
302
|
+
})
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
describe('Unimplemented Methods (Stubs)', () => {
|
|
306
|
+
describe('signup()', () => {
|
|
307
|
+
it('should have signup method defined', () => {
|
|
308
|
+
expect(adapter.signup).toBeDefined()
|
|
309
|
+
expect(typeof adapter.signup).toBe('function')
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
it('should return undefined (not implemented)', async () => {
|
|
313
|
+
const result = await adapter.signup('test@example.com', 'password')
|
|
314
|
+
expect(result).toBeUndefined()
|
|
315
|
+
})
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
describe('signout()', () => {
|
|
319
|
+
it('should have signout method defined', () => {
|
|
320
|
+
expect(adapter.signout).toBeDefined()
|
|
321
|
+
expect(typeof adapter.signout).toBe('function')
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
it('should return undefined (not implemented)', async () => {
|
|
325
|
+
const mockUser = { uid: 'user-123' } as unknown as User
|
|
326
|
+
const result = await adapter.signout(mockUser)
|
|
327
|
+
expect(result).toBeUndefined()
|
|
328
|
+
})
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
describe('revokeAuthToken()', () => {
|
|
332
|
+
it('should have revokeAuthToken method defined', () => {
|
|
333
|
+
expect(adapter.revokeAuthToken).toBeDefined()
|
|
334
|
+
expect(typeof adapter.revokeAuthToken).toBe('function')
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
it('should return undefined (not implemented)', async () => {
|
|
338
|
+
const result = await adapter.revokeAuthToken('token-123')
|
|
339
|
+
expect(result).toBeUndefined()
|
|
340
|
+
})
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
describe('setCustomUserClaims()', () => {
|
|
344
|
+
it('should have setCustomUserClaims method defined', () => {
|
|
345
|
+
expect(adapter.setCustomUserClaims).toBeDefined()
|
|
346
|
+
expect(typeof adapter.setCustomUserClaims).toBe('function')
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
it('should return undefined (not implemented)', async () => {
|
|
350
|
+
const result = await adapter.setCustomUserClaims('user-123', {
|
|
351
|
+
role: 'admin',
|
|
352
|
+
})
|
|
353
|
+
expect(result).toBeUndefined()
|
|
354
|
+
})
|
|
355
|
+
})
|
|
356
|
+
})
|
|
357
|
+
})
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { User } from '@quatrain/backend'
|
|
2
|
+
import {
|
|
3
|
+
Auth,
|
|
4
|
+
AbstractAuthAdapter,
|
|
5
|
+
AuthenticationError,
|
|
6
|
+
AuthParameters,
|
|
7
|
+
} from '@quatrain/auth'
|
|
8
|
+
import { CreateRequest, UpdateRequest, getAuth } from 'firebase-admin/auth'
|
|
9
|
+
import { getApps, initializeApp } from 'firebase-admin/app'
|
|
10
|
+
import * as nativeFetch from 'node-fetch-native'
|
|
11
|
+
|
|
12
|
+
export class FirebaseAuthAdapter extends AbstractAuthAdapter {
|
|
13
|
+
constructor(params: AuthParameters = {}) {
|
|
14
|
+
super(params)
|
|
15
|
+
if (getApps().length === 0) {
|
|
16
|
+
initializeApp(params.config)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Register new user in authentication
|
|
22
|
+
* @param user
|
|
23
|
+
* @returns user unique id
|
|
24
|
+
*/
|
|
25
|
+
async register(user: User, clearPassword?: string) {
|
|
26
|
+
try {
|
|
27
|
+
const {
|
|
28
|
+
name: displayName,
|
|
29
|
+
email,
|
|
30
|
+
phone: phoneNumber,
|
|
31
|
+
password,
|
|
32
|
+
disabled = false,
|
|
33
|
+
} = user._
|
|
34
|
+
const authData: CreateRequest = {
|
|
35
|
+
uid: user.uid,
|
|
36
|
+
email,
|
|
37
|
+
phoneNumber,
|
|
38
|
+
password: clearPassword || password,
|
|
39
|
+
disabled,
|
|
40
|
+
displayName,
|
|
41
|
+
}
|
|
42
|
+
Auth.log(`[FAA] Adding user '${displayName}'`)
|
|
43
|
+
const userRecord = await getAuth().createUser(authData)
|
|
44
|
+
|
|
45
|
+
return userRecord.uid
|
|
46
|
+
} catch (err) {
|
|
47
|
+
throw new AuthenticationError((err as Error).message)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async getAuthToken(bearer: string) {
|
|
52
|
+
return await getAuth().verifyIdToken(bearer)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async signup(login: string, password: string) {}
|
|
56
|
+
|
|
57
|
+
async signout(user: User): Promise<any> {}
|
|
58
|
+
|
|
59
|
+
async update(user: User, updatable: UpdateRequest): Promise<any> {
|
|
60
|
+
Auth.log('auth data to update', JSON.stringify(updatable))
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
if (Object.keys(updatable).length > 0) {
|
|
64
|
+
Auth.log(`Updating ${updatable.displayName} Auth record`)
|
|
65
|
+
await getAuth().updateUser(user.uid, updatable)
|
|
66
|
+
}
|
|
67
|
+
} catch (e) {}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async delete(user: User): Promise<any> {
|
|
71
|
+
return await getAuth().deleteUser(user.uid)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async revokeAuthToken(token: string) {}
|
|
75
|
+
|
|
76
|
+
async setCustomUserClaims(id: string, claims: any) {}
|
|
77
|
+
|
|
78
|
+
async refreshToken(refreshToken: string): Promise<any> {
|
|
79
|
+
if (!this._params.config.apiKey) {
|
|
80
|
+
Auth.warn(`Can't get refresh token, no API key provided`)
|
|
81
|
+
return {}
|
|
82
|
+
}
|
|
83
|
+
const response = await nativeFetch.fetch(
|
|
84
|
+
`https://securetoken.googleapis.com/v1/token?key=${this._params.config.apiKey}`,
|
|
85
|
+
{
|
|
86
|
+
method: 'POST',
|
|
87
|
+
body: JSON.stringify({
|
|
88
|
+
grant_type: 'refresh_token',
|
|
89
|
+
refresh_token: refreshToken,
|
|
90
|
+
}),
|
|
91
|
+
headers: { 'Content-Type': 'application/json' }, //, crossdomain: true },
|
|
92
|
+
}
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
const data = response.json()
|
|
96
|
+
|
|
97
|
+
return data
|
|
98
|
+
}
|
|
99
|
+
}
|
package/src/index.ts
ADDED