Ctrl K
ComunidadGuías…

view/login.html

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
{{define "content"}}
<form class="auth-card" method="POST" action="{{authURL "/login"}}">
  <div class="auth-header">
    {{template "logo" .}}
    <h1 class="auth-title">{{.ThemeTitle}}</h1>
    {{if .ThemeTagline}}<p class="auth-tagline">{{.ThemeTagline}}</p>{{end}}
  </div>
  {{ .csrfField }}
  <input type="hidden" name="state" value="{{.State}}" />
  <input type="hidden" name="redirect_uri" value="{{.RedirectURI}}" />
  <input type="hidden" name="client_id" value="{{.ClientID}}" />
  <input type="hidden" name="scope" value="{{.Scope}}" />
  <input type="hidden" name="nonce" value="{{.Nonce}}" />
  <input type="hidden" name="code_challenge" value="{{.CodeChallenge}}" />
  <input type="hidden" name="code_challenge_method" value="{{.CodeChallengeMethod}}" />
  <input type="hidden" name="prompt" value="{{.Prompt}}" />
  <input type="hidden" name="max_age" value="{{.MaxAge}}" />
  <input type="hidden" name="authorize_sig" value="{{.AuthorizeSig}}" />
  {{if not .HideUsernameField}}
  <div class="auth-field">
    {{if eq .ProfileFieldEmail "is_username"}}
    <label class="auth-label" for="username">Email</label>
    <input class="auth-input" type="email" id="username" name="username" required autocomplete="{{if eq .PasskeyLoginMode "conditional"}}username webauthn{{else}}email{{end}}" />
    {{else}}
    <label class="auth-label" for="username">Username</label>
    <input class="auth-input" type="text" id="username" name="username" required autocomplete="{{if eq .PasskeyLoginMode "conditional"}}username webauthn{{else}}username{{end}}" />
    {{end}}
  </div>
  {{end}}
  {{if not .HidePasswordField}}
  <div class="auth-field">
    <label class="auth-label" for="password">Password</label>
    <input class="auth-input" type="password" id="password" name="password" {{if ne .AuthMode "password_and_passkey"}}required{{end}} autocomplete="current-password" />
  </div>
  {{end}}
  {{if .Error}}
  <div class="auth-error" role="alert" id="login-error">{{.Error}}</div>
  {{else}}
  <div class="auth-error" role="alert" id="login-error" style="display:none"></div>
  {{end}}
  {{if .ShowLoginButton}}
  <button class="auth-btn" type="submit">Log In</button>
  {{end}}
  {{if and .ShowLoginButton (or (ne .AuthMode "password") .MagicLinkEnabled .FederatedProviders)}}
  <div class="auth-divider">or</div>
  {{end}}
  {{if ne .AuthMode "password"}}
  <button type="button" class="{{if .ShowLoginButton}}auth-btn-secondary{{else}}auth-btn{{end}}" id="passkey-login-btn">
    <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
      <circle cx="8" cy="8" r="5"/><path d="m13 13 9 9"/><path d="M13 13l-1.5-1.5"/><path d="m16 16 2-2"/><path d="m19 13 2-2"/>
    </svg>
    Sign in with passkey
  </button>
  {{end}}
  {{if .MagicLinkEnabled}}
  <a href="{{authURL "/magic-link"}}?client_id={{.ClientID}}&redirect_uri={{.RedirectURI}}&state={{.State}}&scope={{.Scope}}&nonce={{.Nonce}}&code_challenge={{.CodeChallenge}}&code_challenge_method={{.CodeChallengeMethod}}&prompt={{.Prompt}}&max_age={{.MaxAge}}" class="auth-btn-secondary">
    <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
      <rect width="20" height="16" x="2" y="4" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/>
    </svg>
    Sign in with email link
  </a>
  {{end}}
  {{if .FederatedProviders}}
  {{range .FederatedProviders}}
  <a href="{{authURL "/federation/"}}{{.ID}}?state={{$.State}}&redirect_uri={{$.RedirectURI}}&client_id={{$.ClientID}}&scope={{$.Scope}}&nonce={{$.Nonce}}&code_challenge={{$.CodeChallenge}}&code_challenge_method={{$.CodeChallengeMethod}}&prompt={{$.Prompt}}&max_age={{$.MaxAge}}" class="auth-btn-secondary">
    {{if .HasIcon}}<img src="{{authURL "/federation/"}}{{.ID}}/icon.svg" alt="" width="20" height="20" class="federation-icon" />{{end}}
    Sign in with {{.Name}}
  </a>
  {{end}}
  {{end}}
  {{if or .AllowSelfSignup (and .SmtpConfigured (not .HidePasswordField))}}
  <div class="auth-links">
    {{if and .SmtpConfigured (not .HidePasswordField)}}
    <div class="auth-link"><a href="{{authURL "/forgot-password"}}?client_id={{.ClientID}}&redirect_uri={{.RedirectURI}}&state={{.State}}&scope={{.Scope}}&nonce={{.Nonce}}&code_challenge={{.CodeChallenge}}&code_challenge_method={{.CodeChallengeMethod}}&prompt={{.Prompt}}&max_age={{.MaxAge}}">Forgot password?</a></div>
    {{end}}
    {{if .AllowSelfSignup}}
    <div class="auth-link" style="margin-left:auto;"><a href="{{.SignupURL}}">Create account</a></div>
    {{end}}
  </div>
  {{end}}
</form>
{{end}}

{{define "scripts"}}
{{if ne .AuthMode "password"}}<script nonce="{{.CspNonce}}" src="{{authURL "/static/passkey.js"}}"></script>{{end}}
<script nonce="{{.CspNonce}}">
  var passkeyAbortController = null;
  function showPasskeyError(msg) {
    const el = document.getElementById('login-error');
    el.textContent = msg;
    el.style.display = '';
  }
  async function startPasskeyLogin() {
    const username = document.getElementById('username').value.trim();
    if (!username) { showPasskeyError('Please enter your {{if eq .ProfileFieldEmail "is_username"}}email{{else}}username{{end}} first.'); return; }
    const params = new URLSearchParams({
      username,
      redirect_uri: document.querySelector('[name=redirect_uri]').value,
      state: document.querySelector('[name=state]').value,
      client_id: document.querySelector('[name=client_id]').value,
      scope: document.querySelector('[name=scope]').value,
      nonce: document.querySelector('[name=nonce]').value,
      code_challenge: document.querySelector('[name=code_challenge]').value,
      code_challenge_method: document.querySelector('[name=code_challenge_method]').value,
      prompt: document.querySelector('[name=prompt]').value,
      max_age: document.querySelector('[name=max_age]').value,
      authorize_sig: document.querySelector('[name=authorize_sig]').value,
    });
    try {
      const beginResp = await fetch('{{authURL "/passkey/login/begin"}}?' + params.toString());
      const data = await beginResp.json();
      if (!beginResp.ok) { showPasskeyError(data.error || 'Passkey login failed'); return; }
      await doPasskeyAuth(data, '{{authURL ""}}');
    } catch (e) { showPasskeyError('Passkey error: ' + e.message); }
  }
  async function startDiscoverableLogin(mediation) {
    if (passkeyAbortController) { passkeyAbortController.abort(); }
    passkeyAbortController = new AbortController();
    var params = new URLSearchParams({
      redirect_uri: document.querySelector('[name=redirect_uri]').value,
      state: document.querySelector('[name=state]').value,
      client_id: document.querySelector('[name=client_id]').value,
      scope: document.querySelector('[name=scope]').value,
      nonce: document.querySelector('[name=nonce]').value,
      code_challenge: document.querySelector('[name=code_challenge]').value,
      code_challenge_method: document.querySelector('[name=code_challenge_method]').value,
      prompt: document.querySelector('[name=prompt]').value,
      max_age: document.querySelector('[name=max_age]').value,
      authorize_sig: document.querySelector('[name=authorize_sig]').value,
    });
    if (mediation === 'conditional') { params.set('mediation', 'conditional'); }
    try {
      var beginResp = await fetch('{{authURL "/passkey/discoverable/begin"}}?' + params.toString());
      var data = await beginResp.json();
      if (!beginResp.ok) { showPasskeyError(data.error || 'Passkey login failed'); return; }
      await doDiscoverablePasskeyAuth(data, '{{authURL ""}}', mediation, passkeyAbortController.signal);
    } catch (e) {
      if (e.name !== 'AbortError') { showPasskeyError('Passkey error: ' + e.message); }
    }
  }
  (function() {
    var btn = document.getElementById('passkey-login-btn');
    {{if eq .PasskeyLoginMode "username_first"}}
    if (btn) { btn.addEventListener('click', startPasskeyLogin); }
    {{else}}
    if (btn) { btn.addEventListener('click', function() { startDiscoverableLogin(); }); }
    {{end}}
    {{if eq .PasskeyLoginMode "conditional"}}
    if (window.PublicKeyCredential && typeof PublicKeyCredential.isConditionalMediationAvailable === 'function') {
      PublicKeyCredential.isConditionalMediationAvailable().then(function(available) {
        if (available) { startDiscoverableLogin('conditional'); }
      });
    }
    var form = document.querySelector('form.auth-card');
    if (form) {
      form.addEventListener('submit', function() {
        if (passkeyAbortController) { passkeyAbortController.abort(); }
      });
    }
    {{end}}
  })();
</script>
{{end}}