@azure/msal-node-extensions 1.0.0-alpha.31 → 1.0.0-alpha.34
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/LICENSE +21 -21
- package/README.md +192 -192
- package/binding.gyp +39 -39
- package/dist/msal-node-extensions.cjs.development.js +4 -4
- package/dist/msal-node-extensions.cjs.development.js.map +1 -1
- package/dist/msal-node-extensions.cjs.production.min.js +1 -1
- package/dist/msal-node-extensions.cjs.production.min.js.map +1 -1
- package/dist/msal-node-extensions.esm.js +4 -4
- package/dist/msal-node-extensions.esm.js.map +1 -1
- package/dist/packageMetadata.d.ts +1 -1
- package/package.json +69 -66
- package/src/Dpapi.ts +12 -12
- package/src/broker/NativeBrokerPlugin.ts +418 -418
- package/src/dpapi-addon/dpapi_addon.h +7 -7
- package/src/dpapi-addon/dpapi_not_supported.cpp +13 -13
- package/src/dpapi-addon/dpapi_win.cpp +106 -106
- package/src/dpapi-addon/main.cpp +30 -30
- package/src/error/NativeAuthError.ts +19 -19
- package/src/error/PersistenceError.ts +101 -101
- package/src/index.ts +17 -17
- package/src/lock/CrossPlatformLock.ts +89 -89
- package/src/lock/CrossPlatformLockOptions.ts +15 -15
- package/src/packageMetadata.ts +3 -3
- package/src/persistence/BasePersistence.ts +43 -43
- package/src/persistence/DataProtectionScope.ts +20 -20
- package/src/persistence/FilePersistence.ts +140 -140
- package/src/persistence/FilePersistenceWithDataProtection.ts +92 -92
- package/src/persistence/IPersistence.ts +17 -17
- package/src/persistence/IPersistenceConfiguration.ts +17 -17
- package/src/persistence/KeychainPersistence.ts +89 -89
- package/src/persistence/LibSecretPersistence.ts +90 -90
- package/src/persistence/PersistenceCachePlugin.ts +106 -106
- package/src/persistence/PersistenceCreator.ts +78 -78
- package/src/utils/Constants.ts +67 -67
- package/src/utils/Environment.ts +101 -101
|
@@ -1,418 +1,418 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
-
* Licensed under the MIT License.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { AccountInfo, AuthenticationResult, AuthenticationScheme, ClientAuthError, ClientConfigurationError, Constants, IdTokenClaims, INativeBrokerPlugin, InteractionRequiredAuthError, Logger, LoggerOptions, NativeRequest, NativeSignOutRequest, PromptValue, ServerError } from "@azure/msal-common";
|
|
7
|
-
import { msalNodeRuntime, Account, AuthParameters, AuthResult, ErrorStatus, MsalRuntimeError, ReadAccountResult, DiscoverAccountsResult, SignOutResult, LogLevel as MsalRuntimeLogLevel} from "@azure/msal-node-runtime";
|
|
8
|
-
import { ErrorCodes } from "../utils/Constants";
|
|
9
|
-
import { NativeAuthError } from "../error/NativeAuthError";
|
|
10
|
-
import { version, name } from "../packageMetadata";
|
|
11
|
-
|
|
12
|
-
export class NativeBrokerPlugin implements INativeBrokerPlugin {
|
|
13
|
-
private logger: Logger;
|
|
14
|
-
isBrokerAvailable: boolean;
|
|
15
|
-
|
|
16
|
-
constructor() {
|
|
17
|
-
const defaultLoggerOptions: LoggerOptions = {
|
|
18
|
-
loggerCallback: (): void => {
|
|
19
|
-
// Empty logger callback
|
|
20
|
-
},
|
|
21
|
-
piiLoggingEnabled: false
|
|
22
|
-
};
|
|
23
|
-
this.logger = new Logger(defaultLoggerOptions, name, version); // Default logger
|
|
24
|
-
this.isBrokerAvailable = msalNodeRuntime.StartupError ? false : true;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
setLogger(loggerOptions: LoggerOptions): void {
|
|
28
|
-
this.logger = new Logger(loggerOptions, name, version);
|
|
29
|
-
const logCallback = (message: string, logLevel: MsalRuntimeLogLevel, containsPii: boolean) => {
|
|
30
|
-
switch(logLevel) {
|
|
31
|
-
case MsalRuntimeLogLevel.Trace:
|
|
32
|
-
if (containsPii) {
|
|
33
|
-
this.logger.tracePii(message);
|
|
34
|
-
} else {
|
|
35
|
-
this.logger.trace(message);
|
|
36
|
-
}
|
|
37
|
-
break;
|
|
38
|
-
case MsalRuntimeLogLevel.Debug:
|
|
39
|
-
if (containsPii) {
|
|
40
|
-
this.logger.tracePii(message);
|
|
41
|
-
} else {
|
|
42
|
-
this.logger.trace(message);
|
|
43
|
-
}
|
|
44
|
-
break;
|
|
45
|
-
case MsalRuntimeLogLevel.Info:
|
|
46
|
-
if (containsPii) {
|
|
47
|
-
this.logger.infoPii(message);
|
|
48
|
-
} else {
|
|
49
|
-
this.logger.info(message);
|
|
50
|
-
}
|
|
51
|
-
break;
|
|
52
|
-
case MsalRuntimeLogLevel.Warning:
|
|
53
|
-
if (containsPii) {
|
|
54
|
-
this.logger.warningPii(message);
|
|
55
|
-
} else {
|
|
56
|
-
this.logger.warning(message);
|
|
57
|
-
}
|
|
58
|
-
break;
|
|
59
|
-
case MsalRuntimeLogLevel.Error:
|
|
60
|
-
if (containsPii) {
|
|
61
|
-
this.logger.errorPii(message);
|
|
62
|
-
} else {
|
|
63
|
-
this.logger.error(message);
|
|
64
|
-
}
|
|
65
|
-
break;
|
|
66
|
-
case MsalRuntimeLogLevel.Fatal:
|
|
67
|
-
if (containsPii) {
|
|
68
|
-
this.logger.errorPii(message);
|
|
69
|
-
} else {
|
|
70
|
-
this.logger.error(message);
|
|
71
|
-
}
|
|
72
|
-
break;
|
|
73
|
-
default:
|
|
74
|
-
if (containsPii) {
|
|
75
|
-
this.logger.infoPii(message);
|
|
76
|
-
} else {
|
|
77
|
-
this.logger.info(message);
|
|
78
|
-
}
|
|
79
|
-
break;
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
try {
|
|
83
|
-
msalNodeRuntime.RegisterLogger(logCallback, loggerOptions.piiLoggingEnabled);
|
|
84
|
-
} catch (e) {
|
|
85
|
-
const wrappedError = this.wrapError(e);
|
|
86
|
-
if (wrappedError) {
|
|
87
|
-
throw wrappedError;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async getAccountById(accountId: string, correlationId: string): Promise<AccountInfo> {
|
|
93
|
-
this.logger.trace("NativeBrokerPlugin - getAccountById called", correlationId);
|
|
94
|
-
const readAccountResult = await this.readAccountById(accountId, correlationId);
|
|
95
|
-
return this.generateAccountInfo(readAccountResult.account);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
async getAllAccounts(clientId:string, correlationId: string): Promise<AccountInfo[]> {
|
|
99
|
-
this.logger.trace("NativeBrokerPlugin - getAllAccounts called", correlationId);
|
|
100
|
-
return new Promise((resolve, reject) => {
|
|
101
|
-
const resultCallback = (result: DiscoverAccountsResult) => {
|
|
102
|
-
try {
|
|
103
|
-
result.CheckError();
|
|
104
|
-
} catch (e) {
|
|
105
|
-
const wrappedError = this.wrapError(e);
|
|
106
|
-
if (wrappedError) {
|
|
107
|
-
reject(wrappedError);
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const accountInfoResult = [];
|
|
113
|
-
result.accounts.forEach((account: Account) => {
|
|
114
|
-
accountInfoResult.push(this.generateAccountInfo(account));
|
|
115
|
-
});
|
|
116
|
-
resolve(accountInfoResult);
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
try {
|
|
120
|
-
msalNodeRuntime.DiscoverAccountsAsync(clientId, correlationId, resultCallback);
|
|
121
|
-
} catch (e) {
|
|
122
|
-
const wrappedError = this.wrapError(e);
|
|
123
|
-
if (wrappedError) {
|
|
124
|
-
reject(wrappedError);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
async acquireTokenSilent(request: NativeRequest): Promise<AuthenticationResult> {
|
|
131
|
-
this.logger.trace("NativeBrokerPlugin - acquireTokenSilent called", request.correlationId);
|
|
132
|
-
const authParams = this.generateRequestParameters(request);
|
|
133
|
-
const account = await this.getAccount(request);
|
|
134
|
-
|
|
135
|
-
return new Promise((resolve: (value: AuthenticationResult) => void, reject) => {
|
|
136
|
-
const resultCallback = (result: AuthResult) => {
|
|
137
|
-
try {
|
|
138
|
-
result.CheckError();
|
|
139
|
-
} catch (e) {
|
|
140
|
-
const wrappedError = this.wrapError(e);
|
|
141
|
-
if (wrappedError) {
|
|
142
|
-
reject(wrappedError);
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
const authenticationResult = this.getAuthenticationResult(request, result);
|
|
147
|
-
resolve(authenticationResult);
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
try {
|
|
151
|
-
if (account) {
|
|
152
|
-
msalNodeRuntime.AcquireTokenSilentlyAsync(authParams, request.correlationId, account, resultCallback);
|
|
153
|
-
} else {
|
|
154
|
-
msalNodeRuntime.SignInSilentlyAsync(authParams, request.correlationId, resultCallback);
|
|
155
|
-
}
|
|
156
|
-
} catch (e) {
|
|
157
|
-
const wrappedError = this.wrapError(e);
|
|
158
|
-
if (wrappedError) {
|
|
159
|
-
reject(wrappedError);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
async acquireTokenInteractive(request: NativeRequest, providedWindowHandle?: Buffer): Promise<AuthenticationResult> {
|
|
166
|
-
this.logger.trace("NativeBrokerPlugin - acquireTokenInteractive called", request.correlationId);
|
|
167
|
-
const authParams = this.generateRequestParameters(request);
|
|
168
|
-
const account = await this.getAccount(request);
|
|
169
|
-
const windowHandle = providedWindowHandle || Buffer.from([0]);
|
|
170
|
-
|
|
171
|
-
return new Promise((resolve: (value: AuthenticationResult) => void, reject) => {
|
|
172
|
-
const resultCallback = (result: AuthResult) => {
|
|
173
|
-
try {
|
|
174
|
-
result.CheckError();
|
|
175
|
-
} catch (e) {
|
|
176
|
-
const wrappedError = this.wrapError(e);
|
|
177
|
-
if (wrappedError) {
|
|
178
|
-
reject(wrappedError);
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
const authenticationResult = this.getAuthenticationResult(request, result);
|
|
183
|
-
resolve(authenticationResult);
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
try {
|
|
187
|
-
switch (request.prompt) {
|
|
188
|
-
case PromptValue.LOGIN:
|
|
189
|
-
case PromptValue.SELECT_ACCOUNT:
|
|
190
|
-
case PromptValue.CREATE:
|
|
191
|
-
this.logger.info("Calling native interop SignInInteractively API", request.correlationId);
|
|
192
|
-
const loginHint = request.loginHint || Constants.EMPTY_STRING;
|
|
193
|
-
msalNodeRuntime.SignInInteractivelyAsync(windowHandle, authParams, request.correlationId, loginHint, resultCallback);
|
|
194
|
-
break;
|
|
195
|
-
case PromptValue.NONE:
|
|
196
|
-
if (account) {
|
|
197
|
-
this.logger.info("Calling native interop AcquireTokenSilently API", request.correlationId);
|
|
198
|
-
msalNodeRuntime.AcquireTokenSilentlyAsync(authParams, request.correlationId, account, resultCallback);
|
|
199
|
-
} else {
|
|
200
|
-
this.logger.info("Calling native interop SignInSilently API", request.correlationId);
|
|
201
|
-
msalNodeRuntime.SignInSilentlyAsync(authParams, request.correlationId, resultCallback);
|
|
202
|
-
}
|
|
203
|
-
break;
|
|
204
|
-
default:
|
|
205
|
-
if (account) {
|
|
206
|
-
this.logger.info("Calling native interop AcquireTokenInteractively API", request.correlationId);
|
|
207
|
-
msalNodeRuntime.AcquireTokenInteractivelyAsync(windowHandle, authParams, request.correlationId, account, resultCallback);
|
|
208
|
-
} else {
|
|
209
|
-
this.logger.info("Calling native interop SignIn API", request.correlationId);
|
|
210
|
-
const loginHint = request.loginHint || Constants.EMPTY_STRING;
|
|
211
|
-
msalNodeRuntime.SignInAsync(windowHandle, authParams, request.correlationId, loginHint, resultCallback);
|
|
212
|
-
}
|
|
213
|
-
break;
|
|
214
|
-
}
|
|
215
|
-
} catch (e) {
|
|
216
|
-
const wrappedError = this.wrapError(e);
|
|
217
|
-
if (wrappedError) {
|
|
218
|
-
reject(wrappedError);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
async signOut(request: NativeSignOutRequest): Promise<void> {
|
|
225
|
-
this.logger.trace("NativeBrokerPlugin - signOut called", request.correlationId);
|
|
226
|
-
|
|
227
|
-
const account = await this.getAccount(request);
|
|
228
|
-
if (!account) {
|
|
229
|
-
throw ClientAuthError.createNoAccountFoundError();
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
return new Promise((resolve, reject) => {
|
|
233
|
-
const resultCallback = (result: SignOutResult) => {
|
|
234
|
-
try {
|
|
235
|
-
result.CheckError();
|
|
236
|
-
} catch (e) {
|
|
237
|
-
const wrappedError = this.wrapError(e);
|
|
238
|
-
if (wrappedError) {
|
|
239
|
-
reject(wrappedError);
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
resolve();
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
try {
|
|
247
|
-
msalNodeRuntime.SignOutSilentlyAsync(request.clientId, request.correlationId, account, resultCallback);
|
|
248
|
-
} catch (e) {
|
|
249
|
-
const wrappedError = this.wrapError(e);
|
|
250
|
-
if (wrappedError) {
|
|
251
|
-
reject(wrappedError);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
private async getAccount(request: NativeRequest | NativeSignOutRequest): Promise<Account|null> {
|
|
258
|
-
if (request.accountId) {
|
|
259
|
-
const readAccountResult = await this.readAccountById(request.accountId, request.correlationId);
|
|
260
|
-
return readAccountResult.account;
|
|
261
|
-
}
|
|
262
|
-
return null;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
private async readAccountById(accountId: string, correlationId: string): Promise<ReadAccountResult> {
|
|
266
|
-
this.logger.trace("NativeBrokerPlugin - readAccountById called", correlationId);
|
|
267
|
-
|
|
268
|
-
return new Promise((resolve, reject) => {
|
|
269
|
-
const resultCallback = (result: ReadAccountResult) => {
|
|
270
|
-
try {
|
|
271
|
-
result.CheckError();
|
|
272
|
-
} catch (e) {
|
|
273
|
-
const wrappedError = this.wrapError(e);
|
|
274
|
-
if (wrappedError) {
|
|
275
|
-
reject(wrappedError);
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
resolve(result);
|
|
280
|
-
};
|
|
281
|
-
|
|
282
|
-
try {
|
|
283
|
-
msalNodeRuntime.ReadAccountByIdAsync(accountId, correlationId, resultCallback);
|
|
284
|
-
} catch (e) {
|
|
285
|
-
const wrappedError = this.wrapError(e);
|
|
286
|
-
if (wrappedError) {
|
|
287
|
-
reject(wrappedError);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
});
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
private generateRequestParameters(request: NativeRequest): AuthParameters {
|
|
294
|
-
this.logger.trace("NativeBrokerPlugin - generateRequestParameters called", request.correlationId);
|
|
295
|
-
const authParams = new msalNodeRuntime.AuthParameters();
|
|
296
|
-
|
|
297
|
-
try{
|
|
298
|
-
authParams.CreateAuthParameters(request.clientId, request.authority);
|
|
299
|
-
authParams.SetRedirectUri(request.redirectUri);
|
|
300
|
-
authParams.SetRequestedScopes(request.scopes.join(" "));
|
|
301
|
-
|
|
302
|
-
if (request.claims) {
|
|
303
|
-
authParams.SetDecodedClaims(request.claims);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
if (request.authenticationScheme === AuthenticationScheme.POP) {
|
|
307
|
-
if (!request.resourceRequestMethod || !request.resourceRequestUri || !request.shrNonce) {
|
|
308
|
-
throw new Error("Authentication Scheme set to POP but one or more of the following parameters are missing: resourceRequestMethod, resourceRequestUri, shrNonce");
|
|
309
|
-
}
|
|
310
|
-
const resourceUrl = new URL(request.resourceRequestUri);
|
|
311
|
-
authParams.SetPopParams(request.resourceRequestMethod, resourceUrl.host, resourceUrl.pathname, request.shrNonce);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
if (request.extraParameters) {
|
|
315
|
-
Object.keys(request.extraParameters).forEach((key) => {
|
|
316
|
-
authParams.SetAdditionalParameter(key, request.extraParameters[key]);
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
} catch (e) {
|
|
320
|
-
const wrappedError = this.wrapError(e);
|
|
321
|
-
if (wrappedError) {
|
|
322
|
-
throw wrappedError;
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
return authParams;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
private getAuthenticationResult(request: NativeRequest, authResult: AuthResult): AuthenticationResult {
|
|
330
|
-
this.logger.trace("NativeBrokerPlugin - getAuthenticationResult called", request.correlationId);
|
|
331
|
-
|
|
332
|
-
let fromCache: boolean;
|
|
333
|
-
try {
|
|
334
|
-
const telemetryJSON = JSON.parse(authResult.telemetryData);
|
|
335
|
-
fromCache = !!telemetryJSON["is_cache"];
|
|
336
|
-
} catch (e) {
|
|
337
|
-
this.logger.error("NativeBrokerPlugin: getAuthenticationResult - Error parsing telemetry data. Could not determine if response came from cache.", request.correlationId);
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
let idTokenClaims: IdTokenClaims;
|
|
341
|
-
try {
|
|
342
|
-
idTokenClaims = JSON.parse(authResult.idToken);
|
|
343
|
-
} catch (e) {
|
|
344
|
-
throw new Error("Unable to parse idToken claims");
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
const accountInfo = this.generateAccountInfo(authResult.account, idTokenClaims);
|
|
348
|
-
|
|
349
|
-
const result: AuthenticationResult = {
|
|
350
|
-
authority: request.authority,
|
|
351
|
-
uniqueId: idTokenClaims.oid || idTokenClaims.sub || "",
|
|
352
|
-
tenantId: idTokenClaims.tid || "",
|
|
353
|
-
scopes: authResult.grantedScopes.split(" "),
|
|
354
|
-
account: accountInfo,
|
|
355
|
-
idToken: authResult.rawIdToken,
|
|
356
|
-
idTokenClaims: idTokenClaims,
|
|
357
|
-
accessToken: authResult.accessToken,
|
|
358
|
-
fromCache: fromCache,
|
|
359
|
-
expiresOn: new Date(authResult.expiresOn
|
|
360
|
-
tokenType: authResult.isPopAuthorization ? AuthenticationScheme.POP : AuthenticationScheme.BEARER,
|
|
361
|
-
correlationId: request.correlationId,
|
|
362
|
-
fromNativeBroker: true
|
|
363
|
-
};
|
|
364
|
-
return result;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
private generateAccountInfo(account: Account, idTokenClaims?: IdTokenClaims): AccountInfo {
|
|
368
|
-
this.logger.trace("NativeBrokerPlugin - generateAccountInfo called");
|
|
369
|
-
|
|
370
|
-
const accountInfo: AccountInfo = {
|
|
371
|
-
homeAccountId: account.homeAccountId,
|
|
372
|
-
environment: account.environment,
|
|
373
|
-
tenantId: account.realm,
|
|
374
|
-
username: account.username,
|
|
375
|
-
localAccountId: account.localAccountId,
|
|
376
|
-
name: account.displayName,
|
|
377
|
-
idTokenClaims: idTokenClaims,
|
|
378
|
-
nativeAccountId: account.accountId
|
|
379
|
-
};
|
|
380
|
-
return accountInfo;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
private isMsalRuntimeError(result: Object): boolean {
|
|
384
|
-
return result.hasOwnProperty("errorCode") ||
|
|
385
|
-
result.hasOwnProperty("errorStatus") ||
|
|
386
|
-
result.hasOwnProperty("errorContext") ||
|
|
387
|
-
result.hasOwnProperty("errorTag");
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
private wrapError(error: Object): NativeAuthError | Object | null {
|
|
391
|
-
if (this.isMsalRuntimeError(error)) {
|
|
392
|
-
const { errorCode, errorStatus, errorContext, errorTag } = error as MsalRuntimeError;
|
|
393
|
-
switch (errorStatus) {
|
|
394
|
-
case ErrorStatus.InteractionRequired:
|
|
395
|
-
case ErrorStatus.AccountUnusable:
|
|
396
|
-
return new InteractionRequiredAuthError(ErrorCodes.INTERATION_REQUIRED_ERROR_CODE, errorContext);
|
|
397
|
-
case ErrorStatus.NoNetwork:
|
|
398
|
-
case ErrorStatus.NetworkTemporarilyUnavailable:
|
|
399
|
-
return ClientAuthError.createNoNetworkConnectivityError();
|
|
400
|
-
case ErrorStatus.ServerTemporarilyUnavailable:
|
|
401
|
-
return new ServerError(ErrorCodes.SERVER_UNAVAILABLE, errorContext);
|
|
402
|
-
case ErrorStatus.UserCanceled:
|
|
403
|
-
return ClientAuthError.createUserCanceledError();
|
|
404
|
-
case ErrorStatus.AuthorityUntrusted:
|
|
405
|
-
return ClientConfigurationError.createUntrustedAuthorityError();
|
|
406
|
-
case ErrorStatus.UserSwitched:
|
|
407
|
-
// Not an error case, if there's customer demand we can surface this as a response property
|
|
408
|
-
return null;
|
|
409
|
-
case ErrorStatus.AccountNotFound:
|
|
410
|
-
return ClientAuthError.createNoAccountFoundError();
|
|
411
|
-
default:
|
|
412
|
-
return new NativeAuthError(ErrorStatus[errorStatus], errorContext, errorCode, errorTag);
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
return error;
|
|
417
|
-
}
|
|
418
|
-
}
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { AccountInfo, AuthenticationResult, AuthenticationScheme, ClientAuthError, ClientConfigurationError, Constants, IdTokenClaims, INativeBrokerPlugin, InteractionRequiredAuthError, Logger, LoggerOptions, NativeRequest, NativeSignOutRequest, PromptValue, ServerError } from "@azure/msal-common";
|
|
7
|
+
import { msalNodeRuntime, Account, AuthParameters, AuthResult, ErrorStatus, MsalRuntimeError, ReadAccountResult, DiscoverAccountsResult, SignOutResult, LogLevel as MsalRuntimeLogLevel} from "@azure/msal-node-runtime";
|
|
8
|
+
import { ErrorCodes } from "../utils/Constants";
|
|
9
|
+
import { NativeAuthError } from "../error/NativeAuthError";
|
|
10
|
+
import { version, name } from "../packageMetadata";
|
|
11
|
+
|
|
12
|
+
export class NativeBrokerPlugin implements INativeBrokerPlugin {
|
|
13
|
+
private logger: Logger;
|
|
14
|
+
isBrokerAvailable: boolean;
|
|
15
|
+
|
|
16
|
+
constructor() {
|
|
17
|
+
const defaultLoggerOptions: LoggerOptions = {
|
|
18
|
+
loggerCallback: (): void => {
|
|
19
|
+
// Empty logger callback
|
|
20
|
+
},
|
|
21
|
+
piiLoggingEnabled: false
|
|
22
|
+
};
|
|
23
|
+
this.logger = new Logger(defaultLoggerOptions, name, version); // Default logger
|
|
24
|
+
this.isBrokerAvailable = msalNodeRuntime.StartupError ? false : true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
setLogger(loggerOptions: LoggerOptions): void {
|
|
28
|
+
this.logger = new Logger(loggerOptions, name, version);
|
|
29
|
+
const logCallback = (message: string, logLevel: MsalRuntimeLogLevel, containsPii: boolean) => {
|
|
30
|
+
switch(logLevel) {
|
|
31
|
+
case MsalRuntimeLogLevel.Trace:
|
|
32
|
+
if (containsPii) {
|
|
33
|
+
this.logger.tracePii(message);
|
|
34
|
+
} else {
|
|
35
|
+
this.logger.trace(message);
|
|
36
|
+
}
|
|
37
|
+
break;
|
|
38
|
+
case MsalRuntimeLogLevel.Debug:
|
|
39
|
+
if (containsPii) {
|
|
40
|
+
this.logger.tracePii(message);
|
|
41
|
+
} else {
|
|
42
|
+
this.logger.trace(message);
|
|
43
|
+
}
|
|
44
|
+
break;
|
|
45
|
+
case MsalRuntimeLogLevel.Info:
|
|
46
|
+
if (containsPii) {
|
|
47
|
+
this.logger.infoPii(message);
|
|
48
|
+
} else {
|
|
49
|
+
this.logger.info(message);
|
|
50
|
+
}
|
|
51
|
+
break;
|
|
52
|
+
case MsalRuntimeLogLevel.Warning:
|
|
53
|
+
if (containsPii) {
|
|
54
|
+
this.logger.warningPii(message);
|
|
55
|
+
} else {
|
|
56
|
+
this.logger.warning(message);
|
|
57
|
+
}
|
|
58
|
+
break;
|
|
59
|
+
case MsalRuntimeLogLevel.Error:
|
|
60
|
+
if (containsPii) {
|
|
61
|
+
this.logger.errorPii(message);
|
|
62
|
+
} else {
|
|
63
|
+
this.logger.error(message);
|
|
64
|
+
}
|
|
65
|
+
break;
|
|
66
|
+
case MsalRuntimeLogLevel.Fatal:
|
|
67
|
+
if (containsPii) {
|
|
68
|
+
this.logger.errorPii(message);
|
|
69
|
+
} else {
|
|
70
|
+
this.logger.error(message);
|
|
71
|
+
}
|
|
72
|
+
break;
|
|
73
|
+
default:
|
|
74
|
+
if (containsPii) {
|
|
75
|
+
this.logger.infoPii(message);
|
|
76
|
+
} else {
|
|
77
|
+
this.logger.info(message);
|
|
78
|
+
}
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
try {
|
|
83
|
+
msalNodeRuntime.RegisterLogger(logCallback, loggerOptions.piiLoggingEnabled);
|
|
84
|
+
} catch (e) {
|
|
85
|
+
const wrappedError = this.wrapError(e);
|
|
86
|
+
if (wrappedError) {
|
|
87
|
+
throw wrappedError;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async getAccountById(accountId: string, correlationId: string): Promise<AccountInfo> {
|
|
93
|
+
this.logger.trace("NativeBrokerPlugin - getAccountById called", correlationId);
|
|
94
|
+
const readAccountResult = await this.readAccountById(accountId, correlationId);
|
|
95
|
+
return this.generateAccountInfo(readAccountResult.account);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async getAllAccounts(clientId:string, correlationId: string): Promise<AccountInfo[]> {
|
|
99
|
+
this.logger.trace("NativeBrokerPlugin - getAllAccounts called", correlationId);
|
|
100
|
+
return new Promise((resolve, reject) => {
|
|
101
|
+
const resultCallback = (result: DiscoverAccountsResult) => {
|
|
102
|
+
try {
|
|
103
|
+
result.CheckError();
|
|
104
|
+
} catch (e) {
|
|
105
|
+
const wrappedError = this.wrapError(e);
|
|
106
|
+
if (wrappedError) {
|
|
107
|
+
reject(wrappedError);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const accountInfoResult = [];
|
|
113
|
+
result.accounts.forEach((account: Account) => {
|
|
114
|
+
accountInfoResult.push(this.generateAccountInfo(account));
|
|
115
|
+
});
|
|
116
|
+
resolve(accountInfoResult);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
msalNodeRuntime.DiscoverAccountsAsync(clientId, correlationId, resultCallback);
|
|
121
|
+
} catch (e) {
|
|
122
|
+
const wrappedError = this.wrapError(e);
|
|
123
|
+
if (wrappedError) {
|
|
124
|
+
reject(wrappedError);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async acquireTokenSilent(request: NativeRequest): Promise<AuthenticationResult> {
|
|
131
|
+
this.logger.trace("NativeBrokerPlugin - acquireTokenSilent called", request.correlationId);
|
|
132
|
+
const authParams = this.generateRequestParameters(request);
|
|
133
|
+
const account = await this.getAccount(request);
|
|
134
|
+
|
|
135
|
+
return new Promise((resolve: (value: AuthenticationResult) => void, reject) => {
|
|
136
|
+
const resultCallback = (result: AuthResult) => {
|
|
137
|
+
try {
|
|
138
|
+
result.CheckError();
|
|
139
|
+
} catch (e) {
|
|
140
|
+
const wrappedError = this.wrapError(e);
|
|
141
|
+
if (wrappedError) {
|
|
142
|
+
reject(wrappedError);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const authenticationResult = this.getAuthenticationResult(request, result);
|
|
147
|
+
resolve(authenticationResult);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
if (account) {
|
|
152
|
+
msalNodeRuntime.AcquireTokenSilentlyAsync(authParams, request.correlationId, account, resultCallback);
|
|
153
|
+
} else {
|
|
154
|
+
msalNodeRuntime.SignInSilentlyAsync(authParams, request.correlationId, resultCallback);
|
|
155
|
+
}
|
|
156
|
+
} catch (e) {
|
|
157
|
+
const wrappedError = this.wrapError(e);
|
|
158
|
+
if (wrappedError) {
|
|
159
|
+
reject(wrappedError);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async acquireTokenInteractive(request: NativeRequest, providedWindowHandle?: Buffer): Promise<AuthenticationResult> {
|
|
166
|
+
this.logger.trace("NativeBrokerPlugin - acquireTokenInteractive called", request.correlationId);
|
|
167
|
+
const authParams = this.generateRequestParameters(request);
|
|
168
|
+
const account = await this.getAccount(request);
|
|
169
|
+
const windowHandle = providedWindowHandle || Buffer.from([0]);
|
|
170
|
+
|
|
171
|
+
return new Promise((resolve: (value: AuthenticationResult) => void, reject) => {
|
|
172
|
+
const resultCallback = (result: AuthResult) => {
|
|
173
|
+
try {
|
|
174
|
+
result.CheckError();
|
|
175
|
+
} catch (e) {
|
|
176
|
+
const wrappedError = this.wrapError(e);
|
|
177
|
+
if (wrappedError) {
|
|
178
|
+
reject(wrappedError);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const authenticationResult = this.getAuthenticationResult(request, result);
|
|
183
|
+
resolve(authenticationResult);
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
switch (request.prompt) {
|
|
188
|
+
case PromptValue.LOGIN:
|
|
189
|
+
case PromptValue.SELECT_ACCOUNT:
|
|
190
|
+
case PromptValue.CREATE:
|
|
191
|
+
this.logger.info("Calling native interop SignInInteractively API", request.correlationId);
|
|
192
|
+
const loginHint = request.loginHint || Constants.EMPTY_STRING;
|
|
193
|
+
msalNodeRuntime.SignInInteractivelyAsync(windowHandle, authParams, request.correlationId, loginHint, resultCallback);
|
|
194
|
+
break;
|
|
195
|
+
case PromptValue.NONE:
|
|
196
|
+
if (account) {
|
|
197
|
+
this.logger.info("Calling native interop AcquireTokenSilently API", request.correlationId);
|
|
198
|
+
msalNodeRuntime.AcquireTokenSilentlyAsync(authParams, request.correlationId, account, resultCallback);
|
|
199
|
+
} else {
|
|
200
|
+
this.logger.info("Calling native interop SignInSilently API", request.correlationId);
|
|
201
|
+
msalNodeRuntime.SignInSilentlyAsync(authParams, request.correlationId, resultCallback);
|
|
202
|
+
}
|
|
203
|
+
break;
|
|
204
|
+
default:
|
|
205
|
+
if (account) {
|
|
206
|
+
this.logger.info("Calling native interop AcquireTokenInteractively API", request.correlationId);
|
|
207
|
+
msalNodeRuntime.AcquireTokenInteractivelyAsync(windowHandle, authParams, request.correlationId, account, resultCallback);
|
|
208
|
+
} else {
|
|
209
|
+
this.logger.info("Calling native interop SignIn API", request.correlationId);
|
|
210
|
+
const loginHint = request.loginHint || Constants.EMPTY_STRING;
|
|
211
|
+
msalNodeRuntime.SignInAsync(windowHandle, authParams, request.correlationId, loginHint, resultCallback);
|
|
212
|
+
}
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
} catch (e) {
|
|
216
|
+
const wrappedError = this.wrapError(e);
|
|
217
|
+
if (wrappedError) {
|
|
218
|
+
reject(wrappedError);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async signOut(request: NativeSignOutRequest): Promise<void> {
|
|
225
|
+
this.logger.trace("NativeBrokerPlugin - signOut called", request.correlationId);
|
|
226
|
+
|
|
227
|
+
const account = await this.getAccount(request);
|
|
228
|
+
if (!account) {
|
|
229
|
+
throw ClientAuthError.createNoAccountFoundError();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return new Promise((resolve, reject) => {
|
|
233
|
+
const resultCallback = (result: SignOutResult) => {
|
|
234
|
+
try {
|
|
235
|
+
result.CheckError();
|
|
236
|
+
} catch (e) {
|
|
237
|
+
const wrappedError = this.wrapError(e);
|
|
238
|
+
if (wrappedError) {
|
|
239
|
+
reject(wrappedError);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
resolve();
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
try {
|
|
247
|
+
msalNodeRuntime.SignOutSilentlyAsync(request.clientId, request.correlationId, account, resultCallback);
|
|
248
|
+
} catch (e) {
|
|
249
|
+
const wrappedError = this.wrapError(e);
|
|
250
|
+
if (wrappedError) {
|
|
251
|
+
reject(wrappedError);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private async getAccount(request: NativeRequest | NativeSignOutRequest): Promise<Account|null> {
|
|
258
|
+
if (request.accountId) {
|
|
259
|
+
const readAccountResult = await this.readAccountById(request.accountId, request.correlationId);
|
|
260
|
+
return readAccountResult.account;
|
|
261
|
+
}
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
private async readAccountById(accountId: string, correlationId: string): Promise<ReadAccountResult> {
|
|
266
|
+
this.logger.trace("NativeBrokerPlugin - readAccountById called", correlationId);
|
|
267
|
+
|
|
268
|
+
return new Promise((resolve, reject) => {
|
|
269
|
+
const resultCallback = (result: ReadAccountResult) => {
|
|
270
|
+
try {
|
|
271
|
+
result.CheckError();
|
|
272
|
+
} catch (e) {
|
|
273
|
+
const wrappedError = this.wrapError(e);
|
|
274
|
+
if (wrappedError) {
|
|
275
|
+
reject(wrappedError);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
resolve(result);
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
try {
|
|
283
|
+
msalNodeRuntime.ReadAccountByIdAsync(accountId, correlationId, resultCallback);
|
|
284
|
+
} catch (e) {
|
|
285
|
+
const wrappedError = this.wrapError(e);
|
|
286
|
+
if (wrappedError) {
|
|
287
|
+
reject(wrappedError);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
private generateRequestParameters(request: NativeRequest): AuthParameters {
|
|
294
|
+
this.logger.trace("NativeBrokerPlugin - generateRequestParameters called", request.correlationId);
|
|
295
|
+
const authParams = new msalNodeRuntime.AuthParameters();
|
|
296
|
+
|
|
297
|
+
try{
|
|
298
|
+
authParams.CreateAuthParameters(request.clientId, request.authority);
|
|
299
|
+
authParams.SetRedirectUri(request.redirectUri);
|
|
300
|
+
authParams.SetRequestedScopes(request.scopes.join(" "));
|
|
301
|
+
|
|
302
|
+
if (request.claims) {
|
|
303
|
+
authParams.SetDecodedClaims(request.claims);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (request.authenticationScheme === AuthenticationScheme.POP) {
|
|
307
|
+
if (!request.resourceRequestMethod || !request.resourceRequestUri || !request.shrNonce) {
|
|
308
|
+
throw new Error("Authentication Scheme set to POP but one or more of the following parameters are missing: resourceRequestMethod, resourceRequestUri, shrNonce");
|
|
309
|
+
}
|
|
310
|
+
const resourceUrl = new URL(request.resourceRequestUri);
|
|
311
|
+
authParams.SetPopParams(request.resourceRequestMethod, resourceUrl.host, resourceUrl.pathname, request.shrNonce);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (request.extraParameters) {
|
|
315
|
+
Object.keys(request.extraParameters).forEach((key) => {
|
|
316
|
+
authParams.SetAdditionalParameter(key, request.extraParameters[key]);
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
} catch (e) {
|
|
320
|
+
const wrappedError = this.wrapError(e);
|
|
321
|
+
if (wrappedError) {
|
|
322
|
+
throw wrappedError;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return authParams;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
private getAuthenticationResult(request: NativeRequest, authResult: AuthResult): AuthenticationResult {
|
|
330
|
+
this.logger.trace("NativeBrokerPlugin - getAuthenticationResult called", request.correlationId);
|
|
331
|
+
|
|
332
|
+
let fromCache: boolean = false;
|
|
333
|
+
try {
|
|
334
|
+
const telemetryJSON = JSON.parse(authResult.telemetryData);
|
|
335
|
+
fromCache = !!telemetryJSON["is_cache"];
|
|
336
|
+
} catch (e) {
|
|
337
|
+
this.logger.error("NativeBrokerPlugin: getAuthenticationResult - Error parsing telemetry data. Could not determine if response came from cache.", request.correlationId);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
let idTokenClaims: IdTokenClaims;
|
|
341
|
+
try {
|
|
342
|
+
idTokenClaims = JSON.parse(authResult.idToken);
|
|
343
|
+
} catch (e) {
|
|
344
|
+
throw new Error("Unable to parse idToken claims");
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const accountInfo = this.generateAccountInfo(authResult.account, idTokenClaims);
|
|
348
|
+
|
|
349
|
+
const result: AuthenticationResult = {
|
|
350
|
+
authority: request.authority,
|
|
351
|
+
uniqueId: idTokenClaims.oid || idTokenClaims.sub || "",
|
|
352
|
+
tenantId: idTokenClaims.tid || "",
|
|
353
|
+
scopes: authResult.grantedScopes.split(" "),
|
|
354
|
+
account: accountInfo,
|
|
355
|
+
idToken: authResult.rawIdToken,
|
|
356
|
+
idTokenClaims: idTokenClaims,
|
|
357
|
+
accessToken: authResult.accessToken,
|
|
358
|
+
fromCache: fromCache,
|
|
359
|
+
expiresOn: new Date(authResult.expiresOn),
|
|
360
|
+
tokenType: authResult.isPopAuthorization ? AuthenticationScheme.POP : AuthenticationScheme.BEARER,
|
|
361
|
+
correlationId: request.correlationId,
|
|
362
|
+
fromNativeBroker: true
|
|
363
|
+
};
|
|
364
|
+
return result;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
private generateAccountInfo(account: Account, idTokenClaims?: IdTokenClaims): AccountInfo {
|
|
368
|
+
this.logger.trace("NativeBrokerPlugin - generateAccountInfo called");
|
|
369
|
+
|
|
370
|
+
const accountInfo: AccountInfo = {
|
|
371
|
+
homeAccountId: account.homeAccountId,
|
|
372
|
+
environment: account.environment,
|
|
373
|
+
tenantId: account.realm,
|
|
374
|
+
username: account.username,
|
|
375
|
+
localAccountId: account.localAccountId,
|
|
376
|
+
name: account.displayName,
|
|
377
|
+
idTokenClaims: idTokenClaims,
|
|
378
|
+
nativeAccountId: account.accountId
|
|
379
|
+
};
|
|
380
|
+
return accountInfo;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
private isMsalRuntimeError(result: Object): boolean {
|
|
384
|
+
return result.hasOwnProperty("errorCode") ||
|
|
385
|
+
result.hasOwnProperty("errorStatus") ||
|
|
386
|
+
result.hasOwnProperty("errorContext") ||
|
|
387
|
+
result.hasOwnProperty("errorTag");
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
private wrapError(error: Object): NativeAuthError | Object | null {
|
|
391
|
+
if (this.isMsalRuntimeError(error)) {
|
|
392
|
+
const { errorCode, errorStatus, errorContext, errorTag } = error as MsalRuntimeError;
|
|
393
|
+
switch (errorStatus) {
|
|
394
|
+
case ErrorStatus.InteractionRequired:
|
|
395
|
+
case ErrorStatus.AccountUnusable:
|
|
396
|
+
return new InteractionRequiredAuthError(ErrorCodes.INTERATION_REQUIRED_ERROR_CODE, errorContext);
|
|
397
|
+
case ErrorStatus.NoNetwork:
|
|
398
|
+
case ErrorStatus.NetworkTemporarilyUnavailable:
|
|
399
|
+
return ClientAuthError.createNoNetworkConnectivityError();
|
|
400
|
+
case ErrorStatus.ServerTemporarilyUnavailable:
|
|
401
|
+
return new ServerError(ErrorCodes.SERVER_UNAVAILABLE, errorContext);
|
|
402
|
+
case ErrorStatus.UserCanceled:
|
|
403
|
+
return ClientAuthError.createUserCanceledError();
|
|
404
|
+
case ErrorStatus.AuthorityUntrusted:
|
|
405
|
+
return ClientConfigurationError.createUntrustedAuthorityError();
|
|
406
|
+
case ErrorStatus.UserSwitched:
|
|
407
|
+
// Not an error case, if there's customer demand we can surface this as a response property
|
|
408
|
+
return null;
|
|
409
|
+
case ErrorStatus.AccountNotFound:
|
|
410
|
+
return ClientAuthError.createNoAccountFoundError();
|
|
411
|
+
default:
|
|
412
|
+
return new NativeAuthError(ErrorStatus[errorStatus], errorContext, errorCode, errorTag);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return error;
|
|
417
|
+
}
|
|
418
|
+
}
|