@nxgt/shared-graphql 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +128 -73
- package/dist/directives/check.d.ts +13 -21
- package/dist/directives/check.d.ts.map +1 -1
- package/dist/directives/deprecation.d.ts +7 -0
- package/dist/directives/deprecation.d.ts.map +1 -0
- package/dist/directives/index.d.ts +5 -0
- package/dist/directives/index.d.ts.map +1 -1
- package/dist/directives/paths.d.ts +20 -0
- package/dist/directives/paths.d.ts.map +1 -0
- package/dist/directives/permission.d.ts +25 -0
- package/dist/directives/permission.d.ts.map +1 -0
- package/dist/directives/requirements.d.ts +14 -0
- package/dist/directives/requirements.d.ts.map +1 -0
- package/dist/directives/scope.d.ts +19 -0
- package/dist/directives/scope.d.ts.map +1 -0
- package/dist/directives/sdl.d.ts +16 -0
- package/dist/directives/sdl.d.ts.map +1 -0
- package/dist/directives/validate.d.ts +29 -0
- package/dist/directives/validate.d.ts.map +1 -0
- package/dist/index.js +495 -139
- package/dist/index.js.map +21 -8
- package/dist/plugins/apply-keto-checks.d.ts +17 -0
- package/dist/plugins/apply-keto-checks.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-checker.d.ts +24 -0
- package/dist/plugins/keto-checker.d.ts.map +1 -0
- package/dist/plugins/keto-checks.d.ts +8 -32
- package/dist/plugins/keto-checks.d.ts.map +1 -1
- package/dist/plugins/keto-helpers.d.ts +38 -0
- package/dist/plugins/keto-helpers.d.ts.map +1 -0
- package/dist/plugins/ory-auth.d.ts +2 -2
- package/dist/plugins/ory-auth.d.ts.map +1 -1
- package/dist/scalars/custom/utils.d.ts.map +1 -1
- package/dist/utils/errors/create-error.d.ts +3 -0
- package/dist/utils/errors/create-error.d.ts.map +1 -0
- package/dist/utils/errors/format-error.d.ts +11 -0
- package/dist/utils/errors/format-error.d.ts.map +1 -0
- package/dist/utils/errors/index.d.ts +5 -0
- package/dist/utils/errors/index.d.ts.map +1 -0
- package/dist/utils/errors/mask-error.d.ts +24 -0
- package/dist/utils/errors/mask-error.d.ts.map +1 -0
- package/dist/utils/errors/ory-unavailable.d.ts +19 -0
- package/dist/utils/errors/ory-unavailable.d.ts.map +1 -0
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/docs/README.md +11 -0
- package/docs/guide/errors.md +104 -0
- package/docs/guide/permissions.md +231 -0
- package/docs/roadmap.md +70 -0
- package/docs/troubleshooting.md +170 -0
- package/graphql/directives/check.graphqls +6 -2
- package/graphql/directives/permission.graphqls +73 -0
- package/package.json +11 -9
- package/dist/utils/errors.utils.d.ts +0 -23
- package/dist/utils/errors.utils.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -10,9 +10,16 @@ and the SDL every service merges into its own schema.
|
|
|
10
10
|
bun add @nxgt/shared-graphql
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
Public on npmjs; no token needed to install.
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
Public on npmjs; no token needed to install. Peers:
|
|
14
|
+
|
|
15
|
+
| Peer | Range | Why |
|
|
16
|
+
| --- | --- | --- |
|
|
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` |
|
|
19
|
+
| `stx-sdk` | `>=1.1.0` | `./security`'s policy types |
|
|
20
|
+
| `typescript` | `^6.0.3` | pinned across every `@nxgt/*` package — the set is unsatisfiable if one of them widens it |
|
|
21
|
+
|
|
22
|
+
The long version of each section below is in [`docs/`](./docs/README.md).
|
|
16
23
|
|
|
17
24
|
## Subpaths
|
|
18
25
|
|
|
@@ -31,9 +38,13 @@ walking up to the nearest `package.json` — the bundle is `dist/index.js`, the
|
|
|
31
38
|
source is `src/utils/schema.utils.ts`, and no single relative path serves both.
|
|
32
39
|
|
|
33
40
|
```ts
|
|
34
|
-
import {
|
|
41
|
+
import { fileURLToPath } from 'node:url';
|
|
42
|
+
import { loadTypeDefs, SHARED_SCHEMA_PATH } from '@nxgt/shared-graphql';
|
|
35
43
|
|
|
36
|
-
const typeDefs = loadTypeDefs(
|
|
44
|
+
const typeDefs = loadTypeDefs(
|
|
45
|
+
SHARED_SCHEMA_PATH,
|
|
46
|
+
fileURLToPath(new URL('../**/*.graphqls', import.meta.url)),
|
|
47
|
+
);
|
|
37
48
|
```
|
|
38
49
|
|
|
39
50
|
```ts
|
|
@@ -47,107 +58,148 @@ A relative path into this package's `src/` will not work from an install — it
|
|
|
47
58
|
is not published, and it was not there in the first place.
|
|
48
59
|
|
|
49
60
|
`SHARED_TYPE_DEFS` is a string of the shared directives (`@authenticated`,
|
|
50
|
-
`@policy`, `@shareable`, `@link`) plus empty root types.
|
|
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
|
|
51
63
|
builds through `buildSubgraphSchema` and never loads `SHARED_TYPE_DEFS` still
|
|
52
|
-
sees
|
|
53
|
-
|
|
54
|
-
|
|
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.
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import { KETO_DIRECTIVES_SDL } from '@nxgt/shared-graphql';
|
|
71
|
+
|
|
72
|
+
createSchema({ typeDefs: [KETO_DIRECTIVES_SDL, typeDefs], resolvers });
|
|
73
|
+
```
|
|
55
74
|
|
|
56
75
|
`buildSubgraphSchema` wraps Apollo's builder and prunes unused types.
|
|
57
76
|
|
|
58
|
-
## `@
|
|
77
|
+
## `@permission` — the permission a field requires
|
|
59
78
|
|
|
60
79
|
`@authenticated` asks whether anyone is calling. `@policy` asks whether they
|
|
61
80
|
*carry* an authority. Neither can ask what an Ory-native API needs to know:
|
|
62
81
|
**may this caller `view` `Note:n1`** — a question about one object, answered by
|
|
63
|
-
Keto.
|
|
64
|
-
|
|
65
|
-
`@check` asks it. The declaration ships in `graphql/directives/check.graphqls`,
|
|
66
|
-
so any schema built from `SHARED_SCHEMA_PATH` already has it; `useKetoChecks(ory)`
|
|
67
|
-
is what answers it.
|
|
82
|
+
Keto. `@permission` asks it; `useKetoChecks(ory)` answers it.
|
|
68
83
|
|
|
69
84
|
```graphql
|
|
70
|
-
note(id: ID!): Note!
|
|
71
|
-
@check(permissions: [[{ namespace: "Note", permit: "view" }]])
|
|
85
|
+
note(id: ID!): Note! @permission(name: "view", type: "Note")
|
|
72
86
|
|
|
73
87
|
updateNote(id: ID!, input: UpdateNoteInput!): Note!
|
|
74
|
-
@
|
|
75
|
-
@
|
|
88
|
+
@permission(name: "view", type: "Note")
|
|
89
|
+
@permission(name: "edit", type: "Note", onDeny: FORBIDDEN)
|
|
90
|
+
|
|
91
|
+
owner: Person @permission(name: "view", type: "Person", id: "parent.ownerId")
|
|
76
92
|
```
|
|
77
93
|
|
|
78
94
|
```ts
|
|
79
|
-
plugins: [useOryAuth(ory), useKetoChecks(ory), useGenericAuth({ … })]
|
|
95
|
+
plugins: [useOryAuth(ory), useKetoChecks(ory, { namespaces: ['Note', 'Person'] }), useGenericAuth({ … })]
|
|
80
96
|
```
|
|
81
97
|
|
|
82
|
-
`
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
98
|
+
- `name` is the permit, `type` the Keto namespace, `id` where the object id
|
|
99
|
+
is read: `args.<path>` (default `args.id`) or `parent.<path>`. A **list**
|
|
100
|
+
value requires the permit on every element.
|
|
101
|
+
- **Repeated, they are AND**, in declaration order, each with its own
|
|
102
|
+
`onDeny`: a stranger fails `view` and gets `NOT_FOUND` (the default), so an
|
|
103
|
+
id cannot be probed; a viewer passes it, fails `edit`, and gets `FORBIDDEN`.
|
|
104
|
+
- `message:` sets the i18n key a denial carries (shared `errors.not-found` /
|
|
105
|
+
`errors.insufficient-permissions` otherwise). Word it like the service
|
|
106
|
+
layer that guards the same object, or the wording tells a caller which
|
|
107
|
+
layer refused.
|
|
108
|
+
- An OR belongs in the Keto model — a permit that unions two relations — not
|
|
109
|
+
in the schema.
|
|
110
|
+
|
|
111
|
+
**Refused when the schema is built**, as a `TypeError` naming `Type.field`: a
|
|
112
|
+
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.
|
|
117
|
+
|
|
118
|
+
`@permission` is read only when its declaration has `name` and `type` — a
|
|
119
|
+
schema with its own `@permission` of another shape keeps it. Loading this
|
|
120
|
+
package's `graphql/` next to such a declaration merges the two, though: see
|
|
121
|
+
[troubleshooting](./docs/troubleshooting.md).
|
|
122
|
+
|
|
123
|
+
**An outage is never a denial.** A Keto failure throws `OryUnavailable`, which
|
|
124
|
+
`createMaskError` answers `503 SERVICE_UNAVAILABLE`.
|
|
125
|
+
|
|
126
|
+
Every question goes through a per-request memo: distinct questions are
|
|
127
|
+
batched into one `POST /relation-tuples/batch/check`, identical ones asked
|
|
128
|
+
once, a failed batch not remembered.
|
|
129
|
+
|
|
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.
|
|
104
136
|
|
|
105
137
|
```graphql
|
|
106
|
-
note(id: ID!): Note!
|
|
107
|
-
@check(
|
|
108
|
-
permissions: [[{ namespace: "Note", permit: "view" }]]
|
|
109
|
-
message: "notes.errors.not-found"
|
|
110
|
-
)
|
|
138
|
+
note(id: ID!): Note! @check(permissions: [[{ namespace: "Note", permit: "view" }]])
|
|
111
139
|
```
|
|
112
140
|
|
|
113
|
-
###
|
|
141
|
+
### From a resolver: `requireUser` and `can`
|
|
114
142
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
143
|
+
```ts
|
|
144
|
+
import { can, type OryGraphQLContext, requireUser } from '@nxgt/shared-graphql';
|
|
145
|
+
import { CustomException } from '@nxgt/shared-exceptions';
|
|
146
|
+
|
|
147
|
+
async function archive(_: unknown, { id }: { id: string }, ctx: OryGraphQLContext) {
|
|
148
|
+
const user = requireUser(ctx); // UNAUTHENTICATED (401) when nobody is calling
|
|
149
|
+
if (!(await can(ctx, { name: 'edit', type: 'Note', id }))) {
|
|
150
|
+
throw CustomException.forbidden({ message: 'notes.errors.read-only' });
|
|
151
|
+
}
|
|
152
|
+
return notes.archive(id, user.sub);
|
|
153
|
+
}
|
|
154
|
+
```
|
|
120
155
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
`POST /relation-tuples/batch/check` and **memoises** identical ones. A field
|
|
124
|
-
guarded by `@check(view)` and a service that then asks the same question pay
|
|
125
|
-
for one round trip between them.
|
|
156
|
+
`can` answers Keto's `true` or `false` through the same memo as the
|
|
157
|
+
directives; an outage throws, never answers `false`.
|
|
126
158
|
|
|
127
|
-
|
|
128
|
-
an `id` naming neither root, and `permissions: [[]]` — a conjunction over no
|
|
129
|
-
terms is vacuously true, so it would admit everyone while looking guarded.
|
|
159
|
+
### What it does not cover, on purpose
|
|
130
160
|
|
|
131
|
-
A
|
|
132
|
-
|
|
161
|
+
A field that answers a **list** the caller is entitled to. "Which notes may I
|
|
162
|
+
see" is not a check — it is a Keto query folded into the database filter
|
|
163
|
+
before the read. A directive there would have to fetch everything and filter
|
|
164
|
+
after, which makes `totalCount` and the cursors lie.
|
|
133
165
|
|
|
134
166
|
### A shipped directive is not a composed directive
|
|
135
167
|
|
|
136
168
|
Shipping the SDL is enough for a standalone Yoga schema. It is **not** enough
|
|
137
|
-
for a subgraph that federation composes
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
169
|
+
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.
|
|
174
|
+
|
|
175
|
+
## Errors
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
createYoga({ maskedErrors: { maskError: createMaskError(translate) } });
|
|
179
|
+
new ApolloServer({ formatError: createFormatError(translate) });
|
|
180
|
+
```
|
|
181
|
+
|
|
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.
|
|
188
|
+
|
|
189
|
+
Under Apollo, `createFormatError` sets the `code` and the translated message
|
|
190
|
+
(and `SERVICE_UNAVAILABLE` for an outage, with `http.status` in `extensions`
|
|
191
|
+
only — `formatError` cannot change the transport status). It masks nothing
|
|
192
|
+
Apollo would not.
|
|
142
193
|
|
|
143
194
|
## Plugins and context
|
|
144
195
|
|
|
145
196
|
| Export | What it does |
|
|
146
197
|
| --- | --- |
|
|
147
|
-
| `useOryAuth(ory)` | Yoga plugin: resolve the caller, set `
|
|
148
|
-
| `useKetoChecks(ory)` | Yoga plugin: per-request
|
|
149
|
-
| `applyKetoChecks(schema)` |
|
|
150
|
-
| `
|
|
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 |
|
|
200
|
+
| `applyKetoChecks(schema, options?)` | the same transform on an already-built schema |
|
|
201
|
+
| `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 |
|
|
151
203
|
| `extractJwtPlugin` | Apollo plugin: copy `request.extensions.payload` onto `context.jwt` |
|
|
152
204
|
|
|
153
205
|
`GraphQLBaseContext.user` is `TokenPrincipal` — the caller as the access token
|
|
@@ -164,7 +216,10 @@ SDL.
|
|
|
164
216
|
|
|
165
217
|
## Things that bite
|
|
166
218
|
|
|
167
|
-
- **`@
|
|
219
|
+
- **`@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)`.
|
|
168
223
|
- **Do not import `graphql-subscriptions` from `graphql-subscriptions`.** Take
|
|
169
224
|
it from this package, same reason mongoose comes from `@nxgt/shared-mongo`.
|
|
170
225
|
- **The sandbox helper is spelled `sandboxExpolorer`.** That is the export
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { type PermissionRequirement, type PermissionTerm } from '@nxgt/ory-sdk';
|
|
1
|
+
import type { PermissionRequirement } from '@nxgt/ory-sdk';
|
|
3
2
|
import type { GraphQLSchema } from 'graphql';
|
|
3
|
+
import { type FieldNode, type ReadOptions } from './scope';
|
|
4
4
|
/**
|
|
5
|
-
* `@check` — the
|
|
5
|
+
* `@check` — the list-of-lists form of `@permission`, deprecated in its
|
|
6
|
+
* favour and kept working.
|
|
6
7
|
*
|
|
7
8
|
* The declaration itself is SDL, in `graphql/directives/check.graphqls`, so it
|
|
8
|
-
* reaches every consumer of `SHARED_SCHEMA_PATH
|
|
9
|
-
*
|
|
10
|
-
* the copy nobody edits is the one that goes wrong.
|
|
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
11
|
*/
|
|
12
12
|
export declare const CHECK_DIRECTIVE_NAME = "check";
|
|
13
13
|
export type CheckDenial = 'NOT_FOUND' | 'FORBIDDEN';
|
|
@@ -18,23 +18,15 @@ export type CheckArgs = {
|
|
|
18
18
|
message?: string;
|
|
19
19
|
};
|
|
20
20
|
/**
|
|
21
|
-
* Every `@check` on a field, in declaration order.
|
|
21
|
+
* Every `@check` on a field, in declaration order, validated.
|
|
22
22
|
*
|
|
23
23
|
* `@check` is repeatable, so this is a list and the order is load-bearing:
|
|
24
24
|
* `view` then `edit` is what turns a denial into 404 for a stranger and 403
|
|
25
|
-
* for a viewer.
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Validated here, when the schema is transformed, and not when a request
|
|
32
|
-
* arrives: a path naming neither root is a wiring mistake, and it should stop
|
|
33
|
-
* the server from booting rather than 500 on the one query nobody tried.
|
|
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.
|
|
34
30
|
*/
|
|
35
|
-
export declare function
|
|
36
|
-
/** Reads `args.x` / `source.x.y` off a resolver's inputs. */
|
|
37
|
-
export declare function readPath(path: string, source: unknown, args: Record<string, unknown>): unknown;
|
|
38
|
-
/** The ids one term has to clear: one value, or every element of a list. */
|
|
39
|
-
export declare function objectIds(value: unknown): string[];
|
|
31
|
+
export declare function readChecks(schema: GraphQLSchema, node: FieldNode, where: string, options?: ReadOptions): CheckArgs[];
|
|
40
32
|
//# sourceMappingURL=check.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/directives/check.ts"],"names":[],"mappings":"
|
|
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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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,3 +1,8 @@
|
|
|
1
1
|
export * from './check';
|
|
2
2
|
export * from './federation';
|
|
3
|
+
export { assertReadablePath, objectIds, readPath } from './paths';
|
|
4
|
+
export * from './permission';
|
|
5
|
+
export * from './requirements';
|
|
6
|
+
export type { ReadOptions } from './scope';
|
|
7
|
+
export * from './sdl';
|
|
3
8
|
//# 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"}
|
|
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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { PermissionTerm } from '@nxgt/ory-sdk';
|
|
2
|
+
/** The id a term reads when it names none. */
|
|
3
|
+
export declare const DEFAULT_ID_PATH = "args.id";
|
|
4
|
+
/**
|
|
5
|
+
* Validated when the schema is transformed, and not when a request arrives: a
|
|
6
|
+
* path naming no root is a wiring mistake, and it should stop the server from
|
|
7
|
+
* booting rather than 500 on the one query nobody tried.
|
|
8
|
+
*/
|
|
9
|
+
export declare function assertReadablePath(term: PermissionTerm, where: string): void;
|
|
10
|
+
/**
|
|
11
|
+
* `args.<name>` must name an argument the field declares. Graphql-js never
|
|
12
|
+
* puts an undeclared argument on `args`, so a typo here resolves no object on
|
|
13
|
+
* every request — refused at build instead, naming the arguments there are.
|
|
14
|
+
*/
|
|
15
|
+
export declare function assertDeclaredArgument(term: PermissionTerm, argumentNames: readonly string[], where: string): void;
|
|
16
|
+
/** Reads `args.x` / `parent.x.y` / `source.x.y` off a resolver's inputs. */
|
|
17
|
+
export declare function readPath(path: string, source: unknown, args: Record<string, unknown>): unknown;
|
|
18
|
+
/** The ids one term has to clear: one value, or every element of a list. */
|
|
19
|
+
export declare function objectIds(value: unknown): string[];
|
|
20
|
+
//# sourceMappingURL=paths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/directives/paths.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAYpD,8CAA8C;AAC9C,eAAO,MAAM,eAAe,YAAY,CAAC;AAEzC;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,QAMrE;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACrC,IAAI,EAAE,cAAc,EACpB,aAAa,EAAE,SAAS,MAAM,EAAE,EAChC,KAAK,EAAE,MAAM,QAUb;AAED,4EAA4E;AAC5E,wBAAgB,QAAQ,CACvB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,OAAO,EACf,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC3B,OAAO,CAOT;AAED,4EAA4E;AAC5E,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAKlD"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { GraphQLSchema } from 'graphql';
|
|
2
|
+
import type { CheckArgs, CheckDenial } from './check';
|
|
3
|
+
import { type FieldNode, type ReadOptions } from './scope';
|
|
4
|
+
/**
|
|
5
|
+
* `@permission(name:, type:, id:, onDeny:, message:)` — one Keto question per
|
|
6
|
+
* directive, the vocabulary `@nxgt/janus-graphql` uses. Declared in
|
|
7
|
+
* `graphql/directives/permission.graphqls` and `PERMISSION_DIRECTIVE_SDL`.
|
|
8
|
+
*/
|
|
9
|
+
export declare const PERMISSION_DIRECTIVE_NAME = "permission";
|
|
10
|
+
export type PermissionDenial = CheckDenial;
|
|
11
|
+
/** One `@permission` as written, after its defaults. */
|
|
12
|
+
export type PermissionArgs = {
|
|
13
|
+
name: string;
|
|
14
|
+
type: string;
|
|
15
|
+
id: string;
|
|
16
|
+
onDeny: PermissionDenial;
|
|
17
|
+
message?: string;
|
|
18
|
+
};
|
|
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.
|
|
23
|
+
*/
|
|
24
|
+
export declare function readPermissions(schema: GraphQLSchema, node: FieldNode, where: string, options?: ReadOptions): CheckArgs[];
|
|
25
|
+
//# sourceMappingURL=permission.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { getDirective } from '@graphql-tools/utils';
|
|
2
|
+
import type { RequirementScope } from './validate';
|
|
3
|
+
/** A field definition or a field config — whatever `getDirective` reads. */
|
|
4
|
+
export type FieldNode = Parameters<typeof getDirective>[1];
|
|
5
|
+
/** What a schema may add to the build-time checks of `@check` and `@permission`. */
|
|
6
|
+
export type ReadOptions = {
|
|
7
|
+
/**
|
|
8
|
+
* The namespaces of the stack's OPL document. When given, a `type` or
|
|
9
|
+
* `namespace` outside it is refused at build.
|
|
10
|
+
*/
|
|
11
|
+
namespaces?: readonly string[];
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* The scope a field's requirements are validated in: the message prefix, the
|
|
15
|
+
* arguments the field declares — read from a field config's `args` map or a
|
|
16
|
+
* definition node's `arguments` list — and the known namespaces.
|
|
17
|
+
*/
|
|
18
|
+
export declare function scopeOf(node: FieldNode, where: string, options: ReadOptions): RequirementScope;
|
|
19
|
+
//# sourceMappingURL=scope.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The directive declarations `graphql/directives/*.graphqls` ship, as strings,
|
|
3
|
+
* for a schema assembled in code rather than loaded from `SHARED_SCHEMA_PATH`
|
|
4
|
+
* — an edge runtime with no file system, or a test.
|
|
5
|
+
*
|
|
6
|
+
* The files are the source; these are copies of their declarations, without
|
|
7
|
+
* the `#` comments, and `sdl.spec.ts` holds each equal to its file once both
|
|
8
|
+
* are parsed and printed. Edit the file, then this.
|
|
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
|
+
/** `@permission` and its `PermissionDenial` enum. */
|
|
13
|
+
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";
|
|
16
|
+
//# sourceMappingURL=sdl.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type PermissionRequirement } from '@nxgt/ory-sdk';
|
|
2
|
+
/**
|
|
3
|
+
* What a requirement is checked against when the schema is built.
|
|
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.
|
|
9
|
+
*/
|
|
10
|
+
export type RequirementScope = {
|
|
11
|
+
/** `@check on Query.note` — the directive and the field, for the message. */
|
|
12
|
+
where: string;
|
|
13
|
+
argumentNames: readonly string[];
|
|
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
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* 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.
|
|
27
|
+
*/
|
|
28
|
+
export declare function assertRequirementShape(permissions: PermissionRequirement, scope: RequirementScope): void;
|
|
29
|
+
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -0,0 +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"}
|