Add password reset and email verification, change nomenclature so that "verification...
[ndcode_site.git] / api / password_reset.json.jst
1 let crypto = require('crypto')
2 let logjson = (await import('@ndcode/logjson')).default
3 let XDate = require('xdate')
4
5 return async env => {
6   let globals = await env.site.get_json('/_config/globals.json')
7   let nodemailer_noreply = await env.site.get_nodemailer(
8     '/_config/nodemailer_noreply.json'
9   )
10   let post_request = await _require('/_lib/post_request.jst')
11   let session_cookie = await _require('/_lib/session_cookie.jst')
12   let Problem = await _require('/_lib/Problem.jst')
13
14   post_request(
15     // env
16     env,
17     // endpoint
18     '/api/password_reset.json',
19     // handler
20     async (email, password) => {
21       // coerce and/or validate
22       email = email.slice(0, 256).toLowerCase()
23       password = password.slice(0, 256)
24       if (email.length === 0 || password.length < 8)
25         throw new Problem(
26           'Bad request',
27           'Minimum length check failed',
28           400
29         )
30
31       let transaction = await env.site.database.Transaction()
32       try {
33         // initialize env.session_key, set cookie in env.response
34         await session_cookie(env, transaction)
35
36         let account = await (
37           await (
38             await transaction.get({})
39           ).get('accounts', {})
40         ).get(email)
41         if (account === undefined)
42           throw new Problem(
43             'Account does not exist',
44             `Please create the account for "${email}" before attempting to reset its password.`
45             421
46           )
47
48         let link_code = crypto.randomBytes(16).toString('hex')
49         let expires = new XDate()
50         expires.addDays(1)
51         account.set(
52           'verify_password',
53           transaction.json_to_logjson(
54             {
55               password,
56               link_code,
57               expires: expires.getTime()
58             }
59           )
60         )
61
62         let given_names = await logjson.logjson_to_json(
63           await account.get('given_names', '')
64         )
65         let family_name = await logjson.logjson_to_json(
66           await account.get('family_name', '')
67         )
68         let name =
69           family_name.length ? `${given_names} ${family_name}` : given_names
70
71         await nodemailer_noreply.sendMail(
72           {
73             from: globals.noreply_from,
74             to: `${name} <${email}>`,
75             subject: 'Password reset',
76             text: `Dear ${given_names},
77
78 We have received a request to reset the account password for your email address.
79
80 If this request is valid, please verify the new password by visiting the below link:
81 ${globals.site_url}/my_account/verify_password/index.html?email=${encodeURIComponent(email)}&link_code=${encodeURIComponent(link_code)}
82
83 The link is valid for 24 hours.
84
85 Thanks,
86 ${globals.noreply_signature}
87 `
88           }
89         )
90
91         await transaction.commit()
92       }
93       catch (error) {
94         transaction.rollback()
95         throw error
96       }
97     }
98   )
99 }