Skip to content

Commit

Permalink
Update
Browse files Browse the repository at this point in the history
  • Loading branch information
timonson committed Aug 30, 2023
1 parent fc93517 commit 6697676
Show file tree
Hide file tree
Showing 9 changed files with 71 additions and 67 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
33 changes: 8 additions & 25 deletions server/auth.ts
Original file line number Diff line number Diff line change
@@ -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<CreationInput & { payload?: Payload }> {
if (validationObject.isError) return { validationObject, methods, options };
Expand All @@ -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 {
Expand Down
32 changes: 16 additions & 16 deletions server/creation.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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,
};
Expand Down Expand Up @@ -91,27 +91,27 @@ export async function createRpcResponseOrBatch(
validationObjectOrBatch: ValidationObject | ValidationObject[],
methods: Methods,
options: Options,
headers: Headers,
authData: AuthData,
): Promise<RpcResponseOrBatchOrNull> {
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,
}),
);
}
Expand Down
2 changes: 1 addition & 1 deletion server/custom_error.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
9 changes: 7 additions & 2 deletions server/deps.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
export {
type Payload,
verify,
type VerifyOptions,
} from "https://deno.land/x/[email protected]/mod.ts";
} from "https://dev.zaubrik.com/[email protected]/mod.ts";

export {
type CryptoKeyOrUpdateInput,
getJwtFromBearer,
verifyJwt,
} from "https://dev.zaubrik.com/[email protected]/functions/mod.ts";
27 changes: 19 additions & 8 deletions server/response.ts
Original file line number Diff line number Diff line change
@@ -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<JsonValue>;
};
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<string, unknown>;
// for jwt verification:
// Verify JWTs:
auth?: {
key: CryptoKey;
input: CryptoKeyOrUpdateInput;
methods?: (keyof Methods)[] | RegExp;
options?: VerifyOptions;
};
};
export type AuthData = {
headers: Headers;
verify?: ReturnType<typeof verifyJwt>;
};

export function respond(methods: Methods, options: Options = {}) {
const verify = options.auth
? verifyJwt(options.auth.input, options.auth.options)
: undefined;
return async (request: Request): Promise<Response> => {
const requestHeaders = request.headers;
const authData = { verify, headers: request.headers };
const validationObjectOrBatch = validateRequest(
await request.text(),
methods,
Expand All @@ -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 });
Expand Down
17 changes: 11 additions & 6 deletions server/response_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -257,15 +262,15 @@ Deno.test("rpc call with jwt", async function (): Promise<void> {
headers: new Headers({
"Authorization": `Bearer ${jwt}`,
}),
auth: { key, methods: ["login"] },
auth: { input: { cryptoKey, algorithm }, methods: ["login"] },
})(reqOne)).text(),
removeWhiteSpace(sentToClient),
);
const reqTwo = createReq(sentToServer);
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}',
Expand All @@ -277,7 +282,7 @@ Deno.test("rpc call with jwt", async function (): Promise<void> {
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}',
Expand All @@ -288,7 +293,7 @@ Deno.test("rpc call with jwt", async function (): Promise<void> {
);
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}',
Expand Down
12 changes: 6 additions & 6 deletions server/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
4 changes: 2 additions & 2 deletions test_deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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/[email protected]/mod.ts";
} from "https://deno.land/std@0.200.0/testing/asserts.ts";
export { create, type Payload } from "https://deno.land/x/[email protected].1/mod.ts";

0 comments on commit 6697676

Please sign in to comment.