You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

299 lines
11 KiB

// 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" 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
}