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