@ -1,40 +1,284 @@
// https://github.com/portier/portier-node
// https://paul-senon.medium.com/node-express-js-cookies-set-get-secure-884311606148
import express from "express" ;
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_ENV ;
case "dev" :
return process . env . DEV_ENV ;
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
const portier = new PortierClient ( {
//broker: "https://broker.flylocal.io",
redirectUri : ( ( process . env . NODE_ENV !== 'production' ) ? "http://localhost:3010" : "https://iflylocal.com" ) + "/login/verify" ,
//broker: process.env.PORTIER_URL ,
redirectUri : baseURL ( ) + "/login/verify" ,
} ) ;
const stylesheet = `
< style >
html {
font - size : max ( 4 vmin , 18 px ) ;
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 1 rem ;
justify - content : center ;
margin : 0 ;
}
header {
font - size : min ( 3 vmin , 18 px ) ;
position : fixed ;
top : 0 ;
width : 100 vw ;
z - index : 403 ;
display : grid ;
grid - template - columns : 1 fr 1 fr 1 fr ;
padding : 1 em 1 em 0.75 em 1 em ;
pointer - events : none ;
}
. header__col {
text - align : center ;
}
# flylocal - logo {
height : clamp ( 20 px , 4 rem , 50 px ) ;
transition : all 0.3 s ;
}
. cls - 1 {
fill : # 9 cb7f3 ;
}
. cls - 2 {
fill : # 2 f80ed ;
}
. cls - 3 {
fill : # 000 ;
transition : all 0.3 s ;
}
. nav - is - showing # flylocal - logo {
height : 3 rem ;
}
. nav - is - showing . cls - 3 {
fill : # 000 ;
}
. white - bkg . cls - 3 {
fill : # 000 ;
}
main {
max - width : 15 rem ;
}
form {
display : flex ;
gap : .5 rem ;
flex - wrap : wrap ;
justify - content : end ;
}
h1 {
font - size : 1 rem ;
text - transform : uppercase ;
font - weight : 900 ;
}
. btn {
border - radius : 999 px ;
padding : 0.5 em 0.8 em ;
font - size : .75 rem ;
white - space : nowrap ;
transition : 0.2 s all ;
display : flex ;
align - items : center ;
/* justify-content: space-around; what was this for? */
justify - content : center ;
gap : 0.25 rem ;
line - height : 1 ;
min - height : 1 em ;
border : 0 ;
text - decoration : none ;
cursor : pointer ;
}
. btn svg {
height : 1 rem ;
width : 1 rem ;
display : inline - block ;
aspect - ratio : 1 ;
transition : 0.2 s all ;
}
. btn . btn -- primary {
background : # 007 fff ;
color : # fff ;
}
. btn . btn -- primary : hover {
background : # 00 ca00 ;
color : # fff ;
}
. btn . btn -- primary : hover svg {
stroke : # ffffff ;
}
input {
background : # eee ;
border - radius : 0.5 rem 3 rem 3 rem 3 rem ;
transition : 0.3 s all ;
padding : 0.5 em 0.8 em ;
font - size : .75 rem ;
line - height : 1 ;
border : 0 ;
width : 100 % ;
}
< / s t y l e > ` ;
const header = `
< header >
< div class = "header__col" > < / d i v >
< 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" / >
< / s v g >
< / d i v >
< div class = "header__col" > < / d i v >
< / h e a d e r >
` ;
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 ( `
< p > Enter your email address : < / p >
< form method = "post" action = "/login/auth" >
< input name = "email" type = "email" >
< button type = "submit" > Login < / b u t t o n >
< / f o r m >
$ { 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 ! < / s t r o n g > < / 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" / >
< / s v g >
< / b u t t o n >
< / f o r m >
< / m a i n >
$ { stylesheet }
` );
// email = cookies('email')
// if email:
// return template('verified', email=email)
// else:
// return template('index')
} ) ;
app . post ( "/auth" , formParser , ( req , res ) => {
//console.log(req.body);
portier . authenticate ( req . body . email ) . then ( ( authUrl ) => {
res . redirect ( 303 , 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);
portier . verify ( req . body . id_token ) . then ( ( email ) => {
res . type ( "html" ) . end ( `
< p > Verified email address $ { email } ! < / p >
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=" ${ baseURL ( ) } ">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 }
< / m a i n >
$ { 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')
// })