mirror of
https://github.com/documenso/documenso.git
synced 2026-08-16 11:31:51 +10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9142a3799a | ||
|
|
8a77747820 | ||
|
|
e7b66fe069 |
@@ -1,7 +1,6 @@
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import {
|
||||
decryptEmailTransportConfig,
|
||||
EMAIL_TRANSPORT_SECRET_KEYS,
|
||||
encryptEmailTransportConfig,
|
||||
ZEmailTransportConfigSchema,
|
||||
} from '@documenso/lib/server-only/email/email-transport-config';
|
||||
@@ -30,16 +29,16 @@ export const updateEmailTransportRoute = adminProcedure
|
||||
const existingConfig = decryptEmailTransportConfig(existing.config);
|
||||
|
||||
// Start from the incoming config; backfill empty secret fields from the existing
|
||||
// config (only when the type is unchanged).
|
||||
const merged: Record<string, unknown> = { ...data.config };
|
||||
// config (only when the type is unchanged). Secrets are never sent back to the
|
||||
// client, so a blank incoming value means "keep the existing secret".
|
||||
const merged = { ...data.config };
|
||||
|
||||
if (existingConfig.type === data.config.type) {
|
||||
for (const key of EMAIL_TRANSPORT_SECRET_KEYS) {
|
||||
const incoming = (data.config as Record<string, unknown>)[key];
|
||||
if (incoming === undefined || incoming === '') {
|
||||
merged[key] = (existingConfig as Record<string, unknown>)[key];
|
||||
}
|
||||
}
|
||||
if (merged.type === 'SMTP_AUTH' && existingConfig.type === 'SMTP_AUTH' && !merged.password) {
|
||||
merged.password = existingConfig.password;
|
||||
}
|
||||
|
||||
if (merged.type !== 'SMTP_AUTH' && merged.type === existingConfig.type && !merged.apiKey) {
|
||||
merged.apiKey = existingConfig.apiKey;
|
||||
}
|
||||
|
||||
const config = ZEmailTransportConfigSchema.parse(merged);
|
||||
|
||||
@@ -148,7 +148,7 @@ export const updateOrganisationMemberRoleRoute = adminProcedure
|
||||
return;
|
||||
}
|
||||
|
||||
const targetRole = role as OrganisationMemberRole;
|
||||
const targetRole = role;
|
||||
|
||||
if (currentOrganisationRole === targetRole) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
|
||||
@@ -54,10 +54,11 @@ export const updateSubscriptionClaimRoute = adminProcedure
|
||||
}
|
||||
});
|
||||
|
||||
function getNewTruthyFlags(a: Partial<TClaimFlags>, b: Partial<TClaimFlags>): Record<keyof TClaimFlags, true> {
|
||||
function getNewTruthyFlags(a: Partial<TClaimFlags>, b: Partial<TClaimFlags>) {
|
||||
const flags: { [key in keyof TClaimFlags]?: true } = {};
|
||||
|
||||
for (const key in b) {
|
||||
// SAFETY: `b` is a Partial<TClaimFlags>, so its enumerable keys are TClaimFlags keys.
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const typedKey = key as keyof TClaimFlags;
|
||||
|
||||
@@ -66,6 +67,5 @@ function getNewTruthyFlags(a: Partial<TClaimFlags>, b: Partial<TClaimFlags>): Re
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
return flags as Record<keyof TClaimFlags, true>;
|
||||
return flags;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ export const createPasskeyRoute = authenticatedProcedure
|
||||
.input(ZCreatePasskeyRequestSchema)
|
||||
.output(ZCreatePasskeyResponseSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
// SAFETY: ZRegistrationResponseJSONSchema validates the same fields RegistrationResponseJSON
|
||||
// declares; the assertion only restores the library type that zod inference loses.
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const verificationResponse = input.verificationResponse as RegistrationResponseJSON;
|
||||
|
||||
|
||||
@@ -29,8 +29,6 @@ export const accessAuthRequest2FAEmailRoute = procedure
|
||||
|
||||
assertRateLimit(rateLimitResult);
|
||||
|
||||
const user = ctx.user;
|
||||
|
||||
// Get document and recipient by token
|
||||
const envelope = await prisma.envelope.findFirst({
|
||||
where: {
|
||||
|
||||
@@ -6,11 +6,11 @@ export const ZUpdateOrganisationEmailRequestSchema = z
|
||||
.object({
|
||||
emailId: z.string(),
|
||||
})
|
||||
.extend(
|
||||
.merge(
|
||||
ZCreateOrganisationEmailRequestSchema.pick({
|
||||
emailName: true,
|
||||
// replyTo: true
|
||||
}).shape,
|
||||
}),
|
||||
);
|
||||
|
||||
export const ZUpdateOrganisationEmailResponseSchema = z.void();
|
||||
|
||||
@@ -174,7 +174,7 @@ export const folderRouter = router({
|
||||
folderId: parentId,
|
||||
type,
|
||||
});
|
||||
} catch (error) {
|
||||
} catch {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Parent folder not found',
|
||||
});
|
||||
|
||||
@@ -92,7 +92,7 @@ export const profileRouter = router({
|
||||
|
||||
const parsedTeamId = teamId ? Number(teamId) : null;
|
||||
|
||||
if (typeof parsedTeamId === 'number') {
|
||||
if (parsedTeamId !== null) {
|
||||
if (Number.isNaN(parsedTeamId) || parsedTeamId <= 0) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'Invalid team ID provided',
|
||||
|
||||
@@ -138,7 +138,7 @@ export const updateTeamSettingsRoute = authenticatedProcedure
|
||||
|
||||
if (brandingCss === null) {
|
||||
sanitizedBrandingCss = null;
|
||||
} else if (typeof brandingCss === 'string') {
|
||||
} else if (brandingCss !== undefined) {
|
||||
const result = sanitizeBrandingCss(brandingCss);
|
||||
sanitizedBrandingCss = result.css.trim() === '' ? null : result.css;
|
||||
cssWarnings = result.warnings;
|
||||
|
||||
@@ -10,7 +10,7 @@ const CONTENT_TYPE_MULTIPART = 'multipart/form-data';
|
||||
const getUrlEncodedBody = async (req: Request) => {
|
||||
const params = new URLSearchParams(await req.text());
|
||||
|
||||
const data: Record<string, string[]> = {};
|
||||
const data: Record<string, unknown> = {};
|
||||
|
||||
for (const key of params.keys()) {
|
||||
data[key] = params.getAll(key);
|
||||
@@ -22,20 +22,18 @@ const getUrlEncodedBody = async (req: Request) => {
|
||||
const getMultipartBody = async (req: Request) => {
|
||||
const formData = await req.formData();
|
||||
|
||||
const data: Record<string, FormDataEntryValue | FormDataEntryValue[]> = {};
|
||||
const data: Record<string, unknown> = {};
|
||||
|
||||
for (const [key, value] of formData.entries()) {
|
||||
// !: Handles cases where our generated SDKs send key[] syntax for arrays.
|
||||
const normalizedKey = key.endsWith('[]') ? key.slice(0, -2) : key;
|
||||
|
||||
const existing = data[normalizedKey];
|
||||
|
||||
if (existing === undefined) {
|
||||
if (data[normalizedKey] === undefined) {
|
||||
data[normalizedKey] = value;
|
||||
} else if (Array.isArray(existing)) {
|
||||
existing.push(value);
|
||||
} else if (Array.isArray(data[normalizedKey])) {
|
||||
data[normalizedKey].push(value);
|
||||
} else {
|
||||
data[normalizedKey] = [existing, value];
|
||||
data[normalizedKey] = [data[normalizedKey], value];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,10 +138,8 @@ const createRequestProxy = async (req: Request, url?: string) => {
|
||||
}
|
||||
|
||||
default:
|
||||
// SAFETY: Every property this trap does not special-case is forwarded from the
|
||||
// original Request, so `prop` can only be a key the caller reads off a Request.
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
return target[prop as keyof Request];
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
|
||||
return (target as unknown as Record<string | number | symbol, unknown>)[prop];
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -164,25 +160,23 @@ export const createOpenApiFetchHandler = async <TRouter extends OpenApiRouter>(
|
||||
const url = new URL(opts.req.url.replace(opts.endpoint, ''));
|
||||
const req: Request = await createRequestProxy(opts.req, url.toString());
|
||||
|
||||
// The handler is typed against Node HTTP req/res, but only reads properties our request
|
||||
// proxy and mock response provide, so we declare it against the fetch-based types we pass.
|
||||
// @ts-expect-error Inherited from original fetch handler in `trpc-to-openapi`
|
||||
const openApiHttpHandler: (req: Request, res: ServerResponse) => void = createOpenApiNodeHttpHandler(opts);
|
||||
const openApiHttpHandler = createOpenApiNodeHttpHandler(opts);
|
||||
|
||||
return new Promise<Response>((resolve) => {
|
||||
let statusCode: number;
|
||||
|
||||
// SAFETY: The Node HTTP handler only calls setHeader/statusCode/end on the response,
|
||||
// which this mock implements to bridge Node HTTP APIs with a Fetch API Response.
|
||||
// Create a mock ServerResponse object that bridges Node HTTP APIs with Fetch API Response.
|
||||
// This allows the Node HTTP handler to work with Fetch API Request objects.
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const res = {
|
||||
setHeader: (key: string, value: string | string[]) => {
|
||||
if (Array.isArray(value)) {
|
||||
setHeader: (key: string, value: string | readonly string[]) => {
|
||||
if (typeof value === 'string') {
|
||||
resHeaders.set(key, value);
|
||||
} else {
|
||||
for (const v of value) {
|
||||
resHeaders.append(key, v);
|
||||
}
|
||||
} else {
|
||||
resHeaders.set(key, value);
|
||||
}
|
||||
},
|
||||
get statusCode() {
|
||||
@@ -201,6 +195,14 @@ export const createOpenApiFetchHandler = async <TRouter extends OpenApiRouter>(
|
||||
},
|
||||
} as ServerResponse;
|
||||
|
||||
void openApiHttpHandler(req, res);
|
||||
// Type assertions are necessary here for interop between Fetch API Request/Response
|
||||
// and Node HTTP IncomingMessage/ServerResponse types.
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const nodeReq = req as unknown as Parameters<typeof openApiHttpHandler>[0];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const nodeRes = res as unknown as Parameters<typeof openApiHttpHandler>[1];
|
||||
|
||||
void openApiHttpHandler(nodeReq, nodeRes);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
BRANDING_LOGO_MAX_SIZE_MB,
|
||||
} from '@documenso/lib/constants/branding';
|
||||
import { megabytesToBytes } from '@documenso/lib/universal/unit-convertions';
|
||||
import type { ZodRawShape } from 'zod';
|
||||
import z from 'zod';
|
||||
import { zfd } from 'zod-form-data';
|
||||
|
||||
@@ -48,10 +49,10 @@ export const zfdBrandingImageFile = () => {
|
||||
* an error. This provides the same functionality as `zfd.formData()` but
|
||||
* can be considered somewhat safer.
|
||||
*/
|
||||
export const zodFormData = <T extends Parameters<typeof z.object>[0]>(schema: T) => {
|
||||
export const zodFormData = <T extends ZodRawShape>(schema: T) => {
|
||||
return z.preprocess((data) => {
|
||||
if (data instanceof FormData) {
|
||||
const formData: Record<string, FormDataEntryValue | FormDataEntryValue[]> = {};
|
||||
const formData: Record<string, unknown> = {};
|
||||
|
||||
for (const key of data.keys()) {
|
||||
const values = data.getAll(key);
|
||||
|
||||
Reference in New Issue
Block a user