mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2025-12-21 12:49:06 +00:00
normalize auth provider
This commit is contained in:
@@ -9,35 +9,24 @@ import { useRouter } from "next/navigation";
|
||||
import useCreateTranscript from "../createTranscript";
|
||||
import SelectSearch from "react-select-search";
|
||||
import { supportedLanguages } from "../../../supportedLanguages";
|
||||
import useSessionStatus from "../../../lib/useSessionStatus";
|
||||
import { featureEnabled } from "../../../domainContext";
|
||||
import { signIn } from "next-auth/react";
|
||||
import {
|
||||
Flex,
|
||||
Box,
|
||||
Spinner,
|
||||
Heading,
|
||||
Button,
|
||||
Card,
|
||||
Center,
|
||||
Link,
|
||||
CardBody,
|
||||
Stack,
|
||||
Text,
|
||||
Icon,
|
||||
Grid,
|
||||
IconButton,
|
||||
Spacer,
|
||||
Menu,
|
||||
Tooltip,
|
||||
Input,
|
||||
} from "@chakra-ui/react";
|
||||
import { useAuth } from "../../../lib/AuthProvider";
|
||||
const TranscriptCreate = () => {
|
||||
const isClient = typeof window !== "undefined";
|
||||
const router = useRouter();
|
||||
const status = useSessionStatus();
|
||||
const isAuthenticated = status === "authenticated";
|
||||
const isLoading = status === "loading";
|
||||
const auth = useAuth();
|
||||
const isAuthenticated = auth.status === "authenticated";
|
||||
const isLoading = auth.status === "loading";
|
||||
const requireLogin = featureEnabled("requireLogin");
|
||||
|
||||
const [name, setName] = useState<string>("");
|
||||
@@ -145,7 +134,7 @@ const TranscriptCreate = () => {
|
||||
{isLoading ? (
|
||||
<Spinner />
|
||||
) : requireLogin && !isAuthenticated ? (
|
||||
<Button onClick={() => signIn("authentik")}>Log in</Button>
|
||||
<Button onClick={() => auth.signIn("authentik")}>Log in</Button>
|
||||
) : (
|
||||
<Flex
|
||||
rounded="xl"
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { DomainContext } from "../../domainContext";
|
||||
import { useTranscriptGet } from "../../lib/apiHooks";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { assertExtendedToken } from "../../lib/types";
|
||||
import { useAuth } from "../../lib/AuthProvider";
|
||||
|
||||
export type Mp3Response = {
|
||||
media: HTMLMediaElement | null;
|
||||
@@ -21,11 +20,9 @@ const useMp3 = (transcriptId: string, waiting?: boolean): Mp3Response => {
|
||||
);
|
||||
const [audioDeleted, setAudioDeleted] = useState<boolean | null>(null);
|
||||
const { api_url } = useContext(DomainContext);
|
||||
const { data: session } = useSession();
|
||||
const sessionExtended =
|
||||
session === null ? null : assertExtendedToken(session);
|
||||
const auth = useAuth();
|
||||
const accessTokenInfo =
|
||||
sessionExtended === null ? null : sessionExtended.accessToken;
|
||||
auth.status === "authenticated" ? auth.accessToken : null;
|
||||
|
||||
const {
|
||||
data: transcript,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
"use client";
|
||||
import { signOut, signIn } from "next-auth/react";
|
||||
import useSessionStatus from "../lib/useSessionStatus";
|
||||
|
||||
import { Spinner, Link } from "@chakra-ui/react";
|
||||
import { useAuth } from "../lib/AuthProvider";
|
||||
|
||||
export default function UserInfo() {
|
||||
const status = useSessionStatus();
|
||||
const auth = useAuth();
|
||||
const status = auth.status;
|
||||
const isLoading = status === "loading";
|
||||
const isAuthenticated = status === "authenticated";
|
||||
return isLoading ? (
|
||||
@@ -13,7 +14,7 @@ export default function UserInfo() {
|
||||
<Link
|
||||
href="/"
|
||||
className="font-light px-2"
|
||||
onClick={() => signIn("authentik")}
|
||||
onClick={() => auth.signIn("authentik")}
|
||||
>
|
||||
Log in
|
||||
</Link>
|
||||
@@ -21,7 +22,7 @@ export default function UserInfo() {
|
||||
<Link
|
||||
href="#"
|
||||
className="font-light px-2"
|
||||
onClick={() => signOut({ callbackUrl: "/" })}
|
||||
onClick={() => auth.signOut({ callbackUrl: "/" })}
|
||||
>
|
||||
Log out
|
||||
</Link>
|
||||
|
||||
@@ -21,13 +21,13 @@ import { toaster } from "../components/ui/toaster";
|
||||
import useRoomMeeting from "./useRoomMeeting";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { notFound } from "next/navigation";
|
||||
import useSessionStatus from "../lib/useSessionStatus";
|
||||
import { useRecordingConsent } from "../recordingConsentContext";
|
||||
import { useMeetingAudioConsent } from "../lib/apiHooks";
|
||||
import type { components } from "../reflector-api";
|
||||
|
||||
type Meeting = components["schemas"]["Meeting"];
|
||||
import { FaBars } from "react-icons/fa6";
|
||||
import { useAuth } from "../lib/AuthProvider";
|
||||
|
||||
export type RoomDetails = {
|
||||
params: {
|
||||
@@ -260,7 +260,7 @@ export default function Room(details: RoomDetails) {
|
||||
const roomName = details.params.roomName;
|
||||
const meeting = useRoomMeeting(roomName);
|
||||
const router = useRouter();
|
||||
const status = useSessionStatus();
|
||||
const status = useAuth().status;
|
||||
const isAuthenticated = status === "authenticated";
|
||||
const isLoading = status === "loading" || meeting.loading;
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import "./styles/globals.scss";
|
||||
import { Metadata, Viewport } from "next";
|
||||
import { Poppins } from "next/font/google";
|
||||
import SessionProvider from "./lib/SessionProvider";
|
||||
import { ErrorProvider } from "./(errors)/errorContext";
|
||||
import ErrorMessage from "./(errors)/errorMessage";
|
||||
import { DomainContextProvider } from "./domainContext";
|
||||
@@ -74,18 +73,16 @@ export default async function RootLayout({
|
||||
return (
|
||||
<html lang="en" className={poppins.className} suppressHydrationWarning>
|
||||
<body className={"h-[100svh] w-[100svw] overflow-x-hidden relative"}>
|
||||
<SessionProvider>
|
||||
<DomainContextProvider config={config}>
|
||||
<RecordingConsentProvider>
|
||||
<ErrorBoundary fallback={<p>"something went really wrong"</p>}>
|
||||
<ErrorProvider>
|
||||
<ErrorMessage />
|
||||
<Providers>{children}</Providers>
|
||||
</ErrorProvider>
|
||||
</ErrorBoundary>
|
||||
</RecordingConsentProvider>
|
||||
</DomainContextProvider>
|
||||
</SessionProvider>
|
||||
<DomainContextProvider config={config}>
|
||||
<RecordingConsentProvider>
|
||||
<ErrorBoundary fallback={<p>"something went really wrong"</p>}>
|
||||
<ErrorProvider>
|
||||
<ErrorMessage />
|
||||
<Providers>{children}</Providers>
|
||||
</ErrorProvider>
|
||||
</ErrorBoundary>
|
||||
</RecordingConsentProvider>
|
||||
</DomainContextProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import { createContext, useContext, useEffect } from "react";
|
||||
import { createContext, useContext } from "react";
|
||||
import { useSession as useNextAuthSession } from "next-auth/react";
|
||||
import { signOut, signIn } from "next-auth/react";
|
||||
import { configureApiAuth } from "./apiClient";
|
||||
import {
|
||||
assertExtendedToken,
|
||||
assertExtendedTokenAndUserId,
|
||||
CustomSession,
|
||||
} from "./types";
|
||||
import { assertExtendedTokenAndUserId, CustomSession } from "./types";
|
||||
import { Session } from "next-auth";
|
||||
import { SessionAutoRefresh } from "./SessionAutoRefresh";
|
||||
|
||||
type AuthContextType =
|
||||
type AuthContextType = (
|
||||
| { status: "loading" }
|
||||
| { status: "unauthenticated"; error?: string }
|
||||
| {
|
||||
@@ -17,25 +16,41 @@ type AuthContextType =
|
||||
accessToken: string;
|
||||
accessTokenExpires: number;
|
||||
user: CustomSession["user"];
|
||||
};
|
||||
}
|
||||
) & {
|
||||
update: () => Promise<Session | null>;
|
||||
signIn: typeof signIn;
|
||||
signOut: typeof signOut;
|
||||
};
|
||||
|
||||
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||
|
||||
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const { data: session, status } = useNextAuthSession();
|
||||
const { data: session, status, update } = useNextAuthSession();
|
||||
const customSession = session ? assertExtendedTokenAndUserId(session) : null;
|
||||
|
||||
const contextValue: AuthContextType =
|
||||
status === "loading"
|
||||
? { status: "loading" as const }
|
||||
const contextValue: AuthContextType = {
|
||||
...(status === "loading"
|
||||
? { status }
|
||||
: status === "authenticated" && customSession?.accessToken
|
||||
? {
|
||||
status: "authenticated" as const,
|
||||
status,
|
||||
accessToken: customSession.accessToken,
|
||||
accessTokenExpires: customSession.accessTokenExpires,
|
||||
user: customSession.user,
|
||||
}
|
||||
: { status: "unauthenticated" as const };
|
||||
: status === "authenticated" && !customSession?.accessToken
|
||||
? (() => {
|
||||
console.warn(
|
||||
"illegal state: authenticated but have no session/or access token. ignoring",
|
||||
);
|
||||
return { status: "unauthenticated" as const };
|
||||
})()
|
||||
: { status: "unauthenticated" as const }),
|
||||
update,
|
||||
signIn,
|
||||
signOut,
|
||||
};
|
||||
|
||||
// not useEffect, we need it ASAP
|
||||
configureApiAuth(
|
||||
@@ -43,7 +58,9 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
);
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={contextValue}>{children}</AuthContext.Provider>
|
||||
<AuthContext.Provider value={contextValue}>
|
||||
<SessionAutoRefresh>{children}</SessionAutoRefresh>
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* This is a custom hook that automatically refreshes the session when the access token is about to expire.
|
||||
* This is a custom provider that automatically refreshes the session when the access token is about to expire.
|
||||
* When communicating with the reflector API, we need to ensure that the access token is always valid.
|
||||
*
|
||||
* We could have implemented that as an interceptor on the API client, but not everything is using the
|
||||
@@ -7,31 +7,29 @@
|
||||
*/
|
||||
"use client";
|
||||
|
||||
import { useSession } from "next-auth/react";
|
||||
import { useEffect } from "react";
|
||||
import { assertExtendedToken, CustomSession } from "./types";
|
||||
import { useAuth } from "./AuthProvider";
|
||||
|
||||
export function SessionAutoRefresh({
|
||||
children,
|
||||
refreshInterval = 20 /* seconds */,
|
||||
}) {
|
||||
const { data: session, update } = useSession();
|
||||
const accessTokenExpires = session
|
||||
? assertExtendedToken(session).accessTokenExpires
|
||||
: null;
|
||||
const auth = useAuth();
|
||||
const accessTokenExpires =
|
||||
auth.status === "authenticated" ? auth.accessTokenExpires : null;
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
if (accessTokenExpires) {
|
||||
const timeLeft = accessTokenExpires - Date.now();
|
||||
if (timeLeft < refreshInterval * 1000) {
|
||||
update();
|
||||
auth.update();
|
||||
}
|
||||
}
|
||||
}, refreshInterval * 1000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [accessTokenExpires, refreshInterval, update]);
|
||||
}, [accessTokenExpires, refreshInterval, auth.update]);
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
"use client";
|
||||
import { SessionProvider as SessionProviderNextAuth } from "next-auth/react";
|
||||
import { SessionAutoRefresh } from "./SessionAutoRefresh";
|
||||
|
||||
export default function SessionProvider({ children }) {
|
||||
return (
|
||||
<SessionProviderNextAuth>
|
||||
<SessionAutoRefresh>{children}</SessionAutoRefresh>
|
||||
</SessionProviderNextAuth>
|
||||
);
|
||||
}
|
||||
@@ -1,11 +1,7 @@
|
||||
import { AuthOptions } from "next-auth";
|
||||
import AuthentikProvider from "next-auth/providers/authentik";
|
||||
import { JWT } from "next-auth/jwt";
|
||||
import {
|
||||
JWTWithAccessToken,
|
||||
CustomSession,
|
||||
assertExtendedToken,
|
||||
} from "./types";
|
||||
import type { JWT } from "next-auth/jwt";
|
||||
import { JWTWithAccessToken, CustomSession } from "./types";
|
||||
import {
|
||||
assertExists,
|
||||
assertExistsAndNonEmptyString,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Session } from "next-auth";
|
||||
import { JWT } from "next-auth/jwt";
|
||||
import type { Session } from "next-auth";
|
||||
import type { JWT } from "next-auth/jwt";
|
||||
import { parseMaybeNonEmptyString } from "./utils";
|
||||
|
||||
export interface JWTWithAccessToken extends JWT {
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useSession as useNextAuthSession } from "next-auth/react";
|
||||
import { CustomSession } from "./types";
|
||||
|
||||
export default function useSessionAccessToken() {
|
||||
const { data: session } = useNextAuthSession();
|
||||
const customSession = session as CustomSession;
|
||||
|
||||
return {
|
||||
accessToken: customSession?.accessToken ?? null,
|
||||
accessTokenExpires: customSession?.accessTokenExpires ?? null,
|
||||
error: customSession?.error,
|
||||
};
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useSession as useNextAuthSession } from "next-auth/react";
|
||||
|
||||
export default function useSessionStatus() {
|
||||
const { status } = useNextAuthSession();
|
||||
return status;
|
||||
}
|
||||
@@ -9,19 +9,22 @@ import { NuqsAdapter } from "nuqs/adapters/next/app";
|
||||
import { QueryClientProvider } from "@tanstack/react-query";
|
||||
import { queryClient } from "./lib/queryClient";
|
||||
import { AuthProvider } from "./lib/AuthProvider";
|
||||
import { SessionProvider as SessionProviderNextAuth } from "next-auth/react";
|
||||
|
||||
export function Providers({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<NuqsAdapter>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AuthProvider>
|
||||
<ChakraProvider value={system}>
|
||||
<WherebyProvider>
|
||||
{children}
|
||||
<Toaster />
|
||||
</WherebyProvider>
|
||||
</ChakraProvider>
|
||||
</AuthProvider>
|
||||
<SessionProviderNextAuth>
|
||||
<AuthProvider>
|
||||
<ChakraProvider value={system}>
|
||||
<WherebyProvider>
|
||||
{children}
|
||||
<Toaster />
|
||||
</WherebyProvider>
|
||||
</ChakraProvider>
|
||||
</AuthProvider>
|
||||
</SessionProviderNextAuth>
|
||||
</QueryClientProvider>
|
||||
</NuqsAdapter>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user