# LS2012: Handler Authorizes Itself

## Intent

LS2012 is a compiler check that reports a command or query handler, the type that runs a use case, authorizing itself instead of leaving authorization to the pipeline guard. A handler authorizes itself when it throws an access refusal or when it takes the authorization guard. This page states what the rule reports, where the error sits, and how to move the check into the declared access.

## Reference

### What the Rule Enforces

LS2012 enforces [Enforce target authorization in the pipeline](https://litespec.dev/4.0.0/backend/application.md#enforce-target-authorization-in-the-pipeline) (`litespec/backend/application#enforce-target-authorization-in-the-pipeline`). It runs in Application, Infrastructure, and Worker. Its category is `LiteSpec.Layers`. Severity and enablement follow the [Analyzers overview](https://litespec.dev/4.0.0/analyzers/README.md).

### What the Rule Reports

The rule reports a command or query handler, which is a class whose name ends in `CommandHandler` or `QueryHandler`, or that implements `ICommandHandler` or `IQueryHandler`, in either of two cases. The first case is a handler that throws an exception type whose name ends in `ForbiddenException`, `UnauthorizedException`, or `AccessDeniedException`. For a `throw new` the diagnostic sits on the created exception type; for any other thrown value it sits on that value. The second case is a handler that takes a constructor parameter whose type is the authorization guard: a type whose name ends in `AuthorizationGuard`, or a type that derives from or implements such a type. The diagnostic sits on the parameter type. In either case its message names the handler and the exception or guard type.

### Failing Example

```csharp
internal sealed class RenewLoanCommandHandler
{
    public void Handle(RenewLoanCommand command)
    {
        throw new LoanForbiddenException();
    }
}
```

```csharp
internal sealed class RenewLoanCommandHandler(IAuthorizationGuard authorizationGuard)
{
}
```

### Passing Example

```csharp
internal sealed class RenewLoanCommandHandler(ILoanRepository loanRepository)
{
    public void Handle(RenewLoanCommand command)
    {
        var loan = loanRepository.GetById(command.LoanId);
        loanRepository.Store(loan);
    }
}
```

### How to Fix

Declare the grant on the use-case record and let the authorization guard enforce it before the handler runs, so the handler never compares the caller with a target, never throws the forbidden refusal itself, and never takes the authorization guard. Remove the guard parameter from the handler constructor and leave the guard in the pipeline.

## Notes

### What the Rule Ignores

The rule does not report a throw of any other exception type, a throw in an event handler or a job, or a rethrow without a value. It does not report the authorization guard taken outside a constructor, taken by a type that is not a command or query handler, or a handler that takes any other constructor type, such as a repository. It does not run in Domain or WebApi.
