-
Notifications
You must be signed in to change notification settings - Fork 9
12968 sso pairing #113
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
12968 sso pairing #113
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
06e4abc
12965 add passwordless config in db
giregk 1464b6c
1965 add uses passwordless unlock route
giregk e56f1be
12968 pairing sso vaults
giregk 18fb53d
12968 add bypass sso system
giregk 07457ba
12968 remove useless code
giregk 27cbf6d
12968 fix issues
giregk f2eff97
12968 clean code
giregk fdc90b8
12968 revert breaking api change
giregk File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
11 changes: 11 additions & 0 deletions
11
migrations/2026-07-27_10-00-00_add_password_backup_public_key.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| //2026-07-27_10-00-00_add_password_backup_public_key | ||
|
|
||
| exports.up = function (db) { | ||
| return db.query( | ||
| 'ALTER TABLE user_devices ADD COLUMN IF NOT EXISTS password_backup_public_key TEXT', | ||
| ); | ||
| }; | ||
|
|
||
| exports.down = function (db) { | ||
| return db.query('ALTER TABLE user_devices DROP COLUMN IF EXISTS password_backup_public_key'); | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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`, | ||
| ); | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| 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 usesPasswordless = await usesPasswordlessUnlockForEmail(email, bankIds.internalId); | ||
| if (usesPasswordless === null) { | ||
| // return 200 to prevent account enumeration. | ||
| res.status(200).json({ usesPasswordlessUnlock: false }); | ||
| return; | ||
| } | ||
|
|
||
| res.status(200).json({ | ||
| usesPasswordlessUnlock: usesPasswordless, | ||
| }); | ||
| return; | ||
| } catch (e) { | ||
| logError('usesPasswordlessUnlock', e); | ||
| // return 200 to prevent account enumeration. | ||
| res.status(200).json({ usesPasswordlessUnlock: false }); | ||
| return; | ||
| } | ||
| }; | ||
|
|
||
| export const usesPasswordlessUnlockForEmail = async ( | ||
| email: string, | ||
| bankId: number, | ||
| ): Promise<boolean | null> => { | ||
| 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`, | ||
| [bankId], | ||
| ); | ||
| if (bankRes.rowCount === 0) { | ||
| logInfo(email, 'usesPasswordlessUnlock fail: bad bank'); | ||
| return null; | ||
| } | ||
|
|
||
| const userRes = await db.query( | ||
| `SELECT deactivated, ms_entra_id FROM users WHERE email = $1 AND bank_id = $2`, | ||
| [email, bankId], | ||
| ); | ||
| if ((userRes.rowCount ?? 0) === 0 || userRes.rows[0].deactivated) { | ||
| logInfo(email, 'usesPasswordlessUnlock fail: email not found or deactivated'); | ||
| return null; | ||
| } | ||
|
|
||
| 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`, | ||
| [bankId, email], | ||
| ); | ||
| const patternPasswordless = (patternRes.rowCount ?? 0) > 0; | ||
| const usesPasswordlessUnlock = msEntraPasswordless || patternPasswordless; | ||
| return usesPasswordlessUnlock; | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| import { db } from '../../../helpers/db'; | ||
| import { logError, logInfo } from '../../../helpers/logger'; | ||
| import { inputSanitizer } from '../../../helpers/sanitizer'; | ||
| import { checkBasicAuth2 } from '../../helpers/authorizationChecks'; | ||
|
|
||
| // Lets an already-authorized device (A) reject a peer device (B) that is itself already | ||
| // AUTHORIZED (identity verified via SSO or email) but still waiting for a password backup | ||
| // (see getPendingSsoDevices) — ie. before the device ever actually gains access to the vault. | ||
| // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any | ||
| export const rejectSsoDevice = async (req: any, res: any) => { | ||
| try { | ||
| const deviceIdToReject = inputSanitizer.getString(req.body?.deviceIdToReject); | ||
| if (!deviceIdToReject) { | ||
| logInfo(req.body?.userEmail, 'rejectSsoDevice fail: missing deviceIdToReject'); | ||
| return res.status(403).end(); | ||
| } | ||
|
|
||
| const basicAuth = await checkBasicAuth2(req); | ||
| if (!basicAuth.granted) { | ||
| logInfo(req.body?.userEmail, 'rejectSsoDevice fail: auth not granted'); | ||
| return res.status(401).end(); | ||
| } | ||
|
|
||
| const result = await db.query( | ||
| `UPDATE user_devices | ||
| SET device_unique_id=null, authorization_status='REVOKED_BY_USER', device_public_key_2=null, | ||
| encrypted_password_backup_2='', password_backup_public_key=null, revocation_date=$1 | ||
| WHERE device_unique_id=$2 AND user_id=$3 AND bank_id=$4 | ||
| AND authorization_status='AUTHORIZED' AND password_backup_public_key IS NOT NULL`, | ||
| [new Date().toISOString(), deviceIdToReject, basicAuth.userId, basicAuth.bankIds.internalId], | ||
| ); | ||
| // applied=false means another authorized device already approved (or also rejected) this | ||
| // same device in the meantime. | ||
| const applied = (result.rowCount ?? 0) > 0; | ||
| logInfo(req.body?.userEmail, 'rejectSsoDevice OK'); | ||
| return res.status(200).json({ applied }); | ||
| } catch (e) { | ||
| logError(req.body?.userEmail, 'rejectSsoDevice', e); | ||
| return res.status(400).end(); | ||
| } | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.