Skip to content

Feat/invites#227

Open
IamKirbki wants to merge 48 commits into
mainfrom
feat/invites
Open

Feat/invites#227
IamKirbki wants to merge 48 commits into
mainfrom
feat/invites

Conversation

@IamKirbki

@IamKirbki IamKirbki commented May 1, 2026

Copy link
Copy Markdown
Contributor

feat: project invite system + auth and project scoping improvements

(closes: #218 #220)

This adds a full project invite flow and updates auth and project scoping.

Project invite system (enterprise)

Full invite flow behind a //go:build enterprise guard. OSS builds return 404 on all invite endpoints.

  • New project_invites table with AES-256-GCM encrypted token storage, nonce column, expiry, and revoke/accept timestamps
  • Tokens are encrypted at rest and decrypted before sending to the frontend. Re-encrypting with the same nonce reproduces the DB ciphertext for querying
  • Auto-revokes any pending invite for the same email + project on new invite creation
  • New endpoints: create, list (with filters), accept, revoke, and a public get-details endpoint requiring no auth
  • Accept invite handles existing project admins, upgrading role if the invite role is higher
  • Frontend mashes nonce + token into a single URL-safe base64 string for invite links
  • Invite management page under project settings with search, filters, pagination, copy link, and revoke
  • Public /invites/:token page handles unauthenticated users, wrong account detection, and auto-accept after Clerk registration via ?autoAccept=1
  • Axios interceptor updated to skip 401 redirect on invite pages and requests flagged with skipAuthRedirect

Project listing scoped to admin membership

ListProjects now queries via project_admins join instead of org-wide scan. GetProject accepts an optional adminID to populate the real role, removing the hardcoded "admin" return.

Auth: dual JWT support

WithJWT now supports RS256 (Clerk/JWKS) and HS256 (basic auth) simultaneously via a multiKeyfunc dispatcher, configured via AUTH_JWKS_URL.

Closes #218
Closes #220

IamKirbki and others added 13 commits April 20, 2026 15:05
- Introduced ProjectInviteListResponse model for listing project invites.
- Added ListProjectInvitesParams for pagination support in listing invites.
- Implemented RevokeProjectInvite, ListProjectInvites, AcceptProjectInvite, and GetInviteDetails methods in the Client interface.
- Created corresponding request and response parsing functions for project invite operations.
- Enhanced AdminsStore with HardDeleteProjectAdmin method for direct deletion of project admins.
- Expanded InvitesStore with methods to handle project invites: GetInviteByToken, AcceptProjectInvite, RevokeProjectInvite, and ListProjectInvites.
- Updated database migration to enforce unique constraint on invite tokens.
- Added a new "Invites" section in the settings menu with a UserPlus icon.
- Enhanced user search functionality in the ListDetail component.
- Updated OrganizationEventRuleEdit to improve accessibility with better aria-labels.
- Modified the InviteController to support filtering project invites by status, role, and expiration dates.
- Updated OpenAPI resources to include new query parameters for invite management.
- Refactored invite handling in the management store to support new filtering options.
- Changed database table references from "invites" to "project_invites" for clarity.
- Implemented revoke project invite functionality with proper middleware handling.
Co-authored-by: Copilot <copilot@github.com>
…rchy logic

Co-authored-by: Copilot <copilot@github.com>
…rtain contexts

Co-authored-by: Copilot <copilot@github.com>
…okens

- Updated API paths to accept a combined token and nonce pair for invite acceptance and revocation.
- Modified ProjectInvite interface to include nonce.
- Implemented nonce generation and encryption in the invite creation process.
- Adjusted database schema to store nonce alongside the invite token.
- Enhanced invite handling logic to support nonce verification during acceptance and revocation.
- Updated frontend components to handle the new token-nonce structure.
- Added necessary environment configurations for invite secret key.

Co-authored-by: Copilot <copilot@github.com>
IamKirbki and others added 8 commits May 5, 2026 11:09
- Changed API endpoint parameter from `tokenNouncePair` to `token` for clarity.
- Updated the `ProjectPushProviderPlatform` enum to replace `email` with `mail`.
- Refactored `AcceptInvite` component to handle new token structure and improved error handling.
- Removed unused `mashTokenNonce` function and simplified token concatenation logic.
- Updated database migrations to reflect changes in project provider platform naming.
- Adjusted related functions and interfaces to ensure consistency with new naming conventions.
- Removed email platform support from various components and validations.

Co-authored-by: Copilot <copilot@github.com>
@IamKirbki IamKirbki requested a review from jeroenrinzema May 6, 2026 12:57
@IamKirbki IamKirbki marked this pull request as ready for review May 6, 2026 12:57
@jeroenrinzema jeroenrinzema requested a review from Copilot May 12, 2026 19:08
jeroenrinzema and others added 24 commits June 16, 2026 22:23
Add store-level tests asserting ListUserSchedules / ListOrganizationSchedules
report has_pending_events=true while events are unfired and flip to false once
all events are marked fired. Guards the badge-stays-lit-forever regression that
this branch fixes.
- Regenerate console management OpenAPI bindings after the inbox
  open->read rename (read_at, /read paths)
- Update inbox-detail-table console consumer to the read terminology so
  it typechecks against the regenerated types; fixes prettier formatting
- Restore oapi.ListStateDraft/ListStateReady constants in lists_test.go
- Use *int16 (ptr.To) for InboxMessage.Priority in consumer test
- Fix client inbox spec tests: /inbox/read path and required identifier
- SMS template data: align textTemplateDataSchema and TextTemplateData to
  the API shape ({ body }); fix SMS preview reading data.text -> data.body
- Enable mode: "onChange" on NewCampaign and ProjectOnboardingDomain forms
  so formState.isValid updates and the submit button isn't stuck disabled
- optionalPhoneSchema: accept "" so an InlineEdit phone can be cleared
- Trim names/IDs in list, journey, and device form schemas to reject
  whitespace-only input
- Users.createUser: optional-chain optional fields to avoid a TypeError
  when only some identity fields are provided
- EmailContentPreview: drop render-phase setCompiledHtml("") (effect already
  clears it) and return null instead of undefined for non-email templates
- searchTimeoutRef: initialize empty instead of scheduling a throwaway timer
Replace the hand-rolled SHA-256/HMAC implementation with crypto/sha256 +
crypto/hmac. The "stdlib panics under WASM" rationale does not hold for the
current TinyGo wasi toolchain (verified: stdlib sha256/hmac produce correct
known-answer values under tinygo 0.40.1 + wasmtime), and the Mailgun provider
already relies on stdlib crypto. This removes ~130 lines of unaudited custom
crypto from the SES request signer.

Also set Spec.Webhook to false: WebhookHandler is a no-op stub that drops all
events (SNS parsing is unimplemented), so advertising webhook support would
register an endpoint that silently discards every delivery/bounce event.
The unused linter now flags it since the consumer test package compiles
again (it was masked by the prior inbox_test.go compile error).
Amazon SES and Mailgun declared Platforms: [PlatformEmail], but Spec.Platforms
is the push-platform list. On provider creation, autoAssignPushProvider
upserts the provider as the default push provider for every platform it lists,
bypassing the validPlatforms allowlist (ios/android/web only). This wrote an
invalid "email" row into project_push_providers for what are email-channel
providers.

Email providers are identified by Channels: [ChannelEmail] (as resend already
does); they have no push platform. Drop the Platforms declaration from both and
remove the now-unused PlatformEmail constant, reverting channel.go to its
prior state.
Route due inbox messages through the inbox process pipeline (with a stable
Msg-Id for idempotent re-injection) instead of emitting an analytics event,
and gate dispatch on IsDue() so future-scheduled messages published
immediately by the client/campaign paths wait for the scheduler. Also guard
generic email/SMS composition against an empty recipient.
Move the destructive 'DROP TABLE campaign_sends' out of the inbox-table
creation migration into its own migration with a down migration that recreates
the table structure (its final post-broadcast_id, composite-PK shape). The
inbox migration's down is now a clean inverse. Also correct the ScanDue*
comments: FOR UPDATE SKIP LOCKED is not run in a held transaction, so
idempotency actually comes from the scheduler's stable Msg-Id and the sent_at
guard, not the row lock.
Backend
- Fix UnarchiveList: drop the GetList pre-check that filtered deleted_at IS
  NULL, which made every restore return 404; rely on the store returning
  sql.ErrNoRows on zero rows affected.
- Fix UnarchiveCampaign handler: add the missing generic error branch so
  non-ErrNoRows DB errors no longer fall through to a false 204.
- Make UnarchiveJourney store return sql.ErrNoRows on zero rows affected,
  matching the list/campaign stores, and simplify the handler.
- Change the list filter semantics from "include deleted (both)" to
  "archived only" so the archived view can paginate server-side with a
  correct total_count. Renamed the store/controller param to archivedOnly
  and updated the include_deleted OAPI parameter description accordingly.

Frontend
- Replace the client-side limit:100 + .filter() archived view with real
  server-side offset pagination in the Lists, Campaigns and Journeys views
  (dedicated archivedOffset, prev/next, footer shown in both views, offset
  reset on toggle/search/unarchive).

Tests
- Add store tests covering the archived-only filter and the unarchive happy
  path plus ErrNoRows on already-active and non-existent rows, for lists,
  campaigns and journeys.
fix: add has_pending_events field to user and organization schedules
…-components-and-provider-crud

fix: enhance components with error handling and improve rate limit logic
…lidation

# Conflicts:
#	console/src/views/settings/IntegrationSetup.tsx
#	console/src/views/users/ListCreateForm.tsx
Normalize arbitrary timezone formats (GMT+2, EST, UTC+05:30, etc) to canonical IANA strings during CSV import. Includes timezone resolver, tests, and timezone selection UI improvements.
…lidation

# Conflicts:
#	console/src/views/users/Users.tsx
# Conflicts:
#	internal/http/controllers/v1/client/oapi/resources_gen.go
#	internal/http/controllers/v1/management/oapi/resources_gen.go
#	internal/wasm/test/provider.wasm
Add inbox messaging system for organizations and users
…s-provider

# Conflicts:
#	go.mod
#	go.sum
#	internal/http/controllers/v1/client/oapi/resources_gen.go
#	internal/http/controllers/v1/management/oapi/resources_gen.go
#	internal/wasm/test/action.wasm
#	internal/wasm/test/provider.wasm
…-files

# Conflicts:
#	go.mod
#	go.sum
#	internal/http/controllers/v1/client/oapi/resources_gen.go
#	internal/http/controllers/v1/management/oapi/resources_gen.go
#	internal/store/journey/journeys_test.go
feat: enhance journey, campaign, and list management with unarchive f…
# Conflicts:
#	console/package-lock.json
#	console/pnpm-lock.yaml
#	console/src/views/broadcast/CreateBroadcastDialog.tsx
#	console/src/views/settings/IntegrationSetup.tsx
#	internal/http/controllers/v1/management/oapi/resources_gen.go
#	internal/rbac/model.go
#	internal/wasm/test/action.wasm
#	internal/wasm/test/provider.wasm
jeroenrinzema
jeroenrinzema previously approved these changes Jun 16, 2026

@jeroenrinzema jeroenrinzema left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed and approved. Resolved conflicts with main: merged RBAC invites+inbox resources, took main's react-hook-form rate_limit handling in IntegrationSetup/CreateBroadcastDialog, regenerated oapi (v2.7.0), kept main's lockfiles (frozen-lockfile verified), took main's wasm. go build + tsc + eslint + rbac tests green.

Rework project invites to resolve by the invitee's IdP-verified email
instead of an encrypted token, and fix five bugs reported on the PR.

Bug fixes:
- migration: project_id NOT NULL CASCADE; inviter_admin_id is now
  nullable with ON DELETE SET NULL so deleting an inviting admin no
  longer violates the constraint
- ListProjectInvites: expires_before/after comparisons were inverted and
  cast to ::date (dropping the time); fixed and switched to ::timestamptz
- accept compares emails case-insensitively (strings.EqualFold) and
  stores invitee email lowercased
- replace uuid.MustParse(actor.ID) with checked uuid.Parse; create now
  returns 201, revoke 204, accept 200 + the project summary
- remove BackfillProjectTuples from the accept hot path; OpenFGA tuples
  are written after the DB commit to avoid orphaned grants

Flow:
- delete AES-GCM token/nonce machinery and the public invite endpoint
- add GET /api/invites/mine and POST /api/invites/{inviteID}/accept,
  authorized by email match rather than project RBAC
- same-org / new-email guard on create (reject inviting an email that
  belongs to another organization until admin-org M2M lands)
- console: replace the token AcceptInvite page with a /invites inbox;
  revoke by id; drop the dead INVITES_SECRET_KEY config
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Project team management and invite acceptance UI Create invite system (backend)

3 participants