import fetchHelper from "../../utils/fetchHelper" import { EMAIL_FIELDS, isValidEmailSyntax } from "../../utils/emailVerification" import { getPendingByToken, confirmPending } from "../../utils/emailVerifyDb" /** * PUBLIC endpoint — applies a pending e-mail change. Body: { token }. * * No session is required: the random single-use token from the confirmation * e-mail IS the authorization (it proves control of the new address, and the * change itself was requested by an authenticated session). The recipient of * the new address may well not have a LogShip login (e.g. accounting@). * * The actual iDempiere write uses the service token; target record and field * were fixed server-side when the change was requested — nothing from this * request influences WHAT is written beyond yes/no. */ export default defineEventHandler(async (event) => { try { const body = await readBody(event) const token = String(body?.token || '') if (!/^[a-f0-9]{64}$/.test(token)) { return { status: 400, message: 'Invalid confirmation link' } } const row = getPendingByToken(token) if (!row) return { status: 404, message: 'Unknown or invalid confirmation link' } const def = EMAIL_FIELDS[row.fieldKey] if (!def || def.kind !== row.kind) { return { status: 400, message: 'Invalid confirmation link' } } const language = row.language || 'de_DE' const isGerman = String(language).startsWith('de') const fieldLabel = isGerman ? def.labelDe : def.labelEn // Idempotent: clicking the link twice shows success, not an error. if (row.confirmedAt) { return { status: 200, alreadyConfirmed: true, fieldKey: row.fieldKey, fieldLabel, newEmail: row.newEmail, language } } if (row.canceledAt) { return { status: 410, message: 'This change request was canceled' } } if (row.expiresAt <= Date.now()) { return { status: 410, message: 'This confirmation link has expired' } } if (!isValidEmailSyntax(row.newEmail)) { return { status: 422, message: 'Invalid email address' } } const config = useRuntimeConfig() const serviceToken = (config.api as any)?.idempieretoken if (!serviceToken) { return { status: 500, message: 'Service token not configured' } } const payload: Record = { [def.writeKey]: row.newEmail } if (row.kind === 'aduser') payload.tableName = 'AD_User' await fetchHelper( event, `models/${row.kind === 'aduser' ? 'ad_user' : 'c_bpartner'}/${row.targetId}`, 'PUT', serviceToken, payload ) confirmPending(row.id) return { status: 200, fieldKey: row.fieldKey, fieldLabel, newEmail: row.newEmail, language } } catch (err: any) { console.error('[EmailVerification] confirm failed:', err?.message) return { status: 500, message: 'Confirmation failed — please try again later' } } })