From ae8852d738b79c915f4cddddd6cf4db6002d6232 Mon Sep 17 00:00:00 2001 From: Andrii Arsenin Date: Tue, 7 Apr 2026 19:33:08 +0300 Subject: [PATCH] feat(auth): add login/password authentication strategy - KeyDescriptor now accepts optional login field alongside keyFile - when login is present, clicks login/password auth method, fills email+password fields, and submits - file-key flow unchanged; validation ensures exactly one strategy is present --- src/auth/auth.service.ts | 52 ++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index aad9de0..bff41cc 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -14,7 +14,8 @@ import { SessionService } from '../session/session.service'; import { EnvironmentService } from '../environment/environment.service'; interface KeyDescriptor { - keyFile: string; + keyFile?: string; + login?: string; password: string; } @@ -64,9 +65,16 @@ export class AuthService { throw new BadRequestException(`Cannot read key descriptor: ${keyId}`, { cause: err }); } - const keyFilePath = path.resolve(this.keysDir, descriptor.keyFile); - if (!fs.existsSync(keyFilePath)) { - throw new BadRequestException(`Key file not found: ${descriptor.keyFile}`); + const useLoginPassword = !!descriptor.login; + + if (!useLoginPassword) { + if (!descriptor.keyFile) { + throw new BadRequestException(`Key descriptor for "${keyId}" must have either "login" or "keyFile"`); + } + const keyFilePath = path.resolve(this.keysDir, descriptor.keyFile); + if (!fs.existsSync(keyFilePath)) { + throw new BadRequestException(`Key file not found: ${descriptor.keyFile}`); + } } const browser = await chromium.launch({ headless: true, executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH }); @@ -77,20 +85,34 @@ export class AuthService { this.logger.log(`Navigating to ${loginUrl}`); await page.goto(loginUrl, { waitUntil: 'networkidle' }); - // Click "Файловий ключ" button - await page.getByText('Файловий ключ').click(); + if (useLoginPassword) { + // Click "Логін і пароль" auth method + await page.locator('p[aria-label="Логін і пароль"]').click(); - // Upload key file via hidden file input - const fileInput = page.locator('input[accept=".dat,.pfx,.pk8,.zs2,.jks"][type="file"]'); - await fileInput.setInputFiles(keyFilePath); + // Fill login and password + await page.getByLabel('Електронна пошта').fill(descriptor.login!); + await page.getByLabel('Пароль').fill(descriptor.password); - // Enter password - await page - .locator('#id-app-login-file-key-password') - .fill(descriptor.password); + // Click "Увійти" + await page.locator('button:has-text("Увійти")').click(); + } else { + const keyFilePath = path.resolve(this.keysDir, descriptor.keyFile!); - // Click "Продовжити" - await page.locator('#id-app-login-file-key-sign-button').click(); + // Click "Файловий ключ" button + await page.getByText('Файловий ключ').click(); + + // Upload key file via hidden file input + const fileInput = page.locator('input[accept=".dat,.pfx,.pk8,.zs2,.jks"][type="file"]'); + await fileInput.setInputFiles(keyFilePath); + + // Enter password + await page + .locator('#id-app-login-file-key-password') + .fill(descriptor.password); + + // Click "Продовжити" + await page.locator('#id-app-login-file-key-sign-button').click(); + } // Wait until redirected to cabinet this.logger.log(`Waiting for redirect to ${cabinetUrl}`);