Skip to content

Commit acef284

Browse files
committed
feat: use express-validator in password endpoints
1 parent 289600d commit acef284

File tree

3 files changed

+92
-79
lines changed

3 files changed

+92
-79
lines changed

packages/password/package.json

+2-3
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,15 @@
2424
"dependencies": {
2525
"@accounts/two-factor": "^0.32.4",
2626
"bcryptjs": "2.4.3",
27-
"tslib": "2.6.2",
28-
"validator": "^13.11.0"
27+
"express-validator": "^7.0.1",
28+
"tslib": "2.6.2"
2929
},
3030
"devDependencies": {
3131
"@accounts/server": "^0.33.1",
3232
"@accounts/types": "^0.33.1",
3333
"@types/bcryptjs": "2.4.6",
3434
"@types/express": "^4.17.21",
3535
"@types/lodash.set": "4.3.9",
36-
"@types/validator": "^13",
3736
"graphql": "16.8.1",
3837
"graphql-modules": "3.0.0-alpha-20231106133212-0b04b56e",
3938
"lodash.set": "4.3.2",
+88-66
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
import { type Injector } from 'graphql-modules';
22
import type { Request, Response, NextFunction } from 'express';
3-
import validator from 'validator';
43
import AccountsPassword from '../accounts-password';
4+
import { body, matchedData, param, validationResult } from 'express-validator';
5+
6+
function matchOrThrow<T extends Record<string, any> = Record<string, any>>(
7+
...args: Parameters<typeof matchedData>
8+
): T {
9+
if (!validationResult(args[0]).isEmpty()) {
10+
throw new Error('Validation error');
11+
}
12+
return matchedData(...args) as T;
13+
}
514

615
function getHtml(title: string, body: string) {
716
return `
@@ -30,62 +39,93 @@ export const infosMiddleware = (req: Request, _res: Response, next: NextFunction
3039
next();
3140
};
3241

33-
export const verifyEmail =
42+
export const verifyEmail = [
43+
param('token').isString().notEmpty(),
3444
(accountsPasswordOrInjector: Injector | AccountsPassword) =>
35-
async (req: Request, res: Response) => {
36-
try {
37-
const { token } = req.params;
38-
if (token == null) {
39-
throw new Error('Token is missing');
40-
}
41-
const accountsPassword =
42-
accountsPasswordOrInjector instanceof AccountsPassword
43-
? accountsPasswordOrInjector
44-
: accountsPasswordOrInjector.get(AccountsPassword);
45-
await accountsPassword.verifyEmail(token);
46-
res.send(
47-
getHtml(
48-
'Email successfully verified',
49-
`
45+
async (req: Request, res: Response) => {
46+
try {
47+
const { token } = matchOrThrow<{ token: string }>(req);
48+
const accountsPassword =
49+
accountsPasswordOrInjector instanceof AccountsPassword
50+
? accountsPasswordOrInjector
51+
: accountsPasswordOrInjector.get(AccountsPassword);
52+
await accountsPassword.verifyEmail(token);
53+
res.send(
54+
getHtml(
55+
'Email successfully verified',
56+
`
5057
<h3>The email address has been successfully verified.</h3>
5158
`
52-
)
53-
);
54-
} catch (err: any) {
55-
res.send(
56-
//codeql[js/xss-through-exception]
57-
getHtml(
58-
'Email verification error',
59-
`
59+
)
60+
);
61+
} catch (err: any) {
62+
res.send(
63+
//codeql[js/xss-through-exception]
64+
getHtml(
65+
'Email verification error',
66+
`
6067
<h3>The email address couldn't be verified: ${err.message ?? 'unknown error'}</h3>
6168
`
62-
)
63-
);
64-
}
65-
};
69+
)
70+
);
71+
}
72+
},
73+
];
6674

67-
export const resetPassword =
75+
export const resetPassword = [
76+
body('token').isString().notEmpty(),
77+
body('newPassword').isString().notEmpty(),
6878
(accountsPasswordOrInjector: Injector | AccountsPassword) =>
69-
async (req: Request, res: Response) => {
70-
try {
71-
const { token, newPassword } = req.body;
72-
if (token == null) {
73-
throw new Error('Token is missing');
74-
}
75-
if (newPassword == null) {
76-
throw new Error('New password is missing');
79+
async (req: Request, res: Response) => {
80+
try {
81+
const { token, newPassword } = matchOrThrow<{ token: string; newPassword: string }>(req);
82+
const accountsPassword =
83+
accountsPasswordOrInjector instanceof AccountsPassword
84+
? accountsPasswordOrInjector
85+
: accountsPasswordOrInjector.get(AccountsPassword);
86+
await accountsPassword.resetPassword(token, newPassword, req.infos);
87+
res.send(
88+
getHtml(
89+
'Password successfully changed',
90+
`
91+
<h3>The password has been successfully changed.</h3>
92+
`
93+
)
94+
);
95+
} catch (err: any) {
96+
//codeql[js/xss-through-exception]
97+
res.send(
98+
getHtml(
99+
'Password reset error',
100+
`
101+
<h3>The password couldn't be changed: ${err.message ?? 'unknown error'}</h3>
102+
`
103+
)
104+
);
77105
}
78-
const accountsPassword =
79-
accountsPasswordOrInjector instanceof AccountsPassword
80-
? accountsPasswordOrInjector
81-
: accountsPasswordOrInjector.get(AccountsPassword);
82-
await accountsPassword.resetPassword(token, newPassword, req.infos);
106+
},
107+
];
108+
109+
export const resetPasswordForm = [
110+
param('token').isString().notEmpty().escape(),
111+
(req: Request, res: Response) => {
112+
try {
113+
const { token } = matchOrThrow<{ token: string }>(req);
83114
res.send(
84115
getHtml(
85-
'Password successfully changed',
116+
'Reset password',
86117
`
87-
<h3>The password has been successfully changed.</h3>
88-
`
118+
<div class="container">
119+
<h1>Reset your password</h1>
120+
<form action="/resetPassword" method="POST">
121+
<input type="hidden" name="token" value=${token} />
122+
<div class="form-group">
123+
<label for="newPassword">New password</label>
124+
<input type="text" class="form-control" id="newPassword" value="" placeholder="Enter your new password" name="newPassword">
125+
</div>
126+
<button type="submit" class="btn btn-primary">Submit</button>
127+
</form>
128+
`
89129
)
90130
);
91131
} catch (err: any) {
@@ -99,23 +139,5 @@ export const resetPassword =
99139
)
100140
);
101141
}
102-
};
103-
104-
export const resetPasswordForm = (req: Request, res: Response): Response =>
105-
res.send(
106-
getHtml(
107-
'Reset password',
108-
`
109-
<div class="container">
110-
<h1>Reset your password</h1>
111-
<form action="/resetPassword" method="POST">
112-
<input type="hidden" name="token" value=${validator.escape(req.params.token)} />
113-
<div class="form-group">
114-
<label for="newPassword">New password</label>
115-
<input type="text" class="form-control" id="newPassword" value="" placeholder="Enter your new password" name="newPassword">
116-
</div>
117-
<button type="submit" class="btn btn-primary">Submit</button>
118-
</form>
119-
`
120-
)
121-
);
142+
},
143+
];

yarn.lock

+2-10
Original file line numberDiff line numberDiff line change
@@ -538,14 +538,13 @@ __metadata:
538538
"@types/bcryptjs": "npm:2.4.6"
539539
"@types/express": "npm:^4.17.21"
540540
"@types/lodash.set": "npm:4.3.9"
541-
"@types/validator": "npm:^13"
542541
bcryptjs: "npm:2.4.3"
542+
express-validator: "npm:^7.0.1"
543543
graphql: "npm:16.8.1"
544544
graphql-modules: "npm:3.0.0-alpha-20231106133212-0b04b56e"
545545
lodash.set: "npm:4.3.2"
546546
reflect-metadata: "npm:0.1.13"
547547
tslib: "npm:2.6.2"
548-
validator: "npm:^13.11.0"
549548
peerDependencies:
550549
"@accounts/server": ^0.32.0 || ^0.33.0
551550
graphql: ^16.0.0
@@ -8542,13 +8541,6 @@ __metadata:
85428541
languageName: node
85438542
linkType: hard
85448543

8545-
"@types/validator@npm:^13":
8546-
version: 13.11.6
8547-
resolution: "@types/validator@npm:13.11.6"
8548-
checksum: 3201902a8e5d4784d1c67f5a5a796d1500bae10fe5413ed75fdbdf5d6b5572952445f3482ffe64908531b20171d4c5cfe94934de3fd401781bb6cf9f95766b02
8549-
languageName: node
8550-
linkType: hard
8551-
85528544
"@types/webidl-conversions@npm:*":
85538545
version: 7.0.3
85548546
resolution: "@types/webidl-conversions@npm:7.0.3"
@@ -28921,7 +28913,7 @@ __metadata:
2892128913
languageName: node
2892228914
linkType: hard
2892328915

28924-
"validator@npm:^13.11.0, validator@npm:^13.9.0":
28916+
"validator@npm:^13.9.0":
2892528917
version: 13.11.0
2892628918
resolution: "validator@npm:13.11.0"
2892728919
checksum: 0107da3add5a4ebc6391dac103c55f6d8ed055bbcc29a4c9cbf89eacfc39ba102a5618c470bdc33c6487d30847771a892134a8c791f06ef0962dd4b7a60ae0f5

0 commit comments

Comments
 (0)