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>&raquo; 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>&raquo; 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>&raquo; 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  }