Skip to content

Commit 9271603

Browse files
authored
Merge pull request #19952 from mozilla/FXA-12883
feat(auth): Use libs email in auth server
2 parents 056213d + 2ed6906 commit 9271603

26 files changed

Lines changed: 1997 additions & 627 deletions

File tree

libs/accounts/email-renderer/src/renderer/email-helpers.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ export const splitEmails = (
4545
*/
4646
export const constructLocalTimeAndDateStrings = (
4747
timeZone?: string,
48-
acceptLanguage?: string
48+
acceptLanguage?: string,
49+
date?: Date | number
4950
): {
5051
acceptLanguage: string;
5152
date: string;
@@ -57,18 +58,18 @@ export const constructLocalTimeAndDateStrings = (
5758
const locale = determineLocale(acceptLanguage) || DEFAULT_LOCALE;
5859
moment.locale(locale);
5960

60-
let timeMoment = moment();
61+
let timeMoment = moment(date ? date : undefined);
6162
if (timeZone) {
6263
timeMoment = timeMoment.tz(timeZone);
6364
}
6465

65-
const time = timeMoment.format('LTS (z)');
66-
const date = timeMoment.format('dddd, ll');
66+
const formattedTime = timeMoment.format('LTS (z)');
67+
const formattedDate = timeMoment.format('dddd, ll');
6768

6869
return {
6970
acceptLanguage: locale,
70-
date,
71-
time,
71+
date: formattedDate,
72+
time: formattedTime,
7273
timeZone: timeZone || DEFAULT_TIMEZONE,
7374
};
7475
};

libs/accounts/email-renderer/src/renderer/email-link-builder.spec.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ describe('EmailLinkBuilder', () => {
3434
'https://survey.alchemer.com/s3/6534408/Privacy-Security-Product-Cancellation-of-Service-Q4-21',
3535
firefoxDesktopUrl:
3636
'https://firefox.com?utm_content=registration-confirmation&utm_medium=email&utm_source=fxa',
37+
unsubscribeUrl:
38+
'https://privacyportal.onetrust.com/webform/1350748f-7139-405c-8188-22740b3b5587/4ba08202-2ede-4934-a89e-f0b0870f95f0',
3739
};
3840

3941
let linkBuilder: EmailLinkBuilder;
@@ -319,6 +321,13 @@ describe('EmailLinkBuilder', () => {
319321
const attrs = linkBuilder.buildCadLink('postVerify', false);
320322
expect(attrs).toEqual('http://localhost:30303/connect_another_device');
321323
});
324+
325+
it('can build one click link', () => {
326+
const attrs = linkBuilder.buildCadLink('postVerify', true, true);
327+
expect(attrs).toEqual(
328+
'http://localhost:30303/connect_another_device?utm_medium=email&utm_campaign=fx-account-verified&utm_content=fx-account-verified-one-click&one_click=true'
329+
);
330+
});
322331
});
323332

324333
describe('buildVerifyLoginLink', () => {
@@ -386,4 +395,49 @@ describe('EmailLinkBuilder', () => {
386395
expect(link).toEqual(mockConfig.firefoxDesktopUrl);
387396
});
388397
});
398+
399+
describe('buildVerifyEmailLink', () => {
400+
it('can build', () => {
401+
const link = linkBuilder.buildVerifyEmailLink('verifyEmail', true, {
402+
code: 'abcd',
403+
uid: '123',
404+
redirectTo: 'http://mozilla.org',
405+
reminder: 'first',
406+
resume: 'xyz',
407+
service: 'sync',
408+
});
409+
expect(link).toEqual(
410+
'http://test.localhost:30303/verify_email?utm_medium=email&utm_campaign=fx-welcome&utm_content=fx-welcome&code=abcd&uid=123&redirectTo=http%3A%2F%2Fmozilla.org&reminder=first&resume=xyz&service=sync'
411+
);
412+
});
413+
it(' can build without utm', () => {
414+
const link = linkBuilder.buildVerifyEmailLink('verifyEmail', false, {
415+
code: 'abcd',
416+
uid: '123',
417+
});
418+
expect(link).toEqual(
419+
'http://test.localhost:30303/verify_email?code=abcd&uid=123'
420+
);
421+
});
422+
});
423+
describe('buildReportSigninLink', () => {
424+
it('can build', () => {
425+
const link = linkBuilder.buildReportSignInLink('verifyEmail', true, {
426+
uid: '123',
427+
unblockCode: 'abcd',
428+
});
429+
expect(link).toEqual(
430+
'http://localhost:30303/report_signin?utm_medium=email&utm_campaign=fx-welcome&utm_content=fx-report&uid=123&unblockCode=abcd'
431+
);
432+
});
433+
it('can build without utm', () => {
434+
const link = linkBuilder.buildReportSignInLink('verifyEmail', false, {
435+
uid: '123',
436+
unblockCode: 'abcd',
437+
});
438+
expect(link).toEqual(
439+
'http://localhost:30303/report_signin?uid=123&unblockCode=abcd'
440+
);
441+
});
442+
});
389443
});

libs/accounts/email-renderer/src/renderer/email-link-builder.ts

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,12 +145,14 @@ export class EmailLinkBuilder {
145145
* @param templateName - Email template name (used to lookup campaign)
146146
* @param metricsEnabled - Inidicates if metrics/tracking is enabled for the user
147147
* @param content - Optional content override (defaults to template's content map value)
148+
* @param oneClickLink - Indicates if this should be a one-click link, adding a suffix to the utm_content value
148149
*/
149150
private addUTMParams(
150151
url: URL,
151152
templateName: string,
152153
metricsEnabled: boolean,
153-
content?: string
154+
content?: string,
155+
oneClickLink = false
154156
): void {
155157
// Don't include utm parameters if metrics are disabled. This flag
156158
// comes from the users's account state and must be supplied.
@@ -168,7 +170,13 @@ export class EmailLinkBuilder {
168170
}
169171

170172
const contentValue = content || this.getContent(templateName);
171-
if (contentValue) {
173+
if (contentValue && oneClickLink) {
174+
url.searchParams.set(
175+
'utm_content',
176+
UTM_PREFIX + contentValue + '-one-click'
177+
);
178+
url.searchParams.set('one_click', 'true');
179+
} else if (contentValue) {
172180
url.searchParams.set('utm_content', UTM_PREFIX + contentValue);
173181
}
174182

@@ -378,9 +386,19 @@ export class EmailLinkBuilder {
378386
return url.toString();
379387
}
380388

381-
buildCadLink(templateName: string, metricsEnabled: boolean) {
389+
buildCadLink(
390+
templateName: string,
391+
metricsEnabled: boolean,
392+
oneClickLink = false
393+
) {
382394
const url = new URL(`${this.baseUri}/connect_another_device`);
383-
this.addUTMParams(url, templateName, metricsEnabled);
395+
this.addUTMParams(
396+
url,
397+
templateName,
398+
metricsEnabled,
399+
undefined,
400+
oneClickLink
401+
);
384402
return url.toString();
385403
}
386404

@@ -424,6 +442,41 @@ export class EmailLinkBuilder {
424442
buildDesktopLink() {
425443
return this.config.firefoxDesktopUrl;
426444
}
445+
446+
buildVerifyEmailLink(
447+
templateName: string,
448+
metricsEnabled: boolean,
449+
query: {
450+
code: string;
451+
uid: string;
452+
resume?: string;
453+
redirectTo?: string;
454+
service?: string;
455+
reminder?: 'first' | 'second' | 'final';
456+
}
457+
): string {
458+
const url = new URL(`${this.baseUri}/verify_email`);
459+
if (this.config.prependVerificationSubdomain.enabled) {
460+
url.host = `${this.config.prependVerificationSubdomain.subdomain}.${url.host}`;
461+
}
462+
this.addUTMParams(url, templateName, metricsEnabled);
463+
this.addQueryParams(url, query);
464+
return url.toString();
465+
}
466+
467+
buildReportSignInLink(
468+
templateName: string,
469+
metricsEnabled: boolean,
470+
query: {
471+
uid: string;
472+
unblockCode: string;
473+
}
474+
): string {
475+
const url = new URL(`${this.baseUri}/report_signin`);
476+
this.addUTMParams(url, templateName, metricsEnabled, 'report');
477+
this.addQueryParams(url, query);
478+
return url.toString();
479+
}
427480
}
428481

429482
// PORTED FROM fxa-shared/subscriptions/configuration/utils.ts

packages/fxa-auth-server/lib/inactive-accounts/index.ts

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ import {
3535
getAccountCustomerByUid,
3636
} from 'fxa-shared/db/models/auth';
3737
import { BOUNCE_TYPES } from 'fxa-shared/db/models/auth/email-bounce';
38+
import { FxaMailer } from '../senders/fxa-mailer';
39+
import { FxaMailerFormat } from '../senders/fxa-mailer-format';
3840

3941
const aDayInMs = 24 * 60 * 60 * 1000;
4042
const sixtyDaysInMs = 60 * aDayInMs;
@@ -55,6 +57,17 @@ export const requestForGlean = {
5557
isMetricsEnabled: () => true,
5658
metricsContext: () => ({}),
5759
ua: {},
60+
geo: {
61+
timeZone: 'UTC',
62+
location: {
63+
city: '',
64+
state: '',
65+
stateCode: '',
66+
country: '',
67+
countryCode: '',
68+
},
69+
},
70+
acceptLanguage: 'en',
5871
},
5972
auth: {},
6073
headers: {
@@ -148,6 +161,7 @@ export class InactiveAccountsManager {
148161
statsd: StatsD;
149162
glean: GleanMetricsType;
150163
log: Logger;
164+
private fxaMailer: FxaMailer;
151165

152166
constructor({
153167
fxaDb,
@@ -175,6 +189,8 @@ export class InactiveAccountsManager {
175189
this.statsd = statsd;
176190
this.glean = glean;
177191
this.log = log;
192+
193+
this.fxaMailer = Container.get(FxaMailer);
178194
}
179195

180196
async isActive(uid: string, activeByDateTimestamp?: number) {
@@ -281,11 +297,31 @@ export class InactiveAccountsManager {
281297
acceptLanguage: account.locale,
282298
inactiveDeletionEta: now + emailTypeSpecificVals.timeToDeletion,
283299
};
284-
await this.mailer[emailTypeSpecificVals.emailSender](
285-
account.emails,
286-
account,
287-
message
288-
);
300+
301+
if (this.fxaMailer.canSend(emailTypeSpecificVals.emailTemplate)) {
302+
await this.fxaMailer[emailTypeSpecificVals.emailSender]({
303+
...FxaMailerFormat.account(account),
304+
...(await FxaMailerFormat.metricsContext(requestForGlean)),
305+
...FxaMailerFormat.localTime(requestForGlean),
306+
...FxaMailerFormat.location(requestForGlean),
307+
...FxaMailerFormat.device(requestForGlean),
308+
...FxaMailerFormat.sync(false),
309+
// use the formatter to convert inactiveDeletionEta to localized strings
310+
deletionDate: FxaMailerFormat.localTime(
311+
requestForGlean,
312+
message.inactiveDeletionEta
313+
).date,
314+
// override acceptLanguage to ensure email is localized as best we can
315+
acceptLanguage: account.locale,
316+
});
317+
} else {
318+
await this.mailer[emailTypeSpecificVals.emailSender](
319+
account.emails,
320+
account,
321+
message
322+
);
323+
}
324+
289325
this.statsd.increment(
290326
`account.inactive.${emailTypeSpecificVals.attempt}-email.sent`
291327
);

packages/fxa-auth-server/lib/routes/account.ts

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -403,28 +403,45 @@ export class AccountHandler {
403403
break;
404404
}
405405
default: {
406-
await this.mailer.sendVerifyEmail([], account, {
407-
code: account.emailCode,
408-
service: form.service || query.service,
409-
redirectTo: form.redirectTo,
410-
resume: form.resume,
411-
acceptLanguage: locale,
412-
deviceId,
413-
flowId,
414-
flowBeginTime,
415-
productId,
416-
planId,
417-
ip,
418-
location: request.app.geo.location,
419-
timeZone: request.app.geo.timeZone,
420-
style,
421-
uaBrowser: sessionToken.uaBrowser,
422-
uaBrowserVersion: sessionToken.uaBrowserVersion,
423-
uaOS: sessionToken.uaOS,
424-
uaOSVersion: sessionToken.uaOSVersion,
425-
uaDeviceType: sessionToken.uaDeviceType,
426-
uid: sessionToken.uid,
427-
});
406+
if (this.fxaMailer.canSend('verify')) {
407+
await this.fxaMailer.sendVerifyEmail({
408+
...FxaMailerFormat.account(account),
409+
...(await FxaMailerFormat.metricsContext(request)),
410+
...FxaMailerFormat.sync(form.service || query.service),
411+
...FxaMailerFormat.localTime(request),
412+
...FxaMailerFormat.location(request),
413+
...FxaMailerFormat.device(request),
414+
code: account.emailCode,
415+
resume: form.resume,
416+
redirectTo: form.redirectTo,
417+
service: form.service || query.service,
418+
});
419+
} else {
420+
console.debug('falling back')
421+
const sent = await this.mailer.sendVerifyEmail([], account, {
422+
code: account.emailCode,
423+
service: form.service || query.service,
424+
redirectTo: form.redirectTo,
425+
resume: form.resume,
426+
acceptLanguage: locale,
427+
deviceId,
428+
flowId,
429+
flowBeginTime,
430+
productId,
431+
planId,
432+
ip,
433+
location: request.app.geo.location,
434+
timeZone: request.app.geo.timeZone,
435+
style,
436+
uaBrowser: sessionToken.uaBrowser,
437+
uaBrowserVersion: sessionToken.uaBrowserVersion,
438+
uaOS: sessionToken.uaOS,
439+
uaOSVersion: sessionToken.uaOSVersion,
440+
uaDeviceType: sessionToken.uaDeviceType,
441+
uid: sessionToken.uid,
442+
});
443+
console.debug('falling back sent!', sent);
444+
}
428445
}
429446
}
430447

0 commit comments

Comments
 (0)