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
+148
View File
@@ -0,0 +1,148 @@
import express from 'express';
import pg from 'pg';
const { Pool } = pg;
import multer from 'multer';
import { randomUUID } from 'crypto';
import path from 'path';
import { fileURLToPath } from 'url';
import fs from 'fs';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const PORT = 7060;
const UPLOAD_DIR = '/opt/amos/data/quixzoom-uploads';
// PostgreSQL connection
const pool = new Pool({
host: 'localhost',
port: 5432,
database: 'amos',
user: 'postgres',
password: 'quixzo…2026'
});
fs.mkdirSync(UPLOAD_DIR, { recursive: true });
const app = express();
app.use(express.json());
const upload = multer({
dest: UPLOAD_DIR,
limits: { fileSize: 50 * 1024 * 1024 },
});
// Serve uploaded images
app.use('/uploads', express.static(UPLOAD_DIR));
// Health check
app.get('/health', async (req, res) => {
try {
const result = await pool.query('SELECT COUNT(*) FROM quixzoom.missions');
res.json({
status: 'ok',
service: 'quixzoom-mission-engine',
port: PORT,
missions_total: parseInt(result.rows[0].count),
uptime_seconds: process.uptime(),
timestamp: new Date().toISOString()
});
} catch (e) {
res.status(500).json({ status: 'error', error: e.message });
}
});
// GET /missions — list all missions
app.get('/missions', async (req, res) => {
try {
const result = await pool.query(`
SELECT
id,
title,
description,
latitude as lat,
longitude as lon,
area_name as address,
status,
reward_credits as reward_sek,
required_photos,
created_at,
expires_at
FROM quixzoom.missions
ORDER BY created_at DESC
`);
res.json({
missions: result.rows,
count: result.rows.length
});
} catch (e) {
res.status(500).json({ error: e.message });
}
});
// GET /api/v1/missions — API format for admin
app.get('/api/v1/missions', async (req, res) => {
try {
const { status, limit = 50, offset = 0 } = req.query;
let query = `
SELECT
m.id,
m.title,
m.description,
m.latitude as lat,
m.longitude as lon,
m.area_name as address,
m.status,
m.reward_credits as reward_sek,
m.required_photos,
m.created_at,
m.expires_at,
COUNT(s.id) as submission_count
FROM quixzoom.missions m
LEFT JOIN quixzoom.submissions s ON s.mission_id = m.id
`;
const params = [];
if (status) {
query += ' WHERE m.status = $1';
params.push(status);
}
query += ` GROUP BY m.id ORDER BY m.created_at DESC LIMIT $${params.length + 1} OFFSET $${params.length + 2}`;
params.push(limit, offset);
const result = await pool.query(query, params);
res.json({
missions: result.rows,
count: result.rows.length,
total: parseInt((await pool.query('SELECT COUNT(*) FROM quixzoom.missions')).rows[0].count)
});
} catch (e) {
res.status(500).json({ error: e.message });
}
});
// GET /api/v1/stats — dashboard stats
app.get('/api/v1/stats', async (req, res) => {
try {
const missionsResult = await pool.query('SELECT COUNT(*) FROM quixzoom.missions');
const activeResult = await pool.query("SELECT COUNT(*) FROM quixzoom.missions WHERE status = 'active'");
const submissionsResult = await pool.query('SELECT COUNT(*) FROM quixzoom.submissions');
const contributorsResult = await pool.query('SELECT COUNT(DISTINCT user_id) FROM quixzoom.submissions');
res.json({
missions_total: parseInt(missionsResult.rows[0].count),
missions_active: parseInt(activeResult.rows[0].count),
submissions_total: parseInt(submissionsResult.rows[0].count),
contributors_total: parseInt(contributorsResult.rows[0].count)
});
} catch (e) {
res.status(500).json({ error: e.message });
}
});
// Start server
app.listen(PORT, () => {
console.log(`quixzoom-mission (PostgreSQL) running on port ${PORT}`);
});
+10
View File
@@ -0,0 +1,10 @@
{
"name": "quixzoom-mission-pg",
"version": "1.0.0",
"type": "module",
"dependencies": {
"express": "^4.18.2",
"pg": "^8.11.3",
"multer": "^1.4.5-lts.1"
}
}