Skip to content

Commit f50437d

Browse files
committed
add Env variables to disable different types of login
1 parent c21c9e1 commit f50437d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+395
-99
lines changed

.env.example

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,13 @@ TRUSTED_PROXIES=null
155155
VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
156156
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
157157

158+
# Disable Basic Auth. This means that the only way to authenticate is via the API token or Oauth.
159+
# This should only be toggled AFTER having set up the admin account and bound the Oauth client.
160+
# DISABLE_BASIC_AUTH=false
161+
162+
# Disable WebAuthn. This means that the only way to authenticate is via the API token, Basic Auth or Oauth.
163+
# DISABLE_WEBAUTHN=false
164+
158165
# Oauth token data
159166
# XXX_REDIRECT_URI should be left as default unless you know exactly what you do.
160167

app/Actions/Diagnostics/Errors.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
use App\Actions\Diagnostics\Pipes\Checks\AdminUserExistsCheck;
1212
use App\Actions\Diagnostics\Pipes\Checks\AppUrlMatchCheck;
13+
use App\Actions\Diagnostics\Pipes\Checks\AuthDisabledCheck;
1314
use App\Actions\Diagnostics\Pipes\Checks\BasicPermissionCheck;
1415
use App\Actions\Diagnostics\Pipes\Checks\CachePasswordCheck;
1516
use App\Actions\Diagnostics\Pipes\Checks\CacheTemporaryUrlCheck;
@@ -44,6 +45,7 @@ class Errors
4445
*/
4546
private array $pipes = [
4647
AdminUserExistsCheck::class,
48+
AuthDisabledCheck::class,
4749
BasicPermissionCheck::class,
4850
ConfigSanityCheck::class,
4951
DBSupportCheck::class,
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
3+
/**
4+
* SPDX-License-Identifier: MIT
5+
* Copyright (c) 2017-2018 Tobias Reich
6+
* Copyright (c) 2018-2025 LycheeOrg.
7+
*/
8+
9+
namespace App\Actions\Diagnostics\Pipes\Checks;
10+
11+
use App\Contracts\DiagnosticPipe;
12+
use App\DTO\DiagnosticData;
13+
use App\Models\User;
14+
use App\Providers\AuthServiceProvider;
15+
use Illuminate\Support\Facades\Schema;
16+
17+
class AuthDisabledCheck implements DiagnosticPipe
18+
{
19+
public const INFO = 'You need to enable at least one authentication method to be able to use Lychee...';
20+
21+
/**
22+
* {@inheritDoc}
23+
*/
24+
public function handle(array &$data, \Closure $next): array
25+
{
26+
if (!Schema::hasTable('users') || !Schema::hasTable('oauth_credentials') || !Schema::hasTable('webauthn_credentials')) {
27+
// @codeCoverageIgnoreStart
28+
return $next($data);
29+
// @codeCoverageIgnoreEnd
30+
}
31+
32+
if (AuthServiceProvider::isBasicAuthEnabled()) {
33+
// If basic auth is enabled, we do not need to check for Oauth or WebAuthn
34+
// as they are optional and can be used in addition to basic auth.
35+
return $next($data);
36+
}
37+
// From now on, we assume that basic auth is disabled.
38+
39+
if (!AuthServiceProvider::isWebAuthnEnabled() && !AuthServiceProvider::isOauthEnabled()) {
40+
$data[] = DiagnosticData::error('All authentication methods are disabled. Really?', self::class, [self::INFO]);
41+
42+
return $next($data);
43+
}
44+
45+
$number_admin_with_oauth = AuthServiceProvider::isOauthEnabled() ? $this->oauthChecks($data) : 0;
46+
$number_admin_with_webauthn = AuthServiceProvider::isWebAuthnEnabled() ? $this->webauthnCheck($data) : 0;
47+
if (($number_admin_with_oauth === 0 && AuthServiceProvider::isOauthEnabled()) &&
48+
($number_admin_with_webauthn === 0 && AuthServiceProvider::isWebAuthnEnabled())
49+
) {
50+
$data[] = DiagnosticData::error('Basic auth is disabled and there are no admin user with Oauth or WebAuthn enabled.', self::class, [self::INFO]);
51+
}
52+
53+
return $next($data);
54+
}
55+
56+
/**
57+
* @param DiagnosticData[] &$data
58+
*
59+
* @return int
60+
*/
61+
private function oauthChecks(array &$data): int
62+
{
63+
$number_admin_with_oauth = User::query()->has('oauthCredentials')->where('may_administrate', '=', true)->count();
64+
if (!AuthServiceProvider::isWebAuthnEnabled() && $number_admin_with_oauth === 0) {
65+
$data[] = DiagnosticData::error('Basic auth and Webauthn are disabled and there are no admin user with Oauth enabled.', self::class, [self::INFO]);
66+
}
67+
68+
return $number_admin_with_oauth;
69+
}
70+
71+
/**
72+
* @param DiagnosticData[] &$data
73+
*
74+
* @return int
75+
*/
76+
private function webauthnCheck(array &$data): int
77+
{
78+
$number_admin_with_webauthn = User::query()->has('webAuthnCredentials')->where('may_administrate', '=', true)->count();
79+
if (!AuthServiceProvider::isOauthEnabled() && $number_admin_with_webauthn === 0) {
80+
$data[] = DiagnosticData::error('Basic auth is disabled and there are no admin user with WebAuthn enabled.', self::class, [self::INFO]);
81+
}
82+
83+
return $number_admin_with_webauthn;
84+
}
85+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
/**
4+
* SPDX-License-Identifier: MIT
5+
* Copyright (c) 2017-2018 Tobias Reich
6+
* Copyright (c) 2018-2025 LycheeOrg.
7+
*/
8+
9+
namespace App\Exceptions;
10+
11+
use Symfony\Component\HttpFoundation\Response;
12+
13+
/**
14+
* BasicAuthDisabledExecption.
15+
*
16+
* Returns status code 403 (Forbidden) to an HTTP client.
17+
*/
18+
class BasicAuthDisabledExecption extends BaseLycheeException
19+
{
20+
public function __construct()
21+
{
22+
parent::__construct(Response::HTTP_FORBIDDEN, 'Basic Auth is disabled by configuration', null);
23+
}
24+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
/**
4+
* SPDX-License-Identifier: MIT
5+
* Copyright (c) 2017-2018 Tobias Reich
6+
* Copyright (c) 2018-2025 LycheeOrg.
7+
*/
8+
9+
namespace App\Exceptions;
10+
11+
use Symfony\Component\HttpFoundation\Response;
12+
13+
/**
14+
* WebAuthnDisabledExecption.
15+
*
16+
* Returns status code 403 (Forbidden) to an HTTP client.
17+
*/
18+
class WebAuthnDisabledExecption extends BaseLycheeException
19+
{
20+
public function __construct()
21+
{
22+
parent::__construct(Response::HTTP_FORBIDDEN, 'WebAuthn is disabled by configuration', null);
23+
}
24+
}

app/Http/Controllers/OauthController.php

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
namespace App\Http\Controllers;
1010

11-
use App\Actions\Oauth\Oauth as OauthAction;
11+
use App\Actions\Oauth\Oauth;
1212
use App\Enum\CacheTag;
1313
use App\Enum\OauthProvidersType;
1414
use App\Events\TaggedRouteCacheUpdated;
@@ -19,6 +19,7 @@
1919
use App\Http\Resources\Oauth\OauthRegistrationData;
2020
use App\Models\OauthCredential;
2121
use App\Models\User;
22+
use App\Providers\AuthServiceProvider;
2223
use Illuminate\Http\RedirectResponse;
2324
use Illuminate\Routing\Controller;
2425
use Illuminate\Routing\Redirector;
@@ -32,7 +33,7 @@
3233
class OauthController extends Controller
3334
{
3435
public function __construct(
35-
private OauthAction $oauth,
36+
private Oauth $oauth,
3637
) {
3738
}
3839

@@ -99,7 +100,7 @@ public function register(string $provider)
99100
}
100101

101102
$provider_enum = $this->oauth->validateProviderOrDie($provider);
102-
Session::put($provider_enum->value, OauthAction::OAUTH_REGISTER);
103+
Session::put($provider_enum->value, Oauth::OAUTH_REGISTER);
103104

104105
TaggedRouteCacheUpdated::dispatch(CacheTag::USER);
105106

@@ -136,12 +137,7 @@ public function listForUser(OauthListRequest $request): array
136137

137138
$credentials = $user->oauthCredentials()->get();
138139

139-
foreach (OauthProvidersType::cases() as $provider) {
140-
$client_id = config('services.' . $provider->value . '.client_id');
141-
if ($client_id === null || $client_id === '') {
142-
continue;
143-
}
144-
140+
foreach (AuthServiceProvider::getAvailableOauthProviders() as $provider) {
145141
// We create a signed route for 5 minutes
146142
$route = URL::signedRoute(
147143
name: 'oauth-register',
@@ -166,17 +162,6 @@ public function listForUser(OauthListRequest $request): array
166162
*/
167163
public function listProviders(): array
168164
{
169-
$oauth_available = [];
170-
171-
foreach (OauthProvidersType::cases() as $oauth_provider) {
172-
$client_id = config('services.' . $oauth_provider->value . '.client_id');
173-
if ($client_id === null || $client_id === '') {
174-
continue;
175-
}
176-
177-
$oauth_available[] = $oauth_provider;
178-
}
179-
180-
return $oauth_available;
165+
return AuthServiceProvider::getAvailableOauthProviders();
181166
}
182167
}

app/Http/Controllers/WebAuthn/WebAuthnLoginController.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
namespace App\Http\Controllers\WebAuthn;
1010

1111
use App\Exceptions\UnauthenticatedException;
12+
use App\Exceptions\WebAuthnDisabledExecption;
1213
use App\Models\User;
14+
use App\Providers\AuthServiceProvider;
1315
use Illuminate\Contracts\Container\BindingResolutionException;
1416
use Illuminate\Contracts\Support\Responsable;
1517
use Illuminate\Routing\Controller;
@@ -31,6 +33,8 @@ class WebAuthnLoginController extends Controller
3133
*/
3234
public function options(AssertionRequest $request): Responsable
3335
{
36+
$this->checkEnabled();
37+
3438
/** @phpstan-ignore staticMethod.dynamicCall */
3539
$fields = $request->validate([
3640
'user_id' => 'sometimes|int',
@@ -54,6 +58,8 @@ public function options(AssertionRequest $request): Responsable
5458
*/
5559
public function login(AssertedRequest $request, AssertionValidator $validator): void
5660
{
61+
$this->checkEnabled();
62+
5763
$credentials = $request->validated();
5864

5965
if (!$this->isSignedChallenge($credentials)) {
@@ -110,4 +116,19 @@ public function retrieveByCredentials(array $credentials): User|null
110116

111117
return $user;
112118
}
119+
120+
/**
121+
* Validate whether the WebAuthn is enabled in the configuration.
122+
* If not throw an exception with status code 403 (Forbidden).
123+
*
124+
* @return void
125+
*
126+
* @throws WebAuthnDisabledExecption
127+
*/
128+
private function checkEnabled(): void
129+
{
130+
if (AuthServiceProvider::isWebAuthnEnabled() === false) {
131+
throw new WebAuthnDisabledExecption();
132+
}
133+
}
113134
}

app/Http/Requests/Profile/UpdateProfileRequest.php

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use App\Http\Requests\Traits\HasPasswordTrait;
1515
use App\Models\User;
1616
use App\Policies\UserPolicy;
17+
use App\Providers\AuthServiceProvider;
1718
use App\Rules\CurrentPasswordRule;
1819
use App\Rules\PasswordRule;
1920
use App\Rules\UsernameRule;
@@ -23,7 +24,6 @@ class UpdateProfileRequest extends BaseApiRequest implements HasPassword
2324
{
2425
use HasPasswordTrait;
2526

26-
protected string $old_password;
2727
protected ?string $username = null;
2828
protected ?string $email = null;
2929

@@ -40,6 +40,12 @@ public function authorize(): bool
4040
*/
4141
public function rules(): array
4242
{
43+
if (!AuthServiceProvider::isBasicAuthEnabled()) {
44+
return [
45+
RequestAttribute::EMAIL_ATTRIBUTE => ['present', 'nullable', 'email:rfc', 'max:100'],
46+
];
47+
}
48+
4349
return [
4450
RequestAttribute::USERNAME_ATTRIBUTE => ['required', new UsernameRule(true)],
4551
RequestAttribute::PASSWORD_ATTRIBUTE => ['sometimes', 'confirmed', new PasswordRule(false)],
@@ -53,27 +59,15 @@ public function rules(): array
5359
*/
5460
protected function processValidatedValues(array $values, array $files): void
5561
{
56-
$this->password = $values[RequestAttribute::PASSWORD_ATTRIBUTE] ?? null;
57-
$this->old_password = $values[RequestAttribute::OLD_PASSWORD_ATTRIBUTE];
58-
59-
$this->username = trim($values[RequestAttribute::USERNAME_ATTRIBUTE]);
60-
$this->username = $this->username === '' ? null : $this->username;
61-
6262
$this->email = trim($values[RequestAttribute::EMAIL_ATTRIBUTE] ?? '');
6363
$this->email = $this->email === '' ? null : $this->email;
64-
}
6564

66-
/**
67-
* Returns the previous password.
68-
*
69-
* See {@link HasPasswordTrait::password()} for an explanation of the
70-
* semantic difference between the return values `null` and `''`.
71-
*
72-
* @return string|null
73-
*/
74-
public function oldPassword(): ?string
75-
{
76-
return $this->old_password;
65+
if (AuthServiceProvider::isBasicAuthEnabled()) {
66+
// If basic auth is enabled, we require the username.
67+
$this->password = $values[RequestAttribute::PASSWORD_ATTRIBUTE] ?? null;
68+
$this->username = trim($values[RequestAttribute::USERNAME_ATTRIBUTE] ?? '');
69+
$this->username = $this->username === '' ? null : $this->username;
70+
}
7771
}
7872

7973
/**

app/Http/Requests/Session/LoginRequest.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111
use App\Contracts\Http\Requests\HasPassword;
1212
use App\Contracts\Http\Requests\HasUsername;
1313
use App\Contracts\Http\Requests\RequestAttribute;
14+
use App\Exceptions\BasicAuthDisabledExecption;
1415
use App\Http\Requests\BaseApiRequest;
1516
use App\Http\Requests\Traits\HasPasswordTrait;
1617
use App\Http\Requests\Traits\HasUsernameTrait;
18+
use App\Providers\AuthServiceProvider;
1719
use App\Rules\PasswordRule;
1820
use App\Rules\UsernameRule;
1921

@@ -27,7 +29,7 @@ class LoginRequest extends BaseApiRequest implements HasUsername, HasPassword
2729
*/
2830
public function authorize(): bool
2931
{
30-
return true;
32+
return AuthServiceProvider::isBasicAuthEnabled() || throw new BasicAuthDisabledExecption();
3133
}
3234

3335
/**

app/Http/Resources/GalleryConfigs/InitConfig.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use App\Enum\ThumbAlbumSubtitleType;
1717
use App\Enum\ThumbOverlayVisibilityType;
1818
use App\Models\Configs;
19+
use App\Providers\AuthServiceProvider;
1920
use Illuminate\Support\Facades\Auth;
2021
use Illuminate\Support\Facades\URL;
2122
use LycheeVerify\Verify;
@@ -89,6 +90,8 @@ class InitConfig extends Data
8990
// Live Metrics settings
9091
public bool $is_live_metrics_enabled;
9192

93+
public bool $is_basic_auth_enabled = true;
94+
public bool $is_webauthn_enabled = true;
9295
// User registration enabled
9396
public bool $is_registration_enabled;
9497

@@ -145,6 +148,8 @@ public function __construct()
145148
$this->title = Configs::getValueAsString('site_title');
146149
$this->dropbox_api_key = Auth::user()?->may_administrate === true ? Configs::getValueAsString('dropbox_key') : 'disabled';
147150

151+
$this->is_basic_auth_enabled = AuthServiceProvider::isBasicAuthEnabled();
152+
$this->is_webauthn_enabled = AuthServiceProvider::isWebAuthnEnabled();
148153
// User registration enabled
149154
$this->is_registration_enabled = Configs::getValueAsBool('user_registration_enabled');
150155

0 commit comments

Comments
 (0)