Skip to content

Commit

Permalink
Auth appended to the routing, no_auth + database_auth injected
Browse files Browse the repository at this point in the history
  • Loading branch information
matborowczyk committed May 31, 2024
1 parent bfcf87d commit 9e30197
Show file tree
Hide file tree
Showing 14 changed files with 269 additions and 107 deletions.
27 changes: 27 additions & 0 deletions src/Data/Auth/AuthProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from "react";
import { AuthConfig, LocalConfig } from "./PrimaryAuthController";
import DatabaseAuthProvider from "./Providers/DatabaseAuthProvider";
import KeycloakAuthContext from "./Providers/KeycloakAuthContext";
import NoAuthProvider from "./Providers/NoAuthProvider";

interface AuthProviderProps {
children: React.ReactNode;
config: undefined | AuthConfig | LocalConfig;
keycloakUrl: string | undefined;
}

const AuthProvider = ({ children, config, keycloakUrl }: AuthProviderProps) => {
if (config?.method === "database") {
return <DatabaseAuthProvider>{children}</DatabaseAuthProvider>;
}
if (config?.method === "oidc") {
return (
<KeycloakAuthContext config={{ ...config, url: keycloakUrl }}>
{children}
</KeycloakAuthContext>
);
}
return <NoAuthProvider>{children}</NoAuthProvider>;
};

export default AuthProvider;
16 changes: 16 additions & 0 deletions src/Data/Auth/Providers/AuthContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { createContext } from "react";
export interface AuthContextProps {
user: string | undefined;
login: () => void;
logout: () => void;
updateUser: (user: string, token: string) => void;
getToken: () => string | null;
}
export const defaultAuthContext: AuthContextProps = {
user: undefined,
login: () => {},
logout: () => {},
updateUser: (_user: string, _token: string) => {},
getToken: () => null,
};
export const AuthContext = createContext(defaultAuthContext);
40 changes: 40 additions & 0 deletions src/Data/Auth/Providers/DatabaseAuthProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import {
createCookie,
getCookie,
removeCookie,
} from "../../Common/CookieHelper";
import { AuthContext } from "./AuthContext";

interface AuthProviderProps {
children: React.ReactNode;
}

const DatabaseAuthProvider = ({ children }: AuthProviderProps) => {
const [user, setUser] = useState<string | undefined>(undefined);
const navigate = useNavigate();

const logout = () => {
removeCookie("inmanta_user");
localStorage.removeItem("inmanta_user");
navigate("/login");
};

const login = () => navigate("/login");

const getToken = () => getCookie("inmanta_user");

const updateUser = (username: string, token: string) => {
setUser(username);
createCookie("inmanta_user", token, 1);
};

return (
<AuthContext.Provider value={{ user, login, logout, updateUser, getToken }}>
{children}
</AuthContext.Provider>
);
};

export default DatabaseAuthProvider;
54 changes: 54 additions & 0 deletions src/Data/Auth/Providers/KeycloakAuthContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React, { useState, useEffect, useCallback, useMemo } from "react";
import Keycloak, { KeycloakConfig } from "keycloak-js";
import { AuthContext } from "./AuthContext";

interface AuthProviderProps {
children: React.ReactNode;
config: KeycloakConfig;
}

const KeycloakAuthProvider = ({ children, config }: AuthProviderProps) => {
const keycloakInstance = useMemo(() => new Keycloak(config), [config]);

const getName = useCallback((): string | undefined => {
return keycloakInstance &&
keycloakInstance.profile &&
keycloakInstance.profile.username
? keycloakInstance.profile.username
: undefined;
}, [keycloakInstance]);

const [user, setUser] = useState<string | undefined>(undefined);

const logout = () => {
keycloakInstance.logout();
};

const login = () => {
keycloakInstance.clearToken();
keycloakInstance.login();
};
const getToken = () => {
return keycloakInstance.token || null;
};

useEffect(() => {
setUser(getName());
}, [getName]);

return (
<AuthContext.Provider
value={{
user,
getToken,
login,
logout,
updateUser: (_user, _token) => ({}),
}}
>
{children}
</AuthContext.Provider>
);
};

export default KeycloakAuthProvider;
23 changes: 23 additions & 0 deletions src/Data/Auth/Providers/NoAuthProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from "react";
import { AuthContext, defaultAuthContext } from "./AuthContext";

interface AuthProviderProps {
children: React.ReactNode;
}

const NoAuthProvider = ({ children }: AuthProviderProps) => {
return (
<AuthContext.Provider
value={{
...defaultAuthContext,
getToken: () => {
return "No token";
},
}}
>
{children}
</AuthContext.Provider>
);
};

export default NoAuthProvider;
28 changes: 3 additions & 25 deletions src/Data/Managers/V2/helpers/useHandleErrors/useHandleErrors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,37 +6,15 @@ import { useContext } from "react";
import { DependencyContext } from "@/UI";

export const useHandleErrors = () => {
const { authController } = useContext(DependencyContext);

/**
* Throws an error indicating that access to a resource is unauthorized.
*/
const throwUnauthorizedError = () => {
throw new Error("Access to this resource is unauthorized");
};
const { useAuth } = useContext(DependencyContext);

/**
* Handles authorization-related errors in the response.
* If the user is authenticated locally, it dispatches an event to open the login page and throws an unauthorized error.
* If the user is authenticated with Keycloak, it clears the token if the response status is 401 (Unauthorized),
* and logs in again if the token is expired.
* @param response - The response object received from the API.
*/
const handleAuthorization = (response: Response) => {
if (authController.isEnabled() && authController.shouldAuthLocally()) {
if (response.status === 401) {
document.dispatchEvent(new CustomEvent("open-login"));
throwUnauthorizedError();
}
} else if (authController.isEnabled()) {
const keycloakInstance = authController.getInstance();
if (response.status === 401) {
keycloakInstance.clearToken();
}

if (keycloakInstance.isTokenExpired()) {
keycloakInstance.login();
}
if (response.status === 401) {
useAuth.login();
}
};

Expand Down
5 changes: 4 additions & 1 deletion src/Injector.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from "react";
import React, { useContext } from "react";
import { useLocation } from "react-router-dom";
import { isJsonParserId, JsonParserId, SchedulerImpl } from "@/Core";
import {
Expand All @@ -25,6 +25,7 @@ import {
EnvironmentModifierImpl,
UrlManagerImpl,
} from "@/UI";
import { AuthContext } from "./Data/Auth/Providers/AuthContext";
import { UpdateBanner } from "./UI/Components/UpdateBanner";
interface Props {
store: Store;
Expand All @@ -34,6 +35,7 @@ export const Injector: React.FC<React.PropsWithChildren<Props>> = ({
store,
children,
}) => {
const useAuth = useContext(AuthContext);
const featureManager = new PrimaryFeatureManager(
GetServerStatusStateHelper(store),
new PrimaryLogger(),
Expand Down Expand Up @@ -91,6 +93,7 @@ export const Injector: React.FC<React.PropsWithChildren<Props>> = ({
authHelper,
archiveHelper,
authController,
useAuth,
}}
>
<UpdateBanner apiHelper={apiHelper} />
Expand Down
26 changes: 5 additions & 21 deletions src/Slices/Login/Page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import React, { useContext, useEffect, useState } from "react";
import React, { useContext, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { LoginPage, ListVariant } from "@patternfly/react-core";
import styled from "styled-components";
import { createCookie } from "@/Data/Common/CookieHelper";
import { useLogin } from "@/Data/Managers/V2/Login";
import { DependencyContext, words } from "@/UI";
import { UserCredentialsForm } from "@/UI/Components/UserCredentialsForm";
Expand All @@ -15,33 +14,18 @@ import logo from "@images/logo.svg";
* @returns {React.FunctionComponent} The rendered component.
*/
export const Login: React.FunctionComponent = () => {
const { authController } = useContext(DependencyContext);
const { useAuth } = useContext(DependencyContext);
const navigate = useNavigate();

const [isLoginOpen, setIsLoginOpen] = useState(false);
const { mutate, isError, error, isSuccess, isPending, data } = useLogin();

useEffect(() => {
if (isSuccess) {
createCookie("inmanta_user", data.data.token, 1);
//reload the page to avoid possible error view due to the 401 calls done in the background
navigate(0);
authController.setLocalUserName(data.data.user.username);
useAuth.updateUser(data.data.user.username, data.data.token);
navigate("/");
}
}, [data, isSuccess, authController, navigate]);
}, [data, isSuccess, useAuth, navigate]);

useEffect(() => {
const openLogin = () => {
setIsLoginOpen(true);
};

document.addEventListener("open-login", openLogin);
return () => {
document.removeEventListener("open-login", openLogin);
};
}, []);

if (!isLoginOpen) return null;
return (
<Wrapper>
<StyledLogin
Expand Down
8 changes: 8 additions & 0 deletions src/UI/Dependency/Dependency.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import {
ArchiveHelper,
AuthController,
} from "@/Core";
import {
AuthContextProps,
defaultAuthContext,
} from "@/Data/Auth/Providers/AuthContext";
import {
DummyCommandResolver,
DummyEnvironmentModifier,
Expand All @@ -38,6 +42,7 @@ export interface Dependencies {
authHelper: AuthHelper;
archiveHelper: ArchiveHelper;
authController: AuthController;
useAuth: AuthContextProps;
}

export const DependencyContext = createContext<Dependencies>({
Expand All @@ -52,6 +57,7 @@ export const DependencyContext = createContext<Dependencies>({
authHelper: new DummyAuthHelper(),
archiveHelper: new DummyArchiveHelper(),
authController: new DummyAuthController(),
useAuth: defaultAuthContext,
});

export const DependencyProvider: React.FC<{
Expand All @@ -70,6 +76,7 @@ export const DependencyProvider: React.FC<{
authHelper,
archiveHelper,
authController,
useAuth,
},
children,
}) => (
Expand All @@ -87,6 +94,7 @@ export const DependencyProvider: React.FC<{
authHelper: authHelper || new DummyAuthHelper(),
archiveHelper: archiveHelper || new DummyArchiveHelper(),
authController: authController || new DummyAuthController(),
useAuth: useAuth || defaultAuthContext,
}}
>
{children}
Expand Down
Loading

0 comments on commit 9e30197

Please sign in to comment.