├── README.md ├── backend ├── .env.example ├── .gitignore ├── .prettierrc ├── package-lock.json ├── package.json ├── src │ ├── app.ts │ ├── controller │ │ ├── secret-controller.ts │ │ └── user-controller.ts │ ├── db │ │ ├── db.ts │ │ └── queries.ts │ ├── index.ts │ ├── middleware │ │ ├── auth.ts │ │ └── error-handler.ts │ ├── routes │ │ ├── api.routes.ts │ │ ├── index.ts │ │ ├── secret.routes.ts │ │ └── user.routes.ts │ └── utils │ │ ├── authentication.ts │ │ ├── corbado-translations.ts │ │ ├── env.ts │ │ └── logger.ts └── tsconfig.json ├── frontend ├── .env.example ├── .gitignore ├── .prettierrc.json ├── README.md ├── env.d.ts ├── eslint.config.js ├── index.html ├── package-lock.json ├── package.json ├── public │ ├── documents-icon.svg │ ├── favicon.ico │ ├── github-icon.svg │ └── logo.svg ├── src │ ├── App.vue │ ├── assets │ │ ├── base.css │ │ ├── logo.svg │ │ └── main.css │ ├── components │ │ ├── MainFooter.vue │ │ └── MainNavigation.vue │ ├── main.ts │ ├── router │ │ └── index.ts │ ├── stores │ │ └── user.ts │ ├── utils │ │ └── corbado-translations.ts │ └── views │ │ ├── HomeView.vue │ │ ├── LoginView.vue │ │ ├── OnboardingView.vue │ │ ├── ProfileView.vue │ │ ├── SignupView.vue │ │ └── UserAreaView.vue ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts └── start.sh /README.md: -------------------------------------------------------------------------------- 1 | GitHub Repo Cover 2 | 3 | # Vue.js and Express Passkey Example App 4 | 5 | This is a sample implementation of the [Corbado passkeys-first authentication solution](https://www.corbado.com) using 6 | Vue.js and Express. The following packages are being used: 7 | 8 | - [Corbado web-js](https://github.com/corbado/javascript/tree/develop/packages/web-js) 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/vuejs-express) 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/views`: Contains all pages used in the frontend 18 | - `frontend/src/router/index.ts`: Contains the route definitions 19 | - `frontend/src/stores/user.svelte.ts`: Global store for user data from Corbado and our own backend 20 | - `backend`: Separate directory for the Express.js backend 21 | - `backend/.env.example`: Example file for environment variables 22 | - `backend/src/app.ts`: Configuration file for the Express 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/controller`: Controllers for the routes 26 | - `backend/src/middleware`: Middleware, e.g. for authentication 27 | - `backend/src/db`: Database configuration and queries 28 | 29 | 30 | ## Setup 31 | 32 | ### Prerequisites 33 | 34 | Please follow the steps in [Getting started](https://docs.corbado.com/overview/getting-started) to create and configure 35 | a project in the [Corbado developer panel](https://app.corbado.com/). 36 | 37 | You need to have [Node](https://nodejs.org/en/download) and `npm` installed to run it. 38 | 39 | ### Configure environment variables 40 | 41 | Use the values you obtained in [Prerequisites](#prerequisites) to configure the following variables inside a `.env` 42 | file you create in frontend and backend directories respectively: 43 | 44 | #### Backend 45 | 46 | The backend needs an api secret to authenticate with the Corbado backend API. 47 | 48 | ```dotenv 49 | CORBADO_PROJECT_ID=pro-XXX 50 | CORBADO_API_SECRET=corbado1_XXX 51 | CORBADO_FRONTEND_API=https://{$CORBADO_PROJECT_ID}.frontendapi.cloud.corbado.io 52 | CORBADO_BACKEND_API=https://backendapi.cloud.corbado.io 53 | ``` 54 | 55 | #### Frontend 56 | 57 | The frontend needs the project ID and the backend base URL. 58 | 59 | ```dotenv 60 | VITE_CORBADO_PROJECT_ID=pro-XXX 61 | VITE_BACKEND_BASE_URL=http://localhost:3001 62 | ``` 63 | 64 | ## Usage 65 | 66 | ### Run the project locally 67 | 68 | Run the following command in the root directory 69 | 70 | ```bash 71 | (cd backend && npm install) 72 | (cd frontend && npm install) 73 | ``` 74 | 75 | to install all dependencies. 76 | 77 | Finally, you can run the project locally with the provided start script or individually for frontend and backend. 78 | 79 | #### Using the start script 80 | 81 | ```bash 82 | ./start.sh 83 | ``` 84 | 85 | #### Running frontend and backend individually 86 | 87 | In one terminal session, run the following command in the `frontend` directory: 88 | ```bash 89 | npm run dev 90 | ``` 91 | 92 | In another terminal session, run the following command in the `backend` directory: 93 | ```bash 94 | npm run start 95 | ``` 96 | 97 | ## Passkeys support 98 | 99 | - Community for Developer Support: https://bit.ly/passkeys-community 100 | - Passkeys Debugger: https://www.passkeys-debugger.io/ 101 | - Passkey Subreddit: https://www.reddit.com/r/passkey/ 102 | 103 | ## Telemetry 104 | 105 | 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. 106 | 107 | To disable telemetry, add the following line to your `frontend/.env` file: 108 | 109 | ```sh 110 | VITE_CORBADO_TELEMETRY_DISABLED=true 111 | ``` -------------------------------------------------------------------------------- /backend/.env.example: -------------------------------------------------------------------------------- 1 | CORBADO_PROJECT_ID= 2 | CORBADO_API_SECRET= 3 | CORBADO_FRONTEND_API= 4 | CORBADO_BACKEND_API= -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .env* 4 | !.env.example 5 | db.json -------------------------------------------------------------------------------- /backend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "printWidth": 80, 4 | "embeddedLanguageFormatting": "auto", 5 | "plugins": [ 6 | "prettier-plugin-embed" 7 | ] 8 | } -------------------------------------------------------------------------------- /backend/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-vanillajs-ts-express", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "js-vanillajs-ts-express", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@corbado/node-sdk": "^3.0.2", 13 | "@corbado/types": "^2.11.2", 14 | "compression": "^1.7.5", 15 | "cookie-parser": "^1.4.7", 16 | "cors": "^2.8.5", 17 | "dotenv": "^16.4.5", 18 | "express": "^4.21.1", 19 | "lowdb": "^7.0.1", 20 | "morgan": "^1.10.0", 21 | "winston": "^3.17.0" 22 | }, 23 | "devDependencies": { 24 | "@types/compression": "^1.7.5", 25 | "@types/cookie-parser": "^1.4.8", 26 | "@types/cors": "^2.8.17", 27 | "@types/express": "^5.0.0", 28 | "@types/morgan": "^1.9.9", 29 | "@types/node": "^22.9.3", 30 | "prettier": "^3.3.3", 31 | "prettier-plugin-embed": "^0.4.15", 32 | "typescript": "^5.7.2" 33 | } 34 | }, 35 | "node_modules/@colors/colors": { 36 | "version": "1.6.0", 37 | "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", 38 | "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", 39 | "license": "MIT", 40 | "engines": { 41 | "node": ">=0.1.90" 42 | } 43 | }, 44 | "node_modules/@corbado/node-sdk": { 45 | "version": "3.0.2", 46 | "resolved": "https://registry.npmjs.org/@corbado/node-sdk/-/node-sdk-3.0.2.tgz", 47 | "integrity": "sha512-uAdZTHsej9akILLLbztDaiIUyn7nR4l9CeYA1zqbuWxPQox/V+JGAwcCPVJImlVGbB17JdxVrzAdI72SWtA3/A==", 48 | "license": "MIT", 49 | "dependencies": { 50 | "axios": "^1.7.7", 51 | "axios-better-stacktrace": "^2.1.7", 52 | "axios-mock-adapter": "^2.0.0", 53 | "dotenv": "^16.3.1", 54 | "express": "^4.18.2", 55 | "jose": "^5.1.3", 56 | "typescript": "^5.3.4" 57 | }, 58 | "engines": { 59 | "node": ">=16.1" 60 | } 61 | }, 62 | "node_modules/@corbado/types": { 63 | "version": "2.11.2", 64 | "resolved": "https://registry.npmjs.org/@corbado/types/-/types-2.11.2.tgz", 65 | "integrity": "sha512-LTMg1rjrOWsazql038ekHLN/aq1laari1kIWrsobnlFETGid045C6hrd5alDSYsnp24tcfKjFf1ZemIqGSASCA==", 66 | "license": "ISC" 67 | }, 68 | "node_modules/@dabh/diagnostics": { 69 | "version": "2.0.3", 70 | "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", 71 | "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", 72 | "license": "MIT", 73 | "dependencies": { 74 | "colorspace": "1.1.x", 75 | "enabled": "2.0.x", 76 | "kuler": "^2.0.0" 77 | } 78 | }, 79 | "node_modules/@types/body-parser": { 80 | "version": "1.19.5", 81 | "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", 82 | "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", 83 | "dev": true, 84 | "license": "MIT", 85 | "dependencies": { 86 | "@types/connect": "*", 87 | "@types/node": "*" 88 | } 89 | }, 90 | "node_modules/@types/compression": { 91 | "version": "1.7.5", 92 | "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.7.5.tgz", 93 | "integrity": "sha512-AAQvK5pxMpaT+nDvhHrsBhLSYG5yQdtkaJE1WYieSNY2mVFKAgmU4ks65rkZD5oqnGCFLyQpUr1CqI4DmUMyDg==", 94 | "dev": true, 95 | "license": "MIT", 96 | "dependencies": { 97 | "@types/express": "*" 98 | } 99 | }, 100 | "node_modules/@types/connect": { 101 | "version": "3.4.38", 102 | "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", 103 | "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", 104 | "dev": true, 105 | "license": "MIT", 106 | "dependencies": { 107 | "@types/node": "*" 108 | } 109 | }, 110 | "node_modules/@types/cookie-parser": { 111 | "version": "1.4.8", 112 | "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.8.tgz", 113 | "integrity": "sha512-l37JqFrOJ9yQfRQkljb41l0xVphc7kg5JTjjr+pLRZ0IyZ49V4BQ8vbF4Ut2C2e+WH4al3xD3ZwYwIUfnbT4NQ==", 114 | "dev": true, 115 | "license": "MIT", 116 | "peerDependencies": { 117 | "@types/express": "*" 118 | } 119 | }, 120 | "node_modules/@types/cors": { 121 | "version": "2.8.17", 122 | "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", 123 | "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", 124 | "dev": true, 125 | "license": "MIT", 126 | "dependencies": { 127 | "@types/node": "*" 128 | } 129 | }, 130 | "node_modules/@types/estree": { 131 | "version": "1.0.6", 132 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", 133 | "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", 134 | "dev": true, 135 | "license": "MIT" 136 | }, 137 | "node_modules/@types/express": { 138 | "version": "5.0.0", 139 | "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz", 140 | "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==", 141 | "dev": true, 142 | "license": "MIT", 143 | "dependencies": { 144 | "@types/body-parser": "*", 145 | "@types/express-serve-static-core": "^5.0.0", 146 | "@types/qs": "*", 147 | "@types/serve-static": "*" 148 | } 149 | }, 150 | "node_modules/@types/express-serve-static-core": { 151 | "version": "5.0.2", 152 | "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.2.tgz", 153 | "integrity": "sha512-vluaspfvWEtE4vcSDlKRNer52DvOGrB2xv6diXy6UKyKW0lqZiWHGNApSyxOv+8DE5Z27IzVvE7hNkxg7EXIcg==", 154 | "dev": true, 155 | "license": "MIT", 156 | "dependencies": { 157 | "@types/node": "*", 158 | "@types/qs": "*", 159 | "@types/range-parser": "*", 160 | "@types/send": "*" 161 | } 162 | }, 163 | "node_modules/@types/http-errors": { 164 | "version": "2.0.4", 165 | "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", 166 | "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", 167 | "dev": true, 168 | "license": "MIT" 169 | }, 170 | "node_modules/@types/mime": { 171 | "version": "1.3.5", 172 | "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", 173 | "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", 174 | "dev": true, 175 | "license": "MIT" 176 | }, 177 | "node_modules/@types/morgan": { 178 | "version": "1.9.9", 179 | "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.9.tgz", 180 | "integrity": "sha512-iRYSDKVaC6FkGSpEVVIvrRGw0DfJMiQzIn3qr2G5B3C//AWkulhXgaBd7tS9/J79GWSYMTHGs7PfI5b3Y8m+RQ==", 181 | "dev": true, 182 | "license": "MIT", 183 | "dependencies": { 184 | "@types/node": "*" 185 | } 186 | }, 187 | "node_modules/@types/node": { 188 | "version": "22.9.3", 189 | "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.3.tgz", 190 | "integrity": "sha512-F3u1fs/fce3FFk+DAxbxc78DF8x0cY09RRL8GnXLmkJ1jvx3TtPdWoTT5/NiYfI5ASqXBmfqJi9dZ3gxMx4lzw==", 191 | "dev": true, 192 | "license": "MIT", 193 | "dependencies": { 194 | "undici-types": "~6.19.8" 195 | } 196 | }, 197 | "node_modules/@types/qs": { 198 | "version": "6.9.17", 199 | "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", 200 | "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==", 201 | "dev": true, 202 | "license": "MIT" 203 | }, 204 | "node_modules/@types/range-parser": { 205 | "version": "1.2.7", 206 | "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", 207 | "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", 208 | "dev": true, 209 | "license": "MIT" 210 | }, 211 | "node_modules/@types/send": { 212 | "version": "0.17.4", 213 | "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", 214 | "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", 215 | "dev": true, 216 | "license": "MIT", 217 | "dependencies": { 218 | "@types/mime": "^1", 219 | "@types/node": "*" 220 | } 221 | }, 222 | "node_modules/@types/serve-static": { 223 | "version": "1.15.7", 224 | "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", 225 | "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", 226 | "dev": true, 227 | "license": "MIT", 228 | "dependencies": { 229 | "@types/http-errors": "*", 230 | "@types/node": "*", 231 | "@types/send": "*" 232 | } 233 | }, 234 | "node_modules/@types/triple-beam": { 235 | "version": "1.3.5", 236 | "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", 237 | "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", 238 | "license": "MIT" 239 | }, 240 | "node_modules/accepts": { 241 | "version": "1.3.8", 242 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 243 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 244 | "license": "MIT", 245 | "dependencies": { 246 | "mime-types": "~2.1.34", 247 | "negotiator": "0.6.3" 248 | }, 249 | "engines": { 250 | "node": ">= 0.6" 251 | } 252 | }, 253 | "node_modules/array-flatten": { 254 | "version": "1.1.1", 255 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 256 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", 257 | "license": "MIT" 258 | }, 259 | "node_modules/async": { 260 | "version": "3.2.6", 261 | "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", 262 | "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", 263 | "license": "MIT" 264 | }, 265 | "node_modules/asynckit": { 266 | "version": "0.4.0", 267 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 268 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", 269 | "license": "MIT" 270 | }, 271 | "node_modules/axios": { 272 | "version": "1.9.0", 273 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", 274 | "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", 275 | "license": "MIT", 276 | "dependencies": { 277 | "follow-redirects": "^1.15.6", 278 | "form-data": "^4.0.0", 279 | "proxy-from-env": "^1.1.0" 280 | } 281 | }, 282 | "node_modules/axios-better-stacktrace": { 283 | "version": "2.1.7", 284 | "resolved": "https://registry.npmjs.org/axios-better-stacktrace/-/axios-better-stacktrace-2.1.7.tgz", 285 | "integrity": "sha512-m16wNbfb7crBpENBukoBdN1G9NwqSCkuIeKjSEP2iUoFvgNUnSW1/1Ov79EkTu29xmg+TsngJcy2lfwqBzVT7g==", 286 | "license": "MIT", 287 | "peerDependencies": { 288 | "axios": "*" 289 | } 290 | }, 291 | "node_modules/axios-mock-adapter": { 292 | "version": "2.1.0", 293 | "resolved": "https://registry.npmjs.org/axios-mock-adapter/-/axios-mock-adapter-2.1.0.tgz", 294 | "integrity": "sha512-AZUe4OjECGCNNssH8SOdtneiQELsqTsat3SQQCWLPjN436/H+L9AjWfV7bF+Zg/YL9cgbhrz5671hoh+Tbn98w==", 295 | "license": "MIT", 296 | "dependencies": { 297 | "fast-deep-equal": "^3.1.3", 298 | "is-buffer": "^2.0.5" 299 | }, 300 | "peerDependencies": { 301 | "axios": ">= 0.17.0" 302 | } 303 | }, 304 | "node_modules/basic-auth": { 305 | "version": "2.0.1", 306 | "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", 307 | "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", 308 | "license": "MIT", 309 | "dependencies": { 310 | "safe-buffer": "5.1.2" 311 | }, 312 | "engines": { 313 | "node": ">= 0.8" 314 | } 315 | }, 316 | "node_modules/basic-auth/node_modules/safe-buffer": { 317 | "version": "5.1.2", 318 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 319 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 320 | "license": "MIT" 321 | }, 322 | "node_modules/body-parser": { 323 | "version": "1.20.3", 324 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", 325 | "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", 326 | "license": "MIT", 327 | "dependencies": { 328 | "bytes": "3.1.2", 329 | "content-type": "~1.0.5", 330 | "debug": "2.6.9", 331 | "depd": "2.0.0", 332 | "destroy": "1.2.0", 333 | "http-errors": "2.0.0", 334 | "iconv-lite": "0.4.24", 335 | "on-finished": "2.4.1", 336 | "qs": "6.13.0", 337 | "raw-body": "2.5.2", 338 | "type-is": "~1.6.18", 339 | "unpipe": "1.0.0" 340 | }, 341 | "engines": { 342 | "node": ">= 0.8", 343 | "npm": "1.2.8000 || >= 1.4.16" 344 | } 345 | }, 346 | "node_modules/bytes": { 347 | "version": "3.1.2", 348 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 349 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 350 | "license": "MIT", 351 | "engines": { 352 | "node": ">= 0.8" 353 | } 354 | }, 355 | "node_modules/call-bind": { 356 | "version": "1.0.7", 357 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", 358 | "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", 359 | "license": "MIT", 360 | "dependencies": { 361 | "es-define-property": "^1.0.0", 362 | "es-errors": "^1.3.0", 363 | "function-bind": "^1.1.2", 364 | "get-intrinsic": "^1.2.4", 365 | "set-function-length": "^1.2.1" 366 | }, 367 | "engines": { 368 | "node": ">= 0.4" 369 | }, 370 | "funding": { 371 | "url": "https://github.com/sponsors/ljharb" 372 | } 373 | }, 374 | "node_modules/color": { 375 | "version": "3.2.1", 376 | "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", 377 | "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", 378 | "license": "MIT", 379 | "dependencies": { 380 | "color-convert": "^1.9.3", 381 | "color-string": "^1.6.0" 382 | } 383 | }, 384 | "node_modules/color-name": { 385 | "version": "1.1.4", 386 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 387 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 388 | "license": "MIT" 389 | }, 390 | "node_modules/color-string": { 391 | "version": "1.9.1", 392 | "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", 393 | "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", 394 | "license": "MIT", 395 | "dependencies": { 396 | "color-name": "^1.0.0", 397 | "simple-swizzle": "^0.2.2" 398 | } 399 | }, 400 | "node_modules/color/node_modules/color-convert": { 401 | "version": "1.9.3", 402 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 403 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 404 | "license": "MIT", 405 | "dependencies": { 406 | "color-name": "1.1.3" 407 | } 408 | }, 409 | "node_modules/color/node_modules/color-name": { 410 | "version": "1.1.3", 411 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 412 | "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", 413 | "license": "MIT" 414 | }, 415 | "node_modules/colorspace": { 416 | "version": "1.1.4", 417 | "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", 418 | "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", 419 | "license": "MIT", 420 | "dependencies": { 421 | "color": "^3.1.3", 422 | "text-hex": "1.0.x" 423 | } 424 | }, 425 | "node_modules/combined-stream": { 426 | "version": "1.0.8", 427 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 428 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 429 | "license": "MIT", 430 | "dependencies": { 431 | "delayed-stream": "~1.0.0" 432 | }, 433 | "engines": { 434 | "node": ">= 0.8" 435 | } 436 | }, 437 | "node_modules/compressible": { 438 | "version": "2.0.18", 439 | "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", 440 | "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", 441 | "license": "MIT", 442 | "dependencies": { 443 | "mime-db": ">= 1.43.0 < 2" 444 | }, 445 | "engines": { 446 | "node": ">= 0.6" 447 | } 448 | }, 449 | "node_modules/compression": { 450 | "version": "1.7.5", 451 | "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.5.tgz", 452 | "integrity": "sha512-bQJ0YRck5ak3LgtnpKkiabX5pNF7tMUh1BSy2ZBOTh0Dim0BUu6aPPwByIns6/A5Prh8PufSPerMDUklpzes2Q==", 453 | "license": "MIT", 454 | "dependencies": { 455 | "bytes": "3.1.2", 456 | "compressible": "~2.0.18", 457 | "debug": "2.6.9", 458 | "negotiator": "~0.6.4", 459 | "on-headers": "~1.0.2", 460 | "safe-buffer": "5.2.1", 461 | "vary": "~1.1.2" 462 | }, 463 | "engines": { 464 | "node": ">= 0.8.0" 465 | } 466 | }, 467 | "node_modules/compression/node_modules/negotiator": { 468 | "version": "0.6.4", 469 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", 470 | "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", 471 | "license": "MIT", 472 | "engines": { 473 | "node": ">= 0.6" 474 | } 475 | }, 476 | "node_modules/content-disposition": { 477 | "version": "0.5.4", 478 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 479 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 480 | "license": "MIT", 481 | "dependencies": { 482 | "safe-buffer": "5.2.1" 483 | }, 484 | "engines": { 485 | "node": ">= 0.6" 486 | } 487 | }, 488 | "node_modules/content-type": { 489 | "version": "1.0.5", 490 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 491 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 492 | "license": "MIT", 493 | "engines": { 494 | "node": ">= 0.6" 495 | } 496 | }, 497 | "node_modules/cookie": { 498 | "version": "0.7.1", 499 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", 500 | "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", 501 | "license": "MIT", 502 | "engines": { 503 | "node": ">= 0.6" 504 | } 505 | }, 506 | "node_modules/cookie-parser": { 507 | "version": "1.4.7", 508 | "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", 509 | "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", 510 | "license": "MIT", 511 | "dependencies": { 512 | "cookie": "0.7.2", 513 | "cookie-signature": "1.0.6" 514 | }, 515 | "engines": { 516 | "node": ">= 0.8.0" 517 | } 518 | }, 519 | "node_modules/cookie-parser/node_modules/cookie": { 520 | "version": "0.7.2", 521 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", 522 | "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", 523 | "license": "MIT", 524 | "engines": { 525 | "node": ">= 0.6" 526 | } 527 | }, 528 | "node_modules/cookie-signature": { 529 | "version": "1.0.6", 530 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 531 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", 532 | "license": "MIT" 533 | }, 534 | "node_modules/cors": { 535 | "version": "2.8.5", 536 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 537 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 538 | "license": "MIT", 539 | "dependencies": { 540 | "object-assign": "^4", 541 | "vary": "^1" 542 | }, 543 | "engines": { 544 | "node": ">= 0.10" 545 | } 546 | }, 547 | "node_modules/debug": { 548 | "version": "2.6.9", 549 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 550 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 551 | "license": "MIT", 552 | "dependencies": { 553 | "ms": "2.0.0" 554 | } 555 | }, 556 | "node_modules/dedent": { 557 | "version": "1.5.3", 558 | "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", 559 | "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", 560 | "dev": true, 561 | "license": "MIT", 562 | "peerDependencies": { 563 | "babel-plugin-macros": "^3.1.0" 564 | }, 565 | "peerDependenciesMeta": { 566 | "babel-plugin-macros": { 567 | "optional": true 568 | } 569 | } 570 | }, 571 | "node_modules/define-data-property": { 572 | "version": "1.1.4", 573 | "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", 574 | "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", 575 | "license": "MIT", 576 | "dependencies": { 577 | "es-define-property": "^1.0.0", 578 | "es-errors": "^1.3.0", 579 | "gopd": "^1.0.1" 580 | }, 581 | "engines": { 582 | "node": ">= 0.4" 583 | }, 584 | "funding": { 585 | "url": "https://github.com/sponsors/ljharb" 586 | } 587 | }, 588 | "node_modules/delayed-stream": { 589 | "version": "1.0.0", 590 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 591 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 592 | "license": "MIT", 593 | "engines": { 594 | "node": ">=0.4.0" 595 | } 596 | }, 597 | "node_modules/depd": { 598 | "version": "2.0.0", 599 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 600 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 601 | "license": "MIT", 602 | "engines": { 603 | "node": ">= 0.8" 604 | } 605 | }, 606 | "node_modules/destroy": { 607 | "version": "1.2.0", 608 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 609 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", 610 | "license": "MIT", 611 | "engines": { 612 | "node": ">= 0.8", 613 | "npm": "1.2.8000 || >= 1.4.16" 614 | } 615 | }, 616 | "node_modules/dotenv": { 617 | "version": "16.4.5", 618 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", 619 | "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", 620 | "license": "BSD-2-Clause", 621 | "engines": { 622 | "node": ">=12" 623 | }, 624 | "funding": { 625 | "url": "https://dotenvx.com" 626 | } 627 | }, 628 | "node_modules/ee-first": { 629 | "version": "1.1.1", 630 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 631 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", 632 | "license": "MIT" 633 | }, 634 | "node_modules/enabled": { 635 | "version": "2.0.0", 636 | "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", 637 | "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", 638 | "license": "MIT" 639 | }, 640 | "node_modules/encodeurl": { 641 | "version": "2.0.0", 642 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", 643 | "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", 644 | "license": "MIT", 645 | "engines": { 646 | "node": ">= 0.8" 647 | } 648 | }, 649 | "node_modules/es-define-property": { 650 | "version": "1.0.0", 651 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", 652 | "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", 653 | "license": "MIT", 654 | "dependencies": { 655 | "get-intrinsic": "^1.2.4" 656 | }, 657 | "engines": { 658 | "node": ">= 0.4" 659 | } 660 | }, 661 | "node_modules/es-errors": { 662 | "version": "1.3.0", 663 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 664 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 665 | "license": "MIT", 666 | "engines": { 667 | "node": ">= 0.4" 668 | } 669 | }, 670 | "node_modules/escape-html": { 671 | "version": "1.0.3", 672 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 673 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", 674 | "license": "MIT" 675 | }, 676 | "node_modules/etag": { 677 | "version": "1.8.1", 678 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 679 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 680 | "license": "MIT", 681 | "engines": { 682 | "node": ">= 0.6" 683 | } 684 | }, 685 | "node_modules/express": { 686 | "version": "4.21.2", 687 | "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", 688 | "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", 689 | "license": "MIT", 690 | "dependencies": { 691 | "accepts": "~1.3.8", 692 | "array-flatten": "1.1.1", 693 | "body-parser": "1.20.3", 694 | "content-disposition": "0.5.4", 695 | "content-type": "~1.0.4", 696 | "cookie": "0.7.1", 697 | "cookie-signature": "1.0.6", 698 | "debug": "2.6.9", 699 | "depd": "2.0.0", 700 | "encodeurl": "~2.0.0", 701 | "escape-html": "~1.0.3", 702 | "etag": "~1.8.1", 703 | "finalhandler": "1.3.1", 704 | "fresh": "0.5.2", 705 | "http-errors": "2.0.0", 706 | "merge-descriptors": "1.0.3", 707 | "methods": "~1.1.2", 708 | "on-finished": "2.4.1", 709 | "parseurl": "~1.3.3", 710 | "path-to-regexp": "0.1.12", 711 | "proxy-addr": "~2.0.7", 712 | "qs": "6.13.0", 713 | "range-parser": "~1.2.1", 714 | "safe-buffer": "5.2.1", 715 | "send": "0.19.0", 716 | "serve-static": "1.16.2", 717 | "setprototypeof": "1.2.0", 718 | "statuses": "2.0.1", 719 | "type-is": "~1.6.18", 720 | "utils-merge": "1.0.1", 721 | "vary": "~1.1.2" 722 | }, 723 | "engines": { 724 | "node": ">= 0.10.0" 725 | }, 726 | "funding": { 727 | "type": "opencollective", 728 | "url": "https://opencollective.com/express" 729 | } 730 | }, 731 | "node_modules/fast-deep-equal": { 732 | "version": "3.1.3", 733 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 734 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 735 | "license": "MIT" 736 | }, 737 | "node_modules/fecha": { 738 | "version": "4.2.3", 739 | "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", 740 | "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", 741 | "license": "MIT" 742 | }, 743 | "node_modules/finalhandler": { 744 | "version": "1.3.1", 745 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", 746 | "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", 747 | "license": "MIT", 748 | "dependencies": { 749 | "debug": "2.6.9", 750 | "encodeurl": "~2.0.0", 751 | "escape-html": "~1.0.3", 752 | "on-finished": "2.4.1", 753 | "parseurl": "~1.3.3", 754 | "statuses": "2.0.1", 755 | "unpipe": "~1.0.0" 756 | }, 757 | "engines": { 758 | "node": ">= 0.8" 759 | } 760 | }, 761 | "node_modules/find-up-simple": { 762 | "version": "1.0.0", 763 | "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.0.tgz", 764 | "integrity": "sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==", 765 | "dev": true, 766 | "license": "MIT", 767 | "engines": { 768 | "node": ">=18" 769 | }, 770 | "funding": { 771 | "url": "https://github.com/sponsors/sindresorhus" 772 | } 773 | }, 774 | "node_modules/fn.name": { 775 | "version": "1.1.0", 776 | "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", 777 | "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", 778 | "license": "MIT" 779 | }, 780 | "node_modules/follow-redirects": { 781 | "version": "1.15.9", 782 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", 783 | "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", 784 | "funding": [ 785 | { 786 | "type": "individual", 787 | "url": "https://github.com/sponsors/RubenVerborgh" 788 | } 789 | ], 790 | "license": "MIT", 791 | "engines": { 792 | "node": ">=4.0" 793 | }, 794 | "peerDependenciesMeta": { 795 | "debug": { 796 | "optional": true 797 | } 798 | } 799 | }, 800 | "node_modules/form-data": { 801 | "version": "4.0.1", 802 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", 803 | "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", 804 | "license": "MIT", 805 | "dependencies": { 806 | "asynckit": "^0.4.0", 807 | "combined-stream": "^1.0.8", 808 | "mime-types": "^2.1.12" 809 | }, 810 | "engines": { 811 | "node": ">= 6" 812 | } 813 | }, 814 | "node_modules/forwarded": { 815 | "version": "0.2.0", 816 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 817 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 818 | "license": "MIT", 819 | "engines": { 820 | "node": ">= 0.6" 821 | } 822 | }, 823 | "node_modules/fresh": { 824 | "version": "0.5.2", 825 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 826 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", 827 | "license": "MIT", 828 | "engines": { 829 | "node": ">= 0.6" 830 | } 831 | }, 832 | "node_modules/function-bind": { 833 | "version": "1.1.2", 834 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 835 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 836 | "license": "MIT", 837 | "funding": { 838 | "url": "https://github.com/sponsors/ljharb" 839 | } 840 | }, 841 | "node_modules/get-intrinsic": { 842 | "version": "1.2.4", 843 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", 844 | "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", 845 | "license": "MIT", 846 | "dependencies": { 847 | "es-errors": "^1.3.0", 848 | "function-bind": "^1.1.2", 849 | "has-proto": "^1.0.1", 850 | "has-symbols": "^1.0.3", 851 | "hasown": "^2.0.0" 852 | }, 853 | "engines": { 854 | "node": ">= 0.4" 855 | }, 856 | "funding": { 857 | "url": "https://github.com/sponsors/ljharb" 858 | } 859 | }, 860 | "node_modules/gopd": { 861 | "version": "1.0.1", 862 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", 863 | "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", 864 | "license": "MIT", 865 | "dependencies": { 866 | "get-intrinsic": "^1.1.3" 867 | }, 868 | "funding": { 869 | "url": "https://github.com/sponsors/ljharb" 870 | } 871 | }, 872 | "node_modules/has-property-descriptors": { 873 | "version": "1.0.2", 874 | "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", 875 | "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", 876 | "license": "MIT", 877 | "dependencies": { 878 | "es-define-property": "^1.0.0" 879 | }, 880 | "funding": { 881 | "url": "https://github.com/sponsors/ljharb" 882 | } 883 | }, 884 | "node_modules/has-proto": { 885 | "version": "1.0.3", 886 | "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", 887 | "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", 888 | "license": "MIT", 889 | "engines": { 890 | "node": ">= 0.4" 891 | }, 892 | "funding": { 893 | "url": "https://github.com/sponsors/ljharb" 894 | } 895 | }, 896 | "node_modules/has-symbols": { 897 | "version": "1.0.3", 898 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 899 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", 900 | "license": "MIT", 901 | "engines": { 902 | "node": ">= 0.4" 903 | }, 904 | "funding": { 905 | "url": "https://github.com/sponsors/ljharb" 906 | } 907 | }, 908 | "node_modules/hasown": { 909 | "version": "2.0.2", 910 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 911 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 912 | "license": "MIT", 913 | "dependencies": { 914 | "function-bind": "^1.1.2" 915 | }, 916 | "engines": { 917 | "node": ">= 0.4" 918 | } 919 | }, 920 | "node_modules/http-errors": { 921 | "version": "2.0.0", 922 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 923 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 924 | "license": "MIT", 925 | "dependencies": { 926 | "depd": "2.0.0", 927 | "inherits": "2.0.4", 928 | "setprototypeof": "1.2.0", 929 | "statuses": "2.0.1", 930 | "toidentifier": "1.0.1" 931 | }, 932 | "engines": { 933 | "node": ">= 0.8" 934 | } 935 | }, 936 | "node_modules/iconv-lite": { 937 | "version": "0.4.24", 938 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 939 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 940 | "license": "MIT", 941 | "dependencies": { 942 | "safer-buffer": ">= 2.1.2 < 3" 943 | }, 944 | "engines": { 945 | "node": ">=0.10.0" 946 | } 947 | }, 948 | "node_modules/inherits": { 949 | "version": "2.0.4", 950 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 951 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 952 | "license": "ISC" 953 | }, 954 | "node_modules/ipaddr.js": { 955 | "version": "1.9.1", 956 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 957 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 958 | "license": "MIT", 959 | "engines": { 960 | "node": ">= 0.10" 961 | } 962 | }, 963 | "node_modules/is-arrayish": { 964 | "version": "0.3.2", 965 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", 966 | "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", 967 | "license": "MIT" 968 | }, 969 | "node_modules/is-buffer": { 970 | "version": "2.0.5", 971 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", 972 | "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", 973 | "funding": [ 974 | { 975 | "type": "github", 976 | "url": "https://github.com/sponsors/feross" 977 | }, 978 | { 979 | "type": "patreon", 980 | "url": "https://www.patreon.com/feross" 981 | }, 982 | { 983 | "type": "consulting", 984 | "url": "https://feross.org/support" 985 | } 986 | ], 987 | "license": "MIT", 988 | "engines": { 989 | "node": ">=4" 990 | } 991 | }, 992 | "node_modules/is-stream": { 993 | "version": "2.0.1", 994 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", 995 | "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", 996 | "license": "MIT", 997 | "engines": { 998 | "node": ">=8" 999 | }, 1000 | "funding": { 1001 | "url": "https://github.com/sponsors/sindresorhus" 1002 | } 1003 | }, 1004 | "node_modules/jose": { 1005 | "version": "5.9.6", 1006 | "resolved": "https://registry.npmjs.org/jose/-/jose-5.9.6.tgz", 1007 | "integrity": "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==", 1008 | "license": "MIT", 1009 | "funding": { 1010 | "url": "https://github.com/sponsors/panva" 1011 | } 1012 | }, 1013 | "node_modules/kuler": { 1014 | "version": "2.0.0", 1015 | "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", 1016 | "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", 1017 | "license": "MIT" 1018 | }, 1019 | "node_modules/logform": { 1020 | "version": "2.7.0", 1021 | "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", 1022 | "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", 1023 | "license": "MIT", 1024 | "dependencies": { 1025 | "@colors/colors": "1.6.0", 1026 | "@types/triple-beam": "^1.3.2", 1027 | "fecha": "^4.2.0", 1028 | "ms": "^2.1.1", 1029 | "safe-stable-stringify": "^2.3.1", 1030 | "triple-beam": "^1.3.0" 1031 | }, 1032 | "engines": { 1033 | "node": ">= 12.0.0" 1034 | } 1035 | }, 1036 | "node_modules/logform/node_modules/ms": { 1037 | "version": "2.1.3", 1038 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1039 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 1040 | "license": "MIT" 1041 | }, 1042 | "node_modules/lowdb": { 1043 | "version": "7.0.1", 1044 | "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-7.0.1.tgz", 1045 | "integrity": "sha512-neJAj8GwF0e8EpycYIDFqEPcx9Qz4GUho20jWFR7YiFeXzF1YMLdxB36PypcTSPMA+4+LvgyMacYhlr18Zlymw==", 1046 | "license": "MIT", 1047 | "dependencies": { 1048 | "steno": "^4.0.2" 1049 | }, 1050 | "engines": { 1051 | "node": ">=18" 1052 | }, 1053 | "funding": { 1054 | "url": "https://github.com/sponsors/typicode" 1055 | } 1056 | }, 1057 | "node_modules/media-typer": { 1058 | "version": "0.3.0", 1059 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 1060 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", 1061 | "license": "MIT", 1062 | "engines": { 1063 | "node": ">= 0.6" 1064 | } 1065 | }, 1066 | "node_modules/merge-descriptors": { 1067 | "version": "1.0.3", 1068 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", 1069 | "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", 1070 | "license": "MIT", 1071 | "funding": { 1072 | "url": "https://github.com/sponsors/sindresorhus" 1073 | } 1074 | }, 1075 | "node_modules/methods": { 1076 | "version": "1.1.2", 1077 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 1078 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", 1079 | "license": "MIT", 1080 | "engines": { 1081 | "node": ">= 0.6" 1082 | } 1083 | }, 1084 | "node_modules/micro-memoize": { 1085 | "version": "4.1.2", 1086 | "resolved": "https://registry.npmjs.org/micro-memoize/-/micro-memoize-4.1.2.tgz", 1087 | "integrity": "sha512-+HzcV2H+rbSJzApgkj0NdTakkC+bnyeiUxgT6/m7mjcz1CmM22KYFKp+EVj1sWe4UYcnriJr5uqHQD/gMHLD+g==", 1088 | "dev": true, 1089 | "license": "MIT" 1090 | }, 1091 | "node_modules/mime": { 1092 | "version": "1.6.0", 1093 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 1094 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 1095 | "license": "MIT", 1096 | "bin": { 1097 | "mime": "cli.js" 1098 | }, 1099 | "engines": { 1100 | "node": ">=4" 1101 | } 1102 | }, 1103 | "node_modules/mime-db": { 1104 | "version": "1.52.0", 1105 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 1106 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 1107 | "license": "MIT", 1108 | "engines": { 1109 | "node": ">= 0.6" 1110 | } 1111 | }, 1112 | "node_modules/mime-types": { 1113 | "version": "2.1.35", 1114 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 1115 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 1116 | "license": "MIT", 1117 | "dependencies": { 1118 | "mime-db": "1.52.0" 1119 | }, 1120 | "engines": { 1121 | "node": ">= 0.6" 1122 | } 1123 | }, 1124 | "node_modules/morgan": { 1125 | "version": "1.10.0", 1126 | "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", 1127 | "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", 1128 | "license": "MIT", 1129 | "dependencies": { 1130 | "basic-auth": "~2.0.1", 1131 | "debug": "2.6.9", 1132 | "depd": "~2.0.0", 1133 | "on-finished": "~2.3.0", 1134 | "on-headers": "~1.0.2" 1135 | }, 1136 | "engines": { 1137 | "node": ">= 0.8.0" 1138 | } 1139 | }, 1140 | "node_modules/morgan/node_modules/on-finished": { 1141 | "version": "2.3.0", 1142 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 1143 | "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", 1144 | "license": "MIT", 1145 | "dependencies": { 1146 | "ee-first": "1.1.1" 1147 | }, 1148 | "engines": { 1149 | "node": ">= 0.8" 1150 | } 1151 | }, 1152 | "node_modules/ms": { 1153 | "version": "2.0.0", 1154 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1155 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", 1156 | "license": "MIT" 1157 | }, 1158 | "node_modules/negotiator": { 1159 | "version": "0.6.3", 1160 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 1161 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", 1162 | "license": "MIT", 1163 | "engines": { 1164 | "node": ">= 0.6" 1165 | } 1166 | }, 1167 | "node_modules/object-assign": { 1168 | "version": "4.1.1", 1169 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 1170 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", 1171 | "license": "MIT", 1172 | "engines": { 1173 | "node": ">=0.10.0" 1174 | } 1175 | }, 1176 | "node_modules/object-inspect": { 1177 | "version": "1.13.3", 1178 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", 1179 | "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", 1180 | "license": "MIT", 1181 | "engines": { 1182 | "node": ">= 0.4" 1183 | }, 1184 | "funding": { 1185 | "url": "https://github.com/sponsors/ljharb" 1186 | } 1187 | }, 1188 | "node_modules/on-finished": { 1189 | "version": "2.4.1", 1190 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 1191 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 1192 | "license": "MIT", 1193 | "dependencies": { 1194 | "ee-first": "1.1.1" 1195 | }, 1196 | "engines": { 1197 | "node": ">= 0.8" 1198 | } 1199 | }, 1200 | "node_modules/on-headers": { 1201 | "version": "1.0.2", 1202 | "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", 1203 | "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", 1204 | "license": "MIT", 1205 | "engines": { 1206 | "node": ">= 0.8" 1207 | } 1208 | }, 1209 | "node_modules/one-time": { 1210 | "version": "1.0.0", 1211 | "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", 1212 | "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", 1213 | "license": "MIT", 1214 | "dependencies": { 1215 | "fn.name": "1.x.x" 1216 | } 1217 | }, 1218 | "node_modules/package-up": { 1219 | "version": "5.0.0", 1220 | "resolved": "https://registry.npmjs.org/package-up/-/package-up-5.0.0.tgz", 1221 | "integrity": "sha512-MQEgDUvXCa3sGvqHg3pzHO8e9gqTCMPVrWUko3vPQGntwegmFo52mZb2abIVTjFnUcW0BcPz0D93jV5Cas1DWA==", 1222 | "dev": true, 1223 | "license": "MIT", 1224 | "dependencies": { 1225 | "find-up-simple": "^1.0.0" 1226 | }, 1227 | "engines": { 1228 | "node": ">=18" 1229 | }, 1230 | "funding": { 1231 | "url": "https://github.com/sponsors/sindresorhus" 1232 | } 1233 | }, 1234 | "node_modules/parseurl": { 1235 | "version": "1.3.3", 1236 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 1237 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 1238 | "license": "MIT", 1239 | "engines": { 1240 | "node": ">= 0.8" 1241 | } 1242 | }, 1243 | "node_modules/path-to-regexp": { 1244 | "version": "0.1.12", 1245 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", 1246 | "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", 1247 | "license": "MIT" 1248 | }, 1249 | "node_modules/prettier": { 1250 | "version": "3.3.3", 1251 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", 1252 | "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", 1253 | "dev": true, 1254 | "license": "MIT", 1255 | "bin": { 1256 | "prettier": "bin/prettier.cjs" 1257 | }, 1258 | "engines": { 1259 | "node": ">=14" 1260 | }, 1261 | "funding": { 1262 | "url": "https://github.com/prettier/prettier?sponsor=1" 1263 | } 1264 | }, 1265 | "node_modules/prettier-plugin-embed": { 1266 | "version": "0.4.15", 1267 | "resolved": "https://registry.npmjs.org/prettier-plugin-embed/-/prettier-plugin-embed-0.4.15.tgz", 1268 | "integrity": "sha512-9pZVIp3bw2jw+Ge+iAMZ4j+sIVC9cPruZ93H2tj5Wa/3YDFDJ/uYyVWdUGfcFUnv28drhW2Bmome9xSGXsPKOw==", 1269 | "dev": true, 1270 | "license": "MIT", 1271 | "dependencies": { 1272 | "@types/estree": "^1.0.5", 1273 | "dedent": "^1.5.1", 1274 | "micro-memoize": "^4.1.2", 1275 | "package-up": "^5.0.0", 1276 | "tiny-jsonc": "^1.0.1", 1277 | "type-fest": "^4.10.3" 1278 | } 1279 | }, 1280 | "node_modules/proxy-addr": { 1281 | "version": "2.0.7", 1282 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 1283 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 1284 | "license": "MIT", 1285 | "dependencies": { 1286 | "forwarded": "0.2.0", 1287 | "ipaddr.js": "1.9.1" 1288 | }, 1289 | "engines": { 1290 | "node": ">= 0.10" 1291 | } 1292 | }, 1293 | "node_modules/proxy-from-env": { 1294 | "version": "1.1.0", 1295 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 1296 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", 1297 | "license": "MIT" 1298 | }, 1299 | "node_modules/qs": { 1300 | "version": "6.13.0", 1301 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", 1302 | "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", 1303 | "license": "BSD-3-Clause", 1304 | "dependencies": { 1305 | "side-channel": "^1.0.6" 1306 | }, 1307 | "engines": { 1308 | "node": ">=0.6" 1309 | }, 1310 | "funding": { 1311 | "url": "https://github.com/sponsors/ljharb" 1312 | } 1313 | }, 1314 | "node_modules/range-parser": { 1315 | "version": "1.2.1", 1316 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 1317 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 1318 | "license": "MIT", 1319 | "engines": { 1320 | "node": ">= 0.6" 1321 | } 1322 | }, 1323 | "node_modules/raw-body": { 1324 | "version": "2.5.2", 1325 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", 1326 | "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", 1327 | "license": "MIT", 1328 | "dependencies": { 1329 | "bytes": "3.1.2", 1330 | "http-errors": "2.0.0", 1331 | "iconv-lite": "0.4.24", 1332 | "unpipe": "1.0.0" 1333 | }, 1334 | "engines": { 1335 | "node": ">= 0.8" 1336 | } 1337 | }, 1338 | "node_modules/readable-stream": { 1339 | "version": "3.6.2", 1340 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", 1341 | "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", 1342 | "license": "MIT", 1343 | "dependencies": { 1344 | "inherits": "^2.0.3", 1345 | "string_decoder": "^1.1.1", 1346 | "util-deprecate": "^1.0.1" 1347 | }, 1348 | "engines": { 1349 | "node": ">= 6" 1350 | } 1351 | }, 1352 | "node_modules/safe-buffer": { 1353 | "version": "5.2.1", 1354 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1355 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 1356 | "funding": [ 1357 | { 1358 | "type": "github", 1359 | "url": "https://github.com/sponsors/feross" 1360 | }, 1361 | { 1362 | "type": "patreon", 1363 | "url": "https://www.patreon.com/feross" 1364 | }, 1365 | { 1366 | "type": "consulting", 1367 | "url": "https://feross.org/support" 1368 | } 1369 | ], 1370 | "license": "MIT" 1371 | }, 1372 | "node_modules/safe-stable-stringify": { 1373 | "version": "2.5.0", 1374 | "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", 1375 | "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", 1376 | "license": "MIT", 1377 | "engines": { 1378 | "node": ">=10" 1379 | } 1380 | }, 1381 | "node_modules/safer-buffer": { 1382 | "version": "2.1.2", 1383 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1384 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 1385 | "license": "MIT" 1386 | }, 1387 | "node_modules/send": { 1388 | "version": "0.19.0", 1389 | "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", 1390 | "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", 1391 | "license": "MIT", 1392 | "dependencies": { 1393 | "debug": "2.6.9", 1394 | "depd": "2.0.0", 1395 | "destroy": "1.2.0", 1396 | "encodeurl": "~1.0.2", 1397 | "escape-html": "~1.0.3", 1398 | "etag": "~1.8.1", 1399 | "fresh": "0.5.2", 1400 | "http-errors": "2.0.0", 1401 | "mime": "1.6.0", 1402 | "ms": "2.1.3", 1403 | "on-finished": "2.4.1", 1404 | "range-parser": "~1.2.1", 1405 | "statuses": "2.0.1" 1406 | }, 1407 | "engines": { 1408 | "node": ">= 0.8.0" 1409 | } 1410 | }, 1411 | "node_modules/send/node_modules/encodeurl": { 1412 | "version": "1.0.2", 1413 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 1414 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", 1415 | "license": "MIT", 1416 | "engines": { 1417 | "node": ">= 0.8" 1418 | } 1419 | }, 1420 | "node_modules/send/node_modules/ms": { 1421 | "version": "2.1.3", 1422 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1423 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 1424 | "license": "MIT" 1425 | }, 1426 | "node_modules/serve-static": { 1427 | "version": "1.16.2", 1428 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", 1429 | "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", 1430 | "license": "MIT", 1431 | "dependencies": { 1432 | "encodeurl": "~2.0.0", 1433 | "escape-html": "~1.0.3", 1434 | "parseurl": "~1.3.3", 1435 | "send": "0.19.0" 1436 | }, 1437 | "engines": { 1438 | "node": ">= 0.8.0" 1439 | } 1440 | }, 1441 | "node_modules/set-function-length": { 1442 | "version": "1.2.2", 1443 | "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", 1444 | "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", 1445 | "license": "MIT", 1446 | "dependencies": { 1447 | "define-data-property": "^1.1.4", 1448 | "es-errors": "^1.3.0", 1449 | "function-bind": "^1.1.2", 1450 | "get-intrinsic": "^1.2.4", 1451 | "gopd": "^1.0.1", 1452 | "has-property-descriptors": "^1.0.2" 1453 | }, 1454 | "engines": { 1455 | "node": ">= 0.4" 1456 | } 1457 | }, 1458 | "node_modules/setprototypeof": { 1459 | "version": "1.2.0", 1460 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 1461 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", 1462 | "license": "ISC" 1463 | }, 1464 | "node_modules/side-channel": { 1465 | "version": "1.0.6", 1466 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", 1467 | "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", 1468 | "license": "MIT", 1469 | "dependencies": { 1470 | "call-bind": "^1.0.7", 1471 | "es-errors": "^1.3.0", 1472 | "get-intrinsic": "^1.2.4", 1473 | "object-inspect": "^1.13.1" 1474 | }, 1475 | "engines": { 1476 | "node": ">= 0.4" 1477 | }, 1478 | "funding": { 1479 | "url": "https://github.com/sponsors/ljharb" 1480 | } 1481 | }, 1482 | "node_modules/simple-swizzle": { 1483 | "version": "0.2.2", 1484 | "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", 1485 | "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", 1486 | "license": "MIT", 1487 | "dependencies": { 1488 | "is-arrayish": "^0.3.1" 1489 | } 1490 | }, 1491 | "node_modules/stack-trace": { 1492 | "version": "0.0.10", 1493 | "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", 1494 | "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", 1495 | "license": "MIT", 1496 | "engines": { 1497 | "node": "*" 1498 | } 1499 | }, 1500 | "node_modules/statuses": { 1501 | "version": "2.0.1", 1502 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 1503 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 1504 | "license": "MIT", 1505 | "engines": { 1506 | "node": ">= 0.8" 1507 | } 1508 | }, 1509 | "node_modules/steno": { 1510 | "version": "4.0.2", 1511 | "resolved": "https://registry.npmjs.org/steno/-/steno-4.0.2.tgz", 1512 | "integrity": "sha512-yhPIQXjrlt1xv7dyPQg2P17URmXbuM5pdGkpiMB3RenprfiBlvK415Lctfe0eshk90oA7/tNq7WEiMK8RSP39A==", 1513 | "license": "MIT", 1514 | "engines": { 1515 | "node": ">=18" 1516 | }, 1517 | "funding": { 1518 | "url": "https://github.com/sponsors/typicode" 1519 | } 1520 | }, 1521 | "node_modules/string_decoder": { 1522 | "version": "1.3.0", 1523 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 1524 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 1525 | "license": "MIT", 1526 | "dependencies": { 1527 | "safe-buffer": "~5.2.0" 1528 | } 1529 | }, 1530 | "node_modules/text-hex": { 1531 | "version": "1.0.0", 1532 | "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", 1533 | "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", 1534 | "license": "MIT" 1535 | }, 1536 | "node_modules/tiny-jsonc": { 1537 | "version": "1.0.1", 1538 | "resolved": "https://registry.npmjs.org/tiny-jsonc/-/tiny-jsonc-1.0.1.tgz", 1539 | "integrity": "sha512-ik6BCxzva9DoiEfDX/li0L2cWKPPENYvixUprFdl3YPi4bZZUhDnNI9YUkacrv+uIG90dnxR5mNqaoD6UhD6Bw==", 1540 | "dev": true 1541 | }, 1542 | "node_modules/toidentifier": { 1543 | "version": "1.0.1", 1544 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 1545 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 1546 | "license": "MIT", 1547 | "engines": { 1548 | "node": ">=0.6" 1549 | } 1550 | }, 1551 | "node_modules/triple-beam": { 1552 | "version": "1.4.1", 1553 | "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", 1554 | "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", 1555 | "license": "MIT", 1556 | "engines": { 1557 | "node": ">= 14.0.0" 1558 | } 1559 | }, 1560 | "node_modules/type-fest": { 1561 | "version": "4.28.0", 1562 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.28.0.tgz", 1563 | "integrity": "sha512-jXMwges/FVbFRe5lTMJZVEZCrO9kI9c8k0PA/z7nF3bo0JSCCLysvokFjNPIUK/itEMas10MQM+AiHoHt/T/XA==", 1564 | "dev": true, 1565 | "license": "(MIT OR CC0-1.0)", 1566 | "engines": { 1567 | "node": ">=16" 1568 | }, 1569 | "funding": { 1570 | "url": "https://github.com/sponsors/sindresorhus" 1571 | } 1572 | }, 1573 | "node_modules/type-is": { 1574 | "version": "1.6.18", 1575 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 1576 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 1577 | "license": "MIT", 1578 | "dependencies": { 1579 | "media-typer": "0.3.0", 1580 | "mime-types": "~2.1.24" 1581 | }, 1582 | "engines": { 1583 | "node": ">= 0.6" 1584 | } 1585 | }, 1586 | "node_modules/typescript": { 1587 | "version": "5.7.2", 1588 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", 1589 | "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", 1590 | "license": "Apache-2.0", 1591 | "bin": { 1592 | "tsc": "bin/tsc", 1593 | "tsserver": "bin/tsserver" 1594 | }, 1595 | "engines": { 1596 | "node": ">=14.17" 1597 | } 1598 | }, 1599 | "node_modules/undici-types": { 1600 | "version": "6.19.8", 1601 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", 1602 | "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", 1603 | "dev": true, 1604 | "license": "MIT" 1605 | }, 1606 | "node_modules/unpipe": { 1607 | "version": "1.0.0", 1608 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1609 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 1610 | "license": "MIT", 1611 | "engines": { 1612 | "node": ">= 0.8" 1613 | } 1614 | }, 1615 | "node_modules/util-deprecate": { 1616 | "version": "1.0.2", 1617 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1618 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", 1619 | "license": "MIT" 1620 | }, 1621 | "node_modules/utils-merge": { 1622 | "version": "1.0.1", 1623 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1624 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", 1625 | "license": "MIT", 1626 | "engines": { 1627 | "node": ">= 0.4.0" 1628 | } 1629 | }, 1630 | "node_modules/vary": { 1631 | "version": "1.1.2", 1632 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1633 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 1634 | "license": "MIT", 1635 | "engines": { 1636 | "node": ">= 0.8" 1637 | } 1638 | }, 1639 | "node_modules/winston": { 1640 | "version": "3.17.0", 1641 | "resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz", 1642 | "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==", 1643 | "license": "MIT", 1644 | "dependencies": { 1645 | "@colors/colors": "^1.6.0", 1646 | "@dabh/diagnostics": "^2.0.2", 1647 | "async": "^3.2.3", 1648 | "is-stream": "^2.0.0", 1649 | "logform": "^2.7.0", 1650 | "one-time": "^1.0.0", 1651 | "readable-stream": "^3.4.0", 1652 | "safe-stable-stringify": "^2.3.1", 1653 | "stack-trace": "0.0.x", 1654 | "triple-beam": "^1.3.0", 1655 | "winston-transport": "^4.9.0" 1656 | }, 1657 | "engines": { 1658 | "node": ">= 12.0.0" 1659 | } 1660 | }, 1661 | "node_modules/winston-transport": { 1662 | "version": "4.9.0", 1663 | "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", 1664 | "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", 1665 | "license": "MIT", 1666 | "dependencies": { 1667 | "logform": "^2.7.0", 1668 | "readable-stream": "^3.6.2", 1669 | "triple-beam": "^1.3.0" 1670 | }, 1671 | "engines": { 1672 | "node": ">= 12.0.0" 1673 | } 1674 | } 1675 | } 1676 | } 1677 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-vanillajs-ts-express", 3 | "version": "1.0.0", 4 | "description": "Express app with TypeScript", 5 | "type": "module", 6 | "main": "dist/index.js", 7 | "scripts": { 8 | "start": "tsc && node dist/index.js", 9 | "prettier:fix": "prettier --write './**/*.{js,html,ts,css}'" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "@corbado/node-sdk": "^3.0.2", 16 | "@corbado/types": "^2.11.2", 17 | "compression": "^1.7.5", 18 | "cookie-parser": "^1.4.7", 19 | "cors": "^2.8.5", 20 | "dotenv": "^16.4.5", 21 | "express": "^4.21.1", 22 | "lowdb": "^7.0.1", 23 | "morgan": "^1.10.0", 24 | "winston": "^3.17.0" 25 | }, 26 | "devDependencies": { 27 | "@types/compression": "^1.7.5", 28 | "@types/cookie-parser": "^1.4.8", 29 | "@types/cors": "^2.8.17", 30 | "@types/express": "^5.0.0", 31 | "@types/morgan": "^1.9.9", 32 | "@types/node": "^22.9.3", 33 | "prettier": "^3.3.3", 34 | "prettier-plugin-embed": "^0.4.15", 35 | "typescript": "^5.7.2" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /backend/src/app.ts: -------------------------------------------------------------------------------- 1 | import express, { Application } from "express"; 2 | import cors from "cors"; 3 | import compression from "compression"; 4 | import morgan from "morgan"; 5 | import routes from "./routes/index.js"; 6 | import errorHandler from "./middleware/error-handler.js"; 7 | import { loggerMiddleware } from "./utils/logger.js"; 8 | import cookieParser from "cookie-parser"; 9 | import { authenticateMiddleware } from "./middleware/auth.js"; 10 | 11 | class App { 12 | public app: Application; 13 | 14 | constructor() { 15 | this.app = express(); 16 | this.initializeMiddlewares(); 17 | this.initializeRoutes(); 18 | this.initializeErrorHandling(); 19 | } 20 | 21 | public listen() { 22 | const port = process.env.PORT || 3001; 23 | this.app.listen(port, () => { 24 | console.log(`Server is running on port ${port}`); 25 | }); 26 | } 27 | 28 | private initializeMiddlewares() { 29 | // Security middlewares 30 | this.app.use( 31 | cors({ 32 | origin: "http://localhost:3000", 33 | credentials: true, 34 | }), 35 | ); 36 | 37 | // Logging middleware 38 | this.app.use(morgan("dev")); 39 | this.app.use(loggerMiddleware); 40 | 41 | // Performance middlewares 42 | this.app.use(compression()); 43 | 44 | // Parsing middlewares 45 | this.app.use( 46 | express.json({ 47 | limit: "10kb", // Protect against large payloads 48 | }), 49 | ); 50 | this.app.use( 51 | express.urlencoded({ 52 | extended: true, 53 | limit: "10kb", 54 | }), 55 | ); 56 | 57 | // Cookie and session parsing 58 | this.app.use(cookieParser()); 59 | 60 | // Authentication middleware 61 | this.app.use(authenticateMiddleware); 62 | } 63 | 64 | private initializeRoutes() { 65 | this.app.use("/", routes); 66 | } 67 | 68 | private initializeErrorHandling() { 69 | this.app.use(errorHandler); 70 | } 71 | } 72 | 73 | export default new App(); 74 | -------------------------------------------------------------------------------- /backend/src/controller/secret-controller.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from "express"; 2 | import assert from "node:assert"; 3 | 4 | const secretString = "Passkeys are cool!"; 5 | 6 | class SecretController { 7 | public getSecret(req: Request, res: Response, next: NextFunction) { 8 | assert(req.user, "User is not defined. Use authentication middleware to protect this route."); 9 | res.json({ secret: secretString }); 10 | } 11 | } 12 | 13 | export default new SecretController(); 14 | -------------------------------------------------------------------------------- /backend/src/controller/user-controller.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from "express"; 2 | import { getUser, insertUser, updateUserCity } from "../db/queries.js"; 3 | import assert from "node:assert"; 4 | import { getUserIdentifiers } from "../utils/authentication.js"; 5 | 6 | class UserController { 7 | public async updateCity(req: Request, res: Response, next: NextFunction) { 8 | assert( 9 | req.user, 10 | "User is not defined. Use authentication middleware to protect this route.", 11 | ); 12 | const city = req.body.city; 13 | await updateUserCity(req.user!.userId, city); 14 | const updatedUser = getUser(req.user.userId); 15 | if (!updatedUser) { 16 | res.status(200).send(); 17 | return; 18 | } 19 | res.json(updatedUser); 20 | } 21 | 22 | public async infoGet(req: Request, res: Response, next: NextFunction) { 23 | const user = req.user; 24 | assert( 25 | user, 26 | "User is not defined. Use authentication middleware to protect this route.", 27 | ); 28 | let dbUser = getUser(user.userId); 29 | // if the user has not logged in before (i.e. getUser returns undefined), 30 | // we insert the user into the database 31 | dbUser ??= await insertUser(user.userId); 32 | // get the users identifiers via the Corbado Node.js SDK 33 | const userIdentifiers = await getUserIdentifiers(user.userId); 34 | // return all user info 35 | res.json({ user: dbUser, identifiers: userIdentifiers.identifiers }); 36 | } 37 | } 38 | 39 | export default new UserController(); 40 | -------------------------------------------------------------------------------- /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 | export default db; 14 | -------------------------------------------------------------------------------- /backend/src/db/queries.ts: -------------------------------------------------------------------------------- 1 | // userModel.ts 2 | import db, { 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(); 5 | -------------------------------------------------------------------------------- /backend/src/middleware/auth.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from "express"; 2 | import { 3 | getAuthenticatedUserFromAuthorizationHeader, 4 | getAuthenticatedUserFromCookie, 5 | } from "../utils/authentication.js"; 6 | import { logger } from "../utils/logger.js"; 7 | 8 | declare global { 9 | namespace Express { 10 | interface Request { 11 | user: { userId: string; fullName: string } | null; 12 | cookies: Record; 13 | } 14 | } 15 | } 16 | 17 | export async function authenticateMiddleware( 18 | req: Request, 19 | res: Response, 20 | next: NextFunction, 21 | ) { 22 | try { 23 | // Attempt to get authenticated user from cookie 24 | let user = await getAuthenticatedUserFromCookie(req); 25 | // Try authorization header if cookie didn't work 26 | user ??= await getAuthenticatedUserFromAuthorizationHeader(req); 27 | 28 | // Attach user to request object 29 | req.user = user; 30 | 31 | next(); 32 | } catch (error) { 33 | logger.error("Authentication middleware error", error); 34 | 35 | res.status(500); 36 | } 37 | } 38 | 39 | // Middleware to require authentication 40 | export function requireAuth(req: Request, res: Response, next: NextFunction) { 41 | if (!req.user) { 42 | res.status(401).json({ 43 | message: "Authentication required", 44 | }); 45 | return; 46 | } 47 | next(); 48 | } 49 | 50 | export function redirectIfNotAuthenticated(href: string) { 51 | return (req: Request, res: Response, next: NextFunction) => { 52 | if (!req.user) { 53 | res.redirect(href); 54 | return; 55 | } 56 | next(); 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /backend/src/middleware/error-handler.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from "express"; 2 | import { logger } from "../utils/logger.js"; 3 | 4 | export class HttpException extends Error { 5 | status: number; 6 | message: string; 7 | 8 | constructor(status: number, message: string) { 9 | super(message); 10 | this.status = status; 11 | this.message = message; 12 | } 13 | } 14 | 15 | export default function errorHandler( 16 | error: HttpException, 17 | req: Request, 18 | res: Response, 19 | next: NextFunction, 20 | ) { 21 | const status = error.status || 500; 22 | const message = error.message || "Something went wrong"; 23 | 24 | // Log the error 25 | logger.error(`[${req.method}] ${req.path} >> ${message}`); 26 | 27 | // Respond with error 28 | res.status(status).json({ 29 | status: "error", 30 | statusCode: status, 31 | message, 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /backend/src/routes/api.routes.ts: -------------------------------------------------------------------------------- 1 | // This file contains all api routes 2 | 3 | import { Router } from "express"; 4 | import secretRoutes from "./secret.routes.js"; 5 | import userRoutes from "./user.routes.js"; 6 | 7 | const router = Router(); 8 | 9 | // Health check route 10 | router.get("/", (req, res) => { 11 | res.json({ 12 | message: "API is running", 13 | timestamp: new Date().toISOString(), 14 | }); 15 | }); 16 | router.use("/user", userRoutes); 17 | router.use("/secret", secretRoutes); 18 | 19 | export default router; 20 | -------------------------------------------------------------------------------- /backend/src/routes/index.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import apiRoutes from "./api.routes.js"; 3 | 4 | const router = Router(); 5 | 6 | // Mount route modules 7 | router.use("/api", apiRoutes); 8 | 9 | export default router; 10 | -------------------------------------------------------------------------------- /backend/src/routes/secret.routes.ts: -------------------------------------------------------------------------------- 1 | // This file contains all secret routes 2 | 3 | import { Router } from "express"; 4 | import { requireAuth } from "../middleware/auth.js"; 5 | import secretController from "../controller/secret-controller.js"; 6 | 7 | const router = Router(); 8 | router.get("/", requireAuth, secretController.getSecret); 9 | 10 | export default router; 11 | -------------------------------------------------------------------------------- /backend/src/routes/user.routes.ts: -------------------------------------------------------------------------------- 1 | import { requireAuth } from "../middleware/auth.js"; 2 | import profileController from "../controller/user-controller.js"; 3 | import { Router } from "express"; 4 | 5 | const router = Router(); 6 | 7 | router.get("/", requireAuth, profileController.infoGet); 8 | router.post("/city", requireAuth, profileController.updateCity); 9 | 10 | export default router; 11 | -------------------------------------------------------------------------------- /backend/src/utils/authentication.ts: -------------------------------------------------------------------------------- 1 | import { Config, SDK } from "@corbado/node-sdk"; 2 | import { Request } from "express"; 3 | 4 | // Retrieve environment variables 5 | const projectID = process.env.CORBADO_PROJECT_ID; 6 | const apiSecret = process.env.CORBADO_API_SECRET; 7 | if (!projectID) { 8 | throw Error("Project ID is not set"); 9 | } 10 | if (!apiSecret) { 11 | throw Error("API secret is not set"); 12 | } 13 | const frontendAPI = process.env.CORBADO_FRONTEND_API; 14 | const backendAPI = process.env.CORBADO_BACKEND_API; 15 | if (!frontendAPI) { 16 | throw Error("Frontend API URL is not set"); 17 | } 18 | if (!backendAPI) { 19 | throw Error("Backend API URL is not set"); 20 | } 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(req: Request) { 27 | // Assuming you're using a cookie parsing middleware like cookie-parser 28 | const sessionToken = req.cookies?.cbo_session_token; 29 | 30 | if (!sessionToken) { 31 | return null; 32 | } 33 | 34 | try { 35 | // Your existing token validation logic 36 | return await sdk.sessions().validateToken(sessionToken); 37 | } catch (error) { 38 | // Log the error if needed 39 | return null; 40 | } 41 | } 42 | 43 | export async function getAuthenticatedUserFromAuthorizationHeader( 44 | req: Request, 45 | ) { 46 | const sessionToken = req.headers.authorization?.replace("Bearer ", ""); 47 | if (!sessionToken) { 48 | return null; 49 | } 50 | try { 51 | return await sdk.sessions().validateToken(sessionToken); 52 | } catch { 53 | return null; 54 | } 55 | } 56 | 57 | // Retrieve all identifiers for a given user ID 58 | export function getUserIdentifiers(userId: string) { 59 | // List user identifiers sorted by creation date in descending order 60 | return sdk.identifiers().listByUserId(userId); 61 | } 62 | -------------------------------------------------------------------------------- /backend/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 | } as const; 31 | 32 | export default englishTranslations; 33 | -------------------------------------------------------------------------------- /backend/src/utils/env.ts: -------------------------------------------------------------------------------- 1 | import dotenv from "dotenv"; 2 | 3 | dotenv.config(); 4 | -------------------------------------------------------------------------------- /backend/src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | import winston from "winston"; 2 | import { NextFunction, Request, Response } from "express"; 3 | 4 | // Create a logger 5 | export const logger = winston.createLogger({ 6 | level: "info", 7 | format: winston.format.combine( 8 | winston.format.timestamp(), 9 | winston.format.errors({ stack: true }), 10 | winston.format.splat(), 11 | winston.format.json(), 12 | ), 13 | transports: [ 14 | new winston.transports.Console({ 15 | format: winston.format.simple(), 16 | }), 17 | ], 18 | }); 19 | 20 | // Middleware to log requests 21 | export const loggerMiddleware = ( 22 | req: Request, 23 | res: Response, 24 | next: NextFunction, 25 | ) => { 26 | logger.info(`[${req.method}] ${req.path}`); 27 | next(); 28 | }; 29 | -------------------------------------------------------------------------------- /backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "module": "NodeNext", 5 | "moduleResolution": "NodeNext", 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "resolveJsonModule": true, 9 | "strict": true, 10 | "esModuleInterop": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | }, 14 | "include": ["src/**/*", "views/**/*", "public/**/*"], 15 | "exclude": ["node_modules", "**/*.spec.ts"], 16 | 17 | } 18 | -------------------------------------------------------------------------------- /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 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | 30 | *.tsbuildinfo 31 | 32 | .env* -------------------------------------------------------------------------------- /frontend/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "printWidth": 80, 4 | "embeddedLanguageFormatting": "auto", 5 | "plugins": [ 6 | "prettier-plugin-embed" 7 | ] 8 | } -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # corbado-vuejs-demo 2 | 3 | This template should help get you started developing with Vue 3 in Vite. 4 | 5 | ## Recommended IDE Setup 6 | 7 | [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur). 8 | 9 | ## Type Support for `.vue` Imports in TS 10 | 11 | TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types. 12 | 13 | ## Customize configuration 14 | 15 | See [Vite Configuration Reference](https://vite.dev/config/). 16 | 17 | ## Project Setup 18 | 19 | ```sh 20 | npm install 21 | ``` 22 | 23 | ### Compile and Hot-Reload for Development 24 | 25 | ```sh 26 | npm run dev 27 | ``` 28 | 29 | ### Type-Check, Compile and Minify for Production 30 | 31 | ```sh 32 | npm run build 33 | ``` 34 | 35 | ### Lint with [ESLint](https://eslint.org/) 36 | 37 | ```sh 38 | npm run lint 39 | ``` 40 | -------------------------------------------------------------------------------- /frontend/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /frontend/eslint.config.js: -------------------------------------------------------------------------------- 1 | import pluginVue from "eslint-plugin-vue"; 2 | import vueTsEslintConfig from "@vue/eslint-config-typescript"; 3 | import skipFormatting from "@vue/eslint-config-prettier/skip-formatting"; 4 | 5 | export default [ 6 | { 7 | name: "app/files-to-lint", 8 | files: ["**/*.{ts,mts,tsx,vue}"], 9 | }, 10 | 11 | { 12 | name: "app/files-to-ignore", 13 | ignores: ["**/dist/**", "**/dist-ssr/**", "**/coverage/**"], 14 | }, 15 | 16 | ...pluginVue.configs["flat/essential"], 17 | ...vueTsEslintConfig(), 18 | skipFormatting, 19 | ]; 20 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "corbado-vuejs-demo", 3 | "version": "0.0.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite --port 3000", 8 | "build": "run-p type-check \"build-only {@}\" --", 9 | "preview": "vite preview", 10 | "build-only": "vite build", 11 | "type-check": "vue-tsc --build", 12 | "lint": "eslint . --fix", 13 | "format": "prettier --write src/" 14 | }, 15 | "dependencies": { 16 | "@corbado/types": "^3.0.0", 17 | "@corbado/web-js": "^3.1.1", 18 | "@corbado/shared-util": "^1.0.10", 19 | "pinia": "^2.3.0", 20 | "prettier-plugin-embed": "^0.4.15", 21 | "vue": "^3.5.13", 22 | "vue-router": "^4.4.5" 23 | }, 24 | "devDependencies": { 25 | "@tsconfig/node22": "^22.0.0", 26 | "@types/node": "^22.9.3", 27 | "@vitejs/plugin-vue": "^5.2.1", 28 | "@vue/eslint-config-prettier": "^10.1.0", 29 | "@vue/eslint-config-typescript": "^14.1.3", 30 | "@vue/tsconfig": "^0.7.0", 31 | "eslint": "^9.14.0", 32 | "eslint-plugin-vue": "^9.30.0", 33 | "npm-run-all2": "^7.0.1", 34 | "prettier": "^3.3.3", 35 | "typescript": "~5.6.3", 36 | "vite": "^6.0.1", 37 | "vite-plugin-vue-devtools": "^7.6.5", 38 | "vue-tsc": "^2.1.10" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /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.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 16 | -------------------------------------------------------------------------------- /frontend/src/assets/base.css: -------------------------------------------------------------------------------- 1 | /* color palette from */ 2 | :root { 3 | --vt-c-white: #ffffff; 4 | --vt-c-white-soft: #f8f8f8; 5 | --vt-c-white-mute: #f2f2f2; 6 | 7 | --vt-c-black: #181818; 8 | --vt-c-black-soft: #222222; 9 | --vt-c-black-mute: #282828; 10 | 11 | --vt-c-indigo: #2c3e50; 12 | 13 | --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); 14 | --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); 15 | --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); 16 | --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); 17 | 18 | --vt-c-text-light-1: var(--vt-c-indigo); 19 | --vt-c-text-light-2: rgba(60, 60, 60, 0.66); 20 | --vt-c-text-dark-1: var(--vt-c-white); 21 | --vt-c-text-dark-2: rgba(235, 235, 235, 0.64); 22 | } 23 | 24 | /* semantic color variables for this project */ 25 | :root { 26 | --color-background: var(--vt-c-white); 27 | --color-background-soft: var(--vt-c-white-soft); 28 | --color-background-mute: var(--vt-c-white-mute); 29 | 30 | --color-border: var(--vt-c-divider-light-2); 31 | --color-border-hover: var(--vt-c-divider-light-1); 32 | 33 | --color-heading: var(--vt-c-text-light-1); 34 | --color-text: var(--vt-c-text-light-1); 35 | 36 | --section-gap: 160px; 37 | } 38 | 39 | @media (prefers-color-scheme: dark) { 40 | :root { 41 | --color-background: var(--vt-c-black); 42 | --color-background-soft: var(--vt-c-black-soft); 43 | --color-background-mute: var(--vt-c-black-mute); 44 | 45 | --color-border: var(--vt-c-divider-dark-2); 46 | --color-border-hover: var(--vt-c-divider-dark-1); 47 | 48 | --color-heading: var(--vt-c-text-dark-1); 49 | --color-text: var(--vt-c-text-dark-2); 50 | } 51 | } 52 | 53 | *, 54 | *::before, 55 | *::after { 56 | box-sizing: border-box; 57 | margin: 0; 58 | font-weight: normal; 59 | } 60 | 61 | body { 62 | min-height: 100vh; 63 | color: var(--color-text); 64 | background: var(--color-background); 65 | transition: 66 | color 0.5s, 67 | background-color 0.5s; 68 | line-height: 1.6; 69 | font-family: 70 | Inter, 71 | -apple-system, 72 | BlinkMacSystemFont, 73 | "Segoe UI", 74 | Roboto, 75 | Oxygen, 76 | Ubuntu, 77 | Cantarell, 78 | "Fira Sans", 79 | "Droid Sans", 80 | "Helvetica Neue", 81 | sans-serif; 82 | font-size: 15px; 83 | text-rendering: optimizeLegibility; 84 | -webkit-font-smoothing: antialiased; 85 | -moz-osx-font-smoothing: grayscale; 86 | } 87 | -------------------------------------------------------------------------------- /frontend/src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /frontend/src/assets/main.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/components/MainFooter.vue: -------------------------------------------------------------------------------- 1 | 29 | -------------------------------------------------------------------------------- /frontend/src/components/MainNavigation.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 79 | -------------------------------------------------------------------------------- /frontend/src/main.ts: -------------------------------------------------------------------------------- 1 | import "./assets/main.css"; 2 | 3 | import { createApp } from "vue"; 4 | import App from "./App.vue"; 5 | import router from "./router"; 6 | import { createPinia } from "pinia"; 7 | 8 | const app = createApp(App); 9 | const pinia = createPinia(); 10 | 11 | app.use(router); 12 | app.use(pinia); 13 | 14 | app.mount("#app"); 15 | -------------------------------------------------------------------------------- /frontend/src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory } from "vue-router"; 2 | import HomeView from "../views/HomeView.vue"; 3 | 4 | const router = createRouter({ 5 | history: createWebHistory(import.meta.env.BASE_URL), 6 | routes: [ 7 | { 8 | path: "/", 9 | name: "home", 10 | component: HomeView, 11 | }, 12 | { 13 | path: "/login", 14 | name: "login", 15 | // route level code-splitting 16 | // this generates a separate chunk (About.[hash].js) for this route 17 | // which is lazy-loaded when the route is visited. 18 | component: () => import("../views/LoginView.vue"), 19 | }, 20 | { 21 | path: "/signup", 22 | name: "signup", 23 | component: () => import("../views/SignupView.vue"), 24 | }, 25 | { 26 | path: "/profile", 27 | name: "profile", 28 | component: () => import("../views/ProfileView.vue"), 29 | }, 30 | { 31 | path: "/signup/onboarding", 32 | name: "onboarding", 33 | component: () => import("../views/OnboardingView.vue"), 34 | }, 35 | { 36 | path: "/userarea", 37 | name: "userarea", 38 | component: () => import("../views/UserAreaView.vue"), 39 | } 40 | ], 41 | }); 42 | 43 | export default router; 44 | -------------------------------------------------------------------------------- /frontend/src/stores/user.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from "pinia"; 2 | import Corbado from "@corbado/web-js"; 3 | import { ref } from "vue"; 4 | import englishTranslations from "@/utils/corbado-translations.ts"; 5 | import { sendEvent, TelemetryEventType } from "@corbado/shared-util"; 6 | 7 | export type UserInfo = 8 | | { 9 | status: "loading"; 10 | } 11 | | { 12 | status: "not-authenticated"; 13 | } 14 | | { 15 | status: "authenticated"; 16 | email: string; 17 | }; 18 | 19 | type UserIdentifiers = Array<{ 20 | identifierID: string; 21 | userID: string; 22 | status: "pending" | "primary" | "verified"; 23 | type: "email" | "phone" | "username"; 24 | value: string; 25 | }>; 26 | type DBUser = { 27 | id: string; 28 | corbado_user_id: string; 29 | city: string | null; 30 | }; 31 | 32 | export type ExternalUserInfo = 33 | | { 34 | status: "not-loaded"; 35 | } 36 | | { 37 | status: "loading"; 38 | } 39 | | { 40 | status: "error"; 41 | message: string; 42 | } 43 | | { 44 | status: "success"; 45 | user: DBUser; // the user object from the database 46 | identifiers: UserIdentifiers; 47 | }; 48 | 49 | export const useUserStore = defineStore("user-store", () => { 50 | // **State Variables** 51 | const userInfo = ref({ 52 | status: "loading", 53 | }); 54 | const externalUserInfo = ref({ 55 | status: "not-loaded", 56 | }); 57 | 58 | const corbadoLoadPromise = Corbado.load( 59 | { 60 | projectId: import.meta.env.VITE_CORBADO_PROJECT_ID, 61 | darkMode: "on", 62 | theme: "cbo-custom-styles", 63 | customTranslations: { en: englishTranslations }, 64 | }, 65 | import.meta.env.VITE_CORBADO_TELEMETRY_DISABLED === "true" 66 | ? false 67 | : undefined, 68 | ); 69 | 70 | if (import.meta.env.VITE_CORBADO_TELEMETRY_DISABLED !== "true") { 71 | sendEvent({ 72 | type: TelemetryEventType.EXAMPLE_APPLICATION_OPENED, 73 | payload: { 74 | exampleName: "corbado/passkeys-vuejs-express", 75 | }, 76 | sdkVersion: "3.1.0", 77 | sdkName: "React SDK", 78 | identifier: import.meta.env.VITE_CORBADO_PROJECT_ID, 79 | }); 80 | } 81 | 82 | // **Actions** 83 | const onCorbadoLoaded = (fn: () => void | Promise) => { 84 | corbadoLoadPromise.then(fn); 85 | }; 86 | 87 | async function loadExternalUserInfo() { 88 | if (externalUserInfo.value.status === "loading") { 89 | return; 90 | } 91 | try { 92 | externalUserInfo.value = { status: "loading" }; 93 | const url = `${import.meta.env.VITE_BACKEND_BASE_URL}/api/user`; 94 | const response = await fetch(url, { credentials: "include" }); 95 | if (!response.ok) { 96 | throw new Error( 97 | `Failed to fetch user info: ${response.status} ${response.statusText}`, 98 | ); 99 | } 100 | const data = (await response.json()) as { 101 | user: DBUser; 102 | identifiers: UserIdentifiers; 103 | }; 104 | externalUserInfo.value = { 105 | status: "success", 106 | user: data.user, 107 | identifiers: data.identifiers, 108 | }; 109 | } catch (e) { 110 | externalUserInfo.value = { 111 | status: "error", 112 | message: `Failed to fetch user info: ${e}`, 113 | }; 114 | } 115 | } 116 | 117 | // the caller must handle any errors 118 | async function updateUserCity(city: string) { 119 | if (externalUserInfo.value.status !== "success") { 120 | throw new Error("User info not loaded"); 121 | } 122 | const citySubmitUrl = `${import.meta.env.VITE_BACKEND_BASE_URL}/api/user/city`; 123 | const rsp = await fetch(citySubmitUrl, { 124 | method: "POST", 125 | credentials: "include", 126 | headers: { 127 | "Content-Type": "application/json", 128 | }, 129 | body: JSON.stringify({ city }), 130 | }); 131 | 132 | if (!rsp.ok) { 133 | throw new Error( 134 | `Failed to submit city: ${rsp.status} ${rsp.statusText}`, 135 | ); 136 | } 137 | 138 | const json = (await rsp.json()) as DBUser; 139 | externalUserInfo.value = { 140 | status: "success", 141 | user: json, 142 | identifiers: externalUserInfo.value.identifiers, 143 | }; 144 | } 145 | 146 | // **Observers / Side Effects** 147 | // Subscribe to Corbado user changes 148 | onCorbadoLoaded(() => { 149 | Corbado.userChanges.subscribe((user) => { 150 | if (user) { 151 | // load external info every time the user changes 152 | void loadExternalUserInfo(); 153 | userInfo.value = { 154 | status: "authenticated", 155 | email: user.name, 156 | }; 157 | } else { 158 | externalUserInfo.value = { status: "not-loaded" }; 159 | userInfo.value = { 160 | status: "not-authenticated", 161 | }; 162 | } 163 | }); 164 | }); 165 | 166 | // **Return Public API** 167 | return { 168 | // State 169 | userInfo, 170 | externalUserInfo, 171 | 172 | // Actions 173 | onCorbadoLoaded, 174 | updateUserCity, 175 | }; 176 | }); 177 | -------------------------------------------------------------------------------- /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/views/HomeView.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 48 | -------------------------------------------------------------------------------- /frontend/src/views/LoginView.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 45 | -------------------------------------------------------------------------------- /frontend/src/views/OnboardingView.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 41 | -------------------------------------------------------------------------------- /frontend/src/views/ProfileView.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 48 | -------------------------------------------------------------------------------- /frontend/src/views/SignupView.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 44 | -------------------------------------------------------------------------------- /frontend/src/views/UserAreaView.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 80 | -------------------------------------------------------------------------------- /frontend/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.dom.json", 3 | "include": [ 4 | "env.d.ts", 5 | "src/**/*", 6 | "src/**/*.vue" 7 | ], 8 | "exclude": [ 9 | "src/**/__tests__/*" 10 | ], 11 | "compilerOptions": { 12 | "composite": true, 13 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 14 | "paths": { 15 | "@/*": [ 16 | "./src/*" 17 | ] 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./tsconfig.node.json" 6 | }, 7 | { 8 | "path": "./tsconfig.app.json" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /frontend/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node22/tsconfig.json", 3 | "include": [ 4 | "vite.config.*", 5 | "vitest.config.*", 6 | "cypress.config.*", 7 | "nightwatch.conf.*", 8 | "playwright.config.*" 9 | ], 10 | "compilerOptions": { 11 | "composite": true, 12 | "noEmit": true, 13 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 14 | "module": "ESNext", 15 | "moduleResolution": "Bundler", 16 | "types": [ 17 | "node" 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /frontend/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from "node:url"; 2 | 3 | import { defineConfig } from "vite"; 4 | import vue from "@vitejs/plugin-vue"; 5 | import vueDevTools from "vite-plugin-vue-devtools"; 6 | 7 | // https://vite.dev/config/ 8 | export default defineConfig({ 9 | plugins: [vue(), vueDevTools()], 10 | resolve: { 11 | alias: { 12 | "@": fileURLToPath(new URL("./src", import.meta.url)), 13 | }, 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /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 start) | 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 --------------------------------------------------------------------------------