Start learning free

LESSON 98 OF 173 · BACKEND: USERS AND SECURITY

Validate Everything

Practice

A client can send anything: an email without an @, a password of one letter, a price of -5, a price written as text. Today the shop stores most of it and crashes with 500 on the rest. A safe backend validates every value before it stores it.

When a value is wrong, the shop answers 400 Bad Request with a message and the name of the field that is wrong, so an app can show the message next to that box. The starting code has two helpers: Field(data, name) reads a field as a string, an int, or null when it is missing, and Invalid(field, message) writes the 400 answer. The is pattern tells a value's type, and Split cuts a text into parts.

object a = 120;
object b = "120";
object c = null;
a is int number          // true, and number is 120
b is int number          // false: a text, even if it looks like a number
c is string text         // false: that's what Field gives for a missing field
"bo@shop.io".Split('@')  // ["bo", "shop.io"]

Read fields with Field(data, …), not data.GetProperty(…): a missing field gives null instead of crashing with 500. And check every route that takes a value: a rule that one route forgets protects nothing.

POST /register  {"email": "ana.example.com", "password": "secret123"}  →  400  {"error": "Enter a valid email, like ana@example.com", "field": "email"}
POST /register  {"password": "secret123"}  →  400  {"error": "Enter a valid email, like ana@example.com", "field": "email"}
POST /register  {"email": "ana@example.com", "password": "123"}  →  400  {"error": "The password needs at least 8 characters", "field": "password"}
POST /register  {"email": "ana@example.com", "password": "secret123"}  →  201  {"email": "ana@example.com", "id": 2}
PUT /me/password  {"old_password": "secret123", "new_password": "abc"}  (Ana's token)  →  400  {"error": "The password needs at least 8 characters", "field": "new_password"}
POST /products  {"name": "Desk", "price": -5}  (admin's token)  →  400  {"error": "The price must be a whole number from 1 to 10000", "field": "price"}
POST /products  {"name": "Desk", "price": "120"}  (admin's token)  →  400  {"error": "The price must be a whole number from 1 to 10000", "field": "price"}
POST /products  {"name": "Desk", "price": 120}  (admin's token)  →  201  {"id": 9, "name": "Desk", "price": 120}

Your task: write the local function ValidEmail(email). Then, with Invalid, check the email and the password at the top of /register and the price at the top of the admins' /products. /me/password already checks the new password.


Assignment
Follow the Coding Tutorial to write ValidEmail and add the three checks.


Hint
Put the type test (is not string, is not int) first in each if: || stops at the first true part, so .Length or .Count never runs on null. An email is valid when it is a text with exactly one @, no space, a name before the @ and a dot after it.


STEP 1 OF 11
    Ctrl + Enter to check