Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding ACUL support for customized-consent screen #58

Merged
merged 1 commit into from
Mar 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions packages/auth0-acul-js/auth0-acul-js/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import AcceptInvitation from './accept-invitation';
import EmailIdentifierChallenge from './email-identifier-challenge';
import InterstitialCaptcha from './interstitial-captcha';
import LoginId from './login-id';
import LoginPassword from './login-password';
import LoginPasswordlessEmailCode from './login-passwordless-email-code';
import LoginPasswordlessSmsOtp from './login-passwordless-sms-otp';
import Login from './login';
import MfaBeginEnrollOptions from './mfa-begin-enroll-options';
import MfaCountryCodes from './mfa-country-codes';
import MfaDetectBrowserCapabilities from './mfa-detect-browser-capabilities';
import MfaEmailChallenge from './mfa-email-challenge';
import MfaEmailList from './mfa-email-list';
import MfaEnrollResult from './mfa-enroll-result';
import MfaLoginOptions from './mfa-login-options';
import MfaOtpChallenge from './mfa-otp-challenge';
import MfaOtpEnrollmentCode from './mfa-otp-enrollment-code';
import MfaOtpEnrollmentQr from './mfa-otp-enrollment-qr';
import MfaPushChallengePush from './mfa-push-challenge-push';
import MfaPushEnrollmentQr from './mfa-push-enrollment-qr';
import MfaPushList from './mfa-push-list';
import MfaPushWelcome from './mfa-push-welcome';
import MfaSmsChallenge from './mfa-sms-challenge';
import MfaSmsEnrollment from './mfa-sms-enrollment';
import MfaSmsList from './mfa-sms-list';
import OrganizationPicker from './organization-picker';
import OrganizationSelection from './organization-selection';
import PasskeyEnrollmentLocal from './passkey-enrollment-local';
import PasskeyEnrollment from './passkey-enrollment';
import PhoneIdentifierChallenge from './phone-identifier-challenge';
import PhoneIdentifierEnrollment from './phone-identifier-enrollment';
import ResetPasswordEmail from './reset-password-email';
import ResetPasswordError from './reset-password-error';
import ResetPasswordMfaEmailChallenge from './reset-password-mfa-email-challenge';
import ResetPasswordMfaOtpChallenge from './reset-password-mfa-otp-challenge';
import ResetPasswordMfaPushChallengePush from './reset-password-mfa-push-challenge-push';
import ResetPasswordMfaSmsChallenge from './reset-password-mfa-sms-challenge';
import ResetPasswordRequest from './reset-password-request';
import ResetPasswordSuccess from './reset-password-success';
import ResetPassword from './reset-password';
import SignupId from './signup-id';
import SignupPassword from './signup-password';
import Signup from './signup';




* [Customized Consent](examples/customized-consent.md)
95 changes: 95 additions & 0 deletions packages/auth0-acul-js/examples/customized-consent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import CustomizedConsent from '@auth0/auth0-acul-js/customized-consent';

# Customized Consent Screen

This screen is displayed when a user needs to consent to specific scopes.

## React Component Example with TailwindCSS

```tsx
import React from 'react';
import CustomizedConsent from '@auth0/auth0-acul-js/customized-consent';

const CustomizedConsentScreen: React.FC = () => {
const customizedConsentManager = new CustomizedConsent();
const { screen } = customizedConsentManager;

const handleAccept = async () => {
try {
await customizedConsentManager.accept();
} catch (error) {
console.error('Failed to accept consent:', error);
}
};

const handleDeny = async () => {
try {
await customizedConsentManager.deny();
} catch (error) {
console.error('Failed to deny consent:', error);
}
};

return (
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-100">
<div className="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4">
<h2 className="text-2xl font-bold mb-4">Consent to the following scopes:</h2>
{
screen.data?.scopes && Object.entries(screen.data.scopes).map(([scope, description]) => (
<div key={scope} className="mb-4">
<h3 className="text-xl font-semibold">{scope}</h3>
<ul>
{description.map((desc, index) => (
<li key={index} className="text-gray-700">{desc}</li>
))}
</ul>
</div>
))
}
<div className="flex justify-between">
<button
className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
type="button"
onClick={handleDeny}
>
Decline
</button>
<button
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
type="button"
onClick={handleAccept}
>
Accept
</button>
</div>
</div>
</div>
);
};

export default CustomizedConsentScreen;
```

## Usage Examples

### Accept Consent

```typescript
import CustomizedConsent from '@auth0/auth0-acul-js/customized-consent';

const customizedConsent = new CustomizedConsent();

// Accept the consent
await customizedConsent.accept();
```

### Decline Consent

```typescript
import CustomizedConsent from '@auth0/auth0-acul-js/customized-consent';

const customizedConsent = new CustomizedConsent();

// Decline the consent
await customizedConsent.deny();
```
1 change: 1 addition & 0 deletions packages/auth0-acul-js/interfaces/export/extended-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@ export type { ScreenMembersOnMfaOtpEnrollmentCode } from '../screens/mfa-otp-enr
export type { ScreenMembersOnResetPasswordMfaOtpChallenge } from '../screens/reset-password-mfa-otp-challenge';
export type { ScreenMembersOnOrganizationSelection } from '../screens/organization-selection';
export type { ScreenMembersOnAcceptInvitation } from '../screens/accept-invitation';
export type { ScreenMembersOnCustomizedConsent } from '../screens/customized-consent';
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,4 @@ export type { ResetPasswordMfaOtpChallengeMembers } from '../screens/reset-passw
export type { OrganizationSelectionMembers } from '../screens/organization-selection';
export type { OrganizationPickerMembers } from '../screens/organization-picker';
export type { AcceptInvitationMembers } from '../screens/accept-invitation';
export type { CustomizedConsentMembers } from '../screens/customized-consent';
2 changes: 1 addition & 1 deletion packages/auth0-acul-js/interfaces/models/screen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export interface ScreenMembers {
captchaSiteKey: string | null;
captchaProvider: string | null;
isCaptchaAvailable: boolean;
data: Record<string, string | boolean | string[] | Array<PhonePrefix>> | null;
data: Record<string, string | boolean | string[] | Record<string, string[]> | Array<PhonePrefix>> | null;
links: Record<string, string> | null;
texts: Record<string, string> | null;
captcha: CaptchaContext | null;
Expand Down
33 changes: 33 additions & 0 deletions packages/auth0-acul-js/interfaces/screens/customized-consent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { CustomOptions } from '../../interfaces/common';
import type { BaseMembers } from '../../interfaces/models/base-context';
import type { ScreenMembers } from '../../interfaces/models/screen';

/**
* Interface describing the data available on the Customized Consent screen.
*/
export interface ScreenMembersOnCustomizedConsent extends ScreenMembers {
data: {
scopes: Record<string, string[]>;
} | null;
}

/**
* Interface describing the members of the Customized Consent screen.
*/
export interface CustomizedConsentMembers extends BaseMembers {
screen: ScreenMembersOnCustomizedConsent;

/**
* Accepts the consent.
* @param {CustomOptions} [payload] - Optional payload.
* @returns {Promise<void>}
*/
accept(payload?: CustomOptions): Promise<void>;

/**
* Declines the consent.
* @param {CustomOptions} [payload] - Optional payload.
* @returns {Promise<void>}
*/
deny(payload?: CustomOptions): Promise<void>;
}
2 changes: 2 additions & 0 deletions packages/auth0-acul-js/interfaces/screens/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,5 @@ export * as ResetPasswordMfaSmsChallenge from './reset-password-mfa-sms-challeng
export * as ResetPasswordMfaOtpChallenge from './reset-password-mfa-otp-challenge';
export * as OrganizationSelection from './organization-selection';
export * as OrganizationPicker from './organization-picker';
export * as AcceptInvitation from './accept-invitation';
export * as CustomizedConsent from './customized-consent';
4 changes: 4 additions & 0 deletions packages/auth0-acul-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,10 @@
"./accept-invitation": {
"import": "./dist/screens/accept-invitation/index.js",
"types": "./dist/types/src/screens/accept-invitation/index.d.ts"
},
"./customized-consent": {
"import": "./dist/screens/customized-consent/index.js",
"types": "./dist/types/src/screens/customized-consent/index.d.ts"
}
},
"scripts": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { BaseContext } from '../../models/base-context';
import { ScreenIds } from '../../utils/enums';
import { FormHandler } from '../../utils/form-handler';

import { ScreenOverride } from './screen-override';

import type { CustomOptions } from '../../../interfaces/common';
import type { ScreenContext } from '../../../interfaces/models/screen';
import type { AcceptInvitationMembers, ScreenMembersOnAcceptInvitation as ScreenOptions } from '../../../interfaces/screens/accept-invitation';
Expand All @@ -21,7 +23,7 @@ export default class AcceptInvitation extends BaseContext implements AcceptInvit
constructor() {
super();
const screenContext = this.getContext('screen') as ScreenContext;
this.screen = screenContext as ScreenOptions;
this.screen = new ScreenOverride(screenContext);
}

/**
Expand Down
60 changes: 60 additions & 0 deletions packages/auth0-acul-js/src/screens/customized-consent/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { BaseContext } from '../../models/base-context';
import { ScreenIds } from '../../utils/enums';
import { FormHandler } from '../../utils/form-handler';

import { ScreenOverride } from './screen-override';

import type { CustomOptions } from '../../../interfaces/common';
import type { ScreenContext } from '../../../interfaces/models/screen';
import type { CustomizedConsentMembers, ScreenMembersOnCustomizedConsent } from '../../../interfaces/screens/customized-consent';

/**
* Class implementing the Customized Consent screen functionality.
*/
export default class CustomizedConsent extends BaseContext implements CustomizedConsentMembers {
static screenIdentifier: string = ScreenIds.CUSTOMIZED_CONSENT;
screen: ScreenMembersOnCustomizedConsent;

/**
* Creates an instance of the CustomizedConsent screen.
*/
constructor() {
super();
const screenContext = this.getContext('screen') as ScreenContext;
this.screen = new ScreenOverride(screenContext);
}

/**
* Accepts the consent.
* @param {CustomOptions} [payload] - Optional payload.
* @returns {Promise<void>}
*/
async accept(payload?: CustomOptions): Promise<void> {
const options = {
state: this.transaction.state,
telemetry: [CustomizedConsent.screenIdentifier, 'accept'],
};

await new FormHandler(options).submitData<CustomOptions>({
...payload,
action: 'accept',
});
}

/**
* Declines the consent.
* @param {CustomOptions} [payload] - Optional payload.
* @returns {Promise<void>}
*/
async deny(payload?: CustomOptions): Promise<void> {
const options = {
state: this.transaction.state,
telemetry: [CustomizedConsent.screenIdentifier, 'deny'],
};

await new FormHandler(options).submitData<CustomOptions>({
...payload,
action: 'deny',
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Screen } from '../../models/screen';

import type { ScreenContext } from '../../../interfaces/models/screen';
import type { ScreenMembersOnCustomizedConsent as OverrideOptions } from '../../../interfaces/screens/customized-consent';

/**
* Screen override class for the customized-consent screen
*/
export class ScreenOverride extends Screen implements OverrideOptions {
data: OverrideOptions['data'];

constructor(screenContext: ScreenContext) {
super(screenContext);
this.data = ScreenOverride.getScreenData(screenContext);
}

/**
* Extracts and transforms the screen data from the context
* @param screenContext The screen context containing the data
* @returns The transformed screen data
*/
static getScreenData = (screenContext: ScreenContext): OverrideOptions['data'] => {
const data = screenContext.data;
if (!data) {
return null;
}

return (data.scopes ? { scopes: data.scopes } : null) as OverrideOptions['data'];
};
}
1 change: 1 addition & 0 deletions packages/auth0-acul-js/src/screens/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,4 @@ export { default as ResetPasswordMfaOtpChallenge } from './reset-password-mfa-ot
export { default as OrganizationSelection } from './organization-selection';
export { default as OrganizationPicker } from './organization-picker';
export { default as AcceptInvitation } from './accept-invitation';
export { default as CustomizedConsent } from './customized-consent';
1 change: 1 addition & 0 deletions packages/auth0-acul-js/src/utils/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,5 @@ export const ScreenIds = {
ORGANIZATION_SELECTION: 'organization-selection',
ORGANIZATION_PICKER: 'organization-picker',
ACCEPT_INVITATION: 'accept-invitation',
CUSTOMIZED_CONSENT: 'customized-consent',
};