feat: Initial login / logout API endpoints

This commit is contained in:
2026-03-26 13:51:37 -04:00
parent 9bb6b40191
commit bd108f69a4
10 changed files with 322 additions and 19 deletions

View File

@@ -2,7 +2,7 @@ import { version, description } from "./package.json";
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
modules: ["@lewebsimple/nuxt-graphql", "@nuxt/ui", "@nuxtjs/seo", "nuxt-svgo"],
modules: ["@lewebsimple/nuxt-graphql", "@nuxt/ui", "@nuxtjs/seo", "nuxt-auth-utils", "nuxt-svgo"],
compatibilityDate: "2026-03-18",
devtools: { enabled: true },
@@ -35,6 +35,7 @@ export default defineNuxtConfig({
},
},
server: {
context: ["server/graphql/context.ts"],
schema: [{ type: "remote", endpoint: `${process.env.NUXT_WP_URL}/graphql` }],
},
},

View File

@@ -23,6 +23,7 @@
"@nuxt/ui": "^4.6.0",
"@nuxtjs/seo": "^4.0.2",
"nuxt": "^4.4.2",
"nuxt-auth-utils": "^0.5.29",
"nuxt-svgo": "^4.2.6",
"tailwindcss": "^4.2.2",
"vue": "^3.5.31",

View File

@@ -23,18 +23,15 @@ importers:
'@nuxtjs/seo':
specifier: ^4.0.2
version: 4.0.2(489f84e1ce5b91b262b98d380824761d)
'@resvg/resvg-js':
specifier: ^2.6.2
version: 2.6.2
nuxt:
specifier: ^4.4.2
version: 4.4.2(@babel/core@7.29.0)(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.5.0)(@vue/compiler-sfc@3.5.31)(cac@6.7.14)(db0@0.3.4)(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.2)(oxlint@1.57.0)(rollup-plugin-visualizer@7.0.1(rollup@4.60.0))(rollup@4.60.0)(terser@5.46.1)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3))(vue-tsc@3.2.6(typescript@5.9.3))(yaml@2.8.3)
nuxt-auth-utils:
specifier: ^0.5.29
version: 0.5.29(magicast@0.5.2)
nuxt-svgo:
specifier: ^4.2.6
version: 4.2.6(magicast@0.5.2)(vue@3.5.31(typescript@5.9.3))
satori:
specifier: ^0.26.0
version: 0.26.0
tailwindcss:
specifier: ^4.2.2
version: 4.2.2
@@ -66,6 +63,18 @@ importers:
packages:
'@adonisjs/hash@9.1.1':
resolution: {integrity: sha512-ZkRguwjAp4skKvKDdRAfdJ2oqQ0N7p9l3sioyXO1E8o0WcsyDgEpsTQtuVNoIdMiw4sn4gJlmL3nyF4BcK1ZDQ==}
engines: {node: '>=20.6.0'}
peerDependencies:
argon2: ^0.31.2 || ^0.41.0 || ^0.43.0
bcrypt: ^5.1.1 || ^6.0.0
peerDependenciesMeta:
argon2:
optional: true
bcrypt:
optional: true
'@alloc/quick-lru@5.2.0':
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'}
@@ -1740,6 +1749,10 @@ packages:
resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==}
engines: {node: '>= 10.0.0'}
'@phc/format@1.0.0':
resolution: {integrity: sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==}
engines: {node: '>=10'}
'@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
@@ -1756,6 +1769,17 @@ packages:
'@poppinss/exception@1.2.3':
resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==}
'@poppinss/object-builder@1.1.0':
resolution: {integrity: sha512-FOrOq52l7u8goR5yncX14+k+Ewi5djnrt1JwXeS/FvnwAPOiveFhiczCDuvXdssAwamtrV2hp5Rw9v+n2T7hQg==}
engines: {node: '>=20.6.0'}
'@poppinss/string@1.7.1':
resolution: {integrity: sha512-OrLzv/nGDU6l6dLXIQHe8nbNSWWfuSbpB/TW5nRpZFf49CLuQlIHlSPN9IdSUv2vG+59yGM6LoibsaHn8B8mDw==}
'@poppinss/utils@6.10.1':
resolution: {integrity: sha512-da+MMyeXhBaKtxQiWPfy7+056wk3lVIhioJnXHXkJ2/OHDaZfFcyKHNl1R06sdYO8lIRXcXdoZ6LO2ARmkAREA==}
engines: {node: '>=18.16.0'}
'@remirror/core-constants@3.0.0':
resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==}
@@ -2474,6 +2498,9 @@ packages:
'@types/node@25.5.0':
resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==}
'@types/pluralize@0.0.33':
resolution: {integrity: sha512-JOqsl+ZoCpP4e8TDke9W79FDcSgPAR0l6pixx2JHkhnRjvShyYiAYw2LVsnA7K08Y6DeOnaU6ujmENO4os/cYg==}
'@types/resolve@1.20.2':
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
@@ -2946,6 +2973,10 @@ packages:
capital-case@1.0.4:
resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==}
case-anything@3.1.2:
resolution: {integrity: sha512-wljhAjDDIv/hM2FzgJnYQg90AWmZMNtESCjTeLH680qTzdo0nErlCxOmgzgX4ZsZAtIvqHyD87ES8QyriXB+BQ==}
engines: {node: '>=18'}
ccount@2.0.1:
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
@@ -3476,6 +3507,10 @@ packages:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
flattie@1.1.1:
resolution: {integrity: sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==}
engines: {node: '>=8'}
fontaine@0.8.0:
resolution: {integrity: sha512-eek1GbzOdWIj9FyQH/emqW1aEdfC3lYRCHepzwlFCm5T77fBSRSyNRKE6/antF1/B1M+SfJXVRQTY9GAr7lnDg==}
engines: {node: '>=18.12.0'}
@@ -3826,6 +3861,9 @@ packages:
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
hasBin: true
jose@6.2.2:
resolution: {integrity: sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==}
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
@@ -4215,6 +4253,23 @@ packages:
nth-check@2.1.1:
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
nuxt-auth-utils@0.5.29:
resolution: {integrity: sha512-aQ9oD8QR51jUCe2BFEsO/G/E0K+XUy8Skjn3hYcNLiqZ9XE4Y3uT/ozBCmAQ+TR3WW6prj8vUR0wQes8W1N0PA==}
peerDependencies:
'@atproto/api': ^0.13.15
'@atproto/oauth-client-node': ^0.2.0
'@simplewebauthn/browser': ^11.0.0
'@simplewebauthn/server': ^11.0.0
peerDependenciesMeta:
'@atproto/api':
optional: true
'@atproto/oauth-client-node':
optional: true
'@simplewebauthn/browser':
optional: true
'@simplewebauthn/server':
optional: true
nuxt-link-checker@4.3.9:
resolution: {integrity: sha512-iYJU+A/xUhk62v4tol9cdjJS1+ZOSl0+tdUObgifdeSts6IqAUByUAiX4H6yOY2tdQYKjahMFbQr1GJ+/4LYnQ==}
@@ -4336,6 +4391,9 @@ packages:
engines: {node: '>=18'}
hasBin: true
oauth4webapi@3.8.5:
resolution: {integrity: sha512-A8jmyUckVhRJj5lspguklcl90Ydqk61H3dcU0oLhH3Yv13KpAliKTt5hknpGGPZSSfOwGyraNEFmofDYH+1kSg==}
obug@2.1.1:
resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==}
@@ -4374,6 +4432,9 @@ packages:
resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==}
engines: {node: '>=20'}
openid-client@6.8.2:
resolution: {integrity: sha512-uOvTCndr4udZsKihJ68H9bUICrriHdUVJ6Az+4Ns6cW55rwM5h0bjVIzDz2SxgOI84LKjFyjOFvERLzdTUROGA==}
orderedmap@2.1.1:
resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==}
@@ -4510,6 +4571,10 @@ packages:
pkg-types@2.3.0:
resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==}
pluralize@8.0.0:
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
engines: {node: '>=4'}
postcss-calc@10.1.1:
resolution: {integrity: sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw==}
engines: {node: ^18.12 || ^20.9 || >=22.0}
@@ -4879,6 +4944,10 @@ packages:
safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
safe-stable-stringify@2.5.0:
resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
engines: {node: '>=10'}
satori@0.26.0:
resolution: {integrity: sha512-tkMFrfIs3l2mQ2JEcyW0ADTy3zGggFRFzi6Ef8YozQSFsFKEqaSO1Y8F9wJg4//PJGQauMalHGTUEkPrFwhVPA==}
engines: {node: '>=16'}
@@ -4890,6 +4959,9 @@ packages:
scule@1.3.0:
resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==}
secure-json-parse@4.1.0:
resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==}
semver@6.3.1:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true
@@ -4972,6 +5044,10 @@ packages:
resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==}
engines: {node: '>=14.16'}
slugify@1.6.8:
resolution: {integrity: sha512-HVk9X1E0gz3mSpoi60h/saazLKXKaZThMLU3u/aNwoYn8/xQyX2MGxL0ui2eaokkD7tF+Zo+cKTHUbe1mmmGzA==}
engines: {node: '>=8.0.0'}
smob@1.6.1:
resolution: {integrity: sha512-KAkBqZl3c2GvNgNhcoyJae1aKldDW0LO279wF9bk1PnluRTETKBq0WyzRXxEhoQLk56yHaOY4JCBEKDuJIET5g==}
engines: {node: '>=20.0.0'}
@@ -5698,6 +5774,11 @@ packages:
snapshots:
'@adonisjs/hash@9.1.1':
dependencies:
'@phc/format': 1.0.0
'@poppinss/utils': 6.10.1
'@alloc/quick-lru@5.2.0': {}
'@antfu/install-pkg@1.1.0':
@@ -7540,6 +7621,8 @@ snapshots:
'@parcel/watcher-win32-ia32': 2.5.6
'@parcel/watcher-win32-x64': 2.5.6
'@phc/format@1.0.0': {}
'@pkgjs/parseargs@0.11.0':
optional: true
@@ -7557,6 +7640,24 @@ snapshots:
'@poppinss/exception@1.2.3': {}
'@poppinss/object-builder@1.1.0': {}
'@poppinss/string@1.7.1':
dependencies:
'@types/pluralize': 0.0.33
case-anything: 3.1.2
pluralize: 8.0.0
slugify: 1.6.8
'@poppinss/utils@6.10.1':
dependencies:
'@poppinss/exception': 1.2.3
'@poppinss/object-builder': 1.1.0
'@poppinss/string': 1.7.1
flattie: 1.1.1
safe-stable-stringify: 2.5.0
secure-json-parse: 4.1.0
'@remirror/core-constants@3.0.0': {}
'@repeaterjs/repeater@3.0.6': {}
@@ -7611,6 +7712,7 @@ snapshots:
'@resvg/resvg-js-win32-arm64-msvc': 2.6.2
'@resvg/resvg-js-win32-ia32-msvc': 2.6.2
'@resvg/resvg-js-win32-x64-msvc': 2.6.2
optional: true
'@rolldown/pluginutils@1.0.0-rc.12': {}
@@ -7798,6 +7900,7 @@ snapshots:
dependencies:
fflate: 0.7.4
string.prototype.codepointat: 0.2.1
optional: true
'@sindresorhus/is@7.2.0': {}
@@ -8163,6 +8266,8 @@ snapshots:
dependencies:
undici-types: 7.18.2
'@types/pluralize@0.0.33': {}
'@types/resolve@1.20.2': {}
'@types/unist@3.0.3': {}
@@ -8590,7 +8695,8 @@ snapshots:
dependencies:
bare-path: 3.0.0
base64-js@0.0.8: {}
base64-js@0.0.8:
optional: true
base64-js@1.5.1: {}
@@ -8663,7 +8769,8 @@ snapshots:
pascal-case: 3.1.2
tslib: 2.6.3
camelize@1.0.1: {}
camelize@1.0.1:
optional: true
caniuse-api@3.0.0:
dependencies:
@@ -8680,6 +8787,8 @@ snapshots:
tslib: 2.6.3
upper-case-first: 2.0.2
case-anything@3.1.2: {}
ccount@2.0.1: {}
chalk@5.6.2: {}
@@ -8852,17 +8961,21 @@ snapshots:
dependencies:
uncrypto: 0.1.3
css-background-parser@0.1.0: {}
css-background-parser@0.1.0:
optional: true
css-box-shadow@1.0.0-3: {}
css-box-shadow@1.0.0-3:
optional: true
css-color-keywords@1.0.0: {}
css-color-keywords@1.0.0:
optional: true
css-declaration-sorter@7.3.1(postcss@8.5.8):
dependencies:
postcss: 8.5.8
css-gradient-parser@0.0.17: {}
css-gradient-parser@0.0.17:
optional: true
css-select@5.2.2:
dependencies:
@@ -8877,6 +8990,7 @@ snapshots:
camelize: 1.0.1
css-color-keywords: 1.0.0
postcss-value-parser: 4.2.0
optional: true
css-tree@2.2.1:
dependencies:
@@ -9068,7 +9182,8 @@ snapshots:
embla-carousel@8.6.0: {}
emoji-regex-xs@2.0.1: {}
emoji-regex-xs@2.0.1:
optional: true
emoji-regex@10.6.0: {}
@@ -9194,7 +9309,8 @@ snapshots:
optionalDependencies:
picomatch: 4.0.4
fflate@0.7.4: {}
fflate@0.7.4:
optional: true
file-uri-to-path@1.0.0: {}
@@ -9202,6 +9318,8 @@ snapshots:
dependencies:
to-regex-range: 5.0.1
flattie@1.1.1: {}
fontaine@0.8.0:
dependencies:
'@capsizecss/unpack': 4.0.0
@@ -9412,7 +9530,8 @@ snapshots:
capital-case: 1.0.4
tslib: 2.6.3
hex-rgb@4.3.0: {}
hex-rgb@4.3.0:
optional: true
hey-listen@1.0.8: {}
@@ -9581,6 +9700,8 @@ snapshots:
jiti@2.6.1: {}
jose@6.2.2: {}
js-tokens@4.0.0: {}
js-tokens@9.0.1: {}
@@ -9670,6 +9791,7 @@ snapshots:
dependencies:
base64-js: 0.0.8
unicode-trie: 2.0.0
optional: true
linkify-it@5.0.0:
dependencies:
@@ -10036,6 +10158,24 @@ snapshots:
dependencies:
boolbase: 1.0.0
nuxt-auth-utils@0.5.29(magicast@0.5.2):
dependencies:
'@adonisjs/hash': 9.1.1
'@nuxt/kit': 4.4.2(magicast@0.5.2)
defu: 6.1.4
h3: 1.15.10
hookable: 6.1.0
jose: 6.2.2
ofetch: 1.5.1
openid-client: 6.8.2
pathe: 2.0.3
scule: 1.3.0
uncrypto: 0.1.3
transitivePeerDependencies:
- argon2
- bcrypt
- magicast
nuxt-link-checker@4.3.9(db0@0.3.4)(ioredis@5.10.1)(magicast@0.5.2)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3))(vue@3.5.31(typescript@5.9.3)):
dependencies:
'@nuxt/devtools-kit': 3.2.4(magicast@0.5.2)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3))
@@ -10509,6 +10649,8 @@ snapshots:
pathe: 2.0.3
tinyexec: 1.0.4
oauth4webapi@3.8.5: {}
obug@2.1.1: {}
ofetch@1.5.1:
@@ -10555,6 +10697,11 @@ snapshots:
powershell-utils: 0.1.0
wsl-utils: 0.3.1
openid-client@6.8.2:
dependencies:
jose: 6.2.2
oauth4webapi: 3.8.5
orderedmap@2.1.1: {}
oxc-minify@0.117.0:
@@ -10717,7 +10864,8 @@ snapshots:
package-manager-detector@1.6.0: {}
pako@0.2.9: {}
pako@0.2.9:
optional: true
param-case@3.0.4:
dependencies:
@@ -10728,6 +10876,7 @@ snapshots:
dependencies:
color-name: 1.1.4
hex-rgb: 4.3.0
optional: true
parse-filepath@1.0.2:
dependencies:
@@ -10799,6 +10948,8 @@ snapshots:
exsolve: 1.0.8
pathe: 2.0.3
pluralize@8.0.0: {}
postcss-calc@10.1.1(postcss@8.5.8):
dependencies:
postcss: 8.5.8
@@ -11220,6 +11371,8 @@ snapshots:
safe-buffer@5.2.1: {}
safe-stable-stringify@2.5.0: {}
satori@0.26.0:
dependencies:
'@shuding/opentype.js': 1.4.0-beta.0
@@ -11233,11 +11386,14 @@ snapshots:
parse-css-color: 0.2.1
postcss-value-parser: 4.2.0
yoga-layout: 3.2.1
optional: true
sax@1.6.0: {}
scule@1.3.0: {}
secure-json-parse@4.1.0: {}
semver@6.3.1: {}
semver@7.7.4: {}
@@ -11334,6 +11490,8 @@ snapshots:
slash@5.1.0: {}
slugify@1.6.8: {}
smob@1.6.1: {}
snake-case@3.0.4:
@@ -11395,7 +11553,8 @@ snapshots:
get-east-asian-width: 1.5.0
strip-ansi: 7.2.0
string.prototype.codepointat@0.2.1: {}
string.prototype.codepointat@0.2.1:
optional: true
string_decoder@1.1.1:
dependencies:
@@ -11590,6 +11749,7 @@ snapshots:
dependencies:
pako: 0.2.9
tiny-inflate: 1.0.3
optional: true
unicorn-magic@0.3.0: {}
@@ -12023,7 +12183,8 @@ snapshots:
yocto-queue@0.1.0: {}
yoga-layout@3.2.1: {}
yoga-layout@3.2.1:
optional: true
youch-core@0.3.3:
dependencies:

View File

@@ -0,0 +1,23 @@
fragment AuthUser on User {
id
email @nonNull
roles @nonNull {
nodes {
name @nonNull
}
}
}
fragment AuthPayload on LoginPayload {
authToken
refreshToken
user {
...AuthUser
}
}
mutation AuthLogin($username: String!, $password: String!) {
login(input: { provider: PASSWORD, credentials: { username: $username, password: $password } }) {
...AuthPayload
}
}

View File

@@ -0,0 +1,27 @@
export default defineEventHandler(async (event) => {
try {
// Validate the request body against the schema
const variables = authLoginFormSchema.parse(await readBody(event));
// Execute the GraphQL operation to authenticate the user
const { data, error } = await executeSchemaOperation(event, {
operationName: "AuthLogin",
variables,
});
// Handle errors and validate the response data
if (error) throw error;
if (!data?.login) throw new Error("Identifiants invalides. Veuillez réessayer.");
// Handle the login process by setting the session data
if (!(await handleLogin(event, data.login))) {
throw new Error("Une erreur est survenue lors de la connexion.");
}
return { success: true, message: "Connexion réussie" };
} catch (error) {
const message =
error instanceof Error ? error.message : "Une erreur est survenue lors de la connexion.";
return { success: false, message };
}
});

View File

@@ -0,0 +1,13 @@
import { defineEventHandler } from "h3";
export default defineEventHandler(async (event) => {
try {
await handleLogout(event);
return { success: true, message: "Déconnexion réussie" };
} catch (error) {
const message =
error instanceof Error ? error.message : "Une erreur est survenue lors de la déconnexion.";
return { success: false, message };
}
});

View File

@@ -0,0 +1,3 @@
export default defineGraphQLContext(async (event) => {
return {};
});

View File

@@ -0,0 +1,49 @@
import type { AuthPayloadFragment, AuthUserFragment } from "#graphql/types";
import type { H3Event } from "h3";
/**
* Handle user login by setting the session data with the provided authentication information.
*
* @param event The H3 event object.
* @param payload The authentication payload containing user and token information.
* @return A promise that resolves to true if the login was successful, or false if there was an error.
*/
export async function handleLogin(
event: H3Event,
{ user, authToken, refreshToken }: AuthPayloadFragment,
) {
if (!user || !authToken || !refreshToken) {
return false;
}
await setUserSession(event, {
user: getAuthUser(user),
secure: { authToken, refreshToken },
loggedInAt: new Date().toISOString(),
});
return true;
}
/**
* Handle user logout by clearing the session data.
*
* @param event The H3 event object.
* @returns A promise that resolves when the session has been cleared.
*/
export async function handleLogout(event: H3Event) {
await clearUserSession(event);
}
/**
* Convert the AuthUserFragment to a User object expected by nuxt-auth-utils
*
* @param user The AuthUserFragment containing user data from the GraphQL response
* @returns A User object with the expected structure for nuxt-auth-utils, including an array of role names
*/
function getAuthUser(user: AuthUserFragment) {
return {
...user,
roles: user.roles.nodes.map(({ name }) => name) || [],
};
}

View File

@@ -0,0 +1,18 @@
declare module "#auth-utils" {
interface User {
id: string;
email: string;
roles: string[];
}
interface UserSession {
loggedInAt: string;
}
interface SecureSessionData {
authToken: string;
refreshToken: string;
}
}
export {};

View File

@@ -0,0 +1,7 @@
import * as z from "zod";
export const authLoginFormSchema = z.object({
username: z.email("Courriel invalide"),
password: z.string("Veuillez saisir votre mot de passe"),
});
export type AuthLoginForm = z.infer<typeof authLoginFormSchema>;