mirror of
https://github.com/sasjs/server.git
synced 2025-12-10 11:24:35 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1a182698e | ||
|
|
4be692b24b | ||
|
|
d2ddd8aaca | ||
|
|
3a45e8f525 | ||
|
|
c0e2f55a7b | ||
|
|
aa027414ed | ||
|
|
8c4c52b1a9 | ||
|
|
ff420434ae | ||
|
|
65e6de9663 | ||
|
|
2e53d43e11 | ||
|
|
3795f748a7 | ||
|
|
e024a92f16 | ||
|
|
92fda183f3 | ||
|
|
6f2e6efd03 | ||
|
|
3b4e9d20d4 |
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [sasjs]
|
||||
27
CHANGELOG.md
27
CHANGELOG.md
@@ -1,3 +1,30 @@
|
||||
## [0.11.2](https://github.com/sasjs/server/compare/v0.11.1...v0.11.2) (2022-07-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* apply icon option only for sas.exe ([d2ddd8a](https://github.com/sasjs/server/commit/d2ddd8aacadfdd143026881f2c6ae8c6b277610a))
|
||||
|
||||
## [0.11.1](https://github.com/sasjs/server/compare/v0.11.0...v0.11.1) (2022-07-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* bank operator ([aa02741](https://github.com/sasjs/server/commit/aa027414ed3ce51f1014ef36c4191e064b2e963d))
|
||||
* ensuring nosplash option only applies for sas.exe ([65e6de9](https://github.com/sasjs/server/commit/65e6de966383fe49a919b1f901d77c7f1e402c9b)), closes [#229](https://github.com/sasjs/server/issues/229)
|
||||
|
||||
# [0.11.0](https://github.com/sasjs/server/compare/v0.10.0...v0.11.0) (2022-07-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **logs:** logs location is configurable ([e024a92](https://github.com/sasjs/server/commit/e024a92f165990e08db8aa26ee326dbcb30e2e46))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **logs:** logs to file with rotating + code split into files ([92fda18](https://github.com/sasjs/server/commit/92fda183f3f0f3956b7c791669eb8dd52c389d1b))
|
||||
|
||||
# [0.10.0](https://github.com/sasjs/server/compare/v0.9.0...v0.10.0) (2022-07-06)
|
||||
|
||||
|
||||
|
||||
@@ -136,6 +136,9 @@ HELMET_CSP_CONFIG_PATH=./csp.config.json
|
||||
# Docs: https://www.npmjs.com/package/morgan#predefined-formats
|
||||
LOG_FORMAT_MORGAN=
|
||||
|
||||
# This location is for server logs with classical UNIX logrotate behavior
|
||||
LOG_LOCATION=./sasjs_root/logs
|
||||
|
||||
# A comma separated string that defines the available runTimes.
|
||||
# Priority is given to the runtime that comes first in the string.
|
||||
# Possible options at the moment are sas and js
|
||||
|
||||
@@ -20,4 +20,5 @@ NODE_PATH=~/.nvm/versions/node/v16.14.0/bin/node
|
||||
|
||||
SASJS_ROOT=./sasjs_root
|
||||
|
||||
LOG_FORMAT_MORGAN=common
|
||||
LOG_FORMAT_MORGAN=common
|
||||
LOG_LOCATION=./sasjs_root/logs
|
||||
1252
api/package-lock.json
generated
1252
api/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@
|
||||
"initial": "npm run swagger && npm run compileSysInit && npm run copySASjsCore",
|
||||
"prestart": "npm run initial",
|
||||
"prebuild": "npm run initial",
|
||||
"start": "nodemon ./src/server.ts",
|
||||
"start": "NODE_ENV=development nodemon ./src/server.ts",
|
||||
"start:prod": "node ./build/src/server.js",
|
||||
"build": "rimraf build && tsc",
|
||||
"postbuild": "npm run copy:files",
|
||||
@@ -63,6 +63,7 @@
|
||||
"mongoose-sequence": "^5.3.1",
|
||||
"morgan": "^1.10.0",
|
||||
"multer": "^1.4.3",
|
||||
"rotating-file-stream": "^3.0.4",
|
||||
"swagger-ui-express": "4.3.0",
|
||||
"unzipper": "^0.10.11",
|
||||
"url": "^0.10.3"
|
||||
|
||||
@@ -1709,13 +1709,13 @@ servers:
|
||||
tags:
|
||||
-
|
||||
name: Info
|
||||
description: 'Get Server Info'
|
||||
description: 'Get Server Information'
|
||||
-
|
||||
name: Session
|
||||
description: 'Get Session information'
|
||||
-
|
||||
name: User
|
||||
description: 'Operations about users'
|
||||
description: 'Operations with users'
|
||||
-
|
||||
name: Permission
|
||||
description: 'Operations about permissions'
|
||||
@@ -1727,16 +1727,16 @@ tags:
|
||||
description: 'Operations about auth'
|
||||
-
|
||||
name: Drive
|
||||
description: 'Operations about drive'
|
||||
description: 'Operations on SASjs Drive'
|
||||
-
|
||||
name: Group
|
||||
description: 'Operations about group'
|
||||
description: 'Operations on groups and group memberships'
|
||||
-
|
||||
name: STP
|
||||
description: 'Operations about STP'
|
||||
description: 'Execution of Stored Programs'
|
||||
-
|
||||
name: CODE
|
||||
description: 'Operations on SAS code'
|
||||
description: 'Execution of code (various runtimes are supported)'
|
||||
-
|
||||
name: Web
|
||||
description: 'Operations on Web'
|
||||
|
||||
21
api/src/app-modules/configureCors.ts
Normal file
21
api/src/app-modules/configureCors.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Express } from 'express'
|
||||
import cors from 'cors'
|
||||
import { CorsType } from '../utils'
|
||||
|
||||
export const configureCors = (app: Express) => {
|
||||
const { CORS, WHITELIST } = process.env
|
||||
|
||||
if (CORS === CorsType.ENABLED) {
|
||||
const whiteList: string[] = []
|
||||
WHITELIST?.split(' ')
|
||||
?.filter((url) => !!url)
|
||||
.forEach((url) => {
|
||||
if (url.startsWith('http'))
|
||||
// removing trailing slash of URLs listing for CORS
|
||||
whiteList.push(url.replace(/\/$/, ''))
|
||||
})
|
||||
|
||||
console.log('All CORS Requests are enabled for:', whiteList)
|
||||
app.use(cors({ credentials: true, origin: whiteList }))
|
||||
}
|
||||
}
|
||||
32
api/src/app-modules/configureExpressSession.ts
Normal file
32
api/src/app-modules/configureExpressSession.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Express } from 'express'
|
||||
import mongoose from 'mongoose'
|
||||
import session from 'express-session'
|
||||
import MongoStore from 'connect-mongo'
|
||||
|
||||
import { ModeType } from '../utils'
|
||||
import { cookieOptions } from '../app'
|
||||
|
||||
export const configureExpressSession = (app: Express) => {
|
||||
const { MODE } = process.env
|
||||
|
||||
if (MODE === ModeType.Server) {
|
||||
let store: MongoStore | undefined
|
||||
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
store = MongoStore.create({
|
||||
client: mongoose.connection!.getClient() as any,
|
||||
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
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
33
api/src/app-modules/configureLogger.ts
Normal file
33
api/src/app-modules/configureLogger.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import path from 'path'
|
||||
import { Express } from 'express'
|
||||
import morgan from 'morgan'
|
||||
import { createStream } from 'rotating-file-stream'
|
||||
import { generateTimestamp } from '@sasjs/utils'
|
||||
import { getLogFolder } from '../utils'
|
||||
|
||||
export const configureLogger = (app: Express) => {
|
||||
const { LOG_FORMAT_MORGAN } = process.env
|
||||
|
||||
let options
|
||||
if (
|
||||
process.env.NODE_ENV !== 'development' &&
|
||||
process.env.NODE_ENV !== 'test'
|
||||
) {
|
||||
const timestamp = generateTimestamp()
|
||||
const filename = `${timestamp}.log`
|
||||
const logsFolder = getLogFolder()
|
||||
|
||||
// create a rotating write stream
|
||||
var accessLogStream = createStream(filename, {
|
||||
interval: '1d', // rotate daily
|
||||
path: logsFolder
|
||||
})
|
||||
|
||||
console.log('Writing Logs to :', path.join(logsFolder, filename))
|
||||
|
||||
options = { stream: accessLogStream }
|
||||
}
|
||||
|
||||
// setup the logger
|
||||
app.use(morgan(LOG_FORMAT_MORGAN as string, options))
|
||||
}
|
||||
26
api/src/app-modules/configureSecurity.ts
Normal file
26
api/src/app-modules/configureSecurity.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Express } from 'express'
|
||||
import { getEnvCSPDirectives } from '../utils/parseHelmetConfig'
|
||||
import { HelmetCoepType, ProtocolType } from '../utils'
|
||||
import helmet from 'helmet'
|
||||
|
||||
export const configureSecurity = (app: Express) => {
|
||||
const { PROTOCOL, HELMET_CSP_CONFIG_PATH, HELMET_COEP } = process.env
|
||||
|
||||
const cspConfigJson: { [key: string]: string[] | null } = getEnvCSPDirectives(
|
||||
HELMET_CSP_CONFIG_PATH
|
||||
)
|
||||
if (PROTOCOL === ProtocolType.HTTP)
|
||||
cspConfigJson['upgrade-insecure-requests'] = null
|
||||
|
||||
app.use(
|
||||
helmet({
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
...helmet.contentSecurityPolicy.getDefaultDirectives(),
|
||||
...cspConfigJson
|
||||
}
|
||||
},
|
||||
crossOriginEmbedderPolicy: HELMET_COEP === HelmetCoepType.TRUE
|
||||
})
|
||||
)
|
||||
}
|
||||
4
api/src/app-modules/index.ts
Normal file
4
api/src/app-modules/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './configureCors'
|
||||
export * from './configureExpressSession'
|
||||
export * from './configureLogger'
|
||||
export * from './configureSecurity'
|
||||
113
api/src/app.ts
113
api/src/app.ts
@@ -1,30 +1,26 @@
|
||||
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'
|
||||
import morgan from 'morgan'
|
||||
import cookieParser from 'cookie-parser'
|
||||
import dotenv from 'dotenv'
|
||||
import cors from 'cors'
|
||||
import helmet from 'helmet'
|
||||
|
||||
import {
|
||||
copySASjsCore,
|
||||
CorsType,
|
||||
getWebBuildFolder,
|
||||
HelmetCoepType,
|
||||
instantiateLogger,
|
||||
loadAppStreamConfig,
|
||||
ModeType,
|
||||
ProtocolType,
|
||||
ReturnCode,
|
||||
setProcessVariables,
|
||||
setupFolders,
|
||||
verifyEnvVariables
|
||||
} from './utils'
|
||||
import { getEnvCSPDirectives } from './utils/parseHelmetConfig'
|
||||
import {
|
||||
configureCors,
|
||||
configureExpressSession,
|
||||
configureLogger,
|
||||
configureSecurity
|
||||
} from './app-modules'
|
||||
|
||||
dotenv.config()
|
||||
|
||||
@@ -34,19 +30,7 @@ if (verifyEnvVariables()) process.exit(ReturnCode.InvalidEnv)
|
||||
|
||||
const app = express()
|
||||
|
||||
app.use(cookieParser())
|
||||
|
||||
const {
|
||||
MODE,
|
||||
CORS,
|
||||
WHITELIST,
|
||||
PROTOCOL,
|
||||
HELMET_CSP_CONFIG_PATH,
|
||||
HELMET_COEP,
|
||||
LOG_FORMAT_MORGAN
|
||||
} = process.env
|
||||
|
||||
app.use(morgan(LOG_FORMAT_MORGAN as string))
|
||||
const { PROTOCOL } = process.env
|
||||
|
||||
export const cookieOptions = {
|
||||
secure: PROTOCOL === ProtocolType.HTTPS,
|
||||
@@ -54,87 +38,44 @@ export const cookieOptions = {
|
||||
maxAge: 24 * 60 * 60 * 1000 // 24 hours
|
||||
}
|
||||
|
||||
const cspConfigJson: { [key: string]: string[] | null } = getEnvCSPDirectives(
|
||||
HELMET_CSP_CONFIG_PATH
|
||||
)
|
||||
if (PROTOCOL === ProtocolType.HTTP)
|
||||
cspConfigJson['upgrade-insecure-requests'] = null
|
||||
|
||||
/***********************************
|
||||
* CSRF Protection *
|
||||
***********************************/
|
||||
export const csrfProtection = csrf({ cookie: cookieOptions })
|
||||
|
||||
/***********************************
|
||||
* Handle security and origin *
|
||||
***********************************/
|
||||
app.use(
|
||||
helmet({
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
...helmet.contentSecurityPolicy.getDefaultDirectives(),
|
||||
...cspConfigJson
|
||||
}
|
||||
},
|
||||
crossOriginEmbedderPolicy: HELMET_COEP === HelmetCoepType.TRUE
|
||||
})
|
||||
)
|
||||
const onError: ErrorRequestHandler = (err, req, res, next) => {
|
||||
if (err.code === 'EBADCSRFTOKEN')
|
||||
return res.status(400).send('Invalid CSRF token!')
|
||||
|
||||
/***********************************
|
||||
* Enabling CORS *
|
||||
***********************************/
|
||||
if (CORS === CorsType.ENABLED) {
|
||||
const whiteList: string[] = []
|
||||
WHITELIST?.split(' ')
|
||||
?.filter((url) => !!url)
|
||||
.forEach((url) => {
|
||||
if (url.startsWith('http'))
|
||||
// removing trailing slash of URLs listing for CORS
|
||||
whiteList.push(url.replace(/\/$/, ''))
|
||||
})
|
||||
|
||||
console.log('All CORS Requests are enabled for:', whiteList)
|
||||
app.use(cors({ credentials: true, origin: whiteList }))
|
||||
console.error(err.stack)
|
||||
res.status(500).send('Something broke!')
|
||||
}
|
||||
|
||||
export default setProcessVariables().then(async () => {
|
||||
app.use(cookieParser())
|
||||
|
||||
configureLogger(app)
|
||||
|
||||
/***********************************
|
||||
* Handle security and origin *
|
||||
***********************************/
|
||||
configureSecurity(app)
|
||||
|
||||
/***********************************
|
||||
* Enabling CORS *
|
||||
***********************************/
|
||||
configureCors(app)
|
||||
|
||||
/***********************************
|
||||
* DB Connection & *
|
||||
* Express Sessions *
|
||||
* With Mongo Store *
|
||||
***********************************/
|
||||
if (MODE === ModeType.Server) {
|
||||
let store: MongoStore | undefined
|
||||
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
store = MongoStore.create({
|
||||
client: mongoose.connection!.getClient() as any,
|
||||
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
|
||||
})
|
||||
)
|
||||
}
|
||||
configureExpressSession(app)
|
||||
|
||||
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!')
|
||||
|
||||
console.error(err.stack)
|
||||
res.status(500).send('Something broke!')
|
||||
}
|
||||
|
||||
await setupFolders()
|
||||
await copySASjsCore()
|
||||
|
||||
|
||||
@@ -101,8 +101,8 @@ ${autoExecContent}`
|
||||
session.path,
|
||||
'-AUTOEXEC',
|
||||
autoExecPath,
|
||||
isWindows() ? '-nosplash' : '',
|
||||
isWindows() ? '-icon' : '',
|
||||
process.sasLoc!.endsWith('sas.exe') ? '-nosplash' : '',
|
||||
process.sasLoc!.endsWith('sas.exe') ? '-icon' : '',
|
||||
isWindows() ? '-nologo' : ''
|
||||
])
|
||||
.then(() => {
|
||||
|
||||
1
api/src/types/system/process.d.ts
vendored
1
api/src/types/system/process.d.ts
vendored
@@ -3,6 +3,7 @@ declare namespace NodeJS {
|
||||
sasLoc?: string
|
||||
nodeLoc?: string
|
||||
driveLoc: string
|
||||
logsLoc: string
|
||||
sasSessionController?: import('../../controllers/internal').SASSessionController
|
||||
jsSessionController?: import('../../controllers/internal').JSSessionController
|
||||
appStreamConfig: import('../').AppStreamConfig
|
||||
|
||||
@@ -22,6 +22,8 @@ export const getDesktopUserAutoExecPath = () =>
|
||||
|
||||
export const getSasjsRootFolder = () => process.driveLoc
|
||||
|
||||
export const getLogFolder = () => process.logsLoc
|
||||
|
||||
export const getAppStreamConfigPath = () =>
|
||||
path.join(getSasjsRootFolder(), 'appStreamConfig.json')
|
||||
|
||||
@@ -32,8 +34,6 @@ export const getUploadsFolder = () => path.join(getSasjsRootFolder(), 'uploads')
|
||||
|
||||
export const getFilesFolder = () => path.join(getSasjsRootFolder(), 'files')
|
||||
|
||||
export const getLogFolder = () => path.join(getSasjsRootFolder(), 'logs')
|
||||
|
||||
export const getWeboutFolder = () => path.join(getSasjsRootFolder(), 'webouts')
|
||||
|
||||
export const getSessionsFolder = () =>
|
||||
|
||||
@@ -40,7 +40,16 @@ export const setProcessVariables = async () => {
|
||||
await createFolder(absPath)
|
||||
process.driveLoc = getRealPath(absPath)
|
||||
|
||||
const { LOG_LOCATION } = process.env
|
||||
const absLogsPath = getAbsolutePath(
|
||||
LOG_LOCATION ?? `sasjs_root${path.sep}logs`,
|
||||
process.cwd()
|
||||
)
|
||||
await createFolder(absLogsPath)
|
||||
process.logsLoc = getRealPath(absLogsPath)
|
||||
|
||||
console.log('sasLoc: ', process.sasLoc)
|
||||
console.log('sasDrive: ', process.driveLoc)
|
||||
console.log('sasLogs: ', process.logsLoc)
|
||||
console.log('runTimes: ', process.runTimes)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user