|
// https://github.com/portier/portier-node
|
|
|
|
// https://paul-senon.medium.com/node-express-js-cookies-set-get-secure-884311606148
|
|
|
|
import express from "express";
|
|
const dotenv = require('dotenv');
|
|
const formParser = require("body-parser").urlencoded({ extended: false });
|
|
import { PortierClient } from "portier";
|
|
|
|
const cookieParser = require('cookie-parser');
|
|
|
|
// const baseURL = function () {
|
|
// switch (process.env.FLYLOCAL_ENV) {
|
|
// case "prod":
|
|
// return process.env.PROD_URL;
|
|
// case "dev":
|
|
// return process.env.DEV_URL;
|
|
// case "local":
|
|
// return "http://" + process.env.LOCAL_IP + ":" + process.env.LOCAL_PORT;
|
|
// default:
|
|
// break;
|
|
// }
|
|
// };
|
|
|
|
// GET / Render homepage
|
|
// POST /login Redirect to the broker and begin authentication
|
|
// POST /verify Receive an id_token via the broker and complete login
|
|
// POST /logout Clear session cookies
|
|
// GET /logout Display a button to POST /logout
|
|
|
|
dotenv.config({ path: './.env' });
|
|
|
|
const portier = new PortierClient({
|
|
//broker: process.env.PORTIER_URL,
|
|
redirectUri: 'https://flylocal.us' + "/login/verify",
|
|
});
|
|
|
|
|
|
const stylesheet = `
|
|
<style>
|
|
html {
|
|
font-size: max(4vmin, 18px);
|
|
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
|
line-height: 1.5;
|
|
}
|
|
|
|
body {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
height: 100%;
|
|
padding: 0 1rem;
|
|
justify-content: center;
|
|
margin: 0;
|
|
}
|
|
|
|
header {
|
|
font-size: min(3vmin, 18px);
|
|
position: fixed;
|
|
top: 0;
|
|
width: 100vw;
|
|
z-index: 403;
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr 1fr;
|
|
padding: 1em 1em 0.75em 1em;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.header__col{
|
|
text-align: center;
|
|
}
|
|
|
|
#flylocal-logo {
|
|
height: clamp(20px, 4rem, 50px);
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
.cls-1 {
|
|
fill: #9cb7f3;
|
|
}
|
|
|
|
.cls-2 {
|
|
fill: #2f80ed;
|
|
}
|
|
|
|
.cls-3 {
|
|
fill: #000;
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
.nav-is-showing #flylocal-logo {
|
|
height: 3rem;
|
|
}
|
|
|
|
.nav-is-showing .cls-3 {
|
|
fill: #000;
|
|
}
|
|
|
|
.white-bkg .cls-3 {
|
|
fill: #000;
|
|
}
|
|
|
|
main {
|
|
max-width: 15rem;
|
|
}
|
|
|
|
form {
|
|
display: flex;
|
|
gap: .5rem;
|
|
flex-wrap: wrap;
|
|
justify-content: end;
|
|
}
|
|
|
|
h1 {
|
|
font-size: 1rem;
|
|
text-transform: uppercase;
|
|
font-weight: 900;
|
|
}
|
|
|
|
.btn {
|
|
border-radius: 999px;
|
|
padding: 0.5em 0.8em;
|
|
font-size: .75rem;
|
|
white-space: nowrap;
|
|
transition: 0.2s all;
|
|
display: flex;
|
|
align-items: center;
|
|
/* justify-content: space-around; what was this for? */
|
|
justify-content: center;
|
|
gap: 0.25rem;
|
|
line-height: 1;
|
|
min-height: 1em;
|
|
border: 0;
|
|
text-decoration: none;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.btn svg {
|
|
height: 1rem;
|
|
width: 1rem;
|
|
display: inline-block;
|
|
aspect-ratio: 1;
|
|
transition: 0.2s all;
|
|
}
|
|
|
|
.btn.btn--primary {
|
|
background: #007fff;
|
|
color: #fff;
|
|
}
|
|
|
|
.btn.btn--primary:hover {
|
|
background: #00ca00;
|
|
color: #fff;
|
|
}
|
|
|
|
.btn.btn--primary:hover svg {
|
|
stroke: #ffffff;
|
|
}
|
|
|
|
input {
|
|
background: #eee;
|
|
border-radius: 0.5rem 3rem 3rem 3rem;
|
|
transition: 0.3s all;
|
|
padding: 0.5em 0.8em;
|
|
font-size: .75rem;
|
|
line-height: 1;
|
|
border: 0;
|
|
width: 100%;
|
|
}
|
|
</style>`;
|
|
|
|
const header = `
|
|
<header>
|
|
<div class="header__col"></div>
|
|
<div class="header__col">
|
|
<svg id="flylocal-logo" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1107.17 373.96">
|
|
<path class="cls-1" d="M.1,242.9a55.68,55.68,0,0,0,111.24-2.42,54.61,54.61,0,0,0-.44-8.21l-.05,0c-3.86-37.55-29.44-82.66-32.6-90.5-6.84,5.84-48.77,34.79-68.78,66.73l-.05,0A55.95,55.95,0,0,0,.1,242.9Z" /><path class="cls-2" d="M141.36,52.27A55.68,55.68,0,0,0,33.8,35.74a57.55,57.55,0,0,0-2.27,7.42,55.82,55.82,0,0,0-1.41,11.53,54.75,54.75,0,0,0,.45,8.22h.05c3.86,37.55,29.79,83.67,32.59,90.5,6.2-4.83,48.77-34.79,68.79-66.73h.05a55.89,55.89,0,0,0,9.31-34.41ZM85.88,56a13,13,0,1,1,15.59-9.74A13,13,0,0,1,85.88,56Z" /><path class="cls-3" d="M156.34,293.15l34.58-173.28H166.33l5.36-26.81h24.6l5.18-25.71q6.83-34,24.5-50.3C242.74,1.6,268.46-2.8,290,3.55c1.12.33,2.24.69,3.34,1.08-.58,2.8-7.2,29.89-7,30-7.63-2.37-16.2-3.35-24-1.43C246.75,37,241.44,53.31,238.64,67.35l-5.18,25.71h32.73l-5.36,26.81H228.09L193.51,293.15Z" /><path class="cls-3" d="M290.8,293.15h-37L311.51,4.66h37Z" /><path class="cls-3" d="M393,224.17l.19,11.1,1.11.18,64-142.39H498.6L385.79,324.22a140.35,140.35,0,0,1-25.06,35.13Q346.21,374,324.58,374a47.75,47.75,0,0,1-9.34-1q-5.08-1-9.89-2.31L315,342.89q2,.38,5.27.74a46.84,46.84,0,0,0,5.09.37q9.44,0,17.85-9.71a91.86,91.86,0,0,0,14-21.72l10.17-20L348.44,93.06h40.31Z" /><path class="cls-3" d="M511.33,293.15h-37L532,4.66h37Z" /><path class="cls-3" d="M553.53,180.53q6.47-41.43,30.14-66.3t58.81-24.87q33.09,0,49.56,25.89t10.17,65.28l-4.07,25.52q-6.66,41.61-30.24,66.29T609.56,297q-33.46,0-50-25.7t-10.08-65.28Zm32.91,25.52q-4.62,28.48,1.85,45.49t25.71,17q17.75,0,30.14-17.75T661,206.05L665,180.53q4.44-27.92-1.94-45.12t-25.25-17.2q-18.3,0-30.69,17.75t-16.65,44.57Z" /><path class="cls-3" d="M779.81,268.55q12.57,0,23.49-10.81t13.87-29h33.1l.37,1.11q-4.26,29.41-26,48.27T775.37,297q-34.39,0-50-25.61t-9.52-64.26l4.44-27.56q6.47-40.48,29.31-65.37t58.9-24.87q28.1,0,43.45,20.53t10.17,53.26H827.52q3.33-20.72-3.14-32.83t-20.53-12.11q-19.23,0-30.79,17.57T757.44,179.6L753,207.16q-4.44,28.11,1,44.75T779.81,268.55Z" /><path class="cls-3" d="M956.91,293.15q-.37-7.39-.37-13.69a101.53,101.53,0,0,1,.74-12.39,93.25,93.25,0,0,1-23.95,21.64Q919.92,297,905.68,297q-22.56,0-33.93-16.27t-6-42.53q6.1-30.53,27.46-46T949,176.83h25.89l4.06-20.53q3.71-19.41-1.57-29.12t-19.51-9.71q-11.64,0-21.45,9.34a41.72,41.72,0,0,0-12.39,23l-35.32-.18-.37-1.11q4.07-24.6,25.8-41.89t51.68-17.29q26.64,0,41.61,17.75t8.69,49.56l-18.67,92.84a183.7,183.7,0,0,0-3.15,22.56,133.93,133.93,0,0,0,.19,21.08Zm-37-25q11.28,0,23-7.76a63.41,63.41,0,0,0,18.95-19.24L970,200.13H943.78q-14.81,0-26.26,11.28A49.81,49.81,0,0,0,903.1,238q-3,14.43,1.29,22.28T919.92,268.18Z" /><path class="cls-3" d="M1049.48,293.15h-37l57.7-288.49h37Z" />
|
|
</svg>
|
|
</div>
|
|
<div class="header__col"></div>
|
|
</header>
|
|
`;
|
|
|
|
const app = express();
|
|
app.use(express.json())
|
|
app.use(express.urlencoded({ extended: false }))
|
|
|
|
// pass argument into cookie parser to make it secret
|
|
//app.use(cookieParser(process.env.PORTIER_SECRET));
|
|
app.use(cookieParser());
|
|
|
|
app.get("/", (req, res) => {
|
|
const redirect =
|
|
(req.query.redirect && typeof req.query.redirect !== 'undefined') ? '<input name="redirect" type="hidden" value="' + req.query.redirect + '">' : '';
|
|
const note =
|
|
(req.query.redirect && typeof req.query.redirect !== 'undefined') ? `<h1>Want to book a flight?<br/>Take 10 seconds and log in.</h1>` : '';
|
|
res.type("html").end(`
|
|
${header}
|
|
<main>
|
|
${note}
|
|
<p>To log in, just enter your email address and we'll send you a magic link. </p>
|
|
<p><strong>No new password to remember!</strong></p>
|
|
<form method="post" action="/login/auth">
|
|
${redirect}
|
|
<input name="email" type="email" value="spencer@seasteading.org" placeholder="coolname@website.com" pattern="/(?:[a-z0-9!#$%&'*+/=?^_{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/" required>
|
|
<button type="submit" class="btn btn--primary">
|
|
get my link
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-wand" width="40" height="40" viewBox="0 0 24 24" stroke-width="1.5" stroke="#ffffff" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
|
<polyline points="6 21 21 6 18 3 3 18 6 21" />
|
|
<line x1="15" y1="6" x2="18" y2="9" />
|
|
<path d="M9 3a2 2 0 0 0 2 2a2 2 0 0 0 -2 2a2 2 0 0 0 -2 -2a2 2 0 0 0 2 -2" />
|
|
<path d="M19 13a2 2 0 0 0 2 2a2 2 0 0 0 -2 2a2 2 0 0 0 -2 -2a2 2 0 0 0 2 -2" />
|
|
</svg>
|
|
</button>
|
|
</form>
|
|
</main>
|
|
${stylesheet}
|
|
`);
|
|
|
|
// email = cookies('email')
|
|
// if email:
|
|
// return template('verified', email=email)
|
|
// else:
|
|
// return template('index')
|
|
});
|
|
|
|
app.post("/auth", formParser, (req, res) => {
|
|
|
|
console.log("auth");
|
|
//console.log(baseURL());
|
|
|
|
//console.log(req.body);
|
|
portier.authenticate(req.body.email).then((authUrl) => {
|
|
if (req.body.redirect && typeof req.body.redirect !== 'undefined') {
|
|
res.redirect(303, authUrl + '?redirect=' + req.body.redirect);
|
|
} else {
|
|
res.redirect(303, authUrl);
|
|
}
|
|
});
|
|
});
|
|
|
|
app.post("/verify", formParser, (req, res) => {
|
|
//console.log(req.body);
|
|
|
|
console.log("verify");
|
|
//console.log(baseURL());
|
|
|
|
portier.verify(req.body.id_token).then((email) => {
|
|
|
|
const redirectButton = (req.query.redirect && typeof req.query.redirect !== 'undefined') ? `<a class="btn btn--primary" href="${req.query.redirect}">back to your flight</a>` : `<a class="btn btn--primary" href="${'https://flylocal.us'}">back to the map</a>`;
|
|
|
|
// needed to string .cookie and .type together
|
|
res.cookie('email', email,
|
|
{
|
|
//httpOnly: true,
|
|
// 👆 blocks access by JavaScript; prevents cookie manipulation
|
|
// 👆 can't be gotten by $cookies.get. this probably needs to be hardened
|
|
|
|
maxAge: 1000 * 60 * 60 * 24 * 30,
|
|
//signed: false,
|
|
//domain: baseURL(),
|
|
path: "/"
|
|
}).type("html").end(`
|
|
${header}
|
|
<main>
|
|
<p>Great! You're logged in.</p>
|
|
${redirectButton}
|
|
</main>
|
|
${stylesheet}
|
|
`);
|
|
});
|
|
});
|
|
|
|
// app.get("/check", (req, res) => {
|
|
// //req.signedCookies.name='Gourav';
|
|
// //req.signedCookies.age=12;
|
|
// const cookie = req.signedCookies.email;
|
|
// console.log(cookie);
|
|
// //console.log(req.signedCookies.name);
|
|
// res.send();
|
|
// });
|
|
|
|
// app.get("/logout", (req, res) => {
|
|
// res.clearCookie("email");
|
|
// res.send('forgotten');
|
|
// });
|
|
|
|
//app.post("/logout")
|
|
//res.clearCookie("email");
|
|
|
|
// app.get('/test', function (req, res) {
|
|
// res.send('Test successful')
|
|
// })
|
|
|
|
export default {
|
|
path: '/login',
|
|
handler: app
|
|
}
|