@nxgt/janus-graphql 0.2.0 → 0.4.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 -24
- 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 +10 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/errors.d.ts +26 -3
- package/dist/errors.d.ts.map +1 -1
- package/dist/index.d.ts +10 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +173 -30
- package/dist/index.js.map +12 -7
- package/dist/options.d.ts +17 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/plugin.d.ts +16 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/renewal.d.ts +30 -0
- package/dist/renewal.d.ts.map +1 -0
- package/dist/report.d.ts +22 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/types.d.ts +6 -2
- package/dist/types.d.ts.map +1 -1
- package/docs/README.md +4 -3
- package/docs/guide/context.md +34 -3
- package/docs/guide/errors.md +68 -4
- package/docs/guide/step-up.md +7 -3
- package/docs/guide/subscriptions.md +255 -0
- package/docs/roadmap.md +29 -8
- package/docs/troubleshooting.md +130 -9
- package/package.json +12 -3
package/dist/index.js
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
// src/directives/apply.ts
|
|
2
|
-
import { MapperKind, mapSchema } from "@graphql-tools/utils";
|
|
3
|
-
import { defaultFieldResolver } from "graphql";
|
|
4
|
-
|
|
5
1
|
// src/context.ts
|
|
6
2
|
import {
|
|
7
3
|
systemClock
|
|
@@ -38,14 +34,15 @@ function memoized(access) {
|
|
|
38
34
|
// src/context.ts
|
|
39
35
|
var checks = new WeakMap;
|
|
40
36
|
var clocks = new WeakMap;
|
|
37
|
+
var answers = new WeakMap;
|
|
41
38
|
function createJanusContext(request, options) {
|
|
42
|
-
const { auth, access, type, clock } = options;
|
|
39
|
+
const { auth, access, type, clock, caller = "useJanus()" } = options;
|
|
43
40
|
let answer;
|
|
44
41
|
let asked = false;
|
|
45
42
|
const authenticated = () => {
|
|
46
43
|
if (!asked) {
|
|
47
44
|
asked = true;
|
|
48
|
-
answer = request === undefined ? Promise.reject(new TypeError(
|
|
45
|
+
answer = request === undefined ? Promise.reject(new TypeError(`${caller}: the GraphQL context has no request to authenticate — build the context from an HTTP request, or accept a graphql-ws connection with janusConnection().onConnect`)) : auth.authenticate(request, type === undefined ? undefined : { type });
|
|
49
46
|
}
|
|
50
47
|
return answer;
|
|
51
48
|
};
|
|
@@ -63,8 +60,12 @@ function createJanusContext(request, options) {
|
|
|
63
60
|
}
|
|
64
61
|
if (clock !== undefined)
|
|
65
62
|
clocks.set(janus, clock);
|
|
63
|
+
answers.set(janus, () => asked ? answer : undefined);
|
|
66
64
|
return janus;
|
|
67
65
|
}
|
|
66
|
+
function answerOf(janus) {
|
|
67
|
+
return answers.get(janus)?.();
|
|
68
|
+
}
|
|
68
69
|
function clockOf(janus) {
|
|
69
70
|
return clocks.get(janus) ?? systemClock;
|
|
70
71
|
}
|
|
@@ -83,12 +84,118 @@ function janusOf(ctx, caller) {
|
|
|
83
84
|
return janus;
|
|
84
85
|
}
|
|
85
86
|
|
|
87
|
+
// src/options.ts
|
|
88
|
+
function checkWiring(caller, { auth, access, clock }) {
|
|
89
|
+
if (typeof auth?.authenticate !== "function") {
|
|
90
|
+
throw new TypeError(`${caller}: auth is not what janus() answered — pass { auth }`);
|
|
91
|
+
}
|
|
92
|
+
if (access !== undefined && typeof access?.can !== "function") {
|
|
93
|
+
throw new TypeError(`${caller}: access is not what permissions() answered — pass { auth, access }`);
|
|
94
|
+
}
|
|
95
|
+
if (clock !== undefined && typeof clock?.now !== "function") {
|
|
96
|
+
throw new TypeError(`${caller}: clock is not a Clock — pass the clock given to janus(), or leave it out for the system clock`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/connection/credential.ts
|
|
101
|
+
var BEARER = /^\s*bearer(?:\s|$)/i;
|
|
102
|
+
function credentialOf(ctx, upgrade) {
|
|
103
|
+
const params = ctx.connectionParams;
|
|
104
|
+
if (params !== undefined && Object.hasOwn(params, "authorization")) {
|
|
105
|
+
const { authorization } = params;
|
|
106
|
+
if (typeof authorization !== "string")
|
|
107
|
+
return null;
|
|
108
|
+
if (BEARER.test(authorization))
|
|
109
|
+
return { authorization };
|
|
110
|
+
}
|
|
111
|
+
const request = upgrade === undefined ? ctx.extra?.request : upgrade(ctx);
|
|
112
|
+
return request ?? null;
|
|
113
|
+
}
|
|
114
|
+
var accepted = new WeakMap;
|
|
115
|
+
function accept(ctx, credential) {
|
|
116
|
+
const { extra } = ctx;
|
|
117
|
+
if (typeof extra !== "object" || extra === null) {
|
|
118
|
+
throw new TypeError("janusConnection(): the connection's extra is not an object — pass the context graphql-ws gave onConnect");
|
|
119
|
+
}
|
|
120
|
+
accepted.set(extra, credential);
|
|
121
|
+
}
|
|
122
|
+
function acceptedOf(context) {
|
|
123
|
+
const extra = context?.extra;
|
|
124
|
+
if (typeof extra !== "object" || extra === null)
|
|
125
|
+
return;
|
|
126
|
+
return accepted.get(extra);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/connection/connection.ts
|
|
130
|
+
function janusConnection(options) {
|
|
131
|
+
const { auth, access, type, clock } = options;
|
|
132
|
+
checkWiring("janusConnection()", { auth, access, clock });
|
|
133
|
+
if (options.upgrade !== undefined && typeof options.upgrade !== "function") {
|
|
134
|
+
throw new TypeError("janusConnection(): upgrade is not a function — pass (ctx) => the upgrade request, or leave it out to read ctx.extra.request");
|
|
135
|
+
}
|
|
136
|
+
const upgrade = options.upgrade?.bind(options);
|
|
137
|
+
const wiring = {
|
|
138
|
+
auth,
|
|
139
|
+
access,
|
|
140
|
+
type,
|
|
141
|
+
clock,
|
|
142
|
+
caller: "janusConnection().context"
|
|
143
|
+
};
|
|
144
|
+
return {
|
|
145
|
+
onConnect: async (ctx) => {
|
|
146
|
+
const credential = credentialOf(ctx, upgrade);
|
|
147
|
+
if (credential === null)
|
|
148
|
+
return false;
|
|
149
|
+
const current = await auth.authenticate(credential, type === undefined ? undefined : { type });
|
|
150
|
+
if (current === null)
|
|
151
|
+
return false;
|
|
152
|
+
accept(ctx, credential);
|
|
153
|
+
return true;
|
|
154
|
+
},
|
|
155
|
+
context: (ctx) => ({
|
|
156
|
+
janus: createJanusContext(acceptedOf(ctx), wiring)
|
|
157
|
+
})
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
// src/directives/apply.ts
|
|
161
|
+
import { MapperKind, mapSchema } from "@graphql-tools/utils";
|
|
162
|
+
import { defaultFieldResolver } from "graphql";
|
|
163
|
+
|
|
86
164
|
// src/errors.ts
|
|
165
|
+
import { JanusError as JanusError2, statusOf as statusOf2 } from "@nxgt/janus";
|
|
166
|
+
import { GraphQLError as GraphQLError2 } from "graphql";
|
|
167
|
+
|
|
168
|
+
// src/report.ts
|
|
87
169
|
import { JanusError, statusOf } from "@nxgt/janus";
|
|
88
170
|
import { GraphQLError } from "graphql";
|
|
171
|
+
function janusErrorIn(error) {
|
|
172
|
+
let current = error;
|
|
173
|
+
while (current instanceof GraphQLError)
|
|
174
|
+
current = current.originalError;
|
|
175
|
+
return current instanceof JanusError ? current : null;
|
|
176
|
+
}
|
|
177
|
+
function reporting(report) {
|
|
178
|
+
const seen = new WeakSet;
|
|
179
|
+
return (error) => {
|
|
180
|
+
const janusError = janusErrorIn(error);
|
|
181
|
+
if (janusError === null || seen.has(janusError))
|
|
182
|
+
return;
|
|
183
|
+
if (statusOf(janusError.code) < 500)
|
|
184
|
+
return;
|
|
185
|
+
seen.add(janusError);
|
|
186
|
+
const warn = (failure) => process.emitWarning(`janusMaskError: report failed on ${janusError.code}: ${failure instanceof Error ? failure.name : typeof failure}`);
|
|
187
|
+
try {
|
|
188
|
+
Promise.resolve(report(janusError)).then(undefined, warn);
|
|
189
|
+
} catch (failure) {
|
|
190
|
+
warn(failure);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// src/errors.ts
|
|
89
196
|
function denial(code, message) {
|
|
90
197
|
const [status, fallback] = denialOf(code);
|
|
91
|
-
return new
|
|
198
|
+
return new GraphQLError2(message ?? fallback, {
|
|
92
199
|
extensions: { code, http: { status } }
|
|
93
200
|
});
|
|
94
201
|
}
|
|
@@ -103,9 +210,9 @@ function denialOf(code) {
|
|
|
103
210
|
}
|
|
104
211
|
}
|
|
105
212
|
function janusGraphQLError(error) {
|
|
106
|
-
const status =
|
|
213
|
+
const status = statusOf2(error.code);
|
|
107
214
|
const code = error.code === "STORE_FAILED" ? "SERVICE_UNAVAILABLE" : error.code;
|
|
108
|
-
return new
|
|
215
|
+
return new GraphQLError2(messageOf(status), {
|
|
109
216
|
originalError: error,
|
|
110
217
|
extensions: { code, http: { status }, ...actionable(error) }
|
|
111
218
|
});
|
|
@@ -142,14 +249,17 @@ function actionable(error) {
|
|
|
142
249
|
}
|
|
143
250
|
}
|
|
144
251
|
function rethrown(error) {
|
|
145
|
-
throw error instanceof
|
|
252
|
+
throw error instanceof JanusError2 ? janusGraphQLError(error) : error;
|
|
146
253
|
}
|
|
147
|
-
function janusMaskError(
|
|
254
|
+
function janusMaskError(options = {}) {
|
|
255
|
+
const { report, fallback = masked } = typeof options === "function" ? { fallback: options } : options;
|
|
256
|
+
const reported = report === undefined ? undefined : reporting(report);
|
|
148
257
|
return (error, message, isDev) => {
|
|
149
|
-
|
|
150
|
-
|
|
258
|
+
reported?.(error);
|
|
259
|
+
const original = error instanceof GraphQLError2 ? error.originalError : error;
|
|
260
|
+
if (original instanceof JanusError2) {
|
|
151
261
|
const answer = janusGraphQLError(original);
|
|
152
|
-
return error instanceof
|
|
262
|
+
return error instanceof GraphQLError2 ? new GraphQLError2(answer.message, {
|
|
153
263
|
nodes: error.nodes ?? null,
|
|
154
264
|
path: error.path ?? null,
|
|
155
265
|
originalError: original,
|
|
@@ -160,18 +270,18 @@ function janusMaskError(fallback = masked) {
|
|
|
160
270
|
};
|
|
161
271
|
}
|
|
162
272
|
function masked(error, message) {
|
|
163
|
-
if (error instanceof
|
|
273
|
+
if (error instanceof GraphQLError2) {
|
|
164
274
|
const original = error.originalError;
|
|
165
|
-
if (original === undefined || original instanceof
|
|
275
|
+
if (original === undefined || original instanceof GraphQLError2) {
|
|
166
276
|
return error;
|
|
167
277
|
}
|
|
168
|
-
return new
|
|
278
|
+
return new GraphQLError2(message, {
|
|
169
279
|
nodes: error.nodes ?? null,
|
|
170
280
|
path: error.path ?? null,
|
|
171
281
|
extensions: { code: "INTERNAL_SERVER_ERROR" }
|
|
172
282
|
});
|
|
173
283
|
}
|
|
174
|
-
return new
|
|
284
|
+
return new GraphQLError2(message, {
|
|
175
285
|
extensions: { code: "INTERNAL_SERVER_ERROR" }
|
|
176
286
|
});
|
|
177
287
|
}
|
|
@@ -625,28 +735,59 @@ async function can(ctx, permission, object, ...options) {
|
|
|
625
735
|
const subject = await janus.user().then(undefined, rethrown);
|
|
626
736
|
return check(subject, permission, object, options[0]).then(undefined, rethrown);
|
|
627
737
|
}
|
|
738
|
+
// src/renewal.ts
|
|
739
|
+
var contexts = new WeakMap;
|
|
740
|
+
function track(request, janus) {
|
|
741
|
+
const known = contexts.get(request);
|
|
742
|
+
if (known === undefined)
|
|
743
|
+
contexts.set(request, [janus]);
|
|
744
|
+
else
|
|
745
|
+
known.push(janus);
|
|
746
|
+
}
|
|
747
|
+
async function resendRenewed({ request, response }, cookie) {
|
|
748
|
+
const known = contexts.get(request);
|
|
749
|
+
if (cookie === undefined || known === undefined)
|
|
750
|
+
return;
|
|
751
|
+
for (const janus of known) {
|
|
752
|
+
const current = await answerOf(janus)?.then((answer) => answer, () => null);
|
|
753
|
+
if (current?.renewed === true && cookieOf(request, cookie.name) === current.token && !sets(response, cookie.name)) {
|
|
754
|
+
response.headers.append("Set-Cookie", cookie.serialize(current.token, current.session));
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
function cookieOf(request, name) {
|
|
759
|
+
const header = request.headers.get("cookie");
|
|
760
|
+
if (header === null)
|
|
761
|
+
return null;
|
|
762
|
+
for (const pair of header.split(";")) {
|
|
763
|
+
const at = pair.indexOf("=");
|
|
764
|
+
if (at !== -1 && pair.slice(0, at).trim() === name) {
|
|
765
|
+
return pair.slice(at + 1).trim();
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
return null;
|
|
769
|
+
}
|
|
770
|
+
function sets(response, name) {
|
|
771
|
+
return response.headers.getSetCookie().some((value) => value.startsWith(`${name}=`));
|
|
772
|
+
}
|
|
773
|
+
|
|
628
774
|
// src/plugin.ts
|
|
629
775
|
function useJanus(options) {
|
|
630
776
|
const { auth, access, type, clock, loaders, conditions } = options;
|
|
631
|
-
|
|
632
|
-
throw new TypeError("useJanus(): auth is not what janus() answered — pass { auth }");
|
|
633
|
-
}
|
|
634
|
-
if (access !== undefined && typeof access?.can !== "function") {
|
|
635
|
-
throw new TypeError("useJanus(): access is not what permissions() answered — pass { auth, access }");
|
|
636
|
-
}
|
|
637
|
-
if (clock !== undefined && typeof clock?.now !== "function") {
|
|
638
|
-
throw new TypeError("useJanus(): clock is not a Clock — pass the clock given to janus(), or leave it out for the system clock");
|
|
639
|
-
}
|
|
777
|
+
checkWiring("useJanus()", { auth, access, clock });
|
|
640
778
|
const applied = new WeakSet;
|
|
641
779
|
return {
|
|
642
780
|
onContextBuilding: ({ context, extendContext }) => {
|
|
643
781
|
const { request } = context;
|
|
644
|
-
const janus = createJanusContext(request, {
|
|
782
|
+
const janus = createJanusContext(request ?? acceptedOf(context), {
|
|
645
783
|
auth,
|
|
646
784
|
access,
|
|
647
785
|
type,
|
|
648
786
|
clock
|
|
649
787
|
});
|
|
788
|
+
if (typeof request === "object" && request !== null) {
|
|
789
|
+
track(request, janus);
|
|
790
|
+
}
|
|
650
791
|
extendContext({ janus });
|
|
651
792
|
},
|
|
652
793
|
onSchemaChange: ({ schema, replaceSchema }) => {
|
|
@@ -661,7 +802,8 @@ function useJanus(options) {
|
|
|
661
802
|
});
|
|
662
803
|
applied.add(next);
|
|
663
804
|
replaceSchema(next);
|
|
664
|
-
}
|
|
805
|
+
},
|
|
806
|
+
onResponse: (payload) => resendRenewed(payload, auth.cookie)
|
|
665
807
|
};
|
|
666
808
|
}
|
|
667
809
|
// src/sdl.ts
|
|
@@ -714,6 +856,7 @@ export {
|
|
|
714
856
|
applyJanusDirectives,
|
|
715
857
|
can,
|
|
716
858
|
denial,
|
|
859
|
+
janusConnection,
|
|
717
860
|
janusGraphQLError,
|
|
718
861
|
janusMaskError,
|
|
719
862
|
janusTypeDefs,
|
|
@@ -722,5 +865,5 @@ export {
|
|
|
722
865
|
useJanus
|
|
723
866
|
};
|
|
724
867
|
|
|
725
|
-
//# debugId=
|
|
868
|
+
//# debugId=33AAA17AADE0081364756E2164756E21
|
|
726
869
|
//# sourceMappingURL=index.js.map
|