Merge pull request #1728 from specklesystems/gergo/serverGuest/main
Server Guest Role
This commit is contained in:
@@ -333,6 +333,7 @@ jobs:
|
|||||||
command: server /data --console-address ":9001"
|
command: server /data --console-address ":9001"
|
||||||
# environment:
|
# environment:
|
||||||
|
|
||||||
|
resource_class: large
|
||||||
environment:
|
environment:
|
||||||
NODE_ENV: test
|
NODE_ENV: test
|
||||||
DATABASE_URL: 'postgres://speckle:speckle@127.0.0.1:5432/speckle2_test'
|
DATABASE_URL: 'postgres://speckle:speckle@127.0.0.1:5432/speckle2_test'
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ const { spawn } = require('child_process')
|
|||||||
const ServerAPI = require('../ifc/api')
|
const ServerAPI = require('../ifc/api')
|
||||||
const objDependencies = require('./objDependencies')
|
const objDependencies = require('./objDependencies')
|
||||||
const { logger } = require('../observability/logging')
|
const { logger } = require('../observability/logging')
|
||||||
|
const { Scopes } = require('@speckle/shared')
|
||||||
|
|
||||||
const HEALTHCHECK_FILE_PATH = '/tmp/last_successful_query'
|
const HEALTHCHECK_FILE_PATH = '/tmp/last_successful_query'
|
||||||
|
|
||||||
@@ -97,7 +98,7 @@ async function doTask(task) {
|
|||||||
const { token } = await serverApi.createToken({
|
const { token } = await serverApi.createToken({
|
||||||
userId: info.userId,
|
userId: info.userId,
|
||||||
name: 'temp upload token',
|
name: 'temp upload token',
|
||||||
scopes: ['streams:write', 'streams:read'],
|
scopes: [Scopes.Streams.Write, Scopes.Streams.Read],
|
||||||
lifespan: 1000000
|
lifespan: 1000000
|
||||||
})
|
})
|
||||||
tempUserToken = token
|
tempUserToken = token
|
||||||
|
|||||||
@@ -143,7 +143,8 @@ export default {
|
|||||||
roleLookupTable: {
|
roleLookupTable: {
|
||||||
[Roles.Server.User]: 'User',
|
[Roles.Server.User]: 'User',
|
||||||
[Roles.Server.Admin]: 'Admin',
|
[Roles.Server.Admin]: 'Admin',
|
||||||
[Roles.Server.ArchivedUser]: 'Archived'
|
[Roles.Server.ArchivedUser]: 'Archived',
|
||||||
|
[Roles.Server.Guest]: 'Guest'
|
||||||
},
|
},
|
||||||
adminUsers: {
|
adminUsers: {
|
||||||
items: [],
|
items: [],
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ extend type Query {
|
|||||||
Get authed user's stream access request
|
Get authed user's stream access request
|
||||||
"""
|
"""
|
||||||
streamAccessRequest(streamId: String!): StreamAccessRequest
|
streamAccessRequest(streamId: String!): StreamAccessRequest
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
}
|
}
|
||||||
|
|
||||||
extend type Stream {
|
extend type Stream {
|
||||||
@@ -21,13 +21,13 @@ extend type Mutation {
|
|||||||
requestId: String!
|
requestId: String!
|
||||||
accept: Boolean!
|
accept: Boolean!
|
||||||
role: StreamRole! = STREAM_CONTRIBUTOR
|
role: StreamRole! = STREAM_CONTRIBUTOR
|
||||||
): Boolean! @hasRole(role: "server:user") @hasScope(scope: "users:invite")
|
): Boolean! @hasServerRole(role: SERVER_GUEST) @hasScope(scope: "users:invite")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Request access to a specific stream
|
Request access to a specific stream
|
||||||
"""
|
"""
|
||||||
streamAccessRequestCreate(streamId: String!): StreamAccessRequest!
|
streamAccessRequestCreate(streamId: String!): StreamAccessRequest!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "users:invite")
|
@hasScope(scope: "users:invite")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ extend type User {
|
|||||||
before: DateTime
|
before: DateTime
|
||||||
cursor: DateTime
|
cursor: DateTime
|
||||||
limit: Int! = 25
|
limit: Int! = 25
|
||||||
): ActivityCollection @hasRole(role: "server:user") @hasScope(scope: "users:read")
|
): ActivityCollection
|
||||||
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
|
@hasScope(scope: "users:read")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
The user's timeline in chronological order
|
The user's timeline in chronological order
|
||||||
@@ -19,7 +21,7 @@ extend type User {
|
|||||||
cursor: DateTime
|
cursor: DateTime
|
||||||
limit: Int! = 25
|
limit: Int! = 25
|
||||||
): ActivityCollection
|
): ActivityCollection
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScopes(scopes: ["users:read", "streams:read"])
|
@hasScopes(scopes: ["users:read", "streams:read"])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,7 +35,9 @@ extend type LimitedUser {
|
|||||||
before: DateTime
|
before: DateTime
|
||||||
cursor: DateTime
|
cursor: DateTime
|
||||||
limit: Int! = 25
|
limit: Int! = 25
|
||||||
): ActivityCollection @hasRole(role: "server:user") @hasScope(scope: "users:read")
|
): ActivityCollection
|
||||||
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
|
@hasScope(scope: "users:read")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
The user's timeline in chronological order
|
The user's timeline in chronological order
|
||||||
@@ -44,7 +48,7 @@ extend type LimitedUser {
|
|||||||
cursor: DateTime
|
cursor: DateTime
|
||||||
limit: Int! = 25
|
limit: Int! = 25
|
||||||
): ActivityCollection
|
): ActivityCollection
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScopes(scopes: ["users:read", "streams:read"])
|
@hasScopes(scopes: ["users:read", "streams:read"])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +62,9 @@ extend type Stream {
|
|||||||
before: DateTime
|
before: DateTime
|
||||||
cursor: DateTime
|
cursor: DateTime
|
||||||
limit: Int! = 25
|
limit: Int! = 25
|
||||||
): ActivityCollection @hasRole(role: "server:user") @hasScope(scope: "streams:read")
|
): ActivityCollection
|
||||||
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
|
@hasScope(scope: "streams:read")
|
||||||
}
|
}
|
||||||
|
|
||||||
extend type Branch {
|
extend type Branch {
|
||||||
@@ -71,7 +77,9 @@ extend type Branch {
|
|||||||
before: DateTime
|
before: DateTime
|
||||||
cursor: DateTime
|
cursor: DateTime
|
||||||
limit: Int! = 25
|
limit: Int! = 25
|
||||||
): ActivityCollection @hasRole(role: "server:user") @hasScope(scope: "streams:read")
|
): ActivityCollection
|
||||||
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
|
@hasScope(scope: "streams:read")
|
||||||
}
|
}
|
||||||
|
|
||||||
extend type Commit {
|
extend type Commit {
|
||||||
@@ -84,7 +92,9 @@ extend type Commit {
|
|||||||
before: DateTime
|
before: DateTime
|
||||||
cursor: DateTime
|
cursor: DateTime
|
||||||
limit: Int! = 25
|
limit: Int! = 25
|
||||||
): ActivityCollection @hasRole(role: "server:user") @hasScope(scope: "streams:read")
|
): ActivityCollection
|
||||||
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
|
@hasScope(scope: "streams:read")
|
||||||
}
|
}
|
||||||
|
|
||||||
type ActivityCollection {
|
type ActivityCollection {
|
||||||
|
|||||||
@@ -47,13 +47,15 @@ extend type User {
|
|||||||
Returns the apps you have authorized.
|
Returns the apps you have authorized.
|
||||||
"""
|
"""
|
||||||
authorizedApps: [ServerAppListItem]
|
authorizedApps: [ServerAppListItem]
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "apps:read")
|
@hasScope(scope: "apps:read")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Returns the apps you have created.
|
Returns the apps you have created.
|
||||||
"""
|
"""
|
||||||
createdApps: [ServerApp!] @hasRole(role: "server:user") @hasScope(scope: "apps:read")
|
createdApps: [ServerApp!]
|
||||||
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
|
@hasScope(scope: "apps:read")
|
||||||
}
|
}
|
||||||
|
|
||||||
extend type Mutation {
|
extend type Mutation {
|
||||||
@@ -61,28 +63,28 @@ extend type Mutation {
|
|||||||
Register a new third party application.
|
Register a new third party application.
|
||||||
"""
|
"""
|
||||||
appCreate(app: AppCreateInput!): String!
|
appCreate(app: AppCreateInput!): String!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_USER)
|
||||||
@hasScope(scope: "apps:write")
|
@hasScope(scope: "apps:write")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Update an existing third party application. **Note: This will invalidate all existing tokens, refresh tokens and access codes and will require existing users to re-authorize it.**
|
Update an existing third party application. **Note: This will invalidate all existing tokens, refresh tokens and access codes and will require existing users to re-authorize it.**
|
||||||
"""
|
"""
|
||||||
appUpdate(app: AppUpdateInput!): Boolean!
|
appUpdate(app: AppUpdateInput!): Boolean!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_USER)
|
||||||
@hasScope(scope: "apps:write")
|
@hasScope(scope: "apps:write")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Deletes a thirty party application.
|
Deletes a thirty party application.
|
||||||
"""
|
"""
|
||||||
appDelete(appId: String!): Boolean!
|
appDelete(appId: String!): Boolean!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_USER)
|
||||||
@hasScope(scope: "apps:write")
|
@hasScope(scope: "apps:write")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Revokes (de-authorizes) an application that you have previously authorized.
|
Revokes (de-authorizes) an application that you have previously authorized.
|
||||||
"""
|
"""
|
||||||
appRevokeAccess(appId: String!): Boolean
|
appRevokeAccess(appId: String!): Boolean
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "apps:write")
|
@hasScope(scope: "apps:write")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -324,7 +324,7 @@ type CommentMutations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extend type Mutation {
|
extend type Mutation {
|
||||||
commentMutations: CommentMutations! @hasServerRole(role: SERVER_USER)
|
commentMutations: CommentMutations! @hasServerRole(role: SERVER_GUEST)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Used for broadcasting real time chat head bubbles and status. Does not persist any info.
|
Used for broadcasting real time chat head bubbles and status. Does not persist any info.
|
||||||
@@ -334,7 +334,7 @@ extend type Mutation {
|
|||||||
resourceId: String!
|
resourceId: String!
|
||||||
data: JSONObject
|
data: JSONObject
|
||||||
): Boolean!
|
): Boolean!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@deprecated(reason: "Use broadcastViewerUserActivity")
|
@deprecated(reason: "Use broadcastViewerUserActivity")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -345,14 +345,14 @@ extend type Mutation {
|
|||||||
commentId: String!
|
commentId: String!
|
||||||
data: JSONObject
|
data: JSONObject
|
||||||
): Boolean!
|
): Boolean!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@deprecated(reason: "Use broadcastViewerUserActivity")
|
@deprecated(reason: "Use broadcastViewerUserActivity")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Creates a comment
|
Creates a comment
|
||||||
"""
|
"""
|
||||||
commentCreate(input: CommentCreateInput!): String!
|
commentCreate(input: CommentCreateInput!): String!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "streams:read")
|
@hasScope(scope: "streams:read")
|
||||||
@deprecated(reason: "Use commentMutations version")
|
@deprecated(reason: "Use commentMutations version")
|
||||||
|
|
||||||
@@ -360,7 +360,7 @@ extend type Mutation {
|
|||||||
Flags a comment as viewed by you (the logged in user).
|
Flags a comment as viewed by you (the logged in user).
|
||||||
"""
|
"""
|
||||||
commentView(streamId: String!, commentId: String!): Boolean!
|
commentView(streamId: String!, commentId: String!): Boolean!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "streams:read")
|
@hasScope(scope: "streams:read")
|
||||||
@deprecated(reason: "Use commentMutations version")
|
@deprecated(reason: "Use commentMutations version")
|
||||||
|
|
||||||
@@ -372,7 +372,7 @@ extend type Mutation {
|
|||||||
commentId: String!
|
commentId: String!
|
||||||
archived: Boolean! = true
|
archived: Boolean! = true
|
||||||
): Boolean!
|
): Boolean!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "streams:read")
|
@hasScope(scope: "streams:read")
|
||||||
@deprecated(reason: "Use commentMutations version")
|
@deprecated(reason: "Use commentMutations version")
|
||||||
|
|
||||||
@@ -380,7 +380,7 @@ extend type Mutation {
|
|||||||
Edits a comment.
|
Edits a comment.
|
||||||
"""
|
"""
|
||||||
commentEdit(input: CommentEditInput!): Boolean!
|
commentEdit(input: CommentEditInput!): Boolean!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "streams:read")
|
@hasScope(scope: "streams:read")
|
||||||
@deprecated(reason: "Use commentMutations version")
|
@deprecated(reason: "Use commentMutations version")
|
||||||
|
|
||||||
@@ -388,7 +388,7 @@ extend type Mutation {
|
|||||||
Adds a reply to a comment.
|
Adds a reply to a comment.
|
||||||
"""
|
"""
|
||||||
commentReply(input: ReplyCreateInput!): String!
|
commentReply(input: ReplyCreateInput!): String!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "streams:read")
|
@hasScope(scope: "streams:read")
|
||||||
@deprecated(reason: "Use commentMutations version")
|
@deprecated(reason: "Use commentMutations version")
|
||||||
}
|
}
|
||||||
@@ -457,7 +457,7 @@ extend type Subscription {
|
|||||||
- for a specific resource/set of resources: pass in a list of resourceIds (commit or object ids); this sub will get called when *any* of the resources provided get a comment.
|
- for a specific resource/set of resources: pass in a list of resourceIds (commit or object ids); this sub will get called when *any* of the resources provided get a comment.
|
||||||
"""
|
"""
|
||||||
commentActivity(streamId: String!, resourceIds: [String]): CommentActivityMessage!
|
commentActivity(streamId: String!, resourceIds: [String]): CommentActivityMessage!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "streams:read")
|
@hasScope(scope: "streams:read")
|
||||||
@deprecated(reason: "Use projectCommentsUpdated")
|
@deprecated(reason: "Use projectCommentsUpdated")
|
||||||
|
|
||||||
@@ -470,7 +470,7 @@ extend type Subscription {
|
|||||||
streamId: String!
|
streamId: String!
|
||||||
commentId: String!
|
commentId: String!
|
||||||
): CommentThreadActivityMessage!
|
): CommentThreadActivityMessage!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "streams:read")
|
@hasScope(scope: "streams:read")
|
||||||
@deprecated(
|
@deprecated(
|
||||||
reason: "Use projectCommentsUpdated or viewerUserActivityBroadcasted for reply status"
|
reason: "Use projectCommentsUpdated or viewerUserActivityBroadcasted for reply status"
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ extend type Mutation {
|
|||||||
projectId: String!
|
projectId: String!
|
||||||
resourceIdString: String!
|
resourceIdString: String!
|
||||||
message: ViewerUserActivityMessageInput!
|
message: ViewerUserActivityMessageInput!
|
||||||
): Boolean! @hasServerRole(role: SERVER_USER)
|
): Boolean! @hasServerRole(role: SERVER_GUEST)
|
||||||
}
|
}
|
||||||
|
|
||||||
extend type Subscription {
|
extend type Subscription {
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ extend type User {
|
|||||||
"""
|
"""
|
||||||
Returns a list of your personal api tokens.
|
Returns a list of your personal api tokens.
|
||||||
"""
|
"""
|
||||||
apiTokens: [ApiToken] @hasRole(role: "server:user") @hasScope(scope: "tokens:read")
|
apiTokens: [ApiToken]
|
||||||
|
@hasServerRole(role: SERVER_USER)
|
||||||
|
@hasScope(scope: "tokens:read")
|
||||||
}
|
}
|
||||||
|
|
||||||
type ApiToken {
|
type ApiToken {
|
||||||
@@ -26,12 +28,12 @@ extend type Mutation {
|
|||||||
Creates an personal api token.
|
Creates an personal api token.
|
||||||
"""
|
"""
|
||||||
apiTokenCreate(token: ApiTokenCreateInput!): String!
|
apiTokenCreate(token: ApiTokenCreateInput!): String!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_USER)
|
||||||
@hasScope(scope: "tokens:write")
|
@hasScope(scope: "tokens:write")
|
||||||
"""
|
"""
|
||||||
Revokes (deletes) an personal api token.
|
Revokes (deletes) an personal api token.
|
||||||
"""
|
"""
|
||||||
apiTokenRevoke(token: String!): Boolean!
|
apiTokenRevoke(token: String!): Boolean!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_USER)
|
||||||
@hasScope(scope: "tokens:write")
|
@hasScope(scope: "tokens:write")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ type Commit {
|
|||||||
Will throw an authorization error if active user isn't authorized to see it, for example,
|
Will throw an authorization error if active user isn't authorized to see it, for example,
|
||||||
if a stream isn't public and the user doesn't have the appropriate rights.
|
if a stream isn't public and the user doesn't have the appropriate rights.
|
||||||
"""
|
"""
|
||||||
stream: Stream! @hasRole(role: "server:user") @hasScope(scope: "streams:read")
|
stream: Stream! @hasServerRole(role: SERVER_GUEST) @hasScope(scope: "streams:read")
|
||||||
}
|
}
|
||||||
|
|
||||||
type BranchCollection {
|
type BranchCollection {
|
||||||
@@ -65,40 +65,40 @@ type CommitCollection {
|
|||||||
|
|
||||||
extend type Mutation {
|
extend type Mutation {
|
||||||
branchCreate(branch: BranchCreateInput!): String!
|
branchCreate(branch: BranchCreateInput!): String!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "streams:write")
|
@hasScope(scope: "streams:write")
|
||||||
branchUpdate(branch: BranchUpdateInput!): Boolean!
|
branchUpdate(branch: BranchUpdateInput!): Boolean!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "streams:write")
|
@hasScope(scope: "streams:write")
|
||||||
branchDelete(branch: BranchDeleteInput!): Boolean!
|
branchDelete(branch: BranchDeleteInput!): Boolean!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "streams:write")
|
@hasScope(scope: "streams:write")
|
||||||
|
|
||||||
commitCreate(commit: CommitCreateInput!): String!
|
commitCreate(commit: CommitCreateInput!): String!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "streams:write")
|
@hasScope(scope: "streams:write")
|
||||||
commitUpdate(commit: CommitUpdateInput!): Boolean!
|
commitUpdate(commit: CommitUpdateInput!): Boolean!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "streams:write")
|
@hasScope(scope: "streams:write")
|
||||||
commitReceive(input: CommitReceivedInput!): Boolean!
|
commitReceive(input: CommitReceivedInput!): Boolean!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "streams:read")
|
@hasScope(scope: "streams:read")
|
||||||
commitDelete(commit: CommitDeleteInput!): Boolean!
|
commitDelete(commit: CommitDeleteInput!): Boolean!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "streams:write")
|
@hasScope(scope: "streams:write")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Move a batch of commits to a new branch
|
Move a batch of commits to a new branch
|
||||||
"""
|
"""
|
||||||
commitsMove(input: CommitsMoveInput!): Boolean!
|
commitsMove(input: CommitsMoveInput!): Boolean!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "streams:write")
|
@hasScope(scope: "streams:write")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Delete a batch of commits
|
Delete a batch of commits
|
||||||
"""
|
"""
|
||||||
commitsDelete(input: CommitsDeleteInput!): Boolean!
|
commitsDelete(input: CommitsDeleteInput!): Boolean!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "streams:write")
|
@hasScope(scope: "streams:write")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,38 +108,38 @@ extend type Subscription {
|
|||||||
Subscribe to branch created event
|
Subscribe to branch created event
|
||||||
"""
|
"""
|
||||||
branchCreated(streamId: String!): JSONObject
|
branchCreated(streamId: String!): JSONObject
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "streams:read")
|
@hasScope(scope: "streams:read")
|
||||||
"""
|
"""
|
||||||
Subscribe to branch updated event.
|
Subscribe to branch updated event.
|
||||||
"""
|
"""
|
||||||
branchUpdated(streamId: String!, branchId: String): JSONObject
|
branchUpdated(streamId: String!, branchId: String): JSONObject
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "streams:read")
|
@hasScope(scope: "streams:read")
|
||||||
"""
|
"""
|
||||||
Subscribe to branch deleted event
|
Subscribe to branch deleted event
|
||||||
"""
|
"""
|
||||||
branchDeleted(streamId: String!): JSONObject
|
branchDeleted(streamId: String!): JSONObject
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "streams:read")
|
@hasScope(scope: "streams:read")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Subscribe to commit created event
|
Subscribe to commit created event
|
||||||
"""
|
"""
|
||||||
commitCreated(streamId: String!): JSONObject
|
commitCreated(streamId: String!): JSONObject
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "streams:read")
|
@hasScope(scope: "streams:read")
|
||||||
"""
|
"""
|
||||||
Subscribe to commit updated event.
|
Subscribe to commit updated event.
|
||||||
"""
|
"""
|
||||||
commitUpdated(streamId: String!, commitId: String): JSONObject
|
commitUpdated(streamId: String!, commitId: String): JSONObject
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "streams:read")
|
@hasScope(scope: "streams:read")
|
||||||
"""
|
"""
|
||||||
Subscribe to commit deleted event
|
Subscribe to commit deleted event
|
||||||
"""
|
"""
|
||||||
commitDeleted(streamId: String!): JSONObject
|
commitDeleted(streamId: String!): JSONObject
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "streams:read")
|
@hasScope(scope: "streams:read")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -164,11 +164,11 @@ type VersionMutations {
|
|||||||
|
|
||||||
extend type Mutation {
|
extend type Mutation {
|
||||||
modelMutations: ModelMutations!
|
modelMutations: ModelMutations!
|
||||||
@hasServerRole(role: SERVER_USER)
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "streams:write")
|
@hasScope(scope: "streams:write")
|
||||||
|
|
||||||
versionMutations: VersionMutations!
|
versionMutations: VersionMutations!
|
||||||
@hasServerRole(role: SERVER_USER)
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "streams:write")
|
@hasScope(scope: "streams:write")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ type ServerInfo {
|
|||||||
roles: [Role]!
|
roles: [Role]!
|
||||||
scopes: [Scope]!
|
scopes: [Scope]!
|
||||||
inviteOnly: Boolean
|
inviteOnly: Boolean
|
||||||
|
guestModeEnabled: Boolean!
|
||||||
version: String
|
version: String
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +38,7 @@ type Scope {
|
|||||||
|
|
||||||
extend type Mutation {
|
extend type Mutation {
|
||||||
serverInfoUpdate(info: ServerInfoUpdateInput!): Boolean
|
serverInfoUpdate(info: ServerInfoUpdateInput!): Boolean
|
||||||
@hasRole(role: "server:admin")
|
@hasServerRole(role: SERVER_ADMIN)
|
||||||
@hasScope(scope: "server:setup")
|
@hasScope(scope: "server:setup")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,4 +49,5 @@ input ServerInfoUpdateInput {
|
|||||||
adminContact: String
|
adminContact: String
|
||||||
termsOfService: String
|
termsOfService: String
|
||||||
inviteOnly: Boolean
|
inviteOnly: Boolean
|
||||||
|
guestModeEnabled: Boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ extend type Query {
|
|||||||
Pass in the `query` parameter to search by name, description or ID.
|
Pass in the `query` parameter to search by name, description or ID.
|
||||||
"""
|
"""
|
||||||
streams(query: String, limit: Int = 25, cursor: String): StreamCollection
|
streams(query: String, limit: Int = 25, cursor: String): StreamCollection
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "streams:read")
|
@hasScope(scope: "streams:read")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -23,7 +23,7 @@ extend type Query {
|
|||||||
visibility: String
|
visibility: String
|
||||||
limit: Int = 25
|
limit: Int = 25
|
||||||
): StreamCollection
|
): StreamCollection
|
||||||
@hasRole(role: "server:admin")
|
@hasServerRole(role: SERVER_ADMIN)
|
||||||
@deprecated(reason: "use admin.projectList instead")
|
@deprecated(reason: "use admin.projectList instead")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -79,7 +79,7 @@ extend type User {
|
|||||||
authenticated user, then this will only return discoverable streams.
|
authenticated user, then this will only return discoverable streams.
|
||||||
"""
|
"""
|
||||||
streams(limit: Int! = 25, cursor: String): StreamCollection!
|
streams(limit: Int! = 25, cursor: String): StreamCollection!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "streams:read")
|
@hasScope(scope: "streams:read")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -87,7 +87,7 @@ extend type User {
|
|||||||
Note: You can't use this to retrieve another user's favorite streams.
|
Note: You can't use this to retrieve another user's favorite streams.
|
||||||
"""
|
"""
|
||||||
favoriteStreams(limit: Int! = 25, cursor: String): StreamCollection!
|
favoriteStreams(limit: Int! = 25, cursor: String): StreamCollection!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "streams:read")
|
@hasScope(scope: "streams:read")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -101,7 +101,7 @@ extend type LimitedUser {
|
|||||||
Returns all discoverable streams that the user is a collaborator on
|
Returns all discoverable streams that the user is a collaborator on
|
||||||
"""
|
"""
|
||||||
streams(limit: Int! = 25, cursor: String): StreamCollection!
|
streams(limit: Int! = 25, cursor: String): StreamCollection!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "streams:read")
|
@hasScope(scope: "streams:read")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -152,43 +152,43 @@ extend type Mutation {
|
|||||||
Creates a new stream.
|
Creates a new stream.
|
||||||
"""
|
"""
|
||||||
streamCreate(stream: StreamCreateInput!): String
|
streamCreate(stream: StreamCreateInput!): String
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_USER)
|
||||||
@hasScope(scope: "streams:write")
|
@hasScope(scope: "streams:write")
|
||||||
"""
|
"""
|
||||||
Updates an existing stream.
|
Updates an existing stream.
|
||||||
"""
|
"""
|
||||||
streamUpdate(stream: StreamUpdateInput!): Boolean!
|
streamUpdate(stream: StreamUpdateInput!): Boolean!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_USER)
|
||||||
@hasScope(scope: "streams:write")
|
@hasScope(scope: "streams:write")
|
||||||
"""
|
"""
|
||||||
Deletes an existing stream.
|
Deletes an existing stream.
|
||||||
"""
|
"""
|
||||||
streamDelete(id: String!): Boolean!
|
streamDelete(id: String!): Boolean!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_USER)
|
||||||
@hasScope(scope: "streams:write")
|
@hasScope(scope: "streams:write")
|
||||||
|
|
||||||
streamsDelete(ids: [String!]): Boolean! @hasRole(role: "server:admin")
|
streamsDelete(ids: [String!]): Boolean! @hasServerRole(role: SERVER_ADMIN)
|
||||||
"""
|
"""
|
||||||
Update permissions of a user on a given stream.
|
Update permissions of a user on a given stream.
|
||||||
"""
|
"""
|
||||||
streamUpdatePermission(permissionParams: StreamUpdatePermissionInput!): Boolean
|
streamUpdatePermission(permissionParams: StreamUpdatePermissionInput!): Boolean
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_USER)
|
||||||
@hasScope(scope: "streams:write")
|
@hasScope(scope: "streams:write")
|
||||||
"""
|
"""
|
||||||
Revokes the permissions of a user on a given stream.
|
Revokes the permissions of a user on a given stream.
|
||||||
"""
|
"""
|
||||||
streamRevokePermission(permissionParams: StreamRevokePermissionInput!): Boolean
|
streamRevokePermission(permissionParams: StreamRevokePermissionInput!): Boolean
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_USER)
|
||||||
@hasScope(scope: "streams:write")
|
@hasScope(scope: "streams:write")
|
||||||
|
|
||||||
# Favorite/unfavorite the given stream
|
# Favorite/unfavorite the given stream
|
||||||
streamFavorite(streamId: String!, favorited: Boolean!): Stream
|
streamFavorite(streamId: String!, favorited: Boolean!): Stream
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Remove yourself from stream collaborators (not possible for the owner)
|
Remove yourself from stream collaborators (not possible for the owner)
|
||||||
"""
|
"""
|
||||||
streamLeave(streamId: String!): Boolean! @hasRole(role: "server:user")
|
streamLeave(streamId: String!): Boolean! @hasServerRole(role: SERVER_GUEST)
|
||||||
}
|
}
|
||||||
|
|
||||||
extend type Subscription {
|
extend type Subscription {
|
||||||
@@ -202,7 +202,7 @@ extend type Subscription {
|
|||||||
**NOTE**: If someone shares a stream with you, this subscription will be triggered with an extra value of `sharedBy` in the payload.
|
**NOTE**: If someone shares a stream with you, this subscription will be triggered with an extra value of `sharedBy` in the payload.
|
||||||
"""
|
"""
|
||||||
userStreamAdded: JSONObject
|
userStreamAdded: JSONObject
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "profile:read")
|
@hasScope(scope: "profile:read")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -210,7 +210,7 @@ extend type Subscription {
|
|||||||
**NOTE**: If someone revokes your permissions on a stream, this subscription will be triggered with an extra value of `revokedBy` in the payload.
|
**NOTE**: If someone revokes your permissions on a stream, this subscription will be triggered with an extra value of `revokedBy` in the payload.
|
||||||
"""
|
"""
|
||||||
userStreamRemoved: JSONObject
|
userStreamRemoved: JSONObject
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "profile:read")
|
@hasScope(scope: "profile:read")
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -222,14 +222,14 @@ extend type Subscription {
|
|||||||
Subscribes to stream updated event. Use this in clients/components that pertain only to this stream.
|
Subscribes to stream updated event. Use this in clients/components that pertain only to this stream.
|
||||||
"""
|
"""
|
||||||
streamUpdated(streamId: String): JSONObject
|
streamUpdated(streamId: String): JSONObject
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "streams:read")
|
@hasScope(scope: "streams:read")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Subscribes to stream deleted event. Use this in clients/components that pertain only to this stream.
|
Subscribes to stream deleted event. Use this in clients/components that pertain only to this stream.
|
||||||
"""
|
"""
|
||||||
streamDeleted(streamId: String): JSONObject
|
streamDeleted(streamId: String): JSONObject
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "streams:read")
|
@hasScope(scope: "streams:read")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ extend type Query {
|
|||||||
Get the (limited) profile information of another server user
|
Get the (limited) profile information of another server user
|
||||||
"""
|
"""
|
||||||
otherUser(id: String!): LimitedUser
|
otherUser(id: String!): LimitedUser
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "users:read")
|
@hasScope(scope: "users:read")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -28,8 +28,8 @@ extend type Query {
|
|||||||
offset: Int! = 0
|
offset: Int! = 0
|
||||||
query: String = null
|
query: String = null
|
||||||
): AdminUsersListCollection
|
): AdminUsersListCollection
|
||||||
|
@hasServerRole(role: SERVER_ADMIN)
|
||||||
@deprecated(reason: "use admin.UserList instead")
|
@deprecated(reason: "use admin.UserList instead")
|
||||||
@hasRole(role: "server:admin")
|
|
||||||
@hasScope(scope: "users:read")
|
@hasScope(scope: "users:read")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -154,18 +154,19 @@ extend type Mutation {
|
|||||||
Delete a user's account.
|
Delete a user's account.
|
||||||
"""
|
"""
|
||||||
userDelete(userConfirmation: UserDeleteInput!): Boolean!
|
userDelete(userConfirmation: UserDeleteInput!): Boolean!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "profile:delete")
|
@hasScope(scope: "profile:delete")
|
||||||
|
|
||||||
adminDeleteUser(userConfirmation: UserDeleteInput!): Boolean!
|
adminDeleteUser(userConfirmation: UserDeleteInput!): Boolean!
|
||||||
@hasRole(role: "server:admin")
|
@hasServerRole(role: SERVER_ADMIN)
|
||||||
|
|
||||||
userRoleChange(userRoleInput: UserRoleInput!): Boolean! @hasRole(role: "server:admin")
|
userRoleChange(userRoleInput: UserRoleInput!): Boolean!
|
||||||
|
@hasServerRole(role: SERVER_ADMIN)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Various Active User oriented mutations
|
Various Active User oriented mutations
|
||||||
"""
|
"""
|
||||||
activeUserMutations: ActiveUserMutations! @hasRole(role: "server:user")
|
activeUserMutations: ActiveUserMutations! @hasServerRole(role: SERVER_GUEST)
|
||||||
}
|
}
|
||||||
|
|
||||||
input UserRoleInput {
|
input UserRoleInput {
|
||||||
|
|||||||
@@ -9,5 +9,5 @@ extend type Mutation {
|
|||||||
"""
|
"""
|
||||||
(Re-)send the account verification e-mail
|
(Re-)send the account verification e-mail
|
||||||
"""
|
"""
|
||||||
requestVerification: Boolean! @hasRole(role: "server:user")
|
requestVerification: Boolean! @hasServerRole(role: SERVER_GUEST)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ extend type User {
|
|||||||
|
|
||||||
extend type Mutation {
|
extend type Mutation {
|
||||||
userNotificationPreferencesUpdate(preferences: JSONObject!): Boolean
|
userNotificationPreferencesUpdate(preferences: JSONObject!): Boolean
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,49 +3,49 @@ extend type Mutation {
|
|||||||
Invite a new user to the speckle server and return the invite ID
|
Invite a new user to the speckle server and return the invite ID
|
||||||
"""
|
"""
|
||||||
serverInviteCreate(input: ServerInviteCreateInput!): Boolean!
|
serverInviteCreate(input: ServerInviteCreateInput!): Boolean!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_USER)
|
||||||
@hasScope(scope: "users:invite")
|
@hasScope(scope: "users:invite")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Invite a new or registered user to the specified stream
|
Invite a new or registered user to the specified stream
|
||||||
"""
|
"""
|
||||||
streamInviteCreate(input: StreamInviteCreateInput!): Boolean!
|
streamInviteCreate(input: StreamInviteCreateInput!): Boolean!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_USER)
|
||||||
@hasScope(scope: "users:invite")
|
@hasScope(scope: "users:invite")
|
||||||
|
|
||||||
serverInviteBatchCreate(input: [ServerInviteCreateInput!]!): Boolean!
|
serverInviteBatchCreate(input: [ServerInviteCreateInput!]!): Boolean!
|
||||||
@hasRole(role: "server:admin")
|
@hasServerRole(role: SERVER_ADMIN)
|
||||||
@hasScope(scope: "users:invite")
|
@hasScope(scope: "users:invite")
|
||||||
|
|
||||||
streamInviteBatchCreate(input: [StreamInviteCreateInput!]!): Boolean!
|
streamInviteBatchCreate(input: [StreamInviteCreateInput!]!): Boolean!
|
||||||
@hasRole(role: "server:admin")
|
@hasServerRole(role: SERVER_ADMIN)
|
||||||
@hasScope(scope: "users:invite")
|
@hasScope(scope: "users:invite")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Accept or decline a stream invite
|
Accept or decline a stream invite
|
||||||
"""
|
"""
|
||||||
streamInviteUse(accept: Boolean!, streamId: String!, token: String!): Boolean!
|
streamInviteUse(accept: Boolean!, streamId: String!, token: String!): Boolean!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Cancel a pending stream invite. Can only be invoked by a stream owner.
|
Cancel a pending stream invite. Can only be invoked by a stream owner.
|
||||||
"""
|
"""
|
||||||
streamInviteCancel(streamId: String!, inviteId: String!): Boolean!
|
streamInviteCancel(streamId: String!, inviteId: String!): Boolean!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "users:invite")
|
@hasScope(scope: "users:invite")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Re-send a pending invite
|
Re-send a pending invite
|
||||||
"""
|
"""
|
||||||
inviteResend(inviteId: String!): Boolean!
|
inviteResend(inviteId: String!): Boolean!
|
||||||
@hasRole(role: "server:admin")
|
@hasServerRole(role: SERVER_ADMIN)
|
||||||
@hasScope(scope: "users:invite")
|
@hasScope(scope: "users:invite")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Delete a pending invite
|
Delete a pending invite
|
||||||
"""
|
"""
|
||||||
inviteDelete(inviteId: String!): Boolean!
|
inviteDelete(inviteId: String!): Boolean!
|
||||||
@hasRole(role: "server:admin")
|
@hasServerRole(role: SERVER_ADMIN)
|
||||||
@hasScope(scope: "users:invite")
|
@hasScope(scope: "users:invite")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ extend type Query {
|
|||||||
Get all invitations to streams that the active user has
|
Get all invitations to streams that the active user has
|
||||||
"""
|
"""
|
||||||
streamInvites: [PendingStreamCollaborator!]!
|
streamInvites: [PendingStreamCollaborator!]!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_GUEST)
|
||||||
@hasScope(scope: "streams:read")
|
@hasScope(scope: "streams:read")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
extend type Stream {
|
extend type Stream {
|
||||||
webhooks(id: String): WebhookCollection
|
webhooks(id: String): WebhookCollection
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_USER)
|
||||||
@hasScope(scope: "streams:write")
|
@hasScope(scope: "streams:write")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9,21 +9,21 @@ extend type Mutation {
|
|||||||
Creates a new webhook on a stream
|
Creates a new webhook on a stream
|
||||||
"""
|
"""
|
||||||
webhookCreate(webhook: WebhookCreateInput!): String!
|
webhookCreate(webhook: WebhookCreateInput!): String!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_USER)
|
||||||
@hasScope(scope: "streams:write")
|
@hasScope(scope: "streams:write")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Updates an existing webhook
|
Updates an existing webhook
|
||||||
"""
|
"""
|
||||||
webhookUpdate(webhook: WebhookUpdateInput!): String!
|
webhookUpdate(webhook: WebhookUpdateInput!): String!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_USER)
|
||||||
@hasScope(scope: "streams:write")
|
@hasScope(scope: "streams:write")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Deletes an existing webhook
|
Deletes an existing webhook
|
||||||
"""
|
"""
|
||||||
webhookDelete(webhook: WebhookDeleteInput!): String!
|
webhookDelete(webhook: WebhookDeleteInput!): String!
|
||||||
@hasRole(role: "server:user")
|
@hasServerRole(role: SERVER_USER)
|
||||||
@hasScope(scope: "streams:write")
|
@hasScope(scope: "streams:write")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import { saveActivity } from '@/modules/activitystream/services'
|
import { saveActivity } from '@/modules/activitystream/services'
|
||||||
import { ActionTypes, ResourceTypes } from '@/modules/activitystream/helpers/types'
|
import { ActionTypes, ResourceTypes } from '@/modules/activitystream/helpers/types'
|
||||||
import { BranchRecord } from '@/modules/core/helpers/types'
|
import { BranchRecord } from '@/modules/core/helpers/types'
|
||||||
import { pubsub, BranchPubsubEvents } from '@/modules/shared'
|
import {
|
||||||
|
pubsub,
|
||||||
|
BranchSubscriptions as BranchPubsubEvents
|
||||||
|
} from '@/modules/shared/utils/subscriptions'
|
||||||
import {
|
import {
|
||||||
BranchDeleteInput,
|
BranchDeleteInput,
|
||||||
BranchUpdateInput,
|
BranchUpdateInput,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
getViewerResourcesForComment,
|
getViewerResourcesForComment,
|
||||||
getViewerResourcesFromLegacyIdentifiers
|
getViewerResourcesFromLegacyIdentifiers
|
||||||
} from '@/modules/core/services/commit/viewerResources'
|
} from '@/modules/core/services/commit/viewerResources'
|
||||||
import { pubsub } from '@/modules/shared'
|
import { pubsub } from '@/modules/shared/utils/subscriptions'
|
||||||
import {
|
import {
|
||||||
CommentSubscriptions,
|
CommentSubscriptions,
|
||||||
ProjectSubscriptions,
|
ProjectSubscriptions,
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { saveActivity } from '@/modules/activitystream/services'
|
import { saveActivity } from '@/modules/activitystream/services'
|
||||||
import { ActionTypes, ResourceTypes } from '@/modules/activitystream/helpers/types'
|
import { ActionTypes, ResourceTypes } from '@/modules/activitystream/helpers/types'
|
||||||
import { CommitPubsubEvents, pubsub } from '@/modules/shared'
|
import {
|
||||||
|
CommitSubscriptions as CommitPubsubEvents,
|
||||||
|
pubsub
|
||||||
|
} from '@/modules/shared/utils/subscriptions'
|
||||||
import {
|
import {
|
||||||
CommitCreateInput,
|
CommitCreateInput,
|
||||||
CommitReceivedInput,
|
CommitReceivedInput,
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import { saveActivity } from '@/modules/activitystream/services'
|
import { saveActivity } from '@/modules/activitystream/services'
|
||||||
import { ActionTypes, ResourceTypes } from '@/modules/activitystream/helpers/types'
|
import { ActionTypes, ResourceTypes } from '@/modules/activitystream/helpers/types'
|
||||||
import { StreamRoles } from '@/modules/core/helpers/mainConstants'
|
import { StreamRoles } from '@/modules/core/helpers/mainConstants'
|
||||||
import { pubsub, StreamPubsubEvents } from '@/modules/shared'
|
import {
|
||||||
|
pubsub,
|
||||||
|
StreamSubscriptions as StreamPubsubEvents
|
||||||
|
} from '@/modules/shared/utils/subscriptions'
|
||||||
import { StreamCreateInput } from '@/test/graphql/generated/graphql'
|
import { StreamCreateInput } from '@/test/graphql/generated/graphql'
|
||||||
import { Knex } from 'knex'
|
import { Knex } from 'knex'
|
||||||
import { getStreamCollaborators } from '@/modules/core/repositories/streams'
|
import { getStreamCollaborators } from '@/modules/core/repositories/streams'
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const { noErrors } = require('@/test/helpers')
|
|||||||
const {
|
const {
|
||||||
addOrUpdateStreamCollaborator
|
addOrUpdateStreamCollaborator
|
||||||
} = require('@/modules/core/services/streams/streamAccessService')
|
} = require('@/modules/core/services/streams/streamAccessService')
|
||||||
const { Roles } = require('@/modules/core/helpers/mainConstants')
|
const { Roles, Scopes } = require('@speckle/shared')
|
||||||
|
|
||||||
let sendRequest
|
let sendRequest
|
||||||
|
|
||||||
@@ -79,14 +79,14 @@ describe('Activity @activity', () => {
|
|||||||
;({ sendRequest } = await initializeTestServer(server, app))
|
;({ sendRequest } = await initializeTestServer(server, app))
|
||||||
|
|
||||||
const normalScopesList = [
|
const normalScopesList = [
|
||||||
'streams:read',
|
Scopes.Streams.Read,
|
||||||
'streams:write',
|
Scopes.Streams.Write,
|
||||||
'users:read',
|
Scopes.Users.Read,
|
||||||
'users:email',
|
Scopes.Users.Email,
|
||||||
'tokens:write',
|
Scopes.Tokens.Write,
|
||||||
'tokens:read',
|
Scopes.Tokens.Read,
|
||||||
'profile:read',
|
Scopes.Profile.Read,
|
||||||
'profile:email'
|
Scopes.Profile.Email
|
||||||
]
|
]
|
||||||
|
|
||||||
// create users
|
// create users
|
||||||
@@ -106,8 +106,8 @@ describe('Activity @activity', () => {
|
|||||||
(token) => (userCr.token = `Bearer ${token}`)
|
(token) => (userCr.token = `Bearer ${token}`)
|
||||||
),
|
),
|
||||||
createPersonalAccessToken(userX.id, 'no users:read test token', [
|
createPersonalAccessToken(userX.id, 'no users:read test token', [
|
||||||
'streams:read',
|
Scopes.Streams.Read,
|
||||||
'streams:write'
|
Scopes.Streams.Write
|
||||||
]).then((token) => (userX.token = `Bearer ${token}`))
|
]).then((token) => (userX.token = `Bearer ${token}`))
|
||||||
// streams
|
// streams
|
||||||
// createStream({ ...collaboratorTestStream, ownerId: userIz.id }).then(
|
// createStream({ ...collaboratorTestStream, ownerId: userIz.id }).then(
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const {
|
|||||||
deleteApp,
|
deleteApp,
|
||||||
revokeExistingAppCredentialsForUser
|
revokeExistingAppCredentialsForUser
|
||||||
} = require('../../services/apps')
|
} = require('../../services/apps')
|
||||||
|
const { Roles } = require('@speckle/shared')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
Query: {
|
Query: {
|
||||||
@@ -56,10 +57,10 @@ module.exports = {
|
|||||||
async appUpdate(parent, args, context) {
|
async appUpdate(parent, args, context) {
|
||||||
const app = await getApp({ id: args.app.id })
|
const app = await getApp({ id: args.app.id })
|
||||||
// only admins can update the default apps, generated by the server
|
// only admins can update the default apps, generated by the server
|
||||||
if (!app.author && context.role !== 'server:admin')
|
if (!app.author && context.role !== Roles.Server.Admin)
|
||||||
throw new ForbiddenError('You are not authorized to edit this app.')
|
throw new ForbiddenError('You are not authorized to edit this app.')
|
||||||
// only the author or an admin can update a 3rd party app
|
// only the author or an admin can update a 3rd party app
|
||||||
if (app.author.id !== context.userId && context.role !== 'server:admin')
|
if (app.author.id !== context.userId && context.role !== Roles.Server.Admin)
|
||||||
throw new ForbiddenError('You are not authorized to edit this app.')
|
throw new ForbiddenError('You are not authorized to edit this app.')
|
||||||
|
|
||||||
await updateApp({ app: args.app })
|
await updateApp({ app: args.app })
|
||||||
@@ -69,9 +70,9 @@ module.exports = {
|
|||||||
async appDelete(parent, args, context) {
|
async appDelete(parent, args, context) {
|
||||||
const app = await getApp({ id: args.appId })
|
const app = await getApp({ id: args.appId })
|
||||||
|
|
||||||
if (!app.author && context.role !== 'server:admin')
|
if (!app.author && context.role !== Roles.Server.Admin)
|
||||||
throw new ForbiddenError('You are not authorized to edit this app.')
|
throw new ForbiddenError('You are not authorized to edit this app.')
|
||||||
if (app.author.id !== context.userId && context.role !== 'server:admin')
|
if (app.author.id !== context.userId && context.role !== Roles.Server.Admin)
|
||||||
throw new ForbiddenError('You are not authorized to edit this app.')
|
throw new ForbiddenError('You are not authorized to edit this app.')
|
||||||
|
|
||||||
return (await deleteApp({ id: args.appId })) === 1
|
return (await deleteApp({ id: args.appId })) === 1
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ const { revokeRefreshToken } = require(`@/modules/auth/services/apps`)
|
|||||||
const { validateScopes } = require(`@/modules/shared`)
|
const { validateScopes } = require(`@/modules/shared`)
|
||||||
const { InvalidAccessCodeRequestError } = require('@/modules/auth/errors')
|
const { InvalidAccessCodeRequestError } = require('@/modules/auth/errors')
|
||||||
const { ForbiddenError } = require('apollo-server-errors')
|
const { ForbiddenError } = require('apollo-server-errors')
|
||||||
|
const { Scopes } = require('@speckle/shared')
|
||||||
|
|
||||||
// TODO: Secure these endpoints!
|
// TODO: Secure these endpoints!
|
||||||
module.exports = (app) => {
|
module.exports = (app) => {
|
||||||
@@ -38,7 +39,7 @@ module.exports = (app) => {
|
|||||||
if (!valid) throw new InvalidAccessCodeRequestError('Invalid token')
|
if (!valid) throw new InvalidAccessCodeRequestError('Invalid token')
|
||||||
|
|
||||||
// 2. Validate token scopes
|
// 2. Validate token scopes
|
||||||
await validateScopes(scopes, 'tokens:write')
|
await validateScopes(scopes, Scopes.Tokens.Write)
|
||||||
|
|
||||||
const ac = await createAuthorizationCode({ appId, userId, challenge })
|
const ac = await createAuthorizationCode({ appId, userId, challenge })
|
||||||
return res.redirect(`${app.redirectUrl}?access_code=${ac}`)
|
return res.redirect(`${app.redirectUrl}?access_code=${ac}`)
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
|
const { Scopes } = require('@speckle/shared')
|
||||||
|
|
||||||
module.exports = [
|
module.exports = [
|
||||||
{
|
{
|
||||||
name: 'apps:read',
|
name: Scopes.Apps.Read,
|
||||||
description: 'See what applications you have created or have authorized.',
|
description: 'See what applications you have created or have authorized.',
|
||||||
public: false
|
public: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'apps:write',
|
name: Scopes.Apps.Write,
|
||||||
description: 'Register applications on your behalf.',
|
description: 'Register applications on your behalf.',
|
||||||
public: false
|
public: false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ module.exports = async (app, session, sessionStorage, finalizeAuth) => {
|
|||||||
clientID: process.env.GITHUB_CLIENT_ID,
|
clientID: process.env.GITHUB_CLIENT_ID,
|
||||||
clientSecret: process.env.GITHUB_CLIENT_SECRET,
|
clientSecret: process.env.GITHUB_CLIENT_SECRET,
|
||||||
callbackURL: new URL(strategy.callbackUrl, process.env.CANONICAL_URL).toString(),
|
callbackURL: new URL(strategy.callbackUrl, process.env.CANONICAL_URL).toString(),
|
||||||
|
// WARNING, the 'user:email' scope belongs to the GITHUB scopes
|
||||||
|
// DO NOT change it to our internal scope definitions !!!
|
||||||
|
// You have been warned.
|
||||||
scope: ['profile', 'user:email'],
|
scope: ['profile', 'user:email'],
|
||||||
passReqToCallback: true
|
passReqToCallback: true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const {
|
|||||||
createAuthorizationCode,
|
createAuthorizationCode,
|
||||||
createAppTokenFromAccessCode
|
createAppTokenFromAccessCode
|
||||||
} = require('../services/apps')
|
} = require('../services/apps')
|
||||||
|
const { Scopes } = require('@speckle/shared')
|
||||||
|
|
||||||
let sendRequest
|
let sendRequest
|
||||||
let server
|
let server
|
||||||
@@ -33,9 +34,9 @@ describe('GraphQL @apps-api', () => {
|
|||||||
|
|
||||||
testUser.id = await createUser(testUser)
|
testUser.id = await createUser(testUser)
|
||||||
testToken = `Bearer ${await createPersonalAccessToken(testUser.id, 'test token', [
|
testToken = `Bearer ${await createPersonalAccessToken(testUser.id, 'test token', [
|
||||||
'profile:read',
|
Scopes.Profile.Read,
|
||||||
'apps:read',
|
Scopes.Apps.Read,
|
||||||
'apps:write'
|
Scopes.Apps.Write
|
||||||
])}`
|
])}`
|
||||||
|
|
||||||
testUser2 = {
|
testUser2 = {
|
||||||
@@ -46,9 +47,9 @@ describe('GraphQL @apps-api', () => {
|
|||||||
|
|
||||||
testUser2.id = await createUser(testUser2)
|
testUser2.id = await createUser(testUser2)
|
||||||
testToken2 = `Bearer ${await createPersonalAccessToken(testUser2.id, 'test token', [
|
testToken2 = `Bearer ${await createPersonalAccessToken(testUser2.id, 'test token', [
|
||||||
'profile:read',
|
Scopes.Profile.Read,
|
||||||
'apps:read',
|
Scopes.Apps.Read,
|
||||||
'apps:write'
|
Scopes.Apps.Write
|
||||||
])}`
|
])}`
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -67,7 +68,7 @@ describe('GraphQL @apps-api', () => {
|
|||||||
name: 'Test App',
|
name: 'Test App',
|
||||||
public: true,
|
public: true,
|
||||||
description: 'Test App Description',
|
description: 'Test App Description',
|
||||||
scopes: ['streams:read'],
|
scopes: [Scopes.Streams.Read],
|
||||||
redirectUrl: 'lol://what'
|
redirectUrl: 'lol://what'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,7 +89,7 @@ describe('GraphQL @apps-api', () => {
|
|||||||
myApp: {
|
myApp: {
|
||||||
name: 'Test App',
|
name: 'Test App',
|
||||||
description: 'Test App Description',
|
description: 'Test App Description',
|
||||||
scopes: ['streams:read'],
|
scopes: [Scopes.Streams.Read],
|
||||||
redirectUrl: 'lol://what'
|
redirectUrl: 'lol://what'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -160,7 +161,7 @@ describe('GraphQL @apps-api', () => {
|
|||||||
id: testAppId,
|
id: testAppId,
|
||||||
name: 'Updated Test App',
|
name: 'Updated Test App',
|
||||||
description: 'Test App Description',
|
description: 'Test App Description',
|
||||||
scopes: ['streams:read'],
|
scopes: [Scopes.Streams.Read],
|
||||||
redirectUrl: 'lol://what'
|
redirectUrl: 'lol://what'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -192,7 +193,7 @@ describe('GraphQL @apps-api', () => {
|
|||||||
name: 'Another Test App',
|
name: 'Another Test App',
|
||||||
public: false,
|
public: false,
|
||||||
description: 'Test App Description',
|
description: 'Test App Description',
|
||||||
scopes: ['streams:read'],
|
scopes: [Scopes.Streams.Read],
|
||||||
redirectUrl: 'lol://what'
|
redirectUrl: 'lol://what'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -203,7 +204,7 @@ describe('GraphQL @apps-api', () => {
|
|||||||
name: 'The n-th Test App',
|
name: 'The n-th Test App',
|
||||||
public: false,
|
public: false,
|
||||||
description: 'Test App Description',
|
description: 'Test App Description',
|
||||||
scopes: ['streams:read'],
|
scopes: [Scopes.Streams.Read],
|
||||||
redirectUrl: 'lol://what'
|
redirectUrl: 'lol://what'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const { pubsub } = require('@/modules/shared')
|
const { pubsub } = require('@/modules/shared/utils/subscriptions')
|
||||||
const { ForbiddenError: ApolloForbiddenError } = require('apollo-server-express')
|
const { ForbiddenError: ApolloForbiddenError } = require('apollo-server-express')
|
||||||
const { ForbiddenError } = require('@/modules/shared/errors')
|
const { ForbiddenError } = require('@/modules/shared/errors')
|
||||||
const { getStream } = require('@/modules/core/services/streams')
|
const { getStream } = require('@/modules/core/services/streams')
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const {
|
|||||||
markCommentViewed
|
markCommentViewed
|
||||||
} = require('@/modules/comments/repositories/comments')
|
} = require('@/modules/comments/repositories/comments')
|
||||||
const { clamp } = require('lodash')
|
const { clamp } = require('lodash')
|
||||||
|
const { Roles } = require('@speckle/shared')
|
||||||
|
|
||||||
const Comments = () => knex('comments')
|
const Comments = () => knex('comments')
|
||||||
const CommentLinks = () => knex('comment_links')
|
const CommentLinks = () => knex('comment_links')
|
||||||
@@ -221,7 +222,7 @@ module.exports = {
|
|||||||
.first()
|
.first()
|
||||||
|
|
||||||
if (comment.authorId !== userId) {
|
if (comment.authorId !== userId) {
|
||||||
if (!aclEntry || aclEntry.role !== 'stream:owner')
|
if (!aclEntry || aclEntry.role !== Roles.Stream.Owner)
|
||||||
throw new ForbiddenError("You don't have permission to archive the comment")
|
throw new ForbiddenError("You don't have permission to archive the comment")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
const { defaultFieldResolver } = require('graphql')
|
const { defaultFieldResolver } = require('graphql')
|
||||||
const { validateServerRole, authorizeResolver } = require('@/modules/shared')
|
const { authorizeResolver } = require('@/modules/shared')
|
||||||
const { ForbiddenError } = require('@/modules/shared/errors')
|
const { ForbiddenError } = require('@/modules/shared/errors')
|
||||||
const { mapSchema, getDirective, MapperKind } = require('@graphql-tools/utils')
|
const { mapSchema, getDirective, MapperKind } = require('@graphql-tools/utils')
|
||||||
const {
|
const {
|
||||||
mapStreamRoleToValue,
|
mapStreamRoleToValue,
|
||||||
mapServerRoleToValue
|
mapServerRoleToValue
|
||||||
} = require('@/modules/core/helpers/graphTypes')
|
} = require('@/modules/core/helpers/graphTypes')
|
||||||
|
const { throwForNotHavingServerRole } = require('@/modules/shared/authz')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
/**
|
/**
|
||||||
@@ -19,6 +20,7 @@ module.exports = {
|
|||||||
enum ServerRole {
|
enum ServerRole {
|
||||||
SERVER_USER
|
SERVER_USER
|
||||||
SERVER_ADMIN
|
SERVER_ADMIN
|
||||||
|
SERVER_GUEST
|
||||||
SERVER_ARCHIVED_USER
|
SERVER_ARCHIVED_USER
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,42 +39,10 @@ module.exports = {
|
|||||||
const { resolve = defaultFieldResolver } = fieldConfig
|
const { resolve = defaultFieldResolver } = fieldConfig
|
||||||
fieldConfig.resolve = async function (...args) {
|
fieldConfig.resolve = async function (...args) {
|
||||||
const context = args[2]
|
const context = args[2]
|
||||||
await validateServerRole(context, mapServerRoleToValue(requiredRole))
|
await throwForNotHavingServerRole(
|
||||||
|
context,
|
||||||
return await resolve.apply(this, args)
|
mapServerRoleToValue(requiredRole)
|
||||||
}
|
)
|
||||||
|
|
||||||
return fieldConfig
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensure that the user has the specified SERVER role (e.g. server user, admin etc.)
|
|
||||||
* @deprecated Use `hasServerRole` instead, as it relies on proper GQL enums
|
|
||||||
* @type {import('@/modules/core/graph/helpers/directiveHelper').GraphqlDirectiveBuilder}
|
|
||||||
*/
|
|
||||||
hasRole: () => {
|
|
||||||
const directiveName = 'hasRole'
|
|
||||||
return {
|
|
||||||
typeDefs: `
|
|
||||||
"""
|
|
||||||
Ensure that the user has the specified SERVER role (e.g. server user, admin etc.)
|
|
||||||
"""
|
|
||||||
directive @${directiveName}(role: String!) on FIELD_DEFINITION
|
|
||||||
`,
|
|
||||||
schemaTransformer: (schema) =>
|
|
||||||
mapSchema(schema, {
|
|
||||||
[MapperKind.OBJECT_FIELD]: (fieldConfig) => {
|
|
||||||
const directive = getDirective(schema, fieldConfig, directiveName)?.[0]
|
|
||||||
if (!directive) return undefined
|
|
||||||
|
|
||||||
const { role: requiredRole } = directive
|
|
||||||
const { resolve = defaultFieldResolver } = fieldConfig
|
|
||||||
fieldConfig.resolve = async function (...args) {
|
|
||||||
const context = args[2]
|
|
||||||
await validateServerRole(context, requiredRole)
|
|
||||||
|
|
||||||
return await resolve.apply(this, args)
|
return await resolve.apply(this, args)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1883,6 +1883,7 @@ export type ServerInfo = {
|
|||||||
canonicalUrl?: Maybe<Scalars['String']>;
|
canonicalUrl?: Maybe<Scalars['String']>;
|
||||||
company?: Maybe<Scalars['String']>;
|
company?: Maybe<Scalars['String']>;
|
||||||
description?: Maybe<Scalars['String']>;
|
description?: Maybe<Scalars['String']>;
|
||||||
|
guestModeEnabled: Scalars['Boolean'];
|
||||||
inviteOnly?: Maybe<Scalars['Boolean']>;
|
inviteOnly?: Maybe<Scalars['Boolean']>;
|
||||||
name: Scalars['String'];
|
name: Scalars['String'];
|
||||||
roles: Array<Maybe<Role>>;
|
roles: Array<Maybe<Role>>;
|
||||||
@@ -1895,6 +1896,7 @@ export type ServerInfoUpdateInput = {
|
|||||||
adminContact?: InputMaybe<Scalars['String']>;
|
adminContact?: InputMaybe<Scalars['String']>;
|
||||||
company?: InputMaybe<Scalars['String']>;
|
company?: InputMaybe<Scalars['String']>;
|
||||||
description?: InputMaybe<Scalars['String']>;
|
description?: InputMaybe<Scalars['String']>;
|
||||||
|
guestModeEnabled?: InputMaybe<Scalars['Boolean']>;
|
||||||
inviteOnly?: InputMaybe<Scalars['Boolean']>;
|
inviteOnly?: InputMaybe<Scalars['Boolean']>;
|
||||||
name: Scalars['String'];
|
name: Scalars['String'];
|
||||||
termsOfService?: InputMaybe<Scalars['String']>;
|
termsOfService?: InputMaybe<Scalars['String']>;
|
||||||
@@ -1915,6 +1917,7 @@ export type ServerInviteCreateInput = {
|
|||||||
export enum ServerRole {
|
export enum ServerRole {
|
||||||
ServerAdmin = 'SERVER_ADMIN',
|
ServerAdmin = 'SERVER_ADMIN',
|
||||||
ServerArchivedUser = 'SERVER_ARCHIVED_USER',
|
ServerArchivedUser = 'SERVER_ARCHIVED_USER',
|
||||||
|
ServerGuest = 'SERVER_GUEST',
|
||||||
ServerUser = 'SERVER_USER'
|
ServerUser = 'SERVER_USER'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3059,12 +3062,6 @@ export type ResolversParentTypes = {
|
|||||||
WebhookUpdateInput: WebhookUpdateInput;
|
WebhookUpdateInput: WebhookUpdateInput;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type HasRoleDirectiveArgs = {
|
|
||||||
role: Scalars['String'];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type HasRoleDirectiveResolver<Result, Parent, ContextType = GraphQLContext, Args = HasRoleDirectiveArgs> = DirectiveResolverFn<Result, Parent, ContextType, Args>;
|
|
||||||
|
|
||||||
export type HasScopeDirectiveArgs = {
|
export type HasScopeDirectiveArgs = {
|
||||||
scope: Scalars['String'];
|
scope: Scalars['String'];
|
||||||
};
|
};
|
||||||
@@ -3722,6 +3719,7 @@ export type ServerInfoResolvers<ContextType = GraphQLContext, ParentType extends
|
|||||||
canonicalUrl?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
canonicalUrl?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||||
company?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
company?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||||
description?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
description?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||||
|
guestModeEnabled?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
|
||||||
inviteOnly?: Resolver<Maybe<ResolversTypes['Boolean']>, ParentType, ContextType>;
|
inviteOnly?: Resolver<Maybe<ResolversTypes['Boolean']>, ParentType, ContextType>;
|
||||||
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||||
roles?: Resolver<Array<Maybe<ResolversTypes['Role']>>, ParentType, ContextType>;
|
roles?: Resolver<Array<Maybe<ResolversTypes['Role']>>, ParentType, ContextType>;
|
||||||
@@ -4067,7 +4065,6 @@ export type Resolvers<ContextType = GraphQLContext> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type DirectiveResolvers<ContextType = GraphQLContext> = {
|
export type DirectiveResolvers<ContextType = GraphQLContext> = {
|
||||||
hasRole?: HasRoleDirectiveResolver<any, any, ContextType>;
|
|
||||||
hasScope?: HasScopeDirectiveResolver<any, any, ContextType>;
|
hasScope?: HasScopeDirectiveResolver<any, any, ContextType>;
|
||||||
hasScopes?: HasScopesDirectiveResolver<any, any, ContextType>;
|
hasScopes?: HasScopesDirectiveResolver<any, any, ContextType>;
|
||||||
hasServerRole?: HasServerRoleDirectiveResolver<any, any, ContextType>;
|
hasServerRole?: HasServerRoleDirectiveResolver<any, any, ContextType>;
|
||||||
|
|||||||
@@ -2,7 +2,11 @@
|
|||||||
|
|
||||||
const { withFilter } = require('graphql-subscriptions')
|
const { withFilter } = require('graphql-subscriptions')
|
||||||
|
|
||||||
const { authorizeResolver, pubsub, BranchPubsubEvents } = require('@/modules/shared')
|
const {
|
||||||
|
pubsub,
|
||||||
|
BranchSubscriptions: BranchPubsubEvents
|
||||||
|
} = require('@/modules/shared/utils/subscriptions')
|
||||||
|
const { authorizeResolver } = require('@/modules/shared')
|
||||||
|
|
||||||
const { getBranchByNameAndStreamId, getBranchById } = require('../../services/branches')
|
const { getBranchByNameAndStreamId, getBranchById } = require('../../services/branches')
|
||||||
const {
|
const {
|
||||||
@@ -15,6 +19,7 @@ const {
|
|||||||
} = require('@/modules/core/services/branch/retrieval')
|
} = require('@/modules/core/services/branch/retrieval')
|
||||||
|
|
||||||
const { getUserById } = require('../../services/users')
|
const { getUserById } = require('../../services/users')
|
||||||
|
const { Roles } = require('@speckle/shared')
|
||||||
|
|
||||||
// subscription events
|
// subscription events
|
||||||
const BRANCH_CREATED = BranchPubsubEvents.BranchCreated
|
const BRANCH_CREATED = BranchPubsubEvents.BranchCreated
|
||||||
@@ -62,7 +67,7 @@ module.exports = {
|
|||||||
await authorizeResolver(
|
await authorizeResolver(
|
||||||
context.userId,
|
context.userId,
|
||||||
args.branch.streamId,
|
args.branch.streamId,
|
||||||
'stream:contributor'
|
Roles.Stream.Contributor
|
||||||
)
|
)
|
||||||
|
|
||||||
const { id } = await createBranchAndNotify(args.branch, context.userId)
|
const { id } = await createBranchAndNotify(args.branch, context.userId)
|
||||||
@@ -74,7 +79,7 @@ module.exports = {
|
|||||||
await authorizeResolver(
|
await authorizeResolver(
|
||||||
context.userId,
|
context.userId,
|
||||||
args.branch.streamId,
|
args.branch.streamId,
|
||||||
'stream:contributor'
|
Roles.Stream.Contributor
|
||||||
)
|
)
|
||||||
|
|
||||||
const newBranch = await updateBranchAndNotify(args.branch, context.userId)
|
const newBranch = await updateBranchAndNotify(args.branch, context.userId)
|
||||||
@@ -85,7 +90,7 @@ module.exports = {
|
|||||||
await authorizeResolver(
|
await authorizeResolver(
|
||||||
context.userId,
|
context.userId,
|
||||||
args.branch.streamId,
|
args.branch.streamId,
|
||||||
'stream:contributor'
|
Roles.Stream.Contributor
|
||||||
)
|
)
|
||||||
|
|
||||||
const deleted = await deleteBranchAndNotify(args.branch, context.userId)
|
const deleted = await deleteBranchAndNotify(args.branch, context.userId)
|
||||||
@@ -97,7 +102,11 @@ module.exports = {
|
|||||||
subscribe: withFilter(
|
subscribe: withFilter(
|
||||||
() => pubsub.asyncIterator([BRANCH_CREATED]),
|
() => pubsub.asyncIterator([BRANCH_CREATED]),
|
||||||
async (payload, variables, context) => {
|
async (payload, variables, context) => {
|
||||||
await authorizeResolver(context.userId, payload.streamId, 'stream:reviewer')
|
await authorizeResolver(
|
||||||
|
context.userId,
|
||||||
|
payload.streamId,
|
||||||
|
Roles.Stream.Reviewer
|
||||||
|
)
|
||||||
|
|
||||||
return payload.streamId === variables.streamId
|
return payload.streamId === variables.streamId
|
||||||
}
|
}
|
||||||
@@ -108,7 +117,11 @@ module.exports = {
|
|||||||
subscribe: withFilter(
|
subscribe: withFilter(
|
||||||
() => pubsub.asyncIterator([BRANCH_UPDATED]),
|
() => pubsub.asyncIterator([BRANCH_UPDATED]),
|
||||||
async (payload, variables, context) => {
|
async (payload, variables, context) => {
|
||||||
await authorizeResolver(context.userId, payload.streamId, 'stream:reviewer')
|
await authorizeResolver(
|
||||||
|
context.userId,
|
||||||
|
payload.streamId,
|
||||||
|
Roles.Stream.Reviewer
|
||||||
|
)
|
||||||
|
|
||||||
const streamMatch = payload.streamId === variables.streamId
|
const streamMatch = payload.streamId === variables.streamId
|
||||||
if (streamMatch && variables.branchId) {
|
if (streamMatch && variables.branchId) {
|
||||||
@@ -124,7 +137,11 @@ module.exports = {
|
|||||||
subscribe: withFilter(
|
subscribe: withFilter(
|
||||||
() => pubsub.asyncIterator([BRANCH_DELETED]),
|
() => pubsub.asyncIterator([BRANCH_DELETED]),
|
||||||
async (payload, variables, context) => {
|
async (payload, variables, context) => {
|
||||||
await authorizeResolver(context.userId, payload.streamId, 'stream:reviewer')
|
await authorizeResolver(
|
||||||
|
context.userId,
|
||||||
|
payload.streamId,
|
||||||
|
Roles.Stream.Reviewer
|
||||||
|
)
|
||||||
|
|
||||||
return payload.streamId === variables.streamId
|
return payload.streamId === variables.streamId
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,11 @@
|
|||||||
|
|
||||||
const { UserInputError, ApolloError } = require('apollo-server-express')
|
const { UserInputError, ApolloError } = require('apollo-server-express')
|
||||||
const { withFilter } = require('graphql-subscriptions')
|
const { withFilter } = require('graphql-subscriptions')
|
||||||
const { authorizeResolver, pubsub, CommitPubsubEvents } = require('@/modules/shared')
|
const {
|
||||||
|
pubsub,
|
||||||
|
CommitSubscriptions: CommitPubsubEvents
|
||||||
|
} = require('@/modules/shared/utils/subscriptions')
|
||||||
|
const { authorizeResolver } = require('@/modules/shared')
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getCommitById,
|
getCommitById,
|
||||||
@@ -39,6 +43,7 @@ const {
|
|||||||
validateStreamAccess
|
validateStreamAccess
|
||||||
} = require('@/modules/core/services/streams/streamAccessService')
|
} = require('@/modules/core/services/streams/streamAccessService')
|
||||||
const { StreamInvalidAccessError } = require('@/modules/core/errors/stream')
|
const { StreamInvalidAccessError } = require('@/modules/core/errors/stream')
|
||||||
|
const { Roles } = require('@speckle/shared')
|
||||||
|
|
||||||
// subscription events
|
// subscription events
|
||||||
const COMMIT_CREATED = CommitPubsubEvents.CommitCreated
|
const COMMIT_CREATED = CommitPubsubEvents.CommitCreated
|
||||||
@@ -167,7 +172,7 @@ module.exports = {
|
|||||||
await authorizeResolver(
|
await authorizeResolver(
|
||||||
context.userId,
|
context.userId,
|
||||||
args.commit.streamId,
|
args.commit.streamId,
|
||||||
'stream:contributor'
|
Roles.Stream.Contributor
|
||||||
)
|
)
|
||||||
|
|
||||||
const rateLimitResult = await getRateLimitResult(
|
const rateLimitResult = await getRateLimitResult(
|
||||||
@@ -190,7 +195,7 @@ module.exports = {
|
|||||||
await authorizeResolver(
|
await authorizeResolver(
|
||||||
context.userId,
|
context.userId,
|
||||||
args.commit.streamId,
|
args.commit.streamId,
|
||||||
'stream:contributor'
|
Roles.Stream.Contributor
|
||||||
)
|
)
|
||||||
|
|
||||||
await updateCommitAndNotify(args.commit, context.userId)
|
await updateCommitAndNotify(args.commit, context.userId)
|
||||||
@@ -198,7 +203,11 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async commitReceive(parent, args, context) {
|
async commitReceive(parent, args, context) {
|
||||||
await authorizeResolver(context.userId, args.input.streamId, 'stream:reviewer')
|
await authorizeResolver(
|
||||||
|
context.userId,
|
||||||
|
args.input.streamId,
|
||||||
|
Roles.Stream.Reviewer
|
||||||
|
)
|
||||||
|
|
||||||
const commit = await getCommitById({
|
const commit = await getCommitById({
|
||||||
streamId: args.input.streamId,
|
streamId: args.input.streamId,
|
||||||
@@ -218,7 +227,7 @@ module.exports = {
|
|||||||
await authorizeResolver(
|
await authorizeResolver(
|
||||||
context.userId,
|
context.userId,
|
||||||
args.commit.streamId,
|
args.commit.streamId,
|
||||||
'stream:contributor'
|
Roles.Stream.Contributor
|
||||||
)
|
)
|
||||||
|
|
||||||
const deleted = await deleteCommitAndNotify(
|
const deleted = await deleteCommitAndNotify(
|
||||||
@@ -244,7 +253,11 @@ module.exports = {
|
|||||||
subscribe: withFilter(
|
subscribe: withFilter(
|
||||||
() => pubsub.asyncIterator([COMMIT_CREATED]),
|
() => pubsub.asyncIterator([COMMIT_CREATED]),
|
||||||
async (payload, variables, context) => {
|
async (payload, variables, context) => {
|
||||||
await authorizeResolver(context.userId, payload.streamId, 'stream:reviewer')
|
await authorizeResolver(
|
||||||
|
context.userId,
|
||||||
|
payload.streamId,
|
||||||
|
Roles.Stream.Reviewer
|
||||||
|
)
|
||||||
return payload.streamId === variables.streamId
|
return payload.streamId === variables.streamId
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -254,7 +267,11 @@ module.exports = {
|
|||||||
subscribe: withFilter(
|
subscribe: withFilter(
|
||||||
() => pubsub.asyncIterator([COMMIT_UPDATED]),
|
() => pubsub.asyncIterator([COMMIT_UPDATED]),
|
||||||
async (payload, variables, context) => {
|
async (payload, variables, context) => {
|
||||||
await authorizeResolver(context.userId, payload.streamId, 'stream:reviewer')
|
await authorizeResolver(
|
||||||
|
context.userId,
|
||||||
|
payload.streamId,
|
||||||
|
Roles.Stream.Reviewer
|
||||||
|
)
|
||||||
|
|
||||||
const streamMatch = payload.streamId === variables.streamId
|
const streamMatch = payload.streamId === variables.streamId
|
||||||
if (streamMatch && variables.commitId) {
|
if (streamMatch && variables.commitId) {
|
||||||
@@ -270,7 +287,11 @@ module.exports = {
|
|||||||
subscribe: withFilter(
|
subscribe: withFilter(
|
||||||
() => pubsub.asyncIterator([COMMIT_DELETED]),
|
() => pubsub.asyncIterator([COMMIT_DELETED]),
|
||||||
async (payload, variables, context) => {
|
async (payload, variables, context) => {
|
||||||
await authorizeResolver(context.userId, payload.streamId, 'stream:reviewer')
|
await authorizeResolver(
|
||||||
|
context.userId,
|
||||||
|
payload.streamId,
|
||||||
|
Roles.Stream.Reviewer
|
||||||
|
)
|
||||||
|
|
||||||
return payload.streamId === variables.streamId
|
return payload.streamId === variables.streamId
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
const {
|
const { validateScopes, authorizeResolver } = require('@/modules/shared')
|
||||||
validateServerRole,
|
|
||||||
validateScopes,
|
|
||||||
authorizeResolver
|
|
||||||
} = require('@/modules/shared')
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
createObjects,
|
createObjects,
|
||||||
@@ -11,6 +7,8 @@ const {
|
|||||||
getObjectChildren,
|
getObjectChildren,
|
||||||
getObjectChildrenQuery
|
getObjectChildrenQuery
|
||||||
} = require('../../services/objects')
|
} = require('../../services/objects')
|
||||||
|
const { Roles, Scopes } = require('@speckle/shared')
|
||||||
|
const { throwForNotHavingServerRole } = require('@/modules/shared/authz')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
Stream: {
|
Stream: {
|
||||||
@@ -59,12 +57,12 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
Mutation: {
|
Mutation: {
|
||||||
async objectCreate(parent, args, context) {
|
async objectCreate(parent, args, context) {
|
||||||
await validateServerRole(context, 'server:user')
|
await throwForNotHavingServerRole(context, Roles.Server.Guest)
|
||||||
await validateScopes(context.scopes, 'streams:write')
|
await validateScopes(context.scopes, Scopes.Streams.Write)
|
||||||
await authorizeResolver(
|
await authorizeResolver(
|
||||||
context.userId,
|
context.userId,
|
||||||
args.objectInput.streamId,
|
args.objectInput.streamId,
|
||||||
'stream:contributor'
|
Roles.Stream.Contributor
|
||||||
)
|
)
|
||||||
|
|
||||||
const ids = await createObjects(
|
const ids = await createObjects(
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ import {
|
|||||||
createStreamInviteAndNotify,
|
createStreamInviteAndNotify,
|
||||||
useStreamInviteAndNotify
|
useStreamInviteAndNotify
|
||||||
} from '@/modules/serverinvites/services/management'
|
} from '@/modules/serverinvites/services/management'
|
||||||
import { authorizeResolver, validateScopes, validateServerRole } from '@/modules/shared'
|
import { authorizeResolver, validateScopes } from '@/modules/shared'
|
||||||
|
import { throwForNotHavingServerRole } from '@/modules/shared/authz'
|
||||||
import {
|
import {
|
||||||
filteredSubscribe,
|
filteredSubscribe,
|
||||||
ProjectSubscriptions,
|
ProjectSubscriptions,
|
||||||
@@ -52,7 +53,7 @@ export = {
|
|||||||
await authorizeResolver(context.userId, args.id, Roles.Stream.Reviewer)
|
await authorizeResolver(context.userId, args.id, Roles.Stream.Reviewer)
|
||||||
|
|
||||||
if (!stream.isPublic) {
|
if (!stream.isPublic) {
|
||||||
await validateServerRole(context, Roles.Server.User)
|
await throwForNotHavingServerRole(context, Roles.Server.Guest)
|
||||||
validateScopes(context.scopes, Scopes.Streams.Read)
|
validateScopes(context.scopes, Scopes.Streams.Read)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
const { validateServerRole, validateScopes } = require('@/modules/shared')
|
const { validateScopes } = require('@/modules/shared')
|
||||||
const {
|
const {
|
||||||
updateServerInfo,
|
updateServerInfo,
|
||||||
getServerInfo,
|
getServerInfo,
|
||||||
getPublicScopes,
|
getPublicScopes,
|
||||||
getPublicRoles
|
getPublicRoles
|
||||||
} = require('../../services/generic')
|
} = require('../../services/generic')
|
||||||
|
const { Roles, Scopes } = require('@speckle/shared')
|
||||||
|
const { throwForNotHavingServerRole } = require('@/modules/shared/authz')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
Query: {
|
Query: {
|
||||||
@@ -26,8 +28,8 @@ module.exports = {
|
|||||||
|
|
||||||
Mutation: {
|
Mutation: {
|
||||||
async serverInfoUpdate(parent, args, context) {
|
async serverInfoUpdate(parent, args, context) {
|
||||||
await validateServerRole(context, 'server:admin')
|
await throwForNotHavingServerRole(context, Roles.Server.Admin)
|
||||||
await validateScopes(context.scopes, 'server:setup')
|
await validateScopes(context.scopes, Scopes.Server.Setup)
|
||||||
|
|
||||||
await updateServerInfo(args.info)
|
await updateServerInfo(args.info)
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -14,12 +14,11 @@ const {
|
|||||||
} = require('@/modules/core/services/streams')
|
} = require('@/modules/core/services/streams')
|
||||||
|
|
||||||
const {
|
const {
|
||||||
authorizeResolver,
|
|
||||||
pubsub,
|
pubsub,
|
||||||
StreamPubsubEvents,
|
StreamSubscriptions: StreamPubsubEvents
|
||||||
validateScopes,
|
} = require(`@/modules/shared/utils/subscriptions`)
|
||||||
validateServerRole
|
|
||||||
} = require(`@/modules/shared`)
|
const { authorizeResolver, validateScopes } = require(`@/modules/shared`)
|
||||||
const {
|
const {
|
||||||
RateLimitError,
|
RateLimitError,
|
||||||
RateLimitAction,
|
RateLimitAction,
|
||||||
@@ -48,8 +47,9 @@ const {
|
|||||||
updateStreamRoleAndNotify
|
updateStreamRoleAndNotify
|
||||||
} = require('@/modules/core/services/streams/management')
|
} = require('@/modules/core/services/streams/management')
|
||||||
const { adminOverrideEnabled } = require('@/modules/shared/helpers/envHelper')
|
const { adminOverrideEnabled } = require('@/modules/shared/helpers/envHelper')
|
||||||
const { Roles } = require('@speckle/shared')
|
const { Roles, Scopes } = require('@speckle/shared')
|
||||||
const { StreamNotFoundError } = require('@/modules/core/errors/stream')
|
const { StreamNotFoundError } = require('@/modules/core/errors/stream')
|
||||||
|
const { throwForNotHavingServerRole } = require('@/modules/shared/authz')
|
||||||
|
|
||||||
// subscription events
|
// subscription events
|
||||||
const USER_STREAM_ADDED = StreamPubsubEvents.UserStreamAdded
|
const USER_STREAM_ADDED = StreamPubsubEvents.UserStreamAdded
|
||||||
@@ -85,11 +85,11 @@ module.exports = {
|
|||||||
throw new StreamNotFoundError('Stream not found')
|
throw new StreamNotFoundError('Stream not found')
|
||||||
}
|
}
|
||||||
|
|
||||||
await authorizeResolver(context.userId, args.id, 'stream:reviewer')
|
await authorizeResolver(context.userId, args.id, Roles.Stream.Reviewer)
|
||||||
|
|
||||||
if (!stream.isPublic) {
|
if (!stream.isPublic) {
|
||||||
await validateServerRole(context, 'server:user')
|
await throwForNotHavingServerRole(context, Roles.Server.Guest)
|
||||||
await validateScopes(context.scopes, 'streams:read')
|
await validateScopes(context.scopes, Scopes.Streams.Read)
|
||||||
}
|
}
|
||||||
|
|
||||||
return stream
|
return stream
|
||||||
@@ -221,13 +221,13 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async streamUpdate(parent, args, context) {
|
async streamUpdate(parent, args, context) {
|
||||||
await authorizeResolver(context.userId, args.stream.id, 'stream:owner')
|
await authorizeResolver(context.userId, args.stream.id, Roles.Stream.Owner)
|
||||||
await updateStreamAndNotify(args.stream, context.userId)
|
await updateStreamAndNotify(args.stream, context.userId)
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
|
|
||||||
async streamDelete(parent, args, context, info) {
|
async streamDelete(parent, args, context, info) {
|
||||||
await authorizeResolver(context.userId, args.id, 'stream:owner')
|
await authorizeResolver(context.userId, args.id, Roles.Stream.Owner)
|
||||||
return await _deleteStream(parent, args, context, info)
|
return await _deleteStream(parent, args, context, info)
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -246,7 +246,7 @@ module.exports = {
|
|||||||
await authorizeResolver(
|
await authorizeResolver(
|
||||||
context.userId,
|
context.userId,
|
||||||
args.permissionParams.streamId,
|
args.permissionParams.streamId,
|
||||||
'stream:owner'
|
Roles.Stream.Owner
|
||||||
)
|
)
|
||||||
|
|
||||||
const result = await updateStreamRoleAndNotify(
|
const result = await updateStreamRoleAndNotify(
|
||||||
@@ -260,7 +260,7 @@ module.exports = {
|
|||||||
await authorizeResolver(
|
await authorizeResolver(
|
||||||
context.userId,
|
context.userId,
|
||||||
args.permissionParams.streamId,
|
args.permissionParams.streamId,
|
||||||
'stream:owner'
|
Roles.Stream.Owner
|
||||||
)
|
)
|
||||||
|
|
||||||
const result = await updateStreamRoleAndNotify(
|
const result = await updateStreamRoleAndNotify(
|
||||||
@@ -310,7 +310,7 @@ module.exports = {
|
|||||||
subscribe: withFilter(
|
subscribe: withFilter(
|
||||||
() => pubsub.asyncIterator([STREAM_UPDATED]),
|
() => pubsub.asyncIterator([STREAM_UPDATED]),
|
||||||
async (payload, variables, context) => {
|
async (payload, variables, context) => {
|
||||||
await authorizeResolver(context.userId, payload.id, 'stream:reviewer')
|
await authorizeResolver(context.userId, payload.id, Roles.Stream.Reviewer)
|
||||||
return payload.id === variables.streamId
|
return payload.id === variables.streamId
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -320,7 +320,11 @@ module.exports = {
|
|||||||
subscribe: withFilter(
|
subscribe: withFilter(
|
||||||
() => pubsub.asyncIterator([STREAM_DELETED]),
|
() => pubsub.asyncIterator([STREAM_DELETED]),
|
||||||
async (payload, variables, context) => {
|
async (payload, variables, context) => {
|
||||||
await authorizeResolver(context.userId, payload.streamId, 'stream:reviewer')
|
await authorizeResolver(
|
||||||
|
context.userId,
|
||||||
|
payload.streamId,
|
||||||
|
Roles.Stream.Reviewer
|
||||||
|
)
|
||||||
return payload.streamId === variables.streamId
|
return payload.streamId === variables.streamId
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,21 +6,21 @@ const {
|
|||||||
getUserRole,
|
getUserRole,
|
||||||
deleteUser,
|
deleteUser,
|
||||||
searchUsers,
|
searchUsers,
|
||||||
makeUserAdmin,
|
changeUserRole
|
||||||
unmakeUserAdmin,
|
} = require('@/modules/core/services/users')
|
||||||
archiveUser
|
|
||||||
} = require('../../services/users')
|
|
||||||
const { updateUserAndNotify } = require('@/modules/core/services/users/management')
|
const { updateUserAndNotify } = require('@/modules/core/services/users/management')
|
||||||
const { saveActivity } = require('@/modules/activitystream/services')
|
const { saveActivity } = require('@/modules/activitystream/services')
|
||||||
const { ActionTypes } = require('@/modules/activitystream/helpers/types')
|
const { ActionTypes } = require('@/modules/activitystream/helpers/types')
|
||||||
const { validateServerRole, validateScopes } = require(`@/modules/shared`)
|
const { validateScopes } = require(`@/modules/shared`)
|
||||||
const zxcvbn = require('zxcvbn')
|
const zxcvbn = require('zxcvbn')
|
||||||
const {
|
const {
|
||||||
getAdminUsersListCollection
|
getAdminUsersListCollection
|
||||||
} = require('@/modules/core/services/users/adminUsersListService')
|
} = require('@/modules/core/services/users/adminUsersListService')
|
||||||
const { Roles, Scopes } = require('@/modules/core/helpers/mainConstants')
|
const { Roles, Scopes } = require('@speckle/shared')
|
||||||
const { markOnboardingComplete } = require('@/modules/core/repositories/users')
|
const { markOnboardingComplete } = require('@/modules/core/repositories/users')
|
||||||
const { UsersMeta } = require('@/modules/core/dbSchema')
|
const { UsersMeta } = require('@/modules/core/dbSchema')
|
||||||
|
const { getServerInfo } = require('@/modules/core/services/generic')
|
||||||
|
const { throwForNotHavingServerRole } = require('@/modules/shared/authz')
|
||||||
|
|
||||||
/** @type {import('@/modules/core/graph/generated/graphql').Resolvers} */
|
/** @type {import('@/modules/core/graph/generated/graphql').Resolvers} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@@ -33,8 +33,8 @@ module.exports = {
|
|||||||
if (!activeUserId) return null
|
if (!activeUserId) return null
|
||||||
|
|
||||||
// Only if authenticated - check for server roles & scopes
|
// Only if authenticated - check for server roles & scopes
|
||||||
await validateServerRole(context, 'server:user')
|
await throwForNotHavingServerRole(context, Roles.Server.Guest)
|
||||||
await validateScopes(context.scopes, 'profile:read')
|
await validateScopes(context.scopes, Scopes.Profile.Read)
|
||||||
|
|
||||||
return await getUser(activeUserId)
|
return await getUser(activeUserId)
|
||||||
},
|
},
|
||||||
@@ -47,10 +47,10 @@ module.exports = {
|
|||||||
// User wants info about himself and he's not authenticated - just return null
|
// User wants info about himself and he's not authenticated - just return null
|
||||||
if (!context.auth && !args.id) return null
|
if (!context.auth && !args.id) return null
|
||||||
|
|
||||||
await validateServerRole(context, 'server:user')
|
await throwForNotHavingServerRole(context, Roles.Server.Guest)
|
||||||
|
|
||||||
if (!args.id) await validateScopes(context.scopes, 'profile:read')
|
if (!args.id) await validateScopes(context.scopes, Scopes.Profile.Read)
|
||||||
else await validateScopes(context.scopes, 'users:read')
|
else await validateScopes(context.scopes, Scopes.Users.Read)
|
||||||
|
|
||||||
if (!args.id && !context.userId) {
|
if (!args.id && !context.userId) {
|
||||||
throw new UserInputError('You must provide an user id.')
|
throw new UserInputError('You must provide an user id.')
|
||||||
@@ -64,9 +64,9 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async userSearch(parent, args, context) {
|
async userSearch(parent, args, context) {
|
||||||
await validateServerRole(context, 'server:user')
|
await throwForNotHavingServerRole(context, Roles.Server.Guest)
|
||||||
await validateScopes(context.scopes, 'profile:read')
|
await validateScopes(context.scopes, Scopes.Profile.Read)
|
||||||
await validateScopes(context.scopes, 'users:read')
|
await validateScopes(context.scopes, Scopes.Users.Read)
|
||||||
|
|
||||||
if (args.query.length < 3)
|
if (args.query.length < 3)
|
||||||
throw new UserInputError('Search query must be at least 3 carachters.')
|
throw new UserInputError('Search query must be at least 3 carachters.')
|
||||||
@@ -97,7 +97,7 @@ module.exports = {
|
|||||||
// NOTE: we're redacting the field (returning null) rather than throwing a full error which would invalidate the request.
|
// NOTE: we're redacting the field (returning null) rather than throwing a full error which would invalidate the request.
|
||||||
if (context.userId === parent.id) {
|
if (context.userId === parent.id) {
|
||||||
try {
|
try {
|
||||||
await validateScopes(context.scopes, 'profile:email')
|
await validateScopes(context.scopes, Scopes.Profile.Email)
|
||||||
return parent.email
|
return parent.email
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return null
|
return null
|
||||||
@@ -106,7 +106,7 @@ module.exports = {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// you should only have access to other users email if you have elevated privileges
|
// you should only have access to other users email if you have elevated privileges
|
||||||
await validateServerRole(context, Roles.Server.Admin)
|
await throwForNotHavingServerRole(context, Roles.Server.Admin)
|
||||||
await validateScopes(context.scopes, Scopes.Users.Email)
|
await validateScopes(context.scopes, Scopes.Users.Email)
|
||||||
return parent.email
|
return parent.email
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -130,25 +130,24 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
Mutation: {
|
Mutation: {
|
||||||
async userUpdate(parent, args, context) {
|
async userUpdate(_parent, args, context) {
|
||||||
await validateServerRole(context, 'server:user')
|
await throwForNotHavingServerRole(context, Roles.Server.Guest)
|
||||||
await updateUserAndNotify(context.userId, args.user)
|
await updateUserAndNotify(context.userId, args.user)
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
|
|
||||||
async userRoleChange(parent, args) {
|
async userRoleChange(_parent, args) {
|
||||||
const roleChangers = {
|
const { guestModeEnabled } = await getServerInfo()
|
||||||
'server:admin': makeUserAdmin,
|
await changeUserRole({
|
||||||
'server:user': unmakeUserAdmin,
|
role: args.userRoleInput.role,
|
||||||
'server:archived-user': archiveUser
|
userId: args.userRoleInput.id,
|
||||||
}
|
guestModeEnabled
|
||||||
const roleChanger = roleChangers[args.userRoleInput.role]
|
})
|
||||||
await roleChanger({ userId: args.userRoleInput.id })
|
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
|
|
||||||
async adminDeleteUser(parent, args, context) {
|
async adminDeleteUser(_parent, args, context) {
|
||||||
await validateServerRole(context, 'server:admin')
|
await throwForNotHavingServerRole(context, Roles.Server.Admin)
|
||||||
const user = await getUserByEmail({ email: args.userConfirmation.email })
|
const user = await getUserByEmail({ email: args.userConfirmation.email })
|
||||||
await deleteUser(user.id)
|
await deleteUser(user.id)
|
||||||
return true
|
return true
|
||||||
@@ -164,8 +163,8 @@ module.exports = {
|
|||||||
// The below are not really needed anymore as we've added the hasRole and hasScope
|
// The below are not really needed anymore as we've added the hasRole and hasScope
|
||||||
// directives in the graphql schema itself.
|
// directives in the graphql schema itself.
|
||||||
// Since I am paranoid, I'll leave them here too.
|
// Since I am paranoid, I'll leave them here too.
|
||||||
await validateServerRole(context, 'server:user')
|
await throwForNotHavingServerRole(context, Roles.Server.Guest)
|
||||||
await validateScopes(context.scopes, 'profile:delete')
|
await validateScopes(context.scopes, Scopes.Profile.Delete)
|
||||||
|
|
||||||
await deleteUser(context.userId, args.user)
|
await deleteUser(context.userId, args.user)
|
||||||
|
|
||||||
|
|||||||
@@ -127,5 +127,7 @@ export function mapServerRoleToValue(graphqlServerRole: ServerRole): ServerRoles
|
|||||||
return Roles.Server.Admin
|
return Roles.Server.Admin
|
||||||
case ServerRole.ServerArchivedUser:
|
case ServerRole.ServerArchivedUser:
|
||||||
return Roles.Server.ArchivedUser
|
return Roles.Server.ArchivedUser
|
||||||
|
case ServerRole.ServerGuest:
|
||||||
|
return Roles.Server.Guest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ export type ServerConfigRecord = {
|
|||||||
canonicalUrl: string
|
canonicalUrl: string
|
||||||
completed: boolean
|
completed: boolean
|
||||||
inviteOnly: boolean
|
inviteOnly: boolean
|
||||||
|
guestModeEnabled: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ServerInfo = ServerConfigRecord & {
|
export type ServerInfo = ServerConfigRecord & {
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { Knex } from 'knex'
|
||||||
|
|
||||||
|
const TABLE_NAME = 'server_config'
|
||||||
|
const COL_NAME = 'guestModeEnabled'
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.alterTable(TABLE_NAME, (table) => {
|
||||||
|
table.boolean(COL_NAME).defaultTo(false).notNullable()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.alterTable(TABLE_NAME, (table) => {
|
||||||
|
table.dropColumn(COL_NAME)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -930,7 +930,7 @@ export async function revokeStreamPermissions(params: {
|
|||||||
.select<StreamAclRecord[]>('*')
|
.select<StreamAclRecord[]>('*')
|
||||||
.first()
|
.first()
|
||||||
|
|
||||||
if (aclEntry?.role === 'stream:owner') {
|
if (aclEntry?.role === Roles.Stream.Owner) {
|
||||||
const [countObj] = await StreamAcl.knex()
|
const [countObj] = await StreamAcl.knex()
|
||||||
.where({
|
.where({
|
||||||
resourceId: streamId,
|
resourceId: streamId,
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
const {
|
const { validateScopes, authorizeResolver } = require('@/modules/shared')
|
||||||
validateScopes,
|
|
||||||
validateServerRole,
|
|
||||||
authorizeResolver
|
|
||||||
} = require('@/modules/shared')
|
|
||||||
|
|
||||||
const { getStream } = require('../services/streams')
|
const { getStream } = require('../services/streams')
|
||||||
|
const { Roles, Scopes } = require('@speckle/shared')
|
||||||
|
const { throwForNotHavingServerRole } = require('@/modules/shared/authz')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
async validatePermissionsReadStream(streamId, req) {
|
async validatePermissionsReadStream(streamId, req) {
|
||||||
@@ -13,7 +11,7 @@ module.exports = {
|
|||||||
if (stream?.isPublic) return { result: true, status: 200 }
|
if (stream?.isPublic) return { result: true, status: 200 }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await validateServerRole(req.context, 'server:user')
|
await throwForNotHavingServerRole(req.context, Roles.Server.Guest)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return { result: false, status: 401 }
|
return { result: false, status: 401 }
|
||||||
}
|
}
|
||||||
@@ -26,13 +24,13 @@ module.exports = {
|
|||||||
|
|
||||||
if (!stream.isPublic) {
|
if (!stream.isPublic) {
|
||||||
try {
|
try {
|
||||||
await validateScopes(req.context.scopes, 'streams:read')
|
await validateScopes(req.context.scopes, Scopes.Streams.Read)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return { result: false, status: 401 }
|
return { result: false, status: 401 }
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await authorizeResolver(req.context.userId, streamId, 'stream:reviewer')
|
await authorizeResolver(req.context.userId, streamId, Roles.Stream.Reviewer)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return { result: false, status: 401 }
|
return { result: false, status: 401 }
|
||||||
}
|
}
|
||||||
@@ -46,19 +44,19 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await validateServerRole(req.context, 'server:user')
|
await throwForNotHavingServerRole(req.context, Roles.Server.Guest)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return { result: false, status: 401 }
|
return { result: false, status: 401 }
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await validateScopes(req.context.scopes, 'streams:write')
|
await validateScopes(req.context.scopes, Scopes.Streams.Write)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return { result: false, status: 401 }
|
return { result: false, status: 401 }
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await authorizeResolver(req.context.userId, streamId, 'stream:contributor')
|
await authorizeResolver(req.context.userId, streamId, Roles.Stream.Contributor)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return { result: false, status: 401 }
|
return { result: false, status: 401 }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,18 @@ module.exports = [
|
|||||||
weight: 100,
|
weight: 100,
|
||||||
public: false
|
public: false
|
||||||
},
|
},
|
||||||
|
// TODO: should this be dynamically pushed if guest role is enabled?
|
||||||
|
// feels risky, since feature can be toggled on and off,
|
||||||
|
// but user roles are not updated
|
||||||
|
// can leave the guest users in a broken state
|
||||||
|
{
|
||||||
|
name: Roles.Server.Guest,
|
||||||
|
description: 'Has limited access to the server.',
|
||||||
|
resourceTarget: 'server',
|
||||||
|
aclTableName: 'server_acl',
|
||||||
|
weight: 50,
|
||||||
|
public: false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: Roles.Server.ArchivedUser,
|
name: Roles.Server.ArchivedUser,
|
||||||
description: 'No longer has access to the server.',
|
description: 'No longer has access to the server.',
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ module.exports = {
|
|||||||
description,
|
description,
|
||||||
adminContact,
|
adminContact,
|
||||||
termsOfService,
|
termsOfService,
|
||||||
inviteOnly
|
inviteOnly,
|
||||||
|
guestModeEnabled
|
||||||
}) {
|
}) {
|
||||||
const serverInfo = await Info().select('*').first()
|
const serverInfo = await Info().select('*').first()
|
||||||
if (!serverInfo)
|
if (!serverInfo)
|
||||||
@@ -49,6 +50,7 @@ module.exports = {
|
|||||||
adminContact,
|
adminContact,
|
||||||
termsOfService,
|
termsOfService,
|
||||||
inviteOnly,
|
inviteOnly,
|
||||||
|
guestModeEnabled,
|
||||||
completed: true
|
completed: true
|
||||||
})
|
})
|
||||||
else
|
else
|
||||||
@@ -59,6 +61,7 @@ module.exports = {
|
|||||||
adminContact,
|
adminContact,
|
||||||
termsOfService,
|
termsOfService,
|
||||||
inviteOnly,
|
inviteOnly,
|
||||||
|
guestModeEnabled,
|
||||||
completed: true
|
completed: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ const { authorizeResolver } = require(`@/modules/shared`)
|
|||||||
|
|
||||||
const { Roles } = require('@/modules/core/helpers/mainConstants')
|
const { Roles } = require('@/modules/core/helpers/mainConstants')
|
||||||
const { LogicError } = require('@/modules/shared/errors')
|
const { LogicError } = require('@/modules/shared/errors')
|
||||||
const { ForbiddenError } = require('apollo-server-express')
|
const { ForbiddenError, UserInputError } = require('apollo-server-express')
|
||||||
const { StreamInvalidAccessError } = require('@/modules/core/errors/stream')
|
const { StreamInvalidAccessError } = require('@/modules/core/errors/stream')
|
||||||
const {
|
const {
|
||||||
addStreamPermissionsAddedActivity,
|
addStreamPermissionsAddedActivity,
|
||||||
@@ -15,6 +15,8 @@ const {
|
|||||||
grantStreamPermissions
|
grantStreamPermissions
|
||||||
} = require('@/modules/core/repositories/streams')
|
} = require('@/modules/core/repositories/streams')
|
||||||
|
|
||||||
|
const { ServerAcl } = require('@/modules/core/dbSchema')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if user is a stream collaborator
|
* Check if user is a stream collaborator
|
||||||
* @param {string} userId
|
* @param {string} userId
|
||||||
@@ -129,6 +131,13 @@ async function addOrUpdateStreamCollaborator(
|
|||||||
|
|
||||||
await validateStreamAccess(addedById, streamId, Roles.Stream.Owner)
|
await validateStreamAccess(addedById, streamId, Roles.Stream.Owner)
|
||||||
|
|
||||||
|
// make sure server guests cannot be stream owners
|
||||||
|
if (role === Roles.Stream.Owner) {
|
||||||
|
const userServerRole = await ServerAcl.knex().where({ userId }).first()
|
||||||
|
if (userServerRole.role === Roles.Server.Guest)
|
||||||
|
throw new UserInputError('Server guests cannot own streams')
|
||||||
|
}
|
||||||
|
|
||||||
const stream = await grantStreamPermissions({
|
const stream = await grantStreamPermissions({
|
||||||
streamId,
|
streamId,
|
||||||
userId,
|
userId,
|
||||||
|
|||||||
@@ -26,17 +26,18 @@ const {
|
|||||||
UserInputError,
|
UserInputError,
|
||||||
PasswordTooShortError
|
PasswordTooShortError
|
||||||
} = require('@/modules/core/errors/userinput')
|
} = require('@/modules/core/errors/userinput')
|
||||||
|
const { Roles } = require('@speckle/shared')
|
||||||
|
|
||||||
const changeUserRole = async ({ userId, role }) =>
|
const _changeUserRole = async ({ userId, role }) =>
|
||||||
await Acl().where({ userId }).update({ role })
|
await Acl().where({ userId }).update({ role })
|
||||||
|
|
||||||
const countAdminUsers = async () => {
|
const countAdminUsers = async () => {
|
||||||
const [{ count }] = await Acl().where({ role: 'server:admin' }).count()
|
const [{ count }] = await Acl().where({ role: Roles.Server.Admin }).count()
|
||||||
return parseInt(count)
|
return parseInt(count)
|
||||||
}
|
}
|
||||||
const _ensureAtleastOneAdminRemains = async (userId) => {
|
const _ensureAtleastOneAdminRemains = async (userId) => {
|
||||||
if ((await countAdminUsers()) === 1) {
|
if ((await countAdminUsers()) === 1) {
|
||||||
const currentAdmin = await Acl().where({ role: 'server:admin' }).first()
|
const currentAdmin = await Acl().where({ role: Roles.Server.Admin }).first()
|
||||||
if (currentAdmin.userId === userId) {
|
if (currentAdmin.userId === userId) {
|
||||||
throw new UserInputError('Cannot remove the last admin role from the server')
|
throw new UserInputError('Cannot remove the last admin role from the server')
|
||||||
}
|
}
|
||||||
@@ -93,7 +94,8 @@ module.exports = {
|
|||||||
const [newUser] = (await Users().insert(user, UsersSchema.cols)) || []
|
const [newUser] = (await Users().insert(user, UsersSchema.cols)) || []
|
||||||
if (!newUser) throw new Error("Couldn't create user")
|
if (!newUser) throw new Error("Couldn't create user")
|
||||||
|
|
||||||
const userRole = (await countAdminUsers()) === 0 ? 'server:admin' : 'server:user'
|
const userRole =
|
||||||
|
(await countAdminUsers()) === 0 ? Roles.Server.Admin : Roles.Server.User
|
||||||
|
|
||||||
await Acl().insert({ userId: newId, role: userRole })
|
await Acl().insert({ userId: newId, role: userRole })
|
||||||
|
|
||||||
@@ -182,7 +184,7 @@ module.exports = {
|
|||||||
.where((queryBuilder) => {
|
.where((queryBuilder) => {
|
||||||
queryBuilder.where({ email: searchQuery }) //match full email or partial name
|
queryBuilder.where({ email: searchQuery }) //match full email or partial name
|
||||||
if (!emailOnly) queryBuilder.orWhere('name', 'ILIKE', `%${searchQuery}%`)
|
if (!emailOnly) queryBuilder.orWhere('name', 'ILIKE', `%${searchQuery}%`)
|
||||||
if (!archived) queryBuilder.andWhere('role', '!=', 'server:archived-user')
|
if (!archived) queryBuilder.andWhere('role', '!=', Roles.Server.ArchivedUser)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (cursor) query.andWhere('users.createdAt', '<', cursor)
|
if (cursor) query.andWhere('users.createdAt', '<', cursor)
|
||||||
@@ -225,9 +227,9 @@ module.exports = {
|
|||||||
(
|
(
|
||||||
-- Get streams ids on which the user is owner
|
-- Get streams ids on which the user is owner
|
||||||
SELECT "resourceId" FROM stream_acl
|
SELECT "resourceId" FROM stream_acl
|
||||||
WHERE role = 'stream:owner' AND "userId" = ?
|
WHERE role = '${Roles.Stream.Owner}' AND "userId" = ?
|
||||||
) AS us ON acl."resourceId" = us."resourceId"
|
) AS us ON acl."resourceId" = us."resourceId"
|
||||||
WHERE acl.role = 'stream:owner'
|
WHERE acl.role = '${Roles.Stream.Owner}'
|
||||||
GROUP BY (acl."resourceId")
|
GROUP BY (acl."resourceId")
|
||||||
) AS soc
|
) AS soc
|
||||||
WHERE cnt = 1
|
WHERE cnt = 1
|
||||||
@@ -262,25 +264,18 @@ module.exports = {
|
|||||||
return users
|
return users
|
||||||
},
|
},
|
||||||
|
|
||||||
async makeUserAdmin({ userId }) {
|
|
||||||
await changeUserRole({ userId, role: 'server:admin' })
|
|
||||||
},
|
|
||||||
|
|
||||||
async unmakeUserAdmin({ userId }) {
|
|
||||||
// dont delete last admin role
|
|
||||||
await _ensureAtleastOneAdminRemains(userId)
|
|
||||||
await changeUserRole({ userId, role: 'server:user' })
|
|
||||||
},
|
|
||||||
|
|
||||||
async archiveUser({ userId }) {
|
|
||||||
// dont change last admin to archived
|
|
||||||
await _ensureAtleastOneAdminRemains(userId)
|
|
||||||
await changeUserRole({ userId, role: 'server:archived-user' })
|
|
||||||
},
|
|
||||||
|
|
||||||
async countUsers(searchQuery = null) {
|
async countUsers(searchQuery = null) {
|
||||||
const query = getUsersBaseQuery(searchQuery)
|
const query = getUsersBaseQuery(searchQuery)
|
||||||
const [userCount] = await query.count()
|
const [userCount] = await query.count()
|
||||||
return parseInt(userCount.count)
|
return parseInt(userCount.count)
|
||||||
|
},
|
||||||
|
|
||||||
|
async changeUserRole({ userId, role, guestModeEnabled = false }) {
|
||||||
|
if (!Object.values(Roles.Server).includes(role))
|
||||||
|
throw new UserInputError(`Invalid role specified: ${role}`)
|
||||||
|
if (!guestModeEnabled && role === Roles.Server.Guest)
|
||||||
|
throw new UserInputError('Guest role is not enabled')
|
||||||
|
if (role !== Roles.Server.Admin) await _ensureAtleastOneAdminRemains(userId)
|
||||||
|
await _changeUserRole({ userId, role })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -373,7 +373,7 @@ describe('Favorite streams', () => {
|
|||||||
|
|
||||||
expect(result.data.streamFavorite).to.not.be.ok
|
expect(result.data.streamFavorite).to.not.be.ok
|
||||||
expect(result.errors).to.have.lengthOf(1)
|
expect(result.errors).to.have.lengthOf(1)
|
||||||
expect(result.errors.at(0).message).to.contain('must provide an auth token')
|
expect(result.errors.at(0).message).to.contain('Must provide an auth token')
|
||||||
})
|
})
|
||||||
|
|
||||||
it("can't be retrieved", async () => {
|
it("can't be retrieved", async () => {
|
||||||
|
|||||||
@@ -13,13 +13,11 @@ const { beforeEachContext } = require('@/test/hooks')
|
|||||||
const { createStream } = require('@/modules/core/services/streams')
|
const { createStream } = require('@/modules/core/services/streams')
|
||||||
const { createUser } = require('@/modules/core/services/users')
|
const { createUser } = require('@/modules/core/services/users')
|
||||||
|
|
||||||
const {
|
const { validateScopes, authorizeResolver } = require('@/modules/shared')
|
||||||
validateServerRole,
|
|
||||||
validateScopes,
|
|
||||||
authorizeResolver
|
|
||||||
} = require('@/modules/shared')
|
|
||||||
const { buildContext } = require('@/modules/shared/middleware')
|
const { buildContext } = require('@/modules/shared/middleware')
|
||||||
const { ForbiddenError } = require('apollo-server-express')
|
const { ForbiddenError } = require('apollo-server-express')
|
||||||
|
const { Roles, Scopes } = require('@speckle/shared')
|
||||||
|
const { throwForNotHavingServerRole } = require('@/modules/shared/authz')
|
||||||
|
|
||||||
describe('Generic AuthN & AuthZ controller tests', () => {
|
describe('Generic AuthN & AuthZ controller tests', () => {
|
||||||
before(async () => {
|
before(async () => {
|
||||||
@@ -60,7 +58,10 @@ describe('Generic AuthN & AuthZ controller tests', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
it('Should validate server role', async () => {
|
it('Should validate server role', async () => {
|
||||||
await validateServerRole({ auth: true, role: 'server:user' }, 'server:admin')
|
await throwForNotHavingServerRole(
|
||||||
|
{ auth: true, role: Roles.Server.User },
|
||||||
|
Roles.Server.Admin
|
||||||
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
throw new Error('This should have been rejected')
|
throw new Error('This should have been rejected')
|
||||||
})
|
})
|
||||||
@@ -68,21 +69,28 @@ describe('Generic AuthN & AuthZ controller tests', () => {
|
|||||||
expect('You do not have the required server role').to.equal(err.message)
|
expect('You do not have the required server role').to.equal(err.message)
|
||||||
)
|
)
|
||||||
|
|
||||||
await validateServerRole({ auth: true, role: 'HACZOR' }, '133TCR3w')
|
await throwForNotHavingServerRole({ auth: true, role: 'HACZOR' }, '133TCR3w')
|
||||||
.then(() => {
|
.then(() => {
|
||||||
throw new Error('This should have been rejected')
|
throw new Error('This should have been rejected')
|
||||||
})
|
})
|
||||||
.catch((err) => expect('Invalid server role specified').to.equal(err.message))
|
.catch((err) =>
|
||||||
|
expect('Invalid role requirement specified').to.equal(err.message)
|
||||||
|
)
|
||||||
|
|
||||||
await validateServerRole({ auth: true, role: 'server:admin' }, '133TCR3w')
|
await throwForNotHavingServerRole(
|
||||||
|
{ auth: true, role: Roles.Server.Admin },
|
||||||
|
'133TCR3w'
|
||||||
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
throw new Error('This should have been rejected')
|
throw new Error('This should have been rejected')
|
||||||
})
|
})
|
||||||
.catch((err) => expect('Invalid server role specified').to.equal(err.message))
|
.catch((err) =>
|
||||||
|
expect('Invalid role requirement specified').to.equal(err.message)
|
||||||
|
)
|
||||||
|
|
||||||
const test = await validateServerRole(
|
const test = await throwForNotHavingServerRole(
|
||||||
{ auth: true, role: 'server:admin' },
|
{ auth: true, role: Roles.Server.Admin },
|
||||||
'server:user'
|
Roles.Server.User
|
||||||
)
|
)
|
||||||
expect(test).to.equal(true)
|
expect(test).to.equal(true)
|
||||||
})
|
})
|
||||||
@@ -95,7 +103,7 @@ describe('Generic AuthN & AuthZ controller tests', () => {
|
|||||||
.catch((err) => expect('Unknown role: bar').to.equal(err.message))
|
.catch((err) => expect('Unknown role: bar').to.equal(err.message))
|
||||||
|
|
||||||
// this caught me out, but streams:read is not a valid role for now
|
// this caught me out, but streams:read is not a valid role for now
|
||||||
await authorizeResolver('foo', 'bar', 'streams:read')
|
await authorizeResolver('foo', 'bar', Scopes.Streams.Read)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
throw new Error('This should have been rejected')
|
throw new Error('This should have been rejected')
|
||||||
})
|
})
|
||||||
@@ -148,9 +156,9 @@ describe('Generic AuthN & AuthZ controller tests', () => {
|
|||||||
const role = await authorizeResolver(
|
const role = await authorizeResolver(
|
||||||
serverOwner.id,
|
serverOwner.id,
|
||||||
myStream.id,
|
myStream.id,
|
||||||
'stream:contributor'
|
Roles.Stream.Contributor
|
||||||
)
|
)
|
||||||
expect(role).to.equal('stream:owner')
|
expect(role).to.equal(Roles.Stream.Owner)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should get the passed in role for server:admins if override enabled', async () => {
|
it('should get the passed in role for server:admins if override enabled', async () => {
|
||||||
@@ -159,13 +167,17 @@ describe('Generic AuthN & AuthZ controller tests', () => {
|
|||||||
const role = await authorizeResolver(
|
const role = await authorizeResolver(
|
||||||
serverOwner.id,
|
serverOwner.id,
|
||||||
myStream.id,
|
myStream.id,
|
||||||
'stream:contributor'
|
Roles.Stream.Contributor
|
||||||
)
|
)
|
||||||
expect(role).to.equal('stream:contributor')
|
expect(role).to.equal(Roles.Stream.Contributor)
|
||||||
})
|
})
|
||||||
it('should not allow server:admins to be anything if adminOverride is disabled', async () => {
|
it('should not allow server:admins to be anything if adminOverride is disabled', async () => {
|
||||||
try {
|
try {
|
||||||
await authorizeResolver(serverOwner.id, notMyStream.id, 'stream:contributor')
|
await authorizeResolver(
|
||||||
|
serverOwner.id,
|
||||||
|
notMyStream.id,
|
||||||
|
Roles.Stream.Contributor
|
||||||
|
)
|
||||||
throw 'This should have thrown'
|
throw 'This should have thrown'
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
expect(e instanceof ForbiddenError)
|
expect(e instanceof ForbiddenError)
|
||||||
@@ -179,14 +191,14 @@ describe('Generic AuthN & AuthZ controller tests', () => {
|
|||||||
const role = await authorizeResolver(
|
const role = await authorizeResolver(
|
||||||
serverOwner.id,
|
serverOwner.id,
|
||||||
notMyStream.id,
|
notMyStream.id,
|
||||||
'stream:contributor'
|
Roles.Stream.Contributor
|
||||||
)
|
)
|
||||||
expect(role).to.equal('stream:contributor')
|
expect(role).to.equal(Roles.Stream.Contributor)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not allow server:users to be anything if adminOverride is disabled', async () => {
|
it('should not allow server:users to be anything if adminOverride is disabled', async () => {
|
||||||
try {
|
try {
|
||||||
await authorizeResolver(otherGuy.id, myStream.id, 'stream:contributor')
|
await authorizeResolver(otherGuy.id, myStream.id, Roles.Stream.Contributor)
|
||||||
throw 'This should have thrown'
|
throw 'This should have thrown'
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
expect(e instanceof ForbiddenError)
|
expect(e instanceof ForbiddenError)
|
||||||
@@ -197,7 +209,7 @@ describe('Generic AuthN & AuthZ controller tests', () => {
|
|||||||
envHelperMock.enable()
|
envHelperMock.enable()
|
||||||
envHelperMock.mockFunction('adminOverrideEnabled', () => true)
|
envHelperMock.mockFunction('adminOverrideEnabled', () => true)
|
||||||
try {
|
try {
|
||||||
await authorizeResolver(otherGuy.id, myStream.id, 'stream:contributor')
|
await authorizeResolver(otherGuy.id, myStream.id, Roles.Stream.Contributor)
|
||||||
throw 'This should have thrown'
|
throw 'This should have thrown'
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
expect(e instanceof ForbiddenError)
|
expect(e instanceof ForbiddenError)
|
||||||
|
|||||||
@@ -5,13 +5,17 @@ const request = require('supertest')
|
|||||||
const { beforeEachContext, initializeTestServer } = require(`@/test/hooks`)
|
const { beforeEachContext, initializeTestServer } = require(`@/test/hooks`)
|
||||||
const { generateManyObjects } = require(`@/test/helpers`)
|
const { generateManyObjects } = require(`@/test/helpers`)
|
||||||
|
|
||||||
const { createUser, getUsers, archiveUser } = require('../services/users')
|
const {
|
||||||
|
createUser,
|
||||||
|
getUsers,
|
||||||
|
changeUserRole
|
||||||
|
} = require('@/modules/core/services/users')
|
||||||
const { createPersonalAccessToken } = require('../services/tokens')
|
const { createPersonalAccessToken } = require('../services/tokens')
|
||||||
const {
|
const {
|
||||||
addOrUpdateStreamCollaborator,
|
addOrUpdateStreamCollaborator,
|
||||||
removeStreamCollaborator
|
removeStreamCollaborator
|
||||||
} = require('@/modules/core/services/streams/streamAccessService')
|
} = require('@/modules/core/services/streams/streamAccessService')
|
||||||
const { Roles } = require('@/modules/core/helpers/mainConstants')
|
const { Roles, Scopes } = require('@speckle/shared')
|
||||||
|
|
||||||
let app
|
let app
|
||||||
let server
|
let server
|
||||||
@@ -44,15 +48,15 @@ describe('GraphQL API Core @core-api', () => {
|
|||||||
userA.id,
|
userA.id,
|
||||||
'test token user A',
|
'test token user A',
|
||||||
[
|
[
|
||||||
'server:setup',
|
Scopes.Server.Setup,
|
||||||
'streams:read',
|
Scopes.Streams.Read,
|
||||||
'streams:write',
|
Scopes.Streams.Write,
|
||||||
'users:read',
|
Scopes.Users.Read,
|
||||||
'users:email',
|
Scopes.Users.Email,
|
||||||
'tokens:write',
|
Scopes.Tokens.Write,
|
||||||
'tokens:read',
|
Scopes.Tokens.Read,
|
||||||
'profile:read',
|
Scopes.Profile.Read,
|
||||||
'profile:email'
|
Scopes.Profile.Email
|
||||||
]
|
]
|
||||||
)}`
|
)}`
|
||||||
userB.id = await createUser(userB)
|
userB.id = await createUser(userB)
|
||||||
@@ -60,14 +64,14 @@ describe('GraphQL API Core @core-api', () => {
|
|||||||
userB.id,
|
userB.id,
|
||||||
'test token user B',
|
'test token user B',
|
||||||
[
|
[
|
||||||
'streams:read',
|
Scopes.Streams.Read,
|
||||||
'streams:write',
|
Scopes.Streams.Write,
|
||||||
'users:read',
|
Scopes.Users.Read,
|
||||||
'users:email',
|
Scopes.Users.Email,
|
||||||
'tokens:write',
|
Scopes.Tokens.Write,
|
||||||
'tokens:read',
|
Scopes.Tokens.Read,
|
||||||
'profile:read',
|
Scopes.Profile.Read,
|
||||||
'profile:email'
|
Scopes.Profile.Email
|
||||||
]
|
]
|
||||||
)}`
|
)}`
|
||||||
userC.id = await createUser(userC)
|
userC.id = await createUser(userC)
|
||||||
@@ -75,14 +79,14 @@ describe('GraphQL API Core @core-api', () => {
|
|||||||
userC.id,
|
userC.id,
|
||||||
'test token user B',
|
'test token user B',
|
||||||
[
|
[
|
||||||
'streams:read',
|
Scopes.Streams.Read,
|
||||||
'streams:write',
|
Scopes.Streams.Write,
|
||||||
'users:read',
|
Scopes.Users.Read,
|
||||||
'users:email',
|
Scopes.Users.Email,
|
||||||
'tokens:write',
|
Scopes.Tokens.Write,
|
||||||
'tokens:read',
|
Scopes.Tokens.Read,
|
||||||
'profile:read',
|
Scopes.Profile.Read,
|
||||||
'profile:email'
|
Scopes.Profile.Email
|
||||||
]
|
]
|
||||||
)}`
|
)}`
|
||||||
|
|
||||||
@@ -243,14 +247,14 @@ describe('GraphQL API Core @core-api', () => {
|
|||||||
userDelete.id,
|
userDelete.id,
|
||||||
'fail token user del',
|
'fail token user del',
|
||||||
[
|
[
|
||||||
'streams:read',
|
Scopes.Streams.Read,
|
||||||
'streams:write',
|
Scopes.Streams.Write,
|
||||||
'users:read',
|
Scopes.Users.Read,
|
||||||
'users:email',
|
Scopes.Users.Email,
|
||||||
'tokens:write',
|
Scopes.Tokens.Write,
|
||||||
'tokens:read',
|
Scopes.Tokens.Read,
|
||||||
'profile:read',
|
Scopes.Profile.Read,
|
||||||
'profile:email'
|
Scopes.Profile.Email
|
||||||
]
|
]
|
||||||
)}`
|
)}`
|
||||||
|
|
||||||
@@ -271,15 +275,15 @@ describe('GraphQL API Core @core-api', () => {
|
|||||||
userDelete.id,
|
userDelete.id,
|
||||||
'test token user del',
|
'test token user del',
|
||||||
[
|
[
|
||||||
'streams:read',
|
Scopes.Streams.Read,
|
||||||
'streams:write',
|
Scopes.Streams.Write,
|
||||||
'users:read',
|
Scopes.Users.Read,
|
||||||
'users:email',
|
Scopes.Users.Email,
|
||||||
'tokens:write',
|
Scopes.Tokens.Write,
|
||||||
'tokens:read',
|
Scopes.Tokens.Read,
|
||||||
'profile:read',
|
Scopes.Profile.Read,
|
||||||
'profile:email',
|
Scopes.Profile.Email,
|
||||||
'profile:delete'
|
Scopes.Profile.Delete
|
||||||
]
|
]
|
||||||
)}`
|
)}`
|
||||||
|
|
||||||
@@ -303,30 +307,30 @@ describe('GraphQL API Core @core-api', () => {
|
|||||||
let queriedUserB = await sendRequest(userA.token, {
|
let queriedUserB = await sendRequest(userA.token, {
|
||||||
query: ` { otherUser(id:"${userB.id}") { id name role } }`
|
query: ` { otherUser(id:"${userB.id}") { id name role } }`
|
||||||
})
|
})
|
||||||
expect(queriedUserB.body.data.otherUser.role).to.equal('server:user')
|
expect(queriedUserB.body.data.otherUser.role).to.equal(Roles.Server.User)
|
||||||
let query = `mutation { userRoleChange(userRoleInput: {id: "${userB.id}", role: "server:admin"})}`
|
let query = `mutation { userRoleChange(userRoleInput: {id: "${userB.id}", role: "${Roles.Server.Admin}"})}`
|
||||||
await sendRequest(userA.token, { query })
|
await sendRequest(userA.token, { query })
|
||||||
queriedUserB = await sendRequest(userA.token, {
|
queriedUserB = await sendRequest(userA.token, {
|
||||||
query: ` { otherUser(id:"${userB.id}") { id name role } }`
|
query: ` { otherUser(id:"${userB.id}") { id name role } }`
|
||||||
})
|
})
|
||||||
expect(queriedUserB.body.data.otherUser.role).to.equal('server:admin')
|
expect(queriedUserB.body.data.otherUser.role).to.equal(Roles.Server.Admin)
|
||||||
expect(queriedUserB.body.data)
|
expect(queriedUserB.body.data)
|
||||||
query = `mutation { userRoleChange(userRoleInput: {id: "${userB.id}", role: "server:user"})}`
|
query = `mutation { userRoleChange(userRoleInput: {id: "${userB.id}", role: "${Roles.Server.User}"})}`
|
||||||
await sendRequest(userA.token, { query })
|
await sendRequest(userA.token, { query })
|
||||||
queriedUserB = await sendRequest(userA.token, {
|
queriedUserB = await sendRequest(userA.token, {
|
||||||
query: ` { otherUser(id:"${userB.id}") { id name role } }`
|
query: ` { otherUser(id:"${userB.id}") { id name role } }`
|
||||||
})
|
})
|
||||||
expect(queriedUserB.body.data.otherUser.role).to.equal('server:user')
|
expect(queriedUserB.body.data.otherUser.role).to.equal(Roles.Server.User)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Only admins can change user role', async () => {
|
it('Only admins can change user role', async () => {
|
||||||
const query = `mutation { userRoleChange(userRoleInput: {id: "${userB.id}", role: "server:admin"})}`
|
const query = `mutation { userRoleChange(userRoleInput: {id: "${userB.id}", role: "${Roles.Server.Admin}"})}`
|
||||||
const res = await sendRequest(userB.token, { query })
|
const res = await sendRequest(userB.token, { query })
|
||||||
const queriedUserB = await sendRequest(userA.token, {
|
const queriedUserB = await sendRequest(userA.token, {
|
||||||
query: ` { otherUser(id:"${userB.id}") { id name role } }`
|
query: ` { otherUser(id:"${userB.id}") { id name role } }`
|
||||||
})
|
})
|
||||||
expect(res.body.errors).to.exist
|
expect(res.body.errors).to.exist
|
||||||
expect(queriedUserB.body.data.otherUser.role).to.equal('server:user')
|
expect(queriedUserB.body.data.otherUser.role).to.equal(Roles.Server.User)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1049,7 +1053,7 @@ describe('GraphQL API Core @core-api', () => {
|
|||||||
expect(res.body.data).to.have.property('user')
|
expect(res.body.data).to.have.property('user')
|
||||||
expect(res.body.data.user.name).to.equal('Miticå')
|
expect(res.body.data.user.name).to.equal('Miticå')
|
||||||
expect(res.body.data.user.email).to.equal('d.1@speckle.systems')
|
expect(res.body.data.user.email).to.equal('d.1@speckle.systems')
|
||||||
expect(res.body.data.user.role).to.equal('server:admin')
|
expect(res.body.data.user.role).to.equal(Roles.Server.Admin)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should retrieve my streams', async () => {
|
it('Should retrieve my streams', async () => {
|
||||||
@@ -1271,8 +1275,8 @@ describe('GraphQL API Core @core-api', () => {
|
|||||||
|
|
||||||
expect(stream.name).to.equal('TS1 (u A) Private UPDATED')
|
expect(stream.name).to.equal('TS1 (u A) Private UPDATED')
|
||||||
expect(stream.collaborators).to.have.lengthOf(2)
|
expect(stream.collaborators).to.have.lengthOf(2)
|
||||||
expect(stream.collaborators[0].role).to.equal('stream:contributor')
|
expect(stream.collaborators[0].role).to.equal(Roles.Stream.Contributor)
|
||||||
expect(stream.collaborators[1].role).to.equal('stream:owner')
|
expect(stream.collaborators[1].role).to.equal(Roles.Stream.Owner)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should retrieve a public stream even if not authenticated', async () => {
|
it('Should retrieve a public stream even if not authenticated', async () => {
|
||||||
@@ -1688,20 +1692,20 @@ describe('GraphQL API Core @core-api', () => {
|
|||||||
archivedUser.id,
|
archivedUser.id,
|
||||||
'this will be archived',
|
'this will be archived',
|
||||||
[
|
[
|
||||||
'streams:read',
|
Scopes.Streams.Read,
|
||||||
'streams:write',
|
Scopes.Streams.Write,
|
||||||
'users:read',
|
Scopes.Users.Read,
|
||||||
'users:email',
|
Scopes.Users.Email,
|
||||||
'tokens:write',
|
Scopes.Tokens.Write,
|
||||||
'tokens:read',
|
Scopes.Tokens.Read,
|
||||||
'profile:read',
|
Scopes.Profile.Read,
|
||||||
'profile:email',
|
Scopes.Profile.Email,
|
||||||
'apps:read',
|
Scopes.Apps.Read,
|
||||||
'apps:write',
|
Scopes.Apps.Write,
|
||||||
'users:invite'
|
Scopes.Users.Invite
|
||||||
]
|
]
|
||||||
)}`
|
)}`
|
||||||
await archiveUser({ userId: archivedUser.id })
|
await changeUserRole({ userId: archivedUser.id, role: Roles.Server.ArchivedUser })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should be able to read public streams', async () => {
|
it('Should be able to read public streams', async () => {
|
||||||
@@ -1730,7 +1734,7 @@ describe('GraphQL API Core @core-api', () => {
|
|||||||
query,
|
query,
|
||||||
variables: {
|
variables: {
|
||||||
tokenInput: {
|
tokenInput: {
|
||||||
scopes: ['streams:read'],
|
scopes: [Scopes.Streams.Read],
|
||||||
name: 'thisWillNotBeCreated',
|
name: 'thisWillNotBeCreated',
|
||||||
lifespan: 1000000
|
lifespan: 1000000
|
||||||
}
|
}
|
||||||
@@ -1835,7 +1839,7 @@ describe('GraphQL API Core @core-api', () => {
|
|||||||
name: 'Test App',
|
name: 'Test App',
|
||||||
public: true,
|
public: true,
|
||||||
description: 'Test App Description',
|
description: 'Test App Description',
|
||||||
scopes: ['streams:read'],
|
scopes: [Scopes.Streams.Read],
|
||||||
redirectUrl: 'lol://what'
|
redirectUrl: 'lol://what'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const { packageRoot } = require('@/bootstrap')
|
|||||||
const {
|
const {
|
||||||
addOrUpdateStreamCollaborator
|
addOrUpdateStreamCollaborator
|
||||||
} = require('@/modules/core/services/streams/streamAccessService')
|
} = require('@/modules/core/services/streams/streamAccessService')
|
||||||
const { Roles } = require('@/modules/core/helpers/mainConstants')
|
const { Roles, Scopes } = require('@speckle/shared')
|
||||||
const { getFreeServerPort } = require('@/test/serverHelper')
|
const { getFreeServerPort } = require('@/test/serverHelper')
|
||||||
|
|
||||||
let addr
|
let addr
|
||||||
@@ -100,14 +100,14 @@ describe('GraphQL API Subscriptions @gql-subscriptions', () => {
|
|||||||
|
|
||||||
userA.id = await createUser(userA)
|
userA.id = await createUser(userA)
|
||||||
const token = await createPersonalAccessToken(userA.id, 'test token user A', [
|
const token = await createPersonalAccessToken(userA.id, 'test token user A', [
|
||||||
'streams:read',
|
Scopes.Streams.Read,
|
||||||
'streams:write',
|
Scopes.Streams.Write,
|
||||||
'users:read',
|
Scopes.Users.Read,
|
||||||
'users:email',
|
Scopes.Users.Email,
|
||||||
'tokens:write',
|
Scopes.Tokens.Write,
|
||||||
'tokens:read',
|
Scopes.Tokens.Read,
|
||||||
'profile:read',
|
Scopes.Profile.Read,
|
||||||
'profile:email'
|
Scopes.Profile.Email
|
||||||
])
|
])
|
||||||
userA.token = `Bearer ${token}`
|
userA.token = `Bearer ${token}`
|
||||||
|
|
||||||
@@ -116,14 +116,14 @@ describe('GraphQL API Subscriptions @gql-subscriptions', () => {
|
|||||||
userB.id,
|
userB.id,
|
||||||
'test token user B',
|
'test token user B',
|
||||||
[
|
[
|
||||||
'streams:read',
|
Scopes.Streams.Read,
|
||||||
'streams:write',
|
Scopes.Streams.Write,
|
||||||
'users:read',
|
Scopes.Users.Read,
|
||||||
'users:email',
|
Scopes.Users.Email,
|
||||||
'tokens:write',
|
Scopes.Tokens.Write,
|
||||||
'tokens:read',
|
Scopes.Tokens.Read,
|
||||||
'profile:read',
|
Scopes.Profile.Read,
|
||||||
'profile:email'
|
Scopes.Profile.Email
|
||||||
]
|
]
|
||||||
)}`
|
)}`
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ describe('GraphQL API Subscriptions @gql-subscriptions', () => {
|
|||||||
userC.token = `Bearer ${await createPersonalAccessToken(
|
userC.token = `Bearer ${await createPersonalAccessToken(
|
||||||
userC.id,
|
userC.id,
|
||||||
'test token user B',
|
'test token user B',
|
||||||
['streams:read', 'streams:write', 'users:read', 'users:email']
|
[Scopes.Streams.Read, Scopes.Streams.Write, Scopes.Users.Read, Scopes.Users.Email]
|
||||||
)}`
|
)}`
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const { createManyObjects } = require('@/test/helpers')
|
|||||||
const { createUser } = require('../services/users')
|
const { createUser } = require('../services/users')
|
||||||
const { createPersonalAccessToken } = require('../services/tokens')
|
const { createPersonalAccessToken } = require('../services/tokens')
|
||||||
const { createStream } = require('../services/streams')
|
const { createStream } = require('../services/streams')
|
||||||
|
const { Scopes } = require('@speckle/shared')
|
||||||
|
|
||||||
describe('Upload/Download Routes @api-rest', () => {
|
describe('Upload/Download Routes @api-rest', () => {
|
||||||
const userA = {
|
const userA = {
|
||||||
@@ -40,14 +41,14 @@ describe('Upload/Download Routes @api-rest', () => {
|
|||||||
userA.id,
|
userA.id,
|
||||||
'test token user A',
|
'test token user A',
|
||||||
[
|
[
|
||||||
'streams:read',
|
Scopes.Streams.Read,
|
||||||
'streams:write',
|
Scopes.Streams.Write,
|
||||||
'users:read',
|
Scopes.Users.Read,
|
||||||
'users:email',
|
Scopes.Users.Email,
|
||||||
'tokens:write',
|
Scopes.Tokens.Write,
|
||||||
'tokens:read',
|
Scopes.Tokens.Read,
|
||||||
'profile:read',
|
Scopes.Profile.Read,
|
||||||
'profile:email'
|
Scopes.Profile.Email
|
||||||
]
|
]
|
||||||
)}`
|
)}`
|
||||||
|
|
||||||
@@ -56,14 +57,14 @@ describe('Upload/Download Routes @api-rest', () => {
|
|||||||
userB.id,
|
userB.id,
|
||||||
'test token user B',
|
'test token user B',
|
||||||
[
|
[
|
||||||
'streams:read',
|
Scopes.Streams.Read,
|
||||||
'streams:write',
|
Scopes.Streams.Write,
|
||||||
'users:read',
|
Scopes.Users.Read,
|
||||||
'users:email',
|
Scopes.Users.Email,
|
||||||
'tokens:write',
|
Scopes.Tokens.Write,
|
||||||
'tokens:read',
|
Scopes.Tokens.Read,
|
||||||
'profile:read',
|
Scopes.Profile.Read,
|
||||||
'profile:email'
|
Scopes.Profile.Email
|
||||||
]
|
]
|
||||||
)}`
|
)}`
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import {
|
|||||||
updateStream,
|
updateStream,
|
||||||
deleteStream,
|
deleteStream,
|
||||||
getStreamUsers,
|
getStreamUsers,
|
||||||
grantPermissionsStream,
|
grantPermissionsStream
|
||||||
revokePermissionsStream
|
|
||||||
} from '../services/streams'
|
} from '../services/streams'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
createBranch,
|
createBranch,
|
||||||
getBranchByNameAndStreamId,
|
getBranchByNameAndStreamId,
|
||||||
@@ -37,7 +37,10 @@ import {
|
|||||||
createTestStream,
|
createTestStream,
|
||||||
createTestStreams
|
createTestStreams
|
||||||
} from '@/test/speckle-helpers/streamHelper'
|
} from '@/test/speckle-helpers/streamHelper'
|
||||||
import { StreamWithOptionalRole } from '@/modules/core/repositories/streams'
|
import {
|
||||||
|
StreamWithOptionalRole,
|
||||||
|
revokeStreamPermissions
|
||||||
|
} from '@/modules/core/repositories/streams'
|
||||||
import { has, times } from 'lodash'
|
import { has, times } from 'lodash'
|
||||||
import { Streams } from '@/modules/core/dbSchema'
|
import { Streams } from '@/modules/core/dbSchema'
|
||||||
import { ApolloServer } from 'apollo-server-express'
|
import { ApolloServer } from 'apollo-server-express'
|
||||||
@@ -49,6 +52,7 @@ import {
|
|||||||
GetUserStreamsQuery
|
GetUserStreamsQuery
|
||||||
} from '@/test/graphql/generated/graphql'
|
} from '@/test/graphql/generated/graphql'
|
||||||
import { Get } from 'type-fest'
|
import { Get } from 'type-fest'
|
||||||
|
import { changeUserRole } from '@/modules/core/services/users'
|
||||||
|
|
||||||
describe('Streams @core-streams', () => {
|
describe('Streams @core-streams', () => {
|
||||||
const userOne: BasicTestUser = {
|
const userOne: BasicTestUser = {
|
||||||
@@ -174,7 +178,7 @@ describe('Streams @core-streams', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('Should revoke permissions on stream', async () => {
|
it('Should revoke permissions on stream', async () => {
|
||||||
await revokePermissionsStream({ streamId: testStream.id, userId: userTwo.id })
|
await revokeStreamPermissions({ streamId: testStream.id, userId: userTwo.id })
|
||||||
const streamWithRole = await getStream({
|
const streamWithRole = await getStream({
|
||||||
streamId: testStream.id,
|
streamId: testStream.id,
|
||||||
userId: userTwo.id
|
userId: userTwo.id
|
||||||
@@ -183,7 +187,7 @@ describe('Streams @core-streams', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('Should not revoke owner permissions', async () => {
|
it('Should not revoke owner permissions', async () => {
|
||||||
await revokePermissionsStream({ streamId: testStream.id, userId: userOne.id })
|
await revokeStreamPermissions({ streamId: testStream.id, userId: userOne.id })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
throw new Error('This should have thrown')
|
throw new Error('This should have thrown')
|
||||||
})
|
})
|
||||||
@@ -215,6 +219,35 @@ describe('Streams @core-streams', () => {
|
|||||||
const userIsCollaborator = await isStreamCollaborator(userTwo.id, streamId)
|
const userIsCollaborator = await isStreamCollaborator(userTwo.id, streamId)
|
||||||
expect(userIsCollaborator).to.not.be.ok
|
expect(userIsCollaborator).to.not.be.ok
|
||||||
})
|
})
|
||||||
|
it('Server guests cannot be stream owners', async () => {
|
||||||
|
const guestGuy: BasicTestUser = {
|
||||||
|
name: 'Some we do not fully trust',
|
||||||
|
email: 'shady@contractor.company',
|
||||||
|
password: 'foobar123',
|
||||||
|
id: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
await createTestUsers([guestGuy])
|
||||||
|
|
||||||
|
await changeUserRole({
|
||||||
|
userId: guestGuy.id,
|
||||||
|
role: Roles.Server.Guest,
|
||||||
|
guestModeEnabled: true
|
||||||
|
})
|
||||||
|
|
||||||
|
await addOrUpdateStreamCollaborator(
|
||||||
|
testStream.id,
|
||||||
|
guestGuy.id,
|
||||||
|
Roles.Stream.Owner,
|
||||||
|
userOne.id
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
throw new Error('This should have thrown')
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
expect(err.message).to.include('Server guests cannot own streams')
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('`UpdatedAt` prop update', () => {
|
describe('`UpdatedAt` prop update', () => {
|
||||||
@@ -253,7 +286,7 @@ describe('Streams @core-streams', () => {
|
|||||||
await grantPermissionsStream({
|
await grantPermissionsStream({
|
||||||
streamId: updatableStream.id,
|
streamId: updatableStream.id,
|
||||||
userId: userTwo.id,
|
userId: userTwo.id,
|
||||||
role: 'stream:contributor'
|
role: Roles.Stream.Contributor
|
||||||
})
|
})
|
||||||
|
|
||||||
// await sleep(100)
|
// await sleep(100)
|
||||||
@@ -262,7 +295,7 @@ describe('Streams @core-streams', () => {
|
|||||||
expect(su!.updatedAt).to.not.equal(lastUpdatedAt)
|
expect(su!.updatedAt).to.not.equal(lastUpdatedAt)
|
||||||
lastUpdatedAt = su!.updatedAt
|
lastUpdatedAt = su!.updatedAt
|
||||||
|
|
||||||
await revokePermissionsStream({
|
await revokeStreamPermissions({
|
||||||
streamId: updatableStream.id,
|
streamId: updatableStream.id,
|
||||||
userId: userTwo.id
|
userId: userTwo.id
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const assert = require('assert')
|
|||||||
const knex = require('@/db/knex')
|
const knex = require('@/db/knex')
|
||||||
|
|
||||||
const {
|
const {
|
||||||
archiveUser,
|
changeUserRole,
|
||||||
createUser,
|
createUser,
|
||||||
findOrCreateUser,
|
findOrCreateUser,
|
||||||
getUser,
|
getUser,
|
||||||
@@ -41,6 +41,7 @@ const {
|
|||||||
|
|
||||||
const { createObject } = require('../services/objects')
|
const { createObject } = require('../services/objects')
|
||||||
const { beforeEachContext } = require('@/test/hooks')
|
const { beforeEachContext } = require('@/test/hooks')
|
||||||
|
const { Scopes, Roles } = require('@speckle/shared')
|
||||||
|
|
||||||
describe('Actors & Tokens @user-services', () => {
|
describe('Actors & Tokens @user-services', () => {
|
||||||
const myTestActor = {
|
const myTestActor = {
|
||||||
@@ -193,7 +194,7 @@ describe('Actors & Tokens @user-services', () => {
|
|||||||
await grantPermissionsStream({
|
await grantPermissionsStream({
|
||||||
streamId: multiOwnerStream.id,
|
streamId: multiOwnerStream.id,
|
||||||
userId: myTestActor.id,
|
userId: myTestActor.id,
|
||||||
role: 'stream:owner'
|
role: Roles.Stream.Owner
|
||||||
})
|
})
|
||||||
|
|
||||||
// create a branch for ballmer on the multiowner stream
|
// create a branch for ballmer on the multiowner stream
|
||||||
@@ -292,7 +293,7 @@ describe('Actors & Tokens @user-services', () => {
|
|||||||
password: 'nanananananaaaa'
|
password: 'nanananananaaaa'
|
||||||
})
|
})
|
||||||
|
|
||||||
await archiveUser({ userId: toBeArchivedId })
|
await changeUserRole({ userId: toBeArchivedId, role: Roles.Server.ArchivedUser })
|
||||||
|
|
||||||
let { users } = await searchUsers('Library', 20, null)
|
let { users } = await searchUsers('Library', 20, null)
|
||||||
expect(users).to.have.lengthOf(1)
|
expect(users).to.have.lengthOf(1)
|
||||||
@@ -368,24 +369,24 @@ describe('Actors & Tokens @user-services', () => {
|
|||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
pregeneratedToken = await createPersonalAccessToken(myTestActor.id, 'Whabadub', [
|
pregeneratedToken = await createPersonalAccessToken(myTestActor.id, 'Whabadub', [
|
||||||
'streams:read',
|
Scopes.Streams.Read,
|
||||||
'streams:write',
|
Scopes.Streams.Write,
|
||||||
'profile:read',
|
Scopes.Profile.Read,
|
||||||
'users:email'
|
Scopes.Users.Email
|
||||||
])
|
])
|
||||||
revokedToken = await createPersonalAccessToken(myTestActor.id, 'Mr. Revoked', [
|
revokedToken = await createPersonalAccessToken(myTestActor.id, 'Mr. Revoked', [
|
||||||
'streams:read'
|
Scopes.Streams.Read
|
||||||
])
|
])
|
||||||
expireSoonToken = await createPersonalAccessToken(
|
expireSoonToken = await createPersonalAccessToken(
|
||||||
myTestActor.id,
|
myTestActor.id,
|
||||||
'Mayfly',
|
'Mayfly',
|
||||||
['streams:read'],
|
[Scopes.Streams.Read],
|
||||||
1
|
1
|
||||||
) // 1ms lifespan
|
) // 1ms lifespan
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should create a personal api token', async () => {
|
it('Should create a personal api token', async () => {
|
||||||
const scopes = ['streams:write', 'profile:read']
|
const scopes = [Scopes.Streams.Write, Scopes.Profile.Read]
|
||||||
const name = 'My Test Token'
|
const name = 'My Test Token'
|
||||||
|
|
||||||
myFirstToken = await createPersonalAccessToken(myTestActor.id, name, scopes)
|
myFirstToken = await createPersonalAccessToken(myTestActor.id, name, scopes)
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ const {
|
|||||||
getUsers,
|
getUsers,
|
||||||
countUsers,
|
countUsers,
|
||||||
deleteUser,
|
deleteUser,
|
||||||
getUserRole,
|
changeUserRole,
|
||||||
unmakeUserAdmin,
|
getUserRole
|
||||||
makeUserAdmin
|
} = require('@/modules/core/services/users')
|
||||||
} = require('../services/users')
|
|
||||||
const { beforeEachContext } = require('@/test/hooks')
|
const { beforeEachContext } = require('@/test/hooks')
|
||||||
|
const { Roles } = require('@speckle/shared')
|
||||||
|
const cryptoRandomString = require('crypto-random-string')
|
||||||
|
|
||||||
describe('User admin @user-services', () => {
|
describe('User admin @user-services', () => {
|
||||||
const myTestActor = {
|
const myTestActor = {
|
||||||
@@ -33,7 +34,7 @@ describe('User admin @user-services', () => {
|
|||||||
const firstUser = users[0]
|
const firstUser = users[0]
|
||||||
|
|
||||||
const userRole = await getUserRole(firstUser.id)
|
const userRole = await getUserRole(firstUser.id)
|
||||||
expect(userRole).to.equal('server:admin')
|
expect(userRole).to.equal(Roles.Server.Admin)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Count user knows how to count', async () => {
|
it('Count user knows how to count', async () => {
|
||||||
@@ -52,14 +53,6 @@ describe('User admin @user-services', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('Get users query limit is sanitized to upper limit', async () => {
|
it('Get users query limit is sanitized to upper limit', async () => {
|
||||||
const createNewDroid = (number) => {
|
|
||||||
return {
|
|
||||||
name: `${number}`,
|
|
||||||
email: `${number}@droidarmy.com`,
|
|
||||||
password: 'sn3aky-1337-b1m'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const userInputs = Array(250)
|
const userInputs = Array(250)
|
||||||
.fill()
|
.fill()
|
||||||
.map((v, i) => createNewDroid(i))
|
.map((v, i) => createNewDroid(i))
|
||||||
@@ -89,27 +82,66 @@ describe('User admin @user-services', () => {
|
|||||||
expect(await countUsers('droid')).to.equal(250)
|
expect(await countUsers('droid')).to.equal(250)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Change user role modifies role', async () => {
|
describe('changeUserRole', () => {
|
||||||
const [user] = await getUsers(1, 10)
|
it('throws for invalid role value', async () => {
|
||||||
|
const role = 'shadow:lurker'
|
||||||
|
try {
|
||||||
|
await changeUserRole({ userId: myTestActor.id, role })
|
||||||
|
assert.fail('This should have failed')
|
||||||
|
} catch (err) {
|
||||||
|
expect(err.message).to.equal(`Invalid role specified: ${role}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('throws if guest role not enabled, but trying to change user role to guest', async () => {
|
||||||
|
const role = Roles.Server.Guest
|
||||||
|
try {
|
||||||
|
await changeUserRole({ userId: myTestActor.id, role })
|
||||||
|
assert.fail('This should have failed')
|
||||||
|
} catch (err) {
|
||||||
|
expect(err.message).to.equal('Guest role is not enabled')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('modifies role', async () => {
|
||||||
|
const userId = await createUser(
|
||||||
|
createNewDroid(cryptoRandomString({ length: 13 }))
|
||||||
|
)
|
||||||
|
|
||||||
const oldRole = await getUserRole(user.id)
|
const oldRole = await getUserRole(userId)
|
||||||
expect(oldRole).to.equal('server:user')
|
expect(oldRole).to.equal(Roles.Server.User)
|
||||||
|
|
||||||
await makeUserAdmin({ userId: user.id })
|
await changeUserRole({ userId, role: Roles.Server.Admin })
|
||||||
let newRole = await getUserRole(user.id)
|
let newRole = await getUserRole(userId)
|
||||||
expect(newRole).to.equal('server:admin')
|
expect(newRole).to.equal(Roles.Server.Admin)
|
||||||
|
|
||||||
await unmakeUserAdmin({ userId: user.id })
|
await changeUserRole({ userId, role: Roles.Server.User })
|
||||||
newRole = await getUserRole(user.id)
|
newRole = await getUserRole(userId)
|
||||||
expect(newRole).to.equal('server:user')
|
expect(newRole).to.equal(Roles.Server.User)
|
||||||
})
|
|
||||||
|
|
||||||
it('Ensure at least one admin remains in the server', async () => {
|
await changeUserRole({
|
||||||
try {
|
userId,
|
||||||
await unmakeUserAdmin({ userId: myTestActor.id, role: 'server:admin' })
|
role: Roles.Server.Guest,
|
||||||
assert.fail('This should have failed')
|
guestModeEnabled: true
|
||||||
} catch (err) {
|
})
|
||||||
expect(err.message).to.equal('Cannot remove the last admin role from the server')
|
newRole = await getUserRole(userId)
|
||||||
}
|
expect(newRole).to.equal(Roles.Server.Guest)
|
||||||
|
})
|
||||||
|
it('Ensures at least one admin remains in the server', async () => {
|
||||||
|
try {
|
||||||
|
await changeUserRole({ userId: myTestActor.id, role: Roles.Server.User })
|
||||||
|
assert.fail('This should have failed')
|
||||||
|
} catch (err) {
|
||||||
|
expect(err.message).to.equal(
|
||||||
|
'Cannot remove the last admin role from the server'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const createNewDroid = (number) => {
|
||||||
|
return {
|
||||||
|
name: `${number}`,
|
||||||
|
email: `${number}@droidarmy.com`,
|
||||||
|
password: 'sn3aky-1337-b1m'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -49,7 +49,8 @@ describe('Activity digest notifications @notifications', () => {
|
|||||||
canonicalUrl: 'this would be localhost:// or whatever',
|
canonicalUrl: 'this would be localhost:// or whatever',
|
||||||
completed: false,
|
completed: false,
|
||||||
inviteOnly: true,
|
inviteOnly: true,
|
||||||
version: 'testing 1 2 3'
|
version: 'testing 1 2 3',
|
||||||
|
guestModeEnabled: false
|
||||||
}
|
}
|
||||||
|
|
||||||
const topic: DigestTopic = {
|
const topic: DigestTopic = {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const { moduleLogger, logger } = require('@/logging/logging')
|
|||||||
const {
|
const {
|
||||||
listenForPreviewGenerationUpdates
|
listenForPreviewGenerationUpdates
|
||||||
} = require('@/modules/previews/services/resultListener')
|
} = require('@/modules/previews/services/resultListener')
|
||||||
|
const { Scopes, Roles } = require('@speckle/shared')
|
||||||
|
|
||||||
const httpErrorImage = (httpErrorCode) =>
|
const httpErrorImage = (httpErrorCode) =>
|
||||||
require.resolve(`#/assets/previews/images/preview_${httpErrorCode}.png`)
|
require.resolve(`#/assets/previews/images/preview_${httpErrorCode}.png`)
|
||||||
@@ -144,7 +145,7 @@ exports.init = (app) => {
|
|||||||
|
|
||||||
if (!stream.isPublic) {
|
if (!stream.isPublic) {
|
||||||
try {
|
try {
|
||||||
await validateScopes(req.context.scopes, 'streams:read')
|
await validateScopes(req.context.scopes, Scopes.Streams.Read)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return { hasPermissions: false, httpErrorCode: 401 }
|
return { hasPermissions: false, httpErrorCode: 401 }
|
||||||
}
|
}
|
||||||
@@ -153,7 +154,7 @@ exports.init = (app) => {
|
|||||||
await authorizeResolver(
|
await authorizeResolver(
|
||||||
req.context.userId,
|
req.context.userId,
|
||||||
req.params.streamId,
|
req.params.streamId,
|
||||||
'stream:reviewer'
|
Roles.Stream.Reviewer
|
||||||
)
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return { hasPermissions: false, httpErrorCode: 401 }
|
return { hasPermissions: false, httpErrorCode: 401 }
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
ServerRoles,
|
ServerRoles,
|
||||||
StreamRoles
|
StreamRoles
|
||||||
} from '@/modules/core/helpers/mainConstants'
|
} from '@/modules/core/helpers/mainConstants'
|
||||||
import { getRoles } from '@/modules/shared'
|
import { getRoles } from '@/modules/shared/roles'
|
||||||
import { getStream } from '@/modules/core/services/streams'
|
import { getStream } from '@/modules/core/services/streams'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -114,17 +114,13 @@ export function validateRole<T extends AvailableRoles>({
|
|||||||
// role validation has nothing to do with auth...
|
// role validation has nothing to do with auth...
|
||||||
//this check doesn't belong here, move it out to the auth pipeline
|
//this check doesn't belong here, move it out to the auth pipeline
|
||||||
if (!context.auth)
|
if (!context.auth)
|
||||||
return authFailed(
|
return authFailed(context, new UnauthorizedError('Must provide an auth token'))
|
||||||
context,
|
|
||||||
new UnauthorizedError('Cannot validate role without auth')
|
|
||||||
)
|
|
||||||
|
|
||||||
const contextRole = roleGetter(context)
|
const contextRole = roleGetter(context)
|
||||||
if (!contextRole)
|
const missingRoleMessage = `You do not have the required ${
|
||||||
return authFailed(
|
requiredRole.split(':')[0]
|
||||||
context,
|
} role`
|
||||||
new ForbiddenError('You do not have the required role')
|
if (!contextRole) return authFailed(context, new ForbiddenError(missingRoleMessage))
|
||||||
)
|
|
||||||
|
|
||||||
const role = roles.find((r) => r.name === requiredRole)
|
const role = roles.find((r) => r.name === requiredRole)
|
||||||
const myRole = roles.find((r) => r.name === contextRole)
|
const myRole = roles.find((r) => r.name === contextRole)
|
||||||
@@ -138,7 +134,7 @@ export function validateRole<T extends AvailableRoles>({
|
|||||||
return authFailed(context, new ForbiddenError('Your role is not valid'))
|
return authFailed(context, new ForbiddenError('Your role is not valid'))
|
||||||
if (myRole.name === iddqd || myRole.weight >= role.weight)
|
if (myRole.name === iddqd || myRole.weight >= role.weight)
|
||||||
return authSuccess(context)
|
return authSuccess(context)
|
||||||
return authFailed(context, new ForbiddenError('You do not have the required role'))
|
return authFailed(context, new ForbiddenError(missingRoleMessage))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,3 +273,16 @@ export const streamReadPermissions = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
if (adminOverrideEnabled()) streamReadPermissions.push(allowForServerAdmins)
|
if (adminOverrideEnabled()) streamReadPermissions.push(allowForServerAdmins)
|
||||||
|
|
||||||
|
export const throwForNotHavingServerRole = async (
|
||||||
|
context: AuthContext,
|
||||||
|
requiredRole: ServerRoles
|
||||||
|
) => {
|
||||||
|
const { authResult } = await validateServerRole({ requiredRole })({
|
||||||
|
context,
|
||||||
|
authResult: { authorized: false }
|
||||||
|
})
|
||||||
|
if (authHasFailed(authResult))
|
||||||
|
throw authResult.error ?? new Error('Auth failed without an error')
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { BaseError } from '@/modules/shared/errors/base'
|
import { BaseError } from '@/modules/shared/errors/base'
|
||||||
|
|
||||||
export class ForbiddenError extends BaseError {
|
export class ForbiddenError extends BaseError {
|
||||||
static code = 'FORBIDDEN_ERROR'
|
static code = 'FORBIDDEN'
|
||||||
static defaultMessage = 'Access to the resource is forbidden'
|
static defaultMessage = 'Access to the resource is forbidden'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,39 +11,9 @@ const { Roles } = require('@speckle/shared')
|
|||||||
const { adminOverrideEnabled } = require('@/modules/shared/helpers/envHelper')
|
const { adminOverrideEnabled } = require('@/modules/shared/helpers/envHelper')
|
||||||
|
|
||||||
const { ServerAcl: ServerAclSchema } = require('@/modules/core/dbSchema')
|
const { ServerAcl: ServerAclSchema } = require('@/modules/core/dbSchema')
|
||||||
|
const { getRoles } = require('@/modules/shared/roles')
|
||||||
const ServerAcl = () => ServerAclSchema.knex()
|
const ServerAcl = () => ServerAclSchema.knex()
|
||||||
|
|
||||||
let roles
|
|
||||||
|
|
||||||
const getRoles = async () => {
|
|
||||||
if (roles) return roles
|
|
||||||
roles = await knex('user_roles').select('*')
|
|
||||||
return roles
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates a server role against the req's context object.
|
|
||||||
* @param {import('@/modules/shared/helpers/typeHelper').GraphQLContext} context
|
|
||||||
* @param {string} requiredRole
|
|
||||||
*/
|
|
||||||
async function validateServerRole(context, requiredRole) {
|
|
||||||
const roles = await getRoles()
|
|
||||||
|
|
||||||
if (!context.auth) throw new ForbiddenError('You must provide an auth token.')
|
|
||||||
|
|
||||||
const role = roles.find((r) => r.name === requiredRole)
|
|
||||||
const myRole = roles.find((r) => r.name === context.role)
|
|
||||||
|
|
||||||
if (!role) throw new ApolloError('Invalid server role specified')
|
|
||||||
if (!myRole)
|
|
||||||
throw new ForbiddenError('You do not have the required server role (null)')
|
|
||||||
|
|
||||||
if (context.role === 'server:admin') return true
|
|
||||||
if (myRole.weight >= role.weight) return true
|
|
||||||
|
|
||||||
throw new ForbiddenError('You do not have the required server role')
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the scope against a list of scopes of the current session.
|
* Validates the scope against a list of scopes of the current session.
|
||||||
* @param {string[]|undefined} scopes
|
* @param {string[]|undefined} scopes
|
||||||
@@ -65,7 +35,7 @@ async function validateScopes(scopes, scope) {
|
|||||||
async function authorizeResolver(userId, resourceId, requiredRole) {
|
async function authorizeResolver(userId, resourceId, requiredRole) {
|
||||||
userId = userId || null
|
userId = userId || null
|
||||||
|
|
||||||
if (!roles) roles = await knex('user_roles').select('*')
|
const roles = await getRoles()
|
||||||
|
|
||||||
// TODO: Cache these results with a TTL of 1 mins or so, it's pointless to query the db every time we get a ping.
|
// TODO: Cache these results with a TTL of 1 mins or so, it's pointless to query the db every time we get a ping.
|
||||||
|
|
||||||
@@ -129,7 +99,7 @@ async function registerOrUpdateRole(role) {
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
registerOrUpdateScope,
|
registerOrUpdateScope,
|
||||||
registerOrUpdateRole,
|
registerOrUpdateRole,
|
||||||
validateServerRole,
|
// validateServerRole,
|
||||||
validateScopes,
|
validateScopes,
|
||||||
authorizeResolver,
|
authorizeResolver,
|
||||||
pubsub,
|
pubsub,
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
const knex = require(`@/db/knex`)
|
||||||
|
let roles
|
||||||
|
|
||||||
|
const getRoles = async () => {
|
||||||
|
if (roles) return roles
|
||||||
|
roles = await knex('user_roles').select('*')
|
||||||
|
return roles
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getRoles
|
||||||
|
}
|
||||||
@@ -66,7 +66,7 @@ describe('AuthZ @shared', () => {
|
|||||||
describe('Role validation', () => {
|
describe('Role validation', () => {
|
||||||
const rolesLookup = async () => [
|
const rolesLookup = async () => [
|
||||||
{ name: '1', weight: 1 },
|
{ name: '1', weight: 1 },
|
||||||
{ name: '2', weight: 2 },
|
{ name: 'server:2', weight: 2 },
|
||||||
{ name: '3', weight: 3 },
|
{ name: '3', weight: 3 },
|
||||||
{ name: 'goku', weight: 9001 },
|
{ name: 'goku', weight: 9001 },
|
||||||
{ name: '42', weight: 42 }
|
{ name: '42', weight: 42 }
|
||||||
@@ -75,15 +75,18 @@ describe('AuthZ @shared', () => {
|
|||||||
const testData = [
|
const testData = [
|
||||||
{
|
{
|
||||||
name: 'Having lower privileged role than required results auth failed',
|
name: 'Having lower privileged role than required results auth failed',
|
||||||
requiredRole: '2',
|
requiredRole: 'server:2',
|
||||||
context: { auth: true, role: '1' },
|
context: { auth: true, role: '1' },
|
||||||
expectedResult: authFailed(null, new SFE('You do not have the required role'))
|
expectedResult: authFailed(
|
||||||
|
null,
|
||||||
|
new SFE('You do not have the required server role')
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Not having auth fails role validation',
|
name: 'Not having auth fails role validation',
|
||||||
requiredRole: '2',
|
requiredRole: 'server:2',
|
||||||
context: { auth: false },
|
context: { auth: false },
|
||||||
expectedResult: authFailed(null, new SUE('Cannot validate role without auth'))
|
expectedResult: authFailed(null, new SUE('Must provide an auth token'))
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Requiring a junk role fails auth',
|
name: 'Requiring a junk role fails auth',
|
||||||
@@ -93,7 +96,7 @@ describe('AuthZ @shared', () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Having a junk role fails auth',
|
name: 'Having a junk role fails auth',
|
||||||
requiredRole: '2',
|
requiredRole: 'server:2',
|
||||||
context: { auth: true, role: 'iddqd' },
|
context: { auth: true, role: 'iddqd' },
|
||||||
expectedResult: authFailed(null, new SFE('Your role is not valid'))
|
expectedResult: authFailed(null, new SFE('Your role is not valid'))
|
||||||
},
|
},
|
||||||
@@ -101,7 +104,10 @@ describe('AuthZ @shared', () => {
|
|||||||
name: 'Not having the required level fails',
|
name: 'Not having the required level fails',
|
||||||
requiredRole: 'goku',
|
requiredRole: 'goku',
|
||||||
context: { auth: true, role: '3' },
|
context: { auth: true, role: '3' },
|
||||||
expectedResult: authFailed(null, new SFE('You do not have the required role'))
|
expectedResult: authFailed(
|
||||||
|
null,
|
||||||
|
new SFE('You do not have the required goku role')
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Having the god mode role defeats even higher privilege requirement',
|
name: 'Having the god mode role defeats even higher privilege requirement',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
const { validateServerRole, validateScopes } = require('@/modules/shared')
|
const { validateScopes } = require('@/modules/shared')
|
||||||
const {
|
const {
|
||||||
getStreamHistory,
|
getStreamHistory,
|
||||||
getCommitHistory,
|
getCommitHistory,
|
||||||
@@ -10,6 +10,8 @@ const {
|
|||||||
getTotalObjectCount,
|
getTotalObjectCount,
|
||||||
getTotalUserCount
|
getTotalUserCount
|
||||||
} = require('../../services')
|
} = require('../../services')
|
||||||
|
const { Roles, Scopes } = require('@speckle/shared')
|
||||||
|
const { throwForNotHavingServerRole } = require('@/modules/shared/authz')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
Query: {
|
Query: {
|
||||||
@@ -17,8 +19,8 @@ module.exports = {
|
|||||||
* @deprecated('Use admin.serverStatistics')
|
* @deprecated('Use admin.serverStatistics')
|
||||||
*/
|
*/
|
||||||
async serverStats(parent, args, context) {
|
async serverStats(parent, args, context) {
|
||||||
await validateServerRole(context, 'server:admin')
|
await throwForNotHavingServerRole(context, Roles.Server.Admin)
|
||||||
await validateScopes(context.scopes, 'server:stats')
|
await validateScopes(context.scopes, Scopes.Server.Stats)
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const {
|
|||||||
getTotalObjectCount,
|
getTotalObjectCount,
|
||||||
getTotalUserCount
|
getTotalUserCount
|
||||||
} = require('../services')
|
} = require('../services')
|
||||||
|
const { Scopes } = require('@speckle/shared')
|
||||||
|
|
||||||
const params = { numUsers: 25, numStreams: 30, numObjects: 100, numCommits: 100 }
|
const params = { numUsers: 25, numStreams: 30, numObjects: 100, numCommits: 100 }
|
||||||
|
|
||||||
@@ -126,24 +127,24 @@ describe('Server stats api @stats-api', function () {
|
|||||||
adminUser.goodToken = `Bearer ${await createPersonalAccessToken(
|
adminUser.goodToken = `Bearer ${await createPersonalAccessToken(
|
||||||
adminUser.id,
|
adminUser.id,
|
||||||
'test token user A',
|
'test token user A',
|
||||||
['server:stats']
|
[Scopes.Server.Stats]
|
||||||
)}`
|
)}`
|
||||||
adminUser.badToken = `Bearer ${await createPersonalAccessToken(
|
adminUser.badToken = `Bearer ${await createPersonalAccessToken(
|
||||||
adminUser.id,
|
adminUser.id,
|
||||||
'test token user A',
|
'test token user A',
|
||||||
['streams:read']
|
[Scopes.Streams.Read]
|
||||||
)}`
|
)}`
|
||||||
|
|
||||||
notAdminUser.id = await createUser(notAdminUser)
|
notAdminUser.id = await createUser(notAdminUser)
|
||||||
notAdminUser.goodToken = `Bearer ${await createPersonalAccessToken(
|
notAdminUser.goodToken = `Bearer ${await createPersonalAccessToken(
|
||||||
notAdminUser.id,
|
notAdminUser.id,
|
||||||
'test token user A',
|
'test token user A',
|
||||||
['server:stats']
|
[Scopes.Server.Stats]
|
||||||
)}`
|
)}`
|
||||||
notAdminUser.badToken = `Bearer ${await createPersonalAccessToken(
|
notAdminUser.badToken = `Bearer ${await createPersonalAccessToken(
|
||||||
notAdminUser.id,
|
notAdminUser.id,
|
||||||
'test token user A',
|
'test token user A',
|
||||||
['streams:read']
|
[Scopes.Streams.Read]
|
||||||
)}`
|
)}`
|
||||||
|
|
||||||
await seedDb(params)
|
await seedDb(params)
|
||||||
|
|||||||
@@ -10,11 +10,12 @@ const {
|
|||||||
getLastWebhookEvents,
|
getLastWebhookEvents,
|
||||||
getWebhookEventsCount
|
getWebhookEventsCount
|
||||||
} = require('../../services/webhooks')
|
} = require('../../services/webhooks')
|
||||||
|
const { Roles } = require('@speckle/shared')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
Stream: {
|
Stream: {
|
||||||
async webhooks(parent, args, context) {
|
async webhooks(parent, args, context) {
|
||||||
await authorizeResolver(context.userId, parent.id, 'stream:owner')
|
await authorizeResolver(context.userId, parent.id, Roles.Stream.Owner)
|
||||||
|
|
||||||
if (args.id) {
|
if (args.id) {
|
||||||
const wh = await getWebhook({ id: args.id })
|
const wh = await getWebhook({ id: args.id })
|
||||||
@@ -41,7 +42,7 @@ module.exports = {
|
|||||||
|
|
||||||
Mutation: {
|
Mutation: {
|
||||||
async webhookCreate(parent, args, context) {
|
async webhookCreate(parent, args, context) {
|
||||||
await authorizeResolver(context.userId, args.webhook.streamId, 'stream:owner')
|
await authorizeResolver(context.userId, args.webhook.streamId, Roles.Stream.Owner)
|
||||||
|
|
||||||
const id = await createWebhook({
|
const id = await createWebhook({
|
||||||
streamId: args.webhook.streamId,
|
streamId: args.webhook.streamId,
|
||||||
@@ -55,7 +56,7 @@ module.exports = {
|
|||||||
return id
|
return id
|
||||||
},
|
},
|
||||||
async webhookUpdate(parent, args, context) {
|
async webhookUpdate(parent, args, context) {
|
||||||
await authorizeResolver(context.userId, args.webhook.streamId, 'stream:owner')
|
await authorizeResolver(context.userId, args.webhook.streamId, Roles.Stream.Owner)
|
||||||
|
|
||||||
const wh = await getWebhook({ id: args.webhook.id })
|
const wh = await getWebhook({ id: args.webhook.id })
|
||||||
if (args.webhook.streamId !== wh.streamId)
|
if (args.webhook.streamId !== wh.streamId)
|
||||||
@@ -75,7 +76,7 @@ module.exports = {
|
|||||||
return !!updated
|
return !!updated
|
||||||
},
|
},
|
||||||
async webhookDelete(parent, args, context) {
|
async webhookDelete(parent, args, context) {
|
||||||
await authorizeResolver(context.userId, args.webhook.streamId, 'stream:owner')
|
await authorizeResolver(context.userId, args.webhook.streamId, Roles.Stream.Owner)
|
||||||
|
|
||||||
const wh = await getWebhook({ id: args.webhook.id })
|
const wh = await getWebhook({ id: args.webhook.id })
|
||||||
if (args.webhook.streamId !== wh.streamId)
|
if (args.webhook.streamId !== wh.streamId)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const {
|
|||||||
} = require('../services/webhooks')
|
} = require('../services/webhooks')
|
||||||
const { createUser } = require('../../core/services/users')
|
const { createUser } = require('../../core/services/users')
|
||||||
const { createStream, grantPermissionsStream } = require('../../core/services/streams')
|
const { createStream, grantPermissionsStream } = require('../../core/services/streams')
|
||||||
|
const { Scopes, Roles } = require('@speckle/shared')
|
||||||
|
|
||||||
describe('Webhooks @webhooks', () => {
|
describe('Webhooks @webhooks', () => {
|
||||||
let server, sendRequest, app
|
let server, sendRequest, app
|
||||||
@@ -139,17 +140,17 @@ describe('Webhooks @webhooks', () => {
|
|||||||
userOne.token = `Bearer ${await createPersonalAccessToken(
|
userOne.token = `Bearer ${await createPersonalAccessToken(
|
||||||
userOne.id,
|
userOne.id,
|
||||||
'userOne test token',
|
'userOne test token',
|
||||||
['streams:read', 'streams:write']
|
[Scopes.Streams.Read, Scopes.Streams.Write]
|
||||||
)}`
|
)}`
|
||||||
userTwo.token = `Bearer ${await createPersonalAccessToken(
|
userTwo.token = `Bearer ${await createPersonalAccessToken(
|
||||||
userTwo.id,
|
userTwo.id,
|
||||||
'userTwo test token',
|
'userTwo test token',
|
||||||
['streams:read', 'streams:write']
|
[Scopes.Streams.Read, Scopes.Streams.Write]
|
||||||
)}`
|
)}`
|
||||||
await grantPermissionsStream({
|
await grantPermissionsStream({
|
||||||
streamId: streamTwo.id,
|
streamId: streamTwo.id,
|
||||||
userId: userOne.id,
|
userId: userOne.id,
|
||||||
role: 'stream:contributor'
|
role: Roles.Stream.Contributor
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
const knex = require('@/db/knex')
|
const knex = require('@/db/knex')
|
||||||
const { logger } = require('@/logging/logging')
|
const { logger } = require('@/logging/logging')
|
||||||
const roles = require('@/modules/core/roles.js')
|
const roles = require('@/modules/core/roles.js')
|
||||||
|
const { Roles } = require('@speckle/shared')
|
||||||
|
|
||||||
const Users = () => knex('users')
|
const Users = () => knex('users')
|
||||||
|
|
||||||
@@ -35,10 +36,10 @@ const migrateColumnValue = async (tableName, columnName, oldUser, newUser) => {
|
|||||||
const serverAclMigration = async ({ lowerUser, upperUser }) => {
|
const serverAclMigration = async ({ lowerUser, upperUser }) => {
|
||||||
const oldAcl = await knex('server_acl').where({ userId: upperUser.id }).first()
|
const oldAcl = await knex('server_acl').where({ userId: upperUser.id }).first()
|
||||||
// if the old user was admin, make the target admin too
|
// if the old user was admin, make the target admin too
|
||||||
if (oldAcl.role === 'server:admin')
|
if (oldAcl.role === Roles.Server.Admin)
|
||||||
await knex('server_acl')
|
await knex('server_acl')
|
||||||
.where({ userId: lowerUser.id })
|
.where({ userId: lowerUser.id })
|
||||||
.update({ role: 'server:admin' })
|
.update({ role: Roles.Server.Admin })
|
||||||
}
|
}
|
||||||
|
|
||||||
const _migrateSingleStreamAccess = async ({ lowerUser, upperStreamAcl }) => {
|
const _migrateSingleStreamAccess = async ({ lowerUser, upperStreamAcl }) => {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ const { init } = require(`@/app`)
|
|||||||
const request = require('supertest')
|
const request = require('supertest')
|
||||||
const { exit } = require('yargs')
|
const { exit } = require('yargs')
|
||||||
const { logger } = require('@/logging/logging')
|
const { logger } = require('@/logging/logging')
|
||||||
|
const { Scopes } = require('@speckle/shared')
|
||||||
|
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
const testStream = {
|
const testStream = {
|
||||||
@@ -30,14 +31,14 @@ const main = async () => {
|
|||||||
userA.id,
|
userA.id,
|
||||||
'test token user A',
|
'test token user A',
|
||||||
[
|
[
|
||||||
'streams:read',
|
Scopes.Streams.Read,
|
||||||
'streams:write',
|
Scopes.Streams.Write,
|
||||||
'users:read',
|
Scopes.Users.Read,
|
||||||
'users:email',
|
Scopes.Users.Email,
|
||||||
'tokens:write',
|
Scopes.Tokens.Write,
|
||||||
'tokens:read',
|
Scopes.Tokens.Read,
|
||||||
'profile:read',
|
Scopes.Profile.Read,
|
||||||
'profile:email'
|
Scopes.Profile.Email
|
||||||
]
|
]
|
||||||
)}`
|
)}`
|
||||||
|
|
||||||
|
|||||||
@@ -1874,6 +1874,7 @@ export type ServerInfo = {
|
|||||||
canonicalUrl?: Maybe<Scalars['String']>;
|
canonicalUrl?: Maybe<Scalars['String']>;
|
||||||
company?: Maybe<Scalars['String']>;
|
company?: Maybe<Scalars['String']>;
|
||||||
description?: Maybe<Scalars['String']>;
|
description?: Maybe<Scalars['String']>;
|
||||||
|
guestModeEnabled: Scalars['Boolean'];
|
||||||
inviteOnly?: Maybe<Scalars['Boolean']>;
|
inviteOnly?: Maybe<Scalars['Boolean']>;
|
||||||
name: Scalars['String'];
|
name: Scalars['String'];
|
||||||
roles: Array<Maybe<Role>>;
|
roles: Array<Maybe<Role>>;
|
||||||
@@ -1886,6 +1887,7 @@ export type ServerInfoUpdateInput = {
|
|||||||
adminContact?: InputMaybe<Scalars['String']>;
|
adminContact?: InputMaybe<Scalars['String']>;
|
||||||
company?: InputMaybe<Scalars['String']>;
|
company?: InputMaybe<Scalars['String']>;
|
||||||
description?: InputMaybe<Scalars['String']>;
|
description?: InputMaybe<Scalars['String']>;
|
||||||
|
guestModeEnabled?: InputMaybe<Scalars['Boolean']>;
|
||||||
inviteOnly?: InputMaybe<Scalars['Boolean']>;
|
inviteOnly?: InputMaybe<Scalars['Boolean']>;
|
||||||
name: Scalars['String'];
|
name: Scalars['String'];
|
||||||
termsOfService?: InputMaybe<Scalars['String']>;
|
termsOfService?: InputMaybe<Scalars['String']>;
|
||||||
@@ -1906,6 +1908,7 @@ export type ServerInviteCreateInput = {
|
|||||||
export enum ServerRole {
|
export enum ServerRole {
|
||||||
ServerAdmin = 'SERVER_ADMIN',
|
ServerAdmin = 'SERVER_ADMIN',
|
||||||
ServerArchivedUser = 'SERVER_ARCHIVED_USER',
|
ServerArchivedUser = 'SERVER_ARCHIVED_USER',
|
||||||
|
ServerGuest = 'SERVER_GUEST',
|
||||||
ServerUser = 'SERVER_USER'
|
ServerUser = 'SERVER_USER'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export const Roles = Object.freeze(<const>{
|
|||||||
Server: {
|
Server: {
|
||||||
Admin: 'server:admin',
|
Admin: 'server:admin',
|
||||||
User: 'server:user',
|
User: 'server:user',
|
||||||
|
Guest: 'server:guest',
|
||||||
ArchivedUser: 'server:archived-user'
|
ArchivedUser: 'server:archived-user'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -47,6 +48,10 @@ export const Scopes = Object.freeze(<const>{
|
|||||||
Tokens: {
|
Tokens: {
|
||||||
Read: 'tokens:read',
|
Read: 'tokens:read',
|
||||||
Write: 'tokens:write'
|
Write: 'tokens:write'
|
||||||
|
},
|
||||||
|
Apps: {
|
||||||
|
Read: 'apps:read',
|
||||||
|
Write: 'apps:write'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user