add auth via uuid only

This commit is contained in:
2026-01-31 15:25:33 +01:00
parent 57020a8913
commit f919079f69
9 changed files with 113 additions and 22 deletions

View File

@@ -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();
}

View File

@@ -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() {

View File

@@ -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 };

View File

@@ -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) {