Finalizing user_creation

This commit is contained in:
Moritz Graf 2026-04-26 18:21:48 +02:00
parent b260255980
commit 08c7ef4660
5 changed files with 320 additions and 4 deletions

View File

@ -9,6 +9,14 @@ TAG="latest"
echo "🚀 Starting deployment for Haumdaucher..." echo "🚀 Starting deployment for Haumdaucher..."
# Check cluster connectivity
echo "🔌 Verifying Kubernetes cluster connectivity..."
if ! kubectl cluster-info > /dev/null 2>&1; then
echo "❌ Error: Kubernetes cluster is unreachable (kubectl failed). Please check your KUBECONFIG or VPN."
exit 1
fi
echo "✅ Cluster is reachable."
# Create namespace if it doesn't exist # Create namespace if it doesn't exist
kubectl create namespace $NAMESPACE --dry-run=client -o yaml | kubectl apply -f - kubectl create namespace $NAMESPACE --dry-run=client -o yaml | kubectl apply -f -

View File

@ -8,6 +8,7 @@ import Datenschutz from './components/sections/Legal/Datenschutz.vue'
import HaumdaucherGame from './components/layout/HaumdaucherGame.vue' import HaumdaucherGame from './components/layout/HaumdaucherGame.vue'
import { messages } from './locales/i18n' import { messages } from './locales/i18n'
import { useAuth } from './composables/useAuth' import { useAuth } from './composables/useAuth'
import PasswordReset from './components/auth/PasswordReset.vue'
const { isAllowed } = useAuth() const { isAllowed } = useAuth()
@ -15,6 +16,8 @@ const theme = ref('classic')
const showGame = ref(false) const showGame = ref(false)
const isNatUnlocked = ref(false) const isNatUnlocked = ref(false)
const boars = ref<{id: number, top: number}[]>([]) const boars = ref<{id: number, top: number}[]>([])
const isPasswordResetMode = ref(false)
const resetOobCode = ref('')
const toggleTheme = (newTheme: string) => { const toggleTheme = (newTheme: string) => {
theme.value = newTheme theme.value = newTheme
@ -52,6 +55,12 @@ const startBoarRun = () => {
} }
onMounted(() => { onMounted(() => {
const urlParams = new URLSearchParams(window.location.search)
if (urlParams.get('mode') === 'resetPassword') {
isPasswordResetMode.value = true
resetOobCode.value = urlParams.get('oobCode') || ''
}
const savedTheme = localStorage.getItem('haumdaucher-theme') const savedTheme = localStorage.getItem('haumdaucher-theme')
if (savedTheme) toggleTheme(savedTheme) if (savedTheme) toggleTheme(savedTheme)
@ -72,6 +81,14 @@ const t = (key: string) => {
</script> </script>
<template> <template>
<div v-if="isPasswordResetMode">
<PasswordReset
:oobCode="resetOobCode"
:t="t"
@close="isPasswordResetMode = false; window.history.replaceState({}, document.title, window.location.pathname)"
/>
</div>
<div v-else>
<Header <Header
:currentTheme="theme" :currentTheme="theme"
:isNatUnlocked="isNatUnlocked" :isNatUnlocked="isNatUnlocked"
@ -117,6 +134,7 @@ const t = (key: string) => {
</nav> </nav>
</div> </div>
</footer> </footer>
</div>
</template> </template>

View File

@ -0,0 +1,202 @@
<script setup lang="ts">
import { ref } from 'vue'
import { confirmPasswordReset, verifyPasswordResetCode } from 'firebase/auth'
import { auth } from '../../firebase'
const props = defineProps<{
oobCode: string
t: (key: string) => any
}>()
const emit = defineEmits(['close'])
const newPassword = ref('')
const confirmPassword = ref('')
const error = ref('')
const success = ref(false)
const isLoading = ref(false)
const handleReset = async () => {
error.value = ''
if (newPassword.value !== confirmPassword.value) {
error.value = 'Die Passwörter stimmen nicht überein.'
return
}
if (newPassword.value.length < 6) {
error.value = 'Das Passwort muss mindestens 6 Zeichen lang sein.'
return
}
isLoading.value = true
try {
// Optional: First verify the code is valid
const email = await verifyPasswordResetCode(auth, props.oobCode)
// Then confirm the reset
await confirmPasswordReset(auth, props.oobCode, newPassword.value)
success.value = true
} catch (err: any) {
console.error('Password reset error:', err)
if (err.code === 'auth/invalid-action-code') {
error.value = 'Der Link ist ungültig oder abgelaufen. Bitte fordere einen neuen an.'
} else {
error.value = 'Ein Fehler ist aufgetreten: ' + err.message
}
} finally {
isLoading.value = false
}
}
</script>
<template>
<div class="reset-container">
<div class="glass-panel auth-card">
<h2 class="section-title">Neues Passwort 🌭</h2>
<div v-if="success" class="success-message">
<h3>Sauber!</h3>
<p>Dein Passwort wurde erfolgreich geändert.</p>
<button class="club-btn" @click="emit('close')">Zurück zur Startseite</button>
</div>
<form v-else @submit.prevent="handleReset" class="auth-form">
<p class="description">Bitte gib dein neues geheimes Haumdaucher-Passwort ein.</p>
<div class="input-group">
<label>Neues Passwort</label>
<input
type="password"
v-model="newPassword"
placeholder="Mindestens 6 Zeichen"
required
class="club-input"
/>
</div>
<div class="input-group">
<label>Passwort bestätigen</label>
<input
type="password"
v-model="confirmPassword"
placeholder="Nochmal zur Sicherheit"
required
class="club-input"
/>
</div>
<div v-if="error" class="error-alert">{{ error }}</div>
<div class="actions">
<button type="button" class="club-btn text-btn" @click="emit('close')">Abbrechen</button>
<button type="submit" class="club-btn" :disabled="isLoading">
{{ isLoading ? 'Speichern...' : 'Passwort ändern' }}
</button>
</div>
</form>
</div>
</div>
</template>
<style scoped>
.reset-container {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
background: var(--bg-color);
}
.auth-card {
width: 100%;
max-width: 400px;
padding: 40px;
text-align: center;
}
.description {
margin-bottom: 30px;
opacity: 0.8;
line-height: 1.5;
}
.auth-form {
display: flex;
flex-direction: column;
gap: 20px;
}
.input-group {
display: flex;
flex-direction: column;
gap: 8px;
text-align: left;
}
.input-group label {
font-size: 0.9rem;
font-weight: 600;
opacity: 0.9;
}
.club-input {
padding: 12px 15px;
border-radius: 8px;
border: 1px solid var(--glass-border);
background: rgba(255, 255, 255, 0.05);
color: var(--text-color);
font-size: 1rem;
font-family: inherit;
transition: all 0.3s ease;
}
.club-input:focus {
outline: none;
border-color: var(--primary-color);
background: rgba(255, 255, 255, 0.1);
}
.actions {
display: flex;
gap: 15px;
margin-top: 10px;
}
.actions .club-btn {
flex: 1;
}
.text-btn {
background: transparent !important;
border: 1px solid var(--glass-border) !important;
}
.text-btn:hover {
background: rgba(255, 255, 255, 0.05) !important;
}
.error-alert {
background: rgba(255, 75, 75, 0.1);
color: #ff4b4b;
padding: 10px;
border-radius: 8px;
font-size: 0.9rem;
border: 1px solid rgba(255, 75, 75, 0.3);
}
.success-message {
display: flex;
flex-direction: column;
gap: 20px;
align-items: center;
padding: 20px 0;
}
.success-message h3 {
color: #4caf50;
font-size: 1.5rem;
}
</style>

View File

@ -0,0 +1,88 @@
# Firebase Email Templates (HAUMDAUCHER n.e.V.)
Da Google Cloud Identity Platform (Firebase) die Konfiguration von E-Mail-Vorlagen über Terraform (`google_identity_platform_config`) nicht nativ unterstützt, müssen diese Texte einmalig manuell in der Firebase Console hinterlegt werden.
## Globale Einstellungen für alle Templates
Bitte stelle in der Firebase Console unter **Authentication > Templates** (oder Identity Platform im GCP Dashboard) Folgendes ein:
- **Sender Name**: HAUMDAUCHER n.e.V.
- **Reply-to**: info@haumdaucher.de
*(Da `haumdaucher.de` bereits bei Google verifiziert ist, sollte der Versand über diese Domain problemlos funktionieren).*
---
## 1. Passwort zurücksetzen (Password Reset)
**Subject:**
```text
🌭 Neues Passwort gefällig? (HAUMDAUCHER n.e.V.)
```
**Message (Above Link):**
```text
Servus %DISPLAY_NAME%,
ohje, hast du dein Passwort für den Haumdaucher-Zugang verschwitzt? Keine Sorge, das passiert den Besten nach ein paar Bratwürsten und Knackern zu viel! 🍻
Damit du wieder vollen Zugriff auf unsere streng geheimen Wurst- und Spezialitäten-Archive in Regensburg bekommst, klick einfach auf den magischen Link unten:
```
**Message (Below Link):**
```text
Lass dir Zeit, der Link rennt nicht weg (anders als eine gute Wurst auf dem Grill 🔥). Falls du gar kein neues Passwort angefordert hast, ignoriere diese E-Mail einfach dein Account ist sicher.
Mit fleischigen Grüßen,
Dein Vorstand des HAUMDAUCHER Wurst und Spezialitäten n.e.V.
```
---
## 2. E-Mail-Adresse bestätigen (Email Address Verification)
**Subject:**
```text
🥓 Bestätige deine E-Mail für den Haumdaucher-Zirkel!
```
**Message (Above Link):**
```text
Servus %DISPLAY_NAME%,
willkommen bei den wahren Feinschmeckern! 🥩 Bevor wir dich in die tiefsten Geheimnisse unserer Wurst- und Spezialitätenkunde einweihen, müssen wir kurz checken, ob du auch ein echter Mensch bist (und kein veganer Spionage-Bot).
Bitte bestätige deine E-Mail-Adresse mit einem herzhaften Klick auf diesen Link:
```
**Message (Below Link):**
```text
Sobald das erledigt ist, bist du offiziell startklar. Wir freuen uns auf dich! 🍻
Mit fleischigen Grüßen,
Dein Vorstand des HAUMDAUCHER Wurst und Spezialitäten n.e.V.
```
---
## 3. E-Mail-Adresse ändern (Email Address Change)
**Subject:**
```text
🚨 E-Mail-Änderung beim Haumdaucher n.e.V.!
```
**Message (Above Link):**
```text
Servus %DISPLAY_NAME%,
wir haben gehört, dass du eine neue E-Mail-Adresse für deinen Haumdaucher-Account verwenden möchtest. Hast du den Provider gewechselt oder war die alte Adresse zu vegetarisch? 🥦
Bitte bestätige deine neue Adresse hier:
```
**Message (Below Link):**
```text
Falls du diese Änderung NICHT angefordert hast, melde dich bitte sofort bei uns unter info@haumdaucher.de vielleicht versucht jemand, an deine geheimen Regensburger Wurst-Rezepte zu kommen! 🕵️‍♂️
Mit fleischigen Grüßen,
Dein Vorstand des HAUMDAUCHER Wurst und Spezialitäten n.e.V.
```

View File

@ -2,7 +2,7 @@ const CONFIG = {
SPREADSHEET_ID: "1q4r08nBA_ClWv3ypPCQ6GVCfMVkQwSKzDSRiokkQQ8Q", // ID from mail_forwarding SPREADSHEET_ID: "1q4r08nBA_ClWv3ypPCQ6GVCfMVkQwSKzDSRiokkQQ8Q", // ID from mail_forwarding
SHEET_NAME: "Form Responses 1", SHEET_NAME: "Form Responses 1",
COL_FORWARD_TO_ADDRESS: 4, // 1-indexed (Column D from mail_forwarding) COL_FORWARD_TO_ADDRESS: 4, // 1-indexed (Column D from mail_forwarding)
ADMIN_EMAIL: "moritz@haumdaucher.de", ADMIN_EMAIL: "info@haumdaucher.de",
PROJECT_ID: "haumdaucher", // Used in the Identity Toolkit REST API PROJECT_ID: "haumdaucher", // Used in the Identity Toolkit REST API
DRY_RUN: false, DRY_RUN: false,
SEND_EMAIL_ON_CREATION: false, SEND_EMAIL_ON_CREATION: false,