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
@@ -29,7 +29,7 @@ h1 { font-size: 2.5rem; font-weight: 700; margin-bottom: 0.5em; line-height: 1.2
h2 { font-size: 1.5rem; font-weight: 600; margin-top: 2em; margin-bottom: 0.5em; }
p { margin-bottom: 1.2em; }
.meta { color: #666; font-size: 0.9rem; margin-bottom: 2em; }
.cta { background: #f5f5f5; padding: 24px; border-radius: 8px; margin: 2em 0; }
.cta { background: #f5f5f5; padding: 24px; border-radius: var(--radius-md); margin: 2em 0; }
.cta a { color: #0066cc; text-decoration: none; font-weight: 600; }
footer { margin-top: 4em; padding-top: 2em; border-top: 1px solid #eee; font-size: 0.85rem; color: #666; }
</style>
@@ -29,7 +29,7 @@ h1 { font-size: 2.5rem; font-weight: 700; margin-bottom: 0.5em; line-height: 1.2
h2 { font-size: 1.5rem; font-weight: 600; margin-top: 2em; margin-bottom: 0.5em; }
p { margin-bottom: 1.2em; }
.meta { color: #666; font-size: 0.9rem; margin-bottom: 2em; }
.cta { background: #f5f5f5; padding: 24px; border-radius: 8px; margin: 2em 0; }
.cta { background: #f5f5f5; padding: 24px; border-radius: var(--radius-md); margin: 2em 0; }
.cta a { color: #0066cc; text-decoration: none; font-weight: 600; }
footer { margin-top: 4em; padding-top: 2em; border-top: 1px solid #eee; font-size: 0.85rem; color: #666; }
</style>
@@ -42,7 +42,7 @@
.breadcrumb a:hover { text-decoration: underline; }
.breadcrumb-sep { color: var(--text-muted); }
.article-meta { display: flex; align-items: center; gap: 12px; margin-bottom: 20px; }
.category-badge { display: inline-block; font-size: 11px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--blue); background: var(--blue-light); border-radius: 6px; padding: 3px 9px; }
.category-badge { display: inline-block; font-size: 11px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--blue); background: var(--blue-light); border-radius: var(--radius-sm); padding: 3px 9px; }
.article-date { font-size: 13px; color: var(--text-muted); font-weight: 500; }
h1 { font-size: clamp(1.7rem, 4vw, 2.4rem); font-weight: 700; letter-spacing: -0.04em; line-height: 1.2; color: var(--text-primary); margin-bottom: 20px; }
.article-lead { font-size: 18px; color: var(--text-secondary); line-height: 1.7; margin-bottom: 40px; padding-bottom: 32px; border-bottom: 1px solid var(--border); }
@@ -42,7 +42,7 @@ h1 { font-size: 2.5rem; font-weight: 700; margin-bottom: 0.5em; line-height: 1.2
h2 { font-size: 1.5rem; font-weight: 600; margin-top: 2em; margin-bottom: 0.5em; }
p { margin-bottom: 1.2em; }
.meta { color: #666; font-size: 0.9rem; margin-bottom: 2em; }
.cta { background: #f5f5f5; padding: 24px; border-radius: 8px; margin: 2em 0; }
.cta { background: #f5f5f5; padding: 24px; border-radius: var(--radius-md); margin: 2em 0; }
.cta a { color: #0066cc; text-decoration: none; font-weight: 600; }
footer { margin-top: 4em; padding-top: 2em; border-top: 1px solid #eee; font-size: 0.85rem; color: #666; }
</style>
@@ -29,7 +29,7 @@ h1 { font-size: 2.5rem; font-weight: 700; margin-bottom: 0.5em; line-height: 1.2
h2 { font-size: 1.5rem; font-weight: 600; margin-top: 2em; margin-bottom: 0.5em; }
p { margin-bottom: 1.2em; }
.meta { color: #666; font-size: 0.9rem; margin-bottom: 2em; }
.cta { background: #f5f5f5; padding: 24px; border-radius: 8px; margin: 2em 0; }
.cta { background: #f5f5f5; padding: 24px; border-radius: var(--radius-md); margin: 2em 0; }
.cta a { color: #0066cc; text-decoration: none; font-weight: 600; }
footer { margin-top: 4em; padding-top: 2em; border-top: 1px solid #eee; font-size: 0.85rem; color: #666; }
</style>
@@ -29,7 +29,7 @@ h1 { font-size: 2.5rem; font-weight: 700; margin-bottom: 0.5em; line-height: 1.2
h2 { font-size: 1.5rem; font-weight: 600; margin-top: 2em; margin-bottom: 0.5em; }
p { margin-bottom: 1.2em; }
.meta { color: #666; font-size: 0.9rem; margin-bottom: 2em; }
.cta { background: #f5f5f5; padding: 24px; border-radius: 8px; margin: 2em 0; }
.cta { background: #f5f5f5; padding: 24px; border-radius: var(--radius-md); margin: 2em 0; }
.cta a { color: #0066cc; text-decoration: none; font-weight: 600; }
footer { margin-top: 4em; padding-top: 2em; border-top: 1px solid #eee; font-size: 0.85rem; color: #666; }
</style>
@@ -29,7 +29,7 @@ h1 { font-size: 2.5rem; font-weight: 700; margin-bottom: 0.5em; line-height: 1.2
h2 { font-size: 1.5rem; font-weight: 600; margin-top: 2em; margin-bottom: 0.5em; }
p { margin-bottom: 1.2em; }
.meta { color: #666; font-size: 0.9rem; margin-bottom: 2em; }
.cta { background: #f5f5f5; padding: 24px; border-radius: 8px; margin: 2em 0; }
.cta { background: #f5f5f5; padding: 24px; border-radius: var(--radius-md); margin: 2em 0; }
.cta a { color: #0066cc; text-decoration: none; font-weight: 600; }
footer { margin-top: 4em; padding-top: 2em; border-top: 1px solid #eee; font-size: 0.85rem; color: #666; }
</style>
@@ -29,7 +29,7 @@ h1 { font-size: 2.5rem; font-weight: 700; margin-bottom: 0.5em; line-height: 1.2
h2 { font-size: 1.5rem; font-weight: 600; margin-top: 2em; margin-bottom: 0.5em; }
p { margin-bottom: 1.2em; }
.meta { color: #666; font-size: 0.9rem; margin-bottom: 2em; }
.cta { background: #f5f5f5; padding: 24px; border-radius: 8px; margin: 2em 0; }
.cta { background: #f5f5f5; padding: 24px; border-radius: var(--radius-md); margin: 2em 0; }
.cta a { color: #0066cc; text-decoration: none; font-weight: 600; }
footer { margin-top: 4em; padding-top: 2em; border-top: 1px solid #eee; font-size: 0.85rem; color: #666; }
</style>
@@ -36,7 +36,7 @@
.breadcrumb a { color: var(--blue); text-decoration: none; } .breadcrumb a:hover { text-decoration: underline; }
.breadcrumb-sep { color: var(--text-muted); }
.article-meta { display: flex; align-items: center; gap: 12px; margin-bottom: 20px; }
.category-badge { display: inline-block; font-size: 11px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--blue); background: var(--blue-light); border-radius: 6px; padding: 3px 9px; }
.category-badge { display: inline-block; font-size: 11px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--blue); background: var(--blue-light); border-radius: var(--radius-sm); padding: 3px 9px; }
.article-date { font-size: 13px; color: var(--text-muted); font-weight: 500; }
h1 { font-size: clamp(1.7rem, 4vw, 2.4rem); font-weight: 700; letter-spacing: -0.04em; line-height: 1.2; color: var(--text-primary); margin-bottom: 20px; }
.article-lead { font-size: 18px; color: var(--text-secondary); line-height: 1.7; margin-bottom: 40px; padding-bottom: 32px; border-bottom: 1px solid var(--border); }
@@ -36,7 +36,7 @@
.breadcrumb a { color: var(--blue); text-decoration: none; } .breadcrumb a:hover { text-decoration: underline; }
.breadcrumb-sep { color: var(--text-muted); }
.article-meta { display: flex; align-items: center; gap: 12px; margin-bottom: 20px; }
.category-badge { display: inline-block; font-size: 11px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--blue); background: var(--blue-light); border-radius: 6px; padding: 3px 9px; }
.category-badge { display: inline-block; font-size: 11px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--blue); background: var(--blue-light); border-radius: var(--radius-sm); padding: 3px 9px; }
.article-date { font-size: 13px; color: var(--text-muted); font-weight: 500; }
h1 { font-size: clamp(1.7rem, 4vw, 2.4rem); font-weight: 700; letter-spacing: -0.04em; line-height: 1.2; color: var(--text-primary); margin-bottom: 20px; }
.article-lead { font-size: 18px; color: var(--text-secondary); line-height: 1.7; margin-bottom: 40px; padding-bottom: 32px; border-bottom: 1px solid var(--border); }
@@ -29,7 +29,7 @@ h1 { font-size: 2.5rem; font-weight: 700; margin-bottom: 0.5em; line-height: 1.2
h2 { font-size: 1.5rem; font-weight: 600; margin-top: 2em; margin-bottom: 0.5em; }
p { margin-bottom: 1.2em; }
.meta { color: #666; font-size: 0.9rem; margin-bottom: 2em; }
.cta { background: #f5f5f5; padding: 24px; border-radius: 8px; margin: 2em 0; }
.cta { background: #f5f5f5; padding: 24px; border-radius: var(--radius-md); margin: 2em 0; }
.cta a { color: #0066cc; text-decoration: none; font-weight: 600; }
footer { margin-top: 4em; padding-top: 2em; border-top: 1px solid #eee; font-size: 0.85rem; color: #666; }
</style>
+1 -1
View File
@@ -11,7 +11,7 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Inter', sans-serif; line
h1 { font-size: 2.5rem; font-weight: 700; margin-bottom: 0.5em; }
.subtitle { color: #666; font-size: 1.2rem; margin-bottom: 3em; }
.article-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 2em; }
.article-card { border: 1px solid #eee; border-radius: 8px; padding: 24px; transition: box-shadow 0.2s; }
.article-card { border: 1px solid #eee; border-radius: var(--radius-md); padding: 24px; transition: box-shadow 0.2s; }
.article-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
.article-card h2 { font-size: 1.3rem; margin-top: 0; }
.article-card a { color: #1a1a1a; text-decoration: none; }
@@ -36,7 +36,7 @@
.breadcrumb a { color: var(--blue); text-decoration: none; } .breadcrumb a:hover { text-decoration: underline; }
.breadcrumb-sep { color: var(--text-muted); }
.article-meta { display: flex; align-items: center; gap: 12px; margin-bottom: 20px; }
.category-badge { display: inline-block; font-size: 11px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--blue); background: var(--blue-light); border-radius: 6px; padding: 3px 9px; }
.category-badge { display: inline-block; font-size: 11px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--blue); background: var(--blue-light); border-radius: var(--radius-sm); padding: 3px 9px; }
.article-date { font-size: 13px; color: var(--text-muted); font-weight: 500; }
h1 { font-size: clamp(1.7rem, 4vw, 2.4rem); font-weight: 700; letter-spacing: -0.04em; line-height: 1.2; color: var(--text-primary); margin-bottom: 20px; }
.article-lead { font-size: 18px; color: var(--text-secondary); line-height: 1.7; margin-bottom: 40px; padding-bottom: 32px; border-bottom: 1px solid var(--border); }
@@ -91,7 +91,7 @@
font-size: 11px; font-weight: 700;
letter-spacing: 0.06em; text-transform: uppercase;
color: var(--blue); background: var(--blue-light);
border-radius: 6px; padding: 3px 9px;
border-radius: var(--radius-sm); padding: 3px 9px;
}
.article-date { font-size: 13px; color: var(--text-muted); font-weight: 500; }
@@ -42,7 +42,7 @@ h1 { font-size: 2.5rem; font-weight: 700; margin-bottom: 0.5em; line-height: 1.2
h2 { font-size: 1.5rem; font-weight: 600; margin-top: 2em; margin-bottom: 0.5em; }
p { margin-bottom: 1.2em; }
.meta { color: #666; font-size: 0.9rem; margin-bottom: 2em; }
.cta { background: #f5f5f5; padding: 24px; border-radius: 8px; margin: 2em 0; }
.cta { background: #f5f5f5; padding: 24px; border-radius: var(--radius-md); margin: 2em 0; }
.cta a { color: #0066cc; text-decoration: none; font-weight: 600; }
footer { margin-top: 4em; padding-top: 2em; border-top: 1px solid #eee; font-size: 0.85rem; color: #666; }
</style>
@@ -29,7 +29,7 @@ h1 { font-size: 2.5rem; font-weight: 700; margin-bottom: 0.5em; line-height: 1.2
h2 { font-size: 1.5rem; font-weight: 600; margin-top: 2em; margin-bottom: 0.5em; }
p { margin-bottom: 1.2em; }
.meta { color: #666; font-size: 0.9rem; margin-bottom: 2em; }
.cta { background: #f5f5f5; padding: 24px; border-radius: 8px; margin: 2em 0; }
.cta { background: #f5f5f5; padding: 24px; border-radius: var(--radius-md); margin: 2em 0; }
.cta a { color: #0066cc; text-decoration: none; font-weight: 600; }
footer { margin-top: 4em; padding-top: 2em; border-top: 1px solid #eee; font-size: 0.85rem; color: #666; }
</style>
@@ -29,7 +29,7 @@ h1 { font-size: 2.5rem; font-weight: 700; margin-bottom: 0.5em; line-height: 1.2
h2 { font-size: 1.5rem; font-weight: 600; margin-top: 2em; margin-bottom: 0.5em; }
p { margin-bottom: 1.2em; }
.meta { color: #666; font-size: 0.9rem; margin-bottom: 2em; }
.cta { background: #f5f5f5; padding: 24px; border-radius: 8px; margin: 2em 0; }
.cta { background: #f5f5f5; padding: 24px; border-radius: var(--radius-md); margin: 2em 0; }
.cta a { color: #0066cc; text-decoration: none; font-weight: 600; }
footer { margin-top: 4em; padding-top: 2em; border-top: 1px solid #eee; font-size: 0.85rem; color: #666; }
</style>
@@ -29,7 +29,7 @@ h1 { font-size: 2.5rem; font-weight: 700; margin-bottom: 0.5em; line-height: 1.2
h2 { font-size: 1.5rem; font-weight: 600; margin-top: 2em; margin-bottom: 0.5em; }
p { margin-bottom: 1.2em; }
.meta { color: #666; font-size: 0.9rem; margin-bottom: 2em; }
.cta { background: #f5f5f5; padding: 24px; border-radius: 8px; margin: 2em 0; }
.cta { background: #f5f5f5; padding: 24px; border-radius: var(--radius-md); margin: 2em 0; }
.cta a { color: #0066cc; text-decoration: none; font-weight: 600; }
footer { margin-top: 4em; padding-top: 2em; border-top: 1px solid #eee; font-size: 0.85rem; color: #666; }
</style>
@@ -29,7 +29,7 @@ h1 { font-size: 2.5rem; font-weight: 700; margin-bottom: 0.5em; line-height: 1.2
h2 { font-size: 1.5rem; font-weight: 600; margin-top: 2em; margin-bottom: 0.5em; }
p { margin-bottom: 1.2em; }
.meta { color: #666; font-size: 0.9rem; margin-bottom: 2em; }
.cta { background: #f5f5f5; padding: 24px; border-radius: 8px; margin: 2em 0; }
.cta { background: #f5f5f5; padding: 24px; border-radius: var(--radius-md); margin: 2em 0; }
.cta a { color: #0066cc; text-decoration: none; font-weight: 600; }
footer { margin-top: 4em; padding-top: 2em; border-top: 1px solid #eee; font-size: 0.85rem; color: #666; }
</style>
@@ -36,7 +36,7 @@
.breadcrumb a { color: var(--blue); text-decoration: none; } .breadcrumb a:hover { text-decoration: underline; }
.breadcrumb-sep { color: var(--text-muted); }
.article-meta { display: flex; align-items: center; gap: 12px; margin-bottom: 20px; }
.category-badge { display: inline-block; font-size: 11px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--blue); background: var(--blue-light); border-radius: 6px; padding: 3px 9px; }
.category-badge { display: inline-block; font-size: 11px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--blue); background: var(--blue-light); border-radius: var(--radius-sm); padding: 3px 9px; }
.article-date { font-size: 13px; color: var(--text-muted); font-weight: 500; }
h1 { font-size: clamp(1.7rem, 4vw, 2.4rem); font-weight: 700; letter-spacing: -0.04em; line-height: 1.2; color: var(--text-primary); margin-bottom: 20px; }
.article-lead { font-size: 18px; color: var(--text-secondary); line-height: 1.7; margin-bottom: 40px; padding-bottom: 32px; border-bottom: 1px solid var(--border); }
@@ -29,7 +29,7 @@ h1 { font-size: 2.5rem; font-weight: 700; margin-bottom: 0.5em; line-height: 1.2
h2 { font-size: 1.5rem; font-weight: 600; margin-top: 2em; margin-bottom: 0.5em; }
p { margin-bottom: 1.2em; }
.meta { color: #666; font-size: 0.9rem; margin-bottom: 2em; }
.cta { background: #f5f5f5; padding: 24px; border-radius: 8px; margin: 2em 0; }
.cta { background: #f5f5f5; padding: 24px; border-radius: var(--radius-md); margin: 2em 0; }
.cta a { color: #0066cc; text-decoration: none; font-weight: 600; }
footer { margin-top: 4em; padding-top: 2em; border-top: 1px solid #eee; font-size: 0.85rem; color: #666; }
</style>