oauth2.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. "use strict";
  2. /*
  3. * This auth adapter is based on the OAuth 2.0 Token Introspection specification.
  4. * See RFC 7662 for details (https://tools.ietf.org/html/rfc7662).
  5. * It's purpose is to validate OAuth2 access tokens using the OAuth2 provider's
  6. * token introspection endpoint (if implemented by the provider).
  7. *
  8. * The adapter accepts the following config parameters:
  9. *
  10. * 1. "tokenIntrospectionEndpointUrl" (string, required)
  11. * The URL of the token introspection endpoint of the OAuth2 provider that
  12. * issued the access token to the client that is to be validated.
  13. *
  14. * 2. "useridField" (string, optional)
  15. * The name of the field in the token introspection response that contains
  16. * the userid. If specified, it will be used to verify the value of the "id"
  17. * field in the "authData" JSON that is coming from the client.
  18. * This can be the "aud" (i.e. audience), the "sub" (i.e. subject) or the
  19. * "username" field in the introspection response, but since only the
  20. * "active" field is required and all other reponse fields are optional
  21. * in the RFC, it has to be optional in this adapter as well.
  22. * Default: - (undefined)
  23. *
  24. * 3. "appidField" (string, optional)
  25. * The name of the field in the token introspection response that contains
  26. * the appId of the client. If specified, it will be used to verify it's
  27. * value against the set of appIds in the adapter config. The concept of
  28. * appIds comes from the two major social login providers
  29. * (Google and Facebook). They have not yet implemented the token
  30. * introspection endpoint, but the concept can be valid for any OAuth2
  31. * provider.
  32. * Default: - (undefined)
  33. *
  34. * 4. "appIds" (array of strings, required if appidField is defined)
  35. * A set of appIds that are used to restrict accepted access tokens based
  36. * on a specific field's value in the token introspection response.
  37. * Default: - (undefined)
  38. *
  39. * 5. "authorizationHeader" (string, optional)
  40. * The value of the "Authorization" HTTP header in requests sent to the
  41. * introspection endpoint. It must contain the raw value.
  42. * Thus if HTTP Basic authorization is to be used, it must contain the
  43. * "Basic" string, followed by whitespace, then by the base64 encoded
  44. * version of the concatenated <username> + ":" + <password> string.
  45. * Eg. "Basic dXNlcm5hbWU6cGFzc3dvcmQ="
  46. *
  47. * The adapter expects requests with the following authData JSON:
  48. *
  49. * {
  50. * "someadapter": {
  51. * "id": "user's OAuth2 provider-specific id as a string",
  52. * "access_token": "an authorized OAuth2 access token for the user",
  53. * }
  54. * }
  55. */
  56. const Parse = require('parse/node').Parse;
  57. const querystring = require('querystring');
  58. const httpsRequest = require('./httpsRequest');
  59. const INVALID_ACCESS = 'OAuth2 access token is invalid for this user.';
  60. const INVALID_ACCESS_APPID = "OAuth2: the access_token's appID is empty or is not in the list of permitted appIDs in the auth configuration.";
  61. const MISSING_APPIDS = 'OAuth2 configuration is missing the client app IDs ("appIds" config parameter).';
  62. const MISSING_URL = 'OAuth2 token introspection endpoint URL is missing from configuration!';
  63. // Returns a promise that fulfills if this user id is valid.
  64. function validateAuthData(authData, options) {
  65. return requestTokenInfo(options, authData.access_token).then(response => {
  66. if (!response || !response.active || options.useridField && authData.id !== response[options.useridField]) {
  67. throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, INVALID_ACCESS);
  68. }
  69. });
  70. }
  71. function validateAppId(appIds, authData, options) {
  72. if (!options || !options.appidField) {
  73. return Promise.resolve();
  74. }
  75. if (!appIds || appIds.length === 0) {
  76. throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, MISSING_APPIDS);
  77. }
  78. return requestTokenInfo(options, authData.access_token).then(response => {
  79. if (!response || !response.active) {
  80. throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, INVALID_ACCESS);
  81. }
  82. const appidField = options.appidField;
  83. if (!response[appidField]) {
  84. throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, INVALID_ACCESS_APPID);
  85. }
  86. const responseValue = response[appidField];
  87. if (!Array.isArray(responseValue) && appIds.includes(responseValue)) {
  88. return;
  89. } else if (Array.isArray(responseValue) && responseValue.some(appId => appIds.includes(appId))) {
  90. return;
  91. } else {
  92. throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, INVALID_ACCESS_APPID);
  93. }
  94. });
  95. }
  96. // A promise wrapper for requests to the OAuth2 token introspection endpoint.
  97. function requestTokenInfo(options, access_token) {
  98. if (!options || !options.tokenIntrospectionEndpointUrl) {
  99. throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, MISSING_URL);
  100. }
  101. const parsedUrl = new URL(options.tokenIntrospectionEndpointUrl);
  102. const postData = querystring.stringify({
  103. token: access_token
  104. });
  105. const headers = {
  106. 'Content-Type': 'application/x-www-form-urlencoded',
  107. 'Content-Length': Buffer.byteLength(postData)
  108. };
  109. if (options.authorizationHeader) {
  110. headers['Authorization'] = options.authorizationHeader;
  111. }
  112. const postOptions = {
  113. hostname: parsedUrl.hostname,
  114. path: parsedUrl.pathname,
  115. method: 'POST',
  116. headers: headers
  117. };
  118. return httpsRequest.request(postOptions, postData);
  119. }
  120. module.exports = {
  121. validateAppId: validateAppId,
  122. validateAuthData: validateAuthData
  123. };
  124. //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJQYXJzZSIsInJlcXVpcmUiLCJxdWVyeXN0cmluZyIsImh0dHBzUmVxdWVzdCIsIklOVkFMSURfQUNDRVNTIiwiSU5WQUxJRF9BQ0NFU1NfQVBQSUQiLCJNSVNTSU5HX0FQUElEUyIsIk1JU1NJTkdfVVJMIiwidmFsaWRhdGVBdXRoRGF0YSIsImF1dGhEYXRhIiwib3B0aW9ucyIsInJlcXVlc3RUb2tlbkluZm8iLCJhY2Nlc3NfdG9rZW4iLCJ0aGVuIiwicmVzcG9uc2UiLCJhY3RpdmUiLCJ1c2VyaWRGaWVsZCIsImlkIiwiRXJyb3IiLCJPQkpFQ1RfTk9UX0ZPVU5EIiwidmFsaWRhdGVBcHBJZCIsImFwcElkcyIsImFwcGlkRmllbGQiLCJQcm9taXNlIiwicmVzb2x2ZSIsImxlbmd0aCIsInJlc3BvbnNlVmFsdWUiLCJBcnJheSIsImlzQXJyYXkiLCJpbmNsdWRlcyIsInNvbWUiLCJhcHBJZCIsInRva2VuSW50cm9zcGVjdGlvbkVuZHBvaW50VXJsIiwicGFyc2VkVXJsIiwiVVJMIiwicG9zdERhdGEiLCJzdHJpbmdpZnkiLCJ0b2tlbiIsImhlYWRlcnMiLCJCdWZmZXIiLCJieXRlTGVuZ3RoIiwiYXV0aG9yaXphdGlvbkhlYWRlciIsInBvc3RPcHRpb25zIiwiaG9zdG5hbWUiLCJwYXRoIiwicGF0aG5hbWUiLCJtZXRob2QiLCJyZXF1ZXN0IiwibW9kdWxlIiwiZXhwb3J0cyJdLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9BZGFwdGVycy9BdXRoL29hdXRoMi5qcyJdLCJzb3VyY2VzQ29udGVudCI6WyIvKlxuICogVGhpcyBhdXRoIGFkYXB0ZXIgaXMgYmFzZWQgb24gdGhlIE9BdXRoIDIuMCBUb2tlbiBJbnRyb3NwZWN0aW9uIHNwZWNpZmljYXRpb24uXG4gKiBTZWUgUkZDIDc2NjIgZm9yIGRldGFpbHMgKGh0dHBzOi8vdG9vbHMuaWV0Zi5vcmcvaHRtbC9yZmM3NjYyKS5cbiAqIEl0J3MgcHVycG9zZSBpcyB0byB2YWxpZGF0ZSBPQXV0aDIgYWNjZXNzIHRva2VucyB1c2luZyB0aGUgT0F1dGgyIHByb3ZpZGVyJ3NcbiAqIHRva2VuIGludHJvc3BlY3Rpb24gZW5kcG9pbnQgKGlmIGltcGxlbWVudGVkIGJ5IHRoZSBwcm92aWRlcikuXG4gKlxuICogVGhlIGFkYXB0ZXIgYWNjZXB0cyB0aGUgZm9sbG93aW5nIGNvbmZpZyBwYXJhbWV0ZXJzOlxuICpcbiAqIDEuIFwidG9rZW5JbnRyb3NwZWN0aW9uRW5kcG9pbnRVcmxcIiAoc3RyaW5nLCByZXF1aXJlZClcbiAqICAgICAgVGhlIFVSTCBvZiB0aGUgdG9rZW4gaW50cm9zcGVjdGlvbiBlbmRwb2ludCBvZiB0aGUgT0F1dGgyIHByb3ZpZGVyIHRoYXRcbiAqICAgICAgaXNzdWVkIHRoZSBhY2Nlc3MgdG9rZW4gdG8gdGhlIGNsaWVudCB0aGF0IGlzIHRvIGJlIHZhbGlkYXRlZC5cbiAqXG4gKiAyLiBcInVzZXJpZEZpZWxkXCIgKHN0cmluZywgb3B0aW9uYWwpXG4gKiAgICAgIFRoZSBuYW1lIG9mIHRoZSBmaWVsZCBpbiB0aGUgdG9rZW4gaW50cm9zcGVjdGlvbiByZXNwb25zZSB0aGF0IGNvbnRhaW5zXG4gKiAgICAgIHRoZSB1c2VyaWQuIElmIHNwZWNpZmllZCwgaXQgd2lsbCBiZSB1c2VkIHRvIHZlcmlmeSB0aGUgdmFsdWUgb2YgdGhlIFwiaWRcIlxuICogICAgICBmaWVsZCBpbiB0aGUgXCJhdXRoRGF0YVwiIEpTT04gdGhhdCBpcyBjb21pbmcgZnJvbSB0aGUgY2xpZW50LlxuICogICAgICBUaGlzIGNhbiBiZSB0aGUgXCJhdWRcIiAoaS5lLiBhdWRpZW5jZSksIHRoZSBcInN1YlwiIChpLmUuIHN1YmplY3QpIG9yIHRoZVxuICogICAgICBcInVzZXJuYW1lXCIgZmllbGQgaW4gdGhlIGludHJvc3BlY3Rpb24gcmVzcG9uc2UsIGJ1dCBzaW5jZSBvbmx5IHRoZVxuICogICAgICBcImFjdGl2ZVwiIGZpZWxkIGlzIHJlcXVpcmVkIGFuZCBhbGwgb3RoZXIgcmVwb25zZSBmaWVsZHMgYXJlIG9wdGlvbmFsXG4gKiAgICAgIGluIHRoZSBSRkMsIGl0IGhhcyB0byBiZSBvcHRpb25hbCBpbiB0aGlzIGFkYXB0ZXIgYXMgd2VsbC5cbiAqICAgICAgRGVmYXVsdDogLSAodW5kZWZpbmVkKVxuICpcbiAqIDMuIFwiYXBwaWRGaWVsZFwiIChzdHJpbmcsIG9wdGlvbmFsKVxuICogICAgICBUaGUgbmFtZSBvZiB0aGUgZmllbGQgaW4gdGhlIHRva2VuIGludHJvc3BlY3Rpb24gcmVzcG9uc2UgdGhhdCBjb250YWluc1xuICogICAgICB0aGUgYXBwSWQgb2YgdGhlIGNsaWVudC4gSWYgc3BlY2lmaWVkLCBpdCB3aWxsIGJlIHVzZWQgdG8gdmVyaWZ5IGl0J3NcbiAqICAgICAgdmFsdWUgYWdhaW5zdCB0aGUgc2V0IG9mIGFwcElkcyBpbiB0aGUgYWRhcHRlciBjb25maWcuIFRoZSBjb25jZXB0IG9mXG4gKiAgICAgIGFwcElkcyBjb21lcyBmcm9tIHRoZSB0d28gbWFqb3Igc29jaWFsIGxvZ2luIHByb3ZpZGVyc1xuICogICAgICAoR29vZ2xlIGFuZCBGYWNlYm9vaykuIFRoZXkgaGF2ZSBub3QgeWV0IGltcGxlbWVudGVkIHRoZSB0b2tlblxuICogICAgICBpbnRyb3NwZWN0aW9uIGVuZHBvaW50LCBidXQgdGhlIGNvbmNlcHQgY2FuIGJlIHZhbGlkIGZvciBhbnkgT0F1dGgyXG4gKiAgICAgIHByb3ZpZGVyLlxuICogICAgICBEZWZhdWx0OiAtICh1bmRlZmluZWQpXG4gKlxuICogNC4gXCJhcHBJZHNcIiAoYXJyYXkgb2Ygc3RyaW5ncywgcmVxdWlyZWQgaWYgYXBwaWRGaWVsZCBpcyBkZWZpbmVkKVxuICogICAgICBBIHNldCBvZiBhcHBJZHMgdGhhdCBhcmUgdXNlZCB0byByZXN0cmljdCBhY2NlcHRlZCBhY2Nlc3MgdG9rZW5zIGJhc2VkXG4gKiAgICAgIG9uIGEgc3BlY2lmaWMgZmllbGQncyB2YWx1ZSBpbiB0aGUgdG9rZW4gaW50cm9zcGVjdGlvbiByZXNwb25zZS5cbiAqICAgICAgRGVmYXVsdDogLSAodW5kZWZpbmVkKVxuICpcbiAqIDUuIFwiYXV0aG9yaXphdGlvbkhlYWRlclwiIChzdHJpbmcsIG9wdGlvbmFsKVxuICogICAgICBUaGUgdmFsdWUgb2YgdGhlIFwiQXV0aG9yaXphdGlvblwiIEhUVFAgaGVhZGVyIGluIHJlcXVlc3RzIHNlbnQgdG8gdGhlXG4gKiAgICAgIGludHJvc3BlY3Rpb24gZW5kcG9pbnQuIEl0IG11c3QgY29udGFpbiB0aGUgcmF3IHZhbHVlLlxuICogICAgICBUaHVzIGlmIEhUVFAgQmFzaWMgYXV0aG9yaXphdGlvbiBpcyB0byBiZSB1c2VkLCBpdCBtdXN0IGNvbnRhaW4gdGhlXG4gKiAgICAgIFwiQmFzaWNcIiBzdHJpbmcsIGZvbGxvd2VkIGJ5IHdoaXRlc3BhY2UsIHRoZW4gYnkgdGhlIGJhc2U2NCBlbmNvZGVkXG4gKiAgICAgIHZlcnNpb24gb2YgdGhlIGNvbmNhdGVuYXRlZCA8dXNlcm5hbWU+ICsgXCI6XCIgKyA8cGFzc3dvcmQ+IHN0cmluZy5cbiAqICAgICAgRWcuIFwiQmFzaWMgZFhObGNtNWhiV1U2Y0dGemMzZHZjbVE9XCJcbiAqXG4gKiBUaGUgYWRhcHRlciBleHBlY3RzIHJlcXVlc3RzIHdpdGggdGhlIGZvbGxvd2luZyBhdXRoRGF0YSBKU09OOlxuICpcbiAqIHtcbiAqICAgXCJzb21lYWRhcHRlclwiOiB7XG4gKiAgICAgXCJpZFwiOiBcInVzZXIncyBPQXV0aDIgcHJvdmlkZXItc3BlY2lmaWMgaWQgYXMgYSBzdHJpbmdcIixcbiAqICAgICBcImFjY2Vzc190b2tlblwiOiBcImFuIGF1dGhvcml6ZWQgT0F1dGgyIGFjY2VzcyB0b2tlbiBmb3IgdGhlIHVzZXJcIixcbiAqICAgfVxuICogfVxuICovXG5cbmNvbnN0IFBhcnNlID0gcmVxdWlyZSgncGFyc2Uvbm9kZScpLlBhcnNlO1xuY29uc3QgcXVlcnlzdHJpbmcgPSByZXF1aXJlKCdxdWVyeXN0cmluZycpO1xuY29uc3QgaHR0cHNSZXF1ZXN0ID0gcmVxdWlyZSgnLi9odHRwc1JlcXVlc3QnKTtcblxuY29uc3QgSU5WQUxJRF9BQ0NFU1MgPSAnT0F1dGgyIGFjY2VzcyB0b2tlbiBpcyBpbnZhbGlkIGZvciB0aGlzIHVzZXIuJztcbmNvbnN0IElOVkFMSURfQUNDRVNTX0FQUElEID1cbiAgXCJPQXV0aDI6IHRoZSBhY2Nlc3NfdG9rZW4ncyBhcHBJRCBpcyBlbXB0eSBvciBpcyBub3QgaW4gdGhlIGxpc3Qgb2YgcGVybWl0dGVkIGFwcElEcyBpbiB0aGUgYXV0aCBjb25maWd1cmF0aW9uLlwiO1xuY29uc3QgTUlTU0lOR19BUFBJRFMgPVxuICAnT0F1dGgyIGNvbmZpZ3VyYXRpb24gaXMgbWlzc2luZyB0aGUgY2xpZW50IGFwcCBJRHMgKFwiYXBwSWRzXCIgY29uZmlnIHBhcmFtZXRlcikuJztcbmNvbnN0IE1JU1NJTkdfVVJMID0gJ09BdXRoMiB0b2tlbiBpbnRyb3NwZWN0aW9uIGVuZHBvaW50IFVSTCBpcyBtaXNzaW5nIGZyb20gY29uZmlndXJhdGlvbiEnO1xuXG4vLyBSZXR1cm5zIGEgcHJvbWlzZSB0aGF0IGZ1bGZpbGxzIGlmIHRoaXMgdXNlciBpZCBpcyB2YWxpZC5cbmZ1bmN0aW9uIHZhbGlkYXRlQXV0aERhdGEoYXV0aERhdGEsIG9wdGlvbnMpIHtcbiAgcmV0dXJuIHJlcXVlc3RUb2tlbkluZm8ob3B0aW9ucywgYXV0aERhdGEuYWNjZXNzX3Rva2VuKS50aGVuKHJlc3BvbnNlID0+IHtcbiAgICBpZiAoXG4gICAgICAhcmVzcG9uc2UgfHxcbiAgICAgICFyZXNwb25zZS5hY3RpdmUgfHxcbiAgICAgIChvcHRpb25zLnVzZXJpZEZpZWxkICYmIGF1dGhEYXRhLmlkICE9PSByZXNwb25zZVtvcHRpb25zLnVzZXJpZEZpZWxkXSlcbiAgICApIHtcbiAgICAgIHRocm93IG5ldyBQYXJzZS5FcnJvcihQYXJzZS5FcnJvci5PQkpFQ1RfTk9UX0ZPVU5ELCBJTlZBTElEX0FDQ0VTUyk7XG4gICAgfVxuICB9KTtcbn1cblxuZnVuY3Rpb24gdmFsaWRhdGVBcHBJZChhcHBJZHMsIGF1dGhEYXRhLCBvcHRpb25zKSB7XG4gIGlmICghb3B0aW9ucyB8fCAhb3B0aW9ucy5hcHBpZEZpZWxkKSB7XG4gICAgcmV0dXJuIFByb21pc2UucmVzb2x2ZSgpO1xuICB9XG4gIGlmICghYXBwSWRzIHx8IGFwcElkcy5sZW5ndGggPT09IDApIHtcbiAgICB0aHJvdyBuZXcgUGFyc2UuRXJyb3IoUGFyc2UuRXJyb3IuT0JKRUNUX05PVF9GT1VORCwgTUlTU0lOR19BUFBJRFMpO1xuICB9XG4gIHJldHVybiByZXF1ZXN0VG9rZW5JbmZvKG9wdGlvbnMsIGF1dGhEYXRhLmFjY2Vzc190b2tlbikudGhlbihyZXNwb25zZSA9PiB7XG4gICAgaWYgKCFyZXNwb25zZSB8fCAhcmVzcG9uc2UuYWN0aXZlKSB7XG4gICAgICB0aHJvdyBuZXcgUGFyc2UuRXJyb3IoUGFyc2UuRXJyb3IuT0JKRUNUX05PVF9GT1VORCwgSU5WQUxJRF9BQ0NFU1MpO1xuICAgIH1cbiAgICBjb25zdCBhcHBpZEZpZWxkID0gb3B0aW9ucy5hcHBpZEZpZWxkO1xuICAgIGlmICghcmVzcG9uc2VbYXBwaWRGaWVsZF0pIHtcbiAgICAgIHRocm93IG5ldyBQYXJzZS5FcnJvcihQYXJzZS5FcnJvci5PQkpFQ1RfTk9UX0ZPVU5ELCBJTlZBTElEX0FDQ0VTU19BUFBJRCk7XG4gICAgfVxuICAgIGNvbnN0IHJlc3BvbnNlVmFsdWUgPSByZXNwb25zZVthcHBpZEZpZWxkXTtcbiAgICBpZiAoIUFycmF5LmlzQXJyYXkocmVzcG9uc2VWYWx1ZSkgJiYgYXBwSWRzLmluY2x1ZGVzKHJlc3BvbnNlVmFsdWUpKSB7XG4gICAgICByZXR1cm47XG4gICAgfSBlbHNlIGlmIChcbiAgICAgIEFycmF5LmlzQXJyYXkocmVzcG9uc2VWYWx1ZSkgJiZcbiAgICAgIHJlc3BvbnNlVmFsdWUuc29tZShhcHBJZCA9PiBhcHBJZHMuaW5jbHVkZXMoYXBwSWQpKVxuICAgICkge1xuICAgICAgcmV0dXJuO1xuICAgIH0gZWxzZSB7XG4gICAgICB0aHJvdyBuZXcgUGFyc2UuRXJyb3IoUGFyc2UuRXJyb3IuT0JKRUNUX05PVF9GT1VORCwgSU5WQUxJRF9BQ0NFU1NfQVBQSUQpO1xuICAgIH1cbiAgfSk7XG59XG5cbi8vIEEgcHJvbWlzZSB3cmFwcGVyIGZvciByZXF1ZXN0cyB0byB0aGUgT0F1dGgyIHRva2VuIGludHJvc3BlY3Rpb24gZW5kcG9pbnQuXG5mdW5jdGlvbiByZXF1ZXN0VG9rZW5JbmZvKG9wdGlvbnMsIGFjY2Vzc190b2tlbikge1xuICBpZiAoIW9wdGlvbnMgfHwgIW9wdGlvbnMudG9rZW5JbnRyb3NwZWN0aW9uRW5kcG9pbnRVcmwpIHtcbiAgICB0aHJvdyBuZXcgUGFyc2UuRXJyb3IoUGFyc2UuRXJyb3IuT0JKRUNUX05PVF9GT1VORCwgTUlTU0lOR19VUkwpO1xuICB9XG4gIGNvbnN0IHBhcnNlZFVybCA9IG5ldyBVUkwob3B0aW9ucy50b2tlbkludHJvc3BlY3Rpb25FbmRwb2ludFVybCk7XG4gIGNvbnN0IHBvc3REYXRhID0gcXVlcnlzdHJpbmcuc3RyaW5naWZ5KHtcbiAgICB0b2tlbjogYWNjZXNzX3Rva2VuLFxuICB9KTtcbiAgY29uc3QgaGVhZGVycyA9IHtcbiAgICAnQ29udGVudC1UeXBlJzogJ2FwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZCcsXG4gICAgJ0NvbnRlbnQtTGVuZ3RoJzogQnVmZmVyLmJ5dGVMZW5ndGgocG9zdERhdGEpLFxuICB9O1xuICBpZiAob3B0aW9ucy5hdXRob3JpemF0aW9uSGVhZGVyKSB7XG4gICAgaGVhZGVyc1snQXV0aG9yaXphdGlvbiddID0gb3B0aW9ucy5hdXRob3JpemF0aW9uSGVhZGVyO1xuICB9XG4gIGNvbnN0IHBvc3RPcHRpb25zID0ge1xuICAgIGhvc3RuYW1lOiBwYXJzZWRVcmwuaG9zdG5hbWUsXG4gICAgcGF0aDogcGFyc2VkVXJsLnBhdGhuYW1lLFxuICAgIG1ldGhvZDogJ1BPU1QnLFxuICAgIGhlYWRlcnM6IGhlYWRlcnMsXG4gIH07XG4gIHJldHVybiBodHRwc1JlcXVlc3QucmVxdWVzdChwb3N0T3B0aW9ucywgcG9zdERhdGEpO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IHtcbiAgdmFsaWRhdGVBcHBJZDogdmFsaWRhdGVBcHBJZCxcbiAgdmFsaWRhdGVBdXRoRGF0YTogdmFsaWRhdGVBdXRoRGF0YSxcbn07XG4iXSwibWFwcGluZ3MiOiI7O0FBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBLE1BQU1BLEtBQUssR0FBR0MsT0FBTyxDQUFDLFlBQVksQ0FBQyxDQUFDRCxLQUFLO0FBQ3pDLE1BQU1FLFdBQVcsR0FBR0QsT0FBTyxDQUFDLGFBQWEsQ0FBQztBQUMxQyxNQUFNRSxZQUFZLEdBQUdGLE9BQU8sQ0FBQyxnQkFBZ0IsQ0FBQztBQUU5QyxNQUFNRyxjQUFjLEdBQUcsK0NBQStDO0FBQ3RFLE1BQU1DLG9CQUFvQixHQUN4QixnSEFBZ0g7QUFDbEgsTUFBTUMsY0FBYyxHQUNsQixpRkFBaUY7QUFDbkYsTUFBTUMsV0FBVyxHQUFHLHdFQUF3RTs7QUFFNUY7QUFDQSxTQUFTQyxnQkFBZ0JBLENBQUNDLFFBQVEsRUFBRUMsT0FBTyxFQUFFO0VBQzNDLE9BQU9DLGdCQUFnQixDQUFDRCxPQUFPLEVBQUVELFFBQVEsQ0FBQ0csWUFBWSxDQUFDLENBQUNDLElBQUksQ0FBQ0MsUUFBUSxJQUFJO0lBQ3ZFLElBQ0UsQ0FBQ0EsUUFBUSxJQUNULENBQUNBLFFBQVEsQ0FBQ0MsTUFBTSxJQUNmTCxPQUFPLENBQUNNLFdBQVcsSUFBSVAsUUFB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