Matthew Meszaros
75f764dc67
feat: require a verified Cloud Tasks token with a pinned audience on task webhooks, manage_settings on integration OAuth and a fresh membership check at every mailbox and integration OAuth finish, user verification for passkey sign-in, ended sessions before a password reset or ban reports success, and a revoked session when a rotated refresh token is replayed; remove the internal DEK delete route, log credential path parameters by name, hold remote images in received mail until the reader loads them, add Calendly and Cal.com signing keys with inbound URL rotation, keep automation signing secrets out of connection responses, and apply the password rules to the bootstrap password
2026-09-30 06:46:24 -07:00
Matthew Meszaros
86c5a06893
feat: disconnect a removed member's realtime sockets from the event broadcaster so campaign and account channels end too, and keep invalidating the remaining cached sessions when one invalidation fails
2026-09-26 21:30:03 -07:00
Matthew Meszaros
2df8991db5
feat: scope every session request to a workspace its user is still a member of, deselect a removed member's workspace on all their sessions, disconnect their realtime sockets on removal, and correct the singular /organization paths in the API docs
2026-09-26 21:18:49 -07:00
Matthew Meszaros
e1989f9116
Merge remote-tracking branch 'origin/main' into fix/password-change-session-revocation
...
# Conflicts:
# internal/app/auth/reset_password.go
2026-09-21 04:56:50 -07:00
Matthew Meszaros
db0f0c6132
feat: carry the caller's session into ReissueSession from the request instead of looking it up, evict every revoked session from the cache and write a revoked tombstone where the delete is refused, and answer a password change whose reissue failed with the distinct 409 password_changed_sign_in_again that the dashboard turns into a sign-out, documented in error-codes, the endpoint reference and OpenAPI
2026-09-21 04:23:46 -07:00
Matthew Meszaros
0f60fd9b84
feat: attach a Google, Apple or OIDC identity to an existing password account only after that account's password is presented: resolveFederatedUser parks the sign-in as link_required with a single-use sso_link pending token, POST /auth/sso/link checks the password against the provider-asserted address on the sign-in failure budget and links then issues the session through finishLoginAs, the dashboard collects it on a new login step, and the API reference, endpoints list, security guide and OpenAPI spec describe the third login result
2026-09-21 03:25:29 -07:00
Matthew Meszaros
7626aaefe4
feat: end every session on a password change, the calling one included, and answer POST /auth/me/password with the token pair of a fresh session for that device; the dashboard stores the new pair, and the endpoint reference, security guide and OpenAPI document the response
2026-09-21 03:18:27 -07:00
Matthew Meszaros
e668a2a36b
feat: complete the ADA CASA v2.1.1 AL1 control set across authentication, sessions, access control, cryptography, input validation and configuration, adding a breached-password denylist and per-account login throttling, enforced multi-factor authentication on the admin panel, step-up confirmation before an action that mints a lasting credential, purpose-scoped session tokens, single-use TOTP steps, tenant verification on every cross-referenced identifier, security headers on every surface, encrypted webhook signing secrets, per-organization idempotency, PKCE and a minimal two-scope Gmail consent on the mailbox OAuth flow, bounded spreadsheet and archive decoding, a patched Go toolchain with govulncheck in CI, and the evidence pack under compliance/casa
2026-09-19 08:18:35 +02:00
Matthew Meszaros
ddd76eeb73
fix: stop a Redis outage locking everyone out, and let the tracking encoder register its schema ( #537 )
...
* feat: treat a session cache that cannot answer as a miss rather than an authentication failure, because the session lives in Postgres and reading it from there is what the caller already falls back to, so a Redis outage costs a database read per request instead of locking every user out of the dashboard with a 500
* feat: carry the tracking event schema with the subject-name strategy so the encoder registers it instead of only looking one up, because a registry with no tracking-events subject had nothing to find and answered every event with 'Could not get id from response', dropping every open and click on the floor
2026-09-15 21:19:03 -07:00
Matthew Meszaros
92e298b6ec
fix: clear every open issue in error tracking by fixing the bugs behind them rather than the reports: a document-level mouseleave handing RippleProvider the document itself, whose classList is undefined; the admin panel posting /getaway without the /v1 its baseURL omits, so its realtime socket 404d on every page; Gmail throttles classified from the 403 status alone and told to re-authorize instead of back off; consumer flag and folder events retrying forever on a message the unibox never stored; a lost token-refresh race answered 500 instead of the documented 401; a nil email_accounts slice crashing the admin user page; Turnstile mounted with an empty sitekey; conditional passkey autofill run after the user navigated away; a boot log filed as an issue; and one publish failure per message on a topic the broker refuses ( #519 )
2026-09-14 21:06:14 -07:00
Matthew Meszaros
51dedc90ee
feat: put every runtime behind one optional error-reporting story: a single internal/observability/errs wrapper that is now the only package importing sentry-go, InitSentry for cmd/forms, release and environment tags on every service from the existing build stamp, optional Sentry in the admin panel and the public forms app, the sentry crate in the Rust tracking service, release tagging in realtime, CI source-map upload that only runs when a Sentry token is configured, and docs covering the DSN for each service
2026-09-07 03:51:06 -07:00
Matthew Meszaros
9ab42cfd65
feat: build the browser half of social sign-in, which was never wired: GOOGLE_CLIENT_ID was read at boot and made the login screen render a Google button, but the button opened a popup at /auth/google/login which no route served, and authService.GoogleAuth/AppleAuth had no caller anywhere in the codebase; internal/app/socialauth now runs Google and Apple through the flow generic OIDC already used (one-time state, PKCE, nonce, id_token verified against the provider JWKS, identity keyed on issuer and subject, JIT provisioning, the ban and 2FA gates), the redirect URI defaults to API_PUBLIC_URL/v1/auth/<provider>/callback and is logged at boot because registering the dashboard origin instead is the mistake that produces a valid OAuth client and a dead button, /auth/config advertises only providers the backend can actually complete, the SSO landing page no longer swallows a two_fa_required response, and OIDC_PROVIDER_NAME finally reaches the button it documents
2026-08-28 01:33:08 -07:00
Matthew Meszaros
1018068942
feat: stop recipient suppression and the entitlement gate being skipped when a campaign has no organization: the send path now fails closed (an orgless campaign is paused with the reason in its activity feed instead of mailing an unsubscribed, bounced or complained address, since routing's own suppression filter joins on the campaign's organization_id and matches nothing when it is NULL), the same tenancy gate covers warmup and unibox sends and an orgless mailbox resolves to the free pool rather than defaulting into the paid one, the state is no longer creatable (sequenceRepository.Create inherits organization_id from its campaign instead of inserting NULL, campaign creation and mailbox onboarding refuse without a workspace via errx.ErrNoOrganization, guardInboxLimit / guardMailboxThrottle / the campaign creation throttle no longer treat a missing org as exempt, and a new session resolves the user's default workspace so the orgless session that produced these rows cannot exist), and migration 000092 backfills then sets organization_id NOT NULL on campaigns, contacts, email_accounts and sequences plus sessions.current_organization_id, provisioning a recovery workspace for any user with none so no row is deleted to satisfy the constraint (live-tested in TestLiveOrglessCampaignDoesNotSendToSuppressedRecipient, TestLiveSuppressedRecipientIsSkipped, TestLiveCampaignRequiresAnOrganization and TestLiveHealthyCampaignStillSends)
2026-08-24 08:47:08 -07:00
Matthew Meszaros
5bcc2baaa8
feat: implement the coming-soon security features — logged-in change-password (verify current, policy-checked, POST /me/password) with a real dialog, and new-device sign-in alerts (security notification category fired from the token service on an unrecognized OS+browser, delivered in-app and by email), removing the comingSoon stub helper and updating docs
2026-06-11 12:30:44 +02:00
Matthew Meszaros
218af13dd9
feat: add session management
...
Add authenticated session listing and revocation APIs, track the auth provider on sessions, and expose active session controls in account security settings.
2026-06-01 05:53:49 +02:00
Matthew Meszaros
87f9bf58b0
feat: add Safari-ready passkey login
...
Add passkey enrollment and login wiring, including a Safari-safe explicit login path that prefetches the WebAuthn challenge before the click and calls the credential ceremony immediately from the user gesture.
2026-06-01 03:09:17 +02:00
Matthew Meszaros
17ce31a942
feat: extend dashboard session lifetime
2026-05-30 14:06:27 +00:00
Matthew Meszaros
8267458f4e
fix(auth): invalidate cached session after refresh — was logging users out at 10 min
...
After /auth/refresh, Postgres got the new access + refresh nonces but
the Redis cached session still held the OLD ones. The next request:
1. Frontend uses the new access token (new access_nonce in JWT)
2. Backend ValidateAccessToken → GetSession → hits Redis cache
3. Cached session has the OLD access_nonce
4. session.AccessNonce != t.Nonce → ErrToken (401)
5. Frontend tries to refresh with the new refresh token
6. RefreshToken → GetSession → again hits stale Redis
7. sess.RefreshNonce (old) != t.Nonce (new) → ErrToken
8. Frontend clears tokens and bounces to /auth/login
The access token's 10-minute TTL was the trigger window because that's
when the first refresh fires. After the first refresh, the stale cache
poisoned every subsequent request.
Fix: delete the cached session after a successful repository update,
mirroring what SwitchOrganization already does for the same reason
(it updates current_organization_id in Postgres and then drops the
Redis copy). Next GetSession misses, re-reads from Postgres, caches
the fresh nonces.
The deleteSession failure path is intentionally swallowed — the
refresh already succeeded and we returned the new tokens, so worst
case is the next request triggers another refresh, not a logout.
2026-05-22 15:37:00 +00:00
Matthew Meszaros
d8d88c7f69
feat: add warmup health tracking, migrate repos to postgres, and overhaul web UI
2026-04-03 06:08:52 +00:00
Matthew Meszaros and Claude Opus 4.6
fbb55379c5
fix: propagate captcha errors and fix JWT signing
...
- Return actual captcha verification errors instead of generic
internal errors in login, registration, and reset password flows
- Fix Sentry capturing wrong variable (err -> xerr) in
RegistrationConfirm
- Pass AuthSecret as []byte to JWT SignedString/keyfunc
- Send form data in request body instead of PostForm for Turnstile
- Improve Turnstile non-200 error handling with response body logging
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com >
2026-02-18 06:30:50 +01:00
Máté Mészáros (Laptop)
6adb4cdd5a
Organization, Subscription, Inqueries, limits.
2026-01-27 05:55:48 +01:00
Matthew Meszaros
772c19820d
New Repository: Add Backend Code
2026-01-17 14:11:14 +00:00