Skip to content

Commit

Permalink
organizing utils
Browse files Browse the repository at this point in the history
  • Loading branch information
wmazoni committed May 1, 2024
1 parent c8c737d commit fdae186
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 116 deletions.
3 changes: 2 additions & 1 deletion frontend/src/AuthContext.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createContext } from "react";
import { TokenData } from "util/requests";
import { TokenData } from "util/auth";


export type AuthContextData = {
authenticated: boolean;
Expand Down
16 changes: 7 additions & 9 deletions frontend/src/components/Navbar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,11 @@ import { useContext, useEffect } from 'react';

import { Link, NavLink } from 'react-router-dom';
import history from 'util/history';
import {
getTokenData,
isAuthenticated,
removeAuthData,
} from 'util/requests';
import { getTokenData, isAuthenticated } from 'util/auth';
import { removeAuthData } from 'util/storage';

const Navbar = () => {

const {authContextData, setAuthContextData} = useContext(AuthContext)
const { authContextData, setAuthContextData } = useContext(AuthContext);

useEffect(() => {
if (isAuthenticated()) {
Expand Down Expand Up @@ -74,10 +70,12 @@ const Navbar = () => {
</ul>
</div>

<div className='nav-login-logout'>
<div className="nav-login-logout">
{authContextData.authenticated ? (
<>
<span className='nav-username'>{authContextData.tokenData?.user_name}</span>
<span className="nav-username">
{authContextData.tokenData?.user_name}
</span>
<Link to="#logout" onClick={handleLogoutClick}>
LOGOUT
</Link>
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/PrivateRoute/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Redirect, Route } from 'react-router-dom';
import { Role, hasAnyRoles, isAuthenticated } from 'util/requests';
import { Role, hasAnyRoles, isAuthenticated } from 'util/auth';

type Props = {
children: React.ReactNode;
path: string;
roles?: Role[]
roles?: Role[];
};

const PrivateRoute = ({ children, path, roles = [] }: Props) => {
Expand Down
44 changes: 29 additions & 15 deletions frontend/src/pages/Admin/Auth/Login/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { Link, useHistory, useLocation } from 'react-router-dom';
import ButtonIcon from 'components/ButtonIcon';
import { useForm } from 'react-hook-form';
import './styles.css';
import { getTokenData, requestBackendLogin, saveAuthData } from 'util/requests';
import { requestBackendLogin } from 'util/requests';
import { useContext, useState } from 'react';
import { AuthContext } from 'AuthContext';
import { saveAuthData } from 'util/storage';
import { getTokenData } from 'util/auth';

type FormData = {
username: string;
Expand All @@ -13,24 +15,28 @@ type FormData = {

type LocationState = {
from: string;
}
};

const Login = () => {
const location = useLocation<LocationState>();
const {from} = location.state || { from: { pathname: '/admin' } };
const {setAuthContextData} = useContext(AuthContext)
const { from } = location.state || { from: { pathname: '/admin' } };
const { setAuthContextData } = useContext(AuthContext);
const [hasError, setHasError] = useState(false);
const { register, handleSubmit, formState: {errors} } = useForm<FormData>();
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormData>();
const history = useHistory();
const onSubmit = (formData: FormData) => {
requestBackendLogin(formData)
.then((response) => {
saveAuthData(response.data)
saveAuthData(response.data);
setHasError(false);
setAuthContextData({
authenticated: true,
tokenData: getTokenData()
})
tokenData: getTokenData(),
});
history.replace(from);
})
.catch((error) => {
Expand All @@ -55,27 +61,35 @@ const Login = () => {
required: 'Campo obrigatório',
pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: 'Email inválido'
}
message: 'Email inválido',
},
})}
type="text"
className={`form-control base-input ${errors.username ? 'is-invalid' : ''}`}
className={`form-control base-input ${
errors.username ? 'is-invalid' : ''
}`}
placeholder="Email"
name="username"
/>
<div className="invalid-feedback d-block">{errors.username?.message}</div>
<div className="invalid-feedback d-block">
{errors.username?.message}
</div>
</div>
<div className="mb-2">
<input
{...register('password', {
required: 'Campo obrigatório'
required: 'Campo obrigatório',
})}
type="password"
className={`form-control base-input ${errors.password ? 'is-invalid' : ''} `}
className={`form-control base-input ${
errors.password ? 'is-invalid' : ''
} `}
placeholder="Password"
name="password"
/>
<div className="invalid-feedback d-block">{errors.password?.message}</div>
<div className="invalid-feedback d-block">
{errors.password?.message}
</div>
</div>
<Link to="/admin/auth/recover" className="login-link-recover">
Esqueci a senha
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/Admin/Navbar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NavLink } from 'react-router-dom';
import './styles.css';
import { hasAnyRoles } from 'util/requests';
import { hasAnyRoles } from 'util/auth';

const Navbar = () => {
return (
Expand Down
33 changes: 33 additions & 0 deletions frontend/src/util/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import jwtDecode from 'jwt-decode';
import { getAuthData } from './storage';

export type Role = 'ROLE_OPERATOR' | 'ROLE_ADMIN';

export type TokenData = {
exp: number;
user_name: string;
authorities: Role[];
};

export const getTokenData = (): TokenData | undefined => {
try {
return jwtDecode(getAuthData().access_token);
} catch (error) {
return undefined;
}
};
export const isAuthenticated = (): boolean => {
const tokenData = getTokenData();
return tokenData && tokenData.exp * 1000 > Date.now() ? true : false;
};
export const hasAnyRoles = (roles: Role[]): boolean => {
if (roles.length === 0) {
return true;
}
const tokenData = getTokenData();

if (tokenData !== undefined) {
return roles.some((role) => tokenData.authorities.includes(role));
}
return false;
};
137 changes: 49 additions & 88 deletions frontend/src/util/requests.ts
Original file line number Diff line number Diff line change
@@ -1,104 +1,65 @@
import qs from 'qs'
import axios, { AxiosRequestConfig } from 'axios'
import history from './history'
import jwtDecode from 'jwt-decode';
import qs from 'qs';
import axios, { AxiosRequestConfig } from 'axios';
import history from './history';
import { getAuthData } from './storage';

export type Role = 'ROLE_OPERATOR' | 'ROLE_ADMIN';
export const BASE_URL =
process.env.REACT_APP_BACKEND_URL ?? 'http://localhost:8080';

export type TokenData = {
exp: number,
user_name: string,
authorities: Role[]
}

type LoginResponse = {
"access_token": string,
"token_type": string,
"expires_in": string,
"scope": string,
"userFirstName": string,
"userId": string
}

export const BASE_URL = process.env.REACT_APP_BACKEND_URL ?? 'http://localhost:8080'
const tokenKey = 'authData'
const CLIENT_ID = process.env.REACT_APP_CLIENT_ID ?? 'dscatalog'
const CLIENT_SECRET = process.env.REACT_APP_CLIENT_SECRET ?? 'dscatalog123'
const basicHeader = () => 'Basic ' + window.btoa(CLIENT_ID + ':' + CLIENT_SECRET)
const CLIENT_ID = process.env.REACT_APP_CLIENT_ID ?? 'dscatalog';
const CLIENT_SECRET = process.env.REACT_APP_CLIENT_SECRET ?? 'dscatalog123';
const basicHeader = () =>
'Basic ' + window.btoa(CLIENT_ID + ':' + CLIENT_SECRET);
type LoginData = {
username: string
password: string
}
export const requestBackendLogin = (loginData : LoginData) => {
const headers = {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: basicHeader()
}
const data = qs.stringify ({
...loginData,
grant_type: 'password'
})
return axios({method: 'POST', baseURL: BASE_URL, url: '/oauth/token', data, headers})
}

export const saveAuthData = (obj : LoginResponse) => {
localStorage.setItem(tokenKey, JSON.stringify(obj))
}

export const getAuthData = () => {
const str = localStorage.getItem(tokenKey) ?? "{}"
return JSON.parse(str) as LoginResponse
}

export const removeAuthData = () => {
localStorage.removeItem(tokenKey);
}
username: string;
password: string;
};
export const requestBackendLogin = (loginData: LoginData) => {
const headers = {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: basicHeader(),
};
const data = qs.stringify({
...loginData,
grant_type: 'password',
});
return axios({
method: 'POST',
baseURL: BASE_URL,
url: '/oauth/token',
data,
headers,
});
};

export const requestBackend = (config: AxiosRequestConfig) => {
const headers = config.withCredentials ? {
const headers = config.withCredentials
? {
...config.headers,
Authorization: "Bearer " + getAuthData().access_token
} : config.headers;
return axios({...config, baseURL: BASE_URL, headers})
}
Authorization: 'Bearer ' + getAuthData().access_token,
}
: config.headers;
return axios({ ...config, baseURL: BASE_URL, headers });
};

// Add a request interceptor
axios.interceptors.request.use(function (config) {
axios.interceptors.request.use(
function (config) {
return config;
}, function (error) {
},
function (error) {
return Promise.reject(error);
});
}
);

axios.interceptors.response.use(function (response) {
axios.interceptors.response.use(
function (response) {
return response;
}, function (error) {
},
function (error) {
if (error.response.status === 401) {
history.push('/admin/auth')
history.push('/admin/auth');
}
return Promise.reject(error);
});

export const getTokenData = () : TokenData | undefined => {
try {
return jwtDecode(getAuthData().access_token);
} catch (error) {
return undefined;
}
}

export const isAuthenticated = () : boolean => {
const tokenData = getTokenData();
return (tokenData && tokenData.exp * 1000 > Date.now()) ? true : false;
}

export const hasAnyRoles = (roles: Role[]) : boolean => {
if (roles.length === 0) {
return true;
}
const tokenData = getTokenData();

if (tokenData !== undefined) {
return roles.some(role => tokenData.authorities.includes(role))
}
return false;
}
);
21 changes: 21 additions & 0 deletions frontend/src/util/storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const tokenKey = 'authData';
type LoginResponse = {
access_token: string;
token_type: string;
expires_in: string;
scope: string;
userFirstName: string;
userId: string;
};
export const saveAuthData = (obj: LoginResponse) => {
localStorage.setItem(tokenKey, JSON.stringify(obj));
};

export const getAuthData = () => {
const str = localStorage.getItem(tokenKey) ?? '{}';
return JSON.parse(str) as LoginResponse;
};

export const removeAuthData = () => {
localStorage.removeItem(tokenKey);
};

0 comments on commit fdae186

Please sign in to comment.