diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index de12ae239f90..353f855a58ec 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -1,4 +1,12 @@ +## 13.15.1 + +_Released 9/26/2024 (PENDING)_ + +**Features:** + +- Client certificate selector option during e2e tests. Addresses [#27302](https://github.com/cypress-io/cypress/issues/27302). + ## 13.15.0 _Released 9/25/2024_ diff --git a/cli/types/cypress.d.ts b/cli/types/cypress.d.ts index f0f0c914b1c3..bcfe4611a4df 100644 --- a/cli/types/cypress.d.ts +++ b/cli/types/cypress.d.ts @@ -2141,6 +2141,11 @@ declare namespace Cypress { */ task(event: string, arg?: any, options?: Partial): Chainable + /** + * Choose a cert in the system's cert store based on group. + */ + chooseCert(group: string): void + /** * Enables you to work with the subject yielded from the previous command. * @@ -2815,6 +2820,10 @@ declare namespace Cypress { } interface PEMCert { + /** + * Group for filtering certificates (Optional) + */ + group?: string /** * Path to the certificate file, relative to project root. */ @@ -2830,6 +2839,10 @@ declare namespace Cypress { } interface PFXCert { + /** + * Group for filtering certificates (Optional) + */ + group?: string /** * Path to the certificate container, relative to project root. */ diff --git a/packages/driver/src/cy/commands/chooseCert.ts b/packages/driver/src/cy/commands/chooseCert.ts new file mode 100644 index 000000000000..4a948825977d --- /dev/null +++ b/packages/driver/src/cy/commands/chooseCert.ts @@ -0,0 +1,47 @@ +import _ from 'lodash' + +import $errUtils from '../../cypress/error_utils' +import type { Log } from '../../cypress/log' +import { runPrivilegedCommand } from '../../util/privileged_channel' + +interface InternalChooseCertOptions extends Partial { + _log?: Log +} + +export default (Commands, Cypress, cy) => { + Commands.addAll({ + chooseCert (group: string | null, userOptions: Partial) { + const options: InternalChooseCertOptions = _.defaults({}, userOptions, { + log: true, + }) + + let consoleOutput = {} + + let message = `choosing cert group: ${group}` + + options._log = Cypress.log({ + hidden: !options.log, + message, + consoleProps () { + return consoleOutput + }, + }) + + if (group !== null && !_.isString(group)) { + $errUtils.throwErrByPath('chooseCert.invalid_argument', { + onFail: options._log, + args: { group: group || 'null' }, + }) + } + + return runPrivilegedCommand({ + commandName: 'chooseCert', + cy, + Cypress: (Cypress as unknown) as InternalCypress.Cypress, + options: { + group, + }, + }) + }, + }) +} diff --git a/packages/driver/src/cy/commands/index.ts b/packages/driver/src/cy/commands/index.ts index 8131a1648570..0232226e1b3d 100644 --- a/packages/driver/src/cy/commands/index.ts +++ b/packages/driver/src/cy/commands/index.ts @@ -6,6 +6,8 @@ import * as Aliasing from './aliasing' import * as Asserting from './asserting' +import * as ChooseCert from './chooseCert' + import * as Clock from './clock' import * as Commands from './commands' @@ -57,6 +59,7 @@ export const allCommands = { Agents, Aliasing, Asserting, + ChooseCert, Clock, Commands, Connectors, diff --git a/packages/driver/src/cypress/error_messages.ts b/packages/driver/src/cypress/error_messages.ts index d77a28deaf29..eb1957cc1c36 100644 --- a/packages/driver/src/cypress/error_messages.ts +++ b/packages/driver/src/cypress/error_messages.ts @@ -197,6 +197,12 @@ export default { }, }, + chooseCert: { + invalid_argument: { + message: `${cmd('chooseCert')} must be passed with a valid string (used in certificate groups) or null. You passed: \`{{group}}\`.`, + }, + }, + clear: { invalid_element: { message: stripIndent`\ diff --git a/packages/network/lib/agent.ts b/packages/network/lib/agent.ts index 36fea530ed7c..736d05c4567e 100644 --- a/packages/network/lib/agent.ts +++ b/packages/network/lib/agent.ts @@ -74,13 +74,13 @@ type FamilyCache = { [host: string]: 4 | 6 } -export function buildConnectReqHead (hostname: string, port: string, proxy: url.Url) { +export function buildConnectReqHead (hostname: string, port: string, proxyAuth?: string | null) { const connectReq = [`CONNECT ${hostname}:${port} HTTP/1.1`] connectReq.push(`Host: ${hostname}:${port}`) - if (proxy.auth) { - connectReq.push(`Proxy-Authorization: Basic ${Buffer.from(proxy.auth).toString('base64')}`) + if (proxyAuth) { + connectReq.push(`Proxy-Authorization: Basic ${Buffer.from(proxyAuth).toString('base64')}`) } return connectReq.join(CRLF) + _.repeat(CRLF, 2) @@ -430,7 +430,7 @@ class HttpsAgent extends https.Agent { proxySocket.once('error', onError) proxySocket.once('data', onData) - const connectReq = buildConnectReqHead(hostname, port, proxy) + const connectReq = buildConnectReqHead(hostname, port, proxy.auth) proxySocket.setNoDelay(true) proxySocket.write(connectReq) diff --git a/packages/network/lib/client-certificates.ts b/packages/network/lib/client-certificates.ts index 14bb992db5d4..a81bdf2f7b15 100644 --- a/packages/network/lib/client-certificates.ts +++ b/packages/network/lib/client-certificates.ts @@ -71,18 +71,19 @@ export class UrlMatcher { * Defines the certificates that should be used for the specified URL */ export class UrlClientCertificates { - constructor (url: string) { - this.subjects = '' - this.url = url - this.pathnameLength = new URL(url).pathname.length - this.clientCertificates = new ClientCertificates() + clientCertificates: Record<'default' | string, ClientCertificates> = { + default: new ClientCertificates(), } - clientCertificates: ClientCertificates url: string - subjects: string + subjects: string = '' pathnameLength: number matchRule: ParsedUrl | undefined + constructor (url: string) { + this.url = url + this.pathnameLength = new URL(url).pathname.length + } + addSubject (subject: string) { if (!this.subjects) { this.subjects = subject @@ -90,6 +91,36 @@ export class UrlClientCertificates { this.subjects = `${this.subjects} - ${subject}` } } + + addCA (ca: Buffer) { + this.clientCertificates.default.ca.push(ca) + } + + getCA () { + return this.clientCertificates.default.ca + } + + addCertGroup (group: string) { + if (!this.clientCertificates[group]) { + this.clientCertificates[group] = new ClientCertificates() + } + } + + getCertificates (group: string | null): ClientCertificates | null { + const certGroup = group || 'default' + const isDefault = group === null || group === 'default' + const certs = this.clientCertificates[certGroup] + + if (!certs || (!isDefault && certs.cert.length === 0 && certs.pfx.length === 0)) { + debug(`no client certificates found for url '${this.url}' and group '${certGroup}'`) + + return null + } + + certs.ca = this.clientCertificates.default.ca + + return certs + } } /** @@ -125,6 +156,7 @@ export class PfxCertificate { export class ClientCertificateStore { private _urlClientCertificates: UrlClientCertificates[] = [] + private certGroup: string | null = null addClientCertificatesForUrl (cert: UrlClientCertificates) { debug( @@ -140,6 +172,16 @@ export class ClientCertificateStore { cert.matchRule = UrlMatcher.buildMatcherRule(cert.url) this._urlClientCertificates.push(cert) + + this.clearCertGroup() + } + + selectCertGroup (group: string | null) { + this.certGroup = group + } + + clearCertGroup () { + this.certGroup = null } getClientCertificateAgentOptionsForUrl (requestUrl: Url): ClientCertificates | null { @@ -150,6 +192,7 @@ export class ClientCertificateStore { return null } + const certGroup = this.certGroup || 'default' const port = !requestUrl.port ? undefined : parseInt(requestUrl.port) const matchingCerts = this._urlClientCertificates.filter((cert) => { return UrlMatcher.matchUrl(requestUrl.hostname, requestUrl.path, port, cert.matchRule) @@ -165,7 +208,7 @@ export class ClientCertificateStore { `using client certificate(s) '${matchingCerts[0].subjects}' for url '${requestUrl.href}'`, ) - return matchingCerts[0].clientCertificates + return matchingCerts[0].getCertificates(certGroup) default: matchingCerts.sort((a, b) => { return b.pathnameLength - a.pathnameLength @@ -175,7 +218,7 @@ export class ClientCertificateStore { `using client certificate(s) '${matchingCerts[0].subjects}' for url '${requestUrl.href}'`, ) - return matchingCerts[0].clientCertificates + return matchingCerts[0].getCertificates(certGroup) } } @@ -185,9 +228,18 @@ export class ClientCertificateStore { clear (): void { this._urlClientCertificates = [] + this.clearCertGroup() } } +export function chooseClientCertificateGroup (group: string | null) { + clientCertificateStore.selectCertGroup(group) +} + +export function clearClientCertificateGroup () { + clientCertificateStore.clearCertGroup() +} + /** * Load and parse the client certificate configuration. The structure and content of this * has already been validated; this function reads cert content from file and adds it to the @@ -222,7 +274,7 @@ export function loadClientCertificateConfig (config) { throw new Error(`Cannot parse CA cert: ${error.message}`) } - urlClientCertificates.clientCertificates.ca.push(caRaw) + urlClientCertificates.addCA(caRaw) } }) } @@ -232,6 +284,8 @@ export function loadClientCertificateConfig (config) { } item.certs.forEach((cert) => { + const certGroup = cert.group ?? 'default' + if (!cert || (!cert.cert && !cert.pfx)) { throw new Error('Either PEM or PFX must be supplied') } @@ -255,7 +309,8 @@ export function loadClientCertificateConfig (config) { throw new Error(`Cannot parse PEM cert: ${error.message}`) } - urlClientCertificates.clientCertificates.cert.push(pemRaw) + urlClientCertificates.addCertGroup(certGroup) + urlClientCertificates.clientCertificates[certGroup].cert.push(pemRaw) let passphrase: string | undefined = undefined @@ -283,15 +338,17 @@ export function loadClientCertificateConfig (config) { throw new Error(`Cannot parse PEM key: ${error.message}`) } - urlClientCertificates.clientCertificates.key.push( + urlClientCertificates.clientCertificates[certGroup].key.push( new PemKey(pemKeyRaw, passphrase), ) const subject = extractSubjectFromPem(pemParsed) urlClientCertificates.addSubject(subject) + debug( `loaded client PEM certificate: ${subject} for url: ${urlClientCertificates.url}`, + certGroup !== 'default' ? `(group: ${certGroup})` : '', ) } @@ -311,15 +368,18 @@ export function loadClientCertificateConfig (config) { const pfxRaw = loadBinaryFromFile(cert.pfx) const pfxParsed = loadPfx(pfxRaw, passphrase) - urlClientCertificates.clientCertificates.pfx.push( + urlClientCertificates.addCertGroup(certGroup) + urlClientCertificates.clientCertificates[certGroup].pfx.push( new PfxCertificate(pfxRaw, passphrase), ) const subject = extractSubjectFromPfx(pfxParsed) urlClientCertificates.addSubject(subject) + debug( `loaded client PFX certificate: ${subject} for url: ${urlClientCertificates.url}`, + certGroup !== 'default' ? `(group: ${certGroup})` : '', ) } }) diff --git a/packages/network/test/unit/agent_spec.ts b/packages/network/test/unit/agent_spec.ts index 2f39e67cc65d..238b11a28dbf 100644 --- a/packages/network/test/unit/agent_spec.ts +++ b/packages/network/test/unit/agent_spec.ts @@ -420,6 +420,7 @@ describe('lib/agent', function () { }) it('#createUpstreamProxyConnection calls to super for caching, TLS-ifying', function () { + // @ts-ignore const spy = sinon.spy(https.Agent.prototype, 'createConnection') const proxy = new DebuggingProxy() @@ -733,9 +734,9 @@ describe('lib/agent', function () { this.clientCert = pemCert const testCerts = new UrlClientCertificates(`https://localhost`) - testCerts.clientCertificates = new ClientCertificates() - testCerts.clientCertificates.cert.push(Buffer.from(pemCert, 'utf-8')) - testCerts.clientCertificates.key.push(new PemKey(Buffer.from(pki.privateKeyToPem(certAndKey[1]), 'utf-8'), undefined)) + testCerts.clientCertificates.default = new ClientCertificates() + testCerts.clientCertificates.default.cert.push(Buffer.from(pemCert, 'utf-8')) + testCerts.clientCertificates.default.key.push(new PemKey(Buffer.from(pki.privateKeyToPem(certAndKey[1]), 'utf-8'), undefined)) clientCertificateStore.addClientCertificatesForUrl(testCerts) } @@ -834,7 +835,7 @@ describe('lib/agent', function () { context('.buildConnectReqHead', function () { it('builds the correct request', function () { - const head = buildConnectReqHead('foo.bar', '1234', {}) + const head = buildConnectReqHead('foo.bar', '1234') expect(head).to.eq([ 'CONNECT foo.bar:1234 HTTP/1.1', @@ -844,9 +845,7 @@ describe('lib/agent', function () { }) it('can do Proxy-Authorization', function () { - const head = buildConnectReqHead('foo.bar', '1234', { - auth: 'baz:quux', - }) + const head = buildConnectReqHead('foo.bar', '1234', 'baz:quux') expect(head).to.eq([ 'CONNECT foo.bar:1234 HTTP/1.1', @@ -902,6 +901,7 @@ describe('lib/agent', function () { }, ].map((testCase) => { it(`detects correctly from ${testCase.protocol} requests`, () => { + // @ts-ignore const spy = sinon.spy(testCase.agent, 'addRequest') return request({ @@ -919,6 +919,7 @@ describe('lib/agent', function () { }) it(`detects correctly from ${testCase.protocol} websocket requests`, () => { + // @ts-ignore const spy = sinon.spy(testCase.agent, 'addRequest') const socket = socketIo.client(`${testCase.protocol}://foo.bar.baz.invalid`, { agent: testCase.agent, @@ -958,6 +959,7 @@ describe('lib/agent', function () { context('.regenerateRequestHead', function () { it('regenerates changed request head', () => { + // @ts-ignore const spy = sinon.spy(http.globalAgent, 'createSocket') return request({ diff --git a/packages/network/test/unit/client_certificates_spec.ts b/packages/network/test/unit/client_certificates_spec.ts index 2d0fd6e7a8d7..8d1b18c91ad1 100644 --- a/packages/network/test/unit/client_certificates_spec.ts +++ b/packages/network/test/unit/client_certificates_spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai' -import { ParsedUrl, UrlMatcher, UrlClientCertificates, ClientCertificateStore, ClientCertificates, loadClientCertificateConfig } from '../../lib/client-certificates' +import { ParsedUrl, UrlMatcher, UrlClientCertificates, ClientCertificateStore, loadClientCertificateConfig } from '../../lib/client-certificates' import { clientCertificateStore } from '../../lib/agent' import urllib from 'url' import fs from 'fs-extra' @@ -23,7 +23,7 @@ function urlShouldNotMatch (url: string, matcher: string) { expect(UrlMatcher.matchUrl(parsedUrl.host, parsedUrl.path, parsedUrl.port, rule), `'${url}' should not match '${matcher}' (rule: ${JSON.stringify(rule)})`).to.be.false } -function checkParsed (parsed: ParsedUrl, host: string, path: string | undefined, port: number | undefined) { +function checkParsed (parsed: ParsedUrl, host: string, path?: string, port?: number) { expect(parsed.host, `'host ${parsed.host}' should be '${host}'`).to.eq(host) expect(parsed.path, `'path ${parsed.path}' should be '${path}'`).to.eq(path) expect(parsed.port, `'port ${parsed.port}' should be '${port}'`).to.eq(port) @@ -34,7 +34,7 @@ describe('lib/client-certificates', () => { it('parses clean URLs', () => { let parsed = new ParsedUrl('https://a.host.com') - checkParsed(parsed, 'a.host.com', undefined, undefined) + checkParsed(parsed, 'a.host.com') parsed = new ParsedUrl('https://a.host.com:1234') expect(parsed.host).to.eq('a.host.com') @@ -137,13 +137,11 @@ describe('lib/client-certificates', () => { const certs1 = new UrlClientCertificates(url1.href) - certs1.clientCertificates = new ClientCertificates() - certs1.clientCertificates.ca.push(Buffer.from([1, 2, 3, 4])) + certs1.addCA(Buffer.from([1, 2, 3, 4])) const certs2 = new UrlClientCertificates(url2.href) - certs2.clientCertificates = new ClientCertificates() - certs2.clientCertificates.ca.push(Buffer.from([4, 3, 2, 1])) + certs2.addCA(Buffer.from([4, 3, 2, 1])) store.addClientCertificatesForUrl(certs1) expect(store.getCertCount()).to.eq(1) @@ -160,8 +158,8 @@ describe('lib/client-certificates', () => { const options1 = store.getClientCertificateAgentOptionsForUrl(url1) const options2 = store.getClientCertificateAgentOptionsForUrl(url2) - expect(options1.ca).to.eq(certs1.clientCertificates.ca) - expect(options2.ca).to.eq(certs2.clientCertificates.ca) + expect(options1?.ca).to.eq(certs1.getCA()) + expect(options2?.ca).to.eq(certs2.getCA()) }) }) }) @@ -189,7 +187,7 @@ describe('lib/client-certificates', () => { // // Neither PEM nor PFX supplied -function createCertAndKey (): [object, object] { +function createCertAndKey (): [Forge.pki.Certificate, Forge.pki.rsa.PrivateKey] { let keys = pki.rsa.generateKeyPair(2048) let cert = pki.createCertificate() @@ -236,8 +234,8 @@ function createCertAndKey (): [object, object] { function createPemFiles ( certFilepath: string, keyFilepath: string, - passphraseFilepath: string | undefined, - passphrase: string | undefined, + passphraseFilepath?: string, + passphrase?: string, ) { const certInfo = createCertAndKey() @@ -248,23 +246,23 @@ function createPemFiles ( fs.writeFileSync(keyFilepath, key) - if (passphraseFilepath) { + if (passphraseFilepath && passphrase) { fs.writeFileSync(passphraseFilepath, passphrase) } } function createPfxFiles ( certFilepath: string, - passphraseFilepath: string | undefined, - passphrase: string | undefined, + passphraseFilepath?: string, + passphrase?: string, ) { const certInfo = createCertAndKey() - let p12Asn1 = pkcs12.toPkcs12Asn1(certInfo[1], [certInfo[0]], passphrase) + let p12Asn1 = pkcs12.toPkcs12Asn1(certInfo[1], [certInfo[0]], passphrase ?? null) fs.writeFileSync(certFilepath, asn1.toDer(p12Asn1).getBytes(), { encoding: 'binary' }) - if (passphraseFilepath) { + if (passphraseFilepath && passphrase) { fs.writeFileSync(passphraseFilepath, passphrase) } } @@ -356,7 +354,7 @@ describe('lib/client-certificates', () => { context('loads cert files', () => { it('loads valid single PEM (no passphrase) and CA via absolute pathing', () => { - createPemFiles(pemFilepath, pemKeyFilepath, undefined, undefined) + createPemFiles(pemFilepath, pemKeyFilepath) createCaFile(caFilepath) const url = createUniqueUrl() @@ -377,14 +375,14 @@ describe('lib/client-certificates', () => { ) expect(options).not.to.be.null - expect(options.ca.length).to.eq(1) - expect(options.ca[0]).to.deep.equal(caFileData) - expect(options.pfx).to.be.empty - expect(options.cert.length).to.eq(1) - expect(options.cert[0]).to.deep.equal(pemFileData) - expect(options.key.length).to.eq(1) - expect(options.key[0].passphrase).to.be.undefined - expect(options.key[0].pem).to.deep.equal(keyFileData) + expect(options?.ca.length).to.eq(1) + expect(options?.ca[0]).to.deep.equal(caFileData) + expect(options?.pfx).to.be.empty + expect(options?.cert.length).to.eq(1) + expect(options?.cert[0]).to.deep.equal(pemFileData) + expect(options?.key.length).to.eq(1) + expect(options?.key[0].passphrase).to.be.undefined + expect(options?.key[0].pem).to.deep.equal(keyFileData) }) it('loads valid multiple PEMs (no passphrase) and CAs', () => { @@ -398,9 +396,9 @@ describe('lib/client-certificates', () => { const keyFilepath3 = path.join(tempDirPath, 'testpem3.key') const caFilepath3 = path.join(tempDirPath, 'testca3.crt') - createPemFiles(pemFilepath1, keyFilepath1, undefined, undefined) - createPemFiles(pemFilepath2, keyFilepath2, undefined, undefined) - createPemFiles(pemFilepath3, keyFilepath3, undefined, undefined) + createPemFiles(pemFilepath1, keyFilepath1) + createPemFiles(pemFilepath2, keyFilepath2) + createPemFiles(pemFilepath3, keyFilepath3) createCaFile(caFilepath1) createCaFile(caFilepath2) createCaFile(caFilepath3) @@ -446,20 +444,20 @@ describe('lib/client-certificates', () => { ) expect(options).not.to.be.null - expect(options.ca.length).to.eq(3) - expect(options.ca[0]).to.deep.equal(caFileData1) - expect(options.ca[1]).to.deep.equal(caFileData2) - expect(options.ca[2]).to.deep.equal(caFileData3) - expect(options.pfx).to.be.empty - expect(options.cert.length).to.eq(3) - expect(options.cert[0]).to.deep.equal(pemFileData1) - expect(options.cert[1]).to.deep.equal(pemFileData2) - expect(options.cert[2]).to.deep.equal(pemFileData3) - expect(options.key.length).to.eq(3) - expect(options.key[0].passphrase).to.be.undefined - expect(options.key[0].pem).to.deep.equal(keyFileData1) - expect(options.key[1].pem).to.deep.equal(keyFileData2) - expect(options.key[2].pem).to.deep.equal(keyFileData3) + expect(options?.ca.length).to.eq(3) + expect(options?.ca[0]).to.deep.equal(caFileData1) + expect(options?.ca[1]).to.deep.equal(caFileData2) + expect(options?.ca[2]).to.deep.equal(caFileData3) + expect(options?.pfx).to.be.empty + expect(options?.cert.length).to.eq(3) + expect(options?.cert[0]).to.deep.equal(pemFileData1) + expect(options?.cert[1]).to.deep.equal(pemFileData2) + expect(options?.cert[2]).to.deep.equal(pemFileData3) + expect(options?.key.length).to.eq(3) + expect(options?.key[0].passphrase).to.be.undefined + expect(options?.key[0].pem).to.deep.equal(keyFileData1) + expect(options?.key[1].pem).to.deep.equal(keyFileData2) + expect(options?.key[2].pem).to.deep.equal(keyFileData3) }) it('loads valid single PEM (with passphrase)', () => { @@ -489,17 +487,17 @@ describe('lib/client-certificates', () => { ) expect(options).not.to.be.null - expect(options.ca.length).to.eq(0) - expect(options.pfx).to.be.empty - expect(options.cert.length).to.eq(1) - expect(options.cert[0]).to.deep.equal(pemFileData) - expect(options.key.length).to.eq(1) - expect(options.key[0].passphrase).to.equal(passphrase) - expect(options.key[0].pem).to.deep.equal(keyFileData) + expect(options?.ca.length).to.eq(0) + expect(options?.pfx).to.be.empty + expect(options?.cert.length).to.eq(1) + expect(options?.cert[0]).to.deep.equal(pemFileData) + expect(options?.key.length).to.eq(1) + expect(options?.key[0].passphrase).to.equal(passphrase) + expect(options?.key[0].pem).to.deep.equal(keyFileData) }) it('loads valid single PEM and CA via relative pathing', () => { - createPemFiles(pemFilepath, pemKeyFilepath, undefined, undefined) + createPemFiles(pemFilepath, pemKeyFilepath) createCaFile(caFilepath) const relativeCaFilepath = path.relative(__dirname, caFilepath) @@ -521,11 +519,11 @@ describe('lib/client-certificates', () => { ) expect(options).not.to.be.null - expect(options.ca.length).to.eq(1) - expect(options.pfx).to.be.empty - expect(options.cert.length).to.eq(1) - expect(options.key.length).to.eq(1) - expect(options.key[0].passphrase).to.be.undefined + expect(options?.ca.length).to.eq(1) + expect(options?.pfx).to.be.empty + expect(options?.cert.length).to.eq(1) + expect(options?.key.length).to.eq(1) + expect(options?.key[0].passphrase).to.be.undefined }) // TODO: fix this flaky test @@ -559,7 +557,7 @@ describe('lib/client-certificates', () => { }) it('detects invalid PEM key file (no passphrase)', () => { - createPemFiles(pemFilepath, pemKeyFilepath, undefined, undefined) + createPemFiles(pemFilepath, pemKeyFilepath) fs.writeFileSync(pemKeyFilepath, 'not-a-key') const url = createUniqueUrl() @@ -762,10 +760,10 @@ describe('lib/client-certificates', () => { ) expect(options).not.to.be.null - expect(options.cert).to.be.empty - expect(options.pfx.length).to.eq(1) - expect(options.pfx[0].buf).to.deep.equal(pfxFileData) - expect(options.pfx[0].passphrase).to.equal(passphrase) + expect(options?.cert).to.be.empty + expect(options?.pfx.length).to.eq(1) + expect(options?.pfx[0].buf).to.deep.equal(pfxFileData) + expect(options?.pfx[0].passphrase).to.equal(passphrase) }) it('detects invalid PFX passphrase', () => { @@ -846,8 +844,7 @@ describe('lib/client-certificates', () => { { url: createUniqueUrl(), ca: [], - certs: [ - ], + certs: [], }, ], } @@ -858,5 +855,85 @@ describe('lib/client-certificates', () => { expect(act).to.throw('Either PEM or PFX must be supplied') }) + + it('loads two PEM certificates with groups (user and admin) for same domain', () => { + const caFilepath = path.join(tempDirPath, 'test2ca.crt') + const group1 = 'user' + const pemFilepath1 = path.join(tempDirPath, 'test2pem1.crt') + const keyFilepath1 = path.join(tempDirPath, 'test2pem1.key') + const group2 = 'admin' + const pemFilepath2 = path.join(tempDirPath, 'test2pem2.crt') + const keyFilepath2 = path.join(tempDirPath, 'test2pem2.key') + + createCaFile(caFilepath) + createPemFiles(pemFilepath1, keyFilepath1, group1) + createPemFiles(pemFilepath2, keyFilepath2, group2) + + const url = createUniqueUrl() + const config = { + projectRoot: __dirname, + clientCertificates: [ + { + url, + ca: [caFilepath], + certs: [ + { + group: group1, + cert: pemFilepath1, + key: keyFilepath1, + }, + { + group: group2, + cert: pemFilepath2, + key: keyFilepath2, + }, + ], + }, + ], + } + + const caFileData = fs.readFileSync(caFilepath) + const pemFileData1 = fs.readFileSync(pemFilepath1) + const keyFileData1 = fs.readFileSync(keyFilepath1) + const pemFileData2 = fs.readFileSync(pemFilepath2) + const keyFileData2 = fs.readFileSync(keyFilepath2) + + clientCertificateStore.clear() + loadClientCertificateConfig(config) + const parsedURL = urllib.parse(url) + let options = clientCertificateStore.getClientCertificateAgentOptionsForUrl(parsedURL) + + expect(options).not.to.be.null + expect(options?.ca.length).to.eq(1) + expect(options?.key).to.be.empty + expect(options?.cert).to.be.empty + expect(options?.pfx).to.be.empty + + clientCertificateStore.selectCertGroup(group1) + options = clientCertificateStore.getClientCertificateAgentOptionsForUrl(parsedURL) + + expect(options).not.to.be.null + expect(options?.ca.length).to.eq(1) + expect(options?.ca[0]).to.deep.equal(caFileData) + expect(options?.pfx).to.be.empty + expect(options?.cert.length).to.eq(1) + expect(options?.cert[0]).to.deep.equal(pemFileData1) + expect(options?.key.length).to.eq(1) + expect(options?.key[0].pem).to.deep.equal(keyFileData1) + + clientCertificateStore.selectCertGroup(group2) + options = clientCertificateStore.getClientCertificateAgentOptionsForUrl(parsedURL) + + expect(options).not.to.be.null + expect(options?.ca.length).to.eq(1) + expect(options?.ca[0]).to.deep.equal(caFileData) + expect(options?.pfx).to.be.empty + expect(options?.cert.length).to.eq(1) + expect(options?.cert[0]).to.deep.equal(pemFileData2) + expect(options?.key.length).to.eq(1) + expect(options?.key[0].pem).to.deep.equal(keyFileData2) + + clientCertificateStore.clearCertGroup() + }) }) }) diff --git a/packages/server/lib/privileged-commands/privileged-channel.js b/packages/server/lib/privileged-commands/privileged-channel.js index 7f4eec37e2e2..44e933586bc3 100644 --- a/packages/server/lib/privileged-commands/privileged-channel.js +++ b/packages/server/lib/privileged-commands/privileged-channel.js @@ -108,6 +108,7 @@ 'selectFile', 'writeFile', 'task', + 'chooseCert', ] const callbackCommands = [ diff --git a/packages/server/lib/privileged-commands/privileged-commands-manager.ts b/packages/server/lib/privileged-commands/privileged-commands-manager.ts index 5f6ddea24352..a3f6fb7d8e9e 100644 --- a/packages/server/lib/privileged-commands/privileged-commands-manager.ts +++ b/packages/server/lib/privileged-commands/privileged-commands-manager.ts @@ -8,6 +8,8 @@ import files from '../files' import { fs } from '../util/fs' import task from '../task' +import { clientCertificates } from '@packages/network' + export interface SpecChannelOptions { isSpecBridge: boolean url: string @@ -112,6 +114,18 @@ class PrivilegedCommandsManager { return task.run(configFile ?? null, options) } + case 'chooseCert': { + // Make the server choose the certificate group + const group = options.group + + if (!group || group === '') { + clientCertificates.clearClientCertificateGroup() + } else { + clientCertificates.chooseClientCertificateGroup(group) + } + + return + } default: throw new Error(`You requested a secure backend event for a command we cannot handle: ${commandName}`) } diff --git a/packages/socket/lib/cdp-browser.ts b/packages/socket/lib/cdp-browser.ts index a5730fd302c1..134262ea86b8 100644 --- a/packages/socket/lib/cdp-browser.ts +++ b/packages/socket/lib/cdp-browser.ts @@ -2,7 +2,6 @@ import Emitter from 'component-emitter' import { v4 as uuidv4 } from 'uuid' import { decode, encode } from './utils' -import type { SocketShape } from './types' type CDPSocketNamespaceKey = `cypressSocket-${string}` type CDPSendToServerNamespaceKey = `cypressSendToServer-${string}` @@ -14,7 +13,7 @@ declare global { } } -export class CDPBrowserSocket extends Emitter implements SocketShape { +export class CDPBrowserSocket extends Emitter { private _namespace: string constructor (namespace: string) { diff --git a/packages/socket/lib/types.ts b/packages/socket/lib/types.ts index d5212cd6b453..9d18f5edfaab 100644 --- a/packages/socket/lib/types.ts +++ b/packages/socket/lib/types.ts @@ -1,3 +1,4 @@ -import type Emitter from 'component-emitter' +import type { Socket } from 'socket.io-client' +import type { CDPBrowserSocket } from './cdp-browser' -export type SocketShape = Emitter +export type SocketShape = Socket | CDPBrowserSocket diff --git a/system-tests/projects/multiple-certificates-chooseCert/certs/admin.crt b/system-tests/projects/multiple-certificates-chooseCert/certs/admin.crt new file mode 100644 index 000000000000..72d439837f48 --- /dev/null +++ b/system-tests/projects/multiple-certificates-chooseCert/certs/admin.crt @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICpjCCAY4CFA2mErrXtki8MGjNJco10dVN+cRGMA0GCSqGSIb3DQEBCwUAMA8x +DTALBgNVBAMMBE15Q0EwHhcNMjQwOTA1MjIyMTA0WhcNMjUwOTA1MjIyMTA0WjAQ +MQ4wDAYDVQQDDAVhZG1pbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ALr8m1euQOplZpaZGRVgVxcOBZEVyeyDtZ8Jy0UmCj6mb5p5C8M+0IFrGq9ELqPD +M3wBjsdfoXynMjRTFtL32JA9fKsVTOthdwkm6M/jcQvXzii/bfaAae3zD8hPSchs +edLzylZ7ZYSqxrCcHmLQzAgPdG8YytOM8TtzaKGwhsPu7FZH9SA9YUGodvprDTkx +FLyHxkRzKQek/XynBi+3la3sWxbXr1PNTlZGSiKDgRgdzb8BwNd2+XKcGOg3tlYU +PVi0jklLkRLKLUubMTokoMuOvi/ASFIS/xYVQDvGfHP2yv7Frf0bBX8pRmv5qVON +pmiU1de7WReTiRQ3XBT6w5UCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAbHzm4dMO ++f5uU1Mea/1KZvAr4SYIOdhrMld6UnKlUGo1bEvsFM4EHzZtUstb/Ao07xVlzP8X +x4Qg47uwdJN/UcyVUTM4qbnynLxXtboAkrnOuxMq6RGGNKNAK+eI67lTxrjpgyz5 +uNoc5ayzJExagy/9PXW9Hii5KY3+peEVJUbzhUb4DlT6zq9UCDsPeIl3b2N0NFTg +YhUmpvP/y1mhmLjH7yyWjrx7OMskYPPbuCp51fbv2N7E31Oqr2DLnDajx7Qd+cmq +p/XiHSLsRldbBbLT2n6dNyLTfp6TC4+tH/CDMonrLS6j8osFBddH8WmIhWWIdioF +XrTLOVaX94hO7g== +-----END CERTIFICATE----- diff --git a/system-tests/projects/multiple-certificates-chooseCert/certs/admin.csr b/system-tests/projects/multiple-certificates-chooseCert/certs/admin.csr new file mode 100644 index 000000000000..6e1d0eb006d2 --- /dev/null +++ b/system-tests/projects/multiple-certificates-chooseCert/certs/admin.csr @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICVTCCAT0CAQAwEDEOMAwGA1UEAwwFYWRtaW4wggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQC6/JtXrkDqZWaWmRkVYFcXDgWRFcnsg7WfCctFJgo+pm+a +eQvDPtCBaxqvRC6jwzN8AY7HX6F8pzI0UxbS99iQPXyrFUzrYXcJJujP43EL184o +v232gGnt8w/IT0nIbHnS88pWe2WEqsawnB5i0MwID3RvGMrTjPE7c2ihsIbD7uxW +R/UgPWFBqHb6aw05MRS8h8ZEcykHpP18pwYvt5Wt7FsW169TzU5WRkoig4EYHc2/ +AcDXdvlynBjoN7ZWFD1YtI5JS5ESyi1LmzE6JKDLjr4vwEhSEv8WFUA7xnxz9sr+ +xa39GwV/KUZr+alTjaZolNXXu1kXk4kUN1wU+sOVAgMBAAGgADANBgkqhkiG9w0B +AQsFAAOCAQEAK15zpAtFvJwHEFj4O9KlMW0PQUae1zKJ4UB9OwLXr+gvHtbuOznh +Csr+Xege82oXoPDwtNCdAKj6Z4Efn8aAyaiXWzlhuoL5QLKSSGtyzWIWcKm0V7Hh +rWRmYpj+hQcIWaY7+jhaWBo6agK0kHMmIGtDVj3bkpS8bauW/1LKMTDnS8WpGZ88 +Ye4h4jpBOJRPPC7qhltZsRsMCq6nM3SVnr0rmEl1Idrvd5HoEdRIABGVtH2ewmD/ +V/lDdxJ0BNTxaC31vRt7vQmvLKTnk7Jsj0rg/yX2jgaDUhyoG2uAn57glFdML1Ei +SwDuCX81HgWfiG6YO5juCeBlPyfz9x/rqg== +-----END CERTIFICATE REQUEST----- diff --git a/system-tests/projects/multiple-certificates-chooseCert/certs/admin.key b/system-tests/projects/multiple-certificates-chooseCert/certs/admin.key new file mode 100644 index 000000000000..8cf947d1b6b4 --- /dev/null +++ b/system-tests/projects/multiple-certificates-chooseCert/certs/admin.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAuvybV65A6mVmlpkZFWBXFw4FkRXJ7IO1nwnLRSYKPqZvmnkL +wz7QgWsar0Quo8MzfAGOx1+hfKcyNFMW0vfYkD18qxVM62F3CSboz+NxC9fOKL9t +9oBp7fMPyE9JyGx50vPKVntlhKrGsJweYtDMCA90bxjK04zxO3NoobCGw+7sVkf1 +ID1hQah2+msNOTEUvIfGRHMpB6T9fKcGL7eVrexbFtevU81OVkZKIoOBGB3NvwHA +13b5cpwY6De2VhQ9WLSOSUuREsotS5sxOiSgy46+L8BIUhL/FhVAO8Z8c/bK/sWt +/RsFfylGa/mpU42maJTV17tZF5OJFDdcFPrDlQIDAQABAoIBAAn4xLQFV/LoG3N2 +kK7iJ2LUXZmiJtWVJI9pr0xtvegfah47zFPKOsZcFt037qDPHSTE1H9vayZBqFy/ +/ZycmREAJnDdPu/ij72ONKY5g+LUzGrB4rRdnsiQyMuu5slod4q3c5fvFrfff58V +hE1Swz6+zD/Me6ySfJ7mBbNxvY4fTWYhQ365sDVAZd83PB+VH8DQsvyp3TFvHICJ +oAJ48acM9gt9E3IlUKqjlIX2Zj7vxrxdpbA9zO6ICZFajBzLGELm8kd+/VBAJ+gL +1nRhGTqYe6p7Crc1XhDr+a4jZor+SjVy7R7EZluETFfIpxhAA6x1EJ9yppUtcZ/b +L9jxy+ECgYEA3yPDTTHVkIn3iyT2vCX70HgluoRvTco70C/RAj6hmpWHT8JyX41y +923i8zdF9cZBs0SxY4xUuTklZkDkV7Id0UJoTLXDE6OSwGcEX/J7InsfOSQBREpI +Y3nqnu/HrY7rEh2y/D+JVDxz7OFka97JbiW38McoHmlfXDgZOPrL7T0CgYEA1oXm +B4UirK8mZrK6K4MvdJ1rN2EGIzhoYDudD9+6x09JDoUF8rv4INq+9dODjbZajXdI +YGDHfAsZqpFXzbti+JN2bvUUQplfaDC2RvkG6aTpPOZWjOljPEs5mgl7Dy1acszr +idfTreW1lG0jRp6Ccvib6xtSjKgyEMi4kIY/xTkCgYEAzyr28j6oV0+gonEpGrnx +NzW1YZv/cqumKL2cQYdAprLZM/YGFizPZmWBZeoVbAArk3Lddt0c8/LYhD0PQ1Q9 +VsFe8Il4mk0tvLBoYWRRakwBSx3ghzSoTKMub22TZ9He8azdNlEPAUQ3JOCGSZhI +T5l5U3S0DLgsLwRudnZyyskCgYBY23men8zvDlWvsFRg9aPQf+wLMksih5jkU40J +KDjrmbG/K+Z81tM+eJ0kogoZxvDXLBN0qGG4xbhJ6I4gD4A9SoeCCk3sEC9urs6x +szDj0oUxzo8HKx0JuncPkOHCNZ0DdFIT9KwfmciOxG4aMEoEaWncBqM40+p4/UtF +wfXkMQKBgHj/LIDyWuM8qTODE4r4yOgqK48NpOLOCU15VzABIx1HaCdVkqlHCP9X +bjob/WtkmWLng036opSZcafHjofTfC/TRAot9UheAQFacx5uls7nt47AwBCMiYuv +CE6nfaCvDEnM118fBqpHlX4QzmGG7c7Ha3Ftcz7L0Fj9XqmwZaHf +-----END RSA PRIVATE KEY----- diff --git a/system-tests/projects/multiple-certificates-chooseCert/certs/ca.crt b/system-tests/projects/multiple-certificates-chooseCert/certs/ca.crt new file mode 100644 index 000000000000..b30e99477097 --- /dev/null +++ b/system-tests/projects/multiple-certificates-chooseCert/certs/ca.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIC/zCCAeegAwIBAgIUUFwjGjxGhoi0OmTJX7v9xU4d/6QwDQYJKoZIhvcNAQEL +BQAwDzENMAsGA1UEAwwETXlDQTAeFw0yNDA5MDUyMjIwMzhaFw0yNDEwMDUyMjIw +MzhaMA8xDTALBgNVBAMMBE15Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC89WqKTIq0wVIW3TAdRTeKozBpdP9Cm7FLSDKQ1WBSuo2tqg34jMrf1pSO +Xl/uMBNFMnImsGQPkIRoGOjzOd+rg63xWB5FHpO4ntHq2Iy1b2tdxA+MicDA3Gcc +dWdowpqBM/v/3gjp2Ip/ykAFDQicpYRft+ZEUQvvuKZnhQZY5KoKa4PfII8vz6vk +qgIlzigyDZ1j8Kbr4CbgZ9/gx0XfY7o2raZHmSnMziSplIzM0igrhJFUKHpst+eY +t/2m6Nq0OyEL800HZYRqgI+AMq5q0PDxd0rMxWoy9IhBF6/ih5eACttduT7NFzD/ +oXBtWqPvDTiGupIE3B4kaVyyPo6nAgMBAAGjUzBRMB0GA1UdDgQWBBRVlYDOw/3L +T74S70Mpg9T3ecQtTjAfBgNVHSMEGDAWgBRVlYDOw/3LT74S70Mpg9T3ecQtTjAP +BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAWXGH+ufr9PlaI58bK +CrKpACTtwBYFxyLKJs5+G9bPfh+5/22wDxnkOif6rpEeUmTLv7Th1Ys48SNcn4ux +c12GxNSbPNsI43lM6yakS815mpM8dhDyEgFhkrHJxvLn8UZ/BQXYzr5NlR7Ip8YR +CbYVgEZ3rLEbw422Lmd/nkB8xY/EXMFMvjkRaX6Kw1iYqSl3bU3292ekOfPcSvHY +MIXKAlO0JfulVaFcpjDK20vg4+l4zD0kXDZ8xTXGmSbJENhueo3qUfPmNYG4QBQJ +78r8NAdV0Okbjk5ZM7awY21WLeRfCkrvNGhs9GGcT8HY3pEv+mLKPwM+EbBThXU9 +3tyW +-----END CERTIFICATE----- diff --git a/system-tests/projects/multiple-certificates-chooseCert/certs/ca.key b/system-tests/projects/multiple-certificates-chooseCert/certs/ca.key new file mode 100644 index 000000000000..68eee661d857 --- /dev/null +++ b/system-tests/projects/multiple-certificates-chooseCert/certs/ca.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAvPVqikyKtMFSFt0wHUU3iqMwaXT/QpuxS0gykNVgUrqNraoN ++IzK39aUjl5f7jATRTJyJrBkD5CEaBjo8znfq4Ot8VgeRR6TuJ7R6tiMtW9rXcQP +jInAwNxnHHVnaMKagTP7/94I6diKf8pABQ0InKWEX7fmRFEL77imZ4UGWOSqCmuD +3yCPL8+r5KoCJc4oMg2dY/Cm6+Am4Gff4MdF32O6Nq2mR5kpzM4kqZSMzNIoK4SR +VCh6bLfnmLf9pujatDshC/NNB2WEaoCPgDKuatDw8XdKzMVqMvSIQRev4oeXgArb +Xbk+zRcw/6FwbVqj7w04hrqSBNweJGlcsj6OpwIDAQABAoIBAGy0KpRhMDKIPHdW +y8Uq+qbQ9prUabyh4L7US/KyLfWpyLOtbtgb9uIKie/VLw7RrWRTfwPu2cFyfBl+ +qxSxqJD88eYzsUZGLuk4xL570YbXEEci8WcCIFdS9CaVC+ZifYvyofgyfaI5poOZ +kHbXZqrnNV3zZ47JqTpBWcmX52zfMK3mzysTOaruFxLwDfGPuJ4wFHpiqKTACUDd +ReSZEuH0aX/e8c+GpAb2NrhC17l2up/OLkrKS7j6ues7YdiVD3G2+WjPZoWRpDkE +vhdbmtHWws6zmPBjpKDTt0Dp+Xq2gghLUhGLM+S+EDD+ZXZg+ClzMVIBjdvvIQoS +u+7vJOECgYEA9bzlZQJdPDC6GqPUwTwiJy7NAWgjOH4A0+gvnRz+SvEBmtsVB/lN +b3drSKSmjeMeKJAwvtc7s4gO7Cw8wQQG93el+qELVzsV19RE7gTFnSGqktA4nCbk +E1K2ti1IfoajGf5SUcBjbiPgTh1EFzps9htsOxc3UTVx79fHD03Cnj8CgYEAxNmD +C07EFGQHAmjw836pju0cqHXr24lQM9nusRrJZ5s8k7X7m0K8a1OlcftiwhzC8xv+ +gseA1pHU3f7r1h6lX8nvliEv94CrI1rJMldb8RPL0wEqbEuYYTUPm1FcJwUPmi7A +Fe7Hz5okBF2kO9/CrjQ+HvvtBDuFFxUpFef5RZkCgYA8dl7GzJ8eGSshNOA7L02D +8Ir2ZqYL10zi1sUoViLnU4c+j9sL3DZGT86wz/o+/PvFpTypKBIDaV2qlHPaVeBU +8TaAhTFiJCKKuSrAmaH5uHfcqajUGumRK4/TIS8uGnh1A9NDVB/i0Kb28VJ4mKKI +p1So8Vxl+c1v4tqSlGJnRwKBgQCs/R09Pa3w4WqLgp9kzEXDtbn+jQqFdilGN9mc +SBcAdpqIkF+ZNM+SXgLBpRJH1bC4pjxGv34IkkWIBXNJSss336UtwOJ/VdKPkkPH +ep/wvt3bA2Y9nz09B708WAZsv/TTIQxq+dkcJrBKKeSfPWe/YWfKIMhMdOXjwBPI +DYCw2QKBgQCs0pAtnOGVGBa63JDGnjbpxtUCgfWQol35ZDvp8Dg4thX37VucjY0h +0ceY5UAhV58HRsmOoaaOQLTvS8eac5Iwa8wZY9wfxnQlcZqI7k/KgwDnjc/RKEiH +WxuDVQZ02azfZlQPOXetAp8Q+Oz+IgRkWihjSt7XrOBpIp6XOd38Fg== +-----END RSA PRIVATE KEY----- diff --git a/system-tests/projects/multiple-certificates-chooseCert/certs/ca.srl b/system-tests/projects/multiple-certificates-chooseCert/certs/ca.srl new file mode 100644 index 000000000000..e1999710215b --- /dev/null +++ b/system-tests/projects/multiple-certificates-chooseCert/certs/ca.srl @@ -0,0 +1 @@ +0DA612BAD7B648BC3068CD25CA35D1D54DF9C447 diff --git a/system-tests/projects/multiple-certificates-chooseCert/certs/server.crt b/system-tests/projects/multiple-certificates-chooseCert/certs/server.crt new file mode 100644 index 000000000000..c209c710ea1a --- /dev/null +++ b/system-tests/projects/multiple-certificates-chooseCert/certs/server.crt @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICqjCCAZICFA2mErrXtki8MGjNJco10dVN+cRFMA0GCSqGSIb3DQEBCwUAMA8x +DTALBgNVBAMMBE15Q0EwHhcNMjQwOTA1MjIyMDU0WhcNMjUwOTA1MjIyMDU0WjAU +MRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQDCxvCbfXXq16pqBLEzhuHsstS8w/5hjxiDJUoZKJPcFIDM4HLcZc3psX2g +BNypHjCP1dUtCfqhV6uagn7eeA/iByk5jT9o4UUKCOMqNZ7oPRHIZX6B5PSrb8N9 +MljT3NMMuIbUs8UQpTEidDn7jsjAu8lgpNDavV7wxMtYqTGMn++g21vbpchDAJ3q +F3pBvepuf49Ii8dZe1DxFo+CsT5sSq7LMFa3US7H8/cVNq8Fb7IHsbezdDja679Z +QEHjMQXSqoyDp5lHyy63ty0Xj/WKl4Pqr9//ySQJmXX/SYn93r6AJzYmdD64E4Pc +tlm/ZCDf81dDOTsI2YM1T4quVBvlAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAAXA +anlmS3q9PzWwyzuNlSksujWZ2gHpNZsi9pVMRCn9rgs/fzotKAmoZjShxNSrt+kX +XBY/ixfFyGW+2NeXu+828iJKnAC/NBF7BDQvcflju40qO3JV91GTqN2aAU+rlr6e +0oi8/0sznU4dh19lQl7lTNsObnfHPQai7d5CyWCFX+EaUBhHc25TSK43A5hDTz6p +rkOQE+/XyQyrrY1zk9xX4pPgygGXV/DdMhDrC7oQaHxtD5zHR+maygtGTnm7vq45 +7VL5WLT83PVvVZJoBagFibKSB+eu2wmtue7cqG1UCw/1hZnV0E1z0bp0eUheZXz1 +iu7pQ6ojBhZ+W6cX4UA= +-----END CERTIFICATE----- diff --git a/system-tests/projects/multiple-certificates-chooseCert/certs/server.csr b/system-tests/projects/multiple-certificates-chooseCert/certs/server.csr new file mode 100644 index 000000000000..c1a43351321b --- /dev/null +++ b/system-tests/projects/multiple-certificates-chooseCert/certs/server.csr @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICWTCCAUECAQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAwsbwm3116teqagSxM4bh7LLUvMP+YY8YgyVKGSiT +3BSAzOBy3GXN6bF9oATcqR4wj9XVLQn6oVermoJ+3ngP4gcpOY0/aOFFCgjjKjWe +6D0RyGV+geT0q2/DfTJY09zTDLiG1LPFEKUxInQ5+47IwLvJYKTQ2r1e8MTLWKkx +jJ/voNtb26XIQwCd6hd6Qb3qbn+PSIvHWXtQ8RaPgrE+bEquyzBWt1Eux/P3FTav +BW+yB7G3s3Q42uu/WUBB4zEF0qqMg6eZR8sut7ctF4/1ipeD6q/f/8kkCZl1/0mJ +/d6+gCc2JnQ+uBOD3LZZv2Qg3/NXQzk7CNmDNU+KrlQb5QIDAQABoAAwDQYJKoZI +hvcNAQELBQADggEBAL91KSuRgwJSMpcqxbS7ovnXmivfa4YYKwf2L4hMhvRkAGVs +CtibPX/iWQX/AUfl8dVyI1vyhXkJXHqFINr+O3edzNEdWBlCR6aZG4IdC0lXMsZo +apSzp6OHMuTiLNW5wNjvbbC3C7WpDr3zGgmdSN6Vbxo1uJvHALBGbmq7M1D25qQl +gucpI6YJ74sDmRGTrHfhRSUuVEowvMm6laKhZd8AyZf81UINJ2K4DBg20Rkn9Fz5 +Iv2cehBkpHgJdufCuNF39oE7/x3V0/1vBa9GAMHIZ31AZ6Qk16AVs8LQaxcBtE9y +yXc3VCAcLzgE4joKDjE9Lm1SQKXUeMCuF/iZfsE= +-----END CERTIFICATE REQUEST----- diff --git a/system-tests/projects/multiple-certificates-chooseCert/certs/server.key b/system-tests/projects/multiple-certificates-chooseCert/certs/server.key new file mode 100644 index 000000000000..1c7baf25090c --- /dev/null +++ b/system-tests/projects/multiple-certificates-chooseCert/certs/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAwsbwm3116teqagSxM4bh7LLUvMP+YY8YgyVKGSiT3BSAzOBy +3GXN6bF9oATcqR4wj9XVLQn6oVermoJ+3ngP4gcpOY0/aOFFCgjjKjWe6D0RyGV+ +geT0q2/DfTJY09zTDLiG1LPFEKUxInQ5+47IwLvJYKTQ2r1e8MTLWKkxjJ/voNtb +26XIQwCd6hd6Qb3qbn+PSIvHWXtQ8RaPgrE+bEquyzBWt1Eux/P3FTavBW+yB7G3 +s3Q42uu/WUBB4zEF0qqMg6eZR8sut7ctF4/1ipeD6q/f/8kkCZl1/0mJ/d6+gCc2 +JnQ+uBOD3LZZv2Qg3/NXQzk7CNmDNU+KrlQb5QIDAQABAoIBAEQPJ7vtVuq8eTua +cCDFHOSMiHF9OsH0MmiA+ydXWnMoZukv9HK20AC8mFz11fTyK53W5ipatkzySXAv +8QzjfwlH30vQ4ztOm8Up3da97czQqgoQDOhujF/rKa/xi8BcBRMI13QzbMSoNEcJ +vcj814EhHPi8Ho6QZUBUQ4mzdTDhhRRD9H2r/Lv2QwRCyJZ0LbY2kbAfRlzcA+Z5 +pABp+OdfjByG3QZB7kS4q5OSkByKInznE+D0SicYhC+gAMxw5zEusxrCTXHU1Qsb +nx6oqww973Ucx0oFrJT/Zyvj4EMtd2xursqcnsZzoMxNEzmxkBQfaFEe8pDYl/9Y +NRGo8AECgYEA9onBn8Q98Ez2++3StVW5rXKcaRnq251SlgTECyPOIQugWSQNQ85i +pnXYcE78cvZToqfammZi7rR/gjWnieQn7GINc7lk+inNHE6HwEGO5DZEVYi4FEOW +DDJXrKC6UOn9lBK9jTPhMJ5w/Zq033yUDMoriM7wl922ZPptzHSXgwECgYEAykCh +WbKcLef2mFffNZfDQ4fND3sgjsTDQr4z5032X2iHxlY2VEW6vszSDfLUjbiDlr2u +l8c67i0CzmAj3uTybrC3KcQS3Md8aFsjDv73b0SS57J0U2rZobcyFt7Q6McsCUWA +m6cCvZOjbVoKyKmEWqxNAs+aNAGmZTH7IyEH7OUCgYEAkYmA4+/VIKZOpyod2S5i +DiI6c1D5c+vLdQQbNvhMFvXmErlVm1Gaqumme6YlITiI5txwD/WhMYkcPKGv5Zbq +eoDnsAkhsww6KRuyx6vrzoy6ikPLZyr+yPRvDVlUo69+Iottmo2lwi3GRe+uFsBU +JbizOm/vV7dqlPHBH5E0YAECgYAlviXeHUyz7GPCTjOjRqzzvzXvO6T5v5JItEHv +Wkctys37nz8IPvL4hDR20egEgnhb+w3H2TAQHmo1pQ9MmyD6sxObQbHbbYaz2r4S +IFaAdyWLA2xd3MS8Pvmj9vS5W+HF4Sh844/JXPHUiEcW5zuByEGe0qsOJXitzcP1 +7EYUWQKBgQDNbORNvyadppkgIl3TjCQHxcceYiyQeo2wWvMPoi2bXblBE7cKwhq4 +oUVnBUc5Lyg/d18oiAgs2/9hpJaZZDeHirepEodo04SP1SnAkvxASLgTHHCq775x +fpQs3oEU89b3PKluLnWW2lOtDBMM3yQYTGD8WMWwV2Udlzcv1aNZjQ== +-----END RSA PRIVATE KEY----- diff --git a/system-tests/projects/multiple-certificates-chooseCert/certs/user.crt b/system-tests/projects/multiple-certificates-chooseCert/certs/user.crt new file mode 100644 index 000000000000..17080624bace --- /dev/null +++ b/system-tests/projects/multiple-certificates-chooseCert/certs/user.crt @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICpTCCAY0CFA2mErrXtki8MGjNJco10dVN+cRHMA0GCSqGSIb3DQEBCwUAMA8x +DTALBgNVBAMMBE15Q0EwHhcNMjQwOTA1MjIyMTEzWhcNMjUwOTA1MjIyMTEzWjAP +MQ0wCwYDVQQDDAR1c2VyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +ukxmIujfjcxWi0RkxFMQ2j1RHFF3LSqgLkZs3lRxOEGV0ex0vjiRUzynTOB6UuGE +M+R/xx8I4HkFkTgM0DbZh6fdS5Otu4j+uHxjaiG1Pp4Kr0kNbbPvBqMB3pah8E+W +C7xD5tQC4ZvL/CCIeIw3HARF58uetmIyyWVDKglCTD/+PmaVNPUt/i0OwXGzPtJg +1lEUxIA5gxw4ninAJiG5X6xxElddkCeNFYMlSydPJClVko4kk1i6cy6Vmc3qf/s6 +/oPU1rxULtE0e+HXmdZEF4/FHNq/em5ez0s7KxD+xB6FYC0oWfL+lOjmBujaOVkw +Wa3iQoUumF+zZ9kd70bIswIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBaBb1yVRY6 +elxykF7OUAKgFXIDennT4FKEr1bImMsIhU+0NjLsI6KaWMm2zbvIQIT8ArCwc/Gg +YglU3agmEaEnYxHDyoArTcvxAaPWND3TPIvaijUu0hvNY7LzelD+9+xCMDDH4mC8 +b1N9IiIcpu8e7zh1wcHkTGOL8yAIH+W5Tss44z4hkkPa5Lfz2GBk4LljfOXMQQoB +6RNYkFCfLi0QfEHMa2LCJG7akBbJHj998M5xjjKqNd4+zboXmcf8h8Zj0sgrcbSo +KcPC0VKaYjrEOxMk0CJ0ANVCU3iUC+dIfvNAsgzgkpejXrOQRqc+zq7JErIg7C++ +HCtYGjuYjbfw +-----END CERTIFICATE----- diff --git a/system-tests/projects/multiple-certificates-chooseCert/certs/user.csr b/system-tests/projects/multiple-certificates-chooseCert/certs/user.csr new file mode 100644 index 000000000000..15241a6f5024 --- /dev/null +++ b/system-tests/projects/multiple-certificates-chooseCert/certs/user.csr @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICVDCCATwCAQAwDzENMAsGA1UEAwwEdXNlcjCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBALpMZiLo343MVotEZMRTENo9URxRdy0qoC5GbN5UcThBldHs +dL44kVM8p0zgelLhhDPkf8cfCOB5BZE4DNA22Yen3UuTrbuI/rh8Y2ohtT6eCq9J +DW2z7wajAd6WofBPlgu8Q+bUAuGby/wgiHiMNxwERefLnrZiMsllQyoJQkw//j5m +lTT1Lf4tDsFxsz7SYNZRFMSAOYMcOJ4pwCYhuV+scRJXXZAnjRWDJUsnTyQpVZKO +JJNYunMulZnN6n/7Ov6D1Na8VC7RNHvh15nWRBePxRzav3puXs9LOysQ/sQehWAt +KFny/pTo5gbo2jlZMFmt4kKFLphfs2fZHe9GyLMCAwEAAaAAMA0GCSqGSIb3DQEB +CwUAA4IBAQCNlt1ionGsVB3iR3/fbofV6tVvZLaR1h0itsTBEYakPKOBr/sc15W0 +vW6ivL/rh1sTwTqtCi48U3dED6G4LGT+uPWi6q5B+5nJYiNQgInE1OrMlot96o3f +GsDKHmv8XbPZmty0DQ06+RaATszrjVJq6aKUGeuPlcJM1o7PIAZEXzXCkGFGn+G1 +0HJkH3sIXoJpGOs6NabfAsEzMJGU58WMPBpQPQ/5qhZ/y/iuIYPJGn67lzw287vt +nkkkd1M4FQ8Kw86e6O9eQKigajf7Ippjxx9yOzjfoAkcKnYIPzeoyzrqT/28JpMu +YDESfjFw5oc8YzsdGMHkZ2Opp2qLaCPy +-----END CERTIFICATE REQUEST----- diff --git a/system-tests/projects/multiple-certificates-chooseCert/certs/user.key b/system-tests/projects/multiple-certificates-chooseCert/certs/user.key new file mode 100644 index 000000000000..b88967350649 --- /dev/null +++ b/system-tests/projects/multiple-certificates-chooseCert/certs/user.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAukxmIujfjcxWi0RkxFMQ2j1RHFF3LSqgLkZs3lRxOEGV0ex0 +vjiRUzynTOB6UuGEM+R/xx8I4HkFkTgM0DbZh6fdS5Otu4j+uHxjaiG1Pp4Kr0kN +bbPvBqMB3pah8E+WC7xD5tQC4ZvL/CCIeIw3HARF58uetmIyyWVDKglCTD/+PmaV +NPUt/i0OwXGzPtJg1lEUxIA5gxw4ninAJiG5X6xxElddkCeNFYMlSydPJClVko4k +k1i6cy6Vmc3qf/s6/oPU1rxULtE0e+HXmdZEF4/FHNq/em5ez0s7KxD+xB6FYC0o +WfL+lOjmBujaOVkwWa3iQoUumF+zZ9kd70bIswIDAQABAoIBABzg4OhCbvz9EuFN +vc0ND0y1OpCINFAlDmV99zxTnKVazU2YONSP0j8z5L8vAOc+HA8rOX91WRvqxqkn +Jh1Uk03LAKvJfaHXb5zLc2GtTfoh54gaa+F4hfHibNTDRuEIhTrWeTQb40JzsyAn +LuB448YNB4UR9E1TVYoQj2PBwawt73X4VfM6WiOkJ0kFVLuXYhJ4ivXP1K82rO22 +WmVOJGloSTgHQAneCK7Uq4BJqu6PGppVzfpucm9sbgr3e6oe6NfiRqS2RLlgehlj +wlyIt643zBFYv0tSbnKpsMse84YFvS6O9ubsnUJdx5CmZ6K2X4GUH7+UPX8xiFsD +QdUxBBkCgYEA6HDk92wfGG4KvTuNVGOInBSk/abMi2amJYzPRTOU4ywh+EOW9kOJ +Lum9+eiFvHA8UAkZTAeeLN+ChGfp8izMUr/GGsxWFVECGh1WvT6RNYXSaKIkxoWP +kU8Lfen4fSNti18u9sdgd2wta+EFj/cd92DOkDbDKEe6UWyCI7h7SE0CgYEAzS5A +bX0HcMJzInI/Cz9Gs+bgSt3Tq7dTxk0EsvLwlTLK5AxNParC8YPOyYmPUr3AsSIg +//OdZpjEC5DAs0N4NoT3+WrQcyOHzVab8T/ABjRqHuN4F4v48LO6TB0xb/0kG2+W +0Ht6DgbDPYHxF5R034QISU8qmZgcT0ucAJQC1P8CgYEAm6MMDQeegpMcKbz4Uusa +i7ffCeBn0Wd4FGiGWIXDSaFBVaScLmTmND1acT1aXuSf3c9Z2ui25sD5+YJCyV9p +qHa0RTlQ4pENYUMH00tTm4zxaBI5oZvIl7lfrn29Xr0cGlgvcBnr48rRVwyi1tTW +gc+WQEYKwqvUe7S2Jwjds3kCgYANGvkuhR/mieUEvDteoooHj6Wf4o9Uln8ZWW4F +aBMJ1Ja0/jwoxjFxb7PTl5+57zjRl5Z4Ar15AiTzgCluyc5x3Qhhu/rYf9p6uR94 +x1OikdJ9apYPGvepmDE8D1lLB+Y4gmYlpt1i6/eohBY8JVVZuXs2A4J7EQYt4mxy +dQHBTQKBgQCNO372zaBh7ylknQVHfgKrQgLUIL+/NxA/QvO1YRXWbBYDb+gBPAhu +O2dKgGK+jNe2igWlxJDL4wzaGUNziJy8DsVLtSPCrH2sMqebQzCz8sX37hyQBBgG +dCavUI7Tkej3GFdwurOuQ/n5gIYTWwfNRBJ+HE3I5C0k1Veruelugg== +-----END RSA PRIVATE KEY----- diff --git a/system-tests/projects/multiple-certificates-chooseCert/cypress.config.ts b/system-tests/projects/multiple-certificates-chooseCert/cypress.config.ts new file mode 100644 index 000000000000..79aaab664da0 --- /dev/null +++ b/system-tests/projects/multiple-certificates-chooseCert/cypress.config.ts @@ -0,0 +1,39 @@ +import { server } from './src/server' +import { certificates } from './scripts/generateCerts' + +module.exports = { + e2e: { + supportFile: false, + setupNodeEvents (on, config) { + on('before:run', async () => { + await certificates.generate() + await server.start() + }) + + on('after:run', async () => { + await server.stop() + await certificates.clean() + }) + + return config + }, + clientCertificates: [ + { + url: 'https://localhost:3000', + ca: ['certs/ca.crt'], + certs: [ + { + group: 'user', + cert: 'certs/user.crt', + key: 'certs/user.key', + }, + { + group: 'admin', + cert: 'certs/admin.crt', + key: 'certs/admin.key', + }, + ], + }, + ], + }, +} diff --git a/system-tests/projects/multiple-certificates-chooseCert/cypress/e2e/server.cy.ts b/system-tests/projects/multiple-certificates-chooseCert/cypress/e2e/server.cy.ts new file mode 100644 index 000000000000..b46730a38c5d --- /dev/null +++ b/system-tests/projects/multiple-certificates-chooseCert/cypress/e2e/server.cy.ts @@ -0,0 +1,35 @@ +// @ts-nocheck +const url = 'https://localhost:3000' + +describe('Certificate validations', () => { + it('Fails with 404 (No certificate provided)', () => { + cy.request({ + url, + failOnStatusCode: false, + }).then((response) => { + expect(response.status).to.eq(404) + }) + }) + + it('Succeeds with 200 (User certificate provided)', () => { + cy.chooseCert('user') + cy.request({ + url, + failOnStatusCode: false, + }).then((response) => { + expect(response.status).to.eq(200) + expect(response.body).to.eq('200: User Access Granted') + }) + }) + + it('Succeeds with 200 (Admin certificate provided)', () => { + cy.chooseCert('admin') + cy.request({ + url, + failOnStatusCode: false, + }).then((response) => { + expect(response.status).to.eq(200) + expect(response.body).to.eq('200: Admin Access Granted') + }) + }) +}) diff --git a/system-tests/projects/multiple-certificates-chooseCert/package.json b/system-tests/projects/multiple-certificates-chooseCert/package.json new file mode 100644 index 000000000000..a28ae849ef12 --- /dev/null +++ b/system-tests/projects/multiple-certificates-chooseCert/package.json @@ -0,0 +1,26 @@ +{ + "name": "multiple-certificates-selector-example", + "version": "1.0.0", + "description": "", + "main": "src/index.ts", + "scripts": { + "start": "npx tsx ./src/index.ts", + "test": "cypress run" + }, + "dependencies": { + "express": "^4.19.2", + "https": "^1.0.0" + }, + "devDependencies": { + "@types/express": "^4.17.21", + "@types/node": "^22.5.4", + "tsx": "^4.19.0", + "typescript": "^5.6.2" + }, + "license": "ISC", + "author": { + "name": "Abhishek Karmakar", + "url": "https://github.com/alienkarma" + }, + "keywords": [] +} diff --git a/system-tests/projects/multiple-certificates-chooseCert/scripts/generateCerts.ts b/system-tests/projects/multiple-certificates-chooseCert/scripts/generateCerts.ts new file mode 100644 index 000000000000..4ee0d46ed722 --- /dev/null +++ b/system-tests/projects/multiple-certificates-chooseCert/scripts/generateCerts.ts @@ -0,0 +1,44 @@ +import * as fs from 'fs' +import * as path from 'path' +import { runCommand } from './utils' + +const certsDir = path.join(__dirname, 'certs') + +export const certificates = { + generate: async () => { + // Create certs directory + certificates.clean() + fs.mkdirSync(certsDir) + + // Generate CA key and certificate + await runCommand(`openssl genrsa -out ${path.join(certsDir, 'ca.key')} 2048`) + await runCommand(`openssl req -new -x509 -key ${path.join(certsDir, 'ca.key')} -out ${path.join(certsDir, 'ca.crt')} -subj "/CN=MyCA"`) + + // Generate server key and CSR + await runCommand(`openssl genrsa -out ${path.join(certsDir, 'server.key')} 2048`) + await runCommand(`openssl req -new -key ${path.join(certsDir, 'server.key')} -out ${path.join(certsDir, 'server.csr')} -subj "/CN=localhost"`) + + // Sign server certificate with CA + await runCommand(`openssl x509 -req -in ${path.join(certsDir, 'server.csr')} -CA ${path.join(certsDir, 'ca.crt')} -CAkey ${path.join(certsDir, 'ca.key')} -CAcreateserial -out ${path.join(certsDir, 'server.crt')} -days 30`) + + // Generate admin client key and CSR + await runCommand(`openssl genrsa -out ${path.join(certsDir, 'admin.key')} 2048`) + await runCommand(`openssl req -new -key ${path.join(certsDir, 'admin.key')} -out ${path.join(certsDir, 'admin.csr')} -subj "/CN=admin"`) + + // Sign admin client certificate with CA + await runCommand(`openssl x509 -req -in ${path.join(certsDir, 'admin.csr')} -CA ${path.join(certsDir, 'ca.crt')} -CAkey ${path.join(certsDir, 'ca.key')} -CAcreateserial -out ${path.join(certsDir, 'admin.crt')} -days 30`) + + // Generate user client key and CSR + await runCommand(`openssl genrsa -out ${path.join(certsDir, 'user.key')} 2048`) + await runCommand(`openssl req -new -key ${path.join(certsDir, 'user.key')} -out ${path.join(certsDir, 'user.csr')} -subj "/CN=user"`) + + // Sign user client certificate with CA + await runCommand(`openssl x509 -req -in ${path.join(certsDir, 'user.csr')} -CA ${path.join(certsDir, 'ca.crt')} -CAkey ${path.join(certsDir, 'ca.key')} -CAcreateserial -out ${path.join(certsDir, 'user.crt')} -days 30`) + }, + clean: async () => { + // Remove certs directory + if (fs.existsSync(certsDir)) { + fs.rmdirSync(certsDir, { recursive: true }) + } + }, +} diff --git a/system-tests/projects/multiple-certificates-chooseCert/scripts/utils.ts b/system-tests/projects/multiple-certificates-chooseCert/scripts/utils.ts new file mode 100644 index 000000000000..402934117dbb --- /dev/null +++ b/system-tests/projects/multiple-certificates-chooseCert/scripts/utils.ts @@ -0,0 +1,19 @@ +import { exec } from 'child_process' +import { promisify } from 'util' + +const execPromise = promisify(exec) + +// Function to run a command +export const runCommand = async (command: string) => { + try { + const { stdout, stderr } = await execPromise(command) + + if (stderr) { + console.error(`Error: ${stderr}`) + } + + console.log(stdout) + } catch (error) { + console.error(`Command failed: ${error}`) + } +} diff --git a/system-tests/projects/multiple-certificates-chooseCert/src/index.d.ts b/system-tests/projects/multiple-certificates-chooseCert/src/index.d.ts new file mode 100644 index 000000000000..023631b96e74 --- /dev/null +++ b/system-tests/projects/multiple-certificates-chooseCert/src/index.d.ts @@ -0,0 +1,8 @@ +declare namespace Express { + export interface Request { + user?: { + isAuthorized: boolean + type: 'admin' | 'user' | null + } + } +} diff --git a/system-tests/projects/multiple-certificates-chooseCert/src/index.ts b/system-tests/projects/multiple-certificates-chooseCert/src/index.ts new file mode 100644 index 000000000000..cafe555fdede --- /dev/null +++ b/system-tests/projects/multiple-certificates-chooseCert/src/index.ts @@ -0,0 +1,8 @@ +import { server } from './server' + +// Starts the server +try { + server.start() +} catch (error) { + console.error(error) +} diff --git a/system-tests/projects/multiple-certificates-chooseCert/src/middleware/validate-client-cert.ts b/system-tests/projects/multiple-certificates-chooseCert/src/middleware/validate-client-cert.ts new file mode 100644 index 000000000000..ab08912f96cf --- /dev/null +++ b/system-tests/projects/multiple-certificates-chooseCert/src/middleware/validate-client-cert.ts @@ -0,0 +1,25 @@ +import { Request, Response, NextFunction, Handler } from 'express' +import { PeerCertificate, TLSSocket } from 'tls' + +// Middleware to verify client certificate +export const validateClientCert: Handler = ( + req: Request, + res: Response, + next: NextFunction, +) => { + const tlsSocket = req.socket as TLSSocket + const cert: PeerCertificate | undefined = tlsSocket.getPeerCertificate() + + if (cert && cert.subject) { + // Check the common name (CN) of the client certificate + if (cert.subject.CN === 'admin') { + res.status(200).send('200: Admin Access Granted') + } else if (cert.subject.CN === 'user') { + res.status(200).send('200: User Access Granted') + } else { + res.status(401).send('401: Unauthorized') + } + } else { + res.status(404).send('404: Not Found') + } +} diff --git a/system-tests/projects/multiple-certificates-chooseCert/src/server.ts b/system-tests/projects/multiple-certificates-chooseCert/src/server.ts new file mode 100644 index 000000000000..512078c60198 --- /dev/null +++ b/system-tests/projects/multiple-certificates-chooseCert/src/server.ts @@ -0,0 +1,46 @@ +import fs from 'fs' +import path from 'path' +import express from 'express' +import { createServer, ServerOptions } from 'https' +import { validateClientCert } from './middleware/validate-client-cert' + +// Certificates directory +const certDir = path.join(__dirname, '../certs') + +// Create Express app +const app = express() + +// Apply the validateClientCert middleware to all routes +app.use(validateClientCert) + +// Load server key and certificate, and CA to validate client certificates +const options: ServerOptions = { + key: fs.readFileSync(path.join(certDir, 'server.key')), + cert: fs.readFileSync(path.join(certDir, 'server.crt')), + ca: fs.readFileSync(path.join(certDir, 'ca.crt')), + requestCert: true, // Request client certificates + rejectUnauthorized: false, // Reject unauthorized clients +} + +// Create the server instancve +const serverInstance = createServer(options, app) + +// Export the server commands for use +export const server = { + start: async () => { + return new Promise((resolve, reject) => { + serverInstance.listen(3000, () => { + console.log('Server running on https://localhost:3000') + resolve(true) + }) + }) + }, + stop: async () => { + return new Promise((resolve, reject) => { + serverInstance.close(() => { + console.log('Server stopped') + resolve() + }) + }) + }, +} diff --git a/system-tests/projects/multiple-certificates-chooseCert/tsconfig.json b/system-tests/projects/multiple-certificates-chooseCert/tsconfig.json new file mode 100644 index 000000000000..da961be83887 --- /dev/null +++ b/system-tests/projects/multiple-certificates-chooseCert/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "target": "ES6", + "module": "CommonJS", + "strict": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "moduleResolution": "node", + "types": ["node", "cypress"] + }, + "include": ["cypress/**/*.ts", "src/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/system-tests/projects/multiple-certificates-chooseCert/yarn.lock b/system-tests/projects/multiple-certificates-chooseCert/yarn.lock new file mode 100644 index 000000000000..694802efb379 --- /dev/null +++ b/system-tests/projects/multiple-certificates-chooseCert/yarn.lock @@ -0,0 +1,765 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@esbuild/aix-ppc64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz#51299374de171dbd80bb7d838e1cfce9af36f353" + integrity sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ== + +"@esbuild/android-arm64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz#58565291a1fe548638adb9c584237449e5e14018" + integrity sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw== + +"@esbuild/android-arm@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.23.1.tgz#5eb8c652d4c82a2421e3395b808e6d9c42c862ee" + integrity sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ== + +"@esbuild/android-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.23.1.tgz#ae19d665d2f06f0f48a6ac9a224b3f672e65d517" + integrity sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg== + +"@esbuild/darwin-arm64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz#05b17f91a87e557b468a9c75e9d85ab10c121b16" + integrity sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q== + +"@esbuild/darwin-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz#c58353b982f4e04f0d022284b8ba2733f5ff0931" + integrity sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw== + +"@esbuild/freebsd-arm64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz#f9220dc65f80f03635e1ef96cfad5da1f446f3bc" + integrity sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA== + +"@esbuild/freebsd-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz#69bd8511fa013b59f0226d1609ac43f7ce489730" + integrity sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g== + +"@esbuild/linux-arm64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz#8050af6d51ddb388c75653ef9871f5ccd8f12383" + integrity sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g== + +"@esbuild/linux-arm@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz#ecaabd1c23b701070484990db9a82f382f99e771" + integrity sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ== + +"@esbuild/linux-ia32@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz#3ed2273214178109741c09bd0687098a0243b333" + integrity sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ== + +"@esbuild/linux-loong64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz#a0fdf440b5485c81b0fbb316b08933d217f5d3ac" + integrity sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw== + +"@esbuild/linux-mips64el@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz#e11a2806346db8375b18f5e104c5a9d4e81807f6" + integrity sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q== + +"@esbuild/linux-ppc64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz#06a2744c5eaf562b1a90937855b4d6cf7c75ec96" + integrity sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw== + +"@esbuild/linux-riscv64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz#65b46a2892fc0d1af4ba342af3fe0fa4a8fe08e7" + integrity sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA== + +"@esbuild/linux-s390x@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz#e71ea18c70c3f604e241d16e4e5ab193a9785d6f" + integrity sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw== + +"@esbuild/linux-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz#d47f97391e80690d4dfe811a2e7d6927ad9eed24" + integrity sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ== + +"@esbuild/netbsd-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz#44e743c9778d57a8ace4b72f3c6b839a3b74a653" + integrity sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA== + +"@esbuild/openbsd-arm64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz#05c5a1faf67b9881834758c69f3e51b7dee015d7" + integrity sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q== + +"@esbuild/openbsd-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz#2e58ae511bacf67d19f9f2dcd9e8c5a93f00c273" + integrity sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA== + +"@esbuild/sunos-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz#adb022b959d18d3389ac70769cef5a03d3abd403" + integrity sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA== + +"@esbuild/win32-arm64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz#84906f50c212b72ec360f48461d43202f4c8b9a2" + integrity sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A== + +"@esbuild/win32-ia32@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz#5e3eacc515820ff729e90d0cb463183128e82fac" + integrity sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ== + +"@esbuild/win32-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz#81fd50d11e2c32b2d6241470e3185b70c7b30699" + integrity sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg== + +"@types/body-parser@*": + version "1.19.5" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4" + integrity sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.38" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" + integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== + dependencies: + "@types/node" "*" + +"@types/express-serve-static-core@^4.17.33": + version "4.19.5" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz#218064e321126fcf9048d1ca25dd2465da55d9c6" + integrity sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express@^4.17.21": + version "4.17.21" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d" + integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.33" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/http-errors@*": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f" + integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== + +"@types/mime@^1": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" + integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== + +"@types/node@*", "@types/node@^22.5.4": + version "22.5.4" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.5.4.tgz#83f7d1f65bc2ed223bdbf57c7884f1d5a4fa84e8" + integrity sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg== + dependencies: + undici-types "~6.19.2" + +"@types/qs@*": + version "6.9.15" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.15.tgz#adde8a060ec9c305a82de1babc1056e73bd64dce" + integrity sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg== + +"@types/range-parser@*": + version "1.2.7" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" + integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== + +"@types/send@*": + version "0.17.4" + resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a" + integrity sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/serve-static@*": + version "1.15.7" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.7.tgz#22174bbd74fb97fe303109738e9b5c2f3064f714" + integrity sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw== + dependencies: + "@types/http-errors" "*" + "@types/node" "*" + "@types/send" "*" + +accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== + +body-parser@1.20.3: + version "1.20.3" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6" + integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== + dependencies: + bytes "3.1.2" + content-type "~1.0.5" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.13.0" + raw-body "2.5.2" + type-is "~1.6.18" + unpipe "1.0.0" + +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +call-bind@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" + integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + set-function-length "^1.2.1" + +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-type@~1.0.4, content-type@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== + +cookie@0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" + integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== + +debug@2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +depd@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + +encodeurl@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" + integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== + +es-define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" + integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== + dependencies: + get-intrinsic "^1.2.4" + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +esbuild@~0.23.0: + version "0.23.1" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.23.1.tgz#40fdc3f9265ec0beae6f59824ade1bd3d3d2dab8" + integrity sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg== + optionalDependencies: + "@esbuild/aix-ppc64" "0.23.1" + "@esbuild/android-arm" "0.23.1" + "@esbuild/android-arm64" "0.23.1" + "@esbuild/android-x64" "0.23.1" + "@esbuild/darwin-arm64" "0.23.1" + "@esbuild/darwin-x64" "0.23.1" + "@esbuild/freebsd-arm64" "0.23.1" + "@esbuild/freebsd-x64" "0.23.1" + "@esbuild/linux-arm" "0.23.1" + "@esbuild/linux-arm64" "0.23.1" + "@esbuild/linux-ia32" "0.23.1" + "@esbuild/linux-loong64" "0.23.1" + "@esbuild/linux-mips64el" "0.23.1" + "@esbuild/linux-ppc64" "0.23.1" + "@esbuild/linux-riscv64" "0.23.1" + "@esbuild/linux-s390x" "0.23.1" + "@esbuild/linux-x64" "0.23.1" + "@esbuild/netbsd-x64" "0.23.1" + "@esbuild/openbsd-arm64" "0.23.1" + "@esbuild/openbsd-x64" "0.23.1" + "@esbuild/sunos-x64" "0.23.1" + "@esbuild/win32-arm64" "0.23.1" + "@esbuild/win32-ia32" "0.23.1" + "@esbuild/win32-x64" "0.23.1" + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +express@^4.19.2: + version "4.20.0" + resolved "https://registry.yarnpkg.com/express/-/express-4.20.0.tgz#f1d08e591fcec770c07be4767af8eb9bcfd67c48" + integrity sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.20.3" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.6.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "2.0.0" + encodeurl "~2.0.0" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.2.0" + fresh "0.5.2" + http-errors "2.0.0" + merge-descriptors "1.0.3" + methods "~1.1.2" + on-finished "2.4.1" + parseurl "~1.3.3" + path-to-regexp "0.1.10" + proxy-addr "~2.0.7" + qs "6.11.0" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.19.0" + serve-static "1.16.0" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +finalhandler@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" + integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + +fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +get-intrinsic@^1.1.3, get-intrinsic@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + +get-tsconfig@^4.7.5: + version "4.8.0" + resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.8.0.tgz#125dc13a316f61650a12b20c97c11b8fd996fedd" + integrity sha512-Pgba6TExTZ0FJAn1qkJAjIeKoDJ3CsI2ChuLohJnZl/tTU8MVrq3b+2t5UOPfRa4RMsorClBjJALkJUMjG1PAw== + dependencies: + resolve-pkg-maps "^1.0.0" + +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + +has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-proto@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" + integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== + +has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +hasown@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + +https@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/https/-/https-1.0.0.tgz#3c37c7ae1a8eeb966904a2ad1e975a194b7ed3a4" + integrity sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg== + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +inherits@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + +merge-descriptors@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" + integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + +ms@2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +object-inspect@^1.13.1: + version "1.13.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff" + integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g== + +on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +path-to-regexp@0.1.10: + version "0.1.10" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b" + integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w== + +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +qs@6.11.0: + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== + dependencies: + side-channel "^1.0.4" + +qs@6.13.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" + integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== + dependencies: + side-channel "^1.0.6" + +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" + integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + +resolve-pkg-maps@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" + integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== + +safe-buffer@5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +send@0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" + integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + +send@0.19.0: + version "0.19.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" + integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + +serve-static@1.16.0: + version "1.16.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.0.tgz#2bf4ed49f8af311b519c46f272bf6ac3baf38a92" + integrity sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.18.0" + +set-function-length@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +side-channel@^1.0.4, side-channel@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" + +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +tsx@^4.19.0: + version "4.19.0" + resolved "https://registry.yarnpkg.com/tsx/-/tsx-4.19.0.tgz#6166cb399b17d14d125e6158d23384045cfdf4f6" + integrity sha512-bV30kM7bsLZKZIOCHeMNVMJ32/LuJzLVajkQI/qf92J2Qr08ueLQvW00PUZGiuLPP760UINwupgUj8qrSCPUKg== + dependencies: + esbuild "~0.23.0" + get-tsconfig "^4.7.5" + optionalDependencies: + fsevents "~2.3.3" + +type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +typescript@^5.6.2: + version "5.6.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.2.tgz#d1de67b6bef77c41823f822df8f0b3bcff60a5a0" + integrity sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw== + +undici-types@~6.19.2: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== diff --git a/system-tests/test/multiple_certificates_chooseCert.ts b/system-tests/test/multiple_certificates_chooseCert.ts new file mode 100644 index 000000000000..318dd774ac9c --- /dev/null +++ b/system-tests/test/multiple_certificates_chooseCert.ts @@ -0,0 +1,10 @@ +import systemTests from '../lib/system-tests' + +describe('e2e', async () => { + systemTests.it('passes', { + project: 'multiple-certificates-chooseCert', + testingType: 'e2e', + snapshot: true, + browser: ['electron'], + }) +})