@remit/backend 0.0.95 → 0.0.96
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/handlers/calendar-feed-validators.test.ts +228 -0
- package/src/handlers/calendar-feed.test.ts +439 -0
- package/src/handlers/calendar-feed.ts +291 -0
- package/src/handlers/calendar.ts +6 -1
- package/src/handlers/index.ts +6 -0
- package/src/index.ts +17 -15
- package/src/jwt-auth.ts +25 -9
- package/src/log-redaction.test.ts +148 -0
- package/src/response.ts +72 -25
- package/src/service/compose-sqlite.ts +2 -0
- package/src/service/create-remit-client.ts +4 -0
- package/src/types.ts +24 -0
package/src/response.ts
CHANGED
|
@@ -7,23 +7,50 @@ import {
|
|
|
7
7
|
resolveAllowedOrigin,
|
|
8
8
|
} from "./request-context.js";
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
10
|
+
const RAW_API_RESPONSE: unique symbol = Symbol("remit.rawApiResponse");
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A response whose bytes are not JSON — an iCalendar feed, and whatever later
|
|
14
|
+
* joins it.
|
|
15
|
+
*
|
|
16
|
+
* Every other handler returns a plain object and lets this module decide the
|
|
17
|
+
* status, the content type and the serialization. That is the right default and
|
|
18
|
+
* stays the default; a handler that has to own its own media type says so with
|
|
19
|
+
* `rawApiResponse` rather than by returning a shape this one has to guess at.
|
|
20
|
+
* The CORS and correlation headers are still added here, so a raw response is
|
|
21
|
+
* not a way around them.
|
|
22
|
+
*/
|
|
23
|
+
export interface RawApiResponse {
|
|
24
|
+
statusCode: number;
|
|
25
|
+
headers: Record<string, string>;
|
|
26
|
+
body: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type MarkedRawApiResponse = RawApiResponse & {
|
|
30
|
+
readonly [RAW_API_RESPONSE]: true;
|
|
31
|
+
};
|
|
21
32
|
|
|
22
|
-
|
|
33
|
+
export const rawApiResponse = (
|
|
34
|
+
response: RawApiResponse,
|
|
35
|
+
): MarkedRawApiResponse =>
|
|
36
|
+
Object.assign(response, { [RAW_API_RESPONSE]: true as const });
|
|
23
37
|
|
|
38
|
+
const isRawApiResponse = (
|
|
39
|
+
body: Record<string, unknown>,
|
|
40
|
+
): body is MarkedRawApiResponse & Record<string, unknown> =>
|
|
41
|
+
RAW_API_RESPONSE in body;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The headers every response carries whatever its body is: the CORS grant the
|
|
45
|
+
* browser needs, and the id the request's log lines are already tagged with.
|
|
46
|
+
* Returning that id is what makes a bug report's "correlation id" resolve to a
|
|
47
|
+
* server-side line; the browser can only read a non-safelisted header when it
|
|
48
|
+
* is exposed.
|
|
49
|
+
*/
|
|
50
|
+
const envelopeHeaders = (): Record<string, string> => {
|
|
24
51
|
const allowOrigin = resolveAllowedOrigin(getRequestOrigin());
|
|
25
52
|
|
|
26
|
-
const
|
|
53
|
+
const headers: Record<string, string> = {
|
|
27
54
|
"Access-Control-Allow-Origin": allowOrigin,
|
|
28
55
|
"Access-Control-Allow-Headers": "Authorization,Content-Type",
|
|
29
56
|
"Access-Control-Allow-Methods": "GET,POST,PUT,PATCH,DELETE,OPTIONS",
|
|
@@ -31,26 +58,46 @@ export const formatResponse = (
|
|
|
31
58
|
};
|
|
32
59
|
|
|
33
60
|
if (allowOrigin !== "*") {
|
|
34
|
-
|
|
61
|
+
headers["Access-Control-Allow-Credentials"] = "true";
|
|
35
62
|
}
|
|
36
63
|
|
|
37
|
-
// The id the request's log lines are already tagged with. Returning it is
|
|
38
|
-
// what makes a bug report's "correlation id" resolve to a server-side line;
|
|
39
|
-
// the browser can only read a non-safelisted header when it is exposed.
|
|
40
64
|
const correlationId = getRequestCorrelationId();
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
65
|
+
if (correlationId) {
|
|
66
|
+
headers["x-correlation-id"] = correlationId;
|
|
67
|
+
headers["Access-Control-Expose-Headers"] = "x-correlation-id";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return headers;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const formatResponse = (
|
|
74
|
+
body: Record<string, unknown>,
|
|
75
|
+
statusCode = 200,
|
|
76
|
+
): APIGatewayProxyResult => {
|
|
77
|
+
if (body.statusCode && typeof body.statusCode === "number") {
|
|
78
|
+
statusCode = body.statusCode;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (isRawApiResponse(body)) {
|
|
82
|
+
logger.debug({ statusCode: body.statusCode }, "response");
|
|
83
|
+
return {
|
|
84
|
+
statusCode: body.statusCode,
|
|
85
|
+
headers: { ...envelopeHeaders(), ...body.headers },
|
|
86
|
+
body: body.body,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if ("body" in body && body.body && typeof body.body === "object") {
|
|
91
|
+
body = body.body as Record<string, unknown>;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
logger.debug({ statusCode }, "response");
|
|
47
95
|
|
|
48
96
|
return {
|
|
49
97
|
statusCode: statusCode,
|
|
50
98
|
headers: {
|
|
51
99
|
"Content-Type": "application/json",
|
|
52
|
-
...
|
|
53
|
-
...correlationHeaders,
|
|
100
|
+
...envelopeHeaders(),
|
|
54
101
|
},
|
|
55
102
|
body: JSON.stringify(body),
|
|
56
103
|
};
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
AddressRepo,
|
|
7
7
|
CalendarCollectionRepo,
|
|
8
8
|
CalendarEventIndexRepo,
|
|
9
|
+
CalendarFeedTokenRepo,
|
|
9
10
|
CalendarObjectRepo,
|
|
10
11
|
CalendarSuggestionRepo,
|
|
11
12
|
ConfigImportRepo,
|
|
@@ -86,6 +87,7 @@ export const buildSqliteClient = async (): Promise<RemitClient> => {
|
|
|
86
87
|
calendarCollection: new CalendarCollectionRepo(genericDb),
|
|
87
88
|
calendarObject: new CalendarObjectRepo(genericDb),
|
|
88
89
|
calendarEventIndex: new CalendarEventIndexRepo(genericDb),
|
|
90
|
+
calendarFeedToken: new CalendarFeedTokenRepo(genericDb),
|
|
89
91
|
calendarSuggestion: new CalendarSuggestionRepo(genericDb),
|
|
90
92
|
calendarUnitOfWork: new DrizzleCalendarUnitOfWork(genericDb),
|
|
91
93
|
unitOfWork: new DrizzleUnitOfWork(messageDataDb),
|
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
IAddressRepository,
|
|
7
7
|
ICalendarCollectionRepository,
|
|
8
8
|
ICalendarEventIndexRepository,
|
|
9
|
+
ICalendarFeedTokenRepository,
|
|
9
10
|
ICalendarObjectRepository,
|
|
10
11
|
ICalendarSuggestionRepository,
|
|
11
12
|
ICalendarUnitOfWork,
|
|
@@ -130,6 +131,7 @@ export interface RemitClient {
|
|
|
130
131
|
calendarCollection: ICalendarCollectionRepository;
|
|
131
132
|
calendarObject: ICalendarObjectRepository;
|
|
132
133
|
calendarEventIndex: ICalendarEventIndexRepository;
|
|
134
|
+
calendarFeedToken: ICalendarFeedTokenRepository;
|
|
133
135
|
calendarSuggestion: ICalendarSuggestionRepository;
|
|
134
136
|
calendarUnitOfWork: ICalendarUnitOfWork;
|
|
135
137
|
|
|
@@ -225,6 +227,7 @@ export interface RemitClientRepositories {
|
|
|
225
227
|
calendarCollection: ICalendarCollectionRepository;
|
|
226
228
|
calendarObject: ICalendarObjectRepository;
|
|
227
229
|
calendarEventIndex: ICalendarEventIndexRepository;
|
|
230
|
+
calendarFeedToken: ICalendarFeedTokenRepository;
|
|
228
231
|
calendarSuggestion: ICalendarSuggestionRepository;
|
|
229
232
|
calendarUnitOfWork: ICalendarUnitOfWork;
|
|
230
233
|
unitOfWork?: IUnitOfWork;
|
|
@@ -466,6 +469,7 @@ export const createRemitClient = (deps: RemitClientDeps): RemitClient => {
|
|
|
466
469
|
calendarCollection: repositories.calendarCollection,
|
|
467
470
|
calendarObject: repositories.calendarObject,
|
|
468
471
|
calendarEventIndex: repositories.calendarEventIndex,
|
|
472
|
+
calendarFeedToken: repositories.calendarFeedToken,
|
|
469
473
|
calendarSuggestion: repositories.calendarSuggestion,
|
|
470
474
|
calendarUnitOfWork: repositories.calendarUnitOfWork,
|
|
471
475
|
unitOfWork: repositories.unitOfWork,
|
package/src/types.ts
CHANGED
|
@@ -63,6 +63,10 @@ export type OperationIds =
|
|
|
63
63
|
| "CalendarDetailOperations_getCalendar"
|
|
64
64
|
| "CalendarDetailOperations_updateCalendar"
|
|
65
65
|
| "CalendarDetailOperations_deleteCalendar"
|
|
66
|
+
| "CalendarDetailOperations_getCalendarFeed"
|
|
67
|
+
| "CalendarDetailOperations_putCalendarFeed"
|
|
68
|
+
| "CalendarDetailOperations_revokeCalendarFeed"
|
|
69
|
+
| "CalendarFeedOperations_getCalendarFeedIcal"
|
|
66
70
|
| "CalendarEventOperations_listCalendarEvents"
|
|
67
71
|
| "CalendarEventOperations_createCalendarEvent"
|
|
68
72
|
| "CalendarEventDetailOperations_getCalendarEvent"
|
|
@@ -180,6 +184,26 @@ export type CalendarDetailOperationIds = MatchPrefix<
|
|
|
180
184
|
OperationIds
|
|
181
185
|
>;
|
|
182
186
|
|
|
187
|
+
/**
|
|
188
|
+
* The feed sub-resource of `/calendars/{calendarId}`, handled beside the
|
|
189
|
+
* collection itself rather than inside it: minting a credential and serving one
|
|
190
|
+
* calendar's settings are different jobs on the same path.
|
|
191
|
+
*/
|
|
192
|
+
export type CalendarFeedDetailOperationIds =
|
|
193
|
+
| "CalendarDetailOperations_getCalendarFeed"
|
|
194
|
+
| "CalendarDetailOperations_putCalendarFeed"
|
|
195
|
+
| "CalendarDetailOperations_revokeCalendarFeed";
|
|
196
|
+
|
|
197
|
+
export type CalendarCollectionDetailOperationIds = Exclude<
|
|
198
|
+
CalendarDetailOperationIds,
|
|
199
|
+
CalendarFeedDetailOperationIds
|
|
200
|
+
>;
|
|
201
|
+
|
|
202
|
+
export type CalendarFeedOperationIds = MatchPrefix<
|
|
203
|
+
"CalendarFeedOperations_",
|
|
204
|
+
OperationIds
|
|
205
|
+
>;
|
|
206
|
+
|
|
183
207
|
export type CalendarEventOperationIds = MatchPrefix<
|
|
184
208
|
"CalendarEventOperations_",
|
|
185
209
|
OperationIds
|