Last active
May 15, 2023 14:36
-
-
Save shreyassanthu77/56a3ea4df7d9372875e34e3de8748336 to your computer and use it in GitHub Desktop.
Utility files for deno backends
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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