@rpcbase/server 0.74.0 → 0.77.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin/README.md +1 -1
- package/bin.js +2 -3
- package/express/dev_save_coverage.js +2 -2
- package/express/index.js +5 -0
- package/express/session_middleware.js +5 -0
- package/index.js +3 -3
- package/mailer/index.js +3 -2
- package/package.json +3 -2
- package/src/auth/check_session.js +15 -0
- package/src/auth/forgot_password_email.html +515 -0
- package/src/auth/index.js +50 -0
- package/src/auth/reset_password.js +70 -0
- package/src/auth/set_new_password.js +63 -0
- package/src/auth/set_new_password_email.html +3 -0
- package/src/auth/sign_in.js +0 -4
- package/src/auth/sign_out.js +11 -0
- package/src/client/README.md +2 -0
- package/src/helpers/async_wrapper.js +8 -0
- package/src/models/ResetPasswordToken.js +14 -0
- package/crypto/index.js +0 -47
package/admin/README.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
admin middleware that exposes models, schemas etc
|
|
1
|
+
admin middleware that exposes the local models, schemas etc
|
package/bin.js
CHANGED
|
@@ -13,8 +13,6 @@ const run_agent = require("./cli/run_agent")
|
|
|
13
13
|
|
|
14
14
|
let is_command = false
|
|
15
15
|
|
|
16
|
-
console.log("PACKVER", pack.version)
|
|
17
|
-
|
|
18
16
|
const args = yargs(hideBin(process.argv))
|
|
19
17
|
.command("start", "runs server/infrastructure", () => {}, (args) => {
|
|
20
18
|
is_command = true
|
|
@@ -29,7 +27,6 @@ const args = yargs(hideBin(process.argv))
|
|
|
29
27
|
build_server()
|
|
30
28
|
})
|
|
31
29
|
.option("verbose", {
|
|
32
|
-
alias: "v",
|
|
33
30
|
type: "boolean",
|
|
34
31
|
default: false,
|
|
35
32
|
description: "Run with verbose logging"
|
|
@@ -40,6 +37,8 @@ if (!is_command) {
|
|
|
40
37
|
console.log("server default com22mand")
|
|
41
38
|
}
|
|
42
39
|
|
|
40
|
+
|
|
41
|
+
// TODO: why was this needed
|
|
43
42
|
// // add helpers to ensure proper process termination
|
|
44
43
|
// const exit_clean = () => {
|
|
45
44
|
// process.exit()
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/* @flow */
|
|
2
|
+
// TODO: replace console debug
|
|
2
3
|
|
|
3
|
-
const is_production = process.env.NODE_ENV === "production"
|
|
4
4
|
|
|
5
|
+
const is_production = process.env.NODE_ENV === "production"
|
|
5
6
|
|
|
6
7
|
module.exports = (app) => {
|
|
7
|
-
|
|
8
8
|
app.post("/api/__dev_save_coverage", (req, res) => {
|
|
9
9
|
console.log("sending coverage for", req.body.path_key)
|
|
10
10
|
res.json(global.__coverage__)
|
package/express/index.js
CHANGED
|
@@ -3,9 +3,13 @@ const cors = require("cors")
|
|
|
3
3
|
const express = require("express")
|
|
4
4
|
const body_parser = require("body-parser")
|
|
5
5
|
|
|
6
|
+
// functionality middlewares
|
|
7
|
+
const auth = require("../src/auth")
|
|
8
|
+
|
|
6
9
|
const dev_save_coverage = require("./dev_save_coverage")
|
|
7
10
|
const session_middleware = require("./session_middleware")
|
|
8
11
|
|
|
12
|
+
|
|
9
13
|
const is_production = process.env.IS_PRODUCTION === "yes"
|
|
10
14
|
const {APP_DOMAIN, CLIENT_PORT} = process.env
|
|
11
15
|
|
|
@@ -57,6 +61,7 @@ module.exports = () => {
|
|
|
57
61
|
app.get("/api/ping", (req, res) => res.json({message: "pong"}))
|
|
58
62
|
app.post("/api/ping", (req, res) => res.json({message: "pong"}))
|
|
59
63
|
|
|
64
|
+
auth(app)
|
|
60
65
|
dev_save_coverage(app)
|
|
61
66
|
|
|
62
67
|
return app
|
|
@@ -58,9 +58,14 @@ setTimeout(async() => {
|
|
|
58
58
|
store: new redis_store({client: redis_client}),
|
|
59
59
|
proxy: true,
|
|
60
60
|
saveUninitialized: false,
|
|
61
|
+
// WARNING
|
|
61
62
|
// TODO: use session secret from env
|
|
62
63
|
secret: "session secret wowow",
|
|
63
64
|
resave: false,
|
|
65
|
+
cookie: {
|
|
66
|
+
// TODO: test this
|
|
67
|
+
maxAge: 1000 * 3600 * 24 * 100 // 100 days
|
|
68
|
+
}
|
|
64
69
|
})
|
|
65
70
|
|
|
66
71
|
// TODO: is this still necessary
|
package/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/* @flow */
|
|
2
|
-
const client_router = require("./client/client_router")
|
|
3
2
|
const database = require("./database")
|
|
4
3
|
const express = require("./express")
|
|
5
|
-
const
|
|
4
|
+
const client_router = require("./src/client/client_router")
|
|
5
|
+
const rpc_router = require("./src/rpc/rpc_router")
|
|
6
6
|
|
|
7
7
|
module.exports = {
|
|
8
|
-
client_router,
|
|
9
8
|
database,
|
|
10
9
|
express,
|
|
10
|
+
client_router,
|
|
11
11
|
rpc_router,
|
|
12
12
|
}
|
package/mailer/index.js
CHANGED
|
@@ -3,11 +3,12 @@ const postmark = require("postmark")
|
|
|
3
3
|
|
|
4
4
|
const {POSTMARK_API_KEY, IS_PRODUCTION} = process.env
|
|
5
5
|
|
|
6
|
-
const is_production = IS_PRODUCTION === "yes"
|
|
6
|
+
const is_production = true //IS_PRODUCTION === "yes"
|
|
7
|
+
console.warn("email sender forcing is production")
|
|
7
8
|
|
|
8
9
|
let client
|
|
9
10
|
|
|
10
|
-
if (is_production) {
|
|
11
|
+
if (is_production && typeof POSTMARK_API_KEY === "string" && POSTMARK_API_KEY.trim() !== "") {
|
|
11
12
|
client = new postmark.ServerClient(POSTMARK_API_KEY)
|
|
12
13
|
} else {
|
|
13
14
|
client = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rpcbase/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.77.0",
|
|
4
4
|
"license": "SSPL-1.0",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"bin": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@rpcbase/agent": "0.9.0",
|
|
14
|
-
"@rpcbase/std": "0.
|
|
14
|
+
"@rpcbase/std": "0.3.0",
|
|
15
15
|
"body-parser": "1.20.0",
|
|
16
16
|
"connect-redis": "6.1.3",
|
|
17
17
|
"cors": "2.8.5",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"express": "4.18.1",
|
|
21
21
|
"express-session": "1.17.3",
|
|
22
22
|
"glob": "8.0.3",
|
|
23
|
+
"lodash": "4.17.21",
|
|
23
24
|
"mongoose": "6.4.0",
|
|
24
25
|
"picocolors": "1.0.0",
|
|
25
26
|
"postmark": "3.0.11",
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
|
|
3
|
+
const check_session = async(payload, ctx) => {
|
|
4
|
+
const {req} = ctx
|
|
5
|
+
// console.log("session check", req.session)
|
|
6
|
+
|
|
7
|
+
const is_signed_in = !!req.session.user_id
|
|
8
|
+
return {
|
|
9
|
+
status: "ok",
|
|
10
|
+
is_signed_in,
|
|
11
|
+
user_id: req.session.user_id,
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = check_session
|
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
2
|
+
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
3
|
+
<head>
|
|
4
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
5
|
+
<meta name="x-apple-disable-message-reformatting" />
|
|
6
|
+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
|
7
|
+
<meta name="color-scheme" content="light dark" />
|
|
8
|
+
<meta name="supported-color-schemes" content="light dark" />
|
|
9
|
+
<title></title>
|
|
10
|
+
<style type="text/css" rel="stylesheet" media="all">
|
|
11
|
+
/* Base ------------------------------ */
|
|
12
|
+
|
|
13
|
+
@import url("https://fonts.googleapis.com/css?family=Nunito+Sans:400,700&display=swap");
|
|
14
|
+
body {
|
|
15
|
+
width: 100% !important;
|
|
16
|
+
height: 100%;
|
|
17
|
+
margin: 0;
|
|
18
|
+
-webkit-text-size-adjust: none;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
a {
|
|
22
|
+
color: #3869D4;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
a img {
|
|
26
|
+
border: none;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
td {
|
|
30
|
+
word-break: break-word;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.preheader {
|
|
34
|
+
display: none !important;
|
|
35
|
+
visibility: hidden;
|
|
36
|
+
mso-hide: all;
|
|
37
|
+
font-size: 1px;
|
|
38
|
+
line-height: 1px;
|
|
39
|
+
max-height: 0;
|
|
40
|
+
max-width: 0;
|
|
41
|
+
opacity: 0;
|
|
42
|
+
overflow: hidden;
|
|
43
|
+
}
|
|
44
|
+
/* Type ------------------------------ */
|
|
45
|
+
|
|
46
|
+
body,
|
|
47
|
+
td,
|
|
48
|
+
th {
|
|
49
|
+
font-family: "Nunito Sans", Helvetica, Arial, sans-serif;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
h1 {
|
|
53
|
+
margin-top: 0;
|
|
54
|
+
color: #333333;
|
|
55
|
+
font-size: 22px;
|
|
56
|
+
font-weight: bold;
|
|
57
|
+
text-align: left;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
h2 {
|
|
61
|
+
margin-top: 0;
|
|
62
|
+
color: #333333;
|
|
63
|
+
font-size: 16px;
|
|
64
|
+
font-weight: bold;
|
|
65
|
+
text-align: left;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
h3 {
|
|
69
|
+
margin-top: 0;
|
|
70
|
+
color: #333333;
|
|
71
|
+
font-size: 14px;
|
|
72
|
+
font-weight: bold;
|
|
73
|
+
text-align: left;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
td,
|
|
77
|
+
th {
|
|
78
|
+
font-size: 16px;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
p,
|
|
82
|
+
ul,
|
|
83
|
+
ol,
|
|
84
|
+
blockquote {
|
|
85
|
+
margin: .4em 0 1.1875em;
|
|
86
|
+
font-size: 16px;
|
|
87
|
+
line-height: 1.625;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
p.sub {
|
|
91
|
+
font-size: 13px;
|
|
92
|
+
}
|
|
93
|
+
/* Utilities ------------------------------ */
|
|
94
|
+
|
|
95
|
+
.align-right {
|
|
96
|
+
text-align: right;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.align-left {
|
|
100
|
+
text-align: left;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.align-center {
|
|
104
|
+
text-align: center;
|
|
105
|
+
}
|
|
106
|
+
/* Buttons ------------------------------ */
|
|
107
|
+
|
|
108
|
+
.button {
|
|
109
|
+
background-color: #3869D4;
|
|
110
|
+
border-top: 10px solid #3869D4;
|
|
111
|
+
border-right: 18px solid #3869D4;
|
|
112
|
+
border-bottom: 10px solid #3869D4;
|
|
113
|
+
border-left: 18px solid #3869D4;
|
|
114
|
+
display: inline-block;
|
|
115
|
+
color: #FFF;
|
|
116
|
+
text-decoration: none;
|
|
117
|
+
border-radius: 3px;
|
|
118
|
+
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.16);
|
|
119
|
+
-webkit-text-size-adjust: none;
|
|
120
|
+
box-sizing: border-box;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.button--green {
|
|
124
|
+
background-color: #22BC66;
|
|
125
|
+
border-top: 10px solid #22BC66;
|
|
126
|
+
border-right: 18px solid #22BC66;
|
|
127
|
+
border-bottom: 10px solid #22BC66;
|
|
128
|
+
border-left: 18px solid #22BC66;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.button--red {
|
|
132
|
+
background-color: #FF6136;
|
|
133
|
+
border-top: 10px solid #FF6136;
|
|
134
|
+
border-right: 18px solid #FF6136;
|
|
135
|
+
border-bottom: 10px solid #FF6136;
|
|
136
|
+
border-left: 18px solid #FF6136;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
@media only screen and (max-width: 500px) {
|
|
140
|
+
.button {
|
|
141
|
+
width: 100% !important;
|
|
142
|
+
text-align: center !important;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/* Attribute list ------------------------------ */
|
|
146
|
+
|
|
147
|
+
.attributes {
|
|
148
|
+
margin: 0 0 21px;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.attributes_content {
|
|
152
|
+
background-color: #F4F4F7;
|
|
153
|
+
padding: 16px;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.attributes_item {
|
|
157
|
+
padding: 0;
|
|
158
|
+
}
|
|
159
|
+
/* Related Items ------------------------------ */
|
|
160
|
+
|
|
161
|
+
.related {
|
|
162
|
+
width: 100%;
|
|
163
|
+
margin: 0;
|
|
164
|
+
padding: 25px 0 0 0;
|
|
165
|
+
-premailer-width: 100%;
|
|
166
|
+
-premailer-cellpadding: 0;
|
|
167
|
+
-premailer-cellspacing: 0;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.related_item {
|
|
171
|
+
padding: 10px 0;
|
|
172
|
+
color: #CBCCCF;
|
|
173
|
+
font-size: 15px;
|
|
174
|
+
line-height: 18px;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.related_item-title {
|
|
178
|
+
display: block;
|
|
179
|
+
margin: .5em 0 0;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.related_item-thumb {
|
|
183
|
+
display: block;
|
|
184
|
+
padding-bottom: 10px;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.related_heading {
|
|
188
|
+
border-top: 1px solid #CBCCCF;
|
|
189
|
+
text-align: center;
|
|
190
|
+
padding: 25px 0 10px;
|
|
191
|
+
}
|
|
192
|
+
/* Discount Code ------------------------------ */
|
|
193
|
+
|
|
194
|
+
.discount {
|
|
195
|
+
width: 100%;
|
|
196
|
+
margin: 0;
|
|
197
|
+
padding: 24px;
|
|
198
|
+
-premailer-width: 100%;
|
|
199
|
+
-premailer-cellpadding: 0;
|
|
200
|
+
-premailer-cellspacing: 0;
|
|
201
|
+
background-color: #F4F4F7;
|
|
202
|
+
border: 2px dashed #CBCCCF;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.discount_heading {
|
|
206
|
+
text-align: center;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.discount_body {
|
|
210
|
+
text-align: center;
|
|
211
|
+
font-size: 15px;
|
|
212
|
+
}
|
|
213
|
+
/* Social Icons ------------------------------ */
|
|
214
|
+
|
|
215
|
+
.social {
|
|
216
|
+
width: auto;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.social td {
|
|
220
|
+
padding: 0;
|
|
221
|
+
width: auto;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.social_icon {
|
|
225
|
+
height: 20px;
|
|
226
|
+
margin: 0 8px 10px 8px;
|
|
227
|
+
padding: 0;
|
|
228
|
+
}
|
|
229
|
+
/* Data table ------------------------------ */
|
|
230
|
+
|
|
231
|
+
.purchase {
|
|
232
|
+
width: 100%;
|
|
233
|
+
margin: 0;
|
|
234
|
+
padding: 35px 0;
|
|
235
|
+
-premailer-width: 100%;
|
|
236
|
+
-premailer-cellpadding: 0;
|
|
237
|
+
-premailer-cellspacing: 0;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.purchase_content {
|
|
241
|
+
width: 100%;
|
|
242
|
+
margin: 0;
|
|
243
|
+
padding: 25px 0 0 0;
|
|
244
|
+
-premailer-width: 100%;
|
|
245
|
+
-premailer-cellpadding: 0;
|
|
246
|
+
-premailer-cellspacing: 0;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.purchase_item {
|
|
250
|
+
padding: 10px 0;
|
|
251
|
+
color: #51545E;
|
|
252
|
+
font-size: 15px;
|
|
253
|
+
line-height: 18px;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.purchase_heading {
|
|
257
|
+
padding-bottom: 8px;
|
|
258
|
+
border-bottom: 1px solid #EAEAEC;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.purchase_heading p {
|
|
262
|
+
margin: 0;
|
|
263
|
+
color: #85878E;
|
|
264
|
+
font-size: 12px;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.purchase_footer {
|
|
268
|
+
padding-top: 15px;
|
|
269
|
+
border-top: 1px solid #EAEAEC;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.purchase_total {
|
|
273
|
+
margin: 0;
|
|
274
|
+
text-align: right;
|
|
275
|
+
font-weight: bold;
|
|
276
|
+
color: #333333;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.purchase_total--label {
|
|
280
|
+
padding: 0 15px 0 0;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
body {
|
|
284
|
+
background-color: #F4F4F7;
|
|
285
|
+
color: #51545E;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
p {
|
|
289
|
+
color: #51545E;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
p.sub {
|
|
293
|
+
color: #6B6E76;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.email-wrapper {
|
|
297
|
+
width: 100%;
|
|
298
|
+
margin: 0;
|
|
299
|
+
padding: 0;
|
|
300
|
+
-premailer-width: 100%;
|
|
301
|
+
-premailer-cellpadding: 0;
|
|
302
|
+
-premailer-cellspacing: 0;
|
|
303
|
+
background-color: #F4F4F7;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.email-content {
|
|
307
|
+
width: 100%;
|
|
308
|
+
margin: 0;
|
|
309
|
+
padding: 0;
|
|
310
|
+
-premailer-width: 100%;
|
|
311
|
+
-premailer-cellpadding: 0;
|
|
312
|
+
-premailer-cellspacing: 0;
|
|
313
|
+
}
|
|
314
|
+
/* Masthead ----------------------- */
|
|
315
|
+
|
|
316
|
+
.email-masthead {
|
|
317
|
+
padding: 25px 0;
|
|
318
|
+
text-align: center;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.email-masthead_logo {
|
|
322
|
+
width: 94px;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.email-masthead_name {
|
|
326
|
+
font-size: 16px;
|
|
327
|
+
font-weight: bold;
|
|
328
|
+
color: #A8AAAF;
|
|
329
|
+
text-decoration: none;
|
|
330
|
+
text-shadow: 0 1px 0 white;
|
|
331
|
+
}
|
|
332
|
+
/* Body ------------------------------ */
|
|
333
|
+
|
|
334
|
+
.email-body {
|
|
335
|
+
width: 100%;
|
|
336
|
+
margin: 0;
|
|
337
|
+
padding: 0;
|
|
338
|
+
-premailer-width: 100%;
|
|
339
|
+
-premailer-cellpadding: 0;
|
|
340
|
+
-premailer-cellspacing: 0;
|
|
341
|
+
background-color: #FFFFFF;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.email-body_inner {
|
|
345
|
+
width: 570px;
|
|
346
|
+
margin: 0 auto;
|
|
347
|
+
padding: 0;
|
|
348
|
+
-premailer-width: 570px;
|
|
349
|
+
-premailer-cellpadding: 0;
|
|
350
|
+
-premailer-cellspacing: 0;
|
|
351
|
+
background-color: #FFFFFF;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
.email-footer {
|
|
355
|
+
width: 570px;
|
|
356
|
+
margin: 0 auto;
|
|
357
|
+
padding: 0;
|
|
358
|
+
-premailer-width: 570px;
|
|
359
|
+
-premailer-cellpadding: 0;
|
|
360
|
+
-premailer-cellspacing: 0;
|
|
361
|
+
text-align: center;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.email-footer p {
|
|
365
|
+
color: #6B6E76;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.body-action {
|
|
369
|
+
width: 100%;
|
|
370
|
+
margin: 30px auto;
|
|
371
|
+
padding: 0;
|
|
372
|
+
-premailer-width: 100%;
|
|
373
|
+
-premailer-cellpadding: 0;
|
|
374
|
+
-premailer-cellspacing: 0;
|
|
375
|
+
text-align: center;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.body-sub {
|
|
379
|
+
margin-top: 25px;
|
|
380
|
+
padding-top: 25px;
|
|
381
|
+
border-top: 1px solid #EAEAEC;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.content-cell {
|
|
385
|
+
padding: 35px;
|
|
386
|
+
}
|
|
387
|
+
/*Media Queries ------------------------------ */
|
|
388
|
+
|
|
389
|
+
@media only screen and (max-width: 600px) {
|
|
390
|
+
.email-body_inner,
|
|
391
|
+
.email-footer {
|
|
392
|
+
width: 100% !important;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
@media (prefers-color-scheme: dark) {
|
|
397
|
+
body,
|
|
398
|
+
.email-body,
|
|
399
|
+
.email-body_inner,
|
|
400
|
+
.email-content,
|
|
401
|
+
.email-wrapper,
|
|
402
|
+
.email-masthead,
|
|
403
|
+
.email-footer {
|
|
404
|
+
background-color: #333333 !important;
|
|
405
|
+
color: #FFF !important;
|
|
406
|
+
}
|
|
407
|
+
p,
|
|
408
|
+
ul,
|
|
409
|
+
ol,
|
|
410
|
+
blockquote,
|
|
411
|
+
h1,
|
|
412
|
+
h2,
|
|
413
|
+
h3,
|
|
414
|
+
span,
|
|
415
|
+
.purchase_item {
|
|
416
|
+
color: #FFF !important;
|
|
417
|
+
}
|
|
418
|
+
.attributes_content,
|
|
419
|
+
.discount {
|
|
420
|
+
background-color: #222 !important;
|
|
421
|
+
}
|
|
422
|
+
.email-masthead_name {
|
|
423
|
+
text-shadow: none !important;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
:root {
|
|
428
|
+
color-scheme: light dark;
|
|
429
|
+
supported-color-schemes: light dark;
|
|
430
|
+
}
|
|
431
|
+
</style>
|
|
432
|
+
<!--[if mso]>
|
|
433
|
+
<style type="text/css">
|
|
434
|
+
.f-fallback {
|
|
435
|
+
font-family: Arial, sans-serif;
|
|
436
|
+
}
|
|
437
|
+
</style>
|
|
438
|
+
<![endif]-->
|
|
439
|
+
</head>
|
|
440
|
+
<body>
|
|
441
|
+
<span class="preheader">Your password reset link is ready</span>
|
|
442
|
+
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
|
443
|
+
<tr>
|
|
444
|
+
<td align="center">
|
|
445
|
+
<table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
|
446
|
+
<tr>
|
|
447
|
+
<td class="email-masthead">
|
|
448
|
+
<a href="https://example.com" class="f-fallback email-masthead_name">
|
|
449
|
+
[Product Name]
|
|
450
|
+
</a>
|
|
451
|
+
</td>
|
|
452
|
+
</tr>
|
|
453
|
+
<!-- Email Body -->
|
|
454
|
+
<tr>
|
|
455
|
+
<td class="email-body" width="100%" cellpadding="0" cellspacing="0">
|
|
456
|
+
<table class="email-body_inner" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
|
|
457
|
+
<!-- Body content -->
|
|
458
|
+
<tr>
|
|
459
|
+
<td class="content-cell">
|
|
460
|
+
<div class="f-fallback">
|
|
461
|
+
<h1>Your password reset link</h1>
|
|
462
|
+
<p>The password reset link you have requested is ready. If you did not request a password reset, ignore this email.</p>
|
|
463
|
+
<!-- Action -->
|
|
464
|
+
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
|
465
|
+
<tr>
|
|
466
|
+
<td align="center">
|
|
467
|
+
<!-- Border based button
|
|
468
|
+
https://litmus.com/blog/a-guide-to-bulletproof-buttons-in-email-design -->
|
|
469
|
+
<table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
|
|
470
|
+
<tr>
|
|
471
|
+
<td align="center">
|
|
472
|
+
<a href="<%= reset_url %>" class="f-fallback button" target="_blank">Reset My Password</a>
|
|
473
|
+
</td>
|
|
474
|
+
</tr>
|
|
475
|
+
</table>
|
|
476
|
+
</td>
|
|
477
|
+
</tr>
|
|
478
|
+
</table>
|
|
479
|
+
|
|
480
|
+
<table class="body-sub" role="presentation">
|
|
481
|
+
<tr>
|
|
482
|
+
<td>
|
|
483
|
+
<p class="f-fallback sub">If you’re having trouble with the button above, copy and paste the URL below into your web browser.</p>
|
|
484
|
+
<p class="f-fallback sub"><%= reset_url %></p>
|
|
485
|
+
</td>
|
|
486
|
+
</tr>
|
|
487
|
+
</table>
|
|
488
|
+
</div>
|
|
489
|
+
</td>
|
|
490
|
+
</tr>
|
|
491
|
+
</table>
|
|
492
|
+
</td>
|
|
493
|
+
</tr>
|
|
494
|
+
<tr>
|
|
495
|
+
<td>
|
|
496
|
+
<table class="email-footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
|
|
497
|
+
<tr>
|
|
498
|
+
<td class="content-cell" align="center">
|
|
499
|
+
<p class="f-fallback sub align-center">© 2022 [Product Name]. All rights reserved.</p>
|
|
500
|
+
<p class="f-fallback sub align-center">
|
|
501
|
+
[Company Name, LLC]
|
|
502
|
+
<br>1234 Street Rd.
|
|
503
|
+
<br>Suite 1234
|
|
504
|
+
</p>
|
|
505
|
+
</td>
|
|
506
|
+
</tr>
|
|
507
|
+
</table>
|
|
508
|
+
</td>
|
|
509
|
+
</tr>
|
|
510
|
+
</table>
|
|
511
|
+
</td>
|
|
512
|
+
</tr>
|
|
513
|
+
</table>
|
|
514
|
+
</body>
|
|
515
|
+
</html>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
const async_wrapper = require("../helpers/async_wrapper")
|
|
3
|
+
|
|
4
|
+
const sign_up = require("./sign_up")
|
|
5
|
+
const sign_in = require("./sign_in")
|
|
6
|
+
const sign_out = require("./sign_out")
|
|
7
|
+
const reset_password = require("./reset_password")
|
|
8
|
+
const set_new_password = require("./set_new_password")
|
|
9
|
+
const check_session = require("./check_session")
|
|
10
|
+
|
|
11
|
+
const sign_up_handler = async(req, res) => {
|
|
12
|
+
const result = await sign_up(req.body, {req})
|
|
13
|
+
res.json(result)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const sign_in_handler = async(req, res) => {
|
|
17
|
+
const result = await sign_in(req.body, {req})
|
|
18
|
+
res.json(result)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const sign_out_handler = async(req, res) => {
|
|
22
|
+
const result = await sign_out(req.body, {req})
|
|
23
|
+
res.json(result)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const reset_password_handler = async(req, res) => {
|
|
27
|
+
const result = await reset_password(req.body, {req})
|
|
28
|
+
res.json(result)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const set_new_password_handler = async(req, res) => {
|
|
32
|
+
const result = await set_new_password(req.body, {req})
|
|
33
|
+
res.json(result)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const check_session_handler = async(req, res) => {
|
|
37
|
+
const result = await check_session(req.body, {req})
|
|
38
|
+
res.json(result)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = (app) => {
|
|
42
|
+
app.post("/api/v1/auth/sign_up", async_wrapper(sign_up_handler))
|
|
43
|
+
app.post("/api/v1/auth/sign_in", async_wrapper(sign_in_handler))
|
|
44
|
+
app.post("/api/v1/auth/sign_out", async_wrapper(sign_out_handler))
|
|
45
|
+
|
|
46
|
+
app.post("/api/v1/auth/reset_password", async_wrapper(reset_password_handler))
|
|
47
|
+
app.post("/api/v1/auth/set_new_password", async_wrapper(set_new_password_handler))
|
|
48
|
+
|
|
49
|
+
app.post("/api/v1/auth/check_session", async_wrapper(check_session_handler))
|
|
50
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
const _template = require("lodash/template")
|
|
3
|
+
const fs = require("fs")
|
|
4
|
+
const path = require("path")
|
|
5
|
+
const isEmail = require("validator/lib/isEmail")
|
|
6
|
+
|
|
7
|
+
const {hash_password, compare_hash} = require("@rpcbase/std/crypto/hash")
|
|
8
|
+
const get_random_str = require("@rpcbase/std/crypto/get_random_str")
|
|
9
|
+
|
|
10
|
+
const mailer = require("../../mailer")
|
|
11
|
+
const mongoose = require("../../mongoose")
|
|
12
|
+
const ResetPasswordToken = require("../models/ResetPasswordToken")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
const {APP_DOMAIN} = process.env
|
|
16
|
+
|
|
17
|
+
const email_tpl = _template(
|
|
18
|
+
fs.readFileSync(path.join(__dirname, "./forgot_password_email.html"), "utf8")
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
const reset_password = async({email}, ctx) => {
|
|
23
|
+
const User = mongoose.model("User")
|
|
24
|
+
|
|
25
|
+
if (!isEmail(email)) {
|
|
26
|
+
throw new Error("reset_password:: invalid email")
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const user = await User.findOne({email}, null, {ctx})
|
|
30
|
+
|
|
31
|
+
if (!user) {
|
|
32
|
+
// TODO: add random delay to prevent detecting if account exists based on response time
|
|
33
|
+
return {status: "ok"}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const token = get_random_str(32)
|
|
37
|
+
const token_hash = await hash_password(token)
|
|
38
|
+
|
|
39
|
+
console.log("WOWOOWOWOW", await compare_hash(token, token_hash))
|
|
40
|
+
|
|
41
|
+
console.log("token", token)
|
|
42
|
+
console.log("token_hash", token_hash)
|
|
43
|
+
|
|
44
|
+
const reset_token = new ResetPasswordToken({
|
|
45
|
+
user_id: user._id,
|
|
46
|
+
token_hash,
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
await reset_token.save()
|
|
50
|
+
|
|
51
|
+
const reset_url = `https://${APP_DOMAIN}/set-new-password?id=${user._id}&token=${token}`
|
|
52
|
+
|
|
53
|
+
const res = await mailer.sendEmail({
|
|
54
|
+
From: "hello@commit-queue.com",
|
|
55
|
+
To: user.email,
|
|
56
|
+
Subject: "Your password reset link",
|
|
57
|
+
HtmlBody: email_tpl({
|
|
58
|
+
reset_url,
|
|
59
|
+
}),
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
// cleanup if email wasn't sent
|
|
63
|
+
if (res.Message !== "OK") {
|
|
64
|
+
await reset_token.delete()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {status: "ok"}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = reset_password
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
const _template = require("lodash/template")
|
|
3
|
+
const fs = require("fs")
|
|
4
|
+
const path = require("path")
|
|
5
|
+
const isEmail = require("validator/lib/isEmail")
|
|
6
|
+
|
|
7
|
+
const {hash_password, compare_hash} = require("@rpcbase/std/crypto/hash")
|
|
8
|
+
const get_random_str = require("@rpcbase/std/crypto/get_random_str")
|
|
9
|
+
|
|
10
|
+
const mailer = require("../../mailer")
|
|
11
|
+
const mongoose = require("../../mongoose")
|
|
12
|
+
const ResetPasswordToken = require("../models/ResetPasswordToken")
|
|
13
|
+
|
|
14
|
+
const {APP_DOMAIN} = process.env
|
|
15
|
+
|
|
16
|
+
const email_tpl = _template(
|
|
17
|
+
fs.readFileSync(path.join(__dirname, "./set_new_password_email.html"), "utf8")
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
const set_new_password = async({user_id, token, password}, ctx) => {
|
|
21
|
+
const User = mongoose.model("User")
|
|
22
|
+
|
|
23
|
+
const reset_tokens = await ResetPasswordToken.find({user_id})
|
|
24
|
+
.limit(100)
|
|
25
|
+
|
|
26
|
+
if (reset_tokens.length === 0) {
|
|
27
|
+
return {
|
|
28
|
+
message: "Unable to validate reset token, please try again in a moment"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let is_match = false
|
|
33
|
+
|
|
34
|
+
for (let i = 0; i < reset_tokens.length; i++) {
|
|
35
|
+
const op = await compare_hash(token, reset_tokens[i].token_hash)
|
|
36
|
+
if (op) {
|
|
37
|
+
is_match = true
|
|
38
|
+
break
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!is_match) {
|
|
43
|
+
return {
|
|
44
|
+
message: "Invalid or expired token, please try again in a moment"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// token is valid, update the user password
|
|
49
|
+
const user = await User.findOne({_id: user_id}, null, {ctx})
|
|
50
|
+
const hashed_password = await hash_password(password)
|
|
51
|
+
user.password_hash = hashed_password
|
|
52
|
+
|
|
53
|
+
await user.save()
|
|
54
|
+
|
|
55
|
+
// WARNING:
|
|
56
|
+
// TODO: important! notify the user that their password has been reset
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
status: "ok"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = set_new_password
|
package/src/auth/sign_in.js
CHANGED
|
@@ -17,7 +17,6 @@ const sign_in = async({email, password}, ctx) => {
|
|
|
17
17
|
// const user = await User.findOne({email}, null, {ctx})
|
|
18
18
|
const user = await User.findOne({email}, null)
|
|
19
19
|
|
|
20
|
-
// console.log("ICII2222", email, password, user)
|
|
21
20
|
|
|
22
21
|
if (!user) {
|
|
23
22
|
return fail()
|
|
@@ -25,9 +24,6 @@ const sign_in = async({email, password}, ctx) => {
|
|
|
25
24
|
|
|
26
25
|
const hashed_pass = user.password_hash
|
|
27
26
|
|
|
28
|
-
// TODO: fix broken bcrypt bins in docker
|
|
29
|
-
// const is_match = await bcrypt.compare(password, hashed_pass)
|
|
30
|
-
console.warn("warning: skipping bcrypt hash check")
|
|
31
27
|
const is_match = await compare_hash(password, hashed_pass)
|
|
32
28
|
|
|
33
29
|
if (is_match) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
const mongoose = require("../../mongoose")
|
|
3
|
+
|
|
4
|
+
const ResetPasswordToken = mongoose.model("ResetPasswordToken", {
|
|
5
|
+
user_id: String,
|
|
6
|
+
token_hash: String,
|
|
7
|
+
created_at: {
|
|
8
|
+
type: Date,
|
|
9
|
+
expires: 360, // 6min
|
|
10
|
+
default: Date.now,
|
|
11
|
+
}
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
module.exports = ResetPasswordToken
|
package/crypto/index.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/* @flow */
|
|
2
|
-
const crypto = require("crypto")
|
|
3
|
-
|
|
4
|
-
const ALGORITHM = "aes-256-cbc"
|
|
5
|
-
const {CRYPTO_SECRET} = process.env
|
|
6
|
-
|
|
7
|
-
if (!CRYPTO_SECRET || CRYPTO_SECRET.trim() === "") {
|
|
8
|
-
throw new Error("CRYPTO_SECRET not found in env")
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
// TODO: move to rb:std
|
|
12
|
-
|
|
13
|
-
const key = crypto.createHash("sha256")
|
|
14
|
-
.update(String(CRYPTO_SECRET))
|
|
15
|
-
.digest()
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const encrypt = (text) => {
|
|
19
|
-
const iv = crypto.randomBytes(16)
|
|
20
|
-
const cipher = crypto.createCipheriv(ALGORITHM, key, iv)
|
|
21
|
-
|
|
22
|
-
const encrypted = Buffer.concat([
|
|
23
|
-
cipher.update(text),
|
|
24
|
-
cipher.final()
|
|
25
|
-
])
|
|
26
|
-
|
|
27
|
-
return {
|
|
28
|
-
iv: iv.toString("hex"),
|
|
29
|
-
content: encrypted.toString("hex")
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const decrypt = (hash) => {
|
|
34
|
-
const decipher = crypto.createDecipheriv(
|
|
35
|
-
ALGORITHM, key, Buffer.from(hash.iv, "hex")
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
const decrpyted = Buffer.concat([
|
|
39
|
-
decipher.update(Buffer.from(hash.content, "hex")),
|
|
40
|
-
decipher.final(),
|
|
41
|
-
])
|
|
42
|
-
|
|
43
|
-
return decrpyted.toString()
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
module.exports = {encrypt, decrypt}
|