GDPR Art. 16 and Art. 5(1)(d) require that personal data be kept accurate and that data subjects can correct inaccurate information. A profile page that displays personal data as read-only — no edit form, no save button — removes the user's ability to exercise this right. This is a quiet but persistent violation: the user's name or contact details may be wrong, and they have no mechanism to fix it. Email address changes carry additional security weight: allowing an email swap without verification exposes accounts to takeover, which creates a compounding GDPR breach risk on top of the rectification failure.
Medium because the violation is a clear but non-urgent Art. 16 gap — users cannot rectify inaccuracies, but the immediate harm is limited unless stale data drives automated decisions that affect the user.
Implement a profile edit form with a PATCH API call that persists changes immediately. Route email address changes through a separate verification flow.
// app/settings/profile/page.tsx
'use client'
export default function ProfileSettingsPage({ user }: { user: User }) {
const [name, setName] = useState(user.name ?? '')
async function handleSave(e: FormEvent) {
e.preventDefault()
const res = await fetch('/api/user/profile', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name }),
})
if (res.ok) toast.success('Profile updated.')
}
return (
<form onSubmit={handleSave}>
<label>Display Name<input value={name} onChange={e => setName(e.target.value)} required /></label>
<p className="text-sm">To change your email, <a href="/settings/change-email">start the email change process</a>. A verification link will be sent to the new address.</p>
<button type="submit">Save changes</button>
</form>
)
}
For the email change flow: store the pending change in a pending_email_changes table with an expiry token, send the verification link to the new address, and only apply the swap after the token is clicked. Never update email in the users table without email verification.
ID: gdpr-readiness.user-rights.right-to-rectification
Severity: medium
What to look for: Look for a user profile or account settings page where users can view and edit their personal information. Check what fields are editable — at minimum, name, email address, phone (if collected), and mailing address (if collected) should all be correctable. Verify that changes are actually persisted when the form is submitted (check that the form has a PATCH/PUT API call, not just a UI update). Check whether email address changes require verification via a confirmation link sent to the new address before the change takes effect — this is important both for GDPR (ensuring data accuracy) and for security (preventing account takeover via email change). Count all instances found and enumerate each.
Pass criteria: Users can view all personal data held about them and edit it from a profile or settings page. Changes are immediately persisted to the database. Email changes trigger a verification email to the new address and only take effect after confirmation. All collected personal data fields are editable. At least 1 implementation must be confirmed.
Fail criteria: No profile editing feature exists. Fields are displayed but read-only. Form appears to submit but no API call is made. Inaccurate data has no correction mechanism.
Skip (N/A) when: Application has no user accounts or collects no personal data beyond authentication credentials managed by a third-party identity provider.
Detail on fail: Example: "Profile page exists but all personal data fields are read-only. No edit form or save button." or "Name field is editable but changes are not persisted — no PATCH request made on submit." or "Email address cannot be changed after account creation.".
Remediation: Implement a profile edit form with verified email changes:
// app/settings/profile/page.tsx
'use client'
import { useState, FormEvent } from 'react'
export default function ProfileSettingsPage({ user }: { user: User }) {
const [name, setName] = useState(user.name ?? '')
const [saving, setSaving] = useState(false)
async function handleSave(e: FormEvent) {
e.preventDefault()
setSaving(true)
const res = await fetch('/api/user/profile', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name }),
})
setSaving(false)
if (res.ok) toast.success('Profile updated.')
}
return (
<form onSubmit={handleSave}>
<label>
Display Name
<input value={name} onChange={e => setName(e.target.value)} required />
</label>
{/* Email change — requires separate verification flow */}
<p className="text-sm text-muted-foreground">
To change your email address,{' '}
<a href="/settings/change-email">start the email change process</a>.
A verification link will be sent to the new address.
</p>
<button type="submit" disabled={saving}>Save changes</button>
</form>
)
}
For the email change flow: send a verification token to the new address, store the pending email change in a pending_email_changes table with expiry, and only apply the change when the token is clicked.