/*! @azure/msal-node v2.5.1 2023-11-07 */
'use strict';
'use strict';

var Constants = require('../utils/Constants.cjs');
var msalCommon = require('@azure/msal-common');
var ClientApplication = require('./ClientApplication.cjs');
var NodeAuthError = require('../error/NodeAuthError.cjs');
var LoopbackClient = require('../network/LoopbackClient.cjs');
var DeviceCodeClient = require('./DeviceCodeClient.cjs');

/*
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License.
 */
/**
 * This class is to be used to acquire tokens for public client applications (desktop, mobile). Public client applications
 * are not trusted to safely store application secrets, and therefore can only request tokens in the name of an user.
 * @public
 */
class PublicClientApplication extends ClientApplication.ClientApplication {
    /**
     * Important attributes in the Configuration object for auth are:
     * - clientID: the application ID of your application. You can obtain one by registering your application with our Application registration portal.
     * - authority: the authority URL for your application.
     *
     * AAD authorities are of the form https://login.microsoftonline.com/\{Enter_the_Tenant_Info_Here\}.
     * - If your application supports Accounts in one organizational directory, replace "Enter_the_Tenant_Info_Here" value with the Tenant Id or Tenant name (for example, contoso.microsoft.com).
     * - If your application supports Accounts in any organizational directory, replace "Enter_the_Tenant_Info_Here" value with organizations.
     * - If your application supports Accounts in any organizational directory and personal Microsoft accounts, replace "Enter_the_Tenant_Info_Here" value with common.
     * - To restrict support to Personal Microsoft accounts only, replace "Enter_the_Tenant_Info_Here" value with consumers.
     *
     * Azure B2C authorities are of the form https://\{instance\}/\{tenant\}/\{policy\}. Each policy is considered
     * its own authority. You will have to set the all of the knownAuthorities at the time of the client application
     * construction.
     *
     * ADFS authorities are of the form https://\{instance\}/adfs.
     */
    constructor(configuration) {
        super(configuration);
        if (this.config.broker.nativeBrokerPlugin) {
            if (this.config.broker.nativeBrokerPlugin.isBrokerAvailable) {
                this.nativeBrokerPlugin = this.config.broker.nativeBrokerPlugin;
                this.nativeBrokerPlugin.setLogger(this.config.system.loggerOptions);
            }
            else {
                this.logger.warning("NativeBroker implementation was provided but the broker is unavailable.");
            }
        }
    }
    /**
     * Acquires a token from the authority using OAuth2.0 device code flow.
     * This flow is designed for devices that do not have access to a browser or have input constraints.
     * The authorization server issues a DeviceCode object with a verification code, an end-user code,
     * and the end-user verification URI. The DeviceCode object is provided through a callback, and the end-user should be
     * instructed to use another device to navigate to the verification URI to input credentials.
     * Since the client cannot receive incoming requests, it polls the authorization server repeatedly
     * until the end-user completes input of credentials.
     */
    async acquireTokenByDeviceCode(request) {
        this.logger.info("acquireTokenByDeviceCode called", request.correlationId);
        const validRequest = Object.assign(request, await this.initializeBaseRequest(request));
        const serverTelemetryManager = this.initializeServerTelemetryManager(Constants.ApiId.acquireTokenByDeviceCode, validRequest.correlationId);
        try {
            const deviceCodeConfig = await this.buildOauthClientConfiguration(validRequest.authority, validRequest.correlationId, serverTelemetryManager, undefined, request.azureCloudOptions);
            const deviceCodeClient = new DeviceCodeClient.DeviceCodeClient(deviceCodeConfig);
            this.logger.verbose("Device code client created", validRequest.correlationId);
            return deviceCodeClient.acquireToken(validRequest);
        }
        catch (e) {
            if (e instanceof msalCommon.AuthError) {
                e.setCorrelationId(validRequest.correlationId);
            }
            serverTelemetryManager.cacheFailedRequest(e);
            throw e;
        }
    }
    /**
     * Acquires a token interactively via the browser by requesting an authorization code then exchanging it for a token.
     */
    async acquireTokenInteractive(request) {
        const correlationId = request.correlationId || this.cryptoProvider.createNewGuid();
        this.logger.trace("acquireTokenInteractive called", correlationId);
        const { openBrowser, successTemplate, errorTemplate, windowHandle, loopbackClient: customLoopbackClient, ...remainingProperties } = request;
        if (this.nativeBrokerPlugin) {
            const brokerRequest = {
                ...remainingProperties,
                clientId: this.config.auth.clientId,
                scopes: request.scopes || msalCommon.OIDC_DEFAULT_SCOPES,
                redirectUri: `${Constants.Constants.HTTP_PROTOCOL}${Constants.Constants.LOCALHOST}`,
                authority: request.authority || this.config.auth.authority,
                correlationId: correlationId,
                extraParameters: {
                    ...remainingProperties.extraQueryParameters,
                    ...remainingProperties.tokenQueryParameters,
                },
                accountId: remainingProperties.account?.nativeAccountId,
            };
            return this.nativeBrokerPlugin.acquireTokenInteractive(brokerRequest, windowHandle);
        }
        const { verifier, challenge } = await this.cryptoProvider.generatePkceCodes();
        const loopbackClient = customLoopbackClient || new LoopbackClient.LoopbackClient();
        let authCodeResponse = {};
        let authCodeListenerError = null;
        try {
            const authCodeListener = loopbackClient
                .listenForAuthCode(successTemplate, errorTemplate)
                .then((response) => {
                authCodeResponse = response;
            })
                .catch((e) => {
                // Store the promise instead of throwing so we can control when its thrown
                authCodeListenerError = e;
            });
            // Wait for server to be listening
            const redirectUri = await this.waitForRedirectUri(loopbackClient);
            const validRequest = {
                ...remainingProperties,
                correlationId: correlationId,
                scopes: request.scopes || msalCommon.OIDC_DEFAULT_SCOPES,
                redirectUri: redirectUri,
                responseMode: msalCommon.ResponseMode.QUERY,
                codeChallenge: challenge,
                codeChallengeMethod: msalCommon.CodeChallengeMethodValues.S256,
            };
            const authCodeUrl = await this.getAuthCodeUrl(validRequest);
            await openBrowser(authCodeUrl);
            await authCodeListener;
            if (authCodeListenerError) {
                throw authCodeListenerError;
            }
            if (authCodeResponse.error) {
                throw new msalCommon.ServerError(authCodeResponse.error, authCodeResponse.error_description, authCodeResponse.suberror);
            }
            else if (!authCodeResponse.code) {
                throw NodeAuthError.NodeAuthError.createNoAuthCodeInResponseError();
            }
            const clientInfo = authCodeResponse.client_info;
            const tokenRequest = {
                code: authCodeResponse.code,
                codeVerifier: verifier,
                clientInfo: clientInfo || msalCommon.Constants.EMPTY_STRING,
                ...validRequest,
            };
            return await this.acquireTokenByCode(tokenRequest); // Await this so the server doesn't close prematurely
        }
        finally {
            loopbackClient.closeServer();
        }
    }
    /**
     * Returns a token retrieved either from the cache or by exchanging the refresh token for a fresh access token. If brokering is enabled the token request will be serviced by the broker.
     * @param request
     * @returns
     */
    async acquireTokenSilent(request) {
        const correlationId = request.correlationId || this.cryptoProvider.createNewGuid();
        this.logger.trace("acquireTokenSilent called", correlationId);
        if (this.nativeBrokerPlugin) {
            const brokerRequest = {
                ...request,
                clientId: this.config.auth.clientId,
                scopes: request.scopes || msalCommon.OIDC_DEFAULT_SCOPES,
                redirectUri: `${Constants.Constants.HTTP_PROTOCOL}${Constants.Constants.LOCALHOST}`,
                authority: request.authority || this.config.auth.authority,
                correlationId: correlationId,
                extraParameters: request.tokenQueryParameters,
                accountId: request.account.nativeAccountId,
                forceRefresh: request.forceRefresh || false,
            };
            return this.nativeBrokerPlugin.acquireTokenSilent(brokerRequest);
        }
        return super.acquireTokenSilent(request);
    }
    /**
     * Removes cache artifacts associated with the given account
     * @param request
     * @returns
     */
    async signOut(request) {
        if (this.nativeBrokerPlugin && request.account.nativeAccountId) {
            const signoutRequest = {
                clientId: this.config.auth.clientId,
                accountId: request.account.nativeAccountId,
                correlationId: request.correlationId ||
                    this.cryptoProvider.createNewGuid(),
            };
            await this.nativeBrokerPlugin.signOut(signoutRequest);
        }
        await this.getTokenCache().removeAccount(request.account);
    }
    /**
     * Returns all cached accounts for this application. If brokering is enabled this request will be serviced by the broker.
     * @returns
     */
    async getAllAccounts() {
        if (this.nativeBrokerPlugin) {
            const correlationId = this.cryptoProvider.createNewGuid();
            return this.nativeBrokerPlugin.getAllAccounts(this.config.auth.clientId, correlationId);
        }
        return this.getTokenCache().getAllAccounts();
    }
    /**
     * Attempts to retrieve the redirectUri from the loopback server. If the loopback server does not start listening for requests within the timeout this will throw.
     * @param loopbackClient
     * @returns
     */
    async waitForRedirectUri(loopbackClient) {
        return new Promise((resolve, reject) => {
            let ticks = 0;
            const id = setInterval(() => {
                if (Constants.LOOPBACK_SERVER_CONSTANTS.TIMEOUT_MS /
                    Constants.LOOPBACK_SERVER_CONSTANTS.INTERVAL_MS <
                    ticks) {
                    clearInterval(id);
                    reject(NodeAuthError.NodeAuthError.createLoopbackServerTimeoutError());
                    return;
                }
                try {
                    const r = loopbackClient.getRedirectUri();
                    clearInterval(id);
                    resolve(r);
                    return;
                }
                catch (e) {
                    if (e instanceof msalCommon.AuthError &&
                        e.errorCode ===
                            NodeAuthError.NodeAuthErrorMessage.noLoopbackServerExists.code) {
                        // Loopback server is not listening yet
                        ticks++;
                        return;
                    }
                    clearInterval(id);
                    reject(e);
                    return;
                }
            }, Constants.LOOPBACK_SERVER_CONSTANTS.INTERVAL_MS);
        });
    }
}

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