-
-
Notifications
You must be signed in to change notification settings - Fork 166
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
617 additions
and
6,444 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
import React, { useState } from 'react' | ||
import { Smartphone, Key } from 'lucide-react' | ||
|
||
const styles = { | ||
container: { | ||
minHeight: '100vh', | ||
backgroundColor: '#f3f4f6', | ||
display: 'flex', | ||
alignItems: 'center', | ||
justifyContent: 'center', | ||
padding: '1rem', | ||
}, | ||
card: { | ||
backgroundColor: 'white', | ||
borderRadius: '0.5rem', | ||
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)', | ||
width: '100%', | ||
maxWidth: '28rem', | ||
padding: '1.5rem', | ||
}, | ||
title: { | ||
fontSize: '1.5rem', | ||
fontWeight: 'bold', | ||
marginBottom: '0.5rem', | ||
}, | ||
description: { | ||
color: '#6b7280', | ||
marginBottom: '1.5rem', | ||
}, | ||
radioGroup: { | ||
display: 'flex', | ||
flexDirection: 'column', | ||
gap: '1rem', | ||
}, | ||
radioOption: { | ||
display: 'flex', | ||
alignItems: 'center', | ||
border: '1px solid #e5e7eb', | ||
borderRadius: '0.375rem', | ||
padding: '1rem', | ||
cursor: 'pointer', | ||
}, | ||
radioInput: { | ||
marginRight: '0.75rem', | ||
}, | ||
radioLabel: { | ||
display: 'flex', | ||
alignItems: 'center', | ||
flex: 1, | ||
}, | ||
radioText: { | ||
marginLeft: '0.75rem', | ||
}, | ||
radioTitle: { | ||
fontWeight: 'bold', | ||
}, | ||
radioDescription: { | ||
fontSize: '0.875rem', | ||
color: '#6b7280', | ||
}, | ||
checkboxContainer: { | ||
display: 'flex', | ||
alignItems: 'center', | ||
marginTop: '1rem', | ||
}, | ||
checkbox: { | ||
marginRight: '0.5rem', | ||
}, | ||
button: { | ||
backgroundColor: '#3b82f6', | ||
color: 'white', | ||
padding: '0.5rem 1rem', | ||
borderRadius: '0.25rem', | ||
border: 'none', | ||
cursor: 'pointer', | ||
width: '100%', | ||
marginTop: '1.5rem', | ||
}, | ||
buttonDisabled: { | ||
backgroundColor: '#9ca3af', | ||
cursor: 'not-allowed', | ||
}, | ||
link: { | ||
color: '#3b82f6', | ||
textDecoration: 'none', | ||
fontSize: '0.875rem', | ||
marginTop: '1rem', | ||
display: 'inline-block', | ||
}, | ||
} | ||
|
||
export default function MFASelectionPage() { | ||
const [selectedMethod, setSelectedMethod] = useState(undefined) | ||
const [rememberMethod, setRememberMethod] = useState(false) | ||
|
||
return ( | ||
<div style={styles.container}> | ||
<div style={styles.card}> | ||
<h2 style={styles.title}>Multi-factor Authentication</h2> | ||
<p style={styles.description}> | ||
Your account is protected with multi-factor authentication (MFA). | ||
To finish signing in, select a method to authenticate with. | ||
</p> | ||
<div style={styles.radioGroup}> | ||
<label style={styles.radioOption}> | ||
<input | ||
type="radio" | ||
value="authenticator" | ||
checked={selectedMethod === 'authenticator'} | ||
onChange={() => setSelectedMethod('authenticator')} | ||
style={styles.radioInput} | ||
/> | ||
<div style={styles.radioLabel}> | ||
<Smartphone size={20} /> | ||
<div style={styles.radioText}> | ||
<div style={styles.radioTitle}>Authenticator app</div> | ||
<div style={styles.radioDescription}> | ||
Authenticate using a code generated by an app installed on your mobile device or computer. | ||
</div> | ||
</div> | ||
</div> | ||
</label> | ||
<label style={styles.radioOption}> | ||
<input | ||
type="radio" | ||
value="passkey" | ||
checked={selectedMethod === 'passkey'} | ||
onChange={() => setSelectedMethod('passkey')} | ||
style={styles.radioInput} | ||
/> | ||
<div style={styles.radioLabel}> | ||
<Key size={20} /> | ||
<div style={styles.radioText}> | ||
<div style={styles.radioTitle}>Passkey or security key</div> | ||
<div style={styles.radioDescription}> | ||
Authenticate using your fingerprint, face, or PIN on your mobile device, computer or FIDO2 security key. | ||
</div> | ||
</div> | ||
</div> | ||
</label> | ||
</div> | ||
<div style={styles.checkboxContainer}> | ||
<input | ||
type="checkbox" | ||
id="remember" | ||
checked={rememberMethod} | ||
onChange={(e) => setRememberMethod(e.target.checked)} | ||
style={styles.checkbox} | ||
/> | ||
<label htmlFor="remember">Remember this method</label> | ||
</div> | ||
<button | ||
style={{ | ||
...styles.button, | ||
...(selectedMethod ? {} : styles.buttonDisabled), | ||
}} | ||
disabled={!selectedMethod} | ||
onClick={() => { | ||
// Handle continue action | ||
console.log('Continuing with method:', selectedMethod) | ||
}} | ||
> | ||
Continue | ||
</button> | ||
<a href="#" style={styles.link} onClick={() => { | ||
// Handle sign in to different account | ||
console.log('Signing in to a different account') | ||
}}> | ||
Sign in to a different account | ||
</a> | ||
</div> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
import React, { useState, useEffect } from 'react' | ||
import { Shield, AlertCircle } from 'lucide-react' | ||
|
||
const styles = { | ||
container: { | ||
minHeight: '100vh', | ||
backgroundColor: '#f3f4f6', | ||
display: 'flex', | ||
alignItems: 'center', | ||
justifyContent: 'center', | ||
padding: '1rem', | ||
}, | ||
card: { | ||
backgroundColor: 'white', | ||
borderRadius: '0.5rem', | ||
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)', | ||
width: '100%', | ||
maxWidth: '28rem', | ||
padding: '1.5rem', | ||
}, | ||
header: { | ||
display: 'flex', | ||
alignItems: 'center', | ||
marginBottom: '1rem', | ||
}, | ||
title: { | ||
fontSize: '1.5rem', | ||
fontWeight: 'bold', | ||
marginLeft: '0.5rem', | ||
}, | ||
description: { | ||
color: '#6b7280', | ||
marginBottom: '1.5rem', | ||
}, | ||
errorContainer: { | ||
backgroundColor: '#FEE2E2', | ||
border: '1px solid #F87171', | ||
borderRadius: '0.375rem', | ||
padding: '1rem', | ||
marginBottom: '1.5rem', | ||
display: 'flex', | ||
alignItems: 'flex-start', | ||
}, | ||
errorIcon: { | ||
color: '#DC2626', | ||
marginRight: '0.5rem', | ||
flexShrink: 0, | ||
}, | ||
errorMessage: { | ||
color: '#DC2626', | ||
fontSize: '0.875rem', | ||
}, | ||
button: { | ||
backgroundColor: '#3b82f6', | ||
color: 'white', | ||
padding: '0.5rem 1rem', | ||
borderRadius: '0.25rem', | ||
border: 'none', | ||
cursor: 'pointer', | ||
width: '100%', | ||
marginTop: '1rem', | ||
}, | ||
link: { | ||
color: '#3b82f6', | ||
textDecoration: 'none', | ||
fontSize: '0.875rem', | ||
marginTop: '1rem', | ||
display: 'inline-block', | ||
}, | ||
} | ||
|
||
// Simulated WebAuthn API call | ||
const simulateWebAuthnAuthentication = (): Promise<void> => { | ||
return new Promise((resolve, reject) => { | ||
setTimeout(() => { | ||
// Simulate a 50% chance of success | ||
if (Math.random() < 0.5) { | ||
resolve() | ||
} else { | ||
reject(new Error('The operation either timed out or was not allowed.')) | ||
} | ||
}, 2000) // Simulate a 2-second delay | ||
}) | ||
} | ||
|
||
export default function MFAWebAuthn() { | ||
const [error, setError] = useState(null) | ||
const [isAuthenticating, setIsAuthenticating] = useState(false) | ||
|
||
useEffect(() => { | ||
startAuthentication() | ||
}, []) | ||
|
||
const startAuthentication = async () => { | ||
setIsAuthenticating(true) | ||
setError(null) | ||
try { | ||
await simulateWebAuthnAuthentication() | ||
// If successful, you would typically redirect the user or update the app state | ||
console.log('Authentication successful') | ||
} catch (err) { | ||
setError((err).message) | ||
} finally { | ||
setIsAuthenticating(false) | ||
} | ||
} | ||
|
||
return ( | ||
<div style={styles.container}> | ||
<div style={styles.card}> | ||
<div style={styles.header}> | ||
<Shield size={24} /> | ||
<h2 style={styles.title}>Keeping you secure</h2> | ||
</div> | ||
<p style={styles.description}> | ||
Your account is protected with a passkey or security key for multi-factor authentication (MFA). | ||
To finish signing in, follow the instructions from your browser or you can select another MFA method. | ||
</p> | ||
{error && ( | ||
<div style={styles.errorContainer}> | ||
<AlertCircle style={styles.errorIcon} size={20} /> | ||
<div style={styles.errorMessage}> | ||
<strong>Unable to authenticate.</strong><br /> | ||
{error} See: <a href="https://www.w3.org/TR/webauthn-2/#sctn-privacy-considerations-client" target="_blank" rel="noopener noreferrer">W3C WebAuthn Specification</a>. | ||
</div> | ||
</div> | ||
)} | ||
<button | ||
style={styles.button} | ||
onClick={startAuthentication} | ||
disabled={isAuthenticating} | ||
> | ||
{isAuthenticating ? 'Authenticating...' : 'Try Again'} | ||
</button> | ||
<a href="#" style={styles.link} onClick={() => { | ||
// Handle selecting another MFA method | ||
console.log('Selecting another MFA method') | ||
}}> | ||
Select another MFA method | ||
</a> | ||
<br></br> | ||
|
||
<a href="#" style={styles.link} onClick={() => { | ||
console.log('Signing in to a different account') | ||
}}> | ||
Sign in to a different account | ||
</a> | ||
<a href="#" style={styles.link} onClick={() => { | ||
console.log('Trouble signing in') | ||
}}> | ||
Trouble signing in? | ||
</a> | ||
</div> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.