Skip to content
Snippets Groups Projects
Commit 28d7be06 authored by Niklas P's avatar Niklas P
Browse files

add api route

parent a8823276
No related branches found
No related tags found
No related merge requests found
......@@ -4,7 +4,6 @@ import 'primereact/resources/themes/md-dark-indigo/theme.css'
import 'primereact/resources/primereact.min.css'
import { Dropdown } from 'primereact/dropdown'
import styles from '@/styles/Home.module.css'
import { usePolkadotExtension } from '@/hooks/use-polkadot-extension';
import { usePolkadotExtensionWithContext } from '@/context/polkadotExtensionContext';
export const accountValueTemplate = (option: any, props: any) => {
......
......@@ -49,7 +49,7 @@ export default function LoginButton() {
// will return a promise https://next-auth.js.org/getting-started/client#using-the-redirect-false-option
const result = await signIn('credentials', {
redirect: false,
callbackUrl: '/protected',
callbackUrl: '/protected-api',
message: JSON.stringify(message),
name: actingAccount?.meta?.name,
signature,
......@@ -58,7 +58,7 @@ export default function LoginButton() {
// take the user to the protected page if they are allowed
if(result?.url) {
router.push("/protected");
router.push("/protected-api");
}
setError( result?.error )
......@@ -85,7 +85,7 @@ export default function LoginButton() {
{ session ?
<>
<Link
href="/protected"
href="/protected-api"
className={styles.card}
>
<h2 className={inter.className}>
......
......@@ -6,10 +6,8 @@ import {
createContext,
ReactNode,
useContext,
useEffect,
useState,
} from "react";
import { InjectedAccountWithMeta } from "@polkadot/extension-inject/types";
import {
usePolkadotExtension,
UsePolkadotExtensionReturnType,
......
import { useEffect, useRef } from 'react';
export const useIsMounted = (): { readonly current: boolean } => {
const isMountedRef = useRef<boolean>(false);
useEffect(() => {
isMountedRef.current = true;
return () => {
isMountedRef.current = false;
};
}, []);
return isMountedRef;
};
\ No newline at end of file
......@@ -3,55 +3,8 @@ import {
InjectedExtension,
} from "@polkadot/extension-inject/types";
import { useEffect, useState } from "react";
import { useIsMounted } from "./use-is-mounted";
import { documentReadyPromise } from "./utils";
interface checkEnabledReturnType {
accounts: InjectedAccountWithMeta[] | null;
error: Error | null;
}
export const extensionSetup = async () => {
const extensionDapp = await import("@polkadot/extension-dapp");
const { web3Accounts, web3Enable, web3AccountsSubscribe } = extensionDapp;
const enabledApps = await web3Enable("polkadot-extension");
console.log("enabled Apps", enabledApps);
if (enabledApps.length === 0) {
console.log("no extension");
return;
}
};
export const checkEnabled: (
extensionName: string
) => Promise<checkEnabledReturnType> = async (
extensionName: string = "polkadot-extension"
) => {
const extensionDapp = await import("@polkadot/extension-dapp");
const { web3Accounts, web3Enable } = extensionDapp;
try {
const enabledApps = await web3Enable(extensionName);
console.log("enabled Apps", enabledApps);
const w3Enabled = enabledApps.length > 0;
let accounts = null;
if (w3Enabled) {
accounts = await web3Accounts();
console.log("accounts", accounts);
return { accounts, error: null };
}
return {
accounts: null,
error: new Error(
"please allow your extension to access this dApp and refresh the page or install a substrate wallet"
),
};
} catch (error: any) {
return { accounts: null, error };
}
};
export interface UsePolkadotExtensionReturnType {
isReady: boolean;
accounts: InjectedAccountWithMeta[] | null;
......@@ -62,7 +15,6 @@ export interface UsePolkadotExtensionReturnType {
}
export const usePolkadotExtension = (): UsePolkadotExtensionReturnType => {
const isMounted = useIsMounted();
const [isReady, setIsReady] = useState(false);
const [accounts, setAccounts] = useState<InjectedAccountWithMeta[] | null>(
null
......@@ -77,15 +29,14 @@ export const usePolkadotExtension = (): UsePolkadotExtensionReturnType => {
const actingAccount = accounts && accounts[actingAccountIdx];
useEffect(() => {
const setup = async () => {
// This effect is used to setup the browser extension
const extensionSetup = async () => {
const extensionDapp = await import("@polkadot/extension-dapp");
const { web3AccountsSubscribe, web3Enable, web3Accounts } = extensionDapp;
// const enabledApps = await web3Enable("polkadot-extension");
const { web3AccountsSubscribe, web3Enable } = extensionDapp;
const injectedPromise = documentReadyPromise(() =>
web3Enable("polkadot-extension")
web3Enable("Polkadot Tokengated Website Demo")
);
// const injectedPromise = await web3Enable("polkadot-extension");
const extensions = await injectedPromise;
setExtensions(extensions);
......@@ -95,8 +46,6 @@ export const usePolkadotExtension = (): UsePolkadotExtensionReturnType => {
return;
}
// const accounts = await web3Accounts();
if (accounts) {
setIsReady(true);
} else {
......@@ -113,11 +62,13 @@ export const usePolkadotExtension = (): UsePolkadotExtensionReturnType => {
};
if (!isReady) {
setup();
extensionSetup();
}
}, [extensions, isReady]);
}, [extensions]);
useEffect(() => {
// This effect is used to get the injector from the selected account
// and is triggered when the accounts or the actingAccountIdx change
const getInjector = async () => {
const { web3FromSource } = await import("@polkadot/extension-dapp");
const actingAccount =
......
/**
* Returns a promise that resolves when the document is ready.
* @param creator
* @returns
*/
export function documentReadyPromise<T>(creator: () => Promise<T>): Promise<T> {
return new Promise((resolve): void => {
if (document.readyState === "complete") {
......
import { getToken } from "next-auth/jwt"
import type { NextApiRequest, NextApiResponse } from "next"
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
// If you don't have the NEXTAUTH_SECRET environment variable set,
// you will have to pass your secret as `secret` to `getToken`
const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET })
console.log( 'token', token)
if (token) {
// Signed in
console.log("JSON Web Token", JSON.stringify(token, null, 2))
res.send(JSON.stringify(token, null, 2))
} else {
// Not Signed in
res.status(401)
}
res.end()
}
\ No newline at end of file
import NextAuth, { NextAuthOptions } from 'next-auth';
import NextAuth, { NextAuthOptions, User } from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
import { signatureVerify } from '@polkadot/util-crypto';
import { encodeAddress } from '@polkadot/keyring';
......@@ -57,7 +57,7 @@ export const authOptions: NextAuthOptions = {
placeholder: 'name',
},
},
async authorize(credentials): Promise<any | null> {
async authorize(credentials): Promise<User | null> {
if (credentials === undefined) {
return null;
}
......@@ -65,25 +65,23 @@ export const authOptions: NextAuthOptions = {
const message = JSON.parse(credentials.message);
//verify the message is from the same domain
console.log(message.uri, message.uri);
console.log('process.env.NEXTAUTH_URL', process.env.NEXTAUTH_URL);
if (message.uri !== process.env.NEXTAUTH_URL) {
return Promise.reject(new Error('🚫 You shall not pass!'));
}
// verify the message was not compromised
console.log(message.nonce, message.nonce);
console.log('credentials.csrfToken', credentials.csrfToken);
if (message.nonce !== credentials.csrfToken) {
return Promise.reject(new Error('🚫 You shall not pass!'));
}
// verify signature of the message
// highlight-start
const { isValid } = signatureVerify(
credentials.message,
credentials.signature,
credentials.address,
);
// highlight-end
if (!isValid) {
return Promise.reject(new Error('🚫 Invalid Signature'));
......@@ -98,6 +96,7 @@ export const authOptions: NextAuthOptions = {
if (credentials?.address) {
const ksmAddress = encodeAddress(credentials.address, 2);
// highlight-start
const accountInfo = await api.query.system.account(ksmAddress);
if (accountInfo.data.free.gt(new BN(1_000_000_000_000))) {
......@@ -111,6 +110,7 @@ export const authOptions: NextAuthOptions = {
} else {
return Promise.reject(new Error('🚫 The gate is closed for you'));
}
// highlight-end
}
return Promise.reject(new Error('🚫 API Error'));
......
......@@ -55,7 +55,13 @@ export default function Home() {
href="/protected"
rel="noopener noreferrer"
>
🔐 Go to /protected
🔐 Go to /protected (SSR)
</Link>
<Link
href="/protected-api"
rel="noopener noreferrer"
>
🔐 Go to /protected-api (Static)
</Link>
</div>
</main>
......
import { useState, useEffect } from "react"
import { useSession } from "next-auth/react"
import styles from '@/styles/Home.module.css'
import Link from "next/link"
import { formatBalance } from '@polkadot/util';
import PolkadotParticles from "@/components/polkadot-particles"
/**
* This is a protected page, it can only be accessed by authenticated users. Instead of using Server Side
* Rendering (SSR) to fetch the content, we use Static Generation and a protected API Route `/pages/api/auth.ts`
* to fetch the content client side after the user has logged in.
*/
export default function ProtectedPage() {
const { data: session } = useSession()
const [content, setContent] = useState()
// Fetch content from protected route
useEffect(() => {
const fetchData = async () => {
const res = await fetch("/api/auth")
if (res.ok) {
const json = await res.json()
if (json?.content) {
setContent(json.content)
}
}
}
fetchData()
}, [session])
// If no session exists, display access denied message
if (!session) {
return (
<main className={ styles.protected }>
<h1>You did not pass the 🚪</h1>
<p><Link href="/" className={ styles.colorA }>&lt; go back</Link></p>
</main>
)
}
// format the big number to a human readable format
const ksmBalance = formatBalance( session.freeBalance, { decimals: 12, withSi: true, withUnit: 'KSM' } )
// If session exists, display content
return (
<main className={ styles.protected }>
<h1>🎉 Welcome { session.user?.name }, you passed the 🚪</h1>
<p>with { ksmBalance }</p>
<p>You are seeing a protected route that uses a static page and a protected api route. See the code at <code>/pages/protected.tsx</code></p>
<p> <Link href="/" className={ styles.colorA }>&lt; go back</Link></p>
<PolkadotParticles />
</main>
)
}
\ No newline at end of file
......@@ -5,9 +5,7 @@ import { authOptions } from "./api/auth/[...nextauth]"
import { BN, formatBalance, BN_ZERO } from '@polkadot/util';
import { GetServerSideProps } from 'next'
import styles from '@/styles/Home.module.css'
import PolkadotParticles from "@/components/polkadot-particles"
export default function Admin( { freeBalance } : { freeBalance : BN } ) : JSX.Element {
......@@ -34,16 +32,14 @@ export default function Admin( { freeBalance } : { freeBalance : BN } ) : JSX.El
<main className={ styles.protected }>
<h1>🎉 Welcome { session.user?.name }, you passed the 🚪</h1>
<p>with { ksmBalance }</p>
<p>You are seeing a /protected route. <Link href="/" className={ styles.colorA }>&lt; go back</Link></p>
<p>You are seeing a /protected route that uses Server-Side Generation at <code>/pages/protected.tsx</code> </p>
<p><Link href="/" className={ styles.colorA }>&lt; go back</Link></p>
<PolkadotParticles />
</main>
)
}
type Data = {
freeBalance: BN,
}
// this tells next to render the page on the server side
export const getServerSideProps: GetServerSideProps = async (context) => {
// Get the user's session based on the request
......
......@@ -17,6 +17,7 @@
width: 100%;
z-index: 2;
font-family: var(--font-mono);
flex-wrap: wrap;
}
.description a {
......@@ -24,6 +25,7 @@
justify-content: center;
align-items: center;
gap: 0.5rem;
padding: 0.5rem;
}
.description p {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment