mirror of
https://github.com/aljazceru/landscape-template.git
synced 2026-01-19 14:24:28 +01:00
refactor: migrate to jwt sessions instead of store sessions
This commit is contained in:
@@ -27,11 +27,10 @@ function isHashUsed(hash) {
|
||||
return prisma.generatedK1.findFirst({ where: { value: hash } })
|
||||
}
|
||||
|
||||
function addHash(hash, sid) {
|
||||
function addHash(hash) {
|
||||
return prisma.generatedK1.create({
|
||||
data: {
|
||||
value: hash,
|
||||
sid,
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -46,7 +45,7 @@ function removeHash(hash) {
|
||||
|
||||
function removeExpiredHashes() {
|
||||
const now = new Date();
|
||||
const lastHourDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours() - 1, now.getMinutes());
|
||||
const lastHourDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours(), now.getMinutes() - 10);
|
||||
|
||||
return prisma.generatedK1.deleteMany({
|
||||
where: {
|
||||
@@ -57,20 +56,21 @@ function removeExpiredHashes() {
|
||||
})
|
||||
}
|
||||
|
||||
async function generateAuthUrl(sid) {
|
||||
async function generateAuthUrl() {
|
||||
const hostname = CONSTS.LNURL_AUTH_HOST;
|
||||
const secret = await generateSecret()
|
||||
await addHash(createHash(secret), sid)
|
||||
const secret = await generateSecret();
|
||||
const hash = createHash(secret);
|
||||
await addHash(hash)
|
||||
const url = `${hostname}?tag=login&k1=${secret}`
|
||||
return {
|
||||
url,
|
||||
encoded: lnurl.encode(url).toUpperCase(),
|
||||
secret,
|
||||
secretHash: hash,
|
||||
}
|
||||
}
|
||||
|
||||
async function getSidByK1(k1) {
|
||||
const hash = createHash(k1)
|
||||
async function getAuthTokenByHash(hash) {
|
||||
const data = await prisma.generatedK1.findFirst({
|
||||
where: {
|
||||
value: hash,
|
||||
@@ -79,6 +79,17 @@ async function getSidByK1(k1) {
|
||||
return data.sid;
|
||||
}
|
||||
|
||||
function associateTokenToHash(hash, token) {
|
||||
return prisma.generatedK1.update({
|
||||
where: {
|
||||
value: hash
|
||||
},
|
||||
data: {
|
||||
sid: token
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function verifySig(sig, k1, key) {
|
||||
if (!lnurl.verifyAuthorizationSignature(sig, k1, key)) {
|
||||
const message = 'Signature verification failed'
|
||||
@@ -105,141 +116,12 @@ function createHash(data) {
|
||||
|
||||
|
||||
|
||||
|
||||
// function setupAuthMiddelwares(app) {
|
||||
// app.use(session({
|
||||
// secret: "12345",
|
||||
// resave: false,
|
||||
// saveUninitialized: true,
|
||||
// store: new SQLiteStore()
|
||||
// }));
|
||||
|
||||
// passport.use(
|
||||
// new lnurlAuth.Strategy(function (linkingPublicKey, done) {
|
||||
// const user = { id: linkingPublicKey };
|
||||
// console.log("Strategy Function");
|
||||
// console.log(user);
|
||||
// // let user = map.user.get(linkingPublicKey);
|
||||
// // if (!user) {
|
||||
// // user = { id: linkingPublicKey };
|
||||
// // map.user.set(linkingPublicKey, user);
|
||||
// // }
|
||||
// done(null, user);
|
||||
// })
|
||||
// );
|
||||
|
||||
// app.use(passport.initialize());
|
||||
// app.use(passport.session());
|
||||
// app.use(passport.authenticate("lnurl-auth"));
|
||||
// passport.serializeUser(function (user, done) {
|
||||
// done(null, user.id);
|
||||
// });
|
||||
|
||||
// passport.deserializeUser(function (id, done) {
|
||||
|
||||
// // done(null, map.user.get(id) || null);
|
||||
// done(null, id || null);
|
||||
// });
|
||||
// return app;
|
||||
// /*
|
||||
// app.get(
|
||||
// "/do-login",
|
||||
// function (req, res, next) {
|
||||
// next();
|
||||
// },
|
||||
// async function (req, res) {
|
||||
|
||||
|
||||
// if (req.query.k1 || req.query.key || req.query.sig) {
|
||||
// // Check signature against provided linking public key.
|
||||
// // This request could originate from a mobile app (ie. not their browser).
|
||||
// let session;
|
||||
// assert.ok(
|
||||
// req.query.k1,
|
||||
// new HttpError('Missing required parameter: "k1"', 400)
|
||||
// );
|
||||
// assert.ok(
|
||||
// req.query.sig,
|
||||
// new HttpError('Missing required parameter: "sig"', 400)
|
||||
// );
|
||||
// assert.ok(
|
||||
// req.query.key,
|
||||
// new HttpError('Missing required parameter: "key"', 400)
|
||||
// );
|
||||
// session = map.session.get(req.query.k1);
|
||||
// assert.ok(
|
||||
// session,
|
||||
// new HttpError("Secret does not match any known session", 400)
|
||||
// );
|
||||
// const { k1, sig, key } = req.query;
|
||||
// assert.ok(
|
||||
// verifyAuthorizationSignature(sig, k1, key),
|
||||
// new HttpError("Invalid signature", 400)
|
||||
// );
|
||||
// session.lnurlAuth = session.lnurlAuth || {};
|
||||
// session.lnurlAuth.linkingPublicKey = req.query.key;
|
||||
|
||||
|
||||
// const result = await session.save();
|
||||
// console.log(result);
|
||||
// res.status(200).json({ status: "OK" });
|
||||
// }
|
||||
|
||||
// req.session = req.session || {};
|
||||
// req.session.lnurlAuth = req.session.lnurlAuth || {};
|
||||
// let k1 = req.session.lnurlAuth.k1 || null;
|
||||
// if (!k1) {
|
||||
// k1 = req.session.lnurlAuth.k1 = generateSecret(32, "hex");
|
||||
// map.session.set(k1, req.session);
|
||||
// }
|
||||
|
||||
// const callbackUrl =
|
||||
// "https://" +
|
||||
// `${req.get("host")}/do-login?${querystring.stringify({
|
||||
// k1,
|
||||
// tag: "login",
|
||||
// })}`;
|
||||
|
||||
// const encoded = lnurl.encode(callbackUrl).toUpperCase();
|
||||
// const qrCode = await qrcode.toDataURL(encoded);
|
||||
// return res.json({
|
||||
// lnurl: encoded,
|
||||
// qrCode: qrCode,
|
||||
// });
|
||||
// }
|
||||
// );
|
||||
// */
|
||||
|
||||
// // app.get("/logout", function (req, res, next) {
|
||||
// // if (req.user) {
|
||||
// // req.session.destroy();
|
||||
// // return res.redirect("/");
|
||||
// // }
|
||||
// // next();
|
||||
// // });
|
||||
|
||||
// // app.get("/me", function (req, res, next) {
|
||||
// // res.json({ user: req.user ? req.user : null });
|
||||
|
||||
// // next();
|
||||
// // });
|
||||
|
||||
// // app.get("/profile", function (req, res, next) {
|
||||
// // if (!req.user) {
|
||||
// // return res.redirect("/login");
|
||||
// // }
|
||||
|
||||
// // res.render("profile", { user: req.user });
|
||||
|
||||
// // next();
|
||||
// // });
|
||||
// }
|
||||
|
||||
module.exports = {
|
||||
generateAuthUrl: generateAuthUrl,
|
||||
verifySig: verifySig,
|
||||
removeHash: removeHash,
|
||||
createHash: createHash,
|
||||
removeExpiredHashes: removeExpiredHashes,
|
||||
getSidByK1: getSidByK1
|
||||
getAuthTokenByHash: getAuthTokenByHash,
|
||||
associateTokenToHash: associateTokenToHash
|
||||
}
|
||||
@@ -1,12 +1,26 @@
|
||||
const { ApolloServer } = require("apollo-server-lambda");
|
||||
const schema = require('./schema')
|
||||
const cookie = require('cookie')
|
||||
const jose = require('jose');
|
||||
const { createExpressApp } = require("../../modules");
|
||||
const { JWT_SECRET } = require("../../utils/consts");
|
||||
|
||||
const extractKey = async (cookieHeader) => {
|
||||
const cookies = cookie.parse(cookieHeader ?? '');
|
||||
const token = cookies.Authorization;
|
||||
if (token) {
|
||||
const { payload } = await jose.jwtVerify(token, Buffer.from(JWT_SECRET), {
|
||||
algorithms: ['HS256'],
|
||||
})
|
||||
return payload.pubKey
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const server = new ApolloServer({
|
||||
schema,
|
||||
context: async ({ event, context, express }) => {
|
||||
const userPubKey = express.req.user?.id;
|
||||
context: async ({ event }) => {
|
||||
const userPubKey = await extractKey(event.headers.cookie ?? event.headers.Cookie)
|
||||
return { userPubKey }
|
||||
},
|
||||
});
|
||||
@@ -19,11 +33,6 @@ const apolloHandler = server.createHandler({
|
||||
origin: ['http://localhost:3000', 'https://studio.apollographql.com'],
|
||||
credentials: true,
|
||||
}
|
||||
},
|
||||
expressAppFromMiddleware(middleware) {
|
||||
const app = createExpressApp();
|
||||
app.use(middleware)
|
||||
return app;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
87
api/functions/is-logged-in/is-logged-in.js
Normal file
87
api/functions/is-logged-in/is-logged-in.js
Normal file
@@ -0,0 +1,87 @@
|
||||
|
||||
const serverless = require('serverless-http');
|
||||
const { getAuthTokenByHash } = require('../../auth/services/lnurl.service');
|
||||
const { createExpressApp } = require('../../modules');
|
||||
const express = require('express');
|
||||
const jose = require('jose');
|
||||
const { JWT_SECRET } = require('../../utils/consts');
|
||||
const lnurlService = require('../../auth/services/lnurl.service');
|
||||
|
||||
|
||||
const isLoggedInHandler = async (req, res) => {
|
||||
// console.log(req.cookies);
|
||||
try {
|
||||
const login_session = req.cookies?.login_session;
|
||||
// console.log(login_session);
|
||||
if (login_session) {
|
||||
|
||||
const { payload } = await jose.jwtVerify(login_session, Buffer.from(JWT_SECRET), {
|
||||
algorithms: ['HS256'],
|
||||
});
|
||||
const hash = payload.hash;
|
||||
const token = await getAuthTokenByHash(hash);
|
||||
|
||||
|
||||
lnurlService.removeHash(hash).catch();
|
||||
lnurlService.removeExpiredHashes().catch();
|
||||
|
||||
res
|
||||
.status(200)
|
||||
.cookie('Authorization', token, {
|
||||
maxAge: 3600000 * 24 * 30,
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
})
|
||||
.clearCookie('login_session', {
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
})
|
||||
.json({
|
||||
logged_in: true
|
||||
});
|
||||
// console.log(payload);
|
||||
} else {
|
||||
|
||||
res.json({
|
||||
me: null
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.json({
|
||||
logged_in: false
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// get session token
|
||||
// check DB to see if this token has an accossiated jwt auth token to it
|
||||
// if yes:
|
||||
// set the auth token to cookie
|
||||
// remove the session token
|
||||
// remove the data row
|
||||
|
||||
}
|
||||
|
||||
express.Router().get('id', (req, res) => {
|
||||
res.clearCookie('Au')
|
||||
})
|
||||
|
||||
|
||||
let app;
|
||||
|
||||
if (process.env.LOCAL) {
|
||||
app = createExpressApp()
|
||||
app.get('/is-logged-in', isLoggedInHandler);
|
||||
}
|
||||
else {
|
||||
const router = express.Router();
|
||||
router.get('/is-logged-in', isLoggedInHandler)
|
||||
app = createExpressApp(router)
|
||||
}
|
||||
|
||||
|
||||
const handler = serverless(app);
|
||||
exports.handler = async (event, context) => {
|
||||
return await handler(event, context);
|
||||
};
|
||||
@@ -2,18 +2,44 @@
|
||||
const { prisma } = require('../../prisma');
|
||||
const LnurlService = require('../../auth/services/lnurl.service')
|
||||
const serverless = require('serverless-http');
|
||||
const { getSidByK1 } = require('../../auth/services/lnurl.service');
|
||||
const { getAuthTokenByHash, createHash, associateTokenToHash } = require('../../auth/services/lnurl.service');
|
||||
const { sessionsStore, createExpressApp } = require('../../modules');
|
||||
const express = require('express');
|
||||
const jose = require('jose');
|
||||
const { JWT_SECRET } = require('../../utils/consts');
|
||||
|
||||
|
||||
|
||||
const router = express.Router();
|
||||
router.get('/login', (req, res) => {
|
||||
res.cookie('login_session', 'value', {
|
||||
maxAge: 1000 * 60 * 2, // 2 mins
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
})
|
||||
})
|
||||
|
||||
const loginHandler = async (req, res) => {
|
||||
const { tag, k1, sig, key } = req.query;
|
||||
// Generate an auth URL
|
||||
if (!sig || !key) {
|
||||
const data = await LnurlService.generateAuthUrl();
|
||||
const maxAge = 1000 * 60 * 3; //2 mins
|
||||
|
||||
const data = await LnurlService.generateAuthUrl(req.sessionID);
|
||||
return res.status(200).json(data);
|
||||
const jwt = await new jose.SignJWT({ hash: data.secretHash })
|
||||
.setProtectedHeader({ alg: 'HS256' })
|
||||
.setIssuedAt()
|
||||
.setExpirationTime('5min')
|
||||
.sign(Buffer.from(JWT_SECRET, 'utf-8'))
|
||||
|
||||
return res
|
||||
.status(200)
|
||||
.cookie('login_session', jwt, {
|
||||
maxAge,
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
})
|
||||
.json(data);
|
||||
}
|
||||
else {
|
||||
if (tag !== 'login')
|
||||
@@ -41,26 +67,26 @@ const loginHandler = async (req, res) => {
|
||||
})
|
||||
}
|
||||
|
||||
// calc the hash of k1
|
||||
const hash = createHash(k1);
|
||||
|
||||
// Update the session with the secret
|
||||
const sid = await getSidByK1(k1);
|
||||
const d = await new Promise((res, rej) => {
|
||||
sessionsStore.get(sid, (err, d) => {
|
||||
if (err) rej(err);
|
||||
res(d)
|
||||
})
|
||||
});
|
||||
// console.log(d);
|
||||
await new Promise((res, rej) => {
|
||||
sessionsStore.set(sid, { ...d, lnurlAuth: { linkingPublicKey: key } }, (err) => {
|
||||
if (err) rej(err);
|
||||
res()
|
||||
})
|
||||
});
|
||||
// generate the auth jwt token
|
||||
const hour = 3600000
|
||||
const maxAge = 30 * 24 * hour;
|
||||
|
||||
const jwt = await new jose.SignJWT({ pubKey: key })
|
||||
.setProtectedHeader({ alg: 'HS256' })
|
||||
.setIssuedAt()
|
||||
.setExpirationTime(maxAge)
|
||||
//TODO: Set audience, issuer
|
||||
.sign(Buffer.from(JWT_SECRET, 'utf-8'))
|
||||
|
||||
LnurlService.removeHash(LnurlService.createHash(k1)).catch();
|
||||
LnurlService.removeExpiredHashes().catch();
|
||||
// associate the auth token with the hash in the db
|
||||
console.log(hash);
|
||||
await associateTokenToHash(hash, jwt);
|
||||
|
||||
// LnurlService.removeHash(LnurlService.createHash(k1)).catch();
|
||||
// LnurlService.removeExpiredHashes().catch();
|
||||
|
||||
return res.status(200).json({ status: "OK" })
|
||||
|
||||
|
||||
@@ -3,11 +3,15 @@ const { createExpressApp } = require('../../modules');
|
||||
const express = require('express');
|
||||
|
||||
const logoutHandler = (req, res, next) => {
|
||||
if (req.user) {
|
||||
req.session.destroy();
|
||||
return res.redirect("/");
|
||||
}
|
||||
next();
|
||||
|
||||
res
|
||||
.clearCookie('Authorization', {
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
})
|
||||
.redirect("/")
|
||||
.end()
|
||||
|
||||
};
|
||||
|
||||
let app;
|
||||
|
||||
@@ -1,52 +1,19 @@
|
||||
|
||||
const express = require('express');
|
||||
const session = require("express-session");
|
||||
const passport = require("passport");
|
||||
const lnurlAuth = require("passport-lnurl-auth");
|
||||
var cors = require('cors');
|
||||
const { SESSION_SECRET } = require('../utils/consts');
|
||||
const createGlobalModule = require('../utils/createGlobalModule');
|
||||
const sessionsStore = require('./sessions-store');
|
||||
const cookieParser = require('cookie-parser');
|
||||
|
||||
const createExpressApp = (router) => {
|
||||
|
||||
const app = express();
|
||||
const routerBasePath = process.env.LOCAL ? `/dev` : `/.netlify/functions`
|
||||
|
||||
app.use(cookieParser());
|
||||
app.use(cors({
|
||||
origin: ['http://localhost:3000', 'https://studio.apollographql.com'],
|
||||
credentials: true,
|
||||
}))
|
||||
|
||||
app.use(session({
|
||||
secret: SESSION_SECRET,
|
||||
resave: false,
|
||||
store: sessionsStore,
|
||||
saveUninitialized: true,
|
||||
cookie: {
|
||||
maxAge: 30 * 24 * 60 * 60 * 1000 // 30 days
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
passport.use(new lnurlAuth.Strategy(function (linkingPublicKey, done) {
|
||||
// The user has successfully authenticated using lnurl-auth.
|
||||
// The linked public key is provided here.
|
||||
// You can use this as a unique reference for the user similar to a username or email address.
|
||||
const user = { id: linkingPublicKey };
|
||||
done(null, user);
|
||||
}));
|
||||
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
app.use(passport.authenticate("lnurl-auth"));
|
||||
passport.serializeUser(function (user, done) {
|
||||
done(null, user?.id);
|
||||
});
|
||||
passport.deserializeUser(function (id, done) {
|
||||
done(null, { id } || null);
|
||||
});
|
||||
|
||||
if (router)
|
||||
app.use(routerBasePath, router);
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
const createExpressApp = require("./express-app");
|
||||
const sessionsStore = require("./sessions-store");
|
||||
|
||||
|
||||
module.exports = {
|
||||
createExpressApp,
|
||||
sessionsStore,
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
const createGlobalModule = require("../utils/createGlobalModule");
|
||||
|
||||
let sessionsStore;
|
||||
|
||||
const createSessionStore = () => {
|
||||
const session = require("express-session");
|
||||
var Store = require('connect-pg-simple')(session);
|
||||
console.log("New Sessions Store");
|
||||
return new Store({
|
||||
createTableIfMissing: true,
|
||||
tableName: "user_sessions",
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
sessionsStore = createGlobalModule('sessions-store', createSessionStore);
|
||||
|
||||
module.exports = sessionsStore;
|
||||
6
api/utils/generateId.js
Normal file
6
api/utils/generateId.js
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
const crypto = require('crypto');
|
||||
|
||||
const generateId = () => crypto.randomUUID({});
|
||||
|
||||
module.exports = generateId;
|
||||
Reference in New Issue
Block a user