├── README.md ├── backend ├── .env.example ├── .gitignore ├── .prettierrc ├── README.md ├── package-lock.json ├── package.json ├── src │ ├── app.ts │ ├── db │ │ ├── db.ts │ │ └── queries.ts │ ├── index.ts │ ├── middleware │ │ └── auth.ts │ ├── routes │ │ ├── api.routes.ts │ │ ├── index.ts │ │ ├── secret.routes.ts │ │ └── user.routes.ts │ └── utils │ │ ├── authentication.ts │ │ └── env.ts └── tsconfig.json ├── frontend ├── .env.example ├── .gitignore ├── .prettierrc ├── README.md ├── eslint.config.js ├── index.html ├── package-lock.json ├── package.json ├── public │ ├── documents-icon.svg │ ├── favicon.ico │ ├── github-icon.svg │ └── logo.svg ├── src │ ├── app.css │ ├── app.tsx │ ├── context │ │ └── user.tsx │ ├── layouts │ │ └── layout.tsx │ ├── main.tsx │ ├── pages │ │ ├── HomePage.tsx │ │ ├── LoginPage.tsx │ │ ├── OnboardingPage.tsx │ │ ├── ProfilePage.tsx │ │ ├── SignupPage.tsx │ │ └── UserAreaPage.tsx │ ├── utils │ │ └── corbado-translations.ts │ └── vite-env.d.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts └── start.sh /README.md: -------------------------------------------------------------------------------- 1 | GitHub Repo Cover 2 | 3 | # Typescript React.js and Hono Passkey Example App 4 | 5 | This is a sample implementation of the [Corbado passkeys-first authentication solution](https://www.corbado.com) using 6 | React and Hono. The following packages are being used: 7 | 8 | - [Corbado React.js](https://github.com/corbado/javascript/tree/develop/packages/react) 9 | - [Corbado Node.js](https://github.com/corbado/corbado-nodejs) 10 | 11 | [![integration-guides](https://github.com/user-attachments/assets/7859201b-a345-4b68-b336-6e2edcc6577b)](https://app.corbado.com/integration-guides/react-hono) 12 | 13 | ## File structure 14 | 15 | - `frontend`: Separate directory for the Vue.js frontend 16 | - `frontend/.env.example`: Example file for environment variables 17 | - `frontend/src/pages`: Contains all pages used in the frontend 18 | - `frontend/src/app.tsx`: Contains configuration like providers and routes 19 | - `frontend/src/context/user.tsx`: Global store for user data from our own backend 20 | - `backend`: Separate directory for the Hono backend 21 | - `backend/.env.example`: Example file for environment variables 22 | - `backend/src/app.ts`: Configuration file for the Hono app 23 | - `backend/src/utils`: Collection of utility functions, e.g. helper functions for authentication 24 | - `backend/src/routes`: Directory configuring the routes for the app 25 | - `backend/src/middleware`: Middleware, e.g. for authentication 26 | - `backend/src/db`: Database configuration and queries 27 | 28 | 29 | ## Setup 30 | 31 | ### Prerequisites 32 | 33 | Please follow the steps in [Getting started](https://docs.corbado.com/overview/getting-started) to create and configure 34 | a project in the [Corbado developer panel](https://app.corbado.com/). 35 | 36 | You need to have [Node](https://nodejs.org/en/download) and `npm` installed to run it. 37 | 38 | ### Configure environment variables 39 | 40 | Use the values you obtained in [Prerequisites](#prerequisites) to configure the following variables inside a `.env` 41 | file you create in frontend and backend directories respectively: 42 | 43 | #### Backend 44 | 45 | The backend needs an api secret to authenticate with the Corbado backend API. 46 | 47 | ```dotenv 48 | CORBADO_PROJECT_ID=pro-XXX 49 | CORBADO_API_SECRET=corbado1_XXX 50 | CORBADO_FRONTEND_API=https://{$CORBADO_PROJECT_ID}.frontendapi.cloud.corbado.io 51 | CORBADO_BACKEND_API=https://backendapi.cloud.corbado.io 52 | ``` 53 | 54 | #### Frontend 55 | 56 | The frontend needs the project ID and the backend base URL. 57 | 58 | ```dotenv 59 | VITE_CORBADO_PROJECT_ID=pro-XXX 60 | VITE_BACKEND_BASE_URL=http://localhost:3001 61 | ``` 62 | 63 | ## Usage 64 | 65 | ### Run the project locally 66 | 67 | Run the following command in the root directory 68 | 69 | ```bash 70 | (cd backend && npm install) 71 | (cd frontend && npm install) 72 | ``` 73 | 74 | to install all dependencies. 75 | 76 | Finally, you can run the project locally with the provided start script or individually for frontend and backend. 77 | 78 | #### Using the start script 79 | 80 | ```bash 81 | ./start.sh 82 | ``` 83 | 84 | #### Running frontend and backend individually 85 | 86 | In one terminal session, run the following command in the `frontend` directory: 87 | ```bash 88 | npm run dev 89 | ``` 90 | 91 | In another terminal session, run the following command in the `backend` directory: 92 | ```bash 93 | npm run dev 94 | ``` 95 | 96 | ## Passkeys support 97 | 98 | - Community for Developer Support: https://bit.ly/passkeys-community 99 | - Passkeys Debugger: https://www.passkeys-debugger.io/ 100 | - Passkey Subreddit: https://www.reddit.com/r/passkey/ 101 | 102 | ## Telemetry 103 | 104 | This example application uses telemetry. By gathering telemetry data, we gain a more comprehensive understanding of how our SDKs, components, and example applications are utilized across various scenarios. This information is crucial in helping us prioritize features that are beneficial and impactful for the majority of our users. Read our [official documentation](https://docs.corbado.com/corbado-complete/other/telemetry) for more details. 105 | 106 | To disable telemetry, add the following line to your `frontend/.env` file: 107 | 108 | ```sh 109 | VITE_CORBADO_TELEMETRY_DISABLED=true 110 | ``` -------------------------------------------------------------------------------- /backend/.env.example: -------------------------------------------------------------------------------- 1 | CORBADO_PROJECT_ID= 2 | CORBADO_API_SECRET= 3 | CORBADO_FRONTEND_API= 4 | CORBADO_BACKEND_API= -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | # dev 2 | .yarn/ 3 | !.yarn/releases 4 | .vscode/* 5 | !.vscode/launch.json 6 | !.vscode/*.code-snippets 7 | .idea/workspace.xml 8 | .idea/usage.statistics.xml 9 | .idea/shelf 10 | 11 | # deps 12 | node_modules/ 13 | 14 | # env 15 | .env* 16 | 17 | # logs 18 | logs/ 19 | *.log 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | pnpm-debug.log* 24 | lerna-debug.log* 25 | 26 | # misc 27 | .DS_Store 28 | 29 | # db 30 | db.json 31 | -------------------------------------------------------------------------------- /backend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "printWidth": 80, 4 | "embeddedLanguageFormatting": "auto", 5 | "plugins": [ 6 | "prettier-plugin-embed" 7 | ] 8 | } -------------------------------------------------------------------------------- /backend/README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | npm install 3 | npm run dev 4 | ``` 5 | 6 | ``` 7 | open http://localhost:3000 8 | ``` 9 | -------------------------------------------------------------------------------- /backend/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "name": "backend", 8 | "dependencies": { 9 | "@corbado/node-sdk": "^3.0.2", 10 | "@hono/node-server": "^1.13.7", 11 | "dotenv": "^16.4.7", 12 | "hono": "^4.6.14", 13 | "lowdb": "^7.0.1" 14 | }, 15 | "devDependencies": { 16 | "@corbado/types": "^2.11.2", 17 | "@types/node": "^20.11.17", 18 | "prettier": "^3.4.2", 19 | "prettier-plugin-embed": "^0.4.15", 20 | "tsx": "^4.7.1" 21 | } 22 | }, 23 | "node_modules/@corbado/node-sdk": { 24 | "version": "3.0.2", 25 | "resolved": "https://registry.npmjs.org/@corbado/node-sdk/-/node-sdk-3.0.2.tgz", 26 | "integrity": "sha512-uAdZTHsej9akILLLbztDaiIUyn7nR4l9CeYA1zqbuWxPQox/V+JGAwcCPVJImlVGbB17JdxVrzAdI72SWtA3/A==", 27 | "license": "MIT", 28 | "dependencies": { 29 | "axios": "^1.7.7", 30 | "axios-better-stacktrace": "^2.1.7", 31 | "axios-mock-adapter": "^2.0.0", 32 | "dotenv": "^16.3.1", 33 | "express": "^4.18.2", 34 | "jose": "^5.1.3", 35 | "typescript": "^5.3.4" 36 | }, 37 | "engines": { 38 | "node": ">=16.1" 39 | } 40 | }, 41 | "node_modules/@corbado/types": { 42 | "version": "2.11.2", 43 | "resolved": "https://registry.npmjs.org/@corbado/types/-/types-2.11.2.tgz", 44 | "integrity": "sha512-LTMg1rjrOWsazql038ekHLN/aq1laari1kIWrsobnlFETGid045C6hrd5alDSYsnp24tcfKjFf1ZemIqGSASCA==", 45 | "dev": true, 46 | "license": "ISC" 47 | }, 48 | "node_modules/@esbuild/aix-ppc64": { 49 | "version": "0.25.5", 50 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", 51 | "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", 52 | "cpu": [ 53 | "ppc64" 54 | ], 55 | "dev": true, 56 | "license": "MIT", 57 | "optional": true, 58 | "os": [ 59 | "aix" 60 | ], 61 | "engines": { 62 | "node": ">=18" 63 | } 64 | }, 65 | "node_modules/@esbuild/android-arm": { 66 | "version": "0.25.5", 67 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", 68 | "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", 69 | "cpu": [ 70 | "arm" 71 | ], 72 | "dev": true, 73 | "license": "MIT", 74 | "optional": true, 75 | "os": [ 76 | "android" 77 | ], 78 | "engines": { 79 | "node": ">=18" 80 | } 81 | }, 82 | "node_modules/@esbuild/android-arm64": { 83 | "version": "0.25.5", 84 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", 85 | "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", 86 | "cpu": [ 87 | "arm64" 88 | ], 89 | "dev": true, 90 | "license": "MIT", 91 | "optional": true, 92 | "os": [ 93 | "android" 94 | ], 95 | "engines": { 96 | "node": ">=18" 97 | } 98 | }, 99 | "node_modules/@esbuild/android-x64": { 100 | "version": "0.25.5", 101 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", 102 | "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", 103 | "cpu": [ 104 | "x64" 105 | ], 106 | "dev": true, 107 | "license": "MIT", 108 | "optional": true, 109 | "os": [ 110 | "android" 111 | ], 112 | "engines": { 113 | "node": ">=18" 114 | } 115 | }, 116 | "node_modules/@esbuild/darwin-arm64": { 117 | "version": "0.25.5", 118 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", 119 | "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", 120 | "cpu": [ 121 | "arm64" 122 | ], 123 | "dev": true, 124 | "license": "MIT", 125 | "optional": true, 126 | "os": [ 127 | "darwin" 128 | ], 129 | "engines": { 130 | "node": ">=18" 131 | } 132 | }, 133 | "node_modules/@esbuild/darwin-x64": { 134 | "version": "0.25.5", 135 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", 136 | "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", 137 | "cpu": [ 138 | "x64" 139 | ], 140 | "dev": true, 141 | "license": "MIT", 142 | "optional": true, 143 | "os": [ 144 | "darwin" 145 | ], 146 | "engines": { 147 | "node": ">=18" 148 | } 149 | }, 150 | "node_modules/@esbuild/freebsd-arm64": { 151 | "version": "0.25.5", 152 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", 153 | "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", 154 | "cpu": [ 155 | "arm64" 156 | ], 157 | "dev": true, 158 | "license": "MIT", 159 | "optional": true, 160 | "os": [ 161 | "freebsd" 162 | ], 163 | "engines": { 164 | "node": ">=18" 165 | } 166 | }, 167 | "node_modules/@esbuild/freebsd-x64": { 168 | "version": "0.25.5", 169 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", 170 | "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", 171 | "cpu": [ 172 | "x64" 173 | ], 174 | "dev": true, 175 | "license": "MIT", 176 | "optional": true, 177 | "os": [ 178 | "freebsd" 179 | ], 180 | "engines": { 181 | "node": ">=18" 182 | } 183 | }, 184 | "node_modules/@esbuild/linux-arm": { 185 | "version": "0.25.5", 186 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", 187 | "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", 188 | "cpu": [ 189 | "arm" 190 | ], 191 | "dev": true, 192 | "license": "MIT", 193 | "optional": true, 194 | "os": [ 195 | "linux" 196 | ], 197 | "engines": { 198 | "node": ">=18" 199 | } 200 | }, 201 | "node_modules/@esbuild/linux-arm64": { 202 | "version": "0.25.5", 203 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", 204 | "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", 205 | "cpu": [ 206 | "arm64" 207 | ], 208 | "dev": true, 209 | "license": "MIT", 210 | "optional": true, 211 | "os": [ 212 | "linux" 213 | ], 214 | "engines": { 215 | "node": ">=18" 216 | } 217 | }, 218 | "node_modules/@esbuild/linux-ia32": { 219 | "version": "0.25.5", 220 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", 221 | "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", 222 | "cpu": [ 223 | "ia32" 224 | ], 225 | "dev": true, 226 | "license": "MIT", 227 | "optional": true, 228 | "os": [ 229 | "linux" 230 | ], 231 | "engines": { 232 | "node": ">=18" 233 | } 234 | }, 235 | "node_modules/@esbuild/linux-loong64": { 236 | "version": "0.25.5", 237 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", 238 | "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", 239 | "cpu": [ 240 | "loong64" 241 | ], 242 | "dev": true, 243 | "license": "MIT", 244 | "optional": true, 245 | "os": [ 246 | "linux" 247 | ], 248 | "engines": { 249 | "node": ">=18" 250 | } 251 | }, 252 | "node_modules/@esbuild/linux-mips64el": { 253 | "version": "0.25.5", 254 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", 255 | "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", 256 | "cpu": [ 257 | "mips64el" 258 | ], 259 | "dev": true, 260 | "license": "MIT", 261 | "optional": true, 262 | "os": [ 263 | "linux" 264 | ], 265 | "engines": { 266 | "node": ">=18" 267 | } 268 | }, 269 | "node_modules/@esbuild/linux-ppc64": { 270 | "version": "0.25.5", 271 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", 272 | "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", 273 | "cpu": [ 274 | "ppc64" 275 | ], 276 | "dev": true, 277 | "license": "MIT", 278 | "optional": true, 279 | "os": [ 280 | "linux" 281 | ], 282 | "engines": { 283 | "node": ">=18" 284 | } 285 | }, 286 | "node_modules/@esbuild/linux-riscv64": { 287 | "version": "0.25.5", 288 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", 289 | "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", 290 | "cpu": [ 291 | "riscv64" 292 | ], 293 | "dev": true, 294 | "license": "MIT", 295 | "optional": true, 296 | "os": [ 297 | "linux" 298 | ], 299 | "engines": { 300 | "node": ">=18" 301 | } 302 | }, 303 | "node_modules/@esbuild/linux-s390x": { 304 | "version": "0.25.5", 305 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", 306 | "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", 307 | "cpu": [ 308 | "s390x" 309 | ], 310 | "dev": true, 311 | "license": "MIT", 312 | "optional": true, 313 | "os": [ 314 | "linux" 315 | ], 316 | "engines": { 317 | "node": ">=18" 318 | } 319 | }, 320 | "node_modules/@esbuild/linux-x64": { 321 | "version": "0.25.5", 322 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", 323 | "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", 324 | "cpu": [ 325 | "x64" 326 | ], 327 | "dev": true, 328 | "license": "MIT", 329 | "optional": true, 330 | "os": [ 331 | "linux" 332 | ], 333 | "engines": { 334 | "node": ">=18" 335 | } 336 | }, 337 | "node_modules/@esbuild/netbsd-arm64": { 338 | "version": "0.25.5", 339 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", 340 | "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", 341 | "cpu": [ 342 | "arm64" 343 | ], 344 | "dev": true, 345 | "license": "MIT", 346 | "optional": true, 347 | "os": [ 348 | "netbsd" 349 | ], 350 | "engines": { 351 | "node": ">=18" 352 | } 353 | }, 354 | "node_modules/@esbuild/netbsd-x64": { 355 | "version": "0.25.5", 356 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", 357 | "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", 358 | "cpu": [ 359 | "x64" 360 | ], 361 | "dev": true, 362 | "license": "MIT", 363 | "optional": true, 364 | "os": [ 365 | "netbsd" 366 | ], 367 | "engines": { 368 | "node": ">=18" 369 | } 370 | }, 371 | "node_modules/@esbuild/openbsd-arm64": { 372 | "version": "0.25.5", 373 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", 374 | "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", 375 | "cpu": [ 376 | "arm64" 377 | ], 378 | "dev": true, 379 | "license": "MIT", 380 | "optional": true, 381 | "os": [ 382 | "openbsd" 383 | ], 384 | "engines": { 385 | "node": ">=18" 386 | } 387 | }, 388 | "node_modules/@esbuild/openbsd-x64": { 389 | "version": "0.25.5", 390 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", 391 | "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", 392 | "cpu": [ 393 | "x64" 394 | ], 395 | "dev": true, 396 | "license": "MIT", 397 | "optional": true, 398 | "os": [ 399 | "openbsd" 400 | ], 401 | "engines": { 402 | "node": ">=18" 403 | } 404 | }, 405 | "node_modules/@esbuild/sunos-x64": { 406 | "version": "0.25.5", 407 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", 408 | "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", 409 | "cpu": [ 410 | "x64" 411 | ], 412 | "dev": true, 413 | "license": "MIT", 414 | "optional": true, 415 | "os": [ 416 | "sunos" 417 | ], 418 | "engines": { 419 | "node": ">=18" 420 | } 421 | }, 422 | "node_modules/@esbuild/win32-arm64": { 423 | "version": "0.25.5", 424 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", 425 | "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", 426 | "cpu": [ 427 | "arm64" 428 | ], 429 | "dev": true, 430 | "license": "MIT", 431 | "optional": true, 432 | "os": [ 433 | "win32" 434 | ], 435 | "engines": { 436 | "node": ">=18" 437 | } 438 | }, 439 | "node_modules/@esbuild/win32-ia32": { 440 | "version": "0.25.5", 441 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", 442 | "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", 443 | "cpu": [ 444 | "ia32" 445 | ], 446 | "dev": true, 447 | "license": "MIT", 448 | "optional": true, 449 | "os": [ 450 | "win32" 451 | ], 452 | "engines": { 453 | "node": ">=18" 454 | } 455 | }, 456 | "node_modules/@esbuild/win32-x64": { 457 | "version": "0.25.5", 458 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", 459 | "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", 460 | "cpu": [ 461 | "x64" 462 | ], 463 | "dev": true, 464 | "license": "MIT", 465 | "optional": true, 466 | "os": [ 467 | "win32" 468 | ], 469 | "engines": { 470 | "node": ">=18" 471 | } 472 | }, 473 | "node_modules/@hono/node-server": { 474 | "version": "1.13.7", 475 | "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.13.7.tgz", 476 | "integrity": "sha512-kTfUMsoloVKtRA2fLiGSd9qBddmru9KadNyhJCwgKBxTiNkaAJEwkVN9KV/rS4HtmmNRtUh6P+YpmjRMl0d9vQ==", 477 | "license": "MIT", 478 | "engines": { 479 | "node": ">=18.14.1" 480 | }, 481 | "peerDependencies": { 482 | "hono": "^4" 483 | } 484 | }, 485 | "node_modules/@types/estree": { 486 | "version": "1.0.6", 487 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", 488 | "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", 489 | "dev": true, 490 | "license": "MIT" 491 | }, 492 | "node_modules/@types/node": { 493 | "version": "20.17.10", 494 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.10.tgz", 495 | "integrity": "sha512-/jrvh5h6NXhEauFFexRin69nA0uHJ5gwk4iDivp/DeoEua3uwCUto6PC86IpRITBOs4+6i2I56K5x5b6WYGXHA==", 496 | "dev": true, 497 | "license": "MIT", 498 | "dependencies": { 499 | "undici-types": "~6.19.2" 500 | } 501 | }, 502 | "node_modules/accepts": { 503 | "version": "1.3.8", 504 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 505 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 506 | "license": "MIT", 507 | "dependencies": { 508 | "mime-types": "~2.1.34", 509 | "negotiator": "0.6.3" 510 | }, 511 | "engines": { 512 | "node": ">= 0.6" 513 | } 514 | }, 515 | "node_modules/array-flatten": { 516 | "version": "1.1.1", 517 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 518 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", 519 | "license": "MIT" 520 | }, 521 | "node_modules/asynckit": { 522 | "version": "0.4.0", 523 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 524 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", 525 | "license": "MIT" 526 | }, 527 | "node_modules/axios": { 528 | "version": "1.9.0", 529 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", 530 | "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", 531 | "license": "MIT", 532 | "dependencies": { 533 | "follow-redirects": "^1.15.6", 534 | "form-data": "^4.0.0", 535 | "proxy-from-env": "^1.1.0" 536 | } 537 | }, 538 | "node_modules/axios-better-stacktrace": { 539 | "version": "2.1.7", 540 | "resolved": "https://registry.npmjs.org/axios-better-stacktrace/-/axios-better-stacktrace-2.1.7.tgz", 541 | "integrity": "sha512-m16wNbfb7crBpENBukoBdN1G9NwqSCkuIeKjSEP2iUoFvgNUnSW1/1Ov79EkTu29xmg+TsngJcy2lfwqBzVT7g==", 542 | "license": "MIT", 543 | "peerDependencies": { 544 | "axios": "*" 545 | } 546 | }, 547 | "node_modules/axios-mock-adapter": { 548 | "version": "2.1.0", 549 | "resolved": "https://registry.npmjs.org/axios-mock-adapter/-/axios-mock-adapter-2.1.0.tgz", 550 | "integrity": "sha512-AZUe4OjECGCNNssH8SOdtneiQELsqTsat3SQQCWLPjN436/H+L9AjWfV7bF+Zg/YL9cgbhrz5671hoh+Tbn98w==", 551 | "license": "MIT", 552 | "dependencies": { 553 | "fast-deep-equal": "^3.1.3", 554 | "is-buffer": "^2.0.5" 555 | }, 556 | "peerDependencies": { 557 | "axios": ">= 0.17.0" 558 | } 559 | }, 560 | "node_modules/body-parser": { 561 | "version": "1.20.3", 562 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", 563 | "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", 564 | "license": "MIT", 565 | "dependencies": { 566 | "bytes": "3.1.2", 567 | "content-type": "~1.0.5", 568 | "debug": "2.6.9", 569 | "depd": "2.0.0", 570 | "destroy": "1.2.0", 571 | "http-errors": "2.0.0", 572 | "iconv-lite": "0.4.24", 573 | "on-finished": "2.4.1", 574 | "qs": "6.13.0", 575 | "raw-body": "2.5.2", 576 | "type-is": "~1.6.18", 577 | "unpipe": "1.0.0" 578 | }, 579 | "engines": { 580 | "node": ">= 0.8", 581 | "npm": "1.2.8000 || >= 1.4.16" 582 | } 583 | }, 584 | "node_modules/bytes": { 585 | "version": "3.1.2", 586 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 587 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 588 | "license": "MIT", 589 | "engines": { 590 | "node": ">= 0.8" 591 | } 592 | }, 593 | "node_modules/call-bind-apply-helpers": { 594 | "version": "1.0.1", 595 | "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", 596 | "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", 597 | "license": "MIT", 598 | "dependencies": { 599 | "es-errors": "^1.3.0", 600 | "function-bind": "^1.1.2" 601 | }, 602 | "engines": { 603 | "node": ">= 0.4" 604 | } 605 | }, 606 | "node_modules/call-bound": { 607 | "version": "1.0.3", 608 | "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", 609 | "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", 610 | "license": "MIT", 611 | "dependencies": { 612 | "call-bind-apply-helpers": "^1.0.1", 613 | "get-intrinsic": "^1.2.6" 614 | }, 615 | "engines": { 616 | "node": ">= 0.4" 617 | }, 618 | "funding": { 619 | "url": "https://github.com/sponsors/ljharb" 620 | } 621 | }, 622 | "node_modules/combined-stream": { 623 | "version": "1.0.8", 624 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 625 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 626 | "license": "MIT", 627 | "dependencies": { 628 | "delayed-stream": "~1.0.0" 629 | }, 630 | "engines": { 631 | "node": ">= 0.8" 632 | } 633 | }, 634 | "node_modules/content-disposition": { 635 | "version": "0.5.4", 636 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 637 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 638 | "license": "MIT", 639 | "dependencies": { 640 | "safe-buffer": "5.2.1" 641 | }, 642 | "engines": { 643 | "node": ">= 0.6" 644 | } 645 | }, 646 | "node_modules/content-type": { 647 | "version": "1.0.5", 648 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 649 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 650 | "license": "MIT", 651 | "engines": { 652 | "node": ">= 0.6" 653 | } 654 | }, 655 | "node_modules/cookie": { 656 | "version": "0.7.1", 657 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", 658 | "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", 659 | "license": "MIT", 660 | "engines": { 661 | "node": ">= 0.6" 662 | } 663 | }, 664 | "node_modules/cookie-signature": { 665 | "version": "1.0.6", 666 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 667 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", 668 | "license": "MIT" 669 | }, 670 | "node_modules/debug": { 671 | "version": "2.6.9", 672 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 673 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 674 | "license": "MIT", 675 | "dependencies": { 676 | "ms": "2.0.0" 677 | } 678 | }, 679 | "node_modules/dedent": { 680 | "version": "1.5.3", 681 | "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", 682 | "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", 683 | "dev": true, 684 | "license": "MIT", 685 | "peerDependencies": { 686 | "babel-plugin-macros": "^3.1.0" 687 | }, 688 | "peerDependenciesMeta": { 689 | "babel-plugin-macros": { 690 | "optional": true 691 | } 692 | } 693 | }, 694 | "node_modules/delayed-stream": { 695 | "version": "1.0.0", 696 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 697 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 698 | "license": "MIT", 699 | "engines": { 700 | "node": ">=0.4.0" 701 | } 702 | }, 703 | "node_modules/depd": { 704 | "version": "2.0.0", 705 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 706 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 707 | "license": "MIT", 708 | "engines": { 709 | "node": ">= 0.8" 710 | } 711 | }, 712 | "node_modules/destroy": { 713 | "version": "1.2.0", 714 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 715 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", 716 | "license": "MIT", 717 | "engines": { 718 | "node": ">= 0.8", 719 | "npm": "1.2.8000 || >= 1.4.16" 720 | } 721 | }, 722 | "node_modules/dotenv": { 723 | "version": "16.4.7", 724 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", 725 | "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", 726 | "license": "BSD-2-Clause", 727 | "engines": { 728 | "node": ">=12" 729 | }, 730 | "funding": { 731 | "url": "https://dotenvx.com" 732 | } 733 | }, 734 | "node_modules/dunder-proto": { 735 | "version": "1.0.1", 736 | "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", 737 | "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", 738 | "license": "MIT", 739 | "dependencies": { 740 | "call-bind-apply-helpers": "^1.0.1", 741 | "es-errors": "^1.3.0", 742 | "gopd": "^1.2.0" 743 | }, 744 | "engines": { 745 | "node": ">= 0.4" 746 | } 747 | }, 748 | "node_modules/ee-first": { 749 | "version": "1.1.1", 750 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 751 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", 752 | "license": "MIT" 753 | }, 754 | "node_modules/encodeurl": { 755 | "version": "2.0.0", 756 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", 757 | "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", 758 | "license": "MIT", 759 | "engines": { 760 | "node": ">= 0.8" 761 | } 762 | }, 763 | "node_modules/es-define-property": { 764 | "version": "1.0.1", 765 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", 766 | "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", 767 | "license": "MIT", 768 | "engines": { 769 | "node": ">= 0.4" 770 | } 771 | }, 772 | "node_modules/es-errors": { 773 | "version": "1.3.0", 774 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 775 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 776 | "license": "MIT", 777 | "engines": { 778 | "node": ">= 0.4" 779 | } 780 | }, 781 | "node_modules/es-object-atoms": { 782 | "version": "1.0.0", 783 | "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", 784 | "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", 785 | "license": "MIT", 786 | "dependencies": { 787 | "es-errors": "^1.3.0" 788 | }, 789 | "engines": { 790 | "node": ">= 0.4" 791 | } 792 | }, 793 | "node_modules/esbuild": { 794 | "version": "0.25.5", 795 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", 796 | "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", 797 | "dev": true, 798 | "hasInstallScript": true, 799 | "license": "MIT", 800 | "bin": { 801 | "esbuild": "bin/esbuild" 802 | }, 803 | "engines": { 804 | "node": ">=18" 805 | }, 806 | "optionalDependencies": { 807 | "@esbuild/aix-ppc64": "0.25.5", 808 | "@esbuild/android-arm": "0.25.5", 809 | "@esbuild/android-arm64": "0.25.5", 810 | "@esbuild/android-x64": "0.25.5", 811 | "@esbuild/darwin-arm64": "0.25.5", 812 | "@esbuild/darwin-x64": "0.25.5", 813 | "@esbuild/freebsd-arm64": "0.25.5", 814 | "@esbuild/freebsd-x64": "0.25.5", 815 | "@esbuild/linux-arm": "0.25.5", 816 | "@esbuild/linux-arm64": "0.25.5", 817 | "@esbuild/linux-ia32": "0.25.5", 818 | "@esbuild/linux-loong64": "0.25.5", 819 | "@esbuild/linux-mips64el": "0.25.5", 820 | "@esbuild/linux-ppc64": "0.25.5", 821 | "@esbuild/linux-riscv64": "0.25.5", 822 | "@esbuild/linux-s390x": "0.25.5", 823 | "@esbuild/linux-x64": "0.25.5", 824 | "@esbuild/netbsd-arm64": "0.25.5", 825 | "@esbuild/netbsd-x64": "0.25.5", 826 | "@esbuild/openbsd-arm64": "0.25.5", 827 | "@esbuild/openbsd-x64": "0.25.5", 828 | "@esbuild/sunos-x64": "0.25.5", 829 | "@esbuild/win32-arm64": "0.25.5", 830 | "@esbuild/win32-ia32": "0.25.5", 831 | "@esbuild/win32-x64": "0.25.5" 832 | } 833 | }, 834 | "node_modules/escape-html": { 835 | "version": "1.0.3", 836 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 837 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", 838 | "license": "MIT" 839 | }, 840 | "node_modules/etag": { 841 | "version": "1.8.1", 842 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 843 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 844 | "license": "MIT", 845 | "engines": { 846 | "node": ">= 0.6" 847 | } 848 | }, 849 | "node_modules/express": { 850 | "version": "4.21.2", 851 | "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", 852 | "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", 853 | "license": "MIT", 854 | "dependencies": { 855 | "accepts": "~1.3.8", 856 | "array-flatten": "1.1.1", 857 | "body-parser": "1.20.3", 858 | "content-disposition": "0.5.4", 859 | "content-type": "~1.0.4", 860 | "cookie": "0.7.1", 861 | "cookie-signature": "1.0.6", 862 | "debug": "2.6.9", 863 | "depd": "2.0.0", 864 | "encodeurl": "~2.0.0", 865 | "escape-html": "~1.0.3", 866 | "etag": "~1.8.1", 867 | "finalhandler": "1.3.1", 868 | "fresh": "0.5.2", 869 | "http-errors": "2.0.0", 870 | "merge-descriptors": "1.0.3", 871 | "methods": "~1.1.2", 872 | "on-finished": "2.4.1", 873 | "parseurl": "~1.3.3", 874 | "path-to-regexp": "0.1.12", 875 | "proxy-addr": "~2.0.7", 876 | "qs": "6.13.0", 877 | "range-parser": "~1.2.1", 878 | "safe-buffer": "5.2.1", 879 | "send": "0.19.0", 880 | "serve-static": "1.16.2", 881 | "setprototypeof": "1.2.0", 882 | "statuses": "2.0.1", 883 | "type-is": "~1.6.18", 884 | "utils-merge": "1.0.1", 885 | "vary": "~1.1.2" 886 | }, 887 | "engines": { 888 | "node": ">= 0.10.0" 889 | }, 890 | "funding": { 891 | "type": "opencollective", 892 | "url": "https://opencollective.com/express" 893 | } 894 | }, 895 | "node_modules/fast-deep-equal": { 896 | "version": "3.1.3", 897 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 898 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 899 | "license": "MIT" 900 | }, 901 | "node_modules/finalhandler": { 902 | "version": "1.3.1", 903 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", 904 | "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", 905 | "license": "MIT", 906 | "dependencies": { 907 | "debug": "2.6.9", 908 | "encodeurl": "~2.0.0", 909 | "escape-html": "~1.0.3", 910 | "on-finished": "2.4.1", 911 | "parseurl": "~1.3.3", 912 | "statuses": "2.0.1", 913 | "unpipe": "~1.0.0" 914 | }, 915 | "engines": { 916 | "node": ">= 0.8" 917 | } 918 | }, 919 | "node_modules/find-up-simple": { 920 | "version": "1.0.0", 921 | "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.0.tgz", 922 | "integrity": "sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==", 923 | "dev": true, 924 | "license": "MIT", 925 | "engines": { 926 | "node": ">=18" 927 | }, 928 | "funding": { 929 | "url": "https://github.com/sponsors/sindresorhus" 930 | } 931 | }, 932 | "node_modules/follow-redirects": { 933 | "version": "1.15.9", 934 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", 935 | "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", 936 | "funding": [ 937 | { 938 | "type": "individual", 939 | "url": "https://github.com/sponsors/RubenVerborgh" 940 | } 941 | ], 942 | "license": "MIT", 943 | "engines": { 944 | "node": ">=4.0" 945 | }, 946 | "peerDependenciesMeta": { 947 | "debug": { 948 | "optional": true 949 | } 950 | } 951 | }, 952 | "node_modules/form-data": { 953 | "version": "4.0.1", 954 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", 955 | "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", 956 | "license": "MIT", 957 | "dependencies": { 958 | "asynckit": "^0.4.0", 959 | "combined-stream": "^1.0.8", 960 | "mime-types": "^2.1.12" 961 | }, 962 | "engines": { 963 | "node": ">= 6" 964 | } 965 | }, 966 | "node_modules/forwarded": { 967 | "version": "0.2.0", 968 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 969 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 970 | "license": "MIT", 971 | "engines": { 972 | "node": ">= 0.6" 973 | } 974 | }, 975 | "node_modules/fresh": { 976 | "version": "0.5.2", 977 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 978 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", 979 | "license": "MIT", 980 | "engines": { 981 | "node": ">= 0.6" 982 | } 983 | }, 984 | "node_modules/fsevents": { 985 | "version": "2.3.3", 986 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 987 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 988 | "dev": true, 989 | "hasInstallScript": true, 990 | "license": "MIT", 991 | "optional": true, 992 | "os": [ 993 | "darwin" 994 | ], 995 | "engines": { 996 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 997 | } 998 | }, 999 | "node_modules/function-bind": { 1000 | "version": "1.1.2", 1001 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 1002 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 1003 | "license": "MIT", 1004 | "funding": { 1005 | "url": "https://github.com/sponsors/ljharb" 1006 | } 1007 | }, 1008 | "node_modules/get-intrinsic": { 1009 | "version": "1.2.6", 1010 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", 1011 | "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", 1012 | "license": "MIT", 1013 | "dependencies": { 1014 | "call-bind-apply-helpers": "^1.0.1", 1015 | "dunder-proto": "^1.0.0", 1016 | "es-define-property": "^1.0.1", 1017 | "es-errors": "^1.3.0", 1018 | "es-object-atoms": "^1.0.0", 1019 | "function-bind": "^1.1.2", 1020 | "gopd": "^1.2.0", 1021 | "has-symbols": "^1.1.0", 1022 | "hasown": "^2.0.2", 1023 | "math-intrinsics": "^1.0.0" 1024 | }, 1025 | "engines": { 1026 | "node": ">= 0.4" 1027 | }, 1028 | "funding": { 1029 | "url": "https://github.com/sponsors/ljharb" 1030 | } 1031 | }, 1032 | "node_modules/get-tsconfig": { 1033 | "version": "4.8.1", 1034 | "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", 1035 | "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==", 1036 | "dev": true, 1037 | "license": "MIT", 1038 | "dependencies": { 1039 | "resolve-pkg-maps": "^1.0.0" 1040 | }, 1041 | "funding": { 1042 | "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" 1043 | } 1044 | }, 1045 | "node_modules/gopd": { 1046 | "version": "1.2.0", 1047 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", 1048 | "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", 1049 | "license": "MIT", 1050 | "engines": { 1051 | "node": ">= 0.4" 1052 | }, 1053 | "funding": { 1054 | "url": "https://github.com/sponsors/ljharb" 1055 | } 1056 | }, 1057 | "node_modules/has-symbols": { 1058 | "version": "1.1.0", 1059 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", 1060 | "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", 1061 | "license": "MIT", 1062 | "engines": { 1063 | "node": ">= 0.4" 1064 | }, 1065 | "funding": { 1066 | "url": "https://github.com/sponsors/ljharb" 1067 | } 1068 | }, 1069 | "node_modules/hasown": { 1070 | "version": "2.0.2", 1071 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 1072 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 1073 | "license": "MIT", 1074 | "dependencies": { 1075 | "function-bind": "^1.1.2" 1076 | }, 1077 | "engines": { 1078 | "node": ">= 0.4" 1079 | } 1080 | }, 1081 | "node_modules/hono": { 1082 | "version": "4.6.14", 1083 | "resolved": "https://registry.npmjs.org/hono/-/hono-4.6.14.tgz", 1084 | "integrity": "sha512-j4VkyUp2xazGJ8eCCLN1Vm/bxdvm/j5ZuU9AIjLu9vapn2M44p9L3Ktr9Vnb2RN2QtcR/wVjZVMlT5k7GJQgPw==", 1085 | "license": "MIT", 1086 | "engines": { 1087 | "node": ">=16.9.0" 1088 | } 1089 | }, 1090 | "node_modules/http-errors": { 1091 | "version": "2.0.0", 1092 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 1093 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 1094 | "license": "MIT", 1095 | "dependencies": { 1096 | "depd": "2.0.0", 1097 | "inherits": "2.0.4", 1098 | "setprototypeof": "1.2.0", 1099 | "statuses": "2.0.1", 1100 | "toidentifier": "1.0.1" 1101 | }, 1102 | "engines": { 1103 | "node": ">= 0.8" 1104 | } 1105 | }, 1106 | "node_modules/iconv-lite": { 1107 | "version": "0.4.24", 1108 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 1109 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 1110 | "license": "MIT", 1111 | "dependencies": { 1112 | "safer-buffer": ">= 2.1.2 < 3" 1113 | }, 1114 | "engines": { 1115 | "node": ">=0.10.0" 1116 | } 1117 | }, 1118 | "node_modules/inherits": { 1119 | "version": "2.0.4", 1120 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1121 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 1122 | "license": "ISC" 1123 | }, 1124 | "node_modules/ipaddr.js": { 1125 | "version": "1.9.1", 1126 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 1127 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 1128 | "license": "MIT", 1129 | "engines": { 1130 | "node": ">= 0.10" 1131 | } 1132 | }, 1133 | "node_modules/is-buffer": { 1134 | "version": "2.0.5", 1135 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", 1136 | "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", 1137 | "funding": [ 1138 | { 1139 | "type": "github", 1140 | "url": "https://github.com/sponsors/feross" 1141 | }, 1142 | { 1143 | "type": "patreon", 1144 | "url": "https://www.patreon.com/feross" 1145 | }, 1146 | { 1147 | "type": "consulting", 1148 | "url": "https://feross.org/support" 1149 | } 1150 | ], 1151 | "license": "MIT", 1152 | "engines": { 1153 | "node": ">=4" 1154 | } 1155 | }, 1156 | "node_modules/jose": { 1157 | "version": "5.9.6", 1158 | "resolved": "https://registry.npmjs.org/jose/-/jose-5.9.6.tgz", 1159 | "integrity": "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==", 1160 | "license": "MIT", 1161 | "funding": { 1162 | "url": "https://github.com/sponsors/panva" 1163 | } 1164 | }, 1165 | "node_modules/lowdb": { 1166 | "version": "7.0.1", 1167 | "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-7.0.1.tgz", 1168 | "integrity": "sha512-neJAj8GwF0e8EpycYIDFqEPcx9Qz4GUho20jWFR7YiFeXzF1YMLdxB36PypcTSPMA+4+LvgyMacYhlr18Zlymw==", 1169 | "license": "MIT", 1170 | "dependencies": { 1171 | "steno": "^4.0.2" 1172 | }, 1173 | "engines": { 1174 | "node": ">=18" 1175 | }, 1176 | "funding": { 1177 | "url": "https://github.com/sponsors/typicode" 1178 | } 1179 | }, 1180 | "node_modules/math-intrinsics": { 1181 | "version": "1.1.0", 1182 | "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", 1183 | "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", 1184 | "license": "MIT", 1185 | "engines": { 1186 | "node": ">= 0.4" 1187 | } 1188 | }, 1189 | "node_modules/media-typer": { 1190 | "version": "0.3.0", 1191 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 1192 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", 1193 | "license": "MIT", 1194 | "engines": { 1195 | "node": ">= 0.6" 1196 | } 1197 | }, 1198 | "node_modules/merge-descriptors": { 1199 | "version": "1.0.3", 1200 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", 1201 | "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", 1202 | "license": "MIT", 1203 | "funding": { 1204 | "url": "https://github.com/sponsors/sindresorhus" 1205 | } 1206 | }, 1207 | "node_modules/methods": { 1208 | "version": "1.1.2", 1209 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 1210 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", 1211 | "license": "MIT", 1212 | "engines": { 1213 | "node": ">= 0.6" 1214 | } 1215 | }, 1216 | "node_modules/micro-memoize": { 1217 | "version": "4.1.2", 1218 | "resolved": "https://registry.npmjs.org/micro-memoize/-/micro-memoize-4.1.2.tgz", 1219 | "integrity": "sha512-+HzcV2H+rbSJzApgkj0NdTakkC+bnyeiUxgT6/m7mjcz1CmM22KYFKp+EVj1sWe4UYcnriJr5uqHQD/gMHLD+g==", 1220 | "dev": true, 1221 | "license": "MIT" 1222 | }, 1223 | "node_modules/mime": { 1224 | "version": "1.6.0", 1225 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 1226 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 1227 | "license": "MIT", 1228 | "bin": { 1229 | "mime": "cli.js" 1230 | }, 1231 | "engines": { 1232 | "node": ">=4" 1233 | } 1234 | }, 1235 | "node_modules/mime-db": { 1236 | "version": "1.52.0", 1237 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 1238 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 1239 | "license": "MIT", 1240 | "engines": { 1241 | "node": ">= 0.6" 1242 | } 1243 | }, 1244 | "node_modules/mime-types": { 1245 | "version": "2.1.35", 1246 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 1247 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 1248 | "license": "MIT", 1249 | "dependencies": { 1250 | "mime-db": "1.52.0" 1251 | }, 1252 | "engines": { 1253 | "node": ">= 0.6" 1254 | } 1255 | }, 1256 | "node_modules/ms": { 1257 | "version": "2.0.0", 1258 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1259 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", 1260 | "license": "MIT" 1261 | }, 1262 | "node_modules/negotiator": { 1263 | "version": "0.6.3", 1264 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 1265 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", 1266 | "license": "MIT", 1267 | "engines": { 1268 | "node": ">= 0.6" 1269 | } 1270 | }, 1271 | "node_modules/object-inspect": { 1272 | "version": "1.13.3", 1273 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", 1274 | "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", 1275 | "license": "MIT", 1276 | "engines": { 1277 | "node": ">= 0.4" 1278 | }, 1279 | "funding": { 1280 | "url": "https://github.com/sponsors/ljharb" 1281 | } 1282 | }, 1283 | "node_modules/on-finished": { 1284 | "version": "2.4.1", 1285 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 1286 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 1287 | "license": "MIT", 1288 | "dependencies": { 1289 | "ee-first": "1.1.1" 1290 | }, 1291 | "engines": { 1292 | "node": ">= 0.8" 1293 | } 1294 | }, 1295 | "node_modules/package-up": { 1296 | "version": "5.0.0", 1297 | "resolved": "https://registry.npmjs.org/package-up/-/package-up-5.0.0.tgz", 1298 | "integrity": "sha512-MQEgDUvXCa3sGvqHg3pzHO8e9gqTCMPVrWUko3vPQGntwegmFo52mZb2abIVTjFnUcW0BcPz0D93jV5Cas1DWA==", 1299 | "dev": true, 1300 | "license": "MIT", 1301 | "dependencies": { 1302 | "find-up-simple": "^1.0.0" 1303 | }, 1304 | "engines": { 1305 | "node": ">=18" 1306 | }, 1307 | "funding": { 1308 | "url": "https://github.com/sponsors/sindresorhus" 1309 | } 1310 | }, 1311 | "node_modules/parseurl": { 1312 | "version": "1.3.3", 1313 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 1314 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 1315 | "license": "MIT", 1316 | "engines": { 1317 | "node": ">= 0.8" 1318 | } 1319 | }, 1320 | "node_modules/path-to-regexp": { 1321 | "version": "0.1.12", 1322 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", 1323 | "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", 1324 | "license": "MIT" 1325 | }, 1326 | "node_modules/prettier": { 1327 | "version": "3.4.2", 1328 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", 1329 | "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", 1330 | "dev": true, 1331 | "license": "MIT", 1332 | "bin": { 1333 | "prettier": "bin/prettier.cjs" 1334 | }, 1335 | "engines": { 1336 | "node": ">=14" 1337 | }, 1338 | "funding": { 1339 | "url": "https://github.com/prettier/prettier?sponsor=1" 1340 | } 1341 | }, 1342 | "node_modules/prettier-plugin-embed": { 1343 | "version": "0.4.15", 1344 | "resolved": "https://registry.npmjs.org/prettier-plugin-embed/-/prettier-plugin-embed-0.4.15.tgz", 1345 | "integrity": "sha512-9pZVIp3bw2jw+Ge+iAMZ4j+sIVC9cPruZ93H2tj5Wa/3YDFDJ/uYyVWdUGfcFUnv28drhW2Bmome9xSGXsPKOw==", 1346 | "dev": true, 1347 | "license": "MIT", 1348 | "dependencies": { 1349 | "@types/estree": "^1.0.5", 1350 | "dedent": "^1.5.1", 1351 | "micro-memoize": "^4.1.2", 1352 | "package-up": "^5.0.0", 1353 | "tiny-jsonc": "^1.0.1", 1354 | "type-fest": "^4.10.3" 1355 | } 1356 | }, 1357 | "node_modules/proxy-addr": { 1358 | "version": "2.0.7", 1359 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 1360 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 1361 | "license": "MIT", 1362 | "dependencies": { 1363 | "forwarded": "0.2.0", 1364 | "ipaddr.js": "1.9.1" 1365 | }, 1366 | "engines": { 1367 | "node": ">= 0.10" 1368 | } 1369 | }, 1370 | "node_modules/proxy-from-env": { 1371 | "version": "1.1.0", 1372 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 1373 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", 1374 | "license": "MIT" 1375 | }, 1376 | "node_modules/qs": { 1377 | "version": "6.13.0", 1378 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", 1379 | "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", 1380 | "license": "BSD-3-Clause", 1381 | "dependencies": { 1382 | "side-channel": "^1.0.6" 1383 | }, 1384 | "engines": { 1385 | "node": ">=0.6" 1386 | }, 1387 | "funding": { 1388 | "url": "https://github.com/sponsors/ljharb" 1389 | } 1390 | }, 1391 | "node_modules/range-parser": { 1392 | "version": "1.2.1", 1393 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 1394 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 1395 | "license": "MIT", 1396 | "engines": { 1397 | "node": ">= 0.6" 1398 | } 1399 | }, 1400 | "node_modules/raw-body": { 1401 | "version": "2.5.2", 1402 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", 1403 | "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", 1404 | "license": "MIT", 1405 | "dependencies": { 1406 | "bytes": "3.1.2", 1407 | "http-errors": "2.0.0", 1408 | "iconv-lite": "0.4.24", 1409 | "unpipe": "1.0.0" 1410 | }, 1411 | "engines": { 1412 | "node": ">= 0.8" 1413 | } 1414 | }, 1415 | "node_modules/resolve-pkg-maps": { 1416 | "version": "1.0.0", 1417 | "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", 1418 | "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", 1419 | "dev": true, 1420 | "license": "MIT", 1421 | "funding": { 1422 | "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" 1423 | } 1424 | }, 1425 | "node_modules/safe-buffer": { 1426 | "version": "5.2.1", 1427 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1428 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 1429 | "funding": [ 1430 | { 1431 | "type": "github", 1432 | "url": "https://github.com/sponsors/feross" 1433 | }, 1434 | { 1435 | "type": "patreon", 1436 | "url": "https://www.patreon.com/feross" 1437 | }, 1438 | { 1439 | "type": "consulting", 1440 | "url": "https://feross.org/support" 1441 | } 1442 | ], 1443 | "license": "MIT" 1444 | }, 1445 | "node_modules/safer-buffer": { 1446 | "version": "2.1.2", 1447 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1448 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 1449 | "license": "MIT" 1450 | }, 1451 | "node_modules/send": { 1452 | "version": "0.19.0", 1453 | "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", 1454 | "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", 1455 | "license": "MIT", 1456 | "dependencies": { 1457 | "debug": "2.6.9", 1458 | "depd": "2.0.0", 1459 | "destroy": "1.2.0", 1460 | "encodeurl": "~1.0.2", 1461 | "escape-html": "~1.0.3", 1462 | "etag": "~1.8.1", 1463 | "fresh": "0.5.2", 1464 | "http-errors": "2.0.0", 1465 | "mime": "1.6.0", 1466 | "ms": "2.1.3", 1467 | "on-finished": "2.4.1", 1468 | "range-parser": "~1.2.1", 1469 | "statuses": "2.0.1" 1470 | }, 1471 | "engines": { 1472 | "node": ">= 0.8.0" 1473 | } 1474 | }, 1475 | "node_modules/send/node_modules/encodeurl": { 1476 | "version": "1.0.2", 1477 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 1478 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", 1479 | "license": "MIT", 1480 | "engines": { 1481 | "node": ">= 0.8" 1482 | } 1483 | }, 1484 | "node_modules/send/node_modules/ms": { 1485 | "version": "2.1.3", 1486 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1487 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 1488 | "license": "MIT" 1489 | }, 1490 | "node_modules/serve-static": { 1491 | "version": "1.16.2", 1492 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", 1493 | "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", 1494 | "license": "MIT", 1495 | "dependencies": { 1496 | "encodeurl": "~2.0.0", 1497 | "escape-html": "~1.0.3", 1498 | "parseurl": "~1.3.3", 1499 | "send": "0.19.0" 1500 | }, 1501 | "engines": { 1502 | "node": ">= 0.8.0" 1503 | } 1504 | }, 1505 | "node_modules/setprototypeof": { 1506 | "version": "1.2.0", 1507 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 1508 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", 1509 | "license": "ISC" 1510 | }, 1511 | "node_modules/side-channel": { 1512 | "version": "1.1.0", 1513 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", 1514 | "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", 1515 | "license": "MIT", 1516 | "dependencies": { 1517 | "es-errors": "^1.3.0", 1518 | "object-inspect": "^1.13.3", 1519 | "side-channel-list": "^1.0.0", 1520 | "side-channel-map": "^1.0.1", 1521 | "side-channel-weakmap": "^1.0.2" 1522 | }, 1523 | "engines": { 1524 | "node": ">= 0.4" 1525 | }, 1526 | "funding": { 1527 | "url": "https://github.com/sponsors/ljharb" 1528 | } 1529 | }, 1530 | "node_modules/side-channel-list": { 1531 | "version": "1.0.0", 1532 | "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", 1533 | "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", 1534 | "license": "MIT", 1535 | "dependencies": { 1536 | "es-errors": "^1.3.0", 1537 | "object-inspect": "^1.13.3" 1538 | }, 1539 | "engines": { 1540 | "node": ">= 0.4" 1541 | }, 1542 | "funding": { 1543 | "url": "https://github.com/sponsors/ljharb" 1544 | } 1545 | }, 1546 | "node_modules/side-channel-map": { 1547 | "version": "1.0.1", 1548 | "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", 1549 | "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", 1550 | "license": "MIT", 1551 | "dependencies": { 1552 | "call-bound": "^1.0.2", 1553 | "es-errors": "^1.3.0", 1554 | "get-intrinsic": "^1.2.5", 1555 | "object-inspect": "^1.13.3" 1556 | }, 1557 | "engines": { 1558 | "node": ">= 0.4" 1559 | }, 1560 | "funding": { 1561 | "url": "https://github.com/sponsors/ljharb" 1562 | } 1563 | }, 1564 | "node_modules/side-channel-weakmap": { 1565 | "version": "1.0.2", 1566 | "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", 1567 | "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", 1568 | "license": "MIT", 1569 | "dependencies": { 1570 | "call-bound": "^1.0.2", 1571 | "es-errors": "^1.3.0", 1572 | "get-intrinsic": "^1.2.5", 1573 | "object-inspect": "^1.13.3", 1574 | "side-channel-map": "^1.0.1" 1575 | }, 1576 | "engines": { 1577 | "node": ">= 0.4" 1578 | }, 1579 | "funding": { 1580 | "url": "https://github.com/sponsors/ljharb" 1581 | } 1582 | }, 1583 | "node_modules/statuses": { 1584 | "version": "2.0.1", 1585 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 1586 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 1587 | "license": "MIT", 1588 | "engines": { 1589 | "node": ">= 0.8" 1590 | } 1591 | }, 1592 | "node_modules/steno": { 1593 | "version": "4.0.2", 1594 | "resolved": "https://registry.npmjs.org/steno/-/steno-4.0.2.tgz", 1595 | "integrity": "sha512-yhPIQXjrlt1xv7dyPQg2P17URmXbuM5pdGkpiMB3RenprfiBlvK415Lctfe0eshk90oA7/tNq7WEiMK8RSP39A==", 1596 | "license": "MIT", 1597 | "engines": { 1598 | "node": ">=18" 1599 | }, 1600 | "funding": { 1601 | "url": "https://github.com/sponsors/typicode" 1602 | } 1603 | }, 1604 | "node_modules/tiny-jsonc": { 1605 | "version": "1.0.1", 1606 | "resolved": "https://registry.npmjs.org/tiny-jsonc/-/tiny-jsonc-1.0.1.tgz", 1607 | "integrity": "sha512-ik6BCxzva9DoiEfDX/li0L2cWKPPENYvixUprFdl3YPi4bZZUhDnNI9YUkacrv+uIG90dnxR5mNqaoD6UhD6Bw==", 1608 | "dev": true 1609 | }, 1610 | "node_modules/toidentifier": { 1611 | "version": "1.0.1", 1612 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 1613 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 1614 | "license": "MIT", 1615 | "engines": { 1616 | "node": ">=0.6" 1617 | } 1618 | }, 1619 | "node_modules/tsx": { 1620 | "version": "4.19.4", 1621 | "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.4.tgz", 1622 | "integrity": "sha512-gK5GVzDkJK1SI1zwHf32Mqxf2tSJkNx+eYcNly5+nHvWqXUJYUkWBQtKauoESz3ymezAI++ZwT855x5p5eop+Q==", 1623 | "dev": true, 1624 | "license": "MIT", 1625 | "dependencies": { 1626 | "esbuild": "~0.25.0", 1627 | "get-tsconfig": "^4.7.5" 1628 | }, 1629 | "bin": { 1630 | "tsx": "dist/cli.mjs" 1631 | }, 1632 | "engines": { 1633 | "node": ">=18.0.0" 1634 | }, 1635 | "optionalDependencies": { 1636 | "fsevents": "~2.3.3" 1637 | } 1638 | }, 1639 | "node_modules/type-fest": { 1640 | "version": "4.31.0", 1641 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.31.0.tgz", 1642 | "integrity": "sha512-yCxltHW07Nkhv/1F6wWBr8kz+5BGMfP+RbRSYFnegVb0qV/UMT0G0ElBloPVerqn4M2ZV80Ir1FtCcYv1cT6vQ==", 1643 | "dev": true, 1644 | "license": "(MIT OR CC0-1.0)", 1645 | "engines": { 1646 | "node": ">=16" 1647 | }, 1648 | "funding": { 1649 | "url": "https://github.com/sponsors/sindresorhus" 1650 | } 1651 | }, 1652 | "node_modules/type-is": { 1653 | "version": "1.6.18", 1654 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 1655 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 1656 | "license": "MIT", 1657 | "dependencies": { 1658 | "media-typer": "0.3.0", 1659 | "mime-types": "~2.1.24" 1660 | }, 1661 | "engines": { 1662 | "node": ">= 0.6" 1663 | } 1664 | }, 1665 | "node_modules/typescript": { 1666 | "version": "5.7.2", 1667 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", 1668 | "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", 1669 | "license": "Apache-2.0", 1670 | "bin": { 1671 | "tsc": "bin/tsc", 1672 | "tsserver": "bin/tsserver" 1673 | }, 1674 | "engines": { 1675 | "node": ">=14.17" 1676 | } 1677 | }, 1678 | "node_modules/undici-types": { 1679 | "version": "6.19.8", 1680 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", 1681 | "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", 1682 | "dev": true, 1683 | "license": "MIT" 1684 | }, 1685 | "node_modules/unpipe": { 1686 | "version": "1.0.0", 1687 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1688 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 1689 | "license": "MIT", 1690 | "engines": { 1691 | "node": ">= 0.8" 1692 | } 1693 | }, 1694 | "node_modules/utils-merge": { 1695 | "version": "1.0.1", 1696 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1697 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", 1698 | "license": "MIT", 1699 | "engines": { 1700 | "node": ">= 0.4.0" 1701 | } 1702 | }, 1703 | "node_modules/vary": { 1704 | "version": "1.1.2", 1705 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1706 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 1707 | "license": "MIT", 1708 | "engines": { 1709 | "node": ">= 0.8" 1710 | } 1711 | } 1712 | } 1713 | } 1714 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "type": "module", 4 | "scripts": { 5 | "dev": "tsx watch src/index.ts" 6 | }, 7 | "dependencies": { 8 | "@corbado/node-sdk": "^3.0.2", 9 | "@hono/node-server": "^1.13.7", 10 | "dotenv": "^16.4.7", 11 | "hono": "^4.6.14", 12 | "lowdb": "^7.0.1" 13 | }, 14 | "devDependencies": { 15 | "@corbado/types": "^2.11.2", 16 | "@types/node": "^20.11.17", 17 | "prettier": "^3.4.2", 18 | "prettier-plugin-embed": "^0.4.15", 19 | "tsx": "^4.7.1" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /backend/src/app.ts: -------------------------------------------------------------------------------- 1 | import type { Hono as HonoApplication } from "hono"; 2 | import { Hono } from "hono"; 3 | import { serve } from "@hono/node-server"; 4 | import { logger } from "hono/logger"; 5 | import { compress } from "hono/compress"; 6 | import { authenticationMiddleware } from "./middleware/auth.js"; 7 | import indexRoutes from "./routes/index.js"; 8 | import { cors } from "hono/cors"; 9 | 10 | class App { 11 | public readonly app: HonoApplication; 12 | 13 | constructor() { 14 | this.app = new Hono(); 15 | this.initializeMiddlewares(); 16 | this.initializeRoutes(); 17 | } 18 | 19 | public listen() { 20 | const port = Number(process.env.PORT) || 3001; 21 | console.log(`Server is running on http://localhost:${port}`); 22 | serve({ 23 | fetch: this.app.fetch, 24 | port, 25 | }); 26 | } 27 | 28 | private initializeMiddlewares() { 29 | this.app.use(logger()); 30 | this.app.use(compress()); 31 | this.app.use( 32 | cors({ 33 | origin: "http://localhost:3000", 34 | credentials: true, 35 | }), 36 | ); 37 | this.app.use(authenticationMiddleware); 38 | } 39 | 40 | private initializeRoutes() { 41 | this.app.route("/", indexRoutes); 42 | } 43 | } 44 | 45 | export default new App(); 46 | -------------------------------------------------------------------------------- /backend/src/db/db.ts: -------------------------------------------------------------------------------- 1 | import { JSONFilePreset } from "lowdb/node"; 2 | 3 | export interface User { 4 | id: string; 5 | corbado_user_id: string; 6 | city: string | null; 7 | } 8 | 9 | // use lowdb for a simple in-memory json database, that persists to disk 10 | const defaultData: { users: User[] } = { users: [] }; 11 | const db = await JSONFilePreset(`${process.cwd()}/db.json`, defaultData); 12 | 13 | 14 | export default db; 15 | -------------------------------------------------------------------------------- /backend/src/db/queries.ts: -------------------------------------------------------------------------------- 1 | // userModel.ts 2 | import db, { type User } from "./db.js"; 3 | 4 | /** 5 | * Retrieves a user by their corbadoUserId. 6 | * @param corbadoUserId - The unique corbadoUserId of the user. 7 | * @returns The user object or undefined if not found. 8 | */ 9 | export function getUser(corbadoUserId?: string): User | undefined { 10 | return db.data.users.find((user) => user.corbado_user_id === corbadoUserId); 11 | } 12 | 13 | /** 14 | * Inserts a new user into the database. 15 | * @param corbadoUserId - The unique corbadoUserId of the user. 16 | * @returns The newly created user object. 17 | */ 18 | export async function insertUser(corbadoUserId: string) { 19 | // check if user already exists 20 | if (getUser(corbadoUserId)) { 21 | throw new Error("User already exists"); 22 | } 23 | const user: User = { 24 | id: crypto.randomUUID(), 25 | corbado_user_id: corbadoUserId, 26 | city: null, 27 | }; 28 | await db.update(({ users }) => users.push(user)); 29 | return user; 30 | } 31 | 32 | /** 33 | * Updates the city of a user identified by corbadoUserId. 34 | * @param corbadoUserId - The unique corbadoUserId of the user. 35 | * @param city - The new city to set. 36 | */ 37 | export async function updateUserCity(corbadoUserId: string, city: string) { 38 | await db.update(({ users }) => { 39 | const user = users.find( 40 | (user) => user.corbado_user_id === corbadoUserId, 41 | ); 42 | if (user) { 43 | user.city = city; 44 | } 45 | }); 46 | } 47 | -------------------------------------------------------------------------------- /backend/src/index.ts: -------------------------------------------------------------------------------- 1 | import "./utils/env.js"; 2 | import app from "./app.js"; 3 | 4 | app.listen(); -------------------------------------------------------------------------------- /backend/src/middleware/auth.ts: -------------------------------------------------------------------------------- 1 | import { createMiddleware } from "hono/factory"; 2 | import { 3 | getAuthenticatedUserFromAuthorizationHeader, 4 | getAuthenticatedUserFromCookie, 5 | } from "../utils/authentication.js"; 6 | import { HTTPException } from 'hono/http-exception'; 7 | 8 | type User = { userId: string; fullName: string }; 9 | type Env = { Variables: { user: User | null } }; 10 | 11 | export const authenticationMiddleware = createMiddleware( 12 | async (c, next) => { 13 | let user = await getAuthenticatedUserFromCookie(c); 14 | user ??= await getAuthenticatedUserFromAuthorizationHeader(c); 15 | c.set("user", user); 16 | await next(); 17 | }, 18 | ); 19 | 20 | export const requireAuth = createMiddleware( 21 | async (c, next) => { 22 | if (!c.get("user")) { 23 | throw new HTTPException(401, { message: "Authentication required" }); 24 | } 25 | await next(); 26 | } 27 | ); -------------------------------------------------------------------------------- /backend/src/routes/api.routes.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from "hono"; 2 | import secretRoutes from "./secret.routes.js"; 3 | import userRoutes from "./user.routes.js"; 4 | 5 | export default new Hono() 6 | .route("/secret", secretRoutes) 7 | .route("/user", userRoutes); 8 | -------------------------------------------------------------------------------- /backend/src/routes/index.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from "hono"; 2 | import apiRoutes from "./api.routes.js"; 3 | 4 | export default new Hono().route("/api", apiRoutes); 5 | -------------------------------------------------------------------------------- /backend/src/routes/secret.routes.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from "hono"; 2 | import { requireAuth } from "../middleware/auth.js"; 3 | 4 | const secretString = "Passkeys are cool!"; 5 | 6 | export default new Hono().get("/", requireAuth, (c) => { 7 | return c.json({ secret: secretString }); 8 | }); -------------------------------------------------------------------------------- /backend/src/routes/user.routes.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from "hono"; 2 | import { requireAuth } from "../middleware/auth.js"; 3 | import { getUser, insertUser, updateUserCity } from "../db/queries.js"; 4 | import { getUserIdentifiers } from "../utils/authentication.js"; 5 | import assert from "node:assert"; 6 | import { HTTPException } from "hono/http-exception"; 7 | 8 | const router = new Hono(); 9 | router.get("/", requireAuth, async (c) => { 10 | const user = c.get("user"); 11 | assert( 12 | user, 13 | "User is not defined. Use authentication middleware to protect this route.", 14 | ); 15 | let dbUser = getUser(user.userId); 16 | // if the user has not logged in before (=> getUser returns undefined), 17 | // we insert the user into the database 18 | dbUser ??= await insertUser(user.userId); 19 | // get the users identifiers via the Corbado Node.js SDK 20 | const userIdentifiers = await getUserIdentifiers(user.userId); 21 | return c.json({ 22 | user: dbUser, 23 | identifiers: userIdentifiers.identifiers, 24 | }); 25 | }); 26 | router.post("/city", requireAuth, async (c) => { 27 | const user = c.get("user"); 28 | assert( 29 | user, 30 | "User is not defined. Use authentication middleware to protect this route.", 31 | ); 32 | const body = await c.req.json(); 33 | const city = body.city; 34 | if (!city || typeof city !== "string") { 35 | throw new HTTPException(400, { 36 | message: "City is required and must be a string", 37 | }); 38 | } 39 | await updateUserCity(user.userId, city); 40 | const updatedUser = getUser(user.userId); 41 | if (!updatedUser) { 42 | return c.status(200); 43 | } 44 | return c.json(updatedUser); 45 | }); 46 | 47 | export default router; 48 | -------------------------------------------------------------------------------- /backend/src/utils/authentication.ts: -------------------------------------------------------------------------------- 1 | import { Config, SDK } from "@corbado/node-sdk"; 2 | import type { Context } from "hono"; 3 | import { getCookie } from "hono/cookie"; 4 | 5 | // Retrieve environment variables 6 | const projectID = process.env.CORBADO_PROJECT_ID; 7 | const apiSecret = process.env.CORBADO_API_SECRET; 8 | if (!projectID) { 9 | throw Error("Project ID is not set"); 10 | } 11 | if (!apiSecret) { 12 | throw Error("API secret is not set"); 13 | } 14 | const frontendAPI = process.env.CORBADO_FRONTEND_API; 15 | const backendAPI = process.env.CORBADO_BACKEND_API; 16 | if (!frontendAPI) { 17 | throw Error("Frontend API URL is not set"); 18 | } 19 | if (!backendAPI) { 20 | throw Error("Backend API URL is not set"); 21 | } 22 | // Initialize the Corbado Node.js SDK with the configuration 23 | const config = new Config(projectID, apiSecret, frontendAPI, backendAPI); 24 | const sdk = new SDK(config); 25 | 26 | export async function getAuthenticatedUserFromCookie(c: Context) { 27 | const sessionToken = getCookie(c, "cbo_session_token"); 28 | 29 | if (!sessionToken) { 30 | return null; 31 | } 32 | 33 | try { 34 | // Your existing token validation logic 35 | return await sdk.sessions().validateToken(sessionToken); 36 | } catch (error) { 37 | // Log the error if needed 38 | return null; 39 | } 40 | } 41 | 42 | export async function getAuthenticatedUserFromAuthorizationHeader(c: Context) { 43 | const sessionToken = c.req.header("Authorization")?.replace("Bearer ", ""); 44 | if (!sessionToken) { 45 | return null; 46 | } 47 | try { 48 | return await sdk.sessions().validateToken(sessionToken); 49 | } catch { 50 | return null; 51 | } 52 | } 53 | 54 | 55 | // Retrieve all identifiers for a given user ID 56 | export function getUserIdentifiers(userId: string) { 57 | // List user identifiers sorted by creation date in descending order 58 | return sdk.identifiers().listByUserId(userId); 59 | } 60 | -------------------------------------------------------------------------------- /backend/src/utils/env.ts: -------------------------------------------------------------------------------- 1 | import dotenv from "dotenv"; 2 | 3 | dotenv.config(); 4 | -------------------------------------------------------------------------------- /backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "NodeNext", 5 | "strict": true, 6 | "verbatimModuleSyntax": true, 7 | "skipLibCheck": true, 8 | "types": [ 9 | "node" 10 | ], 11 | "jsx": "react-jsx", 12 | "jsxImportSource": "hono/jsx" 13 | } 14 | } -------------------------------------------------------------------------------- /frontend/.env.example: -------------------------------------------------------------------------------- 1 | VITE_CORBADO_PROJECT_ID= 2 | VITE_BACKEND_BASE_URL=http://localhost:3001 -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | .env.test 27 | -------------------------------------------------------------------------------- /frontend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "printWidth": 80, 4 | "embeddedLanguageFormatting": "auto", 5 | "plugins": [ 6 | "prettier-plugin-embed" 7 | ] 8 | } -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: 13 | 14 | - Configure the top-level `parserOptions` property like this: 15 | 16 | ```js 17 | export default tseslint.config({ 18 | languageOptions: { 19 | // other options... 20 | parserOptions: { 21 | project: ['./tsconfig.node.json', './tsconfig.app.json'], 22 | tsconfigRootDir: import.meta.dirname, 23 | }, 24 | }, 25 | }) 26 | ``` 27 | 28 | - Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked` 29 | - Optionally add `...tseslint.configs.stylisticTypeChecked` 30 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config: 31 | 32 | ```js 33 | // eslint.config.js 34 | import react from 'eslint-plugin-react' 35 | 36 | export default tseslint.config({ 37 | // Set the react version 38 | settings: { react: { version: '18.3' } }, 39 | plugins: { 40 | // Add the react plugin 41 | react, 42 | }, 43 | rules: { 44 | // other rules... 45 | // Enable its recommended rules 46 | ...react.configs.recommended.rules, 47 | ...react.configs['jsx-runtime'].rules, 48 | }, 49 | }) 50 | ``` 51 | -------------------------------------------------------------------------------- /frontend/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from "@eslint/js"; 2 | import globals from "globals"; 3 | import reactHooks from "eslint-plugin-react-hooks"; 4 | import reactRefresh from "eslint-plugin-react-refresh"; 5 | import tseslint from "typescript-eslint"; 6 | 7 | export default tseslint.config( 8 | { ignores: ["dist"] }, 9 | { 10 | extends: [js.configs.recommended, ...tseslint.configs.recommended], 11 | files: ["**/*.{ts,tsx}"], 12 | languageOptions: { 13 | ecmaVersion: 2020, 14 | globals: globals.browser, 15 | }, 16 | plugins: { 17 | "react-hooks": reactHooks, 18 | "react-refresh": reactRefresh, 19 | }, 20 | rules: { 21 | ...reactHooks.configs.recommended.rules, 22 | "react-refresh/only-export-components": [ 23 | "warn", 24 | { allowConstantExport: true }, 25 | ], 26 | }, 27 | }, 28 | ); 29 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite --port 3000", 8 | "build": "tsc -b && vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@corbado/react": "^3.1.1", 14 | "@corbado/shared-util": "^1.0.10", 15 | "@tanstack/react-query": "^5.62.11", 16 | "react": "^19.0.0", 17 | "react-dom": "^19.0.0", 18 | "react-router": "^7.1.1" 19 | }, 20 | "devDependencies": { 21 | "@corbado/types": "^3.0.0", 22 | "@eslint/js": "^9.17.0", 23 | "@types/react": "^19.0.2", 24 | "@types/react-dom": "^19.0.2", 25 | "@vitejs/plugin-react-swc": "^3.5.0", 26 | "eslint": "^9.17.0", 27 | "eslint-plugin-react-hooks": "^5.0.0", 28 | "eslint-plugin-react-refresh": "^0.4.16", 29 | "globals": "^15.14.0", 30 | "prettier": "^3.4.2", 31 | "prettier-plugin-embed": "^0.4.15", 32 | "typescript": "~5.6.2", 33 | "typescript-eslint": "^8.18.2", 34 | "vite": "^6.0.5" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /frontend/public/documents-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /frontend/public/github-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | github [#142] 6 | Created with Sketch. 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /frontend/public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /frontend/src/app.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Inter:opsz@14..32&family=Space+Grotesk:wght@300..700&display=swap"); 2 | /* CSS reset */ 3 | *, 4 | *::before, 5 | *::after { 6 | box-sizing: border-box; 7 | } 8 | 9 | * { 10 | margin: 0; 11 | } 12 | 13 | body { 14 | line-height: 1.5; 15 | -webkit-font-smoothing: antialiased; 16 | } 17 | 18 | img, 19 | picture, 20 | video, 21 | canvas, 22 | svg { 23 | display: block; 24 | max-width: 100%; 25 | } 26 | 27 | input, 28 | button, 29 | textarea, 30 | select { 31 | font: inherit; 32 | } 33 | 34 | p, 35 | h1, 36 | h2, 37 | h3, 38 | h4, 39 | h5, 40 | h6 { 41 | overflow-wrap: break-word; 42 | } 43 | 44 | li { 45 | list-style-type: none; 46 | } 47 | 48 | a { 49 | text-decoration: none; 50 | } 51 | 52 | button { 53 | border: none; 54 | cursor: pointer; 55 | } 56 | 57 | ul { 58 | padding: 0; 59 | } 60 | 61 | span { 62 | color: inherit; 63 | font-family: inherit; 64 | font-size: inherit; 65 | } 66 | 67 | /* CSS reset end */ 68 | 69 | /* General Theming */ 70 | :root { 71 | --font-space-grotesk: "Space Grotesk", serif; 72 | --font-inter: "Inter", sans-serif; 73 | --text-slate: #94a3b8; 74 | --primary: #2c56f6; 75 | --secondary: #59acfe; 76 | --bg-light: #2c334b; 77 | --bg-medium: #171b29ff; 78 | } 79 | 80 | body { 81 | background-color: var(--bg-medium); 82 | margin: 0 auto; 83 | } 84 | 85 | main { 86 | max-width: 700px; 87 | margin: 0 auto; 88 | padding: 3rem 1rem; 89 | overflow: auto; 90 | } 91 | 92 | h1, 93 | h2, 94 | h3, 95 | h4, 96 | h5, 97 | h6 { 98 | font-family: var(--font-space-grotesk); 99 | color: white; 100 | font-weight: bold; 101 | } 102 | 103 | p, 104 | li, 105 | a { 106 | font-family: var(--font-inter); 107 | } 108 | 109 | button { 110 | font-family: var(--font-space-grotesk); 111 | font-weight: bold; 112 | } 113 | 114 | p, 115 | li, 116 | a { 117 | color: var(--text-slate); 118 | } 119 | 120 | a { 121 | transition: color 0.3s; 122 | } 123 | 124 | a:hover { 125 | color: white; 126 | } 127 | 128 | .loader { 129 | border: 5px solid var(--text-slate); 130 | border-bottom-color: transparent; 131 | border-radius: 50%; 132 | margin: 0 auto; 133 | animation: rotation 1s linear infinite; 134 | width: 2rem; 135 | height: 2rem; 136 | } 137 | 138 | @keyframes rotation { 139 | 0% { 140 | transform: rotate(0deg); 141 | } 142 | 100% { 143 | transform: rotate(360deg); 144 | } 145 | } 146 | 147 | div:has(> nav) { 148 | background-color: var(--bg-light); 149 | } 150 | 151 | /* Navigation */ 152 | nav { 153 | padding: 0.5rem 1rem; 154 | display: grid; 155 | gap: 0.5rem; 156 | grid-template-columns: repeat(1, minmax(0, 1fr)); 157 | align-items: center; 158 | max-width: 1400px; 159 | margin: 0 auto; 160 | } 161 | 162 | nav p { 163 | color: white; 164 | } 165 | 166 | nav ul { 167 | justify-self: center; 168 | display: flex; 169 | justify-content: center; 170 | align-items: center; 171 | background-color: #727a95; 172 | border-radius: 8px; 173 | width: fit-content; 174 | /*max-width: 500px;*/ 175 | padding: 0.3rem; 176 | gap: 0.2rem; 177 | } 178 | 179 | nav ul li { 180 | border-radius: 8px; 181 | transition: background-color 0.3s; 182 | } 183 | 184 | nav ul li:has(a[data-selected="true"]) { 185 | background-color: var(--bg-light); 186 | } 187 | 188 | nav ul li:not(:has(a[data-selected="true"])):hover { 189 | background-color: #61677c; 190 | } 191 | 192 | nav ul li a { 193 | font-family: var(--font-space-grotesk); 194 | color: white; 195 | padding: 0.5rem 1rem; 196 | display: inline-block; 197 | } 198 | 199 | nav > a:first-child { 200 | justify-self: center; 201 | display: flex; 202 | align-items: center; 203 | gap: 0.5rem; 204 | font-family: var(--font-space-grotesk); 205 | font-weight: bold; 206 | font-size: 20px; 207 | } 208 | 209 | nav > button:last-child { 210 | font-family: var(--font-space-grotesk); 211 | background-color: var(--primary); 212 | color: white; 213 | justify-self: center; 214 | transition: background-color 0.3s; 215 | border-radius: 8px; 216 | font-weight: bold; 217 | padding: 0.5rem 1rem; 218 | } 219 | 220 | /* Main */ 221 | 222 | main section { 223 | padding: 1rem; 224 | background-color: #0c0c1f; 225 | box-shadow: 0 0 14px 0 rgba(44, 86, 246, 0.5); 226 | border-radius: 8px; 227 | text-align: center; 228 | } 229 | 230 | main h1 { 231 | color: var(--secondary); 232 | margin: 0 auto; 233 | padding-bottom: 1rem; 234 | } 235 | 236 | main p { 237 | padding-top: 4px; 238 | padding-bottom: 4px; 239 | } 240 | 241 | main .button, 242 | main button { 243 | display: inline-block; 244 | font-family: var(--font-space-grotesk); 245 | font-weight: bold; 246 | background-color: var(--primary); 247 | color: white; 248 | padding: 0.5rem 1rem; 249 | border-radius: 8px; 250 | transition: background-color 0.3s; 251 | margin: 0.4rem 0.2rem; 252 | } 253 | 254 | main .button:disabled, 255 | main button:disabled { 256 | background-color: #1b3494; 257 | cursor: not-allowed; 258 | } 259 | 260 | main .button:hover:not([disabled]), 261 | button:hover:not([type="button"]):not([title]):not(.cb-primary-button):not( 262 | [disabled] 263 | ) { 264 | background-color: var(--secondary); 265 | } 266 | 267 | main ul { 268 | display: flex; 269 | justify-content: center; 270 | align-items: center; 271 | gap: 1rem; 272 | flex-wrap: wrap; 273 | padding: 0.2rem 0; 274 | } 275 | 276 | main ul li { 277 | color: white; 278 | font-family: var(--font-space-grotesk); 279 | } 280 | 281 | main ul li::before { 282 | content: ""; 283 | display: inline-block; 284 | width: 24px; 285 | height: 24px; 286 | margin-right: 2px; 287 | background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgdmlld0JveD0iMCAwIDMyIDMyIiBmaWxsPSJub25lIj4KPGcgZmlsdGVyPSJ1cmwoI2ZpbHRlcjBfZl8zNl83MTUpIj4KPGNpcmNsZSBjeD0iMTYiIGN5PSIxNiIgcj0iNiIgZmlsbD0iIzIxOEVEMiIvPgo8Y2lyY2xlIGN4PSIxNiIgY3k9IjE2IiByPSI2IiBmaWxsPSIjMjE4RUQyIi8+CjwvZz4KPGNpcmNsZSBjeD0iMTYiIGN5PSIxNiIgcj0iNiIgZmlsbD0iIzIxOEVEMiIvPgo8Y2lyY2xlIGN4PSIxNiIgY3k9IjE2IiByPSI2IiBmaWxsPSIjMjE4RUQyIi8+CjxkZWZzPgo8ZmlsdGVyIGlkPSJmaWx0ZXIwX2ZfMzZfNzE1IiB4PSIwIiB5PSIwIiB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJCYWNrZ3JvdW5kSW1hZ2VGaXgiIHJlc3VsdD0ic2hhcGUiLz4KPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iNSIgcmVzdWx0PSJlZmZlY3QxX2ZvcmVncm91bmRCbHVyXzM2XzcxNSIvPgo8L2ZpbHRlcj4KPC9kZWZzPgo8L3N2Zz4="); 288 | background-size: cover; 289 | vertical-align: middle; 290 | } 291 | 292 | #secret-box { 293 | border: 2px solid var(--text-slate); 294 | background: var(--bg-light); 295 | margin-top: 1rem; 296 | justify-self: center; 297 | padding: 0.3rem; 298 | } 299 | 300 | #secret-box h3 { 301 | display: inline; 302 | color: var(--secondary); 303 | } 304 | 305 | #secret-box p { 306 | display: inline; 307 | color: white; 308 | } 309 | 310 | #city-form { 311 | display: flex; 312 | flex-direction: column; 313 | align-items: center; 314 | gap: 0.5rem; 315 | margin-top: 1rem; 316 | } 317 | 318 | #city-form > input { 319 | font-family: var(--font-inter); 320 | background-color: var(--bg-light); 321 | border: white 2px solid; 322 | border-radius: 8px; 323 | color: white; 324 | padding: 0.2rem 0.5rem; 325 | } 326 | 327 | #identifier-list { 328 | display: flex; 329 | flex-direction: column; 330 | padding: 0.5rem; 331 | border: 1px solid var(--text-slate); 332 | border-radius: 8px; 333 | margin-bottom: 0.5rem; 334 | } 335 | 336 | #identifier-list > div { 337 | display: grid; 338 | grid-template-columns: repeat(1, minmax(0, 1fr)); 339 | justify-items: start; 340 | } 341 | 342 | #identifier-list > div:not(:last-child) { 343 | border-bottom: 1px solid var(--text-slate); 344 | } 345 | 346 | /* Footer */ 347 | footer { 348 | margin: 0 auto; 349 | display: flex; 350 | align-items: center; 351 | justify-content: center; 352 | gap: 0.5rem; 353 | padding-top: 3rem; 354 | } 355 | 356 | footer > a { 357 | display: flex; 358 | gap: 0.5rem; 359 | transition: background-color 0.3s; 360 | padding: 0.5rem 1rem; 361 | border-radius: 8px; 362 | } 363 | 364 | footer > a:hover { 365 | background-color: var(--bg-light); 366 | color: var(--text-slate); 367 | } 368 | 369 | footer > svg:hover { 370 | fill: white; 371 | } 372 | 373 | /* Custom Corbado styles */ 374 | 375 | .cbo-custom-styles { 376 | .cb-passkey-list-title { 377 | display: none; 378 | } 379 | 380 | .cb-passkey-list-primary-button { 381 | margin-left: auto; 382 | margin-right: auto; 383 | } 384 | 385 | .cb-passkey-list-primary-button:hover { 386 | background-color: var(--secondary); 387 | } 388 | 389 | .cb-passkey-list-card, 390 | .cb-container { 391 | background-color: var(--bg-light); 392 | } 393 | 394 | .cb-input, 395 | .cb-icon-button-with-icon-only, 396 | .cb-last-identifier { 397 | background-color: var(--bg-medium); 398 | } 399 | } 400 | 401 | @media (min-width: 540px) { 402 | nav ul li a { 403 | padding: 0.5rem 2rem; 404 | } 405 | 406 | #identifier-list > div { 407 | grid-template-columns: repeat(2, minmax(0, 1fr)); 408 | } 409 | } 410 | 411 | @media (min-width: 1150px) { 412 | nav { 413 | grid-template-columns: minmax(0, 1fr) minmax(0, 2fr) minmax(0, 1fr); 414 | } 415 | 416 | nav > a:first-child { 417 | justify-self: start; 418 | } 419 | 420 | nav > button:last-child { 421 | justify-self: end; 422 | } 423 | } 424 | -------------------------------------------------------------------------------- /frontend/src/app.tsx: -------------------------------------------------------------------------------- 1 | import { CorbadoProvider } from "@corbado/react"; 2 | import englishTranslations from "./utils/corbado-translations.ts"; 3 | import UserProvider from "./context/user.tsx"; 4 | import Layout from "./layouts/layout.tsx"; 5 | import { BrowserRouter, Route, Routes } from "react-router"; 6 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 7 | import HomePage from "./pages/HomePage.tsx"; 8 | import UserAreaPage from "./pages/UserAreaPage.tsx"; 9 | import SignupPage from "./pages/SignupPage.tsx"; 10 | import LoginPage from "./pages/LoginPage.tsx"; 11 | import ProfilePage from "./pages/ProfilePage.tsx"; 12 | import OnboardingPage from "./pages/OnboardingPage.tsx"; 13 | import { useRef } from "react"; 14 | import { sendEvent } from "@corbado/shared-util"; 15 | import { useEffect } from "react"; 16 | import { TelemetryEventType } from "@corbado/shared-util"; 17 | 18 | const queryClient = new QueryClient(); 19 | 20 | export default function App() { 21 | const hasSentTelemetry = useRef(false); 22 | 23 | useEffect(() => { 24 | if ( 25 | hasSentTelemetry.current || 26 | import.meta.env.VITE_CORBADO_TELEMETRY_DISABLED === "true" 27 | ) 28 | return; 29 | 30 | void sendEvent({ 31 | type: TelemetryEventType.EXAMPLE_APPLICATION_OPENED, 32 | payload: { 33 | exampleName: "corbado/passkeys-react-hono", 34 | }, 35 | sdkVersion: "3.1.0", 36 | sdkName: "React SDK", 37 | identifier: import.meta.env.VITE_CORBADO_PROJECT_ID, 38 | }); 39 | }, []); 40 | 41 | return ( 42 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | ); 63 | } 64 | 65 | function RoutesDefinition() { 66 | return ( 67 | 68 | 69 | 70 | } /> 71 | } /> 72 | } /> 73 | } /> 74 | } /> 75 | } 78 | /> 79 | 80 | 81 | 82 | ); 83 | } 84 | -------------------------------------------------------------------------------- /frontend/src/context/user.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, ReactNode, useMemo } from "react"; 2 | import { useCorbado } from "@corbado/react"; 3 | import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; 4 | 5 | type UserIdentifiers = Array<{ 6 | identifierID: string; 7 | userID: string; 8 | status: "pending" | "primary" | "verified"; 9 | type: "email" | "phone" | "username"; 10 | value: string; 11 | }>; 12 | type DBUser = { 13 | id: string; 14 | corbado_user_id: string; 15 | city: string | null; 16 | }; 17 | export type ExternalUserInfo = 18 | | { 19 | status: "not-loaded"; 20 | } 21 | | { 22 | status: "loading"; 23 | } 24 | | { 25 | status: "error"; 26 | message: string; 27 | } 28 | | { 29 | status: "success"; 30 | user: DBUser; // the user object from the database 31 | identifiers: UserIdentifiers; 32 | }; 33 | type GlobalContextType = { 34 | externalUserInfo: ExternalUserInfo; 35 | updateUserCity: (city: string) => Promise; 36 | }; 37 | type ExternalUserQueryResponse = { 38 | user: DBUser; 39 | identifiers: UserIdentifiers; 40 | }; 41 | 42 | export const UserContext = createContext({ 43 | externalUserInfo: { status: "not-loaded" }, 44 | updateUserCity: async () => {}, 45 | }); 46 | 47 | async function getExternalUserInfo(): Promise { 48 | const rsp = await fetch( 49 | `${import.meta.env.VITE_BACKEND_BASE_URL}/api/user`, 50 | { 51 | credentials: "include", 52 | }, 53 | ); 54 | return rsp.json(); 55 | } 56 | 57 | async function updateUserCity(newCity: string): Promise { 58 | const rsp = await fetch( 59 | `${import.meta.env.VITE_BACKEND_BASE_URL}/api/user/city`, 60 | { 61 | method: "POST", 62 | credentials: "include", 63 | headers: { 64 | "Content-Type": "application/json", 65 | }, 66 | body: JSON.stringify({ city: newCity }), 67 | }, 68 | ); 69 | 70 | if (!rsp.ok) { 71 | throw new Error( 72 | `Failed to submit city: ${rsp.status} ${rsp.statusText}`, 73 | ); 74 | } 75 | 76 | return rsp.json(); 77 | } 78 | 79 | export default function UserProvider({ children }: { children: ReactNode }) { 80 | const { loading, isAuthenticated, user } = useCorbado(); 81 | const queryClient = useQueryClient(); 82 | 83 | const { 84 | data: externalUserData, 85 | isPending: externalUserDataIsPending, 86 | error: externalUserDataError, 87 | } = useQuery({ 88 | queryKey: ["get-external-user-info", user], 89 | queryFn: getExternalUserInfo, 90 | enabled: isAuthenticated && !loading, 91 | }); 92 | 93 | const updateUserCityMutation = useMutation({ 94 | mutationFn: updateUserCity, 95 | onSuccess: (updatedUser) => { 96 | // Update the cached user data with the new city 97 | queryClient.setQueryData( 98 | ["get-external-user-info", user], 99 | (oldData: ExternalUserQueryResponse | undefined) => { 100 | if (oldData?.user.id === updatedUser.id) { 101 | return { 102 | ...oldData, 103 | user: updatedUser, 104 | }; 105 | } 106 | return oldData; 107 | }, 108 | ); 109 | }, 110 | onError: (error: unknown) => { 111 | console.error("Error updating city:", error); 112 | }, 113 | }); 114 | 115 | const value = useMemo(() => { 116 | let externalUserInfo: ExternalUserInfo; 117 | if (!isAuthenticated) { 118 | externalUserInfo = { status: "not-loaded" }; 119 | } else if (externalUserDataIsPending) { 120 | externalUserInfo = { status: "loading" }; 121 | } else if (externalUserDataError) { 122 | externalUserInfo = { 123 | status: "error", 124 | message: externalUserDataError.message, 125 | }; 126 | } else { 127 | externalUserInfo = { 128 | status: "success", 129 | user: externalUserData.user, 130 | identifiers: externalUserData.identifiers, 131 | }; 132 | } 133 | 134 | return { 135 | externalUserInfo, 136 | updateUserCity: async (newCity: string) => { 137 | await updateUserCityMutation.mutateAsync(newCity); 138 | }, 139 | }; 140 | }, [ 141 | externalUserData?.identifiers, 142 | externalUserData?.user, 143 | externalUserDataError, 144 | externalUserDataIsPending, 145 | isAuthenticated, 146 | updateUserCityMutation, 147 | ]); 148 | 149 | return {children}; 150 | } 151 | -------------------------------------------------------------------------------- /frontend/src/layouts/layout.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | import { Link, useLocation, useNavigate } from "react-router"; 3 | import { useCorbado } from "@corbado/react"; 4 | 5 | export default function Layout({ children }: { children: ReactNode }) { 6 | return ( 7 | <> 8 | 9 |
10 |
{children}
11 |
12 |
13 | 14 | ); 15 | } 16 | 17 | function Navbar() { 18 | const { isAuthenticated } = useCorbado(); 19 | const pathname = useLocation().pathname; 20 | 21 | return ( 22 |
23 | 79 |
80 | ); 81 | } 82 | 83 | function LogoutButton() { 84 | const { isAuthenticated, logout, loading } = useCorbado(); 85 | const navigate = useNavigate(); 86 | 87 | async function onLogout() { 88 | if (!isAuthenticated || loading) return; 89 | navigate("/"); 90 | await logout(); 91 | } 92 | 93 | return ( 94 | 97 | ); 98 | } 99 | 100 | 101 | function Footer() { 102 | return ( 103 |
104 | 108 | GitHub icon 114 | Github 115 | 116 | 120 | Documentation icon 126 | Documentation 127 | 128 |
129 | ); 130 | } 131 | -------------------------------------------------------------------------------- /frontend/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from "react"; 2 | import { createRoot } from "react-dom/client"; 3 | import "./app.css"; 4 | import App from "./app.tsx"; 5 | 6 | createRoot(document.getElementById("root")!).render( 7 | 8 | 9 | , 10 | ); 11 | -------------------------------------------------------------------------------- /frontend/src/pages/HomePage.tsx: -------------------------------------------------------------------------------- 1 | import { useCorbado } from "@corbado/react"; 2 | import { use } from "react"; 3 | import { UserContext } from "../context/user.tsx"; 4 | import { Link } from "react-router"; 5 | 6 | export default function HomePage() { 7 | const { isAuthenticated, user, loading } = useCorbado(); 8 | const userCtx = use(UserContext); 9 | 10 | if (loading) { 11 | return
; 12 | } 13 | 14 | if (!isAuthenticated || !user) { 15 | return ; 16 | } 17 | 18 | const city = 19 | (userCtx.externalUserInfo.status === "success" && 20 | userCtx.externalUserInfo.user.city) || 21 | "unknown"; 22 | 23 | return ( 24 |
25 |

26 | Welcome {user.name} from {city}! 27 |

28 |

29 | You now have access to everything and can visit the user area: 30 |

31 | 32 | User area 33 | 34 |
35 | ); 36 | } 37 | 38 | function GuestHomePage() { 39 | return ( 40 |
41 |

Welcome Guest!

42 |

43 | This example demonstrates Corbado's passkey-first authentication 44 | solution. 45 |

46 |

It covers all relevant aspects like -

47 |
    48 |
  • Sign-up
  • 49 |
  • Login
  • 50 |
  • Protecting Routes
  • 51 |
52 |

53 | It can be used as a starting point for your own application or 54 | to learn. 55 |

56 | 57 | Sign up 58 | 59 | 60 | Login 61 | 62 |
63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /frontend/src/pages/LoginPage.tsx: -------------------------------------------------------------------------------- 1 | import { CorbadoAuth } from "@corbado/react"; 2 | import { use, useEffect } from "react"; 3 | import { UserContext } from "../context/user.tsx"; 4 | import { useNavigate } from "react-router"; 5 | 6 | export default function LoginPage() { 7 | const userCtx = use(UserContext); 8 | const navigate = useNavigate(); 9 | 10 | useEffect(() => { 11 | const externalUserInfo = userCtx.externalUserInfo; 12 | switch (externalUserInfo.status) { 13 | case "success": 14 | if (externalUserInfo.user.city === null) { 15 | navigate("/signup/onboarding"); 16 | } else { 17 | navigate("/profile"); 18 | } 19 | return; 20 | case "error": 21 | // handle this case more gracefully in a real application 22 | console.error(externalUserInfo.message); 23 | return; 24 | } 25 | }, [navigate, userCtx.externalUserInfo]); 26 | 27 | return ( 28 |
29 |

Login

30 | { 32 | // do nothing here. We have to wait for a backend response 33 | // to check whether the user has gone through onboarding already. 34 | // The backend call is made in the user store. 35 | }} 36 | initialBlock="login-init" 37 | /> 38 |
39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /frontend/src/pages/OnboardingPage.tsx: -------------------------------------------------------------------------------- 1 | import { useCorbado } from "@corbado/react"; 2 | import { useNavigate } from "react-router"; 3 | import { UserContext } from "../context/user.tsx"; 4 | import { use } from "react"; 5 | 6 | export default function OnboardingPage() { 7 | const { loading, isAuthenticated } = useCorbado(); 8 | const navigate = useNavigate(); 9 | const userCtx = use(UserContext); 10 | 11 | if (!isAuthenticated && !loading) { 12 | navigate("/login"); 13 | } 14 | 15 | async function onSubmit(event: React.FormEvent) { 16 | event.preventDefault(); 17 | const formData = new FormData(event.currentTarget); 18 | const city = formData.get("city") as string; 19 | try { 20 | await userCtx.updateUserCity(city); 21 | } catch (e) { 22 | console.error("Failed to update user city:", e); 23 | } 24 | navigate("/"); 25 | } 26 | 27 | return ( 28 |
29 |

Onboarding

30 |

Choose your city

31 |
32 | 33 | 34 |
35 |
36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /frontend/src/pages/ProfilePage.tsx: -------------------------------------------------------------------------------- 1 | import { UserContext } from "../context/user.tsx"; 2 | import { use } from "react"; 3 | import { useNavigate } from "react-router"; 4 | import { PasskeyList, useCorbado } from "@corbado/react"; 5 | 6 | export default function ProfilePage() { 7 | const userCtx = use(UserContext); 8 | const navigate = useNavigate(); 9 | const { isAuthenticated, loading } = useCorbado(); 10 | 11 | if (!isAuthenticated && !loading) { 12 | navigate("/login"); 13 | } 14 | 15 | const userInfo = 16 | userCtx.externalUserInfo.status === "success" 17 | ? userCtx.externalUserInfo 18 | : null; 19 | 20 | return ( 21 |
22 |

Profile

23 |

24 | Example userID: 25 | {userInfo?.user.id} 26 |

27 |

28 | Corbado userID: 29 | {userInfo?.user.corbado_user_id} 30 |

31 |

Your Identifiers

32 | {userInfo && ( 33 |
34 | {userInfo.identifiers.map((identifier) => ( 35 |
36 |

37 | Type: 38 | {identifier.type} 39 |

40 |

41 | Value: 42 | {identifier.value} 43 |

44 |
45 | ))} 46 |
47 | )} 48 | 49 |
50 | ); 51 | } 52 | 53 | function PasskeyManagement() { 54 | return ( 55 | <> 56 |

Manage your Passkeys

57 | 58 | 59 | ); 60 | } 61 | 62 | -------------------------------------------------------------------------------- /frontend/src/pages/SignupPage.tsx: -------------------------------------------------------------------------------- 1 | import { CorbadoAuth } from "@corbado/react"; 2 | import { use, useEffect } from "react"; 3 | import { UserContext } from "../context/user.tsx"; 4 | import { useNavigate } from "react-router"; 5 | 6 | export default function SignupPage() { 7 | const userCtx = use(UserContext); 8 | const navigate = useNavigate(); 9 | 10 | useEffect(() => { 11 | const externalUserInfo = userCtx.externalUserInfo; 12 | switch (externalUserInfo.status) { 13 | case "success": 14 | if (externalUserInfo.user.city === null) { 15 | navigate("/signup/onboarding"); 16 | } else { 17 | navigate("/profile"); 18 | } 19 | return; 20 | case "error": 21 | // handle this case more gracefully in a real application 22 | console.error(externalUserInfo.message); 23 | return; 24 | } 25 | }, [navigate, userCtx.externalUserInfo]); 26 | 27 | return ( 28 |
29 |

Signup

30 | { 32 | // do nothing here. We have to wait for a backend response 33 | // to check whether the user has gone through onboarding already. 34 | // The backend call is made in the user store. 35 | }} 36 | initialBlock="signup-init" 37 | /> 38 |
39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /frontend/src/pages/UserAreaPage.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router"; 2 | import { useCorbado } from "@corbado/react"; 3 | import { useQuery } from "@tanstack/react-query"; 4 | 5 | export default function UserAreaPage() { 6 | const { isAuthenticated, loading } = useCorbado(); 7 | 8 | return isAuthenticated && !loading ? ( 9 | 10 | ) : ( 11 | 12 | ); 13 | } 14 | 15 | function UserAreaAuthenticated() { 16 | const { sessionToken, loading } = useCorbado(); 17 | const { 18 | data: secret, 19 | isLoading, 20 | error, 21 | refetch, 22 | } = useQuery({ 23 | queryKey: ["get-secret"], 24 | queryFn: async () => { 25 | const res = await fetch( 26 | `${import.meta.env.VITE_BACKEND_BASE_URL}/api/secret`, 27 | { 28 | headers: { 29 | Authorization: `Bearer ${sessionToken}`, 30 | }, 31 | }, 32 | ); 33 | return (await res.json()).secret as string; 34 | }, 35 | enabled: false, 36 | }); 37 | 38 | return ( 39 |
40 |

User area!

41 |

Since you are logged-in, we can tell you a secret:

42 | 49 |
50 | 55 |
56 |
57 | ); 58 | } 59 | 60 | function RevealSecretResult({ 61 | secret, 62 | loading, 63 | error, 64 | }: { 65 | secret: string | undefined; 66 | loading: boolean; 67 | error: Error | null; 68 | }) { 69 | if (secret) { 70 | return ( 71 |
72 |

Secret:

73 |

{secret}

74 |
75 | ); 76 | } 77 | if (loading) { 78 | return
; 79 | } 80 | if (error) { 81 | return ( 82 |
83 |

Failed to reveal secret: {error?.message}

84 |
85 | ); 86 | } 87 | return null; 88 | } 89 | 90 | function UserAreaGuest() { 91 | return ( 92 |
93 |

User area!

94 |

This page is for logged-in users only. Please login:

95 | 96 | Login 97 | 98 |
99 | ); 100 | } 101 | -------------------------------------------------------------------------------- /frontend/src/utils/corbado-translations.ts: -------------------------------------------------------------------------------- 1 | const englishTranslations = { 2 | signup: { 3 | "signup-init": { 4 | "signup-init": { 5 | header: "Let's create an account", 6 | subheader: "to check ", 7 | text_login: "Would you like to login? ", 8 | button_submit: "Sign up", 9 | textField_fullName: "Full Name", 10 | text_divider: "or use social logins", 11 | }, 12 | }, 13 | }, 14 | login: { 15 | "login-init": { 16 | "login-init": { 17 | header: "Please login", 18 | subheader: "to check ", 19 | text_signup: "Would you like to create an account? ", 20 | button_signup: "Sign up", 21 | button_submit: "Login", 22 | }, 23 | }, 24 | }, 25 | passkeysList: { 26 | button_createPasskey: "You can create passkeys here.", 27 | field_credentialId: "ID: ", 28 | field_status: "Status of Passkey: ", 29 | }, 30 | }; 31 | 32 | export default englishTranslations; 33 | -------------------------------------------------------------------------------- /frontend/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /frontend/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 4 | "target": "ES2020", 5 | "useDefineForClassFields": true, 6 | "lib": [ 7 | "ES2020", 8 | "DOM", 9 | "DOM.Iterable" 10 | ], 11 | "module": "ESNext", 12 | "skipLibCheck": true, 13 | /* Bundler mode */ 14 | "moduleResolution": "bundler", 15 | "allowImportingTsExtensions": true, 16 | "isolatedModules": true, 17 | "moduleDetection": "force", 18 | "noEmit": true, 19 | "jsx": "react-jsx", 20 | /* Linting */ 21 | "strict": true, 22 | "noUnusedLocals": true, 23 | "noUnusedParameters": true, 24 | "noFallthroughCasesInSwitch": true, 25 | "noUncheckedSideEffectImports": true 26 | }, 27 | "include": [ 28 | "src" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./tsconfig.app.json" 6 | }, 7 | { 8 | "path": "./tsconfig.node.json" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /frontend/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 4 | "target": "ES2022", 5 | "lib": [ 6 | "ES2023" 7 | ], 8 | "module": "ESNext", 9 | "skipLibCheck": true, 10 | /* Bundler mode */ 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "isolatedModules": true, 14 | "moduleDetection": "force", 15 | "noEmit": true, 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "noUncheckedSideEffectImports": true 22 | }, 23 | "include": [ 24 | "vite.config.ts" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /frontend/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react-swc"; 3 | 4 | // https://vite.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }); 8 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Exit immediately if a command exits with a non-zero status 4 | set -e 5 | 6 | # Function to handle termination 7 | cleanup() { 8 | echo -e "\nStopping all processes..." 9 | kill "$BACKEND_PID" "$FRONTEND_PID" 2>/dev/null 10 | exit 0 11 | } 12 | 13 | # Trap SIGINT and SIGTERM to run the cleanup function 14 | trap cleanup SIGINT SIGTERM 15 | 16 | # Start the backend 17 | (cd backend && npm run dev) | while IFS= read -r line; do 18 | echo -e "[BACKEND] $line" 19 | done & 20 | BACKEND_PID=$! 21 | 22 | # Start the frontend 23 | (cd frontend && npm run dev) | while IFS= read -r line; do 24 | echo -e "[FRONTEND] $line" 25 | done & 26 | FRONTEND_PID=$! 27 | 28 | # Wait for both processes to finish 29 | wait --------------------------------------------------------------------------------