diff --git a/src/server/controllers/oktaIdxShared.ts b/src/server/controllers/oktaIdxShared.ts index 3c5dfb185..5a2afefdd 100644 --- a/src/server/controllers/oktaIdxShared.ts +++ b/src/server/controllers/oktaIdxShared.ts @@ -1,6 +1,9 @@ import { Request } from 'express'; import { setEncryptedStateCookie } from '@/server/lib/encryptedStateCookie'; -import dangerouslySetPlaceholderPassword from '@/server/lib/okta/dangerouslySetPlaceholderPassword'; +import { + dangerouslySetPlaceholderPassword, + dangerouslySetPlaceholderPasswordUsingRecoveryToken, +} from '@/server/lib/okta/dangerouslySetPlaceholderPassword'; import { challenge, validateChallengeRemediation, @@ -15,12 +18,7 @@ import { OktaError } from '@/server/models/okta/Error'; import { UserResponse } from '@/server/models/okta/User'; import { ResponseWithRequestState } from '@/server/models/Express'; import { IdentifyResponse } from '@/server/lib/okta/idx/identify'; -import { - resetPassword, - validateRecoveryToken, -} from '@/server/lib/okta/api/authentication'; import { deactivateUser, activateUser } from '@/server/lib/okta/api/users'; -import { validateEmailAndPasswordSetSecurely } from '@/server/lib/okta/validateEmail'; import { logger } from '@/server/lib/serverSideLogger'; /** @@ -218,34 +216,11 @@ export const forceUserIntoActiveState = async ({ }); } - // 3. use the recovery token to set a placeholder password for the user - // Validate the token - const { stateToken } = await validateRecoveryToken({ - recoveryToken: tokenResponse.token, - ip: req.ip, - }); - // Check if state token is defined - if (!stateToken) { - throw new OktaError({ - message: - 'Okta set placeholder password failed: state token is undefined', - }); - } - // Set the placeholder password as a cryptographically secure UUID - const placeholderPassword = crypto.randomUUID(); - await resetPassword( - { - stateToken, - newPassword: placeholderPassword, - }, - req.ip, - ); - - // Unset the emailValidated and passwordSetSecurely flags - await validateEmailAndPasswordSetSecurely({ + // set a placeholder password for the user + await dangerouslySetPlaceholderPasswordUsingRecoveryToken({ id: user.id, ip: req.ip, - flagStatus: false, + recoveryToken: tokenResponse.token, }); } catch (error) { logger.error( diff --git a/src/server/controllers/sendChangePasswordEmail.ts b/src/server/controllers/sendChangePasswordEmail.ts index 66b54e5e1..565b10c96 100644 --- a/src/server/controllers/sendChangePasswordEmail.ts +++ b/src/server/controllers/sendChangePasswordEmail.ts @@ -30,7 +30,7 @@ import { sendCreatePasswordEmail } from '@/email/templates/CreatePassword/sendCr import { sendResetPasswordEmail } from '@/email/templates/ResetPassword/sendResetPasswordEmail'; import { PasswordRoutePath, RoutePaths } from '@/shared/model/Routes'; import { mergeRequestState } from '@/server/lib/requestState'; -import dangerouslySetPlaceholderPassword from '@/server/lib/okta/dangerouslySetPlaceholderPassword'; +import { dangerouslySetPlaceholderPassword } from '@/server/lib/okta/dangerouslySetPlaceholderPassword'; import { encryptOktaRecoveryToken } from '@/server/lib/deeplink/oktaRecoveryToken'; import { getConfiguration } from '@/server/lib/getConfiguration'; import { startIdxFlow } from '@/server/lib/okta/idx/startIdxFlow'; diff --git a/src/server/lib/okta/dangerouslySetPlaceholderPassword.ts b/src/server/lib/okta/dangerouslySetPlaceholderPassword.ts index 1312bedf1..de98139e2 100644 --- a/src/server/lib/okta/dangerouslySetPlaceholderPassword.ts +++ b/src/server/lib/okta/dangerouslySetPlaceholderPassword.ts @@ -26,14 +26,14 @@ interface PlaceholderPasswordParams { * @returns The placeholder password if returnPlaceholderPassword is true, otherwise void (undefined) */ // Overload signatures -async function dangerouslySetPlaceholderPassword( +export async function dangerouslySetPlaceholderPassword( params: PlaceholderPasswordParams & { returnPlaceholderPassword: true }, ): Promise; -async function dangerouslySetPlaceholderPassword( +export async function dangerouslySetPlaceholderPassword( params: PlaceholderPasswordParams & { returnPlaceholderPassword?: false }, ): Promise; // Implementation -async function dangerouslySetPlaceholderPassword({ +export async function dangerouslySetPlaceholderPassword({ id, ip, returnPlaceholderPassword = false, @@ -41,34 +41,13 @@ async function dangerouslySetPlaceholderPassword({ try { // Generate an recoveryToken OTT and put user into RECOVERY state const recoveryToken = await dangerouslyResetPassword(id, ip); - // Validate the token - const { stateToken } = await validateRecoveryToken({ - recoveryToken, - ip, - }); - // Check if state token is defined - if (!stateToken) { - throw new OktaError({ - message: - 'dangerouslySetPlaceholderPassword failed: state token is undefined', - }); - } - // Set the placeholder password as a cryptographically secure UUID - const placeholderPassword = crypto.randomUUID(); - await resetPassword( - { - stateToken, - newPassword: placeholderPassword, - }, - ip, - ); - // Unset the emailValidated and passwordSetSecurely flags - await validateEmailAndPasswordSetSecurely({ - id, - ip, - flagStatus: false, - }); + const placeholderPassword = + dangerouslySetPlaceholderPasswordUsingRecoveryToken({ + id, + ip, + recoveryToken, + }); if (returnPlaceholderPassword) { return placeholderPassword; @@ -82,4 +61,62 @@ async function dangerouslySetPlaceholderPassword({ } } -export default dangerouslySetPlaceholderPassword; +// Define the parameter object type +interface PlaceholderPasswordParamsWithRecoveryToken { + id: string; + ip?: string; + recoveryToken: string; +} + +/** + * @name dangerouslySetPlaceholderPasswordUsingRecoveryToken + * @description DANGEROUS: Make sure to read the description. This function is used to set a placeholder password for a user using a recovery token. Only use this if you know what you are doing... + * + * This function is used ONLY for users who do not have a password set at all + * (i.e. social users or users imported from IDAPI). It does the following: + * 1. Takes a recover token generated by an Okta API, e.g dangerouslyResetPassword()/activateUser() + * 2. Uses the OTT to set a cryptographically secure placeholder password for the user. + * + * After these operations, we return the placeholder password, should the caller need it. Be careful how this is used. + * + * @param id The Okta user ID + * @param ip The IP address of the user + * @param recoveryToken The recovery token + * @returns {Promise} The placeholder password + */ +export async function dangerouslySetPlaceholderPasswordUsingRecoveryToken({ + id, + ip, + recoveryToken, +}: PlaceholderPasswordParamsWithRecoveryToken): Promise { + // Validate the token + const { stateToken } = await validateRecoveryToken({ + recoveryToken, + ip, + }); + // Check if state token is defined + if (!stateToken) { + throw new OktaError({ + message: + 'dangerouslySetPlaceholderPassword failed: state token is undefined', + }); + } + // Set the placeholder password as a cryptographically secure UUID + const placeholderPassword = crypto.randomUUID(); + await resetPassword( + { + stateToken, + newPassword: placeholderPassword, + }, + ip, + ); + + // Unset the emailValidated and passwordSetSecurely flags + await validateEmailAndPasswordSetSecurely({ + id, + ip, + flagStatus: false, + }); + + return placeholderPassword; +} diff --git a/src/server/lib/okta/register.ts b/src/server/lib/okta/register.ts index 6fd3d4bdd..c8f29ff0f 100644 --- a/src/server/lib/okta/register.ts +++ b/src/server/lib/okta/register.ts @@ -18,7 +18,7 @@ import { getConfiguration } from '@/server/lib/getConfiguration'; import { sendEmailToUnvalidatedUser } from '@/server/lib/unvalidatedEmail'; import { trackMetric } from '@/server/lib/trackMetric'; import { logger } from '@/server/lib/serverSideLogger'; -import dangerouslySetPlaceholderPassword from './dangerouslySetPlaceholderPassword'; +import { dangerouslySetPlaceholderPassword } from './dangerouslySetPlaceholderPassword'; import { sendCompleteRegistration } from '@/email/templates/CompleteRegistration/sendCompleteRegistration'; import { encryptOktaRecoveryToken } from '@/server/lib/deeplink/oktaRecoveryToken'; import { encryptRegistrationConsents } from '@/server/lib/registrationConsents'; diff --git a/src/server/routes/delete.ts b/src/server/routes/delete.ts index 8a13bc6a4..c3d7e1034 100644 --- a/src/server/routes/delete.ts +++ b/src/server/routes/delete.ts @@ -23,7 +23,7 @@ import { } from '@/shared/lib/queryParams'; import { GenericErrors } from '@/shared/model/Errors'; import { UserAttributesResponse } from '@/shared/lib/members-data-api'; -import dangerouslySetPlaceholderPassword from '@/server/lib/okta/dangerouslySetPlaceholderPassword'; +import { dangerouslySetPlaceholderPassword } from '@/server/lib/okta/dangerouslySetPlaceholderPassword'; import { getConfiguration } from '@/server/lib/getConfiguration'; import { requestStateHasOAuthTokens } from '@/server/lib/middleware/requestState';