Skip to content

Commit

Permalink
Use ulid as id
Browse files Browse the repository at this point in the history
  • Loading branch information
timonson committed May 14, 2024
1 parent d9886d5 commit 13c84f2
Show file tree
Hide file tree
Showing 9 changed files with 109 additions and 37 deletions.
7 changes: 2 additions & 5 deletions client/creation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { RpcRequest } from "../rpc_types.ts";
import { generateUlid } from "./deps.ts";

export type CreateRequestInput = {
method: string;
Expand All @@ -7,10 +8,6 @@ export type CreateRequestInput = {
id?: RpcRequest["id"];
};

function generateId() {
return crypto.getRandomValues(new Uint32Array(1))[0].toString(16);
}

export function createRequest(
{ method, params, isNotification = false, id }: CreateRequestInput,
): RpcRequest {
Expand All @@ -19,7 +16,7 @@ export function createRequest(
method,
};
params && (rpcRequest.params = params);
id = isNotification ? undefined : id !== undefined ? id : generateId();
id = isNotification ? undefined : id !== undefined ? id : generateUlid();
id !== undefined && (rpcRequest.id = id);
return rpcRequest;
}
1 change: 1 addition & 0 deletions client/deps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { generateUlid } from "https://dev.zaubrik.com/sorcery@v0.1.5/util/id.js";
66 changes: 63 additions & 3 deletions client/dist/mod.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,76 @@
// deno-lint-ignore-file
// This code was bundled using `deno bundle` and it's not recommended to edit it manually

function generateId() {
return crypto.getRandomValues(new Uint32Array(1))[0].toString(16);
const ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
const ENCODING_LEN = ENCODING.length;
const TIME_MAX = Math.pow(2, 48) - 1;
const RANDOM_LEN = 16;
function replaceCharAt(str, index, __char) {
return str.substring(0, index) + __char + str.substring(index + 1);
}
function encodeTime(now, len = 10) {
if (!Number.isInteger(now) || now < 0 || now > TIME_MAX) {
throw new Error("Time must be a positive integer less than " + TIME_MAX);
}
let str = "";
for(; len > 0; len--){
const mod = now % ENCODING_LEN;
str = ENCODING[mod] + str;
now = (now - mod) / ENCODING_LEN;
}
return str;
}
function encodeRandom(len) {
let str = "";
const randomBytes = crypto.getRandomValues(new Uint8Array(len));
for (const randomByte of randomBytes){
str += ENCODING[randomByte % ENCODING_LEN];
}
return str;
}
function incrementBase32(str) {
let index = str.length;
let __char;
let charIndex;
const maxCharIndex = ENCODING_LEN - 1;
while(--index >= 0){
__char = str[index];
charIndex = ENCODING.indexOf(__char);
if (charIndex === -1) {
throw new Error("incorrectly encoded string");
}
if (charIndex === maxCharIndex) {
str = replaceCharAt(str, index, ENCODING[0]);
continue;
}
return replaceCharAt(str, index, ENCODING[charIndex + 1]);
}
throw new Error("cannot increment this string");
}
function monotonicFactory(encodeRand = encodeRandom) {
let lastTime = 0;
let lastRandom;
return function ulid(seedTime = Date.now()) {
if (seedTime <= lastTime) {
const incrementedRandom = lastRandom = incrementBase32(lastRandom);
return encodeTime(lastTime, 10) + incrementedRandom;
}
lastTime = seedTime;
const newRandom = lastRandom = encodeRand(RANDOM_LEN);
return encodeTime(seedTime, 10) + newRandom;
};
}
monotonicFactory();
function ulid(seedTime = Date.now()) {
return encodeTime(seedTime, 10) + encodeRandom(16);
}
function createRequest({ method, params, isNotification = false, id }) {
const rpcRequest = {
jsonrpc: "2.0",
method
};
params && (rpcRequest.params = params);
id = isNotification ? undefined : id !== undefined ? id : generateId();
id = isNotification ? undefined : id !== undefined ? id : ulid();
id !== undefined && (rpcRequest.id = id);
return rpcRequest;
}
Expand Down
25 changes: 14 additions & 11 deletions server/auth.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
import { authErrorData } from "./error_data.ts";
import { getJwtFromBearer, type Payload } from "./deps.ts";
import { getJwtFromBearer, isArray, isPresent, type Payload } from "./deps.ts";
import { type CreationInput } from "./creation.ts";
import { type AuthInputAndVerify } from "./response.ts";
import { type AuthInput } from "./response.ts";
import { type ValidationSuccess } from "./validation.ts";

export type AuthData = AuthInputAndVerify & { headers: Headers };
export type AuthData = AuthInput & { headers: Headers };
type VerifyJwtForSelectedMethodsReturnType = CreationInput & {
payload?: Payload;
};

function isPresent<T>(input: T | undefined | null): input is T {
return input !== undefined && input !== null;
}

export async function verifyJwtForSelectedMethods(
{ validationObject, methods, options, authDataArray }: CreationInput & {
authDataArray?: AuthData[];
Expand Down Expand Up @@ -47,14 +43,21 @@ function processAuthData(
return async (authData: AuthData) => {
const authMethods = authData.methods;
if (
Array.isArray(authMethods)
isArray(authMethods)
? authMethods.includes(validationObject.method)
: authMethods?.test(validationObject.method)
) {
try {
const jwt = getJwtFromBearer(authData.headers);
const payload = await authData.verify(jwt, authData.options);
return { validationObject, methods, options, payload };
const verify = authData.verification;
if (typeof verify === "function") {
const jwt = getJwtFromBearer(authData.headers);
const payload = await verify(jwt, authData.options);
return { validationObject, methods, options, payload };
} else {
throw new Error(
"There is no verify function. This error should never happen!",
);
}
} catch (err) {
return {
validationObject: {
Expand Down
4 changes: 2 additions & 2 deletions server/creation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { type AuthData, verifyJwtForSelectedMethods } from "./auth.ts";
import { type RpcBatchResponse, type RpcResponse } from "../rpc_types.ts";
import { type ValidationObject } from "./validation.ts";
import { type Methods, type Options } from "./response.ts";
import { type Payload } from "./deps.ts";
import { isArray, type Payload } from "./deps.ts";

export type CreationInput = {
validationObject: ValidationObject;
Expand Down Expand Up @@ -93,7 +93,7 @@ export async function createRpcResponseOrBatch(
options: Options,
authDataArray?: AuthData[],
): Promise<RpcResponseOrBatchOrNull> {
return Array.isArray(validationObjectOrBatch)
return isArray(validationObjectOrBatch)
? await cleanBatch(
validationObjectOrBatch.map(async (validationObject) =>
createRpcResponse(
Expand Down
6 changes: 6 additions & 0 deletions server/deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,9 @@ export {
getJwtFromBearer,
verifyJwt,
} from "https://dev.zaubrik.com/portal@v0.2.7/functions/mod.ts";
export {
isArray,
isFunction,
isObject,
isPresent,
} from "https://dev.zaubrik.com/sorcery@v0.1.5/type.js";
11 changes: 11 additions & 0 deletions server/object.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* isObject.
* @param {unknown} input
* @returns {input is Record<string, any>}
*/
export function isObject(input) {
return (
input !== null && typeof input === "object" &&
Array.isArray(input) === false
);
}
18 changes: 9 additions & 9 deletions server/response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ export type AuthInput = {
options?: VerifyOptions;
};
export type VerifyInput = CryptoKeyOrUpdateInput | Verify;
export type AuthInputAndVerify = Omit<AuthInput, "verification"> & {
verify: Verify;
};

function addVerifyFunctions(authInput: AuthInput) {
const verify = typeof authInput.verification === "function"
? authInput.verification
: verifyJwt(authInput.verification);
return { ...authInput, verify };
export function ensureVerify(authInput: AuthInput): AuthInput {
const verification = authInput.verification;
return {
...authInput,
verification: typeof verification === "function"
? verification
: verifyJwt(verification),
};
}

export function respond(
Expand All @@ -45,7 +45,7 @@ export function respond(
) {
const authInputArray = [authInput].flat();
const authInputArrayIsNotEmpty = authInputArray.length > 0;
const authInputAndVeryifyArray = authInputArray.map(addVerifyFunctions);
const authInputAndVeryifyArray = authInputArray.map(ensureVerify);
return async (request: Request): Promise<Response> => {
const authData = authInputArrayIsNotEmpty
? authInputAndVeryifyArray.map((authInput) => ({
Expand Down
8 changes: 1 addition & 7 deletions server/validation.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isArray, isObject } from "./deps.ts";
import {
invalidParamsErrorData,
invalidRequestErrorData,
Expand Down Expand Up @@ -55,13 +56,6 @@ function isRpcId(input: unknown): input is RpcId {
}
}

// deno-lint-ignore no-explicit-any
function isObject(obj: unknown): obj is Record<string, any> {
return (
obj !== null && typeof obj === "object" && Array.isArray(obj) === false
);
}

function tryToParse(json: string) {
try {
return [JSON.parse(json), null];
Expand Down

0 comments on commit 13c84f2

Please sign in to comment.