注册并保存密码哈希
机器翻译
本页由英文原文机器翻译而来,尚未经过母语审校,欢迎在 GitHub 上提出修改。内容如有出入,以英文原文为准。
任务: 创建账号,而且从不保存密码本身。
yaml
# Recipe: an account is created with a hash of the password, never the password.
paths:
components: ./components
migrations: ./migrations
datasources:
db:
driver: sqlite
database: ./data/users.db1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
sql
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
password_hash TEXT NOT NULL
);1
2
3
4
5
6
2
3
4
5
6
hashPassword(password) 每次都用新的盐返回一个 bcrypt 哈希;INSERT 保存的就是它。 q:param 的规则最先运行:少于 12 个字符的密码永远不会到达查询。
xml
<q:component name="SignUp">
<q:action name="register" method="POST">
<q:param name="name" required="true" minlength="2" maxlength="80" />
<q:param name="email" type="email" required="true" />
<q:param name="password" required="true" minlength="12" />
<q:query name="taken" datasource="db">
SELECT id FROM users WHERE email = :email
<q:param name="email" value="{email}" type="string" />
</q:query>
<q:if condition="taken_result.recordCount > 0">
<q:redirect url="/" flash="There is already an account for {email}." flashType="error" />
</q:if>
<!-- hashPassword: bcrypt, with a new salt every time. -->
<q:query name="created" datasource="db">
INSERT INTO users (email, name, password_hash) VALUES (:email, :name, :hash)
<q:param name="email" value="{email}" type="string" />
<q:param name="name" value="{name}" type="string" />
<q:param name="hash" value="{hashPassword(password)}" type="string" />
</q:query>
<q:redirect url="/" flash="Account created for {name}." />
</q:action>
<h1>Create an account</h1>
<q:if condition="flash"><p class="flash-{flashType}">{flash}</p></q:if>
<form method="POST">
<input name="name" placeholder="Name" />
<input name="email" type="email" placeholder="E-mail" />
<input name="password" type="password" placeholder="Password (12 characters or more)" />
<button>Create account</button>
</form>
</q:component>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
测试查看数据表:这一行保存的是 bcrypt 哈希(以 $2b$ 开头),而且没有任何一行保存了原样输入的密码:
xml
<q:test name="the account keeps a bcrypt hash, not the password" page="/">
<test:submit action="register" name="Bia" email="bia@example.com" password="a long passphrase" />
<test:expect redirect="/" flash="Account created for Bia." />
<test:expect table="users" count="1" where="email = 'bia@example.com' AND password_hash LIKE '$2b$%'" />
<test:expect table="users" count="0" where="password_hash = 'a long passphrase'" />
</q:test>
<q:test name="a short password is refused on its field" page="/">
<test:submit action="register" name="Bia" email="bia@example.com" password="short" />
<test:expect error="password" message="Must be at least 12 characters" />
<test:expect table="users" count="0" />
</q:test>
<q:test name="an address that already has an account is refused" page="/">
<test:given table="users" email="bia@example.com" name="Bia" password_hash="x" />
<test:submit action="register" name="Bia Again" email="bia@example.com" password="a long passphrase" />
<test:expect redirect="/" flash="There is already an account for bia@example.com." />
<test:expect table="users" count="1" />
</q:test>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
text
tests/signup.test.q
PASS the account keeps a bcrypt hash, not the password
PASS a short password is refused on its field
PASS an address that already has an account is refused
3 passed, 0 failed1
2
3
4
5
2
3
4
5
要用这个哈希登录,参见用哈希密码登录。