Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Local kratos email verification with mailslurper #1648

Merged
merged 11 commits into from
Dec 13, 2022
7 changes: 6 additions & 1 deletion packages/hash/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,12 @@ You'll be able to sign in to these users with the password `password`.

## Sending emails

By default, the API server uses `DummyEmailTransporter` which simulates email sending for local development and testing.
Mails in the HASH application is managed through either Kratos (for everything related to authentication) or through the HASH API Email Transport (for everything else).

These emails templates are located in the following locations:

- Kratos emails in [`./external-services/kratos/templates/`](./external-services/kratos//templates/)
- HASH emails in [`./api/src/email/index.ts`](./api/src/email/index.ts)

To use `AwsSesEmailTransporter` instead, set `export HASH_EMAIL_TRANSPORTER=aws_ses` in your terminal before running the app.
Note that you will need valid AWS credentials for this email transporter to work.
Expand Down
6 changes: 4 additions & 2 deletions packages/hash/api/src/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,10 @@ const setupAuth = (params: {
if (err.response && err.response.status === 403) {
/** @todo: figure out if this should be handled here, or in the next.js app (when implementing 2FA) */
}
logger.error(
`Could not fetch session, got error: [${err.response?.status}] ${err.response?.data}`,
logger.debug(
`Kratos response error: Could not fetch session, got: [${
err.response?.status
}] ${JSON.stringify(err.response?.data)}`,
);
return undefined;
});
Expand Down
56 changes: 0 additions & 56 deletions packages/hash/api/src/email/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,66 +5,10 @@
// @ts-nocheck
import dedent from "dedent";
import { URLSearchParams } from "url";
import { VerificationCode } from "../model";
import { EmailTransporter } from "./transporters";

const { FRONTEND_URL } = require("../lib/config");

export const sendLoginCodeToEmailAddress =
(emailTransporter: EmailTransporter) =>
async (params: {
verificationCode: VerificationCode;
emailAddress: string;
redirectPath?: string;
}): Promise<void> => {
const { verificationCode, emailAddress, redirectPath } = params;

const queryParams = new URLSearchParams({
verificationId: verificationCode.id,
verificationCode: verificationCode.code,
...(redirectPath ? { redirectPath } : {}),
}).toString();

const magicLink = `${FRONTEND_URL}/login?${queryParams}`;

await emailTransporter.sendMail({
to: emailAddress,
subject: "Your HASH verification code",
html: dedent`
<p>To log in, copy and paste your verification code or <a href="${magicLink}">click here</a>.</p>
<code>${verificationCode.code}</code>
`,
});
};

export const sendEmailVerificationCodeToEmailAddress =
(emailTransporter: EmailTransporter) =>
async (params: {
verificationCode: VerificationCode;
emailAddress: string;
magicLinkQueryParams?: string;
}): Promise<void> => {
const { verificationCode, emailAddress, magicLinkQueryParams } = params;

const queryParams = new URLSearchParams({
verificationId: verificationCode.id,
verificationCode: verificationCode.code,
}).toString();

const magicLink = `${FRONTEND_URL}/signup?${queryParams}${
magicLinkQueryParams || ""
}`;

await emailTransporter.sendMail({
to: emailAddress,
subject: "Please verify your HASH email address",
html: dedent`
<p>To verify your email address, copy and paste your verification code or <a href="${magicLink}">click here</a>.</p>
<code>${verificationCode.code}</code>
`,
});
};

export const sendOrgEmailInvitationToEmailAddress =
(emailTransporter: EmailTransporter) =>
async (params: {
Expand Down
44 changes: 0 additions & 44 deletions packages/hash/api/src/email/transporters/DummyEmailTransporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,6 @@ interface PlainEmailDump {

type DerivedPayload =
| { payloadType: "unknown" }
| {
payloadType: "loginVerification";
verificationCode: string;
magicLink: string;
}
| {
payloadType: "signupVerification";
verificationCode: string;
magicLink: string;
}
| {
payloadType: "orgInvitation";
orgName: string;
Expand Down Expand Up @@ -59,22 +49,6 @@ const yamlFileHeader = dedent`
const derivePayload = (plainEmailDump: PlainEmailDump): DerivedPayload => {
try {
const { subject, html } = plainEmailDump;
if (subject === "Your HASH verification code") {
return {
payloadType: "loginVerification",
verificationCode: html!.match(/<code>(.*)<\/code>/)![1]!,
magicLink: html!.match(/href="(.*)"/)![1]!,
};
}

if (subject === "Please verify your HASH email address") {
return {
payloadType: "signupVerification",
verificationCode: html!.match(/<code>(.*)<\/code>/)![1]!,
magicLink: html!.match(/href="(.*)"/)![1]!,
};
}

if (subject === "You've been invited to join an organization at HASH") {
const invitationLink = html!.match(/href="(.*)"/)![1]!;

Expand Down Expand Up @@ -181,24 +155,6 @@ export class DummyEmailTransporter implements EmailTransporter {
const rowsToDisplay: string[] = [`New email to ${emailDump.to}!`, ""];

switch (emailDump.derivedPayload.payloadType) {
case "loginVerification":
rowsToDisplay.push(
"Login link:",
emailDump.derivedPayload.magicLink,
"",
"Verification code:",
emailDump.derivedPayload.verificationCode,
);
break;
case "signupVerification":
rowsToDisplay.push(
"Signup link:",
emailDump.derivedPayload.magicLink,
"",
"Verification code:",
emailDump.derivedPayload.verificationCode,
);
break;
case "orgInvitation":
rowsToDisplay.push(
"Org invitation link:",
Expand Down
1 change: 1 addition & 0 deletions packages/hash/external-services/kratos/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ ENV API_SECRET=$API_SECRET
RUN mkdir -p /etc/config/kratos && \
mkdir -p /home/ory/.postgresql

COPY ./templates /etc/config/kratos/templates
COPY ./hooks /etc/config/kratos/hooks
COPY ./identity.schema.json /etc/config/kratos/
COPY kratos.$ENV.yml /etc/config/kratos/kratos.$ENV.yml
Expand Down
25 changes: 25 additions & 0 deletions packages/hash/external-services/kratos/kratos.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@ selfservice:
password:
enabled: true

link:
config:
# The URL for verification emails are set through the link method
# but we're using the code method, so we disable this method for usage.
enabled: false
# Set through SELFSERVICE_METHODS_LINK_CONFIG_BASE_URL
base_url: http://localhost:3000/api/ory
code:
config:
# and make sure to enable the code method.
enabled: true
Comment on lines +27 to +36
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, should we make an effort from here on to start copying in the documentation from the ory kratos website?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's very helpful for values that we know we will inject new values into such as this and the UI_URLs. Fortunately their env naming matches the path of the yaml but in upper case and with underscores. I think it's helpful to have the env var to be easily-copied, though


flows:
error:
ui_url: http://localhost:3000/error
Expand Down Expand Up @@ -59,6 +71,13 @@ selfservice:
in: header
- hook: session

verification:
use: code
lifespan: 48h
# Set though SELFSERVICE_FLOWS_VERIFICATION_UI_URL
ui_url: http://localhost:3000/verification
enabled: true

log:
level: debug
format: text
Expand All @@ -78,3 +97,9 @@ identity:
schemas:
- id: default
url: file:///etc/config/kratos/identity.schema.json

courier:
template_override_path: /etc/config/kratos/templates
smtp:
# Set through COURIER_SMTP_CONNECTION_URI
connection_uri: smtps://test:test@mailslurper:1025/?skip_ssl_verify=true
27 changes: 27 additions & 0 deletions packages/hash/external-services/kratos/kratos.prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,19 @@ selfservice:
password:
enabled: true

# Email sending diabled in production for now
# link:
# config:
# # The URL for verification emails are set through the link method
# # but we're using the code method, so we disable this method for usage.
# enabled: false
# # Set through SELFSERVICE_METHODS_LINK_CONFIG_BASE_URL
# base_url: http://localhost:3000/api/ory
# code:
# config:
# # and make sure to enable the code method.
# enabled: true

flows:
error:
ui_url: http://localhost:3000/error
Expand Down Expand Up @@ -59,6 +72,14 @@ selfservice:
in: header
- hook: session

# Email sending diabled in production for now
# verification:
# use: code
# lifespan: 48h
# # Set though SELFSERVICE_FLOWS_VERIFICATION_UI_URL
# ui_url: http://localhost:3000/verification
# enabled: true

log:
level: info
format: json
Expand All @@ -78,3 +99,9 @@ identity:
schemas:
- id: default
url: file:///etc/config/kratos/identity.schema.json
# Email sending diabled in production for now
# courier:
# template_override_path: /etc/config/kratos/templates
# smtp:
# # Set through COURIER_SMTP_CONNECTION_URI
# connection_uri: set-through-env-arg-COURIER_SMTP_CONNECTION_URI
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Your verification code is: {{ .Code }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Hi,

you (or someone else) entered this email address when trying to recover access a HASH account.

However, this email address is not on our database of registered users and therefore the attempt has failed.

If this was you, check if you signed up using a different address.

If this was not you, please ignore this email.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Hi,

you (or someone else) entered this email address when trying to recover access a HASH account.

However, this email address is not on our database of registered users and therefore the attempt has failed.

If this was you, check if you signed up using a different address.

If this was not you, please ignore this email.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
HASH account access attempted
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Hi,

please recover access to your HASH account by clicking the following link:

<a href="{{ .RecoveryURL }}">{{ .RecoveryURL }}</a>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Hi,

please recover access to your HASH account by clicking the following link:

{{ .RecoveryURL }}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Recover access to your HASH account
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Hi,

you (or someone else) entered this email address when trying to recover access a HASH account.

However, this email address is not on our database of registered users and therefore the attempt has failed.

If this was you, check if you signed up using a different address.

If this was not you, please ignore this email.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Hi,

you (or someone else) entered this email address when trying to recover access to a HASH account.

However, this email address is not on our database of registered users and therefore the attempt has failed.

If this was you, check if you signed up using a different address.

If this was not you, please ignore this email.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
HASH account access attempted
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Hi,

please recover access to your HASH account by entering the following code:

{{ .RecoveryCode }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Hi,

please recover access to your HASH account by entering the following code:

{{ .RecoveryCode }}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Recover access to your HASH account
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Hi,

someone asked to verify this email address, but we were unable to find a HASH account for this address.

If this was you, check if you signed up using a different address.

If this was not you, please ignore this email.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Hi,

someone asked to verify this email address, but we were unable to find a HASH account for this address.

If this was you, check if you signed up using a different address.

If this was not you, please ignore this email.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Someone tried to verify this email address
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Hi,

please verify your HASH account by entering the following code:<br />
{{ .VerificationCode }}<br />
or clicking the following link:<br />
<a href="{{ .VerificationURL }}" target="_blank">{{ .VerificationURL }}</a><br />

The link is valid for 48 hours.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Hi,

please verify your HASH account by entering the following code:

{{ .VerificationCode }}

or clicking the following link:

{{ .VerificationURL }}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Please verify your email address
Loading