Files
spaceward-database/src/routes/auth.routes.ts

231 lines
5.7 KiB
TypeScript

import express from 'express';
import bcrypt from 'bcrypt';
import { prisma } from '../db';
import axios from 'axios';
import crypto from 'crypto';
import { encrypt, decrypt } from '../utils/encryption';
const router = express.Router();
const SALT_ROUNDS = 12;
const SESSION_DURATION = 7 * 24 * 60 * 60 * 1000; // 7 days
// Middleware to check if user is authenticated
export async function requireAuth(req: any, res: any, next: any) {
const sessionToken = req.cookies?.sessionToken;
if (!sessionToken) {
return res.status(401).json({ error: 'Not authenticated' });
}
try {
const session = await prisma.session.findUnique({
where: { token: sessionToken },
include: { user: true }
});
if (!session || session.expiresAt < new Date()) {
return res.status(401).json({ error: 'Session expired' });
}
req.user = session.user;
next();
} catch (error) {
return res.status(500).json({ error: 'Authentication error' });
}
}
// Register new user
router.post('/register', async (req, res) => {
const { username, password } = req.body;
if (!username || !password) {
return res.status(400).json({ error: 'Username and password required' });
}
if (password.length < 8) {
return res.status(400).json({ error: 'Password must be at least 8 characters' });
}
const normalizedUsername = username.toLowerCase().trim();
try {
const existingUser = await prisma.user.findFirst({
where: {
username: {
equals: normalizedUsername,
mode: 'insensitive'
}
}
});
if (existingUser) {
return res.status(409).json({ error: 'Username already exists' });
}
// Verify credentials with Skyward via port 3000 API
console.log('Verifying Skyward credentials...');
try {
const authCheck = await axios.post('http://localhost:3000/check-auth', {
username,
password
}, {
timeout: 30000
});
if (!authCheck.data.success) {
return res.status(401).json({
error: 'Invalid Skyward credentials. Please verify your username and password.'
});
}
console.log('Skyward credentials verified successfully');
} catch (authError: any) {
if (authError.response?.status === 401) {
return res.status(401).json({
error: 'Invalid Skyward credentials. Please verify your username and password.'
});
}
console.error('Skyward auth check failed:', authError.message);
return res.status(500).json({
error: 'Unable to verify credentials with Skyward. Please try again.'
});
}
// Hash password for login authentication
const hashedPassword = await bcrypt.hash(password, SALT_ROUNDS);
// Encrypt password for Skyward API calls
const encryptedSkywardPassword = encrypt(password);
const user = await prisma.user.create({
data: {
username: normalizedUsername,
password: hashedPassword,
skywardPassword: encryptedSkywardPassword
}
});
console.log('User created successfully');
const sessionToken = crypto.randomBytes(32).toString('hex');
const expiresAt = new Date(Date.now() + SESSION_DURATION);
await prisma.session.create({
data: {
token: sessionToken,
userId: user.id,
expiresAt
}
});
res.cookie('sessionToken', sessionToken, {
httpOnly: true,
secure: false, // set to true in production
sameSite: 'lax',
maxAge: SESSION_DURATION
});
return res.json({
success: true,
user: {
id: user.id,
username: user.username
}
});
} catch (error) {
console.error('Registration error:', error);
return res.status(500).json({ error: 'Registration failed' });
}
});
// Login existing user
router.post('/login', async (req, res) => {
const { username, password } = req.body;
if (!username || !password) {
return res.status(400).json({ error: 'Username and password required' });
}
const normalizedUsername = username.toLowerCase().trim();
try {
const user = await prisma.user.findFirst({
where: {
username: {
equals: normalizedUsername,
mode: 'insensitive'
}
}
});
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const validPassword = await bcrypt.compare(password, user.password);
if (!validPassword) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const sessionToken = crypto.randomBytes(32).toString('hex');
const expiresAt = new Date(Date.now() + SESSION_DURATION);
await prisma.session.create({
data: {
token: sessionToken,
userId: user.id,
expiresAt
}
});
res.cookie('sessionToken', sessionToken, {
httpOnly: true,
secure: false,
sameSite: 'lax',
maxAge: SESSION_DURATION
});
return res.json({
success: true,
user: {
id: user.id,
username: user.username
}
});
} catch (error) {
console.error('Login error:', error);
return res.status(500).json({ error: 'Login failed' });
}
});
// Logout
router.post('/logout', async (req, res) => {
const sessionToken = req.cookies?.sessionToken;
if (sessionToken) {
try {
await prisma.session.delete({
where: { token: sessionToken }
});
} catch (error) {
// Session might not exist
}
}
res.clearCookie('sessionToken');
return res.json({ success: true });
});
// Check auth status
router.get('/me', requireAuth, async (req: any, res) => {
return res.json({
user: {
id: req.user.id,
username: req.user.username
}
});
});
export default router;