lnurl-auth
This commit is contained in:
parent
217e7ab294
commit
2e26e421e7
@ -2,5 +2,6 @@ import user from './user'
|
|||||||
import message from './message'
|
import message from './message'
|
||||||
import item from './item'
|
import item from './item'
|
||||||
import wallet from './wallet'
|
import wallet from './wallet'
|
||||||
|
import lnurl from './lnurl'
|
||||||
|
|
||||||
export default [user, item, message, wallet]
|
export default [user, item, message, wallet, lnurl]
|
||||||
|
@ -91,7 +91,6 @@ export default {
|
|||||||
if (!me) {
|
if (!me) {
|
||||||
throw new AuthenticationError('you must be logged in')
|
throw new AuthenticationError('you must be logged in')
|
||||||
}
|
}
|
||||||
const user = await models.user.findUnique({ where: { name: me.name } })
|
|
||||||
comments = await models.$queryRaw(`
|
comments = await models.$queryRaw(`
|
||||||
${SELECT}
|
${SELECT}
|
||||||
From "Item"
|
From "Item"
|
||||||
@ -99,7 +98,7 @@ export default {
|
|||||||
AND "Item"."userId" <> $1 AND "Item".created_at <= $2
|
AND "Item"."userId" <> $1 AND "Item".created_at <= $2
|
||||||
ORDER BY "Item".created_at DESC
|
ORDER BY "Item".created_at DESC
|
||||||
OFFSET $3
|
OFFSET $3
|
||||||
LIMIT ${LIMIT}`, user.id, decodedCursor.time, decodedCursor.offset)
|
LIMIT ${LIMIT}`, me.id, decodedCursor.time, decodedCursor.offset)
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
cursor: comments.length === LIMIT ? nextCursorEncoded(decodedCursor) : null,
|
cursor: comments.length === LIMIT ? nextCursorEncoded(decodedCursor) : null,
|
||||||
@ -110,14 +109,13 @@ export default {
|
|||||||
if (!me) {
|
if (!me) {
|
||||||
throw new AuthenticationError('you must be logged in')
|
throw new AuthenticationError('you must be logged in')
|
||||||
}
|
}
|
||||||
const user = await models.user.findUnique({ where: { name: me.name } })
|
|
||||||
|
|
||||||
return await models.$queryRaw(`
|
return await models.$queryRaw(`
|
||||||
${SELECT}
|
${SELECT}
|
||||||
From "Item"
|
From "Item"
|
||||||
JOIN "Item" p ON "Item"."parentId" = p.id AND p."userId" = $1
|
JOIN "Item" p ON "Item"."parentId" = p.id AND p."userId" = $1
|
||||||
AND "Item"."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 }) => {
|
item: async (parent, { id }, { models }) => {
|
||||||
const [item] = await models.$queryRaw(`
|
const [item] = await models.$queryRaw(`
|
||||||
@ -229,15 +227,13 @@ export default {
|
|||||||
meSats: async (item, args, { me, models }) => {
|
meSats: async (item, args, { me, models }) => {
|
||||||
if (!me) return 0
|
if (!me) return 0
|
||||||
|
|
||||||
const meFull = await models.user.findUnique({ where: { name: me.name } })
|
|
||||||
|
|
||||||
const { sum: { sats } } = await models.vote.aggregate({
|
const { sum: { sats } } = await models.vote.aggregate({
|
||||||
sum: {
|
sum: {
|
||||||
sats: true
|
sats: true
|
||||||
},
|
},
|
||||||
where: {
|
where: {
|
||||||
itemId: item.id,
|
itemId: item.id,
|
||||||
userId: meFull.id
|
userId: me.id
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ import { AuthenticationError, UserInputError } from 'apollo-server-errors'
|
|||||||
export default {
|
export default {
|
||||||
Query: {
|
Query: {
|
||||||
me: async (parent, args, { models, me }) =>
|
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 }) => {
|
user: async (parent, { name }, { models }) => {
|
||||||
return await models.user.findUnique({ where: { name } })
|
return await models.user.findUnique({ where: { name } })
|
||||||
},
|
},
|
||||||
@ -21,7 +21,7 @@ export default {
|
|||||||
throw new AuthenticationError('you must be logged in')
|
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(`
|
const [{ sum }] = await models.$queryRaw(`
|
||||||
SELECT sum("Vote".sats)
|
SELECT sum("Vote".sats)
|
||||||
@ -32,7 +32,7 @@ export default {
|
|||||||
AND "Vote".boost = false
|
AND "Vote".boost = false
|
||||||
WHERE "Item"."userId" = $1`, user.id, user.checkedNotesAt)
|
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
|
return sum || 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -44,7 +44,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await models.user.update({ where: { name: me.name }, data: { name } })
|
await models.user.update({ where: { id: me.id }, data: { name } })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.code === 'P2002') {
|
if (error.code === 'P2002') {
|
||||||
throw new UserInputError('name taken')
|
throw new UserInputError('name taken')
|
||||||
|
@ -18,7 +18,7 @@ export default {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (inv.user.name !== me.name) {
|
if (inv.user.id !== me.id) {
|
||||||
throw new AuthenticationError('not ur invoice')
|
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')
|
throw new AuthenticationError('not ur withdrawl')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,7 +77,7 @@ export default {
|
|||||||
msatsRequested: amount * 1000,
|
msatsRequested: amount * 1000,
|
||||||
user: {
|
user: {
|
||||||
connect: {
|
connect: {
|
||||||
name: me.name
|
id: me.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import user from './user'
|
|||||||
import message from './message'
|
import message from './message'
|
||||||
import item from './item'
|
import item from './item'
|
||||||
import wallet from './wallet'
|
import wallet from './wallet'
|
||||||
|
import lnurl from './lnurl'
|
||||||
|
|
||||||
const link = gql`
|
const link = gql`
|
||||||
type Query {
|
type Query {
|
||||||
@ -19,4 +20,4 @@ const link = gql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export default [link, user, item, message, wallet]
|
export default [link, user, item, message, wallet, lnurl]
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
import QRCode from 'qrcode.react'
|
import LnQR from './lnqr'
|
||||||
import { CopyInput, InputSkeleton } from './form'
|
|
||||||
import InvoiceStatus from './invoice-status'
|
|
||||||
|
|
||||||
export function Invoice ({ invoice }) {
|
export function Invoice ({ invoice }) {
|
||||||
const qrValue = 'lightning:' + invoice.bolt11.toUpperCase()
|
|
||||||
|
|
||||||
let variant = 'default'
|
let variant = 'default'
|
||||||
let status = 'waiting for you'
|
let status = 'waiting for you'
|
||||||
if (invoice.confirmedAt) {
|
if (invoice.confirmedAt) {
|
||||||
@ -18,27 +14,5 @@ export function Invoice ({ invoice }) {
|
|||||||
status = 'expired'
|
status = 'expired'
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <LnQR value={invoice.bolt11} statusVariant={variant} status={status} />
|
||||||
<>
|
|
||||||
<div>
|
|
||||||
<QRCode className='h-auto mw-100' value={qrValue} renderAs='svg' size={300} />
|
|
||||||
</div>
|
|
||||||
<div className='mt-3 w-100'>
|
|
||||||
<CopyInput type='text' placeholder={invoice.bolt11} readOnly />
|
|
||||||
</div>
|
|
||||||
<InvoiceStatus variant={variant} status={status} />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function InvoiceSkeleton ({ status }) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className='h-auto w-100 clouds' style={{ paddingTop: 'min(300px, 100%)', maxWidth: '300px' }} />
|
|
||||||
<div className='mt-3 w-100'>
|
|
||||||
<InputSkeleton />
|
|
||||||
</div>
|
|
||||||
<InvoiceStatus variant='default' status={status} />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
@ -17,14 +17,14 @@ export default function UpVote ({ itemId, meSats, className }) {
|
|||||||
cache.modify({
|
cache.modify({
|
||||||
id: `Item:${itemId}`,
|
id: `Item:${itemId}`,
|
||||||
fields: {
|
fields: {
|
||||||
|
meSats (existingMeSats = 0) {
|
||||||
|
return existingMeSats + vote
|
||||||
|
},
|
||||||
sats (existingSats = 0) {
|
sats (existingSats = 0) {
|
||||||
return existingSats || vote
|
return meSats === 0 ? existingSats + vote : existingSats
|
||||||
},
|
},
|
||||||
boost (existingBoost = 0) {
|
boost (existingBoost = 0) {
|
||||||
return meSats >= 1 ? existingBoost + vote : existingBoost
|
return meSats >= 1 ? existingBoost + vote : existingBoost
|
||||||
},
|
|
||||||
meSats (existingMeSats = 0) {
|
|
||||||
return existingMeSats + vote
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
"apollo-server-micro": "^2.21.2",
|
"apollo-server-micro": "^2.21.2",
|
||||||
"async-retry": "^1.3.1",
|
"async-retry": "^1.3.1",
|
||||||
"babel-plugin-inline-react-svg": "^2.0.1",
|
"babel-plugin-inline-react-svg": "^2.0.1",
|
||||||
|
"bech32": "^2.0.0",
|
||||||
"bootstrap": "^4.6.0",
|
"bootstrap": "^4.6.0",
|
||||||
"clipboard-copy": "^4.0.1",
|
"clipboard-copy": "^4.0.1",
|
||||||
"formik": "^2.2.6",
|
"formik": "^2.2.6",
|
||||||
@ -28,6 +29,7 @@
|
|||||||
"react-bootstrap": "^1.5.2",
|
"react-bootstrap": "^1.5.2",
|
||||||
"react-dom": "17.0.1",
|
"react-dom": "17.0.1",
|
||||||
"sass": "^1.32.8",
|
"sass": "^1.32.8",
|
||||||
|
"secp256k1": "^4.0.2",
|
||||||
"swr": "^0.5.4",
|
"swr": "^0.5.4",
|
||||||
"yup": "^0.32.9"
|
"yup": "^0.32.9"
|
||||||
},
|
},
|
||||||
|
@ -6,7 +6,65 @@ import prisma from '../../../api/models'
|
|||||||
export default (req, res) => NextAuth(req, res, options)
|
export default (req, res) => NextAuth(req, res, options)
|
||||||
|
|
||||||
const 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: [
|
||||||
|
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({
|
Providers.GitHub({
|
||||||
clientId: process.env.GITHUB_ID,
|
clientId: process.env.GITHUB_ID,
|
||||||
clientSecret: process.env.GITHUB_SECRET,
|
clientSecret: process.env.GITHUB_SECRET,
|
||||||
@ -37,6 +95,10 @@ const options = {
|
|||||||
],
|
],
|
||||||
adapter: Adapters.Prisma.Adapter({ prisma }),
|
adapter: Adapters.Prisma.Adapter({ prisma }),
|
||||||
secret: process.env.SECRET,
|
secret: process.env.SECRET,
|
||||||
|
session: { jwt: true },
|
||||||
|
jwt: {
|
||||||
|
signingKey: process.env.JWT_SIGNING_PRIVATE_KEY
|
||||||
|
},
|
||||||
pages: {
|
pages: {
|
||||||
signIn: '/login'
|
signIn: '/login'
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { useQuery } from '@apollo/client'
|
import { useQuery } from '@apollo/client'
|
||||||
import gql from 'graphql-tag'
|
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'
|
import LayoutCenter from '../../components/layout-center'
|
||||||
|
|
||||||
export async function getServerSideProps ({ params: { id } }) {
|
export async function getServerSideProps ({ params: { id } }) {
|
||||||
@ -34,7 +35,7 @@ function LoadInvoice ({ query }) {
|
|||||||
const { loading, error, data } = useQuery(query, { pollInterval: 1000 })
|
const { loading, error, data } = useQuery(query, { pollInterval: 1000 })
|
||||||
if (error) return <div>error</div>
|
if (error) return <div>error</div>
|
||||||
if (!data || loading) {
|
if (!data || loading) {
|
||||||
return <InvoiceSkeleton status='loading' />
|
return <LnQRSkeleton status='loading' />
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Invoice invoice={data.invoice} />
|
return <Invoice invoice={data.invoice} />
|
||||||
|
147
pages/login.js
147
pages/login.js
@ -3,11 +3,15 @@ import Button from 'react-bootstrap/Button'
|
|||||||
import styles from '../styles/login.module.css'
|
import styles from '../styles/login.module.css'
|
||||||
import GithubIcon from '../svgs/github-fill.svg'
|
import GithubIcon from '../svgs/github-fill.svg'
|
||||||
import TwitterIcon from '../svgs/twitter-fill.svg'
|
import TwitterIcon from '../svgs/twitter-fill.svg'
|
||||||
|
import LightningIcon from '../svgs/lightning.svg'
|
||||||
import { Input, SubmitButton, SyncForm } from '../components/form'
|
import { Input, SubmitButton, SyncForm } from '../components/form'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
import { useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import Alert from 'react-bootstrap/Alert'
|
import Alert from 'react-bootstrap/Alert'
|
||||||
import LayoutCenter from '../components/layout-center'
|
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 } }) {
|
export async function getServerSideProps ({ req, res, query: { callbackUrl, error = null } }) {
|
||||||
const session = await getSession({ req })
|
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()
|
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 = {
|
const errors = {
|
||||||
Signin: 'Try signing with a different account.',
|
Signin: 'Try signing with a different account.',
|
||||||
OAuthSignin: '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.',
|
Callback: 'Try signing with a different account.',
|
||||||
OAuthAccountNotLinked: 'To confirm your identity, sign in with the same account you used originally.',
|
OAuthAccountNotLinked: 'To confirm your identity, sign in with the same account you used originally.',
|
||||||
EmailSignin: 'Check your email address.',
|
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.'
|
default: 'Unable to sign in.'
|
||||||
}
|
}
|
||||||
|
|
||||||
const [errorMessage, setErrorMessage] = useState(error && (errors[error] ?? errors.default))
|
const [errorMessage, setErrorMessage] = useState(error && (errors[error] ?? errors.default))
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LayoutCenter noFooter>
|
<LayoutCenter noFooter>
|
||||||
<div className={styles.login}>
|
<div className={styles.login}>
|
||||||
{errorMessage &&
|
{errorMessage &&
|
||||||
<Alert variant='danger' onClose={() => setErrorMessage(undefined)} dismissible>{errorMessage}</Alert>}
|
<Alert variant='danger' onClose={() => setErrorMessage(undefined)} dismissible>{errorMessage}</Alert>}
|
||||||
{Object.values(providers).map(provider => {
|
{router.query.type === 'lightning'
|
||||||
if (provider.name === 'Email') {
|
? <LightningAuth />
|
||||||
return null
|
: (
|
||||||
}
|
<>
|
||||||
const [variant, Icon] =
|
<Button
|
||||||
|
className={`mt-2 ${styles.providerButton}`}
|
||||||
|
variant='primary'
|
||||||
|
onClick={() => router.push({
|
||||||
|
pathname: router.pathname,
|
||||||
|
query: { ...router.query, type: 'lightning' }
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<LightningIcon
|
||||||
|
className='mr-3'
|
||||||
|
/>Login with Lightning
|
||||||
|
</Button>
|
||||||
|
{Object.values(providers).map(provider => {
|
||||||
|
if (provider.name === 'Email' || provider.name === 'Lightning') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const [variant, Icon] =
|
||||||
provider.name === 'Twitter'
|
provider.name === 'Twitter'
|
||||||
? ['twitter', TwitterIcon]
|
? ['twitter', TwitterIcon]
|
||||||
: ['dark', GithubIcon]
|
: ['dark', GithubIcon]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
className={`mt-2 ${styles.providerButton}`}
|
className={`mt-2 ${styles.providerButton}`}
|
||||||
key={provider.name}
|
key={provider.name}
|
||||||
variant={variant}
|
variant={variant}
|
||||||
onClick={() => signIn(provider.id)}
|
onClick={() => signIn(provider.id)}
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
className='mr-3'
|
className='mr-3'
|
||||||
/>Login with {provider.name}
|
/>Login with {provider.name}
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
<div className='mt-2 text-center text-muted font-weight-bold'>or</div>
|
<div className='mt-2 text-center text-muted font-weight-bold'>or</div>
|
||||||
<SyncForm
|
<SyncForm
|
||||||
initial={{
|
initial={{
|
||||||
email: ''
|
email: ''
|
||||||
}}
|
}}
|
||||||
schema={EmailSchema}
|
schema={EmailSchema}
|
||||||
action='/api/auth/signin/email'
|
action='/api/auth/signin/email'
|
||||||
>
|
>
|
||||||
<input name='csrfToken' type='hidden' defaultValue={csrfToken} />
|
<input name='csrfToken' type='hidden' defaultValue={csrfToken} />
|
||||||
<Input
|
<Input
|
||||||
label='Email'
|
label='Email'
|
||||||
name='email'
|
name='email'
|
||||||
placeholder='email@example.com'
|
placeholder='email@example.com'
|
||||||
required
|
required
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
<SubmitButton variant='secondary' className={styles.providerButton}>Login with Email</SubmitButton>
|
<SubmitButton variant='secondary' className={styles.providerButton}>Login with Email</SubmitButton>
|
||||||
</SyncForm>
|
</SyncForm>
|
||||||
|
</>)}
|
||||||
</div>
|
</div>
|
||||||
</LayoutCenter>
|
</LayoutCenter>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function LnQRAuth ({ k1, encodedUrl }) {
|
||||||
|
const query = gql`
|
||||||
|
{
|
||||||
|
lnAuth(k1: "${k1}") {
|
||||||
|
pubkey
|
||||||
|
k1
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
const { error, data } = useQuery(query, { pollInterval: 1000 })
|
||||||
|
if (error) return <div>error</div>
|
||||||
|
|
||||||
|
if (data && data.lnAuth.pubkey) {
|
||||||
|
signIn('credentials', { ...data.lnAuth, callbackUrl: router.query.callbackUrl })
|
||||||
|
}
|
||||||
|
|
||||||
|
// output pubkey and k1
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<small className='mb-2'>
|
||||||
|
<a className='text-muted' href='https://github.com/fiatjaf/awesome-lnurl#wallets' target='_blank' rel='noreferrer'>Does my wallet support lnurl-auth?</a>
|
||||||
|
</small>
|
||||||
|
<LnQR value={encodedUrl} status='waiting for you' />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LightningAuth () {
|
||||||
|
// query for challenge
|
||||||
|
const [createAuth, { data, error }] = useMutation(gql`
|
||||||
|
mutation createAuth {
|
||||||
|
createAuth {
|
||||||
|
k1
|
||||||
|
encodedUrl
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
|
||||||
|
useEffect(createAuth, [])
|
||||||
|
|
||||||
|
if (error) return <div>error</div>
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return <LnQRSkeleton status='generating' />
|
||||||
|
}
|
||||||
|
|
||||||
|
return <LnQRAuth {...data.createAuth} />
|
||||||
|
}
|
||||||
|
@ -4,7 +4,7 @@ import Link from 'next/link'
|
|||||||
import Button from 'react-bootstrap/Button'
|
import Button from 'react-bootstrap/Button'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
import { gql, useMutation, useQuery } from '@apollo/client'
|
import { gql, useMutation, useQuery } from '@apollo/client'
|
||||||
import { InvoiceSkeleton } from '../components/invoice'
|
import { LnQRSkeleton } from '../components/lnqr'
|
||||||
import LayoutCenter from '../components/layout-center'
|
import LayoutCenter from '../components/layout-center'
|
||||||
import InputGroup from 'react-bootstrap/InputGroup'
|
import InputGroup from 'react-bootstrap/InputGroup'
|
||||||
import { WithdrawlSkeleton } from './withdrawls/[id]'
|
import { WithdrawlSkeleton } from './withdrawls/[id]'
|
||||||
@ -56,7 +56,7 @@ export function FundForm () {
|
|||||||
}`)
|
}`)
|
||||||
|
|
||||||
if (called && !error) {
|
if (called && !error) {
|
||||||
return <InvoiceSkeleton status='generating' />
|
return <LnQRSkeleton status='generating' />
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -27,10 +27,19 @@ model User {
|
|||||||
freeComments Int @default(5)
|
freeComments Int @default(5)
|
||||||
freePosts Int @default(2)
|
freePosts Int @default(2)
|
||||||
checkedNotesAt DateTime?
|
checkedNotesAt DateTime?
|
||||||
|
pubkey String? @unique
|
||||||
|
|
||||||
@@map(name: "users")
|
@@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 {
|
model Message {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
text String
|
text String
|
||||||
|
@ -8,4 +8,8 @@
|
|||||||
.login {
|
.login {
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user