From f919079f69abf8bc90452fa1a86438747a6adf19 Mon Sep 17 00:00:00 2001 From: Alexander Schmidt Date: Sat, 31 Jan 2026 15:25:33 +0100 Subject: [PATCH] add auth via uuid only --- css/components.css | 22 +++++++++++++++++ js/components/app-header.js | 49 ++++++++++++++++++++++++++++++++++--- js/components/app-shell.js | 6 +++++ js/services/auth.js | 28 +++++++++++++++++---- js/services/directus.js | 17 ++++++------- locales/de.json | 3 ++- locales/en.json | 3 ++- locales/fr.json | 3 ++- service-worker.js | 4 ++- 9 files changed, 113 insertions(+), 22 deletions(-) diff --git a/css/components.css b/css/components.css index f2cc099..7401f7e 100644 --- a/css/components.css +++ b/css/components.css @@ -261,6 +261,17 @@ app-header .btn-create-text { display: none; } +app-header .btn-login, +app-header .btn-profile { + padding: var(--space-sm); + border-radius: var(--radius-full); +} + +app-header .btn-login-text, +app-header .btn-profile-text { + display: none; +} + @media (min-width: 480px) { app-header .btn-create { padding: var(--space-sm) var(--space-md); @@ -270,6 +281,17 @@ app-header .btn-create-text { app-header .btn-create-text { display: inline; } + + app-header .btn-login, + app-header .btn-profile { + padding: var(--space-sm) var(--space-md); + border-radius: var(--radius-md); + } + + app-header .btn-login-text, + app-header .btn-profile-text { + display: inline; + } } /* Footer Component */ diff --git a/js/components/app-header.js b/js/components/app-header.js index 09ff5b5..da63bcd 100644 --- a/js/components/app-header.js +++ b/js/components/app-header.js @@ -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 { + + ${auth.isLoggedIn() ? ` + + ` : ` + + `} `; @@ -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(); } diff --git a/js/components/app-shell.js b/js/components/app-shell.js index 0dab634..0730bb0 100644 --- a/js/components/app-shell.js +++ b/js/components/app-shell.js @@ -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 {
+ `; this.main = this.querySelector('#router-outlet'); + + // Try to restore session + auth.tryRestoreSession(); } setupRouter() { diff --git a/js/services/auth.js b/js/services/auth.js index 274ce8f..40b4e2d 100644 --- a/js/services/auth.js +++ b/js/services/auth.js @@ -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 }; diff --git a/js/services/directus.js b/js/services/directus.js index 51cdc25..bea1317 100644 --- a/js/services/directus.js +++ b/js/services/directus.js @@ -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) { diff --git a/locales/de.json b/locales/de.json index d62c56e..cf3de17 100644 --- a/locales/de.json +++ b/locales/de.json @@ -3,7 +3,8 @@ "searchPlaceholder": "Was suchst du?", "createListing": "Anzeige erstellen", "toggleTheme": "Design wechseln", - "selectLanguage": "Sprache auswählen" + "selectLanguage": "Sprache auswählen", + "profile": "Profil" }, "footer": { "rights": "Alle Rechte vorbehalten.", diff --git a/locales/en.json b/locales/en.json index 09910b4..5bb4639 100644 --- a/locales/en.json +++ b/locales/en.json @@ -3,7 +3,8 @@ "searchPlaceholder": "What are you looking for?", "createListing": "Create Listing", "toggleTheme": "Toggle theme", - "selectLanguage": "Select language" + "selectLanguage": "Select language", + "profile": "Profile" }, "footer": { "rights": "All rights reserved.", diff --git a/locales/fr.json b/locales/fr.json index dee284d..e94f638 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -3,7 +3,8 @@ "searchPlaceholder": "Que cherchez-vous ?", "createListing": "Créer une annonce", "toggleTheme": "Changer de thème", - "selectLanguage": "Choisir la langue" + "selectLanguage": "Choisir la langue", + "profile": "Profil" }, "footer": { "rights": "Tous droits réservés.", diff --git a/service-worker.js b/service-worker.js index f49264f..b70cad2 100644 --- a/service-worker.js +++ b/service-worker.js @@ -1,4 +1,4 @@ -const CACHE_NAME = 'dgray-v20'; +const CACHE_NAME = 'dgray-v26'; const STATIC_ASSETS = [ '/', '/index.html', @@ -12,6 +12,8 @@ const STATIC_ASSETS = [ '/js/services/api.js', '/js/services/crypto.js', '/js/services/chat.js', + '/js/services/directus.js', + '/js/services/auth.js', '/js/data/mock-listings.js', '/js/components/chat-widget.js', '/js/components/listing-card.js',