Compare commits

...
25 Commits
Author SHA1 Message Date
YuryShkoda e1007e7e75 fix(api): return uid consistently from login and session endpoints
POST /SASLogon/login and GET /SASjsApi/session still returned the
old `id` field, while the rest of the ID->UID migration (#363)
standardized on `uid`. Not functionally broken - Mongoose provides
a built-in `id` virtual by default (_id.toHexString()) that happened
to resolve to the same value as the new `uid` virtual - but it's an
inconsistent public API surface, and relying on that coincidence
wasn't the intent of the migration.

Neither of these files was touched by any of issue-361's own
commits, so this predates the merge rather than being caused by it.

- web.ts: login response and session storage now source from
  user.uid explicitly
- session.ts: SessionResponse dropped its Omit<UserResponse, 'uid'>
  + id override in favor of just extending UserResponse
- verifyTokenInDB.ts: token-refresh path, same fix
- login.tsx / appContext.tsx: updated to read the corrected field

Verified with a real end-to-end request (genuine app boot, real
MongoDB, real CSRF handshake) - not just type-checking - to confirm
the actual HTTP response bodies carry uid, not id.
2026-07-15 10:56:28 +03:00
YuryShkoda 45bff7b0d1 fix: update test fixtures for userId: string after ID->UID merge
Execution.spec.ts, processProgram.spec.ts and code.spec.ts didn't
exist when issue-361 (ID -> UID) branched, so they were written
against the old userId: number shape. Update them to match the
merged-in string-based uid now that main has been merged in.
2026-07-15 09:48:42 +03:00
YuryShkoda 58c66c2ac4 Merge remote-tracking branch 'origin/main' into issue-361 2026-07-14 16:55:46 +03:00
semantic-release-bot 9c3a1086b6 chore(release): 0.39.7 [skip ci]
## [0.39.7](https://github.com/sasjs/server/compare/v0.39.6...v0.39.7) (2026-07-14)

### Bug Fixes

* **jsonwebtoken:** bumped version to avoid vulnerability ([1473925](https://github.com/sasjs/server/commit/1473925896db0e4472c9ef5ba64955527a1be2de))
2026-07-14 13:53:13 +00:00
Yury ShkodaandGitHub 05768890a2 Merge pull request #366 from sasjs/cpr-fix
Fix API scripts
2026-07-14 16:50:34 +03:00
YuryShkoda 386185d1a0 Merge remote-tracking branch 'origin/main' into cpr-fix
# Conflicts:
#	api/package-lock.json
#	api/package.json
#	api/public/swagger.yaml
2026-07-14 16:28:10 +03:00
Yury ShkodaandGitHub 681f0123ad Merge pull request #387 from sasjs/fix/pin-axios-version
chore: pin axios versions
2026-07-14 15:45:40 +03:00
YuryShkoda daa5274fb0 Merge remote-tracking branch 'origin/main' into fix/pin-axios-version 2026-07-14 15:29:26 +03:00
semantic-release-bot 50fa4320cd chore(release): 0.39.6 [skip ci]
## [0.39.6](https://github.com/sasjs/server/compare/v0.39.5...v0.39.6) (2026-07-14)

### Bug Fixes

* **api:** isolate drive.spec.ts's files folder from the real drive ([dec5191](https://github.com/sasjs/server/commit/dec51914911c10b66b43ebe95f77b0af63e2d03e))
* **api:** stop overwriting a failed JS/PY/R session back to completed ([4858245](https://github.com/sasjs/server/commit/4858245372fa19929b6e59c28148f5d004239c9d))
2026-07-14 09:05:49 +00:00
Allan BoweandGitHub 40b9fa8735 Merge pull request #391 from sasjs/issue-390
Issue 390
2026-07-14 10:02:31 +01:00
YuryShkoda fd31fe94ea Merge remote-tracking branch 'origin/main' into issue-390
# Conflicts:
#	api/src/controllers/internal/spec/Execution.spec.ts
2026-07-14 11:58:24 +03:00
YuryShkoda dec5191491 fix(api): isolate drive.spec.ts's files folder from the real drive
drive.spec.ts already isolates itself into a unique, timestamped
tmpFolder for getSasjsRootFolder()/getUploadsFolder(), cleaned up via
afterAll(() => deleteFolder(tmpFolder)). But getFilesFolder() - what
nearly every test in this file actually writes to - resolves through
a separate, unmocked function (getSasjsDriveFolder()/process.driveLoc),
so every run left real files/folders (e.g. 'level1', 'my/path/...')
behind in the shared api/sasjs_root/drive/files, causing later runs
to fail: folder-listing tests saw stale entries, and file-creation
tests got 409 Conflict against files a previous run already created.

Mock getFilesFolder() the same way getUploadsFolder() already is, so
it resolves inside the same isolated tmpFolder and gets cleaned up
by the existing afterAll. Verified by running the suite twice in a
row from a clean slate: both runs pass, and the real sasjs_root/drive
is never touched.
2026-07-14 11:32:01 +03:00
YuryShkoda 4858245372 fix(api): stop overwriting a failed JS/PY/R session back to completed
For JS/PY/R, processProgram sets session.state = failed (with
failureReason) itself when the interpreter process exits non-zero,
without throwing. ExecutionController.executeProgram then
unconditionally set session.state = completed right after
processProgram returned, silently overwriting that - so anything
downstream inspecting session.state (e.g. scheduleSessionDestroy's
expiresAfterMins branch) would see a crashed session mis-reported as
successful.

Guard the assignment so a failed state is never overwritten. No
effect on SAS, which sets state independently via its own spawned
process lifecycle.

Added Execution.spec.ts covering both the failure case (state stays
failed) and the success case (state still becomes completed), to
guard against regressing in either direction.
2026-07-14 11:29:30 +03:00
mulahasanovic 4f75fd290f chore: pin axios versions 2026-04-01 20:55:50 +02:00
Yury Shkoda 66232aefd2 chore(lint): bumped prettier and fixed lint issues 2023-09-21 17:58:47 +03:00
Yury Shkoda bf35791655 chore(lint): fixed Session.ts 2023-09-21 17:52:28 +03:00
Yury Shkoda 2dc11630e4 chore(api): regenerated swagger.yaml 2023-09-21 17:45:52 +03:00
Yury Shkoda 1473925896 fix(jsonwebtoken): bumped version to avoid vulnerability 2023-09-21 17:45:21 +03:00
Yury Shkoda b472f1bd61 chore(scripts): used cpr package to improve coping 2023-09-21 17:44:31 +03:00
sabhas c43afabe28 chore: remove unused code 2023-08-08 15:07:00 +05:00
sabhas 1531e9cd9c chore: addressed comments 2023-08-08 15:01:32 +05:00
sabhas 8cdf605006 chore: fix specs 2023-05-10 17:02:13 +05:00
sabhas 3f815e9beb chore: fix specs 2023-05-10 14:35:35 +05:00
sabhas 6c88eeabd2 chore: specs fixed 2023-05-09 15:21:54 +05:00
sabhas 093fe90589 feat: replace ID with UID
BREAKING CHANGE: remove auto incremental ids from user, group and permissions and add a virtual uid property that returns string value of documents object id
2023-05-09 15:01:56 +05:00
60 changed files with 852 additions and 732 deletions
+15
View File
@@ -1,3 +1,18 @@
## [0.39.7](https://github.com/sasjs/server/compare/v0.39.6...v0.39.7) (2026-07-14)
### Bug Fixes
* **jsonwebtoken:** bumped version to avoid vulnerability ([1473925](https://github.com/sasjs/server/commit/1473925896db0e4472c9ef5ba64955527a1be2de))
## [0.39.6](https://github.com/sasjs/server/compare/v0.39.5...v0.39.6) (2026-07-14)
### Bug Fixes
* **api:** isolate drive.spec.ts's files folder from the real drive ([dec5191](https://github.com/sasjs/server/commit/dec51914911c10b66b43ebe95f77b0af63e2d03e))
* **api:** stop overwriting a failed JS/PY/R session back to completed ([4858245](https://github.com/sasjs/server/commit/4858245372fa19929b6e59c28148f5d004239c9d))
## [0.39.5](https://github.com/sasjs/server/compare/v0.39.4...v0.39.5) (2026-07-14)
+36 -6
View File
@@ -49,7 +49,8 @@
"@types/swagger-ui-express": "^4.1.3",
"@types/unzipper": "^0.10.5",
"adm-zip": "^0.5.9",
"axios": "^1.12.2",
"axios": "1.12.2",
"cpr": "^3.0.1",
"csrf": "^3.1.0",
"dotenv": "^16.0.1",
"http-headers-validation": "^0.0.1",
@@ -58,7 +59,7 @@
"nodejs-file-downloader": "4.10.2",
"nodemon": "^3.0.0",
"pkg": "5.6.0",
"prettier": "^3.0.0",
"prettier": "^3.0.3",
"rimraf": "^3.0.2",
"supertest": "^6.1.3",
"ts-jest": "^29.1.0",
@@ -3588,11 +3589,10 @@
}
},
"node_modules/axios": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
"integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
"version": "1.12.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
"integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
"dev": true,
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.4",
@@ -4490,6 +4490,36 @@
"node": ">= 0.10"
}
},
"node_modules/cpr": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/cpr/-/cpr-3.0.1.tgz",
"integrity": "sha512-Xch4PXQ/KC8lJ+KfJ9JI6eG/nmppLrPPWg5Q+vh65Qr9EjuJEubxh/H/Le1TmCZ7+Xv7iJuNRqapyOFZB+wsxA==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"graceful-fs": "^4.1.5",
"minimist": "^1.2.0",
"mkdirp": "~0.5.1",
"rimraf": "^2.5.4"
},
"bin": {
"cpr": "bin/cpr"
}
},
"node_modules/cpr/node_modules/rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"deprecated": "Rimraf versions prior to v4 are no longer supported",
"dev": true,
"license": "ISC",
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
}
},
"node_modules/create-jest": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz",
+7 -6
View File
@@ -16,10 +16,10 @@
"lint": "npx prettier --check \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"",
"exe": "npm run build && pkg .",
"copy:files": "npm run public:copy && npm run sasjsbuild:copy && npm run sas:copy && npm run web:copy",
"public:copy": "cp -r ./public/ ./build/public/",
"sasjsbuild:copy": "cp -r ./sasjsbuild/ ./build/sasjsbuild/",
"sas:copy": "cp -r ./sas/ ./build/sas/",
"web:copy": "rimraf web && mkdir web && cp -r ../web/build/ ./web/build/",
"public:copy": "cpr ./public/ ./build/public/",
"sasjsbuild:copy": "cpr ./sasjsbuild/ ./build/sasjsbuild/",
"sas:copy": "cpr ./sas/ ./build/sas/",
"web:copy": "rimraf web && mkdir web && cpr ../web/build/ ./web/build/",
"compileSysInit": "ts-node ./scripts/compileSysInit.ts",
"copySASjsCore": "ts-node ./scripts/copySASjsCore.ts",
"downloadMacros": "ts-node ./scripts/downloadMacros.ts"
@@ -84,7 +84,8 @@
"@types/swagger-ui-express": "^4.1.3",
"@types/unzipper": "^0.10.5",
"adm-zip": "^0.5.9",
"axios": "^1.12.2",
"axios": "1.12.2",
"cpr": "^3.0.1",
"csrf": "^3.1.0",
"dotenv": "^16.0.1",
"http-headers-validation": "^0.0.1",
@@ -93,7 +94,7 @@
"nodejs-file-downloader": "4.10.2",
"nodemon": "^3.0.0",
"pkg": "5.6.0",
"prettier": "^3.0.0",
"prettier": "^3.0.3",
"rimraf": "^3.0.2",
"supertest": "^6.1.3",
"ts-jest": "^29.1.0",
+87 -100
View File
@@ -40,8 +40,7 @@ components:
clientId:
type: string
userId:
type: number
format: double
type: string
required:
- clientId
- userId
@@ -315,9 +314,8 @@ components:
additionalProperties: false
UserResponse:
properties:
id:
type: number
format: double
uid:
type: string
username:
type: string
displayName:
@@ -325,7 +323,7 @@ components:
isAdmin:
type: boolean
required:
- id
- uid
- username
- displayName
- isAdmin
@@ -333,32 +331,30 @@ components:
additionalProperties: false
GroupResponse:
properties:
groupId:
type: number
format: double
uid:
type: string
name:
type: string
description:
type: string
required:
- groupId
- uid
- name
- description
type: object
additionalProperties: false
UserDetailsResponse:
properties:
id:
type: number
format: double
displayName:
uid:
type: string
username:
type: string
isActive:
type: boolean
displayName:
type: string
isAdmin:
type: boolean
isActive:
type: boolean
autoExec:
type: string
groups:
@@ -366,11 +362,11 @@ components:
$ref: '#/components/schemas/GroupResponse'
type: array
required:
- id
- displayName
- uid
- username
- isActive
- displayName
- isAdmin
- isActive
type: object
additionalProperties: false
UserPayload:
@@ -406,9 +402,8 @@ components:
additionalProperties: false
GroupDetailsResponse:
properties:
groupId:
type: number
format: double
uid:
type: string
name:
type: string
description:
@@ -420,7 +415,7 @@ components:
$ref: '#/components/schemas/UserResponse'
type: array
required:
- groupId
- uid
- name
- description
- isActive
@@ -489,9 +484,8 @@ components:
additionalProperties: false
PermissionDetailsResponse:
properties:
permissionId:
type: number
format: double
uid:
type: string
path:
type: string
type:
@@ -503,7 +497,7 @@ components:
group:
$ref: '#/components/schemas/GroupDetailsResponse'
required:
- permissionId
- uid
- path
- type
- setting
@@ -542,10 +536,8 @@ components:
description: 'Indicates the type of principal'
example: user
principalId:
type: number
format: double
type: string
description: 'The id of user or group to which a rule is assigned.'
example: 123
required:
- path
- type
@@ -564,25 +556,37 @@ components:
- setting
type: object
additionalProperties: false
SessionResponse:
Pick_UserResponse.Exclude_keyofUserResponse.uid__:
properties:
id:
type: number
format: double
username:
type: string
displayName:
type: string
isAdmin:
type: boolean
needsToUpdatePassword:
type: boolean
required:
- id
- username
- displayName
- isAdmin
- needsToUpdatePassword
type: object
description: 'From T, pick a set of properties whose keys are in the union K'
SessionResponse:
properties:
username:
type: string
displayName:
type: string
isAdmin:
type: boolean
id:
type: string
needsToUpdatePassword:
type: boolean
required:
- username
- displayName
- isAdmin
- id
type: object
additionalProperties: false
SessionState:
@@ -1278,7 +1282,7 @@ paths:
type: array
examples:
'Example 1':
value: [{id: 123, username: johnusername, displayName: John, isAdmin: false}, {id: 456, username: starkusername, displayName: Stark, isAdmin: true}]
value: [{uid: userIdString, username: johnusername, displayName: John, isAdmin: false}, {uid: anotherUserIdString, username: starkusername, displayName: Stark, isAdmin: true}]
summary: 'Get list of all users (username, displayname). All users can request this.'
tags:
- User
@@ -1297,7 +1301,7 @@ paths:
$ref: '#/components/schemas/UserDetailsResponse'
examples:
'Example 1':
value: {id: 1234, displayName: 'John Snow', username: johnSnow01, isAdmin: false, isActive: true}
value: {uid: userIdString, 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
@@ -1348,7 +1352,7 @@ paths:
$ref: '#/components/schemas/UserDetailsResponse'
examples:
'Example 1':
value: {id: 1234, displayName: 'John Snow', username: johnSnow01, isAdmin: false, isActive: true}
value: {uid: userIdString, 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
@@ -1399,7 +1403,7 @@ paths:
password:
type: string
type: object
'/SASjsApi/user/{userId}':
'/SASjsApi/user/{uid}':
get:
operationId: GetUser
responses:
@@ -1418,14 +1422,12 @@ paths:
bearerAuth: []
parameters:
-
description: 'The user''s identifier'
in: path
name: userId
name: uid
required: true
schema:
format: double
type: number
example: 1234
type: string
'/SASjsApi/user/{userId}':
patch:
operationId: UpdateUser
responses:
@@ -1437,7 +1439,7 @@ paths:
$ref: '#/components/schemas/UserDetailsResponse'
examples:
'Example 1':
value: {id: 1234, displayName: 'John Snow', username: johnSnow01, isAdmin: false, isActive: true}
value: {uid: userIdString, 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
@@ -1451,8 +1453,7 @@ paths:
name: userId
required: true
schema:
format: double
type: number
type: string
example: '1234'
requestBody:
required: true
@@ -1478,8 +1479,7 @@ paths:
name: userId
required: true
schema:
format: double
type: number
type: string
example: 1234
requestBody:
required: true
@@ -1504,7 +1504,7 @@ paths:
type: array
examples:
'Example 1':
value: [{groupId: 123, name: DCGroup, description: 'This group represents Data Controller Users'}]
value: [{uid: groupIdString, 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
@@ -1523,7 +1523,7 @@ paths:
$ref: '#/components/schemas/GroupDetailsResponse'
examples:
'Example 1':
value: {groupId: 123, name: DCGroup, description: 'This group represents Data Controller Users', isActive: true, users: []}
value: {uid: groupIdString, name: DCGroup, description: 'This group represents Data Controller Users', isActive: true, users: []}
summary: 'Create a new group. Admin only.'
tags:
- Group
@@ -1539,7 +1539,7 @@ paths:
$ref: '#/components/schemas/GroupPayload'
'/SASjsApi/group/by/groupname/{name}':
get:
operationId: GetGroupByGroupName
operationId: GetGroupByName
responses:
'200':
description: Ok
@@ -1561,7 +1561,7 @@ paths:
required: true
schema:
type: string
'/SASjsApi/group/{groupId}':
'/SASjsApi/group/{uid}':
get:
operationId: GetGroup
responses:
@@ -1581,12 +1581,11 @@ paths:
-
description: 'The group''s identifier'
in: path
name: groupId
name: uid
required: true
schema:
format: double
type: number
example: 1234
type: string
example: 12ByteString
delete:
operationId: DeleteGroup
responses:
@@ -1608,13 +1607,12 @@ paths:
-
description: 'The group''s identifier'
in: path
name: groupId
name: uid
required: true
schema:
format: double
type: number
example: 1234
'/SASjsApi/group/{groupId}/{userId}':
type: string
example: 12ByteString
'/SASjsApi/group/{groupUid}/{userUid}':
post:
operationId: AddUserToGroup
responses:
@@ -1626,7 +1624,7 @@ paths:
$ref: '#/components/schemas/GroupDetailsResponse'
examples:
'Example 1':
value: {groupId: 123, name: DCGroup, description: 'This group represents Data Controller Users', isActive: true, users: []}
value: {uid: groupIdString, name: DCGroup, description: 'This group represents Data Controller Users', isActive: true, users: []}
summary: 'Add a user to a group. Admin task only.'
tags:
- Group
@@ -1637,21 +1635,18 @@ paths:
-
description: 'The group''s identifier'
in: path
name: groupId
name: groupUid
required: true
schema:
format: double
type: number
example: '1234'
type: string
example: 12ByteString
-
description: 'The user''s identifier'
in: path
name: userId
name: userUid
required: true
schema:
format: double
type: number
example: '6789'
type: string
delete:
operationId: RemoveUserFromGroup
responses:
@@ -1663,8 +1658,8 @@ paths:
$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.'
value: {uid: groupIdString, name: DCGroup, description: 'This group represents Data Controller Users', isActive: true, users: []}
summary: 'Remove a user from a group. Admin task only.'
tags:
- Group
security:
@@ -1674,21 +1669,19 @@ paths:
-
description: 'The group''s identifier'
in: path
name: groupId
name: groupUid
required: true
schema:
format: double
type: number
example: '1234'
type: string
example: 12ByteString
-
description: 'The user''s identifier'
in: path
name: userId
name: userUid
required: true
schema:
format: double
type: number
example: '6789'
type: string
example: 12ByteString
/SASjsApi/info:
get:
operationId: Info
@@ -1739,7 +1732,7 @@ paths:
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: []}}]
value: [{uid: permissionId1String, path: /SASjsApi/code/execute, type: Route, setting: Grant, user: {uid: user1-id, username: johnSnow01, displayName: 'John Snow', isAdmin: false}}, {uid: permissionId2String, path: /SASjsApi/code/execute, type: Route, setting: Grant, group: {uid: group1-id, 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:
@@ -1759,7 +1752,7 @@ paths:
$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}}
value: {uid: permissionIdString, path: /SASjsApi/code/execute, type: Route, setting: Grant, user: {uid: userIdString, username: johnSnow01, displayName: 'John Snow', isAdmin: false}}
summary: 'Create a new permission. Admin only.'
tags:
- Permission
@@ -1773,7 +1766,7 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/RegisterPermissionPayload'
'/SASjsApi/permission/{permissionId}':
'/SASjsApi/permission/{uid}':
patch:
operationId: UpdatePermission
responses:
@@ -1785,7 +1778,7 @@ paths:
$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}}
value: {uid: permissionIdString, path: /SASjsApi/code/execute, type: Route, setting: Grant, user: {uid: userIdString, username: johnSnow01, displayName: 'John Snow', isAdmin: false}}
summary: 'Update permission setting. Admin only'
tags:
- Permission
@@ -1794,14 +1787,11 @@ paths:
bearerAuth: []
parameters:
-
description: 'The permission''s identifier'
in: path
name: permissionId
name: uid
required: true
schema:
format: double
type: number
example: 1234
type: string
requestBody:
required: true
content:
@@ -1821,14 +1811,11 @@ paths:
bearerAuth: []
parameters:
-
description: 'The user''s identifier'
in: path
name: permissionId
name: uid
required: true
schema:
format: double
type: number
example: 1234
type: string
/SASjsApi/session:
get:
operationId: Session
@@ -1841,7 +1828,7 @@ paths:
$ref: '#/components/schemas/SessionResponse'
examples:
'Example 1':
value: {id: 123, username: johnusername, displayName: John, isAdmin: false}
value: {id: userIdString, username: johnusername, displayName: John, isAdmin: false, needsToUpdatePassword: false}
summary: 'Get session info (username).'
tags:
- Session
@@ -2012,7 +1999,7 @@ paths:
application/json:
schema:
properties:
user: {properties: {needsToUpdatePassword: {type: boolean}, isAdmin: {type: boolean}, displayName: {type: string}, username: {type: string}, id: {type: number, format: double}}, required: [needsToUpdatePassword, isAdmin, displayName, username, id], type: object}
user: {properties: {needsToUpdatePassword: {type: boolean}, isAdmin: {type: boolean}, displayName: {type: string}, username: {type: string}, id: {}}, required: [needsToUpdatePassword, isAdmin, displayName, username, id], type: object}
loggedIn: {type: boolean}
required:
- user
+3 -3
View File
@@ -27,14 +27,14 @@ import User from '../model/User'
@Tags('Auth')
export class AuthController {
static authCodes: { [key: string]: { [key: string]: string } } = {}
static saveCode = (userId: number, clientId: string, code: string) => {
static saveCode = (userId: string, clientId: string, code: string) => {
if (AuthController.authCodes[userId])
return (AuthController.authCodes[userId][clientId] = code)
AuthController.authCodes[userId] = { [clientId]: code }
return AuthController.authCodes[userId][clientId]
}
static deleteCode = (userId: number, clientId: string) =>
static deleteCode = (userId: string, clientId: string) =>
delete AuthController.authCodes[userId][clientId]
/**
@@ -159,7 +159,7 @@ const updatePassword = async (
) => {
const { currentPassword, newPassword } = data
const userId = req.user?.userId
const dbUser = await User.findOne({ id: userId })
const dbUser = await User.findOne({ _id: userId })
if (!dbUser)
throw {
+60 -62
View File
@@ -12,28 +12,29 @@ import {
import Group, { GroupPayload, PUBLIC_GROUP_NAME } from '../model/Group'
import User from '../model/User'
import { AuthProviderType } from '../utils'
import { UserResponse } from './user'
import { GetUserBy, UserResponse } from './user'
export interface GroupResponse {
groupId: number
uid: string
name: string
description: string
}
export interface GroupDetailsResponse {
groupId: number
name: string
description: string
export interface GroupDetailsResponse extends GroupResponse {
isActive: boolean
users: UserResponse[]
}
interface GetGroupBy {
groupId?: number
_id?: string
name?: string
}
enum GroupAction {
AddUser = 'addUser',
RemoveUser = 'removeUser'
}
@Security('bearerAuth')
@Route('SASjsApi/group')
@Tags('Group')
@@ -44,7 +45,7 @@ export class GroupController {
*/
@Example<GroupResponse[]>([
{
groupId: 123,
uid: 'groupIdString',
name: 'DCGroup',
description: 'This group represents Data Controller Users'
}
@@ -59,7 +60,7 @@ export class GroupController {
*
*/
@Example<GroupDetailsResponse>({
groupId: 123,
uid: 'groupIdString',
name: 'DCGroup',
description: 'This group represents Data Controller Users',
isActive: true,
@@ -78,7 +79,7 @@ export class GroupController {
* @example dcgroup
*/
@Get('by/groupname/{name}')
public async getGroupByGroupName(
public async getGroupByName(
@Path() name: string
): Promise<GroupDetailsResponse> {
return getGroup({ name })
@@ -86,68 +87,66 @@ export class GroupController {
/**
* @summary Get list of members of a group (userName). All users can request this.
* @param groupId The group's identifier
* @example groupId 1234
* @param uid The group's identifier
* @example uid "12ByteString"
*/
@Get('{groupId}')
public async getGroup(
@Path() groupId: number
): Promise<GroupDetailsResponse> {
return getGroup({ groupId })
@Get('{uid}')
public async getGroup(@Path() uid: string): Promise<GroupDetailsResponse> {
return getGroup({ _id: uid })
}
/**
* @summary Add a user to a group. Admin task only.
* @param groupId The group's identifier
* @example groupId "1234"
* @param userId The user's identifier
* @example userId "6789"
* @param groupUid The group's identifier
* @example groupUid "12ByteString"
* @param userUid The user's identifier
* @example userId "12ByteString"
*/
@Example<GroupDetailsResponse>({
groupId: 123,
uid: 'groupIdString',
name: 'DCGroup',
description: 'This group represents Data Controller Users',
isActive: true,
users: []
})
@Post('{groupId}/{userId}')
@Post('{groupUid}/{userUid}')
public async addUserToGroup(
@Path() groupId: number,
@Path() userId: number
@Path() groupUid: string,
@Path() userUid: string
): Promise<GroupDetailsResponse> {
return addUserToGroup(groupId, userId)
return addUserToGroup(groupUid, userUid)
}
/**
* @summary Remove a user to a group. Admin task only.
* @param groupId The group's identifier
* @example groupId "1234"
* @param userId The user's identifier
* @example userId "6789"
* @summary Remove a user from a group. Admin task only.
* @param groupUid The group's identifier
* @example groupUid "12ByteString"
* @param userUid The user's identifier
* @example userUid "12ByteString"
*/
@Example<GroupDetailsResponse>({
groupId: 123,
uid: 'groupIdString',
name: 'DCGroup',
description: 'This group represents Data Controller Users',
isActive: true,
users: []
})
@Delete('{groupId}/{userId}')
@Delete('{groupUid}/{userUid}')
public async removeUserFromGroup(
@Path() groupId: number,
@Path() userId: number
@Path() groupUid: string,
@Path() userUid: string
): Promise<GroupDetailsResponse> {
return removeUserFromGroup(groupId, userId)
return removeUserFromGroup(groupUid, userUid)
}
/**
* @summary Delete a group. Admin task only.
* @param groupId The group's identifier
* @example groupId 1234
* @param uid The group's identifier
* @example uid "12ByteString"
*/
@Delete('{groupId}')
public async deleteGroup(@Path() groupId: number) {
const group = await Group.findOne({ groupId })
@Delete('{uid}')
public async deleteGroup(@Path() uid: string) {
const group = await Group.findOne({ _id: uid })
if (!group)
throw {
code: 404,
@@ -160,9 +159,7 @@ export class GroupController {
}
const getAllGroups = async (): Promise<GroupResponse[]> =>
await Group.find({})
.select({ _id: 0, groupId: 1, name: 1, description: 1 })
.exec()
await Group.find({}).select('uid name description').exec()
const createGroup = async ({
name,
@@ -187,7 +184,7 @@ const createGroup = async ({
const savedGroup = await group.save()
return {
groupId: savedGroup.groupId,
uid: savedGroup.uid,
name: savedGroup.name,
description: savedGroup.description,
isActive: savedGroup.isActive,
@@ -198,11 +195,12 @@ const createGroup = async ({
const getGroup = async (findBy: GetGroupBy): Promise<GroupDetailsResponse> => {
const group = (await Group.findOne(
findBy,
'groupId name description isActive users -_id'
'uid name description isActive users'
).populate(
'users',
'id username displayName isAdmin -_id'
'uid username displayName isAdmin'
)) as unknown as GroupDetailsResponse
if (!group)
throw {
code: 404,
@@ -211,7 +209,7 @@ const getGroup = async (findBy: GetGroupBy): Promise<GroupDetailsResponse> => {
}
return {
groupId: group.groupId,
uid: group.uid,
name: group.name,
description: group.description,
isActive: group.isActive,
@@ -220,23 +218,23 @@ const getGroup = async (findBy: GetGroupBy): Promise<GroupDetailsResponse> => {
}
const addUserToGroup = async (
groupId: number,
userId: number
groupUid: string,
userUid: string
): Promise<GroupDetailsResponse> =>
updateUsersListInGroup(groupId, userId, 'addUser')
updateUsersListInGroup(groupUid, userUid, GroupAction.AddUser)
const removeUserFromGroup = async (
groupId: number,
userId: number
groupUid: string,
userUid: string
): Promise<GroupDetailsResponse> =>
updateUsersListInGroup(groupId, userId, 'removeUser')
updateUsersListInGroup(groupUid, userUid, GroupAction.RemoveUser)
const updateUsersListInGroup = async (
groupId: number,
userId: number,
action: 'addUser' | 'removeUser'
groupUid: string,
userUid: string,
action: GroupAction
): Promise<GroupDetailsResponse> => {
const group = await Group.findOne({ groupId })
const group = await Group.findOne({ _id: groupUid })
if (!group)
throw {
code: 404,
@@ -258,7 +256,7 @@ const updateUsersListInGroup = async (
message: `Can't add/remove user to group created by external auth provider.`
}
const user = await User.findOne({ id: userId })
const user = await User.findOne({ _id: userUid })
if (!user)
throw {
code: 404,
@@ -274,7 +272,7 @@ const updateUsersListInGroup = async (
}
const updatedGroup =
action === 'addUser'
action === GroupAction.AddUser
? await group.addUser(user)
: await group.removeUser(user)
@@ -286,7 +284,7 @@ const updateUsersListInGroup = async (
}
return {
groupId: updatedGroup.groupId,
uid: updatedGroup.uid,
name: updatedGroup.name,
description: updatedGroup.description,
isActive: updatedGroup.isActive,
+10 -1
View File
@@ -144,7 +144,16 @@ export class ExecutionController {
: ''
// it should be deleted by scheduleSessionDestroy
session.state = SessionState.completed
//
// Guarded: for JS/PY/R, processProgram sets state to `failed` itself
// (without throwing) when the interpreter process exits non-zero - if
// we unconditionally set `completed` here we'd silently overwrite that,
// and anything downstream inspecting session.state (e.g.
// scheduleSessionDestroy's expiresAfterMins branch in Session.ts) would
// see a crashed session mis-reported as successful.
if ((session.state as SessionState) !== SessionState.failed) {
session.state = SessionState.completed
}
const resultParts = []
@@ -8,13 +8,13 @@ import { RunTimeType } from '../../../utils'
const preProgramVariables: PreProgramVars = {
username: 'testUser',
userId: 1,
userId: '1',
displayName: 'Test User',
serverUrl: 'http://localhost:5000',
httpHeaders: []
}
describe('ExecutionController.executeProgram (SAS failure path)', () => {
describe('ExecutionController.executeProgram', () => {
let session: Session
beforeEach(() => {
@@ -33,33 +33,85 @@ describe('ExecutionController.executeProgram (SAS failure path)', () => {
await deleteFolder(session.path)
})
it('throws a SessionExecutionError carrying the complete log when the session fails', async () => {
const logPath = path.join(session.path, 'log.log')
const logContent =
'NOTE: SAS session\nERROR: SAS session terminated. See log for details.\n'
// Regression coverage: for JS/PY/R, processProgram sets session.state to
// `failed` itself (without throwing) on a non-zero interpreter exit.
// ExecutionController.executeProgram used to unconditionally overwrite
// that back to `completed` right after processProgram returned, silently
// losing the failure for anything downstream that inspects session.state.
describe('JS/PY/R failure path', () => {
it('does not overwrite a failed session state back to completed', async () => {
// mirrors processProgram's real JS/PY/R branch on a non-zero exit:
// it sets state/failureReason itself and resolves - it does not throw
jest
.spyOn(ProcessProgramModule, 'processProgram')
.mockImplementation(async () => {
session.state = SessionState.failed
session.failureReason = 'Error: process exited with code 1'
})
await createFile(logPath, logContent)
const controller = new ExecutionController()
jest
.spyOn(ProcessProgramModule, 'processProgram')
.mockImplementation(async () => {
throw new Error('ERROR: SAS session terminated. See log for details.')
await controller.executeProgram({
program: 'throw new Error("boom")',
preProgramVariables,
vars: {},
session,
runTime: RunTimeType.JS
})
const controller = new ExecutionController()
const resultPromise = controller.executeProgram({
program: '%abort;',
preProgramVariables,
vars: {},
session,
runTime: RunTimeType.SAS
expect(session.state).toBe(SessionState.failed)
})
await expect(resultPromise).rejects.toBeInstanceOf(SessionExecutionError)
await expect(resultPromise).rejects.toMatchObject({
log: logContent,
message: expect.stringContaining('SAS session terminated')
it('still marks a genuinely successful session as completed', async () => {
jest
.spyOn(ProcessProgramModule, 'processProgram')
.mockImplementation(async () => {
session.state = SessionState.completed
})
const controller = new ExecutionController()
await controller.executeProgram({
program: 'console.log("hello")',
preProgramVariables,
vars: {},
session,
runTime: RunTimeType.JS
})
expect(session.state).toBe(SessionState.completed)
})
})
describe('SAS failure path', () => {
it('throws a SessionExecutionError carrying the complete log when the session fails', async () => {
const logPath = path.join(session.path, 'log.log')
const logContent =
'NOTE: SAS session\nERROR: SAS session terminated. See log for details.\n'
await createFile(logPath, logContent)
jest
.spyOn(ProcessProgramModule, 'processProgram')
.mockImplementation(async () => {
throw new Error('ERROR: SAS session terminated. See log for details.')
})
const controller = new ExecutionController()
const resultPromise = controller.executeProgram({
program: '%abort;',
preProgramVariables,
vars: {},
session,
runTime: RunTimeType.SAS
})
await expect(resultPromise).rejects.toBeInstanceOf(SessionExecutionError)
await expect(resultPromise).rejects.toMatchObject({
log: logContent,
message: expect.stringContaining('SAS session terminated')
})
})
})
})
@@ -11,7 +11,7 @@ import {
const preProgramVariables: PreProgramVars = {
username: 'testUser',
userId: 1,
userId: '1',
displayName: 'Test User',
serverUrl: 'http://localhost:5000',
httpHeaders: []
+34 -40
View File
@@ -56,9 +56,9 @@ interface RegisterPermissionPayload {
principalType: PrincipalType
/**
* The id of user or group to which a rule is assigned.
* @example 123
* @example 'groupIdString'
*/
principalId: number
principalId: string
}
interface UpdatePermissionPayload {
@@ -70,7 +70,7 @@ interface UpdatePermissionPayload {
}
export interface PermissionDetailsResponse {
permissionId: number
uid: string
path: string
type: string
setting: string
@@ -91,24 +91,24 @@ export class PermissionController {
*/
@Example<PermissionDetailsResponse[]>([
{
permissionId: 123,
uid: 'permissionId1String',
path: '/SASjsApi/code/execute',
type: 'Route',
setting: 'Grant',
user: {
id: 1,
uid: 'user1-id',
username: 'johnSnow01',
displayName: 'John Snow',
isAdmin: false
}
},
{
permissionId: 124,
uid: 'permissionId2String',
path: '/SASjsApi/code/execute',
type: 'Route',
setting: 'Grant',
group: {
groupId: 1,
uid: 'group1-id',
name: 'DCGroup',
description: 'This group represents Data Controller Users',
isActive: true,
@@ -128,12 +128,12 @@ export class PermissionController {
*
*/
@Example<PermissionDetailsResponse>({
permissionId: 123,
uid: 'permissionIdString',
path: '/SASjsApi/code/execute',
type: 'Route',
setting: 'Grant',
user: {
id: 1,
uid: 'userIdString',
username: 'johnSnow01',
displayName: 'John Snow',
isAdmin: false
@@ -149,36 +149,36 @@ export class PermissionController {
/**
* @summary Update permission setting. Admin only
* @param permissionId The permission's identifier
* @example permissionId 1234
* @example permissionId "permissionIdString"
*/
@Example<PermissionDetailsResponse>({
permissionId: 123,
uid: 'permissionIdString',
path: '/SASjsApi/code/execute',
type: 'Route',
setting: 'Grant',
user: {
id: 1,
uid: 'userIdString',
username: 'johnSnow01',
displayName: 'John Snow',
isAdmin: false
}
})
@Patch('{permissionId}')
@Patch('{uid}')
public async updatePermission(
@Path() permissionId: number,
@Path() uid: string,
@Body() body: UpdatePermissionPayload
): Promise<PermissionDetailsResponse> {
return updatePermission(permissionId, body)
return updatePermission(uid, body)
}
/**
* @summary Delete a permission. Admin only.
* @param permissionId The user's identifier
* @example permissionId 1234
* @example permissionId "permissionIdString"
*/
@Delete('{permissionId}')
public async deletePermission(@Path() permissionId: number) {
return deletePermission(permissionId)
@Delete('{uid}')
public async deletePermission(@Path() uid: string) {
return deletePermission(uid)
}
}
@@ -191,7 +191,7 @@ const getAllPermissions = async (
else {
const permissions: PermissionDetailsResponse[] = []
const dbUser = await User.findOne({ id: user?.userId })
const dbUser = await User.findOne({ _id: user?.userId })
if (!dbUser)
throw {
code: 404,
@@ -227,7 +227,7 @@ const createPermission = async ({
switch (principalType) {
case PrincipalType.user: {
const userInDB = await User.findOne({ id: principalId })
const userInDB = await User.findOne({ _id: principalId })
if (!userInDB)
throw {
code: 404,
@@ -259,7 +259,7 @@ const createPermission = async ({
permission.user = userInDB._id
user = {
id: userInDB.id,
uid: userInDB.uid,
username: userInDB.username,
displayName: userInDB.displayName,
isAdmin: userInDB.isAdmin
@@ -267,7 +267,7 @@ const createPermission = async ({
break
}
case PrincipalType.group: {
const groupInDB = await Group.findOne({ groupId: principalId })
const groupInDB = await Group.findOne({ _id: principalId })
if (!groupInDB)
throw {
code: 404,
@@ -291,13 +291,13 @@ const createPermission = async ({
permission.group = groupInDB._id
group = {
groupId: groupInDB.groupId,
uid: groupInDB.uid,
name: groupInDB.name,
description: groupInDB.description,
isActive: groupInDB.isActive,
users: groupInDB.populate({
path: 'users',
select: 'id username displayName isAdmin -_id',
select: 'uid username displayName isAdmin -_id',
options: { limit: 15 }
}) as unknown as UserResponse[]
}
@@ -314,7 +314,7 @@ const createPermission = async ({
const savedPermission = await permission.save()
return {
permissionId: savedPermission.permissionId,
uid: savedPermission.uid,
path: savedPermission.path,
type: savedPermission.type,
setting: savedPermission.setting,
@@ -324,27 +324,21 @@ const createPermission = async ({
}
const updatePermission = async (
id: number,
uid: string,
data: UpdatePermissionPayload
): Promise<PermissionDetailsResponse> => {
const { setting } = data
const updatedPermission = (await Permission.findOneAndUpdate(
{ permissionId: id },
{ _id: uid },
{ setting },
{ new: true }
)
.select({
_id: 0,
permissionId: 1,
path: 1,
type: 1,
setting: 1
})
.populate({ path: 'user', select: 'id username displayName isAdmin -_id' })
.select('uid path type setting')
.populate({ path: 'user', select: 'uid username displayName isAdmin' })
.populate({
path: 'group',
select: 'groupId name description -_id'
select: 'groupId name description'
})) as unknown as PermissionDetailsResponse
if (!updatedPermission)
throw {
@@ -356,13 +350,13 @@ const updatePermission = async (
return updatedPermission
}
const deletePermission = async (id: number) => {
const permission = await Permission.findOne({ permissionId: id })
const deletePermission = async (uid: string) => {
const permission = await Permission.findOne({ _id: uid })
if (!permission)
throw {
code: 404,
status: 'Not Found',
message: 'Permission not found.'
}
await Permission.deleteOne({ permissionId: id })
await Permission.deleteOne({ _id: uid })
}
+6 -5
View File
@@ -5,7 +5,7 @@ import { getSessionController } from './internal'
import { SessionState } from '../types'
interface SessionResponse extends UserResponse {
needsToUpdatePassword: boolean
needsToUpdatePassword?: boolean
}
@Security('bearerAuth')
@@ -16,11 +16,12 @@ export class SessionController {
* @summary Get session info (username).
*
*/
@Example<UserResponse>({
id: 123,
@Example<SessionResponse>({
uid: 'userIdString',
username: 'johnusername',
displayName: 'John',
isAdmin: false
isAdmin: false,
needsToUpdatePassword: false
})
@Get('/')
public async session(
@@ -43,7 +44,7 @@ export class SessionController {
}
const session = (req: express.Request) => ({
id: req.user!.userId,
uid: req.user!.userId,
username: req.user!.username,
displayName: req.user!.displayName,
isAdmin: req.user!.isAdmin,
+31 -37
View File
@@ -26,18 +26,14 @@ import {
import { GroupController, GroupResponse } from './group'
export interface UserResponse {
id: number
uid: string
username: string
displayName: string
isAdmin: boolean
}
export interface UserDetailsResponse {
id: number
displayName: string
username: string
export interface UserDetailsResponse extends UserResponse {
isActive: boolean
isAdmin: boolean
autoExec?: string
groups?: GroupResponse[]
}
@@ -52,13 +48,13 @@ export class UserController {
*/
@Example<UserResponse[]>([
{
id: 123,
uid: 'userIdString',
username: 'johnusername',
displayName: 'John',
isAdmin: false
},
{
id: 456,
uid: 'anotherUserIdString',
username: 'starkusername',
displayName: 'Stark',
isAdmin: true
@@ -74,7 +70,7 @@ export class UserController {
*
*/
@Example<UserDetailsResponse>({
id: 1234,
uid: 'userIdString',
displayName: 'John Snow',
username: 'johnSnow01',
isAdmin: false,
@@ -111,20 +107,20 @@ export class UserController {
* Only Admin or user itself will get user autoExec code.
* @summary Get user properties - such as group memberships, userName, displayName.
* @param userId The user's identifier
* @example userId 1234
* @example userId "userIdString"
*/
@Get('{userId}')
@Get('{uid}')
public async getUser(
@Request() req: express.Request,
@Path() userId: number
@Path() uid: string
): Promise<UserDetailsResponse> {
const { MODE } = process.env
if (MODE === ModeType.Desktop) return getDesktopAutoExec()
const { user } = req
const getAutoExec = user!.isAdmin || user!.userId == userId
return getUser({ id: userId }, getAutoExec)
const getAutoExec = user!.isAdmin || user!.userId === uid
return getUser({ _id: uid }, getAutoExec)
}
/**
@@ -133,7 +129,7 @@ export class UserController {
* @example username "johnSnow01"
*/
@Example<UserDetailsResponse>({
id: 1234,
uid: 'userIdString',
displayName: 'John Snow',
username: 'johnSnow01',
isAdmin: false,
@@ -158,7 +154,7 @@ export class UserController {
* @example userId "1234"
*/
@Example<UserDetailsResponse>({
id: 1234,
uid: 'userIdString',
displayName: 'John Snow',
username: 'johnSnow01',
isAdmin: false,
@@ -166,7 +162,7 @@ export class UserController {
})
@Patch('{userId}')
public async updateUser(
@Path() userId: number,
@Path() userId: string,
@Body() body: UserPayload
): Promise<UserDetailsResponse> {
const { MODE } = process.env
@@ -174,7 +170,7 @@ export class UserController {
if (MODE === ModeType.Desktop)
return updateDesktopAutoExec(body.autoExec ?? '')
return updateUser({ id: userId }, body)
return updateUser({ _id: userId }, body)
}
/**
@@ -198,18 +194,16 @@ export class UserController {
*/
@Delete('{userId}')
public async deleteUser(
@Path() userId: number,
@Path() userId: string,
@Body() body: { password?: string },
@Query() @Hidden() isAdmin: boolean = false
) {
return deleteUser({ id: userId }, isAdmin, body)
return deleteUser({ _id: userId }, isAdmin, body)
}
}
const getAllUsers = async (): Promise<UserResponse[]> =>
await User.find({})
.select({ _id: 0, id: 1, username: 1, displayName: 1, isAdmin: 1 })
.exec()
await User.find({}).select('uid username displayName isAdmin').exec()
const createUser = async (data: UserPayload): Promise<UserDetailsResponse> => {
const { displayName, username, password, isAdmin, isActive, autoExec } = data
@@ -239,15 +233,15 @@ const createUser = async (data: UserPayload): Promise<UserDetailsResponse> => {
const groupController = new GroupController()
const allUsersGroup = await groupController
.getGroupByGroupName(ALL_USERS_GROUP.name)
.getGroupByName(ALL_USERS_GROUP.name)
.catch(() => {})
if (allUsersGroup) {
await groupController.addUserToGroup(allUsersGroup.groupId, savedUser.id)
await groupController.addUserToGroup(allUsersGroup.uid, savedUser.uid)
}
return {
id: savedUser.id,
uid: savedUser.uid,
displayName: savedUser.displayName,
username: savedUser.username,
isActive: savedUser.isActive,
@@ -256,8 +250,8 @@ const createUser = async (data: UserPayload): Promise<UserDetailsResponse> => {
}
}
interface GetUserBy {
id?: number
export interface GetUserBy {
_id?: string
username?: string
}
@@ -267,10 +261,10 @@ const getUser = async (
): Promise<UserDetailsResponse> => {
const user = (await User.findOne(
findBy,
`id displayName username isActive isAdmin autoExec -_id`
`uid displayName username isActive isAdmin autoExec`
).populate(
'groups',
'groupId name description -_id'
'uid name description'
)) as unknown as UserDetailsResponse
if (!user)
@@ -280,7 +274,7 @@ const getUser = async (
}
return {
id: user.id,
uid: user.uid,
displayName: user.displayName,
username: user.username,
isActive: user.isActive,
@@ -293,7 +287,7 @@ const getUser = async (
const getDesktopAutoExec = async () => {
return {
...desktopUser,
id: desktopUser.userId,
uid: desktopUser.userId,
autoExec: await getUserAutoExec()
}
}
@@ -329,8 +323,8 @@ const updateUser = async (
const usernameExist = await User.findOne({ username })
if (usernameExist) {
if (
(findBy.id && usernameExist.id != findBy.id) ||
(findBy.username && usernameExist.username != findBy.username)
(findBy._id && usernameExist.uid !== findBy._id) ||
(findBy.username && usernameExist.username !== findBy.username)
)
throw {
code: 409,
@@ -350,11 +344,11 @@ const updateUser = async (
if (!updatedUser)
throw {
code: 404,
message: `Unable to find user with ${findBy.id || findBy.username}`
message: `Unable to find user with ${findBy._id || findBy.username}`
}
return {
id: updatedUser.id,
uid: updatedUser.uid,
username: updatedUser.username,
displayName: updatedUser.displayName,
isAdmin: updatedUser.isAdmin,
@@ -367,7 +361,7 @@ const updateDesktopAutoExec = async (autoExec: string) => {
await updateUserAutoExec(autoExec)
return {
...desktopUser,
id: desktopUser.userId,
uid: desktopUser.userId,
autoExec
}
}
+2 -2
View File
@@ -121,7 +121,7 @@ const login = async (
req.session.loggedIn = true
req.session.user = {
userId: user.id,
userId: user.uid,
clientId: 'web_app',
username: user.username,
displayName: user.displayName,
@@ -134,7 +134,7 @@ const login = async (
return {
loggedIn: true,
user: {
id: user.id,
uid: user.uid,
username: user.username,
displayName: user.displayName,
isAdmin: user.isAdmin,
+1 -1
View File
@@ -76,7 +76,7 @@ const authenticateToken = async (
const { MODE } = process.env
if (MODE === ModeType.Desktop) {
req.user = {
userId: 1234,
userId: '1234',
clientId: 'desktopModeClientId',
username: 'desktopModeUsername',
displayName: 'desktopModeDisplayName',
+1 -1
View File
@@ -18,7 +18,7 @@ export const authorize: RequestHandler = async (req, res, next) => {
// no need to check for permissions when route is Public
if (await isPublicRoute(req)) return next()
const dbUser = await User.findOne({ id: user.userId })
const dbUser = await User.findOne({ _id: user.userId })
if (!dbUser) return res.sendStatus(401)
const path = getPath(req)
+1 -1
View File
@@ -28,7 +28,7 @@ export const desktopRestrict: RequestHandler = (req, res, next) => {
}
export const desktopUser: RequestUser = {
userId: 12345,
userId: '12345',
clientId: 'desktop_app',
username: userInfo().username,
displayName: userInfo().username,
+2 -2
View File
@@ -8,8 +8,8 @@ export const verifyAdminIfNeeded: RequestHandler = (req, res, next) => {
if (!user?.isAdmin) {
let adminAccountRequired: boolean = true
if (req.params.userId) {
adminAccountRequired = user?.userId !== parseInt(req.params.userId)
if (req.params.uid) {
adminAccountRequired = user?.userId !== req.params.uid
} else if (req.params.username) {
adminAccountRequired = user?.username !== req.params.username
}
-15
View File
@@ -1,15 +0,0 @@
import mongoose, { Schema } from 'mongoose'
const CounterSchema = new Schema({
id: {
type: String,
required: true,
unique: true
},
seq: {
type: Number,
required: true
}
})
export default mongoose.model('Counter', CounterSchema)
+41 -33
View File
@@ -1,9 +1,9 @@
import { Schema, model, Document, Model } from 'mongoose'
import { GroupDetailsResponse } from '../controllers'
import User, { IUser } from './User'
import { AuthProviderType, getSequenceNextValue } from '../utils'
import { AuthProviderType } from '../utils'
export const PUBLIC_GROUP_NAME = 'Public'
export const PUBLIC_GROUP_NAME = 'public'
export interface GroupPayload {
/**
@@ -24,10 +24,12 @@ export interface GroupPayload {
}
interface IGroupDocument extends GroupPayload, Document {
groupId: number
isActive: boolean
users: Schema.Types.ObjectId[]
authProvider?: AuthProviderType
// Declare virtual properties as read-only properties
readonly uid: string
}
interface IGroup extends IGroupDocument {
@@ -37,40 +39,46 @@ interface IGroup extends IGroupDocument {
}
interface IGroupModel extends Model<IGroup> {}
const groupSchema = new Schema<IGroupDocument>({
name: {
type: String,
required: true,
unique: true
},
groupId: {
type: Number,
unique: true
},
description: {
type: String,
default: 'Group description.'
},
authProvider: {
type: String,
enum: AuthProviderType
},
isActive: {
type: Boolean,
default: true
},
users: [{ type: Schema.Types.ObjectId, ref: 'User' }]
})
// Hooks
groupSchema.pre('save', async function () {
if (this.isNew) {
this.groupId = await getSequenceNextValue('groupId')
const opts = {
toJSON: {
virtuals: true,
transform: function (doc: any, ret: any, options: any) {
delete ret._id
delete ret.id
return ret
}
}
}
const groupSchema = new Schema<IGroupDocument>(
{
name: {
type: String,
required: true,
unique: true
},
description: {
type: String,
default: 'Group description.'
},
authProvider: {
type: String,
enum: AuthProviderType
},
isActive: {
type: Boolean,
default: true
},
users: [{ type: Schema.Types.ObjectId, ref: 'User' }]
},
opts
)
groupSchema.virtual('uid').get(function () {
return this._id.toString()
})
groupSchema.post('save', function (group: IGroup, next: Function) {
group.populate('users', 'id username displayName -_id').then(function () {
group.populate('users', 'uid username displayName').then(function () {
next()
})
})
+39 -37
View File
@@ -1,6 +1,5 @@
import { Schema, model, Document, Model } from 'mongoose'
import { PermissionDetailsResponse } from '../controllers'
import { getSequenceNextValue } from '../utils'
interface GetPermissionBy {
user?: Schema.Types.ObjectId
@@ -11,9 +10,11 @@ interface IPermissionDocument extends Document {
path: string
type: string
setting: string
permissionId: number
user: Schema.Types.ObjectId
group: Schema.Types.ObjectId
// Declare virtual properties as read-only properties
readonly uid: string
}
interface IPermission extends IPermissionDocument {}
@@ -22,32 +23,39 @@ interface IPermissionModel extends Model<IPermission> {
get(getBy: GetPermissionBy): Promise<PermissionDetailsResponse[]>
}
const permissionSchema = new Schema<IPermissionDocument>({
permissionId: {
type: Number,
unique: true
},
path: {
type: String,
required: true
},
type: {
type: String,
required: true
},
setting: {
type: String,
required: true
},
user: { type: Schema.Types.ObjectId, ref: 'User' },
group: { type: Schema.Types.ObjectId, ref: 'Group' }
})
// Hooks
permissionSchema.pre('save', async function () {
if (this.isNew) {
this.permissionId = await getSequenceNextValue('permissionId')
const opts = {
toJSON: {
virtuals: true,
transform: function (doc: any, ret: any, options: any) {
delete ret._id
delete ret.id
return ret
}
}
}
const permissionSchema = new Schema<IPermissionDocument>(
{
path: {
type: String,
required: true
},
type: {
type: String,
required: true
},
setting: {
type: String,
required: true
},
user: { type: Schema.Types.ObjectId, ref: 'User' },
group: { type: Schema.Types.ObjectId, ref: 'Group' }
},
opts
)
permissionSchema.virtual('uid').get(function () {
return this._id.toString()
})
// Static Methods
@@ -55,20 +63,14 @@ permissionSchema.static('get', async function (getBy: GetPermissionBy): Promise<
PermissionDetailsResponse[]
> {
return (await this.find(getBy)
.select({
_id: 0,
permissionId: 1,
path: 1,
type: 1,
setting: 1
})
.populate({ path: 'user', select: 'id username displayName isAdmin -_id' })
.select('uid path type setting')
.populate({ path: 'user', select: 'uid username displayName isAdmin' })
.populate({
path: 'group',
select: 'groupId name description -_id',
select: 'uid name description',
populate: {
path: 'users',
select: 'id username displayName isAdmin -_id',
select: 'uid username displayName isAdmin',
options: { limit: 15 }
}
})) as unknown as PermissionDetailsResponse[]
+70 -64
View File
@@ -1,6 +1,6 @@
import { Schema, model, Document, Model } from 'mongoose'
import { Schema, model, Document, Model, ObjectId } from 'mongoose'
import bcrypt from 'bcryptjs'
import { AuthProviderType, getSequenceNextValue } from '../utils'
import { AuthProviderType } from '../utils'
export interface UserPayload {
/**
@@ -36,7 +36,6 @@ export interface UserPayload {
interface IUserDocument extends UserPayload, Document {
_id: Schema.Types.ObjectId
id: number
isAdmin: boolean
isActive: boolean
needsToUpdatePassword: boolean
@@ -44,6 +43,9 @@ interface IUserDocument extends UserPayload, Document {
groups: Schema.Types.ObjectId[]
tokens: [{ [key: string]: string }]
authProvider?: AuthProviderType
// Declare virtual properties as read-only properties
readonly uid: string
}
export interface IUser extends IUserDocument {
@@ -54,70 +56,74 @@ export interface IUser extends IUserDocument {
interface IUserModel extends Model<IUser> {
hashPassword(password: string): string
}
const userSchema = new Schema<IUserDocument>({
displayName: {
type: String,
required: true
},
username: {
type: String,
required: true,
unique: true
},
id: {
type: Number,
unique: true
},
password: {
type: String,
required: true
},
authProvider: {
type: String,
enum: AuthProviderType
},
isAdmin: {
type: Boolean,
default: false
},
isActive: {
type: Boolean,
default: true
},
needsToUpdatePassword: {
type: Boolean,
default: true
},
autoExec: {
type: String
},
groups: [{ type: Schema.Types.ObjectId, ref: 'Group' }],
tokens: [
{
clientId: {
type: String,
required: true
},
accessToken: {
type: String,
required: true
},
refreshToken: {
type: String,
required: true
}
const opts = {
toJSON: {
virtuals: true,
transform: function (doc: any, ret: any, options: any) {
delete ret._id
delete ret.id
return ret
}
]
})
// Hooks
userSchema.pre('save', async function (next) {
if (this.isNew) {
this.id = await getSequenceNextValue('id')
}
}
next()
const userSchema = new Schema<IUserDocument>(
{
displayName: {
type: String,
required: true
},
username: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
authProvider: {
type: String,
enum: AuthProviderType
},
isAdmin: {
type: Boolean,
default: false
},
isActive: {
type: Boolean,
default: true
},
needsToUpdatePassword: {
type: Boolean,
default: true
},
autoExec: {
type: String
},
groups: [{ type: Schema.Types.ObjectId, ref: 'Group' }],
tokens: [
{
clientId: {
type: String,
required: true
},
accessToken: {
type: String,
required: true
},
refreshToken: {
type: String,
required: true
}
}
]
},
opts
)
userSchema.virtual('uid').get(function () {
return this._id.toString()
})
// Static Methods
+24 -20
View File
@@ -1,7 +1,11 @@
import express from 'express'
import { GroupController } from '../../controllers/'
import { authenticateAccessToken, verifyAdmin } from '../../middlewares'
import { getGroupValidation, registerGroupValidation } from '../../utils'
import {
getGroupValidation,
registerGroupValidation,
uidValidation
} from '../../utils'
const groupRouter = express.Router()
@@ -33,12 +37,15 @@ groupRouter.get('/', authenticateAccessToken, async (req, res) => {
}
})
groupRouter.get('/:groupId', authenticateAccessToken, async (req, res) => {
const { groupId } = req.params
groupRouter.get('/:uid', authenticateAccessToken, async (req, res) => {
const { error: uidError, value: params } = uidValidation(req.params)
if (uidError) return res.status(400).send(uidError.details[0].message)
const { uid } = params
const controller = new GroupController()
try {
const response = await controller.getGroup(parseInt(groupId))
const response = await controller.getGroup(uid)
res.send(response)
} catch (err: any) {
res.status(err.code).send(err.message)
@@ -56,7 +63,7 @@ groupRouter.get(
const controller = new GroupController()
try {
const response = await controller.getGroupByGroupName(name)
const response = await controller.getGroupByName(name)
res.send(response)
} catch (err: any) {
res.status(err.code).send(err.message)
@@ -65,18 +72,15 @@ groupRouter.get(
)
groupRouter.post(
'/:groupId/:userId',
'/:groupUid/:userUid',
authenticateAccessToken,
verifyAdmin,
async (req, res) => {
const { groupId, userId } = req.params
const { groupUid, userUid } = req.params
const controller = new GroupController()
try {
const response = await controller.addUserToGroup(
parseInt(groupId),
parseInt(userId)
)
const response = await controller.addUserToGroup(groupUid, userUid)
res.send(response)
} catch (err: any) {
res.status(err.code).send(err.message)
@@ -85,18 +89,15 @@ groupRouter.post(
)
groupRouter.delete(
'/:groupId/:userId',
'/:groupUid/:userUid',
authenticateAccessToken,
verifyAdmin,
async (req, res) => {
const { groupId, userId } = req.params
const { groupUid, userUid } = req.params
const controller = new GroupController()
try {
const response = await controller.removeUserFromGroup(
parseInt(groupId),
parseInt(userId)
)
const response = await controller.removeUserFromGroup(groupUid, userUid)
res.send(response)
} catch (err: any) {
res.status(err.code).send(err.message)
@@ -105,15 +106,18 @@ groupRouter.delete(
)
groupRouter.delete(
'/:groupId',
'/:uid',
authenticateAccessToken,
verifyAdmin,
async (req, res) => {
const { groupId } = req.params
const { error: uidError, value: params } = uidValidation(req.params)
if (uidError) return res.status(400).send(uidError.details[0].message)
const { uid } = params
const controller = new GroupController()
try {
await controller.deleteGroup(parseInt(groupId))
await controller.deleteGroup(uid)
res.status(200).send('Group Deleted!')
} catch (err: any) {
res.status(err.code).send(err.message)
+19 -17
View File
@@ -3,6 +3,7 @@ import { PermissionController } from '../../controllers/'
import { verifyAdmin } from '../../middlewares'
import {
registerPermissionValidation,
uidValidation,
updatePermissionValidation
} from '../../utils'
@@ -34,14 +35,17 @@ permissionRouter.post('/', verifyAdmin, async (req, res) => {
}
})
permissionRouter.patch('/:permissionId', verifyAdmin, async (req: any, res) => {
const { permissionId } = req.params
permissionRouter.patch('/:uid', verifyAdmin, async (req: any, res) => {
const { error: uidError, value: params } = uidValidation(req.params)
if (uidError) return res.status(400).send(uidError.details[0].message)
const { uid } = params
const { error, value: body } = updatePermissionValidation(req.body)
if (error) return res.status(400).send(error.details[0].message)
try {
const response = await controller.updatePermission(permissionId, body)
const response = await controller.updatePermission(uid, body)
res.send(response)
} catch (err: any) {
const statusCode = err.code
@@ -50,20 +54,18 @@ permissionRouter.patch('/:permissionId', verifyAdmin, async (req: any, res) => {
}
})
permissionRouter.delete(
'/:permissionId',
verifyAdmin,
async (req: any, res) => {
const { permissionId } = req.params
permissionRouter.delete('/:uid', verifyAdmin, async (req: any, res) => {
const { error: uidError, value: params } = uidValidation(req.params)
if (uidError) return res.status(400).send(uidError.details[0].message)
try {
await controller.deletePermission(permissionId)
res.status(200).send('Permission Deleted!')
} catch (err: any) {
const statusCode = err.code
delete err.code
res.status(statusCode).send(err.message)
}
const { uid } = params
try {
await controller.deletePermission(uid)
res.status(200).send('Permission Deleted!')
} catch (err: any) {
const statusCode = err.code
delete err.code
res.status(statusCode).send(err.message)
}
)
})
export default permissionRouter
+6 -6
View File
@@ -13,6 +13,7 @@ import {
generateAccessToken,
generateAuthCode,
generateRefreshToken,
randomBytesHexString,
saveTokensInDB,
verifyTokenInDB
} from '../../../utils'
@@ -20,7 +21,6 @@ import {
const clientId = 'someclientID'
const clientSecret = 'someclientSecret'
const user = {
id: 1234,
displayName: 'Test User',
username: 'testUsername',
password: '87654321',
@@ -52,7 +52,7 @@ describe('auth', () => {
describe('token', () => {
const userInfo: InfoJWT = {
clientId,
userId: user.id
userId: randomBytesHexString(12)
}
beforeAll(async () => {
await userController.createUser(user)
@@ -151,10 +151,10 @@ describe('auth', () => {
currentUser = await userController.createUser(user)
refreshToken = generateRefreshToken({
clientId,
userId: currentUser.id
userId: currentUser.uid
})
await saveTokensInDB(
currentUser.id,
currentUser.uid,
clientId,
'accessToken',
refreshToken
@@ -202,11 +202,11 @@ describe('auth', () => {
currentUser = await userController.createUser(user)
accessToken = generateAccessToken({
clientId,
userId: currentUser.id
userId: currentUser.uid
})
await saveTokensInDB(
currentUser.id,
currentUser.uid,
clientId,
accessToken,
'refreshToken'
+6 -6
View File
@@ -40,10 +40,10 @@ describe('client', () => {
const dbUser = await userController.createUser(adminUser)
adminAccessToken = generateAccessToken({
clientId: client.clientId,
userId: dbUser.id
userId: dbUser.uid
})
await saveTokensInDB(
dbUser.id,
dbUser.uid,
client.clientId,
adminAccessToken,
'refreshToken'
@@ -95,10 +95,10 @@ describe('client', () => {
const dbUser = await userController.createUser(user)
const accessToken = generateAccessToken({
clientId: client.clientId,
userId: dbUser.id
userId: dbUser.uid
})
await saveTokensInDB(
dbUser.id,
dbUser.uid,
client.clientId,
accessToken,
'refreshToken'
@@ -212,10 +212,10 @@ describe('client', () => {
const dbUser = await userController.createUser(user)
const accessToken = generateAccessToken({
clientId: client.clientId,
userId: dbUser.id
userId: dbUser.uid
})
await saveTokensInDB(
dbUser.id,
dbUser.uid,
client.clientId,
accessToken,
'refreshToken'
+3 -3
View File
@@ -71,13 +71,13 @@ describe('code', () => {
permissionController = new PermissionController()
const dbUser = await userController.createUser(user)
accessToken = await generateAndSaveToken(dbUser.id)
accessToken = await generateAndSaveToken(dbUser.uid)
await permissionController.createPermission({
path: '/SASjsApi/code/execute',
type: PermissionType.route,
principalType: PrincipalType.user,
principalId: dbUser.id,
principalId: dbUser.uid,
setting: PermissionSettingForRoute.grant
})
@@ -122,7 +122,7 @@ describe('code', () => {
})
})
const generateAndSaveToken = async (userId: number) => {
const generateAndSaveToken = async (userId: string) => {
const accessToken = generateAccessToken({
clientId,
userId
+16 -7
View File
@@ -28,6 +28,15 @@ jest
.spyOn(fileUtilModules, 'getUploadsFolder')
.mockImplementation(() => path.join(tmpFolder, 'uploads'))
// getFilesFolder() resolves via getSasjsDriveFolder()/process.driveLoc, a
// separate root from getSasjsRootFolder()/process.sasjsRoot above - without
// this, every test in this file that creates content under the drive (e.g.
// 'level1', 'my/path/...') writes into the real, shared sasjs_root/drive
// instead of this run's isolated tmpFolder, leaking state into later runs.
jest
.spyOn(fileUtilModules, 'getFilesFolder')
.mockImplementation(() => path.join(tmpFolder, 'drive', 'files'))
import appPromise from '../../../app'
import {
UserController,
@@ -71,31 +80,31 @@ describe('drive', () => {
con = await mongoose.connect(mongoServer.getUri())
const dbUser = await controller.createUser(user)
accessToken = await generateAndSaveToken(dbUser.id)
accessToken = await generateAndSaveToken(dbUser.uid)
await permissionController.createPermission({
...permission,
path: '/SASjsApi/drive/deploy',
principalId: dbUser.id
principalId: dbUser.uid
})
await permissionController.createPermission({
...permission,
path: '/SASjsApi/drive/deploy/upload',
principalId: dbUser.id
principalId: dbUser.uid
})
await permissionController.createPermission({
...permission,
path: '/SASjsApi/drive/file',
principalId: dbUser.id
principalId: dbUser.uid
})
await permissionController.createPermission({
...permission,
path: '/SASjsApi/drive/folder',
principalId: dbUser.id
principalId: dbUser.uid
})
await permissionController.createPermission({
...permission,
path: '/SASjsApi/drive/rename',
principalId: dbUser.id
principalId: dbUser.uid
})
})
@@ -1197,7 +1206,7 @@ const getExampleService = (): ServiceMember =>
((getTreeExample().members[0] as FolderMember).members[0] as FolderMember)
.members[0] as ServiceMember
const generateAndSaveToken = async (userId: number) => {
const generateAndSaveToken = async (userId: string) => {
const adminAccessToken = generateAccessToken({
clientId,
userId
+60 -47
View File
@@ -11,6 +11,7 @@ import {
} from '../../../utils'
import Group, { PUBLIC_GROUP_NAME } from '../../../model/Group'
import User from '../../../model/User'
import { randomBytes } from 'crypto'
const clientId = 'someclientID'
const adminUser = {
@@ -75,7 +76,7 @@ describe('group', () => {
.send(group)
.expect(200)
expect(res.body.groupId).toBeTruthy()
expect(res.body.uid).toBeTruthy()
expect(res.body.name).toEqual(group.name)
expect(res.body.description).toEqual(group.description)
expect(res.body.isActive).toEqual(true)
@@ -155,7 +156,7 @@ describe('group', () => {
const dbGroup = await groupController.createGroup(group)
const res = await request(app)
.delete(`/SASjsApi/group/${dbGroup.groupId}`)
.delete(`/SASjsApi/group/${dbGroup.uid}`)
.auth(adminAccessToken, { type: 'bearer' })
.send()
.expect(200)
@@ -174,17 +175,17 @@ describe('group', () => {
username: 'deletegroup2'
})
await groupController.addUserToGroup(dbGroup.groupId, dbUser1.id)
await groupController.addUserToGroup(dbGroup.groupId, dbUser2.id)
await groupController.addUserToGroup(dbGroup.uid, dbUser1.uid)
await groupController.addUserToGroup(dbGroup.uid, dbUser2.uid)
await request(app)
.delete(`/SASjsApi/group/${dbGroup.groupId}`)
.delete(`/SASjsApi/group/${dbGroup.uid}`)
.auth(adminAccessToken, { type: 'bearer' })
.send()
.expect(200)
const res1 = await request(app)
.get(`/SASjsApi/user/${dbUser1.id}`)
.get(`/SASjsApi/user/${dbUser1.uid}`)
.auth(adminAccessToken, { type: 'bearer' })
.send()
.expect(200)
@@ -192,7 +193,7 @@ describe('group', () => {
expect(res1.body.groups).toEqual([])
const res2 = await request(app)
.get(`/SASjsApi/user/${dbUser2.id}`)
.get(`/SASjsApi/user/${dbUser2.uid}`)
.auth(adminAccessToken, { type: 'bearer' })
.send()
.expect(200)
@@ -201,8 +202,10 @@ describe('group', () => {
})
it('should respond with Not Found if groupId is incorrect', async () => {
const hexValue = randomBytes(12).toString('hex')
const res = await request(app)
.delete(`/SASjsApi/group/1234`)
.delete(`/SASjsApi/group/${hexValue}`)
.auth(adminAccessToken, { type: 'bearer' })
.send()
.expect(404)
@@ -229,7 +232,7 @@ describe('group', () => {
})
const res = await request(app)
.delete(`/SASjsApi/group/${dbGroup.groupId}`)
.delete(`/SASjsApi/group/${dbGroup.uid}`)
.auth(accessToken, { type: 'bearer' })
.send()
.expect(401)
@@ -245,15 +248,15 @@ describe('group', () => {
})
it('should respond with group', async () => {
const { groupId } = await groupController.createGroup(group)
const { uid } = await groupController.createGroup(group)
const res = await request(app)
.get(`/SASjsApi/group/${groupId}`)
.get(`/SASjsApi/group/${uid}`)
.auth(adminAccessToken, { type: 'bearer' })
.send()
.expect(200)
expect(res.body.groupId).toBeTruthy()
expect(res.body.uid).toBeTruthy()
expect(res.body.name).toEqual(group.name)
expect(res.body.description).toEqual(group.description)
expect(res.body.isActive).toEqual(true)
@@ -266,15 +269,15 @@ describe('group', () => {
username: 'get' + user.username
})
const { groupId } = await groupController.createGroup(group)
const { uid } = await groupController.createGroup(group)
const res = await request(app)
.get(`/SASjsApi/group/${groupId}`)
.get(`/SASjsApi/group/${uid}`)
.auth(accessToken, { type: 'bearer' })
.send()
.expect(200)
expect(res.body.groupId).toBeTruthy()
expect(res.body.uid).toBeTruthy()
expect(res.body.name).toEqual(group.name)
expect(res.body.description).toEqual(group.description)
expect(res.body.isActive).toEqual(true)
@@ -292,8 +295,10 @@ describe('group', () => {
})
it('should respond with Not Found if groupId is incorrect', async () => {
const hexValue = randomBytes(12).toString('hex')
const res = await request(app)
.get('/SASjsApi/group/1234')
.get(`/SASjsApi/group/${hexValue}`)
.auth(adminAccessToken, { type: 'bearer' })
.send()
.expect(404)
@@ -312,7 +317,7 @@ describe('group', () => {
.send()
.expect(200)
expect(res.body.groupId).toBeTruthy()
expect(res.body.uid).toBeTruthy()
expect(res.body.name).toEqual(group.name)
expect(res.body.description).toEqual(group.description)
expect(res.body.isActive).toEqual(true)
@@ -333,7 +338,7 @@ describe('group', () => {
.send()
.expect(200)
expect(res.body.groupId).toBeTruthy()
expect(res.body.uid).toBeTruthy()
expect(res.body.name).toEqual(group.name)
expect(res.body.description).toEqual(group.description)
expect(res.body.isActive).toEqual(true)
@@ -379,7 +384,7 @@ describe('group', () => {
expect(res.body).toEqual([
{
groupId: expect.anything(),
uid: expect.anything(),
name: group.name,
description: group.description
}
@@ -401,7 +406,7 @@ describe('group', () => {
expect(res.body).toEqual([
{
groupId: expect.anything(),
uid: expect.anything(),
name: group.name,
description: group.description
}
@@ -426,18 +431,18 @@ describe('group', () => {
const dbUser = await userController.createUser(user)
const res = await request(app)
.post(`/SASjsApi/group/${dbGroup.groupId}/${dbUser.id}`)
.post(`/SASjsApi/group/${dbGroup.uid}/${dbUser.uid}`)
.auth(adminAccessToken, { type: 'bearer' })
.send()
.expect(200)
expect(res.body.groupId).toBeTruthy()
expect(res.body.uid).toBeTruthy()
expect(res.body.name).toEqual(group.name)
expect(res.body.description).toEqual(group.description)
expect(res.body.isActive).toEqual(true)
expect(res.body.users).toEqual([
{
id: expect.anything(),
uid: expect.anything(),
username: user.username,
displayName: user.displayName
}
@@ -452,20 +457,20 @@ describe('group', () => {
})
await request(app)
.post(`/SASjsApi/group/${dbGroup.groupId}/${dbUser.id}`)
.post(`/SASjsApi/group/${dbGroup.uid}/${dbUser.uid}`)
.auth(adminAccessToken, { type: 'bearer' })
.send()
.expect(200)
const res = await request(app)
.get(`/SASjsApi/user/${dbUser.id}`)
.get(`/SASjsApi/user/${dbUser.uid}`)
.auth(adminAccessToken, { type: 'bearer' })
.send()
.expect(200)
expect(res.body.groups).toEqual([
{
groupId: expect.anything(),
uid: expect.anything(),
name: group.name,
description: group.description
}
@@ -478,21 +483,21 @@ describe('group', () => {
...user,
username: 'addUserRandomUser'
})
await groupController.addUserToGroup(dbGroup.groupId, dbUser.id)
await groupController.addUserToGroup(dbGroup.uid, dbUser.uid)
const res = await request(app)
.post(`/SASjsApi/group/${dbGroup.groupId}/${dbUser.id}`)
.post(`/SASjsApi/group/${dbGroup.uid}/${dbUser.uid}`)
.auth(adminAccessToken, { type: 'bearer' })
.send()
.expect(200)
expect(res.body.groupId).toBeTruthy()
expect(res.body.uid).toBeTruthy()
expect(res.body.name).toEqual(group.name)
expect(res.body.description).toEqual(group.description)
expect(res.body.isActive).toEqual(true)
expect(res.body.users).toEqual([
{
id: expect.anything(),
uid: expect.anything(),
username: 'addUserRandomUser',
displayName: user.displayName
}
@@ -526,8 +531,10 @@ describe('group', () => {
})
it('should respond with Not Found if groupId is incorrect', async () => {
const hexValue = randomBytes(12).toString('hex')
const res = await request(app)
.post('/SASjsApi/group/123/123')
.post(`/SASjsApi/group/${hexValue}/123`)
.auth(adminAccessToken, { type: 'bearer' })
.send()
.expect(404)
@@ -538,8 +545,10 @@ describe('group', () => {
it('should respond with Not Found if userId is incorrect', async () => {
const dbGroup = await groupController.createGroup(group)
const hexValue = randomBytes(12).toString('hex')
const res = await request(app)
.post(`/SASjsApi/group/${dbGroup.groupId}/123`)
.post(`/SASjsApi/group/${dbGroup.uid}/${hexValue}`)
.auth(adminAccessToken, { type: 'bearer' })
.send()
.expect(404)
@@ -556,7 +565,7 @@ describe('group', () => {
})
const res = await request(app)
.post(`/SASjsApi/group/${dbGroup.groupId}/${dbUser.id}`)
.post(`/SASjsApi/group/${dbGroup.uid}/${dbUser.uid}`)
.auth(adminAccessToken, { type: 'bearer' })
.send()
.expect(400)
@@ -577,7 +586,7 @@ describe('group', () => {
})
const res = await request(app)
.post(`/SASjsApi/group/${dbGroup.groupId}/${dbUser.id}`)
.post(`/SASjsApi/group/${dbGroup.uid}/${dbUser.uid}`)
.auth(adminAccessToken, { type: 'bearer' })
.send()
.expect(405)
@@ -596,7 +605,7 @@ describe('group', () => {
})
const res = await request(app)
.post(`/SASjsApi/group/${dbGroup.groupId}/${dbUser.id}`)
.post(`/SASjsApi/group/${dbGroup.uid}/${dbUser.uid}`)
.auth(adminAccessToken, { type: 'bearer' })
.send()
.expect(405)
@@ -618,15 +627,15 @@ describe('group', () => {
...user,
username: 'removeUserRandomUser'
})
await groupController.addUserToGroup(dbGroup.groupId, dbUser.id)
await groupController.addUserToGroup(dbGroup.uid, dbUser.uid)
const res = await request(app)
.delete(`/SASjsApi/group/${dbGroup.groupId}/${dbUser.id}`)
.delete(`/SASjsApi/group/${dbGroup.uid}/${dbUser.uid}`)
.auth(adminAccessToken, { type: 'bearer' })
.send()
.expect(200)
expect(res.body.groupId).toBeTruthy()
expect(res.body.uid).toBeTruthy()
expect(res.body.name).toEqual(group.name)
expect(res.body.description).toEqual(group.description)
expect(res.body.isActive).toEqual(true)
@@ -639,16 +648,16 @@ describe('group', () => {
...user,
username: 'removeGroupFromUser'
})
await groupController.addUserToGroup(dbGroup.groupId, dbUser.id)
await groupController.addUserToGroup(dbGroup.uid, dbUser.uid)
await request(app)
.delete(`/SASjsApi/group/${dbGroup.groupId}/${dbUser.id}`)
.delete(`/SASjsApi/group/${dbGroup.uid}/${dbUser.uid}`)
.auth(adminAccessToken, { type: 'bearer' })
.send()
.expect(200)
const res = await request(app)
.get(`/SASjsApi/user/${dbUser.id}`)
.get(`/SASjsApi/user/${dbUser.uid}`)
.auth(adminAccessToken, { type: 'bearer' })
.send()
.expect(200)
@@ -667,7 +676,7 @@ describe('group', () => {
})
const res = await request(app)
.delete(`/SASjsApi/group/${dbGroup.groupId}/${dbUser.id}`)
.delete(`/SASjsApi/group/${dbGroup.uid}/${dbUser.uid}`)
.auth(adminAccessToken, { type: 'bearer' })
.send()
.expect(405)
@@ -686,7 +695,7 @@ describe('group', () => {
})
const res = await request(app)
.delete(`/SASjsApi/group/${dbGroup.groupId}/${dbUser.id}`)
.delete(`/SASjsApi/group/${dbGroup.uid}/${dbUser.uid}`)
.auth(adminAccessToken, { type: 'bearer' })
.send()
.expect(405)
@@ -723,8 +732,10 @@ describe('group', () => {
})
it('should respond with Not Found if groupId is incorrect', async () => {
const hexValue = randomBytes(12).toString('hex')
const res = await request(app)
.delete('/SASjsApi/group/123/123')
.delete(`/SASjsApi/group/${hexValue}/123`)
.auth(adminAccessToken, { type: 'bearer' })
.send()
.expect(404)
@@ -735,8 +746,10 @@ describe('group', () => {
it('should respond with Not Found if userId is incorrect', async () => {
const dbGroup = await groupController.createGroup(group)
const hexValue = randomBytes(12).toString('hex')
const res = await request(app)
.delete(`/SASjsApi/group/${dbGroup.groupId}/123`)
.delete(`/SASjsApi/group/${dbGroup.uid}/${hexValue}`)
.auth(adminAccessToken, { type: 'bearer' })
.send()
.expect(404)
@@ -752,10 +765,10 @@ const generateSaveTokenAndCreateUser = async (
): Promise<string> => {
const dbUser = await userController.createUser(someUser ?? adminUser)
return generateAndSaveToken(dbUser.id)
return generateAndSaveToken(dbUser.uid)
}
const generateAndSaveToken = async (userId: number) => {
const generateAndSaveToken = async (userId: string) => {
const adminAccessToken = generateAccessToken({
clientId,
userId
+38 -32
View File
@@ -17,6 +17,7 @@ import {
PermissionDetailsResponse
} from '../../../controllers'
import { generateAccessToken, saveTokensInDB } from '../../../utils'
import { randomBytes } from 'crypto'
const deployPayload = {
appLoc: 'string',
@@ -103,10 +104,10 @@ describe('permission', () => {
const res = await request(app)
.post('/SASjsApi/permission')
.auth(adminAccessToken, { type: 'bearer' })
.send({ ...permission, principalId: dbUser.id })
.send({ ...permission, principalId: dbUser.uid })
.expect(200)
expect(res.body.permissionId).toBeTruthy()
expect(res.body.uid).toBeTruthy()
expect(res.body.path).toEqual(permission.path)
expect(res.body.type).toEqual(permission.type)
expect(res.body.setting).toEqual(permission.setting)
@@ -122,11 +123,11 @@ describe('permission', () => {
.send({
...permission,
principalType: 'group',
principalId: dbGroup.groupId
principalId: dbGroup.uid
})
.expect(200)
expect(res.body.permissionId).toBeTruthy()
expect(res.body.uid).toBeTruthy()
expect(res.body.path).toEqual(permission.path)
expect(res.body.type).toEqual(permission.type)
expect(res.body.setting).toEqual(permission.setting)
@@ -144,7 +145,7 @@ describe('permission', () => {
})
it('should respond with Unauthorized if access token is not of an admin account', async () => {
const accessToken = await generateAndSaveToken(dbUser.id)
const accessToken = await generateAndSaveToken(dbUser.uid)
const res = await request(app)
.post('/SASjsApi/permission')
@@ -281,17 +282,19 @@ describe('permission', () => {
expect(res.body).toEqual({})
})
it('should respond with Bad Request if principalId is not a number', async () => {
it('should respond with Bad Request if principalId is not a string of 24 hex characters', async () => {
const res = await request(app)
.post('/SASjsApi/permission')
.auth(adminAccessToken, { type: 'bearer' })
.send({
...permission,
principalId: 'someCharacters'
principalId: randomBytes(10).toString('hex')
})
.expect(400)
expect(res.text).toEqual('"principalId" must be a number')
expect(res.text).toEqual(
'"principalId" length must be 24 characters long'
)
expect(res.body).toEqual({})
})
@@ -307,7 +310,7 @@ describe('permission', () => {
.auth(adminAccessToken, { type: 'bearer' })
.send({
...permission,
principalId: adminUser.id
principalId: adminUser.uid
})
.expect(400)
@@ -321,7 +324,7 @@ describe('permission', () => {
.auth(adminAccessToken, { type: 'bearer' })
.send({
...permission,
principalId: 123
principalId: randomBytes(12).toString('hex')
})
.expect(404)
@@ -336,7 +339,7 @@ describe('permission', () => {
.send({
...permission,
principalType: 'group',
principalId: 123
principalId: randomBytes(12).toString('hex')
})
.expect(404)
@@ -347,13 +350,13 @@ describe('permission', () => {
it('should respond with Conflict (409) if permission already exists', async () => {
await permissionController.createPermission({
...permission,
principalId: dbUser.id
principalId: dbUser.uid
})
const res = await request(app)
.post('/SASjsApi/permission')
.auth(adminAccessToken, { type: 'bearer' })
.send({ ...permission, principalId: dbUser.id })
.send({ ...permission, principalId: dbUser.uid })
.expect(409)
expect(res.text).toEqual(
@@ -368,7 +371,7 @@ describe('permission', () => {
beforeAll(async () => {
dbPermission = await permissionController.createPermission({
...permission,
principalId: dbUser.id
principalId: dbUser.uid
})
})
@@ -378,7 +381,7 @@ describe('permission', () => {
it('should respond with updated permission', async () => {
const res = await request(app)
.patch(`/SASjsApi/permission/${dbPermission?.permissionId}`)
.patch(`/SASjsApi/permission/${dbPermission?.uid}`)
.auth(adminAccessToken, { type: 'bearer' })
.send({ setting: PermissionSettingForRoute.deny })
.expect(200)
@@ -388,7 +391,7 @@ describe('permission', () => {
it('should respond with Unauthorized if access token is not present', async () => {
const res = await request(app)
.patch(`/SASjsApi/permission/${dbPermission?.permissionId}`)
.patch(`/SASjsApi/permission/${dbPermission?.uid}`)
.send()
.expect(401)
@@ -403,7 +406,7 @@ describe('permission', () => {
})
const res = await request(app)
.patch(`/SASjsApi/permission/${dbPermission?.permissionId}`)
.patch(`/SASjsApi/permission/${dbPermission?.uid}`)
.auth(accessToken, { type: 'bearer' })
.send()
.expect(401)
@@ -414,7 +417,7 @@ describe('permission', () => {
it('should respond with Bad Request if setting is missing', async () => {
const res = await request(app)
.patch(`/SASjsApi/permission/${dbPermission?.permissionId}`)
.patch(`/SASjsApi/permission/${dbPermission?.uid}`)
.auth(adminAccessToken, { type: 'bearer' })
.send()
.expect(400)
@@ -425,7 +428,7 @@ describe('permission', () => {
it('should respond with Bad Request if setting is invalid', async () => {
const res = await request(app)
.patch(`/SASjsApi/permission/${dbPermission?.permissionId}`)
.patch(`/SASjsApi/permission/${dbPermission?.uid}`)
.auth(adminAccessToken, { type: 'bearer' })
.send({
setting: 'invalid'
@@ -437,8 +440,9 @@ describe('permission', () => {
})
it('should respond with not found (404) if permission with provided id does not exist', async () => {
const hexValue = randomBytes(12).toString('hex')
const res = await request(app)
.patch('/SASjsApi/permission/123')
.patch(`/SASjsApi/permission/${hexValue}`)
.auth(adminAccessToken, { type: 'bearer' })
.send({
setting: PermissionSettingForRoute.deny
@@ -454,10 +458,10 @@ describe('permission', () => {
it('should delete permission', async () => {
const dbPermission = await permissionController.createPermission({
...permission,
principalId: dbUser.id
principalId: dbUser.uid
})
const res = await request(app)
.delete(`/SASjsApi/permission/${dbPermission?.permissionId}`)
.delete(`/SASjsApi/permission/${dbPermission?.uid}`)
.auth(adminAccessToken, { type: 'bearer' })
.send()
.expect(200)
@@ -466,8 +470,10 @@ describe('permission', () => {
})
it('should respond with not found (404) if permission with provided id does not exists', async () => {
const hexValue = randomBytes(12).toString('hex')
const res = await request(app)
.delete('/SASjsApi/permission/123')
.delete(`/SASjsApi/permission/${hexValue}`)
.auth(adminAccessToken, { type: 'bearer' })
.send()
.expect(404)
@@ -481,12 +487,12 @@ describe('permission', () => {
await permissionController.createPermission({
...permission,
path: '/test-1',
principalId: dbUser.id
principalId: dbUser.uid
})
await permissionController.createPermission({
...permission,
path: '/test-2',
principalId: dbUser.id
principalId: dbUser.uid
})
})
@@ -505,12 +511,12 @@ describe('permission', () => {
...user,
username: 'get' + user.username
})
const accessToken = await generateAndSaveToken(nonAdminUser.id)
const accessToken = await generateAndSaveToken(nonAdminUser.uid)
await permissionController.createPermission({
path: '/test-1',
type: PermissionType.route,
principalType: PrincipalType.user,
principalId: nonAdminUser.id,
principalId: nonAdminUser.uid,
setting: PermissionSettingForRoute.grant
})
@@ -531,7 +537,7 @@ describe('permission', () => {
await permissionController.createPermission({
...permission,
path: '/SASjsApi/drive/deploy',
principalId: dbUser.id
principalId: dbUser.uid
})
})
@@ -551,7 +557,7 @@ describe('permission', () => {
})
it('should create files in SASJS drive', async () => {
const accessToken = await generateAndSaveToken(dbUser.id)
const accessToken = await generateAndSaveToken(dbUser.uid)
await request(app)
.get('/SASjsApi/drive/deploy')
@@ -561,7 +567,7 @@ describe('permission', () => {
})
it('should respond unauthorized', async () => {
const accessToken = await generateAndSaveToken(dbUser.id)
const accessToken = await generateAndSaveToken(dbUser.uid)
await request(app)
.get('/SASjsApi/drive/deploy/upload')
@@ -577,10 +583,10 @@ const generateSaveTokenAndCreateUser = async (
): Promise<string> => {
const dbUser = await userController.createUser(someUser ?? adminUser)
return generateAndSaveToken(dbUser.id)
return generateAndSaveToken(dbUser.uid)
}
const generateAndSaveToken = async (userId: number) => {
const generateAndSaveToken = async (userId: string) => {
const adminAccessToken = generateAccessToken({
clientId,
userId
+3 -3
View File
@@ -58,12 +58,12 @@ describe('stp', () => {
mongoServer = await MongoMemoryServer.create()
con = await mongoose.connect(mongoServer.getUri())
const dbUser = await userController.createUser(user)
accessToken = await generateAndSaveToken(dbUser.id)
accessToken = await generateAndSaveToken(dbUser.uid)
await permissionController.createPermission({
path: '/SASjsApi/stp/execute',
type: PermissionType.route,
principalType: PrincipalType.user,
principalId: dbUser.id,
principalId: dbUser.uid,
setting: PermissionSettingForRoute.grant
})
})
@@ -456,7 +456,7 @@ const makeRequestAndAssert = async (
)
}
const generateAndSaveToken = async (userId: number) => {
const generateAndSaveToken = async (userId: string) => {
const accessToken = generateAccessToken({
clientId,
userId
+43 -40
View File
@@ -1,3 +1,4 @@
import { randomBytes } from 'crypto'
import { Express } from 'express'
import mongoose, { Mongoose } from 'mongoose'
import { MongoMemoryServer } from 'mongodb-memory-server'
@@ -101,9 +102,9 @@ describe('user', () => {
const dbUser = await controller.createUser(user)
const accessToken = generateAccessToken({
clientId,
userId: dbUser.id
userId: dbUser.uid
})
await saveTokensInDB(dbUser.id, clientId, accessToken, 'refreshToken')
await saveTokensInDB(dbUser.uid, clientId, accessToken, 'refreshToken')
const res = await request(app)
.post('/SASjsApi/user')
@@ -187,7 +188,7 @@ describe('user', () => {
const newDisplayName = 'My new display Name'
const res = await request(app)
.patch(`/SASjsApi/user/${dbUser.id}`)
.patch(`/SASjsApi/user/${dbUser.uid}`)
.auth(adminAccessToken, { type: 'bearer' })
.send({ ...user, displayName: newDisplayName })
.expect(200)
@@ -200,11 +201,11 @@ describe('user', () => {
it('should respond with updated user when user himself requests', async () => {
const dbUser = await controller.createUser(user)
const accessToken = await generateAndSaveToken(dbUser.id)
const accessToken = await generateAndSaveToken(dbUser.uid)
const newDisplayName = 'My new display Name'
const res = await request(app)
.patch(`/SASjsApi/user/${dbUser.id}`)
.patch(`/SASjsApi/user/${dbUser.uid}`)
.auth(accessToken, { type: 'bearer' })
.send({
displayName: newDisplayName,
@@ -221,11 +222,11 @@ describe('user', () => {
it('should respond with Bad Request, only admin can update isAdmin/isActive', async () => {
const dbUser = await controller.createUser(user)
const accessToken = await generateAndSaveToken(dbUser.id)
const accessToken = await generateAndSaveToken(dbUser.uid)
const newDisplayName = 'My new display Name'
await request(app)
.patch(`/SASjsApi/user/${dbUser.id}`)
.patch(`/SASjsApi/user/${dbUser.uid}`)
.auth(accessToken, { type: 'bearer' })
.send({ ...user, displayName: newDisplayName })
.expect(400)
@@ -277,10 +278,10 @@ describe('user', () => {
...user,
username: 'randomUser'
})
const accessToken = await generateAndSaveToken(dbUser2.id)
const accessToken = await generateAndSaveToken(dbUser2.uid)
const res = await request(app)
.patch(`/SASjsApi/user/${dbUser1.id}`)
.patch(`/SASjsApi/user/${dbUser1.uid}`)
.auth(accessToken, { type: 'bearer' })
.send(user)
.expect(401)
@@ -297,7 +298,7 @@ describe('user', () => {
})
const res = await request(app)
.patch(`/SASjsApi/user/${dbUser1.id}`)
.patch(`/SASjsApi/user/${dbUser1.uid}`)
.auth(adminAccessToken, { type: 'bearer' })
.send({ username: dbUser2.username })
.expect(409)
@@ -325,7 +326,7 @@ describe('user', () => {
it('should respond with updated user when user himself requests', async () => {
const dbUser = await controller.createUser(user)
const accessToken = await generateAndSaveToken(dbUser.id)
const accessToken = await generateAndSaveToken(dbUser.uid)
const newDisplayName = 'My new display Name'
const res = await request(app)
@@ -346,7 +347,7 @@ describe('user', () => {
it('should respond with Bad Request, only admin can update isAdmin/isActive', async () => {
const dbUser = await controller.createUser(user)
const accessToken = await generateAndSaveToken(dbUser.id)
const accessToken = await generateAndSaveToken(dbUser.uid)
const newDisplayName = 'My new display Name'
await request(app)
@@ -372,10 +373,10 @@ describe('user', () => {
...user,
username: 'randomUser'
})
const accessToken = await generateAndSaveToken(dbUser2.id)
const accessToken = await generateAndSaveToken(dbUser2.uid)
const res = await request(app)
.patch(`/SASjsApi/user/${dbUser1.id}`)
.patch(`/SASjsApi/user/${dbUser1.uid}`)
.auth(accessToken, { type: 'bearer' })
.send(user)
.expect(401)
@@ -418,7 +419,7 @@ describe('user', () => {
const dbUser = await controller.createUser(user)
const res = await request(app)
.delete(`/SASjsApi/user/${dbUser.id}`)
.delete(`/SASjsApi/user/${dbUser.uid}`)
.auth(adminAccessToken, { type: 'bearer' })
.send()
.expect(200)
@@ -428,10 +429,10 @@ describe('user', () => {
it('should respond with OK when user himself requests', async () => {
const dbUser = await controller.createUser(user)
const accessToken = await generateAndSaveToken(dbUser.id)
const accessToken = await generateAndSaveToken(dbUser.uid)
const res = await request(app)
.delete(`/SASjsApi/user/${dbUser.id}`)
.delete(`/SASjsApi/user/${dbUser.uid}`)
.auth(accessToken, { type: 'bearer' })
.send({ password: user.password })
.expect(200)
@@ -441,10 +442,10 @@ describe('user', () => {
it('should respond with Bad Request when user himself requests and password is missing', async () => {
const dbUser = await controller.createUser(user)
const accessToken = await generateAndSaveToken(dbUser.id)
const accessToken = await generateAndSaveToken(dbUser.uid)
const res = await request(app)
.delete(`/SASjsApi/user/${dbUser.id}`)
.delete(`/SASjsApi/user/${dbUser.uid}`)
.auth(accessToken, { type: 'bearer' })
.send()
.expect(400)
@@ -469,10 +470,10 @@ describe('user', () => {
...user,
username: 'randomUser'
})
const accessToken = await generateAndSaveToken(dbUser2.id)
const accessToken = await generateAndSaveToken(dbUser2.uid)
const res = await request(app)
.delete(`/SASjsApi/user/${dbUser1.id}`)
.delete(`/SASjsApi/user/${dbUser1.uid}`)
.auth(accessToken, { type: 'bearer' })
.send(user)
.expect(401)
@@ -483,10 +484,10 @@ describe('user', () => {
it('should respond with Unauthorized when user himself requests and password is incorrect', async () => {
const dbUser = await controller.createUser(user)
const accessToken = await generateAndSaveToken(dbUser.id)
const accessToken = await generateAndSaveToken(dbUser.uid)
const res = await request(app)
.delete(`/SASjsApi/user/${dbUser.id}`)
.delete(`/SASjsApi/user/${dbUser.uid}`)
.auth(accessToken, { type: 'bearer' })
.send({ password: 'incorrectpassword' })
.expect(401)
@@ -510,7 +511,7 @@ describe('user', () => {
it('should respond with OK when user himself requests', async () => {
const dbUser = await controller.createUser(user)
const accessToken = await generateAndSaveToken(dbUser.id)
const accessToken = await generateAndSaveToken(dbUser.uid)
const res = await request(app)
.delete(`/SASjsApi/user/by/username/${dbUser.username}`)
@@ -523,7 +524,7 @@ describe('user', () => {
it('should respond with Bad Request when user himself requests and password is missing', async () => {
const dbUser = await controller.createUser(user)
const accessToken = await generateAndSaveToken(dbUser.id)
const accessToken = await generateAndSaveToken(dbUser.uid)
const res = await request(app)
.delete(`/SASjsApi/user/by/username/${dbUser.username}`)
@@ -551,7 +552,7 @@ describe('user', () => {
...user,
username: 'randomUser'
})
const accessToken = await generateAndSaveToken(dbUser2.id)
const accessToken = await generateAndSaveToken(dbUser2.uid)
const res = await request(app)
.delete(`/SASjsApi/user/by/username/${dbUser1.username}`)
@@ -565,7 +566,7 @@ describe('user', () => {
it('should respond with Unauthorized when user himself requests and password is incorrect', async () => {
const dbUser = await controller.createUser(user)
const accessToken = await generateAndSaveToken(dbUser.id)
const accessToken = await generateAndSaveToken(dbUser.uid)
const res = await request(app)
.delete(`/SASjsApi/user/by/username/${dbUser.username}`)
@@ -592,7 +593,7 @@ describe('user', () => {
it('should respond with user autoExec when same user requests', async () => {
const dbUser = await controller.createUser(user)
const userId = dbUser.id
const userId = dbUser.uid
const accessToken = await generateAndSaveToken(userId)
const res = await request(app)
@@ -611,7 +612,7 @@ describe('user', () => {
it('should respond with user autoExec when admin user requests', async () => {
const dbUser = await controller.createUser(user)
const userId = dbUser.id
const userId = dbUser.uid
const res = await request(app)
.get(`/SASjsApi/user/${userId}`)
@@ -634,7 +635,7 @@ describe('user', () => {
})
const dbUser = await controller.createUser(user)
const userId = dbUser.id
const userId = dbUser.uid
const res = await request(app)
.get(`/SASjsApi/user/${userId}`)
@@ -652,7 +653,7 @@ describe('user', () => {
it('should respond with user along with associated groups', async () => {
const dbUser = await controller.createUser(user)
const userId = dbUser.id
const userId = dbUser.uid
const accessToken = await generateAndSaveToken(userId)
const group = {
@@ -661,7 +662,7 @@ describe('user', () => {
}
const groupController = new GroupController()
const dbGroup = await groupController.createGroup(group)
await groupController.addUserToGroup(dbGroup.groupId, dbUser.id)
await groupController.addUserToGroup(dbGroup.uid, dbUser.uid)
const res = await request(app)
.get(`/SASjsApi/user/${userId}`)
@@ -690,8 +691,10 @@ describe('user', () => {
it('should respond with Not Found if userId is incorrect', async () => {
await controller.createUser(user)
const hexValue = randomBytes(12).toString('hex')
const res = await request(app)
.get('/SASjsApi/user/1234')
.get(`/SASjsApi/user/${hexValue}`)
.auth(adminAccessToken, { type: 'bearer' })
.send()
.expect(404)
@@ -703,7 +706,7 @@ describe('user', () => {
describe('by username', () => {
it('should respond with user autoExec when same user requests', async () => {
const dbUser = await controller.createUser(user)
const userId = dbUser.id
const userId = dbUser.uid
const accessToken = await generateAndSaveToken(userId)
const res = await request(app)
@@ -803,13 +806,13 @@ describe('user', () => {
expect(res.body).toEqual([
{
id: expect.anything(),
uid: expect.anything(),
username: adminUser.username,
displayName: adminUser.displayName,
isAdmin: adminUser.isAdmin
},
{
id: expect.anything(),
uid: expect.anything(),
username: user.username,
displayName: user.displayName,
isAdmin: user.isAdmin
@@ -831,13 +834,13 @@ describe('user', () => {
expect(res.body).toEqual([
{
id: expect.anything(),
uid: expect.anything(),
username: adminUser.username,
displayName: adminUser.displayName,
isAdmin: adminUser.isAdmin
},
{
id: expect.anything(),
uid: expect.anything(),
username: 'randomUser',
displayName: user.displayName,
isAdmin: user.isAdmin
@@ -859,10 +862,10 @@ const generateSaveTokenAndCreateUser = async (
): Promise<string> => {
const dbUser = await controller.createUser(someUser ?? adminUser)
return generateAndSaveToken(dbUser.id)
return generateAndSaveToken(dbUser.uid)
}
const generateAndSaveToken = async (userId: number) => {
const generateAndSaveToken = async (userId: string) => {
const adminAccessToken = generateAccessToken({
clientId,
userId
+1 -1
View File
@@ -145,7 +145,7 @@ describe('web', () => {
expect(res.body.loggedIn).toBeTruthy()
expect(res.body.user).toEqual({
id: expect.any(Number),
uid: expect.any(String),
username: user.username,
displayName: user.displayName,
isAdmin: user.isAdmin,
+21 -9
View File
@@ -9,6 +9,7 @@ import {
deleteUserValidation,
getUserValidation,
registerUserValidation,
uidValidation,
updateUserValidation
} from '../../utils'
@@ -56,12 +57,15 @@ userRouter.get(
}
)
userRouter.get('/:userId', authenticateAccessToken, async (req, res) => {
const { userId } = req.params
userRouter.get('/:uid', authenticateAccessToken, async (req, res) => {
const { error, value: params } = uidValidation(req.params)
if (error) return res.status(400).send(error.details[0].message)
const { uid } = params
const controller = new UserController()
try {
const response = await controller.getUser(req, parseInt(userId))
const response = await controller.getUser(req, uid)
res.send(response)
} catch (err: any) {
res.status(err.code).send(err.message)
@@ -97,12 +101,16 @@ userRouter.patch(
)
userRouter.patch(
'/:userId',
'/:uid',
authenticateAccessToken,
verifyAdminIfNeeded,
async (req, res) => {
const { user } = req
const { userId } = req.params
const { error: uidError, value: params } = uidValidation(req.params)
if (uidError) return res.status(400).send(uidError.details[0].message)
const { uid } = params
// only an admin can update `isActive` and `isAdmin` fields
const { error, value: body } = updateUserValidation(req.body, user!.isAdmin)
@@ -110,7 +118,7 @@ userRouter.patch(
const controller = new UserController()
try {
const response = await controller.updateUser(parseInt(userId), body)
const response = await controller.updateUser(uid, body)
res.send(response)
} catch (err: any) {
res.status(err.code).send(err.message)
@@ -147,12 +155,16 @@ userRouter.delete(
)
userRouter.delete(
'/:userId',
'/:uid',
authenticateAccessToken,
verifyAdminIfNeeded,
async (req, res) => {
const { user } = req
const { userId } = req.params
const { error: uidError, value: params } = uidValidation(req.params)
if (uidError) return res.status(400).send(uidError.details[0].message)
const { uid } = params
// only an admin can delete user without providing password
const { error, value: data } = deleteUserValidation(req.body, user!.isAdmin)
@@ -160,7 +172,7 @@ userRouter.delete(
const controller = new UserController()
try {
await controller.deleteUser(parseInt(userId), data, user!.isAdmin)
await controller.deleteUser(uid, data, user!.isAdmin)
res.status(200).send('Account Deleted!')
} catch (err: any) {
res.status(err.code).send(err.message)
+1 -1
View File
@@ -1,4 +1,4 @@
export interface InfoJWT {
clientId: string
userId: number
userId: string
}
+1 -1
View File
@@ -1,6 +1,6 @@
export interface PreProgramVars {
username: string
userId: number
userId: string
displayName: string
serverUrl: string
httpHeaders: string[]
+1 -1
View File
@@ -1,5 +1,5 @@
export interface RequestUser {
userId: number
userId: string
clientId: string
username: string
displayName: string
+4
View File
@@ -0,0 +1,4 @@
import { randomBytes } from 'crypto'
export const randomBytesHexString = (bytesCount: number) =>
randomBytes(bytesCount).toString('hex')
+1 -1
View File
@@ -22,7 +22,7 @@ export const getPreProgramVariables = (req: Request): PreProgramVars => {
//So this is workaround.
return {
username: user ? user.username : 'demo',
userId: user ? user.userId : 0,
userId: user ? user.userId : 'demoId',
displayName: user ? user.displayName : 'demo',
serverUrl: protocol + host,
httpHeaders
-15
View File
@@ -1,15 +0,0 @@
import Counter from '../model/Counter'
export const getSequenceNextValue = async (seqName: string) => {
const seqDoc = await Counter.findOne({ id: seqName })
if (!seqDoc) {
await Counter.create({ id: seqName, seq: 1 })
return 1
}
seqDoc.seq += 1
await seqDoc.save()
return seqDoc.seq
}
+3 -3
View File
@@ -5,7 +5,7 @@ import { InfoJWT } from '../types/InfoJWT'
const isValidToken = async (
token: string,
key: string,
userId: number,
userId: string,
clientId: string
) => {
const promise = new Promise<boolean>((resolve, reject) =>
@@ -24,8 +24,8 @@ const isValidToken = async (
return await promise.then(() => true).catch(() => false)
}
export const getTokensFromDB = async (userId: number, clientId: string) => {
const user = await User.findOne({ id: userId })
export const getTokensFromDB = async (userId: string, clientId: string) => {
const user = await User.findOne({ _id: userId })
if (!user) return
const currentTokenObj = user.tokens.find(
+1 -1
View File
@@ -2,6 +2,7 @@ export * from './appStreamConfig'
export * from './connectDB'
export * from './copySASjsCore'
export * from './createWeboutSasFile'
export * from './crypto'
export * from './desktopAutoExec'
export * from './extractHeaders'
export * from './extractName'
@@ -14,7 +15,6 @@ export * from './getCertificates'
export * from './getDesktopFields'
export * from './getPreProgramVariables'
export * from './getRunTimeAndFilePath'
export * from './getSequenceNextValue'
export * from './getServerUrl'
export * from './getTokensFromDB'
export * from './instantiateLogger'
+1 -1
View File
@@ -22,7 +22,7 @@ export const isPublicRoute = async (req: Request): Promise<boolean> => {
}
export const publicUser: RequestUser = {
userId: 0,
userId: 'public_user_id',
clientId: 'public_app',
username: 'publicUser',
displayName: 'Public User',
+2 -2
View File
@@ -1,7 +1,7 @@
import User from '../model/User'
export const removeTokensInDB = async (userId: number, clientId: string) => {
const user = await User.findOne({ id: userId })
export const removeTokensInDB = async (userId: string, clientId: string) => {
const user = await User.findOne({ _id: userId })
if (!user) return
const tokenObjIndex = user.tokens.findIndex(
+2 -2
View File
@@ -1,12 +1,12 @@
import User from '../model/User'
export const saveTokensInDB = async (
userId: number,
userId: string,
clientId: string,
accessToken: string,
refreshToken: string
) => {
const user = await User.findOne({ id: userId })
const user = await User.findOne({ _id: userId })
if (!user) return
const currentTokenObj = user.tokens.find(
+1 -1
View File
@@ -82,7 +82,7 @@ export const seedDB = async (): Promise<ConfigurationType> => {
}
export const ALL_USERS_GROUP = {
name: 'AllUsers',
name: 'all-users',
description: 'Group contains all users'
}
+6 -1
View File
@@ -12,6 +12,11 @@ const groupnameSchema = Joi.string().lowercase().alphanum().min(3).max(16)
export const blockFileRegex = /\.(exe|sh|htaccess)$/i
export const uidValidation = (data: any) =>
Joi.object({
uid: Joi.string().length(24).hex().required()
}).validate(data)
export const getUserValidation = (data: any): Joi.ValidationResult =>
Joi.object({
username: usernameSchema.required()
@@ -113,7 +118,7 @@ export const registerPermissionValidation = (data: any): Joi.ValidationResult =>
principalType: Joi.string()
.required()
.valid(...Object.values(PrincipalType)),
principalId: Joi.number().required()
principalId: Joi.string().length(24).hex().required()
}).validate(data)
export const updatePermissionValidation = (data: any): Joi.ValidationResult =>
+4 -4
View File
@@ -4,7 +4,7 @@ import { RequestUser } from '../types'
export const fetchLatestAutoExec = async (
reqUser: RequestUser
): Promise<RequestUser | undefined> => {
const dbUser = await User.findOne({ id: reqUser.userId })
const dbUser = await User.findOne({ _id: reqUser.userId })
if (!dbUser) return undefined
@@ -21,12 +21,12 @@ export const fetchLatestAutoExec = async (
}
export const verifyTokenInDB = async (
userId: number,
userId: string,
clientId: string,
token: string,
tokenType: 'accessToken' | 'refreshToken'
): Promise<RequestUser | undefined> => {
const dbUser = await User.findOne({ id: userId })
const dbUser = await User.findOne({ _id: userId })
if (!dbUser) return undefined
@@ -36,7 +36,7 @@ export const verifyTokenInDB = async (
return currentTokenObj?.[tokenType] === token
? {
userId: dbUser.id,
userId: dbUser.uid,
clientId,
username: dbUser.username,
displayName: dbUser.displayName,
+13 -11
View File
@@ -20,7 +20,7 @@
"@types/jest": "^26.0.24",
"@types/node": "^12.20.28",
"@types/react": "^17.0.27",
"axios": "^1.12.2",
"axios": "1.12.2",
"monaco-editor": "^0.33.0",
"react": "^17.0.2",
"react-copy-to-clipboard": "^5.1.0",
@@ -56,7 +56,7 @@
"html-webpack-plugin": "5.5.0",
"monaco-editor-webpack-plugin": "^7.0.1",
"path": "0.12.7",
"prettier": "^2.4.1",
"prettier": "^3.0.3",
"sass": "^1.44.0",
"sass-loader": "^12.3.0",
"style-loader": "^3.3.1",
@@ -4334,7 +4334,6 @@
"version": "1.12.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
"integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.4",
@@ -9713,15 +9712,18 @@
}
},
"node_modules/prettier": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.1.tgz",
"integrity": "sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==",
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz",
"integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==",
"dev": true,
"bin": {
"prettier": "bin-prettier.js"
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=10.13.0"
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/pretty-error": {
@@ -19127,9 +19129,9 @@
"dev": true
},
"prettier": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.1.tgz",
"integrity": "sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==",
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz",
"integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==",
"dev": true
},
"pretty-error": {
+5 -3
View File
@@ -4,7 +4,9 @@
"private": true,
"scripts": {
"start": "webpack-dev-server --config webpack.dev.ts --hot",
"build": "webpack --config webpack.prod.ts"
"build": "webpack --config webpack.prod.ts",
"lint": "npx prettier --check \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"",
"lint:fix": "npx prettier --write \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\""
},
"dependencies": {
"@emotion/react": "^11.4.1",
@@ -19,7 +21,7 @@
"@types/jest": "^26.0.24",
"@types/node": "^12.20.28",
"@types/react": "^17.0.27",
"axios": "^1.12.2",
"axios": "1.12.2",
"monaco-editor": "^0.33.0",
"react": "^17.0.2",
"react-copy-to-clipboard": "^5.1.0",
@@ -55,7 +57,7 @@
"html-webpack-plugin": "5.5.0",
"monaco-editor-webpack-plugin": "^7.0.1",
"path": "0.12.7",
"prettier": "^2.4.1",
"prettier": "^3.0.3",
"sass": "^1.44.0",
"sass-loader": "^12.3.0",
"style-loader": "^3.3.1",
+1 -1
View File
@@ -40,7 +40,7 @@ const Login = () => {
})
if (loggedIn) {
appContext.setUserId?.(user.id)
appContext.setUserId?.(user.uid)
appContext.setUsername?.(user.username)
appContext.setDisplayName?.(user.displayName)
appContext.setIsAdmin?.(user.isAdmin)
@@ -99,8 +99,8 @@ const AddPermissionModal = ({
principalType: principalType.toLowerCase(),
principalId:
principalType.toLowerCase() === 'user'
? userPrincipal?.id
: groupPrincipal?.groupId
? userPrincipal?.uid
: groupPrincipal?.uid
}
permissions.push(addPermissionPayload)
@@ -61,7 +61,7 @@ const PermissionTable = ({
</TableHead>
<TableBody>
{permissions.map((permission) => (
<TableRow key={permission.permissionId}>
<TableRow key={permission.uid}>
<BootstrapTableCell>{permission.path}</BootstrapTableCell>
<BootstrapTableCell>{permission.type}</BootstrapTableCell>
<BootstrapTableCell>
@@ -69,7 +69,7 @@ const useAddPermission = () => {
for (const permission of updatingPermissions) {
await axios
.patch(`/SASjsApi/permission/${permission.permissionId}`, {
.patch(`/SASjsApi/permission/${permission.uid}`, {
setting: permission.setting === 'Grant' ? 'Deny' : 'Grant'
})
.then((res) => {
@@ -24,7 +24,7 @@ const useDeletePermissionModal = () => {
setDeleteConfirmationModalOpen(false)
setIsLoading(true)
axios
.delete(`/SASjsApi/permission/${selectedPermission?.permissionId}`)
.delete(`/SASjsApi/permission/${selectedPermission?.uid}`)
.then((res: any) => {
fetchPermissions()
setSnackbarMessage('Permission deleted!')
@@ -62,21 +62,17 @@ const useFilterPermissions = () => {
: permissions
let filteredArray = uriFilteredPermissions.filter((permission) =>
principalFilteredPermissions.some(
(item) => item.permissionId === permission.permissionId
)
principalFilteredPermissions.some((item) => item.uid === permission.uid)
)
filteredArray = filteredArray.filter((permission) =>
principalTypeFilteredPermissions.some(
(item) => item.permissionId === permission.permissionId
(item) => item.uid === permission.uid
)
)
filteredArray = filteredArray.filter((permission) =>
settingFilteredPermissions.some(
(item) => item.permissionId === permission.permissionId
)
settingFilteredPermissions.some((item) => item.uid === permission.uid)
)
setFilteredPermissions(filteredArray)
@@ -24,7 +24,7 @@ const useUpdatePermissionModal = () => {
setUpdatePermissionModalOpen(false)
setIsLoading(true)
axios
.patch(`/SASjsApi/permission/${selectedPermission?.permissionId}`, {
.patch(`/SASjsApi/permission/${selectedPermission?.uid}`, {
setting
})
.then((res: any) => {
+14 -12
View File
@@ -26,18 +26,20 @@ const Profile = () => {
const [isPasswordModalOpen, setIsPasswordModalOpen] = useState(false)
useEffect(() => {
setIsLoading(true)
axios
.get(`/SASjsApi/user/${appContext.userId}`)
.then((res: any) => {
setUser(res.data)
})
.catch((err) => {
console.log(err)
})
.finally(() => {
setIsLoading(false)
})
if (appContext.userId) {
setIsLoading(true)
axios
.get(`/SASjsApi/user/${appContext.userId}`)
.then((res: any) => {
setUser(res.data)
})
.catch((err) => {
console.log(err)
})
.finally(() => {
setIsLoading(false)
})
}
}, [appContext.userId])
const handleChange = (event: any) => {
+12 -19
View File
@@ -24,39 +24,32 @@ export enum RunTimeType {
interface AppContextProps {
checkingSession: boolean
loggedIn: boolean
setLoggedIn: Dispatch<SetStateAction<boolean>> | null
setLoggedIn?: Dispatch<SetStateAction<boolean>>
needsToUpdatePassword: boolean
setNeedsToUpdatePassword: Dispatch<SetStateAction<boolean>> | null
userId: number
setUserId: Dispatch<SetStateAction<number>> | null
setNeedsToUpdatePassword?: Dispatch<SetStateAction<boolean>>
userId?: string
setUserId?: Dispatch<SetStateAction<string | undefined>>
username: string
setUsername: Dispatch<SetStateAction<string>> | null
setUsername?: Dispatch<SetStateAction<string>>
displayName: string
setDisplayName: Dispatch<SetStateAction<string>> | null
setDisplayName?: Dispatch<SetStateAction<string>>
isAdmin: boolean
setIsAdmin: Dispatch<SetStateAction<boolean>> | null
setIsAdmin?: Dispatch<SetStateAction<boolean>>
mode: ModeType
runTimes: RunTimeType[]
logout: (() => void) | null
logout?: () => void
}
export const AppContext = createContext<AppContextProps>({
checkingSession: false,
loggedIn: false,
setLoggedIn: null,
needsToUpdatePassword: false,
setNeedsToUpdatePassword: null,
userId: 0,
setUserId: null,
userId: '',
username: '',
setUsername: null,
displayName: '',
setDisplayName: null,
isAdmin: false,
setIsAdmin: null,
mode: ModeType.Server,
runTimes: [],
logout: null
runTimes: []
})
const AppContextProvider = (props: { children: ReactNode }) => {
@@ -64,7 +57,7 @@ const AppContextProvider = (props: { children: ReactNode }) => {
const [checkingSession, setCheckingSession] = useState(false)
const [loggedIn, setLoggedIn] = useState(false)
const [needsToUpdatePassword, setNeedsToUpdatePassword] = useState(false)
const [userId, setUserId] = useState(0)
const [userId, setUserId] = useState<string>()
const [username, setUsername] = useState('')
const [displayName, setDisplayName] = useState('')
const [isAdmin, setIsAdmin] = useState(false)
@@ -79,7 +72,7 @@ const AppContextProvider = (props: { children: ReactNode }) => {
.then((res) => res.data)
.then((data: any) => {
setCheckingSession(false)
setUserId(data.id)
setUserId(data.uid)
setUsername(data.username)
setDisplayName(data.displayName)
setIsAdmin(data.isAdmin)
+4 -4
View File
@@ -6,13 +6,13 @@ export const findExistingPermission = (
) => {
for (const permission of existingPermissions) {
if (
permission.user?.id === newPermission.principalId &&
permission.user?.uid === newPermission.principalId &&
hasSameCombination(permission, newPermission)
)
return permission
if (
permission.group?.groupId === newPermission.principalId &&
permission.group?.uid === newPermission.principalId &&
hasSameCombination(permission, newPermission)
)
return permission
@@ -27,13 +27,13 @@ export const findUpdatingPermission = (
) => {
for (const permission of existingPermissions) {
if (
permission.user?.id === newPermission.principalId &&
permission.user?.uid === newPermission.principalId &&
hasDifferentSetting(permission, newPermission)
)
return permission
if (
permission.group?.groupId === newPermission.principalId &&
permission.group?.uid === newPermission.principalId &&
hasDifferentSetting(permission, newPermission)
)
return permission
+4 -4
View File
@@ -1,12 +1,12 @@
export interface UserResponse {
id: number
uid: string
username: string
displayName: string
isAdmin: boolean
}
export interface GroupResponse {
groupId: number
uid: string
name: string
description: string
}
@@ -17,7 +17,7 @@ export interface GroupDetailsResponse extends GroupResponse {
}
export interface PermissionResponse {
permissionId: number
uid: string
path: string
type: string
setting: string
@@ -30,7 +30,7 @@ export interface RegisterPermissionPayload {
type: string
setting: string
principalType: string
principalId: number
principalId: string
}
export interface TreeNode {