Handler e Routing
Ogni handler, le sue azioni, e come Router.bx collega gli URL ad essi.
In questa pagina
Handler e Routing
Mappa degli handler
| Handler | Base | Scopo |
|---|---|---|
AuditLog.bx | BaseSecureHandler | Navigazione, esportazione e purga del registro di audit |
Assets.bx | EventHandler | Distribuisce in streaming gli avatar utente e il logo del branding |
Auth.bx | EventHandler | Login, registrazione, inviti, reset password - tutto pubblico |
BaseSecureHandler.bx | RestHandler | Classe base per ogni handler admin |
Dashboard.bx | BaseSecureHandler | La pagina di destinazione autenticata |
Main.bx | EventHandler | Handler a evento implicito - vedi Architettura |
Permissions.bx | BaseSecureHandler | CRUD degli slug dei permessi |
Profile.bx | BaseSecureHandler | Profilo self-service, password, token API, passkey |
Roles.bx | BaseSecureHandler | CRUD dei ruoli + assegnazione utenti |
Settings.bx | BaseSecureHandler | Registro delle impostazioni dell'app |
Users.bx | BaseSecureHandler | Amministrazione utenti |
BaseSecureHandler
Ogni handler protetto estende BaseSecureHandler, il cui preHandler verifica il CSRF su ogni richiesta che cambia stato, forza il layout Admin, e reindirizza a profile/passkey-required quando cbRequirePasskey è attivo e l'utente non ne ha nessuna. Fornisce anche helper condivisi (getApiResults(), ensureSortDirection(), getPagination()):
component extends="coldbox.system.RestHandler" {
function preHandler( event, rc, prc ){
// ...CSRF verification, deny-by-default...
event.setLayout( "Admin" );
// ...passkey enforcement...
}
}
Costruire un nuovo handler protetto inizia sempre nello stesso modo:
component extends="BaseSecureHandler" secured {
function index( event, rc, prc ){
prc.pageTitle = "My Page";
event.setView( "myhandler/index" );
}
}
AuditLog
@secured("auditlog:admin,auditlog:read") a livello di classe; ogni azione tranne index è @remote:
index,search,show- sfoglia e filtra il registro di auditexport-@secured("auditlog:admin,auditlog:export"), distribuisce in streaming un CSVpurge-@secured("auditlog:admin,auditlog:delete"), elimina le voci più vecchie di una data limiteclear-@secured("auditlog:admin"), elimina ogni voce
Assets
Nessuna annotazione @secured a livello di classe - distribuisce in streaming file binari dal disco privato cbfs assets (vedi Database e ORM e app/config/modules/cbfs.bx), che si trova al di fuori della webroot ed è altrimenti irraggiungibile:
avatar-@secured(qualsiasi utente autenticato), distribuisce in streaming la variante JPEGsm/lgdell'avatar di un utentelogo- pubblico, distribuisce in streaming la variante PNGsm/lgdel logo del branding così la schermata di login e altre pagine per ospiti possono renderizzarlo
Entrambe le azioni restituiscono 404 (piuttosto che errore) per una forma userId/size non riconosciuta o quando il file richiesto semplicemente non esiste, così un chiamante non può distinguere "nessun avatar" da "nessun utente del genere" solo dalla forma della risposta. Il ridimensionamento, il ritaglio e l'archiviazione passano attraverso ImageService (app/models/system/ImageService.bx), invocato tramite getInstance() dentro ogni azione piuttosto che una proprietà @inject - vedi il docblock su Assets.bx per il perché (una stranezza dell'ordine di boot di WireBox con la costruzione di singleton innescata dall'handler).
Auth
Nessuna annotazione @secured - queste azioni devono restare raggiungibili dagli ospiti:
login/doLogin(GET/POST) - verificato CSRF, chiamasecurityService.login(), supportarememberMeregister/doRegister- controllato dall'impostazionecbAllowRegistrationcheckEmailAvailability- endpoint JSON per controlli live sulla disponibilità dell'emailverifyRegistration- consuma un token azionePURPOSE_REGISTRATIONactivateInvitation/doActivateInvitation- imposta una password per un utente invitato, creato dall'adminforgotPassword/doForgotPassword- controllato dacbAllowForgotPasswordresetPassword/doResetPassword- valida il token di reset, imposta una nuova passwordverifyEmailChange- consuma un token azionePURPOSE_EMAIL_CHANGElogout- chiamasecurityService.logout()
preHandler reindirizza un visitatore già autenticato direttamente alla dashboard, e imposta il layout da prc.settings.cbLoginLayout (AuthSplit per default - vedi guides/security.md); verifyEmailChange e logout sono esentati da quel reindirizzamento così restano raggiungibili indipendentemente dal fatto che il visitatore sia già autenticato.
Dashboard
@secured (qualsiasi utente autenticato, nessun permesso specifico richiesto):
index- la home della dashboardnotAuthorized- l'obiettivo diinvalidAuthorizationEvent, mostrato quando a un utente autenticato manca un permesso richiesto
Permissions
@secured("permissions:admin,permissions:read") a livello di classe:
indexcreate-@secured("permissions:admin,permissions:write")update/delete-@remote, stessi permessi di scrittura/eliminazione
Profile
Azioni self-service @secured per l'utente corrente, tutte endpoint AJAX @remote tranne index:
index,passkeyRequiredsave,doPasswordChangerequestEmailChange/cancelEmailChange- avvia/annulla un cambio email in sospeso, confermato tramiteAuth.verifyEmailChangelistTokens/createToken/updateToken/deleteToken- token APIlistPasskeys/updatePasskey/deletePasskeyuploadAvatar/deleteAvatar- accetta l'immagine come URI dati base64 inrc.avatar(BoxLang non ha un parser multipart/form-data, quindi i caricamenti viaggiano come JSON), decodificata tramiteBaseSecureHandler.decodeDataUri(); distribuita in streaming di ritorno daAssets.avatar
Ognuna di queste è verificata per il CSRF da BaseSecureHandler a meno che non sia raggiunta tramite un metodo HTTP sicuro - vedi Verifica CSRF.
Roles
@secured("roles:admin,roles:read") a livello di classe; ogni azione tranne index è @remote:
indexcreate/update/delete-@secured("roles:admin,roles:write"/"...:delete")users/availableUsers- elenca gli utenti su/disponibili per un ruoloaddUser/removeUser-@secured("roles:admin")
Settings
@secured("settings:admin,settings:read") a livello di classe:
indexregistry/registrySearch- registro delle impostazioni paginatocreateRegistry/updateRegistry/toggleRegistryStatus/deleteRegistry-settings:admin,settings:writesave- salvataggio bulk delle impostazioni coreuploadLogo/deleteLogo-settings:admin,settings:write, stessa convenzione URI dati base64 diProfile.uploadAvatar; memorizza/ripristina l'impostazionecbAppLogoe distribuisce in streaming tramiteAssets.logo- Utility admin (tutte
settings:admin):clearTemplateCache,clearSessionsCache,revokeRememberTokens,flushSettingsCache
Users
@secured("users:admin,users:read") a livello di classe:
index,searchcreate/update/delete/resendInvitation-users:admin,users:write/...:deleteshow-users:read- Solo admin (
users:admin):updateProfile,setStatus,resetPassword,verify,revokeRememberTokens,addRole/removeRole,addPermission/removePermission,savePreferences,revokeToken/revokeAllTokens
ensureNotSelf() protegge diverse di queste per impedire a un admin di retrocedere o rimuovere i propri stessi ruoli.

Verifica CSRF
app/config/modules/cbsecurity.bx imposta csrf.enableAutoVerifier: false, quindi non c'è un interceptor globale. Invece, BaseSecureHandler.preHandler() verifica il CSRF deny-by-default per ogni handler che lo estende:
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 )
}
// ...
}
Cosa significa questo quando estendi un handler protetto:
- Non ci si iscrive volontariamente. Qualsiasi azione raggiunta via
POST,PUT,PATCH, oDELETEdeve portare unrc.csrfvalido, fin dal giorno in cui la aggiungi. Non c'è nessuna lista per handler da ricordarsi di aggiornare. - I metodi sicuri sono esentati.
GET,HEAD, eOPTIONSnon devono cambiare stato, quindi non comportano rischio CSRF, eOPTIONS(preflight CORS) non può trasportare affatto un token. Se un metodo sicuro nel tuo codice cambia stato, quello è il bug da correggere. onInvalidCSRF()è sovrascrivibile. L'implementazione base termina con un fallimento di autorizzazione, che è ciò che vogliono gli endpoint JSON/AJAX - ogni mutazione inPermissionsora è una di queste, inviata tramitefetchWithCsrf()(vedi Frontend), che recupera da un token obsoleto invece di richiedere un reindirizzamento.Settingscontinua a sovrascriverlo per mostrare un messaggio flash e reindirizzare i suoi invii di form nativi, così un browser form riceve una pagina invece di un semplice 403. Sovrascrivilo nel tuo handler quando renderizza HTML invece di JSON.
Auth estende coldbox.system.EventHandler, non BaseSecureHandler, perché le sue azioni vengono eseguite per visitatori non autenticati e quindi non possono ereditare il controllo di cui sopra. Ogni azione che cambia stato verifica il proprio token: doLogin, doRegister, doActivateInvitation, doForgotPassword, doResetPassword, e logout.
Mappa delle rotte (app/config/Router.bx)
Tutte le rotte sono dichiarate in un'unica funzione 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
Vedi Riferimento: Mappa delle rotte per la tabella completa di ogni metodo, URL, azione target, e permesso richiesto.