Compare commits

..

4 Commits

Author SHA1 Message Date
Crowdin Bot 5350ab4d53 chore: add translations 2026-06-30 12:41:13 +00:00
Kendry Grullon 562d78e2d7 feat: add granular signin disable flags and OIDC auto-redirect (#2857) 2026-06-30 16:08:09 +10:00
Grégory Chevalier 3b110cf70d fix: french translation for confirmation message (#3050) 2026-06-30 15:52:59 +10:00
David Nguyen 7062fadf0b fix: add additional team group permission checks (#3052) 2026-06-30 15:45:48 +10:00
35 changed files with 981 additions and 557 deletions
+14
View File
@@ -180,6 +180,20 @@ NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP=
NEXT_PUBLIC_DISABLE_OIDC_SIGNUP=
# OPTIONAL: Comma-separated list of email domains allowed to sign up (e.g., example.com,acme.org).
NEXT_PRIVATE_ALLOWED_SIGNUP_DOMAINS=
# OPTIONAL: Set to "true" to disable all signin methods (email, Google, Microsoft, OIDC).
NEXT_PUBLIC_DISABLE_SIGNIN=
# OPTIONAL: Set to "true" to disable email/password signin only. Also closes /forgot-password and /reset-password.
NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNIN=
# OPTIONAL: Set to "true" to hide the Google signin button.
NEXT_PUBLIC_DISABLE_GOOGLE_SIGNIN=
# OPTIONAL: Set to "true" to hide the Microsoft signin button.
NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNIN=
# OPTIONAL: Set to "true" to hide the OIDC signin button.
NEXT_PUBLIC_DISABLE_OIDC_SIGNIN=
# OPTIONAL: When OIDC is the only enabled signin transport, /signin auto-redirects
# to the OIDC provider (rendering only a spinner). Set to "true" to disable this
# and keep showing the signin page.
NEXT_PUBLIC_DISABLE_OIDC_AUTO_REDIRECT=
# OPTIONAL: Set to true to use internal webapp url in browserless requests.
NEXT_PUBLIC_USE_INTERNAL_URL_BROWSERLESS=false
@@ -272,6 +272,12 @@ For detailed certificate setup, see [Signing Certificate](/docs/self-hosting/con
| `NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP` | Block new accounts via Microsoft. Existing linked users can still sign in | `false` |
| `NEXT_PUBLIC_DISABLE_OIDC_SIGNUP` | Block new accounts via OIDC, including the organisation portal | `false` |
| `NEXT_PRIVATE_ALLOWED_SIGNUP_DOMAINS` | Comma-separated list of email domains allowed to sign up (e.g., `example.com,acme.org`) | |
| `NEXT_PUBLIC_DISABLE_SIGNIN` | Master switch. Disable all signin methods application-wide | `false` |
| `NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNIN` | Disable email/password signin. Also closes `/forgot-password` and `/reset-password` | `false` |
| `NEXT_PUBLIC_DISABLE_GOOGLE_SIGNIN` | Hide the Google signin button | `false` |
| `NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNIN` | Hide the Microsoft signin button | `false` |
| `NEXT_PUBLIC_DISABLE_OIDC_SIGNIN` | Hide the OIDC signin button | `false` |
| `NEXT_PUBLIC_DISABLE_OIDC_AUTO_REDIRECT` | Disable the automatic `/signin` redirect when OIDC is the only enabled transport | `false` |
| `NEXT_PUBLIC_POSTHOG_KEY` | PostHog API key for analytics and feature flags | |
| `NEXT_PUBLIC_FEATURE_BILLING_ENABLED` | Enable billing features | `false` |
@@ -303,6 +309,44 @@ NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP="true"
NEXT_PUBLIC_DISABLE_SIGNUP="true"
```
### Sign-in Restrictions
You can control which methods are available for users to sign in with the following environment variables:
- **`NEXT_PUBLIC_DISABLE_SIGNIN`** (master switch): Set to `true` to block all signin methods (email/password, Google, Microsoft, OIDC). Hides every signin entry point on `/signin` and rejects email/password signin server-side with a `SIGNIN_DISABLED` error.
- **`NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNIN`**: Set to `true` to disable email/password signin only. The email/password form is hidden, the `/forgot-password` and `/reset-password` pages redirect to `/signin`, and the corresponding server endpoints reject requests. SSO signin is unaffected.
- **`NEXT_PUBLIC_DISABLE_GOOGLE_SIGNIN`**, **`NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNIN`**, **`NEXT_PUBLIC_DISABLE_OIDC_SIGNIN`**: Set to `true` to hide the matching SSO button on the signin page. Useful when an SSO provider is kept configured for account linking but not advertised as a signin entry point.
These flags are opt-in: when none are set, signin behaviour is unchanged from a stock Documenso instance.
```bash
# Allow only OIDC signin (e.g. enterprise SSO-only)
NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNIN="true"
NEXT_PUBLIC_DISABLE_GOOGLE_SIGNIN="true"
NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNIN="true"
# Or disable signin entirely
NEXT_PUBLIC_DISABLE_SIGNIN="true"
```
### OIDC Auto-redirect
When OIDC is the only enabled signin transport on your instance, `/signin` automatically redirects users straight to the OIDC provider instead of showing the signin form. The page renders a spinner while the redirect happens. No extra configuration is required — disabling every other signin method is enough to trigger it.
- **`NEXT_PUBLIC_DISABLE_OIDC_AUTO_REDIRECT`**: Set to `true` to opt out of the automatic redirect and keep rendering the signin page even when OIDC is the only enabled transport.
The redirect only triggers when OIDC is configured and email/password, Google, and Microsoft signin are all disabled. If any other transport remains enabled, the signin form is shown as normal.
```bash
# OIDC-only signin: disabling all other methods auto-redirects to the provider
NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNIN="true"
NEXT_PUBLIC_DISABLE_GOOGLE_SIGNIN="true"
NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNIN="true"
# Opt out of the auto-redirect while still OIDC-only
# NEXT_PUBLIC_DISABLE_OIDC_AUTO_REDIRECT="true"
```
---
## AI Features
@@ -446,6 +490,16 @@ NEXT_PRIVATE_SIGNING_PASSPHRASE="your-certificate-password"
# NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP="true"
# NEXT_PUBLIC_DISABLE_OIDC_SIGNUP="true"
# NEXT_PRIVATE_ALLOWED_SIGNUP_DOMAINS="example.com,acme.org"
# Sign-in restrictions (optional)
# NEXT_PUBLIC_DISABLE_SIGNIN="true"
# NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNIN="true"
# NEXT_PUBLIC_DISABLE_GOOGLE_SIGNIN="true"
# NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNIN="true"
# NEXT_PUBLIC_DISABLE_OIDC_SIGNIN="true"
# Opt out of the automatic OIDC redirect when OIDC is the only enabled transport (optional)
# NEXT_PUBLIC_DISABLE_OIDC_AUTO_REDIRECT="true"
```
---
@@ -163,6 +163,19 @@ NEXT_PUBLIC_DISABLE_SIGNUP=false
# NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP=true
# NEXT_PUBLIC_DISABLE_OIDC_SIGNUP=true
# NEXT_PRIVATE_ALLOWED_SIGNUP_DOMAINS=example.com,acme.org
# Signin restrictions (optional)
# Master switch — disables every signin method
# NEXT_PUBLIC_DISABLE_SIGNIN=true
# Per-method switches (optional). Each disables that signin path.
# NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNIN=true
# NEXT_PUBLIC_DISABLE_GOOGLE_SIGNIN=true
# NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNIN=true
# NEXT_PUBLIC_DISABLE_OIDC_SIGNIN=true
# When OIDC is the only enabled transport, /signin auto-redirects to the provider.
# Set this to opt out and keep showing the signin page (optional).
# NEXT_PUBLIC_DISABLE_OIDC_AUTO_REDIRECT=true
```
<Callout type="info">Generate secure secrets using: `openssl rand -base64 32`</Callout>
@@ -112,6 +112,12 @@ See [Email Configuration](/docs/self-hosting/configuration/email) for other tran
| `NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP` | Block new accounts via Microsoft OAuth | `false` |
| `NEXT_PUBLIC_DISABLE_OIDC_SIGNUP` | Block new accounts via OIDC (incl. organisation portal) | `false` |
| `NEXT_PRIVATE_ALLOWED_SIGNUP_DOMAINS` | Comma-separated list of allowed signup email domains | |
| `NEXT_PUBLIC_DISABLE_SIGNIN` | Master switch — disable all signin methods | `false` |
| `NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNIN` | Disable email/password signin only | `false` |
| `NEXT_PUBLIC_DISABLE_GOOGLE_SIGNIN` | Hide the Google signin button | `false` |
| `NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNIN` | Hide the Microsoft signin button | `false` |
| `NEXT_PUBLIC_DISABLE_OIDC_SIGNIN` | Hide the OIDC signin button | `false` |
| `NEXT_PUBLIC_DISABLE_OIDC_AUTO_REDIRECT` | Disable auto-redirect to OIDC when it is the only transport | `false` |
For the complete list, see [Environment Variables](/docs/self-hosting/configuration/environment).
@@ -159,6 +159,12 @@ NEXT_PRIVATE_SMTP_FROM_ADDRESS=noreply@yourdomain.com
| `NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP`| Block new accounts via Microsoft OAuth | `false` |
| `NEXT_PUBLIC_DISABLE_OIDC_SIGNUP` | Block new accounts via OIDC (incl. organisation portal)| `false` |
| `NEXT_PRIVATE_ALLOWED_SIGNUP_DOMAINS` | Comma-separated list of allowed signup email domains | |
| `NEXT_PUBLIC_DISABLE_SIGNIN` | Master switch — disable all signin methods | `false` |
| `NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNIN` | Disable email/password signin only | `false` |
| `NEXT_PUBLIC_DISABLE_GOOGLE_SIGNIN` | Hide the Google signin button | `false` |
| `NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNIN`| Hide the Microsoft signin button | `false` |
| `NEXT_PUBLIC_DISABLE_OIDC_SIGNIN` | Hide the OIDC signin button | `false` |
| `NEXT_PUBLIC_DISABLE_OIDC_AUTO_REDIRECT` | Disable auto-redirect to OIDC when it is the only transport | `false` |
| `NEXT_PRIVATE_SIGNING_PASSPHRASE` | Passphrase for signing certificate | - |
| `DOCUMENSO_DISABLE_TELEMETRY` | Disable anonymous telemetry | `false` |
@@ -1,144 +0,0 @@
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import { Trans } from '@lingui/react/macro';
import { useState } from 'react';
export type DocumentPreferencesResetDialogProps = {
disabled?: boolean;
isSubmitting: boolean;
onReset: () => Promise<void>;
showAiFeatures?: boolean;
showDocumentVisibility?: boolean;
showIncludeSenderDetails?: boolean;
trigger?: React.ReactNode;
};
export const DocumentPreferencesResetDialog = ({
disabled = false,
isSubmitting,
onReset,
showAiFeatures = false,
showDocumentVisibility = false,
showIncludeSenderDetails = false,
trigger,
}: DocumentPreferencesResetDialogProps) => {
const [open, setOpen] = useState(false);
const [isResetting, setIsResetting] = useState(false);
const isLoading = isSubmitting || isResetting;
const handleResetToDefaults = async () => {
setIsResetting(true);
try {
await onReset();
setOpen(false);
} finally {
setIsResetting(false);
}
};
return (
<Dialog open={open} onOpenChange={(value) => !isLoading && setOpen(value)}>
<DialogTrigger asChild>
{trigger ?? (
<Button variant="destructive" type="button" disabled={disabled || isLoading}>
<Trans>Reset</Trans>
</Button>
)}
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>
<Trans>Reset document preferences</Trans>
</DialogTitle>
<DialogDescription>
<Trans>
This will reset all document preferences to their default values and save the changes immediately.
</Trans>
</DialogDescription>
</DialogHeader>
<Alert variant="warning">
<AlertDescription>
<p>
<Trans>Once confirmed, the following will be reset:</Trans>
</p>
<ul className="mt-0.5 list-inside list-disc">
{showDocumentVisibility && (
<li>
<Trans>Default document visibility</Trans>
</li>
)}
<li>
<Trans>Default document language</Trans>
</li>
<li>
<Trans>Default date format</Trans>
</li>
<li>
<Trans>Default time zone</Trans>
</li>
<li>
<Trans>Default signature settings</Trans>
</li>
{showIncludeSenderDetails && (
<li>
<Trans>Send on behalf of team</Trans>
</li>
)}
<li>
<Trans>Include the signing certificate in the document</Trans>
</li>
<li>
<Trans>Include the audit logs in the document</Trans>
</li>
<li>
<Trans>Default recipients</Trans>
</li>
<li>
<Trans>Delegate document ownership</Trans>
</li>
<li>
<Trans>Default envelope expiration</Trans>
</li>
<li>
<Trans>Default signing reminders</Trans>
</li>
{showAiFeatures && (
<li>
<Trans>AI features</Trans>
</li>
)}
</ul>
</AlertDescription>
</Alert>
<DialogFooter>
<DialogClose asChild>
<Button type="button" variant="secondary" disabled={isLoading}>
<Trans>Cancel</Trans>
</Button>
</DialogClose>
<Button type="button" variant="destructive" loading={isLoading} onClick={() => void handleResetToDefaults()}>
<Trans>Reset to defaults</Trans>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};
@@ -11,10 +11,10 @@ import { isValidLanguageCode, SUPPORTED_LANGUAGE_CODES, SUPPORTED_LANGUAGES } fr
import { TIME_ZONES } from '@documenso/lib/constants/time-zones';
import type { TDefaultRecipients } from '@documenso/lib/types/default-recipients';
import { ZDefaultRecipientsSchema } from '@documenso/lib/types/default-recipients';
import { type TDocumentMetaDateFormat, ZDocumentMetaDateFormatSchema } from '@documenso/lib/types/document-meta';
import { generateDefaultOrganisationSettings, isPersonalLayout } from '@documenso/lib/utils/organisations';
import { type TDocumentMetaDateFormat, ZDocumentMetaTimezoneSchema } from '@documenso/lib/types/document-meta';
import { isPersonalLayout } from '@documenso/lib/utils/organisations';
import { recipientAbbreviation } from '@documenso/lib/utils/recipient-formatter';
import { extractTeamSignatureSettings, generateDefaultTeamSettings } from '@documenso/lib/utils/teams';
import { extractTeamSignatureSettings } from '@documenso/lib/utils/teams';
import { DocumentSignatureSettingsTooltip } from '@documenso/ui/components/document/document-signature-settings-tooltip';
import { ExpirationPeriodPicker } from '@documenso/ui/components/document/expiration-period-picker';
import { ReminderSettingsPicker } from '@documenso/ui/components/document/reminder-settings-picker';
@@ -38,11 +38,11 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { msg, t } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { DocumentVisibility, OrganisationType, type RecipientRole, type TeamGlobalSettings } from '@prisma/client';
import type { TeamGlobalSettings } from '@prisma/client';
import { DocumentVisibility, OrganisationType, type RecipientRole } from '@prisma/client';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { DocumentPreferencesResetDialog } from '~/components/dialogs/document-preferences-reset-dialog';
import { useOptionalCurrentTeam } from '~/providers/team';
import { DefaultRecipientsMultiSelectCombobox } from '../general/default-recipients-multiselect-combobox';
@@ -93,26 +93,6 @@ export type DocumentPreferencesFormProps = {
onFormSubmit: (data: TDocumentPreferencesFormSchema) => Promise<void>;
};
const getDocumentPreferencesFormValues = (settings: SettingsSubset): TDocumentPreferencesFormSchema => {
const parsedDocumentDateFormat = ZDocumentMetaDateFormatSchema.safeParse(settings.documentDateFormat);
return {
documentVisibility: settings.documentVisibility,
documentLanguage: isValidLanguageCode(settings.documentLanguage) ? settings.documentLanguage : null,
documentTimezone: settings.documentTimezone,
documentDateFormat: parsedDocumentDateFormat.success ? parsedDocumentDateFormat.data : null,
includeSenderDetails: settings.includeSenderDetails,
includeSigningCertificate: settings.includeSigningCertificate,
includeAuditLog: settings.includeAuditLog,
signatureTypes: extractTeamSignatureSettings({ ...settings }),
defaultRecipients: settings.defaultRecipients ? ZDefaultRecipientsSchema.parse(settings.defaultRecipients) : null,
delegateDocumentOwnership: settings.delegateDocumentOwnership,
aiFeaturesEnabled: settings.aiFeaturesEnabled,
envelopeExpirationPeriod: settings.envelopeExpirationPeriod ?? null,
reminderSettings: settings.reminderSettings ?? null,
};
};
export const DocumentPreferencesForm = ({
settings,
onFormSubmit,
@@ -133,7 +113,7 @@ export const DocumentPreferencesForm = ({
documentVisibility: z.nativeEnum(DocumentVisibility).nullable(),
documentLanguage: z.enum(SUPPORTED_LANGUAGE_CODES).nullable(),
documentTimezone: z.string().nullable(),
documentDateFormat: ZDocumentMetaDateFormatSchema.nullable(),
documentDateFormat: ZDocumentMetaTimezoneSchema.nullable(),
includeSenderDetails: z.boolean().nullable(),
includeSigningCertificate: z.boolean().nullable(),
includeAuditLog: z.boolean().nullable(),
@@ -147,27 +127,26 @@ export const DocumentPreferencesForm = ({
reminderSettings: ZEnvelopeReminderSettings.nullable(),
});
const defaultValues = getDocumentPreferencesFormValues(settings);
const defaultSettings = canInherit ? generateDefaultTeamSettings() : generateDefaultOrganisationSettings();
const baseResetValues = getDocumentPreferencesFormValues(defaultSettings);
const resetValues = {
...baseResetValues,
aiFeaturesEnabled: isAiFeaturesConfigured ? baseResetValues.aiFeaturesEnabled : defaultValues.aiFeaturesEnabled,
};
const form = useForm<TDocumentPreferencesFormSchema>({
defaultValues,
defaultValues: {
documentVisibility: settings.documentVisibility,
documentLanguage: isValidLanguageCode(settings.documentLanguage) ? settings.documentLanguage : null,
documentTimezone: settings.documentTimezone,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
documentDateFormat: settings.documentDateFormat as TDocumentMetaDateFormat | null,
includeSenderDetails: settings.includeSenderDetails,
includeSigningCertificate: settings.includeSigningCertificate,
includeAuditLog: settings.includeAuditLog,
signatureTypes: extractTeamSignatureSettings({ ...settings }),
defaultRecipients: settings.defaultRecipients ? ZDefaultRecipientsSchema.parse(settings.defaultRecipients) : null,
delegateDocumentOwnership: settings.delegateDocumentOwnership,
aiFeaturesEnabled: settings.aiFeaturesEnabled,
envelopeExpirationPeriod: settings.envelopeExpirationPeriod ?? null,
reminderSettings: settings.reminderSettings ?? null,
},
resolver: zodResolver(ZDocumentPreferencesFormSchema),
});
const currentValues = form.watch();
const isResetDisabled = !form.formState.isDirty && JSON.stringify(currentValues) === JSON.stringify(resetValues);
const handleResetToDefaults = async () => {
await onFormSubmit(resetValues);
form.reset(resetValues);
};
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
@@ -781,14 +760,6 @@ export const DocumentPreferencesForm = ({
<Button type="submit" loading={form.formState.isSubmitting}>
<Trans>Update</Trans>
</Button>
<DocumentPreferencesResetDialog
disabled={isResetDisabled}
isSubmitting={form.formState.isSubmitting}
onReset={handleResetToDefaults}
showAiFeatures={isAiFeaturesConfigured}
showDocumentVisibility={!isPersonalLayoutMode}
showIncludeSenderDetails={!isPersonalLayoutMode && !isPersonalOrganisation}
/>
</div>
</fieldset>
</form>
+63 -49
View File
@@ -58,6 +58,7 @@ export type TSignInFormSchema = z.infer<typeof ZSignInFormSchema>;
export type SignInFormProps = {
className?: string;
initialEmail?: string;
isEmailPasswordSigninEnabled?: boolean;
isGoogleSSOEnabled?: boolean;
isMicrosoftSSOEnabled?: boolean;
isOIDCSSOEnabled?: boolean;
@@ -68,6 +69,7 @@ export type SignInFormProps = {
export const SignInForm = ({
className,
initialEmail,
isEmailPasswordSigninEnabled = true,
isGoogleSSOEnabled,
isMicrosoftSSOEnabled,
isOIDCSSOEnabled,
@@ -324,66 +326,78 @@ export const SignInForm = ({
<Form {...form}>
<form className={cn('flex w-full flex-col gap-y-4', className)} onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset className="flex w-full flex-col gap-y-4" disabled={isSubmitting || isPasskeyLoading}>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans>Email</Trans>
</FormLabel>
{isEmailPasswordSigninEnabled && (
<>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans>Email</Trans>
</FormLabel>
<FormControl>
<Input type="email" {...field} />
</FormControl>
<FormControl>
<Input type="email" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans>Password</Trans>
</FormLabel>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans>Password</Trans>
</FormLabel>
<FormControl>
<PasswordInput {...field} />
</FormControl>
<FormControl>
<PasswordInput {...field} />
</FormControl>
<FormMessage />
<FormMessage />
<p className="mt-2 text-right">
<Link to="/forgot-password" className="text-muted-foreground text-sm duration-200 hover:opacity-70">
<Trans>Forgot your password?</Trans>
</Link>
</p>
</FormItem>
)}
/>
<p className="mt-2 text-right">
<Link
to="/forgot-password"
className="text-muted-foreground text-sm duration-200 hover:opacity-70"
>
<Trans>Forgot your password?</Trans>
</Link>
</p>
</FormItem>
)}
/>
{turnstileSiteKey && !isTwoFactorAuthenticationDialogOpen && (
<Turnstile
ref={turnstileRef}
siteKey={turnstileSiteKey}
options={{
size: 'flexible',
appearance: 'always',
}}
/>
{turnstileSiteKey && !isTwoFactorAuthenticationDialogOpen && (
<Turnstile
ref={turnstileRef}
siteKey={turnstileSiteKey}
options={{
size: 'flexible',
appearance: 'always',
}}
/>
)}
<Button
type="submit"
size="lg"
loading={isSubmitting}
className="dark:bg-documenso dark:hover:opacity-90"
>
{isSubmitting ? <Trans>Signing in...</Trans> : <Trans>Sign In</Trans>}
</Button>
</>
)}
<Button type="submit" size="lg" loading={isSubmitting} className="dark:bg-documenso dark:hover:opacity-90">
{isSubmitting ? <Trans>Signing in...</Trans> : <Trans>Sign In</Trans>}
</Button>
{!isEmbeddedRedirect && (
<>
{hasSocialAuthEnabled && (
{isEmailPasswordSigninEnabled && hasSocialAuthEnabled && (
<div className="relative flex items-center justify-center gap-x-4 py-2 text-xs uppercase">
<div className="h-px flex-1 bg-border" />
<span className="bg-transparent text-muted-foreground">
@@ -88,7 +88,7 @@ export default function OrganisationSettingsDocumentPage() {
typedSignatureEnabled: signatureTypes.includes(DocumentSignatureType.TYPE),
uploadSignatureEnabled: signatureTypes.includes(DocumentSignatureType.UPLOAD),
drawSignatureEnabled: signatureTypes.includes(DocumentSignatureType.DRAW),
delegateDocumentOwnership,
delegateDocumentOwnership: delegateDocumentOwnership,
aiFeaturesEnabled,
envelopeExpirationPeriod: envelopeExpirationPeriod ?? undefined,
reminderSettings: reminderSettings ?? undefined,
@@ -99,7 +99,7 @@ export default function OrganisationSettingsDocumentPage() {
title: t`Document preferences updated`,
description: t`Your document preferences have been updated`,
});
} catch {
} catch (err) {
toast({
title: t`Something went wrong!`,
description: t`We were unable to update your document preferences at this time, please try again later`,
@@ -82,7 +82,7 @@ export default function TeamsSettingsPage() {
uploadSignatureEnabled: signatureTypes.includes(DocumentSignatureType.UPLOAD),
drawSignatureEnabled: signatureTypes.includes(DocumentSignatureType.DRAW),
}),
delegateDocumentOwnership,
delegateDocumentOwnership: delegateDocumentOwnership,
},
});
@@ -90,7 +90,7 @@ export default function TeamsSettingsPage() {
title: t`Document preferences updated`,
description: t`Your document preferences have been updated`,
});
} catch {
} catch (err) {
toast({
title: t`Something went wrong!`,
description: t`We were unable to update your document preferences at this time, please try again later`,
@@ -1,6 +1,7 @@
import { isSigninEnabledForProvider } from '@documenso/lib/constants/auth';
import { msg } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
import { Link } from 'react-router';
import { Link, redirect } from 'react-router';
import { ForgotPasswordForm } from '~/components/forms/forgot-password';
import { appMetaTags } from '~/utils/meta';
@@ -9,6 +10,14 @@ export function meta() {
return appMetaTags(msg`Forgot Password`);
}
export async function loader() {
if (!isSigninEnabledForProvider('email')) {
throw redirect('/signin');
}
return null;
}
export default function ForgotPasswordPage() {
return (
<div className="w-screen max-w-lg px-4">
@@ -1,3 +1,4 @@
import { isSigninEnabledForProvider } from '@documenso/lib/constants/auth';
import { getResetTokenValidity } from '@documenso/lib/server-only/user/get-reset-token-validity';
import { msg } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
@@ -13,6 +14,10 @@ export function meta() {
}
export async function loader({ params }: Route.LoaderArgs) {
if (!isSigninEnabledForProvider('email')) {
throw redirect('/signin');
}
const { token } = params;
const isValid = await getResetTokenValidity({ token });
@@ -1,7 +1,8 @@
import { isSigninEnabledForProvider } from '@documenso/lib/constants/auth';
import { Button } from '@documenso/ui/primitives/button';
import { msg } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
import { Link } from 'react-router';
import { Link, redirect } from 'react-router';
import { appMetaTags } from '~/utils/meta';
@@ -9,6 +10,14 @@ export function meta() {
return appMetaTags(msg`Reset Password`);
}
export async function loader() {
if (!isSigninEnabledForProvider('email')) {
throw redirect('/signin');
}
return null;
}
export default function ResetPasswordPage() {
return (
<div className="w-screen max-w-lg px-4">
@@ -1,8 +1,11 @@
import { authClient } from '@documenso/auth/client';
import { getOptionalSession } from '@documenso/auth/server/lib/utils/get-session';
import {
IS_GOOGLE_SSO_ENABLED,
IS_MICROSOFT_SSO_ENABLED,
IS_OIDC_AUTO_REDIRECT_DISABLED,
IS_OIDC_SSO_ENABLED,
isSigninEnabledForProvider,
isSignupEnabledForProvider,
OIDC_PROVIDER_LABEL,
} from '@documenso/lib/constants/auth';
@@ -11,6 +14,7 @@ import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { Loader2Icon } from 'lucide-react';
import { useEffect, useState } from 'react';
import { Link, redirect, useSearchParams } from 'react-router';
@@ -28,10 +32,20 @@ export async function loader({ request }: Route.LoaderArgs) {
const { isAuthenticated } = await getOptionalSession(request);
// SSR env variables.
const isGoogleSSOEnabled = IS_GOOGLE_SSO_ENABLED;
const isMicrosoftSSOEnabled = IS_MICROSOFT_SSO_ENABLED;
const isOIDCSSOEnabled = IS_OIDC_SSO_ENABLED;
const isEmailPasswordSigninEnabled = isSigninEnabledForProvider('email');
const isGoogleSSOEnabled = IS_GOOGLE_SSO_ENABLED && isSigninEnabledForProvider('google');
const isMicrosoftSSOEnabled = IS_MICROSOFT_SSO_ENABLED && isSigninEnabledForProvider('microsoft');
const isOIDCSSOEnabled = IS_OIDC_SSO_ENABLED && isSigninEnabledForProvider('oidc');
// Automatically redirect to OIDC when it is the only enabled signin transport,
// unless the redirect has been explicitly disabled via env.
const isOIDCOnlyTransport =
isOIDCSSOEnabled && !isEmailPasswordSigninEnabled && !isGoogleSSOEnabled && !isMicrosoftSSOEnabled;
const shouldAutoRedirectToOIDC = isOIDCOnlyTransport && !IS_OIDC_AUTO_REDIRECT_DISABLED;
const oidcProviderLabel = OIDC_PROVIDER_LABEL;
const isSignupEnabled =
isSignupEnabledForProvider('email') ||
(IS_GOOGLE_SSO_ENABLED && isSignupEnabledForProvider('google')) ||
@@ -47,18 +61,28 @@ export async function loader({ request }: Route.LoaderArgs) {
}
return {
isEmailPasswordSigninEnabled,
isGoogleSSOEnabled,
isMicrosoftSSOEnabled,
isOIDCSSOEnabled,
isSignupEnabled,
oidcProviderLabel,
returnTo,
shouldAutoRedirectToOIDC,
};
}
export default function SignIn({ loaderData }: Route.ComponentProps) {
const { isGoogleSSOEnabled, isMicrosoftSSOEnabled, isOIDCSSOEnabled, isSignupEnabled, oidcProviderLabel, returnTo } =
loaderData;
const {
isEmailPasswordSigninEnabled,
isGoogleSSOEnabled,
isMicrosoftSSOEnabled,
isOIDCSSOEnabled,
isSignupEnabled,
oidcProviderLabel,
returnTo,
shouldAutoRedirectToOIDC,
} = loaderData;
const { _ } = useLingui();
@@ -76,6 +100,27 @@ export default function SignIn({ loaderData }: Route.ComponentProps) {
setIsEmbeddedRedirect(params.get('embedded') === 'true');
}, []);
useEffect(() => {
if (!shouldAutoRedirectToOIDC) {
return;
}
void authClient.oidc.signIn({ redirectPath: returnTo ?? '/' });
}, [shouldAutoRedirectToOIDC, returnTo]);
if (shouldAutoRedirectToOIDC) {
return (
<div className="w-screen max-w-lg px-4">
<div className="flex flex-col items-center justify-center gap-y-4 py-12">
<Loader2Icon className="h-8 w-8 animate-spin text-muted-foreground" />
<p className="text-muted-foreground text-sm">
<Trans>Redirecting to {oidcProviderLabel || 'OIDC'}...</Trans>
</p>
</div>
</div>
);
}
return (
<div className="w-screen max-w-lg px-4">
<div className="z-10 rounded-xl border border-border bg-neutral-100 p-6 dark:bg-background">
@@ -95,6 +140,7 @@ export default function SignIn({ loaderData }: Route.ComponentProps) {
<hr className="-mx-6 my-4" />
<SignInForm
isEmailPasswordSigninEnabled={isEmailPasswordSigninEnabled}
isGoogleSSOEnabled={isGoogleSSOEnabled}
isMicrosoftSSOEnabled={isMicrosoftSSOEnabled}
isOIDCSSOEnabled={isOIDCSSOEnabled}
+6
View File
@@ -64,6 +64,12 @@ services:
- NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP=${NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP}
- NEXT_PUBLIC_DISABLE_OIDC_SIGNUP=${NEXT_PUBLIC_DISABLE_OIDC_SIGNUP}
- NEXT_PRIVATE_ALLOWED_SIGNUP_DOMAINS=${NEXT_PRIVATE_ALLOWED_SIGNUP_DOMAINS}
- NEXT_PUBLIC_DISABLE_SIGNIN=${NEXT_PUBLIC_DISABLE_SIGNIN}
- NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNIN=${NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNIN}
- NEXT_PUBLIC_DISABLE_GOOGLE_SIGNIN=${NEXT_PUBLIC_DISABLE_GOOGLE_SIGNIN}
- NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNIN=${NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNIN}
- NEXT_PUBLIC_DISABLE_OIDC_SIGNIN=${NEXT_PUBLIC_DISABLE_OIDC_SIGNIN}
- NEXT_PUBLIC_DISABLE_OIDC_AUTO_REDIRECT=${NEXT_PUBLIC_DISABLE_OIDC_AUTO_REDIRECT}
- NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH=${NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH:-/opt/documenso/cert.p12}
- NEXT_PRIVATE_SIGNING_PASSPHRASE=${NEXT_PRIVATE_SIGNING_PASSPHRASE}
- NEXT_PUBLIC_USE_INTERNAL_URL_BROWSERLESS=${NEXT_PUBLIC_USE_INTERNAL_URL_BROWSERLESS}
@@ -0,0 +1,163 @@
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { generateDatabaseId } from '@documenso/lib/universal/id';
import { prisma } from '@documenso/prisma';
import { seedTeamMember } from '@documenso/prisma/seed/teams';
import { seedUser } from '@documenso/prisma/seed/users';
import { expect, type Page, test } from '@playwright/test';
import { OrganisationGroupType, OrganisationMemberRole, TeamMemberRole } from '@prisma/client';
import { apiSignin } from '../fixtures/authentication';
const WEBAPP_BASE_URL = NEXT_PUBLIC_WEBAPP_URL();
test.describe.configure({ mode: 'parallel' });
/**
* Calls a team-group tRPC mutation directly, bypassing the UI.
*
* The UI only ever surfaces CUSTOM / INTERNAL_ORGANISATION groups, so these
* authorisation rules must be enforced on the server - a crafted request can
* target any `teamGroupId`, including the system-managed INTERNAL_TEAM groups.
*/
const callTeamGroupMutation = (
page: Page,
procedure: 'team.group.delete' | 'team.group.update',
teamId: number,
input: Record<string, unknown>,
) =>
page.context().request.post(`${WEBAPP_BASE_URL}/api/trpc/${procedure}`, {
headers: { 'content-type': 'application/json', 'x-team-id': teamId.toString() },
data: JSON.stringify({ json: input }),
});
/**
* Every team is created with three system-managed INTERNAL_TEAM groups
* (admin/manager/member). They are the backbone of team-specific access and,
* like organisation internal groups, must not be deletable - deleting them
* silently strips team members of access while leaving the team row in place.
*/
test('[TEAMS]: internal team groups cannot be deleted via the API', async ({ page }) => {
// Member inheritance OFF: membership is granted exclusively through the team's
// INTERNAL_TEAM groups, so removing them is what causes the access loss.
const { user: owner, team } = await seedUser({ inheritMembers: false });
// A direct team member whose access depends on the INTERNAL_TEAM member group.
const directMember = await seedTeamMember({ teamId: team.id, role: TeamMemberRole.MEMBER });
await apiSignin({ page, email: owner.email });
const internalTeamGroups = await prisma.teamGroup.findMany({
where: {
teamId: team.id,
organisationGroup: { type: OrganisationGroupType.INTERNAL_TEAM },
},
});
// admin + manager + member.
expect(internalTeamGroups).toHaveLength(3);
for (const group of internalTeamGroups) {
const response = await callTeamGroupMutation(page, 'team.group.delete', team.id, {
teamId: team.id,
teamGroupId: group.id,
});
expect(response.status(), `INTERNAL_TEAM ${group.teamRole} group must not be deletable`).not.toBe(200);
}
// None of the internal groups were removed.
const remaining = await prisma.teamGroup.count({
where: {
teamId: team.id,
organisationGroup: { type: OrganisationGroupType.INTERNAL_TEAM },
},
});
expect(remaining).toBe(3);
// The direct member therefore keeps their team access.
const memberStillHasAccess = await prisma.teamGroup.findFirst({
where: {
teamId: team.id,
organisationGroup: {
type: OrganisationGroupType.INTERNAL_TEAM,
organisationGroupMembers: {
some: { organisationMember: { userId: directMember.id } },
},
},
},
});
expect(memberStillHasAccess).not.toBeNull();
});
/**
* Guards against over-blocking: user-created (CUSTOM) team groups are not
* internal and must remain removable by team managers/admins.
*/
test('[TEAMS]: custom team groups can still be deleted', async ({ page }) => {
const { user: owner, organisation, team } = await seedUser({ inheritMembers: false });
const customGroup = await prisma.organisationGroup.create({
data: {
id: generateDatabaseId('org_group'),
name: `custom-${team.url}`,
type: OrganisationGroupType.CUSTOM,
organisationRole: OrganisationMemberRole.MEMBER,
organisationId: organisation.id,
teamGroups: {
create: {
id: generateDatabaseId('team_group'),
teamId: team.id,
teamRole: TeamMemberRole.MEMBER,
},
},
},
include: { teamGroups: true },
});
const customTeamGroup = customGroup.teamGroups[0];
await apiSignin({ page, email: owner.email });
const response = await callTeamGroupMutation(page, 'team.group.delete', team.id, {
teamId: team.id,
teamGroupId: customTeamGroup.id,
});
expect(response.status()).toBe(200);
const deleted = await prisma.teamGroup.findUnique({ where: { id: customTeamGroup.id } });
expect(deleted).toBeNull();
});
/**
* The same root cause affects updates: an INTERNAL_TEAM group's role must not be
* editable either, otherwise a team admin could rewrite the backbone roles
* (e.g. promote the member group to admin).
*/
test('[TEAMS]: internal team groups cannot be updated via the API', async ({ page }) => {
const { user: owner, team } = await seedUser({ inheritMembers: false });
await apiSignin({ page, email: owner.email });
const internalMemberGroup = await prisma.teamGroup.findFirstOrThrow({
where: {
teamId: team.id,
teamRole: TeamMemberRole.MEMBER,
organisationGroup: { type: OrganisationGroupType.INTERNAL_TEAM },
},
});
const response = await callTeamGroupMutation(page, 'team.group.update', team.id, {
id: internalMemberGroup.id,
data: { teamRole: TeamMemberRole.ADMIN },
});
expect(response.status()).not.toBe(200);
const reloaded = await prisma.teamGroup.findUniqueOrThrow({ where: { id: internalMemberGroup.id } });
expect(reloaded.teamRole).toBe(TeamMemberRole.MEMBER);
});
@@ -17,6 +17,7 @@ export const AuthenticationErrorCode = {
// TwoFactorMissingSecret: 'TWO_FACTOR_MISSING_SECRET',
// TwoFactorMissingCredentials: 'TWO_FACTOR_MISSING_CREDENTIALS',
InvalidTwoFactorCode: 'INVALID_TWO_FACTOR_CODE',
SigninDisabled: 'SIGNIN_DISABLED',
SignupDisabled: 'SIGNUP_DISABLED',
SignupDisposableEmail: 'SIGNUP_DISPOSABLE_EMAIL',
// IncorrectTwoFactorBackupCode: 'INCORRECT_TWO_FACTOR_BACKUP_CODE',
@@ -1,6 +1,7 @@
import {
isDisposableEmail,
isEmailDomainAllowedForSignup,
isSigninEnabledForProvider,
isSignupEnabledForProvider,
} from '@documenso/lib/constants/auth';
import { EMAIL_VERIFICATION_STATE } from '@documenso/lib/constants/email';
@@ -64,6 +65,12 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
.post('/authorize', sValidator('json', ZSignInSchema), async (c) => {
const requestMetadata = c.get('requestMetadata');
if (!isSigninEnabledForProvider('email')) {
throw new AppError(AuthenticationErrorCode.SigninDisabled, {
statusCode: 400,
});
}
const { email, password, totpCode, backupCode, csrfToken, captchaToken } = c.req.valid('json');
const loginLimitResult = await loginRateLimit.check({
@@ -244,6 +251,12 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
const { password, currentPassword } = c.req.valid('json');
const requestMetadata = c.get('requestMetadata');
if (!isSigninEnabledForProvider('email')) {
throw new AppError(AuthenticationErrorCode.SigninDisabled, {
statusCode: 400,
});
}
const { session, user } = await getSession(c);
await updatePassword({
@@ -346,6 +359,12 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
.post('/forgot-password', sValidator('json', ZForgotPasswordSchema), async (c) => {
const requestMetadata = c.get('requestMetadata');
if (!isSigninEnabledForProvider('email')) {
throw new AppError(AuthenticationErrorCode.SigninDisabled, {
statusCode: 400,
});
}
const { email } = c.req.valid('json');
const forgotLimitResult = await forgotPasswordRateLimit.check({
@@ -377,6 +396,12 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
.post('/reset-password', sValidator('json', ZResetPasswordSchema), async (c) => {
const requestMetadata = c.get('requestMetadata');
if (!isSigninEnabledForProvider('email')) {
throw new AppError(AuthenticationErrorCode.SigninDisabled, {
statusCode: 400,
});
}
const { token, password } = c.req.valid('json');
const resetLimitResult = await resetPasswordRateLimit.check({
+27
View File
@@ -41,6 +41,14 @@ export const IS_OIDC_SSO_ENABLED = Boolean(
export const OIDC_PROVIDER_LABEL = env('NEXT_PRIVATE_OIDC_PROVIDER_LABEL');
/**
* Opt-out flag for the automatic OIDC redirect.
*
* When OIDC is the only enabled signin transport we redirect to the provider
* automatically. Set this to "true" to keep rendering the signin page instead.
*/
export const IS_OIDC_AUTO_REDIRECT_DISABLED = env('NEXT_PUBLIC_DISABLE_OIDC_AUTO_REDIRECT') === 'true';
export const USER_SECURITY_AUDIT_LOG_MAP: Record<string, string> = {
ACCOUNT_SSO_LINK: 'Linked account to SSO',
ACCOUNT_SSO_UNLINK: 'Unlinked account from SSO',
@@ -188,3 +196,22 @@ export const isSignupEnabledForProvider = (provider: 'email' | 'google' | 'micro
return env(flagMap[provider]) !== 'true';
};
/**
* Check if signin is enabled for the given provider.
* The master switch takes precedence over the per-provider flags.
*/
export const isSigninEnabledForProvider = (provider: 'email' | 'google' | 'microsoft' | 'oidc'): boolean => {
if (env('NEXT_PUBLIC_DISABLE_SIGNIN') === 'true') {
return false;
}
const flagMap = {
email: 'NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNIN',
google: 'NEXT_PUBLIC_DISABLE_GOOGLE_SIGNIN',
microsoft: 'NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNIN',
oidc: 'NEXT_PUBLIC_DISABLE_OIDC_SIGNIN',
} as const;
return env(flagMap[provider]) !== 'true';
};
+22 -5
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: de\n"
"Project-Id-Version: documenso-app\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-06-22 13:53\n"
"PO-Revision-Date: 2026-06-30 07:18\n"
"Last-Translator: \n"
"Language-Team: German\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -2441,6 +2441,7 @@ msgstr "Branding-Logo"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.branding.tsx
msgid "Branding Preferences"
msgstr "Markenpräferenzen"
@@ -3572,6 +3573,7 @@ msgid "Currently all organisation members can access this team"
msgstr "Derzeit können alle Organisationsmitglieder auf dieses Team zugreifen"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.branding.tsx
msgid "Currently branding can only be configured for Teams and above plans."
msgstr "Zurzeit kann das Branding nur für Teams und darüber konfiguriert werden."
@@ -4214,8 +4216,8 @@ msgstr "Dokument storniert"
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
#: packages/lib/jobs/definitions/emails/send-document-deleted-emails.handler.ts
#: packages/lib/server-only/admin/admin-super-delete-document.ts
#: packages/lib/server-only/document/delete-document.ts
msgid "Document Cancelled"
msgstr "Dokument storniert"
@@ -7942,6 +7944,11 @@ msgstr "Original"
msgid "Otherwise, the document will be created as a draft."
msgstr "Andernfalls wird das Dokument als Entwurf erstellt."
#: apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx
#: apps/remix/app/components/general/envelope-editor/envelope-editor-fields-page.tsx
msgid "Overlapping fields detected"
msgstr "Überlappende Felder erkannt"
#: apps/remix/app/components/forms/document-preferences-form.tsx
#: apps/remix/app/components/forms/email-preferences-form.tsx
msgid "Override organisation settings"
@@ -8917,6 +8924,11 @@ msgstr "Weiterleitungs-URL"
msgid "Redirecting"
msgstr "Weiterleitung"
#. placeholder {0}: oidcProviderLabel || 'OIDC'
#: apps/remix/app/routes/_unauthenticated+/signin.tsx
msgid "Redirecting to {0}..."
msgstr "Weiterleitung zu {0}..."
#: apps/remix/app/components/forms/signup.tsx
#: apps/remix/app/components/general/claim-account.tsx
msgid "Registration Successful"
@@ -10219,6 +10231,11 @@ msgstr "Website Einstellungen"
msgid "Skip"
msgstr "Überspringen"
#: apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx
#: apps/remix/app/components/general/envelope-editor/envelope-editor-fields-page.tsx
msgid "Some fields are placed on top of each other. This may complicate the signing process or cause fields to not work as expected."
msgstr "Einige Felder sind übereinander platziert. Dies kann den Signaturvorgang erschweren oder dazu führen, dass Felder nicht wie erwartet funktionieren."
#: packages/ui/primitives/document-flow/missing-signature-field-dialog.tsx
msgid "Some signers have not been assigned a signature field. Please assign at least 1 signature field to each signer before proceeding."
msgstr "Einige Unterzeichner haben noch kein Unterschriftsfeld zugewiesen bekommen. Bitte weisen Sie jedem Unterzeichner mindestens ein Unterschriftsfeld zu, bevor Sie fortfahren."
@@ -12342,6 +12359,7 @@ msgstr "Banner aktualisieren"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.email-domains._index.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.branding.tsx
msgid "Update Billing"
msgstr "Rechnungsdaten aktualisieren"
@@ -12942,7 +12960,7 @@ msgstr "Warten"
msgid "Waiting for others"
msgstr "Warten auf andere"
#: packages/lib/server-only/document/send-pending-email.ts
#: packages/lib/jobs/definitions/emails/send-document-pending-email.handler.ts
msgid "Waiting for others to complete signing."
msgstr "Warten auf andere, um die Unterzeichnung abzuschließen."
@@ -13916,8 +13934,7 @@ msgstr "Du wurdest eingeladen, {0} auf Documenso beizutreten"
msgid "You have been invited to join the following organisation"
msgstr "Sie wurden eingeladen, der folgenden Organisation beizutreten"
#: packages/lib/server-only/recipient/delete-envelope-recipient.ts
#: packages/lib/server-only/recipient/set-document-recipients.ts
#: packages/lib/jobs/definitions/emails/send-recipient-removed-email.handler.ts
msgid "You have been removed from a document"
msgstr "Du wurdest von einem Dokument entfernt"
+5
View File
@@ -8908,6 +8908,11 @@ msgstr "Redirect URL"
msgid "Redirecting"
msgstr "Redirecting"
#. placeholder {0}: oidcProviderLabel || 'OIDC'
#: apps/remix/app/routes/_unauthenticated+/signin.tsx
msgid "Redirecting to {0}..."
msgstr "Redirecting to {0}..."
#: apps/remix/app/components/forms/signup.tsx
#: apps/remix/app/components/general/claim-account.tsx
msgid "Registration Successful"
+22 -5
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: es\n"
"Project-Id-Version: documenso-app\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-06-22 13:53\n"
"PO-Revision-Date: 2026-06-30 07:18\n"
"Last-Translator: \n"
"Language-Team: Spanish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -2441,6 +2441,7 @@ msgstr "Logotipo de Marca"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.branding.tsx
msgid "Branding Preferences"
msgstr "Preferencias de marca"
@@ -3572,6 +3573,7 @@ msgid "Currently all organisation members can access this team"
msgstr "Actualmente, todos los miembros de la organización pueden acceder a este equipo"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.branding.tsx
msgid "Currently branding can only be configured for Teams and above plans."
msgstr "Actualmente la marca solo se puede configurar para Equipos y planes superiores."
@@ -4214,8 +4216,8 @@ msgstr "Documento cancelado"
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
#: packages/lib/jobs/definitions/emails/send-document-deleted-emails.handler.ts
#: packages/lib/server-only/admin/admin-super-delete-document.ts
#: packages/lib/server-only/document/delete-document.ts
msgid "Document Cancelled"
msgstr "Documento cancelado"
@@ -7942,6 +7944,11 @@ msgstr "Original"
msgid "Otherwise, the document will be created as a draft."
msgstr "De lo contrario, el documento se creará como un borrador."
#: apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx
#: apps/remix/app/components/general/envelope-editor/envelope-editor-fields-page.tsx
msgid "Overlapping fields detected"
msgstr "Se detectaron campos superpuestos"
#: apps/remix/app/components/forms/document-preferences-form.tsx
#: apps/remix/app/components/forms/email-preferences-form.tsx
msgid "Override organisation settings"
@@ -8917,6 +8924,11 @@ msgstr "URL de redirección"
msgid "Redirecting"
msgstr "Redireccionando"
#. placeholder {0}: oidcProviderLabel || 'OIDC'
#: apps/remix/app/routes/_unauthenticated+/signin.tsx
msgid "Redirecting to {0}..."
msgstr "Redirigiendo a {0}..."
#: apps/remix/app/components/forms/signup.tsx
#: apps/remix/app/components/general/claim-account.tsx
msgid "Registration Successful"
@@ -10219,6 +10231,11 @@ msgstr "Configuraciones del sitio"
msgid "Skip"
msgstr "Omitir"
#: apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx
#: apps/remix/app/components/general/envelope-editor/envelope-editor-fields-page.tsx
msgid "Some fields are placed on top of each other. This may complicate the signing process or cause fields to not work as expected."
msgstr "Algunos campos están colocados uno encima de otro. Esto puede complicar el proceso de firma o hacer que los campos no funcionen como se espera."
#: packages/ui/primitives/document-flow/missing-signature-field-dialog.tsx
msgid "Some signers have not been assigned a signature field. Please assign at least 1 signature field to each signer before proceeding."
msgstr "Algunos firmantes no han sido asignados a un campo de firma. Asigne al menos 1 campo de firma a cada firmante antes de continuar."
@@ -12342,6 +12359,7 @@ msgstr "Actualizar banner"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.email-domains._index.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.branding.tsx
msgid "Update Billing"
msgstr "Actualizar facturación"
@@ -12942,7 +12960,7 @@ msgstr "Esperando"
msgid "Waiting for others"
msgstr "Esperando a otros"
#: packages/lib/server-only/document/send-pending-email.ts
#: packages/lib/jobs/definitions/emails/send-document-pending-email.handler.ts
msgid "Waiting for others to complete signing."
msgstr "Esperando a que otros completen la firma."
@@ -13916,8 +13934,7 @@ msgstr "Te han invitado a unirte a {0} en Documenso"
msgid "You have been invited to join the following organisation"
msgstr "Has sido invitado a unirte a la siguiente organización"
#: packages/lib/server-only/recipient/delete-envelope-recipient.ts
#: packages/lib/server-only/recipient/set-document-recipients.ts
#: packages/lib/jobs/definitions/emails/send-recipient-removed-email.handler.ts
msgid "You have been removed from a document"
msgstr "Te han eliminado de un documento"
+22 -5
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: fr\n"
"Project-Id-Version: documenso-app\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-06-22 13:53\n"
"PO-Revision-Date: 2026-06-30 07:18\n"
"Last-Translator: \n"
"Language-Team: French\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
@@ -2441,6 +2441,7 @@ msgstr "Logo de la marque"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.branding.tsx
msgid "Branding Preferences"
msgstr "Préférences de branding"
@@ -3572,6 +3573,7 @@ msgid "Currently all organisation members can access this team"
msgstr "Actuellement, tous les membres de l'organisation peuvent accéder à cette équipe"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.branding.tsx
msgid "Currently branding can only be configured for Teams and above plans."
msgstr "Actuellement, la personnalisation de la marque ne peut être configurée que pour les plans Équipe et plus."
@@ -4214,8 +4216,8 @@ msgstr "Document annulé"
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
#: packages/lib/jobs/definitions/emails/send-document-deleted-emails.handler.ts
#: packages/lib/server-only/admin/admin-super-delete-document.ts
#: packages/lib/server-only/document/delete-document.ts
msgid "Document Cancelled"
msgstr "Document Annulé"
@@ -7942,6 +7944,11 @@ msgstr "Original"
msgid "Otherwise, the document will be created as a draft."
msgstr "Sinon, le document sera créé sous forme de brouillon."
#: apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx
#: apps/remix/app/components/general/envelope-editor/envelope-editor-fields-page.tsx
msgid "Overlapping fields detected"
msgstr "Chevauchement de champs détecté"
#: apps/remix/app/components/forms/document-preferences-form.tsx
#: apps/remix/app/components/forms/email-preferences-form.tsx
msgid "Override organisation settings"
@@ -8917,6 +8924,11 @@ msgstr "URL de redirection"
msgid "Redirecting"
msgstr "Redirection"
#. placeholder {0}: oidcProviderLabel || 'OIDC'
#: apps/remix/app/routes/_unauthenticated+/signin.tsx
msgid "Redirecting to {0}..."
msgstr "Redirection vers {0}..."
#: apps/remix/app/components/forms/signup.tsx
#: apps/remix/app/components/general/claim-account.tsx
msgid "Registration Successful"
@@ -10219,6 +10231,11 @@ msgstr "Paramètres du site"
msgid "Skip"
msgstr "Ignorer"
#: apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx
#: apps/remix/app/components/general/envelope-editor/envelope-editor-fields-page.tsx
msgid "Some fields are placed on top of each other. This may complicate the signing process or cause fields to not work as expected."
msgstr "Certains champs sont placés les uns sur les autres. Cela peut compliquer le processus de signature ou empêcher certains champs de fonctionner comme prévu."
#: packages/ui/primitives/document-flow/missing-signature-field-dialog.tsx
msgid "Some signers have not been assigned a signature field. Please assign at least 1 signature field to each signer before proceeding."
msgstr "Certains signataires n'ont pas été assignés à un champ de signature. Veuillez assigner au moins 1 champ de signature à chaque signataire avant de continuer."
@@ -12342,6 +12359,7 @@ msgstr "Mettre à jour la bannière"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.email-domains._index.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.branding.tsx
msgid "Update Billing"
msgstr "Mettre à jour la facturation"
@@ -12942,7 +12960,7 @@ msgstr "En attente"
msgid "Waiting for others"
msgstr "En attente des autres"
#: packages/lib/server-only/document/send-pending-email.ts
#: packages/lib/jobs/definitions/emails/send-document-pending-email.handler.ts
msgid "Waiting for others to complete signing."
msgstr "En attente que d'autres terminent la signature."
@@ -13916,8 +13934,7 @@ msgstr "Vous avez été invité à rejoindre {0} sur Documenso"
msgid "You have been invited to join the following organisation"
msgstr "Vous avez été invité à rejoindre l'organisation suivante"
#: packages/lib/server-only/recipient/delete-envelope-recipient.ts
#: packages/lib/server-only/recipient/set-document-recipients.ts
#: packages/lib/jobs/definitions/emails/send-recipient-removed-email.handler.ts
msgid "You have been removed from a document"
msgstr "Vous avez été supprimé d'un document"
+22 -5
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: it\n"
"Project-Id-Version: documenso-app\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-06-22 13:53\n"
"PO-Revision-Date: 2026-06-30 07:18\n"
"Last-Translator: \n"
"Language-Team: Italian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -2441,6 +2441,7 @@ msgstr "Logo del Marchio"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.branding.tsx
msgid "Branding Preferences"
msgstr "Preferenze per il branding"
@@ -3572,6 +3573,7 @@ msgid "Currently all organisation members can access this team"
msgstr "Attualmente tutti i membri dell'organizzazione possono accedere a questo team"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.branding.tsx
msgid "Currently branding can only be configured for Teams and above plans."
msgstr "Attualmente il marchio può essere configurato solo per i piani Team e superiori."
@@ -4214,8 +4216,8 @@ msgstr "Documento annullato"
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
#: packages/lib/jobs/definitions/emails/send-document-deleted-emails.handler.ts
#: packages/lib/server-only/admin/admin-super-delete-document.ts
#: packages/lib/server-only/document/delete-document.ts
msgid "Document Cancelled"
msgstr "Documento Annullato"
@@ -7942,6 +7944,11 @@ msgstr "Originale"
msgid "Otherwise, the document will be created as a draft."
msgstr "Altrimenti, il documento sarà creato come bozza."
#: apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx
#: apps/remix/app/components/general/envelope-editor/envelope-editor-fields-page.tsx
msgid "Overlapping fields detected"
msgstr "Campi sovrapposti rilevati"
#: apps/remix/app/components/forms/document-preferences-form.tsx
#: apps/remix/app/components/forms/email-preferences-form.tsx
msgid "Override organisation settings"
@@ -8917,6 +8924,11 @@ msgstr "URL di reindirizzamento"
msgid "Redirecting"
msgstr "Reindirizzamento"
#. placeholder {0}: oidcProviderLabel || 'OIDC'
#: apps/remix/app/routes/_unauthenticated+/signin.tsx
msgid "Redirecting to {0}..."
msgstr "Reindirizzamento a {0}..."
#: apps/remix/app/components/forms/signup.tsx
#: apps/remix/app/components/general/claim-account.tsx
msgid "Registration Successful"
@@ -10219,6 +10231,11 @@ msgstr "Impostazioni del sito"
msgid "Skip"
msgstr "Salta"
#: apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx
#: apps/remix/app/components/general/envelope-editor/envelope-editor-fields-page.tsx
msgid "Some fields are placed on top of each other. This may complicate the signing process or cause fields to not work as expected."
msgstr "Alcuni campi sono posizionati uno sopra laltro. Questo può complicare il processo di firma o causare il malfunzionamento dei campi."
#: packages/ui/primitives/document-flow/missing-signature-field-dialog.tsx
msgid "Some signers have not been assigned a signature field. Please assign at least 1 signature field to each signer before proceeding."
msgstr "Alcuni firmatari non hanno un campo firma assegnato. Assegna almeno 1 campo di firma a ciascun firmatario prima di procedere."
@@ -12342,6 +12359,7 @@ msgstr "Aggiorna banner"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.email-domains._index.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.branding.tsx
msgid "Update Billing"
msgstr "Aggiorna fatturazione"
@@ -12942,7 +12960,7 @@ msgstr "In attesa"
msgid "Waiting for others"
msgstr "In attesa di altri"
#: packages/lib/server-only/document/send-pending-email.ts
#: packages/lib/jobs/definitions/emails/send-document-pending-email.handler.ts
msgid "Waiting for others to complete signing."
msgstr "In attesa che altri completino la firma."
@@ -13916,8 +13934,7 @@ msgstr "Sei stato invitato a unirti a {0} su Documenso"
msgid "You have been invited to join the following organisation"
msgstr "Sei stato invitato a unirti alla seguente organizzazione"
#: packages/lib/server-only/recipient/delete-envelope-recipient.ts
#: packages/lib/server-only/recipient/set-document-recipients.ts
#: packages/lib/jobs/definitions/emails/send-recipient-removed-email.handler.ts
msgid "You have been removed from a document"
msgstr "Sei stato rimosso da un documento"
+22 -5
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: ja\n"
"Project-Id-Version: documenso-app\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-06-22 13:53\n"
"PO-Revision-Date: 2026-06-30 07:18\n"
"Last-Translator: \n"
"Language-Team: Japanese\n"
"Plural-Forms: nplurals=1; plural=0;\n"
@@ -2441,6 +2441,7 @@ msgstr "ブランディングロゴ"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.branding.tsx
msgid "Branding Preferences"
msgstr "ブランディング設定"
@@ -3572,6 +3573,7 @@ msgid "Currently all organisation members can access this team"
msgstr "現在、すべての組織メンバーがこのチームにアクセスできます"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.branding.tsx
msgid "Currently branding can only be configured for Teams and above plans."
msgstr "現在、ブランディングは Teams プラン以上のみ設定できます。"
@@ -4214,8 +4216,8 @@ msgstr "文書は取り消されました"
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
#: packages/lib/jobs/definitions/emails/send-document-deleted-emails.handler.ts
#: packages/lib/server-only/admin/admin-super-delete-document.ts
#: packages/lib/server-only/document/delete-document.ts
msgid "Document Cancelled"
msgstr "文書はキャンセルされました"
@@ -7942,6 +7944,11 @@ msgstr "オリジナル"
msgid "Otherwise, the document will be created as a draft."
msgstr "チェックを入れない場合、文書は下書きとして作成されます。"
#: apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx
#: apps/remix/app/components/general/envelope-editor/envelope-editor-fields-page.tsx
msgid "Overlapping fields detected"
msgstr "フィールドの重なりが検出されました"
#: apps/remix/app/components/forms/document-preferences-form.tsx
#: apps/remix/app/components/forms/email-preferences-form.tsx
msgid "Override organisation settings"
@@ -8917,6 +8924,11 @@ msgstr "リダイレクト URL"
msgid "Redirecting"
msgstr "リダイレクト中"
#. placeholder {0}: oidcProviderLabel || 'OIDC'
#: apps/remix/app/routes/_unauthenticated+/signin.tsx
msgid "Redirecting to {0}..."
msgstr "{0} にリダイレクトしています…"
#: apps/remix/app/components/forms/signup.tsx
#: apps/remix/app/components/general/claim-account.tsx
msgid "Registration Successful"
@@ -10219,6 +10231,11 @@ msgstr "サイト設定"
msgid "Skip"
msgstr "スキップ"
#: apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx
#: apps/remix/app/components/general/envelope-editor/envelope-editor-fields-page.tsx
msgid "Some fields are placed on top of each other. This may complicate the signing process or cause fields to not work as expected."
msgstr "一部のフィールドが互いに重なるように配置されています。これにより、署名プロセスが複雑になったり、フィールドが期待どおりに動作しない可能性があります。"
#: packages/ui/primitives/document-flow/missing-signature-field-dialog.tsx
msgid "Some signers have not been assigned a signature field. Please assign at least 1 signature field to each signer before proceeding."
msgstr "一部の署名者に署名フィールドが割り当てられていません。続行する前に、各署名者に少なくとも 1 つの署名フィールドを割り当ててください。"
@@ -12342,6 +12359,7 @@ msgstr "バナーを更新"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.email-domains._index.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.branding.tsx
msgid "Update Billing"
msgstr "請求情報を更新"
@@ -12942,7 +12960,7 @@ msgstr "保留中"
msgid "Waiting for others"
msgstr "他の人の完了待ち"
#: packages/lib/server-only/document/send-pending-email.ts
#: packages/lib/jobs/definitions/emails/send-document-pending-email.handler.ts
msgid "Waiting for others to complete signing."
msgstr "他の署名者による署名完了を待っています。"
@@ -13916,8 +13934,7 @@ msgstr "Documenso で {0} に参加するよう招待されています"
msgid "You have been invited to join the following organisation"
msgstr "次の組織に参加するよう招待されています。"
#: packages/lib/server-only/recipient/delete-envelope-recipient.ts
#: packages/lib/server-only/recipient/set-document-recipients.ts
#: packages/lib/jobs/definitions/emails/send-recipient-removed-email.handler.ts
msgid "You have been removed from a document"
msgstr "ドキュメントから削除されました"
+22 -5
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: ko\n"
"Project-Id-Version: documenso-app\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-06-22 13:53\n"
"PO-Revision-Date: 2026-06-30 07:18\n"
"Last-Translator: \n"
"Language-Team: Korean\n"
"Plural-Forms: nplurals=1; plural=0;\n"
@@ -2441,6 +2441,7 @@ msgstr "브랜딩 로고"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.branding.tsx
msgid "Branding Preferences"
msgstr "브랜딩 환경설정"
@@ -3572,6 +3573,7 @@ msgid "Currently all organisation members can access this team"
msgstr "현재 모든 조직 구성원이 이 팀에 접근할 수 있습니다."
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.branding.tsx
msgid "Currently branding can only be configured for Teams and above plans."
msgstr "브랜딩은 현재 Teams 요금제 이상에서만 구성할 수 있습니다."
@@ -4214,8 +4216,8 @@ msgstr "문서가 취소되었습니다"
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
#: packages/lib/jobs/definitions/emails/send-document-deleted-emails.handler.ts
#: packages/lib/server-only/admin/admin-super-delete-document.ts
#: packages/lib/server-only/document/delete-document.ts
msgid "Document Cancelled"
msgstr "문서가 취소됨"
@@ -7942,6 +7944,11 @@ msgstr "원본"
msgid "Otherwise, the document will be created as a draft."
msgstr "그렇지 않으면 문서는 초안으로 생성됩니다."
#: apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx
#: apps/remix/app/components/general/envelope-editor/envelope-editor-fields-page.tsx
msgid "Overlapping fields detected"
msgstr "필드가 서로 겹쳐 있습니다"
#: apps/remix/app/components/forms/document-preferences-form.tsx
#: apps/remix/app/components/forms/email-preferences-form.tsx
msgid "Override organisation settings"
@@ -8917,6 +8924,11 @@ msgstr "리디렉션 URL"
msgid "Redirecting"
msgstr "리디렉션 중"
#. placeholder {0}: oidcProviderLabel || 'OIDC'
#: apps/remix/app/routes/_unauthenticated+/signin.tsx
msgid "Redirecting to {0}..."
msgstr "{0}(으)로 리디렉션하는 중입니다..."
#: apps/remix/app/components/forms/signup.tsx
#: apps/remix/app/components/general/claim-account.tsx
msgid "Registration Successful"
@@ -10219,6 +10231,11 @@ msgstr "사이트 설정"
msgid "Skip"
msgstr "건너뛰기"
#: apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx
#: apps/remix/app/components/general/envelope-editor/envelope-editor-fields-page.tsx
msgid "Some fields are placed on top of each other. This may complicate the signing process or cause fields to not work as expected."
msgstr "일부 필드가 서로 겹쳐 배치되어 있습니다. 이는 서명 과정을 복잡하게 만들거나 필드가 예상대로 작동하지 않게 할 수 있습니다."
#: packages/ui/primitives/document-flow/missing-signature-field-dialog.tsx
msgid "Some signers have not been assigned a signature field. Please assign at least 1 signature field to each signer before proceeding."
msgstr "일부 서명자에게 서명 필드가 할당되지 않았습니다. 진행하기 전에 각 서명자에게 최소 1개 이상의 서명 필드를 할당해 주세요."
@@ -12342,6 +12359,7 @@ msgstr "배너 업데이트"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.email-domains._index.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.branding.tsx
msgid "Update Billing"
msgstr "결제 정보 업데이트"
@@ -12942,7 +12960,7 @@ msgstr "대기 중"
msgid "Waiting for others"
msgstr "다른 사람을 기다리는 중"
#: packages/lib/server-only/document/send-pending-email.ts
#: packages/lib/jobs/definitions/emails/send-document-pending-email.handler.ts
msgid "Waiting for others to complete signing."
msgstr "다른 서명자들이 이 문서에 서명 완료하기를 기다리는 중입니다."
@@ -13916,8 +13934,7 @@ msgstr "Documenso에서 {0} 조직에 초대되었습니다."
msgid "You have been invited to join the following organisation"
msgstr "다음 조직에 참여하라는 초대를 받았습니다."
#: packages/lib/server-only/recipient/delete-envelope-recipient.ts
#: packages/lib/server-only/recipient/set-document-recipients.ts
#: packages/lib/jobs/definitions/emails/send-recipient-removed-email.handler.ts
msgid "You have been removed from a document"
msgstr "문서에서 제거되었습니다."
+22 -5
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: nl\n"
"Project-Id-Version: documenso-app\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-06-22 13:53\n"
"PO-Revision-Date: 2026-06-30 07:18\n"
"Last-Translator: \n"
"Language-Team: Dutch\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -2441,6 +2441,7 @@ msgstr "Branding-logo"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.branding.tsx
msgid "Branding Preferences"
msgstr "Brandingvoorkeuren"
@@ -3572,6 +3573,7 @@ msgid "Currently all organisation members can access this team"
msgstr "Momenteel hebben alle organisatieleden toegang tot dit team"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.branding.tsx
msgid "Currently branding can only be configured for Teams and above plans."
msgstr "Branding kan momenteel alleen worden geconfigureerd voor Teams- en hogere abonnementen."
@@ -4214,8 +4216,8 @@ msgstr "Document geannuleerd"
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
#: packages/lib/jobs/definitions/emails/send-document-deleted-emails.handler.ts
#: packages/lib/server-only/admin/admin-super-delete-document.ts
#: packages/lib/server-only/document/delete-document.ts
msgid "Document Cancelled"
msgstr "Document geannuleerd"
@@ -7942,6 +7944,11 @@ msgstr "Origineel"
msgid "Otherwise, the document will be created as a draft."
msgstr "Anders wordt het document als concept aangemaakt."
#: apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx
#: apps/remix/app/components/general/envelope-editor/envelope-editor-fields-page.tsx
msgid "Overlapping fields detected"
msgstr "Overlappende velden gedetecteerd"
#: apps/remix/app/components/forms/document-preferences-form.tsx
#: apps/remix/app/components/forms/email-preferences-form.tsx
msgid "Override organisation settings"
@@ -8917,6 +8924,11 @@ msgstr "Redirect-URL"
msgid "Redirecting"
msgstr "Doorsturen"
#. placeholder {0}: oidcProviderLabel || 'OIDC'
#: apps/remix/app/routes/_unauthenticated+/signin.tsx
msgid "Redirecting to {0}..."
msgstr "Doorsturen naar {0}..."
#: apps/remix/app/components/forms/signup.tsx
#: apps/remix/app/components/general/claim-account.tsx
msgid "Registration Successful"
@@ -10219,6 +10231,11 @@ msgstr "Siteinstellingen"
msgid "Skip"
msgstr "Overslaan"
#: apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx
#: apps/remix/app/components/general/envelope-editor/envelope-editor-fields-page.tsx
msgid "Some fields are placed on top of each other. This may complicate the signing process or cause fields to not work as expected."
msgstr "Sommige velden zijn boven op elkaar geplaatst. Dit kan het ondertekenproces bemoeilijken of ervoor zorgen dat velden niet naar verwachting werken."
#: packages/ui/primitives/document-flow/missing-signature-field-dialog.tsx
msgid "Some signers have not been assigned a signature field. Please assign at least 1 signature field to each signer before proceeding."
msgstr "Sommige ondertekenaars hebben geen handtekeningveld toegewezen gekregen. Wijs ten minste 1 handtekeningveld toe aan elke ondertekenaar voordat je doorgaat."
@@ -12342,6 +12359,7 @@ msgstr "Banner bijwerken"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.email-domains._index.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.branding.tsx
msgid "Update Billing"
msgstr "Facturering bijwerken"
@@ -12942,7 +12960,7 @@ msgstr "Wachten"
msgid "Waiting for others"
msgstr "Wachten op anderen"
#: packages/lib/server-only/document/send-pending-email.ts
#: packages/lib/jobs/definitions/emails/send-document-pending-email.handler.ts
msgid "Waiting for others to complete signing."
msgstr "Wachten tot anderen het ondertekenen hebben voltooid."
@@ -13916,8 +13934,7 @@ msgstr "Je bent uitgenodigd om {0} op Documenso te joinen"
msgid "You have been invited to join the following organisation"
msgstr "Je bent uitgenodigd om lid te worden van de volgende organisatie"
#: packages/lib/server-only/recipient/delete-envelope-recipient.ts
#: packages/lib/server-only/recipient/set-document-recipients.ts
#: packages/lib/jobs/definitions/emails/send-recipient-removed-email.handler.ts
msgid "You have been removed from a document"
msgstr "Je bent verwijderd uit een document"
File diff suppressed because it is too large Load Diff
+5
View File
@@ -8908,6 +8908,11 @@ msgstr "URL de Redirecionamento"
msgid "Redirecting"
msgstr "Redirecionando"
#. placeholder {0}: oidcProviderLabel || 'OIDC'
#: apps/remix/app/routes/_unauthenticated+/signin.tsx
msgid "Redirecting to {0}..."
msgstr ""
#: apps/remix/app/components/forms/signup.tsx
#: apps/remix/app/components/general/claim-account.tsx
msgid "Registration Successful"
+22 -5
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: zh\n"
"Project-Id-Version: documenso-app\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-06-22 13:53\n"
"PO-Revision-Date: 2026-06-30 07:18\n"
"Last-Translator: \n"
"Language-Team: Chinese Simplified\n"
"Plural-Forms: nplurals=1; plural=0;\n"
@@ -2441,6 +2441,7 @@ msgstr "品牌 Logo"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.branding.tsx
msgid "Branding Preferences"
msgstr "品牌偏好设置"
@@ -3572,6 +3573,7 @@ msgid "Currently all organisation members can access this team"
msgstr "目前所有组织成员都可以访问此团队"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.branding.tsx
msgid "Currently branding can only be configured for Teams and above plans."
msgstr "目前仅 Teams 及以上套餐可以配置品牌。"
@@ -4214,8 +4216,8 @@ msgstr "文档已被取消"
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
#: packages/lib/jobs/definitions/emails/send-document-deleted-emails.handler.ts
#: packages/lib/server-only/admin/admin-super-delete-document.ts
#: packages/lib/server-only/document/delete-document.ts
msgid "Document Cancelled"
msgstr "文档已取消"
@@ -7942,6 +7944,11 @@ msgstr "原始"
msgid "Otherwise, the document will be created as a draft."
msgstr "否则将把文档创建为草稿。"
#: apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx
#: apps/remix/app/components/general/envelope-editor/envelope-editor-fields-page.tsx
msgid "Overlapping fields detected"
msgstr "检测到字段重叠"
#: apps/remix/app/components/forms/document-preferences-form.tsx
#: apps/remix/app/components/forms/email-preferences-form.tsx
msgid "Override organisation settings"
@@ -8917,6 +8924,11 @@ msgstr "重定向 URL"
msgid "Redirecting"
msgstr "正在重定向"
#. placeholder {0}: oidcProviderLabel || 'OIDC'
#: apps/remix/app/routes/_unauthenticated+/signin.tsx
msgid "Redirecting to {0}..."
msgstr "正在重定向到 {0}…"
#: apps/remix/app/components/forms/signup.tsx
#: apps/remix/app/components/general/claim-account.tsx
msgid "Registration Successful"
@@ -10219,6 +10231,11 @@ msgstr "站点设置"
msgid "Skip"
msgstr "跳过"
#: apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx
#: apps/remix/app/components/general/envelope-editor/envelope-editor-fields-page.tsx
msgid "Some fields are placed on top of each other. This may complicate the signing process or cause fields to not work as expected."
msgstr "有些字段彼此重叠放置。这可能会使签署流程变得复杂,或导致字段无法按预期工作。"
#: packages/ui/primitives/document-flow/missing-signature-field-dialog.tsx
msgid "Some signers have not been assigned a signature field. Please assign at least 1 signature field to each signer before proceeding."
msgstr "部分签署人尚未被分配签名字段。请在继续前为每位签署人至少分配 1 个签名字段。"
@@ -12342,6 +12359,7 @@ msgstr "更新横幅"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.email-domains._index.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.branding.tsx
msgid "Update Billing"
msgstr "更新计费"
@@ -12942,7 +12960,7 @@ msgstr "等待中"
msgid "Waiting for others"
msgstr "等待其他人"
#: packages/lib/server-only/document/send-pending-email.ts
#: packages/lib/jobs/definitions/emails/send-document-pending-email.handler.ts
msgid "Waiting for others to complete signing."
msgstr "正在等待其他人完成签署。"
@@ -13916,8 +13934,7 @@ msgstr "您已被邀请加入 Documenso 上的 {0}"
msgid "You have been invited to join the following organisation"
msgstr "您已被邀请加入以下组织"
#: packages/lib/server-only/recipient/delete-envelope-recipient.ts
#: packages/lib/server-only/recipient/set-document-recipients.ts
#: packages/lib/jobs/definitions/emails/send-recipient-removed-email.handler.ts
msgid "You have been removed from a document"
msgstr "您已被从某个文档中移除"
@@ -53,6 +53,15 @@ export const deleteTeamGroupRoute = authenticatedProcedure
});
}
// You cannot delete internal team groups. These are the system-managed
// admin/manager/member groups that back the team's role-based access, and
// deleting them would silently strip team members of their access.
if (group.organisationGroup.type === OrganisationGroupType.INTERNAL_TEAM) {
throw new AppError(AppErrorCode.UNAUTHORIZED, {
message: 'You are not allowed to delete internal team groups',
});
}
// You cannot delete internal organisation groups.
// The only exception is deleting the "member" organisation group which is used to allow
// all organisation members to access a team.
@@ -45,9 +45,12 @@ export const updateTeamGroupRoute = authenticatedProcedure
});
}
if (teamGroup.organisationGroup.type === OrganisationGroupType.INTERNAL_ORGANISATION) {
if (
teamGroup.organisationGroup.type === OrganisationGroupType.INTERNAL_ORGANISATION ||
teamGroup.organisationGroup.type === OrganisationGroupType.INTERNAL_TEAM
) {
throw new AppError(AppErrorCode.UNAUTHORIZED, {
message: 'You are not allowed to update internal organisation groups',
message: 'You are not allowed to update internal groups',
});
}
+7
View File
@@ -93,6 +93,13 @@ declare namespace NodeJS {
NEXT_PUBLIC_DISABLE_OIDC_SIGNUP?: string;
NEXT_PRIVATE_ALLOWED_SIGNUP_DOMAINS?: string;
NEXT_PUBLIC_DISABLE_SIGNIN?: string;
NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNIN?: string;
NEXT_PUBLIC_DISABLE_GOOGLE_SIGNIN?: string;
NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNIN?: string;
NEXT_PUBLIC_DISABLE_OIDC_SIGNIN?: string;
NEXT_PUBLIC_DISABLE_OIDC_AUTO_REDIRECT?: string;
NEXT_PRIVATE_BROWSERLESS_URL?: string;
NEXT_PRIVATE_JOBS_PROVIDER?: 'inngest' | 'local' | 'bullmq';
+12
View File
@@ -163,6 +163,18 @@ services:
sync: false
- key: NEXT_PUBLIC_DISABLE_OIDC_SIGNUP
sync: false
- key: NEXT_PUBLIC_DISABLE_SIGNIN
sync: false
- key: NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNIN
sync: false
- key: NEXT_PUBLIC_DISABLE_GOOGLE_SIGNIN
sync: false
- key: NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNIN
sync: false
- key: NEXT_PUBLIC_DISABLE_OIDC_SIGNIN
sync: false
- key: NEXT_PUBLIC_DISABLE_OIDC_AUTO_REDIRECT
sync: false
- key: NEXT_PUBLIC_USE_INTERNAL_URL_BROWSERLESS
sync: false
+6
View File
@@ -53,6 +53,12 @@
"NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP",
"NEXT_PUBLIC_DISABLE_OIDC_SIGNUP",
"NEXT_PRIVATE_ALLOWED_SIGNUP_DOMAINS",
"NEXT_PUBLIC_DISABLE_SIGNIN",
"NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNIN",
"NEXT_PUBLIC_DISABLE_GOOGLE_SIGNIN",
"NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNIN",
"NEXT_PUBLIC_DISABLE_OIDC_SIGNIN",
"NEXT_PUBLIC_DISABLE_OIDC_AUTO_REDIRECT",
"NEXT_PRIVATE_PLAIN_API_KEY",
"NEXT_PUBLIC_DOCUMENT_SIZE_UPLOAD_LIMIT",
"NEXT_PRIVATE_DOCUMENSO_LICENSE_KEY",