@hahnpro/hpc-api 3.4.2 → 3.4.3
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/http.service.d.ts +4 -1
- package/dist/http.service.js +62 -13
- package/dist/token-set.d.ts +8 -0
- package/dist/token-set.js +19 -0
- package/package.json +3 -4
package/dist/http.service.d.ts
CHANGED
|
@@ -7,8 +7,8 @@ export declare class HttpClient {
|
|
|
7
7
|
private readonly clientId;
|
|
8
8
|
private readonly clientSecret;
|
|
9
9
|
private readonly axiosInstance;
|
|
10
|
+
private readonly authAxiosInstance;
|
|
10
11
|
private readonly requestQueue;
|
|
11
|
-
private client;
|
|
12
12
|
private tokenSet;
|
|
13
13
|
eventSourcesMap: Map<string, {
|
|
14
14
|
eventSource: EventSource;
|
|
@@ -31,4 +31,7 @@ export declare class HttpClient {
|
|
|
31
31
|
destroyEventSource(id: string): void;
|
|
32
32
|
destroyAllEventSources(): void;
|
|
33
33
|
getAccessToken: () => Promise<string>;
|
|
34
|
+
private validateIssuer;
|
|
35
|
+
private discoverIssuer;
|
|
36
|
+
private requestAccessToken;
|
|
34
37
|
}
|
package/dist/http.service.js
CHANGED
|
@@ -4,9 +4,11 @@ exports.HttpClient = void 0;
|
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const axios_1 = tslib_1.__importDefault(require("axios"));
|
|
6
6
|
const eventsource_1 = tslib_1.__importDefault(require("eventsource"));
|
|
7
|
-
const openid_client_1 = require("openid-client");
|
|
8
|
-
const Queue_1 = require("./Queue");
|
|
9
7
|
const crypto_1 = require("crypto");
|
|
8
|
+
const jose_1 = require("jose");
|
|
9
|
+
const Queue_1 = require("./Queue");
|
|
10
|
+
const token_set_1 = require("./token-set");
|
|
11
|
+
const querystring_1 = require("querystring");
|
|
10
12
|
const TOKEN_EXPIRATION_BUFFER = 30;
|
|
11
13
|
class HttpClient {
|
|
12
14
|
constructor(baseURL, authBaseURL, realm, clientId, clientSecret) {
|
|
@@ -33,21 +35,13 @@ class HttpClient {
|
|
|
33
35
|
}));
|
|
34
36
|
};
|
|
35
37
|
this.getAccessToken = async () => {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const authIssuer = await openid_client_1.Issuer.discover(`${this.authBaseURL}/realms/${this.realm}/`);
|
|
39
|
-
this.client = await new authIssuer.Client({
|
|
40
|
-
client_id: this.clientId,
|
|
41
|
-
client_secret: this.clientSecret,
|
|
42
|
-
token_endpoint_auth_method: 'client_secret_jwt',
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
if (!this.tokenSet || this.tokenSet.expired() || this.tokenSet.expires_at < Date.now() / 1000 + TOKEN_EXPIRATION_BUFFER) {
|
|
46
|
-
this.tokenSet = await this.client.grant({ grant_type: 'client_credentials' });
|
|
38
|
+
if (!this.tokenSet || this.tokenSet.expired() || this.tokenSet.expiresAt < Date.now() / 1000 + TOKEN_EXPIRATION_BUFFER) {
|
|
39
|
+
return this.requestAccessToken();
|
|
47
40
|
}
|
|
48
41
|
return this.tokenSet.access_token;
|
|
49
42
|
};
|
|
50
43
|
this.axiosInstance = axios_1.default.create({ baseURL, timeout: 60000 });
|
|
44
|
+
this.authAxiosInstance = axios_1.default.create({ baseURL: authBaseURL || baseURL, timeout: 10000 });
|
|
51
45
|
this.requestQueue = new Queue_1.Queue({ concurrency: 1, timeout: 70000, throwOnTimeout: true });
|
|
52
46
|
}
|
|
53
47
|
async addEventSource(url, listener, errorListener) {
|
|
@@ -80,5 +74,60 @@ class HttpClient {
|
|
|
80
74
|
this.destroyEventSource(key);
|
|
81
75
|
}
|
|
82
76
|
}
|
|
77
|
+
validateIssuer(issuer) {
|
|
78
|
+
var _a, _b;
|
|
79
|
+
if (!issuer.issuer ||
|
|
80
|
+
!((_a = issuer.grant_types_supported) === null || _a === void 0 ? void 0 : _a.includes('client_credentials')) ||
|
|
81
|
+
!((_b = issuer.token_endpoint_auth_methods_supported) === null || _b === void 0 ? void 0 : _b.includes('client_secret_jwt'))) {
|
|
82
|
+
throw new Error('Issuer does not support client_secret_jwt');
|
|
83
|
+
}
|
|
84
|
+
return issuer;
|
|
85
|
+
}
|
|
86
|
+
async discoverIssuer(uri) {
|
|
87
|
+
const wellKnownUri = `${uri}/.well-known/openid-configuration`;
|
|
88
|
+
const issuerResponse = await this.authAxiosInstance.get(wellKnownUri, {
|
|
89
|
+
responseType: 'json',
|
|
90
|
+
headers: { Accept: 'application/json' },
|
|
91
|
+
});
|
|
92
|
+
return this.validateIssuer(issuerResponse.data);
|
|
93
|
+
}
|
|
94
|
+
async requestAccessToken() {
|
|
95
|
+
var _a, _b;
|
|
96
|
+
const issuer = await this.discoverIssuer(`${this.authBaseURL}/realms/${this.realm}`);
|
|
97
|
+
const timestamp = Date.now() / 1000;
|
|
98
|
+
const audience = [...new Set([issuer.issuer, issuer.token_endpoint].filter(Boolean))];
|
|
99
|
+
const assertionPayload = {
|
|
100
|
+
iat: timestamp,
|
|
101
|
+
exp: timestamp + 60,
|
|
102
|
+
jti: (0, crypto_1.randomUUID)(),
|
|
103
|
+
iss: this.clientId,
|
|
104
|
+
sub: this.clientId,
|
|
105
|
+
aud: audience,
|
|
106
|
+
};
|
|
107
|
+
const supportedAlgos = issuer.token_endpoint_auth_signing_alg_values_supported;
|
|
108
|
+
const alg = (_a = issuer.token_endpoint_auth_signing_alg) !== null && _a !== void 0 ? _a : (Array.isArray(supportedAlgos) && supportedAlgos.find((signAlg) => /^HS(?:256|384|512)/.test(signAlg)));
|
|
109
|
+
if (!alg) {
|
|
110
|
+
throw new Error('Issuer has to support HS256, HS384 or HS512');
|
|
111
|
+
}
|
|
112
|
+
const assertion = await new jose_1.CompactSign(Buffer.from(JSON.stringify(assertionPayload)))
|
|
113
|
+
.setProtectedHeader({ alg })
|
|
114
|
+
.sign(new TextEncoder().encode(this.clientSecret));
|
|
115
|
+
const opts = {
|
|
116
|
+
client_id: this.clientId,
|
|
117
|
+
client_assertion: assertion,
|
|
118
|
+
client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
|
|
119
|
+
grant_type: 'client_credentials',
|
|
120
|
+
};
|
|
121
|
+
const authResponse = await this.authAxiosInstance.post(issuer.token_endpoint, (0, querystring_1.stringify)(opts), {
|
|
122
|
+
headers: { Accept: 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
123
|
+
});
|
|
124
|
+
if (((_b = authResponse === null || authResponse === void 0 ? void 0 : authResponse.data) === null || _b === void 0 ? void 0 : _b.access_token) && authResponse.data.expires_in) {
|
|
125
|
+
this.tokenSet = new token_set_1.TokenSet(authResponse.data.access_token, authResponse.data.expires_in);
|
|
126
|
+
return authResponse.data.access_token;
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
throw new Error('Invalid access token received');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
83
132
|
}
|
|
84
133
|
exports.HttpClient = HttpClient;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TokenSet = void 0;
|
|
4
|
+
class TokenSet {
|
|
5
|
+
constructor(access_token, expires_in) {
|
|
6
|
+
this.expires_in = expires_in;
|
|
7
|
+
this.access_token = access_token;
|
|
8
|
+
}
|
|
9
|
+
set expires_in(value) {
|
|
10
|
+
this.expiresAt = Date.now() + Number(value);
|
|
11
|
+
}
|
|
12
|
+
get expires_in() {
|
|
13
|
+
return Math.max(this.expiresAt - Date.now(), 0);
|
|
14
|
+
}
|
|
15
|
+
expired() {
|
|
16
|
+
return this.expires_in === 0;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
exports.TokenSet = TokenSet;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hahnpro/hpc-api",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.3",
|
|
4
4
|
"description": "Module for easy access to the HahnPRO API",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -27,15 +27,14 @@
|
|
|
27
27
|
"axios": "~0.27.2",
|
|
28
28
|
"eventsource": "^2.0.2",
|
|
29
29
|
"form-data": "^4.0.0",
|
|
30
|
+
"jose": "^4.8.3",
|
|
30
31
|
"jwt-decode": "^3.1.2",
|
|
31
|
-
"openid-client": "^5.1.8",
|
|
32
32
|
"p-queue": "^6.6.2",
|
|
33
33
|
"ts-mixer": "^6.0.1"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@types/eventsource": "^1.1.9",
|
|
37
|
-
"axios-mock-adapter": "^1.21.2"
|
|
38
|
-
"nock": "^13.2.9"
|
|
37
|
+
"axios-mock-adapter": "^1.21.2"
|
|
39
38
|
},
|
|
40
39
|
"engines": {
|
|
41
40
|
"node": ">=v14.13"
|