Skip to content

Commit

Permalink
chore: setup next-auth
Browse files Browse the repository at this point in the history
  • Loading branch information
guanbinrui committed Mar 7, 2024
1 parent 01699a2 commit 393ab05
Show file tree
Hide file tree
Showing 11 changed files with 201 additions and 1 deletion.
23 changes: 23 additions & 0 deletions next-auth.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { DefaultSession } from 'next-auth';
import { DefaultJWT } from 'next-auth/jwt';

declare module 'next-auth' {
/**
* Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context
*/
interface Session {
user: {} & DefaultSession['user'];
}
}

declare module 'next-auth/jwt' {
/** Returned by the `jwt` callback and `getToken`, when using JWT sessions */
type JWT = DefaultJWT &
Record<
string,
{
accessToken?: string;
refreshToken?: string;
}
>;
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"react": "^18",
"react-dom": "^18",
"react-use": "^17.4.0",
"twitter-api-sdk": "^1.2.1",
"urlcat": "^3.1.0",
"uuid": "^9.0.0",
"viem": "^1.16.2",
Expand Down
26 changes: 26 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions src/app/[...not_found]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export default function NotFound() {
return (
<main className="grid min-h-full place-items-center bg-white px-6 py-24 sm:py-32 lg:px-8">
<div className="text-center">
<p className="text-base font-semibold text-indigo-600">404</p>
<h1 className="mt-4 text-3xl font-bold tracking-tight text-gray-900 sm:text-5xl">Page not found</h1>
<p className="mt-6 text-base leading-7 text-gray-600">
Sorry, we couldn’t find the page you’re looking for.
</p>
<div className="mt-10 flex items-center justify-center gap-x-6">
<a
href="#"
className="rounded-md bg-indigo-600 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
Go back home
</a>
<a href="#" className="text-sm font-semibold text-gray-900">
Contact support <span aria-hidden="true">&rarr;</span>
</a>
</div>
</div>
</main>
);
}
73 changes: 73 additions & 0 deletions src/app/api/auth/[...nextauth]/options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/* cspell:disable */

import { AuthOptions } from 'next-auth';

import { CredentialsProvider } from '@/esm/CredentialsProvider.js';
import { Twitter } from '@/esm/Twitter.js';

export const authOptions = {
debug: process.env.NODE_ENV === 'development',
providers: [
Twitter({
id: 'twitter_legacy',
clientId: process.env.TWITTER_CLIENT_ID,
clientSecret: process.env.TWITTER_CLIENT_SECRET,
}),
Twitter({
clientId: process.env.TWITTER_CLIENT_ID,
clientSecret: process.env.TWITTER_CLIENT_SECRET,
version: '2.0',
}),
CredentialsProvider({
name: 'Credentials',
credentials: {
username: { label: 'Username', type: 'text', placeholder: 'jsmith' },
password: { label: 'Password', type: 'password' },
},
async authorize(credentials: Record<'username' | 'password', string> | undefined) {
const user = { id: '1', name: 'jsmith', email: '[email protected]' };

if (credentials?.username === user.name && credentials?.password === 'password') {
return user;
}
return null;
},
}),
],
callbacks: {
jwt: async ({ token, user, account, profile, trigger, session }) => {
console.log('DEBUG: jwt');
console.log({
token,
user,
account,
profile,
session,
trigger,
});

// export tokens to session
if (account && session) {
session[account.provider] = {
...session[account.provider],
accessToken: account.accessToken,
refreshToken: account.refreshToken,
};
}

if (account?.provider && !token[account.provider]) {
token[account.provider] = {};
}

if (account?.access_token) {
token[account.provider].accessToken = account.access_token;
}

if (account?.refresh_token) {
token[account.provider].refreshToken = account.refresh_token!;
}

return token;
},
},
} satisfies AuthOptions;
7 changes: 7 additions & 0 deletions src/app/api/auth/[...nextauth]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import NextAuth from 'next-auth';

import { authOptions } from '@/app/api/auth/[...nextauth]/options.js';

const handler = NextAuth(authOptions);

export { handler as GET, handler as POST };
3 changes: 3 additions & 0 deletions src/app/api/health/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export async function GET(request: Request) {
return new Response('OK', { status: 200 });
}
37 changes: 37 additions & 0 deletions src/app/api/twitter/me/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { StatusCodes } from 'http-status-codes';
import { NextRequest } from 'next/server.js';
import { getServerSession } from 'next-auth';
import { getToken, JWT } from 'next-auth/jwt';
import { Client } from 'twitter-api-sdk';

import { authOptions } from '@/app/api/auth/[...nextauth]/options.js';
import { createErrorResponseJSON } from '@/helpers/createErrorResponseJSON.js';
import { createSuccessResponseJSON } from '@/helpers/createSuccessResponseJSON.js';

export function createTwitterClientV2(token: JWT) {
if (!token.twitter.accessToken) throw new Error('No Twitter token found');
return new Client(token.twitter.accessToken);
}


export async function GET(req: NextRequest) {
try {
const token = await getToken({
req,
});
const session = await getServerSession(authOptions);

if (!token || !session) return createErrorResponseJSON('Unauthorized', { status: StatusCodes.UNAUTHORIZED });

const client = createTwitterClientV2(token as JWT);
const results = await client.users.findMyUser();

return createSuccessResponseJSON(results, { status: StatusCodes.OK });
} catch (error) {
console.log(error);

return createErrorResponseJSON(error instanceof Error ? error.message : 'Internal Server Error', {
status: StatusCodes.INTERNAL_SERVER_ERROR,
});
}
}
3 changes: 3 additions & 0 deletions src/esm/CredentialsProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import NextAuthCredentialsProvider from 'next-auth/providers/credentials';

export const CredentialsProvider = NextAuthCredentialsProvider as unknown as typeof NextAuthCredentialsProvider.default as unknown as typeof NextAuthCredentialsProvider.default;
3 changes: 3 additions & 0 deletions src/esm/Twitter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import NextAuthTwitter from 'next-auth/providers/twitter';

export const Twitter = NextAuthTwitter as unknown as typeof NextAuthTwitter.default
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,6 @@
"@/*": ["./src/*"]
}
},
"include": ["globals.d.ts", "next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"include": ["globals.d.ts", "next-env.d.ts", "next-auth.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}

0 comments on commit 393ab05

Please sign in to comment.