@tumbaland/backend-core 1.44.0 → 1.46.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/dist/fileService/client.d.ts +54 -0
- package/dist/fileService/client.d.ts.map +1 -0
- package/dist/fileService/client.js +137 -0
- package/dist/fileService/client.js.map +1 -0
- package/dist/fileService/index.d.ts +4 -0
- package/dist/fileService/index.d.ts.map +1 -0
- package/dist/fileService/index.js +40 -0
- package/dist/fileService/index.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/middleware/corsMiddleware.d.ts.map +1 -1
- package/dist/middleware/corsMiddleware.js +4 -1
- package/dist/middleware/corsMiddleware.js.map +1 -1
- package/dist/middleware/errorHandler.d.ts.map +1 -1
- package/dist/middleware/errorHandler.js +5 -8
- package/dist/middleware/errorHandler.js.map +1 -1
- package/dist/utils/escapeRegex.d.ts +8 -0
- package/dist/utils/escapeRegex.d.ts.map +1 -0
- package/dist/utils/escapeRegex.js +12 -0
- package/dist/utils/escapeRegex.js.map +1 -0
- package/package.json +5 -1
- package/.versionrc.json +0 -7
- package/__mocks__/uuid.js +0 -8
- package/jest.config.js +0 -24
- package/src/apiKeys/ApiKey.test.ts +0 -142
- package/src/apiKeys/ApiKey.ts +0 -102
- package/src/apiKeys/crypto.test.ts +0 -161
- package/src/apiKeys/crypto.ts +0 -163
- package/src/apiKeys/index.test.ts +0 -72
- package/src/apiKeys/index.ts +0 -35
- package/src/apiKeys/middleware.test.ts +0 -651
- package/src/apiKeys/middleware.ts +0 -341
- package/src/apiKeys/service.test.ts +0 -401
- package/src/apiKeys/service.ts +0 -168
- package/src/apiKeys/types.test.ts +0 -41
- package/src/apiKeys/types.ts +0 -124
- package/src/app/createBaseApp.test.ts +0 -109
- package/src/app/createBaseApp.ts +0 -102
- package/src/app/shutdown.test.ts +0 -129
- package/src/app/shutdown.ts +0 -81
- package/src/audit/AuditEvent.ts +0 -123
- package/src/audit/actor.test.ts +0 -95
- package/src/audit/actor.ts +0 -68
- package/src/audit/context.test.ts +0 -91
- package/src/audit/context.ts +0 -83
- package/src/audit/index.ts +0 -11
- package/src/audit/plugin.test.ts +0 -258
- package/src/audit/plugin.ts +0 -254
- package/src/audit/reads.test.ts +0 -164
- package/src/audit/reads.ts +0 -88
- package/src/audit/service.test.ts +0 -115
- package/src/audit/service.ts +0 -95
- package/src/auth/session.test.ts +0 -89
- package/src/auth/session.ts +0 -75
- package/src/config/env.test.ts +0 -28
- package/src/config/env.ts +0 -13
- package/src/database/connection.test.ts +0 -188
- package/src/database/connection.ts +0 -90
- package/src/entitlements/UsageMeter.ts +0 -49
- package/src/entitlements/client.test.ts +0 -200
- package/src/entitlements/client.ts +0 -179
- package/src/entitlements/definitions.test.ts +0 -161
- package/src/entitlements/definitions.ts +0 -268
- package/src/entitlements/index.ts +0 -42
- package/src/entitlements/middleware.test.ts +0 -196
- package/src/entitlements/middleware.ts +0 -150
- package/src/entitlements/reconcile.test.ts +0 -333
- package/src/entitlements/reconcile.ts +0 -384
- package/src/entitlements/types.ts +0 -21
- package/src/entitlements/usage.test.ts +0 -314
- package/src/entitlements/usage.ts +0 -223
- package/src/errors/HttpError.test.ts +0 -76
- package/src/errors/HttpError.ts +0 -91
- package/src/groups/client.test.ts +0 -215
- package/src/groups/client.ts +0 -182
- package/src/groups/index.ts +0 -7
- package/src/groups/membership.test.ts +0 -84
- package/src/groups/membership.ts +0 -133
- package/src/groups/subject.test.ts +0 -85
- package/src/groups/subject.ts +0 -50
- package/src/health/createHealthCheck.test.ts +0 -89
- package/src/health/createHealthCheck.ts +0 -67
- package/src/health/healthController.test.ts +0 -113
- package/src/health/healthController.ts +0 -56
- package/src/index.ts +0 -88
- package/src/logging/logger.test.ts +0 -91
- package/src/logging/logger.ts +0 -103
- package/src/metrics/index.test.ts +0 -116
- package/src/metrics/index.ts +0 -111
- package/src/middleware/authMiddleware.test.ts +0 -275
- package/src/middleware/authMiddleware.ts +0 -91
- package/src/middleware/corsMiddleware.test.ts +0 -135
- package/src/middleware/corsMiddleware.ts +0 -65
- package/src/middleware/errorHandler.test.ts +0 -188
- package/src/middleware/errorHandler.ts +0 -103
- package/src/middleware/internalServiceAuth.test.ts +0 -173
- package/src/middleware/internalServiceAuth.ts +0 -96
- package/src/middleware/requestLogger.test.ts +0 -81
- package/src/middleware/requestLogger.ts +0 -48
- package/src/middleware/security.test.ts +0 -45
- package/src/middleware/security.ts +0 -43
- package/src/middleware/validate.test.ts +0 -72
- package/src/middleware/validate.ts +0 -23
- package/src/oauth/index.ts +0 -29
- package/src/oauth/models.ts +0 -164
- package/src/oauth/service.test.ts +0 -432
- package/src/oauth/service.ts +0 -299
- package/src/oauth/tokens.test.ts +0 -146
- package/src/oauth/tokens.ts +0 -186
- package/src/testing/serviceTestSetup.ts +0 -17
- package/src/tracing/index.test.ts +0 -272
- package/src/tracing/index.ts +0 -110
- package/src/types/auth.ts +0 -45
- package/src/utils/correlation.test.ts +0 -47
- package/src/utils/correlation.ts +0 -22
- package/src/utils/permissionUtils.test.ts +0 -47
- package/src/utils/permissionUtils.ts +0 -68
- package/src/utils/response.test.ts +0 -64
- package/src/utils/response.ts +0 -60
- package/tsconfig.build.json +0 -7
- package/tsconfig.json +0 -23
|
@@ -1,341 +0,0 @@
|
|
|
1
|
-
import { NextFunction, Request, RequestHandler, Response } from 'express';
|
|
2
|
-
import jwt from 'jsonwebtoken';
|
|
3
|
-
import { requireEnv } from '../config/env';
|
|
4
|
-
import logger from '../logging/logger';
|
|
5
|
-
import { beginAudit } from '../audit/context';
|
|
6
|
-
import { isAccessTokenClaims, type AccessTokenClaims } from '../oauth/tokens';
|
|
7
|
-
import type { UserPayload } from '../types/auth';
|
|
8
|
-
import { looksLikeApiKey } from './crypto';
|
|
9
|
-
import { verifyApiKey } from './service';
|
|
10
|
-
import {
|
|
11
|
-
PERSONAL_TENANT,
|
|
12
|
-
groupIdsOf,
|
|
13
|
-
isApiKeyScope,
|
|
14
|
-
readTenants,
|
|
15
|
-
type ApiKeyScope,
|
|
16
|
-
type ApiKeyTenant,
|
|
17
|
-
type Tenant
|
|
18
|
-
} from './types';
|
|
19
|
-
import { isCurrentSession } from '../auth/session';
|
|
20
|
-
import { currentGroupsOf } from '../groups/membership';
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Request-scoped facts about the key a request arrived on. Absent on ordinary
|
|
24
|
-
* session requests, which is itself the signal a handler needs: `req.apiKey`
|
|
25
|
-
* being set means software is acting, not a person.
|
|
26
|
-
*/
|
|
27
|
-
export interface ApiKeyContext {
|
|
28
|
-
keyId: string;
|
|
29
|
-
scopes: ApiKeyScope[];
|
|
30
|
-
/** every tenant this credential may act in, and the one it acts in by default */
|
|
31
|
-
tenants: ApiKeyTenant;
|
|
32
|
-
/** the tenant this particular request resolved to */
|
|
33
|
-
actingAs: Tenant;
|
|
34
|
-
/** which credential this is; they are revoked and audited differently */
|
|
35
|
-
kind: 'api_key' | 'oauth';
|
|
36
|
-
/** what the owner called it — a key's name, or the connected app's */
|
|
37
|
-
label: string;
|
|
38
|
-
/**
|
|
39
|
-
* The stable identity of the credential.
|
|
40
|
-
*
|
|
41
|
-
* A key's document id, or an OAuth client id — never the access token's
|
|
42
|
-
* `jti`, which rotates hourly and would make one connection look like a new
|
|
43
|
-
* actor every time it refreshed.
|
|
44
|
-
*/
|
|
45
|
-
credentialId: string;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
declare global {
|
|
49
|
-
namespace Express {
|
|
50
|
-
interface Request {
|
|
51
|
-
apiKey?: ApiKeyContext;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** Both auth paths read the token from the same two places. */
|
|
57
|
-
const extractToken = (req: Request): string | undefined =>
|
|
58
|
-
req.cookies?.access_token || req.headers.authorization?.replace('Bearer ', '');
|
|
59
|
-
|
|
60
|
-
const unauthorized = (res: Response): void => {
|
|
61
|
-
res.status(401).json({ success: false, message: 'Invalid or expired credentials' });
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* The tenant a request is asking to act in, wherever it named one.
|
|
66
|
-
*
|
|
67
|
-
* Handlers read `groupId` from either the query string or the body depending on
|
|
68
|
-
* the verb, so both are checked — a rule that only covered one of them would be
|
|
69
|
-
* no rule at all. A caller names the personal tenant with the literal
|
|
70
|
-
* `personal`, since "no group" is what the resolved default gets written into
|
|
71
|
-
* and would otherwise be indistinguishable from not having asked.
|
|
72
|
-
*/
|
|
73
|
-
const requestedTenant = (req: Request): Tenant | undefined => {
|
|
74
|
-
const fromQuery = req.query?.groupId;
|
|
75
|
-
if (typeof fromQuery === 'string' && fromQuery.length > 0) return fromQuery;
|
|
76
|
-
|
|
77
|
-
const fromBody = (req.body as Record<string, unknown> | undefined)?.groupId;
|
|
78
|
-
if (typeof fromBody === 'string' && fromBody.length > 0) return fromBody;
|
|
79
|
-
|
|
80
|
-
return undefined;
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
/** Express 5 makes `req.query` a getter, so it is redefined rather than assigned. */
|
|
84
|
-
const setQueryGroupId = (req: Request, groupId: string | undefined): void => {
|
|
85
|
-
const query = { ...req.query } as Record<string, unknown>;
|
|
86
|
-
if (groupId === undefined) delete query.groupId;
|
|
87
|
-
else query.groupId = groupId;
|
|
88
|
-
|
|
89
|
-
Object.defineProperty(req, 'query', {
|
|
90
|
-
value: query,
|
|
91
|
-
writable: true,
|
|
92
|
-
configurable: true,
|
|
93
|
-
enumerable: true
|
|
94
|
-
});
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Hold a credential to the tenants it was granted, and settle which one this
|
|
99
|
-
* request is acting in.
|
|
100
|
-
*
|
|
101
|
-
* A request naming a tenant outside the grant is refused outright — that
|
|
102
|
-
* boundary is the whole reason a credential is safe to hand to an agent, and it
|
|
103
|
-
* is unchanged by the grant being a set rather than one. What the set adds is a
|
|
104
|
-
* choice *inside* it, which is what lets one connection reach a shared journal
|
|
105
|
-
* and a private photo library without reconnecting.
|
|
106
|
-
*
|
|
107
|
-
* A request that names nothing gets the default written in for it, so an agent
|
|
108
|
-
* granted a single tenant never has to know a group id exists and a handler's
|
|
109
|
-
* `if (groupId) … else personal` branch lands where the owner intended.
|
|
110
|
-
*/
|
|
111
|
-
const applyTenantGrant = (req: Request, tenants: ApiKeyTenant): Tenant | null => {
|
|
112
|
-
const requested = requestedTenant(req) ?? tenants.default;
|
|
113
|
-
|
|
114
|
-
if (!tenants.allowed.includes(requested)) return null;
|
|
115
|
-
|
|
116
|
-
// Resolved either way, including when the caller asked for exactly what the
|
|
117
|
-
// default already was: handlers read the personal tenant as an absent
|
|
118
|
-
// `groupId`, so `personal` has to be erased rather than passed through.
|
|
119
|
-
const groupId = requested === PERSONAL_TENANT ? undefined : requested;
|
|
120
|
-
setQueryGroupId(req, groupId);
|
|
121
|
-
if (req.body && typeof req.body === 'object') {
|
|
122
|
-
if (groupId === undefined) delete (req.body as Record<string, unknown>).groupId;
|
|
123
|
-
else (req.body as Record<string, unknown>).groupId = groupId;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return requested;
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
/** A verified non-session credential, whichever kind it arrived as. */
|
|
130
|
-
interface AgentCredential {
|
|
131
|
-
/** the key's document id, or the OAuth client id */
|
|
132
|
-
credentialId: string;
|
|
133
|
-
kind: 'api_key' | 'oauth';
|
|
134
|
-
/** what the owner called it, for a trail a person can read */
|
|
135
|
-
label: string;
|
|
136
|
-
userId: string;
|
|
137
|
-
email: string;
|
|
138
|
-
name: string;
|
|
139
|
-
scopes: ApiKeyScope[];
|
|
140
|
-
tenants: ApiKeyTenant;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Admit software acting for a user, on the terms its credential carries.
|
|
145
|
-
*
|
|
146
|
-
* Shared by both non-session credentials on purpose. An API key and an OAuth
|
|
147
|
-
* access token differ entirely in how they are issued and verified, and not at
|
|
148
|
-
* all in what they mean once they are: a user, a set of scopes, and one tenant.
|
|
149
|
-
* Settling that in one place is what keeps the two from drifting into subtly
|
|
150
|
-
* different amounts of access.
|
|
151
|
-
*/
|
|
152
|
-
async function admitAgent(
|
|
153
|
-
req: Request,
|
|
154
|
-
res: Response,
|
|
155
|
-
next: NextFunction,
|
|
156
|
-
requiredScopes: ApiKeyScope[],
|
|
157
|
-
credential: AgentCredential
|
|
158
|
-
): Promise<void> {
|
|
159
|
-
const missing = requiredScopes.filter((scope) => !credential.scopes.includes(scope));
|
|
160
|
-
if (missing.length > 0) {
|
|
161
|
-
res.status(403).json({
|
|
162
|
-
success: false,
|
|
163
|
-
message: `Credential is missing required scope: ${missing.join(', ')}`
|
|
164
|
-
});
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// A grant names the groups the owner belonged to when they made it. Only the
|
|
169
|
-
// ones they still belong to count, so leaving a group also takes it from
|
|
170
|
-
// every key and connection they gave it to.
|
|
171
|
-
const stillMemberOf = new Set(await currentGroupsOf({ email: credential.email }));
|
|
172
|
-
const tenants: ApiKeyTenant = {
|
|
173
|
-
...credential.tenants,
|
|
174
|
-
allowed: credential.tenants.allowed.filter((tenant) => tenant === PERSONAL_TENANT || stillMemberOf.has(tenant))
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
const actingAs = applyTenantGrant(req, tenants);
|
|
178
|
-
if (actingAs === null) {
|
|
179
|
-
res.status(403).json({
|
|
180
|
-
success: false,
|
|
181
|
-
message: 'Credential is not permitted to act in the requested group'
|
|
182
|
-
});
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
req.user = { id: credential.userId, email: credential.email, name: credential.name };
|
|
187
|
-
// Only the granted groups, never the owner's full membership: this is what
|
|
188
|
-
// stops a credential reaching a group it was not issued for through any
|
|
189
|
-
// handler that consults `userGroups` instead of the `groupId` parameter.
|
|
190
|
-
req.userGroups = groupIdsOf(tenants.allowed);
|
|
191
|
-
req.apiKey = {
|
|
192
|
-
keyId: credential.credentialId,
|
|
193
|
-
scopes: credential.scopes,
|
|
194
|
-
tenants,
|
|
195
|
-
actingAs,
|
|
196
|
-
kind: credential.kind,
|
|
197
|
-
label: credential.label,
|
|
198
|
-
credentialId: credential.credentialId
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
// Attribution comes from authentication, so it is established here rather
|
|
202
|
-
// than mounted separately — a router that authenticates per route would
|
|
203
|
-
// otherwise have no actor in scope when a router-level middleware ran.
|
|
204
|
-
beginAudit(req, res, next);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Authenticate a request that software may legitimately be making.
|
|
209
|
-
*
|
|
210
|
-
* Accepts either an ordinary session — a person at a keyboard, who keeps the
|
|
211
|
-
* full run of their own account — or an API key carrying every one of
|
|
212
|
-
* `requiredScopes`.
|
|
213
|
-
*
|
|
214
|
-
* This is deliberately *not* what `authenticateToken` became. Keys stay refused
|
|
215
|
-
* everywhere by default, and a route opts in by naming the scopes it needs; the
|
|
216
|
-
* alternative, teaching the existing middleware about keys, would have silently
|
|
217
|
-
* opened every route in every service at once, account deletion included.
|
|
218
|
-
*/
|
|
219
|
-
export const authenticateAgent = (...requiredScopes: ApiKeyScope[]): RequestHandler =>
|
|
220
|
-
async function authenticateAgentHandler(req: Request, res: Response, next: NextFunction): Promise<void> {
|
|
221
|
-
const token = extractToken(req);
|
|
222
|
-
if (!token) {
|
|
223
|
-
res.status(401).json({ success: false, message: 'Access token required' });
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
if (!looksLikeApiKey(token)) {
|
|
228
|
-
let claims: UserPayload | AccessTokenClaims;
|
|
229
|
-
try {
|
|
230
|
-
claims = jwt.verify(token, requireEnv('JWT_SECRET')) as UserPayload | AccessTokenClaims;
|
|
231
|
-
} catch {
|
|
232
|
-
unauthorized(res);
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
if (!isAccessTokenClaims(claims)) {
|
|
237
|
-
if (!(await isCurrentSession(claims))) {
|
|
238
|
-
unauthorized(res);
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
const groups = await currentGroupsOf(claims as UserPayload);
|
|
242
|
-
req.user = { ...(claims as UserPayload), groups };
|
|
243
|
-
req.userGroups = groups;
|
|
244
|
-
beginAudit(req, res, next);
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// An OAuth access token: signed with the same secret as a session and
|
|
249
|
-
// arriving in the same header, but nothing like one. It names its user in
|
|
250
|
-
// `sub`, carries scopes, and is pinned to a tenant — so it is admitted
|
|
251
|
-
// the way a key is, not the way a person is.
|
|
252
|
-
//
|
|
253
|
-
// Reading it as a session was the original bug and it failed quietly: the
|
|
254
|
-
// claims have no `id` and no `groups`, so `req.user.id` arrived as
|
|
255
|
-
// `undefined`, scoped writes were refused as inaccessible, and a read
|
|
256
|
-
// whose scope collapsed to nothing fell through to "no user context" and
|
|
257
|
-
// answered from the whole collection.
|
|
258
|
-
await admitAgent(req, res, next, requiredScopes, {
|
|
259
|
-
// The client, not the token: `jti` is a new value every hour.
|
|
260
|
-
credentialId: claims.clientId ?? 'unknown-client',
|
|
261
|
-
kind: 'oauth',
|
|
262
|
-
label: claims.clientName || 'A connected app',
|
|
263
|
-
userId: claims.sub,
|
|
264
|
-
email: claims.email ?? '',
|
|
265
|
-
name: claims.name ?? '',
|
|
266
|
-
scopes: (claims.scope ?? '').split(' ').filter(isApiKeyScope),
|
|
267
|
-
tenants: readTenants(claims)
|
|
268
|
-
});
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
const result = await verifyApiKey(token);
|
|
273
|
-
if (!result.ok) {
|
|
274
|
-
// The client is told only that the credentials failed; which of the five
|
|
275
|
-
// ways it failed is a detail that would help someone guessing.
|
|
276
|
-
logger.warn('API key rejected', { rejection: result.rejection, path: req.path });
|
|
277
|
-
unauthorized(res);
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
await admitAgent(req, res, next, requiredScopes, {
|
|
282
|
-
credentialId: result.keyId!,
|
|
283
|
-
kind: 'api_key',
|
|
284
|
-
label: result.label || 'An API key',
|
|
285
|
-
userId: result.userId!,
|
|
286
|
-
email: result.userEmail ?? '',
|
|
287
|
-
name: result.userName ?? '',
|
|
288
|
-
scopes: result.scopes ?? [],
|
|
289
|
-
tenants: result.tenants ?? readTenants({})
|
|
290
|
-
});
|
|
291
|
-
};
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Require a scope on a route already behind `authenticateAgent`.
|
|
295
|
-
*
|
|
296
|
-
* Splitting authentication from authorization lets one `router.use` cover a
|
|
297
|
-
* whole router while each route still states what it needs — so a read-only key
|
|
298
|
-
* reaches the GETs and stops at the POSTs, instead of being turned away at the
|
|
299
|
-
* door for lacking a scope half the router never uses.
|
|
300
|
-
*
|
|
301
|
-
* A session passes unconditionally: scopes narrow what software may do on a
|
|
302
|
-
* person's behalf, not what the person may do themselves. Everything that is
|
|
303
|
-
* not a session — an API key or an OAuth access token alike — arrives with
|
|
304
|
-
* `req.apiKey` set and is held to it.
|
|
305
|
-
*/
|
|
306
|
-
export const requireScope = (...requiredScopes: ApiKeyScope[]): RequestHandler =>
|
|
307
|
-
function requireScopeHandler(req, res, next) {
|
|
308
|
-
if (!req.apiKey) {
|
|
309
|
-
next();
|
|
310
|
-
return;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
const missing = requiredScopes.filter((scope) => !req.apiKey!.scopes.includes(scope));
|
|
314
|
-
if (missing.length > 0) {
|
|
315
|
-
res.status(403).json({
|
|
316
|
-
success: false,
|
|
317
|
-
message: `Credential is missing required scope: ${missing.join(', ')}`
|
|
318
|
-
});
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
next();
|
|
323
|
-
};
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* Refuse every non-session credential on a route that a session may still use.
|
|
327
|
-
*
|
|
328
|
-
* For the handful of operations that should stay a person's to perform — key
|
|
329
|
-
* management itself, most obviously, since a key that can mint keys is a key
|
|
330
|
-
* that cannot be revoked.
|
|
331
|
-
*/
|
|
332
|
-
export const denyApiKeys: RequestHandler = (req, res, next) => {
|
|
333
|
-
if (req.apiKey) {
|
|
334
|
-
res.status(403).json({
|
|
335
|
-
success: false,
|
|
336
|
-
message: 'This operation requires an interactive session, not an API key'
|
|
337
|
-
});
|
|
338
|
-
return;
|
|
339
|
-
}
|
|
340
|
-
next();
|
|
341
|
-
};
|