Auth/User avancee¶
Auth/User est la brique optionnelle de Forge pour representer une identite utilisateur moderne sans transformer le framework en application metier. Elle fournit des contrats Python, des helpers explicites et des SQL visibles que les projets peuvent adopter progressivement.
Vue d'ensemble¶
Auth/User repond a la question : qui est l'utilisateur ? RBAC repond a la
question : qu'a-t-il le droit de faire ? Les deux briques peuvent etre
reliees par user_roles, mais restent separees.
Principes :
- pas d'ORM impose ;
- pas de modele utilisateur metier riche impose ;
- SQL optionnels visibles dans
mvc/models/sql/; - aucune route login/reset/MFA/OIDC generee automatiquement ;
- aucune ecriture en base cachee dans les helpers de contrat ;
- aucune permission stockee dans
users; - logique applicative et politique de securite finale cote projet.
Les modules Auth/User disponibles couvrent aujourd'hui :
- utilisateur local ;
- mot de passe Argon2id ;
- session utilisateur ;
- tokens a usage limite ;
- verification email ;
- reset password ;
- MFA TOTP, recovery codes, challenge et revalidation ;
- OIDC local avec
state,nonceet PKCE ; - pont Auth/User vers RBAC ;
- administration CLI utilisateurs ;
- audit Auth ;
- rate limit Auth.
Contrat utilisateur¶
AuthUser est le contrat minimal d'un utilisateur authentifiable.
from dataclasses import dataclass
from typing import Any
@dataclass(frozen=True)
class AuthUser:
id: int
email: str
password_hash: str
is_active: bool = True
created_at: Any | None = None
updated_at: Any | None = None
API :
normalize_auth_user(data) -> AuthUservalidate_auth_user_contract(data)is_valid_auth_user(user) -> boolInvalidAuthUserError
id doit etre strictement positif, email non vide, password_hash non vide
et is_active booleen. Forge ne demande pas de nom, avatar, telephone, adresse,
profil proprietaire ou statut metier.
users.sql¶
forge auth:init cree ou preserve :
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
email_verified_at DATETIME NULL,
last_login_at DATETIME NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
email_verified_at et last_login_at sont des colonnes utiles aux flux
applicatifs. Forge ne les met pas a jour automatiquement.
Mot de passe¶
Forge fournit le hachage et la verification de mot de passe avec Argon2id.
from core.auth import hash_password, verify_password, password_needs_rehash
password_hash = hash_password("mot-de-passe")
ok = verify_password("mot-de-passe", password_hash)
needs = password_needs_rehash(password_hash)
API :
hash_password(password)verify_password(password, password_hash) -> boolpassword_needs_rehash(password_hash) -> boolvalidate_new_password(password)InvalidNewPasswordError
Le mot de passe clair n'est jamais stocke. Le reset password valide seulement une regle minimale de nouveau mot de passe : chaine non vide et longueur minimale. Les politiques plus complexes appartiennent aux applications.
Session utilisateur¶
La session Auth/User stocke uniquement l'identifiant utilisateur local sous une
cle de session interne. Elle ne stocke ni email, ni password_hash, ni objet
AuthUser complet.
from core.auth import authenticate_user, login_user, logout_user
user = authenticate_user(email, password, load_user_by_email)
if user is not None:
login_user(request, user)
logout_user(request)
API :
authenticate_user(email, password, user_loader) -> AuthUser | Nonelogin_user(request, user) -> Nonelogout_user(request) -> Noneget_authenticated_user_id(request) -> int | Nonecurrent_user(request, user_loader) -> AuthUser | Noneis_authenticated(request) -> boollogin_required
authenticate_user appelle un loader fourni par l'application. Il refuse les
utilisateurs inactifs et retourne None pour les echecs normaux. Il ne fait pas
de requete SQL lui-meme.
current_user recharge l'utilisateur via un loader applicatif. Si la session
est absente, invalide, si le loader retourne None, ou si l'utilisateur est
inactif, le resultat est None.
@login_required protege une fonction controleur. Il retourne 401 par defaut
ou peut rediriger si redirect_to est fourni.
Tokens Auth¶
AuthToken represente un jeton securise a usage limite. Le token brut est donne
une seule fois a l'application ; seul son hash est stockable.
from core.auth import generate_auth_token, hash_auth_token, verify_auth_token
raw_token = generate_auth_token()
token_hash = hash_auth_token(raw_token)
ok = verify_auth_token(raw_token, token_hash)
Structure :
@dataclass(frozen=True)
class AuthToken:
user_id: int
purpose: str
token_hash: str
expires_at: datetime
used_at: datetime | None = None
created_at: datetime | None = None
API :
generate_auth_token(nbytes=32)hash_auth_token(token)verify_auth_token(token, token_hash)token_expires_at(minutes=60, now=None)is_token_expired(expires_at, now=None)is_token_usable(token_record, purpose=None, now=None)normalize_auth_token(data)validate_auth_token_contract(data)is_valid_auth_token(token_record)
auth_tokens.sql¶
CREATE TABLE IF NOT EXISTS auth_tokens (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
purpose VARCHAR(80) NOT NULL,
token_hash CHAR(64) NOT NULL UNIQUE,
expires_at DATETIME NOT NULL,
used_at DATETIME NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX idx_auth_tokens_user_purpose (user_id, purpose),
INDEX idx_auth_tokens_expires_at (expires_at),
CONSTRAINT fk_auth_tokens_user_id
FOREIGN KEY (user_id)
REFERENCES users(id)
ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
used_at permet a l'application de marquer un token comme consomme. Forge ne
met pas cette colonne a jour automatiquement.
Verification email¶
La verification email s'appuie sur les tokens generiques.
from core.auth import (
EMAIL_VERIFICATION_PURPOSE,
create_email_verification_token,
verify_email_verification_token,
email_verification_timestamp,
is_email_verified,
)
raw_token, token_record = create_email_verification_token(user_id=1)
ok = verify_email_verification_token(raw_token, token_record)
Responsabilites de l'application :
- stocker
token_record.token_hashdansauth_tokens; - envoyer le token brut dans un lien ;
- appeler
verify_email_verification_tokenau retour ; - renseigner
users.email_verified_at; - renseigner
auth_tokens.used_at.
Forge ne fournit pas d'envoi automatique d'email, route de confirmation, controleur ou template.
Mot de passe oublie¶
Le reset password se fait en deux etapes : creation/verifications du token, puis production d'un nouveau hash.
from core.auth import create_password_reset_token, reset_password_with_token
raw_token, token_record = create_password_reset_token(user_id=1)
result = reset_password_with_token(raw_token, token_record, "nouveau-mot-de-passe")
if result is not None:
# users.password_hash = result.password_hash
# auth_tokens.used_at = result.used_at
pass
API :
PASSWORD_RESET_PURPOSEcreate_password_reset_token(user_id, minutes=30, now=None)verify_password_reset_token(token, token_record, now=None)password_reset_timestamp(now=None)create_password_reset_request(user, minutes=30, now=None)reset_password_with_token(token, token_record, new_password, now=None)PasswordResetRequestPasswordResetResult
PasswordResetResult contient user_id, password_hash et used_at. Il ne
contient jamais le mot de passe clair ni le token brut. Forge ne fait aucune
ecriture DB automatique.
MFA¶
Forge fournit le socle MFA par briques :
- contrat et table des facteurs ;
- TOTP ;
- codes de recuperation ;
- challenge MFA a la connexion ;
- revalidation MFA pour actions sensibles.
Facteurs MFA¶
AuthMfaFactor decrit user_id, factor_type, secret_hash, status,
label, confirmed_at, last_used_at, created_at et updated_at.
Statuts :
pendingactivedisabled
Types :
totprecovery
auth_mfa_factors.sql¶
CREATE TABLE IF NOT EXISTS auth_mfa_factors (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
factor_type VARCHAR(40) NOT NULL,
secret_hash VARCHAR(255) NOT NULL,
status VARCHAR(40) NOT NULL DEFAULT 'pending',
label VARCHAR(120) NULL,
confirmed_at DATETIME NULL,
last_used_at DATETIME NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_auth_mfa_factors_user_id (user_id),
INDEX idx_auth_mfa_factors_user_status (user_id, status),
CONSTRAINT fk_auth_mfa_factors_user_id
FOREIGN KEY (user_id)
REFERENCES users(id)
ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Le champ historique s'appelle secret_hash. Pour TOTP, il contient le secret
stocke par l'application. Il doit etre protege en base. Forge ne doit jamais
afficher ce secret, ni l'inclure dans un audit ou une tentative rate limit.
TOTP¶
API principale :
generate_totp_secret()totp_provisioning_uri(secret, account_name, issuer="Forge")create_totp_factor(user_id, secret, label=None)confirm_totp_factor(factor, code, secret, now=None)verify_totp_code(secret, code, now=None)
Le secret brut est necessaire pour verifier les codes. Sa persistance securisee reste une responsabilite applicative.
Codes de recuperation¶
API :
create_recovery_codes(user_id, count=10)generate_recovery_code()hash_recovery_code(code)verify_recovery_code(code, code_hash)consume_recovery_code(code, code_record, now=None)
create_recovery_codes retourne des codes bruts a afficher une seule fois et
des records hashables. Les codes bruts ne doivent jamais etre stockes.
auth_mfa_recovery_codes.sql¶
CREATE TABLE IF NOT EXISTS auth_mfa_recovery_codes (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
code_hash CHAR(64) NOT NULL UNIQUE,
used_at DATETIME NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_auth_mfa_recovery_codes_user_id (user_id),
INDEX idx_auth_mfa_recovery_codes_used_at (used_at),
CONSTRAINT fk_auth_mfa_recovery_codes_user_id
FOREIGN KEY (user_id)
REFERENCES users(id)
ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Challenge MFA¶
API :
start_mfa_challenge(request, user, now=None)has_pending_mfa_challenge(request, max_age_minutes=10, now=None)get_mfa_challenge_user_id(request)verify_mfa_challenge(request, code, factors, recovery_codes=(), now=None)clear_mfa_challenge(request)MfaChallengeResult
Le challenge stocke seulement user_id et un timestamp en session. Il ne
connecte pas l'utilisateur automatiquement.
Revalidation MFA¶
API :
require_recent_mfa(request, max_age_minutes=15, now=None)verify_mfa_revalidation(request, code, factors, recovery_codes=(), now=None)mark_mfa_revalidated(request, user_id, now=None)has_recent_mfa_revalidation(request, max_age_minutes=15, now=None)clear_mfa_revalidation(request)MfaRevalidationResult
La revalidation sert aux actions sensibles deja authentifiees : changement de mot de passe, action admin, export sensible, etc.
OIDC¶
OIDC est fourni comme socle local et generique. Forge gere les structures, la preparation du flux et la validation locale du callback, mais ne fait pas encore l'echange reseau de code contre token et ne valide pas de JWT.
Fournisseur et client¶
OidcProvider contient le nom du fournisseur et ses endpoints. OidcClientConfig
contient le fournisseur, client_id, client_secret, redirect_uri et scopes.
API :
normalize_oidc_providervalidate_oidc_provider_contractis_valid_oidc_providernormalize_oidc_client_configvalidate_oidc_client_config_contractis_valid_oidc_client_config
State, nonce et PKCE¶
API :
generate_oidc_state()generate_oidc_nonce()generate_pkce_code_verifier()pkce_code_challenge_s256(code_verifier)start_oidc_login(request, config, now=None)has_pending_oidc_login(request, max_age_minutes=10, now=None)validate_oidc_callback(request, provider_name, state, code, max_age_minutes=10, now=None)clear_oidc_login(request)
start_oidc_login stocke en session uniquement les donnees temporaires
necessaires : fournisseur, state, nonce, code verifier, redirect URI et timestamp.
Identite externe et compte local¶
API :
OidcExternalIdentityAuthOidcAccountnormalize_oidc_external_identitycreate_oidc_account_link(user_id, identity, now=None)oidc_identity_key(identity)oidc_account_matches_identity(account, identity)
AuthOidcAccount relie un utilisateur local a un couple stable
provider + subject. L'email OIDC peut changer et ne doit pas servir de cle
stable.
auth_oidc_accounts.sql¶
CREATE TABLE IF NOT EXISTS auth_oidc_accounts (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
provider VARCHAR(120) NOT NULL,
subject VARCHAR(255) NOT NULL,
email VARCHAR(255) NULL,
email_verified BOOLEAN NULL,
name VARCHAR(255) NULL,
last_login_at DATETIME NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uq_auth_oidc_accounts_provider_subject (provider, subject),
INDEX idx_auth_oidc_accounts_user_id (user_id),
CONSTRAINT fk_auth_oidc_accounts_user_id
FOREIGN KEY (user_id)
REFERENCES users(id)
ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Forge ne stocke aucun access_token, refresh_token ou id_token dans cette
table. L'ancien SQL auth_oidc_identities.sql reste preserve pour compatibilite
avec l'etat de developpement existant.
Auth/User vers RBAC¶
user_roles est le pont optionnel entre les utilisateurs locaux et les roles
RBAC existants.
from core.auth import (
create_auth_user_role,
get_user_permissions,
get_user_role_ids,
user_has_permission,
require_user_permission,
)
Flux de resolution :
user_roles.sql¶
CREATE TABLE IF NOT EXISTS user_roles (
user_id INT NOT NULL,
role_id INT NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (user_id, role_id),
INDEX idx_user_roles_user_id (user_id),
INDEX idx_user_roles_role_id (role_id),
CONSTRAINT fk_user_roles_user_id
FOREIGN KEY (user_id)
REFERENCES users(id)
ON DELETE CASCADE,
CONSTRAINT fk_user_roles_role_id
FOREIGN KEY (role_id)
REFERENCES roles(id)
ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
require_user_permission("article.edit") lit l'utilisateur Auth/User connecte
et interroge le resolver user_roles -> roles -> permissions. Il retourne 401
si aucun utilisateur Auth/User n'est connecte et 403 si la permission manque.
Difference avec le RBAC historique¶
@require_permission(...), fourni par core.security.rbac, reste le decorateur
historique. Il lit les permissions deja presentes dans request.permissions ou
dans la session RBAC historique. Il ne lit pas automatiquement user_roles.
require_user_permission(...), fourni par core.auth, est le decorateur serveur
pour Auth/User + RBAC.
can(...) dans Jinja est un helper d'affichage. Il peut utiliser le contexte
Auth/User injecte par BaseController.render(..., request=request) ou le mode
historique. Il ne remplace jamais une protection serveur.
Administration CLI¶
Les commandes Auth/User disponibles dans cette copie de Forge sont :
forge auth:init
forge auth:doctor
forge auth:status
forge auth:list-sql
forge auth:user:create --email admin@example.com --password-prompt
forge auth:user:list
forge auth:user:show --email admin@example.com
forge auth:user:disable --email user@example.com
forge auth:user:enable --email user@example.com
forge auth:user:password --email user@example.com --password-prompt
forge auth:user:role:add --email user@example.com --role admin
forge auth:user:role:remove --email user@example.com --role admin
forge auth:user:roles --email user@example.com
forge auth:init cree ou preserve les SQL optionnels suivants :
users.sqlauth_tokens.sqlauth_mfa_factors.sqlauth_mfa_recovery_codes.sqlauth_oidc_accounts.sqlauth_oidc_identities.sqluser_roles.sqlauth_audit_log.sqlauth_rate_limit_attempts.sql
La commande ne cree aucun utilisateur, aucun token, aucun facteur MFA, aucun compte OIDC, aucun role utilisateur, aucun audit et aucune tentative rate limit. Elle n'applique pas non plus le SQL.
Les commandes d'administration utilisateur n'affichent aucun mot de passe,
hash, token ou secret MFA. Elles s'appuient sur la configuration projet
(config.py, env/dev, variables DB_APP_*) et sur la table optionnelle
users.
Les commandes de roles utilisateur manipulent uniquement la table optionnelle
user_roles :
auth:user:role:addattribue a un utilisateur un role RBAC deja existant ;auth:user:role:removeretire cette association ;auth:user:rolesliste les roles attribues.
Elles ne creent aucun utilisateur, aucun role et aucune permission. Les roles
et permissions restent definis par le RBAC (roles, permissions,
role_permissions). Le parametre --role accepte un id numerique, un slug ou
un nom de role existant.
Audit Auth¶
AuthAuditEvent represente un evenement d'audit Auth/User lisible et stockable.
from core.auth import AUTH_EVENT_LOGIN_SUCCESS, create_auth_audit_event
event = create_auth_audit_event(
event_type=AUTH_EVENT_LOGIN_SUCCESS,
user_id=1,
ip_address="192.0.2.10",
user_agent="Mozilla/5.0",
metadata={"method": "password"},
)
API :
AuthAuditEventnormalize_auth_audit_event(data)validate_auth_audit_event_contract(data)is_valid_auth_audit_event(event)create_auth_audit_event(...)sanitize_auth_audit_metadata(metadata)
Evenements standards :
login.successlogin.failedlogoutpassword_reset.requestedpassword_reset.completedemail.verifiedmfa.challenge.successmfa.challenge.failedmfa.revalidation.successmfa.revalidation.faileduser.disableduser.enableduser.password_changeduser_role.addeduser_role.removedoidc.account_linked
metadata est nettoye avant stockage applicatif. Les cles sensibles retirees
incluent password, password_hash, token, raw_token, access_token,
refresh_token, id_token, secret, secret_hash, totp_secret,
recovery_code et code_verifier.
auth_audit_log.sql¶
CREATE TABLE IF NOT EXISTS auth_audit_log (
id INT AUTO_INCREMENT PRIMARY KEY,
event_type VARCHAR(120) NOT NULL,
user_id INT NULL,
actor_user_id INT NULL,
ip_address VARCHAR(45) NULL,
user_agent VARCHAR(255) NULL,
metadata_json TEXT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX idx_auth_audit_log_event_type (event_type),
INDEX idx_auth_audit_log_user_id (user_id),
INDEX idx_auth_audit_log_actor_user_id (actor_user_id),
INDEX idx_auth_audit_log_created_at (created_at),
CONSTRAINT fk_auth_audit_log_user_id
FOREIGN KEY (user_id)
REFERENCES users(id)
ON DELETE SET NULL,
CONSTRAINT fk_auth_audit_log_actor_user_id
FOREIGN KEY (actor_user_id)
REFERENCES users(id)
ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Forge ne branche pas automatiquement l'audit dans login/reset/MFA/OIDC/admin.
Rate limit Auth¶
Le rate limit Auth/User represente des tentatives d'actions sensibles et calcule une decision anti-bruteforce a partir des tentatives chargees par l'application.
from core.auth import (
AUTH_RATE_LIMIT_LOGIN,
AuthRateLimitRule,
check_auth_rate_limit,
create_auth_rate_limit_attempt,
)
rule = AuthRateLimitRule(
action=AUTH_RATE_LIMIT_LOGIN,
max_attempts=5,
window_seconds=900,
)
decision = check_auth_rate_limit(
action=AUTH_RATE_LIMIT_LOGIN,
key=email,
attempts=load_attempts(email),
rule=rule,
)
API :
AuthRateLimitAttemptAuthRateLimitRuleAuthRateLimitDecisionnormalize_rate_limit_key(value)normalize_auth_rate_limit_attempt(data)validate_auth_rate_limit_attempt_contract(data)is_valid_auth_rate_limit_attempt(attempt)normalize_auth_rate_limit_rule(data)validate_auth_rate_limit_rule_contract(data)is_valid_auth_rate_limit_rule(rule)create_auth_rate_limit_attempt(...)check_auth_rate_limit(...)
Actions standards :
loginpassword_resetmfa_challengemfa_revalidationoidc_callback
check_auth_rate_limit compte uniquement les echecs success=False pour le
couple action + key dans la fenetre window_seconds. Les succes, les autres
actions, les autres cles et les tentatives hors fenetre sont ignores. Si la
limite est atteinte, la decision contient retry_after_seconds.
auth_rate_limit_attempts.sql¶
CREATE TABLE IF NOT EXISTS auth_rate_limit_attempts (
id INT AUTO_INCREMENT PRIMARY KEY,
action VARCHAR(120) NOT NULL,
rate_key VARCHAR(255) NOT NULL,
ip_address VARCHAR(45) NULL,
user_id INT NULL,
success BOOLEAN NOT NULL DEFAULT FALSE,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX idx_auth_rate_limit_action_key (action, rate_key),
INDEX idx_auth_rate_limit_created_at (created_at),
INDEX idx_auth_rate_limit_user_id (user_id),
CONSTRAINT fk_auth_rate_limit_user_id
FOREIGN KEY (user_id)
REFERENCES users(id)
ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
La colonne SQL s'appelle rate_key, car key peut etre ambigu. Forge ne stocke
aucun mot de passe, token ou secret dans les tentatives et ne branche pas
automatiquement cette protection dans les flux Auth.
Flux recommandes¶
Login classique sans MFA¶
from core.auth import authenticate_user, login_user
user = authenticate_user(email, password, load_user_by_email)
if user is None:
return invalid_credentials_response()
login_user(request, user)
return redirect("/dashboard")
L'application peut ensuite mettre a jour users.last_login_at, stocker un audit
login.success, ou enregistrer une tentative rate limit reussie si elle le
souhaite.
Login avec MFA¶
from core.auth import authenticate_user, is_mfa_enabled, start_mfa_challenge, login_user
user = authenticate_user(email, password, load_user_by_email)
if user is None:
return invalid_credentials_response()
factors = load_mfa_factors(user.id)
if is_mfa_enabled(factors):
start_mfa_challenge(request, user)
return show_mfa_form()
login_user(request, user)
Puis, dans l'etape MFA :
from core.auth import verify_mfa_challenge, login_user
result = verify_mfa_challenge(
request,
code,
factors=load_mfa_factors(user_id),
recovery_codes=load_recovery_codes(user_id),
)
if result is None:
return invalid_mfa_response()
user = load_user_by_id(result.user_id)
login_user(request, user)
Forge ne persiste pas last_used_at ou used_at automatiquement.
Reset password¶
raw_token, token_record = create_password_reset_token(user_id=1)
# stocker token_record.token_hash, envoyer raw_token
result = reset_password_with_token(raw_token, token_record, new_password)
if result is not None:
# users.password_hash = result.password_hash
# auth_tokens.used_at = result.used_at
pass
Verification email¶
raw_token, token_record = create_email_verification_token(user_id=1)
# stocker token_record.token_hash, envoyer raw_token
if verify_email_verification_token(raw_token, token_record):
verified_at = email_verification_timestamp()
# users.email_verified_at = verified_at
# auth_tokens.used_at = verified_at
OIDC local jusqu'au callback¶
Au callback :
callback = validate_oidc_callback(request, "google", state, code)
if callback is None:
return invalid_oidc_response()
# Echanger callback.code contre les tokens cote application.
# Valider le JWT et le nonce cote application.
# Creer ou retrouver AuthOidcAccount cote application.
Protection route avec require_user_permission¶
from core.auth import require_user_permission
@require_user_permission("articles.edit")
def edit_article(request, article_id):
...
Pour l'affichage :
Le helper Jinja masque l'action ; le decorateur serveur protege la route.
Action sensible avec revalidation MFA¶
from core.auth import require_recent_mfa
def change_password(request):
if not require_recent_mfa(request):
return redirect("/mfa/revalidate")
...
Puis :
result = verify_mfa_revalidation(
request,
code,
factors=load_mfa_factors(user_id),
recovery_codes=load_recovery_codes(user_id),
)
if result is None:
return invalid_mfa_response()
Rate limit autour d'un login applicatif¶
from core.auth import (
AUTH_RATE_LIMIT_LOGIN,
AuthRateLimitRule,
check_auth_rate_limit,
create_auth_rate_limit_attempt,
)
rule = AuthRateLimitRule(
action=AUTH_RATE_LIMIT_LOGIN,
max_attempts=5,
window_seconds=900,
)
decision = check_auth_rate_limit(
action=AUTH_RATE_LIMIT_LOGIN,
key=email,
attempts=load_login_attempts(email),
rule=rule,
)
if not decision.allowed:
return too_many_attempts(decision.retry_after_seconds)
user = authenticate_user(email, password, load_user_by_email)
attempt = create_auth_rate_limit_attempt(
action=AUTH_RATE_LIMIT_LOGIN,
key=email,
ip_address=request.ip,
success=user is not None,
)
# stocker attempt dans auth_rate_limit_attempts
Limites restantes¶
Forge ne fournit pas encore :
- interface HTML admin utilisateurs ;
- routes Auth generees automatiquement ;
- middleware global Auth/User ;
- envoi automatique d'emails ;
- echange reseau OIDC code -> token ;
- validation cryptographique JWT ;
- WebAuthn / passkeys ;
- SAML ;
- OAuth multi-provider avance ;
- multi-tenant Auth/User ;
- consultation CLI ou HTML du journal d'audit ;
- consultation CLI ou HTML des tentatives rate limit ;
- politiques complexes d'organisation ou de delegation admin.
Ces limites sont volontaires. Forge fournit des briques explicites ; les applications choisissent leurs flux, leurs routes, leur persistance et leurs politiques metier.