convert pdf to md, add OVERVIEW and docs
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
---
|
||||
type: process
|
||||
universe: live
|
||||
status: verified
|
||||
consumes: ["../objects/identity/user.md", "../objects/trading/stock.md"]
|
||||
produces: []
|
||||
---
|
||||
|
||||
# authenticate-authorize
|
||||
|
||||
Every request proves who you are with Devise, then proves you may act with Pundit.
|
||||
|
||||
Verified 2026-08-16 against commit `63732df`.
|
||||
|
||||
## Input → Movement → Output
|
||||
|
||||
A request arrives with a session cookie. `ApplicationController` authenticates it by
|
||||
**username**, loads the navbar's stock list, and the controller action asks a Pundit
|
||||
policy whether this user may proceed. The action runs, or a `rescue_from` redirects the
|
||||
user somewhere they are allowed to be.
|
||||
|
||||
## Why this shape
|
||||
|
||||
**Authorization is opt-in, per action.** Pundit's `verify_authorized` after-action is not
|
||||
enabled anywhere in the app — a controller that never calls `authorize` is simply not
|
||||
authorized, and nothing complains. Two consequences are live today; see Gaps below.
|
||||
|
||||
The admin area does not use Pundit at all. `Admin::BaseController` has its own
|
||||
`before_action :authenticate_admin` that redirects unless `current_user&.admin?`
|
||||
(`app/controllers/admin/base_controller.rb:9,13-15`). So `/admin` is guarded by one line,
|
||||
not by policies, and adding a policy will not protect an admin controller.
|
||||
|
||||
The `rescue_from` sends users somewhere sensible instead of a 403 — students go to their
|
||||
own portfolio, everyone else to root (`app/controllers/application_controller.rb:31-40`).
|
||||
That is why an authorization failure often looks like a redirect loop rather than an
|
||||
error.
|
||||
|
||||
## Steps
|
||||
|
||||
1. `before_action :authenticate_user!` on every controller
|
||||
(`app/controllers/application_controller.rb:6`). Devise matches on `username`, not
|
||||
email (`config/initializers/devise.rb:49`).
|
||||
2. `before_action :set_navbar_stocks` runs `policy_scope(Stock).active` on **every**
|
||||
request (`app/controllers/application_controller.rb:8,22-24`) — `StockPolicy::Scope`
|
||||
returns `scope.all` (`app/policies/stock_policy.rb:52-56`).
|
||||
3. Under `/admin`, `authenticate_admin` redirects non-admins
|
||||
(`app/controllers/admin/base_controller.rb:13-15`).
|
||||
4. Elsewhere, the action calls `authorize record` or `policy_scope(Model)`. Role helpers
|
||||
live on the base policy (`app/policies/application_policy.rb:39-53`).
|
||||
5. On `Pundit::NotAuthorizedError`, redirect by role
|
||||
(`app/controllers/application_controller.rb:31-40`).
|
||||
|
||||
## Gaps worth knowing
|
||||
|
||||
Stated as found, not as a recommendation:
|
||||
|
||||
- **`OrdersController#edit` and `#update` never authorize.** `set_order` is an unscoped
|
||||
`Order.find` (`app/controllers/orders_controller.rb:4,66-68`) and only `cancel` calls
|
||||
`authorize` (`:50`). `OrderPolicy` defines `update?` (`app/policies/order_policy.rb:12-14`),
|
||||
but nothing invokes it.
|
||||
- **`GradeBookPolicy#finalize?` is `user.admin?`** (`app/policies/grade_book_policy.rb:12-14`).
|
||||
Teachers may `show` and `update` a gradebook but **cannot finalize it** — only admins
|
||||
release earnings.
|
||||
- **`ClassroomPolicy::Scope` does not inherit `ApplicationPolicy::Scope`**
|
||||
(`app/policies/classroom_policy.rb:40-57`) and returns a bare `[]` rather than
|
||||
`scope.none` for non-teachers — an Array where callers expect a relation.
|
||||
- **`OrdersController#destroy` is defined below `private`** (`:59,105-112`), so the routed
|
||||
`DELETE /orders/:id` cannot dispatch to it. `unauthorized_response` (`:83-88`) is never
|
||||
called.
|
||||
|
||||
## If you change this
|
||||
|
||||
- **Hits:** every controller — this is the one movement with no local blast radius;
|
||||
`ApplicationController`, `Admin::BaseController`, all six policies;
|
||||
[user](../objects/identity/user.md) if you touch the auth key.
|
||||
- **Does not hit:** the four scheduled jobs. `OrderExecutionJob`,
|
||||
`StockPricesUpdateJob`, `StockAttributeUpdateJob` and
|
||||
`MonthlyPortfolioSnapshotJob` run with no `current_user` and never consult a policy —
|
||||
tightening authorization cannot break them, and cannot protect them either.
|
||||
|
||||
## Surfaces
|
||||
|
||||
| Surface | Role |
|
||||
|---|---|
|
||||
| every request | authenticated |
|
||||
| `/admin/*` | admin boolean gate, not Pundit |
|
||||
| Solid Queue jobs | bypass entirely |
|
||||
|
||||
## See
|
||||
|
||||
- Objects: [user](../objects/identity/user.md), [stock](../objects/trading/stock.md)
|
||||
- Source: `app/controllers/application_controller.rb`,
|
||||
`app/controllers/admin/base_controller.rb`, `app/policies/`
|
||||
Reference in New Issue
Block a user