Skip to content

Commit

Permalink
Add logout redirect
Browse files Browse the repository at this point in the history
  • Loading branch information
jacogasp committed Nov 20, 2024
1 parent b96b6f0 commit d29826a
Show file tree
Hide file tree
Showing 12 changed files with 93 additions and 136 deletions.
39 changes: 11 additions & 28 deletions src/app/(auth)/signin/page.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,19 @@
"use client";
import { signIn, useSession } from "next-auth/react";
import { login } from "@/services/auth";
import { useSession } from "next-auth/react";
import { useRouter } from "next/navigation";
import { useEffect, useRef } from "react";
import { useEffect } from "react";

export default function SignIn() {
const { data, status } = useSession();
const { status } = useSession();
const router = useRouter();
const signedIn = useRef(false);
const signingIn = useRef(false);

useEffect(() => {
const login = async () => {
switch (status) {
case "unauthenticated":
if (!signingIn.current) {
console.debug("signing in, redirecting to IAM login service...");
await signIn("indigo-iam", { callbackUrl: "/" });
signingIn.current = true;
}
break;
case "loading":
break;
case "authenticated":
if (!signedIn.current) {
signedIn.current = true;
console.debug("authenticated, redirecting to home");
router.push("/");
}
break;
}
};
login();
}, [router, status, data]);
if (status === "unauthenticated") {
login();
} else if (status === "authenticated") {
router.push("/");
}
console.log(status);
}, [status]);
return <div>Redirecting to login page...</div>;
}
5 changes: 2 additions & 3 deletions src/app/(auth)/signout/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export default function Logout() {
const signingOutRef = useRef(false);

useEffect(() => {
console.log("si?", status);
switch (status) {
case "authenticated":
if (!signingOutRef.current) {
Expand All @@ -26,7 +27,5 @@ export default function Logout() {
default:
}
});
return (
<h1>Logout Page</h1>
)
return <div>Redirecting to login page...</div>;
}
42 changes: 22 additions & 20 deletions src/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ declare module "next-auth/jwt" {
declare module "next-auth" {
interface Session {
access_token?: string & DefaultSession["user"];
expires_at: number;
expired: boolean;
is_admin: boolean;
token_expires?: Date & string;
}
}

Expand Down Expand Up @@ -49,35 +50,36 @@ const IamProvider: OIDCConfig<Profile> = {

export const authConfig: NextAuthConfig = {
providers: [IamProvider],
session: { strategy: "jwt" },
pages: { signIn: "/signin", signOut: "/signout" },
callbacks: {
authorized({ auth }) {
let authorized = false;
if (auth?.access_token) {
authorized = auth.expires_at < Date.now();
authorized({ request, auth }) {
if (request.nextUrl.pathname.startsWith("/api/auth")) {
return true;
}
if (auth?.user && auth?.access_token) {
return true;
}
return authorized;
return false;
},
async jwt({ token, account }) {
if (account) {
if (account?.access_token) {
// first time login, save access token and expiration
const { access_token } = account;
if (!access_token) {
throw Error("Access Token not found");
}
token.access_token = access_token;
token.expires_at = (account.expires_at ?? 0) * 1000;
const expires_at = (account.expires_at ?? 0) * 1000;
const me = await fetchMe(access_token);
token.is_admin =
me["urn:indigo-dc:scim:schemas:IndigoUser"]?.authorities?.includes(
"ROLE_ADMIN"
) ?? false;
const indigoUser = me["urn:indigo-dc:scim:schemas:IndigoUser"];
const is_admin =
indigoUser?.authorities?.includes("ROLE_ADMIN") ?? false;
return { ...token, access_token, is_admin, expires_at };
}
return token;
},
async session({ session, token }) {
session.access_token = token.access_token;
session.expires_at = token.expires_at;
session.is_admin = token.is_admin;
return session;
const { access_token, expires_at, is_admin } = token;
const expired = expires_at < Date.now();
const token_expires = new Date(expires_at);
return { ...session, access_token, is_admin, expired, token_expires };
},
},
};
Expand Down
15 changes: 15 additions & 0 deletions src/components/Buttons/Logout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ArrowRightEndOnRectangleIcon } from "@heroicons/react/24/solid";
import { logout } from "@/services/auth";

export const Logout = async () => {
return (
<form
action={logout}
className="m-auto flex rounded-full p-2 hover:bg-primary-hover"
>
<button type="submit" className="size-6 text-secondary">
<ArrowRightEndOnRectangleIcon />
</button>
</form>
);
};
1 change: 1 addition & 0 deletions src/components/Buttons/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { default as Button } from "./Button";
export { Logout } from "./Logout";
18 changes: 0 additions & 18 deletions src/components/Drawer/Buttons.tsx

This file was deleted.

22 changes: 0 additions & 22 deletions src/components/Drawer/DrawerButton.tsx

This file was deleted.

17 changes: 0 additions & 17 deletions src/components/Notifications/BellButton.tsx

This file was deleted.

26 changes: 17 additions & 9 deletions src/components/Notifications/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"use server";
import { fetchGroupsRequests } from "@/services/group-requests";
import BellButton from "./BellButton";
import { fetchRegistrationRequests } from "@/services/registration";
import Link from "next/link";
import { BellIcon } from "@heroicons/react/24/solid";
import { auth } from "@/auth";

type BadgeProps = {
count: number;
Expand All @@ -13,23 +15,29 @@ function Badge(props: Readonly<BadgeProps>) {
return null;
}
return (
<div className="absolute end-1 top-1 z-10 inline-flex h-5 w-5 items-center justify-center rounded-full bg-danger p-2 text-xs text-secondary">
<div className="absolute end-0 top-0 z-10 inline-flex size-5 items-center justify-center rounded-full bg-danger p-2 text-xs text-secondary">
{count}
</div>
);
}

export default async function Notifications() {
const groupRequests = await fetchGroupsRequests();
const registrationRequests = await fetchRegistrationRequests();
const totalRequests =
groupRequests.totalResults + registrationRequests.length;
const session = await auth();
let totalRequests = 0;
if (session?.is_admin) {
const groupRequests = await fetchGroupsRequests();
const registrationRequests = await fetchRegistrationRequests();
totalRequests = groupRequests.totalResults + registrationRequests.length;
}
const title = totalRequests
? `There are ${totalRequests} pending request(s)`
: "Notifications";
return (
<BellButton title={title}>
<Badge count={totalRequests} />
</BellButton>
<div className="relative m-auto rounded-full p-2 hover:bg-primary-hover">
<Link title={title} href="/requests" className="my-auto">
<BellIcon className="size-6 text-secondary" />
<Badge count={totalRequests} />
</Link>
</div>
);
}
10 changes: 7 additions & 3 deletions src/components/Sidebar/LogoHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { UserCircleIcon } from "@heroicons/react/24/solid";
import { DrawerButtons } from "../Drawer/Buttons";
import { fetchMe } from "@/services/me";
import Notifications from "../Notifications";
import { Logout } from "../Buttons";

const UserLogo = (props: { username: string }) => {
const { username } = props;
Expand All @@ -16,9 +17,12 @@ export default async function LogoHeader() {
const me = await fetchMe();
let username = me.name?.formatted ? me.name.formatted : "Unknown User";
return (
<div id="logo-header" className="mt-8 w-full p-2">
<div id="logo-header" className="flex flex-col gap-2">
<UserLogo username={username} />
<DrawerButtons />
<div className="flex justify-center">
<Notifications />
<Logout />
</div>
<hr className="bg-secondary" />
</div>
);
Expand Down
20 changes: 4 additions & 16 deletions src/middleware.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,16 @@ import { auth } from "@/auth";
import { NextResponse } from "next/server";

export const config = {
matcher: "/((?!api/auth|_next/static|_next/image|favicon.ico|fonts).*)",
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};

export default auth(req => {
const session = req.auth;

const sessionNotFound = !session && req.nextUrl.pathname !== "/signin";
if (sessionNotFound) {
const { pathname } = req.nextUrl;
if (!req.auth?.access_token && pathname !== "/signin") {
const newUrl = new URL("/signin", req.nextUrl.origin);
return Response.redirect(newUrl);
}

const expiration = new Date(session?.expires_at ?? 0);
const now = new Date();
const sessionExpired = expiration < now;

if (
sessionExpired &&
req.nextUrl.pathname !== "/signout" &&
req.nextUrl.pathname !== "/signin"
) {
if (req.auth?.expired && pathname !== "/signout") {
// This is an hack to make redirects on form submit post work.
// Based on https://github.com/vercel/next.js/blob/0cf0d43a48e04820d081de59176cbd75dd4bf193/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts#L82
// Since it is not reported in the official documentation, consider to
Expand All @@ -36,6 +25,5 @@ export default auth(req => {
return Response.redirect(newUrl);
}
}

return NextResponse.next();
});
14 changes: 14 additions & 0 deletions src/services/auth/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"use server";
import { signIn, signOut } from "@/auth";
import getConfig from "@/utils/config";
import { redirect } from "next/navigation";
const { BASE_URL } = getConfig();

export async function login() {
await signIn("indigo-iam", { redirectTo: "/" });
}

export async function logout() {
await signOut({ redirect: false });
redirect(`${BASE_URL}/logout`);
}

0 comments on commit d29826a

Please sign in to comment.