File size: 4,660 Bytes
9756872
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadAzureCredentials = exports.fetchAzureKMSToken = exports.prepareRequest = exports.addAzureParams = exports.tokenCache = exports.AzureCredentialCache = exports.AZURE_BASE_URL = void 0;
const error_1 = require("../../error");
const utils_1 = require("../../utils");
const errors_1 = require("../errors");
const MINIMUM_TOKEN_REFRESH_IN_MILLISECONDS = 6000;
/** Base URL for getting Azure tokens. */
exports.AZURE_BASE_URL = 'http://169.254.169.254/metadata/identity/oauth2/token?';
/**
 * @internal
 */
class AzureCredentialCache {
    constructor() {
        this.cachedToken = null;
    }
    async getToken() {
        if (this.cachedToken == null || this.needsRefresh(this.cachedToken)) {
            this.cachedToken = await this._getToken();
        }
        return { accessToken: this.cachedToken.accessToken };
    }
    needsRefresh(token) {
        const timeUntilExpirationMS = token.expiresOnTimestamp - Date.now();
        return timeUntilExpirationMS <= MINIMUM_TOKEN_REFRESH_IN_MILLISECONDS;
    }
    /**
     * exposed for testing
     */
    resetCache() {
        this.cachedToken = null;
    }
    /**
     * exposed for testing
     */
    _getToken() {
        return fetchAzureKMSToken();
    }
}
exports.AzureCredentialCache = AzureCredentialCache;
/** @internal */
exports.tokenCache = new AzureCredentialCache();
/** @internal */
async function parseResponse(response) {
    const { status, body: rawBody } = response;
    const body = (() => {
        try {
            return JSON.parse(rawBody);
        }
        catch {
            throw new errors_1.MongoCryptAzureKMSRequestError('Malformed JSON body in GET request.');
        }
    })();
    if (status !== 200) {
        throw new errors_1.MongoCryptAzureKMSRequestError('Unable to complete request.', body);
    }
    if (!body.access_token) {
        throw new errors_1.MongoCryptAzureKMSRequestError('Malformed response body - missing field `access_token`.');
    }
    if (!body.expires_in) {
        throw new errors_1.MongoCryptAzureKMSRequestError('Malformed response body - missing field `expires_in`.');
    }
    const expiresInMS = Number(body.expires_in) * 1000;
    if (Number.isNaN(expiresInMS)) {
        throw new errors_1.MongoCryptAzureKMSRequestError('Malformed response body - unable to parse int from `expires_in` field.');
    }
    return {
        accessToken: body.access_token,
        expiresOnTimestamp: Date.now() + expiresInMS
    };
}
/**
 * @internal
 * Get the Azure endpoint URL.
 */
function addAzureParams(url, resource, username) {
    url.searchParams.append('api-version', '2018-02-01');
    url.searchParams.append('resource', resource);
    if (username) {
        url.searchParams.append('client_id', username);
    }
    return url;
}
exports.addAzureParams = addAzureParams;
/**
 * @internal
 *
 * parses any options provided by prose tests to `fetchAzureKMSToken` and merges them with
 * the default values for headers and the request url.
 */
function prepareRequest(options) {
    const url = new URL(options.url?.toString() ?? exports.AZURE_BASE_URL);
    addAzureParams(url, 'https://vault.azure.net');
    const headers = { ...options.headers, 'Content-Type': 'application/json', Metadata: true };
    return { headers, url };
}
exports.prepareRequest = prepareRequest;
/**
 * @internal
 *
 * `AzureKMSRequestOptions` allows prose tests to modify the http request sent to the idms
 * servers.  This is required to simulate different server conditions.  No options are expected to
 * be set outside of tests.
 *
 * exposed for CSFLE
 * [prose test 18](https://github.com/mongodb/specifications/tree/master/source/client-side-encryption/tests#azure-imds-credentials)
 */
async function fetchAzureKMSToken(options = {}) {
    const { headers, url } = prepareRequest(options);
    try {
        const response = await (0, utils_1.get)(url, { headers });
        return await parseResponse(response);
    }
    catch (error) {
        if (error instanceof error_1.MongoNetworkTimeoutError) {
            throw new errors_1.MongoCryptAzureKMSRequestError(`[Azure KMS] ${error.message}`);
        }
        throw error;
    }
}
exports.fetchAzureKMSToken = fetchAzureKMSToken;
/**
 * @internal
 *
 * @throws Will reject with a `MongoCryptError` if the http request fails or the http response is malformed.
 */
async function loadAzureCredentials(kmsProviders) {
    const azure = await exports.tokenCache.getToken();
    return { ...kmsProviders, azure };
}
exports.loadAzureCredentials = loadAzureCredentials;
//# sourceMappingURL=azure.js.map