add forgetten lnurl-auth files
This commit is contained in:
parent
2e26e421e7
commit
9a15c228dc
|
@ -0,0 +1,26 @@
|
||||||
|
import { randomBytes } from 'crypto'
|
||||||
|
import { bech32 } from 'bech32'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
Query: {
|
||||||
|
lnAuth: async (parent, { k1 }, { models }) => {
|
||||||
|
return await models.lnAuth.findUnique({ where: { k1 } })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Mutation: {
|
||||||
|
createAuth: async (parent, args, { models }) => {
|
||||||
|
const k1 = randomBytes(32).toString('hex')
|
||||||
|
return await models.lnAuth.create({ data: { k1 } })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
LnAuth: {
|
||||||
|
encodedUrl: async (lnAuth, args, { models }) => {
|
||||||
|
const url = new URL(process.env.LNAUTH_URL)
|
||||||
|
url.searchParams.set('tag', 'login')
|
||||||
|
url.searchParams.set('k1', lnAuth.k1)
|
||||||
|
// bech32 encode url
|
||||||
|
const words = bech32.toWords(Buffer.from(url.toString(), 'utf8'))
|
||||||
|
return bech32.encode('lnurl', words, 1023)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { gql } from 'apollo-server-micro'
|
||||||
|
|
||||||
|
export default gql`
|
||||||
|
extend type Query {
|
||||||
|
lnAuth(k1: String!): LnAuth!
|
||||||
|
}
|
||||||
|
|
||||||
|
extend type Mutation {
|
||||||
|
createAuth: LnAuth!
|
||||||
|
}
|
||||||
|
|
||||||
|
type LnAuth {
|
||||||
|
id: ID!
|
||||||
|
createdAt: String!
|
||||||
|
k1: String!
|
||||||
|
pubkey: String
|
||||||
|
encodedUrl: String!
|
||||||
|
}
|
||||||
|
`
|
|
@ -0,0 +1,31 @@
|
||||||
|
import QRCode from 'qrcode.react'
|
||||||
|
import { CopyInput, InputSkeleton } from './form'
|
||||||
|
import InvoiceStatus from './invoice-status'
|
||||||
|
|
||||||
|
export default function LnQR ({ value, statusVariant, status }) {
|
||||||
|
const qrValue = 'lightning:' + value.toUpperCase()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<QRCode className='h-auto mw-100' value={qrValue} renderAs='svg' size={300} />
|
||||||
|
</div>
|
||||||
|
<div className='mt-3 w-100'>
|
||||||
|
<CopyInput type='text' placeholder={value} readOnly />
|
||||||
|
</div>
|
||||||
|
<InvoiceStatus variant={statusVariant} status={status} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LnQRSkeleton ({ 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} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { randomBytes } from 'crypto'
|
||||||
|
import { bech32 } from 'bech32'
|
||||||
|
|
||||||
|
export function lnurlAuth (params) {
|
||||||
|
// generate secret (32 random bytes)
|
||||||
|
const secret = Buffer.from(randomBytes(32), 'hex')
|
||||||
|
// create url
|
||||||
|
const url = new URL(process.env.LNAUTH_URL)
|
||||||
|
url.searchParams = new URLSearchParams({
|
||||||
|
...params,
|
||||||
|
k1: secret
|
||||||
|
})
|
||||||
|
// bech32 encode url
|
||||||
|
const words = bech32.toWords(Buffer.from(url.toString(), 'utf8'))
|
||||||
|
const encodedUrl = bech32.encode('lnurl', words, 1023)
|
||||||
|
return { secret, encodedUrl }
|
||||||
|
}
|
|
@ -94,7 +94,7 @@ const options = {
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
adapter: Adapters.Prisma.Adapter({ prisma }),
|
adapter: Adapters.Prisma.Adapter({ prisma }),
|
||||||
secret: process.env.SECRET,
|
secret: process.env.NEXTAUTH_SECRET,
|
||||||
session: { jwt: true },
|
session: { jwt: true },
|
||||||
jwt: {
|
jwt: {
|
||||||
signingKey: process.env.JWT_SIGNING_PRIVATE_KEY
|
signingKey: process.env.JWT_SIGNING_PRIVATE_KEY
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
// verify it's signed
|
||||||
|
// store pubkey in db
|
||||||
|
// create user with pubkey and name truncated pubkey
|
||||||
|
import secp256k1 from 'secp256k1'
|
||||||
|
import models from '../../api/models'
|
||||||
|
|
||||||
|
export default async ({ query }, res) => {
|
||||||
|
const sig = Buffer.from(query.sig, 'hex')
|
||||||
|
const k1 = Buffer.from(query.k1, 'hex')
|
||||||
|
const key = Buffer.from(query.key, 'hex')
|
||||||
|
const signature = secp256k1.signatureImport(sig)
|
||||||
|
try {
|
||||||
|
if (secp256k1.ecdsaVerify(signature, k1, key)) {
|
||||||
|
await models.lnAuth.update({ where: { k1: query.k1 }, data: { pubkey: query.key } })
|
||||||
|
return res.status(200).json({ status: 'OK' })
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
return res.status(400).json({ status: 'ERROR', reason: 'signature verification failed' })
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- A unique constraint covering the columns `[pubkey]` on the table `users` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "users" ADD COLUMN "pubkey" TEXT;
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "LnAuth" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"k1" TEXT NOT NULL,
|
||||||
|
"pubkey" TEXT,
|
||||||
|
|
||||||
|
PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "LnAuth.k1_unique" ON "LnAuth"("k1");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "users.pubkey_unique" ON "users"("pubkey");
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M19 10.1907L8.48754 21L12.6726 12.7423H5L14.6157 3L11.5267 10.2835L19 10.1907Z" fill="black"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 207 B |
Loading…
Reference in New Issue