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 +74 -0
- package/dist/AuthManager.d.ts +2 -1
- package/dist/AuthManager.js +44 -3
- package/package.json +3 -2
- package/src/AuthManager.ts +70 -29
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
|
@@ -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<
|
|
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
|
}
|
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) {
|
|
@@ -49,9 +50,15 @@ class AuthManager {
|
|
|
49
50
|
}
|
|
50
51
|
mustBeLoggedIn() {
|
|
51
52
|
return __awaiter(this, void 0, void 0, function* () {
|
|
52
|
-
|
|
53
|
-
this.
|
|
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
|
|
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,23 +38,30 @@ 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 };
|
|
51
51
|
};
|
|
52
52
|
|
|
53
|
-
public async mustBeLoggedIn(): Promise<
|
|
54
|
-
|
|
55
|
-
this.
|
|
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
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
}
|