supaapps-auth 2.0.0 → 3.0.0

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.
@@ -1,30 +0,0 @@
1
- name: Publish to NPM
2
-
3
- on:
4
- push:
5
- tags:
6
- - "v*"
7
-
8
- jobs:
9
- build:
10
- runs-on: ubuntu-latest
11
- steps:
12
- - name: Checkout code
13
- uses: actions/checkout@v2
14
-
15
- - name: Use Node.js
16
- uses: actions/setup-node@v3
17
- with:
18
- node-version: lts/*
19
- registry-url: 'https://registry.npmjs.org'
20
-
21
- - name: Install dependencies
22
- run: npm install
23
-
24
- - name: Build
25
- run: npm run build
26
-
27
- - name: Publish to npm
28
- run: npm publish
29
- env:
30
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/.prettierrc DELETED
@@ -1,7 +0,0 @@
1
- {
2
- "semi": true,
3
- "trailingComma": "all",
4
- "singleQuote": true,
5
- "printWidth": 70,
6
- "tabWidth": 2
7
- }
@@ -1,47 +0,0 @@
1
- import { AuthManagerEvent, PlatformCheckResponse, UserTokenPayload } from './types';
2
- export declare class AuthManager {
3
- private static instance;
4
- private authServer;
5
- private realmName;
6
- private redirectUri;
7
- private onStateChange;
8
- private constructor();
9
- static initialize(authServer: string, realmName: string, redirectUri: string, onStateChange: (event: AuthManagerEvent) => void): AuthManager;
10
- static getInstance(): AuthManager;
11
- private tokenToPayload;
12
- private toBase64Url;
13
- private generatePKCEPair;
14
- refreshAccessToken(isInitialization?: boolean): Promise<string>;
15
- checkAccessToken(isInitilization?: boolean): Promise<string>;
16
- private isTokenExpired;
17
- mustBeLoggedIn(): Promise<void>;
18
- getLoginWithGoogleUri(): string;
19
- isLoggedIn(): Promise<boolean>;
20
- getAccessToken(mustBeLoggedIn?: boolean): Promise<string>;
21
- platformCheck(email: string): Promise<PlatformCheckResponse>;
22
- verifyEmail(email: string, token: string): Promise<boolean>;
23
- doPassReset(email: string, token: string, newPassword: string): Promise<boolean>;
24
- changeEmail(email: string): Promise<boolean>;
25
- initPasswordReset(email: string): Promise<boolean>;
26
- /**
27
- * Updates user account fields. Only sends fields present in the update object.
28
- * For password, expects: { old: string, new: string }
29
- */
30
- updateAccount(update: {
31
- firstName?: string;
32
- lastName?: string;
33
- email?: string;
34
- password?: {
35
- old: string;
36
- new: string;
37
- };
38
- }): Promise<boolean>;
39
- changePassword(oldPassword: string, newPassword: string, email: string): Promise<boolean>;
40
- registerUsingEmail(firstName: string, lastName: string, email: string, password: string): Promise<void>;
41
- private saveTokens;
42
- loginUsingEmail(email: string, password: string): Promise<void>;
43
- loginUsingPkce(code: string): Promise<void>;
44
- logout(): Promise<void>;
45
- static validateToken(authServer: string, bearerToken: string): Promise<UserTokenPayload>;
46
- static resetInstance(): void;
47
- }
@@ -1,408 +0,0 @@
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.AuthManager = void 0;
13
- const axios_1 = require("axios");
14
- const crypto_1 = require("crypto");
15
- const jsonwebtoken_1 = require("jsonwebtoken"); // Ensure jsonwebtoken is correctly imported
16
- const types_1 = require("./types");
17
- class AuthManager {
18
- constructor(authServer, realmName, redirectUri, onStateChange) {
19
- this.authServer = authServer;
20
- this.realmName = realmName;
21
- this.redirectUri = redirectUri;
22
- this.onStateChange = onStateChange;
23
- AuthManager.instance = this;
24
- }
25
- static initialize(authServer, realmName, redirectUri, onStateChange) {
26
- if (!AuthManager.instance) {
27
- AuthManager.instance = new AuthManager(authServer, realmName, redirectUri, onStateChange);
28
- AuthManager.instance
29
- .checkAccessToken(true)
30
- .then((token) => {
31
- onStateChange({
32
- type: types_1.AuthEventType.INITALIZED_IN,
33
- user: AuthManager.instance.tokenToPayload(token),
34
- });
35
- })
36
- .catch(() => {
37
- onStateChange({ type: types_1.AuthEventType.INITALIZED_OUT });
38
- });
39
- }
40
- return AuthManager.instance;
41
- }
42
- static getInstance() {
43
- if (!AuthManager.instance) {
44
- throw new Error('AuthManager not initialized');
45
- }
46
- return AuthManager.instance;
47
- }
48
- tokenToPayload(token) {
49
- return JSON.parse(atob(token.split('.')[1]));
50
- }
51
- toBase64Url(base64String) {
52
- return base64String
53
- .replace(/\+/g, '-')
54
- .replace(/\//g, '_')
55
- .replace(/=+$/, '');
56
- }
57
- generatePKCEPair() {
58
- var _a, _b;
59
- const verifier = (_a = localStorage.getItem('codeVerifier')) !== null && _a !== void 0 ? _a : this.toBase64Url((0, crypto_1.randomBytes)(32).toString('base64'));
60
- const challenge = (_b = localStorage.getItem('codeChallenge')) !== null && _b !== void 0 ? _b : this.toBase64Url((0, crypto_1.createHash)('sha256').update(verifier).digest('base64'));
61
- localStorage.setItem('codeVerifier', verifier);
62
- localStorage.setItem('codeChallenge', challenge);
63
- return { verifier, challenge };
64
- }
65
- refreshAccessToken() {
66
- return __awaiter(this, arguments, void 0, function* (isInitialization = false) {
67
- try {
68
- const refreshToken = localStorage.getItem('refresh_token');
69
- if (!refreshToken) {
70
- throw new Error('No refresh token found');
71
- }
72
- const response = yield axios_1.default.post(`${this.authServer}auth/refresh`, {
73
- refresh_token: refreshToken,
74
- });
75
- this.saveTokens(response, true);
76
- return response.data.access_token;
77
- }
78
- catch (error) {
79
- console.error(`Refresh token error, logging out: ${error}`);
80
- localStorage.removeItem('access_token');
81
- localStorage.removeItem('refresh_token');
82
- if (!isInitialization) {
83
- // throw refresh fail only if not initialization
84
- this.onStateChange({ type: types_1.AuthEventType.REFRESH_FAILED });
85
- }
86
- throw error;
87
- }
88
- });
89
- }
90
- checkAccessToken() {
91
- return __awaiter(this, arguments, void 0, function* (isInitilization = false) {
92
- const accessToken = localStorage.getItem('access_token');
93
- if (accessToken && this.isTokenExpired(accessToken)) {
94
- return this.refreshAccessToken(isInitilization);
95
- }
96
- return accessToken;
97
- });
98
- }
99
- isTokenExpired(token) {
100
- const decoded = this.tokenToPayload(token);
101
- return decoded.exp < Date.now() / 1000;
102
- }
103
- mustBeLoggedIn() {
104
- return __awaiter(this, void 0, void 0, function* () {
105
- if (!(yield this.isLoggedIn())) {
106
- this.onStateChange({
107
- type: types_1.AuthEventType.FAILED_MUST_LOGIN_CHECK,
108
- });
109
- }
110
- });
111
- }
112
- getLoginWithGoogleUri() {
113
- const { challenge } = this.generatePKCEPair();
114
- return `${this.authServer}auth/login_with_google?realm_name=${this.realmName}&redirect_uri=${encodeURIComponent(this.redirectUri)}&code_challenge=${challenge}&code_challenge_method=S256`;
115
- }
116
- isLoggedIn() {
117
- return __awaiter(this, void 0, void 0, function* () {
118
- try {
119
- yield this.checkAccessToken();
120
- return true;
121
- }
122
- catch (error) {
123
- return false;
124
- }
125
- });
126
- }
127
- getAccessToken() {
128
- return __awaiter(this, arguments, void 0, function* (mustBeLoggedIn = false) {
129
- try {
130
- return yield this.checkAccessToken();
131
- }
132
- catch (error) {
133
- if (mustBeLoggedIn) {
134
- this.onStateChange({
135
- type: types_1.AuthEventType.FAILED_MUST_LOGIN_CHECK,
136
- });
137
- }
138
- return '';
139
- }
140
- });
141
- }
142
- platformCheck(email) {
143
- return __awaiter(this, void 0, void 0, function* () {
144
- const response = yield axios_1.default.post(`${this.authServer}auth/email/platform_check`, {
145
- realm_name: this.realmName,
146
- email,
147
- });
148
- if (response.data.error || response.data.errors) {
149
- throw new Error(response.data.error || response.data.message);
150
- }
151
- return response.status === 200
152
- ? response.data
153
- : { platforms: [] };
154
- });
155
- }
156
- verifyEmail(email, token) {
157
- return __awaiter(this, void 0, void 0, function* () {
158
- const response = yield axios_1.default.post(`${this.authServer}auth/email/verify`, {
159
- realm_name: this.realmName,
160
- email,
161
- token,
162
- });
163
- if (response.data.error || response.data.errors) {
164
- throw new Error(response.data.error || response.data.message);
165
- }
166
- return response.status === 200;
167
- });
168
- }
169
- doPassReset(email, token, newPassword) {
170
- return __awaiter(this, void 0, void 0, function* () {
171
- const response = yield axios_1.default.post(`${this.authServer}auth/email/do_pass_reset`, {
172
- realm_name: this.realmName,
173
- email,
174
- token,
175
- new_password: newPassword,
176
- });
177
- if (response.data.error || response.data.errors) {
178
- throw new Error(response.data.error || response.data.message);
179
- }
180
- return response.status === 200;
181
- });
182
- }
183
- changeEmail(email) {
184
- return __awaiter(this, void 0, void 0, function* () {
185
- const accessToken = localStorage.getItem('access_token');
186
- if (!accessToken) {
187
- throw new Error('Access token not found');
188
- }
189
- const response = yield axios_1.default.post(`${this.authServer}auth/email/change_email`, {
190
- realm_name: this.realmName,
191
- email,
192
- }, {
193
- headers: { Authorization: `Bearer ${accessToken}` },
194
- });
195
- if (response.data.error || response.data.errors) {
196
- throw new Error(response.data.error || response.data.message);
197
- }
198
- return response.status === 200;
199
- });
200
- }
201
- initPasswordReset(email) {
202
- return __awaiter(this, void 0, void 0, function* () {
203
- const response = yield axios_1.default.post(`${this.authServer}auth/email/init_pass_reset`, {
204
- realm_name: this.realmName,
205
- email,
206
- });
207
- if (response.data.error || response.data.errors) {
208
- throw new Error(response.data.error || response.data.message);
209
- }
210
- return response.status === 200 || response.status === 201;
211
- });
212
- }
213
- /**
214
- * Updates user account fields. Only sends fields present in the update object.
215
- * For password, expects: { old: string, new: string }
216
- */
217
- updateAccount(update) {
218
- return __awaiter(this, void 0, void 0, function* () {
219
- const accessToken = localStorage.getItem('access_token');
220
- if (!accessToken) {
221
- throw new Error('Access token not found');
222
- }
223
- // Update name
224
- if (update.firstName || update.lastName) {
225
- const response = yield axios_1.default.post(`${this.authServer}auth/email/update_profile`, Object.assign(Object.assign({ realm_name: this.realmName }, (update.firstName && { first_name: update.firstName })), (update.lastName && { last_name: update.lastName })), {
226
- headers: { Authorization: `Bearer ${accessToken}` },
227
- });
228
- if (response.data.error || response.data.errors) {
229
- throw new Error(response.data.error || response.data.message);
230
- }
231
- }
232
- // Update email
233
- if (update.email) {
234
- const response = yield axios_1.default.post(`${this.authServer}auth/email/change_email`, {
235
- realm_name: this.realmName,
236
- email: update.email,
237
- }, {
238
- headers: { Authorization: `Bearer ${accessToken}` },
239
- });
240
- if (response.data.error || response.data.errors) {
241
- throw new Error(response.data.error || response.data.message);
242
- }
243
- }
244
- // Update password
245
- if (update.password && update.email) {
246
- const response = yield axios_1.default.post(`${this.authServer}auth/email/change_pass`, {
247
- realm_name: this.realmName,
248
- email: update.email,
249
- old_password: update.password.old,
250
- new_password: update.password.new,
251
- }, {
252
- headers: { Authorization: `Bearer ${accessToken}` },
253
- });
254
- if (response.data.error || response.data.errors) {
255
- throw new Error(response.data.error || response.data.message);
256
- }
257
- }
258
- else if (update.password && !update.email) {
259
- throw new Error('Email is required to change password');
260
- }
261
- return true;
262
- });
263
- }
264
- changePassword(oldPassword, newPassword, email) {
265
- return __awaiter(this, void 0, void 0, function* () {
266
- const accessToken = localStorage.getItem('access_token');
267
- if (!accessToken) {
268
- throw new Error('Access token not found');
269
- }
270
- const response = yield axios_1.default.post(`${this.authServer}auth/email/change_pass`, {
271
- realm_name: this.realmName,
272
- email,
273
- old_password: oldPassword,
274
- new_password: newPassword,
275
- }, {
276
- headers: { Authorization: `Bearer ${accessToken}` },
277
- });
278
- if (response.data.error || response.data.errors) {
279
- throw new Error(response.data.error || response.data.message);
280
- }
281
- return response.status === 200;
282
- });
283
- }
284
- registerUsingEmail(firstName, lastName, email, password) {
285
- return __awaiter(this, void 0, void 0, function* () {
286
- const response = yield axios_1.default.post(`${this.authServer}auth/email/register`, {
287
- realm_name: this.realmName,
288
- first_name: firstName,
289
- last_name: lastName,
290
- email,
291
- password,
292
- });
293
- if (response.data.message || response.data.error) {
294
- throw new Error(response.data.message || response.data.error);
295
- }
296
- if (!response.data.access_token) {
297
- throw new Error('Something went wrong');
298
- }
299
- this.saveTokens(response, false);
300
- });
301
- }
302
- saveTokens(response, byRefresh) {
303
- localStorage.setItem('access_token', response.data.access_token);
304
- localStorage.setItem('refresh_token', response.data.refresh_token);
305
- this.onStateChange({
306
- type: byRefresh
307
- ? types_1.AuthEventType.USER_UPDATED
308
- : types_1.AuthEventType.USER_LOGGED_IN,
309
- user: this.tokenToPayload(response.data.access_token),
310
- });
311
- const user = this.tokenToPayload(response.data.access_token);
312
- localStorage.setItem('user', JSON.stringify(user));
313
- }
314
- loginUsingEmail(email, password) {
315
- return __awaiter(this, void 0, void 0, function* () {
316
- const response = yield axios_1.default.post(`${this.authServer}auth/email/login`, {
317
- realm_name: this.realmName,
318
- email,
319
- password,
320
- });
321
- if (response.data.message || response.data.error) {
322
- throw new Error(response.data.message || response.data.error);
323
- }
324
- this.saveTokens(response, false);
325
- });
326
- }
327
- loginUsingPkce(code) {
328
- return __awaiter(this, void 0, void 0, function* () {
329
- try {
330
- const codeVerifier = localStorage.getItem('codeVerifier');
331
- if (!codeVerifier) {
332
- throw new Error('Code verifier not found');
333
- }
334
- const response = yield axios_1.default.post(`${this.authServer}auth/pkce_exchange`, {
335
- realm_name: this.realmName,
336
- code,
337
- redirect_uri: this.redirectUri,
338
- code_verifier: codeVerifier,
339
- });
340
- this.saveTokens(response, false);
341
- }
342
- finally {
343
- localStorage.removeItem('codeVerifier');
344
- localStorage.removeItem('codeChallenge');
345
- }
346
- });
347
- }
348
- logout() {
349
- return __awaiter(this, void 0, void 0, function* () {
350
- try {
351
- const accessToken = localStorage.getItem('access_token');
352
- if (!accessToken) {
353
- throw new Error('Access token not found');
354
- }
355
- yield axios_1.default.post(`${this.authServer}auth/logout`, {}, {
356
- headers: { Authorization: `Bearer ${accessToken}` },
357
- });
358
- }
359
- finally {
360
- localStorage.removeItem('access_token');
361
- localStorage.removeItem('refresh_token');
362
- this.onStateChange({ type: types_1.AuthEventType.USER_LOGGED_OUT });
363
- }
364
- });
365
- }
366
- static validateToken(authServer, bearerToken) {
367
- return __awaiter(this, void 0, void 0, function* () {
368
- var _a;
369
- // @todo tests missing for this static validation
370
- // @todo add caching for public key and algo
371
- const decodedToken = (_a = (0, jsonwebtoken_1.decode)(bearerToken, {
372
- complete: true,
373
- })) === null || _a === void 0 ? void 0 : _a.payload;
374
- if (!decodedToken) {
375
- throw new Error('Not a valid jwt token');
376
- }
377
- const userToken = {
378
- id: decodedToken.id,
379
- iss: decodedToken.iss,
380
- sub: typeof decodedToken.sub === 'string'
381
- ? parseInt(decodedToken.sub)
382
- : decodedToken.sub,
383
- first_name: decodedToken.first_name,
384
- last_name: decodedToken.last_name,
385
- email: decodedToken.email,
386
- aud: decodedToken.aud,
387
- iat: decodedToken.iat,
388
- exp: decodedToken.exp,
389
- scopes: decodedToken.scopes,
390
- realm: decodedToken.realm,
391
- provider: decodedToken.provider,
392
- };
393
- const { data: publicKey } = yield axios_1.default.get(`${authServer}public/public_key`);
394
- const { data: algo } = yield axios_1.default.get(`${authServer}public/algo`);
395
- (0, jsonwebtoken_1.verify)(bearerToken, publicKey, { algorithms: [algo] });
396
- const { data: revokedIds } = yield axios_1.default.get(`${authServer}public/revoked_ids`);
397
- if (revokedIds.includes(decodedToken.id)) {
398
- throw new Error('Token is revoked');
399
- }
400
- return userToken;
401
- });
402
- }
403
- static resetInstance() {
404
- AuthManager.instance = null;
405
- }
406
- }
407
- exports.AuthManager = AuthManager;
408
- AuthManager.instance = null;
package/dist/types.d.ts DELETED
@@ -1,40 +0,0 @@
1
- export declare enum AuthEventType {
2
- INITALIZED_IN = "initialized-logged-in",
3
- INITALIZED_OUT = "initialized-logged-out",
4
- USER_LOGGED_IN = "user-logged-in",
5
- USER_LOGGED_OUT = "user-logged-out",
6
- USER_UPDATED = "user-updated",
7
- FAILED_MUST_LOGIN_CHECK = "failed-must-login",
8
- REFRESH_FAILED = "refresh-failed"
9
- }
10
- export interface UserTokenPayload {
11
- id: number;
12
- iss: string;
13
- sub: number | string;
14
- first_name: string;
15
- last_name: string;
16
- email: string;
17
- aud: string;
18
- iat: number;
19
- exp: number;
20
- scopes: string;
21
- realm: string;
22
- provider: Platforms;
23
- }
24
- export interface AuthManagerEvent {
25
- type: AuthEventType;
26
- user?: UserTokenPayload;
27
- }
28
- export interface PlatformCheckResponse {
29
- platform: Platforms[];
30
- }
31
- export declare enum Platforms {
32
- PASSWORD = "password",
33
- GOOGLE = "google",
34
- FACEBOOK = "facebook",
35
- TWITTER = "twitter",
36
- GITHUB = "github",
37
- APPLE = "apple",
38
- LINKEDIN = "linkedin",
39
- MICROSOFT = "microsoft"
40
- }
package/dist/types.js DELETED
@@ -1,24 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Platforms = exports.AuthEventType = void 0;
4
- var AuthEventType;
5
- (function (AuthEventType) {
6
- AuthEventType["INITALIZED_IN"] = "initialized-logged-in";
7
- AuthEventType["INITALIZED_OUT"] = "initialized-logged-out";
8
- AuthEventType["USER_LOGGED_IN"] = "user-logged-in";
9
- AuthEventType["USER_LOGGED_OUT"] = "user-logged-out";
10
- AuthEventType["USER_UPDATED"] = "user-updated";
11
- AuthEventType["FAILED_MUST_LOGIN_CHECK"] = "failed-must-login";
12
- AuthEventType["REFRESH_FAILED"] = "refresh-failed";
13
- })(AuthEventType || (exports.AuthEventType = AuthEventType = {}));
14
- var Platforms;
15
- (function (Platforms) {
16
- Platforms["PASSWORD"] = "password";
17
- Platforms["GOOGLE"] = "google";
18
- Platforms["FACEBOOK"] = "facebook";
19
- Platforms["TWITTER"] = "twitter";
20
- Platforms["GITHUB"] = "github";
21
- Platforms["APPLE"] = "apple";
22
- Platforms["LINKEDIN"] = "linkedin";
23
- Platforms["MICROSOFT"] = "microsoft";
24
- })(Platforms || (exports.Platforms = Platforms = {}));
package/jest.config.js DELETED
@@ -1,16 +0,0 @@
1
- module.exports = {
2
- testEnvironment: 'jsdom', // Use jsdom environment to run tests
3
- roots: ['<rootDir>/tests'],
4
- testMatch: ['**/*.test.ts'], // Matches test files in the tests directory
5
- transform: {
6
- '^.+\\.tsx?$': 'ts-jest', // Transform TypeScript files using ts-jest
7
- },
8
- setupFilesAfterEnv: ['jest-localstorage-mock'], // Setup local storage mock for all tests
9
- testEnvironment: 'node', // Use node environment to run tests,
10
- collectCoverage: true,
11
- coverageReporters: [
12
- "text",
13
- "cobertura"
14
- ]
15
- };
16
-