add validate callback loading state and encryption

This commit is contained in:
miloschwartz 2025-04-14 20:56:45 -04:00
parent 53be2739bb
commit aa3b527f67
No known key found for this signature in database
11 changed files with 155 additions and 22 deletions

View file

@ -92,7 +92,18 @@ const configSchema = z.object({
})
.optional(),
trust_proxy: z.boolean().optional().default(true),
secret: z.string()
secret: z
.string()
.optional()
.transform(getEnvOrYaml("SERVER_SECRET"))
.pipe(
z
.string()
.min(
32,
"SERVER_SECRET must be at least 32 characters long"
)
)
}),
traefik: z.object({
http_entrypoint: z.string(),

37
server/lib/crypto.ts Normal file
View file

@ -0,0 +1,37 @@
import * as crypto from "crypto";
const ALGORITHM = "aes-256-gcm";
export function encrypt(value: string, key: string): string {
const iv = crypto.randomBytes(12);
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
const encrypted = Buffer.concat([
cipher.update(value, "utf8"),
cipher.final()
]);
const authTag = cipher.getAuthTag();
return [
iv.toString("base64"),
encrypted.toString("base64"),
authTag.toString("base64")
].join(":");
}
export function decrypt(encryptedValue: string, key: string): string {
const [ivB64, encryptedB64, authTagB64] = encryptedValue.split(":");
const iv = Buffer.from(ivB64, "base64");
const encrypted = Buffer.from(encryptedB64, "base64");
const authTag = Buffer.from(authTagB64, "base64");
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
decipher.setAuthTag(authTag);
const decrypted = Buffer.concat([
decipher.update(encrypted),
decipher.final()
]);
return decrypted.toString("utf8");
}

View file

@ -9,6 +9,8 @@ import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
import { idp, idpOidcConfig, idpOrg, orgs } from "@server/db/schemas";
import { generateOidcRedirectUrl } from "@server/lib/idp/generateRedirectUrl";
import { encrypt } from "@server/lib/crypto";
import config from "@server/lib/config";
const paramsSchema = z.object({}).strict();
@ -22,7 +24,8 @@ const bodySchema = z
identifierPath: z.string().nonempty(),
emailPath: z.string().optional(),
namePath: z.string().optional(),
scopes: z.array(z.string().nonempty())
scopes: z.array(z.string().nonempty()),
autoProvision: z.boolean().optional()
})
.strict();
@ -73,9 +76,15 @@ export async function createOidcIdp(
identifierPath,
emailPath,
namePath,
name
name,
autoProvision
} = parsedBody.data;
const key = config.getRawConfig().server.secret;
const encryptedSecret = encrypt(clientSecret, key);
const encryptedClientId = encrypt(clientId, key);
let idpId: number | undefined;
await db.transaction(async (trx) => {
const [idpRes] = await trx
@ -90,11 +99,11 @@ export async function createOidcIdp(
await trx.insert(idpOidcConfig).values({
idpId: idpRes.idpId,
clientId,
clientSecret,
clientId: encryptedClientId,
clientSecret: encryptedSecret,
authUrl,
tokenUrl,
autoProvision: true,
autoProvision,
scopes: JSON.stringify(scopes),
identifierPath,
emailPath,

View file

@ -13,6 +13,7 @@ import { generateOidcRedirectUrl } from "@server/lib/idp/generateRedirectUrl";
import cookie from "cookie";
import jsonwebtoken from "jsonwebtoken";
import config from "@server/lib/config";
import { decrypt } from "@server/lib/crypto";
const paramsSchema = z
.object({
@ -77,10 +78,21 @@ export async function generateOidcUrl(
const parsedScopes = JSON.parse(existingIdp.idpOidcConfig.scopes);
const key = config.getRawConfig().server.secret;
const decryptedClientId = decrypt(
existingIdp.idpOidcConfig.clientId,
key
);
const decryptedClientSecret = decrypt(
existingIdp.idpOidcConfig.clientSecret,
key
);
const redirectUrl = generateOidcRedirectUrl(idpId);
const client = new arctic.OAuth2Client(
existingIdp.idpOidcConfig.clientId,
existingIdp.idpOidcConfig.clientSecret,
decryptedClientId,
decryptedClientSecret,
redirectUrl
);

View file

@ -28,6 +28,7 @@ import {
generateSessionToken,
serializeSessionCookie
} from "@server/auth/sessions/app";
import { decrypt } from "@server/lib/crypto";
const paramsSchema = z
.object({
@ -90,10 +91,21 @@ export async function validateOidcCallback(
);
}
const key = config.getRawConfig().server.secret;
const decryptedClientId = decrypt(
existingIdp.idpOidcConfig.clientId,
key
);
const decryptedClientSecret = decrypt(
existingIdp.idpOidcConfig.clientSecret,
key
);
const redirectUrl = generateOidcRedirectUrl(existingIdp.idp.idpId);
const client = new arctic.OAuth2Client(
existingIdp.idpOidcConfig.clientId,
existingIdp.idpOidcConfig.clientSecret,
decryptedClientId,
decryptedClientSecret,
redirectUrl
);