205 lines
5.1 KiB
JavaScript
205 lines
5.1 KiB
JavaScript
/**
|
|
* Auth Service - UUID-based anonymous authentication
|
|
*
|
|
* No email addresses, no personal data
|
|
* User remembers only their UUID
|
|
*/
|
|
|
|
import { directus } from './directus.js';
|
|
|
|
const AUTH_DOMAIN = 'dgray.io';
|
|
|
|
class AuthService {
|
|
constructor() {
|
|
this.currentUser = null;
|
|
this.listeners = new Set();
|
|
}
|
|
|
|
/**
|
|
* Generates a new UUID for account creation
|
|
* @returns {string} UUID v4
|
|
*/
|
|
generateUuid() {
|
|
// Use native if available (secure contexts)
|
|
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
|
return crypto.randomUUID();
|
|
}
|
|
|
|
// Fallback for non-secure contexts
|
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
|
const r = Math.random() * 16 | 0;
|
|
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
|
return v.toString(16);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Converts UUID to fake email for Directus
|
|
* @param {string} uuid - User UUID
|
|
* @returns {string} Fake email address
|
|
*/
|
|
uuidToEmail(uuid) {
|
|
return `${uuid}@${AUTH_DOMAIN}`;
|
|
}
|
|
|
|
/**
|
|
* Creates a new anonymous account
|
|
* @returns {Promise<{uuid: string, success: boolean, error?: string}>}
|
|
*/
|
|
async createAccount() {
|
|
const uuid = this.generateUuid();
|
|
const email = this.uuidToEmail(uuid);
|
|
|
|
try {
|
|
await directus.register(email, uuid);
|
|
|
|
// Try auto-login (may fail if verification required)
|
|
const loginResult = await this.login(uuid);
|
|
|
|
if (!loginResult.success) {
|
|
// Registration worked but login failed (verification pending)
|
|
return {
|
|
uuid,
|
|
success: true,
|
|
pendingVerification: true,
|
|
message: 'Account created. Login may require activation.'
|
|
};
|
|
}
|
|
|
|
return { uuid, success: true };
|
|
} catch (error) {
|
|
console.error('Registration failed:', error);
|
|
return {
|
|
uuid: null,
|
|
success: false,
|
|
error: error.message || 'Registration failed'
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Logs in with UUID
|
|
* @param {string} uuid - User UUID
|
|
* @returns {Promise<{success: boolean, error?: string}>}
|
|
*/
|
|
async login(uuid) {
|
|
const email = this.uuidToEmail(uuid);
|
|
|
|
try {
|
|
await directus.login(email, uuid);
|
|
this.currentUser = await directus.getCurrentUser();
|
|
this.notifyListeners();
|
|
this.storeUuid(uuid);
|
|
|
|
return { success: true };
|
|
} catch (error) {
|
|
console.error('Login failed:', error);
|
|
return {
|
|
success: false,
|
|
error: error.message || 'Invalid UUID'
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Logs out the current user
|
|
*/
|
|
async logout() {
|
|
try {
|
|
await directus.logout();
|
|
} catch (e) {
|
|
// Ignore errors
|
|
}
|
|
|
|
this.currentUser = null;
|
|
this.clearStoredUuid();
|
|
this.notifyListeners();
|
|
}
|
|
|
|
/**
|
|
* Checks if user is logged in
|
|
* @returns {boolean}
|
|
*/
|
|
isLoggedIn() {
|
|
return directus.isAuthenticated();
|
|
}
|
|
|
|
/**
|
|
* Gets current user data
|
|
* @returns {Promise<Object|null>}
|
|
*/
|
|
async getUser() {
|
|
if (!this.isLoggedIn()) return null;
|
|
|
|
if (!this.currentUser) {
|
|
try {
|
|
this.currentUser = await directus.getCurrentUser();
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
return this.currentUser;
|
|
}
|
|
|
|
/**
|
|
* Stores UUID in localStorage (optional convenience)
|
|
* User should still backup their UUID
|
|
* @param {string} uuid
|
|
*/
|
|
storeUuid(uuid) {
|
|
localStorage.setItem('dgray_uuid', uuid);
|
|
}
|
|
|
|
/**
|
|
* Gets stored UUID if available
|
|
* @returns {string|null}
|
|
*/
|
|
getStoredUuid() {
|
|
return localStorage.getItem('dgray_uuid');
|
|
}
|
|
|
|
/**
|
|
* Clears stored UUID
|
|
*/
|
|
clearStoredUuid() {
|
|
localStorage.removeItem('dgray_uuid');
|
|
}
|
|
|
|
/**
|
|
* Subscribe to auth state changes
|
|
* @param {Function} callback
|
|
* @returns {Function} Unsubscribe function
|
|
*/
|
|
subscribe(callback) {
|
|
this.listeners.add(callback);
|
|
return () => this.listeners.delete(callback);
|
|
}
|
|
|
|
/**
|
|
* Notifies all listeners of auth state change
|
|
*/
|
|
notifyListeners() {
|
|
this.listeners.forEach(cb => cb(this.isLoggedIn(), this.currentUser));
|
|
}
|
|
|
|
/**
|
|
* Tries to restore session from stored tokens
|
|
*/
|
|
async tryRestoreSession() {
|
|
if (directus.isAuthenticated()) {
|
|
try {
|
|
this.currentUser = await directus.getCurrentUser();
|
|
this.notifyListeners();
|
|
return true;
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export const auth = new AuthService();
|
|
export default auth;
|