mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
refactor: add authorization
This commit is contained in:
parent
7cfbea3f60
commit
3cf27a068a
@ -7,12 +7,28 @@ import { createClient } from "redis";
|
|||||||
import { logger } from "./logger";
|
import { logger } from "./logger";
|
||||||
import { type DeployJob, deployJobSchema } from "./schema";
|
import { type DeployJob, deployJobSchema } from "./schema";
|
||||||
import { deploy } from "./utils";
|
import { deploy } from "./utils";
|
||||||
|
import { validateBearerTokenAPI } from "@dokploy/server";
|
||||||
|
|
||||||
const app = new Hono();
|
const app = new Hono();
|
||||||
const redisClient = createClient({
|
const redisClient = createClient({
|
||||||
url: process.env.REDIS_URL,
|
url: process.env.REDIS_URL,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.use(async (c, next) => {
|
||||||
|
const authHeader = c.req.header("authorization");
|
||||||
|
|
||||||
|
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
||||||
|
return c.json({ message: "Authorization header missing" }, 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await validateBearerTokenAPI(authHeader);
|
||||||
|
|
||||||
|
if (!result.user || !result.session) {
|
||||||
|
return c.json({ message: "Invalid session" }, 403);
|
||||||
|
}
|
||||||
|
return next();
|
||||||
|
});
|
||||||
|
|
||||||
app.post("/deploy", zValidator("json", deployJobSchema), (c) => {
|
app.post("/deploy", zValidator("json", deployJobSchema), (c) => {
|
||||||
const data = c.req.valid("json");
|
const data = c.req.valid("json");
|
||||||
const res = queue.add(data, { groupName: data.serverId });
|
const res = queue.add(data, { groupName: data.serverId });
|
||||||
|
@ -254,7 +254,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
|
|
||||||
if (IS_CLOUD && application.serverId) {
|
if (IS_CLOUD && application.serverId) {
|
||||||
jobData.serverId = application.serverId;
|
jobData.serverId = application.serverId;
|
||||||
await deploy(jobData);
|
await deploy(jobData, ctx.session.id);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
await myQueue.add(
|
await myQueue.add(
|
||||||
@ -482,7 +482,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
};
|
};
|
||||||
if (IS_CLOUD && application.serverId) {
|
if (IS_CLOUD && application.serverId) {
|
||||||
jobData.serverId = application.serverId;
|
jobData.serverId = application.serverId;
|
||||||
await deploy(jobData);
|
await deploy(jobData, ctx.session.id);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -571,7 +571,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
};
|
};
|
||||||
if (IS_CLOUD && app.serverId) {
|
if (IS_CLOUD && app.serverId) {
|
||||||
jobData.serverId = app.serverId;
|
jobData.serverId = app.serverId;
|
||||||
await deploy(jobData);
|
await deploy(jobData, ctx.session.id);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
apiRemoveBackup,
|
apiRemoveBackup,
|
||||||
apiUpdateBackup,
|
apiUpdateBackup,
|
||||||
} from "@/server/db/schema";
|
} from "@/server/db/schema";
|
||||||
import { removeJob, schedule } from "@/server/utils/backup";
|
import { removeJob, schedule, updateJob } from "@/server/utils/backup";
|
||||||
import {
|
import {
|
||||||
IS_CLOUD,
|
IS_CLOUD,
|
||||||
createBackup,
|
createBackup,
|
||||||
@ -29,18 +29,21 @@ import { TRPCError } from "@trpc/server";
|
|||||||
export const backupRouter = createTRPCRouter({
|
export const backupRouter = createTRPCRouter({
|
||||||
create: protectedProcedure
|
create: protectedProcedure
|
||||||
.input(apiCreateBackup)
|
.input(apiCreateBackup)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
try {
|
try {
|
||||||
const newBackup = await createBackup(input);
|
const newBackup = await createBackup(input);
|
||||||
|
|
||||||
const backup = await findBackupById(newBackup.backupId);
|
const backup = await findBackupById(newBackup.backupId);
|
||||||
|
|
||||||
if (IS_CLOUD && backup.enabled) {
|
if (IS_CLOUD && backup.enabled) {
|
||||||
await schedule({
|
await schedule(
|
||||||
cronSchedule: backup.schedule,
|
{
|
||||||
backupId: backup.backupId,
|
cronSchedule: backup.schedule,
|
||||||
type: "backup",
|
backupId: backup.backupId,
|
||||||
});
|
type: "backup",
|
||||||
|
},
|
||||||
|
ctx.session.id,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
if (backup.enabled) {
|
if (backup.enabled) {
|
||||||
scheduleBackup(backup);
|
scheduleBackup(backup);
|
||||||
@ -60,17 +63,31 @@ export const backupRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
update: protectedProcedure
|
update: protectedProcedure
|
||||||
.input(apiUpdateBackup)
|
.input(apiUpdateBackup)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
try {
|
try {
|
||||||
await updateBackupById(input.backupId, input);
|
await updateBackupById(input.backupId, input);
|
||||||
const backup = await findBackupById(input.backupId);
|
const backup = await findBackupById(input.backupId);
|
||||||
|
|
||||||
if (IS_CLOUD && backup.enabled) {
|
if (IS_CLOUD) {
|
||||||
await schedule({
|
if (backup.enabled) {
|
||||||
cronSchedule: backup.schedule,
|
await updateJob(
|
||||||
backupId: backup.backupId,
|
{
|
||||||
type: "backup",
|
cronSchedule: backup.schedule,
|
||||||
});
|
backupId: backup.backupId,
|
||||||
|
type: "backup",
|
||||||
|
},
|
||||||
|
ctx.session.id,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await removeJob(
|
||||||
|
{
|
||||||
|
cronSchedule: backup.schedule,
|
||||||
|
backupId: backup.backupId,
|
||||||
|
type: "backup",
|
||||||
|
},
|
||||||
|
ctx.session.id,
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (backup.enabled) {
|
if (backup.enabled) {
|
||||||
removeScheduleBackup(input.backupId);
|
removeScheduleBackup(input.backupId);
|
||||||
@ -88,16 +105,19 @@ export const backupRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
remove: protectedProcedure
|
remove: protectedProcedure
|
||||||
.input(apiRemoveBackup)
|
.input(apiRemoveBackup)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
try {
|
try {
|
||||||
const value = await removeBackupById(input.backupId);
|
const value = await removeBackupById(input.backupId);
|
||||||
if (IS_CLOUD && value) {
|
if (IS_CLOUD && value) {
|
||||||
removeJob({
|
removeJob(
|
||||||
backupId: input.backupId,
|
{
|
||||||
cronSchedule: value.schedule,
|
backupId: input.backupId,
|
||||||
type: "backup",
|
cronSchedule: value.schedule,
|
||||||
});
|
type: "backup",
|
||||||
} else {
|
},
|
||||||
|
ctx.session.id,
|
||||||
|
);
|
||||||
|
} else if (!IS_CLOUD) {
|
||||||
removeScheduleBackup(input.backupId);
|
removeScheduleBackup(input.backupId);
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
|
@ -256,7 +256,7 @@ export const composeRouter = createTRPCRouter({
|
|||||||
|
|
||||||
if (IS_CLOUD && compose.serverId) {
|
if (IS_CLOUD && compose.serverId) {
|
||||||
jobData.serverId = compose.serverId;
|
jobData.serverId = compose.serverId;
|
||||||
await deploy(jobData);
|
await deploy(jobData, ctx.session.id);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
await myQueue.add(
|
await myQueue.add(
|
||||||
@ -288,7 +288,7 @@ export const composeRouter = createTRPCRouter({
|
|||||||
};
|
};
|
||||||
if (IS_CLOUD && compose.serverId) {
|
if (IS_CLOUD && compose.serverId) {
|
||||||
jobData.serverId = compose.serverId;
|
jobData.serverId = compose.serverId;
|
||||||
await deploy(jobData);
|
await deploy(jobData, ctx.session.id);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
await myQueue.add(
|
await myQueue.add(
|
||||||
|
@ -9,12 +9,13 @@ type QueueJob =
|
|||||||
cronSchedule: string;
|
cronSchedule: string;
|
||||||
serverId: string;
|
serverId: string;
|
||||||
};
|
};
|
||||||
export const schedule = async (job: QueueJob) => {
|
export const schedule = async (job: QueueJob, authSession: string) => {
|
||||||
try {
|
try {
|
||||||
const result = await fetch(`${process.env.JOBS_URL}/create-backup`, {
|
const result = await fetch(`${process.env.JOBS_URL}/create-backup`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${authSession}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify(job),
|
body: JSON.stringify(job),
|
||||||
});
|
});
|
||||||
@ -27,12 +28,32 @@ export const schedule = async (job: QueueJob) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const removeJob = async (job: QueueJob) => {
|
export const removeJob = async (job: QueueJob, authSession: string) => {
|
||||||
try {
|
try {
|
||||||
const result = await fetch(`${process.env.JOBS_URL}/remove-job`, {
|
const result = await fetch(`${process.env.JOBS_URL}/remove-job`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${authSession}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify(job),
|
||||||
|
});
|
||||||
|
const data = await result.json();
|
||||||
|
console.log(data);
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateJob = async (job: QueueJob, authSession: string) => {
|
||||||
|
try {
|
||||||
|
const result = await fetch(`${process.env.JOBS_URL}/update-backup`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${authSession}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify(job),
|
body: JSON.stringify(job),
|
||||||
});
|
});
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import type { DeploymentJob } from "../queues/deployments-queue";
|
import type { DeploymentJob } from "../queues/deployments-queue";
|
||||||
|
|
||||||
export const deploy = async (jobData: DeploymentJob) => {
|
export const deploy = async (jobData: DeploymentJob, sessionId: string) => {
|
||||||
try {
|
try {
|
||||||
const result = await fetch(`${process.env.SERVER_URL}/deploy`, {
|
const result = await fetch(`${process.env.SERVER_URL}/deploy`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${sessionId}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify(jobData),
|
body: JSON.stringify(jobData),
|
||||||
});
|
});
|
||||||
|
@ -3,22 +3,62 @@ import { Hono } from "hono";
|
|||||||
import "dotenv/config";
|
import "dotenv/config";
|
||||||
import { zValidator } from "@hono/zod-validator";
|
import { zValidator } from "@hono/zod-validator";
|
||||||
import { logger } from "./logger";
|
import { logger } from "./logger";
|
||||||
import { cleanQueue, removeJob, scheduleJob } from "./queue";
|
import { cleanQueue, getJobRepeatable, removeJob, scheduleJob } from "./queue";
|
||||||
import { jobQueueSchema } from "./schema";
|
import { jobQueueSchema } from "./schema";
|
||||||
import { firstWorker, secondWorker } from "./workers";
|
import { firstWorker, secondWorker } from "./workers";
|
||||||
|
import { validateBearerTokenAPI } from "@dokploy/server";
|
||||||
|
|
||||||
const app = new Hono();
|
const app = new Hono();
|
||||||
|
|
||||||
cleanQueue();
|
cleanQueue();
|
||||||
|
|
||||||
app.post("/create-backup", zValidator("json", jobQueueSchema), (c) => {
|
app.use(async (c, next) => {
|
||||||
|
const authHeader = c.req.header("authorization");
|
||||||
|
|
||||||
|
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
||||||
|
return c.json({ message: "Authorization header missing" }, 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await validateBearerTokenAPI(authHeader);
|
||||||
|
|
||||||
|
if (!result.user || !result.session) {
|
||||||
|
return c.json({ message: "Invalid session" }, 403);
|
||||||
|
}
|
||||||
|
return next();
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/create-backup", zValidator("json", jobQueueSchema), async (c) => {
|
||||||
const data = c.req.valid("json");
|
const data = c.req.valid("json");
|
||||||
scheduleJob(data);
|
scheduleJob(data);
|
||||||
|
|
||||||
logger.info("Backup created successfully", data);
|
logger.info("Backup created successfully", data);
|
||||||
return c.json({ message: "Backup created successfully" });
|
return c.json({ message: "Backup created successfully" });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.post("/update-backup", zValidator("json", jobQueueSchema), async (c) => {
|
||||||
|
const data = c.req.valid("json");
|
||||||
|
const job = await getJobRepeatable(data);
|
||||||
|
if (job) {
|
||||||
|
let result = false;
|
||||||
|
if (data.type === "backup") {
|
||||||
|
result = await removeJob({
|
||||||
|
backupId: data.backupId,
|
||||||
|
type: "backup",
|
||||||
|
cronSchedule: job.pattern,
|
||||||
|
});
|
||||||
|
} else if (data.type === "server") {
|
||||||
|
result = await removeJob({
|
||||||
|
serverId: data.serverId,
|
||||||
|
type: "server",
|
||||||
|
cronSchedule: job.pattern,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
logger.info("Job removed", result);
|
||||||
|
}
|
||||||
|
scheduleJob(data);
|
||||||
|
|
||||||
|
return c.json({ message: "Backup updated successfully" });
|
||||||
|
});
|
||||||
|
|
||||||
app.post("/remove-job", zValidator("json", jobQueueSchema), async (c) => {
|
app.post("/remove-job", zValidator("json", jobQueueSchema), async (c) => {
|
||||||
const data = c.req.valid("json");
|
const data = c.req.valid("json");
|
||||||
const result = await removeJob(data);
|
const result = await removeJob(data);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Queue } from "bullmq";
|
import { Queue, type RepeatableJob } from "bullmq";
|
||||||
import { logger } from "./logger";
|
import { logger } from "./logger";
|
||||||
import type { QueueJob } from "./schema";
|
import type { QueueJob } from "./schema";
|
||||||
|
|
||||||
@ -52,4 +52,24 @@ export const removeJob = async (data: QueueJob) => {
|
|||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getJobRepeatable = async (
|
||||||
|
data: QueueJob,
|
||||||
|
): Promise<RepeatableJob | null> => {
|
||||||
|
const repeatableJobs = await jobQueue.getRepeatableJobs();
|
||||||
|
if (data.type === "backup") {
|
||||||
|
const { backupId } = data;
|
||||||
|
const job = repeatableJobs.find((j) => j.name === backupId);
|
||||||
|
return job ? job : null;
|
||||||
|
}
|
||||||
|
if (data.type === "server") {
|
||||||
|
const { serverId } = data;
|
||||||
|
const job = repeatableJobs.find((j) => j.name === `${serverId}-cleanup`);
|
||||||
|
return job ? job : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
};
|
};
|
||||||
|
@ -5,6 +5,7 @@ import { runJobs } from "./utils";
|
|||||||
export const firstWorker = new Worker(
|
export const firstWorker = new Worker(
|
||||||
"backupQueue",
|
"backupQueue",
|
||||||
async (job: Job<QueueJob>) => {
|
async (job: Job<QueueJob>) => {
|
||||||
|
console.log("Job received", job.data);
|
||||||
await runJobs(job.data);
|
await runJobs(job.data);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -17,6 +18,7 @@ export const firstWorker = new Worker(
|
|||||||
export const secondWorker = new Worker(
|
export const secondWorker = new Worker(
|
||||||
"backupQueue",
|
"backupQueue",
|
||||||
async (job: Job<QueueJob>) => {
|
async (job: Job<QueueJob>) => {
|
||||||
|
console.log(job.data);
|
||||||
await runJobs(job.data);
|
await runJobs(job.data);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -59,3 +59,41 @@ export const validateBearerToken = async (
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const validateBearerTokenAPI = async (
|
||||||
|
authorizationHeader: string,
|
||||||
|
): ReturnValidateToken => {
|
||||||
|
const sessionId = luciaToken.readBearerToken(authorizationHeader ?? "");
|
||||||
|
if (!sessionId) {
|
||||||
|
return {
|
||||||
|
user: null,
|
||||||
|
session: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const result = await luciaToken.validateSession(sessionId);
|
||||||
|
|
||||||
|
if (result.user) {
|
||||||
|
if (result.user?.rol === "admin") {
|
||||||
|
const admin = await findAdminByAuthId(result.user.id);
|
||||||
|
result.user.adminId = admin.adminId;
|
||||||
|
} else if (result.user?.rol === "user") {
|
||||||
|
const userResult = await findUserByAuthId(result.user.id);
|
||||||
|
result.user.adminId = userResult.adminId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
session: result.session,
|
||||||
|
...((result.user && {
|
||||||
|
user: {
|
||||||
|
adminId: result.user.adminId,
|
||||||
|
authId: result.user.id,
|
||||||
|
email: result.user.email,
|
||||||
|
rol: result.user.rol,
|
||||||
|
id: result.user.id,
|
||||||
|
secret: result.user.secret,
|
||||||
|
},
|
||||||
|
}) || {
|
||||||
|
user: null,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user