Skip to content

Commit d112ce4

Browse files
Merge pull request #25 from auth0-samples/consent
feat: add Consent Screen for user consent management
2 parents 9962022 + 60a55ba commit d112ce4

File tree

5 files changed

+115
-228
lines changed

5 files changed

+115
-228
lines changed

src/App.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,20 +51,22 @@ const MfaRecoveryCodeEnrollmentScreen = React.lazy(() => import("./screens/mfa-r
5151
const ResetPasswordMfaPhoneChallengeScreen = React.lazy(() => import("./screens/reset-password-mfa-phone-challenge"));
5252
const PasskeyEnrollmentScreen = React.lazy(() => import("./screens/passkey-enrollment"));
5353
const MfaRecoveryCodeChallengeNewCodeScreen = React.lazy(() => import("./screens/mfa-recovery-code-challenge-new-code"));
54-
const EmailOTPChallengeScreen = React.lazy(() => import("./screens/email-otp-challenge"));
54+
// const EmailOTPChallengeScreen = React.lazy(() => import("./screens/email-otp-challenge"));
5555
const LogoutScreen = React.lazy(() => import("./screens/logout"));
5656
const LogoutAbortedScreen = React.lazy(() => import("./screens/logout-aborted"));
5757
const LogoutCompleteScreen = React.lazy(() => import("./screens/logout-complete"));
5858
const EmailVerificationResultScreen = React.lazy(() => import("./screens/email-verification-result"));
5959
const LoginEmailVerificationScreen = React.lazy(() => import("./screens/login-email-verification"));
6060
const MfaWebAuthnErrorScreen = React.lazy(() => import("./screens/mfa-webauthn-error"));
6161
const MfaWebAuthnPlatformEnrollmentScreen = React.lazy(() => import("./screens/mfa-webauthn-platform-enrollment"));
62-
const MfaWebAuthnNotAvailableErrorScreen = React.lazy(() => import("./screens/mfa-webauthn-not-available-error"))
62+
// const MfaWebAuthnNotAvailableErrorScreen = React.lazy(() => import("./screens/mfa-webauthn-not-available-error"))
6363
const MfaWebAuthnRoamingEnrollment = React.lazy(() => import("./screens/mfa-webauthn-roaming-enrollment"))
6464
const MfaWebAuthnRoamingChallengeScreen = React.lazy(() => import("./screens/mfa-webauthn-roaming-challenge"));
6565
const MfaWebAuthnPlatformChallengeScreen = React.lazy(() => import("./screens/mfa-webauthn-platform-challenge"));
6666
const MfaWebAuthnEnrollmentSuccessScreen = React.lazy(() => import("./screens/mfa-webauthn-enrollment-success"));
6767
const MfaWebAuthnChangeKeyNicknameScreen = React.lazy(() => import("./screens/mfa-webauthn-change-key-nickname"));
68+
// const ConsentScreen = React.lazy(() => import("./screens/consent"));
69+
const ResetPasswordMfaWebAuthnPlatformChallengeScreen = React.lazy(() => import("./screens/reset-password-mfa-webauthn-platform-challenge"));
6870

6971
const App: React.FC = () => {
7072
const [screen, setScreen] = React.useState("login-id");
@@ -175,8 +177,8 @@ const App: React.FC = () => {
175177
return <PasskeyEnrollmentScreen />;
176178
case "mfa-recovery-code-challenge-new-code":
177179
return <MfaRecoveryCodeChallengeNewCodeScreen />;
178-
case "email-otp-challenge":
179-
return <EmailOTPChallengeScreen />;
180+
// case "email-otp-challenge":
181+
// return <EmailOTPChallengeScreen />;
180182
case "logout":
181183
return <LogoutScreen />;
182184
case "logout-aborted":
@@ -191,8 +193,8 @@ const App: React.FC = () => {
191193
return <MfaWebAuthnErrorScreen />;
192194
case "mfa-webauthn-platform-enrollment":
193195
return <MfaWebAuthnPlatformEnrollmentScreen />;
194-
case "mfa-webauthn-not-available-error":
195-
return <MfaWebAuthnNotAvailableErrorScreen />
196+
// case "mfa-webauthn-not-available-error":
197+
// return <MfaWebAuthnNotAvailableErrorScreen />
196198
case "mfa-webauthn-roaming-enrollment":
197199
return <MfaWebAuthnRoamingEnrollment />
198200
case "mfa-webauthn-roaming-challenge":
@@ -201,12 +203,17 @@ const App: React.FC = () => {
201203
return <MfaWebAuthnPlatformChallengeScreen />
202204
case "mfa-webauthn-enrollment-success":
203205
return <MfaWebAuthnEnrollmentSuccessScreen />
206+
case "reset-password-mfa-webauthn-platform-challenge":
207+
return <ResetPasswordMfaWebAuthnPlatformChallengeScreen />;
208+
// case "consent":
209+
// return <ConsentScreen />;
204210
case "mfa-webauthn-change-key-nickname":
205211
return <MfaWebAuthnChangeKeyNicknameScreen />
206212
default:
207213
return <>No screen rendered</>;
208214
}
209215
};
216+
210217
return <Suspense fallback={<div>Loading...</div>}>{renderScreen()}</Suspense>;
211218
};
212219

src/screens/customized-consent/index.tsx

Lines changed: 0 additions & 61 deletions
This file was deleted.

src/screens/email-otp-challenge/index.tsx

Lines changed: 0 additions & 115 deletions
This file was deleted.

src/screens/mfa-webauthn-not-available-error/index.tsx

Lines changed: 0 additions & 46 deletions
This file was deleted.
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import React, { useState, useMemo, useCallback, useEffect } from 'react';
2+
import ResetPasswordMfaWebAuthnPlatformChallenge, {
3+
type ContinueWithPasskeyOptions,
4+
type TryAnotherMethodOptions,
5+
} from '@auth0/auth0-acul-js/reset-password-mfa-webauthn-platform-challenge';
6+
7+
const ResetPasswordMfaWebAuthnPlatformChallengeComponent: React.FC = () => {
8+
const sdk = useMemo(() => new ResetPasswordMfaWebAuthnPlatformChallenge(), []);
9+
const { screen, transaction, client } = sdk;
10+
const texts = screen.texts ?? {};
11+
const { publicKey: publicKeyChallengeOptions, showRememberDevice } = screen;
12+
13+
const [rememberDevice, setRememberDevice] = useState(false);
14+
15+
const handleVerify = useCallback(() => {
16+
const opts: ContinueWithPasskeyOptions = {};
17+
if (showRememberDevice) {
18+
opts.rememberDevice = rememberDevice;
19+
}
20+
sdk.continueWithPasskey(opts);
21+
}, [sdk, rememberDevice, showRememberDevice]);
22+
23+
const handleTryAnotherMethod = useCallback(() => {
24+
const opts: TryAnotherMethodOptions = {}; // Add custom options if needed
25+
sdk.tryAnotherMethod(opts);
26+
}, [sdk]);
27+
28+
// Effect to automatically trigger verification if publicKeyChallengeOptions are available.
29+
// This provides a more seamless UX, prompting the user immediately.
30+
useEffect(() => {
31+
if (publicKeyChallengeOptions) { // Check !isLoading to prevent re-triggering if already in process
32+
console.log("WebAuthn platform challenge options available. Automatically attempting verification.");
33+
handleVerify();
34+
}
35+
}, [handleVerify, publicKeyChallengeOptions]);
36+
37+
return (
38+
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-100 p-4 antialiased">
39+
<div className="w-full max-w-md bg-white rounded-lg shadow-xl p-8 space-y-6">
40+
{client.logoUrl && (
41+
<img src={client.logoUrl} alt={client.name ?? 'Client Logo'} className="mx-auto h-12 mb-6" />
42+
)}
43+
<div className="text-center">
44+
<h1 className="text-2xl font-bold text-gray-800">
45+
{texts.title ?? 'Verify Your Identity'}
46+
</h1>
47+
<p className="mt-2 text-gray-600">
48+
{texts.description ?? 'Please use your device\'s screen lock (fingerprint, face, PIN) or a connected security key to continue resetting your password.'}
49+
</p>
50+
</div>
51+
52+
{transaction.errors && transaction.errors.length > 0 && (
53+
<div className="bg-red-50 border-l-4 border-red-400 text-red-700 p-4 rounded-md" role="alert">
54+
<p className="font-bold">{texts.alertListTitle ?? 'Errors:'}</p>
55+
{transaction.errors.map((err, index) => (
56+
<p key={`tx-err-${index}`}>{err.message}</p>
57+
))}
58+
</div>
59+
)}
60+
61+
{showRememberDevice && (
62+
<div className="flex items-center justify-center mt-4">
63+
<input
64+
id="rememberDevice"
65+
name="rememberDevice"
66+
type="checkbox"
67+
checked={rememberDevice}
68+
onChange={(e) => setRememberDevice(e.target.checked)}
69+
className="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded"
70+
/>
71+
<label htmlFor="rememberDevice" className="ml-2 block text-sm text-gray-900">
72+
{texts.rememberMeText ?? 'Remember this device for 30 days'}
73+
</label>
74+
</div>
75+
)}
76+
77+
<div className="space-y-4 mt-6">
78+
{/* Button to manually trigger verification if auto-trigger fails or as a retry option */}
79+
{/* This might only be shown if publicKeyChallengeOptions exist but initial auto-verify failed */}
80+
{publicKeyChallengeOptions && (
81+
<button
82+
onClick={handleVerify}
83+
disabled={!publicKeyChallengeOptions}
84+
className="w-full flex justify-center items-center px-4 py-2.5 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-60 disabled:cursor-not-allowed"
85+
>
86+
{(texts.retryButtonText ?? 'Retry Verification')}
87+
</button>
88+
)}
89+
90+
<button
91+
onClick={handleTryAnotherMethod}
92+
className="w-full flex justify-center px-4 py-2.5 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-60 disabled:cursor-not-allowed"
93+
>
94+
{texts.pickAuthenticatorText ?? 'Try Another Method'}
95+
</button>
96+
</div>
97+
</div>
98+
</div>
99+
);
100+
};
101+
102+
export default ResetPasswordMfaWebAuthnPlatformChallengeComponent;

0 commit comments

Comments
 (0)