@canva/cli 1.13.0 → 1.15.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 (54) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +1 -0
  3. package/cli.js +353 -353
  4. package/package.json +2 -2
  5. package/templates/base/backend/base_backend/create.ts +10 -0
  6. package/templates/base/backend/routers/auth.ts +12 -9
  7. package/templates/base/package.json +4 -4
  8. package/templates/base/tsconfig.json +0 -1
  9. package/templates/base/webpack.config.ts +0 -5
  10. package/templates/content_publisher/README.md +58 -0
  11. package/templates/content_publisher/canva-app.json +17 -0
  12. package/templates/content_publisher/declarations/declarations.d.ts +29 -0
  13. package/templates/content_publisher/eslint.config.mjs +14 -0
  14. package/templates/content_publisher/package.json +90 -0
  15. package/templates/content_publisher/scripts/copy_env.ts +13 -0
  16. package/templates/content_publisher/scripts/ssl/ssl.ts +131 -0
  17. package/templates/content_publisher/scripts/start/app_runner.ts +223 -0
  18. package/templates/content_publisher/scripts/start/context.ts +171 -0
  19. package/templates/content_publisher/scripts/start/start.ts +46 -0
  20. package/templates/content_publisher/src/index.tsx +4 -0
  21. package/templates/content_publisher/src/intents/content_publisher/index.tsx +113 -0
  22. package/templates/content_publisher/src/intents/content_publisher/post_preview.tsx +226 -0
  23. package/templates/content_publisher/src/intents/content_publisher/preview_ui.tsx +53 -0
  24. package/templates/content_publisher/src/intents/content_publisher/settings_ui.tsx +71 -0
  25. package/templates/content_publisher/src/intents/content_publisher/types.ts +29 -0
  26. package/templates/content_publisher/styles/components.css +56 -0
  27. package/templates/content_publisher/styles/preview_ui.css +88 -0
  28. package/templates/content_publisher/tsconfig.json +56 -0
  29. package/templates/content_publisher/webpack.config.ts +247 -0
  30. package/templates/dam/backend/server.ts +2 -3
  31. package/templates/dam/package.json +6 -5
  32. package/templates/dam/tsconfig.json +0 -1
  33. package/templates/dam/utils/backend/base_backend/create.ts +10 -0
  34. package/templates/dam/webpack.config.ts +0 -5
  35. package/templates/data_connector/package.json +5 -5
  36. package/templates/data_connector/tsconfig.json +0 -1
  37. package/templates/data_connector/webpack.config.ts +0 -5
  38. package/templates/gen_ai/backend/server.ts +2 -3
  39. package/templates/gen_ai/package.json +6 -5
  40. package/templates/gen_ai/tsconfig.json +0 -1
  41. package/templates/gen_ai/utils/backend/base_backend/create.ts +10 -0
  42. package/templates/gen_ai/webpack.config.ts +0 -5
  43. package/templates/hello_world/package.json +4 -4
  44. package/templates/hello_world/tsconfig.json +0 -1
  45. package/templates/hello_world/webpack.config.ts +0 -5
  46. package/templates/mls/package.json +5 -5
  47. package/templates/mls/tsconfig.json +0 -1
  48. package/templates/mls/webpack.config.ts +0 -5
  49. package/templates/base/backend/jwt_middleware/index.ts +0 -1
  50. package/templates/base/backend/jwt_middleware/jwt_middleware.ts +0 -224
  51. package/templates/dam/utils/backend/jwt_middleware/index.ts +0 -1
  52. package/templates/dam/utils/backend/jwt_middleware/jwt_middleware.ts +0 -224
  53. package/templates/gen_ai/utils/backend/jwt_middleware/index.ts +0 -1
  54. package/templates/gen_ai/utils/backend/jwt_middleware/jwt_middleware.ts +0 -224
@@ -1,224 +0,0 @@
1
- /* eslint-disable no-console */
2
- import debug from "debug";
3
- import type { NextFunction, Request, Response } from "express";
4
- import jwt from "jsonwebtoken";
5
- import { JwksClient, SigningKeyNotFoundError } from "jwks-rsa";
6
-
7
- /**
8
- * Prefix your start command with `DEBUG=express:middleware:jwt` to enable debug logging
9
- * for this middleware
10
- */
11
- const debugLogger = debug("express:middleware:jwt");
12
-
13
- const CANVA_BASE_URL = "https://api.canva.com";
14
-
15
- /**
16
- * Augment the Express request context to include the appId/userId/brandId fields decoded
17
- * from the JWT.
18
- */
19
- declare module "express-serve-static-core" {
20
- export interface Request {
21
- canva: {
22
- appId: string;
23
- userId: string;
24
- brandId: string;
25
- };
26
- }
27
- }
28
-
29
- type CanvaJwt = Omit<jwt.Jwt, "payload"> & {
30
- payload: {
31
- aud?: string;
32
- userId?: string;
33
- brandId?: string;
34
- };
35
- };
36
-
37
- const PUBLIC_KEY_DEFAULT_EXPIRY_MS = 60 * 60 * 1_000; // 60 minutes
38
- const PUBLIC_KEY_DEFAULT_FETCH_TIMEOUT_MS = 30 * 1_000; // 30 seconds
39
-
40
- const sendUnauthorizedResponse = (res: Response, message?: string) =>
41
- res.status(401).json({ error: "unauthorized", message });
42
-
43
- const createJwksUrl = (appId: string) =>
44
- `${CANVA_BASE_URL}/rest/v1/apps/${appId}/jwks`;
45
-
46
- /**
47
- * An Express.js middleware for decoding and verifying a JSON Web Token (JWT).
48
- * By default, this middleware extracts the token from the `Authorization` header.
49
- *
50
- * @remarks
51
- * If a JWT is successfully decoded, the following properties are added to the request object:
52
- * - `request.canva.appId` - The ID of the app.
53
- * - `request.canva.brandId` - The ID of the user's team.
54
- * - `request.canva.userId` - The ID of the user.
55
- *
56
- * @param appId - The ID of the app.
57
- * @param getTokenFromRequest - A function that extracts a token from the request. If a token isn't found, throw a `JWTAuthorizationError`.
58
- * @returns An Express.js middleware for verifying and decoding JWTs.
59
- */
60
- export function createJwtMiddleware(
61
- appId: string,
62
- getTokenFromRequest: GetTokenFromRequest = getTokenFromHttpHeader,
63
- ): (req: Request, res: Response, next: NextFunction) => void {
64
- const jwksClient = new JwksClient({
65
- cache: true,
66
- cacheMaxAge: PUBLIC_KEY_DEFAULT_EXPIRY_MS,
67
- timeout: PUBLIC_KEY_DEFAULT_FETCH_TIMEOUT_MS,
68
- rateLimit: true,
69
- jwksUri: createJwksUrl(appId),
70
- });
71
-
72
- return async (req, res, next) => {
73
- try {
74
- debugLogger(`processing JWT for '${req.url}'`);
75
-
76
- const token = await getTokenFromRequest(req);
77
- const unverifiedDecodedToken = jwt.decode(token, {
78
- complete: true,
79
- });
80
-
81
- if (unverifiedDecodedToken?.header?.kid == null) {
82
- console.trace(
83
- `jwtMiddleware: expected token to contain 'kid' claim header`,
84
- );
85
- return sendUnauthorizedResponse(res);
86
- }
87
-
88
- const key = await jwksClient.getSigningKey(
89
- unverifiedDecodedToken.header.kid,
90
- );
91
- const publicKey = key.getPublicKey();
92
- const verifiedToken = jwt.verify(token, publicKey, {
93
- audience: appId,
94
- complete: true,
95
- }) as CanvaJwt;
96
- const { payload } = verifiedToken;
97
- debugLogger("payload: %O", payload);
98
-
99
- if (
100
- payload.userId == null ||
101
- payload.brandId == null ||
102
- payload.aud == null
103
- ) {
104
- console.trace(
105
- "jwtMiddleware: failed to decode jwt missing fields from payload",
106
- );
107
- return sendUnauthorizedResponse(res);
108
- }
109
-
110
- req["canva"] = {
111
- appId: payload.aud,
112
- brandId: payload.brandId,
113
- userId: payload.userId,
114
- };
115
-
116
- return next();
117
- } catch (e) {
118
- if (e instanceof JWTAuthorizationError) {
119
- return sendUnauthorizedResponse(res, e.message);
120
- }
121
-
122
- if (e instanceof SigningKeyNotFoundError) {
123
- return sendUnauthorizedResponse(
124
- res,
125
- `Public key not found. Ensure you have the correct App_ID set`,
126
- );
127
- }
128
-
129
- if (e instanceof jwt.JsonWebTokenError) {
130
- return sendUnauthorizedResponse(res, "Token is invalid");
131
- }
132
-
133
- if (e instanceof jwt.TokenExpiredError) {
134
- return sendUnauthorizedResponse(res, "Token expired");
135
- }
136
-
137
- return next(e);
138
- }
139
- };
140
- }
141
-
142
- export type GetTokenFromRequest = (req: Request) => Promise<string> | string;
143
-
144
- export const getTokenFromQueryString: GetTokenFromRequest = (
145
- req: Request,
146
- ): string => {
147
- // The name of a query string parameter bearing the JWT
148
- const tokenQueryStringParamName = "canva_user_token";
149
-
150
- const queryParam = req.query[tokenQueryStringParamName];
151
- if (!queryParam || typeof queryParam !== "string") {
152
- console.trace(
153
- `jwtMiddleware: missing "${tokenQueryStringParamName}" query parameter`,
154
- );
155
- throw new JWTAuthorizationError(
156
- `Missing "${tokenQueryStringParamName}" query parameter`,
157
- );
158
- }
159
-
160
- if (!looksLikeJWT(queryParam)) {
161
- console.trace(
162
- `jwtMiddleware: invalid "${tokenQueryStringParamName}" query parameter`,
163
- );
164
- throw new JWTAuthorizationError(
165
- `Invalid "${tokenQueryStringParamName}" query parameter`,
166
- );
167
- }
168
-
169
- return queryParam;
170
- };
171
-
172
- export const getTokenFromHttpHeader: GetTokenFromRequest = (
173
- req: Request,
174
- ): string => {
175
- // The names of a HTTP header bearing the JWT, and a scheme
176
- const headerName = "Authorization";
177
- const schemeName = "Bearer";
178
-
179
- const header = req.header(headerName);
180
- if (!header) {
181
- throw new JWTAuthorizationError(`Missing the "${headerName}" header`);
182
- }
183
-
184
- if (!header.match(new RegExp(`^${schemeName}\\s+[^\\s]+$`, "i"))) {
185
- console.trace(
186
- `jwtMiddleware: failed to match token in "${headerName}" header`,
187
- );
188
- throw new JWTAuthorizationError(
189
- `Missing a "${schemeName}" token in the "${headerName}" header`,
190
- );
191
- }
192
-
193
- const token = header.replace(new RegExp(`^${schemeName}\\s+`, "i"), "");
194
- if (!token || !looksLikeJWT(token)) {
195
- throw new JWTAuthorizationError(
196
- `Invalid "${schemeName}" token in the "${headerName}" header`,
197
- );
198
- }
199
-
200
- return token;
201
- };
202
-
203
- /**
204
- * A class representing JWT validation errors in the JWT middleware.
205
- * The error message provided to the constructor will be forwarded to the
206
- * API consumer trying to access a JWT-protected endpoint.
207
- * @private
208
- */
209
- export class JWTAuthorizationError extends Error {
210
- constructor(message: string) {
211
- super(message);
212
-
213
- Object.setPrototypeOf(this, JWTAuthorizationError.prototype);
214
- }
215
- }
216
-
217
- const looksLikeJWT = (
218
- token: string,
219
- ): boolean => // Base64 alphabet includes
220
- // - letters (a-z and A-Z)
221
- // - digits (0-9)
222
- // - two special characters (+/ or -_)
223
- // - padding (=)
224
- token.match(/^[a-z0-9+/\-_=.]+$/i) != null;
@@ -1 +0,0 @@
1
- export { createJwtMiddleware } from "./jwt_middleware";
@@ -1,224 +0,0 @@
1
- /* eslint-disable no-console */
2
- import debug from "debug";
3
- import type { NextFunction, Request, Response } from "express";
4
- import jwt from "jsonwebtoken";
5
- import { JwksClient, SigningKeyNotFoundError } from "jwks-rsa";
6
-
7
- /**
8
- * Prefix your start command with `DEBUG=express:middleware:jwt` to enable debug logging
9
- * for this middleware
10
- */
11
- const debugLogger = debug("express:middleware:jwt");
12
-
13
- const CANVA_BASE_URL = "https://api.canva.com";
14
-
15
- /**
16
- * Augment the Express request context to include the appId/userId/brandId fields decoded
17
- * from the JWT.
18
- */
19
- declare module "express-serve-static-core" {
20
- export interface Request {
21
- canva: {
22
- appId: string;
23
- userId: string;
24
- brandId: string;
25
- };
26
- }
27
- }
28
-
29
- type CanvaJwt = Omit<jwt.Jwt, "payload"> & {
30
- payload: {
31
- aud?: string;
32
- userId?: string;
33
- brandId?: string;
34
- };
35
- };
36
-
37
- const PUBLIC_KEY_DEFAULT_EXPIRY_MS = 60 * 60 * 1_000; // 60 minutes
38
- const PUBLIC_KEY_DEFAULT_FETCH_TIMEOUT_MS = 30 * 1_000; // 30 seconds
39
-
40
- const sendUnauthorizedResponse = (res: Response, message?: string) =>
41
- res.status(401).json({ error: "unauthorized", message });
42
-
43
- const createJwksUrl = (appId: string) =>
44
- `${CANVA_BASE_URL}/rest/v1/apps/${appId}/jwks`;
45
-
46
- /**
47
- * An Express.js middleware for decoding and verifying a JSON Web Token (JWT).
48
- * By default, this middleware extracts the token from the `Authorization` header.
49
- *
50
- * @remarks
51
- * If a JWT is successfully decoded, the following properties are added to the request object:
52
- * - `request.canva.appId` - The ID of the app.
53
- * - `request.canva.brandId` - The ID of the user's team.
54
- * - `request.canva.userId` - The ID of the user.
55
- *
56
- * @param appId - The ID of the app.
57
- * @param getTokenFromRequest - A function that extracts a token from the request. If a token isn't found, throw a `JWTAuthorizationError`.
58
- * @returns An Express.js middleware for verifying and decoding JWTs.
59
- */
60
- export function createJwtMiddleware(
61
- appId: string,
62
- getTokenFromRequest: GetTokenFromRequest = getTokenFromHttpHeader,
63
- ): (req: Request, res: Response, next: NextFunction) => void {
64
- const jwksClient = new JwksClient({
65
- cache: true,
66
- cacheMaxAge: PUBLIC_KEY_DEFAULT_EXPIRY_MS,
67
- timeout: PUBLIC_KEY_DEFAULT_FETCH_TIMEOUT_MS,
68
- rateLimit: true,
69
- jwksUri: createJwksUrl(appId),
70
- });
71
-
72
- return async (req, res, next) => {
73
- try {
74
- debugLogger(`processing JWT for '${req.url}'`);
75
-
76
- const token = await getTokenFromRequest(req);
77
- const unverifiedDecodedToken = jwt.decode(token, {
78
- complete: true,
79
- });
80
-
81
- if (unverifiedDecodedToken?.header?.kid == null) {
82
- console.trace(
83
- `jwtMiddleware: expected token to contain 'kid' claim header`,
84
- );
85
- return sendUnauthorizedResponse(res);
86
- }
87
-
88
- const key = await jwksClient.getSigningKey(
89
- unverifiedDecodedToken.header.kid,
90
- );
91
- const publicKey = key.getPublicKey();
92
- const verifiedToken = jwt.verify(token, publicKey, {
93
- audience: appId,
94
- complete: true,
95
- }) as CanvaJwt;
96
- const { payload } = verifiedToken;
97
- debugLogger("payload: %O", payload);
98
-
99
- if (
100
- payload.userId == null ||
101
- payload.brandId == null ||
102
- payload.aud == null
103
- ) {
104
- console.trace(
105
- "jwtMiddleware: failed to decode jwt missing fields from payload",
106
- );
107
- return sendUnauthorizedResponse(res);
108
- }
109
-
110
- req["canva"] = {
111
- appId: payload.aud,
112
- brandId: payload.brandId,
113
- userId: payload.userId,
114
- };
115
-
116
- return next();
117
- } catch (e) {
118
- if (e instanceof JWTAuthorizationError) {
119
- return sendUnauthorizedResponse(res, e.message);
120
- }
121
-
122
- if (e instanceof SigningKeyNotFoundError) {
123
- return sendUnauthorizedResponse(
124
- res,
125
- `Public key not found. Ensure you have the correct App_ID set`,
126
- );
127
- }
128
-
129
- if (e instanceof jwt.JsonWebTokenError) {
130
- return sendUnauthorizedResponse(res, "Token is invalid");
131
- }
132
-
133
- if (e instanceof jwt.TokenExpiredError) {
134
- return sendUnauthorizedResponse(res, "Token expired");
135
- }
136
-
137
- return next(e);
138
- }
139
- };
140
- }
141
-
142
- export type GetTokenFromRequest = (req: Request) => Promise<string> | string;
143
-
144
- export const getTokenFromQueryString: GetTokenFromRequest = (
145
- req: Request,
146
- ): string => {
147
- // The name of a query string parameter bearing the JWT
148
- const tokenQueryStringParamName = "canva_user_token";
149
-
150
- const queryParam = req.query[tokenQueryStringParamName];
151
- if (!queryParam || typeof queryParam !== "string") {
152
- console.trace(
153
- `jwtMiddleware: missing "${tokenQueryStringParamName}" query parameter`,
154
- );
155
- throw new JWTAuthorizationError(
156
- `Missing "${tokenQueryStringParamName}" query parameter`,
157
- );
158
- }
159
-
160
- if (!looksLikeJWT(queryParam)) {
161
- console.trace(
162
- `jwtMiddleware: invalid "${tokenQueryStringParamName}" query parameter`,
163
- );
164
- throw new JWTAuthorizationError(
165
- `Invalid "${tokenQueryStringParamName}" query parameter`,
166
- );
167
- }
168
-
169
- return queryParam;
170
- };
171
-
172
- export const getTokenFromHttpHeader: GetTokenFromRequest = (
173
- req: Request,
174
- ): string => {
175
- // The names of a HTTP header bearing the JWT, and a scheme
176
- const headerName = "Authorization";
177
- const schemeName = "Bearer";
178
-
179
- const header = req.header(headerName);
180
- if (!header) {
181
- throw new JWTAuthorizationError(`Missing the "${headerName}" header`);
182
- }
183
-
184
- if (!header.match(new RegExp(`^${schemeName}\\s+[^\\s]+$`, "i"))) {
185
- console.trace(
186
- `jwtMiddleware: failed to match token in "${headerName}" header`,
187
- );
188
- throw new JWTAuthorizationError(
189
- `Missing a "${schemeName}" token in the "${headerName}" header`,
190
- );
191
- }
192
-
193
- const token = header.replace(new RegExp(`^${schemeName}\\s+`, "i"), "");
194
- if (!token || !looksLikeJWT(token)) {
195
- throw new JWTAuthorizationError(
196
- `Invalid "${schemeName}" token in the "${headerName}" header`,
197
- );
198
- }
199
-
200
- return token;
201
- };
202
-
203
- /**
204
- * A class representing JWT validation errors in the JWT middleware.
205
- * The error message provided to the constructor will be forwarded to the
206
- * API consumer trying to access a JWT-protected endpoint.
207
- * @private
208
- */
209
- export class JWTAuthorizationError extends Error {
210
- constructor(message: string) {
211
- super(message);
212
-
213
- Object.setPrototypeOf(this, JWTAuthorizationError.prototype);
214
- }
215
- }
216
-
217
- const looksLikeJWT = (
218
- token: string,
219
- ): boolean => // Base64 alphabet includes
220
- // - letters (a-z and A-Z)
221
- // - digits (0-9)
222
- // - two special characters (+/ or -_)
223
- // - padding (=)
224
- token.match(/^[a-z0-9+/\-_=.]+$/i) != null;
@@ -1 +0,0 @@
1
- export { createJwtMiddleware } from "./jwt_middleware";
@@ -1,224 +0,0 @@
1
- /* eslint-disable no-console */
2
- import debug from "debug";
3
- import type { NextFunction, Request, Response } from "express";
4
- import jwt from "jsonwebtoken";
5
- import { JwksClient, SigningKeyNotFoundError } from "jwks-rsa";
6
-
7
- /**
8
- * Prefix your start command with `DEBUG=express:middleware:jwt` to enable debug logging
9
- * for this middleware
10
- */
11
- const debugLogger = debug("express:middleware:jwt");
12
-
13
- const CANVA_BASE_URL = "https://api.canva.com";
14
-
15
- /**
16
- * Augment the Express request context to include the appId/userId/brandId fields decoded
17
- * from the JWT.
18
- */
19
- declare module "express-serve-static-core" {
20
- export interface Request {
21
- canva: {
22
- appId: string;
23
- userId: string;
24
- brandId: string;
25
- };
26
- }
27
- }
28
-
29
- type CanvaJwt = Omit<jwt.Jwt, "payload"> & {
30
- payload: {
31
- aud?: string;
32
- userId?: string;
33
- brandId?: string;
34
- };
35
- };
36
-
37
- const PUBLIC_KEY_DEFAULT_EXPIRY_MS = 60 * 60 * 1_000; // 60 minutes
38
- const PUBLIC_KEY_DEFAULT_FETCH_TIMEOUT_MS = 30 * 1_000; // 30 seconds
39
-
40
- const sendUnauthorizedResponse = (res: Response, message?: string) =>
41
- res.status(401).json({ error: "unauthorized", message });
42
-
43
- const createJwksUrl = (appId: string) =>
44
- `${CANVA_BASE_URL}/rest/v1/apps/${appId}/jwks`;
45
-
46
- /**
47
- * An Express.js middleware for decoding and verifying a JSON Web Token (JWT).
48
- * By default, this middleware extracts the token from the `Authorization` header.
49
- *
50
- * @remarks
51
- * If a JWT is successfully decoded, the following properties are added to the request object:
52
- * - `request.canva.appId` - The ID of the app.
53
- * - `request.canva.brandId` - The ID of the user's team.
54
- * - `request.canva.userId` - The ID of the user.
55
- *
56
- * @param appId - The ID of the app.
57
- * @param getTokenFromRequest - A function that extracts a token from the request. If a token isn't found, throw a `JWTAuthorizationError`.
58
- * @returns An Express.js middleware for verifying and decoding JWTs.
59
- */
60
- export function createJwtMiddleware(
61
- appId: string,
62
- getTokenFromRequest: GetTokenFromRequest = getTokenFromHttpHeader,
63
- ): (req: Request, res: Response, next: NextFunction) => void {
64
- const jwksClient = new JwksClient({
65
- cache: true,
66
- cacheMaxAge: PUBLIC_KEY_DEFAULT_EXPIRY_MS,
67
- timeout: PUBLIC_KEY_DEFAULT_FETCH_TIMEOUT_MS,
68
- rateLimit: true,
69
- jwksUri: createJwksUrl(appId),
70
- });
71
-
72
- return async (req, res, next) => {
73
- try {
74
- debugLogger(`processing JWT for '${req.url}'`);
75
-
76
- const token = await getTokenFromRequest(req);
77
- const unverifiedDecodedToken = jwt.decode(token, {
78
- complete: true,
79
- });
80
-
81
- if (unverifiedDecodedToken?.header?.kid == null) {
82
- console.trace(
83
- `jwtMiddleware: expected token to contain 'kid' claim header`,
84
- );
85
- return sendUnauthorizedResponse(res);
86
- }
87
-
88
- const key = await jwksClient.getSigningKey(
89
- unverifiedDecodedToken.header.kid,
90
- );
91
- const publicKey = key.getPublicKey();
92
- const verifiedToken = jwt.verify(token, publicKey, {
93
- audience: appId,
94
- complete: true,
95
- }) as CanvaJwt;
96
- const { payload } = verifiedToken;
97
- debugLogger("payload: %O", payload);
98
-
99
- if (
100
- payload.userId == null ||
101
- payload.brandId == null ||
102
- payload.aud == null
103
- ) {
104
- console.trace(
105
- "jwtMiddleware: failed to decode jwt missing fields from payload",
106
- );
107
- return sendUnauthorizedResponse(res);
108
- }
109
-
110
- req["canva"] = {
111
- appId: payload.aud,
112
- brandId: payload.brandId,
113
- userId: payload.userId,
114
- };
115
-
116
- return next();
117
- } catch (e) {
118
- if (e instanceof JWTAuthorizationError) {
119
- return sendUnauthorizedResponse(res, e.message);
120
- }
121
-
122
- if (e instanceof SigningKeyNotFoundError) {
123
- return sendUnauthorizedResponse(
124
- res,
125
- `Public key not found. Ensure you have the correct App_ID set`,
126
- );
127
- }
128
-
129
- if (e instanceof jwt.JsonWebTokenError) {
130
- return sendUnauthorizedResponse(res, "Token is invalid");
131
- }
132
-
133
- if (e instanceof jwt.TokenExpiredError) {
134
- return sendUnauthorizedResponse(res, "Token expired");
135
- }
136
-
137
- return next(e);
138
- }
139
- };
140
- }
141
-
142
- export type GetTokenFromRequest = (req: Request) => Promise<string> | string;
143
-
144
- export const getTokenFromQueryString: GetTokenFromRequest = (
145
- req: Request,
146
- ): string => {
147
- // The name of a query string parameter bearing the JWT
148
- const tokenQueryStringParamName = "canva_user_token";
149
-
150
- const queryParam = req.query[tokenQueryStringParamName];
151
- if (!queryParam || typeof queryParam !== "string") {
152
- console.trace(
153
- `jwtMiddleware: missing "${tokenQueryStringParamName}" query parameter`,
154
- );
155
- throw new JWTAuthorizationError(
156
- `Missing "${tokenQueryStringParamName}" query parameter`,
157
- );
158
- }
159
-
160
- if (!looksLikeJWT(queryParam)) {
161
- console.trace(
162
- `jwtMiddleware: invalid "${tokenQueryStringParamName}" query parameter`,
163
- );
164
- throw new JWTAuthorizationError(
165
- `Invalid "${tokenQueryStringParamName}" query parameter`,
166
- );
167
- }
168
-
169
- return queryParam;
170
- };
171
-
172
- export const getTokenFromHttpHeader: GetTokenFromRequest = (
173
- req: Request,
174
- ): string => {
175
- // The names of a HTTP header bearing the JWT, and a scheme
176
- const headerName = "Authorization";
177
- const schemeName = "Bearer";
178
-
179
- const header = req.header(headerName);
180
- if (!header) {
181
- throw new JWTAuthorizationError(`Missing the "${headerName}" header`);
182
- }
183
-
184
- if (!header.match(new RegExp(`^${schemeName}\\s+[^\\s]+$`, "i"))) {
185
- console.trace(
186
- `jwtMiddleware: failed to match token in "${headerName}" header`,
187
- );
188
- throw new JWTAuthorizationError(
189
- `Missing a "${schemeName}" token in the "${headerName}" header`,
190
- );
191
- }
192
-
193
- const token = header.replace(new RegExp(`^${schemeName}\\s+`, "i"), "");
194
- if (!token || !looksLikeJWT(token)) {
195
- throw new JWTAuthorizationError(
196
- `Invalid "${schemeName}" token in the "${headerName}" header`,
197
- );
198
- }
199
-
200
- return token;
201
- };
202
-
203
- /**
204
- * A class representing JWT validation errors in the JWT middleware.
205
- * The error message provided to the constructor will be forwarded to the
206
- * API consumer trying to access a JWT-protected endpoint.
207
- * @private
208
- */
209
- export class JWTAuthorizationError extends Error {
210
- constructor(message: string) {
211
- super(message);
212
-
213
- Object.setPrototypeOf(this, JWTAuthorizationError.prototype);
214
- }
215
- }
216
-
217
- const looksLikeJWT = (
218
- token: string,
219
- ): boolean => // Base64 alphabet includes
220
- // - letters (a-z and A-Z)
221
- // - digits (0-9)
222
- // - two special characters (+/ or -_)
223
- // - padding (=)
224
- token.match(/^[a-z0-9+/\-_=.]+$/i) != null;