supaapps-auth 1.0.6 → 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 +74 -0
- package/dist/AuthManager.d.ts +1 -0
- package/dist/AuthManager.js +35 -0
- package/package.json +3 -2
- package/src/AuthManager.ts +59 -25
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
|
+
```
|
package/dist/AuthManager.d.ts
CHANGED
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) {
|
|
@@ -171,6 +172,40 @@ class AuthManager {
|
|
|
171
172
|
});
|
|
172
173
|
});
|
|
173
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
|
+
}
|
|
174
209
|
}
|
|
175
210
|
exports.AuthManager = AuthManager;
|
|
176
211
|
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.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",
|
package/src/AuthManager.ts
CHANGED
|
@@ -28,9 +28,9 @@ export class AuthManager {
|
|
|
28
28
|
|
|
29
29
|
private toBase64Url = (base64String: string) => {
|
|
30
30
|
return base64String
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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,13 +38,13 @@ export class AuthManager {
|
|
|
38
38
|
|
|
39
39
|
// Generate code verifier
|
|
40
40
|
const newCodeVerifier = this.toBase64Url(
|
|
41
|
-
|
|
41
|
+
randomBytes(NUM_OF_BYTES).toString('base64'),
|
|
42
42
|
);
|
|
43
43
|
|
|
44
44
|
// Generate code challenge
|
|
45
45
|
const hash = createHash(HASH_ALG)
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
.update(newCodeVerifier)
|
|
47
|
+
.digest('base64');
|
|
48
48
|
const newCodeChallenge = this.toBase64Url(hash);
|
|
49
49
|
|
|
50
50
|
return { newCodeVerifier, newCodeChallenge };
|
|
@@ -72,7 +72,7 @@ export class AuthManager {
|
|
|
72
72
|
|
|
73
73
|
if (this.authServer && this.realmName && this.redirectUri) {
|
|
74
74
|
return `${this.authServer}auth/login_with_google?realm_name=${this.realmName}` +
|
|
75
|
-
|
|
75
|
+
`&redirect_uri=${encodeURIComponent(this.redirectUri)}&code_challenge=${codeChallenge}&code_challenge_method=S256`
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
public async isLoggedIn(): Promise<boolean> {
|
|
@@ -145,24 +145,24 @@ export class AuthManager {
|
|
|
145
145
|
code_verifier: codeVerifier,
|
|
146
146
|
}),
|
|
147
147
|
})
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
+
});
|
|
166
166
|
}
|
|
167
167
|
} catch (error) {
|
|
168
168
|
reject(error);
|
|
@@ -170,4 +170,38 @@ export class AuthManager {
|
|
|
170
170
|
});
|
|
171
171
|
}
|
|
172
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
|
+
}
|
|
173
207
|
}
|