@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,189 @@
|
|
|
1
|
+
import { EError } from '../errors/error-handler.js'
|
|
2
|
+
import {
|
|
3
|
+
APIDocs,
|
|
4
|
+
CoreServices,
|
|
5
|
+
CoreSingletonServices,
|
|
6
|
+
CoreUserSession,
|
|
7
|
+
CreateSessionServices,
|
|
8
|
+
} from '../types/core.types.js'
|
|
9
|
+
import {
|
|
10
|
+
CoreAPIFunction,
|
|
11
|
+
CoreAPIFunctionSessionless,
|
|
12
|
+
CoreAPIPermission,
|
|
13
|
+
} from '../types/functions.types.js'
|
|
14
|
+
import { PikkuRequest } from '../pikku-request.js'
|
|
15
|
+
import { PikkuResponse } from '../pikku-response.js'
|
|
16
|
+
import { PikkuHTTPAbstractRequest } from './pikku-http-abstract-request.js'
|
|
17
|
+
import { PikkuHTTPAbstractResponse } from './pikku-http-abstract-response.js'
|
|
18
|
+
|
|
19
|
+
type ExtractRouteParams<S extends string> =
|
|
20
|
+
S extends `${string}:${infer Param}/${infer Rest}`
|
|
21
|
+
? Param | ExtractRouteParams<`/${Rest}`>
|
|
22
|
+
: S extends `${string}:${infer Param}`
|
|
23
|
+
? Param
|
|
24
|
+
: never
|
|
25
|
+
|
|
26
|
+
export type AssertRouteParams<In, Route extends string> =
|
|
27
|
+
ExtractRouteParams<Route> extends keyof In
|
|
28
|
+
? unknown
|
|
29
|
+
: ['Error: Route parameters', ExtractRouteParams<Route>, 'not in', keyof In]
|
|
30
|
+
|
|
31
|
+
export type RunRouteOptions = Partial<{
|
|
32
|
+
skipUserSession: boolean
|
|
33
|
+
respondWith404: boolean
|
|
34
|
+
logWarningsForStatusCodes: number[]
|
|
35
|
+
coerceToArray: boolean
|
|
36
|
+
bubbleErrors: boolean
|
|
37
|
+
}>
|
|
38
|
+
|
|
39
|
+
export type RunRouteParams<In> = {
|
|
40
|
+
singletonServices: CoreSingletonServices
|
|
41
|
+
request: PikkuRequest<In> | PikkuHTTPAbstractRequest<In>
|
|
42
|
+
response?: PikkuResponse | PikkuHTTPAbstractResponse | undefined
|
|
43
|
+
createSessionServices: CreateSessionServices<
|
|
44
|
+
CoreSingletonServices,
|
|
45
|
+
CoreServices<CoreSingletonServices>,
|
|
46
|
+
CoreUserSession
|
|
47
|
+
>
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Represents the HTTP methods supported for API routes.
|
|
52
|
+
*/
|
|
53
|
+
export type HTTPMethod =
|
|
54
|
+
| 'post'
|
|
55
|
+
| 'get'
|
|
56
|
+
| 'delete'
|
|
57
|
+
| 'patch'
|
|
58
|
+
| 'head'
|
|
59
|
+
| 'put'
|
|
60
|
+
| 'options'
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Represents an API route without a function, including metadata such as content type, route, and timeout settings.
|
|
64
|
+
*/
|
|
65
|
+
export type CoreHTTPFunction = {
|
|
66
|
+
contentType?: 'xml' | 'json'
|
|
67
|
+
route: string
|
|
68
|
+
eventChannel?: false
|
|
69
|
+
returnsJSON?: false
|
|
70
|
+
timeout?: number
|
|
71
|
+
docs?: Partial<{
|
|
72
|
+
description: string
|
|
73
|
+
response: string
|
|
74
|
+
errors: Array<typeof EError>
|
|
75
|
+
tags: string[]
|
|
76
|
+
}>
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Represents a http interaction within Pikku, including a request and response.
|
|
80
|
+
*/
|
|
81
|
+
export interface PikkuHTTP {
|
|
82
|
+
request?: PikkuHTTPAbstractRequest
|
|
83
|
+
response?: PikkuHTTPAbstractResponse
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Represents request headers as either a record or a function to get headers by name.
|
|
88
|
+
*/
|
|
89
|
+
export type RequestHeaders =
|
|
90
|
+
| Record<string, string | string[] | undefined>
|
|
91
|
+
| ((headerName: string) => string | string[] | undefined)
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Represents a query object for Pikku, where each key can be a string, a value, or an array of values.
|
|
95
|
+
*/
|
|
96
|
+
export type PikkuQuery<T = Record<string, string | undefined>> = Record<
|
|
97
|
+
string,
|
|
98
|
+
string | T | null | Array<T | null>
|
|
99
|
+
>
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Represents a core API route, which can have different configurations depending on whether it requires authentication and permissions.
|
|
103
|
+
*
|
|
104
|
+
* @template In - The input type.
|
|
105
|
+
* @template Out - The output type.
|
|
106
|
+
* @template R - The route string type.
|
|
107
|
+
* @template APIFunction - The API function type, defaults to `CoreAPIFunction`.
|
|
108
|
+
* @template APIFunctionSessionless - The sessionless API function type, defaults to `CoreAPIFunctionSessionless`.
|
|
109
|
+
* @template APIPermission - The permission function type, defaults to `CoreAPIPermission`.
|
|
110
|
+
*/
|
|
111
|
+
export type CoreHTTPFunctionRoute<
|
|
112
|
+
In,
|
|
113
|
+
Out,
|
|
114
|
+
R extends string,
|
|
115
|
+
APIFunction = CoreAPIFunction<In, Out>,
|
|
116
|
+
APIFunctionSessionless = CoreAPIFunctionSessionless<In, Out>,
|
|
117
|
+
APIPermission = CoreAPIPermission<In>,
|
|
118
|
+
> =
|
|
119
|
+
| (CoreHTTPFunction & {
|
|
120
|
+
route: R
|
|
121
|
+
method: HTTPMethod
|
|
122
|
+
func: APIFunction
|
|
123
|
+
permissions?: Record<string, APIPermission[] | APIPermission>
|
|
124
|
+
auth?: true
|
|
125
|
+
})
|
|
126
|
+
| (CoreHTTPFunction & {
|
|
127
|
+
route: R
|
|
128
|
+
method: HTTPMethod
|
|
129
|
+
func: APIFunctionSessionless
|
|
130
|
+
permissions?: undefined
|
|
131
|
+
auth?: false
|
|
132
|
+
})
|
|
133
|
+
| (CoreHTTPFunction & {
|
|
134
|
+
route: R
|
|
135
|
+
method: 'post'
|
|
136
|
+
func: APIFunction
|
|
137
|
+
permissions?: Record<string, APIPermission[] | APIPermission>
|
|
138
|
+
auth?: true
|
|
139
|
+
query?: Array<keyof In>
|
|
140
|
+
})
|
|
141
|
+
| (CoreHTTPFunction & {
|
|
142
|
+
route: R
|
|
143
|
+
method: 'post'
|
|
144
|
+
func: APIFunctionSessionless
|
|
145
|
+
permissions?: undefined
|
|
146
|
+
auth?: false
|
|
147
|
+
query?: Array<keyof In>
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Represents an array of core API routes.
|
|
152
|
+
*/
|
|
153
|
+
export type CoreHTTPFunctionRoutes = Array<
|
|
154
|
+
CoreHTTPFunctionRoute<any, any, string>
|
|
155
|
+
>
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Represents the input types for route metadata, including parameters, query, and body types.
|
|
159
|
+
*/
|
|
160
|
+
export type HTTPFunctionMetaInputTypes = {
|
|
161
|
+
params?: string
|
|
162
|
+
query?: string
|
|
163
|
+
body?: string
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Represents metadata for a set of routes, including route details, methods, input/output types, and documentation.
|
|
168
|
+
*/
|
|
169
|
+
export type HTTPRoutesMeta = Array<{
|
|
170
|
+
route: string
|
|
171
|
+
method: HTTPMethod
|
|
172
|
+
params?: string[]
|
|
173
|
+
query?: string[]
|
|
174
|
+
input: string | null
|
|
175
|
+
output: string | null
|
|
176
|
+
inputTypes?: HTTPFunctionMetaInputTypes
|
|
177
|
+
docs?: APIDocs
|
|
178
|
+
}>
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Verifies access to a route.
|
|
182
|
+
* @param route - The route to verify access for.
|
|
183
|
+
* @param session - The user session.
|
|
184
|
+
* @returns A promise that resolves if access is granted.
|
|
185
|
+
*/
|
|
186
|
+
export type enforceHTTPAccess = (
|
|
187
|
+
route: CoreHTTPFunctionRoute<unknown, unknown, any>,
|
|
188
|
+
session?: CoreUserSession
|
|
189
|
+
) => Promise<void> | void
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { CoreUserSession } from '../types/core.types.js'
|
|
2
|
+
import { PikkuHTTPAbstractRequest } from './pikku-http-abstract-request.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Interface for handling user sessions.
|
|
6
|
+
* @template UserSession - The type of the user session.
|
|
7
|
+
*/
|
|
8
|
+
export interface HTTPSessionService<UserSession = CoreUserSession> {
|
|
9
|
+
/**
|
|
10
|
+
* Retrieves the user session.
|
|
11
|
+
* @param credentialsRequired - A flag indicating whether credentials are required.
|
|
12
|
+
* @param pikkuRequest - The request object.
|
|
13
|
+
* @returns A promise that resolves to the user session or undefined.
|
|
14
|
+
*/
|
|
15
|
+
getUserSession: (
|
|
16
|
+
credentialsRequired: boolean,
|
|
17
|
+
pikkuRequest: PikkuHTTPAbstractRequest
|
|
18
|
+
) => Promise<UserSession | undefined>
|
|
19
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export * from './pikku-http-session-service.js'
|
|
2
|
+
|
|
3
|
+
export * from './pikku-http-abstract-request.js'
|
|
4
|
+
|
|
5
|
+
export * from './pikku-http-abstract-response.js'
|
|
6
|
+
|
|
7
|
+
export * from './log-http-routes.js'
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
runHTTPRoute,
|
|
11
|
+
getRoutes,
|
|
12
|
+
setHTTPRoutesMeta,
|
|
13
|
+
addRoute,
|
|
14
|
+
} from './http-route-runner.js'
|
|
15
|
+
|
|
16
|
+
export type * from './http-routes.types.js'
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Logger } from '../services/index.js'
|
|
2
|
+
import { getRoutes } from './http-route-runner.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Logs all the loaded routes.
|
|
6
|
+
* @param logger - A logger for logging information.
|
|
7
|
+
*/
|
|
8
|
+
export const logRoutes = (logger: Logger) => {
|
|
9
|
+
const { routes } = getRoutes()
|
|
10
|
+
if (routes.length === 0) {
|
|
11
|
+
logger.info('No routes added')
|
|
12
|
+
return
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let routesMessage = 'Routes loaded:'
|
|
16
|
+
for (const { method, route } of routes) {
|
|
17
|
+
routesMessage += `\n\t- ${method.toUpperCase()} -> ${route}`
|
|
18
|
+
}
|
|
19
|
+
logger.info(routesMessage)
|
|
20
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { parse as parseCookie } from 'cookie'
|
|
2
|
+
import { PikkuRequest } from '../pikku-request.js'
|
|
3
|
+
import { PikkuQuery } from './http-routes.types.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Abstract class representing a pikku request.
|
|
7
|
+
* @template In - The type of the request body.
|
|
8
|
+
* @group RequestResponse
|
|
9
|
+
*/
|
|
10
|
+
export abstract class PikkuHTTPAbstractRequest<
|
|
11
|
+
In = unknown,
|
|
12
|
+
> extends PikkuRequest<In> {
|
|
13
|
+
private params: Partial<Record<string, string | string[]>> = {}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Retrieves the request body.
|
|
17
|
+
* @returns A promise that resolves to the request body.
|
|
18
|
+
*/
|
|
19
|
+
public abstract getBody(): Promise<In>
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Retrieves the raw request body as a Buffer.
|
|
23
|
+
* @returns A promise that resolves to the raw request body.
|
|
24
|
+
*/
|
|
25
|
+
public getRawBody(): Promise<Buffer> {
|
|
26
|
+
throw new Error('Method not implemented.')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Retrieves the value of a specific header.
|
|
31
|
+
* @param headerName - The name of the header to retrieve.
|
|
32
|
+
* @returns The value of the header, or undefined if the header is not found.
|
|
33
|
+
*/
|
|
34
|
+
public abstract getHeader(headerName: string): string | undefined
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Retrieves the cookies from the request.
|
|
38
|
+
* @returns An object containing the cookies.
|
|
39
|
+
*/
|
|
40
|
+
public getCookies(): Partial<Record<string, string>> {
|
|
41
|
+
const cookieHeader = this.getHeader('cookie')
|
|
42
|
+
if (cookieHeader) {
|
|
43
|
+
return parseCookie(cookieHeader)
|
|
44
|
+
}
|
|
45
|
+
return {}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Retrieves the request parameters.
|
|
50
|
+
* @returns An object containing the request parameters.
|
|
51
|
+
*/
|
|
52
|
+
public getParams(): Partial<Record<string, string | string[]>> {
|
|
53
|
+
return this.params
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Sets the request parameters.
|
|
58
|
+
* @param params - An object containing the request parameters to set.
|
|
59
|
+
*/
|
|
60
|
+
public setParams(
|
|
61
|
+
params: Record<string, string | string[] | undefined>
|
|
62
|
+
): void {
|
|
63
|
+
this.params = params
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Retrieves the query parameters from the request.
|
|
68
|
+
* @returns An object containing the query parameters.
|
|
69
|
+
*/
|
|
70
|
+
public getQuery(): PikkuQuery {
|
|
71
|
+
return {}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Retrieves the IP address of the client making the request.
|
|
76
|
+
* @returns The IP address of the client.
|
|
77
|
+
* @throws {Error} This method is not implemented and should be overridden by subclasses.
|
|
78
|
+
*/
|
|
79
|
+
public getIP(): string {
|
|
80
|
+
throw new Error('Method not implemented.')
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Retrieves the combined data from the request, including parameters, query, and body.
|
|
85
|
+
* @returns A promise that resolves to an object containing the combined data.
|
|
86
|
+
*/
|
|
87
|
+
public async getData(): Promise<In> {
|
|
88
|
+
return {
|
|
89
|
+
...this.getParams(),
|
|
90
|
+
...this.getQuery(),
|
|
91
|
+
// TODO: If body isn't an object, we should insert it as the word...
|
|
92
|
+
...(await this.getBody()),
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { SerializeOptions, serialize } from 'cookie'
|
|
2
|
+
import { JSONValue } from '../types/core.types.js'
|
|
3
|
+
import { PikkuResponse } from '../pikku-response.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Abstract class representing a pikku response.
|
|
7
|
+
* @group RequestResponse
|
|
8
|
+
*/
|
|
9
|
+
export abstract class PikkuHTTPAbstractResponse extends PikkuResponse {
|
|
10
|
+
/**
|
|
11
|
+
* Sets the HTTP status code for the response.
|
|
12
|
+
* @param status - The HTTP status code to set.
|
|
13
|
+
*/
|
|
14
|
+
public abstract setStatus(status: number): void
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Sets the JSON body for the response.
|
|
18
|
+
* @param body - The JSON body to set.
|
|
19
|
+
*/
|
|
20
|
+
public abstract setJson(body: JSONValue): void
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Sets the response content.
|
|
24
|
+
* @param response - The response content, which can be a string or a Buffer.
|
|
25
|
+
*/
|
|
26
|
+
public abstract setResponse(response: string | Buffer): void
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Sets a header for the response.
|
|
30
|
+
* @param name - The name of the header.
|
|
31
|
+
* @param value - The value of the header, which can be a string, boolean, or an array of strings.
|
|
32
|
+
*/
|
|
33
|
+
public setHeader(name: string, value: string | boolean | string[]) {
|
|
34
|
+
throw new Error('Method not implemented.')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Sets multiple headers for the response.
|
|
39
|
+
* @param headers - An object containing header names and values.
|
|
40
|
+
*/
|
|
41
|
+
public setHeaders(headers: Record<string, string>) {
|
|
42
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
43
|
+
this.setHeader(name, value)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Sets a cookie for the response.
|
|
49
|
+
* @param name - The name of the cookie.
|
|
50
|
+
* @param value - The value of the cookie.
|
|
51
|
+
* @param options - Options for cookie serialization.
|
|
52
|
+
*/
|
|
53
|
+
public setCookie(name: string, value: string, options: SerializeOptions) {
|
|
54
|
+
this.setHeader('set-cookie', serialize(name, value, options))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Clears a cookie from the response.
|
|
59
|
+
* @param name - The name of the cookie to clear.
|
|
60
|
+
*/
|
|
61
|
+
public clearCookie(name: string): void {
|
|
62
|
+
this.setHeader('set-cookie', serialize(name, '', { expires: new Date(0) }))
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Sets a redirect for the response.
|
|
67
|
+
* @param path - The path to redirect to.
|
|
68
|
+
* @param status - The HTTP status code for the redirect.
|
|
69
|
+
*/
|
|
70
|
+
public setRedirect(path: string, status: number) {
|
|
71
|
+
throw new Error('Method not implemented.')
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Informs the response that it has ended, useful for when setting
|
|
76
|
+
* a status without a body or response.
|
|
77
|
+
*/
|
|
78
|
+
public end() {}
|
|
79
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { test } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { InvalidSessionError, MissingSessionError } from '../errors/errors.js'
|
|
4
|
+
import { PikkuHTTPSessionService } from './pikku-http-session-service.js'
|
|
5
|
+
|
|
6
|
+
test('PikkuHTTPSessionService: Handles JWT-based sessions', async () => {
|
|
7
|
+
const mockJWTService: any = {
|
|
8
|
+
decode: async (token) => {
|
|
9
|
+
if (token === 'validToken') return { id: 1, name: 'Test User' }
|
|
10
|
+
throw new InvalidSessionError()
|
|
11
|
+
},
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const sessionService = new PikkuHTTPSessionService(mockJWTService, {})
|
|
15
|
+
const mockRequest: any = {
|
|
16
|
+
getHeader: (name) =>
|
|
17
|
+
name === 'authorization' ? 'Bearer validToken' : undefined,
|
|
18
|
+
getCookies: () => ({}),
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const session = await sessionService.getUserSession(true, mockRequest)
|
|
22
|
+
assert.deepEqual(session, { id: 1, name: 'Test User' })
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test('PikkuHTTPSessionService: Handles API key-based sessions', async () => {
|
|
26
|
+
const mockJWTService: any = { decode: async () => undefined }
|
|
27
|
+
const mockOptions = {
|
|
28
|
+
getSessionForAPIKey: async (apiKey) => {
|
|
29
|
+
return apiKey === 'validAPIKey' ? { id: 2, name: 'API User' } : undefined
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const sessionService = new PikkuHTTPSessionService(
|
|
34
|
+
mockJWTService,
|
|
35
|
+
mockOptions
|
|
36
|
+
)
|
|
37
|
+
const mockRequest: any = {
|
|
38
|
+
getHeader: (name) => (name === 'x-api-key' ? 'validAPIKey' : undefined),
|
|
39
|
+
getCookies: () => ({}),
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const session = await sessionService.getUserSession(true, mockRequest)
|
|
43
|
+
assert.deepEqual(session, { id: 2, name: 'API User' })
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('PikkuHTTPSessionService: Handles cookie-based sessions', async () => {
|
|
47
|
+
const mockJWTService: any = { decode: async () => undefined }
|
|
48
|
+
const mockOptions = {
|
|
49
|
+
cookieNames: ['session'],
|
|
50
|
+
getSessionForCookieValue: async (cookieValue, cookieName) => {
|
|
51
|
+
if (cookieValue === 'validCookie' && cookieName === 'session') {
|
|
52
|
+
return { id: 3, name: 'Cookie User' }
|
|
53
|
+
}
|
|
54
|
+
return undefined
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const sessionService = new PikkuHTTPSessionService(
|
|
59
|
+
mockJWTService,
|
|
60
|
+
mockOptions
|
|
61
|
+
)
|
|
62
|
+
const mockRequest: any = {
|
|
63
|
+
getHeader: () => undefined,
|
|
64
|
+
getCookies: () => ({ session: 'validCookie' }),
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const session = await sessionService.getUserSession(true, mockRequest)
|
|
68
|
+
assert.deepEqual(session, { id: 3, name: 'Cookie User' })
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test('PikkuHTTPSessionService: Throws MissingSessionError when credentials are required but missing', async () => {
|
|
72
|
+
const mockJWTService: any = { decode: async () => undefined }
|
|
73
|
+
const sessionService = new PikkuHTTPSessionService(mockJWTService, {})
|
|
74
|
+
|
|
75
|
+
const mockRequest: any = {
|
|
76
|
+
getHeader: () => undefined,
|
|
77
|
+
getCookies: () => ({}),
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
await assert.rejects(
|
|
81
|
+
sessionService.getUserSession(true, mockRequest),
|
|
82
|
+
MissingSessionError
|
|
83
|
+
)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
test('PikkuHTTPSessionService: Transforms session when transformSession is provided', async () => {
|
|
87
|
+
const mockJWTService: any = {
|
|
88
|
+
decode: async () => ({ id: 1, name: 'Original User' }),
|
|
89
|
+
}
|
|
90
|
+
const mockOptions = {
|
|
91
|
+
transformSession: async (session) => ({ ...session, transformed: true }),
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const sessionService = new PikkuHTTPSessionService(
|
|
95
|
+
mockJWTService,
|
|
96
|
+
mockOptions
|
|
97
|
+
)
|
|
98
|
+
const mockRequest: any = {
|
|
99
|
+
getHeader: (name) =>
|
|
100
|
+
name === 'authorization' ? 'Bearer validToken' : undefined,
|
|
101
|
+
getCookies: () => ({}),
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const session = await sessionService.getUserSession(true, mockRequest)
|
|
105
|
+
assert.deepEqual(session, { id: 1, name: 'Original User', transformed: true })
|
|
106
|
+
})
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { HTTPSessionService } from './http-session-service.js'
|
|
2
|
+
import { JWTService } from '../services/jwt-service.js'
|
|
3
|
+
import { InvalidSessionError, MissingSessionError } from '../errors/errors.js'
|
|
4
|
+
import { PikkuHTTPAbstractRequest } from './pikku-http-abstract-request.js'
|
|
5
|
+
import { PikkuQuery } from './http-routes.types.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The `PikkuHTTPSessionService` class provides session management capabilities, including handling JWT-based sessions,
|
|
9
|
+
* cookie-based sessions, and API key-based sessions. It allows for retrieving and transforming user sessions based on different
|
|
10
|
+
* authentication mechanisms.
|
|
11
|
+
*
|
|
12
|
+
* @template UserSession - The type representing a user session.
|
|
13
|
+
*/
|
|
14
|
+
export class PikkuHTTPSessionService<UserSession>
|
|
15
|
+
implements HTTPSessionService<UserSession>
|
|
16
|
+
{
|
|
17
|
+
/**
|
|
18
|
+
* Constructs a new instance of the `PikkuHTTPSessionService` class.
|
|
19
|
+
*
|
|
20
|
+
* @param jwtService - The service for handling JWT operations.
|
|
21
|
+
* @param options - Options for configuring the session service.
|
|
22
|
+
*/
|
|
23
|
+
constructor(
|
|
24
|
+
private jwtService: JWTService<UserSession>,
|
|
25
|
+
private options: {
|
|
26
|
+
cookieNames?: string[]
|
|
27
|
+
getSessionForCookieValue?: (
|
|
28
|
+
cookieValue: string,
|
|
29
|
+
cookieName: string
|
|
30
|
+
) => Promise<UserSession>
|
|
31
|
+
getSessionForQueryValue?: (
|
|
32
|
+
queryValue: PikkuQuery
|
|
33
|
+
) => Promise<UserSession> | undefined
|
|
34
|
+
getSessionForAPIKey?: (apiKey: string) => Promise<UserSession> | undefined
|
|
35
|
+
transformSession?: (session: any) => Promise<UserSession>
|
|
36
|
+
}
|
|
37
|
+
) {}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Retrieves a session from cookies if available.
|
|
41
|
+
*
|
|
42
|
+
* @param request - The request object containing cookies.
|
|
43
|
+
* @returns A promise that resolves to the user session, or `undefined` if no session is found.
|
|
44
|
+
*/
|
|
45
|
+
private async getCookieSession(
|
|
46
|
+
request: PikkuHTTPAbstractRequest
|
|
47
|
+
): Promise<UserSession | undefined> {
|
|
48
|
+
const cookies = request.getCookies()
|
|
49
|
+
if (!cookies) {
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let cookieName: string | undefined
|
|
54
|
+
if (this.options.cookieNames) {
|
|
55
|
+
for (const name of this.options.cookieNames) {
|
|
56
|
+
if (cookies[name]) {
|
|
57
|
+
cookieName = name
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!cookieName) {
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const cookieValue = cookies[cookieName]
|
|
67
|
+
if (!cookieValue) {
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!this.options.getSessionForCookieValue) {
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return await this.options.getSessionForCookieValue(cookieValue, cookieName)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Retrieves the user session based on available credentials (JWT, API key, or cookies).
|
|
80
|
+
*
|
|
81
|
+
* @param credentialsRequired - Whether credentials are required to proceed.
|
|
82
|
+
* @param request - The request object containing headers and cookies.
|
|
83
|
+
* @param debugJWTDecode - Whether to enable debugging for JWT decoding.
|
|
84
|
+
* @returns A promise that resolves to the user session, or `undefined` if no session is found and credentials are not required.
|
|
85
|
+
* @throws {MissingSessionError} - Throws an error if credentials are required but no session is found.
|
|
86
|
+
*/
|
|
87
|
+
public async getUserSession(
|
|
88
|
+
credentialsRequired: boolean,
|
|
89
|
+
request: PikkuHTTPAbstractRequest,
|
|
90
|
+
debugJWTDecode?: boolean
|
|
91
|
+
): Promise<UserSession | undefined> {
|
|
92
|
+
let userSession: UserSession | undefined
|
|
93
|
+
|
|
94
|
+
const authorization =
|
|
95
|
+
request.getHeader('authorization') || request.getHeader('Authorization')
|
|
96
|
+
if (authorization) {
|
|
97
|
+
const [part1, part2] = authorization.split(' ')
|
|
98
|
+
if (part1 !== 'Bearer' || !part2) {
|
|
99
|
+
throw new InvalidSessionError()
|
|
100
|
+
}
|
|
101
|
+
userSession = await this.jwtService.decode(
|
|
102
|
+
part2,
|
|
103
|
+
undefined,
|
|
104
|
+
debugJWTDecode
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!userSession && this.options.getSessionForAPIKey) {
|
|
109
|
+
const apiKey = request.getHeader('x-api-key')
|
|
110
|
+
if (apiKey) {
|
|
111
|
+
userSession = await this.options.getSessionForAPIKey(apiKey)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!userSession && this.options.getSessionForCookieValue) {
|
|
116
|
+
userSession = await this.getCookieSession(request)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!userSession && this.options.getSessionForQueryValue) {
|
|
120
|
+
userSession = await this.options.getSessionForQueryValue(
|
|
121
|
+
request.getQuery()
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!userSession && credentialsRequired) {
|
|
126
|
+
throw new MissingSessionError()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (userSession && this.options.transformSession) {
|
|
130
|
+
return await this.options.transformSession(userSession)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return userSession
|
|
134
|
+
}
|
|
135
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @pikku/core
|
|
3
|
+
*/
|
|
4
|
+
export * from './types/core.types.js'
|
|
5
|
+
export * from './types/functions.types.js'
|
|
6
|
+
export * from './pikku-request.js'
|
|
7
|
+
export * from './pikku-response.js'
|
|
8
|
+
export * from './services/index.js'
|
|
9
|
+
export * from './http/index.js'
|
|
10
|
+
export * from './channel/index.js'
|
|
11
|
+
export * from './scheduler/index.js'
|
|
12
|
+
|
|
13
|
+
export { addRoute } from './http/http-route-runner.js'
|
|
14
|
+
export { addChannel } from './channel/channel-runner.js'
|
|
15
|
+
export { addScheduledTask } from './scheduler/scheduler-runner.js'
|