@nsshunt/stsoauth2plugin 0.0.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/.eslintrc.json +27 -0
- package/.github/dependabot.yml +13 -0
- package/.github/workflows/npm-publish.yml +54 -0
- package/LICENSE +21 -0
- package/README.md +1 -0
- package/babel.config.json +6 -0
- package/build.sh +29 -0
- package/dist/Utils/CryptoUtils.js +32 -0
- package/dist/Utils/CryptoUtils.js.map +1 -0
- package/dist/Utils/QueryParams.js +49 -0
- package/dist/Utils/QueryParams.js.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/index.test.js +8 -0
- package/dist/index.test.js.map +1 -0
- package/dist/stsStorage.js +152 -0
- package/dist/stsStorage.js.map +1 -0
- package/dist/stsoauth2manager.js +327 -0
- package/dist/stsoauth2manager.js.map +1 -0
- package/dist/stsoauth2types.js +29 -0
- package/dist/stsoauth2types.js.map +1 -0
- package/dist/stsoauth2worker.js +553 -0
- package/dist/stsoauth2worker.js.map +1 -0
- package/package.json +43 -0
- package/src/Utils/CryptoUtils.ts +32 -0
- package/src/Utils/QueryParams.ts +48 -0
- package/src/index.test.ts +10 -0
- package/src/index.ts +3 -0
- package/src/stsStorage.ts +158 -0
- package/src/stsoauth2manager.ts +350 -0
- package/src/stsoauth2types.ts +108 -0
- package/src/stsoauth2worker.ts +542 -0
- package/tsconfig.json +31 -0
- package/types/Utils/CryptoUtils.d.ts +7 -0
- package/types/Utils/CryptoUtils.d.ts.map +1 -0
- package/types/Utils/QueryParams.d.ts +8 -0
- package/types/Utils/QueryParams.d.ts.map +1 -0
- package/types/index.d.ts +3 -0
- package/types/index.d.ts.map +1 -0
- package/types/index.test.d.ts +1 -0
- package/types/index.test.d.ts.map +1 -0
- package/types/stsStorage.d.ts +22 -0
- package/types/stsStorage.d.ts.map +1 -0
- package/types/stsoauth2manager.d.ts +5 -0
- package/types/stsoauth2manager.d.ts.map +1 -0
- package/types/stsoauth2types.d.ts +89 -0
- package/types/stsoauth2types.d.ts.map +1 -0
- package/types/stsoauth2worker.d.ts +2 -0
- package/types/stsoauth2worker.d.ts.map +1 -0
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
import Debug from "debug";
|
|
2
|
+
const debug = Debug(`proc:${process.pid}:stsoauth2worker.ts`);
|
|
3
|
+
|
|
4
|
+
import 'colors'
|
|
5
|
+
|
|
6
|
+
import axios from "axios";
|
|
7
|
+
|
|
8
|
+
import { JSONObject, OAuth2ParameterType } from '@nsshunt/stsutils';
|
|
9
|
+
|
|
10
|
+
import CryptoUtils from './Utils/CryptoUtils'
|
|
11
|
+
import QueryParams from './Utils/QueryParams'
|
|
12
|
+
|
|
13
|
+
import jwt_decode from "jwt-decode"
|
|
14
|
+
|
|
15
|
+
import { IStsStorage, ClientStorageType, ClientStorageFactory } from './stsStorage'
|
|
16
|
+
|
|
17
|
+
import { StatusCodes } from 'http-status-codes'
|
|
18
|
+
|
|
19
|
+
import { AuthorizeOptionsResponseType, AuthorizeOptionsResponseMode, IAuthorizationCodeFlowParameters, IRefreshFlowParameters,
|
|
20
|
+
IAuthorizeOptions, ITokenResponse, IAuthorizeResponse, IAuthorizeErrorResponse, ITokenErrorResponse, OAuthGrantTypes, AuthenticateEvent,
|
|
21
|
+
IOauth2ListenerMessage, IOauth2ListenerCommand, IOauth2ListenerMessageResponse } from './stsoauth2types'
|
|
22
|
+
|
|
23
|
+
const CreateRandomString = (size = 43) => {
|
|
24
|
+
const randomValues = Array.from(self.crypto.getRandomValues(new Uint8Array(size)))
|
|
25
|
+
const b64 = window.btoa(String.fromCharCode(...randomValues));
|
|
26
|
+
return b64;
|
|
27
|
+
//return randomValues.toString('base64');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// STS Client SDK for SPAs
|
|
31
|
+
class STSOAuth2Worker {
|
|
32
|
+
//#storageManager = null;
|
|
33
|
+
#clientSessionStore: IStsStorage<ITokenResponse> = null; // In memory tokens while the client is logged in
|
|
34
|
+
#cUtils = new CryptoUtils();
|
|
35
|
+
#qParams = new QueryParams();
|
|
36
|
+
#STORAGE_AUTHORIZE_OPTIONS_KEY = 'authorize_options.stsmda.com.au';
|
|
37
|
+
#STORAGE_SESSION_KEY = 'session.stsmda.com.au';
|
|
38
|
+
#aic = null;
|
|
39
|
+
#errorCallback = null; //@@ will be replaced with a message back
|
|
40
|
+
//#store = null;
|
|
41
|
+
#handleAuthenticateEvent: AuthenticateEvent = null;
|
|
42
|
+
#oauthWorkerPort: MessagePort = null;
|
|
43
|
+
#currentMessageId = 0;
|
|
44
|
+
|
|
45
|
+
constructor(workerPort: MessagePort) {
|
|
46
|
+
//this.#store = app.config.globalProperties.$store;
|
|
47
|
+
|
|
48
|
+
// In memory storage for OAuth2 tokens for our valid session
|
|
49
|
+
this.#clientSessionStore = new ClientStorageFactory<ITokenResponse>({clientStorageType: ClientStorageType.MEMORY_STORAGE}).GetStorage();
|
|
50
|
+
|
|
51
|
+
//@@ needs to be sent the instrument manager controller port
|
|
52
|
+
//@@this.#aic = app.config.globalProperties.$sts.aic.PrimaryPublishInstrumentController;
|
|
53
|
+
|
|
54
|
+
//this.#handleAuthenticateEvent = handleAuthenticateEvent;
|
|
55
|
+
this.#handleAuthenticateEvent = (id_token: string) => {
|
|
56
|
+
const message: IOauth2ListenerMessage = {
|
|
57
|
+
messageId: -1, // un-solicited message
|
|
58
|
+
command: IOauth2ListenerCommand.AUTHENTICATE_EVENT
|
|
59
|
+
}
|
|
60
|
+
this.#ProcessCommand(message, id_token);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
this.#errorCallback = (error: any) => {
|
|
64
|
+
const message: IOauth2ListenerMessage = {
|
|
65
|
+
messageId: -1, // un-solicited message
|
|
66
|
+
command: IOauth2ListenerCommand.ERROR
|
|
67
|
+
}
|
|
68
|
+
this.#ProcessCommand(message, error);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
this.#oauthWorkerPort = workerPort;
|
|
72
|
+
this.SetupListener();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Attempt to restore a previous session using the STSBroker
|
|
76
|
+
/*
|
|
77
|
+
{ parameterType: OAuth2ParameterType.CLIENT_ID, errorType: authErrorType.CLIENT_ID_MISMATCH },
|
|
78
|
+
{ parameterType: OAuth2ParameterType.SCOPE, errorType: authErrorType.SCOPE_MISMATCH }
|
|
79
|
+
{ parameterType: OAuth2ParameterType.REDIRECT_URI, errorType: authErrorType.REDIRECT_URI_MISMATCH },
|
|
80
|
+
{ parameterType: OAuth2ParameterType.AUDIENCE, errorType: authErrorType.SCOPE_MISMATCH }
|
|
81
|
+
|
|
82
|
+
Successful Response
|
|
83
|
+
{
|
|
84
|
+
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik5HVEZ2ZEstZnl0aEV1Q...",
|
|
85
|
+
"token_type": "Bearer",
|
|
86
|
+
"expires_in": 3599,
|
|
87
|
+
"scope": "https%3A%2F%2Fgraph.microsoft.com%2Fmail.read",
|
|
88
|
+
"refresh_token": "AwABAAAAvPM1KaPlrEqdFSBzjqfTGAMxZGUTdM0t4B4...",
|
|
89
|
+
"id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJhdWQiOiIyZDRkMTFhMi1mODE0LTQ2YTctOD...",
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
Error Response
|
|
93
|
+
{
|
|
94
|
+
"error": "invalid_scope",
|
|
95
|
+
"error_description": "AADSTS70011: The provided value for the input parameter 'scope' is not valid. The scope https://foo.microsoft.com/mail.read is not valid.\r\nTrace ID: 255d1aef-8c98-452f-ac51-23d051240864\r\nCorrelation ID: fb3d2015-bc17-4bb9-bb85-30c5cf1aaaa7\r\nTimestamp: 2016-01-09 02:02:12Z",
|
|
96
|
+
"error_codes": [
|
|
97
|
+
70011
|
|
98
|
+
],
|
|
99
|
+
"timestamp": "2016-01-09 02:02:12Z",
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
*/
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
SetupListener = () => {
|
|
107
|
+
this.#oauthWorkerPort.onmessage = async (data: MessageEvent) => {
|
|
108
|
+
const auth2ListenerMessage: IOauth2ListenerMessage = data.data as IOauth2ListenerMessage;
|
|
109
|
+
switch (auth2ListenerMessage.command) {
|
|
110
|
+
case IOauth2ListenerCommand.RESTORE_SESSION :
|
|
111
|
+
this.#ProcessCommand(auth2ListenerMessage, await this.#RestoreSession());
|
|
112
|
+
break;
|
|
113
|
+
case IOauth2ListenerCommand.AUTHORIZE :
|
|
114
|
+
this.#ProcessCommand(auth2ListenerMessage, await this.#Authorize());
|
|
115
|
+
break;
|
|
116
|
+
case IOauth2ListenerCommand.HANDLE_REDIRECT :
|
|
117
|
+
this.#ProcessCommand(auth2ListenerMessage, await this.#HandleRedirect(auth2ListenerMessage.payload));
|
|
118
|
+
break;
|
|
119
|
+
case IOauth2ListenerCommand.LOGOUT :
|
|
120
|
+
this.#ProcessCommand(auth2ListenerMessage, await this.#Logout());
|
|
121
|
+
break;
|
|
122
|
+
default :
|
|
123
|
+
throw new Error(`Command: [${auth2ListenerMessage.command}'] not found.`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
#ProcessCommand = async (auth2ListenerMessage: IOauth2ListenerMessage, response: any) => {
|
|
129
|
+
const messageResponse: IOauth2ListenerMessageResponse = {
|
|
130
|
+
messageId: auth2ListenerMessage.messageId,
|
|
131
|
+
command: auth2ListenerMessage.command,
|
|
132
|
+
payload: response
|
|
133
|
+
}
|
|
134
|
+
this.#oauthWorkerPort.postMessage(messageResponse);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
#RestoreSession = async (): Promise<boolean> => {
|
|
138
|
+
//@@ attempt to get from client storage first
|
|
139
|
+
|
|
140
|
+
let restoredSessionData: ITokenResponse = null;
|
|
141
|
+
restoredSessionData = this.#clientSessionStore.get(this.#STORAGE_SESSION_KEY);
|
|
142
|
+
if (restoredSessionData !== null) {
|
|
143
|
+
console.log('Session restored from client storage.');
|
|
144
|
+
if (this.#aic) {
|
|
145
|
+
this.#aic.UpdateInstrument('m', { LogMessage: 'Session restored from client storage.' });
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
const url = `${process.env.BROKER_ENDPOINT}:${process.env.BROKER_PORT}${process.env.BROKER_API_ROOT}/session`;
|
|
149
|
+
console.log('RestoreSession');
|
|
150
|
+
console.log(url);
|
|
151
|
+
if (this.#aic) {
|
|
152
|
+
this.#aic.UpdateInstrument('m', { LogMessage: 'RestoreSession' });
|
|
153
|
+
this.#aic.UpdateInstrument('m', { LogMessage: url });
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
const retVal = await axios({
|
|
157
|
+
method: "post",
|
|
158
|
+
url: url,
|
|
159
|
+
data: {
|
|
160
|
+
[OAuth2ParameterType.CLIENT_ID]: process.env.CLIENT_ID,
|
|
161
|
+
[OAuth2ParameterType.SCOPE]: process.env.SCOPE,
|
|
162
|
+
[OAuth2ParameterType.REDIRECT_URI]: process.env.REDIRECT_URI,
|
|
163
|
+
[OAuth2ParameterType.AUDIENCE]: process.env.AUDIENCE
|
|
164
|
+
},
|
|
165
|
+
withCredentials: true, // Ensure cookies are passed to the service
|
|
166
|
+
timeout: parseInt(process.env.TIMEOUT),
|
|
167
|
+
});
|
|
168
|
+
if (retVal.data.status === StatusCodes.OK) {
|
|
169
|
+
restoredSessionData = retVal.data.detail;
|
|
170
|
+
this.#clientSessionStore.set(this.#STORAGE_SESSION_KEY, restoredSessionData);
|
|
171
|
+
console.log('Session restored from server side cookie.');
|
|
172
|
+
//this.#store.commit('stsOAuth2SDK/SessionData', restoredSessionData);
|
|
173
|
+
} else {
|
|
174
|
+
//@@ handle error better
|
|
175
|
+
//this.#store.commit('stsOAuth2SDK/SessionData', null);
|
|
176
|
+
console.log('Could not restore previous session:-');
|
|
177
|
+
console.log(JSON.stringify(retVal.data));
|
|
178
|
+
}
|
|
179
|
+
} catch (error) {
|
|
180
|
+
//@@ handle error better
|
|
181
|
+
//this.#store.commit('stsOAuth2SDK/SessionData', null);
|
|
182
|
+
console.log('Could not restore previous session (error state):-');
|
|
183
|
+
console.log(error);
|
|
184
|
+
console.log(JSON.stringify(error));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
//@@ must only use in-memory for this ...
|
|
189
|
+
//this.#store.commit('stsOAuth2SDK/SessionData', restoredSessionData);
|
|
190
|
+
if (restoredSessionData !== null) {
|
|
191
|
+
this.#handleAuthenticateEvent(restoredSessionData.id_token);
|
|
192
|
+
console.log('Refreshing tokens ...');
|
|
193
|
+
return this.#RefreshToken();
|
|
194
|
+
} else {
|
|
195
|
+
this.#handleAuthenticateEvent(null);
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
#Authorize = async (): Promise<JSONObject> => {
|
|
201
|
+
console.log('Authorize ...');
|
|
202
|
+
|
|
203
|
+
/* MS Example
|
|
204
|
+
--------------
|
|
205
|
+
https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize?
|
|
206
|
+
client_id=6731de76-14a6-49ae-97bc-6eba6914391e
|
|
207
|
+
&response_type=code
|
|
208
|
+
&redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp%2F
|
|
209
|
+
&response_mode=query
|
|
210
|
+
&scope=offline_access%20https%3A%2F%2Fgraph.microsoft.com%2Fuser.read%20api%3A%2F%2F
|
|
211
|
+
&state=12345
|
|
212
|
+
&code_challenge=YTFjNjI1OWYzMzA3MTI4ZDY2Njg5M2RkNmVjNDE5YmEyZGRhOGYyM2IzNjdmZWFhMTQ1ODg3NDcxY2Nl
|
|
213
|
+
&code_challenge_method=S256
|
|
214
|
+
|
|
215
|
+
Successful Response
|
|
216
|
+
|
|
217
|
+
GET http://localhost?
|
|
218
|
+
code=AwABAAAAvPM1KaPlrEqdFSBzjqfTGBCmLdgfSTLEMPGYuNHSUYBrq...
|
|
219
|
+
&state=12345
|
|
220
|
+
|
|
221
|
+
Error Response
|
|
222
|
+
GET http://localhost?
|
|
223
|
+
error=access_denied
|
|
224
|
+
&error_description=the+user+canceled+the+authentication
|
|
225
|
+
|
|
226
|
+
<< Hybrid Flow >>
|
|
227
|
+
|
|
228
|
+
https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize?
|
|
229
|
+
client_id=6731de76-14a6-49ae-97bc-6eba6914391e
|
|
230
|
+
&response_type=code%20id_token
|
|
231
|
+
&redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp%2F
|
|
232
|
+
&response_mode=fragment
|
|
233
|
+
&scope=openid%20offline_access%20https%3A%2F%2Fgraph.microsoft.com%2Fuser.read
|
|
234
|
+
&state=12345
|
|
235
|
+
&nonce=abcde
|
|
236
|
+
&code_challenge=YTFjNjI1OWYzMzA3MTI4ZDY2Njg5M2RkNmVjNDE5YmEyZGRhOGYyM2IzNjdmZWFhMTQ1ODg3NDcxY2Nl
|
|
237
|
+
&code_challenge_method=S256
|
|
238
|
+
|
|
239
|
+
Successful Response
|
|
240
|
+
|
|
241
|
+
GET https://login.microsoftonline.com/common/oauth2/nativeclient#
|
|
242
|
+
code=AwABAAAAvPM1KaPlrEqdFSBzjqfTGBCmLdgfSTLEMPGYuNHSUYBrq...
|
|
243
|
+
&id_token=eYj...
|
|
244
|
+
&state=12345
|
|
245
|
+
|
|
246
|
+
Notes:
|
|
247
|
+
The nonce is included as a claim inside the returned id_token
|
|
248
|
+
Ref: https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow
|
|
249
|
+
*/
|
|
250
|
+
|
|
251
|
+
const client_id = process.env.CLIENT_ID;
|
|
252
|
+
const nonce = this.#cUtils.CreateRandomString();
|
|
253
|
+
const response_type = [ AuthorizeOptionsResponseType.CODE ]
|
|
254
|
+
const redirect_uri = process.env.REDIRECT_URI;
|
|
255
|
+
const response_mode = AuthorizeOptionsResponseMode.QUERY
|
|
256
|
+
const scope = process.env.SCOPE;
|
|
257
|
+
const state = this.#cUtils.CreateRandomString();
|
|
258
|
+
const code_verifier = this.#cUtils.CreateRandomString();
|
|
259
|
+
const code_challenge = await this.#cUtils.DigestMessage(code_verifier);
|
|
260
|
+
const code_challenge_method = 'S256';
|
|
261
|
+
//let audience = process.env.AUDIENCE;
|
|
262
|
+
|
|
263
|
+
const authorizeOptions: IAuthorizeOptions = {
|
|
264
|
+
client_id,
|
|
265
|
+
nonce,
|
|
266
|
+
response_type,
|
|
267
|
+
redirect_uri,
|
|
268
|
+
response_mode,
|
|
269
|
+
scope,
|
|
270
|
+
state,
|
|
271
|
+
code_challenge,
|
|
272
|
+
code_challenge_method
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const url = `${process.env.AUTH_ENDPOINT}:${process.env.AUTH_PORT}${process.env.AUTH_APIROOT}?${this.#qParams.CreateQueryParams(authorizeOptions)}`;
|
|
276
|
+
|
|
277
|
+
console.log(url);
|
|
278
|
+
|
|
279
|
+
// Now add the code_verifier to the transaction data
|
|
280
|
+
authorizeOptions.code_verifier = code_verifier; //@@ Is this is the only thing required across the transaction ?
|
|
281
|
+
|
|
282
|
+
console.log(`Authorize:authorizeOptions: [${JSON.stringify(authorizeOptions)}]`);
|
|
283
|
+
|
|
284
|
+
return {
|
|
285
|
+
url,
|
|
286
|
+
authorizeOptions
|
|
287
|
+
}
|
|
288
|
+
//window.location.assign(url);
|
|
289
|
+
//@@ this may need to be a message back to the plugin to re-direct
|
|
290
|
+
//window.location.replace(url);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
#HandleRedirect = async (payload: any): Promise<boolean> => {
|
|
294
|
+
const queryVars: IAuthorizeResponse | IAuthorizeErrorResponse = payload.queryVars;
|
|
295
|
+
const authorizeOptions: IAuthorizeOptions = payload.authorizeOptions
|
|
296
|
+
|
|
297
|
+
console.log('HandleRedirect');
|
|
298
|
+
// We have been re-direct back here from the /authorize end-point
|
|
299
|
+
console.log(`HandleRedirect:Query Vars: [${JSON.stringify(queryVars)}]`);
|
|
300
|
+
|
|
301
|
+
if (queryVars[OAuth2ParameterType.CODE]) {
|
|
302
|
+
const response: IAuthorizeResponse = queryVars as IAuthorizeResponse;
|
|
303
|
+
|
|
304
|
+
console.log(`authorizeOptions from transaction state: [${JSON.stringify(authorizeOptions)}]`);
|
|
305
|
+
|
|
306
|
+
const redirectState = response.state;
|
|
307
|
+
const authorizeOptionsState = authorizeOptions.state;
|
|
308
|
+
|
|
309
|
+
if (authorizeOptionsState.localeCompare(redirectState) === 0) {
|
|
310
|
+
console.log('redirected state (from queryVars) matched previously saved transaction authorizeOptions state'.green);
|
|
311
|
+
|
|
312
|
+
return await this.#GetToken(authorizeOptions, response);
|
|
313
|
+
} else {
|
|
314
|
+
console.log('redirected state (from queryVars) did NOT match previously saved transaction authorizeOptions state'.red);
|
|
315
|
+
this.#errorCallback({message: 'State un-matched'});
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
} else if (queryVars[OAuth2ParameterType.ERROR]) {
|
|
319
|
+
const response: IAuthorizeErrorResponse = queryVars as IAuthorizeErrorResponse;
|
|
320
|
+
//@@ pass error back to parent thread (to the plugin) as a message
|
|
321
|
+
const error = response.error;
|
|
322
|
+
const errorDescription = response.error_description;
|
|
323
|
+
this.#errorCallback({message: 'State un-matched'});
|
|
324
|
+
return false;
|
|
325
|
+
} else {
|
|
326
|
+
// Invalid redirect query params
|
|
327
|
+
const error = 'Invalid redirect query params'; //@@ fix
|
|
328
|
+
const errorDescription = 'Invalid redirect query params description'; //@@ fix
|
|
329
|
+
this.#errorCallback({message: 'State un-matched'});
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/*
|
|
335
|
+
client_id=6731de76-14a6-49ae-97bc-6eba6914391e
|
|
336
|
+
&scope=https%3A%2F%2Fgraph.microsoft.com%2Fmail.read
|
|
337
|
+
&code=OAAABAAAAiL9Kn2Z27UubvWFPbm0gLWQJVzCTE9UkP3pSx1aXxUjq3n8b2JRLk4OxVXr...
|
|
338
|
+
&redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp%2F
|
|
339
|
+
&grant_type=authorization_code
|
|
340
|
+
&code_verifier=ThisIsntRandomButItNeedsToBe43CharactersLong
|
|
341
|
+
&client_secret=JqQX2PNo9bpM0uEihUPzyrh // NOTE: Only required for web apps. This secret needs to be URL-Encoded.
|
|
342
|
+
|
|
343
|
+
Successful Response
|
|
344
|
+
{
|
|
345
|
+
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik5HVEZ2ZEstZnl0aEV1Q...",
|
|
346
|
+
"token_type": "Bearer",
|
|
347
|
+
"expires_in": 3599,
|
|
348
|
+
"scope": "https%3A%2F%2Fgraph.microsoft.com%2Fmail.read",
|
|
349
|
+
"refresh_token": "AwABAAAAvPM1KaPlrEqdFSBzjqfTGAMxZGUTdM0t4B4...",
|
|
350
|
+
"id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJhdWQiOiIyZDRkMTFhMi1mODE0LTQ2YTctOD...",
|
|
351
|
+
}
|
|
352
|
+
*/
|
|
353
|
+
|
|
354
|
+
// Get access_token, refresh_token and id_token using OAuth2 Authorization Code Flow
|
|
355
|
+
#GetTokenFromBroker = async (authorizationCodeFlowParameters: IAuthorizationCodeFlowParameters | IRefreshFlowParameters): Promise<boolean> => {
|
|
356
|
+
console.log("#GetTokenFromBroker");
|
|
357
|
+
|
|
358
|
+
this.#clientSessionStore.remove(this.#STORAGE_SESSION_KEY);
|
|
359
|
+
|
|
360
|
+
const url = `${process.env.BROKER_ENDPOINT}:${process.env.BROKER_PORT}${process.env.BROKER_API_ROOT}/token`;
|
|
361
|
+
console.log(`#GetTokenFromBroker:url = [${url}]`);
|
|
362
|
+
console.log(authorizationCodeFlowParameters);
|
|
363
|
+
|
|
364
|
+
try {
|
|
365
|
+
const retVal = await axios({
|
|
366
|
+
method: "post",
|
|
367
|
+
url: url,
|
|
368
|
+
data: authorizationCodeFlowParameters,
|
|
369
|
+
withCredentials: true, // Ensure cookies are passed to the service
|
|
370
|
+
timeout: parseInt(process.env.TIMEOUT),
|
|
371
|
+
});
|
|
372
|
+
console.log(`retVal: ${JSON.stringify(retVal)}`);
|
|
373
|
+
|
|
374
|
+
if (retVal.status === StatusCodes.OK) {
|
|
375
|
+
console.log('Storing tokens...');
|
|
376
|
+
const tokenResponse: ITokenResponse = retVal.data as ITokenResponse;
|
|
377
|
+
//this.#store.commit('stsOAuth2SDK/SessionData', tokenResponse);
|
|
378
|
+
this.#handleAuthenticateEvent(tokenResponse.id_token);
|
|
379
|
+
this.#clientSessionStore.set(this.#STORAGE_SESSION_KEY, tokenResponse);
|
|
380
|
+
return true;
|
|
381
|
+
} else if (retVal.status === StatusCodes.UNAUTHORIZED) {
|
|
382
|
+
console.log('NOT Storing tokens...');
|
|
383
|
+
console.log(retVal.status);
|
|
384
|
+
|
|
385
|
+
//this.#store.commit('stsOAuth2SDK/SessionData', null);
|
|
386
|
+
this.#handleAuthenticateEvent(null);
|
|
387
|
+
|
|
388
|
+
const response: ITokenErrorResponse = retVal.data as ITokenErrorResponse;
|
|
389
|
+
|
|
390
|
+
//@@ store response in state
|
|
391
|
+
//@@ go to error page ??
|
|
392
|
+
return false;
|
|
393
|
+
|
|
394
|
+
} else {
|
|
395
|
+
// General error
|
|
396
|
+
console.log('NOT Storing tokens...');
|
|
397
|
+
console.log(retVal.status);
|
|
398
|
+
|
|
399
|
+
//this.#store.commit('stsOAuth2SDK/SessionData', null);
|
|
400
|
+
this.#handleAuthenticateEvent(null);
|
|
401
|
+
|
|
402
|
+
console.log('Could not obtain access_token from token end-point:-');
|
|
403
|
+
console.log(JSON.stringify(retVal.data));
|
|
404
|
+
//@@ store error in state to show in error page
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
} catch (error) {
|
|
408
|
+
//this.#store.commit('stsOAuth2SDK/SessionData', null);
|
|
409
|
+
this.#handleAuthenticateEvent(null);
|
|
410
|
+
//console.log('Could not restore previous session (error state):-');
|
|
411
|
+
console.log(error);
|
|
412
|
+
console.log(JSON.stringify(error));
|
|
413
|
+
|
|
414
|
+
//@@ store error in state to show in error page
|
|
415
|
+
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Get access_token, refresh_token and id_token using OAuth2 Authorization Code Flow
|
|
421
|
+
#GetToken = async (authorizeOptions: IAuthorizeOptions, authorizeResponse: IAuthorizeResponse): Promise<boolean> => {
|
|
422
|
+
console.log("#GetToken");
|
|
423
|
+
console.log(authorizeResponse);
|
|
424
|
+
|
|
425
|
+
this.#clientSessionStore.set(this.#STORAGE_SESSION_KEY, null);
|
|
426
|
+
|
|
427
|
+
const authorizationCodeFlowParameters: IAuthorizationCodeFlowParameters = {
|
|
428
|
+
client_id: process.env.CLIENT_ID,
|
|
429
|
+
scope: process.env.SCOPE,
|
|
430
|
+
code: authorizeResponse.code,
|
|
431
|
+
redirect_uri: process.env.REDIRECT_URI,
|
|
432
|
+
grant_type: OAuthGrantTypes.AUTHORIZATION_CODE,
|
|
433
|
+
code_verifier: authorizeOptions.code_verifier
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return this.#GetTokenFromBroker(authorizationCodeFlowParameters);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/*
|
|
440
|
+
// Line breaks for legibility only
|
|
441
|
+
|
|
442
|
+
POST /{tenant}/oauth2/v2.0/token HTTP/1.1
|
|
443
|
+
Host: https://login.microsoftonline.com
|
|
444
|
+
Content-Type: application/x-www-form-urlencoded
|
|
445
|
+
|
|
446
|
+
client_id=535fb089-9ff3-47b6-9bfb-4f1264799865
|
|
447
|
+
&scope=https%3A%2F%2Fgraph.microsoft.com%2Fmail.read
|
|
448
|
+
&refresh_token=OAAABAAAAiL9Kn2Z27UubvWFPbm0gLWQJVzCTE9UkP3pSx1aXxUjq...
|
|
449
|
+
&grant_type=refresh_token
|
|
450
|
+
&client_secret=sampleCredentia1s // NOTE: Only required for web apps. This secret needs to be URL-Encoded
|
|
451
|
+
|
|
452
|
+
Error Response
|
|
453
|
+
{
|
|
454
|
+
"error": "invalid_scope",
|
|
455
|
+
"error_description": "AADSTS70011: The provided value for the input parameter 'scope' is not valid. The scope https://foo.microsoft.com/mail.read is not valid.\r\nTrace ID: 255d1aef-8c98-452f-ac51-23d051240864\r\nCorrelation ID: fb3d2015-bc17-4bb9-bb85-30c5cf1aaaa7\r\nTimestamp: 2016-01-09 02:02:12Z",
|
|
456
|
+
"error_codes": [
|
|
457
|
+
70011
|
|
458
|
+
],
|
|
459
|
+
"timestamp": "2016-01-09 02:02:12Z",
|
|
460
|
+
"trace_id": "255d1aef-8c98-452f-ac51-23d051240864",
|
|
461
|
+
"correlation_id": "fb3d2015-bc17-4bb9-bb85-30c5cf1aaaa7"
|
|
462
|
+
}
|
|
463
|
+
*/
|
|
464
|
+
|
|
465
|
+
#RefreshToken = async (): Promise<boolean> => {
|
|
466
|
+
// Get access_token, refresh_token and id_token using OAuth2 Authorization Code Flow
|
|
467
|
+
console.log("RefreshToken");
|
|
468
|
+
|
|
469
|
+
//let currentSessionData = this.#store.getters['stsOAuth2SDK/SessionData'];
|
|
470
|
+
const currentSessionData: ITokenResponse = this.#clientSessionStore.get(this.#STORAGE_SESSION_KEY);
|
|
471
|
+
if (currentSessionData) {
|
|
472
|
+
const refreshFlowParameters: IRefreshFlowParameters = {
|
|
473
|
+
client_id: process.env.CLIENT_ID,
|
|
474
|
+
scope: process.env.SCOPE,
|
|
475
|
+
refresh_token: currentSessionData.refresh_token,
|
|
476
|
+
grant_type: OAuthGrantTypes.REFRESH_TOKEN
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return this.#GetTokenFromBroker(refreshFlowParameters);
|
|
480
|
+
} else {
|
|
481
|
+
// show error
|
|
482
|
+
//@@ no valid session exists for refresh
|
|
483
|
+
return false;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// call broker to logout
|
|
488
|
+
// broker to logout of server
|
|
489
|
+
// delete cookie
|
|
490
|
+
// clear session storage
|
|
491
|
+
// clear all state from $store
|
|
492
|
+
#Logout = async (): Promise<boolean> => {
|
|
493
|
+
console.log('Logout');
|
|
494
|
+
const url = `${process.env.BROKER_ENDPOINT}:${process.env.BROKER_PORT}${process.env.BROKER_API_ROOT}/logout`;
|
|
495
|
+
console.log(url);
|
|
496
|
+
|
|
497
|
+
const currentSessionData: ITokenResponse = this.#clientSessionStore.get(this.#STORAGE_SESSION_KEY);
|
|
498
|
+
const refresh_token = currentSessionData.refresh_token;
|
|
499
|
+
console.log(refresh_token);
|
|
500
|
+
|
|
501
|
+
const decodedRefreshToken: JSONObject = jwt_decode<JSONObject>(refresh_token);
|
|
502
|
+
console.log(decodedRefreshToken);
|
|
503
|
+
const sessionId = decodedRefreshToken.sts_session;
|
|
504
|
+
console.log(sessionId);
|
|
505
|
+
|
|
506
|
+
this.#clientSessionStore.remove(this.#STORAGE_SESSION_KEY);
|
|
507
|
+
//this.#store.commit('stsOAuth2SDK/SessionData', null);
|
|
508
|
+
this.#handleAuthenticateEvent(null);
|
|
509
|
+
|
|
510
|
+
try {
|
|
511
|
+
const retVal = await axios({
|
|
512
|
+
method: "post",
|
|
513
|
+
url: url,
|
|
514
|
+
data: {
|
|
515
|
+
sessionId
|
|
516
|
+
},
|
|
517
|
+
withCredentials: true, // Ensure cookies are passed to the service
|
|
518
|
+
timeout: parseInt(process.env.TIMEOUT),
|
|
519
|
+
});
|
|
520
|
+
if (retVal.data.status === StatusCodes.OK) {
|
|
521
|
+
return true;
|
|
522
|
+
} else {
|
|
523
|
+
console.log('Error during logout (server side)');
|
|
524
|
+
console.log(JSON.stringify(retVal.data));
|
|
525
|
+
return false;
|
|
526
|
+
}
|
|
527
|
+
} catch (error) {
|
|
528
|
+
console.log('Error during logout (server side)');
|
|
529
|
+
console.log(error);
|
|
530
|
+
console.log(JSON.stringify(error));
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
let oAuth2Worker: STSOAuth2Worker = null;
|
|
537
|
+
|
|
538
|
+
onmessage = async function(data: MessageEvent)
|
|
539
|
+
{
|
|
540
|
+
const workerPort = data.data as MessagePort;
|
|
541
|
+
oAuth2Worker = new STSOAuth2Worker(workerPort);
|
|
542
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "@tsconfig/node18/tsconfig.json",
|
|
3
|
+
"include": ["src/**/*" ],
|
|
4
|
+
"exclude": ["node_modules", "**/node_modules/**/*", "**/*.spec.ts"],
|
|
5
|
+
"compilerOptions": {
|
|
6
|
+
"module": "esnext",
|
|
7
|
+
"target": "es2021",
|
|
8
|
+
"moduleResolution": "node",
|
|
9
|
+
"sourceMap": true,
|
|
10
|
+
"outDir": "dist",
|
|
11
|
+
"allowJs": true,
|
|
12
|
+
"declaration": true,
|
|
13
|
+
"declarationDir": "./types",
|
|
14
|
+
"declarationMap": true,
|
|
15
|
+
|
|
16
|
+
"noImplicitAny": false,
|
|
17
|
+
"strictNullChecks": false,
|
|
18
|
+
|
|
19
|
+
"lib": [
|
|
20
|
+
// Should target at least ES2016 in Vue 3
|
|
21
|
+
// Support for newer versions of language built-ins are
|
|
22
|
+
// left for the users to include, because that would require:
|
|
23
|
+
// - either the project doesn't need to support older versions of browsers;
|
|
24
|
+
// - or the project has properly included the necessary polyfills.
|
|
25
|
+
"ES2016",
|
|
26
|
+
"DOM",
|
|
27
|
+
"DOM.Iterable"
|
|
28
|
+
// No `ScriptHost` because Vue 3 dropped support for IE
|
|
29
|
+
],
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CryptoUtils.d.ts","sourceRoot":"","sources":["../../src/Utils/CryptoUtils.ts"],"names":[],"mappings":"AAAA,qBAAa,WAAW;IACvB,aAAa,oCASZ;IAED,kBAAkB,4BAOjB;IAED,oBAAoB,eAOnB;CACD;AAED,eAAe,WAAW,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"QueryParams.d.ts","sourceRoot":"","sources":["../../src/Utils/QueryParams.ts"],"names":[],"mappings":"AAEA,cAAM,WAAW;IAChB,iBAAiB,sBAQhB;IAED,iBAAiB,0BAWhB;IAED,eAAe,qBAcd;IAED,cAAc,WAEb;CACD;AAED,eAAe,WAAW,CAAC"}
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=index.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { JSONObject } from "@nsshunt/stsutils";
|
|
2
|
+
export interface IStsStorage<T> {
|
|
3
|
+
get(key: string): T;
|
|
4
|
+
set(key: string, value: T, options?: JSONObject): void;
|
|
5
|
+
remove(key: string): void;
|
|
6
|
+
}
|
|
7
|
+
export declare enum ClientStorageType {
|
|
8
|
+
LOCAL_STORAGE = "LocalStorage",
|
|
9
|
+
SESSION_STORAGE = "SessionStorage",
|
|
10
|
+
COOKIE_STORAGE = "CookieStorage",
|
|
11
|
+
MEMORY_STORAGE = "MemoryStorage"
|
|
12
|
+
}
|
|
13
|
+
export declare class ClientStorageOptions {
|
|
14
|
+
clientStorageType: ClientStorageType;
|
|
15
|
+
storageOptions?: JSONObject;
|
|
16
|
+
}
|
|
17
|
+
export declare class ClientStorageFactory<T> {
|
|
18
|
+
#private;
|
|
19
|
+
constructor(options: ClientStorageOptions);
|
|
20
|
+
GetStorage(): IStsStorage<T>;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=stsStorage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stsStorage.d.ts","sourceRoot":"","sources":["../src/stsStorage.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE/C,MAAM,WAAW,WAAW,CAAC,CAAC;IAC7B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,CAAA;IACnB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,CAAA;IACtD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,oBAAY,iBAAiB;IAC5B,aAAa,iBAAiB;IAC9B,eAAe,mBAAmB;IAClC,cAAc,kBAAkB;IAChC,cAAc,kBAAkB;CAChC;AA2GD,qBAAa,oBAAoB;IAChC,iBAAiB,EAAE,iBAAiB,CAAoC;IACxE,cAAc,CAAC,EAAE,UAAU,CAAA;CAC3B;AAED,qBAAa,oBAAoB,CAAC,CAAC;;gBAItB,OAAO,EAAE,oBAAoB;IAoBzC,UAAU,IAAI,WAAW,CAAC,CAAC,CAAC;CAI5B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stsoauth2manager.d.ts","sourceRoot":"","sources":["../src/stsoauth2manager.ts"],"names":[],"mappings":"AAsVA,QAAA,MAAM,sBAAsB;;CAK3B,CAAA;AAED,eAAe,sBAAsB,CAAC"}
|