view/signup.html
Rama main · réplica de lectura del repositorio original.
{{define "content"}}
<form class="auth-card" method="POST" action="{{authURL "/signup"}}" data-auth-mode="{{.AuthMode}}" id="signup-form">
<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}}" />
<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="email" />
{{else}}
<label class="auth-label" for="username">Username</label>
<input class="auth-input" type="text" id="username" name="username" required autocomplete="username" />
{{end}}
</div>
{{if or (and .RequireEmailVerification (ne .ProfileFieldEmail "is_username")) (eq .ProfileFieldEmail "required") (and (eq .ProfileFieldEmail "optional") .ShowOptionalFields)}}
<div class="auth-field">
<label class="auth-label" for="email">Email{{if or .RequireEmailVerification (eq .ProfileFieldEmail "required")}} <span style="color:var(--color-danger-bg)">*</span>{{end}}</label>
<input class="auth-input" type="email" id="email" name="email" autocomplete="email" {{if or .RequireEmailVerification (eq .ProfileFieldEmail "required")}}required{{end}} />
</div>
{{end}}
{{if or (eq .ProfileFieldGivenName "required") (and (eq .ProfileFieldGivenName "optional") .ShowOptionalFields)}}
<div class="auth-field">
<label class="auth-label" for="given_name">First Name{{if eq .ProfileFieldGivenName "required"}} <span style="color:var(--color-danger-bg)">*</span>{{end}}</label>
<input class="auth-input" type="text" id="given_name" name="given_name" autocomplete="given-name" {{if eq .ProfileFieldGivenName "required"}}required{{end}} />
</div>
{{end}}
{{if or (eq .ProfileFieldFamilyName "required") (and (eq .ProfileFieldFamilyName "optional") .ShowOptionalFields)}}
<div class="auth-field">
<label class="auth-label" for="family_name">Last Name{{if eq .ProfileFieldFamilyName "required"}} <span style="color:var(--color-danger-bg)">*</span>{{end}}</label>
<input class="auth-input" type="text" id="family_name" name="family_name" autocomplete="family-name" {{if eq .ProfileFieldFamilyName "required"}}required{{end}} />
</div>
{{end}}
{{if or (eq .ProfileFieldPhone "required") (and (eq .ProfileFieldPhone "optional") .ShowOptionalFields)}}
<div class="auth-field">
<label class="auth-label" for="phone_number">Phone Number{{if eq .ProfileFieldPhone "required"}} <span style="color:var(--color-danger-bg)">*</span>{{end}}</label>
<input class="auth-input" type="tel" id="phone_number" name="phone_number" autocomplete="tel" {{if eq .ProfileFieldPhone "required"}}required{{end}} />
</div>
{{end}}
{{if or (eq .ProfileFieldPicture "required") (and (eq .ProfileFieldPicture "optional") .ShowOptionalFields)}}
<div class="auth-field">
<label class="auth-label" for="picture">Profile Picture URL{{if eq .ProfileFieldPicture "required"}} <span style="color:var(--color-danger-bg)">*</span>{{end}}</label>
<input class="auth-input" type="url" id="picture" name="picture" autocomplete="photo" {{if eq .ProfileFieldPicture "required"}}required{{end}} />
</div>
{{end}}
{{if or (eq .ProfileFieldAddress "required") (and (eq .ProfileFieldAddress "optional") .ShowOptionalFields)}}
<div class="auth-field">
<label class="auth-label" for="address_street">Street Address{{if eq .ProfileFieldAddress "required"}} <span style="color:var(--color-danger-bg)">*</span>{{end}}</label>
<input class="auth-input" type="text" id="address_street" name="address_street" autocomplete="street-address" {{if eq .ProfileFieldAddress "required"}}required{{end}} />
</div>
<div class="auth-field">
<label class="auth-label" for="address_locality">City{{if eq .ProfileFieldAddress "required"}} <span style="color:var(--color-danger-bg)">*</span>{{end}}</label>
<input class="auth-input" type="text" id="address_locality" name="address_locality" autocomplete="address-level2" {{if eq .ProfileFieldAddress "required"}}required{{end}} />
</div>
<div class="auth-field">
<label class="auth-label" for="address_region">State / Region{{if eq .ProfileFieldAddress "required"}} <span style="color:var(--color-danger-bg)">*</span>{{end}}</label>
<input class="auth-input" type="text" id="address_region" name="address_region" autocomplete="address-level1" {{if eq .ProfileFieldAddress "required"}}required{{end}} />
</div>
<div class="auth-field">
<label class="auth-label" for="address_postal_code">Postal Code{{if eq .ProfileFieldAddress "required"}} <span style="color:var(--color-danger-bg)">*</span>{{end}}</label>
<input class="auth-input" type="text" id="address_postal_code" name="address_postal_code" autocomplete="postal-code" {{if eq .ProfileFieldAddress "required"}}required{{end}} />
</div>
<div class="auth-field">
<label class="auth-label" for="address_country">Country{{if eq .ProfileFieldAddress "required"}} <span style="color:var(--color-danger-bg)">*</span>{{end}}</label>
<input class="auth-input" type="text" id="address_country" name="address_country" autocomplete="country-name" {{if eq .ProfileFieldAddress "required"}}required{{end}} />
</div>
{{end}}
{{if ne .AuthMode "passkey_only"}}
<div id="password-fields">
<div class="auth-field">
<label class="auth-label" for="password">Password</label>
<input class="auth-input" type="password" id="password" name="password" required autocomplete="new-password" />
</div>
<div class="auth-field">
<label class="auth-label" for="confirm_password">Confirm Password</label>
<input class="auth-input" type="password" id="confirm_password" name="confirm_password" required autocomplete="new-password" />
</div>
</div>
{{end}}
{{if eq .AuthMode "password_and_passkey"}}
<div class="auth-checkbox">
<input type="checkbox" id="use-passkey" />
<label for="use-passkey">Use passkey instead of password</label>
</div>
{{end}}
{{if .Error}}
<div class="auth-error" role="alert" id="signup-error">{{.Error}}</div>
{{else}}
<div class="auth-error" role="alert" id="signup-error" style="display:none"></div>
{{end}}
<button class="auth-btn" type="submit" id="signup-btn">
{{if eq .AuthMode "passkey_only"}}Signup with Passkey{{else}}Create Account{{end}}
</button>
<div class="auth-link">Already have an account? <a href="{{authURL "/authorize"}}?response_type=code&client_id={{.ClientID}}&redirect_uri={{.RedirectURI}}&state={{.State}}&scope={{.Scope}}&nonce={{.Nonce}}&code_challenge={{.CodeChallenge}}&code_challenge_method={{.CodeChallengeMethod}}&prompt={{.LoginPrompt}}&max_age={{.MaxAge}}">Log in</a></div>
</form>
{{end}}
{{define "scripts"}}
{{if ne .AuthMode "password"}}<script nonce="{{.CspNonce}}" src="{{authURL "/static/passkey.js"}}"></script>{{end}}
<script nonce="{{.CspNonce}}">
function showPasskeyError(msg) {
const el = document.getElementById('signup-error');
el.textContent = msg;
el.style.display = '';
}
function togglePasskeyMode(checkbox) {
const pwFields = document.getElementById('password-fields');
const btn = document.getElementById('signup-btn');
const pwInput = document.getElementById('password');
const confirmInput = document.getElementById('confirm_password');
if (checkbox.checked) {
pwFields.style.display = 'none';
pwInput.required = false;
confirmInput.required = false;
btn.textContent = 'Signup with Passkey';
} else {
pwFields.style.display = 'flex';
pwInput.required = true;
confirmInput.required = true;
btn.textContent = 'Create Account';
}
}
async function startPasskeyRegistration() {
const username = document.getElementById('username').value.trim();
const emailEl = document.getElementById('email');
const email = emailEl ? emailEl.value.trim() : '';
document.getElementById('signup-error').style.display = 'none';
const params = new URLSearchParams({
username,
email,
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/register/begin"}}?' + params.toString());
const data = await beginResp.json();
if (!beginResp.ok) { showPasskeyError(data.error || 'Failed to start registration'); return; }
await doPasskeyRegister(data, '{{authURL ""}}');
} catch (e) { showPasskeyError('Passkey error: ' + e.message); }
}
function handleSignupSubmit(event) {
const authMode = document.querySelector('form').dataset.authMode;
const usePasskey = authMode === 'passkey_only' ||
(authMode === 'password_and_passkey' && document.getElementById('use-passkey')?.checked);
if (usePasskey) {
event.preventDefault();
startPasskeyRegistration();
}
}
(function() {
var form = document.getElementById('signup-form');
if (form) { form.addEventListener('submit', handleSignupSubmit); }
var passkeyCheckbox = document.getElementById('use-passkey');
if (passkeyCheckbox) { passkeyCheckbox.addEventListener('change', function() { togglePasskeyMode(this); }); }
})();
</script>
{{end}}