@nxgt/shared-graphql 2.0.1 → 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.
Files changed (79) hide show
  1. package/README.md +196 -81
  2. package/dist/directives/authenticated.d.ts +42 -0
  3. package/dist/directives/authenticated.d.ts.map +1 -0
  4. package/dist/directives/federation.d.ts +6 -0
  5. package/dist/directives/federation.d.ts.map +1 -1
  6. package/dist/directives/index.d.ts +5 -1
  7. package/dist/directives/index.d.ts.map +1 -1
  8. package/dist/directives/paths.d.ts +20 -0
  9. package/dist/directives/paths.d.ts.map +1 -0
  10. package/dist/directives/permission.d.ts +39 -0
  11. package/dist/directives/permission.d.ts.map +1 -0
  12. package/dist/directives/removed-check.d.ts +15 -0
  13. package/dist/directives/removed-check.d.ts.map +1 -0
  14. package/dist/directives/scope.d.ts +19 -0
  15. package/dist/directives/scope.d.ts.map +1 -0
  16. package/dist/directives/sdl.d.ts +26 -0
  17. package/dist/directives/sdl.d.ts.map +1 -0
  18. package/dist/directives/validate.d.ts +23 -0
  19. package/dist/directives/validate.d.ts.map +1 -0
  20. package/dist/index.js +1048 -564
  21. package/dist/index.js.map +37 -20
  22. package/dist/integrations/hono.d.ts +2 -2
  23. package/dist/integrations/hono.d.ts.map +1 -1
  24. package/dist/plugins/apply-authenticated.d.ts +21 -0
  25. package/dist/plugins/apply-authenticated.d.ts.map +1 -0
  26. package/dist/plugins/apply-keto-checks.d.ts +20 -0
  27. package/dist/plugins/apply-keto-checks.d.ts.map +1 -0
  28. package/dist/plugins/auth.d.ts +17 -1
  29. package/dist/plugins/auth.d.ts.map +1 -1
  30. package/dist/plugins/authenticated.d.ts +14 -0
  31. package/dist/plugins/authenticated.d.ts.map +1 -0
  32. package/dist/plugins/extract-jwt.d.ts +19 -6
  33. package/dist/plugins/extract-jwt.d.ts.map +1 -1
  34. package/dist/plugins/field-guard.d.ts +30 -0
  35. package/dist/plugins/field-guard.d.ts.map +1 -0
  36. package/dist/plugins/gateway-trust.d.ts +47 -0
  37. package/dist/plugins/gateway-trust.d.ts.map +1 -0
  38. package/dist/plugins/index.d.ts +6 -0
  39. package/dist/plugins/index.d.ts.map +1 -1
  40. package/dist/plugins/keto-checker.d.ts +24 -0
  41. package/dist/plugins/keto-checker.d.ts.map +1 -0
  42. package/dist/plugins/keto-checks.d.ts +11 -34
  43. package/dist/plugins/keto-checks.d.ts.map +1 -1
  44. package/dist/plugins/keto-helpers.d.ts +38 -0
  45. package/dist/plugins/keto-helpers.d.ts.map +1 -0
  46. package/dist/plugins/ory-auth.d.ts +5 -5
  47. package/dist/plugins/ory-auth.d.ts.map +1 -1
  48. package/dist/scalars/custom/utils.d.ts.map +1 -1
  49. package/dist/shared.d.ts +1 -1
  50. package/dist/shared.d.ts.map +1 -1
  51. package/dist/utils/errors/create-error.d.ts +3 -0
  52. package/dist/utils/errors/create-error.d.ts.map +1 -0
  53. package/dist/utils/errors/denial.d.ts +20 -0
  54. package/dist/utils/errors/denial.d.ts.map +1 -0
  55. package/dist/utils/errors/format-error.d.ts +12 -0
  56. package/dist/utils/errors/format-error.d.ts.map +1 -0
  57. package/dist/utils/errors/index.d.ts +6 -0
  58. package/dist/utils/errors/index.d.ts.map +1 -0
  59. package/dist/utils/errors/mask-error.d.ts +25 -0
  60. package/dist/utils/errors/mask-error.d.ts.map +1 -0
  61. package/dist/utils/errors/ory-unavailable.d.ts +19 -0
  62. package/dist/utils/errors/ory-unavailable.d.ts.map +1 -0
  63. package/dist/utils/index.d.ts +1 -1
  64. package/dist/utils/index.d.ts.map +1 -1
  65. package/dist/utils/ws-context.d.ts +1 -1
  66. package/docs/README.md +13 -0
  67. package/docs/guide/authentication.md +187 -0
  68. package/docs/guide/errors.md +136 -0
  69. package/docs/guide/migrating-to-3.md +276 -0
  70. package/docs/guide/permissions.md +220 -0
  71. package/docs/roadmap.md +71 -0
  72. package/docs/troubleshooting.md +273 -0
  73. package/graphql/directives/permission.graphqls +73 -0
  74. package/package.json +11 -13
  75. package/dist/directives/check.d.ts +0 -40
  76. package/dist/directives/check.d.ts.map +0 -1
  77. package/dist/utils/errors.utils.d.ts +0 -23
  78. package/dist/utils/errors.utils.d.ts.map +0 -1
  79. package/graphql/directives/check.graphqls +0 -86
@@ -1,4 +1,20 @@
1
1
  import type { Plugin } from 'graphql-yoga';
2
2
  import type { GraphQLBaseContext } from '../types';
3
- export declare function useAuth(): Plugin<GraphQLBaseContext>;
3
+ import { type GatewayTrustOptions } from './gateway-trust';
4
+ export type UseAuthOptions = GatewayTrustOptions;
5
+ /**
6
+ * The caller a gateway resolved, read from the GraphQL request's `extensions`
7
+ * — `user` and `token` — and ONLY for a request `trustedGateway` vouches for.
8
+ *
9
+ * `extensions` sits in the request body, which any client writes. A request
10
+ * without the gateway's proof leaves the context as it was: no `user`, no
11
+ * `token`, whatever its `extensions` claim. Without a `trustedGateway` this
12
+ * throws when it is called, so a server cannot boot trusting the body.
13
+ *
14
+ * useAuth({ trustedGateway: gatewaySecret({ secret: env.GATEWAY_SECRET }) })
15
+ *
16
+ * An API that resolves its own callers — a Kratos session, a Hydra token —
17
+ * uses `useOryAuth(ory)` instead.
18
+ */
19
+ export declare function useAuth(options: UseAuthOptions): Plugin<GraphQLBaseContext>;
4
20
  //# sourceMappingURL=auth.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/plugins/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAEnD,wBAAgB,OAAO,IAAI,MAAM,CAAC,kBAAkB,CAAC,CASpD"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/plugins/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AACnD,OAAO,EACN,KAAK,mBAAmB,EAGxB,MAAM,iBAAiB,CAAC;AAEzB,MAAM,MAAM,cAAc,GAAG,mBAAmB,CAAC;AAEjD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,CAAC,kBAAkB,CAAC,CAmB3E"}
@@ -0,0 +1,14 @@
1
+ import type { Plugin } from 'graphql-yoga';
2
+ import type { AuthenticatedOptions } from '../directives/authenticated';
3
+ import type { GraphQLBaseContext } from '../types';
4
+ import type { OryContext } from './ory-auth';
5
+ /**
6
+ * Enforces `@authenticated` and `@authenticated(type:)` on every schema the
7
+ * server uses. Goes after the plugin that sets the caller — `useOryAuth(ory)`
8
+ * or `useAuth({ trustedGateway })`.
9
+ *
10
+ * `applyAuthenticated` returns a schema with no field left to guard as it is,
11
+ * so the replacement settles alongside `useKetoChecks` whatever their order.
12
+ */
13
+ export declare function useAuthenticated(options?: AuthenticatedOptions): Plugin<GraphQLBaseContext & OryContext>;
14
+ //# sourceMappingURL=authenticated.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authenticated.d.ts","sourceRoot":"","sources":["../../src/plugins/authenticated.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAEnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC/B,OAAO,GAAE,oBAAyB,GAChC,MAAM,CAAC,kBAAkB,GAAG,UAAU,CAAC,CAOzC"}
@@ -1,9 +1,22 @@
1
+ import type { ApolloServerPlugin } from '@apollo/server';
1
2
  import type { TokenPrincipal } from '@nxgt/shared';
2
- export declare const extractJwtPlugin: {
3
- requestDidStart({ request, contextValue }: import("@apollo/server").GraphQLRequestContext<{
4
- jwt?: {
5
- payload: TokenPrincipal;
6
- };
7
- }>): Promise<void>;
3
+ import { type GatewayTrustOptions } from './gateway-trust';
4
+ export type ExtractJwtOptions = GatewayTrustOptions;
5
+ /** What `extractJwtPlugin()` sets on an Apollo context. */
6
+ export type JwtContext = {
7
+ jwt?: {
8
+ payload: TokenPrincipal;
9
+ };
8
10
  };
11
+ /**
12
+ * Apollo Server's counterpart of `useAuth()`: the JWT payload a gateway
13
+ * verified and forwarded in `request.extensions.payload`, copied onto
14
+ * `context.jwt` — ONLY for a request `trustedGateway` vouches for.
15
+ *
16
+ * A request without the gateway's proof leaves `context.jwt` as the context
17
+ * function built it, whatever its `extensions` claim.
18
+ *
19
+ * plugins: [extractJwtPlugin({ trustedGateway: gatewaySecret({ secret }) })]
20
+ */
21
+ export declare function extractJwtPlugin(options: ExtractJwtOptions): ApolloServerPlugin<JwtContext>;
9
22
  //# sourceMappingURL=extract-jwt.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"extract-jwt.d.ts","sourceRoot":"","sources":["../../src/plugins/extract-jwt.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAGnD,eAAO,MAAM,gBAAgB;;cAWtB;YAAE,OAAO,EAAE,cAAc,CAAA;SAAE;;CAChC,CAAC"}
1
+ {"version":3,"file":"extract-jwt.d.ts","sourceRoot":"","sources":["../../src/plugins/extract-jwt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EACN,KAAK,mBAAmB,EAGxB,MAAM,iBAAiB,CAAC;AAEzB,MAAM,MAAM,iBAAiB,GAAG,mBAAmB,CAAC;AAEpD,2DAA2D;AAC3D,MAAM,MAAM,UAAU,GAAG;IACxB,GAAG,CAAC,EAAE;QAAE,OAAO,EAAE,cAAc,CAAA;KAAE,CAAC;CAClC,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC/B,OAAO,EAAE,iBAAiB,GACxB,kBAAkB,CAAC,UAAU,CAAC,CAUhC"}
@@ -0,0 +1,30 @@
1
+ import { type GraphQLFieldConfig } from 'graphql';
2
+ type FieldConfig = GraphQLFieldConfig<unknown, unknown>;
3
+ /** What a guard asks before a field runs; it throws to refuse. */
4
+ export type FieldCheck = (source: unknown, args: Record<string, unknown>, context: unknown) => void | Promise<void>;
5
+ /**
6
+ * One transform's guard on one field: its mark, its rank (lower runs first),
7
+ * its check, and whether the check can run before a subscription's stream is
8
+ * opened — only when it reads nothing off an event (`parent.<path>`).
9
+ */
10
+ export type FieldGuard = {
11
+ mark: string;
12
+ rank: number;
13
+ check: FieldCheck;
14
+ onSubscribe: boolean;
15
+ };
16
+ /**
17
+ * Whether the transform owning `mark` guards this field as it stands. A
18
+ * merged schema's unguarded half, or a field whose resolver was replaced,
19
+ * is not — so a transform run again wraps it, never one twice.
20
+ */
21
+ export declare function isGuarded(config: FieldConfig, mark: string): boolean;
22
+ /**
23
+ * The field with `guard` added. Every guard runs by rank — `@authenticated`
24
+ * before `@permission`, whichever transform ran first — before the resolver;
25
+ * those with `onSubscribe` also run before `subscribe`, so a refused
26
+ * subscription opens no stream.
27
+ */
28
+ export declare function guardField(config: FieldConfig, guard: FieldGuard): FieldConfig;
29
+ export {};
30
+ //# sourceMappingURL=field-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"field-guard.d.ts","sourceRoot":"","sources":["../../src/plugins/field-guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,KAAK,kBAAkB,EAEvB,MAAM,SAAS,CAAC;AAEjB,KAAK,WAAW,GAAG,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AAGxD,kEAAkE;AAClE,MAAM,MAAM,UAAU,GAAG,CACxB,MAAM,EAAE,OAAO,EACf,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,EAAE,OAAO,KACZ,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE1B;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,UAAU,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;CACrB,CAAC;AA6BF;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpE;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CACzB,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,UAAU,GACf,WAAW,CA6Bb"}
@@ -0,0 +1,47 @@
1
+ import type { TokenPrincipal } from '@nxgt/shared';
2
+ /** One request header — a fetch `Headers`, or Apollo Server's `HeaderMap`. */
3
+ export type HeaderReader = {
4
+ get(name: string): string | null | undefined;
5
+ };
6
+ /**
7
+ * Whether a request came from the gateway allowed to name the caller in the
8
+ * GraphQL request's `extensions`.
9
+ *
10
+ * `extensions` is part of the request body, which any client writes. It names
11
+ * the caller only when this answers `true` — for a request carrying proof the
12
+ * client cannot forge. `gatewaySecret()` is the usual proof; a function of
13
+ * your own (an mTLS header set by your proxy, say) works as well.
14
+ */
15
+ export type GatewayTrust = (headers: HeaderReader) => boolean | Promise<boolean>;
16
+ /** What `useAuth()` and `extractJwtPlugin()` take: who may set the caller. */
17
+ export type GatewayTrustOptions = {
18
+ trustedGateway: GatewayTrust;
19
+ };
20
+ export type GatewaySecretOptions = {
21
+ /** Shared with the gateway only. At least 16 characters. */
22
+ secret: string;
23
+ /** The header the gateway sends it in. `x-gateway-secret` by default. */
24
+ header?: string;
25
+ };
26
+ export declare const GATEWAY_SECRET_HEADER = "x-gateway-secret";
27
+ /**
28
+ * A `GatewayTrust` that holds for a request whose `header` carries `secret`,
29
+ * compared in constant time. The gateway adds the header to every subgraph
30
+ * request; a client reaching the service directly does not know it.
31
+ *
32
+ * Refuses, when it is built, a secret shorter than 16 characters: an unset
33
+ * environment variable must stop the server, not trust an empty header.
34
+ */
35
+ export declare function gatewaySecret({ secret, header, }: GatewaySecretOptions): GatewayTrust;
36
+ /**
37
+ * The `trustedGateway` a plugin was given, or a `TypeError` naming the plugin:
38
+ * without one there is no caller it could read, and a plugin that silently
39
+ * reads none is a server whose every caller is anonymous.
40
+ */
41
+ export declare function requireGatewayTrust(options: Partial<GatewayTrustOptions> | undefined, plugin: string): GatewayTrust;
42
+ /**
43
+ * The caller a trusted gateway put in `extensions`, when it is shaped like
44
+ * one: an object with a non-empty string `sub`. Anything else names nobody.
45
+ */
46
+ export declare function principalOf(value: unknown): TokenPrincipal | undefined;
47
+ //# sourceMappingURL=gateway-trust.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway-trust.d.ts","sourceRoot":"","sources":["../../src/plugins/gateway-trust.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD,8EAA8E;AAC9E,MAAM,MAAM,YAAY,GAAG;IAC1B,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CAC7C,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,MAAM,YAAY,GAAG,CAC1B,OAAO,EAAE,YAAY,KACjB,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhC,8EAA8E;AAC9E,MAAM,MAAM,mBAAmB,GAAG;IACjC,cAAc,EAAE,YAAY,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IAClC,4DAA4D;IAC5D,MAAM,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,eAAO,MAAM,qBAAqB,qBAAqB,CAAC;AAKxD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,EAC7B,MAAM,EACN,MAA8B,GAC9B,EAAE,oBAAoB,GAAG,YAAY,CAYrC;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAClC,OAAO,EAAE,OAAO,CAAC,mBAAmB,CAAC,GAAG,SAAS,EACjD,MAAM,EAAE,MAAM,GACZ,YAAY,CAQd;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,GAAG,SAAS,CAMtE"}
@@ -1,5 +1,11 @@
1
+ export * from './apply-authenticated';
2
+ export * from './apply-keto-checks';
1
3
  export * from './auth';
4
+ export * from './authenticated';
2
5
  export * from './extract-jwt';
6
+ export * from './gateway-trust';
7
+ export * from './keto-checker';
3
8
  export * from './keto-checks';
9
+ export * from './keto-helpers';
4
10
  export * from './ory-auth';
5
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/plugins/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/plugins/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,QAAQ,CAAC;AACvB,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { type Ory, type Permission, type Subject } from '@nxgt/ory-sdk';
2
+ /**
3
+ * The per-request answer cache, and the reason `@check` / `@permission` cost
4
+ * nothing on top of the access layer that already asks Keto.
5
+ *
6
+ * `DataLoader` here does two things: it **batches** distinct questions into
7
+ * one `POST /relation-tuples/batch/check`, and it **memoises** identical ones
8
+ * for the life of the request. So a field guarded by `@permission(view)` and a
9
+ * service that then calls `require<M>Access` — which asks the same question —
10
+ * pay for one round trip between them.
11
+ *
12
+ * A failed batch is not memoised: `DataLoader` clears the keys of a batch that
13
+ * rejected, so an outage answers `OryUnavailable` to every question it touched
14
+ * and the next ask goes back to Keto.
15
+ *
16
+ * The key is Keto's own notation, `Note:n1#view@idn-7`, so a cache hit is
17
+ * legible in a log line.
18
+ */
19
+ export type KetoChecker = (permission: Permission, subject: Subject) => Promise<boolean>;
20
+ export type KetoChecksContext = {
21
+ ketoChecks?: KetoChecker;
22
+ };
23
+ export declare function createKetoChecks(ory: Ory): KetoChecker;
24
+ //# sourceMappingURL=keto-checker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keto-checker.d.ts","sourceRoot":"","sources":["../../src/plugins/keto-checker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,GAAG,EAAE,KAAK,UAAU,EAAE,KAAK,OAAO,EAAS,MAAM,eAAe,CAAC;AAG/E;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,WAAW,GAAG,CACzB,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,OAAO,KACZ,OAAO,CAAC,OAAO,CAAC,CAAC;AAEtB,MAAM,MAAM,iBAAiB,GAAG;IAC/B,UAAU,CAAC,EAAE,WAAW,CAAC;CACzB,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,GAAG,GAAG,WAAW,CAUtD"}
@@ -1,48 +1,25 @@
1
- import { type Ory, type Permission, type Subject } from '@nxgt/ory-sdk';
2
- import { type GraphQLSchema } from 'graphql';
1
+ import type { Ory } from '@nxgt/ory-sdk';
3
2
  import type { Plugin } from 'graphql-yoga';
3
+ import type { ReadOptions } from '../directives';
4
4
  import type { GraphQLBaseContext } from '../types';
5
+ import { type KetoChecksContext } from './keto-checker';
5
6
  import type { OryContext } from './ory-auth';
6
- /**
7
- * The per-request answer cache, and the reason `@check` costs nothing on top
8
- * of the access layer that already asks Keto.
9
- *
10
- * `DataLoader` here does two things: it **batches** distinct questions into
11
- * one `POST /relation-tuples/batch/check`, and it **memoises** identical ones
12
- * for the life of the request. So a field guarded by `@check(view)` and a
13
- * service that then calls `require<M>Access` — which asks the same question —
14
- * pay for one round trip between them.
15
- *
16
- * The key is Keto's own notation, `Note:n1#view@idn-7`, so a cache hit is
17
- * legible in a log line.
18
- */
19
- export type KetoChecker = (permission: Permission, subject: Subject) => Promise<boolean>;
20
- export type KetoChecksContext = {
21
- ketoChecks?: KetoChecker;
22
- };
23
- export declare function createKetoChecks(ory: Ory): KetoChecker;
24
- /**
25
- * Wraps every field carrying `@check` so the permission is answered before its
26
- * resolver runs.
27
- *
28
- * Modelled on `applyGraphqlPolicy` in `@nxgt/security` — same `mapSchema` over
29
- * `MapperKind.OBJECT_FIELD`, same `fieldConfig.resolve ?? defaultFieldResolver`.
30
- * Exported on its own because a schema transform is far easier to test than a
31
- * plugin, and `useKetoChecks` is a three-line wrapper over it.
32
- */
33
- export declare function applyKetoChecks(schema: GraphQLSchema): GraphQLSchema;
7
+ /** What `useKetoChecks` and `applyKetoChecks` take besides the `Ory` client. */
8
+ export type KetoChecksOptions = ReadOptions;
34
9
  /**
35
10
  * Registers the whole thing: the schema transform, and the per-request checker
36
- * the transform — and the app's own access layer — read off the context.
11
+ * the transform — and the app's own access layer, through `can` — read off the
12
+ * context.
37
13
  *
38
14
  * Goes after `useOryAuth(ory)`, which is what puts `ory.subject` there.
39
15
  *
40
16
  * `replaceSchema` inside `onSchemaChange` re-enters this hook with the new
41
- * schema, so transformed schemas are remembered and passed through. Without
42
- * the guard this loops until the stack gives out.
17
+ * schema, and with every schema another plugin makes of it. `applyKetoChecks`
18
+ * returns a schema with no field left to guard as it is, so the replacement settles
19
+ * instead of looping, alongside `useAuthenticated` whatever their order.
43
20
  *
44
21
  * `OryUnavailable` is deliberately not caught anywhere here: a Keto outage is
45
22
  * a 503 through `createMaskError`, never a denial.
46
23
  */
47
- export declare function useKetoChecks(ory: Ory): Plugin<GraphQLBaseContext & OryContext & KetoChecksContext>;
24
+ export declare function useKetoChecks(ory: Ory, options?: KetoChecksOptions): Plugin<GraphQLBaseContext & OryContext & KetoChecksContext>;
48
25
  //# sourceMappingURL=keto-checks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"keto-checks.d.ts","sourceRoot":"","sources":["../../src/plugins/keto-checks.ts"],"names":[],"mappings":"AACA,OAAO,EAEN,KAAK,GAAG,EACR,KAAK,UAAU,EAEf,KAAK,OAAO,EAEZ,MAAM,eAAe,CAAC;AAGvB,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,SAAS,CAAC;AACnE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAO3C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,WAAW,GAAG,CACzB,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,OAAO,KACZ,OAAO,CAAC,OAAO,CAAC,CAAC;AAEtB,MAAM,MAAM,iBAAiB,GAAG;IAC/B,UAAU,CAAC,EAAE,WAAW,CAAC;CACzB,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,GAAG,GAAG,WAAW,CAUtD;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,aAAa,GAAG,aAAa,CA4CpE;AAmCD;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAC5B,GAAG,EAAE,GAAG,GACN,MAAM,CAAC,kBAAkB,GAAG,UAAU,GAAG,iBAAiB,CAAC,CAc7D"}
1
+ {"version":3,"file":"keto-checks.d.ts","sourceRoot":"","sources":["../../src/plugins/keto-checks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAEnD,OAAO,EAAoB,KAAK,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAC1E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,gFAAgF;AAChF,MAAM,MAAM,iBAAiB,GAAG,WAAW,CAAC;AAE5C;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAC5B,GAAG,EAAE,GAAG,EACR,OAAO,GAAE,iBAAsB,GAC7B,MAAM,CAAC,kBAAkB,GAAG,UAAU,GAAG,iBAAiB,CAAC,CAU7D"}
@@ -0,0 +1,38 @@
1
+ import type { TokenPrincipal } from '@nxgt/shared';
2
+ import type { GraphQLBaseContext } from '../types';
3
+ import type { KetoChecksContext } from './keto-checker';
4
+ import type { OryContext } from './ory-auth';
5
+ /**
6
+ * The context `useOryAuth(ory)` and `useKetoChecks(ory)` build together —
7
+ * what a resolver of an Ory-native API receives.
8
+ *
9
+ * createYoga<{}, OryGraphQLContext>({ plugins: [useOryAuth(ory), useKetoChecks(ory)] })
10
+ */
11
+ export type OryGraphQLContext = GraphQLBaseContext & OryContext & KetoChecksContext;
12
+ /** The question `can` asks, in `@permission`'s words. */
13
+ export type CanQuestion = {
14
+ /** The permit, e.g. `view`. */
15
+ name: string;
16
+ /** The Keto namespace, e.g. `Note`. */
17
+ type: string;
18
+ /** The object id itself — a value, not a path. */
19
+ id: string;
20
+ };
21
+ /**
22
+ * The caller, or an `UNAUTHENTICATED` refusal: a `GraphQLError` answered 401.
23
+ * An absent caller is not an error to swallow in a resolver that needs one.
24
+ */
25
+ export declare function requireUser(context: {
26
+ user?: TokenPrincipal | null;
27
+ }): TokenPrincipal;
28
+ /**
29
+ * Asks Keto what `@permission` would, from a resolver — through the same
30
+ * per-request memo, so a question the directive already asked is free.
31
+ *
32
+ * `true` or `false` is Keto's answer. Everything else throws: no caller is
33
+ * `UNAUTHENTICATED`, a missing `useKetoChecks` is a wiring error, and an
34
+ * outage is `OryUnavailable` (a 503) — never a `false` a caller could take
35
+ * for a denial.
36
+ */
37
+ export declare function can(context: OryContext & KetoChecksContext, question: CanQuestion): Promise<boolean>;
38
+ //# sourceMappingURL=keto-helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keto-helpers.d.ts","sourceRoot":"","sources":["../../src/plugins/keto-helpers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAEnD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG,kBAAkB,GACjD,UAAU,GACV,iBAAiB,CAAC;AAEnB,yDAAyD;AACzD,MAAM,MAAM,WAAW,GAAG;IACzB,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,EAAE,EAAE,MAAM,CAAC;CACX,CAAC;AAEF;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE;IACpC,IAAI,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;CAC7B,GAAG,cAAc,CAKjB;AAED;;;;;;;;GAQG;AACH,wBAAsB,GAAG,CACxB,OAAO,EAAE,UAAU,GAAG,iBAAiB,EACvC,QAAQ,EAAE,WAAW,GACnB,OAAO,CAAC,OAAO,CAAC,CAgBlB"}
@@ -1,7 +1,7 @@
1
- import { type Ory, type OryPrincipal, OryUnavailable } from '@nxgt/ory-sdk';
1
+ import { type Ory, type OryPrincipal, type OryUnavailable } from '@nxgt/ory-sdk';
2
2
  import type { PolicyClaims } from '@nxgt/security/policy';
3
3
  import type { TokenPrincipal } from '@nxgt/shared';
4
- import { GraphQLError } from 'graphql';
4
+ import type { GraphQLError } from 'graphql';
5
5
  import type { Plugin } from 'graphql-yoga';
6
6
  import type { GraphQLBaseContext } from '../types';
7
7
  /**
@@ -60,9 +60,9 @@ export declare function resolveOryPrincipal(ory: Ory, headers: Headers): Promise
60
60
  /**
61
61
  * `useAuth()` for an Ory-native API: resolves the caller through Kratos
62
62
  * (session cookie, session token) or Hydra (Bearer, introspected) and puts
63
- * `user`, `claims`, `token` and `ory` on the context. Wire it exactly where the
64
- * standalone APIs wire `useAuth()`, ahead of `useGenericAuth` — which then
65
- * enforces `@authenticated` from `context.user` unchanged.
63
+ * `user`, `claims`, `token` and `ory` on the context. Wire it ahead of
64
+ * `useAuthenticated()`, which enforces `@authenticated` from `context.user`
65
+ * and `context.ory.kind`.
66
66
  *
67
67
  * The `Ory` instance comes from the app's `createOry()` — one per process —
68
68
  * and is the same one the services use for `isAllowed`.
@@ -1 +1 @@
1
- {"version":3,"file":"ory-auth.d.ts","sourceRoot":"","sources":["../../src/plugins/ory-auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,KAAK,GAAG,EACR,KAAK,YAAY,EACjB,cAAc,EACd,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAEnD;;;;;GAKG;AACH,MAAM,MAAM,UAAU,GAAG;IACxB,GAAG,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAC1B;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;CACtB,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,YAAY,GAAG,cAAc,CAc7D;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,cAAc,GAAG,YAAY,CAQvE;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACxC,GAAG,EAAE,GAAG,EACR,OAAO,EAAE,OAAO,GACd,OAAO,CAAC,UAAU,GAAG;IAAE,IAAI,CAAC,EAAE,cAAc,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAejE;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAAC,kBAAkB,GAAG,UAAU,CAAC,CAM5E"}
1
+ {"version":3,"file":"ory-auth.d.ts","sourceRoot":"","sources":["../../src/plugins/ory-auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,KAAK,GAAG,EACR,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAMnD;;;;;GAKG;AACH,MAAM,MAAM,UAAU,GAAG;IACxB,GAAG,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAC1B;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;CACtB,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,YAAY,GAAG,cAAc,CAc7D;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,cAAc,GAAG,YAAY,CAEvE;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACxC,GAAG,EAAE,GAAG,EACR,OAAO,EAAE,OAAO,GACd,OAAO,CAAC,UAAU,GAAG;IAAE,IAAI,CAAC,EAAE,cAAc,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAejE;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAAC,kBAAkB,GAAG,UAAU,CAAC,CAM5E"}
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/scalars/custom/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAG5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAE5C,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,CAAC,EACpC,MAAM,EAAE,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAAC,EAC/B,EACC,IAAI,EACJ,WAAW,EACX,YAAY,GACZ,EAAE;IACF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;CAClC,2BA2CD"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/scalars/custom/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAG5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAE5C,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,CAAC,EACpC,MAAM,EAAE,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAAC,EAC/B,EACC,IAAI,EACJ,WAAW,EACX,YAAY,GACZ,EAAE;IACF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;CAClC,2BA4CD"}
package/dist/shared.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const SHARED_TYPE_DEFS = "\ntype Query {\n\t_empty: String\n}\ntype Mutation {\n\t_empty: String\n}\ntype Subscription {\n\t_empty: String\n}\n\ndirective @authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM\n\ndirective @policy(policies: [[String!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM\n\ndirective @shareable on OBJECT | FIELD_DEFINITION\n\ndirective @link(url: String!, import: [String!]) on SCHEMA\n\n";
1
+ export declare const SHARED_TYPE_DEFS = "\ntype Query {\n\t_empty: String\n}\ntype Mutation {\n\t_empty: String\n}\ntype Subscription {\n\t_empty: String\n}\n\n\"\"\"\nA signed-in caller \u2014 of one of the types `type` names, when it names some.\n\"\"\"\ndirective @authenticated(\n\t\"\"\"\n\tThe kinds of caller admitted: \"session\" or \"token\" by default. Any caller\n\twhen omitted.\n\t\"\"\"\n\ttype: [String!]\n) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM\n\ndirective @policy(policies: [[String!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM\n\ndirective @shareable on OBJECT | FIELD_DEFINITION\n\ndirective @link(url: String!, import: [String!]) on SCHEMA\n\n";
2
2
  //# sourceMappingURL=shared.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gBAAgB,2aAmB5B,CAAC"}
1
+ {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,gBAAgB,ypBAkB5B,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { GraphQLError, type GraphQLErrorOptions } from 'graphql';
2
+ export declare function createGraphQLError(message: string, options?: GraphQLErrorOptions): GraphQLError;
3
+ //# sourceMappingURL=create-error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-error.d.ts","sourceRoot":"","sources":["../../../src/utils/errors/create-error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAEjE,wBAAgB,kBAAkB,CACjC,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,mBAAmB,GAC3B,YAAY,CAEd"}
@@ -0,0 +1,20 @@
1
+ import { ErrorCode } from '@nxgt/shared-exceptions';
2
+ import { GraphQLError } from 'graphql';
3
+ /** The refusals a guard answers, each with its HTTP status. */
4
+ export type DenialCode = ErrorCode.Unauthenticated | ErrorCode.Forbidden | ErrorCode.NotFound;
5
+ /**
6
+ * A refusal, as the `GraphQLError` a client reads: `extensions.code` and
7
+ * `extensions.http.status` — `UNAUTHENTICATED` 401, `FORBIDDEN` 403,
8
+ * `NOT_FOUND` 404 — so any server answers it with its status, with or without
9
+ * `createMaskError`. What `@permission`, `@authenticated`, `requireUser` and
10
+ * `can` throw.
11
+ *
12
+ * `message` is an i18n key (the shared `errors.*` one by default). It is
13
+ * translated here with `@nxgt/i18n`'s resources, in the request's language
14
+ * when one is known; `createMaskError(translate)` and
15
+ * `createFormatError(translate)` translate it again with yours.
16
+ */
17
+ export declare function denial(code: DenialCode, message?: string): GraphQLError;
18
+ /** The i18n key of a `denial()`, or `undefined` for any other error. */
19
+ export declare function denialMessageKey(error: unknown): string | undefined;
20
+ //# sourceMappingURL=denial.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"denial.d.ts","sourceRoot":"","sources":["../../../src/utils/errors/denial.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,+DAA+D;AAC/D,MAAM,MAAM,UAAU,GACnB,SAAS,CAAC,eAAe,GACzB,SAAS,CAAC,SAAS,GACnB,SAAS,CAAC,QAAQ,CAAC;AAsBtB;;;;;;;;;;;GAWG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,YAAY,CAOvE;AAED,wEAAwE;AACxE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAInE"}
@@ -0,0 +1,12 @@
1
+ import type { ApolloServerOptions } from '@apollo/server';
2
+ import { type LocaleKey, type TranslationContext } from '@nxgt/i18n';
3
+ import type { GraphQLBaseContext } from '../../types';
4
+ /**
5
+ * Apollo Server's `formatError`, taught this repo's exceptions — the Apollo
6
+ * counterpart of `createMaskError`. `OryUnavailable` answers
7
+ * `SERVICE_UNAVAILABLE`, never a denial and never an opaque
8
+ * `INTERNAL_SERVER_ERROR`; a `denial()` keeps its code, and its message is
9
+ * translated with `translate`.
10
+ */
11
+ export declare function createFormatError<K extends LocaleKey, C extends GraphQLBaseContext = GraphQLBaseContext>(translate?: (message: K, context?: TranslationContext) => string, production?: boolean): ApolloServerOptions<C>['formatError'];
12
+ //# sourceMappingURL=format-error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format-error.d.ts","sourceRoot":"","sources":["../../../src/utils/errors/format-error.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAK1D,OAAO,EACN,KAAK,SAAS,EACd,KAAK,kBAAkB,EAEvB,MAAM,YAAY,CAAC;AAIpB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAItD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAChC,CAAC,SAAS,SAAS,EACnB,CAAC,SAAS,kBAAkB,GAAG,kBAAkB,EAEjD,SAAS,GAAE,CACV,OAAO,EAAE,CAAC,EACV,OAAO,CAAC,EAAE,kBAAkB,KACxB,MAAsB,EAC3B,UAAU,CAAC,EAAE,OAAO,GAClB,mBAAmB,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAgEvC"}
@@ -0,0 +1,6 @@
1
+ export * from './create-error';
2
+ export * from './denial';
3
+ export * from './format-error';
4
+ export * from './mask-error';
5
+ export * from './ory-unavailable';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/errors/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { type LocaleKey, type TranslationContext } from '@nxgt/i18n';
2
+ import type { MaskError } from 'graphql-yoga';
3
+ type Translate<K extends LocaleKey> = (message: K, context?: TranslationContext) => string;
4
+ /**
5
+ * Yoga's `maskedErrors.maskError`, taught this repo's exceptions.
6
+ *
7
+ * Yoga masks anything that is not a `GraphQLError` as "Unexpected error." —
8
+ * and `CustomException` extends `Error`, so out of the box a service's
9
+ * `notFound()` or `forbidden()` reaches the client as an opaque 500. This
10
+ * turns one into a `GraphQLError` with the translated message, the
11
+ * exception's `code` in `extensions`, and a matching HTTP status
12
+ * (`UNAUTHENTICATED` 401, `FORBIDDEN` 403, `NOT_FOUND` 404); a Mongoose error
13
+ * goes through `castError` first, as `createFormatError` does for Apollo.
14
+ * `OryUnavailable` (@nxgt/ory-sdk) becomes a 503 `SERVICE_UNAVAILABLE`, never
15
+ * a denial. A `denial()` — what the directives, `requireUser` and `can` throw
16
+ * — keeps its code and status, and its message is translated with `translate`. Everything else is masked as Yoga's default masks it: a
17
+ * `GraphQLError` thrown on purpose — or raised by validation — passes, and one
18
+ * that only wraps a plain `Error` a resolver threw is replaced by `message`,
19
+ * so an internal message never reaches the client.
20
+ *
21
+ * createYoga({ maskedErrors: { maskError: createMaskError(translate) } })
22
+ */
23
+ export declare function createMaskError<K extends LocaleKey>(translate?: Translate<K>): MaskError;
24
+ export {};
25
+ //# sourceMappingURL=mask-error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mask-error.d.ts","sourceRoot":"","sources":["../../../src/utils/errors/mask-error.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,SAAS,EACd,KAAK,kBAAkB,EAEvB,MAAM,YAAY,CAAC;AAIpB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAI9C,KAAK,SAAS,CAAC,CAAC,SAAS,SAAS,IAAI,CACrC,OAAO,EAAE,CAAC,EACV,OAAO,CAAC,EAAE,kBAAkB,KACxB,MAAM,CAAC;AAEZ;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,SAAS,EAClD,SAAS,GAAE,SAAS,CAAC,CAAC,CAAiB,GACrC,SAAS,CAmCX"}
@@ -0,0 +1,19 @@
1
+ import type { OryUnavailable } from '@nxgt/ory-sdk';
2
+ import { GraphQLError } from 'graphql';
3
+ /**
4
+ * Whether an error is `@nxgt/ory-sdk`'s `OryUnavailable` — Kratos, Hydra or
5
+ * Keto could not answer.
6
+ *
7
+ * By name and shape as well as by class: an install that resolves two copies
8
+ * of `@nxgt/ory-sdk` (one under this package, one under the app) throws an
9
+ * `OryUnavailable` this module's `instanceof` does not recognise, and an
10
+ * outage that slips past here is masked as a 500 instead of answered as a 503.
11
+ */
12
+ export declare function isOryUnavailable(error: unknown): error is OryUnavailable;
13
+ /**
14
+ * A 503 the client can read as one — a real `GraphQLError` so Yoga's masking
15
+ * leaves it alone, `extensions.http.status` so the transport says 503 too.
16
+ * Never a denial: an outage must not read as "not signed in" or "not allowed".
17
+ */
18
+ export declare function serviceUnavailableError(error: OryUnavailable): GraphQLError;
19
+ //# sourceMappingURL=ory-unavailable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ory-unavailable.d.ts","sourceRoot":"","sources":["../../../src/utils/errors/ory-unavailable.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,cAAc,CAIxE;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,cAAc,GAAG,YAAY,CAQ3E"}
@@ -1,7 +1,7 @@
1
1
  export * from './change-resolvers.utils';
2
2
  export * from './codegen.utils';
3
3
  export * from './data-loader.utils';
4
- export * from './errors.utils';
4
+ export * from './errors';
5
5
  export * from './publish';
6
6
  export * from './sandbox';
7
7
  export * from './schema.utils';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC"}
@@ -8,7 +8,7 @@ export interface ResolvedWsUser {
8
8
  }
9
9
  /**
10
10
  * Resolves the authenticated TokenPrincipal for a `graphql-ws` connection - the
11
- * WS-transport equivalent of the HTTP-path `useAuth()`/`useGenericAuth()`
11
+ * WS-transport equivalent of the HTTP-path `useOryAuth()` / `useAuth()`
12
12
  * plugins. There is no gateway hop for WS connections, so each app performs
13
13
  * the OAuth introspection call itself using its own `auth` client.
14
14
  *
package/docs/README.md ADDED
@@ -0,0 +1,13 @@
1
+ # `@nxgt/shared-graphql` docs
2
+
3
+ The package's [README](../README.md) is the short version, with one example
4
+ per section. These pages are the long one.
5
+
6
+ | Page | Read it when |
7
+ | --- | --- |
8
+ | [Migrating to 3.0](./guide/migrating-to-3.md) | you upgrade from 2.x: every break, with the code before and after |
9
+ | [Authentication](./guide/authentication.md) | you wire `useOryAuth`, `useAuth` behind a trusted gateway, or `@authenticated(type:)` |
10
+ | [Permissions](./guide/permissions.md) | you guard a field with `@permission`, or call `requireUser` / `can` |
11
+ | [Errors](./guide/errors.md) | you decide what a client receives: denials, `createMaskError`, `createFormatError`, outages |
12
+ | [Troubleshooting](./troubleshooting.md) | you have an error message in hand |
13
+ | [Roadmap](./roadmap.md) | you want to know what is next, and what shipped in 3.0 |