Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / frontend/src/screens/Login.tsx |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 09015496d565a172bcaa15cfe9a8e4b072b13df0 |
| Branch | workstream-b-screenmatch |
| Path or archive member | frontend/src/screens/Login.tsx |
| Original SHA-256 | 313057918dee7c7123896c881ee0448d31b766a6164e5daaa4a7ed5f8f8d6795 |
| Git blob SHA | d89b5236159d5d25133c62aa7ea65b854d7dcb87 |
| Reading-copy SHA-256 | 313057918dee7c7123896c881ee0448d31b766a6164e5daaa4a7ed5f8f8d6795 |
Every recorded occurrence
These 65 records contain identical original source bytes. Their paths and revisions remain part of the archive.
| Original record | Branch or archive | Revision / identifier | Original location |
|---|---|---|---|
| Janady13/missionmatch / frontend/src/screens/Login.tsx | workstream-b-screenmatch | 09015496d565a172bcaa15cfe9a8e4b072b13df0 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | workstream-a-opportunity-intelligence | 1675031db74316e100782d063e9eda1a597fe3a1 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | master | 1e9249840a4302ec82e123b9c9cb6f75badea3e4 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | workstream-d-command-center | 255e7d1d420abf5e277d0cd8c9b14f6f72b6acb9 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | workstream-d-missionmatch | 3be46ba89edb737182075a2b320509b42dc916f8 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | m5-price-to-win | 41000ad30fff4129fdaf5ec84e15ba14c27d4890 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | workstream-e-dataflow-deploy | 433815a042f9741411e60fa553fe72143e9b10db | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | w3-ai-staffing-recommendations | 43d56387270ef4da4cabd794d97ca6a49909bc7d | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | workstream-e-commercial-stubs | 4437da660539284a400ee380b9b427a300c369ee | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | ws3-clearance | 4b8e7ce594c068621867f9c9deed0ec64ae6c553 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | collision-deconfliction | 4ee5a09972608ed89f8377c6665542d5f450f0a6 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | workstream-c-slatematch | 5cd60c918301b46ecd6ed3e03e20c395da0062e2 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | ai-intelligence-layer | 5d5ddff70393a197c5f54a45280bb5dd42b24755 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | ws7-federal-signal | 6a9f6ce9cfb13345ffb47770d4960e7c2d9c17c2 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | ws6-retrieval | 6c56e29231ec05735927fa265c97d517499dfeaf | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | per-tenant-routing | 9bde0f9a74b89d01980fa658e5253acda7528d0c | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | ai-brain-live | aac32fc49c878cc5bca099459307611562d0bac1 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | graphrag-foundation | b0ddeb9f633fd138d09309d1a17b7d13bc3a67f7 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | ws1-ontology-sync | b596f449a32a1016fbd403c36505cc00a1ac9ee6 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | time-to-fill-survival | b66e1e505863d4f17741a4b9ede73509e45bd6c6 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | ruthless-debug-sweep | b7d742f8f032984be3817efffa3eb8b13b651c1f | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | mag-substrate | b82c7100a492dec67dfd1eda6b3f53def859be35 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | capture-readiness | bae6af7cb2997e819174a9e9cfff5e16067a1975 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | deploy-and-ops | bdacd9d0b4d5998d25fc74bf4007f4c3ec538c7a | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | ws8-teaching-loop | c823f0fb6bb2aba0f39001eb6de96f4000d9321f | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | phase2-eval-fairness | d361af254701975fe239927656ababb56374655e | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | workstream-b-sam-contract-awards | d5101cceb98e70000cebb31d3e7ac73d747d3a6d | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | phase2-connectors | e038d4c687aec16f8bd9bc4d907eea14d91f8704 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | workstream-a-shared-core | e0de03ac3a1ec826bd15d0e048e3b02baa23eeff | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | ws2-alias-relationship | e4dada4601c6eb101830f9d223a9655dfa120616 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | presentation-readiness | eb574acaa29d71ce4d0f852572e87c275c79fdb7 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | ws9-bench-ingestion | f585a9ef32fa554663ce1fba9a414a54b6e49cf0 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | vendor-intelligence | fe9796a19c261951d3f2565206c0c1b5ac294465 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | per-tenant-routing | 9bde0f9a74b89d01980fa658e5253acda7528d0c | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | collision-deconfliction | 4ee5a09972608ed89f8377c6665542d5f450f0a6 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | ws6-retrieval | 6c56e29231ec05735927fa265c97d517499dfeaf | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | workstream-a-shared-core | e0de03ac3a1ec826bd15d0e048e3b02baa23eeff | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | m5-price-to-win | 41000ad30fff4129fdaf5ec84e15ba14c27d4890 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | workstream-c-slatematch | 5cd60c918301b46ecd6ed3e03e20c395da0062e2 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | ws1-ontology-sync | b596f449a32a1016fbd403c36505cc00a1ac9ee6 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | master | 1e9249840a4302ec82e123b9c9cb6f75badea3e4 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | ws8-teaching-loop | c823f0fb6bb2aba0f39001eb6de96f4000d9321f | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | w3-ai-staffing-recommendations | 43d56387270ef4da4cabd794d97ca6a49909bc7d | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | mag-substrate | b82c7100a492dec67dfd1eda6b3f53def859be35 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | ws3-clearance | 4b8e7ce594c068621867f9c9deed0ec64ae6c553 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | workstream-d-command-center | 255e7d1d420abf5e277d0cd8c9b14f6f72b6acb9 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | phase2-eval-fairness | d361af254701975fe239927656ababb56374655e | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | workstream-e-dataflow-deploy | 433815a042f9741411e60fa553fe72143e9b10db | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | ruthless-debug-sweep | b7d742f8f032984be3817efffa3eb8b13b651c1f | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | time-to-fill-survival | b66e1e505863d4f17741a4b9ede73509e45bd6c6 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | ai-intelligence-layer | 5d5ddff70393a197c5f54a45280bb5dd42b24755 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | graphrag-foundation | b0ddeb9f633fd138d09309d1a17b7d13bc3a67f7 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | workstream-a-opportunity-intelligence | 1675031db74316e100782d063e9eda1a597fe3a1 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | deploy-and-ops | bdacd9d0b4d5998d25fc74bf4007f4c3ec538c7a | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | ws9-bench-ingestion | f585a9ef32fa554663ce1fba9a414a54b6e49cf0 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | ws2-alias-relationship | e4dada4601c6eb101830f9d223a9655dfa120616 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | presentation-readiness | eb574acaa29d71ce4d0f852572e87c275c79fdb7 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | workstream-b-screenmatch | 09015496d565a172bcaa15cfe9a8e4b072b13df0 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | ws7-federal-signal | 6a9f6ce9cfb13345ffb47770d4960e7c2d9c17c2 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | workstream-e-commercial-stubs | 4437da660539284a400ee380b9b427a300c369ee | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | ai-brain-live | aac32fc49c878cc5bca099459307611562d0bac1 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | workstream-d-missionmatch | 3be46ba89edb737182075a2b320509b42dc916f8 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | phase2-connectors | e038d4c687aec16f8bd9bc4d907eea14d91f8704 | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | workstream-b-sam-contract-awards | d5101cceb98e70000cebb31d3e7ac73d747d3a6d | Open original |
| Janady13/missionmatch / frontend/src/screens/Login.tsx | capture-readiness | bae6af7cb2997e819174a9e9cfff5e16067a1975 | Open original |
Recovered source transcript
Download complete source text ↓202 lines · 7.0 KB
import { useState } from 'react'
import { Loader2, Lock, ShieldCheck } from 'lucide-react'
import { authApi, type EnrollResult } from '../lib/auth'
import { useAuth } from '../lib/useAuth'
type Step = 'credentials' | 'verify' | 'enroll'
// Login is the unauthenticated gate. Stage 1 is email and password (returns a pre MFA session cookie);
// stage 2 is mandatory TOTP, either verifying an existing enrollment or enrolling for the first time.
// On an active session it calls refresh, which flips the AuthProvider to show the app.
export function Login() {
const { refresh } = useAuth()
const [step, setStep] = useState<Step>('credentials')
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [code, setCode] = useState('')
const [useRecovery, setUseRecovery] = useState(false)
const [enroll, setEnroll] = useState<EnrollResult | null>(null)
const [err, setErr] = useState('')
const [busy, setBusy] = useState(false)
async function beginEnroll() {
setBusy(true)
try {
setEnroll(await authApi.mfaEnroll())
setStep('enroll')
} catch {
setErr('Could not start MFA enrollment. Try again.')
} finally {
setBusy(false)
}
}
async function onCredentials(e: React.FormEvent) {
e.preventDefault()
setErr('')
setBusy(true)
try {
const r = await authApi.login(email, password)
if (!r.mfa_required) {
// MFA is disabled for this pilot: the session is already active, go straight in.
await refresh()
setBusy(false)
return
}
setCode('')
if (r.mfa_enrolled) {
setStep('verify')
setBusy(false)
} else {
await beginEnroll()
}
} catch {
setErr('Invalid email or password.')
setBusy(false)
}
}
async function onVerify(e: React.FormEvent) {
e.preventDefault()
setErr('')
setBusy(true)
try {
if (useRecovery) {
await authApi.mfaRecover(code.trim())
} else {
await authApi.mfaVerify(code.trim())
}
await refresh()
} catch {
setErr(useRecovery ? 'Invalid recovery code.' : 'Invalid authentication code.')
} finally {
// Clear busy on both paths so a session that does not flip the gate (for example a refresh
// that returns no active session) leaves the form usable instead of a stuck disabled button.
setBusy(false)
}
}
async function onActivate(e: React.FormEvent) {
e.preventDefault()
setErr('')
setBusy(true)
try {
await authApi.mfaActivate(code.trim())
await refresh()
} catch {
setErr('Invalid code. Make sure the time on your authenticator is correct.')
} finally {
setBusy(false)
}
}
return (
<div className="grid min-h-screen place-items-center bg-navy-950 p-4 text-ink">
<div className="w-full max-w-sm rounded-xl border border-line/70 bg-surface p-6 shadow-2xl">
<div className="mb-5 flex items-center gap-2">
<ShieldCheck className="text-gold" size={22} />
<div className="text-lg font-extrabold tracking-tight">missionMATCH</div>
</div>
{step === 'credentials' && (
<form onSubmit={onCredentials} className="space-y-3">
<h1 className="text-sm font-bold">Sign in</h1>
<Field label="Email" type="email" value={email} onChange={setEmail} autoFocus />
<Field label="Password" type="password" value={password} onChange={setPassword} maxLength={64} />
{err && <p className="text-sm text-risk">{err}</p>}
<Submit busy={busy} icon={<Lock size={15} />} label="Continue" />
</form>
)}
{step === 'verify' && (
<form onSubmit={onVerify} className="space-y-3">
<h1 className="text-sm font-bold">Two factor authentication</h1>
<p className="text-xs text-muted">
{useRecovery
? 'Enter one of your single use recovery codes.'
: 'Enter the 6 digit code from your authenticator app.'}
</p>
<Field label={useRecovery ? 'Recovery code' : 'Authentication code'} value={code} onChange={setCode} autoFocus />
{err && <p className="text-sm text-risk">{err}</p>}
<Submit busy={busy} icon={<ShieldCheck size={15} />} label="Verify" />
<button
type="button"
onClick={() => {
setUseRecovery(!useRecovery)
setCode('')
setErr('')
}}
className="text-[11px] text-muted hover:text-ink"
>
{useRecovery ? 'Use an authenticator code instead' : 'Use a recovery code instead'}
</button>
</form>
)}
{step === 'enroll' && enroll && (
<form onSubmit={onActivate} className="space-y-3">
<h1 className="text-sm font-bold">Set up two factor authentication</h1>
<p className="text-xs text-muted">
Add this account to your authenticator app, then enter the 6 digit code it shows. MFA is
required for every account.
</p>
<div className="rounded-lg bg-navy-950/60 p-3">
<div className="text-[11px] uppercase tracking-wider text-faint">Setup key</div>
<div className="mt-1 break-all font-mono text-sm text-blue-soft">{enroll.secret}</div>
<a href={enroll.otpauth_uri} className="mt-2 inline-block text-[11px] text-muted underline">
Open in authenticator
</a>
</div>
<Field label="Authentication code" value={code} onChange={setCode} autoFocus />
{err && <p className="text-sm text-risk">{err}</p>}
<Submit busy={busy} icon={<ShieldCheck size={15} />} label="Activate and sign in" />
</form>
)}
</div>
</div>
)
}
function Field({
label,
value,
onChange,
type = 'text',
autoFocus,
maxLength,
}: {
label: string
value: string
onChange: (v: string) => void
type?: string
autoFocus?: boolean
maxLength?: number
}) {
return (
<label className="block">
<span className="mb-1 block text-[11px] font-semibold uppercase tracking-wider text-muted">{label}</span>
<input
type={type}
value={value}
autoFocus={autoFocus}
maxLength={maxLength}
onChange={(e) => onChange(e.target.value)}
className="w-full rounded-lg border border-line/70 bg-navy-950/60 px-3 py-2 text-sm text-ink outline-none focus:border-blue/60"
/>
</label>
)
}
function Submit({ busy, icon, label }: { busy: boolean; icon: React.ReactNode; label: string }) {
return (
<button
type="submit"
disabled={busy}
className="flex w-full items-center justify-center gap-2 rounded-lg bg-gradient-to-r from-blue to-navy-700 px-4 py-2 text-sm font-bold text-white hover:brightness-110 disabled:opacity-50"
>
{busy ? <Loader2 className="animate-spin" size={15} /> : icon}
{label}
</button>
)
}