supaapps-auth 1.0.6 → 1.2.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 +85 -0
- package/dist/AuthManager.d.ts +4 -0
- package/dist/AuthManager.js +142 -32
- package/package.json +3 -2
- package/src/AuthManager.ts +158 -57
package/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
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
|
+
|
|
63
|
+
- Log user out
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
await authManager.logout();
|
|
67
|
+
// or
|
|
68
|
+
authManager.logout().then(() => {
|
|
69
|
+
// user is now logged out
|
|
70
|
+
})
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
- Validate access token
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { AuthManager } from './AuthManager';
|
|
77
|
+
|
|
78
|
+
const isValid = await AuthManager.validateToken(BEARER_HEADER_OR_ACCESS_TOKEN)
|
|
79
|
+
// or
|
|
80
|
+
AuthManager.validateToken(BEARER_OR_ACCESS_TOKEN).then((isValid) => {
|
|
81
|
+
if (isValid) {
|
|
82
|
+
// token is valid
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
```
|
package/dist/AuthManager.d.ts
CHANGED
|
@@ -8,9 +8,13 @@ export declare class AuthManager {
|
|
|
8
8
|
static getInstance<T>(): AuthManager;
|
|
9
9
|
private toBase64Url;
|
|
10
10
|
private generatePKCEPair;
|
|
11
|
+
private refreshAccessToken;
|
|
12
|
+
private checkAccessToken;
|
|
11
13
|
mustBeLoggedIn(): Promise<boolean>;
|
|
12
14
|
getLoginWithGoogleUri(): string;
|
|
13
15
|
isLoggedIn(): Promise<boolean>;
|
|
14
16
|
getAccessToken(): Promise<string>;
|
|
15
17
|
loginUsingPkce(code: any): Promise<void>;
|
|
18
|
+
logout(): Promise<void>;
|
|
19
|
+
static validateToken(authServer: string, bearerToken: string): Promise<boolean>;
|
|
16
20
|
}
|
package/dist/AuthManager.js
CHANGED
|
@@ -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) {
|
|
@@ -47,6 +48,79 @@ class AuthManager {
|
|
|
47
48
|
}
|
|
48
49
|
return AuthManager.instance;
|
|
49
50
|
}
|
|
51
|
+
refreshAccessToken() {
|
|
52
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
53
|
+
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
|
|
54
|
+
try {
|
|
55
|
+
const refreshToken = localStorage.getItem('refresh_token');
|
|
56
|
+
if (!refreshToken) {
|
|
57
|
+
throw new Error('No refresh token found');
|
|
58
|
+
}
|
|
59
|
+
const decodedRefreshToken = JSON.parse(atob(refreshToken.split('.')[1]));
|
|
60
|
+
if (decodedRefreshToken) {
|
|
61
|
+
const currentTime = Date.now() / 1000;
|
|
62
|
+
if (decodedRefreshToken.exp < currentTime) {
|
|
63
|
+
throw new Error('Refresh token expired');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
yield fetch(`${this.authServer}auth/refresh`, {
|
|
67
|
+
method: 'POST',
|
|
68
|
+
headers: {
|
|
69
|
+
'Content-Type': 'application/json',
|
|
70
|
+
},
|
|
71
|
+
body: JSON.stringify({
|
|
72
|
+
refresh_token: refreshToken,
|
|
73
|
+
}),
|
|
74
|
+
})
|
|
75
|
+
.then((response) => {
|
|
76
|
+
if (response.status !== 200) {
|
|
77
|
+
throw new Error('Failed to refresh the token');
|
|
78
|
+
}
|
|
79
|
+
return response.json();
|
|
80
|
+
})
|
|
81
|
+
.then((exchangeJson) => {
|
|
82
|
+
localStorage.setItem('refresh_token', exchangeJson.refresh_token);
|
|
83
|
+
localStorage.setItem('access_token', exchangeJson.access_token);
|
|
84
|
+
resolve(exchangeJson.access_token);
|
|
85
|
+
})
|
|
86
|
+
.catch((error) => {
|
|
87
|
+
reject(error);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
reject(error);
|
|
92
|
+
}
|
|
93
|
+
}));
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
checkAccessToken() {
|
|
97
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
98
|
+
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
|
|
99
|
+
try {
|
|
100
|
+
const accessToken = localStorage.getItem('access_token');
|
|
101
|
+
if (!accessToken) {
|
|
102
|
+
throw new Error('No access token found');
|
|
103
|
+
}
|
|
104
|
+
// decode access token and check if it's expired
|
|
105
|
+
const decodedToken = accessToken
|
|
106
|
+
? JSON.parse(atob(accessToken.split('.')[1]))
|
|
107
|
+
: null;
|
|
108
|
+
if (decodedToken) {
|
|
109
|
+
const currentTime = Date.now() / 1000;
|
|
110
|
+
if (decodedToken.exp < currentTime) {
|
|
111
|
+
// refreshing expired token
|
|
112
|
+
const newAccessToken = yield this.refreshAccessToken();
|
|
113
|
+
return resolve(newAccessToken);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
resolve(accessToken);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
reject(error);
|
|
120
|
+
}
|
|
121
|
+
}));
|
|
122
|
+
});
|
|
123
|
+
}
|
|
50
124
|
mustBeLoggedIn() {
|
|
51
125
|
return __awaiter(this, void 0, void 0, function* () {
|
|
52
126
|
return new Promise((resolve, reject) => {
|
|
@@ -75,56 +149,30 @@ class AuthManager {
|
|
|
75
149
|
isLoggedIn() {
|
|
76
150
|
return __awaiter(this, void 0, void 0, function* () {
|
|
77
151
|
// todo here: check if refresh token is expired and if so, try to refresh, then update token
|
|
78
|
-
return new Promise((resolve, reject) => {
|
|
152
|
+
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
|
|
79
153
|
try {
|
|
80
|
-
|
|
81
|
-
if (!accessToken) {
|
|
82
|
-
return resolve(false);
|
|
83
|
-
}
|
|
84
|
-
// decode access token and check if it's expired
|
|
85
|
-
const decodedToken = accessToken ? JSON.parse(atob(accessToken.split('.')[1])) : null;
|
|
86
|
-
if (decodedToken) {
|
|
87
|
-
const currentTime = Date.now() / 1000;
|
|
88
|
-
if (decodedToken.exp < currentTime) {
|
|
89
|
-
// add refresh check here instead and
|
|
90
|
-
localStorage.removeItem('access_token');
|
|
91
|
-
return resolve(false);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
154
|
+
yield this.checkAccessToken();
|
|
94
155
|
return resolve(true);
|
|
95
156
|
}
|
|
96
157
|
catch (error) {
|
|
97
158
|
reject(error);
|
|
98
159
|
}
|
|
99
|
-
});
|
|
160
|
+
}));
|
|
100
161
|
});
|
|
101
162
|
}
|
|
102
163
|
getAccessToken() {
|
|
103
164
|
return __awaiter(this, void 0, void 0, function* () {
|
|
104
165
|
// todo here: check if refresh token is expired and if so, try to refresh, then update token
|
|
105
166
|
// otherwise throw error
|
|
106
|
-
return new Promise((resolve, reject) => {
|
|
167
|
+
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
|
|
107
168
|
try {
|
|
108
|
-
const accessToken =
|
|
109
|
-
if (!accessToken) {
|
|
110
|
-
throw new Error('No access token found');
|
|
111
|
-
}
|
|
112
|
-
// decode access token and check if it's expired
|
|
113
|
-
const decodedToken = accessToken ? JSON.parse(atob(accessToken.split('.')[1])) : null;
|
|
114
|
-
if (decodedToken) {
|
|
115
|
-
const currentTime = Date.now() / 1000;
|
|
116
|
-
if (decodedToken.exp < currentTime) {
|
|
117
|
-
// add refresh check here instead and
|
|
118
|
-
localStorage.removeItem('access_token');
|
|
119
|
-
throw new Error('Access token expired');
|
|
120
|
-
}
|
|
121
|
-
}
|
|
169
|
+
const accessToken = yield this.checkAccessToken();
|
|
122
170
|
return resolve(accessToken);
|
|
123
171
|
}
|
|
124
172
|
catch (error) {
|
|
125
173
|
reject(error);
|
|
126
174
|
}
|
|
127
|
-
});
|
|
175
|
+
}));
|
|
128
176
|
});
|
|
129
177
|
}
|
|
130
178
|
loginUsingPkce(code) {
|
|
@@ -171,6 +219,68 @@ class AuthManager {
|
|
|
171
219
|
});
|
|
172
220
|
});
|
|
173
221
|
}
|
|
222
|
+
logout() {
|
|
223
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
224
|
+
return new Promise((resolve, reject) => {
|
|
225
|
+
try {
|
|
226
|
+
const bearerToken = localStorage.getItem('access_token');
|
|
227
|
+
localStorage.removeItem('access_token');
|
|
228
|
+
localStorage.removeItem('refresh_token');
|
|
229
|
+
fetch(`${this.authServer}auth/logout`, {
|
|
230
|
+
method: 'POST',
|
|
231
|
+
headers: {
|
|
232
|
+
'Content-Type': 'application/json',
|
|
233
|
+
'Authorization': `Bearer ${bearerToken}`,
|
|
234
|
+
},
|
|
235
|
+
}).then((response) => {
|
|
236
|
+
if (response.status !== 200) {
|
|
237
|
+
throw new Error('Failed to attempt logout');
|
|
238
|
+
}
|
|
239
|
+
resolve();
|
|
240
|
+
}).catch((error) => {
|
|
241
|
+
reject(error);
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
reject(error);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
static validateToken(authServer, bearerToken) {
|
|
251
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
252
|
+
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
|
|
253
|
+
try {
|
|
254
|
+
const accessToken = bearerToken.includes('Bearer ') ? bearerToken.replace('Bearer ', '') : bearerToken;
|
|
255
|
+
const decodedToken = accessToken ? JSON.parse(atob(accessToken.split('.')[1])) : null;
|
|
256
|
+
if (!decodedToken) {
|
|
257
|
+
return resolve(false);
|
|
258
|
+
}
|
|
259
|
+
const currentTime = Date.now() / 1000;
|
|
260
|
+
if (decodedToken.exp < currentTime) {
|
|
261
|
+
return resolve(false);
|
|
262
|
+
}
|
|
263
|
+
const { data: publicKey } = yield axios_1.default.get(`${authServer}public/public_key`);
|
|
264
|
+
const { data: algo } = yield axios_1.default.get(`${authServer}public/algo`);
|
|
265
|
+
const jwt = require('jsonwebtoken');
|
|
266
|
+
jwt.verify(accessToken, publicKey, { algorithms: [algo] }, (error, payload) => {
|
|
267
|
+
if (error) {
|
|
268
|
+
return resolve(false);
|
|
269
|
+
}
|
|
270
|
+
axios_1.default.get(`${authServer}public/revoked_ids`).then(({ data: revokedIds }) => {
|
|
271
|
+
if (revokedIds && revokedIds.includes(decodedToken['id'])) {
|
|
272
|
+
return resolve(false);
|
|
273
|
+
}
|
|
274
|
+
return resolve(true);
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
reject(error);
|
|
280
|
+
}
|
|
281
|
+
}));
|
|
282
|
+
});
|
|
283
|
+
}
|
|
174
284
|
}
|
|
175
285
|
exports.AuthManager = AuthManager;
|
|
176
286
|
AuthManager.instance = null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "supaapps-auth",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.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",
|
package/src/AuthManager.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
2
|
import { createHash, randomBytes } from 'crypto';
|
|
3
3
|
|
|
4
|
-
|
|
5
4
|
export class AuthManager {
|
|
6
5
|
private static instance: AuthManager | null = null;
|
|
7
6
|
private readonly authServer: string | null = null;
|
|
@@ -28,9 +27,9 @@ export class AuthManager {
|
|
|
28
27
|
|
|
29
28
|
private toBase64Url = (base64String: string) => {
|
|
30
29
|
return base64String
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
.replace(/\+/g, '-')
|
|
31
|
+
.replace(/\//g, '_')
|
|
32
|
+
.replace(/=+$/, '');
|
|
34
33
|
};
|
|
35
34
|
private generatePKCEPair = () => {
|
|
36
35
|
const NUM_OF_BYTES = 32; // This will generate a verifier of sufficient length
|
|
@@ -38,18 +37,86 @@ export class AuthManager {
|
|
|
38
37
|
|
|
39
38
|
// Generate code verifier
|
|
40
39
|
const newCodeVerifier = this.toBase64Url(
|
|
41
|
-
|
|
40
|
+
randomBytes(NUM_OF_BYTES).toString('base64'),
|
|
42
41
|
);
|
|
43
42
|
|
|
44
43
|
// Generate code challenge
|
|
45
44
|
const hash = createHash(HASH_ALG)
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
.update(newCodeVerifier)
|
|
46
|
+
.digest('base64');
|
|
48
47
|
const newCodeChallenge = this.toBase64Url(hash);
|
|
49
48
|
|
|
50
49
|
return { newCodeVerifier, newCodeChallenge };
|
|
51
50
|
};
|
|
52
51
|
|
|
52
|
+
private async refreshAccessToken(): Promise<string> {
|
|
53
|
+
return new Promise(async (resolve, reject) => {
|
|
54
|
+
try {
|
|
55
|
+
const refreshToken: string | null = localStorage.getItem('refresh_token');
|
|
56
|
+
if (!refreshToken) {
|
|
57
|
+
throw new Error('No refresh token found');
|
|
58
|
+
}
|
|
59
|
+
const decodedRefreshToken = JSON.parse(atob(refreshToken.split('.')[1]));
|
|
60
|
+
if (decodedRefreshToken) {
|
|
61
|
+
const currentTime = Date.now() / 1000;
|
|
62
|
+
if (decodedRefreshToken.exp < currentTime) {
|
|
63
|
+
throw new Error('Refresh token expired');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
await fetch(`${this.authServer}auth/refresh`, {
|
|
67
|
+
method: 'POST',
|
|
68
|
+
headers: {
|
|
69
|
+
'Content-Type': 'application/json',
|
|
70
|
+
},
|
|
71
|
+
body: JSON.stringify({
|
|
72
|
+
refresh_token: refreshToken,
|
|
73
|
+
}),
|
|
74
|
+
})
|
|
75
|
+
.then((response) => {
|
|
76
|
+
if (response.status !== 200) {
|
|
77
|
+
throw new Error('Failed to refresh the token');
|
|
78
|
+
}
|
|
79
|
+
return response.json();
|
|
80
|
+
})
|
|
81
|
+
.then((exchangeJson) => {
|
|
82
|
+
localStorage.setItem('refresh_token', exchangeJson.refresh_token);
|
|
83
|
+
localStorage.setItem('access_token', exchangeJson.access_token);
|
|
84
|
+
resolve(exchangeJson.access_token);
|
|
85
|
+
})
|
|
86
|
+
.catch((error) => {
|
|
87
|
+
reject(error);
|
|
88
|
+
});
|
|
89
|
+
} catch (error) {
|
|
90
|
+
reject(error);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
private async checkAccessToken(): Promise<string> {
|
|
95
|
+
return new Promise(async (resolve, reject) => {
|
|
96
|
+
try {
|
|
97
|
+
const accessToken: string | null = localStorage.getItem('access_token');
|
|
98
|
+
if (!accessToken) {
|
|
99
|
+
throw new Error('No access token found');
|
|
100
|
+
}
|
|
101
|
+
// decode access token and check if it's expired
|
|
102
|
+
const decodedToken = accessToken
|
|
103
|
+
? JSON.parse(atob(accessToken.split('.')[1]))
|
|
104
|
+
: null;
|
|
105
|
+
if (decodedToken) {
|
|
106
|
+
const currentTime = Date.now() / 1000;
|
|
107
|
+
if (decodedToken.exp < currentTime) {
|
|
108
|
+
// refreshing expired token
|
|
109
|
+
const newAccessToken = await this.refreshAccessToken();
|
|
110
|
+
return resolve(newAccessToken);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
resolve(accessToken);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
reject(error);
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
|
|
53
120
|
public async mustBeLoggedIn(): Promise<boolean> {
|
|
54
121
|
return new Promise((resolve, reject) => {
|
|
55
122
|
this.isLoggedIn().then((isLoggedIn) => {
|
|
@@ -72,28 +139,15 @@ export class AuthManager {
|
|
|
72
139
|
|
|
73
140
|
if (this.authServer && this.realmName && this.redirectUri) {
|
|
74
141
|
return `${this.authServer}auth/login_with_google?realm_name=${this.realmName}` +
|
|
75
|
-
|
|
142
|
+
`&redirect_uri=${encodeURIComponent(this.redirectUri)}&code_challenge=${codeChallenge}&code_challenge_method=S256`
|
|
76
143
|
}
|
|
77
144
|
}
|
|
145
|
+
|
|
78
146
|
public async isLoggedIn(): Promise<boolean> {
|
|
79
147
|
// todo here: check if refresh token is expired and if so, try to refresh, then update token
|
|
80
|
-
return new Promise((resolve, reject) => {
|
|
148
|
+
return new Promise(async (resolve, reject) => {
|
|
81
149
|
try {
|
|
82
|
-
|
|
83
|
-
if (!accessToken) {
|
|
84
|
-
return resolve(false);
|
|
85
|
-
}
|
|
86
|
-
// decode access token and check if it's expired
|
|
87
|
-
const decodedToken = accessToken ? JSON.parse(atob(accessToken.split('.')[1])) : null;
|
|
88
|
-
if (decodedToken) {
|
|
89
|
-
const currentTime = Date.now() / 1000;
|
|
90
|
-
if (decodedToken.exp < currentTime) {
|
|
91
|
-
// add refresh check here instead and
|
|
92
|
-
localStorage.removeItem('access_token');
|
|
93
|
-
return resolve(false);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
150
|
+
await this.checkAccessToken();
|
|
97
151
|
return resolve(true);
|
|
98
152
|
} catch (error) {
|
|
99
153
|
reject(error);
|
|
@@ -104,22 +158,9 @@ export class AuthManager {
|
|
|
104
158
|
public async getAccessToken(): Promise<string> {
|
|
105
159
|
// todo here: check if refresh token is expired and if so, try to refresh, then update token
|
|
106
160
|
// otherwise throw error
|
|
107
|
-
return new Promise((resolve, reject) => {
|
|
161
|
+
return new Promise(async (resolve, reject) => {
|
|
108
162
|
try {
|
|
109
|
-
const accessToken
|
|
110
|
-
if (!accessToken) {
|
|
111
|
-
throw new Error('No access token found');
|
|
112
|
-
}
|
|
113
|
-
// decode access token and check if it's expired
|
|
114
|
-
const decodedToken = accessToken ? JSON.parse(atob(accessToken.split('.')[1])) : null;
|
|
115
|
-
if (decodedToken) {
|
|
116
|
-
const currentTime = Date.now() / 1000;
|
|
117
|
-
if (decodedToken.exp < currentTime) {
|
|
118
|
-
// add refresh check here instead and
|
|
119
|
-
localStorage.removeItem('access_token');
|
|
120
|
-
throw new Error('Access token expired');
|
|
121
|
-
}
|
|
122
|
-
}
|
|
163
|
+
const accessToken = await this.checkAccessToken();
|
|
123
164
|
|
|
124
165
|
return resolve(accessToken);
|
|
125
166
|
} catch (error) {
|
|
@@ -145,24 +186,24 @@ export class AuthManager {
|
|
|
145
186
|
code_verifier: codeVerifier,
|
|
146
187
|
}),
|
|
147
188
|
})
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
189
|
+
.then((response) => {
|
|
190
|
+
localStorage.removeItem('codeVerifier');
|
|
191
|
+
localStorage.removeItem('codeChallenge');
|
|
192
|
+
if (response.status !== 200) {
|
|
193
|
+
throw new Error('Failed to exchange code for token');
|
|
194
|
+
}
|
|
195
|
+
return response.json();
|
|
196
|
+
})
|
|
197
|
+
.then((exchangeJson) => {
|
|
198
|
+
localStorage.setItem('access_token', exchangeJson.access_token);
|
|
199
|
+
localStorage.setItem('refresh_token', exchangeJson.refresh_token);
|
|
200
|
+
resolve();
|
|
201
|
+
})
|
|
202
|
+
.catch((error) => {
|
|
203
|
+
localStorage.removeItem('codeVerifier');
|
|
204
|
+
localStorage.removeItem('codeChallenge');
|
|
205
|
+
reject(error);
|
|
206
|
+
});
|
|
166
207
|
}
|
|
167
208
|
} catch (error) {
|
|
168
209
|
reject(error);
|
|
@@ -170,4 +211,64 @@ export class AuthManager {
|
|
|
170
211
|
});
|
|
171
212
|
}
|
|
172
213
|
|
|
214
|
+
public async logout(): Promise<void> {
|
|
215
|
+
return new Promise((resolve, reject) => {
|
|
216
|
+
try {
|
|
217
|
+
const bearerToken = localStorage.getItem('access_token');
|
|
218
|
+
localStorage.removeItem('access_token');
|
|
219
|
+
localStorage.removeItem('refresh_token');
|
|
220
|
+
fetch(`${this.authServer}auth/logout`, {
|
|
221
|
+
method: 'POST',
|
|
222
|
+
headers: {
|
|
223
|
+
'Content-Type': 'application/json',
|
|
224
|
+
'Authorization': `Bearer ${bearerToken}`,
|
|
225
|
+
},
|
|
226
|
+
}).then((response) => {
|
|
227
|
+
if (response.status !== 200) {
|
|
228
|
+
throw new Error('Failed to attempt logout')
|
|
229
|
+
}
|
|
230
|
+
resolve();
|
|
231
|
+
}).catch((error) => {
|
|
232
|
+
reject(error);
|
|
233
|
+
})
|
|
234
|
+
} catch (error) {
|
|
235
|
+
reject(error);
|
|
236
|
+
}
|
|
237
|
+
})
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
public static async validateToken(authServer: string, bearerToken: string): Promise<boolean> {
|
|
241
|
+
return new Promise<boolean>(async (resolve, reject) => {
|
|
242
|
+
try {
|
|
243
|
+
const accessToken = bearerToken.includes('Bearer ') ? bearerToken.replace('Bearer ', '') : bearerToken;
|
|
244
|
+
const decodedToken = accessToken ? JSON.parse(atob(accessToken.split('.')[1])) : null;
|
|
245
|
+
|
|
246
|
+
if (!decodedToken) {
|
|
247
|
+
return resolve(false);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const currentTime = Date.now() / 1000;
|
|
251
|
+
if (decodedToken.exp < currentTime) {
|
|
252
|
+
return resolve(false);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const { data: publicKey } = await axios.get(`${authServer}public/public_key`);
|
|
256
|
+
const { data: algo } = await axios.get(`${authServer}public/algo`);
|
|
257
|
+
const jwt = require('jsonwebtoken');
|
|
258
|
+
jwt.verify(accessToken, publicKey, { algorithms: [algo] }, (error, payload) => {
|
|
259
|
+
if (error) {
|
|
260
|
+
return resolve(false);
|
|
261
|
+
}
|
|
262
|
+
axios.get(`${authServer}public/revoked_ids`).then(({ data: revokedIds }) => {
|
|
263
|
+
if (revokedIds && (revokedIds as number[]).includes(decodedToken['id'])) {
|
|
264
|
+
return resolve(false);
|
|
265
|
+
}
|
|
266
|
+
return resolve(true);
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
} catch (error) {
|
|
270
|
+
reject(error);
|
|
271
|
+
}
|
|
272
|
+
})
|
|
273
|
+
}
|
|
173
274
|
}
|