Skip to content

Feat/345 add support for custom domains - #369

Merged
piffio merged 24 commits into
mainfrom
feat/345-add-support-for-custom-domains
Jun 2, 2026
Merged

Feat/345 add support for custom domains#369
piffio merged 24 commits into
mainfrom
feat/345-add-support-for-custom-domains

Conversation

@piffio

@piffio piffio commented May 15, 2026

Copy link
Copy Markdown
Owner

This PR implements organization-level custom domains with SSL
certificates via Cloudflare for SaaS, including:

Backend:

  • Custom domain model and repository
  • Cloudflare for SaaS API integration
  • Custom domain CRUD API endpoints
  • KV dual-write for hostname:short_code mappings
  • Redirect handler updates for custom domain resolution
  • Scheduled polling for domain status updates
  • Admin API for manual domain polling and listing
  • Database migration for custom_domains table

Frontend:

  • Custom domain management UI
  • Admin domains page with manual polling button
  • Admin API client updates
  • Domains API client
  • Admin sidebar navigation updates

Infrastructure:

  • Added CF_SAAS_API_TOKEN secret to staging and production workflows
  • Added domain polling cron to production

Tier limits are enforced at the organisation level.

Closes #345

piffio added 2 commits May 15, 2026 16:50
This commit implements organization-level custom domains with SSL
certificates via Cloudflare for SaaS, including:

Backend:
- Custom domain model and repository
- Cloudflare for SaaS API integration
- Custom domain CRUD API endpoints
- KV dual-write for hostname:short_code mappings
- Redirect handler updates for custom domain resolution
- Scheduled polling for domain status updates
- Admin API for manual domain polling and listing
- Database migration for custom_domains table

Frontend:
- Custom domain management UI
- Admin domains page with manual polling button
- Admin API client updates
- Domains API client
- Admin sidebar navigation updates

Infrastructure:
- Added CF_SAAS_API_TOKEN secret to staging and production workflows
- Added domain polling cron to production

Tier limits enforced at organization level.
@github-actions

Copy link
Copy Markdown

🚀 Ephemeral Environment Deployed (Unified Worker)

Application: https://rushomon-pr-369.piffio.workers.dev

This unified Worker deployment serves both frontend and backend from the same domain for better security (httpOnly cookies work correctly).

This environment will be automatically cleaned up when the PR is closed.

Worker: rushomon-pr-369


💡 Tip: To skip preview deployment, add the skip-preview label to this PR.

Add the support for a custom fallback domain so that we're not limited
to using the existing one, and allow the use of a sub-domain such as
`redirect.my-domain.com` if preferred.
@github-actions

Copy link
Copy Markdown

🚀 Ephemeral Environment Deployed (Unified Worker)

Application: https://rushomon-pr-369.piffio.workers.dev

This unified Worker deployment serves both frontend and backend from the same domain for better security (httpOnly cookies work correctly).

This environment will be automatically cleaned up when the PR is closed.

Worker: rushomon-pr-369


💡 Tip: To skip preview deployment, add the skip-preview label to this PR.

@github-actions

Copy link
Copy Markdown

🚀 Ephemeral Environment Deployed (Unified Worker)

Application: https://rushomon-pr-369.piffio.workers.dev

This unified Worker deployment serves both frontend and backend from the same domain for better security (httpOnly cookies work correctly).

This environment will be automatically cleaned up when the PR is closed.

Worker: rushomon-pr-369


💡 Tip: To skip preview deployment, add the skip-preview label to this PR.

Cloudflare for SaaS is an Enterprise-only feature. When quota is not allocated,
fall back to dev/test mode instead of failing with 500 error.

- Add quota error detection (error 1404 or 'quota' in message)
- Return stub DNS instructions when CF for SaaS unavailable
- Update documentation to clarify Enterprise plan requirement
@github-actions

Copy link
Copy Markdown

🚀 Ephemeral Environment Deployed (Unified Worker)

Application: https://rushomon-pr-369.piffio.workers.dev

This unified Worker deployment serves both frontend and backend from the same domain for better security (httpOnly cookies work correctly).

This environment will be automatically cleaned up when the PR is closed.

Worker: rushomon-pr-369


💡 Tip: To skip preview deployment, add the skip-preview label to this PR.

…tion

Cloudflare for SaaS now returns ACME validation records in ssl.validation_records
instead of ownership_verification. This fixes the TXT record mismatch where
the dashboard showed _cf-custom-hostname but Cloudflare expected _acme-challenge.

- Add validation_records field to CfSslResult
- Update domain creation to check both validation_records and ownership_verification
- Prioritize ACME validation records if available
@github-actions

Copy link
Copy Markdown

🚀 Ephemeral Environment Deployed (Unified Worker)

Application: https://rushomon-pr-369.piffio.workers.dev

This unified Worker deployment serves both frontend and backend from the same domain for better security (httpOnly cookies work correctly).

This environment will be automatically cleaned up when the PR is closed.

Worker: rushomon-pr-369


💡 Tip: To skip preview deployment, add the skip-preview label to this PR.

… domains

Cloudflare for SaaS requires TWO different TXT verification methods:
1. Domain ownership verification (_cf-custom-hostname.*) - validates hostname ownership
2. SSL certificate validation (_acme-challenge.*) - validates for certificate issuance

Both are required for production traffic. The previous implementation only showed
one TXT record, causing verification failures when Cloudflare expected the other type.

This ensures users see all required TXT records with clear labels indicating
which is for domain ownership vs SSL certificate validation.
@github-actions

Copy link
Copy Markdown

🚀 Ephemeral Environment Deployed (Unified Worker)

Application: https://rushomon-pr-369.piffio.workers.dev

This unified Worker deployment serves both frontend and backend from the same domain for better security (httpOnly cookies work correctly).

This environment will be automatically cleaned up when the PR is closed.

Worker: rushomon-pr-369


💡 Tip: To skip preview deployment, add the skip-preview label to this PR.

SSL validation records (_acme-challenge.*) are only returned when the
certificate is in pending_validation state. The initial CREATE response
may not include them - they appear after Cloudflare starts the certificate
issuance process.

Added a follow-up GET call after creating the custom hostname to fetch
the full hostname details, which includes the validation_records when
the SSL status becomes pending_validation.
@github-actions

Copy link
Copy Markdown

🚀 Ephemeral Environment Deployed (Unified Worker)

Application: https://rushomon-pr-369.piffio.workers.dev

This unified Worker deployment serves both frontend and backend from the same domain for better security (httpOnly cookies work correctly).

This environment will be automatically cleaned up when the PR is closed.

Worker: rushomon-pr-369


💡 Tip: To skip preview deployment, add the skip-preview label to this PR.

The SSL validation records (_acme-challenge.*) from Cloudflare are
returned by the GET endpoint, not just on creation. For non-wildcard
hostnames, CF uses HTTP validation automatically once the CNAME is set,
but falls back to TXT if that fails (showing "Pending Validation (TXT)").
@github-actions

Copy link
Copy Markdown

🚀 Ephemeral Environment Deployed (Unified Worker)

Application: https://rushomon-pr-369.piffio.workers.dev

This unified Worker deployment serves both frontend and backend from the same domain for better security (httpOnly cookies work correctly).

This environment will be automatically cleaned up when the PR is closed.

Worker: rushomon-pr-369


💡 Tip: To skip preview deployment, add the skip-preview label to this PR.

The refresh endpoint was only returning dns_instructions when the
hostname status was not active. But the hostname can be "active" while
the SSL certificate is still "Pending Validation (TXT)" - this is the
common case after CNAME validation succeeds but before SSL completes.

Now when a user clicks Refresh:
- If SSL certificate is still pending (even if hostname is active)
- The response includes both _cf-custom-hostname and _acme-challenge records
- The frontend displays all required TXT records
@github-actions

Copy link
Copy Markdown

🚀 Ephemeral Environment Deployed (Unified Worker)

Application: https://rushomon-pr-369.piffio.workers.dev

This unified Worker deployment serves both frontend and backend from the same domain for better security (httpOnly cookies work correctly).

This environment will be automatically cleaned up when the PR is closed.

Worker: rushomon-pr-369


💡 Tip: To skip preview deployment, add the skip-preview label to this PR.

- Add max_custom_domains to backend usage response
- Add max_custom_domains to frontend UsageResponse type
- Disable domain add input when tier limit reached
- Show upgrade CTAs with consistent amber styling
- Free tier: always disabled, upgrade to Pro
- Pro tier (1 domain): disabled at 1 domain, upgrade to Business
@github-actions

Copy link
Copy Markdown

🚀 Ephemeral Environment Deployed (Unified Worker)

Application: https://rushomon-pr-369.piffio.workers.dev

This unified Worker deployment serves both frontend and backend from the same domain for better security (httpOnly cookies work correctly).

This environment will be automatically cleaned up when the PR is closed.

Worker: rushomon-pr-369


💡 Tip: To skip preview deployment, add the skip-preview label to this PR.

Make sure that a downgrade from Business to Pro or from Pro to Free
is handled gracefully if the user has set up custom domains and has
existing links set up on them.
@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown

🚀 Ephemeral Environment Deployed (Unified Worker)

Application: https://rushomon-pr-369.piffio.workers.dev

This unified Worker deployment serves both frontend and backend from the same domain for better security (httpOnly cookies work correctly).

This environment will be automatically cleaned up when the PR is closed.

Worker: rushomon-pr-369


💡 Tip: To skip preview deployment, add the skip-preview label to this PR.

@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown

🚀 Ephemeral Environment Deployed (Unified Worker)

Application: https://rushomon-pr-369.piffio.workers.dev

This unified Worker deployment serves both frontend and backend from the same domain for better security (httpOnly cookies work correctly).

This environment will be automatically cleaned up when the PR is closed.

Worker: rushomon-pr-369


💡 Tip: To skip preview deployment, add the skip-preview label to this PR.

…udFlare

When deleting a custom domain, if it has been already removed on CF, display
a warning and proceed anyway, rather than being stuck in a loop.
@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown

🚀 Ephemeral Environment Deployed (Unified Worker)

Application: https://rushomon-pr-369.piffio.workers.dev

This unified Worker deployment serves both frontend and backend from the same domain for better security (httpOnly cookies work correctly).

This environment will be automatically cleaned up when the PR is closed.

Worker: rushomon-pr-369


💡 Tip: To skip preview deployment, add the skip-preview label to this PR.

@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown

🚀 Ephemeral Environment Deployed (Unified Worker)

Application: https://rushomon-pr-369.piffio.workers.dev

This unified Worker deployment serves both frontend and backend from the same domain for better security (httpOnly cookies work correctly).

This environment will be automatically cleaned up when the PR is closed.

Worker: rushomon-pr-369


💡 Tip: To skip preview deployment, add the skip-preview label to this PR.

@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown

🚀 Ephemeral Environment Deployed (Unified Worker)

Application: https://rushomon-pr-369.piffio.workers.dev

This unified Worker deployment serves both frontend and backend from the same domain for better security (httpOnly cookies work correctly).

This environment will be automatically cleaned up when the PR is closed.

Worker: rushomon-pr-369


💡 Tip: To skip preview deployment, add the skip-preview label to this PR.

@piffio
piffio merged commit dd59ddf into main Jun 2, 2026
9 checks passed
@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown

🧹 Ephemeral Environment Cleaned Up

Resources for PR #369 cleanup summary:

Resource Status
Worker rushomon-pr-369 ✅ Deleted
KV Namespace URL_MAPPINGS_pr_369 ✅ Deleted
D1 Database rushomon-pr-369 ✅ Deleted
R2 Bucket rushomon-assets-pr-369 ✅ Deleted

Cleanup completed automatically when PR was closed.

Note: If this PR had the skip-preview label, resources were still cleaned up because they were deployed before the label was added.

mfcarroll added a commit to mfcarroll/rushomon that referenced this pull request Jul 11, 2026
Organizations on Business tier and above can verify ownership of their
email domains via a DNS TXT challenge. Users signing in with an email
on a verified org domain are automatically provisioned into that
organization as a member: existing users gain membership, and brand-new
users skip personal-org creation and land directly in the org.

Backend:
- org_domains table (migration 0041), OrgDomain model, and
  OrgDomainRepository; distinct from the custom link domains feature
- OrgService domain methods with owner/admin + Business+ enforcement
- Endpoints under /api/orgs/:id/org-domains (+ verify-org-domain),
  named to avoid colliding with the custom-domains API from piffio#369
- DNS-over-HTTPS TXT verification in utils/dns.rs, plus a server-side
  Cloudflare-nameserver hint (is_cloudflare) computed during list so
  the frontend needs no cross-origin DNS calls under the CSP
- JIT provisioning in the OAuth flow for all three login paths;
  matching pending invitations are auto-accepted, and explicitly
  invited users may sign in even when public signups are disabled
- Invite flow: already-members (e.g. via JIT) get a graceful accept
  that upgrades their role if the invitation offers a different one,
  and invite info reports is_member so the UI can skip the accept step

Frontend:
- OrgDomains management card on the org page (owner + Business+),
  with TXT record instructions, copy button, verify and remove actions
- Invite page routes already-members straight to the dashboard

Includes integration tests for the org-domain endpoints and an OpenAPI
spec regeneration.
mfcarroll added a commit to mfcarroll/rushomon that referenced this pull request Jul 11, 2026
Organizations on Business tier and above can verify ownership of their
email domains via a DNS TXT challenge. Users signing in with an email
on a verified org domain are automatically provisioned into that
organization as a member: existing users gain membership, and brand-new
users skip personal-org creation and land directly in the org.

Backend:
- org_domains table (migration 0041), OrgDomain model, and
  OrgDomainRepository; distinct from the custom link domains feature
- OrgService domain methods with owner/admin + Business+ enforcement
- Endpoints under /api/orgs/:id/org-domains (+ verify-org-domain),
  named to avoid colliding with the custom-domains API from piffio#369
- DNS-over-HTTPS TXT verification in utils/dns.rs, plus a server-side
  Cloudflare-nameserver hint (is_cloudflare) computed during list so
  the frontend needs no cross-origin DNS calls under the CSP
- JIT provisioning in the OAuth flow for all three login paths;
  matching pending invitations are auto-accepted, and explicitly
  invited users may sign in even when public signups are disabled
- Invite flow: already-members (e.g. via JIT) get a graceful accept
  that upgrades their role if the invitation offers a different one,
  and invite info reports is_member so the UI can skip the accept step

Frontend:
- OrgDomains management card on the org page (owner + Business+),
  with TXT record instructions, copy button, verify and remove actions
- Invite page routes already-members straight to the dashboard

Includes integration tests for the org-domain endpoints and an OpenAPI
spec regeneration.
mfcarroll added a commit to mfcarroll/rushomon that referenced this pull request Jul 20, 2026
Organizations on Business tier and above can verify ownership of their
email domains via a DNS TXT challenge. Users signing in with an email
on a verified org domain are automatically provisioned into that
organization as a member: existing users gain membership, and brand-new
users skip personal-org creation and land directly in the org.

Backend:
- org_domains table (migration 0041), OrgDomain model, and
  OrgDomainRepository; distinct from the custom link domains feature
- OrgService domain methods with owner/admin + Business+ enforcement
- Endpoints under /api/orgs/:id/org-domains (+ verify-org-domain),
  named to avoid colliding with the custom-domains API from piffio#369
- DNS-over-HTTPS TXT verification in utils/dns.rs, plus a server-side
  Cloudflare-nameserver hint (is_cloudflare) computed during list so
  the frontend needs no cross-origin DNS calls under the CSP
- JIT provisioning in the OAuth flow for all three login paths;
  matching pending invitations are auto-accepted, and explicitly
  invited users may sign in even when public signups are disabled
- Invite flow: already-members (e.g. via JIT) get a graceful accept
  that upgrades their role if the invitation offers a different one,
  and invite info reports is_member so the UI can skip the accept step

Frontend:
- OrgDomains management card on the org page (owner + Business+),
  with TXT record instructions, copy button, verify and remove actions
- Invite page routes already-members straight to the dashboard

Includes integration tests for the org-domain endpoints and an OpenAPI
spec regeneration.
mfcarroll added a commit to mfcarroll/rushomon that referenced this pull request Jul 20, 2026
Organizations on Business tier and above can verify ownership of their
email domains via a DNS TXT challenge. Users signing in with an email
on a verified org domain are automatically provisioned into that
organization as a member: existing users gain membership, and brand-new
users skip personal-org creation and land directly in the org.

Backend:
- org_domains table (migration 0041), OrgDomain model, and
  OrgDomainRepository; distinct from the custom link domains feature
- OrgService domain methods with owner/admin + Business+ enforcement
- Endpoints under /api/orgs/:id/org-domains (+ verify-org-domain),
  named to avoid colliding with the custom-domains API from piffio#369
- DNS-over-HTTPS TXT verification in utils/dns.rs, plus a server-side
  Cloudflare-nameserver hint (is_cloudflare) computed during list so
  the frontend needs no cross-origin DNS calls under the CSP
- JIT provisioning in the OAuth flow for all three login paths;
  matching pending invitations are auto-accepted, and explicitly
  invited users may sign in even when public signups are disabled
- Invite flow: already-members (e.g. via JIT) get a graceful accept
  that upgrades their role if the invitation offers a different one,
  and invite info reports is_member so the UI can skip the accept step

Frontend:
- OrgDomains management card on the org page (owner + Business+),
  with TXT record instructions, copy button, verify and remove actions
- Invite page routes already-members straight to the dashboard

Includes integration tests for the org-domain endpoints and an OpenAPI
spec regeneration.
mfcarroll added a commit to Standearth/rushomon that referenced this pull request Jul 20, 2026
Organizations on Business tier and above can verify ownership of their
email domains via a DNS TXT challenge. Users signing in with an email
on a verified org domain are automatically provisioned into that
organization as a member: existing users gain membership, and brand-new
users skip personal-org creation and land directly in the org.

Backend:
- org_domains table (migration 0041), OrgDomain model, and
  OrgDomainRepository; distinct from the custom link domains feature
- OrgService domain methods with owner/admin + Business+ enforcement
- Endpoints under /api/orgs/:id/org-domains (+ verify-org-domain),
  named to avoid colliding with the custom-domains API from piffio#369
- DNS-over-HTTPS TXT verification in utils/dns.rs, plus a server-side
  Cloudflare-nameserver hint (is_cloudflare) computed during list so
  the frontend needs no cross-origin DNS calls under the CSP
- JIT provisioning in the OAuth flow for all three login paths;
  matching pending invitations are auto-accepted, and explicitly
  invited users may sign in even when public signups are disabled
- Invite flow: already-members (e.g. via JIT) get a graceful accept
  that upgrades their role if the invitation offers a different one,
  and invite info reports is_member so the UI can skip the accept step

Frontend:
- OrgDomains management card on the org page (owner + Business+),
  with TXT record instructions, copy button, verify and remove actions
- Invite page routes already-members straight to the dashboard

Includes integration tests for the org-domain endpoints and an OpenAPI
spec regeneration.
mfcarroll added a commit to mfcarroll/rushomon that referenced this pull request Jul 21, 2026
Organizations on Business tier and above can verify ownership of their
email domains via a DNS TXT challenge. Users signing in with an email
on a verified org domain are automatically provisioned into that
organization as a member: existing users gain membership, and brand-new
users skip personal-org creation and land directly in the org.

Backend:
- org_domains table (migration 0041), OrgDomain model, and
  OrgDomainRepository; distinct from the custom link domains feature
- OrgService domain methods with owner/admin + Business+ enforcement
- Endpoints under /api/orgs/:id/org-domains (+ verify-org-domain),
  named to avoid colliding with the custom-domains API from piffio#369
- DNS-over-HTTPS TXT verification in utils/dns.rs, plus a server-side
  Cloudflare-nameserver hint (is_cloudflare) computed during list so
  the frontend needs no cross-origin DNS calls under the CSP
- JIT provisioning in the OAuth flow for all three login paths;
  matching pending invitations are auto-accepted, and explicitly
  invited users may sign in even when public signups are disabled
- Invite flow: already-members (e.g. via JIT) get a graceful accept
  that upgrades their role if the invitation offers a different one,
  and invite info reports is_member so the UI can skip the accept step

Frontend:
- OrgDomains management card on the org page (owner + Business+),
  with TXT record instructions, copy button, verify and remove actions
- Invite page routes already-members straight to the dashboard

Includes integration tests for the org-domain endpoints and an OpenAPI
spec regeneration.
mfcarroll added a commit to Standearth/rushomon that referenced this pull request Jul 21, 2026
Organizations on Business tier and above can verify ownership of their
email domains via a DNS TXT challenge. Users signing in with an email
on a verified org domain are automatically provisioned into that
organization as a member: existing users gain membership, and brand-new
users skip personal-org creation and land directly in the org.

Backend:
- org_domains table (migration 0041), OrgDomain model, and
  OrgDomainRepository; distinct from the custom link domains feature
- OrgService domain methods with owner/admin + Business+ enforcement
- Endpoints under /api/orgs/:id/org-domains (+ verify-org-domain),
  named to avoid colliding with the custom-domains API from piffio#369
- DNS-over-HTTPS TXT verification in utils/dns.rs, plus a server-side
  Cloudflare-nameserver hint (is_cloudflare) computed during list so
  the frontend needs no cross-origin DNS calls under the CSP
- JIT provisioning in the OAuth flow for all three login paths;
  matching pending invitations are auto-accepted, and explicitly
  invited users may sign in even when public signups are disabled
- Invite flow: already-members (e.g. via JIT) get a graceful accept
  that upgrades their role if the invitation offers a different one,
  and invite info reports is_member so the UI can skip the accept step

Frontend:
- OrgDomains management card on the org page (owner + Business+),
  with TXT record instructions, copy button, verify and remove actions
- Invite page routes already-members straight to the dashboard

Includes integration tests for the org-domain endpoints and an OpenAPI
spec regeneration.
mfcarroll added a commit to mfcarroll/rushomon that referenced this pull request Jul 21, 2026
Organizations on Business tier and above can verify ownership of their
email domains via a DNS TXT challenge. Users signing in with an email
on a verified org domain are automatically provisioned into that
organization as a member: existing users gain membership, and brand-new
users skip personal-org creation and land directly in the org.

Backend:
- org_domains table (migration 0041), OrgDomain model, and
  OrgDomainRepository; distinct from the custom link domains feature
- OrgService domain methods with owner/admin + Business+ enforcement
- Endpoints under /api/orgs/:id/org-domains (+ verify-org-domain),
  named to avoid colliding with the custom-domains API from piffio#369
- DNS-over-HTTPS TXT verification in utils/dns.rs, plus a server-side
  Cloudflare-nameserver hint (is_cloudflare) computed during list so
  the frontend needs no cross-origin DNS calls under the CSP
- JIT provisioning in the OAuth flow for all three login paths;
  matching pending invitations are auto-accepted, and explicitly
  invited users may sign in even when public signups are disabled
- Invite flow: already-members (e.g. via JIT) get a graceful accept
  that upgrades their role if the invitation offers a different one,
  and invite info reports is_member so the UI can skip the accept step

Frontend:
- OrgDomains management card on the org page (owner + Business+),
  with TXT record instructions, copy button, verify and remove actions
- Invite page routes already-members straight to the dashboard

Includes integration tests for the org-domain endpoints and an OpenAPI
spec regeneration.
mfcarroll added a commit to mfcarroll/rushomon that referenced this pull request Jul 22, 2026
Organizations on Business tier and above can verify ownership of their
email domains via a DNS TXT challenge. Users signing in with an email
on a verified org domain are automatically provisioned into that
organization as a member: existing users gain membership, and brand-new
users skip personal-org creation and land directly in the org.

Backend:
- org_domains table (migration 0041), OrgDomain model, and
  OrgDomainRepository; distinct from the custom link domains feature
- OrgService domain methods with owner/admin + Business+ enforcement
- Endpoints under /api/orgs/:id/org-domains (+ verify-org-domain),
  named to avoid colliding with the custom-domains API from piffio#369
- DNS-over-HTTPS TXT verification in utils/dns.rs, plus a server-side
  Cloudflare-nameserver hint (is_cloudflare) computed during list so
  the frontend needs no cross-origin DNS calls under the CSP
- JIT provisioning in the OAuth flow for all three login paths;
  matching pending invitations are auto-accepted, and explicitly
  invited users may sign in even when public signups are disabled
- Invite flow: already-members (e.g. via JIT) get a graceful accept
  that upgrades their role if the invitation offers a different one,
  and invite info reports is_member so the UI can skip the accept step

Frontend:
- OrgDomains management card on the org page (owner + Business+),
  with TXT record instructions, copy button, verify and remove actions
- Invite page routes already-members straight to the dashboard

Includes integration tests for the org-domain endpoints and an OpenAPI
spec regeneration.
mfcarroll added a commit to mfcarroll/rushomon that referenced this pull request Jul 22, 2026
Organizations on Business tier and above can verify ownership of their
email domains via a DNS TXT challenge. Users signing in with an email
on a verified org domain are automatically provisioned into that
organization as a member: existing users gain membership, and brand-new
users skip personal-org creation and land directly in the org.

Backend:
- org_domains table (migration 0041), OrgDomain model, and
  OrgDomainRepository; distinct from the custom link domains feature
- OrgService domain methods with owner/admin + Business+ enforcement
- Endpoints under /api/orgs/:id/org-domains (+ verify-org-domain),
  named to avoid colliding with the custom-domains API from piffio#369
- DNS-over-HTTPS TXT verification in utils/dns.rs, plus a server-side
  Cloudflare-nameserver hint (is_cloudflare) computed during list so
  the frontend needs no cross-origin DNS calls under the CSP
- JIT provisioning in the OAuth flow for all three login paths;
  matching pending invitations are auto-accepted, and explicitly
  invited users may sign in even when public signups are disabled
- Invite flow: already-members (e.g. via JIT) get a graceful accept
  that upgrades their role if the invitation offers a different one,
  and invite info reports is_member so the UI can skip the accept step

Frontend:
- OrgDomains management card on the org page (owner + Business+),
  with TXT record instructions, copy button, verify and remove actions
- Invite page routes already-members straight to the dashboard

Includes integration tests for the org-domain endpoints and an OpenAPI
spec regeneration.
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.

Add support for custom domains

1 participant