Conversation
Replace the per-IP MFA gate with a country-based one. A new IP inside a country the user has already signed in from is trusted silently; only a new country defers the session to the /verify-ip dual-factor step-up. A new device (identified by a signed kh_device_id cookie) signing in from a trusted country is allowed through but the account owner is emailed a warning. The account's first device is not emailed. Transition is seamless: user_trusted_ips stays as a legacy bridge. On a country miss, a session whose IP is already trusted passes and the country is backfilled into user_trusted_countries, so users signed in before this shipped are never bounced to /verify-ip. The proxy adopts such browsers with a device cookie on the next document navigation, silently. IP normalization (/24, /64) is now used only for the /verify-ip replay match; the full IP is persisted, shown in the active-sessions panel, carried in the pending cookie, and sent in emails. Adds user_trusted_countries and user_trusted_devices tables (migration 0105) plus unit tests for the country gate, device cookie, device trust, and new-device notification.
Address code review on the country/device trust change: - resolveCfCountry now requires the CF-set cf-connecting-ip before honoring cf-ipcountry, so a forged country header on a direct-to-origin request cannot pin a trusted country (mirrors resolveLoginLocation). - Replace the `ip ?? ""` fallbacks in strict-signin and the proxy gate with explicit IP narrowing: when an untrusted country has no resolvable IP (impossible behind CF) we pass rather than issue a replay-unbindable verify cookie. - Add proxy-layer tests for the country gate: trusted/no_country pass, untrusted page redirect to /verify-ip, untrusted API 403, the no-IP pass, document-nav device adoption, and the non-document fan-out guard. - Add a regression test for the forged-cf-ipcountry case.
Let orgs subscribe to any combination of daily/weekly/monthly digests instead of a single cadence, and rework the digest email layout. - Migrate digest settings from a single cadence column to a cadences set plus a per-cadence last_sent map so each schedule sends independently - Add a monthly cadence that fires on the 1st (UTC) and reports the previous calendar month, aligned to the billing cycle - Cron evaluates each subscribed cadence per org and only advances that cadence's last-sent on a successful send - Settings UI: cadence dropdown becomes daily/weekly/monthly checkboxes - Email: explicit UTC period range, distinct-workflow count, success and failure as count plus percentage, grouped Succeeded/Failed sections, inline truncated error with hover tooltip, and a social-icon footer delivered as inline (CID) attachments
feat: trust sign-ins by country and device instead of raw IP
# Conflicts: # drizzle/meta/_journal.json
…ti-cadence feat: multi-cadence execution digest with redesigned email
… the deploy
The docs-site Helm values set the image tag as a bare scalar (tag:
${IMAGE_TAG}). The deploy workflow substitutes the raw git short SHA via
sed. When a short SHA is all decimal digits (no a-f), the bare YAML scalar
is parsed as a number and re-serialised in scientific notation (e.g.
6801028 -> 6.801028e+06), producing an invalid Docker tag and an
InvalidImageName pod failure.
Quote the tag in the prod, staging and techops-prod values files so the
substituted value is always a string scalar, matching the already-quoted
deploy/local template. Other services are immune because they prefix the
tag with letters (app-, event-, executor-, etc.).
…e-tag fix(ci): quote docs-site image tag so numeric short SHAs do not break the deploy
The scheduled k6 load test signs up VUs against /api/auth/sign-up/email which is gated by the Turnstile captcha plugin and cannot solve a real captcha challenge from a headless runner. Wrap the plugin's onRequest to honor an X-Load-Test-Captcha-Bypass header whose value matches LOAD_TEST_CAPTCHA_BYPASS_TOKEN (timing-safe compared via node:crypto). When the env var is unset the wrapper is a no-op and no bypass path exists, so prod is unchanged. - lib/auth.ts: withLoadTestBypass wrapper around the captcha plugin - deploy/keeperhub/staging/values.yaml: mount the env var from SSM - tests/k6/helpers/http.js + ramp-until-breach.sh: send the bypass header on every signup when the k6 env var is set - .github/workflows/load-test.yml: pass the staging-env GH Actions secret to k6 for both execution and http-ramp modes - tests/unit/signup-defenses.test.ts: 5 new tests covering matched, unmatched same-length, wrong-length, missing-header, and env-unset cases - .env.example: document the new env var Pair PR in techops-services/infrastructure provisions the SSM parameter.
…under GitHub Actions
…pass feat(auth): add load-test bypass header for signup captcha
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.