Volver a la wiki

Endpoint GET/POST /signup — página pública de registro invite-only (F1 PR-A)

Descripción

Página web pública (form-post server-side clásico) donde nuevos clientes introducen su código de invitación y crean su organización.

Ruta: GET/POST /signup (sin slash final, exacto) Auth: Público / anónimo Template: account/signup.html Vista: core/views_signup.py::signup_view (60 LOC) Middleware: EXENTO del TenantRLSMiddleware (verificado en plan v5)

Interfaz y flujo

GET /signup

Renderiza el formulario vacío con estética de login (account/base.html).

POST /signup

Procesa el form-post, valida, y:

Campos del formulario

Campo HTMLLabel i18nValidaciónNotas
invitation_code“Invitation code”required, max 64 charsCódigo de invitación (será normalizado a UPPERCASE por el servicio)
organization_name“Company Name”required, max 255 charsNombre de la organización (único case-insensitive)
email“Email”required, email formatEmail del admin (único case-insensitive, recibe enlace de activación)
username“Username”required, max 150 charsUsername (único)

i18n: Reutiliza msgids existentes — “Username”, “Email”, “Company Name” están ya en locale/es/LC_MESSAGES/django.po.

Clase SignupForm (Django Form)

class SignupForm(forms.Form):
    invitation_code = forms.CharField(label=_("Invitation code"), max_length=64)
    organization_name = forms.CharField(label=_("Company Name"), max_length=255)
    email = forms.EmailField(label=_("Email"))
    username = forms.CharField(label=_("Username"), max_length=150)

Protecciones de seguridad

1. Cloudflare Turnstile

Integración:

2. CSRF

Django CSRF ({% csrf_token %} en template) — token per-session.

3. Rate limiting

4. CSP (Content Security Policy)

Context y renderizado

Funciones auxiliar _ctx():

def _ctx(form, **extra):
    return {"form": form, "turnstile_site_key": settings.TURNSTILE_SITE_KEY, **extra}

Context variables:

Flujo de vista detallado

def signup_view(request):
    if GET:
        return render(signup_form_vacia)
    
    # POST
    form = SignupForm(request.POST)
    if form.invalid:
        return render(formulario_con_errores)
    
    # Validar Turnstile (ANTES de crear nada)
    if not verify_turnstile(request.POST["cf-turnstile-response"]):
        form.add_error(None, "Human verification failed...")
        return render(formulario_con_error_global)
    
    # Crear org + user (tx atómica interna)
    try:
        org, user = create_signup(
            code=form.cleaned_data["invitation_code"],
            org_name=form.cleaned_data["organization_name"],
            email=form.cleaned_data["email"],
            username=form.cleaned_data["username"],
        )
    except SignupError as exc:
        form.add_error(None, str(exc))
        return render(formulario_con_error_global)
    
    # Éxito — renderizar confirmación
    return render(success_page)

Template account/signup.html

Campos del form renderizados:

  1. Código de invitación (text input)
  2. Nombre empresa (text input)
  3. Email (email input)
  4. Username (text input)
  5. Widget Turnstile (iframe, condicional)
  6. Pie: “Contact us” → hello@crearack.com
  7. Link desde login: “Have an invitation code?” (discreto)

Errores re-renderizados:

Estado de éxito (si signup_done == True):

Integración con auth y signals

Post-create email: El modelo User tiene un post_save signal que dispara el welcome email. Solo ocurre tras on_commit() (tx completada):

# core/signals.py
@receiver(post_save, sender=User)
def send_welcome_email(sender, instance, created, **kwargs):
    if created:
        # El password es inusable; el email lleva link para setear
        transaction.on_commit(lambda: ...enviar_email...)

Estado actual

Eliminaciones

Endpoint antiguo: POST /api/signup/ fue RETIRADO:

Véase también

Subir