feat(Login): add example jwt token
This commit is contained in:
42
src/components/Login.vue
Normal file
42
src/components/Login.vue
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import AuthService from '@/services/auth';
|
||||||
|
import { inject } from 'vue';
|
||||||
|
|
||||||
|
const username = ref('');
|
||||||
|
const password = ref('');
|
||||||
|
const error = ref('');
|
||||||
|
|
||||||
|
async function login() {
|
||||||
|
try {
|
||||||
|
await AuthService.login({ username: username.value, password: password.value });
|
||||||
|
// opcional: redirigir al dashboard
|
||||||
|
} catch (e) {
|
||||||
|
error.value = e.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ejemplo de llamada a clientes (requiere token)
|
||||||
|
const api = inject('api');
|
||||||
|
|
||||||
|
async function loadCustomers() {
|
||||||
|
try {
|
||||||
|
const data = await api.getCustomers();
|
||||||
|
console.log(data);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<h1>Login</h1>
|
||||||
|
<v-form @submit.prevent="login">
|
||||||
|
<v-text-field v-model="username" label="Usuario" required />
|
||||||
|
<v-text-field v-model="password" label="Contraseña" type="password" required />
|
||||||
|
<v-btn type="submit">Entrar</v-btn>
|
||||||
|
<v-alert v-if="error" type="error">{{ error }}</v-alert>
|
||||||
|
</v-form>
|
||||||
|
|
||||||
|
<v-btn @click="loadCustomers">Cargar clientes</v-btn>
|
||||||
|
</template>
|
||||||
7
src/pages/autenticarse.vue
Normal file
7
src/pages/autenticarse.vue
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<template>
|
||||||
|
<Login />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
//
|
||||||
|
</script>
|
||||||
7
src/pages/login.vue
Normal file
7
src/pages/login.vue
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<template>
|
||||||
|
<Login />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
//
|
||||||
|
</script>
|
||||||
49
src/services/auth.js
Normal file
49
src/services/auth.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
class AuthService {
|
||||||
|
static TOKEN_KEY = 'access_token';
|
||||||
|
static REFRESH_KEY = 'refresh_token';
|
||||||
|
|
||||||
|
static login(credentials) {
|
||||||
|
const url = `${import.meta.env.VITE_DJANGO_BASE_URL}/api/token/`;
|
||||||
|
return fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(credentials),
|
||||||
|
})
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(data => {
|
||||||
|
localStorage.setItem(this.TOKEN_KEY, data.access);
|
||||||
|
localStorage.setItem(this.REFRESH_KEY, data.refresh);
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static getAccessToken() {
|
||||||
|
return localStorage.getItem(this.TOKEN_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
static getRefreshToken() {
|
||||||
|
return localStorage.getItem(this.REFRESH_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async refresh() {
|
||||||
|
const refresh = this.getRefreshToken();
|
||||||
|
if (!refresh) throw new Error('No refresh token');
|
||||||
|
|
||||||
|
const url = `${import.meta.env.VITE_DJANGO_BASE_URL}/api/token/refresh/`;
|
||||||
|
const resp = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ refresh }),
|
||||||
|
});
|
||||||
|
const data = await resp.json();
|
||||||
|
localStorage.setItem(this.TOKEN_KEY, data.access);
|
||||||
|
return data.access;
|
||||||
|
}
|
||||||
|
|
||||||
|
static logout() {
|
||||||
|
localStorage.removeItem(this.TOKEN_KEY);
|
||||||
|
localStorage.removeItem(this.REFRESH_KEY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AuthService;
|
||||||
@@ -1,8 +1,38 @@
|
|||||||
|
import AuthService from '@/services/auth';
|
||||||
|
|
||||||
class DjangoApi {
|
class DjangoApi {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.base = import.meta.env.VITE_DJANGO_BASE_URL;
|
this.base = import.meta.env.VITE_DJANGO_BASE_URL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_authHeaders() {
|
||||||
|
const token = AuthService.getAccessToken();
|
||||||
|
return token ? { Authorization: `Bearer ${token}` } : {};
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleResponse(response) {
|
||||||
|
if (!response.ok) {
|
||||||
|
// Si el token ha expirado (401) intentamos refrescar y re‑intentar
|
||||||
|
if (response.status === 401) {
|
||||||
|
return AuthService.refresh().then(newToken => {
|
||||||
|
// volver a ejecutar la petición original con el nuevo token
|
||||||
|
const retryHeaders = {
|
||||||
|
...response.headers,
|
||||||
|
Authorization: `Bearer ${newToken}`,
|
||||||
|
};
|
||||||
|
// aquí usamos fetch de nuevo con los mismos parámetros
|
||||||
|
// (para simplificar, delegamos a getRequest/postRequest)
|
||||||
|
// En la práctica, extrae la lógica a una función reutilizable.
|
||||||
|
throw new Error('Retry logic should be implemented here');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return response.json().then(err => {
|
||||||
|
throw new Error(`Error ${response.status}: ${err.detail || response.statusText}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
getCustomers() {
|
getCustomers() {
|
||||||
const url = this.base + '/don_confiao/api/customers/';
|
const url = this.base + '/don_confiao/api/customers/';
|
||||||
return this.getRequest(url);
|
return this.getRequest(url);
|
||||||
@@ -79,42 +109,23 @@ class DjangoApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getRequest(url) {
|
getRequest(url) {
|
||||||
return new Promise ((resolve, reject) => {
|
return fetch(url, {
|
||||||
fetch(url)
|
headers: {
|
||||||
.then(response => response.json())
|
'Content-Type': 'application/json',
|
||||||
.then(data => {
|
...this._authHeaders(),
|
||||||
resolve(data);
|
},
|
||||||
})
|
}).then(this._handleResponse);
|
||||||
.catch(error => {
|
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
postRequest(url, content) {
|
postRequest(url, content) {
|
||||||
return new Promise((resolve, reject) => {
|
return fetch(url, {
|
||||||
fetch(url, {
|
method: 'POST',
|
||||||
method: 'POST',
|
headers: {
|
||||||
headers: {
|
'Content-Type': 'application/json',
|
||||||
'Content-Type': 'application/json',
|
...this._authHeaders(),
|
||||||
},
|
},
|
||||||
body: JSON.stringify(content)
|
body: JSON.stringify(content),
|
||||||
})
|
}).then(this._handleResponse);
|
||||||
.then(response => {
|
|
||||||
if (!response.ok) {
|
|
||||||
reject(new Error(`Error ${response.status}: ${response.statusText}`));
|
|
||||||
} else {
|
|
||||||
response.json().then(data => {
|
|
||||||
if (!data) {
|
|
||||||
reject(new Error('La respuesta no es un JSON válido'));
|
|
||||||
} else {
|
|
||||||
resolve(data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => reject(error));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,16 @@ import Vuetify, { transformAssetUrls } from 'vite-plugin-vuetify'
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import { fileURLToPath, URL } from 'node:url'
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
|
const aliasMap = {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('🔧 Alias configurado:');
|
||||||
|
console.log(aliasMap); // <-- se muestra en la terminal al iniciar Vite
|
||||||
|
console.log('Resolución real de "@":', aliasMap['@']);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
@@ -50,7 +60,7 @@ export default defineConfig({
|
|||||||
} },
|
} },
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||||
},
|
},
|
||||||
extensions: [
|
extensions: [
|
||||||
'.js',
|
'.js',
|
||||||
|
|||||||
Reference in New Issue
Block a user