From a342f89c95da5f9cf0083d170147870cc588833e Mon Sep 17 00:00:00 2001 From: Anil Vishnoi Date: Fri, 18 Oct 2024 13:22:40 -0700 Subject: [PATCH] Integrate auto inviter to the login page. If the user is not part of the instructlab org, it will show the pop-up warning with a message to join the org. Popup will provide the button to send the invitation. Once user receives the invite, it will be able to login to the UI. Signed-off-by: Anil Vishnoi Co-authored-by: Brent Salisbury --- next.config.js | 3 + src/app/api/auth/[...nextauth]/route.ts | 2 +- src/app/api/invite/route.ts | 66 +++++++++++++++++++++ src/app/error/error.module.css | 41 ------------- src/app/error/page.tsx | 42 ------------- src/app/login/githublogin.tsx | 78 ++++++++++++++++++++++++- 6 files changed, 147 insertions(+), 85 deletions(-) create mode 100644 src/app/api/invite/route.ts delete mode 100644 src/app/error/error.module.css delete mode 100644 src/app/error/page.tsx diff --git a/next.config.js b/next.config.js index 50fd5b38..5c489d26 100644 --- a/next.config.js +++ b/next.config.js @@ -2,6 +2,9 @@ const nextConfig = { reactStrictMode: true, transpilePackages: ['@patternfly/react-core', '@patternfly/react-styles', '@patternfly/react-table', '@patternfly/react-component-groups'], + experimental: { + missingSuspenseWithCSRBailout: false, + }, }; module.exports = nextConfig; diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts index 62a4dead..161f6db1 100644 --- a/src/app/api/auth/[...nextauth]/route.ts +++ b/src/app/api/auth/[...nextauth]/route.ts @@ -113,7 +113,7 @@ const authOptions: NextAuthOptions = { } else if (response.status === 404) { console.log(`User ${githubProfile.login} is not a member of the ${ORG} organization`); logger.warn(`User ${githubProfile.login} is not a member of the ${ORG} organization`); - return `/error?error=AccessDenied`; // Redirect to custom error page + return `/login?error=NotOrgMember&user=${githubProfile.login}`; // Redirect to custom error page } else { console.log(`Unexpected error while authenticating user ${githubProfile.login} with ${ORG} github organization.`); logger.error(`Unexpected error while authenticating user ${githubProfile.login} with ${ORG} github organization.`); diff --git a/src/app/api/invite/route.ts b/src/app/api/invite/route.ts new file mode 100644 index 00000000..34e2ffe7 --- /dev/null +++ b/src/app/api/invite/route.ts @@ -0,0 +1,66 @@ +// src/api/invite/route.tsx +'use server'; + +import { NextRequest, NextResponse } from 'next/server'; + +const ORG_NAME = process.env.NEXT_PUBLIC_AUTHENTICATION_ORG; +const GITHUB_TOKEN = process.env.GITHUB_TOKEN; + +export async function POST(req: NextRequest) { + try { + const body = await req.json(); + const { githubUsername } = body; + + console.log('Received GitHub username:', githubUsername); + + if (!githubUsername) { + console.log('GitHub username is missing in the request'); + return NextResponse.json({ error: 'GitHub username is required' }, { status: 400 }); + } + + console.log('GITHUB_TOKEN is:', GITHUB_TOKEN ? 'Loaded' : 'Not loaded'); + console.log('github toke:', GITHUB_TOKEN); + + // Step 1: Fetch the GitHub user details by their username + const userResponse = await fetch(`https://api.github.com/users/${githubUsername}`, { + method: 'GET', + headers: { + Authorization: `token ${GITHUB_TOKEN}`, + Accept: 'application/vnd.github.v3+json' + } + }); + + if (!userResponse.ok) { + const errorResponse = await userResponse.text(); + console.log('Failed to fetch GitHub user ID:', errorResponse); + return NextResponse.json({ error: 'Failed to fetch GitHub user ID' }, { status: userResponse.status }); + } + + const userData = await userResponse.json(); + const inviteeId = userData.id; + + const inviteResponse = await fetch(`https://api.github.com/orgs/${ORG_NAME}/invitations`, { + method: 'POST', + headers: { + Authorization: `token ${GITHUB_TOKEN}`, + Accept: 'application/vnd.github.v3+json', + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + invitee_id: inviteeId + }) + }); + + const inviteResponseBody = await inviteResponse.text(); + console.log('GitHub API response status:', inviteResponse.status); + + if (inviteResponse.ok) { + return NextResponse.json({ message: `Invitation sent successfully to ${githubUsername}` }, { status: 200 }); + } else { + return NextResponse.json({ error: inviteResponseBody }, { status: inviteResponse.status }); + } + } catch (error) { + console.error('Failed to send invitation:', error); + return NextResponse.json({ error: 'Failed to send the invitation' }, { status: 500 }); + } +} diff --git a/src/app/error/error.module.css b/src/app/error/error.module.css deleted file mode 100644 index 056902f2..00000000 --- a/src/app/error/error.module.css +++ /dev/null @@ -1,41 +0,0 @@ -/* src/app/error/error.module.css */ -.errorContainer { - text-align: center; - padding: 50px; - font-family: Arial, sans-serif; -} - -.errorTitle { - font-size: 72px; - margin-bottom: 20px; -} - -.errorMessage { - font-size: 24px; - margin-bottom: 20px; -} - -.backLink { - font-size: 20px; - color: #007bff; - text-decoration: none; - display: block; - margin-bottom: 20px; -} - -.backLink:hover { - text-decoration: underline; -} - -.orgLink { - font-size: 18px; -} - -.inlineLink { - color: #007bff; - text-decoration: none; -} - -.inlineLink:hover { - text-decoration: underline; -} diff --git a/src/app/error/page.tsx b/src/app/error/page.tsx deleted file mode 100644 index cbb1ef3a..00000000 --- a/src/app/error/page.tsx +++ /dev/null @@ -1,42 +0,0 @@ -// src/app/error/page.tsx -'use client'; - -import React, { Suspense } from 'react'; -import { useSearchParams } from 'next/navigation'; -import styles from './error.module.css'; - -const ErrorPageContent = () => { - const searchParams = useSearchParams(); - const error = searchParams.get('error'); - - let errorMessage = 'Something went wrong.'; - if (error === 'AccessDenied') { - errorMessage = 'Whoops! You need to be a member of the InstructLab org to access this site. Try joining and then come back!'; - } - - return ( -
-

404

-

{errorMessage}

- - Return to the Login Page - -

- Want to join the InstructLab organization? Visit our - - {' '} - GitHub page - - . -

-
- ); -}; - -const ErrorPage = () => ( - Loading...}> - - -); - -export default ErrorPage; diff --git a/src/app/login/githublogin.tsx b/src/app/login/githublogin.tsx index c0086084..bcb2526f 100644 --- a/src/app/login/githublogin.tsx +++ b/src/app/login/githublogin.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { Button } from '@patternfly/react-core/dist/dynamic/components/Button'; import { Text } from '@patternfly/react-core/dist/dynamic/components/Text'; import { TextContent } from '@patternfly/react-core/dist/dynamic/components/Text'; @@ -7,12 +7,67 @@ import { GridItem } from '@patternfly/react-core/dist/dynamic/layouts/Grid'; import GithubIcon from '@patternfly/react-icons/dist/dynamic/icons/github-icon'; import './githublogin.css'; import { signIn } from 'next-auth/react'; +import { useRouter, useSearchParams } from 'next/navigation'; +import { Modal, ModalVariant } from '@patternfly/react-core/dist/esm/components/Modal'; const GithubLogin: React.FC = () => { + const searchParams = useSearchParams(); + const router = useRouter(); + const [showError, setShowError] = useState(false); + const [errorMsg, setErrorMsg] = useState('Something went wrong.'); + const [githubUsername, setGithubUsername] = useState(null); + + useEffect(() => { + const githubUsername = searchParams.get('user'); + setGithubUsername(githubUsername); + const error = searchParams.get('error'); + if (error === 'NotOrgMember') { + const errorMessage = + 'You are not a member of the InstructLab Public org. Please join the InstructLab Public Organization on Github and try again.'; + setErrorMsg(errorMessage); + setShowError(true); + } + }, []); + const handleGitHubLogin = () => { signIn('github', { callbackUrl: '/' }); // Redirect to home page after login }; + const handleOnClose = () => { + setShowError(false); + router.push('/'); + }; + + const sendInvite = async () => { + console.log('Sending invitation to:', githubUsername); // Log the GitHub username + try { + const response = await fetch('/api/invite', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ githubUsername }) + }); + + const responseBody = await response.text(); + console.log('API response body:', responseBody); + + if (response.ok) { + alert('You have been invited to the GitHub organization!'); + setShowError(false); + router.push('/'); + } else { + console.log('Failed to send invitation:', responseBody); + alert(`Failed to send invitation: ${responseBody}`); + router.push('/'); + } + } catch (error) { + console.error('Error while sending the invitation:', error); + alert('An error occurred while sending the invitation.'); + router.push('/'); + } + }; + return (
@@ -73,6 +128,27 @@ const GithubLogin: React.FC = () => {
+ {showError && ( +
+ handleOnClose()} + actions={[ + , + + ]} + > +

{errorMsg}

+
+
+ )} ); };