From f28d8245bf385d7be3b3b8ee449c3fdc88fa375c Mon Sep 17 00:00:00 2001 From: Corentin Thomasset Date: Wed, 2 Jul 2025 13:36:19 +0200 Subject: [PATCH] feat(auth): added configuration to disable auth by email (#394) --- .changeset/cruel-rings-juggle.md | 6 ++++ apps/papra-client/src/locales/de.yml | 3 ++ apps/papra-client/src/locales/en.yml | 3 ++ apps/papra-client/src/locales/fr.yml | 3 ++ apps/papra-client/src/locales/pt-BR.yml | 3 ++ .../auth/components/no-auth-provider.tsx | 17 +++++++++++ .../src/modules/auth/pages/login.page.tsx | 30 ++++++++++++------- .../src/modules/auth/pages/register.page.tsx | 28 +++++++++++------ .../pages/request-password-reset.page.tsx | 2 +- .../auth/pages/reset-password.page.tsx | 2 +- .../papra-client/src/modules/config/config.ts | 2 +- .../src/modules/i18n/locales.types.ts | 2 ++ .../src/modules/app/auth/auth.config.ts | 8 +++++ .../src/modules/app/auth/auth.services.ts | 2 +- .../src/modules/config/config.models.test.ts | 14 +++++---- .../src/modules/config/config.models.ts | 2 +- 16 files changed, 97 insertions(+), 30 deletions(-) create mode 100644 .changeset/cruel-rings-juggle.md create mode 100644 apps/papra-client/src/modules/auth/components/no-auth-provider.tsx diff --git a/.changeset/cruel-rings-juggle.md b/.changeset/cruel-rings-juggle.md new file mode 100644 index 0000000..2a94d10 --- /dev/null +++ b/.changeset/cruel-rings-juggle.md @@ -0,0 +1,6 @@ +--- +"@papra/app-client": patch +"@papra/app-server": patch +--- + +Added the possibility to disable login via email, to support sso-only auth diff --git a/apps/papra-client/src/locales/de.yml b/apps/papra-client/src/locales/de.yml index 398d5b3..501d2e3 100644 --- a/apps/papra-client/src/locales/de.yml +++ b/apps/papra-client/src/locales/de.yml @@ -71,6 +71,9 @@ auth.legal-links.description: Indem Sie fortfahren, bestätigen Sie, dass Sie di auth.legal-links.terms: Nutzungsbedingungen auth.legal-links.privacy: Datenschutzrichtlinie +auth.no-auth-provider.title: Kein Authentifizierungsanbieter +auth.no-auth-provider.description: Es gibt keine Authentifizierungsanbieter auf dieser Papra-Instanz. Bitte kontaktieren Sie den Administrator dieser Instanz, um sie zu aktivieren. + # User settings user.settings.title: Benutzereinstellungen diff --git a/apps/papra-client/src/locales/en.yml b/apps/papra-client/src/locales/en.yml index 0752021..9fce185 100644 --- a/apps/papra-client/src/locales/en.yml +++ b/apps/papra-client/src/locales/en.yml @@ -71,6 +71,9 @@ auth.legal-links.description: By continuing, you acknowledge that you understand auth.legal-links.terms: Terms of Service auth.legal-links.privacy: Privacy Policy +auth.no-auth-provider.title: No authentication provider +auth.no-auth-provider.description: There are no authentication providers enabled on this instance of Papra. Please contact the administrator of this instance to enable them. + # User settings user.settings.title: User settings diff --git a/apps/papra-client/src/locales/fr.yml b/apps/papra-client/src/locales/fr.yml index 3fb8b2a..64ebf46 100644 --- a/apps/papra-client/src/locales/fr.yml +++ b/apps/papra-client/src/locales/fr.yml @@ -71,6 +71,9 @@ auth.legal-links.description: En continuant, vous reconnaissez que vous comprene auth.legal-links.terms: Conditions d'utilisation auth.legal-links.privacy: Politique de confidentialité +auth.no-auth-provider.title: Aucun fournisseur d'authentification +auth.no-auth-provider.description: Il n'y a pas de fournisseurs d'authentification activés sur cette instance de Papra. Veuillez contacter l'administrateur de cette instance pour les activer. + # User settings user.settings.title: Paramètres de l'utilisateur diff --git a/apps/papra-client/src/locales/pt-BR.yml b/apps/papra-client/src/locales/pt-BR.yml index ca8bd12..69adeab 100644 --- a/apps/papra-client/src/locales/pt-BR.yml +++ b/apps/papra-client/src/locales/pt-BR.yml @@ -71,6 +71,9 @@ auth.legal-links.description: Ao continuar, você reconhece que leu e concorda c auth.legal-links.terms: Termos de Serviço auth.legal-links.privacy: Política de Privacidade +auth.no-auth-provider.title: Nenhum provedor de autenticação +auth.no-auth-provider.description: Não há provedores de autenticação habilitados nesta instância do Papra. Por favor, entre em contato com o administrador desta instância para habilitá-los. + # User settings user.settings.title: Configurações do usuário diff --git a/apps/papra-client/src/modules/auth/components/no-auth-provider.tsx b/apps/papra-client/src/modules/auth/components/no-auth-provider.tsx new file mode 100644 index 0000000..9c16716 --- /dev/null +++ b/apps/papra-client/src/modules/auth/components/no-auth-provider.tsx @@ -0,0 +1,17 @@ +import type { Component } from 'solid-js'; +import { useI18n } from '@/modules/i18n/i18n.provider'; + +export const NoAuthProviderWarning: Component = () => { + const { t } = useI18n(); + + return ( +
+
+

{t('auth.no-auth-provider.title')}

+

+ {t('auth.no-auth-provider.description')} +

+
+
+ ); +}; diff --git a/apps/papra-client/src/modules/auth/pages/login.page.tsx b/apps/papra-client/src/modules/auth/pages/login.page.tsx index 79596ff..952f828 100644 --- a/apps/papra-client/src/modules/auth/pages/login.page.tsx +++ b/apps/papra-client/src/modules/auth/pages/login.page.tsx @@ -14,6 +14,7 @@ import { AuthLayout } from '../../ui/layouts/auth-layout.component'; import { getEnabledSsoProviderConfigs, isEmailVerificationRequiredError } from '../auth.models'; import { authWithProvider, signIn } from '../auth.services'; import { AuthLegalLinks } from '../components/legal-links.component'; +import { NoAuthProviderWarning } from '../components/no-auth-provider'; import { SsoProviderButton } from '../components/sso-provider-button.component'; export const EmailLoginForm: Component = () => { @@ -105,7 +106,7 @@ export const LoginPage: Component = () => { const { config } = useConfig(); const { t } = useI18n(); - const [getShowEmailLogin, setShowEmailLogin] = createSignal(false); + const [getShowEmailLoginForm, setShowEmailLoginForm] = createSignal(false); const loginWithProvider = async (provider: SsoProviderConfig) => { await authWithProvider({ provider, config }); @@ -113,6 +114,10 @@ export const LoginPage: Component = () => { const getHasSsoProviders = () => getEnabledSsoProviderConfigs({ config }).length > 0; + if (!config.auth.providers.email.isEnabled && !getHasSsoProviders()) { + return ; + } + return (
@@ -120,17 +125,22 @@ export const LoginPage: Component = () => {

{t('auth.login.title')}

{t('auth.login.description')}

- {getShowEmailLogin() || !getHasSsoProviders() - ? - : ( - - )} + + {getShowEmailLoginForm() || !getHasSsoProviders() + ? + : ( + + )} + + + + + -
diff --git a/apps/papra-client/src/modules/auth/pages/register.page.tsx b/apps/papra-client/src/modules/auth/pages/register.page.tsx index 300e47b..974166b 100644 --- a/apps/papra-client/src/modules/auth/pages/register.page.tsx +++ b/apps/papra-client/src/modules/auth/pages/register.page.tsx @@ -13,6 +13,7 @@ import { AuthLayout } from '../../ui/layouts/auth-layout.component'; import { getEnabledSsoProviderConfigs } from '../auth.models'; import { authWithProvider, signUp } from '../auth.services'; import { AuthLegalLinks } from '../components/legal-links.component'; +import { NoAuthProviderWarning } from '../components/no-auth-provider'; import { SsoProviderButton } from '../components/sso-provider-button.component'; export const EmailRegisterForm: Component = () => { @@ -139,6 +140,10 @@ export const RegisterPage: Component = () => { const getHasSsoProviders = () => getEnabledSsoProviderConfigs({ config }).length > 0; + if (!config.auth.providers.email.isEnabled && !getHasSsoProviders()) { + return ; + } + return (
@@ -150,17 +155,22 @@ export const RegisterPage: Component = () => { {t('auth.register.description')}

- {getShowEmailRegister() || !getHasSsoProviders() - ? - : ( - - )} + + {getShowEmailRegister() || !getHasSsoProviders() + ? + : ( + + )} + + + + + -
diff --git a/apps/papra-client/src/modules/auth/pages/request-password-reset.page.tsx b/apps/papra-client/src/modules/auth/pages/request-password-reset.page.tsx index 911d86c..c807bc6 100644 --- a/apps/papra-client/src/modules/auth/pages/request-password-reset.page.tsx +++ b/apps/papra-client/src/modules/auth/pages/request-password-reset.page.tsx @@ -58,7 +58,7 @@ export const RequestPasswordResetPage: Component = () => { const navigate = useNavigate(); onMount(() => { - if (!config.auth.isPasswordResetEnabled) { + if (!config.auth.isPasswordResetEnabled || !config.auth.providers.email.isEnabled) { navigate('/login'); } }); diff --git a/apps/papra-client/src/modules/auth/pages/reset-password.page.tsx b/apps/papra-client/src/modules/auth/pages/reset-password.page.tsx index e88ee32..7f569fe 100644 --- a/apps/papra-client/src/modules/auth/pages/reset-password.page.tsx +++ b/apps/papra-client/src/modules/auth/pages/reset-password.page.tsx @@ -62,7 +62,7 @@ export const ResetPasswordPage: Component = () => { const navigate = useNavigate(); onMount(() => { - if (!config.auth.isPasswordResetEnabled) { + if (!config.auth.isPasswordResetEnabled || !config.auth.providers.email.isEnabled) { navigate('/login'); } }); diff --git a/apps/papra-client/src/modules/config/config.ts b/apps/papra-client/src/modules/config/config.ts index 0cb6aeb..bc0d3b3 100644 --- a/apps/papra-client/src/modules/config/config.ts +++ b/apps/papra-client/src/modules/config/config.ts @@ -16,6 +16,7 @@ export const buildTimeConfig = { isEmailVerificationRequired: asBoolean(import.meta.env.VITE_AUTH_IS_EMAIL_VERIFICATION_REQUIRED, true), showLegalLinksOnAuthPage: asBoolean(import.meta.env.VITE_AUTH_SHOW_LEGAL_LINKS_ON_AUTH_PAGE, false), providers: { + email: { isEnabled: asBoolean(import.meta.env.VITE_AUTH_PROVIDERS_EMAIL_IS_ENABLED, true) }, github: { isEnabled: asBoolean(import.meta.env.VITE_AUTH_PROVIDERS_GITHUB_IS_ENABLED, false) }, google: { isEnabled: asBoolean(import.meta.env.VITE_AUTH_PROVIDERS_GOOGLE_IS_ENABLED, false) }, customs: [] as { @@ -35,7 +36,6 @@ export const buildTimeConfig = { }, intakeEmails: { isEnabled: asBoolean(import.meta.env.VITE_INTAKE_EMAILS_IS_ENABLED, false), - emailGenerationDomain: asString(import.meta.env.VITE_INTAKE_EMAILS_EMAIL_GENERATION_DOMAIN), }, isSubscriptionsEnabled: asBoolean(import.meta.env.VITE_IS_SUBSCRIPTIONS_ENABLED, false), } as const; diff --git a/apps/papra-client/src/modules/i18n/locales.types.ts b/apps/papra-client/src/modules/i18n/locales.types.ts index 0a8122b..7f26df3 100644 --- a/apps/papra-client/src/modules/i18n/locales.types.ts +++ b/apps/papra-client/src/modules/i18n/locales.types.ts @@ -68,6 +68,8 @@ export type LocaleKeys = | 'auth.legal-links.description' | 'auth.legal-links.terms' | 'auth.legal-links.privacy' + | 'auth.no-auth-provider.title' + | 'auth.no-auth-provider.description' | 'user.settings.title' | 'user.settings.description' | 'user.settings.email.title' diff --git a/apps/papra-server/src/modules/app/auth/auth.config.ts b/apps/papra-server/src/modules/app/auth/auth.config.ts index 9ed6f17..a07e079 100644 --- a/apps/papra-server/src/modules/app/auth/auth.config.ts +++ b/apps/papra-server/src/modules/app/auth/auth.config.ts @@ -56,6 +56,14 @@ export const authConfig = { env: 'AUTH_SHOW_LEGAL_LINKS', }, providers: { + email: { + isEnabled: { + doc: 'Whether email/password authentication is enabled', + schema: booleanishSchema, + default: true, + env: 'AUTH_PROVIDERS_EMAIL_IS_ENABLED', + }, + }, github: { isEnabled: { doc: 'Whether Github OAuth is enabled', diff --git a/apps/papra-server/src/modules/app/auth/auth.services.ts b/apps/papra-server/src/modules/app/auth/auth.services.ts index 86d89e1..d5141c1 100644 --- a/apps/papra-server/src/modules/app/auth/auth.services.ts +++ b/apps/papra-server/src/modules/app/auth/auth.services.ts @@ -42,7 +42,7 @@ export function getAuth({ }, }, emailAndPassword: { - enabled: true, + enabled: config.auth.providers.email.isEnabled, requireEmailVerification: config.auth.isEmailVerificationRequired, sendResetPassword: config.auth.isPasswordResetEnabled ? authEmailsServices.sendPasswordResetEmail diff --git a/apps/papra-server/src/modules/config/config.models.test.ts b/apps/papra-server/src/modules/config/config.models.test.ts index b84535c..bae0e24 100644 --- a/apps/papra-server/src/modules/config/config.models.test.ts +++ b/apps/papra-server/src/modules/config/config.models.test.ts @@ -1,6 +1,8 @@ +import type { DeepPartial } from '@corentinth/chisels'; import type { Config } from './config.types'; import { describe, expect, test } from 'vitest'; import { getPublicConfig } from './config.models'; +import { overrideConfig } from './config.test-utils'; describe('config models', () => { describe('getPublicConfig', () => { @@ -12,10 +14,10 @@ describe('config models', () => { - auth.providers.*.isEnabled Wether a oauth provider is enabled - documents.deletedExpirationDelayInDays The delay in days before a deleted document is permanently deleted - intakeEmails.isEnabled Whether intake emails are enabled - - intakeEmails.emailGenerationDomain The domain to use when generating email addresses for intake emails + - auth.providers.email.isEnabled Whether email/password authentication is enabled Any other config should not be exposed.`, () => { - const config = { + const config = overrideConfig({ foo: 'bar', auth: { bar: 'baz', @@ -38,9 +40,8 @@ describe('config models', () => { }, intakeEmails: { isEnabled: true, - emailGenerationDomain: 'papra.email', }, - } as unknown as Config; + } as DeepPartial); expect(getPublicConfig({ config })).to.eql({ publicConfig: { @@ -57,7 +58,9 @@ describe('config models', () => { isEnabled: false, }, customs: [], - + email: { + isEnabled: true, + }, }, }, documents: { @@ -65,7 +68,6 @@ describe('config models', () => { }, intakeEmails: { isEnabled: true, - emailGenerationDomain: 'papra.email', }, }, }); diff --git a/apps/papra-server/src/modules/config/config.models.ts b/apps/papra-server/src/modules/config/config.models.ts index b4ccc11..321e1f6 100644 --- a/apps/papra-server/src/modules/config/config.models.ts +++ b/apps/papra-server/src/modules/config/config.models.ts @@ -9,11 +9,11 @@ export function getPublicConfig({ config }: { config: Config }) { 'auth.isPasswordResetEnabled', 'auth.isRegistrationEnabled', 'auth.showLegalLinksOnAuthPage', + 'auth.providers.email.isEnabled', 'auth.providers.github.isEnabled', 'auth.providers.google.isEnabled', 'documents.deletedDocumentsRetentionDays', 'intakeEmails.isEnabled', - 'intakeEmails.emailGenerationDomain', ]), { auth: {