mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2026-03-30 12:11:53 -04:00
Fixes #511
This commit is contained in:
@@ -29,6 +29,8 @@ EMAIL_BACKEND='console'
|
||||
|
||||
# ACCOUNT_EMAIL_VERIFICATION='none' # 'none', 'optional', 'mandatory' # You can change this as needed for your environment
|
||||
|
||||
# FORCE_SOCIALACCOUNT_LOGIN=False # When true, only social login is allowed (no password login) and the login page will show only social providers or redirect directly to the first provider if only one is configured.
|
||||
|
||||
|
||||
# ------------------- #
|
||||
# For Developers to start a Demo Database
|
||||
|
||||
@@ -259,6 +259,8 @@ SOCIALACCOUNT_EMAIL_AUTHENTICATION = True
|
||||
SOCIALACCOUNT_EMAIL_AUTHENTICATION_AUTO_CONNECT = True # Auto-link by email
|
||||
SOCIALACCOUNT_AUTO_SIGNUP = True # Allow auto-signup post adapter checks
|
||||
|
||||
FORCE_SOCIALACCOUNT_LOGIN = getenv('FORCE_SOCIALACCOUNT_LOGIN', 'false').lower() == 'true' # When true, only social login is allowed (no password login) and the login page will show only social providers or redirect directly to the first provider if only one is configured.
|
||||
|
||||
if getenv('EMAIL_BACKEND', 'console') == 'console':
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||
else:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.backends import ModelBackend
|
||||
from allauth.socialaccount.models import SocialAccount
|
||||
from allauth.account.auth_backends import AuthenticationBackend as AllauthBackend
|
||||
@@ -7,6 +8,10 @@ User = get_user_model()
|
||||
|
||||
class NoPasswordAuthBackend(ModelBackend):
|
||||
def authenticate(self, request, username=None, password=None, **kwargs):
|
||||
# Block all password-based logins when social-only mode is enforced
|
||||
if getattr(settings, "FORCE_SOCIALACCOUNT_LOGIN", False) and password:
|
||||
return None
|
||||
|
||||
# Handle allauth-specific authentication (like email login)
|
||||
allauth_backend = AllauthBackend()
|
||||
allauth_user = allauth_backend.authenticate(request, username=username, password=password, **kwargs)
|
||||
|
||||
@@ -171,7 +171,8 @@ class EnabledSocialProvidersView(APIView):
|
||||
providers.append({
|
||||
'provider': provider.provider,
|
||||
'url': f"{getenv('PUBLIC_URL')}/accounts/{new_provider}/login/",
|
||||
'name': provider.name
|
||||
'name': provider.name,
|
||||
'usage_required': settings.FORCE_SOCIALACCOUNT_LOGIN
|
||||
})
|
||||
return Response(providers, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
In addition to the primary configuration variables listed above, there are several optional environment variables that can be set to further customize your AdventureLog instance. These variables are not required for a basic setup but can enhance functionality and security.
|
||||
|
||||
| Name | Required | Description | Default Value |
|
||||
| ---------------------------- | -------- | ------------------------------------------------------------------------------------------ | ------------- |
|
||||
| `ACCOUNT_EMAIL_VERIFICATION` | No | Enable email verification for new accounts. Options are `none`, `optional`, or `mandatory` | `none` |
|
||||
| Name | Required | Description | Default Value |
|
||||
| ---------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------- |
|
||||
| `ACCOUNT_EMAIL_VERIFICATION` | No | Enable email verification for new accounts. Options are `none`, `optional`, or `mandatory` | `none` |
|
||||
| `FORCE_SOCIALACCOUNT_LOGIN` | No | When set to `True`, only social login is allowed (no password login). The login page will show only social providers or redirect directly to the first provider if only one is configured. | `False` |
|
||||
|
||||
@@ -78,7 +78,7 @@ export const actions: Actions = {
|
||||
});
|
||||
|
||||
if (res.status === 401) {
|
||||
return redirect(302, '/login');
|
||||
return redirect(302, '/');
|
||||
} else {
|
||||
return redirect(302, '/');
|
||||
}
|
||||
|
||||
@@ -20,6 +20,29 @@ export const load: PageServerLoad = async (event) => {
|
||||
}
|
||||
let socialProviders = await socialProviderFetch.json();
|
||||
|
||||
// Determine if social auth usage is required. The API returns `usage_required`
|
||||
// either for all providers or none — so check the first provider if present.
|
||||
const usageRequired =
|
||||
socialProviders && socialProviders.length > 0 ? !!socialProviders[0].usage_required : false;
|
||||
|
||||
// If usage is required and there's exactly one social provider, redirect straight
|
||||
// to that provider's URL. If multiple providers and usage is required, instruct
|
||||
// the client to render social-only UI.
|
||||
if (usageRequired) {
|
||||
if (socialProviders.length === 1) {
|
||||
return redirect(302, socialProviders[0].url);
|
||||
} else if (socialProviders.length > 1) {
|
||||
return {
|
||||
props: {
|
||||
quote,
|
||||
background,
|
||||
socialProviders,
|
||||
socialOnly: true
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
quote,
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
|
||||
let socialProviders = data.props?.socialProviders ?? [];
|
||||
|
||||
let socialOnly: boolean = data.props?.socialOnly ?? false;
|
||||
|
||||
import GitHub from '~icons/mdi/github';
|
||||
import OpenIdConnect from '~icons/mdi/openid';
|
||||
|
||||
@@ -78,83 +80,12 @@
|
||||
<h2 class="text-4xl font-bold text-base-content mb-2">{$t('auth.login')}</h2>
|
||||
</div>
|
||||
|
||||
<!-- Form -->
|
||||
<!-- Form / Social Only -->
|
||||
<div class="max-w-sm mx-auto w-full">
|
||||
<form method="post" use:enhance={handleEnhanceSubmit} class="space-y-4">
|
||||
<!-- Username -->
|
||||
<div class="form-control">
|
||||
<label class="label" for="username">
|
||||
<span class="label-text font-medium">{$t('auth.username')}</span>
|
||||
</label>
|
||||
<input
|
||||
name="username"
|
||||
id="username"
|
||||
type="text"
|
||||
class="input input-bordered w-full focus:input-primary"
|
||||
placeholder={$t('auth.enter_username')}
|
||||
autocomplete="username"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Password -->
|
||||
<div class="form-control">
|
||||
<label class="label" for="password">
|
||||
<span class="label-text font-medium">{$t('auth.password')}</span>
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
id="password"
|
||||
class="input input-bordered w-full focus:input-primary"
|
||||
placeholder={$t('auth.enter_password')}
|
||||
autocomplete="current-password"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- TOTP -->
|
||||
{#if $page.form?.mfa_required}
|
||||
<div class="form-control">
|
||||
<label class="label" for="totp">
|
||||
<span class="label-text font-medium">{$t('auth.totp')}</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="totp"
|
||||
id="totp"
|
||||
inputmode="numeric"
|
||||
pattern="[0-9]*"
|
||||
autocomplete="one-time-code"
|
||||
class="input input-bordered w-full focus:input-primary"
|
||||
placeholder="000000"
|
||||
maxlength="6"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Submit Button -->
|
||||
<div class="form-control mt-6">
|
||||
<button type="submit" class="btn btn-primary w-full" disabled={isSubmitting}>
|
||||
{#if isSubmitting}
|
||||
<span class="loading loading-spinner"></span>
|
||||
<span class="ml-2">{$t('auth.logging_in')}...</span>
|
||||
{:else}
|
||||
{$t('auth.login')}
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Error Message -->
|
||||
{#if ($page.form?.message && $page.form?.message.length > 1) || $page.form?.type === 'error'}
|
||||
<div class="alert alert-error mt-4">
|
||||
<span>{$t($page.form.message) || $t('auth.login_error')}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Social Login -->
|
||||
{#if socialOnly}
|
||||
{#if socialProviders.length > 0}
|
||||
<div class="divider text-sm">{$t('auth.or_3rd_party')}</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<div class="divider text-sm">{$t('auth.or_3rd_party')}</div>
|
||||
{#each socialProviders as provider}
|
||||
<a
|
||||
href={provider.url}
|
||||
@@ -171,16 +102,113 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Footer Links -->
|
||||
<div class="flex justify-between text-sm mt-6 pt-4 border-t border-base-300">
|
||||
<a href="/signup" class="link link-primary">
|
||||
{$t('auth.signup')}
|
||||
</a>
|
||||
<a href="/user/reset-password" class="link link-primary">
|
||||
{$t('auth.forgot_password')}
|
||||
</a>
|
||||
<a href="/signup" class="link link-primary">{$t('auth.signup')}</a>
|
||||
<a href="/user/reset-password" class="link link-primary"
|
||||
>{$t('auth.forgot_password')}</a
|
||||
>
|
||||
</div>
|
||||
</form>
|
||||
{:else}
|
||||
<form method="post" use:enhance={handleEnhanceSubmit} class="space-y-4">
|
||||
<!-- Username -->
|
||||
<div class="form-control">
|
||||
<label class="label" for="username">
|
||||
<span class="label-text font-medium">{$t('auth.username')}</span>
|
||||
</label>
|
||||
<input
|
||||
name="username"
|
||||
id="username"
|
||||
type="text"
|
||||
class="input input-bordered w-full focus:input-primary"
|
||||
placeholder={$t('auth.enter_username')}
|
||||
autocomplete="username"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Password -->
|
||||
<div class="form-control">
|
||||
<label class="label" for="password">
|
||||
<span class="label-text font-medium">{$t('auth.password')}</span>
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
id="password"
|
||||
class="input input-bordered w-full focus:input-primary"
|
||||
placeholder={$t('auth.enter_password')}
|
||||
autocomplete="current-password"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- TOTP -->
|
||||
{#if $page.form?.mfa_required}
|
||||
<div class="form-control">
|
||||
<label class="label" for="totp">
|
||||
<span class="label-text font-medium">{$t('auth.totp')}</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="totp"
|
||||
id="totp"
|
||||
inputmode="numeric"
|
||||
pattern="[0-9]*"
|
||||
autocomplete="one-time-code"
|
||||
class="input input-bordered w-full focus:input-primary"
|
||||
placeholder="000000"
|
||||
maxlength="6"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Submit Button -->
|
||||
<div class="form-control mt-6">
|
||||
<button type="submit" class="btn btn-primary w-full" disabled={isSubmitting}>
|
||||
{#if isSubmitting}
|
||||
<span class="loading loading-spinner"></span>
|
||||
<span class="ml-2">{$t('auth.logging_in')}...</span>
|
||||
{:else}
|
||||
{$t('auth.login')}
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Error Message -->
|
||||
{#if ($page.form?.message && $page.form?.message.length > 1) || $page.form?.type === 'error'}
|
||||
<div class="alert alert-error mt-4">
|
||||
<span>{$t($page.form.message) || $t('auth.login_error')}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Social Login -->
|
||||
{#if socialProviders.length > 0}
|
||||
<div class="divider text-sm">{$t('auth.or_3rd_party')}</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
{#each socialProviders as provider}
|
||||
<a
|
||||
href={provider.url}
|
||||
class="btn btn-outline w-full flex items-center gap-2"
|
||||
>
|
||||
{#if provider.provider === 'github'}
|
||||
<GitHub class="w-4 h-4" />
|
||||
{:else if provider.provider === 'openid_connect'}
|
||||
<OpenIdConnect class="w-4 h-4" />
|
||||
{/if}
|
||||
Continue with {provider.name}
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Footer Links -->
|
||||
<div class="flex justify-between text-sm mt-6 pt-4 border-t border-base-300">
|
||||
<a href="/signup" class="link link-primary">{$t('auth.signup')}</a>
|
||||
<a href="/user/reset-password" class="link link-primary"
|
||||
>{$t('auth.forgot_password')}</a
|
||||
>
|
||||
</div>
|
||||
</form>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user