Skip to content
Draft
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
3 changes: 3 additions & 0 deletions packages/auth0-acul-js/interfaces/models/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@
enabled: boolean;
};
};
password_options?: {
complexity: any // TODO, this should be added to auth0-server https://github.com/atko-cic/auth0-server/blob/b957b6eaac93b69bc72a836e4774c6184a5e2a92/packages/%40types/types/packages/Acul.ts#L181

Check failure on line 108 in packages/auth0-acul-js/interfaces/models/transaction.ts

View workflow job for this annotation

GitHub Actions / Build (Lint + Test + Build + Docs) (Monorepo Root)

Unexpected any. Specify a different type
}
};
}

Expand Down
13 changes: 9 additions & 4 deletions packages/auth0-acul-js/interfaces/screens/signup.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import type { IdentifierType } from '../../src/constants';
import type { BaseMembers } from '../models/base-context';
import type { ScreenMembers } from '../models/screen';
import type { TransactionMembers, UsernamePolicy, PasswordPolicy } from '../models/transaction';
import type { IdentifierType } from "../../src/constants";
import type { BaseMembers } from "../models/base-context";
import type { ScreenMembers } from "../models/screen";
import type {
TransactionMembers,
UsernamePolicy,
PasswordPolicy,
} from "../models/transaction";

export interface SignupOptions {
email?: string;
Expand All @@ -27,6 +31,7 @@
requiredIdentifiers: IdentifierType[] | null;
optionalIdentifiers: IdentifierType[] | null;
passwordPolicy: PasswordPolicy | null;
passwordComplexity?: any; // TODO: Define the type for password complexity (get from server https://github.com/atko-cic/auth0-server/blob/b957b6eaac93b69bc72a836e4774c6184a5e2a92/packages/%40types/types/packages/Acul.ts#L181)

Check failure on line 34 in packages/auth0-acul-js/interfaces/screens/signup.ts

View workflow job for this annotation

GitHub Actions / Build (Lint + Test + Build + Docs) (Monorepo Root)

Unexpected any. Specify a different type
}

export interface SignupMembers extends BaseMembers {
Expand Down
47 changes: 32 additions & 15 deletions packages/auth0-acul-js/src/screens/login/transaction-override.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,49 @@
import { ConnectionStrategy, Identifiers } from '../../../src/constants';
import { Transaction } from '../../models/transaction';
import { isSignupEnabled, isForgotPasswordEnabled, isPasskeyEnabled, getPasswordPolicy, getAllowedIdentifiers } from '../../shared/transaction';
import { ConnectionStrategy, Identifiers } from "../../../src/constants";
import { Transaction } from "../../models/transaction";
import {
isSignupEnabled,
isForgotPasswordEnabled,
isPasskeyEnabled,
getPasswordPolicy,
getAllowedIdentifiers,
} from "../../shared/transaction";

import type { TransactionContext } from '../../../interfaces/models/transaction';
import type { TransactionMembersOnLogin as OverrideOptions } from '../../../interfaces/screens/login';
import type { TransactionContext } from "../../../interfaces/models/transaction";
import type { TransactionMembersOnLogin as OverrideOptions } from "../../../interfaces/screens/login";

/**
* Login transaction override implementation
*/
export class TransactionOverride extends Transaction implements OverrideOptions {
isSignupEnabled: OverrideOptions['isSignupEnabled'];
isForgotPasswordEnabled: OverrideOptions['isForgotPasswordEnabled'];
isPasskeyEnabled: OverrideOptions['isPasskeyEnabled'];
passwordPolicy: OverrideOptions['passwordPolicy'];
allowedIdentifiers: OverrideOptions['allowedIdentifiers'];
export class TransactionOverride
extends Transaction
implements OverrideOptions
{
isSignupEnabled: OverrideOptions["isSignupEnabled"];
isForgotPasswordEnabled: OverrideOptions["isForgotPasswordEnabled"];
isPasskeyEnabled: OverrideOptions["isPasskeyEnabled"];
passwordPolicy: OverrideOptions["passwordPolicy"];
allowedIdentifiers: OverrideOptions["allowedIdentifiers"];

constructor(transactionContext: TransactionContext) {
super(transactionContext);
this.isSignupEnabled = isSignupEnabled(transactionContext);
this.isForgotPasswordEnabled = isForgotPasswordEnabled(transactionContext);
this.isPasskeyEnabled = isPasskeyEnabled(transactionContext);
this.passwordPolicy = getPasswordPolicy(transactionContext);
this.allowedIdentifiers = TransactionOverride.getAllowedIdentifiers(transactionContext, this.connectionStrategy);
this.allowedIdentifiers = TransactionOverride.getAllowedIdentifiers(
transactionContext,
this.connectionStrategy
);
}

static getAllowedIdentifiers(transactionContext: TransactionContext, connectionStrategy: string | null): OverrideOptions['allowedIdentifiers'] {
if (connectionStrategy === ConnectionStrategy.SMS) return [Identifiers.PHONE];
if (connectionStrategy === ConnectionStrategy.EMAIL) return [Identifiers.EMAIL];
static getAllowedIdentifiers(
transactionContext: TransactionContext,
connectionStrategy: string | null
): OverrideOptions["allowedIdentifiers"] {
if (connectionStrategy === ConnectionStrategy.SMS)
return [Identifiers.PHONE];
if (connectionStrategy === ConnectionStrategy.EMAIL)
return [Identifiers.EMAIL];
return getAllowedIdentifiers(transactionContext);
}
}
50 changes: 35 additions & 15 deletions packages/auth0-acul-js/src/screens/signup/transaction-override.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,49 @@
import { ConnectionStrategy, Identifiers } from '../../../src/constants';
import { Transaction } from '../../models/transaction';
import { isPasskeyEnabled, getUsernamePolicy, getRequiredIdentifiers, getOptionalIdentifiers, getPasswordPolicy } from '../../shared/transaction';
import { ConnectionStrategy, Identifiers } from "../../../src/constants";
import { Transaction } from "../../models/transaction";
import {
isPasskeyEnabled,
getUsernamePolicy,
getRequiredIdentifiers,
getOptionalIdentifiers,
getPasswordPolicy,
getPasswordComplexity,
} from "../../shared/transaction";

import type { TransactionContext } from '../../../interfaces/models/transaction';
import type { TransactionMembersOnSignup as OverrideOptions } from '../../../interfaces/screens/signup';
import type { TransactionContext } from "../../../interfaces/models/transaction";
import type { TransactionMembersOnSignup as OverrideOptions } from "../../../interfaces/screens/signup";

export class TransactionOverride extends Transaction implements OverrideOptions {
isPasskeyEnabled: OverrideOptions['isPasskeyEnabled'];
usernamePolicy: OverrideOptions['usernamePolicy'];
optionalIdentifiers: OverrideOptions['optionalIdentifiers'];
requiredIdentifiers: OverrideOptions['requiredIdentifiers'];
passwordPolicy: OverrideOptions['passwordPolicy'];
export class TransactionOverride
extends Transaction
implements OverrideOptions
{
isPasskeyEnabled: OverrideOptions["isPasskeyEnabled"];
usernamePolicy: OverrideOptions["usernamePolicy"];
optionalIdentifiers: OverrideOptions["optionalIdentifiers"];
requiredIdentifiers: OverrideOptions["requiredIdentifiers"];
passwordPolicy: OverrideOptions["passwordPolicy"];
passwordComplexity: OverrideOptions["passwordComplexity"];

constructor(transactionContext: TransactionContext) {
super(transactionContext);
this.isPasskeyEnabled = isPasskeyEnabled(transactionContext);
this.usernamePolicy = getUsernamePolicy(transactionContext);
this.optionalIdentifiers = getOptionalIdentifiers(transactionContext);
this.requiredIdentifiers = TransactionOverride.getRequiredIdentifiers(transactionContext, this.connectionStrategy);
this.requiredIdentifiers = TransactionOverride.getRequiredIdentifiers(
transactionContext,
this.connectionStrategy
);
this.passwordPolicy = getPasswordPolicy(transactionContext);
this.passwordComplexity = getPasswordComplexity(transactionContext);
}

static getRequiredIdentifiers(transactionContext: TransactionContext, connectionStrategy: string | null): OverrideOptions['requiredIdentifiers'] {
if (connectionStrategy === ConnectionStrategy.SMS) return [Identifiers.PHONE];
if (connectionStrategy === ConnectionStrategy.EMAIL) return [Identifiers.EMAIL];
static getRequiredIdentifiers(
transactionContext: TransactionContext,
connectionStrategy: string | null
): OverrideOptions["requiredIdentifiers"] {
if (connectionStrategy === ConnectionStrategy.SMS)
return [Identifiers.PHONE];
if (connectionStrategy === ConnectionStrategy.EMAIL)
return [Identifiers.EMAIL];
return getRequiredIdentifiers(transactionContext);
}
}
85 changes: 66 additions & 19 deletions packages/auth0-acul-js/src/shared/transaction.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import type { DBConnection, UsernamePolicy, PasswordPolicy, TransactionContext } from '../../interfaces/models/transaction';
import type { TransactionMembersOnLoginId } from '../../interfaces/screens/login-id';
import type { TransactionMembersOnSignupId } from '../../interfaces/screens/signup-id';
import type { IdentifierType } from '../../src/constants';
import type {
DBConnection,
UsernamePolicy,
PasswordPolicy,
TransactionContext,
} from "../../interfaces/models/transaction";
import type { TransactionMembersOnLoginId } from "../../interfaces/screens/login-id";
import type { TransactionMembersOnSignupId } from "../../interfaces/screens/signup-id";
import type { IdentifierType } from "../../src/constants";

/**
* Checks if signup is enabled for the current connection.
Expand All @@ -19,7 +24,9 @@ export function isSignupEnabled(transaction: TransactionContext): boolean {
* @param transaction - The transaction context from Universal Login
* @returns True if forgot password is enabled, false otherwise
*/
export function isForgotPasswordEnabled(transaction: TransactionContext): boolean {
export function isForgotPasswordEnabled(
transaction: TransactionContext
): boolean {
const connection = transaction?.connection as DBConnection;
return connection?.options?.forgot_password_enabled === true;
}
Expand Down Expand Up @@ -53,7 +60,9 @@ export function isUsernameRequired(transaction: TransactionContext): boolean {
* @param transaction - The transaction context from Universal Login
* @returns The username policy object or null if not defined
*/
export function getUsernamePolicy(transaction: TransactionContext): UsernamePolicy | null {
export function getUsernamePolicy(
transaction: TransactionContext
): UsernamePolicy | null {
const connection = transaction?.connection as DBConnection;
const validation = connection?.options?.attributes?.username?.validation;

Expand All @@ -76,31 +85,48 @@ export function getUsernamePolicy(transaction: TransactionContext): UsernamePoli
* @param transaction - The transaction context from Universal Login
* @returns The password policy object or null if not defined
*/
export function getPasswordPolicy(transaction: TransactionContext): PasswordPolicy | null {
export function getPasswordPolicy(
transaction: TransactionContext
): PasswordPolicy | null {
const connection = transaction?.connection as DBConnection;
const passwordPolicy = connection?.options?.authentication_methods?.password;

if (!passwordPolicy) return null;

return {
minLength: passwordPolicy.min_length,
policy: passwordPolicy.policy as PasswordPolicy['policy'],
policy: passwordPolicy.policy as PasswordPolicy["policy"],
passwordSecurityInfo: passwordPolicy.password_security_info,
};
}

/**
* Retrieves the password complexity configuration from the transaction context.
* This includes properties like required character types and length requirements.
*
* @param transaction - The transaction context from Universal Login
* @returns The password complexity object or null if not defined`
*/
export function getPasswordComplexity(
transaction: TransactionContext
): any | null {
const connection = transaction?.connection as DBConnection;
return connection?.options?.password_options?.complexity || null;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SDKs can mock this rather than waiting for User Management to complete the backend work.

return {
    "min_length": 12,
    "character_types": [
      "uppercase",
      "lowercase",
      "number",
      "special"
    ],
    "character_type_rule": "three_of_four",
    "max_length_exceeded": "error",
    "identical_characters": "block",
    "sequential_characters": "block"
}

}

/**
* Returns the allowed identifiers (email, username, phone) based on the connection settings.
* This includes both required and optional identifier types.
*
* @param transaction - The transaction context from Universal Login
* @returns An array of allowed identifier types or null if none are defined
*/
export function getAllowedIdentifiers(transaction: TransactionContext): TransactionMembersOnLoginId['allowedIdentifiers'] {
export function getAllowedIdentifiers(
transaction: TransactionContext
): TransactionMembersOnLoginId["allowedIdentifiers"] {
const connection = transaction?.connection as DBConnection;
if (!connection?.options?.attributes) return null;

return extractIdentifiersByStatus(connection, ['required', 'optional']);
return extractIdentifiersByStatus(connection, ["required", "optional"]);
}

/**
Expand All @@ -109,8 +135,12 @@ export function getAllowedIdentifiers(transaction: TransactionContext): Transact
* @param transaction - The transaction context from Universal Login
* @returns An array of required identifier types or null if none are defined
*/
export function getRequiredIdentifiers(transaction: TransactionContext): TransactionMembersOnSignupId['requiredIdentifiers'] {
return extractIdentifiersByStatus(transaction?.connection as DBConnection, ['required']);
export function getRequiredIdentifiers(
transaction: TransactionContext
): TransactionMembersOnSignupId["requiredIdentifiers"] {
return extractIdentifiersByStatus(transaction?.connection as DBConnection, [
"required",
]);
}

/**
Expand All @@ -119,8 +149,12 @@ export function getRequiredIdentifiers(transaction: TransactionContext): Transac
* @param transaction - The transaction context from Universal Login
* @returns An array of optional identifier types or null if none are defined
*/
export function getOptionalIdentifiers(transaction: TransactionContext): TransactionMembersOnSignupId['optionalIdentifiers'] {
return extractIdentifiersByStatus(transaction?.connection as DBConnection, ['optional']);
export function getOptionalIdentifiers(
transaction: TransactionContext
): TransactionMembersOnSignupId["optionalIdentifiers"] {
return extractIdentifiersByStatus(transaction?.connection as DBConnection, [
"optional",
]);
}

/**
Expand All @@ -130,7 +164,9 @@ export function getOptionalIdentifiers(transaction: TransactionContext): Transac
* @param transaction - The transaction context from Universal Login
* @returns True if flexible identifiers are supported, false otherwise
*/
export function hasFlexibleIdentifier(transaction: TransactionContext): boolean {
export function hasFlexibleIdentifier(
transaction: TransactionContext
): boolean {
const connection = transaction.connection as DBConnection;
return connection?.options?.attributes ? true : false;
}
Expand All @@ -143,14 +179,25 @@ export function hasFlexibleIdentifier(transaction: TransactionContext): boolean
* @param statuses - Array of statuses to filter by ('required' or 'optional')
* @returns Array of matching identifier types or null if none are found
*/
function extractIdentifiersByStatus(connection: DBConnection | undefined, statuses: ('required' | 'optional')[]): IdentifierType[] | null {
function extractIdentifiersByStatus(
connection: DBConnection | undefined,
statuses: ("required" | "optional")[]
): IdentifierType[] | null {
if (!connection?.options?.attributes) return null;

return Object.entries(connection.options.attributes)
.filter(([, value]) => value.signup_status && statuses.includes(value.signup_status as 'required' | 'optional'))
.filter(
([, value]) =>
value.signup_status &&
statuses.includes(value.signup_status as "required" | "optional")
)
.map(([key]) => key as IdentifierType).length > 0
? Object.entries(connection.options.attributes)
.filter(([, value]) => value.signup_status && statuses.includes(value.signup_status as 'required' | 'optional'))
.filter(
([, value]) =>
value.signup_status &&
statuses.includes(value.signup_status as "required" | "optional")
)
.map(([key]) => key as IdentifierType)
: null;
}
Loading