security: K2 fix — registration token flow

registerUser() returns { registrationToken } (10-Min JWT)
createProfile() sends it as X-Registration-Token header
userId nie mehr aus Browser-Body — kommt aus signiertem Token

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-14 22:55:53 +02:00
parent bccec179a4
commit 520d0d139c
3 changed files with 11 additions and 8 deletions

View File

@@ -28,7 +28,7 @@ export async function getMe(userToken) {
return data // bereits vom API-Server entpackt
}
// Registriert den Directus-User; gibt { userId } zurück
// Registriert den Directus-User; gibt { registrationToken } zurück (10 Min gültig)
export async function registerUser(email, password) {
const res = await fetch(`${BASE}/languparent/auth/register`, {
method: 'POST', headers: json,
@@ -36,7 +36,7 @@ export async function registerUser(email, password) {
})
const data = await res.json()
if (!res.ok) throw new Error(data.error || 'Registrierung fehlgeschlagen.')
return data // { userId }
return data // { registrationToken }
}
export async function checkUsername(username /*, _userToken — nicht mehr nötig */) {
@@ -49,11 +49,13 @@ export async function checkUsername(username /*, _userToken — nicht mehr nöti
}
// Erstellt Profil über API-Server (Admin-Token bleibt server-seitig)
// userToken ist das kurzlebige Registration-Token aus registerUser()
// Gibt { token, expiresIn } zurück — muss via saveToken gespeichert werden
export async function createProfile({ userId, username, nativeLang, targetLang /*, userToken — ignoriert */ }) {
export async function createProfile({ username, nativeLang, targetLang, userToken }) {
const res = await fetch(`${BASE}/languparent/auth/profile`, {
method: 'POST', headers: json,
body: JSON.stringify({ userId, username, nativeLang, targetLang }),
method: 'POST',
headers: { ...json, 'X-Registration-Token': userToken },
body: JSON.stringify({ username, nativeLang, targetLang }),
})
const data = await res.json()
if (!res.ok) throw new Error(data.error || 'Profilerstellung fehlgeschlagen.')

View File

@@ -14,8 +14,8 @@ export default function RegisterStep1({ onSuccess }) {
if (pw.length < 8) { setError('Passwort muss mindestens 8 Zeichen haben.'); return }
setError(''); setLoading(true)
try {
const { userId } = await registerUser(email, pw)
onSuccess(userId, null)
const { registrationToken } = await registerUser(email, pw)
onSuccess(null, registrationToken) // Token statt userId — AuthScreen speichert es als pendingToken
} catch (err) {
setError(err.message)
} finally {

View File

@@ -35,7 +35,8 @@ export default function RegisterStep2({ userId, userToken, onSuccess }) {
if (!available) {
setError('Dieser Username ist bereits vergeben.'); setLoading(false); return
}
const { token } = await createProfile({ userId, username, nativeLang, targetLang })
// userToken ist das kurzlebige Registration-Token aus Schritt 1
const { token } = await createProfile({ username, nativeLang, targetLang, userToken })
saveToken(token)
setUser({ id: userId, username, language_native: nativeLang, language_target: targetLang })
onSuccess(username)