@orieljs/framework 0.1.0-alpha.6 → 0.1.0-alpha.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +4 -0
- package/README.md +10 -1
- package/TRADEMARKS.md +33 -0
- package/dist/{chunk-POEP4B2M.js → chunk-7ZQZYDOC.js} +745 -105
- package/dist/chunk-7ZQZYDOC.js.map +1 -0
- package/dist/{chunk-I63ZFMX6.js → chunk-GVNLZZTN.js} +1 -1
- package/dist/chunk-GVNLZZTN.js.map +1 -0
- package/dist/{chunk-ZOEPKBVF.js → chunk-SPQUX3C7.js} +1347 -428
- package/dist/chunk-SPQUX3C7.js.map +1 -0
- package/dist/{chunk-MFTZQMZM.js → chunk-VOYW3R5J.js} +4 -4
- package/dist/chunk-VOYW3R5J.js.map +1 -0
- package/dist/contracts.d.ts +515 -17
- package/dist/contracts.js +1 -1
- package/dist/index.d-WQpfdIbG.d.ts +630 -0
- package/dist/index.d.ts +964 -152
- package/dist/index.js +4279 -866
- package/dist/index.js.map +1 -1
- package/dist/{jsx-runtime-CBZQYYDS.d.ts → jsx-runtime-6um4Mwfb.d.ts} +96 -5
- package/dist/jsx-runtime.d.ts +2 -2
- package/dist/jsx-runtime.js +3 -3
- package/dist/{testing-BM481Hdz.d.ts → testing-CKwfWJGn.d.ts} +180 -15
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +3 -3
- package/docs/authentication.md +487 -0
- package/docs/authorization.md +435 -0
- package/docs/cache.md +647 -0
- package/docs/collections.md +2270 -0
- package/docs/configuration.md +334 -0
- package/docs/console.md +651 -0
- package/docs/container.md +430 -0
- package/docs/controllers.md +406 -0
- package/docs/csrf.md +126 -0
- package/docs/database-testing.md +144 -0
- package/docs/database.md +236 -0
- package/docs/deployment.md +276 -0
- package/docs/eloquent-collections.md +218 -0
- package/docs/eloquent-factories.md +255 -0
- package/docs/eloquent-mutators.md +254 -0
- package/docs/eloquent-observers.md +145 -0
- package/docs/eloquent-relationships.md +557 -0
- package/docs/eloquent.md +494 -0
- package/docs/encryption.md +104 -0
- package/docs/errors.md +318 -0
- package/docs/events.md +336 -0
- package/docs/facades.md +278 -0
- package/docs/hashing.md +118 -0
- package/docs/helpers.md +1281 -0
- package/docs/http-tests.md +386 -0
- package/docs/inertia.md +485 -0
- package/docs/installation.md +209 -0
- package/docs/lifecycle.md +234 -0
- package/docs/live-tests.md +226 -0
- package/docs/live.md +910 -0
- package/docs/logging.md +255 -0
- package/docs/mail.md +619 -0
- package/docs/middleware.md +384 -0
- package/docs/migrations.md +416 -0
- package/docs/mocking.md +264 -0
- package/docs/navigation.json +273 -0
- package/docs/notifications.md +225 -0
- package/docs/pagination.md +247 -0
- package/docs/passwords.md +207 -0
- package/docs/providers.md +187 -0
- package/docs/queries.md +600 -0
- package/docs/queues.md +330 -0
- package/docs/requests.md +533 -0
- package/docs/responses.md +444 -0
- package/docs/routing.md +738 -0
- package/docs/scheduling.md +277 -0
- package/docs/seeding.md +122 -0
- package/docs/session.md +409 -0
- package/docs/starter-kits.md +396 -0
- package/docs/storage.md +192 -0
- package/docs/structure.md +264 -0
- package/docs/testing.md +165 -0
- package/docs/time-travel.md +138 -0
- package/docs/validation.md +1792 -0
- package/docs/verification.md +152 -0
- package/docs/views.md +566 -0
- package/docs/vite.md +322 -0
- package/package.json +34 -32
- package/dist/chunk-I63ZFMX6.js.map +0 -1
- package/dist/chunk-MFTZQMZM.js.map +0 -1
- package/dist/chunk-POEP4B2M.js.map +0 -1
- package/dist/chunk-ZOEPKBVF.js.map +0 -1
- package/dist/index.d-BJyJx6U1.d.ts +0 -319
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
# Authorization
|
|
2
|
+
|
|
3
|
+
Decide what a signed-in user may do: write gates for single checks and policies for the actions on a model, then check
|
|
4
|
+
them in your controllers, routes and pages.
|
|
5
|
+
|
|
6
|
+
## Introduction
|
|
7
|
+
|
|
8
|
+
[Authentication](authentication.md) tells you who a user is; authorization tells you what they may do, such as
|
|
9
|
+
whether they may update a post. Oriel has two ways to write those rules, as Laravel does. **Gates** are functions,
|
|
10
|
+
each for one ability, and suit checks that don't belong to a model, such as reaching the admin dashboard. **Policies**
|
|
11
|
+
are classes that group the rules for one model, such as `PostPolicy` for `Post`. Most apps use both.
|
|
12
|
+
|
|
13
|
+
Every check is async, since a rule may read the database: `await` it.
|
|
14
|
+
|
|
15
|
+
## Gates
|
|
16
|
+
|
|
17
|
+
### Writing gates
|
|
18
|
+
|
|
19
|
+
Define a gate with `Gate.define()`, in a service provider's `boot()`. A gate receives the signed-in user, then the
|
|
20
|
+
arguments of the check, and returns whether the user may go ahead:
|
|
21
|
+
|
|
22
|
+
```ts title="app/Providers/AppServiceProvider.ts"
|
|
23
|
+
import { Gate, ServiceProvider } from '@orieljs/framework'
|
|
24
|
+
import type { Post } from '../Models/Post'
|
|
25
|
+
import type { User } from '../Models/User'
|
|
26
|
+
|
|
27
|
+
export class AppServiceProvider extends ServiceProvider {
|
|
28
|
+
override boot(): void {
|
|
29
|
+
Gate.define('update-post', (user, post) => (user as User).id === (post as Post).author_id)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
A gate can also be a policy method, given as a class and a method name:
|
|
35
|
+
`Gate.define('update-post', [PostPolicy, 'update'])`. A gate may be async.
|
|
36
|
+
|
|
37
|
+
> [!NOTE]
|
|
38
|
+
> A gate's function receives the user typed as `Authenticatable | undefined`, and the check's arguments as `unknown`,
|
|
39
|
+
> since a check can pass anything: cast them, as above. A policy's methods are yours to type, so a rule that takes a
|
|
40
|
+
> model reads better as a [policy](#writing-policies).
|
|
41
|
+
|
|
42
|
+
### Authorizing actions
|
|
43
|
+
|
|
44
|
+
`Gate.allows()` and `Gate.denies()` check a gate for the signed-in user. You don't pass the user: Oriel gives the
|
|
45
|
+
gate the current one.
|
|
46
|
+
|
|
47
|
+
```ts title="app/Http/Controllers/PostController.ts"
|
|
48
|
+
import { abort, Gate, redirect } from '@orieljs/framework'
|
|
49
|
+
import type { Post } from '../../Models/Post'
|
|
50
|
+
import { Controller } from './Controller'
|
|
51
|
+
|
|
52
|
+
export class PostController extends Controller {
|
|
53
|
+
/** Update the given post. */
|
|
54
|
+
async update(post: Post) {
|
|
55
|
+
if (await Gate.denies('update-post', post)) {
|
|
56
|
+
abort(403)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Update the post...
|
|
60
|
+
|
|
61
|
+
return redirect('/posts')
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`Gate.forUser(user)` checks for another user. `Gate.any()` is `true` when any of a list of abilities is allowed, and
|
|
67
|
+
`Gate.none()` when none is; `Gate.check()` takes one ability or a list, all of which must be allowed:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
declare const user: User
|
|
71
|
+
declare const post: Post
|
|
72
|
+
// ---cut---
|
|
73
|
+
if (await Gate.forUser(user).allows('update-post', post)) {
|
|
74
|
+
// The user can update the post...
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (await Gate.any(['update-post', 'delete-post'], post)) {
|
|
78
|
+
// The user can update or delete the post...
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (await Gate.none(['update-post', 'delete-post'], post)) {
|
|
82
|
+
// The user can't update or delete the post...
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
An ability that no gate or policy defines is denied.
|
|
87
|
+
|
|
88
|
+
#### Authorizing or throwing exceptions
|
|
89
|
+
|
|
90
|
+
`Gate.authorize()` checks a gate and throws an `AuthorizationError` when it denies, which Oriel renders as a 403
|
|
91
|
+
response, "This action is unauthorized.":
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
declare const post: Post
|
|
95
|
+
// ---cut---
|
|
96
|
+
await Gate.authorize('update-post', post)
|
|
97
|
+
|
|
98
|
+
// The action is authorized...
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
#### Supplying additional context
|
|
102
|
+
|
|
103
|
+
A gate receives every argument after the ability's name, in order:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
declare const category: number
|
|
107
|
+
declare const pinned: boolean
|
|
108
|
+
// ---cut---
|
|
109
|
+
Gate.define('create-post', (user, category, pinned) => (user as User).id > 0 && Number(category) > 0 && pinned !== true)
|
|
110
|
+
|
|
111
|
+
if (await Gate.allows('create-post', category, pinned)) {
|
|
112
|
+
// The user can create the post...
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Gate responses
|
|
117
|
+
|
|
118
|
+
A gate can return a `GateResponse`, with a message, in place of a boolean:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
Gate.define('edit-settings', (user) =>
|
|
122
|
+
(user as User).email.endsWith('@example.com') ? GateResponse.allow() : GateResponse.deny('You must be an administrator.'),
|
|
123
|
+
)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`Gate.inspect()` returns the response itself, so you can read its message; `Gate.authorize()` throws an
|
|
127
|
+
`AuthorizationError` with it:
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
const response = await Gate.inspect('edit-settings')
|
|
131
|
+
|
|
132
|
+
if (response.allowed()) {
|
|
133
|
+
// The action is authorized...
|
|
134
|
+
} else {
|
|
135
|
+
const message = response.message()
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
#### Customizing the HTTP response status
|
|
140
|
+
|
|
141
|
+
A denial answers 403. `GateResponse.denyWithStatus()` chooses another status, and `GateResponse.denyAsNotFound()`
|
|
142
|
+
answers 404, which hides from the user that the resource exists:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
Gate.define('edit-settings', (user) =>
|
|
146
|
+
(user as User).email.endsWith('@example.com') ? GateResponse.allow() : GateResponse.denyAsNotFound(),
|
|
147
|
+
)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Intercepting gate checks
|
|
151
|
+
|
|
152
|
+
`Gate.before()` runs before every check. A function that returns `true` or `false` decides the check; one that
|
|
153
|
+
returns nothing lets the check go on:
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
Gate.before((user) => {
|
|
157
|
+
if ((user as User | undefined)?.email === 'admin@example.com') {
|
|
158
|
+
return true
|
|
159
|
+
}
|
|
160
|
+
})
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
`Gate.after()` runs after every check, with its result. Its own result counts only when the check had none, such as an
|
|
164
|
+
ability nobody defined.
|
|
165
|
+
|
|
166
|
+
## Creating policies
|
|
167
|
+
|
|
168
|
+
### Generating policies
|
|
169
|
+
|
|
170
|
+
`make:policy` writes a policy in `app/Policies`. With `--model`, it writes a method for each of the model's usual
|
|
171
|
+
actions, typed with your `User` and the model, each denying until you write it:
|
|
172
|
+
|
|
173
|
+
```sh
|
|
174
|
+
node oriel make:policy PostPolicy --model=Post
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
> [!WARNING]
|
|
178
|
+
> Without `--model`, `make:policy` writes `view()`, `create()`, `update()` and `delete()` methods that return `true`,
|
|
179
|
+
> so the policy allows everything until you write them. Since Oriel finds the policy by its name, it applies as soon
|
|
180
|
+
> as the file exists.
|
|
181
|
+
|
|
182
|
+
### Registering policies
|
|
183
|
+
|
|
184
|
+
#### Policy discovery
|
|
185
|
+
|
|
186
|
+
When your app boots, Oriel reads the files at the top of `app/Policies` whose names end in `Policy`, and uses each
|
|
187
|
+
for the model of the same name: `app/Policies/PostPolicy.ts` authorizes `Post`. The policy is the file's default
|
|
188
|
+
export, or its export named after the file. You don't register it.
|
|
189
|
+
|
|
190
|
+
#### Manually registering policies
|
|
191
|
+
|
|
192
|
+
`Gate.policy()` registers a policy for a model, in a service provider's `boot()`, which wins over discovery:
|
|
193
|
+
|
|
194
|
+
```ts title="app/Providers/AppServiceProvider.ts"
|
|
195
|
+
// @filename: app/Policies/OrderPolicy.ts
|
|
196
|
+
export declare class OrderPolicy {}
|
|
197
|
+
// ---cut---
|
|
198
|
+
import { Gate, ServiceProvider } from '@orieljs/framework'
|
|
199
|
+
import { Order } from '../Models/Order'
|
|
200
|
+
import { OrderPolicy } from '../Policies/OrderPolicy'
|
|
201
|
+
|
|
202
|
+
export class AppServiceProvider extends ServiceProvider {
|
|
203
|
+
override boot(): void {
|
|
204
|
+
Gate.policy(Order, OrderPolicy)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Writing policies
|
|
210
|
+
|
|
211
|
+
### Policy methods
|
|
212
|
+
|
|
213
|
+
A policy's methods are named after the abilities they check, and receive the user and the model. The ability
|
|
214
|
+
`update` calls `update()`, and a kebab-case ability such as `view-any` calls `viewAny()`:
|
|
215
|
+
|
|
216
|
+
```ts title="app/Policies/PostPolicy.ts"
|
|
217
|
+
import type { Post } from '../Models/Post'
|
|
218
|
+
import type { User } from '../Models/User'
|
|
219
|
+
|
|
220
|
+
export class PostPolicy {
|
|
221
|
+
/** Determine if the given post can be updated by the user. */
|
|
222
|
+
update(user: User, post: Post): boolean {
|
|
223
|
+
return user.id === post.author_id
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Oriel makes the policy through the container for each check, so a method can take a service with `inject()`.
|
|
229
|
+
|
|
230
|
+
### Policy responses
|
|
231
|
+
|
|
232
|
+
A policy method can return a `GateResponse`, as a gate can:
|
|
233
|
+
|
|
234
|
+
```ts title="app/Policies/PostPolicy.ts"
|
|
235
|
+
import { GateResponse } from '@orieljs/framework'
|
|
236
|
+
import type { Post } from '../Models/Post'
|
|
237
|
+
import type { User } from '../Models/User'
|
|
238
|
+
|
|
239
|
+
export class PostPolicy {
|
|
240
|
+
/** Determine if the given post can be updated by the user. */
|
|
241
|
+
update(user: User, post: Post): GateResponse {
|
|
242
|
+
return user.id === post.author_id ? GateResponse.allow() : GateResponse.deny('You do not own this post.')
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
`GateResponse.denyAsNotFound()` and `denyWithStatus()` work here too.
|
|
248
|
+
|
|
249
|
+
### Methods without models
|
|
250
|
+
|
|
251
|
+
Some actions have no model yet, such as `create`. Check them with the model's class in place of a model: the class
|
|
252
|
+
chooses the policy, and the method receives only the user:
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
declare const user: User
|
|
256
|
+
// ---cut---
|
|
257
|
+
class PostPolicy {
|
|
258
|
+
create(user: User): boolean {
|
|
259
|
+
return user.email_verified_at !== null
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (await user.can('create', Post)) {
|
|
264
|
+
// ...
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Guest users
|
|
269
|
+
|
|
270
|
+
A check made without a signed-in user is denied, and the gate or the policy method isn't called. To let guests
|
|
271
|
+
through a gate, wrap its function in `allowGuests()`; a policy lets them reach all its methods with
|
|
272
|
+
`static allowGuests = true`, or some of them with a list. The user is then `undefined`:
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
class PostPolicy {
|
|
276
|
+
static allowGuests = ['view']
|
|
277
|
+
|
|
278
|
+
view(user: User | undefined, post: Post): boolean {
|
|
279
|
+
return post.published_at !== null || user?.id === post.author_id
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
Gate.define('view-posts', allowGuests(() => true))
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
> [!NOTE]
|
|
287
|
+
> Laravel lets guests through a gate or a policy method whose user parameter is optional or nullable. TypeScript's
|
|
288
|
+
> types are gone when your app runs, so Oriel asks you to say so: `allowGuests()` or `static allowGuests`.
|
|
289
|
+
|
|
290
|
+
### Policy filters
|
|
291
|
+
|
|
292
|
+
A policy's `before()` method runs before its other methods. Return `true` or `false` to decide, or nothing to let the
|
|
293
|
+
method decide. It runs for every ability checked with the policy's model, even one the policy has no method for, which
|
|
294
|
+
Laravel skips:
|
|
295
|
+
|
|
296
|
+
```ts
|
|
297
|
+
class PostPolicy {
|
|
298
|
+
before(user: User, ability: string): boolean | undefined {
|
|
299
|
+
if (user.email === 'admin@example.com') {
|
|
300
|
+
return true
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return undefined
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Authorizing actions using policies
|
|
309
|
+
|
|
310
|
+
### Via the user model
|
|
311
|
+
|
|
312
|
+
The `Authorizable` mixin, which a new app's `User` has, gives the user `can()` and `cannot()`. When the model has a
|
|
313
|
+
policy, the check runs its method:
|
|
314
|
+
|
|
315
|
+
```ts title="app/Http/Controllers/PostController.ts"
|
|
316
|
+
import { abort, redirect, request } from '@orieljs/framework'
|
|
317
|
+
import type { Post } from '../../Models/Post'
|
|
318
|
+
import type { User } from '../../Models/User'
|
|
319
|
+
import { Controller } from './Controller'
|
|
320
|
+
|
|
321
|
+
export class PostController extends Controller {
|
|
322
|
+
/** Update the given post. */
|
|
323
|
+
async update(post: Post) {
|
|
324
|
+
const user = (await request().user<User>())!
|
|
325
|
+
|
|
326
|
+
if (await user.cannot('update', post)) {
|
|
327
|
+
abort(403)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Update the post...
|
|
331
|
+
|
|
332
|
+
return redirect('/posts')
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
`cant()` is `cannot()`, and `canAny()` takes a list of abilities. A model without a policy is checked against the
|
|
338
|
+
gate of that ability.
|
|
339
|
+
|
|
340
|
+
### Via controller helpers
|
|
341
|
+
|
|
342
|
+
The base controller has `authorize()`, which throws an `AuthorizationError`, a 403, when the user may not:
|
|
343
|
+
|
|
344
|
+
```ts title="app/Http/Controllers/PostController.ts"
|
|
345
|
+
import { redirect } from '@orieljs/framework'
|
|
346
|
+
import type { Post } from '../../Models/Post'
|
|
347
|
+
import { Controller } from './Controller'
|
|
348
|
+
|
|
349
|
+
export class PostController extends Controller {
|
|
350
|
+
/** Update the given blog post. */
|
|
351
|
+
async update(post: Post) {
|
|
352
|
+
await this.authorize('update', post)
|
|
353
|
+
|
|
354
|
+
// The current user can update the blog post...
|
|
355
|
+
|
|
356
|
+
return redirect('/posts')
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
Pass the model's class for an action without a model: `await this.authorize('create', Post)`.
|
|
362
|
+
`authorizeForUser(user, ability, ...)` checks for another user.
|
|
363
|
+
|
|
364
|
+
> [!NOTE]
|
|
365
|
+
> Laravel's `authorizeResource()`, which authorizes every action of a resource controller at once, isn't in Oriel.
|
|
366
|
+
> Call `authorize()` in each action.
|
|
367
|
+
|
|
368
|
+
### Via middleware
|
|
369
|
+
|
|
370
|
+
The `can` middleware authorizes a request before it reaches your route. Its first parameter is the ability, and the
|
|
371
|
+
next ones are route parameters, whose bound models it passes on:
|
|
372
|
+
|
|
373
|
+
```ts title="routes/web.ts"
|
|
374
|
+
import { Route } from '@orieljs/framework'
|
|
375
|
+
import type { Post } from '../app/Models/Post'
|
|
376
|
+
|
|
377
|
+
Route.put('/post/{post}', (post: Post) => {
|
|
378
|
+
// The current user may update the post...
|
|
379
|
+
|
|
380
|
+
return post
|
|
381
|
+
}).middleware(['auth', 'can:update,post'])
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
A parameter that isn't a route parameter names a model, for an action without one: `can:create,Post`. A quoted one
|
|
385
|
+
is passed as text: `can:publish,post,"draft"`.
|
|
386
|
+
|
|
387
|
+
> [!NOTE]
|
|
388
|
+
> Laravel's routes also take `->can('update', 'post')`, and its gates have `Gate::allowIf()` and `Gate::denyIf()`.
|
|
389
|
+
> Oriel has neither: use the `can:` middleware, and `Gate.authorize()` with a gate you define.
|
|
390
|
+
|
|
391
|
+
### Via form requests
|
|
392
|
+
|
|
393
|
+
A form request's `authorize()` runs before its rules; returning `false` answers 403:
|
|
394
|
+
|
|
395
|
+
```ts title="app/Http/Requests/UpdatePostRequest.ts"
|
|
396
|
+
import { FormRequest, Gate, request } from '@orieljs/framework'
|
|
397
|
+
import type { Post } from '../../Models/Post'
|
|
398
|
+
|
|
399
|
+
export class UpdatePostRequest extends FormRequest {
|
|
400
|
+
override async authorize(): Promise<boolean> {
|
|
401
|
+
return Gate.allows('update', request().route<Post>('post'))
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
override rules() {
|
|
405
|
+
return { title: 'required|string|max:255' } as const
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### In views and pages
|
|
411
|
+
|
|
412
|
+
Oriel's views are TSX, so there is no `@can`: work out what the user may do in your route or controller, and pass
|
|
413
|
+
the answers to the view or the page as props:
|
|
414
|
+
|
|
415
|
+
```ts title="app/Http/Controllers/PostController.ts"
|
|
416
|
+
import { inertia, request } from '@orieljs/framework'
|
|
417
|
+
import type { Post } from '../../Models/Post'
|
|
418
|
+
import type { User } from '../../Models/User'
|
|
419
|
+
import { Controller } from './Controller'
|
|
420
|
+
|
|
421
|
+
export class PostController extends Controller {
|
|
422
|
+
/** Show the given post. */
|
|
423
|
+
async show(post: Post) {
|
|
424
|
+
const user = await request().user<User>()
|
|
425
|
+
|
|
426
|
+
return inertia('Posts/Show', {
|
|
427
|
+
post,
|
|
428
|
+
can: { update: (await user?.can('update', post)) ?? false },
|
|
429
|
+
})
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
In a Live component, the route's `can` middleware runs again on every update (see
|
|
435
|
+
[Middleware on updates](live.md#middleware-on-updates)).
|