feat: add basic impl
This commit is contained in:
13
pkgs/shared/package.json
Normal file
13
pkgs/shared/package.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "@pow-captcha/shared",
|
||||
"version": "0.0.0",
|
||||
"module": "dist/lib.js",
|
||||
"types": "dist/lib.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.build.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"base64-js": "^1.5.1",
|
||||
"zod": "^3.25.67"
|
||||
}
|
||||
}
|
||||
2
pkgs/shared/src/lib.ts
Normal file
2
pkgs/shared/src/lib.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * as wire from "./wire";
|
||||
export * as utils from "./utils";
|
||||
6
pkgs/shared/src/utils.ts
Normal file
6
pkgs/shared/src/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export function createArray<T>(
|
||||
length: number,
|
||||
f: (index: number) => T,
|
||||
): Array<T> {
|
||||
return Array.from({ length }, (_, index) => f(index));
|
||||
}
|
||||
72
pkgs/shared/src/wire.ts
Normal file
72
pkgs/shared/src/wire.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { z } from "zod";
|
||||
import {
|
||||
fromByteArray as serializeArray,
|
||||
toByteArray as deserializeArray,
|
||||
} from "base64-js";
|
||||
|
||||
export { serializeArray, deserializeArray };
|
||||
|
||||
export const signedDataSchema = z.object({
|
||||
data: z.string(),
|
||||
hash: z.string(),
|
||||
});
|
||||
export type SignedData = z.output<typeof signedDataSchema>;
|
||||
|
||||
export const challengeEntrySchema = z.tuple([z.string(), z.string()]);
|
||||
export type ChallengeEntry = z.output<typeof challengeEntrySchema>;
|
||||
|
||||
export const CHALLENGE_MAGIC = "2104f639-ba1b-48f3-9443-889128163f5a";
|
||||
|
||||
export const challengeSchema = z.object({
|
||||
magic: z.literal(CHALLENGE_MAGIC),
|
||||
challenges: z.array(challengeEntrySchema),
|
||||
});
|
||||
export type Challenge = z.output<typeof challengeSchema>;
|
||||
|
||||
export const challengeSolutionSchema = z.object({
|
||||
challengesSigned: signedDataSchema,
|
||||
solutions: z.array(z.string()),
|
||||
});
|
||||
export type ChallengeSolution = z.output<typeof challengeSolutionSchema>;
|
||||
|
||||
export const REDEEMED_MAGIC = "90a63087-993a-4376-9532-33c3dc8557c9";
|
||||
|
||||
export const redeemedSchema = z.object({
|
||||
magic: z.literal(REDEEMED_MAGIC),
|
||||
});
|
||||
export type Redeemed = z.output<typeof redeemedSchema>;
|
||||
|
||||
export async function serializeAndSignData<T>(
|
||||
data: T,
|
||||
secret: string,
|
||||
): Promise<SignedData> {
|
||||
const json = JSON.stringify(data);
|
||||
const arr = utf16StringToArrayBuffer(`${json}:${secret}`);
|
||||
const hash = new Uint8Array(await crypto.subtle.digest("SHA-256", arr));
|
||||
return {
|
||||
data: json,
|
||||
hash: serializeArray(hash),
|
||||
};
|
||||
}
|
||||
|
||||
export async function verifyAndDeserializeData<T>(
|
||||
signedData: SignedData,
|
||||
schema: z.ZodType<T>,
|
||||
secret: string,
|
||||
): Promise<T> {
|
||||
const arr = utf16StringToArrayBuffer(`${signedData.data}:${secret}`);
|
||||
const hash = new Uint8Array(await crypto.subtle.digest("SHA-256", arr));
|
||||
if (hash !== deserializeArray(signedData.hash)) {
|
||||
throw new Error(`Signed data verification failed, hash mismatch`);
|
||||
}
|
||||
const data = JSON.parse(signedData.data);
|
||||
return await schema.parseAsync(data);
|
||||
}
|
||||
|
||||
export function utf16StringToArrayBuffer(input: string): ArrayBuffer {
|
||||
const arr = new Uint16Array(input.length);
|
||||
for (let i = 0, strLen = input.length; i < strLen; i += 1) {
|
||||
arr[i] = input.charCodeAt(i);
|
||||
}
|
||||
return arr.buffer;
|
||||
}
|
||||
8
pkgs/shared/tsconfig.build.json
Normal file
8
pkgs/shared/tsconfig.build.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.build.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["**/*.spec.ts"]
|
||||
}
|
||||
4
pkgs/shared/tsconfig.json
Normal file
4
pkgs/shared/tsconfig.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
Reference in New Issue
Block a user