AbdulElahGwaith commited on
Commit
cc63287
·
verified ·
1 Parent(s): e7e032e

Upload folder using huggingface_hub

Browse files
.docker/postgres/init-db.sql ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ CREATE USER blurts WITH ENCRYPTED PASSWORD 'blurts';
2
+ CREATE DATABASE blurts WITH OWNER 'blurts';
3
+ CREATE DATABASE "test-blurts" WITH OWNER 'blurts';
4
+
5
+ ALTER DEFAULT privileges IN SCHEMA public GRANT ALL ON tables to blurts;
.docker/pubsub/setup_pubsub.sh ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # Ensure the emulator host is set
4
+ PUBSUB_HOST=$1
5
+ PUBSUB_PORT=$2
6
+ PROJECT_ID=$3
7
+
8
+ PUBSUB_EMULATOR_HOST="${PUBSUB_HOST}:${PUBSUB_PORT}"
9
+
10
+ # Start PubSub emulator in the background
11
+ gcloud beta emulators pubsub start --host-port=0.0.0.0:${PUBSUB_PORT} --project=${PROJECT_ID} &
12
+ echo "Waiting for Pub/Sub emulator to be ready..."
13
+ until curl -s "http://${PUBSUB_EMULATOR_HOST}/v1/projects/${PROJECT_ID}/schemas" > /dev/null; do
14
+ echo "Pub/Sub emulator not ready yet..."
15
+ sleep 2
16
+ done
17
+ echo "Pub/Sub emulator is ready!"
18
+ echo "Initializing Pub/Sub emulator..."
19
+
20
+ create_topic_and_subscription () {
21
+ if [ $# -ne 2 ]; then
22
+ echo "create_topic_and_subscription takes 2 arguments"
23
+ exit 1
24
+ fi
25
+ if [ -z "$1" ]; then
26
+ echo "create_topic_and_subscription requires non-empty topic"
27
+ exit 1
28
+ fi
29
+ if [ -z "$2" ]; then
30
+ echo "create_topic_and_subscription requires non-empty subscription"
31
+ exit 1
32
+ fi
33
+
34
+ topic=$1
35
+ subscription=$2
36
+ echo "Creating topic '$topic' with subscription '$subscription'..."
37
+
38
+ # Create the topic using REST API instead of gcloud
39
+ curl -s -X PUT "${PUBSUB_EMULATOR_HOST}/v1/projects/${PROJECT_ID}/topics/${topic}"
40
+
41
+ # Create the subscription using REST API instead of gcloud
42
+ curl -s -X PUT "${PUBSUB_EMULATOR_HOST}/v1/projects/${PROJECT_ID}/subscriptions/${subscription}" \
43
+ -H "Content-Type: application/json" \
44
+ -d "{\"topic\": \"projects/${PROJECT_ID}/topics/${topic}\"}"
45
+ }
46
+
47
+ create_topic_and_subscription hibp-breaches hibp-cron
48
+
49
+ echo "Pub/Sub initialization completed."
50
+ touch /tmp/startup.done
51
+
52
+ # Keep emulator running
53
+ wait
.github/dependabot.yml ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # See the documentation for all configuration options:
2
+ # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
3
+
4
+ version: 2
5
+ updates:
6
+ - package-ecosystem: "github-actions"
7
+ directory: "/"
8
+ schedule:
9
+ interval: "weekly"
10
+ cooldown:
11
+ default-days: 8
12
+ - package-ecosystem: "npm"
13
+ directory: "/"
14
+ schedule:
15
+ interval: "weekly"
16
+ cooldown:
17
+ default-days: 8
18
+ open-pull-requests-limit: 10
19
+ groups:
20
+ eslint:
21
+ patterns:
22
+ - "@typescript-eslint/*"
23
+ - "eslint"
24
+ - "eslint-*"
25
+ exclude-patterns:
26
+ - "eslint-config-next"
27
+ - "eslint-plugin-storybook"
28
+ jest:
29
+ patterns:
30
+ - "babel-jest"
31
+ - "jest"
32
+ - "jest-environment-jsdom"
33
+ testing-library:
34
+ patterns:
35
+ - "@testing-library/dom"
36
+ - "@testing-library/react"
37
+ - "@testing-library/user-event"
38
+ react:
39
+ patterns:
40
+ - "react"
41
+ - "react-dom"
42
+ - "react-test-renderer"
43
+ sentry:
44
+ patterns:
45
+ - "@sentry/*"
46
+ aws-sdk:
47
+ patterns:
48
+ - "@aws-sdk/*"
49
+ storybook:
50
+ patterns:
51
+ - "storybook"
52
+ - "@storybook/*"
53
+ - "eslint-plugin-storybook"
54
+ exclude-patterns:
55
+ - "eslint-config-next"
56
+ fluent:
57
+ patterns:
58
+ - "@fluent/*"
59
+ nextjs:
60
+ patterns:
61
+ - "eslint-config-next"
62
+ - "next"
63
+ - "@next/*"
64
+ react-aria:
65
+ patterns:
66
+ - "react-aria"
67
+ - "react-stately"
68
+ stylelint:
69
+ patterns:
70
+ - "stylelint"
71
+ - "stylelint-scss"
72
+ - "stylelint-config-recommended-scss"
73
+ - package-ecosystem: "docker"
74
+ directory: "/"
75
+ schedule:
76
+ interval: "weekly"
77
+ cooldown:
78
+ default-days: 8
79
+ allow:
80
+ - dependency-type: "all"
81
+ ignore:
82
+ - dependency-name: "node"
83
+ # Odd-numbered versions are unstable releases, so skip those.
84
+ # This is using Ruby's version range syntax, specifically, the "twiddle-wakka"
85
+ # to indicate any matching version until the next major one..
86
+ # See https://docs.github.com/en/code-security/dependabot/working-with-dependabot/dependabot-options-reference#versions-ignore
87
+ # and https://guides.rubygems.org/patterns/#pessimistic-version-constraint
88
+ versions: ["~> 23", "<= 24.10", "~> 25", "~> 27", "~> 29"]
89
+ # The newest major version of faker is ESM-only which is difficult
90
+ # to support with jest
91
+ # Since faker is a zero-dependency library, ignore major version
92
+ # upgrades (can consider the work to update if/when we need any
93
+ # functionality from the new version)
94
+ # See https://github.com/faker-js/faker/issues/3606#issuecomment-3233612736
95
+ - dependency-name: "@faker-js/faker"
96
+ update-types: ["version-update:semver-major"]
.github/linter_config.yml ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This Source Code Form is subject to the terms of the Mozilla Public
2
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
+
5
+ # ID checks
6
+ # ID01: check that identifiers only use lowercase and hyphens
7
+ # ID02: check that identifiers have a minimum length
8
+ #
9
+ # Syntax checks
10
+ #
11
+ # Specify if syntax features are disabled. If the config is missing, it
12
+ # will considered as enabled.
13
+ #
14
+ # Example:
15
+ # SY01:
16
+ # disabled: true
17
+ #
18
+ # SY01: terms
19
+ # SY02: message references
20
+ # SY03: term references
21
+ # SY04: variants
22
+ # SY05: attributes
23
+
24
+ ---
25
+ ID01:
26
+ enabled: false
27
+ exclusions:
28
+ messages: []
29
+ files: []
30
+ ID02:
31
+ enabled: false
32
+ min_length: 6
33
+ exclusions:
34
+ messages: []
35
+ files: []
36
+ CO01:
37
+ enabled: true
38
+ brands:
39
+ - Firefox
40
+ - Mozilla
41
+ - Relay
42
+ - Monitor
43
+ - "{ -brand-mozilla } account"
44
+ exclusions:
45
+ messages:
46
+ - breach-checklist-ssn-header
47
+ - fxa-what-to-do-blurb-3
48
+ - monitor-several-emails
49
+ - rec-bank-acc-subhead
50
+ - rec-cc-subhead
51
+ - dashboard-top-banner-monitor-more-cta
52
+ - announcement-add-up-to-20-emails-plus-title
53
+ files: []
54
+ # Variable comments
55
+ VC:
56
+ disabled: false
57
+ # Placeables style
58
+ PS01:
59
+ disabled: false
.github/pull_request_template.md ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!-- The following is intended to be helpful to you. Feel free to remove anything that is not. -->
2
+
3
+ # References:
4
+
5
+ Jira: MNTOR-
6
+ Figma:
7
+
8
+ <!-- When adding a new feature: -->
9
+
10
+ # Description
11
+
12
+ # Screenshot (if applicable)
13
+
14
+ Not applicable.
15
+
16
+ # How to test
17
+
18
+ # Checklist (Definition of Done)
19
+
20
+ - [ ] Localization strings (if needed) have been added.
21
+ - [ ] Commits in this PR are minimal and [have descriptive commit messages](https://chris.beams.io/posts/git-commit/).
22
+ - [ ] I've added or updated the relevant sections in readme and/or code comments
23
+ - [ ] I've added a unit test to test for potential regressions of this bug.
24
+ - [ ] If this PR implements a feature flag or experimentation, I've checked that it still works with the flag both on, and with the flag off.
25
+ - [ ] If this PR implements a feature flag or experimentation, the Ship Behind Feature Flag status in Jira has been set
26
+ - [ ] Product Owner accepted the User Story (demo of functionality completed) or waived the privilege.
27
+ - [ ] All acceptance criteria are met.
28
+ - [ ] Jira ticket has been updated (if needed) to match changes made during the development process.
29
+ - [ ] Jira ticket has been updated (if needed) with suggestions for QA when this PR is deployed to stage.
.github/requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ moz-fluent-linter==0.4.*
.github/workflows/build.yaml ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Build
2
+
3
+ permissions: {}
4
+
5
+ on:
6
+ pull_request:
7
+ push:
8
+
9
+ jobs:
10
+ npm-build:
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - uses: actions/checkout@v6
15
+ with:
16
+ persist-credentials: false
17
+ - name: Use Node.js
18
+ uses: actions/setup-node@v6
19
+ with:
20
+ node-version: "20.19.x"
21
+ - run: npm ci
22
+ - run: npm run build-glean
23
+ # Verify that the build (incl. type-checking) succeeds
24
+ # Upload sourcemaps to Sentry
25
+ - run: npm run build
26
+ env:
27
+ SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
.github/workflows/conflicts.yml ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: No unresolved conflicts
2
+
3
+ permissions: {}
4
+
5
+ on:
6
+ pull_request:
7
+ branches: [ main, localization ]
8
+
9
+ jobs:
10
+ detect-unresolved-conflicts:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v6
14
+ with:
15
+ persist-credentials: false
16
+ - name: List files with merge conflict markers
17
+ # Encode conflict markers so this file does not trigger git's conflict detection.
18
+ run: git --no-pager grep "$(echo 'PDw8PDw8PAo=' | base64 -d)" ":(exclude).github/" || true
19
+ - name: Fail or succeed job if any files with merge conflict markers have been checked in
20
+ # Find lines containing conflict markers then count the number of lines.
21
+ # 0 matching lines results in exit code 0, i.e. success.
22
+ run: exit $(git grep "$(echo 'PDw8PDw8PAo=' | base64 -d)" ":(exclude).github/" | wc --lines)
.github/workflows/docker_build_deploy.yml ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Build Docker image and publish
2
+
3
+ permissions: {}
4
+
5
+ on:
6
+ push:
7
+ branches: [ main ]
8
+ jobs:
9
+ push_to_registry:
10
+ name: Push Docker image to Docker Hub
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - name: Check out the repo
14
+ uses: actions/checkout@v6
15
+ with:
16
+ persist-credentials: false
17
+
18
+ - name: Log in to Docker Hub
19
+ uses: docker/login-action@v3
20
+ with:
21
+ username: ${{ secrets.DOCKER_USERNAME }}
22
+ password: ${{ secrets.DOCKER_PASSWORD }}
23
+
24
+ - name: Extract metadata (tags, labels) for Docker
25
+ id: meta
26
+ uses: docker/metadata-action@v5
27
+ with:
28
+ images: mozilla/blurts-server
29
+ tags: |
30
+ type=semver,pattern={{raw}}
31
+ type=raw,value={{sha}},event=tag
32
+
33
+ - name: Create version.json
34
+ run: |
35
+ echo "{\"commit\":\"$GITHUB_SHA\",\"version\":\"$GITHUB_REF_NAME\",\"source\":\"https://github.com/$GITHUB_REPOSITORY\",\"build\":\"$GITHUB_RUN_ID\"}" > version.json
36
+
37
+ - name: Check Docker Version
38
+ run: docker --version
39
+ - name: Install Latest Docker
40
+ run: |
41
+ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
42
+ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
43
+ sudo apt-get update
44
+ sudo apt-get install docker-ce
45
+
46
+ - name: Build Docker image
47
+ env:
48
+ UPLOAD_SENTRY_SOURCEMAPS: true
49
+ SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
50
+ SENTRY_RELEASE: ${{ github.ref_name }}
51
+ NEXT_PUBLIC_SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
52
+ run: |
53
+ docker build --tag blurts-server \
54
+ --build-arg SENTRY_RELEASE="$SENTRY_RELEASE" \
55
+ --build-arg NEXT_PUBLIC_SENTRY_DSN="$NEXT_PUBLIC_SENTRY_DSN" \
56
+ --secret id=SENTRY_AUTH_TOKEN \
57
+ .
58
+
59
+ - name: Deploy to Dockerhub
60
+ env:
61
+ DOCKERHUB_REPO: ${{ env.DOCKERHUB_REPO }}
62
+ TAGS: ${{ steps.meta.outputs.tags }}
63
+ run: |
64
+ # deploy main
65
+ docker tag blurts-server $TAGS
66
+ docker push $TAGS
.github/workflows/docker_build_deploy_v2.yml ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Build Docker image and publish to GAR
2
+
3
+ permissions: {}
4
+
5
+ on:
6
+ push:
7
+ branches:
8
+ - main
9
+ tags:
10
+ - "*"
11
+
12
+ jobs:
13
+ build_and_push_to_gar:
14
+ # Define permissions at the job level
15
+ permissions:
16
+ contents: "read" # Needed for checkout
17
+ id-token: "write" # Needed for GCP auth
18
+ packages: "none" # Explicitly disable package permissions
19
+ name: Build and Push Docker image to GAR
20
+ runs-on: ubuntu-latest
21
+ environment: build
22
+ env:
23
+ GAR_IMAGE_BASE: ${{ vars.GAR_REPO }}/${{ github.event.repository.name }} # Base name for GAR image
24
+ GAR_REGISTRY: us-docker.pkg.dev
25
+ steps:
26
+ - name: Check out the repo
27
+ uses: actions/checkout@v6
28
+ with:
29
+ persist-credentials: false
30
+
31
+ - name: Authenticate to Google Cloud
32
+ id: gcp-auth
33
+ uses: google-github-actions/auth@v3
34
+ with:
35
+ token_format: access_token
36
+ workload_identity_provider: ${{ vars.GCPV2_GITHUB_WORKLOAD_IDENTITY_PROVIDER }}
37
+ service_account: ${{ vars.GCP_GAR_SERVICE_ACCOUNT }}
38
+
39
+ - name: Login to Artifact Registry
40
+ id: gar-login
41
+ uses: docker/login-action@v3
42
+ with:
43
+ registry: ${{ env.GAR_REGISTRY }}
44
+ username: oauth2accesstoken
45
+ password: ${{ steps.gcp-auth.outputs.access_token }}
46
+
47
+ - name: Extract metadata (tags, labels) for Docker
48
+ id: meta
49
+ uses: docker/metadata-action@v5
50
+ with:
51
+ # Only generate the image name for GAR
52
+ images: ${{ env.GAR_IMAGE_BASE }}
53
+ tags: |
54
+ # Generate tag based on short commit SHA
55
+ type=sha,format=short,prefix=
56
+
57
+ - name: Create version.json
58
+ run: |
59
+ # Use full sha here for version.json content
60
+ echo "{\"commit\":\"$GITHUB_SHA\",\"version\":\"$GITHUB_REF_NAME\",\"source\":\"https://github.com/$GITHUB_REPOSITORY\",\"build\":\"$GITHUB_RUN_ID\"}" > version.json
61
+
62
+ - name: Set up QEMU
63
+ uses: docker/setup-qemu-action@v3
64
+
65
+ - name: Set up Docker Buildx
66
+ id: buildx
67
+ uses: docker/setup-buildx-action@v3
68
+
69
+ - name: Build and push Docker image to GAR
70
+ id: build-and-push
71
+ env:
72
+ TAGS: ${{ steps.meta.outputs.tags }}
73
+ uses: docker/build-push-action@v6
74
+ with:
75
+ context: .
76
+ # Push is true to push to GAR after build
77
+ push: true
78
+ # Tags generated by the metadata action (only GAR tag)
79
+ tags: ${{ env.TAGS }}
80
+ # Pass build arguments
81
+ build-args: |
82
+ SENTRY_RELEASE=${{ github.sha }}
83
+ NEXT_PUBLIC_SENTRY_DSN=${{ secrets.SENTRY_DSN }}
84
+ # Pass secrets securely to the build
85
+ secrets: |
86
+ SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}
87
+ # Enable build cache for faster builds (optional but recommended)
88
+ cache-from: type=gha
89
+ cache-to: type=gha,mode=max
90
+
91
+ - name: Print Image URI
92
+ env:
93
+ TAGS: ${{ steps.meta.outputs.tags }}
94
+ run: |
95
+ echo "Pushed GAR image: $TAGS"
.github/workflows/docker_check.yml ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Build Docker image check
2
+
3
+ permissions: {}
4
+
5
+ on:
6
+ pull_request:
7
+ jobs:
8
+ docker_build:
9
+ name: Build Docker image
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - name: Check Docker Version
13
+ run: docker --version
14
+
15
+ - name: Install Latest Docker
16
+ run: |
17
+ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
18
+ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
19
+ sudo apt-get update
20
+ sudo apt-get install docker-ce
21
+
22
+ - name: Check out the repo
23
+ uses: actions/checkout@v6
24
+ with:
25
+ persist-credentials: false
26
+
27
+ - name: Build Docker image
28
+ run: docker build .
.github/workflows/functional_tests_cron.yml ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Functional Test Suite (cron)
2
+
3
+ permissions: {}
4
+
5
+ on:
6
+ schedule:
7
+ - cron: '0 8 * * *'
8
+ workflow_dispatch:
9
+ inputs:
10
+ test_env:
11
+ description: 'Environment to run the functional test suite against'
12
+ required: false
13
+ default: ''
14
+ type: choice
15
+ options:
16
+ - ''
17
+ - stage
18
+ - production
19
+ jobs:
20
+ functional-tests:
21
+ name: Functional tests (${{ github.event_name == 'workflow_dispatch' && inputs.test_env || matrix.test_env }}) [${{ github.event_name }}]
22
+ timeout-minutes: 60
23
+ runs-on: ubuntu-latest
24
+ strategy:
25
+ matrix:
26
+ test_env: ${{ github.event_name == 'schedule' && fromJson('["stage","production"]') || fromJson('["unset"]') }}
27
+ steps:
28
+ - name: Fail manual run if environment is missing
29
+ if: github.event_name == 'workflow_dispatch' && inputs.test_env == ''
30
+ run: |
31
+ echo "Please select an environment for manual runs"
32
+ exit 1
33
+
34
+ - uses: actions/checkout@v6
35
+ with:
36
+ persist-credentials: false
37
+ - uses: actions/setup-node@v6
38
+ with:
39
+ node-version: 20.19.x
40
+
41
+ - name: Install dependencies
42
+ run: npm ci
43
+ - name: Store Playwright’s Version
44
+ run: |
45
+ # Get the current Playwright version listed in package.json
46
+ PLAYWRIGHT_VERSION=$(npx playwright --version | sed 's/Version //')
47
+ echo "Playwright Version: $PLAYWRIGHT_VERSION"
48
+ echo "PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION" >> $GITHUB_ENV
49
+
50
+ - name: Cache Playwright Browsers for Playwright's Version
51
+ id: cache-playwright-browsers
52
+ uses: actions/cache@v4
53
+ with:
54
+ path: ~/.cache/ms-playwright
55
+ key: playwright-browsers-${{ env.PLAYWRIGHT_VERSION }}
56
+
57
+ - name: Setup Playwright Browser
58
+ if: steps.cache-playwright-browsers.outputs.cache-hit != 'true'
59
+ run: npx playwright install --with-deps
60
+
61
+ - name: Run Playwright tests - ${{ github.event_name == 'workflow_dispatch' && inputs.test_env || matrix.test_env }}
62
+ if: github.event.pull_request.user.login != 'dependabot[bot]'
63
+ run: npm run functional-tests
64
+ timeout-minutes: 40
65
+ env:
66
+ E2E_TEST_ENV: ${{ github.event_name == 'workflow_dispatch' && inputs.test_env || matrix.test_env }}
67
+ E2E_TEST_SECRET: ${{ secrets.E2E_TEST_SECRET }}
68
+ E2E_TEST_ACCOUNT_BASE_EMAIL: ${{ secrets.E2E_TEST_ACCOUNT_BASE_EMAIL }}
69
+ E2E_TEST_ACCOUNT_BASE_PASSWORD: ${{ secrets.E2E_TEST_ACCOUNT_BASE_PASSWORD }}
70
+ - uses: actions/upload-artifact@v5
71
+ if: always()
72
+ with:
73
+ name: playwright-report-${{ github.event_name == 'workflow_dispatch' && inputs.test_env || matrix.test_env }}
74
+ path: playwright-report/
75
+ retention-days: 30
76
+ - uses: actions/upload-artifact@v5
77
+ if: always()
78
+ with:
79
+ name: test-results-${{ github.event_name == 'workflow_dispatch' && inputs.test_env || matrix.test_env }}
80
+ path: src/functional-tests/test-results/
81
+ retention-days: 30
82
+
83
+ - name: Send GitHub Action trigger data to Slack workflow
84
+ id: slack
85
+ uses: slackapi/slack-github-action@v2.1.1
86
+ if: failure() && github.ref == 'refs/heads/main'
87
+ with:
88
+ webhook: ${{ secrets.SLACK_GHA_FAILURES_WEBHOOK }}
89
+ webhook-type: webhook-trigger
90
+ # For posting a message using Block Kit
91
+ payload: |
92
+ {
93
+ "blocks": [
94
+ {
95
+ "type": "section",
96
+ "text": {
97
+ "type": "mrkdwn",
98
+ "text": "*Link to job:* *<https://github.com/mozilla/blurts-server/actions/runs/${{ github.run_id }}|Functional tests>*"
99
+ }
100
+ },
101
+ {
102
+ "type": "divider"
103
+ },
104
+ {
105
+ "type": "section",
106
+ "fields": [
107
+ {
108
+ "type": "mrkdwn",
109
+ "text": "*Workflow:* \n ${{ github.workflow }}"
110
+ },
111
+ {
112
+ "type": "mrkdwn",
113
+ "text": "*Environment:* \n ${{ github.event_name == 'workflow_dispatch' && inputs.test_env || matrix.test_env }}"
114
+ },
115
+ {
116
+ "type": "mrkdwn",
117
+ "text": "*Status:* \n ${{ job.status }}"
118
+ }
119
+ ]
120
+ },
121
+ {
122
+ "type": "section",
123
+ "fields": [
124
+ {
125
+ "type": "mrkdwn",
126
+ "text": "*Ref:*\n ${{ github.ref }} "
127
+ },
128
+ {
129
+ "type": "mrkdwn",
130
+ "text": "*Triggered by:*\n ${{ github.triggering_actor }}"
131
+ }
132
+ ]
133
+ }
134
+ ]
135
+ }
.github/workflows/functional_tests_pr.yml ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Functional Test Suite (PR)
2
+
3
+ permissions: {}
4
+
5
+ on:
6
+ push:
7
+ branches: [ main ]
8
+ pull_request:
9
+ branches: [ main ]
10
+ jobs:
11
+ functional-tests:
12
+ timeout-minutes: 60
13
+ runs-on: ubuntu-latest
14
+ # Service containers to run with `container-job`
15
+ services:
16
+ # Label used to access the service container
17
+ postgres:
18
+ # Docker Hub image
19
+ image: postgres
20
+ # Provide the password for postgres
21
+ env:
22
+ POSTGRES_USER: postgres
23
+ POSTGRES_PASSWORD: postgres
24
+ POSTGRES_DB: blurts
25
+ # Set health checks to wait until postgres has started
26
+ options: >-
27
+ --health-cmd pg_isready
28
+ --health-interval 10s
29
+ --health-timeout 5s
30
+ --health-retries 5
31
+ ports:
32
+ - 5432:5432
33
+
34
+ steps:
35
+ - uses: actions/checkout@v6
36
+ with:
37
+ persist-credentials: false
38
+ - uses: actions/setup-node@v6
39
+ with:
40
+ node-version: 20.19.x
41
+
42
+ - name: Install dependencies
43
+ run: npm ci
44
+ - name: Setting up postgres
45
+ run: npm run db:migrate
46
+ env:
47
+ DATABASE_URL: postgres://postgres:postgres@localhost:5432/blurts
48
+ - name: Store Playwright’s Version
49
+ run: |
50
+ # Get the current Playwright version listed in package.json
51
+ PLAYWRIGHT_VERSION=$(npx playwright --version | sed 's/Version //')
52
+ echo "Playwright Version: $PLAYWRIGHT_VERSION"
53
+ echo "PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION" >> $GITHUB_ENV
54
+ - name: Cache Playwright Browsers for Playwright's Version
55
+ id: cache-playwright-browsers
56
+ uses: actions/cache@v4
57
+ with:
58
+ path: ~/.cache/ms-playwright
59
+ key: playwright-browsers-${{ env.PLAYWRIGHT_VERSION }}
60
+
61
+ - name: Setup Playwright Browser
62
+ if: steps.cache-playwright-browsers.outputs.cache-hit != 'true'
63
+ run: npx playwright install --with-deps
64
+
65
+ - name: Run Playwright tests
66
+ if: github.event.pull_request.user.login != 'dependabot[bot]'
67
+ run: npm run functional-tests
68
+ timeout-minutes: 20
69
+ env:
70
+ ADMINS: ${{ secrets.ADMINS }}
71
+ DATABASE_URL: postgres://postgres:postgres@localhost:5432/blurts
72
+ E2E_TEST_SECRET: ${{ secrets.E2E_TEST_SECRET }}
73
+ E2E_TEST_ACCOUNT_BASE_EMAIL: ${{ secrets.E2E_TEST_ACCOUNT_BASE_EMAIL }}
74
+ E2E_TEST_ACCOUNT_BASE_PASSWORD: ${{ secrets.E2E_TEST_ACCOUNT_BASE_PASSWORD }}
75
+ E2E_TEST_ENV: local
76
+ HIBP_API_TOKEN: ${{ secrets.HIBP_API_TOKEN }}
77
+ HIBP_KANON_API_ROOT: "http://localhost:6060/api/mock/hibp"
78
+ HIBP_KANON_API_TOKEN: ${{ secrets.HIBP_KANON_API_TOKEN }}
79
+ NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }}
80
+ NEXTAUTH_URL: ${{ secrets.NEXTAUTH_URL }}
81
+ OAUTH_ACCOUNT_URI: ${{ secrets.OAUTH_ACCOUNT_URI }}
82
+ OAUTH_CLIENT_SECRET: ${{ secrets.OAUTH_CLIENT_SECRET_LOCAL }}
83
+ ONEREP_API_BASE: "http://localhost:6060/api/mock/onerep/"
84
+ ONEREP_API_KEY: ${{ secrets.ONEREP_API_KEY }}
85
+ PREMIUM_PLAN_ID_MONTHLY_US: ${{ secrets.STAGE_PREMIUM_PLAN_ID_MONTHLY_US }}
86
+ PREMIUM_PLAN_ID_YEARLY_US: ${{ secrets.STAGE_PREMIUM_PLAN_ID_YEARLY_US }}
87
+ PREMIUM_PRODUCT_ID: ${{ secrets.STAGE_PREMIUM_PRODUCT_ID }}
88
+ REDIS_URL: "redis://redis.mock"
89
+ - uses: actions/upload-artifact@v5
90
+ if: always()
91
+ with:
92
+ name: playwright-report
93
+ path: playwright-report/
94
+ retention-days: 30
95
+ - uses: actions/upload-artifact@v5
96
+ if: always()
97
+ with:
98
+ name: test-results
99
+ path: src/functional-tests/test-results/
100
+ retention-days: 30
.github/workflows/glean-probe-scraper.yml ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Glean probe-scraper
2
+
3
+ permissions: {}
4
+
5
+ on:
6
+ push:
7
+ paths:
8
+ - src/telemetry/metrics.yaml
9
+ - src/telemetry/backend-metrics.yaml
10
+ pull_request:
11
+ paths:
12
+ - src/telemetry/metrics.yaml
13
+ - src/telemetry/backend-metrics.yaml
14
+ jobs:
15
+ glean-probe-scraper:
16
+ uses: mozilla/probe-scraper/.github/workflows/glean.yaml@main
.github/workflows/lighthouse_cron.yml ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Lighthouse Report Cron
2
+
3
+ permissions: {}
4
+
5
+ on:
6
+ schedule:
7
+ - cron: '0 6 * * *'
8
+ workflow_dispatch:
9
+ inputs:
10
+ environment:
11
+ description: 'Environment to run LHCI against'
12
+ required: false
13
+ default: 'stage'
14
+ type: choice
15
+ options:
16
+ - stage
17
+ - prod
18
+ jobs:
19
+ lhci:
20
+ name: Lighthouse Report - ${{ inputs.environment != null && inputs.environment || 'stage' }}
21
+ runs-on: ubuntu-latest
22
+ environment: ${{ inputs.environment != null && inputs.environment || 'stage' }}
23
+ permissions:
24
+ contents: read
25
+ id-token: write
26
+ steps:
27
+ - uses: actions/checkout@v6
28
+ with:
29
+ persist-credentials: false
30
+ - name: Use Node.js 20.19.x
31
+ uses: actions/setup-node@v6
32
+ with:
33
+ node-version: 20.19.x
34
+ - name: Run Lighthouse CI
35
+ run: |
36
+ npm install -g @lhci/cli@0.14.x
37
+ npm run lighthouse
38
+ env:
39
+ LIGHTHOUSE_COLLECT_URL: ${{ secrets.LIGHTHOUSE_COLLECT_URL }}
40
+ - name: Build cronjobs
41
+ run: |
42
+ npm ci
43
+ npm run build-cronjobs
44
+ - name: Authenticate to Google Cloud
45
+ uses: google-github-actions/auth@v3
46
+ with:
47
+ workload_identity_provider: ${{ secrets.GC_LIGHTHOUSE_WORKLOAD_IDENTITY_PROVIDER }}
48
+ service_account: ${{ secrets.GC_LIGHTHOUSE_SERVICE_ACCOUNT }}
49
+ - name: Report results
50
+ run: npm run cron:report-lighthouse-results
51
+ env:
52
+ BQ_LIGHTHOUSE_PROJECT: ${{ secrets.BQ_LIGHTHOUSE_PROJECT }}
53
+ BQ_LIGHTHOUSE_DATASET: ${{ secrets.BQ_LIGHTHOUSE_DATASET }}
54
+ BQ_LIGHTHOUSE_TABLE: ${{ secrets.BQ_LIGHTHOUSE_TABLE }}
.github/workflows/lint.yaml ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Lint
2
+
3
+ permissions: {}
4
+
5
+ on:
6
+ pull_request:
7
+ push:
8
+
9
+ jobs:
10
+ npm-lint:
11
+
12
+ runs-on: ubuntu-latest
13
+
14
+ steps:
15
+ - uses: actions/checkout@v6
16
+ with:
17
+ persist-credentials: false
18
+ - name: Use Node.js
19
+ uses: actions/setup-node@v6
20
+ with:
21
+ node-version: '20.19.x'
22
+ - run: npm ci
23
+ - run: npm run build-glean
24
+ - run: npm run build-nimbus
25
+ # Mirror old linter from CircleCI, verifies that linter succeeds
26
+ - run: npm run lint
27
+ - run: node src/scripts/build/checkNodeVersionAlignment.js
28
+ - run: node src/scripts/build/checkGithubActionsBestPractices.js
.github/workflows/production_deploy.yml ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Monitor 1-click Deployment
2
+
3
+ permissions: {}
4
+
5
+ on:
6
+ workflow_dispatch:
7
+ inputs:
8
+ environment:
9
+ description: 'Environment to deploy to'
10
+ required: true
11
+ default: 'prod'
12
+ type: choice
13
+ options:
14
+ - stage
15
+ - prod
16
+ - dev
17
+ originalImageTag:
18
+ description: 'The original image tag that has been deployed'
19
+ required: true
20
+ type: string
21
+ pattern: '^[a-f0-9]{7,12}$'
22
+ jobs:
23
+ pull_retag_push:
24
+ name: Pull, Retag, and Push Images
25
+ runs-on: ubuntu-latest
26
+ environment: build
27
+ permissions:
28
+ contents: "read" # Needed for checkout
29
+ id-token: "write" # Needed for GCP auth
30
+ packages: "none" # Explicitly disable package permissions
31
+ env:
32
+ GAR_IMAGE_BASE: ${{ vars.GAR_REPO }}/${{ github.event.repository.name }}
33
+ GAR_REGISTRY: us-docker.pkg.dev # Define GAR registry hostname
34
+ DOCKERHUB_IMAGE: mozilla/blurts-server # Define Docker Hub image name
35
+ SAFE_IMAGE_TAG: ${{ inputs.originalImageTag }}
36
+ SAFE_ENVIRONMENT: ${{ inputs.environment }}
37
+ steps:
38
+ - name: Checkout Repository
39
+ uses: actions/checkout@v6
40
+ with:
41
+ persist-credentials: false
42
+
43
+ - name: Log in to Docker Hub
44
+ uses: docker/login-action@v3
45
+ with:
46
+ username: ${{ secrets.DOCKER_USERNAME }}
47
+ password: ${{ secrets.DOCKER_PASSWORD }}
48
+
49
+ - name: Authenticate to Google Cloud
50
+ id: gcp-auth
51
+ uses: google-github-actions/auth@v3
52
+ with:
53
+ token_format: access_token
54
+ workload_identity_provider: ${{ vars.GCPV2_GITHUB_WORKLOAD_IDENTITY_PROVIDER }}
55
+ service_account: ${{ vars.GCP_GAR_SERVICE_ACCOUNT }}
56
+
57
+ - name: Login to Artifact Registry
58
+ id: gar-login
59
+ uses: docker/login-action@v3
60
+ with:
61
+ registry: ${{ env.GAR_REGISTRY }}
62
+ username: oauth2accesstoken
63
+ password: ${{ steps.gcp-auth.outputs.access_token }}
64
+
65
+ - name: Pull Docker Hub image
66
+ run: docker pull "$DOCKERHUB_IMAGE:$SAFE_IMAGE_TAG"
67
+
68
+ - name: Retag Docker Hub image
69
+ run: docker tag "$DOCKERHUB_IMAGE:$SAFE_IMAGE_TAG" "$DOCKERHUB_IMAGE:$SAFE_ENVIRONMENT-$SAFE_IMAGE_TAG"
70
+
71
+ - name: Push Docker Hub image
72
+ run: docker push "$DOCKERHUB_IMAGE:$SAFE_ENVIRONMENT-$SAFE_IMAGE_TAG"
73
+
74
+ - name: Pull GAR image
75
+ run: docker pull "$GAR_IMAGE_BASE:$SAFE_IMAGE_TAG"
76
+
77
+ - name: Retag GAR image
78
+ run: docker tag "$GAR_IMAGE_BASE:$SAFE_IMAGE_TAG" "$GAR_IMAGE_BASE:$SAFE_ENVIRONMENT-$SAFE_IMAGE_TAG"
79
+
80
+ - name: Push GAR image
81
+ run: docker push "$GAR_IMAGE_BASE:$SAFE_ENVIRONMENT-$SAFE_IMAGE_TAG"
.github/workflows/reference_linter.yaml ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Lint Reference Files
2
+
3
+ permissions: {}
4
+
5
+ on:
6
+ push:
7
+ pull_request:
8
+ workflow_dispatch:
9
+ jobs:
10
+ l10n-lint:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - name: Clone repository
14
+ uses: actions/checkout@v6
15
+ with:
16
+ persist-credentials: false
17
+ - name: Set up Python 3
18
+ uses: actions/setup-python@v6
19
+ with:
20
+ python-version: '3.10'
21
+ cache: 'pip'
22
+ - name: Install Python dependencies
23
+ run: |
24
+ pip install -r .github/requirements.txt
25
+ - name: Lint reference strings
26
+ run: |
27
+ moz-fluent-lint ./locales/en/ --config .github/linter_config.yml
28
+ - name: Lint pending strings
29
+ if: always() # This step should run even if the previous one fails
30
+ run: |
31
+ moz-fluent-lint ./locales-pending/ --config .github/linter_config.yml
.github/workflows/release_cron_daily.yml ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Daily Pre-release
2
+
3
+ permissions: {}
4
+
5
+ on:
6
+ schedule:
7
+ - cron: '0 0 * * 1-5' # Runs Mon-Fri at midnight UTC
8
+ workflow_dispatch: # Allows manual trigger if needed
9
+
10
+ jobs:
11
+ create-pre-release:
12
+ permissions:
13
+ contents: write
14
+ id-token: write
15
+ runs-on: ubuntu-latest
16
+ environment: build
17
+ env:
18
+ GAR_IMAGE_BASE: ${{ vars.GAR_REPO }}/${{ github.event.repository.name }}
19
+ GAR_REGISTRY: us-docker.pkg.dev
20
+ DOCKERHUB_IMAGE: mozilla/blurts-server # Define Docker Hub image name
21
+
22
+ steps:
23
+ - name: Checkout main branch
24
+ uses: actions/checkout@v6
25
+ with:
26
+ ref: main
27
+ persist-credentials: false
28
+
29
+ - name: Get current date
30
+ run: |
31
+ echo "CURRENT_DATE=$(date +%Y.%m.%d)" >> $GITHUB_ENV
32
+ echo "tag_name: $(date +%Y.%m.%d)"
33
+
34
+ - name: Create Pre-release
35
+ env:
36
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
37
+ run: |
38
+ curl -X POST \
39
+ -H "Authorization: token $GITHUB_TOKEN" \
40
+ -H "Accept: application/vnd.github+json" \
41
+ -H "X-GitHub-Api-Version: 2022-11-28" \
42
+ https://api.github.com/repos/${{ github.repository }}/releases \
43
+ -d '{
44
+ "tag_name": "${{ env.CURRENT_DATE }}",
45
+ "target_commitish": "main",
46
+ "name": "${{ env.CURRENT_DATE }}",
47
+ "body": "Daily pre-release for ${{ env.CURRENT_DATE }}.",
48
+ "prerelease": true,
49
+ "draft": false,
50
+ "generate_release_notes": true
51
+ }'
52
+
53
+ # We cannot rely on the release_retag.yaml workflow because of the
54
+ # auth scope of the default github token. It's a good security practice
55
+ # to prevent a github action being triggered by another.
56
+ # So we will deliberately push to dockerhub below
57
+ - name: Log in to Docker Hub
58
+ uses: docker/login-action@v3
59
+ with:
60
+ username: ${{ secrets.DOCKER_USERNAME }}
61
+ password: ${{ secrets.DOCKER_PASSWORD }}
62
+
63
+ - name: Authenticate to Google Cloud
64
+ id: gcp-auth
65
+ uses: google-github-actions/auth@v3
66
+ with:
67
+ token_format: access_token
68
+ workload_identity_provider: ${{ vars.GCPV2_GITHUB_WORKLOAD_IDENTITY_PROVIDER }}
69
+ service_account: ${{ vars.GCP_GAR_SERVICE_ACCOUNT }}
70
+
71
+ - name: Login to Artifact Registry
72
+ id: gar-login
73
+ uses: docker/login-action@v3
74
+ with:
75
+ registry: ${{ env.GAR_REGISTRY }}
76
+ username: oauth2accesstoken
77
+ password: ${{ steps.gcp-auth.outputs.access_token }}
78
+
79
+ - name: Extract metadata (tags, labels) for Docker
80
+ id: meta
81
+ uses: docker/metadata-action@v5
82
+ with:
83
+ images: ${{ env.DOCKERHUB_IMAGE }}
84
+ tags: type=sha,format=short,prefix=
85
+
86
+ - name: Pull Docker image from GAR with commit tag
87
+ env:
88
+ VERSION: ${{ steps.meta.outputs.version }}
89
+ run: docker pull ${{ env.GAR_IMAGE_BASE }}:$VERSION
90
+
91
+ - name: Tag Docker image for Docker Hub with release tag
92
+ env:
93
+ VERSION: ${{ steps.meta.outputs.version }}
94
+ run: docker tag ${{ env.GAR_IMAGE_BASE }}:$VERSION ${{ env.DOCKERHUB_IMAGE }}:${{ env.CURRENT_DATE }}
95
+
96
+ - name: Push Docker image to Docker Hub with release tag
97
+ run: docker push ${{ env.DOCKERHUB_IMAGE }}:${{ env.CURRENT_DATE }}
98
+
99
+ - name: Tag Docker image for GAR with release tag
100
+ env:
101
+ VERSION: ${{ steps.meta.outputs.version }}
102
+ run: docker tag ${{ env.GAR_IMAGE_BASE }}:$VERSION ${{ env.GAR_IMAGE_BASE }}:${{ env.CURRENT_DATE }}
103
+
104
+ - name: Push Docker image to GAR with release tag
105
+ run: docker push ${{ env.GAR_IMAGE_BASE }}:${{ env.CURRENT_DATE }}
.github/workflows/release_retag.yaml ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Retag and Push Docker Image on Release
2
+
3
+ permissions: {}
4
+
5
+ # GH release should always create a tag automatically
6
+ on:
7
+ push:
8
+ tags:
9
+ - '*'
10
+
11
+ jobs:
12
+ retag-and-push:
13
+ runs-on: ubuntu-latest
14
+
15
+ steps:
16
+ - name: Check out the repo
17
+ uses: actions/checkout@v6
18
+ with:
19
+ persist-credentials: false
20
+
21
+ - name: Log in to Docker Hub
22
+ uses: docker/login-action@v3
23
+ with:
24
+ username: ${{ secrets.DOCKER_USERNAME }}
25
+ password: ${{ secrets.DOCKER_PASSWORD }}
26
+
27
+ - name: Extract metadata (tags, labels) for Docker
28
+ id: meta
29
+ uses: docker/metadata-action@v5
30
+ with:
31
+ images: mozilla/blurts-server
32
+ tags: type=sha,format=short,prefix=
33
+
34
+ - name: Pull Docker image with commit tag
35
+ env:
36
+ TAGS: ${{ steps.meta.outputs.tags }}
37
+ run: docker pull $TAGS
38
+
39
+ - name: Tag Docker image with release tag
40
+ env:
41
+ TAGS: ${{ steps.meta.outputs.tags }}
42
+ run: docker tag $TAGS mozilla/blurts-server:${{ github.ref_name }}
43
+
44
+ - name: Push Docker image with release tag
45
+ run: docker push mozilla/blurts-server:${{ github.ref_name }}
.github/workflows/release_retag_v2.yaml ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Retag and Push GAR Image on Release v2
2
+
3
+ permissions: {}
4
+
5
+ on:
6
+ push:
7
+ tags:
8
+ - '*'
9
+
10
+ jobs:
11
+ retag-and-push-gar:
12
+ permissions:
13
+ contents: "read" # Needed for checkout
14
+ id-token: "write" # Needed for GCP auth
15
+ packages: "none" # Explicitly disable package permissions
16
+ name: Retag and Push GAR image
17
+ runs-on: ubuntu-latest
18
+ environment: build
19
+ env:
20
+ GAR_IMAGE_BASE: ${{ vars.GAR_REPO }}/${{ github.event.repository.name }}
21
+ steps:
22
+ - name: Check out the repo
23
+ uses: actions/checkout@v6
24
+ with:
25
+ persist-credentials: false # Not strictly needed for retagging, but good practice
26
+
27
+ - name: Authenticate to Google Cloud
28
+ id: gcp-auth
29
+ uses: google-github-actions/auth@v3
30
+ with:
31
+ token_format: access_token
32
+ workload_identity_provider: ${{ vars.GCPV2_GITHUB_WORKLOAD_IDENTITY_PROVIDER }}
33
+ service_account: ${{ vars.GCP_GAR_SERVICE_ACCOUNT }}
34
+
35
+ - name: Login to Artifact Registry
36
+ id: gar-login
37
+ uses: docker/login-action@v3
38
+ with:
39
+ registry: us-docker.pkg.dev
40
+ username: oauth2accesstoken
41
+ password: ${{ steps.gcp-auth.outputs.access_token }}
42
+
43
+ - name: Extract metadata (tags, labels) for Docker
44
+ id: meta
45
+ uses: docker/metadata-action@v5
46
+ with:
47
+ # Use the GAR image base
48
+ images: ${{ env.GAR_IMAGE_BASE }}
49
+ tags: |
50
+ # Only generate the tag based on short commit SHA
51
+ type=sha,format=short,prefix=
52
+
53
+ - name: Determine Release-tagged image name
54
+ id: release_tag_info
55
+ run: echo "name=${{ env.GAR_IMAGE_BASE }}:${{ github.ref_name }}" >> $GITHUB_OUTPUT
56
+
57
+ - name: Pull Docker image with commit tag from GAR
58
+ env:
59
+ TAGS: ${{ steps.meta.outputs.tags }}
60
+ run: |
61
+ echo "Pulling $TAGS"
62
+ docker pull $TAGS
63
+
64
+ - name: Tag Docker image with release tag
65
+ env:
66
+ TAGS: ${{ steps.meta.outputs.tags }}
67
+ NAME: ${{ steps.release_tag_info.outputs.name }}
68
+ run: |
69
+ echo "Tagging $TAGS as $NAME"
70
+ docker tag $TAGS $NAME
71
+
72
+ - name: Push Docker image with release tag to GAR
73
+ env:
74
+ NAME: ${{ steps.release_tag_info.outputs.name }}
75
+ run: |
76
+ echo "Pushing $NAME"
77
+ docker push $NAME
78
+
79
+ - name: Print Image URI
80
+ env:
81
+ NAME: ${{ steps.release_tag_info.outputs.name }}
82
+ run: |
83
+ echo "Retagged and pushed GAR image: $NAME"
.github/workflows/test_integrations.yml ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: "Test Integrations"
2
+ permissions: {}
3
+
4
+ on:
5
+ push:
6
+ branches: [ main ]
7
+ pull_request:
8
+ branches: [ main ]
9
+
10
+ jobs:
11
+ test-integrations:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v6
15
+ with:
16
+ persist-credentials: false
17
+
18
+ # Log into Docker Hub to avoid getting rate-limited
19
+ - name: Login to Docker Hub
20
+ # Not giving dependabot secrets access, so continue if login fails
21
+ # and accept rate limit possibility
22
+ continue-on-error: true
23
+ uses: docker/login-action@v3
24
+ with:
25
+ username: ${{ secrets.DOCKER_USERNAME }}
26
+ password: ${{ secrets.DOCKER_PASSWORD }}
27
+
28
+ # Kick off starting docker compose first, since it can take a bit and can run in the background
29
+ - name: Start Docker Compose services
30
+ run: docker compose --env-file=./.env.ci up -d
31
+
32
+ # While we wait for docker compose to be healthy we install node and needed packages for this service
33
+ - name: Set up node
34
+ uses: actions/setup-node@v6
35
+ with:
36
+ node-version: 20.19.x
37
+
38
+ - name: Install dependencies
39
+ run: npm ci
40
+
41
+ # Wait for the docker services we started earlier to all be healthy
42
+ - name: Wait for services to be healthy
43
+ run: docker compose --env-file=./.env.ci up --wait
44
+
45
+ - name: Set up postgres
46
+ run: npm run db:migrate
47
+ env:
48
+ DATABASE_URL: postgres://blurts:blurts@localhost:5432/test-blurts
49
+
50
+ # Let's run those integration tests!
51
+ - name: Run service integration tests
52
+ run: npm run test-integrations
.github/workflows/unittests.yaml ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Unit Tests
2
+
3
+ permissions: {}
4
+
5
+ on:
6
+ pull_request:
7
+ push:
8
+ schedule:
9
+ - cron: '0 8 * * *'
10
+
11
+ jobs:
12
+ unit-tests:
13
+
14
+ runs-on: ubuntu-latest
15
+
16
+ steps:
17
+ - uses: actions/checkout@v6
18
+ with:
19
+ persist-credentials: false
20
+ - name: Use Node.js
21
+ uses: actions/setup-node@v6
22
+ with:
23
+ node-version: '20.19.x'
24
+ - run: npm ci
25
+ - run: npm run build-glean
26
+ # Run the regular tests on push
27
+ - run: npm test
28
+ if: github.event_name != 'schedule'
29
+ # But measure coverage without ignore markers in the nighly job
30
+ - name: Remove `c8 ignore` markers to output full unit test coverage if on the `report-coverage` branch
31
+ run: find src/ -type f -name "*.js" -or -name "*.ts" -or -name "*.jsx" -or -name "*.tsx" -exec sed --in-place --expression='s/c8 ignore/c8 TEMPORARILY DO NOT ignore/g' {} \;
32
+ if: github.event_name == 'schedule'
33
+ # (and set an arbitrary coverage threshold for that "true" coverage):
34
+ - run: npm test -- --coverageThreshold='{"global":{"branches":80,"functions":80,"lines":80,"statements":80}}'
35
+ if: github.event_name == 'schedule'
36
+ - uses: actions/upload-artifact@v5
37
+ if: always()
38
+ with:
39
+ name: coverage-report
40
+ path: coverage/
41
+ retention-days: 30
.gitignore ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ node_modules
2
+ .node-version
3
+ coverage
4
+ .coveralls.yml
5
+ .DS_Store
6
+ breach_hashsets
7
+ version.json
8
+ loadtests/__pycache__
9
+ loadtests/venv
10
+ .tmp
11
+ public/dist
12
+ dist
13
+ public/logo_cache/
14
+ src/client/images/logo_cache
15
+
16
+ # Playwright
17
+ **/test-results/
18
+ **/playwright-report/
19
+ **/playwright/.cache/
20
+ **/blob-report/
21
+ **/functional-test-cache/
22
+ **/traces/
23
+ **/*-snapshots/
24
+
25
+ # Storybook
26
+ */storybook.log
27
+ storybook-static/
28
+
29
+ # The rest of this file was generated by create-next-app:
30
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
31
+
32
+ # dependencies
33
+ /node_modules
34
+ /.pnp
35
+ .pnp.js
36
+
37
+ # testing
38
+ /coverage
39
+
40
+ # load testing
41
+ /src/scripts/loadtest/reports/
42
+
43
+ # next.js
44
+ /.next/
45
+ /out/
46
+
47
+ # production
48
+ /build
49
+
50
+ # misc
51
+ .DS_Store
52
+ *.pem
53
+
54
+ # debug
55
+ npm-debug.log*
56
+ yarn-debug.log*
57
+ yarn-error.log*
58
+
59
+ # local env files
60
+ .env*.local
61
+
62
+ # vercel
63
+ .vercel
64
+
65
+ # typescript
66
+ *.tsbuildinfo
67
+ next-env.d.ts
68
+
69
+ # Sentry Auth Token
70
+ .sentryclirc
71
+
72
+ # Glean
73
+ .venv
74
+ /src/telemetry/generated/
75
+
76
+ # Lighthouse CI
77
+ .lighthouseci
78
+
79
+ # vscode debugger
80
+ .vscode/launch.json
.husky/pre-commit ADDED
@@ -0,0 +1 @@
 
 
1
+ npx lint-staged
.storybook/NextImage.tsx ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
+
5
+ import { StaticImageData } from "next/image";
6
+
7
+ const NextImage = ({
8
+ src,
9
+ alt,
10
+ ...props
11
+ }: {
12
+ src: StaticImageData;
13
+ alt: string;
14
+ // eslint-disable-next-line @next/next/no-img-element
15
+ }) => <img src={`/${src.src}`} alt={alt} {...props} />;
16
+
17
+ export default NextImage;
.storybook/main.ts ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
+
5
+ import type { StorybookConfig } from "@storybook/nextjs";
6
+ const config: StorybookConfig = {
7
+ stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
8
+
9
+ addons: [
10
+ "@storybook/addon-a11y",
11
+ "@storybook/addon-links",
12
+ "@storybook/addon-docs",
13
+ ],
14
+
15
+ framework: {
16
+ name: "@storybook/nextjs",
17
+ options: {},
18
+ },
19
+
20
+ docs: {},
21
+
22
+ staticDirs: [
23
+ // See https://github.com/storybookjs/storybook/tree/4f0c895bc53116272ef598f19e8d869213be49a9/code/frameworks/nextjs#nextfontlocal
24
+ {
25
+ from: "../src/app/fonts",
26
+ to: "src/app/fonts",
27
+ },
28
+ ],
29
+
30
+ async webpackFinal(config) {
31
+ config.module ??= {};
32
+ config.module.rules ??= [];
33
+ config.module.rules.push({
34
+ test: /\.ftl/,
35
+ type: "asset/source",
36
+ });
37
+
38
+ // There is an issue with serving static assests in Storybook 8.4. The fix
39
+ // will be included in version 8.6.0:
40
+ // https://github.com/storybookjs/storybook/issues/29576
41
+ config.resolve = {
42
+ ...(config.resolve ?? {}),
43
+ alias: {
44
+ ...(config.resolve?.alias ?? {}),
45
+ "next/image": require.resolve("./NextImage.tsx"),
46
+ },
47
+ };
48
+
49
+ return config;
50
+ },
51
+
52
+ typescript: {
53
+ reactDocgen: "react-docgen-typescript",
54
+ },
55
+ };
56
+ export default config;
.storybook/preview-head.html ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!--
2
+ This Source Code Form is subject to the terms of the Mozilla Public
3
+ License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ -->
6
+ <style>
7
+ body {
8
+ /*
9
+ The <AppDecorator> in preview.tsx should properly set the Inter font as
10
+ the default font using next/font, but some components are rendered outside
11
+ of the decorator by being appended to the <body> using a React Portal.
12
+ Specifically, React Aria does this for modal elements like the <AppPicker>
13
+ popover. Thus, this is a manual workaround to apply the Inter font there
14
+ too. On the actual website, outside of Storybook, this should not be a
15
+ problem, as the font is applied directly to the <body> using next/font in
16
+ the root layout (src/app/layout.tsx).
17
+ */
18
+ font-family: Inter;
19
+ }
20
+ /*
21
+ Make sure that stories set to render fullscreen (see
22
+ https://storybook.js.org/docs/react/configure/story-layout) also take up the
23
+ full height of the page. This is especially relevant for stories that
24
+ demonstrate full pages.
25
+ */
26
+ .sb-main-fullscreen #storybook-root {
27
+ height: 100%;
28
+ }
29
+ </style>
.storybook/preview.tsx ADDED
@@ -0,0 +1,218 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
+
5
+ import React, { useEffect } from "react";
6
+ import { Inter } from "next/font/google";
7
+ import type { Preview } from "@storybook/nextjs";
8
+ import { action } from "storybook/actions";
9
+ import { linkTo } from "@storybook/addon-links";
10
+ import { sb } from "storybook/test";
11
+ import "../src/app/globals.css";
12
+ import { metropolis } from "../src/app/fonts/Metropolis/metropolis";
13
+ import { TestComponentWrapper } from "../src/TestComponentWrapper";
14
+
15
+ const inter = Inter({ subsets: ["latin"], variable: "--font-inter" });
16
+
17
+ const AppDecorator: Preview["decorators"] = (storyFn) => {
18
+ useEffect(() => {
19
+ // We have to add these classes to the body, rather than simply wrapping the
20
+ // storyFn in a container, because some components (most notably, the ones
21
+ // that use useModalOverlay()) append elements to the end of the body using
22
+ // a React Portal, thus breaking out of a container element.
23
+ document.body.classList.add(inter.className);
24
+ document.body.classList.add(inter.variable);
25
+ document.body.classList.add(metropolis.variable);
26
+ }, []);
27
+
28
+ return <TestComponentWrapper>{storyFn()}</TestComponentWrapper>;
29
+ };
30
+
31
+ sb.mock("../src/app/hooks/locationSuggestions.ts");
32
+
33
+ // Arguments to the `storySort` callback, left as documentation.
34
+ type _SortData = {
35
+ type: "story";
36
+ id: string;
37
+ name: string;
38
+ title: string;
39
+ importPath: string;
40
+ tags: Array<"story" | string>;
41
+ };
42
+
43
+ const preview: Preview = {
44
+ parameters: {
45
+ controls: {
46
+ matchers: {
47
+ color: /(background|color)$/i,
48
+ date: /Date$/,
49
+ },
50
+ },
51
+ // https://storybook.js.org/docs/react/configure/story-layout
52
+ layout: "fullscreen",
53
+ options: {
54
+ // https://storybook.js.org/docs/react/writing-stories/naming-components-and-hierarchy#sorting-stories
55
+ // @ts-ignore Storybook appears to not parse this as TypeScript, so we can't
56
+ // add `SortData` type annotations. See
57
+ // https://github.com/storybookjs/storybook/issues/21702#issuecomment-1517154204
58
+ storySort: (a, b) =>
59
+ a.title.localeCompare(b.title, undefined, { numeric: true }),
60
+ },
61
+ nextjs: {
62
+ // See https://storybook.js.org/blog/integrate-nextjs-and-storybook-automatically/#nextnavigation
63
+ appDirectory: true,
64
+ navigation: {
65
+ push(path: string, ...otherArgs: unknown[]) {
66
+ action("nextNavigation.push")(path, ...otherArgs);
67
+
68
+ if (path === "/") {
69
+ linkTo("Pages/Public/Landing page", "US visitors")();
70
+ }
71
+
72
+ if (path === "/breaches") {
73
+ linkTo("Pages/Public/Breach index")();
74
+ }
75
+
76
+ if (path.startsWith("/breach-details/")) {
77
+ linkTo("Pages/Public/Breach listing")();
78
+ }
79
+
80
+ if (path === "/terms/expiration-offer") {
81
+ linkTo("Pages/Public/Terms/Plus expiration offer")();
82
+ }
83
+
84
+ if (path === "/user/dashboard") {
85
+ linkTo(
86
+ "Pages/Logged in/Dashboard",
87
+ "US user, without Premium, with unresolved scan results, with unresolved breaches",
88
+ )();
89
+ }
90
+
91
+ if (
92
+ path === "/user/dashboard/fix/data-broker-profiles/start-free-scan"
93
+ ) {
94
+ linkTo("Pages/Logged in/Guided resolution/1a. Free scan")();
95
+ }
96
+
97
+ if (
98
+ path ===
99
+ "/user/dashboard/fix/data-broker-profiles/view-data-brokers"
100
+ ) {
101
+ linkTo(
102
+ "Pages/Logged in/Guided resolution/1b. Scan results",
103
+ "With a few unresolved scan results (free)",
104
+ )();
105
+ }
106
+
107
+ if (
108
+ path === "/user/dashboard/fix/data-broker-profiles/manual-remove"
109
+ ) {
110
+ linkTo(
111
+ "Pages/Logged in/Guided resolution/1c. Manually resolve brokers",
112
+ )();
113
+ }
114
+
115
+ if (
116
+ path ===
117
+ "/user/dashboard/fix/high-risk-data-breaches/social-security-number"
118
+ ) {
119
+ linkTo(
120
+ "Pages/Logged in/Guided resolution/2. High-risk data breaches",
121
+ "2a. Social Security Number",
122
+ )();
123
+ }
124
+
125
+ if (
126
+ path === "/user/dashboard/fix/high-risk-data-breaches/credit-card"
127
+ ) {
128
+ linkTo(
129
+ "Pages/Logged in/Guided resolution/2. High-risk data breaches",
130
+ "2b. Credit card",
131
+ )();
132
+ }
133
+
134
+ if (
135
+ path === "/user/dashboard/fix/high-risk-data-breaches/bank-account"
136
+ ) {
137
+ linkTo(
138
+ "Pages/Logged in/Guided resolution/2. High-risk data breaches",
139
+ "2c. Bank account",
140
+ )();
141
+ }
142
+
143
+ if (path === "/user/dashboard/fix/high-risk-data-breaches/pin") {
144
+ linkTo(
145
+ "Pages/Logged in/Guided resolution/2. High-risk data breaches",
146
+ "2d. PIN",
147
+ )();
148
+ }
149
+
150
+ if (path === "/user/dashboard/fix/high-risk-data-breaches/done") {
151
+ linkTo(
152
+ "Pages/Logged in/Guided resolution/2. High-risk data breaches",
153
+ "2e. Done",
154
+ )();
155
+ }
156
+
157
+ if (path === "/user/dashboard/fix/leaked-passwords/passwords") {
158
+ linkTo(
159
+ "Pages/Logged in/Guided resolution/3. Leaked passwords",
160
+ "3a. Passwords",
161
+ )();
162
+ }
163
+
164
+ if (
165
+ path === "/user/dashboard/fix/leaked-passwords/security-questions"
166
+ ) {
167
+ linkTo(
168
+ "Pages/Logged in/Guided resolution/3. Leaked passwords",
169
+ "3b. Security questions",
170
+ )();
171
+ }
172
+
173
+ if (
174
+ path === "/user/dashboard/fix/leaked-passwords/passwords-done" ||
175
+ path ===
176
+ "/user/dashboard/fix/leaked-passwords/security-questions-done"
177
+ ) {
178
+ linkTo(
179
+ "Pages/Logged in/Guided resolution/3. Leaked passwords",
180
+ "3c. Done",
181
+ )();
182
+ }
183
+
184
+ if (path === "/user/dashboard/fix/security-recommendations/phone") {
185
+ linkTo(
186
+ "Pages/Logged in/Guided resolution/4. Security recommendations",
187
+ "4a. Phone number",
188
+ )();
189
+ }
190
+
191
+ if (path === "/user/dashboard/fix/security-recommendations/email") {
192
+ linkTo(
193
+ "Pages/Logged in/Guided resolution/4. Security recommendations",
194
+ "4b. Email address",
195
+ )();
196
+ }
197
+
198
+ if (path === "/user/dashboard/fix/security-recommendations/ip") {
199
+ linkTo(
200
+ "Pages/Logged in/Guided resolution/4. Security recommendations",
201
+ "4c. IP address",
202
+ )();
203
+ }
204
+
205
+ if (path === "/user/dashboard/fix/security-recommendations/done") {
206
+ linkTo(
207
+ "Pages/Logged in/Guided resolution/4. Security recommendations",
208
+ "4d. Done",
209
+ )();
210
+ }
211
+ },
212
+ },
213
+ },
214
+ },
215
+ decorators: [AppDecorator],
216
+ };
217
+
218
+ export default preview;
.vscode/extensions.json ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
3
+ // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
4
+
5
+ // List of extensions which should be recommended for users of this workspace.
6
+ "recommendations": [
7
+ "dbaeumer.vscode-eslint",
8
+ "stylelint.vscode-stylelint",
9
+ "esbenp.prettier-vscode",
10
+ "eamodio.gitlens",
11
+ "Orta.vscode-jest",
12
+ "macabeus.vscode-fluent",
13
+ ],
14
+ // List of extensions recommended by VS Code that should not be recommended for users of this workspace.
15
+ "unwantedRecommendations": []
16
+ }
.vscode/settings.json ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "eslint.enable": true,
3
+ "eslint.format.enable": true,
4
+ "editor.tabSize": 2,
5
+ "editor.formatOnSave": false,
6
+ "editor.formatOnSaveMode": "modificationsIfAvailable",
7
+ "eslint.validate": [
8
+ "javascript"
9
+ ],
10
+ "gitlens.advanced.blame.customArguments": [
11
+ "--ignore-revs-file",
12
+ ".git-blame-ignore-revs"
13
+ ],
14
+ "[css][scss]": {
15
+ "editor.codeActionsOnSave": {
16
+ "source.fixAll.stylelint": "explicit"
17
+ }
18
+ },
19
+ "[javascript][typescript][typescriptreact]": {
20
+ "editor.codeActionsOnSave": {
21
+ "source.fixAll.eslint": "explicit"
22
+ }
23
+ },
24
+ "[javascript][typescript][typescriptreact][markdown][html][css][scss]": {
25
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
26
+ },
27
+ "[fluent]": {
28
+ "files.trimTrailingWhitespace": true
29
+ },
30
+ }
CNAME ADDED
@@ -0,0 +1 @@
 
 
1
+ abdulelathmanhgwaith.link
CODE_OF_CONDUCT.md ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Community Participation Guidelines
2
+
3
+ This repository is governed by Mozilla's code of conduct and etiquette guidelines.
4
+ For more details, please read the
5
+ [Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/).
6
+
7
+ ## How to Report
8
+ For more information on how to report violations of the Community Participation Guidelines, please read our '[How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/)' page.
9
+
10
+ <!--
11
+ ## Project Specific Etiquette
12
+
13
+ In some cases, there will be additional project etiquette i.e.: (https://bugzilla.mozilla.org/page.cgi?id=etiquette.html).
14
+ Please update for your project.
15
+ -->
LICENSE ADDED
@@ -0,0 +1,373 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Mozilla Public License Version 2.0
2
+ ==================================
3
+
4
+ 1. Definitions
5
+ --------------
6
+
7
+ 1.1. "Contributor"
8
+ means each individual or legal entity that creates, contributes to
9
+ the creation of, or owns Covered Software.
10
+
11
+ 1.2. "Contributor Version"
12
+ means the combination of the Contributions of others (if any) used
13
+ by a Contributor and that particular Contributor's Contribution.
14
+
15
+ 1.3. "Contribution"
16
+ means Covered Software of a particular Contributor.
17
+
18
+ 1.4. "Covered Software"
19
+ means Source Code Form to which the initial Contributor has attached
20
+ the notice in Exhibit A, the Executable Form of such Source Code
21
+ Form, and Modifications of such Source Code Form, in each case
22
+ including portions thereof.
23
+
24
+ 1.5. "Incompatible With Secondary Licenses"
25
+ means
26
+
27
+ (a) that the initial Contributor has attached the notice described
28
+ in Exhibit B to the Covered Software; or
29
+
30
+ (b) that the Covered Software was made available under the terms of
31
+ version 1.1 or earlier of the License, but not also under the
32
+ terms of a Secondary License.
33
+
34
+ 1.6. "Executable Form"
35
+ means any form of the work other than Source Code Form.
36
+
37
+ 1.7. "Larger Work"
38
+ means a work that combines Covered Software with other material, in
39
+ a separate file or files, that is not Covered Software.
40
+
41
+ 1.8. "License"
42
+ means this document.
43
+
44
+ 1.9. "Licensable"
45
+ means having the right to grant, to the maximum extent possible,
46
+ whether at the time of the initial grant or subsequently, any and
47
+ all of the rights conveyed by this License.
48
+
49
+ 1.10. "Modifications"
50
+ means any of the following:
51
+
52
+ (a) any file in Source Code Form that results from an addition to,
53
+ deletion from, or modification of the contents of Covered
54
+ Software; or
55
+
56
+ (b) any new file in Source Code Form that contains any Covered
57
+ Software.
58
+
59
+ 1.11. "Patent Claims" of a Contributor
60
+ means any patent claim(s), including without limitation, method,
61
+ process, and apparatus claims, in any patent Licensable by such
62
+ Contributor that would be infringed, but for the grant of the
63
+ License, by the making, using, selling, offering for sale, having
64
+ made, import, or transfer of either its Contributions or its
65
+ Contributor Version.
66
+
67
+ 1.12. "Secondary License"
68
+ means either the GNU General Public License, Version 2.0, the GNU
69
+ Lesser General Public License, Version 2.1, the GNU Affero General
70
+ Public License, Version 3.0, or any later versions of those
71
+ licenses.
72
+
73
+ 1.13. "Source Code Form"
74
+ means the form of the work preferred for making modifications.
75
+
76
+ 1.14. "You" (or "Your")
77
+ means an individual or a legal entity exercising rights under this
78
+ License. For legal entities, "You" includes any entity that
79
+ controls, is controlled by, or is under common control with You. For
80
+ purposes of this definition, "control" means (a) the power, direct
81
+ or indirect, to cause the direction or management of such entity,
82
+ whether by contract or otherwise, or (b) ownership of more than
83
+ fifty percent (50%) of the outstanding shares or beneficial
84
+ ownership of such entity.
85
+
86
+ 2. License Grants and Conditions
87
+ --------------------------------
88
+
89
+ 2.1. Grants
90
+
91
+ Each Contributor hereby grants You a world-wide, royalty-free,
92
+ non-exclusive license:
93
+
94
+ (a) under intellectual property rights (other than patent or trademark)
95
+ Licensable by such Contributor to use, reproduce, make available,
96
+ modify, display, perform, distribute, and otherwise exploit its
97
+ Contributions, either on an unmodified basis, with Modifications, or
98
+ as part of a Larger Work; and
99
+
100
+ (b) under Patent Claims of such Contributor to make, use, sell, offer
101
+ for sale, have made, import, and otherwise transfer either its
102
+ Contributions or its Contributor Version.
103
+
104
+ 2.2. Effective Date
105
+
106
+ The licenses granted in Section 2.1 with respect to any Contribution
107
+ become effective for each Contribution on the date the Contributor first
108
+ distributes such Contribution.
109
+
110
+ 2.3. Limitations on Grant Scope
111
+
112
+ The licenses granted in this Section 2 are the only rights granted under
113
+ this License. No additional rights or licenses will be implied from the
114
+ distribution or licensing of Covered Software under this License.
115
+ Notwithstanding Section 2.1(b) above, no patent license is granted by a
116
+ Contributor:
117
+
118
+ (a) for any code that a Contributor has removed from Covered Software;
119
+ or
120
+
121
+ (b) for infringements caused by: (i) Your and any other third party's
122
+ modifications of Covered Software, or (ii) the combination of its
123
+ Contributions with other software (except as part of its Contributor
124
+ Version); or
125
+
126
+ (c) under Patent Claims infringed by Covered Software in the absence of
127
+ its Contributions.
128
+
129
+ This License does not grant any rights in the trademarks, service marks,
130
+ or logos of any Contributor (except as may be necessary to comply with
131
+ the notice requirements in Section 3.4).
132
+
133
+ 2.4. Subsequent Licenses
134
+
135
+ No Contributor makes additional grants as a result of Your choice to
136
+ distribute the Covered Software under a subsequent version of this
137
+ License (see Section 10.2) or under the terms of a Secondary License (if
138
+ permitted under the terms of Section 3.3).
139
+
140
+ 2.5. Representation
141
+
142
+ Each Contributor represents that the Contributor believes its
143
+ Contributions are its original creation(s) or it has sufficient rights
144
+ to grant the rights to its Contributions conveyed by this License.
145
+
146
+ 2.6. Fair Use
147
+
148
+ This License is not intended to limit any rights You have under
149
+ applicable copyright doctrines of fair use, fair dealing, or other
150
+ equivalents.
151
+
152
+ 2.7. Conditions
153
+
154
+ Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
155
+ in Section 2.1.
156
+
157
+ 3. Responsibilities
158
+ -------------------
159
+
160
+ 3.1. Distribution of Source Form
161
+
162
+ All distribution of Covered Software in Source Code Form, including any
163
+ Modifications that You create or to which You contribute, must be under
164
+ the terms of this License. You must inform recipients that the Source
165
+ Code Form of the Covered Software is governed by the terms of this
166
+ License, and how they can obtain a copy of this License. You may not
167
+ attempt to alter or restrict the recipients' rights in the Source Code
168
+ Form.
169
+
170
+ 3.2. Distribution of Executable Form
171
+
172
+ If You distribute Covered Software in Executable Form then:
173
+
174
+ (a) such Covered Software must also be made available in Source Code
175
+ Form, as described in Section 3.1, and You must inform recipients of
176
+ the Executable Form how they can obtain a copy of such Source Code
177
+ Form by reasonable means in a timely manner, at a charge no more
178
+ than the cost of distribution to the recipient; and
179
+
180
+ (b) You may distribute such Executable Form under the terms of this
181
+ License, or sublicense it under different terms, provided that the
182
+ license for the Executable Form does not attempt to limit or alter
183
+ the recipients' rights in the Source Code Form under this License.
184
+
185
+ 3.3. Distribution of a Larger Work
186
+
187
+ You may create and distribute a Larger Work under terms of Your choice,
188
+ provided that You also comply with the requirements of this License for
189
+ the Covered Software. If the Larger Work is a combination of Covered
190
+ Software with a work governed by one or more Secondary Licenses, and the
191
+ Covered Software is not Incompatible With Secondary Licenses, this
192
+ License permits You to additionally distribute such Covered Software
193
+ under the terms of such Secondary License(s), so that the recipient of
194
+ the Larger Work may, at their option, further distribute the Covered
195
+ Software under the terms of either this License or such Secondary
196
+ License(s).
197
+
198
+ 3.4. Notices
199
+
200
+ You may not remove or alter the substance of any license notices
201
+ (including copyright notices, patent notices, disclaimers of warranty,
202
+ or limitations of liability) contained within the Source Code Form of
203
+ the Covered Software, except that You may alter any license notices to
204
+ the extent required to remedy known factual inaccuracies.
205
+
206
+ 3.5. Application of Additional Terms
207
+
208
+ You may choose to offer, and to charge a fee for, warranty, support,
209
+ indemnity or liability obligations to one or more recipients of Covered
210
+ Software. However, You may do so only on Your own behalf, and not on
211
+ behalf of any Contributor. You must make it absolutely clear that any
212
+ such warranty, support, indemnity, or liability obligation is offered by
213
+ You alone, and You hereby agree to indemnify every Contributor for any
214
+ liability incurred by such Contributor as a result of warranty, support,
215
+ indemnity or liability terms You offer. You may include additional
216
+ disclaimers of warranty and limitations of liability specific to any
217
+ jurisdiction.
218
+
219
+ 4. Inability to Comply Due to Statute or Regulation
220
+ ---------------------------------------------------
221
+
222
+ If it is impossible for You to comply with any of the terms of this
223
+ License with respect to some or all of the Covered Software due to
224
+ statute, judicial order, or regulation then You must: (a) comply with
225
+ the terms of this License to the maximum extent possible; and (b)
226
+ describe the limitations and the code they affect. Such description must
227
+ be placed in a text file included with all distributions of the Covered
228
+ Software under this License. Except to the extent prohibited by statute
229
+ or regulation, such description must be sufficiently detailed for a
230
+ recipient of ordinary skill to be able to understand it.
231
+
232
+ 5. Termination
233
+ --------------
234
+
235
+ 5.1. The rights granted under this License will terminate automatically
236
+ if You fail to comply with any of its terms. However, if You become
237
+ compliant, then the rights granted under this License from a particular
238
+ Contributor are reinstated (a) provisionally, unless and until such
239
+ Contributor explicitly and finally terminates Your grants, and (b) on an
240
+ ongoing basis, if such Contributor fails to notify You of the
241
+ non-compliance by some reasonable means prior to 60 days after You have
242
+ come back into compliance. Moreover, Your grants from a particular
243
+ Contributor are reinstated on an ongoing basis if such Contributor
244
+ notifies You of the non-compliance by some reasonable means, this is the
245
+ first time You have received notice of non-compliance with this License
246
+ from such Contributor, and You become compliant prior to 30 days after
247
+ Your receipt of the notice.
248
+
249
+ 5.2. If You initiate litigation against any entity by asserting a patent
250
+ infringement claim (excluding declaratory judgment actions,
251
+ counter-claims, and cross-claims) alleging that a Contributor Version
252
+ directly or indirectly infringes any patent, then the rights granted to
253
+ You by any and all Contributors for the Covered Software under Section
254
+ 2.1 of this License shall terminate.
255
+
256
+ 5.3. In the event of termination under Sections 5.1 or 5.2 above, all
257
+ end user license agreements (excluding distributors and resellers) which
258
+ have been validly granted by You or Your distributors under this License
259
+ prior to termination shall survive termination.
260
+
261
+ ************************************************************************
262
+ * *
263
+ * 6. Disclaimer of Warranty *
264
+ * ------------------------- *
265
+ * *
266
+ * Covered Software is provided under this License on an "as is" *
267
+ * basis, without warranty of any kind, either expressed, implied, or *
268
+ * statutory, including, without limitation, warranties that the *
269
+ * Covered Software is free of defects, merchantable, fit for a *
270
+ * particular purpose or non-infringing. The entire risk as to the *
271
+ * quality and performance of the Covered Software is with You. *
272
+ * Should any Covered Software prove defective in any respect, You *
273
+ * (not any Contributor) assume the cost of any necessary servicing, *
274
+ * repair, or correction. This disclaimer of warranty constitutes an *
275
+ * essential part of this License. No use of any Covered Software is *
276
+ * authorized under this License except under this disclaimer. *
277
+ * *
278
+ ************************************************************************
279
+
280
+ ************************************************************************
281
+ * *
282
+ * 7. Limitation of Liability *
283
+ * -------------------------- *
284
+ * *
285
+ * Under no circumstances and under no legal theory, whether tort *
286
+ * (including negligence), contract, or otherwise, shall any *
287
+ * Contributor, or anyone who distributes Covered Software as *
288
+ * permitted above, be liable to You for any direct, indirect, *
289
+ * special, incidental, or consequential damages of any character *
290
+ * including, without limitation, damages for lost profits, loss of *
291
+ * goodwill, work stoppage, computer failure or malfunction, or any *
292
+ * and all other commercial damages or losses, even if such party *
293
+ * shall have been informed of the possibility of such damages. This *
294
+ * limitation of liability shall not apply to liability for death or *
295
+ * personal injury resulting from such party's negligence to the *
296
+ * extent applicable law prohibits such limitation. Some *
297
+ * jurisdictions do not allow the exclusion or limitation of *
298
+ * incidental or consequential damages, so this exclusion and *
299
+ * limitation may not apply to You. *
300
+ * *
301
+ ************************************************************************
302
+
303
+ 8. Litigation
304
+ -------------
305
+
306
+ Any litigation relating to this License may be brought only in the
307
+ courts of a jurisdiction where the defendant maintains its principal
308
+ place of business and such litigation shall be governed by laws of that
309
+ jurisdiction, without reference to its conflict-of-law provisions.
310
+ Nothing in this Section shall prevent a party's ability to bring
311
+ cross-claims or counter-claims.
312
+
313
+ 9. Miscellaneous
314
+ ----------------
315
+
316
+ This License represents the complete agreement concerning the subject
317
+ matter hereof. If any provision of this License is held to be
318
+ unenforceable, such provision shall be reformed only to the extent
319
+ necessary to make it enforceable. Any law or regulation which provides
320
+ that the language of a contract shall be construed against the drafter
321
+ shall not be used to construe this License against a Contributor.
322
+
323
+ 10. Versions of the License
324
+ ---------------------------
325
+
326
+ 10.1. New Versions
327
+
328
+ Mozilla Foundation is the license steward. Except as provided in Section
329
+ 10.3, no one other than the license steward has the right to modify or
330
+ publish new versions of this License. Each version will be given a
331
+ distinguishing version number.
332
+
333
+ 10.2. Effect of New Versions
334
+
335
+ You may distribute the Covered Software under the terms of the version
336
+ of the License under which You originally received the Covered Software,
337
+ or under the terms of any subsequent version published by the license
338
+ steward.
339
+
340
+ 10.3. Modified Versions
341
+
342
+ If you create software not governed by this License, and you want to
343
+ create a new license for such software, you may create and use a
344
+ modified version of this License if you rename the license and remove
345
+ any references to the name of the license steward (except to note that
346
+ such modified license differs from this License).
347
+
348
+ 10.4. Distributing Source Code Form that is Incompatible With Secondary
349
+ Licenses
350
+
351
+ If You choose to distribute Source Code Form that is Incompatible With
352
+ Secondary Licenses under the terms of this version of the License, the
353
+ notice described in Exhibit B of this License must be attached.
354
+
355
+ Exhibit A - Source Code Form License Notice
356
+ -------------------------------------------
357
+
358
+ This Source Code Form is subject to the terms of the Mozilla Public
359
+ License, v. 2.0. If a copy of the MPL was not distributed with this
360
+ file, You can obtain one at http://mozilla.org/MPL/2.0/.
361
+
362
+ If it is not possible or desirable to put the notice in a particular
363
+ file, then You may include the notice in a location (such as a LICENSE
364
+ file in a relevant directory) where a recipient would be likely to look
365
+ for such a notice.
366
+
367
+ You may add additional accurate notices of copyright ownership.
368
+
369
+ Exhibit B - "Incompatible With Secondary Licenses" Notice
370
+ ---------------------------------------------------------
371
+
372
+ This Source Code Form is "Incompatible With Secondary Licenses", as
373
+ defined by the Mozilla Public License, v. 2.0.
README.md ADDED
@@ -0,0 +1,323 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Firefox Monitor Server
2
+
3
+ [![Functional Test Suite](https://github.com/mozilla/blurts-server/actions/workflows/functional_tests_cron.yml/badge.svg)](https://github.com/mozilla/blurts-server/actions/workflows/functional_tests_cron.yml)
4
+
5
+ ## Summary
6
+
7
+ Firefox Monitor notifies users when their credentials have been compromised in a data breach.
8
+
9
+ This code is for the monitor.mozilla.org service & website.
10
+
11
+ Breach data is powered by [haveibeenpwned.com](https://haveibeenpwned.com/).
12
+
13
+ See the [Have I Been Pwned about page](https://haveibeenpwned.com/About) for
14
+ the "what" and "why" of data breach alerts.
15
+
16
+ ## Architecture
17
+
18
+ ![Image of Monitor architecture](docs/monitor-architecture.png "Firefox Monitor")
19
+
20
+ ## Development
21
+
22
+ ### Requirements
23
+
24
+ - [Volta](https://volta.sh/) (installs the correct version of Node and npm)
25
+ - [Postgres](https://www.postgresql.org/) | Note: On a Mac, we recommend downloading the [Postgres.app](https://postgresapp.com/) instead.
26
+ - [Python](https://www.python.org/downloads/) | [With Homebrew](https://docs.brew.sh/Homebrew-and-Python)
27
+ - [k6](https://grafana.com/docs/k6/latest/set-up/install-k6/) | k6 load testing tool
28
+
29
+ ### Code style
30
+
31
+ Linting and formatting is enforced via [ESLint](https://eslint.org/) and [Stylelint](https://stylelint.io/) for JS and CSS. Both are installed as dev-dependencies and can be run with `npm run lint`. A push to origin will also trigger linting.
32
+
33
+ ESLint rules are based on [eslint-config-standard](https://github.com/standard/eslint-config-standard). To fix all auto-fixable problems, run `npx eslint . --fix`
34
+
35
+ Stylelint rules are based on [stylelint-config-standard](https://github.com/stylelint/stylelint-config-standard). To fix all auto-fixable problems, run `npx stylelint public/css/ --fix`
36
+
37
+ ### GIT
38
+
39
+ We track commits that are largely style/formatting via `.git-blame-ignore-revs`. This allows Git Blame to ignore the format commit author and show the original code author. In order to enable this in GitLens, add the following to VS Code `settings.json`:
40
+
41
+ ```
42
+ "gitlens.advanced.blame.customArguments": [
43
+ "--ignore-revs-file",
44
+ ".git-blame-ignore-revs"
45
+ ],
46
+ ```
47
+
48
+ ### Database
49
+
50
+ To create the database tables you have two options: manually, or using docker-compose
51
+
52
+ #### Manual setup
53
+
54
+ 1. Create the `blurts` database:
55
+
56
+ ```sh
57
+ createdb blurts
58
+ createdb test-blurts # for tests
59
+ ```
60
+
61
+ 2. Update the `DATABASE_URL` value in your `.env.local` (see step 3 under
62
+ "Install") file with your local db credentials:
63
+
64
+ ```
65
+ DATABASE_URL="postgres://<username>:<password>@localhost:<port>/blurts"
66
+ ```
67
+
68
+ 3. Run the migrations:
69
+
70
+ ```
71
+ npm run db:migrate
72
+ ```
73
+
74
+ #### Via docker-compose
75
+
76
+ This will automatically provision the databases 'blurts' and 'test-blurts', and a user 'blurts' with password 'blurts'. The connection string in your .env.local file should be: "postgres://blurts:blurts@localhost:5432/blurts"
77
+
78
+ 1. Ensure that you have an up-to-date .env.local file.
79
+
80
+ ```$sh
81
+ cp .env.local.example .env.local
82
+ ```
83
+
84
+ 2. Start docker containers (this will stand up all services defined, including pubsub)
85
+
86
+ ```sh
87
+ docker compose --env-file .env.local up -d
88
+ ```
89
+
90
+ 3. To tear down (this will delete stored data):
91
+
92
+ ```sh
93
+ docker compose --env-file .env.local down
94
+ ```
95
+
96
+ ### Install
97
+
98
+ 1. Clone and change to the directory:
99
+
100
+ ```sh
101
+ git clone https://github.com/mozilla/blurts-server.git
102
+ cd blurts-server
103
+ ```
104
+
105
+ 2. Install dependencies:
106
+
107
+ ```sh
108
+ npm install
109
+ ```
110
+
111
+ 3. Copy the `.env.local.example` file to `.env.local`:
112
+
113
+ ```sh
114
+ cp .env.local.example .env.local
115
+ ```
116
+
117
+ 4. Install fluent linter (requires Python)
118
+
119
+ ```sh
120
+ pip install -r .github/requirements.txt
121
+
122
+ OR
123
+
124
+ pip3 install -r .github/requirements.txt
125
+ ```
126
+
127
+ 5. Generate required Glean files (needs re-ran anytime Glean `.yaml` files are updated):
128
+
129
+ ```sh
130
+ npm run build-glean
131
+ ```
132
+
133
+ 6. Generate required Nimbus files (needs re-ran anytime Nimbus' `config/nimbus.yaml` file is updated):
134
+
135
+ ```sh
136
+ npm run build-nimbus
137
+ ```
138
+
139
+ 7. Create location data: Running the script manually is only needed for local development. The location data is being used in the onboarding exposures scan for autocompleting the “City and state” input.
140
+
141
+ ```sh
142
+ npm run create-location-data
143
+ ```
144
+
145
+ 8. Ensure that you have the right `env` variables/keys set in your `.env.local` file. You can retrieve the variables from the Firefox Monitor 1Password Vault, or through [Magic-Wormhole](https://magic-wormhole.readthedocs.io/en/latest/), by asking one of the our engineers.
146
+
147
+ ### Run
148
+
149
+ 1. To run the server similar to production using a build phase, which includes minified and bundled assets:
150
+
151
+ ```sh
152
+ npm start
153
+ ```
154
+
155
+ **_OR_**
156
+
157
+ Run in "dev mode" with:
158
+
159
+ ```sh
160
+ npm run dev
161
+ ```
162
+
163
+ 2. You may receive the error `Required environment variable was not set`. If this is the case, get the required env var(s) from another team member or ask in #fx-monitor-engineering. Otherwise, if the server started successfully, navigate to [localhost:6060](http://localhost:6060/)
164
+
165
+ ### PubSub
166
+
167
+ Monitor uses GCP PubSub for processing incoming breach data, this can be tested locally using an emulator: <https://cloud.google.com/pubsub/docs/emulator>
168
+
169
+ You can run the emulator manually or via docker-compose.
170
+
171
+ #### Manual Setup
172
+
173
+ ##### Run the GCP PubSub emulator
174
+
175
+ ```sh
176
+ gcloud beta emulators pubsub start --project=your-project-name
177
+ ```
178
+
179
+ (Set `your-project-name` as the value for `GCP_PUBSUB_PROJECT_ID` in your `.env.local`.)
180
+
181
+ #### Docker Compose Setup
182
+
183
+ This will automatically provision a pubsub topic named 'hibp-breaches' with a subscription named 'hibp-cron'.
184
+
185
+ 1. Ensure that you have an up-to-date .env.local file.
186
+
187
+ ```$sh
188
+ cp .env.local.example .env.local
189
+ ```
190
+
191
+ 2. Start docker containers (this will stand up all services defined, including postgres)
192
+
193
+ ```sh
194
+ docker compose --env-file .env.local up -d
195
+ ```
196
+
197
+ ### In a different shell, set the environment to point at the emulator and run Monitor in dev mode
198
+
199
+ ```sh
200
+ $(gcloud beta emulators pubsub env-init)
201
+ npm run dev
202
+ ```
203
+
204
+ ### Incoming WebHook requests from HIBP will be of the form
205
+
206
+ ```sh
207
+ curl -d '{ "breachName": "000webhost", "hashPrefix": "test", "hashSuffixes": ["test"] }' \
208
+ -H "Authorization: Bearer unsafe-default-token-for-dev" \
209
+ http://localhost:6060/api/v1/hibp/notify
210
+ ```
211
+
212
+ This emulates HIBP notifying our API that a new breach was found. Our API will
213
+ then add it to the (emulated) pubsub queue.
214
+
215
+ You can also use this request with staging credentials and endpoint to manually trigger alerts in the staging environment. For instructions on how to generate the hashPrefix and hashSuffix values, see [instructions below](#testing-the-breach-alerts-cron-job-locally).
216
+
217
+ ### This pubsub queue will be consumed by this cron job, which is responsible for looking up and emailing impacted users
218
+
219
+ ```sh
220
+ NODE_ENV="development" npm run dev:cron:breach-alerts
221
+ ```
222
+
223
+ ### Emails
224
+
225
+ Monitor generates multiple emails that get sent to subscribers. To preview or test-send these emails see documentation [here](docs/monitor-emails.md).
226
+
227
+ ### Mozilla accounts ("FxA", formerly known as Firefox accounts)
228
+
229
+ The repo comes with a development FxA oauth app pre-configured in `.env`, which
230
+ should work fine running the app on <http://localhost:6060>. You'll need to get
231
+ the `OAUTH_CLIENT_SECRET` value from a team member or someone in #fxmonitor-engineering.
232
+
233
+ ## Testing
234
+
235
+ The unit test suite can be run via `npm test`.
236
+
237
+ At the beginning of a test suite run, the `test-blurts` database will be populated with test tables and seed data found in `src/db/seeds/`
238
+
239
+ At the end of a test suite, coverage info will be sent to [Coveralls](https://coveralls.io/) to assess coverage changes and provide a neat badge. To upload coverage locally, you need a root `.coveralls.yml` which contains a token – get this from another member of the Monitor team.
240
+
241
+ The [functional tests](https://github.com/mozilla/blurts-server/src/functional-tests) use Playwright and can be run via `npm run functional-tests`.
242
+
243
+ #### Test Firefox Integration
244
+
245
+ _**TODO:** the following functionality is disabled but the instructions are left here for posterity._
246
+
247
+ Firefox's internal about:protections page ("Protections Dashboard") fetches and
248
+ displays breach stats for Firefox users who are signed into their FXA.
249
+
250
+ To test this part of Monitor:
251
+
252
+ 1. [Set a Firefox profile to use the staging Firefox Accounts
253
+ server.](https://mozilla.github.io/ecosystem-platform/docs/process/using-the-staging-environment#working-with-staging-firefox-accounts)
254
+ 2. In the same profile, go to about:config and replace [all
255
+ `https://monitor.firefox.com`
256
+ values](https://searchfox.org/mozilla-central/search?q=monitor.firefox.com&path=browser/app/profile/firefox.js) with `http://localhost:6060`
257
+ 3. Restart Firefox with that profile.
258
+ 4. Go to `about:protections`
259
+ 5. Everything should be using your localhost instance of Monitor.
260
+
261
+ #### Load testing
262
+
263
+ k6 is used for load testing.
264
+
265
+ See <https://grafana.com/docs/k6/latest/get-started/running-k6/> for more information.
266
+
267
+ ##### HIBP breach alerts
268
+
269
+ To test the HIBP breach alerts endpoint, use:
270
+
271
+ ```sh
272
+ export SERVER_URL=...
273
+ export HIBP_NOTIFY_TOKEN=...
274
+ npm run loadtest:hibp-webhook
275
+ ```
276
+
277
+ You can customise the number of requests to send in parallel ("virtual users") by setting the
278
+ [`K6_VUS`](https://grafana.com/docs/k6/latest/using-k6/k6-options/reference/#vus) environment
279
+ variable (default 1000), and for how long to send those requests by setting the
280
+ [`K6_DURATION`](https://grafana.com/docs/k6/latest/using-k6/k6-options/reference/#duration)
281
+ environment variable (default 30s).
282
+
283
+ You can also enforce the alert being sent for a specific email address via the
284
+ `LOADTEST_BREACHED_EMAIL` environment variable.
285
+
286
+ #### Testing the Breach Alerts cron job locally
287
+
288
+ 1. Ensure SMTP_URL environment variable is unset; this will log to JSON instead of attempting to send an email
289
+ 1. Follow instructions to start blurts server locally, including the database and emulated GCP PubSub topic
290
+ 1. Create a new account, and note the email address you used for the next step
291
+ 1. Update the email address below and paste into your terminal
292
+
293
+ ```sh
294
+ # Replace with whatever email address you used above, or omit and
295
+ # export env var first to persist between runs
296
+ # `export HIBP_TEST_EAMIL=replace-me-email@example.com`
297
+ HIBP_TEST_EMAIL="replace-me-email@example.com"; \
298
+ HASH=$(echo -n "$HIBP_TEST_EMAIL" | sha1sum | awk '{print $1}'); \
299
+ PREFIX=${HASH:0:7}; \
300
+ SUFFIX=${HASH:7}; \
301
+ curl -d "{\"breachName\": \"000webhost\", \"hashPrefix\": \"$PREFIX\", \"hashSuffixes\": [\"$SUFFIX\"]}" \
302
+ -H "Authorization: Bearer unsafe-default-token-for-dev" \
303
+ -H "Content-Type: application/json" \
304
+ http://localhost:6060/api/v1/hibp/notify
305
+ ```
306
+
307
+ Note that the database must be seeded with breaches or else this request will not trigger emails due to validation error. The breachName must match the name of a breach in the database. Query the `breaches` table in the database for additional breach names to test more than once for the same email address (a user will be notified for a breach only once). Alternatively you can delete the record that was created in the `email_notifications` table to retest.
308
+
309
+ ## Localization
310
+
311
+ All text that is visible to the user is defined in [Fluent](https://projectfluent.org/) files inside `/locales/en/` and `/locales-pending/`. After strings get added to files in the former directory on our `main` branch, they will be made available to our volunteer localizers via Pontoon, Mozilla's localization platform. Be sure to reference the [localization documentation](https://mozilla-l10n.github.io/documentation/localization/dev_best_practices.html) for best practices. It's best to only move the strings to `/locales/en/` when they are more-or-less final and ready for localization. Your PR should be automatically tagged with a reviewer from the [Mozilla L10n team](https://wiki.mozilla.org/L10n:Mozilla_Team) to approve your request.
312
+
313
+ You can check translation status via the [Pontoon site](https://pontoon.mozilla.org/projects/firefox-monitor-website/). After strings have been localized, [a pull request](https://github.com/mozilla/blurts-server/pulls?q=is%3Apr+label%3Al10n+) with the updated strings is automatically opened against our repository. Please be mindful that Mozilla localizers are volunteers, and translations come from different locales at different times – usually after a week or more.
314
+
315
+ To use the strings in code, you need to obtain a `ReactLocalization` instance, which selects the right version of your desired string for the user. How to do that depends on where your code runs: in tests, in a cron job, in a server component, or on the client-side. Generally, you will import a `getL10n` function from one of the modules in `/src/app/functions/l10n/`, except for [Client Components](https://nextjs.org/docs/app/building-your-application/rendering/client-components), which use [the `useL10n` hook](./src/app/hooks/l10n.ts). Look at existing code for inspiration.
316
+
317
+ ## Preview Deployment
318
+
319
+ We use GCP Cloudrun for dev review – official stage and production apps are built by the Dockerfile and Github Actions. Everything that is merged into `main` will deploy automatically to stage. The ADR for preview deployment can be found [here](https://github.com/mozilla/blurts-server/blob/main/docs/adr/0008-preview-deployment.md)
320
+
321
+ _**TODO:** add full deploy process similar to Relay_
322
+
323
+ _**TODO:** consider whether we can re-enable Heroku Review Apps_
google3c147320b7d46152.html ADDED
@@ -0,0 +1 @@
 
 
1
+ google-site-verification: google3c147320b7d46152.html