Gum-Joe / 2Keys

A easy to setup second keyboard, designed for everyone.
GNU General Public License v3.0
11 stars 4 forks source link

Rate limiting of routes to prevent excessive FS ac... #140

Open github-actions[bot] opened 4 years ago

github-actions[bot] commented 4 years ago

Rate limiting of routes to prevent excessive FS access

https://github.com/Gum-Joe/2Keys/blob/d854e718e954180c637e7e7c7406622effb93c89/packages/@twokeys/server/src/routes/api.ts#L46

 */
import { Router } from "express";
import { readFile } from "fs";
import { join } from "path";
import YAML from "yaml";
import { ProjectConfig } from "@twokeys/core/lib/interfaces";
import { loadMainConfig } from "@twokeys/core/lib/config";
import { TWOKEYS_MAIN_CONFIG_DEFAULT_PATH } from "@twokeys/core/lib/constants";
import { AddOnsRegistry } from "@twokeys/addons";
import { loadDetectors } from "../loaders/loadDetectors";
import { loadExecutors } from "../loaders/loadExecutors";
import getTriggerHotkey from "./triggerHotkey";
import { Logger } from "@twokeys/core";

const logger: Logger = new Logger({
    name: "api",
});

const router = Router();

/**
 * Needed so we can use async/await
 */
// TODO: Rate limiting of routes to prevent excessive FS access
// TODO: Remove deprecated routes
export default async function getAPI(projectConfig: ProjectConfig, projectDir: string): Promise<Router> {
    logger.info("Preparing server...");

    // 1: Load config
    // TODO: Watch for changes to project and reload everything
    logger.info("Loading root config...");
    const mainConfig = await loadMainConfig(TWOKEYS_MAIN_CONFIG_DEFAULT_PATH);
    logger.info("Loading add-ons...");
    const registry = new AddOnsRegistry(mainConfig.registry_root);

    // Load add-ons & run startup functions for detectors
    const detectors = await loadDetectors(projectConfig, projectDir, registry);
    const executors = await loadExecutors(registry, projectDir);

    /**
     * Returns to config for the 2Keys project
     */
    router.get("/get/config/project", (req, res) => {
        logger.info("Sending a config copy as JSON...");
        // We can rely on hot reload to ensure it is accurate
        res.statusCode = 200;
        res.json(projectConfig);
    });

    /**
     * Returns config for a detector
     */
    router.get("/get/config/detectors/:detector", (req, res) => {
        const detectorToGet = req. params.detector;
        logger.info(`Requested config for detector ${detectorToGet}`);
        if (detectors.has(detectorToGet)) {
            res.statusCode = 200;
            res.json(detectors.get(detectorToGet));
        } else {
            logger.info(`${detectorToGet} not found!`);
            res.statusCode = 404;
            res.json({
                message: "Not Found"
            });
        }
    });

    /**
     * Returns the config for the 2Keys project
        * @deprecated
     */
    router.get("/get/deprecated/config", (req, res, next) => {
        logger.debug("Sending a config copy as JSON...");
        logger.warn("/get/config is deprecated.");
        readFile(join(process.cwd(), "config.yml"), (err, data) => {
            if (err) {
                return next(err);
            }
            const data_to_send = JSON.stringify(YAML.parse(data.toString()));
            res.setHeader("Content-Type", "application/json");
            res.statusCode = 200;
            res.send(data_to_send);
        });
    });

    /**
     * Trigger a hotkey
     * 
     * Provide these property:
     * ```json
     * {
     *  "hotkey": "^A" // hotkey code to find in keyboard
     *  "event": "up" | "down" | "hold" // OPTIONAL event type
     * }
     * ```
     */
    router.post("/post/trigger/:detector/:keyboard", getTriggerHotkey(detectors, executors));

    /**
     * Trigger a hotkey
     * Info to send:
     * - keyboard: The keyboard name that has been pressed
     * - hotkey: set of keys that have been pressed
     * @deprecated
     */
    /*router.post("/post/depreacted/trigger", async (req, res, next) => {*/
    /**
        * 1: Get hotkey function from config
        * 2: Execute C++ bindings with #Include <root of keyboard>; function()
        */
    // Get vars
    /*const keyboard = req.body.keyboard;
        const hotkey_code = req.body.hotkey;
        const value: EvDevValues = Object.prototype.hasOwnProperty.call(req.body, "value") ? req.body.value : EvDevValues.Down;
        logger.debug(`Got keyboard ${keyboard} and hotkey ${hotkey_code}, with value ${value}`);
        // Parse config
        try {
            const fetched_hotkey = await fetch_hotkey(keyboard, hotkey_code); // Gets hotkey
            let func_to_run: string;

            // Use the value arg to select
            if (typeof fetched_hotkey.func === "object") {
                // Is an object
                logger.debug("Got a multi event hotkey.");
                // Select which function to run
                if (value === EvDevValues.Down) {
                    func_to_run = fetched_hotkey.func.down;
                } else if (value === EvDevValues.Up) {
                    func_to_run = fetched_hotkey.func.up;
                } else {
                    // Stop exec as and error was encountered
                    return next(new TypeError(`The request keyboard event value of ${value} is invalid.  Valid event values are: 0 (Up) & 1 (Down)`));
                }

                // Validate a function actually exists
                if (typeof func_to_run === "undefined") {
                    // Ignore
                    logger.warn(`Ignoring hotkey ${hotkey_code} of value ${value}, as no function to run exists`);
                    res.statusCode = 404;
                    res.send("Hotkey function not found");
                    return;
                }
            } else {
                func_to_run = fetched_hotkey.func;
            }

            // Execute
            run_hotkey(fetched_hotkey.file, func_to_run);

            res.statusCode = 200;
            res.send("OK");
        } catch (err) {
            next(err); // Hand off to error handler
        }
    });*/

    /**
     * Handles keyboard path update
     */
    // TODO: Update this route to use whatever new method for config updates we decide on
    /*router.post("/post/update-keyboard-path", (req, res, next) => {
        const { keyboard, path } = req.body;
        logger.info(`Got update for ${keyboard}, path ${path}`);
        config_loader()
            .then((config) => {
                // Make changes
                config.keyboards[keyboard].path = path;
                // Write
                logger.debug("Writing config...");
                writeFile(CONFIG_FILE, YAML.stringify(config), (err) => {
                    if (err) {
                        return next(err);
                    } else {
                        res.statusCode = 200;
                        res.send("OK");
                    }
                    res.end();
                });
            });
    });*/
    return router;
}
ew file mode 100644
ndex 0000000..58072f4
++ b/packages/@twokeys/server/src/routes/triggerHotkey.ts

7f1bce620b368d6e7c333b8b3092040c1265b84d