@infoxchange/make-it-so 2.10.0-internal-testing-vdt-199-add-oidc-auth.3 → 2.10.0-internal-testing-vdt-199-add-oidc-auth.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.
@@ -1,2 +1,2 @@
1
- export {};
1
+ export declare const handler: (event: any, context: any) => Promise<APIGatewayProxyStructuredResultV2>;
2
2
  //# sourceMappingURL=auth-check.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth-check.d.ts","sourceRoot":"","sources":["../../../src/cdk-constructs/CloudWatchOidcAuth/auth-check.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"auth-check.d.ts","sourceRoot":"","sources":["../../../src/cdk-constructs/CloudWatchOidcAuth/auth-check.ts"],"names":[],"mappings":"AA+FA,eAAO,MAAM,OAAO,0EAiClB,CAAA"}
@@ -1,26 +1,18 @@
1
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
2
- // @ts-nocheck
3
1
  // Based off: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/example_cloudfront_functions_kvs_jwt_verify_section.html
4
2
  import crypto from "crypto";
5
- // @ts-expect-error -- This library only exists in the CloudFront Functions runtime that this code runs in
6
3
  import cf from "cloudfront";
4
+ import { ApiHandler, useCookie } from "sst/node/api";
7
5
  //Response when JWT is not valid.
8
- // const response401 = {
9
- // statusCode: 401,
10
- // statusDescription: 'Unauthorized'
11
- // };
12
- const response401 = {
6
+ const redirectResponse = {
13
7
  statusCode: 302,
14
8
  headers: {
15
9
  location: { value: "/auth/oidc/authorize" },
16
10
  },
17
11
  };
18
- // Remember to associate the KVS with your function before calling the const kvsKey = 'jwt.secret'.
19
- // https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/kvs-with-functions-associate.html
20
12
  const kvsKey = "__placeholder-for-jwt-secret-key__";
21
13
  // set to true to enable console logging
22
- const loggingEnabled = true; // false;
23
- function jwt_decode(token, key, noVerify, algorithm) {
14
+ const loggingEnabled = true;
15
+ function jwtDecode(token, key, noVerify) {
24
16
  // check token
25
17
  if (!token) {
26
18
  throw new Error("No token supplied");
@@ -36,22 +28,23 @@ function jwt_decode(token, key, noVerify, algorithm) {
36
28
  const signatureSeg = segments[2];
37
29
  // base64 decode and parse JSON
38
30
  const payload = JSON.parse(_base64urlDecode(payloadSeg));
39
- if (!noVerify) {
40
- const signingMethod = "sha256";
41
- const signingType = "hmac";
42
- // Verify signature. `sign` will return base64 string.
43
- const signingInput = [headerSeg, payloadSeg].join(".");
44
- if (!_verify(signingInput, key, signingMethod, signingType, signatureSeg)) {
45
- throw new Error("Signature verification failed");
46
- }
47
- // Support for nbf and exp claims.
48
- // According to the RFC, they should be in seconds.
49
- if (payload.nbf && Date.now() < payload.nbf * 1000) {
50
- throw new Error("Token not yet active");
51
- }
52
- if (payload.exp && Date.now() > payload.exp * 1000) {
53
- throw new Error("Token expired");
54
- }
31
+ if (noVerify) {
32
+ return payload;
33
+ }
34
+ const signingMethod = "sha256";
35
+ const signingType = "hmac";
36
+ // Verify signature. `sign` will return base64 string.
37
+ const signingInput = [headerSeg, payloadSeg].join(".");
38
+ if (!_verify(signingInput, key, signingMethod, signingType, signatureSeg)) {
39
+ throw new Error("Signature verification failed");
40
+ }
41
+ // Support for nbf and exp claims.
42
+ // According to the RFC, they should be in seconds.
43
+ if (payload.nbf && Date.now() < payload.nbf * 1000) {
44
+ throw new Error("Token not yet active");
45
+ }
46
+ if (payload.exp && Date.now() > payload.exp * 1000) {
47
+ throw new Error("Token expired");
55
48
  }
56
49
  return payload;
57
50
  }
@@ -79,46 +72,41 @@ function _sign(input, key, method) {
79
72
  return crypto.createHmac(method, key).update(input).digest("base64url");
80
73
  }
81
74
  function _base64urlDecode(str) {
82
- return Buffer.from(str, "base64url");
75
+ return Buffer.from(str, "base64url").toString();
83
76
  }
84
- async function handler(event) {
77
+ export const handler = ApiHandler(async (event) => {
78
+ console.log("Auth check event:", event);
85
79
  const request = event.request;
86
- //Secret key used to verify JWT token.
87
- //Update with your own key.
88
80
  const secret_key = await getSecret();
89
81
  if (!secret_key) {
90
- return response401;
91
- }
92
- console.log("request");
93
- console.log(request);
94
- console.log(request.cookies);
95
- console.log(request.cookies["auth-token"]);
96
- console.log(Object.keys(request.cookies));
97
- // console.logObject.keys(request.cookies))
82
+ return redirectResponse;
83
+ }
84
+ // console.log(request);
85
+ // console.log(request.cookies);
86
+ // console.log(request.cookies["auth-token"]);
87
+ // console.log(Object.keys(request.cookies));
88
+ const jwtToken = useCookie("auth-token");
89
+ console.log("jwtToken:", jwtToken);
90
+ // console.log(Object.keys(request.cookies));
98
91
  // If no JWT token, then generate HTTP redirect 401 response.
99
- if (!request.cookies["auth-token"]) {
92
+ if (!jwtToken) {
100
93
  log("Error: No JWT in the cookies");
101
- return response401;
94
+ return redirectResponse;
102
95
  }
103
- const jwtToken = request.cookies["auth-token"].value;
104
96
  try {
105
- jwt_decode(jwtToken, secret_key);
97
+ jwtDecode(jwtToken, secret_key);
106
98
  }
107
99
  catch (e) {
108
100
  log(e);
109
- return response401;
101
+ return redirectResponse;
110
102
  }
111
- //Remove the JWT from the query string if valid and return.
112
- delete request.querystring.jwt;
103
+ // //Remove the JWT from the query string if valid and return.
104
+ // delete request.querystring.jwt;
113
105
  log("Valid JWT token");
114
106
  return request;
115
- }
116
- const publicKey = `very-secret`;
117
- // get secret from key value store
107
+ });
108
+ // Get secret from key value store
118
109
  async function getSecret() {
119
- // console.log("auth key is:", publicKey)
120
- // return publicKey
121
- // initialize cloudfront kv store and get the key value
122
110
  try {
123
111
  const kvsHandle = cf.kvs();
124
112
  return await kvsHandle.get(kvsKey);
@@ -128,8 +116,8 @@ async function getSecret() {
128
116
  return null;
129
117
  }
130
118
  }
131
- function log(message) {
119
+ const log = (...args) => {
132
120
  if (loggingEnabled) {
133
- console.log(message);
121
+ console.log(...args);
134
122
  }
135
- }
123
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@infoxchange/make-it-so",
3
- "version": "2.10.0-internal-testing-vdt-199-add-oidc-auth.3",
3
+ "version": "2.10.0-internal-testing-vdt-199-add-oidc-auth.4",
4
4
  "description": "Makes deploying services to IX infra easy",
5
5
  "repository": "github:infoxchange/make-it-so",
6
6
  "type": "module",
@@ -51,6 +51,8 @@
51
51
  "sst": "^2.0.0"
52
52
  },
53
53
  "dependencies": {
54
+ "@aws-sdk/client-lambda": "^3.920.0",
55
+ "@types/aws-cloudfront-function": "^1.0.6",
54
56
  "jsonwebtoken": "^9.0.2",
55
57
  "zod": "^3.24.2"
56
58
  }
@@ -1,30 +1,22 @@
1
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
2
- // @ts-nocheck
3
1
  // Based off: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/example_cloudfront_functions_kvs_jwt_verify_section.html
4
2
 
5
3
  import crypto from "crypto";
6
- // @ts-expect-error -- This library only exists in the CloudFront Functions runtime that this code runs in
7
4
  import cf from "cloudfront";
5
+ import { ApiHandler, useCookie } from "sst/node/api";
8
6
 
9
7
  //Response when JWT is not valid.
10
- // const response401 = {
11
- // statusCode: 401,
12
- // statusDescription: 'Unauthorized'
13
- // };
14
- const response401 = {
8
+ const redirectResponse = {
15
9
  statusCode: 302,
16
10
  headers: {
17
11
  location: { value: "/auth/oidc/authorize" },
18
12
  },
19
13
  };
20
14
 
21
- // Remember to associate the KVS with your function before calling the const kvsKey = 'jwt.secret'.
22
- // https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/kvs-with-functions-associate.html
23
15
  const kvsKey = "__placeholder-for-jwt-secret-key__";
24
16
  // set to true to enable console logging
25
- const loggingEnabled = true; // false;
17
+ const loggingEnabled = true;
26
18
 
27
- function jwt_decode(token, key, noVerify, algorithm) {
19
+ function jwtDecode(token: string, key: string, noVerify?: boolean) {
28
20
  // check token
29
21
  if (!token) {
30
22
  throw new Error("No token supplied");
@@ -43,26 +35,28 @@ function jwt_decode(token, key, noVerify, algorithm) {
43
35
  // base64 decode and parse JSON
44
36
  const payload = JSON.parse(_base64urlDecode(payloadSeg));
45
37
 
46
- if (!noVerify) {
47
- const signingMethod = "sha256";
48
- const signingType = "hmac";
38
+ if (noVerify) {
39
+ return payload;
40
+ }
49
41
 
50
- // Verify signature. `sign` will return base64 string.
51
- const signingInput = [headerSeg, payloadSeg].join(".");
42
+ const signingMethod = "sha256";
43
+ const signingType = "hmac";
52
44
 
53
- if (!_verify(signingInput, key, signingMethod, signingType, signatureSeg)) {
54
- throw new Error("Signature verification failed");
55
- }
45
+ // Verify signature. `sign` will return base64 string.
46
+ const signingInput = [headerSeg, payloadSeg].join(".");
56
47
 
57
- // Support for nbf and exp claims.
58
- // According to the RFC, they should be in seconds.
59
- if (payload.nbf && Date.now() < payload.nbf * 1000) {
60
- throw new Error("Token not yet active");
61
- }
48
+ if (!_verify(signingInput, key, signingMethod, signingType, signatureSeg)) {
49
+ throw new Error("Signature verification failed");
50
+ }
62
51
 
63
- if (payload.exp && Date.now() > payload.exp * 1000) {
64
- throw new Error("Token expired");
65
- }
52
+ // Support for nbf and exp claims.
53
+ // According to the RFC, they should be in seconds.
54
+ if (payload.nbf && Date.now() < payload.nbf * 1000) {
55
+ throw new Error("Token not yet active");
56
+ }
57
+
58
+ if (payload.exp && Date.now() > payload.exp * 1000) {
59
+ throw new Error("Token expired");
66
60
  }
67
61
 
68
62
  return payload;
@@ -70,7 +64,7 @@ function jwt_decode(token, key, noVerify, algorithm) {
70
64
 
71
65
  //Function to ensure a constant time comparison to prevent
72
66
  //timing side channels.
73
- function _constantTimeEquals(a, b) {
67
+ function _constantTimeEquals(a: string, b: string) {
74
68
  if (a.length != b.length) {
75
69
  return false;
76
70
  }
@@ -83,7 +77,7 @@ function _constantTimeEquals(a, b) {
83
77
  return 0 === xor;
84
78
  }
85
79
 
86
- function _verify(input, key, method, type, signature) {
80
+ function _verify(input: string, key: string, method: string, type: string, signature: string) {
87
81
  if (type === "hmac") {
88
82
  return _constantTimeEquals(signature, _sign(input, key, method));
89
83
  } else {
@@ -91,60 +85,51 @@ function _verify(input, key, method, type, signature) {
91
85
  }
92
86
  }
93
87
 
94
- function _sign(input, key, method) {
88
+ function _sign(input: string, key: string, method: string) {
95
89
  return crypto.createHmac(method, key).update(input).digest("base64url");
96
90
  }
97
91
 
98
- function _base64urlDecode(str) {
99
- return Buffer.from(str, "base64url");
92
+ function _base64urlDecode(str: string) {
93
+ return Buffer.from(str, "base64url").toString();
100
94
  }
101
95
 
102
- async function handler(event) {
96
+ export const handler = ApiHandler(async (event) => {
97
+ console.log("Auth check event:", event);
103
98
  const request = event.request;
104
-
105
- //Secret key used to verify JWT token.
106
- //Update with your own key.
107
99
  const secret_key = await getSecret();
108
100
 
109
101
  if (!secret_key) {
110
- return response401;
102
+ return redirectResponse;
111
103
  }
112
104
 
113
- console.log("request");
114
- console.log(request);
115
- console.log(request.cookies);
116
- console.log(request.cookies["auth-token"]);
117
- console.log(Object.keys(request.cookies));
118
- // console.logObject.keys(request.cookies))
105
+ // console.log(request);
106
+ // console.log(request.cookies);
107
+ // console.log(request.cookies["auth-token"]);
108
+ // console.log(Object.keys(request.cookies));
109
+ const jwtToken = useCookie("auth-token");
110
+ console.log("jwtToken:", jwtToken);
111
+ // console.log(Object.keys(request.cookies));
119
112
 
120
113
  // If no JWT token, then generate HTTP redirect 401 response.
121
- if (!request.cookies["auth-token"]) {
114
+ if (!jwtToken) {
122
115
  log("Error: No JWT in the cookies");
123
- return response401;
116
+ return redirectResponse;
124
117
  }
125
-
126
- const jwtToken = request.cookies["auth-token"].value;
127
-
128
118
  try {
129
- jwt_decode(jwtToken, secret_key);
119
+ jwtDecode(jwtToken, secret_key);
130
120
  } catch (e) {
131
121
  log(e);
132
- return response401;
122
+ return redirectResponse;
133
123
  }
134
124
 
135
- //Remove the JWT from the query string if valid and return.
136
- delete request.querystring.jwt;
125
+ // //Remove the JWT from the query string if valid and return.
126
+ // delete request.querystring.jwt;
137
127
  log("Valid JWT token");
138
128
  return request;
139
- }
140
-
141
- const publicKey = `very-secret`;
129
+ })
142
130
 
143
- // get secret from key value store
131
+ // Get secret from key value store
144
132
  async function getSecret() {
145
- // console.log("auth key is:", publicKey)
146
- // return publicKey
147
- // initialize cloudfront kv store and get the key value
148
133
  try {
149
134
  const kvsHandle = cf.kvs();
150
135
  return await kvsHandle.get(kvsKey);
@@ -154,8 +139,8 @@ async function getSecret() {
154
139
  }
155
140
  }
156
141
 
157
- function log(message) {
142
+ const log: typeof console.log = (...args) => {
158
143
  if (loggingEnabled) {
159
- console.log(message);
144
+ console.log(...args);
160
145
  }
161
146
  }
@@ -0,0 +1,243 @@
1
+ // NOTE: once this is no longer needed we can remove typeRoots from tsconfig.json as well
2
+
3
+ // This is a copy of @types/aws-cloudfront-function but with optional kvsId in kvs() function. We can't just modify the
4
+ // kvs type since we can't modify the default export without messing up the rest of the types. Which is why we
5
+ // unfortunately have to duplicate the whole file here.
6
+ // But once https://github.com/DefinitelyTyped/DefinitelyTyped/issues/73959 is sorted we can get rid of this.
7
+
8
+ declare namespace AWSCloudFrontFunction {
9
+ interface Event {
10
+ version: "1.0";
11
+ context: Context;
12
+ viewer: Viewer;
13
+ request: Request;
14
+ response: Response;
15
+ }
16
+
17
+ interface Context {
18
+ distributionDomainName: string;
19
+ distributionId: string;
20
+ eventType: "viewer-request" | "viewer-response";
21
+ requestId: string;
22
+ }
23
+
24
+ interface Viewer {
25
+ ip: string;
26
+ }
27
+
28
+ interface Request {
29
+ method: string;
30
+ uri: string;
31
+ querystring: ValueObject;
32
+ headers: ValueObject;
33
+ cookies: ValueObject;
34
+ }
35
+
36
+ interface Response {
37
+ statusCode: number;
38
+ statusDescription?: string;
39
+ headers?: ValueObject;
40
+ cookies?: ResponseCookie;
41
+ body?: string | ResponseBody;
42
+ }
43
+
44
+ interface ResponseBody {
45
+ data: string;
46
+ encoding: "text" | "base64";
47
+ }
48
+
49
+ interface ValueObject {
50
+ [name: string]: {
51
+ value: string;
52
+ multiValue?: Array<{
53
+ value: string;
54
+ }>;
55
+ };
56
+ }
57
+
58
+ interface ResponseCookie {
59
+ [name: string]: {
60
+ value: string;
61
+ attributes: string;
62
+ multiValue?: Array<{
63
+ value: string;
64
+ attributes: string;
65
+ }>;
66
+ };
67
+ }
68
+ }
69
+
70
+ declare module "cloudfront" {
71
+ /**
72
+ * Retrieves a reference to a CloudFront Key-Value Store (KVS) by its ID.
73
+ * @param kvsId The identifier of the KVS to use.
74
+ * @see https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/functions-custom-methods.html
75
+ */
76
+ function kvs(kvsId?: string): KVStore;
77
+
78
+ interface KVStore {
79
+ /**
80
+ * Retrieve a value from the store.
81
+ * @param key Key to retrieve.
82
+ * @throws If key does not exist.
83
+ */
84
+ get(key: string): Promise<string>;
85
+ get(key: string, options: { format: "string" }): Promise<string>;
86
+ get(key: string, options: { format: "bytes" }): Promise<Uint8Array>;
87
+ get(key: string, options: { format: "json" }): Promise<unknown>;
88
+
89
+ /**
90
+ * Check if the key exists in the store.
91
+ * @param key Key to check.
92
+ */
93
+ exists(key: string): Promise<boolean>;
94
+
95
+ /**
96
+ * Retrieve metadata about the key-value store.
97
+ */
98
+ meta(): Promise<{
99
+ creationDateTime: string;
100
+ lastUpdatedDateTime: string;
101
+ keyCount: number;
102
+ }>;
103
+ }
104
+
105
+ interface OriginAccessControlConfig {
106
+ enabled: boolean;
107
+ signingBehavior: "always" | "never" | "no-override";
108
+ signingProtocol: "sigv4";
109
+ originType: "s3" | "mediapackagev2" | "mediastore" | "lambda";
110
+ }
111
+
112
+ interface OriginShield {
113
+ enabled: boolean;
114
+ region: string;
115
+ }
116
+
117
+ interface Timeouts {
118
+ /**
119
+ * Max time (seconds) to wait for a response or next packet. (1–60)
120
+ */
121
+ readTimeout?: number;
122
+
123
+ /**
124
+ * Max time (seconds) to keep the connection alive after response. (1–60)
125
+ */
126
+ keepAliveTimeout?: number;
127
+
128
+ /**
129
+ * Max time (seconds) to wait for connection establishment. (1–10)
130
+ */
131
+ connectionTimeout?: number;
132
+ }
133
+
134
+ interface CustomOriginConfig {
135
+ /**
136
+ * Port number of the origin. e.g., 80 or 443
137
+ */
138
+ port: number;
139
+
140
+ /**
141
+ * Protocol used to connect. Must be "http" or "https"
142
+ */
143
+ protocol: "http" | "https";
144
+
145
+ /**
146
+ * Minimum TLS/SSL version to use for HTTPS connections.
147
+ */
148
+ sslProtocols: Array<"SSLv3" | "TLSv1" | "TLSv1.1" | "TLSv1.2">;
149
+ }
150
+
151
+ interface UpdateRequestOriginParams {
152
+ /**
153
+ * New origin's domain name. Optional if reusing existing origin's domain.
154
+ */
155
+ domainName?: string;
156
+
157
+ /**
158
+ * Path prefix to append when forwarding request to origin.
159
+ */
160
+ originPath?: string;
161
+
162
+ /**
163
+ * Override or clear custom headers for the origin request.
164
+ */
165
+ customHeaders?: Record<string, string>;
166
+
167
+ /**
168
+ * Number of connection attempts (1–3).
169
+ */
170
+ connectionAttempts?: number;
171
+
172
+ /**
173
+ * Origin Shield configuration. Enables shield layer if specified.
174
+ */
175
+ originShield?: OriginShield;
176
+
177
+ /**
178
+ * Origin Access Control (OAC) configuration.
179
+ */
180
+ originAccessControlConfig?: OriginAccessControlConfig;
181
+
182
+ /**
183
+ * Response and connection timeout configurations.
184
+ */
185
+ timeouts?: Timeouts;
186
+
187
+ /**
188
+ * Settings for non-S3 origins or S3 with static website hosting.
189
+ */
190
+ customOriginConfig?: CustomOriginConfig;
191
+ }
192
+
193
+ /**
194
+ * Mutates the current request’s origin.
195
+ * You can specify a new origin (e.g., S3 or ALB), change custom headers, enable OAC, or enable Origin Shield.
196
+ * Missing fields will inherit values from the assigned origin.
197
+ * @see https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/helper-functions-origin-modification.html#update-request-origin-helper-function
198
+ */
199
+ function updateRequestOrigin(params: UpdateRequestOriginParams): void;
200
+
201
+ /**
202
+ * Switches to another origin already defined in the distribution by origin ID.
203
+ * This is more efficient than defining a new one via `updateRequestOrigin()`.
204
+ * @see https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/helper-functions-origin-modification.html#select-request-origin-id-helper-function
205
+ */
206
+ function selectRequestOriginById(originId: string): void;
207
+
208
+ interface CreateRequestOriginGroupParams {
209
+ /**
210
+ * Two origin IDs to form an origin group.
211
+ * The first is primary; the second is used for failover.
212
+ */
213
+ originIds: [string, string];
214
+
215
+ /**
216
+ * Failover selection strategy: default or media-quality-score.
217
+ */
218
+ selectionCriteria?: "default" | "media-quality-score";
219
+
220
+ /**
221
+ * List of status codes that trigger failover to the secondary origin.
222
+ */
223
+ failoverCriteria: {
224
+ statusCodes: number[];
225
+ };
226
+ }
227
+
228
+ /**
229
+ * Creates a new origin group for failover logic.
230
+ * The origin group can be referenced later via origin ID.
231
+ * @see https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/helper-functions-origin-modification.html#create-request-origin-group-helper-function
232
+ */
233
+ function createRequestOriginGroup(params: CreateRequestOriginGroupParams): void;
234
+
235
+ const cf: {
236
+ kvs: typeof kvs;
237
+ updateRequestOrigin: typeof updateRequestOrigin;
238
+ selectRequestOriginById: typeof selectRequestOriginById;
239
+ createRequestOriginGroup: typeof createRequestOriginGroup;
240
+ };
241
+
242
+ export default cf;
243
+ }