diff --git a/dev.db b/dev.db index a194fe0..cbae509 100644 Binary files a/dev.db and b/dev.db differ diff --git a/devbook.md b/devbook.md index 5a85881..dbe34ef 100644 --- a/devbook.md +++ b/devbook.md @@ -122,14 +122,14 @@ Application de gestion d'ateliers SWOT pour entretiens managériaux. ## Phase 3 : Authentification -- [ ] Installer NextAuth.js v5 -- [ ] Configurer le provider Credentials (email/password) -- [ ] Créer les pages : - - [ ] `/login` - Page de connexion - - [ ] `/register` - Page d'inscription -- [ ] Créer le service auth.ts -- [ ] Protéger les routes avec middleware -- [ ] Créer le composant AuthProvider +- [x] Installer NextAuth.js v5 +- [x] Configurer le provider Credentials (email/password) +- [x] Créer les pages : + - [x] `/login` - Page de connexion + - [x] `/register` - Page d'inscription +- [x] Créer le service auth.ts +- [x] Protéger les routes avec middleware +- [x] Créer le composant AuthProvider --- diff --git a/package.json b/package.json index 362346d..9f53bfc 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,11 @@ "version": "0.1.0", "private": true, "pnpm": { - "onlyBuiltDependencies": ["prisma", "@prisma/engines"] + "onlyBuiltDependencies": [ + "prisma", + "@prisma/engines", + "better-sqlite3" + ] }, "scripts": { "dev": "next dev", @@ -12,14 +16,19 @@ "lint": "eslint" }, "dependencies": { + "@prisma/adapter-better-sqlite3": "^7.0.1", "@prisma/client": "^7.0.1", + "bcryptjs": "^3.0.3", + "better-sqlite3": "^12.4.6", "next": "16.0.5", + "next-auth": "5.0.0-beta.30", "prisma": "^7.0.1", "react": "19.2.0", "react-dom": "19.2.0" }, "devDependencies": { "@tailwindcss/postcss": "^4", + "@types/bcryptjs": "^3.0.0", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8dd2f48..28de017 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,15 +8,27 @@ importers: .: dependencies: + '@prisma/adapter-better-sqlite3': + specifier: ^7.0.1 + version: 7.0.1 '@prisma/client': specifier: ^7.0.1 - version: 7.0.1(prisma@7.0.1(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3))(typescript@5.9.3) + version: 7.0.1(prisma@7.0.1(@types/react@19.2.7)(better-sqlite3@12.4.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3))(typescript@5.9.3) + bcryptjs: + specifier: ^3.0.3 + version: 3.0.3 + better-sqlite3: + specifier: ^12.4.6 + version: 12.4.6 next: specifier: 16.0.5 version: 16.0.5(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + next-auth: + specifier: 5.0.0-beta.30 + version: 5.0.0-beta.30(next@16.0.5(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0) prisma: specifier: ^7.0.1 - version: 7.0.1(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + version: 7.0.1(@types/react@19.2.7)(better-sqlite3@12.4.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) react: specifier: 19.2.0 version: 19.2.0 @@ -27,6 +39,9 @@ importers: '@tailwindcss/postcss': specifier: ^4 version: 4.1.17 + '@types/bcryptjs': + specifier: ^3.0.0 + version: 3.0.0 '@types/node': specifier: ^20 version: 20.19.25 @@ -64,6 +79,20 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} + '@auth/core@0.41.0': + resolution: {integrity: sha512-Wd7mHPQ/8zy6Qj7f4T46vg3aoor8fskJm6g2Zyj064oQ3+p0xNZXAV60ww0hY+MbTesfu29kK14Zk5d5JTazXQ==} + peerDependencies: + '@simplewebauthn/browser': ^9.0.1 + '@simplewebauthn/server': ^9.0.2 + nodemailer: ^6.8.0 + peerDependenciesMeta: + '@simplewebauthn/browser': + optional: true + '@simplewebauthn/server': + optional: true + nodemailer: + optional: true + '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} @@ -456,6 +485,12 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@panva/hkdf@1.2.1': + resolution: {integrity: sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==} + + '@prisma/adapter-better-sqlite3@7.0.1': + resolution: {integrity: sha512-fgnn+lkUV/7pUvPnQSI/ZHONwGU+eisWqU6tvBFGK2ovgBUAJN8zG0fbt8v8Brphyo4h4AA3+jyG0TFFb+qVxQ==} + '@prisma/client-runtime-utils@7.0.1': resolution: {integrity: sha512-R26BVX9D/iw4toUmZKZf3jniM/9pMGHHdZN5LVP2L7HNiCQKNQQx/9LuMtjepbgRqSqQO3oHN0yzojHLnKTGEw==} @@ -483,6 +518,9 @@ packages: '@prisma/dev@0.13.0': resolution: {integrity: sha512-QMmF6zFeUF78yv1HYbHvod83AQnl7u6NtKyDhTRZOJup3h1icWs8R7RUVxBJZvM2tBXNAMpLQYYM/8kPlOPegA==} + '@prisma/driver-adapter-utils@7.0.1': + resolution: {integrity: sha512-sBbxm/yysHLLF2iMAB+qcX/nn3WFgsiC4DQNz0uM6BwGSIs8lIvgo0u8nR9nxe5gvFgKiIH8f4z2fgOEMeXc8w==} + '@prisma/engines-version@7.1.0-2.f09f2815f091dbba658cdcd2264306d88bb5bda6': resolution: {integrity: sha512-RA7pShKvijHib4USRB3YuLTQamHKJPkTRDc45AwxfahUQngiGVMlIj4ix4emUxkrum4o/jwn82WIwlG57EtgiQ==} @@ -608,6 +646,10 @@ packages: '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + '@types/bcryptjs@3.0.0': + resolution: {integrity: sha512-WRZOuCuaz8UcZZE4R5HXTco2goQSI2XxjGY3hbM/xDvwmqFWd4ivooImsMx65OKM6CtNKbnZ5YL+YwAwK7c1dg==} + deprecated: This is a stub types definition. bcryptjs provides its own type definitions, so you do not need this installed. + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -864,10 +906,27 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + baseline-browser-mapping@2.8.31: resolution: {integrity: sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==} hasBin: true + bcryptjs@3.0.3: + resolution: {integrity: sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==} + hasBin: true + + better-sqlite3@12.4.6: + resolution: {integrity: sha512-gaYt9yqTbQ1iOxLpJA8FPR5PiaHP+jlg8I5EX0Rs2KFwNzhBsF40KzMZS5FwelY7RG0wzaucWdqSAJM3uNCPCg==} + engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x} + + bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -883,6 +942,9 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + c12@3.1.0: resolution: {integrity: sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==} peerDependencies: @@ -921,6 +983,9 @@ packages: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + citty@0.1.6: resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} @@ -986,6 +1051,14 @@ packages: supports-color: optional: true + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -1044,6 +1117,9 @@ packages: resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} engines: {node: '>=14'} + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + enhanced-resolve@5.18.3: resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} engines: {node: '>=10.13.0'} @@ -1210,6 +1286,10 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + exsolve@1.0.8: resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} @@ -1246,6 +1326,9 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} + file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -1269,6 +1352,9 @@ packages: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -1312,6 +1398,9 @@ packages: resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} hasBin: true + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1389,6 +1478,9 @@ packages: resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==} engines: {node: '>=0.10.0'} + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1405,6 +1497,12 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + internal-slot@1.1.0: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} @@ -1529,6 +1627,9 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true + jose@6.1.2: + resolution: {integrity: sha512-MpcPtHLE5EmztuFIqB0vzHAWJPpmN1E6L4oo+kze56LIs3MyXIj9ZHMDxqOvkP38gBR7K1v3jqd4WU2+nrfONQ==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -1694,6 +1795,10 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -1704,6 +1809,9 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1720,6 +1828,9 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + napi-build-utils@2.0.0: + resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + napi-postinstall@0.3.4: resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} @@ -1728,6 +1839,22 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + next-auth@5.0.0-beta.30: + resolution: {integrity: sha512-+c51gquM3F6nMVmoAusRJ7RIoY0K4Ts9HCCwyy/BRoe4mp3msZpOzYMyb5LAYc1wSo74PMQkGDcaghIO7W6Xjg==} + peerDependencies: + '@simplewebauthn/browser': ^9.0.1 + '@simplewebauthn/server': ^9.0.2 + next: ^14.0.0-0 || ^15.0.0 || ^16.0.0 + nodemailer: ^7.0.7 + react: ^18.2.0 || ^19.0.0 + peerDependenciesMeta: + '@simplewebauthn/browser': + optional: true + '@simplewebauthn/server': + optional: true + nodemailer: + optional: true + next@16.0.5: resolution: {integrity: sha512-XUPsFqSqu/NDdPfn/cju9yfIedkDI7ytDoALD9todaSMxk1Z5e3WcbUjfI9xsanFTys7xz62lnRWNFqJordzkQ==} engines: {node: '>=20.9.0'} @@ -1749,6 +1876,10 @@ packages: sass: optional: true + node-abi@3.85.0: + resolution: {integrity: sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==} + engines: {node: '>=10'} + node-fetch-native@1.6.7: resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} @@ -1760,6 +1891,9 @@ packages: engines: {node: ^14.16.0 || >=16.10.0} hasBin: true + oauth4webapi@3.8.3: + resolution: {integrity: sha512-pQ5BsX3QRTgnt5HxgHwgunIRaDXBdkT23tf8dfzmtTIL2LTpdmxgbpbBm0VgFWAIDlezQvQCTgnVIUmHupXHxw==} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -1795,6 +1929,9 @@ packages: ohash@2.0.11: resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -1862,6 +1999,19 @@ packages: resolution: {integrity: sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==} engines: {node: '>=12'} + preact-render-to-string@6.5.11: + resolution: {integrity: sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==} + peerDependencies: + preact: '>=10' + + preact@10.24.3: + resolution: {integrity: sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==} + + prebuild-install@7.1.3: + resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} + engines: {node: '>=10'} + hasBin: true + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -1890,6 +2040,9 @@ packages: proper-lockfile@4.1.2: resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -1903,6 +2056,10 @@ packages: rc9@2.1.2: resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + react-dom@19.2.0: resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==} peerDependencies: @@ -1915,6 +2072,10 @@ packages: resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==} engines: {node: '>=0.10.0'} + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + readdirp@4.1.2: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} @@ -1964,6 +2125,9 @@ packages: resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} engines: {node: '>=0.4'} + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safe-push-apply@1.0.0: resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} engines: {node: '>= 0.4'} @@ -2037,6 +2201,12 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -2078,10 +2248,17 @@ packages: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -2114,6 +2291,13 @@ packages: resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} engines: {node: '>=6'} + tar-fs@2.1.4: + resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + tinyexec@1.0.2: resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} engines: {node: '>=18'} @@ -2138,6 +2322,9 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -2193,6 +2380,9 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + valibot@1.1.0: resolution: {integrity: sha512-Nk8lX30Qhu+9txPYTwM0cFlWLdPFsFr6LblzqIySfbZph9+BFsAHsNvHOymEviUepeIW6KFHzpX8TKhbptBXXw==} peerDependencies: @@ -2226,6 +2416,9 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -2249,6 +2442,14 @@ snapshots: '@alloc/quick-lru@5.2.0': {} + '@auth/core@0.41.0': + dependencies: + '@panva/hkdf': 1.2.1 + jose: 6.1.2 + oauth4webapi: 3.8.3 + preact: 10.24.3 + preact-render-to-string: 6.5.11(preact@10.24.3) + '@babel/code-frame@7.27.1': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -2623,13 +2824,20 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@panva/hkdf@1.2.1': {} + + '@prisma/adapter-better-sqlite3@7.0.1': + dependencies: + '@prisma/driver-adapter-utils': 7.0.1 + better-sqlite3: 12.4.6 + '@prisma/client-runtime-utils@7.0.1': {} - '@prisma/client@7.0.1(prisma@7.0.1(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3))(typescript@5.9.3)': + '@prisma/client@7.0.1(prisma@7.0.1(@types/react@19.2.7)(better-sqlite3@12.4.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3))(typescript@5.9.3)': dependencies: '@prisma/client-runtime-utils': 7.0.1 optionalDependencies: - prisma: 7.0.1(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + prisma: 7.0.1(@types/react@19.2.7)(better-sqlite3@12.4.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) typescript: 5.9.3 '@prisma/config@7.0.1': @@ -2667,6 +2875,10 @@ snapshots: transitivePeerDependencies: - typescript + '@prisma/driver-adapter-utils@7.0.1': + dependencies: + '@prisma/debug': 7.0.1 + '@prisma/engines-version@7.1.0-2.f09f2815f091dbba658cdcd2264306d88bb5bda6': {} '@prisma/engines@7.0.1': @@ -2780,6 +2992,10 @@ snapshots: tslib: 2.8.1 optional: true + '@types/bcryptjs@3.0.0': + dependencies: + bcryptjs: 3.0.3 + '@types/estree@1.0.8': {} '@types/json-schema@7.0.15': {} @@ -3053,8 +3269,27 @@ snapshots: balanced-match@1.0.2: {} + base64-js@1.5.1: {} + baseline-browser-mapping@2.8.31: {} + bcryptjs@3.0.3: {} + + better-sqlite3@12.4.6: + dependencies: + bindings: 1.5.0 + prebuild-install: 7.1.3 + + bindings@1.5.0: + dependencies: + file-uri-to-path: 1.0.0 + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -3076,6 +3311,11 @@ snapshots: node-releases: 2.0.27 update-browserslist-db: 1.1.4(browserslist@4.28.0) + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + c12@3.1.0: dependencies: chokidar: 4.0.3 @@ -3130,6 +3370,8 @@ snapshots: dependencies: readdirp: 4.1.2 + chownr@1.1.4: {} + citty@0.1.6: dependencies: consola: 3.4.2 @@ -3186,6 +3428,12 @@ snapshots: dependencies: ms: 2.1.3 + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + + deep-extend@0.6.0: {} + deep-is@0.1.4: {} deepmerge-ts@7.1.5: {} @@ -3235,6 +3483,10 @@ snapshots: empathic@2.0.0: {} + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + enhanced-resolve@5.18.3: dependencies: graceful-fs: 4.2.11 @@ -3552,6 +3804,8 @@ snapshots: esutils@2.0.3: {} + expand-template@2.0.3: {} + exsolve@1.0.8: {} fast-check@3.23.2: @@ -3584,6 +3838,8 @@ snapshots: dependencies: flat-cache: 4.0.1 + file-uri-to-path@1.0.0: {} + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -3609,6 +3865,8 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 + fs-constants@1.0.0: {} + function-bind@1.1.2: {} function.prototype.name@1.1.8: @@ -3669,6 +3927,8 @@ snapshots: nypm: 0.6.2 pathe: 2.0.3 + github-from-package@0.0.0: {} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -3730,6 +3990,8 @@ snapshots: dependencies: safer-buffer: 2.1.2 + ieee754@1.2.1: {} + ignore@5.3.2: {} ignore@7.0.5: {} @@ -3741,6 +4003,10 @@ snapshots: imurmurhash@0.1.4: {} + inherits@2.0.4: {} + + ini@1.3.8: {} + internal-slot@1.1.0: dependencies: es-errors: 1.3.0 @@ -3876,6 +4142,8 @@ snapshots: jiti@2.6.1: {} + jose@6.1.2: {} + js-tokens@4.0.0: {} js-yaml@4.1.1: @@ -4004,6 +4272,8 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 + mimic-response@3.1.0: {} + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 @@ -4014,6 +4284,8 @@ snapshots: minimist@1.2.8: {} + mkdirp-classic@0.5.3: {} + ms@2.1.3: {} mysql2@3.15.3: @@ -4034,10 +4306,18 @@ snapshots: nanoid@3.3.11: {} + napi-build-utils@2.0.0: {} + napi-postinstall@0.3.4: {} natural-compare@1.4.0: {} + next-auth@5.0.0-beta.30(next@16.0.5(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0): + dependencies: + '@auth/core': 0.41.0 + next: 16.0.5(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + next@16.0.5(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: '@next/env': 16.0.5 @@ -4061,6 +4341,10 @@ snapshots: - '@babel/core' - babel-plugin-macros + node-abi@3.85.0: + dependencies: + semver: 7.7.3 + node-fetch-native@1.6.7: {} node-releases@2.0.27: {} @@ -4073,6 +4357,8 @@ snapshots: pkg-types: 2.3.0 tinyexec: 1.0.2 + oauth4webapi@3.8.3: {} + object-assign@4.1.1: {} object-inspect@1.13.4: {} @@ -4117,6 +4403,10 @@ snapshots: ohash@2.0.11: {} + once@1.4.0: + dependencies: + wrappy: 1.0.2 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -4182,11 +4472,32 @@ snapshots: postgres@3.4.7: {} + preact-render-to-string@6.5.11(preact@10.24.3): + dependencies: + preact: 10.24.3 + + preact@10.24.3: {} + + prebuild-install@7.1.3: + dependencies: + detect-libc: 2.1.2 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 2.0.0 + node-abi: 3.85.0 + pump: 3.0.3 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.4 + tunnel-agent: 0.6.0 + prelude-ls@1.2.1: {} prettier@3.7.1: {} - prisma@7.0.1(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3): + prisma@7.0.1(@types/react@19.2.7)(better-sqlite3@12.4.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3): dependencies: '@prisma/config': 7.0.1 '@prisma/dev': 0.13.0(typescript@5.9.3) @@ -4195,6 +4506,7 @@ snapshots: mysql2: 3.15.3 postgres: 3.4.7 optionalDependencies: + better-sqlite3: 12.4.6 typescript: 5.9.3 transitivePeerDependencies: - '@types/react' @@ -4214,6 +4526,11 @@ snapshots: retry: 0.12.0 signal-exit: 3.0.7 + pump@3.0.3: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + punycode@2.3.1: {} pure-rand@6.1.0: {} @@ -4225,6 +4542,13 @@ snapshots: defu: 6.1.4 destr: 2.0.5 + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + react-dom@19.2.0(react@19.2.0): dependencies: react: 19.2.0 @@ -4234,6 +4558,12 @@ snapshots: react@19.2.0: {} + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + readdirp@4.1.2: {} reflect.getprototypeof@1.0.10: @@ -4294,6 +4624,8 @@ snapshots: has-symbols: 1.1.0 isarray: 2.0.5 + safe-buffer@5.2.1: {} + safe-push-apply@1.0.0: dependencies: es-errors: 1.3.0 @@ -4407,6 +4739,14 @@ snapshots: signal-exit@4.1.0: {} + simple-concat@1.0.1: {} + + simple-get@4.0.1: + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + source-map-js@1.2.1: {} sqlstring@2.3.3: {} @@ -4470,8 +4810,14 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + strip-bom@3.0.0: {} + strip-json-comments@2.0.1: {} + strip-json-comments@3.1.1: {} styled-jsx@5.1.6(@babel/core@7.28.5)(react@19.2.0): @@ -4491,6 +4837,21 @@ snapshots: tapable@2.3.0: {} + tar-fs@2.1.4: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.3 + tar-stream: 2.2.0 + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + tinyexec@1.0.2: {} tinyglobby@0.2.15: @@ -4515,6 +4876,10 @@ snapshots: tslib@2.8.1: {} + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -4610,6 +4975,8 @@ snapshots: dependencies: punycode: 2.3.1 + util-deprecate@1.0.2: {} + valibot@1.1.0(typescript@5.9.3): optionalDependencies: typescript: 5.9.3 @@ -4661,6 +5028,8 @@ snapshots: word-wrap@1.2.5: {} + wrappy@1.0.2: {} + yallist@3.1.1: {} yocto-queue@0.1.0: {} diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx new file mode 100644 index 0000000..f0b8c0f --- /dev/null +++ b/src/app/(auth)/login/page.tsx @@ -0,0 +1,112 @@ +'use client'; + +import { useState } from 'react'; +import { signIn } from 'next-auth/react'; +import { useRouter } from 'next/navigation'; +import Link from 'next/link'; + +export default function LoginPage() { + const router = useRouter(); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(false); + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + setError(null); + setLoading(true); + + const formData = new FormData(e.currentTarget); + const email = formData.get('email') as string; + const password = formData.get('password') as string; + + try { + const result = await signIn('credentials', { + email, + password, + redirect: false, + }); + + if (result?.error) { + setError('Email ou mot de passe incorrect'); + } else { + router.push('/sessions'); + router.refresh(); + } + } catch { + setError('Une erreur est survenue'); + } finally { + setLoading(false); + } + } + + return ( +
+
+
+ + 📊 + SWOT Manager + +

Connectez-vous à votre compte

+
+ +
+ {error && ( +
+ {error} +
+ )} + +
+ + +
+ +
+ + +
+ + + +

+ Pas encore de compte ?{' '} + + Créer un compte + +

+
+
+
+ ); +} + diff --git a/src/app/(auth)/register/page.tsx b/src/app/(auth)/register/page.tsx new file mode 100644 index 0000000..6803e5a --- /dev/null +++ b/src/app/(auth)/register/page.tsx @@ -0,0 +1,173 @@ +'use client'; + +import { useState } from 'react'; +import { signIn } from 'next-auth/react'; +import { useRouter } from 'next/navigation'; +import Link from 'next/link'; + +export default function RegisterPage() { + const router = useRouter(); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(false); + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + setError(null); + setLoading(true); + + const formData = new FormData(e.currentTarget); + const name = formData.get('name') as string; + const email = formData.get('email') as string; + const password = formData.get('password') as string; + const confirmPassword = formData.get('confirmPassword') as string; + + if (password !== confirmPassword) { + setError('Les mots de passe ne correspondent pas'); + setLoading(false); + return; + } + + if (password.length < 6) { + setError('Le mot de passe doit contenir au moins 6 caractères'); + setLoading(false); + return; + } + + try { + const res = await fetch('/api/auth/register', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ name, email, password }), + }); + + const data = await res.json(); + + if (!res.ok) { + setError(data.error || 'Une erreur est survenue'); + setLoading(false); + return; + } + + // Auto sign in after registration + const result = await signIn('credentials', { + email, + password, + redirect: false, + }); + + if (result?.error) { + setError('Compte créé mais erreur de connexion. Veuillez vous connecter manuellement.'); + } else { + router.push('/sessions'); + router.refresh(); + } + } catch { + setError('Une erreur est survenue'); + } finally { + setLoading(false); + } + } + + return ( +
+
+
+ + 📊 + SWOT Manager + +

Créez votre compte

+
+ +
+ {error && ( +
+ {error} +
+ )} + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + + +

+ Déjà un compte ?{' '} + + Se connecter + +

+
+
+
+ ); +} + diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000..dc9560d --- /dev/null +++ b/src/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,4 @@ +import { handlers } from '@/lib/auth'; + +export const { GET, POST } = handlers; + diff --git a/src/app/api/auth/register/route.ts b/src/app/api/auth/register/route.ts new file mode 100644 index 0000000..0dc4c6c --- /dev/null +++ b/src/app/api/auth/register/route.ts @@ -0,0 +1,25 @@ +import { NextResponse } from 'next/server'; +import { registerUser } from '@/services/auth'; + +export async function POST(request: Request) { + try { + const body = await request.json(); + const { email, password, name } = body; + + if (!email || !password) { + return NextResponse.json({ error: 'Email et mot de passe requis' }, { status: 400 }); + } + + const result = await registerUser({ email, password, name }); + + if (!result.success) { + return NextResponse.json({ error: result.error }, { status: 400 }); + } + + return NextResponse.json({ user: result.user }, { status: 201 }); + } catch (error) { + console.error('Registration error:', error); + return NextResponse.json({ error: 'Erreur lors de la création du compte' }, { status: 500 }); + } +} + diff --git a/src/components/Providers.tsx b/src/components/Providers.tsx index 546661d..4da8ffc 100644 --- a/src/components/Providers.tsx +++ b/src/components/Providers.tsx @@ -1,5 +1,6 @@ 'use client'; +import { SessionProvider } from 'next-auth/react'; import { ThemeProvider } from '@/contexts/ThemeContext'; import { Header } from '@/components/layout/Header'; import { ReactNode } from 'react'; @@ -10,11 +11,13 @@ interface ProvidersProps { export function Providers({ children }: ProvidersProps) { return ( - -
-
- {children} -
-
+ + +
+
+ {children} +
+
+
); } diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index 463cc28..78a060d 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -1,10 +1,14 @@ 'use client'; import Link from 'next/link'; +import { useSession, signOut } from 'next-auth/react'; import { useTheme } from '@/contexts/ThemeContext'; +import { useState } from 'react'; export function Header() { const { theme, toggleTheme } = useTheme(); + const { data: session, status } = useSession(); + const [menuOpen, setMenuOpen] = useState(false); return (
@@ -15,12 +19,14 @@ export function Header() {
); } - diff --git a/src/lib/auth.config.ts b/src/lib/auth.config.ts new file mode 100644 index 0000000..ba8d615 --- /dev/null +++ b/src/lib/auth.config.ts @@ -0,0 +1,29 @@ +import type { NextAuthConfig } from 'next-auth'; + +export const authConfig: NextAuthConfig = { + pages: { + signIn: '/login', + }, + callbacks: { + authorized({ auth, request: { nextUrl } }) { + const isLoggedIn = !!auth?.user; + const isOnProtectedPage = + nextUrl.pathname.startsWith('/sessions') || nextUrl.pathname.startsWith('/api/sessions'); + const isOnAuthPage = + nextUrl.pathname.startsWith('/login') || nextUrl.pathname.startsWith('/register'); + + if (isOnProtectedPage) { + if (isLoggedIn) return true; + return false; // Redirect to login + } + + if (isOnAuthPage && isLoggedIn) { + return Response.redirect(new URL('/sessions', nextUrl)); + } + + return true; + }, + }, + providers: [], // Configured in auth.ts +}; + diff --git a/src/lib/auth.ts b/src/lib/auth.ts new file mode 100644 index 0000000..df77b4a --- /dev/null +++ b/src/lib/auth.ts @@ -0,0 +1,62 @@ +import NextAuth from 'next-auth'; +import Credentials from 'next-auth/providers/credentials'; +import { compare } from 'bcryptjs'; +import { prisma } from '@/services/database'; + +export const { handlers, signIn, signOut, auth } = NextAuth({ + providers: [ + Credentials({ + name: 'credentials', + credentials: { + email: { label: 'Email', type: 'email' }, + password: { label: 'Password', type: 'password' }, + }, + async authorize(credentials) { + if (!credentials?.email || !credentials?.password) { + return null; + } + + const user = await prisma.user.findUnique({ + where: { email: credentials.email as string }, + }); + + if (!user) { + return null; + } + + const isPasswordValid = await compare(credentials.password as string, user.password); + + if (!isPasswordValid) { + return null; + } + + return { + id: user.id, + email: user.email, + name: user.name, + }; + }, + }), + ], + session: { + strategy: 'jwt', + }, + pages: { + signIn: '/login', + }, + callbacks: { + async jwt({ token, user }) { + if (user) { + token.id = user.id; + } + return token; + }, + async session({ session, token }) { + if (session.user) { + session.user.id = token.id as string; + } + return session; + }, + }, +}); + diff --git a/src/middleware.ts b/src/middleware.ts new file mode 100644 index 0000000..f82c687 --- /dev/null +++ b/src/middleware.ts @@ -0,0 +1,10 @@ +import NextAuth from 'next-auth'; +import { authConfig } from '@/lib/auth.config'; + +export default NextAuth(authConfig).auth; + +export const config = { + // Match all paths except static files and api routes that don't need auth + matcher: ['/((?!api/auth|_next/static|_next/image|favicon.ico).*)'], +}; + diff --git a/src/services/auth.ts b/src/services/auth.ts new file mode 100644 index 0000000..1f0a2cc --- /dev/null +++ b/src/services/auth.ts @@ -0,0 +1,68 @@ +import { hash } from 'bcryptjs'; +import { prisma } from '@/services/database'; + +export interface RegisterInput { + email: string; + password: string; + name?: string; +} + +export interface AuthResult { + success: boolean; + error?: string; + user?: { + id: string; + email: string; + name: string | null; + }; +} + +export async function registerUser(input: RegisterInput): Promise { + const { email, password, name } = input; + + // Check if user already exists + const existingUser = await prisma.user.findUnique({ + where: { email }, + }); + + if (existingUser) { + return { + success: false, + error: 'Un compte existe déjà avec cet email', + }; + } + + // Hash password + const hashedPassword = await hash(password, 12); + + // Create user + const user = await prisma.user.create({ + data: { + email, + password: hashedPassword, + name: name || null, + }, + }); + + return { + success: true, + user: { + id: user.id, + email: user.email, + name: user.name, + }, + }; +} + +export async function getUserByEmail(email: string) { + return prisma.user.findUnique({ + where: { email }, + }); +} + +export async function getUserById(id: string) { + return prisma.user.findUnique({ + where: { id }, + }); +} + diff --git a/src/services/database.ts b/src/services/database.ts index e9a8d36..bfcd378 100644 --- a/src/services/database.ts +++ b/src/services/database.ts @@ -1,18 +1,25 @@ import { PrismaClient } from '@prisma/client'; +import { PrismaBetterSqlite3 } from '@prisma/adapter-better-sqlite3'; const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | undefined; }; -export const prisma = - globalForPrisma.prisma ?? - new PrismaClient({ +function createPrismaClient() { + const adapter = new PrismaBetterSqlite3({ + url: process.env.DATABASE_URL ?? 'file:./prisma/dev.db', + }); + + return new PrismaClient({ + adapter, log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'], }); +} + +export const prisma = globalForPrisma.prisma ?? createPrismaClient(); if (process.env.NODE_ENV !== 'production') { globalForPrisma.prisma = prisma; } export default prisma; -