@pikku/core 0.6.9
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 +339 -0
- package/README.md +5 -0
- package/dist/channel/channel-handler.d.ts +3 -0
- package/dist/channel/channel-handler.js +97 -0
- package/dist/channel/channel-runner.d.ts +26 -0
- package/dist/channel/channel-runner.js +86 -0
- package/dist/channel/channel-store.d.ts +14 -0
- package/dist/channel/channel-store.js +2 -0
- package/dist/channel/channel.types.d.ts +106 -0
- package/dist/channel/channel.types.js +1 -0
- package/dist/channel/eventhub-forwarder.d.ts +17 -0
- package/dist/channel/eventhub-forwarder.js +1 -0
- package/dist/channel/eventhub-service.d.ts +23 -0
- package/dist/channel/eventhub-service.js +1 -0
- package/dist/channel/eventhub-store.d.ts +5 -0
- package/dist/channel/eventhub-store.js +2 -0
- package/dist/channel/index.d.ts +9 -0
- package/dist/channel/index.js +9 -0
- package/dist/channel/local/index.d.ts +3 -0
- package/dist/channel/local/index.js +3 -0
- package/dist/channel/local/local-channel-handler.d.ts +17 -0
- package/dist/channel/local/local-channel-handler.js +41 -0
- package/dist/channel/local/local-channel-runner.d.ts +3 -0
- package/dist/channel/local/local-channel-runner.js +46 -0
- package/dist/channel/local/local-eventhub-service.d.ts +44 -0
- package/dist/channel/local/local-eventhub-service.js +81 -0
- package/dist/channel/log-channels.d.ts +6 -0
- package/dist/channel/log-channels.js +17 -0
- package/dist/channel/pikku-abstract-channel-handler.d.ts +15 -0
- package/dist/channel/pikku-abstract-channel-handler.js +33 -0
- package/dist/channel/serverless/index.d.ts +1 -0
- package/dist/channel/serverless/index.js +1 -0
- package/dist/channel/serverless/serverless-channel-runner.d.ts +10 -0
- package/dist/channel/serverless/serverless-channel-runner.js +98 -0
- package/dist/errors/error-handler.d.ts +39 -0
- package/dist/errors/error-handler.js +54 -0
- package/dist/errors/errors.d.ts +190 -0
- package/dist/errors/errors.js +315 -0
- package/dist/errors/index.d.ts +2 -0
- package/dist/errors/index.js +2 -0
- package/dist/http/http-route-runner.d.ts +30 -0
- package/dist/http/http-route-runner.js +208 -0
- package/dist/http/http-routes.types.d.ts +127 -0
- package/dist/http/http-routes.types.js +1 -0
- package/dist/http/http-session-service.d.ts +15 -0
- package/dist/http/http-session-service.js +1 -0
- package/dist/http/index.d.ts +6 -0
- package/dist/http/index.js +5 -0
- package/dist/http/log-http-routes.d.ts +6 -0
- package/dist/http/log-http-routes.js +17 -0
- package/dist/http/pikku-http-abstract-request.d.ts +57 -0
- package/dist/http/pikku-http-abstract-request.js +69 -0
- package/dist/http/pikku-http-abstract-response.d.ts +58 -0
- package/dist/http/pikku-http-abstract-response.js +54 -0
- package/dist/http/pikku-http-session-service.d.ts +45 -0
- package/dist/http/pikku-http-session-service.js +92 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +14 -0
- package/dist/permissions.d.ts +10 -0
- package/dist/permissions.js +32 -0
- package/dist/pikku-request.d.ts +14 -0
- package/dist/pikku-request.js +21 -0
- package/dist/pikku-response.d.ts +6 -0
- package/dist/pikku-response.js +6 -0
- package/dist/scheduler/index.d.ts +3 -0
- package/dist/scheduler/index.js +3 -0
- package/dist/scheduler/log-schedulers.d.ts +6 -0
- package/dist/scheduler/log-schedulers.js +17 -0
- package/dist/scheduler/scheduler-runner.d.ts +21 -0
- package/dist/scheduler/scheduler-runner.js +73 -0
- package/dist/scheduler/scheduler.types.d.ts +21 -0
- package/dist/scheduler/scheduler.types.js +1 -0
- package/dist/schema.d.ts +26 -0
- package/dist/schema.js +100 -0
- package/dist/services/content-service.d.ts +55 -0
- package/dist/services/content-service.js +1 -0
- package/dist/services/index.d.ts +12 -0
- package/dist/services/index.js +14 -0
- package/dist/services/jwt-service.d.ts +29 -0
- package/dist/services/jwt-service.js +1 -0
- package/dist/services/local-content.d.ts +33 -0
- package/dist/services/local-content.js +6 -0
- package/dist/services/local-secrets.d.ts +26 -0
- package/dist/services/local-secrets.js +39 -0
- package/dist/services/local-variables.d.ts +7 -0
- package/dist/services/local-variables.js +12 -0
- package/dist/services/logger-console.d.ts +52 -0
- package/dist/services/logger-console.js +82 -0
- package/dist/services/logger.d.ts +48 -0
- package/dist/services/logger.js +9 -0
- package/dist/services/schema-service.d.ts +24 -0
- package/dist/services/schema-service.js +1 -0
- package/dist/services/secret-service.d.ts +17 -0
- package/dist/services/secret-service.js +1 -0
- package/dist/services/user-session-service.d.ts +6 -0
- package/dist/services/user-session-service.js +1 -0
- package/dist/services/variables-service.d.ts +4 -0
- package/dist/services/variables-service.js +1 -0
- package/dist/types/core.types.d.ts +109 -0
- package/dist/types/core.types.js +1 -0
- package/dist/types/functions.types.d.ts +27 -0
- package/dist/types/functions.types.js +1 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +15 -0
- package/package.json +48 -0
- package/run-tests.sh +53 -0
- package/src/channel/channel-handler.ts +179 -0
- package/src/channel/channel-runner.ts +156 -0
- package/src/channel/channel-store.ts +32 -0
- package/src/channel/channel.types.ts +202 -0
- package/src/channel/eventhub-forwarder.ts +25 -0
- package/src/channel/eventhub-service.ts +30 -0
- package/src/channel/eventhub-store.ts +8 -0
- package/src/channel/index.ts +9 -0
- package/src/channel/local/index.ts +3 -0
- package/src/channel/local/local-channel-handler.ts +56 -0
- package/src/channel/local/local-channel-runner.test.ts +128 -0
- package/src/channel/local/local-channel-runner.ts +94 -0
- package/src/channel/local/local-eventhub-service.test.ts +95 -0
- package/src/channel/local/local-eventhub-service.ts +95 -0
- package/src/channel/log-channels.ts +20 -0
- package/src/channel/pikku-abstract-channel-handler.test.ts +77 -0
- package/src/channel/pikku-abstract-channel-handler.ts +44 -0
- package/src/channel/serverless/index.ts +1 -0
- package/src/channel/serverless/serverless-channel-runner.ts +190 -0
- package/src/errors/error-handler.ts +74 -0
- package/src/errors/error.test.ts +54 -0
- package/src/errors/errors.ts +340 -0
- package/src/errors/index.ts +2 -0
- package/src/http/http-route-runner.test.ts +193 -0
- package/src/http/http-route-runner.ts +365 -0
- package/src/http/http-routes.types.ts +189 -0
- package/src/http/http-session-service.ts +19 -0
- package/src/http/index.ts +16 -0
- package/src/http/log-http-routes.ts +20 -0
- package/src/http/pikku-http-abstract-request.ts +95 -0
- package/src/http/pikku-http-abstract-response.ts +79 -0
- package/src/http/pikku-http-session-service.test.ts +106 -0
- package/src/http/pikku-http-session-service.ts +135 -0
- package/src/index.ts +15 -0
- package/src/permissions.test.ts +58 -0
- package/src/permissions.ts +45 -0
- package/src/pikku-request.ts +19 -0
- package/src/pikku-response.ts +5 -0
- package/src/scheduler/index.ts +11 -0
- package/src/scheduler/log-schedulers.ts +20 -0
- package/src/scheduler/scheduler-runner.ts +132 -0
- package/src/scheduler/scheduler.types.ts +27 -0
- package/src/schema.test.ts +57 -0
- package/src/schema.ts +124 -0
- package/src/services/content-service.ts +69 -0
- package/src/services/index.ts +16 -0
- package/src/services/jwt-service.ts +36 -0
- package/src/services/local-content.ts +39 -0
- package/src/services/local-secrets.ts +43 -0
- package/src/services/local-variables.ts +17 -0
- package/src/services/logger-console.ts +97 -0
- package/src/services/logger.ts +57 -0
- package/src/services/schema-service.ts +26 -0
- package/src/services/secret-service.ts +17 -0
- package/src/services/user-session-service.ts +7 -0
- package/src/services/variables-service.ts +6 -0
- package/src/types/core.types.ts +162 -0
- package/src/types/functions.types.ts +44 -0
- package/src/utils.ts +21 -0
- package/tsconfig.json +13 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { test, describe, beforeEach, afterEach } from 'node:test'
|
|
2
|
+
import * as assert from 'assert'
|
|
3
|
+
import { NotFoundError, NotImplementedError } from '../errors/errors.js'
|
|
4
|
+
import { PikkuHTTPAbstractRequest } from './pikku-http-abstract-request.js'
|
|
5
|
+
import { PikkuHTTPAbstractResponse } from './pikku-http-abstract-response.js'
|
|
6
|
+
import { JSONValue } from '../types/core.types.js'
|
|
7
|
+
import {
|
|
8
|
+
getUserSession,
|
|
9
|
+
runHTTPRoute,
|
|
10
|
+
clearRoutes,
|
|
11
|
+
addRoute,
|
|
12
|
+
} from './http-route-runner.js'
|
|
13
|
+
|
|
14
|
+
class PikkuTestRequest extends PikkuHTTPAbstractRequest {
|
|
15
|
+
public getBody(): Promise<unknown> {
|
|
16
|
+
throw new Error('Method not implemented.')
|
|
17
|
+
}
|
|
18
|
+
public getHeader(_headerName: string): string | undefined {
|
|
19
|
+
throw new Error('Method not implemented.')
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
class PikkuTestResponse extends PikkuHTTPAbstractResponse {
|
|
24
|
+
public setStatus(_status: number): void {
|
|
25
|
+
throw new Error('Method not implemented.')
|
|
26
|
+
}
|
|
27
|
+
public setJson(_body: JSONValue): void {
|
|
28
|
+
throw new Error('Method not implemented.')
|
|
29
|
+
}
|
|
30
|
+
public setResponse(_response: string | Buffer): void {
|
|
31
|
+
throw new Error('Method not implemented.')
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
describe('runHTTPRoute', () => {
|
|
36
|
+
let singletonServices: any
|
|
37
|
+
let createSessionServices: any
|
|
38
|
+
let request: any
|
|
39
|
+
let response: any
|
|
40
|
+
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
clearRoutes()
|
|
43
|
+
|
|
44
|
+
singletonServices = {
|
|
45
|
+
logger: {
|
|
46
|
+
info: () => {},
|
|
47
|
+
warn: () => {},
|
|
48
|
+
error: () => {},
|
|
49
|
+
},
|
|
50
|
+
httpSessionService: {
|
|
51
|
+
getUserSession: async () => {},
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
createSessionServices = async () => ({})
|
|
56
|
+
request = new PikkuTestRequest()
|
|
57
|
+
response = new PikkuTestResponse()
|
|
58
|
+
|
|
59
|
+
request.getData = async () => ({})
|
|
60
|
+
request.getHeader = () => 'application/json'
|
|
61
|
+
response.setStatus = (status: number) => {}
|
|
62
|
+
response.setJson = (json: JSONValue) => {}
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
afterEach(() => {})
|
|
66
|
+
|
|
67
|
+
test('should throw RouteNotFoundError when no matching route is found', async () => {
|
|
68
|
+
const apiRoute = '/test'
|
|
69
|
+
const apiType = 'get'
|
|
70
|
+
|
|
71
|
+
await assert.rejects(
|
|
72
|
+
async () =>
|
|
73
|
+
runHTTPRoute({
|
|
74
|
+
request,
|
|
75
|
+
response,
|
|
76
|
+
singletonServices,
|
|
77
|
+
createSessionServices,
|
|
78
|
+
route: apiRoute,
|
|
79
|
+
method: apiType,
|
|
80
|
+
bubbleErrors: true,
|
|
81
|
+
}),
|
|
82
|
+
NotFoundError
|
|
83
|
+
)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
test('should call the route function and return its result when a matching route is found', async () => {
|
|
87
|
+
const apiRoute = '/test'
|
|
88
|
+
const apiType = 'get'
|
|
89
|
+
const routeFunc = async () => ({ success: true })
|
|
90
|
+
addRoute({
|
|
91
|
+
route: 'test',
|
|
92
|
+
method: 'get',
|
|
93
|
+
func: routeFunc,
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
const result = await runHTTPRoute({
|
|
97
|
+
request,
|
|
98
|
+
response,
|
|
99
|
+
singletonServices,
|
|
100
|
+
createSessionServices,
|
|
101
|
+
route: apiRoute,
|
|
102
|
+
method: apiType,
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
assert.deepStrictEqual(result, { success: true })
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
test('should verify permissions if provided', async () => {
|
|
109
|
+
const apiRoute = '/test'
|
|
110
|
+
const apiType = 'get'
|
|
111
|
+
const permissions = { test: async () => true }
|
|
112
|
+
const routeFunc = async () => ({ success: true })
|
|
113
|
+
|
|
114
|
+
addRoute({
|
|
115
|
+
route: 'test',
|
|
116
|
+
method: 'get',
|
|
117
|
+
func: routeFunc,
|
|
118
|
+
permissions,
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
await runHTTPRoute({
|
|
122
|
+
request,
|
|
123
|
+
response,
|
|
124
|
+
singletonServices,
|
|
125
|
+
createSessionServices,
|
|
126
|
+
route: apiRoute,
|
|
127
|
+
method: apiType,
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
assert.strictEqual(await permissions.test(), true)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
test('should handle errors and set appropriate response', async () => {
|
|
134
|
+
const apiRoute = '/test'
|
|
135
|
+
const apiType = 'get'
|
|
136
|
+
const error = new Error('Test error')
|
|
137
|
+
const routeFunc = async () => {
|
|
138
|
+
throw error
|
|
139
|
+
}
|
|
140
|
+
addRoute({
|
|
141
|
+
route: 'test',
|
|
142
|
+
method: 'get',
|
|
143
|
+
func: routeFunc,
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
await assert.rejects(
|
|
147
|
+
async () =>
|
|
148
|
+
runHTTPRoute({
|
|
149
|
+
request,
|
|
150
|
+
response,
|
|
151
|
+
singletonServices,
|
|
152
|
+
createSessionServices,
|
|
153
|
+
route: apiRoute,
|
|
154
|
+
method: apiType,
|
|
155
|
+
bubbleErrors: true,
|
|
156
|
+
}),
|
|
157
|
+
error
|
|
158
|
+
)
|
|
159
|
+
})
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
describe('getUserSession', () => {
|
|
163
|
+
let sessionService: any
|
|
164
|
+
let request: any
|
|
165
|
+
|
|
166
|
+
beforeEach(() => {
|
|
167
|
+
sessionService = {
|
|
168
|
+
getUserSession: async () => ({}),
|
|
169
|
+
}
|
|
170
|
+
request = new PikkuTestRequest()
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
test('should return user session when sessionService is provided', async () => {
|
|
174
|
+
const userSession = { id: '123', username: 'test' }
|
|
175
|
+
sessionService.getUserSession = async () => userSession
|
|
176
|
+
|
|
177
|
+
const result = await getUserSession(sessionService, true, request)
|
|
178
|
+
|
|
179
|
+
assert.deepStrictEqual(result, userSession)
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
test('should throw NotImplementedError when session is required but sessionService is not provided', async () => {
|
|
183
|
+
await assert.rejects(
|
|
184
|
+
async () => getUserSession(undefined, true, request),
|
|
185
|
+
NotImplementedError
|
|
186
|
+
)
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
test('should return undefined when session is not required and sessionService is not provided', async () => {
|
|
190
|
+
const result = await getUserSession(undefined, false, request)
|
|
191
|
+
assert.strictEqual(result, undefined)
|
|
192
|
+
})
|
|
193
|
+
})
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import { getErrorResponse } from '../errors/error-handler.js'
|
|
2
|
+
import { verifyPermissions } from '../permissions.js'
|
|
3
|
+
import {
|
|
4
|
+
CoreHTTPFunctionRoute,
|
|
5
|
+
HTTPRoutesMeta,
|
|
6
|
+
RunRouteOptions,
|
|
7
|
+
RunRouteParams,
|
|
8
|
+
PikkuHTTP,
|
|
9
|
+
} from './http-routes.types.js'
|
|
10
|
+
import { CoreUserSession, SessionServices } from '../types/core.types.js'
|
|
11
|
+
import { match } from 'path-to-regexp'
|
|
12
|
+
import { PikkuHTTPAbstractRequest } from './pikku-http-abstract-request.js'
|
|
13
|
+
import { PikkuHTTPAbstractResponse } from './pikku-http-abstract-response.js'
|
|
14
|
+
import { Logger, SchemaService } from '../services/index.js'
|
|
15
|
+
import {
|
|
16
|
+
ForbiddenError,
|
|
17
|
+
NotFoundError,
|
|
18
|
+
NotImplementedError,
|
|
19
|
+
} from '../errors/errors.js'
|
|
20
|
+
import crypto from 'crypto'
|
|
21
|
+
import { closeSessionServices } from '../utils.js'
|
|
22
|
+
import { CoreAPIChannel } from '../channel/channel.types.js'
|
|
23
|
+
import { PikkuRequest } from '../pikku-request.js'
|
|
24
|
+
import { PikkuResponse } from '../pikku-response.js'
|
|
25
|
+
import { HTTPSessionService } from './http-session-service.js'
|
|
26
|
+
import { getSchema, validateAndCoerce } from '../schema.js'
|
|
27
|
+
|
|
28
|
+
if (!globalThis.pikku?.httpRoutes) {
|
|
29
|
+
globalThis.pikku = globalThis.pikku || {}
|
|
30
|
+
globalThis.pikku.httpRoutes = []
|
|
31
|
+
globalThis.pikku.httpRoutesMeta = []
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const httpRoutes = (
|
|
35
|
+
data?: CoreHTTPFunctionRoute<any, any, any>[]
|
|
36
|
+
): CoreHTTPFunctionRoute<any, any, any>[] => {
|
|
37
|
+
if (data) {
|
|
38
|
+
globalThis.pikku.httpRoutes = data
|
|
39
|
+
}
|
|
40
|
+
return globalThis.pikku.httpRoutes
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const httpRoutesMeta = (data?: HTTPRoutesMeta): HTTPRoutesMeta => {
|
|
44
|
+
if (data) {
|
|
45
|
+
globalThis.pikku.httpRoutesMeta = data
|
|
46
|
+
}
|
|
47
|
+
return globalThis.pikku.httpRoutesMeta
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const addRoute = <
|
|
51
|
+
In,
|
|
52
|
+
Out,
|
|
53
|
+
Route extends string,
|
|
54
|
+
APIFunction,
|
|
55
|
+
APIFunctionSessionless,
|
|
56
|
+
APIPermission,
|
|
57
|
+
>(
|
|
58
|
+
route: CoreHTTPFunctionRoute<
|
|
59
|
+
In,
|
|
60
|
+
Out,
|
|
61
|
+
Route,
|
|
62
|
+
APIFunction,
|
|
63
|
+
APIFunctionSessionless,
|
|
64
|
+
APIPermission
|
|
65
|
+
>
|
|
66
|
+
) => {
|
|
67
|
+
httpRoutes().push(route as any)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const clearRoutes = () => {
|
|
71
|
+
httpRoutes([])
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @ignore
|
|
76
|
+
*/
|
|
77
|
+
export const setHTTPRoutesMeta = (_routeMeta: HTTPRoutesMeta) => {
|
|
78
|
+
httpRoutesMeta(_routeMeta)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Returns all the registered routes and associated metadata.
|
|
83
|
+
* @internal
|
|
84
|
+
*/
|
|
85
|
+
export const getRoutes = () => {
|
|
86
|
+
return {
|
|
87
|
+
routes: httpRoutes(),
|
|
88
|
+
routesMeta: httpRoutesMeta(),
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const getMatchingRoute = (
|
|
93
|
+
schemaService: SchemaService | undefined,
|
|
94
|
+
requestType: string,
|
|
95
|
+
requestPath: string
|
|
96
|
+
) => {
|
|
97
|
+
for (const route of httpRoutes()) {
|
|
98
|
+
// TODO: This is a performance improvement, but we could
|
|
99
|
+
// run against all routes if we want to return a 405 method.
|
|
100
|
+
// Probably want a cache to support.
|
|
101
|
+
if (route.method !== requestType.toLowerCase()) {
|
|
102
|
+
continue
|
|
103
|
+
}
|
|
104
|
+
const matchFunc = match(`/${route.route}`.replace(/^\/\//, '/'), {
|
|
105
|
+
decode: decodeURIComponent,
|
|
106
|
+
})
|
|
107
|
+
const matchedPath = matchFunc(requestPath.replace(/^\/\//, '/'))
|
|
108
|
+
|
|
109
|
+
if (matchedPath) {
|
|
110
|
+
// TODO: Cache this loop as a performance improvement
|
|
111
|
+
const schemaName = httpRoutesMeta().find(
|
|
112
|
+
(routeMeta) =>
|
|
113
|
+
routeMeta.method === route.method && routeMeta.route === route.route
|
|
114
|
+
)?.input
|
|
115
|
+
if (schemaName && schemaService) {
|
|
116
|
+
const schema = getSchema(schemaName)
|
|
117
|
+
schemaService.compileSchema(schemaName, schema)
|
|
118
|
+
}
|
|
119
|
+
return { matchedPath, params: matchedPath.params, route, schemaName }
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return undefined
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export const getUserSession = async <UserSession extends CoreUserSession>(
|
|
126
|
+
httpSessionService: HTTPSessionService<UserSession> | undefined,
|
|
127
|
+
auth: boolean,
|
|
128
|
+
request: PikkuHTTPAbstractRequest
|
|
129
|
+
): Promise<CoreUserSession | undefined> => {
|
|
130
|
+
if (httpSessionService) {
|
|
131
|
+
return (await httpSessionService.getUserSession(
|
|
132
|
+
auth,
|
|
133
|
+
request
|
|
134
|
+
)) as UserSession
|
|
135
|
+
} else if (auth) {
|
|
136
|
+
throw new NotImplementedError('Session service not implemented')
|
|
137
|
+
}
|
|
138
|
+
return undefined
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export const loadUserSession = async (
|
|
142
|
+
skipUserSession: boolean,
|
|
143
|
+
requiresSession: boolean,
|
|
144
|
+
http: PikkuHTTP | undefined,
|
|
145
|
+
matchedPath: any,
|
|
146
|
+
route:
|
|
147
|
+
| CoreHTTPFunctionRoute<unknown, unknown, any>
|
|
148
|
+
| CoreAPIChannel<unknown, any>,
|
|
149
|
+
logger: Logger,
|
|
150
|
+
httpSessionService: HTTPSessionService | undefined
|
|
151
|
+
) => {
|
|
152
|
+
if (skipUserSession && requiresSession) {
|
|
153
|
+
throw new Error("Can't skip trying to get user session if auth is required")
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (skipUserSession === false) {
|
|
157
|
+
try {
|
|
158
|
+
if (http?.request) {
|
|
159
|
+
return await getUserSession(
|
|
160
|
+
httpSessionService,
|
|
161
|
+
requiresSession,
|
|
162
|
+
http.request
|
|
163
|
+
)
|
|
164
|
+
} else if (requiresSession) {
|
|
165
|
+
logger.error({
|
|
166
|
+
action: 'Can only get user session with HTTP request',
|
|
167
|
+
path: matchedPath,
|
|
168
|
+
route,
|
|
169
|
+
})
|
|
170
|
+
throw new Error('Can only get user session with HTTP request')
|
|
171
|
+
}
|
|
172
|
+
} catch (e: any) {
|
|
173
|
+
if (requiresSession) {
|
|
174
|
+
logger.info({
|
|
175
|
+
action: 'Rejecting route (invalid session)',
|
|
176
|
+
path: matchedPath,
|
|
177
|
+
})
|
|
178
|
+
throw e
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return undefined
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export const createHTTPInteraction = (
|
|
187
|
+
request: PikkuRequest | undefined,
|
|
188
|
+
response: PikkuResponse | undefined
|
|
189
|
+
) => {
|
|
190
|
+
let http: PikkuHTTP | undefined = undefined
|
|
191
|
+
if (
|
|
192
|
+
request instanceof PikkuHTTPAbstractRequest ||
|
|
193
|
+
response instanceof PikkuHTTPAbstractResponse
|
|
194
|
+
) {
|
|
195
|
+
http = {}
|
|
196
|
+
if (request instanceof PikkuHTTPAbstractRequest) {
|
|
197
|
+
http.request = request
|
|
198
|
+
}
|
|
199
|
+
if (response instanceof PikkuHTTPAbstractResponse) {
|
|
200
|
+
http.response = response
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return http
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export const handleError = (
|
|
207
|
+
e: any,
|
|
208
|
+
http: PikkuHTTP | undefined,
|
|
209
|
+
trackerId: string,
|
|
210
|
+
logger: Logger,
|
|
211
|
+
logWarningsForStatusCodes: number[],
|
|
212
|
+
respondWith404: boolean,
|
|
213
|
+
bubbleError: boolean
|
|
214
|
+
) => {
|
|
215
|
+
if (e instanceof NotFoundError && !respondWith404) {
|
|
216
|
+
return
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const errorResponse = getErrorResponse(e)
|
|
220
|
+
if (errorResponse != null) {
|
|
221
|
+
http?.response?.setStatus(errorResponse.status)
|
|
222
|
+
http?.response?.setJson({
|
|
223
|
+
message: errorResponse.message,
|
|
224
|
+
payload: (e as any).payload,
|
|
225
|
+
traceId: trackerId,
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
if (logWarningsForStatusCodes.includes(errorResponse.status)) {
|
|
229
|
+
logger.warn(`Warning id: ${trackerId}`)
|
|
230
|
+
logger.warn(e)
|
|
231
|
+
}
|
|
232
|
+
} else {
|
|
233
|
+
logger.warn(`Error id: ${trackerId}`)
|
|
234
|
+
logger.error(e)
|
|
235
|
+
http?.response?.setStatus(500)
|
|
236
|
+
http?.response?.setJson({ errorId: trackerId })
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (e instanceof NotFoundError) {
|
|
240
|
+
http?.response?.end()
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (bubbleError) {
|
|
244
|
+
throw e
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* @ignore
|
|
250
|
+
*/
|
|
251
|
+
export const runHTTPRoute = async <In, Out>({
|
|
252
|
+
singletonServices,
|
|
253
|
+
request,
|
|
254
|
+
response,
|
|
255
|
+
createSessionServices,
|
|
256
|
+
route: apiRoute,
|
|
257
|
+
method: apiType,
|
|
258
|
+
skipUserSession = false,
|
|
259
|
+
respondWith404 = true,
|
|
260
|
+
logWarningsForStatusCodes = [],
|
|
261
|
+
coerceToArray = false,
|
|
262
|
+
bubbleErrors = false,
|
|
263
|
+
}: Pick<CoreHTTPFunctionRoute<unknown, unknown, any>, 'route' | 'method'> &
|
|
264
|
+
RunRouteOptions &
|
|
265
|
+
RunRouteParams<In>): Promise<Out | void> => {
|
|
266
|
+
let sessionServices: SessionServices<typeof singletonServices> | undefined
|
|
267
|
+
const trackerId: string = crypto.randomUUID().toString()
|
|
268
|
+
|
|
269
|
+
const http = createHTTPInteraction(request, response)
|
|
270
|
+
|
|
271
|
+
const matchedRoute = getMatchingRoute(
|
|
272
|
+
singletonServices.schemaService,
|
|
273
|
+
apiType,
|
|
274
|
+
apiRoute
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
if (!matchedRoute) {
|
|
279
|
+
singletonServices.logger.info({
|
|
280
|
+
message: 'Route not found',
|
|
281
|
+
apiRoute,
|
|
282
|
+
apiType,
|
|
283
|
+
})
|
|
284
|
+
throw new NotFoundError(`Route not found: ${apiRoute}`)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const { matchedPath, params, route, schemaName } = matchedRoute
|
|
288
|
+
const requiresSession = route.auth !== false
|
|
289
|
+
http?.request?.setParams(params)
|
|
290
|
+
|
|
291
|
+
singletonServices.logger.info(
|
|
292
|
+
`Matched route: ${route.route} | method: ${route.method.toUpperCase()} | auth: ${requiresSession.toString()}`
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
const session = await loadUserSession(
|
|
296
|
+
skipUserSession,
|
|
297
|
+
requiresSession,
|
|
298
|
+
http,
|
|
299
|
+
matchedPath,
|
|
300
|
+
route,
|
|
301
|
+
singletonServices.logger,
|
|
302
|
+
singletonServices.httpSessionService
|
|
303
|
+
)
|
|
304
|
+
const data = await request.getData()
|
|
305
|
+
|
|
306
|
+
validateAndCoerce(
|
|
307
|
+
singletonServices.logger,
|
|
308
|
+
singletonServices.schemaService,
|
|
309
|
+
schemaName,
|
|
310
|
+
data,
|
|
311
|
+
coerceToArray
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
sessionServices = await createSessionServices(
|
|
315
|
+
singletonServices,
|
|
316
|
+
{ http },
|
|
317
|
+
session
|
|
318
|
+
)
|
|
319
|
+
const allServices = { ...singletonServices, ...sessionServices, http }
|
|
320
|
+
|
|
321
|
+
if (singletonServices.enforceHTTPAccess) {
|
|
322
|
+
await singletonServices.enforceHTTPAccess(route, session)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const permissioned = await verifyPermissions(
|
|
326
|
+
route.permissions,
|
|
327
|
+
allServices,
|
|
328
|
+
data,
|
|
329
|
+
session
|
|
330
|
+
)
|
|
331
|
+
if (permissioned === false) {
|
|
332
|
+
throw new ForbiddenError('Permission denied')
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const result: any = (await route.func(
|
|
336
|
+
allServices,
|
|
337
|
+
data,
|
|
338
|
+
session!
|
|
339
|
+
)) as unknown as Out
|
|
340
|
+
|
|
341
|
+
if (route.returnsJSON === false) {
|
|
342
|
+
http?.response?.setResponse(result)
|
|
343
|
+
} else {
|
|
344
|
+
http?.response?.setJson(result)
|
|
345
|
+
}
|
|
346
|
+
http?.response?.setStatus(200)
|
|
347
|
+
http?.response?.end()
|
|
348
|
+
|
|
349
|
+
return result
|
|
350
|
+
} catch (e: any) {
|
|
351
|
+
handleError(
|
|
352
|
+
e,
|
|
353
|
+
http,
|
|
354
|
+
trackerId,
|
|
355
|
+
singletonServices.logger,
|
|
356
|
+
logWarningsForStatusCodes,
|
|
357
|
+
respondWith404,
|
|
358
|
+
bubbleErrors
|
|
359
|
+
)
|
|
360
|
+
} finally {
|
|
361
|
+
if (sessionServices) {
|
|
362
|
+
await closeSessionServices(singletonServices.logger, sessionServices)
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|