diff --git a/NOTICE.txt b/NOTICE.txt
index 396db11e6cb..0fa2229e28a 100644
--- a/NOTICE.txt
+++ b/NOTICE.txt
@@ -618,6 +618,13 @@ https://github.com/yonik/noggit
This product includes the Angular UI UI Grid JavaScript library.
Copyright (c) 2015 the AngularUI Team, http://angular-ui.github.com
+=========================================================================
+== jsSHA notice ==
+=========================================================================
+
+This product includes the jsSHA library.
+Copyright (c) 2008-2023 Brian Turek, 1998-2009 Paul Johnston & Contributors
+https://github.com/Caligatio/jsSHA
=========================================================================
== grpc notice ==
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 09679c9a0c1..21055518960 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -51,7 +51,7 @@ Improvements
* SOLR-15474: Make Circuit breakers individually pluggable (Atri Sharma, Christine Poerschke, janhoy)
-* SOLR-16896, SOLR-16897: Add support of OAuth 2.0/OIDC 'code with PKCE' flow (Lamine Idjeraoui, janhoy, Kevin Risden)
+* SOLR-16896, SOLR-16897: Add support of OAuth 2.0/OIDC 'code with PKCE' flow (Lamine Idjeraoui, janhoy, Kevin Risden, Anshum Gupta)
* SOLR-16879: Limit the number of concurrent expensive core admin operations by running them in a
dedicated thread pool. Backup, Restore and Split are expensive operations.
diff --git a/solr/webapp/web/index.html b/solr/webapp/web/index.html
index afffe67f764..35b1d664860 100644
--- a/solr/webapp/web/index.html
+++ b/solr/webapp/web/index.html
@@ -70,6 +70,7 @@
+
diff --git a/solr/webapp/web/js/angular/controllers/login.js b/solr/webapp/web/js/angular/controllers/login.js
index b76ec1f4a8a..0e22100c306 100644
--- a/solr/webapp/web/js/angular/controllers/login.js
+++ b/solr/webapp/web/js/angular/controllers/login.js
@@ -60,92 +60,166 @@ solrAdminApp.controller('LoginController',
var hp = AuthenticationService.decodeHashParams(hash);
var expectedState = sessionStorage.getItem("auth.stateRandom") + "_" + sessionStorage.getItem("auth.location");
sessionStorage.setItem("auth.state", "error");
- if (hp['access_token'] && hp['token_type'] && hp['state']) {
- // Validate state
- if (hp['state'] !== expectedState) {
- $scope.error = "Problem with auth callback";
- console.log("Expected state param " + expectedState + " but got " + hp['state']);
- errorText += "Invalid values in state parameter. ";
- }
- // Validate token type
- if (hp['token_type'].toLowerCase() !== "bearer") {
- console.log("Expected token_type param 'bearer', but got " + hp['token_type']);
- errorText += "Invalid values in token_type parameter. ";
- }
- // Unpack ID token and validate nonce, get username
- if (hp['id_token']) {
- var idToken = hp['id_token'].split(".");
- if (idToken.length === 3) {
- var payload = AuthenticationService.decodeJwtPart(idToken[1]);
- if (!payload['nonce'] || payload['nonce'] !== sessionStorage.getItem("auth.nonce")) {
- errorText += "Invalid 'nonce' value, possible attack detected. Please log in again. ";
- }
+ $scope.authData = AuthenticationService.getAuthDataHeader();
+ if (!validateState(hp['state'], expectedState)) {
+ $scope.error = "Problems with OpenID callback";
+ $scope.errorDescription = errorText;
+ $scope.http401 = "true";
+ sessionStorage.setItem("auth.state", "error");
+ }
+ else {
+ var flow = $scope.authData ? $scope.authData['authorization_flow'] : undefined;
+ console.log("Callback: authorization_flow : " +flow);
+ var isCodePKCE = flow == 'code_pkce';
+ if (isCodePKCE) {
+ // code flow with PKCE
+ var code = hp['code'];
+ var tokenEndpoint = $scope.authData['tokenEndpoint'];
+ // concurrent Solr API calls will trigger 401 and erase session's "auth.realm" in app.js
+ // save it before it's erased
+ var authRealm = sessionStorage.getItem("auth.realm");
+
+ var data = {
+ 'grant_type': 'authorization_code',
+ 'code': code,
+ 'redirect_uri': $window.location.href.split('#')[0],
+ 'scope': "openid " + $scope.authData['scope'],
+ 'code_verifier': sessionStorage.getItem('codeVerifier'),
+ "client_id": $scope.authData['client_id']
+ };
- if (errorText === "") {
- sessionStorage.setItem("auth.username", payload['sub']);
- sessionStorage.setItem("auth.header", "Bearer " + hp['access_token']);
- sessionStorage.removeItem("auth.statusText");
- sessionStorage.removeItem("auth.stateRandom");
- sessionStorage.removeItem("auth.wwwAuthHeader");
- console.log("User " + payload['sub'] + " is logged in");
- var redirectTo = sessionStorage.getItem("auth.location");
- console.log("Redirecting to stored location " + redirectTo);
- sessionStorage.setItem("auth.state", "authenticated");
- sessionStorage.removeItem("http401");
- $location.path(redirectTo).hash("");
+ console.debug(`Callback. Got code: ${code} \nCalling token endpoint:: ${tokenEndpoint} `);
+ AuthenticationService.getOAuthTokens(tokenEndpoint, data).then(function(response) {
+ var accessToken = response.access_token;
+ var idToken = response.id_token;
+ var tokenType = response.access_type;
+ sessionStorage.setItem("auth.realm", authRealm);
+ processTokensResponse(accessToken, idToken, tokenType, expectedState, hp);
+ }).catch(function (error) {
+ errorText += "Error calling token endpoint. ";
+ $scope.error = "Problems with OpenID callback";
+ $scope.errorDescription = errorText;
+ $scope.http401 = "true";
+ sessionStorage.setItem("auth.state", "error");
+ if (error && error.data) {
+ console.error("Error getting tokens: " + JSON.stringify(error.data));
+ } else {
+ console.error("Error getting tokens: " + error);
}
- } else {
- console.log("Expected JWT compact id_token param but got " + idToken);
- errorText += "Invalid values in id_token parameter. ";
- }
- } else {
- console.log("Callback was missing the id_token parameter, could not validate nonce.");
- errorText += "Callback was missing the id_token parameter, could not validate nonce. ";
- }
- if (hp['access_token'].split(".").length !== 3) {
- console.log("Expected JWT compact access_token param but got " + hp['access_token']);
- errorText += "Invalid values in access_token parameter. ";
- }
- if (errorText !== "") {
- $scope.error = "Problems with OpenID callback";
- $scope.errorDescription = errorText;
- $scope.http401 = "true";
+ });
}
- // End callback processing
- } else if (hp['error']) {
- // The callback had errors
- console.log("Error received from idp: " + hp['error']);
- var errorDescriptions = {};
- errorDescriptions['invalid_request'] = "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed.";
- errorDescriptions['unauthorized_client'] = "The client is not authorized to request an access token using this method.";
- errorDescriptions['access_denied'] = "The resource owner or authorization server denied the request.";
- errorDescriptions['unsupported_response_type'] = "The authorization server does not support obtaining an access token using this method.";
- errorDescriptions['invalid_scope'] = "The requested scope is invalid, unknown, or malformed.";
- errorDescriptions['server_error'] = "The authorization server encountered an unexpected condition that prevented it from fulfilling the request.";
- errorDescriptions['temporarily_unavailable'] = "The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server.";
- $scope.error = "Callback from Id Provider contained error. ";
- if (hp['error_description']) {
- $scope.errorDescription = decodeURIComponent(hp['error_description']);
- } else {
- $scope.errorDescription = errorDescriptions[hp['error']];
+ else {
+ // implicit flow
+ processTokensResponse(hp['access_token'], hp['id_token'], hp['token_type'], expectedState, hp);
}
- if (hp['error_uri']) {
- $scope.errorDescription += " More information at " + hp['error_uri'] + ". ";
+ }
+ }
+ }
+
+ function validateState(state, expectedState) {
+ if (state !== expectedState) {
+ $scope.error = "Problem with auth callback";
+ console.error("Expected state param " + expectedState + " but got " + state);
+ errorText += "Invalid values in state parameter. ";
+ return false;
+ }
+ return true;
+ }
+
+ function processTokensResponse(accessToken, idToken, tokenType, expectedState, hp) {
+ if (accessToken && hp['state']) {
+ // Validate token type.
+ if (!tokenType) {
+ //Assume the type is 'bearer' if it's not returned. Most IdProviders support 'bearer' by default but don't always return the type.
+ tokenType = "bearer";
+ }
+ else if(tokenType.toLowerCase() !== "bearer") {
+ console.error("Expected token_type param 'bearer', but got " + tokenType);
+ errorText += "Invalid values in token_type parameter. ";
+ }
+ // Unpack ID token and validate nonce, get username
+ if (idToken) {
+ var idTokenArray = idToken.split(".");
+ if (idTokenArray.length === 3) {
+ var payload = AuthenticationService.decodeJwtPart(idTokenArray[1]);
+ if (!payload['nonce'] || payload['nonce'] !== sessionStorage.getItem("auth.nonce")) {
+ errorText += "Invalid 'nonce' value, possible attack detected. Please log in again. ";
}
- if (hp['state'] !== expectedState) {
- $scope.errorDescription += "The state parameter returned from ID Provider did not match the one we sent.";
+
+ if (errorText === "") {
+ sessionStorage.setItem("auth.username", payload['sub']);
+ sessionStorage.setItem("auth.header", "Bearer " + accessToken);
+ sessionStorage.removeItem("auth.statusText");
+ sessionStorage.removeItem("auth.stateRandom");
+ sessionStorage.removeItem("auth.wwwAuthHeader");
+ console.log("User " + payload['sub'] + " is logged in");
+ var redirectTo = sessionStorage.getItem("auth.location");
+ console.log("Redirecting to stored location " + redirectTo);
+ sessionStorage.setItem("auth.state", "authenticated");
+ sessionStorage.removeItem("http401");
+ sessionStorage.setItem("auth.scheme", "Bearer");
+ $location.path(redirectTo).hash("");
}
- sessionStorage.setItem("auth.state", "error");
+ } else {
+ console.error("Expected JWT compact id_token param but got " + idTokenArray);
+ errorText += "Invalid values in id_token parameter. ";
}
+ } else {
+ console.error("Callback was missing the id_token parameter, could not validate nonce.");
+ errorText += "Callback was missing the id_token parameter, could not validate nonce. ";
+ }
+ if (accessToken.split(".").length !== 3) {
+ console.error("Expected JWT compact access_token param but got " + accessToken);
+ errorText += "Invalid values in access_token parameter. ";
+ }
+ if (errorText !== "") {
+ $scope.error = "Problems with OpenID callback";
+ $scope.errorDescription = errorText;
+ $scope.http401 = "true";
+ }
+ // End callback processing
+ } else if (hp['error']) {
+ // The callback had errors
+ console.error("Error received from idp: " + hp['error']);
+ var errorDescriptions = {};
+ errorDescriptions['invalid_request'] = "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed.";
+ errorDescriptions['unauthorized_client'] = "The client is not authorized to request an access token using this method.";
+ errorDescriptions['access_denied'] = "The resource owner or authorization server denied the request.";
+ errorDescriptions['unsupported_response_type'] = "The authorization server does not support obtaining an access token using this method.";
+ errorDescriptions['invalid_scope'] = "The requested scope is invalid, unknown, or malformed.";
+ errorDescriptions['server_error'] = "The authorization server encountered an unexpected condition that prevented it from fulfilling the request.";
+ errorDescriptions['temporarily_unavailable'] = "The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server.";
+ $scope.error = "Callback from Id Provider contained error. ";
+ if (hp['error_description']) {
+ $scope.errorDescription = decodeURIComponent(hp['error_description']);
+ } else {
+ $scope.errorDescription = errorDescriptions[hp['error']];
+ }
+ if (hp['error_uri']) {
+ $scope.errorDescription += " More information at " + hp['error_uri'] + ". ";
}
+ if (hp['state'] !== expectedState) {
+ $scope.errorDescription += "The state parameter returned from ID Provider did not match the one we sent.";
+ }
+ sessionStorage.setItem("auth.state", "error");
+ }
+ else{
+ console.error(`Invalid data received from idp: accessToken: ${accessToken},
+ idToken: ${idToken}, state: ${hp['state']}`);
+ errorText += "Invalid data received from the OpenID provider. ";
+ $scope.http401 = "true";
+ $scope.error = "Problems with OpenID callback.";
+ $scope.errorDescription = errorText;
+ sessionStorage.setItem("auth.state", "error");
}
+ }
if (errorText === "" && !$scope.error && authParams) {
$scope.error = authParams['error'];
$scope.errorDescription = authParams['error_description'];
$scope.authData = AuthenticationService.getAuthDataHeader();
}
-
+
$scope.authScheme = sessionStorage.getItem("auth.scheme");
$scope.authRealm = sessionStorage.getItem("auth.realm");
$scope.wwwAuthHeader = sessionStorage.getItem("auth.wwwAuthHeader");
@@ -165,20 +239,49 @@ solrAdminApp.controller('LoginController',
$location.path("/");
};
- $scope.jwtLogin = function () {
+ $scope.jwtLogin = async function () {
var stateRandom = Math.random().toString(36).substr(2);
sessionStorage.setItem("auth.stateRandom", stateRandom);
var authState = stateRandom + "_" + sessionStorage.getItem("auth.location");
var authNonce = Math.random().toString(36).substr(2) + Math.random().toString(36).substr(2) + Math.random().toString(36).substr(2);
sessionStorage.setItem("auth.nonce", authNonce);
- var params = {
- "response_type" : "id_token token",
- "client_id" : $scope.authData['client_id'],
- "redirect_uri" : $window.location.href.split('#')[0],
- "scope" : "openid " + $scope.authData['scope'],
- "state" : authState,
- "nonce" : authNonce
- };
+ var authData = AuthenticationService.getAuthDataHeader();
+ var flow = authData ? authData['authorization_flow'] : "implicit";
+ console.log("jwtLogin flow: "+ flow);
+ var isCodePKCE = flow == 'code_pkce';
+
+ var params = {};
+ if (isCodePKCE) {
+ console.debug("Login with 'Code PKCE' flow");
+ var codeVerifier = AuthenticationService.generateCodeVerifier();
+ var code_challenge = await AuthenticationService.generateCodeChallengeFromVerifier(codeVerifier);
+ var codeChallengeMethod = AuthenticationService.getCodeChallengeMethod();
+ sessionStorage.setItem('codeVerifier', codeVerifier);
+ params = {
+ "response_type": "code",
+ "client_id": $scope.authData['client_id'],
+ "redirect_uri": $window.location.href.split('#')[0],
+ "scope": "openid " + $scope.authData['scope'],
+ "state": authState,
+ "nonce": authNonce,
+ "response_mode": "fragment",
+ "code_challenge": code_challenge,
+ "code_challenge_method": codeChallengeMethod
+ };
+ }
+ else {
+ console.debug("Login with 'Implicit' flow");
+ params = {
+ "response_type": "id_token token",
+ "client_id": $scope.authData['client_id'],
+ "redirect_uri": $window.location.href.split('#')[0],
+ "scope": "openid " + $scope.authData['scope'],
+ "state": authState,
+ "nonce": authNonce,
+ "response_mode": 'fragment',
+ "grant_type": 'implicit'
+ };
+ }
var endpointBaseUrl = $scope.authData['authorizationEndpoint'];
var loc = endpointBaseUrl + "?" + paramsToString(params);
@@ -191,7 +294,7 @@ solrAdminApp.controller('LoginController',
for (var p in params) {
if( params.hasOwnProperty(p) ) {
arr.push(p + "=" + encodeURIComponent(params[p]));
- }
+ }
}
return arr.join("&");
}
@@ -204,7 +307,7 @@ solrAdminApp.controller('LoginController',
redirect.forEach(function(uri) { // Check that current node URL is among the configured callback URIs
if ($window.location.href.startsWith(uri)) isLoginNode = true;
});
- return isLoginNode;
+ return isLoginNode;
} else {
return true; // no redirect UIRs configured, all nodes are potential login nodes
}
diff --git a/solr/webapp/web/js/angular/services.js b/solr/webapp/web/js/angular/services.js
index 1c0b702dbe1..2b870ab53ff 100644
--- a/solr/webapp/web/js/angular/services.js
+++ b/solr/webapp/web/js/angular/services.js
@@ -286,8 +286,75 @@ solrAdminServices.factory('System',
})
}])
.factory('AuthenticationService',
- ['base64', function (base64) {
- var service = {};
+ ['base64', '$resource', function (base64, $resource) {
+ var service = {};
+
+ service.getOAuthTokens = function (url, data) {
+ var serializedData = serialize(data);
+ var resource = $resource(url, {}, {
+ getToken: {
+ method: 'POST',
+ timeout: 10000,
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'X-Requested-With': undefined // Set this header to undefined to prevent preflight requests
+ },
+ transformResponse: function (data) {
+ return angular.fromJson(data);
+ }
+ }
+ });
+ return resource.getToken({}, serializedData).$promise;
+ };
+
+ var codeChallengeMethod = "S256";
+ service.getCodeChallengeMethod = function getCodeChallengeMethod() {
+ return codeChallengeMethod;
+ }
+
+ service.generateCodeVerifier = function generateCodeVerifier() {
+ var codeVerifier = '';
+ var possibleChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';
+ for (var i = 0; i < 96; i++) {
+ codeVerifier += possibleChars.charAt(Math.floor(Math.random() * possibleChars.length));
+ }
+ return codeVerifier;
+ }
+
+ service.generateCodeChallengeFromVerifier = async function generateCodeChallengeFromVerifier(v) {
+ var hashed = await sha256(v);
+ var base64encoded = base64urlencode(hashed);
+ return base64encoded;
+ }
+
+ function sha256(str) {
+ const shaObj = new jsSHA("SHA-256", "TEXT", { encoding: "UTF8" });
+ shaObj.update(str);
+ return shaObj.getHash("UINT8ARRAY");
+ }
+
+ function base64urlencode(a) {
+ var str = "";
+ var bytes = new Uint8Array(a);
+ var len = bytes.byteLength;
+ for (var i = 0; i < len; i++) {
+ str += String.fromCharCode(bytes[i]);
+ }
+ return btoa(str)
+ .replace(/\+/g, "-")
+ .replace(/\//g, "_")
+ .replace(/=+$/, "");
+ }
+
+ var serialize = function (obj) {
+ var str = [];
+ for (var p in obj) {
+ if (obj.hasOwnProperty(p)) {
+ str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
+ }
+ }
+ return str.join("&");
+ };
service.SetCredentials = function (username, password) {
var authdata = base64.encode(username + ':' + password);
@@ -305,6 +372,7 @@ solrAdminServices.factory('System',
sessionStorage.removeItem("auth.statusText");
localStorage.removeItem("auth.stateRandom");
sessionStorage.removeItem("auth.nonce");
+ sessionStorage.removeItem("auth.flow");
};
service.getAuthDataHeader = function () {
@@ -330,11 +398,11 @@ solrAdminServices.factory('System',
service.isJwtCallback = function (hash) {
var hp = this.decodeHashParams(hash);
// console.log("Decoded hash as " + JSON.stringify(hp, undefined, 2)); // For debugging callbacks
- return (hp['access_token'] && hp['token_type'] && hp['state']) || hp['error'];
+ return (hp['access_token'] && hp['token_type'] && hp['state']) || (hp['code'] && hp['state'])|| hp['error'];
};
-
+
service.decodeHashParams = function(hash) {
- // access_token, token_type, expires_in, state
+ // access_token, token_type, expires_in, state, code
if (hash == null || hash.length === 0) {
return {};
}
diff --git a/solr/webapp/web/libs/jssha-3.3.1-sha256.min.js b/solr/webapp/web/libs/jssha-3.3.1-sha256.min.js
new file mode 100644
index 00000000000..697ee447f4c
--- /dev/null
+++ b/solr/webapp/web/libs/jssha-3.3.1-sha256.min.js
@@ -0,0 +1,24 @@
+/*
+
+ * A JavaScript implementation of the SHA family of hashes - defined in FIPS PUB 180-4, FIPS PUB 202,
+ * and SP 800-185 - as well as the corresponding HMAC implementation as defined in FIPS PUB 198-1.
+ *
+ * Copyright 2008-2023 Brian Turek, 1998-2009 Paul Johnston & Contributors
+ * Distributed under the BSD License
+ * See http://caligatio.github.com/jsSHA/ for more information
+ *
+ * Two ECMAScript polyfill functions carry the following license:
+ *
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED,
+ * INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
+ * MERCHANTABLITY OR NON-INFRINGEMENT.
+ *
+ * See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
+
+ */
+
+!function(t,r){"object"==typeof exports&&"undefined"!=typeof module?module.exports=r():"function"==typeof define&&define.amd?define(r):(t="undefined"!=typeof globalThis?globalThis:t||self).jsSHA=r()}(this,function(){"use strict";var n=function(t,r){return(n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,r){t.__proto__=r}||function(t,r){for(var n in r)Object.prototype.hasOwnProperty.call(r,n)&&(t[n]=r[n])})(t,r)},v="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",r="ARRAYBUFFER not supported by this environment",e="UINT8ARRAY not supported by this environment";function o(t,r,n,e){for(var o,i,s=r||[0],u=(n=n||0)>>>3,h=-1===e?3:0,a=0;a>>2,s.length<=o&&s.push(0),s[o]|=t[a]<<8*(h+e*(i%4));return{value:s,binLen:8*t.length+n}}function i(t,A,g){switch(A){case"UTF8":case"UTF16BE":case"UTF16LE":break;default:throw new Error("encoding must be UTF8, UTF16BE, or UTF16LE")}switch(t){case"HEX":return function(t,r,n){var e,o,i,s=t,t=r,r=n,u=g;if(0!=s.length%2)throw new Error("String of HEX type must be in byte increments");for(var h=t||[0],a=(r=r||0)>>>3,f=-1===u?3:0,c=0;c>>1)+a)>>>2;h.length<=o;)h.push(0);h[o]|=e<<8*(f+u*(i%4))}return{value:h,binLen:4*s.length+r}};case"TEXT":return function(t,r,n){var e,o,i,s,u,h,a,f,c=t,t=A,p=0,l=g,w=0,d=r||[0],v=(p=n||0)>>>3;if("UTF8"===t)for(a=-1===l?3:0,i=0;i>>6),o.push(128|63&e)):e<55296||57344<=e?o.push(224|e>>>12,128|e>>>6&63,128|63&e):(e=65536+((1023&e)<<10|1023&c.charCodeAt(i+=1)),o.push(240|e>>>18,128|e>>>12&63,128|e>>>6&63,128|63&e)),s=0;s>>2;d.length<=u;)d.push(0);d[u]|=o[s]<<8*(a+l*(h%4)),w+=1}else for(a=-1===l?2:0,f="UTF16LE"===t&&1!==l||"UTF16LE"!==t&&1===l,i=0;i>>8),u=(h=w+v)>>>2;d.length<=u;)d.push(0);d[u]|=e<<8*(a+l*(h%4)),w+=2}return{value:d,binLen:8*w+p}};case"B64":return function(t,r,n){var e,o,i,s,u,h,a=t,t=0,f=g,c=0,p=r||[0],l=(t=n||0)>>>3,w=-1===f?3:0,r=a.indexOf("=");if(-1===a.search(/^[a-zA-Z0-9=+/]+$/))throw new Error("Invalid character in base-64 string");if(a=a.replace(/=/g,""),-1!==r&&r>>2;p.length<=u;)p.push(0);p[u]|=(i>>>16-8*o&255)<<8*(w+f*(h%4)),c+=1}}return{value:p,binLen:8*c+t}};case"BYTES":return function(t,r,n){for(var e,o,i,s=t,t=0,u=g,h=r||[0],a=(t=n||0)>>>3,f=-1===u?3:0,c=0;c>>2,h.length<=o&&h.push(0),h[o]|=e<<8*(f+u*(i%4));return{value:h,binLen:8*s.length+t}};case"ARRAYBUFFER":try{new ArrayBuffer(0)}catch(t){throw new Error(r)}return function(t,r,n){return r=r,n=n,e=g,o(new Uint8Array(t),r,n,e);var e};case"UINT8ARRAY":try{new Uint8Array(0)}catch(t){throw new Error(e)}return function(t,r,n){return o(t,r,n,g)};default:throw new Error("format must be HEX, TEXT, B64, BYTES, ARRAYBUFFER, or UINT8ARRAY")}}function s(t,l,w,d){switch(t){case"HEX":return function(t){for(var r,n=t,e=w,t=d,o="0123456789abcdef",i="",s=l/8,u=-1===e?3:0,h=0;h>>2]>>>8*(u+e*(h%4)),i+=o.charAt(r>>>4&15)+o.charAt(15&r);return t.outputUpper?i.toUpperCase():i};case"B64":return function(t){for(var r,n,e,o,i=t,s=l,u=w,h=d,a="",f=s/8,c=-1===u?3:0,p=0;p>>2]:0,o=p+2>>2]:0,n=(i[p>>>2]>>>8*(c+u*(p%4))&255)<<16|(e>>>8*(c+u*((p+1)%4))&255)<<8|o>>>8*(c+u*((p+2)%4))&255,r=0;r<4;r+=1)a+=8*p+6*r<=s?v.charAt(n>>>6*(3-r)&63):h.b64Pad;return a};case"BYTES":return function(t){for(var r,n=t,e=w,o="",i=l/8,s=-1===e?3:0,u=0;u>>2]>>>8*(s+e*(u%4))&255,o+=String.fromCharCode(r);return o};case"ARRAYBUFFER":try{new ArrayBuffer(0)}catch(t){throw new Error(r)}return function(t){for(var r=t,n=w,e=l/8,t=new ArrayBuffer(e),o=new Uint8Array(t),i=-1===n?3:0,s=0;s>>2]>>>8*(i+n*(s%4))&255;return t};case"UINT8ARRAY":try{new Uint8Array(0)}catch(t){throw new Error(e)}return function(t){for(var r=t,n=w,e=l/8,o=-1===n?3:0,i=new Uint8Array(e),s=0;s>>2]>>>8*(o+n*(s%4))&255;return i};default:throw new Error("format must be HEX, B64, BYTES, ARRAYBUFFER, or UINT8ARRAY")}}var E=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298],u=[3238371032,914150663,812702999,4144912697,4290775857,1750603025,1694076839,3204075428],h=[1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225];function a(t){var r={outputUpper:!1,b64Pad:"=",outputLen:-1},t=t||{},n="Output length must be a multiple of 8";if(r.outputUpper=t.outputUpper||!1,t.b64Pad&&(r.b64Pad=t.b64Pad),t.outputLen){if(t.outputLen%8!=0)throw new Error(n);r.outputLen=t.outputLen}else if(t.shakeLen){if(t.shakeLen%8!=0)throw new Error(n);r.outputLen=t.shakeLen}if("boolean"!=typeof r.outputUpper)throw new Error("Invalid outputUpper formatting option");if("string"!=typeof r.b64Pad)throw new Error("Invalid b64Pad formatting option");return r}function y(t,r){return t>>>r|t<<32-r}function m(t,r){var n=(65535&t)+(65535&r);return(65535&(t>>>16)+(r>>>16)+(n>>>16))<<16|65535&n}function f(t){return("SHA-224"==t?u:h).slice()}function c(t,r){for(var n,e,o,i,s,u,h,a=[],f=r[0],c=r[1],p=r[2],l=r[3],w=r[4],d=r[5],v=r[6],A=r[7],g=0;g<64;g+=1)a[g]=g<16?t[g]:(o=y(o=a[g-2],17)^y(o,19)^o>>>10,i=a[g-7],h=a[g-15],h=y(h,7)^y(h,18)^h>>>3,s=a[g-16],u=void 0,(65535&(o>>>16)+(i>>>16)+(h>>>16)+(s>>>16)+((u=(65535&o)+(65535&i)+(65535&h)+(65535&s))>>>16))<<16|65535&u),o=A,i=y(w,6)^y(w,11)^y(w,25),h=w&d^~w&v,s=E[g],u=a[g],e=void 0,n=m(y(f,2)^y(f,13)^y(f,22),f&c^f&p^c&p),A=v,v=d,d=w,w=m(l,e=(65535&(o>>>16)+(i>>>16)+(h>>>16)+(s>>>16)+(u>>>16)+((e=(65535&o)+(65535&i)+(65535&h)+(65535&s)+(65535&u))>>>16))<<16|65535&e),l=p,p=c,c=f,f=m(e,n);return r[0]=m(f,r[0]),r[1]=m(c,r[1]),r[2]=m(p,r[2]),r[3]=m(l,r[3]),r[4]=m(w,r[4]),r[5]=m(d,r[5]),r[6]=m(v,r[6]),r[7]=m(A,r[7]),r}A.prototype.update=function(t){for(var r=0,n=this.m>>>5,t=this.h(t,this.H,this.Y),e=t.binLen,o=t.value,i=e>>>5,s=0;s>>5),this.Y=e%this.m,this.C=!0,this},A.prototype.getHash=function(t,r){var n,e,o=this.F,r=a(r);if(this.g){if(-1===r.outputLen)throw new Error("Output length must be specified in options");o=r.outputLen}t=s(t,o,this.u,r);if(this.L&&this.t)return t(this.t(r));for(e=this.T(this.H.slice(),this.Y,this.I,this.A(this.R),o),n=1;n>>24-o%32),e=this.T(e,o,0,this.U(this.S),o);return t(e)},A.prototype.setHMACKey=function(t,r,n){if(!this.o)throw new Error("Variant does not support HMAC");if(this.C)throw new Error("Cannot set MAC key after calling update");r=i(r,(n||{}).encoding||"UTF8",this.u);this.B(r(t))},A.prototype.B=function(t){var r,n=this.m>>>3,e=n/4-1;if(1!==this.numRounds)throw new Error("Cannot set numRounds with MAC");if(this.L)throw new Error("MAC key already set");for(n>>9<<4),e=t+n;i.length<=u;)i.push(0);for(i[t>>>5]|=128<<24-t%32,i[u]=4294967295&e,i[u-1]=e/4294967296|0,o=0;o= 1");this.S=t,this.H=[],this.Y=0,this.C=!1,this.I=0,this.L=!1,this.N=[],this.X=[]}});
\ No newline at end of file