Commit Graph
49 Commits
Author SHA1 Message Date
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 eac3f6d615 Merge remote-tracking branch 'origin/main' into fix/sso-link-existing-password-account
# Conflicts:
#	docs/content/docs/guides/security.mdx
2026-09-21 04:28:20 -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 ff2204f8ef feat: route the sso_link completion through the one login path (completeLogin) so the ban check, the 2FA gate, login recording and device memory apply before a parked identity is attached, carry the session provider into every 2FA challenge so a Google or Apple sign-in on a 2FA account is recorded as such, make IdentityRepository.Link report a pair another account holds as ErrIdentityTaken instead of silently updating nothing, refuse a spent address budget before charging a link try, drop the dead expiry check and the duplicate link fields on the web Session model 2026-09-21 03:57:29 -07:00
Matthew Meszaros e0db7d3c72 Merge pull request #642 from warmbly/fix/reset-token-invalidation-after-password-change
feat: refuse a password reset link issued before the password was last changed
2026-09-21 10:50:49 +00:00
Matthew Meszaros a1b7a8ad9b feat: attach a parked federated identity only after the ban check and, on a 2FA account, only once the second factor passes by carrying it through the 2FA pending record into twofa.VerifyLogin, charge each sso_link password attempt atomically before the check, treat an identity a parallel challenge already linked as a re-login instead of a refusal, ask for no password when the identity cannot be linked, end an exhausted or expired challenge with sso_link_expired so the dashboard returns to the email step, and document the code in error-codes, the API reference and OpenAPI 2026-09-21 03:35:17 -07:00
Matthew Meszaros 9426c0da51 feat: validate every person, workspace and company name through internal/pkg/displayname on each write path (profile, onboarding, setup, IdP sign-in, org create and rename, org import, enterprise inquiry, admin testers, warmblyctl) with a 400 invalid_name code, render stored names in platform email through the same rules, mirror them in the web forms, check the org slug format, and document the rules in error-codes, security and AGENTS.md 2026-09-21 03:34:03 -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 36eb5e72e9 feat: stamp users.password_changed_at on every password write and refuse a password reset link issued at or before it, so a link requested earlier dies when the password is changed from settings, by another reset link or by warmblyctl, with the rule documented on the reset endpoint and the security guide 2026-09-21 03:23:14 -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 68ec5a016e feat: delete the reauth attempt counter when a refund leaves it at zero or below so it always carries an expiry, and focus the first field of a 2FA dialog on open 2026-09-19 08:43:11 -07:00
Matthew Meszaros e9d1a7e734 feat: reserve the per-account reauth attempt atomically before checking a password or 2FA code, keep the 2FA dialogs open while a request is in flight, move and trap focus in them, show a retry row when 2FA status fails to load, and drop Idempotency-Key from the recovery-code route with the reason documented 2026-09-19 08:36:59 -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 0e914ee390 feat: stop the password reset flow reporting success while sending nothing, by answering 200 only for an address with no account rather than for every cache and database fault, folding addresses to one case on every account lookup and write so a typed capital cannot miss the row or split an SSO account in two, refunding the two-per-four-hours budget when the failure was ours, retrying one transient send and quoting the link's real lifetime; and clear the rest of error tracking by grouping the engagement breakdown in a subquery so ORDER BY stops resolving opens against email_opens, dropping unibox_mailboxes and email_sync_state writes whose mailbox was deleted mid-sync instead of redelivering them forever, answering a corrupt argon2 hash with ErrCredentials rather than a 500, never filing a cancelled caller as a database incident, and reporting only the first websocket init failure of a streak 2026-09-18 11:42:49 +02: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 7300b3b021 feat: full PostHog coverage: identify the signed-in user and workspace in the dashboard and admin panel with autocapture, heatmaps, dead and rage clicks, web vitals, network timing, console capture and session replay masking only password fields, send server-side signup, trial and subscription events under the user id with the organization as a group, keep the marketing site and form pages cookieless while capturing everything stateless plus a form funnel, upload the form app's source maps, and add WARMBLY_POSTHOG_SESSION_REPLAY 2026-09-13 20:58:22 -07:00
Matthew Meszaros 43dcbde06c feat: tester accounts, creatable from the admin panel (#483)
* feat: excuse one named account from the emailed login code, so a vendor reviewer who cannot read this instance's mail can sign in without turning codes off for everyone, with the reason recorded beside it and every run of warmblyctl status naming the accounts that hold one

* feat: create and manage tester accounts from the admin panel, so letting a reviewer in is a form rather than a shell, with the password shown once and every live exemption listed on one page because forgetting one is the way this goes wrong

* fix: give tester management its own permission bit rather than borrowing ban_users, create the account and its exemption in one transaction so no invisible orphan survives a failure, require an accountable operator on the CLI grant, stop a halted row scan reading as the whole exempt list, and show a failed query as an error instead of as no testers

* chore: re-run CI after the aggregator tripped on a cancelled job from the branch update, with every underlying job green

* chore: retrigger CI, the previous run sat queued indefinitely while other branches ran

* feat: roll back a half-created tester when its workspace step fails and backfill the manage-testers bit onto admins already holding every other permission, so the address is not left taken by an unusable account and the new routes are not 403 for the existing admin

* feat: make the 000151 manage-testers backfill one-way, because clearing bit 22 on the way down would also revoke it from an admin granted it explicitly afterwards and the up migration would not restore that
2026-09-13 03:07:10 -07:00
Matthew Meszaros 26c88d2426 feat: address the CodeRabbit review on the PostHog PR by dropping a referrer that is not a hostname instead of storing its query string or fragment, redacting email-shaped values out of every acquisition field before they reach the database or an analytics property, counting an invited signup which returned before the count was taken, hanging subscription_started off the persisted trial-to-paid transition so a redelivered webhook cannot report a second start, capturing the validated provider in both OAuth mailbox paths including Warmbly Cloud, making the two acquisition toggles mutually exclusive and naming them after what they actually select, and replacing the unsupported CNIL consent-exemption claim with what the guidance says and an explicit note that qualifying is a deployment-specific assessment this document does not make 2026-09-07 05:45:11 -07:00
Matthew Meszaros 8e9f67f46b feat: add cookieless PostHog analytics for the hosted marketing site and dashboard with server-side signup and subscription events, a first-party acquisition record written once at signup on a new organization_acquisition table registered in the org-transfer spec, an acquisition column and channel filter in the admin org list, and never a single request from a self-host because every key is unset by default 2026-09-07 04:18:33 -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 d78e4423e4 fix: address the review of the notifications work — bound detached alert delivery to a fixed pool that drops and logs rather than spawning a goroutine per event, never echo a transport error that embeds the webhook URL, treat an emptied channel target as cleared so switching transport cannot post Slack payloads to a Discord URL, emit user.registered for invited signups and the worker alert after a successful reassignment, format Stripe amounts by currency exponent, refuse an upgrade while the plan list is still loading instead of silently opening the portal, apply a fixed discount to the annual invoice rather than to each month, convert proration from minor units, contain focus in the nested enterprise dialog, hide the odometer wheels from assistive tech, and drop the last dedicated-IP row from the pricing matrix 2026-09-04 06:33:20 -07:00
Matthew Meszaros a0c9d5a5b0 feat: add operator notification channels an admin configures in the panel (Discord, Slack, HMAC-signed webhook, or email) with per-channel event subscriptions, a synchronous test probe, SSRF-guarded URLs and credentials redacted on read, wire nine instance events including the enterprise inquiry that previously wrote a row nobody was told about, and reword the plan-specific limits copy so it reads correctly without billing 2026-09-04 06:02:40 -07:00
Matthew Meszaros d621d9b79b feat: sign a new account in as part of registration: createAccount returns the user, both the verification-free and the code-confirm paths mint a session through finishLoginAs (2FA challenge included), /auth/register/confirm answers with that session instead of 204, and the dashboard drops into the workspace right after signing up instead of bouncing to the sign-in form 2026-08-29 08:41:34 -07:00
Matthew Meszaros 84700bae8d Merge remote-tracking branch 'origin/main' into fix/issue-241
# Conflicts:
#	cmd/consumer/main.go
#	docs/content/docs/guides/deliverability.mdx
#	internal/app/auth/login_risk.go
#	internal/app/auth/provision.go
#	internal/app/contact/import.go
#	internal/app/orgrisk/service.go
2026-08-28 23:06:07 -07:00
Matthew Meszaros 4ead1b200f feat: answer the review on #251 by filing a signup's throwaway-domain finding separately from its soft ones so the aggregate score no longer carries one class, and by measuring import quality across everything a workspace has imported instead of the newest file, so a small clean upload cannot retract a large bad list whose addresses are still stored while the finding still fades as good data outweighs it, with the running counts kept as evidence on the finding and a finding filed before those counts existed folded in as the smallest list that could have flagged it 2026-08-28 22:51:23 -07:00
Matthew Meszaros a5ea55bba5 feat: give a suspended workspace a way back, because risk_state was a one-way door: the derived band is no longer pinned at suspended by the UPDATE in pg_org_risk, an operator's decision is now an explicit risk_override that outranks the score and survives every later detector write until it is lifted, the one-shot detectors (signup origin, import list quality, repeated sign-in anomalies) file findings with a 30-day expiry that a six-hourly consumer sweep retires so a score falls on its own, migration 000104 backfills that expiry onto findings already on file, and four admin endpoints plus an Abuse posture panel in admin/ let an operator finally read the evidence the customer endpoint withholds, retract a finding, pin a band and lift the pin; also stops the reviewing admin's identity reaching the tenant's own audit feed (which resolves an actor to a name and email) by recording the platform as the actor there and the operator in the admin trail, and stops risk_signals riding along in a customer-downloadable org export 2026-08-28 22:42:57 -07:00
Matthew Meszaros 2b25e32f6b feat: judge an organization's abuse posture on what it did rather than on what it looks like, so an agency opening client workspaces from one office under one operator identity and connecting the mailboxes in an afternoon lands on watch instead of restricted: findings now carry a circumstantial/substantive class, shape is capped at 40 points and can never move a band past watch, the signup address and identity clusters count once as one family, the signup finding is substantive only when the domain is really disposable, list_quality retracts on a later clean import, and the nightly sweep gains a recipient-outcome pass scoring 30-day bounce and complaint rates against the provider bands so a band still has evidence it may act on 2026-08-28 22:41:44 -07:00
Matthew Meszaros 70d49e7c5f feat: record the verdict that issued the login challenge instead of re-judging at confirm, and stop a mailbox in no warmup pool reading as healthy: the emailed code is confirmed by a second request that can carry a different address and sees history the first one did not, so re-assessing there could file a challenged sign-in as clean and drop it from the repeat count that reaches the workspace posture; the verdict now travels in the login session and every completion path takes it, while ListLifecycleCandidates returns a NULL health as unknown so leaving the pool is no longer evidence of recovery and a resting mailbox neither resumes nor accrues probation on it 2026-08-28 12:20:11 -07:00
Matthew Meszaros 78ec522758 feat: challenge a sign-in that could not physically be the same person, which a remembered device cannot catch because an attacker holding the cookie looks familiar: sign-ins were only ever a Redis device fingerprint with a TTL, so nothing durable recorded WHERE an account was used and there was nothing to compare a new one against; login_history keeps a bounded window per user, the geo lookup gains the coordinates the city database already had, and a journey implying more than 1000 km/h forces the emailed code even on a known device; it declines to judge a first sign-in, a hop under 500 km, an interval under two minutes or a missing position, because a false challenge locks a real person out of their own account and that is the worse failure, and repeated anomalies inside a fortnight reach the workspace posture while one odd trip does not 2026-08-28 12:08:17 -07:00
Matthew Meszaros 1e37fb6aa3 feat: capture and score a signup's origin instead of discarding it (#234)
* feat: give an organization one fused abuse posture, because every existing control watches a single subject and an actor slightly wrong on several axes sits under all of them: organizations gains risk_state, risk_score, risk_reason and an append-only risk_signals evidence blob, modelled on the warmup participant health machine that already works rather than a second vocabulary for the same idea; restricted cuts per-mailbox cold volume to a quarter and forces the free warmup pool so a risky tenant cannot spend the paid pool's shared reputation, suspended stops sending at the send gate, and watch deliberately changes nothing a customer can feel so evidence accumulates before anything is taken away; an operator's suspension outranks the derived band so a detector clearing cannot release a workspace a human suspended, transitions ride the audit spine to every teammate's dashboard, a banner says which limit is active and why rather than letting volume drop silently, and the posture never travels in a workspace archive since it is one platform's verdict reached from evidence the destination never saw

* feat: make the suspension actually stop sending, and emit the audit transitions the PR claimed: emailsend.SendEmail is only the manual and API path, so campaign and warmup sends went nowhere near the gate and a suspended workspace kept sending on its schedule, while the restricted multiplier floored every mailbox at one a day which quietly turned suspension into a trickle rather than a stop; the campaign scheduler now defers the whole campaign with a reason and the warmup task skips as org-suspended, since warmup is outbound mail from the same domains; separately the band change emitted no audit entry at all despite the entity type and the frontend spine entry both existing, so no banner moved for a teammate and there was no trail of who was restricted when, and only a real transition is logged so a detector re-recording the same finding cannot fill the feed; one of my own live tests also asserted how far out a slot lands, which depends on the hour the suite runs, and now asserts the property it was about

* feat: capture and score a signup's origin instead of discarding it

* feat: document what a signup records, and prove the origin write is on the account path with a seam test rather than only testing the scorer

* chore: drop the em dashes and trim the comments the review flagged
2026-08-28 11:26:44 -07:00
Matthew Meszaros 029bc27bfd feat: bind a browser sign-in to the browser that started it, so a handoff link cannot be forwarded: one-time state proves the callback answers a request this server made, not one THIS browser made, so anyone could run the flow against their own Google or OIDC account and send the resulting URL to someone else, whose browser would then hold the sender's session (RFC 9700 4.7.1); begin now mints a binding secret that never reaches the provider and never appears in a URL, the callback carries it into the handoff, and the exchange refuses a collection that cannot present it with sso_wrong_browser, while the comments this PR added are condensed to the constraint they exist to state 2026-08-28 01:44:39 -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 734cb5fe08 feat: make self-hosted onboarding survivable by fixing invite_only, which could not onboard anyone (the accept route is JWT-only, so redeeming the invitation that would create your account required already having one, making the self-host default silently identical to fully closed), threading the invitation token through registration so an invited person lands in the inviting organization instead of a stray workspace, gating SSO just-in-time provisioning behind DISABLE_REGISTRATION (it bypassed the gate entirely, so an instance set to true was still open to anyone the IdP would assert) with SSO_AUTO_PROVISION as the opt-out, correcting the OIDC redirect URL that pointed at /api/v1 against a route at /v1 and 404'd every SSO login, scoping the first-launch exemption so it no longer overrides an explicit lockdown, preserving the remaining TTL when restoring a losing setup token so a public endpoint cannot hold the claim window open forever, replacing a generic 403 with typed registration_invite_only, registration_closed, invitation_invalid, setup_token_invalid and setup_already_complete codes that name the next step, logging why no claim link was issued on an already-claimed instance instead of staying silent, adding a warmblyctl operator CLI (status with health checks and a non-zero exit, reissuable setup-link, user create/list/reset-password/grant-admin/revoke-admin/disable-2fa, hash-password) so a locked-out operator no longer needs hand-written psql, adding read-only instance configuration over 104 environment variables with structural secret redaction and fingerprints, 35 health checks, a database-backed settings tier for the three keys no environment variable owns, hiding the signup form when the config already says invite_only rather than failing the whole form with a toast, and documenting first run, accounts and access, configuration, instance health and troubleshooting alongside the root .env.example the README told operators to write but never shipped (#114) 2026-08-16 05:58:11 +02:00
Matthew Meszaros 0ae4db2c41 feat: make self-hosted auth work without a mail relay by rewriting the platform SMTP transport with real AUTH and TLS (it did neither, so SMTP_USERNAME/SMTP_PASSWORD were dead and every documented relay was unreachable), adding MAIL_TRANSPORT=smtp|log|ses with a log transport that prints codes so a fresh install can sign in with no relay, demoting the emailed login code to AUTH_LOGIN_CODE=always|new_device|off (off on self-host, per NIST SP 800-63B and OWASP ASVS), claiming the first owner through a single-use setup link or WARMBLY_BOOTSTRAP_* instead of register-then-psql, deriving every emailed URL from APP_URL rather than a hardcoded app.warmbly.com that leaked live reset tokens to the vendor, fixing the confirm hooks that read path params against paramless routes and broke login, register and reset confirmation in the dashboard everywhere, adding generic OIDC with PKCE, one-time state, verified nonce and (issuer,subject) identity binding, enforcing 2FA on the social paths that skipped it, adding a per-IP limiter and trusted-proxy handling to the unthrottled auth group, refusing boot on the published default secrets, and dropping mailpit from the default stack (#99) 2026-08-14 14:57:09 +02:00
Matthew Meszaros a20ba926cb feat: add native Apple/Google sign-in to the backend - POST /auth/apple and /auth/google exchange provider-signed ID tokens for sessions, GET /auth/providers exposes configured providers for app discovery, with a JWKS-verifying idtoken package, auth config, stable error codes, and API/security docs 2026-07-07 05:56:35 +02:00
Matthew Meszaros a21f4d0d0e feat: capture the signup referral code through the auth registration flow and the WireReferral attributor hook 2026-06-28 05:09:50 +00:00
Matthew Meszaros 3c6de16321 fix: forgotten-password reset now revokes all existing sessions, so a reset done because access was lost or compromised fully cuts off prior devices 2026-06-11 12:41:55 +02:00
Matthew Meszaros 3c040649c0 fix: changing your password now revokes every other session (keeping the current device), matching the security promise in the sign-in alert and docs 2026-06-11 12:39:24 +02: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 c40a83c22d feat: add two factor auth core
Adds TOTP enrollment state, sealed secrets, recovery codes, pending login challenges, and the auth-service login gate for users with 2FA enabled.
2026-06-08 15:04:36 +02:00
Matthew Meszaros 3837cd3898 feat: bound auth login network waits
Add request and mail-delivery timeouts around auth flows so login requests cannot hang indefinitely when notification delivery stalls.

Allow the local admin dev origin through default CORS and update context-aware lint fixes so the repository lint gate passes.
2026-06-01 16:56:24 +02:00
Matt d80efc88b4 feat(ban): runtime enforcement for ban-scope bitmask
The bitmask landed in 000045 with schema + UI; this commit wires the
three gates the bits describe.

  - BanScopeLogin    → authService.LoginConfirm checks the scope after
                       password verification and refuses the session
                       with "this account has been suspended"
  - BanScopeOrgCreate → organizationService.Create checks the scope
                       before any other validation and refuses with
                       "this account cannot create new workspaces"
  - BanScopeSend     → emailSendService.SendEmail checks the scope
                       before validating the email account and refuses
                       with "this account cannot send email"

Adds UserRepository.GetBanState(ctx, userID) → uint32 — a single-column
read so the hot paths don't have to fetch the full user row just to
check a flag. Returns 0 when no ban (the column defaults to 0); the
caller treats 0 as "allow."

Threads userRepo into emailSendService — the only constructor change
in this commit. cmd/backend/main.go updated accordingly.
2026-05-28 12:43:50 +02:00
Matthew Meszaros 91ec703093 fix: increment verification code tries on failed attempts
The Tries counter on login and registration sessions was checked but
never incremented, making the brute-force protection dead code. An
attacker could retry verification codes indefinitely within the session
TTL. Now each failed attempt increments and persists the counter.
2026-04-09 12:10:26 +00:00
Matthew MeszarosandClaude 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
Matthew Meszaros 6c6d26d8f0 Update auth and onboarding flow 2026-02-14 05:38:27 +01:00
Matthew Meszaros 2635e9c6da frontend & email design; local email setup 2026-02-12 17:25:46 +01:00
Máté Mészáros (Laptop) 41624a6f79 Analytics & Tracking 2026-01-29 05:59:04 +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