Skip to content

Commit 0c67b99

Browse files
committed
Getting landing page for email verification working
1 parent 6fe845e commit 0c67b99

File tree

8 files changed

+64
-67
lines changed

8 files changed

+64
-67
lines changed

src/authentication/create-signed-token.ts

Lines changed: 0 additions & 7 deletions
This file was deleted.

src/authentication/login/magic-link.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
import {pipe} from 'fp-ts/lib/function';
22
import * as E from 'fp-ts/Either';
33
import * as t from 'io-ts';
4-
import {failure} from '../../types';
54
import {Strategy as CustomStrategy} from 'passport-custom';
6-
import jwt from 'jsonwebtoken';
75
import {Config} from '../../configuration';
86
import {Dependencies} from '../../dependencies';
97
import {User} from '../../types/user';
108
import {logPassThru} from '../../util';
119
import {Logger} from 'pino';
12-
import {createSignedToken} from '../create-signed-token';
10+
import {createSignedToken, verifyToken} from '../signed-token';
1311

1412
const createMagicLink = (conf: Config) => (user: User) =>
1513
pipe(
@@ -22,12 +20,6 @@ const MagicLinkQuery = t.strict({
2220
token: t.string,
2321
});
2422

25-
const verifyToken = (token: string, secret: Config['TOKEN_SECRET']) =>
26-
E.tryCatch(
27-
() => jwt.verify(token, secret),
28-
failure('Could not verify token')
29-
);
30-
3123
const decodeMagicLinkFromQuery =
3224
(logger: Logger, conf: Config) => (input: unknown) =>
3325
pipe(

src/authentication/signed-token.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import jwt from 'jsonwebtoken';
2+
import * as E from 'fp-ts/Either';
3+
import {Config} from '../configuration';
4+
import { failure } from '../types/failure';
5+
6+
export const createSignedToken =
7+
(conf: Config) =>
8+
(payload: object): string =>
9+
jwt.sign(payload, conf.TOKEN_SECRET, {expiresIn: '10m'});
10+
11+
export const verifyToken = (token: string, secret: Config['TOKEN_SECRET']) =>
12+
E.tryCatch(
13+
() => jwt.verify(token, secret),
14+
failure('Could not verify token')
15+
);

src/authentication/verify-email/email-verification-link.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import * as t from 'io-ts';
44
import {Config} from '../../configuration';
55
import {EmailAddressCodec} from '../../types';
66
import {Logger} from 'pino';
7-
import { createSignedToken } from '../create-signed-token';
7+
import { createSignedToken, verifyToken } from '../signed-token';
8+
import {Request} from 'express';
9+
import { logPassThru } from '../../util';
810

911
const VerifyEmailTokenPayload = t.strict({
1012
memberNumber: t.number,
@@ -13,24 +15,27 @@ const VerifyEmailTokenPayload = t.strict({
1315

1416
type VerifyEmailTokenPayload = t.TypeOf<typeof VerifyEmailTokenPayload>;
1517

16-
const createEmailVerificationLink =
18+
export const createEmailVerificationLink =
1719
(conf: Config) => (payload: VerifyEmailTokenPayload) =>
1820
pipe(
1921
VerifyEmailTokenPayload.encode(payload),
2022
createSignedToken(conf),
2123
token => `${conf.PUBLIC_URL}/auth/verify-email/landing?token=${token}`
2224
);
2325

24-
const decodeEmailVerificationFromQuery =
25-
(logger: Logger, conf: Config) => (input: unknown) =>
26+
const EmailVerificationQuery = t.strict({
27+
token: t.string,
28+
});
29+
30+
export const decodeEmailVerificationLink =
31+
(logger: Logger, conf: Config) => (req: Request) =>
32+
// We don't use passport because there is currently only 1 way to encode/decode an email verification link
33+
// so there is no value in adding further abstraction behind passport.
2634
pipe(
27-
input,
28-
decodeTokenFromQuery(logger, conf),
35+
req.query,
36+
logPassThru(logger, 'Attempting to decode email verification link from query'), // Logging is required as a basic form of auth enumeration detection.
37+
EmailVerificationQuery.decode,
38+
E.chainW(({token}) => verifyToken(token, conf.TOKEN_SECRET)),
2939
E.chainW(VerifyEmailTokenPayload.decode)
3040
);
3141

32-
export const emailVerificationLink = {
33-
create: createEmailVerificationLink,
34-
decodeFromQuery: decodeEmailVerificationFromQuery,
35-
};
36-

src/authentication/verify-email/invalid-link.ts

Lines changed: 0 additions & 19 deletions
This file was deleted.

src/authentication/verify-email/landing-page.ts

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {Request, Response} from 'express';
2-
import {isolatedPageTemplate} from '../../templates';
2+
import {isolatedPageTemplate, oopsPage} from '../../templates';
33
import {StatusCodes} from 'http-status-codes';
44
import {
55
html,
@@ -9,29 +9,44 @@ import {
99
} from '../../types/html';
1010
import { Dependencies } from '../../dependencies';
1111
import { Config } from '../../configuration';
12+
import { decodeEmailVerificationLink } from './email-verification-link';
13+
import { pipe } from 'fp-ts/lib/function';
14+
import * as E from 'fp-ts/Either';
15+
import { verifyEmail } from '../../commands/members/verify-email';
16+
17+
const invalidLink = () => oopsPage(
18+
html`The link you have used is (no longer) valid. Go back to your
19+
<a href=/me>homepage</a>.`
20+
);
1221

1322
export const landing =
1423
(deps: Dependencies, conf: Config) =>
1524
(req: Request, res: Response<CompleteHtmlDocument>) => {
16-
req.query
17-
25+
pipe(
26+
req,
27+
decodeEmailVerificationLink(deps.logger, conf),
28+
E.match(
29+
error => {
30+
deps.logger.error(
31+
{error},
32+
'Failed to decode verification link'
33+
);
34+
return invalidLink()
35+
},
36+
decoded => {
37+
verifyEmail.process({
38+
command: {
39+
40+
}
41+
})
42+
}
43+
)
44+
);
1845

19-
const index = req.originalUrl.indexOf('?');
20-
const suffix = index === -1 ? '' : req.originalUrl.slice(index);
21-
const url = '/auth/callback' + suffix;
22-
res.status(StatusCodes.OK).send(
23-
isolatedPageTemplate(sanitizeString('Redirecting...'))(html`
24-
<!doctype html>
25-
<html>
26-
<head>
27-
<meta http-equiv="refresh" content="0; url='${safe(url)}'" />
28-
</head>
29-
<body></body>
30-
</html>
31-
`)
32-
);
3346
};
3447

48+
// TODO - Combine these.
49+
3550
export const verifyEmailCallback =
3651
(deps: Dependencies, conf: Config, invalidLinkPath: string): RequestHandler =>
3752
(req, res) => {

src/authentication/verify-email/routes.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,14 @@ import {Config} from '../../configuration';
33
import {Safe, safe} from '../../types/html';
44
import {Route, get} from '../../types/route';
55
import {landing } from './landing-page';
6-
import { invalidLink } from './invalid-link';
76

87
export const logInPath: Safe = safe('/log-in');
9-
const invalidVerificationLinkPath = '/auth/invalid-email-verification-link';
108

119
export const routes = (
1210
deps: Dependencies,
1311
conf: Config
1412
): ReadonlyArray<Route> => {
1513
return [
1614
get('/auth/verify-email/landing', landing(deps, conf)),
17-
get(invalidVerificationLinkPath, invalidLink),
1815
];
1916
};

src/authentication/verify-email/send-email-verification.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ export const sendEmailVerification = (
5353
) => (memberNumber: number, emailAddress: EmailAddress): TE.TaskEither<Failure, string> => {
5454
const email = toEmail(emailAddress)(
5555
emailVerificationLink.create(conf)({
56-
purpose: 'verify-email',
5756
memberNumber,
5857
emailAddress,
5958
})

0 commit comments

Comments
 (0)