/*! @azure/msal-node v2.6.0 2023-12-01 */
'use strict';
'use strict';

var msalCommon = require('@azure/msal-common');
var Configuration = require('../config/Configuration.cjs');
var CryptoProvider = require('../crypto/CryptoProvider.cjs');
var NodeStorage = require('../cache/NodeStorage.cjs');
var Constants = require('../utils/Constants.cjs');
var TokenCache = require('../cache/TokenCache.cjs');
var packageMetadata = require('../packageMetadata.cjs');
var NodeAuthError = require('../error/NodeAuthError.cjs');
var UsernamePasswordClient = require('./UsernamePasswordClient.cjs');

/*
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License.
 */
/**
 * Base abstract class for all ClientApplications - public and confidential
 * @public
 */
class ClientApplication {
    /**
     * Constructor for the ClientApplication
     */
    constructor(configuration) {
        this.config = Configuration.buildAppConfiguration(configuration);
        this.cryptoProvider = new CryptoProvider.CryptoProvider();
        this.logger = new msalCommon.Logger(this.config.system.loggerOptions, packageMetadata.name, packageMetadata.version);
        this.storage = new NodeStorage.NodeStorage(this.logger, this.config.auth.clientId, this.cryptoProvider, msalCommon.buildStaticAuthorityOptions(this.config.auth));
        this.tokenCache = new TokenCache.TokenCache(this.storage, this.logger, this.config.cache.cachePlugin);
    }
    /**
     * Creates the URL of the authorization request, letting the user input credentials and consent to the
     * application. The URL targets the /authorize endpoint of the authority configured in the
     * application object.
     *
     * Once the user inputs their credentials and consents, the authority will send a response to the redirect URI
     * sent in the request and should contain an authorization code, which can then be used to acquire tokens via
     * `acquireTokenByCode(AuthorizationCodeRequest)`.
     */
    async getAuthCodeUrl(request) {
        this.logger.info("getAuthCodeUrl called", request.correlationId);
        const validRequest = {
            ...request,
            ...(await this.initializeBaseRequest(request)),
            responseMode: request.responseMode || msalCommon.ResponseMode.QUERY,
            authenticationScheme: msalCommon.AuthenticationScheme.BEARER,
        };
        const authClientConfig = await this.buildOauthClientConfiguration(validRequest.authority, validRequest.correlationId, undefined, undefined, request.azureCloudOptions);
        const authorizationCodeClient = new msalCommon.AuthorizationCodeClient(authClientConfig);
        this.logger.verbose("Auth code client created", validRequest.correlationId);
        return authorizationCodeClient.getAuthCodeUrl(validRequest);
    }
    /**
     * Acquires a token by exchanging the Authorization Code received from the first step of OAuth2.0
     * Authorization Code flow.
     *
     * `getAuthCodeUrl(AuthorizationCodeUrlRequest)` can be used to create the URL for the first step of OAuth2.0
     * Authorization Code flow. Ensure that values for redirectUri and scopes in AuthorizationCodeUrlRequest and
     * AuthorizationCodeRequest are the same.
     */
    async acquireTokenByCode(request, authCodePayLoad) {
        this.logger.info("acquireTokenByCode called");
        if (request.state && authCodePayLoad) {
            this.logger.info("acquireTokenByCode - validating state");
            this.validateState(request.state, authCodePayLoad.state || "");
            // eslint-disable-next-line no-param-reassign
            authCodePayLoad = { ...authCodePayLoad, state: "" };
        }
        const validRequest = {
            ...request,
            ...(await this.initializeBaseRequest(request)),
            authenticationScheme: msalCommon.AuthenticationScheme.BEARER,
        };
        const serverTelemetryManager = this.initializeServerTelemetryManager(Constants.ApiId.acquireTokenByCode, validRequest.correlationId);
        try {
            const authClientConfig = await this.buildOauthClientConfiguration(validRequest.authority, validRequest.correlationId, serverTelemetryManager, undefined, request.azureCloudOptions);
            const authorizationCodeClient = new msalCommon.AuthorizationCodeClient(authClientConfig);
            this.logger.verbose("Auth code client created", validRequest.correlationId);
            return await authorizationCodeClient.acquireToken(validRequest, authCodePayLoad);
        }
        catch (e) {
            if (e instanceof msalCommon.AuthError) {
                e.setCorrelationId(validRequest.correlationId);
            }
            serverTelemetryManager.cacheFailedRequest(e);
            throw e;
        }
    }
    /**
     * Acquires a token by exchanging the refresh token provided for a new set of tokens.
     *
     * This API is provided only for scenarios where you would like to migrate from ADAL to MSAL. Otherwise, it is
     * recommended that you use `acquireTokenSilent()` for silent scenarios. When using `acquireTokenSilent()`, MSAL will
     * handle the caching and refreshing of tokens automatically.
     */
    async acquireTokenByRefreshToken(request) {
        this.logger.info("acquireTokenByRefreshToken called", request.correlationId);
        const validRequest = {
            ...request,
            ...(await this.initializeBaseRequest(request)),
            authenticationScheme: msalCommon.AuthenticationScheme.BEARER,
        };
        const serverTelemetryManager = this.initializeServerTelemetryManager(Constants.ApiId.acquireTokenByRefreshToken, validRequest.correlationId);
        try {
            const refreshTokenClientConfig = await this.buildOauthClientConfiguration(validRequest.authority, validRequest.correlationId, serverTelemetryManager, undefined, request.azureCloudOptions);
            const refreshTokenClient = new msalCommon.RefreshTokenClient(refreshTokenClientConfig);
            this.logger.verbose("Refresh token client created", validRequest.correlationId);
            return await refreshTokenClient.acquireToken(validRequest);
        }
        catch (e) {
            if (e instanceof msalCommon.AuthError) {
                e.setCorrelationId(validRequest.correlationId);
            }
            serverTelemetryManager.cacheFailedRequest(e);
            throw e;
        }
    }
    /**
     * Acquires a token silently when a user specifies the account the token is requested for.
     *
     * This API expects the user to provide an account object and looks into the cache to retrieve the token if present.
     * There is also an optional "forceRefresh" boolean the user can send to bypass the cache for access_token and id_token.
     * In case the refresh_token is expired or not found, an error is thrown
     * and the guidance is for the user to call any interactive token acquisition API (eg: `acquireTokenByCode()`).
     */
    async acquireTokenSilent(request) {
        const validRequest = {
            ...request,
            ...(await this.initializeBaseRequest(request)),
            forceRefresh: request.forceRefresh || false,
        };
        const serverTelemetryManager = this.initializeServerTelemetryManager(Constants.ApiId.acquireTokenSilent, validRequest.correlationId, validRequest.forceRefresh);
        try {
            const silentFlowClientConfig = await this.buildOauthClientConfiguration(validRequest.authority, validRequest.correlationId, serverTelemetryManager, undefined, request.azureCloudOptions);
            const silentFlowClient = new msalCommon.SilentFlowClient(silentFlowClientConfig);
            this.logger.verbose("Silent flow client created", validRequest.correlationId);
            return await silentFlowClient.acquireToken(validRequest);
        }
        catch (e) {
            if (e instanceof msalCommon.AuthError) {
                e.setCorrelationId(validRequest.correlationId);
            }
            serverTelemetryManager.cacheFailedRequest(e);
            throw e;
        }
    }
    /**
     * Acquires tokens with password grant by exchanging client applications username and password for credentials
     *
     * The latest OAuth 2.0 Security Best Current Practice disallows the password grant entirely.
     * More details on this recommendation at https://tools.ietf.org/html/draft-ietf-oauth-security-topics-13#section-3.4
     * Microsoft's documentation and recommendations are at:
     * https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-authentication-flows#usernamepassword
     *
     * @param request - UsenamePasswordRequest
     */
    async acquireTokenByUsernamePassword(request) {
        this.logger.info("acquireTokenByUsernamePassword called", request.correlationId);
        const validRequest = {
            ...request,
            ...(await this.initializeBaseRequest(request)),
        };
        const serverTelemetryManager = this.initializeServerTelemetryManager(Constants.ApiId.acquireTokenByUsernamePassword, validRequest.correlationId);
        try {
            const usernamePasswordClientConfig = await this.buildOauthClientConfiguration(validRequest.authority, validRequest.correlationId, serverTelemetryManager, undefined, request.azureCloudOptions);
            const usernamePasswordClient = new UsernamePasswordClient.UsernamePasswordClient(usernamePasswordClientConfig);
            this.logger.verbose("Username password client created", validRequest.correlationId);
            return await usernamePasswordClient.acquireToken(validRequest);
        }
        catch (e) {
            if (e instanceof msalCommon.AuthError) {
                e.setCorrelationId(validRequest.correlationId);
            }
            serverTelemetryManager.cacheFailedRequest(e);
            throw e;
        }
    }
    /**
     * Gets the token cache for the application.
     */
    getTokenCache() {
        this.logger.info("getTokenCache called");
        return this.tokenCache;
    }
    /**
     * Validates OIDC state by comparing the user cached state with the state received from the server.
     *
     * This API is provided for scenarios where you would use OAuth2.0 state parameter to mitigate against
     * CSRF attacks.
     * For more information about state, visit https://datatracker.ietf.org/doc/html/rfc6819#section-3.6.
     * @param state
     * @param cachedState
     */
    validateState(state, cachedState) {
        if (!state) {
            throw NodeAuthError.NodeAuthError.createStateNotFoundError();
        }
        if (state !== cachedState) {
            throw msalCommon.createClientAuthError(msalCommon.ClientAuthErrorCodes.stateMismatch);
        }
    }
    /**
     * Returns the logger instance
     */
    getLogger() {
        return this.logger;
    }
    /**
     * Replaces the default logger set in configurations with new Logger with new configurations
     * @param logger - Logger instance
     */
    setLogger(logger) {
        this.logger = logger;
    }
    /**
     * Builds the common configuration to be passed to the common component based on the platform configurarion
     * @param authority - user passed authority in configuration
     * @param serverTelemetryManager - initializes servertelemetry if passed
     */
    async buildOauthClientConfiguration(authority, requestCorrelationId, serverTelemetryManager, azureRegionConfiguration, azureCloudOptions) {
        this.logger.verbose("buildOauthClientConfiguration called", requestCorrelationId);
        // precedence - azureCloudInstance + tenant >> authority and request  >> config
        const userAzureCloudOptions = azureCloudOptions
            ? azureCloudOptions
            : this.config.auth.azureCloudOptions;
        // using null assertion operator as we ensure that all config values have default values in buildConfiguration()
        const discoveredAuthority = await this.createAuthority(authority, azureRegionConfiguration, requestCorrelationId, userAzureCloudOptions);
        this.logger.info(`Building oauth client configuration with the following authority: ${discoveredAuthority.tokenEndpoint}.`, requestCorrelationId);
        serverTelemetryManager?.updateRegionDiscoveryMetadata(discoveredAuthority.regionDiscoveryMetadata);
        const clientConfiguration = {
            authOptions: {
                clientId: this.config.auth.clientId,
                authority: discoveredAuthority,
                clientCapabilities: this.config.auth.clientCapabilities,
            },
            loggerOptions: {
                logLevel: this.config.system.loggerOptions.logLevel,
                loggerCallback: this.config.system.loggerOptions.loggerCallback,
                piiLoggingEnabled: this.config.system.loggerOptions.piiLoggingEnabled,
                correlationId: requestCorrelationId,
            },
            cacheOptions: {
                claimsBasedCachingEnabled: this.config.cache.claimsBasedCachingEnabled,
            },
            cryptoInterface: this.cryptoProvider,
            networkInterface: this.config.system.networkClient,
            storageInterface: this.storage,
            serverTelemetryManager: serverTelemetryManager,
            clientCredentials: {
                clientSecret: this.clientSecret,
                clientAssertion: this.clientAssertion
                    ? this.getClientAssertion(discoveredAuthority)
                    : undefined,
            },
            libraryInfo: {
                sku: Constants.Constants.MSAL_SKU,
                version: packageMetadata.version,
                cpu: process.arch || msalCommon.Constants.EMPTY_STRING,
                os: process.platform || msalCommon.Constants.EMPTY_STRING,
            },
            telemetry: this.config.telemetry,
            persistencePlugin: this.config.cache.cachePlugin,
            serializableCache: this.tokenCache,
        };
        return clientConfiguration;
    }
    getClientAssertion(authority) {
        return {
            assertion: this.clientAssertion.getJwt(this.cryptoProvider, this.config.auth.clientId, authority.tokenEndpoint),
            assertionType: Constants.Constants.JWT_BEARER_ASSERTION_TYPE,
        };
    }
    /**
     * Generates a request with the default scopes & generates a correlationId.
     * @param authRequest - BaseAuthRequest for initialization
     */
    async initializeBaseRequest(authRequest) {
        this.logger.verbose("initializeRequestScopes called", authRequest.correlationId);
        // Default authenticationScheme to Bearer, log that POP isn't supported yet
        if (authRequest.authenticationScheme &&
            authRequest.authenticationScheme === msalCommon.AuthenticationScheme.POP) {
            this.logger.verbose("Authentication Scheme 'pop' is not supported yet, setting Authentication Scheme to 'Bearer' for request", authRequest.correlationId);
        }
        authRequest.authenticationScheme = msalCommon.AuthenticationScheme.BEARER;
        // Set requested claims hash if claims-based caching is enabled and claims were requested
        if (this.config.cache.claimsBasedCachingEnabled &&
            authRequest.claims &&
            // Checks for empty stringified object "{}" which doesn't qualify as requested claims
            !msalCommon.StringUtils.isEmptyObj(authRequest.claims)) {
            authRequest.requestedClaimsHash =
                await this.cryptoProvider.hashString(authRequest.claims);
        }
        return {
            ...authRequest,
            scopes: [
                ...((authRequest && authRequest.scopes) || []),
                ...msalCommon.OIDC_DEFAULT_SCOPES,
            ],
            correlationId: (authRequest && authRequest.correlationId) ||
                this.cryptoProvider.createNewGuid(),
            authority: authRequest.authority || this.config.auth.authority,
        };
    }
    /**
     * Initializes the server telemetry payload
     * @param apiId - Id for a specific request
     * @param correlationId - GUID
     * @param forceRefresh - boolean to indicate network call
     */
    initializeServerTelemetryManager(apiId, correlationId, forceRefresh) {
        const telemetryPayload = {
            clientId: this.config.auth.clientId,
            correlationId: correlationId,
            apiId: apiId,
            forceRefresh: forceRefresh || false,
        };
        return new msalCommon.ServerTelemetryManager(telemetryPayload, this.storage);
    }
    /**
     * Create authority instance. If authority not passed in request, default to authority set on the application
     * object. If no authority set in application object, then default to common authority.
     * @param authorityString - authority from user configuration
     */
    async createAuthority(authorityString, azureRegionConfiguration, requestCorrelationId, azureCloudOptions) {
        this.logger.verbose("createAuthority called", requestCorrelationId);
        // build authority string based on auth params - azureCloudInstance is prioritized if provided
        const authorityUrl = msalCommon.Authority.generateAuthority(authorityString, azureCloudOptions);
        const authorityOptions = {
            protocolMode: this.config.auth.protocolMode,
            knownAuthorities: this.config.auth.knownAuthorities,
            cloudDiscoveryMetadata: this.config.auth.cloudDiscoveryMetadata,
            authorityMetadata: this.config.auth.authorityMetadata,
            azureRegionConfiguration,
            skipAuthorityMetadataCache: this.config.auth.skipAuthorityMetadataCache,
        };
        return msalCommon.AuthorityFactory.createDiscoveredInstance(authorityUrl, this.config.system.networkClient, this.storage, authorityOptions, this.logger);
    }
    /**
     * Clear the cache
     */
    clearCache() {
        void this.storage.clear();
    }
}

exports.ClientApplication = ClientApplication;
//# sourceMappingURL=ClientApplication.cjs.map
