Skip to content
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
8 changes: 8 additions & 0 deletions .changeset/lazy-paws-greet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@wso2is/admin.identity-providers.v1": minor
"@wso2is/admin.connections.v1": minor
"@wso2is/console": minor
"@wso2is/i18n": patch
---

Introduce a org level config to govern TOTP
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ const getLicenseHeaderPattern = () => {
const LICENSE_HEADER_DEFAULT_PATTERN = [
"*",
{
pattern: " Copyright \\(c\\) \\b(2019|202[0-5])(?:-(202[0-6]))?, WSO2 LLC. \\(https://www.wso2.com\\).$",
pattern: " Copyright \\(c\\) \\b(2019|202[0-6])(?:-(202[0-6]))?, WSO2 LLC. \\(https://www.wso2.com\\).$",
template: " * Copyright (c) {{year}}, WSO2 LLC. (https://www.wso2.com)."
},
" *",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,10 +251,9 @@ export const EditMultiFactorAuthenticator: FunctionComponent<EditMultiFactorAuth
panes.push(...tabPaneExtensions);
}

// If the MFA is TOTP/Magic Link skip the settings tab.
// If the MFA is Magic Link skip the settings tab.
if (
![
LocalAuthenticatorConstants.AUTHENTICATOR_IDS.TOTP_AUTHENTICATOR_ID,
LocalAuthenticatorConstants.AUTHENTICATOR_IDS.MAGIC_LINK_AUTHENTICATOR_ID
].includes(authenticator.id)
) {
Expand All @@ -276,7 +275,6 @@ export const EditMultiFactorAuthenticator: FunctionComponent<EditMultiFactorAuth
const resolveDefaultActiveIndex = (activeIndex: number): number => {

if (![
LocalAuthenticatorConstants.AUTHENTICATOR_IDS.TOTP_AUTHENTICATOR_ID,
LocalAuthenticatorConstants.AUTHENTICATOR_IDS.MAGIC_LINK_AUTHENTICATOR_ID
].includes(authenticator.id)) {
return activeIndex;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ export * from "./google-authenticator-form";
export * from "./microsoft-authenticator-form";
export * from "./sms-otp-authenticator-form";
export * from "./push-authenticator-form";
export * from "./totp-authenticator-form";
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/**
* Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com).
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { TestableComponentInterface } from "@wso2is/core/models";
import { Field, Form } from "@wso2is/form";
import isEmpty from "lodash-es/isEmpty";
import React, { FunctionComponent, ReactElement, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import {
CommonAuthenticatorFormInitialValuesInterface,
CommonAuthenticatorFormMetaInterface,
CommonAuthenticatorFormPropertyInterface
} from "../../../../models/authenticators";
import {
CommonPluggableComponentPropertyInterface
} from "../../../../models/connection";

interface TOTPAuthenticatorFormPropsInterface extends TestableComponentInterface {
/**
* TOTP Authenticator Metadata.
*/
metadata: CommonAuthenticatorFormMetaInterface;
/**
* TOTP Authenticator configured initial values.
*/
initialValues: CommonAuthenticatorFormInitialValuesInterface;
/**
* Callback for form submit.
* @param values - Resolved Form Values.
*/
onSubmit: (values: CommonAuthenticatorFormInitialValuesInterface) => void;
/**
* Is readonly.
*/
readOnly?: boolean;
/**
* Specifies if the form is submitting.
*/
isSubmitting?: boolean;
}

/**
* Form initial values interface.
*/
interface TOTPAuthenticatorFormInitialValuesInterface {
/**
* Enable progressive enrollment.
*/
TOTP_EnrolUserInAuthenticationFlow: boolean;
}

/**
* Proptypes for the TOTP Authenticator Form error messages.
*/
export interface TOTPAuthenticatorFormErrorValidationsInterface {
/**
* Enable progressive enrollment.
*/
TOTP_EnrolUserInAuthenticationFlow: string;
}

const FORM_ID: string = "totp-authenticator-form";

/**
* TOTP Authenticator Form.
*
* @param props - Props injected to the component.
* @returns Functional component.
*/
export const TOTPAuthenticatorForm: FunctionComponent<TOTPAuthenticatorFormPropsInterface> = (
props: TOTPAuthenticatorFormPropsInterface
): ReactElement => {

const {
initialValues: originalInitialValues,
onSubmit,
readOnly,
isSubmitting,
["data-testid"]: testId
} = props;

const { t } = useTranslation();

const [ initialValues, setInitialValues ] = useState<TOTPAuthenticatorFormInitialValuesInterface>(undefined);

/**
* Flattens and resolved form initial values and field metadata.
*/
useEffect(() => {

if (isEmpty(originalInitialValues?.properties)) {
return;
}

let resolvedInitialValues: TOTPAuthenticatorFormInitialValuesInterface = null;

originalInitialValues.properties.forEach((value: CommonAuthenticatorFormPropertyInterface) => {
const moderatedName: string = value.name.replace(/\./g, "_");

resolvedInitialValues = {
...resolvedInitialValues,
[moderatedName]: (value.value === "true" || value.value === "false")
? JSON.parse(value.value)
: value.value
};
});

setInitialValues(resolvedInitialValues);
}, [ originalInitialValues ]);

/**
* Prepare form values for submitting.
*
* @param values - Form values.
* @returns Sanitized form values.
*/
const getUpdatedConfigurations = (values: TOTPAuthenticatorFormInitialValuesInterface)
: CommonAuthenticatorFormInitialValuesInterface => {

const properties: CommonPluggableComponentPropertyInterface[] = [];

for (const [ name, value ] of Object.entries(values)) {
if (name !== undefined) {
const moderatedName: string = name.replace(/_/g, ".");

properties.push({
name: moderatedName,
value: value.toString()
});
}
}

const result: CommonAuthenticatorFormInitialValuesInterface = {
...originalInitialValues,
properties
};

return result;
};

return (
<Form
id={ FORM_ID }
uncontrolledForm={ false }
onSubmit={ (values: Record<string, any>) => {
onSubmit(getUpdatedConfigurations(values as TOTPAuthenticatorFormInitialValuesInterface));
} }
initialValues={ initialValues }
>
<Field.Checkbox
ariaLabel="Enable TOTP device progressive enrollment"
name="TOTP_EnrolUserInAuthenticationFlow"
label={
t("authenticationProvider:forms.authenticatorSettings.totp.enrollUserInAuthenticationFlow.label")
}
hint={
t("authenticationProvider:forms.authenticatorSettings.totp.enrollUserInAuthenticationFlow.hint")
}
readOnly={ readOnly }
width={ 16 }
data-testid={ `${ testId }-totp-enrol-user-in-authentication-flow-checkbox` }
/>
<Field.Button
form={ FORM_ID }
size="small"
buttonType="primary_btn"
ariaLabel="TOTP Authenticator update button"
name="update-button"
data-testid={ `${ testId }-submit-button` }
disabled={ isSubmitting }
loading={ isSubmitting }
label={ t("common:update") }
hidden={ readOnly }
/>
</Form>
);
};

/**
* Default props for the component.
*/
TOTPAuthenticatorForm.defaultProps = {
"data-testid": "totp-authenticator-form"
};
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ import {
GithubAuthenticatorForm,
GoogleAuthenticatorForm,
MicrosoftAuthenticatorForm,
SMSOTPAuthenticatorForm
SMSOTPAuthenticatorForm,
TOTPAuthenticatorForm
} from "../authenticators";
import { SamlAuthenticatorSettingsForm } from "../authenticators/saml-authenticator-form";
import { SIWEAuthenticatorForm } from "../authenticators/swe-authenticator-form";
Expand Down Expand Up @@ -245,6 +246,17 @@ export const AuthenticatorFormFactory: FunctionComponent<AuthenticatorFormFactor
isSubmitting={ isSubmitting }
/>
);
case LocalAuthenticatorConstants.AUTHENTICATOR_IDS.TOTP_AUTHENTICATOR_ID:
return (
<TOTPAuthenticatorForm
initialValues={ initialValues }
metadata={ metadata }
onSubmit={ onSubmit }
data-testid={ testId }
readOnly={ isReadOnly }
isSubmitting={ isSubmitting }
/>
);
case FederatedAuthenticatorConstants.AUTHENTICATOR_IDS.SAML_AUTHENTICATOR_ID:
return (
<SamlAuthenticatorSettingsForm
Expand Down
2 changes: 1 addition & 1 deletion features/admin.connections.v1/meta/authenticator-meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ export class AuthenticatorMeta {
},
isComingSoon: false,
isEnabled: true,
useAuthenticatorsAPI: true
useAuthenticatorsAPI: false
},
[ LocalAuthenticatorConstants.AUTHENTICATOR_IDS.FIDO_AUTHENTICATOR_ID ]: {
content: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020-2024, WSO2 LLC. (https://www.wso2.com).
* Copyright (c) 2020-2026, WSO2 LLC. (https://www.wso2.com).
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
Expand All @@ -16,6 +16,7 @@
* under the License.
*/

import { TOTPAuthenticatorForm } from "@wso2is/admin.connections.v1/components/edit/forms/authenticators";
import {
FederatedAuthenticatorConstants
} from "@wso2is/admin.connections.v1/constants/federated-authenticator-constants";
Expand Down Expand Up @@ -289,6 +290,17 @@ export const AuthenticatorFormFactory: FunctionComponent<AuthenticatorFormFactor
isSubmitting={ isSubmitting }
/>
);
case LocalAuthenticatorConstants.AUTHENTICATOR_IDS.TOTP_AUTHENTICATOR_ID:
return (
<TOTPAuthenticatorForm
initialValues={ initialValues }
metadata={ metadata }
onSubmit={ onSubmit }
data-testid={ testId }
readOnly={ isReadOnly }
isSubmitting={ isSubmitting }
/>
);
default:
return (
<CommonAuthenticatorForm
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -893,6 +893,12 @@ export interface AuthenticationProviderNS {
ariaLabel: string;
};
};
totp: {
enrollUserInAuthenticationFlow: {
hint: string;
label: string;
};
};
};
outboundConnectorAccordion: {
default: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -930,6 +930,12 @@ export const authenticationProvider:AuthenticationProviderNS = {
required: "Enablin push notification device progressive enrollment is required."
}
}
},
totp: {
enrollUserInAuthenticationFlow: {
hint: "When enabled, users may enroll their devices for TOTP at the moment they log in to the application.",
label: "Enable TOTP device progressive enrollment"
}
}
},
certificateSection: {
Expand Down
Loading