mirror of
https://github.com/papra-hq/papra.git
synced 2025-12-21 12:09:39 -06:00
feat(auth): added configuration to disable auth by email (#394)
This commit is contained in:
committed by
GitHub
parent
aad36f3252
commit
f28d8245bf
6
.changeset/cruel-rings-juggle.md
Normal file
6
.changeset/cruel-rings-juggle.md
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 (
|
||||
<div class="flex items-center justify-center h-full p-6 sm:pb-32">
|
||||
<div class="max-w-sm w-full">
|
||||
<h1 class="text-lg font-bold">{t('auth.no-auth-provider.title')}</h1>
|
||||
<p class="text-muted-foreground mt-1 mb-4">
|
||||
{t('auth.no-auth-provider.description')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -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 <AuthLayout><NoAuthProviderWarning /></AuthLayout>;
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthLayout>
|
||||
<div class="flex items-center justify-center h-full p-6 sm:pb-32">
|
||||
@@ -120,17 +125,22 @@ export const LoginPage: Component = () => {
|
||||
<h1 class="text-xl font-bold">{t('auth.login.title')}</h1>
|
||||
<p class="text-muted-foreground mt-1 mb-4">{t('auth.login.description')}</p>
|
||||
|
||||
{getShowEmailLogin() || !getHasSsoProviders()
|
||||
? <EmailLoginForm />
|
||||
: (
|
||||
<Button onClick={() => setShowEmailLogin(true)} class="w-full">
|
||||
<div class="i-tabler-mail mr-2 size-4.5" />
|
||||
{t('auth.login.login-with-provider', { provider: 'Email' })}
|
||||
</Button>
|
||||
)}
|
||||
<Show when={config.auth.providers.email.isEnabled}>
|
||||
{getShowEmailLoginForm() || !getHasSsoProviders()
|
||||
? <EmailLoginForm />
|
||||
: (
|
||||
<Button onClick={() => setShowEmailLoginForm(true)} class="w-full">
|
||||
<div class="i-tabler-mail mr-2 size-4.5" />
|
||||
{t('auth.login.login-with-provider', { provider: 'Email' })}
|
||||
</Button>
|
||||
)}
|
||||
</Show>
|
||||
|
||||
<Show when={config.auth.providers.email.isEnabled && getHasSsoProviders()}>
|
||||
<Separator class="my-4" />
|
||||
</Show>
|
||||
|
||||
<Show when={getHasSsoProviders()}>
|
||||
<Separator class="my-4" />
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<For each={getEnabledSsoProviderConfigs({ config })}>
|
||||
|
||||
@@ -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 <AuthLayout><NoAuthProviderWarning /></AuthLayout>;
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthLayout>
|
||||
<div class="flex items-center justify-center h-full p-6 sm:pb-32">
|
||||
@@ -150,17 +155,22 @@ export const RegisterPage: Component = () => {
|
||||
{t('auth.register.description')}
|
||||
</p>
|
||||
|
||||
{getShowEmailRegister() || !getHasSsoProviders()
|
||||
? <EmailRegisterForm />
|
||||
: (
|
||||
<Button onClick={() => setShowEmailRegister(true)} class="w-full">
|
||||
<div class="i-tabler-mail mr-2 size-4.5" />
|
||||
{t('auth.register.register-with-email')}
|
||||
</Button>
|
||||
)}
|
||||
<Show when={config.auth.providers.email.isEnabled}>
|
||||
{getShowEmailRegister() || !getHasSsoProviders()
|
||||
? <EmailRegisterForm />
|
||||
: (
|
||||
<Button onClick={() => setShowEmailRegister(true)} class="w-full">
|
||||
<div class="i-tabler-mail mr-2 size-4.5" />
|
||||
{t('auth.register.register-with-email')}
|
||||
</Button>
|
||||
)}
|
||||
</Show>
|
||||
|
||||
<Show when={config.auth.providers.email.isEnabled && getHasSsoProviders()}>
|
||||
<Separator class="my-4" />
|
||||
</Show>
|
||||
|
||||
<Show when={getHasSsoProviders()}>
|
||||
<Separator class="my-4" />
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<For each={getEnabledSsoProviderConfigs({ config })}>
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Config>);
|
||||
|
||||
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',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user