From aaf8d0aeb22715657356a505786e6cc623415260 Mon Sep 17 00:00:00 2001 From: rrader2890 Date: Tue, 16 Jun 2026 19:32:26 -0400 Subject: [PATCH] feat(white-label): expose MSA gating on the customer portal Adds `mustSignAgreement` + `agreementSigningUrl` to the CustomerPortalCustomer GraphQL type so the portal can block all sections until the customer signs the MSA. Signing happens on the existing hosted gate page (reused, kept as the signing surface). Adds WhiteLabelAgreement#signing_url helper and reuses it in the provisioning rake task. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../types/customer_portal/customers/object.rb | 23 +++++++++++++++++++ app/models/white_label_agreement.rb | 11 +++++++++ lib/tasks/flobyte.rake | 5 +--- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/app/graphql/types/customer_portal/customers/object.rb b/app/graphql/types/customer_portal/customers/object.rb index 040cdcf9450b..51e8796c2818 100644 --- a/app/graphql/types/customer_portal/customers/object.rb +++ b/app/graphql/types/customer_portal/customers/object.rb @@ -36,6 +36,19 @@ class Object < Types::BaseObject field :premium, Boolean, null: false + # White-label / SDK MSA gating: the portal blocks all sections until the + # customer has signed. Signing happens on the hosted gate page. + field :must_sign_agreement, Boolean, null: false + field :agreement_signing_url, String, null: true + + def must_sign_agreement + pending_agreement.present? + end + + def agreement_signing_url + pending_agreement&.signing_url + end + def billing_configuration { id: "#{object&.id}-c0nf", @@ -53,6 +66,16 @@ def billing_entity_billing_configuration def premium License.premium? end + + private + + def pending_agreement + return @pending_agreement if defined?(@pending_agreement) + + @pending_agreement = WhiteLabelAgreement + .where(customer_id: object.id, status: "pending") + .order(created_at: :desc).first + end end end end diff --git a/app/models/white_label_agreement.rb b/app/models/white_label_agreement.rb index be07b0da594d..ac5404266242 100644 --- a/app/models/white_label_agreement.rb +++ b/app/models/white_label_agreement.rb @@ -42,6 +42,17 @@ def signed_token self.class.verifier.generate(id, purpose: TOKEN_PURPOSE, expires_in: TOKEN_TTL) end + # Full URL of the hosted MSA acceptance gate for this agreement. Used by the + # provisioning rake task (email link) and the customer portal (sign panel). + def self.gate_base_url + (ENV["WHITE_LABEL_GATE_URL"].presence || ENV["LAGO_API_URL"].presence || + ENV["LAGO_FRONT_URL"].presence || "http://localhost:3000").chomp("/") + end + + def signing_url + "#{self.class.gate_base_url}/white-label/#{signed_token}" + end + def accept!(signer:, request_ip:, user_agent:) update!( status: "accepted", diff --git a/lib/tasks/flobyte.rake b/lib/tasks/flobyte.rake index 536f3b6fa226..5e856de85a8c 100644 --- a/lib/tasks/flobyte.rake +++ b/lib/tasks/flobyte.rake @@ -219,10 +219,7 @@ module FlobyteCatalog puts "+ pending agreement #{agreement.id}" end - base = ENV["WHITE_LABEL_GATE_URL"].presence || ENV["LAGO_API_URL"].presence || - ENV["LAGO_FRONT_URL"].presence || "http://localhost:3000" - link = "#{base.chomp("/")}/white-label/#{agreement.signed_token}" - + link = agreement.signing_url puts "\nEmail this acceptance link to #{name} <#{email}> (valid #{WhiteLabelAgreement::TOKEN_TTL.inspect}):\n\n #{link}\n" link end