[package]
name = "loginflow"
version = "0.1.1"
edition = "2021"
rust-version = "1.88"
license = "MIT OR Apache-2.0"
authors = ["Santh Project <contact@santh.dev>"]
repository = "https://github.com/santhreal/loginflow"
homepage = "https://santh.dev"
description = "Browser-driven login discovery, form drive, MFA, and session capture into authjar"
readme = "README.md"
keywords = ["login", "authentication", "session", "browser", "mfa"]
categories = ["authentication", "web-programming"]
publish = true
[package.metadata.santh]
status = "beta"
[dependencies]
authjar = { path = "../authjar", version = "0.2.5" }
runtime-headless = { path = "../headless", version = "0.1.1", optional = true }
scraper = "=0.26.0"
url = "=2.5.8"
serde = { version = "=1.0.228", features = ["derive"] }
serde_json = "1"
thiserror = "=2.0.18"
tokio = { version = "=1.51.1", features = ["macros", "rt", "rt-multi-thread", "time", "net", "io-util"] }
secrecy = { version = "=0.10.3", features = ["serde"] }
zeroize = "1.9"
reqwest = { version = "=0.12.28", default-features = false, features = ["cookies", "json", "rustls-tls"] }
base32 = "=0.5.1"
totp-lite = "=2.0.1"
async-trait = "=0.1.89"
guise = { path = "../guise", version = "0.1.3", optional = true, default-features = false, features = ["fingerprint", "human"] }
toml = "=0.8.23"
clap = { version = "=4.6.1", features = ["derive"] }
santh-cli = { path = "../../general/santh-cli", version = "0.1.1" }
santh-tracing = { path = "../../general/santh-tracing", version = "0.2.1" }
secfinding = { path = "../../scanner/secfinding", version = "0.4.1" }
tracing = { workspace = true }
captchaforge = { version = "=0.2.36", optional = true, default-features = false }
[features]
default = []
browser = ["dep:runtime-headless", "guise?/browser"]
stealth = ["dep:guise", "guise/fingerprint", "guise/http"]
captchaforge = ["dep:captchaforge", "browser"]
[dev-dependencies]
tokio = { version = "=1.51.1", features = ["macros", "rt-multi-thread", "net", "io-util"] }
wiremock = "=0.6.5"
proptest = "=1.9.0"
assert_cmd = "2"
criterion = { workspace = true }
rustls = { workspace = true, features = ["ring"] }
tokio-rustls = { workspace = true }
rcgen = { workspace = true }
[[bench]]
name = "login_bench"
harness = false
[lints.rust]
unsafe_code = "forbid"
[[test]]
name = "property_decode_rejects_strings_without_two_dots"
path = "tests/property/decode_rejects_strings_without_two_dots.rs"
[[test]]
name = "property_does_not_panic_on_any_string"
path = "tests/property/does_not_panic_on_any_string.rs"
[[test]]
name = "gap_mfa_email_link"
path = "tests/gap/mfa_email_link.rs"
[[test]]
name = "integration_spa_hydration"
path = "tests/integration/spa_hydration.rs"
[[test]]
name = "integration_missing_host_rejected"
path = "tests/integration/missing_host_rejected.rs"
[[test]]
name = "unit_ignores_privacy_footer_link"
path = "tests/unit/ignores_privacy_footer_link.rs"
[[test]]
name = "unit_register_bearer"
path = "tests/unit/register_bearer.rs"
[[test]]
name = "unit_custom_hydration_wait_can_be_configured"
path = "tests/unit/custom_hydration_wait_can_be_configured.rs"
[[test]]
name = "unit_credentials_toml_parses_username_password_and_totp"
path = "tests/unit/credentials_toml_parses_username_password_and_totp.rs"
[[test]]
name = "unit_skips_search_style_username_fields"
path = "tests/unit/skips_search_style_username_fields.rs"
[[test]]
name = "unit_detects_totp_and_marks_not_http_simple"
path = "tests/unit/detects_totp_and_marks_not_http_simple.rs"
[[test]]
name = "unit_loginflow_builder_accepts_captcha_solver"
path = "tests/unit/loginflow_builder_accepts_captcha_solver.rs"
[[test]]
name = "unit_respond_to_webauthn_challenge_success"
path = "tests/unit/respond_to_webauthn_challenge_success.rs"
[[test]]
name = "unit_best_entry_is_deterministic"
path = "tests/unit/best_entry_is_deterministic.rs"
[[test]]
name = "unit_decode_invalid_json"
path = "tests/unit/decode_invalid_json.rs"
[[test]]
name = "unit_oauth_drive"
path = "tests/unit/oauth_drive.rs"
[[test]]
name = "unit_mfa_sms_relay"
path = "tests/unit/mfa_sms_relay.rs"
[[test]]
name = "unit_register_bearer_without_prefix"
path = "tests/unit/register_bearer_without_prefix.rs"
[[test]]
name = "unit_extract_invalid_bearer_ignored"
path = "tests/unit/extract_invalid_bearer_ignored.rs"
[[test]]
name = "unit_discovers_username_password_and_csrf"
path = "tests/unit/discovers_username_password_and_csrf.rs"
[[test]]
name = "unit_discovers_google_and_github_providers"
path = "tests/unit/discovers_google_and_github_providers.rs"
[[test]]
name = "unit_decode_valid_jwt"
path = "tests/unit/decode_valid_jwt.rs"
[[test]]
name = "unit_decode_invalid_base64"
path = "tests/unit/decode_invalid_base64.rs"
[[test]]
name = "unit_known_hotp_slice_matches_rfc4226_step_0"
path = "tests/unit/known_hotp_slice_matches_rfc4226_step_0.rs"
[[test]]
name = "unit_totp_is_deterministic_for_fixed_step"
path = "tests/unit/totp_is_deterministic_for_fixed_step.rs"
[[test]]
name = "unit_respond_to_webauthn_challenge_error"
path = "tests/unit/respond_to_webauthn_challenge_error.rs"
[[test]]
name = "unit_mfa_email_link"
path = "tests/unit/mfa_email_link.rs"
[[test]]
name = "unit_extract_auth_headers"
path = "tests/unit/extract_auth_headers.rs"
[[test]]
name = "unit_register_bearer_invalid"
path = "tests/unit/register_bearer_invalid.rs"
[[test]]
name = "unit_scald_fastpath"
path = "tests/unit/scald_fastpath.rs"
[[test]]
name = "unit_captcha_dispatch"
path = "tests/unit/captcha_dispatch.rs"
[[test]]
name = "unit_gitlab_oauth_path_matches"
path = "tests/unit/gitlab_oauth_path_matches.rs"
[[test]]
name = "unit_discover_webauthn_registration"
path = "tests/unit/discover_webauthn_registration.rs"
[[test]]
name = "unit_default_hydration_wait_is_reasonable"
path = "tests/unit/default_hydration_wait_is_reasonable.rs"
[[test]]
name = "unit_decode_invalid_structure"
path = "tests/unit/decode_invalid_structure.rs"
[[test]]
name = "unit_session_json_roundtrip_scald_auth"
path = "tests/unit/session_json_roundtrip_scald_auth.rs"
[[test]]
name = "unit_totp_changes_across_steps"
path = "tests/unit/totp_changes_across_steps.rs"
[[test]]
name = "unit_discover_no_webauthn"
path = "tests/unit/discover_no_webauthn.rs"
[[test]]
name = "unit_resolves_relative_oauth_href"
path = "tests/unit/resolves_relative_oauth_href.rs"
[[test]]
name = "unit_discover_webauthn_authentication"
path = "tests/unit/discover_webauthn_authentication.rs"
[[test]]
name = "unit_auth0_authorize_href_matches"
path = "tests/unit/auth0_authorize_href_matches.rs"
[[test]]
name = "concurrent_loginflow_concurrent"
path = "tests/concurrent/loginflow_concurrent.rs"
[[test]]
name = "contract_cli_fails_on_missing_args"
path = "tests/contract/cli_fails_on_missing_args.rs"
[[test]]
name = "contract_cli_shows_help"
path = "tests/contract/cli_shows_help.rs"
[[test]]
name = "adversarial_discover_webauthn_malformed_html"
path = "tests/adversarial/discover_webauthn_malformed_html.rs"
[[test]]
name = "adversarial_discover_empty_html"
path = "tests/adversarial/discover_empty_html.rs"
[[test]]
name = "adversarial_mfa_sms_relay"
path = "tests/adversarial/mfa_sms_relay.rs"
[[test]]
name = "adversarial_discover_js_obfuscation"
path = "tests/adversarial/discover_js_obfuscation.rs"
[[test]]
name = "regression_redaction"
path = "tests/regression/redaction.rs"