fixed a bug
This commit is contained in:
@@ -929,13 +929,14 @@ router.post('/test-answer', authMiddleware, async (req, res) => {
|
||||
try {
|
||||
const { attemptId, questionToken, userAnswer, submittedAt, answerProof, interactions } = req.body;
|
||||
const userId = req.user?._id;
|
||||
const softRiskFlags: string[] = [];
|
||||
|
||||
if (!userId) {
|
||||
return res.status(401).json({ message: INVALID_CREDENTIAL_MESSAGE });
|
||||
}
|
||||
|
||||
if (!isAllowedVocabularyOrigin(req)) {
|
||||
return res.status(403).json({ message: INVALID_CREDENTIAL_MESSAGE });
|
||||
softRiskFlags.push('invalid_origin');
|
||||
}
|
||||
|
||||
if (!mongoose.isValidObjectId(attemptId)) {
|
||||
@@ -957,9 +958,7 @@ router.post('/test-answer', authMiddleware, async (req, res) => {
|
||||
const now = Date.now();
|
||||
|
||||
if (!Number.isFinite(expiresAt.getTime()) || expiresAt.getTime() < now) {
|
||||
attemptAny.status = 'expired';
|
||||
await attempt.save();
|
||||
return res.status(410).json({ message: INVALID_CREDENTIAL_MESSAGE });
|
||||
softRiskFlags.push('attempt_expired');
|
||||
}
|
||||
|
||||
if (attemptAny.status !== 'active') {
|
||||
@@ -985,16 +984,14 @@ router.post('/test-answer', authMiddleware, async (req, res) => {
|
||||
|
||||
const submittedAtMs = Number.parseInt(String(submittedAt), 10);
|
||||
if (!Number.isFinite(submittedAtMs)) {
|
||||
return res.status(400).json({ message: INVALID_CREDENTIAL_MESSAGE });
|
||||
}
|
||||
|
||||
if (Math.abs(now - submittedAtMs) > 5 * 60 * 1000) {
|
||||
return res.status(400).json({ message: INVALID_CREDENTIAL_MESSAGE });
|
||||
softRiskFlags.push('invalid_submitted_at');
|
||||
} else if (Math.abs(now - submittedAtMs) > 5 * 60 * 1000) {
|
||||
softRiskFlags.push('submitted_at_out_of_sync');
|
||||
}
|
||||
|
||||
const wordId = getObjectIdString(attemptAny.questionWordIds[tokenIndex]);
|
||||
if (!wordId || !verifyQuestionToken(token, attemptId.toString(), wordId, tokenIndex, issuedAt)) {
|
||||
return res.status(400).json({ message: INVALID_CREDENTIAL_MESSAGE });
|
||||
softRiskFlags.push('invalid_question_token');
|
||||
}
|
||||
|
||||
let normalizedUserAnswer = String(userAnswer ?? '').trim().slice(0, 500);
|
||||
@@ -1003,24 +1000,31 @@ router.post('/test-answer', authMiddleware, async (req, res) => {
|
||||
const optionTexts = attemptAny.optionTexts?.[tokenIndex] || [];
|
||||
const selectedOptionIndex = optionTokens.findIndex((optionToken: string) => safeEqualString(optionToken, normalizedUserAnswer));
|
||||
if (selectedOptionIndex < 0) {
|
||||
return res.status(400).json({ message: INVALID_CREDENTIAL_MESSAGE });
|
||||
softRiskFlags.push('invalid_option_token');
|
||||
const fallbackOptionIndex = optionTexts.findIndex((text: string) => text === normalizedUserAnswer);
|
||||
normalizedUserAnswer = fallbackOptionIndex >= 0
|
||||
? optionTexts[fallbackOptionIndex]
|
||||
: String(userAnswer ?? '').trim().slice(0, 500);
|
||||
} else {
|
||||
normalizedUserAnswer = optionTexts[selectedOptionIndex] || '';
|
||||
}
|
||||
normalizedUserAnswer = optionTexts[selectedOptionIndex] || '';
|
||||
}
|
||||
|
||||
const expectedProof = buildAnswerProof(attemptId.toString(), token, String(userAnswer ?? '').trim().slice(0, 500), submittedAtMs);
|
||||
if (!safeEqualString(expectedProof, String(answerProof ?? ''))) {
|
||||
return res.status(400).json({ message: INVALID_CREDENTIAL_MESSAGE });
|
||||
softRiskFlags.push('invalid_answer_proof');
|
||||
}
|
||||
|
||||
const questionShownAtMs = tokenIndex === 0
|
||||
? issuedAt.getTime()
|
||||
: new Date(attemptAny.answers?.[tokenIndex - 1]?.submittedAt || issuedAt).getTime();
|
||||
const duration = Math.max(0, (submittedAtMs - questionShownAtMs) / 1000);
|
||||
const effectiveSubmittedAtMs = Number.isFinite(submittedAtMs) ? submittedAtMs : now;
|
||||
const duration = Math.max(0, (effectiveSubmittedAtMs - questionShownAtMs) / 1000);
|
||||
const interactionSummary = summarizeInteractionEvents(interactions, questionShownAtMs);
|
||||
const riskFlags = Array.from(new Set([
|
||||
...analyzeAnswerRisk(duration, interactionSummary, attemptAny.testType),
|
||||
...analyzeInputTraceRisk(interactions, normalizedUserAnswer, attemptAny.testType)
|
||||
...analyzeInputTraceRisk(interactions, normalizedUserAnswer, attemptAny.testType),
|
||||
...softRiskFlags
|
||||
]));
|
||||
|
||||
const word = await Word.findOne({
|
||||
@@ -1042,7 +1046,7 @@ router.post('/test-answer', authMiddleware, async (req, res) => {
|
||||
userAnswer: evaluation.userAnswer,
|
||||
correctAnswer: evaluation.correctAnswer,
|
||||
isCorrect: evaluation.isCorrect,
|
||||
submittedAt: new Date(submittedAtMs),
|
||||
submittedAt: new Date(effectiveSubmittedAtMs),
|
||||
duration,
|
||||
riskFlags,
|
||||
interactionSummary
|
||||
@@ -1148,13 +1152,14 @@ router.post('/test-record', authMiddleware, async (req, res) => {
|
||||
try {
|
||||
const { attemptId } = req.body;
|
||||
const userId = req.user?._id;
|
||||
const finalRiskFlags: string[] = [];
|
||||
|
||||
if (!userId) {
|
||||
return res.status(401).json({ message: INVALID_CREDENTIAL_MESSAGE });
|
||||
}
|
||||
|
||||
if (!isAllowedVocabularyOrigin(req)) {
|
||||
return res.status(403).json({ message: INVALID_CREDENTIAL_MESSAGE });
|
||||
finalRiskFlags.push('invalid_origin');
|
||||
}
|
||||
|
||||
if (!mongoose.isValidObjectId(attemptId)) {
|
||||
@@ -1176,9 +1181,7 @@ router.post('/test-record', authMiddleware, async (req, res) => {
|
||||
const now = Date.now();
|
||||
|
||||
if (!Number.isFinite(expiresAt.getTime()) || expiresAt.getTime() < now) {
|
||||
attemptAny.status = 'expired';
|
||||
await attempt.save();
|
||||
return res.status(410).json({ message: INVALID_CREDENTIAL_MESSAGE });
|
||||
finalRiskFlags.push('attempt_expired');
|
||||
}
|
||||
|
||||
if (attemptAny.status !== 'active') {
|
||||
@@ -1196,7 +1199,7 @@ router.post('/test-record', authMiddleware, async (req, res) => {
|
||||
const elapsedSeconds = Math.max(0, (lastSubmittedAtMs - issuedAt.getTime()) / 1000);
|
||||
const minimumSeconds = Math.max(2, attemptAny.questionTokens.length * MIN_SECONDS_PER_QUESTION);
|
||||
if (elapsedSeconds < minimumSeconds) {
|
||||
return res.status(400).json({ message: INVALID_CREDENTIAL_MESSAGE });
|
||||
finalRiskFlags.push('too_fast');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1221,6 +1224,7 @@ router.post('/test-record', authMiddleware, async (req, res) => {
|
||||
const totalWords = evaluatedResults.length;
|
||||
const originallyCorrectWords = evaluatedResults.filter(result => result.isCorrect).length;
|
||||
const batchRiskFlags = [
|
||||
...finalRiskFlags,
|
||||
...(isMultipleChoiceAttempt ? [] : analyzeBatchRisk(attemptAny.answerDurations || [])),
|
||||
...analyzeVocabularyAttemptResultRisk({
|
||||
testType: attemptAny.testType,
|
||||
|
||||
Reference in New Issue
Block a user