Introduce user facing error page for enabling mfa

pull/971/head
uowis 2 weeks ago
parent 39bf909c21
commit cdd0a5d1f9

@ -21,6 +21,7 @@ export function createErrPage(appModel: AppModel) {
errPage === 'not-found' ? createNotFoundPage(appModel, errMessage) :
errPage === 'access-denied' ? createForbiddenPage(appModel, errMessage) :
errPage === 'account-deleted' ? createAccountDeletedPage(appModel) :
errPage === 'mfa-not-enabled' ? createMfaNotEnabledErrorPage(appModel) :
createOtherErrorPage(appModel, errMessage);
}
@ -81,6 +82,25 @@ export function createAccountDeletedPage(appModel: AppModel) {
]);
}
/**
* Creates a page that show the user's account does not have multifactor authentication enabled, despite being needed.
*/
export function createMfaNotEnabledErrorPage(appModel: AppModel) {
document.title = t("Multi-factor authentication required{{suffix}}", {suffix: getPageTitleSuffix(getGristConfig())});
const searchParams = new URL(location.href).searchParams;
return pagePanelsError(appModel, t("Multi-factor authentication required{{suffix}}", {suffix: ''}), [
cssErrorText(t("Multi-factor-authentication is required for accessing this site, but it is not set up on your account. Please enable it and try again.")),
cssButtonWrap(bigPrimaryButtonLink(
t("Set up Multi-factor authentication"), {href: getGristConfig().mfaSettingsUrl, target: '_blank'}, testId('error-setup-mfa')
)),
cssButtonWrap(bigBasicButtonLink(
t("Try again"), {href: getSignupUrl({ nextUrl: searchParams.get("next") || "" })}, testId('error-signin')
))
])
}
/**
* Creates a "Page not found" page.
*/

@ -793,6 +793,9 @@ export interface GristLoadConfig {
// If backend has an email service for sending notifications.
notifierEnabled?: boolean;
// The URL to the external IDP, where the user can set up Multi-factor authentication
mfaSettingsUrl?: string;
}
export const Features = StringUnion(

@ -1273,6 +1273,11 @@ export class FlexServer implements GristServer {
this.app.get('/signed-out', expressWrap((req, resp) =>
this._sendAppPage(req, resp, {path: 'error.html', status: 200, config: {errPage: 'signed-out'}})));
// Add a static "mfa-not-enabled" page. This is where logout typically lands when GRIST_OIDC_SP_FORCE_MFA is true
// but the user hasn't configured multi-factor authentication.
this.app.get('/login/error/mfa-not-enabled', expressWrap((req, resp) =>
this._sendAppPage(req, resp, {path: 'error.html', status: 401, config: {errPage: 'mfa-not-enabled'}})));
const comment = await this._loginMiddleware.addEndpoints(this.app);
this.info.push(['loginMiddlewareComment', comment]);

@ -170,7 +170,16 @@ export class OIDCConfig {
if (!amr) {
throw new Error('OIDCConfig: could not verify mfa status due to missing amr claim. Make sure your IDP returns it.');
} else if (!amr.includes("mfa")) {
throw new Error(`OIDCConfig: multi-factor-authentication is not enabled for ${userInfo.email}.`);
log.error(`OIDCConfig: multi-factor-authentication is not enabled for ${userInfo.email}.`);
delete mreq.session.oidc;
// Convert absolute URL into relative, since it will be prefixed further down the line
let targetURL = new URL(targetUrl as string);
let targetUrlRelative = targetURL.pathname;
if (targetURL.searchParams.toString()) targetUrlRelative += "?" + targetURL.searchParams.toString();
res.redirect(`/login/error/mfa-not-enabled?next=${targetUrlRelative}`);
return;
}
}

@ -24,6 +24,7 @@ import * as handlebars from 'handlebars';
import jsesc from 'jsesc';
import * as path from 'path';
import difference = require('lodash/difference');
import * as process from "node:process";
const translate = (req: express.Request, key: string, args?: any) => req.t(`sendAppPage.${key}`, args);
@ -98,6 +99,7 @@ export function makeGristConfig(options: MakeGristConfigOptions): GristLoadConfi
canCloseAccount: isAffirmative(process.env.GRIST_ACCOUNT_CLOSE),
experimentalPlugins: isAffirmative(process.env.GRIST_EXPERIMENTAL_PLUGINS),
notifierEnabled: server?.hasNotifier(),
mfaSettingsUrl: process.env.GRIST_OIDC_SP_MFA_SETTINGS_URL,
...extra,
};
}

Loading…
Cancel
Save