Skip to content

Commit

Permalink
Merge branch 'main' into MPP-3442-new-email-template
Browse files Browse the repository at this point in the history
  • Loading branch information
rafeerahman authored Oct 16, 2023
2 parents 08b39aa + 7085e06 commit 2df038e
Show file tree
Hide file tree
Showing 36 changed files with 2,372 additions and 2,142 deletions.
6 changes: 3 additions & 3 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ commands:
executors:
python:
docker:
- image: cimg/python:3.10.11
- image: cimg/python:3.10.13
auth:
username: $DOCKER_USER
password: $DOCKER_PASS
Expand Down Expand Up @@ -270,7 +270,7 @@ jobs:

upload_coverage:
docker:
- image: cimg/python:3.10.11-node
- image: cimg/python:3.10.13-node
steps:
- attach_workspace:
at: /tmp/workspace
Expand Down Expand Up @@ -390,7 +390,7 @@ jobs:

python_test_postgres:
docker:
- image: cimg/python:3.10.11
- image: cimg/python:3.10.13
auth:
username: $DOCKER_USER
password: $DOCKER_PASS
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/lint-pending-strings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
with:
submodules: 'recursive'
- name: Set up Python 3
uses: actions/[email protected].0
uses: actions/[email protected].1
with:
python-version: '3.10'
cache: 'pip'
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM python:3.10.11
FROM python:3.10.13

ARG CIRCLE_BRANCH
ARG CIRCLE_SHA1
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@
<img src="https://raw.githubusercontent.com/mozilla/fx-private-relay/11ad17e197e23a0453bfb74fa3670c87cfc35e36/frontend/src/components/landing/images/logo-firefox-relay.svg" width="250" />
</p>


# Private Relay
# Private Relay

<!-- Badges include: license, size of repository, overall coverage for project via coveralls.io on main branch, status of what is deployed via whatsdeployed.io and our circleci status for main branch. -->

[![License: MPL 2.0](https://img.shields.io/badge/License-MPL%202.0-brightgreen.svg)](https://raw.githubusercontent.com/mozilla/fx-private-relay/main/LICENSE)
![Repo Size](https://img.shields.io/github/repo-size/Mozilla/fx-private-relay)
[![Coverage Status](https://coveralls.io/repos/github/mozilla/fx-private-relay/badge.svg?branch=main)](https://coveralls.io/github/mozilla/fx-private-relay?branch=main)
[![What's Deployed](https://img.shields.io/badge/whatsdeployed-dev,stage,prod-green.svg)](https://whatsdeployed.io/s/60j/mozilla/fx-private-relay)
[![CircleCI](https://dl.circleci.com/status-badge/img/gh/mozilla/fx-private-relay/tree/main.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/mozilla/fx-private-relay/tree/main)
[![Relay e2e Tests](https://github.com/mozilla/fx-private-relay/actions/workflows/playwright.yml/badge.svg?branch=main)](https://github.com/mozilla/fx-private-relay/actions/workflows/playwright.yml)

Private Relay provides generated email addresses to use in place of personal
email addresses.
Expand Down Expand Up @@ -39,6 +39,7 @@ them](https://www.facebook.com/business/help/606443329504150?helpref=faq_content
- [Production Environments](#production-environments)
- [Requirements](#requirements-1)
- [Environment Variables](#environment-variables)

## Development

Please refer to our [coding standards](docs/coding-standards.md) for code styles, naming conventions and other methodologies.
Expand Down
5 changes: 5 additions & 0 deletions api/tests/authentication_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@
MOCK_BASE = "api.authentication"


# TODO MPP-3527 - Many tests mock FxA responses. This one should specify that it is
# mocking the introspection URL. It could also be refactored to a pytest fixture, or a
# nullable.


def _setup_fxa_response(status_code: int, json: dict | str):
responses.add(
responses.POST,
Expand Down
16 changes: 16 additions & 0 deletions api/tests/views_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,22 @@ def test_201_new_user_created_and_202_user_exists(
assert responses.assert_call_count(self.fxa_verify_path, 2) is True
assert responses.assert_call_count(FXA_PROFILE_URL, 1) is True

@responses.activate()
def test_failed_profile_fetch_for_new_user_returns_500(self):
user_token = "user-123"
self._setup_client(user_token)
now_time = int(datetime.now().timestamp())
exp_time = (now_time + 60 * 60) * 1000
_setup_fxa_response(200, {"active": True, "sub": self.uid, "exp": exp_time})
# FxA profile server is down
responses.add(responses.GET, FXA_PROFILE_URL, status=502, body="")
response = self.client.post(self.path)

assert response.status_code == 500
assert response.json()["detail"] == (
"Did not receive a 200 response for account profile."
)

def test_no_authorization_header_returns_400(self):
client = APIClient()
response = client.post(self.path)
Expand Down
25 changes: 23 additions & 2 deletions api/views/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import json
"""
API views for emails and accounts
TODO: Move these functions to mirror the Django apps
Email stuff should be in api/views/emails.py
Runtime data should be in api/views/privaterelay.py
Profile stuff is strange - model is in emails, but probably should be in privaterelay.
"""

import logging
from django.core.exceptions import ObjectDoesNotExist
from django.template.loader import render_to_string
Expand Down Expand Up @@ -229,14 +238,26 @@ def terms_accepted_user(request):
fxa_profile_resp = requests.get(
FXA_PROFILE_URL, headers={"Authorization": f"Bearer {token}"}
)
if not (fxa_profile_resp.ok and fxa_profile_resp.content):
logger.error(
"terms_accepted_user: bad account profile response",
extra={
"status_code": fxa_profile_resp.status_code,
"content": fxa_profile_resp.content,
},
)
return response.Response(
data={"detail": "Did not receive a 200 response for account profile."},
status=500,
)

# this is not exactly the request object that FirefoxAccountsProvider expects, but
# it has all of the necssary attributes to initiatlize the Provider
provider = FirefoxAccountsProvider(request)
# This may not save the new user that was created
# https://github.com/pennersr/django-allauth/blob/77368a84903d32283f07a260819893ec15df78fb/allauth/socialaccount/providers/base/provider.py#L44
social_login = provider.sociallogin_from_response(
request, json.loads(fxa_profile_resp.content)
request, fxa_profile_resp.json()
)
# Complete social login is called by callback
# (see https://github.com/pennersr/django-allauth/blob/77368a84903d32283f07a260819893ec15df78fb/allauth/socialaccount/providers/oauth/views.py#L118)
Expand Down
2 changes: 1 addition & 1 deletion docs/adr/0001-optional-tls-for-incoming-smtp.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

- Status: Accepted
- Deciders: Luke Crouch, Tony Cinotto
- Date: 2022-04-24
- Date: 2023-04-24

Technical Story: [MPP-2663](https://mozilla-hub.atlassian.net/browse/MPP-2847)

Expand Down
23 changes: 15 additions & 8 deletions docs/adr/0002-use-mask-as-from-address.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Use the Relay mask address as From: address for forwarded emails

- Status: Proposed
- Status: Accepted
- Deciders: Luke Crouch, Doris Deng
- Date: 2023-08-11

Expand Down Expand Up @@ -43,9 +43,16 @@ forwarded emails.

## Decision Outcome

Proceeding with testing and deployment of option 3, use the Relay mask as the
`From:` address. Testing will focus on delivery issues, and quantifying the
risk of categorization as spam.
We proceeded with option 3, use the Relay mask as the `From:` address.

The change was tested by staff in July and August 2023. Testing focused on delivery
issues and quantifying the risk of categorization as spam. The change exposed some
configuration issues in the staging environment, but worked well in production.

The change was launched to all users in September 2023. There were no negative reports
from users. There was a minor uptick in spam reports, up to an additional 1 per 1000
emails. There were some new unhandled processing exceptions, requiring new code to catch
the exceptions and log the values that caused them.

### Positive Consequences

Expand Down Expand Up @@ -77,7 +84,7 @@ Relay forwards with:

```
Subject: A special offer for you
From: "[email protected] [via Relay]" [email protected]
From: "[email protected] [via Relay]" <[email protected]>
To: [email protected]
Reply-To: [email protected]
```
Expand All @@ -101,7 +108,7 @@ Relay forwards with:

```
Subject: A special offer for you
From: "[email protected] [via Relay]" [email protected]
From: "[email protected] [via Relay]" <[email protected]>
To: [email protected]
Reply-To: [email protected]
Resent-From: [email protected]
Expand Down Expand Up @@ -132,7 +139,7 @@ Relay forwards with:

```
Subject: A special offer for you
From: "[email protected] [via Relay]" [email protected]
From: "[email protected] [via Relay]" <[email protected]>
To: [email protected]
Reply-To: [email protected]
Resent-From: [email protected]
Expand Down Expand Up @@ -164,7 +171,7 @@ Relay forwards with:

```
Subject: A special offer for you
From: "[email protected] [via Relay]" [email protected]
From: "[email protected] [via Relay]" <[email protected]>
To: [email protected]
Reply-To: [email protected]
Resent-From: [email protected]
Expand Down
4 changes: 3 additions & 1 deletion e2e-tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,6 @@ E2E_TEST_ACCOUNT_PASSWORD=<arbitrary password>

```
npm run test:e2e
```
```

[![Relay e2e Tests](https://github.com/mozilla/fx-private-relay/actions/workflows/playwright.yml/badge.svg)](https://github.com/mozilla/fx-private-relay/actions/workflows/playwright.yml)
24 changes: 12 additions & 12 deletions e2e-tests/e2eTestUtils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ export const getVerificationCode = async (testEmail: string, page: Page, attempt
throw new Error('Unable to retrieve restmail data');
}

const context = await request.newContext();
const context = await request.newContext();
const res = await context.get(
`http://restmail.net/mail/${testEmail}`,
{
failOnStatusCode: false
}
);
const resJson = await res.json();
const resJson = await res.json();
if (resJson.length) {
const verificationCode = resJson[0].headers['x-verify-short-code']
return verificationCode;
Expand All @@ -42,16 +42,16 @@ export const deleteEmailAddressMessages = async (req: APIRequestContext, testEma
}
};

const setYourPassword = async (page: Page) => {
const setYourPassword = async (page: Page) => {
await page.locator('#password').fill(process.env.E2E_TEST_ACCOUNT_PASSWORD as string)
await page.locator('#vpassword').fill(process.env.E2E_TEST_ACCOUNT_PASSWORD as string)
await page.locator('#age').fill('31');
await page.locator('button:has-text("Create account")').click({force: true})
await page.waitForTimeout(500)
await checkAuthState(page)
await checkAuthState(page)
}

const enterConfirmationCode = async (page: Page) => {
const enterConfirmationCode = async (page: Page) => {
const maybeVerificationCodeInput = 'div.card input'
await page.waitForSelector(maybeVerificationCodeInput, { timeout: 2000 })
const confirmButton = page.locator('#submit-btn')
Expand Down Expand Up @@ -89,16 +89,16 @@ const enterYourPassword = async (page: Page) => {
await checkAuthState(page)
}

export const generateRandomEmail = async () => {
export const generateRandomEmail = async () => {
return `${Date.now()}[email protected]`;
};

export const setEnvVariables = async (email: string) => {
export const setEnvVariables = async (email: string) => {
// set env variables
// stage will currently be the default
process.env['E2E_TEST_ENV'] = process.env.E2E_TEST_ENV || 'stage';
// stage will currently be the default
process.env['E2E_TEST_ENV'] = process.env.E2E_TEST_ENV as string ?? 'stage';
process.env['E2E_TEST_ACCOUNT_FREE'] = email;
process.env['E2E_TEST_BASE_URL'] = ENV_URLS[process.env.E2E_TEST_ENV as string] || 'https://stage.fxprivaterelay.nonprod.cloudops.mozgcp.net'
process.env['E2E_TEST_BASE_URL'] = ENV_URLS[process.env.E2E_TEST_ENV as string] ?? ENV_URLS.stage
}

interface DefaultScreenshotOpts {
Expand All @@ -113,7 +113,7 @@ export const defaultScreenshotOpts: Partial<DefaultScreenshotOpts> = {

export const checkAuthState = async (page: Page) => {
try {
const authStateTitleString = await page.locator('h1').textContent({ timeout: 4000 })
const authStateTitleString = await page.locator('h1').first()?.textContent({ timeout: 4000 })
const checkIfTitleContains = (potentialTitle: string) => {
return authStateTitleString?.includes(potentialTitle)
}
Expand All @@ -138,4 +138,4 @@ export const checkAuthState = async (page: Page) => {
break;
}
} catch {}
}
}
2 changes: 1 addition & 1 deletion e2e-tests/global-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ async function globalSetup() {
// get verification code from restmail
const verificationCode = await getVerificationCode(randomEmail, page)
await authPage.enterVerificationCode(verificationCode)

await page.context().storageState({ path: 'state.json' });
await browser.close();
}
Expand Down
40 changes: 19 additions & 21 deletions e2e-tests/pages/dashboardPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ export class DashboardPage {
readonly emailsForwardedAmount: Locator
readonly emailsBlockedAmount: Locator
readonly emailMasksUsedAmount: Locator
readonly maskCard: string
readonly maskCard: Locator
readonly maskCardString: string
readonly maskCardExpanded: Locator
readonly maskCardExpandButton: Locator
readonly maskCardHeader: Locator
Expand Down Expand Up @@ -77,7 +78,8 @@ export class DashboardPage {
this.dashBoardWithoutMasksEmail = page.locator('//section[starts-with(@class, "profile_no-premium-header")]')

// mask card elements
this.maskCard = '//div[starts-with(@class, "MaskCard_card")]'
this.maskCard = page.getByRole('button', { name: 'Generate new mask' })
this.maskCardString = '//div[starts-with(@class, "MaskCard_card")]'
this.maskCardExpanded = page.locator('//button[starts-with(@class, "MaskCard_expand")]')
this.maskCardExpandButton = page.locator('//button[starts-with(@class, "MaskCard_expand")]')
this.maskCardHeader = page.locator('//div[starts-with(@class, "MaskCard_summary")]')
Expand Down Expand Up @@ -108,7 +110,7 @@ export class DashboardPage {

// generate a new mask and confirm
await this.generateNewMaskButton.click()
await this.page.waitForSelector(this.maskCard, { timeout: 3000 })
await this.page.waitForSelector(this.maskCardString, { timeout: 3000 })

// randomize between 1.5-2.5 secs between each generate to deal with issue of multiple quick clicks
await this.page.waitForTimeout((Math.random() * 2500) + 1500)
Expand Down Expand Up @@ -142,7 +144,7 @@ export class DashboardPage {
let isExpanded = false

try {
numberOfMasks = await this.page.locator(this.maskCard).count()
numberOfMasks = await this.page.locator(this.maskCardString).count()
} catch(err){}

// check number of masks available
Expand All @@ -153,7 +155,7 @@ export class DashboardPage {
// if clear all, check if there's an expanded mask card
if(clearAll){
try {
await this.page.waitForSelector(this.maskCard, { timeout: 3000 })
await this.page.waitForSelector(this.maskCardString, { timeout: 3000 })
} catch (error) {
console.error('There are no masks to delete')
return
Expand Down Expand Up @@ -196,26 +198,22 @@ export class DashboardPage {

// TODO: Replace with a page under control of Relay team
await this.page.goto("https://monitor.firefox.com/", { waitUntil: 'networkidle' })
await this.page.locator('#scan-email-address').fill(generatedMaskEmail as string)
await this.page.locator('button.primary').click()
await this.page.waitForURL('**/scan**')

const monitorEmailInput = this.page.locator('#scan-email-address')
const submitButton = this.page.locator('button.primary')
await monitorEmailInput.fill(generatedMaskEmail as string)
await submitButton.click()
await this.page.getByRole('link', {name: 'Get alerts about new breaches'}).click()

const signupButton = this.page.locator('button.primary')
await signupButton.click()
await this.page.locator('input[name=email]').fill(generatedMaskEmail as string)
await this.page.locator('#submit-btn').click()

const passwordInputField = this.page.locator('#password');
const passwordConfirmInputField = this.page.locator('#vpassword');
const ageInputField = this.page.locator('#age');
const createAccountButton = this.page.locator('#submit-btn');
await this.page.locator('#password').fill(process.env.E2E_TEST_ACCOUNT_PASSWORD as string);
await this.page.locator('#vpassword').fill(process.env.E2E_TEST_ACCOUNT_PASSWORD as string);
await this.page.locator('#age').fill('31');
await this.page.locator('#submit-btn').click()
await this.page.waitForURL('**/confirm_signup_code**')

await passwordInputField.fill(process.env.E2E_TEST_ACCOUNT_PASSWORD as string);
await passwordConfirmInputField.fill(process.env.E2E_TEST_ACCOUNT_PASSWORD as string);
await ageInputField.fill('31');
await createAccountButton.click()

// wait for email to be forward to restmail
// verification email from fxa to generatedMaskEmail should be forwarded to E2E_TEST_ACCOUNT_FREE
await getVerificationCode(process.env.E2E_TEST_ACCOUNT_FREE as string, this.page)
}

Expand Down
Loading

0 comments on commit 2df038e

Please sign in to comment.