@fnd-platform/api 1.0.0-alpha.2 → 1.0.0-alpha.4
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/lib/api-project.d.ts +76 -64
- package/lib/api-project.d.ts.map +1 -1
- package/lib/api-project.js +595 -1
- package/lib/api-project.js.map +1 -1
- package/lib/handlers/content.d.ts +25 -9
- package/lib/handlers/content.d.ts.map +1 -1
- package/lib/handlers/content.js +270 -52
- package/lib/handlers/content.js.map +1 -1
- package/lib/handlers/health.js +10 -10
- package/lib/index.d.ts +2 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +5 -1
- package/lib/index.js.map +1 -1
- package/lib/lib/errors.js +56 -62
- package/lib/lib/keys.d.ts +122 -0
- package/lib/lib/keys.d.ts.map +1 -0
- package/lib/lib/keys.js +116 -0
- package/lib/lib/keys.js.map +1 -0
- package/lib/middleware/auth.js +25 -25
- package/lib/middleware/cors.js +29 -34
- package/lib/middleware/error-handler.js +22 -21
- package/lib/middleware/index.js +14 -44
- package/lib/middleware/logging.js +26 -30
- package/lib/middleware/validation.js +30 -29
- package/lib/options.js +3 -3
- package/package.json +4 -2
package/lib/lib/errors.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* Custom error classes for API responses.
|
|
4
4
|
*
|
|
@@ -7,14 +7,8 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @packageDocumentation
|
|
9
9
|
*/
|
|
10
|
-
Object.defineProperty(exports,
|
|
11
|
-
exports.ConflictError =
|
|
12
|
-
exports.ForbiddenError =
|
|
13
|
-
exports.UnauthorizedError =
|
|
14
|
-
exports.ValidationError =
|
|
15
|
-
exports.NotFoundError =
|
|
16
|
-
exports.ApiError =
|
|
17
|
-
void 0;
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.ConflictError = exports.ForbiddenError = exports.UnauthorizedError = exports.ValidationError = exports.NotFoundError = exports.ApiError = void 0;
|
|
18
12
|
/**
|
|
19
13
|
* Base API error class.
|
|
20
14
|
*
|
|
@@ -29,38 +23,38 @@ exports.ConflictError =
|
|
|
29
23
|
* ```
|
|
30
24
|
*/
|
|
31
25
|
class ApiError extends Error {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
26
|
+
code;
|
|
27
|
+
statusCode;
|
|
28
|
+
details;
|
|
29
|
+
/**
|
|
30
|
+
* Creates a new ApiError.
|
|
31
|
+
*
|
|
32
|
+
* @param message - Human-readable error message
|
|
33
|
+
* @param code - Machine-readable error code (UPPER_SNAKE_CASE)
|
|
34
|
+
* @param statusCode - HTTP status code (default: 500)
|
|
35
|
+
* @param details - Optional additional error details
|
|
36
|
+
*/
|
|
37
|
+
constructor(message, code, statusCode = 500, details) {
|
|
38
|
+
super(message);
|
|
39
|
+
this.code = code;
|
|
40
|
+
this.statusCode = statusCode;
|
|
41
|
+
this.details = details;
|
|
42
|
+
this.name = 'ApiError';
|
|
43
|
+
// Maintains proper stack trace for where error was thrown
|
|
44
|
+
Error.captureStackTrace(this, this.constructor);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Converts the error to a JSON-serializable object.
|
|
48
|
+
*
|
|
49
|
+
* @returns Object with code, message, and details
|
|
50
|
+
*/
|
|
51
|
+
toJSON() {
|
|
52
|
+
return {
|
|
53
|
+
code: this.code,
|
|
54
|
+
message: this.message,
|
|
55
|
+
details: this.details,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
64
58
|
}
|
|
65
59
|
exports.ApiError = ApiError;
|
|
66
60
|
/**
|
|
@@ -74,10 +68,10 @@ exports.ApiError = ApiError;
|
|
|
74
68
|
* ```
|
|
75
69
|
*/
|
|
76
70
|
class NotFoundError extends ApiError {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
71
|
+
constructor(message = 'Resource not found') {
|
|
72
|
+
super(message, 'NOT_FOUND', 404);
|
|
73
|
+
this.name = 'NotFoundError';
|
|
74
|
+
}
|
|
81
75
|
}
|
|
82
76
|
exports.NotFoundError = NotFoundError;
|
|
83
77
|
/**
|
|
@@ -92,10 +86,10 @@ exports.NotFoundError = NotFoundError;
|
|
|
92
86
|
* ```
|
|
93
87
|
*/
|
|
94
88
|
class ValidationError extends ApiError {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
89
|
+
constructor(message = 'Validation failed', details) {
|
|
90
|
+
super(message, 'VALIDATION_ERROR', 400, details);
|
|
91
|
+
this.name = 'ValidationError';
|
|
92
|
+
}
|
|
99
93
|
}
|
|
100
94
|
exports.ValidationError = ValidationError;
|
|
101
95
|
/**
|
|
@@ -109,10 +103,10 @@ exports.ValidationError = ValidationError;
|
|
|
109
103
|
* ```
|
|
110
104
|
*/
|
|
111
105
|
class UnauthorizedError extends ApiError {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
106
|
+
constructor(message = 'Unauthorized') {
|
|
107
|
+
super(message, 'UNAUTHORIZED', 401);
|
|
108
|
+
this.name = 'UnauthorizedError';
|
|
109
|
+
}
|
|
116
110
|
}
|
|
117
111
|
exports.UnauthorizedError = UnauthorizedError;
|
|
118
112
|
/**
|
|
@@ -127,10 +121,10 @@ exports.UnauthorizedError = UnauthorizedError;
|
|
|
127
121
|
* ```
|
|
128
122
|
*/
|
|
129
123
|
class ForbiddenError extends ApiError {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
124
|
+
constructor(message = 'Forbidden') {
|
|
125
|
+
super(message, 'FORBIDDEN', 403);
|
|
126
|
+
this.name = 'ForbiddenError';
|
|
127
|
+
}
|
|
134
128
|
}
|
|
135
129
|
exports.ForbiddenError = ForbiddenError;
|
|
136
130
|
/**
|
|
@@ -145,10 +139,10 @@ exports.ForbiddenError = ForbiddenError;
|
|
|
145
139
|
* ```
|
|
146
140
|
*/
|
|
147
141
|
class ConflictError extends ApiError {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
142
|
+
constructor(message = 'Resource conflict') {
|
|
143
|
+
super(message, 'CONFLICT', 409);
|
|
144
|
+
this.name = 'ConflictError';
|
|
145
|
+
}
|
|
152
146
|
}
|
|
153
147
|
exports.ConflictError = ConflictError;
|
|
154
|
-
//# sourceMappingURL=errors.js.map
|
|
148
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DynamoDB key builders for single-table design.
|
|
3
|
+
*
|
|
4
|
+
* These helpers generate consistent key structures for content entities
|
|
5
|
+
* following the fnd-platform single-table design patterns.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Key structure for DynamoDB primary key.
|
|
11
|
+
*/
|
|
12
|
+
export interface PrimaryKey {
|
|
13
|
+
PK: string;
|
|
14
|
+
SK: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Key structure for GSI1 (slug lookup).
|
|
18
|
+
*/
|
|
19
|
+
export interface GSI1Key {
|
|
20
|
+
GSI1PK: string;
|
|
21
|
+
GSI1SK: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Key structure for GSI2 (type/status listing).
|
|
25
|
+
*/
|
|
26
|
+
export interface GSI2Key {
|
|
27
|
+
GSI2PK: string;
|
|
28
|
+
GSI2SK: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Content key builders for DynamoDB operations.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* // Get primary key for content item
|
|
36
|
+
* const pk = contentKeys.pk('content-123');
|
|
37
|
+
* const sk = contentKeys.sk();
|
|
38
|
+
*
|
|
39
|
+
* // Build GSI1 keys for slug lookup
|
|
40
|
+
* const gsi1Keys = contentKeys.gsi1.bySlug('my-blog-post');
|
|
41
|
+
*
|
|
42
|
+
* // Build GSI2 keys for listing by type
|
|
43
|
+
* const gsi2Keys = contentKeys.gsi2.byType('blog-post', '2024-01-15T12:00:00Z');
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export declare const contentKeys: {
|
|
47
|
+
/**
|
|
48
|
+
* Generate partition key for content item.
|
|
49
|
+
* @param id - Content ID
|
|
50
|
+
* @returns Formatted partition key
|
|
51
|
+
*/
|
|
52
|
+
pk: (id: string) => string;
|
|
53
|
+
/**
|
|
54
|
+
* Generate sort key for content item.
|
|
55
|
+
* @returns Constant sort key for content
|
|
56
|
+
*/
|
|
57
|
+
sk: () => string;
|
|
58
|
+
/**
|
|
59
|
+
* Generate full primary key for content item.
|
|
60
|
+
* @param id - Content ID
|
|
61
|
+
* @returns Primary key object
|
|
62
|
+
*/
|
|
63
|
+
keys: (id: string) => PrimaryKey;
|
|
64
|
+
/**
|
|
65
|
+
* GSI1 key builders for slug-based lookups.
|
|
66
|
+
*/
|
|
67
|
+
gsi1: {
|
|
68
|
+
/**
|
|
69
|
+
* Generate GSI1 keys for looking up content by slug.
|
|
70
|
+
* @param slug - Content slug
|
|
71
|
+
* @returns GSI1 key object
|
|
72
|
+
*/
|
|
73
|
+
bySlug: (slug: string) => GSI1Key;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* GSI2 key builders for listing content by type.
|
|
77
|
+
*/
|
|
78
|
+
gsi2: {
|
|
79
|
+
/**
|
|
80
|
+
* Generate GSI2 keys for listing content by type with timestamp sorting.
|
|
81
|
+
* @param contentType - Content type (e.g., 'blog-post', 'page')
|
|
82
|
+
* @param timestamp - ISO 8601 timestamp for sort ordering
|
|
83
|
+
* @returns GSI2 key object
|
|
84
|
+
*/
|
|
85
|
+
byType: (contentType: string, timestamp: string) => GSI2Key;
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* User key builders for DynamoDB operations.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```typescript
|
|
93
|
+
* // Get primary key for user profile
|
|
94
|
+
* const pk = userKeys.pk('user-123');
|
|
95
|
+
* const sk = userKeys.sk.profile();
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
export declare const userKeys: {
|
|
99
|
+
/**
|
|
100
|
+
* Generate partition key for user.
|
|
101
|
+
* @param id - User ID (Cognito sub)
|
|
102
|
+
* @returns Formatted partition key
|
|
103
|
+
*/
|
|
104
|
+
pk: (id: string) => string;
|
|
105
|
+
/**
|
|
106
|
+
* Sort key generators for user-related items.
|
|
107
|
+
*/
|
|
108
|
+
sk: {
|
|
109
|
+
/**
|
|
110
|
+
* Generate sort key for user profile.
|
|
111
|
+
* @returns Profile sort key
|
|
112
|
+
*/
|
|
113
|
+
profile: () => string;
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* Generate full primary key for user profile.
|
|
117
|
+
* @param id - User ID
|
|
118
|
+
* @returns Primary key object
|
|
119
|
+
*/
|
|
120
|
+
keys: (id: string) => PrimaryKey;
|
|
121
|
+
};
|
|
122
|
+
//# sourceMappingURL=keys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../src/lib/keys.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;CACZ;AAED;;GAEG;AACH,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,WAAW;IACtB;;;;OAIG;aACM,MAAM,KAAG,MAAM;IAExB;;;OAGG;cACK,MAAM;IAEd;;;;OAIG;eACQ,MAAM,KAAG,UAAU;IAK9B;;OAEG;;QAED;;;;WAIG;uBACY,MAAM,KAAG,OAAO;;IAMjC;;OAEG;;QAED;;;;;WAKG;8BACmB,MAAM,aAAa,MAAM,KAAG,OAAO;;CAK5D,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,QAAQ;IACnB;;;;OAIG;aACM,MAAM,KAAG,MAAM;IAExB;;OAEG;;QAED;;;WAGG;uBACU,MAAM;;IAGrB;;;;OAIG;eACQ,MAAM,KAAG,UAAU;CAI/B,CAAC"}
|
package/lib/lib/keys.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* DynamoDB key builders for single-table design.
|
|
4
|
+
*
|
|
5
|
+
* These helpers generate consistent key structures for content entities
|
|
6
|
+
* following the fnd-platform single-table design patterns.
|
|
7
|
+
*
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.userKeys = exports.contentKeys = void 0;
|
|
12
|
+
/**
|
|
13
|
+
* Content key builders for DynamoDB operations.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* // Get primary key for content item
|
|
18
|
+
* const pk = contentKeys.pk('content-123');
|
|
19
|
+
* const sk = contentKeys.sk();
|
|
20
|
+
*
|
|
21
|
+
* // Build GSI1 keys for slug lookup
|
|
22
|
+
* const gsi1Keys = contentKeys.gsi1.bySlug('my-blog-post');
|
|
23
|
+
*
|
|
24
|
+
* // Build GSI2 keys for listing by type
|
|
25
|
+
* const gsi2Keys = contentKeys.gsi2.byType('blog-post', '2024-01-15T12:00:00Z');
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
exports.contentKeys = {
|
|
29
|
+
/**
|
|
30
|
+
* Generate partition key for content item.
|
|
31
|
+
* @param id - Content ID
|
|
32
|
+
* @returns Formatted partition key
|
|
33
|
+
*/
|
|
34
|
+
pk: (id) => `CONTENT#${id}`,
|
|
35
|
+
/**
|
|
36
|
+
* Generate sort key for content item.
|
|
37
|
+
* @returns Constant sort key for content
|
|
38
|
+
*/
|
|
39
|
+
sk: () => 'CONTENT',
|
|
40
|
+
/**
|
|
41
|
+
* Generate full primary key for content item.
|
|
42
|
+
* @param id - Content ID
|
|
43
|
+
* @returns Primary key object
|
|
44
|
+
*/
|
|
45
|
+
keys: (id) => ({
|
|
46
|
+
PK: exports.contentKeys.pk(id),
|
|
47
|
+
SK: exports.contentKeys.sk(),
|
|
48
|
+
}),
|
|
49
|
+
/**
|
|
50
|
+
* GSI1 key builders for slug-based lookups.
|
|
51
|
+
*/
|
|
52
|
+
gsi1: {
|
|
53
|
+
/**
|
|
54
|
+
* Generate GSI1 keys for looking up content by slug.
|
|
55
|
+
* @param slug - Content slug
|
|
56
|
+
* @returns GSI1 key object
|
|
57
|
+
*/
|
|
58
|
+
bySlug: (slug) => ({
|
|
59
|
+
GSI1PK: `CONTENT#SLUG#${slug}`,
|
|
60
|
+
GSI1SK: 'CONTENT',
|
|
61
|
+
}),
|
|
62
|
+
},
|
|
63
|
+
/**
|
|
64
|
+
* GSI2 key builders for listing content by type.
|
|
65
|
+
*/
|
|
66
|
+
gsi2: {
|
|
67
|
+
/**
|
|
68
|
+
* Generate GSI2 keys for listing content by type with timestamp sorting.
|
|
69
|
+
* @param contentType - Content type (e.g., 'blog-post', 'page')
|
|
70
|
+
* @param timestamp - ISO 8601 timestamp for sort ordering
|
|
71
|
+
* @returns GSI2 key object
|
|
72
|
+
*/
|
|
73
|
+
byType: (contentType, timestamp) => ({
|
|
74
|
+
GSI2PK: contentType,
|
|
75
|
+
GSI2SK: timestamp,
|
|
76
|
+
}),
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* User key builders for DynamoDB operations.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```typescript
|
|
84
|
+
* // Get primary key for user profile
|
|
85
|
+
* const pk = userKeys.pk('user-123');
|
|
86
|
+
* const sk = userKeys.sk.profile();
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
exports.userKeys = {
|
|
90
|
+
/**
|
|
91
|
+
* Generate partition key for user.
|
|
92
|
+
* @param id - User ID (Cognito sub)
|
|
93
|
+
* @returns Formatted partition key
|
|
94
|
+
*/
|
|
95
|
+
pk: (id) => `USER#${id}`,
|
|
96
|
+
/**
|
|
97
|
+
* Sort key generators for user-related items.
|
|
98
|
+
*/
|
|
99
|
+
sk: {
|
|
100
|
+
/**
|
|
101
|
+
* Generate sort key for user profile.
|
|
102
|
+
* @returns Profile sort key
|
|
103
|
+
*/
|
|
104
|
+
profile: () => 'PROFILE',
|
|
105
|
+
},
|
|
106
|
+
/**
|
|
107
|
+
* Generate full primary key for user profile.
|
|
108
|
+
* @param id - User ID
|
|
109
|
+
* @returns Primary key object
|
|
110
|
+
*/
|
|
111
|
+
keys: (id) => ({
|
|
112
|
+
PK: exports.userKeys.pk(id),
|
|
113
|
+
SK: exports.userKeys.sk.profile(),
|
|
114
|
+
}),
|
|
115
|
+
};
|
|
116
|
+
//# sourceMappingURL=keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../../src/lib/keys.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;AA0BH;;;;;;;;;;;;;;;GAeG;AACU,QAAA,WAAW,GAAG;IACzB;;;;OAIG;IACH,EAAE,EAAE,CAAC,EAAU,EAAU,EAAE,CAAC,WAAW,EAAE,EAAE;IAE3C;;;OAGG;IACH,EAAE,EAAE,GAAW,EAAE,CAAC,SAAS;IAE3B;;;;OAIG;IACH,IAAI,EAAE,CAAC,EAAU,EAAc,EAAE,CAAC,CAAC;QACjC,EAAE,EAAE,mBAAW,CAAC,EAAE,CAAC,EAAE,CAAC;QACtB,EAAE,EAAE,mBAAW,CAAC,EAAE,EAAE;KACrB,CAAC;IAEF;;OAEG;IACH,IAAI,EAAE;QACJ;;;;WAIG;QACH,MAAM,EAAE,CAAC,IAAY,EAAW,EAAE,CAAC,CAAC;YAClC,MAAM,EAAE,gBAAgB,IAAI,EAAE;YAC9B,MAAM,EAAE,SAAS;SAClB,CAAC;KACH;IAED;;OAEG;IACH,IAAI,EAAE;QACJ;;;;;WAKG;QACH,MAAM,EAAE,CAAC,WAAmB,EAAE,SAAiB,EAAW,EAAE,CAAC,CAAC;YAC5D,MAAM,EAAE,WAAW;YACnB,MAAM,EAAE,SAAS;SAClB,CAAC;KACH;CACF,CAAC;AAEF;;;;;;;;;GASG;AACU,QAAA,QAAQ,GAAG;IACtB;;;;OAIG;IACH,EAAE,EAAE,CAAC,EAAU,EAAU,EAAE,CAAC,QAAQ,EAAE,EAAE;IAExC;;OAEG;IACH,EAAE,EAAE;QACF;;;WAGG;QACH,OAAO,EAAE,GAAW,EAAE,CAAC,SAAS;KACjC;IAED;;;;OAIG;IACH,IAAI,EAAE,CAAC,EAAU,EAAc,EAAE,CAAC,CAAC;QACjC,EAAE,EAAE,gBAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;QACnB,EAAE,EAAE,gBAAQ,CAAC,EAAE,CAAC,OAAO,EAAE;KAC1B,CAAC;CACH,CAAC"}
|
package/lib/middleware/auth.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* Authentication middleware for Lambda handlers.
|
|
4
4
|
*
|
|
5
5
|
* @packageDocumentation
|
|
6
6
|
*/
|
|
7
|
-
Object.defineProperty(exports,
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
8
|
exports.withAuth = withAuth;
|
|
9
|
-
const response_1 = require(
|
|
9
|
+
const response_1 = require("../lib/response");
|
|
10
10
|
/**
|
|
11
11
|
* Middleware that validates JWT tokens from Cognito.
|
|
12
12
|
*
|
|
@@ -37,26 +37,26 @@ const response_1 = require('../lib/response');
|
|
|
37
37
|
* ```
|
|
38
38
|
*/
|
|
39
39
|
function withAuth(options = {}) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
40
|
+
const { roles, skipPaths = [] } = options;
|
|
41
|
+
return (handler) => async (event, context) => {
|
|
42
|
+
// Skip auth for certain paths
|
|
43
|
+
if (skipPaths.some((path) => event.path?.includes(path))) {
|
|
44
|
+
return handler(event, context);
|
|
45
|
+
}
|
|
46
|
+
// Get claims from API Gateway authorizer
|
|
47
|
+
const claims = event.requestContext?.authorizer?.claims;
|
|
48
|
+
if (!claims || !claims.sub) {
|
|
49
|
+
return (0, response_1.unauthorized)('Missing or invalid authentication');
|
|
50
|
+
}
|
|
51
|
+
// Check role requirements
|
|
52
|
+
if (roles && roles.length > 0) {
|
|
53
|
+
const userGroups = claims['cognito:groups'] || [];
|
|
54
|
+
const hasRequiredRole = roles.some((role) => userGroups.includes(role));
|
|
55
|
+
if (!hasRequiredRole) {
|
|
56
|
+
return (0, response_1.forbidden)(`Required role: ${roles.join(' or ')}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return handler(event, context);
|
|
60
|
+
};
|
|
61
61
|
}
|
|
62
|
-
//# sourceMappingURL=auth.js.map
|
|
62
|
+
//# sourceMappingURL=auth.js.map
|
package/lib/middleware/cors.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* CORS middleware for Lambda handlers.
|
|
4
4
|
*
|
|
5
5
|
* @packageDocumentation
|
|
6
6
|
*/
|
|
7
|
-
Object.defineProperty(exports,
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
8
|
exports.withCors = withCors;
|
|
9
9
|
/**
|
|
10
10
|
* Middleware that adds CORS headers to responses.
|
|
@@ -25,38 +25,33 @@ exports.withCors = withCors;
|
|
|
25
25
|
* ```
|
|
26
26
|
*/
|
|
27
27
|
function withCors(options = {}) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
'Access-Control-Allow-Methods': methods.join(','),
|
|
37
|
-
'Access-Control-Allow-Headers': headers.join(','),
|
|
38
|
-
};
|
|
39
|
-
if (credentials) {
|
|
40
|
-
corsHeaders['Access-Control-Allow-Credentials'] = 'true';
|
|
41
|
-
}
|
|
42
|
-
return (handler) => async (event, context) => {
|
|
43
|
-
// Handle preflight requests
|
|
44
|
-
if (event.httpMethod === 'OPTIONS') {
|
|
45
|
-
return {
|
|
46
|
-
statusCode: 204,
|
|
47
|
-
headers: corsHeaders,
|
|
48
|
-
body: '',
|
|
49
|
-
};
|
|
28
|
+
const { origin = '*', methods = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], headers = ['Content-Type', 'Authorization'], credentials = false, } = options;
|
|
29
|
+
const corsHeaders = {
|
|
30
|
+
'Access-Control-Allow-Origin': Array.isArray(origin) ? origin[0] : origin,
|
|
31
|
+
'Access-Control-Allow-Methods': methods.join(','),
|
|
32
|
+
'Access-Control-Allow-Headers': headers.join(','),
|
|
33
|
+
};
|
|
34
|
+
if (credentials) {
|
|
35
|
+
corsHeaders['Access-Control-Allow-Credentials'] = 'true';
|
|
50
36
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
37
|
+
return (handler) => async (event, context) => {
|
|
38
|
+
// Handle preflight requests
|
|
39
|
+
if (event.httpMethod === 'OPTIONS') {
|
|
40
|
+
return {
|
|
41
|
+
statusCode: 204,
|
|
42
|
+
headers: corsHeaders,
|
|
43
|
+
body: '',
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const response = await handler(event, context);
|
|
47
|
+
// Merge CORS headers with response headers
|
|
48
|
+
return {
|
|
49
|
+
...response,
|
|
50
|
+
headers: {
|
|
51
|
+
...response.headers,
|
|
52
|
+
...corsHeaders,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
59
55
|
};
|
|
60
|
-
};
|
|
61
56
|
}
|
|
62
|
-
//# sourceMappingURL=cors.js.map
|
|
57
|
+
//# sourceMappingURL=cors.js.map
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* Error handling middleware for Lambda handlers.
|
|
4
4
|
*
|
|
5
5
|
* @packageDocumentation
|
|
6
6
|
*/
|
|
7
|
-
Object.defineProperty(exports,
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
8
|
exports.withErrorHandler = withErrorHandler;
|
|
9
|
-
const response_1 = require(
|
|
10
|
-
const errors_1 = require(
|
|
9
|
+
const response_1 = require("../lib/response");
|
|
10
|
+
const errors_1 = require("../lib/errors");
|
|
11
11
|
/**
|
|
12
12
|
* Middleware that catches errors and returns appropriate error responses.
|
|
13
13
|
*
|
|
@@ -29,21 +29,22 @@ const errors_1 = require('../lib/errors');
|
|
|
29
29
|
* ```
|
|
30
30
|
*/
|
|
31
31
|
function withErrorHandler(options = {}) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
32
|
+
const { logErrors = true } = options;
|
|
33
|
+
return (handler) => async (event, context) => {
|
|
34
|
+
try {
|
|
35
|
+
return await handler(event, context);
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
if (logErrors) {
|
|
39
|
+
console.error('Handler error:', err);
|
|
40
|
+
}
|
|
41
|
+
if (err instanceof errors_1.ApiError) {
|
|
42
|
+
return (0, response_1.error)(err, err.statusCode);
|
|
43
|
+
}
|
|
44
|
+
// Unknown errors become 500
|
|
45
|
+
const message = err instanceof Error ? err.message : 'Internal server error';
|
|
46
|
+
return (0, response_1.error)(message, 500);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
48
49
|
}
|
|
49
|
-
//# sourceMappingURL=error-handler.js.map
|
|
50
|
+
//# sourceMappingURL=error-handler.js.map
|