github.com/greenpau/go-authcrunch@v1.1.4/assets/portal/templates/basic/sandbox.template (about)

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