28 KiB
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.tomlonly if login-only filling needs rule adjustments; otherwise avoid.
Files to avoid:
libexec/lemana-vpn-dns-cleanupunless DNS behavior breaks.uninstall.shexcept if new Keychain account names require cleanup.- Direct edits inside installed
openconnect-lite; use the existing runtime patch path frombin/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.shwrites~/.config/lemana-vpn/env.bin/vpn-lemanapro.shsources env, applies CLI flag overrides, checks Keychain capability presence, then launchesopenconnect-lite.LemanaVPN.appcalls~/bin/vpn-lemanapro.sh --json <mode>.
Write path:
vpn --configure-loginor equivalent writes username/password only.vpn --configure-keychainremains 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-liteconfig autofill rules.- Swift app menu and reconnect behavior.
- Existing aliases from shell install block.
Migration/cutover:
- Fresh installs write
credential_source=noneandauth_profile=manual. - Existing
credential_source=bitwardeninstalls continue as saved-totp/auto-capable, butvpndefault should still prefer manual-visible unless the user explicitly chooses auto. vpn-autoremains the explicit old behavior.--manual-fullremains 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-litelaunch 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_sourceauth_profileTOTP seedopenconnect-liteBitwarden itemheadless
Шаг 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.
Дополнительные Функции
-
Core manual SSO (default)
- Что делает: открывает видимый браузер Keycloak, не читает Bitwarden/Keychain, не заполняет поля, не нажимает submit.
- Когда включается: всегда на fresh install и при
vpnбез дополнительных настроек. - Когда не включается: если пользователь явно запускает
vpn-autoили выбрал auto profile.
-
Saved LDAP login/password in macOS Keychain
- Что делает: сохраняет LDAP username/password в Keychain и подставляет их в видимую форму.
- Когда включается: пользователь явно запускает настройку сохранения LDAP или выбирает этот пункт в installer.
- OTP: текущий одноразовый код вводится руками; TOTP seed не требуется.
-
Saved TOTP seed
- Что делает: сохраняет постоянный TOTP seed и позволяет
openconnect-liteгенерировать текущий код для autofill. - Когда включается: пользователь явно добавил seed через full Keychain setup или Bitwarden sync.
- Ограничение: текущий 6-значный код нельзя сохранять как настройку.
- Что делает: сохраняет постоянный TOTP seed и позволяет
-
Autofill
- Что делает: заполняет те поля, для которых есть capability: username/password, а затем optional TOTP.
- Когда включается: при
saved-loginилиsaved-totp. - Submit: по умолчанию выключен; пользователь нажимает вход сам.
-
Autosubmit/headless
- Что делает: запускает старый удобный путь с hidden browser, полным autofill и нажатием submit.
- Когда включается: только явным
vpn-auto,--auto, profileautoили отдельным opt-in флагом. - Предусловие: saved login/password и TOTP seed должны быть доступны; без TOTP seed этот режим должен fail fast с понятной ошибкой.
-
Bitwarden sync
- Что делает: читает LDAP password и TOTP seed из Bitwarden item, нормализует TOTP seed и перекладывает в Keychain.
- Когда включается: только если пользователь выбрал Bitwarden provider.
- Fallback: если
bwотсутствует или locked в GUI/no-tty, использовать уже сохранённый Keychain либо manual path, не блокировать default.
-
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
vpnas manual andvpn-autoas 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 throughexpectif 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; fakeopenconnect-litecapture intests/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/--autoas explicit old behavior; guard autosubmit/headless behind full saved credentials; keep--manual-fullcompatibility alias for the default manual profile. - Expected output: current power users can still run the old path intentionally.
- Verification:
tests/smoke.shassertions forvpn-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.shafter 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.shsh -n uninstall.shbash -n bin/vpn-lemanapro.shsh -n tests/smoke.shtests/smoke.shswift build -c release --package-path appgit 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-liteor 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
vpnto auto-connect. Mitigation: keepvpn-autoand document the cutover clearly. - Partial Keychain support may need careful
openconnect-litebehavior 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.shand verify with app source/build orstringsif 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
- Task 2 defines the installer/env contract.
- Task 3 implements the CLI truth owner.
- Task 4 preserves explicit auto compatibility.
- Task 5 updates the app wrapper.
- Task 6 turns the matrix into smoke coverage.
- Task 1 updates docs with final flag/profile names.
- Task 7 verifies and prepares local update instructions.