@nxgt/shared-graphql 2.1.0 → 3.0.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 +112 -52
- package/dist/directives/authenticated.d.ts +42 -0
- package/dist/directives/authenticated.d.ts.map +1 -0
- package/dist/directives/federation.d.ts +6 -0
- package/dist/directives/federation.d.ts.map +1 -1
- package/dist/directives/index.d.ts +1 -2
- package/dist/directives/index.d.ts.map +1 -1
- package/dist/directives/permission.d.ts +20 -6
- package/dist/directives/permission.d.ts.map +1 -1
- package/dist/directives/removed-check.d.ts +15 -0
- package/dist/directives/removed-check.d.ts.map +1 -0
- package/dist/directives/scope.d.ts +3 -3
- package/dist/directives/scope.d.ts.map +1 -1
- package/dist/directives/sdl.d.ts +14 -4
- package/dist/directives/sdl.d.ts.map +1 -1
- package/dist/directives/validate.d.ts +8 -14
- package/dist/directives/validate.d.ts.map +1 -1
- package/dist/index.js +1055 -927
- package/dist/index.js.map +35 -31
- package/dist/integrations/hono.d.ts +2 -2
- package/dist/integrations/hono.d.ts.map +1 -1
- package/dist/plugins/apply-authenticated.d.ts +21 -0
- package/dist/plugins/apply-authenticated.d.ts.map +1 -0
- package/dist/plugins/apply-keto-checks.d.ts +10 -7
- package/dist/plugins/apply-keto-checks.d.ts.map +1 -1
- package/dist/plugins/auth.d.ts +17 -1
- package/dist/plugins/auth.d.ts.map +1 -1
- package/dist/plugins/authenticated.d.ts +14 -0
- package/dist/plugins/authenticated.d.ts.map +1 -0
- package/dist/plugins/extract-jwt.d.ts +19 -6
- package/dist/plugins/extract-jwt.d.ts.map +1 -1
- package/dist/plugins/field-guard.d.ts +30 -0
- package/dist/plugins/field-guard.d.ts.map +1 -0
- package/dist/plugins/gateway-trust.d.ts +47 -0
- package/dist/plugins/gateway-trust.d.ts.map +1 -0
- package/dist/plugins/index.d.ts +3 -0
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/keto-checks.d.ts +3 -2
- package/dist/plugins/keto-checks.d.ts.map +1 -1
- package/dist/plugins/keto-helpers.d.ts +1 -1
- package/dist/plugins/keto-helpers.d.ts.map +1 -1
- package/dist/plugins/ory-auth.d.ts +3 -3
- package/dist/shared.d.ts +1 -1
- package/dist/shared.d.ts.map +1 -1
- package/dist/utils/errors/denial.d.ts +20 -0
- package/dist/utils/errors/denial.d.ts.map +1 -0
- package/dist/utils/errors/format-error.d.ts +2 -1
- package/dist/utils/errors/format-error.d.ts.map +1 -1
- package/dist/utils/errors/index.d.ts +1 -0
- package/dist/utils/errors/index.d.ts.map +1 -1
- package/dist/utils/errors/mask-error.d.ts +2 -1
- package/dist/utils/errors/mask-error.d.ts.map +1 -1
- package/dist/utils/ws-context.d.ts +1 -1
- package/docs/README.md +8 -6
- package/docs/guide/authentication.md +187 -0
- package/docs/guide/errors.md +37 -5
- package/docs/guide/migrating-to-3.md +276 -0
- package/docs/guide/permissions.md +39 -50
- package/docs/roadmap.md +30 -29
- package/docs/troubleshooting.md +151 -48
- package/package.json +7 -11
- package/dist/directives/check.d.ts +0 -32
- package/dist/directives/check.d.ts.map +0 -1
- package/dist/directives/deprecation.d.ts +0 -7
- package/dist/directives/deprecation.d.ts.map +0 -1
- package/dist/directives/requirements.d.ts +0 -14
- package/dist/directives/requirements.d.ts.map +0 -1
- package/graphql/directives/check.graphqls +0 -90
package/docs/troubleshooting.md
CHANGED
|
@@ -1,17 +1,80 @@
|
|
|
1
1
|
# Troubleshooting `@nxgt/shared-graphql`
|
|
2
2
|
|
|
3
3
|
Each entry is headed by the message you see; the parts in `<angle brackets>`
|
|
4
|
-
vary. Messages thrown when the schema is built are
|
|
5
|
-
`
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
vary. Messages thrown when the server starts or the schema is built are
|
|
5
|
+
`TypeError`s, and stop the server from booting; the schema ones name
|
|
6
|
+
`Type.field` after the directive that carries the mistake. The last section
|
|
7
|
+
holds the traps that throw nothing.
|
|
8
|
+
|
|
9
|
+
Upgrading from 2.x, most of these come from [the
|
|
10
|
+
migration](./guide/migrating-to-3.md).
|
|
11
|
+
|
|
12
|
+
## When the server starts
|
|
13
|
+
|
|
14
|
+
### ``useAuth(): name the gateway allowed to set the caller — useAuth({ trustedGateway: gatewaySecret({ secret }) }). …``
|
|
15
|
+
|
|
16
|
+
Also `extractJwtPlugin(): name the gateway allowed to set the caller — …`.
|
|
17
|
+
|
|
18
|
+
Since 3.0 both need to know which gateway may put the caller in the request's
|
|
19
|
+
`extensions`, which a client can otherwise write. Pass one:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
const trustedGateway = gatewaySecret({ secret: process.env.GATEWAY_SECRET! });
|
|
23
|
+
createYoga({ plugins: [useAuth({ trustedGateway })] });
|
|
24
|
+
new ApolloServer({ plugins: [extractJwtPlugin({ trustedGateway })] });
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`extractJwtPlugin` is a function now: `plugins: [extractJwtPlugin]` passes
|
|
28
|
+
the function itself, not a plugin. An API that authenticates its own callers
|
|
29
|
+
uses `useOryAuth(ory)` and needs neither.
|
|
30
|
+
|
|
31
|
+
### ``gatewaySecret(): `secret` must be a string of at least 16 characters — is its environment variable set?``
|
|
32
|
+
|
|
33
|
+
The secret is missing or shorter than 16 characters — most often an
|
|
34
|
+
environment variable that is not set where the server runs. Set it, to the
|
|
35
|
+
same value the gateway sends.
|
|
36
|
+
|
|
37
|
+
### `Cannot find module '@envelop/generic-auth'`
|
|
38
|
+
|
|
39
|
+
Or `@graphql-hive/gateway`, `@envelop/extended-validation`,
|
|
40
|
+
`@hono/zod-validator`, `zod`. 3.0 dropped them from its dependencies — nothing
|
|
41
|
+
here imported them — and your app was using the copy this package brought.
|
|
42
|
+
Declare it yourself:
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
bun add @envelop/generic-auth
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### ``Module '"@nxgt/shared-graphql"' has no exported member 'sandboxExpolorer'. Did you mean 'sandboxExplorer'?``
|
|
49
|
+
|
|
50
|
+
The typo was corrected in 3.0, with no alias. Import `sandboxExplorer`.
|
|
9
51
|
|
|
10
52
|
## When the schema is built
|
|
11
53
|
|
|
54
|
+
### ``@check on <Type.field>: @check was removed in @nxgt/shared-graphql 3.0 — write one @permission(name: "<permit>", type: "<namespace>") per term, and move an OR into the Keto model``
|
|
55
|
+
|
|
56
|
+
The schema declares 2.x's `@check` itself and a field still uses it. This
|
|
57
|
+
package no longer answers it, so the field would boot unguarded; it is refused
|
|
58
|
+
instead. Rewrite it:
|
|
59
|
+
|
|
60
|
+
```graphql
|
|
61
|
+
# before
|
|
62
|
+
note(id: ID!): Note @check(permissions: [[{ namespace: "Note", permit: "view" }]])
|
|
63
|
+
# after
|
|
64
|
+
note(id: ID!): Note @permission(name: "view", type: "Note")
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Then drop your `@check` declaration. [The migration guide](./guide/migrating-to-3.md#2-check-is-removed)
|
|
68
|
+
covers an AND and an OR.
|
|
69
|
+
|
|
70
|
+
### `Unknown directive "@check".`
|
|
71
|
+
|
|
72
|
+
The same, in a schema that loads this package's `graphql/` and nothing else:
|
|
73
|
+
3.0 no longer ships `@check`'s declaration. Rewrite the field as above.
|
|
74
|
+
|
|
12
75
|
### ``@permission on <Type.field>: `id` must be "args.<path>", "parent.<path>" or "source.<path>", got "<id>"``
|
|
13
76
|
|
|
14
|
-
The `id` argument
|
|
77
|
+
The `id` argument names no root. Prefix it:
|
|
15
78
|
|
|
16
79
|
```graphql
|
|
17
80
|
note(noteId: ID!): Note @permission(name: "view", type: "Note", id: "args.noteId")
|
|
@@ -24,28 +87,12 @@ The path reads an argument the field does not have — usually the default
|
|
|
24
87
|
an undeclared argument on `args`, so every request would have failed. Name the
|
|
25
88
|
argument in `id`, or read the parent with `parent.<path>`.
|
|
26
89
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
`@check(permissions: [[]])`. An AND over nothing is true, so the field would
|
|
30
|
-
be open to every signed-in caller while looking guarded. Name a permission,
|
|
31
|
-
or remove the directive.
|
|
90
|
+
In 2.x a `@check` booted with this and logged a warning; a `@permission`
|
|
91
|
+
refuses it.
|
|
32
92
|
|
|
33
|
-
###
|
|
93
|
+
### ``@permission on <Type.field>: every term needs `namespace`, `permit` and `id`, got <term>``
|
|
34
94
|
|
|
35
|
-
`@
|
|
36
|
-
|
|
37
|
-
### ``<…> — @check still boots with this; the next major refuses it, as @permission does``
|
|
38
|
-
|
|
39
|
-
A warning, not an error: one of the two mistakes above — an argument the
|
|
40
|
-
field does not declare, or a guard on an interface field — on a `@check`. The
|
|
41
|
-
server boots as it did in 2.x, but the field answers 500 on every request, or
|
|
42
|
-
is not guarded at all. Fix it as the entry for the same message describes; the
|
|
43
|
-
next major refuses it at build.
|
|
44
|
-
|
|
45
|
-
### ``@check on <Type.field>: every term needs `namespace`, `permit` and `id`, got <term>``
|
|
46
|
-
|
|
47
|
-
A term, or a `@permission`, has an empty `namespace`/`type` or
|
|
48
|
-
`permit`/`name` — `@permission(name: "", type: "Note")`. Name both.
|
|
95
|
+
`@permission(name: "", type: "Note")`: an empty `name` or `type`. Name both.
|
|
49
96
|
|
|
50
97
|
### `@permission on <Type.field>: unknown namespace "<type>" — known: <namespaces>`
|
|
51
98
|
|
|
@@ -54,7 +101,7 @@ names one outside them — most often a typo. Keto would have answered `false`
|
|
|
54
101
|
for ever, without an error. Fix the name, or add the namespace to the list
|
|
55
102
|
when the OPL document gained it.
|
|
56
103
|
|
|
57
|
-
### `<Type.field>: @
|
|
104
|
+
### `<Type.field>: @permission on an interface field guards nothing — no resolver runs there. Put it on each implementing type's field`
|
|
58
105
|
|
|
59
106
|
A resolver runs on an object type's field, never on an interface's. Move the
|
|
60
107
|
directive to every implementing type:
|
|
@@ -67,6 +114,39 @@ type Note implements Node {
|
|
|
67
114
|
}
|
|
68
115
|
```
|
|
69
116
|
|
|
117
|
+
In 2.x a `@check` there booted with a warning; a `@permission` is refused.
|
|
118
|
+
|
|
119
|
+
### ``@authenticated on <where>: `type: []` admits no caller — name a type, or drop `type` ``
|
|
120
|
+
|
|
121
|
+
`<where>` is where the directive sits: `Type.field`, or a type, interface,
|
|
122
|
+
scalar or enum name.
|
|
123
|
+
|
|
124
|
+
An empty list admits nobody. Name the kinds of caller, or drop `type` to
|
|
125
|
+
admit any caller.
|
|
126
|
+
|
|
127
|
+
### `@authenticated on <where>: unknown type "<type>" — known: <types>`
|
|
128
|
+
|
|
129
|
+
`type:` names a value outside the caller types `useAuthenticated` knows —
|
|
130
|
+
`session` and `token` by default. Fix the name, or list your own:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
useAuthenticated({ types: [...CALLER_TYPES, 'service'] });
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### ``@authenticated on <Type.field>: the field's, its type's and its interfaces' `type`s have none in common — no caller could pass``
|
|
137
|
+
|
|
138
|
+
Every `@authenticated` that applies to a field must hold — the field's, its
|
|
139
|
+
type's, its interfaces'. Two of them name disjoint types, so no caller could
|
|
140
|
+
reach the field. Drop or widen one.
|
|
141
|
+
|
|
142
|
+
### `Unknown argument "type" on directive "@authenticated".`
|
|
143
|
+
|
|
144
|
+
The schema declares `@authenticated` without an argument — federation's
|
|
145
|
+
declaration, imported through `@link`, or your own — and a field writes
|
|
146
|
+
`type:`. In a subgraph keep federation's form and drop `type:`; in a schema
|
|
147
|
+
of your own, load `SHARED_TYPE_DEFS` or `AUTHENTICATED_DIRECTIVE_SDL` in place
|
|
148
|
+
of your declaration.
|
|
149
|
+
|
|
70
150
|
### `@nxgt/shared-graphql: no package root`
|
|
71
151
|
|
|
72
152
|
Thrown when the package is imported, if no `package.json` sits anywhere
|
|
@@ -93,7 +173,7 @@ alone.
|
|
|
93
173
|
### `Cannot use GraphQLSchema "<schema>" from another module or realm.`
|
|
94
174
|
|
|
95
175
|
Two copies of `graphql` are installed: `graphql` is a peer of this package
|
|
96
|
-
(`^16.
|
|
176
|
+
(`^16.9.0 || ^17.0.0`), so your app must declare it once, and every GraphQL
|
|
97
177
|
library must resolve that one. Add `graphql` to your `dependencies` if it was
|
|
98
178
|
only there through this package, and check with `bun pm ls graphql` (or
|
|
99
179
|
`npm ls graphql`) that one version remains. graphql runs this check only
|
|
@@ -102,13 +182,12 @@ with less telling errors.
|
|
|
102
182
|
|
|
103
183
|
## When a request runs
|
|
104
184
|
|
|
105
|
-
The three messages below are plain `Error`s thrown inside a resolver:
|
|
106
|
-
`createMaskError` answers the client `Unexpected error.`
|
|
107
|
-
(`INTERNAL_SERVER_ERROR`). The text is in the server log, and in
|
|
108
|
-
`extensions.debugMessage` when `isDev` is on.
|
|
109
|
-
|
|
110
185
|
### `<Type.field>: "<path>" resolved no object id`
|
|
111
186
|
|
|
187
|
+
A plain `Error` thrown inside a resolver: `createMaskError` answers the client
|
|
188
|
+
`Unexpected error.` (`INTERNAL_SERVER_ERROR`); the text is in the server log,
|
|
189
|
+
and in `extensions.debugMessage` when `isDev` is on.
|
|
190
|
+
|
|
112
191
|
The path was valid, but at request time it pointed at nothing — a nullable
|
|
113
192
|
argument nobody passed, an empty list, a parent field the parent resolver did
|
|
114
193
|
not load. It is a 500, never an allow. Make the argument required, or make
|
|
@@ -118,7 +197,7 @@ sure the parent object carries the field.
|
|
|
118
197
|
|
|
119
198
|
The schema was transformed with `applyKetoChecks` but the plugin that puts the
|
|
120
199
|
per-request checker on the context is missing. Register
|
|
121
|
-
`useKetoChecks(ory)` after `useOryAuth(ory)`.
|
|
200
|
+
`useKetoChecks(ory)` after `useOryAuth(ory)`. Answered as the previous entry.
|
|
122
201
|
|
|
123
202
|
### `can(): no checker on the context — useKetoChecks(ory) is not registered`
|
|
124
203
|
|
|
@@ -130,10 +209,12 @@ Keto (or Kratos, Hydra — the name varies) did not answer. This is on
|
|
|
130
209
|
purpose: an outage is never turned into a denial or an anonymous caller. The
|
|
131
210
|
client can retry; `extensions.debugMessage` carries what the SDK saw.
|
|
132
211
|
|
|
133
|
-
###
|
|
212
|
+
### A denial's message reads `notes.errors.not-found`
|
|
134
213
|
|
|
135
|
-
|
|
136
|
-
denial
|
|
214
|
+
A `@permission(message: "notes.errors.not-found")`, or your own
|
|
215
|
+
`denial(code, key)`, reached the client untranslated. A denial translates its
|
|
216
|
+
key with `@nxgt/i18n`'s own resources, which do not hold your app's keys.
|
|
217
|
+
Register the mask with your translator:
|
|
137
218
|
|
|
138
219
|
```ts
|
|
139
220
|
createYoga({ maskedErrors: { maskError: createMaskError(translate) } });
|
|
@@ -141,30 +222,52 @@ createYoga({ maskedErrors: { maskError: createMaskError(translate) } });
|
|
|
141
222
|
|
|
142
223
|
Under Apollo Server, the counterpart is `formatError: createFormatError(translate)`.
|
|
143
224
|
|
|
225
|
+
### `Unexpected error.` with `INTERNAL_SERVER_ERROR` where a service's 404 or 403 was expected
|
|
226
|
+
|
|
227
|
+
A service threw a `CustomException` and `createMaskError` is not registered,
|
|
228
|
+
so Yoga masks it as any non-GraphQL error. The directives and `requireUser` /
|
|
229
|
+
`can` are not concerned — their denials carry their own status — but a
|
|
230
|
+
service's `CustomException.notFound()` is. Register `createMaskError`, as
|
|
231
|
+
above, or throw `denial(code, message)` from the resolver.
|
|
232
|
+
|
|
144
233
|
### `Unexpected error.` where a resolver's own message used to reach the client
|
|
145
234
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
reached the client. Throw a `CustomException` or a `GraphQLError` for a
|
|
235
|
+
`createMaskError` masks a plain `Error` a resolver threw, as Yoga's default
|
|
236
|
+
does. Throw a `CustomException`, a `denial()` or a `GraphQLError` for a
|
|
149
237
|
message meant for the caller.
|
|
150
238
|
|
|
151
239
|
## Traps that throw nothing
|
|
152
240
|
|
|
241
|
+
### Every caller is anonymous behind the gateway
|
|
242
|
+
|
|
243
|
+
`useAuth({ trustedGateway })` reads no caller from a request that does not
|
|
244
|
+
carry the gateway's proof. The gateway is not sending the header — or sends
|
|
245
|
+
another secret — on its subgraph requests. Make it add
|
|
246
|
+
`x-gateway-secret: <secret>` (or your `header`) to every one, with the same
|
|
247
|
+
value the subgraph was given.
|
|
248
|
+
|
|
249
|
+
### `FORBIDDEN` from `@authenticated(type:)` for a caller who should pass
|
|
250
|
+
|
|
251
|
+
The caller's type is `context.ory.kind`, or `context.user.tokenType` without
|
|
252
|
+
Ory. Behind a gateway that writes no `tokenType` in the `user` it forwards,
|
|
253
|
+
every caller has no type, and every `type:` refuses them. Make the gateway
|
|
254
|
+
write it, or drop `type:` on those fields.
|
|
255
|
+
|
|
153
256
|
### A guarded field answers unguarded in the supergraph
|
|
154
257
|
|
|
155
258
|
A subgraph composed by federation drops a directive it was not told to keep.
|
|
156
|
-
Add `@composeDirective(name: "@permission")`
|
|
157
|
-
|
|
259
|
+
Add `@composeDirective(name: "@permission")` and import the directive in the
|
|
260
|
+
subgraph's `@link`.
|
|
158
261
|
|
|
159
|
-
###
|
|
262
|
+
### `@authenticated(type:)` in a subgraph
|
|
160
263
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
264
|
+
Federation declares `@authenticated` without an argument, and a subgraph
|
|
265
|
+
imports that declaration. Keep it: `useAuthenticated` enforces it as "any
|
|
266
|
+
caller". A `type:` restriction is for a schema that declares `@authenticated`
|
|
267
|
+
through `SHARED_TYPE_DEFS` or `AUTHENTICATED_DIRECTIVE_SDL`.
|
|
165
268
|
|
|
166
269
|
### `OryForbidden` from `ory.requireAllowed` is a 500
|
|
167
270
|
|
|
168
271
|
`createMaskError` does not map `OryForbidden`: whether a refusal is a 404 or a
|
|
169
272
|
403 is the caller's decision. In a resolver, use `can` and throw the
|
|
170
|
-
`
|
|
273
|
+
`denial` you mean.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nxgt/shared-graphql",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -49,16 +49,12 @@
|
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"@apollo/server": "^5.5.1",
|
|
51
51
|
"@apollo/subgraph": "^2.14.3",
|
|
52
|
-
"@envelop/extended-validation": "^7.1.1",
|
|
53
|
-
"@envelop/generic-auth": "^11.1.1",
|
|
54
|
-
"@graphql-hive/gateway": "^2.11.2",
|
|
55
52
|
"@graphql-tools/load-files": "^7.0.1",
|
|
56
53
|
"@graphql-tools/merge": "^9.2.3",
|
|
57
54
|
"@graphql-tools/utils": "^12.0.0",
|
|
58
55
|
"@graphql-yoga/redis-event-target": "^3.0.3",
|
|
59
|
-
"@hono/zod-validator": "^0.9.0",
|
|
60
56
|
"@nxgt/i18n": "^1.1.0",
|
|
61
|
-
"@nxgt/security": "^4.0
|
|
57
|
+
"@nxgt/security": "^4.1.0",
|
|
62
58
|
"@nxgt/shared": "^1.0.5",
|
|
63
59
|
"@nxgt/shared-exceptions": "^1.0.4",
|
|
64
60
|
"@nxgt/shared-logging": "^1.0.4",
|
|
@@ -72,19 +68,19 @@
|
|
|
72
68
|
"hono": "^4.13.4",
|
|
73
69
|
"ioredis": "^6.0.0",
|
|
74
70
|
"lodash": "^4.18.1",
|
|
75
|
-
"ws": "^8.21.3"
|
|
76
|
-
"zod": "^4.4.3"
|
|
71
|
+
"ws": "^8.21.3"
|
|
77
72
|
},
|
|
78
73
|
"devDependencies": {
|
|
74
|
+
"@graphql-tools/schema": "^10.1.0",
|
|
79
75
|
"@types/bun": "^1.4.0",
|
|
80
76
|
"@types/graphql-upload": "^17.0.0",
|
|
81
77
|
"@types/lodash": "^4.17.25",
|
|
82
78
|
"@types/ws": "^8.18.1",
|
|
83
|
-
"graphql": "^16.
|
|
79
|
+
"graphql": "^16.9.0"
|
|
84
80
|
},
|
|
85
81
|
"peerDependencies": {
|
|
86
|
-
"@nxgt/ory-sdk": ">=0.1.0",
|
|
87
|
-
"graphql": "^16.
|
|
82
|
+
"@nxgt/ory-sdk": ">=0.1.0 <1",
|
|
83
|
+
"graphql": "^16.9.0 || ^17.0.0",
|
|
88
84
|
"stx-sdk": ">=1.1.0",
|
|
89
85
|
"typescript": "^6.0.3"
|
|
90
86
|
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import type { PermissionRequirement } from '@nxgt/ory-sdk';
|
|
2
|
-
import type { GraphQLSchema } from 'graphql';
|
|
3
|
-
import { type FieldNode, type ReadOptions } from './scope';
|
|
4
|
-
/**
|
|
5
|
-
* `@check` — the list-of-lists form of `@permission`, deprecated in its
|
|
6
|
-
* favour and kept working.
|
|
7
|
-
*
|
|
8
|
-
* The declaration itself is SDL, in `graphql/directives/check.graphqls`, so it
|
|
9
|
-
* reaches every consumer of `SHARED_SCHEMA_PATH`; `CHECK_DIRECTIVE_SDL` is the
|
|
10
|
-
* same text as a string, held equal by a spec. This module only reads it.
|
|
11
|
-
*/
|
|
12
|
-
export declare const CHECK_DIRECTIVE_NAME = "check";
|
|
13
|
-
export type CheckDenial = 'NOT_FOUND' | 'FORBIDDEN';
|
|
14
|
-
export type CheckArgs = {
|
|
15
|
-
permissions: PermissionRequirement;
|
|
16
|
-
onDeny: CheckDenial;
|
|
17
|
-
/** i18n key; the shared `errors.*` one when the field does not say. */
|
|
18
|
-
message?: string;
|
|
19
|
-
};
|
|
20
|
-
/**
|
|
21
|
-
* Every `@check` on a field, in declaration order, validated.
|
|
22
|
-
*
|
|
23
|
-
* `@check` is repeatable, so this is a list and the order is load-bearing:
|
|
24
|
-
* `view` then `edit` is what turns a denial into 404 for a stranger and 403
|
|
25
|
-
* for a viewer.
|
|
26
|
-
*
|
|
27
|
-
* The `id` and `onDeny` defaults are applied here as well as in the SDL:
|
|
28
|
-
* graphql 17 no longer hands an input field's default to `getDirective`, and a
|
|
29
|
-
* term without an id would otherwise refuse the whole schema.
|
|
30
|
-
*/
|
|
31
|
-
export declare function readChecks(schema: GraphQLSchema, node: FieldNode, where: string, options?: ReadOptions): CheckArgs[];
|
|
32
|
-
//# sourceMappingURL=check.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/directives/check.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAkB,MAAM,eAAe,CAAC;AAC3E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAG7C,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,WAAW,EAAW,MAAM,SAAS,CAAC;AAGpE;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,UAAU,CAAC;AAE5C,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG,WAAW,CAAC;AAEpD,MAAM,MAAM,SAAS,GAAG;IACvB,WAAW,EAAE,qBAAqB,CAAC;IACnC,MAAM,EAAE,WAAW,CAAC;IACpB,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CACzB,MAAM,EAAE,aAAa,EACrB,IAAI,EAAE,SAAS,EACf,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,WAAgB,GACvB,SAAS,EAAE,CAgBb"}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* How a `@check` mistake that 2.x let boot is reported: a warning at build,
|
|
3
|
-
* naming the field, that says it becomes a refusal in the next major.
|
|
4
|
-
* `@permission` is new, so the same mistake there throws.
|
|
5
|
-
*/
|
|
6
|
-
export declare function warnCheckMistake(message: string): void;
|
|
7
|
-
//# sourceMappingURL=deprecation.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"deprecation.d.ts","sourceRoot":"","sources":["../../src/directives/deprecation.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,QAI/C"}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { GraphQLSchema } from 'graphql';
|
|
2
|
-
import { type CheckArgs } from './check';
|
|
3
|
-
import type { FieldNode, ReadOptions } from './scope';
|
|
4
|
-
/**
|
|
5
|
-
* Every `@check` and `@permission` on a field, validated, in the order they
|
|
6
|
-
* are written — across the two names, so a field migrating one directive at a
|
|
7
|
-
* time keeps its 404-then-403 ladder.
|
|
8
|
-
*
|
|
9
|
-
* `getDirective` answers per name, so the interleaving is read back off the
|
|
10
|
-
* field's AST. A field built without one (a schema made in code) has no order
|
|
11
|
-
* to keep across names, and gets its `@check`s first.
|
|
12
|
-
*/
|
|
13
|
-
export declare function readRequirements(schema: GraphQLSchema, node: FieldNode, where: string, options?: ReadOptions): CheckArgs[];
|
|
14
|
-
//# sourceMappingURL=requirements.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"requirements.d.ts","sourceRoot":"","sources":["../../src/directives/requirements.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,aAAa,EAAE,MAAM,SAAS,CAAC;AAC5D,OAAO,EAAwB,KAAK,SAAS,EAAc,MAAM,SAAS,CAAC;AAE3E,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEtD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC/B,MAAM,EAAE,aAAa,EACrB,IAAI,EAAE,SAAS,EACf,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,WAAgB,GACvB,SAAS,EAAE,CAeb"}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
# The Ory-native counterpart to @policy.
|
|
2
|
-
#
|
|
3
|
-
# @policy asks whether the caller CARRIES an authority. An Ory principal
|
|
4
|
-
# carries none — `toPrincipal` sets `authorities: []` on purpose — because
|
|
5
|
-
# Keto answers per OBJECT: "may this subject `view` `Note:<id>`". @check is how
|
|
6
|
-
# a schema asks that, and `useKetoChecks(ory)` is what answers it.
|
|
7
|
-
#
|
|
8
|
-
# It lives here, in the shipped SDL, rather than in SHARED_TYPE_DEFS, so that
|
|
9
|
-
# every consumer of SHARED_SCHEMA_PATH sees it — the subgraphs built with
|
|
10
|
-
# `buildSubgraphSchema` included, which never load SHARED_TYPE_DEFS.
|
|
11
|
-
|
|
12
|
-
"""
|
|
13
|
-
One question, with the object left as a path instead of a value.
|
|
14
|
-
"""
|
|
15
|
-
input CheckPermission {
|
|
16
|
-
"""
|
|
17
|
-
The Keto namespace, e.g. "Note" — from the stack's OPL document.
|
|
18
|
-
"""
|
|
19
|
-
namespace: String!
|
|
20
|
-
|
|
21
|
-
"""
|
|
22
|
-
The permit asked of it, e.g. "view". A plain relation works too; Keto
|
|
23
|
-
answers `false`, not an error, for a name it does not know.
|
|
24
|
-
"""
|
|
25
|
-
permit: String!
|
|
26
|
-
|
|
27
|
-
"""
|
|
28
|
-
Where the object id is read: `args.<path>`, or `parent.<path>` /
|
|
29
|
-
`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
|
|
30
|
-
element — that is what a mutation taking `ids: [ID!]!` means.
|
|
31
|
-
"""
|
|
32
|
-
id: String = "args.id"
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
"""
|
|
36
|
-
What a denial looks like from outside.
|
|
37
|
-
"""
|
|
38
|
-
enum CheckDenial {
|
|
39
|
-
"""
|
|
40
|
-
The same answer as for an id that never existed, so ids cannot be probed.
|
|
41
|
-
The default, and the one to keep unless the caller already knows the object
|
|
42
|
-
is there.
|
|
43
|
-
"""
|
|
44
|
-
NOT_FOUND
|
|
45
|
-
|
|
46
|
-
"""
|
|
47
|
-
For a second check on an object the caller can already see: a viewer asked
|
|
48
|
-
to edit already knows it exists, and "you may not change it" is honest.
|
|
49
|
-
"""
|
|
50
|
-
FORBIDDEN
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
"""
|
|
54
|
-
Deprecated: write `@permission(name:, type:)` in a new schema — one Keto
|
|
55
|
-
question per directive, repeated for AND, with OR kept in the Keto model. This
|
|
56
|
-
form keeps working and is evaluated in declaration order with @permission.
|
|
57
|
-
|
|
58
|
-
The permission a field requires, in disjunctive normal form: the OUTER list is
|
|
59
|
-
OR, the INNER list is AND. `[[A, B], [C]]` reads "(A and B) or C" — the same
|
|
60
|
-
shape `@policy(policies: [["ADMIN"]])` uses.
|
|
61
|
-
|
|
62
|
-
Repeatable, and evaluated in declaration order, each with its own `onDeny`.
|
|
63
|
-
That is how the 404-then-403 ladder is written:
|
|
64
|
-
|
|
65
|
-
updateNote(id: ID!, input: UpdateNoteInput!): Note!
|
|
66
|
-
@check(permissions: [[{ namespace: "Note", permit: "view" }]])
|
|
67
|
-
@check(permissions: [[{ namespace: "Note", permit: "edit" }]], onDeny: FORBIDDEN)
|
|
68
|
-
|
|
69
|
-
NOT for a field that answers a LIST the caller is entitled to. "Which notes may
|
|
70
|
-
I see" is not a check, it is a Keto query folded into the database filter
|
|
71
|
-
before the read. A directive there would have to fetch everything and filter
|
|
72
|
-
after, which makes `totalCount` and the cursors lie.
|
|
73
|
-
"""
|
|
74
|
-
directive @check(
|
|
75
|
-
permissions: [[CheckPermission!]!]!
|
|
76
|
-
onDeny: CheckDenial! = NOT_FOUND
|
|
77
|
-
|
|
78
|
-
"""
|
|
79
|
-
The i18n key the denial carries, e.g. "notes.errors.not-found". Defaults to
|
|
80
|
-
`errors.not-found` / `errors.insufficient-permissions`, the shared keys.
|
|
81
|
-
|
|
82
|
-
Set it whenever the API's own service layer answers the same refusal with a
|
|
83
|
-
domain message. Two layers guard these fields — the directive, and the
|
|
84
|
-
`require<M>Access` the service calls — and if they word the same 404
|
|
85
|
-
differently, the wording tells a caller WHICH one refused: a generic message
|
|
86
|
-
means "you may not", a domain one means "it is gone". That is precisely the
|
|
87
|
-
distinction NOT_FOUND exists to hide.
|
|
88
|
-
"""
|
|
89
|
-
message: String
|
|
90
|
-
) repeatable on FIELD_DEFINITION
|