@wbce-d9/api 10.3.1 → 10.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/dist/app.js CHANGED
@@ -108,10 +108,12 @@ export default async function createApp() {
108
108
  await emitter.emitInit('app.before', { app });
109
109
  await emitter.emitInit('middlewares.before', { app });
110
110
  app.use(expressLogger);
111
- app.use((_req, res, next) => {
112
- res.setHeader('X-Powered-By', 'Directus');
113
- next();
114
- });
111
+ if (env['X_POWERED_BY_ENABLED'] === true) {
112
+ app.use((_req, res, next) => {
113
+ res.setHeader('X-Powered-By', 'Directus');
114
+ next();
115
+ });
116
+ }
115
117
  if (env['CORS_ENABLED'] === true) {
116
118
  app.use(cors);
117
119
  }
@@ -3,7 +3,7 @@ import { BaseException } from '@wbce-d9/exceptions';
3
3
  import express, { Router } from 'express';
4
4
  import * as samlify from 'samlify';
5
5
  import { getAuthProvider } from '../../auth.js';
6
- import { ACCESS_COOKIE_OPTIONS, REFRESH_COOKIE_OPTIONS } from '../../constants.js';
6
+ import { ACCESS_COOKIE_CLEAR_OPTIONS, ACCESS_COOKIE_OPTIONS, REFRESH_COOKIE_CLEAR_OPTIONS, REFRESH_COOKIE_OPTIONS, } from '../../constants.js';
7
7
  import getDatabase from '../../database/index.js';
8
8
  import emitter from '../../emitter.js';
9
9
  import env from '../../env.js';
@@ -103,13 +103,13 @@ export function createSAMLAuthRouter(providerName) {
103
103
  const { context } = sp.createLogoutRequest(idp, 'redirect', req.body);
104
104
  const authService = new AuthenticationService({ accountability: req.accountability, schema: req.schema });
105
105
  if (req.cookies[env['ACCESS_TOKEN_COOKIE_NAME']]) {
106
- res.clearCookie(env['ACCESS_TOKEN_COOKIE_NAME'], ACCESS_COOKIE_OPTIONS);
106
+ res.clearCookie(env['ACCESS_TOKEN_COOKIE_NAME'], ACCESS_COOKIE_CLEAR_OPTIONS);
107
107
  }
108
108
  if (req.cookies[env['REFRESH_TOKEN_COOKIE_NAME']]) {
109
109
  const currentRefreshToken = req.cookies[env['REFRESH_TOKEN_COOKIE_NAME']];
110
110
  if (currentRefreshToken) {
111
111
  await authService.logout(currentRefreshToken);
112
- res.clearCookie(env['REFRESH_TOKEN_COOKIE_NAME'], REFRESH_COOKIE_OPTIONS);
112
+ res.clearCookie(env['REFRESH_TOKEN_COOKIE_NAME'], REFRESH_COOKIE_CLEAR_OPTIONS);
113
113
  }
114
114
  }
115
115
  return res.redirect(context);
@@ -230,6 +230,9 @@ REFRESH_TOKEN_COOKIE_NAME="directus_refresh_token"
230
230
  # and it should be greater than the time taken for a login request with an invalid password [500]
231
231
  # LOGIN_STALL_TIME=500
232
232
 
233
+ # Whether or not to enable the X-Powered-By Header [true]
234
+ X_POWERED_BY_ENABLED=false
235
+
233
236
  # Whether or not to enable the CORS headers [false]
234
237
  CORS_ENABLED=true
235
238
 
@@ -9,8 +9,10 @@ export declare const COLUMN_TRANSFORMS: string[];
9
9
  export declare const GENERATE_SPECIAL: string[];
10
10
  export declare const UUID_REGEX = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
11
11
  export declare const REFRESH_COOKIE_OPTIONS: CookieOptions;
12
+ export declare const REFRESH_COOKIE_CLEAR_OPTIONS: CookieOptions;
12
13
  export declare const GET_SET_HEADER: (cookieValue: string) => string;
13
14
  export declare const ACCESS_COOKIE_OPTIONS: CookieOptions;
15
+ export declare const ACCESS_COOKIE_CLEAR_OPTIONS: CookieOptions;
14
16
  export declare const OAS_REQUIRED_SCHEMAS: string[];
15
17
  /** Formats from which transformation is supported */
16
18
  export declare const SUPPORTED_IMAGE_TRANSFORM_FORMATS: string[];
package/dist/constants.js CHANGED
@@ -56,6 +56,8 @@ export const REFRESH_COOKIE_OPTIONS = {
56
56
  sameSite: env['REFRESH_TOKEN_COOKIE_SAME_SITE'] || 'strict',
57
57
  path: '/auth',
58
58
  };
59
+ const { maxAge: refreshCookieMaxAge, expires: refreshCookieExpires, ...refresh_cookie_clear_options } = REFRESH_COOKIE_OPTIONS;
60
+ export const REFRESH_COOKIE_CLEAR_OPTIONS = refresh_cookie_clear_options;
59
61
  export const GET_SET_HEADER = (cookieValue) => {
60
62
  const domainHeader = env['REFRESH_TOKEN_COOKIE_DOMAIN'] ? ` Domain=${env['REFRESH_TOKEN_COOKIE_DOMAIN']};` : '';
61
63
  const partitionedHeader = env['REFRESH_TOKEN_COOKIE_PARTITIONED'] == true ? ' Partitioned;' : '';
@@ -70,6 +72,8 @@ export const ACCESS_COOKIE_OPTIONS = {
70
72
  sameSite: env['ACCESS_TOKEN_COOKIE_SAME_SITE'] || 'strict',
71
73
  path: '/',
72
74
  };
75
+ const { maxAge: accessCookieMaxAge, expires: accessCookieExpires, ...access_cookie_clear_options } = ACCESS_COOKIE_OPTIONS;
76
+ export const ACCESS_COOKIE_CLEAR_OPTIONS = access_cookie_clear_options;
73
77
  export const OAS_REQUIRED_SCHEMAS = ['Diff', 'Schema', 'Query', 'x-metadata'];
74
78
  /** Formats from which transformation is supported */
75
79
  export const SUPPORTED_IMAGE_TRANSFORM_FORMATS = ['image/jpeg', 'image/png', 'image/webp', 'image/tiff', 'image/avif'];
@@ -1,6 +1,6 @@
1
1
  import { Router } from 'express';
2
2
  import { createLDAPAuthRouter, createLocalAuthRouter, createOAuth2AuthRouter, createOpenIDAuthRouter, createSAMLAuthRouter, } from '../auth/drivers/index.js';
3
- import { ACCESS_COOKIE_OPTIONS, DEFAULT_AUTH_PROVIDER, REFRESH_COOKIE_OPTIONS } from '../constants.js';
3
+ import { ACCESS_COOKIE_CLEAR_OPTIONS, ACCESS_COOKIE_OPTIONS, DEFAULT_AUTH_PROVIDER, REFRESH_COOKIE_CLEAR_OPTIONS, REFRESH_COOKIE_OPTIONS, } from '../constants.js';
4
4
  import env from '../env.js';
5
5
  import { InvalidPayloadException } from '../exceptions/index.js';
6
6
  import logger from '../logger.js';
@@ -55,6 +55,9 @@ router.post('/refresh', asyncHandler(async (req, res, next) => {
55
55
  accountability: accountability,
56
56
  schema: req.schema,
57
57
  });
58
+ if (req.cookies[env['ACCESS_TOKEN_COOKIE_NAME']]) {
59
+ res.clearCookie(env['ACCESS_TOKEN_COOKIE_NAME'], ACCESS_COOKIE_CLEAR_OPTIONS);
60
+ }
58
61
  const currentRefreshToken = req.body.refresh_token || req.cookies[env['REFRESH_TOKEN_COOKIE_NAME']];
59
62
  if (!currentRefreshToken) {
60
63
  throw new InvalidPayloadException(`"refresh_token" is required in either the JSON payload or Cookie`);
@@ -95,10 +98,10 @@ router.post('/logout', asyncHandler(async (req, res, next) => {
95
98
  }
96
99
  await authenticationService.logout(currentRefreshToken);
97
100
  if (req.cookies[env['ACCESS_TOKEN_COOKIE_NAME']]) {
98
- res.clearCookie(env['ACCESS_TOKEN_COOKIE_NAME'], ACCESS_COOKIE_OPTIONS);
101
+ res.clearCookie(env['ACCESS_TOKEN_COOKIE_NAME'], ACCESS_COOKIE_CLEAR_OPTIONS);
99
102
  }
100
103
  if (req.cookies[env['REFRESH_TOKEN_COOKIE_NAME']]) {
101
- res.clearCookie(env['REFRESH_TOKEN_COOKIE_NAME'], REFRESH_COOKIE_OPTIONS);
104
+ res.clearCookie(env['REFRESH_TOKEN_COOKIE_NAME'], REFRESH_COOKIE_CLEAR_OPTIONS);
102
105
  }
103
106
  return next();
104
107
  }), respond);
@@ -35,7 +35,7 @@ export default async function runAST(originalAST, schema, options) {
35
35
  return null;
36
36
  // Run the items through the special transforms
37
37
  const payloadService = new PayloadService(collection, { knex, schema });
38
- let items = await payloadService.processValues('read', rawItems);
38
+ let items = await payloadService.processValues('read', rawItems, query.alias ?? {});
39
39
  if (!items || (Array.isArray(items) && items.length === 0))
40
40
  return items;
41
41
  // Apply the `_in` filters to the nested collection batches
package/dist/env.js CHANGED
@@ -54,6 +54,7 @@ const allowedEnvironmentVars = [
54
54
  'HSTS_.+',
55
55
  // hashing
56
56
  'HASH_.+',
57
+ 'X_POWERED_BY_ENABLED',
57
58
  // cors
58
59
  'CORS_ENABLED',
59
60
  'CORS_ORIGIN',
@@ -226,6 +227,7 @@ const defaults = {
226
227
  LOGIN_STALL_TIME: 500,
227
228
  SERVER_SHUTDOWN_TIMEOUT: 1000,
228
229
  ROOT_REDIRECT: './admin',
230
+ X_POWERED_BY_ENABLED: true,
229
231
  CORS_ENABLED: false,
230
232
  CORS_ORIGIN: false,
231
233
  CORS_METHODS: 'GET,POST,PATCH,DELETE',
@@ -2,7 +2,7 @@ import { isEqual } from 'lodash-es';
2
2
  import getDatabase from '../database/index.js';
3
3
  import emitter from '../emitter.js';
4
4
  import env from '../env.js';
5
- import { InvalidCredentialsException } from '../exceptions/index.js';
5
+ import { InvalidCredentialsException, TokenExpiredException } from '../exceptions/index.js';
6
6
  import asyncHandler from '../utils/async-handler.js';
7
7
  import { getIPFromReq } from '../utils/get-ip-from-req.js';
8
8
  import isDirectusJWT from '../utils/is-directus-jwt.js';
@@ -40,6 +40,20 @@ export const handler = async (req, _res, next) => {
40
40
  if (req.token) {
41
41
  if (isDirectusJWT(req.token)) {
42
42
  const payload = verifyAccessJWT(req.token, env['SECRET']);
43
+ // Try finding the user with a valid refresh token stored in the access token
44
+ const user = await database
45
+ .select({
46
+ session_expires: 's.expires',
47
+ user_id: 'u.id',
48
+ })
49
+ .from('directus_sessions AS s')
50
+ .leftJoin('directus_users AS u', 's.user', 'u.id')
51
+ .where('s.token', payload.refresh_token)
52
+ .andWhere('s.expires', '>=', new Date())
53
+ .first();
54
+ if (!user) {
55
+ throw new TokenExpiredException();
56
+ }
43
57
  req.accountability.role = payload.role;
44
58
  req.accountability.admin = payload.admin_access === true || payload.admin_access == 1;
45
59
  req.accountability.app = payload.app_access === true || payload.app_access == 1;
@@ -128,11 +128,22 @@ export class AuthenticationService {
128
128
  throw new InvalidOTPException(`"otp" is invalid`);
129
129
  }
130
130
  }
131
+ const refreshToken = nanoid(64);
132
+ const refreshTokenExpiration = new Date(Date.now() + getMilliseconds(env['REFRESH_TOKEN_TTL'], 0));
133
+ await this.knex('directus_sessions').insert({
134
+ token: refreshToken,
135
+ user: user.id,
136
+ expires: refreshTokenExpiration,
137
+ ip: this.accountability?.ip,
138
+ user_agent: this.accountability?.userAgent,
139
+ origin: this.accountability?.origin,
140
+ });
131
141
  const tokenPayload = {
132
142
  id: user.id,
133
143
  role: user.role,
134
144
  app_access: user.app_access,
135
145
  admin_access: user.admin_access,
146
+ refresh_token: refreshToken,
136
147
  };
137
148
  const customClaims = await emitter.emitFilter('auth.jwt', tokenPayload, {
138
149
  status: 'pending',
@@ -148,16 +159,6 @@ export class AuthenticationService {
148
159
  expiresIn: env['ACCESS_TOKEN_TTL'],
149
160
  issuer: 'directus',
150
161
  });
151
- const refreshToken = nanoid(64);
152
- const refreshTokenExpiration = new Date(Date.now() + getMilliseconds(env['REFRESH_TOKEN_TTL'], 0));
153
- await this.knex('directus_sessions').insert({
154
- token: refreshToken,
155
- user: user.id,
156
- expires: refreshTokenExpiration,
157
- ip: this.accountability?.ip,
158
- user_agent: this.accountability?.userAgent,
159
- origin: this.accountability?.origin,
160
- });
161
162
  await this.knex('directus_sessions').delete().where('expires', '<', new Date());
162
163
  if (this.accountability) {
163
164
  await this.activityService.createOne({
@@ -260,11 +261,20 @@ export class AuthenticationService {
260
261
  admin_access: record.role_admin_access,
261
262
  });
262
263
  }
264
+ const newRefreshToken = nanoid(64);
265
+ const refreshTokenExpiration = new Date(Date.now() + getMilliseconds(env['REFRESH_TOKEN_TTL'], 0));
266
+ await this.knex('directus_sessions')
267
+ .update({
268
+ token: newRefreshToken,
269
+ expires: refreshTokenExpiration,
270
+ })
271
+ .where({ token: refreshToken });
263
272
  const tokenPayload = {
264
273
  id: record.user_id,
265
274
  role: record.role_id,
266
275
  app_access: record.role_app_access,
267
276
  admin_access: record.role_admin_access,
277
+ refresh_token: newRefreshToken,
268
278
  };
269
279
  if (record.share_id) {
270
280
  tokenPayload.share = record.share_id;
@@ -291,14 +301,6 @@ export class AuthenticationService {
291
301
  expiresIn: env['ACCESS_TOKEN_TTL'],
292
302
  issuer: 'directus',
293
303
  });
294
- const newRefreshToken = nanoid(64);
295
- const refreshTokenExpiration = new Date(Date.now() + getMilliseconds(env['REFRESH_TOKEN_TTL'], 0));
296
- await this.knex('directus_sessions')
297
- .update({
298
- token: newRefreshToken,
299
- expires: refreshTokenExpiration,
300
- })
301
- .where({ token: refreshToken });
302
304
  if (record.user_id) {
303
305
  await this.knex('directus_users').update({ last_access: new Date() }).where({ id: record.user_id });
304
306
  }
@@ -5,7 +5,7 @@ import { GraphQLBoolean, GraphQLEnumType, GraphQLError, GraphQLFloat, GraphQLID,
5
5
  import { GraphQLJSON, InputTypeComposer, ObjectTypeComposer, SchemaComposer, toInputObjectType } from 'graphql-compose';
6
6
  import { flatten, get, mapKeys, merge, omit, pick, set, transform, uniq } from 'lodash-es';
7
7
  import { clearSystemCache, getCache } from '../../cache.js';
8
- import { ACCESS_COOKIE_OPTIONS, DEFAULT_AUTH_PROVIDER, GENERATE_SPECIAL, REFRESH_COOKIE_OPTIONS, } from '../../constants.js';
8
+ import { ACCESS_COOKIE_CLEAR_OPTIONS, ACCESS_COOKIE_OPTIONS, DEFAULT_AUTH_PROVIDER, GENERATE_SPECIAL, REFRESH_COOKIE_CLEAR_OPTIONS, REFRESH_COOKIE_OPTIONS, } from '../../constants.js';
9
9
  import getDatabase from '../../database/index.js';
10
10
  import env from '../../env.js';
11
11
  import { ForbiddenException, GraphQLValidationException, InvalidPayloadException } from '../../exceptions/index.js';
@@ -1766,10 +1766,10 @@ export class GraphQLService {
1766
1766
  }
1767
1767
  await authenticationService.logout(currentRefreshToken);
1768
1768
  if (req?.cookies?.[env['ACCESS_TOKEN_COOKIE_NAME']]) {
1769
- res?.clearCookie(env['ACCESS_TOKEN_COOKIE_NAME'], ACCESS_COOKIE_OPTIONS);
1769
+ res?.clearCookie(env['ACCESS_TOKEN_COOKIE_NAME'], ACCESS_COOKIE_CLEAR_OPTIONS);
1770
1770
  }
1771
1771
  if (req?.cookies?.[env['REFRESH_TOKEN_COOKIE_NAME']]) {
1772
- res?.clearCookie(env['REFRESH_TOKEN_COOKIE_NAME'], REFRESH_COOKIE_OPTIONS);
1772
+ res?.clearCookie(env['REFRESH_TOKEN_COOKIE_NAME'], REFRESH_COOKIE_CLEAR_OPTIONS);
1773
1773
  }
1774
1774
  return true;
1775
1775
  },
@@ -27,6 +27,8 @@ export declare class PayloadService {
27
27
  transformers: Transformers;
28
28
  processValues(action: Action, payloads: Partial<Item>[]): Promise<Partial<Item>[]>;
29
29
  processValues(action: Action, payload: Partial<Item>): Promise<Partial<Item>>;
30
+ processValues(action: Action, payloads: Partial<Item>[], aliasMap: Record<string, string>): Promise<Partial<Item>[]>;
31
+ processValues(action: Action, payload: Partial<Item>, aliasMap: Record<string, string>): Promise<Partial<Item>>;
30
32
  processAggregates(payload: Partial<Item>[]): void;
31
33
  processField(field: SchemaOverview['collections'][string]['fields'][string], payload: Partial<Item>, action: Action, accountability: Accountability | null): Promise<any>;
32
34
  /**
@@ -121,12 +121,20 @@ export class PayloadService {
121
121
  return value;
122
122
  },
123
123
  };
124
- async processValues(action, payload) {
124
+ async processValues(action, payload, aliasMap = {}) {
125
125
  const processedPayload = toArray(payload);
126
126
  if (processedPayload.length === 0)
127
127
  return [];
128
128
  const fieldsInPayload = Object.keys(processedPayload[0]);
129
129
  let specialFieldsInCollection = Object.entries(this.schema.collections[this.collection].fields).filter(([_name, field]) => field.special && field.special.length > 0);
130
+ const aliasEntries = Object.entries(aliasMap);
131
+ for (const [name, field] of specialFieldsInCollection) {
132
+ for (const [aliasName, fieldName] of aliasEntries) {
133
+ if (fieldName === name) {
134
+ specialFieldsInCollection.push([aliasName, { ...field, field: aliasName }]);
135
+ }
136
+ }
137
+ }
130
138
  if (action === 'read') {
131
139
  specialFieldsInCollection = specialFieldsInCollection.filter(([name]) => {
132
140
  return fieldsInPayload.includes(name);
@@ -59,6 +59,17 @@ export class SharesService extends ItemsService {
59
59
  await this.knex('directus_shares')
60
60
  .update({ times_used: record.share_times_used + 1 })
61
61
  .where('id', record.share_id);
62
+ const refreshToken = nanoid(64);
63
+ const refreshTokenExpiration = new Date(Date.now() + getMilliseconds(env['REFRESH_TOKEN_TTL'], 0));
64
+ await this.knex('directus_sessions').insert({
65
+ token: refreshToken,
66
+ expires: refreshTokenExpiration,
67
+ ip: this.accountability?.ip,
68
+ user_agent: this.accountability?.userAgent,
69
+ origin: this.accountability?.origin,
70
+ share: record.share_id,
71
+ });
72
+ await this.knex('directus_sessions').delete().where('expires', '<', new Date());
62
73
  const tokenPayload = {
63
74
  app_access: false,
64
75
  admin_access: false,
@@ -68,22 +79,12 @@ export class SharesService extends ItemsService {
68
79
  item: record.share_item,
69
80
  collection: record.share_collection,
70
81
  },
82
+ refresh_token: refreshToken,
71
83
  };
72
84
  const accessToken = jwt.sign(tokenPayload, env['SECRET'], {
73
85
  expiresIn: env['ACCESS_TOKEN_TTL'],
74
86
  issuer: 'directus',
75
87
  });
76
- const refreshToken = nanoid(64);
77
- const refreshTokenExpiration = new Date(Date.now() + getMilliseconds(env['REFRESH_TOKEN_TTL'], 0));
78
- await this.knex('directus_sessions').insert({
79
- token: refreshToken,
80
- expires: refreshTokenExpiration,
81
- ip: this.accountability?.ip,
82
- user_agent: this.accountability?.userAgent,
83
- origin: this.accountability?.origin,
84
- share: record.share_id,
85
- });
86
- await this.knex('directus_sessions').delete().where('expires', '<', new Date());
87
88
  return {
88
89
  accessToken,
89
90
  refreshToken,
@@ -34,6 +34,7 @@ export type DirectusTokenPayload = {
34
34
  collection: string;
35
35
  item: string;
36
36
  };
37
+ refresh_token: string;
37
38
  };
38
39
  export type ShareData = {
39
40
  share_id: string;
package/dist/utils/jwt.js CHANGED
@@ -21,9 +21,9 @@ export function verifyJWT(token, secret) {
21
21
  return payload;
22
22
  }
23
23
  export function verifyAccessJWT(token, secret) {
24
- const { id, role, app_access, admin_access, share, share_scope } = verifyJWT(token, secret);
25
- if (role === undefined || app_access === undefined || admin_access === undefined) {
24
+ const { id, role, app_access, admin_access, share, share_scope, refresh_token } = verifyJWT(token, secret);
25
+ if (role === undefined || app_access === undefined || admin_access === undefined || refresh_token === undefined) {
26
26
  throw new InvalidTokenException('Invalid token payload.');
27
27
  }
28
- return { id, role, app_access, admin_access, share, share_scope };
28
+ return { id, role, app_access, admin_access, share, share_scope, refresh_token };
29
29
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wbce-d9/api",
3
- "version": "10.3.1",
3
+ "version": "10.4.0",
4
4
  "description": "Directus is a real-time API and App dashboard for managing SQL database content",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -143,20 +143,20 @@
143
143
  "uuid-validate": "0.0.3",
144
144
  "vm2": "^3.9.19",
145
145
  "wellknown": "0.5.0",
146
- "@wbce-d9/app": "9.29.1",
147
- "@wbce-d9/constants": "9.26.4",
148
- "@wbce-d9/extensions-sdk": "9.26.6",
149
- "@wbce-d9/specs": "9.26.4",
146
+ "@wbce-d9/app": "9.30.0",
150
147
  "@wbce-d9/exceptions": "9.26.4",
151
- "@wbce-d9/storage": "9.26.4",
148
+ "@wbce-d9/constants": "9.27.0",
149
+ "@wbce-d9/extensions-sdk": "9.26.7",
152
150
  "@wbce-d9/schema": "9.26.4",
153
- "@wbce-d9/storage-driver-azure": "9.26.6",
154
- "@wbce-d9/storage-driver-cloudinary": "9.26.6",
155
- "@wbce-d9/storage-driver-gcs": "9.26.6",
156
- "@wbce-d9/storage-driver-local": "9.26.6",
157
- "@wbce-d9/storage-driver-s3": "9.26.8",
151
+ "@wbce-d9/storage": "9.26.4",
152
+ "@wbce-d9/specs": "9.26.4",
153
+ "@wbce-d9/storage-driver-azure": "9.26.7",
154
+ "@wbce-d9/storage-driver-cloudinary": "9.26.7",
155
+ "@wbce-d9/storage-driver-gcs": "9.26.7",
158
156
  "@wbce-d9/update-check": "9.26.4",
159
- "@wbce-d9/utils": "9.28.1"
157
+ "@wbce-d9/storage-driver-local": "9.26.7",
158
+ "@wbce-d9/storage-driver-s3": "9.26.9",
159
+ "@wbce-d9/utils": "9.29.0"
160
160
  },
161
161
  "devDependencies": {
162
162
  "@ngneat/falso": "6.4.0",
@@ -204,7 +204,7 @@
204
204
  "supertest": "6.3.3",
205
205
  "typescript": "5.0.4",
206
206
  "vitest": "0.30.1",
207
- "@wbce-d9/types": "9.26.4"
207
+ "@wbce-d9/types": "9.27.0"
208
208
  },
209
209
  "optionalDependencies": {
210
210
  "@keyv/redis": "2.5.7",