@labdigital/commercetools-mock 2.11.0 → 2.12.1
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/dist/index.cjs +59 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +59 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/ctMock.test.ts +18 -0
- package/src/ctMock.ts +4 -0
- package/src/exceptions.ts +7 -0
- package/src/oauth/server.test.ts +34 -0
- package/src/oauth/server.ts +40 -3
- package/src/oauth/store.ts +31 -3
package/package.json
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { CommercetoolsMock } from './index.js'
|
|
2
|
+
import { test } from 'vitest'
|
|
3
|
+
|
|
4
|
+
test('ctMock.authServer', async () => {
|
|
5
|
+
const ctMock = new CommercetoolsMock({
|
|
6
|
+
enableAuthentication: false,
|
|
7
|
+
validateCredentials: false,
|
|
8
|
+
apiHost: 'http://api.localhost',
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
ctMock.authStore().addToken({
|
|
12
|
+
token_type: 'Bearer',
|
|
13
|
+
access_token: 'foobar',
|
|
14
|
+
expires_in: 172800,
|
|
15
|
+
scope: 'my-project',
|
|
16
|
+
refresh_token: 'foobar',
|
|
17
|
+
})
|
|
18
|
+
})
|
package/src/ctMock.ts
CHANGED
|
@@ -97,6 +97,10 @@ export class CommercetoolsMock {
|
|
|
97
97
|
)
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
authStore() {
|
|
101
|
+
return this._oauth2.store
|
|
102
|
+
}
|
|
103
|
+
|
|
100
104
|
runServer(port = 3000, options?: AppOptions) {
|
|
101
105
|
const server = this.app.listen(port, () => {
|
|
102
106
|
console.info(`Mock server listening at http://localhost:${port}`)
|
package/src/exceptions.ts
CHANGED
|
@@ -21,3 +21,10 @@ export interface InvalidRequestError {
|
|
|
21
21
|
readonly code: 'invalid_request'
|
|
22
22
|
readonly message: string
|
|
23
23
|
}
|
|
24
|
+
|
|
25
|
+
export interface AuthError {
|
|
26
|
+
readonly statusCode: number
|
|
27
|
+
readonly message: string
|
|
28
|
+
readonly error: string
|
|
29
|
+
readonly error_description: string
|
|
30
|
+
}
|
package/src/oauth/server.test.ts
CHANGED
|
@@ -26,6 +26,40 @@ describe('OAuth2Server', () => {
|
|
|
26
26
|
expect(response.status, JSON.stringify(body)).toBe(200)
|
|
27
27
|
expect(body).toHaveProperty('access_token')
|
|
28
28
|
})
|
|
29
|
+
|
|
30
|
+
it('should failed on invalid refresh token', async () => {
|
|
31
|
+
const response = await supertest(app)
|
|
32
|
+
.post('/token')
|
|
33
|
+
.auth('validClientId', 'validClientSecret')
|
|
34
|
+
.query({ grant_type: 'refresh_token', refresh_token: 'invalid' })
|
|
35
|
+
.send()
|
|
36
|
+
|
|
37
|
+
const body = await response.body
|
|
38
|
+
|
|
39
|
+
expect(response.status, JSON.stringify(body)).toBe(400)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('should refresh a token', async () => {
|
|
43
|
+
const createResponse = await supertest(app)
|
|
44
|
+
.post(`/my-project/anonymous/token`)
|
|
45
|
+
.auth('validClientId', 'validClientSecret')
|
|
46
|
+
.query({ grant_type: 'client_credentials' })
|
|
47
|
+
.send()
|
|
48
|
+
|
|
49
|
+
const refreshToken = createResponse.body.refresh_token
|
|
50
|
+
|
|
51
|
+
const response = await supertest(app)
|
|
52
|
+
.post('/token')
|
|
53
|
+
.auth('validClientId', 'validClientSecret')
|
|
54
|
+
.query({ grant_type: 'refresh_token', refresh_token: refreshToken })
|
|
55
|
+
.send()
|
|
56
|
+
|
|
57
|
+
const body = await response.body
|
|
58
|
+
|
|
59
|
+
expect(response.status, JSON.stringify(body)).toBe(200)
|
|
60
|
+
expect(body.access_token).not.toBe(createResponse.body.access_token)
|
|
61
|
+
expect(body.refresh_token).toBeUndefined()
|
|
62
|
+
})
|
|
29
63
|
})
|
|
30
64
|
|
|
31
65
|
describe('POST /:projectKey/anonymous/token', () => {
|
package/src/oauth/server.ts
CHANGED
|
@@ -6,7 +6,11 @@ import express, {
|
|
|
6
6
|
type Response,
|
|
7
7
|
} from 'express'
|
|
8
8
|
import { InvalidTokenError } from '@commercetools/platform-sdk'
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
AuthError,
|
|
11
|
+
CommercetoolsError,
|
|
12
|
+
InvalidRequestError,
|
|
13
|
+
} from '../exceptions.js'
|
|
10
14
|
import { InvalidClientError, UnsupportedGrantType } from './errors.js'
|
|
11
15
|
import { OAuth2Store } from './store.js'
|
|
12
16
|
import { getBearerToken } from './helpers.js'
|
|
@@ -19,6 +23,13 @@ type AuthRequest = Request & {
|
|
|
19
23
|
clientSecret: string
|
|
20
24
|
}
|
|
21
25
|
}
|
|
26
|
+
export type Token = {
|
|
27
|
+
access_token: string
|
|
28
|
+
token_type: 'Bearer'
|
|
29
|
+
expires_in: number
|
|
30
|
+
scope: string
|
|
31
|
+
refresh_token?: string
|
|
32
|
+
}
|
|
22
33
|
|
|
23
34
|
export class OAuth2Server {
|
|
24
35
|
store: OAuth2Store
|
|
@@ -160,11 +171,37 @@ export class OAuth2Server {
|
|
|
160
171
|
)
|
|
161
172
|
return response.status(200).send(token)
|
|
162
173
|
} else if (grantType === 'refresh_token') {
|
|
163
|
-
const
|
|
174
|
+
const refreshToken = request.query.refresh_token?.toString()
|
|
175
|
+
if (!refreshToken) {
|
|
176
|
+
return next(
|
|
177
|
+
new CommercetoolsError<InvalidRequestError>(
|
|
178
|
+
{
|
|
179
|
+
code: 'invalid_request',
|
|
180
|
+
message: 'Missing required parameter: refresh_token.',
|
|
181
|
+
},
|
|
182
|
+
400
|
|
183
|
+
)
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
const token = this.store.refreshToken(
|
|
164
187
|
request.credentials.clientId,
|
|
165
188
|
request.credentials.clientSecret,
|
|
166
|
-
|
|
189
|
+
refreshToken
|
|
167
190
|
)
|
|
191
|
+
if (!token) {
|
|
192
|
+
return next(
|
|
193
|
+
new CommercetoolsError<AuthError>(
|
|
194
|
+
{
|
|
195
|
+
statusCode: 400,
|
|
196
|
+
message: 'The refresh token was not found. It may have expired.',
|
|
197
|
+
error: 'invalid_grant',
|
|
198
|
+
error_description:
|
|
199
|
+
'The refresh token was not found. It may have expired.',
|
|
200
|
+
},
|
|
201
|
+
400
|
|
202
|
+
)
|
|
203
|
+
)
|
|
204
|
+
}
|
|
168
205
|
return response.status(200).send(token)
|
|
169
206
|
} else {
|
|
170
207
|
return next(
|
package/src/oauth/store.ts
CHANGED
|
@@ -6,6 +6,7 @@ type Token = {
|
|
|
6
6
|
token_type: 'Bearer'
|
|
7
7
|
expires_in: number
|
|
8
8
|
scope: string
|
|
9
|
+
refresh_token?: string
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export class OAuth2Store {
|
|
@@ -16,14 +17,19 @@ export class OAuth2Store {
|
|
|
16
17
|
this.validate = validate
|
|
17
18
|
}
|
|
18
19
|
|
|
20
|
+
addToken(token: Token) {
|
|
21
|
+
this.tokens.push(token)
|
|
22
|
+
}
|
|
23
|
+
|
|
19
24
|
getClientToken(clientId: string, clientSecret: string, scope?: string) {
|
|
20
25
|
const token: Token = {
|
|
21
26
|
access_token: randomBytes(16).toString('base64'),
|
|
22
27
|
token_type: 'Bearer',
|
|
23
28
|
expires_in: 172800,
|
|
24
29
|
scope: scope || 'todo',
|
|
30
|
+
refresh_token: `my-project-${randomBytes(16).toString('base64')}`,
|
|
25
31
|
}
|
|
26
|
-
this.
|
|
32
|
+
this.addToken(token)
|
|
27
33
|
return token
|
|
28
34
|
}
|
|
29
35
|
|
|
@@ -38,8 +44,9 @@ export class OAuth2Store {
|
|
|
38
44
|
scope: scope
|
|
39
45
|
? `${scope} anonymous_id:${anonymousId}`
|
|
40
46
|
: `anonymous_id:${anonymousId}`,
|
|
47
|
+
refresh_token: `my-project-${randomBytes(16).toString('base64')}`,
|
|
41
48
|
}
|
|
42
|
-
this.
|
|
49
|
+
this.addToken(token)
|
|
43
50
|
return token
|
|
44
51
|
}
|
|
45
52
|
|
|
@@ -51,11 +58,32 @@ export class OAuth2Store {
|
|
|
51
58
|
scope: scope
|
|
52
59
|
? `${scope} customer_id:${customerId}`
|
|
53
60
|
: `customer_id:${customerId}`,
|
|
61
|
+
refresh_token: `my-project-${randomBytes(16).toString('base64')}`,
|
|
54
62
|
}
|
|
55
|
-
this.
|
|
63
|
+
this.addToken(token)
|
|
56
64
|
return token
|
|
57
65
|
}
|
|
58
66
|
|
|
67
|
+
refreshToken(clientId: string, clientSecret: string, refreshToken: string) {
|
|
68
|
+
const existing = this.tokens.find((t) => t.refresh_token === refreshToken)
|
|
69
|
+
if (!existing) {
|
|
70
|
+
return undefined
|
|
71
|
+
}
|
|
72
|
+
const token: Token = {
|
|
73
|
+
...existing,
|
|
74
|
+
access_token: randomBytes(16).toString('base64'),
|
|
75
|
+
}
|
|
76
|
+
this.addToken(token)
|
|
77
|
+
|
|
78
|
+
// We don't want to return the refresh_token again
|
|
79
|
+
return {
|
|
80
|
+
access_token: token.access_token,
|
|
81
|
+
token_type: token.token_type,
|
|
82
|
+
expires_in: token.expires_in,
|
|
83
|
+
scope: token.scope,
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
59
87
|
validateToken(token: string) {
|
|
60
88
|
if (!this.validate) return true
|
|
61
89
|
|