Add infrastructure for future DARC Community Logbook (DCL) integration: - Database schema: Add dcl_api_key, my_darc_dok, darc_dok, dcl_qsl_rdate, dcl_qsl_rstatus fields - Create DCL service stub with placeholder functions for when DCL provides API - Backend API: Add /api/auth/dcl-credentials endpoint for API key management - Frontend settings: Add DCL API key input with informational notice about API availability - QSO table: Add My DOK and DOK columns, update confirmation column for multiple services Note: DCL download API is not yet available. These changes prepare the application for future implementation when DCL adds programmatic access. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
145 lines
3.7 KiB
JavaScript
145 lines
3.7 KiB
JavaScript
import { eq } from 'drizzle-orm';
|
|
import { db } from '../config.js';
|
|
import { users } from '../db/schema/index.js';
|
|
|
|
/**
|
|
* Hash a password using Bun's built-in password hashing
|
|
* @param {string} password - Plain text password
|
|
* @returns {Promise<string>} Hashed password
|
|
*/
|
|
async function hashPassword(password) {
|
|
return Bun.password.hash(password);
|
|
}
|
|
|
|
/**
|
|
* Verify a password against a hash
|
|
* @param {string} password - Plain text password
|
|
* @param {string} hash - Hashed password
|
|
* @returns {Promise<boolean>} True if password matches
|
|
*/
|
|
async function verifyPassword(password, hash) {
|
|
return Bun.password.verify(password, hash);
|
|
}
|
|
|
|
/**
|
|
* Register a new user
|
|
* @param {Object} userData - User registration data
|
|
* @param {string} userData.email - User email
|
|
* @param {string} userData.password - Plain text password
|
|
* @param {string} userData.callsign - Ham radio callsign
|
|
* @returns {Promise<Object|null>} Created user object (without password) or null if email exists
|
|
*/
|
|
export async function registerUser({ email, password, callsign }) {
|
|
// Check if user already exists
|
|
const [existingUser] = await db
|
|
.select()
|
|
.from(users)
|
|
.where(eq(users.email, email))
|
|
.limit(1);
|
|
|
|
if (existingUser) {
|
|
return null;
|
|
}
|
|
|
|
// Hash password
|
|
const passwordHash = await hashPassword(password);
|
|
|
|
// Create user
|
|
const newUser = await db
|
|
.insert(users)
|
|
.values({
|
|
email,
|
|
passwordHash,
|
|
callsign: callsign.toUpperCase(),
|
|
})
|
|
.returning();
|
|
|
|
// Return user without password hash
|
|
const { passwordHash: _, ...userWithoutPassword } = newUser[0];
|
|
return userWithoutPassword;
|
|
}
|
|
|
|
/**
|
|
* Authenticate user with email and password
|
|
* @param {string} email - User email
|
|
* @param {string} password - Plain text password
|
|
* @returns {Promise<Object>} User object (without password) if authenticated
|
|
* @throws {Error} If credentials are invalid
|
|
*/
|
|
export async function authenticateUser(email, password) {
|
|
// Find user by email
|
|
const [user] = await db
|
|
.select()
|
|
.from(users)
|
|
.where(eq(users.email, email))
|
|
.limit(1);
|
|
|
|
if (!user) {
|
|
return null;
|
|
}
|
|
|
|
// Verify password
|
|
const isValid = await verifyPassword(password, user.passwordHash);
|
|
if (!isValid) {
|
|
return null;
|
|
}
|
|
|
|
// Return user without password hash
|
|
const { passwordHash: _, ...userWithoutPassword } = user;
|
|
return userWithoutPassword;
|
|
}
|
|
|
|
/**
|
|
* Get user by ID
|
|
* @param {number} userId - User ID
|
|
* @returns {Promise<Object|null>} User object (without password) or null
|
|
*/
|
|
export async function getUserById(userId) {
|
|
const [user] = await db
|
|
.select()
|
|
.from(users)
|
|
.where(eq(users.id, userId))
|
|
.limit(1);
|
|
|
|
if (!user) return null;
|
|
|
|
const { passwordHash: _, ...userWithoutPassword } = user;
|
|
return userWithoutPassword;
|
|
}
|
|
|
|
/**
|
|
* Update user's LoTW credentials (encrypted)
|
|
* @param {number} userId - User ID
|
|
* @param {string} lotwUsername - LoTW username
|
|
* @param {string} lotwPassword - LoTW password (will be encrypted)
|
|
* @returns {Promise<void>}
|
|
*/
|
|
export async function updateLoTWCredentials(userId, lotwUsername, lotwPassword) {
|
|
// Simple encryption for storage (in production, use a proper encryption library)
|
|
// For now, we'll store as-is but marked for encryption
|
|
await db
|
|
.update(users)
|
|
.set({
|
|
lotwUsername,
|
|
lotwPassword, // TODO: Encrypt before storing
|
|
updatedAt: new Date(),
|
|
})
|
|
.where(eq(users.id, userId));
|
|
}
|
|
|
|
/**
|
|
* Update user's DCL API key
|
|
* @param {number} userId - User ID
|
|
* @param {string} dclApiKey - DCL API key
|
|
* @returns {Promise<void>}
|
|
*/
|
|
export async function updateDCLCredentials(userId, dclApiKey) {
|
|
await db
|
|
.update(users)
|
|
.set({
|
|
dclApiKey,
|
|
updatedAt: new Date(),
|
|
})
|
|
.where(eq(users.id, userId));
|
|
}
|