supaapps-auth 1.0.5 → 1.1.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.
package/README.md ADDED
@@ -0,0 +1,74 @@
1
+ # TODO
2
+
3
+
4
+ `npm i supaapps-auth`
5
+
6
+
7
+ - Initialize
8
+ ```ts
9
+ new AuthManager(
10
+ 'https://supaapps-auth-api.testing.sacl.io/',
11
+ 'root',
12
+ 'http://localhost:3001/exchange',
13
+ () => {
14
+ // redirect to login
15
+ }
16
+ );
17
+ ```
18
+
19
+
20
+ - Require login
21
+
22
+ ```ts
23
+ import {AuthManager} from "./AuthManager";
24
+
25
+ const authManager = AuthManager.getInstance();
26
+ authManager.mustBeLoggedIn().then((isLoggedIn) => {
27
+ if (isLoggedIn) {
28
+ // do something
29
+ }
30
+ });
31
+
32
+ // or
33
+
34
+ AuthManager.getInstance().mustBeLoggedIn();
35
+ ```
36
+
37
+
38
+ - Get user info
39
+
40
+ ```ts
41
+ authManager.mustBeLoggedIn().then(
42
+ (isLoggedIn) => {
43
+ isLoggedIn && authManager.getAccessToken().then(
44
+ (token) => {
45
+ const decodedToken = JSON.parse(atob(token.split('.')[1]));
46
+ // access info for example
47
+ // decodedToken.first_name
48
+ }
49
+ );
50
+ }
51
+ )
52
+ ```
53
+
54
+
55
+
56
+ Get login uri
57
+
58
+ ```ts
59
+ AuthManager.getInstance().getLoginWithGoogleUri()
60
+ ```
61
+
62
+ - Validate access token
63
+
64
+ ```typescript
65
+ import { AuthManager } from './AuthManager';
66
+
67
+ const isValid = await AuthManager.validateToken(BEARER_HEADER_OR_ACCESS_TOKEN)
68
+ // or
69
+ AuthManager.validateToken(BEARER_OR_ACCESS_TOKEN).then((isValid) => {
70
+ if (isValid) {
71
+ // token is valid
72
+ }
73
+ })
74
+ ```
@@ -8,9 +8,10 @@ export declare class AuthManager {
8
8
  static getInstance<T>(): AuthManager;
9
9
  private toBase64Url;
10
10
  private generatePKCEPair;
11
- mustBeLoggedIn(): Promise<void>;
11
+ mustBeLoggedIn(): Promise<boolean>;
12
12
  getLoginWithGoogleUri(): string;
13
13
  isLoggedIn(): Promise<boolean>;
14
14
  getAccessToken(): Promise<string>;
15
15
  loginUsingPkce(code: any): Promise<void>;
16
+ static validateToken(authServer: string, bearerToken: string): Promise<boolean>;
16
17
  }
@@ -10,6 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.AuthManager = void 0;
13
+ const axios_1 = require("axios");
13
14
  const crypto_1 = require("crypto");
14
15
  class AuthManager {
15
16
  constructor(authServer, realmName, redirectUri, loginCallback) {
@@ -49,9 +50,15 @@ class AuthManager {
49
50
  }
50
51
  mustBeLoggedIn() {
51
52
  return __awaiter(this, void 0, void 0, function* () {
52
- if (!(yield this.isLoggedIn())) {
53
- this.loginCallback();
54
- }
53
+ return new Promise((resolve, reject) => {
54
+ this.isLoggedIn().then((isLoggedIn) => {
55
+ if (!isLoggedIn) {
56
+ this.loginCallback();
57
+ return resolve(false);
58
+ }
59
+ return resolve(true);
60
+ });
61
+ });
55
62
  });
56
63
  }
57
64
  getLoginWithGoogleUri() {
@@ -165,6 +172,40 @@ class AuthManager {
165
172
  });
166
173
  });
167
174
  }
175
+ static validateToken(authServer, bearerToken) {
176
+ return __awaiter(this, void 0, void 0, function* () {
177
+ return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
178
+ try {
179
+ const accessToken = bearerToken.includes('Bearer ') ? bearerToken.replace('Bearer ', '') : bearerToken;
180
+ const decodedToken = accessToken ? JSON.parse(atob(accessToken.split('.')[1])) : null;
181
+ if (!decodedToken) {
182
+ return resolve(false);
183
+ }
184
+ const currentTime = Date.now() / 1000;
185
+ if (decodedToken.exp < currentTime) {
186
+ return resolve(false);
187
+ }
188
+ const { data: publicKey } = yield axios_1.default.get(`${authServer}public/public_key`);
189
+ const { data: algo } = yield axios_1.default.get(`${authServer}public/algo`);
190
+ const jwt = require('jsonwebtoken');
191
+ jwt.verify(accessToken, publicKey, { algorithms: [algo] }, (error, payload) => {
192
+ if (error) {
193
+ return resolve(false);
194
+ }
195
+ axios_1.default.get(`${authServer}public/revoked_ids`).then(({ data: revokedIds }) => {
196
+ if (revokedIds && revokedIds.includes(decodedToken['id'])) {
197
+ return resolve(false);
198
+ }
199
+ return resolve(true);
200
+ });
201
+ });
202
+ }
203
+ catch (error) {
204
+ reject(error);
205
+ }
206
+ }));
207
+ });
208
+ }
168
209
  }
169
210
  exports.AuthManager = AuthManager;
170
211
  AuthManager.instance = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supaapps-auth",
3
- "version": "1.0.5",
3
+ "version": "1.1.0",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -12,7 +12,8 @@
12
12
  "license": "MIT",
13
13
  "dependencies": {
14
14
  "axios": "^1.6.7",
15
- "crypto": "^1.0.1"
15
+ "crypto": "^1.0.1",
16
+ "jsonwebtoken": "^9.0.2"
16
17
  },
17
18
  "devDependencies": {
18
19
  "@types/node": "^20.11.10",
@@ -28,9 +28,9 @@ export class AuthManager {
28
28
 
29
29
  private toBase64Url = (base64String: string) => {
30
30
  return base64String
31
- .replace(/\+/g, '-')
32
- .replace(/\//g, '_')
33
- .replace(/=+$/, '');
31
+ .replace(/\+/g, '-')
32
+ .replace(/\//g, '_')
33
+ .replace(/=+$/, '');
34
34
  };
35
35
  private generatePKCEPair = () => {
36
36
  const NUM_OF_BYTES = 32; // This will generate a verifier of sufficient length
@@ -38,23 +38,30 @@ export class AuthManager {
38
38
 
39
39
  // Generate code verifier
40
40
  const newCodeVerifier = this.toBase64Url(
41
- randomBytes(NUM_OF_BYTES).toString('base64'),
41
+ randomBytes(NUM_OF_BYTES).toString('base64'),
42
42
  );
43
43
 
44
44
  // Generate code challenge
45
45
  const hash = createHash(HASH_ALG)
46
- .update(newCodeVerifier)
47
- .digest('base64');
46
+ .update(newCodeVerifier)
47
+ .digest('base64');
48
48
  const newCodeChallenge = this.toBase64Url(hash);
49
49
 
50
50
  return { newCodeVerifier, newCodeChallenge };
51
51
  };
52
52
 
53
- public async mustBeLoggedIn(): Promise<void> {
54
- if (!await this.isLoggedIn()) {
55
- this.loginCallback();
56
- }
53
+ public async mustBeLoggedIn(): Promise<boolean> {
54
+ return new Promise((resolve, reject) => {
55
+ this.isLoggedIn().then((isLoggedIn) => {
56
+ if (!isLoggedIn) {
57
+ this.loginCallback();
58
+ return resolve(false);
59
+ }
60
+ return resolve(true);
61
+ });
62
+ });
57
63
  }
64
+
58
65
  public getLoginWithGoogleUri(): string {
59
66
  // get or create codeVerifier and codeChallenge from localstorage
60
67
  const { newCodeVerifier, newCodeChallenge } = this.generatePKCEPair();
@@ -65,7 +72,7 @@ export class AuthManager {
65
72
 
66
73
  if (this.authServer && this.realmName && this.redirectUri) {
67
74
  return `${this.authServer}auth/login_with_google?realm_name=${this.realmName}` +
68
- `&redirect_uri=${encodeURIComponent(this.redirectUri)}&code_challenge=${codeChallenge}&code_challenge_method=S256`
75
+ `&redirect_uri=${encodeURIComponent(this.redirectUri)}&code_challenge=${codeChallenge}&code_challenge_method=S256`
69
76
  }
70
77
  }
71
78
  public async isLoggedIn(): Promise<boolean> {
@@ -138,24 +145,24 @@ export class AuthManager {
138
145
  code_verifier: codeVerifier,
139
146
  }),
140
147
  })
141
- .then((response) => {
142
- localStorage.removeItem('codeVerifier');
143
- localStorage.removeItem('codeChallenge');
144
- if (response.status !== 200) {
145
- throw new Error('Failed to exchange code for token');
146
- }
147
- return response.json();
148
- })
149
- .then((exchangeJson) => {
150
- localStorage.setItem('access_token', exchangeJson.access_token);
151
- localStorage.setItem('refresh_token', exchangeJson.refresh_token);
152
- resolve();
153
- })
154
- .catch((error) => {
155
- localStorage.removeItem('codeVerifier');
156
- localStorage.removeItem('codeChallenge');
157
- reject(error);
158
- });
148
+ .then((response) => {
149
+ localStorage.removeItem('codeVerifier');
150
+ localStorage.removeItem('codeChallenge');
151
+ if (response.status !== 200) {
152
+ throw new Error('Failed to exchange code for token');
153
+ }
154
+ return response.json();
155
+ })
156
+ .then((exchangeJson) => {
157
+ localStorage.setItem('access_token', exchangeJson.access_token);
158
+ localStorage.setItem('refresh_token', exchangeJson.refresh_token);
159
+ resolve();
160
+ })
161
+ .catch((error) => {
162
+ localStorage.removeItem('codeVerifier');
163
+ localStorage.removeItem('codeChallenge');
164
+ reject(error);
165
+ });
159
166
  }
160
167
  } catch (error) {
161
168
  reject(error);
@@ -163,4 +170,38 @@ export class AuthManager {
163
170
  });
164
171
  }
165
172
 
173
+ public static async validateToken(authServer: string, bearerToken: string): Promise<boolean> {
174
+ return new Promise<boolean>(async (resolve, reject) => {
175
+ try {
176
+ const accessToken = bearerToken.includes('Bearer ') ? bearerToken.replace('Bearer ', '') : bearerToken;
177
+ const decodedToken = accessToken ? JSON.parse(atob(accessToken.split('.')[1])) : null;
178
+
179
+ if (!decodedToken) {
180
+ return resolve(false);
181
+ }
182
+
183
+ const currentTime = Date.now() / 1000;
184
+ if (decodedToken.exp < currentTime) {
185
+ return resolve(false);
186
+ }
187
+
188
+ const { data: publicKey } = await axios.get(`${authServer}public/public_key`);
189
+ const { data: algo } = await axios.get(`${authServer}public/algo`);
190
+ const jwt = require('jsonwebtoken');
191
+ jwt.verify(accessToken, publicKey, { algorithms: [algo] }, (error, payload) => {
192
+ if (error) {
193
+ return resolve(false);
194
+ }
195
+ axios.get(`${authServer}public/revoked_ids`).then(({ data: revokedIds }) => {
196
+ if (revokedIds && (revokedIds as number[]).includes(decodedToken['id'])) {
197
+ return resolve(false);
198
+ }
199
+ return resolve(true);
200
+ });
201
+ });
202
+ } catch (error) {
203
+ reject(error);
204
+ }
205
+ })
206
+ }
166
207
  }