diff --git a/.eslintrc.json b/.eslintrc.json index 591518bd..3c8f9494 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,5 +1,10 @@ { "root": true, + "settings": { + "react": { + "version": "detect" + } + }, "ignorePatterns": [ "**/*" ], diff --git a/apps/ove-bridge/src/app/api/features/bridge/service.ts b/apps/ove-bridge/src/app/api/features/bridge/service.ts index 6929d9b0..764625a8 100644 --- a/apps/ove-bridge/src/app/api/features/bridge/service.ts +++ b/apps/ove-bridge/src/app/api/features/bridge/service.ts @@ -115,7 +115,8 @@ export const service: TBridgeService = { try { await createClient(env.HARDWARE[idx]).register.mutate({ pin, - key: env.PUBLIC_KEY + key: env.PUBLIC_KEY, + url: env.URL }); } catch (e) { logger.error(e); diff --git a/apps/ove-bridge/src/env.ts b/apps/ove-bridge/src/env.ts index cfdf59ce..7e103af1 100644 --- a/apps/ove-bridge/src/env.ts +++ b/apps/ove-bridge/src/env.ts @@ -33,7 +33,8 @@ const schema = z.strictObject({ VIDEO_STREAMS: z.array(z.string()).optional(), START_VIDEO_SCRIPT: z.string().optional(), STOP_VIDEO_SCRIPT: z.string().optional(), - GEOMETRY: BoundsSchema.optional() + GEOMETRY: BoundsSchema.optional(), + URL: z.string() }); const staticConfig = { @@ -68,7 +69,8 @@ const defaultConfig: z.infer = { PROTOCOL: "http", HOSTNAME: "localhost", PORT: 4200 - } + }, + URL: "http://localhost:3334" }; const configPath = path.join(app.getPath("userData"), "ove-bridge-config.json"); diff --git a/apps/ove-client/src/env.ts b/apps/ove-client/src/env.ts index 602c6774..e605b6a3 100644 --- a/apps/ove-client/src/env.ts +++ b/apps/ove-client/src/env.ts @@ -14,8 +14,10 @@ const schema = z.strictObject({ HOSTNAME: z.string(), PROTOCOL: z.string() }), + BRIDGE_URL: z.string().optional(), LOG_LEVEL: z.number().optional(), - AUTHORISED_CREDENTIALS: z.array(z.string()) + AUTHORISED_CREDENTIALS: z.string().optional(), + AUTH_ERROR_LIMIT: z.number() }); const staticConfig = { @@ -25,11 +27,10 @@ const staticConfig = { PIN_UPDATE_DELAY: 30_000, TITLE: "next-ove client", DESCRIPTION: "Control interface for observatory rendering nodes.", - CHECKSITE: "www.google.com" + CHECKSITE: "www.google.com", } as const; const defaultConfig: z.infer = { - AUTHORISED_CREDENTIALS: [], PORT: 3334, HOSTNAME: "localhost", PROTOCOL: "http", @@ -37,7 +38,8 @@ const defaultConfig: z.infer = { PORT: 4201, HOSTNAME: "localhost", PROTOCOL: "http" - } + }, + AUTH_ERROR_LIMIT: 3 }; const configPath = path.join(app.getPath("userData"), "ove-client-config.json"); diff --git a/apps/ove-client/src/server/auth/controller.ts b/apps/ove-client/src/server/auth/controller.ts index 5143313a..eec588fd 100644 --- a/apps/ove-client/src/server/auth/controller.ts +++ b/apps/ove-client/src/server/auth/controller.ts @@ -1,12 +1,27 @@ -import { env, logger } from "../../env"; +/* global clearInterval */ + import { state } from "../state"; +import { env, logger } from "../../env"; -export default ({ - register: (pin: string, key: string) => { +const controller = { + register: (pin: string, key: string, url: string) => { logger.info("POST /register - authenticating device"); - if (pin === state.pin && !env.AUTHORISED_CREDENTIALS.includes(key)) { - env.AUTHORISED_CREDENTIALS.push(key); + + if (state.authErrors <= env.AUTH_ERROR_LIMIT && + pin === state.pin && env.AUTHORISED_CREDENTIALS === undefined) { + env.AUTHORISED_CREDENTIALS = key; + env.BRIDGE_URL = url; } + + if (state.pinUpdateHandler !== null) { + clearInterval(state.pinUpdateHandler); + } + state.pin = ""; + state.pinUpdateCallback = null; + state.pinUpdateHandler = null; + return pin === state.pin; } -}); +}; + +export default controller; diff --git a/apps/ove-client/src/server/auth/router.ts b/apps/ove-client/src/server/auth/router.ts index 05ab7965..8c0fe2fb 100644 --- a/apps/ove-client/src/server/auth/router.ts +++ b/apps/ove-client/src/server/auth/router.ts @@ -1,27 +1,18 @@ -import { z } from "zod"; -import { procedure, router } from "../trpc"; import { - type OVEException, OVEExceptionSchema, StatusSchema } from "@ove/ove-types"; -import controller from "./controller"; +import { z } from "zod"; import { logger } from "../../env"; - -const safe = async (handler: () => T): Promise => { - try { - return handler(); - } catch (e) { - logger.error(e); - return { oveError: (e as Error).message }; - } -}; +import controller from "./controller"; +import { safe } from "@ove/ove-utils"; +import { procedure, router } from "../trpc"; export const authRouter = router({ register: procedure .meta({ openapi: { method: "POST", path: "/register" } }) - .input(z.object({ pin: z.string(), key: z.string() })) + .input(z.object({ pin: z.string(), key: z.string(), url: z.string() })) .output(z.union([OVEExceptionSchema, StatusSchema])) - .mutation(({ input: { pin, key } }) => - safe(() => controller.register(pin, key))) + .mutation(({ input: { pin, key, url } }) => + safe(logger, async () => controller.register(pin, key, url))) }); diff --git a/apps/ove-client/src/server/hardware/controller.ts b/apps/ove-client/src/server/hardware/controller.ts index 6ec47c8a..9583b5b3 100644 --- a/apps/ove-client/src/server/hardware/controller.ts +++ b/apps/ove-client/src/server/hardware/controller.ts @@ -14,9 +14,13 @@ export const init = ( closeWindow: (windowId: string) => boolean | null, triggerIPC: OutboundAPI ) => { + // TODO: if authorised, load rendering page service.init(createWindow, takeScreenshots, closeWindow); - state.pinUpdateCallback = triggerIPC["updatePin"]; - state.pinUpdateHandler = setInterval(updatePin, env.PIN_UPDATE_DELAY); + + if (env.AUTHORISED_CREDENTIALS === undefined) { + state.pinUpdateCallback = triggerIPC["updatePin"]; + state.pinUpdateHandler = setInterval(updatePin, env.PIN_UPDATE_DELAY); + } }; const controller: TClientService = { diff --git a/apps/ove-client/src/server/state.ts b/apps/ove-client/src/server/state.ts index 89416b77..38fd1ee9 100644 --- a/apps/ove-client/src/server/state.ts +++ b/apps/ove-client/src/server/state.ts @@ -1,5 +1,6 @@ /* global NodeJS */ +import { env } from "../env"; import { type Browser } from "@ove/ove-types"; type State = { @@ -7,24 +8,26 @@ type State = { pin: string pinUpdateCallback: ((event: string) => void) | null pinUpdateHandler: NodeJS.Timer | null + authErrors: number }; -const generatePin = () => Array(4) +const generatePin = () => env.AUTHORISED_CREDENTIALS === undefined ? Array(4) .fill(0) .map(() => Math.floor(Math.random() * 10)) - .join(""); + .join("") : ""; export const state: State = { browsers: new Map(), pin: "initialising", pinUpdateCallback: null, - pinUpdateHandler: null + pinUpdateHandler: null, + authErrors: 0 }; -export const updatePin = () => { +export const updatePin = env.AUTHORISED_CREDENTIALS === undefined ? () => { state.pin = generatePin(); if (state.pinUpdateCallback === null) { throw new Error("Missing pin update callback"); } state.pinUpdateCallback(state.pin); -}; +} : () => {}; diff --git a/apps/ove-client/src/server/trpc.ts b/apps/ove-client/src/server/trpc.ts index fe78ab7e..55b9c8af 100644 --- a/apps/ove-client/src/server/trpc.ts +++ b/apps/ove-client/src/server/trpc.ts @@ -12,7 +12,7 @@ export const mergeRouters = trpc.mergeRouters; export const procedure = trpc.procedure; const isAuthed = trpc.middleware(({ ctx: { user }, next }) => { - if (user === null || !env.AUTHORISED_CREDENTIALS.includes(user)) { + if (user !== env.AUTHORISED_CREDENTIALS) { throw new TRPCError({ code: "UNAUTHORIZED" }); } diff --git a/docs/api/v1/client.swagger.json b/docs/api/v1/client.swagger.json index 4e8f7986..27fc030e 100644 --- a/docs/api/v1/client.swagger.json +++ b/docs/api/v1/client.swagger.json @@ -619,11 +619,15 @@ }, "key": { "type": "string" + }, + "url": { + "type": "string" } }, "required": [ "pin", - "key" + "key", + "url" ], "additionalProperties": false } diff --git a/libs/ove-utils/src/lib/ove-utils.ts b/libs/ove-utils/src/lib/ove-utils.ts index f375fbdf..f640c2e8 100644 --- a/libs/ove-utils/src/lib/ove-utils.ts +++ b/libs/ove-utils/src/lib/ove-utils.ts @@ -59,6 +59,10 @@ export const safe = async ( return await handler(); } catch (e) { logger.error(e); - return { oveError: (e as Error).message }; + if (typeof e === "string") return { oveError: e }; + else if (typeof e === "object" && e !== null && "message" in e) { + return { oveError: JSON.stringify(e.message) }; + } + return { oveError: `UNKNOWN: ${JSON.stringify(e)}` }; } }; diff --git a/libs/ui-reorderable-list/src/lib/reorderable-list/components/reorderable-item.jsx b/libs/ui-reorderable-list/src/lib/reorderable-list/components/reorderable-item.jsx index eb9c0c63..c3fda099 100644 --- a/libs/ui-reorderable-list/src/lib/reorderable-list/components/reorderable-item.jsx +++ b/libs/ui-reorderable-list/src/lib/reorderable-list/components/reorderable-item.jsx @@ -1,5 +1,3 @@ -/* global ReactElement */ - import React, { Component, createRef } from "react"; import PropTypes from "prop-types"; import {