160 lines
3.8 KiB
TypeScript
160 lines
3.8 KiB
TypeScript
import { db } from "./db";
|
|
import { users, sessions, projectPermissions, projects } from "./db/schema";
|
|
import { eq, and, or, gte } from "drizzle-orm";
|
|
import { randomBytes } from "crypto";
|
|
import Argon2id from "@rabbit-company/argon2id";
|
|
|
|
export async function passwordMatches(password: string, storedPassword: string) {
|
|
return Argon2id.verify(storedPassword, password);
|
|
}
|
|
|
|
export async function hashPassword(password: string) {
|
|
return Argon2id.hashEncoded(password);
|
|
}
|
|
|
|
// Generate a random session ID
|
|
function generateSessionId(): string {
|
|
return randomBytes(32).toString("hex");
|
|
}
|
|
|
|
// Create a new user
|
|
export async function createUser(username: string, password: string) {
|
|
const userId = randomBytes(16).toString("hex");
|
|
const now = new Date();
|
|
const hashedPassword = await Argon2id.hashEncoded(password);
|
|
|
|
await db.insert(users).values({
|
|
id: userId,
|
|
username,
|
|
password: hashedPassword,
|
|
createdAt: now,
|
|
updatedAt: now
|
|
});
|
|
|
|
return userId;
|
|
}
|
|
|
|
// Authenticate user
|
|
export async function authenticateUser(username: string, password: string) {
|
|
const user = await db.select().from(users).where(eq(users.username, username)).get();
|
|
if (!user) return null;
|
|
const verified = await passwordMatches(password, user.password);
|
|
if (!verified) {
|
|
return null;
|
|
}
|
|
|
|
return user;
|
|
}
|
|
|
|
// Create a session
|
|
export async function createSession(userId: string) {
|
|
const sessionId = generateSessionId();
|
|
const expiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); // 30 days
|
|
|
|
await db.insert(sessions).values({
|
|
id: sessionId,
|
|
userId,
|
|
expiresAt,
|
|
createdAt: new Date()
|
|
});
|
|
|
|
return sessionId;
|
|
}
|
|
|
|
// Validate session
|
|
export async function validateSession(sessionId: string) {
|
|
const session = await db
|
|
.select()
|
|
.from(sessions)
|
|
.where(and(eq(sessions.id, sessionId), gte(sessions.expiresAt, new Date())))
|
|
.get();
|
|
|
|
if (!session) {
|
|
return null;
|
|
}
|
|
|
|
// Get user data
|
|
const user = await db.select().from(users).where(eq(users.id, session.userId)).get();
|
|
|
|
return user;
|
|
}
|
|
|
|
// Check if user can edit project
|
|
export async function canUserEditProject(userId: string, projectId: string) {
|
|
// Admin users can edit all projects
|
|
const user = await db.select().from(users).where(eq(users.id, userId)).get();
|
|
if (user?.isAdmin) {
|
|
return true;
|
|
}
|
|
|
|
// Check if user is project owner
|
|
const project = await db
|
|
.select()
|
|
.from(projects)
|
|
.where(and(eq(projects.id, projectId), eq(projects.ownerId, userId)))
|
|
.get();
|
|
if (project) {
|
|
return true;
|
|
}
|
|
|
|
// Check if user has edit permission
|
|
const permission = await db
|
|
.select()
|
|
.from(projectPermissions)
|
|
.where(
|
|
and(
|
|
eq(projectPermissions.projectId, projectId),
|
|
eq(projectPermissions.userId, userId)
|
|
)
|
|
)
|
|
.get();
|
|
|
|
return !!permission;
|
|
}
|
|
|
|
// Check if user can view project
|
|
export async function canUserViewProject(userID: string, projectId: string) {
|
|
if (await canUserEditProject(userID, projectId)) {
|
|
return true;
|
|
}
|
|
|
|
// Check if project is public
|
|
const project = await db
|
|
.select()
|
|
.from(projects)
|
|
.where(and(eq(projects.id, projectId), eq(projects.isPublic, true)))
|
|
.get();
|
|
|
|
return !!project;
|
|
}
|
|
|
|
// Get projects accessible to user
|
|
export async function getUserProjects(userId: string) {
|
|
const user = await db.select().from(users).where(eq(users.id, userId)).get();
|
|
|
|
if (user?.isAdmin) {
|
|
// Admin can see all projects
|
|
return await db.select().from(projects).all();
|
|
}
|
|
|
|
// Get projects where user is owner or has permission
|
|
const accessibleProjects = await db
|
|
.select()
|
|
.from(projects)
|
|
.where(
|
|
or(
|
|
eq(projects.ownerId, userId),
|
|
eq(projectPermissions.userId, userId)
|
|
)
|
|
)
|
|
.leftJoin(projectPermissions, eq(projects.id, projectPermissions.projectId))
|
|
.all();
|
|
|
|
return accessibleProjects.map(row => row.projects);
|
|
}
|
|
|
|
// Delete session (logout)
|
|
export async function deleteSession(sessionId: string) {
|
|
await db.delete(sessions).where(eq(sessions.id, sessionId));
|
|
}
|