@pikku/core 0.11.0 → 0.11.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/CHANGELOG.md +11 -1
- package/dist/errors/errors.d.ts +1 -1
- package/dist/errors/errors.js +3 -3
- package/dist/function/function-runner.d.ts +6 -7
- package/dist/function/function-runner.js +35 -16
- package/dist/function/functions.types.d.ts +22 -30
- package/dist/function/functions.types.js +9 -6
- package/dist/handle-error.d.ts +1 -1
- package/dist/handle-error.js +1 -1
- package/dist/middleware/auth-apikey.js +3 -3
- package/dist/middleware/auth-bearer.js +3 -3
- package/dist/middleware/auth-cookie.js +5 -5
- package/dist/middleware/timeout.js +3 -2
- package/dist/middleware-runner.d.ts +3 -6
- package/dist/middleware-runner.js +11 -12
- package/dist/permissions.d.ts +6 -6
- package/dist/permissions.js +15 -16
- package/dist/services/user-session-service.d.ts +7 -3
- package/dist/services/user-session-service.js +8 -1
- package/dist/types/core.types.d.ts +29 -38
- package/dist/types/core.types.js +3 -14
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +3 -3
- package/dist/wirings/channel/channel-common.js +2 -3
- package/dist/wirings/channel/channel-handler.d.ts +3 -2
- package/dist/wirings/channel/channel-handler.js +6 -9
- package/dist/wirings/channel/channel-runner.d.ts +2 -2
- package/dist/wirings/channel/channel.types.d.ts +10 -5
- package/dist/wirings/channel/local/local-channel-runner.d.ts +1 -1
- package/dist/wirings/channel/local/local-channel-runner.js +19 -26
- package/dist/wirings/channel/serverless/serverless-channel-runner.d.ts +1 -1
- package/dist/wirings/channel/serverless/serverless-channel-runner.js +41 -46
- package/dist/wirings/cli/cli-runner.d.ts +5 -5
- package/dist/wirings/cli/cli-runner.js +10 -28
- package/dist/wirings/cli/cli.types.d.ts +15 -11
- package/dist/wirings/http/http-runner.d.ts +6 -6
- package/dist/wirings/http/http-runner.js +25 -39
- package/dist/wirings/http/http.types.d.ts +5 -10
- package/dist/wirings/mcp/mcp-runner.d.ts +2 -2
- package/dist/wirings/mcp/mcp-runner.js +12 -19
- package/dist/wirings/mcp/mcp.types.d.ts +6 -0
- package/dist/wirings/queue/queue-runner.d.ts +3 -3
- package/dist/wirings/queue/queue-runner.js +7 -17
- package/dist/wirings/queue/queue.types.d.ts +4 -11
- package/dist/wirings/rpc/rpc-runner.d.ts +10 -8
- package/dist/wirings/rpc/rpc-runner.js +36 -47
- package/dist/wirings/scheduler/scheduler-runner.d.ts +3 -3
- package/dist/wirings/scheduler/scheduler-runner.js +13 -24
- package/dist/wirings/scheduler/scheduler.types.d.ts +3 -8
- package/dist/wirings/workflow/index.d.ts +11 -2
- package/dist/wirings/workflow/index.js +10 -2
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +12 -4
- package/dist/wirings/workflow/pikku-workflow-service.js +61 -79
- package/dist/wirings/workflow/workflow-runner.d.ts +3 -4
- package/dist/wirings/workflow/workflow-runner.js +11 -14
- package/dist/wirings/workflow/workflow.types.d.ts +114 -29
- package/package.json +2 -2
- package/src/errors/errors.ts +3 -3
- package/src/factory-functions.test.ts +9 -36
- package/src/function/function-runner.test.ts +57 -68
- package/src/function/function-runner.ts +51 -28
- package/src/function/function-runner.ts.bak +188 -0
- package/src/function/functions.types.ts +68 -51
- package/src/handle-error.ts +1 -1
- package/src/middleware/auth-apikey.test.ts +360 -0
- package/src/middleware/auth-apikey.ts +3 -7
- package/src/middleware/auth-bearer.test.ts +447 -0
- package/src/middleware/auth-bearer.ts +27 -33
- package/src/middleware/auth-cookie.test.ts +525 -0
- package/src/middleware/auth-cookie.ts +6 -6
- package/src/middleware/timeout.ts +4 -2
- package/src/middleware-runner.test.ts +58 -127
- package/src/middleware-runner.ts +13 -16
- package/src/permissions.test.ts +41 -45
- package/src/permissions.ts +21 -21
- package/src/services/user-session-service.ts +14 -4
- package/src/types/core.types.ts +67 -55
- package/src/utils.ts +4 -4
- package/src/wirings/channel/channel-common.ts +4 -7
- package/src/wirings/channel/channel-handler.ts +16 -22
- package/src/wirings/channel/channel-runner.ts +2 -2
- package/src/wirings/channel/channel.types.ts +14 -11
- package/src/wirings/channel/local/local-channel-runner.test.ts +5 -3
- package/src/wirings/channel/local/local-channel-runner.ts +23 -38
- package/src/wirings/channel/serverless/serverless-channel-runner.ts +51 -77
- package/src/wirings/cli/cli-runner.test.ts +7 -7
- package/src/wirings/cli/cli-runner.ts +24 -62
- package/src/wirings/cli/cli.types.ts +22 -16
- package/src/wirings/http/http-runner.test.ts +8 -8
- package/src/wirings/http/http-runner.ts +29 -56
- package/src/wirings/http/http.types.ts +5 -14
- package/src/wirings/mcp/mcp-runner.ts +23 -48
- package/src/wirings/mcp/mcp.types.ts +6 -0
- package/src/wirings/queue/queue-runner.test.ts +641 -0
- package/src/wirings/queue/queue-runner.ts +10 -32
- package/src/wirings/queue/queue.types.ts +4 -11
- package/src/wirings/rpc/rpc-runner.ts +41 -55
- package/src/wirings/scheduler/scheduler-runner.test.ts +627 -0
- package/src/wirings/scheduler/scheduler-runner.ts +24 -50
- package/src/wirings/scheduler/scheduler.types.ts +3 -9
- package/src/wirings/workflow/index.ts +24 -3
- package/src/wirings/workflow/pikku-workflow-service.ts +83 -93
- package/src/wirings/workflow/workflow-runner.ts +12 -25
- package/src/wirings/workflow/workflow.types.ts +144 -54
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
import { describe, test, beforeEach } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { authCookie } from './auth-cookie.js'
|
|
4
|
+
import { resetPikkuState } from '../pikku-state.js'
|
|
5
|
+
import { CoreUserSession } from '../types/core.types.js'
|
|
6
|
+
import { PikkuSessionService } from '../services/user-session-service.js'
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
resetPikkuState()
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
const createMockHTTPRequest = (cookies: Record<string, string> = {}) => ({
|
|
13
|
+
cookie: (name: string) => cookies[name] || null,
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
const createMockHTTPResponse = () => {
|
|
17
|
+
const setCookies: Array<{
|
|
18
|
+
name: string
|
|
19
|
+
value: string
|
|
20
|
+
options: any
|
|
21
|
+
}> = []
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
cookie: (name: string, value: string, options: any) => {
|
|
25
|
+
setCookies.push({ name, value, options })
|
|
26
|
+
},
|
|
27
|
+
getCookies: () => setCookies,
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const createMockLogger = () => {
|
|
32
|
+
const logs: Array<{ level: string; message: string }> = []
|
|
33
|
+
return {
|
|
34
|
+
info: (msg: string) => logs.push({ level: 'info', message: msg }),
|
|
35
|
+
warn: (msg: string) => logs.push({ level: 'warn', message: msg }),
|
|
36
|
+
error: (msg: string) => logs.push({ level: 'error', message: msg }),
|
|
37
|
+
debug: (msg: string) => logs.push({ level: 'debug', message: msg }),
|
|
38
|
+
getLogs: () => logs,
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describe('authCookie middleware', () => {
|
|
43
|
+
test('should decode session from cookie on request', async () => {
|
|
44
|
+
const mockUserSession: CoreUserSession = { userId: 'user123' }
|
|
45
|
+
const SessionService = new PikkuSessionService<CoreUserSession>()
|
|
46
|
+
const jwtService = {
|
|
47
|
+
encode: async () => 'encoded-jwt',
|
|
48
|
+
decode: async (token: string) => {
|
|
49
|
+
assert.equal(token, 'cookie-jwt-value')
|
|
50
|
+
return mockUserSession
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const middleware = authCookie({
|
|
55
|
+
name: 'session',
|
|
56
|
+
expiresIn: { value: 30, unit: 'day' },
|
|
57
|
+
options: { httpOnly: true },
|
|
58
|
+
})
|
|
59
|
+
let nextCalled = false
|
|
60
|
+
|
|
61
|
+
const mockResponse = createMockHTTPResponse()
|
|
62
|
+
|
|
63
|
+
await middleware(
|
|
64
|
+
{
|
|
65
|
+
jwt: jwtService,
|
|
66
|
+
logger: createMockLogger(),
|
|
67
|
+
} as any,
|
|
68
|
+
{
|
|
69
|
+
session: SessionService,
|
|
70
|
+
http: {
|
|
71
|
+
request: createMockHTTPRequest({ session: 'cookie-jwt-value' }),
|
|
72
|
+
response: mockResponse,
|
|
73
|
+
},
|
|
74
|
+
} as any,
|
|
75
|
+
async () => {
|
|
76
|
+
nextCalled = true
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
assert.equal(nextCalled, true)
|
|
81
|
+
assert.deepEqual(SessionService.get(), mockUserSession)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
test('should set cookie in response when session changes', async () => {
|
|
85
|
+
const mockUserSession: CoreUserSession = { userId: 'user456' }
|
|
86
|
+
const SessionService = new PikkuSessionService<CoreUserSession>()
|
|
87
|
+
const jwtService = {
|
|
88
|
+
encode: async (expiresIn: any, payload: any) => {
|
|
89
|
+
assert.deepEqual(expiresIn, { value: 7, unit: 'day' })
|
|
90
|
+
assert.deepEqual(payload, mockUserSession)
|
|
91
|
+
return 'new-encoded-jwt'
|
|
92
|
+
},
|
|
93
|
+
decode: async () => null,
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const middleware = authCookie({
|
|
97
|
+
name: 'auth_token',
|
|
98
|
+
expiresIn: { value: 7, unit: 'day' },
|
|
99
|
+
options: { httpOnly: true, secure: true, sameSite: 'strict' },
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
const mockResponse = createMockHTTPResponse()
|
|
103
|
+
|
|
104
|
+
await middleware(
|
|
105
|
+
{
|
|
106
|
+
jwt: jwtService,
|
|
107
|
+
logger: createMockLogger(),
|
|
108
|
+
} as any,
|
|
109
|
+
{
|
|
110
|
+
session: SessionService,
|
|
111
|
+
http: {
|
|
112
|
+
request: createMockHTTPRequest({}),
|
|
113
|
+
response: mockResponse,
|
|
114
|
+
},
|
|
115
|
+
} as any,
|
|
116
|
+
async () => {
|
|
117
|
+
// Simulate session change
|
|
118
|
+
SessionService.set(mockUserSession)
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
const setCookies = mockResponse.getCookies()
|
|
123
|
+
assert.equal(setCookies.length, 1)
|
|
124
|
+
assert.equal(setCookies[0].name, 'auth_token')
|
|
125
|
+
assert.equal(setCookies[0].value, 'new-encoded-jwt')
|
|
126
|
+
assert.equal(setCookies[0].options.httpOnly, true)
|
|
127
|
+
assert.equal(setCookies[0].options.secure, true)
|
|
128
|
+
assert.equal(setCookies[0].options.sameSite, 'strict')
|
|
129
|
+
assert(setCookies[0].options.expires instanceof Date)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
test('should not set cookie when session does not change', async () => {
|
|
133
|
+
const mockUserSession: CoreUserSession = { userId: 'user789' }
|
|
134
|
+
const SessionService = new PikkuSessionService<CoreUserSession>()
|
|
135
|
+
const jwtService = {
|
|
136
|
+
encode: async () => 'encoded-jwt',
|
|
137
|
+
decode: async () => mockUserSession,
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const middleware = authCookie({
|
|
141
|
+
name: 'session',
|
|
142
|
+
expiresIn: { value: 30, unit: 'day' },
|
|
143
|
+
options: { httpOnly: true },
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
const mockResponse = createMockHTTPResponse()
|
|
147
|
+
|
|
148
|
+
await middleware(
|
|
149
|
+
{
|
|
150
|
+
jwt: jwtService,
|
|
151
|
+
logger: createMockLogger(),
|
|
152
|
+
} as any,
|
|
153
|
+
{
|
|
154
|
+
session: SessionService,
|
|
155
|
+
http: {
|
|
156
|
+
request: createMockHTTPRequest({ session: 'existing-jwt' }),
|
|
157
|
+
response: mockResponse,
|
|
158
|
+
},
|
|
159
|
+
} as any,
|
|
160
|
+
async () => {
|
|
161
|
+
// Session is loaded but not changed
|
|
162
|
+
}
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
const setCookies = mockResponse.getCookies()
|
|
166
|
+
assert.equal(setCookies.length, 0)
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
test('should not decode cookie when cookie is missing', async () => {
|
|
170
|
+
const SessionService = new PikkuSessionService<CoreUserSession>()
|
|
171
|
+
let decodeCalled = false
|
|
172
|
+
const jwtService = {
|
|
173
|
+
encode: async () => 'encoded-jwt',
|
|
174
|
+
decode: async () => {
|
|
175
|
+
decodeCalled = true
|
|
176
|
+
return { userId: 'test' }
|
|
177
|
+
},
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const middleware = authCookie({
|
|
181
|
+
name: 'session',
|
|
182
|
+
expiresIn: { value: 30, unit: 'day' },
|
|
183
|
+
options: { httpOnly: true },
|
|
184
|
+
})
|
|
185
|
+
let nextCalled = false
|
|
186
|
+
|
|
187
|
+
const mockResponse = createMockHTTPResponse()
|
|
188
|
+
|
|
189
|
+
await middleware(
|
|
190
|
+
{
|
|
191
|
+
jwt: jwtService,
|
|
192
|
+
logger: createMockLogger(),
|
|
193
|
+
} as any,
|
|
194
|
+
{
|
|
195
|
+
session: SessionService,
|
|
196
|
+
http: {
|
|
197
|
+
request: createMockHTTPRequest({}),
|
|
198
|
+
response: mockResponse,
|
|
199
|
+
},
|
|
200
|
+
} as any,
|
|
201
|
+
async () => {
|
|
202
|
+
nextCalled = true
|
|
203
|
+
}
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
assert.equal(nextCalled, true)
|
|
207
|
+
assert.equal(decodeCalled, false)
|
|
208
|
+
assert.equal(SessionService.get(), undefined)
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
test('should not set session when JWT decode returns null', async () => {
|
|
212
|
+
const SessionService = new PikkuSessionService<CoreUserSession>()
|
|
213
|
+
const jwtService = {
|
|
214
|
+
encode: async () => 'encoded-jwt',
|
|
215
|
+
decode: async () => null,
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const middleware = authCookie({
|
|
219
|
+
name: 'session',
|
|
220
|
+
expiresIn: { value: 30, unit: 'day' },
|
|
221
|
+
options: { httpOnly: true },
|
|
222
|
+
})
|
|
223
|
+
let nextCalled = false
|
|
224
|
+
|
|
225
|
+
const mockResponse = createMockHTTPResponse()
|
|
226
|
+
|
|
227
|
+
await middleware(
|
|
228
|
+
{
|
|
229
|
+
jwt: jwtService,
|
|
230
|
+
logger: createMockLogger(),
|
|
231
|
+
} as any,
|
|
232
|
+
{
|
|
233
|
+
session: SessionService,
|
|
234
|
+
http: {
|
|
235
|
+
request: createMockHTTPRequest({ session: 'invalid-jwt' }),
|
|
236
|
+
response: mockResponse,
|
|
237
|
+
},
|
|
238
|
+
} as any,
|
|
239
|
+
async () => {
|
|
240
|
+
nextCalled = true
|
|
241
|
+
}
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
assert.equal(nextCalled, true)
|
|
245
|
+
assert.equal(SessionService.get(), undefined)
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
test('should skip middleware when session already exists', async () => {
|
|
249
|
+
const existingSession: CoreUserSession = { userId: 'existing' }
|
|
250
|
+
const SessionService = new PikkuSessionService<CoreUserSession>()
|
|
251
|
+
SessionService.setInitial(existingSession)
|
|
252
|
+
|
|
253
|
+
let decodeCalled = false
|
|
254
|
+
const jwtService = {
|
|
255
|
+
encode: async () => 'encoded-jwt',
|
|
256
|
+
decode: async () => {
|
|
257
|
+
decodeCalled = true
|
|
258
|
+
return { userId: 'new' }
|
|
259
|
+
},
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const middleware = authCookie({
|
|
263
|
+
name: 'session',
|
|
264
|
+
expiresIn: { value: 30, unit: 'day' },
|
|
265
|
+
options: { httpOnly: true },
|
|
266
|
+
})
|
|
267
|
+
let nextCalled = false
|
|
268
|
+
|
|
269
|
+
const mockResponse = createMockHTTPResponse()
|
|
270
|
+
|
|
271
|
+
await middleware(
|
|
272
|
+
{
|
|
273
|
+
jwt: jwtService,
|
|
274
|
+
logger: createMockLogger(),
|
|
275
|
+
} as any,
|
|
276
|
+
{
|
|
277
|
+
session: SessionService,
|
|
278
|
+
http: {
|
|
279
|
+
request: createMockHTTPRequest({ session: 'some-jwt' }),
|
|
280
|
+
response: mockResponse,
|
|
281
|
+
},
|
|
282
|
+
} as any,
|
|
283
|
+
async () => {
|
|
284
|
+
nextCalled = true
|
|
285
|
+
}
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
assert.equal(nextCalled, true)
|
|
289
|
+
assert.equal(decodeCalled, false)
|
|
290
|
+
assert.deepEqual(SessionService.get(), existingSession)
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
test('should skip middleware when http.request is not available', async () => {
|
|
294
|
+
const SessionService = new PikkuSessionService<CoreUserSession>()
|
|
295
|
+
const jwtService = {
|
|
296
|
+
encode: async () => 'encoded-jwt',
|
|
297
|
+
decode: async () => ({ userId: 'test' }),
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const middleware = authCookie({
|
|
301
|
+
name: 'session',
|
|
302
|
+
expiresIn: { value: 30, unit: 'day' },
|
|
303
|
+
options: { httpOnly: true },
|
|
304
|
+
})
|
|
305
|
+
let nextCalled = false
|
|
306
|
+
|
|
307
|
+
await middleware(
|
|
308
|
+
{
|
|
309
|
+
jwt: jwtService,
|
|
310
|
+
logger: createMockLogger(),
|
|
311
|
+
} as any,
|
|
312
|
+
{ session: SessionService } as any,
|
|
313
|
+
async () => {
|
|
314
|
+
nextCalled = true
|
|
315
|
+
}
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
assert.equal(nextCalled, true)
|
|
319
|
+
assert.equal(SessionService.get(), undefined)
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
test('should not decode when jwtService is not available on request', async () => {
|
|
323
|
+
const SessionService = new PikkuSessionService<CoreUserSession>()
|
|
324
|
+
|
|
325
|
+
const middleware = authCookie({
|
|
326
|
+
name: 'session',
|
|
327
|
+
expiresIn: { value: 30, unit: 'day' },
|
|
328
|
+
options: { httpOnly: true },
|
|
329
|
+
})
|
|
330
|
+
let nextCalled = false
|
|
331
|
+
|
|
332
|
+
const mockResponse = createMockHTTPResponse()
|
|
333
|
+
|
|
334
|
+
await middleware(
|
|
335
|
+
{
|
|
336
|
+
jwt: undefined,
|
|
337
|
+
logger: createMockLogger(),
|
|
338
|
+
} as any,
|
|
339
|
+
{
|
|
340
|
+
session: SessionService,
|
|
341
|
+
http: {
|
|
342
|
+
request: createMockHTTPRequest({ session: 'some-jwt' }),
|
|
343
|
+
response: mockResponse,
|
|
344
|
+
},
|
|
345
|
+
} as any,
|
|
346
|
+
async () => {
|
|
347
|
+
nextCalled = true
|
|
348
|
+
}
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
assert.equal(nextCalled, true)
|
|
352
|
+
assert.equal(SessionService.get(), undefined)
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
test('should warn when jwtService is not available on response and session changed', async () => {
|
|
356
|
+
const mockUserSession: CoreUserSession = { userId: 'user999' }
|
|
357
|
+
const SessionService = new PikkuSessionService<CoreUserSession>()
|
|
358
|
+
const mockLogger = createMockLogger()
|
|
359
|
+
|
|
360
|
+
const middleware = authCookie({
|
|
361
|
+
name: 'session',
|
|
362
|
+
expiresIn: { value: 30, unit: 'day' },
|
|
363
|
+
options: { httpOnly: true },
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
const mockResponse = createMockHTTPResponse()
|
|
367
|
+
|
|
368
|
+
await middleware(
|
|
369
|
+
{
|
|
370
|
+
jwt: undefined,
|
|
371
|
+
logger: mockLogger,
|
|
372
|
+
} as any,
|
|
373
|
+
{
|
|
374
|
+
session: SessionService,
|
|
375
|
+
http: {
|
|
376
|
+
request: createMockHTTPRequest({}),
|
|
377
|
+
response: mockResponse,
|
|
378
|
+
},
|
|
379
|
+
} as any,
|
|
380
|
+
async () => {
|
|
381
|
+
// Simulate session change
|
|
382
|
+
SessionService.set(mockUserSession)
|
|
383
|
+
}
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
const logs = mockLogger.getLogs()
|
|
387
|
+
assert.equal(logs.length, 1)
|
|
388
|
+
assert.equal(logs[0].level, 'warn')
|
|
389
|
+
assert.equal(
|
|
390
|
+
logs[0].message,
|
|
391
|
+
'No JWT service available, unable to set cookie'
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
const setCookies = mockResponse.getCookies()
|
|
395
|
+
assert.equal(setCookies.length, 0)
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
test('should not set cookie when http.response is not available', async () => {
|
|
399
|
+
const mockUserSession: CoreUserSession = { userId: 'user888' }
|
|
400
|
+
const SessionService = new PikkuSessionService<CoreUserSession>()
|
|
401
|
+
const jwtService = {
|
|
402
|
+
encode: async () => 'encoded-jwt',
|
|
403
|
+
decode: async () => null,
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const middleware = authCookie({
|
|
407
|
+
name: 'session',
|
|
408
|
+
expiresIn: { value: 30, unit: 'day' },
|
|
409
|
+
options: { httpOnly: true },
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
await middleware(
|
|
413
|
+
{
|
|
414
|
+
jwt: jwtService,
|
|
415
|
+
logger: createMockLogger(),
|
|
416
|
+
} as any,
|
|
417
|
+
{
|
|
418
|
+
session: SessionService,
|
|
419
|
+
http: {
|
|
420
|
+
request: createMockHTTPRequest({}),
|
|
421
|
+
response: undefined,
|
|
422
|
+
},
|
|
423
|
+
} as any,
|
|
424
|
+
async () => {
|
|
425
|
+
// Simulate session change
|
|
426
|
+
SessionService.set(mockUserSession)
|
|
427
|
+
}
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
// Should complete without errors even though response is not available
|
|
431
|
+
assert.equal(SessionService.get(), mockUserSession)
|
|
432
|
+
})
|
|
433
|
+
|
|
434
|
+
test('should handle custom cookie name', async () => {
|
|
435
|
+
const mockUserSession: CoreUserSession = { userId: 'custom-user' }
|
|
436
|
+
const SessionService = new PikkuSessionService<CoreUserSession>()
|
|
437
|
+
const jwtService = {
|
|
438
|
+
encode: async () => 'custom-encoded-jwt',
|
|
439
|
+
decode: async (token: string) => {
|
|
440
|
+
assert.equal(token, 'custom-cookie-value')
|
|
441
|
+
return mockUserSession
|
|
442
|
+
},
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const middleware = authCookie({
|
|
446
|
+
name: 'my_custom_cookie',
|
|
447
|
+
expiresIn: { value: 1, unit: 'hour' },
|
|
448
|
+
options: {},
|
|
449
|
+
})
|
|
450
|
+
|
|
451
|
+
const mockResponse = createMockHTTPResponse()
|
|
452
|
+
|
|
453
|
+
await middleware(
|
|
454
|
+
{
|
|
455
|
+
jwt: jwtService,
|
|
456
|
+
logger: createMockLogger(),
|
|
457
|
+
} as any,
|
|
458
|
+
{
|
|
459
|
+
session: SessionService,
|
|
460
|
+
http: {
|
|
461
|
+
request: createMockHTTPRequest({
|
|
462
|
+
my_custom_cookie: 'custom-cookie-value',
|
|
463
|
+
}),
|
|
464
|
+
response: mockResponse,
|
|
465
|
+
},
|
|
466
|
+
} as any,
|
|
467
|
+
async () => {
|
|
468
|
+
// Modify session to trigger cookie setting
|
|
469
|
+
SessionService.set({ ...mockUserSession, extra: 'data' })
|
|
470
|
+
}
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
const setCookies = mockResponse.getCookies()
|
|
474
|
+
assert.equal(setCookies.length, 1)
|
|
475
|
+
assert.equal(setCookies[0].name, 'my_custom_cookie')
|
|
476
|
+
})
|
|
477
|
+
|
|
478
|
+
test('should properly set expiration time in cookie options', async () => {
|
|
479
|
+
const mockUserSession: CoreUserSession = { userId: 'expiry-test' }
|
|
480
|
+
const SessionService = new PikkuSessionService<CoreUserSession>()
|
|
481
|
+
const jwtService = {
|
|
482
|
+
encode: async () => 'encoded-jwt',
|
|
483
|
+
decode: async () => null,
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const middleware = authCookie({
|
|
487
|
+
name: 'session',
|
|
488
|
+
expiresIn: { value: 15, unit: 'minute' },
|
|
489
|
+
options: { httpOnly: true },
|
|
490
|
+
})
|
|
491
|
+
|
|
492
|
+
const mockResponse = createMockHTTPResponse()
|
|
493
|
+
const beforeTime = new Date()
|
|
494
|
+
|
|
495
|
+
await middleware(
|
|
496
|
+
{
|
|
497
|
+
jwt: jwtService,
|
|
498
|
+
logger: createMockLogger(),
|
|
499
|
+
} as any,
|
|
500
|
+
{
|
|
501
|
+
session: SessionService,
|
|
502
|
+
http: {
|
|
503
|
+
request: createMockHTTPRequest({}),
|
|
504
|
+
response: mockResponse,
|
|
505
|
+
},
|
|
506
|
+
} as any,
|
|
507
|
+
async () => {
|
|
508
|
+
SessionService.set(mockUserSession)
|
|
509
|
+
}
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
const afterTime = new Date()
|
|
513
|
+
const setCookies = mockResponse.getCookies()
|
|
514
|
+
assert.equal(setCookies.length, 1)
|
|
515
|
+
|
|
516
|
+
const expiresDate = setCookies[0].options.expires
|
|
517
|
+
assert(expiresDate instanceof Date)
|
|
518
|
+
|
|
519
|
+
// Expiration should be approximately 15 minutes from now
|
|
520
|
+
const expectedExpiry = new Date(beforeTime.getTime() + 15 * 60 * 1000)
|
|
521
|
+
const timeDiff = Math.abs(expiresDate.getTime() - expectedExpiry.getTime())
|
|
522
|
+
// Allow 5 second tolerance for test execution time
|
|
523
|
+
assert(timeDiff < 5000, `Time difference ${timeDiff}ms is too large`)
|
|
524
|
+
})
|
|
525
|
+
})
|
|
@@ -40,11 +40,11 @@ export const authCookie = pikkuMiddlewareFactory<{
|
|
|
40
40
|
}>(({ name, options, expiresIn }) =>
|
|
41
41
|
pikkuMiddleware(
|
|
42
42
|
async (
|
|
43
|
-
{
|
|
44
|
-
{ http },
|
|
43
|
+
{ jwt: jwtService, logger },
|
|
44
|
+
{ http, session: SessionService },
|
|
45
45
|
next
|
|
46
46
|
) => {
|
|
47
|
-
if (!http?.request ||
|
|
47
|
+
if (!http?.request || !SessionService || SessionService.get()) {
|
|
48
48
|
return next()
|
|
49
49
|
}
|
|
50
50
|
|
|
@@ -53,7 +53,7 @@ export const authCookie = pikkuMiddlewareFactory<{
|
|
|
53
53
|
if (cookieValue && jwtService) {
|
|
54
54
|
const userSession = await jwtService.decode(cookieValue)
|
|
55
55
|
if (userSession) {
|
|
56
|
-
|
|
56
|
+
SessionService.setInitial(userSession)
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
@@ -64,8 +64,8 @@ export const authCookie = pikkuMiddlewareFactory<{
|
|
|
64
64
|
return
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
if (
|
|
68
|
-
const session =
|
|
67
|
+
if (SessionService.sessionChanged) {
|
|
68
|
+
const session = SessionService.get()
|
|
69
69
|
if (jwtService) {
|
|
70
70
|
http.response.cookie(
|
|
71
71
|
name,
|
|
@@ -3,11 +3,13 @@ import { pikkuMiddleware, pikkuMiddlewareFactory } from '../types/core.types.js'
|
|
|
3
3
|
|
|
4
4
|
export const timeout = () =>
|
|
5
5
|
pikkuMiddlewareFactory<{ timeout: number }>(({ timeout }) =>
|
|
6
|
-
pikkuMiddleware(async (_services,
|
|
7
|
-
setTimeout(() => {
|
|
6
|
+
pikkuMiddleware(async (_services, _wire, next) => {
|
|
7
|
+
const timeoutId = setTimeout(() => {
|
|
8
8
|
throw new RequestTimeoutError()
|
|
9
9
|
}, timeout)
|
|
10
10
|
|
|
11
11
|
await next()
|
|
12
|
+
|
|
13
|
+
clearTimeout(timeoutId)
|
|
12
14
|
})
|
|
13
15
|
)
|