mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
feat(template-helpers): Add more parameters to jwt helper
- jwt without parameter now generate a real jwt - keep length parameter as is for backward compatibility - add secret and payload parameters - payload properties iss, iat, exp are automaticly set if not provided
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 { existsSync } from "node:fs";
|
||||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
@@ -9,7 +9,7 @@ import { fetchTemplateFiles } from "./github";
|
|||||||
export interface Schema {
|
export interface Schema {
|
||||||
serverIp: string;
|
serverIp: string;
|
||||||
projectName: string;
|
projectName: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type DomainSchema = Pick<Domain, "host" | "port" | "serviceName"> & {
|
export type DomainSchema = Pick<Domain, "host" | "port" | "serviceName"> & {
|
||||||
path?: string;
|
path?: string;
|
||||||
@@ -22,6 +22,12 @@ export interface Template {
|
|||||||
content: string;
|
content: string;
|
||||||
}>;
|
}>;
|
||||||
domains: DomainSchema[];
|
domains: DomainSchema[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface GenerateJWTOptions {
|
||||||
|
length?: number;
|
||||||
|
secret?: string;
|
||||||
|
payload?: Record<string, unknown> | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const generateRandomDomain = ({
|
export const generateRandomDomain = ({
|
||||||
@@ -59,10 +65,41 @@ export const generatePassword = (quantity = 16): string => {
|
|||||||
*/
|
*/
|
||||||
export function generateBase64(bytes = 32): string {
|
export function generateBase64(bytes = 32): string {
|
||||||
return randomBytes(bytes).toString("base64");
|
return randomBytes(bytes).toString("base64");
|
||||||
}
|
};
|
||||||
|
|
||||||
export function generateJwt(length = 256): string {
|
function safeBase64(str: string): string {
|
||||||
return randomBytes(length).toString("hex");
|
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",
|
||||||
|
});
|
||||||
|
payload.iss || (payload.iss = "dokploy");
|
||||||
|
payload.iat || (payload.iat = Math.floor(Date.now() / 1000));
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
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
|
* Process a string value and replace variables
|
||||||
*/
|
*/
|
||||||
function processValue(
|
export function processValue(
|
||||||
value: string,
|
value: string,
|
||||||
variables: Record<string, string>,
|
variables: Record<string, string>,
|
||||||
schema: Schema,
|
schema: Schema,
|
||||||
@@ -84,11 +84,11 @@ function processValue(
|
|||||||
const length = Number.parseInt(varName.split(":")[1], 10) || 32;
|
const length = Number.parseInt(varName.split(":")[1], 10) || 32;
|
||||||
return generateBase64(length);
|
return generateBase64(length);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (varName.startsWith("password:")) {
|
if (varName.startsWith("password:")) {
|
||||||
const length = Number.parseInt(varName.split(":")[1], 10) || 16;
|
const length = Number.parseInt(varName.split(":")[1], 10) || 16;
|
||||||
return generatePassword(length);
|
return generatePassword(length);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (varName === "password") {
|
if (varName === "password") {
|
||||||
return generatePassword(16);
|
return generatePassword(16);
|
||||||
}
|
}
|
||||||
@@ -114,8 +114,30 @@ function processValue(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (varName.startsWith("jwt:")) {
|
if (varName.startsWith("jwt:")) {
|
||||||
const length = Number.parseInt(varName.split(":")[1], 10) || 256;
|
const params:string[] = varName.split(":").slice(1);
|
||||||
return generateJwt(length);
|
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") {
|
if (varName === "username") {
|
||||||
@@ -147,7 +169,7 @@ export function processVariables(
|
|||||||
): Record<string, string> {
|
): Record<string, string> {
|
||||||
const variables: 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)) {
|
for (const [key, value] of Object.entries(template.variables)) {
|
||||||
if (typeof value !== "string") continue;
|
if (typeof value !== "string") continue;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user