6989a98d75
- 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
141 lines
3.7 KiB
JavaScript
141 lines
3.7 KiB
JavaScript
// Zoomers ID Authentication System
|
|
// Format: CC-RR-CCC-YYYY-G-NNNN
|
|
// Example: SE-VG-GOT-1990-M-0001
|
|
|
|
const ZoomersAuth = {
|
|
// Country codes
|
|
countries: {
|
|
'SE': 'Sweden',
|
|
'NO': 'Norway',
|
|
'DK': 'Denmark',
|
|
'FI': 'Finland',
|
|
'DE': 'Germany',
|
|
'NL': 'Netherlands',
|
|
'GB': 'United Kingdom',
|
|
'US': 'United States'
|
|
},
|
|
|
|
// Region codes (Sweden example)
|
|
regions: {
|
|
'SE': {
|
|
'VG': 'Västra Götaland',
|
|
'ST': 'Stockholm',
|
|
'SK': 'Skåne',
|
|
'UP': 'Uppsala',
|
|
'ÖG': 'Östergötland'
|
|
}
|
|
},
|
|
|
|
// Gender codes
|
|
genders: {
|
|
'M': 'Male',
|
|
'F': 'Female',
|
|
'N': 'Non-binary',
|
|
'X': 'Prefer not to say'
|
|
},
|
|
|
|
// Parse Zoomers ID
|
|
parseId(zoomersId) {
|
|
const parts = zoomersId.split('-');
|
|
if (parts.length !== 6) return null;
|
|
|
|
return {
|
|
country: parts[0],
|
|
region: parts[1],
|
|
city: parts[2],
|
|
birthYear: parts[3],
|
|
gender: parts[4],
|
|
uniqueNumber: parts[5]
|
|
};
|
|
},
|
|
|
|
// Validate Zoomers ID format
|
|
validateId(zoomersId) {
|
|
const pattern = /^[A-Z]{2}-[A-Z]{2,3}-[A-Z]{3}-\d{4}-[MFNX]-\d{4}$/;
|
|
return pattern.test(zoomersId);
|
|
},
|
|
|
|
// Generate new Zoomers ID
|
|
generateId(country, region, city, birthYear, gender) {
|
|
const randomNum = Math.floor(1000 + Math.random() * 9000);
|
|
return `${country}-${region}-${city}-${birthYear}-${gender}-${randomNum}`;
|
|
},
|
|
|
|
// Mock authentication (replace with real API call)
|
|
async authenticate(zoomersId, pin) {
|
|
// Simulate API call
|
|
return new Promise((resolve) => {
|
|
setTimeout(() => {
|
|
if (this.validateId(zoomersId) && pin && pin.length >= 4) {
|
|
const parsed = this.parseId(zoomersId);
|
|
resolve({
|
|
success: true,
|
|
zoomersId: zoomersId,
|
|
profile: {
|
|
country: this.countries[parsed.country] || parsed.country,
|
|
region: this.regions[parsed.country]?.[parsed.region] || parsed.region,
|
|
city: parsed.city,
|
|
birthYear: parsed.birthYear,
|
|
gender: this.genders[parsed.gender] || parsed.gender
|
|
}
|
|
});
|
|
} else {
|
|
resolve({
|
|
success: false,
|
|
error: 'Invalid Zoomers ID or PIN'
|
|
});
|
|
}
|
|
}, 500);
|
|
});
|
|
}
|
|
};
|
|
|
|
// Login Modal Component
|
|
function createLoginModal() {
|
|
const modal = document.createElement('div');
|
|
modal.id = 'zoomers-login-modal';
|
|
modal.innerHTML = `
|
|
<div class="zoomers-modal-overlay">
|
|
<div class="zoomers-modal">
|
|
<button class="zoomers-modal-close">×</button>
|
|
<div class="zoomers-modal-header">
|
|
<h2>Zoomer Login</h2>
|
|
<p>Enter your Zoomers ID</p>
|
|
</div>
|
|
<form id="zoomers-login-form">
|
|
<div class="zoomers-form-group">
|
|
<label for="zoomers-id">Zoomers ID</label>
|
|
<input
|
|
type="text"
|
|
id="zoomers-id"
|
|
placeholder="SE-VG-GOT-1990-M-0001"
|
|
pattern="[A-Z]{2}-[A-Z]{2,3}-[A-Z]{3}-\d{4}-[MFNX]-\d{4}"
|
|
required
|
|
>
|
|
<small>Format: CC-RR-CCC-YYYY-G-NNNN</small>
|
|
</div>
|
|
<div class="zoomers-form-group">
|
|
<label for="zoomers-pin">PIN</label>
|
|
<input
|
|
type="password"
|
|
id="zoomers-pin"
|
|
placeholder="Enter your PIN"
|
|
minlength="4"
|
|
required
|
|
>
|
|
</div>
|
|
<button type="submit" class="zoomers-btn-login">Log In</button>
|
|
</form>
|
|
<div class="zoomers-modal-footer">
|
|
<p>New Zoomer? <a href="#register">Register here</a></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
return modal;
|
|
}
|
|
|
|
// Export for use
|
|
window.ZoomersAuth = ZoomersAuth;
|
|
window.createLoginModal = createLoginModal;
|