diff --git a/CHANGELOG.md b/CHANGELOG.md index 04caf3d..dc10d32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +# [0.9.0](https://github.com/sasjs/server/compare/v0.8.3...v0.9.0) (2022-07-03) + + +### Features + +* removed secrets from env variables ([9c3da56](https://github.com/sasjs/server/commit/9c3da56901672a818f54267f9defc9f4701ab7fb)) + +## [0.8.3](https://github.com/sasjs/server/compare/v0.8.2...v0.8.3) (2022-07-02) + + +### Bug Fixes + +* **deploy:** extract first json from zip file ([e290751](https://github.com/sasjs/server/commit/e290751c872d24009482871a8c398e834357dcde)) + ## [0.8.2](https://github.com/sasjs/server/compare/v0.8.1...v0.8.2) (2022-06-22) diff --git a/README.md b/README.md index 7bd997a..3aab4af 100644 --- a/README.md +++ b/README.md @@ -105,10 +105,6 @@ CERT_CHAIN=certificate.pem (required) CA_ROOT=fullchain.pem (optional) # ENV variables required for MODE: `server` -ACCESS_TOKEN_SECRET= -REFRESH_TOKEN_SECRET= -AUTH_CODE_SECRET= -SESSION_SECRET= DB_CONNECT=mongodb+srv://:@/?retryWrites=true&w=majority # options: [disable|enable] default: `disable` for `server` & `enable` for `desktop` diff --git a/api/.env.example b/api/.env.example index f38b982..dc8374c 100644 --- a/api/.env.example +++ b/api/.env.example @@ -12,10 +12,6 @@ PORT=[5000] default value is 5000 HELMET_CSP_CONFIG_PATH=./csp.config.json if omitted HELMET default will be used HELMET_COEP=[true|false] if omitted HELMET default will be used -ACCESS_TOKEN_SECRET= -REFRESH_TOKEN_SECRET= -AUTH_CODE_SECRET= -SESSION_SECRET= DB_CONNECT=mongodb+srv://:@/?retryWrites=true&w=majority RUN_TIMES=[sas|js|sas,js|js,sas] default considered as sas diff --git a/api/public/swagger.yaml b/api/public/swagger.yaml index afa176f..ec7814c 100644 --- a/api/public/swagger.yaml +++ b/api/public/swagger.yaml @@ -5,225 +5,6 @@ components: requestBodies: {} responses: {} schemas: - GroupResponse: - properties: - groupId: - type: number - format: double - name: - type: string - description: - type: string - required: - - groupId - - name - - description - 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 - 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_' - 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 - PermissionDetailsResponse: - properties: - permissionId: - type: number - format: double - uri: - type: string - setting: - type: string - user: - $ref: '#/components/schemas/UserResponse' - group: - $ref: '#/components/schemas/GroupDetailsResponse' - required: - - permissionId - - uri - - setting - type: object - additionalProperties: false - PermissionSetting: - enum: - - Grant - - Deny - type: string - PrincipalType: - enum: - - user - - group - type: string - RegisterPermissionPayload: - properties: - uri: - type: string - description: 'Name of affected resource' - example: /SASjsApi/code/execute - setting: - $ref: '#/components/schemas/PermissionSetting' - 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: - - uri - - setting - - principalType - - principalId - type: object - additionalProperties: false - UpdatePermissionPayload: - properties: - setting: - $ref: '#/components/schemas/PermissionSetting' - description: 'The indication of whether (and to what extent) access is provided' - example: Grant - required: - - setting - type: object - additionalProperties: false TokenResponse: properties: accessToken: @@ -266,41 +47,6 @@ components: - userId type: object additionalProperties: false - LoginPayload: - properties: - username: - type: string - description: 'Username for user' - example: secretuser - password: - type: string - description: 'Password for user' - example: secretpassword - required: - - username - - password - type: object - additionalProperties: false - AuthorizeResponse: - properties: - code: - type: string - description: 'Authorization code' - example: someRandomCryptoString - required: - - code - type: object - additionalProperties: false - AuthorizePayload: - properties: - clientId: - type: string - description: 'Client ID' - example: clientID1 - required: - - clientId - type: object - additionalProperties: false ClientPayload: properties: clientId: @@ -524,6 +270,160 @@ components: - 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: @@ -566,6 +466,106 @@ components: example: /Public/somefolder/some.file type: object additionalProperties: false + LoginPayload: + properties: + username: + type: string + description: 'Username for user' + example: secretuser + password: + type: string + description: 'Password for user' + example: secretpassword + required: + - username + - password + type: object + additionalProperties: false + AuthorizeResponse: + properties: + code: + type: string + description: 'Authorization code' + example: someRandomCryptoString + required: + - code + type: object + additionalProperties: false + AuthorizePayload: + properties: + clientId: + type: string + description: 'Client ID' + example: clientID1 + required: + - clientId + type: object + additionalProperties: false + PermissionDetailsResponse: + properties: + permissionId: + type: number + format: double + uri: + type: string + setting: + type: string + user: + $ref: '#/components/schemas/UserResponse' + group: + $ref: '#/components/schemas/GroupDetailsResponse' + required: + - permissionId + - uri + - setting + type: object + additionalProperties: false + PermissionSetting: + enum: + - Grant + - Deny + type: string + PrincipalType: + enum: + - user + - group + type: string + RegisterPermissionPayload: + properties: + uri: + type: string + description: 'Name of affected resource' + example: /SASjsApi/code/execute + setting: + $ref: '#/components/schemas/PermissionSetting' + 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: + - uri + - setting + - principalType + - principalId + type: object + additionalProperties: false + UpdatePermissionPayload: + properties: + setting: + $ref: '#/components/schemas/PermissionSetting' + description: 'The indication of whether (and to what extent) access is provided' + example: Grant + required: + - setting + type: object + additionalProperties: false securitySchemes: bearerAuth: type: http @@ -579,534 +579,6 @@ info: name: '4GL Ltd' openapi: 3.0.0 paths: - /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/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/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, uri: /SASjsApi/code/execute, setting: Grant, user: {id: 1, username: johnSnow01, displayName: 'John Snow', isAdmin: false}}, {permissionId: 124, uri: /SASjsApi/code/execute, setting: Grant, group: {groupId: 1, name: DCGroup, description: 'This group represents Data Controller Users', isActive: true, users: []}}] - summary: 'Get list of all permissions (uri, setting and userDetail).' - 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, uri: /SASjsApi/code/execute, 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, uri: /SASjsApi/code/execute, 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/auth/token: post: operationId: Token @@ -1164,86 +636,6 @@ paths: - bearerAuth: [] parameters: [] - /: - get: - operationId: Home - responses: - '200': - description: Ok - content: - application/json: - schema: - type: string - summary: 'Render index.html' - tags: - - Web - security: [] - parameters: [] - /SASLogon/login: - post: - operationId: Login - responses: - '200': - description: Ok - content: - application/json: - schema: - properties: - user: {properties: {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: [] /SASjsApi/client: post: operationId: CreateClient @@ -1585,6 +977,431 @@ paths: - 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: {}}, 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 @@ -1703,6 +1520,189 @@ paths: application/json: schema: $ref: '#/components/schemas/ExecuteReturnJsonPayload' + /: + get: + operationId: Home + responses: + '200': + description: Ok + content: + application/json: + schema: + type: string + summary: 'Render index.html' + tags: + - Web + security: [] + parameters: [] + /SASLogon/login: + post: + operationId: Login + responses: + '200': + description: Ok + content: + application/json: + schema: + properties: + user: {properties: {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: [] + /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, uri: /SASjsApi/code/execute, setting: Grant, user: {id: 1, username: johnSnow01, displayName: 'John Snow', isAdmin: false}}, {permissionId: 124, uri: /SASjsApi/code/execute, setting: Grant, group: {groupId: 1, name: DCGroup, description: 'This group represents Data Controller Users', isActive: true, users: []}}] + summary: 'Get list of all permissions (uri, setting and userDetail).' + 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, uri: /SASjsApi/code/execute, 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, uri: /SASjsApi/code/execute, 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 servers: - url: / diff --git a/api/src/app.ts b/api/src/app.ts index e423b4f..de27567 100644 --- a/api/src/app.ts +++ b/api/src/app.ts @@ -1,5 +1,6 @@ import path from 'path' import express, { ErrorRequestHandler } from 'express' +import mongoose from 'mongoose' import csrf from 'csurf' import session from 'express-session' import MongoStore from 'connect-mongo' @@ -97,45 +98,44 @@ if (CORS === CorsType.ENABLED) { app.use(cors({ credentials: true, origin: whiteList })) } -/*********************************** - * DB Connection & * - * Express Sessions * - * With Mongo Store * - ***********************************/ -if (MODE === ModeType.Server) { - let store: MongoStore | undefined +export default setProcessVariables().then(async () => { + /*********************************** + * DB Connection & * + * Express Sessions * + * With Mongo Store * + ***********************************/ + if (MODE === ModeType.Server) { + let store: MongoStore | undefined - // NOTE: when exporting app.js as agent for supertest - // we should exclude connecting to the real database - if (process.env.NODE_ENV !== 'test') { - const clientPromise = connectDB().then((conn) => conn!.getClient() as any) + if (process.env.NODE_ENV !== 'test') { + store = MongoStore.create({ + client: mongoose.connection!.getClient() as any, + collectionName: 'sessions' + }) + } - store = MongoStore.create({ clientPromise, collectionName: 'sessions' }) + app.use( + session({ + secret: process.secrets.SESSION_SECRET, + saveUninitialized: false, // don't create session until something stored + resave: false, //don't save session if unmodified + store, + cookie: cookieOptions + }) + ) } - app.use( - session({ - secret: process.env.SESSION_SECRET as string, - saveUninitialized: false, // don't create session until something stored - resave: false, //don't save session if unmodified - store, - cookie: cookieOptions - }) - ) -} + app.use(express.json({ limit: '100mb' })) + app.use(express.static(path.join(__dirname, '../public'))) -app.use(express.json({ limit: '100mb' })) -app.use(express.static(path.join(__dirname, '../public'))) + const onError: ErrorRequestHandler = (err, req, res, next) => { + if (err.code === 'EBADCSRFTOKEN') + return res.status(400).send('Invalid CSRF token!') -const onError: ErrorRequestHandler = (err, req, res, next) => { - if (err.code === 'EBADCSRFTOKEN') - return res.status(400).send('Invalid CSRF token!') + console.error(err.stack) + res.status(500).send('Something broke!') + } - console.error(err.stack) - res.status(500).send('Something broke!') -} - -export default setProcessVariables().then(async () => { await setupFolders() await copySASjsCore() diff --git a/api/src/controllers/auth.ts b/api/src/controllers/auth.ts index e642213..778e3bf 100644 --- a/api/src/controllers/auth.ts +++ b/api/src/controllers/auth.ts @@ -129,8 +129,8 @@ const verifyAuthCode = async ( clientId: string, code: string ): Promise => { - return new Promise((resolve, reject) => { - jwt.verify(code, process.env.AUTH_CODE_SECRET as string, (err, data) => { + return new Promise((resolve) => { + jwt.verify(code, process.secrets.AUTH_CODE_SECRET, (err, data) => { if (err) return resolve(undefined) const clientInfo: InfoJWT = { diff --git a/api/src/controllers/group.ts b/api/src/controllers/group.ts index 0c37de9..9f6e41b 100644 --- a/api/src/controllers/group.ts +++ b/api/src/controllers/group.ts @@ -249,9 +249,10 @@ const updateUsersListInGroup = async ( message: 'User not found.' } - const updatedGroup = (action === 'addUser' - ? await group.addUser(user._id) - : await group.removeUser(user._id)) as unknown as GroupDetailsResponse + const updatedGroup = + action === 'addUser' + ? await group.addUser(user) + : await group.removeUser(user) if (!updatedGroup) throw { @@ -260,9 +261,6 @@ const updateUsersListInGroup = async ( message: 'Unable to update group.' } - if (action === 'addUser') user.addGroup(group._id) - else user.removeGroup(group._id) - return { groupId: updatedGroup.groupId, name: updatedGroup.name, diff --git a/api/src/middlewares/authenticateToken.ts b/api/src/middlewares/authenticateToken.ts index be39165..e368384 100644 --- a/api/src/middlewares/authenticateToken.ts +++ b/api/src/middlewares/authenticateToken.ts @@ -47,7 +47,7 @@ export const authenticateAccessToken: RequestHandler = async ( req, res, nextFunction, - process.env.ACCESS_TOKEN_SECRET as string, + process.secrets.ACCESS_TOKEN_SECRET, 'accessToken' ) } @@ -57,7 +57,7 @@ export const authenticateRefreshToken: RequestHandler = (req, res, next) => { req, res, next, - process.env.REFRESH_TOKEN_SECRET as string, + process.secrets.REFRESH_TOKEN_SECRET, 'refreshToken' ) } diff --git a/api/src/model/Configuration.ts b/api/src/model/Configuration.ts new file mode 100644 index 0000000..edcbbc1 --- /dev/null +++ b/api/src/model/Configuration.ts @@ -0,0 +1,45 @@ +import mongoose, { Schema } from 'mongoose' + +export interface ConfigurationType { + /** + * SecretOrPrivateKey to sign Access Token + * @example "someRandomCryptoString" + */ + ACCESS_TOKEN_SECRET: string + /** + * SecretOrPrivateKey to sign Refresh Token + * @example "someRandomCryptoString" + */ + REFRESH_TOKEN_SECRET: string + /** + * SecretOrPrivateKey to sign Auth Code + * @example "someRandomCryptoString" + */ + AUTH_CODE_SECRET: string + /** + * Secret used to sign the session cookie + * @example "someRandomCryptoString" + */ + SESSION_SECRET: string +} + +const ConfigurationSchema = new Schema({ + ACCESS_TOKEN_SECRET: { + type: String, + required: true + }, + REFRESH_TOKEN_SECRET: { + type: String, + required: true + }, + AUTH_CODE_SECRET: { + type: String, + required: true + }, + SESSION_SECRET: { + type: String, + required: true + } +}) + +export default mongoose.model('Configuration', ConfigurationSchema) diff --git a/api/src/model/Group.ts b/api/src/model/Group.ts index 36b7842..6341825 100644 --- a/api/src/model/Group.ts +++ b/api/src/model/Group.ts @@ -1,5 +1,6 @@ import mongoose, { Schema, model, Document, Model } from 'mongoose' -import User from './User' +import { GroupDetailsResponse } from '../controllers' +import User, { IUser } from './User' const AutoIncrement = require('mongoose-sequence')(mongoose) export interface GroupPayload { @@ -27,8 +28,9 @@ interface IGroupDocument extends GroupPayload, Document { } interface IGroup extends IGroupDocument { - addUser(userObjectId: Schema.Types.ObjectId): Promise - removeUser(userObjectId: Schema.Types.ObjectId): Promise + addUser(user: IUser): Promise + removeUser(user: IUser): Promise + hasUser(user: IUser): boolean } interface IGroupModel extends Model {} @@ -70,28 +72,31 @@ groupSchema.pre('remove', async function () { }) // Instance Methods -groupSchema.method( - 'addUser', - async function (userObjectId: Schema.Types.ObjectId) { - const userIdIndex = this.users.indexOf(userObjectId) - if (userIdIndex === -1) { - this.users.push(userObjectId) - } - this.markModified('users') - return this.save() +groupSchema.method('addUser', async function (user: IUser) { + const userObjectId = user._id + const userIdIndex = this.users.indexOf(userObjectId) + if (userIdIndex === -1) { + this.users.push(userObjectId) + user.addGroup(this._id) } -) -groupSchema.method( - 'removeUser', - async function (userObjectId: Schema.Types.ObjectId) { - const userIdIndex = this.users.indexOf(userObjectId) - if (userIdIndex > -1) { - this.users.splice(userIdIndex, 1) - } - this.markModified('users') - return this.save() + this.markModified('users') + return this.save() +}) +groupSchema.method('removeUser', async function (user: IUser) { + const userObjectId = user._id + const userIdIndex = this.users.indexOf(userObjectId) + if (userIdIndex > -1) { + this.users.splice(userIdIndex, 1) + user.removeGroup(this._id) } -) + this.markModified('users') + return this.save() +}) +groupSchema.method('hasUser', function (user: IUser) { + const userObjectId = user._id + const userIdIndex = this.users.indexOf(userObjectId) + return userIdIndex > -1 +}) export const Group: IGroupModel = model( 'Group', diff --git a/api/src/model/User.ts b/api/src/model/User.ts index b70807d..dd0123b 100644 --- a/api/src/model/User.ts +++ b/api/src/model/User.ts @@ -35,6 +35,7 @@ export interface UserPayload { } interface IUserDocument extends UserPayload, Document { + _id: Schema.Types.ObjectId id: number isAdmin: boolean isActive: boolean @@ -43,7 +44,7 @@ interface IUserDocument extends UserPayload, Document { tokens: [{ [key: string]: string }] } -interface IUser extends IUserDocument { +export interface IUser extends IUserDocument { comparePassword(password: string): boolean addGroup(groupObjectId: Schema.Types.ObjectId): Promise removeGroup(groupObjectId: Schema.Types.ObjectId): Promise diff --git a/api/src/types/system/process.d.ts b/api/src/types/system/process.d.ts index 1e56d75..d7eaf71 100644 --- a/api/src/types/system/process.d.ts +++ b/api/src/types/system/process.d.ts @@ -8,5 +8,6 @@ declare namespace NodeJS { appStreamConfig: import('../').AppStreamConfig logger: import('@sasjs/utils/logger').Logger runTimes: import('../../utils').RunTimeType[] + secrets: import('../../model/Configuration').ConfigurationType } } diff --git a/api/src/utils/connectDB.ts b/api/src/utils/connectDB.ts index b6dd383..9d47607 100644 --- a/api/src/utils/connectDB.ts +++ b/api/src/utils/connectDB.ts @@ -9,7 +9,5 @@ export const connectDB = async () => { } console.log('Connected to DB!') - await seedDB() - - return mongoose.connection + return seedDB() } diff --git a/api/src/utils/generateAccessToken.ts b/api/src/utils/generateAccessToken.ts index a924ab9..2b385b6 100644 --- a/api/src/utils/generateAccessToken.ts +++ b/api/src/utils/generateAccessToken.ts @@ -2,6 +2,6 @@ import jwt from 'jsonwebtoken' import { InfoJWT } from '../types' export const generateAccessToken = (data: InfoJWT) => - jwt.sign(data, process.env.ACCESS_TOKEN_SECRET as string, { + jwt.sign(data, process.secrets.ACCESS_TOKEN_SECRET, { expiresIn: '1day' }) diff --git a/api/src/utils/generateAuthCode.ts b/api/src/utils/generateAuthCode.ts index bfacb79..a5f95f3 100644 --- a/api/src/utils/generateAuthCode.ts +++ b/api/src/utils/generateAuthCode.ts @@ -2,6 +2,6 @@ import jwt from 'jsonwebtoken' import { InfoJWT } from '../types' export const generateAuthCode = (data: InfoJWT) => - jwt.sign(data, process.env.AUTH_CODE_SECRET as string, { + jwt.sign(data, process.secrets.AUTH_CODE_SECRET, { expiresIn: '30s' }) diff --git a/api/src/utils/generateRefreshToken.ts b/api/src/utils/generateRefreshToken.ts index b233572..a8365ff 100644 --- a/api/src/utils/generateRefreshToken.ts +++ b/api/src/utils/generateRefreshToken.ts @@ -2,6 +2,6 @@ import jwt from 'jsonwebtoken' import { InfoJWT } from '../types' export const generateRefreshToken = (data: InfoJWT) => - jwt.sign(data, process.env.REFRESH_TOKEN_SECRET as string, { + jwt.sign(data, process.secrets.REFRESH_TOKEN_SECRET, { expiresIn: '30 days' }) diff --git a/api/src/utils/seedDB.ts b/api/src/utils/seedDB.ts index f63233f..7bff3a6 100644 --- a/api/src/utils/seedDB.ts +++ b/api/src/utils/seedDB.ts @@ -1,6 +1,73 @@ import Client from '../model/Client' +import Group from '../model/Group' import User from '../model/User' +import Configuration, { ConfigurationType } from '../model/Configuration' +import { randomBytes } from 'crypto' + +export const SECRETS: ConfigurationType = { + ACCESS_TOKEN_SECRET: randomBytes(64).toString('hex'), + REFRESH_TOKEN_SECRET: randomBytes(64).toString('hex'), + AUTH_CODE_SECRET: randomBytes(64).toString('hex'), + SESSION_SECRET: randomBytes(64).toString('hex') +} + +export const seedDB = async (): Promise => { + // Checking if client is already in the database + const clientExist = await Client.findOne({ clientId: CLIENT.clientId }) + if (!clientExist) { + const client = new Client(CLIENT) + await client.save() + + console.log(`DB Seed - client created: ${CLIENT.clientId}`) + } + + // Checking if 'AllUsers' Group is already in the database + let groupExist = await Group.findOne({ name: GROUP.name }) + if (!groupExist) { + const group = new Group(GROUP) + groupExist = await group.save() + + console.log(`DB Seed - Group created: ${GROUP.name}`) + } + + // Checking if user is already in the database + let usernameExist = await User.findOne({ username: ADMIN_USER.username }) + if (!usernameExist) { + const user = new User(ADMIN_USER) + usernameExist = await user.save() + + console.log(`DB Seed - admin account created: ${ADMIN_USER.username}`) + } + + if (!groupExist.hasUser(usernameExist)) { + groupExist.addUser(usernameExist) + console.log( + `DB Seed - admin account '${ADMIN_USER.username}' added to Group '${GROUP.name}'` + ) + } + + // checking if configuration is present in the database + let configExist = await Configuration.findOne() + if (!configExist) { + const configuration = new Configuration(SECRETS) + configExist = await configuration.save() + + console.log('DB Seed - configuration added') + } + + return { + ACCESS_TOKEN_SECRET: configExist.ACCESS_TOKEN_SECRET, + REFRESH_TOKEN_SECRET: configExist.REFRESH_TOKEN_SECRET, + AUTH_CODE_SECRET: configExist.AUTH_CODE_SECRET, + SESSION_SECRET: configExist.SESSION_SECRET + } +} + +const GROUP = { + name: 'AllUsers', + description: 'Group contains all users' +} const CLIENT = { clientId: 'clientID1', clientSecret: 'clientSecret' @@ -13,23 +80,3 @@ const ADMIN_USER = { isAdmin: true, isActive: true } - -export const seedDB = async () => { - // Checking if client is already in the database - const clientExist = await Client.findOne({ clientId: CLIENT.clientId }) - if (!clientExist) { - const client = new Client(CLIENT) - await client.save() - - console.log(`DB Seed - client created: ${CLIENT.clientId}`) - } - - // Checking if user is already in the database - const usernameExist = await User.findOne({ username: ADMIN_USER.username }) - if (!usernameExist) { - const user = new User(ADMIN_USER) - await user.save() - - console.log(`DB Seed - admin account created: ${ADMIN_USER.username}`) - } -} diff --git a/api/src/utils/setProcessVariables.ts b/api/src/utils/setProcessVariables.ts index f6f21b0..d1b3991 100644 --- a/api/src/utils/setProcessVariables.ts +++ b/api/src/utils/setProcessVariables.ts @@ -1,16 +1,28 @@ import path from 'path' import { createFolder, getAbsolutePath, getRealPath } from '@sasjs/utils' -import { getDesktopFields, ModeType, RunTimeType } from '.' +import { connectDB, getDesktopFields, ModeType, RunTimeType, SECRETS } from '.' export const setProcessVariables = async () => { + const { MODE, RUN_TIMES } = process.env + + if (MODE === ModeType.Server) { + // NOTE: when exporting app.js as agent for supertest + // it should prevent connecting to the real database + if (process.env.NODE_ENV !== 'test') { + const secrets = await connectDB() + + process.secrets = secrets + } else { + process.secrets = SECRETS + } + } + if (process.env.NODE_ENV === 'test') { process.driveLoc = path.join(process.cwd(), 'sasjs_root') return } - const { MODE, RUN_TIMES } = process.env - process.runTimes = (RUN_TIMES?.split(',') as RunTimeType[]) ?? [] if (MODE === ModeType.Server) { diff --git a/api/src/utils/verifyEnvVariables.ts b/api/src/utils/verifyEnvVariables.ts index 8ab857f..84aa607 100644 --- a/api/src/utils/verifyEnvVariables.ts +++ b/api/src/utils/verifyEnvVariables.ts @@ -78,33 +78,7 @@ const verifyMODE = (): string[] => { } if (process.env.MODE === ModeType.Server) { - const { - ACCESS_TOKEN_SECRET, - REFRESH_TOKEN_SECRET, - AUTH_CODE_SECRET, - SESSION_SECRET, - DB_CONNECT - } = process.env - - if (!ACCESS_TOKEN_SECRET) - errors.push( - `- ACCESS_TOKEN_SECRET is required for PROTOCOL '${ModeType.Server}'` - ) - - if (!REFRESH_TOKEN_SECRET) - errors.push( - `- REFRESH_TOKEN_SECRET is required for PROTOCOL '${ModeType.Server}'` - ) - - if (!AUTH_CODE_SECRET) - errors.push( - `- AUTH_CODE_SECRET is required for PROTOCOL '${ModeType.Server}'` - ) - - if (!SESSION_SECRET) - errors.push( - `- SESSION_SECRET is required for PROTOCOL '${ModeType.Server}'` - ) + const { DB_CONNECT } = process.env if (process.env.NODE_ENV !== 'test') if (!DB_CONNECT) diff --git a/api/src/utils/zipped.ts b/api/src/utils/zipped.ts index f90f79a..44a70fa 100644 --- a/api/src/utils/zipped.ts +++ b/api/src/utils/zipped.ts @@ -28,7 +28,8 @@ export const extractJSONFromZip = async (zipFile: Express.Multer.File) => { for await (const entry of zip) { const fileName = entry.path as string - if (fileName.toUpperCase().endsWith('.JSON') && fileName === fileInZip) { + // grab the first json found in .zip + if (fileName.toUpperCase().endsWith('.JSON')) { fileContent = await entry.buffer() break } else {