Skip to content

Commit

Permalink
Merge pull request #276 from torusresearch/feat/setup-mfa
Browse files Browse the repository at this point in the history
Add enable/manage MFA functionality
  • Loading branch information
chaitanyapotti authored Jan 9, 2024
2 parents 53fdf94 + 1b0981c commit 42e3606
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 23 deletions.
18 changes: 9 additions & 9 deletions examples/vue-example/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 43 additions & 6 deletions examples/vue-example/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@
<div class="flex flex-col sm:flex-row gap-4 bottom-gutter">
<button class="btn" @click="getEd25519Key">Get Ed25519Key</button>
</div>
<div class="flex flex-col sm:flex-row gap-4 bottom-gutter">
<button v-if="!isMFAEnabled" class="btn" @click="enableMFA">Enable MFA</button>
<button v-else class="btn" @click="manageMFA">Manage MFA</button>
</div>
<p class="btn-label">Signing</p>
<div class="flex flex-col sm:flex-row gap-4 bottom-gutter">
<button class="btn" :disabled="!ethereumPrivateKeyProvider?.provider" @click="signMessage">Sign test Eth Message</button>
Expand Down Expand Up @@ -177,10 +181,14 @@ export default defineComponent({
};
},
async created() {
const openlogin = this.openloginInstance;
await openlogin.init();
if (openlogin.privKey) {
this.privKey = openlogin.privKey;
await this.openloginInstance.init();
if (this.openloginInstance.state.factorKey) {
this.useMpc = true;
this.openloginInstance.options.useMpc = true;
await this.openloginInstance.init();
}
if (this.openloginInstance.privKey || this.openloginInstance.state.factorKey) {
this.privKey = this.openloginInstance.privKey || this.openloginInstance.state.factorKey as string;
await this.setProvider(this.privKey);
}
this.loading = false;
Expand Down Expand Up @@ -226,12 +234,14 @@ export default defineComponent({
}
: undefined,
});
op.init();
return op;
},
showEmailFlow(): boolean {
return this.selectedLoginProvider === LOGIN_PROVIDER.EMAIL_PASSWORDLESS;
},
isMFAEnabled(): boolean {
return this.openloginInstance.state.userInfo?.isMfaEnabled || false;
}
},
methods: {
async login() {
Expand All @@ -257,6 +267,7 @@ export default defineComponent({
const openLoginObj: LoginParams = {
loginProvider: this.selectedLoginProvider,
mfaLevel: "optional",
// pass empty string '' as loginProvider to open default torus modal
// with all default supported login providers or you can pass specific
// login provider from available list to set as default.
Expand Down Expand Up @@ -300,7 +311,7 @@ export default defineComponent({
async setProvider(privKey: string) {
if (this.useMpc) {
const { factorKey, tssPubKey, tssShareIndex, userInfo, tssShare, tssNonce, signatures } = this.openloginInstance.state;
const { factorKey, tssPubKey, tssShareIndex, userInfo, tssShare, tssNonce, signatures } = this.openloginInstance.state;
this.ethereumPrivateKeyProvider = new EthMpcPrivKeyProvider({
config: {
chainConfig: {
Expand Down Expand Up @@ -395,6 +406,32 @@ export default defineComponent({
this.printToConsole("User Info", userInfo);
},
async enableMFA() {
if (!this.openloginInstance || !this.openloginInstance.sessionId) {
throw new Error("User not logged in")
}
await this.openloginInstance.enableMFA({
loginProvider: this.selectedLoginProvider,
extraLoginOptions: {
login_hint: this.openloginInstance.getUserInfo().email,
flow_type: this.emailFlowType,
}
});
},
async manageMFA() {
if (!this.openloginInstance || !this.openloginInstance.sessionId) {
throw new Error("User not logged in")
}
await this.openloginInstance.manageMFA({
loginProvider: this.selectedLoginProvider,
extraLoginOptions: {
login_hint: this.openloginInstance.getUserInfo().email,
flow_type: this.emailFlowType,
}
});
},
async getOpenloginState() {
if (!this.openloginInstance) {
throw new Error("Openlogin is not available.");
Expand Down
3 changes: 2 additions & 1 deletion packages/openlogin-utils/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ export const MFA_LEVELS = {
export const OPENLOGIN_ACTIONS = {
LOGIN: "login",
ENABLE_MFA: "enable_mfa",
MODIFY_MFA: "modify_mfa",
MANAGE_MFA: "manage_mfa",
MODIFY_SOCIAL_FACTOR: "modify_social_factor",
} as const;

export const BUILD_ENV = {
Expand Down
15 changes: 15 additions & 0 deletions packages/openlogin-utils/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,14 @@ export type LoginParams = BaseRedirectParams & {
curve?: SUPPORTED_KEY_CURVES_TYPE;
};

export type ManageMFAParams = LoginParams & {
/**
* Allows the dapp to set a custom redirect url for the manage mfa flow.
*
*/
dappUrl?: string;
};

export type SocialMfaModParams = {
/**
* loginProvider sets the oauth login method to be used.
Expand Down Expand Up @@ -627,6 +635,13 @@ export type OpenLoginOptions = {
*/
sdkUrl?: string;

/**
* dashboardUrl is for internal development use only and is used to override the
* `buildEnv` parameter.
* @internal
*/
dashboardUrl?: string;

/**
* options for whitelabling default openlogin modal.
*/
Expand Down
57 changes: 50 additions & 7 deletions packages/openlogin/src/OpenLogin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
BUILD_ENV,
jsonToBase64,
LoginParams,
ManageMFAParams,
OPENLOGIN_ACTIONS,
OPENLOGIN_NETWORK,
OpenLoginOptions,
Expand Down Expand Up @@ -46,18 +47,22 @@ class OpenLogin {
if (!options.sdkUrl && !options.useMpc) {
if (options.buildEnv === BUILD_ENV.DEVELOPMENT) {
options.sdkUrl = "http://localhost:3000";
options.dashboardUrl = "http://localhost:5173/wallet/account";
} else if (options.buildEnv === BUILD_ENV.STAGING) {
options.sdkUrl = "https://staging-auth.web3auth.io";
options.dashboardUrl = "https://staging-account.web3auth.io/wallet/account";
} else if (options.buildEnv === BUILD_ENV.TESTING) {
options.sdkUrl = "https://develop-auth.web3auth.io";
options.dashboardUrl = "https://develop-account.web3auth.io/wallet/account";
} else {
options.sdkUrl = "https://auth.web3auth.io";
options.dashboardUrl = "https://account.web3auth.io/wallet/account";
}
}

if (options.useMpc && !options.sdkUrl) {
if (Object.values(TORUS_LEGACY_NETWORK).includes(options.network as TORUS_LEGACY_NETWORK_TYPE))
throw new Error("MPC is not supported on legacy networks");
throw new Error("MPC is not supported on legacy networks, please use sapphire_devnet or sapphire_mainnet.");
if (options.buildEnv === BUILD_ENV.DEVELOPMENT) {
options.sdkUrl = "http://localhost:3000";
} else if (options.buildEnv === BUILD_ENV.STAGING) {
Expand Down Expand Up @@ -250,9 +255,9 @@ class OpenLogin {
this.currentStorage.set("sessionId", "");
}

async setupMFA(params: Partial<BaseRedirectParams>): Promise<boolean> {
async enableMFA(params: Partial<LoginParams>): Promise<boolean> {
if (!this.sessionId) throw LoginError.userNotLoggedIn();

if (this.state.userInfo.isMfaEnabled) throw LoginError.mfaAlreadyEnabled();
// in case of redirect mode, redirect url will be dapp specified
// in case of popup mode, redirect url will be sdk specified
const defaultParams: BaseRedirectParams = {
Expand All @@ -265,17 +270,55 @@ class OpenLogin {
params: {
...defaultParams,
...params,
mfaLevel: "mandatory",
},
sessionId: this.sessionId,
};

const result = await this.openloginHandler(`${this.baseUrl}/start`, dataObject);
if (this.options.uxMode === UX_MODE.REDIRECT) return undefined;
const result = await this.openloginHandler(`${this.baseUrl}/start`, dataObject, getTimeout(params.loginProvider));
if (this.options.uxMode === UX_MODE.REDIRECT) return null;
if (result.error) {
this.dappState = result.state;
throw LoginError.loginFailed(result.error);
}
this.sessionManager.sessionId = result.sessionId;
this.options.sessionNamespace = result.sessionNamespace;
this.currentStorage.set("sessionId", result.sessionId);
await this.rehydrateSession();
return true;
return Boolean(this.state.userInfo?.isMfaEnabled);
}

async manageMFA(params: Partial<ManageMFAParams>): Promise<void> {
if (!this.sessionId) throw LoginError.userNotLoggedIn();
if (!this.state.userInfo.isMfaEnabled) throw LoginError.mfaNotEnabled();

// in case of redirect mode, redirect url will be dapp specified
// in case of popup mode, redirect url will be sdk specified
const defaultParams = {
redirectUrl: this.options.dashboardUrl,
dappUrl: `${window.location.origin}${window.location.pathname}`,
};

const dataObject: OpenloginSessionConfig = {
actionType: OPENLOGIN_ACTIONS.MANAGE_MFA,
options: this.options,
params: {
...defaultParams,
...params,
},
};

const loginId = await this.getLoginId(dataObject);
const configParams: BaseLoginParams = {
loginId,
sessionNamespace: this.options.network,
};
const loginUrl = constructURL({
baseURL: `${this.baseUrl}/start`,
hash: { b64Params: jsonToBase64(configParams) },
});

window.open(loginUrl, "_blank");
}

async changeSocialFactor(params: SocialMfaModParams & Partial<BaseRedirectParams>): Promise<boolean> {
Expand All @@ -288,7 +331,7 @@ class OpenLogin {
};

const dataObject: OpenloginSessionConfig = {
actionType: OPENLOGIN_ACTIONS.MODIFY_MFA,
actionType: OPENLOGIN_ACTIONS.MODIFY_SOCIAL_FACTOR,
options: this.options,
params: {
...defaultParams,
Expand Down
10 changes: 10 additions & 0 deletions packages/openlogin/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ export class LoginError extends OpenloginError {
5113: "login popup has been closed by the user",
5114: "Login failed",
5115: "Popup was blocked. Please call this function as soon as user clicks button or use redirect mode",
5116: "MFA already enabled",
5117: "MFA not yet enabled. Please call `enableMFA` first",
};

public constructor(code: number, message?: string) {
Expand Down Expand Up @@ -111,4 +113,12 @@ export class LoginError extends OpenloginError {
public static popupBlocked(extraMessage = ""): OpenloginError {
return LoginError.fromCode(5115, extraMessage);
}

public static mfaAlreadyEnabled(extraMessage = ""): OpenloginError {
return LoginError.fromCode(5116, extraMessage);
}

public static mfaNotEnabled(extraMessage = ""): OpenloginError {
return LoginError.fromCode(5117, extraMessage);
}
}

0 comments on commit 42e3606

Please sign in to comment.