From 58a9e1edd12ac76a4ef3eccdf2641fb5e4ba5189 Mon Sep 17 00:00:00 2001 From: MTG2000 Date: Thu, 2 Jun 2022 13:22:06 +0300 Subject: [PATCH] refactor: auth funcs --- functions/auth/login.js | 89 ++++++++++--------- functions/auth/services/lnurl.service.js | 14 +-- functions/graphql/index.js | 28 +++--- .../Auth/pages/LoginPage/LoginPage.tsx | 21 +++-- 4 files changed, 86 insertions(+), 66 deletions(-) diff --git a/functions/auth/login.js b/functions/auth/login.js index 23adaff..4faaa09 100644 --- a/functions/auth/login.js +++ b/functions/auth/login.js @@ -20,52 +20,61 @@ async function login(tag, k1, sig, key) { return { status: 'ERROR', reason: 'Not a login request' } } - const result = LnurlService.verifySig(sig, k1, key) - if (!result) { + try { + await LnurlService.verifySig(sig, k1, key) + } catch (error) { return { status: 'ERROR', reason: 'Invalid Signature' } } - 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` - } + + 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, }) - } + return { + status: 'OK', + 'headers': { + 'Set-Cookie': authCookie, + 'Cache-Control': 'no-cache', + }, + } + } catch (error) { + return { status: 'ERROR', reason: 'Unexpected error happened, please try again' } - - // Set cookies on the user's headers - const hour = 3600000 - const maxAge = 30 * 24 * hour - const jwtSecret = CONSTS.JWT_SECRET; - - - 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, - }) - - return { - status: 'OK', - 'headers': { - 'Set-Cookie': authCookie, - 'Cache-Control': 'no-cache', - }, } } diff --git a/functions/auth/services/lnurl.service.js b/functions/auth/services/lnurl.service.js index 802daaa..4eb4a1c 100644 --- a/functions/auth/services/lnurl.service.js +++ b/functions/auth/services/lnurl.service.js @@ -29,7 +29,7 @@ function isHashUsed(hash) { } function addHash(hash) { - prisma.generatedK1.create({ + return prisma.generatedK1.create({ data: { value: hash, } @@ -37,7 +37,7 @@ function addHash(hash) { } function removeHash(hash) { - prisma.generatedK1.delete({ + return prisma.generatedK1.delete({ where: { value: hash, } @@ -48,7 +48,7 @@ function removeExpiredHashes() { const now = new Date(); const lastHourDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours() - 1, now.getMinutes()); - prisma.generatedK1.deleteMany({ + return prisma.generatedK1.deleteMany({ where: { createdAt: { lt: lastHourDate @@ -60,7 +60,7 @@ function removeExpiredHashes() { async function generateAuthUrl() { const hostname = CONSTS.LNURL_AUTH_HOST; const secret = await generateSecret() - addHash(createHash(secret)) + await addHash(createHash(secret)) const url = `${hostname}?tag=login&k1=${secret}` return { url, @@ -69,12 +69,15 @@ async function generateAuthUrl() { } } -function verifySig(sig, k1, key) { +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 } } @@ -94,5 +97,6 @@ module.exports = { generateAuthUrl: generateAuthUrl, verifySig: verifySig, removeHash: removeHash, + createHash: createHash, removeExpiredHashes: removeExpiredHashes } \ No newline at end of file diff --git a/functions/graphql/index.js b/functions/graphql/index.js index 72f1a34..f217fa2 100644 --- a/functions/graphql/index.js +++ b/functions/graphql/index.js @@ -4,25 +4,25 @@ const cookie = require('cookie') const jose = require('jose'); const { CONSTS } = require('../utils'); +const extractKey = async (cookieHeader) => { + const cookies = cookie.parse(cookieHeader ?? ''); + const authToken = cookies.Authorization; + if (authToken) { + const token = authToken.split(' ')[1]; + const { payload } = await jose.jwtVerify(token, Buffer.from(CONSTS.JWT_SECRET), { + algorithms: ['HS256'], + }) + return payload.pubKey + } + return null; +} const server = new ApolloServer({ schema, - context: async ({ event }) => { - const cookies = cookie.parse(event.headers.Cookie ?? ''); - const authToken = cookies.Authorization; - if (authToken) { - const token = authToken.split(' ')[1]; - const { payload } = await jose.jwtVerify(token, Buffer.from(CONSTS.JWT_SECRET), { - algorithms: ['HS256'], - }) - return { userPubKey: payload.pubKey } - } - - return { - - }; + const userPubKey = await extractKey(event.headers.Cookie) + return { userPubKey, } }, }); diff --git a/src/features/Auth/pages/LoginPage/LoginPage.tsx b/src/features/Auth/pages/LoginPage/LoginPage.tsx index 80735ea..f190933 100644 --- a/src/features/Auth/pages/LoginPage/LoginPage.tsx +++ b/src/features/Auth/pages/LoginPage/LoginPage.tsx @@ -1,3 +1,4 @@ +import { useMountEffect } from "@react-hookz/web"; import { useEffect, useState } from "react" import { BsFillLightningChargeFill } from "react-icons/bs"; import { Grid } from "react-loader-spinner"; @@ -5,6 +6,14 @@ import { useNavigate } from "react-router-dom"; import { useMeQuery } from "src/graphql" + +const getLnurlAuth = async () => { + const res = await fetch(process.env.REACT_APP_AUTH_END_POINT! + '/login') + const data = await res.json() + return data; +} + + export default function LoginPage() { const [loadingLnurl, setLoadingLnurl] = useState(true) const [lnurlAuth, setLnurlAuth] = useState(""); @@ -27,14 +36,12 @@ export default function LoginPage() { - useEffect(() => { - (async () => { - const res = await fetch(process.env.REACT_APP_AUTH_END_POINT! + '/login') - const data = await res.json() + useMountEffect(() => { + getLnurlAuth().then(data => { setLoadingLnurl(false); setLnurlAuth(data.encoded) - })(); - }, []) + }) + }) @@ -63,7 +70,7 @@ export default function LoginPage() {

Login

-

+

Zero credentials authentication.
All you need is a connected