levibostian / ExpressjsBlanky

Blank Express.js project to get up and running FAST.
MIT License
7 stars 0 forks source link

[CONCEPT] HTTP request input validation #50

Open levibostian opened 3 years ago

levibostian commented 3 years ago

this is currently how i am validating my requets:

    router.post(
      "/user/login",
      bruteForcePrevent(), // To prevent sending too many emails.
      routesVersioning({
        "1.0.0": createEndpoint({
          validate: [
            check("email")
              .exists()
              .isEmail()
              .customSanitzer(email => normalizeEmail(email), 
            check("bundle")
              .exists()
          ],
          request: async (req, res, next) => {
            const body: AppInfo & {
              email: string
            } = req.body
            const userController: UserController = Di.inject(Dependency.UserController)
            await userController.sendLoginLink(body.email, body)
            return Promise.reject(
              new LoginUserSuccess("Check your email to login.")
            ) 
          }
        })
      })
    )

notice a couple of things.

  1. the email parameter. its required, i check if it’s an email, and I then sanitize so it’s ready for my request. this code can be duplicated in tons of places in my code. lots of copy/paste.
  2. the const body = code I have. that is a type safe defintion of my body.

my idea is: what if I can combine this? What if all I need to do is pass in an object:

    {
        email: Email,
        bundle: AppInfo?,    
    }

This defines my body. i can allow nesting and arrays, too. I allow optionals and required. I define custom type such as Email which will perform validation and sanitization for me.

I need to create Email and AppInfo validators. But that’s ok because…

  1. these validators can be unit tested. this allows me to skip some testing of the endpoints.
  2. they are re-usable.

what is nice is that i have less copy/paste code, my code is more type safe. also I can use this in the future for auto API documentation generation.

This is the api I am thinking:

    router.post(
      "/user/login",
      bruteForcePrevent(), // To prevent sending too many emails.
      routesVersioning({
        "1.0.0": async (req, res, next) => {
            const body = verifyBody(req, next, {
              email: Email,
              appInfo: AppInfo?
            }

            const userController: UserController = Di.inject(Dependency.UserController)
            await userController.sendLoginLink(body.email, body)
            return Promise.reject(
              new LoginUserSuccess("Check your email to login.")
            ) 
          }
      })
    )

verifyBody is the function you call my library. we pass in req and next. req to pull requets from and next if we encounter an error we can call next and bypass your code. stop the request right there.

we use verifyBody and verifyQuery so we are checking a very specific thing. we don’t want a “catch-all” function that verifies either or.