upgrade to next-auth 4 (bonus: improve error pages)
This commit is contained in:
parent
de089aa429
commit
5232b59625
|
@ -847,7 +847,7 @@ export default {
|
|||
return await models.user.findUnique({ where: { id: item.fwdUserId } })
|
||||
},
|
||||
comments: async (item, { sort }, { me, models }) => {
|
||||
if (item.comments) return item.comments
|
||||
if (typeof item.comments !== 'undefined') return item.comments
|
||||
if (item.ncomments === 0) return []
|
||||
|
||||
return comments(me, models, item.id, sort || defaultCommentSort(item.pinId, item.bioId, item.createdAt))
|
||||
|
|
|
@ -73,7 +73,7 @@ async function authMethods (user, args, { models, me }) {
|
|||
}
|
||||
})
|
||||
|
||||
const oauth = accounts.map(a => a.providerId)
|
||||
const oauth = accounts.map(a => a.provider)
|
||||
|
||||
return {
|
||||
lightning: !!user.pubkey,
|
||||
|
@ -87,7 +87,7 @@ async function authMethods (user, args, { models, me }) {
|
|||
export default {
|
||||
Query: {
|
||||
me: async (parent, { skipUpdate }, { models, me }) => {
|
||||
if (!me) {
|
||||
if (!me?.id) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
@ -518,7 +518,7 @@ export default {
|
|||
let user
|
||||
if (authType === 'twitter' || authType === 'github') {
|
||||
user = await models.user.findUnique({ where: { id: me.id } })
|
||||
const account = await models.account.findFirst({ where: { userId: me.id, providerId: authType } })
|
||||
const account = await models.account.findFirst({ where: { userId: me.id, provider: authType } })
|
||||
if (!account) {
|
||||
throw new GraphQLError('no such account', { extensions: { code: 'BAD_INPUT' } })
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { ApolloClient, InMemoryCache } from '@apollo/client'
|
||||
import { SchemaLink } from '@apollo/client/link/schema'
|
||||
import { makeExecutableSchema } from '@graphql-tools/schema'
|
||||
import { getSession } from 'next-auth/client'
|
||||
import resolvers from './resolvers'
|
||||
import typeDefs from './typeDefs'
|
||||
import models from './models'
|
||||
|
@ -11,9 +10,11 @@ import lnd from './lnd'
|
|||
import search from './search'
|
||||
import { ME } from '../fragments/users'
|
||||
import { PRICE } from '../fragments/price'
|
||||
import { getServerSession } from 'next-auth/next'
|
||||
import { getAuthOptions } from '../pages/api/auth/[...nextauth]'
|
||||
|
||||
export default async function getSSRApolloClient (req, me = null) {
|
||||
const session = req && await getSession({ req })
|
||||
export default async function getSSRApolloClient ({ req, res, me = null }) {
|
||||
const session = req && await getServerSession(req, res, getAuthOptions(req))
|
||||
const client = new ApolloClient({
|
||||
ssrMode: true,
|
||||
link: new SchemaLink({
|
||||
|
@ -54,7 +55,7 @@ export default async function getSSRApolloClient (req, me = null) {
|
|||
}
|
||||
|
||||
export function getGetServerSideProps (queryOrFunc, variablesOrFunc = null, notFoundFunc, requireVar) {
|
||||
return async function ({ req, query: params }) {
|
||||
return async function ({ req, res, query: params }) {
|
||||
const { nodata, ...realParams } = params
|
||||
// we want to use client-side cache
|
||||
if (nodata) return { props: { } }
|
||||
|
@ -63,7 +64,7 @@ export function getGetServerSideProps (queryOrFunc, variablesOrFunc = null, notF
|
|||
const vars = { ...realParams, ...variables }
|
||||
const query = typeof queryOrFunc === 'function' ? queryOrFunc(vars) : queryOrFunc
|
||||
|
||||
const client = await getSSRApolloClient(req)
|
||||
const client = await getSSRApolloClient({ req, res })
|
||||
|
||||
const { data: { me } } = await client.query({
|
||||
query: ME,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Component } from 'react'
|
||||
import { StaticLayout } from './layout'
|
||||
import styles from '../styles/404.module.css'
|
||||
import styles from '../styles/error.module.css'
|
||||
import Image from 'react-bootstrap/Image'
|
||||
|
||||
class ErrorBoundary extends Component {
|
||||
|
@ -27,8 +27,8 @@ class ErrorBoundary extends Component {
|
|||
// You can render any custom fallback UI
|
||||
return (
|
||||
<StaticLayout>
|
||||
<Image width='500' height='375' src={`${process.env.NEXT_PUBLIC_ASSET_PREFIX}/floating.gif`} fluid />
|
||||
<h1 className={styles.fourZeroFour} style={{ fontSize: '48px' }}>something went wrong</h1>
|
||||
<Image width='500' height='375' className='rounded-1 shadow-sm' src={`${process.env.NEXT_PUBLIC_ASSET_PREFIX}/floating.gif`} fluid />
|
||||
<h1 className={styles.status} style={{ fontSize: '48px' }}>something went wrong</h1>
|
||||
</StaticLayout>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import NavDropdown from 'react-bootstrap/NavDropdown'
|
|||
import Price from './price'
|
||||
import { useMe } from './me'
|
||||
import Head from 'next/head'
|
||||
import { signOut } from 'next-auth/client'
|
||||
import { signOut } from 'next-auth/react'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { randInRange } from '../lib/rand'
|
||||
import { abbrNum } from '../lib/format'
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { gql, useMutation, useQuery } from '@apollo/client'
|
||||
import { signIn } from 'next-auth/client'
|
||||
import { signIn } from 'next-auth/react'
|
||||
import { useEffect } from 'react'
|
||||
import Col from 'react-bootstrap/Col'
|
||||
import Container from 'react-bootstrap/Container'
|
||||
|
@ -20,9 +20,11 @@ function QrAuth ({ k1, encodedUrl, slashtagUrl, callbackUrl }) {
|
|||
}`
|
||||
const { data } = useQuery(query, { pollInterval: 1000, nextFetchPolicy: 'cache-and-network' })
|
||||
|
||||
if (data && data.lnAuth.pubkey) {
|
||||
signIn(encodedUrl ? 'lightning' : 'slashtags', { ...data.lnAuth, callbackUrl })
|
||||
}
|
||||
useEffect(() => {
|
||||
if (data?.lnAuth?.pubkey) {
|
||||
signIn(encodedUrl ? 'lightning' : 'slashtags', { ...data.lnAuth, callbackUrl })
|
||||
}
|
||||
}, [data?.lnAuth])
|
||||
|
||||
// output pubkey and k1
|
||||
return (
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { signIn } from 'next-auth/client'
|
||||
import { signIn } from 'next-auth/react'
|
||||
import styles from './login.module.css'
|
||||
import { Form, Input, SubmitButton } from '../components/form'
|
||||
import { useState } from 'react'
|
||||
|
@ -33,16 +33,15 @@ export function EmailLoginForm ({ text, callbackUrl }) {
|
|||
|
||||
export default function Login ({ providers, callbackUrl, error, text, Header, Footer }) {
|
||||
const errors = {
|
||||
Signin: 'Try signing with a different account.',
|
||||
OAuthSignin: 'Try signing with a different account.',
|
||||
OAuthCallback: 'Try signing with a different account.',
|
||||
OAuthCreateAccount: 'Try signing with a different account.',
|
||||
EmailCreateAccount: '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.',
|
||||
EmailSignin: 'Check your email address.',
|
||||
CredentialsSignin: 'Auth failed',
|
||||
default: 'Unable to sign in.'
|
||||
OAuthSignin: 'Error constructing OAuth URL. Try again or choose a different method.',
|
||||
OAuthCallback: 'Error handling OAuth response. Try again or choose a different method.',
|
||||
OAuthCreateAccount: 'Could not create OAuth account. Try again or choose a different method.',
|
||||
EmailCreateAccount: 'Could not create Email account. Try again or choose a different method.',
|
||||
Callback: 'Error in callback handler. Try again or choose a different method.',
|
||||
OAuthAccountNotLinked: 'This auth method is linked to another account. To link to this account first unlink the other account.',
|
||||
EmailSignin: 'Failed to send email. Make sure you entered your email address correctly.',
|
||||
CredentialsSignin: 'Auth failed. Try again or choose a different method.',
|
||||
default: 'Auth failed. Try again or choose a different method.'
|
||||
}
|
||||
|
||||
const [errorMessage, setErrorMessage] = useState(error && (errors[error] ?? errors.default))
|
||||
|
@ -70,7 +69,7 @@ export default function Login ({ providers, callbackUrl, error, text, Header, Fo
|
|||
switch (provider.name) {
|
||||
case 'Email':
|
||||
return (
|
||||
<div className='w-100' key={provider.name}>
|
||||
<div className='w-100' key={provider.id}>
|
||||
<div className='mt-2 text-center text-muted fw-bold'>or</div>
|
||||
<EmailLoginForm text={text} callbackUrl={callbackUrl} />
|
||||
</div>
|
||||
|
@ -80,8 +79,8 @@ export default function Login ({ providers, callbackUrl, error, text, Header, Fo
|
|||
return (
|
||||
<LoginButton
|
||||
className={`mt-2 ${styles.providerButton}`}
|
||||
key={provider.name}
|
||||
type={provider.name.toLowerCase()}
|
||||
key={provider.id}
|
||||
type={provider.id.toLowerCase()}
|
||||
onClick={() => router.push({
|
||||
pathname: router.pathname,
|
||||
query: { callbackUrl: router.query.callbackUrl, type: provider.name.toLowerCase() }
|
||||
|
@ -93,8 +92,8 @@ export default function Login ({ providers, callbackUrl, error, text, Header, Fo
|
|||
return (
|
||||
<LoginButton
|
||||
className={`mt-2 ${styles.providerButton}`}
|
||||
key={provider.name}
|
||||
type={provider.name.toLowerCase()}
|
||||
key={provider.id}
|
||||
type={provider.id.toLowerCase()}
|
||||
onClick={() => signIn(provider.id, { callbackUrl })}
|
||||
text={`${text || 'Login'} with`}
|
||||
/>
|
||||
|
|
|
@ -126,7 +126,7 @@ function EarnNotification ({ n }) {
|
|||
<HandCoin className='align-self-center fill-boost mx-1' width={24} height={24} style={{ flex: '0 0 24px', transform: 'rotateY(180deg)' }} />
|
||||
<div className='ms-2'>
|
||||
<div className='fw-bold text-boost'>
|
||||
you stacked {n.earnedSats} sats in rewards<small className='text-muted ms-1'>{timeSince(new Date(n.sortTime))}</small>
|
||||
you stacked {n.earnedSats} sats in rewards<small className='text-muted ms-1 fw-normal'>{timeSince(new Date(n.sortTime))}</small>
|
||||
</div>
|
||||
{n.sources &&
|
||||
<div style={{ fontSize: '80%', color: 'var(--theme-grey)' }}>
|
||||
|
@ -166,7 +166,7 @@ function InvoicePaid ({ n }) {
|
|||
<NotificationLayout href={`/invoices/${n.invoice.id}`}>
|
||||
<div className='fw-bold text-info ms-2 py-1'>
|
||||
<Check className='fill-info me-1' />{n.earnedSats} sats were deposited in your account
|
||||
<small className='text-muted ms-1'>{timeSince(new Date(n.sortTime))}</small>
|
||||
<small className='text-muted ms-1 fw-normal'>{timeSince(new Date(n.sortTime))}</small>
|
||||
</div>
|
||||
</NotificationLayout>
|
||||
)
|
||||
|
@ -177,7 +177,7 @@ function Referral ({ n }) {
|
|||
<NotificationLayout>
|
||||
<small className='fw-bold text-secondary ms-2'>
|
||||
someone joined via one of your <Link href='/referrals/month' className='text-reset'>referral links</Link>
|
||||
<small className='text-muted ms-1'>{timeSince(new Date(n.sortTime))}</small>
|
||||
<small className='text-muted ms-1 fw-normal'>{timeSince(new Date(n.sortTime))}</small>
|
||||
</small>
|
||||
</NotificationLayout>
|
||||
)
|
||||
|
|
|
@ -5,7 +5,7 @@ import { timeLeft } from '../lib/time'
|
|||
import { useMe } from './me'
|
||||
import styles from './poll.module.css'
|
||||
import Check from '../svgs/checkbox-circle-fill.svg'
|
||||
import { signIn } from 'next-auth/client'
|
||||
import { signIn } from 'next-auth/react'
|
||||
import ActionTooltip from './action-tooltip'
|
||||
import { useShowModal } from './modal'
|
||||
import FundError from './fund-error'
|
||||
|
|
|
@ -35,7 +35,7 @@ const UpvotePopover = ({ target, show, handleClose }) => {
|
|||
>
|
||||
<Popover id='popover-basic'>
|
||||
<Popover.Body className='d-flex justify-content-between alert-dismissible' as='h3'>Zapping
|
||||
<button type='button' className='close' onClick={handleClose}><span aria-hidden='true'>×</span><span className='sr-only'>Close alert</span></button>
|
||||
<button type='button' className='close' onClick={handleClose}><span aria-hidden='true'>×</span><span className='visually-hidden-focusable'>Close alert</span></button>
|
||||
</Popover.Body>
|
||||
<Popover.Body>
|
||||
<div className='mb-2'>Press the bolt again to zap {me?.tipDefault || 1} more sat{me?.tipDefault > 1 ? 's' : ''}.</div>
|
||||
|
@ -54,7 +54,7 @@ const TipPopover = ({ target, show, handleClose }) => (
|
|||
>
|
||||
<Popover id='popover-basic'>
|
||||
<Popover.Body className='d-flex justify-content-between alert-dismissible' as='h3'>Press and hold
|
||||
<button type='button' className='close' onClick={handleClose}><span aria-hidden='true'>×</span><span className='sr-only'>Close alert</span></button>
|
||||
<button type='button' className='close' onClick={handleClose}><span aria-hidden='true'>×</span><span className='visually-hidden-focusable'>Close alert</span></button>
|
||||
</Popover.Body>
|
||||
<Popover.Body>
|
||||
<div className='mb-2'>Press and hold bolt to zap a custom amount.</div>
|
||||
|
|
|
@ -12,8 +12,12 @@ function isFirstPage (cursor, existingThings) {
|
|||
}
|
||||
}
|
||||
|
||||
const SSR = typeof window === 'undefined'
|
||||
const defaultFetchPolicy = SSR ? 'cache-only' : 'cache-first'
|
||||
const defaultNextFetchPolicy = SSR ? 'cache-only' : 'cache-first'
|
||||
|
||||
export default function getApolloClient () {
|
||||
if (typeof window === 'undefined') {
|
||||
if (SSR) {
|
||||
return getClient(`${process.env.SELF_URL}/api/graphql`)
|
||||
} else {
|
||||
global.apolloClient ||= getClient('/api/graphql')
|
||||
|
@ -24,7 +28,7 @@ export default function getApolloClient () {
|
|||
function getClient (uri) {
|
||||
return new ApolloClient({
|
||||
link: new HttpLink({ uri }),
|
||||
ssrMode: typeof window === 'undefined',
|
||||
ssrMode: SSR,
|
||||
cache: new InMemoryCache({
|
||||
freezeResults: true,
|
||||
typePolicies: {
|
||||
|
@ -143,13 +147,13 @@ function getClient (uri) {
|
|||
assumeImmutableResults: true,
|
||||
defaultOptions: {
|
||||
watchQuery: {
|
||||
fetchPolicy: 'cache-first',
|
||||
nextFetchPolicy: 'cache-first',
|
||||
fetchPolicy: defaultFetchPolicy,
|
||||
nextFetchPolicy: defaultNextFetchPolicy,
|
||||
canonizeResults: true
|
||||
},
|
||||
query: {
|
||||
fetchPolicy: 'cache-first',
|
||||
nextFetchPolicy: 'cache-first',
|
||||
fetchPolicy: defaultFetchPolicy,
|
||||
nextFetchPolicy: defaultNextFetchPolicy,
|
||||
canonizeResults: true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ function generateRssFeed (items, sub = null) {
|
|||
export default function getGetRssServerSideProps (query, variables = null) {
|
||||
return async function ({ req, res, query: params }) {
|
||||
const emptyProps = { props: {} } // to avoid server side warnings
|
||||
const client = await getSSRApolloClient(req)
|
||||
const client = await getSSRApolloClient({ req, res })
|
||||
const { error, data: { items: { items } } } = await client.query({
|
||||
query, variables: { ...params, ...variables }
|
||||
})
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -12,6 +12,7 @@
|
|||
"@apollo/client": "^3.7.17",
|
||||
"@apollo/server": "^4.8.1",
|
||||
"@as-integrations/next": "^2.0.1",
|
||||
"@auth/prisma-adapter": "^1.0.1",
|
||||
"@graphql-tools/schema": "^10.0.0",
|
||||
"@noble/curves": "^1.1.0",
|
||||
"@opensearch-project/opensearch": "^2.3.1",
|
||||
|
@ -44,11 +45,12 @@
|
|||
"mdast-util-to-string": "^4.0.0",
|
||||
"micromark-extension-gfm": "^3.0.0",
|
||||
"next": "^13.4.12",
|
||||
"next-auth": "^3.29.10",
|
||||
"next-auth": "^4.22.3",
|
||||
"next-plausible": "^3.10.1",
|
||||
"next-seo": "^6.1.0",
|
||||
"nextjs-progressbar": "0.0.16",
|
||||
"node-s3-url-encode": "^0.0.4",
|
||||
"nodemailer": "^6.9.4",
|
||||
"nostr": "^0.2.8",
|
||||
"opentimestamps": "^0.4.9",
|
||||
"page-metadata-parser": "^1.1.4",
|
||||
|
@ -79,6 +81,7 @@
|
|||
"web-push": "^3.6.2",
|
||||
"webln": "^0.3.2",
|
||||
"webpack": "^5.88.2",
|
||||
"workbox-navigation-preload": "^7.0.0",
|
||||
"workbox-precaching": "^7.0.0",
|
||||
"workbox-recipes": "^7.0.0",
|
||||
"workbox-routing": "^7.0.0",
|
||||
|
|
13
pages/404.js
13
pages/404.js
|
@ -1,12 +1,5 @@
|
|||
import Image from 'react-bootstrap/Image'
|
||||
import { StaticLayout } from '../components/layout'
|
||||
import styles from '../styles/404.module.css'
|
||||
import Error from './_error'
|
||||
|
||||
export default function fourZeroFour () {
|
||||
return (
|
||||
<StaticLayout>
|
||||
<Image width='500' height='376' src={`${process.env.NEXT_PUBLIC_ASSET_PREFIX}/maze.gif`} fluid />
|
||||
<h1 className={styles.fourZeroFour}><span>404</span><span className={styles.notFound}>page not found</span></h1>
|
||||
</StaticLayout>
|
||||
)
|
||||
export default function Custom404 () {
|
||||
return <Error statusCode={404} />
|
||||
}
|
||||
|
|
13
pages/500.js
13
pages/500.js
|
@ -1,12 +1,5 @@
|
|||
import Image from 'react-bootstrap/Image'
|
||||
import { StaticLayout } from '../components/layout'
|
||||
import styles from '../styles/404.module.css'
|
||||
import Error from './_error'
|
||||
|
||||
export default function fiveHundo () {
|
||||
return (
|
||||
<StaticLayout>
|
||||
<Image width='500' height='375' src={`${process.env.NEXT_PUBLIC_ASSET_PREFIX}/falling.gif`} fluid />
|
||||
<h1 className={styles.fourZeroFour}><span>500</span><span className={styles.notFound}>server error</span></h1>
|
||||
</StaticLayout>
|
||||
)
|
||||
export default function Custom500 () {
|
||||
return <Error statusCode={500} />
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import '../styles/globals.scss'
|
||||
import { ApolloProvider, gql } from '@apollo/client'
|
||||
import { Provider } from 'next-auth/client'
|
||||
import { MeProvider } from '../components/me'
|
||||
import PlausibleProvider from 'next-plausible'
|
||||
import getApolloClient from '../lib/apollo'
|
||||
|
@ -28,7 +27,7 @@ function writeQuery (client, apollo, data) {
|
|||
}
|
||||
}
|
||||
|
||||
function MyApp ({ Component, pageProps: { session, ...props } }) {
|
||||
function MyApp ({ Component, pageProps: { ...props } }) {
|
||||
const client = getApolloClient()
|
||||
const router = useRouter()
|
||||
|
||||
|
@ -80,21 +79,19 @@ function MyApp ({ Component, pageProps: { session, ...props } }) {
|
|||
</Head>
|
||||
<ErrorBoundary>
|
||||
<PlausibleProvider domain='stacker.news' trackOutboundLinks>
|
||||
<Provider session={session}>
|
||||
<ApolloProvider client={client}>
|
||||
<MeProvider me={me}>
|
||||
<ServiceWorkerProvider>
|
||||
<PriceProvider price={price}>
|
||||
<LightningProvider>
|
||||
<ShowModalProvider>
|
||||
<Component ssrData={ssrData} {...otherProps} />
|
||||
</ShowModalProvider>
|
||||
</LightningProvider>
|
||||
</PriceProvider>
|
||||
</ServiceWorkerProvider>
|
||||
</MeProvider>
|
||||
</ApolloProvider>
|
||||
</Provider>
|
||||
<ApolloProvider client={client}>
|
||||
<MeProvider me={me}>
|
||||
<ServiceWorkerProvider>
|
||||
<PriceProvider price={price}>
|
||||
<LightningProvider>
|
||||
<ShowModalProvider>
|
||||
<Component ssrData={ssrData} {...otherProps} />
|
||||
</ShowModalProvider>
|
||||
</LightningProvider>
|
||||
</PriceProvider>
|
||||
</ServiceWorkerProvider>
|
||||
</MeProvider>
|
||||
</ApolloProvider>
|
||||
</PlausibleProvider>
|
||||
</ErrorBoundary>
|
||||
</>
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
import Image from 'react-bootstrap/Image'
|
||||
import { StaticLayout } from '../components/layout'
|
||||
import styles from '../styles/error.module.css'
|
||||
|
||||
const statusDescribe = {
|
||||
200: 'OK',
|
||||
201: 'Created',
|
||||
202: 'Accepted',
|
||||
203: 'Non-Authoritative Information',
|
||||
204: 'No Content',
|
||||
205: 'Reset Content',
|
||||
206: 'Partial Content',
|
||||
300: 'Multiple Choices',
|
||||
301: 'Moved Permanently',
|
||||
302: 'Found',
|
||||
303: 'See Other',
|
||||
304: 'Not Modified',
|
||||
305: 'Use Proxy',
|
||||
306: 'Unused',
|
||||
307: 'Temporary Redirect',
|
||||
400: 'Bad Request',
|
||||
401: 'Unauthorized',
|
||||
402: 'Payment Required',
|
||||
403: 'Forbidden',
|
||||
404: 'Not Found',
|
||||
405: 'Method Not Allowed',
|
||||
406: 'Not Acceptable',
|
||||
407: 'Proxy Authentication Required',
|
||||
408: 'Request Timeout',
|
||||
409: 'Conflict',
|
||||
410: 'Gone',
|
||||
411: 'Length Required',
|
||||
412: 'Precondition Required',
|
||||
413: 'Request Entry Too Large',
|
||||
414: 'Request-URI Too Long',
|
||||
415: 'Unsupported Media Type',
|
||||
416: 'Requested Range Not Satisfiable',
|
||||
417: 'Expectation Failed',
|
||||
418: 'I\'m a teapot',
|
||||
429: 'Too Many Requests',
|
||||
500: 'Internal Server Error',
|
||||
501: 'Not Implemented',
|
||||
502: 'Bad Gateway',
|
||||
503: 'Service Unavailable',
|
||||
504: 'Gateway Timeout',
|
||||
505: 'HTTP Version Not Supported'
|
||||
}
|
||||
|
||||
function ErrorImage ({ statusCode }) {
|
||||
if (statusCode === 404) {
|
||||
return <Image className='rounded-1 shadow-sm' width='500' height='376' src={`${process.env.NEXT_PUBLIC_ASSET_PREFIX}/maze.gif`} fluid />
|
||||
}
|
||||
if (statusCode >= 500) {
|
||||
return <Image className='rounded-1 shadow-sm' width='300' height='225' src={`${process.env.NEXT_PUBLIC_ASSET_PREFIX}/falling.gif`} fluid />
|
||||
}
|
||||
if (statusCode >= 400) {
|
||||
return <Image className='rounded-1 shadow-sm' width='500' height='381' src={`${process.env.NEXT_PUBLIC_ASSET_PREFIX}/forbidden.gif`} fluid />
|
||||
}
|
||||
if (statusCode >= 300) {
|
||||
return <Image className='rounded-1 shadow-sm' width='500' height='375' src={`${process.env.NEXT_PUBLIC_ASSET_PREFIX}/double.gif`} fluid />
|
||||
}
|
||||
return <Image className='rounded-1 shadow-sm' width='500' height='376' src={`${process.env.NEXT_PUBLIC_ASSET_PREFIX}/falling.gif`} fluid />
|
||||
}
|
||||
|
||||
export default function Error ({ statusCode }) {
|
||||
return (
|
||||
<StaticLayout>
|
||||
<ErrorImage statusCode={statusCode} />
|
||||
<h1 className={styles.status}><span>{statusCode}</span><span className={styles.describe}>{statusDescribe[statusCode].toUpperCase()}</span></h1>
|
||||
</StaticLayout>
|
||||
)
|
||||
}
|
||||
|
||||
Error.getInitialProps = ({ res, err }) => {
|
||||
const statusCode = res ? res.statusCode : err ? err.statusCode : 404
|
||||
return { statusCode }
|
||||
}
|
|
@ -1,40 +1,16 @@
|
|||
import NextAuth from 'next-auth'
|
||||
import Providers from 'next-auth/providers'
|
||||
import { PrismaLegacyAdapter } from '../../../lib/prisma-adapter'
|
||||
import CredentialsProvider from 'next-auth/providers/credentials'
|
||||
import GitHubProvider from 'next-auth/providers/github'
|
||||
import TwitterProvider from 'next-auth/providers/twitter'
|
||||
import EmailProvider from 'next-auth/providers/email'
|
||||
import prisma from '../../../api/models'
|
||||
import nodemailer from 'nodemailer'
|
||||
import { getSession } from 'next-auth/client'
|
||||
import { PrismaAdapter } from '@auth/prisma-adapter'
|
||||
import { getToken } from 'next-auth/jwt'
|
||||
import { NodeNextRequest } from 'next/dist/server/base-http/node'
|
||||
|
||||
// node v16 and next don't give us parsed headers like v14, so we
|
||||
// do our best to parse them in a similar fashion to like in v14
|
||||
// getSession requires req.headers.cookie otherwise it will throw
|
||||
function parsedHeaders (req) {
|
||||
return req.rawHeaders.reduce(
|
||||
(obj, value, index, arr) => {
|
||||
if (index % 2 === 0) {
|
||||
const key = value.toLowerCase()
|
||||
if (typeof obj[key] === 'string') {
|
||||
if (key === 'cookie') {
|
||||
obj[key] = obj[key] + '; ' + arr[index + 1]
|
||||
} else if (key === 'set-cookie') {
|
||||
obj[key] = obj[key].push(arr[index + 1])
|
||||
} else {
|
||||
obj[key] = obj[key] + ', ' + arr[index + 1]
|
||||
}
|
||||
} else {
|
||||
if (key === 'set-cookie') {
|
||||
obj[key] = [arr[index + 1]]
|
||||
} else {
|
||||
obj[key] = arr[index + 1]
|
||||
}
|
||||
}
|
||||
}
|
||||
return obj
|
||||
}, {})
|
||||
}
|
||||
|
||||
export default (req, res) => NextAuth(req, res, {
|
||||
callbacks: {
|
||||
function getCallbacks (req) {
|
||||
return {
|
||||
/**
|
||||
* @param {object} token Decrypted JSON Web Token
|
||||
* @param {object} user User object (only available on sign in)
|
||||
|
@ -43,13 +19,18 @@ export default (req, res) => NextAuth(req, res, {
|
|||
* @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) {
|
||||
async jwt ({ token, user, account, profile, isNewUser }) {
|
||||
if (user) {
|
||||
// token won't have an id on it for new logins, we add it
|
||||
// note: token is what's kept in the jwt
|
||||
token.id = Number(user.id)
|
||||
// HACK next-auth needs this to do account linking with jwts
|
||||
// see: https://github.com/nextauthjs/next-auth/issues/625
|
||||
token.user = { id: Number(user.id) }
|
||||
}
|
||||
|
||||
if (token?.id) {
|
||||
// HACK token.sub is used by nextjs v4 internally and is used like a userId
|
||||
// setting it here allows us to link multiple auth method to an account
|
||||
// ... in v3 this linking field was token.user.id
|
||||
token.sub = Number(token.id)
|
||||
}
|
||||
|
||||
if (isNewUser) {
|
||||
|
@ -82,149 +63,123 @@ export default (req, res) => NextAuth(req, res, {
|
|||
|
||||
return token
|
||||
},
|
||||
async session (session, token) {
|
||||
// we need to add additional session params here
|
||||
session.user.id = Number(token.id)
|
||||
async session ({ session, token }) {
|
||||
// note: this function takes the current token (result of running jwt above)
|
||||
// and returns a new object session that's returned whenever get|use[Server]Session is called
|
||||
session.user.id = token.id
|
||||
|
||||
return session
|
||||
}
|
||||
},
|
||||
providers: [
|
||||
Providers.Credentials({
|
||||
id: 'lightning',
|
||||
// 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 } })
|
||||
await prisma.lnAuth.delete({ where: { k1 } })
|
||||
if (lnauth.pubkey === pubkey) {
|
||||
let user = await prisma.user.findUnique({ where: { pubkey } })
|
||||
req.headers = parsedHeaders(req)
|
||||
const session = await getSession({ req })
|
||||
if (!user) {
|
||||
// if we are logged in, update rather than create
|
||||
if (session?.user) {
|
||||
user = await prisma.user.update({ where: { id: session.user.id }, data: { pubkey } })
|
||||
} else {
|
||||
user = await prisma.user.create({ data: { name: pubkey.slice(0, 10), pubkey } })
|
||||
}
|
||||
} else if (session && session.user?.id !== user.id) {
|
||||
throw new Error('account not linked')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return user
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
async function pubkeyAuth (credentials, req, pubkeyColumnName) {
|
||||
const { k1, pubkey } = credentials
|
||||
try {
|
||||
const lnauth = await prisma.lnAuth.findUnique({ where: { k1 } })
|
||||
await prisma.lnAuth.delete({ where: { k1 } })
|
||||
if (lnauth.pubkey === pubkey) {
|
||||
let user = await prisma.user.findUnique({ where: { [pubkeyColumnName]: pubkey } })
|
||||
const token = await getToken({ req })
|
||||
if (!user) {
|
||||
// if we are logged in, update rather than create
|
||||
if (token?.id) {
|
||||
user = await prisma.user.update({ where: { id: token.id }, data: { [pubkeyColumnName]: pubkey } })
|
||||
} else {
|
||||
user = await prisma.user.create({ data: { name: pubkey.slice(0, 10), [pubkeyColumnName]: pubkey } })
|
||||
}
|
||||
|
||||
} else if (token && token?.id !== user.id) {
|
||||
return null
|
||||
}
|
||||
}),
|
||||
Providers.Credentials({
|
||||
id: 'slashtags',
|
||||
// The name to display on the sign in form (e.g. 'Sign in with...')
|
||||
name: 'Slashtags',
|
||||
// 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 } })
|
||||
await prisma.lnAuth.delete({ where: { k1 } })
|
||||
if (lnauth.pubkey === pubkey) {
|
||||
let user = await prisma.user.findUnique({ where: { slashtagId: pubkey } })
|
||||
req.headers = parsedHeaders(req)
|
||||
const session = await getSession({ req })
|
||||
if (!user) {
|
||||
// if we are logged in, update rather than create
|
||||
if (session?.user) {
|
||||
user = await prisma.user.update({ where: { id: session.user.id }, data: { slashtagId: pubkey } })
|
||||
} else {
|
||||
user = await prisma.user.create({ data: { name: pubkey.slice(0, 10), slashtagId: pubkey } })
|
||||
}
|
||||
} else if (session && session.user?.id !== user.id) {
|
||||
throw new Error('account not linked')
|
||||
}
|
||||
|
||||
return user
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
return user
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
|
||||
return null
|
||||
return null
|
||||
}
|
||||
|
||||
const providers = [
|
||||
CredentialsProvider({
|
||||
id: 'lightning',
|
||||
name: 'Lightning',
|
||||
credentials: {
|
||||
pubkey: { label: 'publickey', type: 'text' },
|
||||
k1: { label: 'k1', type: 'text' }
|
||||
},
|
||||
authorize: async (credentials, req) => await pubkeyAuth(credentials, new NodeNextRequest(req), 'pubkey')
|
||||
}),
|
||||
CredentialsProvider({
|
||||
id: 'slashtags',
|
||||
name: 'Slashtags',
|
||||
credentials: {
|
||||
pubkey: { label: 'publickey', type: 'text' },
|
||||
k1: { label: 'k1', type: 'text' }
|
||||
},
|
||||
authorize: async (credentials, req) => await pubkeyAuth(credentials, new NodeNextRequest(req), 'slashtagId')
|
||||
}),
|
||||
GitHubProvider({
|
||||
clientId: process.env.GITHUB_ID,
|
||||
clientSecret: process.env.GITHUB_SECRET,
|
||||
authorization: {
|
||||
url: 'https://github.com/login/oauth/authorize',
|
||||
params: { scope: '' }
|
||||
},
|
||||
profile: profile => {
|
||||
return {
|
||||
...profile,
|
||||
name: profile.login
|
||||
}
|
||||
}),
|
||||
Providers.GitHub({
|
||||
clientId: process.env.GITHUB_ID,
|
||||
clientSecret: process.env.GITHUB_SECRET,
|
||||
authorization: 'https://github.com/login/oauth/authorize',
|
||||
scope: '', // read-only acces to public information
|
||||
profile: profile => {
|
||||
return {
|
||||
...profile,
|
||||
name: profile.login
|
||||
}
|
||||
}
|
||||
}),
|
||||
TwitterProvider({
|
||||
clientId: process.env.TWITTER_ID,
|
||||
clientSecret: process.env.TWITTER_SECRET,
|
||||
profile: profile => {
|
||||
return {
|
||||
...profile,
|
||||
name: profile.screen_name
|
||||
}
|
||||
}),
|
||||
Providers.Twitter({
|
||||
clientId: process.env.TWITTER_ID,
|
||||
clientSecret: process.env.TWITTER_SECRET,
|
||||
profile: profile => {
|
||||
return {
|
||||
...profile,
|
||||
name: profile.screen_name
|
||||
}
|
||||
}
|
||||
}),
|
||||
Providers.Email({
|
||||
server: process.env.LOGIN_EMAIL_SERVER,
|
||||
from: process.env.LOGIN_EMAIL_FROM,
|
||||
sendVerificationRequest,
|
||||
profile: profile => {
|
||||
return profile
|
||||
}
|
||||
})
|
||||
],
|
||||
adapter: PrismaLegacyAdapter({ prisma }),
|
||||
secret: process.env.NEXTAUTH_SECRET,
|
||||
session: { jwt: true },
|
||||
jwt: {
|
||||
signingKey: process.env.JWT_SIGNING_PRIVATE_KEY
|
||||
}
|
||||
}),
|
||||
EmailProvider({
|
||||
server: process.env.LOGIN_EMAIL_SERVER,
|
||||
from: process.env.LOGIN_EMAIL_FROM,
|
||||
sendVerificationRequest
|
||||
})
|
||||
]
|
||||
|
||||
export const getAuthOptions = req => ({
|
||||
callbacks: getCallbacks(req),
|
||||
providers,
|
||||
adapter: PrismaAdapter(prisma),
|
||||
session: {
|
||||
strategy: 'jwt'
|
||||
},
|
||||
pages: {
|
||||
signIn: '/login',
|
||||
verifyRequest: '/email'
|
||||
verifyRequest: '/email',
|
||||
error: '/auth/error'
|
||||
}
|
||||
})
|
||||
|
||||
export default async (req, res) => {
|
||||
await NextAuth(req, res, getAuthOptions(req))
|
||||
}
|
||||
|
||||
async function sendVerificationRequest ({
|
||||
identifier: email,
|
||||
url,
|
||||
token,
|
||||
baseUrl,
|
||||
provider
|
||||
}) {
|
||||
const user = await prisma.user.findUnique({ where: { email } })
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const { server, from } = provider
|
||||
// Strip protocol from URL and use domain as site name
|
||||
const site = baseUrl.replace(/^https?:\/\//, '')
|
||||
|
||||
const site = new URL(url).host
|
||||
|
||||
nodemailer.createTransport(server).sendMail(
|
||||
{
|
||||
|
|
|
@ -4,7 +4,8 @@ import resolvers from '../../api/resolvers'
|
|||
import models from '../../api/models'
|
||||
import lnd from '../../api/lnd'
|
||||
import typeDefs from '../../api/typeDefs'
|
||||
import { getSession } from 'next-auth/client'
|
||||
import { getServerSession } from 'next-auth/next'
|
||||
import { getAuthOptions } from './auth/[...nextauth]'
|
||||
import search from '../../api/search'
|
||||
import slashtags from '../../api/slashtags'
|
||||
|
||||
|
@ -21,7 +22,7 @@ const apolloServer = new ApolloServer({
|
|||
return (error, result) => {
|
||||
const end = process.hrtime.bigint()
|
||||
const ms = (end - start) / 1000000n
|
||||
if (ms > 20 && info.parentType.name !== 'User') {
|
||||
if (ms > 5) {
|
||||
console.log(`Field ${info.parentType.name}.${info.fieldName} took ${ms}ms`)
|
||||
}
|
||||
if (error) {
|
||||
|
@ -42,8 +43,8 @@ const apolloServer = new ApolloServer({
|
|||
})
|
||||
|
||||
export default startServerAndCreateNextHandler(apolloServer, {
|
||||
context: async (req) => {
|
||||
const session = await getSession({ req })
|
||||
context: async (req, res) => {
|
||||
const session = await getServerSession(req, res, getAuthOptions(req))
|
||||
return {
|
||||
models,
|
||||
lnd,
|
||||
|
|
|
@ -61,7 +61,7 @@ async function doWithdrawal (query, res) {
|
|||
}
|
||||
|
||||
// create withdrawal in gql
|
||||
const client = await getSSRApolloClient(null, me)
|
||||
const client = await getSSRApolloClient({ me })
|
||||
const { error, data } = await client.mutate({
|
||||
mutation: CREATE_WITHDRAWL,
|
||||
variables: { invoice: query.pr, maxFee: 10 }
|
||||
|
|
|
@ -3,7 +3,7 @@ import { ITEM_OTS } from '../../../../fragments/items'
|
|||
import stringifyCanon from 'canonical-json'
|
||||
|
||||
export default async function handler (req, res) {
|
||||
const client = await getSSRApolloClient(req)
|
||||
const client = await getSSRApolloClient({ req, res })
|
||||
const { data } = await client.query({
|
||||
query: ITEM_OTS,
|
||||
variables: { id: req.query.id }
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
import Image from 'react-bootstrap/Image'
|
||||
import { StaticLayout } from '../../components/layout'
|
||||
import styles from '../../styles/error.module.css'
|
||||
import LightningIcon from '../../svgs/bolt.svg'
|
||||
import { useRouter } from 'next/router'
|
||||
import Button from 'react-bootstrap/Button'
|
||||
|
||||
export function getServerSideProps ({ query }) {
|
||||
return {
|
||||
props: {
|
||||
error: query.error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default function AuthError ({ error }) {
|
||||
const router = useRouter()
|
||||
|
||||
if (error === 'AccessDenied') {
|
||||
return (
|
||||
<StaticLayout>
|
||||
<Image className='rounded-1 shadow-sm' width='500' height='381' src={`${process.env.NEXT_PUBLIC_ASSET_PREFIX}/forbidden.gif`} fluid />
|
||||
<h1 className={[styles.status, styles.smaller].join(' ')}><span>ACCESS DENIED</span></h1>
|
||||
</StaticLayout>
|
||||
)
|
||||
} else if (error === 'Verification') {
|
||||
return (
|
||||
<StaticLayout>
|
||||
<Image className='rounded-1 shadow-sm' width='500' height='375' src={`${process.env.NEXT_PUBLIC_ASSET_PREFIX}/double.gif`} fluid />
|
||||
<h2 className='pt-4'>This magic link has expired.</h2>
|
||||
<h4 className='text-muted pt-2'>Get another by logging in.</h4>
|
||||
<Button
|
||||
className='align-items-center my-3'
|
||||
style={{ borderWidth: '2px' }}
|
||||
id='login'
|
||||
onClick={() => router.push('/login')}
|
||||
size='lg'
|
||||
>
|
||||
<LightningIcon
|
||||
width={24}
|
||||
height={24}
|
||||
className='me-2'
|
||||
/>login
|
||||
</Button>
|
||||
</StaticLayout>
|
||||
)
|
||||
} else if (error === 'Configuration') {
|
||||
return (
|
||||
<StaticLayout>
|
||||
<Image className='rounded-1 shadow-sm' width='500' height='375' src={`${process.env.NEXT_PUBLIC_ASSET_PREFIX}/double.gif`} fluid />
|
||||
<h1 className={[styles.status, styles.smaller].join(' ')}><span>configuration error</span></h1>
|
||||
</StaticLayout>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<StaticLayout>
|
||||
<Image className='rounded-1 shadow-sm' width='500' height='375' src={`${process.env.NEXT_PUBLIC_ASSET_PREFIX}/double.gif`} fluid />
|
||||
<h1 className={[styles.status, styles.smaller].join(' ')}><span>auth error</span></h1>
|
||||
</StaticLayout>
|
||||
|
||||
)
|
||||
}
|
|
@ -5,9 +5,9 @@ export default function Email () {
|
|||
return (
|
||||
<StaticLayout>
|
||||
<div className='p-4 text-center'>
|
||||
<h1>Check your email</h1>
|
||||
<h4 className='pb-4'>A sign in link has been sent to your email address</h4>
|
||||
<Image width='500' height='376' src={`${process.env.NEXT_PUBLIC_ASSET_PREFIX}/hello.gif`} fluid />
|
||||
<Image className='rounded-1 shadow-sm' width='320' height='223' src={`${process.env.NEXT_PUBLIC_ASSET_PREFIX}/hello.gif`} fluid />
|
||||
<h2 className='pt-4'>Check your email</h2>
|
||||
<h4 className='text-muted pt-2'>A sign in link has been sent to your email address</h4>
|
||||
</div>
|
||||
</StaticLayout>
|
||||
)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import Login from '../../components/login'
|
||||
import { providers, getSession } from 'next-auth/client'
|
||||
import { getProviders } from 'next-auth/react'
|
||||
import { getServerSession } from 'next-auth/next'
|
||||
import models from '../../api/models'
|
||||
import serialize from '../../api/resolvers/serial'
|
||||
import { gql } from '@apollo/client'
|
||||
|
@ -7,11 +8,12 @@ import { INVITE_FIELDS } from '../../fragments/invites'
|
|||
import getSSRApolloClient from '../../api/ssrApollo'
|
||||
import Link from 'next/link'
|
||||
import { CenterLayout } from '../../components/layout'
|
||||
import { authOptions } from '../api/auth/[...nextauth]'
|
||||
|
||||
export async function getServerSideProps ({ req, res, query: { id, error = null } }) {
|
||||
const session = await getSession({ req })
|
||||
const session = await getServerSession(req, res, authOptions(req))
|
||||
|
||||
const client = await getSSRApolloClient(req)
|
||||
const client = await getSSRApolloClient({ req, res })
|
||||
const { data } = await client.query({
|
||||
query: gql`
|
||||
${INVITE_FIELDS}
|
||||
|
@ -38,16 +40,17 @@ export async function getServerSideProps ({ req, res, query: { id, error = null
|
|||
console.log(e)
|
||||
}
|
||||
|
||||
res.writeHead(302, {
|
||||
Location: '/'
|
||||
})
|
||||
res.end()
|
||||
return { props: {} }
|
||||
return {
|
||||
redirect: {
|
||||
destination: '/',
|
||||
permanent: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
providers: await providers({ req, res }),
|
||||
providers: await getProviders(),
|
||||
callbackUrl: process.env.PUBLIC_URL + req.url,
|
||||
invite: data.invite,
|
||||
error
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import { providers, getSession } from 'next-auth/client'
|
||||
import { getProviders } from 'next-auth/react'
|
||||
import { getServerSession } from 'next-auth/next'
|
||||
import { getAuthOptions } from './api/auth/[...nextauth]'
|
||||
import Link from 'next/link'
|
||||
import { StaticLayout } from '../components/layout'
|
||||
import Login from '../components/login'
|
||||
import { isExternal } from '../lib/url'
|
||||
|
||||
export async function getServerSideProps ({ req, res, query: { callbackUrl, error = null } }) {
|
||||
const session = await getSession({ req })
|
||||
const session = await getServerSession(req, res, getAuthOptions(req))
|
||||
|
||||
// prevent open redirects. See https://github.com/stackernews/stacker.news/issues/264
|
||||
// let undefined urls through without redirect ... otherwise this interferes with multiple auth linking
|
||||
|
@ -20,17 +22,18 @@ export async function getServerSideProps ({ req, res, query: { callbackUrl, erro
|
|||
callbackUrl = '/'
|
||||
}
|
||||
|
||||
if (session && res && callbackUrl) {
|
||||
res.writeHead(302, {
|
||||
Location: callbackUrl
|
||||
})
|
||||
res.end()
|
||||
return { props: {} }
|
||||
if (session && callbackUrl) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: callbackUrl,
|
||||
permanent: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
providers: await providers({ req, res }),
|
||||
providers: await getProviders(),
|
||||
callbackUrl,
|
||||
error
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import { useState } from 'react'
|
|||
import { gql, useMutation, useQuery } from '@apollo/client'
|
||||
import { getGetServerSideProps } from '../api/ssrApollo'
|
||||
import LoginButton from '../components/login-button'
|
||||
import { signIn } from 'next-auth/client'
|
||||
import { signIn } from 'next-auth/react'
|
||||
import { LightningAuth, SlashtagsAuth } from '../components/lightning-auth'
|
||||
import { SETTINGS, SET_SETTINGS } from '../fragments/users'
|
||||
import { useRouter } from 'next/router'
|
||||
|
@ -359,7 +359,7 @@ function AuthMethods ({ methods }) {
|
|||
// if there's only one auth method left
|
||||
const links = providers.reduce((t, p) => t + (methods[p] ? 1 : 0), 0)
|
||||
if (links === 1) {
|
||||
showModal(onClose => (<UnlinkObstacle onClose={onClose} type={type} />))
|
||||
showModal(onClose => (<UnlinkObstacle onClose={onClose} type={type} unlinkAuth={unlinkAuth} />))
|
||||
} else {
|
||||
await unlinkAuth({ variables: { authType: type } })
|
||||
}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `compound_id` on the `accounts` table. All the data in the column will be lost.
|
||||
- The `access_token_expires` column on the `accounts` table would be dropped and recreated. This will lead to data loss if there is data in the column.
|
||||
- You are about to drop the column `access_token` on the `sessions` table. All the data in the column will be lost.
|
||||
- A unique constraint covering the columns `[provider_id,provider_account_id]` on the table `accounts` will be added. If there are existing duplicate values, this will fail.
|
||||
- A unique constraint covering the columns `[identifier,token]` on the table `verification_requests` will be added. If there are existing duplicate values, this will fail.
|
||||
|
||||
*/
|
||||
-- DropIndex
|
||||
DROP INDEX "accounts.compound_id_unique";
|
||||
|
||||
-- DropIndex
|
||||
DROP INDEX "accounts.provider_account_id_index";
|
||||
|
||||
-- DropIndex
|
||||
DROP INDEX "accounts.provider_id_index";
|
||||
|
||||
-- DropIndex
|
||||
DROP INDEX "sessions.access_token_unique";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "accounts" DROP COLUMN "compound_id",
|
||||
ADD COLUMN "id_token" TEXT,
|
||||
ADD COLUMN "scope" TEXT,
|
||||
ADD COLUMN "session_state" TEXT,
|
||||
ADD COLUMN "token_type" TEXT;
|
||||
|
||||
ALTER TABLE accounts ALTER COLUMN "access_token_expires" TYPE TEXT USING CAST(extract(epoch FROM "access_token_expires") AS BIGINT)*1000;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "sessions" DROP COLUMN "access_token";
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "accounts_provider_id_provider_account_id_key" ON "accounts"("provider_id", "provider_account_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "verification_requests_identifier_token_key" ON "verification_requests"("identifier", "token");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "accounts" ADD CONSTRAINT "accounts_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
@ -80,6 +80,8 @@ model User {
|
|||
photo Upload? @relation(fields: [photoId], references: [id])
|
||||
referrer User? @relation("referrals", fields: [referrerId], references: [id])
|
||||
referrees User[] @relation("referrals")
|
||||
Account Account[]
|
||||
Session Session[]
|
||||
|
||||
@@index([createdAt], map: "users.created_at_index")
|
||||
@@index([inviteId], map: "users.inviteId_index")
|
||||
|
@ -426,37 +428,42 @@ model Withdrawl {
|
|||
}
|
||||
|
||||
model Account {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
compoundId String @unique(map: "accounts.compound_id_unique") @map("compound_id")
|
||||
userId Int @map("user_id")
|
||||
providerType String @map("provider_type")
|
||||
providerId String @map("provider_id")
|
||||
providerAccountId String @map("provider_account_id")
|
||||
refreshToken String? @map("refresh_token")
|
||||
accessToken String? @map("access_token")
|
||||
accessTokenExpires DateTime? @map("access_token_expires")
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
userId Int @map("user_id")
|
||||
type String @map("provider_type")
|
||||
provider String @map("provider_id")
|
||||
providerAccountId String @map("provider_account_id")
|
||||
refresh_token String? @map("refresh_token")
|
||||
access_token String? @map("access_token")
|
||||
expires_at String? @map("access_token_expires")
|
||||
token_type String?
|
||||
scope String?
|
||||
id_token String?
|
||||
session_state String?
|
||||
|
||||
@@index([providerAccountId], map: "accounts.provider_account_id_index")
|
||||
@@index([providerId], map: "accounts.provider_id_index")
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([provider, providerAccountId])
|
||||
@@index([userId], map: "accounts.user_id_index")
|
||||
@@map("accounts")
|
||||
}
|
||||
|
||||
model Session {
|
||||
id Int @id @default(autoincrement())
|
||||
sessionToken String @unique(map: "sessions.session_token_unique") @map("session_token")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
userId Int @map("user_id")
|
||||
expires DateTime
|
||||
sessionToken String @unique(map: "sessions.session_token_unique") @map("session_token")
|
||||
accessToken String @unique(map: "sessions.access_token_unique") @map("access_token")
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@map("sessions")
|
||||
}
|
||||
|
||||
model VerificationRequest {
|
||||
model VerificationToken {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
@ -464,6 +471,7 @@ model VerificationRequest {
|
|||
token String @unique(map: "verification_requests.token_unique")
|
||||
expires DateTime
|
||||
|
||||
@@unique([identifier, token])
|
||||
@@map("verification_requests")
|
||||
}
|
||||
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 631 KiB |
Binary file not shown.
After Width: | Height: | Size: 642 KiB |
|
@ -1,13 +0,0 @@
|
|||
.fourZeroFour {
|
||||
font-family: 'lightning';
|
||||
font-size: 96px;
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
margin-top: 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.notFound {
|
||||
font-size: 24px;
|
||||
}
|
|
@ -8,6 +8,10 @@
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.smaller {
|
||||
font-size: 48px;
|
||||
}
|
||||
|
||||
.describe {
|
||||
font-size: 24px;
|
||||
}
|
|
@ -167,6 +167,10 @@ mark {
|
|||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.btn-outline-grey-darkmode:hover, .btn-outline-grey-darkmode:active {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.table {
|
||||
color: var(--theme-color);
|
||||
background-color: var(--theme-body);
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
const PgBoss = require('pg-boss')
|
||||
const dotenv = require('dotenv')
|
||||
dotenv.config({ path: '..' })
|
||||
require('@next/env').loadEnvConfig('..')
|
||||
const { PrismaClient } = require('@prisma/client')
|
||||
const { checkInvoice, checkWithdrawal } = require('./wallet')
|
||||
const { repin } = require('./repin')
|
||||
|
|
Loading…
Reference in New Issue