@graphql-yoga/plugin-jwt 3.7.0 → 3.7.1

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/cjs/plugin.js CHANGED
@@ -4,6 +4,7 @@ exports.useJWT = useJWT;
4
4
  const tslib_1 = require("tslib");
5
5
  const graphql_1 = require("graphql");
6
6
  const jsonwebtoken_1 = tslib_1.__importDefault(require("jsonwebtoken"));
7
+ const promise_helpers_1 = require("@whatwg-node/promise-helpers");
7
8
  const config_js_1 = require("./config.js");
8
9
  const utils_js_1 = require("./utils.js");
9
10
  function useJWT(options) {
@@ -12,30 +13,60 @@ function useJWT(options) {
12
13
  const payloadByContext = new WeakMap();
13
14
  const payloadByRequest = new WeakMap();
14
15
  const validatedRequestAndContextSet = new WeakSet();
15
- const lookupToken = async (payload) => {
16
- for (const lookupLocation of normalizedOptions.tokenLookupLocations) {
17
- const token = await lookupLocation(payload);
18
- if (token) {
19
- return token;
16
+ const lookupToken = (payload) => {
17
+ const iterator = normalizedOptions.tokenLookupLocations[Symbol.iterator]();
18
+ function iterate() {
19
+ const { done, value } = iterator.next();
20
+ if (done) {
21
+ return null;
20
22
  }
23
+ if (!value) {
24
+ return iterate();
25
+ }
26
+ return (0, promise_helpers_1.handleMaybePromise)(() => value(payload), result => {
27
+ if (result?.token) {
28
+ return result;
29
+ }
30
+ return iterate();
31
+ });
21
32
  }
22
- return null;
33
+ return iterate();
23
34
  };
24
- const getSigningKey = async (kid) => {
25
- for (const provider of normalizedOptions.signingKeyProviders) {
26
- try {
27
- const key = await provider(kid);
35
+ const getSigningKey = (kid) => {
36
+ const iterator = normalizedOptions.signingKeyProviders[Symbol.iterator]();
37
+ function iterate() {
38
+ const { done, value } = iterator.next();
39
+ if (done) {
40
+ return null;
41
+ }
42
+ if (!value) {
43
+ return iterate();
44
+ }
45
+ return (0, promise_helpers_1.handleMaybePromise)(() => value(kid), key => {
28
46
  if (key) {
29
47
  return key;
30
48
  }
31
- }
32
- catch (e) {
49
+ return iterate();
50
+ }, e => {
33
51
  logger.error(`Failed to fetch signing key from signing provided:`, e);
34
- }
52
+ return iterate();
53
+ });
35
54
  }
36
- return null;
55
+ return iterate();
37
56
  };
38
- const lookupAndValidate = async (payload) => {
57
+ function handleError(e) {
58
+ // User-facing errors should be handled based on the configuration.
59
+ // These errors are handled based on the value of "reject.invalidToken" config.
60
+ if (e instanceof graphql_1.GraphQLError) {
61
+ if (normalizedOptions.reject.invalidToken) {
62
+ throw e;
63
+ }
64
+ return;
65
+ }
66
+ // Server/internal errors should be thrown, so they can be handled by the error handler and be masked.
67
+ throw e;
68
+ }
69
+ const lookupAndValidate = (payload) => {
39
70
  // Mark the context and request as validated, so we don't process them again.
40
71
  if (payload.serverContext) {
41
72
  if (validatedRequestAndContextSet.has(payload.serverContext)) {
@@ -50,16 +81,15 @@ function useJWT(options) {
50
81
  validatedRequestAndContextSet.add(payload.request);
51
82
  }
52
83
  // Try to find token in request, and reject the request if needed.
53
- const lookupResult = await lookupToken(payload);
54
- if (!lookupResult) {
55
- // If token is missing, we can reject the request based on the configuration.
56
- if (normalizedOptions.reject.missingToken) {
57
- logger.debug(`Token is missing in incoming HTTP request, JWT plugin failed to locate.`);
58
- throw (0, utils_js_1.unauthorizedError)(`Unauthenticated`);
84
+ return (0, promise_helpers_1.handleMaybePromise)(() => lookupToken(payload), lookupResult => {
85
+ if (!lookupResult) {
86
+ // If token is missing, we can reject the request based on the configuration.
87
+ if (normalizedOptions.reject.missingToken) {
88
+ logger.debug(`Token is missing in incoming HTTP request, JWT plugin failed to locate.`);
89
+ throw (0, utils_js_1.unauthorizedError)(`Unauthenticated`);
90
+ }
91
+ return;
59
92
  }
60
- return;
61
- }
62
- try {
63
93
  // Decode the token first, in order to get the key id to use.
64
94
  let decodedToken;
65
95
  try {
@@ -74,48 +104,38 @@ function useJWT(options) {
74
104
  throw (0, utils_js_1.badRequestError)(`Invalid authentication token provided`);
75
105
  }
76
106
  // Fetch the signing key based on the key id.
77
- const signingKey = await getSigningKey(decodedToken?.header.kid);
78
- if (!signingKey) {
79
- logger.warn(`Signing key is not available for the key id: ${decodedToken?.header.kid}. Please make sure signing key providers are configured correctly.`);
80
- throw Error(`Authentication is not available at the moment.`);
81
- }
82
- // Verify the token with the signing key.
83
- const verified = await verify(logger, lookupResult.token, signingKey, normalizedOptions.tokenVerification);
84
- if (!verified) {
85
- logger.debug(`Token failed to verify, JWT plugin failed to authenticate.`);
86
- throw (0, utils_js_1.unauthorizedError)(`Unauthenticated`);
87
- }
88
- if (verified) {
89
- // Link the verified payload with the request (see `onContextBuilding` for the reading part)
90
- const pluginPayload = {
91
- payload: verified,
92
- token: {
93
- value: lookupResult.token,
94
- prefix: lookupResult.prefix,
95
- },
96
- };
97
- if (payload.request) {
98
- payloadByRequest.set(payload.request, pluginPayload);
99
- }
100
- else {
101
- payloadByContext.set(payload.serverContext, pluginPayload);
102
- }
103
- }
104
- }
105
- catch (e) {
106
- // User-facing errors should be handled based on the configuration.
107
- // These errors are handled based on the value of "reject.invalidToken" config.
108
- if (e instanceof graphql_1.GraphQLError) {
109
- if (normalizedOptions.reject.invalidToken) {
110
- throw e;
107
+ return (0, promise_helpers_1.handleMaybePromise)(() => getSigningKey(decodedToken?.header.kid), signingKey => {
108
+ if (!signingKey) {
109
+ logger.warn(`Signing key is not available for the key id: ${decodedToken?.header.kid}. Please make sure signing key providers are configured correctly.`);
110
+ throw Error(`Authentication is not available at the moment.`);
111
111
  }
112
- return;
113
- }
114
- // Server/internal errors should be thrown, so they can be handled by the error handler and be masked.
115
- throw e;
116
- }
112
+ // Verify the token with the signing key.
113
+ return (0, promise_helpers_1.handleMaybePromise)(() => verify(logger, lookupResult.token, signingKey, normalizedOptions.tokenVerification), verified => {
114
+ if (!verified) {
115
+ logger.debug(`Token failed to verify, JWT plugin failed to authenticate.`);
116
+ throw (0, utils_js_1.unauthorizedError)(`Unauthenticated`);
117
+ }
118
+ if (verified) {
119
+ // Link the verified payload with the request (see `onContextBuilding` for the reading part)
120
+ const pluginPayload = {
121
+ payload: verified,
122
+ token: {
123
+ value: lookupResult.token,
124
+ prefix: lookupResult.prefix,
125
+ },
126
+ };
127
+ if (payload.request) {
128
+ payloadByRequest.set(payload.request, pluginPayload);
129
+ }
130
+ else {
131
+ payloadByContext.set(payload.serverContext, pluginPayload);
132
+ }
133
+ }
134
+ }, handleError);
135
+ }, handleError);
136
+ }, handleError);
117
137
  };
118
- async function ensureContext({ request, url, serverContext, extendContext, }) {
138
+ function ensureContext({ request, url, serverContext, extendContext, }) {
119
139
  if (normalizedOptions.extendContextFieldName === null) {
120
140
  return;
121
141
  }
@@ -123,21 +143,24 @@ function useJWT(options) {
123
143
  return;
124
144
  }
125
145
  // Ensure the request has been validated before extending the context.
126
- await lookupAndValidate({
146
+ return (0, promise_helpers_1.handleMaybePromise)(() => lookupAndValidate({
127
147
  request,
128
148
  url,
129
149
  serverContext,
150
+ }), () => {
151
+ // Then check the result
152
+ const result = request
153
+ ? payloadByRequest.get(request)
154
+ : payloadByContext.get(serverContext);
155
+ if (result && normalizedOptions.extendContextFieldName) {
156
+ extendContext({
157
+ [normalizedOptions.extendContextFieldName]: {
158
+ payload: result.payload,
159
+ token: result.token,
160
+ },
161
+ });
162
+ }
130
163
  });
131
- // Then check the result
132
- const result = request ? payloadByRequest.get(request) : payloadByContext.get(serverContext);
133
- if (result && normalizedOptions.extendContextFieldName) {
134
- extendContext({
135
- [normalizedOptions.extendContextFieldName]: {
136
- payload: result.payload,
137
- token: result.token,
138
- },
139
- });
140
- }
141
164
  }
142
165
  let fetchAPI;
143
166
  return {
package/cjs/utils.js CHANGED
@@ -10,6 +10,7 @@ exports.createRemoteJwksSigningKeyProvider = createRemoteJwksSigningKeyProvider;
10
10
  const graphql_yoga_1 = require("graphql-yoga");
11
11
  const jwks_rsa_1 = require("jwks-rsa");
12
12
  require("@whatwg-node/server-plugin-cookies");
13
+ const promise_helpers_1 = require("@whatwg-node/promise-helpers");
13
14
  function extractFromHeader(options) {
14
15
  return ({ request }) => {
15
16
  if (!request) {
@@ -44,7 +45,7 @@ function extractFromHeader(options) {
44
45
  };
45
46
  }
46
47
  function extractFromCookie(options) {
47
- return async ({ request }) => {
48
+ return ({ request }) => {
48
49
  if (!request) {
49
50
  return;
50
51
  }
@@ -52,14 +53,15 @@ function extractFromCookie(options) {
52
53
  if (!cookieStore) {
53
54
  throw new Error('Cookie store is not available on request. Please make sure to configure the cookie plugin.');
54
55
  }
55
- const cookie = await cookieStore.get(options.name);
56
- if (!cookie) {
57
- return undefined;
58
- }
59
- return {
60
- prefix: undefined,
61
- token: cookie.value,
62
- };
56
+ return (0, promise_helpers_1.handleMaybePromise)(() => cookieStore.get(options.name), cookie => {
57
+ if (!cookie) {
58
+ return;
59
+ }
60
+ return {
61
+ prefix: undefined,
62
+ token: cookie.value,
63
+ };
64
+ });
63
65
  };
64
66
  }
65
67
  function extractFromConnectionParams(options) {
package/esm/plugin.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { GraphQLError } from 'graphql';
2
2
  import jsonwebtoken from 'jsonwebtoken';
3
+ import { handleMaybePromise } from '@whatwg-node/promise-helpers';
3
4
  import { normalizeConfig } from './config.js';
4
5
  import { badRequestError, unauthorizedError } from './utils.js';
5
6
  export function useJWT(options) {
@@ -8,30 +9,60 @@ export function useJWT(options) {
8
9
  const payloadByContext = new WeakMap();
9
10
  const payloadByRequest = new WeakMap();
10
11
  const validatedRequestAndContextSet = new WeakSet();
11
- const lookupToken = async (payload) => {
12
- for (const lookupLocation of normalizedOptions.tokenLookupLocations) {
13
- const token = await lookupLocation(payload);
14
- if (token) {
15
- return token;
12
+ const lookupToken = (payload) => {
13
+ const iterator = normalizedOptions.tokenLookupLocations[Symbol.iterator]();
14
+ function iterate() {
15
+ const { done, value } = iterator.next();
16
+ if (done) {
17
+ return null;
16
18
  }
19
+ if (!value) {
20
+ return iterate();
21
+ }
22
+ return handleMaybePromise(() => value(payload), result => {
23
+ if (result?.token) {
24
+ return result;
25
+ }
26
+ return iterate();
27
+ });
17
28
  }
18
- return null;
29
+ return iterate();
19
30
  };
20
- const getSigningKey = async (kid) => {
21
- for (const provider of normalizedOptions.signingKeyProviders) {
22
- try {
23
- const key = await provider(kid);
31
+ const getSigningKey = (kid) => {
32
+ const iterator = normalizedOptions.signingKeyProviders[Symbol.iterator]();
33
+ function iterate() {
34
+ const { done, value } = iterator.next();
35
+ if (done) {
36
+ return null;
37
+ }
38
+ if (!value) {
39
+ return iterate();
40
+ }
41
+ return handleMaybePromise(() => value(kid), key => {
24
42
  if (key) {
25
43
  return key;
26
44
  }
27
- }
28
- catch (e) {
45
+ return iterate();
46
+ }, e => {
29
47
  logger.error(`Failed to fetch signing key from signing provided:`, e);
30
- }
48
+ return iterate();
49
+ });
31
50
  }
32
- return null;
51
+ return iterate();
33
52
  };
34
- const lookupAndValidate = async (payload) => {
53
+ function handleError(e) {
54
+ // User-facing errors should be handled based on the configuration.
55
+ // These errors are handled based on the value of "reject.invalidToken" config.
56
+ if (e instanceof GraphQLError) {
57
+ if (normalizedOptions.reject.invalidToken) {
58
+ throw e;
59
+ }
60
+ return;
61
+ }
62
+ // Server/internal errors should be thrown, so they can be handled by the error handler and be masked.
63
+ throw e;
64
+ }
65
+ const lookupAndValidate = (payload) => {
35
66
  // Mark the context and request as validated, so we don't process them again.
36
67
  if (payload.serverContext) {
37
68
  if (validatedRequestAndContextSet.has(payload.serverContext)) {
@@ -46,16 +77,15 @@ export function useJWT(options) {
46
77
  validatedRequestAndContextSet.add(payload.request);
47
78
  }
48
79
  // Try to find token in request, and reject the request if needed.
49
- const lookupResult = await lookupToken(payload);
50
- if (!lookupResult) {
51
- // If token is missing, we can reject the request based on the configuration.
52
- if (normalizedOptions.reject.missingToken) {
53
- logger.debug(`Token is missing in incoming HTTP request, JWT plugin failed to locate.`);
54
- throw unauthorizedError(`Unauthenticated`);
80
+ return handleMaybePromise(() => lookupToken(payload), lookupResult => {
81
+ if (!lookupResult) {
82
+ // If token is missing, we can reject the request based on the configuration.
83
+ if (normalizedOptions.reject.missingToken) {
84
+ logger.debug(`Token is missing in incoming HTTP request, JWT plugin failed to locate.`);
85
+ throw unauthorizedError(`Unauthenticated`);
86
+ }
87
+ return;
55
88
  }
56
- return;
57
- }
58
- try {
59
89
  // Decode the token first, in order to get the key id to use.
60
90
  let decodedToken;
61
91
  try {
@@ -70,48 +100,38 @@ export function useJWT(options) {
70
100
  throw badRequestError(`Invalid authentication token provided`);
71
101
  }
72
102
  // Fetch the signing key based on the key id.
73
- const signingKey = await getSigningKey(decodedToken?.header.kid);
74
- if (!signingKey) {
75
- logger.warn(`Signing key is not available for the key id: ${decodedToken?.header.kid}. Please make sure signing key providers are configured correctly.`);
76
- throw Error(`Authentication is not available at the moment.`);
77
- }
78
- // Verify the token with the signing key.
79
- const verified = await verify(logger, lookupResult.token, signingKey, normalizedOptions.tokenVerification);
80
- if (!verified) {
81
- logger.debug(`Token failed to verify, JWT plugin failed to authenticate.`);
82
- throw unauthorizedError(`Unauthenticated`);
83
- }
84
- if (verified) {
85
- // Link the verified payload with the request (see `onContextBuilding` for the reading part)
86
- const pluginPayload = {
87
- payload: verified,
88
- token: {
89
- value: lookupResult.token,
90
- prefix: lookupResult.prefix,
91
- },
92
- };
93
- if (payload.request) {
94
- payloadByRequest.set(payload.request, pluginPayload);
95
- }
96
- else {
97
- payloadByContext.set(payload.serverContext, pluginPayload);
98
- }
99
- }
100
- }
101
- catch (e) {
102
- // User-facing errors should be handled based on the configuration.
103
- // These errors are handled based on the value of "reject.invalidToken" config.
104
- if (e instanceof GraphQLError) {
105
- if (normalizedOptions.reject.invalidToken) {
106
- throw e;
103
+ return handleMaybePromise(() => getSigningKey(decodedToken?.header.kid), signingKey => {
104
+ if (!signingKey) {
105
+ logger.warn(`Signing key is not available for the key id: ${decodedToken?.header.kid}. Please make sure signing key providers are configured correctly.`);
106
+ throw Error(`Authentication is not available at the moment.`);
107
107
  }
108
- return;
109
- }
110
- // Server/internal errors should be thrown, so they can be handled by the error handler and be masked.
111
- throw e;
112
- }
108
+ // Verify the token with the signing key.
109
+ return handleMaybePromise(() => verify(logger, lookupResult.token, signingKey, normalizedOptions.tokenVerification), verified => {
110
+ if (!verified) {
111
+ logger.debug(`Token failed to verify, JWT plugin failed to authenticate.`);
112
+ throw unauthorizedError(`Unauthenticated`);
113
+ }
114
+ if (verified) {
115
+ // Link the verified payload with the request (see `onContextBuilding` for the reading part)
116
+ const pluginPayload = {
117
+ payload: verified,
118
+ token: {
119
+ value: lookupResult.token,
120
+ prefix: lookupResult.prefix,
121
+ },
122
+ };
123
+ if (payload.request) {
124
+ payloadByRequest.set(payload.request, pluginPayload);
125
+ }
126
+ else {
127
+ payloadByContext.set(payload.serverContext, pluginPayload);
128
+ }
129
+ }
130
+ }, handleError);
131
+ }, handleError);
132
+ }, handleError);
113
133
  };
114
- async function ensureContext({ request, url, serverContext, extendContext, }) {
134
+ function ensureContext({ request, url, serverContext, extendContext, }) {
115
135
  if (normalizedOptions.extendContextFieldName === null) {
116
136
  return;
117
137
  }
@@ -119,21 +139,24 @@ export function useJWT(options) {
119
139
  return;
120
140
  }
121
141
  // Ensure the request has been validated before extending the context.
122
- await lookupAndValidate({
142
+ return handleMaybePromise(() => lookupAndValidate({
123
143
  request,
124
144
  url,
125
145
  serverContext,
146
+ }), () => {
147
+ // Then check the result
148
+ const result = request
149
+ ? payloadByRequest.get(request)
150
+ : payloadByContext.get(serverContext);
151
+ if (result && normalizedOptions.extendContextFieldName) {
152
+ extendContext({
153
+ [normalizedOptions.extendContextFieldName]: {
154
+ payload: result.payload,
155
+ token: result.token,
156
+ },
157
+ });
158
+ }
126
159
  });
127
- // Then check the result
128
- const result = request ? payloadByRequest.get(request) : payloadByContext.get(serverContext);
129
- if (result && normalizedOptions.extendContextFieldName) {
130
- extendContext({
131
- [normalizedOptions.extendContextFieldName]: {
132
- payload: result.payload,
133
- token: result.token,
134
- },
135
- });
136
- }
137
160
  }
138
161
  let fetchAPI;
139
162
  return {
package/esm/utils.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { createGraphQLError } from 'graphql-yoga';
2
2
  import { JwksClient } from 'jwks-rsa';
3
3
  import '@whatwg-node/server-plugin-cookies';
4
+ import { handleMaybePromise } from '@whatwg-node/promise-helpers';
4
5
  export function extractFromHeader(options) {
5
6
  return ({ request }) => {
6
7
  if (!request) {
@@ -35,7 +36,7 @@ export function extractFromHeader(options) {
35
36
  };
36
37
  }
37
38
  export function extractFromCookie(options) {
38
- return async ({ request }) => {
39
+ return ({ request }) => {
39
40
  if (!request) {
40
41
  return;
41
42
  }
@@ -43,14 +44,15 @@ export function extractFromCookie(options) {
43
44
  if (!cookieStore) {
44
45
  throw new Error('Cookie store is not available on request. Please make sure to configure the cookie plugin.');
45
46
  }
46
- const cookie = await cookieStore.get(options.name);
47
- if (!cookie) {
48
- return undefined;
49
- }
50
- return {
51
- prefix: undefined,
52
- token: cookie.value,
53
- };
47
+ return handleMaybePromise(() => cookieStore.get(options.name), cookie => {
48
+ if (!cookie) {
49
+ return;
50
+ }
51
+ return {
52
+ prefix: undefined,
53
+ token: cookie.value,
54
+ };
55
+ });
54
56
  };
55
57
  }
56
58
  export function extractFromConnectionParams(options) {
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@graphql-yoga/plugin-jwt",
3
- "version": "3.7.0",
3
+ "version": "3.7.1",
4
4
  "description": "jwt plugin for GraphQL Yoga.",
5
5
  "peerDependencies": {
6
6
  "graphql": "^15.2.0 || ^16.0.0",
7
7
  "graphql-yoga": "^5.13.0"
8
8
  },
9
9
  "dependencies": {
10
+ "@whatwg-node/promise-helpers": "^1.2.4",
10
11
  "@whatwg-node/server-plugin-cookies": "^1.0.3",
11
12
  "jsonwebtoken": "^9.0.0",
12
13
  "jwks-rsa": "^3.0.0",