rustango 0.61.13

Batteries-included web framework for Rust: ORM + migrations + auto-admin + multi-tenancy + audit log + auth (sessions, JWT, OAuth2/OIDC, HMAC) + APIs (ViewSet, OpenAPI auto-derive, JSON:API) + jobs (in-mem + Postgres) + email + media (S3 / R2 / B2 / MinIO + presigned uploads + collections + tags) + production middleware (CSRF, CSP, rate-limiting, compression, idempotency, etc.).
Documentation
{% extends "op_layout.html" %}
{% block title %}Shared SSO providers — {{ brand_name | default(value='Rustango Operator Console') }}{% endblock %}
{% block content %}
<h1>Shared SSO providers</h1>
{% if error %}<div class="alert alert-error">{{ error }}</div>{% endif %}
{% if notice %}<div class="alert alert-notice">{{ notice }}</div>{% endif %}

<p>OpenID Connect and social login providers offered on <strong>every</strong>
tenant's login page. A tenant can add its own providers from its admin; on a
slug clash the tenant's provider wins. Client secrets are stored encrypted and
never shown again.</p>
<p>The IdP sends users back to each tenant's own host, so register one redirect
URI per tenant host: <code>{{ callback.pattern }}</code>.</p>

{% if providers | length == 0 %}
  <div class="empty">No shared providers yet{% if edit_enabled %} — add one below{% endif %}.</div>
{% else %}
<table>
  <thead>
    <tr>
      <th>Slug</th>
      <th>Label</th>
      <th>Kind</th>
      <th>Issuer / client</th>
      <th>Enabled</th>
      <th>Email linking</th>
      <th>Order</th>
      <th class="actions-col">Actions</th>
    </tr>
  </thead>
  <tbody>
    {% for p in providers %}
    <tr class="sso-row">
      <td><strong>{{ p.slug }}</strong></td>
      <td>{{ p.label }}</td>
      <td>{{ p.kind }}</td>
      <td>
        {% if p.issuer_url %}<div class="issuer-url">{{ p.issuer_url }}</div>{% endif %}
        <code>{{ p.client_id }}</code>
      </td>
      <td>{% if p.enabled %}✓{% else %}—{% endif %}</td>
      <td>{% if p.allow_email_link %}✓{% else %}—{% endif %}</td>
      <td>{{ p.sort_order }}</td>
      <td class="actions-col">
        {% if edit_enabled %}
        <a class="btn-row-action" href="{{ console_prefix }}/sso-shared/{{ p.id }}/edit">Edit</a>
        <form method="post" action="{{ console_prefix }}/sso-shared/{{ p.id }}/email-link" class="inline-form">
          {{ csrf_input | default(value='') | safe }}
          <input type="hidden" name="allow_email_link" value="{% if p.allow_email_link %}off{% else %}on{% endif %}">
          <button type="submit" class="btn-row-action">{% if p.allow_email_link %}Stop email linking{% else %}Allow email linking{% endif %}</button>
        </form>
        <form method="post" action="{{ console_prefix }}/sso-shared/{{ p.id }}/delete" class="inline-form"
              data-confirm="Delete shared provider {{ p.slug }}? Users linked to it can no longer sign in with it.">
          {{ csrf_input | default(value='') | safe }}
          <button type="submit" class="btn-row-action">Delete</button>
        </form>
        {% endif %}
      </td>
    </tr>
    {#- A full-width row, so a long URL wraps instead of widening the table. -#}
    <tr class="sso-callback-row">
      <td colspan="8">
        <span class="field-helper">Callback URL</span> <code>{{ p.callback.pattern }}</code>
        {% if p.callback.example %}<span class="field-helper">e.g.</span> <code>{{ p.callback.example }}</code>{% endif %}
      </td>
    </tr>
    {% endfor %}
  </tbody>
</table>
{% endif %}

{% if edit_enabled %}
<form method="post" action="{{ console_prefix }}/sso-shared" class="edit-form" autocomplete="off">
  {{ csrf_input | default(value='') | safe }}
  <fieldset class="section">
    <legend>Add a shared provider</legend>
    {% include "_op_sso_shared_form.html" %}
  </fieldset>
  <p class="form-actions">
    <button type="submit" class="btn btn-primary">Add provider</button>
  </p>
</form>
{% endif %}
{% endblock %}