1
0
mirror of https://github.com/sasjs/server.git synced 2025-12-10 19:34:34 +00:00

Compare commits

...

20 Commits

Author SHA1 Message Date
cc6f8a64b5 fix(web-header): show users display name instead of username 2022-04-27 22:43:23 +05:00
Allan Bowe
4ae8f35e9a chore(release): 0.0.58 2022-04-24 20:25:08 +00:00
Allan Bowe
667e26b080 Merge pull request #142 from sasjs/contribute
npm scripts updated
2022-04-24 02:48:07 +03:00
munja
d09876c05f fix: missing dependency 2022-04-24 00:46:14 +01:00
Saad Jutt
fb8e18be75 chore: fix vulnerabilites 2022-04-24 04:38:28 +05:00
Saad Jutt
7ac7a4e083 chore: added start:prod npm script 2022-04-24 04:36:42 +05:00
Allan Bowe
8e23786dd4 Update CONTRIBUTING.md 2022-04-24 00:31:41 +01:00
Allan Bowe
4bd01bcf29 Update CONTRIBUTING.md 2022-04-24 00:30:29 +01:00
Allan Bowe
51f6aa34a1 Merge pull request #140 from sasjs/corebump
fix: bumping core library to get latest user management macros
2022-04-24 02:02:16 +03:00
Allan Bowe
486207128d fix: bumping core library to get latest user management macros 2022-04-23 22:54:34 +00:00
Allan Bowe
1e4b0b9171 Update README.md 2022-04-21 12:38:31 +01:00
Allan Bowe
1ff820605a chore(release): 0.0.57 2022-04-21 09:27:23 +00:00
Muhammad Saad
9c1a781b3a Merge pull request #136 from sasjs/issue-78
feat: add user name and logout functionality
2022-04-20 16:57:32 -07:00
36628551ae chore(web): use AppContext instead of useTokens Hook 2022-04-21 04:37:40 +05:00
23cf8fa06f chore(web): add user name at top right 2022-04-21 04:36:20 +05:00
84ee743eae feat: create AppContext 2022-04-21 04:34:27 +05:00
Allan Bowe
19e5bd7d2d chore: updating README with docs on linux jobs as approach for running server as background job 2022-04-20 13:16:06 +00:00
Allan Bowe
e251747302 chore(release): 0.0.56 2022-04-20 08:44:48 +00:00
Allan Bowe
7e7558d4cf Merge pull request #133 from sasjs/allanbowe/lengths-of-username-password-61
fix: shortening min length of username.  Closes #61
2022-04-20 11:44:15 +03:00
Allan Bowe
f02996facf fix: shortening min length of username. Closes #61 2022-04-20 08:43:38 +00:00
17 changed files with 383 additions and 99 deletions

View File

@@ -2,25 +2,22 @@
Contributions are very welcome! Feel free to raise an issue or start a discussion, for help in getting started.
The app can be deployed using Docker or NodeJS.
## Configuration
Configuration is made in the `configuration` section of `package.json`:
Configuration is made using `.env` files (per [README.md](https://github.com/sasjs/server#env-var-configuration) settings), _except_ for one case, when running in NodeJS in production - in which case the path to the SAS executable is made in the `configuration` section of `package.json`.
- Provide path to SAS9 executable.
The `.env` file should be created in the location(s) below. Each folder contains a `.env.example` file that may be adjusted and renamed.
* `.env` - the root .env file is used only for Docker deploys.
* `api/.env` - this is the primary file used in NodeJS deploys
* `web/.env` - this file is only necessary in NodeJS when running `web` and `api` seperately (on different ports).
### Using dockers:
## Using Docker
There is `.env.example` file present at root of the project. [for Production]
There is `.env.example` file present at `./api` of the project. [for Development]
There is `.env.example` file present at `./web` of the project. [for Development]
Remember to provide enviornment variables.
#### Development
### Docker Development Mode
Command to run docker for development:
@@ -38,7 +35,7 @@ It will build following images if running first time:
- `mongo-seed-clients` - will be populating client data specified in _./mongo-seed/clients/client.json_
#### Production
### Docker Production Mode
Command to run docker for production:
@@ -54,47 +51,45 @@ It will build following images if running first time:
- `mongo-seed-users` - will be populating user data specified in _./mongo-seed/users/user.json_
- `mongo-seed-clients` - will be populating client data specified in _./mongo-seed/clients/client.json_
### Using node:
## Using NodeJS:
#### Development (running api and web seperately):
Be sure to use v16 or above, and to set your environment variables in the relevant `.env` file(s) - else defaults will be used.
##### API
### NodeJS Development Mode
Navigate to `./api`
There is `.env.example` file present at `./api` directory. Remember to provide enviornment variables else default values will be used mentioned in `.env.example` files
Command to install and run api server.
SASjs Server is split between an API server (serving REST requests) and a WEB Server (everything else). These can be run together, or on seperate ports.
### NodeJS Dev - Single Port
Here the environment variables should be configured under `api.env`. Then:
```
cd ./web && npm i && npm build
cd ../api && npm i && npm start
```
### NodeJS Dev - Seperate Ports
Set the backend variables in `api/.env` and the frontend variables in `web/.env`. Then:
#### API server
```
cd api
npm install
npm start
```
##### Web
Navigate to `./web`
There is `.env.example` file present at `./web` directory. Remember to provide enviornment variables else default values will be used mentioned in `.env.example` files
Command to install and run api server.
#### Web Server
```
cd web
npm install
npm start
```
#### Development (running only api server and have web build served):
#### NodeJS Production Mode
##### API server also serving Web build files
There is `.env.example` file present at `./api` directory. Remember to provide enviornment variables else default values will be used mentioned in `.env.example` files
Command to install and run api server.
```
cd ./web && npm i && npm build && cd ../
cd ./api && npm i && npm start
```
#### Production
##### API & WEB
Update the `.env` file in the *api* folder. Then:
```
npm run server
@@ -105,7 +100,7 @@ This will install/build `web` and install `api`, then start prod server.
## Executables
Command to generate executables
In order to generate the final executables:
```
cd ./web && npm i && npm build && cd ../

View File

@@ -2,6 +2,28 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [0.0.58](https://github.com/sasjs/server/compare/v0.0.57...v0.0.58) (2022-04-24)
### Bug Fixes
* bumping core library to get latest user management macros ([4862071](https://github.com/sasjs/server/commit/486207128da58fc4866bd0919c1bed2bd98097ea))
* missing dependency ([d09876c](https://github.com/sasjs/server/commit/d09876c05f89166eec20064f7aa7ed5b867be081))
### [0.0.57](https://github.com/sasjs/server/compare/v0.0.56...v0.0.57) (2022-04-21)
### Features
* create AppContext ([84ee743](https://github.com/sasjs/server/commit/84ee743eae16e87eaa91969393bebf01e2d15a44))
### [0.0.56](https://github.com/sasjs/server/compare/v0.0.55...v0.0.56) (2022-04-20)
### Bug Fixes
* shortening min length of username. Closes [#61](https://github.com/sasjs/server/issues/61) ([f02996f](https://github.com/sasjs/server/commit/f02996facf1019ec4022ccfbc99c1d0137074e1b))
### [0.0.55](https://github.com/sasjs/server/compare/v0.0.53...v0.0.55) (2022-04-20)

View File

@@ -98,7 +98,20 @@ SASV9_OPTIONS= -NOXCMD
## Persisting the Session
Normally the server process will stop when your terminal dies. To keep it going you can use the npm package [pm2](https://www.npmjs.com/package/pm2) (`npm install pm2@latest -g`) as follows:
Normally the server process will stop when your terminal dies. To keep it going you can use the following suggested approaches:
1. Linux Background Job
2. NPM package `pm2`
### Background Job
Trigger the command using NOHUP, redirecting the output commands, eg `nohup ./api-linux > server.log 2>&1 &`.
You can now see the job running using the `jobs` command. To ensure that it will still run when your terminal is closed, execute the `disown` command. To kill it later, use the `kill -9 <pid>` command. You can see your sessions using `top -u <userid>`. Type `c` to see the commands being run against each pid.
### PM2
Install the npm package [pm2](https://www.npmjs.com/package/pm2) (`npm install pm2@latest -g`) and execute, eg as follows:
```bash
export SAS_PATH=/opt/sas9/SASHome/SASFoundation/9.4/sasexe/sas
@@ -132,7 +145,7 @@ Instead of `app_name` you can pass:
## Server Version
The following credentials can be used for the initial connection to SASjs/server. It is recommended to change these on first use.
The following credentials can be used for the initial connection to SASjs/server. It is highly recommended to change these on first use.
- CLIENTID: `clientID1`
- USERNAME: `secretuser`

26
api/package-lock.json generated
View File

@@ -8,7 +8,7 @@
"name": "api",
"version": "0.0.2",
"dependencies": {
"@sasjs/core": "4.9.0",
"@sasjs/core": "^4.19.0",
"@sasjs/utils": "2.42.1",
"bcryptjs": "^2.4.3",
"cookie-parser": "^1.4.6",
@@ -1379,9 +1379,9 @@
}
},
"node_modules/@sasjs/core": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.9.0.tgz",
"integrity": "sha512-zc1Ey0ylHt/eRZAfK0mVG3EqNyq//wLxbiguiK0R6FhVqwYFEkprs3IiLGZ5M9ttKs2rHRIjOe/ckklHm+6HNQ=="
"version": "4.19.0",
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.19.0.tgz",
"integrity": "sha512-vG2YHJveQUQqN0YBhapXb8y+Qp4OniHzRedlqKRxyL0Pc+kwXx5co4Vo+dcOI5/MX0p+8oERP2aCR77s4FEUJg=="
},
"node_modules/@sasjs/utils": {
"version": "2.42.1",
@@ -2448,9 +2448,9 @@
}
},
"node_modules/async": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
"dependencies": {
"lodash": "^4.17.14"
}
@@ -11127,9 +11127,9 @@
}
},
"@sasjs/core": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.9.0.tgz",
"integrity": "sha512-zc1Ey0ylHt/eRZAfK0mVG3EqNyq//wLxbiguiK0R6FhVqwYFEkprs3IiLGZ5M9ttKs2rHRIjOe/ckklHm+6HNQ=="
"version": "4.19.0",
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.19.0.tgz",
"integrity": "sha512-vG2YHJveQUQqN0YBhapXb8y+Qp4OniHzRedlqKRxyL0Pc+kwXx5co4Vo+dcOI5/MX0p+8oERP2aCR77s4FEUJg=="
},
"@sasjs/utils": {
"version": "2.42.1",
@@ -12060,9 +12060,9 @@
}
},
"async": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
"requires": {
"lodash": "^4.17.14"
}

View File

@@ -8,6 +8,7 @@
"prestart": "npm run initial",
"prebuild": "npm run initial",
"start": "nodemon ./src/server.ts",
"start:prod": "node ./build/src/server.js",
"build": "rimraf build && tsc",
"postbuild": "npm run copy:files",
"swagger": "tsoa spec",
@@ -46,7 +47,7 @@
},
"author": "4GL Ltd",
"dependencies": {
"@sasjs/core": "4.9.0",
"@sasjs/core": "^4.19.0",
"@sasjs/utils": "2.42.1",
"bcryptjs": "^2.4.3",
"cookie-parser": "^1.4.6",

View File

@@ -5,23 +5,12 @@
_before_ any user-provided content.
A number of useful CORE macros are also compiled below, so that they can be
available "out of the box".
available by default for Stored Programs.
Note that the full CORE library is available to sessions in SASjs Studio.
<h4> SAS Macros </h4>
@li mcf_stpsrv_header.sas
@li mf_getuser.sas
@li mf_getvarlist.sas
@li mf_mkdir.sas
@li mf_nobs.sas
@li mf_uid.sas
@li mfs_httpheader.sas
@li mp_dirlist.sas
@li mp_ds2ddl.sas
@li mp_ds2md.sas
@li mp_getdbml.sas
@li mp_init.sas
@li mp_makedata.sas
@li mp_zip.sas
@li ms_webout.sas
**/

View File

@@ -1,6 +1,6 @@
import Joi from 'joi'
const usernameSchema = Joi.string().alphanum().min(6).max(20)
const usernameSchema = Joi.string().alphanum().min(3).max(16)
const passwordSchema = Joi.string().min(6).max(1024)
export const blockFileRegex = /\.(exe|sh|htaccess)$/i

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "server",
"version": "0.0.55",
"version": "0.0.58",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "server",
"version": "0.0.55",
"version": "0.0.58",
"devDependencies": {
"prettier": "^2.3.1",
"standard-version": "^9.3.2"

View File

@@ -1,12 +1,12 @@
{
"name": "server",
"version": "0.0.55",
"version": "0.0.58",
"description": "NodeJS wrapper for calling the SAS binary executable",
"repository": "https://github.com/sasjs/server",
"scripts": {
"server": "npm run server:prepare && npm run server:start",
"server:prepare": "cd web && npm ci && npm run build && cd ../api && npm ci && cd ..",
"server:start": "cd api && npm run start",
"server:prepare": "cd web && npm ci && npm run build && cd ../api && npm ci && npm run build && cd ..",
"server:start": "cd api && npm run start:prod",
"release": "standard-version",
"lint-api:fix": "npx prettier --write \"api/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"",
"lint-api": "npx prettier --check \"api/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"",

23
web/package-lock.json generated
View File

@@ -22,6 +22,7 @@
"@types/node": "^12.20.28",
"@types/react": "^17.0.27",
"axios": "^0.24.0",
"jwt-decode": "3.1.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router-dom": "^5.3.0"
@@ -4191,9 +4192,9 @@
"dev": true
},
"node_modules/async": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
"dev": true,
"dependencies": {
"lodash": "^4.17.14"
@@ -8161,6 +8162,11 @@
"node": ">=4.0"
}
},
"node_modules/jwt-decode": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz",
"integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="
},
"node_modules/kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
@@ -14381,9 +14387,9 @@
"dev": true
},
"async": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
"dev": true,
"requires": {
"lodash": "^4.17.14"
@@ -17382,6 +17388,11 @@
"object.assign": "^4.1.2"
}
},
"jwt-decode": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz",
"integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="
},
"kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",

View File

@@ -21,6 +21,7 @@
"@types/node": "^12.20.28",
"@types/react": "^17.0.27",
"axios": "^0.24.0",
"jwt-decode": "3.1.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router-dom": "^5.3.0"

View File

@@ -1,4 +1,4 @@
import React from 'react'
import React, { useContext } from 'react'
import { Route, HashRouter, Switch } from 'react-router-dom'
import { ThemeProvider } from '@mui/material/styles'
import { theme } from './theme'
@@ -9,12 +9,12 @@ import Home from './components/home'
import Drive from './containers/Drive'
import Studio from './containers/Studio'
import useTokens from './components/useTokens'
import { AppContext } from './context/appContext'
function App() {
const { tokens, setTokens } = useTokens()
const appContext = useContext(AppContext)
if (!tokens) {
if (!appContext.tokens) {
return (
<ThemeProvider theme={theme}>
<HashRouter>
@@ -24,7 +24,7 @@ function App() {
<Login getCodeOnly />
</Route>
<Route path="/">
<Login setTokens={setTokens} />
<Login />
</Route>
</Switch>
</HashRouter>

View File

@@ -1,13 +1,20 @@
import React, { useState } from 'react'
import React, { useState, useContext } from 'react'
import { Link, useHistory, useLocation } from 'react-router-dom'
import AppBar from '@mui/material/AppBar'
import Toolbar from '@mui/material/Toolbar'
import Tabs from '@mui/material/Tabs'
import Tab from '@mui/material/Tab'
import Button from '@mui/material/Button'
import {
AppBar,
Toolbar,
Tabs,
Tab,
Button,
Menu,
MenuItem
} from '@mui/material'
import OpenInNewIcon from '@mui/icons-material/OpenInNew'
import UserName from './userName'
import { AppContext } from '../context/appContext'
const NODE_ENV = process.env.NODE_ENV
const PORT_API = process.env.PORT_API
const baseUrl =
@@ -16,11 +23,29 @@ const baseUrl =
const Header = (props: any) => {
const history = useHistory()
const { pathname } = useLocation()
const appContext = useContext(AppContext)
const [tabValue, setTabValue] = useState(pathname)
const [anchorEl, setAnchorEl] = useState<
(EventTarget & HTMLButtonElement) | null
>(null)
const handleMenu = (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
setAnchorEl(event.currentTarget)
}
const handleClose = () => {
setAnchorEl(null)
}
const handleTabChange = (event: React.SyntheticEvent, value: string) => {
setTabValue(value)
}
const handleLogout = () => {
if (appContext.logout) appContext.logout()
}
return (
<AppBar
position="fixed"
@@ -81,6 +106,39 @@ const Header = (props: any) => {
>
App Stream
</Button>
<div
style={{
display: 'flex',
flexGrow: 1,
justifyContent: 'flex-end'
}}
>
<UserName
userName={appContext.displayName}
onClickHandler={handleMenu}
/>
<Menu
id="menu-appbar"
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center'
}}
keepMounted
transformOrigin={{
vertical: 'top',
horizontal: 'center'
}}
open={!!anchorEl}
onClose={handleClose}
>
<MenuItem onClick={handleLogout} sx={{ justifyContent: 'center' }}>
<Button variant="contained" color="primary">
Logout
</Button>
</MenuItem>
</Menu>
</div>
</Toolbar>
</AppBar>
)

View File

@@ -1,8 +1,9 @@
import React, { useState } from 'react'
import React, { useState, useContext } from 'react'
import { useLocation } from 'react-router-dom'
import PropTypes from 'prop-types'
import { CssBaseline, Box, TextField, Button, Typography } from '@mui/material'
import { AppContext } from '../context/appContext'
const headers = {
Accept: 'application/json',
@@ -33,8 +34,9 @@ const getTokens = async (payload: any) => {
}).then((data) => data.json())
}
const Login = ({ setTokens, getCodeOnly }: any) => {
const Login = ({ getCodeOnly }: any) => {
const location = useLocation()
const appContext = useContext(AppContext)
const [username, setUserName] = useState('')
const [password, setPassword] = useState('')
const [errorMessage, setErrorMessage] = useState('')
@@ -71,7 +73,8 @@ const Login = ({ setTokens, getCodeOnly }: any) => {
code
})
setTokens(accessToken, refreshToken)
if (appContext.setTokens) appContext.setTokens(accessToken, refreshToken)
if (appContext.setUserName) appContext.setUserName(username)
}
}
@@ -126,7 +129,7 @@ const Login = ({ setTokens, getCodeOnly }: any) => {
required
/>
{errorMessage && <span>{errorMessage}</span>}
<Button type="submit" variant="outlined">
<Button type="submit" variant="outlined" disabled={!appContext.setTokens}>
Submit
</Button>
</Box>
@@ -134,7 +137,6 @@ const Login = ({ setTokens, getCodeOnly }: any) => {
}
Login.propTypes = {
setTokens: PropTypes.func,
getCodeOnly: PropTypes.bool
}

View File

@@ -0,0 +1,30 @@
import React from 'react'
import { Typography, IconButton } from '@mui/material'
import AccountCircle from '@mui/icons-material/AccountCircle'
const UserName = (props: any) => {
return (
<IconButton
aria-label="account of current user"
aria-controls="menu-appbar"
aria-haspopup="true"
onClick={props.onClickHandler}
color="inherit"
>
{props.avatarContent ? (
<img
src={props.avatarContent}
alt="user-avatar"
style={{ width: '25px' }}
/>
) : (
<AccountCircle></AccountCircle>
)}
<Typography variant="h6" sx={{ color: 'white', padding: '0 8px' }}>
{props.userName}
</Typography>
</IconButton>
)
}
export default UserName

View File

@@ -0,0 +1,159 @@
import React, {
createContext,
Dispatch,
SetStateAction,
useState,
useEffect,
useCallback,
ReactNode
} from 'react'
import axios from 'axios'
import jwt_decode from 'jwt-decode'
const NODE_ENV = process.env.NODE_ENV
const PORT_API = process.env.PORT_API
const baseUrl =
NODE_ENV === 'development' ? `http://localhost:${PORT_API ?? 5000}` : ''
const isAbsoluteURLRegex = /^(?:\w+:)\/\//
const setAxiosRequestHeader = (accessToken: string) => {
axios.interceptors.request.use(function (config) {
if (baseUrl && !isAbsoluteURLRegex.test(config.url as string)) {
config.url = baseUrl + config.url
}
console.log('axios.interceptors.request.use', accessToken)
config.headers!['Authorization'] = `Bearer ${accessToken}`
config.withCredentials = true
return config
})
}
const setAxiosResponse = (setTokens: Function) => {
// Add a response interceptor
axios.interceptors.response.use(
function (response) {
// Any status code that lie within the range of 2xx cause this function to trigger
return response
},
async function (error) {
if (error.response?.status === 401) {
// refresh token
// const { accessToken, refreshToken: newRefresh } = await refreshMyToken(
// refreshToken
// )
// if (accessToken && newRefresh) {
// setTokens(accessToken, newRefresh)
// error.config.headers['Authorization'] = 'Bearer ' + accessToken
// error.config.baseURL = undefined
// return axios.request(error.config)
// }
console.log(53)
setTokens(undefined)
}
return Promise.reject(error)
}
)
}
const getTokens = () => {
const accessToken = localStorage.getItem('accessToken')
const refreshToken = localStorage.getItem('refreshToken')
if (accessToken && refreshToken) {
setAxiosRequestHeader(accessToken)
return { accessToken, refreshToken }
}
return undefined
}
interface AppContextProps {
userName: string
displayName: string
setUserName: Dispatch<SetStateAction<string>> | null
tokens?: { accessToken: string; refreshToken: string }
setTokens: ((accessToken: string, refreshToken: string) => void) | null
logout: (() => void) | null
}
export const AppContext = createContext<AppContextProps>({
userName: '',
displayName: '',
tokens: getTokens(),
setUserName: null,
setTokens: null,
logout: null
})
const AppContextProvider = (props: { children: ReactNode }) => {
const { children } = props
const [userName, setUserName] = useState('')
const [displayName, setDisplayName] = useState('')
const [tokens, setTokens] = useState(getTokens())
useEffect(() => {
setAxiosResponse(setTokens)
}, [])
useEffect(() => {
if (tokens === undefined) {
localStorage.removeItem('accessToken')
localStorage.removeItem('refreshToken')
setUserName('')
setDisplayName('')
} else {
const decoded: any = jwt_decode(tokens.accessToken)
if (decoded.userId) {
axios
.get(`/SASjsApi/user/${decoded.userId}`)
.then((res: any) => {
if (res.data && res.data?.displayName) {
setDisplayName(res.data.displayName)
} else if (res.data && res.data?.username) {
setDisplayName(res.data.username)
}
})
.catch((err) => {
console.log(err)
})
}
}
}, [tokens])
const saveTokens = useCallback(
(accessToken: string, refreshToken: string) => {
localStorage.setItem('accessToken', accessToken)
localStorage.setItem('refreshToken', refreshToken)
setAxiosRequestHeader(accessToken)
setTokens({ accessToken, refreshToken })
},
[]
)
const logout = useCallback(() => {
setUserName('')
setTokens(undefined)
}, [])
return (
<AppContext.Provider
value={{
userName,
displayName,
setUserName,
tokens,
setTokens: saveTokens,
logout
}}
>
{children}
</AppContext.Provider>
)
}
export default AppContextProvider

View File

@@ -2,10 +2,13 @@ import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
import AppContextProvider from './context/appContext'
ReactDOM.render(
<React.StrictMode>
<App />
<AppContextProvider>
<App />
</AppContextProvider>
</React.StrictMode>,
document.getElementById('root')
)