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

initial commit for implementers draft changes #148

Merged
merged 4 commits into from
Sep 21, 2024
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
1 change: 1 addition & 0 deletions interop/authzen-todo-application/.env
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ REACT_APP_OIDC_DOMAIN=citadel.demo.aserto.com
REACT_APP_OIDC_CLIENT_ID=citadel-app
REACT_APP_OIDC_AUDIENCE=citadel-app
REACT_APP_API_ORIGIN=https://authzen-todo-backend.demo.aserto.com
REACT_APP_API_ORIGIN=http://localhost:8080
107 changes: 90 additions & 17 deletions interop/authzen-todo-application/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useAuth } from "oidc-react";
import React, { useCallback, useEffect, useState } from "react";
import { ToastContainer, toast } from "react-toastify";
import { Todos } from "./components/Todos";
import { AppProps, Todo, User } from "./interfaces";
import { AppProps, Todo, User, Config } from "./interfaces";
import { useTodoService, useUser } from "./todoService";
import Select from "react-select";

Expand Down Expand Up @@ -32,18 +32,32 @@ type PDP = {
name: string;
};

type SpecVersion = {
name: string;
};

export const App: React.FC<AppProps> = (props) => {
const auth = useAuth();
const { createTodo, listTodos, listPdps, setPdp } = useTodoService();
const {
createTodo,
listTodos,
getConfig,
pdp,
specVersion,
setPdp,
setSpecVersion,
} = useTodoService();
const userEmail = props.user.email;
const [todos, setTodos] = useState<Todo[] | void>([]);
const [config, setConfig] = useState<Config>();
const [pdps, setPdps] = useState<PDP[]>([]);
const [specVersions, setSpecVersions] = useState<SpecVersion[]>([]);
const [todoTitle, setTodoTitle] = useState<string>("");
const [showCompleted, setShowCompleted] = useState<boolean>(true);
const [showActive, setShowActive] = useState<boolean>(true);
const user: User = useUser(props.user.sub);
const storedPdpOption = localStorage.getItem("pdp");
const currentPdpOption = storedPdpOption ? { name: storedPdpOption } : (pdps && pdps[0]);
const [currentPdpOption, setCurrentPdpOption] = useState<PDP>({ name: pdp });
const [currentSpecVersion, setCurrentSpecVersion] = useState<SpecVersion>({ name: specVersion });

const errorHandler = (errorText: string, close?: number | false) => {
const autoClose = close === undefined ? 3000 : close;
Expand Down Expand Up @@ -89,7 +103,7 @@ export const App: React.FC<AppProps> = (props) => {
refreshTodos();
};

const refreshTodos: () => void = useCallback(() => {
const refreshTodos: () => void = () => {
const getTodos = async () => {
try {
const todos: Todo[] = await listTodos();
Expand All @@ -102,7 +116,7 @@ export const App: React.FC<AppProps> = (props) => {
};

getTodos();
}, [listTodos]);
};

const enableShowCompleted: () => void = () => {
setShowCompleted(true);
Expand All @@ -114,15 +128,32 @@ export const App: React.FC<AppProps> = (props) => {
setShowCompleted(false);
};

const getPdps: () => void = useCallback(() => {
const getSpecVersionsAndPdps: () => void = useCallback(async () => {
const list = async () => {
try {
const pdps: string[] = await listPdps();
const config: Config = await getConfig();
setConfig(config);
const defaultSpecVersion = localStorage.getItem("specVersion") ?? Object.keys(config)[0];
setSpecVersions(
Object.keys(config).map((v) => {
return { name: v };
})
);
if (!specVersion) {
setSpecVersion(defaultSpecVersion);
setCurrentSpecVersion({ name: defaultSpecVersion });
}
const pdps = config[defaultSpecVersion];
const defaultPdp = localStorage.getItem("pdp") ?? pdps[0];
setPdps(
pdps.map((pdp) => {
return { name: pdp };
})
);
if (!pdp) {
setPdp(defaultPdp);
setCurrentPdpOption({ name: defaultPdp });
}
} catch (e) {
if (e instanceof TypeError && e.message === "Failed to fetch") {
errorHandler("", false);
Expand All @@ -131,19 +162,47 @@ export const App: React.FC<AppProps> = (props) => {
};

list();
}, [listPdps]);
}, [getConfig, pdp, specVersion, setPdp, setPdps, setSpecVersion, setSpecVersions]);

const storePdp: (pdp: string) => void = useCallback(
(pdp: string) => {
setPdp(pdp);
setCurrentPdpOption({ name: pdp });
localStorage.setItem("pdp", pdp);
},
[setPdp]
);

const storePdp: ((pdp: string) => void) = useCallback((pdp: string) => {
setPdp(pdp);
localStorage.setItem("pdp", pdp);
}, [setPdp])
const storeSpecVersion: (version: string) => void = useCallback(
(version: string) => {
setSpecVersion(version);
setCurrentSpecVersion({ name: version });
localStorage.setItem("specVersion", version);
const pdps = config && config[version];
if (pdps) {
setPdps(
pdps.map((pdp) => {
return { name: pdp };
})
);
storePdp(pdps[0]);
}
},
[config, setSpecVersion, setPdps, storePdp]
);

useEffect(() => {
getPdps();
refreshTodos();
getSpecVersionsAndPdps();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

useEffect(() => {
if (specVersion && pdp) {
refreshTodos();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [specVersion, pdp])

return (
<div className="App">
<section className="todoapp">
Expand Down Expand Up @@ -218,13 +277,27 @@ export const App: React.FC<AppProps> = (props) => {
<div className="user-controls">
<>
<div className="pdp-info">
<span className="user-name">Authorize using: &nbsp;</span>
<span className="select-title">AuthZEN version: &nbsp;</span>
{pdps.length && (
<Select
className="pdp-select"
isSearchable={false}
options={specVersions}
value={currentSpecVersion}
getOptionLabel={(version: SpecVersion) => version.name}
getOptionValue={(version: SpecVersion) => version.name}
onChange={(option) => storeSpecVersion(option!.name)}
/>
)}
</div>
<div className="pdp-info">
<span className="select-title">Authorize using: &nbsp;</span>
{pdps.length && (
<Select
className="pdp-select"
isSearchable={false}
options={pdps}
defaultValue={currentPdpOption}
value={currentPdpOption}
getOptionLabel={(pdp: PDP) => pdp.name}
getOptionValue={(pdp: PDP) => pdp.name}
onChange={(option) => storePdp(option!.name)}
Expand Down
11 changes: 9 additions & 2 deletions interop/authzen-todo-application/src/LoginWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ export function LoginWrapper() {
const { userData } = auth;
const isAuthenticated = userData?.id_token ? true : false;
const [loggedIn, setLoggedIn] = useState(false);
const [pdp, setPdp] = useState<string>(localStorage.getItem("pdp") ?? "");
const [specVersion, setSpecVersion] = useState<string>("");
const [pdp, setPdp] = useState<string>("");

useEffect(() => {
if (!auth.isLoading && !isAuthenticated) {
Expand All @@ -25,7 +26,13 @@ export function LoginWrapper() {
if (loggedIn && userData?.profile.email) {
return (
<QueryClientProvider client={queryClient}>
<TodoService token={userData.id_token} pdp={pdp} setPdp={setPdp}>
<TodoService
token={userData.id_token}
pdp={pdp}
specVersion={specVersion}
setPdp={setPdp}
setSpecVersion={setSpecVersion}
>
<App
user={{
email: userData.profile.email,
Expand Down
1 change: 1 addition & 0 deletions interop/authzen-todo-application/src/components/Todo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const Todo: React.FC<TodoProps> = (todoProps) => {
<div className="view">
<input
className="toggle"
disabled={!!todoProps.todo.CannotUpdate}
type="checkbox"
onChange={() =>
todoProps.handleCompletedChange(
Expand Down
25 changes: 25 additions & 0 deletions interop/authzen-todo-application/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,29 @@ code {

.pdp-select {
min-width: 300px;
font-size: 20px;
}

.select-title {
min-width: 250px;
}

/* from todomvc-app-css/index.css */
/* override disabled styles to grey out the toggle */
.todo-list li .toggle:disabled + label {
/*
Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433
IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/
*/
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22%23eeeeee%22%20stroke%3D%22%23949494%22%20stroke-width%3D%221%22/%3E%3C/svg%3E');
background-repeat: no-repeat;
background-position: center left;
}

.todo-list li .toggle:checked:disabled + label {
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22%23eeeeee%22%20stroke%3D%22%2359A193%22%20stroke-width%3D%221%22%2F%3E%3Cpath%20fill%3D%22%233EA390%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22%2F%3E%3C%2Fsvg%3E');
}

.todo-list li .toggle:disabled {
cursor: not-allowed;
}
10 changes: 9 additions & 1 deletion interop/authzen-todo-application/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export interface TodoValues {
export interface Todo extends TodoValues {
ID: string;
OwnerID: string;
CannotUpdate?: boolean;
}

export interface User {
Expand All @@ -21,8 +22,11 @@ export interface ITodoService {
saveTodo: (id: string, values: TodoValues) => Promise<Todo[]>;
deleteTodo: (todo: Todo) => Promise<void | Response>;
getUser: (sub: string) => Promise<User>;
listPdps: () => Promise<string[]>;
getConfig: () => Promise<Config>;
setPdp: (pdp: string) => void;
setSpecVersion: (specVersion: string) => void;
pdp: string
specVersion: string
}

export interface TodoProps {
Expand All @@ -47,3 +51,7 @@ export interface AuthUser {
email: string;
sub: string;
}

export type Config = {
[specVersion: string]: string[]
}
36 changes: 25 additions & 11 deletions interop/authzen-todo-application/src/todoService.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import React, { useContext } from "react";
import { Todo, TodoValues, ITodoService, User } from "./interfaces";
import { Todo, TodoValues, ITodoService, User, Config } from "./interfaces";
import { useQuery } from "react-query";

const serviceContext = React.createContext(
{ token: "", pdp: "", setPdp: (_: string) => {} }
);
const serviceContext = React.createContext({
token: "",
pdp: "",
specVersion: "",
setPdp: (_: string) => {},
setSpecVersion: (_: string) => {},
});

const urls = {
pdps: `${process.env.REACT_APP_API_ORIGIN}/pdps`,
Expand All @@ -14,11 +18,14 @@ const urls = {
};

export const useTodoService: () => ITodoService = () => {
const { token, pdp, setPdp } = useContext(serviceContext);
const { token, pdp, specVersion, setPdp, setSpecVersion } = useContext(serviceContext);
const headers: Headers = new Headers();

headers.append("Authorization", `Bearer ${token}`);
headers.append("Content-Type", "application/json");
if (specVersion) {
headers.append("X_AUTHZEN_SPEC_VERSION", specVersion);
}
if (pdp) {
headers.append("X_AUTHZEN_PDP", pdp);
}
Expand Down Expand Up @@ -62,7 +69,7 @@ export const useTodoService: () => ITodoService = () => {
return await jsonOrError(response);
};

const listPdps: () => Promise<string[]> = async () => {
const getConfig: () => Promise<Config> = async () => {
const response = await fetch(urls.pdps, { headers: headers });
return await jsonOrError(response);
};
Expand All @@ -73,14 +80,17 @@ export const useTodoService: () => ITodoService = () => {
saveTodo,
deleteTodo,
getUser,
listPdps,
getConfig,
pdp,
specVersion,
setPdp,
setSpecVersion,
};
};

export const useUser: (userId: string) => User = (userId: string) => {
const { getUser } = useTodoService();
const response = useQuery(['User', userId], () => {
const response = useQuery(["User", userId], () => {
return getUser(userId);
});
return response.data as User;
Expand All @@ -97,17 +107,21 @@ const jsonOrError = async (response: Response): Promise<any> => {
export type ServiceProps = {
token: string;
pdp: string;
setPdp: (pdp: string) => void
specVersion: string;
setPdp: (pdp: string) => void;
setSpecVersion: (specVersion: string) => void;
};

const TodoService: React.FC<React.PropsWithChildren<ServiceProps>> = ({
children,
token,
pdp,
setPdp
specVersion,
setPdp,
setSpecVersion,
}) => {
return (
<serviceContext.Provider value={{ token, pdp, setPdp }}>
<serviceContext.Provider value={{ token, pdp, specVersion, setPdp, setSpecVersion }}>
{children}
</serviceContext.Provider>
);
Expand Down
Loading
Loading