Skip to content

Commit 9cd9499

Browse files
add login updates (#637)
* add login updates * add login updates * edge case fixes
1 parent f6b398f commit 9cd9499

File tree

3 files changed

+284
-44
lines changed

3 files changed

+284
-44
lines changed

src/pages/api/auth/[...nextauth].ts

Lines changed: 80 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,28 @@ import NextAuth from "next-auth";
22
import GithubProvider from "next-auth/providers/github";
33
import type { AuthOptions } from "next-auth";
44

5+
// Utility function to handle GitHub API requests with timeout
6+
const fetchWithTimeout = async (
7+
url: string,
8+
options: RequestInit,
9+
timeout = 5000
10+
): Promise<Response> => {
11+
const controller = new AbortController();
12+
const timeoutId = setTimeout(() => controller.abort(), timeout);
13+
14+
try {
15+
const response = await fetch(url, {
16+
...options,
17+
signal: controller.signal,
18+
});
19+
clearTimeout(timeoutId);
20+
return response;
21+
} catch (error) {
22+
clearTimeout(timeoutId);
23+
throw error;
24+
}
25+
};
26+
527
export const authOptions: AuthOptions = {
628
providers: [
729
GithubProvider({
@@ -16,52 +38,98 @@ export const authOptions: AuthOptions = {
1638
],
1739
callbacks: {
1840
async signIn({ account }) {
19-
if (account?.provider === "github") {
41+
if (account?.provider === "github" && account.access_token) {
2042
try {
21-
const res = await fetch("https://api.github.com/user/orgs", {
22-
headers: {
23-
Authorization: `Bearer ${account.access_token}`,
43+
// Add timeout protection to the GitHub API call
44+
const res = await fetchWithTimeout(
45+
"https://api.github.com/user/orgs",
46+
{
47+
headers: {
48+
Accept: "application/vnd.github.v3+json",
49+
Authorization: `Bearer ${account.access_token}`,
50+
"User-Agent": "NextAuth.js",
51+
},
2452
},
25-
});
53+
5000 // 5 second timeout
54+
);
55+
56+
// Handle rate limiting
57+
if (res.status === 403) {
58+
console.error("GitHub API rate limit exceeded");
59+
return false;
60+
}
2661

2762
if (!res.ok) {
63+
console.error(`GitHub API error: ${res.status}`);
2864
return false;
2965
}
3066

3167
const orgs = await res.json();
3268

3369
if (!Array.isArray(orgs)) {
70+
console.error("Invalid response from GitHub API");
3471
return false;
3572
}
3673

37-
const isMember = orgs.some((org) => org.login === process.env.GITHUB_ORG);
74+
const githubOrg = process.env.GITHUB_ORG;
75+
if (!githubOrg) {
76+
console.error("GITHUB_ORG environment variable not set");
77+
return false;
78+
}
79+
80+
const isMember = orgs.some((org) => org.login === githubOrg);
3881

3982
if (!isMember) {
83+
console.error("User is not a member of the required organization");
4084
return false;
4185
}
86+
87+
return true;
4288
} catch (error) {
89+
if (error instanceof Error) {
90+
if (error.name === "AbortError") {
91+
console.error("GitHub API request timed out");
92+
} else {
93+
console.error("Error checking GitHub organization membership:", error);
94+
}
95+
}
4396
return false;
4497
}
4598
}
4699

47100
return true;
48101
},
49102
async session({ session, token }) {
50-
if (session.user) {
51-
session.user.id = token.id as string;
103+
try {
104+
if (session.user) {
105+
session.user.id = token.id as string;
106+
}
107+
return session;
108+
} catch (error) {
109+
console.error("Error in session callback:", error);
110+
return session;
52111
}
53-
return session;
54112
},
55113
async jwt({ token, user, account }) {
56-
if (account && user) {
57-
token.id = user.id;
114+
try {
115+
if (account && user) {
116+
token.id = user.id;
117+
}
118+
return token;
119+
} catch (error) {
120+
console.error("Error in JWT callback:", error);
121+
return token;
58122
}
59-
return token;
60123
},
61124
},
125+
pages: {
126+
error: "/auth/error", // Add this if you want to handle auth errors with a custom page
127+
},
62128
secret: process.env.NEXTAUTH_SECRET,
129+
debug: process.env.NODE_ENV === "development",
63130
};
64131

132+
// Type augmentation for next-auth
65133
declare module "next-auth" {
66134
interface Session {
67135
user: {

src/pages/api/auth/error.tsx

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import React, { useEffect, useState } from "react";
2+
import { useRouter } from "next/router";
3+
import Link from "next/link";
4+
import type { NextPage } from "next";
5+
import Layout from "@layout/layout-01";
6+
7+
type ErrorType = {
8+
[key: string]: {
9+
title: string;
10+
message: string;
11+
action: string;
12+
};
13+
};
14+
15+
const errorMessages: ErrorType = {
16+
default: {
17+
title: "Authentication Error",
18+
message:
19+
"An unexpected error occurred during the authentication process.",
20+
action: "Please try signing in again.",
21+
},
22+
configuration: {
23+
title: "Server Configuration Error",
24+
message: "There is a problem with the server configuration.",
25+
action: "Please contact support for assistance.",
26+
},
27+
accessdenied: {
28+
title: "Access Denied",
29+
message:
30+
"You must be a member of the required GitHub organization to access this application.",
31+
action: "Please request access from your organization administrator.",
32+
},
33+
verification: {
34+
title: "Account Verification Required",
35+
message: "Your account requires verification before continuing.",
36+
action: "Please check your email for verification instructions.",
37+
},
38+
signin: {
39+
title: "Sign In Error",
40+
message: "The sign in attempt was unsuccessful.",
41+
action: "Please try again or use a different method to sign in.",
42+
},
43+
callback: {
44+
title: "Callback Error",
45+
message: "There was a problem with the authentication callback.",
46+
action: "Please try signing in again. If the problem persists, clear your browser cookies.",
47+
},
48+
oauthsignin: {
49+
title: "GitHub Sign In Error",
50+
message: "Unable to initiate GitHub sign in process.",
51+
action: "Please try again or check if GitHub is accessible.",
52+
},
53+
oauthcallback: {
54+
title: "GitHub Callback Error",
55+
message: "There was a problem processing the GitHub authentication.",
56+
action: "Please try signing in again or ensure you've granted the required permissions.",
57+
},
58+
};
59+
60+
type PageWithLayout = NextPage & {
61+
Layout?: typeof Layout;
62+
};
63+
64+
const AuthError: PageWithLayout = () => {
65+
const router = useRouter();
66+
const [error, setError] = useState(errorMessages.default);
67+
const [countdown, setCountdown] = useState(10);
68+
69+
useEffect(() => {
70+
const errorType = router.query.error as string;
71+
if (errorType && errorMessages[errorType]) {
72+
setError(errorMessages[errorType]);
73+
}
74+
}, [router.query]);
75+
76+
useEffect(() => {
77+
const timer = setInterval(() => {
78+
setCountdown((prev) => {
79+
if (prev <= 1) {
80+
clearInterval(timer);
81+
router.push("/").catch(console.error);
82+
return 0;
83+
}
84+
return prev - 1;
85+
});
86+
}, 1000);
87+
88+
return () => clearInterval(timer);
89+
}, [router]);
90+
91+
return (
92+
<div className="tw-min-h-screen tw-bg-gray-50 tw-flex tw-flex-col tw-justify-center tw-py-12 tw-sm:px-6 tw-lg:px-8">
93+
<div className="tw-sm:mx-auto tw-sm:w-full tw-sm:max-w-md">
94+
<div className="tw-bg-white tw-py-8 tw-px-4 tw-shadow tw-sm:rounded-lg tw-sm:px-10">
95+
<div className="tw-text-center">
96+
<h2 className="tw-text-2xl tw-font-bold tw-text-gray-900 tw-mb-4">
97+
{error.title}
98+
</h2>
99+
<div className="tw-rounded-md tw-bg-red-50 tw-p-4 tw-mb-6">
100+
<div className="tw-flex">
101+
<div className="tw-flex-shrink-0">
102+
<svg
103+
className="tw-h-5 tw-w-5 tw-text-red-400"
104+
xmlns="http://www.w3.org/2000/svg"
105+
viewBox="0 0 20 20"
106+
fill="currentColor"
107+
aria-hidden="true"
108+
>
109+
<path
110+
fillRule="evenodd"
111+
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
112+
clipRule="evenodd"
113+
/>
114+
</svg>
115+
</div>
116+
<div className="tw-ml-3">
117+
<p className="tw-text-sm tw-text-red-700">
118+
{error.message}
119+
</p>
120+
<p className="tw-mt-2 tw-text-sm tw-text-red-700">
121+
{error.action}
122+
</p>
123+
</div>
124+
</div>
125+
</div>
126+
127+
<div className="tw-space-y-4">
128+
<Link
129+
href="/"
130+
className="tw-inline-flex tw-items-center tw-px-4 tw-py-2 tw-border tw-border-transparent tw-text-sm tw-font-medium tw-rounded-md tw-shadow-sm tw-text-white tw-bg-primary tw-hover:tw-bg-opacity-90 tw-focus:tw-outline-none tw-focus:tw-ring-2 tw-focus:tw-ring-offset-2 tw-focus:tw-ring-primary"
131+
>
132+
Return to Home Page
133+
</Link>
134+
135+
<p className="tw-text-sm tw-text-gray-500">
136+
Redirecting in {countdown} seconds...
137+
</p>
138+
139+
<div className="tw-mt-4 tw-text-sm tw-text-gray-500">
140+
Need help?{" "}
141+
<a
142+
href="mailto:[email protected]"
143+
className="tw-font-medium tw-text-primary tw-hover:tw-text-opacity-90"
144+
>
145+
Contact Support
146+
</a>
147+
</div>
148+
</div>
149+
</div>
150+
</div>
151+
</div>
152+
</div>
153+
);
154+
};
155+
156+
AuthError.Layout = Layout;
157+
158+
export default AuthError;

0 commit comments

Comments
 (0)