Harden auth flows and add backend quality gate

This commit is contained in:
2026-03-04 12:57:42 +13:00
parent 1ad4823830
commit c6bc31f27e
24 changed files with 811 additions and 137 deletions

View File

@@ -156,6 +156,8 @@ const formatDate = (value?: string | null) => {
return date.toLocaleString()
}
const isValidEmail = (value: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value.trim())
const isInviteTraceRowInvited = (row: InviteTraceRow) =>
Boolean(String(row.inviterUsername || '').trim() || String(row.inviteCode || '').trim())
@@ -349,6 +351,17 @@ export default function AdminInviteManagementPage() {
const saveInvite = async (event: React.FormEvent) => {
event.preventDefault()
const recipientEmail = inviteForm.recipient_email.trim()
if (!recipientEmail) {
setError('Recipient email is required.')
setStatus(null)
return
}
if (!isValidEmail(recipientEmail)) {
setError('Recipient email must be valid.')
setStatus(null)
return
}
setInviteSaving(true)
setError(null)
setStatus(null)
@@ -363,7 +376,7 @@ export default function AdminInviteManagementPage() {
max_uses: inviteForm.max_uses || null,
enabled: inviteForm.enabled,
expires_at: inviteForm.expires_at || null,
recipient_email: inviteForm.recipient_email || null,
recipient_email: recipientEmail,
send_email: inviteForm.send_email,
message: inviteForm.message || null,
}
@@ -1607,18 +1620,19 @@ export default function AdminInviteManagementPage() {
<div className="invite-form-row">
<div className="invite-form-row-label">
<span>Delivery</span>
<small>Save a recipient email and optionally send the invite immediately.</small>
<small>Recipient email is required. You can optionally send the invite immediately after saving.</small>
</div>
<div className="invite-form-row-control invite-form-row-control--stacked">
<label>
<span>Recipient email</span>
<span>Recipient email (required)</span>
<input
type="email"
required
value={inviteForm.recipient_email}
onChange={(e) =>
setInviteForm((current) => ({ ...current, recipient_email: e.target.value }))
}
placeholder="person@example.com"
placeholder="Required recipient email"
/>
</label>
<label>

View File

@@ -82,6 +82,8 @@ const formatDate = (value?: string | null) => {
return date.toLocaleString()
}
const isValidEmail = (value: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value.trim())
export default function ProfileInvitesPage() {
const router = useRouter()
const [profile, setProfile] = useState<ProfileInfo | null>(null)
@@ -192,6 +194,17 @@ export default function ProfileInvitesPage() {
const saveInvite = async (event: React.FormEvent) => {
event.preventDefault()
const recipientEmail = inviteForm.recipient_email.trim()
if (!recipientEmail) {
setInviteError('Recipient email is required.')
setInviteStatus(null)
return
}
if (!isValidEmail(recipientEmail)) {
setInviteError('Recipient email must be valid.')
setInviteStatus(null)
return
}
setInviteSaving(true)
setInviteError(null)
setInviteStatus(null)
@@ -208,7 +221,7 @@ export default function ProfileInvitesPage() {
code: inviteForm.code || null,
label: inviteForm.label || null,
description: inviteForm.description || null,
recipient_email: inviteForm.recipient_email || null,
recipient_email: recipientEmail,
max_uses: inviteForm.max_uses || null,
expires_at: inviteForm.expires_at || null,
enabled: inviteForm.enabled,
@@ -438,13 +451,14 @@ export default function ProfileInvitesPage() {
<div className="invite-form-row">
<div className="invite-form-row-label">
<span>Delivery</span>
<small>Save a recipient email and optionally send the invite immediately.</small>
<small>Recipient email is required. You can also send the invite immediately after saving.</small>
</div>
<div className="invite-form-row-control invite-form-row-control--stacked">
<label>
<span>Recipient email</span>
<span>Recipient email (required)</span>
<input
type="email"
required
value={inviteForm.recipient_email}
onChange={(event) =>
setInviteForm((current) => ({
@@ -452,7 +466,7 @@ export default function ProfileInvitesPage() {
recipient_email: event.target.value,
}))
}
placeholder="friend@example.com"
placeholder="Required recipient email"
/>
</label>
<label>