@nxgt/janus-graphql 0.1.1 → 0.3.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 +110 -21
- package/dist/connection/connection.d.ts +36 -0
- package/dist/connection/connection.d.ts.map +1 -0
- package/dist/connection/credential.d.ts +34 -0
- package/dist/connection/credential.d.ts.map +1 -0
- package/dist/connection/types.d.ts +57 -0
- package/dist/connection/types.d.ts.map +1 -0
- package/dist/context.d.ts +11 -2
- package/dist/context.d.ts.map +1 -1
- package/dist/directives/apply.d.ts +10 -9
- package/dist/directives/apply.d.ts.map +1 -1
- package/dist/directives/guard.d.ts +2 -1
- package/dist/directives/guard.d.ts.map +1 -1
- package/dist/directives/read.d.ts +9 -1
- package/dist/directives/read.d.ts.map +1 -1
- package/dist/directives/validate.d.ts +7 -2
- package/dist/directives/validate.d.ts.map +1 -1
- package/dist/fresh.d.ts +37 -0
- package/dist/fresh.d.ts.map +1 -0
- package/dist/index.d.ts +12 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +170 -23
- package/dist/index.js.map +14 -10
- package/dist/options.d.ts +17 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/plugin.d.ts +8 -3
- package/dist/plugin.d.ts.map +1 -1
- package/dist/sdl.d.ts.map +1 -1
- package/dist/types.d.ts +6 -1
- package/dist/types.d.ts.map +1 -1
- package/docs/README.md +5 -3
- package/docs/guide/context.md +36 -3
- package/docs/guide/directives.md +64 -5
- package/docs/guide/errors.md +2 -1
- package/docs/guide/federation.md +2 -1
- package/docs/guide/step-up.md +186 -0
- package/docs/guide/subscriptions.md +251 -0
- package/docs/roadmap.md +29 -9
- package/docs/troubleshooting.md +218 -11
- package/graphql/janus.graphqls +8 -0
- package/package.json +11 -2
package/docs/troubleshooting.md
CHANGED
|
@@ -15,6 +15,7 @@ for what causes each.
|
|
|
15
15
|
- [`TypeError: applyJanusDirectives(): @authenticated on … names the user type '…', which is not one of …`](#typeerror-applyjanusdirectives-authenticated-on--names-the-user-type--which-is-not-one-of-)
|
|
16
16
|
- [`TypeError: applyJanusDirectives(): @authenticated on … names no user type`](#typeerror-applyjanusdirectives-authenticated-on--names-no-user-type)
|
|
17
17
|
- [`TypeError: applyJanusDirectives(): the @authenticated on … admit no user type in common`](#typeerror-applyjanusdirectives-the-authenticated-on--admit-no-user-type-in-common)
|
|
18
|
+
- [`TypeError: applyJanusDirectives(): @fresh on … asks maxAge: …`](#typeerror-applyjanusdirectives-fresh-on--asks-maxage-)
|
|
18
19
|
- [`TypeError: applyJanusDirectives(): @permission on … needs the permissions() instance`](#typeerror-applyjanusdirectives-permission-on--needs-the-permissions-instance)
|
|
19
20
|
- [`TypeError: applyJanusDirectives(): @permission on … names the object type '…', which is not one of …`](#typeerror-applyjanusdirectives-permission-on--names-the-object-type--which-is-not-one-of-)
|
|
20
21
|
- [`TypeError: applyJanusDirectives(): @permission on … asks '…', which … does not declare`](#typeerror-applyjanusdirectives-permission-on--asks--which--does-not-declare)
|
|
@@ -23,20 +24,35 @@ for what causes each.
|
|
|
23
24
|
- [`TypeError: applyJanusDirectives(): @permission on … reads the …'s id from …, and … reads '…' of the object itself (fromField)`](#typeerror-applyjanusdirectives-permission-on--reads-the-s-id-from--and--reads--of-the-object-itself-fromfield)
|
|
24
25
|
- [`TypeError: applyJanusDirectives(): @permission on … asks '…' of …, which reaches a when()`](#typeerror-applyjanusdirectives-permission-on--asks--of--which-reaches-a-when)
|
|
25
26
|
- [`TypeError: applyJanusDirectives(): @permission on … finds loaders.…, which is not a function`](#typeerror-applyjanusdirectives-permission-on--finds-loaders-which-is-not-a-function)
|
|
26
|
-
- [`Unknown directive "@authenticated"`, or `"@permission"`](#unknown-directive-authenticated-or-permission)
|
|
27
|
+
- [`Unknown directive "@authenticated"`, `"@fresh"` or `"@permission"`](#unknown-directive-authenticated-fresh-or-permission)
|
|
28
|
+
- [`Argument "@fresh(maxAge:)" of type "Int!" is required`, or `Argument "maxAge" has invalid value …`](#argument-freshmaxage-of-type-int-is-required-or-argument-maxage-has-invalid-value-)
|
|
27
29
|
- [`TypeError: applyJanusDirectives(): type '…' is not a user type of auth`](#typeerror-applyjanusdirectives-type--is-not-a-user-type-of-auth)
|
|
28
30
|
- [`TypeError: useJanus(): auth is not what janus() answered`](#typeerror-usejanus-auth-is-not-what-janus-answered)
|
|
29
31
|
- [`TypeError: useJanus(): access is not what permissions() answered`](#typeerror-usejanus-access-is-not-what-permissions-answered)
|
|
32
|
+
- [`TypeError: useJanus(): clock is not a Clock`](#typeerror-usejanus-clock-is-not-a-clock)
|
|
33
|
+
- [`TypeError: janusConnection(): auth is not what janus() answered`, `access …`, `clock …`](#typeerror-janusconnection-auth-is-not-what-janus-answered-access--clock-)
|
|
34
|
+
- [`TypeError: janusConnection(): upgrade is not a function`](#typeerror-janusconnection-upgrade-is-not-a-function)
|
|
35
|
+
|
|
36
|
+
**Over graphql-ws**
|
|
37
|
+
- [The socket closed `4403: Forbidden` right after connecting](#the-socket-closed-4403-forbidden-right-after-connecting)
|
|
38
|
+
- [The socket closed `4500`](#the-socket-closed-4500)
|
|
39
|
+
- [A stream keeps its events after its session was revoked](#a-stream-keeps-its-events-after-its-session-was-revoked)
|
|
40
|
+
- [`TypeError: janusConnection(): the connection's extra is not an object`](#typeerror-janusconnection-the-connections-extra-is-not-an-object)
|
|
30
41
|
|
|
31
42
|
**In a response**
|
|
32
43
|
- [`UNAUTHENTICATED` for a signed-in user](#unauthenticated-for-a-signed-in-user)
|
|
33
44
|
- [`FORBIDDEN` where the user should be allowed](#forbidden-where-the-user-should-be-allowed)
|
|
45
|
+
- [`STEP_UP_REQUIRED`, 403](#step_up_required-403)
|
|
46
|
+
- [`STEP_UP_REQUIRED` for a user who just signed in](#step_up_required-for-a-user-who-just-signed-in)
|
|
34
47
|
- [`SERVICE_UNAVAILABLE`, 503, on every guarded field](#service_unavailable-503-on-every-guarded-field)
|
|
35
48
|
- [`Unexpected error.`, 500, where a `JanusError` was thrown](#unexpected-error-500-where-a-januserror-was-thrown)
|
|
36
49
|
- [`TypeError: … ctx.janus is not set`](#typeerror--ctxjanus-is-not-set)
|
|
37
|
-
- [`TypeError:
|
|
50
|
+
- [`TypeError: …: the GraphQL context has no request to authenticate`](#typeerror--the-graphql-context-has-no-request-to-authenticate)
|
|
38
51
|
- [`TypeError: can(): ctx.janus.access is not set`](#typeerror-can-ctxjanusaccess-is-not-set)
|
|
39
52
|
- [`TypeError: requireUser(): type is an empty list, which no user could pass`](#typeerror-requireuser-type-is-an-empty-list-which-no-user-could-pass)
|
|
53
|
+
- [`TypeError: requireFresh(): maxAge is a duration with its unit`](#typeerror-requirefresh-maxage-is-a-duration-with-its-unit)
|
|
54
|
+
- [`TypeError: requireFresh(): maxAge: "…" is not a duration`](#typeerror-requirefresh-maxage--is-not-a-duration)
|
|
55
|
+
- [`TypeError: requireFresh(): maxAge: a duration must be above zero`](#typeerror-requirefresh-maxage-a-duration-must-be-above-zero)
|
|
40
56
|
- [`@permission on … resolved no object id from … — answered NOT_FOUND`](#permission-on--resolved-no-object-id-from---answered-not_found)
|
|
41
57
|
- [`NOT_FOUND` where the user should be allowed](#not_found-where-the-user-should-be-allowed)
|
|
42
58
|
- [A 403 that tells a user the object exists](#a-403-that-tells-a-user-the-object-exists)
|
|
@@ -52,6 +68,9 @@ for what causes each.
|
|
|
52
68
|
- [`'…' does not exist in type 'Loaders<…>'`, or `'Conditions<…>'`](#-does-not-exist-in-type-loaders-or-conditions)
|
|
53
69
|
- [`Type '{ …: … }' is not assignable to type 'never'`, on `loaders`](#type-----is-not-assignable-to-type-never-on-loaders)
|
|
54
70
|
- [`Source has 0 element(s) but target requires 1`, on `requireUser()`](#source-has-0-elements-but-target-requires-1-on-requireuser)
|
|
71
|
+
- [`Argument of type '600' is not assignable to parameter of type '`${number}ms` | …'`, on `requireFresh()`](#argument-of-type-600-is-not-assignable-to-parameter-of-type-numberms---on-requirefresh)
|
|
72
|
+
- [`Expected 2 arguments, but got 1`, on `requireFresh()`](#expected-2-arguments-but-got-1-on-requirefresh)
|
|
73
|
+
- [`Type 'number' is not assignable to type 'Clock'`](#type-number-is-not-assignable-to-type-clock)
|
|
55
74
|
|
|
56
75
|
## At start-up
|
|
57
76
|
|
|
@@ -95,6 +114,24 @@ type Ward @authenticated(type: ["staff"]) {
|
|
|
95
114
|
**Fix:** widen one of them, or move the field to a type whose directive
|
|
96
115
|
admits the users it is for.
|
|
97
116
|
|
|
117
|
+
### `TypeError: applyJanusDirectives(): @fresh on … asks maxAge: …`
|
|
118
|
+
|
|
119
|
+
**When:** at start-up, for a schema that uses `@fresh`.
|
|
120
|
+
|
|
121
|
+
**Why:** `maxAge` is not above zero — `@fresh(maxAge: 0)` or a negative
|
|
122
|
+
number — so no session could ever be fresh enough, and every request would
|
|
123
|
+
be refused. The message names the field, and the location the directive was
|
|
124
|
+
written on when it is not the field: `@fresh on Account (read by
|
|
125
|
+
Account.email)`.
|
|
126
|
+
|
|
127
|
+
**Fix:** write a number of **seconds** above zero:
|
|
128
|
+
|
|
129
|
+
```graphql
|
|
130
|
+
type Mutation {
|
|
131
|
+
changeEmail(email: String!): User @fresh(maxAge: 600) # ten minutes
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
98
135
|
### `TypeError: applyJanusDirectives(): @permission on … needs the permissions() instance`
|
|
99
136
|
|
|
100
137
|
**When:** at start-up, for a schema that uses `@permission`.
|
|
@@ -192,7 +229,7 @@ than a function of it, from JavaScript or past a cast.
|
|
|
192
229
|
|
|
193
230
|
**Fix:** `loaders: { record: (id) => records.find(id) }`.
|
|
194
231
|
|
|
195
|
-
### `Unknown directive "@authenticated"`, or `"@permission"`
|
|
232
|
+
### `Unknown directive "@authenticated"`, `"@fresh"` or `"@permission"`
|
|
196
233
|
|
|
197
234
|
**Why:** graphql-js builds the schema before `useJanus()` sees it, and the
|
|
198
235
|
directive is not declared.
|
|
@@ -202,6 +239,18 @@ directive is not declared.
|
|
|
202
239
|
generator reading files takes
|
|
203
240
|
`node_modules/@nxgt/janus-graphql/graphql/janus.graphqls`.
|
|
204
241
|
|
|
242
|
+
### `Argument "@fresh(maxAge:)" of type "Int!" is required`, or `Argument "maxAge" has invalid value …`
|
|
243
|
+
|
|
244
|
+
graphql 16 words the first `Directive "@fresh" argument "maxAge" of type
|
|
245
|
+
"Int!" is required, but it was not provided.`
|
|
246
|
+
|
|
247
|
+
**Why:** graphql-js refuses the directive at start-up: `@fresh` without
|
|
248
|
+
`maxAge`, when the schema is built; or with a value that is not an `Int` —
|
|
249
|
+
a fraction, a string such as `"10m"` — when `useJanus()` reads it.
|
|
250
|
+
|
|
251
|
+
**Fix:** a whole number of seconds: `@fresh(maxAge: 600)`. A duration with a
|
|
252
|
+
unit belongs to `requireFresh(ctx, '10m')`, in a resolver.
|
|
253
|
+
|
|
205
254
|
### `TypeError: applyJanusDirectives(): type '…' is not a user type of auth`
|
|
206
255
|
|
|
207
256
|
**When:** at start-up, with `useJanus({ auth, type })` or
|
|
@@ -228,6 +277,83 @@ its place, or the module that exports `auth` not loaded yet.
|
|
|
228
277
|
**Fix:** `useJanus({ auth, access })`, with `access` what `permissions()`
|
|
229
278
|
answered — or leave `access` out when no field uses `@permission` or `can()`.
|
|
230
279
|
|
|
280
|
+
### `TypeError: useJanus(): clock is not a Clock`
|
|
281
|
+
|
|
282
|
+
**Why:** `clock` has no `now()` — a timestamp such as `Date.now()`, or a
|
|
283
|
+
`Date`, passed from JavaScript or cast past the compiler.
|
|
284
|
+
|
|
285
|
+
**Fix:** pass the clock given to `janus()` — `fixedClock(…)` from
|
|
286
|
+
`@nxgt/janus` in a spec — or leave `clock` out for the system's:
|
|
287
|
+
`useJanus({ auth, clock })`.
|
|
288
|
+
|
|
289
|
+
### `TypeError: janusConnection(): auth is not what janus() answered`, `access …`, `clock …`
|
|
290
|
+
|
|
291
|
+
**Why:** the same checks as `useJanus()`'s, named for `janusConnection()`:
|
|
292
|
+
`auth` is not what `janus()` answered, `access` not what `permissions()`
|
|
293
|
+
answered, `clock` not a `Clock`.
|
|
294
|
+
|
|
295
|
+
**Fix:** `janusConnection({ auth })`, with the `access` and `clock` given to
|
|
296
|
+
`useJanus()` when you pass them at all.
|
|
297
|
+
|
|
298
|
+
### `TypeError: janusConnection(): upgrade is not a function`
|
|
299
|
+
|
|
300
|
+
**Why:** `upgrade` is how `onConnect` finds the upgrade request on a
|
|
301
|
+
transport whose `extra` does not carry it, and it was given something else.
|
|
302
|
+
|
|
303
|
+
**Fix:** a function of graphql-ws's context, or nothing — `ctx.extra.request`
|
|
304
|
+
is then read, as `graphql-ws/use/ws` sets it:
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
janusConnection({ auth, upgrade: (ctx: { readonly extra: { readonly socket: Upgraded } }) => ctx.extra.socket.data.request });
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## Over graphql-ws
|
|
311
|
+
|
|
312
|
+
### The socket closed `4403: Forbidden` right after connecting
|
|
313
|
+
|
|
314
|
+
**Why:** `onConnect` found no credential that authenticates: none at all,
|
|
315
|
+
an unknown or lapsed token, a user gone or inactive, a user of another
|
|
316
|
+
type than `janusConnection({ type })`, or a `connectionParams.authorization`
|
|
317
|
+
that is not a string. A `Bearer` `connectionParams.authorization` is
|
|
318
|
+
read before the upgrade request, and **the first present wins**: a lapsed
|
|
319
|
+
token there refuses the connection even beside a live cookie. One of
|
|
320
|
+
another scheme (`Basic …`) does not count, and the cookie is read.
|
|
321
|
+
|
|
322
|
+
**Fix:** send `connectionParams: { authorization: 'Bearer <token>' }` —
|
|
323
|
+
the key lower-case — or connect from a browser holding the session cookie,
|
|
324
|
+
to the origin that set it. On Bun, pass `upgrade`, or the cookie is never
|
|
325
|
+
read. graphql-ws's client retries a `4403`; a `connectionParams` function
|
|
326
|
+
is asked again each time.
|
|
327
|
+
|
|
328
|
+
### The socket closed `4500`
|
|
329
|
+
|
|
330
|
+
**Why:** the sessions store could not answer while `onConnect`
|
|
331
|
+
authenticated — `STORE_FAILED`. `graphql-ws/use/ws` closes the socket
|
|
332
|
+
`4500` and logs the error with `console.error`. It is an outage, **never a
|
|
333
|
+
refusal**: `4403` would tell the client its session is bad.
|
|
334
|
+
|
|
335
|
+
**Fix:** the store. graphql-ws's client does not retry a `4500` by default;
|
|
336
|
+
set `shouldRetry` to retry an outage.
|
|
337
|
+
|
|
338
|
+
### A stream keeps its events after its session was revoked
|
|
339
|
+
|
|
340
|
+
**Why:** an operation is authenticated once, when it subscribes, and
|
|
341
|
+
each event's `@authenticated` reads that answer. The next operation on
|
|
342
|
+
the connection is refused `UNAUTHENTICATED`, and a reconnection `4403`.
|
|
343
|
+
|
|
344
|
+
**Fix:** close the session's sockets when you revoke it
|
|
345
|
+
([the subscriptions guide](guide/subscriptions.md#a-session-revoked-while-connected)).
|
|
346
|
+
A permission revoked needs nothing: `@permission` asks again on every event.
|
|
347
|
+
|
|
348
|
+
### `TypeError: janusConnection(): the connection's extra is not an object`
|
|
349
|
+
|
|
350
|
+
**Why:** `onConnect` was called with a context whose `extra` is not an
|
|
351
|
+
object — by hand, or by a transport of your own. It remembers the
|
|
352
|
+
connection by that object.
|
|
353
|
+
|
|
354
|
+
**Fix:** pass `onConnect` to graphql-ws's `useServer()` or `makeHandler()`,
|
|
355
|
+
which give it the context they made.
|
|
356
|
+
|
|
231
357
|
## In a response
|
|
232
358
|
|
|
233
359
|
### `UNAUTHENTICATED` for a signed-in user
|
|
@@ -251,12 +377,37 @@ says applies to it too.
|
|
|
251
377
|
**Fix:** read the directives on the field, on its type, and on every
|
|
252
378
|
interface the type implements.
|
|
253
379
|
|
|
380
|
+
### `STEP_UP_REQUIRED`, 403
|
|
381
|
+
|
|
382
|
+
**Why:** a field `@fresh(maxAge)` guards, or a `requireFresh(ctx, maxAge)`,
|
|
383
|
+
was reached by a session that proved who it is `maxAge` ago or more — at
|
|
384
|
+
its sign-in, or at its last `auth.stepUp.confirm`. Renewing a sliding
|
|
385
|
+
session does not count. This is the answer working: it tells the client to
|
|
386
|
+
ask for a step-up.
|
|
387
|
+
|
|
388
|
+
**Fix:** in the client, on `extensions.code === 'STEP_UP_REQUIRED'`, run the
|
|
389
|
+
step-up — `requestStepUp`, then `confirmStepUp(challenge, code)` — and send
|
|
390
|
+
the request again. The mutations are in
|
|
391
|
+
[the step-up over GraphQL](guide/step-up.md).
|
|
392
|
+
|
|
393
|
+
### `STEP_UP_REQUIRED` for a user who just signed in
|
|
394
|
+
|
|
395
|
+
**Why:** `@fresh` reads the time from `useJanus({ clock })`, and the session
|
|
396
|
+
was stamped with `janus()`'s. Two different clocks: `janus({ clock:
|
|
397
|
+
fixedClock(…) })` with `useJanus()` given none, in a spec — the session looks
|
|
398
|
+
days old. Or `maxAge` written as minutes: `@fresh(maxAge: 10)` is ten
|
|
399
|
+
seconds, not ten minutes.
|
|
400
|
+
|
|
401
|
+
**Fix:** give both the same clock — `useJanus({ auth, clock })` — and write
|
|
402
|
+
`maxAge` in seconds: `@fresh(maxAge: 600)` for ten minutes.
|
|
403
|
+
|
|
254
404
|
### `SERVICE_UNAVAILABLE`, 503, on every guarded field
|
|
255
405
|
|
|
256
406
|
**Why:** a store could not answer — `STORE_FAILED`. The sessions store for
|
|
257
|
-
`@authenticated`, `requireUser()
|
|
407
|
+
`@authenticated`, `@fresh`, `requireUser()`, `requireFresh()` and
|
|
408
|
+
`ctx.janus.user()`; the relation store
|
|
258
409
|
for `can()` and `@permission` — and a loader of yours that threw
|
|
259
|
-
`StoreFailure`. It is never answered `UNAUTHENTICATED` or `
|
|
410
|
+
`StoreFailure`. It is never answered `UNAUTHENTICATED`, `FORBIDDEN` or `STEP_UP_REQUIRED`.
|
|
260
411
|
|
|
261
412
|
**Fix:** the store: its connection, its credentials, its availability. The
|
|
262
413
|
message carries nothing of the store's; log the error where you call
|
|
@@ -278,14 +429,21 @@ transformed with `applyJanusDirectives()` and served by something else.
|
|
|
278
429
|
|
|
279
430
|
**Fix:** `plugins: [useJanus({ auth })]`.
|
|
280
431
|
|
|
281
|
-
### `TypeError:
|
|
432
|
+
### `TypeError: …: the GraphQL context has no request to authenticate`
|
|
282
433
|
|
|
283
|
-
**Why:** the context was built without `request
|
|
284
|
-
|
|
434
|
+
**Why:** the context was built without `request`, and for no connection
|
|
435
|
+
`janusConnection().onConnect` accepted: a transport that is not HTTP; a
|
|
436
|
+
graphql-ws server without `onConnect`; or Yoga's recipe with the context
|
|
437
|
+
built from something other than `{ ...ctx, … }`, which drops `ctx.extra`,
|
|
438
|
+
where `useJanus()` finds the connection. The message opens with
|
|
439
|
+
`useJanus():` under Yoga, and with `janusConnection().context:` without
|
|
440
|
+
it — the `context` of a server whose `onConnect` is not
|
|
441
|
+
`janusConnection()`'s.
|
|
285
442
|
|
|
286
|
-
**Fix:**
|
|
287
|
-
|
|
288
|
-
the
|
|
443
|
+
**Fix:** over graphql-ws, pass `onConnect: janusConnection({ auth }).onConnect`
|
|
444
|
+
to `useServer()`, and keep the `...ctx` spread in `yoga.getEnveloped()`
|
|
445
|
+
([the subscriptions guide](guide/subscriptions.md)). Without Yoga, pass
|
|
446
|
+
`context: connection.context` too.
|
|
289
447
|
|
|
290
448
|
### `TypeError: can(): ctx.janus.access is not set`
|
|
291
449
|
|
|
@@ -307,6 +465,30 @@ signed-in user:
|
|
|
307
465
|
const user = await requireUser(ctx, allowed.length > 0 ? { type: allowed } : {});
|
|
308
466
|
```
|
|
309
467
|
|
|
468
|
+
### `TypeError: requireFresh(): maxAge is a duration with its unit`
|
|
469
|
+
|
|
470
|
+
**Why:** `requireFresh(ctx, 600)` — a bare number, from JavaScript or built
|
|
471
|
+
at run time. `@nxgt/janus` would read it as 600 milliseconds, where
|
|
472
|
+
`@fresh(maxAge: 600)` reads ten minutes, so it is refused rather than
|
|
473
|
+
guessed. The compiler refuses a literal number.
|
|
474
|
+
|
|
475
|
+
**Fix:** write the unit: `requireFresh(ctx, '10m')`, or `'600s'`.
|
|
476
|
+
|
|
477
|
+
### `TypeError: requireFresh(): maxAge: "…" is not a duration`
|
|
478
|
+
|
|
479
|
+
**Why:** the string is not a number followed by `ms`, `s`, `m`, `h` or `d`
|
|
480
|
+
— `'10 minutes'`, `'10 m'`.
|
|
481
|
+
|
|
482
|
+
**Fix:** `requireFresh(ctx, '10m')`.
|
|
483
|
+
|
|
484
|
+
### `TypeError: requireFresh(): maxAge: a duration must be above zero`
|
|
485
|
+
|
|
486
|
+
**Why:** `requireFresh(ctx, '0m')` — no session is ever fresh within no
|
|
487
|
+
time, so every request would be refused. The compiler lets it through: the
|
|
488
|
+
type reads a number and a unit, not their value.
|
|
489
|
+
|
|
490
|
+
**Fix:** a duration above zero, `requireFresh(ctx, '10m')`.
|
|
491
|
+
|
|
310
492
|
### `@permission on … resolved no object id from … — answered NOT_FOUND`
|
|
311
493
|
|
|
312
494
|
**Where:** a warning, through `process.emitWarning` with the code
|
|
@@ -469,3 +651,28 @@ throws
|
|
|
469
651
|
|
|
470
652
|
**Fix:** name at least one user type, `{ type: ['staff'] }`, or leave `type`
|
|
471
653
|
out to admit any signed-in user.
|
|
654
|
+
|
|
655
|
+
### `Argument of type '600' is not assignable to parameter of type '`${number}ms` | …'`, on `requireFresh()`
|
|
656
|
+
|
|
657
|
+
The full message ends `` '`${number}ms` | `${number}s` | `${number}m` |
|
|
658
|
+
`${number}h` | `${number}d`' ``; the same for `'10 minutes'`.
|
|
659
|
+
|
|
660
|
+
**Why:** `requireFresh(ctx, maxAge)` takes a duration with its unit. A bare
|
|
661
|
+
number would be milliseconds to `@nxgt/janus` and seconds to `@fresh`, so
|
|
662
|
+
neither is guessed.
|
|
663
|
+
|
|
664
|
+
**Fix:** `requireFresh(ctx, '10m')`.
|
|
665
|
+
|
|
666
|
+
### `Expected 2 arguments, but got 1`, on `requireFresh()`
|
|
667
|
+
|
|
668
|
+
**Why:** `requireFresh(ctx)` — how recent the proof must be has no default.
|
|
669
|
+
|
|
670
|
+
**Fix:** `requireFresh(ctx, '10m')`.
|
|
671
|
+
|
|
672
|
+
### `Type 'number' is not assignable to type 'Clock'`
|
|
673
|
+
|
|
674
|
+
**Why:** `useJanus({ auth, clock: Date.now() })` — a timestamp, where a
|
|
675
|
+
clock answers `now()`.
|
|
676
|
+
|
|
677
|
+
**Fix:** pass the clock given to `janus()`, `fixedClock(…)` in a spec, or
|
|
678
|
+
leave `clock` out.
|
package/graphql/janus.graphqls
CHANGED
|
@@ -7,6 +7,14 @@ directive @authenticated(
|
|
|
7
7
|
type: [String!]
|
|
8
8
|
) on OBJECT | INTERFACE | FIELD_DEFINITION
|
|
9
9
|
|
|
10
|
+
"""
|
|
11
|
+
Only a session that proved who it is less than `maxAge` seconds ago reaches
|
|
12
|
+
this field, or every field of this type or interface: signed in, or confirmed
|
|
13
|
+
since by a step-up. Anonymous is answered UNAUTHENTICATED, an older session
|
|
14
|
+
STEP_UP_REQUIRED. Where several apply, the smallest `maxAge` holds.
|
|
15
|
+
"""
|
|
16
|
+
directive @fresh(maxAge: Int!) on OBJECT | INTERFACE | FIELD_DEFINITION
|
|
17
|
+
|
|
10
18
|
"""
|
|
11
19
|
What a denied `@permission` answers.
|
|
12
20
|
"""
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nxgt/janus-graphql",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "GraphQL for @nxgt/janus: an envelop plugin with the signed-in user on the context, fields guarded by @authenticated and @permission, and every error with the status it deserves",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "GraphQL for @nxgt/janus: an envelop plugin with the signed-in user on the context, fields guarded by @authenticated, @fresh and @permission, and every error with the status it deserves",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.js",
|
|
@@ -57,8 +57,11 @@
|
|
|
57
57
|
"@graphql-tools/utils": "^12.0.1",
|
|
58
58
|
"@nxgt/janus": "^0.12.0",
|
|
59
59
|
"@types/bun": "^1.4.2",
|
|
60
|
+
"@types/ws": "^8.18.1",
|
|
60
61
|
"graphql": "^17.0.2",
|
|
62
|
+
"graphql-ws": "^6.3.0",
|
|
61
63
|
"graphql-yoga": "^5.24.1",
|
|
64
|
+
"ws": "^8.22.0",
|
|
62
65
|
"zod": "^4.6.5"
|
|
63
66
|
},
|
|
64
67
|
"peerDependencies": {
|
|
@@ -66,6 +69,12 @@
|
|
|
66
69
|
"@graphql-tools/utils": ">=10.0.0 <13",
|
|
67
70
|
"@nxgt/janus": "^0.12.0",
|
|
68
71
|
"graphql": "^16.9.0 || ^17.0.0",
|
|
72
|
+
"graphql-ws": "^6.0.0",
|
|
69
73
|
"typescript": "^6.0.3"
|
|
74
|
+
},
|
|
75
|
+
"peerDependenciesMeta": {
|
|
76
|
+
"graphql-ws": {
|
|
77
|
+
"optional": true
|
|
78
|
+
}
|
|
70
79
|
}
|
|
71
80
|
}
|