Files
lemana-vpn/docs/goals/manual-first-auth-flow/PLAN.md

28 KiB
Raw Blame History

Manual-First Auth Flow Implementation Plan

Intent: Перевернуть Lemana VPN из auto/Bitwarden-first в manual-first: новая установка и действие по умолчанию открывают видимый SSO-браузер без сохранённых секретов, а удобства включаются отдельными функциями. Current Behavior: Fresh install по умолчанию выбирает credential_source=bitwarden, ставит Bitwarden/Touch ID, vpn запускает hidden browser с autofill+submit, а manual-full существует как отдельный диагностический режим. Expected Outcome: Базовый путь не требует Bitwarden, Keychain, TOTP seed или автоотправки. Пользователь может поэтапно включить сохранение LDAP login/password в macOS Keychain, затем TOTP seed/autofill, затем Bitwarden sync, Touch ID, autosubmit/headless. Target-Perspective Output: Новый пользователь запускает installer, ничего специально не выбирает, нажимает vpn или основную кнопку в LemanaVPN.app, видит Keycloak форму и вводит LDAP, пароль и одноразовый код руками. Если он включил сохранение LDAP в Keychain, username/password подставляются, а одноразовый код он вводит руками. Truth Owner: bin/vpn-lemanapro.sh владеет runtime-решением режима; ~/.config/lemana-vpn/env хранит выбранные capabilities; macOS Keychain хранит только секреты; Swift app только вызывает CLI и читает JSON. Contract Boundary: Installer пишет env/config, CLI читает env/flags/Keychain и отдаёт JSON Lines/status JSON, Swift app потребляет только этот CLI-контракт. openconnect-lite остаётся SSO/VPN engine. Cutover: Fresh installs become manual-first. Existing configs with LEMANA_VPN_CREDENTIAL_SOURCE=bitwarden keep working, but docs/UI stop presenting Bitwarden as default. Legacy aliases vpn-auto, vpn-manual, vpn-manual-full stay as compatibility shims. Displaced Path: The displaced dominant path is Bitwarden + Touch ID + hidden autofill + autosubmit by default. It becomes an explicit opt-in feature set, not the default route. Value Density: First slice removes the biggest onboarding failure: users without Bitwarden can connect manually immediately, then add only the conveniences they need. Acceptance Evidence: In a clean temporary HOME, sh install.sh --dry-run --non-interactive reports manual/default auth with Bitwarden and Touch ID off; the interactive installer copy reads like a step-by-step setup for a non-IT user; bash bin/vpn-lemanapro.sh --json without credentials reaches a visible browser launch without username_required or keychain_required; a Keychain-login-only fixture fills username/password but leaves OTP manual and submit disabled; the Swift app primary action maps to the manual-first path. Evidence Lane: Non-live smoke ladder first: sh -n, bash -n, tests/smoke.sh, swift build, git diff --check. Live VPN is optional after status check and should start with visible manual mode only. Kill Criteria: No fresh-install default may select Bitwarden, require a TOTP seed, autosubmit, or hide the browser. No docs may state Bitwarden is enabled by default. No default CLI/app path may require saved credentials before opening manual SSO. Architecture Slice: Installer defaults + CLI auth profile/capability model + Swift launch mode labels + README/smoke coverage. Plan Review Gate: Requires PRE review before execution.

Короткий План

🎯 Цель: fresh install, vpn и основная кнопка приложения стартуют с полностью ручного SSO; сохранение LDAP, TOTP, Bitwarden, autosubmit и headless включаются отдельно.

📦 Репозитории: только /Users/dokril/git/home/lemana-vpn.

🧩 Модули: install.sh, bin/vpn-lemanapro.sh, app/Sources/LemanaVPN, tests/smoke.sh, README.md; templates/openconnect-lite-config.toml только если login-only autofill потребует правки правил.

🛠 Основные изменения:

  • Переключить fresh defaults с Bitwarden/auto-first на manual-first.
  • Ввести явный auth profile/capability contract: manual, saved-login, saved-totp, auto.
  • Разрешить Keychain хранить только LDAP username/password без обязательного TOTP seed.
  • Оставить Bitwarden, Touch ID, autosubmit/headless как opt-in функции.
  • Сделать Swift app CLI-wrapper поверх тех же режимов, с manual-first primary action.
  • Зафиксировать всё в smoke-тестах и README.

Проверка: sh -n install.sh, sh -n uninstall.sh, bash -n bin/vpn-lemanapro.sh, sh -n tests/smoke.sh, tests/smoke.sh, swift build -c release --package-path app, git diff --check.

⚠️ Риски: старые пользователи могут ожидать auto-connect на vpn; partial Keychain без TOTP надо проверить fake launch тестом и, при необходимости, видимым manual smoke.

➡️ Следующий шаг: начать с install.sh и зафиксировать новый env/flag contract для manual-first defaults.

Architecture Slice

Files to create:

  • None required for runtime. This goal package is the planning artifact.

Files to modify:

  • install.sh - defaults, wizard wording, flags, env rendering, dry-run output.
  • bin/vpn-lemanapro.sh - auth profile/capability model, default mode, partial Keychain support, JSON events.
  • app/Sources/LemanaVPN/VPNManager.swift - launch mode enum/default, JSON handling if new events are added.
  • app/Sources/LemanaVPN/LemanaVPNApp.swift - menu labels/order so manual-first is primary and auto is secondary.
  • tests/smoke.sh - regression coverage for fresh defaults, manual launch without credentials, partial Keychain, app wiring.
  • README.md - user-facing feature matrix and migration/update instructions.
  • templates/openconnect-lite-config.toml only if login-only filling needs rule adjustments; otherwise avoid.

Files to avoid:

  • libexec/lemana-vpn-dns-cleanup unless DNS behavior breaks.
  • uninstall.sh except if new Keychain account names require cleanup.
  • Direct edits inside installed openconnect-lite; use the existing runtime patch path from bin/vpn-lemanapro.sh.

Source of truth:

  • Repo source for behavior.
  • Installed env file for user-selected capabilities.
  • Keychain for LDAP password and optional TOTP seed.

Read path:

  • install.sh writes ~/.config/lemana-vpn/env.
  • bin/vpn-lemanapro.sh sources env, applies CLI flag overrides, checks Keychain capability presence, then launches openconnect-lite.
  • LemanaVPN.app calls ~/bin/vpn-lemanapro.sh --json <mode>.

Write path:

  • vpn --configure-login or equivalent writes username/password only.
  • vpn --configure-keychain remains the full saved-credentials path and writes password plus permanent TOTP seed.
  • Bitwarden sync, when enabled, writes password plus optional TOTP seed into Keychain.

Contract boundary:

  • CLI flags and JSON events are the public contract for the app and tests.
  • Suggested new runtime contract:
    • auth_profile=manual: visible browser, no preflight, no autofill, no submit.
    • auth_profile=saved-login: visible browser, username/password autofill, OTP manual, no submit.
    • auth_profile=saved-totp: visible browser, username/password/TOTP autofill, no submit.
    • auth_profile=auto: hidden or visible automatic mode, full autofill and submit; explicit only.
    • credential_source=none|keychain|bitwarden: where saved values come from, separate from whether submit/headless are enabled.

Integration points:

  • macOS Keychain service openconnect-lite.
  • Optional Bitwarden CLI item LM LDAP.
  • openconnect-lite config autofill rules.
  • Swift app menu and reconnect behavior.
  • Existing aliases from shell install block.

Migration/cutover:

  • Fresh installs write credential_source=none and auth_profile=manual.
  • Existing credential_source=bitwarden installs continue as saved-totp/auto-capable, but vpn default should still prefer manual-visible unless the user explicitly chooses auto.
  • vpn-auto remains the explicit old behavior.
  • --manual-full remains compatibility alias for the new default manual profile.

Displaced path:

  • Installer Enter=Bitwarden.
  • CLI CONNECT_MODE=auto.
  • Full password+TOTP seed as mandatory preflight for non-manual-full.
  • App primary/default connect action pointing at .auto.

Acceptance evidence gate:

  • A clean temp HOME proves fresh defaults.
  • Captured interactive installer output proves that the default path is understandable without knowing Bitwarden, Keychain, TOTP seed, or credential_source.
  • Fake openconnect-lite launch capture proves display/autofill/click env for each profile.
  • App source/smoke proves primary UI wiring.
  • Installed-bundle verification is required only when refreshing the local app after implementation.

Пользовательский Процесс Установки

Цель этого сценария: человек, далёкий от IT, должен понимать, что сейчас произойдёт, что от него требуется, и что можно пропустить. Installer не должен начинать с технического выбора credential source.

Первый Экран

Текст должен быть коротким:

Lemana VPN установит приложение для подключения к корпоративному VPN.

По умолчанию ничего не сохраняется: при подключении откроется окно входа,
а вы введёте логин, пароль и одноразовый код сами.

Дополнительные удобства можно включить сейчас или позже.

Основное действие по Enter: продолжить с ручным безопасным режимом.

Запрещено на первом экране:

  • credential_source
  • auth_profile
  • TOTP seed
  • openconnect-lite
  • Bitwarden item
  • headless

Шаг 1: Что Будет Установлено

Installer показывает не список пакетов, а понятные пункты:

Будет установлено:
- приложение Lemana VPN в меню macOS;
- команда vpn для подключения из Terminal;
- VPN-компонент, который открывает корпоративную страницу входа;
- безопасная очистка DNS после отключения.

Технические зависимости (openconnect, pipx, openconnect-lite) можно показывать только в подробном логе ниже или в debug/dry-run.

Шаг 2: Базовый Режим Входа

Вопрос:

Как подключаться по умолчанию?

1) Вводить всё вручную (рекомендуется)
   Откроется окно входа. Вы сами введёте логин, пароль и одноразовый код.

2) Запомнить логин и пароль на этом Mac
   При входе логин и пароль подставятся сами. Одноразовый код вы введёте вручную.

3) Настроить автоматический вход
   Нужно заранее сохранить пароль и постоянный код настройки 2FA.

Выбор [1/2/3, Enter=1]:

Поведение:

  • 1 ставит auth_profile=manual, credential_source=none.
  • 2 ставит auth_profile=saved-login, credential_source=keychain, после установки запускает настройку LDAP username/password.
  • 3 открывает следующий уточняющий шаг и не должен быть default.

Шаг 3: Автоматический Вход Только Для Продвинутого Пути

Если выбран пункт 3, installer объясняет риск простым языком:

Автоматический вход работает только если на Mac сохранены:
- корпоративный пароль;
- постоянный код настройки 2FA.

Это не текущие 6 цифр из приложения-аутентификатора.
Если вы не знаете, что это такое, выберите ручной режим.

Варианты:

1) Вернуться к ручному режиму
2) Сохранить пароль и постоянный код 2FA в macOS Keychain
3) Взять пароль и код 2FA из Bitwarden

Поведение:

  • 1 возвращает к auth_profile=manual.
  • 2 ставит auth_profile=saved-totp, credential_source=keychain, запускает full Keychain setup.
  • 3 ставит credential_source=bitwarden; Touch ID спрашивается отдельным простым вопросом.

Шаг 4: Bitwarden Как Дополнительная Функция

Bitwarden не должен выглядеть обязательным. Вопрос появляется только после явного выбора Bitwarden:

Использовать Bitwarden для хранения VPN-пароля?

Подойдёт, если у вас уже есть запись с корпоративным VPN-паролем
и постоянным кодом настройки 2FA.

Если Bitwarden CLI не найден:

Bitwarden не найден на этом Mac.
Можно установить его сейчас или вернуться к ручному режиму.

Default: вернуться к ручному режиму, если пользователь не уверен.

Шаг 5: Touch ID Только После Bitwarden

Вопрос:

Разрешить Touch ID для открытия Bitwarden?

Это не VPN-пароль. Touch ID только помогает достать данные из Bitwarden.

Default: нет, если пользователь не выбирал Bitwarden; да/нет можно спрашивать только в Bitwarden path.

Шаг 6: Понятный Прогресс Установки

Progress log должен говорить человеческим языком:

Проверяю компоненты для VPN...
Устанавливаю приложение Lemana VPN...
Добавляю команду vpn...
Настраиваю безопасное отключение VPN...
Готово.

Если macOS просит пароль администратора, installer заранее объясняет:

macOS может запросить пароль от компьютера.
Он нужен, чтобы разрешить VPN подключаться и корректно очищать DNS после отключения.

Финальный Экран

Для ручного режима:

Готово. Как подключиться:
1. Откройте Lemana VPN из Applications или меню macOS.
2. Нажмите "Подключить".
3. В открывшемся окне введите корпоративный логин, пароль и одноразовый код.

Проверить состояние: vpn --status

Для режима с сохранённым логином и паролем:

Готово. При подключении логин и пароль будут подставлены автоматически.
Одноразовый код нужно ввести вручную.

Для автоматического режима:

Готово. Автоматический вход включён.
Если он не сработает, используйте ручной режим: vpn --manual-full

Ошибки И Подсказки

Ошибки должны говорить, что делать дальше:

  • Нет сохранённого пароля: Запустите vpn --configure-login или подключитесь вручную.
  • Нет постоянного кода 2FA для auto mode: Автоматический вход не настроен. Выберите ручной режим или добавьте постоянный код настройки 2FA.
  • Bitwarden заблокирован в приложении без Terminal: Откройте Terminal и выполните vpn, либо подключитесь вручную.
  • Пользователь выбрал непонятный вариант: Введите 1, 2 или 3. Если не уверены, нажмите Enter.

Acceptance Evidence Для Installer UX

  • Interactive dry-run через expect должен захватить первый экран, выбор режима и финальную подсказку.
  • Smoke должен проверять, что Enter выбирает ручной режим.
  • README должен показывать тот же сценарий, что и installer.
  • В обычном wizard не должно быть слов credential_source, auth_profile, TOTP seed; они допустимы только в help/debug/docs for advanced.

Дополнительные Функции

  1. Core manual SSO (default)

    • Что делает: открывает видимый браузер Keycloak, не читает Bitwarden/Keychain, не заполняет поля, не нажимает submit.
    • Когда включается: всегда на fresh install и при vpn без дополнительных настроек.
    • Когда не включается: если пользователь явно запускает vpn-auto или выбрал auto profile.
  2. Saved LDAP login/password in macOS Keychain

    • Что делает: сохраняет LDAP username/password в Keychain и подставляет их в видимую форму.
    • Когда включается: пользователь явно запускает настройку сохранения LDAP или выбирает этот пункт в installer.
    • OTP: текущий одноразовый код вводится руками; TOTP seed не требуется.
  3. Saved TOTP seed

    • Что делает: сохраняет постоянный TOTP seed и позволяет openconnect-lite генерировать текущий код для autofill.
    • Когда включается: пользователь явно добавил seed через full Keychain setup или Bitwarden sync.
    • Ограничение: текущий 6-значный код нельзя сохранять как настройку.
  4. Autofill

    • Что делает: заполняет те поля, для которых есть capability: username/password, а затем optional TOTP.
    • Когда включается: при saved-login или saved-totp.
    • Submit: по умолчанию выключен; пользователь нажимает вход сам.
  5. Autosubmit/headless

    • Что делает: запускает старый удобный путь с hidden browser, полным autofill и нажатием submit.
    • Когда включается: только явным vpn-auto, --auto, profile auto или отдельным opt-in флагом.
    • Предусловие: saved login/password и TOTP seed должны быть доступны; без TOTP seed этот режим должен fail fast с понятной ошибкой.
  6. Bitwarden sync

    • Что делает: читает LDAP password и TOTP seed из Bitwarden item, нормализует TOTP seed и перекладывает в Keychain.
    • Когда включается: только если пользователь выбрал Bitwarden provider.
    • Fallback: если bw отсутствует или locked в GUI/no-tty, использовать уже сохранённый Keychain либо manual path, не блокировать default.
  7. Touch ID helper

    • Что делает: локально защищает чтение Bitwarden master password.
    • Когда включается: только вместе с Bitwarden provider и явным согласием пользователя.
    • Не делает: не заменяет LDAP password и не является хранением VPN credentials.

Tasks

Task 1: Rewrite user-facing contract and docs

  • Scope: README.md.
  • Change: describe manual-first default, feature matrix, migration from old Bitwarden-first install, and the non-IT installation scenario from this plan.
  • Expected output: docs no longer say Bitwarden/auto are default; examples show vpn as manual and vpn-auto as opt-in; installation instructions use the same wording as the installer.
  • Verification: rg -n "по умолчанию|Bitwarden|vpn-auto|manual-full|одноразовый код|ручной режим" README.md.
  • Acceptance evidence: a new user can read the first install section and understand what will be installed, what they will type during first connect, and that Bitwarden is optional.
  • Parallel: yes, but must be reconciled with installer flag names from Task 2.

Task 2: Change installer defaults and capability selection

  • Scope: install.sh.
  • Change: default credential source becomes none/manual; Bitwarden and Touch ID default off; wizard Enter keeps manual; add or reuse flags for saved LDAP, full Keychain, Bitwarden provider, and auto profile; rewrite interactive copy using the "Пользовательский Процесс Установки" section.
  • Expected output: dry-run shows manual/default auth and no Bitwarden/Touch ID install unless selected; interactive prompts say "вводить всё вручную", "запомнить логин и пароль", and "автоматический вход" instead of exposing low-level config names.
  • Verification: sh -n install.sh; sh install.sh --dry-run --non-interactive; interactive dry-run through expect if available.
  • Acceptance evidence: clean install dry-run does not try to install Bitwarden and does not schedule Keychain credential prompts by default; captured interactive output is understandable without IT vocabulary and Enter chooses manual mode.
  • Parallel: no, because it defines env contract consumed by CLI/tests.

Task 3: Refactor CLI auth profiles and partial Keychain support

  • Scope: bin/vpn-lemanapro.sh.
  • Change: replace auto-first/default credential preflight with an auth-profile resolver; add partial Keychain support for saved login without TOTP; keep full Keychain and Bitwarden sync as opt-ins.
  • Expected output: no-credential default reaches visible manual browser; saved-login fills username/password and leaves OTP manual; saved-totp can fill OTP but still does not submit by default.
  • Verification: bash -n bin/vpn-lemanapro.sh; fake openconnect-lite capture in tests/smoke.sh.
  • Acceptance evidence: JSON events clearly state active auth profile and missing optional capabilities without treating them as fatal in manual profiles.
  • Parallel: no, central truth owner.

Task 4: Preserve automatic compatibility without keeping it dominant

  • Scope: bin/vpn-lemanapro.sh, install.sh, shell aliases.
  • Change: keep vpn-auto/--auto as explicit old behavior; guard autosubmit/headless behind full saved credentials; keep --manual-full compatibility alias for the default manual profile.
  • Expected output: current power users can still run the old path intentionally.
  • Verification: tests/smoke.sh assertions for vpn-auto, --auto, --manual-full, launch env.
  • Acceptance evidence: default path and auto path are both test-covered and visibly separate.
  • Parallel: can run after Task 3.

Task 5: Update Swift menu-bar app surface

  • Scope: app/Sources/LemanaVPN/VPNManager.swift, app/Sources/LemanaVPN/LemanaVPNApp.swift.
  • Change: make the primary menu action manual-first; demote automatic connect to an explicit secondary action; keep app as a CLI wrapper; surface missing saved credentials as setup guidance, not hidden prompts.
  • Expected output: app default action does not trigger Bitwarden or autosubmit unless the user chooses an explicit auto/saved mode.
  • Verification: swift build -c release --package-path app; smoke greps for the new labels/mode wiring.
  • Acceptance evidence: app source and built binary contain the manual-first label and map it to the manual profile.
  • Parallel: yes after CLI mode names are stable.

Task 6: Expand smoke coverage

  • Scope: tests/smoke.sh.
  • Change: update old assertions and add cases for fresh manual default, saved-login-only, saved-totp, Bitwarden opt-in, and app wiring.
  • Expected output: smoke becomes the executable spec for the feature matrix and installer UX.
  • Verification: sh -n tests/smoke.sh; tests/smoke.sh.
  • Acceptance evidence: smoke fails if Bitwarden or auto become fresh defaults again, or if the basic wizard no longer defaults to manual mode with non-technical wording.
  • Parallel: yes after Task 2 and Task 3 contracts are known.

Task 7: Final verification and local update notes

  • Scope: whole repo; installed app only if doing a local refresh.
  • Change: run non-live ladder, then optionally install/update local checkout with sh install.sh after user approval for real local changes.
  • Expected output: plan implemented with evidence and a clear "live VPN not run" or live manual evidence.
  • Verification:
    • sh -n install.sh
    • sh -n uninstall.sh
    • bash -n bin/vpn-lemanapro.sh
    • sh -n tests/smoke.sh
    • tests/smoke.sh
    • swift build -c release --package-path app
    • git diff --check
  • Acceptance evidence: final answer includes how to run, how to check, how to update installed setup, and what live checks were intentionally skipped.
  • Parallel: no, final integration gate.

Non-Goals

  • Do not replace openconnect-lite or implement a separate VPN client in Swift.
  • Do not store current 6-digit OTP codes.
  • Do not make the GUI collect secrets directly.
  • Do not remove existing automatic mode; demote it to explicit opt-in.
  • Do not change DNS cleanup behavior as part of this refactor.

Risks

  • Existing users may expect vpn to auto-connect. Mitigation: keep vpn-auto and document the cutover clearly.
  • Partial Keychain support may need careful openconnect-lite behavior verification when TOTP is absent. Mitigation: fake launch tests first, then one visible manual smoke if needed.
  • Swift app could lag behind installed source again. Mitigation: after implementation refresh installed bundle via sh install.sh and verify with app source/build or strings if doing local install.
  • Credential language can confuse Bitwarden master password, LDAP password, TOTP seed, and current OTP. Mitigation: keep labels explicit in CLI and README.

Execution Order

  1. Task 2 defines the installer/env contract.
  2. Task 3 implements the CLI truth owner.
  3. Task 4 preserves explicit auto compatibility.
  4. Task 5 updates the app wrapper.
  5. Task 6 turns the matrix into smoke coverage.
  6. Task 1 updates docs with final flag/profile names.
  7. Task 7 verifies and prepares local update instructions.