From 762b897ab626f16c039cc7035f752a75f72113fa Mon Sep 17 00:00:00 2001 From: Pagan Gazzard Date: Tue, 9 Jul 2024 17:55:16 +0100 Subject: [PATCH] Update @balena/lint and fix warnings Change-type: patch --- init.ts | 4 ++-- package-lock.json | 8 +++---- package.json | 2 +- src/commands/dummy.ts | 2 +- .../cascade-delete/setup-delete-cascade.ts | 4 +++- src/features/contracts/index.ts | 4 +++- src/features/device-heartbeat/index.ts | 17 ++++++------- src/features/device-logs/lib/backends/loki.ts | 20 ++++++++++------ .../device-logs/lib/backends/metrics.ts | 19 +++++++-------- src/features/device-logs/lib/store.ts | 16 +++++-------- src/features/device-logs/lib/supervisor.ts | 4 +++- src/features/device-proxy/device-proxy.ts | 18 +++++++------- src/features/device-types/storage/s3.ts | 2 +- .../devices/hooks/defaults-validation.ts | 2 ++ src/features/registry/middleware.ts | 17 +++++++------ src/features/registry/registry.ts | 4 ++-- src/features/request-logging/index.ts | 10 ++++---- .../vars-schema/hooks/vars-update-trigger.ts | 6 +++-- src/index.ts | 18 ++++++++------ src/infra/auth/api-keys.ts | 2 +- src/infra/auth/auth.ts | 6 ++--- src/infra/auth/jwt-passport.ts | 14 ++++++----- src/infra/auth/permissions.ts | 3 ++- src/infra/cache/multi-level-memoizee.ts | 2 +- src/infra/scheduler/index.ts | 3 +-- src/lib/utils.ts | 1 + src/translations/v6/hooks.ts | 1 + test/03_device-state.ts | 10 ++++---- test/04_session.ts | 7 +++--- test/06_device-log.ts | 4 +++- test/07_versioned-releases.ts | 20 +++++++++------- test/08_api-keys.ts | 8 ++++--- test/09_contracts.ts | 3 ++- test/10_migrations.ts | 24 ++++++++++++------- test/14_release-pinning.ts | 16 ++++++++----- test/17_vpn.ts | 2 +- test/18_resource_filtering.ts | 5 +++- test/21_fleet-target-state.ts | 2 +- test/test-lib/api-helpers.ts | 8 ++++--- test/test-lib/device-type.ts | 3 ++- test/test-lib/fake-device.ts | 4 ++-- test/test-lib/fixtures.ts | 2 +- test/test-lib/init-tests.ts | 2 +- 43 files changed, 189 insertions(+), 140 deletions(-) diff --git a/init.ts b/init.ts index a5051dca60..b2de6396ab 100644 --- a/init.ts +++ b/init.ts @@ -213,14 +213,14 @@ app.set('trust proxy', TRUST_PROXY); const init = async () => { try { - const generateConfig = (process.env.GENERATE_CONFIG || '').trim(); + const generateConfig = (process.env.GENERATE_CONFIG ?? '').trim(); if (generateConfig.length > 0) { await fs.writeFile(generateConfig, JSON.stringify(config, null, '\t')); process.exit(); } const doRunTests = - (process.env.RUN_TESTS || '').trim() === '1' + (process.env.RUN_TESTS ?? '').trim() === '1' ? await import('./test/test-lib/init-tests.js') : undefined; diff --git a/package-lock.json b/package-lock.json index a765b2e90e..c3f6f498a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -100,7 +100,7 @@ "validator": "^13.12.0" }, "devDependencies": { - "@balena/lint": "^8.0.2", + "@balena/lint": "^8.2.2", "@types/chai": "^4.3.16", "@types/mocha": "^10.0.7", "@types/mockery": "^1.4.33", @@ -1389,9 +1389,9 @@ } }, "node_modules/@balena/lint": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@balena/lint/-/lint-8.1.0.tgz", - "integrity": "sha512-4blzQ5cUTv0CzBuz8KGpAAhvWUlEQJbpuIX6jbT4pK/WMarcs88uZ/daGbU9XbCeXkIrD8jKE7WaxyMx1t2NTA==", + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@balena/lint/-/lint-8.2.2.tgz", + "integrity": "sha512-efBaP+gR7HE0UEBMWrzxpkJ65vFFdXCCfj7QU0FrEISaT4C++aUDB1zhPPgzOZWBDP5xolOyQf/wVrZtDFmCPA==", "dev": true, "license": "Apache 2.0", "dependencies": { diff --git a/package.json b/package.json index 241bf8b961..25bfd83f1e 100644 --- a/package.json +++ b/package.json @@ -134,7 +134,7 @@ "validator": "^13.12.0" }, "devDependencies": { - "@balena/lint": "^8.0.2", + "@balena/lint": "^8.2.2", "@types/chai": "^4.3.16", "@types/mocha": "^10.0.7", "@types/mockery": "^1.4.33", diff --git a/src/commands/dummy.ts b/src/commands/dummy.ts index 5f73adf0e0..8405638c97 100644 --- a/src/commands/dummy.ts +++ b/src/commands/dummy.ts @@ -1,6 +1,6 @@ import type { Application } from 'express'; -export async function execute(_app: Application, args: string[]) { +export function execute(_app: Application, args: string[]) { // grab your args as an array, like so... const [arg1] = args; diff --git a/src/features/cascade-delete/setup-delete-cascade.ts b/src/features/cascade-delete/setup-delete-cascade.ts index ccddd56ec4..2c08e9c1a1 100644 --- a/src/features/cascade-delete/setup-delete-cascade.ts +++ b/src/features/cascade-delete/setup-delete-cascade.ts @@ -3,4 +3,6 @@ import { addDeleteHookForDependents } from '../../infra/cascade-delete/index.js' export const setupDeleteCascade = ( resource: string, dependents: Parameters[2], -) => addDeleteHookForDependents('resin', resource, dependents); +) => { + addDeleteHookForDependents('resin', resource, dependents); +}; diff --git a/src/features/contracts/index.ts b/src/features/contracts/index.ts index e15fba4838..2a53bd98a6 100644 --- a/src/features/contracts/index.ts +++ b/src/features/contracts/index.ts @@ -351,7 +351,9 @@ export const startContractSynchronization = _.once(() => { scheduleJob( 'contractSync', '*/5 * * * *', - async () => await synchronizeContracts(contractRepos), + async () => { + await synchronizeContracts(contractRepos); + }, { // The maximum expected amount of time that the job needs to complete // and it should hold the lock to prevent other jobs from starting. diff --git a/src/features/device-heartbeat/index.ts b/src/features/device-heartbeat/index.ts index f2896d8982..32b865213f 100644 --- a/src/features/device-heartbeat/index.ts +++ b/src/features/device-heartbeat/index.ts @@ -188,7 +188,7 @@ export class DeviceOnlineStateManager extends EventEmitter<{ private readonly featureIsEnabled: boolean; - private isConsuming: boolean = false; + private isConsuming = false; private rsmq: RedisSMQ; public constructor() { @@ -215,11 +215,11 @@ export class DeviceOnlineStateManager extends EventEmitter<{ throw err; } }) - .then(() => + .then(() => { this.setupQueueStatsEmitter( DeviceOnlineStateManager.QUEUE_STATS_INTERVAL_MSEC, - ), - ); + ); + }); } private setupQueueStatsEmitter(interval: number) { @@ -301,7 +301,8 @@ export class DeviceOnlineStateManager extends EventEmitter<{ .then(async (msg) => { if (!('id' in msg)) { // no messages to consume, wait a second... - return await setTimeout(1000); + await setTimeout(1000); + return; } const { id, message } = msg; @@ -358,12 +359,12 @@ export class DeviceOnlineStateManager extends EventEmitter<{ ); } }) - .catch((err: Error) => + .catch((err: Error) => { captureException( err, 'An error occurred while consuming API heartbeat state queue', - ), - ) + ); + }) .then(() => this.consume()); return null; diff --git a/src/features/device-logs/lib/backends/loki.ts b/src/features/device-logs/lib/backends/loki.ts index ff89f433ce..5415c65cfe 100644 --- a/src/features/device-logs/lib/backends/loki.ts +++ b/src/features/device-logs/lib/backends/loki.ts @@ -101,7 +101,7 @@ async function assertLokiLogContext( // Mutate so that we don't have to repeatedly amend the same context and instead cache it (ctx as Writable).belongs_to__application = - device?.belongs_to__application!.__id; + device?.belongs_to__application?.__id; return ctx as types.RequiredField; } @@ -139,9 +139,7 @@ export class LokiBackend implements DeviceLogsBackend { ); } - public get available(): boolean { - return true; - } + public readonly available = true; /** * @@ -232,9 +230,17 @@ export class LokiBackend implements DeviceLogsBackend { { deadline: startAt + PUSH_TIMEOUT, }, - (err, response) => (err ? reject(err) : resolve(response)), + (err, response) => { + if (err) { + reject(err); + } else { + resolve(response); + } + }, ); - }).finally(() => updateLokiPushDurationHistogram(Date.now() - startAt)); + }).finally(() => { + updateLokiPushDurationHistogram(Date.now() - startAt); + }); } public async subscribe($ctx: LogContext, subscription: Subscription) { @@ -327,7 +333,7 @@ export class LokiBackend implements DeviceLogsBackend { try { return stream.getEntriesList().map((entry: loki.EntryAdapter) => { const log = JSON.parse(entry.getLine()); - const timestamp = entry.getTimestamp() as loki.Timestamp; + const timestamp = entry.getTimestamp()!; log.nanoTimestamp = BigInt(timestamp.getSeconds()) * 1000000000n + BigInt(timestamp.getNanos()); diff --git a/src/features/device-logs/lib/backends/metrics.ts b/src/features/device-logs/lib/backends/metrics.ts index f1436b364f..a0e0feb685 100644 --- a/src/features/device-logs/lib/backends/metrics.ts +++ b/src/features/device-logs/lib/backends/metrics.ts @@ -70,38 +70,35 @@ export function setCurrentSubscriptions(value: number) { metrics.gauge(names.api_device_logs_current_subscriptions, value); } -export function incrementSubscriptionTotal(value: number = 1) { +export function incrementSubscriptionTotal(value = 1) { metrics.counter(names.api_device_logs_subscription_total, value); } -export function incrementPublishLogMessagesTotal(value: number = 1) { +export function incrementPublishLogMessagesTotal(value = 1) { metrics.counter(names.api_device_logs_publish_log_messages_total, value); } -export function incrementPublishLogMessagesDropped(value: number = 1) { +export function incrementPublishLogMessagesDropped(value = 1) { metrics.counter(names.api_device_logs_publish_log_messages_dropped, value); } -export function incrementPublishCallTotal(value: number = 1) { +export function incrementPublishCallTotal(value = 1) { metrics.counter(names.api_device_logs_publish_call_total, value); } -export function incrementPublishCallSuccessTotal(value: number = 1) { +export function incrementPublishCallSuccessTotal(value = 1) { metrics.counter(names.api_device_logs_publish_call_success_total, value); } -export function incrementPublishCallFailedTotal(value: number = 1) { +export function incrementPublishCallFailedTotal(value = 1) { metrics.counter(names.api_device_logs_publish_call_failed_total, value); } -export function incrementLokiPushTotal(value: number = 1) { +export function incrementLokiPushTotal(value = 1) { metrics.counter(names.api_device_logs_loki_push_total, value); } -export function incrementLokiPushErrorTotal( - errorCode: string, - value: number = 1, -) { +export function incrementLokiPushErrorTotal(errorCode: string, value = 1) { metrics.counter(names.api_device_logs_loki_push_error_total, value, { errorCode, }); diff --git a/src/features/device-logs/lib/store.ts b/src/features/device-logs/lib/store.ts index 82dae33b6a..8fdd23ced4 100644 --- a/src/features/device-logs/lib/store.ts +++ b/src/features/device-logs/lib/store.ts @@ -106,11 +106,9 @@ export const store: RequestHandler = async (req: Request, res: Response) => { await Promise.all([ getBackend().publish(ctx, logs), shouldPublishToLoki() - ? (await getLokiBackend()) - .publish(ctx, logs) - .catch((err) => - captureException(err, 'Failed to publish logs to Loki'), - ) + ? (await getLokiBackend()).publish(ctx, logs).catch((err) => { + captureException(err, 'Failed to publish logs to Loki'); + }) : undefined, ]); } @@ -150,11 +148,9 @@ const publishBackend = LOKI_ENABLED ) => { const publishingToRedis = backend.publish(ctx, buffer); const publishingToLoki = shouldPublishToLoki() - ? (await getLokiBackend()) - .publish(ctx, buffer) - .catch((err) => - captureException(err, 'Failed to publish logs to Loki'), - ) + ? (await getLokiBackend()).publish(ctx, buffer).catch((err) => { + captureException(err, 'Failed to publish logs to Loki'); + }) : undefined; await Promise.all([publishingToRedis, publishingToLoki]); } diff --git a/src/features/device-logs/lib/supervisor.ts b/src/features/device-logs/lib/supervisor.ts index 0d32fd5e73..ed6cf7d795 100644 --- a/src/features/device-logs/lib/supervisor.ts +++ b/src/features/device-logs/lib/supervisor.ts @@ -48,6 +48,8 @@ export class Supervisor { private isOldLog(log: any): log is OldSupervisorLog { const old: OldSupervisorLog = log; - return !!(old.is_stderr || old.is_system || old.image_id); + return ( + old.is_stderr != null || old.is_system != null || old.image_id != null + ); } } diff --git a/src/features/device-proxy/device-proxy.ts b/src/features/device-proxy/device-proxy.ts index 25fa99776b..7c99547116 100644 --- a/src/features/device-proxy/device-proxy.ts +++ b/src/features/device-proxy/device-proxy.ts @@ -58,7 +58,8 @@ const validateSupervisorResponse = ( try { jsonBody = JSON.parse(body); } catch (e) { - return badSupervisorResponse(req, res, filter, 'Invalid JSON data'); + badSupervisorResponse(req, res, filter, 'Invalid JSON data'); + return; } } res.status(statusCode).json(jsonBody); @@ -125,7 +126,8 @@ export const proxy = async (req: Request, res: Response) => { const responses = await requestDevices({ url, req, filter, data, method }); if (responses.length === 1) { - return validateSupervisorResponse(responses[0], req, res, filter); + validateSupervisorResponse(responses[0], req, res, filter); + return; } res.status(207).json(multiResponse(responses)); } catch (err) { @@ -162,12 +164,12 @@ async function requestDevices( opts: RequestDevicesOpts & { wait: false; }, -): Promise; +): Promise; // This override is identical to the main form in order for `postDevices` to be able // to call it with the generic form async function requestDevices( opts: RequestDevicesOpts, -): Promise; +): Promise; async function requestDevices({ url, filter, @@ -175,7 +177,7 @@ async function requestDevices({ req, wait = true, method = 'POST', -}: RequestDevicesOpts): Promise { +}: RequestDevicesOpts): Promise { if (url == null) { throw new BadRequestError('You must specify a url to request!'); } @@ -263,7 +265,7 @@ async function requestDevices({ vpnIp = `[${vpnIp}]`; } const deviceUrl = `http://${device.uuid}.balena:${ - device.api_port || 80 + device.api_port ?? 80 }${url}?apikey=${device.api_secret}`; try { return await requestAsync({ @@ -300,9 +302,9 @@ export async function postDevices( opts: FixedMethodRequestDevicesOpts & { wait: false; }, -): Promise; +): Promise; export async function postDevices( opts: FixedMethodRequestDevicesOpts, -): Promise { +): Promise { return await requestDevices({ ...opts, method: 'POST' }); } diff --git a/src/features/device-types/storage/s3.ts b/src/features/device-types/storage/s3.ts index 7b2df36ee5..5c2701a18b 100644 --- a/src/features/device-types/storage/s3.ts +++ b/src/features/device-types/storage/s3.ts @@ -134,7 +134,7 @@ export async function listFolders( const objects = _(res.CommonPrefixes) .map(({ Prefix }) => Prefix) // only keep the folder paths (which are ending with `/`) - .filter((p): p is NonNullable => p != null && p.endsWith('/')) + .filter((p): p is NonNullable => p?.endsWith('/') === true) .map((p) => // get the name of the immediately contained folder path.basename(p), diff --git a/src/features/devices/hooks/defaults-validation.ts b/src/features/devices/hooks/defaults-validation.ts index d84e1f0ce7..879df10ada 100644 --- a/src/features/devices/hooks/defaults-validation.ts +++ b/src/features/devices/hooks/defaults-validation.ts @@ -34,6 +34,7 @@ hooks.addPureHook('POST', 'resin', 'device', { if (request.values.is_pinned_on__release !== undefined) { // Add an async boundary so that value updates, // and doesn't remove the properties that we add. + // eslint-disable-next-line @typescript-eslint/await-thenable await null; request.values.should_be_running__release = request.values.is_pinned_on__release; @@ -87,6 +88,7 @@ hooks.addPureHook('PATCH', 'resin', 'device', { if (request.values.is_pinned_on__release !== undefined) { // Add an async boundary so that value updates, // and doesn't remove the properties that we add. + // eslint-disable-next-line @typescript-eslint/await-thenable await null; request.values.should_be_running__release = request.values.is_pinned_on__release; diff --git a/src/features/registry/middleware.ts b/src/features/registry/middleware.ts index 9d21cb71dc..d7f88cac20 100644 --- a/src/features/registry/middleware.ts +++ b/src/features/registry/middleware.ts @@ -18,10 +18,13 @@ export const basicApiKeyAuthenticate: RequestHandler = async ( res, next, ) => { - const creds = BasicAuth.parse(req.headers['authorization']!); - if (creds) { - req.params.subject = creds.name; - req.params.apikey = creds.pass; + const authHeader = req.headers['authorization']; + if (authHeader != null) { + const creds = BasicAuth.parse(authHeader); + if (creds) { + req.params.subject = creds.name; + req.params.apikey = creds.pass; + } } if (req.params.apikey === TOKEN_AUTH_BUILDER_TOKEN) { next(); @@ -47,9 +50,9 @@ export const basicApiKeyAuthenticate: RequestHandler = async ( req.user == null ) { if ( - req.headers['authorization'] || - req.params['apikey'] || - req.body['apikey'] || + req.headers['authorization'] ?? + req.params['apikey'] ?? + req.body['apikey'] ?? req.query['apikey'] ) { throw new InvalidAuthProvidedError(); diff --git a/src/features/registry/registry.ts b/src/features/registry/registry.ts index d63b38c22b..e356d43f31 100644 --- a/src/features/registry/registry.ts +++ b/src/features/registry/registry.ts @@ -555,7 +555,7 @@ const authorizeRequest = async ( }; const generateToken = ( - subject: string = '', + subject = '', audience: string, access: Access[], ): string => { @@ -597,7 +597,7 @@ export const token: RequestHandler = async (req, res) => { ]), ); res.json({ - token: generateToken(sub, REGISTRY2_HOST, access!), + token: generateToken(sub, REGISTRY2_HOST, access), }); } catch (err) { if (handleHttpErrors(req, res, err)) { diff --git a/src/features/request-logging/index.ts b/src/features/request-logging/index.ts index 66e2b7bacd..9db699108b 100644 --- a/src/features/request-logging/index.ts +++ b/src/features/request-logging/index.ts @@ -22,7 +22,7 @@ const getCallerId = (req: Request) => { (req.creds != null && 'service' in req.creds && req.creds.service) || req.apiKey?.permissions?.includes('service') ) { - return `s/${getServiceFromRequest(req) || 'unknown'}`; + return `s/${getServiceFromRequest(req) ?? 'unknown'}`; } if (req.creds != null) { if ('actor' in req.creds && req.creds.actor) { @@ -32,7 +32,7 @@ const getCallerId = (req: Request) => { return `u/${req.creds.id}`; } } - if (req.apiKey && req.apiKey.actor) { + if (req.apiKey?.actor) { return `a/${req.apiKey.actor}`; } return '-'; @@ -59,9 +59,9 @@ export const setupRequestLogging = ( (tokens, req, res) => { const date = new Date().toISOString(); const url = $getUrl(req); - const statusCode = tokens.status(req, res) || '-'; - const responseTime = tokens['response-time'](req, res) || '-'; - const balenaClient = req.headers['x-balena-client'] || '-'; + const statusCode = tokens.status(req, res) ?? '-'; + const responseTime = tokens['response-time'](req, res) ?? '-'; + const balenaClient = req.headers['x-balena-client'] ?? '-'; const callerId = getCallerId(req); return `${date} ${getIP(req)} ${callerId} ${ diff --git a/src/features/vars-schema/hooks/vars-update-trigger.ts b/src/features/vars-schema/hooks/vars-update-trigger.ts index 8f34428979..887ed15949 100644 --- a/src/features/vars-schema/hooks/vars-update-trigger.ts +++ b/src/features/vars-schema/hooks/vars-update-trigger.ts @@ -110,7 +110,7 @@ const addEnvHooks = ( }); }; -const addAppEnvHooks = (resource: string) => +const addAppEnvHooks = (resource: string) => { addEnvHooks(resource, async (args) => { if (args.req.body.application != null) { // If we have an application passed in the body (ie POST) then we can use that to find the devices to update. @@ -144,11 +144,12 @@ const addAppEnvHooks = (resource: string) => }), ]; }); +}; addAppEnvHooks('application_config_variable'); addAppEnvHooks('application_environment_variable'); -const addDeviceEnvHooks = (resource: string) => +const addDeviceEnvHooks = (resource: string) => { addEnvHooks(resource, async (args) => { if (args.req.body.device != null) { // If we have a device passed in the body (ie POST) then we can use that as ID filter. @@ -173,6 +174,7 @@ const addDeviceEnvHooks = (resource: string) => }), ]; }); +}; addDeviceEnvHooks('device_config_variable'); addDeviceEnvHooks('device_environment_variable'); diff --git a/src/index.ts b/src/index.ts index b241f92d7d..a95a322123 100644 --- a/src/index.ts +++ b/src/index.ts @@ -322,7 +322,7 @@ export async function setup(app: Application, options: SetupOptions) { // redirect to https if needed, except for requests to // the /ping endpoint which must always return 200 app.use( - fixProtocolMiddleware(['/ping'].concat(options.skipHttpsPaths || [])), + fixProtocolMiddleware(['/ping'].concat(options.skipHttpsPaths ?? [])), ); app.use((req, res, next) => { @@ -332,12 +332,13 @@ export async function setup(app: Application, options: SetupOptions) { // itself as "Supervisor/X.X.X (Linux; Resin OS X.X.X; prod)" and the // cron-updater uses curl, all of which ignore CORS and other browser related // headers, so we can drop them to save bandwidth. - return next(); + next(); + return; } res.set('X-Frame-Options', 'DENY'); res.set('X-Content-Type-Options', 'nosniff'); - const origin = req.get('Origin') || '*'; + const origin = req.get('Origin') ?? '*'; res.header('Access-Control-Allow-Origin', origin); res.header('Access-Control-Allow-Credentials', 'true'); // Indicates the response headers that should be made available to js code running in browsers, @@ -346,7 +347,8 @@ export async function setup(app: Application, options: SetupOptions) { if (req.method !== 'OPTIONS') { // If we're not a preflight request then carry on to the real implementation - return next(); + next(); + return; } // Otherwise add the preflight CORS headers and return 200 res.header( @@ -408,16 +410,18 @@ export async function setup(app: Application, options: SetupOptions) { function fixProtocolMiddleware(skipUrls: string[] = []): Handler { return (req, res, next) => { if (req.protocol === 'https' || skipUrls.includes(req.url)) { - return next(); + next(); + return; } if (req.headers['x-forwarded-for'] == null) { const trust = req.app.get('trust proxy fn') as ReturnType< typeof import('proxy-addr').compile >; - if (trust(req.socket.remoteAddress!, 0)) { + if (req.socket.remoteAddress && trust(req.socket.remoteAddress, 0)) { // If we trust the origin of the request and they have not set any `x-forwarded-for` header then // allow them to use http connections without needing to set a dummy `x-forwarded-proto` header - return next(); + next(); + return; } } res.redirect(301, `https://${API_HOST}${req.url}`); diff --git a/src/infra/auth/api-keys.ts b/src/infra/auth/api-keys.ts index a9a2e1213e..5177cbc971 100644 --- a/src/infra/auth/api-keys.ts +++ b/src/infra/auth/api-keys.ts @@ -27,7 +27,7 @@ const getAPIKey = async ( } // While this could be omitted, Pine will go to the DB in vain if not handled - const token = (req.get('Authorization') || '').split(' ', 2)[1]; + const token = (req.get('Authorization') ?? '').split(' ', 2)[1]; if (token && !isJWT(token)) { try { // Add support for API keys on Authorization header if a JWT wasn't provided diff --git a/src/infra/auth/auth.ts b/src/infra/auth/auth.ts index 654649ba07..6cef0405b2 100644 --- a/src/infra/auth/auth.ts +++ b/src/infra/auth/auth.ts @@ -145,7 +145,7 @@ export const checkUserPassword = async ( export const reqHasPermission = ( req: Pick, permission: string, -): boolean => userHasPermission(req.apiKey || req.user, permission); +): boolean => userHasPermission(req.apiKey ?? req.user, permission); // If adding/removing fields, please also update `User` // in "typings/common.d.ts". @@ -267,9 +267,7 @@ export async function findUser>( tx: Tx, $select: TProps, ): Promise | undefined>; -export async function findUser< - TProps extends Array, ->( +export async function findUser>( loginInfo: string, tx: Tx, $select: TProps = defaultFindUser$select as TProps, diff --git a/src/infra/auth/jwt-passport.ts b/src/infra/auth/jwt-passport.ts index b1232eb055..589f6e7448 100644 --- a/src/infra/auth/jwt-passport.ts +++ b/src/infra/auth/jwt-passport.ts @@ -70,9 +70,8 @@ export const createStrategy = ( return { service, apikey, permissions: apiKeyPermissions }; } else if ( 'access' in jwtUser && - jwtUser.access != null && - jwtUser.access.actor && - jwtUser.access.permissions + jwtUser.access?.actor && + jwtUser.access?.permissions ) { return jwtUser.access; } else if ('id' in jwtUser) { @@ -108,7 +107,8 @@ export const middleware: RequestHandler = (req, res, next) => { if (!jwtString || typeof jwtString !== 'string' || !jwtString.includes('.')) { // If we don't have any possibility of a valid jwt string then we avoid // attempting authentication with it altogether - return next(); + next(); + return; } const authenticate = passport.authenticate( @@ -130,10 +130,12 @@ export const middleware: RequestHandler = (req, res, next) => { } if (err) { captureException(err, 'Error JWT auth', { req }); - return next(err); + next(err); + return; } if (!auth) { - return next(); + next(); + return; } req.creds = auth; diff --git a/src/infra/auth/permissions.ts b/src/infra/auth/permissions.ts index fcf974ac9c..8723934a77 100644 --- a/src/infra/auth/permissions.ts +++ b/src/infra/auth/permissions.ts @@ -62,7 +62,7 @@ export const assignUserPermission = ( tx: Tx, ) => getOrInsertId('user__has__permission', { user, permission }, tx); -export const revokeUserRole = async (user: number, role: number, tx: Tx) => +export const revokeUserRole = async (user: number, role: number, tx: Tx) => { await api.Auth.delete({ resource: 'user__has__role', id: { @@ -74,6 +74,7 @@ export const revokeUserRole = async (user: number, role: number, tx: Tx) => req: permissions.root, }, }); +}; // api key helpers diff --git a/src/infra/cache/multi-level-memoizee.ts b/src/infra/cache/multi-level-memoizee.ts index 0b30d9e43f..719316a05e 100644 --- a/src/infra/cache/multi-level-memoizee.ts +++ b/src/infra/cache/multi-level-memoizee.ts @@ -248,7 +248,7 @@ function multiCache Promise>( const valueToCache = await fn(...args); // Some caches (eg redis) cannot handle caching undefined/null so we convert it to the `undefinedAs` proxy value // which will be used when storing in the cache and then convert it back to undefined when retrieving from the cache - return valueToCache === undefined ? undefinedAs : valueToCache; + return valueToCache ?? undefinedAs; }); return valueFromCache === undefinedAs ? undefined : valueFromCache; }; diff --git a/src/infra/scheduler/index.ts b/src/infra/scheduler/index.ts index 626e35cd8a..e4940e987c 100644 --- a/src/infra/scheduler/index.ts +++ b/src/infra/scheduler/index.ts @@ -99,8 +99,7 @@ export const scheduleJob = ( jobFunction: JobFunction, lockOptions?: LockSettings, ): schedule.Job => { - const ttl = - lockOptions && lockOptions.ttl ? lockOptions.ttl : JOB_DEFAULT_TTL; + const ttl = lockOptions?.ttl ? lockOptions.ttl : JOB_DEFAULT_TTL; const jobLockKey = JOB_LOCK_PREFIX + jobId; const jobInfoKey = JOB_INFO_PREFIX + jobId; const job: schedule.Job = schedule.scheduleJob( diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 4f9f9854b7..49f2855b29 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -32,6 +32,7 @@ export const checkInt = (num?: string): number | false => { }; export const getIP = (req: Request): string | undefined => + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing req.ip || (req as any)._remoteAddress || req.connection?.remoteAddress || diff --git a/src/translations/v6/hooks.ts b/src/translations/v6/hooks.ts index 2261b67ab2..4832cf111b 100644 --- a/src/translations/v6/hooks.ts +++ b/src/translations/v6/hooks.ts @@ -22,6 +22,7 @@ const translatePropertyTo = if (Object.hasOwn(request.values, currentModelField)) { // Add an async boundary so that other sync hooks can use // the untranslated values. + // eslint-disable-next-line @typescript-eslint/await-thenable await null; request.values[newerModelField] = request.values[currentModelField]; delete request.values[currentModelField]; diff --git a/test/03_device-state.ts b/test/03_device-state.ts index 63c98efa0c..73a0badf6d 100644 --- a/test/03_device-state.ts +++ b/test/03_device-state.ts @@ -849,7 +849,8 @@ export default () => { }); it(`should update the DB heartbeat after the validity period passes`, async () => { - await setTimeout(500 + Date.now() - lastPersistedTimestamp!); + assertExists(lastPersistedTimestamp); + await setTimeout(500 + Date.now() - lastPersistedTimestamp); await fakeDevice.getState(device2, device2.uuid, stateVersion); await waitFor({ checkFn: () => device2ChangeEventSpy.called }); await expectResourceToMatch(pineUser, 'device', device2.id, { @@ -921,7 +922,7 @@ export default () => { }); describe('When decreasing the API_HEARTBEAT_STATE_ONLINE_UPDATE_CACHE_TIMEOUT', function () { - before(async function () { + before(function () { config.TEST_MOCK_ONLY.API_HEARTBEAT_STATE_ONLINE_UPDATE_CACHE_TIMEOUT = 2 * SECONDS; }); @@ -943,7 +944,8 @@ export default () => { }); it(`should update the DB heartbeat after exceeding the new validity period`, async () => { - await setTimeout(500 + Date.now() - lastPersistedTimestamp!); + assertExists(lastPersistedTimestamp); + await setTimeout(500 + Date.now() - lastPersistedTimestamp); await fakeDevice.getState(device2, device2.uuid, stateVersion); await waitFor({ checkFn: () => device2ChangeEventSpy.called }); await expectResourceToMatch(pineUser, 'device', device2.id, { @@ -1237,7 +1239,7 @@ export default () => { const generateValidAddress = ( addr: string, truncLen: number, - delimiter: string = '', + delimiter = '', ): string => { let validAddress = ''; while ( diff --git a/test/04_session.ts b/test/04_session.ts index 41b64947d4..729b917e7e 100644 --- a/test/04_session.ts +++ b/test/04_session.ts @@ -145,9 +145,10 @@ export default () => { describe('granted user token', function () { let token: string; - before(async function () { - expect(admin).to.have.property('token').that.is.a('string'); - token = admin.token!; + before(function () { + assertExists(admin.token); + expect(admin.token).to.be.a('string'); + token = admin.token; }); it('should be refreshable with /user/v1/refresh-token and not include extra properties', async function () { diff --git a/test/06_device-log.ts b/test/06_device-log.ts index 20aa02716d..da172d4a20 100644 --- a/test/06_device-log.ts +++ b/test/06_device-log.ts @@ -194,7 +194,9 @@ export default () => { ]) .expect(201); }); - res.on('end', () => callback(null, Buffer.concat(chunks))); + res.on('end', () => { + callback(null, Buffer.concat(chunks)); + }); }) .expect(200); diff --git a/test/07_versioned-releases.ts b/test/07_versioned-releases.ts index e533c9029d..9df75c2d80 100644 --- a/test/07_versioned-releases.ts +++ b/test/07_versioned-releases.ts @@ -232,8 +232,9 @@ export default () => { }, }) .expect(200); + assertExists(topRevisionRelease.revision); expect(topRevisionRelease.revision).to.be.a('number'); - return topRevisionRelease.revision!; + return topRevisionRelease.revision; }; /* Tests that the computed terms have the correct values based on what values the DB fields hold. */ @@ -781,14 +782,17 @@ export default () => { (rev) => rev, ); expect(revisions).to.deep.equal(_.range(0, newReleases.length)); - return newReleases; + for (const release of newReleases) { + assertExists(release.revision); + expect(release) + .to.have.property('revision') + .that.is.a('number'); + } + return newReleases as Array< + NonNullableField + >; }), ); - v1Releases - .concat(v2Releases) - .forEach((r) => - expect(r).to.have.property('revision').that.is.a('number'), - ); const [versionAReleasesToChangeSemver, [leftBehindV1SemverRelease]] = _.partition( v1Releases, @@ -797,7 +801,7 @@ export default () => { const releaseIdsToChangeSemver = versionAReleasesToChangeSemver.map( (r) => r.id, ); - const maxV2Revision = Math.max(...v2Releases.map((r) => r.revision!)); + const maxV2Revision = Math.max(...v2Releases.map((r) => r.revision)); await updateFn(versionAReleasesToChangeSemver); diff --git a/test/08_api-keys.ts b/test/08_api-keys.ts index ebd839850b..e7baa23b73 100644 --- a/test/08_api-keys.ts +++ b/test/08_api-keys.ts @@ -6,6 +6,7 @@ import type { UserObjectParam } from './test-lib/supertest.js'; import { supertest } from './test-lib/supertest.js'; import * as versions from './test-lib/versions.js'; import { sbvrUtils, permissions } from '@balena/pinejs'; +import { assertExists } from './test-lib/common.js'; const { api } = sbvrUtils; @@ -144,6 +145,7 @@ export default () => { }, }); + assertExists(apiKeyResp); expect(apiKeyResp).to.have.property( 'name', `provision-key-${applicationId}-with-expiry`, @@ -153,8 +155,8 @@ export default () => { `Sample key for application-${applicationId} description.`, ); - expect(apiKeyResp).to.have.property('expiry_date'); - const expiryDate = new Date(apiKeyResp!.expiry_date!); + assertExists(apiKeyResp.expiry_date); + const expiryDate = new Date(apiKeyResp.expiry_date); expect(expiryDate.getTime()).to.equal(tomorrowDate.getTime()); }); @@ -565,7 +567,7 @@ export default () => { }); }); - describe('standard api key endpoints', async function () { + describe('standard api key endpoints', function () { before(async function () { const fx = await fixtures.load(); diff --git a/test/09_contracts.ts b/test/09_contracts.ts index d8ef25e61f..5af66aced6 100644 --- a/test/09_contracts.ts +++ b/test/09_contracts.ts @@ -244,11 +244,12 @@ export default () => { JSON.parse(JSON.stringify(finDtContract)), ); + assertExists(rpiDt); expect(rpiDt) .to.have.property('device_type_alias') .that.is.an('array'); expect( - rpiDt!.device_type_alias + rpiDt.device_type_alias .map((a) => a.is_referenced_by__alias) .sort(), ).to.deep.equal(['raspberry-pi', 'raspberrypi']); diff --git a/test/10_migrations.ts b/test/10_migrations.ts index 2030147b7e..2c642462e7 100644 --- a/test/10_migrations.ts +++ b/test/10_migrations.ts @@ -5,6 +5,8 @@ import _ from 'lodash'; import { execSync } from 'node:child_process'; import path from 'path'; import configJson from '../config.js'; +import type { types } from '@balena/pinejs'; +import { assertExists } from './test-lib/common.js'; // Validate SQL files using squawk function validateSql(file: string): void { @@ -20,16 +22,20 @@ function validateSql(file: string): void { export default () => { describe('migrations', () => { _(configJson.models) - .filter('migrationsPath') + .filter( + (m): m is types.RequiredField => + m.migrationsPath != null, + ) .each(({ modelName, migrationsPath }) => { - describe(modelName!, () => { - if (!path.isAbsolute(migrationsPath!)) { + assertExists(modelName); + describe(modelName, () => { + if (!path.isAbsolute(migrationsPath)) { migrationsPath = fileURLToPath( new URL('../src/' + migrationsPath, import.meta.url), ); } - const fileNames = fs.readdirSync(migrationsPath!); - it('should have unique prefixes', async () => { + const fileNames = fs.readdirSync(migrationsPath); + it('should have unique prefixes', () => { const duplicates = _(fileNames) .groupBy((v) => v.split('-', 1)[0]) .filter((v) => v.length > 1) @@ -45,15 +51,15 @@ export default () => { for (const fileName of fileNames.filter((f) => { return f.endsWith('.sql'); })) { - it(`should have valid sql in ${fileName}`, async () => { - validateSql(path.join(migrationsPath!, fileName)); + it(`should have valid sql in ${fileName}`, () => { + validateSql(path.join(migrationsPath, fileName)); }); } // Sanity check async migrations const asyncMigrationPaths = fileNames .filter((fileName) => fileName.endsWith('.async.ts')) - .map((fileName) => path.join(migrationsPath!, fileName)); + .map((fileName) => path.join(migrationsPath, fileName)); for (const asyncMigrationPath of asyncMigrationPaths) { it(`should have valid sql in ${asyncMigrationPath}`, async () => { const migration = (await import(asyncMigrationPath)).default; @@ -90,7 +96,7 @@ export default () => { }); describe('balena-init.sql', () => { - it('should have valid sql', async () => { + it('should have valid sql', () => { validateSql('src/balena-init.sql'); }); }); diff --git a/test/14_release-pinning.ts b/test/14_release-pinning.ts index c2683d49d0..1d14c188e6 100644 --- a/test/14_release-pinning.ts +++ b/test/14_release-pinning.ts @@ -14,6 +14,7 @@ import { addImageToRelease, } from './test-lib/api-helpers.js'; import type { Application, DeviceType, Release } from '../src/balena-model.js'; +import { assertExists } from './test-lib/common.js'; export default () => { versions.test((version, pineTest) => { @@ -198,9 +199,10 @@ export default () => { beforeEach(async function () { testRunsCount++; + assertExists(admin.id); app1Release = await addReleaseToApp(admin, { belongs_to__application: applicationId, - is_created_by__user: admin.id!, + is_created_by__user: admin.id, build_log: '', commit: `deadbeef${testRunsCount}`, composition: {}, @@ -211,7 +213,7 @@ export default () => { app2Release = await addReleaseToApp(admin, { belongs_to__application: application2Id, - is_created_by__user: admin.id!, + is_created_by__user: admin.id, build_log: '', commit: `deadbeef${testRunsCount}`, composition: {}, @@ -353,9 +355,11 @@ export default () => { }) .expect(200); + assertExists(admin.id); + const app3Release = await addReleaseToApp(admin, { belongs_to__application: application3Id, - is_created_by__user: admin.id!, + is_created_by__user: admin.id, build_log: '', commit: `deadbeef`, composition: {}, @@ -367,7 +371,7 @@ export default () => { app3ReleaseId = app3Release.id; const app4Release = await addReleaseToApp(admin, { belongs_to__application: application3Id, - is_created_by__user: admin.id!, + is_created_by__user: admin.id, build_log: '', commit: `deadbeef2`, composition: {}, @@ -378,7 +382,7 @@ export default () => { app4ReleaseId = app4Release.id; const app5Release = await addReleaseToApp(admin, { belongs_to__application: application3Id, - is_created_by__user: admin.id!, + is_created_by__user: admin.id, build_log: '', commit: `deadbeef3`, composition: {}, @@ -389,7 +393,7 @@ export default () => { app5ReleaseId = app5Release.id; const appToDeleteRelease1 = await addReleaseToApp(admin, { belongs_to__application: applicationToDelete.id, - is_created_by__user: admin.id!, + is_created_by__user: admin.id, build_log: '', commit: `deadbeef4`, composition: {}, diff --git a/test/17_vpn.ts b/test/17_vpn.ts index 997a0a8ac3..a45f989f81 100644 --- a/test/17_vpn.ts +++ b/test/17_vpn.ts @@ -79,7 +79,7 @@ export default () => { }); describe(`given a token that doesn't match any device api key`, function () { - before(async function () { + before(function () { this.uuid = generateDeviceUuid(); this.deviceKey = randomstring.generate(); }); diff --git a/test/18_resource_filtering.ts b/test/18_resource_filtering.ts index 27e86d1ac1..188b570164 100644 --- a/test/18_resource_filtering.ts +++ b/test/18_resource_filtering.ts @@ -6,6 +6,7 @@ import _ from 'lodash'; import type { Application } from '../src/balena-model.js'; import { setTimeout } from 'timers/promises'; import type { PineTest } from 'pinejs-client-supertest'; +import { assertExists } from './test-lib/common.js'; export default () => { versions.test((_version, pineTest) => { @@ -117,11 +118,13 @@ export default () => { }); it('Should filter applications with created_at less or equal than last', async () => { + const lastTestTime = testTimes.at(-1); + assertExists(lastTestTime); const { body } = await pineUser.get({ resource: 'application', options: { $filter: { - created_at: { $le: testTimes.at(-1)!.created_at }, + created_at: { $le: lastTestTime.created_at }, }, }, }); diff --git a/test/21_fleet-target-state.ts b/test/21_fleet-target-state.ts index fbfc71b9aa..952bd47898 100644 --- a/test/21_fleet-target-state.ts +++ b/test/21_fleet-target-state.ts @@ -87,7 +87,7 @@ export default () => { 'release1', 'release2', ] as const - ).forEach(async (testReleaseKey) => { + ).forEach((testReleaseKey) => { it(`with releaseUuid parameter for ${testReleaseKey}`, async () => { const releaseUuidQueryParam = releases[testReleaseKey]?.commit ? `?releaseUuid=${releases[testReleaseKey]?.commit}` diff --git a/test/test-lib/api-helpers.ts b/test/test-lib/api-helpers.ts index 4cf4e7a58a..a57d95accb 100644 --- a/test/test-lib/api-helpers.ts +++ b/test/test-lib/api-helpers.ts @@ -6,6 +6,7 @@ import type { UserObjectParam } from '../test-lib/supertest.js'; import { supertest } from '../test-lib/supertest.js'; import type { TokenUserPayload } from '../../src/index.js'; import type { RequiredField } from '@balena/pinejs/out/sbvr-api/common-types.js'; +import { assertExists } from './common.js'; const version = 'resin'; @@ -113,14 +114,15 @@ export const expectResourceToMatch = async ( ( 'expect' in requestPromise ? (await requestPromise.expect(200)).body - : await requestPromise + : await (requestPromise as Promise) ) as T | undefined; + assertExists(result); expect(result).to.be.an('object'); for (const [key, valueOrAssertion] of Object.entries(expectations)) { if (typeof valueOrAssertion === 'function') { valueOrAssertion( expect(result).to.have.property(key), - result![key as keyof typeof result], + result[key as keyof typeof result], ); } else if ( typeof valueOrAssertion === 'object' && @@ -131,7 +133,7 @@ export const expectResourceToMatch = async ( expect(result).to.have.property(key, valueOrAssertion); } } - return result!; + return result; }; const validJwtProps = ['id', 'jwt_secret', 'authTime', 'iat', 'exp'].sort(); diff --git a/test/test-lib/device-type.ts b/test/test-lib/device-type.ts index 859cbacc59..057c75d972 100644 --- a/test/test-lib/device-type.ts +++ b/test/test-lib/device-type.ts @@ -4,7 +4,7 @@ import type { DeviceType } from '../../src/balena-model.js'; const { api } = sbvrUtils; -export const loadDefaultFixtures = () => +export const loadDefaultFixtures = () => { setDefaultFixtures( 'deviceTypes', new Proxy( @@ -38,3 +38,4 @@ export const loadDefaultFixtures = () => }, ), ); +}; diff --git a/test/test-lib/fake-device.ts b/test/test-lib/fake-device.ts index 0b94cfd69c..4154806d49 100644 --- a/test/test-lib/fake-device.ts +++ b/test/test-lib/fake-device.ts @@ -126,10 +126,10 @@ export async function provisionDevice( return await getState(device, device.uuid, 'v3'); }, patchStateV2: async (devicePatchBody: StatePatchV2Body) => { - return await patchState(device, device.uuid, devicePatchBody, 'v2'); + await patchState(device, device.uuid, devicePatchBody, 'v2'); }, patchStateV3: async (devicePatchBody: StatePatchV3Body) => { - return await patchState(device, device.uuid, devicePatchBody, 'v3'); + await patchState(device, device.uuid, devicePatchBody, 'v3'); }, }; diff --git a/test/test-lib/fixtures.ts b/test/test-lib/fixtures.ts index 0f4f7f9052..b3a450ec22 100644 --- a/test/test-lib/fixtures.ts +++ b/test/test-lib/fixtures.ts @@ -614,7 +614,7 @@ export const load = async (fixtureName?: string): Promise => { with: { type: 'json' }, } ); - return await loadFixtureModel(loaders[model], fixtures, fromJson); + return loadFixtureModel(loaders[model], fixtures, fromJson); })(); } diff --git a/test/test-lib/init-tests.ts b/test/test-lib/init-tests.ts index 43fa7416b5..b98f9e2d56 100644 --- a/test/test-lib/init-tests.ts +++ b/test/test-lib/init-tests.ts @@ -45,7 +45,7 @@ const loadAdminUserAndOrganization = async () => { ).text; const user: UserObjectParam = { - ...(await expectJwt(token)), + ...expectJwt(token), token, };