Handlers & Routage
Chaque handler, ses actions, et comment Router.bx relie les URL à ceux-ci.
Sur cette page
Handlers & Routage
Carte des handlers
| Handler | Base | Objet |
|---|---|---|
AuditLog.bx | BaseSecureHandler | Parcours, export, et purge du journal d'audit |
Assets.bx | EventHandler | Diffuse les avatars utilisateur et le logo de marque |
Auth.bx | EventHandler | Connexion, inscription, invitations, réinitialisation de mot de passe - tout public |
BaseSecureHandler.bx | RestHandler | Classe de base pour chaque handler d'administration |
Dashboard.bx | BaseSecureHandler | La page d'accueil authentifiée |
Main.bx | EventHandler | Handler d'événement implicite - voir Architecture |
Permissions.bx | BaseSecureHandler | CRUD des slugs de permission |
Profile.bx | BaseSecureHandler | Profil en libre-service, mot de passe, jetons API, passkeys |
Roles.bx | BaseSecureHandler | CRUD des rôles + assignation d'utilisateurs |
Settings.bx | BaseSecureHandler | Registre des paramètres de l'application |
Users.bx | BaseSecureHandler | Administration des utilisateurs |
BaseSecureHandler
Chaque handler protégé étend BaseSecureHandler, dont le preHandler vérifie le CSRF sur chaque requête modifiant l'état, impose la mise en page Admin, et redirige vers profile/passkey-required lorsque cbRequirePasskey est activé et que l'utilisateur n'en a aucune. Il fournit aussi des helpers partagés (getApiResults(), ensureSortDirection(), getPagination()) :
component extends="coldbox.system.RestHandler" {
function preHandler( event, rc, prc ){
// ...CSRF verification, deny-by-default...
event.setLayout( "Admin" );
// ...passkey enforcement...
}
}
Créer un nouveau handler sécurisé commence toujours de la même façon :
component extends="BaseSecureHandler" secured {
function index( event, rc, prc ){
prc.pageTitle = "My Page";
event.setView( "myhandler/index" );
}
}
AuditLog
@secured("auditlog:admin,auditlog:read") au niveau de la classe ; toutes les actions sauf index sont @remote :
index,search,show- parcourir et filtrer le journal d'auditexport-@secured("auditlog:admin,auditlog:export"), diffuse un CSVpurge-@secured("auditlog:admin,auditlog:delete"), supprime les entrées antérieures à une date limiteclear-@secured("auditlog:admin"), supprime toutes les entrées
Assets
Aucune annotation @secured au niveau de la classe - il diffuse des fichiers binaires depuis le disque cbfs privé assets (voir Base de données & ORM et app/config/modules/cbfs.bx), qui se trouve hors de la racine web et est autrement inaccessible :
avatar-@secured(tout utilisateur authentifié), diffuse la variante JPEGsm/lgde l'avatar d'un utilisateurlogo- public, diffuse la variante PNGsm/lgdu logo de marque afin que l'écran de connexion et d'autres pages invitées puissent l'afficher
Les deux actions renvoient un 404 (plutôt qu'une erreur) pour une forme de userId/size non reconnue ou lorsque le fichier demandé n'existe tout simplement pas, afin qu'un appelant ne puisse pas distinguer « pas d'avatar » de « aucun utilisateur de ce type » à partir de la seule forme de la réponse. Le redimensionnement, le recadrage, et le stockage passent par ImageService (app/models/system/ImageService.bx), invoqué via getInstance() à l'intérieur de chaque action plutôt qu'une propriété @inject - voir le docblock de Assets.bx pour la raison (une particularité de l'ordre de démarrage de WireBox avec la construction de singleton déclenchée par le handler).
Auth
Aucune annotation @secured - ces actions doivent rester accessibles aux invités :
login/doLogin(GET/POST) - vérifié CSRF, appellesecurityService.login(), prend en chargerememberMeregister/doRegister- conditionné par le paramètrecbAllowRegistrationcheckEmailAvailability- point de terminaison JSON pour les vérifications de disponibilité d'email en directverifyRegistration- consomme un jeton d'actionPURPOSE_REGISTRATIONactivateInvitation/doActivateInvitation- définit un mot de passe pour un utilisateur invité, créé par un administrateurforgotPassword/doForgotPassword- conditionné parcbAllowForgotPasswordresetPassword/doResetPassword- valide le jeton de réinitialisation, définit un nouveau mot de passeverifyEmailChange- consomme un jeton d'actionPURPOSE_EMAIL_CHANGElogout- appellesecurityService.logout()
preHandler redirige un visiteur déjà authentifié directement vers le tableau de bord, et définit la mise en page à partir de prc.settings.cbLoginLayout (AuthSplit par défaut - voir guides/security.md) ; verifyEmailChange et logout sont exemptés de cette redirection afin qu'ils restent accessibles que le visiteur soit déjà authentifié ou non.
Dashboard
@secured (tout utilisateur authentifié, aucune permission spécifique requise) :
index- l'accueil du tableau de bordnotAuthorized- la cible deinvalidAuthorizationEvent, affichée lorsqu'un utilisateur authentifié manque d'une permission requise
Permissions
@secured("permissions:admin,permissions:read") au niveau de la classe :
indexcreate-@secured("permissions:admin,permissions:write")update/delete-@remote, mêmes permissions d'écriture/suppression
Profile
Actions en libre-service @secured pour l'utilisateur courant, toutes des points de terminaison AJAX @remote sauf index :
index,passkeyRequiredsave,doPasswordChangerequestEmailChange/cancelEmailChange- démarre/annule un changement d'email en attente, confirmé viaAuth.verifyEmailChangelistTokens/createToken/updateToken/deleteToken- jetons APIlistPasskeys/updatePasskey/deletePasskeyuploadAvatar/deleteAvatar- accepte l'image en URI de données base64 dansrc.avatar(BoxLang n'a pas d'analyseur multipart/form-data, donc les téléversements voyagent en JSON), décodée viaBaseSecureHandler.decodeDataUri(); renvoyée parAssets.avatar
Chacune de ces actions est vérifiée CSRF par BaseSecureHandler sauf si elle est atteinte via une méthode HTTP sûre - voir Vérification CSRF.
Roles
@secured("roles:admin,roles:read") au niveau de la classe ; toutes les actions sauf index sont @remote :
indexcreate/update/delete-@secured("roles:admin,roles:write"/"...:delete")users/availableUsers- liste les utilisateurs assignés/disponibles pour un rôleaddUser/removeUser-@secured("roles:admin")
Settings
@secured("settings:admin,settings:read") au niveau de la classe :
indexregistry/registrySearch- registre des paramètres paginécreateRegistry/updateRegistry/toggleRegistryStatus/deleteRegistry-settings:admin,settings:writesave- sauvegarde groupée des paramètres principauxuploadLogo/deleteLogo-settings:admin,settings:write, même convention d'URI de données base64 queProfile.uploadAvatar; stocke/restaure le paramètrecbAppLogoet le renvoie viaAssets.logo- Utilitaires d'administration (tous
settings:admin) :clearTemplateCache,clearSessionsCache,revokeRememberTokens,flushSettingsCache
Users
@secured("users:admin,users:read") au niveau de la classe :
index,searchcreate/update/delete/resendInvitation-users:admin,users:write/...:deleteshow-users:read- Réservé aux administrateurs (
users:admin) :updateProfile,setStatus,resetPassword,verify,revokeRememberTokens,addRole/removeRole,addPermission/removePermission,savePreferences,revokeToken/revokeAllTokens
ensureNotSelf() protège plusieurs de ces actions pour empêcher un administrateur de rétrograder ou de retirer ses propres rôles.

Vérification CSRF
app/config/modules/cbsecurity.bx définit csrf.enableAutoVerifier: false, donc il n'y a pas d'intercepteur global. À la place, BaseSecureHandler.preHandler() vérifie le CSRF par défaut restrictif pour chaque handler qui en hérite :
static {
// The safe methods of RFC 9110, exempt from CSRF verification below.
SAFE_HTTP_METHODS = "GET,HEAD,OPTIONS"
}
function preHandler( event, rc, prc ) {
if (
!static.SAFE_HTTP_METHODS.listFindNoCase( event.getHTTPMethod() )
&& !csrfVerify( rc.csrf ?: "" )
) {
return onInvalidCSRF( argumentCollection = arguments )
}
// ...
}
Ce que cela signifie lorsque vous étendez un handler sécurisé :
- Vous n'avez pas à y adhérer explicitement. Toute action atteinte via
POST,PUT,PATCH, ouDELETEdoit porter unrc.csrfvalide, dès le jour où vous l'ajoutez. Il n'existe pas de liste par handler à penser à mettre à jour. - Les méthodes sûres sont exemptées.
GET,HEAD, etOPTIONSne doivent pas modifier l'état, elles ne présentent donc aucun risque CSRF, etOPTIONS(préflight CORS) ne peut porter aucun jeton du tout. Si une méthode sûre de votre code modifie l'état, c'est le bug à corriger. onInvalidCSRF()est surchargeable. L'implémentation de base interrompt avec un échec d'autorisation, ce que veulent les points de terminaison JSON/AJAX - chaque mutation dansPermissionsen fait désormais partie, soumise viafetchWithCsrf()(voir Frontend), qui se rétablit à partir d'un jeton périmé sans avoir besoin de redirection.Settingsla surcharge encore pour afficher un message flash et rediriger ses soumissions de formulaire natives, afin qu'un navigateur reçoive une page plutôt qu'un simple 403. Surchargez-la dans votre propre handler lorsqu'il rend du HTML plutôt que du JSON.
Auth étend coldbox.system.EventHandler, pas BaseSecureHandler, car ses actions s'exécutent pour des visiteurs non authentifiés et ne peuvent donc pas hériter du contrôle ci-dessus. Chaque action modifiant l'état vérifie son propre jeton : doLogin, doRegister, doActivateInvitation, doForgotPassword, doResetPassword, et logout.
Carte des routes (app/config/Router.bx)
Toutes les routes sont déclarées dans une seule fonction configure() :
route( "/healthcheck" ).to( () => "Ok!" );
get( "dashboard" ).to( "Dashboard.index" );
resources( "permissions", parameterName = "permissionId" );
route( "roles/:roleId/available-users" ).to( "Roles.availableUsers" );
route( "roles/:roleId/users" ).toAction( { POST: "addUser" } );
route( "roles/:roleId/users/:userId" ).toAction( { DELETE: "removeUser" } );
resources( "roles", parameterName = "roleId" );
resources( "users", parameterName = "userId" );
route( "profile" ).toAction( { GET: "index", POST: "save" } );
// @app_routes@ ← insertion point for module/scaffold-generated routes
route( ":handler/:action?" ).end(); // conventions-based catch-all
Voir Référence : Carte des routes pour le tableau complet de chaque méthode, URL, action cible, et permission requise.