From e17a9563c1a602b76cc9ef7e9ed8b8fa5db0bdac Mon Sep 17 00:00:00 2001 From: Corentin Mors Date: Fri, 17 May 2024 15:44:22 +0200 Subject: [PATCH] Add steps to manage login --- src/modules/auth/confidential-sso/errors.ts | 5 ++ src/modules/auth/confidential-sso/index.ts | 62 +++++++++++++++++++++ src/modules/auth/confidential-sso/types.ts | 37 ++++++++++++ src/modules/auth/registerDevice.ts | 15 +++-- 4 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 src/modules/auth/confidential-sso/errors.ts create mode 100644 src/modules/auth/confidential-sso/index.ts create mode 100644 src/modules/auth/confidential-sso/types.ts diff --git a/src/modules/auth/confidential-sso/errors.ts b/src/modules/auth/confidential-sso/errors.ts new file mode 100644 index 00000000..3e7af44d --- /dev/null +++ b/src/modules/auth/confidential-sso/errors.ts @@ -0,0 +1,5 @@ +export class SAMLResponseNotFound extends Error { + constructor() { + super('SAML Response not found'); + } +} diff --git a/src/modules/auth/confidential-sso/index.ts b/src/modules/auth/confidential-sso/index.ts new file mode 100644 index 00000000..bfae4a41 --- /dev/null +++ b/src/modules/auth/confidential-sso/index.ts @@ -0,0 +1,62 @@ +import { chromium } from 'playwright-core'; +import { ConfirmLogin2Request, RequestLogin2Request } from './types'; +import { SAMLResponseNotFound } from './errors'; +import { apiConnect } from '../../tunnel-api-connect'; +import { performSSOVerification } from '../../../endpoints/performSSOVerification'; + +interface ConfidentialSSOParams { + requestedLogin: string; +} + +export const doConfidentialSSOVerification = async ({ requestedLogin }: ConfidentialSSOParams) => { + const api = await apiConnect({ isProduction: true, enclavePcrList: [] }); + const requestLoginResponse = await api.sendSecureContent({ + ...api, + path: 'authentication/RequestLogin2', + payload: { login: requestedLogin }, + }); + + const { idpAuthorizeUrl, spCallbackUrl, teamUuid, domainName } = requestLoginResponse; + + const browser = await chromium.launch({ headless: false, channel: 'chrome' }); + const context = await browser.newContext(); + const page = await context.newPage(); + + await page.goto(idpAuthorizeUrl); + + let samlResponseData; + const samlResponsePromise = new Promise((resolve) => { + page.on('request', (req) => { + const reqURL = req.url(); + if (reqURL === spCallbackUrl) { + samlResponseData = req.postData(); + if (browser) { + void browser.close(); + } + console.log(`Intercepted SAML response, browser closed`); + resolve(undefined); + } + }); + }); + + await samlResponsePromise; + + const samlResponse = new URLSearchParams(samlResponseData).get('SAMLResponse'); + + if (!samlResponse) { + throw new SAMLResponseNotFound(); + } + + const confirmLoginResponse = await api.sendSecureContent({ + ...api, + path: 'authentication/ConfirmLogin2', + payload: { teamUuid, domainName, samlResponse }, + }); + + const ssoVerificationResult = await performSSOVerification({ + login: requestedLogin, + ssoToken: confirmLoginResponse.ssoToken, + }); + + return { ...ssoVerificationResult, ssoSpKey: confirmLoginResponse.userServiceProviderKey }; +}; diff --git a/src/modules/auth/confidential-sso/types.ts b/src/modules/auth/confidential-sso/types.ts new file mode 100644 index 00000000..9c56c6d1 --- /dev/null +++ b/src/modules/auth/confidential-sso/types.ts @@ -0,0 +1,37 @@ +export interface RequestLogin2Data { + login: string; +} + +export interface RequestLogin2Output { + domainName: string; + idpAuthorizeUrl: string; + spCallbackUrl: string; + teamUuid: string; + validatedDomains: string[]; +} + +export interface RequestLogin2Request { + path: 'authentication/RequestLogin2'; + input: RequestLogin2Data; + output: RequestLogin2Output; +} + +export interface ConfirmLogin2Data { + teamUuid: string; + domainName: string; + samlResponse: string; +} + +export interface ConfirmLogin2Output { + ssoToken: string; + userServiceProviderKey: string; + exists: boolean; + currentAuthenticationMethods: string[]; + expectedAuthenticationMethods: string[]; +} + +export interface ConfirmLogin2Request { + path: 'authentication/ConfirmLogin2'; + input: ConfirmLogin2Data; + output: ConfirmLogin2Output; +} diff --git a/src/modules/auth/registerDevice.ts b/src/modules/auth/registerDevice.ts index 8e7279d8..277b852e 100644 --- a/src/modules/auth/registerDevice.ts +++ b/src/modules/auth/registerDevice.ts @@ -1,5 +1,6 @@ import winston from 'winston'; import { doSSOVerification } from './sso'; +import { doConfidentialSSOVerification } from './confidential-sso'; import { completeDeviceRegistration, performDashlaneAuthenticatorVerification, @@ -63,13 +64,15 @@ export const registerDevice = async (params: RegisterDevice) => { })); } else if (selectedVerificationMethod.type === 'sso') { if (selectedVerificationMethod.ssoInfo.isNitroProvider) { - throw new Error('Confidential SSO is currently not supported'); + ({ authTicket, ssoSpKey } = await doConfidentialSSOVerification({ + requestedLogin: login, + })); + } else { + ({ authTicket, ssoSpKey } = await doSSOVerification({ + requestedLogin: login, + serviceProviderURL: selectedVerificationMethod.ssoInfo.serviceProviderUrl, + })); } - - ({ authTicket, ssoSpKey } = await doSSOVerification({ - requestedLogin: login, - serviceProviderURL: selectedVerificationMethod.ssoInfo.serviceProviderUrl, - })); } else { throw new Error('Auth verification method not supported: ' + selectedVerificationMethod.type); }