/*! @azure/msal-node v2.5.1 2023-11-07 */ 'use strict'; 'use strict'; var msalCommon = require('@azure/msal-common'); /* * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ /** * OAuth2.0 Device code client */ class DeviceCodeClient extends msalCommon.BaseClient { constructor(configuration) { super(configuration); } /** * Gets device code from device code endpoint, calls back to with device code response, and * polls token endpoint to exchange device code for tokens * @param request */ async acquireToken(request) { const deviceCodeResponse = await this.getDeviceCode(request); request.deviceCodeCallback(deviceCodeResponse); const reqTimestamp = msalCommon.TimeUtils.nowSeconds(); const response = await this.acquireTokenWithDeviceCode(request, deviceCodeResponse); const responseHandler = new msalCommon.ResponseHandler(this.config.authOptions.clientId, this.cacheManager, this.cryptoUtils, this.logger, this.config.serializableCache, this.config.persistencePlugin); // Validate response. This function throws a server error if an error is returned by the server. responseHandler.validateTokenResponse(response); return await responseHandler.handleServerTokenResponse(response, this.authority, reqTimestamp, request); } /** * Creates device code request and executes http GET * @param request */ async getDeviceCode(request) { const queryParametersString = this.createExtraQueryParameters(request); const endpoint = msalCommon.UrlString.appendQueryString(this.authority.deviceCodeEndpoint, queryParametersString); const queryString = this.createQueryString(request); const headers = this.createTokenRequestHeaders(); const thumbprint = { clientId: this.config.authOptions.clientId, authority: request.authority, scopes: request.scopes, claims: request.claims, authenticationScheme: request.authenticationScheme, resourceRequestMethod: request.resourceRequestMethod, resourceRequestUri: request.resourceRequestUri, shrClaims: request.shrClaims, sshKid: request.sshKid, }; return this.executePostRequestToDeviceCodeEndpoint(endpoint, queryString, headers, thumbprint); } /** * Creates query string for the device code request * @param request */ createExtraQueryParameters(request) { const parameterBuilder = new msalCommon.RequestParameterBuilder(); if (request.extraQueryParameters) { parameterBuilder.addExtraQueryParameters(request.extraQueryParameters); } return parameterBuilder.createQueryString(); } /** * Executes POST request to device code endpoint * @param deviceCodeEndpoint * @param queryString * @param headers */ async executePostRequestToDeviceCodeEndpoint(deviceCodeEndpoint, queryString, headers, thumbprint) { const { body: { user_code: userCode, device_code: deviceCode, verification_uri: verificationUri, expires_in: expiresIn, interval, message, }, } = await this.networkManager.sendPostRequest(thumbprint, deviceCodeEndpoint, { body: queryString, headers: headers, }); return { userCode, deviceCode, verificationUri, expiresIn, interval, message, }; } /** * Create device code endpoint query parameters and returns string */ createQueryString(request) { const parameterBuilder = new msalCommon.RequestParameterBuilder(); parameterBuilder.addScopes(request.scopes); parameterBuilder.addClientId(this.config.authOptions.clientId); if (request.extraQueryParameters) { parameterBuilder.addExtraQueryParameters(request.extraQueryParameters); } if (request.claims || (this.config.authOptions.clientCapabilities && this.config.authOptions.clientCapabilities.length > 0)) { parameterBuilder.addClaims(request.claims, this.config.authOptions.clientCapabilities); } return parameterBuilder.createQueryString(); } /** * Breaks the polling with specific conditions. * @param request CommonDeviceCodeRequest * @param deviceCodeResponse DeviceCodeResponse */ continuePolling(deviceCodeExpirationTime, userSpecifiedTimeout, userSpecifiedCancelFlag) { if (userSpecifiedCancelFlag) { this.logger.error("Token request cancelled by setting DeviceCodeRequest.cancel = true"); throw msalCommon.createClientAuthError(msalCommon.ClientAuthErrorCodes.deviceCodePollingCancelled); } else if (userSpecifiedTimeout && userSpecifiedTimeout < deviceCodeExpirationTime && msalCommon.TimeUtils.nowSeconds() > userSpecifiedTimeout) { this.logger.error(`User defined timeout for device code polling reached. The timeout was set for ${userSpecifiedTimeout}`); throw msalCommon.createClientAuthError(msalCommon.ClientAuthErrorCodes.userTimeoutReached); } else if (msalCommon.TimeUtils.nowSeconds() > deviceCodeExpirationTime) { if (userSpecifiedTimeout) { this.logger.verbose(`User specified timeout ignored as the device code has expired before the timeout elapsed. The user specified timeout was set for ${userSpecifiedTimeout}`); } this.logger.error(`Device code expired. Expiration time of device code was ${deviceCodeExpirationTime}`); throw msalCommon.createClientAuthError(msalCommon.ClientAuthErrorCodes.deviceCodeExpired); } return true; } /** * Creates token request with device code response and polls token endpoint at interval set by the device code * response * @param request * @param deviceCodeResponse */ async acquireTokenWithDeviceCode(request, deviceCodeResponse) { const queryParametersString = this.createTokenQueryParameters(request); const endpoint = msalCommon.UrlString.appendQueryString(this.authority.tokenEndpoint, queryParametersString); const requestBody = this.createTokenRequestBody(request, deviceCodeResponse); const headers = this.createTokenRequestHeaders(); const userSpecifiedTimeout = request.timeout ? msalCommon.TimeUtils.nowSeconds() + request.timeout : undefined; const deviceCodeExpirationTime = msalCommon.TimeUtils.nowSeconds() + deviceCodeResponse.expiresIn; const pollingIntervalMilli = deviceCodeResponse.interval * 1000; /* * Poll token endpoint while (device code is not expired AND operation has not been cancelled by * setting CancellationToken.cancel = true). POST request is sent at interval set by pollingIntervalMilli */ while (this.continuePolling(deviceCodeExpirationTime, userSpecifiedTimeout, request.cancel)) { const thumbprint = { clientId: this.config.authOptions.clientId, authority: request.authority, scopes: request.scopes, claims: request.claims, authenticationScheme: request.authenticationScheme, resourceRequestMethod: request.resourceRequestMethod, resourceRequestUri: request.resourceRequestUri, shrClaims: request.shrClaims, sshKid: request.sshKid, }; const response = await this.executePostToTokenEndpoint(endpoint, requestBody, headers, thumbprint, request.correlationId); if (response.body && response.body.error) { // user authorization is pending. Sleep for polling interval and try again if (response.body.error === msalCommon.Constants.AUTHORIZATION_PENDING) { this.logger.info("Authorization pending. Continue polling."); await msalCommon.TimeUtils.delay(pollingIntervalMilli); } else { // for any other error, throw this.logger.info("Unexpected error in polling from the server"); throw msalCommon.createAuthError(msalCommon.AuthErrorCodes.postRequestFailed, response.body.error); } } else { this.logger.verbose("Authorization completed successfully. Polling stopped."); return response.body; } } /* * The above code should've thrown by this point, but to satisfy TypeScript, * and in the rare case the conditionals in continuePolling() may not catch everything... */ this.logger.error("Polling stopped for unknown reasons."); throw msalCommon.createClientAuthError(msalCommon.ClientAuthErrorCodes.deviceCodeUnknownError); } /** * Creates query parameters and converts to string. * @param request * @param deviceCodeResponse */ createTokenRequestBody(request, deviceCodeResponse) { const requestParameters = new msalCommon.RequestParameterBuilder(); requestParameters.addScopes(request.scopes); requestParameters.addClientId(this.config.authOptions.clientId); requestParameters.addGrantType(msalCommon.GrantType.DEVICE_CODE_GRANT); requestParameters.addDeviceCode(deviceCodeResponse.deviceCode); const correlationId = request.correlationId || this.config.cryptoInterface.createNewGuid(); requestParameters.addCorrelationId(correlationId); requestParameters.addClientInfo(); requestParameters.addLibraryInfo(this.config.libraryInfo); requestParameters.addApplicationTelemetry(this.config.telemetry.application); requestParameters.addThrottling(); if (this.serverTelemetryManager) { requestParameters.addServerTelemetry(this.serverTelemetryManager); } if (!msalCommon.StringUtils.isEmptyObj(request.claims) || (this.config.authOptions.clientCapabilities && this.config.authOptions.clientCapabilities.length > 0)) { requestParameters.addClaims(request.claims, this.config.authOptions.clientCapabilities); } return requestParameters.createQueryString(); } } exports.DeviceCodeClient = DeviceCodeClient; //# sourceMappingURL=DeviceCodeClient.cjs.map