Skip to content

Instantly share code, notes, and snippets.

@shreyassanthu77
Last active May 15, 2023 14:36
Show Gist options
  • Save shreyassanthu77/56a3ea4df7d9372875e34e3de8748336 to your computer and use it in GitHub Desktop.
Save shreyassanthu77/56a3ea4df7d9372875e34e3de8748336 to your computer and use it in GitHub Desktop.
Utility files for deno backends
import { serve } from "https://deno.land/std/http/server.ts";
import { app } from "./routes/router.ts";
import { $env } from "./utils/env.ts";
serve(app.fetch, {
port: $env.port,
});
import { Hono } from "https://deno.land/x/hono/mod.ts";
import {
prettyJSON,
cors,
logger,
} from "https://deno.land/x/hono/middleware.ts";
export const app = new Hono();
app.use("*", prettyJSON(), cors(), logger());
// Code copied from Lucia auth:
// https://github.com/pilcrowOnPaper/lucia/blob/763c273d299d4f44148e8a36e114d8c048b28c5c/packages/lucia-auth/src/utils/crypto.ts#L3
// Lucia uses Node dependencies on default bundler settings
import { scryptAsync } from "https://esm.sh/@noble/[email protected]/scrypt";
const nanoid = (() => {
// code copied from Nanoid:
// https://github.com/ai/nanoid/blob/9b748729f8ad5409503b508b65958636e55bd87a/index.browser.js
// nanoid uses Node dependencies on default bundler settings
function getRandomValues(bytes: number) {
return crypto.getRandomValues(new Uint8Array(bytes));
}
const DEFAULT_ALPHABET =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
function generateRandomString(size: number, alphabet = DEFAULT_ALPHABET) {
const mask = (2 << (Math.log(alphabet.length - 1) / Math.LN2)) - 1;
const step = -~((1.6 * mask * size) / alphabet.length);
let bytes = getRandomValues(step);
let id = "";
let index = 0;
while (id.length !== size) {
id += alphabet[bytes[index] & mask] ?? "";
index += 1;
if (index > bytes.length) {
bytes = getRandomValues(step);
index = 0;
}
}
return id;
}
return { generateRandomString };
})();
export async function hashPassword(s: string) {
const salt = nanoid.generateRandomString(16);
const key = await hashWithScrypt(s.normalize("NFKC"), salt);
return `s2:${salt}:${key}`;
}
async function hashWithScrypt(s: string, salt: string, blockSize = 16) {
const keyUint8Array = await scryptAsync(
new TextEncoder().encode(s),
new TextEncoder().encode(salt),
{
N: 16384,
r: blockSize,
p: 1,
dkLen: 64,
}
);
return convertUint8ArrayToHex(keyUint8Array);
}
export async function verifyPassword(s: string, hash: string) {
const arr = hash.split(":");
if (arr.length === 2) {
const [salt, key] = arr;
const targetKey = await hashWithScrypt(s, salt, 8);
return constantTimeEqual(targetKey, key);
}
if (arr.length !== 3) return false;
const [version, salt, key] = arr;
if (version === "s2") {
const targetKey = await hashWithScrypt(s, salt);
return constantTimeEqual(targetKey, key);
}
return false;
}
function constantTimeEqual(a: string, b: string) {
if (a.length !== b.length) {
return false;
}
const aUint8Array = new TextEncoder().encode(a);
const bUint8Array = new TextEncoder().encode(b);
let c = 0;
for (let i = 0; i < a.length; i++) {
c |= aUint8Array[i] ^ bUint8Array[i]; // ^: XOR operator
}
return c === 0;
}
function convertUint8ArrayToHex(arr: Uint8Array) {
return [...arr].map((x) => x.toString(16).padStart(2, "0")).join("");
}
export function importKey(key: string) {
return crypto.subtle.importKey(
"jwk",
{
kty: "oct",
k: key,
alg: "HS512",
key_ops: ["sign", "verify"],
ext: true,
},
"HS512",
true,
["sign", "verify"]
);
}
if (import.meta.main) {
if (Deno.args[0] === "save") {
const key = await crypto.subtle.generateKey(
{
name: "HS512",
length: 512,
},
true,
["sign", "verify"]
);
const k = (await crypto.subtle.exportKey("jwk", key)).k;
console.log(k);
}
}
const dev = Deno.env.get("DEV") === "true";
if (dev) {
console.log("Running in development mode");
await import("https://deno.land/[email protected]/dotenv/load.ts");
}
// deno-lint-ignore no-explicit-any
function assertEnv(env: any) {
const visited = new Set();
for (const key in env) {
if (env[key] === undefined) {
throw new Error(`Environment variable ${key} is missing`);
}
if (typeof env[key] === "object") {
if (visited.has(env[key])) {
continue; // Prevent infinite recursion
}
assertEnv(env[key]);
}
}
}
export const $env = {
dev,
jwt_secret: Deno.env.get("JWT_SECRET")!,
};
assertEnv($env);
export type Ok<T> = { success: true; data: T };
export type Err<E> = { success: false; error: E };
export type Result<T, E> = Ok<T> | Err<E>;
export function ok<T>(data: T): Ok<T> {
return {
success: true,
data,
};
}
export function err<E>(error: E): Err<E> {
return {
success: false,
error,
};
}
export async function resultify<T, E = unknown>(
promise: Promise<T>
): Promise<Result<T, E>> {
try {
const data = await promise;
return ok(data as T);
} catch (error) {
return err(error as E);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment