diff --git a/modules/tokens/db/migrations/20240307005945_init/migration.sql b/modules/tokens/db/migrations/20240501200641_init/migration.sql similarity index 77% rename from modules/tokens/db/migrations/20240307005945_init/migration.sql rename to modules/tokens/db/migrations/20240501200641_init/migration.sql index 67900ad8..2bf2d84c 100644 --- a/modules/tokens/db/migrations/20240307005945_init/migration.sql +++ b/modules/tokens/db/migrations/20240501200641_init/migration.sql @@ -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, @@ -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"); diff --git a/modules/tokens/db/schema.prisma b/modules/tokens/db/schema.prisma index 8d833548..281ed0e6 100644 --- a/modules/tokens/db/schema.prisma +++ b/modules/tokens/db/schema.prisma @@ -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 diff --git a/modules/tokens/scripts/create.ts b/modules/tokens/scripts/create.ts index 9945cce9..48a09323 100644 --- a/modules/tokens/scripts/create.ts +++ b/modules/tokens/scripts/create.ts @@ -1,6 +1,5 @@ import { ScriptContext } from "../module.gen.ts"; -import { TokenWithSecret } from "../utils/types.ts"; -import { tokenFromRow } from "../utils/types.ts"; +import { TokenWithSecret, tokenFromRowWithSecret, hash } from "../utils/types.ts"; export interface Request { type: string; @@ -17,10 +16,9 @@ export async function run( req: Request, ): Promise { 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, @@ -29,7 +27,7 @@ export async function run( }); return { - token: tokenFromRow(token), + token: tokenFromRowWithSecret(token, tokenStr), }; } diff --git a/modules/tokens/scripts/extend.ts b/modules/tokens/scripts/extend.ts index c13bb6c4..b386e0ff 100644 --- a/modules/tokens/scripts/extend.ts +++ b/modules/tokens/scripts/extend.ts @@ -1,6 +1,5 @@ import { ScriptContext } from "../module.gen.ts"; -import { TokenWithSecret } from "../utils/types.ts"; -import { tokenFromRow } from "../utils/types.ts"; +import { Token, tokenFromRow } from "../utils/types.ts"; export interface Request { token: string; @@ -8,7 +7,7 @@ export interface Request { } export interface Response { - token: TokenWithSecret; + token: Token; } export async function run( diff --git a/modules/tokens/scripts/get.ts b/modules/tokens/scripts/get.ts index d46fd7f2..41d9248c 100644 --- a/modules/tokens/scripts/get.ts +++ b/modules/tokens/scripts/get.ts @@ -1,6 +1,5 @@ import { ScriptContext } from "../module.gen.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[]; @@ -23,7 +22,7 @@ export async function run( }, }); - const tokens = rows.map(tokenFromRow); + const tokens = rows.map(row => tokenFromRow(row)); return { tokens }; } diff --git a/modules/tokens/scripts/get_by_token.ts b/modules/tokens/scripts/get_by_token.ts index 61761b9c..fe03c678 100644 --- a/modules/tokens/scripts/get_by_token.ts +++ b/modules/tokens/scripts/get_by_token.ts @@ -1,5 +1,5 @@ import { ScriptContext } from "../module.gen.ts"; -import { Token, tokenFromRow } from "../utils/types.ts"; +import { Token, tokenFromRowWithSecret, hash } from "../utils/types.ts"; export interface Request { tokens: string[]; @@ -13,10 +13,12 @@ export async function run( ctx: ScriptContext, req: Request, ): Promise { + const hashed = await Promise.all(req.tokens.map(hash)); + console.log(hashed); const rows = await ctx.db.token.findMany({ where: { - token: { - in: req.tokens, + tokenHash: { + in: hashed, }, }, orderBy: { @@ -24,7 +26,9 @@ export async function run( }, }); - const tokens = rows.map(tokenFromRow); + // Map from the hashed secrets to the original secrets + const hashMap = Object.fromEntries(req.tokens.map((token, i) => [hashed[i], token])); + const tokens = rows.map(row => tokenFromRowWithSecret(row, hashMap[row.tokenHash])); return { tokens }; } diff --git a/modules/tokens/tests/validate.ts b/modules/tokens/tests/validate.ts index 13a07a39..d61d99f1 100644 --- a/modules/tokens/tests/validate.ts +++ b/modules/tokens/tests/validate.ts @@ -23,7 +23,8 @@ test( meta: { foo: "bar" }, }); - await ctx.modules.tokens.revoke({ tokenIds: [token.id] }); + const { updates } = await ctx.modules.tokens.revoke({ tokenIds: [token.id] }); + assertEquals(updates[token.id], "REVOKED"); const error = await assertRejects(async () => { await ctx.modules.tokens.validate({ token: token.token }); diff --git a/modules/tokens/utils/types.ts b/modules/tokens/utils/types.ts index 5e02f138..904698fa 100644 --- a/modules/tokens/utils/types.ts +++ b/modules/tokens/utils/types.ts @@ -13,13 +13,44 @@ export interface TokenWithSecret extends Token { token: string; } -export function tokenFromRow( + +export function withoutKeys( + obj: T, + keys: K[] +): Omit { + const copy = { ...obj }; + for (const key of keys) { + delete copy[key]; + } + return copy; +} + +export function tokenFromRowWithSecret( row: prisma.Prisma.TokenGetPayload, + origToken: string ): TokenWithSecret { return { - ...row, + ...tokenFromRow(row), + token: origToken, + } +} + +export function tokenFromRow( + row: prisma.Prisma.TokenGetPayload, +): Token { + return { + ...withoutKeys(row, ["tokenHash"]), createdAt: row.createdAt.toISOString(), expireAt: row.expireAt?.toISOString() ?? null, revokedAt: row.revokedAt?.toISOString() ?? null, }; } + +export async function hash(token: string): Promise { + 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; +}