Skip to content

Commit

Permalink
fixes: state not saved when using handleEmailVerify
Browse files Browse the repository at this point in the history
  • Loading branch information
jaredperreault-okta committed Mar 2, 2022
1 parent 54275b4 commit c649bff
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 15 deletions.
53 changes: 52 additions & 1 deletion docs/idx.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ This module provides convenience methods to support popular scenarios to communi

#### Flow

In addition to the default authentication flow, this SDK supports several pre-defined flows, such as [register](#idxregister) and [recoverPassword](#idxrecoverpassword). A flow can be started by calling one of the available [flow entrypoints](#flow-entrypoints) or by passing a valid flow identifier string to [`startTransaction`](#idxstarttransaction). The `flow` is saved with the transaction which enables the [proceed](#idxproceed) method to corrrectly handle remediations without additional context. Starting a new flow discards any existing in-progress transaction of a different type. For example, if an authentication flow is in-progress, a call to [authenticate](#idxauthenticate) or [proceed](#idxproceed) will continue using the current transaction but a call to [register](#idxregister) or [recoverPassword](#idxrecoverpassword) will start a new transaction.
In addition to the default authentication flow, this SDK supports several pre-defined flows, such as [register](#idxregister), [recoverPassword](#idxrecoverpassword) and [unlockAccount](#idxunlockaccount). A flow can be started by calling one of the available [flow entrypoints](#flow-entrypoints) or by passing a valid flow identifier string to [`startTransaction`](#idxstarttransaction). The `flow` is saved with the transaction which enables the [proceed](#idxproceed) method to corrrectly handle remediations without additional context. Starting a new flow discards any existing in-progress transaction of a different type. For example, if an authentication flow is in-progress, a call to [authenticate](#idxauthenticate) or [proceed](#idxproceed) will continue using the current transaction but a call to a [flow entrypoint](#flow-entrypoints) will start a new transaction.

```javascript

Expand All @@ -88,6 +88,7 @@ The [flow](#flow) is set automatically when calling one of these methods:
- [`idx.authenticate`](#idxauthenticate)
- [`idx.register`](#idxregister)
- [`idx.recoverPassword`](#idxrecoverpassword)
- [`idx.unlockAccount`](#idxunlockaccount)

The `flow` will be set to `default` unless otherwise specified in [`idx.startTransaction`](#idxstarttransaction)

Expand Down Expand Up @@ -428,6 +429,56 @@ const {
} = await authClient.idx.proceed({ password: 'xxx' });
```
#### `idx.unlockAccount`
The convenience method for starting a self service account recovery` flow.
Example (Account unlock with email authenticator verification)
**Up-Front**:
```javascript
const {
status, // IdxStatus.PENDING
nextStep: {
inputs // [{ name: 'verificationCode', ... }]
}
} = await authClient.idx.unlockAccount({
username: 'xxx',
authenticators: [AuthenticatorKey.OKTA_EMAIL /* 'okta_email' */]
});
// submit verification code
const {
status, // IdxStatus.TERMINAL
messages // 'Your Account is now unlocked!'
} = await authClient.idx.proceed({ verificationCode: 'xxx' });
```
**On-Demand**:
```javascript
const {
status, // IdxStatus.PENDING
nextStep: {
inputs // [{ name: 'username', ... }]
}
} = await authClient.idx.unlockAccount();
// gather username from user input and
// user sees a list of authenticators and selects "email"
const {
status, // IdxStatus.PENDING
nextStep: {
inputs, // [{ name: 'authenticator', ... }]
options // [{ name: 'email', ... }, ...]
}
} = await authClient.idx.proceed({ username, authenticator: AuthenticatorKey.OKTA_EMAIL });
// gather verification code from email (this call should happen in a separated request)
const {
status, // IdxStatus.TERMINAL
messages // 'Your Account is now unlocked!'
} = await authClient.idx.proceed({ verificationCode: 'xxx' });
```
#### `idx.start`
Alias for [idx.startTransaction](#idxstarttransaction)
Expand Down
2 changes: 0 additions & 2 deletions lib/idx/emailVerify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { OktaAuthInterface } from '../types';

import CustomError from '../errors/CustomError';
import { urlParamsToObject } from '../oidc/util/urlParams';
import { saveTransactionMeta } from './transactionMeta';

export interface EmailVerifyCallbackResponse {
state: string;
Expand Down Expand Up @@ -51,7 +50,6 @@ export function parseEmailVerifyCallback(urlPath: string): EmailVerifyCallbackRe
export async function handleEmailVerifyCallback(authClient: OktaAuthInterface, search: string) {
if (isEmailVerifyCallback(search)) {
const { state, otp } = parseEmailVerifyCallback(search);
await saveTransactionMeta(authClient, { state });
if (authClient.idx.canProceed({ state })) {
// same browser / device
return await authClient.idx.proceed({ state, otp });
Expand Down
6 changes: 5 additions & 1 deletion lib/idx/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
FlowIdentifier,
} from '../types';
import { IdxResponse, isIdxResponse } from './types/idx-js';
import { getSavedTransactionMeta } from './transactionMeta';
import { getSavedTransactionMeta, saveTransactionMeta } from './transactionMeta';
import { ProceedOptions } from './proceed';

export type RunOptions = ProceedOptions & RemediateOptions & {
Expand Down Expand Up @@ -234,6 +234,10 @@ export async function run(
if (shouldClearTransaction) {
authClient.transactionManager.clear({ clearSharedStorage });
}
else if (meta?.state) {
// ensures state is saved to sessionStorage
saveTransactionMeta(authClient, { ...meta });
}

// from idx-js, used by the widget
const { actions, context, neededToProceed, proceed, rawIdxState } = idxResponse || {};
Expand Down
2 changes: 1 addition & 1 deletion test/apps/react-oie/config-overrides.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ envModule.setEnvironmentVarsFromTestEnv();

const env = {};
// List of environment variables made available to the app
['ISSUER', 'CLIENT_ID'].forEach((key) => {
['ISSUER', 'SPA_CLIENT_ID', 'CLIENT_ID'].forEach((key) => {
if (!process.env[key]) {
throw new Error(`Environment variable ${key} must be set. See README.md`);
}
Expand Down
1 change: 1 addition & 0 deletions test/apps/react-oie/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ export default function App() {
<button onClick={startIdxFlow('authenticate')}>Login</button>
<button onClick={startIdxFlow('recoverPassword')}>Recover Password</button>
<button onClick={startIdxFlow('register')}>Registration</button>
<button onClick={startIdxFlow('unlockAccount')}>Unlock Account</button>
<button onClick={startIdxFlow('idp')}>IDP</button>
</div>
);
Expand Down
9 changes: 0 additions & 9 deletions test/spec/idx/emailVerify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,6 @@ import {
EmailVerifyCallbackError
} from '../../../lib/idx/emailVerify';

jest.mock('../../../lib/idx/transactionMeta', () => {
const actual = jest.requireActual('../../../lib/idx/transactionMeta');
return {
...actual,
getSavedTransactionMeta: () => {},
saveTransactionMeta: () => {}
};
});

describe('emailVerify', () => {

describe('isEmailVerifyCallback', () => {
Expand Down
11 changes: 10 additions & 1 deletion test/spec/idx/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import { IdxResponseFactory } from '@okta/test.support/idx';

jest.mock('../../../lib/idx/transactionMeta', () => {
return {
getSavedTransactionMeta: () => {}
getSavedTransactionMeta: () => {},
saveTransactionMeta: () => {}
};
});

Expand Down Expand Up @@ -234,6 +235,14 @@ describe('idx/run', () => {
status: IdxStatus.PENDING,
});
});

it('saves `state` in transaction meta', async () => {
const { authClient, transactionMeta } = testContext;

jest.spyOn(mocked.transactionMeta, 'saveTransactionMeta');
await run(authClient);
expect(mocked.transactionMeta.saveTransactionMeta).toHaveBeenCalledWith(authClient, transactionMeta);
});
});

describe('response is terminal', () => {
Expand Down

0 comments on commit c649bff

Please sign in to comment.