supaapps-auth 2.1.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,419 +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
- const accessToken = localStorage.getItem('access_token');
119
- const refreshToken = localStorage.getItem('refresh_token');
120
- // If either token is missing, user is not logged in
121
- if (!accessToken || !refreshToken) {
122
- return false;
123
- }
124
- // If access token is expired, try to refresh
125
- if (this.isTokenExpired(accessToken)) {
126
- try {
127
- yield this.refreshAccessToken();
128
- return true;
129
- }
130
- catch (_a) {
131
- return false;
132
- }
133
- }
134
- // If access token is valid
135
- return true;
136
- });
137
- }
138
- getAccessToken() {
139
- return __awaiter(this, arguments, void 0, function* (mustBeLoggedIn = false) {
140
- try {
141
- return yield this.checkAccessToken();
142
- }
143
- catch (error) {
144
- if (mustBeLoggedIn) {
145
- this.onStateChange({
146
- type: types_1.AuthEventType.FAILED_MUST_LOGIN_CHECK,
147
- });
148
- }
149
- return '';
150
- }
151
- });
152
- }
153
- platformCheck(email) {
154
- return __awaiter(this, void 0, void 0, function* () {
155
- const response = yield axios_1.default.post(`${this.authServer}auth/email/platform_check`, {
156
- realm_name: this.realmName,
157
- email,
158
- });
159
- if (response.data.error || response.data.errors) {
160
- throw new Error(response.data.error || response.data.message);
161
- }
162
- return response.status === 200
163
- ? response.data
164
- : { platforms: [] };
165
- });
166
- }
167
- verifyEmail(email, token) {
168
- return __awaiter(this, void 0, void 0, function* () {
169
- const response = yield axios_1.default.post(`${this.authServer}auth/email/verify`, {
170
- realm_name: this.realmName,
171
- email,
172
- token,
173
- });
174
- if (response.data.error || response.data.errors) {
175
- throw new Error(response.data.error || response.data.message);
176
- }
177
- return response.status === 200;
178
- });
179
- }
180
- doPassReset(email, token, newPassword) {
181
- return __awaiter(this, void 0, void 0, function* () {
182
- const response = yield axios_1.default.post(`${this.authServer}auth/email/do_pass_reset`, {
183
- realm_name: this.realmName,
184
- email,
185
- token,
186
- new_password: newPassword,
187
- });
188
- if (response.data.error || response.data.errors) {
189
- throw new Error(response.data.error || response.data.message);
190
- }
191
- return response.status === 200;
192
- });
193
- }
194
- changeEmail(email) {
195
- return __awaiter(this, void 0, void 0, function* () {
196
- const accessToken = localStorage.getItem('access_token');
197
- if (!accessToken) {
198
- throw new Error('Access token not found');
199
- }
200
- const response = yield axios_1.default.post(`${this.authServer}auth/email/change_email`, {
201
- realm_name: this.realmName,
202
- email,
203
- }, {
204
- headers: { Authorization: `Bearer ${accessToken}` },
205
- });
206
- if (response.data.error || response.data.errors) {
207
- throw new Error(response.data.error || response.data.message);
208
- }
209
- return response.status === 200;
210
- });
211
- }
212
- initPasswordReset(email) {
213
- return __awaiter(this, void 0, void 0, function* () {
214
- const response = yield axios_1.default.post(`${this.authServer}auth/email/init_pass_reset`, {
215
- realm_name: this.realmName,
216
- email,
217
- });
218
- if (response.data.error || response.data.errors) {
219
- throw new Error(response.data.error || response.data.message);
220
- }
221
- return response.status === 200 || response.status === 201;
222
- });
223
- }
224
- /**
225
- * Updates user account fields. Only sends fields present in the update object.
226
- * For password, expects: { old: string, new: string }
227
- */
228
- updateAccount(update) {
229
- return __awaiter(this, void 0, void 0, function* () {
230
- const accessToken = localStorage.getItem('access_token');
231
- if (!accessToken) {
232
- throw new Error('Access token not found');
233
- }
234
- // Update name
235
- if (update.firstName || update.lastName) {
236
- 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 })), {
237
- headers: { Authorization: `Bearer ${accessToken}` },
238
- });
239
- if (response.data.error || response.data.errors) {
240
- throw new Error(response.data.error || response.data.message);
241
- }
242
- }
243
- // Update email
244
- if (update.email) {
245
- const response = yield axios_1.default.post(`${this.authServer}auth/email/change_email`, {
246
- realm_name: this.realmName,
247
- email: update.email,
248
- }, {
249
- headers: { Authorization: `Bearer ${accessToken}` },
250
- });
251
- if (response.data.error || response.data.errors) {
252
- throw new Error(response.data.error || response.data.message);
253
- }
254
- }
255
- // Update password
256
- if (update.password && update.email) {
257
- const response = yield axios_1.default.post(`${this.authServer}auth/email/change_pass`, {
258
- realm_name: this.realmName,
259
- email: update.email,
260
- old_password: update.password.old,
261
- new_password: update.password.new,
262
- }, {
263
- headers: { Authorization: `Bearer ${accessToken}` },
264
- });
265
- if (response.data.error || response.data.errors) {
266
- throw new Error(response.data.error || response.data.message);
267
- }
268
- }
269
- else if (update.password && !update.email) {
270
- throw new Error('Email is required to change password');
271
- }
272
- return true;
273
- });
274
- }
275
- changePassword(oldPassword, newPassword, email) {
276
- return __awaiter(this, void 0, void 0, function* () {
277
- const accessToken = localStorage.getItem('access_token');
278
- if (!accessToken) {
279
- throw new Error('Access token not found');
280
- }
281
- const response = yield axios_1.default.post(`${this.authServer}auth/email/change_pass`, {
282
- realm_name: this.realmName,
283
- email,
284
- old_password: oldPassword,
285
- new_password: newPassword,
286
- }, {
287
- headers: { Authorization: `Bearer ${accessToken}` },
288
- });
289
- if (response.data.error || response.data.errors) {
290
- throw new Error(response.data.error || response.data.message);
291
- }
292
- return response.status === 200;
293
- });
294
- }
295
- registerUsingEmail(firstName, lastName, email, password) {
296
- return __awaiter(this, void 0, void 0, function* () {
297
- const response = yield axios_1.default.post(`${this.authServer}auth/email/register`, {
298
- realm_name: this.realmName,
299
- first_name: firstName,
300
- last_name: lastName,
301
- email,
302
- password,
303
- });
304
- if (response.data.message || response.data.error) {
305
- throw new Error(response.data.message || response.data.error);
306
- }
307
- if (!response.data.access_token) {
308
- throw new Error('Something went wrong');
309
- }
310
- this.saveTokens(response, false);
311
- });
312
- }
313
- saveTokens(response, byRefresh) {
314
- localStorage.setItem('access_token', response.data.access_token);
315
- localStorage.setItem('refresh_token', response.data.refresh_token);
316
- this.onStateChange({
317
- type: byRefresh
318
- ? types_1.AuthEventType.USER_UPDATED
319
- : types_1.AuthEventType.USER_LOGGED_IN,
320
- user: this.tokenToPayload(response.data.access_token),
321
- });
322
- const user = this.tokenToPayload(response.data.access_token);
323
- localStorage.setItem('user', JSON.stringify(user));
324
- }
325
- loginUsingEmail(email, password) {
326
- return __awaiter(this, void 0, void 0, function* () {
327
- const response = yield axios_1.default.post(`${this.authServer}auth/email/login`, {
328
- realm_name: this.realmName,
329
- email,
330
- password,
331
- });
332
- if (response.data.message || response.data.error) {
333
- throw new Error(response.data.message || response.data.error);
334
- }
335
- this.saveTokens(response, false);
336
- });
337
- }
338
- loginUsingPkce(code) {
339
- return __awaiter(this, void 0, void 0, function* () {
340
- try {
341
- const codeVerifier = localStorage.getItem('codeVerifier');
342
- if (!codeVerifier) {
343
- throw new Error('Code verifier not found');
344
- }
345
- const response = yield axios_1.default.post(`${this.authServer}auth/pkce_exchange`, {
346
- realm_name: this.realmName,
347
- code,
348
- redirect_uri: this.redirectUri,
349
- code_verifier: codeVerifier,
350
- });
351
- this.saveTokens(response, false);
352
- }
353
- finally {
354
- localStorage.removeItem('codeVerifier');
355
- localStorage.removeItem('codeChallenge');
356
- }
357
- });
358
- }
359
- logout() {
360
- return __awaiter(this, void 0, void 0, function* () {
361
- try {
362
- const accessToken = localStorage.getItem('access_token');
363
- if (!accessToken) {
364
- throw new Error('Access token not found');
365
- }
366
- yield axios_1.default.post(`${this.authServer}auth/logout`, {}, {
367
- headers: { Authorization: `Bearer ${accessToken}` },
368
- });
369
- }
370
- finally {
371
- localStorage.removeItem('access_token');
372
- localStorage.removeItem('refresh_token');
373
- this.onStateChange({ type: types_1.AuthEventType.USER_LOGGED_OUT });
374
- }
375
- });
376
- }
377
- static validateToken(authServer, bearerToken) {
378
- return __awaiter(this, void 0, void 0, function* () {
379
- var _a;
380
- // @todo tests missing for this static validation
381
- // @todo add caching for public key and algo
382
- const decodedToken = (_a = (0, jsonwebtoken_1.decode)(bearerToken, {
383
- complete: true,
384
- })) === null || _a === void 0 ? void 0 : _a.payload;
385
- if (!decodedToken) {
386
- throw new Error('Not a valid jwt token');
387
- }
388
- const userToken = {
389
- id: decodedToken.id,
390
- iss: decodedToken.iss,
391
- sub: typeof decodedToken.sub === 'string'
392
- ? parseInt(decodedToken.sub)
393
- : decodedToken.sub,
394
- first_name: decodedToken.first_name,
395
- last_name: decodedToken.last_name,
396
- email: decodedToken.email,
397
- aud: decodedToken.aud,
398
- iat: decodedToken.iat,
399
- exp: decodedToken.exp,
400
- scopes: decodedToken.scopes,
401
- realm: decodedToken.realm,
402
- provider: decodedToken.provider,
403
- };
404
- const { data: publicKey } = yield axios_1.default.get(`${authServer}public/public_key`);
405
- const { data: algo } = yield axios_1.default.get(`${authServer}public/algo`);
406
- (0, jsonwebtoken_1.verify)(bearerToken, publicKey, { algorithms: [algo] });
407
- const { data: revokedIds } = yield axios_1.default.get(`${authServer}public/revoked_ids`);
408
- if (revokedIds.includes(decodedToken.id)) {
409
- throw new Error('Token is revoked');
410
- }
411
- return userToken;
412
- });
413
- }
414
- static resetInstance() {
415
- AuthManager.instance = null;
416
- }
417
- }
418
- exports.AuthManager = AuthManager;
419
- 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
- platforms: 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
-