authscape 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/Datatable.js +118 -0
- package/components/modals/confirmationModal.js +32 -0
- package/components/modals/paymentModal.js +254 -0
- package/components/pricing/checkoutForm.js +55 -0
- package/package.json +4 -1
- package/services/BaseUri.js +3 -0
- package/services/apiService.js +304 -0
- package/services/authService.js +65 -0
- package/services/authorizationComponent.js +37 -0
- package/services/slug.js +13 -0
- package/services/storeWithExpiry.js +30 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import React, { Component } from 'react';
|
|
2
|
+
import apiService from '../services/apiService';
|
|
3
|
+
import DataTable, {createTheme} from "react-data-table-component";
|
|
4
|
+
|
|
5
|
+
export default class Datatable extends Component {
|
|
6
|
+
|
|
7
|
+
static defaultProps = {
|
|
8
|
+
options: {}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
constructor(props) {
|
|
12
|
+
super(props);
|
|
13
|
+
|
|
14
|
+
this.state = {
|
|
15
|
+
pageNumber : 1,
|
|
16
|
+
pageLength : props.pageLength ? props.pageLength : 10,
|
|
17
|
+
data: [],
|
|
18
|
+
loading: false,
|
|
19
|
+
totalRows: 0
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
componentDidMount = async () => {
|
|
24
|
+
await this.GetDataFromUrl(this.state.pageNumber, this.state.pageLength, this.props.params);
|
|
25
|
+
createTheme('dataTable', {
|
|
26
|
+
text:{
|
|
27
|
+
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
reload = async (reset = false) => {
|
|
33
|
+
|
|
34
|
+
if (reset === true)
|
|
35
|
+
{
|
|
36
|
+
this.setState({pageNumber: 1}, async () => {
|
|
37
|
+
await this.GetDataFromUrl(this.state.pageNumber, this.state.pageLength, this.props.params);
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
else
|
|
41
|
+
{
|
|
42
|
+
await this.GetDataFromUrl(this.state.pageNumber, this.state.pageLength, this.props.params);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
GetDataFromUrl = (page, length, postData = {}) => {
|
|
49
|
+
|
|
50
|
+
this.setState({
|
|
51
|
+
loading: true
|
|
52
|
+
}, async function () {
|
|
53
|
+
|
|
54
|
+
let data = postData;
|
|
55
|
+
|
|
56
|
+
data.offset = page;
|
|
57
|
+
data.length = length;
|
|
58
|
+
|
|
59
|
+
let response = null;
|
|
60
|
+
if (this.props.methodType == "get")
|
|
61
|
+
{
|
|
62
|
+
response = await apiService().get(this.props.url);
|
|
63
|
+
}
|
|
64
|
+
else
|
|
65
|
+
{
|
|
66
|
+
response = await apiService().post(this.props.url, postData);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (response != null && response.status === 200)
|
|
70
|
+
{
|
|
71
|
+
if (this.props.returnResult != null)
|
|
72
|
+
{
|
|
73
|
+
this.props.returnResult(response.data.data);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this.setState({
|
|
77
|
+
totalRows: response.data.recordsTotal,
|
|
78
|
+
data: response.data.data,
|
|
79
|
+
loading: false
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
else
|
|
83
|
+
{
|
|
84
|
+
//console.error(response.status + " - " + response.data);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
handlePageChange = async page => {
|
|
92
|
+
const { pageLength } = this.state;
|
|
93
|
+
this.setState({pageNumber: page});
|
|
94
|
+
await this.GetDataFromUrl(page, pageLength, this.props.params);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
render() {
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<div>
|
|
101
|
+
<DataTable
|
|
102
|
+
title={this.props.title}
|
|
103
|
+
columns={this.props.columns}
|
|
104
|
+
data={this.state.data}
|
|
105
|
+
paginationRowsPerPageOptions={this.props.pageLength ? [this.props.pageLength] : [10]}
|
|
106
|
+
progressPending={this.state.loading}
|
|
107
|
+
customStyles={this.props.customStyles}
|
|
108
|
+
paginationPerPage={this.props.pageLength ? this.props.pageLength : 10}
|
|
109
|
+
paginationServer
|
|
110
|
+
pagination
|
|
111
|
+
paginationTotalRows={this.state.totalRows}
|
|
112
|
+
// onChangeRowsPerPage={this.handlePerRowsChange}
|
|
113
|
+
onChangePage={this.handlePageChange}
|
|
114
|
+
noDataComponent={this.props.noDataComponent}
|
|
115
|
+
/>
|
|
116
|
+
</div>);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import Button from '@mui/material/Button';
|
|
3
|
+
import Dialog from '@mui/material/Dialog';
|
|
4
|
+
import DialogActions from '@mui/material/DialogActions';
|
|
5
|
+
import DialogContent from '@mui/material/DialogContent';
|
|
6
|
+
import DialogContentText from '@mui/material/DialogContentText';
|
|
7
|
+
import DialogTitle from '@mui/material/DialogTitle';
|
|
8
|
+
|
|
9
|
+
export default function confirmationModal({title, description, cancelClicked, okClicked, open = false, cancelTitle = "Cancel", okTitle = "OK"}) {
|
|
10
|
+
return (
|
|
11
|
+
<Dialog
|
|
12
|
+
open={open}
|
|
13
|
+
onClose={cancelClicked}
|
|
14
|
+
aria-labelledby="alert-dialog-title"
|
|
15
|
+
aria-describedby="alert-dialog-description">
|
|
16
|
+
<DialogTitle id="alert-dialog-title">
|
|
17
|
+
{title}
|
|
18
|
+
</DialogTitle>
|
|
19
|
+
<DialogContent>
|
|
20
|
+
<DialogContentText id="alert-dialog-description">
|
|
21
|
+
{description}
|
|
22
|
+
</DialogContentText>
|
|
23
|
+
</DialogContent>
|
|
24
|
+
<DialogActions>
|
|
25
|
+
<Button onClick={cancelClicked}>{cancelTitle}</Button>
|
|
26
|
+
<Button onClick={okClicked}>
|
|
27
|
+
{okTitle}
|
|
28
|
+
</Button>
|
|
29
|
+
</DialogActions>
|
|
30
|
+
</Dialog>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import React, {useEffect, useState} from 'react';
|
|
2
|
+
import {Elements} from '@stripe/react-stripe-js';
|
|
3
|
+
import {loadStripe} from '@stripe/stripe-js';
|
|
4
|
+
import Box from '@material-ui/core/Box';
|
|
5
|
+
import Dialog from '@mui/material/Dialog';
|
|
6
|
+
import DialogActions from '@mui/material/DialogActions';
|
|
7
|
+
import DialogContent from '@mui/material/DialogContent';
|
|
8
|
+
import DialogTitle from '@mui/material/DialogTitle';
|
|
9
|
+
import CheckoutForm from '../pricing/CheckoutForm';
|
|
10
|
+
import ApiService from '../../services/apiService';
|
|
11
|
+
import IconButton from '@mui/material/IconButton';
|
|
12
|
+
import CloseIcon from '@mui/icons-material/Close';
|
|
13
|
+
import Tabs from '@mui/material/Tabs';
|
|
14
|
+
import Tab from '@mui/material/Tab';
|
|
15
|
+
import Typography from '@mui/material/Typography';
|
|
16
|
+
import Select from '@mui/material/Select';
|
|
17
|
+
import MenuItem from '@mui/material/MenuItem';
|
|
18
|
+
import Stack from '@mui/material/Stack';
|
|
19
|
+
import Button from '@mui/material/Button';
|
|
20
|
+
import PaymentRoundedIcon from '@mui/icons-material/PaymentRounded';
|
|
21
|
+
import apiService from '../../services/apiService';
|
|
22
|
+
import Grid from '@mui/material/Grid';
|
|
23
|
+
|
|
24
|
+
export default function PaymentModal({title, description, amount, priceId, setIsLoading, isOpen, invoiceId, onModalClose}) {
|
|
25
|
+
|
|
26
|
+
const stripePromise = loadStripe(process.env.stripePublicKey);
|
|
27
|
+
const [options, setOptions] = useState(null);
|
|
28
|
+
const [value, setValue] = React.useState(0);
|
|
29
|
+
const [paymentMethods, setPaymentMethods] = React.useState([]);
|
|
30
|
+
const [paymentMethod, setPaymentMethod] = React.useState(null);
|
|
31
|
+
|
|
32
|
+
useEffect(async () => {
|
|
33
|
+
|
|
34
|
+
if (isOpen)
|
|
35
|
+
{
|
|
36
|
+
let response = await ApiService().post("/StripePayment/ConnectCustomer", {
|
|
37
|
+
paymentRequestType: 3,
|
|
38
|
+
amount: amount,
|
|
39
|
+
priceId: priceId
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
setOptions({
|
|
43
|
+
clientSecret: response.data,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
let responsePayments = await ApiService().get("/NCAInvoices/GetPaymentMethods");
|
|
47
|
+
if (responsePayments != null && responsePayments.status == 200)
|
|
48
|
+
{
|
|
49
|
+
setPaymentMethods(responsePayments.data);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
}, [isOpen]);
|
|
54
|
+
|
|
55
|
+
const handleChange = (event, newValue) => {
|
|
56
|
+
setValue(newValue);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
function a11yProps(index) {
|
|
60
|
+
return {
|
|
61
|
+
id: `simple-tab-${index}`,
|
|
62
|
+
'aria-controls': `simple-tabpanel-${index}`,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const PaymentMethod = ({id, last4, clicked}) => {
|
|
67
|
+
return (
|
|
68
|
+
<>
|
|
69
|
+
<Box fullWidth={true} sx={{height: 160, width:"100%", marginTop:2, backgroundColor:"#2196F3", position:"relative", border: "1px solid #2196F3", borderRadius: 1, display:"flex", flexDirection:"column", justifyContent:"center", textAlign:"center", cursor:"pointer"}}
|
|
70
|
+
onClick={() => {
|
|
71
|
+
clicked(id);
|
|
72
|
+
}}>
|
|
73
|
+
<Typography gutterBottom variant="body" component="div" sx={{fontSize:14, position:"absolute", left:15, top:10, color:"white"}}>
|
|
74
|
+
Visa
|
|
75
|
+
</Typography>
|
|
76
|
+
{/* <Box sx={{position:"absolute", top:10, right:10, color:"#e9e9e9"}}>
|
|
77
|
+
<DeleteRoundedIcon onClick={deleteClicked} />
|
|
78
|
+
</Box> */}
|
|
79
|
+
<Typography gutterBottom variant="body" component="div" sx={{verticalAlign:"middle", fontSize:18, color:"white"}}>
|
|
80
|
+
* * * * * * * * * * * * {last4}
|
|
81
|
+
</Typography>
|
|
82
|
+
|
|
83
|
+
<Grid container spacing={1} sx={{position:"absolute", bottom:8, marginLeft:0, width: "100%"}}>
|
|
84
|
+
<Grid item xs={6} sx={{paddingLeft:2}}>
|
|
85
|
+
<Typography gutterBottom variant="body" component="div" sx={{textAlign:"left", fontSize:12, marginTop:1, paddingLeft:1, color:"#e9e9e9"}}>
|
|
86
|
+
CARD HOLDER
|
|
87
|
+
</Typography>
|
|
88
|
+
|
|
89
|
+
<Typography gutterBottom variant="body" component="div" sx={{textAlign:"left", fontSize:12, marginTop:"-9px", paddingLeft:1, color:"white" }}>
|
|
90
|
+
Brandon Zuech
|
|
91
|
+
</Typography>
|
|
92
|
+
</Grid>
|
|
93
|
+
<Grid item xs={6} sx={{textAlign:"right", paddingRight:2}}>
|
|
94
|
+
<Typography gutterBottom variant="body" component="div" sx={{fontSize:12, marginLeft:2, marginTop:1, color:"#e9e9e9"}}>
|
|
95
|
+
EXPIRES
|
|
96
|
+
</Typography>
|
|
97
|
+
|
|
98
|
+
<Typography gutterBottom variant="body" component="div" sx={{fontSize:12, marginLeft:2, marginTop:"-9px", color:"white"}}>
|
|
99
|
+
08/21
|
|
100
|
+
</Typography>
|
|
101
|
+
</Grid>
|
|
102
|
+
</Grid>
|
|
103
|
+
</Box>
|
|
104
|
+
</>
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
function TabPanel(props) {
|
|
109
|
+
const { children, value, index, ...other } = props;
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<div
|
|
113
|
+
role="tabpanel"
|
|
114
|
+
hidden={value !== index}
|
|
115
|
+
id={`simple-tabpanel-${index}`}
|
|
116
|
+
aria-labelledby={`simple-tab-${index}`}
|
|
117
|
+
{...other}
|
|
118
|
+
>
|
|
119
|
+
{value === index && (
|
|
120
|
+
<Box sx={{ p: 3 }}>
|
|
121
|
+
<Typography>{children}</Typography>
|
|
122
|
+
</Box>
|
|
123
|
+
)}
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<>
|
|
130
|
+
<Dialog
|
|
131
|
+
fullWidth={true}
|
|
132
|
+
maxWidth={"sm"}
|
|
133
|
+
open={isOpen}
|
|
134
|
+
onClose={() => onModalClose()}>
|
|
135
|
+
<DialogTitle>{title}
|
|
136
|
+
|
|
137
|
+
<IconButton
|
|
138
|
+
aria-label="close"
|
|
139
|
+
onClick={() => {
|
|
140
|
+
|
|
141
|
+
onModalClose();
|
|
142
|
+
}}
|
|
143
|
+
sx={{
|
|
144
|
+
position: 'absolute',
|
|
145
|
+
right: 8,
|
|
146
|
+
top: 8,
|
|
147
|
+
color: (theme) => theme.palette.grey[500],
|
|
148
|
+
}}>
|
|
149
|
+
<CloseIcon />
|
|
150
|
+
</IconButton>
|
|
151
|
+
</DialogTitle>
|
|
152
|
+
<DialogContent>
|
|
153
|
+
{description}
|
|
154
|
+
|
|
155
|
+
<Box sx={{ width: '100%' }}>
|
|
156
|
+
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
|
157
|
+
<Tabs value={value} onChange={handleChange} aria-label="basic tabs example">
|
|
158
|
+
<Tab label="Existing Payment Method" {...a11yProps(0)} />
|
|
159
|
+
<Tab label="Add Payment Method" {...a11yProps(1)} />
|
|
160
|
+
</Tabs>
|
|
161
|
+
</Box>
|
|
162
|
+
<TabPanel value={value} index={0}>
|
|
163
|
+
|
|
164
|
+
<Select
|
|
165
|
+
sx={{marginTop:4}}
|
|
166
|
+
fullWidth={true}
|
|
167
|
+
//placeholder={"Select a payment type"}
|
|
168
|
+
//labelId="demo-simple-select-label"
|
|
169
|
+
id="demo-simple-select"
|
|
170
|
+
value={paymentMethod}
|
|
171
|
+
//label="Payment Type"
|
|
172
|
+
onChange={(val) => {
|
|
173
|
+
setPaymentMethod(val.target.value);
|
|
174
|
+
}}>
|
|
175
|
+
{paymentMethods != null && paymentMethods.map((paymentMethod) => {
|
|
176
|
+
return (
|
|
177
|
+
<MenuItem value={paymentMethod.id} fullWidth={true} sx={{width:"100%"}}>
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
<PaymentMethod id={paymentMethod.id} last4={paymentMethod.last4} clicked={() => {
|
|
182
|
+
|
|
183
|
+
}} />
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
{/* <Box elevation={0} sx={{backgroundColor:"white"}}>
|
|
187
|
+
<Stack direction="row" spacing={2}>
|
|
188
|
+
<Box sx={{paddingTop:2}}>
|
|
189
|
+
<img
|
|
190
|
+
style={{marginTop:"9px"}}
|
|
191
|
+
loading="lazy"
|
|
192
|
+
width="50"
|
|
193
|
+
src={'/images/cardType/' + paymentMethod.brand + ".png"}
|
|
194
|
+
srcSet={'/images/cardType/' + paymentMethod.brand + ".png"}
|
|
195
|
+
/>
|
|
196
|
+
</Box>
|
|
197
|
+
<Box>
|
|
198
|
+
<Box>
|
|
199
|
+
{paymentMethod.funding.toUpperCase()}
|
|
200
|
+
</Box>
|
|
201
|
+
<Box>
|
|
202
|
+
**** **** <small>{paymentMethod.last4}</small> <small>Expire: ({paymentMethod.expMonth}/{paymentMethod.expYear})</small>
|
|
203
|
+
</Box>
|
|
204
|
+
</Box>
|
|
205
|
+
</Stack>
|
|
206
|
+
</Box> */}
|
|
207
|
+
|
|
208
|
+
</MenuItem>)
|
|
209
|
+
})}
|
|
210
|
+
</Select>
|
|
211
|
+
|
|
212
|
+
<Button startIcon={<PaymentRoundedIcon/>} type="submit" variant="contained" disabled={paymentMethod == null} sx={{marginTop:2}} onClick={async () => {
|
|
213
|
+
|
|
214
|
+
setIsLoading(true);
|
|
215
|
+
let response = await apiService().post("/NCAInvoices/PayInvoice", {
|
|
216
|
+
InvoiceId: invoiceId,
|
|
217
|
+
WalletId: paymentMethod
|
|
218
|
+
});
|
|
219
|
+
setIsLoading(false);
|
|
220
|
+
|
|
221
|
+
if (response != null && response.status == 200)
|
|
222
|
+
{
|
|
223
|
+
window.location.reload();
|
|
224
|
+
}
|
|
225
|
+
else
|
|
226
|
+
{
|
|
227
|
+
alert("We had an issue with the payment method");
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
}}>Pay Now</Button>
|
|
232
|
+
|
|
233
|
+
</TabPanel>
|
|
234
|
+
<TabPanel value={value} index={1}>
|
|
235
|
+
<div>
|
|
236
|
+
<Box mt={4} mb={2}>
|
|
237
|
+
{options != null &&
|
|
238
|
+
<Elements stripe={stripePromise} options={options}>
|
|
239
|
+
<CheckoutForm />
|
|
240
|
+
</Elements>
|
|
241
|
+
}
|
|
242
|
+
</Box>
|
|
243
|
+
</div>
|
|
244
|
+
</TabPanel>
|
|
245
|
+
</Box>
|
|
246
|
+
|
|
247
|
+
</DialogContent>
|
|
248
|
+
<DialogActions>
|
|
249
|
+
{/* <Button onClick={handleClose}>Close</Button> */}
|
|
250
|
+
</DialogActions>
|
|
251
|
+
</Dialog>
|
|
252
|
+
</>
|
|
253
|
+
)
|
|
254
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import React, {useState} from 'react';
|
|
2
|
+
import Button from '@mui/material/Button';
|
|
3
|
+
import {useStripe, useElements, PaymentElement} from '@stripe/react-stripe-js';
|
|
4
|
+
import PaymentRoundedIcon from '@mui/icons-material/PaymentRounded';
|
|
5
|
+
|
|
6
|
+
const CheckoutForm = () => {
|
|
7
|
+
const stripe = useStripe();
|
|
8
|
+
const elements = useElements();
|
|
9
|
+
|
|
10
|
+
const [errorMessage, setErrorMessage] = useState(null);
|
|
11
|
+
|
|
12
|
+
const handleSubmit = async (event) => {
|
|
13
|
+
|
|
14
|
+
// We don't want to let default form submission happen here,
|
|
15
|
+
// which would refresh the page.
|
|
16
|
+
event.preventDefault();
|
|
17
|
+
|
|
18
|
+
if (!stripe || !elements) {
|
|
19
|
+
// Stripe.js has not yet loaded.
|
|
20
|
+
// Make sure to disable form submission until Stripe.js has loaded.
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const {error} = await stripe.confirmPayment({
|
|
25
|
+
//`Elements` instance that was used to create the Payment Element
|
|
26
|
+
elements,
|
|
27
|
+
confirmParams: {
|
|
28
|
+
return_url: process.env.WebsiteBaseUri + '/confirmPayment?redirectUrl=' + encodeURIComponent(window.location.href),
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (error) {
|
|
33
|
+
// This point will only be reached if there is an immediate error when
|
|
34
|
+
// confirming the payment. Show error to your customer (for example, payment
|
|
35
|
+
// details incomplete)
|
|
36
|
+
setErrorMessage(error.message);
|
|
37
|
+
} else {
|
|
38
|
+
// Your customer will be redirected to your `return_url`. For some payment
|
|
39
|
+
// methods like iDEAL, your customer will be redirected to an intermediate
|
|
40
|
+
// site first to authorize the payment, then redirected to the `return_url`.
|
|
41
|
+
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<form onSubmit={handleSubmit}>
|
|
47
|
+
<PaymentElement />
|
|
48
|
+
<Button startIcon={<PaymentRoundedIcon/>} type="submit" variant="contained" disabled={!stripe} sx={{marginTop:2}}>Pay Now</Button>
|
|
49
|
+
{/* Show error message to your customers */}
|
|
50
|
+
{errorMessage && <div>{errorMessage}</div>}
|
|
51
|
+
</form>
|
|
52
|
+
)
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export default CheckoutForm;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "authscape",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -10,10 +10,13 @@
|
|
|
10
10
|
"license": "ISC",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"axios": "^0.27.2",
|
|
13
|
+
"js-file-download": "^0.4.12",
|
|
13
14
|
"next": "^12.1.6",
|
|
15
|
+
"next-cookies": "^2.0.3",
|
|
14
16
|
"nookies": "^2.5.2",
|
|
15
17
|
"querystring": "^0.2.1",
|
|
16
18
|
"react": "^18.2.0",
|
|
19
|
+
"react-data-table-component": "^7.5.2",
|
|
17
20
|
"react-dom": "^18.2.0"
|
|
18
21
|
}
|
|
19
22
|
}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import axios from 'axios'
|
|
2
|
+
import querystring from 'querystring';
|
|
3
|
+
import fileDownload from 'js-file-download';
|
|
4
|
+
import cookies from 'next-cookies';
|
|
5
|
+
import { parseCookies, setCookie, destroyCookie } from 'nookies';
|
|
6
|
+
import { GetEnvironment } from "./BaseUri";
|
|
7
|
+
|
|
8
|
+
const setupDefaultOptions = async (ctx = null) => {
|
|
9
|
+
|
|
10
|
+
let defaultOptions = {};
|
|
11
|
+
|
|
12
|
+
if (ctx == null)
|
|
13
|
+
{
|
|
14
|
+
let accessToken = cookies(ctx).access_token || '';
|
|
15
|
+
|
|
16
|
+
// let accessToken = window.localStorage.getItem("access_token");
|
|
17
|
+
|
|
18
|
+
if (accessToken !== null && accessToken !== undefined && accessToken != "") {
|
|
19
|
+
defaultOptions = {
|
|
20
|
+
headers: {
|
|
21
|
+
Authorization: "Bearer " + accessToken
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
defaultOptions = {
|
|
27
|
+
headers: {
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else
|
|
33
|
+
{
|
|
34
|
+
defaultOptions = {
|
|
35
|
+
headers: {
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return defaultOptions;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const RefreshToken = async (originalRequest, instance) => {
|
|
44
|
+
|
|
45
|
+
let accessToken = cookies(null).access_token || '';
|
|
46
|
+
let refreshToken = cookies(null).refresh_token || '';
|
|
47
|
+
|
|
48
|
+
let response = await instance.post(process.env.AUTHORITYURI + "/connect/token",
|
|
49
|
+
querystring.stringify({
|
|
50
|
+
grant_type: 'refresh_token',
|
|
51
|
+
client_id: process.env.client_id,
|
|
52
|
+
client_secret: process.env.client_secret,
|
|
53
|
+
refresh_token: refreshToken
|
|
54
|
+
}), {
|
|
55
|
+
headers: {
|
|
56
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
57
|
+
"Authorization": "Bearer " + accessToken
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (response != null && response.status == 200)
|
|
62
|
+
{
|
|
63
|
+
originalRequest.headers['Authorization'] = 'Bearer ' + response.data.access_token;
|
|
64
|
+
|
|
65
|
+
let domain = getCookieDomain();
|
|
66
|
+
|
|
67
|
+
await setCookie(null, "access_token", response.data.access_token,
|
|
68
|
+
{
|
|
69
|
+
maxAge: 2147483647,
|
|
70
|
+
path: '/',
|
|
71
|
+
domain: domain,
|
|
72
|
+
secure: true
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
await setCookie(null, "expires_in", response.data.expires_in,
|
|
76
|
+
{
|
|
77
|
+
maxAge: 2147483647,
|
|
78
|
+
path: '/',
|
|
79
|
+
domain: domain,
|
|
80
|
+
secure: true
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
await setCookie(null, "refresh_token", response.data.refresh_token,
|
|
84
|
+
{
|
|
85
|
+
maxAge: 2147483647,
|
|
86
|
+
path: '/',
|
|
87
|
+
domain: domain,
|
|
88
|
+
secure: true
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const apiService = (ctx = null) => {
|
|
94
|
+
|
|
95
|
+
let env = GetEnvironment();
|
|
96
|
+
if (env == "development")
|
|
97
|
+
{
|
|
98
|
+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let baseUri = process.env.APIURI + "/api";
|
|
102
|
+
let AuthUri = process.env.AUTHORITYURI;
|
|
103
|
+
|
|
104
|
+
const instance = axios.create({
|
|
105
|
+
baseURL: baseUri,
|
|
106
|
+
//timeout: 10000,
|
|
107
|
+
params: {} // do not remove this, its added to add params later in the config
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
instance.interceptors.response.use(
|
|
111
|
+
(response) => {
|
|
112
|
+
|
|
113
|
+
return response;
|
|
114
|
+
},
|
|
115
|
+
async (error) => {
|
|
116
|
+
const originalConfig = error.config;
|
|
117
|
+
if (error.response) {
|
|
118
|
+
|
|
119
|
+
if (error.response.status === 401 && !originalConfig._retry) {
|
|
120
|
+
originalConfig._retry = true;
|
|
121
|
+
|
|
122
|
+
// Do something, call refreshToken() request for example;
|
|
123
|
+
await RefreshToken(originalConfig, instance);
|
|
124
|
+
|
|
125
|
+
// return a request
|
|
126
|
+
return instance.request(originalConfig);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (error.response.status === 400) {
|
|
130
|
+
// Do something
|
|
131
|
+
|
|
132
|
+
if (error.response.config.url.includes("/connect/token")) // remove the access and refresh if invalid
|
|
133
|
+
{
|
|
134
|
+
destroyCookie(ctx, "access_token", {
|
|
135
|
+
maxAge: 2147483647,
|
|
136
|
+
path: '/',
|
|
137
|
+
domain: process.env.cookieDomain
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
destroyCookie(ctx, "refresh_token", {
|
|
141
|
+
maxAge: 2147483647,
|
|
142
|
+
path: '/',
|
|
143
|
+
domain: process.env.cookieDomain
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
destroyCookie(ctx, "expires_in", {
|
|
147
|
+
maxAge: 2147483647,
|
|
148
|
+
path: '/',
|
|
149
|
+
domain: process.env.cookieDomain
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return Promise.reject(error);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return Promise.reject(error);
|
|
158
|
+
}
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
|
|
163
|
+
get: async (url, options= {}) => {
|
|
164
|
+
|
|
165
|
+
try
|
|
166
|
+
{
|
|
167
|
+
let defaultOptions = await setupDefaultOptions(ctx);
|
|
168
|
+
return await instance.get(url, { ...defaultOptions, ...options });
|
|
169
|
+
}
|
|
170
|
+
catch(error)
|
|
171
|
+
{
|
|
172
|
+
return error.response;
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
post: async (url, data, options = {}) => {
|
|
176
|
+
|
|
177
|
+
try
|
|
178
|
+
{
|
|
179
|
+
let defaultOptions = await setupDefaultOptions(ctx);
|
|
180
|
+
return await instance.post(url, data, { ...defaultOptions, ...options });
|
|
181
|
+
}
|
|
182
|
+
catch(error)
|
|
183
|
+
{
|
|
184
|
+
return error.response;
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
put: async (url, data, options = {}) => {
|
|
188
|
+
|
|
189
|
+
try
|
|
190
|
+
{
|
|
191
|
+
let defaultOptions = await setupDefaultOptions(ctx);
|
|
192
|
+
return await instance.put(url, data, { ...defaultOptions, ...options });
|
|
193
|
+
}
|
|
194
|
+
catch(error)
|
|
195
|
+
{
|
|
196
|
+
return error.response;
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
delete: async (url, options = {}) => {
|
|
200
|
+
|
|
201
|
+
try
|
|
202
|
+
{
|
|
203
|
+
let defaultOptions = await setupDefaultOptions(ctx);
|
|
204
|
+
return await instance.delete(url, { ...defaultOptions, ...options });
|
|
205
|
+
}
|
|
206
|
+
catch(error)
|
|
207
|
+
{
|
|
208
|
+
return error.response;
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
login: async () => {
|
|
212
|
+
|
|
213
|
+
await signinRedirect(ctx);
|
|
214
|
+
},
|
|
215
|
+
logout: async () => {
|
|
216
|
+
|
|
217
|
+
let cookieDomain = process.env.cookieDomain;
|
|
218
|
+
|
|
219
|
+
destroyCookie(ctx, "access_token", {
|
|
220
|
+
maxAge: 2147483647,
|
|
221
|
+
path: '/',
|
|
222
|
+
domain: cookieDomain
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
destroyCookie(ctx, "refresh_token", {
|
|
226
|
+
maxAge: 2147483647,
|
|
227
|
+
path: '/',
|
|
228
|
+
domain: cookieDomain
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
destroyCookie(ctx, "expires_in", {
|
|
232
|
+
maxAge: 2147483647,
|
|
233
|
+
path: '/',
|
|
234
|
+
domain: cookieDomain
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
setTimeout(() => {
|
|
238
|
+
window.location.href = AuthUri + "/connect/logout?redirect=" + window.location.href;
|
|
239
|
+
}, 500);
|
|
240
|
+
|
|
241
|
+
},
|
|
242
|
+
signUp: (redirectUrl = null) => {
|
|
243
|
+
|
|
244
|
+
let url = "";
|
|
245
|
+
if (redirectUrl == null)
|
|
246
|
+
{
|
|
247
|
+
url = AuthUri + "/Account/Register?redirectUrl=" + window.location.href;
|
|
248
|
+
localStorage.setItem("redirectUri", window.location.href);
|
|
249
|
+
}
|
|
250
|
+
else
|
|
251
|
+
{
|
|
252
|
+
url = AuthUri + "/Account/Register?redirectUrl=" + redirectUrl;
|
|
253
|
+
localStorage.setItem("redirectUri", redirectUrl);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
window.location.href = url;
|
|
257
|
+
},
|
|
258
|
+
GetCurrentUser: async () => {
|
|
259
|
+
|
|
260
|
+
try
|
|
261
|
+
{
|
|
262
|
+
let accessToken = cookies(ctx).access_token || null;
|
|
263
|
+
if (accessToken)
|
|
264
|
+
{
|
|
265
|
+
let defaultOptions = await setupDefaultOptions(ctx);
|
|
266
|
+
const response = await instance.get('/UserManagement', defaultOptions);
|
|
267
|
+
if (response != null && response.status == 200)
|
|
268
|
+
{
|
|
269
|
+
return response.data;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
} catch(exp) {
|
|
274
|
+
//return -1;
|
|
275
|
+
}
|
|
276
|
+
return null;
|
|
277
|
+
},
|
|
278
|
+
DownloadFile: async (url, fileName, completed) => {
|
|
279
|
+
|
|
280
|
+
try
|
|
281
|
+
{
|
|
282
|
+
//let defaultOptions = await setupDefaultOptions();
|
|
283
|
+
let defaultOptions = {};
|
|
284
|
+
let options = { responseType: "blob" };
|
|
285
|
+
let response = await instance.get(url, { ...defaultOptions, ...options });
|
|
286
|
+
if (response.status === 200) {
|
|
287
|
+
fileDownload(response.data, fileName);
|
|
288
|
+
if (completed !== undefined) {
|
|
289
|
+
completed();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
catch(error)
|
|
294
|
+
{
|
|
295
|
+
console.error(error);
|
|
296
|
+
if (completed !== undefined) {
|
|
297
|
+
completed();
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export default apiService;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
import querystring from 'querystring';
|
|
4
|
+
|
|
5
|
+
const authService = () => {
|
|
6
|
+
|
|
7
|
+
return {
|
|
8
|
+
|
|
9
|
+
dec2hex: (dec) => {
|
|
10
|
+
return ('0' + dec.toString(16)).substr(-2)
|
|
11
|
+
},
|
|
12
|
+
generateRandomString: () => {
|
|
13
|
+
var array = new Uint32Array(56/2);
|
|
14
|
+
window.crypto.getRandomValues(array);
|
|
15
|
+
return Array.from(array, authService().dec2hex).join('');
|
|
16
|
+
},
|
|
17
|
+
sha256: (plain) => {
|
|
18
|
+
const encoder = new TextEncoder();
|
|
19
|
+
const data = encoder.encode(plain);
|
|
20
|
+
return window.crypto.subtle.digest('SHA-256', data);
|
|
21
|
+
},
|
|
22
|
+
base64urlencode: (a) => {
|
|
23
|
+
var str = "";
|
|
24
|
+
var bytes = new Uint8Array(a);
|
|
25
|
+
var len = bytes.byteLength;
|
|
26
|
+
for (var i = 0; i < len; i++) {
|
|
27
|
+
str += String.fromCharCode(bytes[i]);
|
|
28
|
+
}
|
|
29
|
+
return btoa(str)
|
|
30
|
+
.replace(/\+/g, "-")
|
|
31
|
+
.replace(/\//g, "_")
|
|
32
|
+
.replace(/=+$/, "");
|
|
33
|
+
},
|
|
34
|
+
challenge_from_verifier: async (v) => {
|
|
35
|
+
let hashed = await authService().sha256(v);
|
|
36
|
+
let base64encoded = authService().base64urlencode(hashed);
|
|
37
|
+
return base64encoded;
|
|
38
|
+
},
|
|
39
|
+
login: async (redirectUserUri = null, dnsRecord = null, deviceId = null) => {
|
|
40
|
+
|
|
41
|
+
let state = "1234";
|
|
42
|
+
if (redirectUserUri != null)
|
|
43
|
+
{
|
|
44
|
+
localStorage.setItem("redirectUri", redirectUserUri);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let verifier = authService().generateRandomString();
|
|
48
|
+
var challenge = await authService().challenge_from_verifier(verifier);
|
|
49
|
+
|
|
50
|
+
window.localStorage.setItem("verifier", verifier);
|
|
51
|
+
|
|
52
|
+
let redirectUri = window.location.origin + "/signin-oidc";
|
|
53
|
+
let loginUri = process.env.AUTHORITYURI + "/connect/authorize?response_type=code&state=" + state + "&client_id=" + process.env.client_id + "&scope=email%20openid%20offline_access%20profile%20api1&redirect_uri=" + redirectUri + "&code_challenge=" + challenge + "&code_challenge_method=S256";
|
|
54
|
+
|
|
55
|
+
if (deviceId)
|
|
56
|
+
{
|
|
57
|
+
loginUri += "&deviceId=" + deviceId; // will be for chrome extention and mobile apps later
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
window.location.href = loginUri;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export default authService;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React, { useEffect, useState, useRef } from 'react';
|
|
2
|
+
import apiService from '../services/apiService';
|
|
3
|
+
|
|
4
|
+
export default function AuthorizationComponent({children, setCurrentUser, userLoaded}) {
|
|
5
|
+
|
|
6
|
+
const [loaded, setLoaded] = useState(false);
|
|
7
|
+
|
|
8
|
+
useEffect(async () => {
|
|
9
|
+
|
|
10
|
+
if (!loaded)
|
|
11
|
+
{
|
|
12
|
+
setLoaded(true);
|
|
13
|
+
|
|
14
|
+
let usr = await apiService().GetCurrentUser();
|
|
15
|
+
if (usr != null)
|
|
16
|
+
{
|
|
17
|
+
setCurrentUser(usr);
|
|
18
|
+
}
|
|
19
|
+
else
|
|
20
|
+
{
|
|
21
|
+
setCurrentUser(null);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
userLoaded();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
}, [loaded]);
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<>
|
|
31
|
+
<div>
|
|
32
|
+
{children}
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
</>
|
|
36
|
+
)
|
|
37
|
+
}
|
package/services/slug.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const storeWithExpiry = () => {
|
|
2
|
+
|
|
3
|
+
return {
|
|
4
|
+
set: (key, value, ttl) => {
|
|
5
|
+
|
|
6
|
+
const now = new Date()
|
|
7
|
+
const item = {
|
|
8
|
+
value: value,
|
|
9
|
+
expiry: now.getTime() + ttl,
|
|
10
|
+
}
|
|
11
|
+
localStorage.setItem(key, JSON.stringify(item))
|
|
12
|
+
},
|
|
13
|
+
get: (key) => {
|
|
14
|
+
|
|
15
|
+
const itemStr = localStorage.getItem(key)
|
|
16
|
+
if (!itemStr) {
|
|
17
|
+
return null
|
|
18
|
+
}
|
|
19
|
+
const item = JSON.parse(itemStr)
|
|
20
|
+
const now = new Date()
|
|
21
|
+
if (now.getTime() > item.expiry) {
|
|
22
|
+
localStorage.removeItem(key)
|
|
23
|
+
return null
|
|
24
|
+
}
|
|
25
|
+
return item.value
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default storeWithExpiry;
|