diff --git a/apps/api/src/routes/api/v1/handlers.ts b/apps/api/src/routes/api/v1/handlers.ts index 3bdeeca0a..a0217c58a 100644 --- a/apps/api/src/routes/api/v1/handlers.ts +++ b/apps/api/src/routes/api/v1/handlers.ts @@ -3,15 +3,15 @@ import { compareVersions } from "compare-versions"; import cuid from "cuid"; import bcrypt from "bcryptjs"; import { - asyncExecShell, - asyncSleep, - cleanupDockerStorage, - errorHandler, - isDev, - listSettings, - prisma, - uniqueName, - version, + asyncExecShell, + asyncSleep, + cleanupDockerStorage, + errorHandler, + isDev, + listSettings, + prisma, + uniqueName, + version, } from "../../../lib/common"; import { supportedServiceTypesAndVersions } from "../../../lib/services/supportedVersions"; import { scheduler } from "../../../lib/scheduler"; @@ -20,321 +20,321 @@ import type { Login, Update } from "."; import type { GetCurrentUser } from "./types"; export async function hashPassword(password: string): Promise { - const saltRounds = 15; - return bcrypt.hash(password, saltRounds); + const saltRounds = 15; + return bcrypt.hash(password, saltRounds); } export async function cleanupManually(request: FastifyRequest) { - try { - const { serverId } = request.body; - const destination = await prisma.destinationDocker.findUnique({ - where: { id: serverId }, - }); - await cleanupDockerStorage(destination.id, true, true); - return {}; - } catch ({ status, message }) { - return errorHandler({ status, message }); - } + try { + const { serverId } = request.body; + const destination = await prisma.destinationDocker.findUnique({ + where: { id: serverId }, + }); + await cleanupDockerStorage(destination.id, true, true); + return {}; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function checkUpdate(request: FastifyRequest) { - try { - const isStaging = - request.hostname === "staging.coolify.io" || - request.hostname === "arm.coolify.io"; - const currentVersion = version; - const { data: versions } = await axios.get( - `https://get.coollabs.io/versions.json?appId=${process.env["COOLIFY_APP_ID"]}&version=${currentVersion}` - ); - const latestVersion = versions["coolify"].main.version; - const isUpdateAvailable = compareVersions(latestVersion, currentVersion); - if (isStaging) { - return { - isUpdateAvailable: true, - latestVersion: "next", - }; - } - return { - isUpdateAvailable: isStaging ? true : isUpdateAvailable === 1, - latestVersion, - }; - } catch ({ status, message }) { - return errorHandler({ status, message }); - } + try { + const isStaging = + request.hostname === "staging.coolify.io" || + request.hostname === "arm.coolify.io"; + const currentVersion = version; + const { data: versions } = await axios.get( + `https://get.coollabs.io/versions.json?appId=${process.env["COOLIFY_APP_ID"]}&version=${currentVersion}` + ); + const latestVersion = versions["coolify"].main.version; + const isUpdateAvailable = compareVersions(latestVersion, currentVersion); + if (isStaging) { + return { + isUpdateAvailable: true, + latestVersion: "next", + }; + } + return { + isUpdateAvailable: isStaging ? true : isUpdateAvailable === 1, + latestVersion, + }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function update(request: FastifyRequest) { - const { latestVersion } = request.body; - try { - if (!isDev) { - const { isAutoUpdateEnabled } = await prisma.setting.findFirst(); - await asyncExecShell(`docker pull coollabsio/coolify:${latestVersion}`); - await asyncExecShell(`env | grep COOLIFY > .env`); - await asyncExecShell( - `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env` - ); - await asyncExecShell( - `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify && docker rm coolify && docker compose up -d --force-recreate"` - ); - return {}; - } else { - await asyncSleep(2000); - return {}; - } - } catch ({ status, message }) { - return errorHandler({ status, message }); - } + const { latestVersion } = request.body; + try { + if (!isDev) { + const { isAutoUpdateEnabled } = await prisma.setting.findFirst(); + await asyncExecShell(`docker pull coollabsio/coolify:${latestVersion}`); + await asyncExecShell(`env | grep COOLIFY > .env`); + await asyncExecShell( + `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env` + ); + await asyncExecShell( + `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify && docker rm coolify && docker compose up -d --force-recreate"` + ); + return {}; + } else { + await asyncSleep(2000); + return {}; + } + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function resetQueue(request: FastifyRequest) { - try { - const teamId = request.user.teamId; - if (teamId === "0") { - await prisma.build.updateMany({ - where: { status: { in: ["queued", "running"] } }, - data: { status: "canceled" }, - }); - scheduler.workers.get("deployApplication").postMessage("cancel"); - } - } catch ({ status, message }) { - return errorHandler({ status, message }); - } + try { + const teamId = request.user.teamId; + if (teamId === "0") { + await prisma.build.updateMany({ + where: { status: { in: ["queued", "running"] } }, + data: { status: "canceled" }, + }); + scheduler.workers.get("deployApplication").postMessage("cancel"); + } + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function restartCoolify(request: FastifyRequest) { - try { - const teamId = request.user.teamId; - if (teamId === "0") { - if (!isDev) { - asyncExecShell(`docker restart coolify`); - return {}; - } else { - return {}; - } - } - throw { - status: 500, - message: "You are not authorized to restart Coolify.", - }; - } catch ({ status, message }) { - return errorHandler({ status, message }); - } + try { + const teamId = request.user.teamId; + if (teamId === "0") { + if (!isDev) { + asyncExecShell(`docker restart coolify`); + return {}; + } else { + return {}; + } + } + throw { + status: 500, + message: "You are not authorized to restart Coolify.", + }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function showDashboard(request: FastifyRequest) { - try { - const userId = request.user.userId; - const teamId = request.user.teamId; - const applications = await prisma.application.findMany({ - where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } }, - include: { settings: true, destinationDocker: true, teams: true }, - }); - const databases = await prisma.database.findMany({ - where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } }, - include: { settings: true, destinationDocker: true, teams: true }, - }); - const services = await prisma.service.findMany({ - where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } }, - include: { destinationDocker: true, teams: true }, - }); - const gitSources = await prisma.gitSource.findMany({ - where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } }, - include: { teams: true }, - }); - const destinations = await prisma.destinationDocker.findMany({ - where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } }, - include: { teams: true }, - }); - const settings = await listSettings(); - return { - applications, - databases, - services, - gitSources, - destinations, - settings, - }; - } catch ({ status, message }) { - return errorHandler({ status, message }); - } + try { + const userId = request.user.userId; + const teamId = request.user.teamId; + const applications = await prisma.application.findMany({ + where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } }, + include: { settings: true, destinationDocker: true, teams: true }, + }); + const databases = await prisma.database.findMany({ + where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } }, + include: { settings: true, destinationDocker: true, teams: true }, + }); + const services = await prisma.service.findMany({ + where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } }, + include: { destinationDocker: true, teams: true }, + }); + const gitSources = await prisma.gitSource.findMany({ + where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } }, + include: { teams: true }, + }); + const destinations = await prisma.destinationDocker.findMany({ + where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } }, + include: { teams: true }, + }); + const settings = await listSettings(); + return { + applications, + databases, + services, + gitSources, + destinations, + settings, + }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function login( - request: FastifyRequest, - reply: FastifyReply + request: FastifyRequest, + reply: FastifyReply ) { - if (request.user) { - return reply.redirect("/dashboard"); - } else { - const { email, password, isLogin } = request.body || {}; - if (!email || !password) { - throw { status: 500, message: "Email and password are required." }; - } - const users = await prisma.user.count(); - const userFound = await prisma.user.findUnique({ - where: { email }, - include: { teams: true, permission: true }, - rejectOnNotFound: false, - }); - if (!userFound && isLogin) { - throw { status: 500, message: "User not found." }; - } - const { isRegistrationEnabled, id } = await prisma.setting.findFirst(); - let uid = cuid(); - let permission = "read"; - let isAdmin = false; + if (request.user) { + return reply.redirect("/dashboard"); + } else { + const { email, password, isLogin } = request.body || {}; + if (!email || !password) { + throw { status: 500, message: "Email and password are required." }; + } + const users = await prisma.user.count(); + const userFound = await prisma.user.findUnique({ + where: { email }, + include: { teams: true, permission: true }, + rejectOnNotFound: false, + }); + if (!userFound && isLogin) { + throw { status: 500, message: "User not found." }; + } + const { isRegistrationEnabled, id } = await prisma.setting.findFirst(); + let uid = cuid(); + let permission = "read"; + let isAdmin = false; - if (users === 0) { - await prisma.setting.update({ - where: { id }, - data: { isRegistrationEnabled: false }, - }); - uid = "0"; - } - if (userFound) { - if (userFound.type === "email") { - if (userFound.password === "RESETME") { - const hashedPassword = await hashPassword(password); - if (userFound.updatedAt < new Date(Date.now() - 1000 * 60 * 10)) { - if (userFound.id === "0") { - await prisma.user.update({ - where: { email: userFound.email }, - data: { password: "RESETME" }, - }); - } else { - await prisma.user.update({ - where: { email: userFound.email }, - data: { password: "RESETTIMEOUT" }, - }); - } + if (users === 0) { + await prisma.setting.update({ + where: { id }, + data: { isRegistrationEnabled: false }, + }); + uid = "0"; + } + if (userFound) { + if (userFound.type === "email") { + if (userFound.password === "RESETME") { + const hashedPassword = await hashPassword(password); + if (userFound.updatedAt < new Date(Date.now() - 1000 * 60 * 10)) { + if (userFound.id === "0") { + await prisma.user.update({ + where: { email: userFound.email }, + data: { password: "RESETME" }, + }); + } else { + await prisma.user.update({ + where: { email: userFound.email }, + data: { password: "RESETTIMEOUT" }, + }); + } - throw { - status: 500, - message: - "Password reset link has expired. Please request a new one.", - }; - } else { - await prisma.user.update({ - where: { email: userFound.email }, - data: { password: hashedPassword }, - }); - return { - userId: userFound.id, - teamId: userFound.id, - permission: userFound.permission, - isAdmin: true, - }; - } - } + throw { + status: 500, + message: + "Password reset link has expired. Please request a new one.", + }; + } else { + await prisma.user.update({ + where: { email: userFound.email }, + data: { password: hashedPassword }, + }); + return { + userId: userFound.id, + teamId: userFound.id, + permission: userFound.permission, + isAdmin: true, + }; + } + } - const passwordMatch = await bcrypt.compare( - password, - userFound.password - ); - if (!passwordMatch) { - throw { - status: 500, - message: "Wrong password or email address.", - }; - } - uid = userFound.id; - isAdmin = true; - } - } else { - permission = "owner"; - isAdmin = true; - if (!isRegistrationEnabled) { - throw { - status: 404, - message: "Registration disabled by administrator.", - }; - } - const hashedPassword = await hashPassword(password); - if (users === 0) { - await prisma.user.create({ - data: { - id: uid, - email, - password: hashedPassword, - type: "email", - teams: { - create: { - id: uid, - name: uniqueName(), - destinationDocker: { connect: { network: "coolify" } }, - }, - }, - permission: { create: { teamId: uid, permission: "owner" } }, - }, - include: { teams: true }, - }); - } else { - await prisma.user.create({ - data: { - id: uid, - email, - password: hashedPassword, - type: "email", - teams: { - create: { - id: uid, - name: uniqueName(), - }, - }, - permission: { create: { teamId: uid, permission: "owner" } }, - }, - include: { teams: true }, - }); - } - } - return { - userId: uid, - teamId: uid, - permission, - isAdmin, - }; - } + const passwordMatch = await bcrypt.compare( + password, + userFound.password + ); + if (!passwordMatch) { + throw { + status: 500, + message: "Wrong password or email address.", + }; + } + uid = userFound.id; + isAdmin = true; + } + } else { + permission = "owner"; + isAdmin = true; + if (!isRegistrationEnabled) { + throw { + status: 404, + message: "Registration disabled by administrator.", + }; + } + const hashedPassword = await hashPassword(password); + if (users === 0) { + await prisma.user.create({ + data: { + id: uid, + email, + password: hashedPassword, + type: "email", + teams: { + create: { + id: uid, + name: uniqueName(), + destinationDocker: { connect: { network: "coolify" } }, + }, + }, + permission: { create: { teamId: uid, permission: "owner" } }, + }, + include: { teams: true }, + }); + } else { + await prisma.user.create({ + data: { + id: uid, + email, + password: hashedPassword, + type: "email", + teams: { + create: { + id: uid, + name: uniqueName(), + }, + }, + permission: { create: { teamId: uid, permission: "owner" } }, + }, + include: { teams: true }, + }); + } + } + return { + userId: uid, + teamId: uid, + permission, + isAdmin, + }; + } } export async function getCurrentUser( - request: FastifyRequest, - fastify + request: FastifyRequest, + fastify ) { - let token = null; - const { teamId } = request.query; - try { - const user = await prisma.user.findUnique({ - where: { id: request.user.userId }, - }); - if (!user) { - throw "User not found"; - } - } catch (error) { - throw { status: 401, message: error }; - } - if (teamId) { - try { - const user = await prisma.user.findFirst({ - where: { id: request.user.userId, teams: { some: { id: teamId } } }, - include: { teams: true, permission: true }, - }); - if (user) { - const permission = user.permission.find( - (p) => p.teamId === teamId - ).permission; - const payload = { - ...request.user, - teamId, - permission: permission || null, - isAdmin: permission === "owner" || permission === "admin", - }; - token = fastify.jwt.sign(payload); - } - } catch (error) { - // No new token -> not switching teams - } - } - return { - settings: await prisma.setting.findFirst(), - supportedServiceTypesAndVersions, - token, - ...request.user, - }; + let token = null; + const { teamId } = request.query; + try { + const user = await prisma.user.findUnique({ + where: { id: request.user.userId }, + }); + if (!user) { + throw "User not found"; + } + } catch (error) { + throw { status: 401, message: error }; + } + if (teamId) { + try { + const user = await prisma.user.findFirst({ + where: { id: request.user.userId, teams: { some: { id: teamId } } }, + include: { teams: true, permission: true }, + }); + if (user) { + const permission = user.permission.find( + (p) => p.teamId === teamId + ).permission; + const payload = { + ...request.user, + teamId, + permission: permission || null, + isAdmin: permission === "owner" || permission === "admin", + }; + token = fastify.jwt.sign(payload); + } + } catch (error) { + // No new token -> not switching teams + } + } + return { + settings: await prisma.setting.findFirst(), + supportedServiceTypesAndVersions, + token, + ...request.user, + }; }