fix(auth): make login/signup fields password-manager friendly

Password managers (1Password, Bitwarden, …) could not reliably detect or
fill the auth fields because the inputs were loose labels inside a div with
no real form, no name attributes, and a static autocomplete hint.

Changes:
- Wrap the auth card in a real <form @submit.prevent> so password managers
  recognize the credential form and its submit flow
- Add id/name attributes to all fields (username, displayName, password)
- Use a dynamic autocomplete hint on the password field:
  current-password for login (autofill) and new-password for registration
  (offer a generated strong password)
- Set autocomplete="nickname" on the display-name field so it is not
  mistaken for a username or password field
- Mark the close, tab-switch, and OIDC buttons as type="button" so they no
  longer default to type="submit" and trigger form submission
- Make the primary action a type="submit" button so Enter submits natively
- Add margin: 0 to .card since it is now a form element
This commit is contained in:
Tronax 2026-08-16 16:51:16 +02:00
parent d400a4a2a0
commit f97b4f92f0
Signed by: Tronax
SSH key fingerprint: SHA256:2pKKXDZucWvaF/GzXNz0FY53EAO1YDLN80bqS+TTz/o

View file

@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref } from 'vue'
import { computed, ref } from 'vue'
import { login, oidcLogin, register } from '@/game/auth'
import { store } from '@/game/store'
@ -10,6 +10,11 @@ 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 = ''
@ -33,40 +38,40 @@ async function submit(): Promise<void> {
<template>
<div v-if="store.auth.showAuth" class="backdrop" @click.self="close">
<div class="card">
<button class="close" @click="close"></button>
<form class="card" @submit.prevent="submit">
<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>
<div class="tabs">
<button :class="{ active: mode === 'login' }" @click="mode = 'login'">Anmelden</button>
<button :class="{ active: mode === 'register' }" @click="mode = 'register'">Registrieren</button>
<button type="button" :class="{ active: mode === 'login' }" @click="mode = 'login'">Anmelden</button>
<button type="button" :class="{ active: mode === 'register' }" @click="mode = 'register'">Registrieren</button>
</div>
<label class="field">
<span>Benutzername</span>
<input v-model="username" maxlength="16" autocomplete="username" @keyup.enter="submit" />
<input id="username" name="username" v-model="username" maxlength="16" autocomplete="username" />
</label>
<label v-if="mode === 'register'" class="field">
<span>Anzeigename (optional)</span>
<input v-model="displayName" maxlength="24" @keyup.enter="submit" />
<input id="displayName" name="displayName" v-model="displayName" maxlength="24" autocomplete="nickname" />
</label>
<label class="field">
<span>Passwort</span>
<input v-model="password" type="password" autocomplete="current-password" @keyup.enter="submit" />
<input id="password" name="password" v-model="password" type="password" :autocomplete="passwordAutocomplete" />
</label>
<p v-if="error" class="error">{{ error }}</p>
<button class="primary" :disabled="busy" @click="submit">
<button type="submit" class="primary" :disabled="busy">
{{ busy ? '…' : mode === 'login' ? '▶ Anmelden' : '▶ Konto erstellen' }}
</button>
<div v-if="store.auth.oidcEnabled" class="divider"><span>oder</span></div>
<button v-if="store.auth.oidcEnabled" class="oidc" @click="oidcLogin">🔑 {{ store.auth.oidcLabel }}</button>
<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>
</div>
</form>
</div>
</template>
@ -83,6 +88,7 @@ async function submit(): Promise<void> {
}
.card {
position: relative;
margin: 0;
background: var(--panel);
border: 1px solid var(--panel-border);
border-radius: 16px;