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

Compare commits

...

12 Commits

Author SHA1 Message Date
Yury Shkoda
bcef9a4a9d chore: wip replicating folders api 2022-06-21 18:28:13 +03:00
Yury Shkoda
a4d5ee99c4 fix(folders-api): add parent uri to root folder 2022-05-17 09:15:42 +03:00
Yury Shkoda
d7e835778b feat(folders-api): add root folder 2022-05-17 08:51:26 +03:00
Yury Shkoda
d7d3bb285f chore(deps): fixed security vulnerability 2022-04-19 17:00:25 +03:00
Yury Shkoda
d532d74879 docs(readme): fixed typos 2022-04-19 16:59:13 +03:00
Saad Jutt
34e54934fd chore(release): 0.0.10 2021-12-07 16:04:07 +05:00
Allan Bowe
4873e6054f Merge pull request #36 from sasjs/check-session-token-validity
chore: added check session + code refactor
2021-12-07 08:49:00 +00:00
Saad Jutt
b00aa4e17b chore: additional info on SAS session crash 2021-12-07 10:37:36 +05:00
Saad Jutt
9fccfe6f35 chore: Merge branch 'master' into check-session-token-validity 2021-12-07 10:37:02 +05:00
Saad Jutt
0a9d734e09 chore(release): 0.0.9 2021-12-07 10:35:56 +05:00
Muhammad Saad
a0822e6b61 fix: release with files (#35) 2021-12-07 10:33:11 +05:00
Saad Jutt
43545fa04b chore: added check session + code refactor 2021-12-05 08:17:54 +05:00
31 changed files with 584 additions and 76 deletions

View File

@@ -2,8 +2,8 @@ name: SASjs Server Executable Release
on:
push:
branches:
- master
tags:
- 'v*.*.*'
jobs:
release:
@@ -32,17 +32,10 @@ jobs:
env:
CI: true
- name: Create Tag with Release
uses: Klemensas/action-autotag@stable
with:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
- name: Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
with:
files: |
./executables/api-linux
./executables/api-macos
./executables/api-win.exe
token: ${{ secrets.GH_TOKEN }}

View File

@@ -2,6 +2,99 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [0.0.10](https://github.com/sasjs/server/compare/v0.0.9...v0.0.10) (2021-12-07)
### [0.0.9](https://github.com/sasjs/server/compare/v0.0.3...v0.0.9) (2021-12-07)
### Bug Fixes
* release with files ([#35](https://github.com/sasjs/server/issues/35)) ([a0822e6](https://github.com/sasjs/server/commit/a0822e6b61905257475121ffd907fd1f79ed146b))
### [0.0.8](https://github.com/saadjutt01/server/compare/v0.0.7...v0.0.8) (2021-12-07)
### [0.0.7](https://github.com/saadjutt01/server/compare/v0.0.6...v0.0.7) (2021-12-07)
### [0.0.6](https://github.com/saadjutt01/server/compare/v0.0.5...v0.0.6) (2021-12-07)
### [0.0.5](https://github.com/saadjutt01/server/compare/v0.0.4...v0.0.5) (2021-12-07)
### 0.0.4 (2021-12-07)
### Features
* add api endpoint for sasjs drive ([96b5fef](https://github.com/saadjutt01/server/commit/96b5fef3021f67f66e5e3b854319230618421852))
* add new type TreeNode ([bc3cb7b](https://github.com/saadjutt01/server/commit/bc3cb7bb20a1202d17aaf8bbcddd1feef4fff724))
* add pug and directory tree dependencies ([3ffa168](https://github.com/saadjutt01/server/commit/3ffa168c8bafc989caf1a744cebc20d36c6aa11b))
* add sasjsExecutor controller ([279fbf2](https://github.com/saadjutt01/server/commit/279fbf2a9a0bd6bc0938f9a66e9685fb93d86089))
* add top app bar with tab navigation ([a506bc9](https://github.com/saadjutt01/server/commit/a506bc9dd9d201b89fc9ffd1a552c16bd170f058))
* add views and styles for rendering html ([a446f5c](https://github.com/saadjutt01/server/commit/a446f5c4f73a4e829a2c5eec041e3adffeddff52))
* adding _metaperson and _metauser to Stored Programs ([b3147ec](https://github.com/saadjutt01/server/commit/b3147ec680646b3d9c7e89152e472dddc8a36075))
* **api-utility:** create getWebBuildFolderPath utility ([9648c51](https://github.com/saadjutt01/server/commit/9648c51b5491d8b6bbe5497273efa2d11e2486d2))
* **api:** set up endpoint for sas code execution ([f6046b1](https://github.com/saadjutt01/server/commit/f6046b15ae30cd8ace685cf283339871de658b7d))
* authentication with jwt ([22dfcfd](https://github.com/saadjutt01/server/commit/22dfcfddb9abd355a63d1ee5acd925c759e86d69))
* compile systemInit and inject to autoExec ([b75139d](https://github.com/saadjutt01/server/commit/b75139dda5cacc7e10a4d635eb2a222f7dfa3fec))
* **deploy:** add appLoc ([f0f1e1d](https://github.com/saadjutt01/server/commit/f0f1e1d57ea1e961fc3b1cfcbd4cb259a77a90d0))
* **deploy:** add route to deploy a file tree to @sasjs/server ([b4bf72f](https://github.com/saadjutt01/server/commit/b4bf72f70401a81b6d5d0104332a1fbc5f71562b))
* **execute:** add macroVars to job execution ([39e486b](https://github.com/saadjutt01/server/commit/39e486b8cb5efbadc86eb7029b60c7073744eb2b))
* **execute:** add sas controller ([bf1db4d](https://github.com/saadjutt01/server/commit/bf1db4dd47d2488bac073cd468db920ff9fd533d))
* **execution:** add ExecutionController working with session ([8b25641](https://github.com/saadjutt01/server/commit/8b2564120def137f80647064e28062b880d58efe))
* **executor:** improved api response ([707b503](https://github.com/saadjutt01/server/commit/707b50394267217e717aa72f74dbeba3852a93e6))
* **executor:** response with webout ([52275ba](https://github.com/saadjutt01/server/commit/52275ba67d97d5cbdf6c5511c9bd789bd6ca6b4e))
* **express:** increase payload max size ([7b403c1](https://github.com/saadjutt01/server/commit/7b403c151e889cae975944546bb4bb53eff1dd26))
* frontend app for sasjs server ([db8eb8d](https://github.com/saadjutt01/server/commit/db8eb8dd7197bbe36f2d10cabbb58b3eb7ce7c33))
* generate executables for sasjs/server with web component ([514a262](https://github.com/saadjutt01/server/commit/514a262340dc34007de75caf08ad03969e7110c1))
* Groups are added + docs ([2fe9d5c](https://github.com/saadjutt01/server/commit/2fe9d5ca9ce1fb376f03534f8685d65efb2f68a6))
* improved deploy and execute endpoints ([5b4e562](https://github.com/saadjutt01/server/commit/5b4e5626fc7ae3e020819e3ebd334cc3712ae8e7))
* JWT saved in DB + logout api added ([46c5a75](https://github.com/saadjutt01/server/commit/46c5a75ac4fb26ebec219118eb204f1b5049ae90))
* **routes:** separate routes into web and api ([dabef59](https://github.com/saadjutt01/server/commit/dabef597287a59f3bfaff54a18de465f820aa514))
* **session:** add SessionController ([6a34fa1](https://github.com/saadjutt01/server/commit/6a34fa1b1dae07fe032352bea0644ab7a6f9c3f9))
* **session:** add SessionController and ExecutionController ([6e0b04a](https://github.com/saadjutt01/server/commit/6e0b04a6e548ac31baee726c9249b7e25f50f0bf))
* user operation apis added ([728f277](https://github.com/saadjutt01/server/commit/728f277f5ce136d62951071833cd6db478b07e4a))
### Bug Fixes
* **api-cdrive-oller:** throw erow error when file not found ([03d1d60](https://github.com/saadjutt01/server/commit/03d1d60660fc46421ef6ad9cee8493dd884e309a))
* change api endpoint SASjsExecutor/do -> SASjsApi/stp/execute ([d93673f](https://github.com/saadjutt01/server/commit/d93673f2a51098c6af8abc4a793081d4591e27de))
* cors enabled for desktop mode ([2bb10c7](https://github.com/saadjutt01/server/commit/2bb10c71661b5de7ed515c82e5b1967b88449972))
* DB names updates + refresh api is added ([9f17b17](https://github.com/saadjutt01/server/commit/9f17b17e3138ce49f24447cd5ae457e3e90ad4da))
* debug not passed ([d9555e1](https://github.com/saadjutt01/server/commit/d9555e151b0e1d1a4068efdf8ee9ed53b25b9b89))
* **deploy:** fix payload processing ([361b539](https://github.com/saadjutt01/server/commit/361b539271cf95bbe570cca9e44635ab563d3f9e))
* **deps:** removed malicious dependency ([c4b9402](https://github.com/saadjutt01/server/commit/c4b9402f017b76dc412a17a10313f1fd5a3891ef))
* **docker:** docker-compose for prod+development ([4a363c5](https://github.com/saadjutt01/server/commit/4a363c5b9796283199debcc8afa810c6f561f8e6))
* **executor:** create tmp files before execution ([cdbc3fd](https://github.com/saadjutt01/server/commit/cdbc3fd298e2a581773448bdddcad93de3b3544d))
* **executor:** fix nosplash argument and api response ([715b1de](https://github.com/saadjutt01/server/commit/715b1dec68377eefe03aa8203a73debe77842436))
* fix web route ([6c7a6b6](https://github.com/saadjutt01/server/commit/6c7a6b6c6af28c29b391162e4e332da6524b1c61))
* **github:** fixed github flow ([8dab288](https://github.com/saadjutt01/server/commit/8dab28861dfa7c4c7fefc7fe038df50f58d04547))
* **github:** removed npm token ([bbb94d6](https://github.com/saadjutt01/server/commit/bbb94d61ce39c84a6c0c44186e89787ab0e76a8c))
* immplementation of files api fixed ([299319e](https://github.com/saadjutt01/server/commit/299319e2dbe06c7ca99e403fcbdec2ad1db8b7e4))
* load file when url contains filePath ([99d5577](https://github.com/saadjutt01/server/commit/99d55775aaac3b2caaa4b10d4ed698f6cd7fcb2a))
* modify the directory tree algorithm to include relative path with each node ([91e2e2b](https://github.com/saadjutt01/server/commit/91e2e2bc4a46da0d149578593559efdb87681bd4))
* norefferer issue in home page external links fix ([e2b12b7](https://github.com/saadjutt01/server/commit/e2b12b74f52c3ce4541fde9af6af0093b56b157b))
* on clicking execute button open new tab for response ([02f5371](https://github.com/saadjutt01/server/commit/02f5371f57b311ff700ba8108f9d5168da8c22a4))
* prettier ([716ae81](https://github.com/saadjutt01/server/commit/716ae81d9293b42dd2a7047ac52d75401b3b8798))
* **prod-server:** use port from configuration ([4d8efbb](https://github.com/saadjutt01/server/commit/4d8efbb88d32154d84e80b79780e2e3de2f519e4))
* readme overview| ([b3342f0](https://github.com/saadjutt01/server/commit/b3342f00031d19080fb72e3460f023c5f44bac95))
* remove .sas extension from _program parameter at the end of string ([56cb2d1](https://github.com/saadjutt01/server/commit/56cb2d1d512beadb5cfdc4ab4034ac917311ff23))
* removing renegade dash ([4ff4d39](https://github.com/saadjutt01/server/commit/4ff4d39e954e895b46ddc3e2919f7f2c4e1ce01d))
* **root-package.json:** lint:fix command fixed in root package json ([ec6333f](https://github.com/saadjutt01/server/commit/ec6333f6aa67c1b94f54b017ed27eb3b21b4207f))
* **routes:** fix routes imports ([49c152a](https://github.com/saadjutt01/server/commit/49c152a398b60f6b0a0c25a68eb4c1c291984872))
* **semantic-release:** fixed package.json ([ef45787](https://github.com/saadjutt01/server/commit/ef45787019f1e61d0e4e2acee334236e8aca23cc))
* sending _webout as result object in response JSON ([b97523e](https://github.com/saadjutt01/server/commit/b97523e55584cc7d9d682cfeaab8f5b70a10b899))
* session refactoring with Saad & Allan ([cbe07b4](https://github.com/saadjutt01/server/commit/cbe07b4abb2e936037874af1a088cd038e0fc731))
* **ts:** enable files ([37b6936](https://github.com/saadjutt01/server/commit/37b6936cca3cff9c1ca26ec7b4b938a357c448df))
* update api calls from client side ([031e492](https://github.com/saadjutt01/server/commit/031e492d44674dec4f2b3bc1f5bf7affac5716bd))
* update api endpoints ([936a205](https://github.com/saadjutt01/server/commit/936a205e66073b9178089c6ab10d6ac3bf323c54))
* update sasjs drive controller from function base to class base ([3fe475d](https://github.com/saadjutt01/server/commit/3fe475d477c466556659b48c70eeac5153ff5b0e))
* update SASjsApi/stp/execute post api endpoints to capture url params ([d981444](https://github.com/saadjutt01/server/commit/d9814441bb1d269ec2404e50f51124f998c65c40))
* use hash router instead of browser router in react app ([c72867d](https://github.com/saadjutt01/server/commit/c72867d5a70550660c8c37220aa33693716a93f1))
* **web:** infinite call to api end point fixed ([ac745c8](https://github.com/saadjutt01/server/commit/ac745c8f5c3e4aa2ac8d6ca23bb1276452d4018b))
* **web:** remove unnecessary packages and files ([0fb4301](https://github.com/saadjutt01/server/commit/0fb43019668f5a13f6e77fdb4b3e543006b509c0))
* **weeb:** add catch block with each axios request ([552a358](https://github.com/saadjutt01/server/commit/552a3584ec9345bc1dec0ff5377bf773a7928d62))
* **workflow:** fix 'SASjs Server Build' ([174d94a](https://github.com/saadjutt01/server/commit/174d94a23c5036d61a4f2e11296283f128d4dafa))
### 0.0.3 (2021-11-30)

View File

@@ -1,5 +1,4 @@
FROM node:lts-alpine
RUN npm install -g @sasjs/cli
WORKDIR /usr/server/api
COPY ["package.json","package-lock.json", "./"]
RUN npm ci

View File

@@ -59,12 +59,12 @@ It will build following images if running first time:
### Using node:
#### Development (running api and web seperately):
#### Development (running api and web separately):
##### API
Navigate to `./api`
There is `.env.example` file present at `./api` directory. Remember to provide enviornment variables else default values will be used mentioned in `.env.example` files
There is `.env.example` file present at `./api` directory. Remember to provide environment variables else default values will be used mentioned in `.env.example` files
Command to install and run api server.
```

View File

@@ -362,14 +362,15 @@ components:
properties:
status:
type: string
log:
type: string
_webout:
type: string
log:
type: string
message:
type: string
required:
- status
- _webout
type: object
additionalProperties: false
ExecuteReturnJsonPayload:
@@ -981,7 +982,7 @@ paths:
application/json:
schema:
type: string
description: "Trigger a SAS program using it's location in the _program parameter.\r\nEnable debugging using the _debug parameter.\r\nAdditional URL parameters are turned into SAS macro variables.\r\nAny files provided are placed into the session and\r\ncorresponding _WEBIN_XXX variables are created."
description: "Trigger a SAS program using it's location in the _program parameter.\nEnable debugging using the _debug parameter.\nAdditional URL parameters are turned into SAS macro variables.\nAny files provided are placed into the session and\ncorresponding _WEBIN_XXX variables are created."
summary: 'Execute Stored Program, return raw content'
tags:
- STP
@@ -1005,7 +1006,7 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/ExecuteReturnJsonResponse'
description: "Trigger a SAS program using it's location in the _program parameter.\r\nEnable debugging using the _debug parameter.\r\nAdditional URL parameters are turned into SAS macro variables.\r\nAny files provided are placed into the session and\r\ncorresponding _WEBIN_XXX variables are created."
description: "Trigger a SAS program using it's location in the _program parameter.\nEnable debugging using the _debug parameter.\nAdditional URL parameters are turned into SAS macro variables.\nAny files provided are placed into the session and\ncorresponding _WEBIN_XXX variables are created."
summary: 'Execute Stored Program, return JSON'
tags:
- STP
@@ -1026,10 +1027,33 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/ExecuteReturnJsonPayload'
/SASjsApi/session:
get:
operationId: Session
responses:
'200':
description: Ok
content:
application/json:
schema:
$ref: '#/components/schemas/UserResponse'
examples:
'Example 1':
value: {id: 123, username: johnusername, displayName: John}
summary: 'Get session info (username).'
tags:
- Session
security:
-
bearerAuth: []
parameters: []
servers:
-
url: /
tags:
-
name: Session
description: 'Get Session information'
-
name: User
description: 'Operations about users'

View File

@@ -8,6 +8,8 @@ import webRouter from './routes/web'
import apiRouter from './routes/api'
import { connectDB, getWebBuildFolderPath } from './utils'
import { FolderController } from './controllers'
dotenv.config()
const app = express()
@@ -30,4 +32,8 @@ app.use(express.json({ limit: '50mb' }))
app.use(express.static(getWebBuildFolderPath()))
const folderController = new FolderController()
folderController.addRootFolder()
export default connectDB().then(() => app)

View File

@@ -0,0 +1,69 @@
import { Body } from 'tsoa'
import Folder, { FolderPayload, MemberType } from '../model/Folder'
export class FolderController {
public async createFolder(@Body() body: FolderPayload) {
return createFolder(body)
}
public async addRootFolder() {
await addRootFolder()
}
}
interface FolderDetailsResponse {
name: string
parentFolderUri: string
children: []
}
const createFolder = async ({
name,
parentFolderUri,
type
}: FolderPayload): Promise<FolderDetailsResponse> => {
parentFolderUri = parentFolderUri.replace(/\/folders\/folders\//i, '')
const parentFolder = await Folder.findById(parentFolderUri).catch(
(_: any) => {
throw new Error(
`No folder with an URI '${parentFolderUri}' has been found.`
)
}
)
const folder = new Folder({
name,
parentFolderUri,
type
})
const savedFolder = await folder.save().catch((err: any) => {
// TODO: log error
throw new Error(`Error while saving folder.`)
})
await parentFolder?.addMember(savedFolder._id)
return {
name: savedFolder.name,
parentFolderUri: savedFolder.parentFolderUri,
children: []
}
}
const addRootFolder = async () => {
let folder = await Folder.findOne({ name: '/' })
if (folder) return
folder = new Folder({
name: '/',
parentFolderUri: '',
type: MemberType.Folder
})
folder.parentFolderUri = folder._id
return await folder.save()
}
const getItem = async({ path })

View File

@@ -4,3 +4,5 @@ export * from './drive'
export * from './group'
export * from './stp'
export * from './user'
export * from './session'
export * from './folder'

View File

@@ -100,26 +100,20 @@ ${program}`
const debugValue =
typeof vars._debug === 'string' ? parseInt(vars._debug) : vars._debug
let debugResponse: string | undefined
if ((debugValue && debugValue >= 131) || session.crashed) {
debugResponse = `<html><body>${webout}<div style="text-align:left"><hr /><h2>SAS Log</h2><pre>${log}</pre></div></body></html>`
}
session.inUse = false
sessionController.deleteSession(session)
if (returnJson) {
const response: any = {
webout: webout
return {
webout,
log:
(debugValue && debugValue >= 131) || session.crashed ? log : undefined
}
if ((debugValue && debugValue >= 131) || session.crashed) {
response.log = log
}
return response
}
return debugResponse ?? webout
return (debugValue && debugValue >= 131) || session.crashed
? `<html><body>${webout}<div style="text-align:left"><hr /><h2>SAS Log</h2><pre>${log}</pre></div></body></html>`
: webout
}
buildDirectorytree() {

View File

@@ -92,7 +92,7 @@ export class SessionController {
.catch((err) => {
session.completed = true
session.crashed = err.toString()
console.log('session crashed', session.id)
console.log('session crashed', session.id, session.crashed)
})
// we have a triggered session - add to array

View File

@@ -0,0 +1,30 @@
import express from 'express'
import { Request, Security, Route, Tags, Example, Get } from 'tsoa'
import { UserResponse } from './user'
@Security('bearerAuth')
@Route('SASjsApi/session')
@Tags('Session')
export class SessionController {
/**
* @summary Get session info (username).
*
*/
@Example<UserResponse>({
id: 123,
username: 'johnusername',
displayName: 'John'
})
@Get('/')
public async session(
@Request() request: express.Request
): Promise<UserResponse> {
return session(request)
}
}
const session = (req: any) => ({
id: req.user.id,
username: req.user.username,
displayName: req.user.displayName
})

View File

@@ -1,16 +1,6 @@
import express, { response } from 'express'
import express from 'express'
import path from 'path'
import {
Request,
Security,
Route,
Tags,
Example,
Post,
Body,
Get,
Query
} from 'tsoa'
import { Request, Security, Route, Tags, Post, Body, Get, Query } from 'tsoa'
import { ExecutionController } from './internal'
import { PreProgramVars } from '../types'
import { getTmpFilesFolderPath, makeFilesNamesMap } from '../utils'
@@ -24,8 +14,8 @@ interface ExecuteReturnJsonPayload {
}
interface ExecuteReturnJsonResponse {
status: string
_webout: string
log?: string
_webout?: string
message?: string
}
@@ -111,17 +101,17 @@ const executeReturnJson = async (
const filesNamesMap = req.files?.length ? makeFilesNamesMap(req.files) : null
try {
const jsonResult: any = await new ExecutionController().execute(
const { webout, log } = (await new ExecutionController().execute(
sasCodePath,
getPreProgramVariables(req),
{ ...req.query, ...req.body },
{ filesNamesMap: filesNamesMap },
true
)
)) as { webout: string; log: string }
return {
status: 'success',
_webout: jsonResult.webout,
log: jsonResult.log
_webout: webout,
log
}
} catch (err: any) {
throw {

View File

@@ -1,5 +1,7 @@
import jwt from 'jsonwebtoken'
import { Request, Response } from 'express'
import { verifyTokenInDB } from '../utils'
import { headerIsNotPresentMessage, headerIsNotValidMessage } from './header'
export const authenticateAccessToken = (req: any, res: any, next: any) => {
authenticateToken(
@@ -21,12 +23,24 @@ export const authenticateRefreshToken = (req: any, res: any, next: any) => {
)
}
export const verifyAuthHeaderIsPresent = (req: Request, res: Response) => {
console.log(`🤖[verifyAuthHeaderIsPresent]🤖`)
const authHeader = req.headers.authorization
if (!authHeader) {
return res.status(401).json(headerIsNotPresentMessage('Authorization'))
} else if (!/^Bearer\s.{1}/.test(authHeader)) {
return res.status(401).json(headerIsNotValidMessage('Authorization'))
}
}
const authenticateToken = (
req: any,
res: any,
next: any,
key: string,
tokenType: 'accessToken' | 'refreshToken' = 'accessToken'
tokenType: 'accessToken' | 'refreshToken'
) => {
const { MODE } = process.env
if (MODE?.trim() !== 'server') {

View File

@@ -0,0 +1,18 @@
export const desktopRestrict = (req: any, res: any, next: any) => {
const { MODE } = process.env
if (MODE?.trim() !== 'server')
return res.status(403).send('Not Allowed while in Desktop Mode.')
next()
}
export const desktopUsername = (req: any, res: any, next: any) => {
const { MODE } = process.env
if (MODE?.trim() !== 'server')
return res.status(200).send({
userId: 12345,
username: 'DESKTOPusername',
displayName: 'DESKTOP User'
})
next()
}

View File

@@ -1,7 +0,0 @@
export const desktopRestrict = (req: any, res: any, next: any) => {
const { MODE } = process.env
if (MODE?.trim() !== 'server')
return res.status(403).send('Not Allowed while in Desktop Mode.')
next()
}

View File

@@ -0,0 +1,29 @@
import { Request, Response } from 'express'
export const verifyAcceptHeader = (req: Request, res: Response) => {
const acceptHeader = req.headers.accept
if (!acceptHeader) {
return res.status(406).json(headerIsNotPresentMessage('Accept'))
} else if (acceptHeader !== 'application/json') {
return res.status(406).json(headerIsNotValidMessage('Accept'))
}
}
export const verifyContentTypeHeader = (req: Request, res: Response) => {
const contentTypeHeader = req.headers['content-type']
if (!contentTypeHeader) {
return res.status(406).json(headerIsNotPresentMessage('Content-Type'))
} else if (contentTypeHeader !== 'application/json') {
return res.status(406).json(headerIsNotValidMessage('Content-Type'))
}
}
export const headerIsNotPresentMessage = (header: string) => ({
message: `${header} header is not present.`
})
export const headerIsNotValidMessage = (header: string) => ({
message: `${header} header is not valid.`
})

View File

@@ -1,4 +1,6 @@
export * from './authenticateToken'
export * from './desktopRestrict'
export * from './desktop'
export * from './verifyAdmin'
export * from './verifyAdminIfNeeded'
export * from './header'
export * from './mock'

View File

@@ -0,0 +1,24 @@
import {
verifyAuthHeaderIsPresent,
verifyAcceptHeader,
verifyContentTypeHeader
} from './'
import { Request, Response, NextFunction } from 'express'
export const verifyHeaders = (
req: Request,
res: Response,
next: NextFunction
) => {
switch (true) {
case verifyAuthHeaderIsPresent(req, res) !== undefined:
break
case verifyAcceptHeader(req, res) !== undefined:
break
case verifyContentTypeHeader(req, res) !== undefined:
break
default:
return next()
}
}

80
api/src/model/Folder.ts Normal file
View File

@@ -0,0 +1,80 @@
import { Document, Schema, Model, model } from 'mongoose'
import {} from '@sasjs/utils'
export interface FolderPayload {
parentFolderUri: string
name: string
type: MemberType
}
export enum MemberType {
Folder = 'Folder',
File = 'File'
}
const isMemberType = (value: string) => value in MemberType
export const getMemberType = (value: string) => {
value
}
interface IFolderDocument extends FolderPayload, Document {
members: Schema.Types.ObjectId[]
type: MemberType
}
interface IFolder extends IFolderDocument {
addMember(memberId: Schema.Types.ObjectId): Promise<IFolder>
}
interface IFolderModel extends Model<IFolder> {}
const folderSchema = new Schema({
name: { type: String, required: true },
parentFolderUri: { type: String, required: true },
members: [{ type: Schema.Types.ObjectId, refPath: 'member' }],
type: { type: String, required: true }
})
folderSchema.post('save', (folder: IFolder, next: Function) => {
folder.populate('members', '').then(() => next())
next()
})
// folderSchema.get('item', (folder: IFolder, next: Function) => {
// next()
// })
folderSchema.method(
'addMember',
async function (memberId: Schema.Types.ObjectId) {
const folderIdIndex = this.members.indexOf(memberId)
if (folderIdIndex === -1) this.members.push(memberId)
this.markModified('folders')
return this.save()
}
)
folderSchema.method('getItem', async function (path: string) {
console.log(`🤖[getItem]🤖`)
console.log(`🤖[path]🤖`, path)
// const folderIdIndex = this.members.indexOf(memberId)
// if (folderIdIndex === -1) this.members.push(memberId)
// this.markModified('folders')
// return this.save()
})
export const Folder: IFolderModel = model<IFolder, IFolderModel>(
'Folder',
folderSchema
)
export default Folder

View File

@@ -8,11 +8,7 @@ import {
authenticateRefreshToken
} from '../../middlewares'
import {
authorizeValidation,
getDesktopFields,
tokenValidation
} from '../../utils'
import { authorizeValidation, tokenValidation } from '../../utils'
import { InfoJWT } from '../../types'
const authRouter = express.Router()

View File

@@ -0,0 +1,67 @@
import express from 'express'
import { verifyHeaders } from '../../middlewares'
import { verifyQuery, setHeaders } from '../../utils'
import { FolderController } from '../../controllers'
const foldersRouter = express.Router()
const controller = new FolderController()
// https://sas.analytium.co.uk/folders/folders?parentFolderUri=/folders/folders/9e442a90-2c5b-40bb-982a-5fe3ff8a66b7
foldersRouter.post('/folders', verifyHeaders, async (req, res) => {
console.log(`🤖[req.query]🤖`, req.query)
console.log(`🤖[req.body]🤖`, req.body)
try {
const response = await controller.createFolder({
...req.query,
...req.body
})
console.log(`🤖[response]🤖`, response)
res.send(response)
} catch (err: any) {
console.log(`🤖[error]🤖`, err)
res.status(403).send(err.toString())
}
})
foldersRouter.get('/folders/@item', verifyHeaders, async (req, res) => {
const queryParam = 'path'
try {
const response = await controller.getItem({
...req.query,
...req.body
})
console.log(`🤖[response]🤖`, response)
res.send(response)
} catch (err: any) {
console.log(`🤖[error]🤖`, err)
res.status(403).send(err.toString())
}
// if (verifyQuery(req, res, [queryParam])) {
// const folderExist = Math.random() > 0.5
// setHeaders(res, folderExist)
// if (folderExist) {
// res.status(200).json({ message: 'Folder exists!' })
// } else {
// res.status(404).json({
// errorCode: 11512,
// message: 'No folders match the search criteria.',
// details: [`${queryParam}: ${req.query[queryParam]}`],
// links: [],
// version: 2
// })
// }
// }
})
export default foldersRouter

View File

@@ -5,6 +5,7 @@ import swaggerUi from 'swagger-ui-express'
import {
authenticateAccessToken,
desktopRestrict,
desktopUsername,
verifyAdmin
} from '../../middlewares'
@@ -14,9 +15,12 @@ import userRouter from './user'
import groupRouter from './group'
import clientRouter from './client'
import authRouter from './auth'
import sessionRouter from './session'
import foldersRouter from './folders'
const router = express.Router()
router.use('/session', desktopUsername, authenticateAccessToken, sessionRouter)
router.use('/auth', desktopRestrict, authRouter)
router.use(
'/client',
@@ -29,6 +33,7 @@ router.use('/drive', authenticateAccessToken, driveRouter)
router.use('/group', desktopRestrict, groupRouter)
router.use('/stp', authenticateAccessToken, stpRouter)
router.use('/user', desktopRestrict, userRouter)
router.use('/folders', foldersRouter)
router.use(
'/',
swaggerUi.serve,

View File

@@ -0,0 +1,17 @@
import express from 'express'
import { SessionController } from '../../controllers'
import { authenticateAccessToken } from '../../middlewares'
const sessionRouter = express.Router()
sessionRouter.get('/', async (req, res) => {
const controller = new SessionController()
try {
const response = await controller.session(req)
res.send(response)
} catch (err: any) {
res.status(403).send(err.toString())
}
})
export default sessionRouter

View File

@@ -18,13 +18,13 @@ export const connectDB = async () => {
process.driveLoc = driveLoc
return
} else {
const { SAS_PATH } = process.env
const sasDir = SAS_PATH ?? configuration.sasPath
process.sasLoc = path.join(sasDir, 'sas')
}
const { SAS_PATH } = process.env
const sasDir = SAS_PATH ?? configuration.sasPath
process.sasLoc = path.join(sasDir, 'sas')
console.log('sasLoc: ', process.sasLoc)
mongoose.connect(process.env.DB_CONNECT as string, async (err) => {

View File

@@ -10,3 +10,4 @@ export * from './sleep'
export * from './upload'
export * from './validation'
export * from './verifyTokenInDB'
export * from './mock'

View File

@@ -0,0 +1,38 @@
import { Response } from 'express'
import { uuidv4 } from '@sasjs/utils'
export const setHeaders = (res: Response, isSuccess: boolean) => {
res.setHeader(
'cache-control',
`no-cache, no-store, max-age=0, must-revalidate`
)
res.setHeader(
'content-security-policy',
`default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; img-src 'self' *.sas.com blob: data:; style-src 'self' 'unsafe-inline'; child-src 'self' blob: data: mailto:;`
)
res.setHeader(
'content-type',
`application/vnd.sas.${isSuccess ? 'content.folder' : 'error'}+json${
isSuccess ? '' : '; version=2;charset=UTF-8'
}`
)
res.setHeader('pragma', `no-cache`)
res.setHeader('server', `Apache/2.4`)
res.setHeader('strict-transport-security', `max-age=31536000`)
res.setHeader('Transfer-Encoding', `chunked`)
res.setHeader('vary', `User-Agent`)
res.setHeader('x-content-type-options', `nosniff`)
res.setHeader('x-frame-options', `SAMEORIGIN`)
res.setHeader('x-xss-protection', `1; mode=block`)
if (isSuccess) {
const uuid = uuidv4()
res.setHeader('content-location', `/folders/folders/${uuid}`)
res.setHeader('etag', `-2066812946`)
res.setHeader('last-modified', `${new Date(Date.now()).toUTCString()}`)
res.setHeader('location', `/folders/folders/${uuid}`)
} else {
res.setHeader('sas-service-response-flag', `true`)
}
}

View File

@@ -0,0 +1,2 @@
export * from './query'
export * from './header'

View File

@@ -0,0 +1,18 @@
import { Request, Response } from 'express'
export const verifyQuery = (req: Request, res: Response, args: string[]) => {
let isValid = true
const { query } = req
args.forEach((arg: string) => {
if (!Object.keys(query).includes(arg)) {
res.status(400).json({ message: `${arg} query argument is not present.` })
isValid = false
} else if (!query[arg]) {
res.status(400).json({ message: `${arg} query argument is not valid.` })
isValid = false
}
})
return isValid
}

View File

@@ -11,6 +11,10 @@
}
},
"tags": [
{
"name": "Session",
"description": "Get Session information"
},
{
"name": "User",
"description": "Operations about users"

16
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "server",
"version": "0.0.3",
"version": "0.0.10",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "server",
"version": "0.0.3",
"version": "0.0.10",
"devDependencies": {
"prettier": "^2.3.1",
"standard-version": "^9.3.2"
@@ -1350,9 +1350,9 @@
}
},
"node_modules/minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
"dev": true
},
"node_modules/minimist-options": {
@@ -3158,9 +3158,9 @@
}
},
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
"dev": true
},
"minimist-options": {

View File

@@ -1,6 +1,6 @@
{
"name": "server",
"version": "0.0.3",
"version": "0.0.10",
"description": "NodeJS wrapper for calling the SAS binary executable",
"scripts": {
"server": "npm run server:prepare && npm run server:start",