@quatrain/auth 1.1.17 → 1.1.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/LICENSE.md +15 -0
- package/README.md +32 -0
- package/lib/Auth.test.d.ts +1 -0
- package/lib/Auth.test.js +182 -0
- package/lib/MockAuthAdapter.d.ts +24 -0
- package/lib/MockAuthAdapter.js +154 -0
- package/package.json +37 -39
- package/src/AbstractAuthAdapter.ts +47 -0
- package/src/Auth.test.ts +216 -0
- package/src/Auth.ts +67 -0
- package/src/AuthenticationError.ts +1 -0
- package/src/MockAuthAdapter.ts +162 -0
- package/src/index.ts +14 -0
- package/src/middlewares/AbstractAuthMiddleware.ts +0 -0
- package/src/middlewares/AuthMiddleware.ts +6 -0
- package/src/types/AuthInterface.ts +17 -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,32 @@
|
|
|
1
|
+
# @quatrain/auth
|
|
2
|
+
|
|
3
|
+
This package provides authentication and authorization abstractions for Quatrain applications. It defines a consistent interface for managing users, sessions, and tokens across different authentication providers.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Abstract Adapter**: A common interface for authentication providers.
|
|
8
|
+
- **User Management**: Handles user registration, login, and profile updates.
|
|
9
|
+
- **Token Handling**: Manages JWT verification and refresh logic.
|
|
10
|
+
- **Middleware Integration**: Provides hooks for protecting backend routes.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @quatrain/auth
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
This package is meant to be used with a concrete authentication adapter, such as `@quatrain/auth-firebase` or `@quatrain/auth-supabase`.
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { Auth } from '@quatrain/auth'
|
|
24
|
+
import { SupabaseAuthAdapter } from '@quatrain/auth-supabase'
|
|
25
|
+
|
|
26
|
+
// Assuming an adapter has been added
|
|
27
|
+
const user = await Auth.register({
|
|
28
|
+
email: 'user@example.com',
|
|
29
|
+
password: 'password123',
|
|
30
|
+
})
|
|
31
|
+
const token = await Auth.login('user@example.com', 'password123')
|
|
32
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/lib/Auth.test.js
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const Auth_1 = require("./Auth");
|
|
13
|
+
const MockAuthAdapter_1 = require("./MockAuthAdapter");
|
|
14
|
+
describe('Auth', () => {
|
|
15
|
+
let mockAdapter1;
|
|
16
|
+
let mockAdapter2;
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
// Reset providers before each test
|
|
19
|
+
;
|
|
20
|
+
Auth_1.Auth._providers = {};
|
|
21
|
+
Auth_1.Auth.defaultProvider = 'default';
|
|
22
|
+
// Create fresh mock adapters
|
|
23
|
+
mockAdapter1 = new MockAuthAdapter_1.MockAuthAdapter({ debug: false });
|
|
24
|
+
mockAdapter2 = new MockAuthAdapter_1.MockAuthAdapter({ debug: false });
|
|
25
|
+
});
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
// Clean up
|
|
28
|
+
mockAdapter1 === null || mockAdapter1 === void 0 ? void 0 : mockAdapter1.clearAll();
|
|
29
|
+
mockAdapter2 === null || mockAdapter2 === void 0 ? void 0 : mockAdapter2.clearAll();
|
|
30
|
+
});
|
|
31
|
+
describe('AuthAction enum', () => {
|
|
32
|
+
it('should have correct action values', () => {
|
|
33
|
+
expect(Auth_1.AuthAction.SIGNIN).toBe('signin');
|
|
34
|
+
expect(Auth_1.AuthAction.SIGNUP).toBe('signup');
|
|
35
|
+
expect(Auth_1.AuthAction.SIGNOUT).toBe('signout');
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
describe('addProvider', () => {
|
|
39
|
+
it('should add a provider with an alias', () => {
|
|
40
|
+
Auth_1.Auth.addProvider(mockAdapter1, 'test-provider');
|
|
41
|
+
const provider = Auth_1.Auth.getProvider('test-provider');
|
|
42
|
+
expect(provider).toBe(mockAdapter1);
|
|
43
|
+
});
|
|
44
|
+
it('should add multiple providers with different aliases', () => {
|
|
45
|
+
Auth_1.Auth.addProvider(mockAdapter1, 'provider1');
|
|
46
|
+
Auth_1.Auth.addProvider(mockAdapter2, 'provider2');
|
|
47
|
+
const provider1 = Auth_1.Auth.getProvider('provider1');
|
|
48
|
+
const provider2 = Auth_1.Auth.getProvider('provider2');
|
|
49
|
+
expect(provider1).toBe(mockAdapter1);
|
|
50
|
+
expect(provider2).toBe(mockAdapter2);
|
|
51
|
+
});
|
|
52
|
+
it('should set default provider when setDefault is true', () => {
|
|
53
|
+
Auth_1.Auth.addProvider(mockAdapter1, 'custom-provider', true);
|
|
54
|
+
expect(Auth_1.Auth.defaultProvider).toBe('custom-provider');
|
|
55
|
+
const provider = Auth_1.Auth.getProvider(); // Should use default
|
|
56
|
+
expect(provider).toBe(mockAdapter1);
|
|
57
|
+
});
|
|
58
|
+
it('should not change default provider when setDefault is false', () => {
|
|
59
|
+
const originalDefault = Auth_1.Auth.defaultProvider;
|
|
60
|
+
Auth_1.Auth.addProvider(mockAdapter1, 'custom-provider', false);
|
|
61
|
+
expect(Auth_1.Auth.defaultProvider).toBe(originalDefault);
|
|
62
|
+
});
|
|
63
|
+
it('should allow overriding an existing provider', () => {
|
|
64
|
+
Auth_1.Auth.addProvider(mockAdapter1, 'provider');
|
|
65
|
+
Auth_1.Auth.addProvider(mockAdapter2, 'provider');
|
|
66
|
+
const provider = Auth_1.Auth.getProvider('provider');
|
|
67
|
+
expect(provider).toBe(mockAdapter2);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
describe('getProvider', () => {
|
|
71
|
+
it('should return the correct provider by alias', () => {
|
|
72
|
+
Auth_1.Auth.addProvider(mockAdapter1, 'test-provider');
|
|
73
|
+
const provider = Auth_1.Auth.getProvider('test-provider');
|
|
74
|
+
expect(provider).toBe(mockAdapter1);
|
|
75
|
+
});
|
|
76
|
+
it('should return the default provider when no alias is specified', () => {
|
|
77
|
+
Auth_1.Auth.addProvider(mockAdapter1, 'default', true);
|
|
78
|
+
const provider = Auth_1.Auth.getProvider();
|
|
79
|
+
expect(provider).toBe(mockAdapter1);
|
|
80
|
+
});
|
|
81
|
+
it('should throw error when provider alias does not exist', () => {
|
|
82
|
+
expect(() => {
|
|
83
|
+
Auth_1.Auth.getProvider('non-existent');
|
|
84
|
+
}).toThrow("Unknown provider alias: 'non-existent'");
|
|
85
|
+
});
|
|
86
|
+
it('should throw error when getting default provider that does not exist', () => {
|
|
87
|
+
expect(() => {
|
|
88
|
+
Auth_1.Auth.getProvider();
|
|
89
|
+
}).toThrow("Unknown provider alias: 'default'");
|
|
90
|
+
});
|
|
91
|
+
it('should be type-safe with generic parameter', () => {
|
|
92
|
+
Auth_1.Auth.addProvider(mockAdapter1, 'typed-provider');
|
|
93
|
+
const provider = Auth_1.Auth.getProvider('typed-provider');
|
|
94
|
+
expect(provider).toBeInstanceOf(MockAuthAdapter_1.MockAuthAdapter);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
describe('defaultProvider', () => {
|
|
98
|
+
it('should have a default value of "default"', () => {
|
|
99
|
+
expect(Auth_1.Auth.defaultProvider).toBe('default');
|
|
100
|
+
});
|
|
101
|
+
it('should allow changing the default provider', () => {
|
|
102
|
+
Auth_1.Auth.addProvider(mockAdapter1, 'new-default', true);
|
|
103
|
+
expect(Auth_1.Auth.defaultProvider).toBe('new-default');
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
describe('ERROR_EMAIL_EXISTS constant', () => {
|
|
107
|
+
it('should have correct error message', () => {
|
|
108
|
+
expect(Auth_1.Auth.ERROR_EMAIL_EXISTS).toBe('User email already exists');
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
describe('Integration with MockAuthAdapter', () => {
|
|
112
|
+
it('should work with adapter methods', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
113
|
+
Auth_1.Auth.addProvider(mockAdapter1, 'test');
|
|
114
|
+
const provider = Auth_1.Auth.getProvider('test');
|
|
115
|
+
const mockUser = {
|
|
116
|
+
_: { email: 'test@example.com' },
|
|
117
|
+
name: 'Test User',
|
|
118
|
+
};
|
|
119
|
+
const result = yield provider.register(mockUser, 'password123');
|
|
120
|
+
expect(result.success).toBe(true);
|
|
121
|
+
expect(result.user._.email).toBe('test@example.com');
|
|
122
|
+
}));
|
|
123
|
+
it('should maintain separate state between providers', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
124
|
+
Auth_1.Auth.addProvider(mockAdapter1, 'provider1');
|
|
125
|
+
Auth_1.Auth.addProvider(mockAdapter2, 'provider2');
|
|
126
|
+
const adapter1 = Auth_1.Auth.getProvider('provider1');
|
|
127
|
+
const adapter2 = Auth_1.Auth.getProvider('provider2');
|
|
128
|
+
const user1 = {
|
|
129
|
+
_: { email: 'user1@example.com' },
|
|
130
|
+
name: 'User 1',
|
|
131
|
+
};
|
|
132
|
+
const user2 = {
|
|
133
|
+
_: { email: 'user2@example.com' },
|
|
134
|
+
name: 'User 2',
|
|
135
|
+
};
|
|
136
|
+
yield adapter1.register(user1, 'password1');
|
|
137
|
+
yield adapter2.register(user2, 'password2');
|
|
138
|
+
expect(adapter1.getUserByEmail('user1@example.com')).toBeDefined();
|
|
139
|
+
expect(adapter1.getUserByEmail('user2@example.com')).toBeUndefined();
|
|
140
|
+
expect(adapter2.getUserByEmail('user2@example.com')).toBeDefined();
|
|
141
|
+
expect(adapter2.getUserByEmail('user1@example.com')).toBeUndefined();
|
|
142
|
+
}));
|
|
143
|
+
});
|
|
144
|
+
describe('AuthParameters', () => {
|
|
145
|
+
it('should accept valid parameters when creating adapter', () => {
|
|
146
|
+
const params = {
|
|
147
|
+
host: 'https://auth.example.com',
|
|
148
|
+
alias: 'example-auth',
|
|
149
|
+
debug: true,
|
|
150
|
+
config: { timeout: 5000 },
|
|
151
|
+
fixtures: { users: [] },
|
|
152
|
+
};
|
|
153
|
+
const adapter = new MockAuthAdapter_1.MockAuthAdapter(params);
|
|
154
|
+
expect(adapter.getParam('host')).toBe('https://auth.example.com');
|
|
155
|
+
expect(adapter.getParam('debug')).toBe(true);
|
|
156
|
+
expect(adapter.getParam('config')).toEqual({ timeout: 5000 });
|
|
157
|
+
});
|
|
158
|
+
it('should work with empty parameters', () => {
|
|
159
|
+
const adapter = new MockAuthAdapter_1.MockAuthAdapter();
|
|
160
|
+
expect(adapter.getParam('host')).toBeUndefined();
|
|
161
|
+
expect(adapter.getParam('debug')).toBeUndefined();
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
describe('Provider registry behavior', () => {
|
|
165
|
+
it('should maintain providers across multiple operations', () => {
|
|
166
|
+
Auth_1.Auth.addProvider(mockAdapter1, 'persistent');
|
|
167
|
+
// Multiple get operations
|
|
168
|
+
const provider1 = Auth_1.Auth.getProvider('persistent');
|
|
169
|
+
const provider2 = Auth_1.Auth.getProvider('persistent');
|
|
170
|
+
expect(provider1).toBe(provider2);
|
|
171
|
+
expect(provider1).toBe(mockAdapter1);
|
|
172
|
+
});
|
|
173
|
+
it('should handle switching default provider multiple times', () => {
|
|
174
|
+
Auth_1.Auth.addProvider(mockAdapter1, 'provider1', true);
|
|
175
|
+
expect(Auth_1.Auth.defaultProvider).toBe('provider1');
|
|
176
|
+
Auth_1.Auth.addProvider(mockAdapter2, 'provider2', true);
|
|
177
|
+
expect(Auth_1.Auth.defaultProvider).toBe('provider2');
|
|
178
|
+
const defaultProvider = Auth_1.Auth.getProvider();
|
|
179
|
+
expect(defaultProvider).toBe(mockAdapter2);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { AbstractAuthAdapter } from './AbstractAuthAdapter';
|
|
2
|
+
import { User } from '@quatrain/backend';
|
|
3
|
+
import { AuthParameters } from './Auth';
|
|
4
|
+
/**
|
|
5
|
+
* Mock Authentication Adapter for testing purposes
|
|
6
|
+
*/
|
|
7
|
+
export declare class MockAuthAdapter extends AbstractAuthAdapter {
|
|
8
|
+
private registeredUsers;
|
|
9
|
+
private tokens;
|
|
10
|
+
private refreshTokens;
|
|
11
|
+
constructor(params?: AuthParameters);
|
|
12
|
+
register(user: User, clearPassword?: string): Promise<any>;
|
|
13
|
+
signup(login: string, password: string): Promise<any>;
|
|
14
|
+
signout(user: User): Promise<any>;
|
|
15
|
+
update(user: User, updatable: any): Promise<any>;
|
|
16
|
+
delete(user: User): Promise<any>;
|
|
17
|
+
getAuthToken(token: string): any;
|
|
18
|
+
refreshToken(refreshToken: string): Promise<any>;
|
|
19
|
+
revokeAuthToken(token: string): any;
|
|
20
|
+
setCustomUserClaims(id: string, claims: any): any;
|
|
21
|
+
clearAll(): void;
|
|
22
|
+
getUserByEmail(email: string): User | undefined;
|
|
23
|
+
hasToken(token: string): boolean;
|
|
24
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.MockAuthAdapter = void 0;
|
|
13
|
+
const AbstractAuthAdapter_1 = require("./AbstractAuthAdapter");
|
|
14
|
+
/**
|
|
15
|
+
* Mock Authentication Adapter for testing purposes
|
|
16
|
+
*/
|
|
17
|
+
class MockAuthAdapter extends AbstractAuthAdapter_1.AbstractAuthAdapter {
|
|
18
|
+
constructor(params = {}) {
|
|
19
|
+
super(params);
|
|
20
|
+
this.registeredUsers = new Map();
|
|
21
|
+
this.tokens = new Map();
|
|
22
|
+
this.refreshTokens = new Map();
|
|
23
|
+
}
|
|
24
|
+
register(user, clearPassword) {
|
|
25
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
26
|
+
if (this.registeredUsers.has(user._.email)) {
|
|
27
|
+
throw new Error('User email already exists');
|
|
28
|
+
}
|
|
29
|
+
this.registeredUsers.set(user._.email, user);
|
|
30
|
+
return { success: true, user };
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
signup(login, password) {
|
|
34
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
35
|
+
const user = this.registeredUsers.get(login);
|
|
36
|
+
if (!user) {
|
|
37
|
+
throw new Error('User not found');
|
|
38
|
+
}
|
|
39
|
+
const token = `mock-token-${Date.now()}`;
|
|
40
|
+
const refreshToken = `mock-refresh-token-${Date.now()}`;
|
|
41
|
+
this.tokens.set(token, { user, timestamp: Date.now() });
|
|
42
|
+
this.refreshTokens.set(refreshToken, token);
|
|
43
|
+
return {
|
|
44
|
+
success: true,
|
|
45
|
+
token,
|
|
46
|
+
refreshToken,
|
|
47
|
+
user,
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
signout(user) {
|
|
52
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
53
|
+
// Remove all tokens associated with this user
|
|
54
|
+
for (const [token, data] of this.tokens.entries()) {
|
|
55
|
+
if (data.user._.email === user._.email) {
|
|
56
|
+
this.tokens.delete(token);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return { success: true };
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
update(user, updatable) {
|
|
63
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
64
|
+
const existingUser = this.registeredUsers.get(user._.email);
|
|
65
|
+
if (!existingUser) {
|
|
66
|
+
throw new Error('User not found');
|
|
67
|
+
}
|
|
68
|
+
const updatedUser = Object.assign(Object.assign({}, existingUser), updatable);
|
|
69
|
+
this.registeredUsers.set(user._.email, updatedUser);
|
|
70
|
+
return { success: true, user: updatedUser };
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
delete(user) {
|
|
74
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
75
|
+
if (!this.registeredUsers.has(user._.email)) {
|
|
76
|
+
throw new Error('User not found');
|
|
77
|
+
}
|
|
78
|
+
this.registeredUsers.delete(user._.email);
|
|
79
|
+
yield this.signout(user);
|
|
80
|
+
return { success: true };
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
getAuthToken(token) {
|
|
84
|
+
const tokenData = this.tokens.get(token);
|
|
85
|
+
if (!tokenData) {
|
|
86
|
+
throw new Error('Invalid token');
|
|
87
|
+
}
|
|
88
|
+
return tokenData;
|
|
89
|
+
}
|
|
90
|
+
refreshToken(refreshToken) {
|
|
91
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
92
|
+
const oldToken = this.refreshTokens.get(refreshToken);
|
|
93
|
+
if (!oldToken) {
|
|
94
|
+
throw new Error('Invalid refresh token');
|
|
95
|
+
}
|
|
96
|
+
const tokenData = this.tokens.get(oldToken);
|
|
97
|
+
if (!tokenData) {
|
|
98
|
+
throw new Error('Token not found');
|
|
99
|
+
}
|
|
100
|
+
// Generate new tokens
|
|
101
|
+
const newToken = `mock-token-${Date.now()}`;
|
|
102
|
+
const newRefreshToken = `mock-refresh-token-${Date.now()}`;
|
|
103
|
+
// Remove old tokens
|
|
104
|
+
this.tokens.delete(oldToken);
|
|
105
|
+
this.refreshTokens.delete(refreshToken);
|
|
106
|
+
// Add new tokens
|
|
107
|
+
this.tokens.set(newToken, tokenData);
|
|
108
|
+
this.refreshTokens.set(newRefreshToken, newToken);
|
|
109
|
+
return {
|
|
110
|
+
success: true,
|
|
111
|
+
token: newToken,
|
|
112
|
+
refreshToken: newRefreshToken,
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
revokeAuthToken(token) {
|
|
117
|
+
if (!this.tokens.has(token)) {
|
|
118
|
+
throw new Error('Token not found');
|
|
119
|
+
}
|
|
120
|
+
this.tokens.delete(token);
|
|
121
|
+
// Remove associated refresh token
|
|
122
|
+
for (const [refreshToken, associatedToken,] of this.refreshTokens.entries()) {
|
|
123
|
+
if (associatedToken === token) {
|
|
124
|
+
this.refreshTokens.delete(refreshToken);
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return { success: true };
|
|
129
|
+
}
|
|
130
|
+
setCustomUserClaims(id, claims) {
|
|
131
|
+
// Find user by id and set custom claims
|
|
132
|
+
for (const [email, user] of this.registeredUsers.entries()) {
|
|
133
|
+
if (user._.id === id) {
|
|
134
|
+
;
|
|
135
|
+
user.customClaims = claims;
|
|
136
|
+
return { success: true, claims };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
throw new Error('User not found');
|
|
140
|
+
}
|
|
141
|
+
// Helper methods for testing
|
|
142
|
+
clearAll() {
|
|
143
|
+
this.registeredUsers.clear();
|
|
144
|
+
this.tokens.clear();
|
|
145
|
+
this.refreshTokens.clear();
|
|
146
|
+
}
|
|
147
|
+
getUserByEmail(email) {
|
|
148
|
+
return this.registeredUsers.get(email);
|
|
149
|
+
}
|
|
150
|
+
hasToken(token) {
|
|
151
|
+
return this.tokens.has(token);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
exports.MockAuthAdapter = MockAuthAdapter;
|
package/package.json
CHANGED
|
@@ -1,40 +1,38 @@
|
|
|
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
|
-
}
|
|
2
|
+
"name": "@quatrain/auth",
|
|
3
|
+
"version": "1.1.20",
|
|
4
|
+
"license": "AGPL-3.0-only",
|
|
5
|
+
"description": "Auth adapters commons",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"types": "lib/index.d.ts",
|
|
8
|
+
"bun": "src/index.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"LICENSE.md",
|
|
11
|
+
"src/",
|
|
12
|
+
"lib/",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"author": "Quatrain Développement SAS <developers@quatrain.com>",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@quatrain/backend": "^1.1.26",
|
|
18
|
+
"@quatrain/core": "^1.1.43"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@tsconfig/recommended": "^1.0.1",
|
|
22
|
+
"@types/jest": "^27.0.3",
|
|
23
|
+
"@types/node": "^22.10.1",
|
|
24
|
+
"jest": "^30.2.0",
|
|
25
|
+
"jest-node-exports-resolver": "^1.1.6",
|
|
26
|
+
"jest-serial-runner": "^1.2.1",
|
|
27
|
+
"trace-unhandled": "^2.0.1",
|
|
28
|
+
"ts-jest": "^27.1.2",
|
|
29
|
+
"ts-node": "^10.9.1",
|
|
30
|
+
"typescript": "^5.1.5"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"test-ci": "jest --runInBand",
|
|
34
|
+
"build": "tsc",
|
|
35
|
+
"wbuild": "tsc --watch",
|
|
36
|
+
"bump-to": "yarn version"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { AuthParameters, AuthParametersKeys } from './Auth'
|
|
2
|
+
import { User } from '@quatrain/backend'
|
|
3
|
+
import { AuthInterface } from './types/AuthInterface'
|
|
4
|
+
|
|
5
|
+
export abstract class AbstractAuthAdapter implements AuthInterface {
|
|
6
|
+
static UserClass = User
|
|
7
|
+
protected _alias: string = ''
|
|
8
|
+
protected _params: AuthParameters = {}
|
|
9
|
+
|
|
10
|
+
constructor(params: AuthParameters = {}) {
|
|
11
|
+
this._params = params
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
setParam(key: AuthParametersKeys, value: any) {
|
|
15
|
+
this._params[key] = value
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
getParam(key: AuthParametersKeys) {
|
|
19
|
+
return this._params[key]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
set alias(alias: string) {
|
|
23
|
+
this._alias = alias
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get alias() {
|
|
27
|
+
return this._alias
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
abstract register(user: User, clearPassword?: string): Promise<any>
|
|
31
|
+
|
|
32
|
+
abstract signup(login: string, password: string): Promise<any>
|
|
33
|
+
|
|
34
|
+
abstract signout(user: User): Promise<any>
|
|
35
|
+
|
|
36
|
+
abstract update(user: User, updatable: any): Promise<any>
|
|
37
|
+
|
|
38
|
+
abstract delete(user: User): Promise<any>
|
|
39
|
+
|
|
40
|
+
abstract getAuthToken(token: string): any
|
|
41
|
+
|
|
42
|
+
abstract refreshToken(refreshToken: string): Promise<any>
|
|
43
|
+
|
|
44
|
+
abstract revokeAuthToken(token: string): any
|
|
45
|
+
|
|
46
|
+
abstract setCustomUserClaims(id: string, claims: any): any
|
|
47
|
+
}
|
package/src/Auth.test.ts
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { Auth, AuthAction, AuthParameters } from './Auth'
|
|
2
|
+
import { MockAuthAdapter } from './MockAuthAdapter'
|
|
3
|
+
import { User } from '@quatrain/backend'
|
|
4
|
+
|
|
5
|
+
describe('Auth', () => {
|
|
6
|
+
let mockAdapter1: MockAuthAdapter
|
|
7
|
+
let mockAdapter2: MockAuthAdapter
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
// Reset providers before each test
|
|
11
|
+
;(Auth as any)._providers = {}
|
|
12
|
+
Auth.defaultProvider = 'default'
|
|
13
|
+
|
|
14
|
+
// Create fresh mock adapters
|
|
15
|
+
mockAdapter1 = new MockAuthAdapter({ debug: false })
|
|
16
|
+
mockAdapter2 = new MockAuthAdapter({ debug: false })
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
// Clean up
|
|
21
|
+
mockAdapter1?.clearAll()
|
|
22
|
+
mockAdapter2?.clearAll()
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
describe('AuthAction enum', () => {
|
|
26
|
+
it('should have correct action values', () => {
|
|
27
|
+
expect(AuthAction.SIGNIN).toBe('signin')
|
|
28
|
+
expect(AuthAction.SIGNUP).toBe('signup')
|
|
29
|
+
expect(AuthAction.SIGNOUT).toBe('signout')
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
describe('addProvider', () => {
|
|
34
|
+
it('should add a provider with an alias', () => {
|
|
35
|
+
Auth.addProvider(mockAdapter1, 'test-provider')
|
|
36
|
+
|
|
37
|
+
const provider = Auth.getProvider('test-provider')
|
|
38
|
+
expect(provider).toBe(mockAdapter1)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('should add multiple providers with different aliases', () => {
|
|
42
|
+
Auth.addProvider(mockAdapter1, 'provider1')
|
|
43
|
+
Auth.addProvider(mockAdapter2, 'provider2')
|
|
44
|
+
|
|
45
|
+
const provider1 = Auth.getProvider('provider1')
|
|
46
|
+
const provider2 = Auth.getProvider('provider2')
|
|
47
|
+
|
|
48
|
+
expect(provider1).toBe(mockAdapter1)
|
|
49
|
+
expect(provider2).toBe(mockAdapter2)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('should set default provider when setDefault is true', () => {
|
|
53
|
+
Auth.addProvider(mockAdapter1, 'custom-provider', true)
|
|
54
|
+
|
|
55
|
+
expect(Auth.defaultProvider).toBe('custom-provider')
|
|
56
|
+
const provider = Auth.getProvider() // Should use default
|
|
57
|
+
expect(provider).toBe(mockAdapter1)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('should not change default provider when setDefault is false', () => {
|
|
61
|
+
const originalDefault = Auth.defaultProvider
|
|
62
|
+
Auth.addProvider(mockAdapter1, 'custom-provider', false)
|
|
63
|
+
|
|
64
|
+
expect(Auth.defaultProvider).toBe(originalDefault)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('should allow overriding an existing provider', () => {
|
|
68
|
+
Auth.addProvider(mockAdapter1, 'provider')
|
|
69
|
+
Auth.addProvider(mockAdapter2, 'provider')
|
|
70
|
+
|
|
71
|
+
const provider = Auth.getProvider('provider')
|
|
72
|
+
expect(provider).toBe(mockAdapter2)
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
describe('getProvider', () => {
|
|
77
|
+
it('should return the correct provider by alias', () => {
|
|
78
|
+
Auth.addProvider(mockAdapter1, 'test-provider')
|
|
79
|
+
|
|
80
|
+
const provider = Auth.getProvider('test-provider')
|
|
81
|
+
expect(provider).toBe(mockAdapter1)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('should return the default provider when no alias is specified', () => {
|
|
85
|
+
Auth.addProvider(mockAdapter1, 'default', true)
|
|
86
|
+
|
|
87
|
+
const provider = Auth.getProvider()
|
|
88
|
+
expect(provider).toBe(mockAdapter1)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('should throw error when provider alias does not exist', () => {
|
|
92
|
+
expect(() => {
|
|
93
|
+
Auth.getProvider('non-existent')
|
|
94
|
+
}).toThrow("Unknown provider alias: 'non-existent'")
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('should throw error when getting default provider that does not exist', () => {
|
|
98
|
+
expect(() => {
|
|
99
|
+
Auth.getProvider()
|
|
100
|
+
}).toThrow("Unknown provider alias: 'default'")
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('should be type-safe with generic parameter', () => {
|
|
104
|
+
Auth.addProvider(mockAdapter1, 'typed-provider')
|
|
105
|
+
|
|
106
|
+
const provider = Auth.getProvider<MockAuthAdapter>('typed-provider')
|
|
107
|
+
expect(provider).toBeInstanceOf(MockAuthAdapter)
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
describe('defaultProvider', () => {
|
|
112
|
+
it('should have a default value of "default"', () => {
|
|
113
|
+
expect(Auth.defaultProvider).toBe('default')
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('should allow changing the default provider', () => {
|
|
117
|
+
Auth.addProvider(mockAdapter1, 'new-default', true)
|
|
118
|
+
expect(Auth.defaultProvider).toBe('new-default')
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
describe('ERROR_EMAIL_EXISTS constant', () => {
|
|
123
|
+
it('should have correct error message', () => {
|
|
124
|
+
expect(Auth.ERROR_EMAIL_EXISTS).toBe('User email already exists')
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
describe('Integration with MockAuthAdapter', () => {
|
|
129
|
+
it('should work with adapter methods', async () => {
|
|
130
|
+
Auth.addProvider(mockAdapter1, 'test')
|
|
131
|
+
const provider = Auth.getProvider<MockAuthAdapter>('test')
|
|
132
|
+
|
|
133
|
+
const mockUser = {
|
|
134
|
+
_: { email: 'test@example.com' },
|
|
135
|
+
name: 'Test User',
|
|
136
|
+
} as unknown as User
|
|
137
|
+
|
|
138
|
+
const result = await provider.register(mockUser, 'password123')
|
|
139
|
+
expect(result.success).toBe(true)
|
|
140
|
+
expect(result.user._.email).toBe('test@example.com')
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('should maintain separate state between providers', async () => {
|
|
144
|
+
Auth.addProvider(mockAdapter1, 'provider1')
|
|
145
|
+
Auth.addProvider(mockAdapter2, 'provider2')
|
|
146
|
+
|
|
147
|
+
const adapter1 = Auth.getProvider<MockAuthAdapter>('provider1')
|
|
148
|
+
const adapter2 = Auth.getProvider<MockAuthAdapter>('provider2')
|
|
149
|
+
|
|
150
|
+
const user1 = {
|
|
151
|
+
_: { email: 'user1@example.com' },
|
|
152
|
+
name: 'User 1',
|
|
153
|
+
} as unknown as User
|
|
154
|
+
const user2 = {
|
|
155
|
+
_: { email: 'user2@example.com' },
|
|
156
|
+
name: 'User 2',
|
|
157
|
+
} as unknown as User
|
|
158
|
+
|
|
159
|
+
await adapter1.register(user1, 'password1')
|
|
160
|
+
await adapter2.register(user2, 'password2')
|
|
161
|
+
|
|
162
|
+
expect(adapter1.getUserByEmail('user1@example.com')).toBeDefined()
|
|
163
|
+
expect(adapter1.getUserByEmail('user2@example.com')).toBeUndefined()
|
|
164
|
+
|
|
165
|
+
expect(adapter2.getUserByEmail('user2@example.com')).toBeDefined()
|
|
166
|
+
expect(adapter2.getUserByEmail('user1@example.com')).toBeUndefined()
|
|
167
|
+
})
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
describe('AuthParameters', () => {
|
|
171
|
+
it('should accept valid parameters when creating adapter', () => {
|
|
172
|
+
const params: AuthParameters = {
|
|
173
|
+
host: 'https://auth.example.com',
|
|
174
|
+
alias: 'example-auth',
|
|
175
|
+
debug: true,
|
|
176
|
+
config: { timeout: 5000 },
|
|
177
|
+
fixtures: { users: [] },
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const adapter = new MockAuthAdapter(params)
|
|
181
|
+
expect(adapter.getParam('host')).toBe('https://auth.example.com')
|
|
182
|
+
expect(adapter.getParam('debug')).toBe(true)
|
|
183
|
+
expect(adapter.getParam('config')).toEqual({ timeout: 5000 })
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
it('should work with empty parameters', () => {
|
|
187
|
+
const adapter = new MockAuthAdapter()
|
|
188
|
+
expect(adapter.getParam('host')).toBeUndefined()
|
|
189
|
+
expect(adapter.getParam('debug')).toBeUndefined()
|
|
190
|
+
})
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
describe('Provider registry behavior', () => {
|
|
194
|
+
it('should maintain providers across multiple operations', () => {
|
|
195
|
+
Auth.addProvider(mockAdapter1, 'persistent')
|
|
196
|
+
|
|
197
|
+
// Multiple get operations
|
|
198
|
+
const provider1 = Auth.getProvider('persistent')
|
|
199
|
+
const provider2 = Auth.getProvider('persistent')
|
|
200
|
+
|
|
201
|
+
expect(provider1).toBe(provider2)
|
|
202
|
+
expect(provider1).toBe(mockAdapter1)
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
it('should handle switching default provider multiple times', () => {
|
|
206
|
+
Auth.addProvider(mockAdapter1, 'provider1', true)
|
|
207
|
+
expect(Auth.defaultProvider).toBe('provider1')
|
|
208
|
+
|
|
209
|
+
Auth.addProvider(mockAdapter2, 'provider2', true)
|
|
210
|
+
expect(Auth.defaultProvider).toBe('provider2')
|
|
211
|
+
|
|
212
|
+
const defaultProvider = Auth.getProvider()
|
|
213
|
+
expect(defaultProvider).toBe(mockAdapter2)
|
|
214
|
+
})
|
|
215
|
+
})
|
|
216
|
+
})
|
package/src/Auth.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Core } from '@quatrain/core'
|
|
2
|
+
import { AbstractAuthAdapter } from './AbstractAuthAdapter'
|
|
3
|
+
import Middleware from './middlewares/AuthMiddleware'
|
|
4
|
+
|
|
5
|
+
export enum AuthAction {
|
|
6
|
+
SIGNIN = 'signin',
|
|
7
|
+
SIGNUP = 'signup',
|
|
8
|
+
SIGNOUT = 'signout',
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Authentication Parameters acceptable keys
|
|
13
|
+
*/
|
|
14
|
+
export type AuthParametersKeys =
|
|
15
|
+
| 'host'
|
|
16
|
+
| 'alias'
|
|
17
|
+
| 'middlewares'
|
|
18
|
+
| 'config'
|
|
19
|
+
| 'fixtures'
|
|
20
|
+
| 'debug'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Backend parameters interface
|
|
24
|
+
*/
|
|
25
|
+
export interface AuthParameters {
|
|
26
|
+
host?: string
|
|
27
|
+
alias?: string
|
|
28
|
+
middlewares?: Middleware[]
|
|
29
|
+
config?: any
|
|
30
|
+
fixtures?: any
|
|
31
|
+
debug?: boolean
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface AuthAdapter extends AbstractAuthAdapter {}
|
|
35
|
+
|
|
36
|
+
export type AuthRegistry<T extends AbstractAuthAdapter> = { [x: string]: T }
|
|
37
|
+
|
|
38
|
+
export class Auth extends Core {
|
|
39
|
+
static defaultProvider = 'default'
|
|
40
|
+
static logger = this.addLogger('Auth')
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
static ERROR_EMAIL_EXISTS = `User email already exists`
|
|
44
|
+
|
|
45
|
+
protected static _providers: AuthRegistry<any> = {}
|
|
46
|
+
|
|
47
|
+
static addProvider(
|
|
48
|
+
provider: AbstractAuthAdapter,
|
|
49
|
+
alias: string,
|
|
50
|
+
setDefault: boolean = false
|
|
51
|
+
) {
|
|
52
|
+
this._providers[alias] = provider
|
|
53
|
+
if (setDefault) {
|
|
54
|
+
this.defaultProvider = alias
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
static getProvider<T extends AbstractAuthAdapter>(
|
|
59
|
+
alias: string = this.defaultProvider
|
|
60
|
+
): T {
|
|
61
|
+
if (this._providers[alias]) {
|
|
62
|
+
return this._providers[alias]
|
|
63
|
+
} else {
|
|
64
|
+
throw new Error(`Unknown provider alias: '${alias}'`)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export class AuthenticationError extends Error {}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { AbstractAuthAdapter } from './AbstractAuthAdapter'
|
|
2
|
+
import { User } from '@quatrain/backend'
|
|
3
|
+
import { AuthParameters } from './Auth'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Mock Authentication Adapter for testing purposes
|
|
7
|
+
*/
|
|
8
|
+
export class MockAuthAdapter extends AbstractAuthAdapter {
|
|
9
|
+
private registeredUsers: Map<string, User> = new Map()
|
|
10
|
+
private tokens: Map<string, any> = new Map()
|
|
11
|
+
private refreshTokens: Map<string, string> = new Map()
|
|
12
|
+
|
|
13
|
+
constructor(params: AuthParameters = {}) {
|
|
14
|
+
super(params)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async register(user: User, clearPassword?: string): Promise<any> {
|
|
18
|
+
if (this.registeredUsers.has(user._.email)) {
|
|
19
|
+
throw new Error('User email already exists')
|
|
20
|
+
}
|
|
21
|
+
this.registeredUsers.set(user._.email, user)
|
|
22
|
+
return { success: true, user }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async signup(login: string, password: string): Promise<any> {
|
|
26
|
+
const user = this.registeredUsers.get(login)
|
|
27
|
+
if (!user) {
|
|
28
|
+
throw new Error('User not found')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const token = `mock-token-${Date.now()}`
|
|
32
|
+
const refreshToken = `mock-refresh-token-${Date.now()}`
|
|
33
|
+
|
|
34
|
+
this.tokens.set(token, { user, timestamp: Date.now() })
|
|
35
|
+
this.refreshTokens.set(refreshToken, token)
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
success: true,
|
|
39
|
+
token,
|
|
40
|
+
refreshToken,
|
|
41
|
+
user,
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async signout(user: User): Promise<any> {
|
|
46
|
+
// Remove all tokens associated with this user
|
|
47
|
+
for (const [token, data] of this.tokens.entries()) {
|
|
48
|
+
if (data.user._.email === user._.email) {
|
|
49
|
+
this.tokens.delete(token)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return { success: true }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async update(user: User, updatable: any): Promise<any> {
|
|
56
|
+
const existingUser = this.registeredUsers.get(user._.email)
|
|
57
|
+
if (!existingUser) {
|
|
58
|
+
throw new Error('User not found')
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const updatedUser = { ...existingUser, ...updatable }
|
|
62
|
+
this.registeredUsers.set(user._.email, updatedUser)
|
|
63
|
+
|
|
64
|
+
return { success: true, user: updatedUser }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async delete(user: User): Promise<any> {
|
|
68
|
+
if (!this.registeredUsers.has(user._.email)) {
|
|
69
|
+
throw new Error('User not found')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this.registeredUsers.delete(user._.email)
|
|
73
|
+
await this.signout(user)
|
|
74
|
+
|
|
75
|
+
return { success: true }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
getAuthToken(token: string): any {
|
|
79
|
+
const tokenData = this.tokens.get(token)
|
|
80
|
+
if (!tokenData) {
|
|
81
|
+
throw new Error('Invalid token')
|
|
82
|
+
}
|
|
83
|
+
return tokenData
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async refreshToken(refreshToken: string): Promise<any> {
|
|
87
|
+
const oldToken = this.refreshTokens.get(refreshToken)
|
|
88
|
+
if (!oldToken) {
|
|
89
|
+
throw new Error('Invalid refresh token')
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const tokenData = this.tokens.get(oldToken)
|
|
93
|
+
if (!tokenData) {
|
|
94
|
+
throw new Error('Token not found')
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Generate new tokens
|
|
98
|
+
const newToken = `mock-token-${Date.now()}`
|
|
99
|
+
const newRefreshToken = `mock-refresh-token-${Date.now()}`
|
|
100
|
+
|
|
101
|
+
// Remove old tokens
|
|
102
|
+
this.tokens.delete(oldToken)
|
|
103
|
+
this.refreshTokens.delete(refreshToken)
|
|
104
|
+
|
|
105
|
+
// Add new tokens
|
|
106
|
+
this.tokens.set(newToken, tokenData)
|
|
107
|
+
this.refreshTokens.set(newRefreshToken, newToken)
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
success: true,
|
|
111
|
+
token: newToken,
|
|
112
|
+
refreshToken: newRefreshToken,
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
revokeAuthToken(token: string): any {
|
|
117
|
+
if (!this.tokens.has(token)) {
|
|
118
|
+
throw new Error('Token not found')
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
this.tokens.delete(token)
|
|
122
|
+
|
|
123
|
+
// Remove associated refresh token
|
|
124
|
+
for (const [
|
|
125
|
+
refreshToken,
|
|
126
|
+
associatedToken,
|
|
127
|
+
] of this.refreshTokens.entries()) {
|
|
128
|
+
if (associatedToken === token) {
|
|
129
|
+
this.refreshTokens.delete(refreshToken)
|
|
130
|
+
break
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return { success: true }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
setCustomUserClaims(id: string, claims: any): any {
|
|
138
|
+
// Find user by id and set custom claims
|
|
139
|
+
for (const [email, user] of this.registeredUsers.entries()) {
|
|
140
|
+
if ((user as any)._.id === id) {
|
|
141
|
+
;(user as any).customClaims = claims
|
|
142
|
+
return { success: true, claims }
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
throw new Error('User not found')
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Helper methods for testing
|
|
149
|
+
clearAll(): void {
|
|
150
|
+
this.registeredUsers.clear()
|
|
151
|
+
this.tokens.clear()
|
|
152
|
+
this.refreshTokens.clear()
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
getUserByEmail(email: string): User | undefined {
|
|
156
|
+
return this.registeredUsers.get(email)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
hasToken(token: string): boolean {
|
|
160
|
+
return this.tokens.has(token)
|
|
161
|
+
}
|
|
162
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Auth, AuthAction, AuthParameters, AuthParametersKeys } from './Auth'
|
|
2
|
+
import { AbstractAuthAdapter } from './AbstractAuthAdapter'
|
|
3
|
+
import { AuthInterface } from './types/AuthInterface'
|
|
4
|
+
import { AuthenticationError } from './AuthenticationError'
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
Auth,
|
|
8
|
+
AuthAction,
|
|
9
|
+
AuthParameters,
|
|
10
|
+
AuthParametersKeys,
|
|
11
|
+
AuthInterface,
|
|
12
|
+
AbstractAuthAdapter,
|
|
13
|
+
AuthenticationError,
|
|
14
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { User } from '@quatrain/core'
|
|
2
|
+
|
|
3
|
+
export interface AuthInterface {
|
|
4
|
+
register(user: User): Promise<any>
|
|
5
|
+
|
|
6
|
+
signup(login: string, password: string): Promise<any>
|
|
7
|
+
|
|
8
|
+
signout(user: User): Promise<any>
|
|
9
|
+
|
|
10
|
+
update(user: User, updatable: any): Promise<any>
|
|
11
|
+
|
|
12
|
+
delete(user: User): Promise<any>
|
|
13
|
+
|
|
14
|
+
getAuthToken(token: string): any
|
|
15
|
+
|
|
16
|
+
refreshToken(refreshToken: string): Promise<any>
|
|
17
|
+
}
|