mailslurp-examples - firebase-examples

https://github.com/mailslurp/examples

Table of Contents

firebase-examples/vite.config.js

import { defineConfig } from "vite";

export default defineConfig({
  base: "/",
  build: {
    rollupOptions: {
      input: ["index.html", "email-link.html"],
    },
  },
});

firebase-examples/tsconfig.json

{
  "compilerOptions": {
    "target": "ES6",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  }
}

firebase-examples/package.json

{
  "name": "firebase-email-link-test",
  "version": "1.0.0",
  "description": "Examples for testing Firebase authentication using MailSlurp",
  "engines": {
    "npm": ">=9.0.0 <10.0.0",
    "node": ">=18.0.0 <=20.0.0"
  },
  "devDependencies": {
    "@swc/core": "^1.3.100",
    "@types/jsrsasign": "^10.5.8",
    "chromedriver": "^119.0.1",
    "firebase-tools": "^13.0.1",
    "geckodriver": "^4.2.1",
    "mailslurp-client": "^15.17.4",
    "nightwatch": "^2.6.23",
    "prettier": "^3.1.0",
    "start-server-and-test": "^2.0.3",
    "ts-node": "^10.9.2",
    "typescript": "^5.1.6",
    "vite": "^4.4.9"
  },
  "scripts": {
    "dev": "vite",
    "start": "vite",
    "build": "vite build",
    "format": "prettier --write .",
    "emulator": "firebase emulators:start",
    "deploy": "firebase deploy",
    "start-server": "npm start",
    "test": "nightwatch ./nightwatch",
    "test-ci": "start-server-and-test start-server http://localhost:5173 test"
  },
  "dependencies": {
    "firebase": "^10.7.1",
    "jsrsasign": "^10.9.0"
  }
}

firebase-examples/nightwatch.conf.js

//<gen>firebase-nightwatch-conf
module.exports = {
  src_folders: ["test", "nightwatch"],
  test_settings: {
    default: {
      desiredCapabilities: {
        browserName: "firefox",
      },
      webdriver: {
        start_process: true,
        server_path: "./node_modules/.bin/geckodriver",
      },
    },
  },
};
//</gen>

firebase-examples/main.css

/**
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

html,
body {
  font-family: "Roboto", "Helvetica", sans-serif;
  background-color: #f5f5f5;
}
a {
  text-decoration: none;
}
li a {
  text-decoration: underline;
  color: #0288d1;
}
.mdl-card {
  overflow: visible;
}
.grecaptcha-logo {
  background-color: white;
}
.mdl-grid {
  max-width: 1024px;
  margin: auto;
}
.mdl-layout__header-row {
  padding: 0;
}
.quickstart-user-details-container,
.user-details-container {
  margin-top: 20px;
  line-height: 25px;
}
#quickstart-sign-in-status,
#sign-in-status,
#quickstart-tenant-id {
  font-weight: bold;
}
pre {
  overflow-x: scroll;
  line-height: 18px;
}
code {
  white-space: pre-wrap;
  word-break: break-all;
}
h3 {
  background: url("firebase-logo.png") no-repeat;
  background-size: 40px;
  padding-left: 50px;
  color: white;
}
.close-icon {
  cursor: pointer;
  float: right;
  width: 20px;
}
.gcip-heading {
  background: none;
  padding-left: 10px;
}
#verification-code-form {
  display: none;
}
#recaptcha-container {
  margin-top: 10px;
  margin-bottom: 20px;
}
#verify-code-button,
#cancel-verify-code-button {
  margin-left: 20px;
}
#sign-out-button {
  display: none;
}
#sign-in-card {
  z-index: 2;
}
.recaptcha-container {
  transform: scale(0.9);
  transform-origin: 0 0;
  -webkit-transform: scale(0.9);
  -webkit-transform-origin: 0 0;
}
.mfa-info-list-item {
  margin-bottom: 0px;
  margin-top: 0px;
  width: 300px;
}
.mdl-selectfield {
  height: 27px;
  padding-bottom: 20px;
  padding-top: 20px;
  width: 200px;
}
.mdl-selectfield__select {
  background: linear-gradient(45deg, transparent 50%, rgba(0, 0, 0, 0.26) 50%),
    linear-gradient(135deg, rgba(0, 0, 0, 0.26) 50%, transparent 50%),
    linear-gradient(to right, transparent, transparent);
  background-color: transparent;
  background-position:
    calc(100% - 10px) calc(1em - 4px),
    calc(100% - 5px) calc(1em - 4px),
    100% 0;
  background-repeat: no-repeat;
  background-size:
    5px 5px,
    5px 5px;
  border-color: rgba(0, 0, 0, 0.12);
  border-radius: 0;
  border-style: solid;
  border-width: 0 0 1px 0;
  box-shadow: none;
  box-sizing: border-box;
  height: 26px;
  line-height: 18px;
  margin: 0;
  outline: none !important;
  padding-bottom: 4px;
  padding-top: 4px;
  width: 100%;
  -moz-appearance: none;
  -moz-box-sizing: border-box;
  -webkit-appearance: none;
  -webkit-box-sizing: border-box;
}
.mdl-selectfield__label {
  color: rgba(0, 0, 0, 0.26);
  display: block;
  font-size: 16px;
  left: 0;
  overflow: hidden;
  pointer-events: none;
  position: relative;
  text-align: left;
  top: -23px;
  transition-duration: 0.2s;
  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
  white-space: nowrap;
  width: 100%;
}
.mdl-selectfield__label.is-active {
  color: rgb(255, 152, 0);
  font-size: 12px;
  top: -40px;
}
.blinking {
  animation: blinkingText 1.2s infinite;
}
@keyframes blinkingText {
  0% {
    color: #ff0000;
  }
  49% {
    color: transparent;
  }
  50% {
    color: transparent;
  }
  99% {
    color: #ff0000;
  }
  100% {
    color: #ff0000;
  }
}
#quickstart-tenant-card {
  min-height: 500px;
}
#quickstart-tenant-modal-title {
  margin: 0px;
  padding: 2px;
  text-align: center;
}

firebase-examples/index.html

<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>MailSlurp Firebase auth example</title>
    <link
      rel="stylesheet"
      href="https://code.getmdl.io/1.1.3/material.orange-indigo.min.css"
    />
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/icon?family=Material+Icons"
    />
    <script defer src="https://code.getmdl.io/1.1.3/material.min.js"></script>
    <link rel="stylesheet" href="main.css" />
  </head>
  <body>
    <div class="demo-layout mdl-layout mdl-js-layout mdl-layout--fixed-header">
      <header
        class="mdl-layout__header mdl-color-text--white mdl-color--light-blue-700"
      >
        <div class="mdl-cell mdl-cell--12-col mdl-cell--12-col-tablet mdl-grid">
          <div
            class="mdl-layout__header-row mdl-cell mdl-cell--12-col mdl-cell--12-col-tablet mdl-cell--8-col-desktop"
          >
            <h3>Firebase auth example</h3>
          </div>
        </div>
      </header>

      <main class="mdl-layout__content mdl-color--grey-100">
        <div class="mdl-cell mdl-cell--12-col mdl-cell--12-col-tablet mdl-grid">
          <div
            class="mdl-card mdl-shadow--2dp mdl-cell mdl-cell--12-col mdl-cell--12-col-tablet mdl-cell--12-col-desktop"
          >
            <div
              class="mdl-card__title mdl-color--light-blue-600 mdl-color-text--white"
            >
              <h2 class="mdl-card__title-text">Table of Contents</h2>
            </div>
            <div class="mdl-card__supporting-text mdl-color-text--grey-600">
              <p>
                Use the example application to test email login with MailSlurp
              </p>

              <ul>
                <li>
                  <a href="email-link.html" id="page-email-link"
                    >Email Link authentication</a
                  ><br /><br />
                </li>
              </ul>
            </div>
          </div>
        </div>
      </main>
    </div>
  </body>
</html>

firebase-examples/firebase.json

{
  "hosting": {
    "public": "dist",
    "ignore": ["firebase.json", "**/.*", "**/node_modules/**"]
  },
  "emulators": {
    "auth": {
      "port": 9099
    },
    "ui": {
      "enabled": true,
      "port": 4000
    },
    "singleProjectMode": true,
    "hosting": {
      "port": 5002
    }
  }
}
//<gen>firebase_email_link_code
import { initializeApp } from "firebase/app";
import {
  getAdditionalUserInfo,
  getAuth,
  isSignInWithEmailLink,
  onAuthStateChanged,
  sendSignInLinkToEmail,
  signInWithEmailLink,
  signOut,
} from "firebase/auth";
import { firebaseConfig } from "./config";

initializeApp(firebaseConfig);

const auth = getAuth();
const signInButton = document.getElementById(
  "quickstart-sign-in",
)! as HTMLButtonElement;
const emailInput = document.getElementById("email")! as HTMLInputElement;
const signInStatus = document.getElementById(
  "quickstart-sign-in-status",
)! as HTMLSpanElement;
const accountDetails = document.getElementById(
  "quickstart-account-details",
)! as HTMLDivElement;

/**
 * Handles the sign in button press.
 */
function toggleSignIn() {
  // Disable the sign-in button during async sign-in tasks.
  signInButton.disabled = true;

  if (auth.currentUser) {
    signOut(auth).catch(function (error) {
      // Handle Errors here.
      const errorCode = error.code;
      const errorMessage = error.message;
      handleError(error);
    });
  } else {
    const email = emailInput.value;
    // Sending email with sign-in link.
    const actionCodeSettings = {
      // URL you want to redirect back to. The domain (www.example.com) for this URL
      // must be whitelisted in the Firebase Console.
      url: window.location.href, // Here we redirect back to this same page.
      handleCodeInApp: true, // This must be true.
    };

    sendSignInLinkToEmail(auth, email, actionCodeSettings)
      .then(function () {
        // Save the email locally so you don’t need to ask the user for it again if they open
        // the link on the same device.
        window.localStorage.setItem("emailForSignIn", email);
        // The link was successfully sent. Inform the user.
        alert(
          "An email was sent to " +
            email +
            ". Please use the link in the email to sign-in.",
        );
        // Re-enable the sign-in button.
        signInButton.disabled = false;
      })
      .catch(function (error) {
        // Handle Errors here.
        const errorCode = error.code;
        const errorMessage = error.message;
        handleError(error);
      });
  }
}

/**
 * Handles Errors from various Promises..
 */
function handleError(error: any) {
  // Display Error.
  alert("Error: " + error.message);
  console.log(error);
  // Re-enable the sign-in button.
  signInButton.disabled = false;
}

/**
 * Handles automatically signing-in the app if we clicked on the sign-in link in the email.
 */
function handleSignIn() {
  if (isSignInWithEmailLink(auth, window.location.href)) {
    // Disable the sign-in button during async sign-in tasks.
    signInButton.disabled = true;

    // You can also get the other parameters passed in the query string such as state=STATE.
    // Get the email if available.
    let email = window.localStorage.getItem("emailForSignIn");
    if (!email) {
      // User opened the link on a different device. To prevent session fixation attacks, ask the
      // user to provide the associated email again. For example:
      email = window.prompt(
        "Please provide the email you'd like to sign-in with for confirmation.",
      );
    }
    if (email) {
      // The client SDK will parse the code from the link for you.
      signInWithEmailLink(auth, email, window.location.href)
        .then(function (result) {
          // Clear the URL to remove the sign-in link parameters.
          window.history.replaceState(
            {},
            document.title,
            window.location.href.split("?")[0],
          );
          // Clear email from storage.
          window.localStorage.removeItem("emailForSignIn");
          // Signed-in user's information.
          const user = result.user;
          const additionalUserInfo = getAdditionalUserInfo(result);
          const isNewUser = additionalUserInfo?.isNewUser;
          console.log(result);
        })
        .catch(function (error) {
          // Handle Errors here.
          const errorCode = error.code;
          const errorMessage = error.message;
          handleError(error);
        });
    }
  }
}

// Restore the previously used value of the email.
const email = window.localStorage.getItem("emailForSignIn");
emailInput.value = email ?? "";

// Automatically signs the user-in using the link.
handleSignIn();

// Listening for auth state changes.
onAuthStateChanged(auth, function (user) {
  if (user) {
    // Update UI.
    signInStatus.textContent = "Signed in";
    signInButton.textContent = "Sign out";
    accountDetails.textContent = JSON.stringify(user, null, "  ");
  } else {
    // User is signed out.
    // Update UI.
    signInStatus.textContent = "Signed out";
    signInButton.textContent = "Sign In without password";
    accountDetails.textContent = "null";
  }
  signInButton.disabled = false;
});

signInButton.addEventListener("click", toggleSignIn, false);
//</gen>
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Email Link Authentication Example</title>

    <!-- Material Design Theming -->
    <link
      rel="stylesheet"
      href="https://code.getmdl.io/1.1.3/material.orange-indigo.min.css"
    />
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/icon?family=Material+Icons"
    />
    <script defer src="https://code.getmdl.io/1.1.3/material.min.js"></script>

    <link rel="stylesheet" href="main.css" />
  </head>
  <body>
    <div class="demo-layout mdl-layout mdl-js-layout mdl-layout--fixed-header">
      <header
        class="mdl-layout__header mdl-color-text--white mdl-color--light-blue-700"
      >
        <div class="mdl-cell mdl-cell--12-col mdl-cell--12-col-tablet mdl-grid">
          <div
            class="mdl-layout__header-row mdl-cell mdl-cell--12-col mdl-cell--12-col-tablet mdl-cell--8-col-desktop"
          >
            <a href="/"><h3>Firebase Authentication</h3></a>
          </div>
        </div>
      </header>

      <main class="mdl-layout__content mdl-color--grey-100">
        <div class="mdl-cell mdl-cell--12-col mdl-cell--12-col-tablet mdl-grid">
          <div
            class="mdl-card mdl-shadow--2dp mdl-cell mdl-cell--12-col mdl-cell--12-col-tablet mdl-cell--12-col-desktop"
          >
            <div
              class="mdl-card__title mdl-color--light-blue-600 mdl-color-text--white"
            >
              <h2 class="mdl-card__title-text">
                Firebase Email Link Authentication
              </h2>
            </div>
            <div class="mdl-card__supporting-text mdl-color-text--grey-600">
              <p>
                Enter your email below and sign in to your account through a
                link sent to you via email. This will automatically create an
                account if you do not have one already.
              </p>
              <form onsubmit="return false">
                <input
                  class="mdl-textfield__input"
                  style="display: inline; width: auto"
                  type="text"
                  id="email"
                  name="email"
                  placeholder="Email"
                />
                &nbsp;&nbsp;&nbsp;
                <button
                  disabled
                  class="mdl-button mdl-js-button mdl-button--raised"
                  id="quickstart-sign-in"
                  name="signin"
                >
                  Sign In without password
                </button>
              </form>
              <div class="quickstart-user-details-container">
                Firebase sign-in status:
                <span id="quickstart-sign-in-status">Unknown</span>
                <div>Firebase auth <code>currentUser</code> object value:</div>
                <pre><code id="quickstart-account-details">null</code></pre>
              </div>
            </div>
          </div>
        </div>
      </main>
    </div>

    <script type="module" src="email-link.ts"></script>
  </body>
</html>

firebase-examples/config.ts

export const firebaseConfig = {
  apiKey: "AIzaSyDnt9ltQNICAksQucAcNa5zwF-qMpz-HJ0",
  authDomain: "mailslurp-firebase-examples.firebaseapp.com",
  projectId: "mailslurp-firebase-examples",
  storageBucket: "mailslurp-firebase-examples.appspot.com",
  messagingSenderId: "1077264176110",
  appId: "1:1077264176110:web:77cfd41b163bbea387f4ff",
};
/*
//<gen>firebase_config
export const firebaseConfig = {
    apiKey: "YOUR_FIREBASE_API_KEY",
    authDomain: "YOUR_APP.firebaseapp.com",
    projectId: "YOUR_PROJECT",
    storageBucket: "YOUR_STORAGE.appspot.com",
    messagingSenderId: "YOUR_SENDER_ID",
    appId: "YOUR_APP_ID"
};
//</gen>
*/

firebase-examples/README.md

# Firebase examples

Deployed to `mailslurp-firebase-examples.firebaseapp.com`

## Install

```
npm i
```

Setup firebase with emulators

```
npx firebase login
npx firebase init
```

Configure config.ts to match your firebase project.

firebase-examples/Makefile

.PHONY: test
-include ../.env

build:
	npm run build

deploy: build
	npm run deploy

dev:
	npm run dev

test:
	MAILSLURP_API_KEY=$(API_KEY) npm run test-ci

firebase-examples/.firebaserc

{
  "projects": {
    "default": "mailslurp-firebase-examples"
  }
}

firebase-examples/nightwatch/tsconfig.json

{
  "extends": "../tsconfig",
  "compilerOptions": {
    "target": "esnext",
    "module": "commonjs",
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "skipLibCheck": true,
    "allowJs": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true
  },
  "ts-node": {
    "transpileOnly": true
  },
  "include": ["."]
}
import { NightwatchAPI, NightwatchTests } from "nightwatch";
import MailSlurp from "mailslurp-client";
const appUrl = "http://localhost:5173";
const apiKey = process.env.MAILSLURP_API_KEY;
if (!apiKey) {
  throw new Error("MailSlurp API key expected");
}
const home: NightwatchTests = {
  "Email login link test": () => {
    //<gen>firebase_email_link_test_00
    // create mailslurp client for controlling emails
    const mailslurp = new MailSlurp({ apiKey });
    let inbox = null;
    let loginLink = null;
    //</gen>

    // load the example app
    browser
      .perform(async (done) => {
        //<gen>firebase_email_link_test_01
        browser
          .url(appUrl)
          .waitForElementVisible("body")
          .assert.titleContains("MailSlurp")
          // go to the sign in page
          .waitForElementVisible("#page-email-link")
          .click("#page-email-link")
          // assert we are no signed in
          .waitForElementVisible("#quickstart-sign-in-status")
          .assert.containsText("#quickstart-sign-in-status", "Signed out")
          .screenshot("./screenshots/firebase-email-link-01.png", () => {
            done();
          });
        //</gen>
      })
      .perform(async (done) => {
        //<gen>firebase_email_link_test_02
        // create a temp email account
        inbox = await mailslurp.createInbox();
        // fill the email form and submit
        browser
          .setValue("#email", inbox.emailAddress)
          .click("#quickstart-sign-in", () => {
            done();
          });
        //</gen>
      })
      .pause(1000)
      // accept alert
      .getAlertText((result) => {
        browser.assert.equal(
          result.value.indexOf(inbox.emailAddress) > -1,
          true,
          "Alert shows user email address",
        );
      })
      .acceptAlert()
      .perform(async (done) => {
        //<gen>firebase_email_link_test_03
        // now wait for email
        const waitTimeMillis = 120_000;
        const email = await mailslurp.waitForLatestEmail(
          inbox.id,
          waitTimeMillis,
        );
        //</gen>
        //<gen>firebase_email_link_test_04
        browser.assert.equal(
          email.body.indexOf("click this link") > -1,
          true,
          "Expect email body contains a link",
        );
        //</gen>
        //<gen>firebase_email_link_test_05
        // extract the links using MailSlurp
        const links = await mailslurp.emailController.getEmailLinks({
          emailId: email.id,
        });
        browser.assert.equal(
          links.links.length,
          1,
          "Expect to find 1 link in the email",
        );
        loginLink = links.links[0];
        //</gen>
        //<gen>firebase_email_link_test_06
        browser.assert.equal(
          loginLink.indexOf("firebaseapp.com/__/auth/") > -1,
          true,
          "Expect link is well formed firebase email login link",
        );
        // now load the link in the browser
        // equivalent to clicking the link inside the email
        browser.url(loginLink, () => {
          done();
        });
        //</gen>
      })
      // assert we are now signed in!
      .waitForElementVisible("#quickstart-sign-in-status")
      .assert.containsText("#quickstart-sign-in-status", "Signed In")
      .end();
  },
};
export default home;