2241 lines
89 KiB
JavaScript
2241 lines
89 KiB
JavaScript
/*! @azure/msal-node-extensions v1.5.17 2025-07-08 */
|
|
'use strict';
|
|
'use strict';
|
|
|
|
var fs = require('fs');
|
|
var process$1 = require('process');
|
|
var path = require('path');
|
|
var module$1 = require('module');
|
|
var keytar = require('keytar');
|
|
var msalNodeRuntime = require('@azure/msal-node-runtime');
|
|
|
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
/*
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License.
|
|
*/
|
|
const Constants$1 = {
|
|
/**
|
|
* An existing file was the target of an operation that required that the target not exist
|
|
*/
|
|
EEXIST_ERROR: "EEXIST",
|
|
/**
|
|
* No such file or directory: Commonly raised by fs operations to indicate that a component
|
|
* of the specified pathname does not exist. No entity (file or directory) could be found
|
|
* by the given path
|
|
*/
|
|
ENOENT_ERROR: "ENOENT",
|
|
/**
|
|
* Operation not permitted. An attempt was made to perform an operation that requires
|
|
* elevated privileges.
|
|
*/
|
|
EPERM_ERROR: "EPERM",
|
|
/**
|
|
* Default service name for using MSAL Keytar
|
|
*/
|
|
DEFAULT_SERVICE_NAME: "msal-node-extensions",
|
|
/**
|
|
* Test data used to verify underlying persistence mechanism
|
|
*/
|
|
PERSISTENCE_TEST_DATA: "Dummy data to verify underlying persistence mechanism",
|
|
/**
|
|
* This is the value of a the guid if the process is being ran by the root user
|
|
*/
|
|
LINUX_ROOT_USER_GUID: 0,
|
|
/**
|
|
* List of environment variables
|
|
*/
|
|
ENVIRONMENT: {
|
|
HOME: "HOME",
|
|
LOGNAME: "LOGNAME",
|
|
USER: "USER",
|
|
LNAME: "LNAME",
|
|
USERNAME: "USERNAME",
|
|
PLATFORM: "platform",
|
|
LOCAL_APPLICATION_DATA: "LOCALAPPDATA",
|
|
},
|
|
// Name of the default cache file
|
|
DEFAULT_CACHE_FILE_NAME: "cache.json",
|
|
};
|
|
const Platform = {
|
|
WINDOWS: "win32",
|
|
LINUX: "linux",
|
|
MACOS: "darwin",
|
|
};
|
|
const ErrorCodes = {
|
|
INTERATION_REQUIRED_ERROR_CODE: "interaction_required",
|
|
SERVER_UNAVAILABLE: "server_unavailable",
|
|
UNKNOWN: "unknown_error",
|
|
};
|
|
|
|
/*
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License.
|
|
*/
|
|
/**
|
|
* Error thrown when trying to write MSAL cache to persistence.
|
|
*/
|
|
class PersistenceError extends Error {
|
|
constructor(errorCode, errorMessage) {
|
|
const errorString = errorMessage
|
|
? `${errorCode}: ${errorMessage}`
|
|
: errorCode;
|
|
super(errorString);
|
|
Object.setPrototypeOf(this, PersistenceError.prototype);
|
|
this.errorCode = errorCode;
|
|
this.errorMessage = errorMessage;
|
|
this.name = "PersistenceError";
|
|
}
|
|
/**
|
|
* Error thrown when trying to access the file system.
|
|
*/
|
|
static createFileSystemError(errorCode, errorMessage) {
|
|
return new PersistenceError(errorCode, errorMessage);
|
|
}
|
|
/**
|
|
* Error thrown when trying to write, load, or delete data from secret service on linux.
|
|
* Libsecret is used to access secret service.
|
|
*/
|
|
static createLibSecretError(errorMessage) {
|
|
return new PersistenceError("GnomeKeyringError", errorMessage);
|
|
}
|
|
/**
|
|
* Error thrown when trying to write, load, or delete data from keychain on macOs.
|
|
*/
|
|
static createKeychainPersistenceError(errorMessage) {
|
|
return new PersistenceError("KeychainError", errorMessage);
|
|
}
|
|
/**
|
|
* Error thrown when trying to encrypt or decrypt data using DPAPI on Windows.
|
|
*/
|
|
static createFilePersistenceWithDPAPIError(errorMessage) {
|
|
return new PersistenceError("DPAPIEncryptedFileError", errorMessage);
|
|
}
|
|
/**
|
|
* Error thrown when using the cross platform lock.
|
|
*/
|
|
static createCrossPlatformLockError(errorMessage) {
|
|
return new PersistenceError("CrossPlatformLockError", errorMessage);
|
|
}
|
|
/**
|
|
* Throw cache persistence error
|
|
*
|
|
* @param errorMessage string
|
|
* @returns PersistenceError
|
|
*/
|
|
static createCachePersistenceError(errorMessage) {
|
|
return new PersistenceError("CachePersistenceError", errorMessage);
|
|
}
|
|
/**
|
|
* Throw unsupported error
|
|
*
|
|
* @param errorMessage string
|
|
* @returns PersistenceError
|
|
*/
|
|
static createNotSupportedError(errorMessage) {
|
|
return new PersistenceError("NotSupportedError", errorMessage);
|
|
}
|
|
/**
|
|
* Throw persistence not verified error
|
|
*
|
|
* @param errorMessage string
|
|
* @returns PersistenceError
|
|
*/
|
|
static createPersistenceNotVerifiedError(errorMessage) {
|
|
return new PersistenceError("PersistenceNotVerifiedError", errorMessage);
|
|
}
|
|
/**
|
|
* Throw persistence creation validation error
|
|
*
|
|
* @param errorMessage string
|
|
* @returns PersistenceError
|
|
*/
|
|
static createPersistenceNotValidatedError(errorMessage) {
|
|
return new PersistenceError("PersistenceNotValidatedError", errorMessage);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License.
|
|
*/
|
|
/**
|
|
* Returns whether or not the given object is a Node.js error
|
|
*/
|
|
const isNodeError = (error) => {
|
|
return !!error && typeof error === "object" && error.hasOwnProperty("code");
|
|
};
|
|
|
|
/*
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License.
|
|
*/
|
|
/**
|
|
* Cross-process lock that works on all platforms.
|
|
*/
|
|
class CrossPlatformLock {
|
|
constructor(lockFilePath, logger, lockOptions) {
|
|
this.lockFilePath = lockFilePath;
|
|
this.retryNumber = lockOptions ? lockOptions.retryNumber : 500;
|
|
this.retryDelay = lockOptions ? lockOptions.retryDelay : 100;
|
|
this.logger = logger;
|
|
}
|
|
/**
|
|
* Locks cache from read or writes by creating file with same path and name as
|
|
* cache file but with .lockfile extension. If another process has already created
|
|
* the lockfile, will back off and retry based on configuration settings set by CrossPlatformLockOptions
|
|
*/
|
|
async lock() {
|
|
for (let tryCount = 0; tryCount < this.retryNumber; tryCount++) {
|
|
try {
|
|
this.logger.info(`Pid ${process$1.pid} trying to acquire lock`);
|
|
this.lockFileHandle = await fs.promises.open(this.lockFilePath, "wx+");
|
|
this.logger.info(`Pid ${process$1.pid} acquired lock`);
|
|
await this.lockFileHandle.write(process$1.pid.toString());
|
|
return;
|
|
}
|
|
catch (err) {
|
|
if (isNodeError(err)) {
|
|
if (err.code === Constants$1.EEXIST_ERROR ||
|
|
err.code === Constants$1.EPERM_ERROR) {
|
|
this.logger.info(err.message);
|
|
await this.sleep(this.retryDelay);
|
|
}
|
|
else {
|
|
this.logger.error(`${process$1.pid} was not able to acquire lock. Ran into error: ${err.message}`);
|
|
throw PersistenceError.createCrossPlatformLockError(err.message);
|
|
}
|
|
}
|
|
else {
|
|
throw err;
|
|
}
|
|
}
|
|
}
|
|
this.logger.error(`${process$1.pid} was not able to acquire lock. Exceeded amount of retries set in the options`);
|
|
throw PersistenceError.createCrossPlatformLockError("Not able to acquire lock. Exceeded amount of retries set in options");
|
|
}
|
|
/**
|
|
* unlocks cache file by deleting .lockfile.
|
|
*/
|
|
async unlock() {
|
|
try {
|
|
if (this.lockFileHandle) {
|
|
// if we have a file handle to the .lockfile, delete lock file
|
|
await fs.promises.unlink(this.lockFilePath);
|
|
await this.lockFileHandle.close();
|
|
this.logger.info("lockfile deleted");
|
|
}
|
|
else {
|
|
this.logger.warning("lockfile handle does not exist, so lockfile could not be deleted");
|
|
}
|
|
}
|
|
catch (err) {
|
|
if (isNodeError(err)) {
|
|
if (err.code === Constants$1.ENOENT_ERROR) {
|
|
this.logger.info("Tried to unlock but lockfile does not exist");
|
|
}
|
|
else {
|
|
this.logger.error(`${process$1.pid} was not able to release lock. Ran into error: ${err.message}`);
|
|
throw PersistenceError.createCrossPlatformLockError(err.message);
|
|
}
|
|
}
|
|
else {
|
|
throw err;
|
|
}
|
|
}
|
|
}
|
|
sleep(ms) {
|
|
return new Promise((resolve) => {
|
|
setTimeout(resolve, ms);
|
|
});
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License.
|
|
*/
|
|
/**
|
|
* MSAL cache plugin which enables callers to write the MSAL cache to disk on Windows,
|
|
* macOs, and Linux.
|
|
*
|
|
* - Persistence can be one of:
|
|
* - FilePersistence: Writes and reads from an unencrypted file. Can be used on Windows,
|
|
* macOs, or Linux.
|
|
* - FilePersistenceWithDataProtection: Used on Windows, writes and reads from file encrypted
|
|
* with windows dpapi-addon.
|
|
* - KeychainPersistence: Used on macOs, writes and reads from keychain.
|
|
* - LibSecretPersistence: Used on linux, writes and reads from secret service API. Requires
|
|
* libsecret be installed.
|
|
*/
|
|
class PersistenceCachePlugin {
|
|
constructor(persistence, lockOptions) {
|
|
this.persistence = persistence;
|
|
// initialize logger
|
|
this.logger = persistence.getLogger();
|
|
// create file lock
|
|
this.lockFilePath = `${this.persistence.getFilePath()}.lockfile`;
|
|
this.crossPlatformLock = new CrossPlatformLock(this.lockFilePath, this.logger, lockOptions);
|
|
// initialize default values
|
|
this.lastSync = 0;
|
|
this.currentCache = null;
|
|
}
|
|
/**
|
|
* Reads from storage and saves an in-memory copy. If persistence has not been updated
|
|
* since last time data was read, in memory copy is used.
|
|
*
|
|
* If cacheContext.cacheHasChanged === true, then file lock is created and not deleted until
|
|
* afterCacheAccess() is called, to prevent the cache file from changing in between
|
|
* beforeCacheAccess() and afterCacheAccess().
|
|
*/
|
|
async beforeCacheAccess(cacheContext) {
|
|
this.logger.info("Executing before cache access");
|
|
const reloadNecessary = await this.persistence.reloadNecessary(this.lastSync);
|
|
if (!reloadNecessary && this.currentCache !== null) {
|
|
if (cacheContext.cacheHasChanged) {
|
|
this.logger.verbose("Cache context has changed");
|
|
await this.crossPlatformLock.lock();
|
|
}
|
|
return;
|
|
}
|
|
try {
|
|
this.logger.info(`Reload necessary. Last sync time: ${this.lastSync}`);
|
|
await this.crossPlatformLock.lock();
|
|
this.currentCache = await this.persistence.load();
|
|
this.lastSync = new Date().getTime();
|
|
if (this.currentCache) {
|
|
cacheContext.tokenCache.deserialize(this.currentCache);
|
|
}
|
|
else {
|
|
this.logger.info("Cache empty.");
|
|
}
|
|
this.logger.info(`Last sync time updated to: ${this.lastSync}`);
|
|
}
|
|
finally {
|
|
if (!cacheContext.cacheHasChanged) {
|
|
await this.crossPlatformLock.unlock();
|
|
this.logger.info(`Pid ${process$1.pid} released lock`);
|
|
}
|
|
else {
|
|
this.logger.info(`Pid ${process$1.pid} beforeCacheAccess did not release lock`);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Writes to storage if MSAL in memory copy of cache has been changed.
|
|
*/
|
|
async afterCacheAccess(cacheContext) {
|
|
this.logger.info("Executing after cache access");
|
|
try {
|
|
if (cacheContext.cacheHasChanged) {
|
|
this.logger.info("Msal in-memory cache has changed. Writing changes to persistence");
|
|
this.currentCache = cacheContext.tokenCache.serialize();
|
|
await this.persistence.save(this.currentCache);
|
|
}
|
|
else {
|
|
this.logger.info("Msal in-memory cache has not changed. Did not write to persistence");
|
|
}
|
|
}
|
|
finally {
|
|
await this.crossPlatformLock.unlock();
|
|
this.logger.info(`Pid ${process$1.pid} afterCacheAccess released lock`);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*! @azure/msal-common v15.8.1 2025-07-08 */
|
|
/*
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License.
|
|
*/
|
|
const Constants = {
|
|
LIBRARY_NAME: "MSAL.JS",
|
|
SKU: "msal.js.common",
|
|
// Prefix for all library cache entries
|
|
CACHE_PREFIX: "msal",
|
|
// default authority
|
|
DEFAULT_AUTHORITY: "https://login.microsoftonline.com/common/",
|
|
DEFAULT_AUTHORITY_HOST: "login.microsoftonline.com",
|
|
DEFAULT_COMMON_TENANT: "common",
|
|
// ADFS String
|
|
ADFS: "adfs",
|
|
DSTS: "dstsv2",
|
|
// Default AAD Instance Discovery Endpoint
|
|
AAD_INSTANCE_DISCOVERY_ENDPT: "https://login.microsoftonline.com/common/discovery/instance?api-version=1.1&authorization_endpoint=",
|
|
// CIAM URL
|
|
CIAM_AUTH_URL: ".ciamlogin.com",
|
|
AAD_TENANT_DOMAIN_SUFFIX: ".onmicrosoft.com",
|
|
// Resource delimiter - used for certain cache entries
|
|
RESOURCE_DELIM: "|",
|
|
// Placeholder for non-existent account ids/objects
|
|
NO_ACCOUNT: "NO_ACCOUNT",
|
|
// Claims
|
|
CLAIMS: "claims",
|
|
// Consumer UTID
|
|
CONSUMER_UTID: "9188040d-6c67-4c5b-b112-36a304b66dad",
|
|
// Default scopes
|
|
OPENID_SCOPE: "openid",
|
|
PROFILE_SCOPE: "profile",
|
|
OFFLINE_ACCESS_SCOPE: "offline_access",
|
|
EMAIL_SCOPE: "email",
|
|
CODE_GRANT_TYPE: "authorization_code",
|
|
RT_GRANT_TYPE: "refresh_token",
|
|
S256_CODE_CHALLENGE_METHOD: "S256",
|
|
URL_FORM_CONTENT_TYPE: "application/x-www-form-urlencoded;charset=utf-8",
|
|
AUTHORIZATION_PENDING: "authorization_pending",
|
|
NOT_DEFINED: "not_defined",
|
|
EMPTY_STRING: "",
|
|
NOT_APPLICABLE: "N/A",
|
|
NOT_AVAILABLE: "Not Available",
|
|
FORWARD_SLASH: "/",
|
|
IMDS_ENDPOINT: "http://169.254.169.254/metadata/instance/compute/location",
|
|
IMDS_VERSION: "2020-06-01",
|
|
IMDS_TIMEOUT: 2000,
|
|
AZURE_REGION_AUTO_DISCOVER_FLAG: "TryAutoDetect",
|
|
REGIONAL_AUTH_PUBLIC_CLOUD_SUFFIX: "login.microsoft.com",
|
|
KNOWN_PUBLIC_CLOUDS: [
|
|
"login.microsoftonline.com",
|
|
"login.windows.net",
|
|
"login.microsoft.com",
|
|
"sts.windows.net",
|
|
],
|
|
SHR_NONCE_VALIDITY: 240,
|
|
INVALID_INSTANCE: "invalid_instance",
|
|
};
|
|
/**
|
|
* we considered making this "enum" in the request instead of string, however it looks like the allowed list of
|
|
* prompt values kept changing over past couple of years. There are some undocumented prompt values for some
|
|
* internal partners too, hence the choice of generic "string" type instead of the "enum"
|
|
*/
|
|
const PromptValue = {
|
|
LOGIN: "login",
|
|
SELECT_ACCOUNT: "select_account",
|
|
CONSENT: "consent",
|
|
NONE: "none",
|
|
CREATE: "create",
|
|
NO_SESSION: "no_session",
|
|
};
|
|
/**
|
|
* Separators used in cache
|
|
*/
|
|
const Separators = {
|
|
CACHE_KEY_SEPARATOR: "-"};
|
|
const SERVER_TELEM_CONSTANTS = {
|
|
SCHEMA_VERSION: 5,
|
|
MAX_LAST_HEADER_BYTES: 330,
|
|
MAX_CACHED_ERRORS: 50,
|
|
CACHE_KEY: "server-telemetry",
|
|
CATEGORY_SEPARATOR: "|",
|
|
VALUE_SEPARATOR: ",",
|
|
OVERFLOW_TRUE: "1",
|
|
OVERFLOW_FALSE: "0",
|
|
UNKNOWN_ERROR: "unknown_error",
|
|
};
|
|
/**
|
|
* Type of the authentication request
|
|
*/
|
|
const AuthenticationScheme = {
|
|
BEARER: "Bearer",
|
|
POP: "pop",
|
|
SSH: "ssh-cert",
|
|
};
|
|
/**
|
|
* Specifies the reason for fetching the access token from the identity provider
|
|
*/
|
|
const CacheOutcome = {
|
|
// When a token is found in the cache or the cache is not supposed to be hit when making the request
|
|
NOT_APPLICABLE: "0"};
|
|
|
|
/*! @azure/msal-common v15.8.1 2025-07-08 */
|
|
/**
|
|
* General error class thrown by the MSAL.js library.
|
|
*/
|
|
class AuthError extends Error {
|
|
constructor(errorCode, errorMessage, suberror) {
|
|
const errorString = errorMessage
|
|
? `${errorCode}: ${errorMessage}`
|
|
: errorCode;
|
|
super(errorString);
|
|
Object.setPrototypeOf(this, AuthError.prototype);
|
|
this.errorCode = errorCode || Constants.EMPTY_STRING;
|
|
this.errorMessage = errorMessage || Constants.EMPTY_STRING;
|
|
this.subError = suberror || Constants.EMPTY_STRING;
|
|
this.name = "AuthError";
|
|
}
|
|
setCorrelationId(correlationId) {
|
|
this.correlationId = correlationId;
|
|
}
|
|
}
|
|
|
|
/*! @azure/msal-common v15.8.1 2025-07-08 */
|
|
/*
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License.
|
|
*/
|
|
const clientInfoDecodingError = "client_info_decoding_error";
|
|
const clientInfoEmptyError = "client_info_empty_error";
|
|
const tokenParsingError = "token_parsing_error";
|
|
const nullOrEmptyToken = "null_or_empty_token";
|
|
const endpointResolutionError = "endpoints_resolution_error";
|
|
const networkError = "network_error";
|
|
const openIdConfigError = "openid_config_error";
|
|
const hashNotDeserialized = "hash_not_deserialized";
|
|
const invalidState = "invalid_state";
|
|
const stateMismatch = "state_mismatch";
|
|
const stateNotFound = "state_not_found";
|
|
const nonceMismatch = "nonce_mismatch";
|
|
const authTimeNotFound = "auth_time_not_found";
|
|
const maxAgeTranspired = "max_age_transpired";
|
|
const multipleMatchingTokens = "multiple_matching_tokens";
|
|
const multipleMatchingAccounts = "multiple_matching_accounts";
|
|
const multipleMatchingAppMetadata = "multiple_matching_appMetadata";
|
|
const requestCannotBeMade = "request_cannot_be_made";
|
|
const cannotRemoveEmptyScope = "cannot_remove_empty_scope";
|
|
const cannotAppendScopeSet = "cannot_append_scopeset";
|
|
const emptyInputScopeSet = "empty_input_scopeset";
|
|
const deviceCodePollingCancelled = "device_code_polling_cancelled";
|
|
const deviceCodeExpired = "device_code_expired";
|
|
const deviceCodeUnknownError = "device_code_unknown_error";
|
|
const noAccountInSilentRequest = "no_account_in_silent_request";
|
|
const invalidCacheRecord = "invalid_cache_record";
|
|
const invalidCacheEnvironment = "invalid_cache_environment";
|
|
const noAccountFound = "no_account_found";
|
|
const noCryptoObject = "no_crypto_object";
|
|
const unexpectedCredentialType = "unexpected_credential_type";
|
|
const invalidAssertion = "invalid_assertion";
|
|
const invalidClientCredential = "invalid_client_credential";
|
|
const tokenRefreshRequired = "token_refresh_required";
|
|
const userTimeoutReached = "user_timeout_reached";
|
|
const tokenClaimsCnfRequiredForSignedJwt = "token_claims_cnf_required_for_signedjwt";
|
|
const authorizationCodeMissingFromServerResponse = "authorization_code_missing_from_server_response";
|
|
const bindingKeyNotRemoved = "binding_key_not_removed";
|
|
const endSessionEndpointNotSupported = "end_session_endpoint_not_supported";
|
|
const keyIdMissing = "key_id_missing";
|
|
const noNetworkConnectivity = "no_network_connectivity";
|
|
const userCanceled = "user_canceled";
|
|
const missingTenantIdError = "missing_tenant_id_error";
|
|
const methodNotImplemented = "method_not_implemented";
|
|
const nestedAppAuthBridgeDisabled = "nested_app_auth_bridge_disabled";
|
|
|
|
/*! @azure/msal-common v15.8.1 2025-07-08 */
|
|
|
|
/*
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License.
|
|
*/
|
|
/**
|
|
* ClientAuthErrorMessage class containing string constants used by error codes and messages.
|
|
*/
|
|
const ClientAuthErrorMessages = {
|
|
[clientInfoDecodingError]: "The client info could not be parsed/decoded correctly",
|
|
[clientInfoEmptyError]: "The client info was empty",
|
|
[tokenParsingError]: "Token cannot be parsed",
|
|
[nullOrEmptyToken]: "The token is null or empty",
|
|
[endpointResolutionError]: "Endpoints cannot be resolved",
|
|
[networkError]: "Network request failed",
|
|
[openIdConfigError]: "Could not retrieve endpoints. Check your authority and verify the .well-known/openid-configuration endpoint returns the required endpoints.",
|
|
[hashNotDeserialized]: "The hash parameters could not be deserialized",
|
|
[invalidState]: "State was not the expected format",
|
|
[stateMismatch]: "State mismatch error",
|
|
[stateNotFound]: "State not found",
|
|
[nonceMismatch]: "Nonce mismatch error",
|
|
[authTimeNotFound]: "Max Age was requested and the ID token is missing the auth_time variable." +
|
|
" auth_time is an optional claim and is not enabled by default - it must be enabled." +
|
|
" See https://aka.ms/msaljs/optional-claims for more information.",
|
|
[maxAgeTranspired]: "Max Age is set to 0, or too much time has elapsed since the last end-user authentication.",
|
|
[multipleMatchingTokens]: "The cache contains multiple tokens satisfying the requirements. " +
|
|
"Call AcquireToken again providing more requirements such as authority or account.",
|
|
[multipleMatchingAccounts]: "The cache contains multiple accounts satisfying the given parameters. Please pass more info to obtain the correct account",
|
|
[multipleMatchingAppMetadata]: "The cache contains multiple appMetadata satisfying the given parameters. Please pass more info to obtain the correct appMetadata",
|
|
[requestCannotBeMade]: "Token request cannot be made without authorization code or refresh token.",
|
|
[cannotRemoveEmptyScope]: "Cannot remove null or empty scope from ScopeSet",
|
|
[cannotAppendScopeSet]: "Cannot append ScopeSet",
|
|
[emptyInputScopeSet]: "Empty input ScopeSet cannot be processed",
|
|
[deviceCodePollingCancelled]: "Caller has cancelled token endpoint polling during device code flow by setting DeviceCodeRequest.cancel = true.",
|
|
[deviceCodeExpired]: "Device code is expired.",
|
|
[deviceCodeUnknownError]: "Device code stopped polling for unknown reasons.",
|
|
[noAccountInSilentRequest]: "Please pass an account object, silent flow is not supported without account information",
|
|
[invalidCacheRecord]: "Cache record object was null or undefined.",
|
|
[invalidCacheEnvironment]: "Invalid environment when attempting to create cache entry",
|
|
[noAccountFound]: "No account found in cache for given key.",
|
|
[noCryptoObject]: "No crypto object detected.",
|
|
[unexpectedCredentialType]: "Unexpected credential type.",
|
|
[invalidAssertion]: "Client assertion must meet requirements described in https://tools.ietf.org/html/rfc7515",
|
|
[invalidClientCredential]: "Client credential (secret, certificate, or assertion) must not be empty when creating a confidential client. An application should at most have one credential",
|
|
[tokenRefreshRequired]: "Cannot return token from cache because it must be refreshed. This may be due to one of the following reasons: forceRefresh parameter is set to true, claims have been requested, there is no cached access token or it is expired.",
|
|
[userTimeoutReached]: "User defined timeout for device code polling reached",
|
|
[tokenClaimsCnfRequiredForSignedJwt]: "Cannot generate a POP jwt if the token_claims are not populated",
|
|
[authorizationCodeMissingFromServerResponse]: "Server response does not contain an authorization code to proceed",
|
|
[bindingKeyNotRemoved]: "Could not remove the credential's binding key from storage.",
|
|
[endSessionEndpointNotSupported]: "The provided authority does not support logout",
|
|
[keyIdMissing]: "A keyId value is missing from the requested bound token's cache record and is required to match the token to it's stored binding key.",
|
|
[noNetworkConnectivity]: "No network connectivity. Check your internet connection.",
|
|
[userCanceled]: "User cancelled the flow.",
|
|
[missingTenantIdError]: "A tenant id - not common, organizations, or consumers - must be specified when using the client_credentials flow.",
|
|
[methodNotImplemented]: "This method has not been implemented",
|
|
[nestedAppAuthBridgeDisabled]: "The nested app auth bridge is disabled",
|
|
};
|
|
/**
|
|
* Error thrown when there is an error in the client code running on the browser.
|
|
*/
|
|
class ClientAuthError extends AuthError {
|
|
constructor(errorCode, additionalMessage) {
|
|
super(errorCode, additionalMessage
|
|
? `${ClientAuthErrorMessages[errorCode]}: ${additionalMessage}`
|
|
: ClientAuthErrorMessages[errorCode]);
|
|
this.name = "ClientAuthError";
|
|
Object.setPrototypeOf(this, ClientAuthError.prototype);
|
|
}
|
|
}
|
|
function createClientAuthError(errorCode, additionalMessage) {
|
|
return new ClientAuthError(errorCode, additionalMessage);
|
|
}
|
|
|
|
/*! @azure/msal-common v15.8.1 2025-07-08 */
|
|
|
|
/*
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License.
|
|
*/
|
|
/**
|
|
* Log message level.
|
|
*/
|
|
var LogLevel;
|
|
(function (LogLevel) {
|
|
LogLevel[LogLevel["Error"] = 0] = "Error";
|
|
LogLevel[LogLevel["Warning"] = 1] = "Warning";
|
|
LogLevel[LogLevel["Info"] = 2] = "Info";
|
|
LogLevel[LogLevel["Verbose"] = 3] = "Verbose";
|
|
LogLevel[LogLevel["Trace"] = 4] = "Trace";
|
|
})(LogLevel || (LogLevel = {}));
|
|
/**
|
|
* Class which facilitates logging of messages to a specific place.
|
|
*/
|
|
class Logger {
|
|
constructor(loggerOptions, packageName, packageVersion) {
|
|
// Current log level, defaults to info.
|
|
this.level = LogLevel.Info;
|
|
const defaultLoggerCallback = () => {
|
|
return;
|
|
};
|
|
const setLoggerOptions = loggerOptions || Logger.createDefaultLoggerOptions();
|
|
this.localCallback =
|
|
setLoggerOptions.loggerCallback || defaultLoggerCallback;
|
|
this.piiLoggingEnabled = setLoggerOptions.piiLoggingEnabled || false;
|
|
this.level =
|
|
typeof setLoggerOptions.logLevel === "number"
|
|
? setLoggerOptions.logLevel
|
|
: LogLevel.Info;
|
|
this.correlationId =
|
|
setLoggerOptions.correlationId || Constants.EMPTY_STRING;
|
|
this.packageName = packageName || Constants.EMPTY_STRING;
|
|
this.packageVersion = packageVersion || Constants.EMPTY_STRING;
|
|
}
|
|
static createDefaultLoggerOptions() {
|
|
return {
|
|
loggerCallback: () => {
|
|
// allow users to not set loggerCallback
|
|
},
|
|
piiLoggingEnabled: false,
|
|
logLevel: LogLevel.Info,
|
|
};
|
|
}
|
|
/**
|
|
* Create new Logger with existing configurations.
|
|
*/
|
|
clone(packageName, packageVersion, correlationId) {
|
|
return new Logger({
|
|
loggerCallback: this.localCallback,
|
|
piiLoggingEnabled: this.piiLoggingEnabled,
|
|
logLevel: this.level,
|
|
correlationId: correlationId || this.correlationId,
|
|
}, packageName, packageVersion);
|
|
}
|
|
/**
|
|
* Log message with required options.
|
|
*/
|
|
logMessage(logMessage, options) {
|
|
if (options.logLevel > this.level ||
|
|
(!this.piiLoggingEnabled && options.containsPii)) {
|
|
return;
|
|
}
|
|
const timestamp = new Date().toUTCString();
|
|
// Add correlationId to logs if set, correlationId provided on log messages take precedence
|
|
const logHeader = `[${timestamp}] : [${options.correlationId || this.correlationId || ""}]`;
|
|
const log = `${logHeader} : ${this.packageName}@${this.packageVersion} : ${LogLevel[options.logLevel]} - ${logMessage}`;
|
|
// debug(`msal:${LogLevel[options.logLevel]}${options.containsPii ? "-Pii": Constants.EMPTY_STRING}${options.context ? `:${options.context}` : Constants.EMPTY_STRING}`)(logMessage);
|
|
this.executeCallback(options.logLevel, log, options.containsPii || false);
|
|
}
|
|
/**
|
|
* Execute callback with message.
|
|
*/
|
|
executeCallback(level, message, containsPii) {
|
|
if (this.localCallback) {
|
|
this.localCallback(level, message, containsPii);
|
|
}
|
|
}
|
|
/**
|
|
* Logs error messages.
|
|
*/
|
|
error(message, correlationId) {
|
|
this.logMessage(message, {
|
|
logLevel: LogLevel.Error,
|
|
containsPii: false,
|
|
correlationId: correlationId || Constants.EMPTY_STRING,
|
|
});
|
|
}
|
|
/**
|
|
* Logs error messages with PII.
|
|
*/
|
|
errorPii(message, correlationId) {
|
|
this.logMessage(message, {
|
|
logLevel: LogLevel.Error,
|
|
containsPii: true,
|
|
correlationId: correlationId || Constants.EMPTY_STRING,
|
|
});
|
|
}
|
|
/**
|
|
* Logs warning messages.
|
|
*/
|
|
warning(message, correlationId) {
|
|
this.logMessage(message, {
|
|
logLevel: LogLevel.Warning,
|
|
containsPii: false,
|
|
correlationId: correlationId || Constants.EMPTY_STRING,
|
|
});
|
|
}
|
|
/**
|
|
* Logs warning messages with PII.
|
|
*/
|
|
warningPii(message, correlationId) {
|
|
this.logMessage(message, {
|
|
logLevel: LogLevel.Warning,
|
|
containsPii: true,
|
|
correlationId: correlationId || Constants.EMPTY_STRING,
|
|
});
|
|
}
|
|
/**
|
|
* Logs info messages.
|
|
*/
|
|
info(message, correlationId) {
|
|
this.logMessage(message, {
|
|
logLevel: LogLevel.Info,
|
|
containsPii: false,
|
|
correlationId: correlationId || Constants.EMPTY_STRING,
|
|
});
|
|
}
|
|
/**
|
|
* Logs info messages with PII.
|
|
*/
|
|
infoPii(message, correlationId) {
|
|
this.logMessage(message, {
|
|
logLevel: LogLevel.Info,
|
|
containsPii: true,
|
|
correlationId: correlationId || Constants.EMPTY_STRING,
|
|
});
|
|
}
|
|
/**
|
|
* Logs verbose messages.
|
|
*/
|
|
verbose(message, correlationId) {
|
|
this.logMessage(message, {
|
|
logLevel: LogLevel.Verbose,
|
|
containsPii: false,
|
|
correlationId: correlationId || Constants.EMPTY_STRING,
|
|
});
|
|
}
|
|
/**
|
|
* Logs verbose messages with PII.
|
|
*/
|
|
verbosePii(message, correlationId) {
|
|
this.logMessage(message, {
|
|
logLevel: LogLevel.Verbose,
|
|
containsPii: true,
|
|
correlationId: correlationId || Constants.EMPTY_STRING,
|
|
});
|
|
}
|
|
/**
|
|
* Logs trace messages.
|
|
*/
|
|
trace(message, correlationId) {
|
|
this.logMessage(message, {
|
|
logLevel: LogLevel.Trace,
|
|
containsPii: false,
|
|
correlationId: correlationId || Constants.EMPTY_STRING,
|
|
});
|
|
}
|
|
/**
|
|
* Logs trace messages with PII.
|
|
*/
|
|
tracePii(message, correlationId) {
|
|
this.logMessage(message, {
|
|
logLevel: LogLevel.Trace,
|
|
containsPii: true,
|
|
correlationId: correlationId || Constants.EMPTY_STRING,
|
|
});
|
|
}
|
|
/**
|
|
* Returns whether PII Logging is enabled or not.
|
|
*/
|
|
isPiiLoggingEnabled() {
|
|
return this.piiLoggingEnabled || false;
|
|
}
|
|
}
|
|
|
|
/*! @azure/msal-common v15.8.1 2025-07-08 */
|
|
/**
|
|
* Convert seconds to JS Date object. Seconds can be in a number or string format or undefined (will still return a date).
|
|
* @param seconds
|
|
*/
|
|
function toDateFromSeconds(seconds) {
|
|
if (seconds) {
|
|
return new Date(Number(seconds) * 1000);
|
|
}
|
|
return new Date();
|
|
}
|
|
|
|
/*! @azure/msal-common v15.8.1 2025-07-08 */
|
|
/*
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License.
|
|
*/
|
|
const redirectUriEmpty = "redirect_uri_empty";
|
|
const claimsRequestParsingError = "claims_request_parsing_error";
|
|
const authorityUriInsecure = "authority_uri_insecure";
|
|
const urlParseError = "url_parse_error";
|
|
const urlEmptyError = "empty_url_error";
|
|
const emptyInputScopesError = "empty_input_scopes_error";
|
|
const invalidClaims = "invalid_claims";
|
|
const tokenRequestEmpty = "token_request_empty";
|
|
const logoutRequestEmpty = "logout_request_empty";
|
|
const invalidCodeChallengeMethod = "invalid_code_challenge_method";
|
|
const pkceParamsMissing = "pkce_params_missing";
|
|
const invalidCloudDiscoveryMetadata = "invalid_cloud_discovery_metadata";
|
|
const invalidAuthorityMetadata = "invalid_authority_metadata";
|
|
const untrustedAuthority = "untrusted_authority";
|
|
const missingSshJwk = "missing_ssh_jwk";
|
|
const missingSshKid = "missing_ssh_kid";
|
|
const missingNonceAuthenticationHeader = "missing_nonce_authentication_header";
|
|
const invalidAuthenticationHeader = "invalid_authentication_header";
|
|
const cannotSetOIDCOptions = "cannot_set_OIDCOptions";
|
|
const cannotAllowPlatformBroker = "cannot_allow_platform_broker";
|
|
const authorityMismatch = "authority_mismatch";
|
|
|
|
/*! @azure/msal-common v15.8.1 2025-07-08 */
|
|
|
|
/*
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License.
|
|
*/
|
|
const ClientConfigurationErrorMessages = {
|
|
[redirectUriEmpty]: "A redirect URI is required for all calls, and none has been set.",
|
|
[claimsRequestParsingError]: "Could not parse the given claims request object.",
|
|
[authorityUriInsecure]: "Authority URIs must use https. Please see here for valid authority configuration options: https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-js-initializing-client-applications#configuration-options",
|
|
[urlParseError]: "URL could not be parsed into appropriate segments.",
|
|
[urlEmptyError]: "URL was empty or null.",
|
|
[emptyInputScopesError]: "Scopes cannot be passed as null, undefined or empty array because they are required to obtain an access token.",
|
|
[invalidClaims]: "Given claims parameter must be a stringified JSON object.",
|
|
[tokenRequestEmpty]: "Token request was empty and not found in cache.",
|
|
[logoutRequestEmpty]: "The logout request was null or undefined.",
|
|
[invalidCodeChallengeMethod]: 'code_challenge_method passed is invalid. Valid values are "plain" and "S256".',
|
|
[pkceParamsMissing]: "Both params: code_challenge and code_challenge_method are to be passed if to be sent in the request",
|
|
[invalidCloudDiscoveryMetadata]: "Invalid cloudDiscoveryMetadata provided. Must be a stringified JSON object containing tenant_discovery_endpoint and metadata fields",
|
|
[invalidAuthorityMetadata]: "Invalid authorityMetadata provided. Must by a stringified JSON object containing authorization_endpoint, token_endpoint, issuer fields.",
|
|
[untrustedAuthority]: "The provided authority is not a trusted authority. Please include this authority in the knownAuthorities config parameter.",
|
|
[missingSshJwk]: "Missing sshJwk in SSH certificate request. A stringified JSON Web Key is required when using the SSH authentication scheme.",
|
|
[missingSshKid]: "Missing sshKid in SSH certificate request. A string that uniquely identifies the public SSH key is required when using the SSH authentication scheme.",
|
|
[missingNonceAuthenticationHeader]: "Unable to find an authentication header containing server nonce. Either the Authentication-Info or WWW-Authenticate headers must be present in order to obtain a server nonce.",
|
|
[invalidAuthenticationHeader]: "Invalid authentication header provided",
|
|
[cannotSetOIDCOptions]: "Cannot set OIDCOptions parameter. Please change the protocol mode to OIDC or use a non-Microsoft authority.",
|
|
[cannotAllowPlatformBroker]: "Cannot set allowPlatformBroker parameter to true when not in AAD protocol mode.",
|
|
[authorityMismatch]: "Authority mismatch error. Authority provided in login request or PublicClientApplication config does not match the environment of the provided account. Please use a matching account or make an interactive request to login to this authority.",
|
|
};
|
|
/**
|
|
* Error thrown when there is an error in configuration of the MSAL.js library.
|
|
*/
|
|
class ClientConfigurationError extends AuthError {
|
|
constructor(errorCode) {
|
|
super(errorCode, ClientConfigurationErrorMessages[errorCode]);
|
|
this.name = "ClientConfigurationError";
|
|
Object.setPrototypeOf(this, ClientConfigurationError.prototype);
|
|
}
|
|
}
|
|
function createClientConfigurationError(errorCode) {
|
|
return new ClientConfigurationError(errorCode);
|
|
}
|
|
|
|
/*! @azure/msal-common v15.8.1 2025-07-08 */
|
|
const X_CLIENT_EXTRA_SKU = "x-client-xtra-sku";
|
|
|
|
/*! @azure/msal-common v15.8.1 2025-07-08 */
|
|
|
|
/*
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License.
|
|
*/
|
|
/**
|
|
* Error thrown when there is an error with the server code, for example, unavailability.
|
|
*/
|
|
class ServerError extends AuthError {
|
|
constructor(errorCode, errorMessage, subError, errorNo, status) {
|
|
super(errorCode, errorMessage, subError);
|
|
this.name = "ServerError";
|
|
this.errorNo = errorNo;
|
|
this.status = status;
|
|
Object.setPrototypeOf(this, ServerError.prototype);
|
|
}
|
|
}
|
|
|
|
/*! @azure/msal-common v15.8.1 2025-07-08 */
|
|
/**
|
|
* Error thrown when user interaction is required.
|
|
*/
|
|
class InteractionRequiredAuthError extends AuthError {
|
|
constructor(errorCode, errorMessage, subError, timestamp, traceId, correlationId, claims, errorNo) {
|
|
super(errorCode, errorMessage, subError);
|
|
Object.setPrototypeOf(this, InteractionRequiredAuthError.prototype);
|
|
this.timestamp = timestamp || Constants.EMPTY_STRING;
|
|
this.traceId = traceId || Constants.EMPTY_STRING;
|
|
this.correlationId = correlationId || Constants.EMPTY_STRING;
|
|
this.claims = claims || Constants.EMPTY_STRING;
|
|
this.name = "InteractionRequiredAuthError";
|
|
this.errorNo = errorNo;
|
|
}
|
|
}
|
|
|
|
/*! @azure/msal-common v15.8.1 2025-07-08 */
|
|
|
|
/*
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License.
|
|
*/
|
|
const skuGroupSeparator = ",";
|
|
const skuValueSeparator = "|";
|
|
function makeExtraSkuString(params) {
|
|
const { skus, libraryName, libraryVersion, extensionName, extensionVersion, } = params;
|
|
const skuMap = new Map([
|
|
[0, [libraryName, libraryVersion]],
|
|
[2, [extensionName, extensionVersion]],
|
|
]);
|
|
let skuArr = [];
|
|
if (skus?.length) {
|
|
skuArr = skus.split(skuGroupSeparator);
|
|
// Ignore invalid input sku param
|
|
if (skuArr.length < 4) {
|
|
return skus;
|
|
}
|
|
}
|
|
else {
|
|
skuArr = Array.from({ length: 4 }, () => skuValueSeparator);
|
|
}
|
|
skuMap.forEach((value, key) => {
|
|
if (value.length === 2 && value[0]?.length && value[1]?.length) {
|
|
setSku({
|
|
skuArr,
|
|
index: key,
|
|
skuName: value[0],
|
|
skuVersion: value[1],
|
|
});
|
|
}
|
|
});
|
|
return skuArr.join(skuGroupSeparator);
|
|
}
|
|
function setSku(params) {
|
|
const { skuArr, index, skuName, skuVersion } = params;
|
|
if (index >= skuArr.length) {
|
|
return;
|
|
}
|
|
skuArr[index] = [skuName, skuVersion].join(skuValueSeparator);
|
|
}
|
|
/** @internal */
|
|
class ServerTelemetryManager {
|
|
constructor(telemetryRequest, cacheManager) {
|
|
this.cacheOutcome = CacheOutcome.NOT_APPLICABLE;
|
|
this.cacheManager = cacheManager;
|
|
this.apiId = telemetryRequest.apiId;
|
|
this.correlationId = telemetryRequest.correlationId;
|
|
this.wrapperSKU = telemetryRequest.wrapperSKU || Constants.EMPTY_STRING;
|
|
this.wrapperVer = telemetryRequest.wrapperVer || Constants.EMPTY_STRING;
|
|
this.telemetryCacheKey =
|
|
SERVER_TELEM_CONSTANTS.CACHE_KEY +
|
|
Separators.CACHE_KEY_SEPARATOR +
|
|
telemetryRequest.clientId;
|
|
}
|
|
/**
|
|
* API to add MSER Telemetry to request
|
|
*/
|
|
generateCurrentRequestHeaderValue() {
|
|
const request = `${this.apiId}${SERVER_TELEM_CONSTANTS.VALUE_SEPARATOR}${this.cacheOutcome}`;
|
|
const platformFieldsArr = [this.wrapperSKU, this.wrapperVer];
|
|
const nativeBrokerErrorCode = this.getNativeBrokerErrorCode();
|
|
if (nativeBrokerErrorCode?.length) {
|
|
platformFieldsArr.push(`broker_error=${nativeBrokerErrorCode}`);
|
|
}
|
|
const platformFields = platformFieldsArr.join(SERVER_TELEM_CONSTANTS.VALUE_SEPARATOR);
|
|
const regionDiscoveryFields = this.getRegionDiscoveryFields();
|
|
const requestWithRegionDiscoveryFields = [
|
|
request,
|
|
regionDiscoveryFields,
|
|
].join(SERVER_TELEM_CONSTANTS.VALUE_SEPARATOR);
|
|
return [
|
|
SERVER_TELEM_CONSTANTS.SCHEMA_VERSION,
|
|
requestWithRegionDiscoveryFields,
|
|
platformFields,
|
|
].join(SERVER_TELEM_CONSTANTS.CATEGORY_SEPARATOR);
|
|
}
|
|
/**
|
|
* API to add MSER Telemetry for the last failed request
|
|
*/
|
|
generateLastRequestHeaderValue() {
|
|
const lastRequests = this.getLastRequests();
|
|
const maxErrors = ServerTelemetryManager.maxErrorsToSend(lastRequests);
|
|
const failedRequests = lastRequests.failedRequests
|
|
.slice(0, 2 * maxErrors)
|
|
.join(SERVER_TELEM_CONSTANTS.VALUE_SEPARATOR);
|
|
const errors = lastRequests.errors
|
|
.slice(0, maxErrors)
|
|
.join(SERVER_TELEM_CONSTANTS.VALUE_SEPARATOR);
|
|
const errorCount = lastRequests.errors.length;
|
|
// Indicate whether this header contains all data or partial data
|
|
const overflow = maxErrors < errorCount
|
|
? SERVER_TELEM_CONSTANTS.OVERFLOW_TRUE
|
|
: SERVER_TELEM_CONSTANTS.OVERFLOW_FALSE;
|
|
const platformFields = [errorCount, overflow].join(SERVER_TELEM_CONSTANTS.VALUE_SEPARATOR);
|
|
return [
|
|
SERVER_TELEM_CONSTANTS.SCHEMA_VERSION,
|
|
lastRequests.cacheHits,
|
|
failedRequests,
|
|
errors,
|
|
platformFields,
|
|
].join(SERVER_TELEM_CONSTANTS.CATEGORY_SEPARATOR);
|
|
}
|
|
/**
|
|
* API to cache token failures for MSER data capture
|
|
* @param error
|
|
*/
|
|
cacheFailedRequest(error) {
|
|
const lastRequests = this.getLastRequests();
|
|
if (lastRequests.errors.length >=
|
|
SERVER_TELEM_CONSTANTS.MAX_CACHED_ERRORS) {
|
|
// Remove a cached error to make room, first in first out
|
|
lastRequests.failedRequests.shift(); // apiId
|
|
lastRequests.failedRequests.shift(); // correlationId
|
|
lastRequests.errors.shift();
|
|
}
|
|
lastRequests.failedRequests.push(this.apiId, this.correlationId);
|
|
if (error instanceof Error && !!error && error.toString()) {
|
|
if (error instanceof AuthError) {
|
|
if (error.subError) {
|
|
lastRequests.errors.push(error.subError);
|
|
}
|
|
else if (error.errorCode) {
|
|
lastRequests.errors.push(error.errorCode);
|
|
}
|
|
else {
|
|
lastRequests.errors.push(error.toString());
|
|
}
|
|
}
|
|
else {
|
|
lastRequests.errors.push(error.toString());
|
|
}
|
|
}
|
|
else {
|
|
lastRequests.errors.push(SERVER_TELEM_CONSTANTS.UNKNOWN_ERROR);
|
|
}
|
|
this.cacheManager.setServerTelemetry(this.telemetryCacheKey, lastRequests, this.correlationId);
|
|
return;
|
|
}
|
|
/**
|
|
* Update server telemetry cache entry by incrementing cache hit counter
|
|
*/
|
|
incrementCacheHits() {
|
|
const lastRequests = this.getLastRequests();
|
|
lastRequests.cacheHits += 1;
|
|
this.cacheManager.setServerTelemetry(this.telemetryCacheKey, lastRequests, this.correlationId);
|
|
return lastRequests.cacheHits;
|
|
}
|
|
/**
|
|
* Get the server telemetry entity from cache or initialize a new one
|
|
*/
|
|
getLastRequests() {
|
|
const initialValue = {
|
|
failedRequests: [],
|
|
errors: [],
|
|
cacheHits: 0,
|
|
};
|
|
const lastRequests = this.cacheManager.getServerTelemetry(this.telemetryCacheKey);
|
|
return lastRequests || initialValue;
|
|
}
|
|
/**
|
|
* Remove server telemetry cache entry
|
|
*/
|
|
clearTelemetryCache() {
|
|
const lastRequests = this.getLastRequests();
|
|
const numErrorsFlushed = ServerTelemetryManager.maxErrorsToSend(lastRequests);
|
|
const errorCount = lastRequests.errors.length;
|
|
if (numErrorsFlushed === errorCount) {
|
|
// All errors were sent on last request, clear Telemetry cache
|
|
this.cacheManager.removeItem(this.telemetryCacheKey, this.correlationId);
|
|
}
|
|
else {
|
|
// Partial data was flushed to server, construct a new telemetry cache item with errors that were not flushed
|
|
const serverTelemEntity = {
|
|
failedRequests: lastRequests.failedRequests.slice(numErrorsFlushed * 2),
|
|
errors: lastRequests.errors.slice(numErrorsFlushed),
|
|
cacheHits: 0,
|
|
};
|
|
this.cacheManager.setServerTelemetry(this.telemetryCacheKey, serverTelemEntity, this.correlationId);
|
|
}
|
|
}
|
|
/**
|
|
* Returns the maximum number of errors that can be flushed to the server in the next network request
|
|
* @param serverTelemetryEntity
|
|
*/
|
|
static maxErrorsToSend(serverTelemetryEntity) {
|
|
let i;
|
|
let maxErrors = 0;
|
|
let dataSize = 0;
|
|
const errorCount = serverTelemetryEntity.errors.length;
|
|
for (i = 0; i < errorCount; i++) {
|
|
// failedRequests parameter contains pairs of apiId and correlationId, multiply index by 2 to preserve pairs
|
|
const apiId = serverTelemetryEntity.failedRequests[2 * i] ||
|
|
Constants.EMPTY_STRING;
|
|
const correlationId = serverTelemetryEntity.failedRequests[2 * i + 1] ||
|
|
Constants.EMPTY_STRING;
|
|
const errorCode = serverTelemetryEntity.errors[i] || Constants.EMPTY_STRING;
|
|
// Count number of characters that would be added to header, each character is 1 byte. Add 3 at the end to account for separators
|
|
dataSize +=
|
|
apiId.toString().length +
|
|
correlationId.toString().length +
|
|
errorCode.length +
|
|
3;
|
|
if (dataSize < SERVER_TELEM_CONSTANTS.MAX_LAST_HEADER_BYTES) {
|
|
// Adding this entry to the header would still keep header size below the limit
|
|
maxErrors += 1;
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
return maxErrors;
|
|
}
|
|
/**
|
|
* Get the region discovery fields
|
|
*
|
|
* @returns string
|
|
*/
|
|
getRegionDiscoveryFields() {
|
|
const regionDiscoveryFields = [];
|
|
regionDiscoveryFields.push(this.regionUsed || Constants.EMPTY_STRING);
|
|
regionDiscoveryFields.push(this.regionSource || Constants.EMPTY_STRING);
|
|
regionDiscoveryFields.push(this.regionOutcome || Constants.EMPTY_STRING);
|
|
return regionDiscoveryFields.join(",");
|
|
}
|
|
/**
|
|
* Update the region discovery metadata
|
|
*
|
|
* @param regionDiscoveryMetadata
|
|
* @returns void
|
|
*/
|
|
updateRegionDiscoveryMetadata(regionDiscoveryMetadata) {
|
|
this.regionUsed = regionDiscoveryMetadata.region_used;
|
|
this.regionSource = regionDiscoveryMetadata.region_source;
|
|
this.regionOutcome = regionDiscoveryMetadata.region_outcome;
|
|
}
|
|
/**
|
|
* Set cache outcome
|
|
*/
|
|
setCacheOutcome(cacheOutcome) {
|
|
this.cacheOutcome = cacheOutcome;
|
|
}
|
|
setNativeBrokerErrorCode(errorCode) {
|
|
const lastRequests = this.getLastRequests();
|
|
lastRequests.nativeBrokerErrorCode = errorCode;
|
|
this.cacheManager.setServerTelemetry(this.telemetryCacheKey, lastRequests, this.correlationId);
|
|
}
|
|
getNativeBrokerErrorCode() {
|
|
return this.getLastRequests().nativeBrokerErrorCode;
|
|
}
|
|
clearNativeBrokerErrorCode() {
|
|
const lastRequests = this.getLastRequests();
|
|
delete lastRequests.nativeBrokerErrorCode;
|
|
this.cacheManager.setServerTelemetry(this.telemetryCacheKey, lastRequests, this.correlationId);
|
|
}
|
|
static makeExtraSkuString(params) {
|
|
return makeExtraSkuString(params);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License.
|
|
*/
|
|
class BasePersistence {
|
|
async verifyPersistence() {
|
|
// We are using a different location for the test to avoid overriding the functional cache
|
|
const persistenceValidator = await this.createForPersistenceValidation();
|
|
try {
|
|
await persistenceValidator.save(Constants$1.PERSISTENCE_TEST_DATA);
|
|
const retrievedDummyData = await persistenceValidator.load();
|
|
if (!retrievedDummyData) {
|
|
throw PersistenceError.createCachePersistenceError("Persistence check failed. Data was written but it could not be read. " +
|
|
"Possible cause: on Linux, LibSecret is installed but D-Bus isn't running \
|
|
because it cannot be started over SSH.");
|
|
}
|
|
if (retrievedDummyData !== Constants$1.PERSISTENCE_TEST_DATA) {
|
|
throw PersistenceError.createCachePersistenceError(`Persistence check failed. Data written ${Constants$1.PERSISTENCE_TEST_DATA} is different \
|
|
from data read ${retrievedDummyData}`);
|
|
}
|
|
await persistenceValidator.delete();
|
|
return true;
|
|
}
|
|
catch (e) {
|
|
throw PersistenceError.createCachePersistenceError(`Verifing persistence failed with the error: ${e}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License.
|
|
*/
|
|
/**
|
|
* Reads and writes data to file specified by file location. File contents are not
|
|
* encrypted.
|
|
*
|
|
* If file or directory has not been created, it FilePersistence.create() will create
|
|
* file and any directories in the path recursively.
|
|
*/
|
|
class FilePersistence extends BasePersistence {
|
|
constructor(fileLocation, loggerOptions) {
|
|
super();
|
|
this.logger = new Logger(loggerOptions || FilePersistence.createDefaultLoggerOptions());
|
|
this.filePath = fileLocation;
|
|
}
|
|
static async create(fileLocation, loggerOptions) {
|
|
const filePersistence = new FilePersistence(fileLocation, loggerOptions);
|
|
await filePersistence.createCacheFile();
|
|
return filePersistence;
|
|
}
|
|
async save(contents) {
|
|
try {
|
|
await fs.promises.writeFile(this.getFilePath(), contents, "utf-8");
|
|
}
|
|
catch (err) {
|
|
if (isNodeError(err)) {
|
|
throw PersistenceError.createFileSystemError(err.code || ErrorCodes.UNKNOWN, err.message);
|
|
}
|
|
else {
|
|
throw err;
|
|
}
|
|
}
|
|
}
|
|
async saveBuffer(contents) {
|
|
try {
|
|
await fs.promises.writeFile(this.getFilePath(), contents);
|
|
}
|
|
catch (err) {
|
|
if (isNodeError(err)) {
|
|
throw PersistenceError.createFileSystemError(err.code || ErrorCodes.UNKNOWN, err.message);
|
|
}
|
|
else {
|
|
throw err;
|
|
}
|
|
}
|
|
}
|
|
async load() {
|
|
try {
|
|
return await fs.promises.readFile(this.getFilePath(), "utf-8");
|
|
}
|
|
catch (err) {
|
|
if (isNodeError(err)) {
|
|
throw PersistenceError.createFileSystemError(err.code || ErrorCodes.UNKNOWN, err.message);
|
|
}
|
|
else {
|
|
throw err;
|
|
}
|
|
}
|
|
}
|
|
async loadBuffer() {
|
|
try {
|
|
return await fs.promises.readFile(this.getFilePath());
|
|
}
|
|
catch (err) {
|
|
if (isNodeError(err)) {
|
|
throw PersistenceError.createFileSystemError(err.code || ErrorCodes.UNKNOWN, err.message);
|
|
}
|
|
else {
|
|
throw err;
|
|
}
|
|
}
|
|
}
|
|
async delete() {
|
|
try {
|
|
await fs.promises.unlink(this.getFilePath());
|
|
return true;
|
|
}
|
|
catch (err) {
|
|
if (isNodeError(err)) {
|
|
if (err.code === Constants$1.ENOENT_ERROR) {
|
|
// file does not exist, so it was not deleted
|
|
this.logger.warning("Cache file does not exist, so it could not be deleted");
|
|
return false;
|
|
}
|
|
throw PersistenceError.createFileSystemError(err.code || ErrorCodes.UNKNOWN, err.message);
|
|
}
|
|
else {
|
|
throw err;
|
|
}
|
|
}
|
|
}
|
|
getFilePath() {
|
|
return this.filePath;
|
|
}
|
|
async reloadNecessary(lastSync) {
|
|
return lastSync < (await this.timeLastModified());
|
|
}
|
|
getLogger() {
|
|
return this.logger;
|
|
}
|
|
createForPersistenceValidation() {
|
|
const testCacheFileLocation = `${path.dirname(this.filePath)}/test.cache`;
|
|
return FilePersistence.create(testCacheFileLocation);
|
|
}
|
|
static createDefaultLoggerOptions() {
|
|
return {
|
|
loggerCallback: () => {
|
|
// allow users to not set loggerCallback
|
|
},
|
|
piiLoggingEnabled: false,
|
|
logLevel: LogLevel.Info,
|
|
};
|
|
}
|
|
async timeLastModified() {
|
|
try {
|
|
const stats = await fs.promises.stat(this.filePath);
|
|
return stats.mtime.getTime();
|
|
}
|
|
catch (err) {
|
|
if (isNodeError(err)) {
|
|
if (err.code === Constants$1.ENOENT_ERROR) {
|
|
// file does not exist, so it's never been modified
|
|
this.logger.verbose("Cache file does not exist");
|
|
return 0;
|
|
}
|
|
throw PersistenceError.createFileSystemError(err.code || ErrorCodes.UNKNOWN, err.message);
|
|
}
|
|
else {
|
|
throw err;
|
|
}
|
|
}
|
|
}
|
|
async createCacheFile() {
|
|
await this.createFileDirectory();
|
|
// File is created only if it does not exist
|
|
const fileHandle = await fs.promises.open(this.filePath, "a");
|
|
await fileHandle.close();
|
|
this.logger.info(`File created at ${this.filePath}`);
|
|
}
|
|
async createFileDirectory() {
|
|
try {
|
|
await fs.promises.mkdir(path.dirname(this.filePath), { recursive: true });
|
|
}
|
|
catch (err) {
|
|
if (isNodeError(err)) {
|
|
if (err.code === Constants$1.EEXIST_ERROR) {
|
|
this.logger.info(`Directory ${path.dirname(this.filePath)} already exists`);
|
|
}
|
|
else {
|
|
throw PersistenceError.createFileSystemError(err.code || ErrorCodes.UNKNOWN, err.message);
|
|
}
|
|
}
|
|
else {
|
|
throw err;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License.
|
|
*/
|
|
class UnavailableDpapi {
|
|
constructor(errorMessage) {
|
|
this.errorMessage = errorMessage;
|
|
}
|
|
protectData() {
|
|
throw new Error(this.errorMessage);
|
|
}
|
|
unprotectData() {
|
|
throw new Error(this.errorMessage);
|
|
}
|
|
}
|
|
let Dpapi;
|
|
if (process.platform !== "win32") {
|
|
Dpapi = new UnavailableDpapi("Dpapi is not supported on this platform");
|
|
}
|
|
else {
|
|
// In .mjs files, require is not defined. We need to use createRequire to get a require function
|
|
const safeRequire = typeof require !== "undefined"
|
|
? require
|
|
: module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('msal-node-extensions.cjs', document.baseURI).href)));
|
|
try {
|
|
Dpapi = safeRequire(`../bin/${process.arch}/dpapi`);
|
|
}
|
|
catch (e) {
|
|
Dpapi = new UnavailableDpapi("Dpapi bindings unavailable");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License.
|
|
*/
|
|
/**
|
|
* Specifies the scope of the data protection - either the current user or the local
|
|
* machine.
|
|
*
|
|
* You do not need a key to protect or unprotect the data.
|
|
* If you set the Scope to CurrentUser, only applications running on your credentials can
|
|
* unprotect the data; however, that means that any application running on your credentials
|
|
* can access the protected data. If you set the Scope to LocalMachine, any full-trust
|
|
* application on the computer can unprotect, access, and modify the data.
|
|
*
|
|
*/
|
|
const DataProtectionScope = {
|
|
CurrentUser: "CurrentUser",
|
|
LocalMachine: "LocalMachine",
|
|
};
|
|
|
|
/*
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License.
|
|
*/
|
|
/**
|
|
* Uses CryptProtectData and CryptUnprotectData on Windows to encrypt and decrypt file contents.
|
|
*
|
|
* scope: Scope of the data protection. Either local user or the current machine
|
|
* optionalEntropy: Password or other additional entropy used to encrypt the data
|
|
*/
|
|
class FilePersistenceWithDataProtection extends BasePersistence {
|
|
constructor(filePersistence, scope, optionalEntropy) {
|
|
super();
|
|
this.scope = scope;
|
|
this.optionalEntropy = optionalEntropy
|
|
? Buffer.from(optionalEntropy, "utf-8")
|
|
: null;
|
|
this.filePersistence = filePersistence;
|
|
}
|
|
static async create(fileLocation, scope, optionalEntropy, loggerOptions) {
|
|
const filePersistence = await FilePersistence.create(fileLocation, loggerOptions);
|
|
const persistence = new FilePersistenceWithDataProtection(filePersistence, scope, optionalEntropy);
|
|
return persistence;
|
|
}
|
|
async save(contents) {
|
|
try {
|
|
const encryptedContents = Dpapi.protectData(Buffer.from(contents, "utf-8"), this.optionalEntropy, this.scope.toString());
|
|
await this.filePersistence.saveBuffer(encryptedContents);
|
|
}
|
|
catch (err) {
|
|
if (isNodeError(err)) {
|
|
throw PersistenceError.createFilePersistenceWithDPAPIError(err.message);
|
|
}
|
|
else {
|
|
throw err;
|
|
}
|
|
}
|
|
}
|
|
async load() {
|
|
try {
|
|
const encryptedContents = await this.filePersistence.loadBuffer();
|
|
if (typeof encryptedContents === "undefined" ||
|
|
!encryptedContents ||
|
|
0 === encryptedContents.length) {
|
|
this.filePersistence
|
|
.getLogger()
|
|
.info("Encrypted contents loaded from file were null or empty");
|
|
return null;
|
|
}
|
|
return Dpapi.unprotectData(encryptedContents, this.optionalEntropy, this.scope.toString()).toString();
|
|
}
|
|
catch (err) {
|
|
if (isNodeError(err)) {
|
|
throw PersistenceError.createFilePersistenceWithDPAPIError(err.message);
|
|
}
|
|
else {
|
|
throw err;
|
|
}
|
|
}
|
|
}
|
|
async delete() {
|
|
return this.filePersistence.delete();
|
|
}
|
|
async reloadNecessary(lastSync) {
|
|
return this.filePersistence.reloadNecessary(lastSync);
|
|
}
|
|
getFilePath() {
|
|
return this.filePersistence.getFilePath();
|
|
}
|
|
getLogger() {
|
|
return this.filePersistence.getLogger();
|
|
}
|
|
createForPersistenceValidation() {
|
|
const testCacheFileLocation = `${path.dirname(this.filePersistence.getFilePath())}/test.cache`;
|
|
return FilePersistenceWithDataProtection.create(testCacheFileLocation, DataProtectionScope.CurrentUser);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License.
|
|
*/
|
|
/**
|
|
* Uses reads and writes passwords to macOS keychain
|
|
*
|
|
* serviceName: Identifier used as key for whatever value is stored
|
|
* accountName: Account under which password should be stored
|
|
*/
|
|
class KeychainPersistence extends BasePersistence {
|
|
constructor(filePersistence, serviceName, accountName) {
|
|
super();
|
|
this.filePersistence = filePersistence;
|
|
this.serviceName = serviceName;
|
|
this.accountName = accountName;
|
|
}
|
|
static async create(fileLocation, serviceName, accountName, loggerOptions) {
|
|
const filePersistence = await FilePersistence.create(fileLocation, loggerOptions);
|
|
const persistence = new KeychainPersistence(filePersistence, serviceName, accountName);
|
|
return persistence;
|
|
}
|
|
async save(contents) {
|
|
try {
|
|
await keytar.setPassword(this.serviceName, this.accountName, contents);
|
|
}
|
|
catch (err) {
|
|
if (isNodeError(err)) {
|
|
throw PersistenceError.createKeychainPersistenceError(err.message);
|
|
}
|
|
else {
|
|
throw err;
|
|
}
|
|
}
|
|
// Write dummy data to update file mtime
|
|
await this.filePersistence.save("{}");
|
|
}
|
|
async load() {
|
|
try {
|
|
return await keytar.getPassword(this.serviceName, this.accountName);
|
|
}
|
|
catch (err) {
|
|
if (isNodeError(err)) {
|
|
throw PersistenceError.createKeychainPersistenceError(err.message);
|
|
}
|
|
else {
|
|
throw err;
|
|
}
|
|
}
|
|
}
|
|
async delete() {
|
|
try {
|
|
await this.filePersistence.delete();
|
|
return await keytar.deletePassword(this.serviceName, this.accountName);
|
|
}
|
|
catch (err) {
|
|
if (isNodeError(err)) {
|
|
throw PersistenceError.createKeychainPersistenceError(err.message);
|
|
}
|
|
else {
|
|
throw err;
|
|
}
|
|
}
|
|
}
|
|
async reloadNecessary(lastSync) {
|
|
return this.filePersistence.reloadNecessary(lastSync);
|
|
}
|
|
getFilePath() {
|
|
return this.filePersistence.getFilePath();
|
|
}
|
|
getLogger() {
|
|
return this.filePersistence.getLogger();
|
|
}
|
|
createForPersistenceValidation() {
|
|
const testCacheFileLocation = `${path.dirname(this.filePersistence.getFilePath())}/test.cache`;
|
|
return KeychainPersistence.create(testCacheFileLocation, "persistenceValidationServiceName", "persistencValidationAccountName");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License.
|
|
*/
|
|
/**
|
|
* Uses reads and writes passwords to Secret Service API/libsecret. Requires libsecret
|
|
* to be installed.
|
|
*
|
|
* serviceName: Identifier used as key for whatever value is stored
|
|
* accountName: Account under which password should be stored
|
|
*/
|
|
class LibSecretPersistence extends BasePersistence {
|
|
constructor(filePersistence, serviceName, accountName) {
|
|
super();
|
|
this.filePersistence = filePersistence;
|
|
this.serviceName = serviceName;
|
|
this.accountName = accountName;
|
|
}
|
|
static async create(fileLocation, serviceName, accountName, loggerOptions) {
|
|
const filePersistence = await FilePersistence.create(fileLocation, loggerOptions);
|
|
const persistence = new LibSecretPersistence(filePersistence, serviceName, accountName);
|
|
return persistence;
|
|
}
|
|
async save(contents) {
|
|
try {
|
|
await keytar.setPassword(this.serviceName, this.accountName, contents);
|
|
}
|
|
catch (err) {
|
|
if (isNodeError(err)) {
|
|
throw PersistenceError.createLibSecretError(err.message);
|
|
}
|
|
else {
|
|
throw err;
|
|
}
|
|
}
|
|
// Write dummy data to update file mtime
|
|
await this.filePersistence.save("{}");
|
|
}
|
|
async load() {
|
|
try {
|
|
return await keytar.getPassword(this.serviceName, this.accountName);
|
|
}
|
|
catch (err) {
|
|
if (isNodeError(err)) {
|
|
throw PersistenceError.createLibSecretError(err.message);
|
|
}
|
|
else {
|
|
throw err;
|
|
}
|
|
}
|
|
}
|
|
async delete() {
|
|
try {
|
|
await this.filePersistence.delete();
|
|
return await keytar.deletePassword(this.serviceName, this.accountName);
|
|
}
|
|
catch (err) {
|
|
if (isNodeError(err)) {
|
|
throw PersistenceError.createLibSecretError(err.message);
|
|
}
|
|
else {
|
|
throw err;
|
|
}
|
|
}
|
|
}
|
|
async reloadNecessary(lastSync) {
|
|
return this.filePersistence.reloadNecessary(lastSync);
|
|
}
|
|
getFilePath() {
|
|
return this.filePersistence.getFilePath();
|
|
}
|
|
getLogger() {
|
|
return this.filePersistence.getLogger();
|
|
}
|
|
createForPersistenceValidation() {
|
|
const testCacheFileLocation = `${path.dirname(this.filePersistence.getFilePath())}/test.cache`;
|
|
return LibSecretPersistence.create(testCacheFileLocation, "persistenceValidationServiceName", "persistencValidationAccountName");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License.
|
|
*/
|
|
class Environment {
|
|
static get homeEnvVar() {
|
|
return this.getEnvironmentVariable(Constants$1.ENVIRONMENT.HOME);
|
|
}
|
|
static get lognameEnvVar() {
|
|
return this.getEnvironmentVariable(Constants$1.ENVIRONMENT.LOGNAME);
|
|
}
|
|
static get userEnvVar() {
|
|
return this.getEnvironmentVariable(Constants$1.ENVIRONMENT.USER);
|
|
}
|
|
static get lnameEnvVar() {
|
|
return this.getEnvironmentVariable(Constants$1.ENVIRONMENT.LNAME);
|
|
}
|
|
static get usernameEnvVar() {
|
|
return this.getEnvironmentVariable(Constants$1.ENVIRONMENT.USERNAME);
|
|
}
|
|
static getEnvironmentVariable(name) {
|
|
return process.env[name] || "";
|
|
}
|
|
static getEnvironmentPlatform() {
|
|
return process.platform;
|
|
}
|
|
static isWindowsPlatform() {
|
|
return this.getEnvironmentPlatform() === Platform.WINDOWS;
|
|
}
|
|
static isLinuxPlatform() {
|
|
return this.getEnvironmentPlatform() === Platform.LINUX;
|
|
}
|
|
static isMacPlatform() {
|
|
return this.getEnvironmentPlatform() === Platform.MACOS;
|
|
}
|
|
static isLinuxRootUser() {
|
|
if (typeof process.getuid !== "function") {
|
|
return false;
|
|
}
|
|
return process.getuid() === Constants$1.LINUX_ROOT_USER_GUID;
|
|
}
|
|
static getUserRootDirectory() {
|
|
return !this.isWindowsPlatform()
|
|
? this.getUserHomeDirOnUnix()
|
|
: this.getUserHomeDirOnWindows();
|
|
}
|
|
static getUserHomeDirOnWindows() {
|
|
return this.getEnvironmentVariable(Constants$1.ENVIRONMENT.LOCAL_APPLICATION_DATA);
|
|
}
|
|
static getUserHomeDirOnUnix() {
|
|
if (this.isWindowsPlatform()) {
|
|
throw PersistenceError.createNotSupportedError("Getting the user home directory for unix is not supported in windows");
|
|
}
|
|
if (this.homeEnvVar) {
|
|
return this.homeEnvVar;
|
|
}
|
|
let username = null;
|
|
if (this.lognameEnvVar) {
|
|
username = this.lognameEnvVar;
|
|
}
|
|
else if (this.userEnvVar) {
|
|
username = this.userEnvVar;
|
|
}
|
|
else if (this.lnameEnvVar) {
|
|
username = this.lnameEnvVar;
|
|
}
|
|
else if (this.usernameEnvVar) {
|
|
username = this.usernameEnvVar;
|
|
}
|
|
if (this.isMacPlatform()) {
|
|
return username ? path.join("/Users", username) : null;
|
|
}
|
|
else if (this.isLinuxPlatform()) {
|
|
if (this.isLinuxRootUser()) {
|
|
return "/root";
|
|
}
|
|
else {
|
|
return username ? path.join("/home", username) : null;
|
|
}
|
|
}
|
|
else {
|
|
throw PersistenceError.createNotSupportedError("Getting the user home directory for unix is not supported in windows");
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License.
|
|
*/
|
|
class PersistenceCreator {
|
|
static async createPersistence(config) {
|
|
let peristence;
|
|
// On Windows, uses a DPAPI encrypted file
|
|
if (Environment.isWindowsPlatform()) {
|
|
if (!config.cachePath || !config.dataProtectionScope) {
|
|
throw PersistenceError.createPersistenceNotValidatedError("Cache path and/or data protection scope not provided for the FilePersistenceWithDataProtection cache plugin");
|
|
}
|
|
peristence = await FilePersistenceWithDataProtection.create(config.cachePath, DataProtectionScope.CurrentUser, undefined, config.loggerOptions);
|
|
}
|
|
// On Mac, uses keychain.
|
|
else if (Environment.isMacPlatform()) {
|
|
if (!config.cachePath ||
|
|
!config.serviceName ||
|
|
!config.accountName) {
|
|
throw PersistenceError.createPersistenceNotValidatedError("Cache path, service name and/or account name not provided for the KeychainPersistence cache plugin");
|
|
}
|
|
peristence = await KeychainPersistence.create(config.cachePath, config.serviceName, config.accountName, config.loggerOptions);
|
|
}
|
|
// On Linux, uses libsecret to store to secret service. Libsecret has to be installed.
|
|
else if (Environment.isLinuxPlatform()) {
|
|
if (!config.cachePath ||
|
|
!config.serviceName ||
|
|
!config.accountName) {
|
|
throw PersistenceError.createPersistenceNotValidatedError("Cache path, service name and/or account name not provided for the LibSecretPersistence cache plugin");
|
|
}
|
|
peristence = await LibSecretPersistence.create(config.cachePath, config.serviceName, config.accountName, config.loggerOptions);
|
|
}
|
|
else {
|
|
throw PersistenceError.createNotSupportedError("The current environment is not supported by msal-node-extensions yet.");
|
|
}
|
|
await peristence.verifyPersistence().catch(async (e) => {
|
|
if (Environment.isLinuxPlatform() &&
|
|
config.usePlaintextFileOnLinux) {
|
|
if (!config.cachePath) {
|
|
throw PersistenceError.createPersistenceNotValidatedError("Cache path not provided for the FilePersistence cache plugin");
|
|
}
|
|
peristence = await FilePersistence.create(config.cachePath, config.loggerOptions);
|
|
const isFilePersistenceVerified = await peristence.verifyPersistence();
|
|
if (isFilePersistenceVerified) {
|
|
return peristence;
|
|
}
|
|
throw PersistenceError.createPersistenceNotVerifiedError("Persistence could not be verified");
|
|
}
|
|
else {
|
|
throw e;
|
|
}
|
|
});
|
|
return peristence;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License.
|
|
*/
|
|
class NativeAuthError extends AuthError {
|
|
constructor(errorStatus, errorContext, errorCode, errorTag) {
|
|
super(errorStatus, errorContext);
|
|
this.name = "NativeAuthError";
|
|
this.statusCode = errorCode;
|
|
this.tag = errorTag;
|
|
Object.setPrototypeOf(this, NativeAuthError.prototype);
|
|
}
|
|
}
|
|
|
|
/* eslint-disable header/header */
|
|
const name = "@azure/msal-node-extensions";
|
|
const version = "1.5.17";
|
|
|
|
/*
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License.
|
|
*/
|
|
class NativeBrokerPlugin {
|
|
constructor() {
|
|
const defaultLoggerOptions = {
|
|
loggerCallback: () => {
|
|
// Empty logger callback
|
|
},
|
|
piiLoggingEnabled: false,
|
|
};
|
|
this.logger = new Logger(defaultLoggerOptions, name, version); // Default logger
|
|
this.isBrokerAvailable = msalNodeRuntime.msalNodeRuntime.StartupError ? false : true;
|
|
}
|
|
setLogger(loggerOptions) {
|
|
this.logger = new Logger(loggerOptions, name, version);
|
|
const logCallback = (message, logLevel, containsPii) => {
|
|
switch (logLevel) {
|
|
case msalNodeRuntime.LogLevel.Trace:
|
|
if (containsPii) {
|
|
this.logger.tracePii(message);
|
|
}
|
|
else {
|
|
this.logger.trace(message);
|
|
}
|
|
break;
|
|
case msalNodeRuntime.LogLevel.Debug:
|
|
if (containsPii) {
|
|
this.logger.tracePii(message);
|
|
}
|
|
else {
|
|
this.logger.trace(message);
|
|
}
|
|
break;
|
|
case msalNodeRuntime.LogLevel.Info:
|
|
if (containsPii) {
|
|
this.logger.infoPii(message);
|
|
}
|
|
else {
|
|
this.logger.info(message);
|
|
}
|
|
break;
|
|
case msalNodeRuntime.LogLevel.Warning:
|
|
if (containsPii) {
|
|
this.logger.warningPii(message);
|
|
}
|
|
else {
|
|
this.logger.warning(message);
|
|
}
|
|
break;
|
|
case msalNodeRuntime.LogLevel.Error:
|
|
if (containsPii) {
|
|
this.logger.errorPii(message);
|
|
}
|
|
else {
|
|
this.logger.error(message);
|
|
}
|
|
break;
|
|
case msalNodeRuntime.LogLevel.Fatal:
|
|
if (containsPii) {
|
|
this.logger.errorPii(message);
|
|
}
|
|
else {
|
|
this.logger.error(message);
|
|
}
|
|
break;
|
|
default:
|
|
if (containsPii) {
|
|
this.logger.infoPii(message);
|
|
}
|
|
else {
|
|
this.logger.info(message);
|
|
}
|
|
break;
|
|
}
|
|
};
|
|
try {
|
|
msalNodeRuntime.msalNodeRuntime.RegisterLogger(logCallback, loggerOptions.piiLoggingEnabled || false);
|
|
}
|
|
catch (e) {
|
|
const wrappedError = this.wrapError(e);
|
|
if (wrappedError) {
|
|
throw wrappedError;
|
|
}
|
|
}
|
|
}
|
|
async getAccountById(accountId, correlationId) {
|
|
this.logger.trace("NativeBrokerPlugin - getAccountById called", correlationId);
|
|
const readAccountResult = await this.readAccountById(accountId, correlationId);
|
|
return this.generateAccountInfo(readAccountResult.account);
|
|
}
|
|
async getAllAccounts(clientId, correlationId) {
|
|
this.logger.trace("NativeBrokerPlugin - getAllAccounts called", correlationId);
|
|
return new Promise((resolve, reject) => {
|
|
const resultCallback = (result) => {
|
|
try {
|
|
result.CheckError();
|
|
}
|
|
catch (e) {
|
|
const wrappedError = this.wrapError(e);
|
|
if (wrappedError) {
|
|
reject(wrappedError);
|
|
return;
|
|
}
|
|
}
|
|
const accountInfoResult = [];
|
|
result.accounts.forEach((account) => {
|
|
accountInfoResult.push(this.generateAccountInfo(account));
|
|
});
|
|
resolve(accountInfoResult);
|
|
};
|
|
try {
|
|
msalNodeRuntime.msalNodeRuntime.DiscoverAccountsAsync(clientId, correlationId, resultCallback);
|
|
}
|
|
catch (e) {
|
|
const wrappedError = this.wrapError(e);
|
|
if (wrappedError) {
|
|
reject(wrappedError);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
async acquireTokenSilent(request) {
|
|
this.logger.trace("NativeBrokerPlugin - acquireTokenSilent called", request.correlationId);
|
|
const authParams = this.generateRequestParameters(request);
|
|
const account = await this.getAccount(request);
|
|
return new Promise((resolve, reject) => {
|
|
const resultCallback = (result) => {
|
|
try {
|
|
result.CheckError();
|
|
}
|
|
catch (e) {
|
|
const wrappedError = this.wrapError(e);
|
|
if (wrappedError) {
|
|
reject(wrappedError);
|
|
return;
|
|
}
|
|
}
|
|
const authenticationResult = this.getAuthenticationResult(request, result);
|
|
resolve(authenticationResult);
|
|
};
|
|
try {
|
|
if (account) {
|
|
msalNodeRuntime.msalNodeRuntime.AcquireTokenSilentlyAsync(authParams, request.correlationId, account, resultCallback);
|
|
}
|
|
else {
|
|
msalNodeRuntime.msalNodeRuntime.SignInSilentlyAsync(authParams, request.correlationId, resultCallback);
|
|
}
|
|
}
|
|
catch (e) {
|
|
const wrappedError = this.wrapError(e);
|
|
if (wrappedError) {
|
|
reject(wrappedError);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
async acquireTokenInteractive(request, providedWindowHandle) {
|
|
this.logger.trace("NativeBrokerPlugin - acquireTokenInteractive called", request.correlationId);
|
|
const authParams = this.generateRequestParameters(request);
|
|
const account = await this.getAccount(request);
|
|
const windowHandle = providedWindowHandle || Buffer.from([0]);
|
|
return new Promise((resolve, reject) => {
|
|
const resultCallback = (result) => {
|
|
try {
|
|
result.CheckError();
|
|
}
|
|
catch (e) {
|
|
const wrappedError = this.wrapError(e);
|
|
if (wrappedError) {
|
|
reject(wrappedError);
|
|
return;
|
|
}
|
|
}
|
|
const authenticationResult = this.getAuthenticationResult(request, result);
|
|
resolve(authenticationResult);
|
|
};
|
|
try {
|
|
switch (request.prompt) {
|
|
case PromptValue.LOGIN:
|
|
case PromptValue.SELECT_ACCOUNT:
|
|
case PromptValue.CREATE:
|
|
this.logger.info("Calling native interop SignInInteractively API", request.correlationId);
|
|
const loginHint = request.loginHint || Constants.EMPTY_STRING;
|
|
msalNodeRuntime.msalNodeRuntime.SignInInteractivelyAsync(windowHandle, authParams, request.correlationId, loginHint, resultCallback);
|
|
break;
|
|
case PromptValue.NONE:
|
|
if (account) {
|
|
this.logger.info("Calling native interop AcquireTokenSilently API", request.correlationId);
|
|
msalNodeRuntime.msalNodeRuntime.AcquireTokenSilentlyAsync(authParams, request.correlationId, account, resultCallback);
|
|
}
|
|
else {
|
|
this.logger.info("Calling native interop SignInSilently API", request.correlationId);
|
|
msalNodeRuntime.msalNodeRuntime.SignInSilentlyAsync(authParams, request.correlationId, resultCallback);
|
|
}
|
|
break;
|
|
default:
|
|
if (account) {
|
|
this.logger.info("Calling native interop AcquireTokenInteractively API", request.correlationId);
|
|
msalNodeRuntime.msalNodeRuntime.AcquireTokenInteractivelyAsync(windowHandle, authParams, request.correlationId, account, resultCallback);
|
|
}
|
|
else {
|
|
this.logger.info("Calling native interop SignIn API", request.correlationId);
|
|
const loginHint = request.loginHint || Constants.EMPTY_STRING;
|
|
msalNodeRuntime.msalNodeRuntime.SignInAsync(windowHandle, authParams, request.correlationId, loginHint, resultCallback);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
catch (e) {
|
|
const wrappedError = this.wrapError(e);
|
|
if (wrappedError) {
|
|
reject(wrappedError);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
async signOut(request) {
|
|
this.logger.trace("NativeBrokerPlugin - signOut called", request.correlationId);
|
|
const account = await this.getAccount(request);
|
|
if (!account) {
|
|
throw createClientAuthError(noAccountFound);
|
|
}
|
|
return new Promise((resolve, reject) => {
|
|
const resultCallback = (result) => {
|
|
try {
|
|
result.CheckError();
|
|
}
|
|
catch (e) {
|
|
const wrappedError = this.wrapError(e);
|
|
if (wrappedError) {
|
|
reject(wrappedError);
|
|
return;
|
|
}
|
|
}
|
|
resolve();
|
|
};
|
|
try {
|
|
msalNodeRuntime.msalNodeRuntime.SignOutSilentlyAsync(request.clientId, request.correlationId, account, resultCallback);
|
|
}
|
|
catch (e) {
|
|
const wrappedError = this.wrapError(e);
|
|
if (wrappedError) {
|
|
reject(wrappedError);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
async getAccount(request) {
|
|
if (request.accountId) {
|
|
const readAccountResult = await this.readAccountById(request.accountId, request.correlationId);
|
|
return readAccountResult.account;
|
|
}
|
|
return null;
|
|
}
|
|
async readAccountById(accountId, correlationId) {
|
|
this.logger.trace("NativeBrokerPlugin - readAccountById called", correlationId);
|
|
return new Promise((resolve, reject) => {
|
|
const resultCallback = (result) => {
|
|
try {
|
|
result.CheckError();
|
|
}
|
|
catch (e) {
|
|
const wrappedError = this.wrapError(e);
|
|
if (wrappedError) {
|
|
reject(wrappedError);
|
|
return;
|
|
}
|
|
}
|
|
resolve(result);
|
|
};
|
|
try {
|
|
msalNodeRuntime.msalNodeRuntime.ReadAccountByIdAsync(accountId, correlationId, resultCallback);
|
|
}
|
|
catch (e) {
|
|
const wrappedError = this.wrapError(e);
|
|
if (wrappedError) {
|
|
reject(wrappedError);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
generateRequestParameters(request) {
|
|
this.logger.trace("NativeBrokerPlugin - generateRequestParameters called", request.correlationId);
|
|
const authParams = new msalNodeRuntime.msalNodeRuntime.AuthParameters();
|
|
try {
|
|
authParams.CreateAuthParameters(request.clientId, request.authority);
|
|
authParams.SetRedirectUri(request.redirectUri);
|
|
authParams.SetRequestedScopes(request.scopes.join(" "));
|
|
if (request.claims) {
|
|
authParams.SetDecodedClaims(request.claims);
|
|
}
|
|
if (request.authenticationScheme === AuthenticationScheme.POP) {
|
|
if (!request.resourceRequestMethod ||
|
|
!request.resourceRequestUri) {
|
|
throw new Error("Authentication Scheme set to POP but one or more of the following parameters are missing: resourceRequestMethod, resourceRequestUri");
|
|
}
|
|
const resourceUrl = new URL(request.resourceRequestUri);
|
|
authParams.SetPopParams(request.resourceRequestMethod, resourceUrl.host, resourceUrl.pathname, request.shrNonce || "");
|
|
}
|
|
if (request.extraParameters) {
|
|
Object.entries(request.extraParameters).forEach(([key, value]) => {
|
|
authParams.SetAdditionalParameter(key, value);
|
|
});
|
|
}
|
|
const skus = request.extraParameters &&
|
|
request.extraParameters[X_CLIENT_EXTRA_SKU]
|
|
?.length
|
|
? request.extraParameters[X_CLIENT_EXTRA_SKU]
|
|
: "";
|
|
authParams.SetAdditionalParameter(X_CLIENT_EXTRA_SKU, ServerTelemetryManager.makeExtraSkuString({
|
|
skus,
|
|
extensionName: "msal.node.ext",
|
|
extensionVersion: version,
|
|
}));
|
|
}
|
|
catch (e) {
|
|
const wrappedError = this.wrapError(e);
|
|
if (wrappedError) {
|
|
throw wrappedError;
|
|
}
|
|
}
|
|
return authParams;
|
|
}
|
|
getAuthenticationResult(request, authResult) {
|
|
this.logger.trace("NativeBrokerPlugin - getAuthenticationResult called", request.correlationId);
|
|
let fromCache = false;
|
|
try {
|
|
const telemetryJSON = JSON.parse(authResult.telemetryData);
|
|
fromCache = !!telemetryJSON["is_cache"];
|
|
}
|
|
catch (e) {
|
|
this.logger.error("NativeBrokerPlugin: getAuthenticationResult - Error parsing telemetry data. Could not determine if response came from cache.", request.correlationId);
|
|
}
|
|
let idTokenClaims;
|
|
try {
|
|
idTokenClaims = JSON.parse(authResult.idToken);
|
|
}
|
|
catch (e) {
|
|
throw new Error("Unable to parse idToken claims");
|
|
}
|
|
const accountInfo = this.generateAccountInfo(authResult.account, idTokenClaims);
|
|
let accessToken;
|
|
let tokenType;
|
|
if (authResult.isPopAuthorization) {
|
|
// Header includes 'pop ' prefix
|
|
accessToken = authResult.authorizationHeader.split(" ")[1];
|
|
tokenType = AuthenticationScheme.POP;
|
|
}
|
|
else {
|
|
accessToken = authResult.accessToken;
|
|
tokenType = AuthenticationScheme.BEARER;
|
|
}
|
|
const result = {
|
|
authority: request.authority,
|
|
uniqueId: idTokenClaims.oid || idTokenClaims.sub || "",
|
|
tenantId: idTokenClaims.tid || "",
|
|
scopes: authResult.grantedScopes.split(" "),
|
|
account: accountInfo,
|
|
idToken: authResult.rawIdToken,
|
|
idTokenClaims: idTokenClaims,
|
|
accessToken: accessToken,
|
|
fromCache: fromCache,
|
|
// MsalRuntime expiresOn returned in seconds, converting to Date for AuthenticationResult
|
|
expiresOn: toDateFromSeconds(authResult.expiresOn),
|
|
tokenType: tokenType,
|
|
correlationId: request.correlationId,
|
|
fromNativeBroker: true,
|
|
};
|
|
return result;
|
|
}
|
|
generateAccountInfo(account, idTokenClaims) {
|
|
this.logger.trace("NativeBrokerPlugin - generateAccountInfo called");
|
|
const accountInfo = {
|
|
homeAccountId: account.homeAccountId,
|
|
environment: account.environment,
|
|
tenantId: account.realm,
|
|
username: account.username,
|
|
localAccountId: account.localAccountId,
|
|
name: account.displayName,
|
|
idTokenClaims: idTokenClaims,
|
|
nativeAccountId: account.accountId,
|
|
};
|
|
return accountInfo;
|
|
}
|
|
isMsalRuntimeError(result) {
|
|
return (result.hasOwnProperty("errorCode") ||
|
|
result.hasOwnProperty("errorStatus") ||
|
|
result.hasOwnProperty("errorContext") ||
|
|
result.hasOwnProperty("errorTag"));
|
|
}
|
|
wrapError(error) {
|
|
if (error &&
|
|
typeof error === "object" &&
|
|
this.isMsalRuntimeError(error)) {
|
|
const { errorCode, errorStatus, errorContext, errorTag } = error;
|
|
switch (errorStatus) {
|
|
case msalNodeRuntime.ErrorStatus.InteractionRequired:
|
|
case msalNodeRuntime.ErrorStatus.AccountUnusable:
|
|
return new InteractionRequiredAuthError(ErrorCodes.INTERATION_REQUIRED_ERROR_CODE, errorContext);
|
|
case msalNodeRuntime.ErrorStatus.NoNetwork:
|
|
case msalNodeRuntime.ErrorStatus.NetworkTemporarilyUnavailable:
|
|
return createClientAuthError(noNetworkConnectivity);
|
|
case msalNodeRuntime.ErrorStatus.ServerTemporarilyUnavailable:
|
|
return new ServerError(ErrorCodes.SERVER_UNAVAILABLE, errorContext);
|
|
case msalNodeRuntime.ErrorStatus.UserCanceled:
|
|
return createClientAuthError(userCanceled);
|
|
case msalNodeRuntime.ErrorStatus.AuthorityUntrusted:
|
|
return createClientConfigurationError(untrustedAuthority);
|
|
case msalNodeRuntime.ErrorStatus.UserSwitched:
|
|
// Not an error case, if there's customer demand we can surface this as a response property
|
|
return null;
|
|
case msalNodeRuntime.ErrorStatus.AccountNotFound:
|
|
return createClientAuthError(noAccountFound);
|
|
default:
|
|
return new NativeAuthError(msalNodeRuntime.ErrorStatus[errorStatus], errorContext, errorCode, errorTag);
|
|
}
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
exports.DataProtectionScope = DataProtectionScope;
|
|
exports.Environment = Environment;
|
|
exports.FilePersistence = FilePersistence;
|
|
exports.FilePersistenceWithDataProtection = FilePersistenceWithDataProtection;
|
|
exports.KeychainPersistence = KeychainPersistence;
|
|
exports.LibSecretPersistence = LibSecretPersistence;
|
|
exports.NativeBrokerPlugin = NativeBrokerPlugin;
|
|
exports.PersistenceCachePlugin = PersistenceCachePlugin;
|
|
exports.PersistenceCreator = PersistenceCreator;
|
|
//# sourceMappingURL=msal-node-extensions.cjs.map
|