Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify login flow #193

Merged
merged 7 commits into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions vre-panel/environment.d.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
declare namespace NodeJS {
interface ProcessEnv {
NEXT_PUBLIC_SECRET: string,
KEYCLOAK_CLIENT_ID: string,
KEYCLOAK_CLIENT_SECRET: string,
KEYCLOAK_ISSUER: string,
AUTH0_ID: string,
AUTH0_SECRET: string,
AUTH0_ISSUER: string,
Expand Down
1 change: 0 additions & 1 deletion vre-panel/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { AppProps } from 'next/app';
import '../styles/globals.css';
import { SessionProvider } from "next-auth/react"
import { useState } from 'react';
// import RefreshTokenHandler from './auth/refreshTokenHandler';
import getConfig from 'next/config'

import {PaasConfigProvider} from '../context/PaasConfig';
Expand Down
52 changes: 23 additions & 29 deletions vre-panel/pages/api/auth/[...nextauth].ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ const { publicRuntimeConfig } = getConfig()
const refreshAccessToken = async (token: JWT) => {
try {
// Get a new set of tokens with a refreshToken
console.log("KEYCLOAK_ISSUER", process.env.KEYCLOAK_ISSUER)
// console.log("AUTH0_ISSUER", process.env.AUTH0_ISSUER)
const tokenResponse = await axios.post(
process.env.KEYCLOAK_ISSUER + '/protocol/openid-connect/token',
process.env.AUTH0_ISSUER + '/protocol/openid-connect/token',
{
grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',
subject_token: token.accessToken,
client_id: process.env.KEYCLOAK_CLIENT_ID,
grant_type: 'refresh_token',
refresh_token: token.refreshToken,
client_id: process.env.AUTH0_ID,
client_secret: process.env.AUTH0_SECRET,
requested_token_type: 'urn:ietf:params:oauth:token-type:refresh_token'
},
{
Expand All @@ -31,11 +32,13 @@ const refreshAccessToken = async (token: JWT) => {

return {
...token,
accessToken: tokenResponse.data.accessToken,
accessTokenExpiry: tokenResponse.data.accessTokenExpiry,
refreshToken: tokenResponse.data.refreshToken
accessToken: tokenResponse.data.access_token,
accessTokenExpiry: Date.now() / 1e3 + tokenResponse.data.expires_in,
refreshToken: tokenResponse.data.refresh_token,
}
} catch (error) {
console.log('Caught exception in refreshAccessToken')
console.log(error)
return {
...token,
error: "RefreshAccessTokenError",
Expand All @@ -44,8 +47,6 @@ const refreshAccessToken = async (token: JWT) => {
};

export default (req : NextApiRequest, res: NextApiResponse) => {
console.log("AUTH0_ID", process.env.AUTH0_ID);
console.log("AUTH0_ISSUER", process.env.AUTH0_ISSUER);
return NextAuth(req, res, {
providers: [
KeycloakProvider({
Expand All @@ -54,36 +55,28 @@ export default (req : NextApiRequest, res: NextApiResponse) => {
issuer: process.env.AUTH0_ISSUER,
})
],
// The secret should be set to a reasonably long random string.
// It is used to sign cookies and to sign and encrypt JSON Web Tokens, unless
// a separate secret is defined explicitly for encrypting the JWT.
secret: process.env.SECRET,
callbacks: {
async jwt({ token, user, account }) {
if (account && user) {

token.accessToken = account.access_token;
token.refreshToken = account.refresh_token;
token.accessTokenExpiry = account.expires_at;
token.user = user;
}

// console.log(token);

const unixTimeZero = Date.parse('01 Jan 1970 00:00:00 GMT');
const expiryDate = new Date();
expiryDate.setTime(unixTimeZero)
expiryDate.setSeconds(expiryDate.getSeconds() + token.accessTokenExpiry);
const refreshElapse = Math.round(expiryDate.getTime() - Date.now());

if (refreshElapse > 0) return token;

return refreshAccessToken(token);

const expDate = new Date(token.accessTokenExpiry * 1e3)
const nowDate = new Date()
const tokenExpired = (expDate < nowDate)

if (tokenExpired) {
token = await refreshAccessToken(token);
}

return token;

},
async session({ session, token }) {
if (token) {
session.user = token.user;
session.error = token.error;
session.accessToken = token.accessToken;
session.accessTokenExpiry = token.accessTokenExpiry;
Expand All @@ -93,6 +86,7 @@ export default (req : NextApiRequest, res: NextApiResponse) => {
},
pages: {
signIn: `${publicRuntimeConfig.basePath}/auth/signin`
}
},
})

}
18 changes: 0 additions & 18 deletions vre-panel/pages/auth/refreshTokenHandler.ts

This file was deleted.

11 changes: 6 additions & 5 deletions vre-panel/pages/auth/signin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default function SignIn({ providers }: { providers: any }) {
{Object.values(providers).map((provider: any) => (
<div className="self-center" key={provider.name}>
<button className="bg-blue-400/50 hover:bg-blue-400 text-gray-800 font-semibold py-2 px-4 border border-gray-400 rounded shadow mt-10" onClick={() => signIn(provider.id, { callbackUrl: process.env.CALL_BACK_URL })}>
Sign in with {provider.name}
Sign in
</button>
</div>
))}
Expand All @@ -34,16 +34,17 @@ export default function SignIn({ providers }: { providers: any }) {
)
}

export async function getServerSideProps(context: { req: any; }) {
export async function getServerSideProps(context: { req: any, query: any}) {

const { req } = context;
const { req, query } = context;
console.log("getProviders")
const redirectUrl = query.callbackUrl ? query.callbackUrl : '/'
const providers = await getProviders()
const session = await getSession({ req })
if (session) {
console.log("Session exists, redirecting to", '/')
console.log("Session exists, redirecting to", redirectUrl)
return {
redirect: { destination: '/' },
redirect: { destination: redirectUrl },
};
}
console.log("providers: ", providers)
Expand Down
23 changes: 9 additions & 14 deletions vre-panel/pages/auth/useAuth.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { signOut, useSession } from "next-auth/react";
import { signIn, signOut, useSession } from "next-auth/react";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";

Expand All @@ -11,29 +11,24 @@ export default function useAuth(shouldRedirect: boolean) {
useEffect(() => {

if (session?.error === "RefreshAccessTokenError") {

signOut({ redirect: shouldRedirect });
}

if (session === null) {

if (router.route !== `/auth/signin`) {
router.replace(`/auth/signin`);
}

setIsAuthenticated(false);
}

else if (session !== undefined) {

if (router.route === '/auth/signin') {
router.replace('/');
if (router.isReady && (router.route !== `/auth/signin`)) {
const callbackUrl = `${router.basePath}${router.asPath}`
Promise.all([
signIn('keycloak', {callbackUrl: callbackUrl}),
])
}
}

else if (session !== undefined) {
setIsAuthenticated(true);
}

}, [session]);
}, [session, router.isReady]);

return isAuthenticated;
}
4 changes: 2 additions & 2 deletions vre-panel/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const VLabs = ({}) => {
paasConfig.title
)}
</h1>
<p className="text-l text-onSurface">
<div className="text-l text-onSurface">
{paasConfigLoading ? (
<span className="animate-pulse">
<span
Expand All @@ -65,7 +65,7 @@ const VLabs = ({}) => {
<ReactMarkdown>{paasConfig.description}</ReactMarkdown>
</div>
)}
</p>
</div>
{paasConfigLoading || (
paasConfig.documentation_url && (
<p className="mt-4">
Expand Down
11 changes: 7 additions & 4 deletions vre-panel/templates/Nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ const Nav = () => {

const {paasConfig, paasConfigLoading} = useContext(PaasConfigContext)

const signOutOptions = {callbackUrl: router.basePath, shouldRedirect: true}
const signInProvider = 'keycloak'

return (
<header className="top-0 z-30 w-full md:w-72 md:min-h-screen px-2 py-4 bg-surface sm:px-4 shadow-lg">
<nav className="bg-surface border-gray-200 h-10 px-2 sm:px-4 rounded">
Expand Down Expand Up @@ -63,10 +66,10 @@ const Nav = () => {
{status == "authenticated" ? (
<>
<p>Logged in as {session?.user?.name}</p>
<a onClick={() => signOut()} href="#" className="hover:underline">[Logout]</a>
<a onClick={() => signOut(signOutOptions)} href="#" className="hover:underline">[Logout]</a>
</>
) : (
<a onClick={() => signIn()} href="#">Login</a>
<a onClick={() => signIn(signInProvider)} href="#">Login</a>
)}
</li>
</ul>
Expand Down Expand Up @@ -103,7 +106,7 @@ const Nav = () => {
<div className="px-1 py-1 ">
{menuPages.map((page) => {
return (
<Menu.Item>
<Menu.Item key={page.href}>
{({active}) => (
<Link
href={page.href}
Expand Down Expand Up @@ -135,7 +138,7 @@ const Nav = () => {
status == "authenticated" ? "px-6" : "px-2",
'group flex w-full items-center rounded py-2')}
onClick={() => {
status == "authenticated" ? signOut() : signIn()
status == "authenticated" ? signOut(signOutOptions) : signIn(signInProvider)
}}
>
{status == "authenticated" ? "Logout" : "Login"}
Expand Down