Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions migrations/2026-07-28_14-11-47_sso_passwordless.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//2026-07-28_14-11-47_sso_passwordless

exports.up = async function (db) {
await db.query(
`ALTER TABLE allowed_emails ADD COLUMN IF NOT EXISTS uses_passwordless_auth BOOLEAN NOT NULL DEFAULT FALSE`,
);
await db.query(
`ALTER TABLE banks ADD COLUMN IF NOT EXISTS ms_entra_vaults_use_passwordless_auth BOOLEAN NOT NULL DEFAULT FALSE`,
);
await db.query(`ALTER TABLE bank_sso_config DROP COLUMN IF EXISTS is_sso_v2`);
};

exports.down = async function (db) {
await db.query(`ALTER TABLE allowed_emails DROP COLUMN IF EXISTS uses_passwordless_auth`);
await db.query(`ALTER TABLE banks DROP COLUMN IF EXISTS ms_entra_vaults_use_passwordless_auth`);
await db.query(
`ALTER TABLE bank_sso_config ADD COLUMN IF NOT EXISTS is_sso_v2 BOOLEAN NOT NULL DEFAULT FALSE`,
);
};
71 changes: 71 additions & 0 deletions src/api2/routes/authentication/usesPasswordlessUnlock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { Request, Response } from 'express';
import Joi from 'joi';
import { getBankIds } from '../../helpers/bankUUID';
import { db } from '../../../helpers/db';
import { logError, logInfo } from '../../../helpers/logger';

export const usesPasswordlessUnlock = async (req: Request, res: Response): Promise<void> => {
try {
const joiRes = Joi.object({ userEmail: Joi.string().email().lowercase().required() }).validate(
req.body,
);
if (joiRes.error) {
res.status(400).json({ error: joiRes.error.details });
return;
}
const email = joiRes.value.userEmail;
const bankIds = await getBankIds(req);
const bankRes = await db.query(
`SELECT
b.ms_entra_vaults_use_passwordless_auth,
EXISTS (SELECT 1 FROM bank_sso_config WHERE bank_id = b.id) AS sso_configured
FROM banks AS b
WHERE b.id = $1`,
[bankIds.internalId],
);
if (bankRes.rowCount === 0) {
logInfo(email, 'usesPasswordlessUnlock fail: bad bank');
res.status(400).end();
return;
}

const userRes = await db.query(
`SELECT deactivated, ms_entra_id FROM users WHERE email = $1 AND bank_id = $2`,
[email, bankIds.internalId],
);
if ((userRes.rowCount ?? 0) === 0 && userRes.rows[0].deactivated) {
logInfo(email, 'usesPasswordlessUnlock fail: email not found or deactivated');
res.status(400).end();
return;
}

const msEntraPasswordless =
bankRes.rows[0].sso_configured &&
bankRes.rows[0].ms_entra_vaults_use_passwordless_auth &&
(userRes.rowCount ?? 0) > 0 &&
userRes.rows[0].ms_entra_id != null;

const patternRes = await db.query(
`SELECT 1 FROM allowed_emails
WHERE bank_id = $1
AND uses_passwordless_auth = true
AND (
(LEFT(pattern, 2) != '*@' AND pattern = $2) OR
(LEFT(pattern, 2) = '*@' AND SPLIT_PART($2, '@', 2) = SUBSTRING(pattern FROM 3))
)
LIMIT 1`,
[bankIds.internalId, email],
);
const patternPasswordless = (patternRes.rowCount ?? 0) > 0;
const usesPasswordlessUnlock = msEntraPasswordless || patternPasswordless;

res.status(200).json({
usesPasswordlessUnlock,
});
return;
} catch (e) {
logError('usesPasswordlessUnlock', e);
res.status(400).end();
return;
}
};
17 changes: 9 additions & 8 deletions src/api2/routes/bank/getBankConfig.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { Request, Response } from 'express';
import { db } from '../../../helpers/db';
import env from '../../../helpers/env';
import { logError, logInfo } from '../../../helpers/logger';
import { getBankIds } from '../../helpers/bankUUID';

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
export const getBankConfig = async (req: any, res: any): Promise<void> => {
export const getBankConfig = async (req: Request, res: Response): Promise<void> => {
try {
const bankIds = await getBankIds(req);
const bankRes = await db.query(
Expand All @@ -16,8 +15,7 @@ export const getBankConfig = async (req: any, res: any): Promise<void> => {
json_agg(
json_build_object(
'openid_configuration_url', sso.openid_configuration_url,
'client_id', sso.client_id,
'is_sso_v2', sso.is_sso_v2
'client_id', sso.client_id
)
) FILTER (WHERE sso.id IS NOT NULL),
'[]'
Expand All @@ -30,17 +28,20 @@ export const getBankConfig = async (req: any, res: any): Promise<void> => {
);
if (bankRes.rowCount === 0) {
logInfo(req.body?.userEmail, 'getBankConfig fail: bad bank');
return res.status(400).end();
res.status(400).end();
return;
}
logInfo(req.body?.userEmail, 'getBankConfig OK');
return res.status(200).json({
res.status(200).json({
newUrl: bankRes.rows[0].redirect_url,
bankName: bankRes.rows[0].name,
preventUpdatePopup: bankRes.rows[0]?.settings?.PREVENT_UPDATE_POPUP || false,
ssoConfigs: bankRes.rows[0]?.sso_configs.length == 0 ? null : bankRes.rows[0]?.sso_configs,
});
return;
} catch (e) {
logError('getBankConfig', e);
return res.status(400).end();
res.status(400).end();
return;
}
};
5 changes: 5 additions & 0 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ import { shamirSecurityAlert } from './api2/routes/shamirRecovery/shamirSecurity
import { getShamirRecoveryChallenge } from './api2/routes/shamirRecovery/getShamirRecoveryChallenge';
import { getRecoveryKeyPair } from './api2/routes/shamirRecovery/getRecoveryKeyPair';
import helmet from 'helmet';
import { usesPasswordlessUnlock } from './api2/routes/authentication/usesPasswordlessUnlock';

export const app = express();

Expand Down Expand Up @@ -169,6 +170,10 @@ app.post(
app.post(['/:bankUUID/api2/authenticate', '/api2/authenticate'], authenticate2);
app.post(['/:bankUUID/api2/disconnect', '/api2/disconnect'], disconnect2);
app.post(['/:bankUUID/api2/authenticate-device-only'], authenticateDeviceOnly);
app.post(
['/:bankUUID/api2/uses-passwordless-unlock', '/api2/uses-passwordless-unlock'],
usesPasswordlessUnlock,
);

// OPENID
app.post(['/:bankUUID/api2/authenticate-with-openid-auth-code'], authenticateWithOpenidAuthCode);
Expand Down
Loading