From ad98db19348ff48521919d5c3cb90e51080db3b3 Mon Sep 17 00:00:00 2001 From: Jan Romann Date: Fri, 22 Sep 2023 16:46:49 +0200 Subject: [PATCH] chore(binding-http): enable eslint/strict-boolean-expressions --- packages/binding-http/.eslintrc.json | 5 ++- packages/binding-http/src/credential.ts | 21 +++++------ packages/binding-http/src/http-client-impl.ts | 2 +- packages/binding-http/src/http-server.ts | 24 +++++-------- .../src/oauth-token-validation.ts | 9 ++--- packages/binding-http/src/routes/action.ts | 6 ++-- packages/binding-http/src/routes/common.ts | 13 ++++--- packages/binding-http/src/routes/event.ts | 4 +-- .../src/routes/property-observe.ts | 4 +-- packages/binding-http/src/routes/property.ts | 8 ++--- .../src/routes/thing-description.ts | 35 +++++++++++-------- .../test/http-client-basic-test.ts | 2 +- .../binding-http/test/http-client-test.ts | 22 ++++++++---- .../binding-http/test/http-server-test.ts | 2 +- packages/binding-http/test/memory-model.ts | 4 +-- .../test/oauth-token-validation-tests.ts | 2 +- 16 files changed, 89 insertions(+), 74 deletions(-) diff --git a/packages/binding-http/.eslintrc.json b/packages/binding-http/.eslintrc.json index c4237119e..70b9f134b 100644 --- a/packages/binding-http/.eslintrc.json +++ b/packages/binding-http/.eslintrc.json @@ -1,3 +1,6 @@ { - "extends": "../../.eslintrc.js" + "extends": "../../.eslintrc.js", + "rules": { + "@typescript-eslint/strict-boolean-expressions": ["error"] + } } diff --git a/packages/binding-http/src/credential.ts b/packages/binding-http/src/credential.ts index 59c020bea..26adf4782 100644 --- a/packages/binding-http/src/credential.ts +++ b/packages/binding-http/src/credential.ts @@ -184,14 +184,15 @@ export class TuyaCustomBearer extends Credential { await this.requestAndRefreshToken(isTokenExpired); const url: string = request.url; - const body = request.body ? request.body.read().toString() : ""; - const headers = this.getHeaders(true, request.headers.raw(), body, url, request.method); + const body = request.body?.read().toString() ?? ""; + const method = request.method; + const headers = this.getHeaders(true, request.headers.raw(), body, url, method); Object.assign(headers, request.headers.raw()); - return new Request(url, { method: request.method, body: body !== "" ? body : undefined, headers: headers }); + return new Request(url, { method, body, headers }); } protected async requestAndRefreshToken(refresh: boolean): Promise { - const headers = this.getHeaders(false, {}, ""); + const headers = this.getHeaders(false, {}); const request = { headers: headers, method: "GET", @@ -210,26 +211,26 @@ export class TuyaCustomBearer extends Credential { } } - private getHeaders(NormalRequest: boolean, headers: unknown, body: string, url?: string, method?: string) { + private getHeaders(NormalRequest: boolean, headers: unknown, body?: string, url?: string, method?: string) { const requestTime = Date.now().toString(); const replaceUri = this.baseUri.replace("/v1.0", ""); - const _url = url ? url.replace(`${replaceUri}`, "") : undefined; + const _url = url?.replace(`${replaceUri}`, ""); const sign = this.requestSign(NormalRequest, requestTime, body, _url, method); return { t: requestTime, client_id: this.key, sign_method: "HMAC-SHA256", sign, - access_token: this.token || "", + access_token: this.token ?? "", }; } - private requestSign(NormalRequest: boolean, requestTime: string, body: string, path = "", method?: string): string { - const bodyHash = crypto.createHash("sha256").update(body).digest("hex"); + private requestSign(NormalRequest: boolean, requestTime: string, body?: string, path = "", method?: string): string { + const bodyHash = crypto.createHash("sha256").update(body ?? "").digest("hex"); let signUrl = "/v1.0/token?grant_type=1"; const headerString = ""; let useToken = ""; - const _method = method || "GET"; + const _method = method ?? "GET"; if (NormalRequest) { useToken = this.token ?? ""; const pathQuery = queryString.parse(path.split("?")[1]); diff --git a/packages/binding-http/src/http-client-impl.ts b/packages/binding-http/src/http-client-impl.ts index 8a38e4d83..e1f9db489 100644 --- a/packages/binding-http/src/http-client-impl.ts +++ b/packages/binding-http/src/http-client-impl.ts @@ -225,7 +225,7 @@ export default class HttpClient implements ProtocolClient { public async stop(): Promise { // When running in browser mode, Agent.destroy() might not exist. - if (this.agent && this.agent.destroy) this.agent.destroy(); + this.agent?.destroy?.(); } public setSecurity(metadata: Array, credentials?: unknown): boolean { diff --git a/packages/binding-http/src/http-server.ts b/packages/binding-http/src/http-server.ts index aa5b5422b..1c0f831f9 100644 --- a/packages/binding-http/src/http-server.ts +++ b/packages/binding-http/src/http-server.ts @@ -88,25 +88,17 @@ export default class HttpServer implements ProtocolServer { .map((envVar) => { return { key: envVar, value: process.env[envVar] }; }) - .find((envObj) => envObj.value != null && envObj.value !== undefined) as { key: string; value: string }; + .find((envObj) => envObj.value != null) as { key: string; value: string }; - if (environmentObj) { + if (environmentObj != null) { info(`HttpServer Port Overridden to ${environmentObj.value} by Environment Variable ${environmentObj.key}`); this.port = parseInt(environmentObj.value); } - if (config.address !== undefined) { - this.address = config.address; - } - if (config.baseUri !== undefined) { - this.baseUri = config.baseUri; - } - if (config.urlRewrite !== undefined) { - this.urlRewrite = config.urlRewrite; - } - if (config.middleware !== undefined) { - this.middleware = config.middleware; - } + this.address = config.address ?? this.address; + this.baseUri = config.baseUri ?? this.baseUri; + this.urlRewrite = config.urlRewrite ?? this.urlRewrite; + this.middleware = config.middleware ?? this.middleware; const router = Router({ ignoreTrailingSlash: true, @@ -526,7 +518,7 @@ export default class HttpServer implements ProtocolServer { private fillSecurityScheme(thing: ExposedThing) { // User selected one security scheme - if (thing.security) { + if (thing.security != null) { // multiple security schemes are deprecated we are not supporting them const securityScheme = Helpers.toStringArray(thing.security)[0]; const secCandidate = Object.keys(thing.securityDefinitions).find((key) => { @@ -556,7 +548,7 @@ export default class HttpServer implements ProtocolServer { } // The user let the servient choose the security scheme - if (!thing.securityDefinitions || Object.keys(thing.securityDefinitions).length === 0) { + if (Object.keys(thing.securityDefinitions ?? {}).length === 0) { // We are using the first supported security scheme as default thing.securityDefinitions = { [this.supportedSecuritySchemes[0]]: { scheme: this.supportedSecuritySchemes[0] }, diff --git a/packages/binding-http/src/oauth-token-validation.ts b/packages/binding-http/src/oauth-token-validation.ts index 2f3372569..f15d965ba 100644 --- a/packages/binding-http/src/oauth-token-validation.ts +++ b/packages/binding-http/src/oauth-token-validation.ts @@ -60,11 +60,11 @@ function extractTokenFromRequest(request: http.IncomingMessage) { const url = new URL(request.url ?? "", `http://${request.headers.host}`); const queryToken = url.searchParams.get("access_token"); - if (!headerToken && !queryToken) { + if (headerToken == null && queryToken == null) { throw new Error("Invalid request: only one authentication method is allowed"); } - if (queryToken) { + if (queryToken != null) { return queryToken; } @@ -84,9 +84,10 @@ export class EndpointValidator extends Validator { super(); this.config = config; const endpoint = config.endpoint; + const allowSelfSigned = config?.allowSelfSigned ?? false; this.agent = endpoint.startsWith("https") ? new SecureAgent({ - rejectUnauthorized: !config.allowSelfSigned, + rejectUnauthorized: !(allowSelfSigned), }) : new http.Agent(); } @@ -149,7 +150,7 @@ export class EndpointValidator extends Validator { if (!validScope) return false; // Check if the client was allowed in the servient configuration file - if (validationResult.client_id && !validationResult.client_id.match(clients)) { + if (!validationResult.client_id?.match(clients)) { return false; } diff --git a/packages/binding-http/src/routes/action.ts b/packages/binding-http/src/routes/action.ts index a07d928d0..987c00933 100644 --- a/packages/binding-http/src/routes/action.ts +++ b/packages/binding-http/src/routes/action.ts @@ -38,7 +38,7 @@ export default async function actionRoute( } const thing = this.getThings().get(_params.thing); - if (!thing) { + if (thing == null) { res.writeHead(404); res.end(); return; @@ -61,7 +61,7 @@ export default async function actionRoute( const action = thing.actions[_params.action]; - if (!action) { + if (action == null) { res.writeHead(404); res.end(); return; @@ -72,7 +72,7 @@ export default async function actionRoute( const securityScheme = thing.securityDefinitions[Helpers.toStringArray(thing.security)[0]].scheme; if (securityScheme !== "nosec" && !(await this.checkCredentials(thing, req))) { - if (req.method === "OPTIONS" && req.headers.origin) { + if (req.method === "OPTIONS" && req.headers.origin != null) { corsPreflightWithCredentials = true; } else { res.setHeader("WWW-Authenticate", `${securitySchemeToHttpHeader(securityScheme)} realm="${thing.id}"`); diff --git a/packages/binding-http/src/routes/common.ts b/packages/binding-http/src/routes/common.ts index 5a13c5bf5..b4d164fad 100644 --- a/packages/binding-http/src/routes/common.ts +++ b/packages/binding-http/src/routes/common.ts @@ -27,14 +27,17 @@ export function respondUnallowedMethod( if (!allowed.includes("OPTIONS")) { allowed += ", OPTIONS"; } - if (req.method === "OPTIONS" && req.headers.origin && req.headers["access-control-request-method"]) { + + const headers = req.headers; + const origin = headers.origin; + if (req.method === "OPTIONS" && origin != null && headers["access-control-request-method"] != null) { debug( `HttpServer received an CORS preflight request from ${Helpers.toUriLiteral(req.socket.remoteAddress)}:${ req.socket.remotePort }` ); if (corsPreflightWithCredentials) { - res.setHeader("Access-Control-Allow-Origin", req.headers.origin); + res.setHeader("Access-Control-Allow-Origin", origin); res.setHeader("Access-Control-Allow-Credentials", "true"); } else { res.setHeader("Access-Control-Allow-Origin", "*"); @@ -91,8 +94,10 @@ export function securitySchemeToHttpHeader(scheme: string): string { export function setCorsForThing(req: IncomingMessage, res: ServerResponse, thing: ExposedThing): void { const securityScheme = thing.securityDefinitions[Helpers.toStringArray(thing.security)[0]].scheme; // Set CORS headers - if (securityScheme !== "nosec" && req.headers.origin) { - res.setHeader("Access-Control-Allow-Origin", req.headers.origin); + + const origin = req.headers.origin; + if (securityScheme !== "nosec" && origin != null) { + res.setHeader("Access-Control-Allow-Origin", origin); res.setHeader("Access-Control-Allow-Credentials", "true"); } else { res.setHeader("Access-Control-Allow-Origin", "*"); diff --git a/packages/binding-http/src/routes/event.ts b/packages/binding-http/src/routes/event.ts index 6e134b985..0358750d3 100644 --- a/packages/binding-http/src/routes/event.ts +++ b/packages/binding-http/src/routes/event.ts @@ -41,7 +41,7 @@ export default async function eventRoute( const contentType: string = Array.isArray(contentTypeHeader) ? contentTypeHeader[0] : contentTypeHeader; const event = thing.events[_params.event]; - if (!event) { + if (event == null) { res.writeHead(404); res.end(); return; @@ -52,7 +52,7 @@ export default async function eventRoute( const securityScheme = thing.securityDefinitions[Helpers.toStringArray(thing.security)[0]].scheme; if (securityScheme !== "nosec" && !(await this.checkCredentials(thing, req))) { - if (req.method === "OPTIONS" && req.headers.origin) { + if (req.method === "OPTIONS" && req.headers.origin != null) { corsPreflightWithCredentials = true; } else { res.setHeader("WWW-Authenticate", `${securitySchemeToHttpHeader(securityScheme)} realm="${thing.id}"`); diff --git a/packages/binding-http/src/routes/property-observe.ts b/packages/binding-http/src/routes/property-observe.ts index c7bf070e4..ac0d1d5b8 100644 --- a/packages/binding-http/src/routes/property-observe.ts +++ b/packages/binding-http/src/routes/property-observe.ts @@ -42,7 +42,7 @@ export default async function propertyObserveRoute( const contentType: string = Array.isArray(contentTypeHeader) ? contentTypeHeader[0] : contentTypeHeader; const property = thing.properties[_params.property]; - if (!property) { + if (property == null) { res.writeHead(404); res.end(); return; @@ -62,7 +62,7 @@ export default async function propertyObserveRoute( const securityScheme = thing.securityDefinitions[Helpers.toStringArray(thing.security)[0]].scheme; if (securityScheme !== "nosec" && !(await this.checkCredentials(thing, req))) { - if (req.method === "OPTIONS" && req.headers.origin) { + if (req.method === "OPTIONS" && req.headers.origin != null) { corsPreflightWithCredentials = true; } else { res.setHeader("WWW-Authenticate", `${securitySchemeToHttpHeader(securityScheme)} realm="${thing.id}"`); diff --git a/packages/binding-http/src/routes/property.ts b/packages/binding-http/src/routes/property.ts index 4095fb3bd..6d808b87d 100644 --- a/packages/binding-http/src/routes/property.ts +++ b/packages/binding-http/src/routes/property.ts @@ -38,7 +38,7 @@ export default async function propertyRoute( const thing = this.getThings().get(_params.thing); - if (!thing) { + if (thing == null) { res.writeHead(404); res.end(); return; @@ -61,7 +61,7 @@ export default async function propertyRoute( const property = thing.properties[_params.property]; - if (!property) { + if (property == null) { res.writeHead(404); res.end(); return; @@ -82,7 +82,7 @@ export default async function propertyRoute( const securityScheme = thing.securityDefinitions[Helpers.toStringArray(thing.security)[0]].scheme; if (securityScheme !== "nosec" && !(await this.checkCredentials(thing, req))) { - if (req.method === "OPTIONS" && req.headers.origin) { + if (req.method === "OPTIONS" && req.headers.origin != null) { corsPreflightWithCredentials = true; } else { res.setHeader("WWW-Authenticate", `${securitySchemeToHttpHeader(securityScheme)} realm="${thing.id}"`); @@ -106,7 +106,7 @@ export default async function propertyRoute( res.end(message); } } else if (req.method === "PUT") { - if (!property.readOnly) { + if (property.readOnly !== true) { try { await thing.handleWriteProperty(_params.property, new Content(contentType, req), options); diff --git a/packages/binding-http/src/routes/thing-description.ts b/packages/binding-http/src/routes/thing-description.ts index 29188de5d..53eb3aaf1 100644 --- a/packages/binding-http/src/routes/thing-description.ts +++ b/packages/binding-http/src/routes/thing-description.ts @@ -104,21 +104,26 @@ function resetMultiLangThing(thing: ThingDescription, prefLang: string) { * @param req */ function negotiateLanguage(td: ThingDescription, thing: ExposedThing, req: IncomingMessage) { - if (req.headers["accept-language"] && req.headers["accept-language"] !== "*") { - if (td.titles) { - const supportedLanguages = Object.keys(td.titles); // e.g., ['fr', 'en'] - // the loose option allows partial matching on supported languages (e.g., returns "de" for "de-CH") - const prefLang = acceptLanguageParser.pick(supportedLanguages, req.headers["accept-language"], { - loose: true, - }); - if (prefLang) { - // if a preferred language can be found use it - debug( - `TD language negotiation through the Accept-Language header field of HTTP leads to "${prefLang}"` - ); - // TODO: reset titles and descriptions to only contain the preferred language - resetMultiLangThing(td, prefLang); - } + const acceptLanguage = req.headers["accept-language"]; + const noPreference = acceptLanguage == null || acceptLanguage === "*"; + + if (noPreference) { + return; + } + + if (td.titles != null) { + const supportedLanguages = Object.keys(td.titles); // e.g., ['fr', 'en'] + // the loose option allows partial matching on supported languages (e.g., returns "de" for "de-CH") + const prefLang = acceptLanguageParser.pick(supportedLanguages, acceptLanguage, { + loose: true, + }); + if (prefLang != null) { + // if a preferred language can be found use it + debug( + `TD language negotiation through the Accept-Language header field of HTTP leads to "${prefLang}"` + ); + // TODO: reset titles and descriptions to only contain the preferred language + resetMultiLangThing(td, prefLang); } } } diff --git a/packages/binding-http/test/http-client-basic-test.ts b/packages/binding-http/test/http-client-basic-test.ts index 7177f3316..bf725492e 100644 --- a/packages/binding-http/test/http-client-basic-test.ts +++ b/packages/binding-http/test/http-client-basic-test.ts @@ -33,7 +33,7 @@ function mockService(req: express.Request, res: express.Response) { const auth = { login: "admin", password: "password" }; // change this // parse login and password from headers - const b64auth = (req.headers.authorization || "").split(" ")[1] || ""; + const b64auth = (req.headers.authorization ?? "").split(" ")[1] ?? ""; const [login, password] = Buffer.from(b64auth, "base64").toString().split(":"); // Verify login and password are set and correct diff --git a/packages/binding-http/test/http-client-test.ts b/packages/binding-http/test/http-client-test.ts index 8ca5bc718..951faf690 100644 --- a/packages/binding-http/test/http-client-test.ts +++ b/packages/binding-http/test/http-client-test.ts @@ -21,7 +21,6 @@ import { suite, test } from "@testdeck/mocha"; import chai, { expect, should } from "chai"; import * as http from "http"; -import { AddressInfo } from "net"; import { Content, DefaultContent, ContentSerdes, createLoggers, ProtocolServer } from "@node-wot/core"; @@ -96,12 +95,19 @@ class TestHttpServer implements ProtocolServer { /** returns server port number and indicates that server is running when larger than -1 */ public getPort(): number { - if (this.server.address() && typeof this.server.address() === "object") { - return (this.server.address()).port; - } else { - // includes address() typeof "string" case, which is only for unix sockets + const address = this.server?.address(); + + if (typeof address === "object") { + return address?.port ?? -1; + } + + const port = parseInt(address); + + if (isNaN(port)) { return -1; } + + return port; } public expose(thing: unknown): Promise { @@ -117,8 +123,10 @@ class TestHttpServer implements ProtocolServer { } public setTestVector(vector: TestVector) { - if (!vector.op) throw new Error("No vector op given"); - if (!vector.form["htv:methodName"]) { + if (vector.op == null) { + throw new Error("No vector op given"); + } + if (vector.form["htv:methodName"] == null) { // TODO also check all array entries switch (vector.op[0]) { case "readproperty": diff --git a/packages/binding-http/test/http-server-test.ts b/packages/binding-http/test/http-server-test.ts index 586532dd9..2529f5ac4 100644 --- a/packages/binding-http/test/http-server-test.ts +++ b/packages/binding-http/test/http-server-test.ts @@ -52,7 +52,7 @@ class HttpServerTest { @test async "should use middleware if provided"() { const middleware: MiddlewareRequestHandler = async (req, res, next) => { - if (req.url?.endsWith("testMiddleware")) { + if (req.url?.endsWith("testMiddleware") ?? false) { res.statusCode = 401; res.end("Unauthorized"); } else { diff --git a/packages/binding-http/test/memory-model.ts b/packages/binding-http/test/memory-model.ts index 856d5ef8f..5a9c65973 100644 --- a/packages/binding-http/test/memory-model.ts +++ b/packages/binding-http/test/memory-model.ts @@ -92,11 +92,11 @@ export default class InMemoryModel implements ClientCredentialsModel, PasswordMo * Get access token. */ - async getAccessToken(bearerToken: string, callback: Callback): Promise { + async getAccessToken(bearerToken: string, callback?: Callback): Promise { const tokens = this.tokens.filter(function (token) { return token.accessToken === bearerToken; }); - if (callback) { + if (callback != null) { callback(null, tokens[0]); } diff --git a/packages/binding-http/test/oauth-token-validation-tests.ts b/packages/binding-http/test/oauth-token-validation-tests.ts index 3f426141d..a4d183745 100644 --- a/packages/binding-http/test/oauth-token-validation-tests.ts +++ b/packages/binding-http/test/oauth-token-validation-tests.ts @@ -81,7 +81,7 @@ describe("OAuth2.0 Validator tests", () => { const token = req.body.token; - if (!token) { + if (token == null) { return res.status(400).end(); } switch (token) {