Test that a password is stored as a hash
Sign up in a zriz pipeline, then read the user row. The test fails if a result holds the password or the database holds it as plain text.
The problem
A password in plain text is the worst data to lose. The fault hides in two places: a result that returns the user record, and the column in the database. No user sees it until the data leaks.
The test
The example is a shop API with a MySQL database. The column password_hash must hold a bcrypt hash.
Start from a project that zz init made: the quick start gives the commands. Add the action register, the database value, and the database resource.
.zriz/resources/target.json:
{
"type": "http",
"description": "Your app under test",
"base-url": "${env.TARGET_URL}",
"headers": { "Content-Type": "application/json" },
"actions": {
"check": { "method": "GET", "path": "/api/health" },
"register": { "method": "POST", "path": "/api/auth/register" }
}
}
.zriz/environments/local.json:
{
"values": {
"TARGET_URL": "http://localhost:9080",
"SHOP_DB": "user:pass@tcp(localhost:3306)/shop"
},
"sensitive": ["SHOP_DB"]
}
.zriz/resources/shopdb.json:
{
"type": "sql",
"connection": "${env.SHOP_DB}",
"read-only": true,
"actions": {
"password-by-email": { "query": "SELECT password_hash FROM users WHERE email = ${ctx.email}" }
}
}
.zriz/pipelines/password-is-hashed.json:
{
"description": "The password is in no reply and is stored as a hash",
"steps": [
{ "set": { "email": "u-${gen.uuid}@test.com", "password": "secret-${gen.uuid}" } },
{
"call": "target/register",
"body": { "email": "${ctx.email}", "password": "${ctx.password}", "name": "Ann" },
"expect": [
["status", "==", 201],
["body.password", "not-exists"],
["body.password_hash", "not-exists"]
]
},
{
"call": "shopdb/password-by-email",
"expect": [
["row-count", "==", 1],
["rows[0].password_hash", "!=", "${ctx.password}"],
["rows[0].password_hash", "not-contains", "${ctx.password}"],
["rows[0].password_hash", "starts-with", "$2"]
]
}
]
}
Run it
zz run password-is-hashed
"status":"pass"
What it proves
not-existsonbody.passwordandbody.password_hash: the result holds no password and no hash.!=andnot-contains: the column does not hold the text that the test sent.starts-with $2: the value is a bcrypt hash. Change the text if your API uses a different hash.
Note: The test password is not a real secret. A real secret goes in the env file with its name in
sensitive.