github.com/greenpau/go-authcrunch@v1.1.4/pkg/authn/ui/pages.go (about) 1 // Copyright 2022 Paul Greenberg greenpau@outlook.com 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package ui 16 17 // PageTemplates stores UI templates. 18 var PageTemplates = map[string]string{ 19 "basic/login": `<!DOCTYPE html> 20 <html lang="en" class="h-full bg-blue-100"> 21 <head> 22 <title>{{ .MetaTitle }} - {{ .PageTitle }}</title> 23 <!-- Required meta tags --> 24 <meta charset="utf-8" /> 25 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> 26 <meta name="description" content="{{ .MetaDescription }}" /> 27 <meta name="author" content="{{ .MetaAuthor }}" /> 28 <link rel="shortcut icon" href="{{ pathjoin .ActionEndpoint "/assets/images/favicon.png" }}" type="image/png" /> 29 <link rel="icon" href="{{ pathjoin .ActionEndpoint "/assets/images/favicon.png" }}" type="image/png" /> 30 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/google-webfonts/roboto.css" }}" /> 31 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/line-awesome/line-awesome.css" }}" /> 32 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/css/login.css" }}" /> 33 {{ if eq .Data.ui_options.custom_css_required "yes" }} 34 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/css/custom.css" }}" /> 35 {{ end }} 36 </head> 37 38 {{ $authenticatorCount := len .Data.login_options.authenticators }} 39 {{ $qrCodeLink := pathjoin .ActionEndpoint "/qrcode/login.png" }} 40 41 42 <body class="h-full"> 43 <div class="app-page"> 44 <div class="app-content"> 45 <div class="app-container"> 46 <div class="logo-box"> 47 {{ if .LogoURL }} 48 <img class="logo-img" src="{{ .LogoURL }}" alt="{{ .LogoDescription }}" /> 49 {{ end }} 50 <h2 class="logo-txt">{{ .PageTitle }}</h2> 51 </div> 52 53 {{ if eq .Data.login_options.form_required "yes" }} 54 <div id="loginform" {{ if ne $authenticatorCount 1 }}class="hidden"{{ end }}> 55 <div> 56 <form class="space-y-6" action="{{ pathjoin .ActionEndpoint "/login" }}" method="POST"> 57 <div> 58 <label for="username" class="block text-center pb-2 text-lg font-sans font-medium text-primary-700">Please provide username or email address</label> 59 <div class="app-inp-box"> 60 <div class="app-inp-prf-img"><i class="las la-user"></i></div> 61 <input class="app-inp-txt" id="username" name="username" type="text" autocorrect="off" autocapitalize="off" autocomplete="username" spellcheck="false" autofocus required /> 62 </div> 63 </div> 64 65 {{ if eq .Data.login_options.realm_dropdown_required "yes" }} 66 <div class="hidden"> 67 <select id="realm" name="realm" class="app-inp-sel"> 68 {{ range .Data.login_options.realms }} 69 {{ if eq .default "yes" }} 70 <option value="{{ .realm }}" selected>{{ .label }}</option> 71 {{ else }} 72 <option value="{{ .realm }}">{{ .label }}</option> 73 {{ end }} 74 {{ end }} 75 </select> 76 </div> 77 {{ else }} 78 {{ range .Data.login_options.realms }} 79 <div class="hidden"> 80 <input type="hidden" id="realm" name="realm" value="{{ .realm }}" /> 81 </div> 82 {{ end }} 83 {{ end }} 84 85 86 <div class="flex gap-4"> 87 {{ if ne $authenticatorCount 1 }} 88 <div class="flex-none"> 89 <button type="button" onclick="hideLoginForm();return false;" class="app-btn-sec"> 90 <div><i class="las la-caret-left"></i></div> 91 <div class="pl-1 pr-2"><span>Back</span></div> 92 </button> 93 </div> 94 {{ end }} 95 <div class="grow"> 96 <button type="submit" class="app-btn-pri"> 97 <div><i class="las la-check-circle"></i></div> 98 <div class="pl-2"><span>Proceed</span></div> 99 </button> 100 </div> 101 </div> 102 </form> 103 </div> 104 105 <div id="user_actions" class="flex flex-wrap pt-6 justify-center gap-4 {{ if or (ne $authenticatorCount 1) (eq .Data.login_options.hide_links "yes") }}hidden{{ end -}}"> 106 <div id="user_register_link" {{ if eq .Data.login_options.hide_register_link "yes" }}class="hidden"{{ end -}}> 107 <a class="text-primary-600" href="{{ pathjoin .ActionEndpoint "/register" .Data.login_options.default_realm }}"> 108 <i class="las la-book"></i> 109 <span class="text-lg">Register</span> 110 </a> 111 </div> 112 113 <div id="forgot_username_link" {{ if eq .Data.login_options.hide_forgot_username_link "yes" }}class="hidden"{{ end -}}> 114 <a class="text-primary-600" href="{{ pathjoin .ActionEndpoint "/forgot" .Data.login_options.default_realm }}"> 115 <i class="las la-unlock"></i> 116 <span class="text-lg">Forgot Username?</span> 117 </a> 118 </div> 119 120 <div id="contact_support_link" {{ if eq .Data.login_options.hide_contact_support_link "yes" }}class="hidden"{{ end -}}> 121 <a class="text-primary-600" href="{{ pathjoin .ActionEndpoint "/help" .Data.login_options.default_realm }}"> 122 <i class="las la-info-circle"></i> 123 <span class="text-lg">Contact Support</span> 124 </a> 125 </div> 126 </div> 127 </div> 128 {{ end }} 129 130 {{ if eq .Data.login_options.authenticators_required "yes" }} 131 <div id="authenticators" class="flex flex-col gap-2"> 132 {{ range .Data.login_options.authenticators }} 133 <div> 134 {{ if .endpoint }} 135 <a href="{{ .endpoint }}"> 136 <div class="app-login-btn-box"> 137 <div class="p-4 bg-[{{ .background_color }}] text-[{{ .color }}] shadow-sm rounded-l-md text-2xl"> 138 <i class="{{ .class_name }}"></i> 139 </div> 140 <div class="app-login-btn-txt"> 141 <span class="uppercase leading-loose">{{ .text }}</span> 142 </div> 143 </div> 144 </a> 145 {{ else }} 146 <a href="#" onclick="showLoginForm('{{ .realm }}', '{{ .registration_enabled }}', '{{ .username_recovery_enabled }}', '{{ .contact_support_enabled }}', '{{ .ActionEndpoint }}');return false;"> 147 <div class="app-login-btn-box"> 148 <div class="p-4 bg-[{{ .background_color }}] text-[{{ .color }}] shadow-sm rounded-l-md text-2xl"> 149 <i class="{{ .class_name }}"></i> 150 </div> 151 <div class="app-login-btn-txt"> 152 <span class="uppercase leading-loose">{{ .text }}</span> 153 </div> 154 </div> 155 </a> 156 {{ end }} 157 </div> 158 {{ end }} 159 </div> 160 {{ end }} 161 </div> 162 <div id="bookmarks" class="px-4 hidden sm:block"> 163 <div onclick="showQRCode('{{ $qrCodeLink }}');return false;" class="bg-[#24292f] text-[#f6f8fa] py-1 px-1 shadow-xl rounded-b-lg pb-2 text-center" style="max-width: 3em;"> 164 <i class="las la-qrcode text-3xl"></i> 165 </div> 166 </div> 167 <div id="qr" class="px-4 flex justify-center hidden"> 168 <div id="qrcode" onclick="hideQRCode();return false;" class="bg-white border border-t-2 py-1 px-1 shadow-xl rounded-b-lg pb-2 max-w-xs inline-flex"></div> 169 </div> 170 </div> 171 </div> 172 <!-- JavaScript --> 173 <script src="{{ pathjoin .ActionEndpoint "/assets/js/login.js" }}"></script> 174 {{ if eq .Data.ui_options.custom_js_required "yes" }} 175 <script src="{{ pathjoin .ActionEndpoint "/assets/js/custom.js" }}"></script> 176 {{ end }} 177 </body> 178 </html>`, 179 "basic/portal": `<!DOCTYPE html> 180 <html lang="en" class="h-full bg-blue-100"> 181 <head> 182 <title>{{ .MetaTitle }} - {{ .PageTitle }}</title> 183 <!-- Required meta tags --> 184 <meta charset="utf-8" /> 185 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> 186 <meta name="description" content="{{ .MetaDescription }}" /> 187 <meta name="author" content="{{ .MetaAuthor }}" /> 188 <link rel="shortcut icon" href="{{ pathjoin .ActionEndpoint "/assets/images/favicon.png" }}" type="image/png" /> 189 <link rel="icon" href="{{ pathjoin .ActionEndpoint "/assets/images/favicon.png" }}" type="image/png" /> 190 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/google-webfonts/roboto.css" }}" /> 191 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/line-awesome/line-awesome.css" }}" /> 192 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/css/portal.css" }}" /> 193 {{ if eq .Data.ui_options.custom_css_required "yes" }} 194 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/css/custom.css" }}" /> 195 {{ end }} 196 </head> 197 198 <body class="h-full"> 199 <div class="app-page"> 200 <div class="app-content"> 201 <div class="app-container"> 202 <div class="logo-col-box justify-center"> 203 {{ if .LogoURL }} 204 <div> 205 <img class="logo-img" src="{{ .LogoURL }}" alt="{{ .LogoDescription }}" /> 206 </div> 207 {{ end }} 208 <div> 209 <h2 class="logo-col-txt">{{ .PageTitle }}</h2> 210 </div> 211 </div> 212 <div> 213 <p class="app-inp-lbl">Access the following services.</p> 214 </div> 215 <div class="mt-3 grid"> 216 {{ range .PrivateLinks }} 217 <div class="pb-2"> 218 <a href="{{ .Link }}" {{ if .TargetEnabled }}target="{{ .Target }}"{{ end }}> 219 <div class="app-portal-btn-box"> 220 <div class="app-portal-btn-img">{{ if .IconEnabled -}}<i class="{{ .IconName }}"></i>{{- end }}</div> 221 <div class="app-portal-btn-txt"><span>{{ .Title }}</span></div> 222 </div> 223 </a> 224 </div> 225 {{ end }} 226 <div class="pb-2"> 227 <a href="{{ pathjoin .ActionEndpoint "/logout" }}"> 228 <div class="app-portal-btn-box"> 229 <div class="app-portal-btn-img"><i class="las la-sign-out-alt"></i></div> 230 <div class="app-portal-btn-txt"><span>Sign Out</span></div> 231 </div> 232 </a> 233 </div> 234 </div> 235 </div> 236 </div> 237 </div> 238 <!-- JavaScript --> 239 <script src="{{ pathjoin .ActionEndpoint "/assets/js/portal.js" }}"></script> 240 {{ if eq .Data.ui_options.custom_js_required "yes" }} 241 <script src="{{ pathjoin .ActionEndpoint "/assets/js/custom.js" }}"></script> 242 {{ end }} 243 </body> 244 </html>`, 245 "basic/whoami": `<!DOCTYPE html> 246 <html lang="en" class="h-full bg-blue-100"> 247 <head> 248 <title>{{ .MetaTitle }} - {{ .PageTitle }}</title> 249 <!-- Required meta tags --> 250 <meta charset="utf-8" /> 251 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> 252 <meta name="description" content="{{ .MetaDescription }}" /> 253 <meta name="author" content="{{ .MetaAuthor }}" /> 254 <link rel="shortcut icon" href="{{ pathjoin .ActionEndpoint "/assets/images/favicon.png" }}" type="image/png" /> 255 <link rel="icon" href="{{ pathjoin .ActionEndpoint "/assets/images/favicon.png" }}" type="image/png" /> 256 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/google-webfonts/roboto.css" }}" /> 257 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/line-awesome/line-awesome.css" }}" /> 258 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/highlight.js/css/atom-one-dark.min.css" }}" /> 259 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/css/whoami.css" }}" /> 260 {{ if eq .Data.ui_options.custom_css_required "yes" }} 261 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/css/custom.css" }}" /> 262 {{ end }} 263 </head> 264 265 <body class="h-full"> 266 <div class="app-page"> 267 <div class="app-content md:max-w-2xl lg:max-w-4xl"> 268 <div class="app-container"> 269 <div class="logo-col-box justify-center"> 270 {{ if .LogoURL }} 271 <div> 272 <img class="logo-img" src="{{ .LogoURL }}" alt="{{ .LogoDescription }}" /> 273 </div> 274 {{ end }} 275 <div> 276 <h2 class="logo-col-txt">{{ .PageTitle }}</h2> 277 </div> 278 </div> 279 280 <div class="mt-3"> 281 <pre><code class="language-json hljs">{{ .Data.token }}</code></pre> 282 </div> 283 284 <div class="flex flex-wrap pt-6 justify-center gap-4"> 285 <div id="forgot_username_link"> 286 <a class="text-primary-600" href="{{ pathjoin .ActionEndpoint "/portal" }}"> 287 <i class="las la-layer-group"></i> 288 <span class="text-lg">Portal</span> 289 </a> 290 </div> 291 <div id="contact_support_link"> 292 <a class="text-primary-600" href="{{ pathjoin .ActionEndpoint "/logout" }}"> 293 <i class="las la-times-circle"></i> 294 <span class="text-lg">Sign Out</span> 295 </a> 296 </div> 297 </div> 298 </div> 299 </div> 300 </div> 301 <!-- JavaScript --> 302 <script src="{{ pathjoin .ActionEndpoint "/assets/highlight.js/js/highlight.js" }}"></script> 303 <script src="{{ pathjoin .ActionEndpoint "/assets/highlight.js/js/languages/json.min.js" }}"></script> 304 <script src="{{ pathjoin .ActionEndpoint "/assets/js/whoami.js" }}"></script> 305 {{ if eq .Data.ui_options.custom_js_required "yes" }} 306 <script src="{{ pathjoin .ActionEndpoint "/assets/js/custom.js" }}"></script> 307 {{ end }} 308 <script> 309 hljs.initHighlightingOnLoad(); 310 </script> 311 </body> 312 </html>`, 313 "basic/register": `<!DOCTYPE html> 314 <html lang="en" class="h-full bg-blue-100"> 315 <head> 316 <title>{{ .MetaTitle }} - {{ .PageTitle }}</title> 317 <!-- Required meta tags --> 318 <meta charset="utf-8" /> 319 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> 320 <meta name="description" content="{{ .MetaDescription }}" /> 321 <meta name="author" content="{{ .MetaAuthor }}" /> 322 <link rel="shortcut icon" href="{{ pathjoin .ActionEndpoint "/assets/images/favicon.png" }}" type="image/png" /> 323 <link rel="icon" href="{{ pathjoin .ActionEndpoint "/assets/images/favicon.png" }}" type="image/png" /> 324 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/line-awesome/line-awesome.css" }}" /> 325 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/google-webfonts/roboto.css" }}" /> 326 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/google-webfonts/montserrat.css" }}" /> 327 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/css/register.css" }}" /> 328 {{ if eq .Data.ui_options.custom_css_required "yes" }} 329 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/css/custom.css" }}" /> 330 {{ end }} 331 </head> 332 333 <body class="h-full"> 334 <div class="app-page"> 335 <div class="app-content {{ if eq .Data.view "register" }}md:max-w-2xl lg:max-w-4xl{{ end }}"> 336 <div class="app-container"> 337 <div class="logo-col-box justify-center"> 338 {{ if .LogoURL }} 339 <div> 340 <img class="logo-img" src="{{ .LogoURL }}" alt="{{ .LogoDescription }}" /> 341 </div> 342 {{ end }} 343 <div> 344 <h2 class="logo-col-txt">{{ .PageTitle }}</h2> 345 </div> 346 </div> 347 348 {{ if .Message }} 349 <div id="alerts" class="rounded-md bg-red-50 p-4"> 350 <div class="flex items-center"> 351 <div class="flex-shrink-0"><i class="las la-exclamation-triangle text-2xl text-red-600"></i></div> 352 <div class="ml-3"><p class="text-sm font-medium text-red-800">{{ .Message }}</p></div> 353 <div class="ml-auto pl-3"> 354 <div class="-mx-1.5 -my-1.5"> 355 <button type="button" onclick="hideAlert(); return false;" class="app-alert-banner"> 356 <span class="sr-only">Dismiss</span> 357 <i class="las la-times text-2xl text-red-600"></i> 358 </button> 359 </div> 360 </div> 361 </div> 362 </div> 363 {{ end }} 364 365 <div class="mt-3"> 366 {{ if eq .Data.view "register" }} 367 <form method="POST" action="{{ pathjoin .ActionEndpoint "/register" }}" class="grid grid-cols-1 gap-y-6 sm:grid-cols-2 sm:gap-x-8"> 368 {{ end }} 369 370 {{ if eq .Data.view "ack" }} 371 <form method="POST" action="{{ pathjoin .ActionEndpoint "/register/ack" .Data.registration_id }}"> 372 {{ end }} 373 374 {{ if eq .Data.view "register" }} 375 <div> 376 <label for="registrant" class="app-gen-inp-lbl">Username</label> 377 <div class="mt-1"> 378 <input id="registrant" name="registrant" type="text" 379 class="app-gen-inp-txt validate" 380 pattern="{{ .Data.username_validate_pattern }}" 381 title="{{ .Data.username_validate_title }}" 382 autocorrect="off" autocapitalize="off" autocomplete="username" spellcheck="false" 383 required 384 /> 385 </div> 386 </div> 387 <div> 388 <label for="registrant_password" class="app-gen-inp-lbl">Password</label> 389 <div class="mt-1"> 390 <input type="password" name="registrant_password" id="registrant_password" 391 class="app-gen-inp-txt validate" 392 pattern="{{ .Data.password_validate_pattern }}" 393 title="{{ .Data.password_validate_title }}" 394 autocorrect="off" autocapitalize="off" autocomplete="new-password" spellcheck="false" 395 required 396 /> 397 </div> 398 </div> 399 <div> 400 <label for="registrant_email" class="app-gen-inp-lbl">Email</label> 401 <div class="mt-1"> 402 <input id="registrant_email" name="registrant_email" type="email" autocomplete="email" 403 class="app-gen-inp-txt validate" 404 autocorrect="off" autocapitalize="off" autocomplete="email" spellcheck="false" 405 required 406 /> 407 </div> 408 </div> 409 <div> 410 <label for="first_name" class="app-gen-inp-lbl">First name</label> 411 <div class="mt-1"> 412 <input type="text" name="first_name" id="first_name" 413 class="app-gen-inp-txt" 414 autocorrect="off" autocapitalize="off" autocomplete="given-name" spellcheck="false" 415 /> 416 </div> 417 </div> 418 <div> 419 <label for="last_name" class="app-gen-inp-lbl">Last name</label> 420 <div class="mt-1"> 421 <input type="text" name="last_name" id="last_name" 422 class="app-gen-inp-txt" 423 autocorrect="off" autocapitalize="off" autocomplete="family-name" spellcheck="false" 424 /> 425 </div> 426 </div> 427 428 {{ if .Data.require_registration_code }} 429 <div> 430 <label for="registrant_code" class="app-gen-inp-lbl">Registration Code</label> 431 <div class="mt-1"> 432 <input type="text" id="registrant_code" name="registrant_code" 433 class="app-gen-inp-txt validate" 434 autocorrect="off" autocapitalize="off" autocomplete="off" spellcheck="false" 435 /> 436 </div> 437 </div> 438 {{ end }} 439 440 {{ if .Data.require_accept_terms }} 441 <div class="sm:col-span-2"> 442 <div class="flex items-start"> 443 <div class="flex-shrink-0"> 444 <input id="accept_terms" name="accept_terms" type="checkbox" 445 aria-describedby="comments-description" 446 class="focus:ring-primary-500 h-4 w-4 text-primary-600 border-gray-300 rounded" 447 required 448 /> 449 </div> 450 <div class="ml-3"> 451 <p class="text-base text-gray-500"> 452 By selecting this, you agree to the 453 <a href="{{ .Data.terms_conditions_link }}" target="_blank" class="font-medium text-gray-700 underline">Terms and Conditions</a> 454 and 455 <a href="{{ .Data.privacy_policy_link }}" target="_blank" class="font-medium text-gray-700 underline">Privacy Policy</a>. 456 </p> 457 </div> 458 </div> 459 </div> 460 {{ end }} 461 {{ end }} 462 463 {{ if eq .Data.view "registered" }} 464 <div class="app-txt-section"> 465 <p>Thank you for registering and we hope you enjoy the experience!</p> 466 <p>Here are a few things to keep in mind:</p> 467 <ol class="list-decimal pl-8"> 468 <li>You should receive your confirmation email within the next 15 minutes.</li> 469 <li>If you still don't see it, please email support so we can resend it to you.</li> 470 </ol> 471 </div> 472 {{ end }} 473 474 {{ if eq .Data.view "ack" }} 475 <div class="pb-4"> 476 <label for="registration_code" class="app-inp-lbl">Passcode</label> 477 <div class="app-inp-box"> 478 <input id="registration_code" name="registration_code" type="text" 479 class="font-['Montserrat'] app-inp-code-txt validate" 480 pattern="[A-Za-z0-9]{6,8}" maxlength="8" 481 title="The registration code should be 6-8 characters long." 482 autocorrect="off" autocapitalize="off" spellcheck="false" autocomplete="off" 483 required /> 484 </div> 485 </div> 486 {{ end }} 487 488 {{ if eq .Data.view "ackfail" }} 489 <div class="app-txt-section"> 490 <p>Unfortunately, things did not go as expected. {{ .Data.message }}.</p> 491 </div> 492 {{ end }} 493 494 {{ if eq .Data.view "acked" }} 495 496 <div class="app-txt-section"> 497 <p>Thank you for confirming your registration and validating your email address!</p> 498 <p>At this point, once an administrator approves or disapproves your registration, 499 you will get an email about that decision. If approved, you will be able to login with your 500 credentials right away. 501 </p> 502 </div> 503 {{ end }} 504 505 <div class="sm:col-span-2"> 506 <div class="flex gap-4 justify-end"> 507 {{ if eq .Data.view "register" }} 508 <a href="{{ .ActionEndpoint }}"> 509 <button type="button" name="portal" class="app-btn-sec"> 510 <div><i class="las la-home"></i></div> 511 <div class="pl-1 pr-2"><span>Home</span></div> 512 </button> 513 </a> 514 <button type="reset" name="reset" class="app-btn-sec"> 515 <div><i class="las la-redo-alt"></i></i></div> 516 <div class="pl-1 pr-2"><span>Clear</span></div> 517 </button> 518 <button type="submit" name="submit" class="app-btn-pri"> 519 <div><i class="las la-check"></i></div> 520 <div class="pl-1 pr-2"><span>Submit</span></div> 521 </button> 522 {{ end }} 523 524 {{ if and (ne .Data.view "register") (ne .Data.view "ack") }} 525 <a href="{{ .ActionEndpoint }}"> 526 <button type="button" name="portal" class="app-btn-sec"> 527 <div><i class="las la-home"></i></div> 528 <div class="pl-1 pr-2"><span>Home</span></div> 529 </button> 530 </a> 531 {{ end }} 532 533 {{ if eq .Data.view "ack" }} 534 <a href="{{ .ActionEndpoint }}"> 535 <button type="button" name="portal" class="app-btn-sec"> 536 <div><i class="las la-home"></i></div> 537 </button> 538 </a> 539 <button type="reset" name="reset" class="app-btn-sec"> 540 <div><i class="las la-redo-alt"></i></i></div> 541 <div class="pl-1 pr-2"><span>Clear</span></div> 542 </button> 543 <button type="submit" name="submit" class="app-btn-pri"> 544 <div><i class="las la-check"></i></div> 545 <div class="pl-1 pr-2"><span>Submit</span></div> 546 </button> 547 {{ end }} 548 </div> 549 </div> 550 551 {{ if or (eq .Data.view "register") (eq .Data.view "ack") }} 552 </form> 553 {{ end }} 554 555 </div> 556 </div> 557 </div> 558 </div> 559 <!-- JavaScript --> 560 <script src="{{ pathjoin .ActionEndpoint "/assets/js/register.js" }}"></script> 561 {{ if eq .Data.ui_options.custom_js_required "yes" }} 562 <script src="{{ pathjoin .ActionEndpoint "/assets/js/custom.js" }}"></script> 563 {{ end }} 564 {{ if .Message }} 565 <script> 566 function hideAlert() { 567 document.getElementById("alerts").remove(); 568 } 569 </script> 570 {{ end }} 571 </body> 572 </html>`, 573 "basic/generic": `<!DOCTYPE html> 574 <html lang="en" class="h-full bg-blue-100"> 575 <head> 576 <title>{{ .MetaTitle }} - {{ .PageTitle }}</title> 577 <!-- Required meta tags --> 578 <meta charset="utf-8" /> 579 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> 580 <meta name="description" content="{{ .MetaDescription }}" /> 581 <meta name="author" content="{{ .MetaAuthor }}" /> 582 <link rel="shortcut icon" href="{{ pathjoin .ActionEndpoint "/assets/images/favicon.png" }}" type="image/png" /> 583 <link rel="icon" href="{{ pathjoin .ActionEndpoint "/assets/images/favicon.png" }}" type="image/png" /> 584 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/google-webfonts/roboto.css" }}" /> 585 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/line-awesome/line-awesome.css" }}" /> 586 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/css/generic.css" }}" /> 587 {{ if eq .Data.ui_options.custom_css_required "yes" }} 588 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/css/custom.css" }}" /> 589 {{ end }} 590 </head> 591 592 <body class="h-full"> 593 <div class="app-page"> 594 <div class="app-content md:max-w-2xl lg:max-w-2xl"> 595 <div class="bg-white py-8 px-4 shadow-lg sm:rounded-lg sm:px-10"> 596 <div class="bg-white min-h-full px-4 py-16 sm:px-6 sm:py-24 md:grid md:place-items-center lg:px-8"> 597 <div class="max-w-max mx-auto"> 598 <main class="sm:flex"> 599 {{ if .LogoURL }} 600 <img class="logo-img" src="{{ .LogoURL }}" alt="{{ .LogoDescription }}" /> 601 {{ end }} 602 <div class="sm:ml-6"> 603 <div class="app-gen-banner-box"> 604 <h1 class="app-gen-banner-header">{{ .PageTitle }}</h1> 605 <p class="app-gen-banner-message">{{ .Data.message }}</p> 606 </div> 607 {{ if .Data.go_back_url }} 608 <div class="app-gen-btn-box"> 609 <a href="{{ .Data.go_back_url }}" class="app-gen-btn-txt"> Go back </a> 610 </div> 611 {{ end }} 612 </div> 613 </main> 614 </div> 615 </div> 616 </div> 617 </div> 618 </div> 619 <!-- JavaScript --> 620 <script src="{{ pathjoin .ActionEndpoint "/assets/js/generic.js" }}"></script> 621 {{ if eq .Data.ui_options.custom_js_required "yes" }} 622 <script src="{{ pathjoin .ActionEndpoint "/assets/js/custom.js" }}"></script> 623 {{ end }} 624 </body> 625 </html>`, 626 "basic/settings": `<!doctype html> 627 <html lang="en"> 628 <head> 629 <title>{{ .MetaTitle }} - {{ .PageTitle }}</title> 630 <!-- Required meta tags --> 631 <meta charset="utf-8"> 632 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> 633 <meta name="description" content="{{ .MetaDescription }}" /> 634 <meta name="author" content="{{ .MetaAuthor }}" /> 635 <link rel="shortcut icon" href="{{ pathjoin .ActionEndpoint "/assets/images/favicon.png" }}" type="image/png"> 636 <link rel="icon" href="{{ pathjoin .ActionEndpoint "/assets/images/favicon.png" }}" type="image/png"> 637 638 <!-- Matrialize CSS --> 639 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/materialize-css/css/materialize.css" }}" /> 640 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/google-webfonts/roboto.css" }}" /> 641 {{ if or (eq .Data.view "mfa-add-app") (eq .Data.view "mfa-test-app") }} 642 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/google-webfonts/montserrat.css" }}" /> 643 {{ end }} 644 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/line-awesome/line-awesome.css" }}" /> 645 {{ if or (eq .Data.view "sshkeys-add") (eq .Data.view "gpgkeys-add") (eq .Data.view "sshkeys-view") (eq .Data.view "gpgkeys-view") }} 646 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/highlight.js/css/atom-one-dark.min.css" }}" /> 647 {{ end }} 648 {{ if or (eq .Data.view "apikeys-add") (eq .Data.view "apikeys-add-status") }} 649 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/highlight.js/css/atom-one-dark.min.css" }}" /> 650 {{ end }} 651 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/css/styles.css" }}" /> 652 {{ if eq .Data.ui_options.custom_css_required "yes" }} 653 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/css/custom.css" }}" /> 654 {{ end }} 655 {{ if or (eq .Data.view "mfa-add-app") (eq .Data.view "mfa-test-app") }} 656 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/css/mfa_app.css" }}" /> 657 {{ end }} 658 </head> 659 <body class="app-body"> 660 <div class="container app-container"> 661 <div class="row"> 662 <nav> 663 <div class="nav-wrapper"> 664 {{ if .LogoURL }} 665 <img src="{{ .LogoURL }}" alt="{{ .LogoDescription }}" /> 666 {{ end }} 667 <a href="#" class="brand-logo">{{ .PageTitle }}</a> 668 <ul id="nav-mobile" class="right hide-on-med-and-down"> 669 <li> 670 <a href="{{ pathjoin .ActionEndpoint "/portal" }}"> 671 <button type="button" class="btn waves-effect waves-light navbtn active"> 672 <span class="app-btn-text">Portal</span> 673 <i class="las la-home left app-btn-icon app-navbar-btn-icon"></i> 674 </button> 675 </a> 676 </li> 677 <li> 678 <a href="{{ pathjoin .ActionEndpoint "/logout" }}" class="navbtn-last"> 679 <button type="button" class="btn waves-effect waves-light navbtn active navbtn-last"> 680 <span class="app-btn-text">Logout</span> 681 <i class="las la-sign-out-alt left app-btn-icon app-navbar-btn-icon"></i> 682 </button> 683 </a> 684 </li> 685 </ul> 686 </div> 687 </nav> 688 </div> 689 <div class="row"> 690 <div class="col s12 l3"> 691 <div class="collection"> 692 <a href="{{ pathjoin .ActionEndpoint "/settings/" }}" class="collection-item{{ if eq .Data.view "general" }} active{{ end }}">General</a> 693 <a href="{{ pathjoin .ActionEndpoint "/settings/sshkeys" }}" class="collection-item{{ if eq .Data.view "sshkeys" }} active{{ end }}">SSH Keys</a> 694 <a href="{{ pathjoin .ActionEndpoint "/settings/gpgkeys" }}" class="collection-item{{ if eq .Data.view "gpgkeys" }} active{{ end }}">GPG Keys</a> 695 <a href="{{ pathjoin .ActionEndpoint "/settings/apikeys" }}" class="collection-item{{ if eq .Data.view "apikeys" }} active{{ end }}">API Keys</a> 696 <a href="{{ pathjoin .ActionEndpoint "/settings/mfa" }}" class="collection-item{{ if eq .Data.view "mfa" }} active{{ end }}">MFA</a> 697 <a href="{{ pathjoin .ActionEndpoint "/settings/password" }}" class="collection-item{{ if eq .Data.view "password" }} active{{ end }}">Password</a> 698 <a href="{{ pathjoin .ActionEndpoint "/settings/connected" }}" class="collection-item{{ if eq .Data.view "connected" }} active{{ end }}">Connected Accounts</a> 699 <a href="{{ pathjoin .ActionEndpoint "/portal" }}" class="hide-on-med-and-up collection-item">Portal</a> 700 <a href="{{ pathjoin .ActionEndpoint "/logout" }}" class="hide-on-med-and-up collection-item">Logout</a> 701 </div> 702 </div> 703 <div class="col s12 l9 app-content"> 704 {{ if eq .Data.view "general" }} 705 <div class="row"> 706 <div class="col s12"> 707 {{ if eq .Data.status "SUCCESS" }} 708 <p> 709 <b>ID</b>: {{ .Data.metadata.ID }}<br/> 710 {{ if .Data.metadata.Name }}<b>Name</b>: {{ .Data.metadata.Name }}<br/>{{ end }} 711 {{ if .Data.metadata.Title }}<b>Title</b>: {{ .Data.metadata.Title }}<br/>{{ end }} 712 <b>Username</b>: {{ .Data.metadata.Username }}<br/> 713 <b>Email</b>: {{ .Data.metadata.Email }}<br/> 714 <b>Created</b>: {{ .Data.metadata.Created }}<br/> 715 <b>LastModified</b>: {{ .Data.metadata.LastModified }}<br/> 716 <b>Revision</b>: {{ .Data.metadata.Revision }} 717 </p> 718 {{ else }} 719 <p>{{.Data.status }}: {{ .Data.status_reason }}</p> 720 {{ end }} 721 </div> 722 </div> 723 {{ end }} 724 {{ if eq .Data.view "sshkeys" }} 725 <div class="row right"> 726 <div class="col s12 right"> 727 <a href="{{ pathjoin .ActionEndpoint "/settings/sshkeys/add" }}"> 728 <button type="button" class="btn waves-effect waves-light navbtn active app-btn"> 729 <i class="las la-key left app-btn-icon"></i> 730 <span class="app-btn-text">Add SSH Key</span> 731 </button> 732 </a> 733 </div> 734 </div> 735 <div class="row"> 736 <div class="col s12"> 737 {{ if .Data.sshkeys }} 738 {{range .Data.sshkeys}} 739 <div class="card"> 740 <div class="card-content"> 741 <span class="card-title">{{ .Comment }}</span> 742 <p> 743 <b>ID</b>: {{ .ID }}<br/> 744 <b>Type:</b> {{ .Type }}<br/> 745 <b>Fingerprint (SHA256)</b>: {{ .Fingerprint }}<br/> 746 <b>Fingerprint (MD5)</b>: {{ .FingerprintMD5 }}<br/> 747 <b>Created At</b>: {{ .CreatedAt }} 748 </p> 749 </div> 750 <div class="card-action"> 751 <a href="{{ pathjoin $.ActionEndpoint "/settings/sshkeys/delete" .ID }}">Delete</a> 752 <a href="{{ pathjoin $.ActionEndpoint "/settings/sshkeys/view" .ID }}">View</a> 753 </div> 754 </div> 755 {{ end }} 756 {{ else }} 757 <p>No registered SSH Keys found</p> 758 {{ end }} 759 </div> 760 </div> 761 {{ end }} 762 {{ if eq .Data.view "sshkeys-add" }} 763 <form action="{{ pathjoin .ActionEndpoint "/settings/sshkeys/add" }}" method="POST"> 764 <div class="row"> 765 <div class="col s12"> 766 <h1>Add SSH Key</h1> 767 <p>Please paste your public SSH key here.</p> 768 <div class="input-field shell-textarea-wrapper"> 769 <textarea id="key1" name="key1" class="hljs shell-textarea"></textarea> 770 </div> 771 <div class="input-field"> 772 <input placeholder="Comment" name="comment1" id="comment1" type="text" autocorrect="off" autocapitalize="off" autocomplete="off" class="validate"> 773 </div> 774 <div class="right"> 775 <button type="submit" name="submit" class="btn waves-effect waves-light navbtn active navbtn-last app-btn"> 776 <i class="las la-plus-circle left app-btn-icon"></i> 777 <span class="app-btn-text">Add SSH Key</span> 778 </button> 779 </div> 780 </div> 781 </div> 782 </form> 783 {{ end }} 784 {{ if eq .Data.view "sshkeys-add-status" }} 785 <div class="row"> 786 <div class="col s12"> 787 {{ if eq .Data.status "SUCCESS" }} 788 <h1>Public SSH Key</h1> 789 <p>{{ .Data.status_reason }}</p> 790 <a href="{{ pathjoin .ActionEndpoint "/settings/sshkeys" }}"> 791 <button type="button" class="btn waves-effect waves-light navbtn active"> 792 <i class="las la-undo-alt left app-btn-icon"></i> 793 <span class="app-btn-text">Go Back</span> 794 </button> 795 </a> 796 {{ else }} 797 <h1>Public SSH Key</h1> 798 <p>Reason: {{ .Data.status_reason }} </p> 799 <a href="{{ pathjoin .ActionEndpoint "/settings/sshkeys/add" }}"> 800 <button type="button" class="btn waves-effect waves-light navbtn active"> 801 <i class="las la-undo-alt left app-btn-icon"></i> 802 <span class="app-btn-text">Try Again</span> 803 </button> 804 </a> 805 {{ end }} 806 </div> 807 </div> 808 {{ end }} 809 {{ if eq .Data.view "sshkeys-delete-status" }} 810 <div class="row"> 811 <div class="col s12"> 812 <h1>Public SSH Key</h1> 813 <p>{{.Data.status }}: {{ .Data.status_reason }}</p> 814 <a href="{{ pathjoin .ActionEndpoint "/settings/sshkeys" }}"> 815 <button type="button" class="btn waves-effect waves-light navbtn active"> 816 <i class="las la-undo-alt left app-btn-icon"></i> 817 <span class="app-btn-text">Go Back</span> 818 </button> 819 </a> 820 </div> 821 </div> 822 {{ end }} 823 {{ if eq .Data.view "gpgkeys" }} 824 <div class="row right"> 825 <div class="col s12 right"> 826 <a href="{{ pathjoin .ActionEndpoint "/settings/gpgkeys/add" }}"> 827 <button type="button" class="btn waves-effect waves-light navbtn active app-btn"> 828 <i class="las la-key left app-btn-icon"></i> 829 <span class="app-btn-text">Add GPG Key</span> 830 </button> 831 </a> 832 </div> 833 </div> 834 <div class="row"> 835 <div class="col s12"> 836 {{ if .Data.gpgkeys }} 837 {{range .Data.gpgkeys}} 838 <div class="card"> 839 <div class="card-content"> 840 <span class="card-title">{{ .Comment }}</span> 841 <p> 842 <b>ID</b>: {{ .ID }}<br/> 843 <b>Usage:</b> {{ .Usage }}<br/> 844 <b>Type:</b> {{ .Type }}<br/> 845 <b>Fingerprint</b>: {{ .Fingerprint }}<br/> 846 <b>Created At</b>: {{ .CreatedAt }} 847 </p> 848 </div> 849 <div class="card-action"> 850 <a href="{{ pathjoin $.ActionEndpoint "/settings/gpgkeys/delete" .ID }}">Delete</a> 851 <a href="{{ pathjoin $.ActionEndpoint "/settings/gpgkeys/view" .ID }}">View</a> 852 </div> 853 </div> 854 {{ end }} 855 {{ else }} 856 <p>No registered GPG Keys found</p> 857 {{ end }} 858 </div> 859 </div> 860 {{ end }} 861 {{ if eq .Data.view "gpgkeys-add" }} 862 <form action="{{ pathjoin .ActionEndpoint "/settings/gpgkeys/add" }}" method="POST"> 863 <div class="row"> 864 <div class="col s12"> 865 <h1>Add GPG Key</h1> 866 <p>Please paste your public GPG key here.</p> 867 <div class="input-field shell-textarea-wrapper"> 868 <textarea id="key1" name="key1" class="hljs shell-textarea"></textarea> 869 </div> 870 <div class="input-field"> 871 <input placeholder="Comment" name="comment1" id="comment1" type="text" autocorrect="off" autocapitalize="off" autocomplete="off" class="validate"> 872 </div> 873 <div class="right"> 874 <button type="submit" name="submit" class="btn waves-effect waves-light navbtn active navbtn-last app-btn"> 875 <i class="las la-plus-circle left app-btn-icon"></i> 876 <span class="app-btn-text">Add GPG Key</span> 877 </button> 878 </div> 879 </div> 880 </div> 881 </form> 882 {{ end }} 883 {{ if eq .Data.view "gpgkeys-add-status" }} 884 <div class="row"> 885 <div class="col s12"> 886 {{ if eq .Data.status "SUCCESS" }} 887 <h1>Public GPG Key</h1> 888 <p>{{ .Data.status_reason }}</p> 889 <a href="{{ pathjoin .ActionEndpoint "/settings/gpgkeys" }}"> 890 <button type="button" class="btn waves-effect waves-light navbtn active"> 891 <i class="las la-undo-alt left app-btn-icon"></i> 892 <span class="app-btn-text">Go Back</span> 893 </button> 894 </a> 895 {{ else }} 896 <h1>Public GPG Key</h1> 897 <p>Reason: {{ .Data.status_reason }} </p> 898 <a href="{{ pathjoin .ActionEndpoint "/settings/gpgkeys/add" }}"> 899 <button type="button" class="btn waves-effect waves-light navbtn active"> 900 <i class="las la-undo-alt left app-btn-icon"></i> 901 <span class="app-btn-text">Try Again</span> 902 </button> 903 </a> 904 {{ end }} 905 </div> 906 </div> 907 {{ end }} 908 {{ if eq .Data.view "gpgkeys-delete-status" }} 909 <div class="row"> 910 <div class="col s12"> 911 <h1>Public GPG Key</h1> 912 <p>{{.Data.status }}: {{ .Data.status_reason }}</p> 913 <a href="{{ pathjoin .ActionEndpoint "/settings/gpgkeys" }}"> 914 <button type="button" class="btn waves-effect waves-light navbtn active"> 915 <i class="las la-undo-alt left app-btn-icon"></i> 916 <span class="app-btn-text">Go Back</span> 917 </button> 918 </a> 919 </div> 920 </div> 921 {{ end }} 922 {{ if or (eq .Data.view "sshkeys-view") (eq .Data.view "gpgkeys-view") }} 923 <div class="row"> 924 <div class="col s12"> 925 {{ if eq .Data.view "gpgkeys-view" }} 926 <h1>GPG Key</h1> 927 {{ else }} 928 <h1>SSH Key</h1> 929 {{ end }} 930 <pre><code class="language-json hljs">{{ .Data.key }}</code></pre> 931 {{ if .Data.pem_key }} 932 <h5>PEM</h5> 933 <pre><code class="language-text hljs">{{ .Data.pem_key }}</code></pre> 934 {{ end }} 935 {{ if .Data.openssh_key }} 936 <h5>OpenSSH</h5> 937 <pre><code class="language-text hljs">{{ .Data.openssh_key }}</code></pre> 938 {{ end }} 939 {{ if eq .Data.view "gpgkeys-view" }} 940 <a href="{{ pathjoin .ActionEndpoint "/settings/gpgkeys" }}"> 941 {{ else }} 942 <a href="{{ pathjoin .ActionEndpoint "/settings/sshkeys" }}"> 943 {{ end }} 944 <button type="button" class="btn waves-effect waves-light navbtn active"> 945 <i class="las la-undo-alt left app-btn-icon"></i> 946 <span class="app-btn-text">Go Back</span> 947 </button> 948 </a> 949 </div> 950 </div> 951 {{ end }} 952 {{ if eq .Data.view "apikeys" }} 953 <div class="row right"> 954 <div class="col s12 right"> 955 <a href="{{ pathjoin .ActionEndpoint "/settings/apikeys/add" }}"> 956 <button type="button" class="btn waves-effect waves-light navbtn active app-btn"> 957 <i class="las la-key left app-btn-icon"></i> 958 <span class="app-btn-text">Add API Key</span> 959 </button> 960 </a> 961 </div> 962 </div> 963 <div class="row"> 964 <div class="col s12"> 965 {{ if .Data.apikeys }} 966 {{range .Data.apikeys}} 967 <div class="card"> 968 <div class="card-content"> 969 <span class="card-title">{{ .Comment }}</span> 970 <p> 971 <b>ID</b>: {{ .ID }}<br/> 972 <b>Created At</b>: {{ .CreatedAt }} 973 </p> 974 </div> 975 <div class="card-action"> 976 <a href="{{ pathjoin $.ActionEndpoint "/settings/apikeys/delete" .ID }}">Delete</a> 977 </div> 978 </div> 979 {{ end }} 980 {{ else }} 981 <p>No registered API Keys found</p> 982 {{ end }} 983 </div> 984 </div> 985 {{ end }} 986 {{ if eq .Data.view "apikeys-add" }} 987 <form action="{{ pathjoin .ActionEndpoint "/settings/apikeys/add" }}" method="POST"> 988 <div class="row"> 989 <div class="col s12"> 990 <h1>Add API Key</h1> 991 <p>Please provide a nickname to identify your new API key.</p> 992 <div class="input-field"> 993 <input placeholder="Comment" name="comment1" id="comment1" type="text" autocorrect="off" autocapitalize="off" autocomplete="off" class="validate"> 994 </div> 995 <div class="right"> 996 <button type="submit" name="submit" class="btn waves-effect waves-light navbtn active navbtn-last app-btn"> 997 <i class="las la-plus-circle left app-btn-icon"></i> 998 <span class="app-btn-text">Add API Key</span> 999 </button> 1000 </div> 1001 </div> 1002 </div> 1003 </form> 1004 {{ end }} 1005 {{ if eq .Data.view "apikeys-add-status" }} 1006 <div class="row"> 1007 <div class="col s12"> 1008 <h1>API Key</h1> 1009 {{ if eq .Data.status "SUCCESS" }} 1010 <p>Keep this key secret!</p> 1011 <pre><code class="language-text hljs">{{ .Data.api_key }}</code></pre> 1012 {{ else }} 1013 <p>{{.Data.status }}: {{ .Data.status_reason }}</p> 1014 {{ end }} 1015 <a href="{{ pathjoin .ActionEndpoint "/settings/apikeys" }}"> 1016 <button type="button" class="btn waves-effect waves-light navbtn active"> 1017 <i class="las la-undo-alt left app-btn-icon"></i> 1018 <span class="app-btn-text">Go Back</span> 1019 </button> 1020 </a> 1021 </div> 1022 </div> 1023 {{ end }} 1024 {{ if eq .Data.view "apikeys-delete-status" }} 1025 <div class="row"> 1026 <div class="col s12"> 1027 <h1>API Key</h1> 1028 <p>{{.Data.status }}: {{ .Data.status_reason }}</p> 1029 <a href="{{ pathjoin .ActionEndpoint "/settings/apikeys" }}"> 1030 <button type="button" class="btn waves-effect waves-light navbtn active"> 1031 <i class="las la-undo-alt left app-btn-icon"></i> 1032 <span class="app-btn-text">Go Back</span> 1033 </button> 1034 </a> 1035 </div> 1036 </div> 1037 {{ end }} 1038 1039 1040 {{ if eq .Data.view "mfa" }} 1041 <div class="row right"> 1042 <div class="col s12 right"> 1043 <a href="{{ pathjoin .ActionEndpoint "/settings/mfa/add/app" }}"> 1044 <button type="button" class="btn waves-effect waves-light navbtn active app-btn"> 1045 <i class="las la-mobile-alt left app-btn-icon"></i> 1046 <span class="app-btn-text">Add MFA App</span> 1047 </button> 1048 </a> 1049 <a href="{{ pathjoin .ActionEndpoint "/settings/mfa/add/u2f" }}" class="navbtn-last"> 1050 <button type="button" class="btn waves-effect waves-light navbtn active navbtn-last app-btn"> 1051 <i class="las la-key left app-btn-icon"></i> 1052 <span class="app-btn-text">Add U2F Key</span> 1053 </button> 1054 </a> 1055 </div> 1056 </div> 1057 <div class="row"> 1058 <div class="col s12"> 1059 {{ if .Data.mfa_tokens }} 1060 {{range .Data.mfa_tokens}} 1061 <div class="card"> 1062 <div class="card-content"> 1063 <span class="card-title">{{ .Comment }}</span> 1064 <p> 1065 <b>ID</b>: {{ .ID }}<br/> 1066 {{ if eq .Type "u2f" }} 1067 <b>Type</b>: Hardware/U2F Token<br/> 1068 {{ else }} 1069 <b>Type</b>: Authenticator App<br/> 1070 <b>Algorithm</b>: {{ .Algorithm }}<br/> 1071 <b>Period</b>: {{ .Period }} seconds<br/> 1072 <b>Digits</b>: {{ .Digits }}<br/> 1073 {{ end }} 1074 <b>Created At</b>: {{ .CreatedAt }} 1075 </p> 1076 </div> 1077 <div class="card-action"> 1078 <a href="{{ pathjoin $.ActionEndpoint "/settings/mfa/delete/" .ID }}">Delete</a> 1079 {{ if eq .Type "totp" }} 1080 <a href="{{ pathjoin $.ActionEndpoint "/settings/mfa/test/app/" (printf "%d" .Digits) .ID }}">Test</a> 1081 {{ end }} 1082 {{ if eq .Type "u2f" }} 1083 <a href="{{ pathjoin $.ActionEndpoint "/settings/mfa/test/u2f/generic" .ID }}">Test</a> 1084 {{ end }} 1085 </div> 1086 </div> 1087 {{ end }} 1088 {{ else }} 1089 <p>No registered MFA devices found</p> 1090 {{ end }} 1091 </div> 1092 </div> 1093 {{ end }} 1094 {{ if eq .Data.view "mfa-add-app" }} 1095 <form id="mfa-add-app-form" action="{{ pathjoin .ActionEndpoint "/settings/mfa/add/app" }}" method="POST"> 1096 <div class="row"> 1097 <h1>Add MFA Authenticator Application</h1> 1098 <div class="col s12 m11 l11"> 1099 <div id="token-params"> 1100 <h6 id="token-params-mode" class="hide">Token Parameters</h6> 1101 <p><b>Step 1</b>: Amend the label and comment associated with the authenticator. 1102 The label is what you would see in your authenticator app. 1103 The comment is what you would see in this portal. 1104 </p> 1105 <div class="input-field"> 1106 <input id="label" name="label" type="text" class="validate" pattern="[A-Za-z0-9 -]{4,25}" 1107 title="Authentication code should contain 4-25 characters and consists of A-Z, a-z, 0-9, space, and dash characters." 1108 maxlength="25" 1109 autocorrect="off" autocapitalize="off" autocomplete="off" 1110 value="{{ .Data.mfa_label }}" 1111 required /> 1112 <label for="label">Label</label> 1113 </div> 1114 <div class="input-field"> 1115 <input id="comment" name="comment" type="text" class="validate" pattern="[A-Za-z0-9 -]{4,25}" 1116 title="Authentication code should contain 4-25 characters and consists of A-Z, a-z, 0-9, space, and dash characters." 1117 maxlength="25" 1118 autocorrect="off" autocapitalize="off" autocomplete="off" 1119 value="{{ .Data.mfa_comment }}" 1120 required /> 1121 <label for="comment">Comment</label> 1122 </div> 1123 <p><b>Step 1a</b> (<i>optional</i>): If necessary, click 1124 <a href="#advanced-setup-mode" onclick="toggleAdvancedSetupMode()">here</a> to customize default values. 1125 </p> 1126 <div id="advanced-setup-all" class="hide"> 1127 <h6 id="advanced-setup-mode" class="hide">Advanced Setup Mode</h6> 1128 <div id="advanced-setup-secret" class="input-field"> 1129 <input id="secret" name="secret" type="text" class="validate" pattern="[A-Za-z0-9]{10,100}" 1130 title="Token secret should contain 10-200 characters and consists of A-Z and 0-9 characters only." 1131 autocorrect="off" autocapitalize="off" autocomplete="off" 1132 maxlength="100" 1133 value="{{ .Data.mfa_secret }}" 1134 required /> 1135 <label for="secret">Token Secret</label> 1136 </div> 1137 <div id="advanced-setup-period" class="input-field"> 1138 <select id="period" name="period" class="browser-default"> 1139 <option value="15" {{ if eq .Data.mfa_period "15" }} selected{{ end }}>15 Seconds Lifetime</option> 1140 <option value="30" {{ if eq .Data.mfa_period "30" }} selected{{ end }}>30 Seconds Lifetime</option> 1141 <option value="60" {{ if eq .Data.mfa_period "60" }} selected{{ end }}>60 Seconds Lifetime</option> 1142 <option value="90" {{ if eq .Data.mfa_period "90" }} selected{{ end }}>90 Seconds Lifetime</option> 1143 </select> 1144 </div> 1145 <div id="advanced-setup-digits" class="input-field"> 1146 <select id="digits" name="digits" class="browser-default"> 1147 <option value="4" {{ if eq .Data.mfa_digits "4" }} selected{{ end }}>4 Digit Code</option> 1148 <option value="6" {{ if eq .Data.mfa_digits "6" }} selected{{ end }}>6 Digit Code</option> 1149 <option value="8" {{ if eq .Data.mfa_digits "8" }} selected{{ end }}>8 Digit Code</option> 1150 </select> 1151 </div> 1152 </div> 1153 <p><b>Step 2</b>: Open your MFA authenticator application, e.g. Microsoft/Google Authenticator, Authy, etc., 1154 add new entry and click the "Get QR" link. 1155 </p> 1156 <div id="mfa-get-qr-code" class="center-align"> 1157 <a href="#qr-code-mode" onclick="getQRCode()">Get QR Code</a> 1158 </div> 1159 </div> 1160 <div id="mfa-qr-code" class="hide"> 1161 <h6 id="qr-code-mode" class="hide">QR Code Mode</h6> 1162 <div class="center-align"> 1163 <p>» Scan the QR code image.</p> 1164 </div> 1165 <div id="mfa-qr-code-image" class="center-align"> 1166 <img src="{{ pathjoin .ActionEndpoint "/settings/mfa/barcode/" .Data.code_uri_encoded }}.png" alt="QR Code" /> 1167 </div> 1168 <div class="center-align"> 1169 <p>» Can't scan? Click or copy the link below.</p> 1170 </div> 1171 <div id="mfa-no-camera-link" class="center-align"> 1172 <a href="{{ .Data.code_uri }}">No Camera Link</a> 1173 </div> 1174 <p><b>Step 3</b>: Enter the authentication code you see in the app and click "Add".</p> 1175 <div class="input-field mfa-app-auth-ctrl mfa-app-auth-form"> 1176 <input class="mfa-app-auth-passcode" id="passcode" name="passcode" type="text" class="validate" pattern="[0-9]{4,8}" 1177 title="Authentication code should contain 4-8 characters and consists of 0-9 characters." 1178 autocorrect="off" autocapitalize="off" autocomplete="off" 1179 placeholder="______" 1180 required /> 1181 </div> 1182 <input id="email" name="email" type="hidden" value="{{ .Data.mfa_email }}" /> 1183 <input id="type" name="type" type="hidden" value="{{ .Data.mfa_type }}" /> 1184 <input id="barcode_uri" name "barcode_uri" type="hidden" value="{{ pathjoin .ActionEndpoint "/settings/mfa/barcode/"}}" /> 1185 <div class="row right"> 1186 <button type="submit" name="submit" class="btn waves-effect waves-light navbtn active navbtn-last app-btn"> 1187 <i class="las la-plus-circle left app-btn-icon"></i> 1188 <span class="app-btn-text">Add</span> 1189 </button> 1190 </div> 1191 </div> 1192 </div> 1193 </div> 1194 </form> 1195 {{ end }} 1196 {{ if eq .Data.view "mfa-add-app-status" }} 1197 <div class="row"> 1198 <div class="col s12"> 1199 <h1>MFA Token</h1> 1200 <p>{{.Data.status }}: {{ .Data.status_reason }}</p> 1201 {{ if eq .Data.status "SUCCESS" }} 1202 <a href="{{ pathjoin .ActionEndpoint "/settings/mfa" }}"> 1203 <button type="button" class="btn waves-effect waves-light navbtn active"> 1204 <i class="las la-undo-alt left app-btn-icon"></i> 1205 <span class="app-btn-text">Go Back</span> 1206 </button> 1207 </a> 1208 {{ else }} 1209 <a href="{{ pathjoin .ActionEndpoint "/settings/mfa/add/app" }}"> 1210 <button type="button" class="btn waves-effect waves-light navbtn active"> 1211 <i class="las la-undo-alt left app-btn-icon"></i> 1212 <span class="app-btn-text">Try Again</span> 1213 </button> 1214 </a> 1215 {{ end }} 1216 </div> 1217 </div> 1218 {{ end }} 1219 {{ if eq .Data.view "mfa-test-app" }} 1220 <form id="mfa-test-app-form" action="{{ pathjoin .ActionEndpoint "/settings/mfa/test/app/" .Data.mfa_digits .Data.mfa_token_id }}" method="POST"> 1221 <div class="row"> 1222 <h1>Test MFA Authenticator Application</h1> 1223 <div class="row"> 1224 <div class="col s12 m12 l12"> 1225 <p>Please open your MFA authenticator application to view your authentication code and verify your identity</p> 1226 <div class="input-field mfa-app-auth-ctrl mfa-app-auth-form"> 1227 <input class="mfa-app-auth-passcode" id="passcode" name="passcode" type="text" class="validate" pattern="[0-9]{4,8}" 1228 title="Authentication code should contain 4-8 characters and consists of 0-9 characters." 1229 maxlength="6" 1230 autocorrect="off" autocapitalize="off" autocomplete="off" 1231 placeholder="______" 1232 required /> 1233 </div> 1234 <input id="token_id" name="token_id" type="hidden" value="{{ .Data.mfa_token_id }}" /> 1235 <input id="digits" name="digits" type="hidden" value="{{ .Data.mfa_digits }}" /> 1236 <div class="center-align"> 1237 <button type="reset" name="reset" class="btn waves-effect waves-light navbtn active navbtn-last red lighten-1"> 1238 <i class="las la-redo-alt left app-btn-icon"></i> 1239 </button> 1240 <button type="submit" name="submit" class="btn waves-effect waves-light navbtn active navbtn-last"> 1241 <i class="las la-check-square left app-btn-icon"></i> 1242 <span class="app-btn-text">Verify</span> 1243 </button> 1244 </div> 1245 </div> 1246 </div> 1247 </form> 1248 {{ end }} 1249 {{ if eq .Data.view "mfa-test-app-status" }} 1250 <div class="row"> 1251 <div class="col s12"> 1252 <h1>Test MFA Authenticator Application</h1> 1253 <p>{{.Data.status }}: {{ .Data.status_reason }}</p> 1254 {{ if eq .Data.status "SUCCESS" }} 1255 <a href="{{ pathjoin .ActionEndpoint "/settings/mfa" }}"> 1256 <button type="button" class="btn waves-effect waves-light navbtn active"> 1257 <i class="las la-undo-alt left app-btn-icon"></i> 1258 <span class="app-btn-text">Go Back</span> 1259 </button> 1260 </a> 1261 {{ else }} 1262 {{ if ne .Data.mfa_token_id "" }} 1263 <a href="{{ pathjoin .ActionEndpoint "/settings/mfa/test/app/" .Data.mfa_digits .Data.mfa_token_id }}"> 1264 <button type="button" class="btn waves-effect waves-light navbtn active"> 1265 <i class="las la-undo-alt left app-btn-icon"></i> 1266 <span class="app-btn-text">Try Again</span> 1267 </button> 1268 </a> 1269 {{ end }} 1270 {{ end }} 1271 </div> 1272 </div> 1273 {{ end }} 1274 {{ if eq .Data.view "mfa-delete-status" }} 1275 <div class="row"> 1276 <div class="col s12"> 1277 <h1>MFA Token</h1> 1278 <p>{{.Data.status }}: {{ .Data.status_reason }}</p> 1279 <a href="{{ pathjoin .ActionEndpoint "/settings/mfa" }}"> 1280 <button type="button" class="btn waves-effect waves-light navbtn active"> 1281 <i class="las la-undo-alt left app-btn-icon"></i> 1282 <span class="app-btn-text">Go Back</span> 1283 </button> 1284 </a> 1285 </div> 1286 </div> 1287 {{ end }} 1288 {{ if eq .Data.view "mfa-add-u2f" }} 1289 <form id="mfa-add-u2f-form" action="{{ pathjoin .ActionEndpoint "/settings/mfa/add/u2f" }}" method="POST"> 1290 <div class="row"> 1291 <div class="col s12"> 1292 <h1>Add U2F Security Key</h1> 1293 <p>Please insert your U2F (USB, NFC, or Bluetooth) Security Key, e.g. Yubikey.</p> 1294 <p>Then, please click "Register" button below.</p> 1295 <div class="input-field"> 1296 <input id="comment" name="comment" type="text" class="validate" pattern="[A-Za-z0-9 -]{4,25}" 1297 title="Authentication code should contain 4-25 characters and consists of A-Z, a-z, 0-9, space, and dash characters." 1298 autocorrect="off" autocapitalize="off" autocomplete="off" 1299 required /> 1300 <label for="comment">Comment</label> 1301 </div> 1302 <input class="hide" id="webauthn_register" name="webauthn_register" type="text" /> 1303 <input class="hide" id="webauthn_challenge" name="webauthn_challenge" type="text" value="{{ .Data.webauthn_challenge }}" /> 1304 <button id="mfa-add-u2f-button" type="button" name="action" onclick="u2f_token_register('mfa-add-u2f-form', 'mfa-add-u2f-button');" class="btn waves-effect waves-light navbtn active navbtn-last app-btn"> 1305 <i class="las la-plus-circle left app-btn-icon"></i> 1306 <span class="app-btn-text">Register</span> 1307 </button> 1308 </div> 1309 </div> 1310 </form> 1311 {{ end }} 1312 {{ if eq .Data.view "mfa-add-u2f-status" }} 1313 <div class="row"> 1314 <div class="col s12"> 1315 <h1>U2F Security Key</h1> 1316 <p>{{.Data.status }}: {{ .Data.status_reason }}</p> 1317 {{ if eq .Data.status "SUCCESS" }} 1318 <a href="{{ pathjoin .ActionEndpoint "/settings/mfa" }}"> 1319 <button type="button" class="btn waves-effect waves-light navbtn active"> 1320 <i class="las la-undo-alt left app-btn-icon"></i> 1321 <span class="app-btn-text">Go Back</span> 1322 </button> 1323 </a> 1324 {{ else }} 1325 <a href="{{ pathjoin .ActionEndpoint "/settings/mfa/add/u2f" }}"> 1326 <button type="button" class="btn waves-effect waves-light navbtn active"> 1327 <i class="las la-undo-alt left app-btn-icon"></i> 1328 <span class="app-btn-text">Try Again</span> 1329 </button> 1330 </a> 1331 {{ end }} 1332 </div> 1333 </div> 1334 {{ end }} 1335 {{ if eq .Data.view "mfa-test-u2f" }} 1336 <form id="mfa-test-u2f-form" action="{{ pathjoin .ActionEndpoint "/settings/mfa/test/u2f/generic" .Data.mfa_token_id }}" method="POST"> 1337 <div class="row"> 1338 <div class="col s12 m12 l12"> 1339 <h1>Test Token</h1> 1340 <p> 1341 Insert your hardware token into a USB port. 1342 Next, click "Authenticate" button below. 1343 When prompted, touch, or otherwise trigger the hardware token. 1344 </p> 1345 <input id="webauthn_request" name="webauthn_request" type="hidden" /> 1346 <a id="mfa-test-u2f-button" onclick="u2f_token_authenticate('mfa-test-u2f-form', 'mfa-test-u2f-button');" class="btn waves-effect waves-light navbtn active navbtn-last"> 1347 <i class="las la-check-square left app-btn-icon"></i> 1348 <span class="app-btn-text">Verify</span> 1349 </a> 1350 </div> 1351 <input id="token_id" name="token_id" type="hidden" value="{{ .Data.mfa_token_id }}" /> 1352 </div> 1353 </form> 1354 {{ end }} 1355 {{ if eq .Data.view "mfa-test-u2f-status" }} 1356 <div class="row"> 1357 <div class="col s12"> 1358 <h1>Test Token</h1> 1359 <p>{{.Data.status }}: {{ .Data.status_reason }}</p> 1360 {{ if eq .Data.status "SUCCESS" }} 1361 <a href="{{ pathjoin .ActionEndpoint "/settings/mfa" }}"> 1362 <button type="button" class="btn waves-effect waves-light navbtn active"> 1363 <i class="las la-undo-alt left app-btn-icon"></i> 1364 <span class="app-btn-text">Go Back</span> 1365 </button> 1366 </a> 1367 {{ else }} 1368 {{ if ne .Data.mfa_token_id "" }} 1369 <a href="{{ pathjoin .ActionEndpoint "/settings/mfa/test/u2f/generic" .Data.mfa_token_id }}"> 1370 <button type="button" class="btn waves-effect waves-light navbtn active"> 1371 <i class="las la-undo-alt left app-btn-icon"></i> 1372 <span class="app-btn-text">Try Again</span> 1373 </button> 1374 </a> 1375 {{ end }} 1376 {{ end }} 1377 </div> 1378 </div> 1379 {{ end }} 1380 {{ if eq .Data.view "password" }} 1381 <form action="{{ pathjoin .ActionEndpoint "/settings/password/edit" }}" method="POST"> 1382 <div class="row"> 1383 <h1>Password Management</h1> 1384 <div class="row"> 1385 <div class="col s12 m6 l6"> 1386 <p>If you want to change your password, please provide your current password and 1387 </p> 1388 <div class="input-field"> 1389 <input id="secret1" name="secret1" type="password" autocorrect="off" autocapitalize="off" autocomplete="off" required /> 1390 <label for="secret1">Current Password</label> 1391 </div> 1392 <div class="input-field"> 1393 <input id="secret2" name="secret2" type="password" autocorrect="off" autocapitalize="off" autocomplete="off" required /> 1394 <label for="secret2">New Password</label> 1395 </div> 1396 <div class="input-field"> 1397 <input id="secret3" name="secret3" type="password" autocorrect="off" autocapitalize="off" autocomplete="off" required /> 1398 <label for="secret3">Confirm New Password</label> 1399 </div> 1400 </div> 1401 </div> 1402 </div> 1403 <div class="row right"> 1404 <button type="submit" name="submit" class="btn waves-effect waves-light navbtn active navbtn-last app-btn"> 1405 <i class="las la-paper-plane left app-btn-icon"></i> 1406 <span class="app-btn-text">Change Password</span> 1407 </button> 1408 </div> 1409 </form> 1410 {{ end }} 1411 {{ if eq .Data.view "password-edit" }} 1412 <div class="row"> 1413 <div class="col s12"> 1414 {{ if eq .Data.status "SUCCESS" }} 1415 <h1>Password Has Been Changed</h1> 1416 <p>Please log out and log back in.</p> 1417 {{ else }} 1418 <h1>Password Change Failed</h1> 1419 <p>Reason: {{ .Data.status_reason }} </p> 1420 <a href="{{ pathjoin .ActionEndpoint "/settings/password" }}"> 1421 <button type="button" class="btn waves-effect waves-light navbtn active"> 1422 <i class="las la-undo-alt left app-btn-icon"></i> 1423 <span class="app-btn-text">Try Again</span> 1424 </button> 1425 </a> 1426 {{ end }} 1427 </div> 1428 </div> 1429 {{ end }} 1430 {{ if eq .Data.view "connected" }} 1431 <div class="row"> 1432 <div class="col s12"> 1433 <p>No connected accounts found.</p> 1434 </div> 1435 </div> 1436 {{ end }} 1437 </div> 1438 </div> 1439 </div> 1440 1441 <!-- Optional JavaScript --> 1442 <script src="{{ pathjoin .ActionEndpoint "/assets/materialize-css/js/materialize.js" }}"></script> 1443 {{ if or (eq .Data.view "sshkeys-add") (eq .Data.view "gpgkeys-add") (eq .Data.view "sshkeys-view") (eq .Data.view "gpgkeys-view") }} 1444 <script src="{{ pathjoin .ActionEndpoint "/assets/highlight.js/js/highlight.js" }}"></script> 1445 <script src="{{ pathjoin .ActionEndpoint "/assets/highlight.js/js/languages/json.min.js" }}"></script> 1446 <script src="{{ pathjoin .ActionEndpoint "/assets/highlight.js/js/languages/plaintext.min.js" }}"></script> 1447 {{ end }} 1448 {{ if or (eq .Data.view "apikeys-add") (eq .Data.view "apikeys-add-status") }} 1449 <script src="{{ pathjoin .ActionEndpoint "/assets/highlight.js/js/highlight.js" }}"></script> 1450 <script src="{{ pathjoin .ActionEndpoint "/assets/highlight.js/js/languages/json.min.js" }}"></script> 1451 <script src="{{ pathjoin .ActionEndpoint "/assets/highlight.js/js/languages/plaintext.min.js" }}"></script> 1452 {{ end }} 1453 {{ if or (eq .Data.view "mfa-add-u2f") (eq .Data.view "mfa-test-u2f") }} 1454 <script src="{{ pathjoin .ActionEndpoint "/assets/cbor/cbor.js" }}"></script> 1455 {{ end }} 1456 {{ if eq .Data.ui_options.custom_js_required "yes" }} 1457 <script src="{{ pathjoin .ActionEndpoint "/assets/js/custom.js" }}"></script> 1458 {{ end }} 1459 {{ if or (eq .Data.view "mfa-add-app") (eq .Data.view "mfa-test-app") }} 1460 <script src="{{ pathjoin .ActionEndpoint "/assets/js/mfa_add_app.js" }}"></script> 1461 {{ end }} 1462 {{ if eq .Data.view "mfa-add-u2f" }} 1463 <script src="{{ pathjoin .ActionEndpoint "/assets/js/mfa_add_u2f.js" }}"></script> 1464 {{ end }} 1465 {{ if eq .Data.view "mfa-test-u2f" }} 1466 <script src="{{ pathjoin .ActionEndpoint "/assets/js/mfa_add_u2f.js" }}"></script> 1467 <script src="{{ pathjoin .ActionEndpoint "/assets/js/mfa_test_u2f.js" }}"></script> 1468 {{ end }} 1469 {{ if or (eq .Data.view "sshkeys-add") (eq .Data.view "gpgkeys-add") (eq .Data.view "sshkeys-view") (eq .Data.view "gpgkeys-view") }} 1470 <script> 1471 hljs.initHighlightingOnLoad(); 1472 </script> 1473 {{ end }} 1474 {{ if or (eq .Data.view "apikeys-add") (eq .Data.view "apikeys-add-status") }} 1475 <script> 1476 hljs.initHighlightingOnLoad(); 1477 </script> 1478 {{ end }} 1479 {{ if .Message }} 1480 <script> 1481 var toastHTML = '<span class="app-error-text">{{ .Message }}</span><button class="btn-flat toast-action" onclick="M.Toast.dismissAll();">Close</button>'; 1482 toastElement = M.toast({ 1483 html: toastHTML, 1484 classes: 'toast-error' 1485 }); 1486 const appContainer = document.querySelector('.app-container') 1487 appContainer.prepend(toastElement.el) 1488 </script> 1489 {{ end }} 1490 {{ if eq .Data.view "mfa-add-u2f" }} 1491 <script> 1492 function u2f_token_register(formID, btnID) { 1493 const params = { 1494 challenge: "{{ .Data.webauthn_challenge }}", 1495 rp_name: "{{ .Data.webauthn_rp_name }}", 1496 user_id: "{{ .Data.webauthn_user_id }}", 1497 user_name: "{{ .Data.webauthn_user_email }}", 1498 user_display_name: "{{ .Data.webauthn_user_display_name }}", 1499 user_verification: "{{ .Data.webauthn_user_verification }}", 1500 attestation: "{{ .Data.webauthn_attestation }}", 1501 }; 1502 register_u2f_token(formID, btnID, params); 1503 } 1504 </script> 1505 {{ end }} 1506 1507 {{ if eq .Data.view "mfa-test-u2f" }} 1508 <script> 1509 function u2f_token_authenticate(formID, btnID) { 1510 const params = { 1511 challenge: "{{ .Data.webauthn_challenge }}", 1512 timeout: {{ .Data.webauthn_timeout }}, 1513 rp_name: "{{ .Data.webauthn_rp_name }}", 1514 user_verification: "{{ .Data.webauthn_user_verification }}", 1515 {{ if .Data.webauthn_credentials }} 1516 allowed_credentials: [ 1517 {{ range .Data.webauthn_credentials }} 1518 { 1519 id: "{{ .id }}", 1520 type: "{{ .type }}", 1521 transports: [{{ .transports }}], 1522 }, 1523 {{ end }} 1524 ], 1525 {{ else }} 1526 allowed_credentials: [], 1527 {{ end }} 1528 ext_uvm: {{ .Data.webauthn_ext_uvm }}, 1529 ext_loc: {{ .Data.webauthn_ext_loc }}, 1530 ext_tx_auth_simple: "{{ .Data.webauthn_tx_auth_simple }}", 1531 }; 1532 authenticate_u2f_token(formID, btnID, params); 1533 } 1534 </script> 1535 {{ end }} 1536 </body> 1537 </html>`, 1538 "basic/sandbox": `<!doctype html> 1539 <html lang="en" class="h-full bg-blue-100"> 1540 <head> 1541 <title>{{ .MetaTitle }} - {{ .PageTitle }}</title> 1542 <!-- Required meta tags --> 1543 <meta charset="utf-8"> 1544 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> 1545 <meta name="description" content="{{ .MetaDescription }}" /> 1546 <meta name="author" content="{{ .MetaAuthor }}" /> 1547 <link rel="shortcut icon" href="{{ pathjoin .ActionEndpoint "/assets/images/favicon.png" }}" type="image/png"> 1548 <link rel="icon" href="{{ pathjoin .ActionEndpoint "/assets/images/favicon.png" }}" type="image/png"> 1549 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/google-webfonts/roboto.css" }}" /> 1550 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/google-webfonts/montserrat.css" }}" /> 1551 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/line-awesome/line-awesome.css" }}" /> 1552 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/css/sandbox.css" }}" /> 1553 1554 {{ if eq .Data.ui_options.custom_css_required "yes" }} 1555 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/css/custom.css" }}" /> 1556 {{ end }} 1557 {{ if or (eq .Data.view "mfa_app_auth") (eq .Data.view "mfa_app_register") }} 1558 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/css/mfa_app.css" }}" /> 1559 {{ end }} 1560 {{ if eq .Data.view "password_recovery" }} 1561 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/css/password.css" }}" /> 1562 {{ end }} 1563 </head> 1564 <body class="h-full"> 1565 <div class="app-page"> 1566 <div class="app-content"> 1567 <div class="app-container"> 1568 <div class="logo-box"> 1569 {{ if .LogoURL }} 1570 <img class="logo-img" src="{{ .LogoURL }}" alt="{{ .LogoDescription }}" /> 1571 {{ end }} 1572 <h2 class="logo-txt">{{ .PageTitle }}</h2> 1573 </div> 1574 1575 {{ if or (eq .Data.view "mfa_mixed_auth") (eq .Data.view "mfa_mixed_register") }} 1576 <div class="app-txt-section"> 1577 <p>Your session requires multi-factor authentication.</p> 1578 {{ if eq .Data.view "mfa_mixed_register" }} 1579 <p>However, you do not have second factor authentication method configured.</p> 1580 <p>Please click the authentication methods below to proceed with the configuration.</p> 1581 {{ else }} 1582 <p>Please click the appropriate second factor authentication method to proceed further.</p> 1583 {{ end }} 1584 </div> 1585 <ul role="list" class="divide-y divide-primary-200"> 1586 <li class="py-4 flex"> 1587 <i class="las la-mobile text-2xl text-primary-500"></i> 1588 <div class="ml-3"> 1589 {{ if eq .Data.view "mfa_mixed_register" }} 1590 <a class="app-lst-lnk" href="{{ pathjoin .ActionEndpoint "sandbox" .Data.id "mfa-app-register" }}"><span>Authenticator App</a> 1591 {{ else }} 1592 <a class="app-lst-lnk" href="{{ pathjoin .ActionEndpoint "sandbox" .Data.id "mfa-app-auth" }}">Authenticator App</a> 1593 {{ end }} 1594 </div> 1595 </li> 1596 <li class="py-4 flex"> 1597 <i class="las la-microchip text-2xl text-primary-500"></i> 1598 <div class="ml-3"> 1599 {{ if eq .Data.view "mfa_mixed_register" }} 1600 <a class="app-lst-lnk" href="{{ pathjoin .ActionEndpoint "sandbox" .Data.id "mfa-u2f-register" }}">Hardware Token</a> 1601 {{ else }} 1602 <a class="app-lst-lnk" href="{{ pathjoin .ActionEndpoint "sandbox" .Data.id "mfa-u2f-auth" }}">Hardware Token</a> 1603 {{ end }} 1604 </div> 1605 </li> 1606 </ul> 1607 {{ else if eq .Data.view "password_auth" }} 1608 <div> 1609 <form class="space-y-6" 1610 action="{{ pathjoin .ActionEndpoint "sandbox" .Data.id "password-auth" }}" 1611 method="POST" 1612 autocomplete="off" 1613 > 1614 <div> 1615 <label for="secret" class="app-inp-lbl text-center">Please provide your password</label> 1616 <div class="app-inp-box"> 1617 <div class="app-inp-prf-img"> 1618 <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"> 1619 <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" /> 1620 </svg> 1621 </div> 1622 <input id="secret" name="secret" type="password" class="app-inp-txt" 1623 autocorrect="off" autocapitalize="off" autocomplete="current-password" spellcheck="false" autofocus required /> 1624 </div> 1625 </div> 1626 1627 <div class="hidden"> 1628 <input id="sandbox_id" name="sandbox_id" type="hidden" value="{{ .Data.id }}" /> 1629 </div> 1630 1631 <div class="flex gap-4"> 1632 <div class="flex-none"> 1633 <a href="{{ pathjoin .ActionEndpoint "sandbox" .Data.id "terminate" }}"> 1634 <button type="button" class="app-btn-sec"> 1635 <div> 1636 <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"> 1637 <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" /> 1638 </svg> 1639 </div> 1640 </button> 1641 </a> 1642 </div> 1643 <div class="flex-none"> 1644 <button type="reset" name="reset" class="app-btn-sec"> 1645 <div> 1646 <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"> 1647 <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" /> 1648 </svg> 1649 </div> 1650 </button> 1651 </div> 1652 1653 <div class="grow"> 1654 <button type="submit" name="submit" class="app-btn-pri"> 1655 <div> 1656 <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"> 1657 <path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" /> 1658 </svg> 1659 </div> 1660 <div class="pl-2"> 1661 <span>Authenticate</span> 1662 </div> 1663 </button> 1664 </div> 1665 </div> 1666 </form> 1667 </div> 1668 {{ else if eq .Data.view "password_recovery" }} 1669 1670 <!-- Start of Password Recovery --> 1671 <div> 1672 <form class="space-y-6" 1673 action="{{ pathjoin .ActionEndpoint "sandbox" .Data.id "password-recovery" }}" 1674 method="POST" 1675 autocomplete="off" 1676 > 1677 <div class="py-4"> 1678 <label for="email" class="app-inp-lbl">Email Address</label> 1679 <div class="app-inp-box"> 1680 <input id="email" name="email" type="text" 1681 class="app-inp-txt" 1682 autocorrect="off" autocapitalize="off" autocomplete="email" spellcheck="false" autocomplete="off" 1683 required /> 1684 </div> 1685 </div> 1686 1687 <input id="sandbox_id" name="sandbox_id" type="hidden" value="{{ .Data.id }}" /> 1688 1689 <div class="flex gap-4"> 1690 <div class="flex-none"> 1691 <a href="{{ pathjoin .ActionEndpoint "sandbox" .Data.id "terminate" }}"> 1692 <button type="button" class="app-btn-sec"> 1693 <div> 1694 <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"> 1695 <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" /> 1696 </svg> 1697 </div> 1698 </button> 1699 </a> 1700 </div> 1701 <div class="grow"> 1702 <button type="submit" name="submit" class="app-btn-pri"> 1703 <div> 1704 <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"> 1705 <path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" /> 1706 </svg> 1707 </div> 1708 <div class="pl-2"> 1709 <span>Recover</span> 1710 </div> 1711 </button> 1712 </div> 1713 </div> 1714 </form> 1715 </div> 1716 <!-- End of Password Recovery --> 1717 1718 {{ else if eq .Data.view "mfa_app_auth" }} 1719 <div> 1720 <form class="space-y-6" 1721 action="{{ pathjoin .ActionEndpoint "sandbox" .Data.id "mfa-app-auth" }}" 1722 method="POST" 1723 autocomplete="off" 1724 > 1725 <div class="py-4"> 1726 <label for="passcode" class="app-inp-lbl">Passcode</label> 1727 <div class="app-inp-box"> 1728 <input id="passcode" name="passcode" type="text" 1729 class="font-['Montserrat'] app-inp-code-txt validate" 1730 pattern="[0-9]{4,8}" maxlength="8" 1731 title="Authentication code should contain 4-8 characters and consists of 0-9 characters." 1732 autocorrect="off" autocapitalize="off" spellcheck="false" autocomplete="off" 1733 required /> 1734 </div> 1735 </div> 1736 <div class="flex gap-4"> 1737 <div class="flex-none"> 1738 <a href="{{ pathjoin .ActionEndpoint "sandbox" .Data.id "terminate" }}"> 1739 <button type="button" class="app-btn-sec"> 1740 <div> 1741 <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"> 1742 <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" /> 1743 </svg> 1744 </div> 1745 </button> 1746 </a> 1747 </div> 1748 <div class="flex-none"> 1749 <button type="reset" name="reset" class="app-btn-sec"> 1750 <div> 1751 <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"> 1752 <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" /> 1753 </svg> 1754 </div> 1755 </button> 1756 </div> 1757 <div class="grow"> 1758 <button type="submit" name="submit" class="app-btn-pri"> 1759 <div> 1760 <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"> 1761 <path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /> 1762 </svg> 1763 </div> 1764 <div class="pl-2"> 1765 <span>Verify</span> 1766 </div> 1767 </button> 1768 </div> 1769 </div> 1770 </form> 1771 </div> 1772 {{ else if eq .Data.view "mfa_u2f_auth" }} 1773 <div> 1774 <form id="mfa-u2f-auth-form" class="space-y-6" 1775 action="{{ pathjoin .ActionEndpoint "sandbox" .Data.id "mfa-u2f-auth" }}" 1776 method="POST" 1777 autocomplete="off" 1778 > 1779 <input id="webauthn_request" name="webauthn_request" type="hidden" value="" /> 1780 <input id="sandbox_id" name="sandbox_id" type="hidden" value="{{ .Data.id }}" /> 1781 <div class="app-txt-section"> 1782 <p>Insert your hardware token into a USB port. When prompted, touch, 1783 or otherwise trigger the hardware token.</p> 1784 </div> 1785 </form> 1786 <div id="mfa-u2f-auth-form-rst" class="pt-4 hidden"> 1787 <a href="{{ pathjoin .ActionEndpoint "sandbox" .Data.id }}"> 1788 <button type="button" name="button" class="app-btn-pri"> 1789 <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"> 1790 <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" /> 1791 </svg> 1792 <div class="pl-2"> 1793 <span>Try Again</span> 1794 </div> 1795 </button> 1796 </a> 1797 </div> 1798 </div> 1799 {{ else if eq .Data.view "mfa_app_register" }} 1800 <div> 1801 <form class="mfa-add-app-form" 1802 action="{{ pathjoin .ActionEndpoint "sandbox" .Data.id "mfa-app-register" }}" 1803 method="POST" 1804 autocomplete="off" 1805 > 1806 <div id="token-params"> 1807 <div class="app-txt-section"> 1808 <p><b>Step 1</b>: If necessary, amend the label and comment associated with the authenticator. 1809 The label is what you would see in your authenticator app. 1810 The comment is what you would see in this portal. 1811 </p> 1812 </div> 1813 1814 <div> 1815 <label for="label" class="app-inp-lbl">Name</label> 1816 <div class="app-inp-box"> 1817 <div class="app-inp-prf-img"> 1818 <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"> 1819 <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" /> 1820 </svg> 1821 </div> 1822 <input id="label" name="label" type="text" 1823 class="app-inp-txt validate" 1824 value="{{ .Data.mfa_label }}" pattern="[A-Za-z0-9]{4,25}" maxlength="25" 1825 title="Name should contain 4-25 characters and consists of A-Z, a-z, 0-9 characters." 1826 autocorrect="off" autocapitalize="off" spellcheck="false" autocomplete="off" 1827 required /> 1828 </div> 1829 </div> 1830 1831 <div class="pt-4"> 1832 <label for="comment" class="app-inp-lbl">Comment</label> 1833 <div class="app-inp-box"> 1834 <div class="app-inp-prf-img"> 1835 <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"> 1836 <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" /> 1837 </svg> 1838 </div> 1839 <input id="comment" name="comment" type="text" 1840 class="app-inp-txt validate" 1841 value="{{ .Data.mfa_comment }}" pattern="[A-Za-z0-9 -]{4,25}" maxlength="50" 1842 title="Comment should contain 4-50 characters and consists of A-Z, a-z, 0-9, space, and dash characters." 1843 autocorrect="off" autocapitalize="off" spellcheck="false" autocomplete="off" 1844 required /> 1845 </div> 1846 </div> 1847 1848 <div class="app-txt-section"> 1849 <p><b>Step 1a</b> (<i>optional</i>): If necessary, click 1850 <a class="text-secondary-500 hover:text-primary-500" href="#advanced-setup-all" 1851 onclick="toggleAdvancedSetupMode(); return false;">here</a> 1852 to customize default values. 1853 </p> 1854 </div> 1855 1856 <div id="advanced-setup-all" class="app-txt-section hidden"> 1857 <div class="pt-4"> 1858 <label for="secret" class="app-inp-lbl">Token Secret</label> 1859 <div class="app-inp-box"> 1860 <div class="app-inp-prf-img"> 1861 <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"> 1862 <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" /> 1863 </svg> 1864 </div> 1865 <input id="secret" name="secret" type="text" 1866 class="app-inp-txt validate" 1867 value="{{ .Data.mfa_secret }}" pattern="[A-Za-z0-9]{10,100}" maxlength="100" 1868 title="Token secret should contain 10-200 characters and consists of A-Z and 0-9 characters only." 1869 autocorrect="off" autocapitalize="off" spellcheck="false" autocomplete="off" 1870 required /> 1871 </div> 1872 </div> 1873 <div class="app-inp-box"> 1874 <select id="period" name="period" class="app-inp-sel"> 1875 <option value="15" {{ if eq .Data.mfa_period "15" }} selected{{ end }}>15 Seconds Lifetime</option> 1876 <option value="30" {{ if eq .Data.mfa_period "30" }} selected{{ end }}>30 Seconds Lifetime</option> 1877 <option value="60" {{ if eq .Data.mfa_period "60" }} selected{{ end }}>60 Seconds Lifetime</option> 1878 <option value="90" {{ if eq .Data.mfa_period "90" }} selected{{ end }}>90 Seconds Lifetime</option> 1879 </select> 1880 </div> 1881 <div class="app-inp-box"> 1882 <select id="digits" name="digits" class="app-inp-sel"> 1883 <option value="4" {{ if eq .Data.mfa_digits "4" }} selected{{ end }}>4 Digit Code</option> 1884 <option value="6" {{ if eq .Data.mfa_digits "6" }} selected{{ end }}>6 Digit Code</option> 1885 <option value="8" {{ if eq .Data.mfa_digits "8" }} selected{{ end }}>8 Digit Code</option> 1886 </select> 1887 </div> 1888 </div> 1889 1890 <div class="app-txt-section"> 1891 <p><b>Step 2</b>: Open your MFA authenticator application, e.g. Microsoft/Google Authenticator, Authy, etc., 1892 add new entry and click the "Get QR" link. 1893 </p> 1894 <div id="mfa-get-qr-code" class="text-center"> 1895 <a class="text-secondary-500 hover:text-primary-500" href="#qr-code-mode" onclick="getQRCode()">Get QR Code</a> 1896 </div> 1897 </div> 1898 </div> 1899 1900 <div id="mfa-qr-code" class="hidden"> 1901 <div id="mfa-qr-code-image" class="flex items-center justify-center"> 1902 <img src="{{ pathjoin .ActionEndpoint "sandbox" .Data.id "mfa-app-barcode" .Data.code_uri_encoded }}.png" alt="QR Code" /> 1903 </div> 1904 <div class="app-txt-section"> 1905 <p>» Can't scan? Click or copy the link below.</p> 1906 </div> 1907 <div id="mfa-no-camera-link" class="app-txt-section text-center"> 1908 <a class="text-secondary-500 hover:text-primary-500" href="{{ .Data.code_uri }}">No Camera Link</a> 1909 </div> 1910 1911 <div class="app-txt-section"> 1912 <p><b>Step 3</b>: Enter the authentication code you see in the app and click "Add".</p> 1913 </div> 1914 1915 <input id="email" name="email" type="hidden" value="{{ .Data.mfa_email }}" /> 1916 <input id="type" name="type" type="hidden" value="{{ .Data.mfa_type }}" /> 1917 <input id="barcode_uri" name "barcode_uri" type="hidden" value="{{ pathjoin .ActionEndpoint "sandbox" .Data.id "mfa-app-barcode" }}" /> 1918 1919 <div class="py-4"> 1920 <label for="passcode" class="app-inp-lbl">Passcode</label> 1921 <div class="app-inp-box"> 1922 <input id="passcode" name="passcode" type="text" 1923 class="font-['Montserrat'] app-inp-code-txt validate" 1924 pattern="[0-9]{4,8}" maxlength="8" 1925 title="Authentication code should contain 4-8 characters and consists of 0-9 characters." 1926 autocorrect="off" autocapitalize="off" spellcheck="false" autocomplete="off" 1927 required /> 1928 </div> 1929 </div> 1930 1931 <div class="flex gap-4"> 1932 <div class="grow"> 1933 <button type="submit" name="submit" class="app-btn-pri"> 1934 <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"> 1935 <path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4" /> 1936 </svg> 1937 <div class="pl-2"> 1938 <span>Add</span> 1939 </div> 1940 </button> 1941 </div> 1942 </div> 1943 </div> 1944 1945 </form> 1946 </div> 1947 {{ else if eq .Data.view "mfa_u2f_register" }} 1948 <div> 1949 <form id="mfa-add-u2f-form" class="space-y-6" 1950 action="{{ pathjoin .ActionEndpoint "sandbox" .Data.id "mfa-u2f-register" }}" 1951 method="POST" 1952 autocomplete="off" 1953 > 1954 <div class="space-y-6 text-lg leading-7 text-primary-600"> 1955 <p>Please insert your U2F (USB, NFC, or Bluetooth) Security Key, e.g. Yubikey.</p> 1956 <p>Then, please click "Register" button below.</p> 1957 </div> 1958 <input class="hidden" id="webauthn_register" name="webauthn_register" type="text" /> 1959 <input class="hidden" id="webauthn_challenge" name="webauthn_challenge" type="text" value="{{ .Data.webauthn_challenge }}" /> 1960 1961 <div> 1962 <label for="comment" class="app-inp-lbl">Name your token (optional)</label> 1963 <div class="app-inp-box"> 1964 <div class="app-inp-prf-img"> 1965 <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"> 1966 <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" /> 1967 </svg> 1968 </div> 1969 <input id="comment" name="comment" type="text" 1970 class="app-inp-txt validate" 1971 pattern="[A-Za-z0-9 -]{4,25}" maxlength="25" 1972 title="A comment should contain 4-25 characters and consists of A-Z, a-z, 0-9, space, and dash characters." 1973 autocorrect="off" autocapitalize="off" spellcheck="false" autocomplete="off" /> 1974 </div> 1975 </div> 1976 1977 <div class="flex gap-4"> 1978 <div class="flex-none"> 1979 <a href="{{ pathjoin .ActionEndpoint "sandbox" .Data.id "terminate" }}"> 1980 <button type="button" class="app-btn-sec"> 1981 <div> 1982 <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"> 1983 <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" /> 1984 </svg> 1985 </div> 1986 </button> 1987 </a> 1988 </div> 1989 <div class="flex-none"> 1990 <button type="reset" name="reset" class="app-btn-sec"> 1991 <div> 1992 <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"> 1993 <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" /> 1994 </svg> 1995 </div> 1996 </button> 1997 </div> 1998 1999 <div class="grow"> 2000 <button id="mfa-add-u2f-button" type="button" name="action" class="app-btn-pri" 2001 onclick="u2f_token_register('mfa-add-u2f-form', 'mfa-add-u2f-button'); return false;"> 2002 <div> 2003 <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"> 2004 <path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" /> 2005 </svg> 2006 </div> 2007 <div class="pl-2"> 2008 <span>Register</span> 2009 </div> 2010 </button> 2011 </div> 2012 </div> 2013 </form> 2014 2015 <div id="mfa-add-u2f-form-rst" class="hidden"> 2016 <a href="{{ pathjoin .ActionEndpoint "sandbox" .Data.id }}"> 2017 <button type="button" name="button" class="app-btn-pri"> 2018 <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"> 2019 <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" /> 2020 </svg> 2021 <div class="pl-2"> 2022 <span>Try Again</span> 2023 </div> 2024 </button> 2025 </a> 2026 </div> 2027 </div> 2028 {{ else if eq .Data.view "terminate" }} 2029 <div class="app-txt-section"> 2030 <p>{{ .Data.error }}.</p> 2031 </div> 2032 <div class="flex gap-4"> 2033 <div class="grow"> 2034 <a href="{{ pathjoin .ActionEndpoint "login" }}"> 2035 <button type="button" class="app-btn-pri"> 2036 <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"> 2037 <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" /> 2038 </svg> 2039 <div class="pl-2"> 2040 <span>Start Over</span> 2041 </div> 2042 </button> 2043 </a> 2044 </div> 2045 </div> 2046 {{ else if eq .Data.view "error" }} 2047 <div class="app-txt-section"> 2048 <p>Your session failed to meet authorization requirements.</p> 2049 <p>{{ .Data.error }}.</p> 2050 </div> 2051 <div class="flex gap-4"> 2052 <div class="grow"> 2053 <a href="{{ pathjoin .ActionEndpoint "sandbox" .Data.id }}"> 2054 <button type="button" class="app-btn-pri"> 2055 <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"> 2056 <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" /> 2057 </svg> 2058 <div class="pl-2"> 2059 <span>Try Again</span> 2060 </div> 2061 </button> 2062 </a> 2063 </div> 2064 </div> 2065 {{ else }} 2066 <div class="app-txt-section"> 2067 <p>The {{ .Data.view }} view is unsupported.</p> 2068 </div> 2069 {{ end }} 2070 2071 </div> 2072 </div> 2073 </div> 2074 2075 <!-- Optional JavaScript --> 2076 <script src="{{ pathjoin .ActionEndpoint "/assets/js/sandbox.js" }}"></script> 2077 2078 {{ if eq .Data.ui_options.custom_js_required "yes" }} 2079 <script src="{{ pathjoin .ActionEndpoint "/assets/js/custom.js" }}"></script> 2080 {{ end }} 2081 {{ if eq .Data.view "mfa_app_register" }} 2082 <!-- App Authentication Registration Scripts --> 2083 <script src="{{ pathjoin .ActionEndpoint "/assets/js/sandbox_mfa_add_app.js" }}"></script> 2084 {{ end }} 2085 {{ if or (eq .Data.view "mfa_u2f_register") (eq .Data.view "mfa_u2f_auth") }} 2086 <!-- U2F Authentication Scripts --> 2087 <script src="{{ pathjoin .ActionEndpoint "/assets/cbor/cbor.js" }}"></script> 2088 <script src="{{ pathjoin .ActionEndpoint "/assets/js/sandbox_mfa_u2f.js" }}"></script> 2089 {{ end }} 2090 2091 {{ if eq .Data.view "mfa_u2f_register" }} 2092 <script> 2093 function u2f_token_register(formID, btnID) { 2094 const params = { 2095 challenge: "{{ .Data.webauthn_challenge }}", 2096 rp_name: "{{ .Data.webauthn_rp_name }}", 2097 user_id: "{{ .Data.webauthn_user_id }}", 2098 user_name: "{{ .Data.webauthn_user_email }}", 2099 user_display_name: "{{ .Data.webauthn_user_display_name }}", 2100 user_verification: "{{ .Data.webauthn_user_verification }}", 2101 attestation: "{{ .Data.webauthn_attestation }}", 2102 }; 2103 register_u2f_token(formID, btnID, params); 2104 } 2105 </script> 2106 {{ end }} 2107 {{ if eq .Data.view "mfa_u2f_auth" }} 2108 <script> 2109 function u2f_token_authenticate(formID) { 2110 const params = { 2111 challenge: "{{ .Data.webauthn_challenge }}", 2112 timeout: {{ .Data.webauthn_timeout }}, 2113 rp_name: "{{ .Data.webauthn_rp_name }}", 2114 user_verification: "{{ .Data.webauthn_user_verification }}", 2115 {{- if .Data.webauthn_credentials }} 2116 allowed_credentials: [ 2117 {{- range .Data.webauthn_credentials }} 2118 { 2119 id: "{{ .id }}", 2120 type: "{{ .type }}", 2121 transports: [{{ range .transports }}"{{ . }}",{{ end }}], 2122 }, 2123 {{- end }} 2124 ], 2125 {{ else }} 2126 allowed_credentials: [], 2127 {{end -}} 2128 ext_uvm: {{ .Data.webauthn_ext_uvm }}, 2129 ext_loc: {{ .Data.webauthn_ext_loc }}, 2130 ext_tx_auth_simple: "{{ .Data.webauthn_tx_auth_simple }}", 2131 }; 2132 authenticate_u2f_token(formID, params); 2133 } 2134 2135 window.addEventListener("load", u2f_token_authenticate('mfa-u2f-auth-form')); 2136 </script> 2137 {{ end }} 2138 {{ if .Message }} 2139 <script> 2140 var toastHTML = '<span>{{ .Message }}</span><button class="btn-flat toast-action" onclick="M.Toast.dismissAll();">Close</button>'; 2141 toastElement = M.toast({ 2142 html: toastHTML, 2143 classes: 'toast-error' 2144 }); 2145 const appContainer = document.querySelector('.app-card-container') 2146 appContainer.prepend(toastElement.el) 2147 </script> 2148 {{ end }} 2149 </body> 2150 </html>`, 2151 "basic/apps_sso": `<!DOCTYPE html> 2152 <html lang="en" class="h-full bg-blue-100"> 2153 <head> 2154 <title>{{ .MetaTitle }} - {{ .PageTitle }}</title> 2155 <!-- Required meta tags --> 2156 <meta charset="utf-8" /> 2157 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> 2158 <meta name="description" content="{{ .MetaDescription }}" /> 2159 <meta name="author" content="{{ .MetaAuthor }}" /> 2160 <link rel="shortcut icon" href="{{ pathjoin .ActionEndpoint "/assets/images/favicon.png" }}" type="image/png" /> 2161 <link rel="icon" href="{{ pathjoin .ActionEndpoint "/assets/images/favicon.png" }}" type="image/png" /> 2162 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/google-webfonts/roboto.css" }}" /> 2163 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/line-awesome/line-awesome.css" }}" /> 2164 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/css/apps_sso.css" }}" /> 2165 {{ if eq .Data.ui_options.custom_css_required "yes" }} 2166 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/css/custom.css" }}" /> 2167 {{ end }} 2168 </head> 2169 2170 <body class="h-full"> 2171 <div class="app-page"> 2172 <div class="app-content"> 2173 <div class="app-container"> 2174 <div class="logo-col-box justify-center"> 2175 {{ if .LogoURL }} 2176 <div> 2177 <img class="logo-img" src="{{ .LogoURL }}" alt="{{ .LogoDescription }}" /> 2178 </div> 2179 {{ end }} 2180 <div> 2181 <h2 class="logo-col-txt">{{ .PageTitle }}</h2> 2182 </div> 2183 </div> 2184 2185 {{ if gt .Data.role_count 0 }} 2186 <div class="pb-4 pt-4"> 2187 <p class="app-inp-lbl">Assume any of the following roles on the associated AWS accounts by clicking the name of the role.</p> 2188 </div> 2189 2190 <div class="flex flex-col"> 2191 <div class="-my-2 -mx-4 overflow-x-auto sm:-mx-6 lg:-mx-8"> 2192 <div class="inline-block min-w-full py-2 align-middle md:px-6 lg:px-8"> 2193 <table class="min-w-full divide-y divide-gray-300"> 2194 <thead> 2195 <tr> 2196 <th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-primary-700 sm:pl-6 md:pl-0">Role Name</th> 2197 <th scope="col" class="py-3.5 px-3 text-left text-sm font-semibold text-primary-700">Account ID</th> 2198 </tr> 2199 </thead> 2200 <tbody class="divide-y divide-gray-200"> 2201 {{ range .Data.roles }} 2202 <tr> 2203 <td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-primary-700 sm:pl-6 md:pl-0 leading-none"> 2204 <a href="{{ pathjoin $.ActionEndpoint "/apps/sso" .ProviderName "assume" .AccountID .Name }}">{{ brsplitline .Name }}</a> 2205 </td> 2206 <td class="whitespace-nowrap py-4 px-3 text-sm text-primary-500">{{ .AccountID }}</td> 2207 </tr> 2208 {{ end }} 2209 </tbody> 2210 </table> 2211 </div> 2212 </div> 2213 </div> 2214 {{ else }} 2215 <div class="pb-4 pt-4"> 2216 <p class="app-inp-lbl">Your user identity has no roles associated with AWS accounts.</p> 2217 </div> 2218 {{ end }} 2219 2220 2221 <div class="flex flex-wrap {{ if gt .Data.role_count 0 }}pt-6{{ end }} justify-center gap-4"> 2222 <div id="forgot_username_link"> 2223 <a class="text-primary-600" href="{{ pathjoin .ActionEndpoint "/portal" }}"> 2224 <i class="las la-layer-group"></i> 2225 <span class="text-lg">Portal</span> 2226 </a> 2227 </div> 2228 <div id="contact_support_link"> 2229 <a class="text-primary-600" href="{{ pathjoin .ActionEndpoint "/logout" }}"> 2230 <i class="las la-times-circle"></i> 2231 <span class="text-lg">Sign Out</span> 2232 </a> 2233 </div> 2234 </div> 2235 </div> 2236 </div> 2237 </div> 2238 <!-- JavaScript --> 2239 <script src="{{ pathjoin .ActionEndpoint "/assets/js/apps_sso.js" }}"></script> 2240 {{ if eq .Data.ui_options.custom_js_required "yes" }} 2241 <script src="{{ pathjoin .ActionEndpoint "/assets/js/custom.js" }}"></script> 2242 {{ end }} 2243 </body> 2244 </html>`, 2245 "basic/apps_mobile_access": `<!DOCTYPE html> 2246 <html lang="en" class="h-full bg-blue-100"> 2247 <head> 2248 <title>{{ .MetaTitle }} - {{ .PageTitle }}</title> 2249 <!-- Required meta tags --> 2250 <meta charset="utf-8" /> 2251 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> 2252 <meta name="description" content="{{ .MetaDescription }}" /> 2253 <meta name="author" content="{{ .MetaAuthor }}" /> 2254 <link rel="shortcut icon" href="{{ pathjoin .ActionEndpoint "/assets/images/favicon.png" }}" type="image/png" /> 2255 <link rel="icon" href="{{ pathjoin .ActionEndpoint "/assets/images/favicon.png" }}" type="image/png" /> 2256 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/google-webfonts/roboto.css" }}" /> 2257 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/line-awesome/line-awesome.css" }}" /> 2258 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/css/apps_mobile_access.css" }}" /> 2259 {{ if eq .Data.ui_options.custom_css_required "yes" }} 2260 <link rel="stylesheet" href="{{ pathjoin .ActionEndpoint "/assets/css/custom.css" }}" /> 2261 {{ end }} 2262 </head> 2263 2264 <body class="h-full"> 2265 <div class="app-page"> 2266 <div class="app-content"> 2267 <div class="app-container"> 2268 <div class="logo-col-box"> 2269 {{ if .LogoURL }} 2270 <div> 2271 <img class="logo-img" src="{{ .LogoURL }}" alt="{{ .LogoDescription }}" /> 2272 </div> 2273 {{ end }} 2274 <div> 2275 <h2 class="logo-col-txt">{{ .PageTitle }}</h2> 2276 </div> 2277 </div> 2278 <div> 2279 <p class="app-inp-lbl">Scan the below QR code and follow the link to perform one-time passwordless login.</p> 2280 </div> 2281 2282 <div class="flex flex-wrap pt-6 justify-center gap-4"> 2283 <div id="forgot_username_link"> 2284 <a class="text-primary-600" href="{{ pathjoin .ActionEndpoint "/portal" }}"> 2285 <i class="las la-layer-group"></i> 2286 <span class="text-lg">Portal</span> 2287 </a> 2288 </div> 2289 <div id="contact_support_link"> 2290 <a class="text-primary-600" href="{{ pathjoin .ActionEndpoint "/logout" }}"> 2291 <i class="las la-times-circle"></i> 2292 <span class="text-lg">Sign Out</span> 2293 </a> 2294 </div> 2295 </div> 2296 </div> 2297 </div> 2298 </div> 2299 <!-- JavaScript --> 2300 <script src="{{ pathjoin .ActionEndpoint "/assets/js/apps_mobile_access.js" }}"></script> 2301 {{ if eq .Data.ui_options.custom_js_required "yes" }} 2302 <script src="{{ pathjoin .ActionEndpoint "/assets/js/custom.js" }}"></script> 2303 {{ end }} 2304 </body> 2305 </html>`, 2306 }