@openstax/ts-utils 1.21.11 → 1.23.0

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 (252) hide show
  1. package/dist/cjs/assertions/index.d.ts +85 -0
  2. package/dist/cjs/assertions/index.js +157 -0
  3. package/dist/cjs/aws/ssmService.d.ts +5 -0
  4. package/dist/cjs/aws/ssmService.js +9 -0
  5. package/dist/cjs/config/awsParameterConfig.d.ts +10 -0
  6. package/dist/cjs/config/awsParameterConfig.js +26 -0
  7. package/dist/cjs/config/envConfig.d.ts +24 -0
  8. package/dist/cjs/config/envConfig.js +57 -0
  9. package/dist/cjs/config/index.d.ts +48 -0
  10. package/dist/cjs/config/index.js +35 -0
  11. package/dist/cjs/config/lambdaParameterConfig.d.ts +12 -0
  12. package/dist/cjs/config/lambdaParameterConfig.js +45 -0
  13. package/dist/cjs/config/replaceConfig.d.ts +14 -0
  14. package/dist/cjs/config/replaceConfig.js +22 -0
  15. package/dist/cjs/config/resolveConfigValue.d.ts +5 -0
  16. package/dist/cjs/config/resolveConfigValue.js +12 -0
  17. package/dist/cjs/errors/index.d.ts +77 -0
  18. package/dist/cjs/errors/index.js +109 -0
  19. package/dist/cjs/fetch/fetchStatusRetry.d.ts +7 -0
  20. package/dist/cjs/fetch/fetchStatusRetry.js +16 -0
  21. package/dist/cjs/fetch/index.d.ts +64 -0
  22. package/dist/cjs/fetch/index.js +55 -0
  23. package/dist/cjs/guards/index.d.ts +30 -0
  24. package/dist/cjs/guards/index.js +35 -0
  25. package/dist/cjs/index.d.ts +4 -0
  26. package/dist/cjs/index.js +20 -0
  27. package/dist/cjs/middleware/apiErrorHandler.d.ts +24 -0
  28. package/dist/cjs/middleware/apiErrorHandler.js +41 -0
  29. package/dist/cjs/middleware/apiSlowResponseMiddleware.d.ts +23 -0
  30. package/dist/cjs/middleware/apiSlowResponseMiddleware.js +54 -0
  31. package/dist/cjs/middleware/index.d.ts +47 -0
  32. package/dist/cjs/middleware/index.js +48 -0
  33. package/dist/cjs/middleware/lambdaCorsResponseMiddleware.d.ts +20 -0
  34. package/dist/cjs/middleware/lambdaCorsResponseMiddleware.js +42 -0
  35. package/dist/cjs/middleware/throwNotFoundMiddleware.d.ts +4 -0
  36. package/dist/cjs/middleware/throwNotFoundMiddleware.js +14 -0
  37. package/dist/cjs/misc/hashValue.d.ts +10 -0
  38. package/dist/cjs/misc/hashValue.js +17 -0
  39. package/dist/cjs/misc/helpers.d.ts +124 -0
  40. package/dist/cjs/misc/helpers.js +214 -0
  41. package/dist/cjs/misc/merge.d.ts +21 -0
  42. package/dist/cjs/misc/merge.js +45 -0
  43. package/dist/cjs/misc/partitionSequence.d.ts +35 -0
  44. package/dist/cjs/misc/partitionSequence.js +55 -0
  45. package/dist/cjs/pagination/index.d.ts +91 -0
  46. package/dist/cjs/pagination/index.js +83 -0
  47. package/dist/cjs/routing/helpers.d.ts +57 -0
  48. package/dist/cjs/routing/helpers.js +90 -0
  49. package/dist/cjs/routing/index.d.ts +272 -0
  50. package/dist/cjs/routing/index.js +270 -0
  51. package/dist/cjs/routing/validators/zod.d.ts +4 -0
  52. package/dist/cjs/routing/validators/zod.js +12 -0
  53. package/dist/cjs/services/accountsGateway/index.d.ts +85 -0
  54. package/dist/cjs/services/accountsGateway/index.js +118 -0
  55. package/dist/cjs/services/apiGateway/index.d.ts +63 -0
  56. package/dist/cjs/services/apiGateway/index.js +108 -0
  57. package/dist/cjs/services/authProvider/browser.d.ts +74 -0
  58. package/dist/cjs/services/authProvider/browser.js +154 -0
  59. package/dist/cjs/services/authProvider/decryption.d.ts +19 -0
  60. package/dist/cjs/services/authProvider/decryption.js +61 -0
  61. package/dist/cjs/services/authProvider/index.d.ts +61 -0
  62. package/dist/cjs/services/authProvider/index.js +26 -0
  63. package/dist/cjs/services/authProvider/subrequest.d.ts +16 -0
  64. package/dist/cjs/services/authProvider/subrequest.js +50 -0
  65. package/dist/cjs/services/authProvider/utils/decryptAndVerify.d.ts +29 -0
  66. package/dist/cjs/services/authProvider/utils/decryptAndVerify.js +91 -0
  67. package/dist/cjs/services/authProvider/utils/embeddedAuthProvider.d.ts +26 -0
  68. package/dist/cjs/services/authProvider/utils/embeddedAuthProvider.js +47 -0
  69. package/dist/cjs/services/authProvider/utils/userRoleValidator.d.ts +13 -0
  70. package/dist/cjs/services/authProvider/utils/userRoleValidator.js +37 -0
  71. package/dist/cjs/services/documentStore/dynamoEncoding.d.ts +10 -0
  72. package/dist/cjs/services/documentStore/dynamoEncoding.js +52 -0
  73. package/dist/cjs/services/documentStore/index.d.ts +14 -0
  74. package/dist/cjs/services/documentStore/index.js +2 -0
  75. package/dist/cjs/services/documentStore/unversioned/dynamodb.d.ts +16 -0
  76. package/dist/cjs/services/documentStore/unversioned/dynamodb.js +122 -0
  77. package/dist/cjs/services/documentStore/unversioned/file-system.d.ts +18 -0
  78. package/dist/cjs/services/documentStore/unversioned/file-system.js +121 -0
  79. package/dist/cjs/services/documentStore/unversioned/index.d.ts +2 -0
  80. package/dist/cjs/services/documentStore/unversioned/index.js +2 -0
  81. package/dist/cjs/services/documentStore/versioned/dynamodb.d.ts +22 -0
  82. package/dist/cjs/services/documentStore/versioned/dynamodb.js +135 -0
  83. package/dist/cjs/services/documentStore/versioned/file-system.d.ts +24 -0
  84. package/dist/cjs/services/documentStore/versioned/file-system.js +62 -0
  85. package/dist/cjs/services/documentStore/versioned/index.d.ts +17 -0
  86. package/dist/cjs/services/documentStore/versioned/index.js +2 -0
  87. package/dist/cjs/services/exercisesGateway/index.d.ts +71 -0
  88. package/dist/cjs/services/exercisesGateway/index.js +97 -0
  89. package/dist/cjs/services/fileServer/index.d.ts +17 -0
  90. package/dist/cjs/services/fileServer/index.js +19 -0
  91. package/dist/cjs/services/fileServer/localFileServer.d.ts +13 -0
  92. package/dist/cjs/services/fileServer/localFileServer.js +23 -0
  93. package/dist/cjs/services/fileServer/s3FileServer.d.ts +16 -0
  94. package/dist/cjs/services/fileServer/s3FileServer.js +25 -0
  95. package/dist/cjs/services/launchParams/index.d.ts +2 -0
  96. package/dist/cjs/services/launchParams/index.js +7 -0
  97. package/dist/cjs/services/launchParams/signer.d.ts +27 -0
  98. package/dist/cjs/services/launchParams/signer.js +58 -0
  99. package/dist/cjs/services/launchParams/verifier.d.ts +22 -0
  100. package/dist/cjs/services/launchParams/verifier.js +94 -0
  101. package/dist/cjs/services/logger/console.d.ts +4 -0
  102. package/dist/cjs/services/logger/console.js +12 -0
  103. package/dist/cjs/services/logger/index.d.ts +39 -0
  104. package/dist/cjs/services/logger/index.js +31 -0
  105. package/dist/cjs/services/lrsGateway/addStatementDefaultFields.d.ts +5 -0
  106. package/dist/cjs/services/lrsGateway/addStatementDefaultFields.js +21 -0
  107. package/dist/cjs/services/lrsGateway/attempt-utils.d.ts +70 -0
  108. package/dist/cjs/services/lrsGateway/attempt-utils.js +258 -0
  109. package/dist/cjs/services/lrsGateway/file-system.d.ts +17 -0
  110. package/dist/cjs/services/lrsGateway/file-system.js +140 -0
  111. package/dist/cjs/services/lrsGateway/index.d.ts +125 -0
  112. package/dist/cjs/services/lrsGateway/index.js +138 -0
  113. package/dist/cjs/services/lrsGateway/xapiUtils.d.ts +61 -0
  114. package/dist/cjs/services/lrsGateway/xapiUtils.js +94 -0
  115. package/dist/cjs/services/postgresConnection/index.d.ts +35 -0
  116. package/dist/cjs/services/postgresConnection/index.js +63 -0
  117. package/dist/cjs/services/searchProvider/index.d.ts +31 -0
  118. package/dist/cjs/services/searchProvider/index.js +2 -0
  119. package/dist/cjs/services/searchProvider/memorySearchTheBadWay.d.ts +14 -0
  120. package/dist/cjs/services/searchProvider/memorySearchTheBadWay.js +89 -0
  121. package/dist/cjs/tsconfig.without-specs.cjs.tsbuildinfo +1 -0
  122. package/dist/cjs/types.d.ts +31 -0
  123. package/dist/cjs/types.js +2 -0
  124. package/dist/esm/assertions/index.d.ts +85 -0
  125. package/dist/esm/assertions/index.js +146 -0
  126. package/dist/esm/aws/ssmService.d.ts +5 -0
  127. package/dist/esm/aws/ssmService.js +6 -0
  128. package/dist/esm/config/awsParameterConfig.d.ts +10 -0
  129. package/dist/esm/config/awsParameterConfig.js +22 -0
  130. package/dist/esm/config/envConfig.d.ts +24 -0
  131. package/dist/esm/config/envConfig.js +53 -0
  132. package/dist/esm/config/index.d.ts +48 -0
  133. package/dist/esm/config/index.js +17 -0
  134. package/dist/esm/config/lambdaParameterConfig.d.ts +12 -0
  135. package/dist/esm/config/lambdaParameterConfig.js +38 -0
  136. package/dist/esm/config/replaceConfig.d.ts +14 -0
  137. package/dist/esm/config/replaceConfig.js +18 -0
  138. package/dist/esm/config/resolveConfigValue.d.ts +5 -0
  139. package/dist/esm/config/resolveConfigValue.js +8 -0
  140. package/dist/esm/errors/index.d.ts +77 -0
  141. package/dist/esm/errors/index.js +99 -0
  142. package/dist/esm/fetch/fetchStatusRetry.d.ts +7 -0
  143. package/dist/esm/fetch/fetchStatusRetry.js +12 -0
  144. package/dist/esm/fetch/index.d.ts +64 -0
  145. package/dist/esm/fetch/index.js +46 -0
  146. package/dist/esm/guards/index.d.ts +30 -0
  147. package/dist/esm/guards/index.js +28 -0
  148. package/dist/esm/index.d.ts +4 -0
  149. package/dist/esm/index.js +4 -0
  150. package/dist/esm/middleware/apiErrorHandler.d.ts +24 -0
  151. package/dist/esm/middleware/apiErrorHandler.js +37 -0
  152. package/dist/esm/middleware/apiSlowResponseMiddleware.d.ts +23 -0
  153. package/dist/esm/middleware/apiSlowResponseMiddleware.js +50 -0
  154. package/dist/esm/middleware/index.d.ts +47 -0
  155. package/dist/esm/middleware/index.js +44 -0
  156. package/dist/esm/middleware/lambdaCorsResponseMiddleware.d.ts +20 -0
  157. package/dist/esm/middleware/lambdaCorsResponseMiddleware.js +38 -0
  158. package/dist/esm/middleware/throwNotFoundMiddleware.d.ts +4 -0
  159. package/dist/esm/middleware/throwNotFoundMiddleware.js +10 -0
  160. package/dist/esm/misc/hashValue.d.ts +10 -0
  161. package/dist/esm/misc/hashValue.js +13 -0
  162. package/dist/esm/misc/helpers.d.ts +124 -0
  163. package/dist/esm/misc/helpers.js +199 -0
  164. package/dist/esm/misc/merge.d.ts +21 -0
  165. package/dist/esm/misc/merge.js +40 -0
  166. package/dist/esm/misc/partitionSequence.d.ts +35 -0
  167. package/dist/esm/misc/partitionSequence.js +48 -0
  168. package/dist/esm/pagination/index.d.ts +91 -0
  169. package/dist/esm/pagination/index.js +77 -0
  170. package/dist/esm/routing/helpers.d.ts +57 -0
  171. package/dist/esm/routing/helpers.js +83 -0
  172. package/dist/esm/routing/index.d.ts +272 -0
  173. package/dist/esm/routing/index.js +232 -0
  174. package/dist/esm/routing/validators/zod.d.ts +4 -0
  175. package/dist/esm/routing/validators/zod.js +8 -0
  176. package/dist/esm/services/accountsGateway/index.d.ts +85 -0
  177. package/dist/esm/services/accountsGateway/index.js +111 -0
  178. package/dist/esm/services/apiGateway/index.d.ts +63 -0
  179. package/dist/esm/services/apiGateway/index.js +77 -0
  180. package/dist/esm/services/authProvider/browser.d.ts +74 -0
  181. package/dist/esm/services/authProvider/browser.js +150 -0
  182. package/dist/esm/services/authProvider/decryption.d.ts +19 -0
  183. package/dist/esm/services/authProvider/decryption.js +57 -0
  184. package/dist/esm/services/authProvider/index.d.ts +61 -0
  185. package/dist/esm/services/authProvider/index.js +18 -0
  186. package/dist/esm/services/authProvider/subrequest.d.ts +16 -0
  187. package/dist/esm/services/authProvider/subrequest.js +43 -0
  188. package/dist/esm/services/authProvider/utils/decryptAndVerify.d.ts +29 -0
  189. package/dist/esm/services/authProvider/utils/decryptAndVerify.js +85 -0
  190. package/dist/esm/services/authProvider/utils/embeddedAuthProvider.d.ts +26 -0
  191. package/dist/esm/services/authProvider/utils/embeddedAuthProvider.js +40 -0
  192. package/dist/esm/services/authProvider/utils/userRoleValidator.d.ts +13 -0
  193. package/dist/esm/services/authProvider/utils/userRoleValidator.js +33 -0
  194. package/dist/esm/services/documentStore/dynamoEncoding.d.ts +10 -0
  195. package/dist/esm/services/documentStore/dynamoEncoding.js +45 -0
  196. package/dist/esm/services/documentStore/index.d.ts +14 -0
  197. package/dist/esm/services/documentStore/index.js +1 -0
  198. package/dist/esm/services/documentStore/unversioned/dynamodb.d.ts +16 -0
  199. package/dist/esm/services/documentStore/unversioned/dynamodb.js +118 -0
  200. package/dist/esm/services/documentStore/unversioned/file-system.d.ts +18 -0
  201. package/dist/esm/services/documentStore/unversioned/file-system.js +91 -0
  202. package/dist/esm/services/documentStore/unversioned/index.d.ts +2 -0
  203. package/dist/esm/services/documentStore/unversioned/index.js +1 -0
  204. package/dist/esm/services/documentStore/versioned/dynamodb.d.ts +22 -0
  205. package/dist/esm/services/documentStore/versioned/dynamodb.js +131 -0
  206. package/dist/esm/services/documentStore/versioned/file-system.d.ts +24 -0
  207. package/dist/esm/services/documentStore/versioned/file-system.js +58 -0
  208. package/dist/esm/services/documentStore/versioned/index.d.ts +17 -0
  209. package/dist/esm/services/documentStore/versioned/index.js +1 -0
  210. package/dist/esm/services/exercisesGateway/index.d.ts +71 -0
  211. package/dist/esm/services/exercisesGateway/index.js +70 -0
  212. package/dist/esm/services/fileServer/index.d.ts +17 -0
  213. package/dist/esm/services/fileServer/index.js +13 -0
  214. package/dist/esm/services/fileServer/localFileServer.d.ts +13 -0
  215. package/dist/esm/services/fileServer/localFileServer.js +16 -0
  216. package/dist/esm/services/fileServer/s3FileServer.d.ts +16 -0
  217. package/dist/esm/services/fileServer/s3FileServer.js +21 -0
  218. package/dist/esm/services/launchParams/index.d.ts +2 -0
  219. package/dist/esm/services/launchParams/index.js +2 -0
  220. package/dist/esm/services/launchParams/signer.d.ts +27 -0
  221. package/dist/esm/services/launchParams/signer.js +51 -0
  222. package/dist/esm/services/launchParams/verifier.d.ts +22 -0
  223. package/dist/esm/services/launchParams/verifier.js +67 -0
  224. package/dist/esm/services/logger/console.d.ts +4 -0
  225. package/dist/esm/services/logger/console.js +8 -0
  226. package/dist/esm/services/logger/index.d.ts +39 -0
  227. package/dist/esm/services/logger/index.js +27 -0
  228. package/dist/esm/services/lrsGateway/addStatementDefaultFields.d.ts +5 -0
  229. package/dist/esm/services/lrsGateway/addStatementDefaultFields.js +14 -0
  230. package/dist/esm/services/lrsGateway/attempt-utils.d.ts +70 -0
  231. package/dist/esm/services/lrsGateway/attempt-utils.js +236 -0
  232. package/dist/esm/services/lrsGateway/file-system.d.ts +17 -0
  233. package/dist/esm/services/lrsGateway/file-system.js +110 -0
  234. package/dist/esm/services/lrsGateway/index.d.ts +125 -0
  235. package/dist/esm/services/lrsGateway/index.js +111 -0
  236. package/dist/esm/services/lrsGateway/xapiUtils.d.ts +61 -0
  237. package/dist/esm/services/lrsGateway/xapiUtils.js +84 -0
  238. package/dist/esm/services/postgresConnection/index.d.ts +35 -0
  239. package/dist/esm/services/postgresConnection/index.js +56 -0
  240. package/dist/esm/services/searchProvider/index.d.ts +31 -0
  241. package/dist/esm/services/searchProvider/index.js +1 -0
  242. package/dist/esm/services/searchProvider/memorySearchTheBadWay.d.ts +14 -0
  243. package/dist/esm/services/searchProvider/memorySearchTheBadWay.js +85 -0
  244. package/dist/esm/tsconfig.without-specs.esm.tsbuildinfo +1 -0
  245. package/dist/esm/types.d.ts +31 -0
  246. package/dist/esm/types.js +1 -0
  247. package/package.json +16 -16
  248. package/script/bin/deploy.bash +8 -0
  249. package/script/bin/get-env-param.bash +3 -3
  250. package/script/bin/init-params-script.bash +10 -1
  251. package/script/bin/upload-params.bash +3 -3
  252. package/dist/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,85 @@
1
+ import { ConfigProviderForConfig } from '../../config';
2
+ import { GenericFetch } from '../../fetch';
3
+ import { JsonCompatibleStruct } from '../../routing';
4
+ import { ApiUser, ConsentPreferences } from '../authProvider';
5
+ import { Logger } from '../logger';
6
+ export declare type Config = {
7
+ accountsBase: string;
8
+ accountsAuthToken: string;
9
+ };
10
+ interface Initializer<C> {
11
+ configSpace?: C;
12
+ fetch: GenericFetch;
13
+ }
14
+ export declare type FindUserPayload = ({
15
+ external_id: string;
16
+ } | {
17
+ uuid: string;
18
+ }) & {
19
+ sso?: string;
20
+ };
21
+ export declare type FindOrCreateUserPayload = {
22
+ external_id: string;
23
+ email?: string;
24
+ already_verified?: boolean;
25
+ first_name?: string;
26
+ last_name?: string;
27
+ full_name?: string;
28
+ salesforce_contact_id?: string;
29
+ faculty_status?: string;
30
+ role?: string;
31
+ school_type?: string;
32
+ is_test?: boolean;
33
+ sso?: string;
34
+ };
35
+ export declare type FindOrCreateUserResponse = {
36
+ id: number;
37
+ uuid: string;
38
+ external_ids: string[];
39
+ is_test: boolean;
40
+ sso: string;
41
+ };
42
+ export declare type FindUserResponse = (FindOrCreateUserResponse & {
43
+ external_ids: string[];
44
+ }) | undefined;
45
+ export declare type LinkUserPayload = {
46
+ userId: number;
47
+ externalId: string;
48
+ };
49
+ export declare type LinkUserResponse = {
50
+ user_id: number;
51
+ external_id: string;
52
+ };
53
+ export declare type SearchUsersPayload = {
54
+ q: string;
55
+ order_by?: string;
56
+ };
57
+ export declare type SearchUsersResponse = {
58
+ items: Array<ApiUser & {
59
+ external_ids: string[];
60
+ } & JsonCompatibleStruct>;
61
+ total_count: number;
62
+ };
63
+ export declare type UpdateUserPayload = ConsentPreferences;
64
+ export declare type MappedUserInfo<T> = {
65
+ data: T;
66
+ fullName: string;
67
+ platformUserId?: string;
68
+ uuid: string;
69
+ };
70
+ export declare const accountsGateway: <C extends string = "accounts">(initializer: Initializer<C>) => (configProvider: { [key in C]: {
71
+ accountsBase: import("../../config").ConfigValueProvider<string>;
72
+ accountsAuthToken: import("../../config").ConfigValueProvider<string>;
73
+ }; }) => {
74
+ findOrCreateUser: (body: FindOrCreateUserPayload) => Promise<FindOrCreateUserResponse>;
75
+ findUser: (body: FindUserPayload) => Promise<FindUserResponse>;
76
+ getUser: (token: string) => Promise<ApiUser & JsonCompatibleStruct>;
77
+ linkUser: (body: LinkUserPayload) => Promise<LinkUserResponse>;
78
+ mapUserUuids: <T>(userUuidsMap: {
79
+ [uuid: string]: T;
80
+ }, logger: Logger, platformId?: string | undefined) => Promise<MappedUserInfo<T>[]>;
81
+ searchUsers: (payload: SearchUsersPayload) => Promise<SearchUsersResponse>;
82
+ updateUser: (token: string, body: UpdateUserPayload) => Promise<ApiUser & JsonCompatibleStruct>;
83
+ };
84
+ export declare type AccountsGateway = ReturnType<ReturnType<typeof accountsGateway>>;
85
+ export {};
@@ -0,0 +1,111 @@
1
+ import { chunk, isEqual } from 'lodash';
2
+ import queryString from 'query-string';
3
+ import { resolveConfigValue } from '../../config';
4
+ import { ifDefined } from '../../guards';
5
+ import { METHOD } from '../../routing';
6
+ import { Level } from '../logger';
7
+ class ApiError extends Error {
8
+ constructor(message, status) {
9
+ super(message);
10
+ this.status = status;
11
+ }
12
+ }
13
+ export const accountsGateway = (initializer) => (configProvider) => {
14
+ const config = configProvider[ifDefined(initializer.configSpace, 'accounts')];
15
+ const accountsBase = resolveConfigValue(config.accountsBase);
16
+ const accountsAuthToken = resolveConfigValue(config.accountsAuthToken);
17
+ const request = async (method, path, options, statuses = [200, 201]) => {
18
+ const host = (await accountsBase).replace(/\/+$/, '');
19
+ const url = `${host}/api/${path}`;
20
+ const config = {
21
+ headers: {
22
+ Authorization: `Bearer ${options.token || await accountsAuthToken}`,
23
+ },
24
+ method,
25
+ };
26
+ if (options.body) {
27
+ config.body = JSON.stringify(options.body);
28
+ }
29
+ const response = await initializer.fetch(url, config);
30
+ if (!statuses.includes(response.status)) {
31
+ throw new ApiError(`Received unexpected status code ${response.status} for Accounts API call: ${method} ${url}`, response.status);
32
+ }
33
+ return response.json();
34
+ };
35
+ const findOrCreateUser = async (body) => request(METHOD.POST, 'user/find-or-create', { body });
36
+ const findUser = async (body) => {
37
+ try {
38
+ return await request(METHOD.POST, 'user/find', { body });
39
+ }
40
+ catch (error) {
41
+ if (error instanceof ApiError && error.status === 404) {
42
+ return undefined;
43
+ }
44
+ else {
45
+ throw error;
46
+ }
47
+ }
48
+ };
49
+ const getUser = async (token) => request(METHOD.GET, 'user', { token });
50
+ const linkUser = async (body) => request(METHOD.POST, 'user/external-ids', {
51
+ body: {
52
+ external_id: body.externalId,
53
+ user_id: body.userId,
54
+ }
55
+ });
56
+ const searchUsers = async (payload) => request(METHOD.GET, `users?${queryString.stringify(payload)}`, {});
57
+ const updateUser = async (token, body) => request(METHOD.PUT, 'user', { body, token });
58
+ const getPlatformUserId = (externalIds, platformId) => {
59
+ for (const externalId of externalIds) {
60
+ const [userPlatformId, userId] = externalId.split('/', 2);
61
+ if (userPlatformId === platformId) {
62
+ return userId;
63
+ }
64
+ }
65
+ };
66
+ /*
67
+ * If a platformId is given, returns an array where
68
+ * the first element is the user id from the platform
69
+ * and the second is the value from the map
70
+ * Otherwise, returns an array where
71
+ * the first element is the user's full_name
72
+ * and the second is the value from the map
73
+ */
74
+ const mapUserUuids = async (userUuidsMap, logger, platformId) => {
75
+ const results = [];
76
+ // Accounts will not return any results if this search returns more than 10 users
77
+ const chunkedUuids = chunk(Object.keys(userUuidsMap), 10);
78
+ await Promise.all(chunkedUuids.map(async (uuids) => {
79
+ const { items } = await searchUsers({ q: uuids.map((uuid) => `uuid:${uuid}`).join(' ') });
80
+ const accountsUuids = items.map((user) => user.uuid);
81
+ if (!isEqual(accountsUuids.sort(), uuids.sort())) {
82
+ logger.logEvent(Level.Warn, {
83
+ message: 'Unexpected Accounts user search results',
84
+ uuids,
85
+ accountsUuids,
86
+ });
87
+ }
88
+ items.forEach((user) => {
89
+ const platformUserId = platformId ? getPlatformUserId(user.external_ids, platformId) : undefined;
90
+ if (platformId && !platformUserId) {
91
+ logger.logEvent(Level.Warn, {
92
+ message: 'Accounts user has no external_id matching the given platformId',
93
+ accountsUuid: user.uuid,
94
+ platformId,
95
+ });
96
+ }
97
+ if (!user.full_name) {
98
+ logger.logEvent(Level.Warn, {
99
+ message: 'Accounts user has no full_name',
100
+ accountsUuid: user.uuid,
101
+ });
102
+ }
103
+ results.push({
104
+ data: userUuidsMap[user.uuid], fullName: user.full_name, platformUserId, uuid: user.uuid,
105
+ });
106
+ });
107
+ }));
108
+ return results;
109
+ };
110
+ return { findOrCreateUser, findUser, getUser, linkUser, mapUserUuids, searchUsers, updateUser };
111
+ };
@@ -0,0 +1,63 @@
1
+ import { Headers } from 'node-fetch';
2
+ import { ConfigProviderForConfig } from '../../config';
3
+ import { ConfigForFetch, GenericFetch, Response } from '../../fetch';
4
+ import { AnyRoute, ApiResponse, OutputForRoute, ParamsForRoute, PayloadForRoute, QueryParams } from '../../routing';
5
+ import { UnwrapPromise } from '../../types';
6
+ import { Logger } from '../logger';
7
+ declare type TResponsePayload<R> = R extends ApiResponse<any, infer P> ? P : never;
8
+ declare type TResponseStatus<R> = R extends ApiResponse<infer S, any> ? S : never;
9
+ declare type RouteClient<R> = {
10
+ (config: {
11
+ fetchConfig?: any;
12
+ query?: QueryParams;
13
+ } & (ParamsForRoute<R> extends undefined ? {} : {
14
+ params: ParamsForRoute<R>;
15
+ }) & (PayloadForRoute<R> extends undefined ? {} : {
16
+ payload: PayloadForRoute<R>;
17
+ })): Promise<UnsafeApiClientResponse<UnwrapPromise<OutputForRoute<R>>>>;
18
+ renderUrl: (config: {
19
+ query?: QueryParams;
20
+ } & (ParamsForRoute<R> extends undefined ? {} : {
21
+ params: ParamsForRoute<R>;
22
+ })) => Promise<string>;
23
+ };
24
+ interface AcceptStatus<Ro> {
25
+ <S extends TResponseStatus<Ro>[]>(...args: S): Promise<ApiClientResponse<Extract<Ro, Record<'statusCode', S[number]>>>>;
26
+ <S extends number[]>(...args: S): Promise<ApiClientResponse<any>>;
27
+ }
28
+ declare type UnsafeApiClientResponse<Ro> = {
29
+ headers: Headers;
30
+ load: () => Promise<any>;
31
+ status: number;
32
+ acceptStatus: AcceptStatus<Ro>;
33
+ };
34
+ declare type ApiClientResponse<Ro> = Ro extends any ? {
35
+ headers: Headers;
36
+ status: TResponseStatus<Ro>;
37
+ load: () => Promise<TResponsePayload<Ro>>;
38
+ } : never;
39
+ declare type MapRoutesToClient<Ru> = [Ru] extends [AnyRoute<Ru>] ? {
40
+ [N in Ru['name']]: RouteClient<Extract<Ru, Record<'name', N>>>;
41
+ } : never;
42
+ declare type MapRoutesToConfig<Ru> = [Ru] extends [AnyRoute<Ru>] ? {
43
+ [N in Ru['name']]: {
44
+ path: string;
45
+ method: string;
46
+ };
47
+ } : never;
48
+ /** Pulls the content out of a response based on the content type */
49
+ export declare const loadResponse: (response: Response) => () => Promise<any>;
50
+ interface MakeApiGateway<F> {
51
+ <Ru>(config: ConfigProviderForConfig<{
52
+ apiBase: string;
53
+ }>, routes: MapRoutesToConfig<Ru>, appProvider?: {
54
+ authProvider?: {
55
+ getAuthorizedFetchConfig: () => Promise<ConfigForFetch<F>>;
56
+ };
57
+ logger?: Logger;
58
+ }): MapRoutesToClient<Ru>;
59
+ }
60
+ export declare const createApiGateway: <F extends GenericFetch<import("../../fetch").FetchConfig, Response>>(initializer: {
61
+ fetch: F;
62
+ }) => MakeApiGateway<F>;
63
+ export {};
@@ -0,0 +1,77 @@
1
+ import * as pathToRegexp from 'path-to-regexp';
2
+ import queryString from 'query-string';
3
+ import { v4 as uuid } from 'uuid';
4
+ import { merge } from '../..';
5
+ import { resolveConfigValue } from '../../config';
6
+ import { ForbiddenError, SessionExpiredError, UnauthorizedError } from '../../errors';
7
+ import { fetchStatusRetry } from '../../fetch/fetchStatusRetry';
8
+ import { Level } from '../logger';
9
+ /** Pulls the content out of a response based on the content type */
10
+ export const loadResponse = (response) => () => {
11
+ const [contentType] = (response.headers.get('content-type') || '').split(';');
12
+ switch (contentType) {
13
+ case 'text/plain':
14
+ return response.text();
15
+ case 'application/json':
16
+ return response.json();
17
+ default:
18
+ throw new Error(`unknown content type ${contentType}`);
19
+ }
20
+ };
21
+ const makeRouteClient = (initializer, config, route, appProvider) => {
22
+ /* TODO this duplicates code with makeRenderRouteUrl, reuse that */
23
+ const renderUrl = async ({ params, query }) => {
24
+ const apiBase = await resolveConfigValue(config.apiBase);
25
+ const getPathForParams = pathToRegexp.compile(route.path, { encode: encodeURIComponent });
26
+ const search = query && queryString.stringify(query);
27
+ return apiBase.replace(/\/+$/, '') + getPathForParams(params || {}) + (search ? `?${search}` : '');
28
+ };
29
+ const routeClient = async ({ params, payload, query, fetchConfig }) => {
30
+ var _a, _b;
31
+ const { fetch } = initializer;
32
+ const url = await renderUrl({ params, query });
33
+ const body = payload ? JSON.stringify(payload) : undefined;
34
+ const baseOptions = merge((await ((_a = appProvider === null || appProvider === void 0 ? void 0 : appProvider.authProvider) === null || _a === void 0 ? void 0 : _a.getAuthorizedFetchConfig())) || {}, fetchConfig || {});
35
+ const requestId = uuid();
36
+ const requestLogger = (_b = appProvider === null || appProvider === void 0 ? void 0 : appProvider.logger) === null || _b === void 0 ? void 0 : _b.createSubContext();
37
+ requestLogger === null || requestLogger === void 0 ? void 0 : requestLogger.setContext({ requestId, url, timeStamp: new Date().getTime() });
38
+ const fetcher = fetchStatusRetry(fetch, { retries: 1, status: [502], logger: requestLogger });
39
+ requestLogger === null || requestLogger === void 0 ? void 0 : requestLogger.log('Request Initiated', Level.Info);
40
+ return fetcher(url, merge(baseOptions, {
41
+ method: route.method,
42
+ body,
43
+ headers: {
44
+ ...fetchConfig === null || fetchConfig === void 0 ? void 0 : fetchConfig.headers,
45
+ ...(body ? { 'content-type': 'application/json' } : {}),
46
+ 'X-Request-ID': requestId,
47
+ }
48
+ })).then(response => {
49
+ if (response.status === 401) {
50
+ throw new UnauthorizedError();
51
+ }
52
+ if (response.status === 403) {
53
+ throw new ForbiddenError();
54
+ }
55
+ if (response.status === 440) {
56
+ throw new SessionExpiredError();
57
+ }
58
+ return {
59
+ status: response.status,
60
+ acceptStatus: async (...status) => {
61
+ if (!status.includes(response.status)) {
62
+ throw new Error(`unexpected HTTP ${response.status} response from api: ${await response.text()}`);
63
+ }
64
+ return { status: response.status, headers: response.headers, load: loadResponse(response) };
65
+ },
66
+ headers: response.headers,
67
+ load: loadResponse(response),
68
+ };
69
+ });
70
+ };
71
+ routeClient.renderUrl = renderUrl;
72
+ return routeClient;
73
+ };
74
+ export const createApiGateway = (initializer) => (config, routes, appProvider) => {
75
+ return Object.fromEntries(Object.entries(routes)
76
+ .map(([key, routeConfig]) => ([key, makeRouteClient(initializer, config, routeConfig, appProvider)])));
77
+ };
@@ -0,0 +1,74 @@
1
+ import { ConfigProviderForConfig } from '../../config';
2
+ import { FetchConfig, GenericFetch } from '../../fetch';
3
+ import { User } from '.';
4
+ declare type Config = {
5
+ accountsBase: string;
6
+ };
7
+ interface Initializer<C> {
8
+ configSpace?: C;
9
+ window: Window;
10
+ }
11
+ export declare type EventHandler = (e: {
12
+ data: any;
13
+ origin: string;
14
+ source: Pick<Window, 'postMessage'>;
15
+ }) => void;
16
+ export interface Window {
17
+ fetch: GenericFetch;
18
+ parent: Pick<Window, 'postMessage'>;
19
+ location: {
20
+ search: string;
21
+ };
22
+ document: {
23
+ referrer: string;
24
+ };
25
+ postMessage: (data: any, origin: string) => void;
26
+ addEventListener: (event: 'message', callback: EventHandler) => void;
27
+ removeEventListener: (event: 'message', callback: EventHandler) => void;
28
+ }
29
+ export declare type UpdatableUserFields = Partial<Pick<User, 'consent_preferences' | 'first_name' | 'last_name'>>;
30
+ export declare const browserAuthProvider: <C extends string = "auth">({ window, configSpace }: Initializer<C>) => (configProvider: { [key in C]: {
31
+ accountsBase: import("../../config").ConfigValueProvider<string>;
32
+ }; }) => {
33
+ /**
34
+ * gets the authentication token
35
+ */
36
+ getAuthToken: () => Promise<string | null>;
37
+ /**
38
+ * adds auth parameters to the url. this is only safe to use when using javascript to navigate
39
+ * within the current window, eg `window.location = 'https://my.otherservice.com';` anchors
40
+ * should use getAuthorizedLinkUrl for their href.
41
+ *
42
+ * result unreliable unless `getUser` is resolved first.
43
+ */
44
+ getAuthorizedUrl: (urlString: string) => string;
45
+ /**
46
+ * all link href-s must be rendered with auth tokens so that they work when opened in a new tab
47
+ *
48
+ * result unreliable unless `getUser` is resolved first.
49
+ */
50
+ getAuthorizedLinkUrl: (urlString: string) => string;
51
+ /**
52
+ * gets an authorized url for an iframe src. sets params on the url and saves its
53
+ * origin to trust releasing user identity to it
54
+ */
55
+ getAuthorizedEmbedUrl: (urlString: string, extraParams?: {
56
+ [key: string]: string;
57
+ } | undefined) => string;
58
+ /**
59
+ * gets second argument for `fetch` that has authentication token or cookie
60
+ */
61
+ getAuthorizedFetchConfig: () => Promise<FetchConfig>;
62
+ /**
63
+ * loads current user identity. does not reflect changes in identity after being called the first time.
64
+ */
65
+ getUser: () => Promise<User | undefined>;
66
+ /**
67
+ * updates user settings, for example the cookie consent preferences
68
+ */
69
+ updateUser: (updates: UpdatableUserFields) => Promise<{
70
+ user: User;
71
+ token: string | null;
72
+ }>;
73
+ };
74
+ export {};
@@ -0,0 +1,150 @@
1
+ import { once } from '../..';
2
+ import { resolveConfigValue } from '../../config';
3
+ import { ifDefined } from '../../guards';
4
+ import { METHOD, unsafePayloadValidator } from '../../routing';
5
+ import { embeddedAuthProvider, PostMessageTypes } from './utils/embeddedAuthProvider';
6
+ const isUserData = unsafePayloadValidator();
7
+ export const browserAuthProvider = ({ window, configSpace }) => (configProvider) => {
8
+ const config = configProvider[ifDefined(configSpace, 'auth')];
9
+ const accountsBase = once(() => resolveConfigValue(config.accountsBase));
10
+ const queryString = window.location.search;
11
+ const queryKey = 'auth';
12
+ const urlSearchParams = new URLSearchParams(queryString);
13
+ const authQuery = urlSearchParams.get(queryKey);
14
+ const referrer = window.document.referrer ? new URL(window.document.referrer) : undefined;
15
+ const isEmbedded = window.parent !== window;
16
+ const trustedParent = isEmbedded && referrer && referrer.hostname.match(/^(openstax\.org|((.*)(\.openstax\.org|local|localhost)))$/) ? referrer : undefined;
17
+ const { embeddedQueryKey, embeddedQueryValue, getAuthorizedEmbedUrl } = embeddedAuthProvider(() => getUserData(), { authQuery: { key: queryKey, value: authQuery }, window });
18
+ const embeddedQuery = urlSearchParams.get(embeddedQueryKey);
19
+ let userData = { token: authQuery };
20
+ const getAuthToken = async () => {
21
+ return (await getUserData()).token;
22
+ };
23
+ const getAuthorizedLinkUrl = (urlString) => {
24
+ const url = new URL(urlString);
25
+ if (userData.token) {
26
+ url.searchParams.set(queryKey, userData.token);
27
+ }
28
+ return url.href;
29
+ };
30
+ const getAuthorizedUrl = (urlString) => {
31
+ const url = new URL(urlString);
32
+ if (authQuery) {
33
+ url.searchParams.set(queryKey, authQuery);
34
+ }
35
+ else if (embeddedQuery) {
36
+ url.searchParams.set(queryKey, 'embedded');
37
+ }
38
+ if (embeddedQuery) {
39
+ url.searchParams.set(embeddedQueryKey, embeddedQuery);
40
+ }
41
+ return url.href;
42
+ };
43
+ // *note* that this does not actually prevent cookies from being sent on same-origin
44
+ // requests, i'm not sure if its possible to stop browsers from sending cookies in
45
+ // that case
46
+ const getAuthorizedFetchConfigFromData = (data) => {
47
+ const { token } = data;
48
+ return token ? {
49
+ headers: { Authorization: `Bearer ${token}` },
50
+ } : {
51
+ credentials: 'include',
52
+ };
53
+ };
54
+ const getAuthorizedFetchConfig = async () => {
55
+ return getAuthorizedFetchConfigFromData(userData.token ? userData : await getUserData());
56
+ };
57
+ /*
58
+ * requests user identity from parent window via postMessage
59
+ */
60
+ const getParentWindowUser = () => new Promise((resolve, reject) => {
61
+ if (!window.parent || !trustedParent) {
62
+ return reject(new Error('parent window is undefined or not trusted'));
63
+ }
64
+ const handler = (event) => {
65
+ if (event.data.type === PostMessageTypes.ReceiveUser && event.origin === trustedParent.origin) {
66
+ clearTimeout(timeout);
67
+ window.removeEventListener('message', handler);
68
+ resolve(event.data.userData);
69
+ }
70
+ };
71
+ window.addEventListener('message', handler);
72
+ window.parent.postMessage({ type: PostMessageTypes.RequestUser }, trustedParent.origin);
73
+ const timeout = setTimeout(() => {
74
+ window.removeEventListener('message', handler);
75
+ reject(new Error('loading user identity timed out'));
76
+ }, 5000);
77
+ });
78
+ /*
79
+ * requests user identity from accounts api using given token or cookie
80
+ */
81
+ const getFetchUser = async () => {
82
+ const response = await window.fetch((await accountsBase()).replace(/\/+$/, '') + '/api/user?always_200=true', getAuthorizedFetchConfigFromData(userData));
83
+ if (response.status === 200) {
84
+ const body = await response.json();
85
+ const user = isUserData(body) ? body : undefined;
86
+ return { ...userData, user };
87
+ }
88
+ const message = await response.text();
89
+ throw new Error(`Error response from Accounts ${response.status}: ${message}`);
90
+ };
91
+ const getUserData = once(async () => {
92
+ // For backwards compatibility
93
+ if (authQuery === 'embedded') {
94
+ return getParentWindowUser();
95
+ }
96
+ // getFetchUser() will throw here if authQuery is not set
97
+ return await (embeddedQuery === embeddedQueryValue ? getParentWindowUser() : getFetchUser());
98
+ });
99
+ const getUser = async () => {
100
+ return (await getUserData()).user;
101
+ };
102
+ const updateUser = async (updates) => {
103
+ const response = await window.fetch((await accountsBase()).replace(/\/+$/, '') + '/api/user', { ...getAuthorizedFetchConfigFromData(userData), method: METHOD.PUT, body: JSON.stringify(updates) });
104
+ if (response.status === 200) {
105
+ const user = await response.json();
106
+ if (isUserData(user)) {
107
+ return { ...userData, user };
108
+ }
109
+ }
110
+ const message = await response.text();
111
+ throw new Error(`Error response from Accounts ${response.status}: ${message}`);
112
+ };
113
+ return {
114
+ /**
115
+ * gets the authentication token
116
+ */
117
+ getAuthToken,
118
+ /**
119
+ * adds auth parameters to the url. this is only safe to use when using javascript to navigate
120
+ * within the current window, eg `window.location = 'https://my.otherservice.com';` anchors
121
+ * should use getAuthorizedLinkUrl for their href.
122
+ *
123
+ * result unreliable unless `getUser` is resolved first.
124
+ */
125
+ getAuthorizedUrl,
126
+ /**
127
+ * all link href-s must be rendered with auth tokens so that they work when opened in a new tab
128
+ *
129
+ * result unreliable unless `getUser` is resolved first.
130
+ */
131
+ getAuthorizedLinkUrl,
132
+ /**
133
+ * gets an authorized url for an iframe src. sets params on the url and saves its
134
+ * origin to trust releasing user identity to it
135
+ */
136
+ getAuthorizedEmbedUrl,
137
+ /**
138
+ * gets second argument for `fetch` that has authentication token or cookie
139
+ */
140
+ getAuthorizedFetchConfig,
141
+ /**
142
+ * loads current user identity. does not reflect changes in identity after being called the first time.
143
+ */
144
+ getUser,
145
+ /**
146
+ * updates user settings, for example the cookie consent preferences
147
+ */
148
+ updateUser,
149
+ };
150
+ };
@@ -0,0 +1,19 @@
1
+ import type { ConfigProviderForConfig } from '../../config';
2
+ import { AuthProvider, CookieAuthProvider } from '.';
3
+ declare type Config = {
4
+ cookieName: string;
5
+ encryptionPrivateKey: string;
6
+ signaturePublicKey: string;
7
+ };
8
+ interface Initializer<C> {
9
+ configSpace?: C;
10
+ }
11
+ export declare type DecryptionAuthProvider = AuthProvider & {
12
+ getTokenExpiration: (tokenString?: string) => Promise<number | null | undefined>;
13
+ };
14
+ export declare const decryptionAuthProvider: <C extends string = "decryption">(initializer: Initializer<C>) => (configProvider: { [key in C]: {
15
+ cookieName: import("../../config").ConfigValueProvider<string>;
16
+ encryptionPrivateKey: import("../../config").ConfigValueProvider<string>;
17
+ signaturePublicKey: import("../../config").ConfigValueProvider<string>;
18
+ }; }) => CookieAuthProvider<DecryptionAuthProvider>;
19
+ export {};
@@ -0,0 +1,57 @@
1
+ import { resolveConfigValue } from '../../config/resolveConfigValue';
2
+ import { SessionExpiredError } from '../../errors';
3
+ import { ifDefined } from '../../guards';
4
+ import { once } from '../../misc/helpers';
5
+ import { decryptAndVerify } from './utils/decryptAndVerify';
6
+ import { getAuthTokenOrCookie } from '.';
7
+ export const decryptionAuthProvider = (initializer) => (configProvider) => {
8
+ const config = configProvider[ifDefined(initializer.configSpace, 'decryption')];
9
+ const cookieName = once(() => resolveConfigValue(config.cookieName));
10
+ const encryptionPrivateKey = once(() => resolveConfigValue(config.encryptionPrivateKey));
11
+ const signaturePublicKey = once(() => resolveConfigValue(config.signaturePublicKey));
12
+ return ({ request, logger }) => {
13
+ let user;
14
+ const getAuthorizedFetchConfig = async () => {
15
+ const [token, headers] = getAuthTokenOrCookie(request, await cookieName());
16
+ if (!token) {
17
+ return {};
18
+ }
19
+ return { headers };
20
+ };
21
+ const getPayload = async (tokenString) => {
22
+ const token = tokenString !== null && tokenString !== void 0 ? tokenString : getAuthTokenOrCookie(request, await cookieName())[0];
23
+ if (!token) {
24
+ return undefined;
25
+ }
26
+ return decryptAndVerify(token, await encryptionPrivateKey(), await signaturePublicKey());
27
+ };
28
+ const loadUser = async () => {
29
+ const result = await getPayload();
30
+ if (!result) {
31
+ return undefined;
32
+ }
33
+ if ('error' in result && result.error == 'expired token') {
34
+ throw new SessionExpiredError();
35
+ }
36
+ if ('user' in result) {
37
+ logger.setContext({ user: result.user.uuid });
38
+ return result.user;
39
+ }
40
+ return undefined;
41
+ };
42
+ return {
43
+ getAuthorizedFetchConfig,
44
+ getTokenExpiration: async (tokenString) => {
45
+ var _a;
46
+ const payload = await getPayload(tokenString);
47
+ return payload ? ((_a = payload.exp) !== null && _a !== void 0 ? _a : null) : undefined;
48
+ },
49
+ getUser: async () => {
50
+ if (!user) {
51
+ user = await loadUser();
52
+ }
53
+ return user;
54
+ },
55
+ };
56
+ };
57
+ };