Final Feature 9c #10

Merged
Lars merged 18 commits from develop into main 2026-03-21 12:41:41 +01:00
3 changed files with 54 additions and 7 deletions
Showing only changes of commit 9fb6e27256 - Show all commits

View File

@ -7,7 +7,7 @@ import os
import secrets import secrets
import smtplib import smtplib
from typing import Optional from typing import Optional
from datetime import datetime, timedelta from datetime import datetime, timedelta, timezone
from email.mime.text import MIMEText from email.mime.text import MIMEText
from fastapi import APIRouter, HTTPException, Header, Depends from fastapi import APIRouter, HTTPException, Header, Depends
@ -233,19 +233,20 @@ async def register(req: RegisterRequest, request: Request):
# Generate verification token # Generate verification token
verification_token = secrets.token_urlsafe(32) verification_token = secrets.token_urlsafe(32)
verification_expires = datetime.now() + timedelta(hours=24) verification_expires = datetime.now(timezone.utc) + timedelta(hours=24)
# Create profile (inactive until verified) # Create profile (inactive until verified)
profile_id = str(secrets.token_hex(16)) profile_id = str(secrets.token_hex(16))
pin_hash = hash_pin(password) pin_hash = hash_pin(password)
trial_ends = datetime.now(timezone.utc) + timedelta(days=14) # 14-day trial
cur.execute(""" cur.execute("""
INSERT INTO profiles ( INSERT INTO profiles (
id, name, email, pin_hash, auth_type, role, tier, id, name, email, pin_hash, auth_type, role, tier,
email_verified, verification_token, verification_expires, email_verified, verification_token, verification_expires,
created trial_ends_at, created
) VALUES (%s, %s, %s, %s, 'email', 'user', 'free', FALSE, %s, %s, CURRENT_TIMESTAMP) ) VALUES (%s, %s, %s, %s, 'email', 'user', 'free', FALSE, %s, %s, %s, CURRENT_TIMESTAMP)
""", (profile_id, name, email, pin_hash, verification_token, verification_expires)) """, (profile_id, name, email, pin_hash, verification_token, verification_expires, trial_ends))
# Send verification email # Send verification email
app_url = os.getenv("APP_URL", "https://mitai.jinkendo.de") app_url = os.getenv("APP_URL", "https://mitai.jinkendo.de")
@ -294,7 +295,7 @@ async def verify_email(token: str):
raise HTTPException(400, "E-Mail-Adresse bereits bestätigt") raise HTTPException(400, "E-Mail-Adresse bereits bestätigt")
# Check if token expired # Check if token expired
if prof['verification_expires'] and datetime.now() > prof['verification_expires']: if prof['verification_expires'] and datetime.now(timezone.utc) > prof['verification_expires']:
raise HTTPException(400, "Verifikations-Link abgelaufen. Bitte registriere dich erneut.") raise HTTPException(400, "Verifikations-Link abgelaufen. Bitte registriere dich erneut.")
# Mark as verified and clear token # Mark as verified and clear token
@ -306,7 +307,7 @@ async def verify_email(token: str):
# Create session (auto-login after verification) # Create session (auto-login after verification)
session_token = make_token() session_token = make_token()
expires = datetime.now() + timedelta(days=30) expires = datetime.now(timezone.utc) + timedelta(days=30)
cur.execute(""" cur.execute("""
INSERT INTO sessions (token, profile_id, expires_at, created) INSERT INTO sessions (token, profile_id, expires_at, created)
VALUES (%s, %s, %s, CURRENT_TIMESTAMP) VALUES (%s, %s, %s, CURRENT_TIMESTAMP)

View File

@ -0,0 +1,42 @@
export default function EmailVerificationBanner({ profile }) {
// Only show if email is not verified
if (!profile || profile.email_verified !== false) return null
return (
<div style={{
background: '#FFF4E6',
border: '2px solid #F59E0B',
borderRadius: 12,
padding: '16px 20px',
marginBottom: 20,
display: 'flex',
alignItems: 'center',
gap: 16
}}>
<div style={{
fontSize: 32,
lineHeight: 1
}}>
📧
</div>
<div style={{flex: 1}}>
<div style={{
fontWeight: 700,
fontSize: 15,
color: '#D97706',
marginBottom: 4
}}>
E-Mail-Adresse noch nicht bestätigt
</div>
<div style={{
fontSize: 13,
color: 'var(--text2)',
lineHeight: 1.4
}}>
Bitte prüfe dein Postfach und klicke auf den Bestätigungslink.
Ohne Bestätigung ist dein Account eingeschränkt.
</div>
</div>
</div>
)
}

View File

@ -9,6 +9,7 @@ import { api } from '../utils/api'
import { useProfile } from '../context/ProfileContext' import { useProfile } from '../context/ProfileContext'
import { getBfCategory } from '../utils/calc' import { getBfCategory } from '../utils/calc'
import TrialBanner from '../components/TrialBanner' import TrialBanner from '../components/TrialBanner'
import EmailVerificationBanner from '../components/EmailVerificationBanner'
import { getInterpretation, getStatusColor, getStatusBg } from '../utils/interpret' import { getInterpretation, getStatusColor, getStatusBg } from '../utils/interpret'
import Markdown from '../utils/Markdown' import Markdown from '../utils/Markdown'
import dayjs from 'dayjs' import dayjs from 'dayjs'
@ -316,6 +317,9 @@ export default function Dashboard() {
</div> </div>
</div> </div>
{/* Email Verification Banner */}
<EmailVerificationBanner profile={activeProfile}/>
{/* Trial Banner */} {/* Trial Banner */}
<TrialBanner profile={activeProfile}/> <TrialBanner profile={activeProfile}/>