@rytass/wms-module-graphql 0.3.0 → 0.4.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/README.md +28 -0
- package/adapters/member-base.d.ts +35 -0
- package/adapters/member-base.js +47 -0
- package/docs/README.md +3 -0
- package/docs/api-reference.md +60 -1
- package/docs/authorization.md +349 -0
- package/docs/integration-guide.md +38 -2
- package/index.cjs.js +172 -3
- package/index.d.ts +2 -0
- package/index.js +4 -0
- package/lib/auth/host-skip.constant.d.ts +27 -0
- package/lib/auth/host-skip.constant.js +29 -0
- package/lib/auth/index.d.ts +4 -0
- package/lib/auth/wms-auth.guard.d.ts +19 -0
- package/lib/auth/wms-auth.guard.js +41 -0
- package/lib/auth/wms-auth.types.d.ts +36 -0
- package/lib/auth/wms-auth.types.js +6 -0
- package/lib/auth/wms-operation.decorator.d.ts +23 -0
- package/lib/auth/wms-operation.decorator.js +29 -0
- package/lib/graphql.module.d.ts +8 -1
- package/lib/graphql.module.js +12 -3
- package/lib/mutations/allocate-inventory-order.mutation.js +5 -0
- package/lib/mutations/hold-inventory-order.mutation.js +5 -0
- package/lib/mutations/loader.mutation.js +7 -0
- package/lib/mutations/location.mutation.js +4 -0
- package/lib/mutations/material.mutation.js +4 -0
- package/lib/mutations/merge-batch.mutation.js +3 -0
- package/lib/mutations/quality-inspection-order.mutation.js +3 -0
- package/lib/mutations/receive-inventory-order.mutation.js +5 -0
- package/lib/mutations/reclassify-inventory-order.mutation.js +3 -0
- package/lib/mutations/scrape-order.mutation.js +3 -0
- package/lib/mutations/ship-inventory-order.mutation.js +6 -0
- package/lib/mutations/split-batch.mutation.js +3 -0
- package/lib/mutations/transfer-customer-order.mutation.js +5 -0
- package/lib/mutations/transfer-order.mutation.js +6 -0
- package/lib/mutations/transfer-vendor-order.mutation.js +5 -0
- package/lib/mutations/warehouse-map.mutation.js +3 -0
- package/lib/queries/allocate-inventory-order.query.js +3 -0
- package/lib/queries/batch.query.js +3 -0
- package/lib/queries/hold-inventory-order.query.js +3 -0
- package/lib/queries/loader.query.js +8 -0
- package/lib/queries/location.query.js +4 -0
- package/lib/queries/material.query.js +4 -0
- package/lib/queries/recieve-inventory-order.query.js +4 -0
- package/lib/queries/sale-order.query.js +4 -0
- package/lib/queries/ship-inventory-order.query.js +4 -0
- package/lib/queries/stock.query.js +7 -0
- package/lib/queries/transfer-order.query.js +4 -0
- package/lib/queries/unit.query.js +3 -0
- package/lib/queries/warehouse-map.query.js +3 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -62,6 +62,32 @@ You register a **single** `GraphQLModule.forRootAsync(...)` of your own, mix you
|
|
|
62
62
|
|
|
63
63
|
`WmsModuleGraphqlModule` internally wires `WmsModuleCoreModule.forRootAsync` so you do not need to import the core module separately when using this package.
|
|
64
64
|
|
|
65
|
+
## Authorization
|
|
66
|
+
|
|
67
|
+
Every WMS root `@Query` / `@Mutation` is annotated with `@WmsOperation({ resource, action })` — a **host-neutral** permission requirement enforced by the library's own route-level `WmsAuthGuard`, which delegates the allow/deny decision to an `authChecker` **you** supply. The library never imports your auth system; you map WMS's `resource`/`action` onto whatever you use (Casbin, RBAC, JWT scopes…).
|
|
68
|
+
|
|
69
|
+
> ⚠️ **Using a global guard (e.g. member-base's `CasbinGuard` as `APP_GUARD`)?** A deny-by-default global guard blankets *every* resolver in the schema, including WMS's, and rejects them all because they carry no host-specific metadata — "every WMS operation is forbidden". WMS fixes this by stamping each operation with a host-skip marker so your global guard **defers**, while `WmsAuthGuard` does the real enforcement. **You do not touch your global guard.**
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { WmsModuleGraphqlModule } from '@rytass/wms-module-graphql';
|
|
73
|
+
import { createMemberBaseWmsAuthChecker } from '@rytass/wms-module-graphql/adapters/member-base';
|
|
74
|
+
|
|
75
|
+
WmsModuleGraphqlModule.forRootAsync({
|
|
76
|
+
useFactory: async () => ({}),
|
|
77
|
+
defaultLoaderSerialId: 'DEFAULT',
|
|
78
|
+
defaultLoaderTypeKey: 'DEFAULT',
|
|
79
|
+
defaultLocationKey: 'DEFAULT',
|
|
80
|
+
// optional — omit to stay fully permissive (local dev / demos)
|
|
81
|
+
authChecker: createMemberBaseWmsAuthChecker({
|
|
82
|
+
map: ({ resource, action }) => [[`wms:${resource}`, action]],
|
|
83
|
+
}),
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The member-base adapter reuses everything `CasbinGuard` already put on the request (verified `payload`, `enforcer`, your custom `casbinPermissionChecker`) so WMS enforces with the **exact same** policy engine as your own resolvers — with **zero** member-base import on the WMS side. For custom JWT/RBAC, pass your own `authChecker: (ctx, { resource, action }) => boolean`.
|
|
88
|
+
|
|
89
|
+
➡️ **Full design & recipes: [`docs/authorization.md`](./docs/authorization.md)** (the problem, execution order, member-base / custom-JWT / no-guard recipes, the single coupling point, public API, FAQ).
|
|
90
|
+
|
|
65
91
|
## DataLoader context
|
|
66
92
|
|
|
67
93
|
The module ships a `HelperModule` (auto-registered) that provides a singleton `LoaderService`. To avoid N+1 queries, the WMS resolvers expect a per-request DataLoader trio at `context.loaders`:
|
|
@@ -97,12 +123,14 @@ Full documentation is shipped inside this package under the `docs/` directory (a
|
|
|
97
123
|
| Document | Description |
|
|
98
124
|
|---|---|
|
|
99
125
|
| [`docs/integration-guide.md`](./docs/integration-guide.md) | Step-by-step wiring guide for NestJS + Next.js consumers |
|
|
126
|
+
| [`docs/authorization.md`](./docs/authorization.md) | Complete resolver-authorization design & integration recipes (member-base / custom JWT / no-guard) |
|
|
100
127
|
| [`docs/api-reference.md`](./docs/api-reference.md) | Complete export catalog with signatures and usage examples |
|
|
101
128
|
|
|
102
129
|
GitHub hosted links (always up-to-date):
|
|
103
130
|
|
|
104
131
|
- [Integration Guide — §4 Backend setup](https://github.com/Rytass/WMS-Module/blob/main/docs/integration-guide.md#4-backend-setup-nestjs--graphql--typeorm)
|
|
105
132
|
- [Integration Guide — §13.1 Existing GraphQLModule co-existence](https://github.com/Rytass/WMS-Module/blob/main/docs/integration-guide.md#13-integrating-into-an-existing-nestjs--nextjs-project)
|
|
133
|
+
- [Authorization — design & recipes](https://github.com/Rytass/WMS-Module/blob/main/libs/graphql/docs/authorization.md)
|
|
106
134
|
- [API Reference — @rytass/wms-module-graphql](https://github.com/Rytass/WMS-Module/blob/main/docs/api-reference.md#rytasswms-module-graphql)
|
|
107
135
|
|
|
108
136
|
## License
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { WmsAuthChecker, WmsOperationRequirement } from '../lib/auth';
|
|
2
|
+
/**
|
|
3
|
+
* A Casbin `[subject, action]` tuple, e.g. `['wms:material', 'read']`.
|
|
4
|
+
*/
|
|
5
|
+
export type CasbinAction = [string, string];
|
|
6
|
+
export interface MemberBaseWmsAuthCheckerOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Map a host-neutral WMS requirement onto one or more Casbin
|
|
9
|
+
* `[subject, action]` tuples. This is where your project's permission naming
|
|
10
|
+
* convention lives. Returning several tuples means "allow if *any* passes"
|
|
11
|
+
* (member-base's default checker is OR-based).
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* map: ({ resource, action }) => [[`wms:${resource}`, action]]
|
|
16
|
+
* // material:read -> enforce(memberId, domain, 'wms:material', 'read')
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
readonly map: (requirement: WmsOperationRequirement) => CasbinAction[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Builds a {@link WmsAuthChecker} that authorizes WMS operations against
|
|
23
|
+
* `@rytass/member-base-nestjs-module`'s Casbin setup.
|
|
24
|
+
*
|
|
25
|
+
* It reuses everything the host's global `CasbinGuard` already prepared on the
|
|
26
|
+
* request — the verified `payload`, the `enforcer`, and even the consumer's
|
|
27
|
+
* (possibly customized) `casbinPermissionChecker`. WMS therefore enforces with
|
|
28
|
+
* the *exact same* policy engine and rules as the host's own resolvers, with no
|
|
29
|
+
* member-base import on the WMS side.
|
|
30
|
+
*
|
|
31
|
+
* Prerequisite: register `MemberBaseModule` with its global guard enabled
|
|
32
|
+
* (the default), and expose the request on the GraphQL context as `req`
|
|
33
|
+
* (Apollo's default).
|
|
34
|
+
*/
|
|
35
|
+
export declare const createMemberBaseWmsAuthChecker: (options: MemberBaseWmsAuthCheckerOptions) => WmsAuthChecker;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { GqlExecutionContext } from '@nestjs/graphql';
|
|
2
|
+
|
|
3
|
+
const extractRequest = (context) => {
|
|
4
|
+
const gqlContext = GqlExecutionContext.create(context).getContext();
|
|
5
|
+
return gqlContext?.req;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Builds a {@link WmsAuthChecker} that authorizes WMS operations against
|
|
9
|
+
* `@rytass/member-base-nestjs-module`'s Casbin setup.
|
|
10
|
+
*
|
|
11
|
+
* It reuses everything the host's global `CasbinGuard` already prepared on the
|
|
12
|
+
* request — the verified `payload`, the `enforcer`, and even the consumer's
|
|
13
|
+
* (possibly customized) `casbinPermissionChecker`. WMS therefore enforces with
|
|
14
|
+
* the *exact same* policy engine and rules as the host's own resolvers, with no
|
|
15
|
+
* member-base import on the WMS side.
|
|
16
|
+
*
|
|
17
|
+
* Prerequisite: register `MemberBaseModule` with its global guard enabled
|
|
18
|
+
* (the default), and expose the request on the GraphQL context as `req`
|
|
19
|
+
* (Apollo's default).
|
|
20
|
+
*/
|
|
21
|
+
const createMemberBaseWmsAuthChecker = (options) => {
|
|
22
|
+
return async (context, requirement) => {
|
|
23
|
+
const request = extractRequest(context);
|
|
24
|
+
// No verified member → unauthenticated. CasbinGuard sets `payload` to
|
|
25
|
+
// undefined when the token is missing or invalid.
|
|
26
|
+
if (!request?.payload) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
// The enforcer / checker are injected by CasbinGuard. Their absence means
|
|
30
|
+
// the host did not enable the global guard — fail closed rather than
|
|
31
|
+
// silently allowing everything.
|
|
32
|
+
if (!request.enforcer || !request.casbinPermissionChecker) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
const actions = options.map(requirement);
|
|
36
|
+
if (actions.length === 0) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
return request.casbinPermissionChecker({
|
|
40
|
+
enforcer: request.enforcer,
|
|
41
|
+
payload: request.payload,
|
|
42
|
+
actions,
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export { createMemberBaseWmsAuthChecker };
|
package/docs/README.md
CHANGED
|
@@ -7,6 +7,7 @@ Reference material for consumers of `@rytass/wms-module-{core,graphql,react}`.
|
|
|
7
7
|
| Document | Description |
|
|
8
8
|
|------------------------------------------------|---------------------------------------------------------------------------------------------------|
|
|
9
9
|
| [Integration Guide](./integration-guide.md) | Step-by-step instructions for wiring the three packages into a new consumer project (NestJS + Next.js + PostgreSQL). Includes ready-to-paste `_app.tsx`, `app.module.ts`, `next.config.ts`, the 19 Pages Router shell pattern, AuthGuard stub, seed script template, deployment notes, and known issues. |
|
|
10
|
+
| [Authorization](./authorization.md) | Complete design of the resolver-level authorization protocol (`@WmsOperation` + `WmsAuthGuard` + `authChecker`). Explains the global deny-by-default guard pitfall, execution order, and integration recipes for member-base / Casbin, custom JWT / RBAC, and no-guard setups. Read this if any WMS query/mutation returns *forbidden*. |
|
|
10
11
|
| [API Reference](./api-reference.md) | Complete export catalog for all three packages — every NestJS module, TypeORM entity, GraphQL resolver, DTO, React component, hook, and utility, organized by role with source paths and usage examples. Mirrors the actual `src/index.ts` barrel exports of `@rytass/wms-module-graphql@0.3.0` and `@rytass/wms-module-{core,react}@0.1.6`. |
|
|
11
12
|
|
|
12
13
|
## Quick links
|
|
@@ -23,6 +24,8 @@ Reference material for consumers of `@rytass/wms-module-{core,graphql,react}`.
|
|
|
23
24
|
| Look up a specific export's signature | [API Reference](./api-reference.md) |
|
|
24
25
|
| Understand why the lib needs `transpilePackages` | [Integration Guide section 5.3](./integration-guide.md#53-appsclientnextconfigts) |
|
|
25
26
|
| Replace the `AuthGuard` stub with real auth | [Integration Guide section 8.4](./integration-guide.md#84-real-auth-integration) |
|
|
27
|
+
| Authorize WMS resolvers with member-base / Casbin / RBAC | [Authorization](./authorization.md) |
|
|
28
|
+
| Fix "every WMS query/mutation is forbidden" | [Authorization section 1](./authorization.md#1-the-problem) |
|
|
26
29
|
| Decide between PostgreSQL and SQLite | [Integration Guide section 9](./integration-guide.md#9-database-support-matrix) |
|
|
27
30
|
| Build a custom UI on top of the lib's GraphQL operations| [Integration Guide section 7.3](./integration-guide.md#73-graphql-operations--typed-hooks) |
|
|
28
31
|
|
package/docs/api-reference.md
CHANGED
|
@@ -301,17 +301,23 @@ enum ShipInventoryOrderStatus { PENDING, PARTIAL_SHIPPED, SHIPPED, REMOVED }
|
|
|
301
301
|
**`forRootAsync(options): DynamicModule`** (since 0.3.0)
|
|
302
302
|
```ts
|
|
303
303
|
static forRootAsync(
|
|
304
|
-
options:
|
|
304
|
+
options: WmsModuleGraphqlModuleAsyncOptions,
|
|
305
|
+
// = WmsModuleCoreModuleAsyncOptions & WmsAuthModuleOptions
|
|
306
|
+
//
|
|
305
307
|
// WMSBaseModuleAsyncOptions(Vault/DB 選項)
|
|
306
308
|
// + WmsModuleCoreModuleOptions:
|
|
307
309
|
// defaultLoaderSerialId: string — 庫存作業預設載具序號
|
|
308
310
|
// defaultLoaderTypeKey: string — 預設載具類型 key
|
|
309
311
|
// defaultLocationKey: string — 預設儲存位置 key
|
|
312
|
+
// + WmsAuthModuleOptions:
|
|
313
|
+
// authChecker?: WmsAuthChecker — 選填;resolver 授權委派函式(見 Authorization)
|
|
310
314
|
): DynamicModule
|
|
311
315
|
```
|
|
312
316
|
|
|
313
317
|
> **Breaking from 0.2.x**: 0.2.0 took a second `gqlOptions` argument and started Apollo Server internally; 0.3.0 dropped it. Consumers now own `GraphQLModule.forRootAsync(...)` and the GraphQL context factory. Use `createWmsLoaders(loaderService)` inside `context` to provide the WMS DataLoader trio (see below).
|
|
314
318
|
|
|
319
|
+
> **Authorization (`authChecker`)**: optional. Omit it and the module stays fully permissive. Supply it to authorize every WMS root op against your permission system — see the dedicated [**Authorization**](#authorization) section below and [`docs/authorization.md`](./authorization.md).
|
|
320
|
+
|
|
315
321
|
### `LoaderService` (`@Injectable()`, since 0.3.0 publicly exported)
|
|
316
322
|
- 用途: 提供 DataLoader 工廠方法(`batchEntity()`、`batchEntities()`、`batchEntitiesWithPagination()`)。每次呼叫產生新的 DataLoader instance — 須在 GraphQL context factory **per request** 呼叫,**勿** 在 module scope 重用。
|
|
317
323
|
- 來源: `lib/helpers/loader.service.ts`
|
|
@@ -390,6 +396,59 @@ material(
|
|
|
390
396
|
|
|
391
397
|
---
|
|
392
398
|
|
|
399
|
+
## Authorization
|
|
400
|
+
|
|
401
|
+
Host-neutral, opt-in resolver authorization. Every WMS root `@Query`/`@Mutation` carries `@WmsOperation({ resource, action })`; `WmsAuthGuard` enforces it by delegating to the consumer-supplied `authChecker`. Full design & recipes: [`docs/authorization.md`](./authorization.md).
|
|
402
|
+
|
|
403
|
+
### Root exports (`@rytass/wms-module-graphql`)
|
|
404
|
+
|
|
405
|
+
| Export | Kind | Signature / shape |
|
|
406
|
+
|---|---|---|
|
|
407
|
+
| `WmsOperation` | decorator | `(requirement: WmsOperationRequirement) => MethodDecorator` — already applied across the library; add it to any new root op. |
|
|
408
|
+
| `WmsAuthGuard` | `@Injectable()` guard | route-level `CanActivate`; reads the requirement, calls `authChecker`. Auto-registered by `forRootAsync`. |
|
|
409
|
+
| `WmsAuthChecker` | type | `(context: ExecutionContext, requirement: WmsOperationRequirement) => boolean \| Promise<boolean>` |
|
|
410
|
+
| `WmsOperationRequirement` | type | `{ readonly resource: string; readonly action: string }` |
|
|
411
|
+
| `WmsAuthModuleOptions` | type | `{ readonly authChecker?: WmsAuthChecker }` |
|
|
412
|
+
| `WmsModuleGraphqlModuleAsyncOptions` | type | `WmsModuleCoreModuleAsyncOptions & WmsAuthModuleOptions` (the `forRootAsync` arg) |
|
|
413
|
+
| `WMS_AUTH_OPTIONS` | `symbol` | DI token holding the resolved auth options |
|
|
414
|
+
| `WMS_OPERATION_METADATA` | `string` (`'wms:operation'`) | Reflector key carrying the requirement |
|
|
415
|
+
| `HOST_SKIP_METADATA_KEY` | `string` (`'IS_ROUTE_PUBLIC'`) | host-skip metadata key stamped on each op so a global guard defers (default matches member-base) |
|
|
416
|
+
|
|
417
|
+
### Adapter subpath (`@rytass/wms-module-graphql/adapters/member-base`)
|
|
418
|
+
|
|
419
|
+
| Export | Kind | Signature / shape |
|
|
420
|
+
|---|---|---|
|
|
421
|
+
| `createMemberBaseWmsAuthChecker` | factory | `(options: MemberBaseWmsAuthCheckerOptions) => WmsAuthChecker` |
|
|
422
|
+
| `MemberBaseWmsAuthCheckerOptions` | type | `{ readonly map: (requirement: WmsOperationRequirement) => CasbinAction[] }` |
|
|
423
|
+
| `CasbinAction` | type | `[string, string]` — a `[subject, action]` tuple |
|
|
424
|
+
|
|
425
|
+
```ts
|
|
426
|
+
// member-base / Casbin
|
|
427
|
+
import { createMemberBaseWmsAuthChecker } from '@rytass/wms-module-graphql/adapters/member-base';
|
|
428
|
+
|
|
429
|
+
WmsModuleGraphqlModule.forRootAsync({
|
|
430
|
+
useFactory: async () => ({}),
|
|
431
|
+
defaultLoaderSerialId: 'DEFAULT',
|
|
432
|
+
defaultLoaderTypeKey: 'DEFAULT',
|
|
433
|
+
defaultLocationKey: 'DEFAULT',
|
|
434
|
+
authChecker: createMemberBaseWmsAuthChecker({
|
|
435
|
+
map: ({ resource, action }) => [[`wms:${resource}`, action]],
|
|
436
|
+
}),
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
// custom JWT / RBAC
|
|
440
|
+
import type { WmsAuthChecker } from '@rytass/wms-module-graphql';
|
|
441
|
+
|
|
442
|
+
const authChecker: WmsAuthChecker = (ctx, { resource, action }) => {
|
|
443
|
+
// read your session off ctx and decide
|
|
444
|
+
return true;
|
|
445
|
+
};
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
The adapter imports nothing from member-base — it reads the `payload` / `enforcer` / `casbinPermissionChecker` that `CasbinGuard` attaches to the request at runtime. member-base is therefore **not** a dependency or peer dependency of this package.
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
393
452
|
## Resolvers(`@Resolver()` decorator class)
|
|
394
453
|
|
|
395
454
|
以下 Resolver 類別處理 GraphQL `@ResolveField`,解析關聯欄位(防止 N+1):
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
# Authorization
|
|
2
|
+
|
|
3
|
+
How `@rytass/wms-module-graphql` lets **you** authorize its resolvers with **your**
|
|
4
|
+
permission system — without the library knowing anything about it, and without
|
|
5
|
+
your global auth guard rejecting every WMS operation.
|
|
6
|
+
|
|
7
|
+
- [1. The problem](#1-the-problem)
|
|
8
|
+
- [2. The design](#2-the-design)
|
|
9
|
+
- [3. Execution order — why it composes](#3-execution-order--why-it-composes)
|
|
10
|
+
- [4. Recipe A — member-base / Casbin](#4-recipe-a--member-base--casbin)
|
|
11
|
+
- [5. Recipe B — your own JWT / RBAC](#5-recipe-b--your-own-jwt--rbac)
|
|
12
|
+
- [6. Recipe C — no global guard / fully open](#6-recipe-c--no-global-guard--fully-open)
|
|
13
|
+
- [7. The single coupling point](#7-the-single-coupling-point)
|
|
14
|
+
- [8. Public API](#8-public-api)
|
|
15
|
+
- [9. FAQ & gotchas](#9-faq--gotchas)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 1. The problem
|
|
20
|
+
|
|
21
|
+
The library exposes ~66 root `@Query` / `@Mutation` operations. Like any
|
|
22
|
+
publishable NestJS GraphQL module, it cannot know how *your* application
|
|
23
|
+
authorizes requests — RBAC? ABAC? Casbin? JWT scopes? a custom session table?
|
|
24
|
+
So historically its resolvers were **bare** (`@Query()` with no guard metadata).
|
|
25
|
+
|
|
26
|
+
That collides badly with a **globally registered, deny-by-default guard**. The
|
|
27
|
+
canonical example is the `CasbinGuard` that `@rytass/member-base-nestjs-module`
|
|
28
|
+
registers as `APP_GUARD`:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
Host App
|
|
32
|
+
APP_GUARD = CasbinGuard <- one net over EVERY resolver in the schema
|
|
33
|
+
|
|
|
34
|
+
v
|
|
35
|
+
+------------------------------+
|
|
36
|
+
| compiled GraphQL schema |
|
|
37
|
+
| - your resolvers | carry @AllowActions -> authorized OK
|
|
38
|
+
| - WMS resolvers (bare) | no metadata -> guard returns false -> ALL denied
|
|
39
|
+
+------------------------------+
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`CasbinGuard` is white-list: a handler with no `@AllowActions` / `@Authenticated`
|
|
43
|
+
/ `@IsPublic` metadata is **rejected**. WMS resolvers had none, so every WMS
|
|
44
|
+
query and mutation came back *forbidden*. You could not fix it from the consumer
|
|
45
|
+
side either — you do not own the WMS resolver classes, so you cannot decorate
|
|
46
|
+
them.
|
|
47
|
+
|
|
48
|
+
**Conclusion:** the library itself must participate in authorization, while
|
|
49
|
+
staying ignorant of which auth system you use.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 2. The design
|
|
54
|
+
|
|
55
|
+
A host-neutral, opt-in authorization protocol with three moving parts, all
|
|
56
|
+
shipped by the library:
|
|
57
|
+
|
|
58
|
+
| Part | What it is | Who supplies it |
|
|
59
|
+
|------|------------|-----------------|
|
|
60
|
+
| `@WmsOperation({ resource, action })` | Method decorator on every WMS root op. Declares a **neutral** permission requirement (just two strings) and wires the guard. | Library (already applied) |
|
|
61
|
+
| `WmsAuthGuard` | Route-level guard (NOT global) that reads the requirement and delegates the decision. | Library |
|
|
62
|
+
| `authChecker` | Your function `(ctx, { resource, action }) => boolean`. Maps WMS's neutral requirement onto your real permission system. | **You**, via `forRootAsync` |
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
WMS resolver
|
|
66
|
+
|
|
|
67
|
+
+- @WmsOperation({ resource: 'material', action: 'read' })
|
|
68
|
+
| |
|
|
69
|
+
| +- stamps neutral metadata ............ read by WmsAuthGuard
|
|
70
|
+
| +- stamps a host-skip marker ........... tells your global guard to defer
|
|
71
|
+
| +- @UseGuards(WmsAuthGuard) ............ the guard that actually enforces
|
|
72
|
+
|
|
|
73
|
+
v
|
|
74
|
+
WmsAuthGuard.canActivate(ctx)
|
|
75
|
+
|
|
|
76
|
+
+- reads { resource, action } from the handler
|
|
77
|
+
+- calls YOUR authChecker(ctx, { resource, action })
|
|
78
|
+
|
|
|
79
|
+
v
|
|
80
|
+
true -> resolve / false -> ForbiddenError
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Key properties:
|
|
84
|
+
|
|
85
|
+
- **The library never imports your auth system.** It only knows two strings.
|
|
86
|
+
- **`authChecker` is optional.** Omit it and the module stays fully permissive
|
|
87
|
+
(handy for local dev / demos) — identical to the pre-auth releases.
|
|
88
|
+
- **`resource` / `action` are internal metadata**, not part of the GraphQL
|
|
89
|
+
schema. Refining them later is *not* a breaking change for GraphQL consumers.
|
|
90
|
+
- The host-skip marker is what lets WMS coexist with your global guard — see §3.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 3. Execution order — why it composes
|
|
95
|
+
|
|
96
|
+
NestJS runs guards in the order **global -> controller -> route**. So a global
|
|
97
|
+
`APP_GUARD` (your `CasbinGuard`) runs *before* WMS's route-level `WmsAuthGuard`.
|
|
98
|
+
That is exactly why `@WmsOperation` stamps a host-skip marker: it makes the
|
|
99
|
+
global guard *defer* on WMS operations so it never blanket-rejects them.
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
request
|
|
103
|
+
|
|
|
104
|
+
v [1] APP_GUARD = CasbinGuard (global, runs first)
|
|
105
|
+
| - verifies token, attaches payload/enforcer to the request
|
|
106
|
+
| - sees host-skip marker (IS_ROUTE_PUBLIC) -> returns true (defers)
|
|
107
|
+
|
|
|
108
|
+
v [2] @UseGuards(WmsAuthGuard) (route-level, runs next)
|
|
109
|
+
| - reads { resource, action }
|
|
110
|
+
| - calls your authChecker -> real allow/deny decision
|
|
111
|
+
|
|
|
112
|
+
v resolver executes
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
With member-base specifically there is a bonus: before `CasbinGuard` defers on
|
|
116
|
+
the skip marker, it has *already* attached the verified `payload`, the Casbin
|
|
117
|
+
`enforcer`, and your `casbinPermissionChecker` to the request. The member-base
|
|
118
|
+
adapter (§4) reuses those — so WMS enforces with the **exact same** policy
|
|
119
|
+
engine and rules as your own resolvers.
|
|
120
|
+
|
|
121
|
+
> Your global guard configuration does **not** change. You do not whitelist WMS
|
|
122
|
+
> routes, you do not enumerate operations, you do not weaken your default-deny
|
|
123
|
+
> posture. WMS makes itself transparent to the global guard and self-enforces.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 4. Recipe A — member-base / Casbin
|
|
128
|
+
|
|
129
|
+
The common case: you use `@rytass/member-base-nestjs-module` with its global
|
|
130
|
+
`CasbinGuard`. Use the ready-made adapter from the dedicated subpath.
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
// app.module.ts
|
|
134
|
+
import { WmsModuleGraphqlModule } from '@rytass/wms-module-graphql';
|
|
135
|
+
import { createMemberBaseWmsAuthChecker } from '@rytass/wms-module-graphql/adapters/member-base';
|
|
136
|
+
|
|
137
|
+
@Module({
|
|
138
|
+
imports: [
|
|
139
|
+
// ...MemberBaseModule.forRoot({ ... }) with its global guard enabled (default)...
|
|
140
|
+
WmsModuleGraphqlModule.forRootAsync({
|
|
141
|
+
// existing core options:
|
|
142
|
+
useFactory: async () => ({}),
|
|
143
|
+
defaultLoaderSerialId: 'DEFAULT',
|
|
144
|
+
defaultLoaderTypeKey: 'DEFAULT',
|
|
145
|
+
defaultLocationKey: 'DEFAULT',
|
|
146
|
+
|
|
147
|
+
// authorization:
|
|
148
|
+
authChecker: createMemberBaseWmsAuthChecker({
|
|
149
|
+
// Map WMS resource/action onto YOUR Casbin [subject, action] tuples.
|
|
150
|
+
// This is the one place your permission naming convention lives.
|
|
151
|
+
// Return several tuples to mean "allow if ANY passes" (OR semantics).
|
|
152
|
+
map: ({ resource, action }) => [[`wms:${resource}`, action]],
|
|
153
|
+
// material + read -> enforce(memberId, domain, 'wms:material', 'read')
|
|
154
|
+
}),
|
|
155
|
+
}),
|
|
156
|
+
// ...your own GraphQLModule.forRootAsync (must expose `req` on context)...
|
|
157
|
+
],
|
|
158
|
+
})
|
|
159
|
+
export class AppModule {}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
What the adapter does, step by step:
|
|
163
|
+
|
|
164
|
+
1. Pulls the request off the GraphQL `ExecutionContext`
|
|
165
|
+
(`GqlExecutionContext.create(ctx).getContext().req` — member-base's own
|
|
166
|
+
convention).
|
|
167
|
+
2. Reads `request.payload` (the JWT member, attached by `CasbinGuard`). Missing
|
|
168
|
+
payload -> **deny** (unauthenticated).
|
|
169
|
+
3. Reads `request.enforcer` and `request.casbinPermissionChecker` (also attached
|
|
170
|
+
by `CasbinGuard`). Missing -> **deny** (fail closed: the global guard was not
|
|
171
|
+
actually enabled).
|
|
172
|
+
4. Runs `your map({ resource, action })` to get the Casbin tuples.
|
|
173
|
+
5. Calls the **consumer's own** `casbinPermissionChecker({ enforcer, payload,
|
|
174
|
+
actions })` and returns its verdict.
|
|
175
|
+
|
|
176
|
+
The adapter imports **nothing** from member-base — it relies only on the runtime
|
|
177
|
+
contract `CasbinGuard` already established on the request. So member-base is not
|
|
178
|
+
even a dependency of `@rytass/wms-module-graphql`.
|
|
179
|
+
|
|
180
|
+
**Prerequisites**
|
|
181
|
+
|
|
182
|
+
- Keep member-base's global guard enabled (`enableGlobalGuard: true`, the
|
|
183
|
+
default).
|
|
184
|
+
- Your `GraphQLModule` context must expose the request as `req` (Apollo's
|
|
185
|
+
default): `context: ({ req }) => ({ ...createWmsLoaders(loaderService), req })`.
|
|
186
|
+
|
|
187
|
+
**Permission grants.** Make sure your Casbin policy grants the mapped subjects.
|
|
188
|
+
With the example `map` above, a warehouse operator role might be granted
|
|
189
|
+
`wms:material:read`, `wms:receive-inventory-order:create`, etc. The full list of
|
|
190
|
+
`resource` values mirrors the WMS domains (`material`, `loader`, `stock`,
|
|
191
|
+
`location`, `unit`, `batch`, `warehouse-map`, `transfer-order`,
|
|
192
|
+
`receive-inventory-order`, `ship-inventory-order`, `allocate-inventory-order`,
|
|
193
|
+
`hold-inventory-order`, `reclassify-inventory-order`, `transfer-customer-order`,
|
|
194
|
+
`transfer-vendor-order`, `sale-order`, `scrape-order`,
|
|
195
|
+
`quality-inspection-order`). `action` is `read` for queries, and the leading
|
|
196
|
+
verb (`create` / `update` / `delete` / `ship` / `hold` / `allocate` / `split` /
|
|
197
|
+
`merge` / `transfer` / ...) for mutations.
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## 5. Recipe B — your own JWT / RBAC
|
|
202
|
+
|
|
203
|
+
`authChecker` is a plain function — you do not need Casbin or member-base. Read
|
|
204
|
+
whatever you need off the `ExecutionContext` and decide.
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
import { GqlExecutionContext } from '@nestjs/graphql';
|
|
208
|
+
import type { WmsAuthChecker } from '@rytass/wms-module-graphql';
|
|
209
|
+
|
|
210
|
+
const myWmsAuthChecker: WmsAuthChecker = (ctx, { resource, action }) => {
|
|
211
|
+
const { req } = GqlExecutionContext.create(ctx).getContext<{ req: { user?: { scopes: string[] } } }>();
|
|
212
|
+
const user = req.user;
|
|
213
|
+
|
|
214
|
+
if (!user) return false; // unauthenticated -> deny
|
|
215
|
+
|
|
216
|
+
// your own convention; e.g. scopes like "wms:material:read" or "wms:*"
|
|
217
|
+
return user.scopes.includes('wms:*')
|
|
218
|
+
|| user.scopes.includes(`wms:${resource}:${action}`)
|
|
219
|
+
|| user.scopes.includes(`wms:${resource}:*`);
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
WmsModuleGraphqlModule.forRootAsync({
|
|
223
|
+
useFactory: async () => ({}),
|
|
224
|
+
defaultLoaderSerialId: 'DEFAULT',
|
|
225
|
+
defaultLoaderTypeKey: 'DEFAULT',
|
|
226
|
+
defaultLocationKey: 'DEFAULT',
|
|
227
|
+
authChecker: myWmsAuthChecker,
|
|
228
|
+
});
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
`authChecker` may be sync or async (return `Promise<boolean>`). Throwing also
|
|
232
|
+
works — the exception propagates exactly like any guard rejection (use a
|
|
233
|
+
`ForbiddenException` if you want a clean 403-style GraphQL error).
|
|
234
|
+
|
|
235
|
+
If you *also* run a global deny-by-default guard that is **not** member-base, you
|
|
236
|
+
must make it skip WMS routes too — see §7 to change the host-skip key.
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## 6. Recipe C — no global guard / fully open
|
|
241
|
+
|
|
242
|
+
- **No `authChecker`.** The module is permissive: `WmsAuthGuard` lets every
|
|
243
|
+
operation through. The host-skip marker is inert. Good for local dev, internal
|
|
244
|
+
tooling, or demos.
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
WmsModuleGraphqlModule.forRootAsync({
|
|
248
|
+
useFactory: async () => ({}),
|
|
249
|
+
defaultLoaderSerialId: 'DEFAULT',
|
|
250
|
+
defaultLoaderTypeKey: 'DEFAULT',
|
|
251
|
+
defaultLocationKey: 'DEFAULT',
|
|
252
|
+
// no authChecker -> open
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
- **You guard your own resolvers explicitly** (`@UseGuards(...)` per resolver
|
|
257
|
+
instead of a global `APP_GUARD`). Then there is no net over WMS at all; the
|
|
258
|
+
skip marker does nothing and WMS still self-enforces through your
|
|
259
|
+
`authChecker` if you supply one.
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## 7. The single coupling point
|
|
264
|
+
|
|
265
|
+
`@WmsOperation` stamps a host-skip metadata key so your global guard defers. A
|
|
266
|
+
global guard reads its skip key *statically* (at decoration time), so the key
|
|
267
|
+
must be a fixed value baked into the decorator. The default matches
|
|
268
|
+
member-base's:
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
// libs/graphql/src/lib/auth/host-skip.constant.ts
|
|
272
|
+
export const HOST_SKIP_METADATA_KEY = 'IS_ROUTE_PUBLIC';
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
This is the **only** place WMS assumes anything about a specific host guard. If
|
|
276
|
+
your global guard uses a different "public / skip" metadata key, change this one
|
|
277
|
+
constant (when building from source) — nothing else moves. If you do not run a
|
|
278
|
+
global guard, it is irrelevant.
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## 8. Public API
|
|
283
|
+
|
|
284
|
+
Exported from the package root (`@rytass/wms-module-graphql`):
|
|
285
|
+
|
|
286
|
+
| Export | Kind | Purpose |
|
|
287
|
+
|--------|------|---------|
|
|
288
|
+
| `WmsOperation(requirement)` | decorator | Annotate a WMS root op (already applied across the library). |
|
|
289
|
+
| `WmsAuthGuard` | class | Route-level guard that delegates to `authChecker`. |
|
|
290
|
+
| `WmsAuthChecker` | type | `(ctx: ExecutionContext, req: WmsOperationRequirement) => boolean \| Promise<boolean>` |
|
|
291
|
+
| `WmsOperationRequirement` | type | `{ readonly resource: string; readonly action: string }` |
|
|
292
|
+
| `WmsAuthModuleOptions` | type | `{ readonly authChecker?: WmsAuthChecker }` |
|
|
293
|
+
| `WmsModuleGraphqlModuleAsyncOptions` | type | Core async options **+** `WmsAuthModuleOptions`. |
|
|
294
|
+
| `WMS_AUTH_OPTIONS` | symbol | DI token holding the resolved auth options. |
|
|
295
|
+
| `WMS_OPERATION_METADATA` | string | Reflector key carrying the requirement. |
|
|
296
|
+
| `HOST_SKIP_METADATA_KEY` | string | The host-skip metadata key (default `'IS_ROUTE_PUBLIC'`). |
|
|
297
|
+
|
|
298
|
+
Exported from the adapter subpath (`@rytass/wms-module-graphql/adapters/member-base`):
|
|
299
|
+
|
|
300
|
+
| Export | Kind | Purpose |
|
|
301
|
+
|--------|------|---------|
|
|
302
|
+
| `createMemberBaseWmsAuthChecker(options)` | factory | Builds a `WmsAuthChecker` for member-base / Casbin. |
|
|
303
|
+
| `MemberBaseWmsAuthCheckerOptions` | type | `{ readonly map: (req: WmsOperationRequirement) => CasbinAction[] }` |
|
|
304
|
+
| `CasbinAction` | type | `[string, string]` — a `[subject, action]` tuple. |
|
|
305
|
+
|
|
306
|
+
`forRootAsync` accepts the optional `authChecker` on its single options object:
|
|
307
|
+
|
|
308
|
+
```ts
|
|
309
|
+
static forRootAsync(options: WmsModuleGraphqlModuleAsyncOptions): DynamicModule;
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## 9. FAQ & gotchas
|
|
315
|
+
|
|
316
|
+
**"Every WMS query/mutation is forbidden after I added member-base."**
|
|
317
|
+
Your global `CasbinGuard` is rejecting bare resolvers. Upgrade to a WMS version
|
|
318
|
+
that ships `@WmsOperation` (this protocol) and wire an `authChecker` per §4. You
|
|
319
|
+
do not need to touch the global guard.
|
|
320
|
+
|
|
321
|
+
**Does the GraphQL context need anything special?**
|
|
322
|
+
Yes — expose the request as `req` on the context (Apollo's default). Both the
|
|
323
|
+
member-base adapter and most custom checkers read `GqlExecutionContext.create(ctx).getContext().req`.
|
|
324
|
+
|
|
325
|
+
**Is member-base a dependency now?**
|
|
326
|
+
No. The adapter reads runtime fields the host's guard attaches to the request;
|
|
327
|
+
it imports nothing from member-base. member-base is not in this package's
|
|
328
|
+
`dependencies` or `peerDependencies`.
|
|
329
|
+
|
|
330
|
+
**What about `@ResolveField` resolvers?**
|
|
331
|
+
They are intentionally *not* annotated. A field resolver is only ever reached
|
|
332
|
+
through a root `@Query`/`@Mutation` that already passed `WmsAuthGuard`, so
|
|
333
|
+
authorizing the root op authorizes the whole selection. `WmsAuthGuard` returns
|
|
334
|
+
`true` for any handler without a requirement.
|
|
335
|
+
|
|
336
|
+
**Fail-open or fail-closed?**
|
|
337
|
+
The library is permissive only when *you* opt out (no `authChecker`). When an
|
|
338
|
+
`authChecker` is configured, missing identity / missing enforcer -> **deny**. The
|
|
339
|
+
member-base adapter fails closed.
|
|
340
|
+
|
|
341
|
+
**Can I change `resource`/`action` strings?**
|
|
342
|
+
Yes, freely — they are internal metadata, never exposed in the GraphQL schema.
|
|
343
|
+
Changing them only affects your `authChecker` / `map`, never your GraphQL
|
|
344
|
+
clients.
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
See also: [Integration Guide §8.5](./integration-guide.md#85-backend-authorization-resolver-level)
|
|
349
|
+
· [API Reference](./api-reference.md#rytasswms-module-graphql)
|
|
@@ -741,7 +741,7 @@ The full list of generated `Document` constants and their types is in
|
|
|
741
741
|
|
|
742
742
|
## 8. Authentication / authorization
|
|
743
743
|
|
|
744
|
-
The lib
|
|
744
|
+
The lib ships **no frontend auth** — the page components assume they render in an authenticated context and call queries on mount without checking session state. On the **backend**, `@rytass/wms-module-graphql` ships an *opt-in, host-neutral authorization protocol* you wire to your own permission system (member-base / Casbin / JWT / anything). See [§8.5 Backend authorization](#85-backend-authorization-resolver-level).
|
|
745
745
|
|
|
746
746
|
### 8.1 Stub for demos
|
|
747
747
|
|
|
@@ -829,7 +829,43 @@ Replace the `AuthGuard` stub with your own logic that:
|
|
|
829
829
|
2. Provides member identity to GraphQL via the Apollo client's `context` link.
|
|
830
830
|
3. Handles 401 responses (logout / redirect).
|
|
831
831
|
|
|
832
|
-
|
|
832
|
+
### 8.5 Backend authorization (resolver-level)
|
|
833
|
+
|
|
834
|
+
> 📖 This is the condensed version. The full design — execution order, member-base / custom-JWT / no-guard recipes, the single coupling point, the exported API, and a troubleshooting FAQ — lives in [**`docs/authorization.md`**](./authorization.md).
|
|
835
|
+
|
|
836
|
+
Every WMS root `@Query` / `@Mutation` is annotated with `@WmsOperation({ resource, action })` — a host-neutral permission requirement. The library enforces it through its own route-level `WmsAuthGuard`, which delegates the actual allow/deny decision to an `authChecker` **you** supply at module registration. The library never imports your auth system; you map WMS's `resource`/`action` onto whatever your system understands.
|
|
837
|
+
|
|
838
|
+
> ⚠️ **The global-guard pitfall.** A deny-by-default global guard — e.g. the `CasbinGuard` that `@rytass/member-base-nestjs-module` registers as `APP_GUARD` — blankets *every* resolver in the compiled schema, including WMS's. Because WMS resolvers carry no host-specific permission metadata, such a guard rejects them all ("every WMS query/mutation is forbidden"). WMS solves this by stamping each operation with a host-skip marker (default: member-base's `IS_ROUTE_PUBLIC`) so the global guard defers, while `WmsAuthGuard` does the real enforcement. **You do not need to touch your global guard.**
|
|
839
|
+
|
|
840
|
+
#### Wire it up (member-base / Casbin)
|
|
841
|
+
|
|
842
|
+
```ts
|
|
843
|
+
// app.module.ts
|
|
844
|
+
import { WmsModuleGraphqlModule } from '@rytass/wms-module-graphql';
|
|
845
|
+
import { createMemberBaseWmsAuthChecker } from '@rytass/wms-module-graphql/adapters/member-base';
|
|
846
|
+
|
|
847
|
+
WmsModuleGraphqlModule.forRootAsync({
|
|
848
|
+
// ...existing core options (defaultLoaderSerialId, useFactory, etc.)...
|
|
849
|
+
authChecker: createMemberBaseWmsAuthChecker({
|
|
850
|
+
// Map WMS resource/action onto your Casbin [subject, action] tuples.
|
|
851
|
+
// Return multiple tuples to mean "allow if ANY passes" (OR semantics).
|
|
852
|
+
map: ({ resource, action }) => [[`wms:${resource}`, action]],
|
|
853
|
+
// e.g. material:read -> enforce(memberId, domain, 'wms:material', 'read')
|
|
854
|
+
}),
|
|
855
|
+
});
|
|
856
|
+
```
|
|
857
|
+
|
|
858
|
+
The member-base adapter reuses everything `CasbinGuard` already prepared on the request (the verified `payload`, the `enforcer`, and your possibly-customized `casbinPermissionChecker`), so WMS enforces with the **exact same** policy engine as your own resolvers — with zero member-base import on the WMS side. Requirement: keep member-base's global guard enabled (default) and expose the request on the GraphQL context as `req` (Apollo's default).
|
|
859
|
+
|
|
860
|
+
#### Other auth systems
|
|
861
|
+
|
|
862
|
+
`authChecker` is a plain function — `(execContext, { resource, action }) => boolean | Promise<boolean>`. For JWT/RBAC without Casbin, read your session off the `ExecutionContext` and decide there. If your global guard uses a different "public/skip" metadata key than member-base's `IS_ROUTE_PUBLIC`, change the single `HOST_SKIP_METADATA_KEY` constant in `libs/graphql/src/lib/auth/host-skip.constant.ts`.
|
|
863
|
+
|
|
864
|
+
#### No global guard at all
|
|
865
|
+
|
|
866
|
+
If you omit `authChecker`, the module stays fully permissive (handy for local dev). If you protect your own resolvers with explicit `@UseGuards(...)` instead of a global `APP_GUARD`, the host-skip marker is simply inert and WMS still self-enforces through `authChecker`.
|
|
867
|
+
|
|
868
|
+
The WMS `resource`/`action` strings are **internal metadata only** — they are not part of the GraphQL schema, so refining them later is not a breaking change for GraphQL consumers (it only affects your `map`).
|
|
833
869
|
|
|
834
870
|
---
|
|
835
871
|
|