diff --git a/api/auth/services/lnurl.service.js b/api/auth/services/lnurl.service.js new file mode 100644 index 0000000..a94c1aa --- /dev/null +++ b/api/auth/services/lnurl.service.js @@ -0,0 +1,245 @@ +const lnurl = require('lnurl') +const crypto = require('crypto') +const { prisma } = require('../../prisma') +const { CONSTS } = require('../../utils') + +async function generateSecret() { + let secret = null + const maxAttempts = 5 + let attempt = 0 + while (secret === null && attempt < maxAttempts) { + secret = crypto.randomBytes(32).toString('hex') + const hash = createHash(secret) + const isUsed = await isHashUsed(hash); + if (isUsed) { + secret = null + } + attempt++ + } + if (!secret) { + const message = 'Too many failed attempts to generate unique secret' + throw new Error(message) + } + return secret +} + +function isHashUsed(hash) { + return prisma.generatedK1.findFirst({ where: { value: hash } }) +} + +function addHash(hash, sid) { + return prisma.generatedK1.create({ + data: { + value: hash, + sid, + } + }) +} + +function removeHash(hash) { + return prisma.generatedK1.delete({ + where: { + value: hash, + } + }) +} + +function removeExpiredHashes() { + const now = new Date(); + const lastHourDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours() - 1, now.getMinutes()); + + return prisma.generatedK1.deleteMany({ + where: { + createdAt: { + lt: lastHourDate + } + } + }) +} + +async function generateAuthUrl(sid) { + const hostname = CONSTS.LNURL_AUTH_HOST; + const secret = await generateSecret() + await addHash(createHash(secret), sid) + const url = `${hostname}?tag=login&k1=${secret}` + return { + url, + encoded: lnurl.encode(url).toUpperCase(), + secret, + } +} + +async function getSidByK1(k1) { + const hash = createHash(k1) + const data = await prisma.generatedK1.findFirst({ + where: { + value: hash, + } + }); + return data.sid; +} + +async function verifySig(sig, k1, key) { + if (!lnurl.verifyAuthorizationSignature(sig, k1, key)) { + const message = 'Signature verification failed' + throw new Error(message) + } + const hash = createHash(k1) + const hashExist = await isHashUsed(hash); + if (!hashExist) + throw new Error('Provided k1 is not issued by server') + return { key, hash } +} + +function createHash(data) { + if (!(typeof data === 'string' || Buffer.isBuffer(data))) { + throw new Error( + JSON.stringify({ status: 'ERROR', reason: 'Secret must be a string or a Buffer' }) + ) + } + if (typeof data === 'string') { + data = Buffer.from(data, 'hex') + } + return crypto.createHash('sha256').update(data).digest('hex') +} + + + + +// 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 +} \ No newline at end of file diff --git a/functions/auth/utils/helperFuncs.js b/api/auth/utils/helperFuncs.js similarity index 100% rename from functions/auth/utils/helperFuncs.js rename to api/auth/utils/helperFuncs.js diff --git a/functions/graphql/index.js b/api/functions/graphql/index.js similarity index 93% rename from functions/graphql/index.js rename to api/functions/graphql/index.js index 2a9a841..274dc12 100644 --- a/functions/graphql/index.js +++ b/api/functions/graphql/index.js @@ -1,6 +1,6 @@ const { ApolloServer } = require("apollo-server-lambda"); const schema = require('./schema') -const { createExpressApp } = require('../utils/express-app') +const { createExpressApp } = require("../../modules"); const server = new ApolloServer({ diff --git a/functions/graphql/nexus-typegen.ts b/api/functions/graphql/nexus-typegen.ts similarity index 100% rename from functions/graphql/nexus-typegen.ts rename to api/functions/graphql/nexus-typegen.ts diff --git a/functions/graphql/schema.graphql b/api/functions/graphql/schema.graphql similarity index 100% rename from functions/graphql/schema.graphql rename to api/functions/graphql/schema.graphql diff --git a/functions/graphql/schema/index.js b/api/functions/graphql/schema/index.js similarity index 100% rename from functions/graphql/schema/index.js rename to api/functions/graphql/schema/index.js diff --git a/functions/graphql/types/_scalars.js b/api/functions/graphql/types/_scalars.js similarity index 100% rename from functions/graphql/types/_scalars.js rename to api/functions/graphql/types/_scalars.js diff --git a/functions/graphql/types/category.js b/api/functions/graphql/types/category.js similarity index 98% rename from functions/graphql/types/category.js rename to api/functions/graphql/types/category.js index 7bec1ae..0e7b205 100644 --- a/functions/graphql/types/category.js +++ b/api/functions/graphql/types/category.js @@ -4,7 +4,7 @@ const { extendType, nonNull, } = require('nexus'); -const { prisma } = require('../../prisma') +const { prisma } = require('../../../prisma') const Category = objectType({ diff --git a/functions/graphql/types/donation.js b/api/functions/graphql/types/donation.js similarity index 97% rename from functions/graphql/types/donation.js rename to api/functions/graphql/types/donation.js index 6f6e645..b94521a 100644 --- a/functions/graphql/types/donation.js +++ b/api/functions/graphql/types/donation.js @@ -7,8 +7,8 @@ const { extendType, nonNull, } = require('nexus'); -const { prisma } = require('../../prisma'); -const { CONSTS } = require('../../utils'); +const { prisma } = require('../../../prisma'); +const { CONSTS } = require('../../../utils'); const { getPaymetRequestForItem, hexToUint8Array } = require('./helpers'); diff --git a/functions/graphql/types/hackathon.js b/api/functions/graphql/types/hackathon.js similarity index 97% rename from functions/graphql/types/hackathon.js rename to api/functions/graphql/types/hackathon.js index cb88cdc..5b434f3 100644 --- a/functions/graphql/types/hackathon.js +++ b/api/functions/graphql/types/hackathon.js @@ -5,7 +5,7 @@ const { extendType, nonNull, } = require('nexus'); -const { prisma } = require('../../prisma') +const { prisma } = require('../../../prisma'); diff --git a/functions/graphql/types/helpers.js b/api/functions/graphql/types/helpers.js similarity index 100% rename from functions/graphql/types/helpers.js rename to api/functions/graphql/types/helpers.js diff --git a/functions/graphql/types/index.js b/api/functions/graphql/types/index.js similarity index 100% rename from functions/graphql/types/index.js rename to api/functions/graphql/types/index.js diff --git a/functions/graphql/types/post.js b/api/functions/graphql/types/post.js similarity index 99% rename from functions/graphql/types/post.js rename to api/functions/graphql/types/post.js index 4564260..8bcd06c 100644 --- a/functions/graphql/types/post.js +++ b/api/functions/graphql/types/post.js @@ -11,8 +11,8 @@ const { inputObjectType, } = require('nexus'); const { paginationArgs } = require('./helpers'); -const { prisma } = require('../../prisma'); -const { getUserByPubKey } = require('../../auth/utils/helperFuncs'); +const { prisma } = require('../../../prisma'); +const { getUserByPubKey } = require('../../../auth/utils/helperFuncs'); const { ApolloError } = require('apollo-server-lambda'); diff --git a/functions/graphql/types/project.js b/api/functions/graphql/types/project.js similarity index 99% rename from functions/graphql/types/project.js rename to api/functions/graphql/types/project.js index 8085860..52ca97e 100644 --- a/functions/graphql/types/project.js +++ b/api/functions/graphql/types/project.js @@ -5,7 +5,7 @@ const { extendType, nonNull, } = require('nexus') -const { prisma } = require('../../prisma') +const { prisma } = require('../../../prisma'); const { paginationArgs, getLnurlDetails, lightningAddressToLnurl } = require('./helpers'); diff --git a/functions/graphql/types/users.js b/api/functions/graphql/types/users.js similarity index 95% rename from functions/graphql/types/users.js rename to api/functions/graphql/types/users.js index 30e25e4..3a83b5c 100644 --- a/functions/graphql/types/users.js +++ b/api/functions/graphql/types/users.js @@ -1,6 +1,7 @@ -const { prisma } = require("../../prisma"); + +const { prisma } = require('../../../prisma'); const { objectType, extendType, intArg, nonNull, inputObjectType } = require("nexus"); -const { getUserByPubKey } = require("../../auth/utils/helperFuncs"); +const { getUserByPubKey } = require("../../../auth/utils/helperFuncs"); const { removeNulls } = require("./helpers"); diff --git a/functions/graphql/types/vote.js b/api/functions/graphql/types/vote.js similarity index 98% rename from functions/graphql/types/vote.js rename to api/functions/graphql/types/vote.js index 276f334..bde3766 100644 --- a/functions/graphql/types/vote.js +++ b/api/functions/graphql/types/vote.js @@ -10,8 +10,8 @@ const { const { parsePaymentRequest } = require('invoices'); const { getPaymetRequestForItem, hexToUint8Array } = require('./helpers'); const { createHash } = require('crypto'); -const { prisma } = require('../../prisma'); -const { CONSTS } = require('../../utils'); +const { prisma } = require('../../../prisma'); +const { CONSTS } = require('../../../utils'); diff --git a/api/functions/login/login.js b/api/functions/login/login.js new file mode 100644 index 0000000..c9eb4aa --- /dev/null +++ b/api/functions/login/login.js @@ -0,0 +1,91 @@ + +const { prisma } = require('../../prisma'); +const LnurlService = require('../../auth/services/lnurl.service') +const serverless = require('serverless-http'); +const { getSidByK1 } = require('../../auth/services/lnurl.service'); +const { sessionsStore, createExpressApp } = require('../../modules'); +const express = require('express'); + + +const loginHandler = async (req, res) => { + const { tag, k1, sig, key } = req.query; + // Generate an auth URL + if (!sig || !key) { + + const data = await LnurlService.generateAuthUrl(req.sessionID); + return res.status(200).json(data); + } + else { + if (tag !== 'login') + return res.status(400).send("Invalid tag provided") + + // Verify login params + try { + await LnurlService.verifySig(sig, k1, key) + } catch (error) { + return res.status(400).json({ status: 'ERROR', reason: 'Invalid Signature' }) + + } + + + try { + //Create user if not already existing + const user = await prisma.user.findFirst({ where: { pubKey: key } }) + if (user === null) { + await prisma.user.create({ + data: { + pubKey: key, + name: key, + avatar: `https://avatars.dicebear.com/api/bottts/${key}.svg` + } + }) + } + + + // 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() + }) + }); + + + LnurlService.removeHash(LnurlService.createHash(k1)).catch(); + LnurlService.removeExpiredHashes().catch(); + + return res.status(200).json({ status: "OK" }) + + } catch (error) { + console.log(error); + return res.status(200).json({ status: 'ERROR', reason: 'Unexpected error happened, please try again' }) + } + } +} + + +let app; + +if (process.env.NETLIFY) { + const router = express.Router(); + router.get('/login', loginHandler) + app = createExpressApp(router) +} +else { + app = createExpressApp() + app.get('/login', loginHandler); +} + + +const handler = serverless(app); +exports.handler = async (event, context) => { + return await handler(event, context); +}; diff --git a/api/functions/logout/logout.js b/api/functions/logout/logout.js new file mode 100644 index 0000000..73724df --- /dev/null +++ b/api/functions/logout/logout.js @@ -0,0 +1,29 @@ +const serverless = require('serverless-http'); +const { createExpressApp } = require('../../modules'); +const express = require('express'); + +const logoutHandler = (req, res, next) => { + if (req.user) { + req.session.destroy(); + return res.redirect("/"); + } + next(); +}; + +let app; + +if (process.env.NETLIFY) { + const router = express.Router(); + router.get('/login', logoutHandler) + app = createExpressApp(router) +} +else { + app = createExpressApp() + app.get('/login', logoutHandler); +} + + +const handler = serverless(app); +exports.handler = async (event, context) => { + return await handler(event, context); +}; diff --git a/functions/utils/express-app.js b/api/modules/express-app.js similarity index 77% rename from functions/utils/express-app.js rename to api/modules/express-app.js index 80b7854..8cb54d1 100644 --- a/functions/utils/express-app.js +++ b/api/modules/express-app.js @@ -3,14 +3,17 @@ const express = require('express'); const session = require("express-session"); const passport = require("passport"); const lnurlAuth = require("passport-lnurl-auth"); -const { sessionsStore } = require('./sessionsStore'); var cors = require('cors'); -const { SESSION_SECRET } = require('./consts'); +const { SESSION_SECRET } = require('../utils/consts'); +const createGlobalModule = require('../utils/createGlobalModule'); +const sessionsStore = require('./sessions-store'); -const createExpressApp = () => { +const createExpressApp = (router) => { const app = express(); + const routerBasePath = process.env.NETLIFY ? `/.netlify/functions` : `/dev` + app.use(cors({ origin: ['http://localhost:3000', 'https://studio.apollographql.com'], credentials: true, @@ -45,14 +48,11 @@ const createExpressApp = () => { done(null, { id } || null); }); + if (router) + app.use(routerBasePath, router); + return app; } -let expressApp; -if (!global.expressApp) { - global.expressApp = createExpressApp(); -} -expressApp = global.expressApp; - -module.exports = { createExpressApp: () => expressApp }; \ No newline at end of file +module.exports = createExpressApp; \ No newline at end of file diff --git a/api/modules/index.js b/api/modules/index.js new file mode 100644 index 0000000..12c6d6c --- /dev/null +++ b/api/modules/index.js @@ -0,0 +1,8 @@ +const createExpressApp = require("./express-app"); +const sessionsStore = require("./sessions-store"); + + +module.exports = { + createExpressApp, + sessionsStore, +} \ No newline at end of file diff --git a/functions/utils/sessionsStore.js b/api/modules/sessions-store.js similarity index 50% rename from functions/utils/sessionsStore.js rename to api/modules/sessions-store.js index 36ab925..4b0b1dd 100644 --- a/functions/utils/sessionsStore.js +++ b/api/modules/sessions-store.js @@ -1,16 +1,19 @@ +const createGlobalModule = require("../utils/createGlobalModule"); let sessionsStore; -if (!global.sessionsStore) { +const createSessionStore = () => { const session = require("express-session"); var Store = require('connect-pg-simple')(session); console.log("New Sessions Store"); - global.sessionsStore = new Store({ + return new Store({ createTableIfMissing: true, tableName: "user_sessions", - }); + }) } -sessionsStore = global.sessionsStore; -module.exports = { sessionsStore }; \ No newline at end of file + +sessionsStore = createGlobalModule('sessions-store', createSessionStore); + +module.exports = sessionsStore; \ No newline at end of file diff --git a/functions/prisma/index.d.ts b/api/prisma/index.d.ts similarity index 100% rename from functions/prisma/index.d.ts rename to api/prisma/index.d.ts diff --git a/api/prisma/index.js b/api/prisma/index.js new file mode 100644 index 0000000..2013b99 --- /dev/null +++ b/api/prisma/index.js @@ -0,0 +1,16 @@ +const { PrismaClient } = require('@prisma/client'); +const createGlobalModule = require('../utils/createGlobalModule'); + + +const createPrismaClient = () => { + console.log("New Prisma Client"); + return new PrismaClient({ + log: ["info"], + }); +} + +const prisma = createGlobalModule('prisma', createPrismaClient) + +module.exports = { + prisma +} \ No newline at end of file diff --git a/functions/utils/consts.js b/api/utils/consts.js similarity index 100% rename from functions/utils/consts.js rename to api/utils/consts.js diff --git a/api/utils/createGlobalModule.js b/api/utils/createGlobalModule.js new file mode 100644 index 0000000..c1a0234 --- /dev/null +++ b/api/utils/createGlobalModule.js @@ -0,0 +1,9 @@ +const createGlobalModule = (name, factoryFn) => { + if (!global[name]) { + global[name] = factoryFn(); + } + return global[name]; +} + +module.exports = createGlobalModule + diff --git a/functions/utils/index.js b/api/utils/index.js similarity index 100% rename from functions/utils/index.js rename to api/utils/index.js diff --git a/functions/auth/services/lnurl.service.js b/functions/auth/services/lnurl.service.js deleted file mode 100644 index 9b74e19..0000000 --- a/functions/auth/services/lnurl.service.js +++ /dev/null @@ -1,252 +0,0 @@ -const lnurl = require('lnurl') -const crypto = require('crypto') -const { prisma } = require('../../prisma') -const { CONSTS } = require('../../utils') -const express = require('express'); -const session = require("express-session"); -const passport = require("passport"); -const lnurlAuth = require("passport-lnurl-auth"); -const assert = require('assert'); -const { HttpError } = require('lnurl/lib'); -var SQLiteStore = require('connect-sqlite3')(session); - -async function generateSecret() { - let secret = null - const maxAttempts = 5 - let attempt = 0 - while (secret === null && attempt < maxAttempts) { - secret = crypto.randomBytes(32).toString('hex') - const hash = createHash(secret) - const isUsed = await isHashUsed(hash); - if (isUsed) { - secret = null - } - attempt++ - } - if (!secret) { - const message = 'Too many failed attempts to generate unique secret' - throw new Error(message) - } - return secret -} - -function isHashUsed(hash) { - return prisma.generatedK1.findFirst({ where: { value: hash } }) -} - -function addHash(hash, sid) { - return prisma.generatedK1.create({ - data: { - value: hash, - sid, - } - }) -} - -function removeHash(hash) { - return prisma.generatedK1.delete({ - where: { - value: hash, - } - }) -} - -function removeExpiredHashes() { - const now = new Date(); - const lastHourDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours() - 1, now.getMinutes()); - - return prisma.generatedK1.deleteMany({ - where: { - createdAt: { - lt: lastHourDate - } - } - }) -} - -async function generateAuthUrl(sid) { - const hostname = CONSTS.LNURL_AUTH_HOST; - const secret = await generateSecret() - await addHash(createHash(secret), sid) - const url = `${hostname}?tag=login&k1=${secret}` - return { - url, - encoded: lnurl.encode(url).toUpperCase(), - secret, - } -} - -async function getSidByK1(k1) { - const hash = createHash(k1) - const data = await prisma.generatedK1.findFirst({ - where: { - value: hash, - } - }); - return data.sid; -} - -async function verifySig(sig, k1, key) { - if (!lnurl.verifyAuthorizationSignature(sig, k1, key)) { - const message = 'Signature verification failed' - throw new Error(message) - } - const hash = createHash(k1) - const hashExist = await isHashUsed(hash); - if (!hashExist) - throw new Error('Provided k1 is not issued by server') - return { key, hash } -} - -function createHash(data) { - if (!(typeof data === 'string' || Buffer.isBuffer(data))) { - throw new Error( - JSON.stringify({ status: 'ERROR', reason: 'Secret must be a string or a Buffer' }) - ) - } - if (typeof data === 'string') { - data = Buffer.from(data, 'hex') - } - return crypto.createHash('sha256').update(data).digest('hex') -} - - - - -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 -} \ No newline at end of file diff --git a/functions/login/login.js b/functions/login/login.js deleted file mode 100644 index 4e1e5ff..0000000 --- a/functions/login/login.js +++ /dev/null @@ -1,194 +0,0 @@ - -const { prisma } = require('../prisma'); -const LnurlService = require('../auth/services/lnurl.service') -const cookie = require('cookie') -const jose = require('jose'); -const { CONSTS } = require('../utils'); -const { CORS_HEADERS } = require('../utils/consts'); -const serverless = require('serverless-http'); -// const { expressApp } = require('../utils/express-app'); -// const { sessionsStore } = require('../utils/sessionsStore'); -const { getSidByK1 } = require('../auth/services/lnurl.service'); -const express = require('express') - -const session = require("express-session"); -const passport = require("passport"); -const lnurlAuth = require("passport-lnurl-auth"); -// const { sessionsStore } = require('./sessionsStore'); -var cors = require('cors'); -const { createExpressApp } = require('../utils/express-app'); -const { sessionsStore } = require('../utils/sessionsStore'); - -async function login(req, res) { - - const { tag, k1, sig, key } = req.query; - if (tag !== 'login') { - return { - statusCode: 400, - CORS_HEADERS, - body: JSON.stringify({ status: 'ERROR', reason: 'Not a login request' }) - } - } - - try { - await LnurlService.verifySig(sig, k1, key) - } catch (error) { - return { - statusCode: 400, - CORS_HEADERS, - body: JSON.stringify({ status: 'ERROR', reason: 'Invalid Signature' }) - } - } - - - try { - const user = await prisma.user.findFirst({ where: { pubKey: key } }) - if (user === null) { - await prisma.user.create({ - data: { - pubKey: key, - name: key, - avatar: `https://avatars.dicebear.com/api/bottts/${key}.svg` - } - }) - } - - - - // Set cookies on the user's headers - const hour = 3600000 - const maxAge = 30 * 24 * hour - const jwtSecret = CONSTS.JWT_SECRET; - - LnurlService.removeHash(LnurlService.createHash(k1)).catch(); - LnurlService.removeExpiredHashes().catch(); - - const jwt = await new jose.SignJWT({ pubKey: key }) - .setProtectedHeader({ alg: 'HS256' }) - .setIssuedAt() - .setExpirationTime(maxAge) - //TODO: Set audience, issuer - .sign(Buffer.from(jwtSecret, 'utf-8')) - - - - const authCookie = cookie.serialize('Authorization', `Bearer ${jwt}`, { - secure: true, - httpOnly: true, - path: '/', - maxAge: maxAge, - sameSite: 'none', - }) - - return { - - statusCode: 200, - 'headers': { - 'Set-Cookie': authCookie, - 'Cache-Control': 'no-cache', - ...CORS_HEADERS - }, - body: JSON.stringify({ - status: 'OK', - }) - } - } catch (error) { - return { - statusCode: 200, - headers: CORS_HEADERS, - body: JSON.stringify({ status: 'ERROR', reason: 'Unexpected error happened, please try again' }) - } - - - } -} - -const app = createExpressApp(); - -app.get('/login', async (req, res) => { - const { tag, k1, sig, key } = req.query; - - // Generate an auth URL - if (!sig || !key) { - - const data = await LnurlService.generateAuthUrl(req.sessionID); - return res.status(200).json(data); - } - else { - if (tag !== 'login') - return res.status(400).send("Invalid tag provided") - - // Verify login params - try { - await LnurlService.verifySig(sig, k1, key) - } catch (error) { - return res.status(400).json({ status: 'ERROR', reason: 'Invalid Signature' }) - - } - - - try { - //Create user if not already existing - const user = await prisma.user.findFirst({ where: { pubKey: key } }) - if (user === null) { - await prisma.user.create({ - data: { - pubKey: key, - name: key, - avatar: `https://avatars.dicebear.com/api/bottts/${key}.svg` - } - }) - } - - - // 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() - }) - }); - - - LnurlService.removeHash(LnurlService.createHash(k1)).catch(); - LnurlService.removeExpiredHashes().catch(); - - return res.status(200).json({ status: "OK" }) - - // const jwt = await new jose.SignJWT({ pubKey: key }) - // .setProtectedHeader({ alg: 'HS256' }) - // .setIssuedAt() - // .setExpirationTime(maxAge) - // //TODO: Set audience, issuer - // .sign(Buffer.from(jwtSecret, 'utf-8')) - - - - // const authCookie = cookie.serialize('Authorization', `Bearer ${jwt}`, { - // secure: true, - // httpOnly: true, - // path: '/', - // maxAge: maxAge, - // sameSite: 'none', - // }) - - - } catch (error) { - console.log(error); - return res.status(200).json({ status: 'ERROR', reason: 'Unexpected error happened, please try again' }) - } - } -}) - -const handler = serverless(app); -exports.handler = async (event, context) => { - return await handler(event, context); -}; \ No newline at end of file diff --git a/functions/logout/logout.js b/functions/logout/logout.js deleted file mode 100644 index 7c11e75..0000000 --- a/functions/logout/logout.js +++ /dev/null @@ -1,16 +0,0 @@ -const serverless = require('serverless-http'); -const { createExpressApp } = require('../utils/express-app'); -const app = createExpressApp(); - -app.get('/logout', (req, res, next) => { - if (req.user) { - req.session.destroy(); - return res.redirect("/"); - } - next(); -}) - -const handler = serverless(app); -exports.handler = async (event, context) => { - return await handler(event, context); -}; \ No newline at end of file diff --git a/functions/prisma/index.js b/functions/prisma/index.js deleted file mode 100644 index 5057359..0000000 --- a/functions/prisma/index.js +++ /dev/null @@ -1,15 +0,0 @@ -const { PrismaClient } = require('@prisma/client') - -let prisma; - -if (!global.prisma) { - console.log("New Prisma Client"); - global.prisma = new PrismaClient({ - log: ["info"], - }); -} -prisma = global.prisma; - -module.exports = { - prisma -} \ No newline at end of file diff --git a/netlify.toml b/netlify.toml index 8ed2427..e8e832f 100644 --- a/netlify.toml +++ b/netlify.toml @@ -1,7 +1,11 @@ + + [build] - functions = "functions" # netlify-lambda builds to this folder AND Netlify reads functions from here + functions = "api/functions" # netlify-lambda builds to this folder AND Netlify reads functions from here [dev] framework = "#static" - functions = "functions" # netlify-lambda builds to this folder AND Netlify reads functions from here + functions = "api/functions" # netlify-lambda builds to this folder AND Netlify reads functions from here publish = "build" # create-react-app builds to this folder, Netlify should serve all these files statically + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index cc346af..5ac0a73 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "axios": "^0.26.1", "better-sqlite3": "^7.5.3", "better-sqlite3-session-store": "^0.0.3", + "body-parser": "^1.20.0", "chance": "^1.1.8", "connect-pg-simple": "^7.0.0", "connect-sqlite3": "^0.9.13", diff --git a/package.json b/package.json index dfe098d..2610016 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "axios": "^0.26.1", "better-sqlite3": "^7.5.3", "better-sqlite3-session-store": "^0.0.3", + "body-parser": "^1.20.0", "chance": "^1.1.8", "connect-pg-simple": "^7.0.0", "connect-sqlite3": "^0.9.13", @@ -105,7 +106,7 @@ "db:reset": "prisma migrate reset", "db:seed": "prisma db seed", "db:gui": "prisma studio", - "netlify:start": "netlify dev" + "netlify:start": "set NETLIFY=true&& netlify dev" }, "prisma": { "seed": "node prisma/seed/index.js" diff --git a/serverless.yml b/serverless.yml index 68e383d..deb7eb8 100644 --- a/serverless.yml +++ b/serverless.yml @@ -14,7 +14,7 @@ provider: functions: graphql: - handler: functions/graphql/index.handler + handler: api/functions/graphql/index.handler events: - http: path: graphql @@ -23,7 +23,7 @@ functions: path: graphql method: get login: - handler: functions/login/login.handler + handler: api/functions/login/login.handler events: - http: path: login @@ -32,7 +32,7 @@ functions: path: login method: get logout: - handler: functions/logout/logout.handler + handler: api/functions/logout/logout.handler events: - http: path: logout