diff --git a/api/src/__utils__/items-utils.ts b/api/src/__utils__/items-utils.ts index a1d50932ff..868a7f7cec 100644 --- a/api/src/__utils__/items-utils.ts +++ b/api/src/__utils__/items-utils.ts @@ -2,8 +2,8 @@ export const sqlFieldFormatter = (schema: Record, table: string) => { const fields = []; // Exclude alias fields, unable to selected in DB - for (const field of Object.keys(schema.collections[table].fields)) { - if (schema.collections[table].fields[field].type !== 'alias') { + for (const field of Object.keys(schema['collections'][table].fields)) { + if (schema['collections'][table].fields[field].type !== 'alias') { fields.push(field); } } @@ -19,8 +19,8 @@ export const sqlFieldFormatter = (schema: Record, table: string) => export const sqlFieldList = (schema: Record, table: string) => { const fields = []; // Exclude alias fields, unable to selected in DB - for (const field of Object.keys(schema.collections[table].fields)) { - if (schema.collections[table].fields[field].type !== 'alias') { + for (const field of Object.keys(schema['collections'][table].fields)) { + if (schema['collections'][table].fields[field].type !== 'alias') { fields.push(field); } } diff --git a/api/src/app.ts b/api/src/app.ts index 6afbd89433..f208657596 100644 --- a/api/src/app.ts +++ b/api/src/app.ts @@ -66,7 +66,7 @@ export default async function createApp(): Promise { validateEnv(['KEY', 'SECRET']); - if (!new Url(env.PUBLIC_URL).isAbsolute()) { + if (!new Url(env['PUBLIC_URL']).isAbsolute()) { logger.warn('PUBLIC_URL should be a full URL'); } @@ -97,7 +97,7 @@ export default async function createApp(): Promise { const app = express(); app.disable('x-powered-by'); - app.set('trust proxy', env.IP_TRUST_PROXY); + app.set('trust proxy', env['IP_TRUST_PROXY']); app.set('query parser', (str: string) => qs.parse(str, { depth: 10 })); app.use( @@ -128,7 +128,7 @@ export default async function createApp(): Promise { ) ); - if (env.HSTS_ENABLED) { + if (env['HSTS_ENABLED']) { app.use(helmet.hsts(getConfigFromEnv('HSTS_', ['HSTS_ENABLED']))); } @@ -143,14 +143,14 @@ export default async function createApp(): Promise { next(); }); - if (env.CORS_ENABLED === true) { + if (env['CORS_ENABLED'] === true) { app.use(cors); } app.use((req, res, next) => { ( express.json({ - limit: env.MAX_PAYLOAD_SIZE, + limit: env['MAX_PAYLOAD_SIZE'], }) as RequestHandler )(req, res, (err: any) => { if (err) { @@ -166,8 +166,8 @@ export default async function createApp(): Promise { app.use(extractToken); app.get('/', (_req, res, next) => { - if (env.ROOT_REDIRECT) { - res.redirect(env.ROOT_REDIRECT); + if (env['ROOT_REDIRECT']) { + res.redirect(env['ROOT_REDIRECT']); } else { next(); } @@ -176,12 +176,12 @@ export default async function createApp(): Promise { app.get('/robots.txt', (_, res) => { res.set('Content-Type', 'text/plain'); res.status(200); - res.send(env.ROBOTS_TXT); + res.send(env['ROBOTS_TXT']); }); - if (env.SERVE_APP) { + if (env['SERVE_APP']) { const adminPath = require.resolve('@directus/app'); - const adminUrl = new Url(env.PUBLIC_URL).addPath('admin'); + const adminUrl = new Url(env['PUBLIC_URL']).addPath('admin'); const embeds = extensionManager.getEmbeds(); @@ -209,10 +209,10 @@ export default async function createApp(): Promise { } // use the rate limiter - all routes for now - if (env.RATE_LIMITER_GLOBAL_ENABLED === true) { + if (env['RATE_LIMITER_GLOBAL_ENABLED'] === true) { app.use(rateLimiterGlobal); } - if (env.RATE_LIMITER_ENABLED === true) { + if (env['RATE_LIMITER_ENABLED'] === true) { app.use(rateLimiter); } diff --git a/api/src/auth.ts b/api/src/auth.ts index 88edf2b36c..18b6fc560f 100644 --- a/api/src/auth.ts +++ b/api/src/auth.ts @@ -10,7 +10,7 @@ import type { AuthDriverOptions } from './types'; import { getConfigFromEnv } from './utils/get-config-from-env'; import { getSchema } from './utils/get-schema'; -const providerNames = toArray(env.AUTH_PROVIDERS); +const providerNames = toArray(env['AUTH_PROVIDERS']); const providers: Map = new Map(); @@ -26,12 +26,12 @@ export async function registerAuthProviders(): Promise { const options = { knex: getDatabase(), schema: await getSchema() }; // Register default provider if not disabled - if (!env.AUTH_DISABLE_DEFAULT) { + if (!env['AUTH_DISABLE_DEFAULT']) { const defaultProvider = getProviderInstance('local', options)!; providers.set(DEFAULT_AUTH_PROVIDER, defaultProvider); } - if (!env.AUTH_PROVIDERS) { + if (!env['AUTH_PROVIDERS']) { return; } @@ -83,4 +83,6 @@ function getProviderInstance( case 'saml': return new SAMLAuthDriver(options, config); } + + return undefined; } diff --git a/api/src/auth/drivers/ldap.ts b/api/src/auth/drivers/ldap.ts index 1882817c9e..df76209e28 100644 --- a/api/src/auth/drivers/ldap.ts +++ b/api/src/auth/drivers/ldap.ts @@ -62,12 +62,12 @@ export class LDAPAuthDriver extends AuthDriver { bindPassword === undefined || !userDn || !provider || - (!clientUrl && !config.client?.socketPath) + (!clientUrl && !config['client']?.socketPath) ) { throw new InvalidConfigException('Invalid provider config', { provider }); } - const clientConfig = typeof config.client === 'object' ? config.client : {}; + const clientConfig = typeof config['client'] === 'object' ? config['client'] : {}; this.bindClient = ldap.createClient({ url: clientUrl, reconnect: true, ...clientConfig }); this.bindClient.on('error', (err: Error) => { @@ -147,8 +147,8 @@ export class LDAPAuthDriver extends AuthDriver { res.on('searchEntry', ({ object }: SearchEntry) => { const user: UserInfo = { - dn: object.dn, - userAccountControl: Number(getEntryValue(object.userAccountControl) ?? 0), + dn: object['dn'], + userAccountControl: Number(getEntryValue(object['userAccountControl']) ?? 0), }; const firstName = getEntryValue(object[firstNameAttribute]); @@ -160,7 +160,7 @@ export class LDAPAuthDriver extends AuthDriver { const email = getEntryValue(object[mailAttribute]); if (email) user.email = email; - const uid = getEntryValue(object.uid); + const uid = getEntryValue(object['uid']); if (uid) user.uid = uid; resolve(user); @@ -197,10 +197,10 @@ export class LDAPAuthDriver extends AuthDriver { } res.on('searchEntry', ({ object }: SearchEntry) => { - if (typeof object.cn === 'object') { - userGroups = [...userGroups, ...object.cn]; - } else if (object.cn) { - userGroups.push(object.cn); + if (typeof object['cn'] === 'object') { + userGroups = [...userGroups, ...object['cn']]; + } else if (object['cn']) { + userGroups.push(object['cn']); } }); @@ -227,7 +227,7 @@ export class LDAPAuthDriver extends AuthDriver { } async getUserID(payload: Record): Promise { - if (!payload.identifier) { + if (!payload['identifier']) { throw new InvalidCredentialsException(); } @@ -239,7 +239,7 @@ export class LDAPAuthDriver extends AuthDriver { userDn, new EqualityFilter({ attribute: userAttribute ?? 'cn', - value: payload.identifier, + value: payload['identifier'], }), userScope ?? 'one' ); @@ -288,7 +288,7 @@ export class LDAPAuthDriver extends AuthDriver { try { await this.usersService.createOne({ - provider: this.config.provider, + provider: this.config['provider'], first_name: userInfo.firstName, last_name: userInfo.lastName, email: userInfo.email, @@ -312,9 +312,9 @@ export class LDAPAuthDriver extends AuthDriver { } return new Promise((resolve, reject) => { - const clientConfig = typeof this.config.client === 'object' ? this.config.client : {}; + const clientConfig = typeof this.config['client'] === 'object' ? this.config['client'] : {}; const client = ldap.createClient({ - url: this.config.clientUrl, + url: this.config['clientUrl'], ...clientConfig, reconnect: false, }); @@ -334,11 +334,11 @@ export class LDAPAuthDriver extends AuthDriver { }); } - async login(user: User, payload: Record): Promise { - await this.verify(user, payload.password); + override async login(user: User, payload: Record): Promise { + await this.verify(user, payload['password']); } - async refresh(user: User): Promise { + override async refresh(user: User): Promise { await this.validateBindClient(); const userInfo = await this.fetchUserInfo(user.external_identifier!); @@ -415,20 +415,20 @@ export function createLDAPAuthRouter(provider: string): Router { } as Record>; if (mode === 'json') { - payload.data.refresh_token = refreshToken; + payload['data']['refresh_token'] = refreshToken; } if (mode === 'cookie') { - res.cookie(env.REFRESH_TOKEN_COOKIE_NAME, refreshToken, { + res.cookie(env['REFRESH_TOKEN_COOKIE_NAME'], refreshToken, { httpOnly: true, - domain: env.REFRESH_TOKEN_COOKIE_DOMAIN, - maxAge: getMilliseconds(env.REFRESH_TOKEN_TTL), - secure: env.REFRESH_TOKEN_COOKIE_SECURE ?? false, - sameSite: (env.REFRESH_TOKEN_COOKIE_SAME_SITE as 'lax' | 'strict' | 'none') || 'strict', + domain: env['REFRESH_TOKEN_COOKIE_DOMAIN'], + maxAge: getMilliseconds(env['REFRESH_TOKEN_TTL']), + secure: env['REFRESH_TOKEN_COOKIE_SECURE'] ?? false, + sameSite: (env['REFRESH_TOKEN_COOKIE_SAME_SITE'] as 'lax' | 'strict' | 'none') || 'strict', }); } - res.locals.payload = payload; + res.locals['payload'] = payload; return next(); }), diff --git a/api/src/auth/drivers/local.ts b/api/src/auth/drivers/local.ts index 08693628da..f95870d44f 100644 --- a/api/src/auth/drivers/local.ts +++ b/api/src/auth/drivers/local.ts @@ -16,14 +16,14 @@ import { AuthDriver } from '../auth'; export class LocalAuthDriver extends AuthDriver { async getUserID(payload: Record): Promise { - if (!payload.email) { + if (!payload['email']) { throw new InvalidCredentialsException(); } const user = await this.knex .select('id') .from('directus_users') - .whereRaw('LOWER(??) = ?', ['email', payload.email.toLowerCase()]) + .whereRaw('LOWER(??) = ?', ['email', payload['email'].toLowerCase()]) .first(); if (!user) { @@ -39,8 +39,8 @@ export class LocalAuthDriver extends AuthDriver { } } - async login(user: User, payload: Record): Promise { - await this.verify(user, payload.password); + override async login(user: User, payload: Record): Promise { + await this.verify(user, payload['password']); } } @@ -57,7 +57,7 @@ export function createLocalAuthRouter(provider: string): Router { router.post( '/', asyncHandler(async (req, res, next) => { - const STALL_TIME = env.LOGIN_STALL_TIME; + const STALL_TIME = env['LOGIN_STALL_TIME']; const timeStart = performance.now(); const accountability: Accountability = { @@ -96,14 +96,14 @@ export function createLocalAuthRouter(provider: string): Router { } as Record>; if (mode === 'json') { - payload.data.refresh_token = refreshToken; + payload['data']['refresh_token'] = refreshToken; } if (mode === 'cookie') { - res.cookie(env.REFRESH_TOKEN_COOKIE_NAME, refreshToken, COOKIE_OPTIONS); + res.cookie(env['REFRESH_TOKEN_COOKIE_NAME'], refreshToken, COOKIE_OPTIONS); } - res.locals.payload = payload; + res.locals['payload'] = payload; return next(); }), diff --git a/api/src/auth/drivers/oauth2.ts b/api/src/auth/drivers/oauth2.ts index 4b9fa6919c..edcb224507 100644 --- a/api/src/auth/drivers/oauth2.ts +++ b/api/src/auth/drivers/oauth2.ts @@ -37,11 +37,11 @@ export class OAuth2AuthDriver extends LocalAuthDriver { const { authorizeUrl, accessUrl, profileUrl, clientId, clientSecret, ...additionalConfig } = config; - if (!authorizeUrl || !accessUrl || !profileUrl || !clientId || !clientSecret || !additionalConfig.provider) { - throw new InvalidConfigException('Invalid provider config', { provider: additionalConfig.provider }); + if (!authorizeUrl || !accessUrl || !profileUrl || !clientId || !clientSecret || !additionalConfig['provider']) { + throw new InvalidConfigException('Invalid provider config', { provider: additionalConfig['provider'] }); } - const redirectUrl = new Url(env.PUBLIC_URL).addPath('auth', 'login', additionalConfig.provider, 'callback'); + const redirectUrl = new Url(env['PUBLIC_URL']).addPath('auth', 'login', additionalConfig['provider'], 'callback'); this.redirectUrl = redirectUrl.toString(); this.usersService = new UsersService({ knex: this.knex, schema: this.schema }); @@ -51,12 +51,12 @@ export class OAuth2AuthDriver extends LocalAuthDriver { authorization_endpoint: authorizeUrl, token_endpoint: accessUrl, userinfo_endpoint: profileUrl, - issuer: additionalConfig.provider, + issuer: additionalConfig['provider'], }); const clientOptionsOverrides = getConfigFromEnv( - `AUTH_${config.provider.toUpperCase()}_CLIENT_`, - [`AUTH_${config.provider.toUpperCase()}_CLIENT_ID`, `AUTH_${config.provider.toUpperCase()}_CLIENT_SECRET`], + `AUTH_${config['provider'].toUpperCase()}_CLIENT_`, + [`AUTH_${config['provider'].toUpperCase()}_CLIENT_ID`, `AUTH_${config['provider'].toUpperCase()}_CLIENT_SECRET`], 'underscore' ); @@ -76,10 +76,10 @@ export class OAuth2AuthDriver extends LocalAuthDriver { generateAuthUrl(codeVerifier: string, prompt = false): string { try { const codeChallenge = generators.codeChallenge(codeVerifier); - const paramsConfig = typeof this.config.params === 'object' ? this.config.params : {}; + const paramsConfig = typeof this.config['params'] === 'object' ? this.config['params'] : {}; return this.client.authorizationUrl({ - scope: this.config.scope ?? 'email', + scope: this.config['scope'] ?? 'email', access_type: 'offline', prompt: prompt ? 'consent' : undefined, ...paramsConfig, @@ -103,8 +103,8 @@ export class OAuth2AuthDriver extends LocalAuthDriver { return user?.id; } - async getUserID(payload: Record): Promise { - if (!payload.code || !payload.codeVerifier || !payload.state) { + override async getUserID(payload: Record): Promise { + if (!payload['code'] || !payload['codeVerifier'] || !payload['state']) { logger.warn('[OAuth2] No code, codeVerifier or state in payload'); throw new InvalidCredentialsException(); } @@ -115,8 +115,8 @@ export class OAuth2AuthDriver extends LocalAuthDriver { try { tokenSet = await this.client.oauthCallback( this.redirectUrl, - { code: payload.code, state: payload.state }, - { code_verifier: payload.codeVerifier, state: generators.codeChallenge(payload.codeVerifier) } + { code: payload['code'], state: payload['state'] }, + { code_verifier: payload['codeVerifier'], state: generators.codeChallenge(payload['codeVerifier']) } ); userInfo = await this.client.userinfo(tokenSet.access_token!); } catch (e) { @@ -158,11 +158,11 @@ export class OAuth2AuthDriver extends LocalAuthDriver { try { await this.usersService.createOne({ provider, - first_name: userInfo[this.config.firstNameKey], - last_name: userInfo[this.config.lastNameKey], + first_name: userInfo[this.config['firstNameKey']], + last_name: userInfo[this.config['lastNameKey']], email: email, external_identifier: identifier, - role: this.config.defaultRoleId, + role: this.config['defaultRoleId'], auth_data: tokenSet.refresh_token && JSON.stringify({ refreshToken: tokenSet.refresh_token }), }); } catch (e) { @@ -176,11 +176,11 @@ export class OAuth2AuthDriver extends LocalAuthDriver { return (await this.fetchUserId(identifier)) as string; } - async login(user: User): Promise { + override async login(user: User): Promise { return this.refresh(user); } - async refresh(user: User): Promise { + override async refresh(user: User): Promise { let authData = user.auth_data as AuthData; if (typeof authData === 'string') { @@ -191,9 +191,9 @@ export class OAuth2AuthDriver extends LocalAuthDriver { } } - if (authData?.refreshToken) { + if (authData?.['refreshToken']) { try { - const tokenSet = await this.client.refresh(authData.refreshToken); + const tokenSet = await this.client.refresh(authData['refreshToken']); // Update user refreshToken if provided if (tokenSet.refresh_token) { await this.usersService.updateOne(user.id, { @@ -239,11 +239,15 @@ export function createOAuth2AuthRouter(providerName: string): Router { (req, res) => { const provider = getAuthProvider(providerName) as OAuth2AuthDriver; const codeVerifier = provider.generateCodeVerifier(); - const prompt = !!req.query.prompt; - const token = jwt.sign({ verifier: codeVerifier, redirect: req.query.redirect, prompt }, env.SECRET as string, { - expiresIn: '5m', - issuer: 'directus', - }); + const prompt = !!req.query['prompt']; + const token = jwt.sign( + { verifier: codeVerifier, redirect: req.query['redirect'], prompt }, + env['SECRET'] as string, + { + expiresIn: '5m', + issuer: 'directus', + } + ); res.cookie(`oauth2.${providerName}`, token, { httpOnly: true, @@ -269,7 +273,9 @@ export function createOAuth2AuthRouter(providerName: string): Router { let tokenData; try { - tokenData = jwt.verify(req.cookies[`oauth2.${providerName}`], env.SECRET as string, { issuer: 'directus' }) as { + tokenData = jwt.verify(req.cookies[`oauth2.${providerName}`], env['SECRET'] as string, { + issuer: 'directus', + }) as { verifier: string; redirect?: string; prompt: boolean; @@ -302,9 +308,9 @@ export function createOAuth2AuthRouter(providerName: string): Router { try { res.clearCookie(`oauth2.${providerName}`); authResponse = await authenticationService.login(providerName, { - code: req.query.code, + code: req.query['code'], codeVerifier: verifier, - state: req.query.state, + state: req.query['state'], }); } catch (error: any) { // Prompt user for a new refresh_token if invalidated @@ -331,18 +337,18 @@ export function createOAuth2AuthRouter(providerName: string): Router { const { accessToken, refreshToken, expires } = authResponse; if (redirect) { - res.cookie(env.REFRESH_TOKEN_COOKIE_NAME, refreshToken, { + res.cookie(env['REFRESH_TOKEN_COOKIE_NAME'], refreshToken, { httpOnly: true, - domain: env.REFRESH_TOKEN_COOKIE_DOMAIN, - maxAge: getMilliseconds(env.REFRESH_TOKEN_TTL), - secure: env.REFRESH_TOKEN_COOKIE_SECURE ?? false, - sameSite: (env.REFRESH_TOKEN_COOKIE_SAME_SITE as 'lax' | 'strict' | 'none') || 'strict', + domain: env['REFRESH_TOKEN_COOKIE_DOMAIN'], + maxAge: getMilliseconds(env['REFRESH_TOKEN_TTL']), + secure: env['REFRESH_TOKEN_COOKIE_SECURE'] ?? false, + sameSite: (env['REFRESH_TOKEN_COOKIE_SAME_SITE'] as 'lax' | 'strict' | 'none') || 'strict', }); return res.redirect(redirect); } - res.locals.payload = { + res.locals['payload'] = { data: { access_token: accessToken, refresh_token: refreshToken, expires }, }; diff --git a/api/src/auth/drivers/openid.ts b/api/src/auth/drivers/openid.ts index 96b725dd07..7acfba5976 100644 --- a/api/src/auth/drivers/openid.ts +++ b/api/src/auth/drivers/openid.ts @@ -37,15 +37,15 @@ export class OpenIDAuthDriver extends LocalAuthDriver { const { issuerUrl, clientId, clientSecret, ...additionalConfig } = config; - if (!issuerUrl || !clientId || !clientSecret || !additionalConfig.provider) { - throw new InvalidConfigException('Invalid provider config', { provider: additionalConfig.provider }); + if (!issuerUrl || !clientId || !clientSecret || !additionalConfig['provider']) { + throw new InvalidConfigException('Invalid provider config', { provider: additionalConfig['provider'] }); } - const redirectUrl = new Url(env.PUBLIC_URL).addPath('auth', 'login', additionalConfig.provider, 'callback'); + const redirectUrl = new Url(env['PUBLIC_URL']).addPath('auth', 'login', additionalConfig['provider'], 'callback'); const clientOptionsOverrides = getConfigFromEnv( - `AUTH_${config.provider.toUpperCase()}_CLIENT_`, - [`AUTH_${config.provider.toUpperCase()}_CLIENT_ID`, `AUTH_${config.provider.toUpperCase()}_CLIENT_SECRET`], + `AUTH_${config['provider'].toUpperCase()}_CLIENT_`, + [`AUTH_${config['provider'].toUpperCase()}_CLIENT_ID`, `AUTH_${config['provider'].toUpperCase()}_CLIENT_SECRET`], 'underscore' ); @@ -55,11 +55,11 @@ export class OpenIDAuthDriver extends LocalAuthDriver { this.client = new Promise((resolve, reject) => { Issuer.discover(issuerUrl) .then((issuer) => { - const supportedTypes = issuer.metadata.response_types_supported as string[] | undefined; + const supportedTypes = issuer.metadata['response_types_supported'] as string[] | undefined; if (!supportedTypes?.includes('code')) { reject( new InvalidConfigException('OpenID provider does not support required code flow', { - provider: additionalConfig.provider, + provider: additionalConfig['provider'], }) ); } @@ -89,10 +89,10 @@ export class OpenIDAuthDriver extends LocalAuthDriver { try { const client = await this.client; const codeChallenge = generators.codeChallenge(codeVerifier); - const paramsConfig = typeof this.config.params === 'object' ? this.config.params : {}; + const paramsConfig = typeof this.config['params'] === 'object' ? this.config['params'] : {}; return client.authorizationUrl({ - scope: this.config.scope ?? 'openid profile email', + scope: this.config['scope'] ?? 'openid profile email', access_type: 'offline', prompt: prompt ? 'consent' : undefined, ...paramsConfig, @@ -117,8 +117,8 @@ export class OpenIDAuthDriver extends LocalAuthDriver { return user?.id; } - async getUserID(payload: Record): Promise { - if (!payload.code || !payload.codeVerifier || !payload.state) { + override async getUserID(payload: Record): Promise { + if (!payload['code'] || !payload['codeVerifier'] || !payload['state']) { logger.warn('[OpenID] No code, codeVerifier or state in payload'); throw new InvalidCredentialsException(); } @@ -128,15 +128,15 @@ export class OpenIDAuthDriver extends LocalAuthDriver { try { const client = await this.client; - const codeChallenge = generators.codeChallenge(payload.codeVerifier); + const codeChallenge = generators.codeChallenge(payload['codeVerifier']); tokenSet = await client.callback( this.redirectUrl, - { code: payload.code, state: payload.state }, - { code_verifier: payload.codeVerifier, state: codeChallenge, nonce: codeChallenge } + { code: payload['code'], state: payload['state'] }, + { code_verifier: payload['codeVerifier'], state: codeChallenge, nonce: codeChallenge } ); userInfo = tokenSet.claims(); - if (client.issuer.metadata.userinfo_endpoint) { + if (client.issuer.metadata['userinfo_endpoint']) { userInfo = { ...userInfo, ...(await client.userinfo(tokenSet.access_token!)), @@ -151,7 +151,7 @@ export class OpenIDAuthDriver extends LocalAuthDriver { const { provider, identifierKey, allowPublicRegistration, requireVerifiedEmail } = this.config; - const email = userInfo.email ? String(userInfo.email) : undefined; + const email = userInfo['email'] ? String(userInfo['email']) : undefined; // Fallback to email if explicit identifier not found const identifier = userInfo[identifierKey ?? 'sub'] ? String(userInfo[identifierKey ?? 'sub']) : email; @@ -172,7 +172,7 @@ export class OpenIDAuthDriver extends LocalAuthDriver { return userId; } - const isEmailVerified = !requireVerifiedEmail || userInfo.email_verified; + const isEmailVerified = !requireVerifiedEmail || userInfo['email_verified']; // Is public registration allowed? if (!allowPublicRegistration || !isEmailVerified) { @@ -183,11 +183,11 @@ export class OpenIDAuthDriver extends LocalAuthDriver { try { await this.usersService.createOne({ provider, - first_name: userInfo.given_name, - last_name: userInfo.family_name, + first_name: userInfo['given_name'], + last_name: userInfo['family_name'], email: email, external_identifier: identifier, - role: this.config.defaultRoleId, + role: this.config['defaultRoleId'], auth_data: tokenSet.refresh_token && JSON.stringify({ refreshToken: tokenSet.refresh_token }), }); } catch (e) { @@ -201,11 +201,11 @@ export class OpenIDAuthDriver extends LocalAuthDriver { return (await this.fetchUserId(identifier)) as string; } - async login(user: User): Promise { + override async login(user: User): Promise { return this.refresh(user); } - async refresh(user: User): Promise { + override async refresh(user: User): Promise { let authData = user.auth_data as AuthData; if (typeof authData === 'string') { @@ -216,10 +216,10 @@ export class OpenIDAuthDriver extends LocalAuthDriver { } } - if (authData?.refreshToken) { + if (authData?.['refreshToken']) { try { const client = await this.client; - const tokenSet = await client.refresh(authData.refreshToken); + const tokenSet = await client.refresh(authData['refreshToken']); // Update user refreshToken if provided if (tokenSet.refresh_token) { await this.usersService.updateOne(user.id, { @@ -265,11 +265,15 @@ export function createOpenIDAuthRouter(providerName: string): Router { asyncHandler(async (req, res) => { const provider = getAuthProvider(providerName) as OpenIDAuthDriver; const codeVerifier = provider.generateCodeVerifier(); - const prompt = !!req.query.prompt; - const token = jwt.sign({ verifier: codeVerifier, redirect: req.query.redirect, prompt }, env.SECRET as string, { - expiresIn: '5m', - issuer: 'directus', - }); + const prompt = !!req.query['prompt']; + const token = jwt.sign( + { verifier: codeVerifier, redirect: req.query['redirect'], prompt }, + env['SECRET'] as string, + { + expiresIn: '5m', + issuer: 'directus', + } + ); res.cookie(`openid.${providerName}`, token, { httpOnly: true, @@ -296,7 +300,9 @@ export function createOpenIDAuthRouter(providerName: string): Router { let tokenData; try { - tokenData = jwt.verify(req.cookies[`openid.${providerName}`], env.SECRET as string, { issuer: 'directus' }) as { + tokenData = jwt.verify(req.cookies[`openid.${providerName}`], env['SECRET'] as string, { + issuer: 'directus', + }) as { verifier: string; redirect?: string; prompt: boolean; @@ -329,9 +335,9 @@ export function createOpenIDAuthRouter(providerName: string): Router { try { res.clearCookie(`openid.${providerName}`); authResponse = await authenticationService.login(providerName, { - code: req.query.code, + code: req.query['code'], codeVerifier: verifier, - state: req.query.state, + state: req.query['state'], }); } catch (error: any) { // Prompt user for a new refresh_token if invalidated @@ -360,18 +366,18 @@ export function createOpenIDAuthRouter(providerName: string): Router { const { accessToken, refreshToken, expires } = authResponse; if (redirect) { - res.cookie(env.REFRESH_TOKEN_COOKIE_NAME, refreshToken, { + res.cookie(env['REFRESH_TOKEN_COOKIE_NAME'], refreshToken, { httpOnly: true, - domain: env.REFRESH_TOKEN_COOKIE_DOMAIN, - maxAge: getMilliseconds(env.REFRESH_TOKEN_TTL), - secure: env.REFRESH_TOKEN_COOKIE_SECURE ?? false, - sameSite: (env.REFRESH_TOKEN_COOKIE_SAME_SITE as 'lax' | 'strict' | 'none') || 'strict', + domain: env['REFRESH_TOKEN_COOKIE_DOMAIN'], + maxAge: getMilliseconds(env['REFRESH_TOKEN_TTL']), + secure: env['REFRESH_TOKEN_COOKIE_SECURE'] ?? false, + sameSite: (env['REFRESH_TOKEN_COOKIE_SAME_SITE'] as 'lax' | 'strict' | 'none') || 'strict', }); return res.redirect(redirect); } - res.locals.payload = { + res.locals['payload'] = { data: { access_token: accessToken, refresh_token: refreshToken, expires }, }; diff --git a/api/src/auth/drivers/saml.ts b/api/src/auth/drivers/saml.ts index c6c6f01657..1ddd3ad6ad 100644 --- a/api/src/auth/drivers/saml.ts +++ b/api/src/auth/drivers/saml.ts @@ -30,8 +30,8 @@ export class SAMLAuthDriver extends LocalAuthDriver { this.config = config; this.usersService = new UsersService({ knex: this.knex, schema: this.schema }); - this.sp = samlify.ServiceProvider(getConfigFromEnv(`AUTH_${config.provider.toUpperCase()}_SP`)); - this.idp = samlify.IdentityProvider(getConfigFromEnv(`AUTH_${config.provider.toUpperCase()}_IDP`)); + this.sp = samlify.ServiceProvider(getConfigFromEnv(`AUTH_${config['provider'].toUpperCase()}_SP`)); + this.idp = samlify.IdentityProvider(getConfigFromEnv(`AUTH_${config['provider'].toUpperCase()}_IDP`)); } async fetchUserID(identifier: string) { @@ -44,7 +44,7 @@ export class SAMLAuthDriver extends LocalAuthDriver { return user?.id; } - async getUserID(payload: Record) { + override async getUserID(payload: Record) { const { provider, emailKey, identifierKey, givenNameKey, familyNameKey, allowPublicRegistration } = this.config; const email = payload[emailKey ?? 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress']; @@ -69,7 +69,7 @@ export class SAMLAuthDriver extends LocalAuthDriver { last_name: lastName, email: email, external_identifier: identifier.toLowerCase(), - role: this.config.defaultRoleId, + role: this.config['defaultRoleId'], }); } catch (error) { if (error instanceof RecordNotUniqueException) { @@ -82,7 +82,7 @@ export class SAMLAuthDriver extends LocalAuthDriver { } // There's no local checks to be done when the user is authenticated in the IDP - async login(_user: User): Promise { + override async login(_user: User): Promise { return; } } @@ -105,8 +105,8 @@ export function createSAMLAuthRouter(providerName: string) { const { context: url } = await sp.createLoginRequest(idp, 'redirect'); const parsedUrl = new URL(url); - if (req.query.redirect) { - parsedUrl.searchParams.append('RelayState', req.query.redirect as string); + if (req.query['redirect']) { + parsedUrl.searchParams.append('RelayState', req.query['redirect'] as string); } return res.redirect(parsedUrl.toString()); @@ -121,12 +121,12 @@ export function createSAMLAuthRouter(providerName: string) { const authService = new AuthenticationService({ accountability: req.accountability, schema: req.schema }); - if (req.cookies[env.REFRESH_TOKEN_COOKIE_NAME]) { - const currentRefreshToken = req.cookies[env.REFRESH_TOKEN_COOKIE_NAME]; + if (req.cookies[env['REFRESH_TOKEN_COOKIE_NAME']]) { + const currentRefreshToken = req.cookies[env['REFRESH_TOKEN_COOKIE_NAME']]; if (currentRefreshToken) { await authService.logout(currentRefreshToken); - res.clearCookie(env.REFRESH_TOKEN_COOKIE_NAME, COOKIE_OPTIONS); + res.clearCookie(env['REFRESH_TOKEN_COOKIE_NAME'], COOKIE_OPTIONS); } } @@ -147,7 +147,7 @@ export function createSAMLAuthRouter(providerName: string) { const authService = new AuthenticationService({ accountability: req.accountability, schema: req.schema }); const { accessToken, refreshToken, expires } = await authService.login(providerName, extract.attributes); - res.locals.payload = { + res.locals['payload'] = { data: { access_token: accessToken, refresh_token: refreshToken, @@ -156,7 +156,7 @@ export function createSAMLAuthRouter(providerName: string) { }; if (relayState) { - res.cookie(env.REFRESH_TOKEN_COOKIE_NAME, refreshToken, COOKIE_OPTIONS); + res.cookie(env['REFRESH_TOKEN_COOKIE_NAME'], refreshToken, COOKIE_OPTIONS); return res.redirect(relayState); } diff --git a/api/src/cache.ts b/api/src/cache.ts index fe8e277192..bffaba8496 100644 --- a/api/src/cache.ts +++ b/api/src/cache.ts @@ -20,11 +20,16 @@ type Store = 'memory' | 'redis' | 'memcache'; const messenger = getMessenger(); -if (env.MESSENGER_STORE === 'redis' && env.CACHE_STORE === 'memory' && env.CACHE_AUTO_PURGE && !messengerSubscribed) { +if ( + env['MESSENGER_STORE'] === 'redis' && + env['CACHE_STORE'] === 'memory' && + env['CACHE_AUTO_PURGE'] && + !messengerSubscribed +) { messengerSubscribed = true; messenger.subscribe('schemaChanged', async (opts) => { - if (cache && opts?.autoPurgeCache !== false) { + if (cache && opts?.['autoPurgeCache'] !== false) { await cache.clear(); } }); @@ -37,29 +42,29 @@ export function getCache(): { localSchemaCache: Keyv; lockCache: Keyv; } { - if (env.CACHE_ENABLED === true && cache === null) { + if (env['CACHE_ENABLED'] === true && cache === null) { validateEnv(['CACHE_NAMESPACE', 'CACHE_TTL', 'CACHE_STORE']); - cache = getKeyvInstance(env.CACHE_STORE, getMilliseconds(env.CACHE_TTL)); + cache = getKeyvInstance(env['CACHE_STORE'], getMilliseconds(env['CACHE_TTL'])); cache.on('error', (err) => logger.warn(err, `[cache] ${err}`)); } if (systemCache === null) { - systemCache = getKeyvInstance(env.CACHE_STORE, getMilliseconds(env.CACHE_SYSTEM_TTL), '_system'); + systemCache = getKeyvInstance(env['CACHE_STORE'], getMilliseconds(env['CACHE_SYSTEM_TTL']), '_system'); systemCache.on('error', (err) => logger.warn(err, `[system-cache] ${err}`)); } if (sharedSchemaCache === null) { - sharedSchemaCache = getKeyvInstance(env.CACHE_STORE, getMilliseconds(env.CACHE_SYSTEM_TTL), '_schema_shared'); + sharedSchemaCache = getKeyvInstance(env['CACHE_STORE'], getMilliseconds(env['CACHE_SYSTEM_TTL']), '_schema_shared'); sharedSchemaCache.on('error', (err) => logger.warn(err, `[shared-schema-cache] ${err}`)); } if (localSchemaCache === null) { - localSchemaCache = getKeyvInstance('memory', getMilliseconds(env.CACHE_SYSTEM_TTL), '_schema'); + localSchemaCache = getKeyvInstance('memory', getMilliseconds(env['CACHE_SYSTEM_TTL']), '_schema'); localSchemaCache.on('error', (err) => logger.warn(err, `[schema-cache] ${err}`)); } if (lockCache === null) { - lockCache = getKeyvInstance(env.CACHE_STORE, undefined, '_lock'); + lockCache = getKeyvInstance(env['CACHE_STORE'], undefined, '_lock'); lockCache.on('error', (err) => logger.warn(err, `[lock-cache] ${err}`)); } @@ -156,14 +161,14 @@ function getKeyvInstance(store: Store, ttl: number | undefined, namespaceSuffix? function getConfig(store: Store = 'memory', ttl: number | undefined, namespaceSuffix = ''): Options { const config: Options = { - namespace: `${env.CACHE_NAMESPACE}${namespaceSuffix}`, + namespace: `${env['CACHE_NAMESPACE']}${namespaceSuffix}`, ttl, }; if (store === 'redis') { const KeyvRedis = require('@keyv/redis'); - config.store = new KeyvRedis(env.CACHE_REDIS || getConfigFromEnv('CACHE_REDIS_')); + config.store = new KeyvRedis(env['CACHE_REDIS'] || getConfigFromEnv('CACHE_REDIS_')); } if (store === 'memcache') { @@ -171,7 +176,9 @@ function getConfig(store: Store = 'memory', ttl: number | undefined, namespaceSu // keyv-memcache uses memjs which only accepts a comma separated string instead of an array, // so we need to join array into a string when applicable. See #7986 - const cacheMemcache = Array.isArray(env.CACHE_MEMCACHE) ? env.CACHE_MEMCACHE.join(',') : env.CACHE_MEMCACHE; + const cacheMemcache = Array.isArray(env['CACHE_MEMCACHE']) + ? env['CACHE_MEMCACHE'].join(',') + : env['CACHE_MEMCACHE']; config.store = new KeyvMemcache(cacheMemcache); } diff --git a/api/src/cli/commands/bootstrap/index.ts b/api/src/cli/commands/bootstrap/index.ts index a0127566cd..544bd4f50c 100644 --- a/api/src/cli/commands/bootstrap/index.ts +++ b/api/src/cli/commands/bootstrap/index.ts @@ -32,9 +32,9 @@ export default async function bootstrap({ skipAdminInit }: { skipAdminInit?: boo logger.info('Skipping creation of default Admin user and role...'); } - if (env.PROJECT_NAME && typeof env.PROJECT_NAME === 'string' && env.PROJECT_NAME.length > 0) { + if (env['PROJECT_NAME'] && typeof env['PROJECT_NAME'] === 'string' && env['PROJECT_NAME'].length > 0) { const settingsService = new SettingsService({ schema }); - await settingsService.upsertSingleton({ project_name: env.PROJECT_NAME }); + await settingsService.upsertSingleton({ project_name: env['PROJECT_NAME'] }); } } else { logger.info('Database already initialized, skipping install'); @@ -60,6 +60,8 @@ async function waitForDatabase(database: Knex) { // This will throw and exit the process if the database is not available await validateDatabaseConnection(database); + + return database; } async function createDefaultAdmin(schema: SchemaOverview) { @@ -72,14 +74,14 @@ async function createDefaultAdmin(schema: SchemaOverview) { logger.info('Adding first admin user...'); const usersService = new UsersService({ schema }); - let adminEmail = env.ADMIN_EMAIL; + let adminEmail = env['ADMIN_EMAIL']; if (!adminEmail) { logger.info('No admin email provided. Defaulting to "admin@example.com"'); adminEmail = 'admin@example.com'; } - let adminPassword = env.ADMIN_PASSWORD; + let adminPassword = env['ADMIN_PASSWORD']; if (!adminPassword) { adminPassword = nanoid(12); diff --git a/api/src/cli/utils/create-db-connection.ts b/api/src/cli/utils/create-db-connection.ts index 422b907b85..a31651cc0c 100644 --- a/api/src/cli/utils/create-db-connection.ts +++ b/api/src/cli/utils/create-db-connection.ts @@ -35,7 +35,7 @@ export default function createDBConnection(client: Driver, credentials: Credenti if (client === 'pg' || client === 'cockroachdb') { const { ssl } = credentials as Credentials; - connection['ssl'] = ssl; + connection.ssl = ssl; } if (client === 'mssql') { diff --git a/api/src/cli/utils/create-env/index.ts b/api/src/cli/utils/create-env/index.ts index c06f818b92..8a56c3e9b6 100644 --- a/api/src/cli/utils/create-env/index.ts +++ b/api/src/cli/utils/create-env/index.ts @@ -33,7 +33,7 @@ export default async function createEnv( }; for (const [key, value] of Object.entries(credentials)) { - config.database[`DB_${key.toUpperCase()}`] = value; + config['database'][`DB_${key.toUpperCase()}`] = value; } const configAsStrings: any = {}; diff --git a/api/src/constants.ts b/api/src/constants.ts index 894a8d2984..83a9c35845 100644 --- a/api/src/constants.ts +++ b/api/src/constants.ts @@ -55,10 +55,10 @@ export const UUID_REGEX = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a export const COOKIE_OPTIONS: CookieOptions = { httpOnly: true, - domain: env.REFRESH_TOKEN_COOKIE_DOMAIN, - maxAge: getMilliseconds(env.REFRESH_TOKEN_TTL), - secure: env.REFRESH_TOKEN_COOKIE_SECURE ?? false, - sameSite: (env.REFRESH_TOKEN_COOKIE_SAME_SITE as 'lax' | 'strict' | 'none') || 'strict', + domain: env['REFRESH_TOKEN_COOKIE_DOMAIN'], + maxAge: getMilliseconds(env['REFRESH_TOKEN_TTL']), + secure: env['REFRESH_TOKEN_COOKIE_SECURE'] ?? false, + sameSite: (env['REFRESH_TOKEN_COOKIE_SAME_SITE'] as 'lax' | 'strict' | 'none') || 'strict', }; export const OAS_REQUIRED_SCHEMAS = ['Diff', 'Schema', 'Query', 'x-metadata']; diff --git a/api/src/controllers/activity.ts b/api/src/controllers/activity.ts index 5ef3fa0988..cb2947e29f 100644 --- a/api/src/controllers/activity.ts +++ b/api/src/controllers/activity.ts @@ -36,7 +36,7 @@ const readHandler = asyncHandler(async (req, res, next) => { const meta = await metaService.getMetaForQuery('directus_activity', req.sanitizedQuery); - res.locals.payload = { + res.locals['payload'] = { data: result, meta, }; @@ -55,9 +55,9 @@ router.get( schema: req.schema, }); - const record = await service.readOne(req.params.pk, req.sanitizedQuery); + const record = await service.readOne(req.params['pk'], req.sanitizedQuery); - res.locals.payload = { + res.locals['payload'] = { data: record || null, }; @@ -98,7 +98,7 @@ router.post( try { const record = await service.readOne(primaryKey, req.sanitizedQuery); - res.locals.payload = { + res.locals['payload'] = { data: record || null, }; } catch (error: any) { @@ -132,12 +132,12 @@ router.patch( throw new InvalidPayloadException(error.message); } - const primaryKey = await service.updateOne(req.params.pk, req.body); + const primaryKey = await service.updateOne(req.params['pk'], req.body); try { const record = await service.readOne(primaryKey, req.sanitizedQuery); - res.locals.payload = { + res.locals['payload'] = { data: record || null, }; } catch (error: any) { @@ -165,13 +165,13 @@ router.delete( schema: req.schema, }); - const item = await adminService.readOne(req.params.pk, { fields: ['action'] }); + const item = await adminService.readOne(req.params['pk'], { fields: ['action'] }); - if (!item || item.action !== 'comment') { + if (!item || item['action'] !== 'comment') { throw new ForbiddenException(); } - await service.deleteOne(req.params.pk); + await service.deleteOne(req.params['pk']); return next(); }), diff --git a/api/src/controllers/assets.ts b/api/src/controllers/assets.ts index 7e671ea7c8..8013c0a951 100644 --- a/api/src/controllers/assets.ts +++ b/api/src/controllers/assets.ts @@ -62,9 +62,9 @@ router.get( } // Check against ASSETS_TRANSFORM_MAX_OPERATIONS - if (transforms.length > Number(env.ASSETS_TRANSFORM_MAX_OPERATIONS)) { + if (transforms.length > Number(env['ASSETS_TRANSFORM_MAX_OPERATIONS'])) { throw new InvalidQueryException( - `"transforms" Parameter is only allowed ${env.ASSETS_TRANSFORM_MAX_OPERATIONS} transformations.` + `"transforms" Parameter is only allowed ${env['ASSETS_TRANSFORM_MAX_OPERATIONS']} transformations.` ); } @@ -77,37 +77,39 @@ router.get( } }); - transformation.transforms = transforms; + transformation['transforms'] = transforms; } - const systemKeys = SYSTEM_ASSET_ALLOW_LIST.map((transformation) => transformation.key!); + const systemKeys = SYSTEM_ASSET_ALLOW_LIST.map((transformation) => transformation['key']!); const allKeys: string[] = [ ...systemKeys, - ...(assetSettings.storage_asset_presets || []).map((transformation: TransformationParams) => transformation.key), + ...(assetSettings.storage_asset_presets || []).map( + (transformation: TransformationParams) => transformation['key'] + ), ]; // For use in the next request handler - res.locals.shortcuts = [...SYSTEM_ASSET_ALLOW_LIST, ...(assetSettings.storage_asset_presets || [])]; - res.locals.transformation = transformation; + res.locals['shortcuts'] = [...SYSTEM_ASSET_ALLOW_LIST, ...(assetSettings.storage_asset_presets || [])]; + res.locals['transformation'] = transformation; if ( Object.keys(transformation).length === 0 || - ('transforms' in transformation && transformation.transforms!.length === 0) + ('transforms' in transformation && transformation['transforms']!.length === 0) ) { return next(); } if (assetSettings.storage_asset_transform === 'all') { - if (transformation.key && allKeys.includes(transformation.key as string) === false) { - throw new InvalidQueryException(`Key "${transformation.key}" isn't configured.`); + if (transformation['key'] && allKeys.includes(transformation['key'] as string) === false) { + throw new InvalidQueryException(`Key "${transformation['key']}" isn't configured.`); } return next(); } else if (assetSettings.storage_asset_transform === 'presets') { - if (allKeys.includes(transformation.key as string)) return next(); + if (allKeys.includes(transformation['key'] as string)) return next(); throw new InvalidQueryException(`Only configured presets can be used in asset generation.`); } else { - if (transformation.key && systemKeys.includes(transformation.key as string)) return next(); + if (transformation['key'] && systemKeys.includes(transformation['key'] as string)) return next(); throw new InvalidQueryException(`Dynamic asset generation has been disabled for this project.`); } }), @@ -130,18 +132,18 @@ router.get( // Return file asyncHandler(async (req, res) => { - const id = req.params.pk?.substring(0, 36); + const id = req.params['pk']?.substring(0, 36); const service = new AssetsService({ accountability: req.accountability, schema: req.schema, }); - const transformation: TransformationParams | TransformationPreset = res.locals.transformation.key - ? (res.locals.shortcuts as TransformationPreset[]).find( - (transformation) => transformation.key === res.locals.transformation.key + const transformation: TransformationParams | TransformationPreset = res.locals['transformation'].key + ? (res.locals['shortcuts'] as TransformationPreset[]).find( + (transformation) => transformation['key'] === res.locals['transformation'].key ) - : res.locals.transformation; + : res.locals['transformation']; let range: Range | undefined = undefined; @@ -165,10 +167,10 @@ router.get( const { stream, file, stat } = await service.getAsset(id, transformation, range); - res.attachment(req.params.filename ?? file.filename_download); + res.attachment(req.params['filename'] ?? file.filename_download); res.setHeader('Content-Type', file.type); res.setHeader('Accept-Ranges', 'bytes'); - res.setHeader('Cache-Control', getCacheControlHeader(req, getMilliseconds(env.ASSETS_CACHE_TTL), false, true)); + res.setHeader('Cache-Control', getCacheControlHeader(req, getMilliseconds(env['ASSETS_CACHE_TTL']), false, true)); const unixTime = Date.parse(file.modified_on); if (!Number.isNaN(unixTime)) { @@ -227,6 +229,8 @@ router.get( }); } }); + + return undefined; }) ); diff --git a/api/src/controllers/auth.ts b/api/src/controllers/auth.ts index d926c2144d..ba861f59b2 100644 --- a/api/src/controllers/auth.ts +++ b/api/src/controllers/auth.ts @@ -54,7 +54,7 @@ for (const authProvider of authProviders) { router.use(`/login/${authProvider.name}`, authRouter); } -if (!env.AUTH_DISABLE_DEFAULT) { +if (!env['AUTH_DISABLE_DEFAULT']) { router.use('/login', createLocalAuthRouter(DEFAULT_AUTH_PROVIDER)); } @@ -77,7 +77,7 @@ router.post( schema: req.schema, }); - const currentRefreshToken = req.body.refresh_token || req.cookies[env.REFRESH_TOKEN_COOKIE_NAME]; + const currentRefreshToken = req.body.refresh_token || req.cookies[env['REFRESH_TOKEN_COOKIE_NAME']]; if (!currentRefreshToken) { throw new InvalidPayloadException(`"refresh_token" is required in either the JSON payload or Cookie`); @@ -92,14 +92,14 @@ router.post( } as Record>; if (mode === 'json') { - payload.data.refresh_token = refreshToken; + payload['data']['refresh_token'] = refreshToken; } if (mode === 'cookie') { - res.cookie(env.REFRESH_TOKEN_COOKIE_NAME, refreshToken, COOKIE_OPTIONS); + res.cookie(env['REFRESH_TOKEN_COOKIE_NAME'], refreshToken, COOKIE_OPTIONS); } - res.locals.payload = payload; + res.locals['payload'] = payload; return next(); }), respond @@ -124,7 +124,7 @@ router.post( schema: req.schema, }); - const currentRefreshToken = req.body.refresh_token || req.cookies[env.REFRESH_TOKEN_COOKIE_NAME]; + const currentRefreshToken = req.body.refresh_token || req.cookies[env['REFRESH_TOKEN_COOKIE_NAME']]; if (!currentRefreshToken) { throw new InvalidPayloadException(`"refresh_token" is required in either the JSON payload or Cookie`); @@ -132,12 +132,12 @@ router.post( await authenticationService.logout(currentRefreshToken); - if (req.cookies[env.REFRESH_TOKEN_COOKIE_NAME]) { - res.clearCookie(env.REFRESH_TOKEN_COOKIE_NAME, { + if (req.cookies[env['REFRESH_TOKEN_COOKIE_NAME']]) { + res.clearCookie(env['REFRESH_TOKEN_COOKIE_NAME'], { httpOnly: true, - domain: env.REFRESH_TOKEN_COOKIE_DOMAIN, - secure: env.REFRESH_TOKEN_COOKIE_SECURE ?? false, - sameSite: (env.REFRESH_TOKEN_COOKIE_SAME_SITE as 'lax' | 'strict' | 'none') || 'strict', + domain: env['REFRESH_TOKEN_COOKIE_DOMAIN'], + secure: env['REFRESH_TOKEN_COOKIE_SECURE'] ?? false, + sameSite: (env['REFRESH_TOKEN_COOKIE_SAME_SITE'] as 'lax' | 'strict' | 'none') || 'strict', }); } @@ -213,9 +213,9 @@ router.post( router.get( '/', asyncHandler(async (req, res, next) => { - res.locals.payload = { + res.locals['payload'] = { data: getAuthProviders(), - disableDefault: env.AUTH_DISABLE_DEFAULT, + disableDefault: env['AUTH_DISABLE_DEFAULT'], }; return next(); }), diff --git a/api/src/controllers/collections.ts b/api/src/controllers/collections.ts index b4305a5d47..77cb1a35c3 100644 --- a/api/src/controllers/collections.ts +++ b/api/src/controllers/collections.ts @@ -19,11 +19,11 @@ router.post( if (Array.isArray(req.body)) { const collectionKey = await collectionsService.createMany(req.body); const records = await collectionsService.readMany(collectionKey); - res.locals.payload = { data: records || null }; + res.locals['payload'] = { data: records || null }; } else { const collectionKey = await collectionsService.createOne(req.body); const record = await collectionsService.readOne(collectionKey); - res.locals.payload = { data: record || null }; + res.locals['payload'] = { data: record || null }; } return next(); @@ -52,7 +52,7 @@ const readHandler = asyncHandler(async (req, res, next) => { const meta = await metaService.getMetaForQuery('directus_collections', {}); - res.locals.payload = { data: result, meta }; + res.locals['payload'] = { data: result, meta }; return next(); }); @@ -67,8 +67,8 @@ router.get( schema: req.schema, }); - const collection = await collectionsService.readOne(req.params.collection); - res.locals.payload = { data: collection || null }; + const collection = await collectionsService.readOne(req.params['collection']); + res.locals['payload'] = { data: collection || null }; return next(); }), @@ -87,7 +87,7 @@ router.patch( try { const collections = await collectionsService.readMany(collectionKeys); - res.locals.payload = { data: collections || null }; + res.locals['payload'] = { data: collections || null }; } catch (error: any) { if (error instanceof ForbiddenException) { return next(); @@ -109,11 +109,11 @@ router.patch( schema: req.schema, }); - await collectionsService.updateOne(req.params.collection, req.body); + await collectionsService.updateOne(req.params['collection'], req.body); try { - const collection = await collectionsService.readOne(req.params.collection); - res.locals.payload = { data: collection || null }; + const collection = await collectionsService.readOne(req.params['collection']); + res.locals['payload'] = { data: collection || null }; } catch (error: any) { if (error instanceof ForbiddenException) { return next(); @@ -135,7 +135,7 @@ router.delete( schema: req.schema, }); - await collectionsService.deleteOne(req.params.collection); + await collectionsService.deleteOne(req.params['collection']); return next(); }), diff --git a/api/src/controllers/dashboards.ts b/api/src/controllers/dashboards.ts index fe3468c963..9f2699e362 100644 --- a/api/src/controllers/dashboards.ts +++ b/api/src/controllers/dashboards.ts @@ -33,10 +33,10 @@ router.post( try { if (Array.isArray(req.body)) { const items = await service.readMany(savedKeys, req.sanitizedQuery); - res.locals.payload = { data: items }; + res.locals['payload'] = { data: items }; } else { const item = await service.readOne(savedKeys[0], req.sanitizedQuery); - res.locals.payload = { data: item }; + res.locals['payload'] = { data: item }; } } catch (error) { if (error instanceof ForbiddenException) { @@ -64,7 +64,7 @@ const readHandler = asyncHandler(async (req, res, next) => { const records = await service.readByQuery(req.sanitizedQuery); const meta = await metaService.getMetaForQuery(req.collection, req.sanitizedQuery); - res.locals.payload = { data: records || null, meta }; + res.locals['payload'] = { data: records || null, meta }; return next(); }); @@ -79,9 +79,9 @@ router.get( schema: req.schema, }); - const record = await service.readOne(req.params.pk, req.sanitizedQuery); + const record = await service.readOne(req.params['pk'], req.sanitizedQuery); - res.locals.payload = { data: record || null }; + res.locals['payload'] = { data: record || null }; return next(); }), respond @@ -109,7 +109,7 @@ router.patch( try { const result = await service.readMany(keys, req.sanitizedQuery); - res.locals.payload = { data: result }; + res.locals['payload'] = { data: result }; } catch (error) { if (error instanceof ForbiddenException) { return next(); @@ -131,11 +131,11 @@ router.patch( schema: req.schema, }); - const primaryKey = await service.updateOne(req.params.pk, req.body); + const primaryKey = await service.updateOne(req.params['pk'], req.body); try { const item = await service.readOne(primaryKey, req.sanitizedQuery); - res.locals.payload = { data: item || null }; + res.locals['payload'] = { data: item || null }; } catch (error) { if (error instanceof ForbiddenException) { return next(); @@ -179,7 +179,7 @@ router.delete( schema: req.schema, }); - await service.deleteOne(req.params.pk); + await service.deleteOne(req.params['pk']); return next(); }), diff --git a/api/src/controllers/extensions.ts b/api/src/controllers/extensions.ts index 420bc5594c..60065c8891 100644 --- a/api/src/controllers/extensions.ts +++ b/api/src/controllers/extensions.ts @@ -15,7 +15,7 @@ const router = Router(); router.get( '/:type', asyncHandler(async (req, res, next) => { - const type = depluralize(req.params.type as Plural); + const type = depluralize(req.params['type'] as Plural); if (!isIn(type, EXTENSION_TYPES)) { throw new RouteNotFoundException(req.path); @@ -25,7 +25,7 @@ router.get( const extensions = extensionManager.getExtensionsList(type); - res.locals.payload = { + res.locals['payload'] = { data: extensions, }; @@ -45,7 +45,10 @@ router.get( } res.setHeader('Content-Type', 'application/javascript; charset=UTF-8'); - res.setHeader('Cache-Control', getCacheControlHeader(req, getMilliseconds(env.EXTENSIONS_CACHE_TTL), false, false)); + res.setHeader( + 'Cache-Control', + getCacheControlHeader(req, getMilliseconds(env['EXTENSIONS_CACHE_TTL']), false, false) + ); res.setHeader('Vary', 'Origin, Cache-Control'); res.end(extensionSource); }) diff --git a/api/src/controllers/fields.ts b/api/src/controllers/fields.ts index d77626521b..63e45e6d43 100644 --- a/api/src/controllers/fields.ts +++ b/api/src/controllers/fields.ts @@ -23,7 +23,7 @@ router.get( }); const fields = await service.readAll(); - res.locals.payload = { data: fields || null }; + res.locals['payload'] = { data: fields || null }; return next(); }), respond @@ -37,9 +37,9 @@ router.get( accountability: req.accountability, schema: req.schema, }); - const fields = await service.readAll(req.params.collection); + const fields = await service.readAll(req.params['collection']); - res.locals.payload = { data: fields || null }; + res.locals['payload'] = { data: fields || null }; return next(); }), respond @@ -54,9 +54,9 @@ router.get( schema: req.schema, }); - const field = await service.readOne(req.params.collection, req.params.field); + const field = await service.readOne(req.params['collection'], req.params['field']); - res.locals.payload = { data: field || null }; + res.locals['payload'] = { data: field || null }; return next(); }), respond @@ -96,11 +96,11 @@ router.post( const field: Partial & { field: string; type: Type | null } = req.body; - await service.createField(req.params.collection, field); + await service.createField(req.params['collection'], field); try { - const createdField = await service.readOne(req.params.collection, field.field); - res.locals.payload = { data: createdField || null }; + const createdField = await service.readOne(req.params['collection'], field.field); + res.locals['payload'] = { data: createdField || null }; } catch (error: any) { if (error instanceof ForbiddenException) { return next(); @@ -128,15 +128,15 @@ router.patch( } for (const field of req.body) { - await service.updateField(req.params.collection, field); + await service.updateField(req.params['collection'], field); } try { const results: any = []; for (const field of req.body) { - const updatedField = await service.readOne(req.params.collection, field.field); + const updatedField = await service.readOne(req.params['collection'], field.field); results.push(updatedField); - res.locals.payload = { data: results || null }; + res.locals['payload'] = { data: results || null }; } } catch (error: any) { if (error instanceof ForbiddenException) { @@ -186,13 +186,13 @@ router.patch( const fieldData: Partial & { field: string; type: Type } = req.body; - if (!fieldData.field) fieldData.field = req.params.field; + if (!fieldData.field) fieldData.field = req.params['field']; - await service.updateField(req.params.collection, fieldData); + await service.updateField(req.params['collection'], fieldData); try { - const updatedField = await service.readOne(req.params.collection, req.params.field); - res.locals.payload = { data: updatedField || null }; + const updatedField = await service.readOne(req.params['collection'], req.params['field']); + res.locals['payload'] = { data: updatedField || null }; } catch (error: any) { if (error instanceof ForbiddenException) { return next(); @@ -214,7 +214,7 @@ router.delete( accountability: req.accountability, schema: req.schema, }); - await service.deleteField(req.params.collection, req.params.field); + await service.deleteField(req.params['collection'], req.params['field']); return next(); }), respond diff --git a/api/src/controllers/files.ts b/api/src/controllers/files.ts index da14b1ff7a..7e9e1d4c41 100644 --- a/api/src/controllers/files.ts +++ b/api/src/controllers/files.ts @@ -38,7 +38,7 @@ export const multipartHandler: RequestHandler = (req, res, next) => { const savedFiles: PrimaryKey[] = []; const service = new FilesService({ accountability: req.accountability, schema: req.schema }); - const existingPrimaryKey = req.params.pk || undefined; + const existingPrimaryKey = req.params['pk'] || undefined; /** * The order of the fields in multipart/form-data is important. We require that all fields @@ -46,7 +46,7 @@ export const multipartHandler: RequestHandler = (req, res, next) => { * the row in directus_files async during the upload of the actual file. */ - let disk: string = toArray(env.STORAGE_LOCATIONS)[0]; + let disk: string = toArray(env['STORAGE_LOCATIONS'])[0]; let payload: any = {}; let fileCount = 0; @@ -95,6 +95,8 @@ export const multipartHandler: RequestHandler = (req, res, next) => { } catch (error: any) { busboy.emit('error', error); } + + return undefined; }); busboy.on('error', (error: Error) => { @@ -113,7 +115,7 @@ export const multipartHandler: RequestHandler = (req, res, next) => { return next(new InvalidPayloadException(`No files were included in the body`)); } - res.locals.savedFiles = savedFiles; + res.locals['savedFiles'] = savedFiles; return next(); } } @@ -130,7 +132,7 @@ router.post( let keys: PrimaryKey | PrimaryKey[] = []; if (req.is('multipart/form-data')) { - keys = res.locals.savedFiles; + keys = res.locals['savedFiles']; } else { keys = await service.createOne(req.body); } @@ -139,14 +141,14 @@ router.post( if (Array.isArray(keys) && keys.length > 1) { const records = await service.readMany(keys, req.sanitizedQuery); - res.locals.payload = { + res.locals['payload'] = { data: records, }; } else { const key = Array.isArray(keys) ? keys[0] : keys; const record = await service.readOne(key, req.sanitizedQuery); - res.locals.payload = { + res.locals['payload'] = { data: record, }; } @@ -186,7 +188,7 @@ router.post( try { const record = await service.readOne(primaryKey, req.sanitizedQuery); - res.locals.payload = { data: record || null }; + res.locals['payload'] = { data: record || null }; } catch (error: any) { if (error instanceof ForbiddenException) { return next(); @@ -223,7 +225,7 @@ const readHandler = asyncHandler(async (req, res, next) => { const meta = await metaService.getMetaForQuery('directus_files', req.sanitizedQuery); - res.locals.payload = { data: result, meta }; + res.locals['payload'] = { data: result, meta }; return next(); }); @@ -238,8 +240,8 @@ router.get( schema: req.schema, }); - const record = await service.readOne(req.params.pk, req.sanitizedQuery); - res.locals.payload = { data: record || null }; + const record = await service.readOne(req.params['pk'], req.sanitizedQuery); + res.locals['payload'] = { data: record || null }; return next(); }), respond @@ -267,7 +269,7 @@ router.patch( try { const result = await service.readMany(keys, req.sanitizedQuery); - res.locals.payload = { data: result || null }; + res.locals['payload'] = { data: result || null }; } catch (error: any) { if (error instanceof ForbiddenException) { return next(); @@ -290,11 +292,11 @@ router.patch( schema: req.schema, }); - await service.updateOne(req.params.pk, req.body); + await service.updateOne(req.params['pk'], req.body); try { - const record = await service.readOne(req.params.pk, req.sanitizedQuery); - res.locals.payload = { data: record || null }; + const record = await service.readOne(req.params['pk'], req.sanitizedQuery); + res.locals['payload'] = { data: record || null }; } catch (error: any) { if (error instanceof ForbiddenException) { return next(); @@ -339,7 +341,7 @@ router.delete( schema: req.schema, }); - await service.deleteOne(req.params.pk); + await service.deleteOne(req.params['pk']); return next(); }), diff --git a/api/src/controllers/flows.ts b/api/src/controllers/flows.ts index c9a014f1f5..bcdbadbe34 100644 --- a/api/src/controllers/flows.ts +++ b/api/src/controllers/flows.ts @@ -18,7 +18,7 @@ const webhookFlowHandler = asyncHandler(async (req, res, next) => { const flowManager = getFlowManager(); const result = await flowManager.runWebhookFlow( - `${req.method}-${req.params.pk}`, + `${req.method}-${req.params['pk']}`, { path: req.path, query: req.query, @@ -32,7 +32,7 @@ const webhookFlowHandler = asyncHandler(async (req, res, next) => { } ); - res.locals.payload = result; + res.locals['payload'] = result; return next(); }); @@ -60,10 +60,10 @@ router.post( try { if (Array.isArray(req.body)) { const items = await service.readMany(savedKeys, req.sanitizedQuery); - res.locals.payload = { data: items }; + res.locals['payload'] = { data: items }; } else { const item = await service.readOne(savedKeys[0], req.sanitizedQuery); - res.locals.payload = { data: item }; + res.locals['payload'] = { data: item }; } } catch (error) { if (error instanceof ForbiddenException) { @@ -91,7 +91,7 @@ const readHandler = asyncHandler(async (req, res, next) => { const records = await service.readByQuery(req.sanitizedQuery); const meta = await metaService.getMetaForQuery(req.collection, req.sanitizedQuery); - res.locals.payload = { data: records || null, meta }; + res.locals['payload'] = { data: records || null, meta }; return next(); }); @@ -106,9 +106,9 @@ router.get( schema: req.schema, }); - const record = await service.readOne(req.params.pk, req.sanitizedQuery); + const record = await service.readOne(req.params['pk'], req.sanitizedQuery); - res.locals.payload = { data: record || null }; + res.locals['payload'] = { data: record || null }; return next(); }), respond @@ -136,7 +136,7 @@ router.patch( try { const result = await service.readMany(keys, req.sanitizedQuery); - res.locals.payload = { data: result }; + res.locals['payload'] = { data: result }; } catch (error) { if (error instanceof ForbiddenException) { return next(); @@ -158,11 +158,11 @@ router.patch( schema: req.schema, }); - const primaryKey = await service.updateOne(req.params.pk, req.body); + const primaryKey = await service.updateOne(req.params['pk'], req.body); try { const item = await service.readOne(primaryKey, req.sanitizedQuery); - res.locals.payload = { data: item || null }; + res.locals['payload'] = { data: item || null }; } catch (error) { if (error instanceof ForbiddenException) { return next(); @@ -206,7 +206,7 @@ router.delete( schema: req.schema, }); - await service.deleteOne(req.params.pk); + await service.deleteOne(req.params['pk']); return next(); }), diff --git a/api/src/controllers/folders.ts b/api/src/controllers/folders.ts index 719465cc0e..c01bb933bc 100644 --- a/api/src/controllers/folders.ts +++ b/api/src/controllers/folders.ts @@ -33,10 +33,10 @@ router.post( try { if (Array.isArray(req.body)) { const records = await service.readMany(savedKeys, req.sanitizedQuery); - res.locals.payload = { data: records }; + res.locals['payload'] = { data: records }; } else { const record = await service.readOne(savedKeys[0], req.sanitizedQuery); - res.locals.payload = { data: record }; + res.locals['payload'] = { data: record }; } } catch (error: any) { if (error instanceof ForbiddenException) { @@ -73,7 +73,7 @@ const readHandler = asyncHandler(async (req, res, next) => { const meta = await metaService.getMetaForQuery('directus_folders', req.sanitizedQuery); - res.locals.payload = { data: result, meta }; + res.locals['payload'] = { data: result, meta }; return next(); }); @@ -87,9 +87,9 @@ router.get( accountability: req.accountability, schema: req.schema, }); - const record = await service.readOne(req.params.pk, req.sanitizedQuery); + const record = await service.readOne(req.params['pk'], req.sanitizedQuery); - res.locals.payload = { data: record || null }; + res.locals['payload'] = { data: record || null }; return next(); }), respond @@ -117,7 +117,7 @@ router.patch( try { const result = await service.readMany(keys, req.sanitizedQuery); - res.locals.payload = { data: result || null }; + res.locals['payload'] = { data: result || null }; } catch (error: any) { if (error instanceof ForbiddenException) { return next(); @@ -139,11 +139,11 @@ router.patch( schema: req.schema, }); - const primaryKey = await service.updateOne(req.params.pk, req.body); + const primaryKey = await service.updateOne(req.params['pk'], req.body); try { const record = await service.readOne(primaryKey, req.sanitizedQuery); - res.locals.payload = { data: record || null }; + res.locals['payload'] = { data: record || null }; } catch (error: any) { if (error instanceof ForbiddenException) { return next(); @@ -188,7 +188,7 @@ router.delete( schema: req.schema, }); - await service.deleteOne(req.params.pk); + await service.deleteOne(req.params['pk']); return next(); }), diff --git a/api/src/controllers/graphql.ts b/api/src/controllers/graphql.ts index 0687544699..312c2c7816 100644 --- a/api/src/controllers/graphql.ts +++ b/api/src/controllers/graphql.ts @@ -16,10 +16,10 @@ router.use( scope: 'system', }); - res.locals.payload = await service.execute(res.locals.graphqlParams); + res.locals['payload'] = await service.execute(res.locals['graphqlParams']); - if (res.locals.payload?.errors?.length > 0) { - res.locals.cache = false; + if (res.locals['payload']?.errors?.length > 0) { + res.locals['cache'] = false; } return next(); @@ -37,10 +37,10 @@ router.use( scope: 'items', }); - res.locals.payload = await service.execute(res.locals.graphqlParams); + res.locals['payload'] = await service.execute(res.locals['graphqlParams']); - if (res.locals.payload?.errors?.length > 0) { - res.locals.cache = false; + if (res.locals['payload']?.errors?.length > 0) { + res.locals['cache'] = false; } return next(); diff --git a/api/src/controllers/items.ts b/api/src/controllers/items.ts index e9e19e5496..0205d9e0ae 100644 --- a/api/src/controllers/items.ts +++ b/api/src/controllers/items.ts @@ -14,7 +14,7 @@ router.post( '/:collection', collectionExists, asyncHandler(async (req, res, next) => { - if (req.params.collection.startsWith('directus_')) throw new ForbiddenException(); + if (req.params['collection'].startsWith('directus_')) throw new ForbiddenException(); if (req.singleton) { throw new RouteNotFoundException(req.path); @@ -38,10 +38,10 @@ router.post( try { if (Array.isArray(req.body)) { const result = await service.readMany(savedKeys, req.sanitizedQuery); - res.locals.payload = { data: result || null }; + res.locals['payload'] = { data: result || null }; } else { const result = await service.readOne(savedKeys[0], req.sanitizedQuery); - res.locals.payload = { data: result || null }; + res.locals['payload'] = { data: result || null }; } } catch (error: any) { if (error instanceof ForbiddenException) { @@ -57,7 +57,7 @@ router.post( ); const readHandler = asyncHandler(async (req, res, next) => { - if (req.params.collection.startsWith('directus_')) throw new ForbiddenException(); + if (req.params['collection'].startsWith('directus_')) throw new ForbiddenException(); const service = new ItemsService(req.collection, { accountability: req.accountability, @@ -81,7 +81,7 @@ const readHandler = asyncHandler(async (req, res, next) => { const meta = await metaService.getMetaForQuery(req.collection, req.sanitizedQuery); - res.locals.payload = { + res.locals['payload'] = { meta: meta, data: result, }; @@ -96,16 +96,16 @@ router.get( '/:collection/:pk', collectionExists, asyncHandler(async (req, res, next) => { - if (req.params.collection.startsWith('directus_')) throw new ForbiddenException(); + if (req.params['collection'].startsWith('directus_')) throw new ForbiddenException(); const service = new ItemsService(req.collection, { accountability: req.accountability, schema: req.schema, }); - const result = await service.readOne(req.params.pk, req.sanitizedQuery); + const result = await service.readOne(req.params['pk'], req.sanitizedQuery); - res.locals.payload = { + res.locals['payload'] = { data: result || null, }; @@ -119,7 +119,7 @@ router.patch( collectionExists, validateBatch('update'), asyncHandler(async (req, res, next) => { - if (req.params.collection.startsWith('directus_')) throw new ForbiddenException(); + if (req.params['collection'].startsWith('directus_')) throw new ForbiddenException(); const service = new ItemsService(req.collection, { accountability: req.accountability, @@ -130,7 +130,7 @@ router.patch( await service.upsertSingleton(req.body); const item = await service.readSingleton(req.sanitizedQuery); - res.locals.payload = { data: item || null }; + res.locals['payload'] = { data: item || null }; return next(); } @@ -147,7 +147,7 @@ router.patch( try { const result = await service.readMany(keys, req.sanitizedQuery); - res.locals.payload = { data: result }; + res.locals['payload'] = { data: result }; } catch (error: any) { if (error instanceof ForbiddenException) { return next(); @@ -165,7 +165,7 @@ router.patch( '/:collection/:pk', collectionExists, asyncHandler(async (req, res, next) => { - if (req.params.collection.startsWith('directus_')) throw new ForbiddenException(); + if (req.params['collection'].startsWith('directus_')) throw new ForbiddenException(); if (req.singleton) { throw new RouteNotFoundException(req.path); @@ -176,11 +176,11 @@ router.patch( schema: req.schema, }); - const updatedPrimaryKey = await service.updateOne(req.params.pk, req.body); + const updatedPrimaryKey = await service.updateOne(req.params['pk'], req.body); try { const result = await service.readOne(updatedPrimaryKey, req.sanitizedQuery); - res.locals.payload = { data: result || null }; + res.locals['payload'] = { data: result || null }; } catch (error: any) { if (error instanceof ForbiddenException) { return next(); @@ -199,7 +199,7 @@ router.delete( collectionExists, validateBatch('delete'), asyncHandler(async (req, res, next) => { - if (req.params.collection.startsWith('directus_')) throw new ForbiddenException(); + if (req.params['collection'].startsWith('directus_')) throw new ForbiddenException(); const service = new ItemsService(req.collection, { accountability: req.accountability, @@ -224,14 +224,14 @@ router.delete( '/:collection/:pk', collectionExists, asyncHandler(async (req, res, next) => { - if (req.params.collection.startsWith('directus_')) throw new ForbiddenException(); + if (req.params['collection'].startsWith('directus_')) throw new ForbiddenException(); const service = new ItemsService(req.collection, { accountability: req.accountability, schema: req.schema, }); - await service.deleteOne(req.params.pk); + await service.deleteOne(req.params['pk']); return next(); }), respond diff --git a/api/src/controllers/notifications.ts b/api/src/controllers/notifications.ts index 79cbb3207c..3521b2cb83 100644 --- a/api/src/controllers/notifications.ts +++ b/api/src/controllers/notifications.ts @@ -33,10 +33,10 @@ router.post( try { if (Array.isArray(req.body)) { const records = await service.readMany(savedKeys, req.sanitizedQuery); - res.locals.payload = { data: records }; + res.locals['payload'] = { data: records }; } else { const record = await service.readOne(savedKeys[0], req.sanitizedQuery); - res.locals.payload = { data: record }; + res.locals['payload'] = { data: record }; } } catch (error: any) { if (error instanceof ForbiddenException) { @@ -73,7 +73,7 @@ const readHandler = asyncHandler(async (req, res, next) => { const meta = await metaService.getMetaForQuery('directus_notifications', req.sanitizedQuery); - res.locals.payload = { data: result, meta }; + res.locals['payload'] = { data: result, meta }; return next(); }); @@ -88,9 +88,9 @@ router.get( schema: req.schema, }); - const record = await service.readOne(req.params.pk, req.sanitizedQuery); + const record = await service.readOne(req.params['pk'], req.sanitizedQuery); - res.locals.payload = { data: record || null }; + res.locals['payload'] = { data: record || null }; return next(); }), respond @@ -118,7 +118,7 @@ router.patch( try { const result = await service.readMany(keys, req.sanitizedQuery); - res.locals.payload = { data: result }; + res.locals['payload'] = { data: result }; } catch (error: any) { if (error instanceof ForbiddenException) { return next(); @@ -140,11 +140,11 @@ router.patch( schema: req.schema, }); - const primaryKey = await service.updateOne(req.params.pk, req.body); + const primaryKey = await service.updateOne(req.params['pk'], req.body); try { const record = await service.readOne(primaryKey, req.sanitizedQuery); - res.locals.payload = { data: record }; + res.locals['payload'] = { data: record }; } catch (error: any) { if (error instanceof ForbiddenException) { return next(); @@ -189,7 +189,7 @@ router.delete( schema: req.schema, }); - await service.deleteOne(req.params.pk); + await service.deleteOne(req.params['pk']); return next(); }), diff --git a/api/src/controllers/operations.ts b/api/src/controllers/operations.ts index 1fa00a7b07..0d59f71fd8 100644 --- a/api/src/controllers/operations.ts +++ b/api/src/controllers/operations.ts @@ -33,10 +33,10 @@ router.post( try { if (Array.isArray(req.body)) { const items = await service.readMany(savedKeys, req.sanitizedQuery); - res.locals.payload = { data: items }; + res.locals['payload'] = { data: items }; } else { const item = await service.readOne(savedKeys[0], req.sanitizedQuery); - res.locals.payload = { data: item }; + res.locals['payload'] = { data: item }; } } catch (error) { if (error instanceof ForbiddenException) { @@ -64,7 +64,7 @@ const readHandler = asyncHandler(async (req, res, next) => { const records = await service.readByQuery(req.sanitizedQuery); const meta = await metaService.getMetaForQuery(req.collection, req.sanitizedQuery); - res.locals.payload = { data: records || null, meta }; + res.locals['payload'] = { data: records || null, meta }; return next(); }); @@ -79,9 +79,9 @@ router.get( schema: req.schema, }); - const record = await service.readOne(req.params.pk, req.sanitizedQuery); + const record = await service.readOne(req.params['pk'], req.sanitizedQuery); - res.locals.payload = { data: record || null }; + res.locals['payload'] = { data: record || null }; return next(); }), respond @@ -109,7 +109,7 @@ router.patch( try { const result = await service.readMany(keys, req.sanitizedQuery); - res.locals.payload = { data: result }; + res.locals['payload'] = { data: result }; } catch (error) { if (error instanceof ForbiddenException) { return next(); @@ -131,11 +131,11 @@ router.patch( schema: req.schema, }); - const primaryKey = await service.updateOne(req.params.pk, req.body); + const primaryKey = await service.updateOne(req.params['pk'], req.body); try { const item = await service.readOne(primaryKey, req.sanitizedQuery); - res.locals.payload = { data: item || null }; + res.locals['payload'] = { data: item || null }; } catch (error) { if (error instanceof ForbiddenException) { return next(); @@ -179,7 +179,7 @@ router.delete( schema: req.schema, }); - await service.deleteOne(req.params.pk); + await service.deleteOne(req.params['pk']); return next(); }), diff --git a/api/src/controllers/panels.ts b/api/src/controllers/panels.ts index e0893489d7..1f834c8973 100644 --- a/api/src/controllers/panels.ts +++ b/api/src/controllers/panels.ts @@ -33,10 +33,10 @@ router.post( try { if (Array.isArray(req.body)) { const items = await service.readMany(savedKeys, req.sanitizedQuery); - res.locals.payload = { data: items }; + res.locals['payload'] = { data: items }; } else { const item = await service.readOne(savedKeys[0], req.sanitizedQuery); - res.locals.payload = { data: item }; + res.locals['payload'] = { data: item }; } } catch (error) { if (error instanceof ForbiddenException) { @@ -64,7 +64,7 @@ const readHandler = asyncHandler(async (req, res, next) => { const records = await service.readByQuery(req.sanitizedQuery); const meta = await metaService.getMetaForQuery(req.collection, req.sanitizedQuery); - res.locals.payload = { data: records || null, meta }; + res.locals['payload'] = { data: records || null, meta }; return next(); }); @@ -79,9 +79,9 @@ router.get( schema: req.schema, }); - const record = await service.readOne(req.params.pk, req.sanitizedQuery); + const record = await service.readOne(req.params['pk'], req.sanitizedQuery); - res.locals.payload = { data: record || null }; + res.locals['payload'] = { data: record || null }; return next(); }), respond @@ -109,7 +109,7 @@ router.patch( try { const result = await service.readMany(keys, req.sanitizedQuery); - res.locals.payload = { data: result }; + res.locals['payload'] = { data: result }; } catch (error) { if (error instanceof ForbiddenException) { return next(); @@ -131,11 +131,11 @@ router.patch( schema: req.schema, }); - const primaryKey = await service.updateOne(req.params.pk, req.body); + const primaryKey = await service.updateOne(req.params['pk'], req.body); try { const item = await service.readOne(primaryKey, req.sanitizedQuery); - res.locals.payload = { data: item || null }; + res.locals['payload'] = { data: item || null }; } catch (error) { if (error instanceof ForbiddenException) { return next(); @@ -179,7 +179,7 @@ router.delete( schema: req.schema, }); - await service.deleteOne(req.params.pk); + await service.deleteOne(req.params['pk']); return next(); }), diff --git a/api/src/controllers/permissions.ts b/api/src/controllers/permissions.ts index c22f559202..362a2b5071 100644 --- a/api/src/controllers/permissions.ts +++ b/api/src/controllers/permissions.ts @@ -33,10 +33,10 @@ router.post( try { if (Array.isArray(req.body)) { const items = await service.readMany(savedKeys, req.sanitizedQuery); - res.locals.payload = { data: items }; + res.locals['payload'] = { data: items }; } else { const item = await service.readOne(savedKeys[0], req.sanitizedQuery); - res.locals.payload = { data: item }; + res.locals['payload'] = { data: item }; } } catch (error: any) { if (error instanceof ForbiddenException) { @@ -73,7 +73,7 @@ const readHandler = asyncHandler(async (req, res, next) => { const meta = await metaService.getMetaForQuery('directus_permissions', req.sanitizedQuery); - res.locals.payload = { data: result, meta }; + res.locals['payload'] = { data: result, meta }; return next(); }); @@ -89,9 +89,9 @@ router.get( schema: req.schema, }); - const record = await service.readOne(req.params.pk, req.sanitizedQuery); + const record = await service.readOne(req.params['pk'], req.sanitizedQuery); - res.locals.payload = { data: record }; + res.locals['payload'] = { data: record }; return next(); }), respond @@ -119,7 +119,7 @@ router.patch( try { const result = await service.readMany(keys, req.sanitizedQuery); - res.locals.payload = { data: result }; + res.locals['payload'] = { data: result }; } catch (error: any) { if (error instanceof ForbiddenException) { return next(); @@ -141,11 +141,11 @@ router.patch( schema: req.schema, }); - const primaryKey = await service.updateOne(req.params.pk, req.body); + const primaryKey = await service.updateOne(req.params['pk'], req.body); try { const item = await service.readOne(primaryKey, req.sanitizedQuery); - res.locals.payload = { data: item || null }; + res.locals['payload'] = { data: item || null }; } catch (error: any) { if (error instanceof ForbiddenException) { return next(); @@ -190,7 +190,7 @@ router.delete( schema: req.schema, }); - await service.deleteOne(req.params.pk); + await service.deleteOne(req.params['pk']); return next(); }), diff --git a/api/src/controllers/presets.ts b/api/src/controllers/presets.ts index 75ed8130f6..a5c416356d 100644 --- a/api/src/controllers/presets.ts +++ b/api/src/controllers/presets.ts @@ -33,10 +33,10 @@ router.post( try { if (Array.isArray(req.body)) { const records = await service.readMany(savedKeys, req.sanitizedQuery); - res.locals.payload = { data: records }; + res.locals['payload'] = { data: records }; } else { const record = await service.readOne(savedKeys[0], req.sanitizedQuery); - res.locals.payload = { data: record }; + res.locals['payload'] = { data: record }; } } catch (error: any) { if (error instanceof ForbiddenException) { @@ -73,7 +73,7 @@ const readHandler = asyncHandler(async (req, res, next) => { const meta = await metaService.getMetaForQuery('directus_presets', req.sanitizedQuery); - res.locals.payload = { data: result, meta }; + res.locals['payload'] = { data: result, meta }; return next(); }); @@ -88,9 +88,9 @@ router.get( schema: req.schema, }); - const record = await service.readOne(req.params.pk, req.sanitizedQuery); + const record = await service.readOne(req.params['pk'], req.sanitizedQuery); - res.locals.payload = { data: record || null }; + res.locals['payload'] = { data: record || null }; return next(); }), respond @@ -118,7 +118,7 @@ router.patch( try { const result = await service.readMany(keys, req.sanitizedQuery); - res.locals.payload = { data: result }; + res.locals['payload'] = { data: result }; } catch (error: any) { if (error instanceof ForbiddenException) { return next(); @@ -140,11 +140,11 @@ router.patch( schema: req.schema, }); - const primaryKey = await service.updateOne(req.params.pk, req.body); + const primaryKey = await service.updateOne(req.params['pk'], req.body); try { const record = await service.readOne(primaryKey, req.sanitizedQuery); - res.locals.payload = { data: record }; + res.locals['payload'] = { data: record }; } catch (error: any) { if (error instanceof ForbiddenException) { return next(); @@ -189,7 +189,7 @@ router.delete( schema: req.schema, }); - await service.deleteOne(req.params.pk); + await service.deleteOne(req.params['pk']); return next(); }), diff --git a/api/src/controllers/relations.ts b/api/src/controllers/relations.ts index a4a2b91efd..68448f517f 100644 --- a/api/src/controllers/relations.ts +++ b/api/src/controllers/relations.ts @@ -20,7 +20,7 @@ router.get( }); const relations = await service.readAll(); - res.locals.payload = { data: relations || null }; + res.locals['payload'] = { data: relations || null }; return next(); }), respond @@ -34,9 +34,9 @@ router.get( accountability: req.accountability, schema: req.schema, }); - const relations = await service.readAll(req.params.collection); + const relations = await service.readAll(req.params['collection']); - res.locals.payload = { data: relations || null }; + res.locals['payload'] = { data: relations || null }; return next(); }), respond @@ -51,9 +51,9 @@ router.get( schema: req.schema, }); - const relation = await service.readOne(req.params.collection, req.params.field); + const relation = await service.readOne(req.params['collection'], req.params['field']); - res.locals.payload = { data: relation || null }; + res.locals['payload'] = { data: relation || null }; return next(); }), respond @@ -89,7 +89,7 @@ router.post( try { const createdRelation = await service.readOne(req.body.collection, req.body.field); - res.locals.payload = { data: createdRelation || null }; + res.locals['payload'] = { data: createdRelation || null }; } catch (error: any) { if (error instanceof ForbiddenException) { return next(); @@ -130,11 +130,11 @@ router.patch( throw new InvalidPayloadException(error.message); } - await service.updateOne(req.params.collection, req.params.field, req.body); + await service.updateOne(req.params['collection'], req.params['field'], req.body); try { - const updatedField = await service.readOne(req.params.collection, req.params.field); - res.locals.payload = { data: updatedField || null }; + const updatedField = await service.readOne(req.params['collection'], req.params['field']); + res.locals['payload'] = { data: updatedField || null }; } catch (error: any) { if (error instanceof ForbiddenException) { return next(); @@ -156,7 +156,7 @@ router.delete( accountability: req.accountability, schema: req.schema, }); - await service.deleteOne(req.params.collection, req.params.field); + await service.deleteOne(req.params['collection'], req.params['field']); return next(); }), respond diff --git a/api/src/controllers/revisions.ts b/api/src/controllers/revisions.ts index f6a15b1e41..d53d65189d 100644 --- a/api/src/controllers/revisions.ts +++ b/api/src/controllers/revisions.ts @@ -22,7 +22,7 @@ const readHandler = asyncHandler(async (req, res, next) => { const records = await service.readByQuery(req.sanitizedQuery); const meta = await metaService.getMetaForQuery('directus_revisions', req.sanitizedQuery); - res.locals.payload = { data: records || null, meta }; + res.locals['payload'] = { data: records || null, meta }; return next(); }); @@ -37,9 +37,9 @@ router.get( schema: req.schema, }); - const record = await service.readOne(req.params.pk, req.sanitizedQuery); + const record = await service.readOne(req.params['pk'], req.sanitizedQuery); - res.locals.payload = { data: record || null }; + res.locals['payload'] = { data: record || null }; return next(); }), respond diff --git a/api/src/controllers/roles.ts b/api/src/controllers/roles.ts index 44625998fa..cfd83eb0fb 100644 --- a/api/src/controllers/roles.ts +++ b/api/src/controllers/roles.ts @@ -33,10 +33,10 @@ router.post( try { if (Array.isArray(req.body)) { const items = await service.readMany(savedKeys, req.sanitizedQuery); - res.locals.payload = { data: items }; + res.locals['payload'] = { data: items }; } else { const item = await service.readOne(savedKeys[0], req.sanitizedQuery); - res.locals.payload = { data: item }; + res.locals['payload'] = { data: item }; } } catch (error: any) { if (error instanceof ForbiddenException) { @@ -65,7 +65,7 @@ const readHandler = asyncHandler(async (req, res, next) => { const records = await service.readByQuery(req.sanitizedQuery); const meta = await metaService.getMetaForQuery('directus_roles', req.sanitizedQuery); - res.locals.payload = { data: records || null, meta }; + res.locals['payload'] = { data: records || null, meta }; return next(); }); @@ -80,9 +80,9 @@ router.get( schema: req.schema, }); - const record = await service.readOne(req.params.pk, req.sanitizedQuery); + const record = await service.readOne(req.params['pk'], req.sanitizedQuery); - res.locals.payload = { data: record || null }; + res.locals['payload'] = { data: record || null }; return next(); }), respond @@ -110,7 +110,7 @@ router.patch( try { const result = await service.readMany(keys, req.sanitizedQuery); - res.locals.payload = { data: result }; + res.locals['payload'] = { data: result }; } catch (error: any) { if (error instanceof ForbiddenException) { return next(); @@ -132,11 +132,11 @@ router.patch( schema: req.schema, }); - const primaryKey = await service.updateOne(req.params.pk, req.body); + const primaryKey = await service.updateOne(req.params['pk'], req.body); try { const item = await service.readOne(primaryKey, req.sanitizedQuery); - res.locals.payload = { data: item || null }; + res.locals['payload'] = { data: item || null }; } catch (error: any) { if (error instanceof ForbiddenException) { return next(); @@ -181,7 +181,7 @@ router.delete( schema: req.schema, }); - await service.deleteOne(req.params.pk); + await service.deleteOne(req.params['pk']); return next(); }), diff --git a/api/src/controllers/schema.ts b/api/src/controllers/schema.ts index 06e8ad2669..9109aab406 100644 --- a/api/src/controllers/schema.ts +++ b/api/src/controllers/schema.ts @@ -17,7 +17,7 @@ router.get( asyncHandler(async (req, res, next) => { const service = new SchemaService({ accountability: req.accountability }); const currentSnapshot = await service.snapshot(); - res.locals.payload = { data: currentSnapshot }; + res.locals['payload'] = { data: currentSnapshot }; return next(); }), respond @@ -36,7 +36,7 @@ router.post( const schemaMultipartHandler: RequestHandler = (req, res, next) => { if (req.is('application/json')) { if (Object.keys(req.body).length === 0) throw new InvalidPayloadException(`No data was included in the body`); - res.locals.uploadedSnapshot = req.body; + res.locals['uploadedSnapshot'] = req.body; return next(); } @@ -82,7 +82,7 @@ const schemaMultipartHandler: RequestHandler = (req, res, next) => { if (!uploadedSnapshot) throw new InvalidPayloadException(`No file was included in the body`); - res.locals.uploadedSnapshot = uploadedSnapshot; + res.locals['uploadedSnapshot'] = uploadedSnapshot; return next(); } catch (error: any) { @@ -104,14 +104,14 @@ router.post( asyncHandler(schemaMultipartHandler), asyncHandler(async (req, res, next) => { const service = new SchemaService({ accountability: req.accountability }); - const snapshot: Snapshot = res.locals.uploadedSnapshot; + const snapshot: Snapshot = res.locals['uploadedSnapshot']; const currentSnapshot = await service.snapshot(); const snapshotDiff = await service.diff(snapshot, { currentSnapshot, force: 'force' in req.query }); if (!snapshotDiff) return next(); const currentSnapshotHash = getVersionedHash(currentSnapshot); - res.locals.payload = { data: { hash: currentSnapshotHash, diff: snapshotDiff } }; + res.locals['payload'] = { data: { hash: currentSnapshotHash, diff: snapshotDiff } }; return next(); }), respond diff --git a/api/src/controllers/server.ts b/api/src/controllers/server.ts index 4bd025ec7f..acf2a47b5c 100644 --- a/api/src/controllers/server.ts +++ b/api/src/controllers/server.ts @@ -15,7 +15,7 @@ router.get( schema: req.schema, }); - res.locals.payload = await service.oas.generate(); + res.locals['payload'] = await service.oas.generate(); return next(); }), respond @@ -34,13 +34,13 @@ router.get( schema: req.schema, }); - const scope = req.params.scope || 'items'; + const scope = req.params['scope'] || 'items'; if (['items', 'system'].includes(scope) === false) throw new RouteNotFoundException(req.path); const info = await serverService.serverInfo(); const result = await service.graphql.generate(scope as 'items' | 'system'); - const filename = info.project.project_name + '_' + format(new Date(), 'yyyy-MM-dd') + '.graphql'; + const filename = info['project'].project_name + '_' + format(new Date(), 'yyyy-MM-dd') + '.graphql'; res.attachment(filename); res.send(result); @@ -55,7 +55,7 @@ router.get( schema: req.schema, }); const data = await service.serverInfo(); - res.locals.payload = { data }; + res.locals['payload'] = { data }; return next(); }), respond @@ -73,9 +73,9 @@ router.get( res.setHeader('Content-Type', 'application/health+json'); - if (data.status === 'error') res.status(503); - res.locals.payload = data; - res.locals.cache = false; + if (data['status'] === 'error') res.status(503); + res.locals['payload'] = data; + res.locals['cache'] = false; return next(); }), respond diff --git a/api/src/controllers/settings.ts b/api/src/controllers/settings.ts index 5a22694249..f89fd59d3f 100644 --- a/api/src/controllers/settings.ts +++ b/api/src/controllers/settings.ts @@ -17,7 +17,7 @@ router.get( schema: req.schema, }); const records = await service.readSingleton(req.sanitizedQuery); - res.locals.payload = { data: records || null }; + res.locals['payload'] = { data: records || null }; return next(); }), respond @@ -34,7 +34,7 @@ router.patch( try { const record = await service.readSingleton(req.sanitizedQuery); - res.locals.payload = { data: record || null }; + res.locals['payload'] = { data: record || null }; } catch (error: any) { if (error instanceof ForbiddenException) { return next(); diff --git a/api/src/controllers/shares.ts b/api/src/controllers/shares.ts index 46051aa808..9adbb26f2a 100644 --- a/api/src/controllers/shares.ts +++ b/api/src/controllers/shares.ts @@ -36,9 +36,9 @@ router.post( const { accessToken, refreshToken, expires } = await service.login(req.body); - res.cookie(env.REFRESH_TOKEN_COOKIE_NAME, refreshToken, COOKIE_OPTIONS); + res.cookie(env['REFRESH_TOKEN_COOKIE_NAME'], refreshToken, COOKIE_OPTIONS); - res.locals.payload = { data: { access_token: accessToken, expires } }; + res.locals['payload'] = { data: { access_token: accessToken, expires } }; return next(); }), @@ -92,10 +92,10 @@ router.post( try { if (Array.isArray(req.body)) { const items = await service.readMany(savedKeys, req.sanitizedQuery); - res.locals.payload = { data: items }; + res.locals['payload'] = { data: items }; } else { const item = await service.readOne(savedKeys[0], req.sanitizedQuery); - res.locals.payload = { data: item }; + res.locals['payload'] = { data: item }; } } catch (error) { if (error instanceof ForbiddenException) { @@ -118,7 +118,7 @@ const readHandler = asyncHandler(async (req, res, next) => { const records = await service.readByQuery(req.sanitizedQuery); - res.locals.payload = { data: records || null }; + res.locals['payload'] = { data: records || null }; return next(); }); @@ -132,7 +132,7 @@ router.get( schema: req.schema, }); - const record = await service.readOne(req.params.pk, { + const record = await service.readOne(req.params['pk'], { fields: ['id', 'collection', 'item', 'password', 'max_uses', 'times_used', 'date_start', 'date_end'], filter: { _and: [ @@ -168,7 +168,7 @@ router.get( }, }); - res.locals.payload = { data: record || null }; + res.locals['payload'] = { data: record || null }; return next(); }), respond @@ -182,9 +182,9 @@ router.get( schema: req.schema, }); - const record = await service.readOne(req.params.pk, req.sanitizedQuery); + const record = await service.readOne(req.params['pk'], req.sanitizedQuery); - res.locals.payload = { data: record || null }; + res.locals['payload'] = { data: record || null }; return next(); }), respond @@ -212,7 +212,7 @@ router.patch( try { const result = await service.readMany(keys, req.sanitizedQuery); - res.locals.payload = { data: result }; + res.locals['payload'] = { data: result }; } catch (error) { if (error instanceof ForbiddenException) { return next(); @@ -234,11 +234,11 @@ router.patch( schema: req.schema, }); - const primaryKey = await service.updateOne(req.params.pk, req.body); + const primaryKey = await service.updateOne(req.params['pk'], req.body); try { const item = await service.readOne(primaryKey, req.sanitizedQuery); - res.locals.payload = { data: item || null }; + res.locals['payload'] = { data: item || null }; } catch (error) { if (error instanceof ForbiddenException) { return next(); @@ -282,7 +282,7 @@ router.delete( schema: req.schema, }); - await service.deleteOne(req.params.pk); + await service.deleteOne(req.params['pk']); return next(); }), diff --git a/api/src/controllers/users.ts b/api/src/controllers/users.ts index 645fcb2bbb..a6540bcaea 100644 --- a/api/src/controllers/users.ts +++ b/api/src/controllers/users.ts @@ -35,10 +35,10 @@ router.post( try { if (Array.isArray(req.body)) { const items = await service.readMany(savedKeys, req.sanitizedQuery); - res.locals.payload = { data: items }; + res.locals['payload'] = { data: items }; } else { const item = await service.readOne(savedKeys[0], req.sanitizedQuery); - res.locals.payload = { data: item }; + res.locals['payload'] = { data: item }; } } catch (error: any) { if (error instanceof ForbiddenException) { @@ -67,7 +67,7 @@ const readHandler = asyncHandler(async (req, res, next) => { const item = await service.readByQuery(req.sanitizedQuery); const meta = await metaService.getMetaForQuery('directus_users', req.sanitizedQuery); - res.locals.payload = { data: item || null, meta }; + res.locals['payload'] = { data: item || null, meta }; return next(); }); @@ -86,7 +86,7 @@ router.get( app_access: false, }, }; - res.locals.payload = { data: user }; + res.locals['payload'] = { data: user }; return next(); } @@ -101,10 +101,10 @@ router.get( try { const item = await service.readOne(req.accountability.user, req.sanitizedQuery); - res.locals.payload = { data: item || null }; + res.locals['payload'] = { data: item || null }; } catch (error: any) { if (error instanceof ForbiddenException) { - res.locals.payload = { data: { id: req.accountability.user } }; + res.locals['payload'] = { data: { id: req.accountability.user } }; return next(); } @@ -125,9 +125,9 @@ router.get( schema: req.schema, }); - const items = await service.readOne(req.params.pk, req.sanitizedQuery); + const items = await service.readOne(req.params['pk'], req.sanitizedQuery); - res.locals.payload = { data: items || null }; + res.locals['payload'] = { data: items || null }; return next(); }), respond @@ -148,7 +148,7 @@ router.patch( const primaryKey = await service.updateOne(req.accountability.user, req.body); const item = await service.readOne(primaryKey, req.sanitizedQuery); - res.locals.payload = { data: item || null }; + res.locals['payload'] = { data: item || null }; return next(); }), respond @@ -195,7 +195,7 @@ router.patch( try { const result = await service.readMany(keys, req.sanitizedQuery); - res.locals.payload = { data: result }; + res.locals['payload'] = { data: result }; } catch (error: any) { if (error instanceof ForbiddenException) { return next(); @@ -217,11 +217,11 @@ router.patch( schema: req.schema, }); - const primaryKey = await service.updateOne(req.params.pk, req.body); + const primaryKey = await service.updateOne(req.params['pk'], req.body); try { const item = await service.readOne(primaryKey, req.sanitizedQuery); - res.locals.payload = { data: item || null }; + res.locals['payload'] = { data: item || null }; } catch (error: any) { if (error instanceof ForbiddenException) { return next(); @@ -266,7 +266,7 @@ router.delete( schema: req.schema, }); - await service.deleteOne(req.params.pk); + await service.deleteOne(req.params['pk']); return next(); }), @@ -339,7 +339,7 @@ router.post( const { url, secret } = await service.generateTFA(req.accountability.user); - res.locals.payload = { data: { secret, otpauth_url: url } }; + res.locals['payload'] = { data: { secret, otpauth_url: url } }; return next(); }), respond @@ -469,7 +469,7 @@ router.post( throw new InvalidCredentialsException(); } - if (!req.accountability.admin || !req.params.pk) { + if (!req.accountability.admin || !req.params['pk']) { throw new ForbiddenException(); } @@ -478,7 +478,7 @@ router.post( schema: req.schema, }); - await service.disableTFA(req.params.pk); + await service.disableTFA(req.params['pk']); return next(); }), respond diff --git a/api/src/controllers/utils.ts b/api/src/controllers/utils.ts index 2e8623a2a8..f5538300ca 100644 --- a/api/src/controllers/utils.ts +++ b/api/src/controllers/utils.ts @@ -23,10 +23,10 @@ router.get( asyncHandler(async (req, res) => { const { nanoid } = await import('nanoid'); - if (req.query && req.query.length && Number(req.query.length) > 500) + if (req.query && req.query['length'] && Number(req.query['length']) > 500) throw new InvalidQueryException(`"length" can't be more than 500 characters`); - const string = nanoid(req.query?.length ? Number(req.query.length) : 32); + const string = nanoid(req.query?.['length'] ? Number(req.query['length']) : 32); return res.json({ data: string }); }) @@ -91,7 +91,7 @@ router.post( accountability: req.accountability, schema: req.schema, }); - await service.revert(req.params.revision); + await service.revert(req.params['revision']); next(); }), respond @@ -124,7 +124,7 @@ router.post( busboy.on('file', async (_fieldname, fileStream, { mimeType }) => { try { - await service.import(req.params.collection, mimeType, fileStream); + await service.import(req.params['collection'], mimeType, fileStream); } catch (err: any) { return next(err); } @@ -158,7 +158,7 @@ router.post( const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability ?? null); // We're not awaiting this, as it's supposed to run async in the background - service.exportToFile(req.params.collection, sanitizedQuery, req.body.format, { + service.exportToFile(req.params['collection'], sanitizedQuery, req.body.format, { file: req.body.file, }); diff --git a/api/src/controllers/webhooks.ts b/api/src/controllers/webhooks.ts index 14af1f5ec2..ae2a23c1fa 100644 --- a/api/src/controllers/webhooks.ts +++ b/api/src/controllers/webhooks.ts @@ -33,10 +33,10 @@ router.post( try { if (Array.isArray(req.body)) { const items = await service.readMany(savedKeys, req.sanitizedQuery); - res.locals.payload = { data: items }; + res.locals['payload'] = { data: items }; } else { const item = await service.readOne(savedKeys[0], req.sanitizedQuery); - res.locals.payload = { data: item }; + res.locals['payload'] = { data: item }; } } catch (error: any) { if (error instanceof ForbiddenException) { @@ -64,7 +64,7 @@ const readHandler = asyncHandler(async (req, res, next) => { const records = await service.readByQuery(req.sanitizedQuery); const meta = await metaService.getMetaForQuery(req.collection, req.sanitizedQuery); - res.locals.payload = { data: records || null, meta }; + res.locals['payload'] = { data: records || null, meta }; return next(); }); @@ -79,9 +79,9 @@ router.get( schema: req.schema, }); - const record = await service.readOne(req.params.pk, req.sanitizedQuery); + const record = await service.readOne(req.params['pk'], req.sanitizedQuery); - res.locals.payload = { data: record || null }; + res.locals['payload'] = { data: record || null }; return next(); }), respond @@ -107,7 +107,7 @@ router.patch( try { const result = await service.readMany(keys, req.sanitizedQuery); - res.locals.payload = { data: result }; + res.locals['payload'] = { data: result }; } catch (error: any) { if (error instanceof ForbiddenException) { return next(); @@ -129,11 +129,11 @@ router.patch( schema: req.schema, }); - const primaryKey = await service.updateOne(req.params.pk, req.body); + const primaryKey = await service.updateOne(req.params['pk'], req.body); try { const item = await service.readOne(primaryKey, req.sanitizedQuery); - res.locals.payload = { data: item || null }; + res.locals['payload'] = { data: item || null }; } catch (error: any) { if (error instanceof ForbiddenException) { return next(); @@ -177,7 +177,7 @@ router.delete( schema: req.schema, }); - await service.deleteOne(req.params.pk); + await service.deleteOne(req.params['pk']); return next(); }), diff --git a/api/src/database/helpers/date/dialects/mssql.ts b/api/src/database/helpers/date/dialects/mssql.ts index 54d89b838a..2186e4bfc8 100644 --- a/api/src/database/helpers/date/dialects/mssql.ts +++ b/api/src/database/helpers/date/dialects/mssql.ts @@ -2,7 +2,7 @@ import { DateHelper } from '../types'; import { parseISO } from 'date-fns'; export class DateHelperMSSQL extends DateHelper { - writeTimestamp(date: string): Date { + override writeTimestamp(date: string): Date { const parsedDate = parseISO(date); return new Date(parsedDate.getTime() + parsedDate.getTimezoneOffset() * 60000); } diff --git a/api/src/database/helpers/date/dialects/mysql.ts b/api/src/database/helpers/date/dialects/mysql.ts index 72aa242f7a..a056a2e26b 100644 --- a/api/src/database/helpers/date/dialects/mysql.ts +++ b/api/src/database/helpers/date/dialects/mysql.ts @@ -2,12 +2,12 @@ import { DateHelper } from '../types'; import { parseISO } from 'date-fns'; export class DateHelperMySQL extends DateHelper { - readTimestampString(date: string): string { + override readTimestampString(date: string): string { const parsedDate = new Date(date); return new Date(parsedDate.getTime() - parsedDate.getTimezoneOffset() * 60000).toISOString(); } - writeTimestamp(date: string): Date { + override writeTimestamp(date: string): Date { const parsedDate = parseISO(date); return new Date(parsedDate.getTime() + parsedDate.getTimezoneOffset() * 60000); } diff --git a/api/src/database/helpers/date/dialects/oracle.ts b/api/src/database/helpers/date/dialects/oracle.ts index 4c35a6b87f..f786082074 100644 --- a/api/src/database/helpers/date/dialects/oracle.ts +++ b/api/src/database/helpers/date/dialects/oracle.ts @@ -1,7 +1,7 @@ import { DateHelper } from '../types'; export class DateHelperOracle extends DateHelper { - fieldFlagForField(fieldType: string): string { + override fieldFlagForField(fieldType: string): string { switch (fieldType) { case 'dateTime': return 'cast-datetime'; diff --git a/api/src/database/helpers/date/dialects/sqlite.ts b/api/src/database/helpers/date/dialects/sqlite.ts index 7323b4c2f8..d7e01e9a48 100644 --- a/api/src/database/helpers/date/dialects/sqlite.ts +++ b/api/src/database/helpers/date/dialects/sqlite.ts @@ -1,7 +1,7 @@ import { DateHelper } from '../types'; export class DateHelperSQLite extends DateHelper { - parse(date: string | Date): string { + override parse(date: string | Date): string { if (!date) { return date; } @@ -20,7 +20,7 @@ export class DateHelperSQLite extends DateHelper { return String(new Date(date).getTime()); } - fieldFlagForField(fieldType: string): string { + override fieldFlagForField(fieldType: string): string { switch (fieldType) { case 'timestamp': return 'cast-timestamp'; diff --git a/api/src/database/helpers/fn/types.ts b/api/src/database/helpers/fn/types.ts index 2e5da9ad93..0d82dcb861 100644 --- a/api/src/database/helpers/fn/types.ts +++ b/api/src/database/helpers/fn/types.ts @@ -10,7 +10,7 @@ export type FnHelperOptions = { }; export abstract class FnHelper extends DatabaseHelper { - constructor(protected knex: Knex, protected schema: SchemaOverview) { + constructor(knex: Knex, protected schema: SchemaOverview) { super(knex); this.schema = schema; } diff --git a/api/src/database/helpers/geometry/dialects/mssql.ts b/api/src/database/helpers/geometry/dialects/mssql.ts index 1d594443b7..f58eb01951 100644 --- a/api/src/database/helpers/geometry/dialects/mssql.ts +++ b/api/src/database/helpers/geometry/dialects/mssql.ts @@ -4,33 +4,40 @@ import type { GeoJSONGeometry } from 'wellknown'; import { GeometryHelper } from '../types'; export class GeometryHelperMSSQL extends GeometryHelper { - isTrue(expression: Knex.Raw) { + override isTrue(expression: Knex.Raw) { return expression.wrap(``, ` = 1`); } - isFalse(expression: Knex.Raw) { + + override isFalse(expression: Knex.Raw) { return expression.wrap(``, ` = 0`); } - createColumn(table: Knex.CreateTableBuilder, field: RawField | Field) { + + override createColumn(table: Knex.CreateTableBuilder, field: RawField | Field) { if (field.type.split('.')[1]) { field.meta!.special = [field.type]; } return table.specificType(field.field, 'geometry'); } - asText(table: string, column: string): Knex.Raw { + + override asText(table: string, column: string): Knex.Raw { return this.knex.raw('??.??.STAsText() as ??', [table, column, column]); } - fromText(text: string): Knex.Raw { + + override fromText(text: string): Knex.Raw { return this.knex.raw('geometry::STGeomFromText(?, 4326)', text); } - _intersects(key: string, geojson: GeoJSONGeometry): Knex.Raw { + + override _intersects(key: string, geojson: GeoJSONGeometry): Knex.Raw { const geometry = this.fromGeoJSON(geojson); return this.knex.raw('??.STIntersects(?)', [key, geometry]); } - _intersects_bbox(key: string, geojson: GeoJSONGeometry): Knex.Raw { + + override _intersects_bbox(key: string, geojson: GeoJSONGeometry): Knex.Raw { const geometry = this.fromGeoJSON(geojson); return this.knex.raw('??.STEnvelope().STIntersects(?.STEnvelope())', [key, geometry]); } - collect(table: string, column: string): Knex.Raw { + + override collect(table: string, column: string): Knex.Raw { return this.knex.raw('geometry::CollectionAggregate(??.??).STAsText()', [table, column]); } } diff --git a/api/src/database/helpers/geometry/dialects/mysql.ts b/api/src/database/helpers/geometry/dialects/mysql.ts index ecf036e89b..e288c7ffb3 100644 --- a/api/src/database/helpers/geometry/dialects/mysql.ts +++ b/api/src/database/helpers/geometry/dialects/mysql.ts @@ -2,15 +2,17 @@ import type { Knex } from 'knex'; import { GeometryHelper } from '../types'; export class GeometryHelperMySQL extends GeometryHelper { - collect(table: string, column: string): Knex.Raw { + override collect(table: string, column: string): Knex.Raw { return this.knex.raw( `concat('geometrycollection(', group_concat(? separator ', '), ')'`, this.asText(table, column) ); } - fromText(text: string): Knex.Raw { + + override fromText(text: string): Knex.Raw { return this.knex.raw('st_geomfromtext(?)', text); } + asGeoJSON(table: string, column: string): Knex.Raw { return this.knex.raw('st_asgeojson(??.??) as ??', [table, column, column]); } diff --git a/api/src/database/helpers/geometry/dialects/oracle.ts b/api/src/database/helpers/geometry/dialects/oracle.ts index 24fc124a29..77ca252e57 100644 --- a/api/src/database/helpers/geometry/dialects/oracle.ts +++ b/api/src/database/helpers/geometry/dialects/oracle.ts @@ -4,36 +4,44 @@ import type { GeoJSONGeometry } from 'wellknown'; import { GeometryHelper } from '../types'; export class GeometryHelperOracle extends GeometryHelper { - isTrue(expression: Knex.Raw) { + override isTrue(expression: Knex.Raw) { return expression.wrap(``, ` = 'TRUE'`); } - isFalse(expression: Knex.Raw) { + + override isFalse(expression: Knex.Raw) { return expression.wrap(``, ` = 'FALSE'`); } - createColumn(table: Knex.CreateTableBuilder, field: RawField | Field) { + + override createColumn(table: Knex.CreateTableBuilder, field: RawField | Field) { if (field.type.split('.')[1]) { field.meta!.special = [field.type]; } return table.specificType(field.field, 'sdo_geometry'); } - asText(table: string, column: string): Knex.Raw { + + override asText(table: string, column: string): Knex.Raw { return this.knex.raw('sdo_util.to_wktgeometry(??.??) as ??', [table, column, column]); } + asGeoJSON(table: string, column: string): Knex.Raw { return this.knex.raw('sdo_util.to_geojson(??.??) as ??', [table, column, column]); } - fromText(text: string): Knex.Raw { + + override fromText(text: string): Knex.Raw { return this.knex.raw('sdo_geometry(?, 4326)', text); } - _intersects(key: string, geojson: GeoJSONGeometry): Knex.Raw { + + override _intersects(key: string, geojson: GeoJSONGeometry): Knex.Raw { const geometry = this.fromGeoJSON(geojson); return this.knex.raw(`sdo_overlapbdyintersect(??, ?)`, [key, geometry]); } - _intersects_bbox(key: string, geojson: GeoJSONGeometry): Knex.Raw { + + override _intersects_bbox(key: string, geojson: GeoJSONGeometry): Knex.Raw { const geometry = this.fromGeoJSON(geojson); return this.knex.raw(`sdo_overlapbdyintersect(sdo_geom.sdo_mbr(??), sdo_geom.sdo_mbr(?))`, [key, geometry]); } - collect(table: string, column: string): Knex.Raw { + + override collect(table: string, column: string): Knex.Raw { return this.knex.raw(`concat('geometrycollection(', listagg(?, ', '), ')'`, this.asText(table, column)); } } diff --git a/api/src/database/helpers/geometry/dialects/postgres.ts b/api/src/database/helpers/geometry/dialects/postgres.ts index 5f61fdb1d6..f446e88e3b 100644 --- a/api/src/database/helpers/geometry/dialects/postgres.ts +++ b/api/src/database/helpers/geometry/dialects/postgres.ts @@ -4,18 +4,21 @@ import type { GeoJSONGeometry } from 'wellknown'; import { GeometryHelper } from '../types'; export class GeometryHelperPostgres extends GeometryHelper { - async supported() { + override async supported() { const res = await this.knex.select('oid').from('pg_proc').where({ proname: 'postgis_version' }); return res.length > 0; } - createColumn(table: Knex.CreateTableBuilder, field: RawField | Field) { + + override createColumn(table: Knex.CreateTableBuilder, field: RawField | Field) { const type = field.type.split('.')[1] ?? 'geometry'; return table.specificType(field.field, `geometry(${type}, 4326)`); } - _intersects_bbox(key: string, geojson: GeoJSONGeometry): Knex.Raw { + + override _intersects_bbox(key: string, geojson: GeoJSONGeometry): Knex.Raw { const geometry = this.fromGeoJSON(geojson); return this.knex.raw('?? && ?', [key, geometry]); } + asGeoJSON(table: string, column: string): Knex.Raw { return this.knex.raw('st_asgeojson(??.??) as ??', [table, column, column]); } diff --git a/api/src/database/helpers/geometry/dialects/redshift.ts b/api/src/database/helpers/geometry/dialects/redshift.ts index d69b90da6b..1c65bd56c4 100644 --- a/api/src/database/helpers/geometry/dialects/redshift.ts +++ b/api/src/database/helpers/geometry/dialects/redshift.ts @@ -3,12 +3,13 @@ import type { Knex } from 'knex'; import { GeometryHelper } from '../types'; export class GeometryHelperRedshift extends GeometryHelper { - createColumn(table: Knex.CreateTableBuilder, field: RawField | Field) { + override createColumn(table: Knex.CreateTableBuilder, field: RawField | Field) { if (field.type.split('.')[1]) { field.meta!.special = [field.type]; } return table.specificType(field.field, 'geometry'); } + asGeoJSON(table: string, column: string): Knex.Raw { return this.knex.raw('st_asgeojson(??.??) as ??', [table, column, column]); } diff --git a/api/src/database/helpers/geometry/dialects/sqlite.ts b/api/src/database/helpers/geometry/dialects/sqlite.ts index e427d4a25c..1a5cb97c06 100644 --- a/api/src/database/helpers/geometry/dialects/sqlite.ts +++ b/api/src/database/helpers/geometry/dialects/sqlite.ts @@ -2,10 +2,11 @@ import type { Knex } from 'knex'; import { GeometryHelper } from '../types'; export class GeometryHelperSQLite extends GeometryHelper { - async supported() { + override async supported() { const res = await this.knex.select('name').from('pragma_function_list').where({ name: 'spatialite_version' }); return res.length > 0; } + asGeoJSON(table: string, column: string): Knex.Raw { return this.knex.raw('asgeojson(??.??) as ??', [table, column, column]); } diff --git a/api/src/database/helpers/schema/dialects/cockroachdb.ts b/api/src/database/helpers/schema/dialects/cockroachdb.ts index 837ef13e88..b544a2a77c 100644 --- a/api/src/database/helpers/schema/dialects/cockroachdb.ts +++ b/api/src/database/helpers/schema/dialects/cockroachdb.ts @@ -2,7 +2,7 @@ import type { KNEX_TYPES } from '@directus/shared/constants'; import { Options, SchemaHelper } from '../types'; export class SchemaHelperCockroachDb extends SchemaHelper { - async changeToType( + override async changeToType( table: string, column: string, type: (typeof KNEX_TYPES)[number], @@ -11,7 +11,7 @@ export class SchemaHelperCockroachDb extends SchemaHelper { await this.changeToTypeByCopy(table, column, type, options); } - constraintName(existingName: string): string { + override constraintName(existingName: string): string { const suffix = '_replaced'; // CockroachDB does not allow for dropping/creating constraints with the same // name in a single transaction. reference issue #14873 diff --git a/api/src/database/helpers/schema/dialects/mssql.ts b/api/src/database/helpers/schema/dialects/mssql.ts index 0d3874659c..aab9f419e3 100644 --- a/api/src/database/helpers/schema/dialects/mssql.ts +++ b/api/src/database/helpers/schema/dialects/mssql.ts @@ -2,7 +2,7 @@ import type { Knex } from 'knex'; import { SchemaHelper } from '../types'; export class SchemaHelperMSSQL extends SchemaHelper { - applyLimit(rootQuery: Knex.QueryBuilder, limit: number): void { + override applyLimit(rootQuery: Knex.QueryBuilder, limit: number): void { // The ORDER BY clause is invalid in views, inline functions, derived tables, subqueries, // and common table expressions, unless TOP, OFFSET or FOR XML is also specified. if (limit === -1) { @@ -12,12 +12,12 @@ export class SchemaHelperMSSQL extends SchemaHelper { } } - applyOffset(rootQuery: Knex.QueryBuilder, offset: number): void { + override applyOffset(rootQuery: Knex.QueryBuilder, offset: number): void { rootQuery.offset(offset); rootQuery.orderBy(1); } - formatUUID(uuid: string): string { + override formatUUID(uuid: string): string { return uuid.toUpperCase(); } } diff --git a/api/src/database/helpers/schema/dialects/mysql.ts b/api/src/database/helpers/schema/dialects/mysql.ts index 7622a22439..4eeeacc819 100644 --- a/api/src/database/helpers/schema/dialects/mysql.ts +++ b/api/src/database/helpers/schema/dialects/mysql.ts @@ -3,7 +3,7 @@ import { getDatabaseVersion } from '../../../../database'; import { SchemaHelper } from '../types'; export class SchemaHelperMySQL extends SchemaHelper { - applyMultiRelationalSort( + override applyMultiRelationalSort( knex: Knex, dbQuery: Knex.QueryBuilder, table: string, diff --git a/api/src/database/helpers/schema/dialects/oracle.ts b/api/src/database/helpers/schema/dialects/oracle.ts index 1f07387577..ea07c2333b 100644 --- a/api/src/database/helpers/schema/dialects/oracle.ts +++ b/api/src/database/helpers/schema/dialects/oracle.ts @@ -3,7 +3,7 @@ import type { Field, Relation, Type } from '@directus/shared/types'; import { Options, SchemaHelper } from '../types'; export class SchemaHelperOracle extends SchemaHelper { - async changeToType( + override async changeToType( table: string, column: string, type: (typeof KNEX_TYPES)[number], @@ -12,11 +12,11 @@ export class SchemaHelperOracle extends SchemaHelper { await this.changeToTypeByCopy(table, column, type, options); } - castA2oPrimaryKey(): string { + override castA2oPrimaryKey(): string { return 'CAST(?? AS VARCHAR2(255))'; } - preRelationChange(relation: Partial): void { + override preRelationChange(relation: Partial): void { if (relation.collection === relation.related_collection) { // Constraints are not allowed on self referencing relationships // Setting NO ACTION throws - ORA-00905: missing keyword @@ -26,7 +26,7 @@ export class SchemaHelperOracle extends SchemaHelper { } } - processFieldType(field: Field): Type { + override processFieldType(field: Field): Type { if (field.type === 'integer') { if (field.schema?.numeric_precision === 20) { return 'bigInteger'; diff --git a/api/src/database/helpers/schema/dialects/sqlite.ts b/api/src/database/helpers/schema/dialects/sqlite.ts index 4bf994ef49..cbe1b6d5c9 100644 --- a/api/src/database/helpers/schema/dialects/sqlite.ts +++ b/api/src/database/helpers/schema/dialects/sqlite.ts @@ -1,7 +1,7 @@ import { SchemaHelper } from '../types'; export class SchemaHelperSQLite extends SchemaHelper { - async preColumnChange(): Promise { + override async preColumnChange(): Promise { const foreignCheckStatus = (await this.knex.raw('PRAGMA foreign_keys'))[0].foreign_keys === 1; if (foreignCheckStatus) { @@ -11,7 +11,7 @@ export class SchemaHelperSQLite extends SchemaHelper { return foreignCheckStatus; } - async postColumnChange(): Promise { + override async postColumnChange(): Promise { await this.knex.raw('PRAGMA foreign_keys = ON'); } } diff --git a/api/src/database/index.ts b/api/src/database/index.ts index 5d17bace0f..50872ef9aa 100644 --- a/api/src/database/index.ts +++ b/api/src/database/index.ts @@ -38,7 +38,7 @@ export default function getDatabase(): Knex { break; case 'oracledb': - if (!env.DB_CONNECT_STRING) { + if (!env['DB_CONNECT_STRING']) { requiredEnvVars.push('DB_HOST', 'DB_PORT', 'DB_DATABASE', 'DB_USER', 'DB_PASSWORD'); } else { requiredEnvVars.push('DB_USER', 'DB_PASSWORD', 'DB_CONNECT_STRING'); @@ -54,7 +54,7 @@ export default function getDatabase(): Knex { } break; case 'mssql': - if (!env.DB_TYPE || env.DB_TYPE === 'default') { + if (!env['DB_TYPE'] || env['DB_TYPE'] === 'default') { requiredEnvVars.push('DB_HOST', 'DB_PORT', 'DB_DATABASE', 'DB_USER', 'DB_PASSWORD'); } break; @@ -241,7 +241,7 @@ export async function validateMigrations(): Promise { try { let migrationFiles = await fse.readdir(path.join(__dirname, 'migrations')); - const customMigrationsPath = path.resolve(env.EXTENSIONS_PATH, 'migrations'); + const customMigrationsPath = path.resolve(env['EXTENSIONS_PATH'], 'migrations'); let customMigrationFiles = ((await fse.pathExists(customMigrationsPath)) && (await fse.readdir(customMigrationsPath))) || []; @@ -297,11 +297,11 @@ async function validateDatabaseCharset(database?: Knex): Promise { const tables = await database('information_schema.tables') .select({ name: 'TABLE_NAME', collation: 'TABLE_COLLATION' }) - .where({ TABLE_SCHEMA: env.DB_DATABASE }); + .where({ TABLE_SCHEMA: env['DB_DATABASE'] }); const columns = await database('information_schema.columns') .select({ table_name: 'TABLE_NAME', name: 'COLUMN_NAME', collation: 'COLLATION_NAME' }) - .where({ TABLE_SCHEMA: env.DB_DATABASE }) + .where({ TABLE_SCHEMA: env['DB_DATABASE'] }) .whereNot({ COLLATION_NAME: collation }); let inconsistencies = ''; diff --git a/api/src/database/migrations/20210805B-change-image-metadata-structure.ts b/api/src/database/migrations/20210805B-change-image-metadata-structure.ts index 078f9086d3..3cf6be7441 100644 --- a/api/src/database/migrations/20210805B-change-image-metadata-structure.ts +++ b/api/src/database/migrations/20210805B-change-image-metadata-structure.ts @@ -66,25 +66,25 @@ export async function down(knex: Knex): Promise { // Put all data under 'exif' and rename/move keys afterwards const newMetadata: { exif: Record; icc?: unknown; iptc?: unknown } = { exif: prevMetadata }; - if (newMetadata.exif.ifd0) { - newMetadata.exif.image = newMetadata.exif.ifd0; - delete newMetadata.exif.ifd0; + if (newMetadata.exif['ifd0']) { + newMetadata.exif['image'] = newMetadata.exif['ifd0']; + delete newMetadata.exif['ifd0']; } - if (newMetadata.exif.ifd1) { - newMetadata.exif.thumbnail = newMetadata.exif.ifd1; - delete newMetadata.exif.ifd1; + if (newMetadata.exif['ifd1']) { + newMetadata.exif['thumbnail'] = newMetadata.exif['ifd1']; + delete newMetadata.exif['ifd1']; } - if (newMetadata.exif.interop) { - newMetadata.exif.interoperability = newMetadata.exif.interop; - delete newMetadata.exif.interop; + if (newMetadata.exif['interop']) { + newMetadata.exif['interoperability'] = newMetadata.exif['interop']; + delete newMetadata.exif['interop']; } - if (newMetadata.exif.icc) { - newMetadata.icc = newMetadata.exif.icc; - delete newMetadata.exif.icc; + if (newMetadata.exif['icc']) { + newMetadata.icc = newMetadata.exif['icc']; + delete newMetadata.exif['icc']; } - if (newMetadata.exif.iptc) { - newMetadata.iptc = newMetadata.exif.iptc; - delete newMetadata.exif.iptc; + if (newMetadata.exif['iptc']) { + newMetadata.iptc = newMetadata.exif['iptc']; + delete newMetadata.exif['iptc']; } await knex('directus_files') diff --git a/api/src/database/migrations/run.ts b/api/src/database/migrations/run.ts index cfb090aa55..cb0c035a28 100644 --- a/api/src/database/migrations/run.ts +++ b/api/src/database/migrations/run.ts @@ -13,7 +13,7 @@ import formatTitle from '@directus/format-title'; export default async function run(database: Knex, direction: 'up' | 'down' | 'latest', log = true): Promise { let migrationFiles = await fse.readdir(__dirname); - const customMigrationsPath = path.resolve(env.EXTENSIONS_PATH, 'migrations'); + const customMigrationsPath = path.resolve(env['EXTENSIONS_PATH'], 'migrations'); let customMigrationFiles = ((await fse.pathExists(customMigrationsPath)) && (await fse.readdir(customMigrationsPath))) || []; diff --git a/api/src/database/run-ast.ts b/api/src/database/run-ast.ts index 7870de6412..30f055bb86 100644 --- a/api/src/database/run-ast.ts +++ b/api/src/database/run-ast.ts @@ -84,7 +84,7 @@ export default async function runAST( const payloadService = new PayloadService(collection, { knex, schema }); let items: null | Item | Item[] = await payloadService.processValues('read', rawItems); - if (!items || items.length === 0) return items; + if (!items || (Array.isArray(items) && items.length === 0)) return items; // Apply the `_in` filters to the nested collection batches const nestedNodes = applyParentFilters(schema, nestedCollectionNodes, items); @@ -100,8 +100,8 @@ export default async function runAST( while (hasMore) { const node = merge({}, nestedNode, { query: { - limit: env.RELATIONAL_BATCH_SIZE, - offset: batchCount * env.RELATIONAL_BATCH_SIZE, + limit: env['RELATIONAL_BATCH_SIZE'], + offset: batchCount * env['RELATIONAL_BATCH_SIZE'], page: null, }, }); @@ -112,7 +112,7 @@ export default async function runAST( items = mergeWithParentItems(schema, nestedItems, items, nestedNode); } - if (!nestedItems || nestedItems.length < env.RELATIONAL_BATCH_SIZE) { + if (!nestedItems || nestedItems.length < env['RELATIONAL_BATCH_SIZE']) { hasMore = false; } diff --git a/api/src/database/system-data/collections/index.ts b/api/src/database/system-data/collections/index.ts index 168ce41942..2ef25f92d0 100644 --- a/api/src/database/system-data/collections/index.ts +++ b/api/src/database/system-data/collections/index.ts @@ -4,6 +4,6 @@ import { requireYAML } from '../../../utils/require-yaml'; const systemData = requireYAML(require.resolve('./collections.yaml')); -export const systemCollectionRows: CollectionMeta[] = systemData.data.map((row: Record) => { - return merge({ system: true }, systemData.defaults, row); +export const systemCollectionRows: CollectionMeta[] = systemData['data'].map((row: Record) => { + return merge({ system: true }, systemData['defaults'], row); }); diff --git a/api/src/database/system-data/fields/index.ts b/api/src/database/system-data/fields/index.ts index 388e6f1af9..36f50f45e5 100644 --- a/api/src/database/system-data/fields/index.ts +++ b/api/src/database/system-data/fields/index.ts @@ -18,16 +18,16 @@ for (const filepath of fieldData) { const systemFields = requireYAML(path.resolve(__dirname, filepath)); - (systemFields.fields as FieldMeta[]).forEach((field, index) => { + (systemFields['fields'] as FieldMeta[]).forEach((field, index) => { const systemField = merge({ system: true }, defaults, field, { - collection: systemFields.table, + collection: systemFields['table'], sort: index + 1, }); // Dynamically populate auth providers field if (systemField.collection === 'directus_users' && systemField.field === 'provider') { getAuthProviders().forEach(({ name }) => { - systemField.options?.choices?.push({ + systemField.options?.['choices']?.push({ text: formatTitle(name), value: name, }); diff --git a/api/src/env.test.ts b/api/src/env.test.ts index 9b381eaf47..51f8e45052 100644 --- a/api/src/env.test.ts +++ b/api/src/env.test.ts @@ -14,26 +14,26 @@ describe('env processed values', async () => { const env = ((await vi.importActual('../src/env')) as { default: Record }).default; test('Number value should be a number', () => { - expect(env.NUMBER).toStrictEqual(1234); + expect(env['NUMBER']).toStrictEqual(1234); }); test('Number value casted as string should be a string', () => { - expect(env.NUMBER_CAST_AS_STRING).toStrictEqual('1234'); + expect(env['NUMBER_CAST_AS_STRING']).toStrictEqual('1234'); }); test('Value casted as regex', () => { - expect(env.REGEX).toBeInstanceOf(RegExp); + expect(env['REGEX']).toBeInstanceOf(RegExp); }); test('CSV value should be an array', () => { - expect(env.CSV).toStrictEqual(['one', 'two', 'three', 'four']); + expect(env['CSV']).toStrictEqual(['one', 'two', 'three', 'four']); }); test('CSV value casted as string should be a string', () => { - expect(env.CSV_CAST_AS_STRING).toStrictEqual('one,two,three,four'); + expect(env['CSV_CAST_AS_STRING']).toStrictEqual('one,two,three,four'); }); test('Multiple type cast', () => { - expect(env.MULTIPLE).toStrictEqual(['https://example.com', /\.example2\.com$/]); + expect(env['MULTIPLE']).toStrictEqual(['https://example.com', /\.example2\.com$/]); }); }); diff --git a/api/src/env.ts b/api/src/env.ts index 143a51867c..6c8be98e1d 100644 --- a/api/src/env.ts +++ b/api/src/env.ts @@ -343,7 +343,7 @@ export function refreshEnv(): void { } function processConfiguration() { - const configPath = path.resolve(process.env.CONFIG_PATH || defaults.CONFIG_PATH); + const configPath = path.resolve(process.env['CONFIG_PATH'] || defaults['CONFIG_PATH']); if (fs.existsSync(configPath) === false) return {}; diff --git a/api/src/extensions.ts b/api/src/extensions.ts index 58cb174f5f..9a5df06547 100644 --- a/api/src/extensions.ts +++ b/api/src/extensions.ts @@ -84,7 +84,7 @@ type Options = { const defaultOptions: Options = { schedule: true, - watch: env.EXTENSIONS_AUTO_RELOAD && env.NODE_ENV !== 'development', + watch: env['EXTENSIONS_AUTO_RELOAD'] && env['NODE_ENV'] !== 'development', }; class ExtensionManager { @@ -237,7 +237,7 @@ class ExtensionManager { private async load(): Promise { try { - await ensureExtensionDirs(env.EXTENSIONS_PATH, NESTED_EXTENSION_TYPES); + await ensureExtensionDirs(env['EXTENSIONS_PATH'], NESTED_EXTENSION_TYPES); this.extensions = await this.getExtensions(); } catch (err: any) { @@ -250,7 +250,7 @@ class ExtensionManager { await this.registerOperations(); await this.registerBundles(); - if (env.SERVE_APP) { + if (env['SERVE_APP']) { this.appExtensions = await this.generateExtensionBundle(); } @@ -262,7 +262,7 @@ class ExtensionManager { this.apiEmitter.offAll(); - if (env.SERVE_APP) { + if (env['SERVE_APP']) { this.appExtensions = null; } @@ -274,7 +274,7 @@ class ExtensionManager { logger.info('Watching extensions for changes...'); const localExtensionPaths = NESTED_EXTENSION_TYPES.flatMap((type) => { - const typeDir = path.posix.join(pathToRelativeUrl(env.EXTENSIONS_PATH), pluralize(type)); + const typeDir = path.posix.join(pathToRelativeUrl(env['EXTENSIONS_PATH']), pluralize(type)); if (isIn(type, HYBRID_EXTENSION_TYPES)) { return [path.posix.join(typeDir, '*', 'app.js'), path.posix.join(typeDir, '*', 'api.js')]; @@ -323,12 +323,12 @@ class ExtensionManager { } private async getExtensions(): Promise { - const packageExtensions = await getPackageExtensions(env.PACKAGE_FILE_LOCATION); - const localPackageExtensions = await resolvePackageExtensions(env.EXTENSIONS_PATH); - const localExtensions = await getLocalExtensions(env.EXTENSIONS_PATH); + const packageExtensions = await getPackageExtensions(env['PACKAGE_FILE_LOCATION']); + const localPackageExtensions = await resolvePackageExtensions(env['EXTENSIONS_PATH']); + const localExtensions = await getLocalExtensions(env['EXTENSIONS_PATH']); return [...packageExtensions, ...localPackageExtensions, ...localExtensions].filter( - (extension) => env.SERVE_APP || APP_EXTENSION_TYPES.includes(extension.type as any) === false + (extension) => env['SERVE_APP'] || APP_EXTENSION_TYPES.includes(extension.type as any) === false ); } @@ -370,7 +370,7 @@ class ExtensionManager { const depName = appDir.find((file) => depRegex.test(file)); if (depName) { - const depUrl = new Url(env.PUBLIC_URL).addPath('admin', 'assets', depName); + const depUrl = new Url(env['PUBLIC_URL']).addPath('admin', 'assets', depName); depsMapping[dep] = depUrl.toString({ rootRelative: true }); } else { diff --git a/api/src/flows.ts b/api/src/flows.ts index 2b8dfbe6b4..4f976e6b39 100644 --- a/api/src/flows.ts +++ b/api/src/flows.ts @@ -76,7 +76,7 @@ class FlowManager { const messenger = getMessenger(); messenger.subscribe('flows', (event) => { - if (event.type === 'reload') { + if (event['type'] === 'reload') { this.reloadQueue.enqueue(async () => { if (this.isLoaded) { await this.unload(); @@ -146,13 +146,13 @@ class FlowManager { if (flow.trigger === 'event') { let events: string[] = []; - if (flow.options?.scope) { - events = toArray(flow.options.scope) + if (flow.options?.['scope']) { + events = toArray(flow.options['scope']) .map((scope: string) => { if (['items.create', 'items.update', 'items.delete'].includes(scope)) { - if (!flow.options?.collections) return []; + if (!flow.options?.['collections']) return []; - return toArray(flow.options.collections).map((collection: string) => { + return toArray(flow.options['collections']).map((collection: string) => { if (collection.startsWith('directus_')) { const action = scope.split('.')[1]; return collection.substring(9) + '.' + action; @@ -167,15 +167,15 @@ class FlowManager { .flat(); } - if (flow.options.type === 'filter') { + if (flow.options['type'] === 'filter') { const handler: FilterHandler = (payload, meta, context) => this.executeFlow( flow, { payload, ...meta }, { - accountability: context.accountability, - database: context.database, - getSchema: context.schema ? () => context.schema : getSchema, + accountability: context['accountability'], + database: context['database'], + getSchema: context['schema'] ? () => context['schema'] : getSchema, } ); @@ -184,12 +184,12 @@ class FlowManager { id: flow.id, events: events.map((event) => ({ type: 'filter', name: event, handler })), }); - } else if (flow.options.type === 'action') { + } else if (flow.options['type'] === 'action') { const handler: ActionHandler = (meta, context) => this.executeFlow(flow, meta, { - accountability: context.accountability, + accountability: context['accountability'], database: getDatabase(), - getSchema: context.schema ? () => context.schema : getSchema, + getSchema: context['schema'] ? () => context['schema'] : getSchema, }); events.forEach((event) => emitter.onAction(event, handler)); @@ -199,8 +199,8 @@ class FlowManager { }); } } else if (flow.trigger === 'schedule') { - if (validate(flow.options.cron)) { - const task = schedule(flow.options.cron, async () => { + if (validate(flow.options['cron'])) { + const task = schedule(flow.options['cron'], async () => { try { await this.executeFlow(flow); } catch (error: any) { @@ -210,7 +210,7 @@ class FlowManager { this.triggerHandlers.push({ id: flow.id, events: [{ type: flow.trigger, task }] }); } else { - logger.warn(`Couldn't register cron trigger. Provided cron is invalid: ${flow.options.cron}`); + logger.warn(`Couldn't register cron trigger. Provided cron is invalid: ${flow.options['cron']}`); } } else if (flow.trigger === 'operation') { const handler = (data: unknown, context: Record) => this.executeFlow(flow, data, context); @@ -218,23 +218,24 @@ class FlowManager { this.operationFlowHandlers[flow.id] = handler; } else if (flow.trigger === 'webhook') { const handler = (data: unknown, context: Record) => { - if (flow.options.async) { + if (flow.options['async']) { this.executeFlow(flow, data, context); + return undefined; } else { return this.executeFlow(flow, data, context); } }; - const method = flow.options?.method ?? 'GET'; + const method = flow.options?.['method'] ?? 'GET'; // Default return to $last for webhooks - flow.options.return = flow.options.return ?? '$last'; + flow.options['return'] = flow.options['return'] ?? '$last'; this.webhookFlowHandlers[`${method}-${flow.id}`] = handler; } else if (flow.trigger === 'manual') { const handler = (data: unknown, context: Record) => { - const enabledCollections = flow.options?.collections ?? []; - const targetCollection = (data as Record)?.body.collection; + const enabledCollections = flow.options?.['collections'] ?? []; + const targetCollection = (data as Record)?.['body'].collection; if (!targetCollection) { logger.warn(`Manual trigger requires "collection" to be specified in the payload`); @@ -251,15 +252,16 @@ class FlowManager { throw new exceptions.ForbiddenException(); } - if (flow.options.async) { + if (flow.options['async']) { this.executeFlow(flow, data, context); + return undefined; } else { return this.executeFlow(flow, data, context); } }; // Default return to $last for manual - flow.options.return = '$last'; + flow.options['return'] = '$last'; this.webhookFlowHandlers[`POST-${flow.id}`] = handler; } @@ -293,14 +295,14 @@ class FlowManager { } private async executeFlow(flow: Flow, data: unknown = null, context: Record = {}): Promise { - const database = (context.database as Knex) ?? getDatabase(); - const schema = (context.schema as SchemaOverview) ?? (await getSchema({ database })); + const database = (context['database'] as Knex) ?? getDatabase(); + const schema = (context['schema'] as SchemaOverview) ?? (await getSchema({ database })); const keyedData: Record = { [TRIGGER_KEY]: data, [LAST_KEY]: data, - [ACCOUNTABILITY_KEY]: context?.accountability ?? null, - [ENV_KEY]: pick(env, env.FLOWS_ENV_ALLOW_LIST ? toArray(env.FLOWS_ENV_ALLOW_LIST) : []), + [ACCOUNTABILITY_KEY]: context?.['accountability'] ?? null, + [ENV_KEY]: pick(env, env['FLOWS_ENV_ALLOW_LIST'] ? toArray(env['FLOWS_ENV_ALLOW_LIST']) : []), }; let nextOperation = flow.operation; @@ -330,7 +332,7 @@ class FlowManager { schema: schema, }); - const accountability = context?.accountability as Accountability | undefined; + const accountability = context?.['accountability'] as Accountability | undefined; const activity = await activityService.createOne({ action: Action.RUN, @@ -360,14 +362,14 @@ class FlowManager { } } - if (flow.trigger === 'event' && flow.options.type === 'filter' && lastOperationStatus === 'reject') { + if (flow.trigger === 'event' && flow.options['type'] === 'filter' && lastOperationStatus === 'reject') { throw keyedData[LAST_KEY]; } - if (flow.options.return === '$all') { + if (flow.options['return'] === '$all') { return keyedData; - } else if (flow.options.return) { - return get(keyedData, flow.options.return); + } else if (flow.options['return']) { + return get(keyedData, flow.options['return']); } return undefined; diff --git a/api/src/logger.ts b/api/src/logger.ts index cf10aa593c..5f646b41a3 100644 --- a/api/src/logger.ts +++ b/api/src/logger.ts @@ -9,21 +9,21 @@ import { getConfigFromEnv } from './utils/get-config-from-env'; import { redactHeaderCookie } from './utils/redact-header-cookies'; const pinoOptions: LoggerOptions = { - level: env.LOG_LEVEL || 'info', + level: env['LOG_LEVEL'] || 'info', redact: { - paths: ['req.headers.authorization', `req.cookies.${env.REFRESH_TOKEN_COOKIE_NAME}`], + paths: ['req.headers.authorization', `req.cookies.${env['REFRESH_TOKEN_COOKIE_NAME']}`], censor: '--redact--', }, }; const httpLoggerOptions: LoggerOptions = { - level: env.LOG_LEVEL || 'info', + level: env['LOG_LEVEL'] || 'info', redact: { - paths: ['req.headers.authorization', `req.cookies.${env.REFRESH_TOKEN_COOKIE_NAME}`], + paths: ['req.headers.authorization', `req.cookies.${env['REFRESH_TOKEN_COOKIE_NAME']}`], censor: '--redact--', }, }; -if (env.LOG_STYLE !== 'raw') { +if (env['LOG_STYLE'] !== 'raw') { pinoOptions.transport = { target: 'pino-pretty', options: { @@ -48,10 +48,10 @@ if (env.LOG_STYLE !== 'raw') { const loggerEnvConfig = getConfigFromEnv('LOGGER_', 'LOGGER_HTTP'); // Expose custom log levels into formatter function -if (loggerEnvConfig.levels) { +if (loggerEnvConfig['levels']) { const customLogLevels: { [key: string]: string } = {}; - for (const el of toArray(loggerEnvConfig.levels)) { + for (const el of toArray(loggerEnvConfig['levels'])) { const key_val = el.split(':'); customLogLevels[key_val[0].trim()] = key_val[1].trim(); } @@ -73,7 +73,7 @@ if (loggerEnvConfig.levels) { }, }; - delete loggerEnvConfig.levels; + delete loggerEnvConfig['levels']; } const logger = pino(merge(pinoOptions, loggerEnvConfig)); @@ -87,10 +87,10 @@ export const expressLogger = pinoHTTP({ req(request: Request) { const output = stdSerializers.req(request); output.url = redactQuery(output.url); - if (output.headers?.cookie) { - output.headers.cookie = redactHeaderCookie(output.headers.cookie, [ + if (output.headers?.['cookie']) { + output.headers['cookie'] = redactHeaderCookie(output.headers['cookie'], [ 'access_token', - `${env.REFRESH_TOKEN_COOKIE_NAME}`, + `${env['REFRESH_TOKEN_COOKIE_NAME']}`, ]); } return output; @@ -99,7 +99,7 @@ export const expressLogger = pinoHTTP({ if (response.headers?.['set-cookie']) { response.headers['set-cookie'] = redactHeaderCookie(response.headers['set-cookie'], [ 'access_token', - `${env.REFRESH_TOKEN_COOKIE_NAME}`, + `${env['REFRESH_TOKEN_COOKIE_NAME']}`, ]); } return response; diff --git a/api/src/mailer.ts b/api/src/mailer.ts index db480cc5ed..7559606fe8 100644 --- a/api/src/mailer.ts +++ b/api/src/mailer.ts @@ -8,13 +8,13 @@ let transporter: Transporter; export default function getMailer(): Transporter { if (transporter) return transporter; - const transportName = env.EMAIL_TRANSPORT.toLowerCase(); + const transportName = env['EMAIL_TRANSPORT'].toLowerCase(); if (transportName === 'sendmail') { transporter = nodemailer.createTransport({ sendmail: true, - newline: env.EMAIL_SENDMAIL_NEW_LINE || 'unix', - path: env.EMAIL_SENDMAIL_PATH || '/usr/sbin/sendmail', + newline: env['EMAIL_SENDMAIL_NEW_LINE'] || 'unix', + path: env['EMAIL_SENDMAIL_PATH'] || '/usr/sbin/sendmail', }); } else if (transportName === 'ses') { const aws = require('@aws-sdk/client-ses'); @@ -29,22 +29,22 @@ export default function getMailer(): Transporter { } else if (transportName === 'smtp') { let auth: boolean | { user?: string; pass?: string } = false; - if (env.EMAIL_SMTP_USER || env.EMAIL_SMTP_PASSWORD) { + if (env['EMAIL_SMTP_USER'] || env['EMAIL_SMTP_PASSWORD']) { auth = { - user: env.EMAIL_SMTP_USER, - pass: env.EMAIL_SMTP_PASSWORD, + user: env['EMAIL_SMTP_USER'], + pass: env['EMAIL_SMTP_PASSWORD'], }; } const tls: Record = getConfigFromEnv('EMAIL_SMTP_TLS_'); transporter = nodemailer.createTransport({ - name: env.EMAIL_SMTP_NAME, - pool: env.EMAIL_SMTP_POOL, - host: env.EMAIL_SMTP_HOST, - port: env.EMAIL_SMTP_PORT, - secure: env.EMAIL_SMTP_SECURE, - ignoreTLS: env.EMAIL_SMTP_IGNORE_TLS, + name: env['EMAIL_SMTP_NAME'], + pool: env['EMAIL_SMTP_POOL'], + host: env['EMAIL_SMTP_HOST'], + port: env['EMAIL_SMTP_PORT'], + secure: env['EMAIL_SMTP_SECURE'], + ignoreTLS: env['EMAIL_SMTP_IGNORE_TLS'], auth, tls, } as Record); @@ -53,17 +53,17 @@ export default function getMailer(): Transporter { transporter = nodemailer.createTransport( mg({ auth: { - api_key: env.EMAIL_MAILGUN_API_KEY, - domain: env.EMAIL_MAILGUN_DOMAIN, + api_key: env['EMAIL_MAILGUN_API_KEY'], + domain: env['EMAIL_MAILGUN_DOMAIN'], }, - host: env.EMAIL_MAILGUN_HOST || 'api.mailgun.net', + host: env['EMAIL_MAILGUN_HOST'] || 'api.mailgun.net', }) as any ); } else if (transportName === 'sendgrid') { const sg = require('nodemailer-sendgrid'); transporter = nodemailer.createTransport( sg({ - apiKey: env.EMAIL_SENDGRID_API_KEY, + apiKey: env['EMAIL_SENDGRID_API_KEY'], }) as any ); } else { diff --git a/api/src/messenger.ts b/api/src/messenger.ts index 0a984d1368..9cb09d5b4f 100644 --- a/api/src/messenger.ts +++ b/api/src/messenger.ts @@ -40,9 +40,9 @@ export class MessengerRedis implements Messenger { constructor() { const config = getConfigFromEnv('MESSENGER_REDIS'); - this.pub = new IORedis(env.MESSENGER_REDIS ?? config); - this.sub = new IORedis(env.MESSENGER_REDIS ?? config); - this.namespace = env.MESSENGER_NAMESPACE ?? 'directus'; + this.pub = new IORedis(env['MESSENGER_REDIS'] ?? config); + this.sub = new IORedis(env['MESSENGER_REDIS'] ?? config); + this.namespace = env['MESSENGER_NAMESPACE'] ?? 'directus'; } publish(channel: string, payload: Record) { @@ -71,7 +71,7 @@ let messenger: Messenger; export function getMessenger() { if (messenger) return messenger; - if (env.MESSENGER_STORE === 'redis') { + if (env['MESSENGER_STORE'] === 'redis') { messenger = new MessengerRedis(); } else { messenger = new MessengerMemory(); diff --git a/api/src/middleware/authenticate.test.ts b/api/src/middleware/authenticate.test.ts index 643a6a2870..ed8e5c4f1c 100644 --- a/api/src/middleware/authenticate.test.ts +++ b/api/src/middleware/authenticate.test.ts @@ -96,7 +96,7 @@ test('Sets accountability to payload contents if valid token is passed', async ( share, share_scope: shareScope, }, - env.SECRET, + env['SECRET'], { issuer: 'directus' } ); @@ -145,7 +145,7 @@ test('Sets accountability to payload contents if valid token is passed', async ( share, share_scope: shareScope, }, - env.SECRET, + env['SECRET'], { issuer: 'directus' } ); diff --git a/api/src/middleware/authenticate.ts b/api/src/middleware/authenticate.ts index b8beed79fc..4d3e39f30d 100644 --- a/api/src/middleware/authenticate.ts +++ b/api/src/middleware/authenticate.ts @@ -52,7 +52,7 @@ export const handler = async (req: Request, res: Response, next: NextFunction) = if (req.token) { if (isDirectusJWT(req.token)) { - const payload = verifyAccessJWT(req.token, env.SECRET); + const payload = verifyAccessJWT(req.token, env['SECRET']); req.accountability.role = payload.role; req.accountability.admin = payload.admin_access === true || payload.admin_access == 1; diff --git a/api/src/middleware/cache.ts b/api/src/middleware/cache.ts index 8f78e43b5e..2cdf86a508 100644 --- a/api/src/middleware/cache.ts +++ b/api/src/middleware/cache.ts @@ -11,11 +11,11 @@ const checkCacheMiddleware: RequestHandler = asyncHandler(async (req, res, next) const { cache } = getCache(); if (req.method.toLowerCase() !== 'get' && req.originalUrl?.startsWith('/graphql') === false) return next(); - if (env.CACHE_ENABLED !== true) return next(); + if (env['CACHE_ENABLED'] !== true) return next(); if (!cache) return next(); if (shouldSkipCache(req)) { - if (env.CACHE_STATUS_HEADER) res.setHeader(`${env.CACHE_STATUS_HEADER}`, 'MISS'); + if (env['CACHE_STATUS_HEADER']) res.setHeader(`${env['CACHE_STATUS_HEADER']}`, 'MISS'); return next(); } @@ -27,7 +27,7 @@ const checkCacheMiddleware: RequestHandler = asyncHandler(async (req, res, next) cachedData = await getCacheValue(cache, key); } catch (err: any) { logger.warn(err, `[cache] Couldn't read key ${key}. ${err.message}`); - if (env.CACHE_STATUS_HEADER) res.setHeader(`${env.CACHE_STATUS_HEADER}`, 'MISS'); + if (env['CACHE_STATUS_HEADER']) res.setHeader(`${env['CACHE_STATUS_HEADER']}`, 'MISS'); return next(); } @@ -38,7 +38,7 @@ const checkCacheMiddleware: RequestHandler = asyncHandler(async (req, res, next) cacheExpiryDate = (await getCacheValue(cache, `${key}__expires_at`))?.exp; } catch (err: any) { logger.warn(err, `[cache] Couldn't read key ${`${key}__expires_at`}. ${err.message}`); - if (env.CACHE_STATUS_HEADER) res.setHeader(`${env.CACHE_STATUS_HEADER}`, 'MISS'); + if (env['CACHE_STATUS_HEADER']) res.setHeader(`${env['CACHE_STATUS_HEADER']}`, 'MISS'); return next(); } @@ -46,11 +46,11 @@ const checkCacheMiddleware: RequestHandler = asyncHandler(async (req, res, next) res.setHeader('Cache-Control', getCacheControlHeader(req, cacheTTL, true, true)); res.setHeader('Vary', 'Origin, Cache-Control'); - if (env.CACHE_STATUS_HEADER) res.setHeader(`${env.CACHE_STATUS_HEADER}`, 'HIT'); + if (env['CACHE_STATUS_HEADER']) res.setHeader(`${env['CACHE_STATUS_HEADER']}`, 'HIT'); return res.json(cachedData); } else { - if (env.CACHE_STATUS_HEADER) res.setHeader(`${env.CACHE_STATUS_HEADER}`, 'MISS'); + if (env['CACHE_STATUS_HEADER']) res.setHeader(`${env['CACHE_STATUS_HEADER']}`, 'MISS'); return next(); } }); diff --git a/api/src/middleware/collection-exists.ts b/api/src/middleware/collection-exists.ts index 5d68edf2a4..dc9afddbc8 100644 --- a/api/src/middleware/collection-exists.ts +++ b/api/src/middleware/collection-exists.ts @@ -8,13 +8,13 @@ import { ForbiddenException } from '../exceptions'; import asyncHandler from '../utils/async-handler'; const collectionExists: RequestHandler = asyncHandler(async (req, res, next) => { - if (!req.params.collection) return next(); + if (!req.params['collection']) return next(); - if (req.params.collection in req.schema.collections === false) { + if (req.params['collection'] in req.schema.collections === false) { throw new ForbiddenException(); } - req.collection = req.params.collection; + req.collection = req.params['collection']; if (req.collection.startsWith('directus_')) { const systemRow = systemCollectionRows.find((collection) => { diff --git a/api/src/middleware/cors.ts b/api/src/middleware/cors.ts index e3d9040d9a..2965e13318 100644 --- a/api/src/middleware/cors.ts +++ b/api/src/middleware/cors.ts @@ -4,14 +4,14 @@ import env from '../env'; let corsMiddleware: RequestHandler = (req, res, next) => next(); -if (env.CORS_ENABLED === true) { +if (env['CORS_ENABLED'] === true) { corsMiddleware = cors({ - origin: env.CORS_ORIGIN || true, - methods: env.CORS_METHODS || 'GET,POST,PATCH,DELETE', - allowedHeaders: env.CORS_ALLOWED_HEADERS, - exposedHeaders: env.CORS_EXPOSED_HEADERS, - credentials: env.CORS_CREDENTIALS || undefined, - maxAge: env.CORS_MAX_AGE || undefined, + origin: env['CORS_ORIGIN'] || true, + methods: env['CORS_METHODS'] || 'GET,POST,PATCH,DELETE', + allowedHeaders: env['CORS_ALLOWED_HEADERS'], + exposedHeaders: env['CORS_EXPOSED_HEADERS'], + credentials: env['CORS_CREDENTIALS'] || undefined, + maxAge: env['CORS_MAX_AGE'] || undefined, }); } diff --git a/api/src/middleware/error-handler.ts b/api/src/middleware/error-handler.ts index d12e54411d..ad5d63691e 100644 --- a/api/src/middleware/error-handler.ts +++ b/api/src/middleware/error-handler.ts @@ -33,7 +33,7 @@ const errorHandler: ErrorRequestHandler = (err, req, res, _next) => { } for (const err of errors) { - if (env.NODE_ENV === 'development') { + if (env['NODE_ENV'] === 'development') { err.extensions = { ...(err.extensions || {}), stack: err.stack, @@ -54,7 +54,7 @@ const errorHandler: ErrorRequestHandler = (err, req, res, _next) => { }); if (err instanceof MethodNotAllowedException) { - res.header('Allow', err.extensions.allow.join(', ')); + res.header('Allow', err.extensions['allow'].join(', ')); } } else { logger.error(err); diff --git a/api/src/middleware/extract-token.ts b/api/src/middleware/extract-token.ts index 7df8b79179..a730d1b482 100644 --- a/api/src/middleware/extract-token.ts +++ b/api/src/middleware/extract-token.ts @@ -12,8 +12,8 @@ import type { RequestHandler } from 'express'; const extractToken: RequestHandler = (req, res, next) => { let token: string | null = null; - if (req.query && req.query.access_token) { - token = req.query.access_token as string; + if (req.query && req.query['access_token']) { + token = req.query['access_token'] as string; } if (req.headers && req.headers.authorization) { diff --git a/api/src/middleware/graphql.ts b/api/src/middleware/graphql.ts index ba91e4a954..64209977ed 100644 --- a/api/src/middleware/graphql.ts +++ b/api/src/middleware/graphql.ts @@ -16,11 +16,11 @@ export const parseGraphQL: RequestHandler = asyncHandler(async (req, res, next) let document: DocumentNode; if (req.method === 'GET') { - query = (req.query.query as string | undefined) || null; + query = (req.query['query'] as string | undefined) || null; - if (req.query.variables) { + if (req.query['variables']) { try { - variables = parseJSON(req.query.variables as string); + variables = parseJSON(req.query['variables'] as string); } catch { throw new InvalidQueryException(`Variables are invalid JSON.`); } @@ -28,7 +28,7 @@ export const parseGraphQL: RequestHandler = asyncHandler(async (req, res, next) variables = {}; } - operationName = (req.query.operationName as string | undefined) || null; + operationName = (req.query['operationName'] as string | undefined) || null; } else { query = req.body.query || null; variables = req.body.variables || null; @@ -58,10 +58,16 @@ export const parseGraphQL: RequestHandler = asyncHandler(async (req, res, next) // Prevent caching responses when mutations are made if (operationAST?.operation === 'mutation') { - res.locals.cache = false; + res.locals['cache'] = false; } - res.locals.graphqlParams = { document, query, variables, operationName, contextValue: { req, res } } as GraphQLParams; + res.locals['graphqlParams'] = { + document, + query, + variables, + operationName, + contextValue: { req, res }, + } as GraphQLParams; return next(); }); diff --git a/api/src/middleware/rate-limiter-global.ts b/api/src/middleware/rate-limiter-global.ts index 782e8fe5d2..a36840a7b3 100644 --- a/api/src/middleware/rate-limiter-global.ts +++ b/api/src/middleware/rate-limiter-global.ts @@ -13,7 +13,7 @@ const RATE_LIMITER_GLOBAL_KEY = 'global-rate-limit'; let checkRateLimit: RequestHandler = (_req, _res, next) => next(); export let rateLimiterGlobal: RateLimiterRedis | RateLimiterMemcache | RateLimiterMemory; -if (env.RATE_LIMITER_GLOBAL_ENABLED === true) { +if (env['RATE_LIMITER_GLOBAL_ENABLED'] === true) { validateEnv(['RATE_LIMITER_GLOBAL_STORE', 'RATE_LIMITER_GLOBAL_DURATION', 'RATE_LIMITER_GLOBAL_POINTS']); validateConfiguration(); @@ -27,7 +27,7 @@ if (env.RATE_LIMITER_GLOBAL_ENABLED === true) { res.set('Retry-After', String(Math.round(rateLimiterRes.msBeforeNext / 1000))); throw new HitRateLimitException(`Too many requests, retry after ${ms(rateLimiterRes.msBeforeNext)}.`, { - limit: +env.RATE_LIMITER_GLOBAL_POINTS, + limit: +env['RATE_LIMITER_GLOBAL_POINTS'], reset: new Date(Date.now() + rateLimiterRes.msBeforeNext), }); } @@ -39,13 +39,13 @@ if (env.RATE_LIMITER_GLOBAL_ENABLED === true) { export default checkRateLimit; function validateConfiguration() { - if (env.RATE_LIMITER_ENABLED !== true) { + if (env['RATE_LIMITER_ENABLED'] !== true) { logger.error(`The IP based rate limiter needs to be enabled when using the global rate limiter.`); process.exit(1); } const globalPointsPerSec = - Number(env.RATE_LIMITER_GLOBAL_POINTS) / Math.max(Number(env.RATE_LIMITER_GLOBAL_DURATION), 1); - const regularPointsPerSec = Number(env.RATE_LIMITER_POINTS) / Math.max(Number(env.RATE_LIMITER_DURATION), 1); + Number(env['RATE_LIMITER_GLOBAL_POINTS']) / Math.max(Number(env['RATE_LIMITER_GLOBAL_DURATION']), 1); + const regularPointsPerSec = Number(env['RATE_LIMITER_POINTS']) / Math.max(Number(env['RATE_LIMITER_DURATION']), 1); if (globalPointsPerSec <= regularPointsPerSec) { logger.error(`The global rate limiter needs to allow more requests per second than the IP based rate limiter.`); process.exit(1); diff --git a/api/src/middleware/rate-limiter-ip.ts b/api/src/middleware/rate-limiter-ip.ts index 779ca8d934..11a557dc40 100644 --- a/api/src/middleware/rate-limiter-ip.ts +++ b/api/src/middleware/rate-limiter-ip.ts @@ -11,7 +11,7 @@ import { validateEnv } from '../utils/validate-env'; let checkRateLimit: RequestHandler = (_req, _res, next) => next(); export let rateLimiter: RateLimiterRedis | RateLimiterMemcache | RateLimiterMemory; -if (env.RATE_LIMITER_ENABLED === true) { +if (env['RATE_LIMITER_ENABLED'] === true) { validateEnv(['RATE_LIMITER_STORE', 'RATE_LIMITER_DURATION', 'RATE_LIMITER_POINTS']); rateLimiter = createRateLimiter('RATE_LIMITER'); @@ -24,7 +24,7 @@ if (env.RATE_LIMITER_ENABLED === true) { res.set('Retry-After', String(Math.round(rateLimiterRes.msBeforeNext / 1000))); throw new HitRateLimitException(`Too many requests, retry after ${ms(rateLimiterRes.msBeforeNext)}.`, { - limit: +env.RATE_LIMITER_POINTS, + limit: +env['RATE_LIMITER_POINTS'], reset: new Date(Date.now() + rateLimiterRes.msBeforeNext), }); } diff --git a/api/src/middleware/respond.ts b/api/src/middleware/respond.ts index 188d73572e..66ff75a45b 100644 --- a/api/src/middleware/respond.ts +++ b/api/src/middleware/respond.ts @@ -16,30 +16,30 @@ export const respond: RequestHandler = asyncHandler(async (req, res) => { let exceedsMaxSize = false; - if (env.CACHE_VALUE_MAX_SIZE !== false) { - const valueSize = res.locals.payload ? stringByteSize(JSON.stringify(res.locals.payload)) : 0; - const maxSize = parseBytesConfiguration(env.CACHE_VALUE_MAX_SIZE); + if (env['CACHE_VALUE_MAX_SIZE'] !== false) { + const valueSize = res.locals['payload'] ? stringByteSize(JSON.stringify(res.locals['payload'])) : 0; + const maxSize = parseBytesConfiguration(env['CACHE_VALUE_MAX_SIZE']); exceedsMaxSize = valueSize > maxSize; } if ( (req.method.toLowerCase() === 'get' || req.originalUrl?.startsWith('/graphql')) && - env.CACHE_ENABLED === true && + env['CACHE_ENABLED'] === true && cache && !req.sanitizedQuery.export && - res.locals.cache !== false && + res.locals['cache'] !== false && exceedsMaxSize === false ) { const key = getCacheKey(req); try { - await setCacheValue(cache, key, res.locals.payload, getMilliseconds(env.CACHE_TTL)); - await setCacheValue(cache, `${key}__expires_at`, { exp: Date.now() + getMilliseconds(env.CACHE_TTL, 0) }); + await setCacheValue(cache, key, res.locals['payload'], getMilliseconds(env['CACHE_TTL'])); + await setCacheValue(cache, `${key}__expires_at`, { exp: Date.now() + getMilliseconds(env['CACHE_TTL'], 0) }); } catch (err: any) { logger.warn(err, `[cache] Couldn't set key ${key}. ${err}`); } - res.setHeader('Cache-Control', getCacheControlHeader(req, getMilliseconds(env.CACHE_TTL), true, true)); + res.setHeader('Cache-Control', getCacheControlHeader(req, getMilliseconds(env['CACHE_TTL']), true, true)); res.setHeader('Vary', 'Origin, Cache-Control'); } else { // Don't cache anything by default @@ -63,32 +63,32 @@ export const respond: RequestHandler = asyncHandler(async (req, res) => { if (req.sanitizedQuery.export === 'json') { res.attachment(`${filename}.json`); res.set('Content-Type', 'application/json'); - return res.status(200).send(exportService.transform(res.locals.payload?.data, 'json')); + return res.status(200).send(exportService.transform(res.locals['payload']?.data, 'json')); } if (req.sanitizedQuery.export === 'xml') { res.attachment(`${filename}.xml`); res.set('Content-Type', 'text/xml'); - return res.status(200).send(exportService.transform(res.locals.payload?.data, 'xml')); + return res.status(200).send(exportService.transform(res.locals['payload']?.data, 'xml')); } if (req.sanitizedQuery.export === 'csv') { res.attachment(`${filename}.csv`); res.set('Content-Type', 'text/csv'); - return res.status(200).send(exportService.transform(res.locals.payload?.data, 'csv')); + return res.status(200).send(exportService.transform(res.locals['payload']?.data, 'csv')); } if (req.sanitizedQuery.export === 'yaml') { res.attachment(`${filename}.yaml`); res.set('Content-Type', 'text/yaml'); - return res.status(200).send(exportService.transform(res.locals.payload?.data, 'yaml')); + return res.status(200).send(exportService.transform(res.locals['payload']?.data, 'yaml')); } } - if (Buffer.isBuffer(res.locals.payload)) { - return res.end(res.locals.payload); - } else if (res.locals.payload) { - return res.json(res.locals.payload); + if (Buffer.isBuffer(res.locals['payload'])) { + return res.end(res.locals['payload']); + } else if (res.locals['payload']) { + return res.json(res.locals['payload']); } else { return res.status(204).end(); } diff --git a/api/src/middleware/sanitize-query.ts b/api/src/middleware/sanitize-query.ts index fc9f4e5e84..8fe69f5392 100644 --- a/api/src/middleware/sanitize-query.ts +++ b/api/src/middleware/sanitize-query.ts @@ -13,7 +13,7 @@ const sanitizeQueryMiddleware: RequestHandler = (req, _res, next) => { req.sanitizedQuery = sanitizeQuery( { - fields: req.query.fields || '*', + fields: req.query['fields'] || '*', ...req.query, }, req.accountability || null diff --git a/api/src/operations/exec/index.ts b/api/src/operations/exec/index.ts index 678d1e0dbc..6069b76922 100644 --- a/api/src/operations/exec/index.ts +++ b/api/src/operations/exec/index.ts @@ -9,10 +9,10 @@ type Options = { export default defineOperationApi({ id: 'exec', handler: async ({ code }, { data, env }) => { - const allowedModules = env.FLOWS_EXEC_ALLOWED_MODULES ? toArray(env.FLOWS_EXEC_ALLOWED_MODULES) : []; + const allowedModules = env['FLOWS_EXEC_ALLOWED_MODULES'] ? toArray(env['FLOWS_EXEC_ALLOWED_MODULES']) : []; const allowedModulesBuiltIn: string[] = []; const allowedModulesExternal: string[] = []; - const allowedEnv = data.$env ?? {}; + const allowedEnv = data['$env'] ?? {}; const opts: NodeVMOptions = { eval: false, diff --git a/api/src/rate-limiter.ts b/api/src/rate-limiter.ts index 43e6ecbd5f..aef1b4aad7 100644 --- a/api/src/rate-limiter.ts +++ b/api/src/rate-limiter.ts @@ -16,7 +16,7 @@ export function createRateLimiter( configPrefix = 'RATE_LIMITER', configOverrides?: IRateLimiterOptionsOverrides ): RateLimiterAbstract { - switch (env.RATE_LIMITER_STORE) { + switch (env['RATE_LIMITER_STORE']) { case 'redis': return new RateLimiterRedis(getConfig('redis', configPrefix, configOverrides)); case 'memcache': diff --git a/api/src/request/validate-ip.ts b/api/src/request/validate-ip.ts index e3e7b7dbdc..4f1da15e92 100644 --- a/api/src/request/validate-ip.ts +++ b/api/src/request/validate-ip.ts @@ -4,11 +4,11 @@ import { getEnv } from '../env'; export const validateIP = async (ip: string, url: string) => { const env = getEnv(); - if (env.IMPORT_IP_DENY_LIST.includes(ip)) { + if (env['IMPORT_IP_DENY_LIST'].includes(ip)) { throw new Error(`Requested URL "${url}" resolves to a denied IP address`); } - if (env.IMPORT_IP_DENY_LIST.includes('0.0.0.0')) { + if (env['IMPORT_IP_DENY_LIST'].includes('0.0.0.0')) { const networkInterfaces = os.networkInterfaces(); for (const networkInfo of Object.values(networkInterfaces)) { diff --git a/api/src/server.ts b/api/src/server.ts index 94c4114f84..9fc818a643 100644 --- a/api/src/server.ts +++ b/api/src/server.ts @@ -94,7 +94,7 @@ export async function createServer(): Promise { return server; async function beforeShutdown() { - if (env.NODE_ENV !== 'development') { + if (env['NODE_ENV'] !== 'development') { logger.info('Shutting down...'); } } @@ -117,7 +117,7 @@ export async function createServer(): Promise { } ); - if (env.NODE_ENV !== 'development') { + if (env['NODE_ENV'] !== 'development') { logger.info('Directus shut down OK. Bye bye!'); } } @@ -126,8 +126,8 @@ export async function createServer(): Promise { export async function startServer(): Promise { const server = await createServer(); - const host = env.HOST; - const port = env.PORT; + const host = env['HOST']; + const port = env['PORT']; server .listen(port, host, () => { diff --git a/api/src/services/activity.ts b/api/src/services/activity.ts index 50731f5284..faaadaf919 100644 --- a/api/src/services/activity.ts +++ b/api/src/services/activity.ts @@ -23,11 +23,11 @@ export class ActivityService extends ItemsService { this.usersService = new UsersService({ schema: this.schema }); } - async createOne(data: Partial, opts?: MutationOptions): Promise { - if (data.action === Action.COMMENT && typeof data.comment === 'string') { + override async createOne(data: Partial, opts?: MutationOptions): Promise { + if (data['action'] === Action.COMMENT && typeof data['comment'] === 'string') { const usersRegExp = new RegExp(/@[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}/gi); - const mentions = uniq(data.comment.match(usersRegExp) ?? []); + const mentions = uniq(data['comment'].match(usersRegExp) ?? []); const sender = await this.usersService.readOne(this.accountability!.user!, { fields: ['id', 'first_name', 'last_name', 'email'], @@ -42,9 +42,9 @@ export class ActivityService extends ItemsService { const accountability: Accountability = { user: userID, - role: user.role?.id ?? null, - admin: user.role?.admin_access ?? null, - app: user.role?.app_access ?? null, + role: user['role']?.id ?? null, + admin: user['role']?.admin_access ?? null, + app: user['role']?.app_access ?? null, }; accountability.permissions = await getPermissions(accountability, this.schema); @@ -53,7 +53,7 @@ export class ActivityService extends ItemsService { const usersService = new UsersService({ schema: this.schema, accountability }); try { - await authorizationService.checkAccess('read', data.collection, data.item); + await authorizationService.checkAccess('read', data['collection'], data['item']); const templateData = await usersService.readByQuery({ fields: ['id', 'first_name', 'last_name', 'email'], @@ -61,11 +61,11 @@ export class ActivityService extends ItemsService { }); const userPreviews = templateData.reduce((acc, user) => { - acc[user.id] = `${userName(user)}`; + acc[user['id']] = `${userName(user)}`; return acc; }, {} as Record); - let comment = data.comment; + let comment = data['comment']; for (const mention of mentions) { const uuid = mention.substring(1); @@ -83,18 +83,18 @@ ${userName(sender)} has mentioned you in a comment: ${comment} -Click here to view. `; await this.notificationsService.createOne({ recipient: userID, - sender: sender.id, - subject: `You were mentioned in ${data.collection}`, + sender: sender['id'], + subject: `You were mentioned in ${data['collection']}`, message, - collection: data.collection, - item: data.item, + collection: data['collection'], + item: data['item'], }); } catch (err: any) { if (err instanceof ForbiddenException) { diff --git a/api/src/services/assets.ts b/api/src/services/assets.ts index 7a214e3073..2c62195825 100644 --- a/api/src/services/assets.ts +++ b/api/src/services/assets.ts @@ -145,8 +145,8 @@ export class AssetsService { if ( !width || !height || - width > env.ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION || - height > env.ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION + width > env['ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION'] || + height > env['ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION'] ) { throw new IllegalAssetTransformation( `Image is too large to be transformed, or image size couldn't be determined.` @@ -155,7 +155,7 @@ export class AssetsService { const { queue, process } = sharp.counters(); - if (queue + process > env.ASSETS_TRANSFORM_MAX_CONCURRENT) { + if (queue + process > env['ASSETS_TRANSFORM_MAX_CONCURRENT']) { throw new ServiceUnavailableException('Server too busy', { service: 'files', }); @@ -164,13 +164,13 @@ export class AssetsService { const readStream = await storage.location(file.storage).read(file.filename_disk, range); const transformer = sharp({ - limitInputPixels: Math.pow(env.ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION, 2), + limitInputPixels: Math.pow(env['ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION'], 2), sequentialRead: true, - failOn: env.ASSETS_INVALID_IMAGE_SENSITIVITY_LEVEL, + failOn: env['ASSETS_INVALID_IMAGE_SENSITIVITY_LEVEL'], }); transformer.timeout({ - seconds: clamp(Math.round(getMilliseconds(env.ASSETS_TRANSFORM_TIMEOUT, 0) / 1000), 1, 3600), + seconds: clamp(Math.round(getMilliseconds(env['ASSETS_TRANSFORM_TIMEOUT'], 0) / 1000), 1, 3600), }); if (transforms.find((transform) => transform[0] === 'rotate') === undefined) transformer.rotate(); diff --git a/api/src/services/authentication.ts b/api/src/services/authentication.ts index f8c021d186..3643839b06 100644 --- a/api/src/services/authentication.ts +++ b/api/src/services/authentication.ts @@ -50,7 +50,7 @@ export class AuthenticationService { ): Promise { const { nanoid } = await import('nanoid'); - const STALL_TIME = env.LOGIN_STALL_TIME; + const STALL_TIME = env['LOGIN_STALL_TIME']; const timeStart = performance.now(); const provider = getAuthProvider(providerName); @@ -203,13 +203,13 @@ export class AuthenticationService { } ); - const accessToken = jwt.sign(customClaims, env.SECRET as string, { - expiresIn: env.ACCESS_TOKEN_TTL, + const accessToken = jwt.sign(customClaims, env['SECRET'] as string, { + expiresIn: env['ACCESS_TOKEN_TTL'], issuer: 'directus', }); const refreshToken = nanoid(64); - const refreshTokenExpiration = new Date(Date.now() + getMilliseconds(env.REFRESH_TOKEN_TTL, 0)); + const refreshTokenExpiration = new Date(Date.now() + getMilliseconds(env['REFRESH_TOKEN_TTL'], 0)); await this.knex('directus_sessions').insert({ token: refreshToken, @@ -247,7 +247,7 @@ export class AuthenticationService { return { accessToken, refreshToken, - expires: getMilliseconds(env.ACCESS_TOKEN_TTL), + expires: getMilliseconds(env['ACCESS_TOKEN_TTL']), id: user.id, }; } @@ -358,13 +358,13 @@ export class AuthenticationService { } ); - const accessToken = jwt.sign(customClaims, env.SECRET as string, { - expiresIn: env.ACCESS_TOKEN_TTL, + const accessToken = jwt.sign(customClaims, env['SECRET'] as string, { + expiresIn: env['ACCESS_TOKEN_TTL'], issuer: 'directus', }); const newRefreshToken = nanoid(64); - const refreshTokenExpiration = new Date(Date.now() + getMilliseconds(env.REFRESH_TOKEN_TTL, 0)); + const refreshTokenExpiration = new Date(Date.now() + getMilliseconds(env['REFRESH_TOKEN_TTL'], 0)); await this.knex('directus_sessions') .update({ @@ -380,7 +380,7 @@ export class AuthenticationService { return { accessToken, refreshToken: newRefreshToken, - expires: getMilliseconds(env.ACCESS_TOKEN_TTL), + expires: getMilliseconds(env['ACCESS_TOKEN_TTL']), id: record.user_id, }; } diff --git a/api/src/services/collections.ts b/api/src/services/collections.ts index f7712f80fd..c80f9bc95c 100644 --- a/api/src/services/collections.ts +++ b/api/src/services/collections.ts @@ -164,7 +164,7 @@ export class CollectionsService { return payload.collection; } finally { - if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) { + if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) { await this.cache.clear(); } @@ -213,7 +213,7 @@ export class CollectionsService { return collections; } finally { - if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) { + if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) { await this.cache.clear(); } @@ -306,8 +306,8 @@ export class CollectionsService { } } - if (env.DB_EXCLUDE_TABLES) { - return collections.filter((collection) => env.DB_EXCLUDE_TABLES.includes(collection.collection) === false); + if (env['DB_EXCLUDE_TABLES']) { + return collections.filter((collection) => env['DB_EXCLUDE_TABLES'].includes(collection.collection) === false); } return collections; @@ -396,7 +396,7 @@ export class CollectionsService { return collectionKey; } finally { - if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) { + if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) { await this.cache.clear(); } @@ -452,7 +452,7 @@ export class CollectionsService { } }); } finally { - if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) { + if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) { await this.cache.clear(); } @@ -502,7 +502,7 @@ export class CollectionsService { return collectionKeys; } finally { - if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) { + if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) { await this.cache.clear(); } @@ -631,7 +631,7 @@ export class CollectionsService { return collectionKey; } finally { - if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) { + if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) { await this.cache.clear(); } @@ -679,7 +679,7 @@ export class CollectionsService { return collectionKeys; } finally { - if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) { + if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) { await this.cache.clear(); } diff --git a/api/src/services/fields.ts b/api/src/services/fields.ts index 8a5bac6f0c..922a15ea0a 100644 --- a/api/src/services/fields.ts +++ b/api/src/services/fields.ts @@ -337,7 +337,7 @@ export class FieldsService { await this.helpers.schema.postColumnChange(); } - if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) { + if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) { await this.cache.clear(); } @@ -456,7 +456,7 @@ export class FieldsService { await this.helpers.schema.postColumnChange(); } - if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) { + if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) { await this.cache.clear(); } @@ -571,11 +571,11 @@ export class FieldsService { const collectionMetaUpdates: Record = {}; if (collectionMeta?.archive_field === field) { - collectionMetaUpdates.archive_field = null; + collectionMetaUpdates['archive_field'] = null; } if (collectionMeta?.sort_field === field) { - collectionMetaUpdates.sort_field = null; + collectionMetaUpdates['sort_field'] = null; } if (Object.keys(collectionMetaUpdates).length > 0) { @@ -622,7 +622,7 @@ export class FieldsService { await this.helpers.schema.postColumnChange(); } - if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) { + if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) { await this.cache.clear(); } diff --git a/api/src/services/files.ts b/api/src/services/files.ts index 66223d08ed..e34badc5aa 100644 --- a/api/src/services/files.ts +++ b/api/src/services/files.ts @@ -114,7 +114,7 @@ export class FilesService extends ItemsService { await sudoService.updateOne(primaryKey, payload, { emitEvents: false }); - if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) { + if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) { await this.cache.clear(); } @@ -140,7 +140,7 @@ export class FilesService extends ItemsService { /** * Extract metadata from a buffer's content */ - async getMetadata(stream: Readable, allowList = env.FILE_METADATA_ALLOW_LIST): Promise { + async getMetadata(stream: Readable, allowList = env['FILE_METADATA_ALLOW_LIST']): Promise { return new Promise((resolve, reject) => { pipeline( stream, @@ -214,14 +214,14 @@ export class FilesService extends ItemsService { } } - if (fullMetadata?.iptc?.Caption && typeof fullMetadata.iptc.Caption === 'string') { - metadata.description = fullMetadata.iptc?.Caption; + if (fullMetadata?.iptc?.['Caption'] && typeof fullMetadata.iptc['Caption'] === 'string') { + metadata.description = fullMetadata.iptc?.['Caption']; } - if (fullMetadata?.iptc?.Headline && typeof fullMetadata.iptc.Headline === 'string') { - metadata.title = fullMetadata.iptc.Headline; + if (fullMetadata?.iptc?.['Headline'] && typeof fullMetadata.iptc['Headline'] === 'string') { + metadata.title = fullMetadata.iptc['Headline']; } - if (fullMetadata?.iptc?.Keywords) { - metadata.tags = fullMetadata.iptc.Keywords; + if (fullMetadata?.iptc?.['Keywords']) { + metadata.tags = fullMetadata.iptc['Keywords']; } if (allowList === '*' || allowList?.[0] === '*') { @@ -277,7 +277,7 @@ export class FilesService extends ItemsService { const payload = { filename_download: filename, - storage: toArray(env.STORAGE_LOCATIONS)[0], + storage: toArray(env['STORAGE_LOCATIONS'])[0], type: fileResponse.headers['content-type'], title: formatTitle(filename), ...(body || {}), @@ -290,7 +290,7 @@ export class FilesService extends ItemsService { * Create a file (only applicable when it is not a multipart/data POST request) * Useful for associating metadata with existing file in storage */ - async createOne(data: Partial, opts?: MutationOptions): Promise { + override async createOne(data: Partial, opts?: MutationOptions): Promise { if (!data.type) { throw new InvalidPayloadException(`"type" is required`); } @@ -302,7 +302,7 @@ export class FilesService extends ItemsService { /** * Delete a file */ - async deleteOne(key: PrimaryKey, opts?: MutationOptions): Promise { + override async deleteOne(key: PrimaryKey, opts?: MutationOptions): Promise { await this.deleteMany([key], opts); return key; } @@ -310,7 +310,7 @@ export class FilesService extends ItemsService { /** * Delete multiple files */ - async deleteMany(keys: PrimaryKey[], opts?: MutationOptions): Promise { + override async deleteMany(keys: PrimaryKey[], opts?: MutationOptions): Promise { const storage = await getStorage(); const files = await super.readMany(keys, { fields: ['id', 'storage'], limit: -1 }); @@ -321,15 +321,15 @@ export class FilesService extends ItemsService { await super.deleteMany(keys); for (const file of files) { - const disk = storage.location(file.storage); + const disk = storage.location(file['storage']); // Delete file + thumbnails - for await (const filepath of disk.list(file.id)) { + for await (const filepath of disk.list(file['id'])) { await disk.delete(filepath); } } - if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) { + if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) { await this.cache.clear(); } diff --git a/api/src/services/flows.ts b/api/src/services/flows.ts index eb57bb5f0f..d07abe1398 100644 --- a/api/src/services/flows.ts +++ b/api/src/services/flows.ts @@ -8,7 +8,7 @@ export class FlowsService extends ItemsService { super('directus_flows', options); } - async createOne(data: Partial, opts?: MutationOptions): Promise { + override async createOne(data: Partial, opts?: MutationOptions): Promise { const flowManager = getFlowManager(); const result = await super.createOne(data, opts); @@ -17,7 +17,7 @@ export class FlowsService extends ItemsService { return result; } - async createMany(data: Partial[], opts?: MutationOptions): Promise { + override async createMany(data: Partial[], opts?: MutationOptions): Promise { const flowManager = getFlowManager(); const result = await super.createMany(data, opts); @@ -26,7 +26,7 @@ export class FlowsService extends ItemsService { return result; } - async updateBatch(data: Partial[], opts?: MutationOptions): Promise { + override async updateBatch(data: Partial[], opts?: MutationOptions): Promise { const flowManager = getFlowManager(); const result = await super.updateBatch(data, opts); @@ -35,7 +35,7 @@ export class FlowsService extends ItemsService { return result; } - async updateMany(keys: PrimaryKey[], data: Partial, opts?: MutationOptions): Promise { + override async updateMany(keys: PrimaryKey[], data: Partial, opts?: MutationOptions): Promise { const flowManager = getFlowManager(); const result = await super.updateMany(keys, data, opts); @@ -44,7 +44,7 @@ export class FlowsService extends ItemsService { return result; } - async deleteMany(keys: PrimaryKey[], opts?: MutationOptions): Promise { + override async deleteMany(keys: PrimaryKey[], opts?: MutationOptions): Promise { const flowManager = getFlowManager(); // this is to prevent foreign key constraint error on directus_operations resolve/reject during cascade deletion diff --git a/api/src/services/graphql/index.ts b/api/src/services/graphql/index.ts index fcd656f7f1..9603b80996 100644 --- a/api/src/services/graphql/index.ts +++ b/api/src/services/graphql/index.ts @@ -93,7 +93,7 @@ import processError from './utils/process-error'; const validationRules = Array.from(specifiedRules); -if (env.GRAPHQL_INTROSPECTION === false) { +if (env['GRAPHQL_INTROSPECTION'] === false) { validationRules.push(NoSchemaIntrospectionCustomRule); } @@ -158,9 +158,10 @@ export class GraphQLService { const formattedResult: FormattedExecutionResult = {}; - if (result.data) formattedResult.data = result.data; - if (result.errors) formattedResult.errors = result.errors.map((error) => processError(this.accountability, error)); - if (result.extensions) formattedResult.extensions = result.extensions; + if (result['data']) formattedResult.data = result['data']; + if (result['errors']) + formattedResult.errors = result['errors'].map((error) => processError(this.accountability, error)); + if (result['extensions']) formattedResult.extensions = result['extensions']; return formattedResult; } @@ -313,11 +314,11 @@ export class GraphQLService { .reduce((acc, collection) => { const collectionName = this.scope === 'items' ? collection.collection : collection.collection.substring(9); - acc[`delete_${collectionName}_items`] = DeleteCollectionTypes.many.getResolver( + acc[`delete_${collectionName}_items`] = DeleteCollectionTypes['many'].getResolver( `delete_${collection.collection}_items` ); - acc[`delete_${collectionName}_item`] = DeleteCollectionTypes.one.getResolver( + acc[`delete_${collectionName}_item`] = DeleteCollectionTypes['one'].getResolver( `delete_${collection.collection}_item` ); @@ -519,7 +520,7 @@ export class GraphQLService { path = path.reverse().slice(0, -1); - let parent = context.data; + let parent = context['data']; for (const pathPart of path) { parent = parent[pathPart]; @@ -981,7 +982,7 @@ export class GraphQLService { ), resolve: async ({ info, context }: { info: GraphQLResolveInfo; context: Record }) => { const result = await self.resolveQuery(info); - context.data = result; + context['data'] = result; return result; }, }; @@ -1035,7 +1036,7 @@ export class GraphQLService { }, resolve: async ({ info, context }: { info: GraphQLResolveInfo; context: Record }) => { const result = await self.resolveQuery(info); - context.data = result; + context['data'] = result; return result; }, @@ -1050,7 +1051,7 @@ export class GraphQLService { }, resolve: async ({ info, context }: { info: GraphQLResolveInfo; context: Record }) => { const result = await self.resolveQuery(info); - context.data = result; + context['data'] = result; return result; }, }); @@ -1264,14 +1265,14 @@ export class GraphQLService { } } - DeleteCollectionTypes.many = schemaComposer.createObjectTC({ + DeleteCollectionTypes['many'] = schemaComposer.createObjectTC({ name: `delete_many`, fields: { ids: new GraphQLNonNull(new GraphQLList(GraphQLID)), }, }); - DeleteCollectionTypes.one = schemaComposer.createObjectTC({ + DeleteCollectionTypes['one'] = schemaComposer.createObjectTC({ name: `delete_one`, fields: { id: new GraphQLNonNull(GraphQLID), @@ -1279,9 +1280,9 @@ export class GraphQLService { }); for (const collection of Object.values(schema.delete.collections)) { - DeleteCollectionTypes.many.addResolver({ + DeleteCollectionTypes['many'].addResolver({ name: `delete_${collection.collection}_items`, - type: DeleteCollectionTypes.many, + type: DeleteCollectionTypes['many'], args: { ids: new GraphQLNonNull(new GraphQLList(GraphQLID)), }, @@ -1289,9 +1290,9 @@ export class GraphQLService { await self.resolveMutation(args, info), }); - DeleteCollectionTypes.one.addResolver({ + DeleteCollectionTypes['one'].addResolver({ name: `delete_${collection.collection}_item`, - type: DeleteCollectionTypes.one, + type: DeleteCollectionTypes['one'], args: { id: new GraphQLNonNull(GraphQLID), }, @@ -1330,13 +1331,13 @@ export class GraphQLService { collection = collection.slice(0, -6); } } - if (args.id) { + if (args['id']) { query.filter = { _and: [ query.filter || {}, { [this.schema.collections[collection].primary]: { - _eq: args.id, + _eq: args['id'], }, }, ], @@ -1354,7 +1355,7 @@ export class GraphQLService { const result = await this.read(collection, query); - if (args.id) { + if (args['id']) { return result?.[0] || null; } @@ -1362,8 +1363,8 @@ export class GraphQLService { // for every entry in result add a group field based on query.group; const aggregateKeys = Object.keys(query.aggregate ?? {}); - result.map((field: Item) => { - field.group = omit(field, aggregateKeys); + result['map']((field: Item) => { + field['group'] = omit(field, aggregateKeys); }); } @@ -1395,7 +1396,7 @@ export class GraphQLService { if (collection.endsWith('_item')) collection = collection.slice(0, -5); if (singleton && action === 'update') { - return await this.upsertSingleton(collection, args.data, query); + return await this.upsertSingleton(collection, args['data'], query); } const service = this.getService(collection); @@ -1404,22 +1405,24 @@ export class GraphQLService { try { if (single) { if (action === 'create') { - const key = await service.createOne(args.data); + const key = await service.createOne(args['data']); return hasQuery ? await service.readOne(key, query) : true; } if (action === 'update') { - const key = await service.updateOne(args.id, args.data); + const key = await service.updateOne(args['id'], args['data']); return hasQuery ? await service.readOne(key, query) : true; } if (action === 'delete') { - await service.deleteOne(args.id); - return { id: args.id }; + await service.deleteOne(args['id']); + return { id: args['id'] }; } + + return undefined; } else { if (action === 'create') { - const keys = await service.createMany(args.data); + const keys = await service.createMany(args['data']); return hasQuery ? await service.readMany(keys, query) : true; } @@ -1427,18 +1430,20 @@ export class GraphQLService { const keys: PrimaryKey[] = []; if (batchUpdate) { - keys.push(...(await service.updateBatch(args.data))); + keys.push(...(await service.updateBatch(args['data']))); } else { - keys.push(...(await service.updateMany(args.ids, args.data))); + keys.push(...(await service.updateMany(args['ids'], args['data']))); } return hasQuery ? await service.readMany(keys, query) : true; } if (action === 'delete') { - const keys = await service.deleteMany(args.ids); + const keys = await service.deleteMany(args['ids']); return { ids: keys }; } + + return undefined; } } catch (err: any) { return this.formatError(err); @@ -1490,7 +1495,7 @@ export class GraphQLService { * of arguments */ parseArgs(args: readonly ArgumentNode[], variableValues: GraphQLResolveInfo['variableValues']): Record { - if (!args || args.length === 0) return {}; + if (!args || args['length'] === 0) return {}; const parse = (node: ValueNode): any => { switch (node.kind) { @@ -1515,7 +1520,7 @@ export class GraphQLService { } }; - const argsObject = Object.fromEntries(args.map((arg) => [arg.name.value, parse(arg.value)])); + const argsObject = Object.fromEntries(args['map']((arg) => [arg.name.value, parse(arg.value)])); return argsObject; } @@ -1712,10 +1717,10 @@ export class GraphQLService { */ formatError(error: BaseException | BaseException[]): GraphQLError { if (Array.isArray(error)) { - error[0].extensions.code = error[0].code; + error[0].extensions['code'] = error[0].code; return new GraphQLError(error[0].message, undefined, undefined, undefined, undefined, error[0]); } - error.extensions.code = error.code; + error.extensions['code'] = error.code; return new GraphQLError(error.message, undefined, undefined, undefined, undefined, error); } @@ -1938,7 +1943,7 @@ export class GraphQLService { const service = new GraphQLService({ schema: this.schema, accountability: this.accountability, - scope: args.scope ?? 'items', + scope: args['scope'] ?? 'items', }); return service.getSchema('sdl'); }, @@ -2009,19 +2014,19 @@ export class GraphQLService { schema: this.schema, }); const result = await authenticationService.login(DEFAULT_AUTH_PROVIDER, args, args?.otp); - if (args.mode === 'cookie') { - res?.cookie(env.REFRESH_TOKEN_COOKIE_NAME, result.refreshToken, { + if (args['mode'] === 'cookie') { + res?.cookie(env['REFRESH_TOKEN_COOKIE_NAME'], result['refreshToken'], { httpOnly: true, - domain: env.REFRESH_TOKEN_COOKIE_DOMAIN, - maxAge: getMilliseconds(env.REFRESH_TOKEN_TTL), - secure: env.REFRESH_TOKEN_COOKIE_SECURE ?? false, - sameSite: (env.REFRESH_TOKEN_COOKIE_SAME_SITE as 'lax' | 'strict' | 'none') || 'strict', + domain: env['REFRESH_TOKEN_COOKIE_DOMAIN'], + maxAge: getMilliseconds(env['REFRESH_TOKEN_TTL']), + secure: env['REFRESH_TOKEN_COOKIE_SECURE'] ?? false, + sameSite: (env['REFRESH_TOKEN_COOKIE_SAME_SITE'] as 'lax' | 'strict' | 'none') || 'strict', }); } return { - access_token: result.accessToken, - expires: result.expires, - refresh_token: result.refreshToken, + access_token: result['accessToken'], + expires: result['expires'], + refresh_token: result['refreshToken'], }; }, }, @@ -2046,24 +2051,24 @@ export class GraphQLService { accountability: accountability, schema: this.schema, }); - const currentRefreshToken = args.refresh_token || req?.cookies[env.REFRESH_TOKEN_COOKIE_NAME]; + const currentRefreshToken = args['refresh_token'] || req?.cookies[env['REFRESH_TOKEN_COOKIE_NAME']]; if (!currentRefreshToken) { throw new InvalidPayloadException(`"refresh_token" is required in either the JSON payload or Cookie`); } const result = await authenticationService.refresh(currentRefreshToken); - if (args.mode === 'cookie') { - res?.cookie(env.REFRESH_TOKEN_COOKIE_NAME, result.refreshToken, { + if (args['mode'] === 'cookie') { + res?.cookie(env['REFRESH_TOKEN_COOKIE_NAME'], result['refreshToken'], { httpOnly: true, - domain: env.REFRESH_TOKEN_COOKIE_DOMAIN, - maxAge: getMilliseconds(env.REFRESH_TOKEN_TTL), - secure: env.REFRESH_TOKEN_COOKIE_SECURE ?? false, - sameSite: (env.REFRESH_TOKEN_COOKIE_SAME_SITE as 'lax' | 'strict' | 'none') || 'strict', + domain: env['REFRESH_TOKEN_COOKIE_DOMAIN'], + maxAge: getMilliseconds(env['REFRESH_TOKEN_TTL']), + secure: env['REFRESH_TOKEN_COOKIE_SECURE'] ?? false, + sameSite: (env['REFRESH_TOKEN_COOKIE_SAME_SITE'] as 'lax' | 'strict' | 'none') || 'strict', }); } return { - access_token: result.accessToken, - expires: result.expires, - refresh_token: result.refreshToken, + access_token: result['accessToken'], + expires: result['expires'], + refresh_token: result['refreshToken'], }; }, }, @@ -2087,7 +2092,7 @@ export class GraphQLService { accountability: accountability, schema: this.schema, }); - const currentRefreshToken = args.refresh_token || req?.cookies[env.REFRESH_TOKEN_COOKIE_NAME]; + const currentRefreshToken = args['refresh_token'] || req?.cookies[env['REFRESH_TOKEN_COOKIE_NAME']]; if (!currentRefreshToken) { throw new InvalidPayloadException(`"refresh_token" is required in either the JSON payload or Cookie`); } @@ -2114,7 +2119,7 @@ export class GraphQLService { const service = new UsersService({ accountability, schema: this.schema }); try { - await service.requestPasswordReset(args.email, args.reset_url || null); + await service.requestPasswordReset(args['email'], args['reset_url'] || null); } catch (err: any) { if (err instanceof InvalidPayloadException) { throw err; @@ -2142,7 +2147,7 @@ export class GraphQLService { if (origin) accountability.origin = origin; const service = new UsersService({ accountability, schema: this.schema }); - await service.resetPassword(args.token, args.password); + await service.resetPassword(args['token'], args['password']); return true; }, }, @@ -2167,7 +2172,7 @@ export class GraphQLService { accountability: this.accountability, schema: this.schema, }); - await authService.verifyPassword(this.accountability.user, args.password); + await authService.verifyPassword(this.accountability.user, args['password']); const { url, secret } = await service.generateTFA(this.accountability.user); return { secret, otpauth_url: url }; }, @@ -2185,7 +2190,7 @@ export class GraphQLService { schema: this.schema, }); - await service.enableTFA(this.accountability.user, args.otp, args.secret); + await service.enableTFA(this.accountability.user, args['otp'], args['secret']); return true; }, }, @@ -2200,7 +2205,7 @@ export class GraphQLService { accountability: this.accountability, schema: this.schema, }); - const otpValid = await service.verifyOTP(this.accountability.user, args.otp); + const otpValid = await service.verifyOTP(this.accountability.user, args['otp']); if (otpValid === false) { throw new InvalidPayloadException(`"otp" is invalid`); } @@ -2214,7 +2219,7 @@ export class GraphQLService { string: new GraphQLNonNull(GraphQLString), }, resolve: async (_, args) => { - return await generateHash(args.string); + return await generateHash(args['string']); }, }, utils_hash_verify: { @@ -2224,7 +2229,7 @@ export class GraphQLService { hash: new GraphQLNonNull(GraphQLString), }, resolve: async (_, args) => { - return await argon2.verify(args.hash, args.string); + return await argon2.verify(args['hash'], args['string']); }, }, utils_sort: { @@ -2240,7 +2245,7 @@ export class GraphQLService { schema: this.schema, }); const { item, to } = args; - await service.sort(args.collection, { item, to }); + await service.sort(args['collection'], { item, to }); return true; }, }, @@ -2254,7 +2259,7 @@ export class GraphQLService { accountability: this.accountability, schema: this.schema, }); - await service.revert(args.revision); + await service.revert(args['revision']); return true; }, }, @@ -2284,7 +2289,7 @@ export class GraphQLService { accountability: this.accountability, schema: this.schema, }); - await service.acceptInvite(args.token, args.password); + await service.acceptInvite(args['token'], args['password']); return true; }, }, @@ -2339,7 +2344,7 @@ export class GraphQLService { schema: this.schema, }); - return await collectionsService.readOne(args.name); + return await collectionsService.readOne(args['name']); }, }, }); @@ -2406,7 +2411,7 @@ export class GraphQLService { schema: this.schema, }); - return await service.readAll(args.collection); + return await service.readAll(args['collection']); }, }, fields_by_name: { @@ -2420,7 +2425,7 @@ export class GraphQLService { accountability: this.accountability, schema: this.schema, }); - return await service.readOne(args.collection, args.field); + return await service.readOne(args['collection'], args['field']); }, }, }); @@ -2479,7 +2484,7 @@ export class GraphQLService { schema: this.schema, }); - return await service.readAll(args.collection); + return await service.readAll(args['collection']); }, }, relations_by_name: { @@ -2493,7 +2498,7 @@ export class GraphQLService { accountability: this.accountability, schema: this.schema, }); - return await service.readOne(args.collection, args.field); + return await service.readOne(args['collection'], args['field']); }, }, }); @@ -2517,7 +2522,7 @@ export class GraphQLService { accountability: this.accountability, schema: this.schema, }); - const collectionKey = await collectionsService.createOne(args.data); + const collectionKey = await collectionsService.createOne(args['data']); return await collectionsService.readOne(collectionKey); }, }, @@ -2534,7 +2539,7 @@ export class GraphQLService { accountability: this.accountability, schema: this.schema, }); - const collectionKey = await collectionsService.updateOne(args.collection, args.data); + const collectionKey = await collectionsService.updateOne(args['collection'], args['data']); return await collectionsService.readOne(collectionKey); }, }, @@ -2553,8 +2558,8 @@ export class GraphQLService { accountability: this.accountability, schema: this.schema, }); - await collectionsService.deleteOne(args.collection); - return { collection: args.collection }; + await collectionsService.deleteOne(args['collection']); + return { collection: args['collection'] }; }, }, }); @@ -2571,8 +2576,8 @@ export class GraphQLService { accountability: this.accountability, schema: this.schema, }); - await service.createField(args.collection, args.data); - return await service.readOne(args.collection, args.data.field); + await service.createField(args['collection'], args['data']); + return await service.readOne(args['collection'], args['data'].field); }, }, update_fields_item: { @@ -2587,11 +2592,11 @@ export class GraphQLService { accountability: this.accountability, schema: this.schema, }); - await service.updateField(args.collection, { - ...args.data, - field: args.field, + await service.updateField(args['collection'], { + ...args['data'], + field: args['field'], }); - return await service.readOne(args.collection, args.data.field); + return await service.readOne(args['collection'], args['data'].field); }, }, delete_fields_item: { @@ -2611,7 +2616,7 @@ export class GraphQLService { accountability: this.accountability, schema: this.schema, }); - await service.deleteField(args.collection, args.field); + await service.deleteField(args['collection'], args['field']); const { collection, field } = args; return { collection, field }; }, @@ -2630,8 +2635,8 @@ export class GraphQLService { schema: this.schema, }); - await relationsService.createOne(args.data); - return await relationsService.readOne(args.data.collection, args.data.field); + await relationsService.createOne(args['data']); + return await relationsService.readOne(args['data'].collection, args['data'].field); }, }, update_relations_item: { @@ -2647,8 +2652,8 @@ export class GraphQLService { schema: this.schema, }); - await relationsService.updateOne(args.collection, args.field, args.data); - return await relationsService.readOne(args.data.collection, args.data.field); + await relationsService.updateOne(args['collection'], args['field'], args['data']); + return await relationsService.readOne(args['data'].collection, args['data'].field); }, }, delete_relations_item: { @@ -2668,8 +2673,8 @@ export class GraphQLService { accountability: this.accountability, schema: this.schema, }); - await relationsService.deleteOne(args.collection, args.field); - return { collection: args.collection, field: args.field }; + await relationsService.deleteOne(args['collection'], args['field']); + return { collection: args['collection'], field: args['field'] }; }, }, }); @@ -2708,7 +2713,7 @@ export class GraphQLService { accountability: this.accountability, }); - await service.updateOne(this.accountability.user, args.data); + await service.updateOne(this.accountability.user, args['data']); if ('directus_users' in ReadCollectionTypes) { const selections = this.replaceFragmentsInSelections( @@ -2778,7 +2783,7 @@ export class GraphQLService { accountability: this.accountability, schema: this.schema, }); - const primaryKey = await service.updateOne(args.id, { comment: args.comment }); + const primaryKey = await service.updateOne(args['id'], { comment: args['comment'] }); if ('directus_activity' in ReadCollectionTypes) { const selections = this.replaceFragmentsInSelections( @@ -2799,7 +2804,7 @@ export class GraphQLService { if ('directus_activity' in schema.delete.collections) { schemaComposer.Mutation.addFields({ delete_comment: { - type: DeleteCollectionTypes.one, + type: DeleteCollectionTypes['one'], args: { id: new GraphQLNonNull(GraphQLID), }, @@ -2808,8 +2813,8 @@ export class GraphQLService { accountability: this.accountability, schema: this.schema, }); - await service.deleteOne(args.id); - return { id: args.id }; + await service.deleteOne(args['id']); + return { id: args['id'] }; }, }, }); @@ -2828,7 +2833,7 @@ export class GraphQLService { accountability: this.accountability, schema: this.schema, }); - const primaryKey = await service.importOne(args.url, args.data); + const primaryKey = await service.importOne(args['url'], args['data']); if ('directus_files' in ReadCollectionTypes) { const selections = this.replaceFragmentsInSelections( @@ -2859,7 +2864,7 @@ export class GraphQLService { accountability: this.accountability, schema: this.schema, }); - await service.inviteUser(args.email, args.role, args.invite_url || null); + await service.inviteUser(args['email'], args['role'], args['invite_url'] || null); return true; }, }, diff --git a/api/src/services/import-export.ts b/api/src/services/import-export.ts index d7bef3a4aa..81c080a433 100644 --- a/api/src/services/import-export.ts +++ b/api/src/services/import-export.ts @@ -224,26 +224,26 @@ export class ExportService { count: ['*'], }, }) - .then((result) => Number(result?.[0]?.count ?? 0)); + .then((result) => Number(result?.[0]?.['count'] ?? 0)); const count = query.limit ? Math.min(totalCount, query.limit) : totalCount; const requestedLimit = query.limit ?? -1; - const batchesRequired = Math.ceil(count / env.EXPORT_BATCH_SIZE); + const batchesRequired = Math.ceil(count / env['EXPORT_BATCH_SIZE']); let readCount = 0; for (let batch = 0; batch < batchesRequired; batch++) { - let limit = env.EXPORT_BATCH_SIZE; + let limit = env['EXPORT_BATCH_SIZE']; - if (requestedLimit > 0 && env.EXPORT_BATCH_SIZE > requestedLimit - readCount) { + if (requestedLimit > 0 && env['EXPORT_BATCH_SIZE'] > requestedLimit - readCount) { limit = requestedLimit - readCount; } const result = await service.readByQuery({ ...query, limit, - offset: batch * env.EXPORT_BATCH_SIZE, + offset: batch * env['EXPORT_BATCH_SIZE'], }); readCount += result.length; @@ -265,7 +265,7 @@ export class ExportService { schema: this.schema, }); - const storage: string = toArray(env.STORAGE_LOCATIONS)[0]; + const storage: string = toArray(env['STORAGE_LOCATIONS'])[0]; const title = `export-${collection}-${getDateFormatted()}`; const filename = `${title}.${format}`; diff --git a/api/src/services/items.test.ts b/api/src/services/items.test.ts index 35d000db14..1dea8c981d 100644 --- a/api/src/services/items.test.ts +++ b/api/src/services/items.test.ts @@ -90,12 +90,12 @@ describe('Integration Tests', () => { it(`the returned UUID primary key for MS SQL should be uppercase`, async () => { vi.mocked(getDatabaseClient).mockReturnValue('mssql'); - const table = schemas.system.tables[0]; + const table = schemas['system'].tables[0]; const itemsService = new ItemsService(table, { knex: db, accountability: { role: 'admin', admin: true }, - schema: schemas.system.schema, + schema: schemas['system'].schema, }); tracker.on.insert(table).responseOnce(item); diff --git a/api/src/services/items.ts b/api/src/services/items.ts index 0ea90cfa19..5a2430d2a6 100644 --- a/api/src/services/items.ts +++ b/api/src/services/items.ts @@ -265,7 +265,7 @@ export class ItemsService implements AbstractSer } } - if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) { + if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) { await this.cache.clear(); } @@ -308,7 +308,7 @@ export class ItemsService implements AbstractSer } } - if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) { + if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) { await this.cache.clear(); } @@ -491,7 +491,7 @@ export class ItemsService implements AbstractSer } }); } finally { - if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) { + if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) { await this.cache.clear(); } } @@ -668,7 +668,7 @@ export class ItemsService implements AbstractSer } }); - if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) { + if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) { await this.cache.clear(); } @@ -755,7 +755,7 @@ export class ItemsService implements AbstractSer return primaryKeys; }); - if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) { + if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) { await this.cache.clear(); } @@ -844,7 +844,7 @@ export class ItemsService implements AbstractSer } }); - if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) { + if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) { await this.cache.clear(); } diff --git a/api/src/services/mail/index.ts b/api/src/services/mail/index.ts index 56250fc221..ad603bdaf4 100644 --- a/api/src/services/mail/index.ts +++ b/api/src/services/mail/index.ts @@ -13,7 +13,7 @@ import type { AbstractServiceOptions } from '../../types'; import { Url } from '../../utils/url'; const liquidEngine = new Liquid({ - root: [path.resolve(env.EXTENSIONS_PATH, 'templates'), path.resolve(__dirname, 'templates')], + root: [path.resolve(env['EXTENSIONS_PATH'], 'templates'), path.resolve(__dirname, 'templates')], extname: '.liquid', }); @@ -36,7 +36,7 @@ export class MailService { this.knex = opts?.knex || getDatabase(); this.mailer = getMailer(); - if (env.EMAIL_VERIFY_SETUP) { + if (env['EMAIL_VERIFY_SETUP']) { this.mailer.verify((error) => { if (error) { logger.warn(`Email connection failed:`); @@ -52,7 +52,7 @@ export class MailService { const defaultTemplateData = await this.getDefaultTemplateData(); - const from = `${defaultTemplateData.projectName} <${options.from || (env.EMAIL_FROM as string)}>`; + const from = `${defaultTemplateData.projectName} <${options.from || (env['EMAIL_FROM'] as string)}>`; if (template) { let templateData = template.data; @@ -78,7 +78,7 @@ export class MailService { } private async renderTemplate(template: string, variables: Record) { - const customTemplatePath = path.resolve(env.EXTENSIONS_PATH, 'templates', template + '.liquid'); + const customTemplatePath = path.resolve(env['EXTENSIONS_PATH'], 'templates', template + '.liquid'); const systemTemplatePath = path.join(__dirname, 'templates', template + '.liquid'); const templatePath = (await fse.pathExists(customTemplatePath)) ? customTemplatePath : systemTemplatePath; @@ -107,7 +107,7 @@ export class MailService { }; function getProjectLogoURL(logoID?: string) { - const projectLogoUrl = new Url(env.PUBLIC_URL); + const projectLogoUrl = new Url(env['PUBLIC_URL']); if (logoID) { projectLogoUrl.addPath('assets', logoID); diff --git a/api/src/services/meta.ts b/api/src/services/meta.ts index 5ac5a2161c..67efde89e2 100644 --- a/api/src/services/meta.ts +++ b/api/src/services/meta.ts @@ -24,6 +24,7 @@ export class MetaService { query.meta.map((metaVal: string) => { if (metaVal === 'total_count') return this.totalCount(collection); if (metaVal === 'filter_count') return this.filterCount(collection, query); + return undefined; }) ); diff --git a/api/src/services/notifications.ts b/api/src/services/notifications.ts index 6cd125209c..b057a6b1d5 100644 --- a/api/src/services/notifications.ts +++ b/api/src/services/notifications.ts @@ -18,7 +18,7 @@ export class NotificationsService extends ItemsService { this.mailService = new MailService({ schema: this.schema, accountability: this.accountability }); } - async createOne(data: Partial, opts?: MutationOptions): Promise { + override async createOne(data: Partial, opts?: MutationOptions): Promise { const response = await super.createOne(data, opts); await this.sendEmail(data); @@ -26,7 +26,7 @@ export class NotificationsService extends ItemsService { return response; } - async createMany(data: Partial[], opts?: MutationOptions): Promise { + override async createMany(data: Partial[], opts?: MutationOptions): Promise { const response = await super.createMany(data, opts); for (const notification of data) { @@ -41,18 +41,18 @@ export class NotificationsService extends ItemsService { const user = await this.usersService.readOne(data.recipient, { fields: ['id', 'email', 'email_notifications', 'role.app_access'], }); - const manageUserAccountUrl = new Url(env.PUBLIC_URL).addPath('admin', 'users', user.id).toString(); + const manageUserAccountUrl = new Url(env['PUBLIC_URL']).addPath('admin', 'users', user['id']).toString(); const html = data.message ? md(data.message) : ''; - if (user.email && user.email_notifications === true) { + if (user['email'] && user['email_notifications'] === true) { try { await this.mailService.send({ template: { name: 'base', - data: user.role?.app_access ? { url: manageUserAccountUrl, html } : { html }, + data: user['role']?.app_access ? { url: manageUserAccountUrl, html } : { html }, }, - to: user.email, + to: user['email'], subject: data.subject, }); } catch (error: any) { diff --git a/api/src/services/operations.ts b/api/src/services/operations.ts index 3cb3845fad..097ef2ca9b 100644 --- a/api/src/services/operations.ts +++ b/api/src/services/operations.ts @@ -8,7 +8,7 @@ export class OperationsService extends ItemsService { super('directus_operations', options); } - async createOne(data: Partial, opts?: MutationOptions): Promise { + override async createOne(data: Partial, opts?: MutationOptions): Promise { const flowManager = getFlowManager(); const result = await super.createOne(data, opts); @@ -17,7 +17,7 @@ export class OperationsService extends ItemsService { return result; } - async createMany(data: Partial[], opts?: MutationOptions): Promise { + override async createMany(data: Partial[], opts?: MutationOptions): Promise { const flowManager = getFlowManager(); const result = await super.createMany(data, opts); @@ -26,7 +26,7 @@ export class OperationsService extends ItemsService { return result; } - async updateBatch(data: Partial[], opts?: MutationOptions): Promise { + override async updateBatch(data: Partial[], opts?: MutationOptions): Promise { const flowManager = getFlowManager(); const result = await super.updateBatch(data, opts); @@ -35,7 +35,7 @@ export class OperationsService extends ItemsService { return result; } - async updateMany(keys: PrimaryKey[], data: Partial, opts?: MutationOptions): Promise { + override async updateMany(keys: PrimaryKey[], data: Partial, opts?: MutationOptions): Promise { const flowManager = getFlowManager(); const result = await super.updateMany(keys, data, opts); @@ -44,7 +44,7 @@ export class OperationsService extends ItemsService { return result; } - async deleteMany(keys: PrimaryKey[], opts?: MutationOptions): Promise { + override async deleteMany(keys: PrimaryKey[], opts?: MutationOptions): Promise { const flowManager = getFlowManager(); const result = await super.deleteMany(keys, opts); diff --git a/api/src/services/permissions.ts b/api/src/services/permissions.ts index e8b9b8afda..d420c34fe5 100644 --- a/api/src/services/permissions.ts +++ b/api/src/services/permissions.ts @@ -42,7 +42,7 @@ export class PermissionsService extends ItemsService { return fieldsPerCollection; } - async readByQuery(query: Query, opts?: QueryOptions): Promise[]> { + override async readByQuery(query: Query, opts?: QueryOptions): Promise[]> { const result = await super.readByQuery(query, opts); if (Array.isArray(result) && this.accountability && this.accountability.app === true) { @@ -60,7 +60,7 @@ export class PermissionsService extends ItemsService { return result; } - async readMany(keys: PrimaryKey[], query: Query = {}, opts?: QueryOptions): Promise[]> { + override async readMany(keys: PrimaryKey[], query: Query = {}, opts?: QueryOptions): Promise[]> { const result = await super.readMany(keys, query, opts); if (this.accountability && this.accountability.app === true) { @@ -78,37 +78,37 @@ export class PermissionsService extends ItemsService { return result; } - async createOne(data: Partial, opts?: MutationOptions) { + override async createOne(data: Partial, opts?: MutationOptions) { const res = await super.createOne(data, opts); await clearSystemCache({ autoPurgeCache: opts?.autoPurgeCache }); return res; } - async createMany(data: Partial[], opts?: MutationOptions) { + override async createMany(data: Partial[], opts?: MutationOptions) { const res = await super.createMany(data, opts); await clearSystemCache({ autoPurgeCache: opts?.autoPurgeCache }); return res; } - async updateBatch(data: Partial[], opts?: MutationOptions) { + override async updateBatch(data: Partial[], opts?: MutationOptions) { const res = await super.updateBatch(data, opts); await clearSystemCache({ autoPurgeCache: opts?.autoPurgeCache }); return res; } - async updateMany(keys: PrimaryKey[], data: Partial, opts?: MutationOptions) { + override async updateMany(keys: PrimaryKey[], data: Partial, opts?: MutationOptions) { const res = await super.updateMany(keys, data, opts); await clearSystemCache({ autoPurgeCache: opts?.autoPurgeCache }); return res; } - async upsertMany(payloads: Partial[], opts?: MutationOptions) { + override async upsertMany(payloads: Partial[], opts?: MutationOptions) { const res = await super.upsertMany(payloads, opts); await clearSystemCache({ autoPurgeCache: opts?.autoPurgeCache }); return res; } - async deleteMany(keys: PrimaryKey[], opts?: MutationOptions) { + override async deleteMany(keys: PrimaryKey[], opts?: MutationOptions) { const res = await super.deleteMany(keys, opts); await clearSystemCache({ autoPurgeCache: opts?.autoPurgeCache }); return res; diff --git a/api/src/services/revisions.ts b/api/src/services/revisions.ts index 723743a86b..fbdbaaea1c 100644 --- a/api/src/services/revisions.ts +++ b/api/src/services/revisions.ts @@ -12,14 +12,14 @@ export class RevisionsService extends ItemsService { if (!revision) throw new ForbiddenException(); - if (!revision.data) throw new InvalidPayloadException(`Revision doesn't contain data to revert to`); + if (!revision['data']) throw new InvalidPayloadException(`Revision doesn't contain data to revert to`); - const service = new ItemsService(revision.collection, { + const service = new ItemsService(revision['collection'], { accountability: this.accountability, knex: this.knex, schema: this.schema, }); - await service.updateOne(revision.item, revision.data); + await service.updateOne(revision['item'], revision['data']); } } diff --git a/api/src/services/roles.ts b/api/src/services/roles.ts index 7af9a77720..f53a6f65da 100644 --- a/api/src/services/roles.ts +++ b/api/src/services/roles.ts @@ -33,9 +33,9 @@ export class RolesService extends ItemsService { let userKeys: PrimaryKey[] = []; if (Array.isArray(users)) { - userKeys = users.map((user) => (typeof user === 'string' ? user : user.id)).filter((id) => id); + userKeys = users.map((user) => (typeof user === 'string' ? user : user['id'])).filter((id) => id); } else { - userKeys = users.update.map((user) => user.id).filter((id) => id); + userKeys = users.update.map((user) => user['id']).filter((id) => id); } const usersThatWereInRoleBefore = (await this.knex.select('id').from('directus_users').where('role', '=', key)).map( @@ -69,10 +69,10 @@ export class RolesService extends ItemsService { return; } - async updateOne(key: PrimaryKey, data: Record, opts?: MutationOptions): Promise { + override async updateOne(key: PrimaryKey, data: Record, opts?: MutationOptions): Promise { try { if ('users' in data) { - await this.checkForOtherAdminUsers(key, data.users); + await this.checkForOtherAdminUsers(key, data['users']); } } catch (err: any) { (opts || (opts = {})).preMutationException = err; @@ -81,11 +81,11 @@ export class RolesService extends ItemsService { return super.updateOne(key, data, opts); } - async updateBatch(data: Record[], opts?: MutationOptions): Promise { + override async updateBatch(data: Record[], opts?: MutationOptions): Promise { const primaryKeyField = this.schema.collections[this.collection].primary; const keys = data.map((item) => item[primaryKeyField]); - const setsToNoAdmin = data.some((item) => item.admin_access === false); + const setsToNoAdmin = data.some((item) => item['admin_access'] === false); try { if (setsToNoAdmin) { @@ -98,9 +98,13 @@ export class RolesService extends ItemsService { return super.updateBatch(data, opts); } - async updateMany(keys: PrimaryKey[], data: Record, opts?: MutationOptions): Promise { + override async updateMany( + keys: PrimaryKey[], + data: Record, + opts?: MutationOptions + ): Promise { try { - if ('admin_access' in data && data.admin_access === false) { + if ('admin_access' in data && data['admin_access'] === false) { await this.checkForOtherAdminRoles(keys); } } catch (err: any) { @@ -110,12 +114,12 @@ export class RolesService extends ItemsService { return super.updateMany(keys, data, opts); } - async deleteOne(key: PrimaryKey): Promise { + override async deleteOne(key: PrimaryKey): Promise { await this.deleteMany([key]); return key; } - async deleteMany(keys: PrimaryKey[]): Promise { + override async deleteMany(keys: PrimaryKey[]): Promise { const opts: MutationOptions = {}; try { @@ -182,7 +186,7 @@ export class RolesService extends ItemsService { return keys; } - deleteByQuery(query: Query, opts?: MutationOptions): Promise { + override deleteByQuery(query: Query, opts?: MutationOptions): Promise { return super.deleteByQuery(query, opts); } } diff --git a/api/src/services/server.ts b/api/src/services/server.ts index d6aa7df487..04856d4e5d 100644 --- a/api/src/services/server.ts +++ b/api/src/services/server.ts @@ -49,44 +49,44 @@ export class ServerService { ], }); - info.project = projectInfo; + info['project'] = projectInfo; if (this.accountability?.user) { - if (env.RATE_LIMITER_ENABLED) { - info.rateLimit = { - points: env.RATE_LIMITER_POINTS, - duration: env.RATE_LIMITER_DURATION, + if (env['RATE_LIMITER_ENABLED']) { + info['rateLimit'] = { + points: env['RATE_LIMITER_POINTS'], + duration: env['RATE_LIMITER_DURATION'], }; } else { - info.rateLimit = false; + info['rateLimit'] = false; } - if (env.RATE_LIMITER_GLOBAL_ENABLED) { - info.rateLimitGlobal = { - points: env.RATE_LIMITER_GLOBAL_POINTS, - duration: env.RATE_LIMITER_GLOBAL_DURATION, + if (env['RATE_LIMITER_GLOBAL_ENABLED']) { + info['rateLimitGlobal'] = { + points: env['RATE_LIMITER_GLOBAL_POINTS'], + duration: env['RATE_LIMITER_GLOBAL_DURATION'], }; } else { - info.rateLimitGlobal = false; + info['rateLimitGlobal'] = false; } - info.flows = { - execAllowedModules: env.FLOWS_EXEC_ALLOWED_MODULES ? toArray(env.FLOWS_EXEC_ALLOWED_MODULES) : [], + info['flows'] = { + execAllowedModules: env['FLOWS_EXEC_ALLOWED_MODULES'] ? toArray(env['FLOWS_EXEC_ALLOWED_MODULES']) : [], }; } if (this.accountability?.admin === true) { const { osType, osVersion } = getOSInfo(); - info.directus = { + info['directus'] = { version, }; - info.node = { + info['node'] = { version: process.versions.node, uptime: Math.round(process.uptime()), }; - info.os = { + info['os'] = { type: osType, version: osVersion, uptime: Math.round(os.uptime()), @@ -124,7 +124,7 @@ export class ServerService { const data: HealthData = { status: 'ok', releaseId: version, - serviceId: env.KEY, + serviceId: env['KEY'], checks: merge( ...(await Promise.all([ testDatabase(), @@ -166,7 +166,7 @@ export class ServerService { async function testDatabase(): Promise> { const database = getDatabase(); - const client = env.DB_CLIENT; + const client = env['DB_CLIENT']; const checks: Record = {}; @@ -178,7 +178,7 @@ export class ServerService { componentType: 'datastore', observedUnit: 'ms', observedValue: 0, - threshold: env.DB_HEALTHCHECK_THRESHOLD ? +env.DB_HEALTHCHECK_THRESHOLD : 150, + threshold: env['DB_HEALTHCHECK_THRESHOLD'] ? +env['DB_HEALTHCHECK_THRESHOLD'] : 150, }, ]; @@ -221,7 +221,7 @@ export class ServerService { } async function testCache(): Promise> { - if (env.CACHE_ENABLED !== true) { + if (env['CACHE_ENABLED'] !== true) { return {}; } @@ -234,7 +234,7 @@ export class ServerService { componentType: 'cache', observedValue: 0, observedUnit: 'ms', - threshold: env.CACHE_HEALTHCHECK_THRESHOLD ? +env.CACHE_HEALTHCHECK_THRESHOLD : 150, + threshold: env['CACHE_HEALTHCHECK_THRESHOLD'] ? +env['CACHE_HEALTHCHECK_THRESHOLD'] : 150, }, ], }; @@ -263,7 +263,7 @@ export class ServerService { } async function testRateLimiter(): Promise> { - if (env.RATE_LIMITER_ENABLED !== true) { + if (env['RATE_LIMITER_ENABLED'] !== true) { return {}; } @@ -274,7 +274,7 @@ export class ServerService { componentType: 'ratelimiter', observedValue: 0, observedUnit: 'ms', - threshold: env.RATE_LIMITER_HEALTHCHECK_THRESHOLD ? +env.RATE_LIMITER_HEALTHCHECK_THRESHOLD : 150, + threshold: env['RATE_LIMITER_HEALTHCHECK_THRESHOLD'] ? +env['RATE_LIMITER_HEALTHCHECK_THRESHOLD'] : 150, }, ], }; @@ -303,7 +303,7 @@ export class ServerService { } async function testRateLimiterGlobal(): Promise> { - if (env.RATE_LIMITER_GLOBAL_ENABLED !== true) { + if (env['RATE_LIMITER_GLOBAL_ENABLED'] !== true) { return {}; } @@ -314,8 +314,8 @@ export class ServerService { componentType: 'ratelimiter', observedValue: 0, observedUnit: 'ms', - threshold: env.RATE_LIMITER_GLOBAL_HEALTHCHECK_THRESHOLD - ? +env.RATE_LIMITER_GLOBAL_HEALTHCHECK_THRESHOLD + threshold: env['RATE_LIMITER_GLOBAL_HEALTHCHECK_THRESHOLD'] + ? +env['RATE_LIMITER_GLOBAL_HEALTHCHECK_THRESHOLD'] : 150, }, ], @@ -350,7 +350,7 @@ export class ServerService { const checks: Record = {}; - for (const location of toArray(env.STORAGE_LOCATIONS)) { + for (const location of toArray(env['STORAGE_LOCATIONS'])) { const disk = storage.location(location); const envThresholdKey = `STORAGE_${location}_HEALTHCHECK_THRESHOLD`.toUpperCase(); checks[`storage:${location}:responseTime`] = [ diff --git a/api/src/services/shares.ts b/api/src/services/shares.ts index ad769f1080..7920595d52 100644 --- a/api/src/services/shares.ts +++ b/api/src/services/shares.ts @@ -33,8 +33,8 @@ export class SharesService extends ItemsService { }); } - async createOne(data: Partial, opts?: MutationOptions): Promise { - await this.authorizationService.checkAccess('share', data.collection, data.item); + override async createOne(data: Partial, opts?: MutationOptions): Promise { + await this.authorizationService.checkAccess('share', data['collection'], data['item']); return super.createOne(data, opts); } @@ -54,7 +54,7 @@ export class SharesService extends ItemsService { share_password: 'password', }) .from('directus_shares') - .where('id', payload.share) + .where('id', payload['share']) .andWhere((subQuery) => { subQuery.whereNull('date_end').orWhere('date_end', '>=', new Date()); }) @@ -70,7 +70,7 @@ export class SharesService extends ItemsService { throw new InvalidCredentialsException(); } - if (record.share_password && !(await argon2.verify(record.share_password, payload.password))) { + if (record.share_password && !(await argon2.verify(record.share_password, payload['password']))) { throw new InvalidCredentialsException(); } @@ -89,13 +89,13 @@ export class SharesService extends ItemsService { }, }; - const accessToken = jwt.sign(tokenPayload, env.SECRET as string, { - expiresIn: env.ACCESS_TOKEN_TTL, + const accessToken = jwt.sign(tokenPayload, env['SECRET'] as string, { + expiresIn: env['ACCESS_TOKEN_TTL'], issuer: 'directus', }); const refreshToken = nanoid(64); - const refreshTokenExpiration = new Date(Date.now() + getMilliseconds(env.REFRESH_TOKEN_TTL, 0)); + const refreshTokenExpiration = new Date(Date.now() + getMilliseconds(env['REFRESH_TOKEN_TTL'], 0)); await this.knex('directus_sessions').insert({ token: refreshToken, @@ -111,7 +111,7 @@ export class SharesService extends ItemsService { return { accessToken, refreshToken, - expires: getMilliseconds(env.ACCESS_TOKEN_TTL), + expires: getMilliseconds(env['ACCESS_TOKEN_TTL']), }; } @@ -138,9 +138,9 @@ export class SharesService extends ItemsService { const message = ` Hello! -${userName(userInfo)} has invited you to view an item in ${share.collection}. +${userName(userInfo)} has invited you to view an item in ${share['collection']}. -[Open](${new Url(env.PUBLIC_URL).addPath('admin', 'shared', payload.share).toString()}) +[Open](${new Url(env['PUBLIC_URL']).addPath('admin', 'shared', payload.share).toString()}) `; for (const email of payload.emails) { diff --git a/api/src/services/specifications.ts b/api/src/services/specifications.ts index 5936b67257..788b3794da 100644 --- a/api/src/services/specifications.ts +++ b/api/src/services/specifications.ts @@ -109,7 +109,7 @@ class OASSpecsService implements SpecificationSubService { }, servers: [ { - url: env.PUBLIC_URL, + url: env['PUBLIC_URL'], description: 'Your current Directus instance.', }, ], @@ -224,7 +224,7 @@ class OASSpecsService implements SpecificationSubService { { description: listBase[method].description.replace('item', collection + ' item'), tags: [tag.name], - parameters: 'parameters' in listBase ? this.filterCollectionFromParams(listBase['parameters']) : [], + parameters: 'parameters' in listBase ? this.filterCollectionFromParams(listBase.parameters) : [], operationId: `${this.getActionForMethod(method)}${tag.name}`, requestBody: ['get', 'delete'].includes(method) ? undefined @@ -270,6 +270,7 @@ class OASSpecsService implements SpecificationSubService { }, (obj, src) => { if (Array.isArray(obj)) return obj.concat(src); + return undefined; } ); } @@ -281,8 +282,7 @@ class OASSpecsService implements SpecificationSubService { description: detailBase[method].description.replace('item', collection + ' item'), tags: [tag.name], operationId: `${this.getActionForMethod(method)}Single${tag.name}`, - parameters: - 'parameters' in detailBase ? this.filterCollectionFromParams(detailBase['parameters']) : [], + parameters: 'parameters' in detailBase ? this.filterCollectionFromParams(detailBase.parameters) : [], requestBody: ['get', 'delete'].includes(method) ? undefined : { @@ -315,6 +315,7 @@ class OASSpecsService implements SpecificationSubService { }, (obj, src) => { if (Array.isArray(obj)) return obj.concat(src); + return undefined; } ); } diff --git a/api/src/services/users.ts b/api/src/services/users.ts index b445dd6c8d..12834b1a77 100644 --- a/api/src/services/users.ts +++ b/api/src/services/users.ts @@ -1,8 +1,7 @@ import { FailedValidationException } from '@directus/shared/exceptions'; -import type { Accountability, Query, SchemaOverview } from '@directus/shared/types'; +import type { Query } from '@directus/shared/types'; import { getSimpleHash, toArray } from '@directus/shared/utils'; import jwt from 'jsonwebtoken'; -import type { Knex } from 'knex'; import { cloneDeep } from 'lodash'; import { performance } from 'perf_hooks'; import getDatabase from '../database'; @@ -18,10 +17,6 @@ import { MailService } from './mail'; import { SettingsService } from './settings'; export class UsersService extends ItemsService { - knex: Knex; - accountability: Accountability | null; - schema: SchemaOverview; - constructor(options: AbstractServiceOptions) { super('directus_users', options); @@ -142,7 +137,7 @@ export class UsersService extends ItemsService { /** * Create a new user */ - async createOne(data: Partial, opts?: MutationOptions): Promise { + override async createOne(data: Partial, opts?: MutationOptions): Promise { const result = await this.createMany([data], opts); return result[0]; } @@ -150,9 +145,9 @@ export class UsersService extends ItemsService { /** * Create multiple new users */ - async createMany(data: Partial[], opts?: MutationOptions): Promise { - const emails = data.map((payload) => payload.email).filter((email) => email); - const passwords = data.map((payload) => payload.password).filter((password) => password); + override async createMany(data: Partial[], opts?: MutationOptions): Promise { + const emails = data['map']((payload) => payload['email']).filter((email) => email); + const passwords = data['map']((payload) => payload['password']).filter((password) => password); try { if (emails.length) { @@ -172,7 +167,7 @@ export class UsersService extends ItemsService { /** * Update many users by query */ - async updateByQuery(query: Query, data: Partial, opts?: MutationOptions): Promise { + override async updateByQuery(query: Query, data: Partial, opts?: MutationOptions): Promise { const keys = await this.getKeysByQuery(query); return keys.length ? await this.updateMany(keys, data, opts) : []; } @@ -180,12 +175,12 @@ export class UsersService extends ItemsService { /** * Update a single user by primary key */ - async updateOne(key: PrimaryKey, data: Partial, opts?: MutationOptions): Promise { + override async updateOne(key: PrimaryKey, data: Partial, opts?: MutationOptions): Promise { await this.updateMany([key], data, opts); return key; } - async updateBatch(data: Partial[], opts?: MutationOptions): Promise { + override async updateBatch(data: Partial[], opts?: MutationOptions): Promise { const primaryKeyField = this.schema.collections[this.collection].primary; const keys: PrimaryKey[] = []; @@ -209,11 +204,11 @@ export class UsersService extends ItemsService { /** * Update many users by primary key */ - async updateMany(keys: PrimaryKey[], data: Partial, opts?: MutationOptions): Promise { + override async updateMany(keys: PrimaryKey[], data: Partial, opts?: MutationOptions): Promise { try { - if (data.role) { - // data.role will be an object with id with GraphQL mutations - const roleId = data.role?.id ?? data.role; + if (data['role']) { + // data['role'] will be an object with id with GraphQL mutations + const roleId = data['role']?.id ?? data['role']; const newRole = await this.knex.select('admin_access').from('directus_roles').where('id', roleId).first(); @@ -222,43 +217,43 @@ export class UsersService extends ItemsService { } } - if (data.status !== undefined && data.status !== 'active') { + if (data['status'] !== undefined && data['status'] !== 'active') { await this.checkRemainingActiveAdmin(keys); } - if (data.email) { + if (data['email']) { if (keys.length > 1) { throw new RecordNotUniqueException('email', { collection: 'directus_users', field: 'email', - invalid: data.email, + invalid: data['email'], }); } - await this.checkUniqueEmails([data.email], keys[0]); + await this.checkUniqueEmails([data['email']], keys[0]); } - if (data.password) { - await this.checkPasswordPolicy([data.password]); + if (data['password']) { + await this.checkPasswordPolicy([data['password']]); } - if (data.tfa_secret !== undefined) { + if (data['tfa_secret'] !== undefined) { throw new InvalidPayloadException(`You can't change the "tfa_secret" value manually.`); } - if (data.provider !== undefined) { + if (data['provider'] !== undefined) { if (this.accountability && this.accountability.admin !== true) { throw new InvalidPayloadException(`You can't change the "provider" value manually.`); } - data.auth_data = null; + data['auth_data'] = null; } - if (data.external_identifier !== undefined) { + if (data['external_identifier'] !== undefined) { if (this.accountability && this.accountability.admin !== true) { throw new InvalidPayloadException(`You can't change the "external_identifier" value manually.`); } - data.auth_data = null; + data['auth_data'] = null; } } catch (err: any) { (opts || (opts = {})).preMutationException = err; @@ -270,7 +265,7 @@ export class UsersService extends ItemsService { /** * Delete a single user by primary key */ - async deleteOne(key: PrimaryKey, opts?: MutationOptions): Promise { + override async deleteOne(key: PrimaryKey, opts?: MutationOptions): Promise { await this.deleteMany([key], opts); return key; } @@ -278,7 +273,7 @@ export class UsersService extends ItemsService { /** * Delete multiple users by primary key */ - async deleteMany(keys: PrimaryKey[], opts?: MutationOptions): Promise { + override async deleteMany(keys: PrimaryKey[], opts?: MutationOptions): Promise { try { await this.checkRemainingAdminExistence(keys); } catch (err: any) { @@ -291,7 +286,7 @@ export class UsersService extends ItemsService { return keys; } - async deleteByQuery(query: Query, opts?: MutationOptions): Promise { + override async deleteByQuery(query: Query, opts?: MutationOptions): Promise { const primaryKeyField = this.schema.collections[this.collection].primary; const readQuery = cloneDeep(query); readQuery.fields = [primaryKeyField]; @@ -314,7 +309,7 @@ export class UsersService extends ItemsService { const opts: MutationOptions = {}; try { - if (url && isUrlAllowed(url, env.USER_INVITE_URL_ALLOW_LIST) === false) { + if (url && isUrlAllowed(url, env['USER_INVITE_URL_ALLOW_LIST']) === false) { throw new InvalidPayloadException(`Url "${url}" can't be used to invite users.`); } } catch (err: any) { @@ -329,9 +324,9 @@ export class UsersService extends ItemsService { for (const email of emails) { const payload = { email, scope: 'invite' }; - const token = jwt.sign(payload, env.SECRET as string, { expiresIn: '7d', issuer: 'directus' }); + const token = jwt.sign(payload, env['SECRET'] as string, { expiresIn: '7d', issuer: 'directus' }); const subjectLine = subject ?? "You've been invited"; - const inviteURL = url ? new Url(url) : new Url(env.PUBLIC_URL).addPath('admin', 'accept-invite'); + const inviteURL = url ? new Url(url) : new Url(env['PUBLIC_URL']).addPath('admin', 'accept-invite'); inviteURL.setQuery('token', token); // Create user first to verify uniqueness @@ -352,7 +347,7 @@ export class UsersService extends ItemsService { } async acceptInvite(token: string, password: string): Promise { - const { email, scope } = jwt.verify(token, env.SECRET as string, { issuer: 'directus' }) as { + const { email, scope } = jwt.verify(token, env['SECRET'] as string, { issuer: 'directus' }) as { email: string; scope: string; }; @@ -389,7 +384,7 @@ export class UsersService extends ItemsService { throw new ForbiddenException(); } - if (url && isUrlAllowed(url, env.PASSWORD_RESET_URL_ALLOW_LIST) === false) { + if (url && isUrlAllowed(url, env['PASSWORD_RESET_URL_ALLOW_LIST']) === false) { throw new InvalidPayloadException(`Url "${url}" can't be used to reset passwords.`); } @@ -400,10 +395,10 @@ export class UsersService extends ItemsService { }); const payload = { email, scope: 'password-reset', hash: getSimpleHash('' + user.password) }; - const token = jwt.sign(payload, env.SECRET as string, { expiresIn: '1d', issuer: 'directus' }); + const token = jwt.sign(payload, env['SECRET'] as string, { expiresIn: '1d', issuer: 'directus' }); const acceptURL = url ? new Url(url).setQuery('token', token).toString() - : new Url(env.PUBLIC_URL).addPath('admin', 'reset-password').setQuery('token', token); + : new Url(env['PUBLIC_URL']).addPath('admin', 'reset-password').setQuery('token', token); const subjectLine = subject ? subject : 'Password Reset Request'; await mailService.send({ @@ -422,7 +417,7 @@ export class UsersService extends ItemsService { } async resetPassword(token: string, password: string): Promise { - const { email, scope, hash } = jwt.verify(token, env.SECRET as string, { issuer: 'directus' }) as { + const { email, scope, hash } = jwt.verify(token, env['SECRET'] as string, { issuer: 'directus' }) as { email: string; scope: string; hash: string; diff --git a/api/src/services/webhooks.ts b/api/src/services/webhooks.ts index 67c3883443..dfa1c97ef6 100644 --- a/api/src/services/webhooks.ts +++ b/api/src/services/webhooks.ts @@ -10,25 +10,25 @@ export class WebhooksService extends ItemsService { this.messenger = getMessenger(); } - async createOne(data: Partial, opts?: MutationOptions): Promise { + override async createOne(data: Partial, opts?: MutationOptions): Promise { const result = await super.createOne(data, opts); this.messenger.publish('webhooks', { type: 'reload' }); return result; } - async createMany(data: Partial[], opts?: MutationOptions): Promise { + override async createMany(data: Partial[], opts?: MutationOptions): Promise { const result = await super.createMany(data, opts); this.messenger.publish('webhooks', { type: 'reload' }); return result; } - async updateMany(keys: PrimaryKey[], data: Partial, opts?: MutationOptions): Promise { + override async updateMany(keys: PrimaryKey[], data: Partial, opts?: MutationOptions): Promise { const result = await super.updateMany(keys, data, opts); this.messenger.publish('webhooks', { type: 'reload' }); return result; } - async deleteMany(keys: PrimaryKey[], opts?: MutationOptions): Promise { + override async deleteMany(keys: PrimaryKey[], opts?: MutationOptions): Promise { const result = await super.deleteMany(keys, opts); this.messenger.publish('webhooks', { type: 'reload' }); return result; diff --git a/api/src/storage/register-locations.ts b/api/src/storage/register-locations.ts index 967f125a53..3da54b2892 100644 --- a/api/src/storage/register-locations.ts +++ b/api/src/storage/register-locations.ts @@ -8,7 +8,7 @@ import { getConfigFromEnv } from '../utils/get-config-from-env'; export const registerLocations = async (storage: StorageManager) => { const env = getEnv(); - const locations = toArray(env.STORAGE_LOCATIONS); + const locations = toArray(env['STORAGE_LOCATIONS']); locations.forEach((location: string) => { location = location.trim(); diff --git a/api/src/utils/apply-query.ts b/api/src/utils/apply-query.ts index 7d1f6667a2..1e81a13bf3 100644 --- a/api/src/utils/apply-query.ts +++ b/api/src/utils/apply-query.ts @@ -310,6 +310,8 @@ export function applySort( rootQuery.clear('order'); rootQuery.orderBy(sortRecords); + + return undefined; } export function applyLimit(knex: Knex, rootQuery: Knex.QueryBuilder, limit: any) { diff --git a/api/src/utils/dynamic-import.ts b/api/src/utils/dynamic-import.ts index 80ef53fbaa..ffd30cf008 100644 --- a/api/src/utils/dynamic-import.ts +++ b/api/src/utils/dynamic-import.ts @@ -1,3 +1,3 @@ export const dynamicImport = async (mod: string) => { - return process.env.VITEST ? await import(mod) : require(mod); + return process.env['VITEST'] ? await import(mod) : require(mod); }; diff --git a/api/src/utils/generate-hash.ts b/api/src/utils/generate-hash.ts index d474cd170f..5f11482a3c 100644 --- a/api/src/utils/generate-hash.ts +++ b/api/src/utils/generate-hash.ts @@ -5,6 +5,6 @@ export function generateHash(stringToHash: string): Promise { const argon2HashConfigOptions = getConfigFromEnv('HASH_', 'HASH_RAW'); // Disallow the HASH_RAW option, see https://github.com/directus/directus/discussions/7670#discussioncomment-1255805 // associatedData, if specified, must be passed as a Buffer to argon2.hash, see https://github.com/ranisalt/node-argon2/wiki/Options#associateddata 'associatedData' in argon2HashConfigOptions && - (argon2HashConfigOptions.associatedData = Buffer.from(argon2HashConfigOptions.associatedData)); + (argon2HashConfigOptions['associatedData'] = Buffer.from(argon2HashConfigOptions['associatedData'])); return argon2.hash(stringToHash, argon2HashConfigOptions); } diff --git a/api/src/utils/get-ast-from-query.ts b/api/src/utils/get-ast-from-query.ts index 447aeaa8b1..64b2094722 100644 --- a/api/src/utils/get-ast-from-query.ts +++ b/api/src/utils/get-ast-from-query.ts @@ -248,7 +248,7 @@ export default async function getASTFromQuery( } // update query alias for children parseFields - const deepAlias = getDeepQuery(deep?.[fieldKey] || {})?.alias; + const deepAlias = getDeepQuery(deep?.[fieldKey] || {})?.['alias']; if (!isEmpty(deepAlias)) query.alias = deepAlias; child = { diff --git a/api/src/utils/get-auth-providers.ts b/api/src/utils/get-auth-providers.ts index e380a5d20b..678cda4727 100644 --- a/api/src/utils/get-auth-providers.ts +++ b/api/src/utils/get-auth-providers.ts @@ -9,7 +9,7 @@ interface AuthProvider { } export function getAuthProviders(): AuthProvider[] { - return toArray(env.AUTH_PROVIDERS) + return toArray(env['AUTH_PROVIDERS']) .filter((provider) => provider && env[`AUTH_${provider.toUpperCase()}_DRIVER`]) .map((provider) => ({ name: provider, diff --git a/api/src/utils/get-cache-headers.ts b/api/src/utils/get-cache-headers.ts index a08c8a4362..403030dc46 100644 --- a/api/src/utils/get-cache-headers.ts +++ b/api/src/utils/get-cache-headers.ts @@ -23,7 +23,7 @@ export function getCacheControlHeader( if (ttl === undefined || ttl < 0) return 'no-cache'; // When the API cache can invalidate at any moment - if (globalCacheSettings && env.CACHE_AUTO_PURGE === true) return 'no-cache'; + if (globalCacheSettings && env['CACHE_AUTO_PURGE'] === true) return 'no-cache'; const headerValues = []; @@ -39,8 +39,8 @@ export function getCacheControlHeader( headerValues.push(`max-age=${ttlSeconds}`); // When the s-maxage flag should be included - if (globalCacheSettings && Number.isInteger(env.CACHE_CONTROL_S_MAXAGE) && env.CACHE_CONTROL_S_MAXAGE >= 0) { - headerValues.push(`s-maxage=${env.CACHE_CONTROL_S_MAXAGE}`); + if (globalCacheSettings && Number.isInteger(env['CACHE_CONTROL_S_MAXAGE']) && env['CACHE_CONTROL_S_MAXAGE'] >= 0) { + headerValues.push(`s-maxage=${env['CACHE_CONTROL_S_MAXAGE']}`); } return headerValues.join(', '); diff --git a/api/src/utils/get-collection-from-alias.ts b/api/src/utils/get-collection-from-alias.ts index 59ab1b1e41..a55acf7d7f 100644 --- a/api/src/utils/get-collection-from-alias.ts +++ b/api/src/utils/get-collection-from-alias.ts @@ -10,4 +10,6 @@ export function getCollectionFromAlias(alias: string, aliasMap: AliasMap): strin return aliasValue.collection; } } + + return undefined; } diff --git a/api/src/utils/get-default-value.ts b/api/src/utils/get-default-value.ts index e727443bb2..10023fa96e 100644 --- a/api/src/utils/get-default-value.ts +++ b/api/src/utils/get-default-value.ts @@ -50,7 +50,7 @@ function castToObject(value: any): any | any[] { try { return parseJSON(value); } catch (err: any) { - if (env.NODE_ENV === 'development') { + if (env['NODE_ENV'] === 'development') { logger.error(err); } diff --git a/api/src/utils/get-ip-from-req.ts b/api/src/utils/get-ip-from-req.ts index 1c0c429234..86888d8655 100644 --- a/api/src/utils/get-ip-from-req.ts +++ b/api/src/utils/get-ip-from-req.ts @@ -6,8 +6,8 @@ import logger from '../logger'; export function getIPFromReq(req: Request): string { let ip = req.ip; - if (env.IP_CUSTOM_HEADER) { - const customIPHeaderValue = req.get(env.IP_CUSTOM_HEADER) as unknown; + if (env['IP_CUSTOM_HEADER']) { + const customIPHeaderValue = req.get(env['IP_CUSTOM_HEADER']) as unknown; if (typeof customIPHeaderValue === 'string' && isIP(customIPHeaderValue) !== 0) { ip = customIPHeaderValue; diff --git a/api/src/utils/get-permissions.ts b/api/src/utils/get-permissions.ts index 3a2a65a6d3..870653c734 100644 --- a/api/src/utils/get-permissions.ts +++ b/api/src/utils/get-permissions.ts @@ -21,7 +21,7 @@ export async function getPermissions(accountability: Accountability, schema: Sch const { user, role, app, admin, share_scope } = accountability; const cacheKey = `permissions-${hash({ user, role, app, admin, share_scope })}`; - if (cache && env.CACHE_PERMISSIONS !== false) { + if (cache && env['CACHE_PERMISSIONS'] !== false) { let cachedPermissions; try { @@ -31,23 +31,23 @@ export async function getPermissions(accountability: Accountability, schema: Sch } if (cachedPermissions) { - if (!cachedPermissions.containDynamicData) { - return processPermissions(accountability, cachedPermissions.permissions, {}); + if (!cachedPermissions['containDynamicData']) { + return processPermissions(accountability, cachedPermissions['permissions'], {}); } const cachedFilterContext = await getCacheValue( cache, - `filterContext-${hash({ user, role, permissions: cachedPermissions.permissions })}` + `filterContext-${hash({ user, role, permissions: cachedPermissions['permissions'] })}` ); if (cachedFilterContext) { - return processPermissions(accountability, cachedPermissions.permissions, cachedFilterContext); + return processPermissions(accountability, cachedPermissions['permissions'], cachedFilterContext); } else { const { permissions: parsedPermissions, requiredPermissionData, containDynamicData, - } = parsePermissions(cachedPermissions.permissions); + } = parsePermissions(cachedPermissions['permissions']); permissions = parsedPermissions; @@ -55,7 +55,7 @@ export async function getPermissions(accountability: Accountability, schema: Sch ? await getFilterContext(schema, accountability, requiredPermissionData) : {}; - if (containDynamicData && env.CACHE_ENABLED !== false) { + if (containDynamicData && env['CACHE_ENABLED'] !== false) { await setCacheValue(cache, `filterContext-${hash({ user, role, permissions })}`, filterContext); } @@ -99,10 +99,10 @@ export async function getPermissions(accountability: Accountability, schema: Sch ? await getFilterContext(schema, accountability, requiredPermissionData) : {}; - if (cache && env.CACHE_PERMISSIONS !== false) { + if (cache && env['CACHE_PERMISSIONS'] !== false) { await setSystemCache(cacheKey, { permissions, containDynamicData }); - if (containDynamicData && env.CACHE_ENABLED !== false) { + if (containDynamicData && env['CACHE_ENABLED'] !== false) { await setCacheValue(cache, `filterContext-${hash({ user, role, permissions })}`, filterContext); } } @@ -179,13 +179,13 @@ async function getFilterContext(schema: SchemaOverview, accountability: Accounta const filterContext: Record = {}; if (accountability.user && requiredPermissionData.$CURRENT_USER.length > 0) { - filterContext.$CURRENT_USER = await usersService.readOne(accountability.user, { + filterContext['$CURRENT_USER'] = await usersService.readOne(accountability.user, { fields: requiredPermissionData.$CURRENT_USER, }); } if (accountability.role && requiredPermissionData.$CURRENT_ROLE.length > 0) { - filterContext.$CURRENT_ROLE = await rolesService.readOne(accountability.role, { + filterContext['$CURRENT_ROLE'] = await rolesService.readOne(accountability.role, { fields: requiredPermissionData.$CURRENT_ROLE, }); } diff --git a/api/src/utils/get-schema.ts b/api/src/utils/get-schema.ts index da7479c573..f6b0803f77 100644 --- a/api/src/utils/get-schema.ts +++ b/api/src/utils/get-schema.ts @@ -28,7 +28,7 @@ export async function getSchema(options?: { let result: SchemaOverview; - if (!options?.bypassCache && env.CACHE_SCHEMA !== false) { + if (!options?.bypassCache && env['CACHE_SCHEMA'] !== false) { let cachedSchema; try { @@ -74,7 +74,7 @@ async function getDatabaseSchema( ]; for (const [collection, info] of Object.entries(schemaOverview)) { - if (toArray(env.DB_EXCLUDE_TABLES).includes(collection)) { + if (toArray(env['DB_EXCLUDE_TABLES']).includes(collection)) { logger.trace(`Collection "${collection}" is configured to be excluded and will be ignored`); continue; } diff --git a/api/src/utils/is-url-allowed.ts b/api/src/utils/is-url-allowed.ts index b07ca72951..c56981baab 100644 --- a/api/src/utils/is-url-allowed.ts +++ b/api/src/utils/is-url-allowed.ts @@ -10,14 +10,18 @@ export default function isUrlAllowed(url: string, allowList: string | string[]): if (urlAllowList.includes(url)) return true; - const parsedWhitelist = urlAllowList.map((allowedURL) => { - try { - const { hostname, pathname } = new URL(allowedURL); - return hostname + pathname; - } catch { - logger.warn(`Invalid URL used "${url}"`); - } - }); + const parsedWhitelist = urlAllowList + .map((allowedURL) => { + try { + const { hostname, pathname } = new URL(allowedURL); + return hostname + pathname; + } catch { + logger.warn(`Invalid URL used "${url}"`); + } + + return null; + }) + .filter((f) => f) as string[]; try { const { hostname, pathname } = new URL(url); diff --git a/api/src/utils/redact-header-cookies.test.ts b/api/src/utils/redact-header-cookies.test.ts index 17a9aad99e..475bde5446 100644 --- a/api/src/utils/redact-header-cookies.test.ts +++ b/api/src/utils/redact-header-cookies.test.ts @@ -5,7 +5,7 @@ import { redactHeaderCookie } from './redact-header-cookies'; describe('redactHeaderCookie', () => { describe('Given auth cookies', () => { test('When it finds a refresh_token, it should redact the value', () => { - const tokenKey = env.REFRESH_TOKEN_COOKIE_NAME; + const tokenKey = env['REFRESH_TOKEN_COOKIE_NAME']; const cookieHeader = `${tokenKey}=shh;`; const cookieNames = [`${tokenKey}`]; @@ -21,19 +21,19 @@ describe('redactHeaderCookie', () => { expect(redactedCookie).toBe(`${tokenKey}=--redacted--;`); }); test('When it finds both an access_token and refresh_token, it should redact both values', () => { - const cookieHeader = `access_token=secret; ${env.REFRESH_TOKEN_COOKIE_NAME}=shhhhhhh; randomCookie=Erdtree;`; - const cookieNames = ['access_token', `${env.REFRESH_TOKEN_COOKIE_NAME}`]; + const cookieHeader = `access_token=secret; ${env['REFRESH_TOKEN_COOKIE_NAME']}=shhhhhhh; randomCookie=Erdtree;`; + const cookieNames = ['access_token', `${env['REFRESH_TOKEN_COOKIE_NAME']}`]; const redactedCookie = redactHeaderCookie(cookieHeader, cookieNames); expect(redactedCookie).toBe( - `access_token=--redacted--; ${env.REFRESH_TOKEN_COOKIE_NAME}=--redacted--; randomCookie=Erdtree;` + `access_token=--redacted--; ${env['REFRESH_TOKEN_COOKIE_NAME']}=--redacted--; randomCookie=Erdtree;` ); }); }); describe('Given negligible cookies', () => { test('It should return the orignal value', () => { const originalCookie = `Crown=Swords; Hail=Sithis;`; - const cookieNames = [env.REFRESH_TOKEN_COOKIE_NAME, 'access_token']; + const cookieNames = [env['REFRESH_TOKEN_COOKIE_NAME'], 'access_token']; const redactedCookie = redactHeaderCookie(originalCookie, cookieNames); expect(redactedCookie).toBe(originalCookie); diff --git a/api/src/utils/sanitize-query.ts b/api/src/utils/sanitize-query.ts index 3419c466d2..09d1f1b425 100644 --- a/api/src/utils/sanitize-query.ts +++ b/api/src/utils/sanitize-query.ts @@ -7,62 +7,62 @@ import { Meta } from '../types'; export function sanitizeQuery(rawQuery: Record, accountability?: Accountability | null): Query { const query: Query = {}; - if (rawQuery.limit !== undefined) { - const limit = sanitizeLimit(rawQuery.limit); + if (rawQuery['limit'] !== undefined) { + const limit = sanitizeLimit(rawQuery['limit']); if (typeof limit === 'number') { query.limit = limit; } } - if (rawQuery.fields) { - query.fields = sanitizeFields(rawQuery.fields); + if (rawQuery['fields']) { + query.fields = sanitizeFields(rawQuery['fields']); } - if (rawQuery.groupBy) { - query.group = sanitizeFields(rawQuery.groupBy); + if (rawQuery['groupBy']) { + query.group = sanitizeFields(rawQuery['groupBy']); } - if (rawQuery.aggregate) { - query.aggregate = sanitizeAggregate(rawQuery.aggregate); + if (rawQuery['aggregate']) { + query.aggregate = sanitizeAggregate(rawQuery['aggregate']); } - if (rawQuery.sort) { - query.sort = sanitizeSort(rawQuery.sort); + if (rawQuery['sort']) { + query.sort = sanitizeSort(rawQuery['sort']); } - if (rawQuery.filter) { - query.filter = sanitizeFilter(rawQuery.filter, accountability || null); + if (rawQuery['filter']) { + query.filter = sanitizeFilter(rawQuery['filter'], accountability || null); } - if (rawQuery.offset) { - query.offset = sanitizeOffset(rawQuery.offset); + if (rawQuery['offset']) { + query.offset = sanitizeOffset(rawQuery['offset']); } - if (rawQuery.page) { - query.page = sanitizePage(rawQuery.page); + if (rawQuery['page']) { + query.page = sanitizePage(rawQuery['page']); } - if (rawQuery.meta) { - (query as any).meta = sanitizeMeta(rawQuery.meta); + if (rawQuery['meta']) { + (query as any).meta = sanitizeMeta(rawQuery['meta']); } - if (rawQuery.search && typeof rawQuery.search === 'string') { - query.search = rawQuery.search; + if (rawQuery['search'] && typeof rawQuery['search'] === 'string') { + query.search = rawQuery['search']; } - if (rawQuery.export) { - query.export = rawQuery.export as 'json' | 'csv'; + if (rawQuery['export']) { + query.export = rawQuery['export'] as 'json' | 'csv'; } - if (rawQuery.deep as Record) { + if (rawQuery['deep'] as Record) { if (!query.deep) query.deep = {}; - query.deep = sanitizeDeep(rawQuery.deep, accountability); + query.deep = sanitizeDeep(rawQuery['deep'], accountability); } - if (rawQuery.alias) { - query.alias = sanitizeAlias(rawQuery.alias); + if (rawQuery['alias']) { + query.alias = sanitizeAlias(rawQuery['alias']); } return query; diff --git a/api/src/utils/should-skip-cache.ts b/api/src/utils/should-skip-cache.ts index 3cc2d09a37..936b3c1cab 100644 --- a/api/src/utils/should-skip-cache.ts +++ b/api/src/utils/should-skip-cache.ts @@ -12,10 +12,10 @@ export function shouldSkipCache(req: Request): boolean { const env = getEnv(); // Always skip cache for requests coming from the data studio based on Referer header - const adminUrl = new Url(env.PUBLIC_URL).addPath('admin').toString(); + const adminUrl = new Url(env['PUBLIC_URL']).addPath('admin').toString(); if (req.get('Referer')?.startsWith(adminUrl)) return true; - if (env.CACHE_SKIP_ALLOWED && req.get('cache-control')?.includes('no-store')) return true; + if (env['CACHE_SKIP_ALLOWED'] && req.get('cache-control')?.includes('no-store')) return true; return false; } diff --git a/api/src/utils/track.ts b/api/src/utils/track.ts index 597c79824a..1682bf3e81 100644 --- a/api/src/utils/track.ts +++ b/api/src/utils/track.ts @@ -10,13 +10,13 @@ import { getMilliseconds } from './get-milliseconds'; export async function track(event: string): Promise { const axios = (await import('axios')).default; - if (env.TELEMETRY !== false) { + if (env['TELEMETRY'] !== false) { const info = await getEnvInfo(event); try { await axios.post('https://telemetry.directus.io/', info); } catch (err: any) { - if (env.NODE_ENV === 'development') { + if (env['NODE_ENV'] === 'development') { logger.error(err); } } @@ -27,9 +27,9 @@ async function getEnvInfo(event: string) { return { version: version, event: event, - project_id: env.KEY, + project_id: env['KEY'], machine_id: await machineId(), - environment: env.NODE_ENV, + environment: env['NODE_ENV'], stack: 'node', os: { arch: os.arch(), @@ -37,37 +37,37 @@ async function getEnvInfo(event: string) { release: os.release(), }, rate_limiter: { - enabled: env.RATE_LIMITER_ENABLED, - points: +env.RATE_LIMITER_POINTS, - duration: +env.RATE_LIMITER_DURATION, - store: env.RATE_LIMITER_STORE, + enabled: env['RATE_LIMITER_ENABLED'], + points: +env['RATE_LIMITER_POINTS'], + duration: +env['RATE_LIMITER_DURATION'], + store: env['RATE_LIMITER_STORE'], }, cache: { - enabled: env.CACHE_ENABLED, - ttl: getMilliseconds(env.CACHE_TTL), - store: env.CACHE_STORE, + enabled: env['CACHE_ENABLED'], + ttl: getMilliseconds(env['CACHE_TTL']), + store: env['CACHE_STORE'], }, storage: { drivers: getStorageDrivers(), }, cors: { - enabled: env.CORS_ENABLED, + enabled: env['CORS_ENABLED'], }, email: { - transport: env.EMAIL_TRANSPORT, + transport: env['EMAIL_TRANSPORT'], }, auth: { - providers: toArray(env.AUTH_PROVIDERS) + providers: toArray(env['AUTH_PROVIDERS']) .map((v: string) => v.trim()) .filter((v: string) => v), }, - db_client: env.DB_CLIENT, + db_client: env['DB_CLIENT'], }; } function getStorageDrivers() { const drivers: string[] = []; - const locations = toArray(env.STORAGE_LOCATIONS) + const locations = toArray(env['STORAGE_LOCATIONS']) .map((v: string) => v.trim()) .filter((v: string) => v); diff --git a/api/src/utils/validate-query.ts b/api/src/utils/validate-query.ts index e1d6fa3227..036310fc4e 100644 --- a/api/src/utils/validate-query.ts +++ b/api/src/utils/validate-query.ts @@ -173,7 +173,7 @@ function validateAlias(alias: any) { } function validateRelationalDepth(query: Query) { - const maxRelationalDepth = Number(env.MAX_RELATIONAL_DEPTH) > 2 ? Number(env.MAX_RELATIONAL_DEPTH) : 2; + const maxRelationalDepth = Number(env['MAX_RELATIONAL_DEPTH']) > 2 ? Number(env['MAX_RELATIONAL_DEPTH']) : 2; // Process the fields in the same way as api/src/utils/get-ast-from-query.ts let fields = ['*']; diff --git a/api/src/utils/validate-storage.ts b/api/src/utils/validate-storage.ts index 2b0cdf8e75..713fa0d08f 100644 --- a/api/src/utils/validate-storage.ts +++ b/api/src/utils/validate-storage.ts @@ -6,31 +6,31 @@ import path from 'path'; import { toArray } from '@directus/shared/utils'; export async function validateStorage(): Promise { - if (env.DB_CLIENT === 'sqlite3') { + if (env['DB_CLIENT'] === 'sqlite3') { try { - await access(path.dirname(env.DB_FILENAME), constants.R_OK | constants.W_OK); + await access(path.dirname(env['DB_FILENAME']), constants.R_OK | constants.W_OK); } catch { logger.warn( - `Directory for SQLite database file (${path.resolve(path.dirname(env.DB_FILENAME))}) is not read/writeable!` + `Directory for SQLite database file (${path.resolve(path.dirname(env['DB_FILENAME']))}) is not read/writeable!` ); } } - const usedStorageDrivers = toArray(env.STORAGE_LOCATIONS).map( + const usedStorageDrivers = toArray(env['STORAGE_LOCATIONS']).map( (location) => env[`STORAGE_${location.toUpperCase()}_DRIVER`] ); if (usedStorageDrivers.includes('local')) { try { - await access(env.STORAGE_LOCAL_ROOT, constants.R_OK | constants.W_OK); + await access(env['STORAGE_LOCAL_ROOT'], constants.R_OK | constants.W_OK); } catch { - logger.warn(`Upload directory (${path.resolve(env.STORAGE_LOCAL_ROOT)}) is not read/writeable!`); + logger.warn(`Upload directory (${path.resolve(env['STORAGE_LOCAL_ROOT'])}) is not read/writeable!`); } } try { - await access(env.EXTENSIONS_PATH, constants.R_OK); + await access(env['EXTENSIONS_PATH'], constants.R_OK); } catch { - logger.warn(`Extensions directory (${path.resolve(env.EXTENSIONS_PATH)}) is not readable!`); + logger.warn(`Extensions directory (${path.resolve(env['EXTENSIONS_PATH'])}) is not readable!`); } } diff --git a/api/src/webhooks.ts b/api/src/webhooks.ts index 2433536a6c..b56481fe16 100644 --- a/api/src/webhooks.ts +++ b/api/src/webhooks.ts @@ -18,7 +18,7 @@ export async function init(): Promise { const messenger = getMessenger(); messenger.subscribe('webhooks', (event) => { - if (event.type === 'reload') { + if (event['type'] === 'reload') { reloadQueue.enqueue(async () => { await reload(); }); @@ -56,7 +56,7 @@ export function unregister(): void { function createHandler(webhook: Webhook, event: string): ActionHandler { return async (meta, context) => { - if (webhook.collections.includes(meta.collection) === false) return; + if (webhook.collections.includes(meta['collection']) === false) return; const axios = await getAxios(); const webhookPayload = { diff --git a/api/tsconfig.json b/api/tsconfig.json index 7543089155..7f2ef3f42f 100644 --- a/api/tsconfig.json +++ b/api/tsconfig.json @@ -6,12 +6,16 @@ "checkJs": true, "declaration": true, "esModuleInterop": true, - "importsNotUsedAsValues": "error", "exactOptionalPropertyTypes": true, + "forceConsistentCasingInFileNames": true, + "importsNotUsedAsValues": "error", "lib": ["es2022"], "module": "commonjs", "moduleResolution": "node16", "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noPropertyAccessFromIndexSignature": true, "noUnusedLocals": true, "outDir": "dist", "resolveJsonModule": true,