Enhance License Key Management and Enterprise Features: Update license key validation logic to ensure proper handling of enterprise licenses, including new cron job for refreshing license validity. Introduce new SQL migration for isValidEnterpriseLicense column and refactor related API procedures for better error handling and user feedback.

This commit is contained in:
Mauricio Siu
2026-01-29 22:37:10 -06:00
parent 9a8de9ae16
commit 12a87f9f8b
11 changed files with 7395 additions and 51 deletions

View File

@@ -1,4 +1,5 @@
import { user } from "@dokploy/server/db/schema";
import { validateLicenseKey } from "@dokploy/server/index";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
import { z } from "zod";
@@ -7,7 +8,6 @@ import { db } from "@/server/db";
import {
activateLicenseKey,
deactivateLicenseKey,
validateLicenseKey,
} from "@/server/utils/enterprise";
export const licenseKeyRouter = createTRPCRouter({
@@ -34,7 +34,15 @@ export const licenseKeyRouter = createTRPCRouter({
});
}
return await activateLicenseKey(input.licenseKey);
await activateLicenseKey(input.licenseKey);
await db
.update(user)
.set({
licenseKey: input.licenseKey,
isValidEnterpriseLicense: true,
})
.where(eq(user.id, currentUserId));
return { success: true };
} catch (error) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
@@ -46,39 +54,51 @@ export const licenseKeyRouter = createTRPCRouter({
});
}
}),
validate: adminProcedure
.input(z.object({ licenseKey: z.string() }))
.mutation(async ({ input, ctx }) => {
try {
const currentUserId = ctx.user.id;
const currentUser = await db.query.user.findFirst({
where: eq(user.id, currentUserId),
});
if (!currentUser) {
throw new TRPCError({
code: "NOT_FOUND",
message: "User not found",
});
}
if (!currentUser.enableEnterpriseFeatures) {
throw new TRPCError({
code: "BAD_REQUEST",
message:
"Please activate enterprise features to validate license key",
});
}
return await validateLicenseKey(input.licenseKey);
} catch (error) {
validate: adminProcedure.mutation(async ({ ctx }) => {
try {
const currentUserId = ctx.user.id;
const currentUser = await db.query.user.findFirst({
where: eq(user.id, currentUserId),
});
if (!currentUser) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message:
error instanceof Error
? error.message
: "Failed to validate license key",
code: "NOT_FOUND",
message: "User not found",
});
}
}),
if (!currentUser.licenseKey) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "No license key found",
});
}
if (!currentUser.enableEnterpriseFeatures) {
throw new TRPCError({
code: "BAD_REQUEST",
message:
"Please activate enterprise features to validate license key",
});
}
const valid = await validateLicenseKey(currentUser.licenseKey);
if (valid) {
await db
.update(user)
.set({ isValidEnterpriseLicense: true })
.where(eq(user.id, currentUserId));
}
return valid;
} catch (error) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message:
error instanceof Error
? error.message
: "Failed to validate license key",
});
}
}),
deactivate: adminProcedure.mutation(async ({ ctx }) => {
try {
const currentUserId = ctx.user.id;
@@ -97,7 +117,15 @@ export const licenseKeyRouter = createTRPCRouter({
message: "No license key found",
});
}
return await deactivateLicenseKey(currentUser.licenseKey);
await deactivateLicenseKey(currentUser.licenseKey);
await db
.update(user)
.set({
licenseKey: null,
isValidEnterpriseLicense: false,
})
.where(eq(user.id, currentUserId));
return { success: true };
} catch (error) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
@@ -130,18 +158,15 @@ export const licenseKeyRouter = createTRPCRouter({
const currentUserId = ctx.user.id;
const currentUser = await db.query.user.findFirst({
where: eq(user.id, currentUserId),
columns: {
enableEnterpriseFeatures: true,
isValidEnterpriseLicense: true,
},
});
if (!currentUser?.enableEnterpriseFeatures) {
return false;
}
if (!currentUser.licenseKey) {
return false;
}
try {
return await validateLicenseKey(currentUser.licenseKey ?? "");
} catch (error) {
return false;
}
return !!(
currentUser?.enableEnterpriseFeatures &&
currentUser?.isValidEnterpriseLicense
);
}),
updateEnterpriseSettings: adminProcedure

View File

@@ -2,11 +2,11 @@ import { ssoProvider } from "@dokploy/server/db/schema";
import { TRPCError } from "@trpc/server";
import { and, eq } from "drizzle-orm";
import { z } from "zod";
import { adminProcedure, createTRPCRouter } from "@/server/api/trpc";
import { createTRPCRouter, enterpriseProcedure } from "@/server/api/trpc";
import { db } from "@/server/db";
export const ssoRouter = createTRPCRouter({
listProviders: adminProcedure.query(async ({ ctx }) => {
listProviders: enterpriseProcedure.query(async ({ ctx }) => {
const providers = await db.query.ssoProvider.findMany({
where: eq(ssoProvider.userId, ctx.user.id),
columns: {
@@ -22,7 +22,7 @@ export const ssoRouter = createTRPCRouter({
return providers;
}),
deleteProvider: adminProcedure
deleteProvider: enterpriseProcedure
.input(z.object({ providerId: z.string().min(1) }))
.mutation(async ({ ctx, input }) => {
const [deleted] = await db

View File

@@ -7,8 +7,10 @@
* need to use are documented accordingly near the end.
*/
import { user as userSchema } from "@dokploy/server/db/schema";
import { validateRequest } from "@dokploy/server/lib/auth";
import type { OpenApiMeta } from "@dokploy/trpc-openapi";
import { eq } from "drizzle-orm";
import { initTRPC, TRPCError } from "@trpc/server";
import type { CreateNextContextOptions } from "@trpc/server/adapters/next";
import {
@@ -217,3 +219,40 @@ export const adminProcedure = t.procedure.use(({ ctx, next }) => {
},
});
});
/**
* Requires admin/owner role AND enterprise enabled with a license key in DB.
* Does NOT call the license server on every request; full validation (haveValidLicenseKey)
* is used in the UI gate and when activating/validating keys.
*/
export const enterpriseProcedure = t.procedure.use(async ({ ctx, next }) => {
if (
!ctx.session ||
!ctx.user ||
(ctx.user.role !== "owner" && ctx.user.role !== "admin")
) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
const currentUser = await ctx.db.query.user.findFirst({
where: eq(userSchema.id, ctx.user.id),
columns: {
enableEnterpriseFeatures: true,
isValidEnterpriseLicense: true,
},
});
if (!currentUser?.enableEnterpriseFeatures || !currentUser.isValidEnterpriseLicense) {
throw new TRPCError({
code: "FORBIDDEN",
message: "Valid enterprise license required",
});
}
return next({
ctx: {
session: ctx.session,
user: ctx.user,
},
});
});