Login mit E-Mail-Code (Appwrite-2FA)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
21325c4037
commit
b8e4de97c7
5 files changed
+253
-7
No files matched your search
@@ -8,9 +8,12 @@ import {
|
||||
Query,
|
||||
hasAppwriteSession,
|
||||
projectId as PROJECT_ID,
|
||||
client,
|
||||
} from '../lib/appwrite'
|
||||
import { createMfaApi, isMoreFactorsError, maskEmail, mfaErrorMessage } from '../lib/mfa'
|
||||
|
||||
const AuthContext = createContext()
|
||||
const mfa = createMfaApi(client)
|
||||
|
||||
function clearStaleAppwriteSessions() {
|
||||
if (typeof window === 'undefined' || !window.localStorage) return
|
||||
@@ -89,6 +92,9 @@ export function AuthProvider({ children }) {
|
||||
const [user, setUser] = useState(null)
|
||||
const [employee, setEmployee] = useState(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
// 2FA: Passwort stimmt, Sitzung braucht noch den Code aus der E-Mail.
|
||||
// challengeId null = noch keine Mail verschickt (z. B. nach dem Neuladen)
|
||||
const [mfaPending, setMfaPending] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
checkUser()
|
||||
@@ -106,6 +112,12 @@ export function AuthProvider({ children }) {
|
||||
setUser(session)
|
||||
setEmployee(await ensureEmployeeExists(session))
|
||||
} catch (error) {
|
||||
if (isMoreFactorsError(error)) {
|
||||
// Halbe Sitzung: Code-Schritt zeigen, aber keine Mail von selbst (jedes Neuladen wuerde sonst eine ausloesen)
|
||||
setMfaPending({ challengeId: null, email: '' })
|
||||
setUser(null)
|
||||
return
|
||||
}
|
||||
if (error.code !== 401 && error.code !== 404) {
|
||||
console.error('Unexpected error checking user:', error)
|
||||
}
|
||||
@@ -126,7 +138,15 @@ export function AuthProvider({ children }) {
|
||||
|
||||
await createSessionWithCleanup(normalizedEmail, normalizedPassword)
|
||||
|
||||
const session = await account.get()
|
||||
let session
|
||||
try {
|
||||
session = await account.get()
|
||||
} catch (error) {
|
||||
if (!isMoreFactorsError(error)) throw error
|
||||
setMfaPending({ challengeId: null, email: maskEmail(normalizedEmail) })
|
||||
const sent = await sendCode()
|
||||
return { success: false, mfaRequired: true, error: sent.error }
|
||||
}
|
||||
setUser(session)
|
||||
setEmployee(await ensureEmployeeExists(session))
|
||||
|
||||
@@ -156,6 +176,38 @@ export function AuthProvider({ children }) {
|
||||
}
|
||||
}
|
||||
|
||||
async function sendCode() {
|
||||
try {
|
||||
const challengeId = await mfa.createEmailChallenge()
|
||||
setMfaPending((pending) => ({ email: '', ...pending, challengeId }))
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
return { success: false, error: mfaErrorMessage(error) }
|
||||
}
|
||||
}
|
||||
|
||||
async function verifyCode(otp) {
|
||||
if (!mfaPending?.challengeId) return { success: false, error: 'Bitte zuerst einen Code anfordern.' }
|
||||
try {
|
||||
await mfa.completeChallenge(mfaPending.challengeId, String(otp || '').trim())
|
||||
const session = await account.get()
|
||||
setUser(session)
|
||||
setEmployee(await ensureEmployeeExists(session))
|
||||
setMfaPending(null)
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
return { success: false, error: mfaErrorMessage(error) }
|
||||
}
|
||||
}
|
||||
|
||||
async function cancelMfa() {
|
||||
try {
|
||||
await account.deleteSession('current')
|
||||
} catch { /* Sitzung ggf. schon weg */ }
|
||||
clearAllLocalAppwriteSessions()
|
||||
setMfaPending(null)
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
try {
|
||||
await account.deleteSessions()
|
||||
@@ -165,6 +217,7 @@ export function AuthProvider({ children }) {
|
||||
clearAllLocalAppwriteSessions()
|
||||
setUser(null)
|
||||
setEmployee(null)
|
||||
setMfaPending(null)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,7 +229,11 @@ export function AuthProvider({ children }) {
|
||||
loading,
|
||||
login,
|
||||
logout,
|
||||
isAdmin
|
||||
isAdmin,
|
||||
mfaPending,
|
||||
sendCode,
|
||||
verifyCode,
|
||||
cancelMfa,
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
Reference in new issue
Block a user