Skip to content
This repository has been archived by the owner on Sep 17, 2024. It is now read-only.

Commit

Permalink
fix(tokens): Hash tokens in tokens module to resist ND2DB-style tim…
Browse files Browse the repository at this point in the history
…ing attack
  • Loading branch information
Blckbrry-Pi committed May 1, 2024
1 parent 1317b2b commit b2cefc7
Show file tree
Hide file tree
Showing 7 changed files with 28 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
-- CreateTable
CREATE TABLE "Token" (
"id" UUID NOT NULL,
"token" TEXT NOT NULL,
"tokenHash" TEXT NOT NULL,
"type" TEXT NOT NULL,
"meta" JSONB NOT NULL,
"trace" JSONB NOT NULL,
Expand All @@ -13,4 +13,4 @@ CREATE TABLE "Token" (
);

-- CreateIndex
CREATE UNIQUE INDEX "Token_token_key" ON "Token"("token");
CREATE UNIQUE INDEX "Token_tokenHash_key" ON "Token"("tokenHash");
2 changes: 1 addition & 1 deletion modules/tokens/db/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ datasource db {

model Token {
id String @id @default(uuid()) @db.Uuid
token String @unique
tokenHash String @unique
type String
meta Json
trace Json
Expand Down
8 changes: 3 additions & 5 deletions modules/tokens/scripts/create.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { ScriptContext } from "../_gen/scripts/create.ts";
import { TokenWithSecret } from "../utils/types.ts";
import { tokenFromRow } from "../utils/types.ts";
import { TokenWithSecret, tokenFromRow, hash } from "../utils/types.ts";

export interface Request {
type: string;
Expand All @@ -17,10 +16,9 @@ export async function run(
req: Request,
): Promise<Response> {
const tokenStr = generateToken(req.type);

const token = await ctx.db.token.create({
data: {
token: tokenStr,
tokenHash: await hash(tokenStr),
type: req.type,
meta: req.meta,
trace: ctx.trace,
Expand All @@ -29,7 +27,7 @@ export async function run(
});

return {
token: tokenFromRow(token),
token: tokenFromRow(token, () => tokenStr),
};
}

Expand Down
5 changes: 2 additions & 3 deletions modules/tokens/scripts/extend.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { ScriptContext } from "../_gen/scripts/extend.ts";
import { TokenWithSecret } from "../types/common.ts";
import { tokenFromRow } from "../types/common.ts";
import { TokenWithSecret, tokenFromRow } from "../utils/types.ts";

export interface Request {
token: string;
Expand Down Expand Up @@ -32,6 +31,6 @@ export async function run(

// Return the updated token
return {
token: tokenFromRow(newToken),
token: tokenFromRow(newToken, () => req.token),
};
}
5 changes: 2 additions & 3 deletions modules/tokens/scripts/get.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { ScriptContext } from "../_gen/scripts/get.ts";
import { Token } from "../utils/types.ts";
import { tokenFromRow } from "../utils/types.ts";
import { Token, tokenFromRow } from "../utils/types.ts";

export interface Request {
tokenIds: string[];
Expand All @@ -23,7 +22,7 @@ export async function run(
},
});

const tokens = rows.map(tokenFromRow);
const tokens = rows.map(row => tokenFromRow(row, () => ""));

return { tokens };
}
11 changes: 7 additions & 4 deletions modules/tokens/scripts/get_by_token.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ScriptContext } from "../_gen/scripts/get_by_token.ts";
import { Token, tokenFromRow } from "../utils/types.ts";
import { Token, tokenFromRow, hash } from "../utils/types.ts";

export interface Request {
tokens: string[];
Expand All @@ -13,18 +13,21 @@ export async function run(
ctx: ScriptContext,
req: Request,
): Promise<Response> {
const hashed = await Promise.all(req.tokens.map(hash));
const rows = await ctx.db.token.findMany({
where: {
token: {
in: req.tokens,
tokenHash: {
in: hashed,
},
},
orderBy: {
createdAt: "desc",
},
});

const tokens = rows.map(tokenFromRow);
const hashMap = Object.fromEntries(req.tokens.map((token, i) => [hashed[i], token]));

const tokens = rows.map(row => tokenFromRow(row, h => hashMap[h]));

return { tokens };
}
11 changes: 11 additions & 0 deletions modules/tokens/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,22 @@ export interface TokenWithSecret extends Token {

export function tokenFromRow(
row: prisma.Prisma.TokenGetPayload<any>,
hashToOrig: (hash: string) => string,
): TokenWithSecret {
return {
...row,
createdAt: row.createdAt.toISOString(),
expireAt: row.expireAt?.toISOString() ?? null,
revokedAt: row.revokedAt?.toISOString() ?? null,
token: hashToOrig(row.tokenHash),
};
}

export async function hash(token: string): Promise<string> {
const encoder = new TextEncoder();
const data = encoder.encode(token);
const hash = await crypto.subtle.digest("SHA-256", data);
const digest = Array.from(new Uint8Array(hash));
const strDigest = digest.map(b => b.toString(16).padStart(2, "0")).join("");
return strDigest;
}

0 comments on commit b2cefc7

Please sign in to comment.