packages/sqrl-cli/src/SqrlServer.ts (95 lines of code) (raw):

/** * Copyright 2018 Twitter, Inc. * Licensed under the Apache License, Version 2.0 * http://www.apache.org/licenses/LICENSE-2.0 */ import * as micro from "micro"; import * as microQuery from "micro-query"; // tslint:disable-next-line:no-submodule-imports (it is the documented suggestion) import * as dispatch from "micro-route/dispatch"; import { IncomingMessage, ServerResponse, Server } from "http"; import { Context, FeatureMap, Executable, Execution, FiredRule } from "sqrl"; import { CliManipulator } from "sqrl-cli-functions"; function userInvariant(cond, message) { if (!cond) { throw micro.createError(400, message); } } interface ApiRulesResponse { [ruleName: string]: { reason: string; }; } interface ApiResponse { /** Overall block/allow verdict for the action */ allow: boolean; /** Details about why the verdict was reached */ verdict: { blockRules: string[]; whitelistRules: string[]; }; rules: ApiRulesResponse; /** Returns features requested via the API */ features?: FeatureMap; } async function run( ctx: Context, executable: Executable, req: IncomingMessage, res: ServerResponse ) { const query = microQuery(req); const featureTimeoutMs = parseInt(query.timeoutMs || "1000", 10); userInvariant(!isNaN(featureTimeoutMs), "timeoutMs was not a valid integer"); const inputs = await micro.json(req, { limit: "128mb" }); const manipulator = new CliManipulator(); const execution: Execution = await executable.execute(ctx, { manipulator, inputs, featureTimeoutMs, }); await execution.fetchFeature("SqrlExecutionComplete"); const rules: ApiRulesResponse = {}; function ruleToName(rule: FiredRule) { rules[rule.name] = { reason: rule.reason, }; return rule.name; } const rv: ApiResponse = { allow: !manipulator.wasBlocked(), verdict: { blockRules: manipulator.blockedRules.map(ruleToName) || [], whitelistRules: manipulator.whitelistedRules.map(ruleToName) || [], }, rules, }; if (query.features) { const featureNames = query.features.split(","); try { rv.features = {}; await Promise.all( featureNames.map(async (featureName) => { rv.features[featureName] = await execution.fetchValue(featureName); }) ); } catch (e) { throw micro.createError(500, e.message, e); } } await manipulator.mutate(ctx); res.setHeader("Content-Type", "application/json"); if (query.hasOwnProperty("pretty")) { return JSON.stringify(rv, undefined, 2) + "\n"; } else { return JSON.stringify(rv); } } async function deleteRoute( ctx: Context, req: IncomingMessage, res: ServerResponse ) { // @TODO: Delete entity throw micro.createError(500, "Not implemented\n"); } export function createSqrlServer(ctx: Context, executable: Executable): Server { const router = dispatch() .dispatch("/run", ["POST"], (req, res) => run(ctx, executable, req, res)) .dispatch("/delete", ["POST"], (req, res) => deleteRoute(ctx, req, res)) .otherwise(async (req, res) => { throw micro.createError(404, "Route not found\n"); }); return micro(router); } export type ServerWaitCallback = (props: { server: Server }) => Promise<void>;