Skip to content

Commit

Permalink
Minimal idx factory for SIW gen3 (v2) (#1476)
Browse files Browse the repository at this point in the history
OKTA-656620 Added factory `createMinimalOktaAuthIdx` to create minimal client for SIW Gen3
  • Loading branch information
denysoblohin-okta authored Dec 4, 2023
1 parent 89dcafd commit 3710f59
Show file tree
Hide file tree
Showing 34 changed files with 550 additions and 63 deletions.
37 changes: 37 additions & 0 deletions lib/idx/factory/MinimalOktaAuthIdx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { OktaAuthOptionsConstructor } from '../../base/types';
import { StorageManagerConstructor } from '../../storage/types';
import { IdxTransactionManagerInterface, MinimalOktaAuthIdxInterface, OktaAuthIdxConstructor } from '../types/api';
import { IdxTransactionMeta } from '../types/meta';
import { IdxStorageManagerInterface } from '../types/storage';
import { OktaAuthIdxOptions } from '../types/options';
import { TransactionManagerConstructor, MinimalOktaOAuthInterface } from '../../oidc/types';
import { mixinMinimalIdx } from '../mixinMinimal';
import { createOktaAuthBase } from '../../base/factory';
import { mixinStorage } from '../../storage/mixin';
import { mixinHttp } from '../../http/mixin';
import { mixinSession } from '../../session/mixin';
import { mixinMinimalOAuth } from '../../oidc/mixin/minimal';

export function createMinimalOktaAuthIdx<
M extends IdxTransactionMeta = IdxTransactionMeta,
S extends IdxStorageManagerInterface<M> = IdxStorageManagerInterface<M>,
O extends OktaAuthIdxOptions = OktaAuthIdxOptions,
TM extends IdxTransactionManagerInterface = IdxTransactionManagerInterface
>(
StorageManagerConstructor: StorageManagerConstructor<S>,
OptionsConstructor: OktaAuthOptionsConstructor<O>,
TransactionManagerConstructor: TransactionManagerConstructor<TM>
)
: OktaAuthIdxConstructor<
MinimalOktaAuthIdxInterface<M, S, O, TM> & MinimalOktaOAuthInterface<M, S, O, TM>
>
{
const Base = createOktaAuthBase(OptionsConstructor);
const WithStorage = mixinStorage<S, O>(Base, StorageManagerConstructor);
const WithHttp = mixinHttp<S, O>(WithStorage);
const WithSession = mixinSession<S, O>(WithHttp);
const WithOAuth = mixinMinimalOAuth<M, S, O, TM>(WithSession, TransactionManagerConstructor);
// do not mixin core
const WithIdx = mixinMinimalIdx(WithOAuth);
return WithIdx;
}
7 changes: 7 additions & 0 deletions lib/idx/factory/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,16 @@ import {
} from '../transactionMeta';
import { FlowIdentifier, IdxAPI, OktaAuthIdxInterface } from '../types';
import { unlockAccount } from '../unlockAccount';
import * as remediators from '../remediators';
import { getFlowSpecification } from '../flow/FlowSpecification';
import { setRemediatorsCtx } from '../util';

// Factory
export function createIdxAPI(sdk: OktaAuthIdxInterface): IdxAPI {
setRemediatorsCtx({
remediators,
getFlowSpecification,
});
const boundStartTransaction = startTransaction.bind(null, sdk);
const idx = {
interact: interact.bind(null, sdk),
Expand Down
1 change: 1 addition & 0 deletions lib/idx/factory/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './api';
export * from './OktaAuthIdx';
export * from './MinimalOktaAuthIdx';
48 changes: 48 additions & 0 deletions lib/idx/factory/minimalApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*!
* Copyright (c) 2015-present, Okta, Inc. and/or its affiliates. All rights reserved.
* The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (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 { makeIdxState } from '../idxState';
import { canProceed, proceed } from '../proceed';
import { startTransaction } from '../startTransaction';
import {
clearTransactionMeta,
createTransactionMeta,
getSavedTransactionMeta,
getTransactionMeta,
isTransactionMetaValid,
saveTransactionMeta
} from '../transactionMeta';
import { MinimalIdxAPI, MinimalOktaAuthIdxInterface, OktaAuthIdxInterface } from '../types';

// Factory
export function createMinimalIdxAPI(minimalSdk: MinimalOktaAuthIdxInterface): MinimalIdxAPI {
const sdk = minimalSdk as OktaAuthIdxInterface;
const boundStartTransaction = startTransaction.bind(null, sdk);
const idx = {
makeIdxResponse: makeIdxState.bind(null, sdk),

start: boundStartTransaction,
startTransaction: boundStartTransaction, // Use `start` instead. `startTransaction` will be removed in 7.0
proceed: proceed.bind(null, sdk),
canProceed: canProceed.bind(null, sdk),

getSavedTransactionMeta: getSavedTransactionMeta.bind(null, sdk),
createTransactionMeta: createTransactionMeta.bind(null, sdk),
getTransactionMeta: getTransactionMeta.bind(null, sdk),
saveTransactionMeta: saveTransactionMeta.bind(null, sdk),
clearTransactionMeta: clearTransactionMeta.bind(null, sdk),
isTransactionMetaValid,
};
return idx;
}

10 changes: 1 addition & 9 deletions lib/idx/flow/FlowSpecification.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,8 @@
import { OktaAuthIdxInterface, FlowIdentifier } from '../types';
import { OktaAuthIdxInterface, FlowIdentifier, FlowSpecification } from '../types';
import { AuthenticationFlow } from './AuthenticationFlow';
import { PasswordRecoveryFlow } from './PasswordRecoveryFlow';
import { RegistrationFlow } from './RegistrationFlow';
import { AccountUnlockFlow } from './AccountUnlockFlow';
import { RemediationFlow } from './RemediationFlow';

export interface FlowSpecification {
flow: FlowIdentifier;
remediators: RemediationFlow;
actions?: string[];
withCredentials?: boolean;
}

// eslint-disable-next-line complexity
export function getFlowSpecification(
Expand Down
22 changes: 12 additions & 10 deletions lib/idx/idxState/v1/idxResponseParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,22 @@
*/

/* eslint-disable max-len */
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
import { OktaAuthIdxInterface } from '../../types'; // auth-js/types

import { OktaAuthIdxInterface, IdxResponse, IdxRemediation, IdxContext } from '../../types'; // auth-js/types
import { IdxActions } from '../../types/idx-js';
import { generateRemediationFunctions } from './remediationParser';
import generateIdxAction from './generateIdxAction';
import { jsonpath } from '../../../util/jsonpath';
import { AuthSdkError } from '../../../errors';

const SKIP_FIELDS = Object.fromEntries([
'remediation', // remediations are put into proceed/neededToProceed
'context', // the API response of 'context' isn't externally useful. We ignore it and put all non-action (contextual) info into idxState.context
].map( (field) => [ field, !!'skip this field' ] ));
const SKIP_FIELDS = {
'remediation': true, // remediations are put into proceed/neededToProceed
'context': true, // the API response of 'context' isn't externally useful. We ignore it and put all non-action (contextual) info into idxState.context
};

export const parseNonRemediations = function parseNonRemediations( authClient: OktaAuthIdxInterface, idxResponse, toPersist = {} ) {
export const parseNonRemediations = function parseNonRemediations( authClient: OktaAuthIdxInterface, idxResponse: IdxResponse, toPersist = {} ) {
const actions = {};
const context = {};
const context = {} as IdxContext;

Object.keys(idxResponse)
.filter( field => !SKIP_FIELDS[field])
Expand Down Expand Up @@ -56,10 +56,12 @@ export const parseNonRemediations = function parseNonRemediations( authClient: O

// We are an object field containing an object value
context[field].value = {};
Object.entries(fieldValue)
Object.entries<IdxRemediation>(fieldValue)
.forEach( ([subField, value]) => {
if (value.rel) { // is [field].value[subField] an action?
// add any "action" value subfields to actions
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
actions[`${field}-${subField.name || subField}`] = generateIdxAction(authClient, value, toPersist);
} else {
// add non-action value subfields to context
Expand Down
4 changes: 2 additions & 2 deletions lib/idx/idxState/v1/makeIdxState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* See the License for the specific language governing permissions and limitations under the License.
*/

import { IdxResponse, IdxToPersist } from '../../types/idx-js';
import { IdxResponse, IdxToPersist, IdxActionParams } from '../../types/idx-js';
import { OktaAuthIdxInterface, RawIdxResponse } from '../../types'; // auth-js/types
import { parseIdxResponse } from './idxResponseParser';

Expand Down Expand Up @@ -41,7 +41,7 @@ export function makeIdxState(
return Promise.reject(`Current remediation cannot make form submit action: [${remediationChoice}]`);
}

return remediationChoiceObject.action(paramsFromUser);
return remediationChoiceObject.action!(paramsFromUser as IdxActionParams);
};

const findCode = item => item.name === 'interaction_code';
Expand Down
2 changes: 0 additions & 2 deletions lib/idx/idxState/v1/parsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
* See the License for the specific language governing permissions and limitations under the License.
*/

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
import { makeIdxState } from './makeIdxState';

export default {
Expand Down
16 changes: 6 additions & 10 deletions lib/idx/idxState/v1/remediationParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,16 @@
* See the License for the specific language governing permissions and limitations under the License.
*/

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
import { OktaAuthIdxInterface } from '../../types'; // auth-js/types
import { OktaAuthIdxInterface, IdxRemediation } from '../../types'; // auth-js/types
import generateIdxAction from './generateIdxAction';

export const generateRemediationFunctions = function generateRemediationFunctions(
authClient: OktaAuthIdxInterface,
remediationValue,
remediationValue: IdxRemediation[],
toPersist = {}
) {
return Object.fromEntries( remediationValue.map( remediation => {
return [
remediation.name,
generateIdxAction(authClient, remediation, toPersist),
];
}) );
return remediationValue.reduce((obj, remediation) => ({
...obj,
[remediation.name]: generateIdxAction(authClient, remediation, toPersist)
}), {});
};
39 changes: 39 additions & 0 deletions lib/idx/mixinMinimal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { OktaAuthConstructor } from '../base/types';
import { MinimalOktaOAuthInterface } from '../oidc/types';
import {
IdxTransactionManagerInterface,
OktaAuthIdxConstructor,
OktaAuthIdxOptions,
MinimalIdxAPI,
WebauthnAPI,
MinimalOktaAuthIdxInterface
} from './types';
import { IdxTransactionMeta } from './types/meta';
import { IdxStorageManagerInterface } from './types/storage';
import { createMinimalIdxAPI } from '../idx/factory/minimalApi';
import * as webauthn from './webauthn';

export function mixinMinimalIdx
<
M extends IdxTransactionMeta = IdxTransactionMeta,
S extends IdxStorageManagerInterface<M> = IdxStorageManagerInterface<M>,
O extends OktaAuthIdxOptions = OktaAuthIdxOptions,
TM extends IdxTransactionManagerInterface = IdxTransactionManagerInterface,
TBase extends OktaAuthConstructor<MinimalOktaOAuthInterface<M, S, O, TM>>
= OktaAuthConstructor<MinimalOktaOAuthInterface<M, S, O, TM>>
>
(
Base: TBase
): TBase & OktaAuthIdxConstructor<MinimalOktaAuthIdxInterface<M, S, O, TM>>
{
return class OktaAuthIdx extends Base implements MinimalOktaAuthIdxInterface<M, S, O, TM>
{
idx: MinimalIdxAPI;
static webauthn: WebauthnAPI = webauthn;

constructor(...args: any[]) {
super(...args);
this.idx = createMinimalIdxAPI(this);
}
};
}
1 change: 1 addition & 0 deletions lib/idx/remediators/Base/Remediator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export interface RemediationValues {
}

export interface RemediatorConstructor {
remediationName: string;
new<T extends RemediationValues>(
remediation: IdxRemediation,
values?: T,
Expand Down
27 changes: 16 additions & 11 deletions lib/idx/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@
import { interact } from './interact';
import { introspect } from './introspect';
import { remediate } from './remediate';
import { getFlowSpecification } from './flow';
import * as remediators from './remediators';
import { RemediationValues } from './remediators/Base/Remediator';
import {
OktaAuthIdxInterface,
IdxStatus,
Expand All @@ -29,12 +28,18 @@ import {
} from './types';
import { IdxMessage, IdxResponse } from './types/idx-js';
import { getSavedTransactionMeta, saveTransactionMeta } from './transactionMeta';
import { getAvailableSteps, getEnabledFeatures, getMessagesFromResponse, isTerminalResponse } from './util';
import {
getAvailableSteps,
getEnabledFeatures,
getMessagesFromResponse,
isTerminalResponse,
getFlowSpecification
} from './util';
import { Tokens } from '../oidc/types';
import { APIError } from '../errors/types';
declare interface RunData {
options: RunOptions;
values: remediators.RemediationValues;
values: RemediationValues;
status?: IdxStatus;
tokens?: Tokens;
nextStep?: NextStep;
Expand Down Expand Up @@ -70,7 +75,7 @@ function initializeValues(options: RunOptions) {
return values;
}

function initializeData(authClient, data: RunData): RunData {
function initializeData(authClient: OktaAuthIdxInterface, data: RunData): RunData {
let { options } = data;
options = {
...authClient.options.idx,
Expand All @@ -86,9 +91,9 @@ function initializeData(authClient, data: RunData): RunData {
const status = IdxStatus.PENDING;

// certain options can be set by the flow specification
flow = flow || authClient.idx.getFlow() || 'default';
flow = flow || authClient.idx.getFlow?.() || 'default';
if (flow) {
authClient.idx.setFlow(flow);
authClient.idx.setFlow?.(flow);
const flowSpec = getFlowSpecification(authClient, flow);
// Favor option values over flow spec
withCredentials = (typeof withCredentials !== 'undefined') ? withCredentials : flowSpec.withCredentials;
Expand All @@ -109,7 +114,7 @@ function initializeData(authClient, data: RunData): RunData {
};
}

async function getDataFromIntrospect(authClient, data: RunData): Promise<RunData> {
async function getDataFromIntrospect(authClient: OktaAuthIdxInterface, data: RunData): Promise<RunData> {
const { options } = data;
const {
stateHandle,
Expand Down Expand Up @@ -154,7 +159,7 @@ async function getDataFromIntrospect(authClient, data: RunData): Promise<RunData
return { ...data, idxResponse, meta };
}

async function getDataFromRemediate(authClient, data: RunData): Promise<RunData> {
async function getDataFromRemediate(authClient: OktaAuthIdxInterface, data: RunData): Promise<RunData> {
let {
idxResponse,
options,
Expand Down Expand Up @@ -202,7 +207,7 @@ async function getDataFromRemediate(authClient, data: RunData): Promise<RunData>
return { ...data, idxResponse, nextStep, canceled };
}

async function getTokens(authClient, data: RunData): Promise<Tokens> {
async function getTokens(authClient: OktaAuthIdxInterface, data: RunData): Promise<Tokens> {
let { meta, idxResponse } = data;
const { interactionCode } = idxResponse as IdxResponse;
const {
Expand All @@ -224,7 +229,7 @@ async function getTokens(authClient, data: RunData): Promise<Tokens> {
return tokenResponse.tokens;
}

async function finalizeData(authClient, data: RunData): Promise<RunData> {
async function finalizeData(authClient: OktaAuthIdxInterface, data: RunData): Promise<RunData> {
let {
options,
idxResponse,
Expand Down
9 changes: 9 additions & 0 deletions lib/idx/types/FlowSpecification.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { FlowIdentifier } from './FlowIdentifier';
import type { RemediationFlow } from '../flow/RemediationFlow';

export interface FlowSpecification {
flow: FlowIdentifier;
remediators: RemediationFlow;
actions?: string[];
withCredentials?: boolean;
}
Loading

0 comments on commit 3710f59

Please sign in to comment.