Merge remote-tracking branch 'origin/recaptcha'

# Conflicts:
#	public/mix-manifest.json
#	src/Domain/Settings/Controllers/StoreSocialServiceCredentialsController.php
This commit is contained in:
Čarodej
2022-01-25 16:29:37 +01:00
17 changed files with 223 additions and 10 deletions
+3
View File
@@ -68,6 +68,9 @@ GOOGLE_CLIENT_SECRET=
GITHUB_CLIENT_ID= GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET= 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 SANCTUM_STATEFUL_DOMAINS=localhost,localhost:8000,127.0.0.1,127.0.0.1:8000,::1
IS_ADMIN_VUEFILEMANAGER_BAR=true IS_ADMIN_VUEFILEMANAGER_BAR=true
+5
View File
@@ -51,4 +51,9 @@ return [
'client_secret' => env('FACEBOOK_CLIENT_SECRET'), 'client_secret' => env('FACEBOOK_CLIENT_SECRET'),
'redirect' => env('APP_URL') . '/socialite/facebook/callback', 'redirect' => env('APP_URL') . '/socialite/facebook/callback',
], ],
'recaptcha' => [
'client_id' => env('RECAPTCHA_CLIENT_ID'),
'client_secret' => env('RECAPTCHA_CLIENT_SECRET'),
],
]; ];
+16 -3
View File
@@ -9103,6 +9103,11 @@
"readable-stream": "^2.0.2" "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": { "recast": {
"version": "0.11.23", "version": "0.11.23",
"resolved": "https://registry.npmjs.org/recast/-/recast-0.11.23.tgz", "resolved": "https://registry.npmjs.org/recast/-/recast-0.11.23.tgz",
@@ -11613,9 +11618,9 @@
"dev": true "dev": true
}, },
"vue-i18n": { "vue-i18n": {
"version": "8.26.8", "version": "8.27.0",
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-8.26.8.tgz", "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-8.27.0.tgz",
"integrity": "sha512-BN2OXolO15AKS95yNF8oOtARibaO6RxyKkAYNV4XpOmL7S4eVZYMIDtyvDv+XGZaiUmBJSH9mdNqzexvGMnK2A==" "integrity": "sha512-SX35iJHL5PJ4Gfh0Mo/q0shyHiI2V6Zkh51c+k8E9O1RKv5BQyYrCxRzpvPrsIOJEnLaeiovet3dsUB0e/kDzw=="
}, },
"vue-loader": { "vue-loader": {
"version": "15.9.7", "version": "15.9.7",
@@ -11638,6 +11643,14 @@
"vue": "^2.6.7" "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": { "vue-resize-sensor": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/vue-resize-sensor/-/vue-resize-sensor-2.0.0.tgz", "resolved": "https://registry.npmjs.org/vue-resize-sensor/-/vue-resize-sensor-2.0.0.tgz",
+2 -1
View File
@@ -31,8 +31,9 @@
"vee-validate": "^3.4.14", "vee-validate": "^3.4.14",
"vue": "^2.6.14", "vue": "^2.6.14",
"vue-feather-icons": "^5.1.0", "vue-feather-icons": "^5.1.0",
"vue-i18n": "^8.26.8", "vue-i18n": "^8.27.0",
"vue-paystack": "^2.0.4", "vue-paystack": "^2.0.4",
"vue-recaptcha-v3": "^1.9.0",
"vue-router": "^3.5.3", "vue-router": "^3.5.3",
"vuex": "^3.6.2" "vuex": "^3.6.2"
} }
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

+9
View File
@@ -26,6 +26,15 @@ const ValidatorHelpers = {
Vue.prototype.$isInvalidEmail = function (email) { Vue.prototype.$isInvalidEmail = function (email) {
return email.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/) === null return email.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/) === null
} }
Vue.prototype.$reCaptchaToken = async function (action) {
await this.$recaptchaLoaded()
let token = await this.$recaptcha(action)
return token
}
} }
} }
+7 -1
View File
@@ -11,7 +11,8 @@ import SubscriptionHelpers from "./helpers/SubscriptionHelpers";
import ValidatorHelpers from "./helpers/ValidatorHelpers"; import ValidatorHelpers from "./helpers/ValidatorHelpers";
import functionHelpers from "./helpers/functionHelpers"; import functionHelpers from "./helpers/functionHelpers";
import AlertHelpers from "./helpers/AlertHelpers"; 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(VueRouter);
Vue.use(SubscriptionHelpers); Vue.use(SubscriptionHelpers);
@@ -19,6 +20,11 @@ Vue.use(ValidatorHelpers);
Vue.use(functionHelpers); Vue.use(functionHelpers);
Vue.use(AlertHelpers); Vue.use(AlertHelpers);
Vue.use(itemHelpers); Vue.use(itemHelpers);
Vue.use(VueReCaptcha, { siteKey: config.recaptcha_client_id,
loaderOptions: {
autoHideBadge: true
}
})
Vue.config.productionTip = false; Vue.config.productionTip = false;
+5
View File
@@ -141,6 +141,11 @@ const mutations = {
state.config.allowedGithubLogin = true state.config.allowedGithubLogin = true
state.config.isGithubLoginConfigured = true state.config.isGithubLoginConfigured = true
} }
if (service === 'recaptcha') {
state.config.allowedRecaptcha = true
state.config.isRecaptchaConfigured = true
}
}, },
SET_STRIPE_CREDENTIALS(state, data) { SET_STRIPE_CREDENTIALS(state, data) {
state.config.stripe_public_key = data.key state.config.stripe_public_key = data.key
@@ -85,6 +85,56 @@
</AppInputSwitch> </AppInputSwitch>
</div> </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--> <!--Facebook Social Authentication-->
<div class="card shadow-card"> <div class="card shadow-card">
<img :src="$getSocialLogo('facebook')" alt="Facebook" class="mb-8 h-5"> <img :src="$getSocialLogo('facebook')" alt="Facebook" class="mb-8 h-5">
@@ -106,7 +156,7 @@
<!--Set up facebook credentials--> <!--Set up facebook credentials-->
<ValidationObserver <ValidationObserver
v-if="(! config.isFacebookLoginConfigured || facebook.isVisibleCredentialsForm) && facebook.allowedService" v-if="(! config.isFacebookLoginConfigured || facebook.isVisibleCredentialsForm) && facebook.allowedService"
@submit.prevent="storeCredentials('facebook')" @submit.prevent="storeCredentials('facebook_login')"
ref="credentialsForm" ref="credentialsForm"
v-slot="{ invalid }" v-slot="{ invalid }"
tag="form" tag="form"
@@ -156,7 +206,7 @@
<!--Set up Google credentials--> <!--Set up Google credentials-->
<ValidationObserver <ValidationObserver
v-if="(! config.isGoogleLoginConfigured || google.isVisibleCredentialsForm) && google.allowedService" v-if="(! config.isGoogleLoginConfigured || google.isVisibleCredentialsForm) && google.allowedService"
@submit.prevent="storeCredentials('google')" @submit.prevent="storeCredentials('google_login')"
ref="credentialsForm" ref="credentialsForm"
v-slot="{ invalid }" v-slot="{ invalid }"
tag="form" tag="form"
@@ -206,7 +256,7 @@
<!--Set up github credentials--> <!--Set up github credentials-->
<ValidationObserver <ValidationObserver
v-if="(! config.isGithubLoginConfigured || github.isVisibleCredentialsForm) && github.allowedService" v-if="(! config.isGithubLoginConfigured || github.isVisibleCredentialsForm) && github.allowedService"
@submit.prevent="storeCredentials('github')" @submit.prevent="storeCredentials('github_login')"
ref="credentialsForm" ref="credentialsForm"
v-slot="{ invalid }" v-slot="{ invalid }"
tag="form" tag="form"
@@ -289,6 +339,14 @@
isLoading: true, isLoading: true,
isFlushingCache: false, isFlushingCache: false,
app: undefined, app: undefined,
recaptcha: {
allowedService: false,
isVisibleCredentialsForm: false,
credentials: {
key: undefined,
secret: undefined,
},
},
facebook: { facebook: {
allowedService: false, allowedService: false,
isVisibleCredentialsForm: false, isVisibleCredentialsForm: false,
@@ -372,6 +430,7 @@
} }
}, },
mounted() { mounted() {
this.recaptcha.allowedService = this.config.allowedRecaptcha
this.facebook.allowedService = this.config.allowedFacebookLogin this.facebook.allowedService = this.config.allowedFacebookLogin
this.google.allowedService = this.config.allowedGoogleLogin this.google.allowedService = this.config.allowedGoogleLogin
this.github.allowedService = this.config.allowedGithubLogin this.github.allowedService = this.config.allowedGithubLogin
+8
View File
@@ -122,6 +122,7 @@
email: '', email: '',
password: '', password: '',
password_confirmation: '', password_confirmation: '',
reCaptcha:null,
}, },
} }
}, },
@@ -136,6 +137,13 @@
// Start loading // Start loading
this.isLoading = true 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 // Send request to get user token
axios axios
.post('/api/register', this.register) .post('/api/register', this.register)
@@ -92,6 +92,7 @@
contact: { contact: {
email: '', email: '',
message: '', message: '',
reCaptcha: null,
}, },
} }
}, },
@@ -106,6 +107,13 @@
// Start loading // Start loading
this.isLoading = true 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 // Send request to get user token
axios axios
.post('/api/contact', this.contact) .post('/api/contact', this.contact)
+5
View File
@@ -128,6 +128,11 @@
stripe_public_key: '{{ env('STRIPE_PUBLIC_KEY') }}', stripe_public_key: '{{ env('STRIPE_PUBLIC_KEY') }}',
stripe_payment_description: '{{ $settings->stripe_payment_description ?? '' }}', 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 // Social logins
allowedFacebookLogin: {{ $settings->allowed_facebook_login ?? 0 }}, allowedFacebookLogin: {{ $settings->allowed_facebook_login ?? 0 }},
isFacebookLoginConfigured: {{ env('FACEBOOK_CLIENT_ID') ? 1 : 0 }}, isFacebookLoginConfigured: {{ env('FACEBOOK_CLIENT_ID') ? 1 : 0 }},
@@ -2,8 +2,10 @@
namespace App\Users\Requests; namespace App\Users\Requests;
use App\Users\Rules\EmailProvider; use App\Users\Rules\EmailProvider;
use App\Users\Rules\ReCaptchaRules;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use App\Users\Rules\PasswordValidationRules; use App\Users\Rules\PasswordValidationRules;
use Illuminate\Validation\Rules\RequiredIf;
class RegisterUserRequest extends FormRequest class RegisterUserRequest extends FormRequest
{ {
@@ -30,6 +32,7 @@ class RegisterUserRequest extends FormRequest
'email' => ['required', 'string', 'email', 'max:255', 'unique:users,email', new EmailProvider], 'email' => ['required', 'string', 'email', 'max:255', 'unique:users,email', new EmailProvider],
'name' => 'required|string|max:255', 'name' => 'required|string|max:255',
'password' => $this->passwordRules(), 'password' => $this->passwordRules(),
'reCaptcha' => [new RequiredIf(get_settings('allowed_recaptcha') == 1), 'string', app(ReCaptchaRules::class)]
]; ];
} }
} }
+38
View 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 <?php
namespace Domain\Homepage\Requests; namespace Domain\Homepage\Requests;
use App\Users\Rules\ReCaptchaRules;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\RequiredIf;
class SendContactMessageRequest extends FormRequest class SendContactMessageRequest extends FormRequest
{ {
@@ -25,6 +27,7 @@ class SendContactMessageRequest extends FormRequest
return [ return [
'email' => 'required|email', 'email' => 'required|email',
'message' => 'required|string', '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 // Set on social login
Setting::updateOrCreate([ Setting::updateOrCreate([
'name' => "allowed_{$request->input('service')}_login", 'name' => "allowed_{$request->input('service')}",
], [ ], [
'value' => 1, 'value' => 1,
]); ]);
@@ -38,6 +38,11 @@ class StoreSocialServiceCredentialsController
'GITHUB_CLIENT_ID' => $request->input('client_id'), 'GITHUB_CLIENT_ID' => $request->input('client_id'),
'GITHUB_CLIENT_SECRET' => $request->input('client_secret'), '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 // Store credentials into the .env file
+30
View File
@@ -5,6 +5,7 @@ use Storage;
use Notification; use Notification;
use Tests\TestCase; use Tests\TestCase;
use App\Users\Models\User; use App\Users\Models\User;
use App\Users\Rules\ReCaptchaRules;
use Domain\Settings\Models\Setting; use Domain\Settings\Models\Setting;
use Illuminate\Support\Facades\Password; use Illuminate\Support\Facades\Password;
use App\Users\Notifications\ResetPassword; use App\Users\Notifications\ResetPassword;
@@ -329,4 +330,33 @@ class SignFlowTest extends TestCase
'email' => $user->email, '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',
]);
}
} }