mirror of
https://github.com/sasjs/server.git
synced 2025-12-12 11:54:35 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
efacb1e916 | ||
|
|
d19ce253b4 | ||
|
|
e11a4b66e7 | ||
|
|
d0a1457f44 |
14
CHANGELOG.md
14
CHANGELOG.md
@@ -2,6 +2,20 @@
|
|||||||
|
|
||||||
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.
|
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.12](https://github.com/sasjs/server/compare/v0.0.11...v0.0.12) (2021-12-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* use env if provided for desktop mode ([d19ce25](https://github.com/sasjs/server/commit/d19ce253b4e2d2a7dd912d43a553d4c1bd60ba58))
|
||||||
|
|
||||||
|
### [0.0.11](https://github.com/sasjs/server/compare/v0.0.10...v0.0.11) (2021-12-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* added authorization route for web ([#37](https://github.com/sasjs/server/issues/37)) ([d0a1457](https://github.com/sasjs/server/commit/d0a1457f44a3d8993b57106e5e681c4e51fe8e7d))
|
||||||
|
|
||||||
### [0.0.10](https://github.com/sasjs/server/compare/v0.0.9...v0.0.10) (2021-12-07)
|
### [0.0.10](https://github.com/sasjs/server/compare/v0.0.9...v0.0.10) (2021-12-07)
|
||||||
|
|
||||||
### [0.0.9](https://github.com/sasjs/server/compare/v0.0.3...v0.0.9) (2021-12-07)
|
### [0.0.9](https://github.com/sasjs/server/compare/v0.0.3...v0.0.9) (2021-12-07)
|
||||||
|
|||||||
@@ -6,3 +6,6 @@ ACCESS_TOKEN_SECRET=<secret>
|
|||||||
REFRESH_TOKEN_SECRET=<secret>
|
REFRESH_TOKEN_SECRET=<secret>
|
||||||
AUTH_CODE_SECRET=<secret>
|
AUTH_CODE_SECRET=<secret>
|
||||||
DB_CONNECT=mongodb+srv://<DB_USERNAME>:<DB_PASSWORD>@<CLUSTER>/<DB_NAME>?retryWrites=true&w=majority
|
DB_CONNECT=mongodb+srv://<DB_USERNAME>:<DB_PASSWORD>@<CLUSTER>/<DB_NAME>?retryWrites=true&w=majority
|
||||||
|
|
||||||
|
SAS_PATH=/opt/sas/sas9/SASHome/SASFoundation/9.4/sas
|
||||||
|
SAS_DRIVE=./tmp
|
||||||
|
|||||||
@@ -86,6 +86,6 @@
|
|||||||
"typescript": "^4.3.2"
|
"typescript": "^4.3.2"
|
||||||
},
|
},
|
||||||
"configuration": {
|
"configuration": {
|
||||||
"sasPath": "/opt/sas/sas9/SASHome/SASFoundation/9.4"
|
"sasPath": "/opt/sas/sas9/SASHome/SASFoundation/9.4/sas"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
api/src/types/Process.d.ts
vendored
2
api/src/types/Process.d.ts
vendored
@@ -1,7 +1,7 @@
|
|||||||
declare namespace NodeJS {
|
declare namespace NodeJS {
|
||||||
export interface Process {
|
export interface Process {
|
||||||
sasLoc: string
|
sasLoc: string
|
||||||
driveLoc?: string
|
driveLoc: string
|
||||||
sessionController?: import('../controllers/internal').SessionController
|
sessionController?: import('../controllers/internal').SessionController
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ import mongoose from 'mongoose'
|
|||||||
import { configuration } from '../../package.json'
|
import { configuration } from '../../package.json'
|
||||||
import { getDesktopFields } from '.'
|
import { getDesktopFields } from '.'
|
||||||
import { populateClients } from '../routes/api/auth'
|
import { populateClients } from '../routes/api/auth'
|
||||||
|
import { getRealPath } from '@sasjs/utils'
|
||||||
|
|
||||||
export const connectDB = async () => {
|
export const connectDB = async () => {
|
||||||
// NOTE: when exporting app.js as agent for supertest
|
// NOTE: when exporting app.js as agent for supertest
|
||||||
// we should exlcude connecting to the real database
|
// we should exlcude connecting to the real database
|
||||||
if (process.env.NODE_ENV !== 'test') {
|
if (process.env.NODE_ENV !== 'test') {
|
||||||
const { MODE } = process.env
|
const { MODE } = process.env
|
||||||
|
|
||||||
if (MODE?.trim() !== 'server') {
|
if (MODE?.trim() !== 'server') {
|
||||||
console.log('Running in Destop Mode, no DB to connect.')
|
console.log('Running in Destop Mode, no DB to connect.')
|
||||||
|
|
||||||
@@ -16,16 +18,19 @@ export const connectDB = async () => {
|
|||||||
|
|
||||||
process.sasLoc = sasLoc
|
process.sasLoc = sasLoc
|
||||||
process.driveLoc = driveLoc
|
process.driveLoc = driveLoc
|
||||||
|
} else {
|
||||||
|
const { SAS_PATH, DRIVE_PATH } = process.env
|
||||||
|
|
||||||
return
|
process.sasLoc = SAS_PATH ?? configuration.sasPath
|
||||||
|
process.driveLoc = getRealPath(
|
||||||
|
path.join(process.cwd(), DRIVE_PATH ?? 'tmp')
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { SAS_PATH } = process.env
|
|
||||||
const sasDir = SAS_PATH ?? configuration.sasPath
|
|
||||||
|
|
||||||
process.sasLoc = path.join(sasDir, 'sas')
|
|
||||||
|
|
||||||
console.log('sasLoc: ', process.sasLoc)
|
console.log('sasLoc: ', process.sasLoc)
|
||||||
|
console.log('sasDrive: ', process.driveLoc)
|
||||||
|
|
||||||
|
if (MODE?.trim() !== 'server') return
|
||||||
|
|
||||||
mongoose.connect(process.env.DB_CONNECT as string, async (err) => {
|
mongoose.connect(process.env.DB_CONNECT as string, async (err) => {
|
||||||
if (err) throw err
|
if (err) throw err
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { getRealPath } from '@sasjs/utils'
|
|
||||||
|
|
||||||
export const apiRoot = path.join(__dirname, '..', '..')
|
export const apiRoot = path.join(__dirname, '..', '..')
|
||||||
export const codebaseRoot = path.join(apiRoot, '..')
|
export const codebaseRoot = path.join(apiRoot, '..')
|
||||||
@@ -12,8 +11,7 @@ export const sysInitCompiledPath = path.join(
|
|||||||
export const getWebBuildFolderPath = () =>
|
export const getWebBuildFolderPath = () =>
|
||||||
path.join(codebaseRoot, 'web', 'build')
|
path.join(codebaseRoot, 'web', 'build')
|
||||||
|
|
||||||
export const getTmpFolderPath = () =>
|
export const getTmpFolderPath = () => process.driveLoc
|
||||||
process.driveLoc ?? getRealPath(path.join(process.cwd(), 'tmp'))
|
|
||||||
|
|
||||||
export const getTmpFilesFolderPath = () =>
|
export const getTmpFilesFolderPath = () =>
|
||||||
path.join(getTmpFolderPath(), 'files')
|
path.join(getTmpFolderPath(), 'files')
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ import { createFolder, fileExists, folderExists } from '@sasjs/utils'
|
|||||||
const isWindows = () => process.platform === 'win32'
|
const isWindows = () => process.platform === 'win32'
|
||||||
|
|
||||||
export const getDesktopFields = async () => {
|
export const getDesktopFields = async () => {
|
||||||
const sasLoc = await getSASLocation()
|
const { SAS_PATH, DRIVE_PATH } = process.env
|
||||||
const driveLoc = await getDriveLocation()
|
|
||||||
|
const sasLoc = SAS_PATH ?? (await getSASLocation())
|
||||||
|
const driveLoc = DRIVE_PATH ?? (await getDriveLocation())
|
||||||
|
|
||||||
return { sasLoc, driveLoc }
|
return { sasLoc, driveLoc }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ services:
|
|||||||
context: .
|
context: .
|
||||||
dockerfile: DockerfileApi
|
dockerfile: DockerfileApi
|
||||||
environment:
|
environment:
|
||||||
MODE: ${MODE}
|
MODE: 'server'
|
||||||
CORS: ${CORS}
|
CORS: ${CORS}
|
||||||
PORT: ${PORT_API}
|
PORT: ${PORT_API}
|
||||||
PORT_WEB: ${PORT_WEB}
|
PORT_WEB: ${PORT_WEB}
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "server",
|
"name": "server",
|
||||||
"version": "0.0.10",
|
"version": "0.0.12",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "server",
|
"name": "server",
|
||||||
"version": "0.0.10",
|
"version": "0.0.12",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"prettier": "^2.3.1",
|
"prettier": "^2.3.1",
|
||||||
"standard-version": "^9.3.2"
|
"standard-version": "^9.3.2"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "server",
|
"name": "server",
|
||||||
"version": "0.0.10",
|
"version": "0.0.12",
|
||||||
"description": "NodeJS wrapper for calling the SAS binary executable",
|
"description": "NodeJS wrapper for calling the SAS binary executable",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"server": "npm run server:prepare && npm run server:start",
|
"server": "npm run server:prepare && npm run server:start",
|
||||||
|
|||||||
@@ -19,7 +19,14 @@ function App() {
|
|||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<HashRouter>
|
<HashRouter>
|
||||||
<Header />
|
<Header />
|
||||||
|
<Switch>
|
||||||
|
<Route exact path="/SASjsLogon">
|
||||||
|
<Login getCodeOnly />
|
||||||
|
</Route>
|
||||||
|
<Route path="/">
|
||||||
<Login setTokens={setTokens} />
|
<Login setTokens={setTokens} />
|
||||||
|
</Route>
|
||||||
|
</Switch>
|
||||||
</HashRouter>
|
</HashRouter>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
)
|
)
|
||||||
@@ -39,6 +46,9 @@ function App() {
|
|||||||
<Route exact path="/SASjsStudio">
|
<Route exact path="/SASjsStudio">
|
||||||
<Studio />
|
<Studio />
|
||||||
</Route>
|
</Route>
|
||||||
|
<Route exact path="/SASjsLogon">
|
||||||
|
<Login getCodeOnly />
|
||||||
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
</HashRouter>
|
</HashRouter>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
|
import { useLocation } from 'react-router-dom'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import { CssBaseline, Box, TextField, Button } from '@mui/material'
|
import { CssBaseline, Box, TextField, Button, Typography } from '@mui/material'
|
||||||
|
|
||||||
const headers = {
|
const headers = {
|
||||||
Accept: 'application/json',
|
Accept: 'application/json',
|
||||||
@@ -18,7 +19,12 @@ const getAuthCode = async (credentials: any) => {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers,
|
headers,
|
||||||
body: JSON.stringify(credentials)
|
body: JSON.stringify(credentials)
|
||||||
}).then((data) => data.json())
|
}).then(async (response) => {
|
||||||
|
const resText = await response.text()
|
||||||
|
if (response.status !== 200) throw resText
|
||||||
|
|
||||||
|
return JSON.parse(resText)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
const getTokens = async (payload: any) => {
|
const getTokens = async (payload: any) => {
|
||||||
return fetch(`${baseUrl}/SASjsApi/auth/token`, {
|
return fetch(`${baseUrl}/SASjsApi/auth/token`, {
|
||||||
@@ -28,20 +34,40 @@ const getTokens = async (payload: any) => {
|
|||||||
}).then((data) => data.json())
|
}).then((data) => data.json())
|
||||||
}
|
}
|
||||||
|
|
||||||
const Login = ({ setTokens }: any) => {
|
const Login = ({ setTokens, getCodeOnly }: any) => {
|
||||||
const [username, setUserName] = useState()
|
const location = useLocation()
|
||||||
const [password, setPassword] = useState()
|
const [username, setUserName] = useState('')
|
||||||
|
const [password, setPassword] = useState('')
|
||||||
|
const [errorMessage, setErrorMessage] = useState('')
|
||||||
|
let error: boolean
|
||||||
|
const [displayCode, setDisplayCode] = useState(null)
|
||||||
|
|
||||||
const handleSubmit = async (e: any) => {
|
const handleSubmit = async (e: any) => {
|
||||||
|
error = false
|
||||||
|
setErrorMessage('')
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
const { REACT_APP_CLIENT_ID: clientId } = process.env
|
let { REACT_APP_CLIENT_ID: clientId } = process.env
|
||||||
|
|
||||||
|
if (getCodeOnly) {
|
||||||
|
const params = new URLSearchParams(location.search)
|
||||||
|
const responseType = params.get('response_type')
|
||||||
|
if (responseType === 'code')
|
||||||
|
clientId = params.get('client_id') ?? undefined
|
||||||
|
}
|
||||||
|
|
||||||
const { code } = await getAuthCode({
|
const { code } = await getAuthCode({
|
||||||
clientId,
|
clientId,
|
||||||
username,
|
username,
|
||||||
password
|
password
|
||||||
|
}).catch((err: string) => {
|
||||||
|
error = true
|
||||||
|
setErrorMessage(err)
|
||||||
|
return {}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (!error) {
|
||||||
|
if (getCodeOnly) return setDisplayCode(code)
|
||||||
|
|
||||||
const { accessToken, refreshToken } = await getTokens({
|
const { accessToken, refreshToken } = await getTokens({
|
||||||
clientId,
|
clientId,
|
||||||
code
|
code
|
||||||
@@ -49,6 +75,22 @@ const Login = ({ setTokens }: any) => {
|
|||||||
|
|
||||||
setTokens(accessToken, refreshToken)
|
setTokens(accessToken, refreshToken)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (displayCode) {
|
||||||
|
return (
|
||||||
|
<Box className="main">
|
||||||
|
<CssBaseline />
|
||||||
|
<br />
|
||||||
|
<h2>Authorization Code</h2>
|
||||||
|
<Typography m={2} p={3} style={{ overflowWrap: 'anywhere' }}>
|
||||||
|
{displayCode}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@@ -61,7 +103,12 @@ const Login = ({ setTokens }: any) => {
|
|||||||
>
|
>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<br />
|
<br />
|
||||||
<h2>Welcome to SASjs Server!</h2>
|
<h2 style={{ width: 'auto' }}>Welcome to SASjs Server!</h2>
|
||||||
|
{getCodeOnly && (
|
||||||
|
<p style={{ width: 'auto' }}>
|
||||||
|
Provide credentials to get authorization code.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
@@ -80,6 +127,7 @@ const Login = ({ setTokens }: any) => {
|
|||||||
onChange={(e: any) => setPassword(e.target.value)}
|
onChange={(e: any) => setPassword(e.target.value)}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
{errorMessage && <span>{errorMessage}</span>}
|
||||||
<Button type="submit" variant="outlined">
|
<Button type="submit" variant="outlined">
|
||||||
Submit
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
@@ -88,7 +136,8 @@ const Login = ({ setTokens }: any) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Login.propTypes = {
|
Login.propTypes = {
|
||||||
setTokens: PropTypes.func.isRequired
|
setTokens: PropTypes.func,
|
||||||
|
getCodeOnly: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Login
|
export default Login
|
||||||
|
|||||||
@@ -3,15 +3,8 @@ import { useEffect, useState } from 'react'
|
|||||||
|
|
||||||
export default function useTokens() {
|
export default function useTokens() {
|
||||||
const getTokens = () => {
|
const getTokens = () => {
|
||||||
const accessTokenString = localStorage.getItem('accessToken')
|
const accessToken = localStorage.getItem('accessToken')
|
||||||
const accessToken: string = accessTokenString
|
const refreshToken = localStorage.getItem('refreshToken')
|
||||||
? JSON.parse(accessTokenString)
|
|
||||||
: undefined
|
|
||||||
|
|
||||||
const refreshTokenString = localStorage.getItem('refreshToken')
|
|
||||||
const refreshToken: string = refreshTokenString
|
|
||||||
? JSON.parse(refreshTokenString)
|
|
||||||
: undefined
|
|
||||||
|
|
||||||
if (accessToken && refreshToken) {
|
if (accessToken && refreshToken) {
|
||||||
setAxiosRequestHeader(accessToken)
|
setAxiosRequestHeader(accessToken)
|
||||||
@@ -31,8 +24,8 @@ export default function useTokens() {
|
|||||||
setAxiosResponse(setTokens)
|
setAxiosResponse(setTokens)
|
||||||
|
|
||||||
const saveTokens = (accessToken: string, refreshToken: string) => {
|
const saveTokens = (accessToken: string, refreshToken: string) => {
|
||||||
localStorage.setItem('accessToken', JSON.stringify(accessToken))
|
localStorage.setItem('accessToken', accessToken)
|
||||||
localStorage.setItem('refreshToken', JSON.stringify(refreshToken))
|
localStorage.setItem('refreshToken', refreshToken)
|
||||||
setAxiosRequestHeader(accessToken)
|
setAxiosRequestHeader(accessToken)
|
||||||
setTokens({ accessToken, refreshToken })
|
setTokens({ accessToken, refreshToken })
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user