@openstax/ts-utils 1.0.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 (57) hide show
  1. package/README.md +118 -0
  2. package/dist/README.md +118 -0
  3. package/dist/assertions.d.ts +9 -0
  4. package/dist/assertions.js +99 -0
  5. package/dist/aws/securityTokenService.d.ts +2 -0
  6. package/dist/aws/securityTokenService.js +6 -0
  7. package/dist/aws/ssmService.d.ts +2 -0
  8. package/dist/aws/ssmService.js +6 -0
  9. package/dist/config.d.ts +26 -0
  10. package/dist/config.js +110 -0
  11. package/dist/errors.d.ts +12 -0
  12. package/dist/errors.js +32 -0
  13. package/dist/fetch.d.ts +59 -0
  14. package/dist/fetch.js +47 -0
  15. package/dist/guards.d.ts +5 -0
  16. package/dist/guards.js +34 -0
  17. package/dist/index.d.ts +16 -0
  18. package/dist/index.js +120 -0
  19. package/dist/middleware.d.ts +9 -0
  20. package/dist/middleware.js +38 -0
  21. package/dist/package.json +65 -0
  22. package/dist/pagination.d.ts +65 -0
  23. package/dist/pagination.js +83 -0
  24. package/dist/routing.d.ts +100 -0
  25. package/dist/routing.js +237 -0
  26. package/dist/services/apiGateway/index.d.ts +61 -0
  27. package/dist/services/apiGateway/index.js +70 -0
  28. package/dist/services/authProvider/browser.d.ts +17 -0
  29. package/dist/services/authProvider/browser.js +30 -0
  30. package/dist/services/authProvider/decryption.d.ts +16 -0
  31. package/dist/services/authProvider/decryption.js +56 -0
  32. package/dist/services/authProvider/index.d.ts +37 -0
  33. package/dist/services/authProvider/index.js +17 -0
  34. package/dist/services/authProvider/subrequest.d.ts +16 -0
  35. package/dist/services/authProvider/subrequest.js +39 -0
  36. package/dist/services/exercisesGateway/index.d.ts +80 -0
  37. package/dist/services/exercisesGateway/index.js +94 -0
  38. package/dist/services/lrsGateway/attempt-utils.d.ts +60 -0
  39. package/dist/services/lrsGateway/attempt-utils.js +270 -0
  40. package/dist/services/lrsGateway/file-system.d.ts +15 -0
  41. package/dist/services/lrsGateway/file-system.js +126 -0
  42. package/dist/services/lrsGateway/index.d.ts +110 -0
  43. package/dist/services/lrsGateway/index.js +116 -0
  44. package/dist/services/searchProvider/index.d.ts +20 -0
  45. package/dist/services/searchProvider/index.js +2 -0
  46. package/dist/services/searchProvider/memorySearchTheBadWay.d.ts +12 -0
  47. package/dist/services/searchProvider/memorySearchTheBadWay.js +51 -0
  48. package/dist/services/versionedDocumentStore/dynamodb.d.ts +20 -0
  49. package/dist/services/versionedDocumentStore/dynamodb.js +151 -0
  50. package/dist/services/versionedDocumentStore/file-system.d.ts +22 -0
  51. package/dist/services/versionedDocumentStore/file-system.js +112 -0
  52. package/dist/services/versionedDocumentStore/index.d.ts +23 -0
  53. package/dist/services/versionedDocumentStore/index.js +2 -0
  54. package/dist/tsconfig.tsbuildinfo +1 -0
  55. package/dist/types.d.ts +6 -0
  56. package/dist/types.js +2 -0
  57. package/package.json +65 -0
@@ -0,0 +1,237 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.requestPayloadProvider = exports.unsafePayloadValidator = exports.getRequestBody = exports.getHeader = exports.METHOD = exports.apiTextResponse = exports.apiJsonResponse = exports.makeGetRequestResponder = exports.renderAnyRouteUrl = exports.makeRenderRouteUrl = exports.makeCreateRoute = void 0;
30
+ const pathToRegexp = __importStar(require("path-to-regexp"));
31
+ const query_string_1 = __importDefault(require("query-string"));
32
+ const assertions_1 = require("./assertions");
33
+ const errors_1 = require("./errors");
34
+ const guards_1 = require("./guards");
35
+ const _1 = require(".");
36
+ /*
37
+ * route definition helper. the only required params of the route are the name, path, and handler. other params
38
+ * can be added to the type and then later used in the routeMatcher. when defining the `createRoute` method, only
39
+ * the request input format is defined, the result format is derived from the routes.
40
+ *
41
+ * eg:
42
+ * export const createRoute = makeCreateRoute<AppServices, ApiRouteRequest, {
43
+ * method: METHOD;
44
+ * }>();
45
+ *
46
+ * eg when defining requestServiceProvider in line, the types have a hard time, it helps to put in another argument:
47
+ * export const exampleRoute = createRoute({name: 'exampleRoute', method: METHOD.GET, path: '/api/example/:key',
48
+ * requestServiceProvider: requestServiceProvider({
49
+ * cookieAuthMiddleware,
50
+ * documentStoreMiddleware,
51
+ * }},
52
+ * async(params: {key: string}, services) => {
53
+ * const result = await services.myDocumentStore.getItem(params.key);
54
+ *
55
+ * if (!result) {
56
+ * throw new NotFoundError('requested item not found');
57
+ * }
58
+ *
59
+ * return apiJsonResponse(200, result);
60
+ * }
61
+ * );
62
+ *
63
+ * eg when using a pre-existing provider variable the types work better:
64
+ * export const exampleRoute = createRoute({name: 'exampleRoute', method: METHOD.GET, path: '/api/example/:key',
65
+ * requestServiceProvider,
66
+ * handler: async(params: {key: string}, services) => {
67
+ * const result = await services.myDocumentStore.getItem(params.key);
68
+ *
69
+ * if (!result) {
70
+ * throw new NotFoundError('requested item not found');
71
+ * }
72
+ *
73
+ * return apiJsonResponse(200, result);
74
+ * }
75
+ * });
76
+ */
77
+ const makeCreateRoute = () => (...args) => {
78
+ return (args.length === 1
79
+ ? args[0]
80
+ : { ...args[0], handler: args[1] });
81
+ };
82
+ exports.makeCreateRoute = makeCreateRoute;
83
+ /* begin reverse routing utils */
84
+ const makeRenderRouteUrl = () => (route, params, query = {}) => {
85
+ const getPathForParams = pathToRegexp.compile(route.path, { encode: encodeURIComponent });
86
+ const search = query_string_1.default.stringify(query);
87
+ const path = getPathForParams(params) + (search ? `?${search}` : '');
88
+ return path;
89
+ };
90
+ exports.makeRenderRouteUrl = makeRenderRouteUrl;
91
+ exports.renderAnyRouteUrl = (0, exports.makeRenderRouteUrl)();
92
+ const bindRoute = (services, pathExtractor, matcher) => (route) => {
93
+ const getParamsFromPath = pathToRegexp.match(route.path, { decode: decodeURIComponent });
94
+ const boundServiceProvider = route.requestServiceProvider && route.requestServiceProvider(services);
95
+ return (request) => {
96
+ const path = pathExtractor(request);
97
+ const match = getParamsFromPath(path);
98
+ if ((!matcher || matcher(request, route)) && match) {
99
+ return () => route.handler(match.params, boundServiceProvider ? boundServiceProvider({ request }, { route, params: match.params }) : undefined);
100
+ }
101
+ };
102
+ };
103
+ /*
104
+ * here among other things we're specifying a generic response format that the response and error handling middleware can use,
105
+ * if any routes have responses that don't adhere to this it'll complain about it.
106
+ *
107
+ * eg:
108
+ * export const getRequestResponder = makeGetRequestResponder<AppServices, TRoutes, ApiRouteRequest, Promise<ApiRouteResponse>>()({
109
+ * routes: apiRoutes, // the route definitions
110
+ * pathExtractor, // how to get the path out of the request format
111
+ * routeMatcher, // logic for matching route (if there is any in addition to the path matching)
112
+ * errorHandler, // any special error handling
113
+ * });
114
+ *
115
+ * eg an lambda entrypoint:
116
+ * export const handler: (request: APIGatewayProxyEventV2) => Promise<ApiRouteResponse> =
117
+ * getRequestResponder(
118
+ * lambdaServices, // the AppServices for this entrypoint
119
+ * lambdaMiddleware // environment specific response middleware (like cors)
120
+ * );
121
+ */
122
+ const makeGetRequestResponder = () => ({ routes, pathExtractor, routeMatcher, errorHandler }) => (services, responseMiddleware) => {
123
+ const boundRoutes = routes().map(bindRoute(services, pathExtractor, routeMatcher));
124
+ const boundResponseMiddleware = responseMiddleware ? responseMiddleware(services) : undefined;
125
+ // *note* this opaque promise guard is less generic than i hoped so
126
+ // i'm leaving it here instead of the guards file.
127
+ //
128
+ // its less than ideal than because it enforces that the handlers return
129
+ // the same type as the parent promise, usually a handler can be either a
130
+ // promise or a non-promise value and the promise figures it out, but those
131
+ // types are getting complicated quickly here.
132
+ const isPromise = (thing) => thing instanceof Promise;
133
+ return (request) => {
134
+ try {
135
+ const executor = (0, _1.mapFind)(boundRoutes, (route) => route(request));
136
+ if (executor) {
137
+ const result = boundResponseMiddleware ?
138
+ boundResponseMiddleware(executor(), request) : executor();
139
+ if (isPromise(result) && errorHandler) {
140
+ const errorHandlerWithMiddleware = (e) => boundResponseMiddleware ?
141
+ boundResponseMiddleware(errorHandler(e), request) : errorHandler(e);
142
+ return result.catch(errorHandlerWithMiddleware);
143
+ }
144
+ else {
145
+ return result;
146
+ }
147
+ }
148
+ else if (boundResponseMiddleware) {
149
+ return boundResponseMiddleware(undefined, request);
150
+ }
151
+ }
152
+ catch (e) {
153
+ if (errorHandler && e instanceof Error) {
154
+ return boundResponseMiddleware ? boundResponseMiddleware(errorHandler(e), request) : errorHandler(e);
155
+ }
156
+ throw e;
157
+ }
158
+ return undefined;
159
+ };
160
+ };
161
+ exports.makeGetRequestResponder = makeGetRequestResponder;
162
+ const apiJsonResponse = (statusCode, data, headers) => ({ statusCode, data, body: JSON.stringify(data), headers: { ...headers, 'content-type': 'application/json' } });
163
+ exports.apiJsonResponse = apiJsonResponse;
164
+ const apiTextResponse = (statusCode, data, headers) => ({ statusCode, data, body: data, headers: { ...headers, 'content-type': 'text/plain' } });
165
+ exports.apiTextResponse = apiTextResponse;
166
+ var METHOD;
167
+ (function (METHOD) {
168
+ METHOD["GET"] = "GET";
169
+ METHOD["HEAD"] = "HEAD";
170
+ METHOD["POST"] = "POST";
171
+ METHOD["PUT"] = "PUT";
172
+ METHOD["PATCH"] = "PATCH";
173
+ METHOD["DELETE"] = "DELETE";
174
+ METHOD["OPTIONS"] = "OPTIONS";
175
+ })(METHOD = exports.METHOD || (exports.METHOD = {}));
176
+ /* utils and middleware for loading request payload (must follow this pattern for `PayloadForRoute` to work) */
177
+ // use this to support case insensitive header keys
178
+ const getHeader = (headers, name) => {
179
+ const key = Object.keys(headers).find(header => header.toLowerCase() === name.toLowerCase());
180
+ return key ? headers[key] : undefined;
181
+ };
182
+ exports.getHeader = getHeader;
183
+ const getRequestBody = (request) => {
184
+ if ((0, exports.getHeader)(request.headers, 'content-type') !== 'application/json') {
185
+ throw new errors_1.InvalidRequestError('unknown content type: ' + (0, exports.getHeader)(request.headers, 'content-type'));
186
+ }
187
+ if (!request.body) {
188
+ return {};
189
+ }
190
+ try {
191
+ return JSON.parse(request.body);
192
+ }
193
+ catch (error) {
194
+ // Since the body is provided by the user, invalid JSON in the body is an invalid request
195
+ // We return the message which tells them why the JSON is invalid, but no backtrace
196
+ throw new errors_1.InvalidRequestError((0, assertions_1.assertErrorInstanceOf)(error, SyntaxError).message);
197
+ }
198
+ };
199
+ exports.getRequestBody = getRequestBody;
200
+ // stub validator because writing validators is annoying
201
+ const unsafePayloadValidator = () => (input) => {
202
+ return (0, guards_1.isPlainObject)(input) && Object.keys(input).length > 0;
203
+ };
204
+ exports.unsafePayloadValidator = unsafePayloadValidator;
205
+ /*
206
+ * the given validator is a guard, which provides the correct type this helper loads the body, runs the validator, throws if it isn't valid, or returns it as
207
+ * the correct type if it is valid.
208
+ *
209
+ * this accomplishes a few things:
210
+ * - establishes type of payload for route body logic
211
+ * - validates the payload for route logic
212
+ * - establishes type of payload for client logic calling this route
213
+ *
214
+ * eg:
215
+ * export const exampleRoute = createRoute({name: 'exampleRoute', method: METHOD.POST, path: '/example/:id',
216
+ * requestServiceProvider: composeServiceMiddleware(
217
+ * requestServiceProvider, // previously compiled middleware can be re-composed if you have something to add
218
+ * requestPayloadProvider(validatePayload)
219
+ * )},
220
+ * async(params: {id: string}, services) => {
221
+ * const result = await services.myDocumentStore.putItem({
222
+ * ...services.payload,
223
+ * id: params.id,
224
+ * });
225
+ * return apiJsonResponse(201, result);
226
+ * }
227
+ * );
228
+ * */
229
+ const requestPayloadProvider = (validator) => () => (requestServices) => {
230
+ const payload = (0, exports.getRequestBody)(requestServices.request);
231
+ // for more precise error messages, throw your own InvalidRequestError from your validator function
232
+ if (!validator(payload)) {
233
+ throw new errors_1.InvalidRequestError();
234
+ }
235
+ return { ...requestServices, payload };
236
+ };
237
+ exports.requestPayloadProvider = requestPayloadProvider;
@@ -0,0 +1,61 @@
1
+ import { ConfigProviderForConfig } from '../../config';
2
+ import { ConfigForFetch, GenericFetch } from '../../fetch';
3
+ import { AnyRoute, ApiResponse, OutputForRoute, ParamsForRoute, PayloadForRoute, QueryParams } from '../../routing';
4
+ import { UnwrapPromise } from '../../types';
5
+ declare type TResponsePayload<R> = R extends ApiResponse<any, infer P> ? P : never;
6
+ declare type TResponseStatus<R> = R extends ApiResponse<infer S, any> ? S : never;
7
+ declare type RouteClient<R> = {
8
+ (config: {
9
+ fetchConfig?: any;
10
+ query?: QueryParams;
11
+ } & (ParamsForRoute<R> extends undefined ? {} : {
12
+ params: ParamsForRoute<R>;
13
+ }) & (PayloadForRoute<R> extends undefined ? {} : {
14
+ payload: PayloadForRoute<R>;
15
+ })): Promise<UnsafeApiClientResponse<UnwrapPromise<OutputForRoute<R>>>>;
16
+ renderUrl: (config: {
17
+ query?: QueryParams;
18
+ } & (ParamsForRoute<R> extends undefined ? {} : {
19
+ params: ParamsForRoute<R>;
20
+ })) => Promise<string>;
21
+ };
22
+ interface AcceptStatus<Ro> {
23
+ <S extends TResponseStatus<Ro>[]>(...args: S): ApiClientResponse<Extract<Ro, Record<'statusCode', S[number]>>>;
24
+ <S extends number[]>(...args: S): ApiClientResponse<any>;
25
+ }
26
+ declare type UnsafeApiClientResponse<Ro> = {
27
+ load: () => Promise<any>;
28
+ status: number;
29
+ acceptStatus: AcceptStatus<Ro>;
30
+ };
31
+ declare type ApiClientResponse<Ro> = Ro extends any ? {
32
+ status: TResponseStatus<Ro>;
33
+ load: () => Promise<TResponsePayload<Ro>>;
34
+ } : never;
35
+ declare type MapRoutesToClient<Ru> = [Ru] extends [AnyRoute<Ru>] ? {
36
+ [N in Ru['name']]: RouteClient<Extract<Ru, Record<'name', N>>>;
37
+ } : never;
38
+ declare type MapRoutesToConfig<Ru> = [Ru] extends [AnyRoute<Ru>] ? {
39
+ [N in Ru['name']]: {
40
+ path: string;
41
+ method: string;
42
+ };
43
+ } : never;
44
+ interface MakeApiGateway<F> {
45
+ <Ru>(config: ConfigProviderForConfig<{
46
+ apiBase: string;
47
+ }>, routes: MapRoutesToConfig<Ru>, authProvider?: {
48
+ getAuthorizedFetchConfig: () => ConfigForFetch<F>;
49
+ }): MapRoutesToClient<Ru>;
50
+ }
51
+ export declare const createApiGateway: <F extends GenericFetch<import("../../fetch").FetchConfig, {
52
+ status: number;
53
+ headers: {
54
+ get: (name: string) => string | null;
55
+ };
56
+ json: () => Promise<any>;
57
+ text: () => Promise<string>;
58
+ }>>(initializer: {
59
+ fetch: F;
60
+ }) => MakeApiGateway<F>;
61
+ export {};
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.createApiGateway = void 0;
30
+ const pathToRegexp = __importStar(require("path-to-regexp"));
31
+ const query_string_1 = __importDefault(require("query-string"));
32
+ const __1 = require("../..");
33
+ const config_1 = require("../../config");
34
+ const makeRouteClient = (initializer, config, route, authProvider) => {
35
+ const renderUrl = async ({ params, query }) => {
36
+ const apiBase = await (0, config_1.resolveConfigValue)(config.apiBase);
37
+ const getPathForParams = pathToRegexp.compile(route.path, { encode: encodeURIComponent });
38
+ const search = query && query_string_1.default.stringify(query);
39
+ return apiBase.replace(/\/+$/, '') + getPathForParams(params || {}) + (search ? `?${search}` : '');
40
+ };
41
+ const routeClient = async ({ params, payload, query, fetchConfig }) => {
42
+ const url = await renderUrl({ params, query });
43
+ const body = payload ? JSON.stringify(payload) : undefined;
44
+ const baseOptions = (0, __1.merge)((authProvider === null || authProvider === void 0 ? void 0 : authProvider.getAuthorizedFetchConfig()) || {}, fetchConfig || {});
45
+ return initializer.fetch(url, (0, __1.merge)(baseOptions, {
46
+ method: route.method,
47
+ body,
48
+ headers: {
49
+ ...fetchConfig === null || fetchConfig === void 0 ? void 0 : fetchConfig.headers,
50
+ ...(body ? { 'content-type': 'application/json' } : {}),
51
+ }
52
+ })).then(response => ({
53
+ status: response.status,
54
+ acceptStatus: (...status) => {
55
+ if (!status.includes(response.status)) {
56
+ throw new Error('unexpected response from api');
57
+ }
58
+ return { status: response.status, load: () => response.json() };
59
+ },
60
+ load: () => response.json(),
61
+ }));
62
+ };
63
+ routeClient.renderUrl = renderUrl;
64
+ return routeClient;
65
+ };
66
+ const createApiGateway = (initializer) => (config, routes, authProvider) => {
67
+ return Object.fromEntries(Object.entries(routes)
68
+ .map(([key, routeConfig]) => ([key, makeRouteClient(initializer, config, routeConfig, authProvider)])));
69
+ };
70
+ exports.createApiGateway = createApiGateway;
@@ -0,0 +1,17 @@
1
+ import { ConfigProviderForConfig } from '../../config';
2
+ import { FetchConfig, GenericFetch } from '../../fetch';
3
+ import { User } from '.';
4
+ declare type Config = {
5
+ accountsUrl: string;
6
+ };
7
+ interface Initializer<C> {
8
+ configSpace?: C;
9
+ fetch: GenericFetch;
10
+ }
11
+ export declare const browserAuthProvider: <C extends string = "auth">(initializer: Initializer<C>) => (configProvider: { [key in C]: {
12
+ accountsUrl: import("../../config").ConfigValueProvider<string>;
13
+ }; }) => (queryString?: string) => {
14
+ getAuthorizedFetchConfig: () => FetchConfig;
15
+ getUser: () => Promise<User | undefined>;
16
+ };
17
+ export {};
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.browserAuthProvider = void 0;
4
+ const __1 = require("../..");
5
+ const config_1 = require("../../config");
6
+ const guards_1 = require("../../guards");
7
+ const browserAuthProvider = (initializer) => (configProvider) => {
8
+ const config = configProvider[(0, guards_1.ifDefined)(initializer.configSpace, 'auth')];
9
+ const accountsUrl = (0, config_1.resolveConfigValue)(config.accountsUrl);
10
+ return (queryString = '') => {
11
+ const token = new URLSearchParams(queryString).get('auth');
12
+ // *note* that this does not actually prevent cookies from being sent on same-origin
13
+ // requests, i'm not sure if its possible to stop browsers from sending cookies in
14
+ // that case
15
+ const getAuthorizedFetchConfig = () => token ? {
16
+ headers: { Authorization: `Bearer ${token}` },
17
+ } : {
18
+ credentials: 'include',
19
+ };
20
+ const getUser = (0, __1.once)(async () => {
21
+ return initializer.fetch((await accountsUrl).replace(/\/+$/, '') + '/accounts/api/user', getAuthorizedFetchConfig())
22
+ .then(response => response.status === 200 ? response.json() : undefined);
23
+ });
24
+ return {
25
+ getAuthorizedFetchConfig,
26
+ getUser
27
+ };
28
+ };
29
+ };
30
+ exports.browserAuthProvider = browserAuthProvider;
@@ -0,0 +1,16 @@
1
+ import { ConfigProviderForConfig } from '../../config';
2
+ import { 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 const decryptionAuthProvider: <C extends string = "decryption">(initializer: Initializer<C>) => (configProvider: { [key in C]: {
12
+ cookieName: import("../../config").ConfigValueProvider<string>;
13
+ encryptionPrivateKey: import("../../config").ConfigValueProvider<string>;
14
+ signaturePublicKey: import("../../config").ConfigValueProvider<string>;
15
+ }; }) => CookieAuthProvider;
16
+ export {};
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.decryptionAuthProvider = void 0;
4
+ const jose_1 = require("jose");
5
+ const config_1 = require("../../config");
6
+ const guards_1 = require("../../guards");
7
+ const _1 = require(".");
8
+ const decryptionAuthProvider = (initializer) => (configProvider) => {
9
+ const config = configProvider[(0, guards_1.ifDefined)(initializer.configSpace, 'decryption')];
10
+ const cookieName = (0, config_1.resolveConfigValue)(config.cookieName);
11
+ const encryptionPrivateKey = (0, config_1.resolveConfigValue)(config.encryptionPrivateKey);
12
+ const signaturePublicKey = (0, config_1.resolveConfigValue)(config.signaturePublicKey);
13
+ const decryptAndVerify = async (jwt) => {
14
+ try {
15
+ // Decrypt SSO cookie
16
+ const { plaintext } = await (0, jose_1.compactDecrypt)(jwt, Buffer.from(await encryptionPrivateKey), // Note: Buffer.from() is node-js only
17
+ { contentEncryptionAlgorithms: ['A256GCM'], keyManagementAlgorithms: ['dir'] });
18
+ // Verify SSO cookie signature
19
+ const { payload } = await (0, jose_1.compactVerify)(plaintext, await (0, jose_1.importSPKI)(await signaturePublicKey, 'RS256'), { algorithms: ['RS256'] });
20
+ return payload;
21
+ }
22
+ catch {
23
+ return undefined;
24
+ }
25
+ };
26
+ return (request) => {
27
+ let user;
28
+ const loadUser = async () => {
29
+ const token = (0, _1.getAuthTokenOrCookie)(request, await cookieName);
30
+ if (!token) {
31
+ return undefined;
32
+ }
33
+ const payload = await decryptAndVerify(token);
34
+ if (!payload) {
35
+ return undefined;
36
+ }
37
+ // Note: Uint8Array.toString() returns text in node-js only
38
+ // The browser version is new TextDecoder().decode(payload)
39
+ const jwt = JSON.parse(payload.toString());
40
+ // Allow clock skew up to 5 minutes
41
+ if (!jwt.sub || !jwt.sub.uuid || (jwt.exp && jwt.exp < Math.floor(Date.now() / 1000) - 300)) {
42
+ return undefined;
43
+ }
44
+ return jwt.sub;
45
+ };
46
+ return {
47
+ getUser: async () => {
48
+ if (!user) {
49
+ user = await loadUser();
50
+ }
51
+ return user;
52
+ }
53
+ };
54
+ };
55
+ };
56
+ exports.decryptionAuthProvider = decryptionAuthProvider;
@@ -0,0 +1,37 @@
1
+ import { HttpHeaders } from '../../routing';
2
+ export interface User {
3
+ name: string;
4
+ first_name: string;
5
+ last_name: string;
6
+ full_name: string;
7
+ uuid: string;
8
+ faculty_status: string;
9
+ is_administrator: string;
10
+ is_not_gdpr_location: string;
11
+ contact_infos: Array<{
12
+ type: string;
13
+ value: string;
14
+ is_verified: boolean;
15
+ is_guessed_preferred: boolean;
16
+ }>;
17
+ }
18
+ export declare type AuthProvider = {
19
+ getUser: () => Promise<User | undefined>;
20
+ };
21
+ export declare type CookieAuthProviderRequest = {
22
+ headers: HttpHeaders;
23
+ cookies?: string[];
24
+ };
25
+ export declare type CookieAuthProvider = (request: CookieAuthProviderRequest) => AuthProvider;
26
+ export declare type StubAuthProvider = (user: User | undefined) => AuthProvider;
27
+ export declare const stubAuthProvider: (user?: User | undefined) => {
28
+ getUser: () => Promise<User | undefined>;
29
+ getAuthorizedFetchConfig: () => {
30
+ headers: {
31
+ Authorization: string;
32
+ };
33
+ } | {
34
+ headers?: undefined;
35
+ };
36
+ };
37
+ export declare const getAuthTokenOrCookie: (request: CookieAuthProviderRequest, cookieName: string) => string;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getAuthTokenOrCookie = exports.stubAuthProvider = void 0;
7
+ const cookie_1 = __importDefault(require("cookie"));
8
+ const routing_1 = require("../../routing");
9
+ const stubAuthProvider = (user) => ({ getUser: () => Promise.resolve(user), getAuthorizedFetchConfig: () => (user ? { headers: { Authorization: user.uuid } } : {}) });
10
+ exports.stubAuthProvider = stubAuthProvider;
11
+ const getAuthTokenOrCookie = (request, cookieName) => {
12
+ var _a;
13
+ const authHeader = (0, routing_1.getHeader)(request.headers, 'authorization');
14
+ return authHeader && authHeader.length >= 8 && authHeader.startsWith('Bearer ') ?
15
+ authHeader.slice(7) : cookie_1.default.parse(((_a = request.cookies) === null || _a === void 0 ? void 0 : _a.join('; ')) || '')[cookieName];
16
+ };
17
+ exports.getAuthTokenOrCookie = getAuthTokenOrCookie;
@@ -0,0 +1,16 @@
1
+ import { ConfigProviderForConfig } from '../../config';
2
+ import { GenericFetch } from '../../fetch';
3
+ import { CookieAuthProvider } from '.';
4
+ declare type Config = {
5
+ cookieName: string;
6
+ accountsUrl: string;
7
+ };
8
+ interface Initializer<C> {
9
+ configSpace?: C;
10
+ fetch: GenericFetch;
11
+ }
12
+ export declare const subrequestAuthProvider: <C extends string = "subrequest">(initializer: Initializer<C>) => (configProvider: { [key in C]: {
13
+ cookieName: import("../../config").ConfigValueProvider<string>;
14
+ accountsUrl: import("../../config").ConfigValueProvider<string>;
15
+ }; }) => CookieAuthProvider;
16
+ export {};
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.subrequestAuthProvider = void 0;
7
+ const cookie_1 = __importDefault(require("cookie"));
8
+ const config_1 = require("../../config");
9
+ const guards_1 = require("../../guards");
10
+ const _1 = require(".");
11
+ const subrequestAuthProvider = (initializer) => (configProvider) => {
12
+ const config = configProvider[(0, guards_1.ifDefined)(initializer.configSpace, 'subrequest')];
13
+ const cookieName = (0, config_1.resolveConfigValue)(config.cookieName);
14
+ const accountsUrl = (0, config_1.resolveConfigValue)(config.accountsUrl);
15
+ return (request) => {
16
+ let user;
17
+ const loadUser = async () => {
18
+ const token = (0, _1.getAuthTokenOrCookie)(request, await cookieName);
19
+ if (!token) {
20
+ return undefined;
21
+ }
22
+ return initializer.fetch(await accountsUrl, {
23
+ headers: {
24
+ cookie: cookie_1.default.serialize(await cookieName, token)
25
+ }
26
+ })
27
+ .then(response => response.json());
28
+ };
29
+ return {
30
+ getUser: async () => {
31
+ if (!user) {
32
+ user = await loadUser();
33
+ }
34
+ return user;
35
+ }
36
+ };
37
+ };
38
+ };
39
+ exports.subrequestAuthProvider = subrequestAuthProvider;