Compare commits

...

6 Commits

Author SHA1 Message Date
Tudor Stanciu f6fbdd94f7 @flare/tuitio-client-react 2023-02-15 01:25:18 +02:00
Tudor Stanciu 4ad8c1657d license 2023-02-14 01:45:11 +02:00
Tudor Stanciu 79c71f86b8 login with enter key 2023-02-14 01:44:07 +02:00
Tudor Stanciu 7f6056baf4 @flare/tuitio-react-client 2023-02-14 01:36:06 +02:00
Tudor Stanciu e1c4b2ca04 removed REACT_APP_NETWORK_RESURRECTOR_SERVER_URL 2023-02-07 00:45:03 +02:00
Tudor Stanciu 3f94695d79 tuitio 2023-02-05 00:44:52 +02:00
16 changed files with 434 additions and 507 deletions

9
.env
View File

@ -1,9 +1,8 @@
#REACT_APP_IDENTITY_AUTHENTICATION_URL=http://localhost:5063/identity/authenticate?UserName={username}&Password={password} #REACT_APP_TUITIO_URL=http://localhost:5063/identity/authenticate?UserName={username}&Password={password}
REACT_APP_IDENTITY_AUTHENTICATION_URL=https://lab.code-rove.com/identity-server-api/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=http://localhost:5064
#REACT_APP_NETWORK_RESURRECTOR_SERVER_URL=http://localhost:5062 REACT_APP_NETWORK_RESURRECTOR_API_URL=https://lab.code-rove.com/network-resurrector-api
#REACT_APP_NETWORK_RESURRECTOR_SERVER_URL=https://lab.code-rove.com/network-resurrector-server-api
#600000 milliseconds = 10 minutes #600000 milliseconds = 10 minutes
REACT_APP_MACHINE_PING_INTERVAL=600000 REACT_APP_MACHINE_PING_INTERVAL=600000

View File

@ -1,5 +1,5 @@
PUBLIC_URL=/network-resurrector/ PUBLIC_URL=/network-resurrector/
REACT_APP_IDENTITY_AUTHENTICATION_URL=https://lab.code-rove.com/tuitio/identity/authenticate?UserName={username}&Password={password} REACT_APP_TUITIO_URL=https://lab.code-rove.com/tuitio
REACT_APP_NETWORK_RESURRECTOR_API_URL=https://lab.code-rove.com/network-resurrector-api REACT_APP_NETWORK_RESURRECTOR_API_URL=https://lab.code-rove.com/network-resurrector-api
#900000 milliseconds = 15 minutes #900000 milliseconds = 15 minutes

22
LICENSE Normal file
View File

@ -0,0 +1,22 @@
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.

692
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,7 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@flare/js-utils": "^1.0.2", "@flare/tuitio-client-react": "^1.0.0",
"@material-ui/core": "^4.11.2", "@material-ui/core": "^4.11.2",
"@material-ui/icons": "^4.11.2", "@material-ui/icons": "^4.11.2",
"@testing-library/jest-dom": "^5.11.6", "@testing-library/jest-dom": "^5.11.6",

View File

@ -2,7 +2,7 @@ import React from "react";
import ApplicationStepper from "./stepper/ApplicationStepper"; import ApplicationStepper from "./stepper/ApplicationStepper";
import Switcher from "./Switcher"; import Switcher from "./Switcher";
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles";
import { useAuthorizationToken } from "../../hooks"; import { useTuitioToken } from "@flare/tuitio-client-react";
import LoginContainer from "../../features/login/components/LoginContainer"; import LoginContainer from "../../features/login/components/LoginContainer";
const useStyles = makeStyles(() => ({ const useStyles = makeStyles(() => ({
@ -16,7 +16,7 @@ const useStyles = makeStyles(() => ({
const Main = () => { const Main = () => {
const classes = useStyles(); const classes = useStyles();
const { validateToken } = useAuthorizationToken(); const { validate: validateToken } = useTuitioToken();
const tokenIsValid = validateToken(); const tokenIsValid = validateToken();
return ( return (

View File

@ -1,4 +1,4 @@
import React, { useState, useCallback } from "react"; import React, { useState, useMemo } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles";
import { import {
@ -18,35 +18,29 @@ import LoginComponent from "./LoginComponent";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useToast } from "../../../hooks"; import { useToast } from "../../../hooks";
import styles from "../styles"; import styles from "../styles";
import { useTuitioUser } from "@flare/tuitio-client-react";
const useStyles = makeStyles(styles); const useStyles = makeStyles(styles);
const LoggedInComponent = ({ const LoggedInComponent = ({ credentials, onChange, onLogin, onLogout }) => {
credentials,
token,
onChange,
onLogin,
onLogout
}) => {
const classes = useStyles(); const classes = useStyles();
const { t } = useTranslation(); const { t } = useTranslation();
const [expanded, setExpanded] = useState(false); const [expanded, setExpanded] = useState(false);
const { info } = useToast(); const { info } = useToast();
const { lastLoginDate, userName } = useTuitioUser();
const handleExpandLogin = () => { const handleExpandLogin = () => {
setExpanded(!expanded); setExpanded(!expanded);
}; };
const getTokenValidFrom = useCallback(() => { const loginDate = useMemo(() => {
const tokenValidFrom = token?.validFrom; if (lastLoginDate) {
const valueForDisplay = t("LONG_DATE", { date: lastLoginDate });
if (tokenValidFrom) {
const valueForDisplay = t("LONG_DATE", { date: tokenValidFrom });
return valueForDisplay; return valueForDisplay;
} }
return "N/A"; return "N/A";
}, [token, t]); }, [lastLoginDate, t]);
const handleLogin = async () => { const handleLogin = async () => {
const result = await onLogin(); const result = await onLogin();
@ -65,15 +59,11 @@ const LoggedInComponent = ({
<AccountBox /> <AccountBox />
</Avatar> </Avatar>
} }
title={ title={<strong>{t("Login.Hello", { username: userName })}</strong>}
<strong>
{t("Login.Hello", { username: credentials.userName })}
</strong>
}
subheader={ subheader={
<Tooltip title={t("Login.AuthenticationDate")}> <Tooltip title={t("Login.AuthenticationDate")}>
<Typography variant="caption" display="block"> <Typography variant="caption" display="block">
{getTokenValidFrom()} {loginDate}
</Typography> </Typography>
</Tooltip> </Tooltip>
} }
@ -117,7 +107,6 @@ const LoggedInComponent = ({
LoggedInComponent.propTypes = { LoggedInComponent.propTypes = {
credentials: PropTypes.object.isRequired, credentials: PropTypes.object.isRequired,
token: PropTypes.object,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
onLogin: PropTypes.func.isRequired, onLogin: PropTypes.func.isRequired,
onLogout: PropTypes.func.isRequired onLogout: PropTypes.func.isRequired

View File

@ -43,6 +43,9 @@ const LoginComponent = ({ credentials, onChange, onLogin }) => {
className={classes.field} className={classes.field}
onChange={onChange("password")} onChange={onChange("password")}
value={credentials.password} value={credentials.password}
onKeyDown={e => {
if (e.key === "Enter") onLogin();
}}
/> />
</CardContent> </CardContent>
<CardActions> <CardActions>

View File

@ -1,59 +1,49 @@
import React, { useContext } from "react"; import React, { useState } from "react";
import LoginCard from "./LoginCard"; import LoginCard from "./LoginCard";
import { authenticate, invalidate } from "../../../utils/identity"; import { useToast } from "../../../hooks";
import {
ApplicationStateContext,
ApplicationDispatchContext
} from "../../../state/ApplicationContexts";
import { useToast, useAuthorizationToken } from "../../../hooks";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import LoggedInComponent from "./LoggedInComponent"; import LoggedInComponent from "./LoggedInComponent";
import { useTuitioClient, useTuitioToken } from "@flare/tuitio-client-react";
const LoginContainer = () => { const LoginContainer = () => {
const state = useContext(ApplicationStateContext); const [credentials, setCredentials] = useState({
const dispatchActions = useContext(ApplicationDispatchContext); userName: "",
password: ""
});
const { error } = useToast(); const { error } = useToast();
const { t } = useTranslation(); const { t } = useTranslation();
const { tokenIsValid, invalidateToken, getToken } = useAuthorizationToken(); const { login, logout } = useTuitioClient({
onLoginFailed: response => error(t("Login.IncorrectCredentials")),
onLoginError: err => error(err.message)
});
const { valid: tokenIsValid } = useTuitioToken();
const handleChange = prop => event => { const handleChange = prop => event => {
dispatchActions.onCredentialsChange(prop, event.target.value); setCredentials(prev => ({ ...prev, [prop]: event.target.value }));
}; };
const handleLogin = async () => { const handleLogin = () => {
const { userName, password } = state.credentials; const { userName, password } = credentials;
return login(userName, password);
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 = () => { const handleLogout = () => {
invalidate(); logout();
invalidateToken();
}; };
return ( return (
<> <>
{tokenIsValid ? ( {tokenIsValid ? (
<LoggedInComponent <LoggedInComponent
credentials={state.credentials} credentials={credentials}
token={getToken()}
onChange={handleChange} onChange={handleChange}
onLogin={handleLogin} onLogin={handleLogin}
onLogout={handleLogout} onLogout={handleLogout}
/> />
) : ( ) : (
<LoginCard <LoginCard
credentials={state.credentials} credentials={credentials}
onChange={handleChange} onChange={handleChange}
onLogin={handleLogin} onLogin={handleLogin}
/> />

View File

@ -1,2 +1 @@
export { useToast } from "./useToast"; export { useToast } from "./useToast";
export { useAuthorizationToken } from "./useAuthorizationToken";

View File

@ -1,29 +0,0 @@
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 };
};

View File

@ -4,11 +4,14 @@ import "./index.css";
import "./utils/i18n"; import "./utils/i18n";
import App from "./components/App"; import App from "./components/App";
import { BrowserRouter as Router } from "react-router-dom"; import { BrowserRouter as Router } from "react-router-dom";
import { TuitioProvider } from "@flare/tuitio-client-react";
ReactDOM.render( ReactDOM.render(
<Router basename={process.env.PUBLIC_URL || ""}> <Router basename={process.env.PUBLIC_URL || ""}>
<Suspense fallback={<div>Loading...</div>}> <Suspense fallback={<div>Loading...</div>}>
<TuitioProvider tuitioUrl={process.env.REACT_APP_TUITIO_URL}>
<App /> <App />
</TuitioProvider>
</Suspense> </Suspense>
</Router>, </Router>,
document.getElementById("root") document.getElementById("root")

View File

@ -1,20 +1,4 @@
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 = { export const initialState = {
credentials: {
userName: userName || "",
password: ""
},
security: {
authorization: {
token
}
},
network: { network: {
machines: Object.assign([], { loaded: false }), machines: Object.assign([], { loaded: false }),
test: "" test: ""

View File

@ -1,15 +1,5 @@
export function reducer(state, action) { export function reducer(state, action) {
switch (action.type) { switch (action.type) {
case "onCredentialsChange": {
const { prop, value } = action.payload;
return {
...state,
credentials: {
...state.credentials,
[prop]: value
}
};
}
case "onNetworkChange": { case "onNetworkChange": {
const { prop, value } = action.payload; const { prop, value } = action.payload;
return { return {
@ -20,19 +10,6 @@ export function reducer(state, action) {
} }
}; };
} }
case "onAuthorizationTokenChange": {
const { token } = action.payload;
return {
...state,
security: {
...state.security,
authorization: {
...state.security.authorization,
token
}
}
};
}
default: { default: {
return state; return state;
} }
@ -40,10 +17,6 @@ export function reducer(state, action) {
} }
export const dispatchActions = dispatch => ({ export const dispatchActions = dispatch => ({
onCredentialsChange: (prop, value) =>
dispatch({ type: "onCredentialsChange", payload: { prop, value } }),
onNetworkChange: (prop, value) => onNetworkChange: (prop, value) =>
dispatch({ type: "onNetworkChange", payload: { prop, value } }), dispatch({ type: "onNetworkChange", payload: { prop, value } })
onAuthorizationTokenChange: token =>
dispatch({ type: "onAuthorizationTokenChange", payload: { token } })
}); });

View File

@ -1,12 +1,9 @@
import axios from "axios"; import axios from "axios";
import i18next from "i18next"; import i18next from "i18next";
import { localStorage } from "@flare/js-utils"; import { fetch as fetchTuitioData } from "@flare/tuitio-client";
import { storageKeys } from "./identity";
const { getItem } = localStorage;
function getHeaders() { function getHeaders() {
const token = getItem(storageKeys.TOKEN); const { token } = fetchTuitioData();
const language = i18next.language; const language = i18next.language;
return { return {

View File

@ -1,37 +0,0 @@
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 };