@dudousxd/nestjs-authz-inertia 0.1.0
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/CHANGELOG.md +55 -0
- package/LICENSE +21 -0
- package/README.md +117 -0
- package/dist/authorize-resource.d.ts +28 -0
- package/dist/authorize-resource.d.ts.map +1 -0
- package/dist/authorize-resource.js +49 -0
- package/dist/authorize-resource.js.map +1 -0
- package/dist/authz-denial.filter.d.ts +42 -0
- package/dist/authz-denial.filter.d.ts.map +1 -0
- package/dist/authz-denial.filter.js +172 -0
- package/dist/authz-denial.filter.js.map +1 -0
- package/dist/authz-inertia.interceptor.d.ts +21 -0
- package/dist/authz-inertia.interceptor.d.ts.map +1 -0
- package/dist/authz-inertia.interceptor.js +67 -0
- package/dist/authz-inertia.interceptor.js.map +1 -0
- package/dist/client.d.ts +10 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +9 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/module.d.ts +25 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +53 -0
- package/dist/module.js.map +1 -0
- package/dist/resolve-abilities.d.ts +58 -0
- package/dist/resolve-abilities.d.ts.map +1 -0
- package/dist/resolve-abilities.js +58 -0
- package/dist/resolve-abilities.js.map +1 -0
- package/dist/share.d.ts +28 -0
- package/dist/share.d.ts.map +1 -0
- package/dist/share.js +29 -0
- package/dist/share.js.map +1 -0
- package/dist/tokens.d.ts +3 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +3 -0
- package/dist/tokens.js.map +1 -0
- package/dist/types.d.ts +83 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +78 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# @dudousxd/nestjs-authz-inertia
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#2](https://github.com/DavideCarvalho/nestjs-authz/pull/2) [`2ecb0f4`](https://github.com/DavideCarvalho/nestjs-authz/commit/2ecb0f46342fa4527fc01f1097720f2e7bcf9aa7) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Add `@dudousxd/nestjs-authz-inertia`: a 3-tier Inertia integration that lets client `can(...)`
|
|
8
|
+
checks resolve **without a network request** (the Laravel/Inertia model).
|
|
9
|
+
|
|
10
|
+
- **Tier 1 — shared props (no request):** `AuthzInertiaModule` (a global interceptor that calls
|
|
11
|
+
`req.inertia.share(...)`) or `createAuthzShare(gate, policyRegistry)` (a factory for
|
|
12
|
+
`InertiaModule.forRoot({ share })`) resolves the current user's class-level abilities — all
|
|
13
|
+
ad-hoc gates + class-level `@Policy` methods — into `props.auth.can` via direct in-process
|
|
14
|
+
`gate.allows(...)` calls.
|
|
15
|
+
- **Tier 2 — per-resource map (no request):** `authorizeResource(gate, instance, abilities)`
|
|
16
|
+
returns a `{ update, delete }` map a controller attaches to a serialized resource.
|
|
17
|
+
- **Tier 3 — fallback endpoint (last resort):** consumes core's opt-in `POST /authz/can`.
|
|
18
|
+
- **Framework-neutral client** (`@dudousxd/nestjs-authz-inertia/client`): an `AbilityStore`,
|
|
19
|
+
`hydrateFromInertiaProps` / `hydrateResource`, and a `createCan` resolver that reads hydrated
|
|
20
|
+
decisions synchronously (no fetch on a cache hit) and only falls back (fetch the endpoint, or
|
|
21
|
+
deny) when the ability/resource is unknown.
|
|
22
|
+
- **Denial filter (`AuthzDenialFilter`):** `AuthzInertiaModule.forRoot` also registers a global
|
|
23
|
+
`APP_FILTER` that converts `@Roles`/`@Can` denials (`ForbiddenException`) into a friendly 303
|
|
24
|
+
redirect on Inertia requests (`X-Inertia` header) — `/login` when unauthenticated, `/403` when
|
|
25
|
+
authenticated-but-forbidden (auth-state read from the optional `CONTEXT_ACCESSOR`). Non-Inertia
|
|
26
|
+
(REST) requests are rethrown and keep the normal 403 JSON. Configurable via
|
|
27
|
+
`forRoot({ denial: { loginUrl, forbiddenUrl, enabled, handler } })` (`enabled: false` opts out;
|
|
28
|
+
`handler(exception, host)` is a full escape hatch). Targets pass the same open-redirect guard
|
|
29
|
+
nestjs-inertia uses, falling back to a safe default if unsafe.
|
|
30
|
+
|
|
31
|
+
`@dudousxd/nestjs-authz` (patch): add an opt-in `POST /authz/can` fallback controller behind
|
|
32
|
+
`AuthzModule.forRoot({ canEndpoint: true })` (default off; accepts a custom path string). It runs
|
|
33
|
+
`gate.allows(ability, resource?)` for the current context user and returns `{ allowed }`, failing
|
|
34
|
+
closed for unresolved abilities. Also exposes `Gate.gateNames()` and `PolicyRegistry.classAbilities()`
|
|
35
|
+
so integrations can enumerate a user's class-level abilities.
|
|
36
|
+
|
|
37
|
+
### Patch Changes
|
|
38
|
+
|
|
39
|
+
- [#2](https://github.com/DavideCarvalho/nestjs-authz/pull/2) [`2ecb0f4`](https://github.com/DavideCarvalho/nestjs-authz/commit/2ecb0f46342fa4527fc01f1097720f2e7bcf9aa7) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Extract the framework-neutral ability store into a new `@dudousxd/nestjs-authz-client`
|
|
40
|
+
package and add a `@dudousxd/nestjs-authz-react` package.
|
|
41
|
+
|
|
42
|
+
- **`@dudousxd/nestjs-authz-client`** (new): the canonical home for the in-memory
|
|
43
|
+
`AbilityStore`, `createCan` (synchronous cache hit; configurable `fallback: 'deny' | 'fetch'`
|
|
44
|
+
to `POST /authz/can` on a miss), and `hydrateFromInertiaProps` / `hydrateResource`. Pure
|
|
45
|
+
TypeScript plus an injectable `fetch` — no React/Vue/NestJS/Inertia dependency.
|
|
46
|
+
- **`@dudousxd/nestjs-authz-react`** (new): `AuthzProvider` (holds a hydrated `AbilityStore`),
|
|
47
|
+
`useCan(ability, resource?)` (returns `{ allowed, loading }`, synchronous and request-free on a
|
|
48
|
+
cache hit; honors the store's fallback on a miss), and `<Can ability of fallback>`. A missing
|
|
49
|
+
provider fails closed (deny).
|
|
50
|
+
- **`@dudousxd/nestjs-authz-inertia`** (patch): now depends on `@dudousxd/nestjs-authz-client` and
|
|
51
|
+
re-exports its store instead of carrying a private copy. The
|
|
52
|
+
`@dudousxd/nestjs-authz-inertia/client` entry point is unchanged for consumers.
|
|
53
|
+
|
|
54
|
+
- Updated dependencies [[`2ecb0f4`](https://github.com/DavideCarvalho/nestjs-authz/commit/2ecb0f46342fa4527fc01f1097720f2e7bcf9aa7)]:
|
|
55
|
+
- @dudousxd/nestjs-authz-client@0.1.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Davi Carvalho
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# @dudousxd/nestjs-authz-inertia
|
|
2
|
+
|
|
3
|
+
Inertia.js integration for [`@dudousxd/nestjs-authz`](../core). It makes client-side
|
|
4
|
+
`can(...)` checks resolve **without a network request** by sharing the current
|
|
5
|
+
user's abilities as Inertia props — the Laravel/Inertia model.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @dudousxd/nestjs-authz-inertia
|
|
9
|
+
# peers: @dudousxd/nestjs-authz, @dudousxd/nestjs-inertia
|
|
10
|
+
# (optional) @dudousxd/nestjs-context
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## The 3-tier model — prefer props → resource map → endpoint fallback
|
|
14
|
+
|
|
15
|
+
Authorization decisions reach the front end through three tiers. Prefer the earlier
|
|
16
|
+
(no-request) tiers; the endpoint is a last resort.
|
|
17
|
+
|
|
18
|
+
### Tier 1 — Shared props (no request)
|
|
19
|
+
|
|
20
|
+
Every Inertia render carries the current user's **class-level abilities** (all ad-hoc
|
|
21
|
+
gates + class-level `@Policy` methods like `create`/`viewAny`) under `props.auth.can`:
|
|
22
|
+
|
|
23
|
+
```jsonc
|
|
24
|
+
{ "auth": { "can": { "post.create": true, "access-admin": false } } }
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Two equivalent ways to wire it:
|
|
28
|
+
|
|
29
|
+
**A. Module (auto, via `req.inertia.share`)** — register `AuthzInertiaModule`; a global
|
|
30
|
+
interceptor shares the map on every Inertia request:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
@Module({
|
|
34
|
+
imports: [
|
|
35
|
+
AuthzModule.forRoot({ policies: [PostPolicy] }),
|
|
36
|
+
InertiaModule.forRoot({ /* ... */ }),
|
|
37
|
+
AuthzInertiaModule.forRoot(), // shares props.auth.can automatically
|
|
38
|
+
],
|
|
39
|
+
})
|
|
40
|
+
export class AppModule {}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**B. Share factory (explicit, via inertia's own seam)** — drop a factory into
|
|
44
|
+
`InertiaModule.forRoot({ share })`:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
InertiaModule.forRoot({
|
|
48
|
+
share: createAuthzShare(gate, policyRegistry),
|
|
49
|
+
})
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Both resolve abilities by direct in-process `gate.allows(...)` calls — no HTTP round-trip.
|
|
53
|
+
The ability name defaults to `"<resourceLower>.<method>"` (e.g. `post.create`); pass
|
|
54
|
+
`abilityNamer: (_, m) => m` for bare method names, or a custom `propKey`.
|
|
55
|
+
|
|
56
|
+
### Tier 2 — Per-resource `can` map (no request)
|
|
57
|
+
|
|
58
|
+
For instance-specific decisions (`update`/`delete` of *this* post), serialize a `can`
|
|
59
|
+
map onto the resource in your controller:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
const post = await this.posts.find(id);
|
|
63
|
+
const can = await authorizeResource(this.gate, post, ['update', 'delete']);
|
|
64
|
+
return this.inertia.render('Post/Show', { post: { ...post, can } });
|
|
65
|
+
// → { post: { id, title, can: { update: true, delete: false } } }
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
On the client, hydrate it into the store keyed by `type#id` so `can('update', post)`
|
|
69
|
+
answers synchronously. (`authorizeResourceForUser` checks an explicit user.)
|
|
70
|
+
|
|
71
|
+
### Tier 3 — Fallback endpoint (request, last resort)
|
|
72
|
+
|
|
73
|
+
For abilities not hydrated on the client, enable the opt-in endpoint in **core**:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
AuthzModule.forRoot({ canEndpoint: true }) // POST /authz/can → { allowed }
|
|
77
|
+
// or a custom path: canEndpoint: 'api/authz/can'
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
It runs `gate.allows(ability, resource?)` for the current context user and returns
|
|
81
|
+
`{ allowed }`. Off by default. This is the path the codegen-emitted `can()` targets.
|
|
82
|
+
|
|
83
|
+
> **Class-level only.** The endpoint can resolve **class-level abilities and ad-hoc gates**
|
|
84
|
+
> only — the `{ type, id }` resource shim it receives never matches a `@Policy` by constructor.
|
|
85
|
+
> Per-instance decisions MUST be hydrated via tiers 1-2 (shared props / `authorizeResource`);
|
|
86
|
+
> a **resource-bound** ability that misses the cache and falls through to `/authz/can` will
|
|
87
|
+
> **deny** (and `createCan` logs a one-time `console.warn`). Treat tier 3 as a class-level /
|
|
88
|
+
> ad-hoc fallback, not a per-instance one.
|
|
89
|
+
|
|
90
|
+
## Client store (framework-neutral)
|
|
91
|
+
|
|
92
|
+
`@dudousxd/nestjs-authz-inertia/client` is a tiny in-memory store + resolver with no
|
|
93
|
+
framework dependency — `-react` and the codegen client reuse it.
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { AbilityStore, hydrateFromInertiaProps, hydrateResource, createCan } from '@dudousxd/nestjs-authz-inertia/client';
|
|
97
|
+
|
|
98
|
+
const store = new AbilityStore();
|
|
99
|
+
hydrateFromInertiaProps(store, page.props); // tier 1: props.auth.can
|
|
100
|
+
hydrateResource(store, { type: 'Post', id: post.id }, post.can); // tier 2
|
|
101
|
+
|
|
102
|
+
const can = createCan(store, { fallback: 'fetch', endpoint: '/authz/can' });
|
|
103
|
+
|
|
104
|
+
can('post.create'); // → true (sync, no request)
|
|
105
|
+
can('update', { type: 'Post', id: 7 }); // → false (sync, from resource map)
|
|
106
|
+
can('mystery'); // fallback: 'deny' → false; 'fetch' → Promise<boolean>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
- **Cache hit** → returns a `boolean` synchronously, never touches the network.
|
|
110
|
+
- **Cache miss** → `fallback: 'deny'` (default) returns `false`; `fallback: 'fetch'`
|
|
111
|
+
POSTs to the tier-3 endpoint and returns a `Promise<boolean>`. A **resource-bound**
|
|
112
|
+
miss under `'fetch'` can only deny (the endpoint resolves class-level abilities only)
|
|
113
|
+
and logs a one-time `console.warn` — hydrate per-instance decisions via tiers 1-2.
|
|
114
|
+
|
|
115
|
+
## License
|
|
116
|
+
|
|
117
|
+
MIT
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Gate, Resource } from '@dudousxd/nestjs-authz';
|
|
2
|
+
import type { CanMap } from './resolve-abilities.js';
|
|
3
|
+
/**
|
|
4
|
+
* Resolve a per-INSTANCE `can` map for one resource, with NO HTTP round-trip:
|
|
5
|
+
* each ability is a direct `gate.allows(ability, resource)` call against the
|
|
6
|
+
* in-process Gate for the current (context) user.
|
|
7
|
+
*
|
|
8
|
+
* Use it controller-side to attach decisions to a serialized resource:
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* const post = await this.posts.find(id);
|
|
12
|
+
* const can = await authorizeResource(this.gate, post, ['update', 'delete']);
|
|
13
|
+
* return this.inertia.render('Post/Show', { post: { ...post, can } });
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* The client store keys these maps by `type#id`, so hydrating them lets
|
|
17
|
+
* `can('update', post)` return the cached verdict synchronously on the front end.
|
|
18
|
+
*
|
|
19
|
+
* @returns e.g. `{ update: true, delete: false }`
|
|
20
|
+
*/
|
|
21
|
+
export declare function authorizeResource(gate: Gate, resource: Resource, abilities: string[]): Promise<CanMap>;
|
|
22
|
+
/**
|
|
23
|
+
* Like {@link authorizeResource}, but resolves for an explicit user via
|
|
24
|
+
* `gate.forUser(user)` instead of the current context user. Useful outside a
|
|
25
|
+
* request, or to serialize decisions for a user other than the caller.
|
|
26
|
+
*/
|
|
27
|
+
export declare function authorizeResourceForUser(gate: Gate, user: unknown, resource: Resource, abilities: string[]): Promise<CanMap>;
|
|
28
|
+
//# sourceMappingURL=authorize-resource.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authorize-resource.d.ts","sourceRoot":"","sources":["../src/authorize-resource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAErD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,QAAQ,EAClB,SAAS,EAAE,MAAM,EAAE,GAClB,OAAO,CAAC,MAAM,CAAC,CAUjB;AAED;;;;GAIG;AACH,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,OAAO,EACb,QAAQ,EAAE,QAAQ,EAClB,SAAS,EAAE,MAAM,EAAE,GAClB,OAAO,CAAC,MAAM,CAAC,CAWjB"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a per-INSTANCE `can` map for one resource, with NO HTTP round-trip:
|
|
3
|
+
* each ability is a direct `gate.allows(ability, resource)` call against the
|
|
4
|
+
* in-process Gate for the current (context) user.
|
|
5
|
+
*
|
|
6
|
+
* Use it controller-side to attach decisions to a serialized resource:
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* const post = await this.posts.find(id);
|
|
10
|
+
* const can = await authorizeResource(this.gate, post, ['update', 'delete']);
|
|
11
|
+
* return this.inertia.render('Post/Show', { post: { ...post, can } });
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* The client store keys these maps by `type#id`, so hydrating them lets
|
|
15
|
+
* `can('update', post)` return the cached verdict synchronously on the front end.
|
|
16
|
+
*
|
|
17
|
+
* @returns e.g. `{ update: true, delete: false }`
|
|
18
|
+
*/
|
|
19
|
+
export async function authorizeResource(gate, resource, abilities) {
|
|
20
|
+
const out = {};
|
|
21
|
+
for (const ability of abilities) {
|
|
22
|
+
try {
|
|
23
|
+
out[ability] = await gate.allows(ability, resource);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
out[ability] = false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Like {@link authorizeResource}, but resolves for an explicit user via
|
|
33
|
+
* `gate.forUser(user)` instead of the current context user. Useful outside a
|
|
34
|
+
* request, or to serialize decisions for a user other than the caller.
|
|
35
|
+
*/
|
|
36
|
+
export async function authorizeResourceForUser(gate, user, resource, abilities) {
|
|
37
|
+
const bound = gate.forUser(user);
|
|
38
|
+
const out = {};
|
|
39
|
+
for (const ability of abilities) {
|
|
40
|
+
try {
|
|
41
|
+
out[ability] = await bound.allows(ability, resource);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
out[ability] = false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=authorize-resource.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authorize-resource.js","sourceRoot":"","sources":["../src/authorize-resource.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,IAAU,EACV,QAAkB,EAClB,SAAmB;IAEnB,MAAM,GAAG,GAAW,EAAE,CAAC;IACvB,KAAK,MAAM,OAAO,IAAI,SAAS,EAAE,CAAC;QAChC,IAAI,CAAC;YACH,GAAG,CAAC,OAAO,CAAC,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QACtD,CAAC;QAAC,MAAM,CAAC;YACP,GAAG,CAAC,OAAO,CAAC,GAAG,KAAK,CAAC;QACvB,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,IAAU,EACV,IAAa,EACb,QAAkB,EAClB,SAAmB;IAEnB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,MAAM,GAAG,GAAW,EAAE,CAAC;IACvB,KAAK,MAAM,OAAO,IAAI,SAAS,EAAE,CAAC;QAChC,IAAI,CAAC;YACH,GAAG,CAAC,OAAO,CAAC,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QACvD,CAAC;QAAC,MAAM,CAAC;YACP,GAAG,CAAC,OAAO,CAAC,GAAG,KAAK,CAAC;QACvB,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type ContextAccessor } from '@dudousxd/nestjs-authz';
|
|
2
|
+
import { type ArgumentsHost, type ExceptionFilter, ForbiddenException } from '@nestjs/common';
|
|
3
|
+
import type { AuthzInertiaOptions } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Default redirect target for an UNAUTHENTICATED Inertia denial.
|
|
6
|
+
*/
|
|
7
|
+
export declare const DEFAULT_LOGIN_URL = "/login";
|
|
8
|
+
/**
|
|
9
|
+
* Default redirect target for an AUTHENTICATED-but-FORBIDDEN Inertia denial.
|
|
10
|
+
*/
|
|
11
|
+
export declare const DEFAULT_FORBIDDEN_URL = "/403";
|
|
12
|
+
/**
|
|
13
|
+
* Catches authorization denials (`@Roles`/`@Can` → `ForbiddenException`) and, on
|
|
14
|
+
* an Inertia request (`X-Inertia` header), 303-redirects to a friendly page
|
|
15
|
+
* instead of bubbling a raw 403. Mirrors the structure of nestjs-inertia's
|
|
16
|
+
* `InertiaValidationFilter`: detect the Inertia request via the `X-Inertia`
|
|
17
|
+
* header, issue a safe same-origin redirect (303 + `Location`), and rethrow
|
|
18
|
+
* everything else so normal Nest handling (JSON 403) applies — preserving REST
|
|
19
|
+
* clients.
|
|
20
|
+
*
|
|
21
|
+
* Redirect targets are decided by auth-state, read from the OPTIONAL
|
|
22
|
+
* `CONTEXT_ACCESSOR` (`userRef()` undefined → unauthenticated):
|
|
23
|
+
* - unauthenticated → `loginUrl` (default `/login`)
|
|
24
|
+
* - authenticated but forbidden → `forbiddenUrl` (default `/403`)
|
|
25
|
+
*
|
|
26
|
+
* Registered unconditionally as an `APP_FILTER` by
|
|
27
|
+
* `AuthzInertiaModule.forRoot`; on by default and disabled via
|
|
28
|
+
* `forRoot({ denial: { enabled: false } })`. A `handler(exception, host)`
|
|
29
|
+
* escape hatch fully overrides the behavior on Inertia requests.
|
|
30
|
+
*/
|
|
31
|
+
export declare class AuthzDenialFilter implements ExceptionFilter {
|
|
32
|
+
private readonly options;
|
|
33
|
+
private readonly context?;
|
|
34
|
+
constructor(options?: AuthzInertiaOptions, context?: ContextAccessor | undefined);
|
|
35
|
+
catch(exception: ForbiddenException, host: ArgumentsHost): void;
|
|
36
|
+
/**
|
|
37
|
+
* Pick the redirect target from auth-state. An undefined `userRef()` (or no
|
|
38
|
+
* context accessor at all) is treated as unauthenticated.
|
|
39
|
+
*/
|
|
40
|
+
private resolveRedirectTarget;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=authz-denial.filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authz-denial.filter.d.ts","sourceRoot":"","sources":["../src/authz-denial.filter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAChF,OAAO,EACL,KAAK,aAAa,EAElB,KAAK,eAAe,EACpB,kBAAkB,EAGnB,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAsB,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAE1E;;GAEG;AACH,eAAO,MAAM,iBAAiB,WAAW,CAAC;AAE1C;;GAEG;AACH,eAAO,MAAM,qBAAqB,SAAS,CAAC;AAE5C;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBACa,iBAAkB,YAAW,eAAe;IAIrD,OAAO,CAAC,QAAQ,CAAC,OAAO;IAGxB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;gBAHR,OAAO,GAAE,mBAAwB,EAGjC,OAAO,CAAC,EAAE,eAAe,YAAA;IAG5C,KAAK,CAAC,SAAS,EAAE,kBAAkB,EAAE,IAAI,EAAE,aAAa,GAAG,IAAI;IA2B/D;;;OAGG;IACH,OAAO,CAAC,qBAAqB;CAO9B"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
11
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
|
+
};
|
|
13
|
+
import { CONTEXT_ACCESSOR } from '@dudousxd/nestjs-authz';
|
|
14
|
+
import { Catch, ForbiddenException, Inject, Optional, } from '@nestjs/common';
|
|
15
|
+
import { AUTHZ_INERTIA_OPTIONS } from './tokens.js';
|
|
16
|
+
/**
|
|
17
|
+
* Default redirect target for an UNAUTHENTICATED Inertia denial.
|
|
18
|
+
*/
|
|
19
|
+
export const DEFAULT_LOGIN_URL = '/login';
|
|
20
|
+
/**
|
|
21
|
+
* Default redirect target for an AUTHENTICATED-but-FORBIDDEN Inertia denial.
|
|
22
|
+
*/
|
|
23
|
+
export const DEFAULT_FORBIDDEN_URL = '/403';
|
|
24
|
+
/**
|
|
25
|
+
* Catches authorization denials (`@Roles`/`@Can` → `ForbiddenException`) and, on
|
|
26
|
+
* an Inertia request (`X-Inertia` header), 303-redirects to a friendly page
|
|
27
|
+
* instead of bubbling a raw 403. Mirrors the structure of nestjs-inertia's
|
|
28
|
+
* `InertiaValidationFilter`: detect the Inertia request via the `X-Inertia`
|
|
29
|
+
* header, issue a safe same-origin redirect (303 + `Location`), and rethrow
|
|
30
|
+
* everything else so normal Nest handling (JSON 403) applies — preserving REST
|
|
31
|
+
* clients.
|
|
32
|
+
*
|
|
33
|
+
* Redirect targets are decided by auth-state, read from the OPTIONAL
|
|
34
|
+
* `CONTEXT_ACCESSOR` (`userRef()` undefined → unauthenticated):
|
|
35
|
+
* - unauthenticated → `loginUrl` (default `/login`)
|
|
36
|
+
* - authenticated but forbidden → `forbiddenUrl` (default `/403`)
|
|
37
|
+
*
|
|
38
|
+
* Registered unconditionally as an `APP_FILTER` by
|
|
39
|
+
* `AuthzInertiaModule.forRoot`; on by default and disabled via
|
|
40
|
+
* `forRoot({ denial: { enabled: false } })`. A `handler(exception, host)`
|
|
41
|
+
* escape hatch fully overrides the behavior on Inertia requests.
|
|
42
|
+
*/
|
|
43
|
+
let AuthzDenialFilter = class AuthzDenialFilter {
|
|
44
|
+
options;
|
|
45
|
+
context;
|
|
46
|
+
constructor(options = {}, context) {
|
|
47
|
+
this.options = options;
|
|
48
|
+
this.context = context;
|
|
49
|
+
}
|
|
50
|
+
catch(exception, host) {
|
|
51
|
+
const denial = this.options.denial ?? {};
|
|
52
|
+
// Disabled, or non-HTTP context: rethrow so normal Nest handling applies.
|
|
53
|
+
if (denial.enabled === false || host.getType() !== 'http') {
|
|
54
|
+
throw exception;
|
|
55
|
+
}
|
|
56
|
+
const http = host.switchToHttp();
|
|
57
|
+
const req = http.getRequest();
|
|
58
|
+
const res = http.getResponse();
|
|
59
|
+
// Gate: only Inertia requests. Else rethrow → normal 403 JSON for REST.
|
|
60
|
+
if (!getHeader(req, 'X-Inertia')) {
|
|
61
|
+
throw exception;
|
|
62
|
+
}
|
|
63
|
+
// Escape hatch: a custom handler takes full control on Inertia requests.
|
|
64
|
+
if (denial.handler) {
|
|
65
|
+
denial.handler(exception, host);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const target = this.resolveRedirectTarget(denial);
|
|
69
|
+
sendRedirect(res, 303, target);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Pick the redirect target from auth-state. An undefined `userRef()` (or no
|
|
73
|
+
* context accessor at all) is treated as unauthenticated.
|
|
74
|
+
*/
|
|
75
|
+
resolveRedirectTarget(denial) {
|
|
76
|
+
const loginUrl = denial.loginUrl ?? DEFAULT_LOGIN_URL;
|
|
77
|
+
const forbiddenUrl = denial.forbiddenUrl ?? DEFAULT_FORBIDDEN_URL;
|
|
78
|
+
const authenticated = this.context?.userRef() !== undefined;
|
|
79
|
+
const target = authenticated ? forbiddenUrl : loginUrl;
|
|
80
|
+
return toSafePath(target, authenticated ? DEFAULT_FORBIDDEN_URL : DEFAULT_LOGIN_URL);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
AuthzDenialFilter = __decorate([
|
|
84
|
+
Catch(ForbiddenException),
|
|
85
|
+
__param(0, Optional()),
|
|
86
|
+
__param(0, Inject(AUTHZ_INERTIA_OPTIONS)),
|
|
87
|
+
__param(1, Optional()),
|
|
88
|
+
__param(1, Inject(CONTEXT_ACCESSOR)),
|
|
89
|
+
__metadata("design:paramtypes", [Object, Object])
|
|
90
|
+
], AuthzDenialFilter);
|
|
91
|
+
export { AuthzDenialFilter };
|
|
92
|
+
/**
|
|
93
|
+
* Reduce a configured target to a safe same-origin path via the same
|
|
94
|
+
* open-redirect guard nestjs-inertia uses (`validateLocationUrl`), falling back
|
|
95
|
+
* to a known-safe default if the target is rejected.
|
|
96
|
+
*/
|
|
97
|
+
function toSafePath(target, fallback) {
|
|
98
|
+
try {
|
|
99
|
+
return validateLocationUrl(target);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return fallback;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Local mirror of nestjs-inertia's `getHeader`. Reads a request header in a
|
|
107
|
+
* cross-runtime way (Express `req.header(name)` / raw Fastify `req.headers`).
|
|
108
|
+
* The package couples to nestjs-inertia STRUCTURALLY (request/response shape),
|
|
109
|
+
* so this is replicated rather than imported.
|
|
110
|
+
*/
|
|
111
|
+
function getHeader(req, name) {
|
|
112
|
+
const r = req;
|
|
113
|
+
if (typeof r.header === 'function')
|
|
114
|
+
return r.header(name);
|
|
115
|
+
const v = r.headers?.[name.toLowerCase()];
|
|
116
|
+
return Array.isArray(v) ? v[0] : v;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Local mirror of nestjs-inertia's `validateLocationUrl` open-redirect guard.
|
|
120
|
+
* Only relative URLs (single leading `/`) or recognizable relative paths are
|
|
121
|
+
* permitted; absolute/protocol-relative/unsafe-scheme URLs throw.
|
|
122
|
+
*/
|
|
123
|
+
function validateLocationUrl(url) {
|
|
124
|
+
if (url.startsWith('/') && !url.startsWith('//'))
|
|
125
|
+
return url;
|
|
126
|
+
if (url.startsWith('//')) {
|
|
127
|
+
throw new Error(`[nestjs-authz-inertia] denial redirect rejected protocol-relative URL: ${url}`);
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
const parsed = new URL(url);
|
|
131
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
132
|
+
throw new Error(`[nestjs-authz-inertia] denial redirect rejected unsafe URL scheme: ${url}`);
|
|
133
|
+
}
|
|
134
|
+
throw new Error(`[nestjs-authz-inertia] denial redirect rejected absolute URL to external host: ${url}.`);
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
if (err instanceof TypeError) {
|
|
138
|
+
if (/^[a-zA-Z0-9]/.test(url) || url.startsWith('.')) {
|
|
139
|
+
return url;
|
|
140
|
+
}
|
|
141
|
+
throw new Error(`[nestjs-authz-inertia] denial redirect rejected unrecognized URL: ${url}`);
|
|
142
|
+
}
|
|
143
|
+
throw err;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Local mirror of nestjs-inertia's cross-runtime redirect emit. Sets the status
|
|
148
|
+
* code + `Location` header and ends the response, duck-typing Express
|
|
149
|
+
* (`status`/`setHeader`/`end`) vs raw Fastify (`code`/`header`/`send`).
|
|
150
|
+
*/
|
|
151
|
+
function sendRedirect(res, status, url) {
|
|
152
|
+
const r = res;
|
|
153
|
+
if (typeof r.status === 'function') {
|
|
154
|
+
r.status(status);
|
|
155
|
+
}
|
|
156
|
+
else if (typeof r.code === 'function') {
|
|
157
|
+
r.code(status);
|
|
158
|
+
}
|
|
159
|
+
if (typeof r.setHeader === 'function') {
|
|
160
|
+
r.setHeader('Location', url);
|
|
161
|
+
}
|
|
162
|
+
else if (typeof r.header === 'function') {
|
|
163
|
+
r.header('Location', url);
|
|
164
|
+
}
|
|
165
|
+
if (typeof r.end === 'function') {
|
|
166
|
+
r.end();
|
|
167
|
+
}
|
|
168
|
+
else if (typeof r.send === 'function') {
|
|
169
|
+
r.send();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=authz-denial.filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authz-denial.filter.js","sourceRoot":"","sources":["../src/authz-denial.filter.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,OAAO,EAAE,gBAAgB,EAAwB,MAAM,wBAAwB,CAAC;AAChF,OAAO,EAEL,KAAK,EAEL,kBAAkB,EAClB,MAAM,EACN,QAAQ,GACT,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAGpD;;GAEG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,QAAQ,CAAC;AAE1C;;GAEG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,CAAC;AAE5C;;;;;;;;;;;;;;;;;;GAkBG;AAEI,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;IAIT;IAGA;IANnB,YAGmB,UAA+B,EAAE,EAGjC,OAAyB;QAHzB,YAAO,GAAP,OAAO,CAA0B;QAGjC,YAAO,GAAP,OAAO,CAAkB;IACzC,CAAC;IAEJ,KAAK,CAAC,SAA6B,EAAE,IAAmB;QACtD,MAAM,MAAM,GAAuB,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC;QAE7D,0EAA0E;QAC1E,IAAI,MAAM,CAAC,OAAO,KAAK,KAAK,IAAI,IAAI,CAAC,OAAO,EAAE,KAAK,MAAM,EAAE,CAAC;YAC1D,MAAM,SAAS,CAAC;QAClB,CAAC;QAED,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;QACjC,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,EAAW,CAAC;QACvC,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,EAAW,CAAC;QAExC,wEAAwE;QACxE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,WAAW,CAAC,EAAE,CAAC;YACjC,MAAM,SAAS,CAAC;QAClB,CAAC;QAED,yEAAyE;QACzE,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,MAAM,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YAChC,OAAO;QACT,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,qBAAqB,CAAC,MAAM,CAAC,CAAC;QAClD,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;IACjC,CAAC;IAED;;;OAGG;IACK,qBAAqB,CAAC,MAA0B;QACtD,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,iBAAiB,CAAC;QACtD,MAAM,YAAY,GAAG,MAAM,CAAC,YAAY,IAAI,qBAAqB,CAAC;QAClE,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,SAAS,CAAC;QAC5D,MAAM,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC;QACvD,OAAO,UAAU,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC;IACvF,CAAC;CACF,CAAA;AAhDY,iBAAiB;IAD7B,KAAK,CAAC,kBAAkB,CAAC;IAGrB,WAAA,QAAQ,EAAE,CAAA;IACV,WAAA,MAAM,CAAC,qBAAqB,CAAC,CAAA;IAE7B,WAAA,QAAQ,EAAE,CAAA;IACV,WAAA,MAAM,CAAC,gBAAgB,CAAC,CAAA;;GANhB,iBAAiB,CAgD7B;;AAED;;;;GAIG;AACH,SAAS,UAAU,CAAC,MAAc,EAAE,QAAgB;IAClD,IAAI,CAAC;QACH,OAAO,mBAAmB,CAAC,MAAM,CAAC,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,QAAQ,CAAC;IAClB,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,SAAS,SAAS,CAAC,GAAY,EAAE,IAAY;IAC3C,MAAM,CAAC,GAAG,GAGT,CAAC;IACF,IAAI,OAAO,CAAC,CAAC,MAAM,KAAK,UAAU;QAAE,OAAO,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC1D,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IAC1C,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACrC,CAAC;AAED;;;;GAIG;AACH,SAAS,mBAAmB,CAAC,GAAW;IACtC,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,GAAG,CAAC;IAC7D,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CACb,0EAA0E,GAAG,EAAE,CAChF,CAAC;IACJ,CAAC;IACD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAChE,MAAM,IAAI,KAAK,CAAC,sEAAsE,GAAG,EAAE,CAAC,CAAC;QAC/F,CAAC;QACD,MAAM,IAAI,KAAK,CACb,kFAAkF,GAAG,GAAG,CACzF,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,SAAS,EAAE,CAAC;YAC7B,IAAI,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBACpD,OAAO,GAAG,CAAC;YACb,CAAC;YACD,MAAM,IAAI,KAAK,CAAC,qEAAqE,GAAG,EAAE,CAAC,CAAC;QAC9F,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,YAAY,CAAC,GAAY,EAAE,MAAc,EAAE,GAAW;IAC7D,MAAM,CAAC,GAAG,GAOT,CAAC;IAEF,IAAI,OAAO,CAAC,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;QACnC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACnB,CAAC;SAAM,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QACxC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACjB,CAAC;IAED,IAAI,OAAO,CAAC,CAAC,SAAS,KAAK,UAAU,EAAE,CAAC;QACtC,CAAC,CAAC,SAAS,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;IAC/B,CAAC;SAAM,IAAI,OAAO,CAAC,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;QAC1C,CAAC,CAAC,MAAM,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;IAC5B,CAAC;IAED,IAAI,OAAO,CAAC,CAAC,GAAG,KAAK,UAAU,EAAE,CAAC;QAChC,CAAC,CAAC,GAAG,EAAE,CAAC;IACV,CAAC;SAAM,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QACxC,CAAC,CAAC,IAAI,EAAE,CAAC;IACX,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Gate, PolicyRegistry } from '@dudousxd/nestjs-authz';
|
|
2
|
+
import { type CallHandler, type ExecutionContext, type NestInterceptor } from '@nestjs/common';
|
|
3
|
+
import type { Observable } from 'rxjs';
|
|
4
|
+
import type { AuthzInertiaOptions } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Auto-shares the current user's class-level abilities into `props.<propKey>.can`
|
|
7
|
+
* for every HTTP request that carries an attached Inertia service (`req.inertia`),
|
|
8
|
+
* by calling `req.inertia.share(...)` BEFORE the route handler renders.
|
|
9
|
+
*
|
|
10
|
+
* No network request: abilities are resolved by direct `gate.allows` calls.
|
|
11
|
+
* Non-HTTP contexts and requests without `req.inertia` are passed through untouched.
|
|
12
|
+
*/
|
|
13
|
+
export declare class AuthzInertiaInterceptor implements NestInterceptor {
|
|
14
|
+
private readonly gate;
|
|
15
|
+
private readonly policies;
|
|
16
|
+
private readonly options;
|
|
17
|
+
private readonly propKey;
|
|
18
|
+
constructor(gate: Gate, policies: PolicyRegistry, options?: AuthzInertiaOptions);
|
|
19
|
+
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown>;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=authz-inertia.interceptor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authz-inertia.interceptor.d.ts","sourceRoot":"","sources":["../src/authz-inertia.interceptor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,gBAAgB,EAGrB,KAAK,eAAe,EAErB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAKvC,OAAO,KAAK,EAAE,mBAAmB,EAAsB,MAAM,YAAY,CAAC;AAE1E;;;;;;;GAOG;AACH,qBACa,uBAAwB,YAAW,eAAe;IAI3D,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAGzB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAP1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAGd,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,cAAc,EAGxB,OAAO,GAAE,mBAAwB;IAKpD,SAAS,CAAC,OAAO,EAAE,gBAAgB,EAAE,IAAI,EAAE,WAAW,GAAG,UAAU,CAAC,OAAO,CAAC;CAuB7E"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
11
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
|
+
};
|
|
13
|
+
import { Gate, PolicyRegistry } from '@dudousxd/nestjs-authz';
|
|
14
|
+
import { Inject, Injectable, Optional, } from '@nestjs/common';
|
|
15
|
+
import { from, switchMap } from 'rxjs';
|
|
16
|
+
import { resolveClassAbilities } from './resolve-abilities.js';
|
|
17
|
+
import { DEFAULT_PROP_KEY } from './share.js';
|
|
18
|
+
import { AUTHZ_INERTIA_OPTIONS } from './tokens.js';
|
|
19
|
+
/**
|
|
20
|
+
* Auto-shares the current user's class-level abilities into `props.<propKey>.can`
|
|
21
|
+
* for every HTTP request that carries an attached Inertia service (`req.inertia`),
|
|
22
|
+
* by calling `req.inertia.share(...)` BEFORE the route handler renders.
|
|
23
|
+
*
|
|
24
|
+
* No network request: abilities are resolved by direct `gate.allows` calls.
|
|
25
|
+
* Non-HTTP contexts and requests without `req.inertia` are passed through untouched.
|
|
26
|
+
*/
|
|
27
|
+
let AuthzInertiaInterceptor = class AuthzInertiaInterceptor {
|
|
28
|
+
gate;
|
|
29
|
+
policies;
|
|
30
|
+
options;
|
|
31
|
+
propKey;
|
|
32
|
+
constructor(gate, policies, options = {}) {
|
|
33
|
+
this.gate = gate;
|
|
34
|
+
this.policies = policies;
|
|
35
|
+
this.options = options;
|
|
36
|
+
this.propKey = options?.propKey ?? DEFAULT_PROP_KEY;
|
|
37
|
+
}
|
|
38
|
+
intercept(context, next) {
|
|
39
|
+
if (context.getType() !== 'http')
|
|
40
|
+
return next.handle();
|
|
41
|
+
const req = context.switchToHttp().getRequest();
|
|
42
|
+
const inertia = req?.inertia;
|
|
43
|
+
if (!inertia || typeof inertia.share !== 'function')
|
|
44
|
+
return next.handle();
|
|
45
|
+
return from(resolveClassAbilities(this.gate, this.policies, {
|
|
46
|
+
...(this.options.abilities !== undefined ? { abilities: this.options.abilities } : {}),
|
|
47
|
+
...(this.options.abilityNamer !== undefined
|
|
48
|
+
? { abilityNamer: this.options.abilityNamer }
|
|
49
|
+
: {}),
|
|
50
|
+
...(this.options.includeBareMethodNames !== undefined
|
|
51
|
+
? { includeBareMethodNames: this.options.includeBareMethodNames }
|
|
52
|
+
: {}),
|
|
53
|
+
})).pipe(switchMap((can) => {
|
|
54
|
+
inertia.share({ [this.propKey]: { can } });
|
|
55
|
+
return next.handle();
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
AuthzInertiaInterceptor = __decorate([
|
|
60
|
+
Injectable(),
|
|
61
|
+
__param(2, Optional()),
|
|
62
|
+
__param(2, Inject(AUTHZ_INERTIA_OPTIONS)),
|
|
63
|
+
__metadata("design:paramtypes", [Gate,
|
|
64
|
+
PolicyRegistry, Object])
|
|
65
|
+
], AuthzInertiaInterceptor);
|
|
66
|
+
export { AuthzInertiaInterceptor };
|
|
67
|
+
//# sourceMappingURL=authz-inertia.interceptor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authz-inertia.interceptor.js","sourceRoot":"","sources":["../src/authz-inertia.interceptor.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAGL,MAAM,EACN,UAAU,EAEV,QAAQ,GACT,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AACvC,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAGpD;;;;;;;GAOG;AAEI,IAAM,uBAAuB,GAA7B,MAAM,uBAAuB;IAIf;IACA;IAGA;IAPF,OAAO,CAAS;IAEjC,YACmB,IAAU,EACV,QAAwB,EAGxB,UAA+B,EAAE;QAJjC,SAAI,GAAJ,IAAI,CAAM;QACV,aAAQ,GAAR,QAAQ,CAAgB;QAGxB,YAAO,GAAP,OAAO,CAA0B;QAElD,IAAI,CAAC,OAAO,GAAG,OAAO,EAAE,OAAO,IAAI,gBAAgB,CAAC;IACtD,CAAC;IAED,SAAS,CAAC,OAAyB,EAAE,IAAiB;QACpD,IAAI,OAAO,CAAC,OAAO,EAAE,KAAK,MAAM;YAAE,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC;QACvD,MAAM,GAAG,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAsB,CAAC;QACpE,MAAM,OAAO,GAAG,GAAG,EAAE,OAAO,CAAC;QAC7B,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,CAAC,KAAK,KAAK,UAAU;YAAE,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC;QAE1E,OAAO,IAAI,CACT,qBAAqB,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE;YAC9C,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACtF,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,KAAK,SAAS;gBACzC,CAAC,CAAC,EAAE,YAAY,EAAE,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE;gBAC7C,CAAC,CAAC,EAAE,CAAC;YACP,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,sBAAsB,KAAK,SAAS;gBACnD,CAAC,CAAC,EAAE,sBAAsB,EAAE,IAAI,CAAC,OAAO,CAAC,sBAAsB,EAAE;gBACjE,CAAC,CAAC,EAAE,CAAC;SACR,CAAC,CACH,CAAC,IAAI,CACJ,SAAS,CAAC,CAAC,GAAG,EAAE,EAAE;YAChB,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;YAC3C,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC;QACvB,CAAC,CAAC,CACH,CAAC;IACJ,CAAC;CACF,CAAA;AApCY,uBAAuB;IADnC,UAAU,EAAE;IAOR,WAAA,QAAQ,EAAE,CAAA;IACV,WAAA,MAAM,CAAC,qBAAqB,CAAC,CAAA;qCAHP,IAAI;QACA,cAAc;GALhC,uBAAuB,CAoCnC"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-export of the framework-neutral ability store, now canonicalized in
|
|
3
|
+
* {@link @dudousxd/nestjs-authz-client}. Kept here so the long-standing
|
|
4
|
+
* `@dudousxd/nestjs-authz-inertia/client` entry point continues to work.
|
|
5
|
+
*
|
|
6
|
+
* @module client
|
|
7
|
+
*/
|
|
8
|
+
export { AbilityStore, createCan, hydrateFromInertiaProps, hydrateResource, } from '@dudousxd/nestjs-authz-client';
|
|
9
|
+
export type { CanMap, ResourceRef, HydratableProps, FallbackMode, AbilityStoreOptions, CanResolver, } from '@dudousxd/nestjs-authz-client';
|
|
10
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,YAAY,EACZ,SAAS,EACT,uBAAuB,EACvB,eAAe,GAChB,MAAM,+BAA+B,CAAC;AACvC,YAAY,EACV,MAAM,EACN,WAAW,EACX,eAAe,EACf,YAAY,EACZ,mBAAmB,EACnB,WAAW,GACZ,MAAM,+BAA+B,CAAC"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-export of the framework-neutral ability store, now canonicalized in
|
|
3
|
+
* {@link @dudousxd/nestjs-authz-client}. Kept here so the long-standing
|
|
4
|
+
* `@dudousxd/nestjs-authz-inertia/client` entry point continues to work.
|
|
5
|
+
*
|
|
6
|
+
* @module client
|
|
7
|
+
*/
|
|
8
|
+
export { AbilityStore, createCan, hydrateFromInertiaProps, hydrateResource, } from '@dudousxd/nestjs-authz-client';
|
|
9
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,YAAY,EACZ,SAAS,EACT,uBAAuB,EACvB,eAAe,GAChB,MAAM,+BAA+B,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const VERSION = "0.0.0";
|
|
2
|
+
export { AuthzInertiaModule } from './module.js';
|
|
3
|
+
export { AuthzInertiaInterceptor } from './authz-inertia.interceptor.js';
|
|
4
|
+
export { AuthzDenialFilter, DEFAULT_LOGIN_URL, DEFAULT_FORBIDDEN_URL, } from './authz-denial.filter.js';
|
|
5
|
+
export { createAuthzShare, DEFAULT_PROP_KEY } from './share.js';
|
|
6
|
+
export type { CreateAuthzShareOptions } from './share.js';
|
|
7
|
+
export { resolveClassAbilities, defaultAbilityNamer, } from './resolve-abilities.js';
|
|
8
|
+
export type { CanMap, AbilityNamer, ResolveAbilitiesOptions, } from './resolve-abilities.js';
|
|
9
|
+
export { authorizeResource, authorizeResourceForUser } from './authorize-resource.js';
|
|
10
|
+
export { AUTHZ_INERTIA_OPTIONS } from './tokens.js';
|
|
11
|
+
export type { AuthzInertiaOptions, AuthzDenialOptions, SharedAuthProps, InertiaShareable, RequestWithInertia, } from './types.js';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO,UAAU,CAAC;AAE/B,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACzE,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAChE,YAAY,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EACL,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EACV,MAAM,EACN,YAAY,EACZ,uBAAuB,GACxB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACtF,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,YAAY,EACV,mBAAmB,EACnB,kBAAkB,EAClB,eAAe,EACf,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const VERSION = '0.0.0';
|
|
2
|
+
export { AuthzInertiaModule } from './module.js';
|
|
3
|
+
export { AuthzInertiaInterceptor } from './authz-inertia.interceptor.js';
|
|
4
|
+
export { AuthzDenialFilter, DEFAULT_LOGIN_URL, DEFAULT_FORBIDDEN_URL, } from './authz-denial.filter.js';
|
|
5
|
+
export { createAuthzShare, DEFAULT_PROP_KEY } from './share.js';
|
|
6
|
+
export { resolveClassAbilities, defaultAbilityNamer, } from './resolve-abilities.js';
|
|
7
|
+
export { authorizeResource, authorizeResourceForUser } from './authorize-resource.js';
|
|
8
|
+
export { AUTHZ_INERTIA_OPTIONS } from './tokens.js';
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC;AAE/B,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACzE,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEhE,OAAO,EACL,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,wBAAwB,CAAC;AAMhC,OAAO,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACtF,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/module.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type DynamicModule } from '@nestjs/common';
|
|
2
|
+
import type { AuthzInertiaOptions } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Wires the {@link AuthzInertiaInterceptor} as a global `APP_INTERCEPTOR` so every
|
|
5
|
+
* Inertia render automatically carries `props.<propKey>.can` — the current user's
|
|
6
|
+
* class-level abilities, resolved in-process (no network request).
|
|
7
|
+
*
|
|
8
|
+
* Requires `@dudousxd/nestjs-authz`'s `AuthzModule` (for `Gate`/`PolicyRegistry`)
|
|
9
|
+
* and `@dudousxd/nestjs-inertia`'s `InertiaModule` (for `req.inertia`) to be
|
|
10
|
+
* imported. Order does not matter; the interceptor no-ops when `req.inertia` is
|
|
11
|
+
* absent.
|
|
12
|
+
*
|
|
13
|
+
* Alternatively, skip this module and pass `createAuthzShare(gate, policies)` to
|
|
14
|
+
* `InertiaModule.forRoot({ share })` for the same result via inertia's own
|
|
15
|
+
* prop-sharing seam.
|
|
16
|
+
*
|
|
17
|
+
* It also registers the {@link AuthzDenialFilter} as a global `APP_FILTER`, so
|
|
18
|
+
* `@Roles`/`@Can` denials (`ForbiddenException`) redirect nicely on Inertia
|
|
19
|
+
* requests (`/login` when unauthenticated, `/403` when forbidden) while staying
|
|
20
|
+
* a normal 403 JSON for REST. Opt out via `forRoot({ denial: { enabled: false } })`.
|
|
21
|
+
*/
|
|
22
|
+
export declare class AuthzInertiaModule {
|
|
23
|
+
static forRoot(options?: AuthzInertiaOptions): DynamicModule;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAAyB,MAAM,gBAAgB,CAAC;AAK3E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEtD;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBACa,kBAAkB;IAC7B,MAAM,CAAC,OAAO,CAAC,OAAO,GAAE,mBAAwB,GAAG,aAAa;CAejE"}
|
package/dist/module.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var AuthzInertiaModule_1;
|
|
8
|
+
import { Module } from '@nestjs/common';
|
|
9
|
+
import { APP_FILTER, APP_INTERCEPTOR } from '@nestjs/core';
|
|
10
|
+
import { AuthzDenialFilter } from './authz-denial.filter.js';
|
|
11
|
+
import { AuthzInertiaInterceptor } from './authz-inertia.interceptor.js';
|
|
12
|
+
import { AUTHZ_INERTIA_OPTIONS } from './tokens.js';
|
|
13
|
+
/**
|
|
14
|
+
* Wires the {@link AuthzInertiaInterceptor} as a global `APP_INTERCEPTOR` so every
|
|
15
|
+
* Inertia render automatically carries `props.<propKey>.can` — the current user's
|
|
16
|
+
* class-level abilities, resolved in-process (no network request).
|
|
17
|
+
*
|
|
18
|
+
* Requires `@dudousxd/nestjs-authz`'s `AuthzModule` (for `Gate`/`PolicyRegistry`)
|
|
19
|
+
* and `@dudousxd/nestjs-inertia`'s `InertiaModule` (for `req.inertia`) to be
|
|
20
|
+
* imported. Order does not matter; the interceptor no-ops when `req.inertia` is
|
|
21
|
+
* absent.
|
|
22
|
+
*
|
|
23
|
+
* Alternatively, skip this module and pass `createAuthzShare(gate, policies)` to
|
|
24
|
+
* `InertiaModule.forRoot({ share })` for the same result via inertia's own
|
|
25
|
+
* prop-sharing seam.
|
|
26
|
+
*
|
|
27
|
+
* It also registers the {@link AuthzDenialFilter} as a global `APP_FILTER`, so
|
|
28
|
+
* `@Roles`/`@Can` denials (`ForbiddenException`) redirect nicely on Inertia
|
|
29
|
+
* requests (`/login` when unauthenticated, `/403` when forbidden) while staying
|
|
30
|
+
* a normal 403 JSON for REST. Opt out via `forRoot({ denial: { enabled: false } })`.
|
|
31
|
+
*/
|
|
32
|
+
let AuthzInertiaModule = AuthzInertiaModule_1 = class AuthzInertiaModule {
|
|
33
|
+
static forRoot(options = {}) {
|
|
34
|
+
const providers = [
|
|
35
|
+
{ provide: AUTHZ_INERTIA_OPTIONS, useValue: options },
|
|
36
|
+
AuthzInertiaInterceptor,
|
|
37
|
+
{ provide: APP_INTERCEPTOR, useClass: AuthzInertiaInterceptor },
|
|
38
|
+
AuthzDenialFilter,
|
|
39
|
+
{ provide: APP_FILTER, useClass: AuthzDenialFilter },
|
|
40
|
+
];
|
|
41
|
+
return {
|
|
42
|
+
module: AuthzInertiaModule_1,
|
|
43
|
+
global: true,
|
|
44
|
+
providers,
|
|
45
|
+
exports: [AUTHZ_INERTIA_OPTIONS, AuthzInertiaInterceptor, AuthzDenialFilter],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
AuthzInertiaModule = AuthzInertiaModule_1 = __decorate([
|
|
50
|
+
Module({})
|
|
51
|
+
], AuthzInertiaModule);
|
|
52
|
+
export { AuthzInertiaModule };
|
|
53
|
+
//# sourceMappingURL=module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.js","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":";;;;;;;AAAA,OAAO,EAAsB,MAAM,EAAiB,MAAM,gBAAgB,CAAC;AAC3E,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACzE,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAGpD;;;;;;;;;;;;;;;;;;GAkBG;AAEI,IAAM,kBAAkB,0BAAxB,MAAM,kBAAkB;IAC7B,MAAM,CAAC,OAAO,CAAC,UAA+B,EAAE;QAC9C,MAAM,SAAS,GAAe;YAC5B,EAAE,OAAO,EAAE,qBAAqB,EAAE,QAAQ,EAAE,OAAO,EAAE;YACrD,uBAAuB;YACvB,EAAE,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,uBAAuB,EAAE;YAC/D,iBAAiB;YACjB,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,iBAAiB,EAAE;SACrD,CAAC;QACF,OAAO;YACL,MAAM,EAAE,oBAAkB;YAC1B,MAAM,EAAE,IAAI;YACZ,SAAS;YACT,OAAO,EAAE,CAAC,qBAAqB,EAAE,uBAAuB,EAAE,iBAAiB,CAAC;SAC7E,CAAC;IACJ,CAAC;CACF,CAAA;AAhBY,kBAAkB;IAD9B,MAAM,CAAC,EAAE,CAAC;GACE,kBAAkB,CAgB9B"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { Gate, PolicyRegistry } from '@dudousxd/nestjs-authz';
|
|
2
|
+
import type { CanMap } from '@dudousxd/nestjs-authz-client';
|
|
3
|
+
/**
|
|
4
|
+
* A flat record of `ability -> verdict`, e.g. `{ 'posts.create': true }`.
|
|
5
|
+
* Re-exported from `@dudousxd/nestjs-authz-client` so there is a single source
|
|
6
|
+
* of truth shared with the client store.
|
|
7
|
+
*/
|
|
8
|
+
export type { CanMap };
|
|
9
|
+
/**
|
|
10
|
+
* How an ability name is built from a policy's resource class + method name when
|
|
11
|
+
* enumerating class-level policy abilities. Default: `"<ResourceName>.<method>"`
|
|
12
|
+
* lower-cased on the resource (e.g. `Post` + `create` → `posts.create`... no — see
|
|
13
|
+
* below). Kept overridable so an app can match the naming its `@Can` decorators use.
|
|
14
|
+
*/
|
|
15
|
+
export type AbilityNamer = (resourceName: string, method: string) => string;
|
|
16
|
+
/**
|
|
17
|
+
* Default namer: `"<resourceNameLower>.<method>"` (e.g. `Post`,`create` →
|
|
18
|
+
* `post.create`). Apps whose `@Can` abilities are bare method names
|
|
19
|
+
* (`create`, `viewAny`) can pass `(_, m) => m`.
|
|
20
|
+
*/
|
|
21
|
+
export declare const defaultAbilityNamer: AbilityNamer;
|
|
22
|
+
export interface ResolveAbilitiesOptions {
|
|
23
|
+
/**
|
|
24
|
+
* Extra ad-hoc gate / explicit ability names to resolve in addition to the
|
|
25
|
+
* gates auto-discovered via {@link Gate.gateNames}. Useful for gates registered
|
|
26
|
+
* lazily or abilities you always want present in props.
|
|
27
|
+
*/
|
|
28
|
+
abilities?: string[];
|
|
29
|
+
/**
|
|
30
|
+
* Build the ability name from a policy's `(resourceClassName, method)`. Defaults
|
|
31
|
+
* to {@link defaultAbilityNamer}. Use `(_, m) => m` for bare method-name abilities.
|
|
32
|
+
*/
|
|
33
|
+
abilityNamer?: AbilityNamer;
|
|
34
|
+
/**
|
|
35
|
+
* Include the bare policy method name as an additional key alongside the
|
|
36
|
+
* namespaced one (so both `post.create` and `create` resolve). Default `false`.
|
|
37
|
+
*/
|
|
38
|
+
includeBareMethodNames?: boolean;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Resolve the current user's CLASS-LEVEL abilities into a flat `{ ability: boolean }`
|
|
42
|
+
* map, with NO per-request HTTP round-trip — each entry is a direct `gate.allows`
|
|
43
|
+
* call against the in-process Gate.
|
|
44
|
+
*
|
|
45
|
+
* Sources, in order:
|
|
46
|
+
* 1. Ad-hoc gates registered via `gate.define(...)` (model-less, e.g. `access-admin`).
|
|
47
|
+
* 2. Class-level `@Policy` methods (those taking no resource instance — heuristically
|
|
48
|
+
* arity `<= 1`, e.g. `create(user)`/`viewAny(user)`), dispatched against the resource
|
|
49
|
+
* CLASS so no instance is loaded. Instance methods like `update(user, post)` are NOT
|
|
50
|
+
* enumerated (see {@link PolicyRegistry.classAbilities}) — dispatching them against the
|
|
51
|
+
* class would write a bogus class-level verdict.
|
|
52
|
+
* 3. Any extra `options.abilities` names.
|
|
53
|
+
*
|
|
54
|
+
* Gates are checked by name; policy methods are checked by passing the resource
|
|
55
|
+
* class as the dispatch target (Gate resolves the class-level policy method).
|
|
56
|
+
*/
|
|
57
|
+
export declare function resolveClassAbilities(gate: Gate, policies: PolicyRegistry, options?: ResolveAbilitiesOptions): Promise<CanMap>;
|
|
58
|
+
//# sourceMappingURL=resolve-abilities.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-abilities.d.ts","sourceRoot":"","sources":["../src/resolve-abilities.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,cAAc,EAAY,MAAM,wBAAwB,CAAC;AAC7E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AAE5D;;;;GAIG;AACH,YAAY,EAAE,MAAM,EAAE,CAAC;AAEvB;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;AAE5E;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,YACS,CAAC;AAE5C,MAAM,WAAW,uBAAuB;IACtC;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB;;;OAGG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B;;;OAGG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,cAAc,EACxB,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,MAAM,CAAC,CAwBjB"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default namer: `"<resourceNameLower>.<method>"` (e.g. `Post`,`create` →
|
|
3
|
+
* `post.create`). Apps whose `@Can` abilities are bare method names
|
|
4
|
+
* (`create`, `viewAny`) can pass `(_, m) => m`.
|
|
5
|
+
*/
|
|
6
|
+
export const defaultAbilityNamer = (resourceName, method) => `${resourceName.toLowerCase()}.${method}`;
|
|
7
|
+
/**
|
|
8
|
+
* Resolve the current user's CLASS-LEVEL abilities into a flat `{ ability: boolean }`
|
|
9
|
+
* map, with NO per-request HTTP round-trip — each entry is a direct `gate.allows`
|
|
10
|
+
* call against the in-process Gate.
|
|
11
|
+
*
|
|
12
|
+
* Sources, in order:
|
|
13
|
+
* 1. Ad-hoc gates registered via `gate.define(...)` (model-less, e.g. `access-admin`).
|
|
14
|
+
* 2. Class-level `@Policy` methods (those taking no resource instance — heuristically
|
|
15
|
+
* arity `<= 1`, e.g. `create(user)`/`viewAny(user)`), dispatched against the resource
|
|
16
|
+
* CLASS so no instance is loaded. Instance methods like `update(user, post)` are NOT
|
|
17
|
+
* enumerated (see {@link PolicyRegistry.classAbilities}) — dispatching them against the
|
|
18
|
+
* class would write a bogus class-level verdict.
|
|
19
|
+
* 3. Any extra `options.abilities` names.
|
|
20
|
+
*
|
|
21
|
+
* Gates are checked by name; policy methods are checked by passing the resource
|
|
22
|
+
* class as the dispatch target (Gate resolves the class-level policy method).
|
|
23
|
+
*/
|
|
24
|
+
export async function resolveClassAbilities(gate, policies, options = {}) {
|
|
25
|
+
const namer = options.abilityNamer ?? defaultAbilityNamer;
|
|
26
|
+
const out = {};
|
|
27
|
+
// 1. Ad-hoc gates (model-less). Resolve by ability name only.
|
|
28
|
+
const gateNames = new Set([...gate.gateNames(), ...(options.abilities ?? [])]);
|
|
29
|
+
for (const ability of gateNames) {
|
|
30
|
+
out[ability] = await safeAllows(gate, ability);
|
|
31
|
+
}
|
|
32
|
+
// 2. Class-level policy methods, dispatched against the resource CLASS.
|
|
33
|
+
for (const { resource, abilities } of policies.classAbilities()) {
|
|
34
|
+
const resourceName = resource.name ?? 'Resource';
|
|
35
|
+
for (const method of abilities) {
|
|
36
|
+
const key = namer(resourceName, method);
|
|
37
|
+
// Gate.allows accepts a Type (class) as the resource for class-level abilities.
|
|
38
|
+
out[key] = await safeAllows(gate, method, resource);
|
|
39
|
+
if (options.includeBareMethodNames && out[method] === undefined) {
|
|
40
|
+
out[method] = out[key];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* `gate.allows` may throw (unresolved/ambiguous ability). For prop sharing we
|
|
48
|
+
* fail closed (deny) rather than crash the render.
|
|
49
|
+
*/
|
|
50
|
+
async function safeAllows(gate, ability, resource) {
|
|
51
|
+
try {
|
|
52
|
+
return await gate.allows(ability, resource);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=resolve-abilities.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-abilities.js","sourceRoot":"","sources":["../src/resolve-abilities.ts"],"names":[],"mappings":"AAkBA;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAiB,CAAC,YAAY,EAAE,MAAM,EAAE,EAAE,CACxE,GAAG,YAAY,CAAC,WAAW,EAAE,IAAI,MAAM,EAAE,CAAC;AAqB5C;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,IAAU,EACV,QAAwB,EACxB,UAAmC,EAAE;IAErC,MAAM,KAAK,GAAG,OAAO,CAAC,YAAY,IAAI,mBAAmB,CAAC;IAC1D,MAAM,GAAG,GAAW,EAAE,CAAC;IAEvB,8DAA8D;IAC9D,MAAM,SAAS,GAAG,IAAI,GAAG,CAAS,CAAC,GAAG,IAAI,CAAC,SAAS,EAAE,EAAE,GAAG,CAAC,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACvF,KAAK,MAAM,OAAO,IAAI,SAAS,EAAE,CAAC;QAChC,GAAG,CAAC,OAAO,CAAC,GAAG,MAAM,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACjD,CAAC;IAED,wEAAwE;IACxE,KAAK,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,QAAQ,CAAC,cAAc,EAAE,EAAE,CAAC;QAChE,MAAM,YAAY,GAAI,QAA8B,CAAC,IAAI,IAAI,UAAU,CAAC;QACxE,KAAK,MAAM,MAAM,IAAI,SAAS,EAAE,CAAC;YAC/B,MAAM,GAAG,GAAG,KAAK,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;YACxC,gFAAgF;YAChF,GAAG,CAAC,GAAG,CAAC,GAAG,MAAM,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAA+B,CAAC,CAAC;YAC3E,IAAI,OAAO,CAAC,sBAAsB,IAAI,GAAG,CAAC,MAAM,CAAC,KAAK,SAAS,EAAE,CAAC;gBAChE,GAAG,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;YACzB,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,UAAU,CAAC,IAAU,EAAE,OAAe,EAAE,QAAmB;IACxE,IAAI,CAAC;QACH,OAAO,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
package/dist/share.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Gate, PolicyRegistry } from '@dudousxd/nestjs-authz';
|
|
2
|
+
import { type ResolveAbilitiesOptions } from './resolve-abilities.js';
|
|
3
|
+
/** Default prop namespace: `props.auth.can`. */
|
|
4
|
+
export declare const DEFAULT_PROP_KEY = "auth";
|
|
5
|
+
export interface CreateAuthzShareOptions extends ResolveAbilitiesOptions {
|
|
6
|
+
/** Prop key the `{ can }` object nests under. Default `'auth'`. */
|
|
7
|
+
propKey?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Build an Inertia **share factory** that resolves the current user's class-level
|
|
11
|
+
* abilities into `props.<propKey>.can` — with NO network request (direct in-process
|
|
12
|
+
* `gate.allows` calls).
|
|
13
|
+
*
|
|
14
|
+
* Drop it into `InertiaModule.forRoot({ share })` (matching nestjs-inertia's
|
|
15
|
+
* prop-sharing idiom):
|
|
16
|
+
*
|
|
17
|
+
* ```ts
|
|
18
|
+
* InertiaModule.forRoot({
|
|
19
|
+
* share: createAuthzShare(gate, policyRegistry),
|
|
20
|
+
* })
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* The returned factory is an async `(req) => Props`; nestjs-inertia awaits it per
|
|
24
|
+
* render and merges the result into every page's shared props. The client store's
|
|
25
|
+
* `hydrateFromInertiaProps` then reads `props.auth.can`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function createAuthzShare(gate: Gate, policies: PolicyRegistry, options?: CreateAuthzShareOptions): () => Promise<Record<string, unknown>>;
|
|
28
|
+
//# sourceMappingURL=share.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"share.d.ts","sourceRoot":"","sources":["../src/share.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,EAAE,KAAK,uBAAuB,EAAyB,MAAM,wBAAwB,CAAC;AAE7F,gDAAgD;AAChD,eAAO,MAAM,gBAAgB,SAAS,CAAC;AAEvC,MAAM,WAAW,uBAAwB,SAAQ,uBAAuB;IACtE,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,cAAc,EACxB,OAAO,GAAE,uBAA4B,GACpC,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAMxC"}
|
package/dist/share.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { resolveClassAbilities } from './resolve-abilities.js';
|
|
2
|
+
/** Default prop namespace: `props.auth.can`. */
|
|
3
|
+
export const DEFAULT_PROP_KEY = 'auth';
|
|
4
|
+
/**
|
|
5
|
+
* Build an Inertia **share factory** that resolves the current user's class-level
|
|
6
|
+
* abilities into `props.<propKey>.can` — with NO network request (direct in-process
|
|
7
|
+
* `gate.allows` calls).
|
|
8
|
+
*
|
|
9
|
+
* Drop it into `InertiaModule.forRoot({ share })` (matching nestjs-inertia's
|
|
10
|
+
* prop-sharing idiom):
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* InertiaModule.forRoot({
|
|
14
|
+
* share: createAuthzShare(gate, policyRegistry),
|
|
15
|
+
* })
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* The returned factory is an async `(req) => Props`; nestjs-inertia awaits it per
|
|
19
|
+
* render and merges the result into every page's shared props. The client store's
|
|
20
|
+
* `hydrateFromInertiaProps` then reads `props.auth.can`.
|
|
21
|
+
*/
|
|
22
|
+
export function createAuthzShare(gate, policies, options = {}) {
|
|
23
|
+
const propKey = options.propKey ?? DEFAULT_PROP_KEY;
|
|
24
|
+
return async () => {
|
|
25
|
+
const can = await resolveClassAbilities(gate, policies, options);
|
|
26
|
+
return { [propKey]: { can } };
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=share.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"share.js","sourceRoot":"","sources":["../src/share.ts"],"names":[],"mappings":"AACA,OAAO,EAAgC,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAE7F,gDAAgD;AAChD,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAOvC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,gBAAgB,CAC9B,IAAU,EACV,QAAwB,EACxB,UAAmC,EAAE;IAErC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,gBAAgB,CAAC;IACpD,OAAO,KAAK,IAAI,EAAE;QAChB,MAAM,GAAG,GAAG,MAAM,qBAAqB,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;QACjE,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC;IAChC,CAAC,CAAC;AACJ,CAAC"}
|
package/dist/tokens.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,eAAO,MAAM,qBAAqB,eAAuD,CAAC"}
|
package/dist/tokens.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,CAAC,GAAG,CAAC,wCAAwC,CAAC,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { ArgumentsHost, ForbiddenException } from '@nestjs/common';
|
|
2
|
+
import type { AbilityNamer, CanMap } from './resolve-abilities.js';
|
|
3
|
+
/**
|
|
4
|
+
* Configures the {@link AuthzDenialFilter}: how authorization denials
|
|
5
|
+
* (`@Roles`/`@Can` → `ForbiddenException`) render on Inertia requests. Non-Inertia
|
|
6
|
+
* (REST) requests are unaffected — they keep the normal 403 JSON.
|
|
7
|
+
*/
|
|
8
|
+
export interface AuthzDenialOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Disable the filter entirely (it is registered unconditionally; this opts
|
|
11
|
+
* out at runtime, leaving the raw 403 for Inertia requests too).
|
|
12
|
+
*
|
|
13
|
+
* Default: `true` (denials redirect on Inertia requests).
|
|
14
|
+
*/
|
|
15
|
+
enabled?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Redirect target for an UNAUTHENTICATED denial (no `CONTEXT_ACCESSOR` user).
|
|
18
|
+
*
|
|
19
|
+
* Default: `'/login'`.
|
|
20
|
+
*/
|
|
21
|
+
loginUrl?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Redirect target for an AUTHENTICATED-but-FORBIDDEN denial.
|
|
24
|
+
*
|
|
25
|
+
* Default: `'/403'`.
|
|
26
|
+
*/
|
|
27
|
+
forbiddenUrl?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Escape hatch: fully handle the denial on an Inertia request yourself (e.g.
|
|
30
|
+
* render an error page instead of redirecting). Receives the thrown
|
|
31
|
+
* `ForbiddenException` and the `ArgumentsHost`. When provided, it replaces the
|
|
32
|
+
* default redirect behavior for Inertia requests.
|
|
33
|
+
*/
|
|
34
|
+
handler?: (exception: ForbiddenException, host: ArgumentsHost) => void;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Minimal structural mirror of the request object carrying `req.inertia`, set by
|
|
38
|
+
* `@dudousxd/nestjs-inertia`'s middleware. We deliberately do NOT import the
|
|
39
|
+
* InertiaService type to keep the coupling structural (the real service satisfies
|
|
40
|
+
* this shape). `share(input)` merges `input` into the page's shared props.
|
|
41
|
+
*/
|
|
42
|
+
export interface InertiaShareable {
|
|
43
|
+
share(input: Record<string, unknown>): unknown;
|
|
44
|
+
}
|
|
45
|
+
/** A request that may carry an attached Inertia service. */
|
|
46
|
+
export interface RequestWithInertia {
|
|
47
|
+
inertia?: InertiaShareable;
|
|
48
|
+
}
|
|
49
|
+
export interface AuthzInertiaOptions {
|
|
50
|
+
/**
|
|
51
|
+
* Prop key the resolved ability map is shared under. The client store reads
|
|
52
|
+
* `props.auth.can`, so the default nests the map at `auth.can`. Override to
|
|
53
|
+
* change the namespace (e.g. `'abilities'` → `props.abilities`).
|
|
54
|
+
*
|
|
55
|
+
* Default: `'auth'` → `{ auth: { can: { ... } } }`.
|
|
56
|
+
*/
|
|
57
|
+
propKey?: string;
|
|
58
|
+
/**
|
|
59
|
+
* Extra ad-hoc ability names to resolve in addition to auto-discovered gates.
|
|
60
|
+
*/
|
|
61
|
+
abilities?: string[];
|
|
62
|
+
/**
|
|
63
|
+
* Build a policy ability name from `(resourceClassName, method)`. Defaults to
|
|
64
|
+
* `"<resourceLower>.<method>"`. Use `(_, m) => m` for bare method names.
|
|
65
|
+
*/
|
|
66
|
+
abilityNamer?: AbilityNamer;
|
|
67
|
+
/**
|
|
68
|
+
* Also emit the bare policy method name as a key alongside the namespaced one.
|
|
69
|
+
* Default `false`.
|
|
70
|
+
*/
|
|
71
|
+
includeBareMethodNames?: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Configures how authorization denials render on Inertia requests via the
|
|
74
|
+
* {@link AuthzDenialFilter}. Omit to use the defaults (redirect on; `/login`
|
|
75
|
+
* unauthenticated, `/403` forbidden).
|
|
76
|
+
*/
|
|
77
|
+
denial?: AuthzDenialOptions;
|
|
78
|
+
}
|
|
79
|
+
/** The shape shared into props (under {@link AuthzInertiaOptions.propKey}). */
|
|
80
|
+
export interface SharedAuthProps {
|
|
81
|
+
can: CanMap;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAEnE;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,kBAAkB,EAAE,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;CACxE;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC;CAChD;AAED,4DAA4D;AAC5D,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,gBAAgB,CAAC;CAC5B;AAED,MAAM,WAAW,mBAAmB;IAClC;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB;;;OAGG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B;;;OAGG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC;;;;OAIG;IACH,MAAM,CAAC,EAAE,kBAAkB,CAAC;CAC7B;AAED,+EAA+E;AAC/E,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;CACb"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dudousxd/nestjs-authz-inertia",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Inertia.js integration for @dudousxd/nestjs-authz — share a user's abilities as props so client `can()` checks need no network request.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/DavideCarvalho/nestjs-authz.git",
|
|
9
|
+
"directory": "packages/inertia"
|
|
10
|
+
},
|
|
11
|
+
"author": "Davi Carvalho <davi@goflip.ai>",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./client": {
|
|
22
|
+
"types": "./dist/client.d.ts",
|
|
23
|
+
"import": "./dist/client.js",
|
|
24
|
+
"default": "./dist/client.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist/",
|
|
29
|
+
"README.md",
|
|
30
|
+
"CHANGELOG.md"
|
|
31
|
+
],
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@dudousxd/nestjs-authz-client": "^0.1.0"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"@dudousxd/nestjs-authz": ">=0.2.0",
|
|
37
|
+
"@dudousxd/nestjs-context": ">=0.1.0",
|
|
38
|
+
"@dudousxd/nestjs-inertia": ">=1.0.0",
|
|
39
|
+
"@nestjs/common": ">=10.0.0",
|
|
40
|
+
"@nestjs/core": ">=10.0.0",
|
|
41
|
+
"reflect-metadata": ">=0.1.13"
|
|
42
|
+
},
|
|
43
|
+
"peerDependenciesMeta": {
|
|
44
|
+
"@dudousxd/nestjs-context": {
|
|
45
|
+
"optional": true
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@dudousxd/nestjs-inertia": "^1.5.0",
|
|
50
|
+
"@nestjs/common": "^11.0.0",
|
|
51
|
+
"@nestjs/core": "^11.0.0",
|
|
52
|
+
"@nestjs/testing": "^11.0.0",
|
|
53
|
+
"@types/node": "^20.0.0",
|
|
54
|
+
"reflect-metadata": "^0.2.2",
|
|
55
|
+
"rxjs": "^7.8.1",
|
|
56
|
+
"typescript": "^5.4.0",
|
|
57
|
+
"vitest": "^3.0.0",
|
|
58
|
+
"@dudousxd/nestjs-authz": "^0.3.0"
|
|
59
|
+
},
|
|
60
|
+
"engines": {
|
|
61
|
+
"node": ">=20"
|
|
62
|
+
},
|
|
63
|
+
"keywords": [
|
|
64
|
+
"nestjs",
|
|
65
|
+
"authorization",
|
|
66
|
+
"authz",
|
|
67
|
+
"inertia",
|
|
68
|
+
"inertia.js",
|
|
69
|
+
"abilities",
|
|
70
|
+
"can"
|
|
71
|
+
],
|
|
72
|
+
"scripts": {
|
|
73
|
+
"build": "tsc -p tsconfig.json",
|
|
74
|
+
"test": "vitest run --passWithNoTests",
|
|
75
|
+
"test:watch": "vitest",
|
|
76
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
77
|
+
}
|
|
78
|
+
}
|