Compare commits
No commits in common. "f6fbdd94f73c92f453c291f82bae3f6f323dda30" and "8856f25eacf4d555d6fbd6a568c1fe25acee29ce" have entirely different histories.
f6fbdd94f7
...
8856f25eac
9
.env
9
.env
|
@ -1,8 +1,9 @@
|
|||
#REACT_APP_TUITIO_URL=http://localhost:5063/identity/authenticate?UserName={username}&Password={password}
|
||||
#REACT_APP_IDENTITY_AUTHENTICATION_URL=http://localhost:5063/identity/authenticate?UserName={username}&Password={password}
|
||||
|
||||
REACT_APP_TUITIO_URL=https://lab.code-rove.com/tuitio
|
||||
#REACT_APP_NETWORK_RESURRECTOR_API_URL=http://localhost:5064
|
||||
REACT_APP_NETWORK_RESURRECTOR_API_URL=https://lab.code-rove.com/network-resurrector-api
|
||||
REACT_APP_IDENTITY_AUTHENTICATION_URL=https://lab.code-rove.com/identity-server-api/identity/authenticate?UserName={username}&Password={password}
|
||||
REACT_APP_NETWORK_RESURRECTOR_API_URL=http://localhost:5064
|
||||
#REACT_APP_NETWORK_RESURRECTOR_SERVER_URL=http://localhost:5062
|
||||
#REACT_APP_NETWORK_RESURRECTOR_SERVER_URL=https://lab.code-rove.com/network-resurrector-server-api
|
||||
|
||||
#600000 milliseconds = 10 minutes
|
||||
REACT_APP_MACHINE_PING_INTERVAL=600000
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
PUBLIC_URL=/network-resurrector/
|
||||
REACT_APP_TUITIO_URL=https://lab.code-rove.com/tuitio
|
||||
REACT_APP_IDENTITY_AUTHENTICATION_URL=https://lab.code-rove.com/tuitio/identity/authenticate?UserName={username}&Password={password}
|
||||
REACT_APP_NETWORK_RESURRECTOR_API_URL=https://lab.code-rove.com/network-resurrector-api
|
||||
|
||||
#900000 milliseconds = 15 minutes
|
||||
|
|
22
LICENSE
22
LICENSE
|
@ -1,22 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 Tudor Stanciu
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
File diff suppressed because it is too large
Load Diff
|
@ -13,7 +13,7 @@
|
|||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@flare/tuitio-client-react": "^1.0.0",
|
||||
"@flare/js-utils": "^1.0.2",
|
||||
"@material-ui/core": "^4.11.2",
|
||||
"@material-ui/icons": "^4.11.2",
|
||||
"@testing-library/jest-dom": "^5.11.6",
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from "react";
|
|||
import ApplicationStepper from "./stepper/ApplicationStepper";
|
||||
import Switcher from "./Switcher";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import { useTuitioToken } from "@flare/tuitio-client-react";
|
||||
import { useAuthorizationToken } from "../../hooks";
|
||||
import LoginContainer from "../../features/login/components/LoginContainer";
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
|
@ -16,7 +16,7 @@ const useStyles = makeStyles(() => ({
|
|||
|
||||
const Main = () => {
|
||||
const classes = useStyles();
|
||||
const { validate: validateToken } = useTuitioToken();
|
||||
const { validateToken } = useAuthorizationToken();
|
||||
const tokenIsValid = validateToken();
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, useMemo } from "react";
|
||||
import React, { useState, useCallback } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import {
|
||||
|
@ -18,29 +18,35 @@ import LoginComponent from "./LoginComponent";
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { useToast } from "../../../hooks";
|
||||
import styles from "../styles";
|
||||
import { useTuitioUser } from "@flare/tuitio-client-react";
|
||||
|
||||
const useStyles = makeStyles(styles);
|
||||
|
||||
const LoggedInComponent = ({ credentials, onChange, onLogin, onLogout }) => {
|
||||
const LoggedInComponent = ({
|
||||
credentials,
|
||||
token,
|
||||
onChange,
|
||||
onLogin,
|
||||
onLogout
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
const { t } = useTranslation();
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const { info } = useToast();
|
||||
const { lastLoginDate, userName } = useTuitioUser();
|
||||
|
||||
const handleExpandLogin = () => {
|
||||
setExpanded(!expanded);
|
||||
};
|
||||
|
||||
const loginDate = useMemo(() => {
|
||||
if (lastLoginDate) {
|
||||
const valueForDisplay = t("LONG_DATE", { date: lastLoginDate });
|
||||
const getTokenValidFrom = useCallback(() => {
|
||||
const tokenValidFrom = token?.validFrom;
|
||||
|
||||
if (tokenValidFrom) {
|
||||
const valueForDisplay = t("LONG_DATE", { date: tokenValidFrom });
|
||||
return valueForDisplay;
|
||||
}
|
||||
|
||||
return "N/A";
|
||||
}, [lastLoginDate, t]);
|
||||
}, [token, t]);
|
||||
|
||||
const handleLogin = async () => {
|
||||
const result = await onLogin();
|
||||
|
@ -59,11 +65,15 @@ const LoggedInComponent = ({ credentials, onChange, onLogin, onLogout }) => {
|
|||
<AccountBox />
|
||||
</Avatar>
|
||||
}
|
||||
title={<strong>{t("Login.Hello", { username: userName })}</strong>}
|
||||
title={
|
||||
<strong>
|
||||
{t("Login.Hello", { username: credentials.userName })}
|
||||
</strong>
|
||||
}
|
||||
subheader={
|
||||
<Tooltip title={t("Login.AuthenticationDate")}>
|
||||
<Typography variant="caption" display="block">
|
||||
{loginDate}
|
||||
{getTokenValidFrom()}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
}
|
||||
|
@ -107,6 +117,7 @@ const LoggedInComponent = ({ credentials, onChange, onLogin, onLogout }) => {
|
|||
|
||||
LoggedInComponent.propTypes = {
|
||||
credentials: PropTypes.object.isRequired,
|
||||
token: PropTypes.object,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onLogin: PropTypes.func.isRequired,
|
||||
onLogout: PropTypes.func.isRequired
|
||||
|
|
|
@ -43,9 +43,6 @@ const LoginComponent = ({ credentials, onChange, onLogin }) => {
|
|||
className={classes.field}
|
||||
onChange={onChange("password")}
|
||||
value={credentials.password}
|
||||
onKeyDown={e => {
|
||||
if (e.key === "Enter") onLogin();
|
||||
}}
|
||||
/>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
|
|
|
@ -1,49 +1,59 @@
|
|||
import React, { useState } from "react";
|
||||
import React, { useContext } from "react";
|
||||
import LoginCard from "./LoginCard";
|
||||
import { useToast } from "../../../hooks";
|
||||
import { authenticate, invalidate } from "../../../utils/identity";
|
||||
import {
|
||||
ApplicationStateContext,
|
||||
ApplicationDispatchContext
|
||||
} from "../../../state/ApplicationContexts";
|
||||
import { useToast, useAuthorizationToken } from "../../../hooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import LoggedInComponent from "./LoggedInComponent";
|
||||
import { useTuitioClient, useTuitioToken } from "@flare/tuitio-client-react";
|
||||
|
||||
const LoginContainer = () => {
|
||||
const [credentials, setCredentials] = useState({
|
||||
userName: "",
|
||||
password: ""
|
||||
});
|
||||
|
||||
const state = useContext(ApplicationStateContext);
|
||||
const dispatchActions = useContext(ApplicationDispatchContext);
|
||||
const { error } = useToast();
|
||||
const { t } = useTranslation();
|
||||
const { login, logout } = useTuitioClient({
|
||||
onLoginFailed: response => error(t("Login.IncorrectCredentials")),
|
||||
onLoginError: err => error(err.message)
|
||||
});
|
||||
const { valid: tokenIsValid } = useTuitioToken();
|
||||
const { tokenIsValid, invalidateToken, getToken } = useAuthorizationToken();
|
||||
|
||||
const handleChange = prop => event => {
|
||||
setCredentials(prev => ({ ...prev, [prop]: event.target.value }));
|
||||
dispatchActions.onCredentialsChange(prop, event.target.value);
|
||||
};
|
||||
|
||||
const handleLogin = () => {
|
||||
const { userName, password } = credentials;
|
||||
return login(userName, password);
|
||||
const handleLogin = async () => {
|
||||
const { userName, password } = state.credentials;
|
||||
|
||||
try {
|
||||
const response = await authenticate(userName, password);
|
||||
if (response.status === "SUCCESS") {
|
||||
dispatchActions.onAuthorizationTokenChange(response.token);
|
||||
return response.token;
|
||||
} else if (response.status === "BAD_CREDENTIALS") {
|
||||
error(t("Login.IncorrectCredentials"));
|
||||
}
|
||||
} catch (err) {
|
||||
error(err.message);
|
||||
}
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
logout();
|
||||
invalidate();
|
||||
invalidateToken();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{tokenIsValid ? (
|
||||
<LoggedInComponent
|
||||
credentials={credentials}
|
||||
credentials={state.credentials}
|
||||
token={getToken()}
|
||||
onChange={handleChange}
|
||||
onLogin={handleLogin}
|
||||
onLogout={handleLogout}
|
||||
/>
|
||||
) : (
|
||||
<LoginCard
|
||||
credentials={credentials}
|
||||
credentials={state.credentials}
|
||||
onChange={handleChange}
|
||||
onLogin={handleLogin}
|
||||
/>
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
export { useToast } from "./useToast";
|
||||
export { useAuthorizationToken } from "./useAuthorizationToken";
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
import { useContext } from "react";
|
||||
import {
|
||||
ApplicationStateContext,
|
||||
ApplicationDispatchContext
|
||||
} from "../state/ApplicationContexts";
|
||||
|
||||
export const useAuthorizationToken = () => {
|
||||
const state = useContext(ApplicationStateContext);
|
||||
const dispatchActions = useContext(ApplicationDispatchContext);
|
||||
|
||||
const getToken = () => state.security.authorization.token;
|
||||
const validateToken = () => {
|
||||
const token = getToken();
|
||||
if (!token) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const valid = new Date(token.validUntil) >= new Date();
|
||||
return valid;
|
||||
};
|
||||
|
||||
const tokenIsValid = validateToken();
|
||||
|
||||
const invalidateToken = () => {
|
||||
dispatchActions.onAuthorizationTokenChange(null);
|
||||
};
|
||||
|
||||
return { getToken, validateToken, tokenIsValid, invalidateToken };
|
||||
};
|
|
@ -4,14 +4,11 @@ import "./index.css";
|
|||
import "./utils/i18n";
|
||||
import App from "./components/App";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
import { TuitioProvider } from "@flare/tuitio-client-react";
|
||||
|
||||
ReactDOM.render(
|
||||
<Router basename={process.env.PUBLIC_URL || ""}>
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<TuitioProvider tuitioUrl={process.env.REACT_APP_TUITIO_URL}>
|
||||
<App />
|
||||
</TuitioProvider>
|
||||
<App />
|
||||
</Suspense>
|
||||
</Router>,
|
||||
document.getElementById("root")
|
||||
|
|
|
@ -1,4 +1,20 @@
|
|||
import { localStorage } from "@flare/js-utils";
|
||||
import { storageKeys } from "../utils/identity";
|
||||
|
||||
const { getItem } = localStorage;
|
||||
const token = getItem(storageKeys.TOKEN);
|
||||
const userName = getItem(storageKeys.USER);
|
||||
|
||||
export const initialState = {
|
||||
credentials: {
|
||||
userName: userName || "",
|
||||
password: ""
|
||||
},
|
||||
security: {
|
||||
authorization: {
|
||||
token
|
||||
}
|
||||
},
|
||||
network: {
|
||||
machines: Object.assign([], { loaded: false }),
|
||||
test: ""
|
||||
|
|
|
@ -1,5 +1,15 @@
|
|||
export function reducer(state, action) {
|
||||
switch (action.type) {
|
||||
case "onCredentialsChange": {
|
||||
const { prop, value } = action.payload;
|
||||
return {
|
||||
...state,
|
||||
credentials: {
|
||||
...state.credentials,
|
||||
[prop]: value
|
||||
}
|
||||
};
|
||||
}
|
||||
case "onNetworkChange": {
|
||||
const { prop, value } = action.payload;
|
||||
return {
|
||||
|
@ -10,6 +20,19 @@ export function reducer(state, action) {
|
|||
}
|
||||
};
|
||||
}
|
||||
case "onAuthorizationTokenChange": {
|
||||
const { token } = action.payload;
|
||||
return {
|
||||
...state,
|
||||
security: {
|
||||
...state.security,
|
||||
authorization: {
|
||||
...state.security.authorization,
|
||||
token
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
|
@ -17,6 +40,10 @@ export function reducer(state, action) {
|
|||
}
|
||||
|
||||
export const dispatchActions = dispatch => ({
|
||||
onCredentialsChange: (prop, value) =>
|
||||
dispatch({ type: "onCredentialsChange", payload: { prop, value } }),
|
||||
onNetworkChange: (prop, value) =>
|
||||
dispatch({ type: "onNetworkChange", payload: { prop, value } })
|
||||
dispatch({ type: "onNetworkChange", payload: { prop, value } }),
|
||||
onAuthorizationTokenChange: token =>
|
||||
dispatch({ type: "onAuthorizationTokenChange", payload: { token } })
|
||||
});
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import axios from "axios";
|
||||
import i18next from "i18next";
|
||||
import { fetch as fetchTuitioData } from "@flare/tuitio-client";
|
||||
import { localStorage } from "@flare/js-utils";
|
||||
import { storageKeys } from "./identity";
|
||||
|
||||
const { getItem } = localStorage;
|
||||
|
||||
function getHeaders() {
|
||||
const { token } = fetchTuitioData();
|
||||
const token = getItem(storageKeys.TOKEN);
|
||||
const language = i18next.language;
|
||||
|
||||
return {
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
import { request } from "./axios";
|
||||
import { localStorage } from "@flare/js-utils";
|
||||
|
||||
const { setItem, getItem, removeItem } = localStorage;
|
||||
|
||||
const storageKeys = {
|
||||
TOKEN: "AUTHORIZATION_TOKEN",
|
||||
USER: "USER_NAME"
|
||||
};
|
||||
|
||||
const authenticate = async (userName, password) => {
|
||||
const urlTemplate = process.env.REACT_APP_IDENTITY_AUTHENTICATION_URL;
|
||||
const url = urlTemplate
|
||||
.replace("{username}", userName)
|
||||
.replace("{password}", password);
|
||||
const options = {
|
||||
method: "post"
|
||||
};
|
||||
|
||||
const response = await request(url, options);
|
||||
if (response.status === "SUCCESS") {
|
||||
setItem(storageKeys.TOKEN, response.token);
|
||||
setItem(storageKeys.USER, userName);
|
||||
}
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
const invalidate = () => {
|
||||
const token = getItem(storageKeys.TOKEN);
|
||||
if (token) {
|
||||
removeItem(storageKeys.TOKEN);
|
||||
removeItem(storageKeys.USER);
|
||||
}
|
||||
};
|
||||
|
||||
export { storageKeys, authenticate, invalidate };
|
Loading…
Reference in New Issue