feat (auth): server side proxy login request

This commit is contained in:
Abizrh
2025-08-07 16:29:06 +07:00
parent d9bd19baaf
commit 2ecabaffb2
6 changed files with 101 additions and 40 deletions
+24 -5
View File
@@ -1,22 +1,41 @@
<script setup lang="ts">
// import PasswordInput from '~/components/PasswordInput.vue'
import { Loader2 } from 'lucide-vue-next'
const email = ref('demo@gmail.com')
const password = ref('password')
const isLoading = ref(false)
function onSubmit(event: Event) {
async function onSubmit(event: Event) {
event.preventDefault()
if (!email.value || !password.value) return
isLoading.value = true
setTimeout(() => {
if (email.value === 'demo@gmail.com' && password.value === 'password') navigateTo('/')
try {
const { data: respData } = await useFetch('/api/v1/authentication/login', {
method: 'POST',
body: JSON.stringify({
name: 'system',
password: 'the-SYSTEM-1234',
}),
})
const resp = respData.value
if (!resp) throw new Error('No response')
const { data: rawdata, meta } = resp
console.log('DATA', rawdata)
console.log('META', meta)
if (meta.status === 'verified') {
await nextTick()
navigateTo('/')
}
} catch (error) {
console.error('Login failed:', error)
} finally {
isLoading.value = false
}, 3000)
}
}
</script>
+23 -21
View File
@@ -1,5 +1,5 @@
import antfu from "@antfu/eslint-config";
import withNuxt from "./.nuxt/eslint.config.mjs";
import antfu from '@antfu/eslint-config'
import withNuxt from './.nuxt/eslint.config.mjs'
export default withNuxt(
antfu(
@@ -9,7 +9,7 @@ export default withNuxt(
// Relaxed stylistic options
stylistic: {
semi: false,
quotes: "single",
quotes: 'single',
// Less strict formatting
jsx: false,
},
@@ -17,34 +17,36 @@ export default withNuxt(
{
rules: {
// Basic rules
quotes: ["error", "single", { avoidEscape: true }],
"style/no-trailing-spaces": ["error", { ignoreComments: true }],
quotes: ['error', 'single', { avoidEscape: true }],
'style/no-trailing-spaces': ['error', { ignoreComments: true }],
'no-console': 'off',
// Relax strict formatting rules
"style/brace-style": "off", // Allow inline if
curly: ["error", "multi-line"], // Only require braces for multi-line
"style/arrow-parens": "off",
'style/brace-style': 'off', // Allow inline if
curly: ['error', 'multi-line'], // Only require braces for multi-line
'style/arrow-parens': 'off',
// UnoCSS - make it warning instead of error, or disable completely
"unocss/order": "off", // atau 'off' untuk disable
'unocss/order': 'off', // atau 'off' untuk disable
// Vue specific - relax template formatting
"vue/html-indent": "off",
"vue/max-attributes-per-line": "off",
"vue/singleline-html-element-content-newline": "off",
"vue/multiline-html-element-content-newline": "off",
"vue/html-self-closing": "off",
'vue/html-indent': 'off',
'vue/max-attributes-per-line': 'off',
'vue/singleline-html-element-content-newline': 'off',
'vue/multiline-html-element-content-newline': 'off',
'vue/html-self-closing': 'off',
// Allow more flexible code style
"style/max-statements-per-line": ["error", { max: 3 }],
"antfu/if-newline": "off", // Disable newline after if requirement
'style/max-statements-per-line': ['error', { max: 3 }],
'antfu/if-newline': 'off', // Disable newline after if requirement
},
},
{
files: ["**/*.md"],
files: ['**/*.md'],
rules: {
"style/no-trailing-spaces": "off",
'style/no-trailing-spaces': 'off',
},
},
),
);
}
)
)
+3
View File
@@ -1,6 +1,9 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
devtools: { enabled: true },
runtimeConfig: {
API_ORIGIN: process.env.API_ORIGIN || 'https://main-api.dev-hopis.sabbi.id',
},
modules: [
'@unocss/nuxt',
'shadcn-nuxt',
+5 -4
View File
@@ -19,14 +19,12 @@
"@unovis/ts": "^1.5.1",
"@unovis/vue": "^1.5.1",
"embla-carousel": "^8.5.2",
"embla-carousel-vue": "^8.5.2"
"embla-carousel-vue": "^8.5.2",
"h3": "^1.15.4"
},
"devDependencies": {
"@antfu/eslint-config": "^4.10.1",
"@nuxt/eslint": "^1.2.0",
"prettier": "^3.6.2",
"prettier-plugin-tailwindcss": "^0.5.14",
"eslint-config-prettier": "^9.1.0",
"@nuxt/icon": "^1.11.0",
"@nuxtjs/color-mode": "^3.5.2",
"@pinia/nuxt": "^0.5.1",
@@ -39,9 +37,12 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"eslint": "^9.22.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-format": "^1.0.1",
"lucide-vue-next": "^0.482.0",
"nuxt": "^4.0.1",
"prettier": "^3.6.2",
"prettier-plugin-tailwindcss": "^0.5.14",
"radix-vue": "^1.9.17",
"shadcn-nuxt": "^0.10.4",
"tailwind-merge": "^2.6.0",
+3 -10
View File
@@ -26,6 +26,9 @@ dependencies:
embla-carousel-vue:
specifier: ^8.5.2
version: 8.6.0(vue@3.5.18)
h3:
specifier: ^1.15.4
version: 1.15.4
devDependencies:
'@antfu/eslint-config':
@@ -5279,7 +5282,6 @@ packages:
/cookie-es@1.2.2:
resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==}
dev: true
/cookie-es@2.0.0:
resolution: {integrity: sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==}
@@ -5365,7 +5367,6 @@ packages:
resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==}
dependencies:
uncrypto: 0.1.3
dev: true
/css-declaration-sorter@7.2.0(postcss@8.5.6):
resolution: {integrity: sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==}
@@ -5865,7 +5866,6 @@ packages:
/defu@6.1.4:
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
dev: true
/delaunator@5.0.1:
resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==}
@@ -5890,7 +5890,6 @@ packages:
/destr@2.0.5:
resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==}
dev: true
/detect-libc@1.0.3:
resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
@@ -7320,7 +7319,6 @@ packages:
radix3: 1.1.2
ufo: 1.6.1
uncrypto: 0.1.3
dev: true
/has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
@@ -7486,7 +7484,6 @@ packages:
/iron-webcrypto@1.2.1:
resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==}
dev: true
/is-arrayish@0.2.1:
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
@@ -8817,7 +8814,6 @@ packages:
/node-mock-http@1.0.2:
resolution: {integrity: sha512-zWaamgDUdo9SSLw47we78+zYw/bDr5gH8pH7oRRs8V3KmBtu8GLgGIbV2p/gRPd3LWpEOpjQj7X1FOU3VFMJ8g==}
dev: true
/node-releases@2.0.19:
resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
@@ -9945,7 +9941,6 @@ packages:
/radix3@1.1.2:
resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==}
dev: true
/randombytes@2.1.0:
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
@@ -10883,7 +10878,6 @@ packages:
/ufo@1.6.1:
resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==}
dev: true
/ultrahtml@1.6.0:
resolution: {integrity: sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==}
@@ -10900,7 +10894,6 @@ packages:
/uncrypto@0.1.3:
resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==}
dev: true
/unctx@2.4.1:
resolution: {integrity: sha512-AbaYw0Nm4mK4qjhns67C+kgxR2YWiwlDBPzxrN8h8C6VtAdCgditAY5Dezu3IJy4XVqAnbrXt9oQJvsn3fyozg==}
@@ -0,0 +1,43 @@
import { useRuntimeConfig } from '#imports'
import { getRequestURL, readBody, setCookie } from 'h3'
export default defineEventHandler(async (event) => {
const body = await readBody(event)
const url = getRequestURL(event)
const config = useRuntimeConfig()
const apiOrigin = config.API_ORIGIN
const externalUrl = apiOrigin + url.pathname.replace(/^\/api/, '') + url.search
const resp = await fetch(externalUrl, {
method: 'POST',
body: JSON.stringify(body),
headers: {
'Content-Type': 'application/json',
},
})
if (resp.status === 200) {
const data = await resp.json()
if (data?.data?.accessToken) {
setCookie(event, 'authentication', data.data.accessToken, {
path: '/',
httpOnly: true,
sameSite: 'strict',
maxAge: 60 * 60 * 24,
})
delete data.data.accessToken
return data
}
}
return new Response(await resp.text(), {
status: resp.status,
headers: {
'Content-Type': resp.headers.get('content-type') || 'text/plain',
},
})
})