@financialforcedev/orizuru-auth
Version:
Salesforce authentication for Heroku Node applications.
106 lines • 13.5 kB
JavaScript
;
/*
* Copyright (c) 2019, FinancialForce.com, inc
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the FinancialForce.com, inc nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
Object.defineProperty(exports, "__esModule", { value: true });
/**
* @module client/oauth2Jwt/jwt
*/
const jsonwebtoken_1 = require("jsonwebtoken");
const uuid_1 = require("uuid");
/**
* The assertion types that can be created.
*/
var AssertionType;
(function (AssertionType) {
AssertionType["CLIENT"] = "client";
AssertionType["GRANT"] = "grant";
})(AssertionType || (AssertionType = {}));
/**
* Creates a [private_key_jwt](https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication) assertion for use in the authentication process.
*
* @param params The parameters required for either the [auth](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest) or [refresh](https://openid.net/specs/openid-connect-core-1_0.html#RefreshingAccessToken) grant requests.
* @param signingSecret The secret used to sign the payload.
* @param tokenEndpoint The [OpenID token endpoint](https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint).
*/
async function createJwtBearerClientAssertion(params, signingSecret, tokenEndpoint) {
const payload = createPayload(tokenEndpoint, params.clientId, params.clientId);
return signClientAssertion(payload, signingSecret, AssertionType.CLIENT);
}
exports.createJwtBearerClientAssertion = createJwtBearerClientAssertion;
/**
* Creates a [private_key_jwt](https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication) assertion for use in the authentication process.
*
* Unlike the standard client assertion, this assertion provides the username as the
* subject as defined by [Salesforce](https://help.salesforce.com/articleView?id=remoteaccess_oauth_jwt_flow.htm#create_token).
*
* @param env The auth environment parameters.
* @param params The parameters required for the [JWT Bearer](https://help.salesforce.com/articleView?id=remoteaccess_oauth_jwt_flow.htm) grant request.
* @param user The user to generate the assertion for.
*/
async function createJwtBearerGrantAssertion(env, params) {
const payload = createPayload(env.issuerURI, params.clientId, params.user.username);
return signClientAssertion(payload, params.signingSecret, AssertionType.GRANT);
}
exports.createJwtBearerGrantAssertion = createJwtBearerGrantAssertion;
/**
* Create the JWT payload.
*
* @param aud Audience(s) that this ID Token is intended for.
* @param iss Issuer.
* @param sub Subject identifier.
*/
function createPayload(aud, iss, sub) {
const iat = Math.floor(Date.now() / 1000);
const exp = iat + (60 * 4);
return {
aud,
exp,
iat,
iss,
jti: uuid_1.v4().toString(),
sub
};
}
/**
* Sign the payload using the given private key.
*
* @param payload The payload to sign.
* @param signingSecret The private key to sign the payload with.
* @param type The assertion type (either client or grant).
*/
function signClientAssertion(payload, signingSecret, type) {
return new Promise((resolve, reject) => {
jsonwebtoken_1.sign(payload, signingSecret, { algorithm: 'RS256' }, (err, encoded) => {
if (err) {
reject(new Error(`Failed to sign ${type} assertion.`));
}
else {
resolve(encoded);
}
});
});
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiand0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vc3JjL2luZGV4L2NsaWVudC9vYXV0aDJKd3Qvand0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBd0JHOztBQUVIOztHQUVHO0FBRUgsK0NBQTRDO0FBQzVDLCtCQUFrQztBQU1sQzs7R0FFRztBQUNILElBQUssYUFHSjtBQUhELFdBQUssYUFBYTtJQUNqQixrQ0FBaUIsQ0FBQTtJQUNqQixnQ0FBZSxDQUFBO0FBQ2hCLENBQUMsRUFISSxhQUFhLEtBQWIsYUFBYSxRQUdqQjtBQUVEOzs7Ozs7R0FNRztBQUNJLEtBQUssVUFBVSw4QkFBOEIsQ0FBQyxNQUFtQixFQUFFLGFBQXFCLEVBQUUsYUFBcUI7SUFDckgsTUFBTSxPQUFPLEdBQUcsYUFBYSxDQUFDLGFBQWEsRUFBRSxNQUFNLENBQUMsUUFBUSxFQUFFLE1BQU0sQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUMvRSxPQUFPLG1CQUFtQixDQUFDLE9BQU8sRUFBRSxhQUFhLEVBQUUsYUFBYSxDQUFDLE1BQU0sQ0FBQyxDQUFDO0FBRTFFLENBQUM7QUFKRCx3RUFJQztBQUVEOzs7Ozs7Ozs7R0FTRztBQUNJLEtBQUssVUFBVSw2QkFBNkIsQ0FBQyxHQUFnQixFQUFFLE1BQXNCO0lBQzNGLE1BQU0sT0FBTyxHQUFHLGFBQWEsQ0FBQyxHQUFHLENBQUMsU0FBUyxFQUFFLE1BQU0sQ0FBQyxRQUFRLEVBQUUsTUFBTSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUNwRixPQUFPLG1CQUFtQixDQUFDLE9BQU8sRUFBRSxNQUFNLENBQUMsYUFBYSxFQUFFLGFBQWEsQ0FBQyxLQUFLLENBQUMsQ0FBQztBQUNoRixDQUFDO0FBSEQsc0VBR0M7QUFFRDs7Ozs7O0dBTUc7QUFDSCxTQUFTLGFBQWEsQ0FBQyxHQUFXLEVBQUUsR0FBVyxFQUFFLEdBQVc7SUFFM0QsTUFBTSxHQUFHLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsR0FBRyxFQUFFLEdBQUcsSUFBSSxDQUFDLENBQUM7SUFDMUMsTUFBTSxHQUFHLEdBQUcsR0FBRyxHQUFHLENBQUMsRUFBRSxHQUFHLENBQUMsQ0FBQyxDQUFDO0lBRTNCLE9BQU87UUFDTixHQUFHO1FBQ0gsR0FBRztRQUNILEdBQUc7UUFDSCxHQUFHO1FBQ0gsR0FBRyxFQUFFLFNBQUksRUFBRSxDQUFDLFFBQVEsRUFBRTtRQUN0QixHQUFHO0tBQ0gsQ0FBQztBQUVILENBQUM7QUFFRDs7Ozs7O0dBTUc7QUFDSCxTQUFTLG1CQUFtQixDQUFDLE9BQVksRUFBRSxhQUFxQixFQUFFLElBQW1CO0lBRXBGLE9BQU8sSUFBSSxPQUFPLENBQVMsQ0FBQyxPQUFPLEVBQUUsTUFBTSxFQUFFLEVBQUU7UUFFOUMsbUJBQUksQ0FBQyxPQUFPLEVBQUUsYUFBYSxFQUFFLEVBQUUsU0FBUyxFQUFFLE9BQU8sRUFBRSxFQUFFLENBQUMsR0FBRyxFQUFFLE9BQU8sRUFBRSxFQUFFO1lBQ3JFLElBQUksR0FBRyxFQUFFO2dCQUNSLE1BQU0sQ0FBQyxJQUFJLEtBQUssQ0FBQyxrQkFBa0IsSUFBSSxhQUFhLENBQUMsQ0FBQyxDQUFDO2FBQ3ZEO2lCQUFNO2dCQUNOLE9BQU8sQ0FBQyxPQUFPLENBQUMsQ0FBQzthQUNqQjtRQUNGLENBQUMsQ0FBQyxDQUFDO0lBRUosQ0FBQyxDQUFDLENBQUM7QUFFSixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLypcbiAqIENvcHlyaWdodCAoYykgMjAxOSwgRmluYW5jaWFsRm9yY2UuY29tLCBpbmNcbiAqIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKlxuICogUmVkaXN0cmlidXRpb24gYW5kIHVzZSBpbiBzb3VyY2UgYW5kIGJpbmFyeSBmb3Jtcywgd2l0aCBvciB3aXRob3V0IG1vZGlmaWNhdGlvbixcbiAqICAgYXJlIHBlcm1pdHRlZCBwcm92aWRlZCB0aGF0IHRoZSBmb2xsb3dpbmcgY29uZGl0aW9ucyBhcmUgbWV0OlxuICpcbiAqIC0gUmVkaXN0cmlidXRpb25zIG9mIHNvdXJjZSBjb2RlIG11c3QgcmV0YWluIHRoZSBhYm92ZSBjb3B5cmlnaHQgbm90aWNlLFxuICogICAgICB0aGlzIGxpc3Qgb2YgY29uZGl0aW9ucyBhbmQgdGhlIGZvbGxvd2luZyBkaXNjbGFpbWVyLlxuICogLSBSZWRpc3RyaWJ1dGlvbnMgaW4gYmluYXJ5IGZvcm0gbXVzdCByZXByb2R1Y2UgdGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UsXG4gKiAgICAgIHRoaXMgbGlzdCBvZiBjb25kaXRpb25zIGFuZCB0aGUgZm9sbG93aW5nIGRpc2NsYWltZXIgaW4gdGhlIGRvY3VtZW50YXRpb25cbiAqICAgICAgYW5kL29yIG90aGVyIG1hdGVyaWFscyBwcm92aWRlZCB3aXRoIHRoZSBkaXN0cmlidXRpb24uXG4gKiAtIE5laXRoZXIgdGhlIG5hbWUgb2YgdGhlIEZpbmFuY2lhbEZvcmNlLmNvbSwgaW5jIG5vciB0aGUgbmFtZXMgb2YgaXRzIGNvbnRyaWJ1dG9yc1xuICogICAgICBtYXkgYmUgdXNlZCB0byBlbmRvcnNlIG9yIHByb21vdGUgcHJvZHVjdHMgZGVyaXZlZCBmcm9tIHRoaXMgc29mdHdhcmUgd2l0aG91dFxuICogICAgICBzcGVjaWZpYyBwcmlvciB3cml0dGVuIHBlcm1pc3Npb24uXG4gKlxuICogVEhJUyBTT0ZUV0FSRSBJUyBQUk9WSURFRCBCWSBUSEUgQ09QWVJJR0hUIEhPTERFUlMgQU5EIENPTlRSSUJVVE9SUyBcIkFTIElTXCIgQU5EXG4gKiAgQU5ZIEVYUFJFU1MgT1IgSU1QTElFRCBXQVJSQU5USUVTLCBJTkNMVURJTkcsIEJVVCBOT1QgTElNSVRFRCBUTywgVEhFIElNUExJRUQgV0FSUkFOVElFU1xuICogIE9GIE1FUkNIQU5UQUJJTElUWSBBTkQgRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQVJFIERJU0NMQUlNRUQuIElOIE5PIEVWRU5UIFNIQUxMXG4gKiAgVEhFIENPUFlSSUdIVCBIT0xERVIgT1IgQ09OVFJJQlVUT1JTIEJFIExJQUJMRSBGT1IgQU5ZIERJUkVDVCwgSU5ESVJFQ1QsIElOQ0lERU5UQUwsIFNQRUNJQUwsXG4gKiAgRVhFTVBMQVJZLCBPUiBDT05TRVFVRU5USUFMIERBTUFHRVMgKElOQ0xVRElORywgQlVUIE5PVCBMSU1JVEVEIFRPLCBQUk9DVVJFTUVOVCBPRiBTVUJTVElUVVRFIEdPT0RTXG4gKiAgT1IgU0VSVklDRVM7IExPU1MgT0YgVVNFLCBEQVRBLCBPUiBQUk9GSVRTOyBPUiBCVVNJTkVTUyBJTlRFUlJVUFRJT04pIEhPV0VWRVIgQ0FVU0VEIEFORCBPTiBBTlkgVEhFT1JZXG4gKiAgT0YgTElBQklMSVRZLCBXSEVUSEVSIElOIENPTlRSQUNULCBTVFJJQ1QgTElBQklMSVRZLCBPUiBUT1JUIChJTkNMVURJTkcgTkVHTElHRU5DRSBPUiBPVEhFUldJU0UpXG4gKiAgQVJJU0lORyBJTiBBTlkgV0FZIE9VVCBPRiBUSEUgVVNFIE9GIFRISVMgU09GVFdBUkUsIEVWRU4gSUYgQURWSVNFRCBPRiBUSEUgUE9TU0lCSUxJVFkgT0YgU1VDSCBEQU1BR0UuXG4gKi9cblxuLyoqXG4gKiBAbW9kdWxlIGNsaWVudC9vYXV0aDJKd3Qvand0XG4gKi9cblxuaW1wb3J0IHsgU2VjcmV0LCBzaWduIH0gZnJvbSAnanNvbndlYnRva2VuJztcbmltcG9ydCB7IHY0IGFzIHV1aWQgfSBmcm9tICd1dWlkJztcblxuaW1wb3J0IHsgRW52aXJvbm1lbnQgfSBmcm9tICcuLi9jYWNoZSc7XG5pbXBvcnQgeyBHcmFudFBhcmFtcyB9IGZyb20gJy4uL29hdXRoMic7XG5pbXBvcnQgeyBKV1QsIEp3dEdyYW50UGFyYW1zIH0gZnJvbSAnLi4vb2F1dGgySnd0JztcblxuLyoqXG4gKiBUaGUgYXNzZXJ0aW9uIHR5cGVzIHRoYXQgY2FuIGJlIGNyZWF0ZWQuXG4gKi9cbmVudW0gQXNzZXJ0aW9uVHlwZSB7XG5cdENMSUVOVCA9ICdjbGllbnQnLFxuXHRHUkFOVCA9ICdncmFudCdcbn1cblxuLyoqXG4gKiBDcmVhdGVzIGEgW3ByaXZhdGVfa2V5X2p3dF0oaHR0cHM6Ly9vcGVuaWQubmV0L3NwZWNzL29wZW5pZC1jb25uZWN0LWNvcmUtMV8wLmh0bWwjQ2xpZW50QXV0aGVudGljYXRpb24pIGFzc2VydGlvbiBmb3IgdXNlIGluIHRoZSBhdXRoZW50aWNhdGlvbiBwcm9jZXNzLlxuICpcbiAqIEBwYXJhbSBwYXJhbXMgVGhlIHBhcmFtZXRlcnMgcmVxdWlyZWQgZm9yIGVpdGhlciB0aGUgW2F1dGhdKGh0dHBzOi8vb3BlbmlkLm5ldC9zcGVjcy9vcGVuaWQtY29ubmVjdC1jb3JlLTFfMC5odG1sI0F1dGhSZXF1ZXN0KSBvciBbcmVmcmVzaF0oaHR0cHM6Ly9vcGVuaWQubmV0L3NwZWNzL29wZW5pZC1jb25uZWN0LWNvcmUtMV8wLmh0bWwjUmVmcmVzaGluZ0FjY2Vzc1Rva2VuKSBncmFudCByZXF1ZXN0cy5cbiAqIEBwYXJhbSBzaWduaW5nU2VjcmV0IFRoZSBzZWNyZXQgdXNlZCB0byBzaWduIHRoZSBwYXlsb2FkLlxuICogQHBhcmFtIHRva2VuRW5kcG9pbnQgVGhlIFtPcGVuSUQgdG9rZW4gZW5kcG9pbnRdKGh0dHBzOi8vb3BlbmlkLm5ldC9zcGVjcy9vcGVuaWQtY29ubmVjdC1jb3JlLTFfMC5odG1sI1Rva2VuRW5kcG9pbnQpLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gY3JlYXRlSnd0QmVhcmVyQ2xpZW50QXNzZXJ0aW9uKHBhcmFtczogR3JhbnRQYXJhbXMsIHNpZ25pbmdTZWNyZXQ6IFNlY3JldCwgdG9rZW5FbmRwb2ludDogc3RyaW5nKSB7XG5cdGNvbnN0IHBheWxvYWQgPSBjcmVhdGVQYXlsb2FkKHRva2VuRW5kcG9pbnQsIHBhcmFtcy5jbGllbnRJZCwgcGFyYW1zLmNsaWVudElkKTtcblx0cmV0dXJuIHNpZ25DbGllbnRBc3NlcnRpb24ocGF5bG9hZCwgc2lnbmluZ1NlY3JldCwgQXNzZXJ0aW9uVHlwZS5DTElFTlQpO1xuXG59XG5cbi8qKlxuICogQ3JlYXRlcyBhIFtwcml2YXRlX2tleV9qd3RdKGh0dHBzOi8vb3BlbmlkLm5ldC9zcGVjcy9vcGVuaWQtY29ubmVjdC1jb3JlLTFfMC5odG1sI0NsaWVudEF1dGhlbnRpY2F0aW9uKSBhc3NlcnRpb24gZm9yIHVzZSBpbiB0aGUgYXV0aGVudGljYXRpb24gcHJvY2Vzcy5cbiAqXG4gKiBVbmxpa2UgdGhlIHN0YW5kYXJkIGNsaWVudCBhc3NlcnRpb24sIHRoaXMgYXNzZXJ0aW9uIHByb3ZpZGVzIHRoZSB1c2VybmFtZSBhcyB0aGVcbiAqIHN1YmplY3QgYXMgZGVmaW5lZCBieSBbU2FsZXNmb3JjZV0oaHR0cHM6Ly9oZWxwLnNhbGVzZm9yY2UuY29tL2FydGljbGVWaWV3P2lkPXJlbW90ZWFjY2Vzc19vYXV0aF9qd3RfZmxvdy5odG0jY3JlYXRlX3Rva2VuKS5cbiAqXG4gKiBAcGFyYW0gZW52IFRoZSBhdXRoIGVudmlyb25tZW50IHBhcmFtZXRlcnMuXG4gKiBAcGFyYW0gcGFyYW1zIFRoZSBwYXJhbWV0ZXJzIHJlcXVpcmVkIGZvciB0aGUgW0pXVCBCZWFyZXJdKGh0dHBzOi8vaGVscC5zYWxlc2ZvcmNlLmNvbS9hcnRpY2xlVmlldz9pZD1yZW1vdGVhY2Nlc3Nfb2F1dGhfand0X2Zsb3cuaHRtKSBncmFudCByZXF1ZXN0LlxuICogQHBhcmFtIHVzZXIgVGhlIHVzZXIgdG8gZ2VuZXJhdGUgdGhlIGFzc2VydGlvbiBmb3IuXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBjcmVhdGVKd3RCZWFyZXJHcmFudEFzc2VydGlvbihlbnY6IEVudmlyb25tZW50LCBwYXJhbXM6IEp3dEdyYW50UGFyYW1zKSB7XG5cdGNvbnN0IHBheWxvYWQgPSBjcmVhdGVQYXlsb2FkKGVudi5pc3N1ZXJVUkksIHBhcmFtcy5jbGllbnRJZCwgcGFyYW1zLnVzZXIudXNlcm5hbWUpO1xuXHRyZXR1cm4gc2lnbkNsaWVudEFzc2VydGlvbihwYXlsb2FkLCBwYXJhbXMuc2lnbmluZ1NlY3JldCwgQXNzZXJ0aW9uVHlwZS5HUkFOVCk7XG59XG5cbi8qKlxuICogQ3JlYXRlIHRoZSBKV1QgcGF5bG9hZC5cbiAqXG4gKiBAcGFyYW0gYXVkIEF1ZGllbmNlKHMpIHRoYXQgdGhpcyBJRCBUb2tlbiBpcyBpbnRlbmRlZCBmb3IuXG4gKiBAcGFyYW0gaXNzIElzc3Vlci5cbiAqIEBwYXJhbSBzdWIgU3ViamVjdCBpZGVudGlmaWVyLlxuICovXG5mdW5jdGlvbiBjcmVhdGVQYXlsb2FkKGF1ZDogc3RyaW5nLCBpc3M6IHN0cmluZywgc3ViOiBzdHJpbmcpOiBKV1Qge1xuXG5cdGNvbnN0IGlhdCA9IE1hdGguZmxvb3IoRGF0ZS5ub3coKSAvIDEwMDApO1xuXHRjb25zdCBleHAgPSBpYXQgKyAoNjAgKiA0KTtcblxuXHRyZXR1cm4ge1xuXHRcdGF1ZCxcblx0XHRleHAsXG5cdFx0aWF0LFxuXHRcdGlzcyxcblx0XHRqdGk6IHV1aWQoKS50b1N0cmluZygpLFxuXHRcdHN1YlxuXHR9O1xuXG59XG5cbi8qKlxuICogU2lnbiB0aGUgcGF5bG9hZCB1c2luZyB0aGUgZ2l2ZW4gcHJpdmF0ZSBrZXkuXG4gKlxuICogQHBhcmFtIHBheWxvYWQgVGhlIHBheWxvYWQgdG8gc2lnbi5cbiAqIEBwYXJhbSBzaWduaW5nU2VjcmV0IFRoZSBwcml2YXRlIGtleSB0byBzaWduIHRoZSBwYXlsb2FkIHdpdGguXG4gKiBAcGFyYW0gdHlwZSBUaGUgYXNzZXJ0aW9uIHR5cGUgKGVpdGhlciBjbGllbnQgb3IgZ3JhbnQpLlxuICovXG5mdW5jdGlvbiBzaWduQ2xpZW50QXNzZXJ0aW9uKHBheWxvYWQ6IEpXVCwgc2lnbmluZ1NlY3JldDogU2VjcmV0LCB0eXBlOiBBc3NlcnRpb25UeXBlKSB7XG5cblx0cmV0dXJuIG5ldyBQcm9taXNlPHN0cmluZz4oKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuXG5cdFx0c2lnbihwYXlsb2FkLCBzaWduaW5nU2VjcmV0LCB7IGFsZ29yaXRobTogJ1JTMjU2JyB9LCAoZXJyLCBlbmNvZGVkKSA9PiB7XG5cdFx0XHRpZiAoZXJyKSB7XG5cdFx0XHRcdHJlamVjdChuZXcgRXJyb3IoYEZhaWxlZCB0byBzaWduICR7dHlwZX0gYXNzZXJ0aW9uLmApKTtcblx0XHRcdH0gZWxzZSB7XG5cdFx0XHRcdHJlc29sdmUoZW5jb2RlZCk7XG5cdFx0XHR9XG5cdFx0fSk7XG5cblx0fSk7XG5cbn1cbiJdfQ==