From 669767655d3037d0637ace608b8f8d6cc4190a1c Mon Sep 17 00:00:00 2001 From: timonson Date: Wed, 30 Aug 2023 21:43:56 +0200 Subject: [PATCH] Update --- .github/workflows/test.yml | 2 +- server/auth.ts | 33 ++++++++------------------------- server/creation.ts | 32 ++++++++++++++++---------------- server/custom_error.ts | 2 +- server/deps.ts | 9 +++++++-- server/response.ts | 27 +++++++++++++++++++-------- server/response_test.ts | 17 +++++++++++------ server/validation.ts | 12 ++++++------ test_deps.ts | 4 ++-- 9 files changed, 71 insertions(+), 67 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e35d761..106d1c1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: - name: Setup Deno run: | - curl -fsSL https://deno.land/x/install/install.sh | sh ${{ matrix.deno == 'old' && '-s v1.34.0' || '' }} + curl -fsSL https://deno.land/x/install/install.sh | sh ${{ matrix.deno == 'old' && '-s v1.36.3' || '' }} echo "$HOME/.deno/bin" >> $${{ runner.os == 'Windows' && 'env:' || '' }}GITHUB_PATH - name: Upgrade to Deno canary if: matrix.deno == 'canary' diff --git a/server/auth.ts b/server/auth.ts index 77cebfb..0ad5f7a 100644 --- a/server/auth.ts +++ b/server/auth.ts @@ -1,21 +1,11 @@ import { authErrorData } from "./error_data.ts"; -import { type Payload, verify } from "./deps.ts"; -import type { CreationInput } from "./creation.ts"; +import { getJwtFromBearer, type Payload } from "./deps.ts"; +import { type CreationInput } from "./creation.ts"; +import { type AuthData } from "./response.ts"; -function getJwtFromBearer(headers: Headers): string { - const authHeader = headers.get("Authorization"); - if (authHeader === null) { - throw new Error("No 'Authorization' header."); - } else if (!authHeader.startsWith("Bearer ") || authHeader.length <= 7) { - throw new Error("Invalid 'Authorization' header."); - } else { - return authHeader.slice(7); - } -} - -export async function verifyJwt( - { validationObject, methods, options, headers }: CreationInput & { - headers: Headers; +export async function verifyJwtForSelectedMethods( + { validationObject, methods, options, authData }: CreationInput & { + authData: AuthData; }, ): Promise { if (validationObject.isError) return { validationObject, methods, options }; @@ -27,15 +17,8 @@ export async function verifyJwt( : methodsOrUndefined?.test(validationObject.method) ) { try { - if (!(options.auth.key instanceof CryptoKey)) { - throw new Error("Authentication requires a CryptoKey."); - } - const jwt = getJwtFromBearer(headers); - const payload = await verify( - jwt, - options.auth.key, - options.auth.options, - ); + const jwt = getJwtFromBearer(authData.headers); + const payload = await (await authData.verify!)(jwt); return { validationObject, methods, options, payload }; } catch (err) { return { diff --git a/server/creation.ts b/server/creation.ts index 915813e..4ad194a 100644 --- a/server/creation.ts +++ b/server/creation.ts @@ -1,10 +1,10 @@ import { internalErrorData } from "./error_data.ts"; import { CustomError } from "./custom_error.ts"; -import { verifyJwt } from "./auth.ts"; -import type { RpcBatchResponse, RpcResponse } from "../rpc_types.ts"; -import type { ValidationObject } from "./validation.ts"; -import type { Methods, Options } from "./response.ts"; -import type { Payload } from "./deps.ts"; +import { verifyJwtForSelectedMethods } from "./auth.ts"; +import { type RpcBatchResponse, type RpcResponse } from "../rpc_types.ts"; +import { type ValidationObject } from "./validation.ts"; +import { type AuthData, type Methods, type Options } from "./response.ts"; +import { type Payload } from "./deps.ts"; export type CreationInput = { validationObject: ValidationObject; @@ -33,19 +33,19 @@ async function executeMethods( ? await method(validationObject.params) : await method(validationObject.params, additionalArgument), }; - } catch (err) { - if (err instanceof CustomError) { + } catch (error) { + if (error instanceof CustomError) { return { - code: err.code, - message: err.message, + code: error.code, + message: error.message, id: validationObject.id, - data: err.data, + data: error.data, isError: true, }; } return { id: validationObject.id, - data: options.publicErrorStack ? err.stack : undefined, + data: options.publicErrorStack ? error.stack : undefined, isError: true, ...internalErrorData, }; @@ -91,27 +91,27 @@ export async function createRpcResponseOrBatch( validationObjectOrBatch: ValidationObject | ValidationObject[], methods: Methods, options: Options, - headers: Headers, + authData: AuthData, ): Promise { return Array.isArray(validationObjectOrBatch) ? await cleanBatch( validationObjectOrBatch.map(async (validationObject) => createRpcResponse( - await verifyJwt({ + await verifyJwtForSelectedMethods({ validationObject, methods, options, - headers, + authData, }), ) ), ) : await createRpcResponse( - await verifyJwt({ + await verifyJwtForSelectedMethods({ validationObject: validationObjectOrBatch, methods, options, - headers, + authData, }), ); } diff --git a/server/custom_error.ts b/server/custom_error.ts index 7b5ae33..79e8d62 100644 --- a/server/custom_error.ts +++ b/server/custom_error.ts @@ -1,4 +1,4 @@ -import type { JsonValue } from "../rpc_types.ts"; +import { type JsonValue } from "../rpc_types.ts"; export class CustomError extends Error { code: number; diff --git a/server/deps.ts b/server/deps.ts index 55402cc..603799c 100644 --- a/server/deps.ts +++ b/server/deps.ts @@ -1,5 +1,10 @@ export { type Payload, - verify, type VerifyOptions, -} from "https://deno.land/x/djwt@v2.9.1/mod.ts"; +} from "https://dev.zaubrik.com/djwt@v2.9.1/mod.ts"; + +export { + type CryptoKeyOrUpdateInput, + getJwtFromBearer, + verifyJwt, +} from "https://dev.zaubrik.com/portal@v0.2.4/functions/mod.ts"; diff --git a/server/response.ts b/server/response.ts index cfe28bc..7f8d8f4 100644 --- a/server/response.ts +++ b/server/response.ts @@ -1,30 +1,41 @@ import { createRpcResponseOrBatch } from "./creation.ts"; import { validateRequest } from "./validation.ts"; -import type { JsonValue } from "../rpc_types.ts"; -import type { VerifyOptions } from "./deps.ts"; +import { type JsonValue } from "../rpc_types.ts"; +import { + type CryptoKeyOrUpdateInput, + verifyJwt, + type VerifyOptions, +} from "./deps.ts"; export type Methods = { // deno-lint-ignore no-explicit-any [method: string]: (...arg: any[]) => JsonValue | Promise; }; export type Options = { - // Add headers to the default header '{"content-type" : "application/json"}': + // Add response headers: headers?: Headers; - // include or don't include server side error messages in response: + // Include server side error messages in response: publicErrorStack?: boolean; // Additional arguments ('payload' is reserved for jwt payload!): args?: Record; - // for jwt verification: + // Verify JWTs: auth?: { - key: CryptoKey; + input: CryptoKeyOrUpdateInput; methods?: (keyof Methods)[] | RegExp; options?: VerifyOptions; }; }; +export type AuthData = { + headers: Headers; + verify?: ReturnType; +}; export function respond(methods: Methods, options: Options = {}) { + const verify = options.auth + ? verifyJwt(options.auth.input, options.auth.options) + : undefined; return async (request: Request): Promise => { - const requestHeaders = request.headers; + const authData = { verify, headers: request.headers }; const validationObjectOrBatch = validateRequest( await request.text(), methods, @@ -34,7 +45,7 @@ export function respond(methods: Methods, options: Options = {}) { validationObjectOrBatch, methods, options, - requestHeaders, + authData, ); if (rpcResponseOrBatchOrNull === null) { return new Response(null, { status: 204, headers }); diff --git a/server/response_test.ts b/server/response_test.ts index f03cc98..10ef7d5 100644 --- a/server/response_test.ts +++ b/server/response_test.ts @@ -33,12 +33,17 @@ const methods = { }, }; -const key = await crypto.subtle.generateKey( +const cryptoKey = await crypto.subtle.generateKey( { name: "HMAC", hash: "SHA-384" }, true, ["sign", "verify"], ); -const jwt = await create({ alg: "HS384", typ: "JWT" }, { user: "Bob" }, key); +const algorithm = "HS384" as const; +const jwt = await create( + { alg: algorithm, typ: "JWT" }, + { user: "Bob" }, + cryptoKey, +); Deno.test("rpc call with positional parameters", async function (): Promise< void @@ -257,7 +262,7 @@ Deno.test("rpc call with jwt", async function (): Promise { headers: new Headers({ "Authorization": `Bearer ${jwt}`, }), - auth: { key, methods: ["login"] }, + auth: { input: { cryptoKey, algorithm }, methods: ["login"] }, })(reqOne)).text(), removeWhiteSpace(sentToClient), ); @@ -265,7 +270,7 @@ Deno.test("rpc call with jwt", async function (): Promise { reqTwo.headers.append("Authorization", `Bearer ${jwt.slice(1)}`), assertEquals( await (await respond(methods, { - auth: { key, methods: ["login"] }, + auth: { input: { cryptoKey, algorithm }, methods: ["login"] }, })(reqTwo)).text(), removeWhiteSpace( '{"jsonrpc": "2.0", "error": {"code": -32020, "message": "Authorization error"}, "id": 3}', @@ -277,7 +282,7 @@ Deno.test("rpc call with jwt", async function (): Promise { reqThree.headers.append("Authorization", `Bearer ${jwt.slice(1)}`); assertEquals( await (await respond(methods, { - auth: { key, methods: new RegExp(".+") }, + auth: { input: { cryptoKey, algorithm }, methods: new RegExp(".+") }, })(reqThree)).text(), removeWhiteSpace( '{"jsonrpc": "2.0", "error": {"code": -32020, "message": "Authorization error"}, "id": 3}', @@ -288,7 +293,7 @@ Deno.test("rpc call with jwt", async function (): Promise { ); assertEquals( await (await respond(methods, { - auth: { key, methods: ["login"] }, + auth: { input: { cryptoKey, algorithm }, methods: ["login"] }, })(reqFour)).text(), removeWhiteSpace( '{"jsonrpc": "2.0", "error": {"code": -32020, "message": "Authorization error"}, "id": 3}', diff --git a/server/validation.ts b/server/validation.ts index 34d7f00..932d11f 100644 --- a/server/validation.ts +++ b/server/validation.ts @@ -6,12 +6,12 @@ import { } from "./error_data.ts"; import { type Methods } from "./response.ts"; -import type { - JsonArray, - JsonObject, - JsonValue, - RpcId, - RpcMethod, +import { + type JsonArray, + type JsonObject, + type JsonValue, + type RpcId, + type RpcMethod, } from "../rpc_types.ts"; export type ValidationSuccess = { diff --git a/test_deps.ts b/test_deps.ts index e3cb2b1..c3e195c 100644 --- a/test_deps.ts +++ b/test_deps.ts @@ -2,5 +2,5 @@ export { assertEquals, assertNotEquals, assertThrows, -} from "https://deno.land/std@0.192.0/testing/asserts.ts"; -export { create, type Payload } from "https://deno.land/x/djwt@v2.9/mod.ts"; +} from "https://deno.land/std@0.200.0/testing/asserts.ts"; +export { create, type Payload } from "https://deno.land/x/djwt@v2.9.1/mod.ts";