Feat/345 add support for custom domains - #369
Conversation
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.
|
🚀 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: 💡 Tip: To skip preview deployment, add the |
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.
|
🚀 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: 💡 Tip: To skip preview deployment, add the |
|
🚀 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: 💡 Tip: To skip preview deployment, add the |
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
|
🚀 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: 💡 Tip: To skip preview deployment, add the |
…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
|
🚀 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: 💡 Tip: To skip preview deployment, add the |
… 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.
|
🚀 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: 💡 Tip: To skip preview deployment, add the |
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.
|
🚀 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: 💡 Tip: To skip preview deployment, add the |
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)").
|
🚀 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: 💡 Tip: To skip preview deployment, add the |
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
|
🚀 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: 💡 Tip: To skip preview deployment, add the |
- 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
|
🚀 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: 💡 Tip: To skip preview deployment, add the |
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.
|
🚀 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: 💡 Tip: To skip preview deployment, add the |
|
🚀 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: 💡 Tip: To skip preview deployment, add the |
…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.
|
🚀 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: 💡 Tip: To skip preview deployment, add the |
|
🚀 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: 💡 Tip: To skip preview deployment, add the |
|
🚀 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: 💡 Tip: To skip preview deployment, add the |
|
🧹 Ephemeral Environment Cleaned Up Resources for PR #369 cleanup summary:
Cleanup completed automatically when PR was closed. Note: If this PR had the |
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
This PR implements organization-level custom domains with SSL
certificates via Cloudflare for SaaS, including:
Backend:
Frontend:
Infrastructure:
Tier limits are enforced at the organisation level.
Closes #345