Factorizer/Cluster ERP WikiCluster ERP Türkçe
Home / Identity and access

Explanation

Signup

The self-service signup that creates a tenant and its membership in multi-tenant mode, with domain verification, the privacy notice, and provisioning.

Signup is the anonymous, public entry through which a company buys a membership and gets its own tenant. It exists only in multi-tenant mode. The routes are in server/routes/signup.py and the flow in server/signup.py; both work on the control plane, never on a tenant database. This page explains validation, the privacy notice, domain verification, payment, provisioning, and the anonymous status poll. The tenant model itself is in multi-tenancy; the web form is described in mail and profile screens.

In short

  • The routes are mounted only when multi_tenant is on; otherwise GET /api/v1/signup/ answers {"signup": "closed"} (src/cluster_erp/server/app.py:1440-1457).
  • The setting signup_enabled closes the door on a multi-tenant node: the probe answers closed and a create is refused with SIGNUP_CLOSED (src/cluster_erp/server/routes/signup.py:129-133, src/cluster_erp/server/signup.py:226-234).
  • A signup needs the current privacy notice version, read from signup_notice_dir and signup_notice_version; without a configured notice, signups are refused (src/cluster_erp/server/signup.py:206-253).
  • The founder's e-mail must be on the claimed mail domain, and with signup_verify_email on, provisioning waits until a link mailed to that address is confirmed (src/cluster_erp/server/signup.py:256-280, src/cluster_erp/server/signup.py:336-372).
  • The anonymous surface accepts only capability tokens in request bodies; the control plane stores their SHA-256 digests (src/cluster_erp/server/routes/signup.py:8-15, src/cluster_erp/core/tenancy.py:73-84, src/cluster_erp/core/tenancy.py:233-239).

How it works

Steps

The sequence shows a signup with e-mail verification on.

sequenceDiagram
    participant V as visitor browser
    participant R as signup routes
    participant S as start_signup
    participant T as TenantRegistry
    participant P as provision
    V->>R: GET /api/v1/signup/
    R-->>V: open or closed, regions, notice
    V->>R: POST /api/v1/signup/
    R->>S: SignupRequest
    S->>T: domain_owner, create_tenant, Membership
    S-->>V: signup_token, state queued
    V->>R: POST /api/v1/signup/verify with verify_token
    R->>P: _provision_in_background
    V->>R: POST /api/v1/signup/status with signup_token
    R-->>V: state, tenant_domain when ready

In prose:

  1. GET /api/v1/signup/ returns whether the door is open, the regions, the default region, the node country, the country-to-node map, and the notice (src/cluster_erp/server/routes/signup.py:114-143).
  2. POST /api/v1/signup/ applies a per-client hourly limit of signup_rate_limit_per_hour, counted in control_rate_limits (src/cluster_erp/server/routes/signup.py:63-85, src/cluster_erp/server/routes/signup.py:146-149).
  3. start_signup checks the door, validates the request, requires the current notice, refuses a domain that any live tenant already owns, and asks the payment port to check out (src/cluster_erp/server/signup.py:291-310).
  4. It derives a tenant key from the domain, checks the region, registers the tenant with status provisioning, and stores a Membership with status active, provisioning state queued, the notice version, and the acceptance time (src/cluster_erp/server/signup.py:312-353).
  5. With verification on, only an Argon2 hash of the founder's password waits on the control plane, and a verification mail goes out (src/cluster_erp/server/signup.py:344-364). With verification off, provisioning starts at once (src/cluster_erp/server/signup.py:366-372).
  6. POST /api/v1/signup/verify stamps verified_at and starts provisioning with the stored identity; an expired link answers HTTP 410 (src/cluster_erp/server/routes/signup.py:171-189, src/cluster_erp/server/signup.py:472-516).
  7. _provision_in_background sets the state to running, then ready, and clears the waiting hash; on failure it sets failed and logs details only for the operator (src/cluster_erp/server/signup.py:534-568).
  8. POST /api/v1/signup/status returns the state for a signup token and, only when ready, the sign-in domain (src/cluster_erp/server/routes/signup.py:222-243).

Membership states

Field Values Source
Membership.status pending, active, suspended, cancelled src/cluster_erp/core/tenancy.py:276-279
Membership.provision_state queued, running, ready, failed src/cluster_erp/core/tenancy.py:280-283
State shown by the status route the provisioning state, or awaiting_verification while a verify token is open src/cluster_erp/server/routes/signup.py:229-232

Other routes

Route Effect Source
POST /api/v1/signup/resend Rotates the verify token and mails a fresh link; rate-limited src/cluster_erp/server/routes/signup.py:192-202, src/cluster_erp/server/signup.py:415-441
POST /api/v1/signup/cancel Withdraws a signup that is queued or failed; frees the domain src/cluster_erp/server/routes/signup.py:205-219, src/cluster_erp/server/signup.py:444-469

Ports and settings

  • Payment: signup_payment_provider; the only provider in code is offline, which approves and sets a trial period of signup_trial_days days (src/cluster_erp/server/signup.py:85-103, src/cluster_erp/server/signup.py:179-188).
  • Mail: signup_mail_transport is spool (a JSON file in signup_mail_spool_dir) or smtp with the signup_smtp_* settings (src/cluster_erp/server/signup.py:105-176, src/cluster_erp/config/settings.py:283-289).
  • Verification: signup_verify_email and signup_verify_ttl_hours (src/cluster_erp/config/settings.py:277-279). The link carries its token in the URL fragment (src/cluster_erp/server/signup.py:384-389).
  • Notice: signup_notice_dir holds signup_notice.<lang>.md files; signup_notice_version names the version (src/cluster_erp/server/signup.py:206-223, src/cluster_erp/config/settings.py:299-306).

Types and defaults are in the settings reference.

Why it is built this way

  • The poll handle is the only key the anonymous surface accepts, so signup state cannot be used to enumerate tenants (src/cluster_erp/server/signup.py:17-22).
  • Tokens travel in bodies and fragments so that they reach no access log, browser history, or Referer header, and the page confirms with a POST so that a mail scanner's GET changes nothing (src/cluster_erp/server/routes/signup.py:8-15).
  • Tokens are stored as digests so that a copy of the table cannot be replayed (src/cluster_erp/core/tenancy.py:233-238).
  • Verification by a link on the claimed domain stops first-come squatting of another company's domain (src/cluster_erp/core/tenancy.py:240-243).
  • The rate limit is counted in the control database so that all workers and nodes count a client together (src/cluster_erp/server/routes/signup.py:41-44).

Rules and boundaries

  • The founder becomes the only administrator of the new tenant: provisioning runs with bootstrap_admin_exclusive, which deactivates any seeded default account first (src/cluster_erp/config/settings.py:253-258, src/cluster_erp/server/app.py:882-890).
  • A failed provisioning error is never shown to the anonymous poller (src/cluster_erp/server/signup.py:564-568).
  • Tests: tests/test_signup_closed.py (4), tests/test_signup_notice.py (4), tests/test_signup_tokens.py (3), tests/test_signup_validation.py (5), counted with grep -c "def test_"; e2e web-client/_e2e_tenant_signup.mjs.

Interaction with other parts

Limits and open points

  • A live payment provider is not in code; payment_port_for refuses any provider other than offline (src/cluster_erp/server/signup.py:179-188).
  • Retention of abandoned signups runs in the control-plane retention pass; its rules are in scheduled jobs and retention.

Terms introduced

Term Code identifier Definition Turkish term Source
signup token signup_token The capability handle a signer uses to poll, resend, or cancel. kayıt belirteci src/cluster_erp/core/tenancy.py:239
verify token verify_token The capability in the mailed link that proves control of the claimed domain. doğrulama belirteci src/cluster_erp/core/tenancy.py:244
provisioning state provision_state How far the creation of a signup's tenant database has progressed. kurulum durumu src/cluster_erp/core/tenancy.py:257
payment port PaymentPort The interface behind which a payment provider checks out a signup. ödeme bağlantı noktası src/cluster_erp/server/signup.py:85

Verified against commit 92f8051 on 2026-11-13.

Verified against commit 92f8051 · 2026-11-13