From 6b58f7cde41a1d2971ebc7a65c2e707f0190347b Mon Sep 17 00:00:00 2001 From: Ethan Turner Date: Sun, 18 Jan 2026 15:26:56 -0800 Subject: [PATCH] feat(CAS Bridge): gateway authentication (auto-login) support --- public/CASBridge/dist/casbridge.dev.js | 2 +- public/CASBridge/dist/casbridge.min.js | 2 +- public/CASBridge/index.js | 136 ++++++++++++++----------- public/CASBridge/package-lock.json | 1 + 4 files changed, 80 insertions(+), 61 deletions(-) diff --git a/public/CASBridge/dist/casbridge.dev.js b/public/CASBridge/dist/casbridge.dev.js index e1528df4..075551df 100644 --- a/public/CASBridge/dist/casbridge.dev.js +++ b/public/CASBridge/dist/casbridge.dev.js @@ -16,7 +16,7 @@ \******************/ /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var jose__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! jose */ \"./node_modules/jose/dist/browser/index.js\");\n/* harmony import */ var js_cookie__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! js-cookie */ \"./node_modules/js-cookie/dist/js.cookie.mjs\");\n\n\n\nasync function verifyJWT(token) {\n try {\n const jwks = await (0,jose__WEBPACK_IMPORTED_MODULE_0__.createRemoteJWKSet)(new URL('https://one.libretexts.org/api/v1/auth/cas-bridge/jwks'));\n const { payload } = await (0,jose__WEBPACK_IMPORTED_MODULE_0__.jwtVerify)(token, jwks, {\n issuer: 'https://one.libretexts.org',\n audience: 'libretexts.org',\n });\n return payload;\n } catch (e) {\n console.error(e);\n return null;\n }\n}\n\nwindow.LibreTextsCASBridgeVerifyJWT = verifyJWT;\n\nwindow.LibreTextsLoginCAS = (event) => {\n event.preventDefault();\n js_cookie__WEBPACK_IMPORTED_MODULE_1__[\"default\"].set('cas_bridge_source', window.location.host, { domain: 'libretexts.org', sameSite: 'lax' });\n js_cookie__WEBPACK_IMPORTED_MODULE_1__[\"default\"].set('cas_bridge_redirect', window.location.href, { domain: 'libretexts.org', sameSite: 'lax' });\n window.location.href = 'https://one.libretexts.org/api/v1/auth/cas-bridge';\n};\n\nwindow.LibreTextsLogoutCAS = (event) => {\n event.preventDefault();\n js_cookie__WEBPACK_IMPORTED_MODULE_1__[\"default\"].remove(`cas_bridge_token_${window.location.host}`, { domain: 'libretexts.org' });\n js_cookie__WEBPACK_IMPORTED_MODULE_1__[\"default\"].remove('cas_bridge_redirect', { domain: 'libretexts.org' });\n js_cookie__WEBPACK_IMPORTED_MODULE_1__[\"default\"].remove('cas_bridge_source', { domain: 'libretexts.org' });\n window.location.href = 'https://auth.libretexts.org/cas/logout';\n};\n\nwindow.addEventListener('DOMContentLoaded', async () => {\n if (window.location.href.endsWith('#')) {\n history.replaceState('', document.title, window.location.pathname + window.location.search);\n }\n\n // \n let login = document.getElementById('emailHolder').innerText;\n const $target = $('.mt-user-quick-login');\n if (login) {\n return;\n }\n // \n\n // \n const loginLink = document.getElementById('ssoHolder').innerText;\n const authorizedLibCookie = js_cookie__WEBPACK_IMPORTED_MODULE_1__[\"default\"].get(`cas_bridge_authorized_${window.location.host}`);\n if (authorizedLibCookie && authorizedLibCookie === 'true' && loginLink) {\n window.location.href = loginLink;\n }\n // \n\n // \n const casBridgeToken = js_cookie__WEBPACK_IMPORTED_MODULE_1__[\"default\"].get(`cas_bridge_token_${window.location.host}`);\n if (casBridgeToken) {\n const payload = await verifyJWT(casBridgeToken);\n if (payload && payload.sub) {\n login = payload;\n }\n }\n if (login) {\n $target.replaceWith(`
  • ${login.first_name} ${login.last_name}
  • `);\n $('.elm-header-user-nav').addClass('authenticated-sso');\n\n const $nativeSignIn = $('.mt-icon-quick-sign-in');\n $nativeSignIn.hide();\n return;\n }\n //
    \n \n if ($target) {\n $target.before(`
  • Sign in
  • `)\n }\n});\n\n\n//# sourceURL=webpack://@libretexts/casbridge/./index.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var jose__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! jose */ \"./node_modules/jose/dist/browser/index.js\");\n/* harmony import */ var js_cookie__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! js-cookie */ \"./node_modules/js-cookie/dist/js.cookie.mjs\");\n\n\n\n/**\n * @param token - token to verify using JWKS\n */\nasync function verifyJWT(token) {\n try {\n const isStaging = window.location.hostname === 'dev.libretexts.org';\n const jwks = await (0,jose__WEBPACK_IMPORTED_MODULE_0__.createRemoteJWKSet)(new URL(`https://${isStaging ? 'staging.' : ''}one.libretexts.org/api/v1/auth/cas-bridge/jwks`));\n const { payload } = await (0,jose__WEBPACK_IMPORTED_MODULE_0__.jwtVerify)(token, jwks, {\n issuer: `https://${isStaging ? 'staging.' : ''}one.libretexts.org`,\n audience: 'libretexts.org',\n });\n return payload;\n } catch (e) {\n console.error(e);\n return null;\n }\n}\n\nwindow.LibreTextsCASBridgeVerifyJWT = verifyJWT;\n\nwindow.LibreTextsLoginCAS = (event, gateway = false) => {\n event?.preventDefault();\n js_cookie__WEBPACK_IMPORTED_MODULE_1__[\"default\"].set('cas_bridge_source', window.location.host, { domain: 'libretexts.org', sameSite: 'lax' });\n js_cookie__WEBPACK_IMPORTED_MODULE_1__[\"default\"].set('cas_bridge_redirect', window.location.href, { domain: 'libretexts.org', sameSite: 'lax' });\n const isStaging = window.location.hostname === 'dev.libretexts.org';\n window.location.href = `https://${isStaging ? 'staging.' : ''}one.libretexts.org/api/v1/auth/cas-bridge${gateway ? '?gateway=true' : ''}`;\n};\n\nwindow.LibreTextsLogoutCAS = (event) => {\n event.preventDefault();\n js_cookie__WEBPACK_IMPORTED_MODULE_1__[\"default\"].remove(`cas_bridge_token_${window.location.host}`, { domain: 'libretexts.org' });\n js_cookie__WEBPACK_IMPORTED_MODULE_1__[\"default\"].remove('cas_bridge_redirect', { domain: 'libretexts.org' });\n js_cookie__WEBPACK_IMPORTED_MODULE_1__[\"default\"].remove('cas_bridge_source', { domain: 'libretexts.org' });\n js_cookie__WEBPACK_IMPORTED_MODULE_1__[\"default\"].remove(`cas_bridge_gateway_check_${window.location.host}`, { domain: 'libretexts.org' });\n const isStaging = window.location.hostname === 'dev.libretexts.org';\n window.location.href = `https://${isStaging ? 'castest2' : 'auth'}.libretexts.org/cas/logout`;\n};\n\n(async () => {\n if (window.location.href.endsWith('#')) {\n history.replaceState('', document.title, window.location.pathname + window.location.search);\n }\n\n // \n let login = document.getElementById('emailHolder').innerText;\n const $target = $('.mt-user-quick-login');\n if (login) {\n return;\n }\n // \n\n // \n const loginLink = document.getElementById('ssoHolder').innerText;\n const authorizedLibCookie = js_cookie__WEBPACK_IMPORTED_MODULE_1__[\"default\"].get(`cas_bridge_authorized_${window.location.host}`);\n if (authorizedLibCookie && authorizedLibCookie === 'true' && loginLink) {\n window.location.href = loginLink;\n return;\n }\n // \n\n // \n const currentPath = window.location.pathname;\n const gatewayCheckKey = `cas_bridge_gateway_check_${window.location.host}`;\n if (sessionStorage.getItem(gatewayCheckKey) !== '1' && !currentPath.toLowerCase().includes('special:')) {\n sessionStorage.setItem(gatewayCheckKey, '1');\n window.LibreTextsLoginCAS(null, true);\n return;\n }\n // \n\n // \n const casBridgeToken = js_cookie__WEBPACK_IMPORTED_MODULE_1__[\"default\"].get(`cas_bridge_token_${window.location.host}`);\n if (casBridgeToken) {\n const payload = await verifyJWT(casBridgeToken);\n if (payload && payload.sub) {\n login = payload;\n }\n }\n if (login) {\n $target.replaceWith(`
  • ${login.first_name} ${login.last_name}
  • `);\n $('.elm-header-user-nav').addClass('authenticated-sso');\n\n const $nativeSignIn = $('.mt-icon-quick-sign-in');\n $nativeSignIn.hide();\n return;\n }\n //
    \n\n if ($target) {\n $target.before('
  • Sign in
  • ');\n }\n})();\n\n\n//# sourceURL=webpack://@libretexts/casbridge/./index.js?"); /***/ }), diff --git a/public/CASBridge/dist/casbridge.min.js b/public/CASBridge/dist/casbridge.min.js index 4c480193..8066c025 100644 --- a/public/CASBridge/dist/casbridge.min.js +++ b/public/CASBridge/dist/casbridge.min.js @@ -1,2 +1,2 @@ /*! For license information please see casbridge.min.js.LICENSE.txt */ -(()=>{"use strict";const e=new TextEncoder,t=new TextDecoder;const r=e=>{let r=e;r instanceof Uint8Array&&(r=t.decode(r)),r=r.replace(/-/g,"+").replace(/_/g,"/").replace(/\s/g,"");try{return(e=>{const t=atob(e),r=new Uint8Array(t.length);for(let e=0;ee instanceof CryptoKey;y.getRandomValues.bind(y);const m=async e=>{var t,n;if(!e.alg)throw new TypeError('"alg" argument is required when "jwk.alg" is not present');const{algorithm:o,keyUsages:a}=function(e){let t,r;switch(e.kty){case"oct":switch(e.alg){case"HS256":case"HS384":case"HS512":t={name:"HMAC",hash:`SHA-${e.alg.slice(-3)}`},r=["sign","verify"];break;case"A128CBC-HS256":case"A192CBC-HS384":case"A256CBC-HS512":throw new s(`${e.alg} keys cannot be imported as CryptoKey instances`);case"A128GCM":case"A192GCM":case"A256GCM":case"A128GCMKW":case"A192GCMKW":case"A256GCMKW":t={name:"AES-GCM"},r=["encrypt","decrypt"];break;case"A128KW":case"A192KW":case"A256KW":t={name:"AES-KW"},r=["wrapKey","unwrapKey"];break;case"PBES2-HS256+A128KW":case"PBES2-HS384+A192KW":case"PBES2-HS512+A256KW":t={name:"PBKDF2"},r=["deriveBits"];break;default:throw new s('Invalid or unsupported JWK "alg" (Algorithm) Parameter value')}break;case"RSA":switch(e.alg){case"PS256":case"PS384":case"PS512":t={name:"RSA-PSS",hash:`SHA-${e.alg.slice(-3)}`},r=e.d?["sign"]:["verify"];break;case"RS256":case"RS384":case"RS512":t={name:"RSASSA-PKCS1-v1_5",hash:`SHA-${e.alg.slice(-3)}`},r=e.d?["sign"]:["verify"];break;case"RSA-OAEP":case"RSA-OAEP-256":case"RSA-OAEP-384":case"RSA-OAEP-512":t={name:"RSA-OAEP",hash:`SHA-${parseInt(e.alg.slice(-3),10)||1}`},r=e.d?["decrypt","unwrapKey"]:["encrypt","wrapKey"];break;default:throw new s('Invalid or unsupported JWK "alg" (Algorithm) Parameter value')}break;case"EC":switch(e.alg){case"ES256":t={name:"ECDSA",namedCurve:"P-256"},r=e.d?["sign"]:["verify"];break;case"ES384":t={name:"ECDSA",namedCurve:"P-384"},r=e.d?["sign"]:["verify"];break;case"ES512":t={name:"ECDSA",namedCurve:"P-521"},r=e.d?["sign"]:["verify"];break;case"ECDH-ES":case"ECDH-ES+A128KW":case"ECDH-ES+A192KW":case"ECDH-ES+A256KW":t={name:"ECDH",namedCurve:e.crv},r=e.d?["deriveBits"]:[];break;default:throw new s('Invalid or unsupported JWK "alg" (Algorithm) Parameter value')}break;case"OKP":switch(e.alg){case"EdDSA":t={name:e.crv},r=e.d?["sign"]:["verify"];break;case"ECDH-ES":case"ECDH-ES+A128KW":case"ECDH-ES+A192KW":case"ECDH-ES+A256KW":t={name:e.crv},r=e.d?["deriveBits"]:[];break;default:throw new s('Invalid or unsupported JWK "alg" (Algorithm) Parameter value')}break;default:throw new s('Invalid or unsupported JWK "kty" (Key Type) Parameter value')}return{algorithm:t,keyUsages:r}}(e),i=[o,null!==(t=e.ext)&&void 0!==t&&t,null!==(n=e.key_ops)&&void 0!==n?n:a];if("PBKDF2"===o.name)return y.subtle.importKey("raw",r(e.k),...i);const c={...e};return delete c.alg,delete c.use,y.subtle.importKey("jwk",c,...i)};function g(e){if("object"!=typeof(t=e)||null===t||"[object Object]"!==Object.prototype.toString.call(e))return!1;var t;if(null===Object.getPrototypeOf(e))return!0;let r=e;for(;null!==Object.getPrototypeOf(r);)r=Object.getPrototypeOf(r);return Object.getPrototypeOf(e)===r}const S=function(e,t,r,n,o){if(void 0!==o.crit&&void 0===n.crit)throw new e('"crit" (Critical) Header Parameter MUST be integrity protected');if(!n||void 0===n.crit)return new Set;if(!Array.isArray(n.crit)||0===n.crit.length||n.crit.some((e=>"string"!=typeof e||0===e.length)))throw new e('"crit" (Critical) Header Parameter MUST be an array of non-empty strings when present');let a;a=void 0!==r?new Map([...Object.entries(r),...t.entries()]):t;for(const t of n.crit){if(!a.has(t))throw new s(`Extension Header Parameter "${t}" is not recognized`);if(void 0===o[t])throw new e(`Extension Header Parameter "${t}" is missing`);if(a.get(t)&&void 0===n[t])throw new e(`Extension Header Parameter "${t}" MUST be integrity protected`)}return new Set(n.crit)};function b(e,t="algorithm.name"){return new TypeError(`CryptoKey does not support this operation, its ${t} must be ${e}`)}function E(e,t){return e.name===t}function v(e){return parseInt(e.name.slice(4),10)}function A(e,t,...r){switch(t){case"HS256":case"HS384":case"HS512":{if(!E(e.algorithm,"HMAC"))throw b("HMAC");const r=parseInt(t.slice(2),10);if(v(e.algorithm.hash)!==r)throw b(`SHA-${r}`,"algorithm.hash");break}case"RS256":case"RS384":case"RS512":{if(!E(e.algorithm,"RSASSA-PKCS1-v1_5"))throw b("RSASSA-PKCS1-v1_5");const r=parseInt(t.slice(2),10);if(v(e.algorithm.hash)!==r)throw b(`SHA-${r}`,"algorithm.hash");break}case"PS256":case"PS384":case"PS512":{if(!E(e.algorithm,"RSA-PSS"))throw b("RSA-PSS");const r=parseInt(t.slice(2),10);if(v(e.algorithm.hash)!==r)throw b(`SHA-${r}`,"algorithm.hash");break}case"EdDSA":if("Ed25519"!==e.algorithm.name&&"Ed448"!==e.algorithm.name)throw b("Ed25519 or Ed448");break;case"ES256":case"ES384":case"ES512":{if(!E(e.algorithm,"ECDSA"))throw b("ECDSA");const r=function(e){switch(e){case"ES256":return"P-256";case"ES384":return"P-384";case"ES512":return"P-521";default:throw new Error("unreachable")}}(t);if(e.algorithm.namedCurve!==r)throw b(r,"algorithm.namedCurve");break}default:throw new TypeError("CryptoKey does not support this operation")}!function(e,t){if(t.length&&!t.some((t=>e.usages.includes(t)))){let e="CryptoKey does not support this operation, its usages must include ";if(t.length>2){const r=t.pop();e+=`one of ${t.join(", ")}, or ${r}.`}else 2===t.length?e+=`one of ${t[0]} or ${t[1]}.`:e+=`${t[0]}.`;throw new TypeError(e)}}(e,r)}function _(e,t,...r){if(r.length>2){const t=r.pop();e+=`one of type ${r.join(", ")}, or ${t}.`}else 2===r.length?e+=`one of type ${r[0]} or ${r[1]}.`:e+=`of type ${r[0]}.`;return null==t?e+=` Received ${t}`:"function"==typeof t&&t.name?e+=` Received function ${t.name}`:"object"==typeof t&&null!=t&&t.constructor&&t.constructor.name&&(e+=` Received an instance of ${t.constructor.name}`),e}Symbol();const k=(e,...t)=>_("Key must be ",e,...t);function R(e,t,...r){return _(`Key for the ${e} algorithm must be `,t,...r)}const C=e=>w(e),T=["CryptoKey"],P=async(e,t,r,n)=>{const o=await function(e,t,r){if(w(t))return A(t,e,r),t;if(t instanceof Uint8Array){if(!e.startsWith("HS"))throw new TypeError(k(t,...T));return y.subtle.importKey("raw",t,{hash:`SHA-${e.slice(-3)}`,name:"HMAC"},!1,[r])}throw new TypeError(k(t,...T,"Uint8Array"))}(e,t,"verify");((e,t)=>{if(e.startsWith("RS")||e.startsWith("PS")){const{modulusLength:r}=t.algorithm;if("number"!=typeof r||r<2048)throw new TypeError(`${e} requires key modulusLength to be 2048 bits or larger`)}})(e,o);const a=function(e,t){const r=`SHA-${e.slice(-3)}`;switch(e){case"HS256":case"HS384":case"HS512":return{hash:r,name:"HMAC"};case"PS256":case"PS384":case"PS512":return{hash:r,name:"RSA-PSS",saltLength:e.slice(-3)>>3};case"RS256":case"RS384":case"RS512":return{hash:r,name:"RSASSA-PKCS1-v1_5"};case"ES256":case"ES384":case"ES512":return{hash:r,name:"ECDSA",namedCurve:t.namedCurve};case"EdDSA":return{name:t.name};default:throw new s(`alg ${e} is not supported either by JOSE or your javascript runtime`)}}(e,o.algorithm);try{return await y.subtle.verify(a,o,r,n)}catch(e){return!1}},W=(...e)=>{const t=e.filter(Boolean);if(0===t.length||1===t.length)return!0;let r;for(const e of t){const t=Object.keys(e);if(r&&0!==r.size)for(const e of t){if(r.has(e))return!1;r.add(e)}else r=new Set(t)}return!0},K=(e,t,r)=>{e.startsWith("HS")||"dir"===e||e.startsWith("PBES2")||/^A\d{3}(?:GCM)?KW$/.test(e)?((e,t)=>{if(!(t instanceof Uint8Array)){if(!C(t))throw new TypeError(R(e,t,...T,"Uint8Array"));if("secret"!==t.type)throw new TypeError(`${T.join(" or ")} instances for symmetric algorithms must be of type "secret"`)}})(e,t):((e,t,r)=>{if(!C(t))throw new TypeError(R(e,t,...T));if("secret"===t.type)throw new TypeError(`${T.join(" or ")} instances for asymmetric algorithms must not be of type "secret"`);if("sign"===r&&"public"===t.type)throw new TypeError(`${T.join(" or ")} instances for asymmetric algorithm signing must be of type "private"`);if("decrypt"===r&&"public"===t.type)throw new TypeError(`${T.join(" or ")} instances for asymmetric algorithm decryption must be of type "private"`);if(t.algorithm&&"verify"===r&&"private"===t.type)throw new TypeError(`${T.join(" or ")} instances for asymmetric algorithm verifying must be of type "public"`);if(t.algorithm&&"encrypt"===r&&"private"===t.type)throw new TypeError(`${T.join(" or ")} instances for asymmetric algorithm encryption must be of type "public"`)})(e,t,r)},x=(e,t)=>{if(void 0!==t&&(!Array.isArray(t)||t.some((e=>"string"!=typeof e))))throw new TypeError(`"${e}" option must be an array of strings`);if(t)return new Set(t)};async function H(n,o,a){if(n instanceof Uint8Array&&(n=t.decode(n)),"string"!=typeof n)throw new c("Compact JWS must be a string or Uint8Array");const{0:s,1:d,2:u,length:l}=n.split(".");if(3!==l)throw new c("Invalid Compact JWS");const h=await async function(n,o,a){var s;if(!g(n))throw new c("Flattened JWS must be an object");if(void 0===n.protected&&void 0===n.header)throw new c('Flattened JWS must have either of the "protected" or "header" members');if(void 0!==n.protected&&"string"!=typeof n.protected)throw new c("JWS Protected Header incorrect type");if(void 0===n.payload)throw new c("JWS Payload missing");if("string"!=typeof n.signature)throw new c("JWS Signature missing or incorrect type");if(void 0!==n.header&&!g(n.header))throw new c("JWS Unprotected Header incorrect type");let d={};if(n.protected)try{const e=r(n.protected);d=JSON.parse(t.decode(e))}catch(e){throw new c("JWS Protected Header is invalid")}if(!W(d,n.header))throw new c("JWS Protected and JWS Unprotected Header Parameter names must be disjoint");const u={...d,...n.header};let l=!0;if(S(c,new Map([["b64",!0]]),null==a?void 0:a.crit,d,u).has("b64")&&(l=d.b64,"boolean"!=typeof l))throw new c('The "b64" (base64url-encode payload) Header Parameter must be a boolean');const{alg:h}=u;if("string"!=typeof h||!h)throw new c('JWS "alg" (Algorithm) Header Parameter missing or invalid');const p=a&&x("algorithms",a.algorithms);if(p&&!p.has(h))throw new i('"alg" (Algorithm) Header Parameter not allowed');if(l){if("string"!=typeof n.payload)throw new c("JWS Payload must be a string")}else if("string"!=typeof n.payload&&!(n.payload instanceof Uint8Array))throw new c("JWS Payload must be a string or an Uint8Array instance");let y=!1;"function"==typeof o&&(o=await o(d,n),y=!0),K(h,o,"verify");const w=function(...e){const t=e.reduce(((e,{length:t})=>e+t),0),r=new Uint8Array(t);let n=0;return e.forEach((e=>{r.set(e,n),n+=e.length})),r}(e.encode(null!==(s=n.protected)&&void 0!==s?s:""),e.encode("."),"string"==typeof n.payload?e.encode(n.payload):n.payload),m=r(n.signature);if(!await P(h,o,m,w))throw new f;let b;b=l?r(n.payload):"string"==typeof n.payload?e.encode(n.payload):n.payload;const E={payload:b};return void 0!==n.protected&&(E.protectedHeader=d),void 0!==n.header&&(E.unprotectedHeader=n.header),y?{...E,key:o}:E}({payload:d,protected:s,signature:u},o,a),p={payload:h.payload,protectedHeader:h.protectedHeader};return"function"==typeof o?{...p,key:h.key}:p}const I=/^(\d+|\d+\.\d+) ?(seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)$/i,O=e=>{const t=I.exec(e);if(!t)throw new TypeError("Invalid time period format");const r=parseFloat(t[1]);switch(t[2].toLowerCase()){case"sec":case"secs":case"second":case"seconds":case"s":return Math.round(r);case"minute":case"minutes":case"min":case"mins":case"m":return Math.round(60*r);case"hour":case"hours":case"hr":case"hrs":case"h":return Math.round(3600*r);case"day":case"days":case"d":return Math.round(86400*r);case"week":case"weeks":case"w":return Math.round(604800*r);default:return Math.round(31557600*r)}},J=e=>e.toLowerCase().replace(/^application\//,""),D=(e,r,n={})=>{const{typ:i}=n;if(i&&("string"!=typeof e.typ||J(e.typ)!==J(i)))throw new o('unexpected "typ" JWT header value',"typ","check_failed");let s;try{s=JSON.parse(t.decode(r))}catch(e){}if(!g(s))throw new d("JWT Claims Set must be a top-level JSON object");const{requiredClaims:c=[],issuer:u,subject:l,audience:h,maxTokenAge:p}=n;void 0!==p&&c.push("iat"),void 0!==h&&c.push("aud"),void 0!==l&&c.push("sub"),void 0!==u&&c.push("iss");for(const e of new Set(c.reverse()))if(!(e in s))throw new o(`missing required "${e}" claim`,e,"missing");if(u&&!(Array.isArray(u)?u:[u]).includes(s.iss))throw new o('unexpected "iss" claim value',"iss","check_failed");if(l&&s.sub!==l)throw new o('unexpected "sub" claim value',"sub","check_failed");if(h&&(y="string"==typeof h?[h]:h,!("string"==typeof(f=s.aud)?y.includes(f):Array.isArray(f)&&y.some(Set.prototype.has.bind(new Set(f))))))throw new o('unexpected "aud" claim value',"aud","check_failed");var f,y;let w;switch(typeof n.clockTolerance){case"string":w=O(n.clockTolerance);break;case"number":w=n.clockTolerance;break;case"undefined":w=0;break;default:throw new TypeError("Invalid clockTolerance option type")}const{currentDate:m}=n,S=(b=m||new Date,Math.floor(b.getTime()/1e3));var b;if((void 0!==s.iat||p)&&"number"!=typeof s.iat)throw new o('"iat" claim must be a number',"iat","invalid");if(void 0!==s.nbf){if("number"!=typeof s.nbf)throw new o('"nbf" claim must be a number',"nbf","invalid");if(s.nbf>S+w)throw new o('"nbf" claim timestamp check failed',"nbf","check_failed")}if(void 0!==s.exp){if("number"!=typeof s.exp)throw new o('"exp" claim must be a number',"exp","invalid");if(s.exp<=S-w)throw new a('"exp" claim timestamp check failed',"exp","check_failed")}if(p){const e=S-s.iat;if(e-w>("number"==typeof p?p:O(p)))throw new a('"iat" claim timestamp check failed (too far in the past)',"iat","check_failed");if(e<0-w)throw new o('"iat" claim timestamp check failed (it should be in the past)',"iat","check_failed")}return s};function L(e){return e&&"object"==typeof e&&Array.isArray(e.keys)&&e.keys.every(j)}function j(e){return g(e)}class M{constructor(e){if(this._cached=new WeakMap,!L(e))throw new u("JSON Web Key Set malformed");var t;this._jwks=(t=e,"function"==typeof structuredClone?structuredClone(t):JSON.parse(JSON.stringify(t)))}async getKey(e,t){const{alg:r,kid:n}={...e,...null==t?void 0:t.header},o=function(e){switch("string"==typeof e&&e.slice(0,2)){case"RS":case"PS":return"RSA";case"ES":return"EC";case"Ed":return"OKP";default:throw new s('Unsupported "alg" value for a JSON Web Key Set')}}(r),a=this._jwks.keys.filter((e=>{let t=o===e.kty;if(t&&"string"==typeof n&&(t=n===e.kid),t&&"string"==typeof e.alg&&(t=r===e.alg),t&&"string"==typeof e.use&&(t="sig"===e.use),t&&Array.isArray(e.key_ops)&&(t=e.key_ops.includes("verify")),t&&"EdDSA"===r&&(t="Ed25519"===e.crv||"Ed448"===e.crv),t)switch(r){case"ES256":t="P-256"===e.crv;break;case"ES256K":t="secp256k1"===e.crv;break;case"ES384":t="P-384"===e.crv;break;case"ES512":t="P-521"===e.crv}return t})),{0:i,length:c}=a;if(0===c)throw new l;if(1!==c){const e=new h,{_cached:t}=this;throw e[Symbol.asyncIterator]=async function*(){for(const e of a)try{yield await U(t,e,r)}catch(e){continue}},e}return U(this._cached,i,r)}}async function U(e,t,n){const o=e.get(t)||e.set(t,{}).get(t);if(void 0===o[n]){const e=await async function(e,t,n){var o;if(!g(e))throw new TypeError("JWK must be an object");switch(t||(t=e.alg),e.kty){case"oct":if("string"!=typeof e.k||!e.k)throw new TypeError('missing "k" (Key Value) Parameter value');return null!=n||(n=!0!==e.ext),n?m({...e,alg:t,ext:null!==(o=e.ext)&&void 0!==o&&o}):r(e.k);case"RSA":if(void 0!==e.oth)throw new s('RSA JWK "oth" (Other Primes Info) Parameter value is not supported');case"EC":case"OKP":return m({...e,alg:t});default:throw new s('Unsupported "kty" (Key Type) Parameter value')}}({...t,ext:!0},n);if(e instanceof Uint8Array||"public"!==e.type)throw new u("JSON Web Key Set members must be public keys");o[n]=e}return o[n]}class N extends M{constructor(e,t){if(super({keys:[]}),this._jwks=void 0,!(e instanceof URL))throw new TypeError("url must be an instance of URL");this._url=new URL(e.href),this._options={agent:null==t?void 0:t.agent,headers:null==t?void 0:t.headers},this._timeoutDuration="number"==typeof(null==t?void 0:t.timeoutDuration)?null==t?void 0:t.timeoutDuration:5e3,this._cooldownDuration="number"==typeof(null==t?void 0:t.cooldownDuration)?null==t?void 0:t.cooldownDuration:3e4,this._cacheMaxAge="number"==typeof(null==t?void 0:t.cacheMaxAge)?null==t?void 0:t.cacheMaxAge:6e5}coolingDown(){return"number"==typeof this._jwksTimestamp&&Date.now(){let o,a,i=!1;"function"==typeof AbortController&&(o=new AbortController,a=setTimeout((()=>{i=!0,o.abort()}),t));const s=await fetch(e.href,{signal:o?o.signal:void 0,redirect:"manual",headers:r.headers}).catch((e=>{if(i)throw new p;throw e}));if(void 0!==a&&clearTimeout(a),200!==s.status)throw new n("Expected 200 OK from the JSON Web Key Set HTTP response");try{return await s.json()}catch(e){throw new n("Failed to parse the JSON Web Key Set HTTP response as JSON")}})(this._url,this._timeoutDuration,this._options).then((e=>{if(!L(e))throw new u("JSON Web Key Set malformed");this._jwks={keys:e.keys},this._jwksTimestamp=Date.now(),this._pendingFetch=void 0})).catch((e=>{throw this._pendingFetch=void 0,e}))),await this._pendingFetch}}function F(e){for(var t=1;t{e.preventDefault(),B.set("cas_bridge_source",window.location.host,{domain:"libretexts.org",sameSite:"lax"}),B.set("cas_bridge_redirect",window.location.href,{domain:"libretexts.org",sameSite:"lax"}),window.location.href="https://one.libretexts.org/api/v1/auth/cas-bridge"},window.LibreTextsLogoutCAS=e=>{e.preventDefault(),B.remove(`cas_bridge_token_${window.location.host}`,{domain:"libretexts.org"}),B.remove("cas_bridge_redirect",{domain:"libretexts.org"}),B.remove("cas_bridge_source",{domain:"libretexts.org"}),window.location.href="https://auth.libretexts.org/cas/logout"},window.addEventListener("DOMContentLoaded",(async()=>{window.location.href.endsWith("#")&&history.replaceState("",document.title,window.location.pathname+window.location.search);let e=document.getElementById("emailHolder").innerText;const t=$(".mt-user-quick-login");if(e)return;const r=document.getElementById("ssoHolder").innerText,n=B.get(`cas_bridge_authorized_${window.location.host}`);n&&"true"===n&&r&&(window.location.href=r);const o=B.get(`cas_bridge_token_${window.location.host}`);if(o){const t=await G(o);t&&t.sub&&(e=t)}e?(t.replaceWith(`
  • ${e.first_name} ${e.last_name}
  • `),$(".elm-header-user-nav").addClass("authenticated-sso"),$(".mt-icon-quick-sign-in").hide()):t&&t.before('
  • ')}))})(); \ No newline at end of file +(()=>{"use strict";const e=new TextEncoder,t=new TextDecoder;const r=e=>{let r=e;r instanceof Uint8Array&&(r=t.decode(r)),r=r.replace(/-/g,"+").replace(/_/g,"/").replace(/\s/g,"");try{return(e=>{const t=atob(e),r=new Uint8Array(t.length);for(let e=0;ee instanceof CryptoKey;w.getRandomValues.bind(w);const m=async e=>{var t,o;if(!e.alg)throw new TypeError('"alg" argument is required when "jwk.alg" is not present');const{algorithm:n,keyUsages:a}=function(e){let t,r;switch(e.kty){case"oct":switch(e.alg){case"HS256":case"HS384":case"HS512":t={name:"HMAC",hash:`SHA-${e.alg.slice(-3)}`},r=["sign","verify"];break;case"A128CBC-HS256":case"A192CBC-HS384":case"A256CBC-HS512":throw new s(`${e.alg} keys cannot be imported as CryptoKey instances`);case"A128GCM":case"A192GCM":case"A256GCM":case"A128GCMKW":case"A192GCMKW":case"A256GCMKW":t={name:"AES-GCM"},r=["encrypt","decrypt"];break;case"A128KW":case"A192KW":case"A256KW":t={name:"AES-KW"},r=["wrapKey","unwrapKey"];break;case"PBES2-HS256+A128KW":case"PBES2-HS384+A192KW":case"PBES2-HS512+A256KW":t={name:"PBKDF2"},r=["deriveBits"];break;default:throw new s('Invalid or unsupported JWK "alg" (Algorithm) Parameter value')}break;case"RSA":switch(e.alg){case"PS256":case"PS384":case"PS512":t={name:"RSA-PSS",hash:`SHA-${e.alg.slice(-3)}`},r=e.d?["sign"]:["verify"];break;case"RS256":case"RS384":case"RS512":t={name:"RSASSA-PKCS1-v1_5",hash:`SHA-${e.alg.slice(-3)}`},r=e.d?["sign"]:["verify"];break;case"RSA-OAEP":case"RSA-OAEP-256":case"RSA-OAEP-384":case"RSA-OAEP-512":t={name:"RSA-OAEP",hash:`SHA-${parseInt(e.alg.slice(-3),10)||1}`},r=e.d?["decrypt","unwrapKey"]:["encrypt","wrapKey"];break;default:throw new s('Invalid or unsupported JWK "alg" (Algorithm) Parameter value')}break;case"EC":switch(e.alg){case"ES256":t={name:"ECDSA",namedCurve:"P-256"},r=e.d?["sign"]:["verify"];break;case"ES384":t={name:"ECDSA",namedCurve:"P-384"},r=e.d?["sign"]:["verify"];break;case"ES512":t={name:"ECDSA",namedCurve:"P-521"},r=e.d?["sign"]:["verify"];break;case"ECDH-ES":case"ECDH-ES+A128KW":case"ECDH-ES+A192KW":case"ECDH-ES+A256KW":t={name:"ECDH",namedCurve:e.crv},r=e.d?["deriveBits"]:[];break;default:throw new s('Invalid or unsupported JWK "alg" (Algorithm) Parameter value')}break;case"OKP":switch(e.alg){case"EdDSA":t={name:e.crv},r=e.d?["sign"]:["verify"];break;case"ECDH-ES":case"ECDH-ES+A128KW":case"ECDH-ES+A192KW":case"ECDH-ES+A256KW":t={name:e.crv},r=e.d?["deriveBits"]:[];break;default:throw new s('Invalid or unsupported JWK "alg" (Algorithm) Parameter value')}break;default:throw new s('Invalid or unsupported JWK "kty" (Key Type) Parameter value')}return{algorithm:t,keyUsages:r}}(e),i=[n,null!==(t=e.ext)&&void 0!==t&&t,null!==(o=e.key_ops)&&void 0!==o?o:a];if("PBKDF2"===n.name)return w.subtle.importKey("raw",r(e.k),...i);const c={...e};return delete c.alg,delete c.use,w.subtle.importKey("jwk",c,...i)};function g(e){if("object"!=typeof(t=e)||null===t||"[object Object]"!==Object.prototype.toString.call(e))return!1;var t;if(null===Object.getPrototypeOf(e))return!0;let r=e;for(;null!==Object.getPrototypeOf(r);)r=Object.getPrototypeOf(r);return Object.getPrototypeOf(e)===r}const S=function(e,t,r,o,n){if(void 0!==n.crit&&void 0===o.crit)throw new e('"crit" (Critical) Header Parameter MUST be integrity protected');if(!o||void 0===o.crit)return new Set;if(!Array.isArray(o.crit)||0===o.crit.length||o.crit.some((e=>"string"!=typeof e||0===e.length)))throw new e('"crit" (Critical) Header Parameter MUST be an array of non-empty strings when present');let a;a=void 0!==r?new Map([...Object.entries(r),...t.entries()]):t;for(const t of o.crit){if(!a.has(t))throw new s(`Extension Header Parameter "${t}" is not recognized`);if(void 0===n[t])throw new e(`Extension Header Parameter "${t}" is missing`);if(a.get(t)&&void 0===o[t])throw new e(`Extension Header Parameter "${t}" MUST be integrity protected`)}return new Set(o.crit)};function b(e,t="algorithm.name"){return new TypeError(`CryptoKey does not support this operation, its ${t} must be ${e}`)}function v(e,t){return e.name===t}function E(e){return parseInt(e.name.slice(4),10)}function A(e,t,...r){switch(t){case"HS256":case"HS384":case"HS512":{if(!v(e.algorithm,"HMAC"))throw b("HMAC");const r=parseInt(t.slice(2),10);if(E(e.algorithm.hash)!==r)throw b(`SHA-${r}`,"algorithm.hash");break}case"RS256":case"RS384":case"RS512":{if(!v(e.algorithm,"RSASSA-PKCS1-v1_5"))throw b("RSASSA-PKCS1-v1_5");const r=parseInt(t.slice(2),10);if(E(e.algorithm.hash)!==r)throw b(`SHA-${r}`,"algorithm.hash");break}case"PS256":case"PS384":case"PS512":{if(!v(e.algorithm,"RSA-PSS"))throw b("RSA-PSS");const r=parseInt(t.slice(2),10);if(E(e.algorithm.hash)!==r)throw b(`SHA-${r}`,"algorithm.hash");break}case"EdDSA":if("Ed25519"!==e.algorithm.name&&"Ed448"!==e.algorithm.name)throw b("Ed25519 or Ed448");break;case"ES256":case"ES384":case"ES512":{if(!v(e.algorithm,"ECDSA"))throw b("ECDSA");const r=function(e){switch(e){case"ES256":return"P-256";case"ES384":return"P-384";case"ES512":return"P-521";default:throw new Error("unreachable")}}(t);if(e.algorithm.namedCurve!==r)throw b(r,"algorithm.namedCurve");break}default:throw new TypeError("CryptoKey does not support this operation")}!function(e,t){if(t.length&&!t.some((t=>e.usages.includes(t)))){let e="CryptoKey does not support this operation, its usages must include ";if(t.length>2){const r=t.pop();e+=`one of ${t.join(", ")}, or ${r}.`}else 2===t.length?e+=`one of ${t[0]} or ${t[1]}.`:e+=`${t[0]}.`;throw new TypeError(e)}}(e,r)}function _(e,t,...r){if(r.length>2){const t=r.pop();e+=`one of type ${r.join(", ")}, or ${t}.`}else 2===r.length?e+=`one of type ${r[0]} or ${r[1]}.`:e+=`of type ${r[0]}.`;return null==t?e+=` Received ${t}`:"function"==typeof t&&t.name?e+=` Received function ${t.name}`:"object"==typeof t&&null!=t&&t.constructor&&t.constructor.name&&(e+=` Received an instance of ${t.constructor.name}`),e}Symbol();const k=(e,...t)=>_("Key must be ",e,...t);function R(e,t,...r){return _(`Key for the ${e} algorithm must be `,t,...r)}const C=e=>y(e),T=["CryptoKey"],P=async(e,t,r,o)=>{const n=await function(e,t,r){if(y(t))return A(t,e,r),t;if(t instanceof Uint8Array){if(!e.startsWith("HS"))throw new TypeError(k(t,...T));return w.subtle.importKey("raw",t,{hash:`SHA-${e.slice(-3)}`,name:"HMAC"},!1,[r])}throw new TypeError(k(t,...T,"Uint8Array"))}(e,t,"verify");((e,t)=>{if(e.startsWith("RS")||e.startsWith("PS")){const{modulusLength:r}=t.algorithm;if("number"!=typeof r||r<2048)throw new TypeError(`${e} requires key modulusLength to be 2048 bits or larger`)}})(e,n);const a=function(e,t){const r=`SHA-${e.slice(-3)}`;switch(e){case"HS256":case"HS384":case"HS512":return{hash:r,name:"HMAC"};case"PS256":case"PS384":case"PS512":return{hash:r,name:"RSA-PSS",saltLength:e.slice(-3)>>3};case"RS256":case"RS384":case"RS512":return{hash:r,name:"RSASSA-PKCS1-v1_5"};case"ES256":case"ES384":case"ES512":return{hash:r,name:"ECDSA",namedCurve:t.namedCurve};case"EdDSA":return{name:t.name};default:throw new s(`alg ${e} is not supported either by JOSE or your javascript runtime`)}}(e,n.algorithm);try{return await w.subtle.verify(a,n,r,o)}catch(e){return!1}},W=(...e)=>{const t=e.filter(Boolean);if(0===t.length||1===t.length)return!0;let r;for(const e of t){const t=Object.keys(e);if(r&&0!==r.size)for(const e of t){if(r.has(e))return!1;r.add(e)}else r=new Set(t)}return!0},K=(e,t,r)=>{e.startsWith("HS")||"dir"===e||e.startsWith("PBES2")||/^A\d{3}(?:GCM)?KW$/.test(e)?((e,t)=>{if(!(t instanceof Uint8Array)){if(!C(t))throw new TypeError(R(e,t,...T,"Uint8Array"));if("secret"!==t.type)throw new TypeError(`${T.join(" or ")} instances for symmetric algorithms must be of type "secret"`)}})(e,t):((e,t,r)=>{if(!C(t))throw new TypeError(R(e,t,...T));if("secret"===t.type)throw new TypeError(`${T.join(" or ")} instances for asymmetric algorithms must not be of type "secret"`);if("sign"===r&&"public"===t.type)throw new TypeError(`${T.join(" or ")} instances for asymmetric algorithm signing must be of type "private"`);if("decrypt"===r&&"public"===t.type)throw new TypeError(`${T.join(" or ")} instances for asymmetric algorithm decryption must be of type "private"`);if(t.algorithm&&"verify"===r&&"private"===t.type)throw new TypeError(`${T.join(" or ")} instances for asymmetric algorithm verifying must be of type "public"`);if(t.algorithm&&"encrypt"===r&&"private"===t.type)throw new TypeError(`${T.join(" or ")} instances for asymmetric algorithm encryption must be of type "public"`)})(e,t,r)},x=(e,t)=>{if(void 0!==t&&(!Array.isArray(t)||t.some((e=>"string"!=typeof e))))throw new TypeError(`"${e}" option must be an array of strings`);if(t)return new Set(t)};async function H(o,n,a){if(o instanceof Uint8Array&&(o=t.decode(o)),"string"!=typeof o)throw new c("Compact JWS must be a string or Uint8Array");const{0:s,1:d,2:u,length:l}=o.split(".");if(3!==l)throw new c("Invalid Compact JWS");const h=await async function(o,n,a){var s;if(!g(o))throw new c("Flattened JWS must be an object");if(void 0===o.protected&&void 0===o.header)throw new c('Flattened JWS must have either of the "protected" or "header" members');if(void 0!==o.protected&&"string"!=typeof o.protected)throw new c("JWS Protected Header incorrect type");if(void 0===o.payload)throw new c("JWS Payload missing");if("string"!=typeof o.signature)throw new c("JWS Signature missing or incorrect type");if(void 0!==o.header&&!g(o.header))throw new c("JWS Unprotected Header incorrect type");let d={};if(o.protected)try{const e=r(o.protected);d=JSON.parse(t.decode(e))}catch(e){throw new c("JWS Protected Header is invalid")}if(!W(d,o.header))throw new c("JWS Protected and JWS Unprotected Header Parameter names must be disjoint");const u={...d,...o.header};let l=!0;if(S(c,new Map([["b64",!0]]),null==a?void 0:a.crit,d,u).has("b64")&&(l=d.b64,"boolean"!=typeof l))throw new c('The "b64" (base64url-encode payload) Header Parameter must be a boolean');const{alg:h}=u;if("string"!=typeof h||!h)throw new c('JWS "alg" (Algorithm) Header Parameter missing or invalid');const p=a&&x("algorithms",a.algorithms);if(p&&!p.has(h))throw new i('"alg" (Algorithm) Header Parameter not allowed');if(l){if("string"!=typeof o.payload)throw new c("JWS Payload must be a string")}else if("string"!=typeof o.payload&&!(o.payload instanceof Uint8Array))throw new c("JWS Payload must be a string or an Uint8Array instance");let w=!1;"function"==typeof n&&(n=await n(d,o),w=!0),K(h,n,"verify");const y=function(...e){const t=e.reduce(((e,{length:t})=>e+t),0),r=new Uint8Array(t);let o=0;return e.forEach((e=>{r.set(e,o),o+=e.length})),r}(e.encode(null!==(s=o.protected)&&void 0!==s?s:""),e.encode("."),"string"==typeof o.payload?e.encode(o.payload):o.payload),m=r(o.signature);if(!await P(h,n,m,y))throw new f;let b;b=l?r(o.payload):"string"==typeof o.payload?e.encode(o.payload):o.payload;const v={payload:b};return void 0!==o.protected&&(v.protectedHeader=d),void 0!==o.header&&(v.unprotectedHeader=o.header),w?{...v,key:n}:v}({payload:d,protected:s,signature:u},n,a),p={payload:h.payload,protectedHeader:h.protectedHeader};return"function"==typeof n?{...p,key:h.key}:p}const I=/^(\d+|\d+\.\d+) ?(seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)$/i,J=e=>{const t=I.exec(e);if(!t)throw new TypeError("Invalid time period format");const r=parseFloat(t[1]);switch(t[2].toLowerCase()){case"sec":case"secs":case"second":case"seconds":case"s":return Math.round(r);case"minute":case"minutes":case"min":case"mins":case"m":return Math.round(60*r);case"hour":case"hours":case"hr":case"hrs":case"h":return Math.round(3600*r);case"day":case"days":case"d":return Math.round(86400*r);case"week":case"weeks":case"w":return Math.round(604800*r);default:return Math.round(31557600*r)}},O=e=>e.toLowerCase().replace(/^application\//,""),D=(e,r,o={})=>{const{typ:i}=o;if(i&&("string"!=typeof e.typ||O(e.typ)!==O(i)))throw new n('unexpected "typ" JWT header value',"typ","check_failed");let s;try{s=JSON.parse(t.decode(r))}catch(e){}if(!g(s))throw new d("JWT Claims Set must be a top-level JSON object");const{requiredClaims:c=[],issuer:u,subject:l,audience:h,maxTokenAge:p}=o;void 0!==p&&c.push("iat"),void 0!==h&&c.push("aud"),void 0!==l&&c.push("sub"),void 0!==u&&c.push("iss");for(const e of new Set(c.reverse()))if(!(e in s))throw new n(`missing required "${e}" claim`,e,"missing");if(u&&!(Array.isArray(u)?u:[u]).includes(s.iss))throw new n('unexpected "iss" claim value',"iss","check_failed");if(l&&s.sub!==l)throw new n('unexpected "sub" claim value',"sub","check_failed");if(h&&(w="string"==typeof h?[h]:h,!("string"==typeof(f=s.aud)?w.includes(f):Array.isArray(f)&&w.some(Set.prototype.has.bind(new Set(f))))))throw new n('unexpected "aud" claim value',"aud","check_failed");var f,w;let y;switch(typeof o.clockTolerance){case"string":y=J(o.clockTolerance);break;case"number":y=o.clockTolerance;break;case"undefined":y=0;break;default:throw new TypeError("Invalid clockTolerance option type")}const{currentDate:m}=o,S=(b=m||new Date,Math.floor(b.getTime()/1e3));var b;if((void 0!==s.iat||p)&&"number"!=typeof s.iat)throw new n('"iat" claim must be a number',"iat","invalid");if(void 0!==s.nbf){if("number"!=typeof s.nbf)throw new n('"nbf" claim must be a number',"nbf","invalid");if(s.nbf>S+y)throw new n('"nbf" claim timestamp check failed',"nbf","check_failed")}if(void 0!==s.exp){if("number"!=typeof s.exp)throw new n('"exp" claim must be a number',"exp","invalid");if(s.exp<=S-y)throw new a('"exp" claim timestamp check failed',"exp","check_failed")}if(p){const e=S-s.iat;if(e-y>("number"==typeof p?p:J(p)))throw new a('"iat" claim timestamp check failed (too far in the past)',"iat","check_failed");if(e<0-y)throw new n('"iat" claim timestamp check failed (it should be in the past)',"iat","check_failed")}return s};function L(e){return e&&"object"==typeof e&&Array.isArray(e.keys)&&e.keys.every(j)}function j(e){return g(e)}class M{constructor(e){if(this._cached=new WeakMap,!L(e))throw new u("JSON Web Key Set malformed");var t;this._jwks=(t=e,"function"==typeof structuredClone?structuredClone(t):JSON.parse(JSON.stringify(t)))}async getKey(e,t){const{alg:r,kid:o}={...e,...null==t?void 0:t.header},n=function(e){switch("string"==typeof e&&e.slice(0,2)){case"RS":case"PS":return"RSA";case"ES":return"EC";case"Ed":return"OKP";default:throw new s('Unsupported "alg" value for a JSON Web Key Set')}}(r),a=this._jwks.keys.filter((e=>{let t=n===e.kty;if(t&&"string"==typeof o&&(t=o===e.kid),t&&"string"==typeof e.alg&&(t=r===e.alg),t&&"string"==typeof e.use&&(t="sig"===e.use),t&&Array.isArray(e.key_ops)&&(t=e.key_ops.includes("verify")),t&&"EdDSA"===r&&(t="Ed25519"===e.crv||"Ed448"===e.crv),t)switch(r){case"ES256":t="P-256"===e.crv;break;case"ES256K":t="secp256k1"===e.crv;break;case"ES384":t="P-384"===e.crv;break;case"ES512":t="P-521"===e.crv}return t})),{0:i,length:c}=a;if(0===c)throw new l;if(1!==c){const e=new h,{_cached:t}=this;throw e[Symbol.asyncIterator]=async function*(){for(const e of a)try{yield await U(t,e,r)}catch(e){continue}},e}return U(this._cached,i,r)}}async function U(e,t,o){const n=e.get(t)||e.set(t,{}).get(t);if(void 0===n[o]){const e=await async function(e,t,o){var n;if(!g(e))throw new TypeError("JWK must be an object");switch(t||(t=e.alg),e.kty){case"oct":if("string"!=typeof e.k||!e.k)throw new TypeError('missing "k" (Key Value) Parameter value');return null!=o||(o=!0!==e.ext),o?m({...e,alg:t,ext:null!==(n=e.ext)&&void 0!==n&&n}):r(e.k);case"RSA":if(void 0!==e.oth)throw new s('RSA JWK "oth" (Other Primes Info) Parameter value is not supported');case"EC":case"OKP":return m({...e,alg:t});default:throw new s('Unsupported "kty" (Key Type) Parameter value')}}({...t,ext:!0},o);if(e instanceof Uint8Array||"public"!==e.type)throw new u("JSON Web Key Set members must be public keys");n[o]=e}return n[o]}class N extends M{constructor(e,t){if(super({keys:[]}),this._jwks=void 0,!(e instanceof URL))throw new TypeError("url must be an instance of URL");this._url=new URL(e.href),this._options={agent:null==t?void 0:t.agent,headers:null==t?void 0:t.headers},this._timeoutDuration="number"==typeof(null==t?void 0:t.timeoutDuration)?null==t?void 0:t.timeoutDuration:5e3,this._cooldownDuration="number"==typeof(null==t?void 0:t.cooldownDuration)?null==t?void 0:t.cooldownDuration:3e4,this._cacheMaxAge="number"==typeof(null==t?void 0:t.cacheMaxAge)?null==t?void 0:t.cacheMaxAge:6e5}coolingDown(){return"number"==typeof this._jwksTimestamp&&Date.now(){let n,a,i=!1;"function"==typeof AbortController&&(n=new AbortController,a=setTimeout((()=>{i=!0,n.abort()}),t));const s=await fetch(e.href,{signal:n?n.signal:void 0,redirect:"manual",headers:r.headers}).catch((e=>{if(i)throw new p;throw e}));if(void 0!==a&&clearTimeout(a),200!==s.status)throw new o("Expected 200 OK from the JSON Web Key Set HTTP response");try{return await s.json()}catch(e){throw new o("Failed to parse the JSON Web Key Set HTTP response as JSON")}})(this._url,this._timeoutDuration,this._options).then((e=>{if(!L(e))throw new u("JSON Web Key Set malformed");this._jwks={keys:e.keys},this._jwksTimestamp=Date.now(),this._pendingFetch=void 0})).catch((e=>{throw this._pendingFetch=void 0,e}))),await this._pendingFetch}}function F(e){for(var t=1;t{e?.preventDefault(),B.set("cas_bridge_source",window.location.host,{domain:"libretexts.org",sameSite:"lax"}),B.set("cas_bridge_redirect",window.location.href,{domain:"libretexts.org",sameSite:"lax"});const r="dev.libretexts.org"===window.location.hostname;window.location.href=`https://${r?"staging.":""}one.libretexts.org/api/v1/auth/cas-bridge${t?"?gateway=true":""}`},window.LibreTextsLogoutCAS=e=>{e.preventDefault(),B.remove(`cas_bridge_token_${window.location.host}`,{domain:"libretexts.org"}),B.remove("cas_bridge_redirect",{domain:"libretexts.org"}),B.remove("cas_bridge_source",{domain:"libretexts.org"}),B.remove(`cas_bridge_gateway_check_${window.location.host}`,{domain:"libretexts.org"});const t="dev.libretexts.org"===window.location.hostname;window.location.href=`https://${t?"castest2":"auth"}.libretexts.org/cas/logout`},(async()=>{window.location.href.endsWith("#")&&history.replaceState("",document.title,window.location.pathname+window.location.search);let e=document.getElementById("emailHolder").innerText;const t=$(".mt-user-quick-login");if(e)return;const r=document.getElementById("ssoHolder").innerText,o=B.get(`cas_bridge_authorized_${window.location.host}`);if(o&&"true"===o&&r)return void(window.location.href=r);const n=window.location.pathname,a=`cas_bridge_gateway_check_${window.location.host}`;if("1"!==sessionStorage.getItem(a)&&!n.toLowerCase().includes("special:"))return sessionStorage.setItem(a,"1"),void window.LibreTextsLoginCAS(null,!0);const i=B.get(`cas_bridge_token_${window.location.host}`);if(i){const t=await G(i);t&&t.sub&&(e=t)}e?(t.replaceWith(`
  • ${e.first_name} ${e.last_name}
  • `),$(".elm-header-user-nav").addClass("authenticated-sso"),$(".mt-icon-quick-sign-in").hide()):t&&t.before('
  • ')})()})(); \ No newline at end of file diff --git a/public/CASBridge/index.js b/public/CASBridge/index.js index d7dd0da8..8fb646ba 100644 --- a/public/CASBridge/index.js +++ b/public/CASBridge/index.js @@ -1,77 +1,95 @@ import { createRemoteJWKSet, jwtVerify } from 'jose'; import { default as cookieJar } from 'js-cookie'; +/** + * @param token - token to verify using JWKS + */ async function verifyJWT(token) { - try { - const jwks = await createRemoteJWKSet(new URL('https://one.libretexts.org/api/v1/auth/cas-bridge/jwks')); - const { payload } = await jwtVerify(token, jwks, { - issuer: 'https://one.libretexts.org', - audience: 'libretexts.org', - }); - return payload; - } catch (e) { - console.error(e); - return null; - } + try { + const isStaging = window.location.hostname === 'dev.libretexts.org'; + const jwks = await createRemoteJWKSet(new URL(`https://${isStaging ? 'staging.' : ''}one.libretexts.org/api/v1/auth/cas-bridge/jwks`)); + const { payload } = await jwtVerify(token, jwks, { + issuer: `https://${isStaging ? 'staging.' : ''}one.libretexts.org`, + audience: 'libretexts.org', + }); + return payload; + } catch (e) { + console.error(e); + return null; + } } window.LibreTextsCASBridgeVerifyJWT = verifyJWT; -window.LibreTextsLoginCAS = (event) => { - event.preventDefault(); - cookieJar.set('cas_bridge_source', window.location.host, { domain: 'libretexts.org', sameSite: 'lax' }); - cookieJar.set('cas_bridge_redirect', window.location.href, { domain: 'libretexts.org', sameSite: 'lax' }); - window.location.href = 'https://one.libretexts.org/api/v1/auth/cas-bridge'; +window.LibreTextsLoginCAS = (event, gateway = false) => { + event?.preventDefault(); + cookieJar.set('cas_bridge_source', window.location.host, { domain: 'libretexts.org', sameSite: 'lax' }); + cookieJar.set('cas_bridge_redirect', window.location.href, { domain: 'libretexts.org', sameSite: 'lax' }); + const isStaging = window.location.hostname === 'dev.libretexts.org'; + window.location.href = `https://${isStaging ? 'staging.' : ''}one.libretexts.org/api/v1/auth/cas-bridge${gateway ? '?gateway=true' : ''}`; }; window.LibreTextsLogoutCAS = (event) => { - event.preventDefault(); - cookieJar.remove(`cas_bridge_token_${window.location.host}`, { domain: 'libretexts.org' }); - cookieJar.remove('cas_bridge_redirect', { domain: 'libretexts.org' }); - cookieJar.remove('cas_bridge_source', { domain: 'libretexts.org' }); - window.location.href = 'https://auth.libretexts.org/cas/logout'; + event.preventDefault(); + cookieJar.remove(`cas_bridge_token_${window.location.host}`, { domain: 'libretexts.org' }); + cookieJar.remove('cas_bridge_redirect', { domain: 'libretexts.org' }); + cookieJar.remove('cas_bridge_source', { domain: 'libretexts.org' }); + cookieJar.remove(`cas_bridge_gateway_check_${window.location.host}`, { domain: 'libretexts.org' }); + const isStaging = window.location.hostname === 'dev.libretexts.org'; + window.location.href = `https://${isStaging ? 'castest2' : 'auth'}.libretexts.org/cas/logout`; }; -window.addEventListener('DOMContentLoaded', async () => { - if (window.location.href.endsWith('#')) { - history.replaceState('', document.title, window.location.pathname + window.location.search); - } +(async () => { + if (window.location.href.endsWith('#')) { + history.replaceState('', document.title, window.location.pathname + window.location.search); + } - // - let login = document.getElementById('emailHolder').innerText; - const $target = $('.mt-user-quick-login'); - if (login) { - return; - } - // + // + let login = document.getElementById('emailHolder').innerText; + const $target = $('.mt-user-quick-login'); + if (login) { + return; + } + // - // - const loginLink = document.getElementById('ssoHolder').innerText; - const authorizedLibCookie = cookieJar.get(`cas_bridge_authorized_${window.location.host}`); - if (authorizedLibCookie && authorizedLibCookie === 'true' && loginLink) { - window.location.href = loginLink; - } - // + // + const loginLink = document.getElementById('ssoHolder').innerText; + const authorizedLibCookie = cookieJar.get(`cas_bridge_authorized_${window.location.host}`); + if (authorizedLibCookie && authorizedLibCookie === 'true' && loginLink) { + window.location.href = loginLink; + return; + } + // - // - const casBridgeToken = cookieJar.get(`cas_bridge_token_${window.location.host}`); - if (casBridgeToken) { - const payload = await verifyJWT(casBridgeToken); - if (payload && payload.sub) { - login = payload; - } - } - if (login) { - $target.replaceWith(`
  • ${login.first_name} ${login.last_name}
  • `); - $('.elm-header-user-nav').addClass('authenticated-sso'); + // + const currentPath = window.location.pathname; + const gatewayCheckKey = `cas_bridge_gateway_check_${window.location.host}`; + if (sessionStorage.getItem(gatewayCheckKey) !== '1' && !currentPath.toLowerCase().includes('special:')) { + sessionStorage.setItem(gatewayCheckKey, '1'); + window.LibreTextsLoginCAS(null, true); + return; + } + // - const $nativeSignIn = $('.mt-icon-quick-sign-in'); - $nativeSignIn.hide(); - return; - } - //
    - - if ($target) { - $target.before(`
  • `) + // + const casBridgeToken = cookieJar.get(`cas_bridge_token_${window.location.host}`); + if (casBridgeToken) { + const payload = await verifyJWT(casBridgeToken); + if (payload && payload.sub) { + login = payload; } -}); + } + if (login) { + $target.replaceWith(`
  • ${login.first_name} ${login.last_name}
  • `); + $('.elm-header-user-nav').addClass('authenticated-sso'); + + const $nativeSignIn = $('.mt-icon-quick-sign-in'); + $nativeSignIn.hide(); + return; + } + //
    + + if ($target) { + $target.before('
  • '); + } +})(); diff --git a/public/CASBridge/package-lock.json b/public/CASBridge/package-lock.json index 12de25fd..2d89c015 100644 --- a/public/CASBridge/package-lock.json +++ b/public/CASBridge/package-lock.json @@ -1254,6 +1254,7 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", "dev": true, + "license": "MIT", "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^1.2.0",