Skip to content

Commit

Permalink
Better session handling an dless flickering
Browse files Browse the repository at this point in the history
  • Loading branch information
soerface committed Nov 12, 2024
1 parent b5b900d commit aab0c1a
Show file tree
Hide file tree
Showing 12 changed files with 145 additions and 94 deletions.
28 changes: 17 additions & 11 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,36 @@
import './App.css'
// import { Navigate, useLocation, useNavigate } from "react-router-dom";
import Login from "./Login.tsx";
import {AuthProvider} from "./AuthContext.tsx";
import {Route, Routes} from "react-router-dom";
import {useAuth} from "./AuthContext.tsx";
import {Navigate, Route, Routes} from "react-router-dom";
import Home from "./Home.tsx";
import RequireAuth from "./RequireAuth.tsx";
import LoginCallback from "./LoginCallback.tsx";
import NavBar from "./NavBar.tsx";

import NotFound from "./NotFound.tsx";

function App() {

return <AuthProvider>
const {user} = useAuth();

const AnonRoutes = <>
<Route path="/" element={<Navigate to="/login"/>}/>
</>

const LoggedInRoutes = <>
<Route path="/" element={<Home/>}/>
</>

return <>
<NavBar/>
<div id="content">
<RequireAuth>
<Routes>
<Route path="/" element={<Home/>}/>
</Routes>
</RequireAuth>
<Routes>
{user === undefined ? null : user ? LoggedInRoutes : AnonRoutes}
<Route path="/login" element={<Login/>}/>
<Route path="/login/callback" element={<LoginCallback/>}/>
<Route path="*" element={<NotFound/>}/>
</Routes>
</div>
</AuthProvider>
</>

// const auth = useAuth();
// // let navigate = useNavigate();
Expand Down
51 changes: 28 additions & 23 deletions src/AuthContext.tsx
Original file line number Diff line number Diff line change
@@ -1,50 +1,55 @@
import './App.css'
import {User} from 'oidc-client-ts'
import {User, UserManager, WebStorageStateStore} from 'oidc-client-ts'
// import { Navigate, useLocation, useNavigate } from "react-router-dom";
import {createContext, useContext, useState} from "react";
import AuthService from "./AuthService.ts";
import {createContext, useContext, useEffect, useMemo, useState} from "react";

interface AuthContextType {
user: null | User;
login: () => void;
logout: () => void;
loginCallback: () => Promise<null | User>;
user: undefined | null | User;
login: () => Promise<void>;
logout: () => Promise<void>;
loginCallback: () => Promise<void>;
}

const AuthContext = createContext<AuthContextType>({
user: null,
login: () => {
login: async () => {
console.error("login not implemented. Did you forget to wrap your app in an AuthProvider?");
},
logout: () => {
logout: async () => {
console.error("logout not implemented. Did you forget to wrap your app in an AuthProvider?");
},
loginCallback: async () => {
console.error("loginCallback not implemented. Did you forget to wrap your app in an AuthProvider?");
return null;
}
});

const useAuth = () => useContext(AuthContext);

function AuthProvider({children}: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(
JSON.parse(
sessionStorage.getItem("session") || "null"
) || undefined
);

const authService = new AuthService();
const [user, setUser] = useState<User | null | undefined>(undefined);
const userManager = useMemo(() => new UserManager({
authority: "https://login.flipdot.org/realms/flipdot",
client_id: "flipdot-app-dashboard",
redirect_uri: window.location.origin + "/login/callback",
response_type: "code",
userStore: new WebStorageStateStore({store: window.localStorage}),
}), []);

useEffect(() => {
userManager.getUser().then(setUser);
}, [userManager]);

const loginCallback = async () => {
const authedUser = await authService.loginCallback();
setUser(authedUser);
return authedUser;
setUser(await userManager.signinRedirectCallback());
};

const login = () => authService.login();
const login = async () => {
await userManager.signinRedirect();
setUser(await userManager.getUser());
}
const logout = async () => {
await authService.logout();
// only logout in this application, not on the oidc server
await userManager.revokeTokens();
await userManager.removeUser();
setUser(null);
}

Expand Down
33 changes: 0 additions & 33 deletions src/AuthService.ts

This file was deleted.

4 changes: 0 additions & 4 deletions src/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,6 @@ function Home() {
Hier würdest du jetzt eine Liste alle flipdot Apps sehen.
Ich bin aber noch nicht fertig, sorry :)
</p>
<p>
Übrigens bleibt man auch noch nicht eingeloggt wenn man die Seite neu lädt.
Upsi.
</p>
<a href="https://github.com/flipdot/app-dashboard/">https://github.com/flipdot/app-dashboard</a>
</div>
</>
Expand Down
11 changes: 7 additions & 4 deletions src/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,23 @@ import flipdotLogo from './assets/flipdot.svg'
import './App.css'
import {useAuth} from "./AuthContext.tsx";
import {useNavigate} from "react-router-dom";
import {useEffect} from "react";


function Login() {
const auth = useAuth();
const navigate = useNavigate();

if (auth.user) {
navigate("/", {replace: true});
}
useEffect(() => {
if (auth.user) {
navigate("/", {replace: true});
}
}, [auth, navigate]);

return (
<>
<div>
<img src={flipdotLogo} width="60%" style={
<img src={flipdotLogo} height="100em" style={
{
margin: "auto",
display: "block",
Expand Down
20 changes: 11 additions & 9 deletions src/LoginCallback.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import {useAuth} from "./AuthContext.tsx";
import {useNavigate} from "react-router-dom";
import {useEffect} from "react";
import Spinner from "./Spinner.tsx";

function LoginCallback() {
const auth = useAuth();
const navigate = useNavigate();
auth.loginCallback().then(() => {

useEffect(() => {
auth.loginCallback().then(() => {
navigate("/", {replace: true});
}
).catch(
() => {
}).catch(() => {
console.error("Login callback failed");
navigate("/login", {replace: true});
}
);
return <div>
<p>Logging in…</p>
</div>;
});
});

return <Spinner size="lg"/>;
}

export default LoginCallback;
13 changes: 6 additions & 7 deletions src/NavBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,20 @@ import {useAuth} from "./AuthContext.tsx";
import fdLogo from './assets/fd.svg';
import "./NavBar.css";
import {Link} from "react-router-dom";
import Spinner from "./Spinner.tsx";

function NavBar() {
const auth = useAuth();
const loginButton = <li>
<button className="login-button" onClick={auth.login}>Login</button>
</li>;
const userMenu = <li>
const loginButton = <button className="login-button" onClick={auth.login}>Login</button>
const userMenu = <>
<span className="title">{auth.user?.profile.preferred_username}</span>
<ul>
<li>
<button onClick={auth.logout}>Logout</button>
</li>
</ul>
</li>
const rightItem = auth.user ? userMenu : loginButton;
</>
const rightItem = auth.user === undefined ? <Spinner/> : auth.user ? userMenu : loginButton;
return (
<nav className="navbar">
<Link to="/" className="logo"><img src={fdLogo} alt="fd" onError={
Expand All @@ -29,7 +28,7 @@ function NavBar() {
{/*<Link to="/" className="logo"><img src="broken" alt="fd"/></Link>*/}
<ul className="nav-links">
{/*<li><Link to="/">Home</Link></li>*/}
{rightItem}
<li>{rightItem}</li>
</ul>
</nav>
)
Expand Down
27 changes: 27 additions & 0 deletions src/NotFound.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {useAuth} from "./AuthContext.tsx";
import {useLocation, useNavigate} from "react-router-dom";
import {useEffect} from "react";

function NotFound() {
const auth = useAuth();
const navigate = useNavigate();
const location = useLocation();

useEffect(() => {
if (auth.user === null) {
navigate("/login", {replace: true, state: {from: location}});
}
}, [auth, navigate, location]);

if (auth.user === undefined) {
// avoids flickering
return <div></div>
}

return <div>
<h1>404</h1>
<p>Page not found</p>
</div>
}

export default NotFound
5 changes: 3 additions & 2 deletions src/RequireAuth.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {useAuth} from "./AuthContext.tsx";
import {Navigate} from "react-router-dom";
import {Navigate, useLocation} from "react-router-dom";

function RequireAuth({children}: { children: React.ReactNode }) {
const auth = useAuth();
const location = useLocation();
const notLoggedIn = auth.user === undefined || auth.user === null;
return notLoggedIn ? <Navigate to="/login" replace/> : children;
return notLoggedIn ? <Navigate to="/login" replace state={{from: location}}/> : children;
}

export default RequireAuth
33 changes: 33 additions & 0 deletions src/Spinner.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
.spinner {
border: 0.2em solid #aaa;
border-bottom-color: transparent;
border-radius: 50%;
display: inline-block;
box-sizing: border-box;
animation: rotation 1s cubic-bezier(.62, .27, .5, .75) infinite;
}

@keyframes rotation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

.spinner.spinner-sm {
width: 0.5em;
height: 0.5em;
}

.spinner.spinner-md {
width: 1em;
height: 1em;
}

.spinner.spinner-lg {
width: 4em;
height: 4em;
border-width: 0.7em;
}
9 changes: 9 additions & 0 deletions src/Spinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import "./Spinner.css";

function Spinner({size}: { size?: "sm" | "md" | "lg" }) {
size = size || "md";
return <div className={`spinner spinner-${size}`}>
</div>;
}

export default Spinner;
5 changes: 4 additions & 1 deletion src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import App from './App.tsx'

import * as Sentry from "@sentry/react";
import {BrowserRouter} from "react-router-dom";
import {AuthProvider} from "./AuthContext.tsx";

Sentry.init({
dsn: "https://[email protected]/6",
Expand All @@ -15,7 +16,9 @@ Sentry.init({
createRoot(document.getElementById('root')!).render(
<StrictMode>
<BrowserRouter>
<App/>
<AuthProvider>
<App/>
</AuthProvider>
</BrowserRouter>
</StrictMode>,
)

0 comments on commit aab0c1a

Please sign in to comment.