Skip to content

Commit

Permalink
chore(binding-http): enable eslint/strict-boolean-expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
JKRhb committed Sep 22, 2023
1 parent bd247a0 commit ad98db1
Show file tree
Hide file tree
Showing 16 changed files with 89 additions and 74 deletions.
5 changes: 4 additions & 1 deletion packages/binding-http/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
{
"extends": "../../.eslintrc.js"
"extends": "../../.eslintrc.js",
"rules": {
"@typescript-eslint/strict-boolean-expressions": ["error"]
}
}
21 changes: 11 additions & 10 deletions packages/binding-http/src/credential.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
const headers = this.getHeaders(false, {}, "");
const headers = this.getHeaders(false, {});
const request = {
headers: headers,
method: "GET",
Expand All @@ -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]);
Expand Down
2 changes: 1 addition & 1 deletion packages/binding-http/src/http-client-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ export default class HttpClient implements ProtocolClient {

public async stop(): Promise<void> {
// 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<TD.SecurityScheme>, credentials?: unknown): boolean {
Expand Down
24 changes: 8 additions & 16 deletions packages/binding-http/src/http-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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] },
Expand Down
9 changes: 5 additions & 4 deletions packages/binding-http/src/oauth-token-validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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();
}
Expand Down Expand Up @@ -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;
}

Expand Down
6 changes: 3 additions & 3 deletions packages/binding-http/src/routes/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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}"`);
Expand Down
13 changes: 9 additions & 4 deletions packages/binding-http/src/routes/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", "*");
Expand Down Expand Up @@ -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", "*");
Expand Down
4 changes: 2 additions & 2 deletions packages/binding-http/src/routes/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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}"`);
Expand Down
4 changes: 2 additions & 2 deletions packages/binding-http/src/routes/property-observe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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}"`);
Expand Down
8 changes: 4 additions & 4 deletions packages/binding-http/src/routes/property.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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}"`);
Expand All @@ -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);

Expand Down
35 changes: 20 additions & 15 deletions packages/binding-http/src/routes/thing-description.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/binding-http/test/http-client-basic-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 15 additions & 7 deletions packages/binding-http/test/http-client-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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 (<AddressInfo>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<void> {
Expand All @@ -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":
Expand Down
2 changes: 1 addition & 1 deletion packages/binding-http/test/http-server-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading

0 comments on commit ad98db1

Please sign in to comment.