feat: integrate NextAuth.js for authentication, update database service to use better-sqlite3 adapter, and enhance header component with user session management

This commit is contained in:
Julien Froidefond
2025-11-27 13:08:09 +01:00
parent 68ef3731fa
commit 6a9bf88a65
15 changed files with 965 additions and 31 deletions

BIN
dev.db

Binary file not shown.

View File

@@ -122,14 +122,14 @@ Application de gestion d'ateliers SWOT pour entretiens managériaux.
## Phase 3 : Authentification ## Phase 3 : Authentification
- [ ] Installer NextAuth.js v5 - [x] Installer NextAuth.js v5
- [ ] Configurer le provider Credentials (email/password) - [x] Configurer le provider Credentials (email/password)
- [ ] Créer les pages : - [x] Créer les pages :
- [ ] `/login` - Page de connexion - [x] `/login` - Page de connexion
- [ ] `/register` - Page d'inscription - [x] `/register` - Page d'inscription
- [ ] Créer le service auth.ts - [x] Créer le service auth.ts
- [ ] Protéger les routes avec middleware - [x] Protéger les routes avec middleware
- [ ] Créer le composant AuthProvider - [x] Créer le composant AuthProvider
--- ---

View File

@@ -3,7 +3,11 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"pnpm": { "pnpm": {
"onlyBuiltDependencies": ["prisma", "@prisma/engines"] "onlyBuiltDependencies": [
"prisma",
"@prisma/engines",
"better-sqlite3"
]
}, },
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
@@ -12,14 +16,19 @@
"lint": "eslint" "lint": "eslint"
}, },
"dependencies": { "dependencies": {
"@prisma/adapter-better-sqlite3": "^7.0.1",
"@prisma/client": "^7.0.1", "@prisma/client": "^7.0.1",
"bcryptjs": "^3.0.3",
"better-sqlite3": "^12.4.6",
"next": "16.0.5", "next": "16.0.5",
"next-auth": "5.0.0-beta.30",
"prisma": "^7.0.1", "prisma": "^7.0.1",
"react": "19.2.0", "react": "19.2.0",
"react-dom": "19.2.0" "react-dom": "19.2.0"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",
"@types/bcryptjs": "^3.0.0",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",

379
pnpm-lock.yaml generated
View File

@@ -8,15 +8,27 @@ importers:
.: .:
dependencies: dependencies:
'@prisma/adapter-better-sqlite3':
specifier: ^7.0.1
version: 7.0.1
'@prisma/client': '@prisma/client':
specifier: ^7.0.1 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: next:
specifier: 16.0.5 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) 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: prisma:
specifier: ^7.0.1 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: react:
specifier: 19.2.0 specifier: 19.2.0
version: 19.2.0 version: 19.2.0
@@ -27,6 +39,9 @@ importers:
'@tailwindcss/postcss': '@tailwindcss/postcss':
specifier: ^4 specifier: ^4
version: 4.1.17 version: 4.1.17
'@types/bcryptjs':
specifier: ^3.0.0
version: 3.0.0
'@types/node': '@types/node':
specifier: ^20 specifier: ^20
version: 20.19.25 version: 20.19.25
@@ -64,6 +79,20 @@ packages:
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'} 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': '@babel/code-frame@7.27.1':
resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
@@ -456,6 +485,12 @@ packages:
resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==}
engines: {node: '>=12.4.0'} 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': '@prisma/client-runtime-utils@7.0.1':
resolution: {integrity: sha512-R26BVX9D/iw4toUmZKZf3jniM/9pMGHHdZN5LVP2L7HNiCQKNQQx/9LuMtjepbgRqSqQO3oHN0yzojHLnKTGEw==} resolution: {integrity: sha512-R26BVX9D/iw4toUmZKZf3jniM/9pMGHHdZN5LVP2L7HNiCQKNQQx/9LuMtjepbgRqSqQO3oHN0yzojHLnKTGEw==}
@@ -483,6 +518,9 @@ packages:
'@prisma/dev@0.13.0': '@prisma/dev@0.13.0':
resolution: {integrity: sha512-QMmF6zFeUF78yv1HYbHvod83AQnl7u6NtKyDhTRZOJup3h1icWs8R7RUVxBJZvM2tBXNAMpLQYYM/8kPlOPegA==} 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': '@prisma/engines-version@7.1.0-2.f09f2815f091dbba658cdcd2264306d88bb5bda6':
resolution: {integrity: sha512-RA7pShKvijHib4USRB3YuLTQamHKJPkTRDc45AwxfahUQngiGVMlIj4ix4emUxkrum4o/jwn82WIwlG57EtgiQ==} resolution: {integrity: sha512-RA7pShKvijHib4USRB3YuLTQamHKJPkTRDc45AwxfahUQngiGVMlIj4ix4emUxkrum4o/jwn82WIwlG57EtgiQ==}
@@ -608,6 +646,10 @@ packages:
'@tybys/wasm-util@0.10.1': '@tybys/wasm-util@0.10.1':
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} 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': '@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
@@ -864,10 +906,27 @@ packages:
balanced-match@1.0.2: balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
baseline-browser-mapping@2.8.31: baseline-browser-mapping@2.8.31:
resolution: {integrity: sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==} resolution: {integrity: sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==}
hasBin: true 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: brace-expansion@1.1.12:
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
@@ -883,6 +942,9 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true hasBin: true
buffer@5.7.1:
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
c12@3.1.0: c12@3.1.0:
resolution: {integrity: sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==} resolution: {integrity: sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==}
peerDependencies: peerDependencies:
@@ -921,6 +983,9 @@ packages:
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
engines: {node: '>= 14.16.0'} engines: {node: '>= 14.16.0'}
chownr@1.1.4:
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
citty@0.1.6: citty@0.1.6:
resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==}
@@ -986,6 +1051,14 @@ packages:
supports-color: supports-color:
optional: true 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: deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
@@ -1044,6 +1117,9 @@ packages:
resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==}
engines: {node: '>=14'} engines: {node: '>=14'}
end-of-stream@1.4.5:
resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
enhanced-resolve@5.18.3: enhanced-resolve@5.18.3:
resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==}
engines: {node: '>=10.13.0'} engines: {node: '>=10.13.0'}
@@ -1210,6 +1286,10 @@ packages:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'} 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: exsolve@1.0.8:
resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==}
@@ -1246,6 +1326,9 @@ packages:
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
engines: {node: '>=16.0.0'} 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: fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -1269,6 +1352,9 @@ packages:
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
engines: {node: '>=14'} engines: {node: '>=14'}
fs-constants@1.0.0:
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
function-bind@1.1.2: function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
@@ -1312,6 +1398,9 @@ packages:
resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==}
hasBin: true hasBin: true
github-from-package@0.0.0:
resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==}
glob-parent@5.1.2: glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
@@ -1389,6 +1478,9 @@ packages:
resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==} resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
ieee754@1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
ignore@5.3.2: ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'} engines: {node: '>= 4'}
@@ -1405,6 +1497,12 @@ packages:
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
engines: {node: '>=0.8.19'} 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: internal-slot@1.1.0:
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -1529,6 +1627,9 @@ packages:
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
hasBin: true hasBin: true
jose@6.1.2:
resolution: {integrity: sha512-MpcPtHLE5EmztuFIqB0vzHAWJPpmN1E6L4oo+kze56LIs3MyXIj9ZHMDxqOvkP38gBR7K1v3jqd4WU2+nrfONQ==}
js-tokens@4.0.0: js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
@@ -1694,6 +1795,10 @@ packages:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'} 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: minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
@@ -1704,6 +1809,9 @@ packages:
minimist@1.2.8: minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
mkdirp-classic@0.5.3:
resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
ms@2.1.3: ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
@@ -1720,6 +1828,9 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true hasBin: true
napi-build-utils@2.0.0:
resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==}
napi-postinstall@0.3.4: napi-postinstall@0.3.4:
resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==} resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==}
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
@@ -1728,6 +1839,22 @@ packages:
natural-compare@1.4.0: natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} 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: next@16.0.5:
resolution: {integrity: sha512-XUPsFqSqu/NDdPfn/cju9yfIedkDI7ytDoALD9todaSMxk1Z5e3WcbUjfI9xsanFTys7xz62lnRWNFqJordzkQ==} resolution: {integrity: sha512-XUPsFqSqu/NDdPfn/cju9yfIedkDI7ytDoALD9todaSMxk1Z5e3WcbUjfI9xsanFTys7xz62lnRWNFqJordzkQ==}
engines: {node: '>=20.9.0'} engines: {node: '>=20.9.0'}
@@ -1749,6 +1876,10 @@ packages:
sass: sass:
optional: true optional: true
node-abi@3.85.0:
resolution: {integrity: sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==}
engines: {node: '>=10'}
node-fetch-native@1.6.7: node-fetch-native@1.6.7:
resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==}
@@ -1760,6 +1891,9 @@ packages:
engines: {node: ^14.16.0 || >=16.10.0} engines: {node: ^14.16.0 || >=16.10.0}
hasBin: true hasBin: true
oauth4webapi@3.8.3:
resolution: {integrity: sha512-pQ5BsX3QRTgnt5HxgHwgunIRaDXBdkT23tf8dfzmtTIL2LTpdmxgbpbBm0VgFWAIDlezQvQCTgnVIUmHupXHxw==}
object-assign@4.1.1: object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@@ -1795,6 +1929,9 @@ packages:
ohash@2.0.11: ohash@2.0.11:
resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==}
once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
optionator@0.9.4: optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
@@ -1862,6 +1999,19 @@ packages:
resolution: {integrity: sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==} resolution: {integrity: sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==}
engines: {node: '>=12'} 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: prelude-ls@1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
@@ -1890,6 +2040,9 @@ packages:
proper-lockfile@4.1.2: proper-lockfile@4.1.2:
resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==}
pump@3.0.3:
resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==}
punycode@2.3.1: punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'} engines: {node: '>=6'}
@@ -1903,6 +2056,10 @@ packages:
rc9@2.1.2: rc9@2.1.2:
resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==}
rc@1.2.8:
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
hasBin: true
react-dom@19.2.0: react-dom@19.2.0:
resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==} resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==}
peerDependencies: peerDependencies:
@@ -1915,6 +2072,10 @@ packages:
resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==} resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
readable-stream@3.6.2:
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
engines: {node: '>= 6'}
readdirp@4.1.2: readdirp@4.1.2:
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
engines: {node: '>= 14.18.0'} engines: {node: '>= 14.18.0'}
@@ -1964,6 +2125,9 @@ packages:
resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==}
engines: {node: '>=0.4'} engines: {node: '>=0.4'}
safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
safe-push-apply@1.0.0: safe-push-apply@1.0.0:
resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -2037,6 +2201,12 @@ packages:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
engines: {node: '>=14'} 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: source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@@ -2078,10 +2248,17 @@ packages:
resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
string_decoder@1.3.0:
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
strip-bom@3.0.0: strip-bom@3.0.0:
resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
engines: {node: '>=4'} 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: strip-json-comments@3.1.1:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -2114,6 +2291,13 @@ packages:
resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
engines: {node: '>=6'} 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: tinyexec@1.0.2:
resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -2138,6 +2322,9 @@ packages:
tslib@2.8.1: tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
tunnel-agent@0.6.0:
resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
type-check@0.4.0: type-check@0.4.0:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
@@ -2193,6 +2380,9 @@ packages:
uri-js@4.4.1: uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
valibot@1.1.0: valibot@1.1.0:
resolution: {integrity: sha512-Nk8lX30Qhu+9txPYTwM0cFlWLdPFsFr6LblzqIySfbZph9+BFsAHsNvHOymEviUepeIW6KFHzpX8TKhbptBXXw==} resolution: {integrity: sha512-Nk8lX30Qhu+9txPYTwM0cFlWLdPFsFr6LblzqIySfbZph9+BFsAHsNvHOymEviUepeIW6KFHzpX8TKhbptBXXw==}
peerDependencies: peerDependencies:
@@ -2226,6 +2416,9 @@ packages:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
yallist@3.1.1: yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
@@ -2249,6 +2442,14 @@ snapshots:
'@alloc/quick-lru@5.2.0': {} '@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': '@babel/code-frame@7.27.1':
dependencies: dependencies:
'@babel/helper-validator-identifier': 7.28.5 '@babel/helper-validator-identifier': 7.28.5
@@ -2623,13 +2824,20 @@ snapshots:
'@nolyfill/is-core-module@1.0.39': {} '@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-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: dependencies:
'@prisma/client-runtime-utils': 7.0.1 '@prisma/client-runtime-utils': 7.0.1
optionalDependencies: 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 typescript: 5.9.3
'@prisma/config@7.0.1': '@prisma/config@7.0.1':
@@ -2667,6 +2875,10 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- typescript - typescript
'@prisma/driver-adapter-utils@7.0.1':
dependencies:
'@prisma/debug': 7.0.1
'@prisma/engines-version@7.1.0-2.f09f2815f091dbba658cdcd2264306d88bb5bda6': {} '@prisma/engines-version@7.1.0-2.f09f2815f091dbba658cdcd2264306d88bb5bda6': {}
'@prisma/engines@7.0.1': '@prisma/engines@7.0.1':
@@ -2780,6 +2992,10 @@ snapshots:
tslib: 2.8.1 tslib: 2.8.1
optional: true optional: true
'@types/bcryptjs@3.0.0':
dependencies:
bcryptjs: 3.0.3
'@types/estree@1.0.8': {} '@types/estree@1.0.8': {}
'@types/json-schema@7.0.15': {} '@types/json-schema@7.0.15': {}
@@ -3053,8 +3269,27 @@ snapshots:
balanced-match@1.0.2: {} balanced-match@1.0.2: {}
base64-js@1.5.1: {}
baseline-browser-mapping@2.8.31: {} 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: brace-expansion@1.1.12:
dependencies: dependencies:
balanced-match: 1.0.2 balanced-match: 1.0.2
@@ -3076,6 +3311,11 @@ snapshots:
node-releases: 2.0.27 node-releases: 2.0.27
update-browserslist-db: 1.1.4(browserslist@4.28.0) 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: c12@3.1.0:
dependencies: dependencies:
chokidar: 4.0.3 chokidar: 4.0.3
@@ -3130,6 +3370,8 @@ snapshots:
dependencies: dependencies:
readdirp: 4.1.2 readdirp: 4.1.2
chownr@1.1.4: {}
citty@0.1.6: citty@0.1.6:
dependencies: dependencies:
consola: 3.4.2 consola: 3.4.2
@@ -3186,6 +3428,12 @@ snapshots:
dependencies: dependencies:
ms: 2.1.3 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: {} deep-is@0.1.4: {}
deepmerge-ts@7.1.5: {} deepmerge-ts@7.1.5: {}
@@ -3235,6 +3483,10 @@ snapshots:
empathic@2.0.0: {} empathic@2.0.0: {}
end-of-stream@1.4.5:
dependencies:
once: 1.4.0
enhanced-resolve@5.18.3: enhanced-resolve@5.18.3:
dependencies: dependencies:
graceful-fs: 4.2.11 graceful-fs: 4.2.11
@@ -3552,6 +3804,8 @@ snapshots:
esutils@2.0.3: {} esutils@2.0.3: {}
expand-template@2.0.3: {}
exsolve@1.0.8: {} exsolve@1.0.8: {}
fast-check@3.23.2: fast-check@3.23.2:
@@ -3584,6 +3838,8 @@ snapshots:
dependencies: dependencies:
flat-cache: 4.0.1 flat-cache: 4.0.1
file-uri-to-path@1.0.0: {}
fill-range@7.1.1: fill-range@7.1.1:
dependencies: dependencies:
to-regex-range: 5.0.1 to-regex-range: 5.0.1
@@ -3609,6 +3865,8 @@ snapshots:
cross-spawn: 7.0.6 cross-spawn: 7.0.6
signal-exit: 4.1.0 signal-exit: 4.1.0
fs-constants@1.0.0: {}
function-bind@1.1.2: {} function-bind@1.1.2: {}
function.prototype.name@1.1.8: function.prototype.name@1.1.8:
@@ -3669,6 +3927,8 @@ snapshots:
nypm: 0.6.2 nypm: 0.6.2
pathe: 2.0.3 pathe: 2.0.3
github-from-package@0.0.0: {}
glob-parent@5.1.2: glob-parent@5.1.2:
dependencies: dependencies:
is-glob: 4.0.3 is-glob: 4.0.3
@@ -3730,6 +3990,8 @@ snapshots:
dependencies: dependencies:
safer-buffer: 2.1.2 safer-buffer: 2.1.2
ieee754@1.2.1: {}
ignore@5.3.2: {} ignore@5.3.2: {}
ignore@7.0.5: {} ignore@7.0.5: {}
@@ -3741,6 +4003,10 @@ snapshots:
imurmurhash@0.1.4: {} imurmurhash@0.1.4: {}
inherits@2.0.4: {}
ini@1.3.8: {}
internal-slot@1.1.0: internal-slot@1.1.0:
dependencies: dependencies:
es-errors: 1.3.0 es-errors: 1.3.0
@@ -3876,6 +4142,8 @@ snapshots:
jiti@2.6.1: {} jiti@2.6.1: {}
jose@6.1.2: {}
js-tokens@4.0.0: {} js-tokens@4.0.0: {}
js-yaml@4.1.1: js-yaml@4.1.1:
@@ -4004,6 +4272,8 @@ snapshots:
braces: 3.0.3 braces: 3.0.3
picomatch: 2.3.1 picomatch: 2.3.1
mimic-response@3.1.0: {}
minimatch@3.1.2: minimatch@3.1.2:
dependencies: dependencies:
brace-expansion: 1.1.12 brace-expansion: 1.1.12
@@ -4014,6 +4284,8 @@ snapshots:
minimist@1.2.8: {} minimist@1.2.8: {}
mkdirp-classic@0.5.3: {}
ms@2.1.3: {} ms@2.1.3: {}
mysql2@3.15.3: mysql2@3.15.3:
@@ -4034,10 +4306,18 @@ snapshots:
nanoid@3.3.11: {} nanoid@3.3.11: {}
napi-build-utils@2.0.0: {}
napi-postinstall@0.3.4: {} napi-postinstall@0.3.4: {}
natural-compare@1.4.0: {} 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): next@16.0.5(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
dependencies: dependencies:
'@next/env': 16.0.5 '@next/env': 16.0.5
@@ -4061,6 +4341,10 @@ snapshots:
- '@babel/core' - '@babel/core'
- babel-plugin-macros - babel-plugin-macros
node-abi@3.85.0:
dependencies:
semver: 7.7.3
node-fetch-native@1.6.7: {} node-fetch-native@1.6.7: {}
node-releases@2.0.27: {} node-releases@2.0.27: {}
@@ -4073,6 +4357,8 @@ snapshots:
pkg-types: 2.3.0 pkg-types: 2.3.0
tinyexec: 1.0.2 tinyexec: 1.0.2
oauth4webapi@3.8.3: {}
object-assign@4.1.1: {} object-assign@4.1.1: {}
object-inspect@1.13.4: {} object-inspect@1.13.4: {}
@@ -4117,6 +4403,10 @@ snapshots:
ohash@2.0.11: {} ohash@2.0.11: {}
once@1.4.0:
dependencies:
wrappy: 1.0.2
optionator@0.9.4: optionator@0.9.4:
dependencies: dependencies:
deep-is: 0.1.4 deep-is: 0.1.4
@@ -4182,11 +4472,32 @@ snapshots:
postgres@3.4.7: {} 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: {} prelude-ls@1.2.1: {}
prettier@3.7.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: dependencies:
'@prisma/config': 7.0.1 '@prisma/config': 7.0.1
'@prisma/dev': 0.13.0(typescript@5.9.3) '@prisma/dev': 0.13.0(typescript@5.9.3)
@@ -4195,6 +4506,7 @@ snapshots:
mysql2: 3.15.3 mysql2: 3.15.3
postgres: 3.4.7 postgres: 3.4.7
optionalDependencies: optionalDependencies:
better-sqlite3: 12.4.6
typescript: 5.9.3 typescript: 5.9.3
transitivePeerDependencies: transitivePeerDependencies:
- '@types/react' - '@types/react'
@@ -4214,6 +4526,11 @@ snapshots:
retry: 0.12.0 retry: 0.12.0
signal-exit: 3.0.7 signal-exit: 3.0.7
pump@3.0.3:
dependencies:
end-of-stream: 1.4.5
once: 1.4.0
punycode@2.3.1: {} punycode@2.3.1: {}
pure-rand@6.1.0: {} pure-rand@6.1.0: {}
@@ -4225,6 +4542,13 @@ snapshots:
defu: 6.1.4 defu: 6.1.4
destr: 2.0.5 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): react-dom@19.2.0(react@19.2.0):
dependencies: dependencies:
react: 19.2.0 react: 19.2.0
@@ -4234,6 +4558,12 @@ snapshots:
react@19.2.0: {} 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: {} readdirp@4.1.2: {}
reflect.getprototypeof@1.0.10: reflect.getprototypeof@1.0.10:
@@ -4294,6 +4624,8 @@ snapshots:
has-symbols: 1.1.0 has-symbols: 1.1.0
isarray: 2.0.5 isarray: 2.0.5
safe-buffer@5.2.1: {}
safe-push-apply@1.0.0: safe-push-apply@1.0.0:
dependencies: dependencies:
es-errors: 1.3.0 es-errors: 1.3.0
@@ -4407,6 +4739,14 @@ snapshots:
signal-exit@4.1.0: {} 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: {} source-map-js@1.2.1: {}
sqlstring@2.3.3: {} sqlstring@2.3.3: {}
@@ -4470,8 +4810,14 @@ snapshots:
define-properties: 1.2.1 define-properties: 1.2.1
es-object-atoms: 1.1.1 es-object-atoms: 1.1.1
string_decoder@1.3.0:
dependencies:
safe-buffer: 5.2.1
strip-bom@3.0.0: {} strip-bom@3.0.0: {}
strip-json-comments@2.0.1: {}
strip-json-comments@3.1.1: {} strip-json-comments@3.1.1: {}
styled-jsx@5.1.6(@babel/core@7.28.5)(react@19.2.0): styled-jsx@5.1.6(@babel/core@7.28.5)(react@19.2.0):
@@ -4491,6 +4837,21 @@ snapshots:
tapable@2.3.0: {} 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: {} tinyexec@1.0.2: {}
tinyglobby@0.2.15: tinyglobby@0.2.15:
@@ -4515,6 +4876,10 @@ snapshots:
tslib@2.8.1: {} tslib@2.8.1: {}
tunnel-agent@0.6.0:
dependencies:
safe-buffer: 5.2.1
type-check@0.4.0: type-check@0.4.0:
dependencies: dependencies:
prelude-ls: 1.2.1 prelude-ls: 1.2.1
@@ -4610,6 +4975,8 @@ snapshots:
dependencies: dependencies:
punycode: 2.3.1 punycode: 2.3.1
util-deprecate@1.0.2: {}
valibot@1.1.0(typescript@5.9.3): valibot@1.1.0(typescript@5.9.3):
optionalDependencies: optionalDependencies:
typescript: 5.9.3 typescript: 5.9.3
@@ -4661,6 +5028,8 @@ snapshots:
word-wrap@1.2.5: {} word-wrap@1.2.5: {}
wrappy@1.0.2: {}
yallist@3.1.1: {} yallist@3.1.1: {}
yocto-queue@0.1.0: {} yocto-queue@0.1.0: {}

View File

@@ -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<string | null>(null);
const [loading, setLoading] = useState(false);
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
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 (
<div className="flex min-h-screen items-center justify-center bg-background px-4">
<div className="w-full max-w-md">
<div className="mb-8 text-center">
<Link href="/" className="inline-flex items-center gap-2">
<span className="text-3xl">📊</span>
<span className="text-2xl font-bold text-foreground">SWOT Manager</span>
</Link>
<p className="mt-2 text-muted">Connectez-vous à votre compte</p>
</div>
<form
onSubmit={handleSubmit}
className="rounded-xl border border-border bg-card p-8 shadow-lg"
>
{error && (
<div className="mb-4 rounded-lg border border-destructive/20 bg-destructive/10 p-3 text-sm text-destructive">
{error}
</div>
)}
<div className="mb-4">
<label htmlFor="email" className="mb-2 block text-sm font-medium text-foreground">
Email
</label>
<input
id="email"
name="email"
type="email"
required
autoComplete="email"
className="w-full rounded-lg border border-input-border bg-input px-4 py-2.5 text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20"
placeholder="vous@exemple.com"
/>
</div>
<div className="mb-6">
<label htmlFor="password" className="mb-2 block text-sm font-medium text-foreground">
Mot de passe
</label>
<input
id="password"
name="password"
type="password"
required
autoComplete="current-password"
className="w-full rounded-lg border border-input-border bg-input px-4 py-2.5 text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20"
placeholder="••••••••"
/>
</div>
<button
type="submit"
disabled={loading}
className="w-full rounded-lg bg-primary px-4 py-2.5 font-semibold text-primary-foreground transition-colors hover:bg-primary-hover disabled:cursor-not-allowed disabled:opacity-50"
>
{loading ? 'Connexion...' : 'Se connecter'}
</button>
<p className="mt-6 text-center text-sm text-muted">
Pas encore de compte ?{' '}
<Link href="/register" className="font-medium text-primary hover:underline">
Créer un compte
</Link>
</p>
</form>
</div>
</div>
);
}

View File

@@ -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<string | null>(null);
const [loading, setLoading] = useState(false);
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
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 (
<div className="flex min-h-screen items-center justify-center bg-background px-4">
<div className="w-full max-w-md">
<div className="mb-8 text-center">
<Link href="/" className="inline-flex items-center gap-2">
<span className="text-3xl">📊</span>
<span className="text-2xl font-bold text-foreground">SWOT Manager</span>
</Link>
<p className="mt-2 text-muted">Créez votre compte</p>
</div>
<form
onSubmit={handleSubmit}
className="rounded-xl border border-border bg-card p-8 shadow-lg"
>
{error && (
<div className="mb-4 rounded-lg border border-destructive/20 bg-destructive/10 p-3 text-sm text-destructive">
{error}
</div>
)}
<div className="mb-4">
<label htmlFor="name" className="mb-2 block text-sm font-medium text-foreground">
Nom
</label>
<input
id="name"
name="name"
type="text"
autoComplete="name"
className="w-full rounded-lg border border-input-border bg-input px-4 py-2.5 text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20"
placeholder="Jean Dupont"
/>
</div>
<div className="mb-4">
<label htmlFor="email" className="mb-2 block text-sm font-medium text-foreground">
Email
</label>
<input
id="email"
name="email"
type="email"
required
autoComplete="email"
className="w-full rounded-lg border border-input-border bg-input px-4 py-2.5 text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20"
placeholder="vous@exemple.com"
/>
</div>
<div className="mb-4">
<label htmlFor="password" className="mb-2 block text-sm font-medium text-foreground">
Mot de passe
</label>
<input
id="password"
name="password"
type="password"
required
autoComplete="new-password"
className="w-full rounded-lg border border-input-border bg-input px-4 py-2.5 text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20"
placeholder="••••••••"
/>
</div>
<div className="mb-6">
<label
htmlFor="confirmPassword"
className="mb-2 block text-sm font-medium text-foreground"
>
Confirmer le mot de passe
</label>
<input
id="confirmPassword"
name="confirmPassword"
type="password"
required
autoComplete="new-password"
className="w-full rounded-lg border border-input-border bg-input px-4 py-2.5 text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20"
placeholder="••••••••"
/>
</div>
<button
type="submit"
disabled={loading}
className="w-full rounded-lg bg-primary px-4 py-2.5 font-semibold text-primary-foreground transition-colors hover:bg-primary-hover disabled:cursor-not-allowed disabled:opacity-50"
>
{loading ? 'Création...' : 'Créer mon compte'}
</button>
<p className="mt-6 text-center text-sm text-muted">
Déjà un compte ?{' '}
<Link href="/login" className="font-medium text-primary hover:underline">
Se connecter
</Link>
</p>
</form>
</div>
</div>
);
}

View File

@@ -0,0 +1,4 @@
import { handlers } from '@/lib/auth';
export const { GET, POST } = handlers;

View File

@@ -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 });
}
}

View File

@@ -1,5 +1,6 @@
'use client'; 'use client';
import { SessionProvider } from 'next-auth/react';
import { ThemeProvider } from '@/contexts/ThemeContext'; import { ThemeProvider } from '@/contexts/ThemeContext';
import { Header } from '@/components/layout/Header'; import { Header } from '@/components/layout/Header';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
@@ -10,11 +11,13 @@ interface ProvidersProps {
export function Providers({ children }: ProvidersProps) { export function Providers({ children }: ProvidersProps) {
return ( return (
<SessionProvider>
<ThemeProvider> <ThemeProvider>
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<Header /> <Header />
{children} {children}
</div> </div>
</ThemeProvider> </ThemeProvider>
</SessionProvider>
); );
} }

View File

@@ -1,10 +1,14 @@
'use client'; 'use client';
import Link from 'next/link'; import Link from 'next/link';
import { useSession, signOut } from 'next-auth/react';
import { useTheme } from '@/contexts/ThemeContext'; import { useTheme } from '@/contexts/ThemeContext';
import { useState } from 'react';
export function Header() { export function Header() {
const { theme, toggleTheme } = useTheme(); const { theme, toggleTheme } = useTheme();
const { data: session, status } = useSession();
const [menuOpen, setMenuOpen] = useState(false);
return ( return (
<header className="sticky top-0 z-50 border-b border-border bg-card/80 backdrop-blur-sm"> <header className="sticky top-0 z-50 border-b border-border bg-card/80 backdrop-blur-sm">
@@ -15,12 +19,14 @@ export function Header() {
</Link> </Link>
<nav className="flex items-center gap-4"> <nav className="flex items-center gap-4">
{status === 'authenticated' && session?.user && (
<Link <Link
href="/sessions" href="/sessions"
className="text-muted transition-colors hover:text-foreground" className="text-muted transition-colors hover:text-foreground"
> >
Mes Sessions Mes Sessions
</Link> </Link>
)}
<button <button
onClick={toggleTheme} onClick={toggleTheme}
@@ -29,9 +35,66 @@ export function Header() {
> >
{theme === 'light' ? '🌙' : '☀️'} {theme === 'light' ? '🌙' : '☀️'}
</button> </button>
{status === 'loading' ? (
<div className="h-9 w-20 animate-pulse rounded-lg bg-card-hover" />
) : status === 'authenticated' && session?.user ? (
<div className="relative">
<button
onClick={() => setMenuOpen(!menuOpen)}
className="flex h-9 items-center gap-2 rounded-lg border border-border bg-card px-3 transition-colors hover:bg-card-hover"
>
<span className="text-sm font-medium text-foreground">
{session.user.name || session.user.email?.split('@')[0]}
</span>
<svg
className={`h-4 w-4 text-muted transition-transform ${menuOpen ? 'rotate-180' : ''}`}
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M19 9l-7 7-7-7"
/>
</svg>
</button>
{menuOpen && (
<>
<div
className="fixed inset-0 z-10"
onClick={() => setMenuOpen(false)}
/>
<div className="absolute right-0 z-20 mt-2 w-48 rounded-lg border border-border bg-card py-1 shadow-lg">
<div className="border-b border-border px-4 py-2">
<p className="text-xs text-muted">Connecté en tant que</p>
<p className="truncate text-sm font-medium text-foreground">
{session.user.email}
</p>
</div>
<button
onClick={() => signOut({ callbackUrl: '/' })}
className="w-full px-4 py-2 text-left text-sm text-destructive hover:bg-card-hover"
>
Se déconnecter
</button>
</div>
</>
)}
</div>
) : (
<Link
href="/login"
className="flex h-9 items-center rounded-lg bg-primary px-4 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary-hover"
>
Connexion
</Link>
)}
</nav> </nav>
</div> </div>
</header> </header>
); );
} }

29
src/lib/auth.config.ts Normal file
View File

@@ -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
};

62
src/lib/auth.ts Normal file
View File

@@ -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;
},
},
});

10
src/middleware.ts Normal file
View File

@@ -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).*)'],
};

68
src/services/auth.ts Normal file
View File

@@ -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<AuthResult> {
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 },
});
}

View File

@@ -1,18 +1,25 @@
import { PrismaClient } from '@prisma/client'; import { PrismaClient } from '@prisma/client';
import { PrismaBetterSqlite3 } from '@prisma/adapter-better-sqlite3';
const globalForPrisma = globalThis as unknown as { const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined; prisma: PrismaClient | undefined;
}; };
export const prisma = function createPrismaClient() {
globalForPrisma.prisma ?? const adapter = new PrismaBetterSqlite3({
new PrismaClient({ url: process.env.DATABASE_URL ?? 'file:./prisma/dev.db',
});
return new PrismaClient({
adapter,
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'], log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
}); });
}
export const prisma = globalForPrisma.prisma ?? createPrismaClient();
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma; globalForPrisma.prisma = prisma;
} }
export default prisma; export default prisma;