Integrate shared JWT authentication into data-entry-app: - Add Oracle pool initialization for auth service - Add AuthenticationMiddleware to protect API routes - Update all receipt endpoints to use CurrentUser from JWT - Add shared auth router (/api/auth/login, /api/auth/refresh) Add nomenclature synchronization feature: - Create SQLite models for synced suppliers, local suppliers, and cash registers - Add nomenclature router with sync triggers and CRUD endpoints - Add sync service for Oracle → SQLite nomenclature data - Update nomenclature_service to use synced SQLite data with fallbacks Create shared frontend components: - Add shared/frontend/ with LoginView.vue, auth store factory, login.css - Integrate shared login and auth into data-entry-app frontend - Add axios-based API service with token refresh interceptor 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
134 lines
3.7 KiB
JavaScript
134 lines
3.7 KiB
JavaScript
/**
|
|
* Shared Auth Store Factory
|
|
*
|
|
* Creates a Pinia auth store that can be used by any ROA2WEB application.
|
|
* Each app passes its own apiService instance configured with the correct baseURL.
|
|
*
|
|
* Usage:
|
|
* import { createAuthStore } from '@shared/frontend/stores/auth';
|
|
* import { apiService } from '../services/api';
|
|
* export const useAuthStore = createAuthStore(apiService);
|
|
*/
|
|
|
|
import { defineStore } from "pinia";
|
|
import { ref, computed } from "vue";
|
|
|
|
/**
|
|
* Factory function to create an auth store with the provided API service
|
|
* @param {Object} apiService - Axios instance configured for the app's API
|
|
* @returns {Function} Pinia store definition
|
|
*/
|
|
export function createAuthStore(apiService) {
|
|
return defineStore("auth", () => {
|
|
// State
|
|
const accessToken = ref(localStorage.getItem("access_token"));
|
|
const refreshToken = ref(localStorage.getItem("refresh_token"));
|
|
const user = ref(JSON.parse(localStorage.getItem("user") || "null"));
|
|
const isLoading = ref(false);
|
|
const error = ref(null);
|
|
|
|
// Getters
|
|
const isAuthenticated = computed(() => !!accessToken.value);
|
|
const currentUser = computed(() => user.value);
|
|
|
|
// Actions
|
|
const login = async (credentials) => {
|
|
isLoading.value = true;
|
|
error.value = null;
|
|
|
|
try {
|
|
const response = await apiService.post("/auth/login", {
|
|
username: credentials.username,
|
|
password: credentials.password,
|
|
});
|
|
const { access_token, refresh_token, user: userData } = response.data;
|
|
|
|
accessToken.value = access_token;
|
|
refreshToken.value = refresh_token;
|
|
user.value = userData;
|
|
|
|
localStorage.setItem("access_token", access_token);
|
|
localStorage.setItem("refresh_token", refresh_token);
|
|
localStorage.setItem("user", JSON.stringify(userData));
|
|
|
|
apiService.defaults.headers.common["Authorization"] = `Bearer ${access_token}`;
|
|
|
|
return { success: true };
|
|
} catch (err) {
|
|
error.value = err.response?.data?.detail || "Login failed";
|
|
return { success: false, error: error.value };
|
|
} finally {
|
|
isLoading.value = false;
|
|
}
|
|
};
|
|
|
|
const logout = () => {
|
|
accessToken.value = null;
|
|
refreshToken.value = null;
|
|
user.value = null;
|
|
error.value = null;
|
|
|
|
localStorage.removeItem("access_token");
|
|
localStorage.removeItem("refresh_token");
|
|
localStorage.removeItem("user");
|
|
|
|
delete apiService.defaults.headers.common["Authorization"];
|
|
};
|
|
|
|
const refreshAccessToken = async () => {
|
|
if (!refreshToken.value) {
|
|
logout();
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
const response = await apiService.post("/auth/refresh", {
|
|
refresh_token: refreshToken.value,
|
|
});
|
|
|
|
const { access_token } = response.data;
|
|
accessToken.value = access_token;
|
|
localStorage.setItem("access_token", access_token);
|
|
apiService.defaults.headers.common["Authorization"] = `Bearer ${access_token}`;
|
|
|
|
return true;
|
|
} catch (err) {
|
|
console.error("Token refresh failed:", err);
|
|
logout();
|
|
return false;
|
|
}
|
|
};
|
|
|
|
const initializeAuth = () => {
|
|
if (accessToken.value) {
|
|
apiService.defaults.headers.common["Authorization"] = `Bearer ${accessToken.value}`;
|
|
}
|
|
};
|
|
|
|
const clearError = () => {
|
|
error.value = null;
|
|
};
|
|
|
|
// Initialize on store creation
|
|
initializeAuth();
|
|
|
|
return {
|
|
// State
|
|
accessToken,
|
|
refreshToken,
|
|
user,
|
|
isLoading,
|
|
error,
|
|
// Getters
|
|
isAuthenticated,
|
|
currentUser,
|
|
// Actions
|
|
login,
|
|
logout,
|
|
refreshAccessToken,
|
|
initializeAuth,
|
|
clearError,
|
|
};
|
|
});
|
|
}
|