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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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.