React (#247) @Franpastoragusti

Thanks to @Franpastoragusti for the amazing app https://github.com/Franpastoragusti/oidc-react-app
This commit is contained in:
Ivan Paulovich 2020-11-01 15:00:15 +01:00 committed by GitHub
parent 381fd74c9d
commit 3f7021e775
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 633 additions and 467 deletions

11
.vscode/launch.json vendored
View File

@ -5,8 +5,8 @@
"version": "0.2.0",
"compounds": [
{
"name": "Identity Server & Accounts API & SPA",
"configurations": ["Accounts API", "Identity Server", "SPA"]
"name": "Solution",
"configurations": ["Accounts API", "Identity Server", "Start SPA", "Debug SPA"]
}
],
"configurations": [
@ -30,7 +30,7 @@
"ASPNETCORE_URLS": "https://localhost:5000",
"ASPNETCORE_HTTPS_PORT": "5000",
"IDENTITY_SERVER_ORIGIN": "https://localhost:5000",
"RedirectUris": "https://localhost:5002/callback",
"RedirectUris": "https://localhost:5002/signin-oidc",
"PostLogoutRedirectUris": "https://localhost:5002",
"AllowedCorsOrigins": "https://localhost:5002"
},
@ -65,7 +65,7 @@
}
},
{
"name": "SPA",
"name": "Start SPA",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}/wallet-spa",
@ -77,7 +77,8 @@
"request": "launch",
"name": "Debug SPA",
"url": "https://localhost:5002",
"webRoot": "${workspaceFolder}/wallet-spa"
"webRoot": "${workspaceFolder}/wallet-spa",
"runtimeExecutable": "canary"
},
{
"name": "Accounts API (Docker)",

View File

@ -1,6 +1,14 @@
REACT_APP_AUTHORITY=https://localhost:5000
REACT_APP_REDIRECT_URI=https://localhost:5002/callback
REACT_APP_POST_LOGOUT_REDIRECT_URI=https://localhost:5002
REACT_APP_ACCOUNTS_API=https://localhost:5001
HTTPS=true
PORT=5002
REACT_APP_ACCOUNTS_API=https://localhost:5001
REACT_APP_AUDIENCE=https://localhost:5000
REACT_APP_AUTH_URL=https://localhost:5000
REACT_APP_AUTHORITY=https://localhost:5000
REACT_APP_IDENTITY_CLIENT_ID=spa
REACT_APP_ISSUER=https://localhost:5000
REACT_APP_LOGOFF_REDIRECT_URL=https://localhost:5002/logout
REACT_APP_POST_LOGOUT_REDIRECT_URI=https://localhost:5002/logout/callback
REACT_APP_REDIRECT_URI=https://localhost:5002/signin-oidc
REACT_APP_REDIRECT_URL=https://localhost:5002/signin-oidc
REACT_APP_SCOPE=openid profile api1.full_access
REACT_APP_SILENT_REDIRECT_URL=https://localhost:5002/silentrenew

View File

@ -1,18 +1,14 @@
import "./App.css";
import React from "react";
import { Component } from "react";
import Layout from "./components/Layout";
import React, { Component } from "react";
import { AuthProvider } from "./providers/authProvider";
import { BrowserRouter } from "react-router-dom";
import { Routes } from "./routes/routes";
export default class App extends Component {
static displayName = App.name;
constructor(props) {
super(props)
}
render() {
return (
<Layout openIdManager={this.props.openIdManager}/>
<AuthProvider>
<BrowserRouter children={Routes} basename={"/"} />
</AuthProvider>
)
};
}

View File

@ -1,9 +0,0 @@
import React from 'react';
import { render } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
const { getByText } = render(<App />);
const linkElement = getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

View File

@ -95,10 +95,6 @@ const styles = (theme) => ({
class Header extends React.Component {
constructor(props) {
super(props)
}
render() {
const { handleChangeNavDrawer, navDrawerOpen, classes } = this.props

View File

@ -6,7 +6,7 @@ import SideMenu from "../components/SideMenu";
import Footer from "../components/Footer";
import MainContent from "../components/MainContent";
import { ThemeProvider } from "@material-ui/core/styles";
import defaultTheme, { customTheme } from "../theme";
import defaultTheme from "../theme";
import classNames from "classnames";
const styles = () => ({
@ -69,10 +69,9 @@ class Layout extends React.Component {
return (
<ThemeProvider theme={theme}>
<Header handleChangeNavDrawer={this.handleChangeNavDrawer} navDrawerOpen={navDrawerOpen} />
<SideMenu openIdManager={this.props.openIdManager} navDrawerOpen={navDrawerOpen}
handleChangeNavDrawer={this.handleChangeNavDrawer} />
<SideMenu navDrawerOpen={navDrawerOpen} handleChangeNavDrawer={this.handleChangeNavDrawer} />
<div className={classNames(classes.container, !navDrawerOpen && classes.containerFull)}>
<MainContent openIdManager={this.props.openIdManager} />
<MainContent />
</div>
<Footer />
</ThemeProvider>

View File

@ -6,7 +6,6 @@ import {
import { Container } from 'reactstrap';
import Accounts from "../pages/Accounts";
import Account from "../pages/Account";
import { AuthCallback } from "../pages/AuthCallback";
import OpenAccount from "../pages/OpenAccount";
import Deposit from "../pages/Deposit";
import Withdraw from "../pages/Withdraw";
@ -41,68 +40,17 @@ const styles = theme => ({
});
class MainContent extends Component {
constructor(props) {
super(props);
}
render() {
return (
<Container>
<Route
exact
path="/OpenAccount"
render={() => {
return <OpenAccount openIdManager={this.props.openIdManager} />;
}}
/>
<Route
exact
path="/"
render={() => {
return <Accounts openIdManager={this.props.openIdManager} />;
}}
/>
<Route
exact
path="/accounts/:accountId/close"
render={() => {
return <CloseAccount openIdManager={this.props.openIdManager} />;
}}
/>
<Route
exact
path="/accounts/:accountId/transfer"
render={() => {
return <Transfer openIdManager={this.props.openIdManager} />;
}}
/>
<Route
exact
path="/accounts/:accountId/deposit"
render={() => {
return <Deposit openIdManager={this.props.openIdManager} />;
}}
/>
<Route
exact
path="/accounts/:accountId/withdraw"
render={() => {
return <Withdraw openIdManager={this.props.openIdManager} />;
}}
/>
<Route
exact
path="/accounts/:accountId"
render={() => {
return <Account openIdManager={this.props.openIdManager} />;
}}
/>
<Route
path="/callback"
render={() => {
return <AuthCallback />;
}}
/>
<Route exact={true} path="/dashboard/openaccount" component={OpenAccount} />
<Route exact={true} path="/dashboard/accounts/:accountId/close" component={CloseAccount} />
<Route exact={true} path="/dashboard/accounts/:accountId/transfer" component={Transfer} />
<Route exact={true} path="/dashboard/accounts/:accountId/deposit" component={Deposit} />
<Route exact={true} path="/dashboard/accounts/:accountId/withdraw" component={Withdraw} />
<Route exact={true} path="/dashboard/accounts/:accountId" component={Account} />
<Route exact={true} path="/dashboard" component={Accounts} />
</Container>
)
}

View File

@ -4,16 +4,15 @@ import List from "@material-ui/core/List";
import ListItem from "@material-ui/core/ListItem";
import ListItemIcon from "@material-ui/core/ListItemIcon";
import ListItemText from "@material-ui/core/ListItemText";
import AccountCircle from "@material-ui/icons/AccountCircle";
import HomeIcon from '@material-ui/icons/Home';
import AccountBalanceIcon from '@material-ui/icons/AccountBalance';
import ExitToApp from "@material-ui/icons/ExitToApp";
import AddIcon from '@material-ui/icons/Add';
import { Avatar } from "@material-ui/core";
import { Grid } from "@material-ui/core";
import { withStyles } from "@material-ui/core/styles";
import classNames from "classnames";
import { fade } from "@material-ui/core/styles/colorManipulator";
import { AuthConsumer } from "../providers/authProvider"
import AuthService from "../store/authService";
const drawStyles = theme => {
return {
@ -80,17 +79,6 @@ const drawStyles = theme => {
color: "white",
boxShadow: "rgba(0, 0, 0, 0.16) 0px 3px 10px, rgba(0, 0, 0, 0.23) 0px 3px 10px"
},
menuItem: {
padding: "10px 16px",
color: "white",
fontSize: 14,
"&:focus": {
backgroundColor: theme.palette.primary.main,
"& .MuiListItemIcon-root, & .MuiListItemText-primary": {
color: theme.palette.common.white
}
}
},
miniMenuItem: {
color: "white",
margin: "10px 0",
@ -114,31 +102,33 @@ const drawStyles = theme => {
};
class SideMenu extends Component {
authService;
constructor(props) {
super(props);
this.state = {
isLoggedIn: false,
user: null
user: {
profile: {
name: ""
}
}
}
this.performLogin = this.performLogin.bind(this);
this.performLogout = this.performLogout.bind(this);
this.authService = new AuthService();
}
componentDidMount() {
this.props.openIdManager.getUser().then((user) => {
if (user) {
this.setState({ isLoggedIn: true, user: user });
} else {
this.setState({ isLoggedIn: false, user: null });
}
});
this.authService
.getUser()
.then((user) => {
this.setState({ user: user });
});
}
render() {
const { user } = this.state;
const { classes, navDrawerOpen, handleChangeNavDrawer } = this.props;
const { classes, navDrawerOpen } = this.props;
return (
<div>
<Drawer
@ -146,17 +136,38 @@ class SideMenu extends Component {
variant="permanent"
classes={{
paper: classNames(classes.drawerPaper, !navDrawerOpen && classes.drawerPaperClose)
}}
>
}} >
<div>
<div className={classes.logo}>My Wallet</div>
{this.state.isLoggedIn && <div className={classNames(classes.avatarRoot, !navDrawerOpen && classes.avatarRootMini)}>
<div className={classNames(classes.avatarRoot, !navDrawerOpen && classes.avatarRootMini)}>
<Avatar size={navDrawerOpen ? 48 : 32} classes={{ root: classes.avatarIcon }} />
<span className={classes.avatarSpan}>{user.profile.name}</span>
</div>}
<span className={classes.avatarSpan}>{this.state.user.profile.name}</span>
</div>
<List>
{this.state.isLoggedIn ? this.renderWhenTrue() : this.renderWhenFalse()}
<ListItem button component="a" href="/Dashboard">
<ListItemIcon style={{ color: "white" }}>
<AccountBalanceIcon />
</ListItemIcon>
<ListItemText primary="My Accounts" />
</ListItem>
<ListItem button component="a" href="/Dashboard/OpenAccount">
<ListItemIcon style={{ color: "white" }}>
<AddIcon />
</ListItemIcon>
<ListItemText primary="Open an Account" />
</ListItem>
<ListItem button>
<ListItemIcon style={{ color: "white" }}>
<ExitToApp />
</ListItemIcon>
<AuthConsumer>
{({ logout }) => {
return (
<ListItemText primary="Sign Out" onClick={logout} />
);
}}
</AuthConsumer>
</ListItem>
</List>
</div>
@ -164,52 +175,6 @@ class SideMenu extends Component {
</div>
);
}
renderWhenTrue() {
return (
<React.Fragment>
<ListItem button component="a" href="/">
<ListItemIcon style={{ color: "white" }}>
<AccountBalanceIcon />
</ListItemIcon>
<ListItemText primary="My Accounts" />
</ListItem>
<ListItem button component="a" href="/OpenAccount">
<ListItemIcon style={{ color: "white" }}>
<AddIcon />
</ListItemIcon>
<ListItemText primary="Open an Account" />
</ListItem>
<ListItem button>
<ListItemIcon style={{ color: "white" }}>
<ExitToApp />
</ListItemIcon>
<ListItemText primary="Sign Out" onClick={this.performLogout} />
</ListItem>
</React.Fragment>
)
}
renderWhenFalse() {
return (
<React.Fragment>
<ListItem button>
<ListItemIcon style={{ color: "white" }}>
<AccountCircle />
</ListItemIcon>
<ListItemText primary="Sign In" onClick={this.performLogin} />
</ListItem>
</React.Fragment>
)
}
performLogin() {
this.props.openIdManager.signinRedirect();
}
performLogout() {
this.props.openIdManager.signoutRedirect();
}
}
export default withStyles(drawStyles, { withTheme: true })(SideMenu);

View File

@ -0,0 +1,12 @@
import * as React from "react";
import { AuthConsumer } from "../../providers/authProvider";
export const Callback = () => (
<AuthConsumer>
{({ signinRedirectCallback }) => {
signinRedirectCallback();
return <span>loading</span>;
}}
</AuthConsumer>
);

View File

@ -0,0 +1,11 @@
import * as React from "react";
import { AuthConsumer } from "../../providers/authProvider";
export const Logout = () => (
<AuthConsumer>
{({ logout }) => {
logout();
return <span>loading</span>;
}}
</AuthConsumer>
);

View File

@ -0,0 +1,11 @@
import React from "react";
import { AuthConsumer } from "../../providers/authProvider";
export const LogoutCallback = () => (
<AuthConsumer>
{({ signoutRedirectCallback }) => {
signoutRedirectCallback();
return <span>loading</span>;
}}
</AuthConsumer>
);

View File

@ -0,0 +1,9 @@
import React from 'react';
export const Register = () => {
return (
<div>
register
</div>
);
};

View File

@ -0,0 +1,12 @@
import React from "react";
import { AuthConsumer } from "../../providers/authProvider";
export const SilentRenew = () => (
<AuthConsumer>
{({ signinSilentCallback }) => {
signinSilentCallback();
return <span>loading</span>;
}}
</AuthConsumer>
);

View File

@ -0,0 +1,8 @@
import React from 'react';
import Layout from './Layout'
export const PrivatePage = () => {
return (
<Layout />
);
};

View File

@ -0,0 +1,54 @@
import React from "react";
import {
Button,
Grid,
Paper,
AppBar,
Typography,
Toolbar,
} from "@material-ui/core";
import { AuthConsumer } from "../providers/authProvider"
export const PublicPage = () => (
<div>
<AppBar position="static" alignitems="center" color="primary">
<Toolbar>
<Grid container justify="center" wrap="wrap">
<Grid item>
<Typography variant="h6">Clean Architecture Manga</Typography>
</Grid>
</Grid>
</Toolbar>
</AppBar>
<Grid container spacing={0} justify="center" direction="row">
<Grid item>
<Grid
container
direction="column"
justify="center"
spacing={2}
className="login-form"
>
<Paper
variant="elevation"
elevation={2}
className="login-background"
>
<Grid item>
<AuthConsumer>
{({ signinRedirect }) => {
return <Button variant="contained" color="primary" type="submit" className="button-block" onClick={signinRedirect}>Sign In</Button>;
}}
</AuthConsumer>
</Grid>
</Paper>
</Grid>
</Grid>
</Grid>
</div>
);

View File

@ -1,31 +1,11 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
import * as Oidc from "oidc-client";
import "./styles.scss";
import "font-awesome/css/font-awesome.css";
const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href');
const rootElement = document.getElementById('root');
const config = {
authority: process.env.REACT_APP_AUTHORITY,
client_id: "spa",
redirect_uri: process.env.REACT_APP_REDIRECT_URI,
response_type: "code",
scope: "openid profile api1.full_access",
post_logout_redirect_uri: process.env.REACT_APP_POST_LOGOUT_REDIRECT_URI,
};
const openIdManager = new Oidc.UserManager(config);
ReactDOM.render(
<BrowserRouter basename={baseUrl}>
<App openIdManager={openIdManager} />
</BrowserRouter>,
rootElement);
ReactDOM.render(<App />, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.

View File

@ -33,19 +33,15 @@ class Account extends React.Component {
}
fetchData = id => {
this.props.openIdManager.getUser().then((user) => {
if (user) {
accountsService
.getAccount(user, id)
.then((response) => {
this.setState(response.data);
console.log(response.data);
})
.catch((e) => {
console.log(e);
});
}
});
accountsService
.getAccount(id)
.then((response) => {
this.setState(response.data);
console.log(response.data);
})
.catch((e) => {
console.log(e);
});
};
componentDidMount() {
@ -166,19 +162,19 @@ class Account extends React.Component {
<div style={styles.buttons}>
<Button component={Link} to={`/accounts/${this.state.accountId}/Deposit`} variant="contained" color="primary">
<Button component={Link} to={`/dashboard/accounts/${this.state.accountId}/Deposit`} variant="contained" color="primary">
Deposit
</Button>
<Button component={Link} to={`/accounts/${this.state.accountId}/Withdraw`} variant="contained" color="primary">
<Button component={Link} to={`/dashboard/accounts/${this.state.accountId}/Withdraw`} variant="contained" color="primary">
Withdraw
</Button>
<Button component={Link} to={`/accounts/${this.state.accountId}/Transfer`} variant="contained" color="primary">
<Button component={Link} to={`/dashboard/accounts/${this.state.accountId}/Transfer`} variant="contained" color="primary">
Transfer
</Button>
<Button component={Link} to={`/accounts/${this.state.accountId}/Close`} variant="contained" color="secondary">
<Button component={Link} to={`/dashboard/accounts/${this.state.accountId}/Close`} variant="contained" color="secondary">
Close
</Button>

View File

@ -20,22 +20,18 @@ class Accounts extends React.Component {
this.state = {
accounts: []
}
}
componentDidMount() {
this.props.openIdManager.getUser().then((user) => {
if (user) {
accountsService
.getAccounts(user)
.then((response) => {
this.setState(response.data);
console.log(response.data);
})
.catch((e) => {
console.log(e);
});
}
});
accountsService
.getAccounts()
.then((response) => {
this.setState(response.data);
console.log(response.data);
})
.catch((e) => {
console.log(e);
});
}
render() {
@ -60,7 +56,7 @@ class Accounts extends React.Component {
this.state.accounts.map((account) => {
return (
<TableRow key={account.accountId}>
<TableCell><a className="text-dark" href={`/accounts/${account.accountId}`}>{account.accountId}</a></TableCell>
<TableCell><a className="text-dark" href={`/dashboard/accounts/${account.accountId}`}>{account.accountId}</a></TableCell>
<TableCell>{account.currentBalance}</TableCell>
<TableCell>{account.currency}</TableCell>
</TableRow>

View File

@ -1,31 +0,0 @@
import React, { PureComponent } from "react";
import * as Oidc from "oidc-client";
import { Redirect } from "react-router-dom";
export class AuthCallback extends PureComponent {
constructor(props) {
super(props);
this.state = {
redirect: false,
};
}
componentDidMount() {
new Oidc.UserManager({ response_mode: "query" })
.signinRedirectCallback()
.then(() => {
this.setState({ redirect: true });
})
.catch(function (e) {
console.error(e);
});
}
render() {
if (this.state.redirect) {
return <Redirect to="/" push />;
}
return <div>Redirecting...</div>;
}
}

View File

@ -5,6 +5,7 @@ import PageBase from "../components/PageBase";
import { Link } from "react-router-dom";
import Button from "@material-ui/core/Button";
import { grey } from "@material-ui/core/colors";
import {Redirect} from "react-router-dom";
class CloseAccount extends React.Component {
@ -34,21 +35,17 @@ class CloseAccount extends React.Component {
};
saveCloseAccount = () => {
this.props.openIdManager.getUser().then((user) => {
if (user) {
accountsService
.closeAccount(user, this.state.accountId)
.then(response => {
this.setState({
accountId: response.data.accountId,
submitted: true
});
})
.catch(e => {
console.log(e);
});
}
})
accountsService
.closeAccount(this.state.accountId)
.then(response => {
this.setState({
accountId: response.data.accountId,
submitted: true
});
})
.catch(e => {
console.log(e);
});
};
newCloseAccount = () => {
@ -83,20 +80,8 @@ class CloseAccount extends React.Component {
<PageBase title="Close Account" navigation="My Accounts / Close Account">
{this.state.submitted ? (
<div>
<div style={styles.buttons}>
<Button
style={styles.saveButton}
variant="contained"
type="submit"
color="primary"
onClick={this.newCloseAccount}
>
Another one
</Button>
</div>
</div>
) : (
<Redirect to={`/dashboard`} push />
) : (
<div style={styles.buttons}>
<Link to="/">
<Button variant="contained">Cancel</Button>

View File

@ -10,6 +10,7 @@ import Select from "@material-ui/core/Select";
import { grey } from "@material-ui/core/colors";
import FormControl from "@material-ui/core/FormControl";
import InputLabel from "@material-ui/core/InputLabel";
import {Redirect} from "react-router-dom";
class Deposit extends React.Component {
@ -43,27 +44,23 @@ class Deposit extends React.Component {
};
saveDeposit = () => {
this.props.openIdManager.getUser().then((user) => {
if (user) {
var bodyFormData = new FormData();
bodyFormData.append('amount', this.state.amount);
bodyFormData.append('currency', this.state.currency);
var bodyFormData = new FormData();
bodyFormData.append('amount', this.state.amount);
bodyFormData.append('currency', this.state.currency);
transactionService
.deposit(user, this.state.accountId, bodyFormData)
.then(response => {
this.setState({
transactionId: response.data.transaction.transactionId,
transactionDate: response.data.transaction.transactionDate,
submitted: true
});
console.log(response.data);
})
.catch(e => {
console.log(e);
});
}
})
transactionService
.deposit(this.state.accountId, bodyFormData)
.then(response => {
this.setState({
transactionId: response.data.transaction.transactionId,
transactionDate: response.data.transaction.transactionDate,
submitted: true
});
console.log(response.data);
})
.catch(e => {
console.log(e);
});
};
newDeposit = () => {
@ -78,7 +75,6 @@ class Deposit extends React.Component {
};
render() {
const styles = {
toggleDiv: {
marginTop: 20,
@ -102,19 +98,7 @@ class Deposit extends React.Component {
<PageBase title="Deposit" navigation="My Accounts / Deposit">
{this.state.submitted ? (
<div>
<div style={styles.buttons}>
<Button
style={styles.saveButton}
variant="contained"
type="submit"
color="primary"
onClick={this.newDeposit}
>
Another one
</Button>
</div>
</div>
<Redirect to={`/dashboard/accounts/${this.state.accountId}`} push />
) : (
<div>

View File

@ -10,6 +10,7 @@ import Select from "@material-ui/core/Select";
import { grey } from "@material-ui/core/colors";
import FormControl from "@material-ui/core/FormControl";
import InputLabel from "@material-ui/core/InputLabel";
import {Redirect} from "react-router-dom";
class OpenAccount extends React.Component {
@ -41,27 +42,23 @@ class OpenAccount extends React.Component {
};
saveAccount = () => {
this.props.openIdManager.getUser().then((user) => {
if (user) {
var bodyFormData = new FormData();
bodyFormData.append('amount', this.state.amount);
bodyFormData.append('currency', this.state.currency);
var bodyFormData = new FormData();
bodyFormData.append('amount', this.state.amount);
bodyFormData.append('currency', this.state.currency);
accountsService
.openAccount(user, bodyFormData)
.then(response => {
this.setState({
id: response.data.account.accountId,
title: response.data.account.accountId,
description: response.data.account.accountId,
submitted: true
});
})
.catch(e => {
console.log(e);
});
}
})
accountsService
.openAccount(bodyFormData)
.then(response => {
this.setState({
id: response.data.account.accountId,
title: response.data.account.accountId,
description: response.data.account.accountId,
submitted: true
});
})
.catch(e => {
console.log(e);
});
};
newAccount = () => {
@ -98,20 +95,8 @@ class OpenAccount extends React.Component {
<PageBase title="Open a New Account" navigation="My Accounts / Open a New Account">
{this.state.submitted ? (
<div>
<div style={styles.buttons}>
<Button
style={styles.saveButton}
variant="contained"
type="submit"
color="primary"
onClick={this.newAccount}
>
Another one
</Button>
</div>
</div>
) : (
<Redirect to={`/dashboard`} push />
) : (
<div>
<TextField

View File

@ -10,6 +10,7 @@ import Select from "@material-ui/core/Select";
import { grey } from "@material-ui/core/colors";
import FormControl from "@material-ui/core/FormControl";
import InputLabel from "@material-ui/core/InputLabel";
import {Redirect} from "react-router-dom";
class Transfer extends React.Component {
@ -44,26 +45,22 @@ class Transfer extends React.Component {
};
saveTransfer = () => {
this.props.openIdManager.getUser().then((user) => {
if (user) {
var bodyFormData = new FormData();
bodyFormData.append('amount', this.state.amount);
bodyFormData.append('currency', this.state.currency);
var bodyFormData = new FormData();
bodyFormData.append('amount', this.state.amount);
bodyFormData.append('currency', this.state.currency);
transactionService
.transfer(user, this.state.accountId, this.state.destinationAccountId, bodyFormData)
.then(response => {
this.setState({
transactionId: response.data.transaction.transactionId,
transactionDate: response.data.transaction.transactionDate,
submitted: true
});
})
.catch(e => {
console.log(e);
});
}
})
transactionService
.transfer(this.state.accountId, this.state.destinationAccountId, bodyFormData)
.then(response => {
this.setState({
transactionId: response.data.transaction.transactionId,
transactionDate: response.data.transaction.transactionDate,
submitted: true
});
})
.catch(e => {
console.log(e);
});
};
newTransfer = () => {
@ -103,20 +100,8 @@ class Transfer extends React.Component {
<PageBase title="Transfer" navigation="My Accounts / Transfer">
{this.state.submitted ? (
<div>
<div style={styles.buttons}>
<Button
style={styles.saveButton}
variant="contained"
type="submit"
color="primary"
onClick={this.newTransfer}
>
Another one
</Button>
</div>
</div>
) : (
<Redirect to={`/dashboard/accounts/${this.state.accountId}`} push />
) : (
<div>
<TextField

View File

@ -10,6 +10,7 @@ import Select from "@material-ui/core/Select";
import { grey } from "@material-ui/core/colors";
import FormControl from "@material-ui/core/FormControl";
import InputLabel from "@material-ui/core/InputLabel";
import {Redirect} from "react-router-dom";
class Withdraw extends React.Component {
@ -43,27 +44,23 @@ class Withdraw extends React.Component {
};
saveWithdraw = () => {
this.props.openIdManager.getUser().then((user) => {
if (user) {
var bodyFormData = new FormData();
bodyFormData.append('amount', this.state.amount);
bodyFormData.append('currency', this.state.currency);
var bodyFormData = new FormData();
bodyFormData.append('amount', this.state.amount);
bodyFormData.append('currency', this.state.currency);
transactionService
.withdraw(user, this.state.accountId, bodyFormData)
.then(response => {
this.setState({
transactionId: response.data.transaction.transactionId,
transactionDate: response.data.transaction.transactionDate,
submitted: true
});
console.log(response.data);
})
.catch(e => {
console.log(e);
});
}
})
transactionService
.withdraw(this.state.accountId, bodyFormData)
.then(response => {
this.setState({
transactionId: response.data.transaction.transactionId,
transactionDate: response.data.transaction.transactionDate,
submitted: true
});
console.log(response.data);
})
.catch(e => {
console.log(e);
});
};
newWithdraw = () => {
@ -102,20 +99,8 @@ class Withdraw extends React.Component {
<PageBase title="Withdraw" navigation="My Accounts / Withdraw">
{this.state.submitted ? (
<div>
<div style={styles.buttons}>
<Button
style={styles.saveButton}
variant="contained"
type="submit"
color="primary"
onClick={this.newWithdraw}
>
Another one
</Button>
</div>
</div>
) : (
<Redirect to={`/dashboard/accounts/${this.state.accountId}`} push />
) : (
<div>
<TextField

View File

@ -0,0 +1,25 @@
import React, { Component } from "react";
import AuthService from "../store/authService";
const AuthContext = React.createContext({
signinRedirectCallback: () => ({}),
logout: () => ({}),
signoutRedirectCallback: () => ({}),
isAuthenticated: () => ({}),
signinRedirect: () => ({}),
signinSilentCallback: () => ({}),
createSigninRequest: () => ({})
});
export const AuthConsumer = AuthContext.Consumer;
export class AuthProvider extends Component {
authService;
constructor(props) {
super(props);
this.authService = new AuthService();
}
render() {
return <AuthContext.Provider value={this.authService}>{this.props.children}</AuthContext.Provider>;
}
}

View File

@ -0,0 +1,20 @@
import React from "react";
import { Route } from "react-router-dom";
import { AuthConsumer } from "../providers/authProvider";
export const PrivateRoute = ({ component, ...rest }) => {
const renderFn = Component => props => (
<AuthConsumer>
{({ isAuthenticated, signinRedirect }) => {
if (!!Component && isAuthenticated()) {
return <Component {...props} />;
} else {
signinRedirect();
return <span>loading</span>;
}
}}
</AuthConsumer>
);
return <Route {...rest} render={renderFn(component)} />;
};

View File

@ -0,0 +1,22 @@
import * as React from "react";
import { Route, Switch } from "react-router-dom";
import { Callback } from "../components/auth/callback";
import { Logout } from "../components/auth/logout";
import { LogoutCallback } from "../components/auth/logoutCallback";
import { PrivateRoute } from "./privateRoute";
import { Register } from "../components/auth/register";
import { SilentRenew } from "../components/auth/silentRenew";
import { PublicPage } from "../components/publicPage";
import { PrivatePage } from "../components/privatePage";
export const Routes = (
<Switch>
<Route exact={true} path="/signin-oidc" component={Callback} />
<Route exact={true} path="/logout" component={Logout} />
<Route exact={true} path="/logout/callback" component={LogoutCallback} />
<Route exact={true} path="/register" component={Register} />
<Route exact={true} path="/silentrenew" component={SilentRenew} />
<PrivateRoute path="/dashboard" component={PrivatePage} />
<Route path="/" component={PublicPage} />
</Switch>
);

View File

@ -1,5 +0,0 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect';

View File

@ -1,26 +1,22 @@
import http from "./http-common"
import api from "./oauth"
const getAccounts = (user) => {
return http
.createAxios(user)
const getAccounts = () => {
return api
.get("/api/v1/accounts");
};
const getAccount = (user, id) => {
return http
.createAxios(user)
const getAccount = (id) => {
return api
.get(`/api/v1/accounts/${id}`);
};
const openAccount = (user, data) => {
return http
.createAxios(user)
const openAccount = (data) => {
return api
.post("/api/v1/accounts", data);
};
const closeAccount = (user, id) => {
return http
.createAxios(user)
const closeAccount = (id) => {
return api
.delete(`/api/v1/accounts/${id}`);
};

View File

@ -0,0 +1,106 @@
import { IDENTITY_CONFIG, METADATA_OIDC } from "../utils/authConst";
import { UserManager, WebStorageStateStore, Log } from "oidc-client";
export default class AuthService {
UserManager;
constructor() {
this.UserManager = new UserManager({
...IDENTITY_CONFIG,
userStore: new WebStorageStateStore({ store: window.sessionStorage }),
metadata: {
...METADATA_OIDC,
},
});
// Logger
Log.logger = console;
Log.level = Log.DEBUG;
this.UserManager.events.addUserLoaded((user) => {
if (window.location.href.indexOf("signin-oidc") !== -1) {
this.navigateToScreen();
}
});
this.UserManager.events.addSilentRenewError((e) => {
console.log("silent renew error", e.message);
});
this.UserManager.events.addAccessTokenExpired(() => {
console.log("token expired");
this.logout();
});
}
signinRedirectCallback = () => {
this.UserManager.signinRedirectCallback()
.then((user) => {
})
.catch(function (e) {
console.error(e);
});
};
getUser = async () => {
const user = await this.UserManager.getUser();
if (!user) {
return await this.UserManager.signinRedirectCallback();
}
return user;
};
parseJwt = (token) => {
const base64Url = token.split(".")[1];
const base64 = base64Url.replace("-", "+").replace("_", "/");
return JSON.parse(window.atob(base64));
};
signinRedirect = () => {
localStorage.setItem("redirectUri", window.location.pathname);
this.UserManager.signinRedirect({});
};
navigateToScreen = () => {
window.location.replace("/dashboard");
};
isAuthenticated = () => {
const oidcStorage = JSON.parse(
sessionStorage.getItem(
`oidc.user:${process.env.REACT_APP_AUTH_URL}:${process.env.REACT_APP_IDENTITY_CLIENT_ID}`
)
);
return !!oidcStorage && !!oidcStorage.access_token;
};
signinSilent = () => {
this.UserManager.signinSilent()
.then((user) => {
console.log("signed in", user);
})
.catch((err) => {
console.log(err);
});
};
signinSilentCallback = () => {
this.UserManager.signinSilentCallback();
};
createSigninRequest = () => {
return this.UserManager.createSigninRequest();
};
logout = () => {
this.UserManager.signoutRedirect({
id_token_hint: localStorage.getItem("id_token"),
});
this.UserManager.clearStaleState();
};
signoutRedirectCallback = () => {
this.UserManager.signoutRedirectCallback().then(() => {
localStorage.clear();
window.location.replace(process.env.REACT_APP_PUBLIC_URL);
});
this.UserManager.clearStaleState();
};
}

View File

@ -1,15 +0,0 @@
import axios from "axios";
const createAxios = user => {
return axios.create({
baseURL: process.env.REACT_APP_ACCOUNTS_API,
headers: {
'Authorization': 'Bearer ' + user.access_token,
'Content-Type': 'multipart/form-data'
}
})
};
export default {
createAxios
};

View File

@ -0,0 +1,29 @@
import axios from "axios";
const api = axios.create({
headers: {
'Content-Type': 'application/json'
},
baseURL: "https://localhost:5001/"
});
api.interceptors.request.use(
function (config) {
const oidcStorage = JSON.parse(
window.sessionStorage.getItem(
`oidc.user:${process.env.REACT_APP_AUTH_URL}:${process.env.REACT_APP_IDENTITY_CLIENT_ID}`
)
);
if (!!oidcStorage && !!oidcStorage.access_token) {
config.headers.Authorization = `Bearer ${oidcStorage.access_token}`;
}
return config;
},
function (err) {
return Promise.reject(err);
}
);
export default api;

View File

@ -1,20 +1,17 @@
import http from "./http-common"
import api from "./oauth"
const deposit = (user, accountId, data) => {
return http
.createAxios(user)
const deposit = (accountId, data) => {
return api
.patch(`/api/v1/transactions/${accountId}/deposit`, data);
};
const withdraw = (user, accountId, data) => {
return http
.createAxios(user)
const withdraw = (accountId, data) => {
return api
.patch(`/api/v1/transactions/${accountId}/withdraw`, data);
};
const transfer = (user, originAccountId, destinationAccountId, data) => {
return http
.createAxios(user)
const transfer = (originAccountId, destinationAccountId, data) => {
return api
.patch(`/api/v1/transactions/${originAccountId}/${destinationAccountId}`, data);
};

View File

@ -0,0 +1,105 @@
export const IDENTITY_CONFIG = {
authority: process.env.REACT_APP_AUTH_URL,
client_id: process.env.REACT_APP_IDENTITY_CLIENT_ID,
redirect_uri: process.env.REACT_APP_REDIRECT_URL,
silent_redirect_uri: process.env.REACT_APP_SILENT_REDIRECT_URL,
post_logout_redirect_uri: process.env.REACT_APP_LOGOFF_REDIRECT_URL,
audience: process.env.REACT_APP_AUDIENCE,
response_type: "code",
response_mode: "query",
automaticSilentRenew: false,
loadUserInfo: true,
scope: "openid profile api1.full_access",
};
export const METADATA_OIDC = {
issuer: process.env.REACT_APP_ISSUER,
jwks_uri: process.env.REACT_APP_AUTH_URL + "/.well-known/openid-configuration/jwks",
authorization_endpoint: process.env.REACT_APP_AUTH_URL + "/connect/authorize",
token_endpoint: process.env.REACT_APP_AUTH_URL + "/connect/token",
userinfo_endpoint: process.env.REACT_APP_AUTH_URL + "/connect/userinfo",
end_session_endpoint: process.env.REACT_APP_AUTH_URL + "/connect/endsession",
check_session_iframe: process.env.REACT_APP_AUTH_URL + "/connect/checksession",
revocation_endpoint: process.env.REACT_APP_AUTH_URL + "/connect/revocation",
introspection_endpoint: process.env.REACT_APP_AUTH_URL + "/connect/introspect",
};
// {
// "issuer":"https://localhost:5000",
// "jwks_uri":"https://localhost:5000/.well-known/openid-configuration/jwks",
// "authorization_endpoint":"https://localhost:5000/connect/authorize",
// "token_endpoint":"https://localhost:5000/connect/token",
// "userinfo_endpoint":"https://localhost:5000/connect/userinfo",
// "end_session_endpoint":"https://localhost:5000/connect/endsession",
// "check_session_iframe":"https://localhost:5000/connect/checksession",
// "revocation_endpoint":"https://localhost:5000/connect/revocation",
// "introspection_endpoint":"https://localhost:5000/connect/introspect",
// "device_authorization_endpoint":"https://localhost:5000/connect/deviceauthorization",
// "frontchannel_logout_supported":true,
// "frontchannel_logout_session_supported":true,
// "backchannel_logout_supported":true,
// "backchannel_logout_session_supported":true,
// "scopes_supported":[
// "openid",
// "profile",
// "api1.read_only",
// "api1.full_access",
// "offline_access"
// ],
// "claims_supported":[
// "sub",
// "name",
// "family_name",
// "given_name",
// "middle_name",
// "nickname",
// "preferred_username",
// "profile",
// "picture",
// "website",
// "gender",
// "birthdate",
// "zoneinfo",
// "locale",
// "updated_at"
// ],
// "grant_types_supported":[
// "authorization_code",
// "client_credentials",
// "refresh_token",
// "implicit",
// "password",
// "urn:ietf:params:oauth:grant-type:device_code"
// ],
// "response_types_supported":[
// "code",
// "token",
// "id_token",
// "id_token token",
// "code id_token",
// "code token",
// "code id_token token"
// ],
// "response_modes_supported":[
// "form_post",
// "query",
// "fragment"
// ],
// "token_endpoint_auth_methods_supported":[
// "client_secret_basic",
// "client_secret_post"
// ],
// "id_token_signing_alg_values_supported":[
// "RS256"
// ],
// "subject_types_supported":[
// "public"
// ],
// "code_challenge_methods_supported":[
// "plain",
// "S256"
// ],
// "request_parameter_supported":true
// }