Skip to main content

sagecoders-auth

SDK Python de SageAuth (comptes SageCoders). Il ne dépend d'aucun framework : FastAPI, Django, Flask ou script.

pip install sagecoders-auth

Ce qu'il fait :

  • API : vérification des access tokens (clés publiques en cache, rotation) et des rôles du projet.
  • Connexion d'une application web : code + PKCE, retour (callback), rafraîchissement, déconnexion, déconnexion back-channel.
  • Comptes de service : jeton automatique (JWT bearer), import de comptes, déclaration des rôles et du catalogue d'événements, attribution des rôles.
  • Webhooks de compte : vérification de la signature.
  • SageEvent : droits d'émission et d'écoute (registre des événements).

Toutes les erreurs sont des SageAuthError avec un code stable et un status HTTP.

Protéger une API

from sagecoders_auth import SageAuth, SageAuthError

auth = SageAuth(issuer="https://account.sagecoders.com", project_id="<ID du projet>")

user = auth.authenticate(request_authorization_header, roles=["hotel.manager"])  # SageUser
user.sub, user.roles, user.phone, user.email

FastAPI

from fastapi import Depends, FastAPI, Header, HTTPException

def current_user(authorization: str | None = Header(None)):
    try:
        return auth.authenticate(authorization)
    except SageAuthError as e:
        raise HTTPException(e.status or 401, e.code)

def require(*roles):
    def dep(user=Depends(current_user)):
        if not user.has_role(roles):
            raise HTTPException(403, "forbidden")
        return user
    return dep

@app.get("/rooms")
def rooms(user=Depends(require("hotel.manager"))): ...

Django (middleware) : request.sage_user = auth.authenticate(request.headers.get("Authorization")), avec renvoi de JsonResponse({"error": e.code}, status=e.status) sur SageAuthError.

Flask : même appel dans un before_request ; garder l'utilisateur dans g.user.

Ne contactez pas SageAuth à chaque requête : les clés sont gardées 10 minutes, rechargées si une nouvelle clé apparaît, et conservées si SageAuth est momentanément injoignable.

Connexion d'une application web

Le SDK ne touche pas aux cookies : gardez state, nonce et code_verifier dans votre session.

auth = SageAuth(issuer=ISSUER, client_id="<client_id>", redirect_uri="https://app.exemple.ci/auth/callback", project_id="<ID projet>")

# /auth/login
req = auth.create_authorization()            # prompt="none", max_age=…, login_hint=…, offline=True
session["sageauth"] = {"state": req.state, "nonce": req.nonce, "code_verifier": req.code_verifier}
return redirect(req.url)

# /auth/callback
s = session.pop("sageauth")
result = auth.handle_callback(str(request.url), **s)   # vérifie state, code, ID token, nonce
session["user"] = result.user.sub                       # result.tokens.refresh_token si offline=True

# /auth/logout
return redirect(auth.logout_url(id_token_hint=..., post_logout_redirect_uri="https://app.exemple.ci/"))

# déconnexion back-channel (POST form logout_token)
who = auth.verify_logout_token(form["logout_token"])     # {"sub": ..., "sid": ...}

Confirmer une action sensible par un code (mode vérification)

L'utilisateur reçoit un code sur son numéro ou son email, non modifiable :

from dataclasses import asdict

req = auth.create_authorization(verify="phone", login_hint=user.phone)   # ou verify="email"
session["confirm"] = {"state": req.state, "nonce": req.nonce, "code_verifier": req.code_verifier,
                      "verify": asdict(req.verify), "expected_sub": user.sub}
return redirect(req.url)

# callback : compte, contact vérifié, vérification de moins de 5 min (max_auth_age)
auth.handle_callback(str(request.url), **session.pop("confirm"))   # wrong_account, contact_not_verified, stale_authentication

Compte de service

Clé JSON téléchargée dans la console (Projets → votre projet → Comptes de service), à mettre dans une variable d'environnement, idéalement en base64 :

import os
from sagecoders_auth import ServiceAccount

svc = ServiceAccount(issuer=ISSUER, key=os.environ["SAGEAUTH_SERVICE_KEY_B64"], sage_auth_project_id="<ID projet SageAuth>")

# Rôles et permissions (interrupteur « Déclare les rôles et événements du projet »)
svc.roles.sync(PROJECT_ID, [
    {"key": "hotel.manager", "name": "Gérant", "permissions": ["rooms:write", "bookings:*"]},
    {"key": "hotel.staff", "name": "Réception", "permissions": ["bookings:write"]},
], prune=True)
svc.roles.set_for_user(PROJECT_ID, sub, ["hotel.manager"])
perms = svc.roles.of_user(PROJECT_ID, sub)["permissions"]   # à garder en cache

from sagecoders_auth import has_permission
has_permission(perms, "console:data:read:sage_hotel")   # « console:* », « console:data:* » et « * » sont des jokers
has_permission(perms, ["a:read", "b:read"], all=True)

# Import de comptes existants (rôle accounts.import sur SageAuth)
svc.import_users("paradapi", [{"externalId": "42", "givenName": "Aya", "familyName": "Kouassi", "phone": "+2250700000000"}])

# Catalogue d'événements (espace de noms choisi dans la console)
svc.events.sync(PROJECT_ID, [{"key": "sagehotel.booking.created", "name": "Réservation créée", "version": 1}])

# Appeler l'API d'un autre projet sans utilisateur
token = svc.get_token(audience="<ID du projet appelé>")

SageEvent

Compte de service avec le rôle events.check sur le projet SageAuth :

svc.events.check("publish", "sagemessaging.message.sent", sub="<compte de service émetteur>")
svc.events.check("subscribe", "sagehotel.booking.*", client_id="<application du navigateur SSE>")
svc.events.rights(sub="...")      # à garder 1 min en cache
svc.events.catalog()              # à garder 5 min en cache
svc.events.grants()               # autorisations entre projets (owner, grantee, action, pattern)

Annuaire (lecture seule)

Compte de service avec le rôle directory.read sur le projet SageAuth. Aucune donnée de sécurité.

svc.directory.organizations()                    # {"items": [{id, name, createdAt, products}], "nextCursor": ...}
svc.directory.users(query="aya", cursor=None)    # {"items": [{sub, name, email, orgId, orgName, state, createdAt}], "nextCursor": ...}
svc.directory.service_accounts(project_id=...)   # [{userId, name, projectId, projectName}]

Webhooks

from sagecoders_auth import verify_webhook
event = verify_webhook(raw_body, request.headers.get("SageAuth-Signature"), os.environ["SAGEAUTH_WEBHOOK_SECRET"])

Passez le corps brut, avant tout parsing JSON.

Développement

python -m venv .venv && .venv/Scripts/pip install -e .   # .venv/bin/pip sous Linux
python -m unittest discover -s tests

Metadata

Release files for sagecoders-auth 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for sagecoders-auth 0.2.0
File Size Uploaded Provenance Actions
sagecoders_auth-0.2.0.tar.gz 16.2 kB Without provenance Details for sagecoders_auth-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for sagecoders-auth 0.2.0
File Interpreter ABI Platform Actions
sagecoders_auth-0.2.0-py3-none-any.whl Python 3 none any Details for sagecoders_auth-0.2.0-py3-none-any.whl

Total release size: 33.8 kB

Release files / sagecoders_auth-0.2.0.tar.gz

Download URL sagecoders_auth-0.2.0.tar.gz
Size 16.2 kB
Tags Source
SHA-256 checksum
How to use checksums
7c84f558a78d25eb5feac4c8a8e5ae34c8dfb9a3945543187495aa0a87a8d580
BLAKE2b-256 checksum
How to use checksums
fa81669371abb15de68b2fb8f64e7d84c0e4abc77f3219525d5e1f434b1cf5fc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.9

Release files / sagecoders_auth-0.2.0-py3-none-any.whl

Download URL sagecoders_auth-0.2.0-py3-none-any.whl
Size 17.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0bfea4f048e4ff7180407d5fc92a90c3dbc86f53a62a4a39846ad2f8bae1e421
BLAKE2b-256 checksum
How to use checksums
78aa60cc630c87c7d39c61762df69ff204df02e151fe12bb6c561c8dead2b5d0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.9

Security for sagecoders-auth 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page