1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
|
import { Router } from "express";
import rateLimit from "express-rate-limit";
import {
buildSessionToken,
getDailySong,
getUtcDate,
GuessEntry,
normalizeSignedState,
obfuscateSong,
SignedState,
signState,
verifySessionToken,
verifyStateSignature,
} from "./shared";
export const dailyRouter = Router();
const guessLimiter = rateLimit({
windowMs: 60 * 1000,
limit: 30,
standardHeaders: true,
legacyHeaders: false,
message: { error: "Too many requests. Please slow down." },
});
dailyRouter.get("/today", (_req, res) => {
const date = getUtcDate();
const song = getDailySong(date);
const initialState: SignedState = {
date,
currentTry: 0,
didGuess: false,
guesses: [],
};
res.json({
date,
data: obfuscateSong(song, date),
sessionToken: buildSessionToken(date),
initialSig: signState(initialState),
});
});
dailyRouter.post("/guess", guessLimiter, (req, res) => {
const today = getUtcDate();
const body = req.body as {
sessionToken?: unknown;
state?: unknown;
sig?: unknown;
guess?: unknown;
};
if (!verifySessionToken(body.sessionToken, today)) {
res.status(401).json({ error: "Invalid session token." });
return;
}
const state = normalizeSignedState(body.state);
if (!state) {
res.status(400).json({ error: "Invalid state payload." });
return;
}
if (state.date !== today) {
res.status(409).json({ error: "State date is not valid for today." });
return;
}
if (!verifyStateSignature(state, body.sig)) {
res.status(403).json({ error: "State signature mismatch." });
return;
}
if (state.didGuess || state.currentTry >= 6) {
res.status(409).json({ error: "Round already finished." });
return;
}
let nextGuess: GuessEntry;
const guess = body.guess as { artist?: unknown; name?: unknown } | null | undefined;
if (guess == null) {
nextGuess = {
state: "Skipped",
};
} else if (typeof guess.artist === "string" && typeof guess.name === "string") {
const solution = getDailySong(today);
if (guess.artist === solution.artist && guess.name === solution.name) {
nextGuess = {
song: { artist: guess.artist, name: guess.name },
state: "Correct",
};
} else if (guess.artist === solution.artist) {
nextGuess = {
song: { artist: guess.artist, name: guess.name },
state: "PartiallyCorrect",
};
} else {
nextGuess = {
song: { artist: guess.artist, name: guess.name },
state: "Incorrect",
};
}
} else {
res.status(400).json({ error: "Invalid guess payload." });
return;
}
const nextState: SignedState = {
date: state.date,
currentTry: Math.min(6, state.currentTry + 1),
didGuess: state.didGuess || nextGuess.state === "Correct",
guesses: [...state.guesses, nextGuess],
};
res.json({
state: nextState,
sig: signState(nextState),
guessState: nextGuess.state,
});
});
|