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
+17 -17
View File
@@ -18,55 +18,55 @@
html,body{height:100%;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:var(--bg);color:var(--text);overflow:hidden}
#map{position:fixed;top:0;left:0;right:0;bottom:0;z-index:1}
.leaflet-control-zoom{margin-top:calc(var(--safe-top) + 64px) !important;margin-left:16px !important}
.leaflet-control-zoom a{background:var(--surface) !important;color:var(--text) !important;border-color:var(--border) !important;width:34px !important;height:34px !important;line-height:34px !important;border-radius:8px !important;font-size:18px !important}
.leaflet-control-zoom a{background:var(--surface) !important;color:var(--text) !important;border-color:var(--border) !important;width:34px !important;height:34px !important;line-height:34px !important;border-radius: var(--radius-md) !important;font-size:18px !important}
#hdr{position:fixed;top:0;left:0;right:0;z-index:1000;background:rgba(13,17,23,0.9);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);border-bottom:1px solid var(--border);padding:calc(var(--safe-top) + 10px) 16px 10px}
#hdr-inner{max-width:1200px;margin:0 auto;display:flex;align-items:center;gap:12px}
#logo{font-size:15px;font-weight:700;color:var(--text);text-decoration:none;white-space:nowrap}
#logo span{color:var(--green)}
#hdr-sub{flex:1;font-size:13px;color:var(--text2)}
.badge{font-size:10px;font-weight:700;letter-spacing:0.8px;text-transform:uppercase;background:rgba(63,185,80,0.15);color:var(--green);border:1px solid rgba(63,185,80,0.3);border-radius:20px;padding:3px 9px}
#ui-tog{position:fixed;top:calc(var(--safe-top) + 62px);right:16px;z-index:1000;background:rgba(22,27,34,0.92);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid var(--border);border-radius:10px;padding:7px 11px;display:flex;align-items:center;gap:7px;cursor:pointer;font-size:12px;font-weight:600;color:var(--text);user-select:none;transition:opacity 0.2s}
.badge{font-size:10px;font-weight:700;letter-spacing:0.8px;text-transform:uppercase;background:rgba(63,185,80,0.15);color:var(--green);border:1px solid rgba(63,185,80,0.3);border-radius: var(--radius-xl);padding:3px 9px}
#ui-tog{position:fixed;top:calc(var(--safe-top) + 62px);right:16px;z-index:1000;background:rgba(22,27,34,0.92);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid var(--border);border-radius: var(--radius-md);padding:7px 11px;display:flex;align-items:center;gap:7px;cursor:pointer;font-size:12px;font-weight:600;color:var(--text);user-select:none;transition:opacity 0.2s}
#ui-tog:active{opacity:0.7}
#ui-led{width:8px;height:8px;border-radius:50%;background:var(--green);flex-shrink:0;transition:background 0.2s}
#ui-led{width:8px;height:8px;border-radius: var(--radius-full);background:var(--green);flex-shrink:0;transition:background 0.2s}
#ui-tog.off #ui-led{background:var(--text2)}
#panel{position:fixed;bottom:0;left:0;right:0;z-index:900;background:rgba(22,27,34,0.94);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);border-top:1px solid var(--border);padding:10px 16px calc(var(--safe-bottom) + 10px)}
#panel-inner{max-width:1200px;margin:0 auto}
#ph{width:36px;height:4px;border-radius:2px;background:rgba(255,255,255,0.12);margin:0 auto 10px}
#ph{width:36px;height:4px;border-radius: var(--radius-sm);background:rgba(255,255,255,0.12);margin:0 auto 10px}
#leg{display:flex;gap:6px;flex-wrap:wrap;justify-content:center;margin-bottom:8px}
.li{display:flex;align-items:center;gap:5px;font-size:11px;color:var(--text2);background:var(--surface2);border-radius:20px;padding:4px 10px;border:1px solid var(--border)}
.ld{width:9px;height:9px;border-radius:50%;flex-shrink:0}
.li{display:flex;align-items:center;gap:5px;font-size:11px;color:var(--text2);background:var(--surface2);border-radius: var(--radius-xl);padding:4px 10px;border:1px solid var(--border)}
.ld{width:9px;height:9px;border-radius: var(--radius-full);flex-shrink:0}
#note{font-size:11px;color:var(--text2);text-align:center;line-height:1.4}
#note a{color:var(--blue);text-decoration:none}
.leaflet-popup-content-wrapper{background:var(--surface) !important;border:1px solid var(--border) !important;border-radius:12px !important;box-shadow:0 8px 32px rgba(0,0,0,0.6) !important;padding:0 !important}
.leaflet-popup-content-wrapper{background:var(--surface) !important;border:1px solid var(--border) !important;border-radius: var(--radius-md) !important;box-shadow:0 8px 32px rgba(0,0,0,0.6) !important;padding:0 !important}
.leaflet-popup-content{margin:0 !important;width:260px !important;color:var(--text)}
.leaflet-popup-tip{background:var(--surface) !important}
.leaflet-popup-close-button{color:var(--text2) !important;top:8px !important;right:10px !important}
.zpop{padding:14px 16px}
.zpop-head{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;margin-bottom:10px}
.zpop-name{font-size:14px;font-weight:700;color:var(--text);line-height:1.3}
.zpop-conf{font-size:11px;color:var(--text2);background:var(--surface2);border-radius:6px;padding:2px 7px;white-space:nowrap;flex-shrink:0;font-weight:600}
.zpop-conf{font-size:11px;color:var(--text2);background:var(--surface2);border-radius: var(--radius-sm);padding:2px 7px;white-space:nowrap;flex-shrink:0;font-weight:600}
.zpop-scores{display:grid;grid-template-columns:1fr 1fr;gap:5px;margin-bottom:10px}
.zpop-sc{background:var(--surface2);border-radius:7px;padding:6px 8px}
.zpop-sc{background:var(--surface2);border-radius: var(--radius-sm);padding:6px 8px}
.zpop-sl{font-size:10px;color:var(--text2);font-weight:500;margin-bottom:3px}
.zpop-bar{height:3px;border-radius:2px;background:rgba(255,255,255,0.08);margin-bottom:2px}
.zpop-fill{height:100%;border-radius:2px}
.zpop-bar{height:3px;border-radius: var(--radius-sm);background:rgba(255,255,255,0.08);margin-bottom:2px}
.zpop-fill{height:100%;border-radius: var(--radius-sm)}
.zpop-sv{font-size:12px;font-weight:700;color:var(--text)}
.zpop-c{border-radius:8px;padding:7px 10px;font-size:11px;line-height:1.5;margin-bottom:8px}
.zpop-c{border-radius: var(--radius-md);padding:7px 10px;font-size:11px;line-height:1.5;margin-bottom:8px}
.zpop-c.low{background:rgba(63,185,80,0.1);color:#7ee787;border:1px solid rgba(63,185,80,0.2)}
.zpop-c.medium{background:rgba(240,136,62,0.1);color:#ffa657;border:1px solid rgba(240,136,62,0.2)}
.zpop-c.high{background:rgba(248,81,73,0.1);color:#ff7b72;border:1px solid rgba(248,81,73,0.2)}
.zpop-cl{font-weight:700;display:block;margin-bottom:2px}
.zpop-d{font-size:10px;color:var(--text2);border-top:1px solid var(--border);padding-top:7px;line-height:1.4}
.mpop{padding:14px 16px}
.mpop-cat{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:0.5px;color:var(--blue);background:rgba(88,166,255,0.1);border-radius:5px;padding:2px 7px;display:inline-block;margin-bottom:7px}
.mpop-cat{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:0.5px;color:var(--blue);background:rgba(88,166,255,0.1);border-radius: var(--radius-sm);padding:2px 7px;display:inline-block;margin-bottom:7px}
.mpop-title{font-size:14px;font-weight:600;color:var(--text);line-height:1.3;margin-bottom:10px}
.mpop-meta{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:10px}
.mpop-item{background:var(--surface2);border-radius:8px;padding:7px 9px}
.mpop-item{background:var(--surface2);border-radius: var(--radius-md);padding:7px 9px}
.mpop-lbl{font-size:10px;color:var(--text2);margin-bottom:2px;font-weight:500}
.mpop-val{font-size:13px;font-weight:700;color:var(--text)}
.mpop-reward{color:var(--green)}
.mpop-risk{display:flex;align-items:center;justify-content:space-between;gap:8px;border-top:1px solid var(--border);padding-top:9px}
.rb{font-size:10px;font-weight:700;border-radius:6px;padding:3px 8px}
.rb{font-size:10px;font-weight:700;border-radius: var(--radius-sm);padding:3px 8px}
.rb.L1{background:rgba(63,185,80,0.15);color:#7ee787}
.rb.L2{background:rgba(210,153,34,0.15);color:#e3b341}
.rb.L3{background:rgba(240,136,62,0.15);color:#ffa657}
@@ -129,7 +129,7 @@
];
const AS={green:{color:'#3fb950',fo:0.12,w:1.5,d:null},yellow:{color:'#d29922',fo:0.15,w:1.5,d:'5 4'},orange:{color:'#f0883e',fo:0.20,w:2,d:'5 4'},red:{color:'#f85149',fo:0.25,w:2,d:'4 3'},darkred:{color:'#8b0000',fo:0.30,w:2.5,d:'4 3'}};
const CL={low:'Contradiction Score: Low',medium:'Contradiction Score: Medium',high:'Contradiction Score: HIGH ⚠️'};
const CL={low:'Contradiction Score: Low',medium:'Contradiction Score: Medium',high:'Contradiction Score: HIGH <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>'};
function bar(v){const p=Math.round(v*10),c=v>=7.5?'#3fb950':v>=5?'#d29922':'#f85149';return `<div class="zpop-bar"><div class="zpop-fill" style="width:${p}%;background:${c}"></div></div>`;}