1<!DOCTYPE html> 2<html lang="en"> 3 4<head> 5 <title>Login | Mailgun</title> 6 7 <meta charset="utf-8" /> 8 <meta http-equiv="X-UA-Compatible" content="IE=edge" /> 9 <meta name="description" content="Sign into your Mailgun account. Mailgun 10 provides powerful APIs so you can send, receive, and track email 11 effortlessly." /> 12 <meta name="viewport" content="width=device-width, initial-scale=1" /> 13 14 <!-- Data Layer -->
15<script> 16window.dataLayer = window.dataLayer || []; 17</script>
17 18 19<!-- Google Tag Manager --> 20
21<script>
vendor: 328 bytes, lines 21-26
21 22(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': 23new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], 24j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= 25'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); 26})(window,document,'script','dataLayer','
26GTM-WN2J6
vendor: 4 bytes, line 26
26');
27</script>
27 28
29<script>
vendor: 328 bytes, lines 29-34
29 30(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': 31new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], 32j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= 33'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); 34})(window,document,'script','dataLayer','
34GTM-K833FLHN
vendor: 4 bytes, line 34
34');
35</script>
35 36 37 38<!-- Page view data layer -->
39<script> 40window.dataLayer = window.dataLayer || []; 41 42window.pushGtmPageView = function() { 43 if (window.gtmPageViewPushed) { 44 return; 45 } 46 window.gtmPageViewPushed = true; 47 clearTimeout(window.gtmPageViewTimeout); 48 49 var rudderstackId = ""; 50 if (window.rudderanalytics && rudderanalytics.getAnonymousId) { 51 rudderstackId = rudderanalytics.getAnonymousId(); 52 } 53 54 window.dataLayer.push({ 55 event: "page_view", 56 page_info: { 57 page_url: window.location.href, 58 page_path: window.location.pathname, 59 page_title: document.title, 60 page_referrer: document.referrer, 61 portal: "mailgun", 62 platform: "app", 63 language: (navigator.language || "").toLowerCase(), 64 business_country: "us", 65 business_region: "na", 66 environment: "prod", 67 timezone: "utc-5", 68 }, 69 user_info: { 70 logged_in: false, 71 // Only relevant when the user's remember-me cookie is set. 72 internal_traffic: false, 73 user_id: "", 74 rudderstack_id: rudderstackId, 75 }, 76 }); 77}; 78 79// Do not block page_view indefinitely if RudderStack is slow or blocked. 80window.gtmPageViewTimeout = setTimeout(function() { 81 window.pushGtmPageView(); 82}, 1000); 83</script>
83 84 85<!-- Rudderstack -->
86<script> 87!function(){"use strict";window.RudderSnippetVersion="3.2.0";var e="rudderanalytics";window[e]||(window[e]=[]) 88;var rudderanalytics=window[e];if(Array.isArray(rudderanalytics)){ 89if(true===rudderanalytics.snippetExecuted&&window.console&&console.error){ 90console.error("RudderStack JavaScript SDK snippet included more than once.")}else{rudderanalytics.snippetExecuted=true, 91window.rudderAnalyticsBuildType="legacy";var sdkBaseUrl="https://cdn.rudderlabs.com";var sdkVersion="v3" 92;var sdkFileName="rsa.min.js";var scriptLoadingMode="async" 93;var r=["setDefaultInstanceKey","load","ready","page","track","identify","alias","group","reset","setAnonymousId","startSession","endSession","consent","addCustomIntegration"] 94;for(var n=0;n<r.length;n++){var t=r[n];rudderanalytics[t]=function(r){return function(){var n 95;Array.isArray(window[e])?rudderanalytics.push([r].concat(Array.prototype.slice.call(arguments))):null===(n=window[e][r])||void 0===n||n.apply(window[e],arguments) 96}}(t)}try{ 97new Function('class Test{field=()=>{};test({prop=[]}={}){return prop?(prop?.property??[...prop]):import("");}}'), 98window.rudderAnalyticsBuildType="modern"}catch(i){}var d=document.head||document.getElementsByTagName("head")[0] 99;var o=document.body||document.getElementsByTagName("body")[0];window.rudderAnalyticsAddScript=function(e,r,n){ 100var t=document.createElement("script");t.src=e,t.setAttribute("data-loader","RS_JS_SDK"),r&&n&&t.setAttribute(r,n), 101"async"===scriptLoadingMode?t.async=true:"defer"===scriptLoadingMode&&(t.defer=true), 102d?d.insertBefore(t,d.firstChild):o.insertBefore(t,o.firstChild)},window.rudderAnalyticsMount=function(){!function(){ 103if("undefined"==typeof globalThis){var e;var r=function getGlobal(){ 104return"undefined"!=typeof self?self:"undefined"!=typeof window?window:null}();r&&Object.defineProperty(r,"globalThis",{ 105value:r,configurable:true})} 106}(),window.rudderAnalyticsAddScript("".concat(sdkBaseUrl,"/").concat(sdkVersion,"/").concat(window.rudderAnalyticsBuildType,"/").concat(sdkFileName),"data-rsa-write-key","1rAOS5TDzgx9T39bG58kwN5ZUQz") 107}, 108"undefined"==typeof Promise||"undefined"==typeof globalThis?window.rudderAnalyticsAddScript("https://polyfill-fastly.io/v3/polyfill.min.js?version=3.111.0&features=Symbol%2CPromise&callback=rudderAnalyticsMount"):window.rudderAnalyticsMount() 109; 110var loadOptions = {}; 111rudderanalytics.load("1rAOS5TDzgx9T39bG58kwN5ZUQz", "https://mailgun-dataplane.rudderstack.com", loadOptions); 112rudderanalytics.ready(function() { 113 window.pushGtmPageView(); 114});}}}(); 115</script>
115 116 117<!-- Global styles --> 118<link href="/login/static/styles/sinch_login.css?v=1.0.0" rel="stylesheet" /> 119 120<!-- Favicon --> 121<link href="/login/static/favicon.png" rel="icon" type="image/png" /> 122
123<script> 124 window.sentryOnLoad = function () { 125 // add ?sentry_debug=true to enable debugging 126 const params = new URLSearchParams(window.location.search); 127 const debugKey = 'sentry_debug'; 128 if (params.get(debugKey) == 'true') { 129 localStorage.setItem(debugKey, true) 130 } else if (params.get(debugKey) == 'false') { 131 localStorage.removeItem(debugKey) 132 } 133 134 const environment = "mf-use4"; 135 const enabled = environment !== 'local'; 136 137 Sentry.init({ 138 debug: !!localStorage.getItem(debugKey), 139 release: "[email protected]", 140 enabled, 141 integrations: [ 142 Sentry.browserTracingIntegration(), 143 ], 144 environment, 145 sampleRate: 0.02, 146 tracesSampleRate: 0.02, 147 sendDefaultPii: false, 148 }); 149 150 if (enabled) { 151 Sentry.lazyLoadIntegration("captureConsoleIntegration").then( 152 (integration) => { 153 Sentry.addIntegration(integration({ 154 levels: ['error'] 155 })); 156 }, 157 ); 158 } 159 }; 160</script>
160 161 162 163<!-- 164 Nectary Bootstrapping 165 166 Subresource Integrity hash generated from https://www.srihash.org/ 167 or manually with: 168 ``` 169 openssl dgst -sha384 -binary FILENAME.js | openssl base64 -A 170 ``` 171--> 172<!-- Sourced from https://esm.sh/v135/@nectary/[email protected]/es2022/standalone.js -->
173<script async crossorigin="anonymous" 174 integrity="sha384-fuTBUHu7CloEMNY2h49ryLPrgM3sSTLJubW9lBwXSImr9Wbdzwn7bntzb3XlqVnO" 175 src="/login/static/scripts/nectary.js?v=4.7.2" type="module"></script>
175 176 177<!-- Sourced from https://esm.sh/v135/@nectary/[email protected]/es2022/theme-base.css --> 178<link crossorigin="anonymous" href="/login/static/styles/nectary-theme-base.css?v=1.4.1" 179 integrity="sha384-xOYujAaF/C7tR9qk3nLSPWjIdPE0nSjXzgzTdE0P8HqJ17/90cxPRq4HokDzo/jC" rel="stylesheet" /> 180 181<!-- Sourced from https://esm.sh/v135/@nectary/[email protected]/es2022/theme-cpaas-base.css --> 182<link crossorigin="anonymous" href="/login/static/styles/nectary-theme-cpaas.css?v=1.1.0" 183 integrity="sha384-Z+tQehtkYAPOWqpubvCM7JNSjFamtQ+zzqVZtPuRJsxJU4nYOxEMxngYaus/X953" rel="stylesheet" /> 184 185<!-- Sourced from https://esm.sh/v135/@nectary/[email protected]/es2022/theme-cpaas-mailgun.css --> 186<!-- SOME manually hacked fixes are included in this file --> 187<link crossorigin="anonymous" href="/login/static/styles/nectary-theme-cpaas-mailgun.css?v=1.1.0" 188 integrity="sha384-vac6Iuz/BxN/TLZns4FWC1hSkfYnq1hW08UWd07mgMmEFc5Zpso2nkvEpkNFQjGt" rel="stylesheet" /> 189 190 191<!-- Umbrella.JS - Lightweight DOM utils -->
192<script crossorigin="anonymous" defer 193 integrity="sha384-Gi29SVoexXQhiEdmsWbF75honkW88QXma0jWL0AmI9qGPWmH6pkGDCJ9JY7Q7A2U" 194 src="/login/static/scripts/umbrella.min.js"></script>
194 195 196<!-- QRious.JS - QR code utils -->
197<script crossorigin="anonymous" defer 198 integrity="sha384-V13uVxWWqYBo65PM66jXr5pqe5n8gF9TYuHicOojbpaPxSVZWI3oxVsqgmK+LofD" 199 src="/login/static/scripts/qrious.js"></script>
199 200
201<script src="/login/static/scripts/sentry.js" 202integrity="sha384-C8r+KzJLGPkV7u52rhGw6CkoWv+sDYhm/fl6DToVroTSQPbNQprUNR2E0iFpWB68" 203crossorigin="anonymous"></script>
203 204 205
205<script> 206 // global function to handle state management for sinch-checkbox 207 function manageCheckbox(checkboxId) { 208 const checkbox = document.getElementById(checkboxId) 209 checkbox.addEventListener('click', () => { 210 const checkedValue = !checkbox.getAttribute('checked') 211 checkbox.setAttribute('checked', checkedValue) 212 }) 213 } 214 215 // global function to show error/success messages 216 function showMessage(error, type = 'error', locationId = 'loginError') { 217 document.body.classList.add(type) 218 const loginErrorElement = document.getElementById(locationId) 219 loginErrorElement.innerHTML = `<img src="/login/static/${type}.svg" /> <span class="loginErrorText">${error}</span>`; 220 loginErrorElement.classList.remove('hidden') 221 } 222 223 function hideMessage() { 224 const loginErrorElement = document.getElementById('loginError') 225 loginErrorElement.innerHTML = ''; 226 loginErrorElement.classList.add('hidden') 227 } 228 229 function copyTextListener(copyButtonId, textId) { 230 document.getElementById(copyButtonId).addEventListener('-click', () => { 231 const recoveryCodeEl = document.getElementById(textId) 232 navigator.clipboard.writeText(recoveryCodeEl.innerText); 233 const range = document.createRange(); 234 range.selectNodeContents(recoveryCodeEl); 235 236 const selection = window.getSelection(); 237 selection.removeAllRanges(); 238 selection.addRange(range); 239 }) 240 } 241 </script>
241 242</head> 243 244<body class="nectary-theme-base cpaas-theme-base cpaas-theme-mailgun"> 245 <!-- Google Tag Manager (noscript) -->
vendor: 78 bytes, lines 245-248
245 246 247 <noscript> 248 <iframe src="https://www.googletagmanager.com/ns.html?id=
248GTM-WN2J6
vendor: 172 bytes, lines 248-253
248" height="0" width="0" 249 style="display: none; visibility: hidden"></iframe> 250 </noscript> 251 252 <noscript> 253 <iframe src="https://www.googletagmanager.com/ns.html?id=
253GTM-K833FLHN
vendor: 101 bytes, lines 253-258
253" height="0" width="0" 254 style="display: none; visibility: hidden"></iframe> 255 </noscript> 256 257 258
258<main> 259 <section> 260 <svg class="sinch-mailgun-wordmark" viewBox="0 0 430 80" fill="none" xmlns="http://www.w3.org/2000/svg"> 261 <g clip-path="url(#clip0_2232_6547)"> 262 <path d="M231.65 55.09V27.7132H237.501V30.4171C239.267 28.3194 242.027 27.1613 245.284 27.1613C249.204 27.1613 252.349 28.7613 254.171 31.8528C256.269 28.9272 259.8 27.1613 263.886 27.1613C270.619 27.1613 275.31 31.9086 275.31 39.3597V55.09H269.183V39.9117C269.183 35.552 266.81 32.9566 262.892 32.9566C258.974 32.9566 256.545 35.6605 256.545 39.9117V55.09H250.417V39.9117C250.417 35.552 248.044 32.9566 244.18 32.9566C240.317 32.9566 237.777 35.6605 237.777 39.9117V55.09H231.65Z" fill="black"/> 263 <path d="M279.061 41.4031C279.061 33.4 285.022 27.1628 292.859 27.1628C296.723 27.1628 299.979 28.7085 302.022 31.3582V27.7147H307.873V55.0915H302.022V51.4481C300.035 54.0977 296.723 55.6434 292.859 55.6434C285.022 55.6434 279.061 49.4062 279.061 41.4031ZM301.802 41.4031C301.802 36.4915 298.325 32.9039 293.523 32.9039C288.721 32.9039 285.188 36.4915 285.188 41.4031C285.188 46.3147 288.72 49.8481 293.523 49.8481C298.326 49.8481 301.802 46.3163 301.802 41.4031Z" fill="black"/> 264 <path d="M314.495 19.9318C314.495 17.8899 315.985 16.4 318.027 16.4C320.069 16.4 321.558 17.8899 321.558 19.9318C321.558 21.9737 320.069 23.4093 318.027 23.4093C315.985 23.4093 314.495 21.9194 314.495 19.9318ZM314.937 55.09V27.7132H321.064V55.09H314.937Z" fill="black"/> 265 <path d="M328.072 55.09V17.0063H334.199V55.09H328.072Z" fill="black"/> 266 <path d="M339.904 59.0094L345.424 56.4156C346.638 59.2853 349.067 60.9969 353.593 60.9969C358.56 60.9969 361.816 58.4574 361.816 53.214V50.9504C359.774 53.4342 356.572 54.9241 352.819 54.9241C344.872 54.9241 339.075 48.8528 339.075 41.0156C339.075 33.1783 345.256 27.1613 352.873 27.1613C356.517 27.1613 359.772 28.4853 361.814 31.0807V27.7132H367.72V53.1024C367.72 62.2094 361.096 66.459 353.535 66.459C346.968 66.459 342.276 63.7551 339.903 59.0078L339.904 59.0094ZM361.54 41.0156C361.54 36.2683 358.118 32.7923 353.371 32.7923C348.624 32.7923 345.202 36.2698 345.202 41.0156C345.202 45.7613 348.624 49.2388 353.371 49.2388C358.118 49.2388 361.54 45.7613 361.54 41.0156Z" fill="black"/> 267 <path d="M373.185 43.2792V27.7148H379.312V42.7272C379.312 47.1985 381.686 49.8466 385.603 49.8466C389.521 49.8466 392.171 47.0869 392.171 42.7272V27.7148H398.298V55.0916H392.337V52.442C390.571 54.4838 387.865 55.6435 384.554 55.6435C377.931 55.6435 373.183 50.842 373.183 43.2807L373.185 43.2792Z" fill="black"/> 268 <path d="M404.094 55.09V27.7132H409.945V30.5287C411.711 28.4311 414.526 27.1613 418.004 27.1613C424.737 27.1613 429.54 31.9628 429.54 39.5256V55.09H423.413V40.0776C423.413 35.6063 420.985 32.9582 417.01 32.9582C413.035 32.9582 410.221 35.7179 410.221 40.0776V55.09H404.094Z" fill="black"/> 269 <path d="M198.402 29.1948C200.322 27.8779 202.588 27.1553 204.919 27.1156C212.667 27.1156 218.053 32.4069 218.053 40.248V55.1868H210.976V40.9135C210.976 36.5651 208.331 34.0138 204.742 34.0138C200.866 34.0138 198.503 36.7664 198.503 40.9135V55.1789H191.396V19.4648L198.402 15.4V29.1948Z" fill="black"/> 270 <path d="M103.932 38.1963C104.408 38.3098 104.907 38.4286 105.423 38.558C110.62 39.8796 114.59 41.6751 114.592 46.8729C114.592 52.2592 109.3 55.9421 102.684 55.9421C97.2046 55.9341 93.0464 54.4255 90.4 50.0771L96.6389 46.4878C97.8654 48.9456 99.9429 50.1738 102.873 50.1706C105.624 50.1706 107.315 48.9441 107.315 47.147C107.315 45.35 104.955 44.7874 100.887 43.9349C95.3125 42.7051 91.7232 40.4406 91.7232 35.7151C91.7232 30.6044 96.9194 27.1134 102.683 27.1134C107.597 27.1134 110.996 28.4382 114.02 32.224L108.068 35.6279C106.936 33.6629 105.233 32.6994 102.776 32.6994C100.225 32.6994 98.8099 33.8325 98.8099 35.3458C98.8099 36.9754 100.923 37.4792 103.932 38.1963Z" fill="black"/> 271 <path d="M118.368 55.1869L125.361 55.179V27.1157L118.368 31.2074V55.1869Z" fill="black"/> 272 <path d="M143.597 27.1157C135.848 27.1157 130.275 32.8824 130.275 40.443V55.1885L137.371 55.1806V40.9152C137.371 36.7681 139.734 34.0155 143.61 34.0155C147.199 34.0155 149.844 36.5668 149.844 40.9152V55.1806H156.921V40.443C156.921 33.2596 151.461 27.1157 143.597 27.1157Z" fill="black"/> 273 <path d="M167.86 41.4794C167.86 45.8278 170.789 49.1335 175.137 49.1335C177.972 49.1335 180.43 47.71
27352 181.561 45.354L187.607 48.853C186.361 51.0287 184.558 52.8321 182.382 54.0761C180.207 55.3201 177.739 55.9603 175.232 55.9286C166.843 55.9365 160.688 49.8894 160.688 41.481C160.688 33.0726 166.734 27.1173 175.232 27.1173C180.522 27.1173 185.244 29.8684 187.512 34.1106L181.466 37.7031C180.872 36.5415 179.964 35.5717 178.845 34.9029C177.725 34.2342 176.44 33.8935 175.137 33.9204C170.979 33.9204 167.86 37.0375 167.86 41.4794Z" fill="black"/> 274 <path d="M0 15.3998C0 7.44751 6.44769 0.999817 14.4 0.999817H57.6C65.5523 0.999817 72 7.44751 72 15.3998V58.5998C72 66.5521 65.5523 72.9998 57.6 72.9998H14.4C6.44769 72.9998 0 66.5521 0 58.5998V15.3998Z" fill="#F04126"/> 275 <path d="M62.082 32.4584C60.84 30.6929 59.1106 29.3691 57.0841 28.6349C55.7808 28.1475 54.4146 27.907 53.0232 27.9211C50.6005 27.9369 48.0316 28.6962 45.5995 30.119L23.9808 42.7749C20.6164 44.7432 18.1151 44.4791 16.6011 43.9068C15.4896 43.5106 14.5448 42.7921 13.864 41.8316C13.1817 40.8694 12.817 39.739 12.8123 38.5615C12.806 37.3824 13.155 36.2473 13.8263 35.2788C14.496 34.3088 15.4346 33.5809 16.5477 33.1674C18.0475 32.581 20.5441 32.287 23.9305 34.2129L26.3658 35.6058C26.2243 36.0948 26.119 36.5979 26.0529 37.101L25.9272 38.0537L44.3701 27.4071L44.4078 27.3835C44.9015 27.0345 45.2426 26.5156 45.37 25.9261C45.4973 25.335 45.3999 24.7218 45.0933 24.2014C44.7883 23.681 44.3009 23.2959 43.7208 23.1182C43.1422 22.9406 42.5244 22.9862 41.9788 23.2471L41.9616 23.255L35.9921 26.7216L29.911 23.244L29.8764 23.2267C29.3246 22.9689 28.702 22.9296 28.1235 23.1151C27.5449 23.3006 27.0607 23.6936 26.7604 24.2234C26.4601 24.7533 26.3721 25.3711 26.5136 25.9622C26.6535 26.5534 27.0088 27.0675 27.5135 27.4071L31.2237 29.509L30.7144 29.8093L30.7034 29.8156C29.9817 30.2731 29.3214 30.8202 28.7366 31.4412L26.3013 30.0373C22.3757 27.8017 18.2865 27.3222 14.79 28.6884C13.3326 29.2512 11.9916 30.1457 10.9131 31.2777C9.83459 32.4081 9.00292 33.79 8.50927 35.2773C7.67288 37.8839 7.87097 40.6745 9.0721 43.1349C10.2732 45.5953 12.35 47.4709 14.9174 48.4142C16.2223 48.9015 17.5916 49.1421 18.9798 49.1279C21.4009 49.1122 23.9683 48.3513 26.4035 46.9269L26.4413 46.9033L48.0112 34.2726C51.374 32.3059 53.8769 32.57 55.394 33.1407C56.504 33.5353 57.4504 34.2506 58.1327 35.2112C58.8135 36.1734 59.1735 37.3053 59.1766 38.4844C59.1782 39.1054 59.0839 39.7217 58.8984 40.305C58.6248 41.1131 58.1689 41.8661 57.5762 42.4793C56.9851 43.094 56.2509 43.5798 55.4506 43.8848C53.9508 44.4696 51.4511 44.7621 48.0647 42.8377L45.6294 41.4448C45.785 40.9103 45.8951 40.3632 45.9611 39.8129L46.0743 38.8681L31.7095 47.2791V52.8382L41.284 47.2476L41.2997 47.2382C42.0182 46.7807 42.6785 46.2336 43.2617 45.6126L45.6986 47.0165C49.6399 49.2443 53.7291 49.7222 57.2052 48.3654C59.2269 47.6061 60.939 46.2635 62.1606 44.4822C63.3806 42.7025 64.0157 40.6194 63.9969 38.4624H64C63.9874 36.3007 63.3271 34.2255 62.0851 32.46L62.082 32.4584Z" fill="white"/> 276 </g> 277 <defs> 278 <clipPath id="clip0_2232_6547"> 279 <rect width="430" height="80" fill="white"/> 280 </clipPath> 281 </defs> 282 </svg> 283 284
285<script type="module"> 286 let currentStepSelector = "#usernameForm"; 287 288const loginForm = document.getElementById("submitForm"); 289const spinner = document.getElementById("spinner"); 290 291// Form submit will happen natively on 'enter' press 292// but Nectary buttons don't trigger submit so they need some help 293const nextButton = document.getElementById("next"); 294nextButton.addEventListener("-click", () => { 295 loginForm.requestSubmit(); 296}); 297 298const prevButton = document.getElementById("previous"); 299/** 300 * Go to previous step 301 */ 302prevButton.addEventListener("-click", (e) => { 303 e.preventDefault(); 304 document.getElementById("loginError").classList.add("hidden"); 305 goToFormStep("#usernameForm"); 306}); 307 308// reload page if browser back button is hit 309// ex. sinch id email -> redirected to sinch login -> user hits browser back button 310window.onpageshow = function (event) { 311 if (event.persisted) { 312 window.location.reload(); 313 } 314}; 315 316/** 317 * Hijack submit 318 */ 319loginForm.addEventListener("submit", (e) => { 320 e.preventDefault(); 321 322 const buttonText = nextButton.text; 323 if (buttonText !== "Next" && buttonText !== "Log in") { 324 return false; 325 } 326 327 nextButton.text = ""; 328 document.body.classList.remove("error"); 329 330 const nextMethod = getStepElement(currentStepSelector).data("next"); 331 console.info(`Calling ${nextMethod}`); 332 // form methods will complete any network requests and dom manipulation for next step 333 formMethods[nextMethod](); 334}); 335 336window.onpopstate = function () { 337 if (currentStepSelector !== "#usernameForm") { 338 return location.reload(); 339 } 340 history.back(); 341}; 342 343getStepElement(currentStepSelector, "input").first().focus(); 344 345history.pushState({}, "", ""); 346 347// Display an error message if exists 348const error_message = "False"; 349if (error_message !== "False") { 350 showMessage(error_message, "error"); 351} 352 353/** 354 * Returns a user-friendly error message based on the error object's status code. 355 * 356 * @param {Object} error - The error object. 357 * @param {number} error.status - The HTTP status code representing the error. 358 * @param {string} [error.message] - The optional error message. 359 * @returns {string} A user-friendly error message corresponding to the error status. 360 */ 361function getErrorMessage(error) { 362 switch (error.status) { 363 case 403: 364 case 400: 365 return error.message || "Bad username or password"; 366 case 503: 367 return "Login temporarily disabled"; 368 default: 369 return "Login service is down"; 370 } 371} 372 373/** 374 * Check username and go to password form 375 */ 376async function checkUsername() { 377 const email = getStepElement(currentStepSelector, "input") 378 .first() 379 .value.toLowerCase(); 380 // @TODO: better email validation 381 if (!email) { 382 spinner.classList.remove("hidden"); 383 nextButton.text = "Next"; 384 showMessage("Please enter a valid email", "error"); 385 } else { 386 const data = new FormData(); 387 data.append("email", email); 388 try { 389 await post("/login/email", data); 390 goToFormStep("#passwordForm"); 391 } catch (e) { 392 Sentry.captureException(e); 393 showMessage(getErrorMessage(e), "error"); 394 spinner.classList.add("hidden"); 395 nextButton.text = "Next"; 396 } 397 } 398} 399 400/** 401 * Get otp secret from url param string 402 * @param {string} otp 403 * @return {false|string} 404 */ 405function getSecretFromOTP(otp) { 406 const secretStart = otp.indexOf("secret="); 407 const secretEnd = otp.indexOf("&", secretStart); 408 409 if (secretStart > -1 && secretEnd > -1) { 410 return otp.substring(secretStart + 7, secretEnd); 411 } 412 return false; 413} 414 415/** 416 * Check password and go to either auth challenge, tfa auth, or tfa setup forms 417 */ 418async function checkPassword() { 419 const data = new FormData(); 420 data.append( 421 "password", 422 getStepElement(currentStepSelector, "#password").first().value, 423 ); 424 try { 425 const response = await post("/login/password", data); 426 if (response.step === "challenge") { 427 goToFormStep("#challengeForm"); 428 showMessage( 429 "Weâve detected unusual login activity. For security reasons, weâve sent you a code to your email to validate your account.", 430 "success", 431 ); 432 } else if (response.otp) { 433 const qr = new QRious({ 434 size: 128, 435 value: response.otp, 436 }); 437 const qrURL = qr.toDataURL(); 438 const otpSecret = getSecretFromOTP(response.otp); 439 440 const img = document.createElement("img"); 441 img.src = qrURL; 442 document.getElementById("otp-qr").appendChild(img); 443 document.getElementById("otp-secret").textContent = otpSecret; 444 document.getElementById("recovery-code").textContent = response.secret;
445 goToFormStep("#tfaSetupForm"); 446 447 // manually handle stepper login button 448 nextButton.classList.add("hidden"); 449 const tfaLoginButton = document.getElementById("tfa-step-login"); 450 tfaLoginButton.addEventListener("-click", () => { 451 tfaLoginButton.text = ""; 452 document 453 .getElementById("tfa-step-login-spinner") 454 .classList.remove("hidden"); 455 // need to explicitly call complete2fa instead of requesting form submission 456 /* loginForm.requestSubmit(); */ 457 complete2fa(); 458 }); 459 } else { 460 goToFormStep("#tfaForm"); 461 } 462 } catch (e) { 463 Sentry.captureException(e); 464 showMessage(getErrorMessage(e), "error"); 465 spinner.classList.add("hidden"); 466 nextButton.text = "Log in"; 467 } 468} 469 470/** 471 * Check 2fa auth code 472 */ 473async function check2fa() { 474 const data = new FormData(); 475 476 const rememberMeCheckbox = document.getElementById("otp-checkbox"); 477 if (rememberMeCheckbox.getAttribute("checked")) { 478 data.set("otp-remember-me", true); 479 } 480 481 const otpInput = getStepElement(currentStepSelector, "input").nodes; 482 data.set("otp", otpInput[0].value); 483 if (otpInput.length > 1 && otpInput[1].checked) { 484 data.set(otpInput[1].name, otpInput[1].checked); 485 } 486 487 try { 488 await post("/login/2fa", data); 489 showMessage("Great, we're logging you in.", "success"); 490 } catch (e) { 491 Sentry.captureException(e); 492 showMessage(getErrorMessage(e), "error"); 493 spinner.classList.add("hidden"); 494 nextButton.text = "Next"; 495 } 496} 497 498/** 499 * Complete 2fa auth setup 500 */ 501async function complete2fa() { 502 const data = new FormData(); 503 504 const rememberMeCheckbox = document.getElementById("otp-setup-checkbox"); 505 if (rememberMeCheckbox.getAttribute("checked")) { 506 data.set("otp-remember-me", true); 507 } else { 508 data.set("otp-remember-me", false); 509 } 510 511 const otpInput = getStepElement(currentStepSelector, "input").nodes; 512 data.set("otp", otpInput[0].value); 513 if (otpInput.length > 1 && otpInput[1].checked) { 514 data.set(otpInput[1].name, otpInput[1].checked); 515 } 516 517 try { 518 await post("/login/finish2fa", data); 519 } catch (e) { 520 Sentry.captureException(e); 521 showMessage(getErrorMessage(e), "error", "loginErrorComplete2fa"); 522 document.getElementById("tfa-step-login-spinner").classList.add("hidden"); 523 document.getElementById("tfa-step-login").text = "Log In"; 524 } 525} 526 527// Each form's action is set dynamically, so a lookup is used 528const formMethods = { 529 checkUsername, 530 checkPassword, 531 check2fa, 532 complete2fa, 533}; 534 535/** 536 * Handle form traversal 537 * Hides current form and shows next form 538 * 539 * @param {string} nextStepSelector - the next form to show 540 */ 541function goToFormStep(nextStepSelector) { 542 spinner.classList.add("hidden"); 543 hideMessage(); 544 545 let currentStep = getStepElement(currentStepSelector); 546 if (nextStepSelector === "#passwordForm") { 547 prevButton.classList.remove("hidden"); 548 const input = document.getElementById("password"); 549 input.setAttribute("required", true); 550 } else { 551 prevButton.classList.add("hidden"); 552 } 553 554 if (currentStepSelector === "#usernameForm") { 555 // ensure password form is not required in case we are coming back to username form 556 const passwordInput = document.getElementById("password"); 557 passwordInput.removeAttribute("required"); 558 currentStep.addClass("hidden"); 559 currentStepSelector = nextStepSelector; 560 let nextStep = getStepElement(nextStepSelector); 561 const email = getStepElement("#usernameForm", "input") 562 .first() 563 .value.toLowerCase(); 564 getStepElement(nextStepSelector, "input").first().value = email; 565 nextStep.removeClass("hidden"); 566 567 if (getStepElement(currentStepSelector, "input").first()) { 568 getStepElement(currentStepSelector, "input").first().focus(); 569 } 570 571 nextButton.text = "Log in"; 572 } else { 573 currentStep.addClass("hidden"); 574 575 currentStepSelector = nextStepSelector; 576 let nextStep = getStepElement(nextStepSelector); 577 nextStep.removeClass("hidden"); 578 579 if (getStepElement(currentStepSelector, "input").first()) { 580 getStepElement(currentStepSelector, "input").first().focus(); 581 } 582 583 nextButton.text = "Next"; 584 } 585} 586 587/** 588 * Retrieves the first matching element based on a combined CSS selector. 589 *
590 * This function constructs a CSS selector by concatenating the `current` selector 591 * with an optional `selector` parameter, then queries the document for the first matching element. 592 * 593 * @param {string} current - The base CSS selector. 594 * @param {string} [selector=''] - An optional CSS selector to append to the current selector. 595 * @returns {Element|null} The first matching element or null if no match is found. 596 */ 597function getStepElement(current, selector = "") { 598 const step = selector ? `${current} ${selector}` : current; 599 return u(step); 600} 601 602/** 603 * Does a post request 604 * 605 * @param {string} url - endpoint to request 606 * @param {FormData} data - data to send to endpoint 607 */ 608function post(url, data) { 609 nextButton.text = ""; 610 spinner.classList.remove("hidden"); 611 612 data.append("csrf_token", "IjdkOWRlZjMyY2Q2ZjllZDk4YjgzNjgwOGMzMjg5ZGExY2I5ZWFlYjAi.arTILw.AhTKPwRiSvp20K8Tc6DwLRtTsGY"); 613 614 return new Promise(function (resolve, reject) { 615 fetch(url, { 616 method: "Post", 617 body: data, 618 }) 619 .then(function (response) { 620 response 621 .json() 622 .then(function (jsonResponse) { 623 if (response.ok) { 624 // May be auth redirect or Sinch migration 625 if (jsonResponse.redirect) { 626 return (window.location = jsonResponse.redirect); 627 } 628 resolve(jsonResponse); 629 } else { 630 reject({ 631 message: jsonResponse.message, 632 status: response.status, 633 statusText: response.statusText, 634 }); 635 } 636 }) 637 .catch(function (err) { 638 Sentry.captureException(err); 639 reject({ 640 status: response.status, 641 statusText: response.statusText, 642 }); 643 }); 644 }) 645 .catch(function (err) { 646 Sentry.captureException(err); 647 reject({ 648 status: 500, 649 statusText: err.message, 650 }); 651 }); 652 }); 653} 654</script>
654 655 656<div id="mainContent"> 657 658 <h1>Log in to Mailgun</h1> 659 660 661 662 <form id="submitForm" class="login-form"> 663 <!-- EMAIL/USERNAME FORM --> 664 <div id="usernameForm" data-next="checkUsername"> 665 <p>Enter your email to continue</p> 666 667 <!-- Email input --> 668 <div class="labeled-input"> 669 <input autocomplete="username" autofocus id="username" name="username" placeholder="Email Address" required 670 type="email" value="" /> 671 <label for="username">Email Address</label> 672 </div> 673 </div> 674 675 <!-- PASSWORD FORM --> 676 <div class="hidden" data-next="checkPassword" id="passwordForm"> 677 <p>Enter your password to continue</p> 678 <!-- Email input - Disabled --> 679 <input autocomplete="username" disabled id="username" placeholder="Email Address" type="email" /> 680 681 <!-- Password input --> 682 <div class="labeled-input"> 683 <input autocomplete="password" autofocus id="password" name="password" type="password" placeholder="password" 684 value="" /> 685 <label for="password">Password</label> 686 </div> 687 688 <!-- "Forgot password" link --> 689 <sinch-link aria-label="Link" href="/recovery/new" standalone text="Forgot password?"></sinch-link> 690 </div> 691 692 <!-- TFA FORM --> 693 <div id='tfaForm' class='hidden' data-next='check2fa'> 694 <p>Enter your two-factor code to continue</p> 695 <!-- Two Factor Auth --> 696 <div class="labeled-input"> 697 <input autocomplete="one-time-code" inputmode="numeric" autofocus id="tfa" 698 placeholder="Two-factor authentication token" type="number" /> 699 <label for="tfa"> 700 Two-factor authentication token 701 </label> 702 </div> 703 704 <!-- "Don't ask again" checkbox --> 705 <sinch-checkbox aria-label="Don't ask again" id="otp-checkbox" 706 text="Don't ask again on this computer."></sinch-checkbox> 707
707<script> 708 <!-- because of course you have to manually manage state for a nectary checkbox ð--> 709 manageCheckbox('otp-checkbox') 710 </script>
710 711 <br /> 712 <br /> 713 <sinch-link aria-label="2fa Recovery" class="small-text" href="/recovery/2fa" 714 text="Lost your authenticator?"></sinch-link> 715 </div> 716 717 <!-- TFA SET UP FORM --> 718 <div id='tfaSetupForm' class='hidden' data-next='complete2fa'> 719
719<script type="module"> 720 const stepper = document.getElementById('stepper'); 721 722 document 723 .querySelectorAll('[id^="next-"]') 724 .forEach(button => button.addEventListener('-click', nextStep)); 725 726 document 727 .querySelectorAll('[id^="prev-"]') 728 .forEach(button => button.addEventListener('-click', prevStep)); 729 730 function nextStep() { 731 stepper.index = parseInt(stepper.index) + 1; 732 } 733 734 function prevStep() { 735 stepper.index = parseInt(stepper.index) - 1; 736 } 737 </script>
737 738 739 <h2 style="margin-bottom: 16px;">Set up Two-Factor Auth</h2> 740 741 <sinch-vertical-stepper aria-label="Stepper" id="stepper" index="1"> 742 <!-- Install an authentication app --> 743 <sinch-vertical-stepper-item label="Step 1" description="Install an Authentication App"> 744 745 <p> 746 You must have an authentication app installed to verify 2-factor auth 747 tokens. 748 </p> 749 <p> 750 We recommend one of the following: 751 </p> 752 753 <!-- Authentication Apps list--> 754 <ul class="breakout"> 755 <li class="extra-margin"> 756 2FAS — 757 <sinch-link aria-label="Link" class="small-text" external 758 href="https://play.google.com/store/apps/details?id=com.twofasapp" text="Android"></sinch-link> 759 <sinch-link aria-label="Link" class="small-text" external 760 href="https://apps.apple.com/us/app/2fas-auth/id1217793794" text="iOS"></sinch-link> 761 </li> 762 763 <li class="extra-margin"> 764 Ravio OTP — 765 <sinch-link aria-label="Link" external 766 href="https://apps.apple.com/app/raivo-otp/id1459042137?platform=iphone" text="iOS"></sinch-link> 767 </li> 768 769 <li class="extra-margin"> 770 Aegis — 771 <sinch-link aria-label="Link" external 772 href="https://play.google.com/store/apps/details?id=com.beemdevelopment.aegis" 773 text="Android"></sinch-link> 774 </li> 775 776 <li class="extra-margin"> 777 Google Authenticator — 778 <sinch-link aria-label="Link" external 779 href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2" 780 text="Android"></sinch-link> 781 <sinch-link aria-label="Link" external 782 href="https://apps.apple.com/us/app/google-authenticator/id388497605" text="iOS"></sinch-link> 783 </li> 784 785 <li class="extra-margin"> 786 Microsoft Authenticator — 787 <sinch-link aria-label="Link" class="small-text" external 788 href="https://play.google.com/store/apps/details?id=com.azure.authenticator" 789 text="Android"></sinch-link> 790 <sinch-link aria-label="Link" external 791 href="https://apps.apple.com/us/app/microsoft-authenticator/id983156458" text="iOS"></sinch-link> 792 </li> 793 </ul> 794 795 <div class="align-end"> 796 <sinch-button aria-label="Next" id="next-1" text="Next" type="primary"> 797 </sinch-button> 798 </div> 799 </sinch-vertical-stepper-item> 800 801 <!-- Input the auth code --> 802 <sinch-vertical-stepper-item label="Step 2" description="Scan QR Code"> 803 <p>Use your authentication app to scan this QR code: </p> 804 805 <div id="otp-qr"></div> 806 807 <p>Or, manually type in the following secret: </p> 808 809 <div> 810 <pre id="otp-secret" class="code"></pre> 811 <div class="flex-fill"> 812 <sinch-button id="copy-otp" type="secondary" text="Copy Code" aria-label="Copy Code"> 813 <sinch-icon-file-copy slot="icon" /> 814 </sinch-button> 815 </div> 816 </div> 817 818 <div class="align-end"> 819 <sinch-button aria-label="Previous" id="prev-2" text="Previous" type="subtle-primary"> 820 </sinch-button> 821 <sinch-button aria-label="Next" id="next-2" text="Next" type="primary"> 822 </sinch-button> 823 </div> 824 825
825<script> 826 // copy button 827 copyTextListener('copy-otp', 'otp-secret') 828 </script>
828 829 </sinch-vertical-stepper-item> 830 831 <!-- Save the recovery code --> 832 <sinch-vertical-stepper-item label="Step 3" description="Recovery Code"> 833 <sinch-text> 834 Copy this code and keep it somewhere safe. You'll need it if you ever need to log in 835 without your device. 836 </sinch-text> 837 <div> 838 <pre id="recovery-code" class="code"></pre> 839 <div class="flex-fill"> 840 <sinch-button id="copy-recovery" type="secondary" text="Copy Code" aria-label="Copy Code"> 841 <sinch-icon-file-copy slot="icon" /> 842 </sinch-button> 843 </div> 844 <br /> 845 <sinch-checkbox id="recovery-checkbox" text='I have safely recorded this code' aria-label="Checkbox" /> 846 </div> 847 848 <div class="align-end"> 849 <sinch-button aria-label="Previous" id="prev-3" text="Previous" type="subtle-primary"> 850 </sinch-button> 851 <sinch-button aria-label="Next" id="next-3" text="Next" type="primary"> 852 </sinch-button> 853 </div> 854 855
855<script> 856 // manage checkbox + next button 857 const nextBtn = document.getElementById('next-3') 858 nextBtn.setAttribute('disabled', true) 859 const checkbox = document.getElementById('recovery-checkbox') 860 checkbox.addEventListener('click', () => { 861 const checkedValue = !checkbox.getAttribute('checked') 862 checkbox.setAttribute('checked', checkedValue) 863 nextBtn.setAttribute('disabled', !checkedValue) 864 }) 865 866 // copy button 867 copyTextListener('copy-recovery', 'recovery-code') 868 </script>
868 869 </sinch-vertical-stepper-item> 870 871 <!-- Confirm --> 872 <sinch-vertical-stepper-item label="Step 4" description="Confirm 2FA"> 873 874 <!-- Two Factor Auth --> 875 <div class="labeled-input"> 876 <input autocomplete="one-time-code" inputmode="numeric" autofocus id="otp" 877 placeholder="Enter your one-time code" type="number" /> 878 <label for="otp"> 879 Enter your one-time code 880 </label> 881 </div> 882 883 <!-- "Don't ask again" checkbox --> 884 <sinch-checkbox aria-label="Don't ask again" id="otp-setup-checkbox" 885 text="Don't ask again on this computer."></sinch-checkbox> 886
886<script> 887 <!-- because of course you have to manually manage state for a nectary checkbox ð--> 888 manageCheckbox("otp-setup-checkbox") 889 </script>
889 890 891 <div class="hidden" id="loginErrorComplete2fa" style="padding-top: 1em;"></div> 892 <div class="flex-fill"> 893 <sinch-button aria-label="Previous" id="prev-4" size="l" text="Previous" type="subtle-primary"> 894 </sinch-button> 895 <sinch-button aria-label="Log In" id="tfa-step-login" size="l" text="Log In" type="cta-primary"> 896 <sinch-spinner class="hidden" id="tfa-step-login-spinner" slot="icon" /> 897 </sinch-button> 898 </div> 899 </sinch-vertical-stepper-item> 900 </sinch-vertical-stepper> 901 </div> 902 903 <!-- AUTHENTICATION CHALLENGE FORM --> 904 <div id='challengeForm' class='hidden' data-next='check2fa'> 905 <h2>Authentication Code</h2> 906 907 <p>Check your email for the code</p> 908 909 <!-- Two Factor Auth --> 910 <input autocomplete="off" autofocus class="show-placeholder" id="otp-auth-input" name="otp" placeholder="123456" 911 type="text" /> 912 913 <!-- TODO: When we enable this we will need make a small change to the challenge logic in users --> 914 <!-- 915 <div class='rememberMe'> 916 <input type='checkbox' id='otp-checkbox-challenge' name="otp-remember-me" checked/> 917 <label for='otp-checkbox-challenge' class='labelGroup__label'>Don't ask again on this computer</label> 918 </div> 919 --> 920 </div> 921 922 <div class="hidden" id="loginError" style="padding-top: 1em;"> 923 </div> 924 925 <div class='flex-fill'> 926 <sinch-button aria-label="Back" class="hidden" id="previous" text="Back" size="l" type="subtle-primary"> 927 </sinch-button> 928 929 <sinch-button aria-label="Next" id="next" text="Next" size="l" type="cta-primary"> 930 <sinch-spinner class="hidden" id="spinner" slot="icon" /> 931 </sinch-button> 932 </div> 933 934 <!-- Submit - hidden input for keyboard support --> 935 <button hidden type="submit"></button> 936 </form> 937</div> 938 939 </section> 940 </main> 941 942
942<script type="module"> 943 if ('') { 944 showMessage('', ''); 945 } 946 </script>
946 947 948 <footer> 949 950 <div> 951 Don't have an account? 952 <a href="https://signup.mailgun.com/new/signup">Signup</a> 953</div> 954 955<div> 956 <ul> 957 <li>© <a href="https://sinch.com">Sinch</a></li> 958 959 <li> 960 <a href="https://www.mailgun.com/terms"> 961 Terms of Service</a> 962 </li> 963 964 <li> 965 <a href="https://www.mailgun.com/privacy-policy"> Privacy 966 Policy</a> 967 </li> 968 969 <li><a href="https://www.mailgun.com/aup"> Sending Policy</a> 970 </li> 971 </ul> 972</div> 973 </footer> 974</body> 975 976</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.