@theshelf/authentication-driver-openid 0.4.0 → 0.4.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.
- package/dist/OpenID.d.ts +2 -1
- package/dist/OpenID.js +22 -30
- package/dist/SecretManager.d.ts +1 -0
- package/dist/SecretManager.js +6 -3
- package/package.json +1 -1
package/dist/OpenID.d.ts
CHANGED
package/dist/OpenID.js
CHANGED
|
@@ -1,27 +1,25 @@
|
|
|
1
|
-
import { allowInsecureRequests, authorizationCodeGrant, buildAuthorizationUrlWithPAR, calculatePKCECodeChallenge, discovery, fetchUserInfo, randomNonce, randomPKCECodeVerifier, refreshTokenGrant, tokenRevocation } from 'openid-client';
|
|
2
1
|
import crypto from 'node:crypto';
|
|
3
|
-
import {
|
|
2
|
+
import { allowInsecureRequests, authorizationCodeGrant, buildAuthorizationUrlWithPAR, calculatePKCECodeChallenge, discovery, fetchUserInfo, randomNonce, randomPKCECodeVerifier, refreshTokenGrant, tokenRevocation } from 'openid-client';
|
|
3
|
+
import { LoginFailed, NotConnected, RefreshFailed, generateId } from '@theshelf/authentication';
|
|
4
4
|
import SecretManager from './SecretManager.js';
|
|
5
|
-
const TTL = 30_000;
|
|
6
5
|
const HMAC_ALGORITHM = 'sha512';
|
|
7
6
|
const URL_ENCODING = 'base64url';
|
|
8
7
|
export default class OpenID {
|
|
9
|
-
#
|
|
10
|
-
#
|
|
8
|
+
#configuration;
|
|
9
|
+
#secretManager;
|
|
11
10
|
#clientConfiguration;
|
|
12
|
-
#secretManager = new SecretManager();
|
|
13
11
|
constructor(configuration) {
|
|
14
|
-
this.#
|
|
15
|
-
this.#
|
|
12
|
+
this.#configuration = configuration;
|
|
13
|
+
this.#secretManager = new SecretManager(configuration.ttl);
|
|
16
14
|
}
|
|
17
15
|
get name() { return OpenID.name; }
|
|
18
16
|
get connected() {
|
|
19
17
|
return this.#clientConfiguration !== undefined;
|
|
20
18
|
}
|
|
21
19
|
async connect() {
|
|
22
|
-
const issuer = new URL(this.#
|
|
23
|
-
const clientId = this.#
|
|
24
|
-
const clientSecret = this.#
|
|
20
|
+
const issuer = new URL(this.#configuration.issuer);
|
|
21
|
+
const clientId = this.#configuration.clientId;
|
|
22
|
+
const clientSecret = this.#configuration.clientSecret;
|
|
25
23
|
const requestOptions = this.#getRequestOptions();
|
|
26
24
|
this.#clientConfiguration = await discovery(issuer, clientId, clientSecret, undefined, requestOptions);
|
|
27
25
|
this.#secretManager.start();
|
|
@@ -31,7 +29,7 @@ export default class OpenID {
|
|
|
31
29
|
this.#clientConfiguration = undefined;
|
|
32
30
|
}
|
|
33
31
|
async getLoginUrl(origin) {
|
|
34
|
-
const redirect_uri = new URL(this.#
|
|
32
|
+
const redirect_uri = new URL(this.#configuration.redirectPath, origin).href;
|
|
35
33
|
const scope = 'openid profile email';
|
|
36
34
|
const nonce = randomNonce();
|
|
37
35
|
const codeVerifier = randomPKCECodeVerifier();
|
|
@@ -55,7 +53,7 @@ export default class OpenID {
|
|
|
55
53
|
}
|
|
56
54
|
async login(origin, data) {
|
|
57
55
|
const clientConfiguration = this.#getClientConfiguration();
|
|
58
|
-
const url = new URL(this.#
|
|
56
|
+
const url = new URL(this.#configuration.redirectPath, origin);
|
|
59
57
|
for (const [key, value] of Object.entries(data)) {
|
|
60
58
|
url.searchParams.set(key, String(value));
|
|
61
59
|
}
|
|
@@ -107,9 +105,13 @@ export default class OpenID {
|
|
|
107
105
|
expires: new Date(expires)
|
|
108
106
|
};
|
|
109
107
|
}
|
|
110
|
-
logout(session) {
|
|
108
|
+
async logout(session) {
|
|
111
109
|
const config = this.#getClientConfiguration();
|
|
112
|
-
|
|
110
|
+
const revocations = [tokenRevocation(config, session.accessToken)];
|
|
111
|
+
if (session.refreshToken !== undefined) {
|
|
112
|
+
revocations.push(tokenRevocation(config, session.refreshToken));
|
|
113
|
+
}
|
|
114
|
+
await Promise.allSettled(revocations);
|
|
113
115
|
}
|
|
114
116
|
#getClientConfiguration() {
|
|
115
117
|
if (this.#clientConfiguration === undefined) {
|
|
@@ -119,7 +121,7 @@ export default class OpenID {
|
|
|
119
121
|
}
|
|
120
122
|
#getRequestOptions() {
|
|
121
123
|
const options = {};
|
|
122
|
-
if (this.#
|
|
124
|
+
if (this.#configuration.allowInsecureRequests) {
|
|
123
125
|
options.execute = [allowInsecureRequests];
|
|
124
126
|
}
|
|
125
127
|
return options;
|
|
@@ -132,16 +134,11 @@ export default class OpenID {
|
|
|
132
134
|
return claims;
|
|
133
135
|
}
|
|
134
136
|
#createPayload() {
|
|
135
|
-
return
|
|
136
|
-
jti: crypto.randomBytes(32).toString('base64'),
|
|
137
|
-
iat: Date.now(),
|
|
138
|
-
exp: Date.now() + TTL
|
|
139
|
-
};
|
|
137
|
+
return crypto.randomBytes(64).toString('base64');
|
|
140
138
|
}
|
|
141
|
-
#calculateState(
|
|
142
|
-
const data = JSON.stringify(payload);
|
|
139
|
+
#calculateState(data) {
|
|
143
140
|
const value = Buffer.from(data).toString(URL_ENCODING);
|
|
144
|
-
const signature = crypto.createHmac(HMAC_ALGORITHM, this.#
|
|
141
|
+
const signature = crypto.createHmac(HMAC_ALGORITHM, this.#configuration.signingSecret).update(data).digest(URL_ENCODING);
|
|
145
142
|
return `${value}.${signature}`;
|
|
146
143
|
}
|
|
147
144
|
#getState(data) {
|
|
@@ -156,15 +153,10 @@ export default class OpenID {
|
|
|
156
153
|
const [value, signature] = parts;
|
|
157
154
|
const decodedValue = Buffer.from(value, URL_ENCODING).toString('utf8');
|
|
158
155
|
const decodedSignature = Buffer.from(signature, URL_ENCODING);
|
|
159
|
-
const check = Buffer.from(crypto.createHmac(HMAC_ALGORITHM, this.#
|
|
156
|
+
const check = Buffer.from(crypto.createHmac(HMAC_ALGORITHM, this.#configuration.signingSecret).update(decodedValue).digest());
|
|
160
157
|
if (check.length !== decodedSignature.length || crypto.timingSafeEqual(check, decodedSignature) === false) {
|
|
161
158
|
throw new LoginFailed('Invalid state');
|
|
162
159
|
}
|
|
163
|
-
const payload = JSON.parse(decodedValue);
|
|
164
|
-
const now = Date.now();
|
|
165
|
-
if (payload.iat > now || payload.exp < now) {
|
|
166
|
-
throw new LoginFailed('Invalid state');
|
|
167
|
-
}
|
|
168
160
|
return state;
|
|
169
161
|
}
|
|
170
162
|
}
|
package/dist/SecretManager.d.ts
CHANGED
package/dist/SecretManager.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
const
|
|
2
|
-
const CLEANUP_INTERVAL = 10_000;
|
|
1
|
+
const CLEANUP_INTERVAL = 60_000;
|
|
3
2
|
export default class SecretManager {
|
|
4
3
|
#cache = new Map();
|
|
5
4
|
#cleanupInterval;
|
|
5
|
+
#TTL;
|
|
6
|
+
constructor(ttl = 3600_000) {
|
|
7
|
+
this.#TTL = ttl;
|
|
8
|
+
}
|
|
6
9
|
set(key, secret) {
|
|
7
10
|
const entry = {
|
|
8
11
|
secret,
|
|
9
|
-
expiresAt: Date.now() + TTL
|
|
12
|
+
expiresAt: Date.now() + this.#TTL
|
|
10
13
|
};
|
|
11
14
|
this.#cache.set(key, entry);
|
|
12
15
|
}
|