Skip to content

Commit

Permalink
refactor(dangerouslySetPlaceholderPassword): make named export, add `…
Browse files Browse the repository at this point in the history
…dangerouslySetPlaceholderPasswordUsingRecoveryToken` to share code
  • Loading branch information
coldlink committed Oct 16, 2024
1 parent 623f8d5 commit b034b36
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 66 deletions.
39 changes: 7 additions & 32 deletions src/server/controllers/oktaIdxShared.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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';

/**
Expand Down Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion src/server/controllers/sendChangePasswordEmail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
99 changes: 68 additions & 31 deletions src/server/lib/okta/dangerouslySetPlaceholderPassword.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,49 +26,28 @@ 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<string>;
async function dangerouslySetPlaceholderPassword(
export async function dangerouslySetPlaceholderPassword(
params: PlaceholderPasswordParams & { returnPlaceholderPassword?: false },
): Promise<void>;
// Implementation
async function dangerouslySetPlaceholderPassword({
export async function dangerouslySetPlaceholderPassword({
id,
ip,
returnPlaceholderPassword = false,
}: PlaceholderPasswordParams): Promise<string | void> {
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;
Expand All @@ -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<string>} The placeholder password
*/
export async function dangerouslySetPlaceholderPasswordUsingRecoveryToken({
id,
ip,
recoveryToken,
}: PlaceholderPasswordParamsWithRecoveryToken): Promise<string> {
// 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;
}
2 changes: 1 addition & 1 deletion src/server/lib/okta/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
2 changes: 1 addition & 1 deletion src/server/routes/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down

0 comments on commit b034b36

Please sign in to comment.