(env: Env, request: Request)
| 165 | } |
| 166 | |
| 167 | async function handleLoginSubmit(env: Env, request: Request): Promise<Response> { |
| 168 | if (!isSameOriginPost(request)) { |
| 169 | return new Response('bad request\n', { status: 400 }); |
| 170 | } |
| 171 | |
| 172 | const contentLength = Number(request.headers.get('content-length')); |
| 173 | if (Number.isFinite(contentLength) && contentLength > MAX_LOGIN_BODY_BYTES) { |
| 174 | return new Response('payload too large\n', { status: 413 }); |
| 175 | } |
| 176 | |
| 177 | let form: FormData; |
| 178 | try { |
| 179 | form = await request.formData(); |
| 180 | } catch { |
| 181 | return new Response('bad request\n', { status: 400 }); |
| 182 | } |
| 183 | |
| 184 | const next = safeNextPath(String(form.get('next') ?? '/')); |
| 185 | const password = form.get('password'); |
| 186 | const styleNonce = nonce(); |
| 187 | const fail = (error: string, status: number): Response => |
| 188 | html(renderLoginPage({ next, error, nonce: styleNonce }), { status, nonce: styleNonce }); |
| 189 | |
| 190 | if (!(await loginRateLimitOk(env, request))) { |
| 191 | return fail('Too many attempts. Wait a minute and try again.', 429); |
| 192 | } |
| 193 | if (typeof password !== 'string' || password.length === 0) { |
| 194 | return fail('Enter the password to continue.', 400); |
| 195 | } |
| 196 | if (!(await checkPassword(env, password))) { |
| 197 | return fail('That password is not right.', 401); |
| 198 | } |
| 199 | |
| 200 | return redirect(next, { headers: { 'set-cookie': sessionCookie(await issueSession(env)) } }); |
| 201 | } |
| 202 | |
| 203 | /** |
| 204 | * The chart endpoints live in src/api.ts and return data, not responses, so this |
no test coverage detected