From d8c04cba1472d580ce31d998bc1c74af3045e62b Mon Sep 17 00:00:00 2001 From: timonson Date: Wed, 24 Apr 2024 23:43:26 +0200 Subject: [PATCH] Updatge --- functions/crypto/hash.ts | 3 +++ functions/deps.ts | 15 ++++++++------- functions/env.ts | 27 ++++++++++----------------- functions/jwt.ts | 6 ++++++ functions/path.ts | 14 +++++++++++++- functions/subprocess.ts | 4 ++-- middlewares/deps.ts | 30 +++++++++++++++++++----------- middlewares/fetch.ts | 12 ++++++------ middlewares/log.ts | 21 +++++++++++++++------ middlewares/mod.ts | 1 + middlewares/rpc.ts | 36 ++++++++++++++++++++++++++++++++++++ mod.ts | 38 ++++++++++++++++++++++++++------------ 12 files changed, 145 insertions(+), 62 deletions(-) create mode 100644 middlewares/rpc.ts diff --git a/functions/crypto/hash.ts b/functions/crypto/hash.ts index b466eba..d530e8a 100644 --- a/functions/crypto/hash.ts +++ b/functions/crypto/hash.ts @@ -50,6 +50,9 @@ export async function generateSecret( password: string | Uint8Array, salt: Uint8Array, ): Promise { + if (password.length < 1) { + throw new Error("The password's length must be at least one."); + } return appendSaltToHash( await derivePasswordHashWithEncryption(password, salt), salt, diff --git a/functions/deps.ts b/functions/deps.ts index abf3993..dc0747e 100644 --- a/functions/deps.ts +++ b/functions/deps.ts @@ -10,14 +10,14 @@ export { join, normalize, resolve, -} from "https://deno.land/std@0.209.0/path/mod.ts"; -export { ensureDir, ensureFile } from "https://deno.land/std@0.209.0/fs/mod.ts"; +} from "https://deno.land/std@0.223.0/path/mod.ts"; +export { ensureDir, ensureFile } from "https://deno.land/std@0.223.0/fs/mod.ts"; export { decode as decodeFromHex, encode as encodeToHex, -} from "https://deno.land/std@0.209.0/encoding/hex.ts"; -export * as base64 from "https://deno.land/std@0.209.0/encoding/base64.ts"; -export * as semver from "https://deno.land/std@0.209.0/semver/mod.ts"; +} from "https://deno.land/std@0.223.0/encoding/hex.ts"; +export * as base64 from "https://deno.land/std@0.223.0/encoding/base64.ts"; +export * as semver from "https://deno.land/std@0.223.0/semver/mod.ts"; /** * Zaubrik @@ -29,11 +29,12 @@ export { type Payload, verify, type VerifyOptions, -} from "https://deno.land/x/djwt@v3.0.1/mod.ts"; +} from "https://deno.land/x/djwt@v3.0.2/mod.ts"; export { isNumber, isObject, + isPresent, isString, isUndefined, isUrl, -} from "https://dev.zaubrik.com/sorcery@v0.1.4/type.js"; +} from "https://dev.zaubrik.com/sorcery@v0.1.5/type.js"; diff --git a/functions/env.ts b/functions/env.ts index 4f90d22..baaf998 100644 --- a/functions/env.ts +++ b/functions/env.ts @@ -1,17 +1,4 @@ -/** - * Removes falsey values. Copy/import it from sorcery? - * https://github.com/robertmassaioli/ts-is-present/blob/master/src/index.ts - * ```js - * const foo: = [2,3, null, 4]; - * const bar = foo.filter(isPresent); // number[] - * ``` - * @template T - * @param {T|undefined|null} input - * @returns {input is T} - */ -export function isPresent(input: T | undefined | null): input is T { - return input !== undefined && input !== null; -} +import { isPresent } from "./deps.ts"; export function assertEnv(env: string[]): string[] { const result = env.map((v) => Deno.env.get(v)).filter(isPresent); @@ -24,6 +11,12 @@ export function assertEnv(env: string[]): string[] { } } -export const isProduction = JSON.parse( - Deno.env.get("IS_PRODUCTION") ?? "false", -); +export function isProduction() { + return JSON.parse( + Deno.env.get("IS_PRODUCTION") ?? "false", + ); +} + +export function getHomeDirectory() { + return Deno.env.get("HOME") || Deno.env.get("USERPROFILE"); +} diff --git a/functions/jwt.ts b/functions/jwt.ts index 6fd41bb..c414c44 100644 --- a/functions/jwt.ts +++ b/functions/jwt.ts @@ -40,6 +40,12 @@ function isUpdateInput(input: any): input is UpdateInput { } } +export function isCryptoKeyOrUpdateInput( + input: unknown, +): input is CryptoKeyOrUpdateInput { + return isCryptoKey(input) || isUpdateInput(input); +} + export function getJwtFromBearer(headers: Headers): string { const authHeader = headers.get("Authorization"); if (authHeader === null) { diff --git a/functions/path.ts b/functions/path.ts index 5c3bd10..0d61e62 100644 --- a/functions/path.ts +++ b/functions/path.ts @@ -52,7 +52,19 @@ export function getPathnameFs(urlOrPath: URL | string): string { * @return {string} */ export function resolveMainModule(relativePath: string): string { - return getPathnameFs(new URL(relativePath, Deno.mainModule)); + return getPathnameFs(resolveMainModuleToUrl(relativePath)); +} + +/** + * Takes a `string` a pathname to the main module. + * ```ts + * resolveMainModuleToUrl("./home/foo"); + * ``` + * @param {string} relativePath + * @return {URL} + */ +export function resolveMainModuleToUrl(relativePath: string): URL { + return new URL(relativePath, Deno.mainModule); } /** diff --git a/functions/subprocess.ts b/functions/subprocess.ts index 5100e55..c9a698d 100644 --- a/functions/subprocess.ts +++ b/functions/subprocess.ts @@ -1,6 +1,6 @@ import { ensureFile } from "./deps.ts"; import { decode } from "./util.ts"; -import { getPathnameFs } from "./path.ts"; +import { getPathnameFs, resolveMainModule } from "./path.ts"; type CommandOptions = ConstructorParameters[1]; @@ -38,7 +38,7 @@ export async function spawnSubprocess( } else { const err = decode(stderr) ? decode(stderr) : decode(stdout); if (options?.debug) { - const path = getPathnameFs(new URL(options.debug, Deno.mainModule)); + const path = getPathnameFs(resolveMainModule(options.debug)); await ensureFile(path); await Deno.writeTextFile(path, `${JSON.stringify([err])},`, { append: true, diff --git a/middlewares/deps.ts b/middlewares/deps.ts index 217dba7..2c9f15a 100644 --- a/middlewares/deps.ts +++ b/middlewares/deps.ts @@ -7,15 +7,15 @@ export { isAbsolute, join, normalize, -} from "https://deno.land/std@0.209.0/path/mod.ts"; +} from "https://deno.land/std@0.223.0/path/mod.ts"; export { ensureFile, ensureFileSync, -} from "https://deno.land/std@0.209.0/fs/mod.ts"; +} from "https://deno.land/std@0.223.0/fs/mod.ts"; export * from "https://deno.land/std@0.206.0/http/http_errors.ts"; -export * as semver from "https://deno.land/std@0.209.0/semver/mod.ts"; export * from "https://deno.land/std@0.209.0/http/http_status.ts"; -export { serveFile } from "https://deno.land/std@0.209.0/http/file_server.ts"; +export { serveFile } from "https://deno.land/std@0.223.0/http/file_server.ts"; +export * as semver from "https://deno.land/std@0.223.0/semver/mod.ts"; /** * mixed @@ -27,7 +27,7 @@ export { type Payload, verify, type VerifyOptions, -} from "https://deno.land/x/djwt@v3.0.1/mod.ts"; +} from "https://deno.land/x/djwt@v3.0.2/mod.ts"; export { type ClientOptions, type SendConfig, @@ -43,24 +43,32 @@ export { Context, createHandler, type Middleware, + type ServerHandlerOptions, } from "https://dev.zaubrik.com/composium@v0.1.1/mod.ts"; +export { + type AuthInput, + type Methods, + type Options, + respond, +} from "https://dev.zaubrik.com/schicksal@v0.1.3/server/response.ts"; + export { mergeUrl, type UrlProperties, -} from "https://dev.zaubrik.com/sorcery@v0.1.4/path.js"; -export { copyResponse } from "https://dev.zaubrik.com/sorcery@v0.1.4/response.js"; +} from "https://dev.zaubrik.com/sorcery@v0.1.5/url.js"; +export { copyResponse } from "https://dev.zaubrik.com/sorcery@v0.1.5/response.js"; export { isEmpty, -} from "https://dev.zaubrik.com/sorcery@v0.1.4/collections/length.js"; +} from "https://dev.zaubrik.com/sorcery@v0.1.5/collections/length.js"; export { equals, isFalse, -} from "https://dev.zaubrik.com/sorcery@v0.1.4/booleans/equality.js"; +} from "https://dev.zaubrik.com/sorcery@v0.1.5/booleans/equality.js"; export { hasNotProperty, hasProperty, hasPropertyOf, -} from "https://dev.zaubrik.com/sorcery@v0.1.4/objects/membership.js"; +} from "https://dev.zaubrik.com/sorcery@v0.1.5/objects/membership.js"; export { isDefined, isError, @@ -72,4 +80,4 @@ export { isString, isUndefined, isUrl, -} from "https://dev.zaubrik.com/sorcery@v0.1.4/type.js"; +} from "https://dev.zaubrik.com/sorcery@v0.1.5/type.js"; diff --git a/middlewares/fetch.ts b/middlewares/fetch.ts index 4666445..9ef8121 100644 --- a/middlewares/fetch.ts +++ b/middlewares/fetch.ts @@ -11,18 +11,18 @@ import { getSubdomainPath } from "./subdomain.ts"; type Options = { hasSubdomainDirectory?: boolean; - disableCopying?: boolean; + isCopy?: boolean; }; /** * A curried middleware which fetches and returns a `Response` from another or - * partial `URL` object. The option `disableCopying` returns the original and + * partial `URL` object. The option `isCopy` returns a copy and not the original, * unmutable `Response`. The option `hasSubdomainDirectory` moves subdomains to * the pathname. */ export function fetchResponse( urlOrProps?: UrlProperties, - { hasSubdomainDirectory, disableCopying }: Options = {}, + { hasSubdomainDirectory, isCopy = true }: Options = {}, ) { return async (ctx: C): Promise => { try { @@ -32,10 +32,10 @@ export function fetchResponse( } const newRequest = new Request(url.href, ctx.request); const response = await fetch(newRequest); - if (disableCopying) { - ctx.response = response; + if (isCopy) { + ctx.response = copyResponse(response); } else { - ctx.response = copyResponse(ctx.response); + ctx.response = response; } return ctx; } catch (error) { diff --git a/middlewares/log.ts b/middlewares/log.ts index 6a8ea0f..3502cf4 100644 --- a/middlewares/log.ts +++ b/middlewares/log.ts @@ -71,7 +71,7 @@ function logWithOptions(path: string, options: LoggerOptions) { }; } -type LoggerOptions = { +export type LoggerOptions = { print?: boolean; file?: boolean; debug?: boolean; @@ -83,13 +83,13 @@ type LoggerOptions = { * ``` */ export function logger( - url: string | URL = "access.log", + path: string | URL = "access.log", { print = true, file = true, debug = false }: LoggerOptions = {}, ) { - const path = isUrl(url) || isAbsolute(url) - ? getPathnameFs(url) - : resolveMainModule("./" + join(".log/", url)); - const log = logWithOptions(path, { print, file, debug }); + const absolutePath = isUrl(path) || isAbsolute(path) + ? getPathnameFs(path) + : resolveMainModule("./" + join(".log/", path)); + const log = logWithOptions(absolutePath, { print, file, debug }); const generator = queue(log); return (ctx: C): C => { const logObject = createLog(ctx); @@ -97,3 +97,12 @@ export function logger( return ctx; }; } + +export function logCtx(property?: keyof Context) { + return (ctx: C): C => { + const value = property ? ctx[property] : ctx; + const prefix = property ? `ctx["${property}"]:` : "ctx:"; + console.log(prefix, value); + return ctx; + }; +} diff --git a/middlewares/mod.ts b/middlewares/mod.ts index 7baa90f..354eb8e 100644 --- a/middlewares/mod.ts +++ b/middlewares/mod.ts @@ -4,6 +4,7 @@ export * from "./fetch.ts"; export * from "./jwt.ts"; export * from "./log.ts"; export * from "./repository.ts"; +export * from "./rpc.ts"; export * from "./smtp.ts"; export * from "./static.ts"; export * from "./subdomain.ts"; diff --git a/middlewares/rpc.ts b/middlewares/rpc.ts new file mode 100644 index 0000000..17f7e4d --- /dev/null +++ b/middlewares/rpc.ts @@ -0,0 +1,36 @@ +import { + type AuthInput, + type Context, + isFunction, + type Methods, + type Options, + respond, +} from "./deps.ts"; +import { isCryptoKeyOrUpdateInput, verifyJwt } from "../functions/jwt.ts"; + +export function rpcRespond( + methods: Methods, + options: Options & { methodsUrl?: URL } = {}, + authInput?: AuthInput, +) { + const verificationInputOrUndefined = authInput?.verification; + const verify = isFunction(verificationInputOrUndefined) + ? verificationInputOrUndefined + : isCryptoKeyOrUpdateInput(verificationInputOrUndefined) + ? verifyJwt(verificationInputOrUndefined) + : undefined; + return async (ctx: C) => { + const rpcMethods = options.methodsUrl + ? { ...methods, ...await import(options.methodsUrl.pathname) } + : methods; + const newAuthInput = authInput + ? verify ? { ...authInput, verification: verify } : authInput + : undefined; + ctx.response = await respond( + rpcMethods, + options, + newAuthInput, + )(ctx.request); + return ctx; + }; +} diff --git a/mod.ts b/mod.ts index ea17163..dfc3265 100644 --- a/mod.ts +++ b/mod.ts @@ -1,12 +1,19 @@ export * from "./functions/mod.ts"; export * from "./middlewares/mod.ts"; -import { Context, createHandler, type Middleware } from "./middlewares/deps.ts"; -import { fallBack, logger } from "./middlewares/mod.ts"; +import { + Context, + createHandler, + type Middleware, + type ServerHandlerOptions, +} from "./middlewares/deps.ts"; +import { fallBack, logger, type LoggerOptions } from "./middlewares/mod.ts"; +import { resolveMainModule } from "./functions/path.ts"; -type Options = - & { serveOptions?: Deno.ServeOptions & Deno.ServeTlsOptions } - & { logFile?: Parameters[0] }; +export type DefaultHandlerOptions = + & { handlerOptions?: ServerHandlerOptions> } + & { loggerOptions?: LoggerOptions } + & { hostname: string }; /** * Starts a default HTTP server and handles incoming requests using @@ -17,17 +24,24 @@ type Options = * @example * ```ts * import { serveStatic } from "./middlewares/mod.ts"; - * serve(serveStatic("./examples/static/")); + * createDefaultHandler(serveStatic("./examples/static/"), { hostname: "zaurbik.de" }); * ``` */ -export function serve( +export function createDefaultHandler( tryMiddleware: Middleware, - options: Options = {}, + options: DefaultHandlerOptions, ) { + const pid = { + path: resolveMainModule("./.pid"), + name: options.hostname, + append: true, + ...options.handlerOptions?.pid, + }; + const handlerOptions = { ...options.handlerOptions, pid }; + const loggerOptions = { debug: true, ...options.loggerOptions }; + const handler = createHandler(Context)(tryMiddleware)(fallBack)( - logger(options.logFile), + logger(options.hostname, loggerOptions), ); - return options.serveOptions - ? Deno.serve(options.serveOptions, handler) - : Deno.serve(handler); + return handler; }