add auth via uuid only
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { i18n, t } from '../i18n.js';
|
||||
import { router } from '../router.js';
|
||||
import { auth } from '../services/auth.js';
|
||||
|
||||
class AppHeader extends HTMLElement {
|
||||
constructor() {
|
||||
@@ -14,11 +15,18 @@ class AppHeader extends HTMLElement {
|
||||
this.setupEventListeners();
|
||||
document.addEventListener('click', this.handleOutsideClick);
|
||||
document.addEventListener('keydown', this.handleKeydown);
|
||||
|
||||
// Subscribe to auth changes (only once!)
|
||||
this.authUnsubscribe = auth.subscribe(() => {
|
||||
this.render();
|
||||
this.setupEventListeners();
|
||||
});
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
document.removeEventListener('click', this.handleOutsideClick);
|
||||
document.removeEventListener('keydown', this.handleKeydown);
|
||||
if (this.authUnsubscribe) this.authUnsubscribe();
|
||||
}
|
||||
|
||||
handleOutsideClick() {
|
||||
@@ -110,15 +118,15 @@ class AppHeader extends HTMLElement {
|
||||
|
||||
<div class="dropdown" id="lang-dropdown">
|
||||
<button
|
||||
class="btn btn-outline"
|
||||
class="btn btn-icon btn-outline"
|
||||
id="lang-toggle"
|
||||
aria-haspopup="listbox"
|
||||
aria-expanded="${this.langDropdownOpen}"
|
||||
aria-label="${t('header.selectLanguage')}"
|
||||
title="${t('header.selectLanguage')}"
|
||||
>
|
||||
<span id="current-lang">${i18n.getLocale().toUpperCase()}</span>
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
<svg width="20" height="20" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M8 .25a7.77 7.77 0 0 1 7.75 7.78 7.75 7.75 0 0 1-7.52 7.72h-.25A7.75 7.75 0 0 1 .25 8.24v-.25A7.75 7.75 0 0 1 8 .25zm1.95 8.5h-3.9c.15 2.9 1.17 5.34 1.88 5.5H8c.68 0 1.72-2.37 1.93-5.23zm4.26 0h-2.76c-.09 1.96-.53 3.78-1.18 5.08A6.26 6.26 0 0 0 14.17 9zm-9.67 0H1.8a6.26 6.26 0 0 0 3.94 5.08 12.59 12.59 0 0 1-1.16-4.7l-.03-.38zm1.2-6.58-.12.05a6.26 6.26 0 0 0-3.83 5.03h2.75c.09-1.83.48-3.54 1.06-4.81zm2.25-.42c-.7 0-1.78 2.51-1.94 5.5h3.9c-.15-2.9-1.18-5.34-1.89-5.5h-.07zm2.28.43.03.05a12.95 12.95 0 0 1 1.15 5.02h2.75a6.28 6.28 0 0 0-3.93-5.07z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="dropdown-menu" role="listbox" aria-label="${t('header.selectLanguage')}">
|
||||
@@ -134,6 +142,24 @@ class AppHeader extends HTMLElement {
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${auth.isLoggedIn() ? `
|
||||
<button class="btn btn-outline btn-profile" id="profile-btn" title="${t('header.profile')}">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
|
||||
<circle cx="12" cy="7" r="4"></circle>
|
||||
</svg>
|
||||
<span class="btn-profile-text">${t('header.profile')}</span>
|
||||
</button>
|
||||
` : `
|
||||
<button class="btn btn-outline btn-login" id="login-btn" title="${t('auth.login')}">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
|
||||
<circle cx="12" cy="7" r="4"></circle>
|
||||
</svg>
|
||||
<span class="btn-login-text">${t('auth.login')}</span>
|
||||
</button>
|
||||
`}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -164,6 +190,21 @@ class AppHeader extends HTMLElement {
|
||||
});
|
||||
});
|
||||
|
||||
// Login button
|
||||
const loginBtn = this.querySelector('#login-btn');
|
||||
loginBtn?.addEventListener('click', () => {
|
||||
const authModal = document.querySelector('auth-modal');
|
||||
if (authModal) {
|
||||
authModal.show('login');
|
||||
}
|
||||
});
|
||||
|
||||
// Profile button
|
||||
const profileBtn = this.querySelector('#profile-btn');
|
||||
profileBtn?.addEventListener('click', () => {
|
||||
router.navigate('/profile');
|
||||
});
|
||||
|
||||
this.updateThemeIcon();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { router } from '../router.js';
|
||||
import { i18n } from '../i18n.js';
|
||||
import { auth } from '../services/auth.js';
|
||||
import './app-header.js';
|
||||
import './app-footer.js';
|
||||
import './auth-modal.js';
|
||||
import './pages/page-home.js';
|
||||
import './pages/page-search.js';
|
||||
import './pages/page-listing.js';
|
||||
@@ -29,9 +31,13 @@ class AppShell extends HTMLElement {
|
||||
<app-header></app-header>
|
||||
<main class="container" id="router-outlet"></main>
|
||||
<app-footer></app-footer>
|
||||
<auth-modal hidden></auth-modal>
|
||||
`;
|
||||
|
||||
this.main = this.querySelector('#router-outlet');
|
||||
|
||||
// Try to restore session
|
||||
auth.tryRestoreSession();
|
||||
}
|
||||
|
||||
setupRouter() {
|
||||
|
||||
@@ -20,7 +20,17 @@ class AuthService {
|
||||
* @returns {string} UUID v4
|
||||
*/
|
||||
generateUuid() {
|
||||
return crypto.randomUUID();
|
||||
// Use native if available (secure contexts)
|
||||
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
|
||||
// Fallback for non-secure contexts
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
const r = Math.random() * 16 | 0;
|
||||
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -43,8 +53,18 @@ class AuthService {
|
||||
try {
|
||||
await directus.register(email, uuid);
|
||||
|
||||
// Auto-login after registration
|
||||
await this.login(uuid);
|
||||
// Try auto-login (may fail if verification required)
|
||||
const loginResult = await this.login(uuid);
|
||||
|
||||
if (!loginResult.success) {
|
||||
// Registration worked but login failed (verification pending)
|
||||
return {
|
||||
uuid,
|
||||
success: true,
|
||||
pendingVerification: true,
|
||||
message: 'Account created. Login may require activation.'
|
||||
};
|
||||
}
|
||||
|
||||
return { uuid, success: true };
|
||||
} catch (error) {
|
||||
@@ -69,8 +89,6 @@ class AuthService {
|
||||
await directus.login(email, uuid);
|
||||
this.currentUser = await directus.getCurrentUser();
|
||||
this.notifyListeners();
|
||||
|
||||
// Store UUID for convenience (optional)
|
||||
this.storeUuid(uuid);
|
||||
|
||||
return { success: true };
|
||||
|
||||
@@ -100,12 +100,15 @@ class DirectusService {
|
||||
headers
|
||||
});
|
||||
|
||||
// Token abgelaufen - versuche Refresh
|
||||
if (response.status === 401 && this.refreshToken) {
|
||||
// Token abgelaufen - versuche Refresh (aber nicht für auth-Endpoints)
|
||||
if (response.status === 401 && this.refreshToken && !endpoint.startsWith('/auth/')) {
|
||||
const refreshed = await this.refreshSession();
|
||||
if (refreshed) {
|
||||
headers['Authorization'] = `Bearer ${this.accessToken}`;
|
||||
return this.request(endpoint, options);
|
||||
} else {
|
||||
// Refresh failed - clear tokens to prevent loops
|
||||
this.clearTokens();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,13 +219,9 @@ class DirectusService {
|
||||
return false;
|
||||
}
|
||||
|
||||
async register(email, password, userData = {}) {
|
||||
return this.post('/users', {
|
||||
email,
|
||||
password,
|
||||
...userData,
|
||||
role: null // Wird durch Directus Flow/Policy gesetzt
|
||||
});
|
||||
async register(email, password) {
|
||||
// Public registration (no verification required)
|
||||
return this.post('/users/register', { email, password });
|
||||
}
|
||||
|
||||
async requestPasswordReset(email) {
|
||||
|
||||
Reference in New Issue
Block a user