github.com/greenpau/go-authcrunch@v1.1.4/assets/portal/templates/basic/sandbox.template (about) 1 <!doctype html> 2 <html lang="en" class="h-full bg-blue-100"> 3 <head> 4 <title>{{ .MetaTitle }} - {{ .PageTitle }}</title> 5 <!-- Required meta tags --> 6 <meta charset="utf-8"> 7 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> 8 <meta name="description" content="{{ .MetaDescription }}" /> 9 <meta name="author" content="{{ .MetaAuthor }}" /> 10 <link rel="shortcut icon" href="{{ pathjoin .ActionEndpoint "/assets/images/favicon.png" }}" type="image/png"> 11 <link rel="icon" href="{{ pathjoin .ActionEndpoint "/assets/images/favicon.png" }}" type="image/png"> 12 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/google-webfonts/roboto.css" }}" /> 13 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/google-webfonts/montserrat.css" }}" /> 14 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/line-awesome/line-awesome.css" }}" /> 15 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/css/sandbox.css" }}" /> 16 17 {{ if eq .Data.ui_options.custom_css_required "yes" }} 18 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/css/custom.css" }}" /> 19 {{ end }} 20 {{ if or (eq .Data.view "mfa_app_auth") (eq .Data.view "mfa_app_register") }} 21 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/css/mfa_app.css" }}" /> 22 {{ end }} 23 {{ if eq .Data.view "password_recovery" }} 24 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/css/password.css" }}" /> 25 {{ end }} 26 </head> 27 <body class="h-full"> 28 <div class="app-page"> 29 <div class="app-content"> 30 <div class="app-container"> 31 <div class="logo-box"> 32 {{ if .LogoURL }} 33 <img class="logo-img" src="{{ .LogoURL }}" alt="{{ .LogoDescription }}" /> 34 {{ end }} 35 <h2 class="logo-txt">{{ .PageTitle }}</h2> 36 </div> 37 38 {{ if or (eq .Data.view "mfa_mixed_auth") (eq .Data.view "mfa_mixed_register") }} 39 <div class="app-txt-section"> 40 <p>Your session requires multi-factor authentication.</p> 41 {{ if eq .Data.view "mfa_mixed_register" }} 42 <p>However, you do not have second factor authentication method configured.</p> 43 <p>Please click the authentication methods below to proceed with the configuration.</p> 44 {{ else }} 45 <p>Please click the appropriate second factor authentication method to proceed further.</p> 46 {{ end }} 47 </div> 48 <ul role="list" class="divide-y divide-primary-200"> 49 <li class="py-4 flex"> 50 <i class="las la-mobile text-2xl text-primary-500"></i> 51 <div class="ml-3"> 52 {{ if eq .Data.view "mfa_mixed_register" }} 53 <a class="app-lst-lnk" href="{{ pathjoin .ActionEndpoint "sandbox" .Data.id "mfa-app-register" }}"><span>Authenticator App</a> 54 {{ else }} 55 <a class="app-lst-lnk" href="{{ pathjoin .ActionEndpoint "sandbox" .Data.id "mfa-app-auth" }}">Authenticator App</a> 56 {{ end }} 57 </div> 58 </li> 59 <li class="py-4 flex"> 60 <i class="las la-microchip text-2xl text-primary-500"></i> 61 <div class="ml-3"> 62 {{ if eq .Data.view "mfa_mixed_register" }} 63 <a class="app-lst-lnk" href="{{ pathjoin .ActionEndpoint "sandbox" .Data.id "mfa-u2f-register" }}">Hardware Token</a> 64 {{ else }} 65 <a class="app-lst-lnk" href="{{ pathjoin .ActionEndpoint "sandbox" .Data.id "mfa-u2f-auth" }}">Hardware Token</a> 66 {{ end }} 67 </div> 68 </li> 69 </ul> 70 {{ else if eq .Data.view "password_auth" }} 71 <div> 72 <form class="space-y-6" 73 action="{{ pathjoin .ActionEndpoint "sandbox" .Data.id "password-auth" }}" 74 method="POST" 75 autocomplete="off" 76 > 77 <div> 78 <label for="secret" class="app-inp-lbl text-center">Please provide your password</label> 79 <div class="app-inp-box"> 80 <div class="app-inp-prf-img"> 81 <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> 82 <path stroke-linecap="round" stroke-linejoin="round" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" /> 83 </svg> 84 </div> 85 <input id="secret" name="secret" type="password" class="app-inp-txt" 86 autocorrect="off" autocapitalize="off" autocomplete="current-password" spellcheck="false" autofocus required /> 87 </div> 88 </div> 89 90 <div class="hidden"> 91 <input id="sandbox_id" name="sandbox_id" type="hidden" value="{{ .Data.id }}" /> 92 </div> 93 94 <div class="flex gap-4"> 95 <div class="flex-none"> 96 <a href="{{ pathjoin .ActionEndpoint "sandbox" .Data.id "terminate" }}"> 97 <button type="button" class="app-btn-sec"> 98 <div> 99 <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> 100 <path stroke-linecap="round" stroke-linejoin="round" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" /> 101 </svg> 102 </div> 103 </button> 104 </a> 105 </div> 106 <div class="flex-none"> 107 <button type="reset" name="reset" class="app-btn-sec"> 108 <div> 109 <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> 110 <path stroke-linecap="round" stroke-linejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" /> 111 </svg> 112 </div> 113 </button> 114 </div> 115 116 <div class="grow"> 117 <button type="submit" name="submit" class="app-btn-pri"> 118 <div> 119 <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> 120 <path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" /> 121 </svg> 122 </div> 123 <div class="pl-2"> 124 <span>Authenticate</span> 125 </div> 126 </button> 127 </div> 128 </div> 129 </form> 130 </div> 131 {{ else if eq .Data.view "password_recovery" }} 132 133 <!-- Start of Password Recovery --> 134 <div> 135 <form class="space-y-6" 136 action="{{ pathjoin .ActionEndpoint "sandbox" .Data.id "password-recovery" }}" 137 method="POST" 138 autocomplete="off" 139 > 140 <div class="py-4"> 141 <label for="email" class="app-inp-lbl">Email Address</label> 142 <div class="app-inp-box"> 143 <input id="email" name="email" type="text" 144 class="app-inp-txt" 145 autocorrect="off" autocapitalize="off" autocomplete="email" spellcheck="false" autocomplete="off" 146 required /> 147 </div> 148 </div> 149 150 <input id="sandbox_id" name="sandbox_id" type="hidden" value="{{ .Data.id }}" /> 151 152 <div class="flex gap-4"> 153 <div class="flex-none"> 154 <a href="{{ pathjoin .ActionEndpoint "sandbox" .Data.id "terminate" }}"> 155 <button type="button" class="app-btn-sec"> 156 <div> 157 <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> 158 <path stroke-linecap="round" stroke-linejoin="round" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" /> 159 </svg> 160 </div> 161 </button> 162 </a> 163 </div> 164 <div class="grow"> 165 <button type="submit" name="submit" class="app-btn-pri"> 166 <div> 167 <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> 168 <path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" /> 169 </svg> 170 </div> 171 <div class="pl-2"> 172 <span>Recover</span> 173 </div> 174 </button> 175 </div> 176 </div> 177 </form> 178 </div> 179 <!-- End of Password Recovery --> 180 181 {{ else if eq .Data.view "mfa_app_auth" }} 182 <div> 183 <form class="space-y-6" 184 action="{{ pathjoin .ActionEndpoint "sandbox" .Data.id "mfa-app-auth" }}" 185 method="POST" 186 autocomplete="off" 187 > 188 <div class="py-4"> 189 <label for="passcode" class="app-inp-lbl">Passcode</label> 190 <div class="app-inp-box"> 191 <input id="passcode" name="passcode" type="text" 192 class="font-['Montserrat'] app-inp-code-txt validate" 193 pattern="[0-9]{4,8}" maxlength="8" 194 title="Authentication code should contain 4-8 characters and consists of 0-9 characters." 195 autocorrect="off" autocapitalize="off" spellcheck="false" autocomplete="off" 196 required /> 197 </div> 198 </div> 199 <div class="flex gap-4"> 200 <div class="flex-none"> 201 <a href="{{ pathjoin .ActionEndpoint "sandbox" .Data.id "terminate" }}"> 202 <button type="button" class="app-btn-sec"> 203 <div> 204 <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> 205 <path stroke-linecap="round" stroke-linejoin="round" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" /> 206 </svg> 207 </div> 208 </button> 209 </a> 210 </div> 211 <div class="flex-none"> 212 <button type="reset" name="reset" class="app-btn-sec"> 213 <div> 214 <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> 215 <path stroke-linecap="round" stroke-linejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" /> 216 </svg> 217 </div> 218 </button> 219 </div> 220 <div class="grow"> 221 <button type="submit" name="submit" class="app-btn-pri"> 222 <div> 223 <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> 224 <path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /> 225 </svg> 226 </div> 227 <div class="pl-2"> 228 <span>Verify</span> 229 </div> 230 </button> 231 </div> 232 </div> 233 </form> 234 </div> 235 {{ else if eq .Data.view "mfa_u2f_auth" }} 236 <div> 237 <form id="mfa-u2f-auth-form" class="space-y-6" 238 action="{{ pathjoin .ActionEndpoint "sandbox" .Data.id "mfa-u2f-auth" }}" 239 method="POST" 240 autocomplete="off" 241 > 242 <input id="webauthn_request" name="webauthn_request" type="hidden" value="" /> 243 <input id="sandbox_id" name="sandbox_id" type="hidden" value="{{ .Data.id }}" /> 244 <div class="app-txt-section"> 245 <p>Insert your hardware token into a USB port. When prompted, touch, 246 or otherwise trigger the hardware token.</p> 247 </div> 248 </form> 249 <div id="mfa-u2f-auth-form-rst" class="pt-4 hidden"> 250 <a href="{{ pathjoin .ActionEndpoint "sandbox" .Data.id }}"> 251 <button type="button" name="button" class="app-btn-pri"> 252 <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> 253 <path stroke-linecap="round" stroke-linejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" /> 254 </svg> 255 <div class="pl-2"> 256 <span>Try Again</span> 257 </div> 258 </button> 259 </a> 260 </div> 261 </div> 262 {{ else if eq .Data.view "mfa_app_register" }} 263 <div> 264 <form class="mfa-add-app-form" 265 action="{{ pathjoin .ActionEndpoint "sandbox" .Data.id "mfa-app-register" }}" 266 method="POST" 267 autocomplete="off" 268 > 269 <div id="token-params"> 270 <div class="app-txt-section"> 271 <p><b>Step 1</b>: If necessary, amend the label and comment associated with the authenticator. 272 The label is what you would see in your authenticator app. 273 The comment is what you would see in this portal. 274 </p> 275 </div> 276 277 <div> 278 <label for="label" class="app-inp-lbl">Name</label> 279 <div class="app-inp-box"> 280 <div class="app-inp-prf-img"> 281 <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> 282 <path stroke-linecap="round" stroke-linejoin="round" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" /> 283 </svg> 284 </div> 285 <input id="label" name="label" type="text" 286 class="app-inp-txt validate" 287 value="{{ .Data.mfa_label }}" pattern="[A-Za-z0-9]{4,25}" maxlength="25" 288 title="Name should contain 4-25 characters and consists of A-Z, a-z, 0-9 characters." 289 autocorrect="off" autocapitalize="off" spellcheck="false" autocomplete="off" 290 required /> 291 </div> 292 </div> 293 294 <div class="pt-4"> 295 <label for="comment" class="app-inp-lbl">Comment</label> 296 <div class="app-inp-box"> 297 <div class="app-inp-prf-img"> 298 <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> 299 <path stroke-linecap="round" stroke-linejoin="round" d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z" /> 300 </svg> 301 </div> 302 <input id="comment" name="comment" type="text" 303 class="app-inp-txt validate" 304 value="{{ .Data.mfa_comment }}" pattern="[A-Za-z0-9 -]{4,25}" maxlength="50" 305 title="Comment should contain 4-50 characters and consists of A-Z, a-z, 0-9, space, and dash characters." 306 autocorrect="off" autocapitalize="off" spellcheck="false" autocomplete="off" 307 required /> 308 </div> 309 </div> 310 311 <div class="app-txt-section"> 312 <p><b>Step 1a</b> (<i>optional</i>): If necessary, click 313 <a class="text-secondary-500 hover:text-primary-500" href="#advanced-setup-all" 314 onclick="toggleAdvancedSetupMode(); return false;">here</a> 315 to customize default values. 316 </p> 317 </div> 318 319 <div id="advanced-setup-all" class="app-txt-section hidden"> 320 <div class="pt-4"> 321 <label for="secret" class="app-inp-lbl">Token Secret</label> 322 <div class="app-inp-box"> 323 <div class="app-inp-prf-img"> 324 <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> 325 <path stroke-linecap="round" stroke-linejoin="round" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" /> 326 </svg> 327 </div> 328 <input id="secret" name="secret" type="text" 329 class="app-inp-txt validate" 330 value="{{ .Data.mfa_secret }}" pattern="[A-Za-z0-9]{10,100}" maxlength="100" 331 title="Token secret should contain 10-200 characters and consists of A-Z and 0-9 characters only." 332 autocorrect="off" autocapitalize="off" spellcheck="false" autocomplete="off" 333 required /> 334 </div> 335 </div> 336 <div class="app-inp-box"> 337 <select id="period" name="period" class="app-inp-sel"> 338 <option value="15" {{ if eq .Data.mfa_period "15" }} selected{{ end }}>15 Seconds Lifetime</option> 339 <option value="30" {{ if eq .Data.mfa_period "30" }} selected{{ end }}>30 Seconds Lifetime</option> 340 <option value="60" {{ if eq .Data.mfa_period "60" }} selected{{ end }}>60 Seconds Lifetime</option> 341 <option value="90" {{ if eq .Data.mfa_period "90" }} selected{{ end }}>90 Seconds Lifetime</option> 342 </select> 343 </div> 344 <div class="app-inp-box"> 345 <select id="digits" name="digits" class="app-inp-sel"> 346 <option value="4" {{ if eq .Data.mfa_digits "4" }} selected{{ end }}>4 Digit Code</option> 347 <option value="6" {{ if eq .Data.mfa_digits "6" }} selected{{ end }}>6 Digit Code</option> 348 <option value="8" {{ if eq .Data.mfa_digits "8" }} selected{{ end }}>8 Digit Code</option> 349 </select> 350 </div> 351 </div> 352 353 <div class="app-txt-section"> 354 <p><b>Step 2</b>: Open your MFA authenticator application, e.g. Microsoft/Google Authenticator, Authy, etc., 355 add new entry and click the "Get QR" link. 356 </p> 357 <div id="mfa-get-qr-code" class="text-center"> 358 <a class="text-secondary-500 hover:text-primary-500" href="#qr-code-mode" onclick="getQRCode()">Get QR Code</a> 359 </div> 360 </div> 361 </div> 362 363 <div id="mfa-qr-code" class="hidden"> 364 <div id="mfa-qr-code-image" class="flex items-center justify-center"> 365 <img src="{{ pathjoin .ActionEndpoint "sandbox" .Data.id "mfa-app-barcode" .Data.code_uri_encoded }}.png" alt="QR Code" /> 366 </div> 367 <div class="app-txt-section"> 368 <p>» Can't scan? Click or copy the link below.</p> 369 </div> 370 <div id="mfa-no-camera-link" class="app-txt-section text-center"> 371 <a class="text-secondary-500 hover:text-primary-500" href="{{ .Data.code_uri }}">No Camera Link</a> 372 </div> 373 374 <div class="app-txt-section"> 375 <p><b>Step 3</b>: Enter the authentication code you see in the app and click "Add".</p> 376 </div> 377 378 <input id="email" name="email" type="hidden" value="{{ .Data.mfa_email }}" /> 379 <input id="type" name="type" type="hidden" value="{{ .Data.mfa_type }}" /> 380 <input id="barcode_uri" name "barcode_uri" type="hidden" value="{{ pathjoin .ActionEndpoint "sandbox" .Data.id "mfa-app-barcode" }}" /> 381 382 <div class="py-4"> 383 <label for="passcode" class="app-inp-lbl">Passcode</label> 384 <div class="app-inp-box"> 385 <input id="passcode" name="passcode" type="text" 386 class="font-['Montserrat'] app-inp-code-txt validate" 387 pattern="[0-9]{4,8}" maxlength="8" 388 title="Authentication code should contain 4-8 characters and consists of 0-9 characters." 389 autocorrect="off" autocapitalize="off" spellcheck="false" autocomplete="off" 390 required /> 391 </div> 392 </div> 393 394 <div class="flex gap-4"> 395 <div class="grow"> 396 <button type="submit" name="submit" class="app-btn-pri"> 397 <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> 398 <path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4" /> 399 </svg> 400 <div class="pl-2"> 401 <span>Add</span> 402 </div> 403 </button> 404 </div> 405 </div> 406 </div> 407 408 </form> 409 </div> 410 {{ else if eq .Data.view "mfa_u2f_register" }} 411 <div> 412 <form id="mfa-add-u2f-form" class="space-y-6" 413 action="{{ pathjoin .ActionEndpoint "sandbox" .Data.id "mfa-u2f-register" }}" 414 method="POST" 415 autocomplete="off" 416 > 417 <div class="space-y-6 text-lg leading-7 text-primary-600"> 418 <p>Please insert your U2F (USB, NFC, or Bluetooth) Security Key, e.g. Yubikey.</p> 419 <p>Then, please click "Register" button below.</p> 420 </div> 421 <input class="hidden" id="webauthn_register" name="webauthn_register" type="text" /> 422 <input class="hidden" id="webauthn_challenge" name="webauthn_challenge" type="text" value="{{ .Data.webauthn_challenge }}" /> 423 424 <div> 425 <label for="comment" class="app-inp-lbl">Name your token (optional)</label> 426 <div class="app-inp-box"> 427 <div class="app-inp-prf-img"> 428 <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> 429 <path stroke-linecap="round" stroke-linejoin="round" d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z" /> 430 </svg> 431 </div> 432 <input id="comment" name="comment" type="text" 433 class="app-inp-txt validate" 434 pattern="[A-Za-z0-9 -]{4,25}" maxlength="25" 435 title="A comment should contain 4-25 characters and consists of A-Z, a-z, 0-9, space, and dash characters." 436 autocorrect="off" autocapitalize="off" spellcheck="false" autocomplete="off" /> 437 </div> 438 </div> 439 440 <div class="flex gap-4"> 441 <div class="flex-none"> 442 <a href="{{ pathjoin .ActionEndpoint "sandbox" .Data.id "terminate" }}"> 443 <button type="button" class="app-btn-sec"> 444 <div> 445 <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> 446 <path stroke-linecap="round" stroke-linejoin="round" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" /> 447 </svg> 448 </div> 449 </button> 450 </a> 451 </div> 452 <div class="flex-none"> 453 <button type="reset" name="reset" class="app-btn-sec"> 454 <div> 455 <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> 456 <path stroke-linecap="round" stroke-linejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" /> 457 </svg> 458 </div> 459 </button> 460 </div> 461 462 <div class="grow"> 463 <button id="mfa-add-u2f-button" type="button" name="action" class="app-btn-pri" 464 onclick="u2f_token_register('mfa-add-u2f-form', 'mfa-add-u2f-button'); return false;"> 465 <div> 466 <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> 467 <path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" /> 468 </svg> 469 </div> 470 <div class="pl-2"> 471 <span>Register</span> 472 </div> 473 </button> 474 </div> 475 </div> 476 </form> 477 478 <div id="mfa-add-u2f-form-rst" class="hidden"> 479 <a href="{{ pathjoin .ActionEndpoint "sandbox" .Data.id }}"> 480 <button type="button" name="button" class="app-btn-pri"> 481 <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> 482 <path stroke-linecap="round" stroke-linejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" /> 483 </svg> 484 <div class="pl-2"> 485 <span>Try Again</span> 486 </div> 487 </button> 488 </a> 489 </div> 490 </div> 491 {{ else if eq .Data.view "terminate" }} 492 <div class="app-txt-section"> 493 <p>{{ .Data.error }}.</p> 494 </div> 495 <div class="flex gap-4"> 496 <div class="grow"> 497 <a href="{{ pathjoin .ActionEndpoint "login" }}"> 498 <button type="button" class="app-btn-pri"> 499 <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> 500 <path stroke-linecap="round" stroke-linejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" /> 501 </svg> 502 <div class="pl-2"> 503 <span>Start Over</span> 504 </div> 505 </button> 506 </a> 507 </div> 508 </div> 509 {{ else if eq .Data.view "error" }} 510 <div class="app-txt-section"> 511 <p>Your session failed to meet authorization requirements.</p> 512 <p>{{ .Data.error }}.</p> 513 </div> 514 <div class="flex gap-4"> 515 <div class="grow"> 516 <a href="{{ pathjoin .ActionEndpoint "sandbox" .Data.id }}"> 517 <button type="button" class="app-btn-pri"> 518 <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> 519 <path stroke-linecap="round" stroke-linejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" /> 520 </svg> 521 <div class="pl-2"> 522 <span>Try Again</span> 523 </div> 524 </button> 525 </a> 526 </div> 527 </div> 528 {{ else }} 529 <div class="app-txt-section"> 530 <p>The {{ .Data.view }} view is unsupported.</p> 531 </div> 532 {{ end }} 533 534 </div> 535 </div> 536 </div> 537 538 <!-- Optional JavaScript --> 539 <script src="{{ pathjoin .ActionEndpoint "/assets/js/sandbox.js" }}"></script> 540 541 {{ if eq .Data.ui_options.custom_js_required "yes" }} 542 <script src="{{ pathjoin .ActionEndpoint "/assets/js/custom.js" }}"></script> 543 {{ end }} 544 {{ if eq .Data.view "mfa_app_register" }} 545 <!-- App Authentication Registration Scripts --> 546 <script src="{{ pathjoin .ActionEndpoint "/assets/js/sandbox_mfa_add_app.js" }}"></script> 547 {{ end }} 548 {{ if or (eq .Data.view "mfa_u2f_register") (eq .Data.view "mfa_u2f_auth") }} 549 <!-- U2F Authentication Scripts --> 550 <script src="{{ pathjoin .ActionEndpoint "/assets/cbor/cbor.js" }}"></script> 551 <script src="{{ pathjoin .ActionEndpoint "/assets/js/sandbox_mfa_u2f.js" }}"></script> 552 {{ end }} 553 554 {{ if eq .Data.view "mfa_u2f_register" }} 555 <script> 556 function u2f_token_register(formID, btnID) { 557 const params = { 558 challenge: "{{ .Data.webauthn_challenge }}", 559 rp_name: "{{ .Data.webauthn_rp_name }}", 560 user_id: "{{ .Data.webauthn_user_id }}", 561 user_name: "{{ .Data.webauthn_user_email }}", 562 user_display_name: "{{ .Data.webauthn_user_display_name }}", 563 user_verification: "{{ .Data.webauthn_user_verification }}", 564 attestation: "{{ .Data.webauthn_attestation }}", 565 }; 566 register_u2f_token(formID, btnID, params); 567 } 568 </script> 569 {{ end }} 570 {{ if eq .Data.view "mfa_u2f_auth" }} 571 <script> 572 function u2f_token_authenticate(formID) { 573 const params = { 574 challenge: "{{ .Data.webauthn_challenge }}", 575 timeout: {{ .Data.webauthn_timeout }}, 576 rp_name: "{{ .Data.webauthn_rp_name }}", 577 user_verification: "{{ .Data.webauthn_user_verification }}", 578 {{- if .Data.webauthn_credentials }} 579 allowed_credentials: [ 580 {{- range .Data.webauthn_credentials }} 581 { 582 id: "{{ .id }}", 583 type: "{{ .type }}", 584 transports: [{{ range .transports }}"{{ . }}",{{ end }}], 585 }, 586 {{- end }} 587 ], 588 {{ else }} 589 allowed_credentials: [], 590 {{end -}} 591 ext_uvm: {{ .Data.webauthn_ext_uvm }}, 592 ext_loc: {{ .Data.webauthn_ext_loc }}, 593 ext_tx_auth_simple: "{{ .Data.webauthn_tx_auth_simple }}", 594 }; 595 authenticate_u2f_token(formID, params); 596 } 597 598 window.addEventListener("load", u2f_token_authenticate('mfa-u2f-auth-form')); 599 </script> 600 {{ end }} 601 {{ if .Message }} 602 <script> 603 var toastHTML = '<span>{{ .Message }}</span><button class="btn-flat toast-action" onclick="M.Toast.dismissAll();">Close</button>'; 604 toastElement = M.toast({ 605 html: toastHTML, 606 classes: 'toast-error' 607 }); 608 const appContainer = document.querySelector('.app-card-container') 609 appContainer.prepend(toastElement.el) 610 </script> 611 {{ end }} 612 </body> 613 </html>