diff --git a/api/resolvers/index.js b/api/resolvers/index.js
index 9bfe0ea4..484e2f7d 100644
--- a/api/resolvers/index.js
+++ b/api/resolvers/index.js
@@ -2,5 +2,6 @@ import user from './user'
import message from './message'
import item from './item'
import wallet from './wallet'
+import lnurl from './lnurl'
-export default [user, item, message, wallet]
+export default [user, item, message, wallet, lnurl]
diff --git a/api/resolvers/item.js b/api/resolvers/item.js
index b721c4b5..9690b77e 100644
--- a/api/resolvers/item.js
+++ b/api/resolvers/item.js
@@ -91,7 +91,6 @@ export default {
if (!me) {
throw new AuthenticationError('you must be logged in')
}
- const user = await models.user.findUnique({ where: { name: me.name } })
comments = await models.$queryRaw(`
${SELECT}
From "Item"
@@ -99,7 +98,7 @@ export default {
AND "Item"."userId" <> $1 AND "Item".created_at <= $2
ORDER BY "Item".created_at DESC
OFFSET $3
- LIMIT ${LIMIT}`, user.id, decodedCursor.time, decodedCursor.offset)
+ LIMIT ${LIMIT}`, me.id, decodedCursor.time, decodedCursor.offset)
}
return {
cursor: comments.length === LIMIT ? nextCursorEncoded(decodedCursor) : null,
@@ -110,14 +109,13 @@ export default {
if (!me) {
throw new AuthenticationError('you must be logged in')
}
- const user = await models.user.findUnique({ where: { name: me.name } })
return await models.$queryRaw(`
${SELECT}
From "Item"
JOIN "Item" p ON "Item"."parentId" = p.id AND p."userId" = $1
AND "Item"."userId" <> $1
- ORDER BY "Item".created_at DESC`, user.id)
+ ORDER BY "Item".created_at DESC`, me.id)
},
item: async (parent, { id }, { models }) => {
const [item] = await models.$queryRaw(`
@@ -229,15 +227,13 @@ export default {
meSats: async (item, args, { me, models }) => {
if (!me) return 0
- const meFull = await models.user.findUnique({ where: { name: me.name } })
-
const { sum: { sats } } = await models.vote.aggregate({
sum: {
sats: true
},
where: {
itemId: item.id,
- userId: meFull.id
+ userId: me.id
}
})
diff --git a/api/resolvers/user.js b/api/resolvers/user.js
index eab54024..9a198482 100644
--- a/api/resolvers/user.js
+++ b/api/resolvers/user.js
@@ -3,7 +3,7 @@ import { AuthenticationError, UserInputError } from 'apollo-server-errors'
export default {
Query: {
me: async (parent, args, { models, me }) =>
- me ? await models.user.findUnique({ where: { name: me.name } }) : null,
+ me ? await models.user.findUnique({ where: { id: me.id } }) : null,
user: async (parent, { name }, { models }) => {
return await models.user.findUnique({ where: { name } })
},
@@ -21,7 +21,7 @@ export default {
throw new AuthenticationError('you must be logged in')
}
- const user = await models.user.findUnique({ where: { name: me.name } })
+ const user = await models.user.findUnique({ where: { id: me.id } })
const [{ sum }] = await models.$queryRaw(`
SELECT sum("Vote".sats)
@@ -32,7 +32,7 @@ export default {
AND "Vote".boost = false
WHERE "Item"."userId" = $1`, user.id, user.checkedNotesAt)
- await models.user.update({ where: { name: me.name }, data: { checkedNotesAt: new Date() } })
+ await models.user.update({ where: { id: me.id }, data: { checkedNotesAt: new Date() } })
return sum || 0
}
},
@@ -44,7 +44,7 @@ export default {
}
try {
- await models.user.update({ where: { name: me.name }, data: { name } })
+ await models.user.update({ where: { id: me.id }, data: { name } })
} catch (error) {
if (error.code === 'P2002') {
throw new UserInputError('name taken')
diff --git a/api/resolvers/wallet.js b/api/resolvers/wallet.js
index ff4e3e7d..7d89e6fd 100644
--- a/api/resolvers/wallet.js
+++ b/api/resolvers/wallet.js
@@ -18,7 +18,7 @@ export default {
}
})
- if (inv.user.name !== me.name) {
+ if (inv.user.id !== me.id) {
throw new AuthenticationError('not ur invoice')
}
@@ -38,7 +38,7 @@ export default {
}
})
- if (wdrwl.user.name !== me.name) {
+ if (wdrwl.user.id !== me.id) {
throw new AuthenticationError('not ur withdrawl')
}
@@ -77,7 +77,7 @@ export default {
msatsRequested: amount * 1000,
user: {
connect: {
- name: me.name
+ id: me.id
}
}
}
diff --git a/api/typeDefs/index.js b/api/typeDefs/index.js
index 1bccc60a..025546b9 100644
--- a/api/typeDefs/index.js
+++ b/api/typeDefs/index.js
@@ -4,6 +4,7 @@ import user from './user'
import message from './message'
import item from './item'
import wallet from './wallet'
+import lnurl from './lnurl'
const link = gql`
type Query {
@@ -19,4 +20,4 @@ const link = gql`
}
`
-export default [link, user, item, message, wallet]
+export default [link, user, item, message, wallet, lnurl]
diff --git a/components/invoice.js b/components/invoice.js
index dba26121..51230dc7 100644
--- a/components/invoice.js
+++ b/components/invoice.js
@@ -1,10 +1,6 @@
-import QRCode from 'qrcode.react'
-import { CopyInput, InputSkeleton } from './form'
-import InvoiceStatus from './invoice-status'
+import LnQR from './lnqr'
export function Invoice ({ invoice }) {
- const qrValue = 'lightning:' + invoice.bolt11.toUpperCase()
-
let variant = 'default'
let status = 'waiting for you'
if (invoice.confirmedAt) {
@@ -18,27 +14,5 @@ export function Invoice ({ invoice }) {
status = 'expired'
}
- return (
- <>
-
-
-
-
-
-
-
- >
- )
-}
-
-export function InvoiceSkeleton ({ status }) {
- return (
- <>
-
-
-
-
-
- >
- )
+ return
}
diff --git a/components/upvote.js b/components/upvote.js
index 8eda6dc0..053a7377 100644
--- a/components/upvote.js
+++ b/components/upvote.js
@@ -17,14 +17,14 @@ export default function UpVote ({ itemId, meSats, className }) {
cache.modify({
id: `Item:${itemId}`,
fields: {
+ meSats (existingMeSats = 0) {
+ return existingMeSats + vote
+ },
sats (existingSats = 0) {
- return existingSats || vote
+ return meSats === 0 ? existingSats + vote : existingSats
},
boost (existingBoost = 0) {
return meSats >= 1 ? existingBoost + vote : existingBoost
- },
- meSats (existingMeSats = 0) {
- return existingMeSats + vote
}
}
})
diff --git a/package.json b/package.json
index 414d1b97..d049df97 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,7 @@
"apollo-server-micro": "^2.21.2",
"async-retry": "^1.3.1",
"babel-plugin-inline-react-svg": "^2.0.1",
+ "bech32": "^2.0.0",
"bootstrap": "^4.6.0",
"clipboard-copy": "^4.0.1",
"formik": "^2.2.6",
@@ -28,6 +29,7 @@
"react-bootstrap": "^1.5.2",
"react-dom": "17.0.1",
"sass": "^1.32.8",
+ "secp256k1": "^4.0.2",
"swr": "^0.5.4",
"yup": "^0.32.9"
},
diff --git a/pages/api/auth/[...nextauth].js b/pages/api/auth/[...nextauth].js
index 0c12f4c4..7c464623 100644
--- a/pages/api/auth/[...nextauth].js
+++ b/pages/api/auth/[...nextauth].js
@@ -6,7 +6,65 @@ import prisma from '../../../api/models'
export default (req, res) => NextAuth(req, res, options)
const options = {
+ callbacks: {
+ /**
+ * @param {object} token Decrypted JSON Web Token
+ * @param {object} user User object (only available on sign in)
+ * @param {object} account Provider account (only available on sign in)
+ * @param {object} profile Provider profile (only available on sign in)
+ * @param {boolean} isNewUser True if new user (only available on sign in)
+ * @return {object} JSON Web Token that will be saved
+ */
+ async jwt (token, user, account, profile, isNewUser) {
+ // Add additional session params
+ if (user?.id) {
+ token.id = user.id
+ }
+ // XXX We need to update the user name incase they update it ... kind of hacky
+ // better if we use user id everywhere an ignore the username ...
+ if (token?.id) {
+ const { name } = await prisma.user.findUnique({ where: { id: token.id } })
+ token.name = name
+ }
+ return token
+ },
+ async session (session, token) {
+ // we need to add additional session params here
+ session.user.id = token.id
+ session.user.name = token.name
+ return session
+ }
+ },
providers: [
+ Providers.Credentials({
+ // The name to display on the sign in form (e.g. 'Sign in with...')
+ name: 'Lightning',
+ // The credentials is used to generate a suitable form on the sign in page.
+ // You can specify whatever fields you are expecting to be submitted.
+ // e.g. domain, username, password, 2FA token, etc.
+ credentials: {
+ pubkey: { label: 'publickey', type: 'text' },
+ k1: { label: 'k1', type: 'text' }
+ },
+ async authorize (credentials, req) {
+ const { k1, pubkey } = credentials
+ try {
+ const lnauth = await prisma.lnAuth.findUnique({ where: { k1 } })
+ if (lnauth.pubkey === pubkey) {
+ let user = await prisma.user.findUnique({ where: { pubkey } })
+ if (!user) {
+ user = await prisma.user.create({ data: { name: pubkey.slice(0, 10), pubkey } })
+ }
+ await prisma.lnAuth.delete({ where: { k1 } })
+ return user
+ }
+ } catch (error) {
+ console.log(error)
+ }
+
+ return null
+ }
+ }),
Providers.GitHub({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
@@ -37,6 +95,10 @@ const options = {
],
adapter: Adapters.Prisma.Adapter({ prisma }),
secret: process.env.SECRET,
+ session: { jwt: true },
+ jwt: {
+ signingKey: process.env.JWT_SIGNING_PRIVATE_KEY
+ },
pages: {
signIn: '/login'
}
diff --git a/pages/invoices/[id].js b/pages/invoices/[id].js
index c07e60c6..80daee96 100644
--- a/pages/invoices/[id].js
+++ b/pages/invoices/[id].js
@@ -1,6 +1,7 @@
import { useQuery } from '@apollo/client'
import gql from 'graphql-tag'
-import { Invoice, InvoiceSkeleton } from '../../components/invoice'
+import { Invoice } from '../../components/invoice'
+import { LnQRSkeleton } from '../../components/lnqr'
import LayoutCenter from '../../components/layout-center'
export async function getServerSideProps ({ params: { id } }) {
@@ -34,7 +35,7 @@ function LoadInvoice ({ query }) {
const { loading, error, data } = useQuery(query, { pollInterval: 1000 })
if (error) return error
if (!data || loading) {
- return
+ return
}
return
diff --git a/pages/login.js b/pages/login.js
index 6c81fb02..0207b341 100644
--- a/pages/login.js
+++ b/pages/login.js
@@ -3,11 +3,15 @@ import Button from 'react-bootstrap/Button'
import styles from '../styles/login.module.css'
import GithubIcon from '../svgs/github-fill.svg'
import TwitterIcon from '../svgs/twitter-fill.svg'
+import LightningIcon from '../svgs/lightning.svg'
import { Input, SubmitButton, SyncForm } from '../components/form'
import * as Yup from 'yup'
-import { useState } from 'react'
+import { useEffect, useState } from 'react'
import Alert from 'react-bootstrap/Alert'
import LayoutCenter from '../components/layout-center'
+import router, { useRouter } from 'next/router'
+import LnQR, { LnQRSkeleton } from '../components/lnqr'
+import { gql, useMutation, useQuery } from '@apollo/client'
export async function getServerSideProps ({ req, res, query: { callbackUrl, error = null } }) {
const session = await getSession({ req })
@@ -33,7 +37,7 @@ export const EmailSchema = Yup.object({
email: Yup.string().email('email is no good').required('required').trim()
})
-export default function login ({ providers, csrfToken, error }) {
+export default function Login ({ providers, csrfToken, error }) {
const errors = {
Signin: 'Try signing with a different account.',
OAuthSignin: 'Try signing with a different account.',
@@ -43,58 +47,123 @@ export default function login ({ providers, csrfToken, error }) {
Callback: 'Try signing with a different account.',
OAuthAccountNotLinked: 'To confirm your identity, sign in with the same account you used originally.',
EmailSignin: 'Check your email address.',
- CredentialsSignin: 'Sign in failed. Check the details you provided are correct.',
+ CredentialsSignin: 'Lightning auth failed.',
default: 'Unable to sign in.'
}
const [errorMessage, setErrorMessage] = useState(error && (errors[error] ?? errors.default))
+ const router = useRouter()
return (
{errorMessage &&
setErrorMessage(undefined)} dismissible>{errorMessage}}
- {Object.values(providers).map(provider => {
- if (provider.name === 'Email') {
- return null
- }
- const [variant, Icon] =
+ {router.query.type === 'lightning'
+ ?
+ : (
+ <>
+
+ {Object.values(providers).map(provider => {
+ if (provider.name === 'Email' || provider.name === 'Lightning') {
+ return null
+ }
+ const [variant, Icon] =
provider.name === 'Twitter'
? ['twitter', TwitterIcon]
: ['dark', GithubIcon]
- return (
-
- )
- })}
-
or
-
-
-
- Login with Email
-
+ return (
+
+ )
+ })}
+
or
+
+
+
+ Login with Email
+
+ >)}
)
}
+
+function LnQRAuth ({ k1, encodedUrl }) {
+ const query = gql`
+ {
+ lnAuth(k1: "${k1}") {
+ pubkey
+ k1
+ }
+ }`
+ const { error, data } = useQuery(query, { pollInterval: 1000 })
+ if (error) return error
+
+ if (data && data.lnAuth.pubkey) {
+ signIn('credentials', { ...data.lnAuth, callbackUrl: router.query.callbackUrl })
+ }
+
+ // output pubkey and k1
+ return (
+ <>
+
+ Does my wallet support lnurl-auth?
+
+
+ >
+ )
+}
+
+export function LightningAuth () {
+ // query for challenge
+ const [createAuth, { data, error }] = useMutation(gql`
+ mutation createAuth {
+ createAuth {
+ k1
+ encodedUrl
+ }
+ }`)
+
+ useEffect(createAuth, [])
+
+ if (error) return error
+
+ if (!data) {
+ return
+ }
+
+ return
+}
diff --git a/pages/wallet.js b/pages/wallet.js
index e931d821..da0333f6 100644
--- a/pages/wallet.js
+++ b/pages/wallet.js
@@ -4,7 +4,7 @@ import Link from 'next/link'
import Button from 'react-bootstrap/Button'
import * as Yup from 'yup'
import { gql, useMutation, useQuery } from '@apollo/client'
-import { InvoiceSkeleton } from '../components/invoice'
+import { LnQRSkeleton } from '../components/lnqr'
import LayoutCenter from '../components/layout-center'
import InputGroup from 'react-bootstrap/InputGroup'
import { WithdrawlSkeleton } from './withdrawls/[id]'
@@ -56,7 +56,7 @@ export function FundForm () {
}`)
if (called && !error) {
- return
+ return
}
return (
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index e06fc4b2..f93e5211 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -27,10 +27,19 @@ model User {
freeComments Int @default(5)
freePosts Int @default(2)
checkedNotesAt DateTime?
+ pubkey String? @unique
@@map(name: "users")
}
+model LnAuth {
+ id Int @id @default(autoincrement())
+ createdAt DateTime @default(now()) @map(name: "created_at")
+ updatedAt DateTime @default(now()) @updatedAt @map(name: "updated_at")
+ k1 String @unique
+ pubkey String?
+}
+
model Message {
id Int @id @default(autoincrement())
text String
diff --git a/styles/login.module.css b/styles/login.module.css
index 71c578a3..fcb43f78 100644
--- a/styles/login.module.css
+++ b/styles/login.module.css
@@ -8,4 +8,8 @@
.login {
max-width: 300px;
width: 100%;
+ justify-content: center;
+ align-items: center;
+ display: flex;
+ flex-direction: column;
}
\ No newline at end of file