mirror of
https://github.com/sasjs/server.git
synced 2025-12-10 19:34:34 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a131adbae7 | ||
|
|
a20c3b9719 | ||
|
|
eee3a7b084 | ||
|
|
9c3da56901 | ||
|
|
7e6524d7e4 | ||
|
|
0ea2690616 | ||
|
|
b369759f0f | ||
|
|
ac9a835c5a | ||
|
|
e290751c87 | ||
|
|
71bcbb9134 | ||
|
|
c86f0feff8 | ||
|
|
d3d2ab9a36 | ||
| 5cc85b57f8 | |||
|
|
ae0fc0c48c | ||
|
|
555c5d54e2 | ||
| 1b5859ee37 | |||
| 65380be2f3 | |||
|
|
1933be15c2 | ||
|
|
56b20beb8c | ||
|
|
bfc5ac6a4f | ||
|
|
6376173de0 | ||
|
|
3130fbeff0 | ||
|
|
01e9a1d9e9 | ||
|
|
2119e9de9a |
36
CHANGELOG.md
36
CHANGELOG.md
@@ -1,3 +1,39 @@
|
||||
# [0.9.0](https://github.com/sasjs/server/compare/v0.8.3...v0.9.0) (2022-07-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* removed secrets from env variables ([9c3da56](https://github.com/sasjs/server/commit/9c3da56901672a818f54267f9defc9f4701ab7fb))
|
||||
|
||||
## [0.8.3](https://github.com/sasjs/server/compare/v0.8.2...v0.8.3) (2022-07-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deploy:** extract first json from zip file ([e290751](https://github.com/sasjs/server/commit/e290751c872d24009482871a8c398e834357dcde))
|
||||
|
||||
## [0.8.2](https://github.com/sasjs/server/compare/v0.8.1...v0.8.2) (2022-06-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* getRuntimeAndFilePath function to handle the scenarion when path is provided with an extension other than runtimes ([5cc85b5](https://github.com/sasjs/server/commit/5cc85b57f80b13296156811fe966d7b37d45f213))
|
||||
|
||||
## [0.8.1](https://github.com/sasjs/server/compare/v0.8.0...v0.8.1) (2022-06-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* make CA_ROOT optional in getCertificates method ([1b5859e](https://github.com/sasjs/server/commit/1b5859ee37ae73c419115b9debfd5141a79733de))
|
||||
* update /logout route to /SASLogon/logout ([65380be](https://github.com/sasjs/server/commit/65380be2f3945bae559f1749064845b514447a53))
|
||||
|
||||
# [0.8.0](https://github.com/sasjs/server/compare/v0.7.3...v0.8.0) (2022-06-21)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **certs:** ENV variables updated and set CA Root for HTTPS server ([2119e9d](https://github.com/sasjs/server/commit/2119e9de9ab1e5ce1222658f554ac74f4f35cf4d))
|
||||
|
||||
## [0.7.3](https://github.com/sasjs/server/compare/v0.7.2...v0.7.3) (2022-06-20)
|
||||
|
||||
|
||||
|
||||
19
PULL_REQUEST_TEMPLATE.md
Normal file
19
PULL_REQUEST_TEMPLATE.md
Normal file
@@ -0,0 +1,19 @@
|
||||
## Issue
|
||||
|
||||
Link any related issue(s) in this section.
|
||||
|
||||
## Intent
|
||||
|
||||
What this PR intends to achieve.
|
||||
|
||||
## Implementation
|
||||
|
||||
What code changes have been made to achieve the intent.
|
||||
|
||||
## Checks
|
||||
|
||||
- [ ] Code is formatted correctly (`npm run lint:fix`).
|
||||
- [ ] Any new functionality has been unit tested.
|
||||
- [ ] All unit tests are passing (`npm test`).
|
||||
- [ ] All CI checks are green.
|
||||
- [ ] Reviewer is assigned.
|
||||
11
README.md
11
README.md
@@ -99,15 +99,12 @@ SASV9_OPTIONS= -NOXCMD
|
||||
## Additional Web Server Options
|
||||
#
|
||||
|
||||
# ENV variables required for PROTOCOL: `https`
|
||||
PRIVATE_KEY=privkey.pem
|
||||
FULL_CHAIN=fullchain.pem
|
||||
# ENV variables for PROTOCOL: `https`
|
||||
PRIVATE_KEY=privkey.pem (required)
|
||||
CERT_CHAIN=certificate.pem (required)
|
||||
CA_ROOT=fullchain.pem (optional)
|
||||
|
||||
# ENV variables required for MODE: `server`
|
||||
ACCESS_TOKEN_SECRET=<secret>
|
||||
REFRESH_TOKEN_SECRET=<secret>
|
||||
AUTH_CODE_SECRET=<secret>
|
||||
SESSION_SECRET=<secret>
|
||||
DB_CONNECT=mongodb+srv://<DB_USERNAME>:<DB_PASSWORD>@<CLUSTER>/<DB_NAME>?retryWrites=true&w=majority
|
||||
|
||||
# options: [disable|enable] default: `disable` for `server` & `enable` for `desktop`
|
||||
|
||||
@@ -4,17 +4,14 @@ WHITELIST=<space separated urls, each starting with protocol `http` or `https`>
|
||||
|
||||
PROTOCOL=[http|https] default considered as http
|
||||
PRIVATE_KEY=privkey.pem
|
||||
FULL_CHAIN=fullchain.pem
|
||||
CERT_CHAIN=certificate.pem
|
||||
CA_ROOT=fullchain.pem
|
||||
|
||||
PORT=[5000] default value is 5000
|
||||
|
||||
HELMET_CSP_CONFIG_PATH=./csp.config.json if omitted HELMET default will be used
|
||||
HELMET_COEP=[true|false] if omitted HELMET default will be used
|
||||
|
||||
ACCESS_TOKEN_SECRET=<secret>
|
||||
REFRESH_TOKEN_SECRET=<secret>
|
||||
AUTH_CODE_SECRET=<secret>
|
||||
SESSION_SECRET=<secret>
|
||||
DB_CONNECT=mongodb+srv://<DB_USERNAME>:<DB_PASSWORD>@<CLUSTER>/<DB_NAME>?retryWrites=true&w=majority
|
||||
|
||||
RUN_TIMES=[sas|js|sas,js|js,sas] default considered as sas
|
||||
|
||||
@@ -47,41 +47,6 @@ components:
|
||||
- userId
|
||||
type: object
|
||||
additionalProperties: false
|
||||
LoginPayload:
|
||||
properties:
|
||||
username:
|
||||
type: string
|
||||
description: 'Username for user'
|
||||
example: secretuser
|
||||
password:
|
||||
type: string
|
||||
description: 'Password for user'
|
||||
example: secretpassword
|
||||
required:
|
||||
- username
|
||||
- password
|
||||
type: object
|
||||
additionalProperties: false
|
||||
AuthorizeResponse:
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
description: 'Authorization code'
|
||||
example: someRandomCryptoString
|
||||
required:
|
||||
- code
|
||||
type: object
|
||||
additionalProperties: false
|
||||
AuthorizePayload:
|
||||
properties:
|
||||
clientId:
|
||||
type: string
|
||||
description: 'Client ID'
|
||||
example: clientID1
|
||||
required:
|
||||
- clientId
|
||||
type: object
|
||||
additionalProperties: false
|
||||
ClientPayload:
|
||||
properties:
|
||||
clientId:
|
||||
@@ -440,13 +405,13 @@ components:
|
||||
type: object
|
||||
Pick__LeanDocument_T_.Exclude_keyof_LeanDocument_T_.Exclude_keyofDocument._id-or-id-or-__v_-or-%24isSingleNested__:
|
||||
properties:
|
||||
id:
|
||||
description: 'The string version of this documents _id.'
|
||||
_id:
|
||||
$ref: '#/components/schemas/_LeanDocument__LeanDocument_T__'
|
||||
description: 'This documents _id.'
|
||||
__v:
|
||||
description: 'This documents __v.'
|
||||
id:
|
||||
description: 'The string version of this documents _id.'
|
||||
type: object
|
||||
description: 'From T, pick a set of properties whose keys are in the union K'
|
||||
Omit__LeanDocument_this_.Exclude_keyofDocument._id-or-id-or-__v_-or-%24isSingleNested_:
|
||||
@@ -488,6 +453,41 @@ components:
|
||||
example: /Public/somefolder/some.file
|
||||
type: object
|
||||
additionalProperties: false
|
||||
LoginPayload:
|
||||
properties:
|
||||
username:
|
||||
type: string
|
||||
description: 'Username for user'
|
||||
example: secretuser
|
||||
password:
|
||||
type: string
|
||||
description: 'Password for user'
|
||||
example: secretpassword
|
||||
required:
|
||||
- username
|
||||
- password
|
||||
type: object
|
||||
additionalProperties: false
|
||||
AuthorizeResponse:
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
description: 'Authorization code'
|
||||
example: someRandomCryptoString
|
||||
required:
|
||||
- code
|
||||
type: object
|
||||
additionalProperties: false
|
||||
AuthorizePayload:
|
||||
properties:
|
||||
clientId:
|
||||
type: string
|
||||
description: 'Client ID'
|
||||
example: clientID1
|
||||
required:
|
||||
- clientId
|
||||
type: object
|
||||
additionalProperties: false
|
||||
securitySchemes:
|
||||
bearerAuth:
|
||||
type: http
|
||||
@@ -558,86 +558,6 @@ paths:
|
||||
-
|
||||
bearerAuth: []
|
||||
parameters: []
|
||||
/:
|
||||
get:
|
||||
operationId: Home
|
||||
responses:
|
||||
'200':
|
||||
description: Ok
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
summary: 'Render index.html'
|
||||
tags:
|
||||
- Web
|
||||
security: []
|
||||
parameters: []
|
||||
/SASLogon/login:
|
||||
post:
|
||||
operationId: Login
|
||||
responses:
|
||||
'200':
|
||||
description: Ok
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
user: {properties: {displayName: {type: string}, username: {type: string}, id: {type: number, format: double}}, required: [displayName, username, id], type: object}
|
||||
loggedIn: {type: boolean}
|
||||
required:
|
||||
- user
|
||||
- loggedIn
|
||||
type: object
|
||||
summary: 'Accept a valid username/password'
|
||||
tags:
|
||||
- Web
|
||||
security: []
|
||||
parameters: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/LoginPayload'
|
||||
/SASLogon/authorize:
|
||||
post:
|
||||
operationId: Authorize
|
||||
responses:
|
||||
'200':
|
||||
description: Ok
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AuthorizeResponse'
|
||||
examples:
|
||||
'Example 1':
|
||||
value: {code: someRandomCryptoString}
|
||||
summary: 'Accept a valid username/password, plus a CLIENT_ID, and return an AUTH_CODE'
|
||||
tags:
|
||||
- Web
|
||||
security: []
|
||||
parameters: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AuthorizePayload'
|
||||
/logout:
|
||||
get:
|
||||
operationId: Logout
|
||||
responses:
|
||||
'200':
|
||||
description: Ok
|
||||
content:
|
||||
application/json:
|
||||
schema: {}
|
||||
summary: 'Accept a valid username/password'
|
||||
tags:
|
||||
- Web
|
||||
security: []
|
||||
parameters: []
|
||||
/SASjsApi/client:
|
||||
post:
|
||||
operationId: CreateClient
|
||||
@@ -763,7 +683,7 @@ paths:
|
||||
examples:
|
||||
'Example 1':
|
||||
value: {status: failure, message: 'Deployment failed!'}
|
||||
description: "Accepts JSON file and zipped compressed JSON file as well.\r\nCompressed file should only contain one JSON file and should have same name\r\nas of compressed file e.g. deploy.JSON should be compressed to deploy.JSON.zip\r\nAny other file or JSON file in zipped will be ignored!"
|
||||
description: "Accepts JSON file and zipped compressed JSON file as well.\nCompressed file should only contain one JSON file and should have same name\nas of compressed file e.g. deploy.JSON should be compressed to deploy.JSON.zip\nAny other file or JSON file in zipped will be ignored!"
|
||||
summary: 'Creates/updates files within SASjs Drive using uploaded JSON/compressed JSON file.'
|
||||
tags:
|
||||
- Drive
|
||||
@@ -851,7 +771,7 @@ paths:
|
||||
examples:
|
||||
'Example 1':
|
||||
value: {status: failure, message: 'File request failed.'}
|
||||
description: "It's optional to either provide `_filePath` in url as query parameter\r\nOr provide `filePath` in body as form field.\r\nBut it's required to provide else API will respond with Bad Request."
|
||||
description: "It's optional to either provide `_filePath` in url as query parameter\nOr provide `filePath` in body as form field.\nBut it's required to provide else API will respond with Bad Request."
|
||||
summary: 'Create a file in SASjs Drive'
|
||||
tags:
|
||||
- Drive
|
||||
@@ -902,7 +822,7 @@ paths:
|
||||
examples:
|
||||
'Example 1':
|
||||
value: {status: failure, message: 'File request failed.'}
|
||||
description: "It's optional to either provide `_filePath` in url as query parameter\r\nOr provide `filePath` in body as form field.\r\nBut it's required to provide else API will respond with Bad Request."
|
||||
description: "It's optional to either provide `_filePath` in url as query parameter\nOr provide `filePath` in body as form field.\nBut it's required to provide else API will respond with Bad Request."
|
||||
summary: 'Modify a file in SASjs Drive'
|
||||
tags:
|
||||
- Drive
|
||||
@@ -1454,7 +1374,7 @@ paths:
|
||||
anyOf:
|
||||
- {type: string}
|
||||
- {type: string, format: byte}
|
||||
description: "Trigger a SAS or JS program using the _program URL parameter.\r\n\r\nAccepts URL parameters and file uploads. For more details, see docs:\r\n\r\nhttps://server.sasjs.io/storedprograms"
|
||||
description: "Trigger a SAS or JS program using the _program URL parameter.\n\nAccepts URL parameters and file uploads. For more details, see docs:\n\nhttps://server.sasjs.io/storedprograms"
|
||||
summary: 'Execute a Stored Program, returns raw _webout content.'
|
||||
tags:
|
||||
- STP
|
||||
@@ -1482,7 +1402,7 @@ paths:
|
||||
examples:
|
||||
'Example 1':
|
||||
value: {status: success, _webout: 'webout content', log: [], httpHeaders: {Content-type: application/zip, Cache-Control: 'public, max-age=1000'}}
|
||||
description: "Trigger a SAS or JS program using the _program URL parameter.\r\n\r\nAccepts URL parameters and file uploads. For more details, see docs:\r\n\r\nhttps://server.sasjs.io/storedprograms\r\n\r\nThe response will be a JSON object with the following root attributes:\r\nlog, webout, headers.\r\n\r\nThe webout attribute will be nested JSON ONLY if the response-header\r\ncontains a content-type of application/json AND it is valid JSON.\r\nOtherwise it will be a stringified version of the webout content."
|
||||
description: "Trigger a SAS or JS program using the _program URL parameter.\n\nAccepts URL parameters and file uploads. For more details, see docs:\n\nhttps://server.sasjs.io/storedprograms\n\nThe response will be a JSON object with the following root attributes:\nlog, webout, headers.\n\nThe webout attribute will be nested JSON ONLY if the response-header\ncontains a content-type of application/json AND it is valid JSON.\nOtherwise it will be a stringified version of the webout content."
|
||||
summary: 'Execute a Stored Program, return a JSON object'
|
||||
tags:
|
||||
- STP
|
||||
@@ -1504,6 +1424,86 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ExecuteReturnJsonPayload'
|
||||
/:
|
||||
get:
|
||||
operationId: Home
|
||||
responses:
|
||||
'200':
|
||||
description: Ok
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
summary: 'Render index.html'
|
||||
tags:
|
||||
- Web
|
||||
security: []
|
||||
parameters: []
|
||||
/SASLogon/login:
|
||||
post:
|
||||
operationId: Login
|
||||
responses:
|
||||
'200':
|
||||
description: Ok
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
user: {properties: {displayName: {type: string}, username: {type: string}, id: {type: number, format: double}}, required: [displayName, username, id], type: object}
|
||||
loggedIn: {type: boolean}
|
||||
required:
|
||||
- user
|
||||
- loggedIn
|
||||
type: object
|
||||
summary: 'Accept a valid username/password'
|
||||
tags:
|
||||
- Web
|
||||
security: []
|
||||
parameters: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/LoginPayload'
|
||||
/SASLogon/authorize:
|
||||
post:
|
||||
operationId: Authorize
|
||||
responses:
|
||||
'200':
|
||||
description: Ok
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AuthorizeResponse'
|
||||
examples:
|
||||
'Example 1':
|
||||
value: {code: someRandomCryptoString}
|
||||
summary: 'Accept a valid username/password, plus a CLIENT_ID, and return an AUTH_CODE'
|
||||
tags:
|
||||
- Web
|
||||
security: []
|
||||
parameters: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AuthorizePayload'
|
||||
/SASLogon/logout:
|
||||
get:
|
||||
operationId: Logout
|
||||
responses:
|
||||
'200':
|
||||
description: Ok
|
||||
content:
|
||||
application/json:
|
||||
schema: {}
|
||||
summary: 'Destroy the session stored in cookies'
|
||||
tags:
|
||||
- Web
|
||||
security: []
|
||||
parameters: []
|
||||
servers:
|
||||
-
|
||||
url: /
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import path from 'path'
|
||||
import express, { ErrorRequestHandler } from 'express'
|
||||
import mongoose from 'mongoose'
|
||||
import csrf from 'csurf'
|
||||
import session from 'express-session'
|
||||
import MongoStore from 'connect-mongo'
|
||||
@@ -97,45 +98,44 @@ if (CORS === CorsType.ENABLED) {
|
||||
app.use(cors({ credentials: true, origin: whiteList }))
|
||||
}
|
||||
|
||||
/***********************************
|
||||
* DB Connection & *
|
||||
* Express Sessions *
|
||||
* With Mongo Store *
|
||||
***********************************/
|
||||
if (MODE === ModeType.Server) {
|
||||
let store: MongoStore | undefined
|
||||
export default setProcessVariables().then(async () => {
|
||||
/***********************************
|
||||
* DB Connection & *
|
||||
* Express Sessions *
|
||||
* With Mongo Store *
|
||||
***********************************/
|
||||
if (MODE === ModeType.Server) {
|
||||
let store: MongoStore | undefined
|
||||
|
||||
// NOTE: when exporting app.js as agent for supertest
|
||||
// we should exclude connecting to the real database
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
const clientPromise = connectDB().then((conn) => conn!.getClient() as any)
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
store = MongoStore.create({
|
||||
client: mongoose.connection!.getClient() as any,
|
||||
collectionName: 'sessions'
|
||||
})
|
||||
}
|
||||
|
||||
store = MongoStore.create({ clientPromise, collectionName: 'sessions' })
|
||||
app.use(
|
||||
session({
|
||||
secret: process.secrets.SESSION_SECRET,
|
||||
saveUninitialized: false, // don't create session until something stored
|
||||
resave: false, //don't save session if unmodified
|
||||
store,
|
||||
cookie: cookieOptions
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
app.use(
|
||||
session({
|
||||
secret: process.env.SESSION_SECRET as string,
|
||||
saveUninitialized: false, // don't create session until something stored
|
||||
resave: false, //don't save session if unmodified
|
||||
store,
|
||||
cookie: cookieOptions
|
||||
})
|
||||
)
|
||||
}
|
||||
app.use(express.json({ limit: '100mb' }))
|
||||
app.use(express.static(path.join(__dirname, '../public')))
|
||||
|
||||
app.use(express.json({ limit: '100mb' }))
|
||||
app.use(express.static(path.join(__dirname, '../public')))
|
||||
const onError: ErrorRequestHandler = (err, req, res, next) => {
|
||||
if (err.code === 'EBADCSRFTOKEN')
|
||||
return res.status(400).send('Invalid CSRF token!')
|
||||
|
||||
const onError: ErrorRequestHandler = (err, req, res, next) => {
|
||||
if (err.code === 'EBADCSRFTOKEN')
|
||||
return res.status(400).send('Invalid CSRF token!')
|
||||
console.error(err.stack)
|
||||
res.status(500).send('Something broke!')
|
||||
}
|
||||
|
||||
console.error(err.stack)
|
||||
res.status(500).send('Something broke!')
|
||||
}
|
||||
|
||||
export default setProcessVariables().then(async () => {
|
||||
await setupFolders()
|
||||
await copySASjsCore()
|
||||
|
||||
|
||||
@@ -129,8 +129,8 @@ const verifyAuthCode = async (
|
||||
clientId: string,
|
||||
code: string
|
||||
): Promise<InfoJWT | undefined> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
jwt.verify(code, process.env.AUTH_CODE_SECRET as string, (err, data) => {
|
||||
return new Promise((resolve) => {
|
||||
jwt.verify(code, process.secrets.AUTH_CODE_SECRET, (err, data) => {
|
||||
if (err) return resolve(undefined)
|
||||
|
||||
const clientInfo: InfoJWT = {
|
||||
|
||||
@@ -20,7 +20,7 @@ export interface GroupResponse {
|
||||
description: string
|
||||
}
|
||||
|
||||
interface GroupDetailsResponse {
|
||||
export interface GroupDetailsResponse {
|
||||
groupId: number
|
||||
name: string
|
||||
description: string
|
||||
@@ -249,9 +249,10 @@ const updateUsersListInGroup = async (
|
||||
message: 'User not found.'
|
||||
}
|
||||
|
||||
const updatedGroup = (action === 'addUser'
|
||||
? await group.addUser(user._id)
|
||||
: await group.removeUser(user._id)) as unknown as GroupDetailsResponse
|
||||
const updatedGroup =
|
||||
action === 'addUser'
|
||||
? await group.addUser(user)
|
||||
: await group.removeUser(user)
|
||||
|
||||
if (!updatedGroup)
|
||||
throw {
|
||||
@@ -260,9 +261,6 @@ const updateUsersListInGroup = async (
|
||||
message: 'Unable to update group.'
|
||||
}
|
||||
|
||||
if (action === 'addUser') user.addGroup(group._id)
|
||||
else user.removeGroup(group._id)
|
||||
|
||||
return {
|
||||
groupId: updatedGroup.groupId,
|
||||
name: updatedGroup.name,
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
import {
|
||||
getSASSessionController,
|
||||
getJSSessionController,
|
||||
processProgram
|
||||
} from './'
|
||||
import { getSessionController, processProgram } from './'
|
||||
import { readFile, fileExists, createFile, readFileBinary } from '@sasjs/utils'
|
||||
import { PreProgramVars, Session, TreeNode } from '../../types'
|
||||
import {
|
||||
@@ -76,10 +72,7 @@ export class ExecutionController {
|
||||
session: sessionByFileUpload,
|
||||
runTime
|
||||
}: ExecuteProgramParams): Promise<ExecuteReturnRaw | ExecuteReturnJson> {
|
||||
const sessionController =
|
||||
runTime === RunTimeType.SAS
|
||||
? getSASSessionController()
|
||||
: getJSSessionController()
|
||||
const sessionController = getSessionController(runTime)
|
||||
|
||||
const session =
|
||||
sessionByFileUpload ?? (await sessionController.getSession())
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Request, RequestHandler } from 'express'
|
||||
import multer from 'multer'
|
||||
import { uuidv4 } from '@sasjs/utils'
|
||||
import { getSASSessionController, getJSSessionController } from '.'
|
||||
import { getSessionController } from '.'
|
||||
import {
|
||||
executeProgramRawValidation,
|
||||
getRunTimeAndFilePath,
|
||||
@@ -37,17 +37,23 @@ export class FileUploadController {
|
||||
try {
|
||||
;({ runTime } = await getRunTimeAndFilePath(programPath))
|
||||
} catch (err: any) {
|
||||
res.status(400).send({
|
||||
return res.status(400).send({
|
||||
status: 'failure',
|
||||
message: 'Job execution failed',
|
||||
error: typeof err === 'object' ? err.toString() : err
|
||||
})
|
||||
}
|
||||
|
||||
const sessionController =
|
||||
runTime === RunTimeType.SAS
|
||||
? getSASSessionController()
|
||||
: getJSSessionController()
|
||||
let sessionController
|
||||
try {
|
||||
sessionController = getSessionController(runTime)
|
||||
} catch (err: any) {
|
||||
return res.status(400).send({
|
||||
status: 'failure',
|
||||
message: err.message,
|
||||
error: typeof err === 'object' ? err.toString() : err
|
||||
})
|
||||
}
|
||||
|
||||
const session = await sessionController.getSession()
|
||||
// marking consumed true, so that it's not available
|
||||
|
||||
@@ -5,14 +5,16 @@ import { execFile } from 'child_process'
|
||||
import {
|
||||
getSessionsFolder,
|
||||
generateUniqueFileName,
|
||||
sysInitCompiledPath
|
||||
sysInitCompiledPath,
|
||||
RunTimeType
|
||||
} from '../../utils'
|
||||
import {
|
||||
deleteFolder,
|
||||
createFile,
|
||||
fileExists,
|
||||
generateTimestamp,
|
||||
readFile
|
||||
readFile,
|
||||
isWindows
|
||||
} from '@sasjs/utils'
|
||||
|
||||
const execFilePromise = promisify(execFile)
|
||||
@@ -88,7 +90,7 @@ ${autoExecContent}`
|
||||
|
||||
// Additional windows specific options to avoid the desktop popups.
|
||||
|
||||
execFilePromise(process.sasLoc, [
|
||||
execFilePromise(process.sasLoc!, [
|
||||
'-SYSIN',
|
||||
codePath,
|
||||
'-LOG',
|
||||
@@ -99,9 +101,9 @@ ${autoExecContent}`
|
||||
session.path,
|
||||
'-AUTOEXEC',
|
||||
autoExecPath,
|
||||
process.platform === 'win32' ? '-nosplash' : '',
|
||||
process.platform === 'win32' ? '-icon' : '',
|
||||
process.platform === 'win32' ? '-nologo' : ''
|
||||
isWindows() ? '-nosplash' : '',
|
||||
isWindows() ? '-icon' : '',
|
||||
isWindows() ? '-nologo' : ''
|
||||
])
|
||||
.then(() => {
|
||||
session.completed = true
|
||||
@@ -192,7 +194,21 @@ export class JSSessionController extends SessionController {
|
||||
}
|
||||
}
|
||||
|
||||
export const getSASSessionController = (): SASSessionController => {
|
||||
export const getSessionController = (
|
||||
runTime: RunTimeType
|
||||
): SASSessionController | JSSessionController => {
|
||||
if (runTime === RunTimeType.SAS) {
|
||||
return getSASSessionController()
|
||||
}
|
||||
|
||||
if (runTime === RunTimeType.JS) {
|
||||
return getJSSessionController()
|
||||
}
|
||||
|
||||
throw new Error('No Runtime is configured')
|
||||
}
|
||||
|
||||
const getSASSessionController = (): SASSessionController => {
|
||||
if (process.sasSessionController) return process.sasSessionController
|
||||
|
||||
process.sasSessionController = new SASSessionController()
|
||||
@@ -200,7 +216,7 @@ export const getSASSessionController = (): SASSessionController => {
|
||||
return process.sasSessionController
|
||||
}
|
||||
|
||||
export const getJSSessionController = (): JSSessionController => {
|
||||
const getJSSessionController = (): JSSessionController => {
|
||||
if (process.jsSessionController) return process.jsSessionController
|
||||
|
||||
process.jsSessionController = new JSSessionController()
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isWindows } from '@sasjs/utils'
|
||||
import { PreProgramVars, Session } from '../../types'
|
||||
import { generateFileUploadJSCode } from '../../utils'
|
||||
import { ExecutionVars } from './'
|
||||
@@ -20,9 +21,7 @@ export const createJSProgram = async (
|
||||
const preProgramVarStatments = `
|
||||
let _webout = '';
|
||||
const weboutPath = '${
|
||||
process.platform === 'win32'
|
||||
? weboutPath.replace(/\\/g, '\\\\')
|
||||
: weboutPath
|
||||
isWindows() ? weboutPath.replace(/\\/g, '\\\\') : weboutPath
|
||||
}';
|
||||
const _sasjs_tokenfile = '${tokenFile}';
|
||||
const _sasjs_username = '${preProgramVariables?.username}';
|
||||
|
||||
@@ -40,7 +40,7 @@ export const processProgram = async (
|
||||
// waiting for the open event so that we can have underlying file descriptor
|
||||
await once(writeStream, 'open')
|
||||
|
||||
execFileSync(process.nodeLoc, [codePath], {
|
||||
execFileSync(process.nodeLoc!, [codePath], {
|
||||
stdio: ['ignore', writeStream, writeStream]
|
||||
})
|
||||
|
||||
|
||||
@@ -49,10 +49,10 @@ export class WebController {
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Accept a valid username/password
|
||||
* @summary Destroy the session stored in cookies
|
||||
*
|
||||
*/
|
||||
@Get('/logout')
|
||||
@Get('/SASLogon/logout')
|
||||
public async logout(@Request() req: express.Request) {
|
||||
return new Promise((resolve) => {
|
||||
req.session.destroy(() => {
|
||||
|
||||
@@ -35,7 +35,7 @@ export const authenticateAccessToken: RequestHandler = async (
|
||||
req,
|
||||
res,
|
||||
next,
|
||||
process.env.ACCESS_TOKEN_SECRET as string,
|
||||
process.secrets.ACCESS_TOKEN_SECRET,
|
||||
'accessToken'
|
||||
)
|
||||
}
|
||||
@@ -45,7 +45,7 @@ export const authenticateRefreshToken: RequestHandler = (req, res, next) => {
|
||||
req,
|
||||
res,
|
||||
next,
|
||||
process.env.REFRESH_TOKEN_SECRET as string,
|
||||
process.secrets.REFRESH_TOKEN_SECRET,
|
||||
'refreshToken'
|
||||
)
|
||||
}
|
||||
|
||||
45
api/src/model/Configuration.ts
Normal file
45
api/src/model/Configuration.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import mongoose, { Schema } from 'mongoose'
|
||||
|
||||
export interface ConfigurationType {
|
||||
/**
|
||||
* SecretOrPrivateKey to sign Access Token
|
||||
* @example "someRandomCryptoString"
|
||||
*/
|
||||
ACCESS_TOKEN_SECRET: string
|
||||
/**
|
||||
* SecretOrPrivateKey to sign Refresh Token
|
||||
* @example "someRandomCryptoString"
|
||||
*/
|
||||
REFRESH_TOKEN_SECRET: string
|
||||
/**
|
||||
* SecretOrPrivateKey to sign Auth Code
|
||||
* @example "someRandomCryptoString"
|
||||
*/
|
||||
AUTH_CODE_SECRET: string
|
||||
/**
|
||||
* Secret used to sign the session cookie
|
||||
* @example "someRandomCryptoString"
|
||||
*/
|
||||
SESSION_SECRET: string
|
||||
}
|
||||
|
||||
const ConfigurationSchema = new Schema<ConfigurationType>({
|
||||
ACCESS_TOKEN_SECRET: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
REFRESH_TOKEN_SECRET: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
AUTH_CODE_SECRET: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
SESSION_SECRET: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
export default mongoose.model('Configuration', ConfigurationSchema)
|
||||
@@ -1,5 +1,6 @@
|
||||
import mongoose, { Schema, model, Document, Model } from 'mongoose'
|
||||
import User from './User'
|
||||
import { GroupDetailsResponse } from '../controllers'
|
||||
import User, { IUser } from './User'
|
||||
const AutoIncrement = require('mongoose-sequence')(mongoose)
|
||||
|
||||
export interface GroupPayload {
|
||||
@@ -27,8 +28,9 @@ interface IGroupDocument extends GroupPayload, Document {
|
||||
}
|
||||
|
||||
interface IGroup extends IGroupDocument {
|
||||
addUser(userObjectId: Schema.Types.ObjectId): Promise<IGroup>
|
||||
removeUser(userObjectId: Schema.Types.ObjectId): Promise<IGroup>
|
||||
addUser(user: IUser): Promise<GroupDetailsResponse>
|
||||
removeUser(user: IUser): Promise<GroupDetailsResponse>
|
||||
hasUser(user: IUser): boolean
|
||||
}
|
||||
interface IGroupModel extends Model<IGroup> {}
|
||||
|
||||
@@ -70,28 +72,31 @@ groupSchema.pre('remove', async function () {
|
||||
})
|
||||
|
||||
// Instance Methods
|
||||
groupSchema.method(
|
||||
'addUser',
|
||||
async function (userObjectId: Schema.Types.ObjectId) {
|
||||
const userIdIndex = this.users.indexOf(userObjectId)
|
||||
if (userIdIndex === -1) {
|
||||
this.users.push(userObjectId)
|
||||
}
|
||||
this.markModified('users')
|
||||
return this.save()
|
||||
groupSchema.method('addUser', async function (user: IUser) {
|
||||
const userObjectId = user._id
|
||||
const userIdIndex = this.users.indexOf(userObjectId)
|
||||
if (userIdIndex === -1) {
|
||||
this.users.push(userObjectId)
|
||||
user.addGroup(this._id)
|
||||
}
|
||||
)
|
||||
groupSchema.method(
|
||||
'removeUser',
|
||||
async function (userObjectId: Schema.Types.ObjectId) {
|
||||
const userIdIndex = this.users.indexOf(userObjectId)
|
||||
if (userIdIndex > -1) {
|
||||
this.users.splice(userIdIndex, 1)
|
||||
}
|
||||
this.markModified('users')
|
||||
return this.save()
|
||||
this.markModified('users')
|
||||
return this.save()
|
||||
})
|
||||
groupSchema.method('removeUser', async function (user: IUser) {
|
||||
const userObjectId = user._id
|
||||
const userIdIndex = this.users.indexOf(userObjectId)
|
||||
if (userIdIndex > -1) {
|
||||
this.users.splice(userIdIndex, 1)
|
||||
user.removeGroup(this._id)
|
||||
}
|
||||
)
|
||||
this.markModified('users')
|
||||
return this.save()
|
||||
})
|
||||
groupSchema.method('hasUser', function (user: IUser) {
|
||||
const userObjectId = user._id
|
||||
const userIdIndex = this.users.indexOf(userObjectId)
|
||||
return userIdIndex > -1
|
||||
})
|
||||
|
||||
export const Group: IGroupModel = model<IGroup, IGroupModel>(
|
||||
'Group',
|
||||
|
||||
@@ -35,6 +35,7 @@ export interface UserPayload {
|
||||
}
|
||||
|
||||
interface IUserDocument extends UserPayload, Document {
|
||||
_id: Schema.Types.ObjectId
|
||||
id: number
|
||||
isAdmin: boolean
|
||||
isActive: boolean
|
||||
@@ -43,7 +44,7 @@ interface IUserDocument extends UserPayload, Document {
|
||||
tokens: [{ [key: string]: string }]
|
||||
}
|
||||
|
||||
interface IUser extends IUserDocument {
|
||||
export interface IUser extends IUserDocument {
|
||||
comparePassword(password: string): boolean
|
||||
addGroup(groupObjectId: Schema.Types.ObjectId): Promise<IUser>
|
||||
removeGroup(groupObjectId: Schema.Types.ObjectId): Promise<IUser>
|
||||
|
||||
@@ -48,7 +48,7 @@ webRouter.post(
|
||||
}
|
||||
)
|
||||
|
||||
webRouter.get('/logout', desktopRestrict, async (req, res) => {
|
||||
webRouter.get('/SASLogon/logout', desktopRestrict, async (req, res) => {
|
||||
try {
|
||||
await controller.logout(req)
|
||||
res.status(200).send('OK!')
|
||||
|
||||
@@ -16,9 +16,9 @@ appPromise.then(async (app) => {
|
||||
)
|
||||
})
|
||||
} else {
|
||||
const { key, cert } = await getCertificates()
|
||||
const { key, cert, ca } = await getCertificates()
|
||||
|
||||
const httpsServer = createServer({ key, cert }, app)
|
||||
const httpsServer = createServer({ key, cert, ca }, app)
|
||||
httpsServer.listen(sasJsPort, () => {
|
||||
console.log(
|
||||
`⚡️[server]: Server is running at https://localhost:${sasJsPort}`
|
||||
|
||||
5
api/src/types/system/process.d.ts
vendored
5
api/src/types/system/process.d.ts
vendored
@@ -1,12 +1,13 @@
|
||||
declare namespace NodeJS {
|
||||
export interface Process {
|
||||
sasLoc: string
|
||||
nodeLoc: string
|
||||
sasLoc?: string
|
||||
nodeLoc?: string
|
||||
driveLoc: string
|
||||
sasSessionController?: import('../../controllers/internal').SASSessionController
|
||||
jsSessionController?: import('../../controllers/internal').JSSessionController
|
||||
appStreamConfig: import('../').AppStreamConfig
|
||||
logger: import('@sasjs/utils/logger').Logger
|
||||
runTimes: import('../../utils').RunTimeType[]
|
||||
secrets: import('../../model/Configuration').ConfigurationType
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,5 @@ export const connectDB = async () => {
|
||||
}
|
||||
|
||||
console.log('Connected to DB!')
|
||||
await seedDB()
|
||||
|
||||
return mongoose.connection
|
||||
return seedDB()
|
||||
}
|
||||
|
||||
@@ -2,6 +2,6 @@ import jwt from 'jsonwebtoken'
|
||||
import { InfoJWT } from '../types'
|
||||
|
||||
export const generateAccessToken = (data: InfoJWT) =>
|
||||
jwt.sign(data, process.env.ACCESS_TOKEN_SECRET as string, {
|
||||
jwt.sign(data, process.secrets.ACCESS_TOKEN_SECRET, {
|
||||
expiresIn: '1day'
|
||||
})
|
||||
|
||||
@@ -2,6 +2,6 @@ import jwt from 'jsonwebtoken'
|
||||
import { InfoJWT } from '../types'
|
||||
|
||||
export const generateAuthCode = (data: InfoJWT) =>
|
||||
jwt.sign(data, process.env.AUTH_CODE_SECRET as string, {
|
||||
jwt.sign(data, process.secrets.AUTH_CODE_SECRET, {
|
||||
expiresIn: '30s'
|
||||
})
|
||||
|
||||
@@ -2,6 +2,6 @@ import jwt from 'jsonwebtoken'
|
||||
import { InfoJWT } from '../types'
|
||||
|
||||
export const generateRefreshToken = (data: InfoJWT) =>
|
||||
jwt.sign(data, process.env.REFRESH_TOKEN_SECRET as string, {
|
||||
jwt.sign(data, process.secrets.REFRESH_TOKEN_SECRET, {
|
||||
expiresIn: '30 days'
|
||||
})
|
||||
|
||||
@@ -2,22 +2,32 @@ import path from 'path'
|
||||
import { fileExists, getString, readFile } from '@sasjs/utils'
|
||||
|
||||
export const getCertificates = async () => {
|
||||
const { PRIVATE_KEY, FULL_CHAIN } = process.env
|
||||
const { PRIVATE_KEY, CERT_CHAIN, CA_ROOT } = process.env
|
||||
|
||||
let ca
|
||||
|
||||
const keyPath = PRIVATE_KEY ?? (await getFileInput('Private Key (PEM)'))
|
||||
const certPath = FULL_CHAIN ?? (await getFileInput('Full Chain (PEM)'))
|
||||
const certPath = CERT_CHAIN ?? (await getFileInput('Certificate Chain (PEM)'))
|
||||
const caPath = CA_ROOT
|
||||
|
||||
console.log('keyPath: ', keyPath)
|
||||
console.log('certPath: ', certPath)
|
||||
if (caPath) console.log('caPath: ', caPath)
|
||||
|
||||
const key = await readFile(keyPath)
|
||||
const cert = await readFile(certPath)
|
||||
if (caPath) ca = await readFile(caPath)
|
||||
|
||||
return { key, cert }
|
||||
return { key, cert, ca }
|
||||
}
|
||||
|
||||
const getFileInput = async (filename: string): Promise<string> => {
|
||||
const getFileInput = async (
|
||||
filename: string,
|
||||
required: boolean = true
|
||||
): Promise<string> => {
|
||||
const validator = async (filePath: string) => {
|
||||
if (!required) return true
|
||||
|
||||
if (!filePath) return `Path to ${filename} is required.`
|
||||
|
||||
if (!(await fileExists(path.join(process.cwd(), filePath)))) {
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
import path from 'path'
|
||||
import { getString } from '@sasjs/utils/input'
|
||||
import { createFolder, fileExists, folderExists } from '@sasjs/utils'
|
||||
|
||||
const isWindows = () => process.platform === 'win32'
|
||||
import { createFolder, fileExists, folderExists, isWindows } from '@sasjs/utils'
|
||||
import { RunTimeType } from './verifyEnvVariables'
|
||||
|
||||
export const getDesktopFields = async () => {
|
||||
const { SAS_PATH, NODE_PATH } = process.env
|
||||
|
||||
const sasLoc = SAS_PATH ?? (await getSASLocation())
|
||||
const nodeLoc = NODE_PATH ?? (await getNodeLocation())
|
||||
// const driveLoc = DRIVE_PATH ?? (await getDriveLocation())
|
||||
let sasLoc, nodeLoc
|
||||
|
||||
if (process.runTimes.includes(RunTimeType.SAS)) {
|
||||
sasLoc = SAS_PATH ?? (await getSASLocation())
|
||||
}
|
||||
|
||||
if (process.runTimes.includes(RunTimeType.JS)) {
|
||||
nodeLoc = NODE_PATH ?? (await getNodeLocation())
|
||||
}
|
||||
|
||||
return { sasLoc, nodeLoc }
|
||||
}
|
||||
|
||||
@@ -5,14 +5,10 @@ import { RunTimeType } from '.'
|
||||
|
||||
export const getRunTimeAndFilePath = async (programPath: string) => {
|
||||
const ext = path.extname(programPath)
|
||||
// if program path is provided with extension we should split that into code path and ext as run time
|
||||
if (ext) {
|
||||
// If programPath (_program) is provided with a ".sas" or ".js" extension
|
||||
// we should use that extension to determine the appropriate runTime
|
||||
if (ext && Object.values(RunTimeType).includes(ext.slice(1) as RunTimeType)) {
|
||||
const runTime = ext.slice(1)
|
||||
const runTimeTypes = Object.values(RunTimeType)
|
||||
|
||||
if (!runTimeTypes.includes(runTime as RunTimeType)) {
|
||||
throw `The '${runTime}' runtime is not supported.`
|
||||
}
|
||||
|
||||
const codePath = path
|
||||
.join(getFilesFolder(), programPath)
|
||||
|
||||
@@ -1,6 +1,73 @@
|
||||
import Client from '../model/Client'
|
||||
import Group from '../model/Group'
|
||||
import User from '../model/User'
|
||||
import Configuration, { ConfigurationType } from '../model/Configuration'
|
||||
|
||||
import { randomBytes } from 'crypto'
|
||||
|
||||
export const SECRETS: ConfigurationType = {
|
||||
ACCESS_TOKEN_SECRET: randomBytes(64).toString('hex'),
|
||||
REFRESH_TOKEN_SECRET: randomBytes(64).toString('hex'),
|
||||
AUTH_CODE_SECRET: randomBytes(64).toString('hex'),
|
||||
SESSION_SECRET: randomBytes(64).toString('hex')
|
||||
}
|
||||
|
||||
export const seedDB = async (): Promise<ConfigurationType> => {
|
||||
// Checking if client is already in the database
|
||||
const clientExist = await Client.findOne({ clientId: CLIENT.clientId })
|
||||
if (!clientExist) {
|
||||
const client = new Client(CLIENT)
|
||||
await client.save()
|
||||
|
||||
console.log(`DB Seed - client created: ${CLIENT.clientId}`)
|
||||
}
|
||||
|
||||
// Checking if 'AllUsers' Group is already in the database
|
||||
let groupExist = await Group.findOne({ name: GROUP.name })
|
||||
if (!groupExist) {
|
||||
const group = new Group(GROUP)
|
||||
groupExist = await group.save()
|
||||
|
||||
console.log(`DB Seed - Group created: ${GROUP.name}`)
|
||||
}
|
||||
|
||||
// Checking if user is already in the database
|
||||
let usernameExist = await User.findOne({ username: ADMIN_USER.username })
|
||||
if (!usernameExist) {
|
||||
const user = new User(ADMIN_USER)
|
||||
usernameExist = await user.save()
|
||||
|
||||
console.log(`DB Seed - admin account created: ${ADMIN_USER.username}`)
|
||||
}
|
||||
|
||||
if (!groupExist.hasUser(usernameExist)) {
|
||||
groupExist.addUser(usernameExist)
|
||||
console.log(
|
||||
`DB Seed - admin account '${ADMIN_USER.username}' added to Group '${GROUP.name}'`
|
||||
)
|
||||
}
|
||||
|
||||
// checking if configuration is present in the database
|
||||
let configExist = await Configuration.findOne()
|
||||
if (!configExist) {
|
||||
const configuration = new Configuration(SECRETS)
|
||||
configExist = await configuration.save()
|
||||
|
||||
console.log('DB Seed - configuration added')
|
||||
}
|
||||
|
||||
return {
|
||||
ACCESS_TOKEN_SECRET: configExist.ACCESS_TOKEN_SECRET,
|
||||
REFRESH_TOKEN_SECRET: configExist.REFRESH_TOKEN_SECRET,
|
||||
AUTH_CODE_SECRET: configExist.AUTH_CODE_SECRET,
|
||||
SESSION_SECRET: configExist.SESSION_SECRET
|
||||
}
|
||||
}
|
||||
|
||||
const GROUP = {
|
||||
name: 'AllUsers',
|
||||
description: 'Group contains all users'
|
||||
}
|
||||
const CLIENT = {
|
||||
clientId: 'clientID1',
|
||||
clientSecret: 'clientSecret'
|
||||
@@ -13,23 +80,3 @@ const ADMIN_USER = {
|
||||
isAdmin: true,
|
||||
isActive: true
|
||||
}
|
||||
|
||||
export const seedDB = async () => {
|
||||
// Checking if client is already in the database
|
||||
const clientExist = await Client.findOne({ clientId: CLIENT.clientId })
|
||||
if (!clientExist) {
|
||||
const client = new Client(CLIENT)
|
||||
await client.save()
|
||||
|
||||
console.log(`DB Seed - client created: ${CLIENT.clientId}`)
|
||||
}
|
||||
|
||||
// Checking if user is already in the database
|
||||
const usernameExist = await User.findOne({ username: ADMIN_USER.username })
|
||||
if (!usernameExist) {
|
||||
const user = new User(ADMIN_USER)
|
||||
await user.save()
|
||||
|
||||
console.log(`DB Seed - admin account created: ${ADMIN_USER.username}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,33 @@
|
||||
import path from 'path'
|
||||
import { createFolder, getAbsolutePath, getRealPath } from '@sasjs/utils'
|
||||
|
||||
import { getDesktopFields, ModeType, RunTimeType } from '.'
|
||||
import { connectDB, getDesktopFields, ModeType, RunTimeType, SECRETS } from '.'
|
||||
|
||||
export const setProcessVariables = async () => {
|
||||
const { MODE, RUN_TIMES } = process.env
|
||||
|
||||
if (MODE === ModeType.Server) {
|
||||
// NOTE: when exporting app.js as agent for supertest
|
||||
// it should prevent connecting to the real database
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
const secrets = await connectDB()
|
||||
|
||||
process.secrets = secrets
|
||||
} else {
|
||||
process.secrets = SECRETS
|
||||
}
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
process.driveLoc = path.join(process.cwd(), 'sasjs_root')
|
||||
return
|
||||
}
|
||||
|
||||
const { MODE } = process.env
|
||||
process.runTimes = (RUN_TIMES?.split(',') as RunTimeType[]) ?? []
|
||||
|
||||
if (MODE === ModeType.Server) {
|
||||
process.sasLoc = process.env.SAS_PATH as string
|
||||
process.nodeLoc = process.env.NODE_PATH as string
|
||||
process.sasLoc = process.env.SAS_PATH
|
||||
process.nodeLoc = process.env.NODE_PATH
|
||||
} else {
|
||||
const { sasLoc, nodeLoc } = await getDesktopFields()
|
||||
|
||||
@@ -26,9 +40,6 @@ export const setProcessVariables = async () => {
|
||||
await createFolder(absPath)
|
||||
process.driveLoc = getRealPath(absPath)
|
||||
|
||||
const { RUN_TIMES } = process.env
|
||||
process.runTimes = (RUN_TIMES as string).split(',') as RunTimeType[]
|
||||
|
||||
console.log('sasLoc: ', process.sasLoc)
|
||||
console.log('sasDrive: ', process.driveLoc)
|
||||
console.log('runTimes: ', process.runTimes)
|
||||
|
||||
@@ -78,33 +78,7 @@ const verifyMODE = (): string[] => {
|
||||
}
|
||||
|
||||
if (process.env.MODE === ModeType.Server) {
|
||||
const {
|
||||
ACCESS_TOKEN_SECRET,
|
||||
REFRESH_TOKEN_SECRET,
|
||||
AUTH_CODE_SECRET,
|
||||
SESSION_SECRET,
|
||||
DB_CONNECT
|
||||
} = process.env
|
||||
|
||||
if (!ACCESS_TOKEN_SECRET)
|
||||
errors.push(
|
||||
`- ACCESS_TOKEN_SECRET is required for PROTOCOL '${ModeType.Server}'`
|
||||
)
|
||||
|
||||
if (!REFRESH_TOKEN_SECRET)
|
||||
errors.push(
|
||||
`- REFRESH_TOKEN_SECRET is required for PROTOCOL '${ModeType.Server}'`
|
||||
)
|
||||
|
||||
if (!AUTH_CODE_SECRET)
|
||||
errors.push(
|
||||
`- AUTH_CODE_SECRET is required for PROTOCOL '${ModeType.Server}'`
|
||||
)
|
||||
|
||||
if (!SESSION_SECRET)
|
||||
errors.push(
|
||||
`- SESSION_SECRET is required for PROTOCOL '${ModeType.Server}'`
|
||||
)
|
||||
const { DB_CONNECT } = process.env
|
||||
|
||||
if (process.env.NODE_ENV !== 'test')
|
||||
if (!DB_CONNECT)
|
||||
@@ -129,16 +103,16 @@ const verifyPROTOCOL = (): string[] => {
|
||||
}
|
||||
|
||||
if (process.env.PROTOCOL === ProtocolType.HTTPS) {
|
||||
const { PRIVATE_KEY, FULL_CHAIN } = process.env
|
||||
const { PRIVATE_KEY, CERT_CHAIN } = process.env
|
||||
|
||||
if (!PRIVATE_KEY)
|
||||
errors.push(
|
||||
`- PRIVATE_KEY is required for PROTOCOL '${ProtocolType.HTTPS}'`
|
||||
)
|
||||
|
||||
if (!FULL_CHAIN)
|
||||
if (!CERT_CHAIN)
|
||||
errors.push(
|
||||
`- FULL_CHAIN is required for PROTOCOL '${ProtocolType.HTTPS}'`
|
||||
`- CERT_CHAIN is required for PROTOCOL '${ProtocolType.HTTPS}'`
|
||||
)
|
||||
}
|
||||
|
||||
@@ -258,5 +232,5 @@ const DEFAULTS = {
|
||||
PORT: '5000',
|
||||
HELMET_COEP: HelmetCoepType.TRUE,
|
||||
LOG_FORMAT_MORGAN: LOG_FORMAT_MORGANType.Common,
|
||||
RUN_TIMES: `${RunTimeType.SAS}`
|
||||
RUN_TIMES: RunTimeType.SAS
|
||||
}
|
||||
|
||||
@@ -28,7 +28,8 @@ export const extractJSONFromZip = async (zipFile: Express.Multer.File) => {
|
||||
|
||||
for await (const entry of zip) {
|
||||
const fileName = entry.path as string
|
||||
if (fileName.toUpperCase().endsWith('.JSON') && fileName === fileInZip) {
|
||||
// grab the first json found in .zip
|
||||
if (fileName.toUpperCase().endsWith('.JSON')) {
|
||||
fileContent = await entry.buffer()
|
||||
break
|
||||
} else {
|
||||
|
||||
@@ -88,7 +88,7 @@ const AppContextProvider = (props: { children: ReactNode }) => {
|
||||
}, [])
|
||||
|
||||
const logout = useCallback(() => {
|
||||
axios.get('/logout').then(() => {
|
||||
axios.get('/SASLogon/logout').then(() => {
|
||||
setLoggedIn(false)
|
||||
setUsername('')
|
||||
setDisplayName('')
|
||||
|
||||
Reference in New Issue
Block a user