Skip to content

Commit c71d8e0

Browse files
committed
auth.js logic
1 parent 471b9c8 commit c71d8e0

File tree

3 files changed

+119
-16
lines changed

3 files changed

+119
-16
lines changed

auth.ts

Lines changed: 94 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,37 +5,115 @@ import NextAuth from 'next-auth';
55
import authConfig from './auth.config';
66
import mongoClientPromise from './lib/mongo-client';
77

8+
// database but jwt ¯\_(ツ)_/¯
9+
// https://authjs.dev/guides/edge-compatibility#middleware
10+
811
export const { handlers, signIn, signOut, auth } = NextAuth({
912
adapter: MongoDBAdapter(mongoClientPromise),
1013
session: { strategy: 'jwt' },
1114
...authConfig,
1215
events: {
13-
signIn: async ({ account, user, profile, isNewUser }) => {
14-
if (isNewUser && account?.provider === 'github') {
16+
signIn: async ({ account, user, profile }) => {
17+
// profile - user entity from github
18+
// user - user entity from the database
19+
// account - OAuth account information
20+
21+
if (account) {
1522
const client = await mongoClientPromise;
16-
const db = client.db('auth');
17-
18-
await db
19-
.collection('accounts')
20-
.updateOne(
21-
{ userId: new ObjectId(user.id) },
22-
{ $set: { githubId: profile?.node_id, githubLogin: profile?.login } },
23-
);
23+
const db = client.db('auth').collection('accounts');
24+
25+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
26+
const { expires_in, refresh_token_expires_in, ...accountData } = account;
27+
28+
await db.updateOne(
29+
{ userId: new ObjectId(user.id), provider: 'github' },
30+
{ $set: { ...accountData, githubId: profile?.node_id, githubLogin: profile?.login } },
31+
);
2432
}
2533
},
2634
},
2735
callbacks: {
2836
async jwt({ token, profile, account }) {
29-
if (profile && account?.provider === 'github') {
30-
token.githubLogin = profile.login;
37+
// token: the existing token (an object you can modify)
38+
// user: only present on first sign-in (the returned user object)
39+
// account: only on first sign-in (OAuth/account info)
40+
// profile: only on first sign-in (OAuth profile)
41+
// isNewUser: boolean flag on first sign-in
42+
43+
token.error = undefined;
44+
45+
if (account && profile) {
46+
// First-time login, save the `access_token`, its expiry and the `refresh_token`
47+
return {
48+
...token,
49+
access_token: account.access_token,
50+
expires_at: account.expires_at,
51+
refresh_token: account.refresh_token,
52+
githubLogin: profile.login,
53+
};
54+
} else if (Date.now() < (token.expires_at as number) * 1000) {
55+
// Subsequent logins, but the `access_token` is still valid
56+
return token;
57+
}
58+
59+
// Subsequent logins, but the `access_token` has expired, try to refresh it
60+
if (!token.refresh_token) {
61+
throw new TypeError('Missing refresh_token');
62+
}
63+
64+
try {
65+
const response = await fetch('https://github.com/login/oauth/access_token', {
66+
method: 'POST',
67+
headers: {
68+
'Content-Type': 'application/x-www-form-urlencoded',
69+
Accept: 'application/json',
70+
},
71+
body: new URLSearchParams({
72+
client_id: process.env.AUTH_GITHUB_ID!,
73+
client_secret: process.env.AUTH_GITHUB_SECRET!,
74+
grant_type: 'refresh_token',
75+
refresh_token: token.refresh_token as string,
76+
}),
77+
});
78+
79+
const tokensOrError = await response.json();
80+
81+
if (!response.ok) {
82+
throw tokensOrError;
83+
}
84+
85+
const newTokens = tokensOrError as {
86+
access_token: string;
87+
expires_in: number;
88+
refresh_token?: string;
89+
};
90+
91+
const newData = {
92+
access_token: newTokens.access_token,
93+
expires_at: Math.floor(Date.now() / 1000 + newTokens.expires_in),
94+
refresh_token: newTokens.refresh_token,
95+
};
96+
97+
const client = await mongoClientPromise;
98+
const db = client.db('auth').collection('accounts');
99+
100+
await db.updateOne({ githubLogin: token.githubLogin, provider: 'github' }, { $set: newData });
101+
102+
return { ...token, ...newData };
103+
} catch {
104+
// If we fail to refresh the token, return an error so we can handle it on the page
105+
token.error = 'RefreshTokenError';
106+
return token;
31107
}
32-
return token;
33108
},
34109

35110
async session({ session, token }) {
36-
if (token?.githubLogin) {
37-
session.user.githubLogin = token.githubLogin as string;
38-
}
111+
// session: what will be returned to the client
112+
// token: the latest JWT (as returned by your jwt() callback)
113+
// user: database user (if you’re using a database session strategy)
114+
115+
session.error = token.error as string | undefined;
116+
session.user.githubLogin = token.githubLogin as string;
39117
return session;
40118
},
41119
},

components/signin-button/signin-button.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ import { signIn, signOut, useSession } from 'next-auth/react';
77
import { getInitials } from '@/utils/get-initials';
88

99
import { Button } from '../ui/button';
10+
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '../ui/dialog';
1011
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../ui/dropdown-menu';
1112

1213
export default function SigninButton() {
1314
const { data: session } = useSession();
1415

16+
console.log('session', session);
17+
1518
return (
1619
<div>
1720
{session ? (
@@ -34,6 +37,23 @@ export default function SigninButton() {
3437
<LogIn className="size-4" />
3538
</Button>
3639
)}
40+
41+
<Dialog open={session?.error === 'RefreshTokenError'}>
42+
<DialogContent>
43+
<DialogHeader>
44+
<DialogTitle>Oops, Token Timeout!</DialogTitle>
45+
<DialogDescription>
46+
Your GitHub access token decided to take a break. Sign in again to get back to business.
47+
</DialogDescription>
48+
</DialogHeader>
49+
<DialogFooter>
50+
<Button type="button" variant="secondary" onClick={() => signOut()}>
51+
Maybe later
52+
</Button>
53+
<Button onClick={() => signIn('github')}>Sign in again</Button>
54+
</DialogFooter>
55+
</DialogContent>
56+
</Dialog>
3757
</div>
3858
);
3959
}

types/next-auth.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ declare module 'next-auth' {
66
}
77

88
interface Session extends DefaultSession {
9+
error?: string;
910
user: {
1011
githubLogin?: string;
1112
} & DefaultSession['user'];
@@ -14,6 +15,10 @@ declare module 'next-auth' {
1415

1516
declare module 'next-auth/jwt' {
1617
interface JWT extends DefaultJWT {
18+
error?: string;
19+
access_token?: string;
20+
expires_at?: number;
21+
refresh_token?: string;
1722
githubLogin?: string;
1823
}
1924
}

0 commit comments

Comments
 (0)