feat: Mini-Cloud Plattform - komplette Implementierung Phase 0-8
Selbstgehostete Web-Cloud mit Dateiverwaltung, Kalender, Kontakte, Email-Webclient, Office-Viewer und Passwort-Manager. Backend (Flask/Python): - JWT-Auth mit Access/Refresh Tokens, Benutzerverwaltung - Dateien: Upload/Download, Ordner, Berechtigungen, Share-Links - Kalender: CRUD, Teilen, iCal-Export, CalDAV well-known URLs - Kontakte: Adressbuecher, vCard-Export, Teilen - Email: IMAP/SMTP-Proxy, Multi-Account - Office-Viewer: DOCX/XLSX/PPTX/PDF Vorschau - Passwort-Manager: AES-256-GCM clientseitig, KeePass-Import - Sync-API fuer Desktop/Mobile-Clients - SQLite mit WAL-Modus Frontend (Vue 3 + PrimeVue): - Datei-Explorer mit Breadcrumbs und Share-Dialogen - Monatskalender mit Event-Verwaltung - Kontaktliste mit Adressbuch-Sidebar - Email-Client mit 3-Spalten-Layout - Passwort-Manager mit TOTP und Passwort-Generator - Admin-Panel, Settings, oeffentliche Share-Seite Docker: Multi-Stage Build, Bind Mounts (keine Volumes) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
import axios from 'axios'
|
||||
import { useAuthStore } from '../stores/auth'
|
||||
import router from '../router'
|
||||
|
||||
const apiClient = axios.create({
|
||||
baseURL: '/api',
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
// Request interceptor: attach access token
|
||||
apiClient.interceptors.request.use((config) => {
|
||||
const auth = useAuthStore()
|
||||
if (auth.accessToken) {
|
||||
config.headers.Authorization = `Bearer ${auth.accessToken}`
|
||||
}
|
||||
return config
|
||||
})
|
||||
|
||||
// Response interceptor: handle 401 with token refresh
|
||||
let isRefreshing = false
|
||||
let failedQueue = []
|
||||
|
||||
const processQueue = (error, token = null) => {
|
||||
failedQueue.forEach((prom) => {
|
||||
if (error) {
|
||||
prom.reject(error)
|
||||
} else {
|
||||
prom.resolve(token)
|
||||
}
|
||||
})
|
||||
failedQueue = []
|
||||
}
|
||||
|
||||
apiClient.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (error) => {
|
||||
const originalRequest = error.config
|
||||
|
||||
if (error.response?.status === 401 && !originalRequest._retry) {
|
||||
if (originalRequest.url === '/auth/refresh' || originalRequest.url === '/auth/login') {
|
||||
const auth = useAuthStore()
|
||||
auth.logout()
|
||||
router.push('/login')
|
||||
return Promise.reject(error)
|
||||
}
|
||||
|
||||
if (isRefreshing) {
|
||||
return new Promise((resolve, reject) => {
|
||||
failedQueue.push({ resolve, reject })
|
||||
}).then((token) => {
|
||||
originalRequest.headers.Authorization = `Bearer ${token}`
|
||||
return apiClient(originalRequest)
|
||||
})
|
||||
}
|
||||
|
||||
originalRequest._retry = true
|
||||
isRefreshing = true
|
||||
|
||||
try {
|
||||
const auth = useAuthStore()
|
||||
const newToken = await auth.refreshToken()
|
||||
processQueue(null, newToken)
|
||||
originalRequest.headers.Authorization = `Bearer ${newToken}`
|
||||
return apiClient(originalRequest)
|
||||
} catch (refreshError) {
|
||||
processQueue(refreshError, null)
|
||||
const auth = useAuthStore()
|
||||
auth.logout()
|
||||
router.push('/login')
|
||||
return Promise.reject(refreshError)
|
||||
} finally {
|
||||
isRefreshing = false
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
export default apiClient
|
||||
Reference in New Issue
Block a user