mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-14 22:23:42 +01:00
Merge pull request #36219 from overleaf/jpa-zod-web-platform
[web] platform: adopt strict zod schema and extend test coverage GitOrigin-RevId: e3a0b7b4c52ac116f28be0d284284e3bd3994cda
This commit is contained in:
@@ -4,15 +4,23 @@ import UserGetter from '../../../../app/src/Features/User/UserGetter.mjs'
|
||||
import UserRegistrationHandler from '../../../../app/src/Features/User/UserRegistrationHandler.mjs'
|
||||
import ErrorController from '../../../../app/src/Features/Errors/ErrorController.mjs'
|
||||
import { expressify } from '@overleaf/promise-utils'
|
||||
import { z, parseReq } from '../../../../app/src/infrastructure/Validation.mjs'
|
||||
|
||||
const __dirname = Path.dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
const registerSchema = z.object({
|
||||
body: z.strictObject({
|
||||
email: z.string().optional(),
|
||||
}),
|
||||
})
|
||||
|
||||
function registerNewUser(req, res, next) {
|
||||
res.render(Path.resolve(__dirname, '../views/user/register'))
|
||||
}
|
||||
|
||||
async function register(req, res, next) {
|
||||
const { email } = req.body
|
||||
const { body } = parseReq(req, registerSchema, { logOnly: true })
|
||||
const { email } = body
|
||||
if (email == null || email === '') {
|
||||
return res.sendStatus(422) // Unprocessable Entity
|
||||
}
|
||||
@@ -26,18 +34,43 @@ async function register(req, res, next) {
|
||||
})
|
||||
}
|
||||
|
||||
const activateAccountPageSchema = z.object({
|
||||
// Non-strict: this link is emailed out and may get sent back through an
|
||||
// email gateway/link-rewriter that appends its own tracking query params
|
||||
// (e.g. utm_* or a click-tracking id) -- rejecting the whole request over
|
||||
// an unrecognized key would turn a legitimate activation click into a 400
|
||||
// for the user, so extra params are tolerated rather than named/rejected.
|
||||
query: z.object({
|
||||
// A legitimate activation link only ever carries a bare user_id string,
|
||||
// but a tampered URL can turn it into a nested object via qs bracket
|
||||
// notation (`?user_id[x]=y`), which the handler below explicitly
|
||||
// detects (the `typeof` check) and turns into its own rendered 403
|
||||
// page -- not the generic JSON 400 that handleValidationError would
|
||||
// produce for a value rejected by the schema itself. So this stays a
|
||||
// loose union rather than zz.objectId(), leaving the type check (and
|
||||
// its response) to the handler.
|
||||
user_id: z
|
||||
.union([z.string(), z.record(z.string(), z.unknown())])
|
||||
.optional(),
|
||||
token: z.string().optional(),
|
||||
}),
|
||||
})
|
||||
|
||||
async function activateAccountPage(req, res, next) {
|
||||
const { query } = parseReq(req, activateAccountPageSchema, {
|
||||
logOnly: true,
|
||||
})
|
||||
// An 'activation' is actually just a password reset on an account that
|
||||
// was set with a random password originally.
|
||||
if (req.query.user_id == null || req.query.token == null) {
|
||||
if (query.user_id == null || query.token == null) {
|
||||
return ErrorController.notFound(req, res)
|
||||
}
|
||||
|
||||
if (typeof req.query.user_id !== 'string') {
|
||||
if (typeof query.user_id !== 'string') {
|
||||
return ErrorController.forbidden(req, res)
|
||||
}
|
||||
|
||||
const user = await UserGetter.promises.getUser(req.query.user_id, {
|
||||
const user = await UserGetter.promises.getUser(query.user_id, {
|
||||
email: 1,
|
||||
loginCount: 1,
|
||||
})
|
||||
@@ -58,7 +91,7 @@ async function activateAccountPage(req, res, next) {
|
||||
res.render(Path.resolve(__dirname, '../views/user/activate'), {
|
||||
title: 'activate_account',
|
||||
email: user.email,
|
||||
token: req.query.token,
|
||||
token: query.token,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user