feat: Passwordless cross-device authentication

- Arkitektur: docs/auth/passwordless-architecture.md
- Backend: iom/quixzoom-auth-service/ (FastAPI + Redis)
- Webb: quixzoom-market-pages/se/login/ (QR-kod + polling)
- App: iom/quixzoom-app/src/features/auth/ (push + deep links)

Flöde: QR-kod → app-godkännande → webb-inloggad
This commit is contained in:
Bernt
2026-07-07 07:11:50 +00:00
parent 4aa984ad74
commit 6989a98d75
61843 changed files with 5491611 additions and 872231 deletions
+50 -50
View File
@@ -32,7 +32,7 @@
.nav-badge {
font-size: 12px; font-weight: 600; padding: 4px 10px;
background: rgba(10,132,255,0.15); color: var(--ios-blue);
border-radius: 20px;
border-radius: var(--radius-xl);
}
/* Hero */
@@ -53,7 +53,7 @@
.progress-overview {
margin: 0 16px 20px;
background: var(--background-secondary);
border-radius: 16px; overflow: hidden;
border-radius: var(--radius-lg); overflow: hidden;
border: 0.5px solid var(--separator);
padding: 16px;
}
@@ -64,10 +64,10 @@
.progress-label-text { font-size: 14px; color: var(--label-secondary); }
.progress-label-count { font-size: 14px; font-weight: 700; color: var(--ios-blue); }
.progress-track {
height: 8px; background: var(--fill-secondary); border-radius: 4px; overflow: hidden;
height: 8px; background: var(--fill-secondary); border-radius: var(--radius-sm); overflow: hidden;
}
.progress-fill {
height: 100%; border-radius: 4px;
height: 100%; border-radius: var(--radius-sm);
background: linear-gradient(90deg, var(--ios-blue), var(--ios-indigo));
transition: width 0.8s cubic-bezier(0.34,1.56,0.64,1);
}
@@ -75,7 +75,7 @@
display: flex; gap: 8px; margin-top: 12px; flex-wrap: wrap;
}
.progress-chip {
font-size: 12px; font-weight: 500; padding: 4px 10px; border-radius: 12px;
font-size: 12px; font-weight: 500; padding: 4px 10px; border-radius: var(--radius-md);
}
.chip-done { background: rgba(48,209,88,0.15); color: var(--ios-green); }
.chip-todo { background: var(--fill-secondary); color: var(--label-secondary); }
@@ -96,16 +96,16 @@
border: 0.5px solid var(--separator);
margin-bottom: -0.5px;
}
.step-card:first-child { border-radius: 14px 14px 0 0; }
.step-card:first-child { border-radius: var(--radius-lg) 14px 0 0; }
.step-card:last-child { border-radius: 0 0 14px 14px; margin-bottom: 0; }
.step-card:only-child { border-radius: 14px; }
.step-card:only-child { border-radius: var(--radius-lg); }
.step-header {
display: flex; align-items: center; gap: 14px;
padding: 16px; cursor: pointer;
}
.step-number {
width: 32px; height: 32px; border-radius: 50%;
width: 32px; height: 32px; border-radius: var(--radius-full);
display: flex; align-items: center; justify-content: center;
font-size: 14px; font-weight: 700; flex-shrink: 0;
}
@@ -128,17 +128,17 @@
.step-detail-row {
display: flex; gap: 8px; margin-bottom: 8px; align-items: flex-start;
}
.step-detail-dot { margin-top: 5px; width: 5px; height: 5px; border-radius: 50%; background: var(--ios-blue); flex-shrink: 0; }
.step-detail-dot { margin-top: 5px; width: 5px; height: 5px; border-radius: var(--radius-full); background: var(--ios-blue); flex-shrink: 0; }
.step-detail-text { flex: 1; color: var(--label-primary); }
.step-code {
font-family: var(--font-mono); font-size: 13px;
background: var(--fill-secondary); padding: 2px 7px; border-radius: 5px;
background: var(--fill-secondary); padding: 2px 7px; border-radius: var(--radius-sm);
color: var(--ios-teal);
}
.step-cta {
display: inline-flex; align-items: center; gap: 6px;
margin-top: 10px; padding: 9px 16px; border-radius: 10px;
margin-top: 10px; padding: 9px 16px; border-radius: var(--radius-md);
background: rgba(10,132,255,0.12); color: var(--ios-blue);
font-size: 14px; font-weight: 600; text-decoration: none;
transition: opacity 0.12s;
@@ -148,7 +148,7 @@
/* Legal checklist */
.legal-card {
background: var(--background-secondary);
border-radius: 14px; overflow: hidden;
border-radius: var(--radius-lg); overflow: hidden;
border: 0.5px solid var(--separator);
}
.legal-row {
@@ -170,7 +170,7 @@
}
.screenshot-card {
background: var(--background-secondary);
border-radius: 12px; overflow: hidden;
border-radius: var(--radius-md); overflow: hidden;
border: 0.5px solid var(--separator);
padding: 14px 10px; text-align: center;
}
@@ -182,7 +182,7 @@
.info-card {
background: rgba(10,132,255,0.08);
border: 0.5px solid rgba(10,132,255,0.2);
border-radius: 14px; padding: 14px 16px;
border-radius: var(--radius-lg); padding: 14px 16px;
margin-top: 12px;
}
.info-card-title { font-size: 13px; font-weight: 600; color: var(--ios-blue); margin-bottom: 4px; }
@@ -192,7 +192,7 @@
.warn-card {
background: rgba(255,159,10,0.08);
border: 0.5px solid rgba(255,159,10,0.2);
border-radius: 14px; padding: 14px 16px;
border-radius: var(--radius-lg); padding: 14px 16px;
margin: 16px 16px 8px;
}
.warn-card-title { font-size: 13px; font-weight: 600; color: var(--ios-orange); margin-bottom: 4px; }
@@ -247,7 +247,7 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
<!-- Hero -->
<div class="hero">
<div class="hero-icon">📱</div>
<div class="hero-icon"></div>
<div class="hero-title">App Store Submission</div>
<div class="hero-sub">Steg-för-steg guide för quiXzoom<br>iOS App — augusti 2026</div>
</div>
@@ -262,9 +262,9 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
<div class="progress-fill" id="progress-fill" style="width:33%"></div>
</div>
<div class="progress-chips">
<span class="progress-chip chip-done"> Privacy Policy</span>
<span class="progress-chip chip-done"> Support URL</span>
<span class="progress-chip chip-done"> Terms of Service</span>
<span class="progress-chip chip-done"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="9" stroke="#22c55e" stroke-width="2"/><path d="M6 10L9 13L14 7" stroke="#22c55e" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg> Privacy Policy</span>
<span class="progress-chip chip-done"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="9" stroke="#22c55e" stroke-width="2"/><path d="M6 10L9 13L14 7" stroke="#22c55e" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg> Support URL</span>
<span class="progress-chip chip-done"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="9" stroke="#22c55e" stroke-width="2"/><path d="M6 10L9 13L14 7" stroke="#22c55e" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg> Terms of Service</span>
<span class="progress-chip chip-todo">⏳ SwiftUI App</span>
<span class="progress-chip chip-todo">⏳ Screenshots</span>
<span class="progress-chip chip-todo">⏳ App Store Connect</span>
@@ -284,7 +284,7 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
<div class="step-title">App Store Connect konto</div>
<div class="step-desc">Apple Developer Program · $99/år</div>
</div>
<span class="step-check">⚠️</span>
<span class="step-check"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M10 3L17 17H3L10 3Z" stroke="#f59e0b" stroke-width="2"/><path d="M10 8V12M10 14V14.5" stroke="#f59e0b" stroke-width="2" stroke-linecap="round"/></svg></span>
</div>
<div class="step-body" id="step-1">
<div class="step-detail-row">
@@ -300,7 +300,7 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
<div class="step-detail-text">Navigera till "Apps" → klicka "+" → "New App"</div>
</div>
<a class="step-cta" href="https://appstoreconnect.apple.com" target="_blank">
🔗 Öppna App Store Connect
Öppna App Store Connect
</a>
</div>
</div>
@@ -313,7 +313,7 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
<div class="step-title">App ID & Bundle ID</div>
<div class="step-desc">Registrera unik identifierare</div>
</div>
<span class="step-check">⚠️</span>
<span class="step-check"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M10 3L17 17H3L10 3Z" stroke="#f59e0b" stroke-width="2"/><path d="M10 8V12M10 14V14.5" stroke="#f59e0b" stroke-width="2" stroke-linecap="round"/></svg></span>
</div>
<div class="step-body" id="step-2">
<div class="step-detail-row">
@@ -333,7 +333,7 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
<div class="step-detail-text">Capabilities att aktivera: Push Notifications, Sign in with Apple, Location Services</div>
</div>
<div class="info-card">
<div class="info-card-title">💡 Bundle ID</div>
<div class="info-card-title"> Bundle ID</div>
<div class="info-card-body">Bundle ID är permanent och kan inte ändras efter publicering. Använd <code>se.wavult.quixzoom</code> exakt.</div>
</div>
</div>
@@ -347,7 +347,7 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
<div class="step-title">App Metadata</div>
<div class="step-desc">Namn, kategori, beskrivning</div>
</div>
<span class="step-check">⚠️</span>
<span class="step-check"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M10 3L17 17H3L10 3Z" stroke="#f59e0b" stroke-width="2"/><path d="M10 8V12M10 14V14.5" stroke="#f59e0b" stroke-width="2" stroke-linecap="round"/></svg></span>
</div>
<div class="step-body" id="step-3">
<div class="step-detail-row">
@@ -389,22 +389,22 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
<div class="step-title">Screenshots</div>
<div class="step-desc">3 storlekar krävda av Apple</div>
</div>
<span class="step-check">⚠️</span>
<span class="step-check"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M10 3L17 17H3L10 3Z" stroke="#f59e0b" stroke-width="2"/><path d="M10 8V12M10 14V14.5" stroke="#f59e0b" stroke-width="2" stroke-linecap="round"/></svg></span>
</div>
<div class="step-body" id="step-4">
<div class="screenshot-grid">
<div class="screenshot-card">
<div class="screenshot-icon">📱</div>
<div class="screenshot-icon"></div>
<div class="screenshot-size">6.7"</div>
<div class="screenshot-note">iPhone 15 Plus<br>1290×2796</div>
</div>
<div class="screenshot-card">
<div class="screenshot-icon">📱</div>
<div class="screenshot-icon"></div>
<div class="screenshot-size">6.1"</div>
<div class="screenshot-note">iPhone 15<br>1179×2556</div>
</div>
<div class="screenshot-card">
<div class="screenshot-icon">🖥</div>
<div class="screenshot-icon"></div>
<div class="screenshot-size">iPad 12.9"</div>
<div class="screenshot-note">iPad Pro<br>2048×2732</div>
</div>
@@ -422,7 +422,7 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
<div class="step-detail-text">Verktyg: Figma + iPhone Simulator (Xcode), eller Rottenwood/AppLaunchpad</div>
</div>
<div class="info-card">
<div class="info-card-title">💡 Pro tip</div>
<div class="info-card-title"> Pro tip</div>
<div class="info-card-body">Lägg till device frames och svenska texter i bilderna. Localize screenshots till SV för bättre konvertering.</div>
</div>
</div>
@@ -436,7 +436,7 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
<div class="step-title">App Review Information</div>
<div class="step-desc">Kontaktinfo + demouppgifter</div>
</div>
<span class="step-check">⚠️</span>
<span class="step-check"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M10 3L17 17H3L10 3Z" stroke="#f59e0b" stroke-width="2"/><path d="M10 8V12M10 14V14.5" stroke="#f59e0b" stroke-width="2" stroke-linecap="round"/></svg></span>
</div>
<div class="step-body" id="step-5">
<div class="step-detail-row">
@@ -474,23 +474,23 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
<div class="step-title">Privacy Policy URL</div>
<div class="step-desc">GDPR-kompatibel · live</div>
</div>
<span class="step-check"></span>
<span class="step-check"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="9" stroke="#22c55e" stroke-width="2"/><path d="M6 10L9 13L14 7" stroke="#22c55e" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
</div>
<div class="step-body" id="step-6">
<div class="step-detail-row">
<div class="step-detail-dot"></div>
<div class="step-detail-text">URL: <span class="step-code">https://quixzoom.com/privacy</span> Live</div>
<div class="step-detail-text">URL: <span class="step-code">https://quixzoom.com/privacy</span> <svg width="20" height="20" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="9" stroke="#22c55e" stroke-width="2"/><path d="M6 10L9 13L14 7" stroke="#22c55e" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg> Live</div>
</div>
<div class="step-detail-row">
<div class="step-detail-dot"></div>
<div class="step-detail-text">GDPR compliance: Täcker datainsamling, behandling, raderingsrätt</div>
<div class="step-detail-text">GDPR compliance: <svg width="20" height="20" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="9" stroke="#22c55e" stroke-width="2"/><path d="M6 10L9 13L14 7" stroke="#22c55e" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg> Täcker datainsamling, behandling, raderingsrätt</div>
</div>
<div class="step-detail-row">
<div class="step-detail-dot"></div>
<div class="step-detail-text">Måste vara tillgänglig utan inloggning (publikt URL)</div>
</div>
<a class="step-cta" href="https://quixzoom.com/privacy" target="_blank">
🔗 Visa Privacy Policy
Visa Privacy Policy
</a>
</div>
</div>
@@ -503,12 +503,12 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
<div class="step-title">Support URL</div>
<div class="step-desc">Kundservice · live</div>
</div>
<span class="step-check"></span>
<span class="step-check"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="9" stroke="#22c55e" stroke-width="2"/><path d="M6 10L9 13L14 7" stroke="#22c55e" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
</div>
<div class="step-body" id="step-7">
<div class="step-detail-row">
<div class="step-detail-dot"></div>
<div class="step-detail-text">URL: <span class="step-code">https://quixzoom.com/support</span> Live</div>
<div class="step-detail-text">URL: <span class="step-code">https://quixzoom.com/support</span> <svg width="20" height="20" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="9" stroke="#22c55e" stroke-width="2"/><path d="M6 10L9 13L14 7" stroke="#22c55e" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg> Live</div>
</div>
<div class="step-detail-row">
<div class="step-detail-dot"></div>
@@ -519,7 +519,7 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
<div class="step-detail-text">Phone: <span class="step-code">+46 8 123 45 67</span></div>
</div>
<a class="step-cta" href="https://quixzoom.com/support" target="_blank">
🔗 Visa Support-sida
Visa Support-sida
</a>
</div>
</div>
@@ -579,7 +579,7 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
</div>
<div class="info-card">
<div class="info-card-title">⏱ Tidslinje</div>
<div class="info-card-body">Lämna in senast 24 juli → App Review 25 dagar → Klar 3031 juli → Launch Day 1 augusti 🚀</div>
<div class="info-card-body">Lämna in senast 24 juli → App Review 25 dagar → Klar 3031 juli → Launch Day 1 augusti <svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 2L10 8L14 9L8 14L6 8L2 7L8 2Z" fill="#666"/></svg></div>
</div>
</div>
</div>
@@ -592,15 +592,15 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
<div class="section-header">Juridisk checklista</div>
<div class="legal-card">
<div class="legal-row">
<span class="legal-icon">🇪🇺</span>
<span class="legal-icon"></span>
<div class="legal-info">
<div class="legal-title">GDPR Compliance</div>
<div class="legal-sub">Privacy policy live, dataskyddsombud</div>
</div>
<span class="legal-status"></span>
<span class="legal-status"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="9" stroke="#22c55e" stroke-width="2"/><path d="M6 10L9 13L14 7" stroke="#22c55e" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
</div>
<div class="legal-row">
<span class="legal-icon">📊</span>
<span class="legal-icon"></span>
<div class="legal-info">
<div class="legal-title">DAC7 Rapportering</div>
<div class="legal-sub">EU-direktiv för plattformsekonomin — pågår</div>
@@ -608,43 +608,43 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
<span class="legal-status"></span>
</div>
<div class="legal-row">
<span class="legal-icon">📄</span>
<span class="legal-icon"></span>
<div class="legal-info">
<div class="legal-title">Zoomer-avtal</div>
<div class="legal-sub">Uppdragstagares villkor — behövs innan launch</div>
</div>
<span class="legal-status"></span>
<span class="legal-status"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="9" stroke="#ef4444" stroke-width="2"/><path d="M7 7L13 13M13 7L7 13" stroke="#ef4444" stroke-width="2" stroke-linecap="round"/></svg></span>
</div>
<div class="legal-row">
<span class="legal-icon">📱</span>
<span class="legal-icon"></span>
<div class="legal-info">
<div class="legal-title">SwiftUI App</div>
<div class="legal-sub">Native iOS-app för App Store</div>
</div>
<span class="legal-status"></span>
<span class="legal-status"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="9" stroke="#ef4444" stroke-width="2"/><path d="M7 7L13 13M13 7L7 13" stroke="#ef4444" stroke-width="2" stroke-linecap="round"/></svg></span>
</div>
<div class="legal-row">
<span class="legal-icon">⚖️</span>
<span class="legal-icon"></span>
<div class="legal-info">
<div class="legal-title">Terms of Service</div>
<div class="legal-sub">Användarvillkor live</div>
</div>
<span class="legal-status"></span>
<span class="legal-status"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="9" stroke="#22c55e" stroke-width="2"/><path d="M6 10L9 13L14 7" stroke="#22c55e" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
</div>
<div class="legal-row">
<span class="legal-icon">✈️</span>
<span class="legal-icon"></span>
<div class="legal-info">
<div class="legal-title">Drönartillstånd (LFV/Transportstyrelsen)</div>
<div class="legal-sub">Zoomer ansvarar för eget tillstånd</div>
</div>
<span class="legal-status">⚠️</span>
<span class="legal-status"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M10 3L17 17H3L10 3Z" stroke="#f59e0b" stroke-width="2"/><path d="M10 8V12M10 14V14.5" stroke="#f59e0b" stroke-width="2" stroke-linecap="round"/></svg></span>
</div>
</div>
</div>
<!-- Critical path warning -->
<div class="warn-card">
<div class="warn-card-title">⚠️ Kritisk väg till 1 augusti</div>
<div class="warn-card-title"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M10 3L17 17H3L10 3Z" stroke="#f59e0b" stroke-width="2"/><path d="M10 8V12M10 14V14.5" stroke="#f59e0b" stroke-width="2" stroke-linecap="round"/></svg> Kritisk väg till 1 augusti</div>
<div class="warn-card-body">SwiftUI-app måste vara klar senast <strong>5 juni</strong> för att hinna med screenshots, testning och App Review-processen. Det är den enda blockande faktorn för en lyckad launch.</div>
</div>