@furystack/rest-service 4.0.19

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.
Files changed (285) hide show
  1. package/LICENSE +339 -0
  2. package/README.md +219 -0
  3. package/dist/actions/error-action.d.ts +14 -0
  4. package/dist/actions/error-action.d.ts.map +1 -0
  5. package/dist/actions/error-action.js +29 -0
  6. package/dist/actions/error-action.js.map +1 -0
  7. package/dist/actions/error-action.spec.d.ts +2 -0
  8. package/dist/actions/error-action.spec.d.ts.map +1 -0
  9. package/dist/actions/error-action.spec.js +51 -0
  10. package/dist/actions/error-action.spec.js.map +1 -0
  11. package/dist/actions/get-current-user.d.ts +11 -0
  12. package/dist/actions/get-current-user.d.ts.map +1 -0
  13. package/dist/actions/get-current-user.js +15 -0
  14. package/dist/actions/get-current-user.js.map +1 -0
  15. package/dist/actions/get-current-user.spec.d.ts +2 -0
  16. package/dist/actions/get-current-user.spec.d.ts.map +1 -0
  17. package/dist/actions/get-current-user.spec.js +20 -0
  18. package/dist/actions/get-current-user.spec.js.map +1 -0
  19. package/dist/actions/index.d.ts +7 -0
  20. package/dist/actions/index.d.ts.map +1 -0
  21. package/dist/actions/index.js +10 -0
  22. package/dist/actions/index.js.map +1 -0
  23. package/dist/actions/is-authenticated.d.ts +14 -0
  24. package/dist/actions/is-authenticated.d.ts.map +1 -0
  25. package/dist/actions/is-authenticated.js +17 -0
  26. package/dist/actions/is-authenticated.js.map +1 -0
  27. package/dist/actions/is-authenticated.spec.d.ts +2 -0
  28. package/dist/actions/is-authenticated.spec.d.ts.map +1 -0
  29. package/dist/actions/is-authenticated.spec.js +19 -0
  30. package/dist/actions/is-authenticated.spec.js.map +1 -0
  31. package/dist/actions/login-action.spec.d.ts +2 -0
  32. package/dist/actions/login-action.spec.d.ts.map +1 -0
  33. package/dist/actions/login-action.spec.js +35 -0
  34. package/dist/actions/login-action.spec.js.map +1 -0
  35. package/dist/actions/login.d.ts +16 -0
  36. package/dist/actions/login.d.ts.map +1 -0
  37. package/dist/actions/login.js +26 -0
  38. package/dist/actions/login.js.map +1 -0
  39. package/dist/actions/logout-action.spec.d.ts +2 -0
  40. package/dist/actions/logout-action.spec.d.ts.map +1 -0
  41. package/dist/actions/logout-action.spec.js +23 -0
  42. package/dist/actions/logout-action.spec.js.map +1 -0
  43. package/dist/actions/logout.d.ts +14 -0
  44. package/dist/actions/logout.d.ts.map +1 -0
  45. package/dist/actions/logout.js +20 -0
  46. package/dist/actions/logout.js.map +1 -0
  47. package/dist/actions/not-found-action.d.ts +10 -0
  48. package/dist/actions/not-found-action.d.ts.map +1 -0
  49. package/dist/actions/not-found-action.js +14 -0
  50. package/dist/actions/not-found-action.js.map +1 -0
  51. package/dist/actions/not-found-action.spec.d.ts +2 -0
  52. package/dist/actions/not-found-action.spec.d.ts.map +1 -0
  53. package/dist/actions/not-found-action.spec.js +17 -0
  54. package/dist/actions/not-found-action.spec.js.map +1 -0
  55. package/dist/add-cors-header.spec.d.ts +2 -0
  56. package/dist/add-cors-header.spec.d.ts.map +1 -0
  57. package/dist/add-cors-header.spec.js +99 -0
  58. package/dist/add-cors-header.spec.js.map +1 -0
  59. package/dist/api-manager.d.ts +61 -0
  60. package/dist/api-manager.d.ts.map +1 -0
  61. package/dist/api-manager.js +144 -0
  62. package/dist/api-manager.js.map +1 -0
  63. package/dist/authenticate.d.ts +5 -0
  64. package/dist/authenticate.d.ts.map +1 -0
  65. package/dist/authenticate.js +20 -0
  66. package/dist/authenticate.js.map +1 -0
  67. package/dist/authenticate.spec.d.ts +2 -0
  68. package/dist/authenticate.spec.d.ts.map +1 -0
  69. package/dist/authenticate.spec.js +59 -0
  70. package/dist/authenticate.spec.js.map +1 -0
  71. package/dist/authorize.d.ts +5 -0
  72. package/dist/authorize.d.ts.map +1 -0
  73. package/dist/authorize.js +22 -0
  74. package/dist/authorize.js.map +1 -0
  75. package/dist/authorize.spec.d.ts +2 -0
  76. package/dist/authorize.spec.d.ts.map +1 -0
  77. package/dist/authorize.spec.js +55 -0
  78. package/dist/authorize.spec.js.map +1 -0
  79. package/dist/endpoint-generators/create-delete-endpoint.d.ts +17 -0
  80. package/dist/endpoint-generators/create-delete-endpoint.d.ts.map +1 -0
  81. package/dist/endpoint-generators/create-delete-endpoint.js +24 -0
  82. package/dist/endpoint-generators/create-delete-endpoint.js.map +1 -0
  83. package/dist/endpoint-generators/create-delete-endpoint.spec.d.ts +2 -0
  84. package/dist/endpoint-generators/create-delete-endpoint.spec.d.ts.map +1 -0
  85. package/dist/endpoint-generators/create-delete-endpoint.spec.js +33 -0
  86. package/dist/endpoint-generators/create-delete-endpoint.spec.js.map +1 -0
  87. package/dist/endpoint-generators/create-get-collection-endpoint.d.ts +17 -0
  88. package/dist/endpoint-generators/create-get-collection-endpoint.d.ts.map +1 -0
  89. package/dist/endpoint-generators/create-get-collection-endpoint.js +26 -0
  90. package/dist/endpoint-generators/create-get-collection-endpoint.js.map +1 -0
  91. package/dist/endpoint-generators/create-get-collection-endpoint.spec.d.ts +2 -0
  92. package/dist/endpoint-generators/create-get-collection-endpoint.spec.d.ts.map +1 -0
  93. package/dist/endpoint-generators/create-get-collection-endpoint.spec.js +143 -0
  94. package/dist/endpoint-generators/create-get-collection-endpoint.spec.js.map +1 -0
  95. package/dist/endpoint-generators/create-get-entity-endpoint.d.ts +17 -0
  96. package/dist/endpoint-generators/create-get-entity-endpoint.d.ts.map +1 -0
  97. package/dist/endpoint-generators/create-get-entity-endpoint.js +29 -0
  98. package/dist/endpoint-generators/create-get-entity-endpoint.js.map +1 -0
  99. package/dist/endpoint-generators/create-get-entity-endpoint.spec.d.ts +2 -0
  100. package/dist/endpoint-generators/create-get-entity-endpoint.spec.d.ts.map +1 -0
  101. package/dist/endpoint-generators/create-get-entity-endpoint.spec.js +74 -0
  102. package/dist/endpoint-generators/create-get-entity-endpoint.spec.js.map +1 -0
  103. package/dist/endpoint-generators/create-patch-endpoint.d.ts +18 -0
  104. package/dist/endpoint-generators/create-patch-endpoint.d.ts.map +1 -0
  105. package/dist/endpoint-generators/create-patch-endpoint.js +26 -0
  106. package/dist/endpoint-generators/create-patch-endpoint.js.map +1 -0
  107. package/dist/endpoint-generators/create-patch-endpoint.spec.d.ts +2 -0
  108. package/dist/endpoint-generators/create-patch-endpoint.spec.d.ts.map +1 -0
  109. package/dist/endpoint-generators/create-patch-endpoint.spec.js +36 -0
  110. package/dist/endpoint-generators/create-patch-endpoint.spec.js.map +1 -0
  111. package/dist/endpoint-generators/create-post-endpoint.d.ts +18 -0
  112. package/dist/endpoint-generators/create-post-endpoint.d.ts.map +1 -0
  113. package/dist/endpoint-generators/create-post-endpoint.js +29 -0
  114. package/dist/endpoint-generators/create-post-endpoint.js.map +1 -0
  115. package/dist/endpoint-generators/create-post-endpoint.spec.d.ts +2 -0
  116. package/dist/endpoint-generators/create-post-endpoint.spec.d.ts.map +1 -0
  117. package/dist/endpoint-generators/create-post-endpoint.spec.js +34 -0
  118. package/dist/endpoint-generators/create-post-endpoint.spec.js.map +1 -0
  119. package/dist/endpoint-generators/index.d.ts +6 -0
  120. package/dist/endpoint-generators/index.d.ts.map +1 -0
  121. package/dist/endpoint-generators/index.js +9 -0
  122. package/dist/endpoint-generators/index.js.map +1 -0
  123. package/dist/endpoint-generators/utils.d.ts +9 -0
  124. package/dist/endpoint-generators/utils.d.ts.map +1 -0
  125. package/dist/endpoint-generators/utils.js +27 -0
  126. package/dist/endpoint-generators/utils.js.map +1 -0
  127. package/dist/http-authentication-settings.d.ts +17 -0
  128. package/dist/http-authentication-settings.d.ts.map +1 -0
  129. package/dist/http-authentication-settings.js +26 -0
  130. package/dist/http-authentication-settings.js.map +1 -0
  131. package/dist/http-user-context.d.ts +54 -0
  132. package/dist/http-user-context.d.ts.map +1 -0
  133. package/dist/http-user-context.js +153 -0
  134. package/dist/http-user-context.js.map +1 -0
  135. package/dist/http-user-context.spec.d.ts +4 -0
  136. package/dist/http-user-context.spec.d.ts.map +1 -0
  137. package/dist/http-user-context.spec.js +267 -0
  138. package/dist/http-user-context.spec.js.map +1 -0
  139. package/dist/incoming-message-extensions.d.ts +8 -0
  140. package/dist/incoming-message-extensions.d.ts.map +1 -0
  141. package/dist/incoming-message-extensions.js +14 -0
  142. package/dist/incoming-message-extensions.js.map +1 -0
  143. package/dist/incoming-message-extensions.spec.d.ts +2 -0
  144. package/dist/incoming-message-extensions.spec.d.ts.map +1 -0
  145. package/dist/incoming-message-extensions.spec.js +39 -0
  146. package/dist/incoming-message-extensions.spec.js.map +1 -0
  147. package/dist/index.d.ts +17 -0
  148. package/dist/index.d.ts.map +1 -0
  149. package/dist/index.js +20 -0
  150. package/dist/index.js.map +1 -0
  151. package/dist/injector-extensions.d.ts +21 -0
  152. package/dist/injector-extensions.d.ts.map +1 -0
  153. package/dist/injector-extensions.js +14 -0
  154. package/dist/injector-extensions.js.map +1 -0
  155. package/dist/injector-extensions.spec.d.ts +2 -0
  156. package/dist/injector-extensions.spec.d.ts.map +1 -0
  157. package/dist/injector-extensions.spec.js +19 -0
  158. package/dist/injector-extensions.spec.js.map +1 -0
  159. package/dist/models/cors-options.d.ts +22 -0
  160. package/dist/models/cors-options.d.ts.map +1 -0
  161. package/dist/models/cors-options.js +3 -0
  162. package/dist/models/cors-options.js.map +1 -0
  163. package/dist/models/default-session.d.ts +14 -0
  164. package/dist/models/default-session.d.ts.map +1 -0
  165. package/dist/models/default-session.js +10 -0
  166. package/dist/models/default-session.js.map +1 -0
  167. package/dist/request-action-implementation.d.ts +54 -0
  168. package/dist/request-action-implementation.d.ts.map +1 -0
  169. package/dist/request-action-implementation.js +42 -0
  170. package/dist/request-action-implementation.js.map +1 -0
  171. package/dist/rest-service.integration.spec.d.ts +2 -0
  172. package/dist/rest-service.integration.spec.d.ts.map +1 -0
  173. package/dist/rest-service.integration.spec.js +129 -0
  174. package/dist/rest-service.integration.spec.js.map +1 -0
  175. package/dist/rest.integration.test.d.ts +58 -0
  176. package/dist/rest.integration.test.d.ts.map +1 -0
  177. package/dist/rest.integration.test.js +94 -0
  178. package/dist/rest.integration.test.js.map +1 -0
  179. package/dist/schema-validator/index.d.ts +3 -0
  180. package/dist/schema-validator/index.d.ts.map +1 -0
  181. package/dist/schema-validator/index.js +6 -0
  182. package/dist/schema-validator/index.js.map +1 -0
  183. package/dist/schema-validator/schema-validation-error.d.ts +10 -0
  184. package/dist/schema-validator/schema-validation-error.d.ts.map +1 -0
  185. package/dist/schema-validator/schema-validation-error.js +15 -0
  186. package/dist/schema-validator/schema-validation-error.js.map +1 -0
  187. package/dist/schema-validator/schema-validator.d.ts +20 -0
  188. package/dist/schema-validator/schema-validator.d.ts.map +1 -0
  189. package/dist/schema-validator/schema-validator.js +36 -0
  190. package/dist/schema-validator/schema-validator.js.map +1 -0
  191. package/dist/schema-validator/schema-validator.test.d.ts +2 -0
  192. package/dist/schema-validator/schema-validator.test.d.ts.map +1 -0
  193. package/dist/schema-validator/schema-validator.test.js +62 -0
  194. package/dist/schema-validator/schema-validator.test.js.map +1 -0
  195. package/dist/schema-validator/validate-examples.d.ts +37 -0
  196. package/dist/schema-validator/validate-examples.d.ts.map +1 -0
  197. package/dist/schema-validator/validate-examples.js +29 -0
  198. package/dist/schema-validator/validate-examples.js.map +1 -0
  199. package/dist/server-manager.d.ts +30 -0
  200. package/dist/server-manager.d.ts.map +1 -0
  201. package/dist/server-manager.js +71 -0
  202. package/dist/server-manager.js.map +1 -0
  203. package/dist/server-response-extensions.d.ts +21 -0
  204. package/dist/server-response-extensions.d.ts.map +1 -0
  205. package/dist/server-response-extensions.js +15 -0
  206. package/dist/server-response-extensions.js.map +1 -0
  207. package/dist/server-response-extensions.spec.d.ts +2 -0
  208. package/dist/server-response-extensions.spec.d.ts.map +1 -0
  209. package/dist/server-response-extensions.spec.js +49 -0
  210. package/dist/server-response-extensions.spec.js.map +1 -0
  211. package/dist/utils.d.ts +24 -0
  212. package/dist/utils.d.ts.map +1 -0
  213. package/dist/utils.js +66 -0
  214. package/dist/utils.js.map +1 -0
  215. package/dist/validate.d.ts +18 -0
  216. package/dist/validate.d.ts.map +1 -0
  217. package/dist/validate.integration.schema.d.ts +69 -0
  218. package/dist/validate.integration.schema.d.ts.map +1 -0
  219. package/dist/validate.integration.schema.js +3 -0
  220. package/dist/validate.integration.schema.js.map +1 -0
  221. package/dist/validate.integration.spec.d.ts +13 -0
  222. package/dist/validate.integration.spec.d.ts.map +1 -0
  223. package/dist/validate.integration.spec.js +223 -0
  224. package/dist/validate.integration.spec.js.map +1 -0
  225. package/dist/validate.integration.spec.schema.json +749 -0
  226. package/dist/validate.js +49 -0
  227. package/dist/validate.js.map +1 -0
  228. package/package.json +56 -0
  229. package/src/actions/error-action.spec.ts +54 -0
  230. package/src/actions/error-action.ts +34 -0
  231. package/src/actions/get-current-user.spec.ts +23 -0
  232. package/src/actions/get-current-user.ts +15 -0
  233. package/src/actions/index.ts +6 -0
  234. package/src/actions/is-authenticated.spec.ts +18 -0
  235. package/src/actions/is-authenticated.ts +13 -0
  236. package/src/actions/login-action.spec.ts +41 -0
  237. package/src/actions/login.ts +26 -0
  238. package/src/actions/logout-action.spec.ts +27 -0
  239. package/src/actions/logout.ts +16 -0
  240. package/src/actions/not-found-action.spec.ts +17 -0
  241. package/src/actions/not-found-action.ts +13 -0
  242. package/src/add-cors-header.spec.ts +133 -0
  243. package/src/api-manager.ts +222 -0
  244. package/src/authenticate.spec.ts +78 -0
  245. package/src/authenticate.ts +22 -0
  246. package/src/authorize.spec.ts +69 -0
  247. package/src/authorize.ts +19 -0
  248. package/src/endpoint-generators/create-delete-endpoint.spec.ts +34 -0
  249. package/src/endpoint-generators/create-delete-endpoint.ts +25 -0
  250. package/src/endpoint-generators/create-get-collection-endpoint.spec.ts +164 -0
  251. package/src/endpoint-generators/create-get-collection-endpoint.ts +28 -0
  252. package/src/endpoint-generators/create-get-entity-endpoint.spec.ts +75 -0
  253. package/src/endpoint-generators/create-get-entity-endpoint.ts +29 -0
  254. package/src/endpoint-generators/create-patch-endpoint.spec.ts +36 -0
  255. package/src/endpoint-generators/create-patch-endpoint.ts +27 -0
  256. package/src/endpoint-generators/create-post-endpoint.spec.ts +32 -0
  257. package/src/endpoint-generators/create-post-endpoint.ts +30 -0
  258. package/src/endpoint-generators/index.ts +5 -0
  259. package/src/endpoint-generators/utils.ts +34 -0
  260. package/src/http-authentication-settings.ts +23 -0
  261. package/src/http-user-context.spec.ts +299 -0
  262. package/src/http-user-context.ts +160 -0
  263. package/src/incoming-message-extensions.spec.ts +41 -0
  264. package/src/incoming-message-extensions.ts +19 -0
  265. package/src/index.ts +16 -0
  266. package/src/injector-extensions.spec.ts +19 -0
  267. package/src/injector-extensions.ts +35 -0
  268. package/src/models/cors-options.ts +21 -0
  269. package/src/models/default-session.ts +14 -0
  270. package/src/request-action-implementation.ts +70 -0
  271. package/src/rest-service.integration.spec.ts +166 -0
  272. package/src/rest.integration.test.ts +112 -0
  273. package/src/schema-validator/index.ts +2 -0
  274. package/src/schema-validator/schema-validation-error.ts +11 -0
  275. package/src/schema-validator/schema-validator.test.ts +72 -0
  276. package/src/schema-validator/schema-validator.ts +31 -0
  277. package/src/schema-validator/validate-examples.ts +38 -0
  278. package/src/server-manager.ts +88 -0
  279. package/src/server-response-extensions.spec.ts +53 -0
  280. package/src/server-response-extensions.ts +30 -0
  281. package/src/utils.ts +65 -0
  282. package/src/validate.integration.schema.ts +50 -0
  283. package/src/validate.integration.spec.schema.json +779 -0
  284. package/src/validate.integration.spec.ts +218 -0
  285. package/src/validate.ts +60 -0
@@ -0,0 +1,222 @@
1
+ import { Disposable, PathHelper, usingAsync } from '@furystack/utils'
2
+ import { deserializeQueryString, RestApi } from '@furystack/rest'
3
+ import { Injectable, Injector } from '@furystack/inject'
4
+ import { ServerManager, OnRequest } from './server-manager'
5
+ import { pathToRegexp, match } from 'path-to-regexp'
6
+ import { NotFoundAction } from './actions/not-found-action'
7
+ import { CorsOptions } from './models/cors-options'
8
+ import { Utils } from './utils'
9
+ import { ErrorAction } from './actions/error-action'
10
+ import './server-response-extensions'
11
+ import { IdentityContext, User } from '@furystack/core'
12
+ import { HttpUserContext } from './http-user-context'
13
+ import { RequestAction } from './request-action-implementation'
14
+
15
+ export type RestApiImplementation<T extends RestApi> = {
16
+ [TMethod in keyof T]: {
17
+ [TUrl in keyof T[TMethod]]: T[TMethod][TUrl] extends { result: unknown } ? RequestAction<T[TMethod][TUrl]> : never
18
+ }
19
+ }
20
+
21
+ export interface ImplementApiOptions<T extends RestApi> {
22
+ api: RestApiImplementation<T>
23
+ injector: Injector
24
+ hostName?: string
25
+ root: string
26
+ port: number
27
+ cors?: CorsOptions
28
+ deserializeQueryParams?: (param: string) => any
29
+ }
30
+
31
+ export type CompiledApi = {
32
+ [K: string]: {
33
+ [R: string]: { fullPath: string; regex: RegExp; action: RequestAction<any> }
34
+ }
35
+ }
36
+
37
+ export type OnRequestOptions = OnRequest & {
38
+ compiledApi: CompiledApi
39
+ hostName?: string
40
+ port: number
41
+ rootApiPath: string
42
+ injector: Injector
43
+ cors?: CorsOptions
44
+ supportedMethods: string[]
45
+ deserializeQueryParams?: (param: string) => any
46
+ }
47
+
48
+ @Injectable({ lifetime: 'singleton' })
49
+ export class ApiManager implements Disposable {
50
+ private readonly apis = new Map<string, CompiledApi>()
51
+
52
+ public dispose() {
53
+ this.apis.clear()
54
+ }
55
+
56
+ private getSuportedMethods<T extends RestApi>(api: RestApiImplementation<T>): string[] {
57
+ return Object.keys(api) as any
58
+ }
59
+
60
+ private compileApi<T extends RestApi>(api: RestApiImplementation<T>, root: string) {
61
+ const compiledApi = {} as CompiledApi
62
+ this.getSuportedMethods(api).forEach((method) => {
63
+ const endpoint = {}
64
+
65
+ Object.entries((api as any)[method]).forEach(([path, action]) => {
66
+ const fullPath = `/${PathHelper.joinPaths(root, path)}`
67
+ const regex = pathToRegexp(fullPath)
68
+ ;(endpoint as any)[path] = { regex, action, fullPath }
69
+ })
70
+ ;(compiledApi as any)[method] = endpoint
71
+ })
72
+ return compiledApi
73
+ }
74
+
75
+ public async addApi<T extends RestApi>({
76
+ api,
77
+ hostName,
78
+ port,
79
+ root,
80
+ cors,
81
+ injector,
82
+ deserializeQueryParams,
83
+ }: ImplementApiOptions<T>) {
84
+ const supportedMethods = this.getSuportedMethods(api)
85
+ const rootApiPath = PathHelper.normalize(root)
86
+ const server = await this.serverManager.getOrCreate({ hostName, port })
87
+ const compiledApi = this.compileApi(api, root)
88
+ server.apis.push({
89
+ shouldExec: (msg) =>
90
+ this.shouldExecRequest({
91
+ ...msg,
92
+ method: msg.req.method?.toUpperCase(),
93
+ rootApiPath,
94
+ supportedMethods,
95
+ url: PathHelper.normalize(msg.req.url || ''),
96
+ }),
97
+ onRequest: (msg) =>
98
+ this.onMessage({
99
+ ...msg,
100
+ compiledApi,
101
+ rootApiPath,
102
+ port,
103
+ supportedMethods,
104
+ cors,
105
+ injector,
106
+ hostName,
107
+ deserializeQueryParams,
108
+ }),
109
+ })
110
+ }
111
+
112
+ public shouldExecRequest(options: {
113
+ method?: string
114
+ url?: string
115
+ rootApiPath: string
116
+ supportedMethods: string[]
117
+ }): boolean {
118
+ return options.method &&
119
+ options.url &&
120
+ (options.supportedMethods.includes(options.method) || options.method === 'OPTIONS') &&
121
+ PathHelper.normalize(options.url).startsWith(options.rootApiPath)
122
+ ? true
123
+ : false
124
+ }
125
+
126
+ private getActionFromEndpoint(compiledEndpoint: CompiledApi, fullUrl: URL, method: string) {
127
+ return (Object.values(compiledEndpoint[method]).find((route) => (route as any).regex.test(fullUrl.pathname)) ||
128
+ undefined) as
129
+ | {
130
+ action: RequestAction<{ body: {}; result: {}; query: {}; url: {}; headers: {} }>
131
+ regex: RegExp
132
+ fullPath: string
133
+ }
134
+ | undefined
135
+ }
136
+
137
+ private async executeAction({
138
+ injector,
139
+ req,
140
+ res,
141
+ fullUrl,
142
+ action,
143
+ regex,
144
+ fullPath,
145
+ deserializeQueryParams,
146
+ }: OnRequestOptions & {
147
+ fullUrl: URL
148
+ action: RequestAction<{ body: {}; result: {}; query: {}; url: {}; headers: {} }>
149
+ regex: RegExp
150
+ fullPath: string
151
+ }) {
152
+ await usingAsync(injector.createChild(), async (i) => {
153
+ const utils = i.getInstance(Utils)
154
+ const httpUserContext = i.getInstance(HttpUserContext)
155
+ i.setExplicitInstance<IdentityContext>(
156
+ {
157
+ getCurrentUser: <TUser extends User>() => httpUserContext.getCurrentUser(req) as Promise<TUser>,
158
+ isAuthorized: (...roles) => httpUserContext.isAuthorized(req, ...roles),
159
+ isAuthenticated: () => httpUserContext.isAuthenticated(req),
160
+ },
161
+ IdentityContext,
162
+ )
163
+ try {
164
+ const actionResult = await action({
165
+ request: req,
166
+ response: res,
167
+ injector: i,
168
+ getBody: () => utils.readPostBody<any>(req),
169
+ headers: req.headers,
170
+ getQuery: () =>
171
+ deserializeQueryParams ? deserializeQueryParams(fullUrl.search) : deserializeQueryString(fullUrl.search),
172
+ getUrlParams: () => {
173
+ if (!req.url || !regex) {
174
+ throw new Error('Error parsing request parameters. Missing URL or RegExp.')
175
+ }
176
+ const matcher = match(fullPath, { decode: decodeURIComponent })
177
+ const { params } = matcher(fullUrl.pathname) as any
178
+ return params
179
+ },
180
+ })
181
+ res.sendActionResult(actionResult)
182
+ } catch (error) {
183
+ const errorActionResult = await ErrorAction({
184
+ request: req,
185
+ response: res,
186
+ injector: i,
187
+ getBody: async () => error,
188
+ })
189
+ res.sendActionResult(errorActionResult)
190
+ }
191
+ return
192
+ })
193
+ }
194
+
195
+ private async onMessage(options: OnRequestOptions) {
196
+ const fullUrl = new URL(
197
+ PathHelper.joinPaths(
198
+ 'http://',
199
+ `${options.hostName || ServerManager.DEFAULT_HOST}:${options.port}`,
200
+ options.req.url as string,
201
+ ),
202
+ )
203
+
204
+ options.cors && options.injector.getInstance(Utils).addCorsHeaders(options.cors, options.req, options.res)
205
+ if (options.req.method === 'OPTIONS') {
206
+ options.res.writeHead(200)
207
+ options.res.end()
208
+ return
209
+ }
210
+
211
+ const action = this.getActionFromEndpoint(options.compiledApi, fullUrl, options.req.method?.toUpperCase() as string)
212
+ if (action) {
213
+ await this.executeAction({ ...options, ...action, fullUrl })
214
+ } else {
215
+ options.res.sendActionResult(
216
+ await NotFoundAction({ injector: options.injector, request: options.req, response: options.res }),
217
+ )
218
+ }
219
+ }
220
+
221
+ constructor(private readonly serverManager: ServerManager) {}
222
+ }
@@ -0,0 +1,78 @@
1
+ import { IncomingMessage } from 'http'
2
+ import { Injector } from '@furystack/inject'
3
+ import { usingAsync } from '@furystack/utils'
4
+ import { HttpUserContext } from './http-user-context'
5
+ import { Authenticate } from './authenticate'
6
+ import { ServerResponse } from 'http'
7
+ import { IdentityContext } from '@furystack/core'
8
+ import { EmptyResult } from './request-action-implementation'
9
+
10
+ describe('Authenticate', () => {
11
+ const response = {} as any as ServerResponse
12
+ const request = { url: 'http://google.com' } as IncomingMessage
13
+
14
+ it('Should return 403 w/o basic auth header, when unauthorized and basic auth is disabled', async () => {
15
+ await usingAsync(new Injector(), async (i) => {
16
+ const isAuthenticatedAction = jest.fn(async () => false)
17
+
18
+ i.setExplicitInstance(
19
+ { isAuthenticated: isAuthenticatedAction, getCurrentUser: async () => Promise.reject(':(') },
20
+ IdentityContext,
21
+ )
22
+
23
+ i.setExplicitInstance(
24
+ {
25
+ authentication: { enableBasicAuth: false },
26
+ },
27
+ HttpUserContext,
28
+ )
29
+ const exampleAuthenticatedAction = jest.fn(async (_args: any) => EmptyResult())
30
+ const authorized = Authenticate()(exampleAuthenticatedAction)
31
+
32
+ const result = await authorized({ injector: i, request, response })
33
+ expect(result.statusCode).toBe(401)
34
+ expect(result.chunk).toEqual({ error: 'unauthorized' })
35
+ expect(result.headers).toEqual({ 'Content-Type': 'application/json' })
36
+ expect(exampleAuthenticatedAction).not.toBeCalled()
37
+ })
38
+ })
39
+
40
+ it('Should return 403 with basic auth headers when unauthorized and basic auth is enabled', async () => {
41
+ await usingAsync(new Injector(), async (i) => {
42
+ const isAuthenticatedAction = jest.fn(async () => false)
43
+ i.setExplicitInstance(
44
+ {
45
+ isAuthenticated: isAuthenticatedAction,
46
+ getCurrentUser: async () => Promise.reject(':('),
47
+ authentication: { enableBasicAuth: true },
48
+ },
49
+ HttpUserContext,
50
+ )
51
+ const exampleAuthenticatedAction = jest.fn(async (_args: any) => EmptyResult())
52
+ const authorized = Authenticate()(exampleAuthenticatedAction)
53
+
54
+ const result = await authorized({ injector: i, request, response })
55
+ expect(result.statusCode).toBe(401)
56
+ expect(result.chunk).toEqual({ error: 'unauthorized' })
57
+ expect(result.headers).toEqual({ 'Content-Type': 'application/json', 'WWW-Authenticate': 'Basic' })
58
+ expect(exampleAuthenticatedAction).not.toBeCalled()
59
+ })
60
+ })
61
+
62
+ it('Should exec the original action if authorized', async () => {
63
+ await usingAsync(new Injector(), async (i) => {
64
+ const isAuthenticatedAction = jest.fn(async () => true)
65
+ i.setExplicitInstance(
66
+ { isAuthenticated: isAuthenticatedAction, getCurrentUser: async () => Promise.reject(':(') },
67
+ IdentityContext,
68
+ )
69
+ const exampleAuthenticatedAction = jest.fn(async (_args: any) => EmptyResult())
70
+ const authorized = Authenticate()(exampleAuthenticatedAction)
71
+ const params = { injector: i, body: undefined, query: undefined, request, response }
72
+ const result = await authorized(params)
73
+ expect(result.statusCode).toBe(200)
74
+ expect(result.chunk).toBe(undefined)
75
+ expect(exampleAuthenticatedAction).toBeCalledWith(params)
76
+ })
77
+ })
78
+ })
@@ -0,0 +1,22 @@
1
+ import { sleepAsync } from '@furystack/utils'
2
+ import { HttpUserContext } from './http-user-context'
3
+ import { ActionResult, JsonResult, RequestAction, RequestActionOptions } from './request-action-implementation'
4
+
5
+ export const Authenticate =
6
+ () =>
7
+ <T extends { result: unknown }>(action: RequestAction<T>): RequestAction<T> => {
8
+ return async (args: RequestActionOptions<T>): Promise<ActionResult<T>> => {
9
+ const authenticated = await args.injector.isAuthenticated()
10
+ if (!authenticated) {
11
+ await sleepAsync(Math.random() * 1000)
12
+ return JsonResult(
13
+ { error: 'unauthorized' },
14
+ 401,
15
+ args.injector.getInstance(HttpUserContext).authentication.enableBasicAuth
16
+ ? { 'WWW-Authenticate': 'Basic' }
17
+ : {},
18
+ ) as unknown as ActionResult<T>
19
+ }
20
+ return (await action(args)) as any
21
+ }
22
+ }
@@ -0,0 +1,69 @@
1
+ import { IncomingMessage } from 'http'
2
+ import { Injector } from '@furystack/inject'
3
+ import { usingAsync } from '@furystack/utils'
4
+ import { User, IdentityContext } from '@furystack/core'
5
+ import { Authorize } from './authorize'
6
+ import { ServerResponse } from 'http'
7
+ import { EmptyResult } from './request-action-implementation'
8
+
9
+ describe('Authorize', () => {
10
+ const response = {} as any as ServerResponse
11
+ const request = { url: 'http://google.com' } as IncomingMessage
12
+
13
+ it('Should return 403 when failed to get current user', async () => {
14
+ await usingAsync(new Injector(), async (i) => {
15
+ const isAuthorizedAction = jest.fn(async () => false)
16
+ i.setExplicitInstance(
17
+ { isAuthorized: isAuthorizedAction, getCurrentUser: () => Promise.reject(':(') },
18
+ IdentityContext,
19
+ )
20
+ const exampleAuthorizedAction = jest.fn(async (_args: any) => EmptyResult())
21
+ const authorized = Authorize('Role1')(exampleAuthorizedAction)
22
+
23
+ const result = await authorized({ injector: i, request, response })
24
+ expect(result.statusCode).toBe(403)
25
+ expect(result.chunk).toEqual({ error: 'forbidden' })
26
+ expect(exampleAuthorizedAction).not.toBeCalled()
27
+ })
28
+ })
29
+
30
+ it('Should return 403 if the current user does not have the role', async () => {
31
+ await usingAsync(new Injector(), async (i) => {
32
+ const isAuthorizedAction = jest.fn(async () => false)
33
+ i.setExplicitInstance(
34
+ {
35
+ isAuthorized: isAuthorizedAction,
36
+ getCurrentUser: async () => Promise.resolve<User>({ username: 'a', roles: ['Role1'] }),
37
+ },
38
+ IdentityContext,
39
+ )
40
+ const exampleAuthorizedAction = jest.fn(async (_args: any) => EmptyResult())
41
+ const authorized = Authorize('Role2')(exampleAuthorizedAction)
42
+
43
+ const result = await authorized({ injector: i, request, response })
44
+ expect(result.statusCode).toBe(403)
45
+ expect(result.chunk).toEqual({ error: 'forbidden' })
46
+ expect(exampleAuthorizedAction).not.toBeCalled()
47
+ })
48
+ })
49
+
50
+ it('Should exec the original action if authorized', async () => {
51
+ await usingAsync(new Injector(), async (i) => {
52
+ const isAuthorizedAction = jest.fn(async () => true)
53
+ i.setExplicitInstance(
54
+ {
55
+ isAuthorized: isAuthorizedAction,
56
+ getCurrentUser: async () => Promise.resolve<User>({ username: 'a', roles: ['Role1'] }),
57
+ },
58
+ IdentityContext,
59
+ )
60
+ const exampleAuthorizedAction = jest.fn(async (_args: any) => EmptyResult())
61
+ const authorized = Authorize('Role1')(exampleAuthorizedAction)
62
+ const params = { injector: i, body: undefined, query: undefined, request, response }
63
+ const result = await authorized(params)
64
+ expect(result.statusCode).toBe(200)
65
+ expect(result.chunk).toBe(undefined)
66
+ expect(exampleAuthorizedAction).toBeCalledWith(params)
67
+ })
68
+ })
69
+ })
@@ -0,0 +1,19 @@
1
+ import { sleepAsync } from '@furystack/utils'
2
+ import { ActionResult, JsonResult, RequestAction, RequestActionOptions } from './request-action-implementation'
3
+
4
+ export const Authorize =
5
+ (...roles: string[]) =>
6
+ <T extends { result: unknown }>(action: RequestAction<T>): RequestAction<T> => {
7
+ return async (options: RequestActionOptions<T>): Promise<ActionResult<T>> => {
8
+ try {
9
+ const authorized = await options.injector.isAuthorized(...roles)
10
+ if (!authorized) {
11
+ await sleepAsync(Math.random() * 1000)
12
+ return JsonResult({ error: 'forbidden' }, 403) as any
13
+ }
14
+ } catch (error) {
15
+ return JsonResult({ error: 'forbidden' }, 403) as any
16
+ }
17
+ return (await action(options)) as any
18
+ }
19
+ }
@@ -0,0 +1,34 @@
1
+ import { usingAsync } from '@furystack/utils'
2
+ import { Injector } from '@furystack/inject'
3
+ import { DeleteEndpoint } from '@furystack/rest'
4
+ import { createDeleteEndpoint } from './create-delete-endpoint'
5
+ import got from 'got'
6
+ import { MockClass, setupContext } from './utils'
7
+
8
+ describe('createDeleteEndpoint', () => {
9
+ it('Should delete the entity and report the success', async () => {
10
+ await usingAsync(new Injector(), async (i) => {
11
+ setupContext(i)
12
+ await i.useRestService<{ DELETE: { '/:id': DeleteEndpoint<MockClass, 'id'> } }>({
13
+ root: '/api',
14
+ port: 1111,
15
+ api: {
16
+ DELETE: {
17
+ '/:id': createDeleteEndpoint({ model: MockClass, primaryKey: 'id' }),
18
+ },
19
+ },
20
+ })
21
+ await i.getDataSetFor(MockClass, 'id').add(i, { id: 'mock', value: 'mock' })
22
+
23
+ const countBeforeDelete = await i.getDataSetFor(MockClass, 'id').count(i)
24
+ expect(countBeforeDelete).toBe(1)
25
+
26
+ const response = await got('http://127.0.0.1:1111/api/mock', { method: 'DELETE' })
27
+ expect(response.statusCode).toBe(204)
28
+ expect(response.body).toBe('')
29
+
30
+ const countAfterDelete = await i.getDataSetFor(MockClass, 'id').count(i)
31
+ expect(countAfterDelete).toBe(0)
32
+ })
33
+ })
34
+ })