feat(auth): alert users on a new-country login (BUNYIP-366) #369
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/BUNYIP-366-login-location-alerts"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Detects a country-level login-location change and emails the user, per BUNYIP-366 (country-level MVP; region/ASN deferred).
On a genuine login the client IP is resolved to an ISO 3166-1 alpha-2 country via an offline IP2Location LITE database and compared to the last country recorded for the user. On a change (and only when the user has not opted out) mokosh sends a "new sign-in from " email, then persists the new country. The first geolocatable login records the country silently, with no alert.
The whole check is best-effort: every failure (DB miss, unresolvable IP, email send error) is logged and swallowed so it can never block a login, and it no-ops entirely when
IP2LOCATION_DB_PATHis unset or the.BINfails to load (kill switch).Design
GeoIpService(crates/bunyip-domain/src/services/geoip.rs) wraps the IP2Location DB, the same LITE.BINthe ecosystem already deploys for dmarc-reporter (/data/IP2LOCATION-LITE-DB11.BIN). Lookups are fully offline: no per-login external call, no client IP sent to a third party.AuthService::check_login_locationruns off the existingUserRepository::update_last_loginmarker at every genuine login-success branch: password, trusted-device 2FA skip, magic link, 2FA completion, and both admin-invite-accept paths. It deliberately does not hookcreate_tokens, which is shared with silent refresh and would fire on every token rotation.EmailServiceis now constructed ahead ofAuthServiceinmain.rsso it can be injected;GeoIpServiceis built from the optional config path and passed in asOption<Arc<..>>.Schema
New migration
20260714000010_add_login_location_alerts.sqladds tousers:last_login_countryTEXT, nullable (ISO2, capped at 64 chars), the last country seen for the user.login_location_alertsBOOLEAN NOT NULL DEFAULT TRUE, the per-user opt-out.Config
IP2LOCATION_DB_PATH(optional) ->Config.ip2location_db_path. Unset disables the feature.Tests
geoip::tests: country-code normalization rejects IP2Location's"-"placeholder and blanks, trims and keeps real ISO2 codes.auth::tests::non_public_ip_detection: loopback / RFC1918 / link-local / unique-local / unspecified are treated as non-public; routable v4/v6 are public.just check-containergreen: fmt + clippy (-D warnings) + workspace lib tests.Deploy note
The email alert only sends where
IP2LOCATION_DB_PATHpoints at a readable LITE DB11.BIN; otherwise the feature stays dormant with a single startup log line. No behavior change for deployments that do not set it.#BUNYIP-366
🤖 Generated with Claude Code