From 0a0ba2cca5db867de46fb2486d856a84ec68d3b4 Mon Sep 17 00:00:00 2001 From: Mihajlo Medjedovic Date: Thu, 25 Aug 2022 15:58:08 +0200 Subject: [PATCH 01/10] feat: added mocking endpoints --- .gitignore | 1 + README.md | 3 + api/public/swagger.yaml | 3820 +++++++++++++++---------------- api/src/routes/api/mock-sas9.ts | 57 + api/src/routes/api/mock-viya.ts | 13 + api/src/routes/web/web.ts | 31 +- 6 files changed, 1947 insertions(+), 1978 deletions(-) create mode 100644 api/src/routes/api/mock-sas9.ts create mode 100644 api/src/routes/api/mock-viya.ts diff --git a/.gitignore b/.gitignore index 9c567cef..7a79a4aa 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ node_modules/ sas/ sasjs_root/ tmp/ +api/mocks/* build/ sasjsbuild/ sasjscore/ diff --git a/README.md b/README.md index f3555d25..83647471 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,9 @@ PROTOCOL= # default: 5000 PORT= +# options: [sas9|sasviya] +# If not present, mocking function is disabled +MOCK_SERVERTYPE= # ## Additional SAS Options diff --git a/api/public/swagger.yaml b/api/public/swagger.yaml index 1e077bcc..7467ba6c 100644 --- a/api/public/swagger.yaml +++ b/api/public/swagger.yaml @@ -1,1980 +1,1866 @@ components: - examples: {} - headers: {} - parameters: {} - requestBodies: {} - responses: {} - schemas: - TokenResponse: - properties: - accessToken: - type: string - description: 'Access Token' - example: someRandomCryptoString - refreshToken: - type: string - description: 'Refresh Token' - example: someRandomCryptoString - required: - - accessToken - - refreshToken - type: object - additionalProperties: false - TokenPayload: - properties: - clientId: - type: string - description: 'Client ID' - example: clientID1 - code: - type: string - description: 'Authorization code' - example: someRandomCryptoString - required: - - clientId - - code - type: object - additionalProperties: false - InfoJWT: - properties: - clientId: - type: string - userId: - type: number - format: double - required: - - clientId - - userId - type: object - additionalProperties: false - ClientPayload: - properties: - clientId: - type: string - description: 'Client ID' - example: someFormattedClientID1234 - clientSecret: - type: string - description: 'Client Secret' - example: someRandomCryptoString - required: - - clientId - - clientSecret - type: object - additionalProperties: false - IRecordOfAny: - properties: {} - type: object - additionalProperties: {} - LogLine: - properties: - line: - type: string - required: - - line - type: object - additionalProperties: false - HTTPHeaders: - properties: {} - type: object - additionalProperties: - type: string - ExecuteReturnJsonResponse: - properties: - status: - type: string - _webout: - anyOf: - - type: string - - $ref: '#/components/schemas/IRecordOfAny' - log: - items: - $ref: '#/components/schemas/LogLine' - type: array - message: - type: string - httpHeaders: - $ref: '#/components/schemas/HTTPHeaders' - required: - - status - - _webout - - log - - httpHeaders - type: object - additionalProperties: false - RunTimeType: - enum: - - sas - - js - type: string - ExecuteCodePayload: - properties: - code: - type: string - description: 'Code of program' - example: '* Code HERE;' - runTime: - $ref: '#/components/schemas/RunTimeType' - description: 'runtime for program' - example: js - required: - - code - - runTime - type: object - additionalProperties: false - MemberType.folder: - enum: - - folder - type: string - FolderMember: - properties: - name: - type: string - type: - $ref: '#/components/schemas/MemberType.folder' - members: - items: - anyOf: - - $ref: '#/components/schemas/FolderMember' - - $ref: '#/components/schemas/ServiceMember' - - $ref: '#/components/schemas/FileMember' - type: array - required: - - name - - type - - members - type: object - additionalProperties: false - MemberType.service: - enum: - - service - type: string - ServiceMember: - properties: - name: - type: string - type: - $ref: '#/components/schemas/MemberType.service' - code: - type: string - required: - - name - - type - - code - type: object - additionalProperties: false - MemberType.file: - enum: - - file - type: string - FileMember: - properties: - name: - type: string - type: - $ref: '#/components/schemas/MemberType.file' - code: - type: string - required: - - name - - type - - code - type: object - additionalProperties: false - FileTree: - properties: - members: - items: - anyOf: - - $ref: '#/components/schemas/FolderMember' - - $ref: '#/components/schemas/ServiceMember' - - $ref: '#/components/schemas/FileMember' - type: array - required: - - members - type: object - additionalProperties: false - DeployResponse: - properties: - status: - type: string - message: - type: string - streamServiceName: - type: string - example: - $ref: '#/components/schemas/FileTree' - required: - - status - - message - type: object - additionalProperties: false - DeployPayload: - properties: - appLoc: - type: string - streamWebFolder: - type: string - fileTree: - $ref: '#/components/schemas/FileTree' - required: - - appLoc - - fileTree - type: object - additionalProperties: false - FileFolderResponse: - properties: - status: - type: string - message: - type: string - required: - - status - type: object - additionalProperties: false - AddFolderPayload: - properties: - folderPath: - type: string - description: 'Location of folder' - example: /Public/someFolder - required: - - folderPath - type: object - additionalProperties: false - RenamePayload: - properties: - oldPath: - type: string - description: 'Old path of file/folder' - example: /Public/someFolder - newPath: - type: string - description: 'New path of file/folder' - example: /Public/newFolder - required: - - oldPath - - newPath - type: object - additionalProperties: false - TreeNode: - properties: - name: - type: string - relativePath: - type: string - absolutePath: - type: string - isFolder: - type: boolean - children: - items: - $ref: '#/components/schemas/TreeNode' - type: array - required: - - name - - relativePath - - absolutePath - - isFolder - - children - type: object - additionalProperties: false - GetFileTreeResponse: - properties: - status: - type: string - tree: - $ref: '#/components/schemas/TreeNode' - required: - - status - - tree - type: object - additionalProperties: false - UserResponse: - properties: - id: - type: number - format: double - username: - type: string - displayName: - type: string - isAdmin: - type: boolean - required: - - id - - username - - displayName - - isAdmin - type: object - additionalProperties: false - GroupResponse: - properties: - groupId: - type: number - format: double - name: - type: string - description: - type: string - required: - - groupId - - name - - description - type: object - additionalProperties: false - UserDetailsResponse: - properties: - id: - type: number - format: double - displayName: - type: string - username: - type: string - isActive: - type: boolean - isAdmin: - type: boolean - autoExec: - type: string - groups: - items: - $ref: '#/components/schemas/GroupResponse' - type: array - required: - - id - - displayName - - username - - isActive - - isAdmin - type: object - additionalProperties: false - UserPayload: - properties: - displayName: - type: string - description: 'Display name for user' - example: 'John Snow' - username: - type: string - description: 'Username for user' - example: johnSnow01 - password: - type: string - description: 'Password for user' - isAdmin: - type: boolean - description: 'Account should be admin or not, defaults to false' - example: 'false' - isActive: - type: boolean - description: 'Account should be active or not, defaults to true' - example: 'true' - autoExec: - type: string - description: 'User-specific auto-exec code' - example: '' - required: - - displayName - - username - - password - type: object - additionalProperties: false - GroupDetailsResponse: - properties: - groupId: - type: number - format: double - name: - type: string - description: - type: string - isActive: - type: boolean - users: - items: - $ref: '#/components/schemas/UserResponse' - type: array - required: - - groupId - - name - - description - - isActive - - users - type: object - additionalProperties: false - GroupPayload: - properties: - name: - type: string - description: 'Name of the group' - example: DCGroup - description: - type: string - description: 'Description of the group' - example: 'This group represents Data Controller Users' - isActive: - type: boolean - description: 'Group should be active or not, defaults to true' - example: 'true' - required: - - name - - description - type: object - additionalProperties: false - _LeanDocument__LeanDocument_T__: - properties: {} - type: object - Pick__LeanDocument_T_.Exclude_keyof_LeanDocument_T_.Exclude_keyofDocument._id-or-id-or-__v_-or-%24isSingleNested__: - properties: - _id: - $ref: '#/components/schemas/_LeanDocument__LeanDocument_T__' - description: 'This documents _id.' - __v: - description: 'This documents __v.' - id: - description: 'The string version of this documents _id.' - type: object - description: 'From T, pick a set of properties whose keys are in the union K' - Omit__LeanDocument_this_.Exclude_keyofDocument._id-or-id-or-__v_-or-%24isSingleNested_: - $ref: '#/components/schemas/Pick__LeanDocument_T_.Exclude_keyof_LeanDocument_T_.Exclude_keyofDocument._id-or-id-or-__v_-or-%24isSingleNested__' - description: 'Construct a type with the properties of T except for those in type K.' - LeanDocument_this_: - $ref: '#/components/schemas/Omit__LeanDocument_this_.Exclude_keyofDocument._id-or-id-or-__v_-or-%24isSingleNested_' - IGroup: - $ref: '#/components/schemas/LeanDocument_this_' - InfoResponse: - properties: - mode: - type: string - cors: - type: string - whiteList: - items: - type: string - type: array - protocol: - type: string - runTimes: - items: - type: string - type: array - required: - - mode - - cors - - whiteList - - protocol - - runTimes - type: object - additionalProperties: false - AuthorizedRoutesResponse: - properties: - paths: - items: - type: string - type: array - required: - - paths - type: object - additionalProperties: false - PermissionDetailsResponse: - properties: - permissionId: - type: number - format: double - path: - type: string - type: - type: string - setting: - type: string - user: - $ref: '#/components/schemas/UserResponse' - group: - $ref: '#/components/schemas/GroupDetailsResponse' - required: - - permissionId - - path - - type - - setting - type: object - additionalProperties: false - PermissionType: - enum: - - Route - type: string - PermissionSettingForRoute: - enum: - - Grant - - Deny - type: string - PrincipalType: - enum: - - user - - group - type: string - RegisterPermissionPayload: - properties: - path: - type: string - description: 'Name of affected resource' - example: /SASjsApi/code/execute - type: - $ref: '#/components/schemas/PermissionType' - description: 'Type of affected resource' - example: Route - setting: - $ref: '#/components/schemas/PermissionSettingForRoute' - description: 'The indication of whether (and to what extent) access is provided' - example: Grant - principalType: - $ref: '#/components/schemas/PrincipalType' - description: 'Indicates the type of principal' - example: user - principalId: - type: number - format: double - description: 'The id of user or group to which a rule is assigned.' - example: 123 - required: - - path - - type - - setting - - principalType - - principalId - type: object - additionalProperties: false - UpdatePermissionPayload: - properties: - setting: - $ref: '#/components/schemas/PermissionSettingForRoute' - description: 'The indication of whether (and to what extent) access is provided' - example: Grant - required: - - setting - type: object - additionalProperties: false - ExecuteReturnJsonPayload: - properties: - _program: - type: string - description: 'Location of SAS program' - example: /Public/somefolder/some.file - type: object - additionalProperties: false - LoginPayload: - properties: - username: - type: string - description: 'Username for user' - example: secretuser - password: - type: string - description: 'Password for user' - example: secretpassword - required: - - username - - password - type: object - additionalProperties: false - AuthorizeResponse: - properties: - code: - type: string - description: 'Authorization code' - example: someRandomCryptoString - required: - - code - type: object - additionalProperties: false - AuthorizePayload: - properties: - clientId: - type: string - description: 'Client ID' - example: clientID1 - required: - - clientId - type: object - additionalProperties: false - securitySchemes: - bearerAuth: - type: http - scheme: bearer - bearerFormat: JWT -info: - title: api - version: 0.0.2 - description: 'Api of SASjs server' - contact: - name: '4GL Ltd' -openapi: 3.0.0 -paths: - /SASjsApi/auth/token: - post: - operationId: Token - responses: - '200': - description: Ok - content: - application/json: - schema: - $ref: '#/components/schemas/TokenResponse' - examples: - 'Example 1': - value: - { - accessToken: someRandomCryptoString, - refreshToken: someRandomCryptoString - } - summary: 'Accepts client/auth code and returns access/refresh tokens' - tags: - - Auth - security: [] - parameters: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TokenPayload' - /SASjsApi/auth/refresh: - post: - operationId: Refresh - responses: - '200': - description: Ok - content: - application/json: - schema: - $ref: '#/components/schemas/TokenResponse' - examples: - 'Example 1': - value: - { - accessToken: someRandomCryptoString, - refreshToken: someRandomCryptoString - } - summary: 'Returns new access/refresh tokens' - tags: - - Auth - security: - - bearerAuth: [] - parameters: [] - /SASjsApi/auth/logout: - post: - operationId: Logout - responses: - '204': - description: 'No content' - summary: 'Logout terminate access/refresh tokens and returns nothing' - tags: - - Auth - security: - - bearerAuth: [] - parameters: [] - /SASjsApi/client: - post: - operationId: CreateClient - responses: - '200': - description: Ok - content: - application/json: - schema: - $ref: '#/components/schemas/ClientPayload' - examples: - 'Example 1': - value: - { - clientId: someFormattedClientID1234, - clientSecret: someRandomCryptoString - } - summary: 'Create client with the following attributes: ClientId, ClientSecret. Admin only task.' - tags: - - Client - security: - - bearerAuth: [] - parameters: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ClientPayload' - /SASjsApi/code/execute: - post: - operationId: ExecuteCode - responses: - '200': - description: Ok - content: - application/json: - schema: - $ref: '#/components/schemas/ExecuteReturnJsonResponse' - description: 'Execute SAS code.' - summary: 'Run SAS Code and returns log' - tags: - - CODE - security: - - bearerAuth: [] - parameters: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ExecuteCodePayload' - /SASjsApi/drive/deploy: - post: - operationId: Deploy - responses: - '200': - description: Ok - content: - application/json: - schema: - $ref: '#/components/schemas/DeployResponse' - examples: - 'Example 1': - value: - { - status: success, - message: 'Files deployed successfully to @sasjs/server.' - } - '400': - description: 'Invalid Format' - content: - application/json: - schema: - $ref: '#/components/schemas/DeployResponse' - examples: - 'Example 1': - value: - { - status: failure, - message: 'Provided not supported data format.' - } - '500': - description: 'Execution Error' - content: - application/json: - schema: - $ref: '#/components/schemas/DeployResponse' - examples: - 'Example 1': - value: { status: failure, message: 'Deployment failed!' } - summary: 'Creates/updates files within SASjs Drive using provided payload.' - tags: - - Drive - security: - - bearerAuth: [] - parameters: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/DeployPayload' - /SASjsApi/drive/deploy/upload: - post: - operationId: DeployUpload - responses: - '200': - description: Ok - content: - application/json: - schema: - $ref: '#/components/schemas/DeployResponse' - examples: - 'Example 1': - value: - { - status: success, - message: 'Files deployed successfully to @sasjs/server.' - } - '400': - description: 'Invalid Format' - content: - application/json: - schema: - $ref: '#/components/schemas/DeployResponse' - examples: - 'Example 1': - value: - { - status: failure, - message: 'Provided not supported data format.' - } - '500': - description: 'Execution Error' - content: - application/json: - schema: - $ref: '#/components/schemas/DeployResponse' - examples: - 'Example 1': - 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!" - summary: 'Creates/updates files within SASjs Drive using uploaded JSON/compressed JSON file.' - tags: - - Drive - security: - - bearerAuth: [] - parameters: [] - requestBody: - required: true - content: - multipart/form-data: - schema: - type: object - properties: - file: - type: string - format: binary - required: - - file - /SASjsApi/drive/file: - get: - operationId: GetFile - responses: - '204': - description: 'No content' - summary: 'Get file from SASjs Drive' - tags: - - Drive - security: - - bearerAuth: [] - parameters: - - in: query - name: _filePath - required: true - schema: - type: string - example: /Public/somefolder/some.file - delete: - operationId: DeleteFile - responses: - '200': - description: Ok - content: - application/json: - schema: - properties: - status: { type: string } - required: - - status - type: object - summary: 'Delete file from SASjs Drive' - tags: - - Drive - security: - - bearerAuth: [] - parameters: - - in: query - name: _filePath - required: true - schema: + examples: {} + headers: {} + parameters: {} + requestBodies: {} + responses: {} + schemas: + TokenResponse: + properties: + accessToken: + type: string + description: 'Access Token' + example: someRandomCryptoString + refreshToken: + type: string + description: 'Refresh Token' + example: someRandomCryptoString + required: + - accessToken + - refreshToken + type: object + additionalProperties: false + TokenPayload: + properties: + clientId: + type: string + description: 'Client ID' + example: clientID1 + code: + type: string + description: 'Authorization code' + example: someRandomCryptoString + required: + - clientId + - code + type: object + additionalProperties: false + InfoJWT: + properties: + clientId: + type: string + userId: + type: number + format: double + required: + - clientId + - userId + type: object + additionalProperties: false + ClientPayload: + properties: + clientId: + type: string + description: 'Client ID' + example: someFormattedClientID1234 + clientSecret: + type: string + description: 'Client Secret' + example: someRandomCryptoString + required: + - clientId + - clientSecret + type: object + additionalProperties: false + IRecordOfAny: + properties: {} + type: object + additionalProperties: {} + LogLine: + properties: + line: + type: string + required: + - line + type: object + additionalProperties: false + HTTPHeaders: + properties: {} + type: object + additionalProperties: + type: string + ExecuteReturnJsonResponse: + properties: + status: + type: string + _webout: + anyOf: + - + type: string + - + $ref: '#/components/schemas/IRecordOfAny' + log: + items: + $ref: '#/components/schemas/LogLine' + type: array + message: + type: string + httpHeaders: + $ref: '#/components/schemas/HTTPHeaders' + required: + - status + - _webout + - log + - httpHeaders + type: object + additionalProperties: false + RunTimeType: + enum: + - sas + - js + - py type: string - example: /Public/somefolder/some.file - post: - operationId: SaveFile - responses: - '200': - description: Ok - content: - application/json: - schema: - $ref: '#/components/schemas/FileFolderResponse' - examples: - 'Example 1': - value: { status: success } - '403': - description: 'File already exists' - content: - application/json: - schema: - $ref: '#/components/schemas/FileFolderResponse' - examples: - 'Example 1': - 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." - summary: 'Create a file in SASjs Drive' - tags: - - Drive - security: - - bearerAuth: [] - parameters: - - description: 'Location of file' - in: query - name: _filePath - required: false - schema: + ExecuteCodePayload: + properties: + code: + type: string + description: 'Code of program' + example: '* Code HERE;' + runTime: + $ref: '#/components/schemas/RunTimeType' + description: 'runtime for program' + example: js + required: + - code + - runTime + type: object + additionalProperties: false + MemberType.folder: + enum: + - folder type: string - example: /Public/somefolder/some.file.sas - requestBody: - required: true - content: - multipart/form-data: - schema: - type: object - properties: - file: - type: string - format: binary - filePath: - type: string - required: - - file - patch: - operationId: UpdateFile - responses: - '200': - description: Ok - content: - application/json: - schema: - $ref: '#/components/schemas/FileFolderResponse' - examples: - 'Example 1': - value: { status: success } - '403': - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/FileFolderResponse' - examples: - 'Example 1': - 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." - summary: 'Modify a file in SASjs Drive' - tags: - - Drive - security: - - bearerAuth: [] - parameters: - - description: 'Location of SAS program' - in: query - name: _filePath - required: false - schema: + FolderMember: + properties: + name: + type: string + type: + $ref: '#/components/schemas/MemberType.folder' + members: + items: + anyOf: + - + $ref: '#/components/schemas/FolderMember' + - + $ref: '#/components/schemas/ServiceMember' + - + $ref: '#/components/schemas/FileMember' + type: array + required: + - name + - type + - members + type: object + additionalProperties: false + MemberType.service: + enum: + - service type: string - example: /Public/somefolder/some.file.sas - requestBody: - required: true - content: - multipart/form-data: - schema: - type: object - properties: - file: - type: string - format: binary - filePath: - type: string - required: + ServiceMember: + properties: + name: + type: string + type: + $ref: '#/components/schemas/MemberType.service' + code: + type: string + required: + - name + - type + - code + type: object + additionalProperties: false + MemberType.file: + enum: - file - /SASjsApi/drive/folder: - get: - operationId: GetFolder - responses: - '200': - description: Ok - content: - application/json: - schema: - properties: - folders: { items: { type: string }, type: array } - files: { items: { type: string }, type: array } - required: - - folders - - files - type: object - summary: 'Get folder contents from SASjs Drive' - tags: - - Drive - security: - - bearerAuth: [] - parameters: - - in: query - name: _folderPath - required: false - schema: type: string - example: /Public/somefolder - delete: - operationId: DeleteFolder - responses: - '200': - description: Ok - content: - application/json: - schema: - properties: - status: { type: string } - required: - - status - type: object - summary: 'Delete folder from SASjs Drive' - tags: - - Drive - security: - - bearerAuth: [] - parameters: - - in: query - name: _folderPath - required: true - schema: + FileMember: + properties: + name: + type: string + type: + $ref: '#/components/schemas/MemberType.file' + code: + type: string + required: + - name + - type + - code + type: object + additionalProperties: false + FileTree: + properties: + members: + items: + anyOf: + - + $ref: '#/components/schemas/FolderMember' + - + $ref: '#/components/schemas/ServiceMember' + - + $ref: '#/components/schemas/FileMember' + type: array + required: + - members + type: object + additionalProperties: false + DeployResponse: + properties: + status: + type: string + message: + type: string + streamServiceName: + type: string + example: + $ref: '#/components/schemas/FileTree' + required: + - status + - message + type: object + additionalProperties: false + DeployPayload: + properties: + appLoc: + type: string + streamWebFolder: + type: string + fileTree: + $ref: '#/components/schemas/FileTree' + required: + - appLoc + - fileTree + type: object + additionalProperties: false + FileFolderResponse: + properties: + status: + type: string + message: + type: string + required: + - status + type: object + additionalProperties: false + AddFolderPayload: + properties: + folderPath: + type: string + description: 'Location of folder' + example: /Public/someFolder + required: + - folderPath + type: object + additionalProperties: false + RenamePayload: + properties: + oldPath: + type: string + description: 'Old path of file/folder' + example: /Public/someFolder + newPath: + type: string + description: 'New path of file/folder' + example: /Public/newFolder + required: + - oldPath + - newPath + type: object + additionalProperties: false + TreeNode: + properties: + name: + type: string + relativePath: + type: string + absolutePath: + type: string + isFolder: + type: boolean + children: + items: + $ref: '#/components/schemas/TreeNode' + type: array + required: + - name + - relativePath + - absolutePath + - isFolder + - children + type: object + additionalProperties: false + GetFileTreeResponse: + properties: + status: + type: string + tree: + $ref: '#/components/schemas/TreeNode' + required: + - status + - tree + type: object + additionalProperties: false + UserResponse: + properties: + id: + type: number + format: double + username: + type: string + displayName: + type: string + isAdmin: + type: boolean + required: + - id + - username + - displayName + - isAdmin + type: object + additionalProperties: false + GroupResponse: + properties: + groupId: + type: number + format: double + name: + type: string + description: + type: string + required: + - groupId + - name + - description + type: object + additionalProperties: false + UserDetailsResponse: + properties: + id: + type: number + format: double + displayName: + type: string + username: + type: string + isActive: + type: boolean + isAdmin: + type: boolean + autoExec: + type: string + groups: + items: + $ref: '#/components/schemas/GroupResponse' + type: array + required: + - id + - displayName + - username + - isActive + - isAdmin + type: object + additionalProperties: false + UserPayload: + properties: + displayName: + type: string + description: 'Display name for user' + example: 'John Snow' + username: + type: string + description: 'Username for user' + example: johnSnow01 + password: + type: string + description: 'Password for user' + isAdmin: + type: boolean + description: 'Account should be admin or not, defaults to false' + example: 'false' + isActive: + type: boolean + description: 'Account should be active or not, defaults to true' + example: 'true' + autoExec: + type: string + description: 'User-specific auto-exec code' + example: "" + required: + - displayName + - username + - password + type: object + additionalProperties: false + GroupDetailsResponse: + properties: + groupId: + type: number + format: double + name: + type: string + description: + type: string + isActive: + type: boolean + users: + items: + $ref: '#/components/schemas/UserResponse' + type: array + required: + - groupId + - name + - description + - isActive + - users + type: object + additionalProperties: false + GroupPayload: + properties: + name: + type: string + description: 'Name of the group' + example: DCGroup + description: + type: string + description: 'Description of the group' + example: 'This group represents Data Controller Users' + isActive: + type: boolean + description: 'Group should be active or not, defaults to true' + example: 'true' + required: + - name + - description + type: object + additionalProperties: false + FlattenMaps_T_: + properties: {} + type: object + IGroup: + $ref: '#/components/schemas/FlattenMaps_T_' + ObjectId: type: string - example: /Public/somefolder/ - post: - operationId: AddFolder - responses: - '200': - description: Ok - content: - application/json: - schema: - $ref: '#/components/schemas/FileFolderResponse' - examples: - 'Example 1': - value: { status: success } - '409': - description: 'Folder already exists' - content: - application/json: - schema: - $ref: '#/components/schemas/FileFolderResponse' - examples: - 'Example 1': - value: - { status: failure, message: 'Add folder request failed.' } - summary: 'Create an empty folder in SASjs Drive' - tags: - - Drive - security: - - bearerAuth: [] - parameters: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/AddFolderPayload' - /SASjsApi/drive/rename: - post: - operationId: Rename - responses: - '200': - description: Ok - content: - application/json: - schema: - $ref: '#/components/schemas/FileFolderResponse' - examples: - 'Example 1': - value: { status: success } - '409': - description: 'Folder already exists' - content: - application/json: - schema: - $ref: '#/components/schemas/FileFolderResponse' - examples: - 'Example 1': - value: { status: failure, message: 'rename request failed.' } - summary: 'Renames a file/folder in SASjs Drive' - tags: - - Drive - security: - - bearerAuth: [] - parameters: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/RenamePayload' - /SASjsApi/drive/filetree: - get: - operationId: GetFileTree - responses: - '200': - description: Ok - content: - application/json: - schema: - $ref: '#/components/schemas/GetFileTreeResponse' - summary: 'Fetch file tree within SASjs Drive.' - tags: - - Drive - security: - - bearerAuth: [] - parameters: [] - /SASjsApi/user: - get: - operationId: GetAllUsers - responses: - '200': - description: Ok - content: - application/json: - schema: - items: - $ref: '#/components/schemas/UserResponse' - type: array - examples: - 'Example 1': - 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.' - tags: - - User - security: - - bearerAuth: [] - parameters: [] - post: - operationId: CreateUser - responses: - '200': - description: Ok - content: - application/json: - schema: - $ref: '#/components/schemas/UserDetailsResponse' - examples: - 'Example 1': - 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.' - tags: - - User - security: - - bearerAuth: [] - parameters: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/UserPayload' - '/SASjsApi/user/by/username/{username}': - get: - operationId: GetUserByUsername - responses: - '200': - description: Ok - content: - application/json: - schema: - $ref: '#/components/schemas/UserDetailsResponse' - description: 'Only Admin or user itself will get user autoExec code.' - summary: 'Get user properties - such as group memberships, userName, displayName.' - tags: - - User - security: - - bearerAuth: [] - parameters: - - description: "The User's username" - in: path - name: username - required: true - schema: + InfoResponse: + properties: + mode: + type: string + cors: + type: string + whiteList: + items: + type: string + type: array + protocol: + type: string + runTimes: + items: + type: string + type: array + required: + - mode + - cors + - whiteList + - protocol + - runTimes + type: object + additionalProperties: false + AuthorizedRoutesResponse: + properties: + paths: + items: + type: string + type: array + required: + - paths + type: object + additionalProperties: false + PermissionDetailsResponse: + properties: + permissionId: + type: number + format: double + path: + type: string + type: + type: string + setting: + type: string + user: + $ref: '#/components/schemas/UserResponse' + group: + $ref: '#/components/schemas/GroupDetailsResponse' + required: + - permissionId + - path + - type + - setting + type: object + additionalProperties: false + PermissionType: + enum: + - Route type: string - example: johnSnow01 - patch: - operationId: UpdateUserByUsername - responses: - '200': - description: Ok - content: - application/json: - schema: - $ref: '#/components/schemas/UserDetailsResponse' - examples: - 'Example 1': - 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.' - tags: - - User - security: - - bearerAuth: [] - parameters: - - description: "The User's username" - in: path - name: username - required: true - schema: + PermissionSettingForRoute: + enum: + - Grant + - Deny type: string - example: johnSnow01 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/UserPayload' - delete: - operationId: DeleteUserByUsername - responses: - '204': - description: 'No content' - summary: 'Delete a user. Can be performed either by admins, or the user in question.' - tags: - - User - security: - - bearerAuth: [] - parameters: - - description: "The User's username" - in: path - name: username - required: true - schema: + PrincipalType: + enum: + - user + - group type: string - example: johnSnow01 - requestBody: - required: true - content: - application/json: - schema: - properties: - password: - type: string - type: object - '/SASjsApi/user/{userId}': - get: - operationId: GetUser - responses: - '200': - description: Ok - content: - application/json: - schema: - $ref: '#/components/schemas/UserDetailsResponse' - description: 'Only Admin or user itself will get user autoExec code.' - summary: 'Get user properties - such as group memberships, userName, displayName.' - tags: - - User - security: - - bearerAuth: [] - parameters: - - description: "The user's identifier" - in: path - name: userId - required: true - schema: - format: double - type: number - example: 1234 - patch: - operationId: UpdateUser - responses: - '200': - description: Ok - content: - application/json: - schema: - $ref: '#/components/schemas/UserDetailsResponse' - examples: - 'Example 1': - 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.' - tags: - - User - security: - - bearerAuth: [] - parameters: - - description: "The user's identifier" - in: path - name: userId - required: true - schema: - format: double - type: number - example: '1234' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/UserPayload' - delete: - operationId: DeleteUser - responses: - '204': - description: 'No content' - summary: 'Delete a user. Can be performed either by admins, or the user in question.' - tags: - - User - security: - - bearerAuth: [] - parameters: - - description: "The user's identifier" - in: path - name: userId - required: true - schema: - format: double - type: number - example: 1234 - requestBody: - required: true - content: - application/json: - schema: - properties: + RegisterPermissionPayload: + properties: + path: + type: string + description: 'Name of affected resource' + example: /SASjsApi/code/execute + type: + $ref: '#/components/schemas/PermissionType' + description: 'Type of affected resource' + example: Route + setting: + $ref: '#/components/schemas/PermissionSettingForRoute' + description: 'The indication of whether (and to what extent) access is provided' + example: Grant + principalType: + $ref: '#/components/schemas/PrincipalType' + description: 'Indicates the type of principal' + example: user + principalId: + type: number + format: double + description: 'The id of user or group to which a rule is assigned.' + example: 123 + required: + - path + - type + - setting + - principalType + - principalId + type: object + additionalProperties: false + UpdatePermissionPayload: + properties: + setting: + $ref: '#/components/schemas/PermissionSettingForRoute' + description: 'The indication of whether (and to what extent) access is provided' + example: Grant + required: + - setting + type: object + additionalProperties: false + ExecuteReturnJsonPayload: + properties: + _program: + type: string + description: 'Location of SAS program' + example: /Public/somefolder/some.file + type: object + additionalProperties: false + LoginPayload: + properties: + username: + type: string + description: 'Username for user' + example: secretuser password: - type: string - type: object - /SASjsApi/group: - get: - operationId: GetAllGroups - responses: - '200': - description: Ok - content: - application/json: - schema: - items: - $ref: '#/components/schemas/GroupResponse' - type: array - examples: - 'Example 1': - 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.' - tags: - - Group - security: - - bearerAuth: [] - parameters: [] - post: - operationId: CreateGroup - responses: - '200': - description: Ok - content: - application/json: - schema: - $ref: '#/components/schemas/GroupDetailsResponse' - examples: - 'Example 1': - value: - { - groupId: 123, - name: DCGroup, - description: 'This group represents Data Controller Users', - isActive: true, - users: [] - } - summary: 'Create a new group. Admin only.' - tags: - - Group - security: - - bearerAuth: [] - parameters: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/GroupPayload' - '/SASjsApi/group/by/groupname/{name}': - get: - operationId: GetGroupByGroupName - responses: - '200': - description: Ok - content: - application/json: - schema: - $ref: '#/components/schemas/GroupDetailsResponse' - summary: 'Get list of members of a group (userName). All users can request this.' - tags: - - Group - security: - - bearerAuth: [] - parameters: - - description: "The group's name" - in: path - name: name - required: true - schema: - type: string - '/SASjsApi/group/{groupId}': - get: - operationId: GetGroup - responses: - '200': - description: Ok - content: - application/json: - schema: - $ref: '#/components/schemas/GroupDetailsResponse' - summary: 'Get list of members of a group (userName). All users can request this.' - tags: - - Group - security: - - bearerAuth: [] - parameters: - - description: "The group's identifier" - in: path - name: groupId - required: true - schema: - format: double - type: number - example: 1234 - delete: - operationId: DeleteGroup - responses: - '200': - description: Ok - content: - application/json: - schema: - allOf: - - { $ref: '#/components/schemas/IGroup' } - - { properties: { _id: {} }, required: [_id], type: object } - summary: 'Delete a group. Admin task only.' - tags: - - Group - security: - - bearerAuth: [] - parameters: - - description: "The group's identifier" - in: path - name: groupId - required: true - schema: - format: double - type: number - example: 1234 - '/SASjsApi/group/{groupId}/{userId}': - post: - operationId: AddUserToGroup - responses: - '200': - description: Ok - content: - application/json: - schema: - $ref: '#/components/schemas/GroupDetailsResponse' - examples: - 'Example 1': - 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.' - tags: - - Group - security: - - bearerAuth: [] - parameters: - - description: "The group's identifier" - in: path - name: groupId - required: true - schema: - format: double - type: number - example: '1234' - - description: "The user's identifier" - in: path - name: userId - required: true - schema: - format: double - type: number - example: '6789' - delete: - operationId: RemoveUserFromGroup - responses: - '200': - description: Ok - content: - application/json: - schema: - $ref: '#/components/schemas/GroupDetailsResponse' - examples: - 'Example 1': - 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.' - tags: - - Group - security: - - bearerAuth: [] - parameters: - - description: "The group's identifier" - in: path - name: groupId - required: true - schema: - format: double - type: number - example: '1234' - - description: "The user's identifier" - in: path - name: userId - required: true - schema: - format: double - type: number - example: '6789' - /SASjsApi/info: - get: - operationId: Info - responses: - '200': - description: Ok - content: - application/json: - schema: - $ref: '#/components/schemas/InfoResponse' - examples: - 'Example 1': - 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).' - tags: - - Info - security: [] - parameters: [] - /SASjsApi/info/authorizedRoutes: - get: - operationId: AuthorizedRoutes - responses: - '200': - description: Ok - content: - application/json: - schema: - $ref: '#/components/schemas/AuthorizedRoutesResponse' - examples: - 'Example 1': - value: { paths: [/AppStream, /SASjsApi/stp/execute] } - summary: 'Get the list of available routes to which permissions can be applied. Used to populate the dialog in the URI Permissions feature.' - tags: - - Info - security: [] - parameters: [] - /SASjsApi/permission: - get: - operationId: GetAllPermissions - responses: - '200': - description: Ok - content: - application/json: - schema: - items: - $ref: '#/components/schemas/PermissionDetailsResponse' - type: array - examples: - 'Example 1': - value: - [ - { - permissionId: 123, - path: /SASjsApi/code/execute, - type: Route, - setting: Grant, - user: - { - id: 1, - username: johnSnow01, - displayName: 'John Snow', - isAdmin: false - } - }, - { - permissionId: 124, - path: /SASjsApi/code/execute, - type: Route, - setting: Grant, - group: - { - groupId: 1, - name: DCGroup, - description: 'This group represents Data Controller Users', - isActive: true, - users: [] - } - } - ] - description: "Get the list of permission rules applicable the authenticated user.\nIf the user is an admin, all rules are returned." - summary: 'Get the list of permission rules. If the user is admin, all rules are returned.' - tags: - - Permission - security: - - bearerAuth: [] - parameters: [] - post: - operationId: CreatePermission - responses: - '200': - description: Ok - content: - application/json: - schema: - $ref: '#/components/schemas/PermissionDetailsResponse' - examples: - 'Example 1': - value: - { - permissionId: 123, - path: /SASjsApi/code/execute, - type: Route, - setting: Grant, - user: - { - id: 1, - username: johnSnow01, - displayName: 'John Snow', - isAdmin: false - } - } - summary: 'Create a new permission. Admin only.' - tags: - - Permission - security: - - bearerAuth: [] - parameters: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/RegisterPermissionPayload' - '/SASjsApi/permission/{permissionId}': - patch: - operationId: UpdatePermission - responses: - '200': - description: Ok - content: - application/json: - schema: - $ref: '#/components/schemas/PermissionDetailsResponse' - examples: - 'Example 1': - value: - { - permissionId: 123, - path: /SASjsApi/code/execute, - type: Route, - setting: Grant, - user: - { - id: 1, - username: johnSnow01, - displayName: 'John Snow', - isAdmin: false - } - } - summary: 'Update permission setting. Admin only' - tags: - - Permission - security: - - bearerAuth: [] - parameters: - - description: "The permission's identifier" - in: path - name: permissionId - required: true - schema: - format: double - type: number - example: 1234 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/UpdatePermissionPayload' - delete: - operationId: DeletePermission - responses: - '204': - description: 'No content' - summary: 'Delete a permission. Admin only.' - tags: - - Permission - security: - - bearerAuth: [] - parameters: - - description: "The user's identifier" - in: path - name: permissionId - required: true - schema: - format: double - type: number - example: 1234 - /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, - isAdmin: false - } - summary: 'Get session info (username).' - tags: - - Session - security: - - bearerAuth: [] - parameters: [] - /SASjsApi/stp/execute: - get: - operationId: ExecuteReturnRaw - responses: - '200': - description: Ok - content: - application/json: - schema: - anyOf: - - { type: string } - - { 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" - summary: 'Execute a Stored Program, returns raw _webout content.' - tags: - - STP - security: - - bearerAuth: [] - parameters: - - description: 'Location of SAS or JS code' - in: query - name: _program - required: true - schema: - type: string - example: /Projects/myApp/some/program - post: - operationId: ExecuteReturnJson - responses: - '200': - description: Ok - content: - application/json: - schema: - $ref: '#/components/schemas/ExecuteReturnJsonResponse' - examples: - 'Example 1': - value: - { - status: success, - _webout: 'webout content', - log: [], - httpHeaders: - { - Content-type: application/zip, - Cache-Control: 'public, max-age=1000' - } - } - description: "Trigger a SAS or JS program using the _program URL parameter.\n\nAccepts URL parameters and file uploads. For more details, see docs:\n\nhttps://server.sasjs.io/storedprograms\n\nThe response will be a JSON object with the following root attributes:\nlog, webout, headers.\n\nThe webout attribute will be nested JSON ONLY if the response-header\ncontains a content-type of application/json AND it is valid JSON.\nOtherwise it will be a stringified version of the webout content." - summary: 'Execute a Stored Program, return a JSON object' - tags: - - STP - security: - - bearerAuth: [] - parameters: - - description: 'Location of SAS or JS code' - in: query - name: _program - required: false - schema: - type: string - example: /Projects/myApp/some/program - requestBody: - required: false - content: - application/json: - schema: - $ref: '#/components/schemas/ExecuteReturnJsonPayload' - /: - get: - operationId: Home - responses: - '200': - description: Ok - content: - application/json: - schema: - type: string - summary: 'Render index.html' - tags: - - Web - security: [] - parameters: [] - /SASLogon/login: - post: - operationId: Login - responses: - '200': - description: Ok - content: - application/json: - schema: - properties: - user: - { - properties: - { - 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: - - user - - loggedIn - type: object - summary: 'Accept a valid username/password' - tags: - - Web - security: [] - parameters: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/LoginPayload' - /SASLogon/authorize: - post: - operationId: Authorize - responses: - '200': - description: Ok - content: - application/json: - schema: - $ref: '#/components/schemas/AuthorizeResponse' - examples: - 'Example 1': - value: { code: someRandomCryptoString } - summary: 'Accept a valid username/password, plus a CLIENT_ID, and return an AUTH_CODE' - tags: - - Web - security: [] - parameters: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/AuthorizePayload' - /SASLogon/logout: - get: - operationId: Logout - responses: - '200': - description: Ok - content: - application/json: - schema: {} - summary: 'Destroy the session stored in cookies' - tags: - - Web - security: [] - parameters: [] + type: string + description: 'Password for user' + example: secretpassword + required: + - username + - password + type: object + additionalProperties: false + AuthorizeResponse: + properties: + code: + type: string + description: 'Authorization code' + example: someRandomCryptoString + required: + - code + type: object + additionalProperties: false + AuthorizePayload: + properties: + clientId: + type: string + description: 'Client ID' + example: clientID1 + required: + - clientId + type: object + additionalProperties: false + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT +info: + title: api + version: 0.0.2 + description: 'Api of SASjs server' + contact: + name: '4GL Ltd' +openapi: 3.0.0 +paths: + /SASjsApi/auth/token: + post: + operationId: Token + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/TokenResponse' + examples: + 'Example 1': + value: {accessToken: someRandomCryptoString, refreshToken: someRandomCryptoString} + summary: 'Accepts client/auth code and returns access/refresh tokens' + tags: + - Auth + security: [] + parameters: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TokenPayload' + /SASjsApi/auth/refresh: + post: + operationId: Refresh + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/TokenResponse' + examples: + 'Example 1': + value: {accessToken: someRandomCryptoString, refreshToken: someRandomCryptoString} + summary: 'Returns new access/refresh tokens' + tags: + - Auth + security: + - + bearerAuth: [] + parameters: [] + /SASjsApi/auth/logout: + post: + operationId: Logout + responses: + '204': + description: 'No content' + summary: 'Logout terminate access/refresh tokens and returns nothing' + tags: + - Auth + security: + - + bearerAuth: [] + parameters: [] + /SASjsApi/client: + post: + operationId: CreateClient + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/ClientPayload' + examples: + 'Example 1': + value: {clientId: someFormattedClientID1234, clientSecret: someRandomCryptoString} + summary: 'Create client with the following attributes: ClientId, ClientSecret. Admin only task.' + tags: + - Client + security: + - + bearerAuth: [] + parameters: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ClientPayload' + /SASjsApi/code/execute: + post: + operationId: ExecuteCode + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/ExecuteReturnJsonResponse' + description: 'Execute SAS code.' + summary: 'Run SAS Code and returns log' + tags: + - CODE + security: + - + bearerAuth: [] + parameters: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExecuteCodePayload' + /SASjsApi/drive/deploy: + post: + operationId: Deploy + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/DeployResponse' + examples: + 'Example 1': + value: {status: success, message: 'Files deployed successfully to @sasjs/server.'} + '400': + description: 'Invalid Format' + content: + application/json: + schema: + $ref: '#/components/schemas/DeployResponse' + examples: + 'Example 1': + value: {status: failure, message: 'Provided not supported data format.'} + '500': + description: 'Execution Error' + content: + application/json: + schema: + $ref: '#/components/schemas/DeployResponse' + examples: + 'Example 1': + value: {status: failure, message: 'Deployment failed!'} + summary: 'Creates/updates files within SASjs Drive using provided payload.' + tags: + - Drive + security: + - + bearerAuth: [] + parameters: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/DeployPayload' + /SASjsApi/drive/deploy/upload: + post: + operationId: DeployUpload + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/DeployResponse' + examples: + 'Example 1': + value: {status: success, message: 'Files deployed successfully to @sasjs/server.'} + '400': + description: 'Invalid Format' + content: + application/json: + schema: + $ref: '#/components/schemas/DeployResponse' + examples: + 'Example 1': + value: {status: failure, message: 'Provided not supported data format.'} + '500': + description: 'Execution Error' + content: + application/json: + schema: + $ref: '#/components/schemas/DeployResponse' + examples: + 'Example 1': + 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!" + summary: 'Creates/updates files within SASjs Drive using uploaded JSON/compressed JSON file.' + tags: + - Drive + security: + - + bearerAuth: [] + parameters: [] + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + file: + type: string + format: binary + required: + - file + /SASjsApi/drive/file: + get: + operationId: GetFile + responses: + '204': + description: 'No content' + summary: 'Get file from SASjs Drive' + tags: + - Drive + security: + - + bearerAuth: [] + parameters: + - + in: query + name: _filePath + required: true + schema: + type: string + example: /Public/somefolder/some.file + delete: + operationId: DeleteFile + responses: + '200': + description: Ok + content: + application/json: + schema: + properties: + status: {type: string} + required: + - status + type: object + summary: 'Delete file from SASjs Drive' + tags: + - Drive + security: + - + bearerAuth: [] + parameters: + - + in: query + name: _filePath + required: true + schema: + type: string + example: /Public/somefolder/some.file + post: + operationId: SaveFile + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/FileFolderResponse' + examples: + 'Example 1': + value: {status: success} + '403': + description: 'File already exists' + content: + application/json: + schema: + $ref: '#/components/schemas/FileFolderResponse' + examples: + 'Example 1': + 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." + summary: 'Create a file in SASjs Drive' + tags: + - Drive + security: + - + bearerAuth: [] + parameters: + - + description: 'Location of file' + in: query + name: _filePath + required: false + schema: + type: string + example: /Public/somefolder/some.file.sas + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + file: + type: string + format: binary + filePath: + type: string + required: + - file + patch: + operationId: UpdateFile + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/FileFolderResponse' + examples: + 'Example 1': + value: {status: success} + '403': + description: "" + content: + application/json: + schema: + $ref: '#/components/schemas/FileFolderResponse' + examples: + 'Example 1': + 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." + summary: 'Modify a file in SASjs Drive' + tags: + - Drive + security: + - + bearerAuth: [] + parameters: + - + description: 'Location of SAS program' + in: query + name: _filePath + required: false + schema: + type: string + example: /Public/somefolder/some.file.sas + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + file: + type: string + format: binary + filePath: + type: string + required: + - file + /SASjsApi/drive/folder: + get: + operationId: GetFolder + responses: + '200': + description: Ok + content: + application/json: + schema: + properties: + folders: {items: {type: string}, type: array} + files: {items: {type: string}, type: array} + required: + - folders + - files + type: object + summary: 'Get folder contents from SASjs Drive' + tags: + - Drive + security: + - + bearerAuth: [] + parameters: + - + in: query + name: _folderPath + required: false + schema: + type: string + example: /Public/somefolder + delete: + operationId: DeleteFolder + responses: + '200': + description: Ok + content: + application/json: + schema: + properties: + status: {type: string} + required: + - status + type: object + summary: 'Delete folder from SASjs Drive' + tags: + - Drive + security: + - + bearerAuth: [] + parameters: + - + in: query + name: _folderPath + required: true + schema: + type: string + example: /Public/somefolder/ + post: + operationId: AddFolder + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/FileFolderResponse' + examples: + 'Example 1': + value: {status: success} + '409': + description: 'Folder already exists' + content: + application/json: + schema: + $ref: '#/components/schemas/FileFolderResponse' + examples: + 'Example 1': + value: {status: failure, message: 'Add folder request failed.'} + summary: 'Create an empty folder in SASjs Drive' + tags: + - Drive + security: + - + bearerAuth: [] + parameters: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AddFolderPayload' + /SASjsApi/drive/rename: + post: + operationId: Rename + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/FileFolderResponse' + examples: + 'Example 1': + value: {status: success} + '409': + description: 'Folder already exists' + content: + application/json: + schema: + $ref: '#/components/schemas/FileFolderResponse' + examples: + 'Example 1': + value: {status: failure, message: 'rename request failed.'} + summary: 'Renames a file/folder in SASjs Drive' + tags: + - Drive + security: + - + bearerAuth: [] + parameters: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/RenamePayload' + /SASjsApi/drive/filetree: + get: + operationId: GetFileTree + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/GetFileTreeResponse' + summary: 'Fetch file tree within SASjs Drive.' + tags: + - Drive + security: + - + bearerAuth: [] + parameters: [] + /SASjsApi/user: + get: + operationId: GetAllUsers + responses: + '200': + description: Ok + content: + application/json: + schema: + items: + $ref: '#/components/schemas/UserResponse' + type: array + examples: + 'Example 1': + 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.' + tags: + - User + security: + - + bearerAuth: [] + parameters: [] + post: + operationId: CreateUser + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/UserDetailsResponse' + examples: + 'Example 1': + 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.' + tags: + - User + security: + - + bearerAuth: [] + parameters: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UserPayload' + '/SASjsApi/user/by/username/{username}': + get: + operationId: GetUserByUsername + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/UserDetailsResponse' + description: 'Only Admin or user itself will get user autoExec code.' + summary: 'Get user properties - such as group memberships, userName, displayName.' + tags: + - User + security: + - + bearerAuth: [] + parameters: + - + description: 'The User''s username' + in: path + name: username + required: true + schema: + type: string + example: johnSnow01 + patch: + operationId: UpdateUserByUsername + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/UserDetailsResponse' + examples: + 'Example 1': + 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.' + tags: + - User + security: + - + bearerAuth: [] + parameters: + - + description: 'The User''s username' + in: path + name: username + required: true + schema: + type: string + example: johnSnow01 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UserPayload' + delete: + operationId: DeleteUserByUsername + responses: + '204': + description: 'No content' + summary: 'Delete a user. Can be performed either by admins, or the user in question.' + tags: + - User + security: + - + bearerAuth: [] + parameters: + - + description: 'The User''s username' + in: path + name: username + required: true + schema: + type: string + example: johnSnow01 + requestBody: + required: true + content: + application/json: + schema: + properties: + password: + type: string + type: object + '/SASjsApi/user/{userId}': + get: + operationId: GetUser + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/UserDetailsResponse' + description: 'Only Admin or user itself will get user autoExec code.' + summary: 'Get user properties - such as group memberships, userName, displayName.' + tags: + - User + security: + - + bearerAuth: [] + parameters: + - + description: 'The user''s identifier' + in: path + name: userId + required: true + schema: + format: double + type: number + example: 1234 + patch: + operationId: UpdateUser + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/UserDetailsResponse' + examples: + 'Example 1': + 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.' + tags: + - User + security: + - + bearerAuth: [] + parameters: + - + description: 'The user''s identifier' + in: path + name: userId + required: true + schema: + format: double + type: number + example: '1234' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UserPayload' + delete: + operationId: DeleteUser + responses: + '204': + description: 'No content' + summary: 'Delete a user. Can be performed either by admins, or the user in question.' + tags: + - User + security: + - + bearerAuth: [] + parameters: + - + description: 'The user''s identifier' + in: path + name: userId + required: true + schema: + format: double + type: number + example: 1234 + requestBody: + required: true + content: + application/json: + schema: + properties: + password: + type: string + type: object + /SASjsApi/group: + get: + operationId: GetAllGroups + responses: + '200': + description: Ok + content: + application/json: + schema: + items: + $ref: '#/components/schemas/GroupResponse' + type: array + examples: + 'Example 1': + 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.' + tags: + - Group + security: + - + bearerAuth: [] + parameters: [] + post: + operationId: CreateGroup + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/GroupDetailsResponse' + examples: + 'Example 1': + value: {groupId: 123, name: DCGroup, description: 'This group represents Data Controller Users', isActive: true, users: []} + summary: 'Create a new group. Admin only.' + tags: + - Group + security: + - + bearerAuth: [] + parameters: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/GroupPayload' + '/SASjsApi/group/by/groupname/{name}': + get: + operationId: GetGroupByGroupName + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/GroupDetailsResponse' + summary: 'Get list of members of a group (userName). All users can request this.' + tags: + - Group + security: + - + bearerAuth: [] + parameters: + - + description: 'The group''s name' + in: path + name: name + required: true + schema: + type: string + '/SASjsApi/group/{groupId}': + get: + operationId: GetGroup + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/GroupDetailsResponse' + summary: 'Get list of members of a group (userName). All users can request this.' + tags: + - Group + security: + - + bearerAuth: [] + parameters: + - + description: 'The group''s identifier' + in: path + name: groupId + required: true + schema: + format: double + type: number + example: 1234 + delete: + operationId: DeleteGroup + responses: + '200': + description: Ok + content: + application/json: + schema: + allOf: + - {$ref: '#/components/schemas/IGroup'} + - {properties: {_id: {$ref: '#/components/schemas/ObjectId'}}, required: [_id], type: object} + summary: 'Delete a group. Admin task only.' + tags: + - Group + security: + - + bearerAuth: [] + parameters: + - + description: 'The group''s identifier' + in: path + name: groupId + required: true + schema: + format: double + type: number + example: 1234 + '/SASjsApi/group/{groupId}/{userId}': + post: + operationId: AddUserToGroup + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/GroupDetailsResponse' + examples: + 'Example 1': + 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.' + tags: + - Group + security: + - + bearerAuth: [] + parameters: + - + description: 'The group''s identifier' + in: path + name: groupId + required: true + schema: + format: double + type: number + example: '1234' + - + description: 'The user''s identifier' + in: path + name: userId + required: true + schema: + format: double + type: number + example: '6789' + delete: + operationId: RemoveUserFromGroup + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/GroupDetailsResponse' + examples: + 'Example 1': + 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.' + tags: + - Group + security: + - + bearerAuth: [] + parameters: + - + description: 'The group''s identifier' + in: path + name: groupId + required: true + schema: + format: double + type: number + example: '1234' + - + description: 'The user''s identifier' + in: path + name: userId + required: true + schema: + format: double + type: number + example: '6789' + /SASjsApi/info: + get: + operationId: Info + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/InfoResponse' + examples: + 'Example 1': + 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).' + tags: + - Info + security: [] + parameters: [] + /SASjsApi/info/authorizedRoutes: + get: + operationId: AuthorizedRoutes + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/AuthorizedRoutesResponse' + examples: + 'Example 1': + value: {paths: [/AppStream, /SASjsApi/stp/execute]} + summary: 'Get the list of available routes to which permissions can be applied. Used to populate the dialog in the URI Permissions feature.' + tags: + - Info + security: [] + parameters: [] + /SASjsApi/permission: + get: + operationId: GetAllPermissions + responses: + '200': + description: Ok + content: + application/json: + schema: + items: + $ref: '#/components/schemas/PermissionDetailsResponse' + type: array + examples: + 'Example 1': + value: [{permissionId: 123, path: /SASjsApi/code/execute, type: Route, setting: Grant, user: {id: 1, username: johnSnow01, displayName: 'John Snow', isAdmin: false}}, {permissionId: 124, path: /SASjsApi/code/execute, type: Route, setting: Grant, group: {groupId: 1, name: DCGroup, description: 'This group represents Data Controller Users', isActive: true, users: []}}] + description: "Get the list of permission rules applicable the authenticated user.\nIf the user is an admin, all rules are returned." + summary: 'Get the list of permission rules. If the user is admin, all rules are returned.' + tags: + - Permission + security: + - + bearerAuth: [] + parameters: [] + post: + operationId: CreatePermission + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/PermissionDetailsResponse' + examples: + 'Example 1': + value: {permissionId: 123, path: /SASjsApi/code/execute, type: Route, setting: Grant, user: {id: 1, username: johnSnow01, displayName: 'John Snow', isAdmin: false}} + summary: 'Create a new permission. Admin only.' + tags: + - Permission + security: + - + bearerAuth: [] + parameters: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/RegisterPermissionPayload' + '/SASjsApi/permission/{permissionId}': + patch: + operationId: UpdatePermission + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/PermissionDetailsResponse' + examples: + 'Example 1': + value: {permissionId: 123, path: /SASjsApi/code/execute, type: Route, setting: Grant, user: {id: 1, username: johnSnow01, displayName: 'John Snow', isAdmin: false}} + summary: 'Update permission setting. Admin only' + tags: + - Permission + security: + - + bearerAuth: [] + parameters: + - + description: 'The permission''s identifier' + in: path + name: permissionId + required: true + schema: + format: double + type: number + example: 1234 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdatePermissionPayload' + delete: + operationId: DeletePermission + responses: + '204': + description: 'No content' + summary: 'Delete a permission. Admin only.' + tags: + - Permission + security: + - + bearerAuth: [] + parameters: + - + description: 'The user''s identifier' + in: path + name: permissionId + required: true + schema: + format: double + type: number + example: 1234 + /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, isAdmin: false} + summary: 'Get session info (username).' + tags: + - Session + security: + - + bearerAuth: [] + parameters: [] + /SASjsApi/stp/execute: + get: + operationId: ExecuteReturnRaw + responses: + '200': + description: Ok + content: + application/json: + schema: + anyOf: + - {type: string} + - {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" + summary: 'Execute a Stored Program, returns raw _webout content.' + tags: + - STP + security: + - + bearerAuth: [] + parameters: + - + description: 'Location of SAS or JS code' + in: query + name: _program + required: true + schema: + type: string + example: /Projects/myApp/some/program + post: + operationId: ExecuteReturnJson + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/ExecuteReturnJsonResponse' + examples: + 'Example 1': + value: {status: success, _webout: 'webout content', log: [], httpHeaders: {Content-type: application/zip, Cache-Control: 'public, max-age=1000'}} + description: "Trigger a SAS or JS program using the _program URL parameter.\n\nAccepts URL parameters and file uploads. For more details, see docs:\n\nhttps://server.sasjs.io/storedprograms\n\nThe response will be a JSON object with the following root attributes:\nlog, webout, headers.\n\nThe webout attribute will be nested JSON ONLY if the response-header\ncontains a content-type of application/json AND it is valid JSON.\nOtherwise it will be a stringified version of the webout content." + summary: 'Execute a Stored Program, return a JSON object' + tags: + - STP + security: + - + bearerAuth: [] + parameters: + - + description: 'Location of SAS or JS code' + in: query + name: _program + required: false + schema: + type: string + example: /Projects/myApp/some/program + requestBody: + required: false + content: + application/json: + schema: + $ref: '#/components/schemas/ExecuteReturnJsonPayload' + /: + get: + operationId: Home + responses: + '200': + description: Ok + content: + application/json: + schema: + type: string + summary: 'Render index.html' + tags: + - Web + security: [] + parameters: [] + /SASLogon/login: + post: + operationId: Login + responses: + '200': + description: Ok + content: + application/json: + schema: + properties: + user: {properties: {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: + - user + - loggedIn + type: object + summary: 'Accept a valid username/password' + tags: + - Web + security: [] + parameters: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/LoginPayload' + /SASLogon/authorize: + post: + operationId: Authorize + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/AuthorizeResponse' + examples: + 'Example 1': + value: {code: someRandomCryptoString} + summary: 'Accept a valid username/password, plus a CLIENT_ID, and return an AUTH_CODE' + tags: + - Web + security: [] + parameters: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AuthorizePayload' + /SASLogon/logout: + get: + operationId: Logout + responses: + '200': + description: Ok + content: + application/json: + schema: {} + summary: 'Destroy the session stored in cookies' + tags: + - Web + security: [] + parameters: [] servers: - - url: / + - + url: / tags: - - name: Auth - description: 'Operations about auth' - - name: Client - description: 'Operations about clients' - - name: CODE - description: 'Execution of code (various runtimes are supported)' - - name: Drive - description: 'Operations on SASjs Drive' - - name: Group - description: 'Operations on groups and group memberships' - - name: Info - description: 'Get Server Information' - - name: Permission - description: 'Operations about permissions' - - name: Session - description: 'Get Session information' - - name: STP - description: 'Execution of Stored Programs' - - name: User - description: 'Operations with users' - - name: Web - description: 'Operations on Web' + - + name: Auth + description: 'Operations about auth' + - + name: Client + description: 'Operations about clients' + - + name: CODE + description: 'Execution of code (various runtimes are supported)' + - + name: Drive + description: 'Operations on SASjs Drive' + - + name: Group + description: 'Operations on groups and group memberships' + - + name: Info + description: 'Get Server Information' + - + name: Permission + description: 'Operations about permissions' + - + name: Session + description: 'Get Session information' + - + name: STP + description: 'Execution of Stored Programs' + - + name: User + description: 'Operations with users' + - + name: Web + description: 'Operations on Web' diff --git a/api/src/routes/api/mock-sas9.ts b/api/src/routes/api/mock-sas9.ts new file mode 100644 index 00000000..f06d85b4 --- /dev/null +++ b/api/src/routes/api/mock-sas9.ts @@ -0,0 +1,57 @@ +import { readFile } from '@sasjs/utils' +import express, { Request } from 'express' +import path from 'path' + +const mockSas9Router = express.Router() + +const { MOCK_SERVERTYPE } = process.env + +mockSas9Router.post('/SASStoredProcess/do/', async (req, res) => { + let program = req.query._program?.toString() || '' + program = program.replace('/', '') + const filePath = path.join(process.cwd(), 'mocks', program) + + let file + + try { + file = await readFile( + filePath + ) + } catch (err: any) { + console.error(`Mocked file on path: ${filePath} is not found.`) + + return + } + + if (!file) { + console.error(`Mocked file on path: ${filePath} is not found.`) + return + } + + let fileContent = '' + + try { + fileContent = JSON.parse(file) + } catch (err: any) { + fileContent = file + } + + try { + res.send(fileContent) + } catch (err: any) { + res.status(403).send(err.toString()) + } +}) + +if (MOCK_SERVERTYPE === undefined) { + mockSas9Router.post('/SASLogon/login', async (req, res) => { + try { + res.send({ msg: 'Login' }) + } catch (err: any) { + res.status(403).send(err.toString()) + } + }) + +} + +export default mockSas9Router diff --git a/api/src/routes/api/mock-viya.ts b/api/src/routes/api/mock-viya.ts new file mode 100644 index 00000000..8f999e91 --- /dev/null +++ b/api/src/routes/api/mock-viya.ts @@ -0,0 +1,13 @@ +import express from 'express' + +const mockViyaRouter = express.Router() + +mockViyaRouter.post('/SASJobExecution/', async (req, res) => { + try { + res.send({ test: 'test' }) + } catch (err: any) { + res.status(403).send(err.toString()) + } +}) + +export default mockViyaRouter diff --git a/api/src/routes/web/web.ts b/api/src/routes/web/web.ts index 03510b5c..8acc0923 100644 --- a/api/src/routes/web/web.ts +++ b/api/src/routes/web/web.ts @@ -2,10 +2,13 @@ import express from 'express' import { WebController } from '../../controllers/web' import { authenticateAccessToken, desktopRestrict } from '../../middlewares' import { authorizeValidation, loginWebValidation } from '../../utils' +import mockSas9Router from '../api/mock-sas9' const webRouter = express.Router() const controller = new WebController() +const { MOCK_SERVERTYPE } = process.env + webRouter.get('/', async (req, res) => { let response try { @@ -23,17 +26,19 @@ webRouter.get('/', async (req, res) => { } }) -webRouter.post('/SASLogon/login', desktopRestrict, async (req, res) => { - const { error, value: body } = loginWebValidation(req.body) - if (error) return res.status(400).send(error.details[0].message) - - try { - const response = await controller.login(req, body) - res.send(response) - } catch (err: any) { - res.status(403).send(err.toString()) - } -}) +if (MOCK_SERVERTYPE !== undefined) { + webRouter.post('/SASLogon/login', desktopRestrict, async (req, res) => { + const { error, value: body } = loginWebValidation(req.body) + if (error) return res.status(400).send(error.details[0].message) + + try { + const response = await controller.login(req, body) + res.send(response) + } catch (err: any) { + res.status(403).send(err.toString()) + } + }) +} webRouter.post( '/SASLogon/authorize', @@ -61,4 +66,8 @@ webRouter.get('/SASLogon/logout', desktopRestrict, async (req, res) => { } }) +webRouter.use('/', mockSas9Router) +// disabled for now +// webRouter.use('/', mockViyaRouter) + export default webRouter From eeba2328c0eeaf7bf5b7a73ecce2e2a82f973708 Mon Sep 17 00:00:00 2001 From: Mihajlo Medjedovic Date: Fri, 26 Aug 2022 17:59:07 +0200 Subject: [PATCH 02/10] chore: added login, logout endpoints --- .gitignore | 2 +- api/src/routes/api/mock-sas9.ts | 88 ++++++++++++++++++++++++++++++--- api/src/routes/web/web.ts | 26 ++++++---- 3 files changed, 99 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index 7a79a4aa..896d7ee2 100644 --- a/.gitignore +++ b/.gitignore @@ -5,8 +5,8 @@ node_modules/ .env* sas/ sasjs_root/ +api/mocks/ tmp/ -api/mocks/* build/ sasjsbuild/ sasjscore/ diff --git a/api/src/routes/api/mock-sas9.ts b/api/src/routes/api/mock-sas9.ts index f06d85b4..7799dfc1 100644 --- a/api/src/routes/api/mock-sas9.ts +++ b/api/src/routes/api/mock-sas9.ts @@ -1,15 +1,44 @@ import { readFile } from '@sasjs/utils' -import express, { Request } from 'express' +import express from 'express' import path from 'path' const mockSas9Router = express.Router() const { MOCK_SERVERTYPE } = process.env +let loggedIn: boolean = false + +mockSas9Router.get('/SASStoredProcess', async (req, res) => { + if (!loggedIn) { + res.redirect('/SASLogon/login') + return + } + + const filePath = path.join(process.cwd(), 'mocks', "generic", "sas9", "sas-stored-process") + + let file + + try { + file = await readFile( + filePath + ) + } catch (err: any) { + console.error(`Mocked file on path: ${filePath} is not found.`) + res.status(403).send(err.toString()) + return + } + + try { + res.send(file) + } catch (err: any) { + res.status(403).send(err.toString()) + } +}) + mockSas9Router.post('/SASStoredProcess/do/', async (req, res) => { let program = req.query._program?.toString() || '' program = program.replace('/', '') - const filePath = path.join(process.cwd(), 'mocks', program) + const filePath = path.join(process.cwd(), 'mocks', ...program.split('/')) let file @@ -18,13 +47,21 @@ mockSas9Router.post('/SASStoredProcess/do/', async (req, res) => { filePath ) } catch (err: any) { - console.error(`Mocked file on path: ${filePath} is not found.`) - + let err = `Mocked file on path: ${filePath} is not found.` + console.error(err) + res.status(403).send(err) return } if (!file) { - console.error(`Mocked file on path: ${filePath} is not found.`) + let err = `Mocked file on path: ${filePath} is not found.` + console.error(err) + res.status(403).send(err) + return + } + + if (!loggedIn) { + res.redirect('/SASLogon/login') return } @@ -43,15 +80,52 @@ mockSas9Router.post('/SASStoredProcess/do/', async (req, res) => { } }) -if (MOCK_SERVERTYPE === undefined) { +if (MOCK_SERVERTYPE !== undefined) { + mockSas9Router.get('/SASLogon/login', async (req, res) => { + const filePath = path.join(process.cwd(), 'mocks', 'generic', 'sas9', 'login') + + try { + const file = await readFile( + filePath + ) + + res.send(file) + } catch (err: any) { + res.status(403).send(err.toString()) + } + }) + mockSas9Router.post('/SASLogon/login', async (req, res) => { + loggedIn = true + + const filePath = path.join(process.cwd(), 'mocks', 'generic', 'sas9', 'logged-in') + try { - res.send({ msg: 'Login' }) + const file = await readFile( + filePath + ) + + res.send(file) } catch (err: any) { res.status(403).send(err.toString()) } }) + mockSas9Router.get('/SASLogon/logout', async (req, res) => { + loggedIn = false + + const filePath = path.join(process.cwd(), 'mocks', 'generic', 'sas9', 'logged-out') + + try { + const file = await readFile( + filePath + ) + + res.send(file) + } catch (err: any) { + res.status(403).send(err.toString()) + } + }) } export default mockSas9Router diff --git a/api/src/routes/web/web.ts b/api/src/routes/web/web.ts index 8acc0923..956056d9 100644 --- a/api/src/routes/web/web.ts +++ b/api/src/routes/web/web.ts @@ -26,7 +26,10 @@ webRouter.get('/', async (req, res) => { } }) -if (MOCK_SERVERTYPE !== undefined) { +/** + * If any type of mock is enabled, we won't use regular working logon endpoints + */ +if (MOCK_SERVERTYPE === undefined) { webRouter.post('/SASLogon/login', desktopRestrict, async (req, res) => { const { error, value: body } = loginWebValidation(req.body) if (error) return res.status(400).send(error.details[0].message) @@ -57,14 +60,19 @@ webRouter.post( } ) -webRouter.get('/SASLogon/logout', desktopRestrict, async (req, res) => { - try { - await controller.logout(req) - res.status(200).send('OK!') - } catch (err: any) { - res.status(403).send(err.toString()) - } -}) +/** + * If any type of mock is enabled, we won't use regular working logon endpoints + */ +if (MOCK_SERVERTYPE === undefined) { + webRouter.get('/SASLogon/logout', desktopRestrict, async (req, res) => { + try { + await controller.logout(req) + res.status(200).send('OK!') + } catch (err: any) { + res.status(403).send(err.toString()) + } + }) +} webRouter.use('/', mockSas9Router) // disabled for now From 3b188cd724f3f05144666797c3c8f38244885aae Mon Sep 17 00:00:00 2001 From: Mihajlo Medjedovic Date: Fri, 26 Aug 2022 18:03:28 +0200 Subject: [PATCH 03/10] style: lint --- api/src/routes/api/mock-sas9.ts | 54 +++++++++++++++++++++------------ api/src/routes/web/web.ts | 2 +- 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/api/src/routes/api/mock-sas9.ts b/api/src/routes/api/mock-sas9.ts index 7799dfc1..4281d1bf 100644 --- a/api/src/routes/api/mock-sas9.ts +++ b/api/src/routes/api/mock-sas9.ts @@ -14,14 +14,18 @@ mockSas9Router.get('/SASStoredProcess', async (req, res) => { return } - const filePath = path.join(process.cwd(), 'mocks', "generic", "sas9", "sas-stored-process") + const filePath = path.join( + process.cwd(), + 'mocks', + 'generic', + 'sas9', + 'sas-stored-process' + ) let file try { - file = await readFile( - filePath - ) + file = await readFile(filePath) } catch (err: any) { console.error(`Mocked file on path: ${filePath} is not found.`) res.status(403).send(err.toString()) @@ -39,13 +43,11 @@ mockSas9Router.post('/SASStoredProcess/do/', async (req, res) => { let program = req.query._program?.toString() || '' program = program.replace('/', '') const filePath = path.join(process.cwd(), 'mocks', ...program.split('/')) - + let file try { - file = await readFile( - filePath - ) + file = await readFile(filePath) } catch (err: any) { let err = `Mocked file on path: ${filePath} is not found.` console.error(err) @@ -82,12 +84,16 @@ mockSas9Router.post('/SASStoredProcess/do/', async (req, res) => { if (MOCK_SERVERTYPE !== undefined) { mockSas9Router.get('/SASLogon/login', async (req, res) => { - const filePath = path.join(process.cwd(), 'mocks', 'generic', 'sas9', 'login') + const filePath = path.join( + process.cwd(), + 'mocks', + 'generic', + 'sas9', + 'login' + ) try { - const file = await readFile( - filePath - ) + const file = await readFile(filePath) res.send(file) } catch (err: any) { @@ -98,12 +104,16 @@ if (MOCK_SERVERTYPE !== undefined) { mockSas9Router.post('/SASLogon/login', async (req, res) => { loggedIn = true - const filePath = path.join(process.cwd(), 'mocks', 'generic', 'sas9', 'logged-in') + const filePath = path.join( + process.cwd(), + 'mocks', + 'generic', + 'sas9', + 'logged-in' + ) try { - const file = await readFile( - filePath - ) + const file = await readFile(filePath) res.send(file) } catch (err: any) { @@ -114,12 +124,16 @@ if (MOCK_SERVERTYPE !== undefined) { mockSas9Router.get('/SASLogon/logout', async (req, res) => { loggedIn = false - const filePath = path.join(process.cwd(), 'mocks', 'generic', 'sas9', 'logged-out') + const filePath = path.join( + process.cwd(), + 'mocks', + 'generic', + 'sas9', + 'logged-out' + ) try { - const file = await readFile( - filePath - ) + const file = await readFile(filePath) res.send(file) } catch (err: any) { diff --git a/api/src/routes/web/web.ts b/api/src/routes/web/web.ts index 956056d9..e53099b1 100644 --- a/api/src/routes/web/web.ts +++ b/api/src/routes/web/web.ts @@ -33,7 +33,7 @@ if (MOCK_SERVERTYPE === undefined) { webRouter.post('/SASLogon/login', desktopRestrict, async (req, res) => { const { error, value: body } = loginWebValidation(req.body) if (error) return res.status(400).send(error.details[0].message) - + try { const response = await controller.login(req, body) res.send(response) From 71a4a48443e46a6da01a9721df356bdf10678173 Mon Sep 17 00:00:00 2001 From: Mihajlo Medjedovic Date: Mon, 29 Aug 2022 10:30:01 +0200 Subject: [PATCH 04/10] chore: generic sas9 mock responses --- .gitignore | 3 +- api/mocks/custom/.keep | 0 api/mocks/generic/sas9/logged-in | 126 +++++++++ api/mocks/generic/sas9/logged-out | 134 ++++++++++ api/mocks/generic/sas9/login | 208 +++++++++++++++ api/mocks/generic/sas9/sas-stored-process | 295 ++++++++++++++++++++++ 6 files changed, 765 insertions(+), 1 deletion(-) create mode 100644 api/mocks/custom/.keep create mode 100644 api/mocks/generic/sas9/logged-in create mode 100644 api/mocks/generic/sas9/logged-out create mode 100644 api/mocks/generic/sas9/login create mode 100644 api/mocks/generic/sas9/sas-stored-process diff --git a/.gitignore b/.gitignore index 896d7ee2..bb6d85e8 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,8 @@ node_modules/ .env* sas/ sasjs_root/ -api/mocks/ +api/mocks/custom/* +!api/mocks/custom/.keep tmp/ build/ sasjsbuild/ diff --git a/api/mocks/custom/.keep b/api/mocks/custom/.keep new file mode 100644 index 00000000..e69de29b diff --git a/api/mocks/generic/sas9/logged-in b/api/mocks/generic/sas9/logged-in new file mode 100644 index 00000000..ec33db1d --- /dev/null +++ b/api/mocks/generic/sas9/logged-in @@ -0,0 +1,126 @@ + + + + + + + + + + + + SAS® Logon Manager + + + + + + + + + +
+
+ + + + + + +
+ +
+
+

You have signed in.

+

For increased security, sign out and close your web browser when you finish accessing services that require authentication.

+ + +
+
+
+ + + +
+ + + + +
+ + + + +
+ +
+
+
+ + +
+ +
+
+

Product name: SAS® Logon Manager

+

Release: 9.4

+

Legal Notices

+
+
+

Copyright 2002-2020, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. + This software is protected by copyright laws and international treaties.

+

U.S. Government Restricted Rights

+

Use, duplication or disclosure of this software and related documentation by the United States government is subject to the license terms of the Agreement with SAS Institute Inc. pursuant to, as applicable, FAR 12.212, DFAR 227.7202-1(a), DFAR 227.7202-3(a) and DFAR 227.7202-4 and, to the extent required under United States federal law, the minimum restricted rights as set out in FAR 52.227-19 (DEC 2007).

+

Third-Party Software Usage

+

Central Authentication Service

+

Copyright © 2007, JA-SIG, Inc. All rights reserved.

+

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

+
    +
  • +

    Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

    +
  • +
    +
  • +

    Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

    +
  • +
    +
  • +

    Neither the name of the JA-SIG, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

    +
  • +
+

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+
+
+
+ +
+ + + + + + + + \ No newline at end of file diff --git a/api/mocks/generic/sas9/logged-out b/api/mocks/generic/sas9/logged-out new file mode 100644 index 00000000..fec405de --- /dev/null +++ b/api/mocks/generic/sas9/logged-out @@ -0,0 +1,134 @@ + + + + + + + + + + + + SAS® Logon Manager + + + + + + + + + +
+
+ + + + + + +
+ +
+
+ + + + +

You have signed out.

+ + + +

For increased security, close your browser.

+ + +
+ + +
+
+ + +
+ + + + +
+ + + + +
+ +
+
+
+ + +
+ +
+
+

Product name: SAS® Logon Manager

+

Release: 9.4

+

Legal Notices

+
+
+

Copyright 2002-2020, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. + This software is protected by copyright laws and international treaties.

+

U.S. Government Restricted Rights

+

Use, duplication or disclosure of this software and related documentation by the United States government is subject to the license terms of the Agreement with SAS Institute Inc. pursuant to, as applicable, FAR 12.212, DFAR 227.7202-1(a), DFAR 227.7202-3(a) and DFAR 227.7202-4 and, to the extent required under United States federal law, the minimum restricted rights as set out in FAR 52.227-19 (DEC 2007).

+

Third-Party Software Usage

+

Central Authentication Service

+

Copyright © 2007, JA-SIG, Inc. All rights reserved.

+

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

+
    +
  • +

    Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

    +
  • +
    +
  • +

    Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

    +
  • +
    +
  • +

    Neither the name of the JA-SIG, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

    +
  • +
+

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+
+
+
+ +
+ + + + + + + + \ No newline at end of file diff --git a/api/mocks/generic/sas9/login b/api/mocks/generic/sas9/login new file mode 100644 index 00000000..06fefa89 --- /dev/null +++ b/api/mocks/generic/sas9/login @@ -0,0 +1,208 @@ + + + + + + + + + + + + SAS® Logon Manager + + + + + + + + +
+
+
+ + + + + + + + + + + + + + +
+ +
+ + + +
+ + + + + + + + + + + + + + + + + +
+
+ + +
+
+ + +
+ + + + +
+ + + + +
+ +
+
+
+ + +
+ +
+
+

Product name: SAS® Logon Manager

+

Release: 9.4

+

Legal Notices

+
+
+

Copyright 2002-2020, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. + This software is protected by copyright laws and international treaties.

+

U.S. Government Restricted Rights

+

Use, duplication or disclosure of this software and related documentation by the United States government is subject to the license terms of the Agreement with SAS Institute Inc. pursuant to, as applicable, FAR 12.212, DFAR 227.7202-1(a), DFAR 227.7202-3(a) and DFAR 227.7202-4 and, to the extent required under United States federal law, the minimum restricted rights as set out in FAR 52.227-19 (DEC 2007).

+

Third-Party Software Usage

+

Central Authentication Service

+

Copyright © 2007, JA-SIG, Inc. All rights reserved.

+

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

+
    +
  • +

    Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

    +
  • +
    +
  • +

    Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

    +
  • +
    +
  • +

    Neither the name of the JA-SIG, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

    +
  • +
+

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+
+
+
+ +
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/api/mocks/generic/sas9/sas-stored-process b/api/mocks/generic/sas9/sas-stored-process new file mode 100644 index 00000000..38e5ed0e --- /dev/null +++ b/api/mocks/generic/sas9/sas-stored-process @@ -0,0 +1,295 @@ + + + + + + + + SAS Stored Process Web Application + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + + + + +
+ + + +

+ Welcome to the Version 9 SAS Stored Process Web Application. This application allows you to execute SAS Stored Processes from a Web browser. +

+ +
+ +
+
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + \ No newline at end of file From 091268bf58fcce40c80e914a5481f0b33d0adba6 Mon Sep 17 00:00:00 2001 From: Mihajlo Medjedovic Date: Mon, 29 Aug 2022 12:40:29 +0200 Subject: [PATCH 05/10] chore: mocking only mandatory bits from sas9 responses --- api/mocks/generic/sas9/logged-in | 127 +--------- api/mocks/generic/sas9/logged-out | 135 +--------- api/mocks/generic/sas9/login | 204 +-------------- api/mocks/generic/sas9/sas-stored-process | 296 +--------------------- 4 files changed, 16 insertions(+), 746 deletions(-) diff --git a/api/mocks/generic/sas9/logged-in b/api/mocks/generic/sas9/logged-in index ec33db1d..a871cac7 100644 --- a/api/mocks/generic/sas9/logged-in +++ b/api/mocks/generic/sas9/logged-in @@ -1,126 +1 @@ - - - - - - - - - - - - SAS® Logon Manager - - - - - - - - - -
-
- - - - - - -
- -
-
-

You have signed in.

-

For increased security, sign out and close your web browser when you finish accessing services that require authentication.

- - -
-
-
- - - -
- - - - -
- - - - -
- -
-
-
- - -
- -
-
-

Product name: SAS® Logon Manager

-

Release: 9.4

-

Legal Notices

-
-
-

Copyright 2002-2020, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. - This software is protected by copyright laws and international treaties.

-

U.S. Government Restricted Rights

-

Use, duplication or disclosure of this software and related documentation by the United States government is subject to the license terms of the Agreement with SAS Institute Inc. pursuant to, as applicable, FAR 12.212, DFAR 227.7202-1(a), DFAR 227.7202-3(a) and DFAR 227.7202-4 and, to the extent required under United States federal law, the minimum restricted rights as set out in FAR 52.227-19 (DEC 2007).

-

Third-Party Software Usage

-

Central Authentication Service

-

Copyright © 2007, JA-SIG, Inc. All rights reserved.

-

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

-
    -
  • -

    Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

    -
  • -
    -
  • -

    Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

    -
  • -
    -
  • -

    Neither the name of the JA-SIG, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

    -
  • -
-

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

-
-
-
- -
- - - - - - - - \ No newline at end of file +You have signed in. \ No newline at end of file diff --git a/api/mocks/generic/sas9/logged-out b/api/mocks/generic/sas9/logged-out index fec405de..70c33d45 100644 --- a/api/mocks/generic/sas9/logged-out +++ b/api/mocks/generic/sas9/logged-out @@ -1,134 +1 @@ - - - - - - - - - - - - SAS® Logon Manager - - - - - - - - - -
-
- - - - - - -
- -
-
- - - - -

You have signed out.

- - - -

For increased security, close your browser.

- - -
- - -
-
- - -
- - - - -
- - - - -
- -
-
-
- - -
- -
-
-

Product name: SAS® Logon Manager

-

Release: 9.4

-

Legal Notices

-
-
-

Copyright 2002-2020, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. - This software is protected by copyright laws and international treaties.

-

U.S. Government Restricted Rights

-

Use, duplication or disclosure of this software and related documentation by the United States government is subject to the license terms of the Agreement with SAS Institute Inc. pursuant to, as applicable, FAR 12.212, DFAR 227.7202-1(a), DFAR 227.7202-3(a) and DFAR 227.7202-4 and, to the extent required under United States federal law, the minimum restricted rights as set out in FAR 52.227-19 (DEC 2007).

-

Third-Party Software Usage

-

Central Authentication Service

-

Copyright © 2007, JA-SIG, Inc. All rights reserved.

-

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

-
    -
  • -

    Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

    -
  • -
    -
  • -

    Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

    -
  • -
    -
  • -

    Neither the name of the JA-SIG, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

    -
  • -
-

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

-
-
-
- -
- - - - - - - - \ No newline at end of file +You have signed out. \ No newline at end of file diff --git a/api/mocks/generic/sas9/login b/api/mocks/generic/sas9/login index 06fefa89..a34b3914 100644 --- a/api/mocks/generic/sas9/login +++ b/api/mocks/generic/sas9/login @@ -1,208 +1,30 @@ - - - - - - - - SAS® Logon Manager - - - -
-
-
- - - - - - - - - - - - - - -
- -
- - - -
- - - - - - - - - - - - - - - - + + + + + -
-
+ + + + -
-
- - -
- - - - -
+ + + - -
- -
-
-
- - -
- -
-
-

Product name: SAS® Logon Manager

-

Release: 9.4

-

Legal Notices

-
-
-

Copyright 2002-2020, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. - This software is protected by copyright laws and international treaties.

-

U.S. Government Restricted Rights

-

Use, duplication or disclosure of this software and related documentation by the United States government is subject to the license terms of the Agreement with SAS Institute Inc. pursuant to, as applicable, FAR 12.212, DFAR 227.7202-1(a), DFAR 227.7202-3(a) and DFAR 227.7202-4 and, to the extent required under United States federal law, the minimum restricted rights as set out in FAR 52.227-19 (DEC 2007).

-

Third-Party Software Usage

-

Central Authentication Service

-

Copyright © 2007, JA-SIG, Inc. All rights reserved.

-

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

-
    -
  • -

    Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

    -
  • -
    -
  • -

    Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

    -
  • -
    -
  • -

    Neither the name of the JA-SIG, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

    -
  • -
-

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

-
-
+
- -
- - - - - - - - - - - - - \ No newline at end of file diff --git a/api/mocks/generic/sas9/sas-stored-process b/api/mocks/generic/sas9/sas-stored-process index 38e5ed0e..192c02a5 100644 --- a/api/mocks/generic/sas9/sas-stored-process +++ b/api/mocks/generic/sas9/sas-stored-process @@ -1,295 +1 @@ - - - - - - - - SAS Stored Process Web Application - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
- - - - - - -
- - - -

- Welcome to the Version 9 SAS Stored Process Web Application. This application allows you to execute SAS Stored Processes from a Web browser. -

- -
- -
-
- -
-
- - -
- - - - - - - - - -
- - - - - \ No newline at end of file +"title": "Log Off SAS Demo User" \ No newline at end of file From 572fe22d50b926ea337ab7319fb6bc263f0901c4 Mon Sep 17 00:00:00 2001 From: Mihajlo Medjedovic Date: Tue, 30 Aug 2022 17:27:37 +0200 Subject: [PATCH 06/10] chore: mocksas9 controller --- api/src/controllers/mock-sas9.ts | 190 +++++++++++++++++++++++++++++++ api/src/routes/api/mock-sas9.ts | 105 +++-------------- 2 files changed, 208 insertions(+), 87 deletions(-) create mode 100644 api/src/controllers/mock-sas9.ts diff --git a/api/src/controllers/mock-sas9.ts b/api/src/controllers/mock-sas9.ts new file mode 100644 index 00000000..39eabf59 --- /dev/null +++ b/api/src/controllers/mock-sas9.ts @@ -0,0 +1,190 @@ +import { readFile } from '@sasjs/utils' +import express from 'express' +import path from 'path' +import { Request, Post, Get } from 'tsoa' + +export interface Sas9Response { + content: string + redirect?: string + error?: boolean +} + +export interface MockFileRead { + content: string + error?: boolean +} + +export class MockSas9Controller { + private loggedIn: boolean = false + + @Get('/SASStoredProcess') + public async sasStoredProcess(): Promise { + if (!this.loggedIn) { + return { + content: '', + redirect: '/SASLogon/login' + } + } + + const content = await getMockResponseFromFile([ + process.cwd(), + 'mocks', + 'generic', + 'sas9', + 'sas-stored-process' + ]) + + if (content.error) { + return { + content: content.content, + error: true + } + } else { + return { + content: content.content + } + } + } + + @Post('/SASStoredProcess/do/') + public async sasStoredProcessDo( + @Request() req: express.Request + ): Promise { + if (!this.loggedIn) { + return { + content: '', + redirect: '/SASLogon/login' + } + } + + let program = req.query._program?.toString() || '' + program = program.replace('/', '') + + const content = await getMockResponseFromFile([ + process.cwd(), + 'mocks', + ...program.split('/') + ]) + + if (content.error) { + return { + content: content.content, + error: true + } + } + + const parsedContent = parseJsonIfValid(content.content) + + return { + content: parsedContent + } + } + + @Get('/SASLogon/login') + public async loginGet(): Promise { + const content = await getMockResponseFromFile([ + process.cwd(), + 'mocks', + 'generic', + 'sas9', + 'login' + ]) + + if (content.error) { + return { + content: content.content, + error: true + } + } else { + return { + content: content.content + } + } + } + + @Post('/SASLogon/login') + public async loginPost(): Promise { + this.loggedIn = true + + const content = await getMockResponseFromFile([ + process.cwd(), + 'mocks', + 'generic', + 'sas9', + 'logged-in' + ]) + + if (content.error) { + return { + content: content.content, + error: true + } + } else { + return { + content: content.content + } + } + } + + @Get('/SASLogon/logout') + public async logout(): Promise { + this.loggedIn = false + + const content = await getMockResponseFromFile([ + process.cwd(), + 'mocks', + 'generic', + 'sas9', + 'logged-out' + ]) + + if (content.error) { + return { + content: content.content, + error: true + } + } else { + return { + content: content.content + } + } + } +} + +/** + * If JSON is valid it will be parsed otherwise will return text unaltered + * @param content string to be parsed + * @returns JSON or string + */ +const parseJsonIfValid = (content: string) => { + let fileContent = '' + + try { + fileContent = JSON.parse(content) + } catch (err: any) { + fileContent = content + } + + return fileContent +} + +const getMockResponseFromFile = async ( + filePath: string[] +): Promise => { + const filePathParsed = path.join(...filePath) + let error: boolean = false + + let file = await readFile(filePathParsed).catch((err: any) => { + const errMsg = `Error reading mocked file on path: ${filePathParsed}\nError: ${err}` + console.error(errMsg) + + error = true + + return errMsg + }) + + return { + content: file, + error: error + } +} diff --git a/api/src/routes/api/mock-sas9.ts b/api/src/routes/api/mock-sas9.ts index 4281d1bf..3421eba0 100644 --- a/api/src/routes/api/mock-sas9.ts +++ b/api/src/routes/api/mock-sas9.ts @@ -1,82 +1,41 @@ import { readFile } from '@sasjs/utils' import express from 'express' import path from 'path' +import { MockSas9Controller } from '../../controllers/mock-sas9' const mockSas9Router = express.Router() const { MOCK_SERVERTYPE } = process.env -let loggedIn: boolean = false +// Mock controller must be singleton because it keeps the states +// for example `isLoggedIn` and potentially more in future mocks +const mockSas9Controller = new MockSas9Controller() mockSas9Router.get('/SASStoredProcess', async (req, res) => { - if (!loggedIn) { - res.redirect('/SASLogon/login') - return - } - - const filePath = path.join( - process.cwd(), - 'mocks', - 'generic', - 'sas9', - 'sas-stored-process' - ) - - let file + const response = await mockSas9Controller.sasStoredProcess() - try { - file = await readFile(filePath) - } catch (err: any) { - console.error(`Mocked file on path: ${filePath} is not found.`) - res.status(403).send(err.toString()) + if (response.redirect) { + res.redirect(response.redirect) return } try { - res.send(file) + res.send(response.content) } catch (err: any) { res.status(403).send(err.toString()) } }) mockSas9Router.post('/SASStoredProcess/do/', async (req, res) => { - let program = req.query._program?.toString() || '' - program = program.replace('/', '') - const filePath = path.join(process.cwd(), 'mocks', ...program.split('/')) - - let file - - try { - file = await readFile(filePath) - } catch (err: any) { - let err = `Mocked file on path: ${filePath} is not found.` - console.error(err) - res.status(403).send(err) - return - } + const response = await mockSas9Controller.sasStoredProcessDo(req) - if (!file) { - let err = `Mocked file on path: ${filePath} is not found.` - console.error(err) - res.status(403).send(err) + if (response.redirect) { + res.redirect(response.redirect) return } - if (!loggedIn) { - res.redirect('/SASLogon/login') - return - } - - let fileContent = '' - try { - fileContent = JSON.parse(file) - } catch (err: any) { - fileContent = file - } - - try { - res.send(fileContent) + res.send(response.content) } catch (err: any) { res.status(403).send(err.toString()) } @@ -84,58 +43,30 @@ mockSas9Router.post('/SASStoredProcess/do/', async (req, res) => { if (MOCK_SERVERTYPE !== undefined) { mockSas9Router.get('/SASLogon/login', async (req, res) => { - const filePath = path.join( - process.cwd(), - 'mocks', - 'generic', - 'sas9', - 'login' - ) + const response = await mockSas9Controller.loginGet() try { - const file = await readFile(filePath) - - res.send(file) + res.send(response.content) } catch (err: any) { res.status(403).send(err.toString()) } }) mockSas9Router.post('/SASLogon/login', async (req, res) => { - loggedIn = true - - const filePath = path.join( - process.cwd(), - 'mocks', - 'generic', - 'sas9', - 'logged-in' - ) + const response = await mockSas9Controller.loginPost() try { - const file = await readFile(filePath) - - res.send(file) + res.send(response.content) } catch (err: any) { res.status(403).send(err.toString()) } }) mockSas9Router.get('/SASLogon/logout', async (req, res) => { - loggedIn = false - - const filePath = path.join( - process.cwd(), - 'mocks', - 'generic', - 'sas9', - 'logged-out' - ) + const response = await mockSas9Controller.logout() try { - const file = await readFile(filePath) - - res.send(file) + res.send(response.content) } catch (err: any) { res.status(403).send(err.toString()) } From 763360831810bb7868e73e58624d2113552d7d83 Mon Sep 17 00:00:00 2001 From: Mihajlo Medjedovic Date: Wed, 31 Aug 2022 13:31:28 +0200 Subject: [PATCH 07/10] chore: mocker architecture fix, env validation --- api/src/routes/api/mock-sas9.ts | 76 ------------------------- api/src/routes/api/mock-viya.ts | 13 ----- api/src/routes/web/index.ts | 20 ++++++- api/src/routes/web/sas9-web.ts | 88 +++++++++++++++++++++++++++++ api/src/routes/web/sasviya-web.ts | 32 +++++++++++ api/src/routes/web/web.ts | 53 ++++++----------- api/src/utils/verifyEnvVariables.ts | 24 ++++++++ 7 files changed, 181 insertions(+), 125 deletions(-) delete mode 100644 api/src/routes/api/mock-sas9.ts delete mode 100644 api/src/routes/api/mock-viya.ts create mode 100644 api/src/routes/web/sas9-web.ts create mode 100644 api/src/routes/web/sasviya-web.ts diff --git a/api/src/routes/api/mock-sas9.ts b/api/src/routes/api/mock-sas9.ts deleted file mode 100644 index 3421eba0..00000000 --- a/api/src/routes/api/mock-sas9.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { readFile } from '@sasjs/utils' -import express from 'express' -import path from 'path' -import { MockSas9Controller } from '../../controllers/mock-sas9' - -const mockSas9Router = express.Router() - -const { MOCK_SERVERTYPE } = process.env - -// Mock controller must be singleton because it keeps the states -// for example `isLoggedIn` and potentially more in future mocks -const mockSas9Controller = new MockSas9Controller() - -mockSas9Router.get('/SASStoredProcess', async (req, res) => { - const response = await mockSas9Controller.sasStoredProcess() - - if (response.redirect) { - res.redirect(response.redirect) - return - } - - try { - res.send(response.content) - } catch (err: any) { - res.status(403).send(err.toString()) - } -}) - -mockSas9Router.post('/SASStoredProcess/do/', async (req, res) => { - const response = await mockSas9Controller.sasStoredProcessDo(req) - - if (response.redirect) { - res.redirect(response.redirect) - return - } - - try { - res.send(response.content) - } catch (err: any) { - res.status(403).send(err.toString()) - } -}) - -if (MOCK_SERVERTYPE !== undefined) { - mockSas9Router.get('/SASLogon/login', async (req, res) => { - const response = await mockSas9Controller.loginGet() - - try { - res.send(response.content) - } catch (err: any) { - res.status(403).send(err.toString()) - } - }) - - mockSas9Router.post('/SASLogon/login', async (req, res) => { - const response = await mockSas9Controller.loginPost() - - try { - res.send(response.content) - } catch (err: any) { - res.status(403).send(err.toString()) - } - }) - - mockSas9Router.get('/SASLogon/logout', async (req, res) => { - const response = await mockSas9Controller.logout() - - try { - res.send(response.content) - } catch (err: any) { - res.status(403).send(err.toString()) - } - }) -} - -export default mockSas9Router diff --git a/api/src/routes/api/mock-viya.ts b/api/src/routes/api/mock-viya.ts deleted file mode 100644 index 8f999e91..00000000 --- a/api/src/routes/api/mock-viya.ts +++ /dev/null @@ -1,13 +0,0 @@ -import express from 'express' - -const mockViyaRouter = express.Router() - -mockViyaRouter.post('/SASJobExecution/', async (req, res) => { - try { - res.send({ test: 'test' }) - } catch (err: any) { - res.status(403).send(err.toString()) - } -}) - -export default mockViyaRouter diff --git a/api/src/routes/web/index.ts b/api/src/routes/web/index.ts index 6f75c113..1ddd4e0f 100644 --- a/api/src/routes/web/index.ts +++ b/api/src/routes/web/index.ts @@ -1,8 +1,26 @@ import express from 'express' +import sas9WebRouter from './sas9-web' +import sasViyaWebRouter from './sasviya-web' import webRouter from './web' const router = express.Router() -router.use('/', webRouter) +const { MOCK_SERVERTYPE } = process.env + +console.log('MOCK_SERVERTYPE', MOCK_SERVERTYPE) + +switch (MOCK_SERVERTYPE?.toUpperCase()) { + case 'SAS9': { + router.use('/', sas9WebRouter) + break + } + case 'SASVIYA': { + router.use('/', sasViyaWebRouter) + break + } + default: { + router.use('/', webRouter) + } +} export default router diff --git a/api/src/routes/web/sas9-web.ts b/api/src/routes/web/sas9-web.ts new file mode 100644 index 00000000..09f53550 --- /dev/null +++ b/api/src/routes/web/sas9-web.ts @@ -0,0 +1,88 @@ +import express from 'express' +import { WebController } from '../../controllers' +import { MockSas9Controller } from '../../controllers/mock-sas9' + +const sas9WebRouter = express.Router() +const webController = new WebController() +// Mock controller must be singleton because it keeps the states +// for example `isLoggedIn` and potentially more in future mocks +const controller = new MockSas9Controller() + +sas9WebRouter.get('/', async (req, res) => { + let response + try { + response = await webController.home() + } catch (_) { + response = 'Web Build is not present' + } finally { + const codeToInject = `` + const injectedContent = response?.replace( + '', + `${codeToInject}` + ) + + return res.send(injectedContent) + } +}) + +sas9WebRouter.get('/SASStoredProcess', async (req, res) => { + const response = await controller.sasStoredProcess() + + if (response.redirect) { + res.redirect(response.redirect) + return + } + + try { + res.send(response.content) + } catch (err: any) { + res.status(403).send(err.toString()) + } +}) + +sas9WebRouter.post('/SASStoredProcess/do/', async (req, res) => { + const response = await controller.sasStoredProcessDo(req) + + if (response.redirect) { + res.redirect(response.redirect) + return + } + + try { + res.send(response.content) + } catch (err: any) { + res.status(403).send(err.toString()) + } +}) + +sas9WebRouter.get('/SASLogon/login', async (req, res) => { + const response = await controller.loginGet() + + try { + res.send(response.content) + } catch (err: any) { + res.status(403).send(err.toString()) + } +}) + +sas9WebRouter.post('/SASLogon/login', async (req, res) => { + const response = await controller.loginPost() + + try { + res.send(response.content) + } catch (err: any) { + res.status(403).send(err.toString()) + } +}) + +sas9WebRouter.get('/SASLogon/logout', async (req, res) => { + const response = await controller.logout() + + try { + res.send(response.content) + } catch (err: any) { + res.status(403).send(err.toString()) + } +}) + +export default sas9WebRouter diff --git a/api/src/routes/web/sasviya-web.ts b/api/src/routes/web/sasviya-web.ts new file mode 100644 index 00000000..98093190 --- /dev/null +++ b/api/src/routes/web/sasviya-web.ts @@ -0,0 +1,32 @@ +import express from 'express' +import { WebController } from '../../controllers/web' + +const sasViyaWebRouter = express.Router() +const controller = new WebController() + +sasViyaWebRouter.get('/', async (req, res) => { + let response + try { + response = await controller.home() + } catch (_) { + response = 'Web Build is not present' + } finally { + const codeToInject = `` + const injectedContent = response?.replace( + '', + `${codeToInject}` + ) + + return res.send(injectedContent) + } +}) + +sasViyaWebRouter.post('/SASJobExecution/', async (req, res) => { + try { + res.send({ test: 'test' }) + } catch (err: any) { + res.status(403).send(err.toString()) + } +}) + +export default sasViyaWebRouter diff --git a/api/src/routes/web/web.ts b/api/src/routes/web/web.ts index e53099b1..03510b5c 100644 --- a/api/src/routes/web/web.ts +++ b/api/src/routes/web/web.ts @@ -2,13 +2,10 @@ import express from 'express' import { WebController } from '../../controllers/web' import { authenticateAccessToken, desktopRestrict } from '../../middlewares' import { authorizeValidation, loginWebValidation } from '../../utils' -import mockSas9Router from '../api/mock-sas9' const webRouter = express.Router() const controller = new WebController() -const { MOCK_SERVERTYPE } = process.env - webRouter.get('/', async (req, res) => { let response try { @@ -26,22 +23,17 @@ webRouter.get('/', async (req, res) => { } }) -/** - * If any type of mock is enabled, we won't use regular working logon endpoints - */ -if (MOCK_SERVERTYPE === undefined) { - webRouter.post('/SASLogon/login', desktopRestrict, async (req, res) => { - const { error, value: body } = loginWebValidation(req.body) - if (error) return res.status(400).send(error.details[0].message) +webRouter.post('/SASLogon/login', desktopRestrict, async (req, res) => { + const { error, value: body } = loginWebValidation(req.body) + if (error) return res.status(400).send(error.details[0].message) - try { - const response = await controller.login(req, body) - res.send(response) - } catch (err: any) { - res.status(403).send(err.toString()) - } - }) -} + try { + const response = await controller.login(req, body) + res.send(response) + } catch (err: any) { + res.status(403).send(err.toString()) + } +}) webRouter.post( '/SASLogon/authorize', @@ -60,22 +52,13 @@ webRouter.post( } ) -/** - * If any type of mock is enabled, we won't use regular working logon endpoints - */ -if (MOCK_SERVERTYPE === undefined) { - webRouter.get('/SASLogon/logout', desktopRestrict, async (req, res) => { - try { - await controller.logout(req) - res.status(200).send('OK!') - } catch (err: any) { - res.status(403).send(err.toString()) - } - }) -} - -webRouter.use('/', mockSas9Router) -// disabled for now -// webRouter.use('/', mockViyaRouter) +webRouter.get('/SASLogon/logout', desktopRestrict, async (req, res) => { + try { + await controller.logout(req) + res.status(200).send('OK!') + } catch (err: any) { + res.status(403).send(err.toString()) + } +}) export default webRouter diff --git a/api/src/utils/verifyEnvVariables.ts b/api/src/utils/verifyEnvVariables.ts index 023cf059..a783b0e0 100644 --- a/api/src/utils/verifyEnvVariables.ts +++ b/api/src/utils/verifyEnvVariables.ts @@ -1,3 +1,8 @@ +export enum MOCK_SERVERTYPEType { + SAS9 = 'SAS9', + SASVIYA = 'SASVIYA' +} + export enum ModeType { Server = 'server', Desktop = 'desktop' @@ -40,6 +45,8 @@ export enum ReturnCode { export const verifyEnvVariables = (): ReturnCode => { const errors: string[] = [] + errors.push(...verifyMOCK_SERVERTYPE()) + errors.push(...verifyMODE()) errors.push(...verifyPROTOCOL()) @@ -66,6 +73,23 @@ export const verifyEnvVariables = (): ReturnCode => { return ReturnCode.Success } +const verifyMOCK_SERVERTYPE = (): string[] => { + const errors: string[] = [] + const { MOCK_SERVERTYPE } = process.env + + if (MOCK_SERVERTYPE) { + const modeTypes = Object.values(MOCK_SERVERTYPEType) + if (!modeTypes.includes(MOCK_SERVERTYPE as MOCK_SERVERTYPEType)) + errors.push( + `- MOCK_SERVERTYPE '${MOCK_SERVERTYPE}'\n - valid options ${modeTypes}` + ) + } else { + process.env.MOCK_SERVERTYPE = undefined + } + + return errors +} + const verifyMODE = (): string[] => { const errors: string[] = [] const { MODE } = process.env From 76bf84316e4a63da2d5798facc16379a4d56b729 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Thu, 1 Sep 2022 23:34:57 +0500 Subject: [PATCH 08/10] chore: MOCK_SERVERTYPE instead of string literals --- api/src/routes/web/index.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/api/src/routes/web/index.ts b/api/src/routes/web/index.ts index 1ddd4e0f..d5d4a64c 100644 --- a/api/src/routes/web/index.ts +++ b/api/src/routes/web/index.ts @@ -2,6 +2,7 @@ import express from 'express' import sas9WebRouter from './sas9-web' import sasViyaWebRouter from './sasviya-web' import webRouter from './web' +import { MOCK_SERVERTYPEType } from '../../utils' const router = express.Router() @@ -9,12 +10,12 @@ const { MOCK_SERVERTYPE } = process.env console.log('MOCK_SERVERTYPE', MOCK_SERVERTYPE) -switch (MOCK_SERVERTYPE?.toUpperCase()) { - case 'SAS9': { +switch (MOCK_SERVERTYPE) { + case MOCK_SERVERTYPEType.SAS9: { router.use('/', sas9WebRouter) break } - case 'SASVIYA': { + case MOCK_SERVERTYPEType.SASVIYA: { router.use('/', sasViyaWebRouter) break } From 7477326b229adb3d13f48384ff4c87893a600ea8 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Thu, 1 Sep 2022 23:38:04 +0500 Subject: [PATCH 09/10] chore: lower cased env values --- api/src/utils/verifyEnvVariables.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/utils/verifyEnvVariables.ts b/api/src/utils/verifyEnvVariables.ts index a783b0e0..b446b4b8 100644 --- a/api/src/utils/verifyEnvVariables.ts +++ b/api/src/utils/verifyEnvVariables.ts @@ -1,6 +1,6 @@ export enum MOCK_SERVERTYPEType { - SAS9 = 'SAS9', - SASVIYA = 'SASVIYA' + SAS9 = 'sas9', + SASVIYA = 'sasviya' } export enum ModeType { From b9a596616d2f9d1aea4bf5bee3de2908ce095640 Mon Sep 17 00:00:00 2001 From: Mihajlo Medjedovic Date: Mon, 5 Sep 2022 12:20:56 +0200 Subject: [PATCH 10/10] chore: cleanup --- api/src/controllers/mock-sas9.ts | 57 +++----------------------------- api/src/routes/web/index.ts | 2 -- 2 files changed, 5 insertions(+), 54 deletions(-) diff --git a/api/src/controllers/mock-sas9.ts b/api/src/controllers/mock-sas9.ts index 39eabf59..7136b178 100644 --- a/api/src/controllers/mock-sas9.ts +++ b/api/src/controllers/mock-sas9.ts @@ -26,24 +26,13 @@ export class MockSas9Controller { } } - const content = await getMockResponseFromFile([ + return await getMockResponseFromFile([ process.cwd(), 'mocks', 'generic', 'sas9', 'sas-stored-process' ]) - - if (content.error) { - return { - content: content.content, - error: true - } - } else { - return { - content: content.content - } - } } @Post('/SASStoredProcess/do/') @@ -67,10 +56,7 @@ export class MockSas9Controller { ]) if (content.error) { - return { - content: content.content, - error: true - } + return content } const parsedContent = parseJsonIfValid(content.content) @@ -82,72 +68,39 @@ export class MockSas9Controller { @Get('/SASLogon/login') public async loginGet(): Promise { - const content = await getMockResponseFromFile([ + return await getMockResponseFromFile([ process.cwd(), 'mocks', 'generic', 'sas9', 'login' ]) - - if (content.error) { - return { - content: content.content, - error: true - } - } else { - return { - content: content.content - } - } } @Post('/SASLogon/login') public async loginPost(): Promise { this.loggedIn = true - const content = await getMockResponseFromFile([ + return await getMockResponseFromFile([ process.cwd(), 'mocks', 'generic', 'sas9', 'logged-in' ]) - - if (content.error) { - return { - content: content.content, - error: true - } - } else { - return { - content: content.content - } - } } @Get('/SASLogon/logout') public async logout(): Promise { this.loggedIn = false - const content = await getMockResponseFromFile([ + return await getMockResponseFromFile([ process.cwd(), 'mocks', 'generic', 'sas9', 'logged-out' ]) - - if (content.error) { - return { - content: content.content, - error: true - } - } else { - return { - content: content.content - } - } } } diff --git a/api/src/routes/web/index.ts b/api/src/routes/web/index.ts index d5d4a64c..7bd4b828 100644 --- a/api/src/routes/web/index.ts +++ b/api/src/routes/web/index.ts @@ -8,8 +8,6 @@ const router = express.Router() const { MOCK_SERVERTYPE } = process.env -console.log('MOCK_SERVERTYPE', MOCK_SERVERTYPE) - switch (MOCK_SERVERTYPE) { case MOCK_SERVERTYPEType.SAS9: { router.use('/', sas9WebRouter)