Skip to content

Commit

Permalink
Merge pull request #147 from Tauffer-Consulting/feat/token-expiry
Browse files Browse the repository at this point in the history
Token expiration date
  • Loading branch information
nathan-vm authored Nov 15, 2023
2 parents 27d45cb + 6fda8c9 commit 9da92c9
Show file tree
Hide file tree
Showing 12 changed files with 154 additions and 67 deletions.
1 change: 1 addition & 0 deletions frontend/src/context/authentication/api/postAuthLogin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ interface IPostAuthLoginResponseInterface {
user_id: string;
group_ids: number[];
access_token: string;
token_expires_in: number;
}

/**
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/context/authentication/api/postAuthRegister.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ interface IPostAuthRegisterParams {
}

interface IPostAuthRegisterResponseInterface {
id: string;
user_id: string;
email: string;
token_expires_in: number;
groups: Array<{ group_id: number; group_name: string }>;
}

Expand All @@ -27,7 +28,8 @@ export const postAuthRegister: (

export const postAuthRegisterMockResponse: IPostAuthRegisterResponseInterface =
{
id: "some_id",
user_id: "some_id",
email: "[email protected]",
token_expires_in: 3600,
groups: [{ group_id: 0, group_name: "some group" }],
};
69 changes: 54 additions & 15 deletions frontend/src/context/authentication/authentication.context.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Loading from "components/Loading";
import React, {
type ReactNode,
useCallback,
Expand All @@ -13,6 +14,7 @@ import { createCustomContext } from "utils";

import { postAuthLogin, postAuthRegister } from "./api";
import {
authStatus,
type IAuthenticationContext,
type IAuthenticationStore,
} from "./authentication.interface";
Expand All @@ -29,6 +31,7 @@ export const AuthenticationProvider: React.FC<{ children: ReactNode }> = ({
children,
}) => {
const navigate = useNavigate();
const [status, setStatus] = useState(authStatus.Loading);
const [authLoading, setAuthLoading] = useState(false);
const [store, setStore] = useState<IAuthenticationStore>({
token: localStorage.getItem("auth_token"),
Expand All @@ -38,15 +41,25 @@ export const AuthenticationProvider: React.FC<{ children: ReactNode }> = ({
const isLogged = useRef(!!store.token);

const login = useCallback(
(token: string, userId: string, redirect = "") => {
(token: string, userId: string, tokenExpiresIn: number, redirect = "") => {
isLogged.current = true;
setStore((store) => ({
...store,
token,
userId,
tokenExpiresIn,
}));
const currentDate = new Date();
const tokenExpirationDate = new Date(
currentDate.getTime() + tokenExpiresIn * 1000,
);
localStorage.setItem("auth_token", token);
localStorage.setItem("userId", userId);
localStorage.setItem(
"tokenExpiresAtTimestamp",
tokenExpirationDate.getTime().toString(),
);
setStatus(authStatus.SignedIn);
navigate(redirect);
},
[navigate],
Expand All @@ -60,19 +73,21 @@ export const AuthenticationProvider: React.FC<{ children: ReactNode }> = ({
...store,
token: null,
}));
setStatus(authStatus.SignedOut);
navigate("/sign-in");
}, [navigate]);

/**
* @todo improve error handling
*/
const authenticate = useCallback(
async (email: string, password: string) => {
setAuthLoading(true);
void postAuthLogin({ email, password })
.then((res) => {
if (res.status === 200) {
login(res.data.access_token, res.data.user_id);
login(
res.data.access_token,
res.data.user_id,
res.data.token_expires_in,
);
}
})
.finally(() => {
Expand Down Expand Up @@ -107,16 +122,15 @@ export const AuthenticationProvider: React.FC<{ children: ReactNode }> = ({
[authenticate],
);

const value = useMemo((): IAuthenticationContext => {
return {
store,
isLogged: isLogged.current,
authLoading,
logout,
authenticate,
register,
};
}, [store, logout, authenticate, register, authLoading]);
const tokenExpired = useCallback(() => {
const tokenTimestamp = localStorage.getItem("tokenExpiresAtTimestamp");
if (tokenTimestamp) {
const date1 = Number(tokenTimestamp);
const date2 = new Date().getTime();
return date1 <= date2;
}
return true;
}, []);

/**
* Listen to "logout" event and handles it (ie. unauthorized request)
Expand All @@ -132,6 +146,31 @@ export const AuthenticationProvider: React.FC<{ children: ReactNode }> = ({
};
}, [logout]);

useEffect(() => {
const expired = tokenExpired();

if (expired) {
logout();
} else {
setStatus(authStatus.SignedIn);
}
}, [tokenExpired]);

const value = useMemo((): IAuthenticationContext => {
return {
store,
isLogged: isLogged.current,
authLoading,
logout,
authenticate,
register,
};
}, [store, logout, authenticate, register, authLoading]);

if (status === authStatus.Loading) {
return <Loading />;
}

return (
<AuthenticationContext.Provider value={value}>
{children}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ export interface IAuthenticationStore {
userId: string | null;
}

export enum authStatus {
Loading,
SignedIn,
SignedOut,
}

export interface IAuthenticationContext {
store: IAuthenticationStore;
isLogged: boolean;
Expand Down
60 changes: 38 additions & 22 deletions frontend/src/context/workspaces/api/getWorkspaceMembers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,37 @@ interface IGetWorkspaceMembers {
pageSize: number;
}

const getWorkspaceUsersUrl = (
auth: boolean,
workspaceId: string,
page: number,
pageSize: number,
) => {
return auth && workspaceId
? `/workspaces/${workspaceId}/users?page=${page}&page_size=${pageSize}`
: null;
};

/**
* Get workspaces using GET /workspaces
* @returns workspaces
*/
const getWorkspaceUsers: (
auth: boolean,
workspaceId: string,
page: number,
pageSize: number,
) => Promise<AxiosResponse<IGetWorkspaceUsersResponse>> = async (
) => Promise<AxiosResponse<IGetWorkspaceUsersResponse> | undefined> = async (
auth,
workspaceId,
page,
pageSize,
) => {
return await dominoApiClient.get(
`/workspaces/${workspaceId}/users?page=${page}&page_size=${pageSize}`,
);
if (auth && workspaceId && page && pageSize) {
const url = getWorkspaceUsersUrl(auth, workspaceId, page, pageSize);

if (url) return await dominoApiClient.get(url);
}
};

/**
Expand All @@ -37,30 +52,31 @@ const getWorkspaceUsers: (
export const useAuthenticatedGetWorkspaceUsers = (
params: IGetWorkspaceMembers,
) => {
const fetcher = useCallback(async (params: IGetWorkspaceMembers) => {
return await getWorkspaceUsers(
params.workspaceId,
params.page,
params.pageSize,
).then((data) => data.data);
}, []);

const auth = useAuthentication();

if (!params.page) {
params.page = 0;
}
if (!params.pageSize) {
params.pageSize = 10;
}

const auth = useAuthentication();

const fetcher = useCallback(async () => {
return await getWorkspaceUsers(
auth.isLogged,
params.workspaceId,
params.page,
params.pageSize,
).then((data) => data?.data);
}, [params, auth]);

return useSWR(
auth.isLogged && params.workspaceId
? `/workspaces/${params.workspaceId}/users?page=${params.page}&page_size=${params.pageSize}`
: null,
async () => await fetcher(params),
{
revalidateOnFocus: false,
revalidateOnReconnect: false,
},
getWorkspaceUsersUrl(
auth.isLogged,
params.workspaceId,
params.page,
params.pageSize,
),
fetcher,
);
};
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import RefreshIcon from "@mui/icons-material/Refresh";
import { Button, Grid } from "@mui/material";
import {
type GridSlotsComponentsProps,
Expand All @@ -8,15 +9,17 @@ import React from "react";
declare module "@mui/x-data-grid" {
interface FooterPropsOverrides {
triggerRun: () => void;
refresh: () => void;
}
}

interface Props extends NonNullable<GridSlotsComponentsProps["footer"]> {
triggerRun: () => void;
refresh: () => void;
}

export const WorkflowRunTableFooter = React.forwardRef<HTMLDivElement, Props>(
({ triggerRun }) => {
({ triggerRun, refresh }, ref) => {
const rootProps = useGridRootProps();

const paginationElement = rootProps.pagination &&
Expand All @@ -26,7 +29,7 @@ export const WorkflowRunTableFooter = React.forwardRef<HTMLDivElement, Props>(
);

return (
<Grid container>
<Grid container ref={ref}>
<Grid item xs={6}>
<Grid
container
Expand All @@ -40,13 +43,12 @@ export const WorkflowRunTableFooter = React.forwardRef<HTMLDivElement, Props>(
</Button>
</Grid>
<Grid item sx={{ paddingLeft: "1rem" }}>
<Button disabled variant="contained">
Cancel
</Button>
</Grid>
<Grid item sx={{ paddingLeft: "1rem" }}>
<Button disabled variant="contained">
Pause
<Button
variant="contained"
onClick={refresh}
startIcon={<RefreshIcon />}
>
Refresh
</Button>
</Grid>
</Grid>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ interface Props {
selectedRun: IWorkflowRuns | null;
onSelectedRunChange: (run: IWorkflowRuns | null) => void;
triggerRun: () => void;
refresh: () => void;
}

export interface WorkflowRunsTableRef {
Expand All @@ -32,6 +33,7 @@ export const WorkflowRunsTable = forwardRef<WorkflowRunsTableRef, Props>(
selectedRun,
onSelectedRunChange: setSelectedRun,
triggerRun,
refresh,
},
ref,
) => {
Expand Down Expand Up @@ -164,7 +166,7 @@ export const WorkflowRunsTable = forwardRef<WorkflowRunsTableRef, Props>(
footer: WorkflowRunTableFooter,
}}
slotProps={{
footer: { triggerRun },
footer: { triggerRun, refresh },
}}
sx={{
"&.MuiDataGrid-root .MuiDataGrid-cell:focus": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ export const WorkflowDetail: React.FC = () => {
setAutoUpdate(true);
}
}}
refresh={refresh}
selectedRun={selectedRun}
ref={workflowRunsTableRef}
onSelectedRunChange={handleSelectRun}
Expand Down
10 changes: 4 additions & 6 deletions rest/schemas/responses/auth.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
from pydantic import BaseModel
from typing import List

# # Base Models
# class UserGroupBase(BaseModel):
# group_id: int
# group_name: str

# Responses Models
class LoginResponse(BaseModel):
user_id: int
email: str
workspaces_ids: List[int]
access_token: str
token_expires_in: int

class RegisterResponse(BaseModel):
id: int
user_id: int
email: str
workspaces_ids: List[int]
access_token: str
token_expires_in: int
Loading

0 comments on commit 9da92c9

Please sign in to comment.