@nsshunt/stsappframework 3.1.216 → 3.1.218

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.
@@ -1,283 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
25
- var __importDefault = (this && this.__importDefault) || function (mod) {
26
- return (mod && mod.__esModule) ? mod : { "default": mod };
27
- };
28
- Object.defineProperty(exports, "__esModule", { value: true });
29
- exports.TestHelper = void 0;
30
- /* eslint @typescript-eslint/no-explicit-any: 0 */ // --> OFF
31
- const chalk_1 = __importDefault(require("chalk"));
32
- const tough = __importStar(require("tough-cookie"));
33
- const https_1 = __importDefault(require("https"));
34
- const crypto_1 = __importDefault(require("crypto"));
35
- const axios_1 = __importDefault(require("axios"));
36
- const testcontainers_1 = require("testcontainers");
37
- const stsconfig_1 = require("@nsshunt/stsconfig");
38
- const stsutils_1 = require("@nsshunt/stsutils");
39
- const authutilsnode_1 = require("./authutilsnode");
40
- class TestHelper {
41
- //#regexBase64URL = /^[A-Za-z0-9_-]+$/ // Base64URL - https://base64.guru/standards/base64url
42
- #regexURLSafeStringComponent = /[-a-zA-Z0-9@:%._+~#=]{1,256}/; // URL safe string component
43
- //#regexBase64 = /(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?/ // Base64 - https://stackoverflow.com/questions/475074/regex-to-parse-or-validate-base64-data
44
- #regexSTSBase64 = /SES_(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?/; // Base64
45
- #regexJWT = /[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+/; // JWT (Base64URL.Base64URL.Base64URL)
46
- #authUtilsNode = new authutilsnode_1.AuthUtilsNode(stsutils_1.defaultLogger);
47
- #databaseContainer;
48
- #stsAuthContainer;
49
- #network;
50
- #authEndpoint = '';
51
- #authPort = '';
52
- #authHost = '';
53
- #httpsAgent = null;
54
- constructor() {
55
- this.#authEndpoint = 'https://localhost:3002'; //@@
56
- }
57
- LogDebugMessage(message) {
58
- stsutils_1.defaultLogger.debug(message);
59
- }
60
- #GetHttpsAgent = () => {
61
- if (this.#httpsAgent === null) {
62
- // https://nodejs.org/api/http.html#class-httpagent
63
- this.#httpsAgent = new https_1.default.Agent({
64
- keepAlive: stsconfig_1.goptions.keepAlive,
65
- maxSockets: stsconfig_1.goptions.maxSockets,
66
- maxTotalSockets: stsconfig_1.goptions.maxTotalSockets,
67
- maxFreeSockets: stsconfig_1.goptions.maxFreeSockets,
68
- timeout: stsconfig_1.goptions.timeout,
69
- rejectUnauthorized: false
70
- });
71
- }
72
- return this.#httpsAgent;
73
- };
74
- StartNetwork = async () => {
75
- this.#network = await new testcontainers_1.Network().start();
76
- };
77
- StopNetwork = async () => {
78
- await this.#network.stop();
79
- };
80
- get network() {
81
- return this.#network;
82
- }
83
- get authPort() {
84
- return this.#authPort;
85
- }
86
- get authHost() {
87
- return this.#authHost;
88
- }
89
- get authEndpoint() {
90
- return this.#authEndpoint;
91
- }
92
- get getHttpsAgent() {
93
- return this.#GetHttpsAgent();
94
- }
95
- CreateRandomString = () => {
96
- const charset = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_~.'; // /[0-9A-Za-z\-_~.]/
97
- let random = '';
98
- const randomValues = Array.from(crypto_1.default.getRandomValues(new Uint8Array(43)));
99
- randomValues.forEach(v => (random += charset[v % charset.length]));
100
- return random;
101
- };
102
- Login = async (username, password) => {
103
- const client_id = process.env.CLIENT_ID;
104
- const nonce = crypto_1.default.randomBytes(43).toString('base64'); //CreateRandomString();
105
- const response_type = 'code';
106
- const redirect_uri = process.env.REDIRECT_URI;
107
- const response_mode = 'query';
108
- const scope = process.env.SCOPE;
109
- const state = crypto_1.default.randomBytes(43).toString('base64'); // CreateRandomString();
110
- const code_verifier = this.CreateRandomString();
111
- const code_challenge = crypto_1.default.createHash('sha256').update(code_verifier).digest('base64');
112
- const code_challenge_method = 'S256';
113
- const authoriseOptions = {
114
- email: username,
115
- password,
116
- client_id,
117
- nonce,
118
- response_type,
119
- redirect_uri,
120
- response_mode,
121
- scope,
122
- state,
123
- code_challenge,
124
- code_challenge_method
125
- };
126
- const url = `${this.#authEndpoint}${stsconfig_1.goptions.asapiroot}/login`;
127
- const headers = { 'Content-Type': 'application/json' };
128
- const retVal = await (0, axios_1.default)({
129
- url,
130
- method: 'post',
131
- data: authoriseOptions,
132
- headers: headers,
133
- httpsAgent: this.#GetHttpsAgent()
134
- });
135
- //const cookieString = retVal.headers['set-cookie'];
136
- /*
137
- const api = request(this.#endpoint);
138
- const retVal: any = await (api as any)
139
- .post(`${goptions.asapiroot}/login`)
140
- .send(authoriseOptions)
141
- //.expect('set-cookie', /consent_cookie=.*; Max-Age=86; Path=\/; Expires=.*; HttpOnly; Secure; SameSite=Strict/);
142
-
143
- const cookieString = retVal.header['set-cookie'];
144
-
145
- if (cookieString) {
146
- retVal.cookie = new Cookie(cookieString[0]);
147
- }
148
- */
149
- return retVal;
150
- };
151
- /*
152
- GetAuthServerAPITokenFromServer = async (): Promise<string> => {
153
- return await this.#authUtilsNode.GetAPITokenFromAuthServer(STSClientID.STSTestingService,
154
- "eN9u0mHZLGWZrdnE1zit2vL6xwUFW466sTZcbkXDml5KWxlvKaZ1uiOZmA==",
155
- goptions.asapiidentifier, this.#authEndpoint)
156
- }
157
- */
158
- ValidateJWT = async (token) => {
159
- return await this.#authUtilsNode.ValidateJWT(token, stsconfig_1.goptions.asapiidentifier, this.#authEndpoint);
160
- };
161
- StartDatabase = async () => {
162
- this.#databaseContainer = await new testcontainers_1.GenericContainer("postgres")
163
- .withExposedPorts(5432)
164
- .withEnvironment({
165
- POSTGRES_PASSWORD: "postgres",
166
- //UV_THREADPOOL_SIZE: "64"
167
- })
168
- .withNetwork(this.#network)
169
- .withNetworkAliases("database")
170
- .start();
171
- const httpPort = this.#databaseContainer.getMappedPort(5432);
172
- const host = this.#databaseContainer.getHost();
173
- const networkIpAddress = this.#databaseContainer.getIpAddress(this.#network.getName());
174
- process.env.DB_HOST = `${host}:${httpPort}`;
175
- (0, stsconfig_1.$ResetOptions)();
176
- this.LogDebugMessage(chalk_1.default.green(`httpPort: [${httpPort}]`));
177
- this.LogDebugMessage(chalk_1.default.green(`host: [${host}]`));
178
- this.LogDebugMessage(chalk_1.default.green(`networkIpAddress: [${networkIpAddress}]`));
179
- this.LogDebugMessage(chalk_1.default.green(`connectionString: [${stsconfig_1.goptions.connectionString}]`));
180
- this.LogDebugMessage(chalk_1.default.green(`defaultDatabaseConnectionString: [${stsconfig_1.goptions.defaultDatabaseConnectionString}]`));
181
- };
182
- StopDatabase = async () => {
183
- if (this.#databaseContainer) {
184
- await this.#databaseContainer.stop();
185
- this.LogDebugMessage(chalk_1.default.yellow(`Used the following parameters for the database during testing:`));
186
- this.LogDebugMessage(chalk_1.default.yellow(`connectionString: [${stsconfig_1.goptions.connectionString}]`));
187
- this.LogDebugMessage(chalk_1.default.yellow(`defaultDatabaseConnectionString: [${stsconfig_1.goptions.defaultDatabaseConnectionString}]`));
188
- }
189
- };
190
- // Note: .withCopyFilesToContainer and .withCopyContentToContainer have a defect in that Jest will not close. A file handle/stream is left open
191
- // within the underlying code.
192
- InitializeDatabase = async () => {
193
- const stsAuthContainerInit = await new testcontainers_1.GenericContainer("serza/stsauth:latest")
194
- .withEnvironment({
195
- DB_USER: "postgres",
196
- DB_PASSWORD: "postgres",
197
- DB_HOST: "database:5432", // "192.168.14.101",
198
- POOL_SIZE: "50",
199
- MAX_CPU: "2",
200
- DEBUG: "proc*",
201
- HTTPS_SERVER_KEY_PATH: "/var/lib/sts/stsglobalresources/keys-tmp/server.key",
202
- HTTPS_SERVER_CERT_PATH: "/var/lib/sts/stsglobalresources/keys-tmp/server.cert",
203
- AS_ENDPOINT: "https://stscore.stsmda.org"
204
- })
205
- .withCommand(["node", "dist/app", "create"])
206
- .withNetwork(this.#network)
207
- .withNetworkAliases("stsauthrunnerinit")
208
- .withWaitStrategy(testcontainers_1.Wait.forLogMessage(`User registered: {"status":200,"detail":{"id":"USR_STSGlobalAdminUser@stsmda.com","name":"STSGlobalAdminUser@stsmda.com","email":"STSGlobalAdminUser@stsmda.com","roles":[]}}`))
209
- .start();
210
- await (0, stsutils_1.Sleep)(500);
211
- await stsAuthContainerInit.stop();
212
- };
213
- StartAuthService = async () => {
214
- this.#stsAuthContainer = await new testcontainers_1.GenericContainer("serza/stsauth:latest")
215
- .withExposedPorts(3002)
216
- .withEnvironment({
217
- DB_USER: "postgres",
218
- DB_PASSWORD: "postgres",
219
- DB_HOST: "database:5432",
220
- POOL_SIZE: "50",
221
- MAX_CPU: "2",
222
- DEBUG: "proc*",
223
- HTTPS_SERVER_KEY_PATH: "/var/lib/sts/stsglobalresources/keys-tmp/server.key",
224
- HTTPS_SERVER_CERT_PATH: "/var/lib/sts/stsglobalresources/keys-tmp/server.cert",
225
- AS_ENDPOINT: "https://stscore.stsmda.org"
226
- })
227
- .withNetwork(this.#network)
228
- .withNetworkAliases("stsauthrunner")
229
- .withWaitStrategy(testcontainers_1.Wait.forHttp("/stsauth/v1.0/latency", 3002).usingTls().allowInsecure())
230
- .start();
231
- const httpAuthPort = this.#stsAuthContainer.getMappedPort(3002);
232
- await (0, stsutils_1.Sleep)(200);
233
- this.LogDebugMessage(chalk_1.default.green(`-------------------------------------------------------------------------------------------`));
234
- this.LogDebugMessage(chalk_1.default.green(` *** STSAuth Started ***: [${httpAuthPort}]`));
235
- this.LogDebugMessage(chalk_1.default.green(`-------------------------------------------------------------------------------------------`));
236
- this.#authHost = 'https://localhost';
237
- this.#authPort = httpAuthPort;
238
- this.#authEndpoint = `${this.#authHost}:${this.#authPort}`;
239
- };
240
- StopAuthService = async () => {
241
- if (this.#stsAuthContainer) {
242
- await this.#stsAuthContainer.stop();
243
- await (0, stsutils_1.Sleep)(200);
244
- }
245
- };
246
- TestLoginAndVerify = async () => {
247
- expect.assertions(4);
248
- const retVal = await this.Login('user01@stsmda.com.au', 'user01password');
249
- expect(retVal.status).toEqual(200);
250
- this.LogDebugMessage(chalk_1.default.red(`${JSON.stringify(retVal.data)}`));
251
- this.LogDebugMessage(chalk_1.default.magenta(`${JSON.stringify(retVal.headers)}`));
252
- this.LogDebugMessage(chalk_1.default.yellow(`${JSON.stringify(retVal.headers['set-cookie'])}`));
253
- const cookies = retVal.headers['set-cookie'];
254
- this.LogDebugMessage(chalk_1.default.yellow(`${cookies[0]}`));
255
- this.LogDebugMessage(chalk_1.default.green(`${JSON.stringify(tough.Cookie.parse(cookies[0]))}`));
256
- const cookie = tough.Cookie.parse(cookies[0]);
257
- const desiredCookieResultAxios = {
258
- key: 'consent_cookie',
259
- value: expect.stringMatching(this.#regexURLSafeStringComponent),
260
- path: '/',
261
- secure: true,
262
- httpOnly: true,
263
- sameSite: 'strict',
264
- };
265
- const cookieResult = JSON.parse(JSON.stringify(cookie));
266
- expect(cookieResult).toMatchObject(desiredCookieResultAxios);
267
- const cookieExpireDate = new Date(cookie.expires);
268
- expect(cookieExpireDate.getTime()).toBeGreaterThan(new Date().getTime());
269
- const desiredResult = {
270
- sessionId: expect.stringMatching(this.#regexSTSBase64),
271
- id_token: expect.stringMatching(this.#regexJWT),
272
- consentRequired: [
273
- 'api://d8277fce-bb48-44c2-bbf1-257fe13a444b/res01.create',
274
- 'api://d8277fce-bb48-44c2-bbf1-257fe13a444b/res01.read',
275
- 'api://d8277fce-bb48-44c2-bbf1-257fe13a444b/res01.update',
276
- 'api://d8277fce-bb48-44c2-bbf1-257fe13a444b/res01.delete'
277
- ]
278
- };
279
- expect(retVal.data.detail).toMatchObject(desiredResult);
280
- };
281
- }
282
- exports.TestHelper = TestHelper;
283
- //# sourceMappingURL=testHelpers.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"testHelpers.js","sourceRoot":"","sources":["../src/testHelpers.ts"],"names":[],"mappings":"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"}
@@ -1,360 +0,0 @@
1
- import tough from 'tough-cookie';
2
- import https from 'https'
3
- import jwt from 'jsonwebtoken';
4
- import { jwtDecode } from "jwt-decode";
5
- import jwksClient from 'jwks-rsa';
6
-
7
- import axios from 'axios';
8
-
9
- import { goptions } from '@nsshunt/stsconfig'
10
-
11
- import { GetErrorPayload, ISTSLogger, JSONObject } from '@nsshunt/stsutils'
12
-
13
- import { AppFrameworkErrorCode } from './validation/errors'
14
-
15
- import { StatusCodes } from 'http-status-codes';
16
-
17
- import chalk from 'chalk';
18
-
19
- import { Gauge, PublishInstrumentController } from '@nsshunt/stsobservability'
20
-
21
- import { iss } from './commonTypes'
22
-
23
- export interface IAuthUtilsNodeOptions {
24
- permissions: string[]
25
- }
26
-
27
- export interface IGetAPITokenFromAuthServerUsingScopeOptions {
28
- clientId: string
29
- authClientSecret: string
30
- scope: string
31
- endPoint: string
32
- instrumentController?: PublishInstrumentController
33
- outputErrorsToConsole?: boolean
34
- }
35
-
36
- declare interface ICacheScopeRecord {
37
- scope: string
38
- timeout: NodeJS.Timeout
39
- }
40
-
41
- declare interface ICacheRecord {
42
- scopes: Record<string, ICacheScopeRecord>
43
- }
44
-
45
- export class AuthUtilsNode
46
- {
47
- #cache: Record<string, ICacheRecord> = { };
48
- #cacheTimeout: number = 1000;
49
- #logger: ISTSLogger
50
- #cookiejar: tough.CookieJar;
51
- #httpsAgent: https.Agent | null = null;
52
- // Regular expression to match the origin
53
- #originRegex = /^(api:\/\/\w+)/;
54
-
55
- constructor(logger: ISTSLogger) {
56
- this.#logger = logger;
57
- this.#cookiejar = new tough.CookieJar();
58
- }
59
-
60
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
61
- #LogDebugMessage(message: any) {
62
- this.#logger.debug(message);
63
- }
64
-
65
- #GetHttpsAgent = () => {
66
- if (this.#httpsAgent === null) {
67
- // https://nodejs.org/api/http.html#class-httpagent
68
- this.#httpsAgent = new https.Agent({
69
- keepAlive: goptions.keepAlive,
70
- maxSockets: goptions.maxSockets,
71
- maxTotalSockets: goptions.maxTotalSockets,
72
- maxFreeSockets: goptions.maxFreeSockets,
73
- timeout: goptions.timeout,
74
- rejectUnauthorized: false
75
- });
76
- }
77
- return this.#httpsAgent;
78
- }
79
-
80
- ResetAgent = () => {
81
- this.#httpsAgent = null;
82
- }
83
-
84
- VerifyRequestMiddlewareFactory = (options: IAuthUtilsNodeOptions) => {
85
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
86
- return async (req: any, res: any, next: any) => {
87
- if (options.permissions) {
88
-
89
- const permissionsKey = options.permissions.join('_');
90
- const scopeKey = (req.auth.scope as string).split(' ').join('_');
91
-
92
- if (this.#cache[permissionsKey] && this.#cache[permissionsKey].scopes[scopeKey]) {
93
- next();
94
- return;
95
- }
96
-
97
- const scopes = req.auth.scope.split(' ');
98
-
99
- const requiredPermissions = [ ];
100
- for (let i=0; i < options.permissions.length; i++) {
101
- const permission = options.permissions[i];
102
- if (!scopes.includes(permission)) {
103
- requiredPermissions.push(permission);
104
- }
105
- }
106
- if (requiredPermissions.length > 0) {
107
- const errorPayload = GetErrorPayload(AppFrameworkErrorCode.APPFRAMEWORK_MISSING_PERMISSION, requiredPermissions);
108
- res.status(StatusCodes.UNAUTHORIZED).send( { status: StatusCodes.UNAUTHORIZED, error: errorPayload });
109
- return;
110
- }
111
-
112
- if (!this.#cache[permissionsKey]) {
113
- this.#cache[permissionsKey] = {
114
- scopes: { }
115
- }
116
- }
117
-
118
- this.#cache[permissionsKey].scopes[scopeKey] = {
119
- scope: scopeKey,
120
- timeout: setTimeout(() => {
121
- delete this.#cache[permissionsKey].scopes[scopeKey]
122
- }, this.#cacheTimeout).unref()
123
- }
124
- }
125
- next();
126
- }
127
- }
128
- /*
129
- let cookies = await this.GetCookiesFromJar();
130
- const valid = this.#ValidateCookies(cookies);
131
- if (valid) {
132
- next();
133
- } else {
134
- const error = { }; //@@
135
- const invalidToken = false; //@@
136
- if (invalidToken)
137
- {
138
- res.status(StatusCodes.UNAUTHORIZED).send( { status: StatusCodes.UNAUTHORIZED, error: 'Invalid Token', detail: error } );
139
- } else{
140
- res.status(StatusCodes.INTERNAL_SERVER_ERROR).send( { status: StatusCodes.INTERNAL_SERVER_ERROR, error: 'Operation was not successful', detail: error } );
141
- }
142
- }
143
- */
144
-
145
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
146
- async verifyRequestMiddleware(req: any, res: any, next: any)
147
- {
148
- next();
149
- /*
150
-
151
- let cookies = await this.GetCookiesFromJar();
152
-
153
- const valid = this.#ValidateCookies(cookies);
154
-
155
- if (valid) {
156
- next();
157
- } else {
158
- const error = { }; //@@
159
- const invalidToken = false; //@@
160
- if (invalidToken)
161
- {
162
- res.status(StatusCodes.UNAUTHORIZED).send( { status: StatusCodes.UNAUTHORIZED, error: 'Invalid Token', detail: error } );
163
- } else{
164
- res.status(StatusCodes.INTERNAL_SERVER_ERROR).send( { status: StatusCodes.INTERNAL_SERVER_ERROR, error: 'Operation was not successful', detail: error } );
165
- }
166
- }
167
- */
168
- }
169
-
170
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
171
- SetCookiesToJar = async (headers: Record<string, any>, endpoint: string): Promise<tough.Cookie[]> =>
172
- {
173
- if (headers['set-cookie']) {
174
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
175
- headers['set-cookie'].map((headerCookie: any) => {
176
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
177
- const cookie: any = tough.Cookie.parse(headerCookie);
178
- this.#cookiejar.setCookieSync(cookie, endpoint);
179
- });
180
- } else {
181
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
182
- const cookie: any = tough.Cookie.parse(headers['set-cookie']);
183
- this.#cookiejar.setCookieSync(cookie, endpoint);
184
- }
185
-
186
- return this.#cookiejar.getCookies(endpoint);
187
- };
188
-
189
- GetCookiesFromJar = async (endpoint: string): Promise<tough.Cookie[]> =>
190
- {
191
- return this.#cookiejar.getCookies(endpoint);
192
- };
193
-
194
- ValidateJWT = async (token: string, audience: string, endpoint?: string): Promise<string> => {
195
- const jwksClientUri = (endpoint
196
- ? `${endpoint}${goptions.asoauthapiroot}${goptions.asjwksjsonpath}`
197
- : `${goptions.asendpoint}:${goptions.asport}${goptions.asoauthapiroot}${goptions.asjwksjsonpath}`);
198
-
199
- const jwks = jwksClient({
200
- cache: true, //@@ all config items
201
- cacheMaxEntries: 5, // Default value
202
- cacheMaxAge: 600000, // Defaults to 10m
203
- rateLimit: true,
204
- jwksRequestsPerMinute: 10, // Default value
205
- jwksUri: jwksClientUri,
206
- timeout: 30000, //@@ config
207
- requestAgent: this.#GetHttpsAgent()
208
- });
209
-
210
- // Use decode to get the kid
211
- const decodedRefreshToken = jwtDecode<JSONObject>(token, { header: true });
212
- const kid = decodedRefreshToken.kid;
213
-
214
- const key = await jwks.getSigningKey(kid);
215
- const signingKey = key.getPublicKey();
216
-
217
- const verifyOptions = {
218
- issuer: iss,
219
- //subject: s,
220
- audience: audience,
221
- //expiresIn: 600, // 10 minutes
222
- algorithm: ["RS256"] // RSASSA [ "RS256", "RS384", "RS512" ]
223
- };
224
-
225
- return jwt.verify(token, signingKey, verifyOptions) as string;
226
- }
227
-
228
- // Function to extract the origin from a URI
229
- ExtractOrigin = (uri: string) => {
230
- const match = uri.match(this.#originRegex);
231
- return match ? match[1] : null;
232
- }
233
-
234
- GetAPITokenFromAuthServerUsingScope = async (options: IGetAPITokenFromAuthServerUsingScopeOptions, errorCb: (error: Error) => void): Promise<string> => {
235
- const { scope, clientId, authClientSecret, endPoint, instrumentController, outputErrorsToConsole } = options;
236
-
237
- let stage = '1';
238
-
239
- const invokeErrorCb = (error: Error) => {
240
- this.#LogDebugMessage(error);
241
- if (instrumentController) {
242
- instrumentController.UpdateInstrument(Gauge.AUTHENTICATION_ERROR_COUNT_GAUGE, { // auth error
243
- Inc: 1
244
- });
245
- }
246
- errorCb(error);
247
- };
248
-
249
- try {
250
- stage = '2';
251
- const scopes = scope.split(' ');
252
- let origin: string | null = null;
253
- let error: Error | null = null;
254
- stage = '3';
255
- for (let i=0; i < scopes.length; i++) {
256
- const s = scopes[i];
257
- if (!origin) {
258
- origin = this.ExtractOrigin(s);
259
- if (!origin) {
260
- error = new Error(`Scope: [${scope}] not in required format. Must use (space seperated) api://<client id>[/<resource>.<permission>].`);
261
- break;
262
- }
263
- } else {
264
- const nextOrigin: string | null = this.ExtractOrigin(s);
265
- if (!nextOrigin) {
266
- error = new Error(`Scope: [${scope}] not in required format. Must use (space seperated) api://<client id>[/<resource>.<permission>].`);
267
- break;
268
- } else {
269
- if (origin.localeCompare(nextOrigin) !== 0) {
270
- error = new Error(`Scope: [${scope}] not all from the same client API. All scopes must come from the same client API.`);
271
- break;
272
- }
273
- }
274
- }
275
- }
276
- stage = '4';
277
- if (error) {
278
- invokeErrorCb(error);
279
- return "";
280
- }
281
-
282
- stage = '5';
283
- const headers = { 'Content-Type': 'application/json'};
284
- const payload = { //@@ make a type
285
- client_id: clientId, // The service calling this method
286
- client_secret: authClientSecret, // Auth service client secret
287
- //client_secret: goptions.brokerclientsecret, // Broker service client secret
288
- scope: scope, // required API
289
- //@@ remove audience
290
- //@@ need scope to be the API identifier
291
- grant_type: "client_credentials"
292
- }
293
- stage = '6';
294
- const url = (endPoint
295
- ? `${endPoint}${goptions.asoauthapiroot}/token`
296
- : `${goptions.asendpoint}:${goptions.asport}${goptions.asoauthapiroot}/token`);
297
-
298
- stage = `6.5: url: [${url}] payload: [${JSON.stringify(payload)}]`
299
-
300
- const retVal = await axios({
301
- url
302
- ,method: 'post'
303
- ,data: payload
304
- ,headers: headers
305
- ,httpsAgent: this.#GetHttpsAgent()
306
- });
307
-
308
- stage = '7';
309
- if (retVal.status) {
310
- if (retVal.status !== 200) {
311
- // Just provide a warning here
312
- this.#LogDebugMessage(chalk.magenta(`Error (AuthUtilsNode:GetAPITokenFromServer): Invalid response from server: [${retVal.status}]`));
313
- }
314
- } else {
315
- invokeErrorCb(new Error(chalk.red(`Error (AuthUtilsNode:GetAPITokenFromServer:No retVal.status)`)));
316
- return "";
317
- }
318
- stage = '8';
319
- if (retVal.data) {
320
- stage = '9';
321
- if (retVal.data.access_token) {
322
- stage = '10';
323
- if (instrumentController) {
324
- stage = '11';
325
- instrumentController.UpdateInstrument(Gauge.AUTHENTICATION_COUNT_GAUGE, {
326
- Inc: 1
327
- });
328
- }
329
-
330
- stage = '12';
331
- return retVal.data.access_token as string;
332
- } else {
333
- stage = '13';
334
- invokeErrorCb(new Error(`Error (AuthUtilsNode:GetAPITokenFromServer:No retVal.data.access_token)`));
335
- return "";
336
- }
337
- } else {
338
- stage = '14';
339
- invokeErrorCb(new Error(`Error (AuthUtilsNode:GetAPITokenFromServer:No retVal.data)`));
340
- return "";
341
- }
342
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
343
- } catch (error: any) {
344
- if (outputErrorsToConsole === true) {
345
- console.error(error);
346
- }
347
- let details = 'None available.';
348
- if (error.response && error.response.data) {
349
- try {
350
- details = JSON.stringify(error.response.data);
351
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
352
- } catch (error) {
353
- details = `Could not JSON.stringify(error.response.data)`;
354
- }
355
- }
356
- invokeErrorCb(new Error(`Error (AuthUtilsNode:GetAPITokenFromServer:catch): [${error}], Stage: [${stage}], Details: [${details}]`));
357
- return "";
358
- }
359
- }
360
- }