fix(auth): split login and signup into separate 1Password-compatible forms

1Password showed "No items to display" on the signup password field
because login and registration shared a single form whose password input
switched autocomplete between current-password and new-password, and the
display-name field was dynamically added via v-if. Per 1Password compatible
website design, unrelated flows must be separate forms with stable fields.

Changes:
- Split into two distinct <form> elements: login (autocomplete
  current-password) and signup (autocomplete new-password), each with
  unique field ids so 1Password does not cache the field as a login field
- Add passwordrules, minlength=8, and maxlength=128 to the signup password
  input so 1Password can generate a password matching the server rules
  (min 8 chars)
- Remove the dynamic autocomplete computed and the v-if display-name field;
  each form now has a static, complete field set
- Add .fields form styling (flex column, gap) now that the forms sit inside
  the card container
This commit is contained in:
Tronax 2026-08-16 16:58:51 +02:00
parent f97b4f92f0
commit cf63f27839
Signed by: Tronax
SSH key fingerprint: SHA256:2pKKXDZucWvaF/GzXNz0FY53EAO1YDLN80bqS+TTz/o

View file

@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { ref } from 'vue'
import { login, oidcLogin, register } from '@/game/auth'
import { store } from '@/game/store'
@ -10,11 +10,6 @@ const displayName = ref('')
const busy = ref(false)
const error = ref('')
// Password managers (1Password, Bitwarden, ) rely on distinct autocomplete
// hints: "current-password" for login, "new-password" so a strong password is
// suggested/saved during registration.
const passwordAutocomplete = computed(() => (mode.value === 'login' ? 'current-password' : 'new-password'))
function close(): void {
store.auth.showAuth = false
error.value = ''
@ -38,7 +33,7 @@ async function submit(): Promise<void> {
<template>
<div v-if="store.auth.showAuth" class="backdrop" @click.self="close">
<form class="card" @submit.prevent="submit">
<div class="card">
<button type="button" class="close" @click="close"></button>
<h2>{{ mode === 'login' ? 'Anmelden' : 'Konto erstellen' }}</h2>
<p class="sub">Speichere Kristalle, Forschung & Statistiken über alle Runden hinweg.</p>
@ -48,30 +43,56 @@ async function submit(): Promise<void> {
<button type="button" :class="{ active: mode === 'register' }" @click="mode = 'register'">Registrieren</button>
</div>
<!-- Anmeldung: separates Formular mit current-password -->
<form v-if="mode === 'login'" class="fields" @submit.prevent="submit">
<label class="field">
<span>Benutzername</span>
<input id="username" name="username" v-model="username" maxlength="16" autocomplete="username" />
<input id="login-username" name="username" v-model="username" maxlength="16" autocomplete="username" />
</label>
<label v-if="mode === 'register'" class="field">
<label class="field">
<span>Passwort</span>
<input id="login-password" name="password" v-model="password" type="password" autocomplete="current-password" />
</label>
<p v-if="error" class="error">{{ error }}</p>
<button type="submit" class="primary" :disabled="busy">{{ busy ? '…' : '▶ Anmelden' }}</button>
</form>
<!-- Registrierung: separates Formular mit new-password + Generierungs-Regeln -->
<form v-else class="fields" @submit.prevent="submit">
<label class="field">
<span>Benutzername</span>
<input id="register-username" name="username" v-model="username" maxlength="16" autocomplete="username" />
</label>
<label class="field">
<span>Anzeigename (optional)</span>
<input id="displayName" name="displayName" v-model="displayName" maxlength="24" autocomplete="nickname" />
</label>
<label class="field">
<span>Passwort</span>
<input id="password" name="password" v-model="password" type="password" :autocomplete="passwordAutocomplete" />
<input
id="register-password"
name="password"
v-model="password"
type="password"
autocomplete="new-password"
minlength="8"
maxlength="128"
passwordrules="minlength: 8; maxlength: 128;"
/>
</label>
<p v-if="error" class="error">{{ error }}</p>
<button type="submit" class="primary" :disabled="busy">
{{ busy ? '…' : mode === 'login' ? '▶ Anmelden' : '▶ Konto erstellen' }}
</button>
<button type="submit" class="primary" :disabled="busy">{{ busy ? '…' : '▶ Konto erstellen' }}</button>
</form>
<div v-if="store.auth.oidcEnabled" class="divider"><span>oder</span></div>
<button type="button" v-if="store.auth.oidcEnabled" class="oidc" @click="oidcLogin">🔑 {{ store.auth.oidcLabel }}</button>
<p class="hint">Als Gast spielen? Einfach schließen Fortschritt wird lokal gespeichert.</p>
</form>
</div>
</div>
</template>
@ -138,6 +159,12 @@ h2 {
color: var(--text);
border-color: var(--accent);
}
.fields {
display: flex;
flex-direction: column;
gap: 12px;
margin: 0;
}
.field {
display: flex;
flex-direction: column;