@nsshunt/stsoauth2plugin 0.1.67 → 0.1.68

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,438 +0,0 @@
1
- import Debug from "debug";
2
- const debug = Debug(`proc:${process.pid}:stsoauth2manager.ts`);
3
-
4
- import { JSONObject, OAuth2ParameterType } from '@nsshunt/stsutils';
5
-
6
- import CryptoUtils from './Utils/CryptoUtils'
7
- import QueryParams from './Utils/QueryParams';
8
-
9
- import { IAuthorizeOptions, IAuthorizeResponse, IAuthorizeErrorResponse, AuthenticateEvent,
10
- ISTSOAuth2ManagerOptions, IOauth2ListenerMessage, IOauth2ListenerMessageResponse,
11
- IOauth2ListenerCommand, ISTSOAuth2WorkerMessage, StsOauth2WorkerFactory } from './stsoauth2types'
12
-
13
- import { IStsStorage, ClientStorageType, ClientStorageFactory } from './stsStorage'
14
-
15
- import { Gauge, InstrumentBaseTelemetry } from '@nsshunt/stsinstrumentation'
16
-
17
- import jwt_decode from "jwt-decode";
18
-
19
- import { createPinia, defineStore } from 'pinia'
20
- import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
21
- //import piniaPersist from 'pinia-plugin-persist'
22
-
23
- // STS Client SDK for SPAs
24
- export class STSOAuth2Manager {
25
- #storageManager = null;
26
- #router: any = null;
27
- #store = null;
28
- #cUtils = new CryptoUtils();
29
- #qParams = new QueryParams();
30
- #STORAGE_AUTHORIZE_OPTIONS_KEY = 'authorize_options.stsmda.com.au';
31
- #STORAGE_SESSION_KEY = 'session.stsmda.com.au';
32
- #aic = null;
33
- #options: ISTSOAuth2ManagerOptions = null;
34
- #messages: Record<number, IOauth2ListenerMessage> = { };
35
- #oauth2ManagerPort: MessagePort;
36
- #messageId = 0;
37
- #messageHandlers: Record<number, any> = { }; // keyed by messageId
38
- #messageTimeout = 1000;
39
- #worker: Worker = null;
40
- #transactionStore: IStsStorage<IAuthorizeOptions> = null; // Transient transaction data used to establish a session via OAuth2 authorize handshake
41
- #auth2Store = null;
42
-
43
- constructor(app, options: ISTSOAuth2ManagerOptions) {
44
- this.#options = options;
45
- this.#storageManager = app.config.globalProperties.$sts.storage;
46
- this.#store = app.config.globalProperties.$store;
47
- this.#aic = app.config.globalProperties.$sts.aic.PrimaryPublishInstrumentController;
48
- this.#router = app.config.globalProperties.$router;
49
-
50
- // Use session storage for the transient nature of the OAuth2 authorize handshake. Once completed, the storage will be removed.
51
- this.#transactionStore = new ClientStorageFactory<IAuthorizeOptions>({clientStorageType: ClientStorageType.SESSION_STORAGE}).GetStorage();
52
-
53
- this.#worker = this.#options.workerFactory();
54
-
55
- this.#worker.onmessage = (data: MessageEvent) => {
56
- console.log(`this.#worker.onmessage = [${data}]`); // green
57
- };
58
-
59
- this.#worker.onerror = function(error) {
60
- console.log(`this.#worker.onerror = [${JSON.stringify(error)}]`); // green
61
- };
62
-
63
- const {
64
- port1: oauth2ManagerPort, // process message port
65
- port2: oauth2WorkerPort // collector message port
66
- } = new MessageChannel();
67
- this.#oauth2ManagerPort = oauth2ManagerPort;
68
-
69
- const workerMessage: ISTSOAuth2WorkerMessage = {
70
- workerPort: oauth2WorkerPort,
71
- options: this.#options.workerOptions
72
- }
73
-
74
- this.#worker.postMessage(workerMessage, [ oauth2WorkerPort ]);
75
-
76
- this.#oauth2ManagerPort.onmessage = (data: MessageEvent) => {
77
- this.#ProcessMessageResponse(data);
78
- }
79
-
80
- this.#SetupStoreNamespace();
81
-
82
- this.#SetupPiniaStoreNamespace();
83
-
84
- this.#SetupRoute(app, this.#router);
85
- }
86
-
87
- #ProcessMessageResponse = (data: MessageEvent) => {
88
- const messageResponse: IOauth2ListenerMessageResponse = data.data as IOauth2ListenerMessageResponse;
89
- if (messageResponse.messageId === -1) {
90
- // unsolicted message
91
- switch (messageResponse.command) {
92
- case IOauth2ListenerCommand.AUTHENTICATE_EVENT :
93
- this.#HandleAuthenticateEvent(messageResponse.payload as string);
94
- break;
95
- case IOauth2ListenerCommand.ERROR :
96
- this.#HandleErrorEvent(messageResponse.payload as JSONObject);
97
- break;
98
- case IOauth2ListenerCommand.LOG :
99
- this.#HandleLogEvent(messageResponse.payload as string);
100
- break;
101
- case IOauth2ListenerCommand.UPDATE_INSTRUMENT :
102
- this.#HandleUpdateInstrumentEvent(messageResponse.payload.instrumentName, messageResponse.payload.telemetry);
103
- break;
104
- default :
105
- throw new Error(`ProcessMessageResponse command [${messageResponse.command}] not valid.`);
106
- }
107
- } else {
108
- const callBack = this.#messageHandlers[messageResponse.messageId];
109
- if (callBack) {
110
- callBack(messageResponse);
111
- } else {
112
- throw new Error(`Message: [${messageResponse.messageId}] does not exists in callBacks.`);
113
- }
114
- }
115
- }
116
-
117
- #PostMessage = (message: IOauth2ListenerMessage): Promise<IOauth2ListenerMessageResponse> => {
118
- message.messageId = this.#messageId++;
119
-
120
- return new Promise<IOauth2ListenerMessageResponse>((resolve, reject) => {
121
- // Setup message timeout
122
- const timeout: NodeJS.Timeout = setTimeout(() => {
123
- delete this.#messageHandlers[message.messageId];
124
- reject(`Message: [${message.messageId}] timeout error after: [${this.#messageTimeout}] ms.`);
125
- }, this.#messageTimeout);
126
-
127
- // Setup message callback based on messageId
128
- this.#messageHandlers[message.messageId] = (response: IOauth2ListenerMessageResponse) => {
129
- clearTimeout(timeout);
130
- delete this.#messageHandlers[message.messageId];
131
- resolve(response);
132
- }
133
-
134
- // Send the message
135
- this.#oauth2ManagerPort.postMessage(message);
136
- });
137
- }
138
-
139
- #HandleLogEvent = (message: string): void => {
140
- if (this.#aic) {
141
- this.#aic.LogEx(message);
142
- }
143
- debug(message);
144
- }
145
-
146
- // UpdateInstrument = (instrumentName: Gauge, telemetry: InstrumentBaseTelemetry): void => {
147
- #HandleUpdateInstrumentEvent = (instrumentName: Gauge, telemetry: InstrumentBaseTelemetry): void => {
148
- if (this.#aic) {
149
- this.#aic.UpdateInstrument(instrumentName, telemetry);
150
- }
151
- }
152
-
153
- // Will come from message channel
154
- #HandleErrorEvent = (error: JSONObject): void => {
155
- this.#store.commit('AuthorizeError', { // Authorize applications store
156
- message: error
157
- });
158
-
159
- // plugin to do this ...
160
- setTimeout(() => {
161
- this.#router.replace('/error'); //@@ was push
162
- }, 0);
163
- }
164
-
165
- #HandleAuthenticateEvent: AuthenticateEvent = (id_token: string): void => {
166
- if (this.#options.authenticateEvent) {
167
- this.#options.authenticateEvent(id_token);
168
- }
169
- this.#store.commit('stsOAuth2SDK/SessionData', id_token);
170
-
171
- //@@ new version here
172
- this.#auth2Store.UpdateSessionData(id_token);
173
- }
174
-
175
- #SetupRoute = (app, router) => {
176
- router.beforeEach(async (to, from) => {
177
- const store = app.config.globalProperties.$store;
178
- const sts = app.config.globalProperties.$sts;
179
-
180
- debug(`beforeEach: from: [${from.path}], to: [${to.path}]`); // gray
181
- if (store.getters['stsOAuth2SDK/LoggedIn'] === false) {
182
- console.log(`Not logged in`);
183
- // Not logged in
184
- if (to.path.localeCompare('/authorize') === 0) {
185
- console.log(`to = /authorize`);
186
- return true;
187
- } else if (to.path.localeCompare('/consent') === 0) {
188
- // Need to check if we are in the correct state, if not - drop back to the start of the process
189
- if (typeof store.getters.Session.sessionId !== 'undefined') {
190
- return true;
191
- }
192
- }
193
- if (to.path.localeCompare('/logout') === 0) {
194
- return true;
195
- }
196
- if (to.path.localeCompare('/error') === 0) {
197
- return true;
198
- }
199
- if (to.path.localeCompare('/logout') === 0) {
200
- return true;
201
- }
202
-
203
- const str = to.query;
204
- // Check if this route is from a redirect from the authorization server
205
- if (str[OAuth2ParameterType.CODE] || str[OAuth2ParameterType.ERROR]) {
206
-
207
- console.log(`#SetupRout:str = [${str}]`);
208
-
209
- const retVal: boolean = await sts.om.HandleRedirect(str);
210
- if (retVal) {
211
- // Success
212
- setTimeout(() => {
213
- window.history.replaceState(
214
- {},
215
- document.title,
216
- window.location.origin + '/');
217
- }, 0);
218
- return true;
219
- } else {
220
- // Error
221
- //@@ need the error data here - or use the vuex store ?
222
- this.#router.replace('/error'); //@@ was push
223
-
224
- //@@ should replaceState be used as in above?
225
- return false;
226
- }
227
- }
228
-
229
- const sessionRestored = await sts.om.RestoreSession();
230
- console.log(`#SetupRoute:sessionRestored [${sessionRestored}]`);
231
-
232
- if (sessionRestored !== true) {
233
- console.log('Session not restored - Need to Authorize');
234
- sts.om.Authorize();
235
- return false;
236
- } else {
237
- return '/';
238
- //router.replace({ path: '/' })
239
- }
240
- } else {
241
- // Prevent pages if already logged in
242
- if (to.path.localeCompare('/consent') === 0) {
243
- return '/';
244
- /*
245
- router.replace({ path: '/' })
246
- return false;
247
- */
248
- }
249
- if (to.path.localeCompare('/authorize') === 0) {
250
- router.replace({ path: '/' })
251
- return false;
252
- }
253
- if (to.path.localeCompare('/logout') === 0) {
254
- router.replace({ path: '/' })
255
- return false;
256
- }
257
- return true;
258
-
259
- /*
260
- if (to.path.localeCompare('/') === 0) {
261
- // In case press the back button in the browser shows previous query string params, replace them ...
262
- setTimeout(() => {
263
- window.history.replaceState(
264
- {},
265
- document.title,
266
- window.location.origin + '/');
267
- }, 0);
268
- return true;
269
- }
270
- */
271
- }
272
- })
273
- }
274
-
275
- // Replace with pinia
276
- // https://pinia.vuejs.org/
277
- // https://seb-l.github.io/pinia-plugin-persist/
278
- #SetupStoreNamespace = () => {
279
- this.#store.registerModule('stsOAuth2SDK', {
280
- namespaced: true,
281
-
282
- state () {
283
- return {
284
- // STS Client SDK options. These are parameters initiated by the client SPA and used for the end-to-end transaction processing.
285
- //authorizeOptions: { },
286
-
287
- sessionData: { },
288
- }
289
- },
290
-
291
- getters: {
292
- SessionData (state) {
293
- return state.sessionData;
294
- },
295
- LoggedIn (state) {
296
- if (typeof state.sessionData === 'undefined') {
297
- return false;
298
- }
299
- if (state.sessionData === null) {
300
- return false;
301
- }
302
- return true;
303
- },
304
- UserDetails (state) {
305
- //if (state.sessionData && state.sessionData.id_token) {
306
- if (state.sessionData) {
307
- const id_token = state.sessionData;
308
- const decodedIdToken = jwt_decode(id_token);
309
- return decodedIdToken;
310
- } else {
311
- return null;
312
- }
313
- }
314
- },
315
-
316
- mutations: {
317
- SessionData (state, sessionData) {
318
- state.sessionData = sessionData;
319
- console.log(`commit [sessionData]: ${JSON.stringify(sessionData)}`)
320
- },
321
- }
322
- }, { preserveState: true });
323
- }
324
-
325
- // Replace with pinia
326
- // https://pinia.vuejs.org/
327
- // https://seb-l.github.io/pinia-plugin-persist/
328
- #SetupPiniaStoreNamespace = () => {
329
- const store = defineStore('__pin_stsOAuth2SDK', {
330
- state: () => {
331
- return {
332
- sessionData: { }
333
- }
334
- },
335
- actions: {
336
- UpdateSessionData(newState) {
337
- this.sessionData = newState;
338
- }
339
- },
340
- getters: {
341
- SessionData: (state) => {
342
- return state.sessionData;
343
- },
344
- LoggedIn: (state) => {
345
- if (typeof state.sessionData === 'undefined') {
346
- return false;
347
- }
348
- if (state.sessionData === null) {
349
- return false;
350
- }
351
- return true;
352
- },
353
- UserDetails: (state) => {
354
- //if (state.sessionData && state.sessionData.id_token) {
355
- if (state.sessionData) {
356
- const id_token = state.sessionData;
357
- const decodedIdToken = jwt_decode(id_token);
358
- return decodedIdToken;
359
- } else {
360
- return null;
361
- }
362
- }
363
- },
364
- persist: {
365
- storage: window.sessionStorage
366
- }
367
- });
368
-
369
- this.#auth2Store = store();
370
- console.log(`================================= Pinia store setup`);
371
- console.log(this.#auth2Store);
372
- }
373
-
374
- RestoreSession = async(): Promise<boolean> => {
375
- try {
376
- const response: IOauth2ListenerMessageResponse = await this.#PostMessage({ command: IOauth2ListenerCommand.RESTORE_SESSION });
377
- return response.payload;
378
- } catch (error) {
379
- console.log(`RestoreSession Error: ${error}`); //red
380
- return false;
381
- }
382
- }
383
-
384
- Authorize = async (): Promise<void> => {
385
- try {
386
- const response: IOauth2ListenerMessageResponse = await this.#PostMessage({ command: IOauth2ListenerCommand.AUTHORIZE });
387
- this.#transactionStore.set(this.#STORAGE_AUTHORIZE_OPTIONS_KEY, response.payload.authorizeOptions);
388
- const url = response.payload.url;
389
- window.location.replace(url);
390
- } catch (error) {
391
- console.log(`Authorize Error: ${error}`); // red
392
- }
393
- }
394
-
395
- HandleRedirect = async (queryVars: JSONObject): Promise<boolean> => {
396
- try {
397
- let response: IOauth2ListenerMessageResponse = null;
398
- if (queryVars[OAuth2ParameterType.CODE]) {
399
-
400
- const authorizeOptions: IAuthorizeOptions = this.#transactionStore.get(this.#STORAGE_AUTHORIZE_OPTIONS_KEY) as IAuthorizeOptions;
401
- this.#transactionStore.remove(this.#STORAGE_AUTHORIZE_OPTIONS_KEY);
402
-
403
- response = await this.#PostMessage({ command: IOauth2ListenerCommand.HANDLE_REDIRECT, payload: {
404
- queryVars: queryVars as IAuthorizeResponse,
405
- authorizeOptions
406
- }});
407
- } else {
408
- response = await this.#PostMessage({ command: IOauth2ListenerCommand.HANDLE_REDIRECT, payload: queryVars as IAuthorizeErrorResponse });
409
- }
410
- return response.payload;
411
- } catch (error) {
412
- console.log(`HandleRedirect Error: ${error}`); // red
413
- return false;
414
- }
415
- }
416
-
417
- Logout = async (): Promise<boolean> => {
418
- try {
419
- const response: IOauth2ListenerMessageResponse = await this.#PostMessage({ command: IOauth2ListenerCommand.LOGOUT });
420
- return response.payload;
421
- } catch (error) {
422
- console.log(`Logout Error: ${error}`); // red
423
- return false;
424
- }
425
- }
426
-
427
- /*
428
- GetIDToken = async (): Promise<string> => {
429
- try {
430
- const response: IOauth2ListenerMessageResponse = await this.#PostMessage({ command: IOauth2ListenerCommand.ID_TOKEN });
431
- return response.payload;
432
- } catch (error) {
433
- console.log(`Logout Error: ${error}`); // red
434
- return null;
435
- }
436
- }
437
- */
438
- }
@@ -1,134 +0,0 @@
1
- export enum AuthorizeOptionsResponseType {
2
- CODE = 'code',
3
- ID_TOKEN = 'id_token',
4
- TOKEN = 'token'
5
- }
6
-
7
- export enum AuthorizeOptionsResponseMode {
8
- QUERY = 'query',
9
- FRAGMENT = 'fragment',
10
- FORM_POST = 'form_post'
11
- }
12
-
13
- // https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow
14
- export interface IAuthorizeOptions {
15
- client_id: string,
16
- nonce: string,
17
- response_type: AuthorizeOptionsResponseType[], // Must include: 'code' and may include: 'id_token' and/or 'token'
18
- redirect_uri: string,
19
- response_mode: AuthorizeOptionsResponseMode
20
- scope: string, // A space-separated list of scopes that you want the user to consent to. For the /authorize leg of the request, this parameter can cover multiple resources. This value allows your app to get consent for multiple web APIs you want to call.
21
- state: string, // Client application state (if required)
22
- code_challenge: string,
23
- code_challenge_method: string, //@@ enum S256
24
- code_verifier?: string
25
- }
26
-
27
- export interface IAuthorizeResponse {
28
- state: string, // state passed to authorize end-point
29
- code: string // authorization code
30
- }
31
-
32
- export interface IAuthorizeErrorResponse {
33
- error: string,
34
- error_description: string
35
- }
36
-
37
- export enum OAuthGrantTypes {
38
- CLIENT_CREDENTIALS = 'client_credentials',
39
- AUTHORIZATION_CODE = 'authorization_code',
40
- REFRESH_TOKEN = 'refresh_token'
41
- }
42
-
43
- export interface IAuthorizationCodeFlowParameters {
44
- client_id: string,
45
- scope: string,
46
- code: string,
47
- redirect_uri: string, // URI
48
- grant_type: OAuthGrantTypes, // 'authorization_code', //@@ need enum
49
- code_verifier: string
50
- }
51
-
52
- export interface IRefreshFlowParameters {
53
- client_id: string,
54
- scope: string,
55
- refresh_token: string, // JWT
56
- grant_type: OAuthGrantTypes // 'refresh_token' //@@ enum
57
- }
58
-
59
- export interface ITokenResponse {
60
- access_token: string, // JWT
61
- token_type: string, //@@ "Bearer" only
62
- expires_in: number,
63
- scope: string,
64
- refresh_token: string, // JWT
65
- id_token: string, // JWT
66
- }
67
-
68
- export interface ITokenErrorResponse {
69
- error: string,
70
- error_description: string,
71
- error_codes: number[],
72
- timestamp: number
73
- details: unknown //@@ STS attribute ?
74
- //"trace_id": "255d1aef-8c98-452f-ac51-23d051240864", //@@ MS attribute
75
- //"correlation_id": "fb3d2015-bc17-4bb9-bb85-30c5cf1aaaa7" //@@ MS attribute
76
- }
77
-
78
- export type AuthenticateEvent = (id_token: string) => void;
79
-
80
- // ---------------
81
-
82
- export enum IOauth2ListenerCommand {
83
- RESTORE_SESSION = 'RestoreSession',
84
- AUTHORIZE = 'Authorize',
85
- HANDLE_REDIRECT = 'HandleRedirect',
86
- LOGOUT = 'Logout',
87
- AUTHENTICATE_EVENT = 'AuthenticateEvent',
88
- ERROR = 'Error',
89
- LOG = '__LOG',
90
- UPDATE_INSTRUMENT = '__UPDATE_INSTRUMENT',
91
- ID_TOKEN = '__ID_TOKEN'
92
- }
93
-
94
- export interface IOauth2ListenerMessage {
95
- messageId?: number
96
- command: IOauth2ListenerCommand
97
- payload?: any
98
- }
99
-
100
- export interface IOauth2ListenerMessageResponse {
101
- messageId: number
102
- command: IOauth2ListenerCommand
103
- payload: any
104
- }
105
-
106
- export type StsOauth2WorkerFactory = () => Worker
107
-
108
- export interface ISTSOAuth2WorkerOptions {
109
- client_id: string
110
- scope: string
111
- redirect_uri: string
112
- audience: string
113
-
114
- brokerendpoint: string
115
- brokerport: string
116
- brokerapiroot: string
117
-
118
- authorizeendpoint: string
119
- authorizeport: string
120
- authorizeapiroot: string
121
-
122
- timeout: number
123
- }
124
-
125
- export interface ISTSOAuth2ManagerOptions {
126
- authenticateEvent?: AuthenticateEvent
127
- workerFactory?: StsOauth2WorkerFactory
128
- workerOptions: ISTSOAuth2WorkerOptions
129
- }
130
-
131
- export interface ISTSOAuth2WorkerMessage {
132
- workerPort: MessagePort,
133
- options: ISTSOAuth2WorkerOptions
134
- }