@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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@labdigital/commercetools-mock",
3
3
  "author": "Michael van Tellingen",
4
- "version": "2.11.0",
4
+ "version": "2.12.1",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.cjs",
7
7
  "module": "dist/index.js",
@@ -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
+ }
@@ -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', () => {
@@ -6,7 +6,11 @@ import express, {
6
6
  type Response,
7
7
  } from 'express'
8
8
  import { InvalidTokenError } from '@commercetools/platform-sdk'
9
- import { CommercetoolsError, InvalidRequestError } from '../exceptions.js'
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 token = this.store.getClientToken(
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
- request.query.scope?.toString()
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(
@@ -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.tokens.push(token)
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.tokens.push(token)
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.tokens.push(token)
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