Add player functionality with HLS support and API integration
- Implemented a new Player class in player.js to handle audio playback, including HLS support using hls.js. - Created a shared API module in api.js for making HTTP requests with proper error handling. - Added DOM utility functions in dom.js for creating and clearing elements. - Introduced WebSocket connection handling in ws.js for real-time updates. - Developed a comprehensive CSS stylesheet for styling the application, including a high-contrast theme.
This commit is contained in:
88
server/auth.js
Normal file
88
server/auth.js
Normal file
@@ -0,0 +1,88 @@
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
import { getDb } from './db/index.js';
|
||||
|
||||
const SESSION_DAYS = 30;
|
||||
const COOKIE_NAME = 'oradio_sid';
|
||||
|
||||
export function hashPassword(plain) {
|
||||
return bcrypt.hashSync(plain, 10);
|
||||
}
|
||||
export function verifyPassword(plain, hash) {
|
||||
return bcrypt.compareSync(plain, hash);
|
||||
}
|
||||
|
||||
export function createSession(userId) {
|
||||
const token = randomBytes(32).toString('hex');
|
||||
const expires = new Date(Date.now() + SESSION_DAYS * 86400e3).toISOString();
|
||||
getDb().prepare('INSERT INTO sessions (token, user_id, expires_at) VALUES (?, ?, ?)')
|
||||
.run(token, userId, expires);
|
||||
return { token, expires };
|
||||
}
|
||||
|
||||
export function destroySession(token) {
|
||||
if (token) getDb().prepare('DELETE FROM sessions WHERE token = ?').run(token);
|
||||
}
|
||||
|
||||
export function getUserBySession(token) {
|
||||
if (!token) return null;
|
||||
return getDb().prepare(`
|
||||
SELECT u.id, u.username, u.role
|
||||
FROM sessions s JOIN users u ON u.id = s.user_id
|
||||
WHERE s.token = ? AND s.expires_at > datetime('now')
|
||||
`).get(token);
|
||||
}
|
||||
|
||||
export function readSessionToken(req) {
|
||||
const raw = req.headers.cookie || '';
|
||||
for (const part of raw.split(';')) {
|
||||
const [k, v] = part.trim().split('=');
|
||||
if (k === COOKIE_NAME) return decodeURIComponent(v || '');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function setSessionCookie(res, token, expires) {
|
||||
const attrs = [
|
||||
`${COOKIE_NAME}=${encodeURIComponent(token)}`,
|
||||
'Path=/',
|
||||
'HttpOnly',
|
||||
'SameSite=Lax',
|
||||
`Expires=${new Date(expires).toUTCString()}`
|
||||
];
|
||||
res.setHeader('Set-Cookie', attrs.join('; '));
|
||||
}
|
||||
|
||||
export function clearSessionCookie(res) {
|
||||
res.setHeader('Set-Cookie', `${COOKIE_NAME}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`);
|
||||
}
|
||||
|
||||
export function authMiddleware(req, _res, next) {
|
||||
const token = readSessionToken(req);
|
||||
req.session = { token };
|
||||
req.user = getUserBySession(token);
|
||||
next();
|
||||
}
|
||||
|
||||
export function requireUser(req, res, next) {
|
||||
if (!req.user) return res.status(401).json({ error: 'auth required' });
|
||||
next();
|
||||
}
|
||||
|
||||
export function requireAdmin(req, res, next) {
|
||||
if (!req.user) return res.status(401).json({ error: 'auth required' });
|
||||
if (req.user.role !== 'admin') return res.status(403).json({ error: 'admin only' });
|
||||
next();
|
||||
}
|
||||
|
||||
export function ensureBootstrapAdmin({ username, password }) {
|
||||
if (!username || !password) return;
|
||||
const db = getDb();
|
||||
const existing = db.prepare('SELECT id FROM users WHERE username = ?').get(username);
|
||||
if (existing) return;
|
||||
const info = db.prepare('INSERT INTO users (username, password_hash, role) VALUES (?, ?, ?)')
|
||||
.run(username, hashPassword(password), 'admin');
|
||||
db.prepare('INSERT INTO profiles (user_id, display_name) VALUES (?, ?)')
|
||||
.run(info.lastInsertRowid, username);
|
||||
console.log(`[auth] bootstrap admin '${username}' created`);
|
||||
}
|
||||
Reference in New Issue
Block a user