@nxgt/shared-graphql 2.1.0 → 3.0.1

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.
Files changed (68) hide show
  1. package/README.md +112 -52
  2. package/dist/directives/authenticated.d.ts +42 -0
  3. package/dist/directives/authenticated.d.ts.map +1 -0
  4. package/dist/directives/federation.d.ts +6 -0
  5. package/dist/directives/federation.d.ts.map +1 -1
  6. package/dist/directives/index.d.ts +1 -2
  7. package/dist/directives/index.d.ts.map +1 -1
  8. package/dist/directives/permission.d.ts +20 -6
  9. package/dist/directives/permission.d.ts.map +1 -1
  10. package/dist/directives/removed-check.d.ts +15 -0
  11. package/dist/directives/removed-check.d.ts.map +1 -0
  12. package/dist/directives/scope.d.ts +3 -3
  13. package/dist/directives/scope.d.ts.map +1 -1
  14. package/dist/directives/sdl.d.ts +14 -4
  15. package/dist/directives/sdl.d.ts.map +1 -1
  16. package/dist/directives/validate.d.ts +8 -14
  17. package/dist/directives/validate.d.ts.map +1 -1
  18. package/dist/index.js +1037 -926
  19. package/dist/index.js.map +35 -31
  20. package/dist/integrations/hono.d.ts +2 -2
  21. package/dist/integrations/hono.d.ts.map +1 -1
  22. package/dist/plugins/apply-authenticated.d.ts +21 -0
  23. package/dist/plugins/apply-authenticated.d.ts.map +1 -0
  24. package/dist/plugins/apply-keto-checks.d.ts +10 -7
  25. package/dist/plugins/apply-keto-checks.d.ts.map +1 -1
  26. package/dist/plugins/auth.d.ts +17 -1
  27. package/dist/plugins/auth.d.ts.map +1 -1
  28. package/dist/plugins/authenticated.d.ts +14 -0
  29. package/dist/plugins/authenticated.d.ts.map +1 -0
  30. package/dist/plugins/extract-jwt.d.ts +19 -6
  31. package/dist/plugins/extract-jwt.d.ts.map +1 -1
  32. package/dist/plugins/field-guard.d.ts +30 -0
  33. package/dist/plugins/field-guard.d.ts.map +1 -0
  34. package/dist/plugins/gateway-trust.d.ts +15 -0
  35. package/dist/plugins/gateway-trust.d.ts.map +1 -0
  36. package/dist/plugins/index.d.ts +3 -0
  37. package/dist/plugins/index.d.ts.map +1 -1
  38. package/dist/plugins/keto-checks.d.ts +3 -2
  39. package/dist/plugins/keto-checks.d.ts.map +1 -1
  40. package/dist/plugins/keto-helpers.d.ts +1 -1
  41. package/dist/plugins/keto-helpers.d.ts.map +1 -1
  42. package/dist/plugins/ory-auth.d.ts +3 -3
  43. package/dist/shared.d.ts +1 -1
  44. package/dist/shared.d.ts.map +1 -1
  45. package/dist/utils/errors/denial.d.ts +20 -0
  46. package/dist/utils/errors/denial.d.ts.map +1 -0
  47. package/dist/utils/errors/format-error.d.ts +2 -1
  48. package/dist/utils/errors/format-error.d.ts.map +1 -1
  49. package/dist/utils/errors/index.d.ts +1 -0
  50. package/dist/utils/errors/index.d.ts.map +1 -1
  51. package/dist/utils/errors/mask-error.d.ts +2 -1
  52. package/dist/utils/errors/mask-error.d.ts.map +1 -1
  53. package/dist/utils/ws-context.d.ts +1 -1
  54. package/docs/README.md +8 -6
  55. package/docs/guide/authentication.md +187 -0
  56. package/docs/guide/errors.md +37 -5
  57. package/docs/guide/migrating-to-3.md +276 -0
  58. package/docs/guide/permissions.md +39 -50
  59. package/docs/roadmap.md +30 -29
  60. package/docs/troubleshooting.md +151 -48
  61. package/package.json +7 -11
  62. package/dist/directives/check.d.ts +0 -32
  63. package/dist/directives/check.d.ts.map +0 -1
  64. package/dist/directives/deprecation.d.ts +0 -7
  65. package/dist/directives/deprecation.d.ts.map +0 -1
  66. package/dist/directives/requirements.d.ts +0 -14
  67. package/dist/directives/requirements.d.ts.map +0 -1
  68. package/graphql/directives/check.graphqls +0 -90
package/README.md CHANGED
@@ -4,6 +4,10 @@ The GraphQL layer: Yoga + Hono wiring, the federation subgraph builder, shared
4
4
  scalars and directives, dataloaders, subscriptions over Redis, upload handling,
5
5
  and the SDL every service merges into its own schema.
6
6
 
7
+ **Upgrading from 2.x?** Read [Migrating to 3.0](./docs/guide/migrating-to-3.md)
8
+ — the caller is no longer read from a request body a client writes, `@check`
9
+ is gone, and denials are `GraphQLError`s.
10
+
7
11
  ## Install
8
12
 
9
13
  ```bash
@@ -14,8 +18,8 @@ Public on npmjs; no token needed to install. Peers:
14
18
 
15
19
  | Peer | Range | Why |
16
20
  | --- | --- | --- |
17
- | `graphql` | `^16.4.2 \|\| ^17.0.0` | one copy for your schema and this package's transforms; the suite runs on both majors |
18
- | `@nxgt/ory-sdk` | `>=0.1.0` | `useOryAuth`, `useKetoChecks`, `can` |
21
+ | `graphql` | `^16.9.0 \|\| ^17.0.0` | one copy for your schema and this package's transforms; the suite runs on both majors |
22
+ | `@nxgt/ory-sdk` | `>=0.1.0 <1` | `useOryAuth`, `useKetoChecks`, `can`; a 1.x is admitted once it is tested |
19
23
  | `stx-sdk` | `>=1.1.0` | `./security`'s policy types |
20
24
  | `typescript` | `^6.0.3` | pinned across every `@nxgt/*` package — the set is unsatisfiable if one of them widens it |
21
25
 
@@ -57,23 +61,72 @@ schema: ['./src/**/*.graphqls', SHARED_SCHEMA_PATH],
57
61
  A relative path into this package's `src/` will not work from an install — it
58
62
  is not published, and it was not there in the first place.
59
63
 
60
- `SHARED_TYPE_DEFS` is a string of the shared directives (`@authenticated`,
61
- `@policy`, `@shareable`, `@link`) plus empty root types. `@permission` and
62
- `@check` are not in it: they live in `graphql/directives/`, so a subgraph that
63
- builds through `buildSubgraphSchema` and never loads `SHARED_TYPE_DEFS` still
64
- sees them through `SHARED_SCHEMA_PATH`. For a schema assembled in code, the
65
- same declarations ship as strings — `PERMISSION_DIRECTIVE_SDL`,
66
- `CHECK_DIRECTIVE_SDL`, or both as `KETO_DIRECTIVES_SDL` — held equal to the
67
- files by a spec.
64
+ `SHARED_TYPE_DEFS` is a string of the shared directives
65
+ (`@authenticated(type:)`, `@policy`, `@shareable`, `@link`) plus empty root
66
+ types. `@permission` is not in it: it lives in `graphql/directives/`, so a
67
+ subgraph that builds through `buildSubgraphSchema` and never loads
68
+ `SHARED_TYPE_DEFS` still sees it through `SHARED_SCHEMA_PATH`. For a schema
69
+ assembled in code, the declarations ship as strings —
70
+ `PERMISSION_DIRECTIVE_SDL` (also exported as `KETO_DIRECTIVES_SDL`), held
71
+ equal to its file by a spec, and `AUTHENTICATED_DIRECTIVE_SDL`, which has no
72
+ file: it lives in `SHARED_TYPE_DEFS` and in this string only.
68
73
 
69
74
  ```ts
70
- import { KETO_DIRECTIVES_SDL } from '@nxgt/shared-graphql';
75
+ import { AUTHENTICATED_DIRECTIVE_SDL, PERMISSION_DIRECTIVE_SDL } from '@nxgt/shared-graphql';
71
76
 
72
- createSchema({ typeDefs: [KETO_DIRECTIVES_SDL, typeDefs], resolvers });
77
+ createSchema({ typeDefs: [AUTHENTICATED_DIRECTIVE_SDL, PERMISSION_DIRECTIVE_SDL, typeDefs], resolvers });
73
78
  ```
74
79
 
75
80
  `buildSubgraphSchema` wraps Apollo's builder and prunes unused types.
76
81
 
82
+ ## Who is calling
83
+
84
+ The caller comes from a source the server verified — never from the request
85
+ body alone.
86
+
87
+ ```ts
88
+ // An API that authenticates its own callers: Kratos session, Hydra token.
89
+ createYoga({ plugins: [useOryAuth(ory), useAuthenticated()] });
90
+
91
+ // A subgraph behind a gateway that resolved the caller and put it in `extensions`.
92
+ const trustedGateway = gatewaySecret({ secret: process.env.GATEWAY_SECRET! });
93
+ createYoga({ plugins: [useAuth({ trustedGateway }), useAuthenticated()] });
94
+ new ApolloServer({ plugins: [extractJwtPlugin({ trustedGateway })] });
95
+ ```
96
+
97
+ - `useOryAuth(ory)` resolves the caller through Kratos or Hydra, and sets
98
+ `user`, `claims`, `token` and `ory`. An Ory outage is a 503, never an
99
+ anonymous caller.
100
+ - `useAuth()` and `extractJwtPlugin()` read the caller a gateway put in the
101
+ GraphQL request's `extensions` — **only** for a request `trustedGateway`
102
+ vouches for. `gatewaySecret({ secret, header? })` holds for a request whose
103
+ `x-gateway-secret` header carries the secret (compared in constant time,
104
+ 16 characters at least). Anything else leaves the context without a caller.
105
+ Without a `trustedGateway`, both throw when the server starts.
106
+
107
+ ### `@authenticated(type: [String!])`
108
+
109
+ ```graphql
110
+ me: User @authenticated
111
+ webhooks: [Webhook!]! @authenticated(type: ["token"])
112
+ type Staff @authenticated(type: ["session"]) { … }
113
+ ```
114
+
115
+ `useAuthenticated(options?)` enforces it: no caller is `UNAUTHENTICATED`
116
+ (401), a caller of a type `type:` does not name is `FORBIDDEN` (403). The type
117
+ is Ory's `kind` — `session` or `token` — or `user.tokenType` without Ory;
118
+ `useAuthenticated({ types: [...CALLER_TYPES, 'service'] })` names other
119
+ values. The field's, its type's and its interfaces' directives are AND-ed; a
120
+ scalar's or an enum's guards every field returning it; a subscription is
121
+ refused before its stream opens. Refused at build, naming where the directive
122
+ sits: `type: []`, an unknown type, and restrictions with nothing in common.
123
+
124
+ Federation declares `@authenticated` with no argument, and a subgraph imports
125
+ that declaration. `useAuthenticated` reads that shape as "any caller", so keep
126
+ federation's declaration in a subgraph; `type:` is for the schemas this
127
+ package's `SHARED_TYPE_DEFS` builds. Details: [the authentication
128
+ guide](./docs/guide/authentication.md).
129
+
77
130
  ## `@permission` — the permission a field requires
78
131
 
79
132
  `@authenticated` asks whether anyone is calling. `@policy` asks whether they
@@ -92,7 +145,7 @@ owner: Person @permission(name: "view", type: "Person", id: "parent.ownerId")
92
145
  ```
93
146
 
94
147
  ```ts
95
- plugins: [useOryAuth(ory), useKetoChecks(ory, { namespaces: ['Note', 'Person'] }), useGenericAuth({ … })]
148
+ plugins: [useOryAuth(ory), useKetoChecks(ory, { namespaces: ['Note', 'Person'] })]
96
149
  ```
97
150
 
98
151
  - `name` is the permit, `type` the Keto namespace, `id` where the object id
@@ -110,10 +163,8 @@ plugins: [useOryAuth(ory), useKetoChecks(ory, { namespaces: ['Note', 'Person'] }
110
163
 
111
164
  **Refused when the schema is built**, as a `TypeError` naming `Type.field`: a
112
165
  path naming no root, an `args.<name>` the field does not declare, a namespace
113
- outside `namespaces` (when you pass them), and a guard on an interface field,
114
- where no resolver runs. On a `@check`, the undeclared argument and the
115
- interface field are logged as warnings instead, since 2.x booted with them;
116
- the next major refuses them there too.
166
+ outside `namespaces` (when you pass them), a guard on an interface field,
167
+ where no resolver runs, and a `@check` — removed in 3.0 — left in the schema.
117
168
 
118
169
  `@permission` is read only when its declaration has `name` and `type` — a
119
170
  schema with its own `@permission` of another shape keeps it. Loading this
@@ -127,27 +178,16 @@ Every question goes through a per-request memo: distinct questions are
127
178
  batched into one `POST /relation-tuples/batch/check`, identical ones asked
128
179
  once, a failed batch not remembered.
129
180
 
130
- ### `@check`, the list-of-lists form — deprecated
131
-
132
- `@check(permissions: [[{ namespace, permit, id }]], onDeny, message)` keeps
133
- working, is validated the same way, and is evaluated in declaration order with
134
- `@permission` on the same field. Its outer list is OR, its inner list AND. Write
135
- `@permission` in a new schema; `@check` is planned for removal in a major.
136
-
137
- ```graphql
138
- note(id: ID!): Note! @check(permissions: [[{ namespace: "Note", permit: "view" }]])
139
- ```
140
-
141
181
  ### From a resolver: `requireUser` and `can`
142
182
 
143
183
  ```ts
144
- import { can, type OryGraphQLContext, requireUser } from '@nxgt/shared-graphql';
145
- import { CustomException } from '@nxgt/shared-exceptions';
184
+ import { can, denial, type OryGraphQLContext, requireUser } from '@nxgt/shared-graphql';
185
+ import { ErrorCode } from '@nxgt/shared-exceptions';
146
186
 
147
187
  async function archive(_: unknown, { id }: { id: string }, ctx: OryGraphQLContext) {
148
188
  const user = requireUser(ctx); // UNAUTHENTICATED (401) when nobody is calling
149
189
  if (!(await can(ctx, { name: 'edit', type: 'Note', id }))) {
150
- throw CustomException.forbidden({ message: 'notes.errors.read-only' });
190
+ throw denial(ErrorCode.Forbidden, 'notes.errors.read-only');
151
191
  }
152
192
  return notes.archive(id, user.sub);
153
193
  }
@@ -167,24 +207,28 @@ after, which makes `totalCount` and the cursors lie.
167
207
 
168
208
  Shipping the SDL is enough for a standalone Yoga schema. It is **not** enough
169
209
  for a subgraph that federation composes: that subgraph needs
170
- `@composeDirective(name: "@permission")` (or `"@check"`) and the directive in
171
- its own `@link` import list. Without them the composition drops it silently —
172
- the supergraph SDL comes out valid, the field loses its guard, and nothing
173
- fails.
210
+ `@composeDirective(name: "@permission")` and the directive in its own `@link`
211
+ import list. Without them the composition drops it silently — the supergraph
212
+ SDL comes out valid, the field loses its guard, and nothing fails.
174
213
 
175
214
  ## Errors
176
215
 
216
+ A denial — from `@permission`, `@authenticated`, `requireUser` or `can`, or
217
+ your own `denial(code, message?)` — is a `GraphQLError` with
218
+ `extensions { code, http { status } }`: `UNAUTHENTICATED` 401, `FORBIDDEN` 403,
219
+ `NOT_FOUND` 404. Any Yoga or Apollo server answers it with that status. To
220
+ translate its message and map the rest, register:
221
+
177
222
  ```ts
178
223
  createYoga({ maskedErrors: { maskError: createMaskError(translate) } });
179
224
  new ApolloServer({ formatError: createFormatError(translate) });
180
225
  ```
181
226
 
182
- Under Yoga, `createMaskError` turns a `CustomException` into a `GraphQLError`
183
- with `extensions { code, http { status } }` — `UNAUTHENTICATED` 401,
184
- `FORBIDDEN` 403, `NOT_FOUND` 404 — and its translated message; `OryUnavailable`
185
- into `SERVICE_UNAVAILABLE` 503, recognised even from a second copy of
186
- `@nxgt/ory-sdk`; and a plain `Error` a resolver threw into the mask message, as
187
- Yoga's default does.
227
+ Under Yoga, `createMaskError` translates a denial's key with your
228
+ `translate`; turns a `CustomException` into a `GraphQLError` with its code,
229
+ status and translated message; `OryUnavailable` into `SERVICE_UNAVAILABLE`
230
+ 503, recognised even from a second copy of `@nxgt/ory-sdk`; and a plain
231
+ `Error` a resolver threw into the mask message, as Yoga's default does.
188
232
 
189
233
  Under Apollo, `createFormatError` sets the `code` and the translated message
190
234
  (and `SERVICE_UNAVAILABLE` for an outage, with `http.status` in `extensions`
@@ -195,19 +239,23 @@ Apollo would not.
195
239
 
196
240
  | Export | What it does |
197
241
  | --- | --- |
198
- | `useOryAuth(ory)` | Yoga plugin: resolve the caller through Kratos or Hydra, set `user`, `claims`, `ory` |
199
- | `useKetoChecks(ory, options?)` | Yoga plugin: the per-request Keto memo, and the `@permission` / `@check` transform |
242
+ | `useOryAuth(ory)` | Yoga plugin: resolve the caller through Kratos or Hydra, set `user`, `claims`, `token`, `ory` |
243
+ | `useAuth({ trustedGateway })` | Yoga plugin: the caller a trusted gateway put in `extensions` — `user`, `token` |
244
+ | `extractJwtPlugin({ trustedGateway })` | Apollo plugin: the payload a trusted gateway put in `extensions.payload`, on `context.jwt` |
245
+ | `gatewaySecret({ secret, header? })` | the stock `trustedGateway`: a shared secret in a header |
246
+ | `useAuthenticated(options?)` | Yoga plugin: enforce `@authenticated(type:)`; `CALLER_TYPES` is the default `types` |
247
+ | `applyAuthenticated(schema, options?)` | the same transform on an already-built schema |
248
+ | `useKetoChecks(ory, options?)` | Yoga plugin: the per-request Keto memo, and the `@permission` transform |
200
249
  | `applyKetoChecks(schema, options?)` | the same transform on an already-built schema |
201
250
  | `requireUser(ctx)`, `can(ctx, question)` | the caller or 401; Keto's answer through the memo |
202
- | `useAuth()` | Yoga plugin: copy `user` and `token` from the request's `extensions` — trust it only behind a gateway that sets them |
203
- | `extractJwtPlugin` | Apollo plugin: copy `request.extensions.payload` onto `context.jwt` |
251
+ | `denial(code, message?)` | a refusal as a `GraphQLError` with its status |
204
252
 
205
253
  `GraphQLBaseContext.user` is `TokenPrincipal` — the caller as the access token
206
254
  describes them (`sub`, `uid`, `scope`). It is not `Principal`, which is the
207
255
  header-derived shape used by the REST services.
208
256
 
209
257
  `createYogaHono` / `honoYoga` (from this package's integrations) mount Yoga on
210
- Hono. `sandboxExpolorer` serves Apollo Sandbox. Subscriptions go over Redis
258
+ Hono. `sandboxExplorer` serves Apollo Sandbox. Subscriptions go over Redis
211
259
  (`graphql-subscriptions` is re-exported). `DataLoader` is re-exported so a
212
260
  subgraph does not take a second copy.
213
261
 
@@ -217,13 +265,25 @@ SDL.
217
265
  ## Things that bite
218
266
 
219
267
  - **`@permission` on a list field is the wrong tool.** Filter before the read.
220
- - **`useAuth()` reads the request body's `extensions`.** A client that can
221
- reach the service directly can set them. Put the service behind the gateway
222
- that writes them, or resolve the caller with `useOryAuth(ory)`.
268
+ - **`useAuth()` without the gateway's header reads no caller.** A gateway
269
+ that does not send `x-gateway-secret` on every subgraph request makes every
270
+ caller anonymous. Send it from the gateway, not from clients.
271
+ - **An object type's `@authenticated` guards its fields, not the field that
272
+ returns it** (a scalar's or an enum's does guard the fields returning it).
273
+ Put the directive on the field too when the lookup itself must not run for
274
+ an anonymous caller.
223
275
  - **Do not import `graphql-subscriptions` from `graphql-subscriptions`.** Take
224
276
  it from this package, same reason mongoose comes from `@nxgt/shared-mongo`.
225
- - **The sandbox helper is spelled `sandboxExpolorer`.** That is the export
226
- name. A corrected spelling is a breaking change, not a typo fix in the
227
- consumer.
228
277
  - **`stx-sdk` is required.** Unlike `@nxgt/security`, this package does not
229
278
  mark it optional.
279
+
280
+ ## Documentation
281
+
282
+ | Page | Read it when |
283
+ | --- | --- |
284
+ | [Migrating to 3.0](./docs/guide/migrating-to-3.md) | you upgrade from 2.x |
285
+ | [Authentication](./docs/guide/authentication.md) | you wire `useOryAuth`, `useAuth` behind a gateway, or `@authenticated` |
286
+ | [Permissions](./docs/guide/permissions.md) | you guard a field with `@permission`, or ask Keto from a resolver |
287
+ | [Errors](./docs/guide/errors.md) | you decide what a client receives, or write your own mask |
288
+ | [Troubleshooting](./docs/troubleshooting.md) | you have an error message in hand |
289
+ | [Roadmap](./docs/roadmap.md) | you want to know what is next |
@@ -0,0 +1,42 @@
1
+ import type { GraphQLSchema } from 'graphql';
2
+ import type { FieldNode } from './scope';
3
+ export declare const AUTHENTICATED_DIRECTIVE_NAME = "authenticated";
4
+ /**
5
+ * The kinds of caller `@authenticated(type:)` can name by default: Ory's
6
+ * `OryPrincipal.kind` — a Kratos `session`, or an OAuth2 access `token`
7
+ * (introspected by Hydra, whether a person's or a `client_credentials` one).
8
+ */
9
+ export declare const CALLER_TYPES: readonly ["session", "token"];
10
+ export type AuthenticatedOptions = {
11
+ /**
12
+ * Every value a `type:` may name, when the caller's type is not Ory's
13
+ * `kind` — `TokenPrincipal.tokenType` behind a gateway, say. A `type`
14
+ * outside it is refused at build. `CALLER_TYPES` by default.
15
+ */
16
+ types?: readonly string[];
17
+ };
18
+ /**
19
+ * What the `@authenticated`s that apply to one field demand together: a
20
+ * caller, and — when `types` is set — one of those types. Every directive
21
+ * that applies must hold, so `types` is their intersection.
22
+ */
23
+ export type CallerRequirement = {
24
+ types?: readonly string[];
25
+ };
26
+ /**
27
+ * The `@authenticated` on a field, a type or an interface, read and
28
+ * validated: `null` when there is none, `{}` for any caller, `{ types }` for
29
+ * a caller of one of them.
30
+ *
31
+ * Federation declares `@authenticated` with no argument. A schema that
32
+ * imports that declaration has no `type` to read, and every `@authenticated`
33
+ * in it means "any caller" — the shape is accepted as it is.
34
+ */
35
+ export declare function readAuthenticated(schema: GraphQLSchema, node: FieldNode, where: string, known: readonly string[]): CallerRequirement | null;
36
+ /**
37
+ * Every requirement that applies to one field, AND-ed: a caller when any
38
+ * applies, and the types all of them admit. Refused at build when they admit
39
+ * no type in common — no request could pass.
40
+ */
41
+ export declare function combineRequirements(found: readonly (CallerRequirement | null)[], where: string): CallerRequirement | null;
42
+ //# sourceMappingURL=authenticated.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authenticated.d.ts","sourceRoot":"","sources":["../../src/directives/authenticated.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,eAAO,MAAM,4BAA4B,kBAAkB,CAAC;AAE5D;;;;GAIG;AACH,eAAO,MAAM,YAAY,+BAAgC,CAAC;AAE1D,MAAM,MAAM,oBAAoB,GAAG;IAClC;;;;OAIG;IACH,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1B,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAAE,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC;AAE9D;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAChC,MAAM,EAAE,aAAa,EACrB,IAAI,EAAE,SAAS,EACf,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,SAAS,MAAM,EAAE,GACtB,iBAAiB,GAAG,IAAI,CAmB1B;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAClC,KAAK,EAAE,SAAS,CAAC,iBAAiB,GAAG,IAAI,CAAC,EAAE,EAC5C,KAAK,EAAE,MAAM,GACX,iBAAiB,GAAG,IAAI,CAc1B"}
@@ -1,2 +1,8 @@
1
+ /**
2
+ * Federation's own directive declarations, as a subgraph imports them.
3
+ * `@authenticated` keeps federation's shape — no argument — which
4
+ * `useAuthenticated` reads as "any caller". `AUTHENTICATED_DIRECTIVE_SDL`, in
5
+ * `SHARED_TYPE_DEFS`, is the one with `type:`.
6
+ */
1
7
  export declare const FEDERATION_DIRECTIVES = "\ndirective @external on FIELD_DEFINITION | OBJECT\ndirective @requires(fields: FieldSet!) on FIELD_DEFINITION\ndirective @provides(fields: FieldSet!) on FIELD_DEFINITION\ndirective @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\ndirective @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\ndirective @tag(name: String!) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\ndirective @override(from: String!) on FIELD_DEFINITION\ndirective @composeDirective(name: String!) repeatable on SCHEMA\ndirective @interfaceObject on OBJECT\ndirective @authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM\n";
2
8
  //# sourceMappingURL=federation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"federation.d.ts","sourceRoot":"","sources":["../../src/directives/federation.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,qBAAqB,45BAYjC,CAAC"}
1
+ {"version":3,"file":"federation.d.ts","sourceRoot":"","sources":["../../src/directives/federation.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,45BAYjC,CAAC"}
@@ -1,8 +1,7 @@
1
- export * from './check';
1
+ export { AUTHENTICATED_DIRECTIVE_NAME, type AuthenticatedOptions, CALLER_TYPES, } from './authenticated';
2
2
  export * from './federation';
3
3
  export { assertReadablePath, objectIds, readPath } from './paths';
4
4
  export * from './permission';
5
- export * from './requirements';
6
5
  export type { ReadOptions } from './scope';
7
6
  export * from './sdl';
8
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/directives/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAClE,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC3C,cAAc,OAAO,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/directives/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,4BAA4B,EAC5B,KAAK,oBAAoB,EACzB,YAAY,GACZ,MAAM,iBAAiB,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAClE,cAAc,cAAc,CAAC;AAC7B,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC3C,cAAc,OAAO,CAAC"}
@@ -1,5 +1,5 @@
1
+ import type { PermissionRequirement } from '@nxgt/ory-sdk';
1
2
  import type { GraphQLSchema } from 'graphql';
2
- import type { CheckArgs, CheckDenial } from './check';
3
3
  import { type FieldNode, type ReadOptions } from './scope';
4
4
  /**
5
5
  * `@permission(name:, type:, id:, onDeny:, message:)` — one Keto question per
@@ -7,7 +7,7 @@ import { type FieldNode, type ReadOptions } from './scope';
7
7
  * `graphql/directives/permission.graphqls` and `PERMISSION_DIRECTIVE_SDL`.
8
8
  */
9
9
  export declare const PERMISSION_DIRECTIVE_NAME = "permission";
10
- export type PermissionDenial = CheckDenial;
10
+ export type PermissionDenial = 'NOT_FOUND' | 'FORBIDDEN';
11
11
  /** One `@permission` as written, after its defaults. */
12
12
  export type PermissionArgs = {
13
13
  name: string;
@@ -17,9 +17,23 @@ export type PermissionArgs = {
17
17
  message?: string;
18
18
  };
19
19
  /**
20
- * Every `@permission` on a field, in declaration order, validated, each as the
21
- * one-term requirement `@check` would spell `[[{ namespace: type, permit:
22
- * name, id }]]` — so the transform answers both through one path.
20
+ * One `@permission` as the transform answers it: the one-term requirement
21
+ * `[[{ namespace: type, permit: name, id }]]` that `evaluateRequirement`
22
+ * takes, its denial, and its i18n key.
23
23
  */
24
- export declare function readPermissions(schema: GraphQLSchema, node: FieldNode, where: string, options?: ReadOptions): CheckArgs[];
24
+ export type FieldPermission = {
25
+ permissions: PermissionRequirement;
26
+ onDeny: PermissionDenial;
27
+ /** i18n key; the shared `errors.*` one when the field does not say. */
28
+ message?: string;
29
+ };
30
+ /**
31
+ * Every `@permission` on a field, in declaration order, validated.
32
+ *
33
+ * Repeatable, so this is a list and the order is load-bearing: `view` then
34
+ * `edit` is what turns a denial into 404 for a stranger and 403 for a viewer.
35
+ * The `id` and `onDeny` defaults are applied here as well as in the SDL, which
36
+ * graphql 17 does not always hand to `getDirective`.
37
+ */
38
+ export declare function readPermissions(schema: GraphQLSchema, node: FieldNode, where: string, options?: ReadOptions): FieldPermission[];
25
39
  //# sourceMappingURL=permission.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"permission.d.ts","sourceRoot":"","sources":["../../src/directives/permission.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEtD,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,WAAW,EAAW,MAAM,SAAS,CAAC;AAGpE;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,eAAe,CAAC;AAEtD,MAAM,MAAM,gBAAgB,GAAG,WAAW,CAAC;AAE3C,wDAAwD;AACxD,MAAM,MAAM,cAAc,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,gBAAgB,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,eAAe,CAC9B,MAAM,EAAE,aAAa,EACrB,IAAI,EAAE,SAAS,EACf,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,WAAgB,GACvB,SAAS,EAAE,CAab"}
1
+ {"version":3,"file":"permission.d.ts","sourceRoot":"","sources":["../../src/directives/permission.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,WAAW,EAAW,MAAM,SAAS,CAAC;AAGpE;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,eAAe,CAAC;AAEtD,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG,WAAW,CAAC;AAEzD,wDAAwD;AACxD,MAAM,MAAM,cAAc,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,gBAAgB,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG;IAC7B,WAAW,EAAE,qBAAqB,CAAC;IACnC,MAAM,EAAE,gBAAgB,CAAC;IACzB,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC9B,MAAM,EAAE,aAAa,EACrB,IAAI,EAAE,SAAS,EACf,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,WAAgB,GACvB,eAAe,EAAE,CAanB"}
@@ -0,0 +1,15 @@
1
+ import type { GraphQLSchema } from 'graphql';
2
+ import type { FieldNode } from './scope';
3
+ /**
4
+ * `@check`, the list-of-lists form, was removed in 3.0. This package no longer
5
+ * ships its declaration, so a schema that still writes one either fails to
6
+ * build (`Unknown directive "@check"`) or declares it itself — and then the
7
+ * transform, which no longer reads it, would leave the field open in silence.
8
+ *
9
+ * So a field carrying a `@check` whose declaration has `permissions` — the
10
+ * shape 2.x shipped — is refused when the schema is built, naming the field
11
+ * and the rewrite. A `@check` of another shape is the schema's own and is left
12
+ * alone, as a foreign `@permission` is.
13
+ */
14
+ export declare function refuseRemovedCheck(schema: GraphQLSchema, node: FieldNode, where: string): void;
15
+ //# sourceMappingURL=removed-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"removed-check.d.ts","sourceRoot":"","sources":["../../src/directives/removed-check.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CACjC,MAAM,EAAE,aAAa,EACrB,IAAI,EAAE,SAAS,EACf,KAAK,EAAE,MAAM,QAQb"}
@@ -2,11 +2,11 @@ import type { getDirective } from '@graphql-tools/utils';
2
2
  import type { RequirementScope } from './validate';
3
3
  /** A field definition or a field config — whatever `getDirective` reads. */
4
4
  export type FieldNode = Parameters<typeof getDirective>[1];
5
- /** What a schema may add to the build-time checks of `@check` and `@permission`. */
5
+ /** What a schema may add to the build-time checks of `@permission`. */
6
6
  export type ReadOptions = {
7
7
  /**
8
- * The namespaces of the stack's OPL document. When given, a `type` or
9
- * `namespace` outside it is refused at build.
8
+ * The namespaces of the stack's OPL document. When given, a `type`
9
+ * outside it is refused at build.
10
10
  */
11
11
  namespaces?: readonly string[];
12
12
  };
@@ -1 +1 @@
1
- {"version":3,"file":"scope.d.ts","sourceRoot":"","sources":["../../src/directives/scope.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,4EAA4E;AAC5E,MAAM,MAAM,SAAS,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;AAE3D,oFAAoF;AACpF,MAAM,MAAM,WAAW,GAAG;IACzB;;;OAGG;IACH,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC/B,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,OAAO,CACtB,IAAI,EAAE,SAAS,EACf,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,WAAW,GAClB,gBAAgB,CAMlB"}
1
+ {"version":3,"file":"scope.d.ts","sourceRoot":"","sources":["../../src/directives/scope.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,4EAA4E;AAC5E,MAAM,MAAM,SAAS,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;AAE3D,uEAAuE;AACvE,MAAM,MAAM,WAAW,GAAG;IACzB;;;OAGG;IACH,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC/B,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,OAAO,CACtB,IAAI,EAAE,SAAS,EACf,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,WAAW,GAClB,gBAAgB,CAMlB"}
@@ -7,10 +7,20 @@
7
7
  * the `#` comments, and `sdl.spec.ts` holds each equal to its file once both
8
8
  * are parsed and printed. Edit the file, then this.
9
9
  */
10
- /** `@check`, its `CheckPermission` input and its `CheckDenial` enum. */
11
- export declare const CHECK_DIRECTIVE_SDL = "\"\"\"\nOne question, with the object left as a path instead of a value.\n\"\"\"\ninput CheckPermission {\n\t\"\"\"\n\tThe Keto namespace, e.g. \"Note\" \u2014 from the stack's OPL document.\n\t\"\"\"\n\tnamespace: String!\n\n\t\"\"\"\n\tThe permit asked of it, e.g. \"view\". A plain relation works too; Keto\n\tanswers `false`, not an error, for a name it does not know.\n\t\"\"\"\n\tpermit: String!\n\n\t\"\"\"\n\tWhere the object id is read: `args.<path>`, or `parent.<path>` /\n\t`source.<path>` (the same root under two names), dotted paths allowed. A value that turns out to be a LIST requires the permit on every\n\telement \u2014 that is what a mutation taking `ids: [ID!]!` means.\n\t\"\"\"\n\tid: String = \"args.id\"\n}\n\n\"\"\"\nWhat a denial looks like from outside.\n\"\"\"\nenum CheckDenial {\n\t\"\"\"\n\tThe same answer as for an id that never existed, so ids cannot be probed.\n\tThe default, and the one to keep unless the caller already knows the object\n\tis there.\n\t\"\"\"\n\tNOT_FOUND\n\n\t\"\"\"\n\tFor a second check on an object the caller can already see: a viewer asked\n\tto edit already knows it exists, and \"you may not change it\" is honest.\n\t\"\"\"\n\tFORBIDDEN\n}\n\n\"\"\"\nDeprecated: write `@permission(name:, type:)` in a new schema \u2014 one Keto\nquestion per directive, repeated for AND, with OR kept in the Keto model. This\nform keeps working and is evaluated in declaration order with @permission.\n\nThe permission a field requires, in disjunctive normal form: the OUTER list is\nOR, the INNER list is AND. `[[A, B], [C]]` reads \"(A and B) or C\" \u2014 the same\nshape `@policy(policies: [[\"ADMIN\"]])` uses.\n\nRepeatable, and evaluated in declaration order, each with its own `onDeny`.\nThat is how the 404-then-403 ladder is written:\n\n updateNote(id: ID!, input: UpdateNoteInput!): Note!\n @check(permissions: [[{ namespace: \"Note\", permit: \"view\" }]])\n @check(permissions: [[{ namespace: \"Note\", permit: \"edit\" }]], onDeny: FORBIDDEN)\n\nNOT for a field that answers a LIST the caller is entitled to. \"Which notes may\nI see\" is not a check, it is a Keto query folded into the database filter\nbefore the read. A directive there would have to fetch everything and filter\nafter, which makes `totalCount` and the cursors lie.\n\"\"\"\ndirective @check(\n\tpermissions: [[CheckPermission!]!]!\n\tonDeny: CheckDenial! = NOT_FOUND\n\n\t\"\"\"\n\tThe i18n key the denial carries, e.g. \"notes.errors.not-found\". Defaults to\n\t`errors.not-found` / `errors.insufficient-permissions`, the shared keys.\n\n\tSet it whenever the API's own service layer answers the same refusal with a\n\tdomain message. Two layers guard these fields \u2014 the directive, and the\n\t`require<M>Access` the service calls \u2014 and if they word the same 404\n\tdifferently, the wording tells a caller WHICH one refused: a generic message\n\tmeans \"you may not\", a domain one means \"it is gone\". That is precisely the\n\tdistinction NOT_FOUND exists to hide.\n\t\"\"\"\n\tmessage: String\n) repeatable on FIELD_DEFINITION\n";
12
10
  /** `@permission` and its `PermissionDenial` enum. */
13
11
  export declare const PERMISSION_DIRECTIVE_SDL = "\"\"\"\nWhat a refused @permission looks like from outside.\n\"\"\"\nenum PermissionDenial {\n\t\"\"\"\n\tThe same answer as for an id that never existed, so ids cannot be probed.\n\tThe default.\n\t\"\"\"\n\tNOT_FOUND\n\n\t\"\"\"\n\tFor a permission asked of an object the caller can already see: a viewer\n\tasked to edit already knows it exists, and \"you may not change it\" is\n\thonest.\n\t\"\"\"\n\tFORBIDDEN\n}\n\n\"\"\"\nThe permission a field requires: the caller must hold the permit `name` on the\n`type` object whose id `id` points at.\n\nRepeatable, and repeated ones are AND, evaluated in declaration order \u2014 the\n404-then-403 ladder:\n\n updateNote(id: ID!, input: UpdateNoteInput!): Note!\n @permission(name: \"view\", type: \"Note\")\n @permission(name: \"edit\", type: \"Note\", onDeny: FORBIDDEN)\n\nNOT for a field that answers a LIST the caller is entitled to: fold a Keto\nquery into the database filter before the read instead.\n\"\"\"\ndirective @permission(\n\t\"\"\"\n\tThe permit asked of Keto, e.g. \"view\". A plain relation works too; Keto\n\tanswers `false`, not an error, for a name it does not know.\n\t\"\"\"\n\tname: String!\n\n\t\"\"\"\n\tThe Keto namespace, e.g. \"Note\" \u2014 from the stack's OPL document.\n\t\"\"\"\n\ttype: String!\n\n\t\"\"\"\n\tWhere the object id is read: `args.<path>` or `parent.<path>`, dotted\n\tpaths allowed, `args.id` when omitted. A value that turns out to be a LIST\n\trequires the permit on every element.\n\t\"\"\"\n\tid: String\n\n\tonDeny: PermissionDenial! = NOT_FOUND\n\n\t\"\"\"\n\tThe i18n key the denial carries, e.g. \"notes.errors.not-found\". Defaults to\n\t`errors.not-found` / `errors.insufficient-permissions`, the shared keys.\n\tSet it when the service layer words the same refusal with a domain message,\n\tso the wording does not tell a caller which layer refused.\n\t\"\"\"\n\tmessage: String\n) repeatable on FIELD_DEFINITION\n";
14
- /** Both, for `createSchema({ typeDefs: [KETO_DIRECTIVES_SDL, …] })`. */
15
- export declare const KETO_DIRECTIVES_SDL = "\"\"\"\nOne question, with the object left as a path instead of a value.\n\"\"\"\ninput CheckPermission {\n\t\"\"\"\n\tThe Keto namespace, e.g. \"Note\" \u2014 from the stack's OPL document.\n\t\"\"\"\n\tnamespace: String!\n\n\t\"\"\"\n\tThe permit asked of it, e.g. \"view\". A plain relation works too; Keto\n\tanswers `false`, not an error, for a name it does not know.\n\t\"\"\"\n\tpermit: String!\n\n\t\"\"\"\n\tWhere the object id is read: `args.<path>`, or `parent.<path>` /\n\t`source.<path>` (the same root under two names), dotted paths allowed. A value that turns out to be a LIST requires the permit on every\n\telement \u2014 that is what a mutation taking `ids: [ID!]!` means.\n\t\"\"\"\n\tid: String = \"args.id\"\n}\n\n\"\"\"\nWhat a denial looks like from outside.\n\"\"\"\nenum CheckDenial {\n\t\"\"\"\n\tThe same answer as for an id that never existed, so ids cannot be probed.\n\tThe default, and the one to keep unless the caller already knows the object\n\tis there.\n\t\"\"\"\n\tNOT_FOUND\n\n\t\"\"\"\n\tFor a second check on an object the caller can already see: a viewer asked\n\tto edit already knows it exists, and \"you may not change it\" is honest.\n\t\"\"\"\n\tFORBIDDEN\n}\n\n\"\"\"\nDeprecated: write `@permission(name:, type:)` in a new schema \u2014 one Keto\nquestion per directive, repeated for AND, with OR kept in the Keto model. This\nform keeps working and is evaluated in declaration order with @permission.\n\nThe permission a field requires, in disjunctive normal form: the OUTER list is\nOR, the INNER list is AND. `[[A, B], [C]]` reads \"(A and B) or C\" \u2014 the same\nshape `@policy(policies: [[\"ADMIN\"]])` uses.\n\nRepeatable, and evaluated in declaration order, each with its own `onDeny`.\nThat is how the 404-then-403 ladder is written:\n\n updateNote(id: ID!, input: UpdateNoteInput!): Note!\n @check(permissions: [[{ namespace: \"Note\", permit: \"view\" }]])\n @check(permissions: [[{ namespace: \"Note\", permit: \"edit\" }]], onDeny: FORBIDDEN)\n\nNOT for a field that answers a LIST the caller is entitled to. \"Which notes may\nI see\" is not a check, it is a Keto query folded into the database filter\nbefore the read. A directive there would have to fetch everything and filter\nafter, which makes `totalCount` and the cursors lie.\n\"\"\"\ndirective @check(\n\tpermissions: [[CheckPermission!]!]!\n\tonDeny: CheckDenial! = NOT_FOUND\n\n\t\"\"\"\n\tThe i18n key the denial carries, e.g. \"notes.errors.not-found\". Defaults to\n\t`errors.not-found` / `errors.insufficient-permissions`, the shared keys.\n\n\tSet it whenever the API's own service layer answers the same refusal with a\n\tdomain message. Two layers guard these fields \u2014 the directive, and the\n\t`require<M>Access` the service calls \u2014 and if they word the same 404\n\tdifferently, the wording tells a caller WHICH one refused: a generic message\n\tmeans \"you may not\", a domain one means \"it is gone\". That is precisely the\n\tdistinction NOT_FOUND exists to hide.\n\t\"\"\"\n\tmessage: String\n) repeatable on FIELD_DEFINITION\n\n\"\"\"\nWhat a refused @permission looks like from outside.\n\"\"\"\nenum PermissionDenial {\n\t\"\"\"\n\tThe same answer as for an id that never existed, so ids cannot be probed.\n\tThe default.\n\t\"\"\"\n\tNOT_FOUND\n\n\t\"\"\"\n\tFor a permission asked of an object the caller can already see: a viewer\n\tasked to edit already knows it exists, and \"you may not change it\" is\n\thonest.\n\t\"\"\"\n\tFORBIDDEN\n}\n\n\"\"\"\nThe permission a field requires: the caller must hold the permit `name` on the\n`type` object whose id `id` points at.\n\nRepeatable, and repeated ones are AND, evaluated in declaration order \u2014 the\n404-then-403 ladder:\n\n updateNote(id: ID!, input: UpdateNoteInput!): Note!\n @permission(name: \"view\", type: \"Note\")\n @permission(name: \"edit\", type: \"Note\", onDeny: FORBIDDEN)\n\nNOT for a field that answers a LIST the caller is entitled to: fold a Keto\nquery into the database filter before the read instead.\n\"\"\"\ndirective @permission(\n\t\"\"\"\n\tThe permit asked of Keto, e.g. \"view\". A plain relation works too; Keto\n\tanswers `false`, not an error, for a name it does not know.\n\t\"\"\"\n\tname: String!\n\n\t\"\"\"\n\tThe Keto namespace, e.g. \"Note\" \u2014 from the stack's OPL document.\n\t\"\"\"\n\ttype: String!\n\n\t\"\"\"\n\tWhere the object id is read: `args.<path>` or `parent.<path>`, dotted\n\tpaths allowed, `args.id` when omitted. A value that turns out to be a LIST\n\trequires the permit on every element.\n\t\"\"\"\n\tid: String\n\n\tonDeny: PermissionDenial! = NOT_FOUND\n\n\t\"\"\"\n\tThe i18n key the denial carries, e.g. \"notes.errors.not-found\". Defaults to\n\t`errors.not-found` / `errors.insufficient-permissions`, the shared keys.\n\tSet it when the service layer words the same refusal with a domain message,\n\tso the wording does not tell a caller which layer refused.\n\t\"\"\"\n\tmessage: String\n) repeatable on FIELD_DEFINITION\n";
12
+ /**
13
+ * Every directive `useKetoChecks` answers, for
14
+ * `createSchema({ typeDefs: [KETO_DIRECTIVES_SDL, …] })`. Since `@check` was
15
+ * removed in 3.0, that is `@permission` alone.
16
+ */
17
+ export declare const KETO_DIRECTIVES_SDL = "\"\"\"\nWhat a refused @permission looks like from outside.\n\"\"\"\nenum PermissionDenial {\n\t\"\"\"\n\tThe same answer as for an id that never existed, so ids cannot be probed.\n\tThe default.\n\t\"\"\"\n\tNOT_FOUND\n\n\t\"\"\"\n\tFor a permission asked of an object the caller can already see: a viewer\n\tasked to edit already knows it exists, and \"you may not change it\" is\n\thonest.\n\t\"\"\"\n\tFORBIDDEN\n}\n\n\"\"\"\nThe permission a field requires: the caller must hold the permit `name` on the\n`type` object whose id `id` points at.\n\nRepeatable, and repeated ones are AND, evaluated in declaration order \u2014 the\n404-then-403 ladder:\n\n updateNote(id: ID!, input: UpdateNoteInput!): Note!\n @permission(name: \"view\", type: \"Note\")\n @permission(name: \"edit\", type: \"Note\", onDeny: FORBIDDEN)\n\nNOT for a field that answers a LIST the caller is entitled to: fold a Keto\nquery into the database filter before the read instead.\n\"\"\"\ndirective @permission(\n\t\"\"\"\n\tThe permit asked of Keto, e.g. \"view\". A plain relation works too; Keto\n\tanswers `false`, not an error, for a name it does not know.\n\t\"\"\"\n\tname: String!\n\n\t\"\"\"\n\tThe Keto namespace, e.g. \"Note\" \u2014 from the stack's OPL document.\n\t\"\"\"\n\ttype: String!\n\n\t\"\"\"\n\tWhere the object id is read: `args.<path>` or `parent.<path>`, dotted\n\tpaths allowed, `args.id` when omitted. A value that turns out to be a LIST\n\trequires the permit on every element.\n\t\"\"\"\n\tid: String\n\n\tonDeny: PermissionDenial! = NOT_FOUND\n\n\t\"\"\"\n\tThe i18n key the denial carries, e.g. \"notes.errors.not-found\". Defaults to\n\t`errors.not-found` / `errors.insufficient-permissions`, the shared keys.\n\tSet it when the service layer words the same refusal with a domain message,\n\tso the wording does not tell a caller which layer refused.\n\t\"\"\"\n\tmessage: String\n) repeatable on FIELD_DEFINITION\n";
18
+ /**
19
+ * `@authenticated`, with the `type:` `useAuthenticated` reads: any caller
20
+ * without it, a caller of one of the types with it. Part of
21
+ * `SHARED_TYPE_DEFS`, and not in `graphql/`: a federation subgraph loads
22
+ * `SHARED_SCHEMA_PATH` and imports federation's own `@authenticated`, which
23
+ * takes no argument — a shape `useAuthenticated` reads as "any caller".
24
+ */
25
+ export declare const AUTHENTICATED_DIRECTIVE_SDL = "\"\"\"\nA signed-in caller \u2014 of one of the types `type` names, when it names some.\n\"\"\"\ndirective @authenticated(\n\t\"\"\"\n\tThe kinds of caller admitted: \"session\" or \"token\" by default. Any caller\n\twhen omitted.\n\t\"\"\"\n\ttype: [String!]\n) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM\n";
16
26
  //# sourceMappingURL=sdl.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sdl.d.ts","sourceRoot":"","sources":["../../src/directives/sdl.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,wEAAwE;AACxE,eAAO,MAAM,mBAAmB,ohGA+E/B,CAAC;AAEF,qDAAqD;AACrD,eAAO,MAAM,wBAAwB,u5DA6DpC,CAAC;AAEF,wEAAwE;AACxE,eAAO,MAAM,mBAAmB,w6JAAwD,CAAC"}
1
+ {"version":3,"file":"sdl.d.ts","sourceRoot":"","sources":["../../src/directives/sdl.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,qDAAqD;AACrD,eAAO,MAAM,wBAAwB,u5DA6DpC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,u5DAA2B,CAAC;AAE5D;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,sUAUvC,CAAC"}
@@ -2,28 +2,22 @@ import { type PermissionRequirement } from '@nxgt/ory-sdk';
2
2
  /**
3
3
  * What a requirement is checked against when the schema is built.
4
4
  *
5
- * `namespaces` is optional: without it any namespace is taken on trust, as
6
- * before. With it — the namespaces of the stack's OPL document — a misspelt
7
- * `type` stops the server from booting instead of answering `false` for ever,
8
- * which Keto does, without an error, for a namespace it does not know.
5
+ * `namespaces` is optional: without it any namespace is taken on trust. With
6
+ * it — the namespaces of the stack's OPL document — a misspelt `type` stops
7
+ * the server from booting instead of answering `false` for ever, which Keto
8
+ * does, without an error, for a namespace it does not know.
9
9
  */
10
10
  export type RequirementScope = {
11
- /** `@check on Query.note` — the directive and the field, for the message. */
11
+ /** `@permission on Query.note` — the directive and the field, for the message. */
12
12
  where: string;
13
13
  argumentNames: readonly string[];
14
14
  namespaces?: readonly string[];
15
- /**
16
- * Set for `@check` alone: a mistake it used to let boot — an argument the
17
- * field does not declare — is reported here instead of thrown, so a 2.x
18
- * schema that booted still boots. It becomes a refusal in the next major.
19
- */
20
- lenient?: (message: string) => void;
21
15
  };
22
16
  /**
23
17
  * Every mistake a requirement can carry that is visible without a request, as
24
- * a `TypeError` naming the field: an empty requirement or group (`[[]]` admits
25
- * everyone), a path naming no root, an argument the field does not declare,
26
- * and a namespace the model does not have.
18
+ * a `TypeError` naming the field: an empty name or type, a path naming no
19
+ * root, an argument the field does not declare, and a namespace the model
20
+ * does not have.
27
21
  */
28
22
  export declare function assertRequirementShape(permissions: PermissionRequirement, scope: RequirementScope): void;
29
23
  //# sourceMappingURL=validate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/directives/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAG9E;;;;;;;GAOG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC9B,6EAA6E;IAC7E,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACrC,WAAW,EAAE,qBAAqB,EAClC,KAAK,EAAE,gBAAgB,QAmBvB"}
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/directives/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAG9E;;;;;;;GAOG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC9B,kFAAkF;IAClF,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC/B,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACrC,WAAW,EAAE,qBAAqB,EAClC,KAAK,EAAE,gBAAgB,QAmBvB"}