mirror of
https://github.com/kmc7468/arkvault.git
synced 2025-12-15 06:18:48 +00:00
챌린지 Reply Attack 방어 구현
This commit is contained in:
@@ -118,12 +118,21 @@ export const getUserClientChallenge = async (answer: string, ip: string) => {
|
|||||||
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),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.execute();
|
.execute();
|
||||||
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))
|
||||||
|
.execute();
|
||||||
|
};
|
||||||
|
|
||||||
export const cleanupExpiredUserClientChallenges = async () => {
|
export const cleanupExpiredUserClientChallenges = async () => {
|
||||||
await db
|
await db
|
||||||
.delete(userClientChallenge)
|
.delete(userClientChallenge)
|
||||||
|
|||||||
@@ -42,4 +42,5 @@ export const userClientChallenge = sqliteTable("user_client_challenge", {
|
|||||||
answer: text("challenge").notNull().unique(), // Base64
|
answer: text("challenge").notNull().unique(), // Base64
|
||||||
allowedIp: text("allowed_ip").notNull(),
|
allowedIp: text("allowed_ip").notNull(),
|
||||||
expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
|
expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
|
||||||
|
isUsed: integer("is_used", { mode: "boolean" }).notNull().default(false),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,4 +28,5 @@ export const tokenUpgradeChallenge = sqliteTable("token_upgrade_challenge", {
|
|||||||
answer: text("challenge").notNull().unique(), // Base64
|
answer: text("challenge").notNull().unique(), // Base64
|
||||||
allowedIp: text("allowed_ip").notNull(),
|
allowedIp: text("allowed_ip").notNull(),
|
||||||
expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
|
expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
|
||||||
|
isUsed: integer("is_used", { mode: "boolean" }).notNull().default(false),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -102,12 +102,21 @@ export const getTokenUpgradeChallenge = async (answer: string, ip: string) => {
|
|||||||
eq(tokenUpgradeChallenge.answer, answer),
|
eq(tokenUpgradeChallenge.answer, answer),
|
||||||
eq(tokenUpgradeChallenge.allowedIp, ip),
|
eq(tokenUpgradeChallenge.allowedIp, ip),
|
||||||
gt(tokenUpgradeChallenge.expiresAt, new Date()),
|
gt(tokenUpgradeChallenge.expiresAt, new Date()),
|
||||||
|
eq(tokenUpgradeChallenge.isUsed, false),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.execute();
|
.execute();
|
||||||
return challenges[0] ?? null;
|
return challenges[0] ?? null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const markTokenUpgradeChallengeAsUsed = async (id: number) => {
|
||||||
|
await db
|
||||||
|
.update(tokenUpgradeChallenge)
|
||||||
|
.set({ isUsed: true })
|
||||||
|
.where(eq(tokenUpgradeChallenge.id, id))
|
||||||
|
.execute();
|
||||||
|
};
|
||||||
|
|
||||||
export const cleanupExpiredTokenUpgradeChallenges = async () => {
|
export const cleanupExpiredTokenUpgradeChallenges = async () => {
|
||||||
await db
|
await db
|
||||||
.delete(tokenUpgradeChallenge)
|
.delete(tokenUpgradeChallenge)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
revokeRefreshToken,
|
revokeRefreshToken,
|
||||||
registerTokenUpgradeChallenge,
|
registerTokenUpgradeChallenge,
|
||||||
getTokenUpgradeChallenge,
|
getTokenUpgradeChallenge,
|
||||||
|
markTokenUpgradeChallengeAsUsed,
|
||||||
} from "$lib/server/db/token";
|
} from "$lib/server/db/token";
|
||||||
import { issueToken, verifyToken, TokenError } from "$lib/server/modules/auth";
|
import { issueToken, verifyToken, TokenError } from "$lib/server/modules/auth";
|
||||||
import { verifySignature, generateChallenge } from "$lib/server/modules/crypto";
|
import { verifySignature, generateChallenge } from "$lib/server/modules/crypto";
|
||||||
@@ -152,7 +153,7 @@ export const upgradeToken = async (
|
|||||||
error(401, "Invalid challenge answer signature");
|
error(401, "Invalid challenge answer signature");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Replay attack prevention
|
await markTokenUpgradeChallengeAsUsed(challenge.id);
|
||||||
|
|
||||||
const newJti = uuidv4();
|
const newJti = uuidv4();
|
||||||
if (!(await upgradeRefreshToken(oldJti, newJti, client.id))) {
|
if (!(await upgradeRefreshToken(oldJti, newJti, client.id))) {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
setUserClientStateToPending,
|
setUserClientStateToPending,
|
||||||
registerUserClientChallenge,
|
registerUserClientChallenge,
|
||||||
getUserClientChallenge,
|
getUserClientChallenge,
|
||||||
|
markUserClientChallengeAsUsed,
|
||||||
} from "$lib/server/db/client";
|
} from "$lib/server/db/client";
|
||||||
import { verifyPubKey, verifySignature, generateChallenge } from "$lib/server/modules/crypto";
|
import { verifyPubKey, verifySignature, generateChallenge } from "$lib/server/modules/crypto";
|
||||||
import { isInitialMekNeeded } from "$lib/server/modules/mek";
|
import { isInitialMekNeeded } from "$lib/server/modules/mek";
|
||||||
@@ -107,7 +108,6 @@ export const verifyUserClient = async (
|
|||||||
error(401, "Invalid challenge answer signature");
|
error(401, "Invalid challenge answer signature");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Replay attack prevention
|
await markUserClientChallengeAsUsed(challenge.id);
|
||||||
|
|
||||||
await setUserClientStateToPending(userId, challenge.clientId);
|
await setUserClientStateToPending(userId, challenge.clientId);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user