@constructive-io/graphql-server 5.28.8 → 5.29.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 +12 -0
- package/errors/api-errors.d.ts +8 -0
- package/errors/api-errors.js +13 -1
- package/esm/errors/api-errors.js +11 -0
- package/esm/middleware/cookie.js +24 -0
- package/esm/middleware/error-handler.js +5 -20
- package/esm/middleware/{mask-error.js → format-error.js} +37 -62
- package/esm/middleware/graphile.js +10 -8
- package/esm/plugins/auth-cookie-plugin.js +27 -29
- package/esm/plugins/error-events-plugin.js +3 -3
- package/esm/plugins/webauthn-plugin.js +392 -0
- package/esm/server.js +4 -1
- package/middleware/cookie.d.ts +18 -1
- package/middleware/cookie.js +27 -1
- package/middleware/error-handler.js +5 -20
- package/middleware/format-error.d.ts +28 -0
- package/middleware/{mask-error.js → format-error.js} +38 -63
- package/middleware/graphile.js +10 -8
- package/package.json +21 -19
- package/plugins/auth-cookie-plugin.js +26 -28
- package/plugins/error-events-plugin.js +4 -4
- package/plugins/webauthn-plugin.d.ts +3 -0
- package/plugins/webauthn-plugin.js +395 -0
- package/server.js +3 -0
- package/middleware/mask-error.d.ts +0 -27
|
@@ -3,43 +3,24 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
6
|
+
exports.formatError = exports.normalizeError = void 0;
|
|
7
7
|
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
8
8
|
const errors_1 = require("@constructive-io/errors");
|
|
9
|
-
const env_1 = require("@pgpmjs/env");
|
|
10
9
|
const logger_1 = require("@pgpmjs/logger");
|
|
11
|
-
const
|
|
12
|
-
/**
|
|
13
|
-
* GraphQL framework protocol codes. These originate in the GraphQL/grafast
|
|
14
|
-
* transport layer (not in constructive-db), so they are not Constructive domain
|
|
15
|
-
* codes in the `@constructive-io/errors` registry. They are always safe to
|
|
16
|
-
* surface — they carry no sensitive detail. Everything else (auth, account,
|
|
17
|
-
* resource, constraint, and every constructive-db code) is classified by the
|
|
18
|
-
* registry, which is the single source of truth for public vs. internal.
|
|
19
|
-
*/
|
|
20
|
-
const GRAPHQL_PROTOCOL_CODES = new Set([
|
|
21
|
-
'GRAPHQL_VALIDATION_FAILED',
|
|
22
|
-
'GRAPHQL_PARSE_FAILED',
|
|
23
|
-
'PERSISTED_QUERY_NOT_FOUND',
|
|
24
|
-
'PERSISTED_QUERY_NOT_SUPPORTED'
|
|
25
|
-
]);
|
|
26
|
-
/** A code is safe to surface when the registry classifies it public, or it is a
|
|
27
|
-
* GraphQL framework protocol code. */
|
|
28
|
-
const isPublicCode = (code) => Boolean(code) && ((0, errors_1.classify)(code) === 'public' || GRAPHQL_PROTOCOL_CODES.has(code));
|
|
10
|
+
const formatErrorLog = new logger_1.Logger('graphile:formatError');
|
|
29
11
|
/**
|
|
30
12
|
* An error the GraphQL layer raised about the *request*, before any resolver
|
|
31
13
|
* ran: an unknown input field, a value of the wrong type, a missing required
|
|
32
14
|
* variable. graphql-js reports variable coercion without an `extensions.code`
|
|
33
15
|
* (unlike parse/validation, which carry `GRAPHQL_PARSE_FAILED` /
|
|
34
|
-
* `GRAPHQL_VALIDATION_FAILED`), so code-based classification alone
|
|
35
|
-
*
|
|
36
|
-
* failure, with a reference id pointing at nothing.
|
|
16
|
+
* `GRAPHQL_VALIDATION_FAILED`), so code-based classification alone would read
|
|
17
|
+
* it as a server failure instead of the client's own malformed query.
|
|
37
18
|
*
|
|
38
19
|
* A request error is answered before a field is resolved, so it carries no
|
|
39
20
|
* response `path` — every execution error has one. Coercion wraps the inner
|
|
40
21
|
* complaint about the value, so `originalError` may be set, but only ever to
|
|
41
22
|
* another GraphQL-layer error: anything a resolver or the database threw arrives
|
|
42
|
-
* as a foreign error (a pg error, an `Error`)
|
|
23
|
+
* as a foreign error (a pg error, an `Error`). The wrap
|
|
43
24
|
* is recognized by name rather than by `instanceof`, because the error is raised
|
|
44
25
|
* by whichever copy of graphql-js grafast resolved, not by this package's.
|
|
45
26
|
*/
|
|
@@ -69,8 +50,10 @@ const withoutInputValues = (error) => {
|
|
|
69
50
|
.replace(/^Value "[\s\S]*" does not exist in/, 'Value does not exist in');
|
|
70
51
|
return `${variable} got invalid value${at}; ${complaint}`;
|
|
71
52
|
};
|
|
72
|
-
/** The code a
|
|
53
|
+
/** The code a request error carries when graphql-js supplied none. */
|
|
73
54
|
const BAD_USER_INPUT = 'BAD_USER_INPUT';
|
|
55
|
+
/** The code any other error carries when nothing supplied one. */
|
|
56
|
+
const INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR';
|
|
74
57
|
/**
|
|
75
58
|
* Normalize any GraphQL/database error into a canonical Constructive shape.
|
|
76
59
|
*
|
|
@@ -87,21 +70,21 @@ const normalizeError = (error) => {
|
|
|
87
70
|
};
|
|
88
71
|
exports.normalizeError = normalizeError;
|
|
89
72
|
/**
|
|
90
|
-
*
|
|
73
|
+
* Format every GraphQL error the same way, in every environment. Nothing is
|
|
74
|
+
* masked: the client always receives the real message.
|
|
91
75
|
*
|
|
92
|
-
* 1.
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
* 2.
|
|
96
|
-
* 3.
|
|
97
|
-
* 4.
|
|
98
|
-
* the
|
|
76
|
+
* 1. Lift the structured code onto `extensions.code`/`class`/`context` from the
|
|
77
|
+
* parsed error, so database errors reach clients with a machine-readable
|
|
78
|
+
* code instead of a bare message with empty `extensions`.
|
|
79
|
+
* 2. A request error graphql-js raised without a code is `BAD_USER_INPUT`.
|
|
80
|
+
* 3. Any other error without a code is `INTERNAL_SERVER_ERROR`.
|
|
81
|
+
* 4. Every internal error (unknown, or registered as internal) carries an
|
|
82
|
+
* `errorId` and is logged under it, so a report can be matched to the log.
|
|
99
83
|
*/
|
|
100
|
-
const
|
|
84
|
+
const formatError = (error) => {
|
|
101
85
|
const { code, context, class: errorClass } = (0, exports.normalizeError)(error);
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
// on GraphQLError, so we build a formatted error rather than mutating it).
|
|
86
|
+
// `extensions` is read-only on GraphQLError, so build a formatted error
|
|
87
|
+
// rather than mutating it.
|
|
105
88
|
const extensions = { ...error.extensions };
|
|
106
89
|
if (code) {
|
|
107
90
|
extensions.code = code;
|
|
@@ -110,34 +93,26 @@ const maskError = (error) => {
|
|
|
110
93
|
extensions.context = context;
|
|
111
94
|
}
|
|
112
95
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
96
|
+
let internal = Boolean(code) && errorClass === 'internal';
|
|
97
|
+
let message = error.message;
|
|
98
|
+
if (!code && !error.extensions?.code) {
|
|
99
|
+
const requestError = isRequestError(error);
|
|
100
|
+
if (requestError)
|
|
101
|
+
message = withoutInputValues(error);
|
|
102
|
+
extensions.code = requestError ? BAD_USER_INPUT : INTERNAL_SERVER_ERROR;
|
|
103
|
+
internal = !requestError;
|
|
121
104
|
}
|
|
122
|
-
if (
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
message: error.message,
|
|
127
|
-
...(error.locations ? { locations: error.locations } : {}),
|
|
128
|
-
...(error.path ? { path: error.path } : {}),
|
|
129
|
-
extensions,
|
|
130
|
-
};
|
|
105
|
+
if (internal) {
|
|
106
|
+
const errorId = node_crypto_1.default.randomBytes(8).toString('hex');
|
|
107
|
+
extensions.errorId = errorId;
|
|
108
|
+
formatErrorLog.error(`[graphql-error:${errorId}]`, error);
|
|
131
109
|
}
|
|
132
|
-
//
|
|
133
|
-
const errorId = node_crypto_1.default.randomBytes(8).toString('hex');
|
|
134
|
-
maskErrorLog.error(`[masked-error:${errorId}]`, error);
|
|
110
|
+
// grafserv strips originalError before serializing to the client.
|
|
135
111
|
return {
|
|
136
|
-
message
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
112
|
+
message,
|
|
113
|
+
...(error.locations ? { locations: error.locations } : {}),
|
|
114
|
+
...(error.path ? { path: error.path } : {}),
|
|
115
|
+
extensions
|
|
141
116
|
};
|
|
142
117
|
};
|
|
143
|
-
exports.
|
|
118
|
+
exports.formatError = formatError;
|
package/middleware/graphile.js
CHANGED
|
@@ -7,7 +7,6 @@ exports.clearInFlightMap = clearInFlightMap;
|
|
|
7
7
|
require("./types"); // for Request type
|
|
8
8
|
const errors_1 = require("@constructive-io/errors");
|
|
9
9
|
const express_context_1 = require("@constructive-io/express-context");
|
|
10
|
-
const env_1 = require("@pgpmjs/env");
|
|
11
10
|
const logger_1 = require("@pgpmjs/logger");
|
|
12
11
|
const graphile_cache_1 = require("graphile-cache");
|
|
13
12
|
const graphile_function_bindings_1 = require("graphile-function-bindings");
|
|
@@ -20,10 +19,10 @@ const graphql_response_1 = require("../errors/graphql-response");
|
|
|
20
19
|
const auth_cookie_plugin_1 = require("../plugins/auth-cookie-plugin");
|
|
21
20
|
const error_events_plugin_1 = require("../plugins/error-events-plugin");
|
|
22
21
|
const request_protection_plugin_1 = require("../plugins/request-protection-plugin");
|
|
22
|
+
const webauthn_plugin_1 = require("../plugins/webauthn-plugin");
|
|
23
|
+
const format_error_1 = require("./format-error");
|
|
23
24
|
const graphile_introspection_1 = require("./graphile-introspection");
|
|
24
|
-
const mask_error_1 = require("./mask-error");
|
|
25
25
|
const graphile_build_stats_1 = require("./observability/graphile-build-stats");
|
|
26
|
-
const isDev = () => (0, env_1.getNodeEnv)() === 'development';
|
|
27
26
|
// =============================================================================
|
|
28
27
|
// Single-Flight Pattern: In-Flight Tracking
|
|
29
28
|
// =============================================================================
|
|
@@ -63,7 +62,7 @@ const reqLabel = (req) => (req.requestId ? `[${req.requestId}]` : '[req]');
|
|
|
63
62
|
* plugin preset. Without settings the default preset is used
|
|
64
63
|
* (everything on except aggregates).
|
|
65
64
|
*/
|
|
66
|
-
const buildPreset = async (pool, schemas, anonRole, roleName, introspectionRole, graphileOptions, databaseSettings, apiId, compute) => {
|
|
65
|
+
const buildPreset = async (pool, schemas, anonRole, roleName, introspectionRole, graphileOptions, databaseSettings, apiId, compute, webauthn) => {
|
|
67
66
|
const introspection = await (0, graphile_introspection_1.makeIntrospectionWiring)(pool, schemas, graphileOptions, undefined, introspectionRole);
|
|
68
67
|
const configuredPreset = graphileOptions?.preset ?? {};
|
|
69
68
|
const grafastCachePreset = (0, graphile_settings_1.createGrafastCacheLimitsPreset)(graphileOptions?.grafastCache);
|
|
@@ -83,6 +82,9 @@ const buildPreset = async (pool, schemas, anonRole, roleName, introspectionRole,
|
|
|
83
82
|
auth_cookie_plugin_1.AuthCookiePlugin,
|
|
84
83
|
request_protection_plugin_1.RequestProtectionPlugin,
|
|
85
84
|
(0, error_events_plugin_1.createErrorEventsPlugin)(pool),
|
|
85
|
+
// Only when the tenant provisioned the WebAuthn module; the policy
|
|
86
|
+
// itself is read per request from `req.api.webauthnSettings`.
|
|
87
|
+
...(webauthn ? [webauthn_plugin_1.WebauthnPlugin] : []),
|
|
86
88
|
// Only registered when the compute module is provisioned for this
|
|
87
89
|
// database — all schema/table names come from the constructive
|
|
88
90
|
// metaschema (express-context compute module loader); the plugin has
|
|
@@ -109,10 +111,10 @@ const buildPreset = async (pool, schemas, anonRole, roleName, introspectionRole,
|
|
|
109
111
|
graphiqlPath: '/graphiql',
|
|
110
112
|
graphiql: true,
|
|
111
113
|
graphiqlOnGraphQLGET: false,
|
|
112
|
-
maskError:
|
|
114
|
+
maskError: format_error_1.formatError
|
|
113
115
|
},
|
|
114
116
|
grafast: {
|
|
115
|
-
explain:
|
|
117
|
+
explain: graphileOptions?.explain === true,
|
|
116
118
|
context: (requestContext) => {
|
|
117
119
|
// In grafserv/express/v4, the request is available at requestContext.expressv4.req
|
|
118
120
|
const req = requestContext?.expressv4?.req;
|
|
@@ -315,7 +317,7 @@ const graphile = (opts) => {
|
|
|
315
317
|
// concurrent requests coalesce while optional plugin modules load.
|
|
316
318
|
const creationPromise = (async () => {
|
|
317
319
|
const compute = api.apiId ? await req.constructive?.useModule('compute') : undefined;
|
|
318
|
-
const preset = await buildPreset(pool, schema || [], anonRole, roleName, opts.api?.introspectionRole, opts.graphile, api.databaseSettings, api.apiId, compute);
|
|
320
|
+
const preset = await buildPreset(pool, schema || [], anonRole, roleName, opts.api?.introspectionRole, opts.graphile, api.databaseSettings, api.apiId, compute, Boolean(api.webauthnSettings));
|
|
319
321
|
return (0, graphile_build_stats_1.observeGraphileBuild)({
|
|
320
322
|
cacheKey: key,
|
|
321
323
|
serviceKey: key,
|
|
@@ -349,7 +351,7 @@ const graphile = (opts) => {
|
|
|
349
351
|
log.error(`${label} PostGraphile middleware error`, e);
|
|
350
352
|
if (!res.headersSent) {
|
|
351
353
|
(0, graphql_response_1.respondWithGraphQLError)(res, errors_1.errors.INTERNAL_FAILURE({
|
|
352
|
-
details:
|
|
354
|
+
details: e?.message ?? String(e)
|
|
353
355
|
}));
|
|
354
356
|
return;
|
|
355
357
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constructive-io/graphql-server",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.29.0",
|
|
4
4
|
"author": "Constructive <developers@constructive.io>",
|
|
5
5
|
"description": "Constructive GraphQL Server",
|
|
6
6
|
"main": "index.js",
|
|
@@ -30,8 +30,7 @@
|
|
|
30
30
|
"debug:heap:capture": "node scripts/capture-heap-snapshot.mjs",
|
|
31
31
|
"lint": "eslint . --fix",
|
|
32
32
|
"test": "jest --passWithNoTests",
|
|
33
|
-
"test:watch": "jest --watch"
|
|
34
|
-
"bucket:create": "ts-node src/scripts/create-bucket.ts"
|
|
33
|
+
"test:watch": "jest --watch"
|
|
35
34
|
},
|
|
36
35
|
"keywords": [
|
|
37
36
|
"server",
|
|
@@ -42,20 +41,22 @@
|
|
|
42
41
|
],
|
|
43
42
|
"dependencies": {
|
|
44
43
|
"@agentic-kit/ollama": "2.15.0",
|
|
45
|
-
"@constructive-io/csrf": "^0.30.
|
|
46
|
-
"@constructive-io/errors": "^0.
|
|
47
|
-
"@constructive-io/express-context": "^0.33.
|
|
48
|
-
"@constructive-io/graphql-env": "^3.
|
|
49
|
-
"@constructive-io/graphql-types": "^3.
|
|
44
|
+
"@constructive-io/csrf": "^0.30.1",
|
|
45
|
+
"@constructive-io/errors": "^0.24.0",
|
|
46
|
+
"@constructive-io/express-context": "^0.33.7",
|
|
47
|
+
"@constructive-io/graphql-env": "^3.35.0",
|
|
48
|
+
"@constructive-io/graphql-types": "^3.34.0",
|
|
50
49
|
"@constructive-io/llm-env": "^0.15.0",
|
|
51
|
-
"@constructive-io/query-builder": "^3.16.
|
|
50
|
+
"@constructive-io/query-builder": "^3.16.8",
|
|
52
51
|
"@constructive-io/s3-utils": "^2.34.0",
|
|
53
52
|
"@constructive-io/url-domains": "^2.31.0",
|
|
54
53
|
"@graphile-contrib/pg-many-to-many": "2.0.0-rc.2",
|
|
55
|
-
"@pgpmjs/env": "^2.
|
|
54
|
+
"@pgpmjs/env": "^2.47.0",
|
|
56
55
|
"@pgpmjs/logger": "^2.26.0",
|
|
57
|
-
"@pgpmjs/server-utils": "^3.29.
|
|
58
|
-
"@pgpmjs/types": "^2.
|
|
56
|
+
"@pgpmjs/server-utils": "^3.29.7",
|
|
57
|
+
"@pgpmjs/types": "^2.57.0",
|
|
58
|
+
"@pgsql/quotes": "^18.2.4",
|
|
59
|
+
"@simplewebauthn/server": "13.3.2",
|
|
59
60
|
"cors": "^2.8.6",
|
|
60
61
|
"deepmerge": "^4.3.1",
|
|
61
62
|
"express": "^5.2.1",
|
|
@@ -64,17 +65,18 @@
|
|
|
64
65
|
"grafserv": "1.0.1",
|
|
65
66
|
"graphile-build": "5.1.1",
|
|
66
67
|
"graphile-build-pg": "5.1.3",
|
|
67
|
-
"graphile-cache": "^4.14.
|
|
68
|
+
"graphile-cache": "^4.14.7",
|
|
68
69
|
"graphile-config": "1.1.0",
|
|
69
|
-
"graphile-function-bindings": "^1.17.
|
|
70
|
-
"graphile-
|
|
71
|
-
"graphile-
|
|
70
|
+
"graphile-function-bindings": "^1.17.8",
|
|
71
|
+
"graphile-plugin-utils": "^1.13.0",
|
|
72
|
+
"graphile-scoped-introspection": "^0.4.8",
|
|
73
|
+
"graphile-settings": "^6.25.0",
|
|
72
74
|
"graphile-utils": "5.0.3",
|
|
73
75
|
"graphql": "16.13.0",
|
|
74
76
|
"graphql-upload": "^13.0.0",
|
|
75
77
|
"lru-cache": "^11.2.7",
|
|
76
78
|
"pg": "^8.21.0",
|
|
77
|
-
"pg-cache": "^3.29.
|
|
79
|
+
"pg-cache": "^3.29.7",
|
|
78
80
|
"pg-env": "^1.32.0",
|
|
79
81
|
"pg-query-context": "^2.31.0",
|
|
80
82
|
"pg-sql2": "5.0.1",
|
|
@@ -91,11 +93,11 @@
|
|
|
91
93
|
"@types/request-ip": "^0.0.41",
|
|
92
94
|
"@types/supertest": "^7.2.1",
|
|
93
95
|
"cookie-parser": "^1.4.7",
|
|
94
|
-
"graphile-test": "5.17.
|
|
96
|
+
"graphile-test": "5.17.8",
|
|
95
97
|
"makage": "^0.8.0",
|
|
96
98
|
"nodemon": "^3.1.14",
|
|
97
99
|
"supertest": "^7.2.2",
|
|
98
100
|
"ts-node": "^10.9.2"
|
|
99
101
|
},
|
|
100
|
-
"gitHead": "
|
|
102
|
+
"gitHead": "1f3501d518e175803a1a3372cf9249922d82bfe5"
|
|
101
103
|
}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.AuthCookiePlugin = void 0;
|
|
4
4
|
require("../middleware/types");
|
|
5
5
|
const logger_1 = require("@pgpmjs/logger");
|
|
6
|
+
const graphql_1 = require("graphql");
|
|
6
7
|
const cookie_1 = require("../middleware/cookie");
|
|
7
8
|
const log = new logger_1.Logger('auth-cookie');
|
|
8
9
|
/**
|
|
@@ -69,6 +70,7 @@ const SIGN_IN_MUTATIONS = new Set([
|
|
|
69
70
|
'completeMfaChallenge',
|
|
70
71
|
'signInOneTimeToken',
|
|
71
72
|
'signInCrossOrigin',
|
|
73
|
+
'webauthnSignInFinish',
|
|
72
74
|
]);
|
|
73
75
|
/**
|
|
74
76
|
* Auth mutations that should clear the session cookie.
|
|
@@ -79,26 +81,21 @@ const SIGN_OUT_MUTATIONS = new Set([
|
|
|
79
81
|
'revokeAllSessions',
|
|
80
82
|
]);
|
|
81
83
|
/**
|
|
82
|
-
*
|
|
84
|
+
* Top-level field names of the mutation that will execute: the operation
|
|
85
|
+
* picked by `operationName`, so other operations in the document never count.
|
|
83
86
|
*/
|
|
84
|
-
const extractMutationNames = (query) => {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
const extractMutationNames = (query, operationName) => {
|
|
88
|
+
let document;
|
|
89
|
+
try {
|
|
90
|
+
document = (0, graphql_1.parse)(query);
|
|
88
91
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
return mutations;
|
|
92
|
-
const bodyContent = query.slice(bodyStart + 1);
|
|
93
|
-
const fieldPattern = /(\w+)\s*(?:\(|{)/g;
|
|
94
|
-
let match;
|
|
95
|
-
while ((match = fieldPattern.exec(bodyContent)) !== null) {
|
|
96
|
-
const name = match[1];
|
|
97
|
-
if (name !== 'mutation' && name !== 'query' && name !== 'fragment') {
|
|
98
|
-
mutations.push(name);
|
|
99
|
-
}
|
|
92
|
+
catch {
|
|
93
|
+
return [];
|
|
100
94
|
}
|
|
101
|
-
|
|
95
|
+
const operation = (0, graphql_1.getOperationAST)(document, operationName);
|
|
96
|
+
if (operation?.operation !== 'mutation')
|
|
97
|
+
return [];
|
|
98
|
+
return operation.selectionSet.selections.flatMap((selection) => selection.kind === graphql_1.Kind.FIELD ? [selection.name.value] : []);
|
|
102
99
|
};
|
|
103
100
|
/**
|
|
104
101
|
* Extract access token from mutation response.
|
|
@@ -201,7 +198,7 @@ exports.AuthCookiePlugin = {
|
|
|
201
198
|
return result;
|
|
202
199
|
}
|
|
203
200
|
// Extract mutation names
|
|
204
|
-
const mutationNames = extractMutationNames(body.query);
|
|
201
|
+
const mutationNames = extractMutationNames(body.query, body.operationName);
|
|
205
202
|
if (mutationNames.length === 0) {
|
|
206
203
|
return result;
|
|
207
204
|
}
|
|
@@ -216,24 +213,25 @@ exports.AuthCookiePlugin = {
|
|
|
216
213
|
// Parse response body
|
|
217
214
|
const payload = bufferResult.buffer.toString('utf8');
|
|
218
215
|
const graphqlResponse = JSON.parse(payload);
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
return result;
|
|
222
|
-
}
|
|
223
|
-
const data = graphqlResponse.data;
|
|
216
|
+
const succeeded = !graphqlResponse.errors?.length && Boolean(graphqlResponse.data);
|
|
217
|
+
const data = graphqlResponse.data ?? {};
|
|
224
218
|
const authSettings = req.api?.authSettings;
|
|
225
219
|
const cookiesToSet = [];
|
|
226
|
-
//
|
|
227
|
-
|
|
228
|
-
|
|
220
|
+
// signOut always clears the cookies, whatever the mutation returned
|
|
221
|
+
// and however the request authenticated: a Bearer client that was
|
|
222
|
+
// once handed a session cookie must not keep sending it afterwards.
|
|
223
|
+
// revokeSession/revokeAllSessions clear them only on success.
|
|
224
|
+
if (signOutMutation === 'signOut' || (signOutMutation && succeeded && data[signOutMutation])) {
|
|
225
|
+
log.info(`[auth-cookie] ${signOutMutation}: clearing session cookie`);
|
|
229
226
|
const config = (0, cookie_1.getSessionCookieConfig)(authSettings);
|
|
230
227
|
cookiesToSet.push(serializeClearCookie(cookie_1.SESSION_COOKIE_NAME, config));
|
|
231
228
|
// Also clear device token on sign-out
|
|
232
229
|
const deviceConfig = (0, cookie_1.getDeviceTokenCookieConfig)(authSettings);
|
|
233
230
|
cookiesToSet.push(serializeClearCookie(cookie_1.DEVICE_TOKEN_COOKIE_NAME, deviceConfig));
|
|
234
231
|
}
|
|
235
|
-
// Handle sign-in mutations
|
|
236
|
-
|
|
232
|
+
// Handle sign-in mutations; a client that sent the opt-out header
|
|
233
|
+
// reads the token from the response body and gets no cookies.
|
|
234
|
+
if (signInMutation && succeeded && !(0, cookie_1.isSessionCookieOmitted)(req)) {
|
|
237
235
|
const accessToken = extractAccessToken(data, signInMutation);
|
|
238
236
|
if (accessToken) {
|
|
239
237
|
const rememberMe = hasRememberMe(body.variables);
|
|
@@ -6,17 +6,17 @@ const logger_1 = require("@pgpmjs/logger");
|
|
|
6
6
|
const graphql_1 = require("graphql");
|
|
7
7
|
const pg_1 = require("pg");
|
|
8
8
|
const pg_query_context_1 = require("pg-query-context");
|
|
9
|
-
const
|
|
9
|
+
const format_error_1 = require("../middleware/format-error");
|
|
10
10
|
const log = new logger_1.Logger('error-events');
|
|
11
11
|
const getExpressRequest = (requestContext) => requestContext?.expressv4?.req;
|
|
12
12
|
/**
|
|
13
13
|
* The first structured, public-classified registry code among the errors.
|
|
14
|
-
* Internal/unknown errors are bugs, not refusals: they are
|
|
15
|
-
*
|
|
14
|
+
* Internal/unknown errors are bugs, not refusals: they are logged by
|
|
15
|
+
* `formatError` and never recorded as tenant events.
|
|
16
16
|
*/
|
|
17
17
|
const refusalCode = (errors) => {
|
|
18
18
|
for (const error of errors ?? []) {
|
|
19
|
-
const { code, class: errorClass } = (0,
|
|
19
|
+
const { code, class: errorClass } = (0, format_error_1.normalizeError)(error);
|
|
20
20
|
if (code && errorClass === 'public')
|
|
21
21
|
return code;
|
|
22
22
|
}
|