Skip to content

Commit

Permalink
Merge pull request #45 from eseiker/use-argon2ian
Browse files Browse the repository at this point in the history
Use argon2ian instead of npm:argon2-browser
  • Loading branch information
eseiker committed Aug 4, 2023
2 parents 461b438 + ca5db1d commit 80d1712
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 50 deletions.
109 changes: 109 additions & 0 deletions web/argon2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import {
type ArgonOptions,
ArgonWorker,
} from "https://deno.land/x/argon2ian@2.0.1/src/async.ts";
import {
decode as decodeBase64,
encode as encodeBase64,
} from "std/encoding/base64.ts";

const worker = new ArgonWorker();
const encoder = new TextEncoder();

const variants = ["argon2d", "argon2i", "argon2id"] as const;
const variantsMap = new Map(variants.map((v, i) => [v, i as 0 | 1 | 2]));

const defaultOptions = {
variant: 2, // argon2id
m: 1 << 16,
t: 3,
p: 1,
// https://github.com/valpackett/argon2ian/blob/trunk/src/argon2.ts#L35
} satisfies ArgonOptions;

const generateSalt = (size = 16) => {
const buf = new Uint8Array(size);
crypto.getRandomValues(buf);
return buf;
};

const encodeHashString = (params: VerifyParams) => {
const { hash, salt, options } = params;
if (!options.variant || !options.m || !options.t || !options.p) {
throw new Error("Required options not provided.");
}

const sections = [
variants[options.variant],
"v=19", // argon2ian2 uses argon2 version 0x13
`m=${options.m},t=${options.t},p=${options.p}`,
encodeBase64(salt),
encodeBase64(hash),
] as const;

return `$${sections.join("$")}`;
};

const decodeHashString = (encoded: string): VerifyParams => {
const [_0, variantStr, _v, paramsStr, salt, hash] = encoded.split("$");
const params = paramsStr.split(",").reduce((acc, curr) => {
const [k, v] = curr.split("=");
acc[k] = parseInt(v, 10);
return acc;
}, {} as { [k: string]: number });

return {
hash: decodeBase64(hash),
salt: decodeBase64(salt),
options: {
variant: variantsMap.get(variantStr as (typeof variants)[number]),
...params,
},
};
};

interface HashParams {
salt?: string | Uint8Array;
options?: ArgonOptions;
}

export const hash = async (
password: string | Uint8Array,
params: HashParams = {},
) => {
await worker.ready;

const options = { ...defaultOptions, ...params.options };

const salt = params.salt != null
? typeof params.salt === "string"
? encoder.encode(params.salt)
: params.salt
: generateSalt();

password = typeof password === "string" ? encoder.encode(password) : password;

const hash = await worker.hash(password, salt, options);

return encodeHashString({ hash, salt, options });
};

interface VerifyParams {
hash: Uint8Array;
salt: Uint8Array;
options: ArgonOptions;
}

export const verify = async (
password: string | Uint8Array,
params: VerifyParams | string,
) => {
await worker.ready;

if (typeof params === "string") params = decodeHashString(params);

password = typeof password === "string" ? encoder.encode(password) : password;

return params.hash ===
(await worker.hash(password, params.salt, params.options));
};
11 changes: 3 additions & 8 deletions web/routes/api/join.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { Handlers, Status } from "fresh/server.ts";
import type { WithSession } from "fresh-session";

import { prisma } from "~/main.ts";
import { argon2, redirect } from "~/util.ts";
import { redirect } from "~/util.ts";
import { hash } from "~/argon2.ts";

export const handler: Handlers<unknown, WithSession> = {
async POST(req, ctx) {
Expand All @@ -19,13 +20,7 @@ export const handler: Handlers<unknown, WithSession> = {
const user = await prisma.user.create({
data: {
email,
password: (
await argon2.hash({
pass: password,
salt: crypto.getRandomValues(new Uint8Array(16)),
type: argon2.ArgonType.Argon2id,
})
).encoded,
password: await hash(password),
},
});

Expand Down
16 changes: 3 additions & 13 deletions web/routes/api/login.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Handlers, Status } from "fresh/server.ts";
import type { WithSession } from "fresh-session";
import { decode } from "std/encoding/base64.ts";

import { prisma } from "~/main.ts";
import { argon2, redirect } from "~/util.ts";
import { redirect } from "~/util.ts";
import { verify } from "~/argon2.ts";

export const handler: Handlers<unknown, WithSession> = {
async POST(req, ctx) {
Expand All @@ -18,17 +18,7 @@ export const handler: Handlers<unknown, WithSession> = {
}

const user = await prisma.user.findUnique({ where: { email } });
if (
!user ||
user.password !==
(
await argon2.hash({
pass: password,
salt: decode(user.password.split("$")[4]),
type: argon2.ArgonType.Argon2id,
})
).encoded
) {
if (!user || (await verify(password, user.password))) {
return new Response("Invalid email/password", {
status: Status.Unauthorized,
});
Expand Down
29 changes: 0 additions & 29 deletions web/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,32 +31,3 @@ export const checkPermission = async (
if (entries.find((e) => e.userId === user.id)) return true;
return false;
};

const globalThisShim = globalThis as {
process?: { versions?: { node?: unknown } };
};
const needPatch = globalThisShim.process !== undefined &&
globalThisShim.process.versions !== undefined &&
globalThisShim.process.versions.node !== undefined;
if (needPatch) {
Object.assign(globalThis, {
process: {
...globalThisShim.process,
versions: { ...globalThisShim.process!.versions, node: undefined },
},
});
}
import _argon2 from "https://esm.sh/argon2-browser@1.18.0/dist/argon2-bundled.min.js";
export import argon2 = _argon2;
await argon2.hash({ pass: new Uint8Array(0), salt: new Uint8Array(8) });
if (needPatch) {
Object.assign(globalThis, {
process: {
...globalThisShim.process,
versions: {
...globalThisShim.process!.versions,
node: globalThisShim.process!.versions!.node,
},
},
});
}

0 comments on commit 80d1712

Please sign in to comment.