https://github.com/mailslurp/examples
import { defineConfig } from "vite";
export default defineConfig({
base: "/",
build: {
rollupOptions: {
input: ["index.html", "email-link.html"],
},
},
});
{
"compilerOptions": {
"target": "ES6",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
{
"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"
}
}
//<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>
/**
* 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;
}
<!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>
{
"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"
/>
<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>
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
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.
.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
{
"projects": {
"default": "mailslurp-firebase-examples"
}
}
{
"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;