mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Merge pull request #1749 from malko/template-helpers
Template helpers: Enhanced JWT generation
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { randomBytes } from "node:crypto";
|
||||
import { randomBytes, createHmac } from "node:crypto";
|
||||
import { existsSync } from "node:fs";
|
||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
@@ -24,6 +24,12 @@ export interface Template {
|
||||
domains: DomainSchema[];
|
||||
}
|
||||
|
||||
export interface GenerateJWTOptions {
|
||||
length?: number;
|
||||
secret?: string;
|
||||
payload?: Record<string, unknown> | undefined;
|
||||
}
|
||||
|
||||
export const generateRandomDomain = ({
|
||||
serverIp,
|
||||
projectName,
|
||||
@@ -61,8 +67,48 @@ export function generateBase64(bytes = 32): string {
|
||||
return randomBytes(bytes).toString("base64");
|
||||
}
|
||||
|
||||
export function generateJwt(length = 256): string {
|
||||
return randomBytes(length).toString("hex");
|
||||
function safeBase64(str: string): string {
|
||||
return str.replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
||||
}
|
||||
function objToJWTBase64(obj: any): string {
|
||||
return safeBase64(
|
||||
Buffer.from(JSON.stringify(obj), "utf8").toString("base64"),
|
||||
);
|
||||
}
|
||||
|
||||
export function generateJwt(options: GenerateJWTOptions = {}): string {
|
||||
let { length, secret, payload = {} } = options;
|
||||
if (length) {
|
||||
return randomBytes(length).toString("hex");
|
||||
}
|
||||
const encodedHeader = objToJWTBase64({
|
||||
alg: "HS256",
|
||||
typ: "JWT",
|
||||
});
|
||||
if (!payload.iss) {
|
||||
payload.iss = "dokploy";
|
||||
}
|
||||
if (!payload.iat) {
|
||||
payload.iat = Math.floor(Date.now() / 1000);
|
||||
}
|
||||
if (!payload.exp) {
|
||||
payload.exp = Math.floor(new Date("2030-01-01T00:00:00Z").getTime() / 1000);
|
||||
}
|
||||
const encodedPayload = objToJWTBase64({
|
||||
iat: Math.floor(Date.now() / 1000),
|
||||
exp: Math.floor(new Date("2030-01-01T00:00:00Z").getTime() / 1000),
|
||||
...payload,
|
||||
});
|
||||
if (!secret) {
|
||||
secret = randomBytes(32).toString("hex");
|
||||
}
|
||||
const signature = safeBase64(
|
||||
createHmac("SHA256", secret)
|
||||
.update(`${encodedHeader}.${encodedPayload}`)
|
||||
.digest("base64"),
|
||||
);
|
||||
|
||||
return `${encodedHeader}.${encodedPayload}.${signature}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -65,7 +65,7 @@ export interface Template {
|
||||
/**
|
||||
* Process a string value and replace variables
|
||||
*/
|
||||
function processValue(
|
||||
export function processValue(
|
||||
value: string,
|
||||
variables: Record<string, string>,
|
||||
schema: Schema,
|
||||
@@ -84,11 +84,11 @@ function processValue(
|
||||
const length = Number.parseInt(varName.split(":")[1], 10) || 32;
|
||||
return generateBase64(length);
|
||||
}
|
||||
|
||||
if (varName.startsWith("password:")) {
|
||||
const length = Number.parseInt(varName.split(":")[1], 10) || 16;
|
||||
return generatePassword(length);
|
||||
}
|
||||
|
||||
if (varName === "password") {
|
||||
return generatePassword(16);
|
||||
}
|
||||
@@ -97,14 +97,31 @@ function processValue(
|
||||
const length = Number.parseInt(varName.split(":")[1], 10) || 8;
|
||||
return generateHash(length);
|
||||
}
|
||||
if (varName === "hash") {
|
||||
return generateHash();
|
||||
}
|
||||
|
||||
if (varName === "uuid") {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
|
||||
if (varName === "timestamp") {
|
||||
if (varName === "timestamp" || varName === "timestampms") {
|
||||
return Date.now().toString();
|
||||
}
|
||||
|
||||
if (varName === "timestamps") {
|
||||
return Math.round(Date.now() / 1000).toString();
|
||||
}
|
||||
|
||||
if (varName.startsWith("timestampms:")) {
|
||||
return new Date(varName.slice(12)).getTime().toString();
|
||||
}
|
||||
if (varName.startsWith("timestamps:")) {
|
||||
return Math.round(
|
||||
new Date(varName.slice(11)).getTime() / 1000,
|
||||
).toString();
|
||||
}
|
||||
|
||||
if (varName === "randomPort") {
|
||||
return Math.floor(Math.random() * 65535).toString();
|
||||
}
|
||||
@@ -114,8 +131,34 @@ function processValue(
|
||||
}
|
||||
|
||||
if (varName.startsWith("jwt:")) {
|
||||
const length = Number.parseInt(varName.split(":")[1], 10) || 256;
|
||||
return generateJwt(length);
|
||||
const params: string[] = varName.split(":").slice(1);
|
||||
if (params.length === 1 && params[0] && params[0].match(/^\d{1,3}$/)) {
|
||||
return generateJwt({ length: Number.parseInt(params[0], 10) });
|
||||
}
|
||||
let [secret, payload] = params;
|
||||
if (typeof payload === "string" && variables[payload]) {
|
||||
payload = variables[payload];
|
||||
}
|
||||
if (
|
||||
typeof payload === "string" &&
|
||||
payload.startsWith("{") &&
|
||||
payload.endsWith("}")
|
||||
) {
|
||||
try {
|
||||
payload = JSON.parse(payload);
|
||||
} catch (e) {
|
||||
// If payload is not a valid JSON, invalid it
|
||||
payload = undefined;
|
||||
console.error("Invalid JWT payload", e);
|
||||
}
|
||||
}
|
||||
if (typeof payload !== "object") {
|
||||
payload = undefined;
|
||||
}
|
||||
return generateJwt({
|
||||
secret: secret ? variables[secret] || secret : undefined,
|
||||
payload: payload as any,
|
||||
});
|
||||
}
|
||||
|
||||
if (varName === "username") {
|
||||
@@ -147,7 +190,7 @@ export function processVariables(
|
||||
): Record<string, string> {
|
||||
const variables: Record<string, string> = {};
|
||||
|
||||
// First pass: Process variables that don't depend on other variables
|
||||
// First pass: Process some variables that don't depend on other variables
|
||||
for (const [key, value] of Object.entries(template.variables)) {
|
||||
if (typeof value !== "string") continue;
|
||||
|
||||
@@ -161,6 +204,8 @@ export function processVariables(
|
||||
const match = value.match(/\${password:(\d+)}/);
|
||||
const length = match?.[1] ? Number.parseInt(match[1], 10) : 16;
|
||||
variables[key] = generatePassword(length);
|
||||
} else if (value === "${hash}") {
|
||||
variables[key] = generateHash();
|
||||
} else if (value.startsWith("${hash:")) {
|
||||
const match = value.match(/\${hash:(\d+)}/);
|
||||
const length = match?.[1] ? Number.parseInt(match[1], 10) : 8;
|
||||
|
||||
Reference in New Issue
Block a user