암호 키 등록 챌린지 처리 방식을 세션 업그레이드 챌린지 처리 방식과 동일하게 변경

This commit is contained in:
static
2025-01-12 07:59:49 +09:00
parent 1a86c8d9e0
commit be8587694e
3 changed files with 36 additions and 31 deletions

View File

@@ -119,26 +119,21 @@ export const registerUserClientChallenge = async (
}); });
}; };
export const getUserClientChallenge = async (answer: string, ip: string) => { export const consumeUserClientChallenge = async (userId: number, answer: string, ip: string) => {
const challenges = await db const challenges = await db
.select() .delete(userClientChallenge)
.from(userClientChallenge)
.where( .where(
and( and(
eq(userClientChallenge.userId, userId),
eq(userClientChallenge.answer, answer), eq(userClientChallenge.answer, answer),
eq(userClientChallenge.allowedIp, ip), eq(userClientChallenge.allowedIp, ip),
gt(userClientChallenge.expiresAt, new Date()), gt(userClientChallenge.expiresAt, new Date()),
eq(userClientChallenge.isUsed, false),
), ),
) )
.limit(1); .returning({ clientId: userClientChallenge.clientId });
return challenges[0] ?? null; return challenges[0] ?? null;
}; };
export const markUserClientChallengeAsUsed = async (id: number) => {
await db.update(userClientChallenge).set({ isUsed: true }).where(eq(userClientChallenge.id, id));
};
export const cleanupExpiredUserClientChallenges = async () => { export const cleanupExpiredUserClientChallenges = async () => {
await db.delete(userClientChallenge).where(lte(userClientChallenge.expiresAt, new Date())); await db.delete(userClientChallenge).where(lte(userClientChallenge.expiresAt, new Date()));
}; };

View File

@@ -1,4 +1,11 @@
import { sqliteTable, text, integer, primaryKey, unique } from "drizzle-orm/sqlite-core"; import {
sqliteTable,
text,
integer,
primaryKey,
foreignKey,
unique,
} from "drizzle-orm/sqlite-core";
import { user } from "./user"; import { user } from "./user";
export const client = sqliteTable( export const client = sqliteTable(
@@ -31,16 +38,24 @@ export const userClient = sqliteTable(
}), }),
); );
export const userClientChallenge = sqliteTable("user_client_challenge", { export const userClientChallenge = sqliteTable(
id: integer("id").primaryKey(), "user_client_challenge",
userId: integer("user_id") {
.notNull() id: integer("id").primaryKey(),
.references(() => user.id), userId: integer("user_id")
clientId: integer("client_id") .notNull()
.notNull() .references(() => user.id),
.references(() => client.id), clientId: integer("client_id")
answer: text("answer").notNull().unique(), // Base64 .notNull()
allowedIp: text("allowed_ip").notNull(), .references(() => client.id),
expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(), answer: text("answer").notNull().unique(), // Base64
isUsed: integer("is_used", { mode: "boolean" }).notNull().default(false), allowedIp: text("allowed_ip").notNull(),
}); expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
},
(t) => ({
ref: foreignKey({
columns: [t.userId, t.clientId],
foreignColumns: [userClient.userId, userClient.clientId],
}),
}),
);

View File

@@ -8,8 +8,7 @@ import {
getUserClient, getUserClient,
setUserClientStateToPending, setUserClientStateToPending,
registerUserClientChallenge, registerUserClientChallenge,
getUserClientChallenge, consumeUserClientChallenge,
markUserClientChallengeAsUsed,
} from "$lib/server/db/client"; } from "$lib/server/db/client";
import { IntegrityError } from "$lib/server/db/error"; import { IntegrityError } from "$lib/server/db/error";
import { verifyPubKey, verifySignature, generateChallenge } from "$lib/server/modules/crypto"; import { verifyPubKey, verifySignature, generateChallenge } from "$lib/server/modules/crypto";
@@ -81,15 +80,11 @@ export const verifyUserClient = async (
answer: string, answer: string,
answerSig: string, answerSig: string,
) => { ) => {
const challenge = await getUserClientChallenge(answer, ip); const challenge = await consumeUserClientChallenge(userId, answer, ip);
if (!challenge) { if (!challenge) {
error(403, "Invalid challenge answer"); error(403, "Invalid challenge answer");
} else if (challenge.userId !== userId) {
error(403, "Forbidden");
} }
await markUserClientChallengeAsUsed(challenge.id);
const client = await getClient(challenge.clientId); const client = await getClient(challenge.clientId);
if (!client) { if (!client) {
error(500, "Invalid challenge answer"); error(500, "Invalid challenge answer");
@@ -97,7 +92,7 @@ export const verifyUserClient = async (
error(403, "Invalid challenge answer signature"); error(403, "Invalid challenge answer signature");
} }
await setUserClientStateToPending(userId, challenge.clientId); await setUserClientStateToPending(userId, client.id);
}; };
export const getUserClientStatus = async (userId: number, clientId: number) => { export const getUserClientStatus = async (userId: number, clientId: number) => {