2023-03-17 14:33:48 +00:00
< ? php
namespace App\Exceptions ;
2023-06-08 06:18:14 +00:00
use App\Models\InstanceSettings ;
2023-09-15 19:13:50 +00:00
use App\Models\User ;
2026-09-08 10:14:29 +00:00
use App\Providers\RouteServiceProvider ;
feat(observability): add structured audit log channel for API and webhook events
Introduce a dedicated `audit` log channel (daily rotation, configurable retention via
LOG_AUDIT_DAYS) and a small `auditLog()` / `auditLogWebhookFailure()` helper used to
record state-changing API operations and webhook events.
Instrumented:
- API mutation endpoints (create / update / delete / start / stop / restart) across
applications, services, databases (incl. backups, env vars, storage), servers,
projects + environments, scheduled tasks, private keys, GitHub apps, cloud provider
tokens, Hetzner server provisioning, instance enable/disable.
- Webhook signature verification outcomes for GitHub, GitLab, Bitbucket, Gitea and
Stripe, plus the Sentinel push endpoint.
- Authentication and authorization outcomes via the global exception handler and
the `ApiAbility` middleware (unauthenticated, ability-denied, policy-denied).
The helper is wrapped in try/catch so logging failures never affect the request
path. Successful operations log at `info`; suspicious/denied requests log at
`warning`. Operators wanting a failures-only feed can set `LOG_AUDIT_LEVEL=warning`.
Includes a feature test suite covering the helper, the webhook providers and the
new auth/authorization log paths.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 12:50:37 +00:00
use Illuminate\Auth\Access\AuthorizationException ;
2023-11-13 20:16:48 +00:00
use Illuminate\Auth\AuthenticationException ;
2023-03-17 14:33:48 +00:00
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler ;
2026-09-08 10:14:29 +00:00
use Illuminate\Session\TokenMismatchException ;
feat(observability): add structured audit log channel for API and webhook events
Introduce a dedicated `audit` log channel (daily rotation, configurable retention via
LOG_AUDIT_DAYS) and a small `auditLog()` / `auditLogWebhookFailure()` helper used to
record state-changing API operations and webhook events.
Instrumented:
- API mutation endpoints (create / update / delete / start / stop / restart) across
applications, services, databases (incl. backups, env vars, storage), servers,
projects + environments, scheduled tasks, private keys, GitHub apps, cloud provider
tokens, Hetzner server provisioning, instance enable/disable.
- Webhook signature verification outcomes for GitHub, GitLab, Bitbucket, Gitea and
Stripe, plus the Sentinel push endpoint.
- Authentication and authorization outcomes via the global exception handler and
the `ApiAbility` middleware (unauthenticated, ability-denied, policy-denied).
The helper is wrapped in try/catch so logging failures never affect the request
path. Successful operations log at `info`; suspicious/denied requests log at
`warning`. Operators wanting a failures-only feed can set `LOG_AUDIT_LEVEL=warning`.
Includes a feature test suite covering the helper, the webhook providers and the
new auth/authorization log paths.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 12:50:37 +00:00
use Psr\Log\LogLevel ;
2023-11-20 09:32:06 +00:00
use RuntimeException ;
2023-05-30 07:50:50 +00:00
use Sentry\Laravel\Integration ;
2023-09-01 09:20:58 +00:00
use Sentry\State\Scope ;
2023-08-08 09:51:36 +00:00
use Throwable ;
2023-03-17 14:33:48 +00:00
class Handler extends ExceptionHandler
{
/**
* A list of exception types with their corresponding custom log levels .
*
feat(observability): add structured audit log channel for API and webhook events
Introduce a dedicated `audit` log channel (daily rotation, configurable retention via
LOG_AUDIT_DAYS) and a small `auditLog()` / `auditLogWebhookFailure()` helper used to
record state-changing API operations and webhook events.
Instrumented:
- API mutation endpoints (create / update / delete / start / stop / restart) across
applications, services, databases (incl. backups, env vars, storage), servers,
projects + environments, scheduled tasks, private keys, GitHub apps, cloud provider
tokens, Hetzner server provisioning, instance enable/disable.
- Webhook signature verification outcomes for GitHub, GitLab, Bitbucket, Gitea and
Stripe, plus the Sentinel push endpoint.
- Authentication and authorization outcomes via the global exception handler and
the `ApiAbility` middleware (unauthenticated, ability-denied, policy-denied).
The helper is wrapped in try/catch so logging failures never affect the request
path. Successful operations log at `info`; suspicious/denied requests log at
`warning`. Operators wanting a failures-only feed can set `LOG_AUDIT_LEVEL=warning`.
Includes a feature test suite covering the helper, the webhook providers and the
new auth/authorization log paths.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 12:50:37 +00:00
* @ var array < class - string < Throwable > , LogLevel ::*>
2023-03-17 14:33:48 +00:00
*/
protected $levels = [
//
];
2024-06-10 20:43:34 +00:00
2023-03-17 14:33:48 +00:00
/**
* A list of the exception types that are not reported .
*
feat(observability): add structured audit log channel for API and webhook events
Introduce a dedicated `audit` log channel (daily rotation, configurable retention via
LOG_AUDIT_DAYS) and a small `auditLog()` / `auditLogWebhookFailure()` helper used to
record state-changing API operations and webhook events.
Instrumented:
- API mutation endpoints (create / update / delete / start / stop / restart) across
applications, services, databases (incl. backups, env vars, storage), servers,
projects + environments, scheduled tasks, private keys, GitHub apps, cloud provider
tokens, Hetzner server provisioning, instance enable/disable.
- Webhook signature verification outcomes for GitHub, GitLab, Bitbucket, Gitea and
Stripe, plus the Sentinel push endpoint.
- Authentication and authorization outcomes via the global exception handler and
the `ApiAbility` middleware (unauthenticated, ability-denied, policy-denied).
The helper is wrapped in try/catch so logging failures never affect the request
path. Successful operations log at `info`; suspicious/denied requests log at
`warning`. Operators wanting a failures-only feed can set `LOG_AUDIT_LEVEL=warning`.
Includes a feature test suite covering the helper, the webhook providers and the
new auth/authorization log paths.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 12:50:37 +00:00
* @ var array < int , class - string < Throwable >>
2023-03-17 14:33:48 +00:00
*/
protected $dontReport = [
2024-06-10 20:43:34 +00:00
ProcessException :: class ,
2025-09-08 07:18:25 +00:00
NonReportableException :: class ,
2025-11-11 14:08:26 +00:00
DeploymentException :: class ,
2023-03-17 14:33:48 +00:00
];
2024-06-10 20:43:34 +00:00
2023-03-17 14:33:48 +00:00
/**
* A list of the inputs that are never flashed to the session on validation exceptions .
*
* @ var array < int , string >
*/
protected $dontFlash = [
'current_password' ,
'password' ,
'password_confirmation' ,
];
2024-06-10 20:43:34 +00:00
2023-08-08 09:51:36 +00:00
private InstanceSettings $settings ;
2023-03-17 14:33:48 +00:00
2023-11-13 20:16:48 +00:00
protected function unauthenticated ( $request , AuthenticationException $exception )
{
if ( $request -> is ( 'api/*' ) || $request -> expectsJson () || $this -> shouldReturnJson ( $request , $exception )) {
feat(observability): add structured audit log channel for API and webhook events
Introduce a dedicated `audit` log channel (daily rotation, configurable retention via
LOG_AUDIT_DAYS) and a small `auditLog()` / `auditLogWebhookFailure()` helper used to
record state-changing API operations and webhook events.
Instrumented:
- API mutation endpoints (create / update / delete / start / stop / restart) across
applications, services, databases (incl. backups, env vars, storage), servers,
projects + environments, scheduled tasks, private keys, GitHub apps, cloud provider
tokens, Hetzner server provisioning, instance enable/disable.
- Webhook signature verification outcomes for GitHub, GitLab, Bitbucket, Gitea and
Stripe, plus the Sentinel push endpoint.
- Authentication and authorization outcomes via the global exception handler and
the `ApiAbility` middleware (unauthenticated, ability-denied, policy-denied).
The helper is wrapped in try/catch so logging failures never affect the request
path. Successful operations log at `info`; suspicious/denied requests log at
`warning`. Operators wanting a failures-only feed can set `LOG_AUDIT_LEVEL=warning`.
Includes a feature test suite covering the helper, the webhook providers and the
new auth/authorization log paths.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 12:50:37 +00:00
if ( $request -> is ( 'api/*' )) {
auditLog ( 'api.auth.unauthenticated' , [
'reason' => $exception -> getMessage (),
'guards' => $exception -> guards (),
], 'warning' );
}
2023-11-13 20:16:48 +00:00
return response () -> json ([ 'message' => $exception -> getMessage ()], 401 );
}
2024-06-10 20:43:34 +00:00
2024-07-11 12:12:28 +00:00
return redirect () -> guest ( $exception -> redirectTo ( $request ) ? ? route ( 'login' ));
2023-11-13 20:16:48 +00:00
}
2024-06-10 20:43:34 +00:00
2025-08-23 16:45:56 +00:00
/**
* Render an exception into an HTTP response .
*/
public function render ( $request , Throwable $e )
{
2026-09-08 10:14:29 +00:00
// A duplicate login or 2FA submission carries a stale token on an already authenticated session, see https://github.com/coollabsio/coolify/issues/10670
if ( $e instanceof TokenMismatchException && $request -> routeIs ( 'login.store' , 'two-factor.login.store' ) && $request -> user ()) {
return redirect () -> intended ( RouteServiceProvider :: HOME );
}
2026-07-06 15:40:37 +00:00
// Handle authorization exceptions for API routes. Exceptions carrying
// an explicit status (e.g. denyAsNotFound) keep it via parent::render.
if ( $e instanceof AuthorizationException && ! $e -> hasStatus ()) {
2025-08-23 16:45:56 +00:00
if ( $request -> is ( 'api/*' ) || $request -> expectsJson ()) {
feat(observability): add structured audit log channel for API and webhook events
Introduce a dedicated `audit` log channel (daily rotation, configurable retention via
LOG_AUDIT_DAYS) and a small `auditLog()` / `auditLogWebhookFailure()` helper used to
record state-changing API operations and webhook events.
Instrumented:
- API mutation endpoints (create / update / delete / start / stop / restart) across
applications, services, databases (incl. backups, env vars, storage), servers,
projects + environments, scheduled tasks, private keys, GitHub apps, cloud provider
tokens, Hetzner server provisioning, instance enable/disable.
- Webhook signature verification outcomes for GitHub, GitLab, Bitbucket, Gitea and
Stripe, plus the Sentinel push endpoint.
- Authentication and authorization outcomes via the global exception handler and
the `ApiAbility` middleware (unauthenticated, ability-denied, policy-denied).
The helper is wrapped in try/catch so logging failures never affect the request
path. Successful operations log at `info`; suspicious/denied requests log at
`warning`. Operators wanting a failures-only feed can set `LOG_AUDIT_LEVEL=warning`.
Includes a feature test suite covering the helper, the webhook providers and the
new auth/authorization log paths.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 12:50:37 +00:00
if ( $request -> is ( 'api/*' )) {
auditLog ( 'api.auth.policy_denied' , [
'reason' => $e -> getMessage (),
'route' => $request -> route () ? -> getName () ? ? $request -> path (),
], 'warning' );
}
2025-08-23 16:45:56 +00:00
// Get the custom message from the policy if available
$message = $e -> getMessage ();
// Clean up the message for API responses (remove HTML tags if present)
$message = strip_tags ( str_replace ( '<br/>' , ' ' , $message ));
// If no custom message, use a default one
if ( empty ( $message ) || $message === 'This action is unauthorized.' ) {
$message = 'You are not authorized to perform this action.' ;
}
return response () -> json ([
'message' => $message ,
'error' => 'Unauthorized' ,
], 403 );
}
}
return parent :: render ( $request , $e );
}
2023-03-17 14:33:48 +00:00
/**
* Register the exception handling callbacks for the application .
*/
public function register () : void
{
$this -> reportable ( function ( Throwable $e ) {
2023-09-15 13:34:25 +00:00
if ( isDev ()) {
2024-04-17 13:30:08 +00:00
return ;
2023-11-20 09:32:06 +00:00
}
if ( $e instanceof RuntimeException ) {
2023-09-15 13:34:25 +00:00
return ;
}
2024-10-01 08:37:40 +00:00
$this -> settings = instanceSettings ();
2023-09-15 13:34:25 +00:00
if ( $this -> settings -> do_not_track ) {
2023-06-08 06:18:14 +00:00
return ;
}
2023-09-01 09:20:58 +00:00
app ( 'sentry' ) -> configureScope (
2023-09-11 20:45:07 +00:00
function ( Scope $scope ) {
2023-09-29 08:21:11 +00:00
$email = auth () ? -> user () ? auth () -> user () -> email : 'guest' ;
$instanceAdmin = User :: find ( 0 ) -> email ? ? 'admin@localhost' ;
2023-09-11 20:45:07 +00:00
$scope -> setUser (
[
2023-09-29 08:21:11 +00:00
'email' => $email ,
2024-06-10 20:43:34 +00:00
'instanceAdmin' => $instanceAdmin ,
2023-09-11 20:45:07 +00:00
]
);
2023-09-01 09:20:58 +00:00
}
);
2025-09-08 07:18:25 +00:00
// Check for errors that should not be reported to Sentry
2024-03-04 07:49:53 +00:00
if ( str ( $e -> getMessage ()) -> contains ( 'No space left on device' )) {
2025-09-08 07:18:25 +00:00
// Log locally but don't send to Sentry
logger () -> warning ( 'Disk space error: ' . $e -> getMessage ());
2024-03-04 07:49:53 +00:00
return ;
}
2025-09-08 07:18:25 +00:00
2023-05-30 07:50:50 +00:00
Integration :: captureUnhandledException ( $e );
2023-03-17 14:33:48 +00:00
});
}
}