mirror of
https://github.com/VueFileManager/vuefilemanager.git
synced 2026-04-05 18:23:48 +00:00
Merge remote-tracking branch 'origin/recaptcha'
# Conflicts: # public/mix-manifest.json # src/Domain/Settings/Controllers/StoreSocialServiceCredentialsController.php
This commit is contained in:
@@ -68,6 +68,9 @@ GOOGLE_CLIENT_SECRET=
|
||||
GITHUB_CLIENT_ID=
|
||||
GITHUB_CLIENT_SECRET=
|
||||
|
||||
RECAPTCHA_CLIENT_ID=
|
||||
RECAPTCHA_CLIENT_SECRET=
|
||||
|
||||
SANCTUM_STATEFUL_DOMAINS=localhost,localhost:8000,127.0.0.1,127.0.0.1:8000,::1
|
||||
|
||||
IS_ADMIN_VUEFILEMANAGER_BAR=true
|
||||
|
||||
@@ -51,4 +51,9 @@ return [
|
||||
'client_secret' => env('FACEBOOK_CLIENT_SECRET'),
|
||||
'redirect' => env('APP_URL') . '/socialite/facebook/callback',
|
||||
],
|
||||
|
||||
'recaptcha' => [
|
||||
'client_id' => env('RECAPTCHA_CLIENT_ID'),
|
||||
'client_secret' => env('RECAPTCHA_CLIENT_SECRET'),
|
||||
],
|
||||
];
|
||||
|
||||
19
package-lock.json
generated
19
package-lock.json
generated
@@ -9103,6 +9103,11 @@
|
||||
"readable-stream": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"recaptcha-v3": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/recaptcha-v3/-/recaptcha-v3-1.10.0.tgz",
|
||||
"integrity": "sha512-aGTxYSk3FFNKnXeKDbLpgRDRyIHRZNBF5HyaXXAN1Aj4TSyyZvmoAn9CylvpqLV3pYpIQavwc+2rzhNFn5SsLQ=="
|
||||
},
|
||||
"recast": {
|
||||
"version": "0.11.23",
|
||||
"resolved": "https://registry.npmjs.org/recast/-/recast-0.11.23.tgz",
|
||||
@@ -11613,9 +11618,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"vue-i18n": {
|
||||
"version": "8.26.8",
|
||||
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-8.26.8.tgz",
|
||||
"integrity": "sha512-BN2OXolO15AKS95yNF8oOtARibaO6RxyKkAYNV4XpOmL7S4eVZYMIDtyvDv+XGZaiUmBJSH9mdNqzexvGMnK2A=="
|
||||
"version": "8.27.0",
|
||||
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-8.27.0.tgz",
|
||||
"integrity": "sha512-SX35iJHL5PJ4Gfh0Mo/q0shyHiI2V6Zkh51c+k8E9O1RKv5BQyYrCxRzpvPrsIOJEnLaeiovet3dsUB0e/kDzw=="
|
||||
},
|
||||
"vue-loader": {
|
||||
"version": "15.9.7",
|
||||
@@ -11638,6 +11643,14 @@
|
||||
"vue": "^2.6.7"
|
||||
}
|
||||
},
|
||||
"vue-recaptcha-v3": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/vue-recaptcha-v3/-/vue-recaptcha-v3-1.9.0.tgz",
|
||||
"integrity": "sha512-WQIlhcOcETk3SYbEC88podUSq1J7UjmHpKgJhSy0Xm3DAjTIPjl19g9kn5KRdyTxNLiS/eR5C6H3Jk3c7b9baA==",
|
||||
"requires": {
|
||||
"recaptcha-v3": "^1.8.0"
|
||||
}
|
||||
},
|
||||
"vue-resize-sensor": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/vue-resize-sensor/-/vue-resize-sensor-2.0.0.tgz",
|
||||
|
||||
@@ -31,8 +31,9 @@
|
||||
"vee-validate": "^3.4.14",
|
||||
"vue": "^2.6.14",
|
||||
"vue-feather-icons": "^5.1.0",
|
||||
"vue-i18n": "^8.26.8",
|
||||
"vue-i18n": "^8.27.0",
|
||||
"vue-paystack": "^2.0.4",
|
||||
"vue-recaptcha-v3": "^1.9.0",
|
||||
"vue-router": "^3.5.3",
|
||||
"vuex": "^3.6.2"
|
||||
}
|
||||
|
||||
12
public/assets/others/recaptcha.svg
Normal file
12
public/assets/others/recaptcha.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 12 KiB |
9
resources/js/helpers/ValidatorHelpers.js
vendored
9
resources/js/helpers/ValidatorHelpers.js
vendored
@@ -26,6 +26,15 @@ const ValidatorHelpers = {
|
||||
Vue.prototype.$isInvalidEmail = function (email) {
|
||||
return email.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/) === null
|
||||
}
|
||||
|
||||
Vue.prototype.$reCaptchaToken = async function (action) {
|
||||
|
||||
await this.$recaptchaLoaded()
|
||||
|
||||
let token = await this.$recaptcha(action)
|
||||
|
||||
return token
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
8
resources/js/main.js
vendored
8
resources/js/main.js
vendored
@@ -11,7 +11,8 @@ import SubscriptionHelpers from "./helpers/SubscriptionHelpers";
|
||||
import ValidatorHelpers from "./helpers/ValidatorHelpers";
|
||||
import functionHelpers from "./helpers/functionHelpers";
|
||||
import AlertHelpers from "./helpers/AlertHelpers";
|
||||
import itemHelpers from "./helpers/itemHelpers"
|
||||
import itemHelpers from "./helpers/itemHelpers";
|
||||
import { VueReCaptcha } from 'vue-recaptcha-v3';
|
||||
|
||||
Vue.use(VueRouter);
|
||||
Vue.use(SubscriptionHelpers);
|
||||
@@ -19,6 +20,11 @@ Vue.use(ValidatorHelpers);
|
||||
Vue.use(functionHelpers);
|
||||
Vue.use(AlertHelpers);
|
||||
Vue.use(itemHelpers);
|
||||
Vue.use(VueReCaptcha, { siteKey: config.recaptcha_client_id,
|
||||
loaderOptions: {
|
||||
autoHideBadge: true
|
||||
}
|
||||
})
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
|
||||
5
resources/js/store/modules/app.js
vendored
5
resources/js/store/modules/app.js
vendored
@@ -141,6 +141,11 @@ const mutations = {
|
||||
state.config.allowedGithubLogin = true
|
||||
state.config.isGithubLoginConfigured = true
|
||||
}
|
||||
|
||||
if (service === 'recaptcha') {
|
||||
state.config.allowedRecaptcha = true
|
||||
state.config.isRecaptchaConfigured = true
|
||||
}
|
||||
},
|
||||
SET_STRIPE_CREDENTIALS(state, data) {
|
||||
state.config.stripe_public_key = data.key
|
||||
|
||||
@@ -85,6 +85,56 @@
|
||||
</AppInputSwitch>
|
||||
</div>
|
||||
|
||||
<!-- ReCaptcha -->
|
||||
<div class="card shadow-card">
|
||||
<img src="/assets/others/recaptcha.svg" alt="reCaptcha" class="mb-8 h-10">
|
||||
|
||||
<AppInputSwitch :title="$t('Allow ReCaptcha')" :description="$t('ReCaptcha will be allowed on Registration and Contact Us forms.')" :is-last="! recaptcha.allowedService">
|
||||
<SwitchInput
|
||||
@input="$updateText('/admin/settings', 'allowed_recaptcha', recaptcha.allowedService)"
|
||||
v-model="recaptcha.allowedService"
|
||||
class="switch"
|
||||
:state="recaptcha.allowedService"
|
||||
/>
|
||||
</AppInputSwitch>
|
||||
|
||||
<div v-if="config.isRecaptchaConfigured && recaptcha.allowedService" @click="recaptcha.isVisibleCredentialsForm = !recaptcha.isVisibleCredentialsForm" class="flex items-center cursor-pointer" :class="{'mb-4': recaptcha.isVisibleCredentialsForm}">
|
||||
<edit2-icon size="12" class="vue-feather text-theme mr-2" />
|
||||
<b class="text-xs">{{ $t('Update Your Credentials') }}</b>
|
||||
</div>
|
||||
|
||||
<!--Set up recaptcha credentials-->
|
||||
<ValidationObserver
|
||||
v-if="(! config.isRecaptchaConfigured || recaptcha.isVisibleCredentialsForm) && recaptcha.allowedService"
|
||||
@submit.prevent="storeCredentials('recaptcha')"
|
||||
ref="credentialsForm"
|
||||
v-slot="{ invalid }"
|
||||
tag="form"
|
||||
class="p-5 shadow-lg rounded-xl"
|
||||
>
|
||||
<FormLabel v-if="! config.isRecaptchaConfigured" icon="shield">
|
||||
{{ $t('Configure Credentials') }}
|
||||
</FormLabel>
|
||||
|
||||
<ValidationProvider tag="div" mode="passive" name="Site Key" rules="required" v-slot="{ errors }">
|
||||
<AppInputText :title="$t('Site Key')" :error="errors[0]">
|
||||
<input v-model="recaptcha.credentials.client_id" :placeholder="$t('Paste your Site Key here')" type="text" :class="{'border-red': errors[0]}" class="focus-border-theme input-dark" />
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
|
||||
<ValidationProvider tag="div" mode="passive" name="Secret key" rules="required" v-slot="{ errors }">
|
||||
<AppInputText :title="$t('Secret Key')" :error="errors[0]">
|
||||
<input v-model="recaptcha.credentials.client_secret" :placeholder="$t('Paste your Secret key here')" type="text" :class="{'border-red': errors[0]}" class="focus-border-theme input-dark" />
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
|
||||
<ButtonBase :disabled="isLoading" :loading="isLoading" button-style="theme" type="submit" class="w-full">
|
||||
{{ $t('Store Credentials') }}
|
||||
</ButtonBase>
|
||||
</ValidationObserver>
|
||||
|
||||
</div>
|
||||
|
||||
<!--Facebook Social Authentication-->
|
||||
<div class="card shadow-card">
|
||||
<img :src="$getSocialLogo('facebook')" alt="Facebook" class="mb-8 h-5">
|
||||
@@ -106,7 +156,7 @@
|
||||
<!--Set up facebook credentials-->
|
||||
<ValidationObserver
|
||||
v-if="(! config.isFacebookLoginConfigured || facebook.isVisibleCredentialsForm) && facebook.allowedService"
|
||||
@submit.prevent="storeCredentials('facebook')"
|
||||
@submit.prevent="storeCredentials('facebook_login')"
|
||||
ref="credentialsForm"
|
||||
v-slot="{ invalid }"
|
||||
tag="form"
|
||||
@@ -156,7 +206,7 @@
|
||||
<!--Set up Google credentials-->
|
||||
<ValidationObserver
|
||||
v-if="(! config.isGoogleLoginConfigured || google.isVisibleCredentialsForm) && google.allowedService"
|
||||
@submit.prevent="storeCredentials('google')"
|
||||
@submit.prevent="storeCredentials('google_login')"
|
||||
ref="credentialsForm"
|
||||
v-slot="{ invalid }"
|
||||
tag="form"
|
||||
@@ -206,7 +256,7 @@
|
||||
<!--Set up github credentials-->
|
||||
<ValidationObserver
|
||||
v-if="(! config.isGithubLoginConfigured || github.isVisibleCredentialsForm) && github.allowedService"
|
||||
@submit.prevent="storeCredentials('github')"
|
||||
@submit.prevent="storeCredentials('github_login')"
|
||||
ref="credentialsForm"
|
||||
v-slot="{ invalid }"
|
||||
tag="form"
|
||||
@@ -289,6 +339,14 @@
|
||||
isLoading: true,
|
||||
isFlushingCache: false,
|
||||
app: undefined,
|
||||
recaptcha: {
|
||||
allowedService: false,
|
||||
isVisibleCredentialsForm: false,
|
||||
credentials: {
|
||||
key: undefined,
|
||||
secret: undefined,
|
||||
},
|
||||
},
|
||||
facebook: {
|
||||
allowedService: false,
|
||||
isVisibleCredentialsForm: false,
|
||||
@@ -372,6 +430,7 @@
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.recaptcha.allowedService = this.config.allowedRecaptcha
|
||||
this.facebook.allowedService = this.config.allowedFacebookLogin
|
||||
this.google.allowedService = this.config.allowedGoogleLogin
|
||||
this.github.allowedService = this.config.allowedGithubLogin
|
||||
|
||||
@@ -122,6 +122,7 @@
|
||||
email: '',
|
||||
password: '',
|
||||
password_confirmation: '',
|
||||
reCaptcha:null,
|
||||
},
|
||||
}
|
||||
},
|
||||
@@ -136,6 +137,13 @@
|
||||
// Start loading
|
||||
this.isLoading = true
|
||||
|
||||
// Get ReCaptcha token
|
||||
if(config.allowedRecaptcha) {
|
||||
this.register.reCaptcha = await this.$reCaptchaToken('register').then((response) => {
|
||||
return response
|
||||
})
|
||||
}
|
||||
|
||||
// Send request to get user token
|
||||
axios
|
||||
.post('/api/register', this.register)
|
||||
|
||||
@@ -92,6 +92,7 @@
|
||||
contact: {
|
||||
email: '',
|
||||
message: '',
|
||||
reCaptcha: null,
|
||||
},
|
||||
}
|
||||
},
|
||||
@@ -106,6 +107,13 @@
|
||||
// Start loading
|
||||
this.isLoading = true
|
||||
|
||||
// Get ReCaptcha token
|
||||
if(config.allowedRecaptcha) {
|
||||
this.register.reCaptcha = await this.$reCaptchaToken('register').then((response) => {
|
||||
return response
|
||||
})
|
||||
}
|
||||
|
||||
// Send request to get user token
|
||||
axios
|
||||
.post('/api/contact', this.contact)
|
||||
|
||||
@@ -128,6 +128,11 @@
|
||||
stripe_public_key: '{{ env('STRIPE_PUBLIC_KEY') }}',
|
||||
stripe_payment_description: '{{ $settings->stripe_payment_description ?? '' }}',
|
||||
|
||||
// ReCaptcha
|
||||
recaptcha_client_id: '{{ env('RECAPTCHA_CLIENT_ID') }}',
|
||||
allowedRecaptcha: {{ $settings->allowed_recaptcha ?? 0 }},
|
||||
isRecaptchaConfigured: {{ env('RECAPTCHA_CLIENT_ID') ? 1 : 0 }},
|
||||
|
||||
// Social logins
|
||||
allowedFacebookLogin: {{ $settings->allowed_facebook_login ?? 0 }},
|
||||
isFacebookLoginConfigured: {{ env('FACEBOOK_CLIENT_ID') ? 1 : 0 }},
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
namespace App\Users\Requests;
|
||||
|
||||
use App\Users\Rules\EmailProvider;
|
||||
use App\Users\Rules\ReCaptchaRules;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use App\Users\Rules\PasswordValidationRules;
|
||||
use Illuminate\Validation\Rules\RequiredIf;
|
||||
|
||||
class RegisterUserRequest extends FormRequest
|
||||
{
|
||||
@@ -30,6 +32,7 @@ class RegisterUserRequest extends FormRequest
|
||||
'email' => ['required', 'string', 'email', 'max:255', 'unique:users,email', new EmailProvider],
|
||||
'name' => 'required|string|max:255',
|
||||
'password' => $this->passwordRules(),
|
||||
'reCaptcha' => [new RequiredIf(get_settings('allowed_recaptcha') == 1), 'string', app(ReCaptchaRules::class)]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
38
src/App/Users/Rules/ReCaptchaRules.php
Normal file
38
src/App/Users/Rules/ReCaptchaRules.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Users\Rules;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use Illuminate\Contracts\Validation\Rule;
|
||||
|
||||
class ReCaptchaRules implements Rule
|
||||
{
|
||||
/**
|
||||
* Determine if the validation rule passes.
|
||||
*
|
||||
* @param string $attribute
|
||||
* @param mixed $value
|
||||
* @return bool
|
||||
*/
|
||||
public function passes($attribute, $value)
|
||||
{
|
||||
$client = new Client();
|
||||
$response = $client->post('https://www.google.com/recaptcha/api/siteverify',
|
||||
[
|
||||
'form_params' => [
|
||||
'secret' => env('RECAPTCHA_CLIENT_SECRET', false),
|
||||
'remoteip' => request()->getClientIp(),
|
||||
'response' => $value
|
||||
]
|
||||
]
|
||||
);
|
||||
$body = json_decode((string)$response->getBody());
|
||||
|
||||
return $body->success;
|
||||
}
|
||||
|
||||
public function message(): string
|
||||
{
|
||||
return 'Are you a robot?';
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
<?php
|
||||
namespace Domain\Homepage\Requests;
|
||||
|
||||
use App\Users\Rules\ReCaptchaRules;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rules\RequiredIf;
|
||||
|
||||
class SendContactMessageRequest extends FormRequest
|
||||
{
|
||||
@@ -25,6 +27,7 @@ class SendContactMessageRequest extends FormRequest
|
||||
return [
|
||||
'email' => 'required|email',
|
||||
'message' => 'required|string',
|
||||
'reCaptcha' => [new RequiredIf(get_settings('allowed_recaptcha') == 1), 'string', app(ReCaptchaRules::class)]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ class StoreSocialServiceCredentialsController
|
||||
|
||||
// Set on social login
|
||||
Setting::updateOrCreate([
|
||||
'name' => "allowed_{$request->input('service')}_login",
|
||||
'name' => "allowed_{$request->input('service')}",
|
||||
], [
|
||||
'value' => 1,
|
||||
]);
|
||||
@@ -27,7 +27,7 @@ class StoreSocialServiceCredentialsController
|
||||
if (! app()->runningUnitTests()) {
|
||||
$credentials = [
|
||||
'facebook' => [
|
||||
'FACEBOOK_CLIENT_ID' => $request->input('client_id'),
|
||||
'FACEBOOK_CLIENT_ID' => $request->input('client_id'),
|
||||
'FACEBOOK_CLIENT_SECRET' => $request->input('client_secret'),
|
||||
],
|
||||
'google' => [
|
||||
@@ -38,6 +38,11 @@ class StoreSocialServiceCredentialsController
|
||||
'GITHUB_CLIENT_ID' => $request->input('client_id'),
|
||||
'GITHUB_CLIENT_SECRET' => $request->input('client_secret'),
|
||||
],
|
||||
'recaptcha' => [
|
||||
'RECAPTCHA_CLIENT_ID' => $request->input('client_id'),
|
||||
'RECAPTCHA_CLIENT_SECRET' => $request->input('client_secret'),
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
// Store credentials into the .env file
|
||||
|
||||
@@ -5,6 +5,7 @@ use Storage;
|
||||
use Notification;
|
||||
use Tests\TestCase;
|
||||
use App\Users\Models\User;
|
||||
use App\Users\Rules\ReCaptchaRules;
|
||||
use Domain\Settings\Models\Setting;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
use App\Users\Notifications\ResetPassword;
|
||||
@@ -329,4 +330,33 @@ class SignFlowTest extends TestCase
|
||||
'email' => $user->email,
|
||||
]);
|
||||
}
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_create_user_from_register_form_with_reCaptcha()
|
||||
{
|
||||
|
||||
Setting::updateOrCreate([
|
||||
'name' => 'allowed_recaptcha',
|
||||
], [
|
||||
'value' => 1,
|
||||
]);
|
||||
|
||||
$this->mock(ReCaptchaRules::class, function ($mock) {
|
||||
$mock->shouldReceive('passes')->andReturn(true);
|
||||
});
|
||||
|
||||
$this->postJson('api/register', [
|
||||
'email' => 'john@doe.com',
|
||||
'password' => 'SecretPassword',
|
||||
'password_confirmation' => 'SecretPassword',
|
||||
'name' => 'John Doe',
|
||||
'reCaptcha' => 'fakeToken'
|
||||
])->assertStatus(201);
|
||||
|
||||
$this
|
||||
->assertDatabaseHas('users', [
|
||||
'email' => 'john@doe.com',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user