@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 CHANGED
@@ -4,7 +4,8 @@ type OpenIDConfiguration = {
4
4
  clientId: string;
5
5
  clientSecret: string;
6
6
  redirectPath: string;
7
- secretKey: string;
7
+ signingSecret: string;
8
+ ttl?: number;
8
9
  allowInsecureRequests: boolean;
9
10
  };
10
11
  export default class OpenID implements Driver {
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 { LoginFailed, RefreshFailed, NotConnected, generateId } from '@theshelf/authentication';
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
- #providerConfiguration;
10
- #key;
8
+ #configuration;
9
+ #secretManager;
11
10
  #clientConfiguration;
12
- #secretManager = new SecretManager();
13
11
  constructor(configuration) {
14
- this.#providerConfiguration = configuration;
15
- this.#key = configuration.secretKey;
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.#providerConfiguration.issuer);
23
- const clientId = this.#providerConfiguration.clientId;
24
- const clientSecret = this.#providerConfiguration.clientSecret;
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.#providerConfiguration.redirectPath, origin).href;
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.#providerConfiguration.redirectPath, origin);
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
- return tokenRevocation(config, session.refreshToken ?? session.accessToken);
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.#providerConfiguration.allowInsecureRequests) {
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(payload) {
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.#key).update(data).digest(URL_ENCODING);
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.#key).update(decodedValue).digest());
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
  }
@@ -4,6 +4,7 @@ type Secret = {
4
4
  };
5
5
  export default class SecretManager {
6
6
  #private;
7
+ constructor(ttl?: number);
7
8
  set(key: string, secret: Secret): void;
8
9
  get(key: string): Secret | undefined;
9
10
  start(): void;
@@ -1,12 +1,15 @@
1
- const TTL = 40_000;
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
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@theshelf/authentication-driver-openid",
3
3
  "private": false,
4
- "version": "0.4.0",
4
+ "version": "0.4.1",
5
5
  "type": "module",
6
6
  "repository": {
7
7
  "url": "git+https://github.com/MaskingTechnology/theshelf.git"