@canva/cli 1.19.0 → 1.21.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 (231) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +1 -9
  3. package/cli.js +625 -626
  4. package/package.json +2 -3
  5. package/templates/base/backend/base_backend/create.ts +0 -114
  6. package/templates/base/backend/database/database.ts +0 -42
  7. package/templates/base/backend/routers/auth.ts +0 -288
  8. package/templates/base/declarations/declarations.d.ts +0 -29
  9. package/templates/base/eslint.config.mjs +0 -14
  10. package/templates/base/package.json +0 -91
  11. package/templates/base/scripts/copy_env.ts +0 -13
  12. package/templates/base/scripts/ssl/ssl.ts +0 -131
  13. package/templates/base/scripts/start/app_runner.ts +0 -223
  14. package/templates/base/scripts/start/context.ts +0 -171
  15. package/templates/base/scripts/start/start.ts +0 -46
  16. package/templates/base/scripts/start.tests.ts +0 -61
  17. package/templates/base/styles/components.css +0 -38
  18. package/templates/base/tsconfig.json +0 -56
  19. package/templates/base/webpack.config.ts +0 -247
  20. package/templates/common/.env.template +0 -6
  21. package/templates/common/.gitignore.template +0 -8
  22. package/templates/common/.nvmrc +0 -1
  23. package/templates/common/.prettierrc +0 -21
  24. package/templates/common/LICENSE.md +0 -48
  25. package/templates/common/README.md +0 -179
  26. package/templates/common/jest.config.mjs +0 -35
  27. package/templates/common/jest.setup.ts +0 -35
  28. package/templates/content_publisher/README.md +0 -58
  29. package/templates/content_publisher/canva-app.json +0 -17
  30. package/templates/content_publisher/declarations/declarations.d.ts +0 -29
  31. package/templates/content_publisher/eslint.config.mjs +0 -14
  32. package/templates/content_publisher/package.json +0 -90
  33. package/templates/content_publisher/scripts/copy_env.ts +0 -13
  34. package/templates/content_publisher/scripts/ssl/ssl.ts +0 -131
  35. package/templates/content_publisher/scripts/start/app_runner.ts +0 -223
  36. package/templates/content_publisher/scripts/start/context.ts +0 -171
  37. package/templates/content_publisher/scripts/start/start.ts +0 -46
  38. package/templates/content_publisher/src/index.tsx +0 -4
  39. package/templates/content_publisher/src/intents/content_publisher/index.tsx +0 -107
  40. package/templates/content_publisher/src/intents/content_publisher/post_preview.tsx +0 -240
  41. package/templates/content_publisher/src/intents/content_publisher/preview_ui.tsx +0 -62
  42. package/templates/content_publisher/src/intents/content_publisher/settings_ui.tsx +0 -81
  43. package/templates/content_publisher/src/intents/content_publisher/types.ts +0 -29
  44. package/templates/content_publisher/styles/components.css +0 -38
  45. package/templates/content_publisher/styles/preview_ui.css +0 -49
  46. package/templates/content_publisher/tsconfig.json +0 -56
  47. package/templates/content_publisher/webpack.config.ts +0 -247
  48. package/templates/dam/backend/routers/dam.ts +0 -108
  49. package/templates/dam/backend/server.ts +0 -65
  50. package/templates/dam/canva-app.json +0 -25
  51. package/templates/dam/declarations/declarations.d.ts +0 -29
  52. package/templates/dam/eslint.config.mjs +0 -14
  53. package/templates/dam/package.json +0 -97
  54. package/templates/dam/scripts/copy_env.ts +0 -13
  55. package/templates/dam/scripts/ssl/ssl.ts +0 -131
  56. package/templates/dam/scripts/start/app_runner.ts +0 -223
  57. package/templates/dam/scripts/start/context.ts +0 -171
  58. package/templates/dam/scripts/start/start.ts +0 -46
  59. package/templates/dam/src/index.tsx +0 -4
  60. package/templates/dam/src/intents/design_editor/adapter.ts +0 -44
  61. package/templates/dam/src/intents/design_editor/app.tsx +0 -35
  62. package/templates/dam/src/intents/design_editor/config.ts +0 -220
  63. package/templates/dam/src/intents/design_editor/index.css +0 -3
  64. package/templates/dam/src/intents/design_editor/index.tsx +0 -25
  65. package/templates/dam/tsconfig.json +0 -56
  66. package/templates/dam/utils/backend/base_backend/create.ts +0 -114
  67. package/templates/dam/webpack.config.ts +0 -247
  68. package/templates/data_connector/README.md +0 -84
  69. package/templates/data_connector/canva-app.json +0 -21
  70. package/templates/data_connector/declarations/declarations.d.ts +0 -29
  71. package/templates/data_connector/eslint.config.mjs +0 -14
  72. package/templates/data_connector/package.json +0 -92
  73. package/templates/data_connector/scripts/copy_env.ts +0 -13
  74. package/templates/data_connector/scripts/ssl/ssl.ts +0 -131
  75. package/templates/data_connector/scripts/start/app_runner.ts +0 -223
  76. package/templates/data_connector/scripts/start/context.ts +0 -171
  77. package/templates/data_connector/scripts/start/start.ts +0 -46
  78. package/templates/data_connector/src/api/connect_client.ts +0 -6
  79. package/templates/data_connector/src/api/data_source.ts +0 -97
  80. package/templates/data_connector/src/api/data_sources/designs.tsx +0 -296
  81. package/templates/data_connector/src/api/data_sources/index.ts +0 -4
  82. package/templates/data_connector/src/api/data_sources/templates.tsx +0 -328
  83. package/templates/data_connector/src/api/fetch_data_table.ts +0 -55
  84. package/templates/data_connector/src/api/index.ts +0 -4
  85. package/templates/data_connector/src/api/oauth.ts +0 -8
  86. package/templates/data_connector/src/api/tests/data_source.test.tsx +0 -99
  87. package/templates/data_connector/src/components/app_error.tsx +0 -15
  88. package/templates/data_connector/src/components/footer.tsx +0 -26
  89. package/templates/data_connector/src/components/header.tsx +0 -40
  90. package/templates/data_connector/src/components/index.ts +0 -3
  91. package/templates/data_connector/src/components/inputs/messages.tsx +0 -95
  92. package/templates/data_connector/src/components/inputs/search_filter.tsx +0 -109
  93. package/templates/data_connector/src/components/inputs/select_field.tsx +0 -26
  94. package/templates/data_connector/src/context/app_context.tsx +0 -125
  95. package/templates/data_connector/src/context/index.ts +0 -2
  96. package/templates/data_connector/src/context/use_app_context.ts +0 -17
  97. package/templates/data_connector/src/index.tsx +0 -4
  98. package/templates/data_connector/src/intents/data_connector/app.tsx +0 -20
  99. package/templates/data_connector/src/intents/data_connector/entrypoint.tsx +0 -70
  100. package/templates/data_connector/src/intents/data_connector/home.tsx +0 -21
  101. package/templates/data_connector/src/intents/data_connector/index.tsx +0 -56
  102. package/templates/data_connector/src/pages/data_source_config.tsx +0 -9
  103. package/templates/data_connector/src/pages/error.tsx +0 -37
  104. package/templates/data_connector/src/pages/index.ts +0 -4
  105. package/templates/data_connector/src/pages/login.tsx +0 -145
  106. package/templates/data_connector/src/pages/select_source.tsx +0 -24
  107. package/templates/data_connector/src/routes/index.ts +0 -2
  108. package/templates/data_connector/src/routes/paths.ts +0 -7
  109. package/templates/data_connector/src/routes/protected_route.tsx +0 -26
  110. package/templates/data_connector/src/routes/routes.tsx +0 -42
  111. package/templates/data_connector/src/utils/data_params.ts +0 -17
  112. package/templates/data_connector/src/utils/data_table.ts +0 -116
  113. package/templates/data_connector/src/utils/fetch_result.ts +0 -36
  114. package/templates/data_connector/src/utils/index.ts +0 -2
  115. package/templates/data_connector/src/utils/tests/data_table.test.ts +0 -133
  116. package/templates/data_connector/styles/components.css +0 -38
  117. package/templates/data_connector/tsconfig.json +0 -56
  118. package/templates/data_connector/webpack.config.ts +0 -247
  119. package/templates/gen_ai/README.md +0 -27
  120. package/templates/gen_ai/backend/routers/image.ts +0 -232
  121. package/templates/gen_ai/backend/server.ts +0 -65
  122. package/templates/gen_ai/canva-app.json +0 -25
  123. package/templates/gen_ai/declarations/declarations.d.ts +0 -29
  124. package/templates/gen_ai/eslint.config.mjs +0 -14
  125. package/templates/gen_ai/package.json +0 -101
  126. package/templates/gen_ai/scripts/copy_env.ts +0 -13
  127. package/templates/gen_ai/scripts/ssl/ssl.ts +0 -131
  128. package/templates/gen_ai/scripts/start/app_runner.ts +0 -223
  129. package/templates/gen_ai/scripts/start/context.ts +0 -171
  130. package/templates/gen_ai/scripts/start/start.ts +0 -46
  131. package/templates/gen_ai/src/api/api.ts +0 -194
  132. package/templates/gen_ai/src/api/index.ts +0 -1
  133. package/templates/gen_ai/src/components/app_error.tsx +0 -18
  134. package/templates/gen_ai/src/components/footer.messages.ts +0 -48
  135. package/templates/gen_ai/src/components/footer.tsx +0 -156
  136. package/templates/gen_ai/src/components/image_grid.tsx +0 -103
  137. package/templates/gen_ai/src/components/index.ts +0 -7
  138. package/templates/gen_ai/src/components/loading_results.tsx +0 -169
  139. package/templates/gen_ai/src/components/prompt_input.messages.ts +0 -14
  140. package/templates/gen_ai/src/components/prompt_input.tsx +0 -154
  141. package/templates/gen_ai/src/components/remaining_credits.tsx +0 -84
  142. package/templates/gen_ai/src/components/report_box.tsx +0 -54
  143. package/templates/gen_ai/src/components/tests/remaining_credit.tests.tsx +0 -47
  144. package/templates/gen_ai/src/config.ts +0 -21
  145. package/templates/gen_ai/src/context/app_context.tsx +0 -153
  146. package/templates/gen_ai/src/context/context.messages.ts +0 -30
  147. package/templates/gen_ai/src/context/index.ts +0 -2
  148. package/templates/gen_ai/src/context/use_app_context.ts +0 -17
  149. package/templates/gen_ai/src/index.tsx +0 -4
  150. package/templates/gen_ai/src/intents/design_editor/app.tsx +0 -19
  151. package/templates/gen_ai/src/intents/design_editor/home.tsx +0 -13
  152. package/templates/gen_ai/src/intents/design_editor/index.tsx +0 -17
  153. package/templates/gen_ai/src/pages/error.tsx +0 -41
  154. package/templates/gen_ai/src/pages/generate.tsx +0 -9
  155. package/templates/gen_ai/src/pages/index.ts +0 -3
  156. package/templates/gen_ai/src/pages/results.tsx +0 -31
  157. package/templates/gen_ai/src/routes/index.ts +0 -1
  158. package/templates/gen_ai/src/routes/paths.ts +0 -4
  159. package/templates/gen_ai/src/routes/routes.tsx +0 -24
  160. package/templates/gen_ai/src/utils/index.ts +0 -1
  161. package/templates/gen_ai/src/utils/obscenity_filter.ts +0 -33
  162. package/templates/gen_ai/styles/components.css +0 -38
  163. package/templates/gen_ai/styles/utils.css +0 -3
  164. package/templates/gen_ai/tsconfig.json +0 -56
  165. package/templates/gen_ai/utils/backend/base_backend/create.ts +0 -114
  166. package/templates/gen_ai/webpack.config.ts +0 -247
  167. package/templates/hello_world/canva-app.json +0 -21
  168. package/templates/hello_world/declarations/declarations.d.ts +0 -29
  169. package/templates/hello_world/eslint.config.mjs +0 -14
  170. package/templates/hello_world/package.json +0 -90
  171. package/templates/hello_world/scripts/copy_env.ts +0 -13
  172. package/templates/hello_world/scripts/ssl/ssl.ts +0 -131
  173. package/templates/hello_world/scripts/start/app_runner.ts +0 -223
  174. package/templates/hello_world/scripts/start/context.ts +0 -171
  175. package/templates/hello_world/scripts/start/start.ts +0 -46
  176. package/templates/hello_world/src/index.tsx +0 -4
  177. package/templates/hello_world/src/intents/design_editor/app.tsx +0 -86
  178. package/templates/hello_world/src/intents/design_editor/index.tsx +0 -25
  179. package/templates/hello_world/src/intents/design_editor/tests/__snapshots__/app.tests.tsx.snap +0 -45
  180. package/templates/hello_world/src/intents/design_editor/tests/app.tests.tsx +0 -92
  181. package/templates/hello_world/styles/components.css +0 -38
  182. package/templates/hello_world/tsconfig.json +0 -56
  183. package/templates/hello_world/webpack.config.ts +0 -247
  184. package/templates/mls/README.md +0 -81
  185. package/templates/mls/canva-app.json +0 -25
  186. package/templates/mls/declarations/declarations.d.ts +0 -29
  187. package/templates/mls/eslint.config.mjs +0 -14
  188. package/templates/mls/jest.config.mjs +0 -36
  189. package/templates/mls/jest.setup.ts +0 -39
  190. package/templates/mls/package.json +0 -117
  191. package/templates/mls/scripts/copy_env.ts +0 -13
  192. package/templates/mls/scripts/ssl/ssl.ts +0 -131
  193. package/templates/mls/scripts/start/app_runner.ts +0 -223
  194. package/templates/mls/scripts/start/context.ts +0 -171
  195. package/templates/mls/scripts/start/start.ts +0 -46
  196. package/templates/mls/src/__tests__/app.tests.tsx +0 -11
  197. package/templates/mls/src/__tests__/office_selection_page.tests.tsx +0 -72
  198. package/templates/mls/src/__tests__/utils.tsx +0 -19
  199. package/templates/mls/src/adapter.ts +0 -126
  200. package/templates/mls/src/components/agent/agent_card.tsx +0 -57
  201. package/templates/mls/src/components/agent/agent_grid.tsx +0 -37
  202. package/templates/mls/src/components/agent/agent_list.tsx +0 -17
  203. package/templates/mls/src/components/agent/agent_search_filters.tsx +0 -88
  204. package/templates/mls/src/components/breadcrumb/breadcrumb.tsx +0 -40
  205. package/templates/mls/src/components/listing/listing_card.tsx +0 -64
  206. package/templates/mls/src/components/listing/listing_grid.tsx +0 -37
  207. package/templates/mls/src/components/listing/listing_list.tsx +0 -21
  208. package/templates/mls/src/components/listing/listing_search_filters.tsx +0 -145
  209. package/templates/mls/src/components/placeholders/placeholders.tsx +0 -65
  210. package/templates/mls/src/data.ts +0 -359
  211. package/templates/mls/src/index.tsx +0 -4
  212. package/templates/mls/src/intents/design_editor/app.tsx +0 -44
  213. package/templates/mls/src/intents/design_editor/index.tsx +0 -25
  214. package/templates/mls/src/pages/agent_details_page/agent_details_page.tsx +0 -175
  215. package/templates/mls/src/pages/list_page/agent_tab_panel.tsx +0 -126
  216. package/templates/mls/src/pages/list_page/list_page.tsx +0 -67
  217. package/templates/mls/src/pages/list_page/listing_tab_panel.tsx +0 -135
  218. package/templates/mls/src/pages/listing_details_page/listing_details_page.tsx +0 -418
  219. package/templates/mls/src/pages/loading_page/loading_page.tsx +0 -152
  220. package/templates/mls/src/pages/office_selection_page/office_selection_page.tsx +0 -144
  221. package/templates/mls/src/real_estate.type.ts +0 -44
  222. package/templates/mls/src/util/use_add_element.tsx +0 -62
  223. package/templates/mls/src/util/use_drag_element.tsx +0 -68
  224. package/templates/mls/styles/components.css +0 -38
  225. package/templates/mls/tsconfig.json +0 -54
  226. package/templates/mls/webpack.config.ts +0 -248
  227. package/templates/optional/.cursor/mcp.json +0 -8
  228. package/templates/optional/.vscode/extensions.json +0 -6
  229. package/templates/optional/.vscode/mcp.json +0 -9
  230. package/templates/optional/AGENTS.md +0 -154
  231. package/templates/optional/CLAUDE.md +0 -154
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canva/cli",
3
- "version": "1.19.0",
3
+ "version": "1.21.0",
4
4
  "description": "The official Canva CLI.",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "author": "Canva Pty Ltd.",
@@ -23,7 +23,6 @@
23
23
  "canva": "./cli.js"
24
24
  },
25
25
  "files": [
26
- "templates",
27
26
  "cli.js",
28
27
  "LICENSE.md",
29
28
  "README.md",
@@ -33,7 +32,7 @@
33
32
  "dependencies": {
34
33
  "ink": "6.3.1",
35
34
  "react": "^19.2.3",
36
- "@modelcontextprotocol/sdk": "1.25.2",
35
+ "@modelcontextprotocol/sdk": "1.27.1",
37
36
  "react-docgen-typescript": "2.4.0"
38
37
  },
39
38
  "keywords": [
@@ -1,114 +0,0 @@
1
- /* eslint-disable no-console */
2
- import { TokenVerificationError } from "@canva/app-middleware";
3
- import debug from "debug";
4
- import express from "express";
5
- import type { NextFunction, Request, Response } from "express";
6
- import fs from "fs";
7
- import http from "http";
8
- import https from "https";
9
-
10
- const serverDebug = debug("server");
11
-
12
- interface BaseServer {
13
- app: express.Express;
14
-
15
- /**
16
- * Starts the server on the address or port provided
17
- * @param address port number or string address or if left undefined express defaults to port 3000
18
- */
19
- start: (address: number | string | undefined) => void;
20
- }
21
-
22
- /**
23
- * createBaseServer instantiates a customised express server with:
24
- * - json body handling
25
- * - health check endpoint
26
- * - catchall endpoint
27
- * - error handler catch route
28
- * - process termination handling
29
- * - debug logging - prefix starting your server with `DEBUG=server npm run XXX`
30
- *
31
- * @returns BaseServer object containing the express app and a start function
32
- */
33
- export function createBaseServer(router: express.Router): BaseServer {
34
- const SHOULD_ENABLE_HTTPS = process.env?.SHOULD_ENABLE_HTTPS === "true";
35
- const HTTPS_CERT_FILE = process.env?.HTTPS_CERT_FILE;
36
- const HTTPS_KEY_FILE = process.env?.HTTPS_KEY_FILE;
37
-
38
- const app = express();
39
- app.use(express.json());
40
-
41
- // It can help to provide an extra layer of obsecurity to reduce server fingerprinting.
42
- app.disable("x-powered-by");
43
-
44
- // Health check endpoint
45
- app.get("/healthz", (req, res) => {
46
- res.sendStatus(200);
47
- });
48
-
49
- // logging middleware
50
- app.use((req, _res, next) => {
51
- serverDebug(`${new Date().toISOString()}: ${req.method} ${req.url}`);
52
- next();
53
- });
54
-
55
- // Custom routes router
56
- app.use(router);
57
-
58
- // catch all router
59
- app.all("*", (req, res) => {
60
- res.status(404).send({
61
- error: `unhandled '${req.method}' on '${req.url}'`,
62
- });
63
- });
64
-
65
- // default error handler
66
- app.use((err: Error, _req: Request, res: Response, _next: NextFunction) => {
67
- // Handle authentication errors from @canva/app-middleware
68
- if (err instanceof TokenVerificationError) {
69
- res.status(err.statusCode).json({
70
- error: err.code,
71
- message: err.message,
72
- });
73
- return;
74
- }
75
-
76
- console.error(err.stack);
77
- res.status(500).send({
78
- error: "something went wrong",
79
- });
80
- });
81
-
82
- let server;
83
- if (SHOULD_ENABLE_HTTPS) {
84
- if (!HTTPS_CERT_FILE || !HTTPS_KEY_FILE) {
85
- throw new Error(
86
- "Looks like you're running the example with --use-https flag, but SSL certificates haven't been generated. Please remove the .ssl/ folder and re-run the command again.",
87
- );
88
- }
89
-
90
- server = https.createServer(
91
- {
92
- key: fs.readFileSync(HTTPS_KEY_FILE),
93
- cert: fs.readFileSync(HTTPS_CERT_FILE),
94
- },
95
- app,
96
- );
97
- } else {
98
- server = http.createServer(app);
99
- }
100
-
101
- return {
102
- app,
103
- start: (address: number | string | undefined) => {
104
- console.log(`Listening on '${address}'`);
105
- server.listen(address);
106
- process.on("SIGTERM", () => {
107
- serverDebug("SIGTERM signal received: closing HTTP server");
108
- server.close(() => {
109
- serverDebug("HTTP server closed");
110
- });
111
- });
112
- },
113
- };
114
- }
@@ -1,42 +0,0 @@
1
- import fs from "fs/promises";
2
- import path from "path";
3
-
4
- /**
5
- * This file creates a "database" out of a JSON file. It's only for
6
- * demonstration purposes. A real app should use a real database.
7
- */
8
- const DATABASE_FILE_PATH = path.join(__dirname, "db.json");
9
-
10
- interface Database<T> {
11
- read(): Promise<T>;
12
- write(data: T): Promise<void>;
13
- }
14
-
15
- export class JSONFileDatabase<T> implements Database<T> {
16
- constructor(private readonly seedData: T) {}
17
-
18
- // Creates a database file if one doesn't already exist
19
- private async init(): Promise<void> {
20
- try {
21
- // Do nothing, since the database is initialized
22
- await fs.stat(DATABASE_FILE_PATH);
23
- } catch {
24
- const file = JSON.stringify(this.seedData);
25
- await fs.writeFile(DATABASE_FILE_PATH, file);
26
- }
27
- }
28
-
29
- // Loads and parses the database file
30
- async read(): Promise<T> {
31
- await this.init();
32
- const file = await fs.readFile(DATABASE_FILE_PATH, "utf8");
33
- return JSON.parse(file);
34
- }
35
-
36
- // Overwrites the database file with provided data
37
- async write(data: T): Promise<void> {
38
- await this.init();
39
- const file = JSON.stringify(data);
40
- await fs.writeFile(DATABASE_FILE_PATH, file);
41
- }
42
- }
@@ -1,288 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-non-null-assertion -- user is guaranteed by verifyToken middleware */
2
- import { tokenExtractors, user } from "@canva/app-middleware/express";
3
- import chalk from "chalk";
4
- import cookieParser from "cookie-parser";
5
- import crypto from "crypto";
6
- import "dotenv/config";
7
- import express from "express";
8
- import basicAuth from "express-basic-auth";
9
- import { JSONFileDatabase } from "../database/database";
10
-
11
- /**
12
- * These are the hard-coded credentials for logging in to this template.
13
- */
14
- const USERNAME = "username";
15
- const PASSWORD = "password";
16
-
17
- const COOKIE_EXPIRY_MS = 5 * 60 * 1000; // 5 minutes
18
-
19
- const CANVA_BASE_URL = "https://www.canva.com";
20
-
21
- interface Data {
22
- users: string[];
23
- }
24
-
25
- /**
26
- * For more information on Authentication, refer to our documentation: {@link https://www.canva.dev/docs/apps/authenticating-users/#types-of-authentication}.
27
- */
28
- export const createAuthRouter = () => {
29
- const APP_ID = getAppId();
30
-
31
- /**
32
- * Set up a database with a "users" table. In this example code, the
33
- * database is simply a JSON file.
34
- */
35
- const db = new JSONFileDatabase<Data>({ users: [] });
36
-
37
- const router = express.Router();
38
-
39
- /**
40
- * The `cookieParser` middleware allows the routes to read and write cookies.
41
- *
42
- * By passing a value into the middleware, we enable the "signed cookies" feature of Express.js. The
43
- * value should be static and cryptographically generated. If it's dynamic (as shown below), cookies
44
- * won't persist between server restarts.
45
- *
46
- * TODO: Replace `crypto.randomUUID()` with a static value, loaded via an environment variable.
47
- */
48
- router.use(cookieParser(crypto.randomUUID()));
49
-
50
- /**
51
- * This endpoint is hit at the start of the authentication flow. It contains a state which must
52
- * be passed back to canva so that Canva can verify the response. It must also set a nonce in the
53
- * user's browser cookies and send the nonce back to Canva as a url parameter.
54
- *
55
- * If Canva can validate the state, it will then redirect back to the chosen redirect url.
56
- */
57
- router.get("/configuration/start", async (req, res) => {
58
- /**
59
- * Generate a unique nonce for each request. A nonce is a random, single-use value
60
- * that's impossible to guess or enumerate. We recommended using a Version 4 UUID that
61
- * is cryptographically secure, such as one generated by the `randomUUID` method.
62
- */
63
- const nonce = crypto.randomUUID();
64
- // Set the expiry time for the nonce. We recommend 5 minutes.
65
- const expiry = Date.now() + COOKIE_EXPIRY_MS;
66
-
67
- // Create a JSON string that contains the nonce and an expiry time
68
- const nonceWithExpiry = JSON.stringify([nonce, expiry]);
69
-
70
- // Set a cookie that contains the nonce and the expiry time
71
- res.cookie("nonce", nonceWithExpiry, {
72
- secure: true,
73
- httpOnly: true,
74
- maxAge: COOKIE_EXPIRY_MS,
75
- signed: true,
76
- });
77
-
78
- // Create the query parameters that Canva requires
79
- const params = new URLSearchParams({
80
- nonce,
81
- state: req?.query?.state?.toString() || "",
82
- });
83
-
84
- // Redirect to Canva with required parameters
85
- res.redirect(302, `${CANVA_BASE_URL}/apps/configure/link?${params}`);
86
- });
87
-
88
- /**
89
- * This endpoint renders a login page. Once the user logs in, they're
90
- * redirected back to Canva, which completes the authentication flow.
91
- */
92
- router.get(
93
- "/redirect-url",
94
- /**
95
- * Use a JSON Web Token (JWT) to verify incoming requests. The JWT is
96
- * extracted from the `canva_user_token` parameter.
97
- */
98
- user.verifyToken({
99
- appId: APP_ID,
100
- tokenExtractor: tokenExtractors.fromQuery("canva_user_token"),
101
- }),
102
- /**
103
- * Warning: For demonstration purposes, we're using basic authentication and
104
- * hard- coding a username and password. This is not a production-ready
105
- * solution!
106
- */
107
- basicAuth({
108
- users: { [USERNAME]: PASSWORD },
109
- challenge: true,
110
- }),
111
- async (req, res) => {
112
- const failureResponse = () => {
113
- const params = new URLSearchParams({
114
- success: "false",
115
- state: req.query.state?.toString() || "",
116
- });
117
- res.redirect(302, `${CANVA_BASE_URL}/apps/configured?${params}`);
118
- };
119
-
120
- // Get the nonce and expiry time stored in the cookie.
121
- const cookieNonceAndExpiry = req.signedCookies.nonce;
122
-
123
- // Get the nonce from the query parameter.
124
- const queryNonce = req.query.nonce?.toString();
125
-
126
- // After reading the cookie, clear it. This forces abandoned auth flows to be restarted.
127
- res.clearCookie("nonce");
128
-
129
- let cookieNonce = "";
130
- let expiry = 0;
131
-
132
- try {
133
- [cookieNonce, expiry] = JSON.parse(cookieNonceAndExpiry);
134
- } catch {
135
- // If the nonce can't be parsed, assume something has been compromised and exit.
136
- return failureResponse();
137
- }
138
-
139
- // If the nonces are empty, exit the authentication flow.
140
- if (
141
- isEmpty(cookieNonceAndExpiry) ||
142
- isEmpty(queryNonce) ||
143
- isEmpty(cookieNonce)
144
- ) {
145
- return failureResponse();
146
- }
147
-
148
- /**
149
- * Check that:
150
- *
151
- * - The nonce in the cookie and query parameter contain the same value
152
- * - The nonce has not expired
153
- *
154
- * **Note:** We could rely on the cookie expiry, but that is vulnerable to tampering
155
- * with the browser's time. This allows us to double-check based on server time.
156
- */
157
- if (expiry < Date.now() || cookieNonce !== queryNonce) {
158
- return failureResponse();
159
- }
160
-
161
- // Get the userId from user object in the request context
162
- const { userId } = req.canva.user!;
163
-
164
- // Load the database
165
- const data = await db.read();
166
-
167
- // Add the user to the database
168
- if (!data.users.includes(userId)) {
169
- data.users.push(userId);
170
- await db.write(data);
171
- }
172
-
173
- // Create query parameters for redirecting back to Canva
174
- const params = new URLSearchParams({
175
- success: "true",
176
- state: req?.query?.state?.toString() || "",
177
- });
178
-
179
- // Redirect the user back to Canva
180
- res.redirect(302, `${CANVA_BASE_URL}/apps/configured?${params}`);
181
- },
182
- );
183
-
184
- /**
185
- * TODO: Add this middleware to all routes that will receive requests from
186
- * your app.
187
- */
188
- const jwtMiddleware = user.verifyToken({ appId: APP_ID });
189
-
190
- /**
191
- * This endpoint is called when a user disconnects an app from their account.
192
- * The app is expected to de-authenticate the user on its backend, so if the
193
- * user reconnects the app, they'll need to re-authenticate.
194
- *
195
- * Note: The name of the endpoint is *not* configurable.
196
- *
197
- * Note: This endpoint is called by Canva's backend directly and must be
198
- * exposed via a public URL. To test this endpoint, add a proxy URL, such as
199
- * one generated by nGrok, to the 'Add authentication' section in the
200
- * Developer Portal. Localhost addresses will not work to test this endpoint.
201
- */
202
- router.post("/configuration/delete", jwtMiddleware, async (req, res) => {
203
- // Get the userId from user object in the request context
204
- const { userId } = req.canva.user!;
205
-
206
- // Load the database
207
- const data = await db.read();
208
-
209
- // Remove the user from the database
210
- await db.write({
211
- users: data.users.filter((user) => user !== userId),
212
- });
213
-
214
- // Confirm that the user was removed
215
- res.send({
216
- type: "SUCCESS",
217
- });
218
- });
219
-
220
- /**
221
- * All routes that start with /api will be protected by JWT authentication
222
- */
223
- router.use("/api", jwtMiddleware);
224
-
225
- /**
226
- * This endpoint checks if a user is authenticated.
227
- */
228
- router.post("/api/authentication/status", async (req, res) => {
229
- // Load the database
230
- const data = await db.read();
231
-
232
- // Check if the user is authenticated
233
- const isAuthenticated = data.users.includes(req.canva.user!.userId);
234
-
235
- // Return the authentication status
236
- res.send({
237
- isAuthenticated,
238
- });
239
- });
240
-
241
- return router;
242
- };
243
-
244
- /**
245
- * Checks if a given param is nullish or an empty string
246
- *
247
- * @param str The string to check
248
- * @returns true if the string is nullish or empty, false otherwise
249
- */
250
- function isEmpty(str?: string): boolean {
251
- return str == null || str.length === 0;
252
- }
253
-
254
- /**
255
- * Retrieves the CANVA_APP_ID from the environment variables.
256
- * Throws an error if the CANVA_APP_ID environment variable is undefined or set to a default value.
257
- *
258
- * @returns {string} The Canva App ID
259
- * @throws {Error} If CANVA_APP_ID environment variable is undefined or set to a default value
260
- */
261
- function getAppId(): string {
262
- // TODO: Set the CANVA_APP_ID environment variable in the project's .env file
263
- const appId = process.env.CANVA_APP_ID;
264
-
265
- if (!appId) {
266
- throw new Error(
267
- `The CANVA_APP_ID environment variable is undefined. Set the variable in the project's .env file.`,
268
- );
269
- }
270
-
271
- if (appId === "YOUR_APP_ID_HERE") {
272
- // eslint-disable-next-line no-console
273
- console.log(
274
- chalk.bgRedBright(
275
- "Default 'CANVA_APP_ID' environment variable detected.",
276
- ),
277
- );
278
- // eslint-disable-next-line no-console
279
- console.log(
280
- "Please update the 'CANVA_APP_ID' environment variable in your project's `.env` file " +
281
- `with the App ID obtained from the Canva Developer Portal: ${chalk.greenBright(
282
- "https://www.canva.com/developers/apps",
283
- )}\n`,
284
- );
285
- }
286
-
287
- return appId;
288
- }
@@ -1,29 +0,0 @@
1
- declare module "*.css" {
2
- const styles: { [className: string]: string };
3
- export = styles;
4
- }
5
-
6
- declare module "*.jpg" {
7
- const content: string;
8
- export default content;
9
- }
10
-
11
- declare module "*.jpeg" {
12
- const content: string;
13
- export default content;
14
- }
15
-
16
- declare module "*.png" {
17
- const content: string;
18
- export default content;
19
- }
20
-
21
- declare module "*.svg" {
22
- const content: React.FunctionComponent<{
23
- size?: "tiny" | "small" | "medium" | "large";
24
- className?: string;
25
- }>;
26
- export default content;
27
- }
28
-
29
- declare const BACKEND_HOST: string;
@@ -1,14 +0,0 @@
1
- import canvaPlugin from "@canva/app-eslint-plugin";
2
-
3
- export default [
4
- {
5
- ignores: [
6
- "**/node_modules/",
7
- "**/dist",
8
- "**/*.d.ts",
9
- "**/*.d.tsx",
10
- "**/*.config.*",
11
- ],
12
- },
13
- ...canvaPlugin.configs.apps,
14
- ];
@@ -1,91 +0,0 @@
1
- {
2
- "private": true,
3
- "name": "base",
4
- "version": "1.0.0",
5
- "description": "All other templates should use this as the base. create-app/create-app-steps looks at the canvaCliMetadata when patching the package json outputs",
6
- "license": "SEE LICENSE IN LICENSE.md",
7
- "author": "Canva Pty Ltd.",
8
- "dependencies": {
9
- "@canva/app-hooks": "^0.0.0-beta.4",
10
- "@canva/app-i18n-kit": "^1.2.0",
11
- "@canva/app-ui-kit": "^5.5.0",
12
- "@canva/asset": "^2.3.0",
13
- "@canva/design": "^2.8.0",
14
- "@canva/error": "^2.2.0",
15
- "@canva/platform": "^2.2.1",
16
- "@canva/user": "^2.1.2",
17
- "cookie-parser": "1.4.7",
18
- "react": "^19.2.3",
19
- "react-dom": "^19.2.3",
20
- "react-intl": "^7.1.11"
21
- },
22
- "devDependencies": {
23
- "@canva/cli": ">= 0.0.1-beta.13",
24
- "@ngrok/ngrok": "1.5.2",
25
- "@svgr/webpack": "8.1.0",
26
- "@types/debug": "4.1.12",
27
- "@types/express": "4.17.21",
28
- "@types/jest": "29.5.14",
29
- "@types/jsonwebtoken": "9.0.10",
30
- "@types/node": "20.19.2",
31
- "@types/node-fetch": "2.6.13",
32
- "@types/node-forge": "1.3.14",
33
- "@types/nodemon": "1.19.6",
34
- "@types/prompts": "2.4.9",
35
- "@types/react": "19.2.2",
36
- "@types/react-dom": "19.2.1",
37
- "@types/webpack-env": "1.18.8",
38
- "chalk": "4.1.2",
39
- "cli-table3": "0.6.5",
40
- "css-loader": "7.1.2",
41
- "css-modules-typescript-loader": "4.0.1",
42
- "cssnano": "7.1.1",
43
- "debug": "4.4.1",
44
- "dotenv": "16.6.0",
45
- "exponential-backoff": "3.1.2",
46
- "express": "4.22.1",
47
- "express-basic-auth": "1.2.1",
48
- "jest": "29.7.0",
49
- "jsonwebtoken": "9.0.3",
50
- "jwks-rsa": "3.2.0",
51
- "mini-css-extract-plugin": "2.9.4",
52
- "node-fetch": "3.3.2",
53
- "node-forge": "1.3.2",
54
- "nodemon": "3.0.1",
55
- "open": "8.4.2",
56
- "postcss-loader": "8.1.1",
57
- "prettier": "3.6.2",
58
- "prompts": "2.4.2",
59
- "style-loader": "4.0.0",
60
- "terser-webpack-plugin": "5.3.14",
61
- "tree-kill": "1.2.2",
62
- "ts-jest": "29.4.1",
63
- "ts-loader": "9.5.4",
64
- "ts-node": "10.9.2",
65
- "typescript": "5.9.2",
66
- "url-loader": "4.1.1",
67
- "webpack": "5.99.9",
68
- "webpack-cli": "6.0.1",
69
- "webpack-dev-server": "5.2.2",
70
- "yargs": "17.7.2"
71
- },
72
- "keywords": [
73
- "canva-apps-sdk"
74
- ],
75
- "engines": {
76
- "node": ">=20.10.0",
77
- "npm": "^10 || ^11"
78
- },
79
- "canvaCliMetadata": {
80
- "metaFields": [
81
- "name",
82
- "version"
83
- ],
84
- "inheritable": [
85
- "author",
86
- "license",
87
- "engines",
88
- "keywords"
89
- ]
90
- }
91
- }
@@ -1,13 +0,0 @@
1
- #!/usr/bin/env node
2
- /* eslint-disable no-console */
3
- import fs from "fs";
4
- import path from "path";
5
-
6
- const envPath = path.resolve(__dirname, "..", ".env");
7
- const templatePath = path.resolve(__dirname, "..", ".env.template");
8
-
9
- if (!fs.existsSync(templatePath)) {
10
- console.warn(".env.template file does not exist, skipping copy of .env file");
11
- } else if (!fs.existsSync(envPath)) {
12
- fs.copyFileSync(templatePath, envPath);
13
- }