mirror of
https://github.com/nicotsx/ironmount.git
synced 2025-12-10 12:10:51 +01:00
180 lines
4.3 KiB
TypeScript
180 lines
4.3 KiB
TypeScript
import { eq, lt } from "drizzle-orm";
|
|
import { db } from "../../db/db";
|
|
import { sessionsTable, usersTable } from "../../db/schema";
|
|
import { logger } from "../../utils/logger";
|
|
|
|
const SESSION_DURATION = 60 * 60 * 24 * 30; // 30 days in seconds
|
|
|
|
export class AuthService {
|
|
/**
|
|
* Register a new user with username and password
|
|
*/
|
|
async register(username: string, password: string) {
|
|
const [existingUser] = await db.select().from(usersTable);
|
|
|
|
if (existingUser) {
|
|
throw new Error("Admin user already exists");
|
|
}
|
|
|
|
const passwordHash = await Bun.password.hash(password, {
|
|
algorithm: "argon2id",
|
|
memoryCost: 19456,
|
|
timeCost: 2,
|
|
});
|
|
|
|
const [user] = await db.insert(usersTable).values({ username, passwordHash }).returning();
|
|
|
|
if (!user) {
|
|
throw new Error("User registration failed");
|
|
}
|
|
|
|
logger.info(`User registered: ${username}`);
|
|
const sessionId = crypto.randomUUID();
|
|
const expiresAt = Math.floor(Date.now() / 1000) + SESSION_DURATION;
|
|
|
|
await db.insert(sessionsTable).values({
|
|
id: sessionId,
|
|
userId: user.id,
|
|
expiresAt,
|
|
});
|
|
|
|
return {
|
|
user: {
|
|
id: user.id,
|
|
username: user.username,
|
|
createdAt: user.createdAt,
|
|
hasDownloadedResticPassword: user.hasDownloadedResticPassword,
|
|
},
|
|
sessionId,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Login user with username and password
|
|
*/
|
|
async login(username: string, password: string) {
|
|
const [user] = await db.select().from(usersTable).where(eq(usersTable.username, username));
|
|
|
|
if (!user) {
|
|
throw new Error("Invalid credentials");
|
|
}
|
|
|
|
const isValid = await Bun.password.verify(password, user.passwordHash);
|
|
|
|
if (!isValid) {
|
|
throw new Error("Invalid credentials");
|
|
}
|
|
|
|
const sessionId = crypto.randomUUID();
|
|
const expiresAt = Math.floor(Date.now() / 1000) + SESSION_DURATION;
|
|
|
|
await db.insert(sessionsTable).values({
|
|
id: sessionId,
|
|
userId: user.id,
|
|
expiresAt,
|
|
});
|
|
|
|
logger.info(`User logged in: ${username}`);
|
|
|
|
return {
|
|
sessionId,
|
|
user: {
|
|
id: user.id,
|
|
username: user.username,
|
|
hasDownloadedResticPassword: user.hasDownloadedResticPassword,
|
|
},
|
|
expiresAt,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Logout user by deleting their session
|
|
*/
|
|
async logout(sessionId: string) {
|
|
await db.delete(sessionsTable).where(eq(sessionsTable.id, sessionId));
|
|
logger.info(`User logged out: session ${sessionId}`);
|
|
}
|
|
|
|
/**
|
|
* Verify a session and return the associated user
|
|
*/
|
|
async verifySession(sessionId: string) {
|
|
const [session] = await db
|
|
.select({
|
|
session: sessionsTable,
|
|
user: usersTable,
|
|
})
|
|
.from(sessionsTable)
|
|
.innerJoin(usersTable, eq(sessionsTable.userId, usersTable.id))
|
|
.where(eq(sessionsTable.id, sessionId));
|
|
|
|
if (!session) {
|
|
return null;
|
|
}
|
|
|
|
if (session.session.expiresAt < Math.floor(Date.now() / 1000)) {
|
|
await db.delete(sessionsTable).where(eq(sessionsTable.id, sessionId));
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
user: {
|
|
id: session.user.id,
|
|
username: session.user.username,
|
|
hasDownloadedResticPassword: session.user.hasDownloadedResticPassword,
|
|
},
|
|
session: {
|
|
id: session.session.id,
|
|
expiresAt: session.session.expiresAt,
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Clean up expired sessions
|
|
*/
|
|
async cleanupExpiredSessions() {
|
|
const result = await db.delete(sessionsTable).where(lt(sessionsTable.expiresAt, Math.floor(Date.now() / 1000))).returning();
|
|
if (result.length > 0) {
|
|
logger.info(`Cleaned up ${result.length} expired sessions`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if any users exist in the system
|
|
*/
|
|
async hasUsers(): Promise<boolean> {
|
|
const [user] = await db.select({ id: usersTable.id }).from(usersTable).limit(1);
|
|
return !!user;
|
|
}
|
|
|
|
/**
|
|
* Change password for a user
|
|
*/
|
|
async changePassword(userId: number, currentPassword: string, newPassword: string) {
|
|
const [user] = await db.select().from(usersTable).where(eq(usersTable.id, userId));
|
|
|
|
if (!user) {
|
|
throw new Error("User not found");
|
|
}
|
|
|
|
const isValid = await Bun.password.verify(currentPassword, user.passwordHash);
|
|
|
|
if (!isValid) {
|
|
throw new Error("Current password is incorrect");
|
|
}
|
|
|
|
const newPasswordHash = await Bun.password.hash(newPassword, {
|
|
algorithm: "argon2id",
|
|
memoryCost: 19456,
|
|
timeCost: 2,
|
|
});
|
|
|
|
await db.update(usersTable).set({ passwordHash: newPasswordHash }).where(eq(usersTable.id, userId));
|
|
|
|
logger.info(`Password changed for user: ${user.username}`);
|
|
}
|
|
}
|
|
|
|
export const authService = new AuthService();
|