refactor: add authorization

This commit is contained in:
Mauricio Siu 2024-10-06 01:37:39 -06:00
parent 7cfbea3f60
commit 3cf27a068a
10 changed files with 191 additions and 33 deletions

View File

@ -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 });

View File

@ -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;
} }

View File

@ -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;

View File

@ -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(

View File

@ -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),
}); });

View File

@ -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),
}); });

View File

@ -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);

View File

@ -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;
}; };

View File

@ -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);
}, },
{ {

View File

@ -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,
}),
};
};