1
0
mirror of https://github.com/sasjs/server.git synced 2026-01-17 19:00:05 +00:00

chore: merge main into issue-198

This commit is contained in:
2022-07-22 22:31:32 +05:00
19 changed files with 2281 additions and 3099 deletions

3
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
# These are supported funding model platforms
github: [sasjs]

View File

@@ -1,3 +1,51 @@
## [0.11.5](https://github.com/sasjs/server/compare/v0.11.4...v0.11.5) (2022-07-19)
### Bug Fixes
* Revert "fix(security): missing cookie flags are added" ([ce5218a](https://github.com/sasjs/server/commit/ce5218a2278cc750f2b1032024685dc6cd72f796))
## [0.11.4](https://github.com/sasjs/server/compare/v0.11.3...v0.11.4) (2022-07-19)
### Bug Fixes
* **security:** missing cookie flags are added ([526402f](https://github.com/sasjs/server/commit/526402fd73407ee4fa2d31092111a7e6a1741487))
## [0.11.3](https://github.com/sasjs/server/compare/v0.11.2...v0.11.3) (2022-07-19)
### Bug Fixes
* filePath fix in code.js file for windows ([2995121](https://github.com/sasjs/server/commit/299512135d77c2ac9e34853cf35aee6f2e1d4da4))
## [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) # [0.10.0](https://github.com/sasjs/server/compare/v0.9.0...v0.10.0) (2022-07-06)

View File

@@ -136,6 +136,9 @@ HELMET_CSP_CONFIG_PATH=./csp.config.json
# Docs: https://www.npmjs.com/package/morgan#predefined-formats # Docs: https://www.npmjs.com/package/morgan#predefined-formats
LOG_FORMAT_MORGAN= 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. # A comma separated string that defines the available runTimes.
# Priority is given to the runtime that comes first in the string. # Priority is given to the runtime that comes first in the string.
# Possible options at the moment are sas and js # Possible options at the moment are sas and js

View File

@@ -21,3 +21,4 @@ NODE_PATH=~/.nvm/versions/node/v16.14.0/bin/node
SASJS_ROOT=./sasjs_root SASJS_ROOT=./sasjs_root
LOG_FORMAT_MORGAN=common LOG_FORMAT_MORGAN=common
LOG_LOCATION=./sasjs_root/logs

1252
api/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@
"initial": "npm run swagger && npm run compileSysInit && npm run copySASjsCore", "initial": "npm run swagger && npm run compileSysInit && npm run copySASjsCore",
"prestart": "npm run initial", "prestart": "npm run initial",
"prebuild": "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", "start:prod": "node ./build/src/server.js",
"build": "rimraf build && tsc", "build": "rimraf build && tsc",
"postbuild": "npm run copy:files", "postbuild": "npm run copy:files",
@@ -63,6 +63,7 @@
"mongoose-sequence": "^5.3.1", "mongoose-sequence": "^5.3.1",
"morgan": "^1.10.0", "morgan": "^1.10.0",
"multer": "^1.4.3", "multer": "^1.4.3",
"rotating-file-stream": "^3.0.4",
"swagger-ui-express": "4.3.0", "swagger-ui-express": "4.3.0",
"unzipper": "^0.10.11", "unzipper": "^0.10.11",
"url": "^0.10.3" "url": "^0.10.3"

View File

@@ -85,10 +85,8 @@ components:
type: string type: string
_webout: _webout:
anyOf: anyOf:
- - type: string
type: string - $ref: '#/components/schemas/IRecordOfAny'
-
$ref: '#/components/schemas/IRecordOfAny'
log: log:
items: items:
$ref: '#/components/schemas/LogLine' $ref: '#/components/schemas/LogLine'
@@ -137,12 +135,9 @@ components:
members: members:
items: items:
anyOf: anyOf:
- - $ref: '#/components/schemas/FolderMember'
$ref: '#/components/schemas/FolderMember' - $ref: '#/components/schemas/ServiceMember'
- - $ref: '#/components/schemas/FileMember'
$ref: '#/components/schemas/ServiceMember'
-
$ref: '#/components/schemas/FileMember'
type: array type: array
required: required:
- name - name
@@ -191,12 +186,9 @@ components:
members: members:
items: items:
anyOf: anyOf:
- - $ref: '#/components/schemas/FolderMember'
$ref: '#/components/schemas/FolderMember' - $ref: '#/components/schemas/ServiceMember'
- - $ref: '#/components/schemas/FileMember'
$ref: '#/components/schemas/ServiceMember'
-
$ref: '#/components/schemas/FileMember'
type: array type: array
required: required:
- members - members
@@ -382,7 +374,7 @@ components:
autoExec: autoExec:
type: string type: string
description: 'User-specific auto-exec code' description: 'User-specific auto-exec code'
example: "" example: ''
required: required:
- displayName - displayName
- username - username
@@ -619,7 +611,11 @@ paths:
$ref: '#/components/schemas/TokenResponse' $ref: '#/components/schemas/TokenResponse'
examples: examples:
'Example 1': 'Example 1':
value: {accessToken: someRandomCryptoString, refreshToken: someRandomCryptoString} value:
{
accessToken: someRandomCryptoString,
refreshToken: someRandomCryptoString
}
summary: 'Accepts client/auth code and returns access/refresh tokens' summary: 'Accepts client/auth code and returns access/refresh tokens'
tags: tags:
- Auth - Auth
@@ -643,13 +639,16 @@ paths:
$ref: '#/components/schemas/TokenResponse' $ref: '#/components/schemas/TokenResponse'
examples: examples:
'Example 1': 'Example 1':
value: {accessToken: someRandomCryptoString, refreshToken: someRandomCryptoString} value:
{
accessToken: someRandomCryptoString,
refreshToken: someRandomCryptoString
}
summary: 'Returns new access/refresh tokens' summary: 'Returns new access/refresh tokens'
tags: tags:
- Auth - Auth
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: [] parameters: []
/SASjsApi/auth/logout: /SASjsApi/auth/logout:
post: post:
@@ -661,8 +660,7 @@ paths:
tags: tags:
- Auth - Auth
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: [] parameters: []
/SASjsApi/client: /SASjsApi/client:
post: post:
@@ -676,13 +674,16 @@ paths:
$ref: '#/components/schemas/ClientPayload' $ref: '#/components/schemas/ClientPayload'
examples: examples:
'Example 1': 'Example 1':
value: {clientId: someFormattedClientID1234, clientSecret: someRandomCryptoString} value:
{
clientId: someFormattedClientID1234,
clientSecret: someRandomCryptoString
}
summary: 'Create client with the following attributes: ClientId, ClientSecret. Admin only task.' summary: 'Create client with the following attributes: ClientId, ClientSecret. Admin only task.'
tags: tags:
- Client - Client
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: [] parameters: []
requestBody: requestBody:
required: true required: true
@@ -705,8 +706,7 @@ paths:
tags: tags:
- CODE - CODE
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: [] parameters: []
requestBody: requestBody:
required: true required: true
@@ -726,7 +726,11 @@ paths:
$ref: '#/components/schemas/DeployResponse' $ref: '#/components/schemas/DeployResponse'
examples: examples:
'Example 1': 'Example 1':
value: {status: success, message: 'Files deployed successfully to @sasjs/server.'} value:
{
status: success,
message: 'Files deployed successfully to @sasjs/server.'
}
'400': '400':
description: 'Invalid Format' description: 'Invalid Format'
content: content:
@@ -735,7 +739,11 @@ paths:
$ref: '#/components/schemas/DeployResponse' $ref: '#/components/schemas/DeployResponse'
examples: examples:
'Example 1': 'Example 1':
value: {status: failure, message: 'Provided not supported data format.'} value:
{
status: failure,
message: 'Provided not supported data format.'
}
'500': '500':
description: 'Execution Error' description: 'Execution Error'
content: content:
@@ -744,13 +752,12 @@ paths:
$ref: '#/components/schemas/DeployResponse' $ref: '#/components/schemas/DeployResponse'
examples: examples:
'Example 1': 'Example 1':
value: {status: failure, message: 'Deployment failed!'} value: { status: failure, message: 'Deployment failed!' }
summary: 'Creates/updates files within SASjs Drive using provided payload.' summary: 'Creates/updates files within SASjs Drive using provided payload.'
tags: tags:
- Drive - Drive
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: [] parameters: []
requestBody: requestBody:
required: true required: true
@@ -770,7 +777,11 @@ paths:
$ref: '#/components/schemas/DeployResponse' $ref: '#/components/schemas/DeployResponse'
examples: examples:
'Example 1': 'Example 1':
value: {status: success, message: 'Files deployed successfully to @sasjs/server.'} value:
{
status: success,
message: 'Files deployed successfully to @sasjs/server.'
}
'400': '400':
description: 'Invalid Format' description: 'Invalid Format'
content: content:
@@ -779,7 +790,11 @@ paths:
$ref: '#/components/schemas/DeployResponse' $ref: '#/components/schemas/DeployResponse'
examples: examples:
'Example 1': 'Example 1':
value: {status: failure, message: 'Provided not supported data format.'} value:
{
status: failure,
message: 'Provided not supported data format.'
}
'500': '500':
description: 'Execution Error' description: 'Execution Error'
content: content:
@@ -788,14 +803,13 @@ paths:
$ref: '#/components/schemas/DeployResponse' $ref: '#/components/schemas/DeployResponse'
examples: examples:
'Example 1': 'Example 1':
value: {status: failure, message: 'Deployment failed!'} value: { status: failure, message: 'Deployment failed!' }
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!" 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.' summary: 'Creates/updates files within SASjs Drive using uploaded JSON/compressed JSON file.'
tags: tags:
- Drive - Drive
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: [] parameters: []
requestBody: requestBody:
required: true required: true
@@ -819,11 +833,9 @@ paths:
tags: tags:
- Drive - Drive
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: parameters:
- - in: query
in: query
name: _filePath name: _filePath
required: true required: true
schema: schema:
@@ -838,7 +850,7 @@ paths:
application/json: application/json:
schema: schema:
properties: properties:
status: {type: string} status: { type: string }
required: required:
- status - status
type: object type: object
@@ -846,11 +858,9 @@ paths:
tags: tags:
- Drive - Drive
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: parameters:
- - in: query
in: query
name: _filePath name: _filePath
required: true required: true
schema: schema:
@@ -867,7 +877,7 @@ paths:
$ref: '#/components/schemas/FileFolderResponse' $ref: '#/components/schemas/FileFolderResponse'
examples: examples:
'Example 1': 'Example 1':
value: {status: success} value: { status: success }
'403': '403':
description: 'File already exists' description: 'File already exists'
content: content:
@@ -876,17 +886,15 @@ paths:
$ref: '#/components/schemas/FileFolderResponse' $ref: '#/components/schemas/FileFolderResponse'
examples: examples:
'Example 1': 'Example 1':
value: {status: failure, message: 'File request failed.'} value: { status: failure, message: 'File request failed.' }
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." 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' summary: 'Create a file in SASjs Drive'
tags: tags:
- Drive - Drive
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: parameters:
- - description: 'Location of file'
description: 'Location of file'
in: query in: query
name: _filePath name: _filePath
required: false required: false
@@ -918,26 +926,24 @@ paths:
$ref: '#/components/schemas/FileFolderResponse' $ref: '#/components/schemas/FileFolderResponse'
examples: examples:
'Example 1': 'Example 1':
value: {status: success} value: { status: success }
'403': '403':
description: "" description: ''
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/FileFolderResponse' $ref: '#/components/schemas/FileFolderResponse'
examples: examples:
'Example 1': 'Example 1':
value: {status: failure, message: 'File request failed.'} value: { status: failure, message: 'File request failed.' }
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." 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' summary: 'Modify a file in SASjs Drive'
tags: tags:
- Drive - Drive
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: parameters:
- - description: 'Location of SAS program'
description: 'Location of SAS program'
in: query in: query
name: _filePath name: _filePath
required: false required: false
@@ -968,8 +974,8 @@ paths:
application/json: application/json:
schema: schema:
properties: properties:
folders: {items: {type: string}, type: array} folders: { items: { type: string }, type: array }
files: {items: {type: string}, type: array} files: { items: { type: string }, type: array }
required: required:
- folders - folders
- files - files
@@ -978,11 +984,9 @@ paths:
tags: tags:
- Drive - Drive
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: parameters:
- - in: query
in: query
name: _folderPath name: _folderPath
required: false required: false
schema: schema:
@@ -997,7 +1001,7 @@ paths:
application/json: application/json:
schema: schema:
properties: properties:
status: {type: string} status: { type: string }
required: required:
- status - status
type: object type: object
@@ -1005,11 +1009,9 @@ paths:
tags: tags:
- Drive - Drive
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: parameters:
- - in: query
in: query
name: _folderPath name: _folderPath
required: true required: true
schema: schema:
@@ -1026,7 +1028,7 @@ paths:
$ref: '#/components/schemas/FileFolderResponse' $ref: '#/components/schemas/FileFolderResponse'
examples: examples:
'Example 1': 'Example 1':
value: {status: success} value: { status: success }
'409': '409':
description: 'Folder already exists' description: 'Folder already exists'
content: content:
@@ -1035,13 +1037,13 @@ paths:
$ref: '#/components/schemas/FileFolderResponse' $ref: '#/components/schemas/FileFolderResponse'
examples: examples:
'Example 1': 'Example 1':
value: {status: failure, message: 'Add folder request failed.'} value:
{ status: failure, message: 'Add folder request failed.' }
summary: 'Create an empty folder in SASjs Drive' summary: 'Create an empty folder in SASjs Drive'
tags: tags:
- Drive - Drive
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: [] parameters: []
requestBody: requestBody:
required: true required: true
@@ -1061,7 +1063,7 @@ paths:
$ref: '#/components/schemas/FileFolderResponse' $ref: '#/components/schemas/FileFolderResponse'
examples: examples:
'Example 1': 'Example 1':
value: {status: success} value: { status: success }
'409': '409':
description: 'Folder already exists' description: 'Folder already exists'
content: content:
@@ -1070,13 +1072,12 @@ paths:
$ref: '#/components/schemas/FileFolderResponse' $ref: '#/components/schemas/FileFolderResponse'
examples: examples:
'Example 1': 'Example 1':
value: {status: failure, message: 'rename request failed.'} value: { status: failure, message: 'rename request failed.' }
summary: 'Renames a file/folder in SASjs Drive' summary: 'Renames a file/folder in SASjs Drive'
tags: tags:
- Drive - Drive
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: [] parameters: []
requestBody: requestBody:
required: true required: true
@@ -1098,8 +1099,7 @@ paths:
tags: tags:
- Drive - Drive
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: [] parameters: []
/SASjsApi/user: /SASjsApi/user:
get: get:
@@ -1115,13 +1115,26 @@ paths:
type: array type: array
examples: examples:
'Example 1': 'Example 1':
value: [{id: 123, username: johnusername, displayName: John, isAdmin: false}, {id: 456, username: starkusername, displayName: Stark, isAdmin: true}] value:
[
{
id: 123,
username: johnusername,
displayName: John,
isAdmin: false
},
{
id: 456,
username: starkusername,
displayName: Stark,
isAdmin: true
}
]
summary: 'Get list of all users (username, displayname). All users can request this.' summary: 'Get list of all users (username, displayname). All users can request this.'
tags: tags:
- User - User
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: [] parameters: []
post: post:
operationId: CreateUser operationId: CreateUser
@@ -1134,13 +1147,19 @@ paths:
$ref: '#/components/schemas/UserDetailsResponse' $ref: '#/components/schemas/UserDetailsResponse'
examples: examples:
'Example 1': 'Example 1':
value: {id: 1234, displayName: 'John Snow', username: johnSnow01, isAdmin: false, isActive: true} value:
{
id: 1234,
displayName: 'John Snow',
username: johnSnow01,
isAdmin: false,
isActive: true
}
summary: 'Create user with the following attributes: UserId, UserName, Password, isAdmin, isActive. Admin only task.' summary: 'Create user with the following attributes: UserId, UserName, Password, isAdmin, isActive. Admin only task.'
tags: tags:
- User - User
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: [] parameters: []
requestBody: requestBody:
required: true required: true
@@ -1163,11 +1182,9 @@ paths:
tags: tags:
- User - User
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: parameters:
- - description: "The User's username"
description: 'The User''s username'
in: path in: path
name: username name: username
required: true required: true
@@ -1185,16 +1202,21 @@ paths:
$ref: '#/components/schemas/UserDetailsResponse' $ref: '#/components/schemas/UserDetailsResponse'
examples: examples:
'Example 1': 'Example 1':
value: {id: 1234, displayName: 'John Snow', username: johnSnow01, isAdmin: false, isActive: true} value:
{
id: 1234,
displayName: 'John Snow',
username: johnSnow01,
isAdmin: false,
isActive: true
}
summary: 'Update user properties - such as displayName. Can be performed either by admins, or the user in question.' summary: 'Update user properties - such as displayName. Can be performed either by admins, or the user in question.'
tags: tags:
- User - User
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: parameters:
- - description: "The User's username"
description: 'The User''s username'
in: path in: path
name: username name: username
required: true required: true
@@ -1216,11 +1238,9 @@ paths:
tags: tags:
- User - User
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: parameters:
- - description: "The User's username"
description: 'The User''s username'
in: path in: path
name: username name: username
required: true required: true
@@ -1251,11 +1271,9 @@ paths:
tags: tags:
- User - User
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: parameters:
- - description: "The user's identifier"
description: 'The user''s identifier'
in: path in: path
name: userId name: userId
required: true required: true
@@ -1274,16 +1292,21 @@ paths:
$ref: '#/components/schemas/UserDetailsResponse' $ref: '#/components/schemas/UserDetailsResponse'
examples: examples:
'Example 1': 'Example 1':
value: {id: 1234, displayName: 'John Snow', username: johnSnow01, isAdmin: false, isActive: true} value:
{
id: 1234,
displayName: 'John Snow',
username: johnSnow01,
isAdmin: false,
isActive: true
}
summary: 'Update user properties - such as displayName. Can be performed either by admins, or the user in question.' summary: 'Update user properties - such as displayName. Can be performed either by admins, or the user in question.'
tags: tags:
- User - User
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: parameters:
- - description: "The user's identifier"
description: 'The user''s identifier'
in: path in: path
name: userId name: userId
required: true required: true
@@ -1306,11 +1329,9 @@ paths:
tags: tags:
- User - User
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: parameters:
- - description: "The user's identifier"
description: 'The user''s identifier'
in: path in: path
name: userId name: userId
required: true required: true
@@ -1341,13 +1362,19 @@ paths:
type: array type: array
examples: examples:
'Example 1': 'Example 1':
value: [{groupId: 123, name: DCGroup, description: 'This group represents Data Controller Users'}] value:
[
{
groupId: 123,
name: DCGroup,
description: 'This group represents Data Controller Users'
}
]
summary: 'Get list of all groups (groupName and groupDescription). All users can request this.' summary: 'Get list of all groups (groupName and groupDescription). All users can request this.'
tags: tags:
- Group - Group
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: [] parameters: []
post: post:
operationId: CreateGroup operationId: CreateGroup
@@ -1360,13 +1387,19 @@ paths:
$ref: '#/components/schemas/GroupDetailsResponse' $ref: '#/components/schemas/GroupDetailsResponse'
examples: examples:
'Example 1': 'Example 1':
value: {groupId: 123, name: DCGroup, description: 'This group represents Data Controller Users', isActive: true, users: []} value:
{
groupId: 123,
name: DCGroup,
description: 'This group represents Data Controller Users',
isActive: true,
users: []
}
summary: 'Create a new group. Admin only.' summary: 'Create a new group. Admin only.'
tags: tags:
- Group - Group
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: [] parameters: []
requestBody: requestBody:
required: true required: true
@@ -1388,11 +1421,9 @@ paths:
tags: tags:
- Group - Group
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: parameters:
- - description: "The group's name"
description: 'The group''s name'
in: path in: path
name: name name: name
required: true required: true
@@ -1412,11 +1443,9 @@ paths:
tags: tags:
- Group - Group
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: parameters:
- - description: "The group's identifier"
description: 'The group''s identifier'
in: path in: path
name: groupId name: groupId
required: true required: true
@@ -1433,17 +1462,15 @@ paths:
application/json: application/json:
schema: schema:
allOf: allOf:
- {$ref: '#/components/schemas/IGroup'} - { $ref: '#/components/schemas/IGroup' }
- {properties: {_id: {}}, required: [_id], type: object} - { properties: { _id: {} }, required: [_id], type: object }
summary: 'Delete a group. Admin task only.' summary: 'Delete a group. Admin task only.'
tags: tags:
- Group - Group
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: parameters:
- - description: "The group's identifier"
description: 'The group''s identifier'
in: path in: path
name: groupId name: groupId
required: true required: true
@@ -1463,16 +1490,21 @@ paths:
$ref: '#/components/schemas/GroupDetailsResponse' $ref: '#/components/schemas/GroupDetailsResponse'
examples: examples:
'Example 1': 'Example 1':
value: {groupId: 123, name: DCGroup, description: 'This group represents Data Controller Users', isActive: true, users: []} value:
{
groupId: 123,
name: DCGroup,
description: 'This group represents Data Controller Users',
isActive: true,
users: []
}
summary: 'Add a user to a group. Admin task only.' summary: 'Add a user to a group. Admin task only.'
tags: tags:
- Group - Group
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: parameters:
- - description: "The group's identifier"
description: 'The group''s identifier'
in: path in: path
name: groupId name: groupId
required: true required: true
@@ -1480,8 +1512,7 @@ paths:
format: double format: double
type: number type: number
example: '1234' example: '1234'
- - description: "The user's identifier"
description: 'The user''s identifier'
in: path in: path
name: userId name: userId
required: true required: true
@@ -1500,16 +1531,21 @@ paths:
$ref: '#/components/schemas/GroupDetailsResponse' $ref: '#/components/schemas/GroupDetailsResponse'
examples: examples:
'Example 1': 'Example 1':
value: {groupId: 123, name: DCGroup, description: 'This group represents Data Controller Users', isActive: true, users: []} value:
{
groupId: 123,
name: DCGroup,
description: 'This group represents Data Controller Users',
isActive: true,
users: []
}
summary: 'Remove a user to a group. Admin task only.' summary: 'Remove a user to a group. Admin task only.'
tags: tags:
- Group - Group
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: parameters:
- - description: "The group's identifier"
description: 'The group''s identifier'
in: path in: path
name: groupId name: groupId
required: true required: true
@@ -1517,8 +1553,7 @@ paths:
format: double format: double
type: number type: number
example: '1234' example: '1234'
- - description: "The user's identifier"
description: 'The user''s identifier'
in: path in: path
name: userId name: userId
required: true required: true
@@ -1538,7 +1573,14 @@ paths:
$ref: '#/components/schemas/InfoResponse' $ref: '#/components/schemas/InfoResponse'
examples: examples:
'Example 1': 'Example 1':
value: {mode: desktop, cors: enable, whiteList: ['http://example.com', 'http://example2.com'], protocol: http, runTimes: [sas, js]} value:
{
mode: desktop,
cors: enable,
whiteList: ['http://example.com', 'http://example2.com'],
protocol: http,
runTimes: [sas, js]
}
summary: 'Get server info (mode, cors, whiteList, protocol).' summary: 'Get server info (mode, cors, whiteList, protocol).'
tags: tags:
- Info - Info
@@ -1556,7 +1598,7 @@ paths:
$ref: '#/components/schemas/AuthorizedRoutesResponse' $ref: '#/components/schemas/AuthorizedRoutesResponse'
examples: examples:
'Example 1': 'Example 1':
value: {URIs: [/AppStream, /SASjsApi/stp/execute]} value: { URIs: [/AppStream, /SASjsApi/stp/execute] }
summary: 'Get authorized routes.' summary: 'Get authorized routes.'
tags: tags:
- Info - Info
@@ -1574,13 +1616,18 @@ paths:
$ref: '#/components/schemas/UserResponse' $ref: '#/components/schemas/UserResponse'
examples: examples:
'Example 1': 'Example 1':
value: {id: 123, username: johnusername, displayName: John, isAdmin: false} value:
{
id: 123,
username: johnusername,
displayName: John,
isAdmin: false
}
summary: 'Get session info (username).' summary: 'Get session info (username).'
tags: tags:
- Session - Session
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: [] parameters: []
/SASjsApi/stp/execute: /SASjsApi/stp/execute:
get: get:
@@ -1592,18 +1639,16 @@ paths:
application/json: application/json:
schema: schema:
anyOf: anyOf:
- {type: string} - { type: string }
- {type: string, format: byte} - { type: string, format: byte }
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" 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.' summary: 'Execute a Stored Program, returns raw _webout content.'
tags: tags:
- STP - STP
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: parameters:
- - description: 'Location of SAS or JS code'
description: 'Location of SAS or JS code'
in: query in: query
name: _program name: _program
required: true required: true
@@ -1621,17 +1666,25 @@ paths:
$ref: '#/components/schemas/ExecuteReturnJsonResponse' $ref: '#/components/schemas/ExecuteReturnJsonResponse'
examples: examples:
'Example 1': 'Example 1':
value: {status: success, _webout: 'webout content', log: [], httpHeaders: {Content-type: application/zip, Cache-Control: 'public, max-age=1000'}} 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.\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." 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' summary: 'Execute a Stored Program, return a JSON object'
tags: tags:
- STP - STP
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: parameters:
- - description: 'Location of SAS or JS code'
description: 'Location of SAS or JS code'
in: query in: query
name: _program name: _program
required: false required: false
@@ -1669,8 +1722,19 @@ paths:
application/json: application/json:
schema: schema:
properties: properties:
user: {properties: {isAdmin: {type: boolean}, displayName: {type: string}, username: {type: string}, id: {type: number, format: double}}, required: [isAdmin, displayName, username, id], type: object} user:
loggedIn: {type: boolean} {
properties:
{
isAdmin: { type: boolean },
displayName: { type: string },
username: { type: string },
id: { type: number, format: double }
},
required: [isAdmin, displayName, username, id],
type: object
}
loggedIn: { type: boolean }
required: required:
- user - user
- loggedIn - loggedIn
@@ -1698,7 +1762,7 @@ paths:
$ref: '#/components/schemas/AuthorizeResponse' $ref: '#/components/schemas/AuthorizeResponse'
examples: examples:
'Example 1': 'Example 1':
value: {code: someRandomCryptoString} value: { code: someRandomCryptoString }
summary: 'Accept a valid username/password, plus a CLIENT_ID, and return an AUTH_CODE' summary: 'Accept a valid username/password, plus a CLIENT_ID, and return an AUTH_CODE'
tags: tags:
- Web - Web
@@ -1738,13 +1802,39 @@ paths:
type: array type: array
examples: examples:
'Example 1': 'Example 1':
value: [{permissionId: 123, uri: /SASjsApi/code/execute, setting: Grant, user: {id: 1, username: johnSnow01, displayName: 'John Snow', isAdmin: false}}, {permissionId: 124, uri: /SASjsApi/code/execute, setting: Grant, group: {groupId: 1, name: DCGroup, description: 'This group represents Data Controller Users', isActive: true, users: []}}] value:
[
{
permissionId: 123,
uri: /SASjsApi/code/execute,
setting: Grant,
user:
{
id: 1,
username: johnSnow01,
displayName: 'John Snow',
isAdmin: false
}
},
{
permissionId: 124,
uri: /SASjsApi/code/execute,
setting: Grant,
group:
{
groupId: 1,
name: DCGroup,
description: 'This group represents Data Controller Users',
isActive: true,
users: []
}
}
]
summary: 'Get list of all permissions (uri, setting and userDetail).' summary: 'Get list of all permissions (uri, setting and userDetail).'
tags: tags:
- Permission - Permission
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: [] parameters: []
post: post:
operationId: CreatePermission operationId: CreatePermission
@@ -1757,13 +1847,24 @@ paths:
$ref: '#/components/schemas/PermissionDetailsResponse' $ref: '#/components/schemas/PermissionDetailsResponse'
examples: examples:
'Example 1': 'Example 1':
value: {permissionId: 123, uri: /SASjsApi/code/execute, setting: Grant, user: {id: 1, username: johnSnow01, displayName: 'John Snow', isAdmin: false}} value:
{
permissionId: 123,
uri: /SASjsApi/code/execute,
setting: Grant,
user:
{
id: 1,
username: johnSnow01,
displayName: 'John Snow',
isAdmin: false
}
}
summary: 'Create a new permission. Admin only.' summary: 'Create a new permission. Admin only.'
tags: tags:
- Permission - Permission
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: [] parameters: []
requestBody: requestBody:
required: true required: true
@@ -1783,16 +1884,26 @@ paths:
$ref: '#/components/schemas/PermissionDetailsResponse' $ref: '#/components/schemas/PermissionDetailsResponse'
examples: examples:
'Example 1': 'Example 1':
value: {permissionId: 123, uri: /SASjsApi/code/execute, setting: Grant, user: {id: 1, username: johnSnow01, displayName: 'John Snow', isAdmin: false}} value:
{
permissionId: 123,
uri: /SASjsApi/code/execute,
setting: Grant,
user:
{
id: 1,
username: johnSnow01,
displayName: 'John Snow',
isAdmin: false
}
}
summary: 'Update permission setting. Admin only' summary: 'Update permission setting. Admin only'
tags: tags:
- Permission - Permission
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: parameters:
- - description: "The permission's identifier"
description: 'The permission''s identifier'
in: path in: path
name: permissionId name: permissionId
required: true required: true
@@ -1815,11 +1926,9 @@ paths:
tags: tags:
- Permission - Permission
security: security:
- - bearerAuth: []
bearerAuth: []
parameters: parameters:
- - description: "The user's identifier"
description: 'The user''s identifier'
in: path in: path
name: permissionId name: permissionId
required: true required: true
@@ -1828,39 +1937,27 @@ paths:
type: number type: number
example: 1234 example: 1234
servers: servers:
- - url: /
url: /
tags: tags:
- - name: Auth
name: Auth
description: 'Operations about auth' description: 'Operations about auth'
- - name: Client
name: Client
description: 'Operations about clients' description: 'Operations about clients'
- - name: CODE
name: CODE
description: 'Execution of code (various runtimes are supported)' description: 'Execution of code (various runtimes are supported)'
- - name: Drive
name: Drive
description: 'Operations on SASjs Drive' description: 'Operations on SASjs Drive'
- - name: Group
name: Group
description: 'Operations on groups and group memberships' description: 'Operations on groups and group memberships'
- - name: Info
name: Info
description: 'Get Server Information' description: 'Get Server Information'
- - name: Permission
name: Permission
description: 'Operations about permissions' description: 'Operations about permissions'
- - name: Session
name: Session
description: 'Get Session information' description: 'Get Session information'
- - name: STP
name: STP
description: 'Execution of Stored Programs' description: 'Execution of Stored Programs'
- - name: User
name: User
description: 'Operations with users' description: 'Operations with users'
- - name: Web
name: Web
description: 'Operations on Web' description: 'Operations on Web'

View 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 }))
}
}

View 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
})
)
}
}

View 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))
}

View 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
})
)
}

View File

@@ -0,0 +1,4 @@
export * from './configureCors'
export * from './configureExpressSession'
export * from './configureLogger'
export * from './configureSecurity'

View File

@@ -1,30 +1,26 @@
import path from 'path' import path from 'path'
import express, { ErrorRequestHandler } from 'express' import express, { ErrorRequestHandler } from 'express'
import mongoose from 'mongoose'
import csrf from 'csurf' import csrf from 'csurf'
import session from 'express-session'
import MongoStore from 'connect-mongo'
import morgan from 'morgan'
import cookieParser from 'cookie-parser' import cookieParser from 'cookie-parser'
import dotenv from 'dotenv' import dotenv from 'dotenv'
import cors from 'cors'
import helmet from 'helmet'
import { import {
copySASjsCore, copySASjsCore,
CorsType,
getWebBuildFolder, getWebBuildFolder,
HelmetCoepType,
instantiateLogger, instantiateLogger,
loadAppStreamConfig, loadAppStreamConfig,
ModeType,
ProtocolType, ProtocolType,
ReturnCode, ReturnCode,
setProcessVariables, setProcessVariables,
setupFolders, setupFolders,
verifyEnvVariables verifyEnvVariables
} from './utils' } from './utils'
import { getEnvCSPDirectives } from './utils/parseHelmetConfig' import {
configureCors,
configureExpressSession,
configureLogger,
configureSecurity
} from './app-modules'
dotenv.config() dotenv.config()
@@ -34,19 +30,7 @@ if (verifyEnvVariables()) process.exit(ReturnCode.InvalidEnv)
const app = express() const app = express()
app.use(cookieParser()) const { PROTOCOL } = process.env
const {
MODE,
CORS,
WHITELIST,
PROTOCOL,
HELMET_CSP_CONFIG_PATH,
HELMET_COEP,
LOG_FORMAT_MORGAN
} = process.env
app.use(morgan(LOG_FORMAT_MORGAN as string))
export const cookieOptions = { export const cookieOptions = {
secure: PROTOCOL === ProtocolType.HTTPS, secure: PROTOCOL === ProtocolType.HTTPS,
@@ -54,86 +38,43 @@ export const cookieOptions = {
maxAge: 24 * 60 * 60 * 1000 // 24 hours 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 * * CSRF Protection *
***********************************/ ***********************************/
export const csrfProtection = csrf({ cookie: cookieOptions }) export const csrfProtection = csrf({ cookie: cookieOptions })
/*********************************** const onError: ErrorRequestHandler = (err, req, res, next) => {
* Handle security and origin *
***********************************/
app.use(
helmet({
contentSecurityPolicy: {
directives: {
...helmet.contentSecurityPolicy.getDefaultDirectives(),
...cspConfigJson
}
},
crossOriginEmbedderPolicy: HELMET_COEP === HelmetCoepType.TRUE
})
)
/***********************************
* 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 }))
}
export default setProcessVariables().then(async () => {
/***********************************
* 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
})
)
}
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') if (err.code === 'EBADCSRFTOKEN')
return res.status(400).send('Invalid CSRF token!') return res.status(400).send('Invalid CSRF token!')
console.error(err.stack) console.error(err.stack)
res.status(500).send('Something broke!') 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 *
***********************************/
configureExpressSession(app)
app.use(express.json({ limit: '100mb' }))
app.use(express.static(path.join(__dirname, '../public')))
await setupFolders() await setupFolders()
await copySASjsCore() await copySASjsCore()

View File

@@ -101,8 +101,8 @@ ${autoExecContent}`
session.path, session.path,
'-AUTOEXEC', '-AUTOEXEC',
autoExecPath, autoExecPath,
isWindows() ? '-nosplash' : '', process.sasLoc!.endsWith('sas.exe') ? '-nosplash' : '',
isWindows() ? '-icon' : '', process.sasLoc!.endsWith('sas.exe') ? '-icon' : '',
isWindows() ? '-nologo' : '' isWindows() ? '-nologo' : ''
]) ])
.then(() => { .then(() => {

View File

@@ -23,7 +23,9 @@ let _webout = '';
const weboutPath = '${ const weboutPath = '${
isWindows() ? weboutPath.replace(/\\/g, '\\\\') : weboutPath isWindows() ? weboutPath.replace(/\\/g, '\\\\') : weboutPath
}'; }';
const _sasjs_tokenfile = '${tokenFile}'; const _sasjs_tokenfile = '${
isWindows() ? tokenFile.replace(/\\/g, '\\\\') : tokenFile
}';
const _sasjs_username = '${preProgramVariables?.username}'; const _sasjs_username = '${preProgramVariables?.username}';
const _sasjs_userid = '${preProgramVariables?.userId}'; const _sasjs_userid = '${preProgramVariables?.userId}';
const _sasjs_displayname = '${preProgramVariables?.displayName}'; const _sasjs_displayname = '${preProgramVariables?.displayName}';

View File

@@ -3,6 +3,7 @@ declare namespace NodeJS {
sasLoc?: string sasLoc?: string
nodeLoc?: string nodeLoc?: string
driveLoc: string driveLoc: string
logsLoc: string
sasSessionController?: import('../../controllers/internal').SASSessionController sasSessionController?: import('../../controllers/internal').SASSessionController
jsSessionController?: import('../../controllers/internal').JSSessionController jsSessionController?: import('../../controllers/internal').JSSessionController
appStreamConfig: import('../').AppStreamConfig appStreamConfig: import('../').AppStreamConfig

View File

@@ -22,6 +22,8 @@ export const getDesktopUserAutoExecPath = () =>
export const getSasjsRootFolder = () => process.driveLoc export const getSasjsRootFolder = () => process.driveLoc
export const getLogFolder = () => process.logsLoc
export const getAppStreamConfigPath = () => export const getAppStreamConfigPath = () =>
path.join(getSasjsRootFolder(), 'appStreamConfig.json') 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 getFilesFolder = () => path.join(getSasjsRootFolder(), 'files')
export const getLogFolder = () => path.join(getSasjsRootFolder(), 'logs')
export const getWeboutFolder = () => path.join(getSasjsRootFolder(), 'webouts') export const getWeboutFolder = () => path.join(getSasjsRootFolder(), 'webouts')
export const getSessionsFolder = () => export const getSessionsFolder = () =>

View File

@@ -40,7 +40,16 @@ export const setProcessVariables = async () => {
await createFolder(absPath) await createFolder(absPath)
process.driveLoc = getRealPath(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('sasLoc: ', process.sasLoc)
console.log('sasDrive: ', process.driveLoc) console.log('sasDrive: ', process.driveLoc)
console.log('sasLogs: ', process.logsLoc)
console.log('runTimes: ', process.runTimes) console.log('runTimes: ', process.runTimes)
} }

View File

@@ -1,6 +1,6 @@
import path from 'path' import path from 'path'
import { MulterFile } from '../types/Upload' import { MulterFile } from '../types/Upload'
import { listFilesInFolder, readFileBinary } from '@sasjs/utils' import { listFilesInFolder, readFileBinary, isWindows } from '@sasjs/utils'
interface FilenameMapSingle { interface FilenameMapSingle {
fieldName: string fieldName: string
@@ -118,7 +118,9 @@ export const generateFileUploadJSCode = async (
if (fileName.includes('req_file')) { if (fileName.includes('req_file')) {
fileCount++ fileCount++
const filePath = path.join(sessionFolder, fileName) const filePath = path.join(sessionFolder, fileName)
uploadCode += `\nconst _WEBIN_FILEREF${fileCount} = fs.readFileSync('${filePath}')` uploadCode += `\nconst _WEBIN_FILEREF${fileCount} = fs.readFileSync('${
isWindows() ? filePath.replace(/\\/g, '\\\\') : filePath
}')`
uploadCode += `\nconst _WEBIN_FILENAME${fileCount} = '${filesNamesMap[fileName].originalName}'` uploadCode += `\nconst _WEBIN_FILENAME${fileCount} = '${filesNamesMap[fileName].originalName}'`
uploadCode += `\nconst _WEBIN_NAME${fileCount} = '${filesNamesMap[fileName].fieldName}'` uploadCode += `\nconst _WEBIN_NAME${fileCount} = '${filesNamesMap[fileName].fieldName}'`
} }