@ossy/users 1.13.0 → 1.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +12 -6
- package/src/create-api-token.action.js +1 -35
- package/src/create-api-token.task.js +34 -0
- package/src/get-api-tokens.action.js +1 -20
- package/src/get-api-tokens.task.js +19 -0
- package/src/get-current-user-history.action.js +1 -11
- package/src/get-current-user-history.task.js +10 -0
- package/src/index.js +7 -4
- package/src/invalidate-api-token.action.js +1 -16
- package/src/invalidate-api-token.task.js +15 -0
- package/src/join-workspace.action.js +1 -18
- package/src/join-workspace.task.js +17 -0
- package/src/leave-workspace.action.js +1 -17
- package/src/leave-workspace.task.js +16 -0
- package/src/server.js +4 -0
- package/src/update-details.action.js +1 -29
- package/src/update-details.task.js +28 -0
- package/src/users.spec.js +153 -202
- package/src/users.startup.js +1 -1
- package/src/list.api.js +0 -23
- package/src/me-history.api.js +0 -23
- package/src/me-token.api.js +0 -32
- package/src/me-tokens.api.js +0 -35
- package/src/me.api.js +0 -41
- package/src/workspace-membership.api.js +0 -72
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossy/users",
|
|
3
|
-
"description": "User domain
|
|
4
|
-
"version": "1.13.
|
|
3
|
+
"description": "User domain - aggregate, events, and validators for the Ossy user model",
|
|
4
|
+
"version": "1.13.2",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./src/index.js",
|
|
8
8
|
"module": "./src/index.js",
|
|
9
9
|
"exports": {
|
|
10
|
-
".": "./src/index.js"
|
|
10
|
+
".": "./src/index.js",
|
|
11
|
+
"./server": "./src/server.js"
|
|
11
12
|
},
|
|
12
13
|
"scripts": {
|
|
13
14
|
"test": "NODE_OPTIONS=--experimental-vm-modules jest --verbose"
|
|
@@ -17,17 +18,22 @@
|
|
|
17
18
|
"ossy": {
|
|
18
19
|
"src": "./src"
|
|
19
20
|
},
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"@ossy/authentication": ">=1.0.0",
|
|
23
|
+
"@ossy/sdk-react": ">=1.0.0",
|
|
24
|
+
"react": ">=19.0.0 <20.0.0"
|
|
25
|
+
},
|
|
20
26
|
"publishConfig": {
|
|
21
27
|
"access": "public",
|
|
22
28
|
"registry": "https://registry.npmjs.org"
|
|
23
29
|
},
|
|
24
30
|
"dependencies": {
|
|
25
|
-
"@ossy/observability": "^1.8.
|
|
31
|
+
"@ossy/observability": "^1.8.2",
|
|
26
32
|
"nanoid": "^5.1.11"
|
|
27
33
|
},
|
|
28
34
|
"devDependencies": {
|
|
29
35
|
"@jest/globals": "^30.2.0",
|
|
30
|
-
"@ossy/platform": "^1.39.
|
|
36
|
+
"@ossy/platform": "^1.39.2",
|
|
31
37
|
"casual": "^1.6.2",
|
|
32
38
|
"jest": "^30.2.0"
|
|
33
39
|
},
|
|
@@ -35,5 +41,5 @@
|
|
|
35
41
|
"/src",
|
|
36
42
|
"README.md"
|
|
37
43
|
],
|
|
38
|
-
"gitHead": "
|
|
44
|
+
"gitHead": "2b8745d57fee8b6c08787e2755b4df489291a5cd"
|
|
39
45
|
}
|
|
@@ -1,35 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { Aggregate } from '@ossy/event-store'
|
|
3
|
-
import { Token, TokenEvents } from '@ossy/tokens'
|
|
4
|
-
import { ConfigService } from '@ossy/platform'
|
|
5
|
-
import { createLogger } from '@ossy/observability'
|
|
6
|
-
|
|
7
|
-
const log = createLogger('users/create-api-token')
|
|
8
|
-
|
|
9
|
-
const SECONDS_IN_100_YEARS = 60 * 60 * 24 * 365 * 100
|
|
10
|
-
|
|
11
|
-
export const id = 'users/create-api-token'
|
|
12
|
-
export const access = 'authenticated'
|
|
13
|
-
|
|
14
|
-
export async function run({ payload, req }) {
|
|
15
|
-
const createdBy = payload?.userId ?? req?.userId
|
|
16
|
-
const name = payload?.name
|
|
17
|
-
const description = payload?.description
|
|
18
|
-
|
|
19
|
-
if (!name || typeof name !== 'string') {
|
|
20
|
-
log.debug('[users/create-api-token] No name provided')
|
|
21
|
-
throw Object.assign(new Error('No name provided'), { status: 400 })
|
|
22
|
-
}
|
|
23
|
-
if (!description || typeof description !== 'string') {
|
|
24
|
-
log.debug('[users/create-api-token] No description provided')
|
|
25
|
-
throw Object.assign(new Error('No description provided'), { status: 400 })
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const expiresIn = SECONDS_IN_100_YEARS
|
|
29
|
-
const expiresAt = Date.now() + expiresIn * 1000
|
|
30
|
-
const token = jwt.sign({ sub: createdBy, type: 'Api' }, ConfigService.TokenSecret, { expiresIn })
|
|
31
|
-
|
|
32
|
-
const event = TokenEvents.Created({ name, description, createdBy, type: 'Api', subject: createdBy, token, expiresAt })
|
|
33
|
-
|
|
34
|
-
return Aggregate.Of(Token, event).then(Aggregate.View())
|
|
35
|
-
}
|
|
1
|
+
export const metadata = { id: 'users/create-api-token', access: 'authenticated' }
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import jwt from 'jsonwebtoken'
|
|
2
|
+
import { Aggregate } from '@ossy/event-store'
|
|
3
|
+
import { Token, TokenEvents } from '@ossy/tokens/server'
|
|
4
|
+
import { ConfigService } from '@ossy/platform'
|
|
5
|
+
import { createLogger } from '@ossy/observability'
|
|
6
|
+
|
|
7
|
+
export const metadata = { id: 'users/create-api-token' }
|
|
8
|
+
|
|
9
|
+
const log = createLogger('users/create-api-token')
|
|
10
|
+
|
|
11
|
+
const SECONDS_IN_100_YEARS = 60 * 60 * 24 * 365 * 100
|
|
12
|
+
|
|
13
|
+
export async function run({ payload, req }) {
|
|
14
|
+
const createdBy = payload?.userId ?? req?.userId
|
|
15
|
+
const name = payload?.name
|
|
16
|
+
const description = payload?.description
|
|
17
|
+
|
|
18
|
+
if (!name || typeof name !== 'string') {
|
|
19
|
+
log.debug('[users/create-api-token] No name provided')
|
|
20
|
+
throw Object.assign(new Error('No name provided'), { status: 400 })
|
|
21
|
+
}
|
|
22
|
+
if (!description || typeof description !== 'string') {
|
|
23
|
+
log.debug('[users/create-api-token] No description provided')
|
|
24
|
+
throw Object.assign(new Error('No description provided'), { status: 400 })
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const expiresIn = SECONDS_IN_100_YEARS
|
|
28
|
+
const expiresAt = Date.now() + expiresIn * 1000
|
|
29
|
+
const token = jwt.sign({ sub: createdBy, type: 'Api' }, ConfigService.TokenSecret, { expiresIn })
|
|
30
|
+
|
|
31
|
+
const event = TokenEvents.Created({ name, description, createdBy, type: 'Api', subject: createdBy, token, expiresAt })
|
|
32
|
+
|
|
33
|
+
return Aggregate.Of(Token, event).then(Aggregate.View())
|
|
34
|
+
}
|
|
@@ -1,20 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { createLogger } from '@ossy/observability'
|
|
3
|
-
|
|
4
|
-
const log = createLogger('users/get-api-tokens')
|
|
5
|
-
|
|
6
|
-
export const id = 'users/get-api-tokens'
|
|
7
|
-
export const access = 'authenticated'
|
|
8
|
-
|
|
9
|
-
export async function run({ payload, req }) {
|
|
10
|
-
const userId = payload?.userId ?? req?.userId
|
|
11
|
-
|
|
12
|
-
log.info('[users/get-api-tokens] Fetching API tokens for user')
|
|
13
|
-
const tokens = await Aggregate.Collection.find({
|
|
14
|
-
type: 'Token',
|
|
15
|
-
'state.subject': userId,
|
|
16
|
-
'state.type': 'Api',
|
|
17
|
-
}, { state: true }).toArray().then(docs => docs.map(d => d.state))
|
|
18
|
-
|
|
19
|
-
return tokens.map(({ token: _secret, ...meta }) => meta)
|
|
20
|
-
}
|
|
1
|
+
export const metadata = { id: 'users/get-api-tokens', access: 'authenticated' }
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Aggregate } from '@ossy/event-store'
|
|
2
|
+
import { createLogger } from '@ossy/observability'
|
|
3
|
+
|
|
4
|
+
export const metadata = { id: 'users/get-api-tokens' }
|
|
5
|
+
|
|
6
|
+
const log = createLogger('users/get-api-tokens')
|
|
7
|
+
|
|
8
|
+
export async function run({ payload, req }) {
|
|
9
|
+
const userId = payload?.userId ?? req?.userId
|
|
10
|
+
|
|
11
|
+
log.info('[users/get-api-tokens] Fetching API tokens for user')
|
|
12
|
+
const tokens = await Aggregate.Collection.find({
|
|
13
|
+
type: 'Token',
|
|
14
|
+
'state.subject': userId,
|
|
15
|
+
'state.type': 'Api',
|
|
16
|
+
}, { state: true }).toArray().then(docs => docs.map(d => d.state))
|
|
17
|
+
|
|
18
|
+
return tokens.map(({ token: _secret, ...meta }) => meta)
|
|
19
|
+
}
|
|
@@ -1,11 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { User } from '@ossy/users'
|
|
3
|
-
|
|
4
|
-
export const id = 'users/get-current-user-history'
|
|
5
|
-
export const access = 'authenticated'
|
|
6
|
-
|
|
7
|
-
export async function run({ payload, req }) {
|
|
8
|
-
const userId = payload?.userId ?? req?.userId
|
|
9
|
-
const events = await EventStore.GetEventStream({ aggregateId: userId, fromVersion: 0 })
|
|
10
|
-
return User.History(events)
|
|
11
|
-
}
|
|
1
|
+
export const metadata = { id: 'users/get-current-user-history', access: 'authenticated' }
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { EventStore } from '@ossy/event-store'
|
|
2
|
+
import { User } from '@ossy/users/server'
|
|
3
|
+
|
|
4
|
+
export const metadata = { id: 'users/get-current-user-history' }
|
|
5
|
+
|
|
6
|
+
export async function run({ payload, req }) {
|
|
7
|
+
const userId = payload?.userId ?? req?.userId
|
|
8
|
+
const events = await EventStore.GetEventStream({ aggregateId: userId, fromVersion: 0 })
|
|
9
|
+
return User.History(events)
|
|
10
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
-
export
|
|
1
|
+
export { metadata as GetUserApiTokens } from './get-api-tokens.action.js'
|
|
2
|
+
export { metadata as CreateUserApiToken } from './create-api-token.action.js'
|
|
3
|
+
export { metadata as InvalidateApiToken } from './invalidate-api-token.action.js'
|
|
4
|
+
export { metadata as UpdateUserDetails } from './update-details.action.js'
|
|
5
|
+
export { metadata as GetCurrentUserHistory } from './get-current-user-history.action.js'
|
|
6
|
+
export { metadata as JoinWorkspace } from './join-workspace.action.js'
|
|
7
|
+
export { metadata as LeaveWorkspace } from './leave-workspace.action.js'
|
|
@@ -1,16 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { Token, TokenEvents } from '@ossy/tokens'
|
|
3
|
-
|
|
4
|
-
export const id = 'users/invalidate-api-token'
|
|
5
|
-
export const access = 'authenticated'
|
|
6
|
-
|
|
7
|
-
export async function run({ payload, req }) {
|
|
8
|
-
const createdBy = payload?.userId ?? req?.userId
|
|
9
|
-
const tokenId = payload?.tokenId ?? req?.params?.tokenId
|
|
10
|
-
|
|
11
|
-
if (!tokenId) throw Object.assign(new Error('tokenId is required'), { status: 400 })
|
|
12
|
-
|
|
13
|
-
const event = TokenEvents.Revoked({ createdBy })
|
|
14
|
-
await Aggregate.Of(Token, tokenId).then(Aggregate.Add(event))
|
|
15
|
-
return ''
|
|
16
|
-
}
|
|
1
|
+
export const metadata = { id: 'users/invalidate-api-token', access: 'authenticated' }
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Aggregate } from '@ossy/event-store'
|
|
2
|
+
import { Token, TokenEvents } from '@ossy/tokens/server'
|
|
3
|
+
|
|
4
|
+
export const metadata = { id: 'users/invalidate-api-token' }
|
|
5
|
+
|
|
6
|
+
export async function run({ payload, req }) {
|
|
7
|
+
const createdBy = payload?.userId ?? req?.userId
|
|
8
|
+
const tokenId = payload?.tokenId ?? req?.params?.tokenId
|
|
9
|
+
|
|
10
|
+
if (!tokenId) throw Object.assign(new Error('tokenId is required'), { status: 400 })
|
|
11
|
+
|
|
12
|
+
const event = TokenEvents.Revoked({ createdBy })
|
|
13
|
+
await Aggregate.Of(Token, tokenId).then(Aggregate.Add(event))
|
|
14
|
+
return ''
|
|
15
|
+
}
|
|
@@ -1,18 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { User, UsersEvents } from '@ossy/users'
|
|
3
|
-
|
|
4
|
-
export const id = 'users/join-workspace'
|
|
5
|
-
export const access = 'authenticated'
|
|
6
|
-
|
|
7
|
-
export async function run({ payload, req }) {
|
|
8
|
-
const userId = payload?.targetUserId ?? payload?.userId ?? req?.userId
|
|
9
|
-
const workspaceId = payload?.workspaceId
|
|
10
|
-
const createdBy = payload?.createdBy ?? userId
|
|
11
|
-
|
|
12
|
-
if (!workspaceId) throw Object.assign(new Error('workspaceId is required'), { status: 400 })
|
|
13
|
-
|
|
14
|
-
await Aggregate.Of(User, userId)
|
|
15
|
-
.then(Aggregate.Add(UsersEvents.WorkspaceJoined({ workspaceId, createdBy })))
|
|
16
|
-
.then(Aggregate.Save())
|
|
17
|
-
return null
|
|
18
|
-
}
|
|
1
|
+
export const metadata = { id: 'users/join-workspace', access: 'authenticated' }
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Aggregate } from '@ossy/event-store'
|
|
2
|
+
import { User, UsersEvents } from '@ossy/users/server'
|
|
3
|
+
|
|
4
|
+
export const metadata = { id: 'users/join-workspace' }
|
|
5
|
+
|
|
6
|
+
export async function run({ payload, req }) {
|
|
7
|
+
const userId = payload?.targetUserId ?? payload?.userId ?? req?.userId
|
|
8
|
+
const workspaceId = payload?.workspaceId
|
|
9
|
+
const createdBy = payload?.createdBy ?? userId
|
|
10
|
+
|
|
11
|
+
if (!workspaceId) throw Object.assign(new Error('workspaceId is required'), { status: 400 })
|
|
12
|
+
|
|
13
|
+
await Aggregate.Of(User, userId)
|
|
14
|
+
.then(Aggregate.Add(UsersEvents.WorkspaceJoined({ workspaceId, createdBy })))
|
|
15
|
+
.then(Aggregate.Save())
|
|
16
|
+
return null
|
|
17
|
+
}
|
|
@@ -1,17 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { User, UsersEvents } from '@ossy/users'
|
|
3
|
-
|
|
4
|
-
export const id = 'users/leave-workspace'
|
|
5
|
-
export const access = 'authenticated'
|
|
6
|
-
|
|
7
|
-
export async function run({ payload, req }) {
|
|
8
|
-
const userId = payload?.userId ?? req?.userId
|
|
9
|
-
const workspaceId = payload?.workspaceId
|
|
10
|
-
|
|
11
|
-
if (!workspaceId) throw Object.assign(new Error('workspaceId is required'), { status: 400 })
|
|
12
|
-
|
|
13
|
-
await Aggregate.Of(User, userId)
|
|
14
|
-
.then(Aggregate.Add(UsersEvents.WorkspaceLeft({ workspaceId, createdBy: userId })))
|
|
15
|
-
.then(Aggregate.Save())
|
|
16
|
-
return null
|
|
17
|
-
}
|
|
1
|
+
export const metadata = { id: 'users/leave-workspace', access: 'authenticated' }
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Aggregate } from '@ossy/event-store'
|
|
2
|
+
import { User, UsersEvents } from '@ossy/users/server'
|
|
3
|
+
|
|
4
|
+
export const metadata = { id: 'users/leave-workspace' }
|
|
5
|
+
|
|
6
|
+
export async function run({ payload, req }) {
|
|
7
|
+
const userId = payload?.userId ?? req?.userId
|
|
8
|
+
const workspaceId = payload?.workspaceId
|
|
9
|
+
|
|
10
|
+
if (!workspaceId) throw Object.assign(new Error('workspaceId is required'), { status: 400 })
|
|
11
|
+
|
|
12
|
+
await Aggregate.Of(User, userId)
|
|
13
|
+
.then(Aggregate.Add(UsersEvents.WorkspaceLeft({ workspaceId, createdBy: userId })))
|
|
14
|
+
.then(Aggregate.Save())
|
|
15
|
+
return null
|
|
16
|
+
}
|
package/src/server.js
ADDED
|
@@ -1,29 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { User, UsersEvents } from '@ossy/users'
|
|
3
|
-
|
|
4
|
-
export const id = 'users/update-details'
|
|
5
|
-
export const access = 'authenticated'
|
|
6
|
-
|
|
7
|
-
export async function run({ payload, req }) {
|
|
8
|
-
const createdBy = payload?.userId ?? req?.userId
|
|
9
|
-
const currentUser = payload?.currentUser ?? req?.user
|
|
10
|
-
|
|
11
|
-
const firstName = (payload?.firstName ?? currentUser?.firstName)?.trim?.()
|
|
12
|
-
const lastName = (payload?.lastName ?? currentUser?.lastName)?.trim?.()
|
|
13
|
-
|
|
14
|
-
if (!firstName || typeof firstName !== 'string') {
|
|
15
|
-
throw Object.assign(new Error('No firstName provided'), { status: 400 })
|
|
16
|
-
}
|
|
17
|
-
if (!lastName || typeof lastName !== 'string') {
|
|
18
|
-
throw Object.assign(new Error('No lastName provided'), { status: 400 })
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const hasChanges = firstName + lastName !== currentUser?.firstName + currentUser?.lastName
|
|
22
|
-
if (!hasChanges) {
|
|
23
|
-
throw Object.assign(new Error('No changes provided'), { status: 400 })
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return Aggregate.Of(User, createdBy)
|
|
27
|
-
.then(Aggregate.Add(UsersEvents.NameUpdated({ firstName, lastName, createdBy })))
|
|
28
|
-
.then(Aggregate.View())
|
|
29
|
-
}
|
|
1
|
+
export const metadata = { id: 'users/update-details', access: 'authenticated' }
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Aggregate } from '@ossy/event-store'
|
|
2
|
+
import { User, UsersEvents } from '@ossy/users/server'
|
|
3
|
+
|
|
4
|
+
export const metadata = { id: 'users/update-details' }
|
|
5
|
+
|
|
6
|
+
export async function run({ payload, req }) {
|
|
7
|
+
const createdBy = payload?.userId ?? req?.userId
|
|
8
|
+
const currentUser = payload?.currentUser ?? req?.user
|
|
9
|
+
|
|
10
|
+
const firstName = (payload?.firstName ?? currentUser?.firstName)?.trim?.()
|
|
11
|
+
const lastName = (payload?.lastName ?? currentUser?.lastName)?.trim?.()
|
|
12
|
+
|
|
13
|
+
if (!firstName || typeof firstName !== 'string') {
|
|
14
|
+
throw Object.assign(new Error('No firstName provided'), { status: 400 })
|
|
15
|
+
}
|
|
16
|
+
if (!lastName || typeof lastName !== 'string') {
|
|
17
|
+
throw Object.assign(new Error('No lastName provided'), { status: 400 })
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const hasChanges = firstName + lastName !== currentUser?.firstName + currentUser?.lastName
|
|
21
|
+
if (!hasChanges) {
|
|
22
|
+
throw Object.assign(new Error('No changes provided'), { status: 400 })
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return Aggregate.Of(User, createdBy)
|
|
26
|
+
.then(Aggregate.Add(UsersEvents.NameUpdated({ firstName, lastName, createdBy })))
|
|
27
|
+
.then(Aggregate.View())
|
|
28
|
+
}
|
package/src/users.spec.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import casual from 'casual'
|
|
2
2
|
import { TestUtil, getApiTestBaseUrl, getSetCookieHeader } from '@ossy/platform/test'
|
|
3
3
|
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
const authHeaders = (token) => ({
|
|
5
|
+
'Content-Type': 'application/json',
|
|
6
|
+
Authorization: token,
|
|
7
|
+
})
|
|
7
8
|
|
|
8
9
|
const invalidEmails = [
|
|
9
10
|
undefined,
|
|
@@ -22,36 +23,32 @@ const invalidVerifySignInCases = [
|
|
|
22
23
|
{ label: 'numeric string', query: 'token=2', status: 401, body: '' },
|
|
23
24
|
]
|
|
24
25
|
|
|
25
|
-
function
|
|
26
|
+
function invalidSignUpPayload(invalidEmail) {
|
|
26
27
|
const o = { firstName: 'Test', lastName: 'User' }
|
|
27
28
|
if (invalidEmail !== undefined) o.email = invalidEmail
|
|
28
|
-
return
|
|
29
|
+
return o
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
describe('[/
|
|
32
|
+
describe('[authentication/sign-up]', () => {
|
|
32
33
|
|
|
33
34
|
describe('given an email that is not already in use', () => {
|
|
34
35
|
|
|
35
|
-
it('must return OK 200', () => TestUtil.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
expectedResponseStatus: 200,
|
|
41
|
-
expectedResponseBody: ''
|
|
36
|
+
it('must return OK 200', () => TestUtil.AssertActionResponse({
|
|
37
|
+
actionId: 'authentication/sign-up',
|
|
38
|
+
body: TestUtil.signUpBody({ email: casual.email }),
|
|
39
|
+
expectedResponseStatus: 200,
|
|
40
|
+
expectedResponseBody: { ok: true }
|
|
42
41
|
}))
|
|
43
42
|
|
|
44
43
|
it('must produce a SignedUp event', async () => {
|
|
45
44
|
|
|
46
45
|
const email = casual.email
|
|
47
46
|
|
|
48
|
-
await TestUtil.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
expectedResponseStatus: 200,
|
|
54
|
-
expectedResponseBody: ''
|
|
47
|
+
await TestUtil.AssertActionResponse({
|
|
48
|
+
actionId: 'authentication/sign-up',
|
|
49
|
+
body: TestUtil.signUpBody({ email }),
|
|
50
|
+
expectedResponseStatus: 200,
|
|
51
|
+
expectedResponseBody: { ok: true }
|
|
55
52
|
})
|
|
56
53
|
|
|
57
54
|
await TestUtil.AssertEventExist({
|
|
@@ -70,32 +67,28 @@ describe('[/users/sign-up][POST]', () => {
|
|
|
70
67
|
const email = casual.email
|
|
71
68
|
|
|
72
69
|
const signUpTestRequest = {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
expectedResponseStatus: 200,
|
|
78
|
-
expectedResponseBody: ''
|
|
70
|
+
actionId: 'authentication/sign-up',
|
|
71
|
+
body: TestUtil.signUpBody({ email }),
|
|
72
|
+
expectedResponseStatus: 200,
|
|
73
|
+
expectedResponseBody: { ok: true }
|
|
79
74
|
}
|
|
80
75
|
|
|
81
|
-
await TestUtil.
|
|
82
|
-
await TestUtil.
|
|
76
|
+
await TestUtil.AssertActionResponse(signUpTestRequest)
|
|
77
|
+
await TestUtil.AssertActionResponse(signUpTestRequest)
|
|
83
78
|
})
|
|
84
79
|
|
|
85
80
|
it('must not produce another SignUp event', async () => {
|
|
86
81
|
const email = `dup-${Date.now()}-${casual.email}`
|
|
87
82
|
|
|
88
83
|
const signUpTestRequest = {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
expectedResponseStatus: 200,
|
|
94
|
-
expectedResponseBody: ''
|
|
84
|
+
actionId: 'authentication/sign-up',
|
|
85
|
+
body: TestUtil.signUpBody({ email }),
|
|
86
|
+
expectedResponseStatus: 200,
|
|
87
|
+
expectedResponseBody: { ok: true }
|
|
95
88
|
}
|
|
96
89
|
|
|
97
|
-
await TestUtil.
|
|
98
|
-
await TestUtil.
|
|
90
|
+
await TestUtil.AssertActionResponse(signUpTestRequest)
|
|
91
|
+
await TestUtil.AssertActionResponse(signUpTestRequest)
|
|
99
92
|
|
|
100
93
|
const signUpEvents = await TestUtil.GetEvents({
|
|
101
94
|
aggregateType: 'User',
|
|
@@ -113,13 +106,11 @@ describe('[/users/sign-up][POST]', () => {
|
|
|
113
106
|
invalidEmails.forEach(invalidEmail => {
|
|
114
107
|
it(
|
|
115
108
|
`must return BAD REQUEST 400 for invalid email: ${invalidEmail}`,
|
|
116
|
-
() => TestUtil.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
headers: { 'Content-Type': 'application/json'},
|
|
120
|
-
body: invalidSignUpBody(invalidEmail),
|
|
109
|
+
() => TestUtil.AssertActionResponse({
|
|
110
|
+
actionId: 'authentication/sign-up',
|
|
111
|
+
payload: invalidSignUpPayload(invalidEmail),
|
|
121
112
|
expectedResponseStatus: 400,
|
|
122
|
-
expectedResponseBody: 'Invalid email'
|
|
113
|
+
expectedResponseBody: { error: 'Invalid email' }
|
|
123
114
|
})
|
|
124
115
|
)
|
|
125
116
|
})
|
|
@@ -127,31 +118,25 @@ describe('[/users/sign-up][POST]', () => {
|
|
|
127
118
|
|
|
128
119
|
})
|
|
129
120
|
|
|
130
|
-
describe('[/
|
|
121
|
+
describe('[authentication/request-sign-in]', () => {
|
|
131
122
|
|
|
132
123
|
describe('given an email that is in use', () => {
|
|
133
124
|
|
|
134
125
|
it('must return an OK 200 response', async () => {
|
|
135
126
|
const email = casual.email
|
|
136
127
|
|
|
137
|
-
await TestUtil.
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
body: TestUtil.signUpBody({ email }),
|
|
143
|
-
expectedResponseStatus: 200,
|
|
144
|
-
expectedResponseBody: ''
|
|
128
|
+
await TestUtil.AssertActionResponse({
|
|
129
|
+
actionId: 'authentication/sign-up',
|
|
130
|
+
body: TestUtil.signUpBody({ email }),
|
|
131
|
+
expectedResponseStatus: 200,
|
|
132
|
+
expectedResponseBody: { ok: true }
|
|
145
133
|
})
|
|
146
134
|
|
|
147
|
-
await TestUtil.
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
body: JSON.stringify({ email }),
|
|
153
|
-
expectedResponseStatus: 200,
|
|
154
|
-
expectedResponseBody: ''
|
|
135
|
+
await TestUtil.AssertActionResponse({
|
|
136
|
+
actionId: 'authentication/request-sign-in',
|
|
137
|
+
payload: { email },
|
|
138
|
+
expectedResponseStatus: 200,
|
|
139
|
+
expectedResponseBody: { ok: true }
|
|
155
140
|
})
|
|
156
141
|
|
|
157
142
|
})
|
|
@@ -160,24 +145,18 @@ describe('[/users/sign-in][POST]', () => {
|
|
|
160
145
|
|
|
161
146
|
const email = casual.email
|
|
162
147
|
|
|
163
|
-
await TestUtil.
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
body: TestUtil.signUpBody({ email }),
|
|
169
|
-
expectedResponseStatus: 200,
|
|
170
|
-
expectedResponseBody: ''
|
|
148
|
+
await TestUtil.AssertActionResponse({
|
|
149
|
+
actionId: 'authentication/sign-up',
|
|
150
|
+
body: TestUtil.signUpBody({ email }),
|
|
151
|
+
expectedResponseStatus: 200,
|
|
152
|
+
expectedResponseBody: { ok: true }
|
|
171
153
|
})
|
|
172
154
|
|
|
173
|
-
await TestUtil.
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
body: JSON.stringify({ email }),
|
|
179
|
-
expectedResponseStatus: 200,
|
|
180
|
-
expectedResponseBody: ''
|
|
155
|
+
await TestUtil.AssertActionResponse({
|
|
156
|
+
actionId: 'authentication/request-sign-in',
|
|
157
|
+
payload: { email },
|
|
158
|
+
expectedResponseStatus: 200,
|
|
159
|
+
expectedResponseBody: { ok: true }
|
|
181
160
|
})
|
|
182
161
|
|
|
183
162
|
const signedUpEvent = await TestUtil.GetEvent({
|
|
@@ -195,14 +174,11 @@ describe('[/users/sign-in][POST]', () => {
|
|
|
195
174
|
describe('given an email that is not in use', () => {
|
|
196
175
|
|
|
197
176
|
it('must, for security reasons, return an OK 200 response', async () => {
|
|
198
|
-
await TestUtil.
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
body: JSON.stringify({ email: casual.email }),
|
|
204
|
-
expectedResponseStatus: 200,
|
|
205
|
-
expectedResponseBody: ''
|
|
177
|
+
await TestUtil.AssertActionResponse({
|
|
178
|
+
actionId: 'authentication/request-sign-in',
|
|
179
|
+
payload: { email: casual.email },
|
|
180
|
+
expectedResponseStatus: 200,
|
|
181
|
+
expectedResponseBody: { ok: true }
|
|
206
182
|
})
|
|
207
183
|
})
|
|
208
184
|
|
|
@@ -210,14 +186,11 @@ describe('[/users/sign-in][POST]', () => {
|
|
|
210
186
|
|
|
211
187
|
const verificationTokensBefore = await TestUtil.countVerificationTokenEvents()
|
|
212
188
|
|
|
213
|
-
await TestUtil.
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
body: JSON.stringify({ email: casual.email }),
|
|
219
|
-
expectedResponseStatus: 200,
|
|
220
|
-
expectedResponseBody: ''
|
|
189
|
+
await TestUtil.AssertActionResponse({
|
|
190
|
+
actionId: 'authentication/request-sign-in',
|
|
191
|
+
payload: { email: casual.email },
|
|
192
|
+
expectedResponseStatus: 200,
|
|
193
|
+
expectedResponseBody: { ok: true }
|
|
221
194
|
})
|
|
222
195
|
|
|
223
196
|
const verificationTokensAfter = await TestUtil.countVerificationTokenEvents()
|
|
@@ -233,13 +206,11 @@ describe('[/users/sign-in][POST]', () => {
|
|
|
233
206
|
invalidEmails.forEach(invalidEmail => {
|
|
234
207
|
it(
|
|
235
208
|
`must return BAD REQUEST 400 for invalid email: ${invalidEmail}`,
|
|
236
|
-
() => TestUtil.
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
headers: { 'Content-Type': 'application/json'},
|
|
240
|
-
body: JSON.stringify({ email: invalidEmail }),
|
|
209
|
+
() => TestUtil.AssertActionResponse({
|
|
210
|
+
actionId: 'authentication/request-sign-in',
|
|
211
|
+
payload: { email: invalidEmail },
|
|
241
212
|
expectedResponseStatus: 400,
|
|
242
|
-
expectedResponseBody: {
|
|
213
|
+
expectedResponseBody: { error: 'No email provided' }
|
|
243
214
|
})
|
|
244
215
|
)
|
|
245
216
|
})
|
|
@@ -253,13 +224,11 @@ describe('[/users/verify-sign-in][GET]', () => {
|
|
|
253
224
|
it('must return OK 200 and an auth cookie and must produce a SignInVerified event', async () => {
|
|
254
225
|
const email = casual.email
|
|
255
226
|
|
|
256
|
-
await TestUtil.
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
expectedResponseStatus: 200,
|
|
262
|
-
expectedResponseBody: ''
|
|
227
|
+
await TestUtil.AssertActionResponse({
|
|
228
|
+
actionId: 'authentication/sign-up',
|
|
229
|
+
body: TestUtil.signUpBody({ email }),
|
|
230
|
+
expectedResponseStatus: 200,
|
|
231
|
+
expectedResponseBody: { ok: true }
|
|
263
232
|
})
|
|
264
233
|
|
|
265
234
|
const signedUpEvent = await TestUtil.GetEvent({
|
|
@@ -294,22 +263,18 @@ describe('[/users/verify-sign-in][GET]', () => {
|
|
|
294
263
|
it('must return OK 200 and an auth cookie and must produce a SignInVerified event', async () => {
|
|
295
264
|
const email = casual.email
|
|
296
265
|
|
|
297
|
-
await TestUtil.
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
expectedResponseStatus: 200,
|
|
303
|
-
expectedResponseBody: ''
|
|
266
|
+
await TestUtil.AssertActionResponse({
|
|
267
|
+
actionId: 'authentication/sign-up',
|
|
268
|
+
body: TestUtil.signUpBody({ email }),
|
|
269
|
+
expectedResponseStatus: 200,
|
|
270
|
+
expectedResponseBody: { ok: true }
|
|
304
271
|
})
|
|
305
272
|
|
|
306
|
-
await TestUtil.
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
expectedResponseStatus: 200,
|
|
312
|
-
expectedResponseBody: ''
|
|
273
|
+
await TestUtil.AssertActionResponse({
|
|
274
|
+
actionId: 'authentication/request-sign-in',
|
|
275
|
+
payload: { email },
|
|
276
|
+
expectedResponseStatus: 200,
|
|
277
|
+
expectedResponseBody: { ok: true }
|
|
313
278
|
})
|
|
314
279
|
|
|
315
280
|
const signedUpEvent = await TestUtil.GetEvent({
|
|
@@ -369,22 +334,23 @@ describe('[/users/verify-sign-in][GET]', () => {
|
|
|
369
334
|
|
|
370
335
|
})
|
|
371
336
|
|
|
372
|
-
describe('[/
|
|
337
|
+
describe('[authentication/get-current-user]', () => {
|
|
373
338
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
339
|
+
describe('given no auth token is provided', () => {
|
|
340
|
+
it('must return null', async () => {
|
|
341
|
+
const response = await TestUtil.InvokeAction({ actionId: 'authentication/get-current-user' })
|
|
342
|
+
expect(response.status).toEqual(200)
|
|
343
|
+
await expect(response.json()).resolves.toBeNull()
|
|
344
|
+
})
|
|
378
345
|
})
|
|
379
346
|
|
|
380
347
|
describe('given an auth token', () => {
|
|
381
348
|
it('must return OK 200 with a view of the current user state', async () => {
|
|
382
349
|
const user = await TestUtil.GetAuthenticatedTestUser()
|
|
383
350
|
|
|
384
|
-
const response = await TestUtil.
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
headers: { 'Content-Type': 'application/json', 'Authorization': user.token }
|
|
351
|
+
const response = await TestUtil.InvokeAction({
|
|
352
|
+
actionId: 'authentication/get-current-user',
|
|
353
|
+
headers: authHeaders(user.token),
|
|
388
354
|
})
|
|
389
355
|
|
|
390
356
|
const body = await response.json()
|
|
@@ -404,22 +370,19 @@ describe('[/users/me][GET]', () => {
|
|
|
404
370
|
})
|
|
405
371
|
})
|
|
406
372
|
|
|
407
|
-
describe('[
|
|
373
|
+
describe('[users/get-current-user-history]', () => {
|
|
408
374
|
|
|
409
|
-
TestUtil.
|
|
410
|
-
|
|
411
|
-
method: 'GET',
|
|
412
|
-
headers: { 'Content-Type': 'application/json' },
|
|
375
|
+
TestUtil.AssertActionAuthenticationNeeded({
|
|
376
|
+
actionId: 'users/get-current-user-history',
|
|
413
377
|
})
|
|
414
378
|
|
|
415
379
|
describe('given an auth token', () => {
|
|
416
380
|
it('must return OK 200 with a view of the history of the current user', async () => {
|
|
417
381
|
const user = await TestUtil.GetAuthenticatedTestUser()
|
|
418
382
|
|
|
419
|
-
const response = await TestUtil.
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
headers: { 'Content-Type': 'application/json', 'Authorization': user.token }
|
|
383
|
+
const response = await TestUtil.InvokeAction({
|
|
384
|
+
actionId: 'users/get-current-user-history',
|
|
385
|
+
headers: authHeaders(user.token),
|
|
423
386
|
})
|
|
424
387
|
|
|
425
388
|
const body = await response.json()
|
|
@@ -436,24 +399,21 @@ describe('[/users/me/history][GET]', () => {
|
|
|
436
399
|
})
|
|
437
400
|
})
|
|
438
401
|
|
|
439
|
-
describe('[
|
|
402
|
+
describe('[users/create-api-token]', () => {
|
|
440
403
|
|
|
441
|
-
TestUtil.
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
headers: { 'Content-Type': 'application/json' },
|
|
445
|
-
body: JSON.stringify({ name: 't', description: 'd' })
|
|
404
|
+
TestUtil.AssertActionAuthenticationNeeded({
|
|
405
|
+
actionId: 'users/create-api-token',
|
|
406
|
+
payload: { name: 't', description: 'd' }
|
|
446
407
|
})
|
|
447
408
|
|
|
448
409
|
describe('given a valid auth token', () => {
|
|
449
410
|
it('must return OK 200 with an API token and produce an ApiTokenCreated event', async () => {
|
|
450
411
|
const user = await TestUtil.GetAuthenticatedTestUser()
|
|
451
412
|
|
|
452
|
-
const response = await TestUtil.
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
body: JSON.stringify({ name: 'cli', description: casual.sentence })
|
|
413
|
+
const response = await TestUtil.InvokeAction({
|
|
414
|
+
actionId: 'users/create-api-token',
|
|
415
|
+
headers: authHeaders(user.token),
|
|
416
|
+
payload: { name: 'cli', description: casual.sentence }
|
|
457
417
|
})
|
|
458
418
|
|
|
459
419
|
const responseBody = await response.json()
|
|
@@ -487,11 +447,10 @@ describe('[/users/me/tokens][POST]', () => {
|
|
|
487
447
|
else if (invalidDescription === '') payload = { name: 'token', description: '' }
|
|
488
448
|
else payload = invalidDescription
|
|
489
449
|
|
|
490
|
-
const response = await TestUtil.
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
body: JSON.stringify(payload)
|
|
450
|
+
const response = await TestUtil.InvokeAction({
|
|
451
|
+
actionId: 'users/create-api-token',
|
|
452
|
+
headers: authHeaders(user.token),
|
|
453
|
+
payload,
|
|
495
454
|
})
|
|
496
455
|
|
|
497
456
|
const responseBody = await response.json()
|
|
@@ -504,7 +463,7 @@ describe('[/users/me/tokens][POST]', () => {
|
|
|
504
463
|
|
|
505
464
|
expect(response.status).toEqual(400)
|
|
506
465
|
expect(response.headers.get('Content-Type').includes('application/json')).toEqual(true)
|
|
507
|
-
expect(responseBody).toEqual('')
|
|
466
|
+
expect(responseBody).toEqual({ error: 'No description provided' })
|
|
508
467
|
await expect(event).rejects.toEqual(undefined)
|
|
509
468
|
})
|
|
510
469
|
})
|
|
@@ -512,12 +471,10 @@ describe('[/users/me/tokens][POST]', () => {
|
|
|
512
471
|
|
|
513
472
|
})
|
|
514
473
|
|
|
515
|
-
describe('[
|
|
474
|
+
describe('[users/get-api-tokens]', () => {
|
|
516
475
|
|
|
517
|
-
TestUtil.
|
|
518
|
-
|
|
519
|
-
method: 'GET',
|
|
520
|
-
headers: { 'Content-Type': 'application/json' },
|
|
476
|
+
TestUtil.AssertActionAuthenticationNeeded({
|
|
477
|
+
actionId: 'users/get-api-tokens',
|
|
521
478
|
})
|
|
522
479
|
|
|
523
480
|
describe('given authentication token is provided', () => {
|
|
@@ -525,17 +482,15 @@ describe('[/users/me/tokens][GET]', () => {
|
|
|
525
482
|
|
|
526
483
|
const user = await TestUtil.GetAuthenticatedTestUser()
|
|
527
484
|
|
|
528
|
-
const token = await TestUtil.
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
body: JSON.stringify({ name: 'list-test', description: casual.sentence })
|
|
485
|
+
const token = await TestUtil.InvokeAction({
|
|
486
|
+
actionId: 'users/create-api-token',
|
|
487
|
+
headers: authHeaders(user.token),
|
|
488
|
+
payload: { name: 'list-test', description: casual.sentence }
|
|
533
489
|
}).then(response => response.json())
|
|
534
490
|
|
|
535
|
-
const response = await TestUtil.
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
headers: { 'Content-Type': 'application/json', 'Authorization': user.token }
|
|
491
|
+
const response = await TestUtil.InvokeAction({
|
|
492
|
+
actionId: 'users/get-api-tokens',
|
|
493
|
+
headers: authHeaders(user.token),
|
|
539
494
|
})
|
|
540
495
|
|
|
541
496
|
const responseBody = await response.json()
|
|
@@ -560,22 +515,20 @@ describe('[/users/me/tokens][GET]', () => {
|
|
|
560
515
|
})
|
|
561
516
|
|
|
562
517
|
const createApiToken = async (user) => {
|
|
563
|
-
const token = await TestUtil.
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
body: JSON.stringify({ name: 'delete-test', description: casual.sentence })
|
|
518
|
+
const token = await TestUtil.InvokeAction({
|
|
519
|
+
actionId: 'users/create-api-token',
|
|
520
|
+
headers: authHeaders(user.token),
|
|
521
|
+
payload: { name: 'delete-test', description: casual.sentence }
|
|
568
522
|
}).then(response => response.json())
|
|
569
523
|
|
|
570
524
|
return token
|
|
571
525
|
}
|
|
572
526
|
|
|
573
|
-
describe('[
|
|
527
|
+
describe('[users/invalidate-api-token]', () => {
|
|
574
528
|
|
|
575
|
-
TestUtil.
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
headers: { 'Content-Type': 'application/json' },
|
|
529
|
+
TestUtil.AssertActionAuthenticationNeeded({
|
|
530
|
+
actionId: 'users/invalidate-api-token',
|
|
531
|
+
payload: { tokenId: '123' },
|
|
579
532
|
})
|
|
580
533
|
|
|
581
534
|
describe('authentication with user token', () => {
|
|
@@ -584,10 +537,10 @@ describe('[/users/me/tokens/:tokenId][DELETE]', () => {
|
|
|
584
537
|
const user = await TestUtil.GetAuthenticatedTestUser()
|
|
585
538
|
const token = await createApiToken(user)
|
|
586
539
|
|
|
587
|
-
const response = await TestUtil.
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
540
|
+
const response = await TestUtil.InvokeAction({
|
|
541
|
+
actionId: 'users/invalidate-api-token',
|
|
542
|
+
headers: authHeaders(user.token),
|
|
543
|
+
payload: { tokenId: token.id },
|
|
591
544
|
})
|
|
592
545
|
|
|
593
546
|
const responseBody = await response.json()
|
|
@@ -608,16 +561,15 @@ describe('[/users/me/tokens/:tokenId][DELETE]', () => {
|
|
|
608
561
|
const user = await TestUtil.GetAuthenticatedTestUser()
|
|
609
562
|
const token = await createApiToken(user)
|
|
610
563
|
|
|
611
|
-
await TestUtil.
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
564
|
+
await TestUtil.InvokeAction({
|
|
565
|
+
actionId: 'users/invalidate-api-token',
|
|
566
|
+
headers: authHeaders(user.token),
|
|
567
|
+
payload: { tokenId: token.id },
|
|
615
568
|
})
|
|
616
569
|
|
|
617
|
-
const response = await TestUtil.
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
headers: { 'Content-Type': 'application/json', 'Authorization': user.token }
|
|
570
|
+
const response = await TestUtil.InvokeAction({
|
|
571
|
+
actionId: 'users/get-api-tokens',
|
|
572
|
+
headers: authHeaders(user.token),
|
|
621
573
|
})
|
|
622
574
|
|
|
623
575
|
const responseBody = await response.json()
|
|
@@ -634,10 +586,10 @@ describe('[/users/me/tokens/:tokenId][DELETE]', () => {
|
|
|
634
586
|
const user = await TestUtil.GetAuthenticatedTestUser()
|
|
635
587
|
const token = await createApiToken(user)
|
|
636
588
|
|
|
637
|
-
const response = await TestUtil.
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
589
|
+
const response = await TestUtil.InvokeAction({
|
|
590
|
+
actionId: 'users/invalidate-api-token',
|
|
591
|
+
headers: authHeaders(token.token),
|
|
592
|
+
payload: { tokenId: token.id },
|
|
641
593
|
})
|
|
642
594
|
|
|
643
595
|
const responseBody = await response.json()
|
|
@@ -658,22 +610,21 @@ describe('[/users/me/tokens/:tokenId][DELETE]', () => {
|
|
|
658
610
|
const user = await TestUtil.GetAuthenticatedTestUser()
|
|
659
611
|
const token = await createApiToken(user)
|
|
660
612
|
|
|
661
|
-
await TestUtil.
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
613
|
+
await TestUtil.InvokeAction({
|
|
614
|
+
actionId: 'users/invalidate-api-token',
|
|
615
|
+
headers: authHeaders(token.token),
|
|
616
|
+
payload: { tokenId: token.id },
|
|
665
617
|
})
|
|
666
618
|
|
|
667
|
-
const response = await TestUtil.
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
headers: { 'Content-Type': 'application/json', 'Authorization': token.token }
|
|
619
|
+
const response = await TestUtil.InvokeAction({
|
|
620
|
+
actionId: 'users/get-api-tokens',
|
|
621
|
+
headers: authHeaders(token.token),
|
|
671
622
|
})
|
|
672
623
|
|
|
673
624
|
const responseBody = await response.json()
|
|
674
625
|
|
|
675
626
|
expect(response.status).toEqual(401)
|
|
676
|
-
expect(responseBody).toEqual('')
|
|
627
|
+
expect(responseBody).toEqual({ error: 'Unauthorized' })
|
|
677
628
|
})
|
|
678
629
|
|
|
679
630
|
})
|
package/src/users.startup.js
CHANGED
package/src/list.api.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { ActionService } from '@ossy/platform'
|
|
2
|
-
|
|
3
|
-
export const metadata = {
|
|
4
|
-
id: 'users.list',
|
|
5
|
-
path: '/api/v0/users',
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export default async function handle(req, res) {
|
|
9
|
-
if (req.method !== 'GET') {
|
|
10
|
-
res.setHeader('Allow', 'GET')
|
|
11
|
-
res.status(405).json({ error: 'Method Not Allowed' })
|
|
12
|
-
return
|
|
13
|
-
}
|
|
14
|
-
try {
|
|
15
|
-
const users = await ActionService.invoke('workspaces/get-users', {
|
|
16
|
-
payload: { workspaceId: req.workspaceId },
|
|
17
|
-
req,
|
|
18
|
-
})
|
|
19
|
-
res.json(users)
|
|
20
|
-
} catch (err) {
|
|
21
|
-
res.status(err?.status ?? 500).json()
|
|
22
|
-
}
|
|
23
|
-
}
|
package/src/me-history.api.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { ActionService } from '@ossy/platform'
|
|
2
|
-
|
|
3
|
-
export const metadata = {
|
|
4
|
-
id: 'users.me.history',
|
|
5
|
-
path: '/api/v0/users/me/history',
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export default async function handle(req, res) {
|
|
9
|
-
if (req.method !== 'GET') {
|
|
10
|
-
res.setHeader('Allow', 'GET')
|
|
11
|
-
res.status(405).json({ error: 'Method Not Allowed' })
|
|
12
|
-
return
|
|
13
|
-
}
|
|
14
|
-
try {
|
|
15
|
-
const history = await ActionService.invoke('users/get-current-user-history', {
|
|
16
|
-
payload: { userId: req.userId },
|
|
17
|
-
req,
|
|
18
|
-
})
|
|
19
|
-
res.status(200).json(history)
|
|
20
|
-
} catch (err) {
|
|
21
|
-
res.status(err?.status ?? 500).json('')
|
|
22
|
-
}
|
|
23
|
-
}
|
package/src/me-token.api.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { ActionService } from '@ossy/platform'
|
|
2
|
-
|
|
3
|
-
export const metadata = {
|
|
4
|
-
id: 'users.me.token',
|
|
5
|
-
path: '/api/v0/users/me/tokens/:tokenId',
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export default async function handle(req, res) {
|
|
9
|
-
if (req.method !== 'DELETE') {
|
|
10
|
-
res.setHeader('Allow', 'DELETE')
|
|
11
|
-
res.status(405).json({ error: 'Method Not Allowed' })
|
|
12
|
-
return
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const pathname = (req.path || new URL(req.originalUrl, 'http://localhost').pathname).replace(/\/+$/, '')
|
|
16
|
-
const m = pathname.match(/^\/api\/v0\/users\/me\/tokens\/([^/]+)$/)
|
|
17
|
-
if (!m) {
|
|
18
|
-
res.status(404).json('')
|
|
19
|
-
return
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const tokenId = decodeURIComponent(m[1])
|
|
23
|
-
try {
|
|
24
|
-
await ActionService.invoke('users/invalidate-api-token', {
|
|
25
|
-
payload: { userId: req.userId, tokenId },
|
|
26
|
-
req,
|
|
27
|
-
})
|
|
28
|
-
res.status(200).json('')
|
|
29
|
-
} catch (err) {
|
|
30
|
-
res.status(err?.status ?? 500).json()
|
|
31
|
-
}
|
|
32
|
-
}
|
package/src/me-tokens.api.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { ActionService } from '@ossy/platform'
|
|
2
|
-
|
|
3
|
-
export const metadata = {
|
|
4
|
-
id: 'users.me.tokens',
|
|
5
|
-
path: '/api/v0/users/me/tokens',
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export default async function handle(req, res) {
|
|
9
|
-
if (req.method === 'GET') {
|
|
10
|
-
try {
|
|
11
|
-
const tokens = await ActionService.invoke('users/get-api-tokens', {
|
|
12
|
-
payload: { userId: req.userId },
|
|
13
|
-
req,
|
|
14
|
-
})
|
|
15
|
-
res.json(tokens)
|
|
16
|
-
} catch (err) {
|
|
17
|
-
res.status(err?.status ?? 500).json()
|
|
18
|
-
}
|
|
19
|
-
return
|
|
20
|
-
}
|
|
21
|
-
if (req.method === 'POST') {
|
|
22
|
-
try {
|
|
23
|
-
const token = await ActionService.invoke('users/create-api-token', {
|
|
24
|
-
payload: { userId: req.userId, name: req.body?.name, description: req.body?.description },
|
|
25
|
-
req,
|
|
26
|
-
})
|
|
27
|
-
res.json(token)
|
|
28
|
-
} catch (err) {
|
|
29
|
-
res.status(err?.status ?? 400).json('')
|
|
30
|
-
}
|
|
31
|
-
return
|
|
32
|
-
}
|
|
33
|
-
res.setHeader('Allow', 'GET, POST')
|
|
34
|
-
res.status(405).json({ error: 'Method Not Allowed' })
|
|
35
|
-
}
|
package/src/me.api.js
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { ActionService } from '@ossy/platform'
|
|
2
|
-
|
|
3
|
-
export const metadata = {
|
|
4
|
-
id: 'users.me',
|
|
5
|
-
path: '/api/v0/users/me',
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export default async function handle(req, res) {
|
|
9
|
-
if (req.method === 'GET' || req.method === 'POST') {
|
|
10
|
-
try {
|
|
11
|
-
if (!req.user || req.user.anonymous) {
|
|
12
|
-
res.status(401).json('')
|
|
13
|
-
return
|
|
14
|
-
}
|
|
15
|
-
const user = await ActionService.invoke('authentication/get-current-user', { req })
|
|
16
|
-
res.status(200).json(user)
|
|
17
|
-
} catch (err) {
|
|
18
|
-
res.status(err?.status ?? 500).json('')
|
|
19
|
-
}
|
|
20
|
-
return
|
|
21
|
-
}
|
|
22
|
-
if (req.method === 'PUT') {
|
|
23
|
-
try {
|
|
24
|
-
const user = await ActionService.invoke('users/update-details', {
|
|
25
|
-
payload: {
|
|
26
|
-
userId: req.userId,
|
|
27
|
-
firstName: req.body?.firstName,
|
|
28
|
-
lastName: req.body?.lastName,
|
|
29
|
-
currentUser: req.user,
|
|
30
|
-
},
|
|
31
|
-
req,
|
|
32
|
-
})
|
|
33
|
-
res.status(200).json(user)
|
|
34
|
-
} catch (err) {
|
|
35
|
-
res.status(err?.status ?? 400).json(err?.message)
|
|
36
|
-
}
|
|
37
|
-
return
|
|
38
|
-
}
|
|
39
|
-
res.setHeader('Allow', 'GET, POST, PUT')
|
|
40
|
-
res.status(405).json({ error: 'Method Not Allowed' })
|
|
41
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { ConfigService } from '@ossy/platform'
|
|
2
|
-
import { ActionService } from '@ossy/platform'
|
|
3
|
-
import { createLogger } from '@ossy/observability'
|
|
4
|
-
|
|
5
|
-
const log = createLogger('users')
|
|
6
|
-
|
|
7
|
-
function isBotUser(req) {
|
|
8
|
-
return req.userId === ConfigService.BotUserId
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const metadata = {
|
|
12
|
-
id: 'users.workspace-membership',
|
|
13
|
-
path: '/api/v0/users/:userId/workspaces',
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export default async function handle(req, res) {
|
|
17
|
-
const pathname = (req.path || new URL(req.originalUrl, 'http://localhost').pathname).replace(/\/+$/, '')
|
|
18
|
-
|
|
19
|
-
const deleteMatch = pathname.match(/^\/api\/v0\/users\/([^/]+)\/workspaces\/([^/]+)$/)
|
|
20
|
-
if (deleteMatch && req.method === 'DELETE') {
|
|
21
|
-
const userId = decodeURIComponent(deleteMatch[1])
|
|
22
|
-
const workspaceId = decodeURIComponent(deleteMatch[2])
|
|
23
|
-
|
|
24
|
-
if (!isBotUser(req)) {
|
|
25
|
-
res.status(403).json({ error: 'Forbidden' })
|
|
26
|
-
return
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
try {
|
|
30
|
-
await ActionService.invoke('users/leave-workspace', {
|
|
31
|
-
payload: { userId, workspaceId },
|
|
32
|
-
req,
|
|
33
|
-
})
|
|
34
|
-
res.status(204).send()
|
|
35
|
-
} catch (err) {
|
|
36
|
-
log.error('[workspace-membership.api] remove error', undefined, err)
|
|
37
|
-
res.status(err?.status ?? 500).json({ error: 'Internal Server Error' })
|
|
38
|
-
}
|
|
39
|
-
return
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const postMatch = pathname.match(/^\/api\/v0\/users\/([^/]+)\/workspaces$/)
|
|
43
|
-
if (postMatch && req.method === 'POST') {
|
|
44
|
-
const userId = decodeURIComponent(postMatch[1])
|
|
45
|
-
const workspaceId = req.body?.workspaceId
|
|
46
|
-
const createdBy = req.body?.createdBy ?? userId
|
|
47
|
-
|
|
48
|
-
if (!isBotUser(req)) {
|
|
49
|
-
res.status(403).json({ error: 'Forbidden' })
|
|
50
|
-
return
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (!workspaceId) {
|
|
54
|
-
res.status(400).json({ error: 'workspaceId is required' })
|
|
55
|
-
return
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
try {
|
|
59
|
-
await ActionService.invoke('users/join-workspace', {
|
|
60
|
-
payload: { targetUserId: userId, workspaceId, createdBy },
|
|
61
|
-
req,
|
|
62
|
-
})
|
|
63
|
-
res.status(204).send()
|
|
64
|
-
} catch (err) {
|
|
65
|
-
log.error('[workspace-membership.api] add error', undefined, err)
|
|
66
|
-
res.status(err?.status ?? 500).json({ error: 'Internal Server Error' })
|
|
67
|
-
}
|
|
68
|
-
return
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
res.status(404).json({ error: 'Not Found' })
|
|
72
|
-
}
|