Finalizing user_creation
This commit is contained in:
parent
b260255980
commit
08c7ef4660
|
|
@ -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 -
|
||||||
|
|
||||||
|
|
|
||||||
18
src/App.vue
18
src/App.vue
|
|
@ -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>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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.
|
||||||
|
```
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue