Handler & Routing
Jeder Handler, seine Aktionen und wie Router.bx URLs mit ihnen verdrahtet.
Auf dieser Seite
Handler & Routing
Handler-Übersicht
| Handler | Basis | Zweck |
|---|---|---|
AuditLog.bx | BaseSecureHandler | Durchsuchen, Exportieren und Bereinigen des Audit-Trails |
Assets.bx | EventHandler | Streamt Benutzer-Avatare und das Branding-Logo |
Auth.bx | EventHandler | Login, Registrierung, Einladungen, Passwort-Zurücksetzung - alles öffentlich |
BaseSecureHandler.bx | RestHandler | Basisklasse für jeden Admin-Handler |
Dashboard.bx | BaseSecureHandler | Die authentifizierte Startseite |
Main.bx | EventHandler | Impliziter Event-Handler - siehe Architektur |
Permissions.bx | BaseSecureHandler | CRUD für Berechtigungs-Slugs |
Profile.bx | BaseSecureHandler | Self-Service-Profil, Passwort, API-Tokens, Passkeys |
Roles.bx | BaseSecureHandler | Rollen-CRUD + Benutzerzuweisung |
Settings.bx | BaseSecureHandler | App-Einstellungsregistrierung |
Users.bx | BaseSecureHandler | Benutzerverwaltung |
BaseSecureHandler
Jeder geschützte Handler erweitert BaseSecureHandler, dessen preHandler CSRF bei jedem zustandsändernden Request verifiziert, das Admin-Layout erzwingt und zu profile/passkey-required umleitet, wenn cbRequirePasskey aktiv ist und der Benutzer keinen Passkey hat. Er stellt außerdem gemeinsame Hilfsmethoden bereit (getApiResults(), ensureSortDirection(), getPagination()):
component extends="coldbox.system.RestHandler" {
function preHandler( event, rc, prc ){
// ...CSRF verification, deny-by-default...
event.setLayout( "Admin" );
// ...passkey enforcement...
}
}
Einen neuen geschützten Handler zu bauen, beginnt jedes Mal gleich:
component extends="BaseSecureHandler" secured {
function index( event, rc, prc ){
prc.pageTitle = "My Page";
event.setView( "myhandler/index" );
}
}
AuditLog
@secured("auditlog:admin,auditlog:read") auf Klassenebene; jede Aktion außer index ist @remote:
index,search,show- den Audit-Trail durchsuchen und filternexport-@secured("auditlog:admin,auditlog:export"), streamt CSVpurge-@secured("auditlog:admin,auditlog:delete"), löscht Einträge älter als ein Cutoff-Datumclear-@secured("auditlog:admin"), löscht jeden Eintrag
Assets
Keine @secured-Annotation auf Klassenebene - es streamt binäre Dateien von der privaten cbfs-assets-Disk (siehe Datenbank & ORM und app/config/modules/cbfs.bx), die außerhalb des Webroots liegt und sonst nicht erreichbar ist:
avatar-@secured(jeder authentifizierte Benutzer), streamt diesm/lg-Avatar-JPEG-Variante eines Benutzerslogo- öffentlich, streamt diesm/lg-Branding-Logo-PNG-Variante, sodass der Login-Bildschirm und andere Gast-Seiten sie rendern können
Beide Aktionen geben einen 404 (statt eines Fehlers) für eine nicht erkannte userId/size-Form zurück oder wenn die angeforderte Datei schlicht nicht existiert, sodass ein Aufrufer "kein Avatar" nicht allein anhand der Antwortform von "kein solcher Benutzer" unterscheiden kann. Skalierung, Zuschneiden und Speicherung laufen über ImageService (app/models/system/ImageService.bx), aufgerufen über getInstance() innerhalb jeder Aktion statt einer @inject-Eigenschaft - siehe den Docblock auf Assets.bx für den Grund (eine WireBox-Boot-Reihenfolge-Eigenheit bei handler-ausgelöster Singleton-Konstruktion).
Auth
Keine @secured-Annotation - diese Aktionen müssen für Gäste erreichbar bleiben:
login/doLogin(GET/POST) - CSRF-verifiziert, ruftsecurityService.login()auf, unterstütztrememberMeregister/doRegister- abhängig von der EinstellungcbAllowRegistrationcheckEmailAvailability- JSON-Endpunkt für Live-Prüfungen der E-Mail-VerfügbarkeitverifyRegistration- verbraucht einPURPOSE_REGISTRATION-Aktions-TokenactivateInvitation/doActivateInvitation- setzt ein Passwort für einen eingeladenen, admin-erstellten BenutzerforgotPassword/doForgotPassword- abhängig voncbAllowForgotPasswordresetPassword/doResetPassword- validiert das Reset-Token, setzt ein neues PasswortverifyEmailChange- verbraucht einPURPOSE_EMAIL_CHANGE-Aktions-Tokenlogout- ruftsecurityService.logout()auf
preHandler leitet einen bereits authentifizierten Besucher direkt zum Dashboard um und setzt das Layout aus prc.settings.cbLoginLayout (AuthSplit standardmäßig - siehe guides/security.md); verifyEmailChange und logout sind von dieser Umleitung ausgenommen, damit sie erreichbar bleiben, egal ob der Besucher bereits authentifiziert ist.
Dashboard
@secured (jeder authentifizierte Benutzer, keine bestimmte Berechtigung erforderlich):
index- die Dashboard-StartseitenotAuthorized- das Ziel voninvalidAuthorizationEvent, gezeigt, wenn einem authentifizierten Benutzer eine erforderliche Berechtigung fehlt
Permissions
@secured("permissions:admin,permissions:read") auf Klassenebene:
indexcreate-@secured("permissions:admin,permissions:write")update/delete-@remote, dieselben Schreib-/Löschberechtigungen
Profile
@secured-Self-Service-Aktionen für den aktuellen Benutzer, alle @remote-AJAX-Endpunkte außer index:
index,passkeyRequiredsave,doPasswordChangerequestEmailChange/cancelEmailChange- startet/bricht eine ausstehende E-Mail-Änderung ab, bestätigt überAuth.verifyEmailChangelistTokens/createToken/updateToken/deleteToken- API-TokenslistPasskeys/updatePasskey/deletePasskeyuploadAvatar/deleteAvatar- akzeptiert das Bild als base64-Data-URI inrc.avatar(BoxLang hat keinen multipart/form-data-Parser, daher reisen Uploads als JSON), dekodiert überBaseSecureHandler.decodeDataUri(); zurückgestreamt vonAssets.avatar
Jede dieser Aktionen wird von BaseSecureHandler CSRF-verifiziert, es sei denn, sie wird über eine sichere HTTP-Methode erreicht - siehe CSRF-Verifizierung.
Roles
@secured("roles:admin,roles:read") auf Klassenebene; jede Aktion außer index ist @remote:
indexcreate/update/delete-@secured("roles:admin,roles:write"/"...:delete")users/availableUsers- Benutzer einer Rolle bzw. für eine Rolle verfügbare Benutzer auflistenaddUser/removeUser-@secured("roles:admin")
Settings
@secured("settings:admin,settings:read") auf Klassenebene:
indexregistry/registrySearch- paginierte EinstellungsregistrierungcreateRegistry/updateRegistry/toggleRegistryStatus/deleteRegistry-settings:admin,settings:writesave- Massenspeicherung der KerneinstellungenuploadLogo/deleteLogo-settings:admin,settings:write, dieselbe base64-Data-URI-Konvention wieProfile.uploadAvatar; speichert/stellt diecbAppLogo-Einstellung wieder her und streamt überAssets.logozurück- Admin-Hilfsmittel (alle
settings:admin):clearTemplateCache,clearSessionsCache,revokeRememberTokens,flushSettingsCache
Users
@secured("users:admin,users:read") auf Klassenebene:
index,searchcreate/update/delete/resendInvitation-users:admin,users:write/...:deleteshow-users:read- Nur Admin (
users:admin):updateProfile,setStatus,resetPassword,verify,revokeRememberTokens,addRole/removeRole,addPermission/removePermission,savePreferences,revokeToken/revokeAllTokens
ensureNotSelf() schützt mehrere dieser Aktionen davor, dass ein Admin die eigenen Rollen herabstuft oder entfernt.

CSRF-Verifizierung
app/config/modules/cbsecurity.bx setzt csrf.enableAutoVerifier: false, sodass es keinen globalen Interceptor gibt. Stattdessen verifiziert BaseSecureHandler.preHandler() CSRF deny-by-default für jeden Handler, der ihn erweitert:
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 )
}
// ...
}
Was das bedeutet, wenn du einen gesicherten Handler erweiterst:
- Du meldest dich nicht opt-in an. Jede Aktion, die über
POST,PUT,PATCHoderDELETEerreicht wird, muss ab dem Tag, an dem du sie hinzufügst, ein gültigesrc.csrfmitführen. Es gibt keine handler-spezifische Liste, die aktuell gehalten werden müsste. - Sichere Methoden sind ausgenommen.
GET,HEADundOPTIONSdürfen den Zustand nicht ändern, tragen also kein CSRF-Risiko, undOPTIONS(CORS-Preflight) kann überhaupt kein Token mitführen. Wenn eine sichere Methode in deinem Code doch den Zustand ändert, ist das der zu behebende Fehler. onInvalidCSRF()ist überschreibbar. Die Basisimplementierung bricht mit einem Autorisierungsfehler ab, was für die JSON-/AJAX-Endpunkte gewünscht ist - jede Mutation inPermissionsist inzwischen eine davon, übermittelt überfetchWithCsrf()(siehe Frontend), das sich von einem veralteten Token erholt, statt eine Umleitung zu benötigen.Settingsüberschreibt sie weiterhin, um eine Nachricht anzuzeigen und native Formular-Posts umzuleiten, sodass ein Browser-Formular eine Seite statt eines nackten 403 erhält. Überschreibe sie in deinem eigenen Handler, wenn er HTML statt JSON rendert.
Auth erweitert coldbox.system.EventHandler, nicht BaseSecureHandler, weil seine Aktionen für nicht authentifizierte Besucher laufen und daher die obige Prüfung nicht erben können. Jede zustandsändernde Aktion verifiziert ihr eigenes Token: doLogin, doRegister, doActivateInvitation, doForgotPassword, doResetPassword und logout.
Routen-Übersicht (app/config/Router.bx)
Alle Routen werden in einer configure()-Funktion deklariert:
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
Siehe Referenz: Routen-Übersicht für die vollständige Tabelle jeder Methode, URL, Zielaktion und erforderlichen Berechtigung.