Merge branch 'release/1.3.1' into master

This commit is contained in:
Bubka 2020-10-12 23:14:47 +02:00
commit 535752f8a7
29 changed files with 3041 additions and 1214 deletions

View File

@ -1,8 +1,8 @@
language: php
php:
- 7.2
- 7.3
- 7.4
before_script:
- cp .env.travis .env

View File

@ -6,7 +6,7 @@
# 2FAuth
A web app to manage your Two-Factor Authentication (2FA) accounts and generate their security codes
![screens](https://user-images.githubusercontent.com/858858/74479269-267a1600-4eaf-11ea-9281-415e5a54bd9f.png)
![screens](https://user-images.githubusercontent.com/858858/95789691-47b13180-0cde-11eb-9d06-7c6d2ede5f3c.png)
#### [2FAuth Demo](https://demo.2fauth.app/)
@ -37,9 +37,9 @@ #### RFC compliance
2FAuth generates OTP according to RFC 4226 (HOTP Algorithm) and RFC 6238 (TOTP Algorithm) thanks to [Spomky-Labs/OTPHP](https://github.com/Spomky-Labs/otphp) php library.
## Requirements
[![Requires PHP7](https://img.shields.io/badge/php-7.2.*-red.svg?style=flat-square)](https://secure.php.net/downloads.php)
* See [Laravel server requirements](https://laravel.com/docs/5.8/installation#server-requirements)
* Any database [supported by Laravel](https://laravel.com/docs/5.8/database)
[![Requires PHP7](https://img.shields.io/badge/php-7.3.*-red.svg?style=flat-square)](https://secure.php.net/downloads.php)
* See [Laravel server requirements](https://laravel.com/docs/7.x/installation#server-requirements)
* Any database [supported by Laravel](https://laravel.com/docs/7.x/database)
## Installation (using command line)

View File

@ -21,11 +21,12 @@ class Kernel extends ConsoleKernel
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*
* @codeCoverageIgnore Because no code will always remains Not Executed code
*/
protected function schedule(Schedule $schedule)
{
// $schedule->command('inspire')
// ->hourly();
//
}
/**

View File

@ -2,7 +2,7 @@
namespace App\Exceptions;
use Exception;
use Throwable;
use Illuminate\Http\Response;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundException;
@ -33,10 +33,10 @@ class Handler extends ExceptionHandler
/**
* Report or log an exception.
*
* @param \Exception $exception
* @param \Throwable $exception
* @return void
*/
public function report(Exception $exception)
public function report(Throwable $exception)
{
parent::report($exception);
}
@ -45,10 +45,10 @@ public function report(Exception $exception)
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @param \Exception $exception
* @param \Throwable $exception
* @return \Illuminate\Http\Response
*/
public function render($request, Exception $exception)
public function render($request, Throwable $exception)
{
if ( $request->wantsJson() ) {
@ -65,10 +65,10 @@ public function render($request, Exception $exception)
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @param \Exception $exception
* @param \Throwable $exception
* @return \Illuminate\Http\JsonResponse
*/
private function handleApiException($request, Exception $exception)
private function handleApiException($request, Throwable $exception)
{
$debug = [
'exception' => get_class($exception),
@ -96,7 +96,7 @@ private function handleApiException($request, Exception $exception)
/**
* Set a specific response payload for commons http error codes
*
* @param \Exception $exception
* @param \Throwable $exception
* @return \Illuminate\Http\JsonResponse
*/
private function customApiResponse($exception, $debug)

View File

@ -11,7 +11,7 @@
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class QrCodecontroller extends Controller
class QrCodeController extends Controller
{
/**
* Handle uploaded qr code image

View File

@ -63,6 +63,7 @@ class Kernel extends HttpKernel
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'AvoidResetPassword' => \App\Http\Middleware\AvoidPasswordResetInDemo::class,
];
/**

View File

@ -0,0 +1,26 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Response;
class AvoidPasswordResetInDemo
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if( config('app.options.isDemoApp') ) {
return response()->json(['requestFailed' => __('auth.forms.no_reset_password_in_demo')], Response::HTTP_UNAUTHORIZED);
}
return $next($request);
}
}

View File

@ -2,8 +2,10 @@
namespace App\Providers;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
@ -23,6 +25,6 @@ public function register()
*/
public function boot()
{
//
Blade::withoutComponentTags();
}
}

View File

@ -1,5 +1,15 @@
# Change log
## [1.3.1] - 2020-10-12
### Changed
- Upgrade to Laravel 7.0
- Drop PHP 7.2 support
- Enable the Request reset password form in Demo mode but inactivated
### Fixed
- Fix missing notifications in Auth views
## [1.3.0] - 2020-10-09
### Added

View File

@ -1,31 +1,31 @@
{
"name": "laravel/laravel",
"name": "bubka/2fauth",
"type": "project",
"description": "The Laravel Framework.",
"description": "Two-Factor authentication generator",
"keywords": [
"framework",
"laravel"
"2fauth",
"two-actor authentication"
],
"license": "MIT",
"require": {
"php": "^7.1.3",
"appstract/laravel-options": "^3.0",
"php": "^7.3",
"appstract/laravel-options": "^4.1.1",
"doctrine/dbal": "^2.10",
"fideloper/proxy": "^4.0",
"fideloper/proxy": "^4.2",
"khanamiryan/qrcode-detector-decoder": "^1.0",
"laravel/framework": "5.8.*",
"laravel/passport": "^7.2",
"laravel/tinker": "^1.0",
"spatie/eloquent-sortable": "^3.8",
"laravel/framework": "^7.0",
"laravel/passport": "^9.3.2",
"laravel/tinker": "^2.0",
"laravel/ui": "^2.0",
"spatie/eloquent-sortable": "^3.9",
"spomky-labs/otphp": "^10.0"
},
"require-dev": {
"beyondcode/laravel-dump-server": "^1.0",
"filp/whoops": "^2.0",
"fzaninotto/faker": "^1.4",
"mockery/mockery": "^1.0",
"nunomaduro/collision": "^3.0",
"phpunit/phpunit": "^7.5"
"facade/ignition": "^2.3",
"fzaninotto/faker": "^1.9",
"mockery/mockery": "^1.3",
"nunomaduro/collision": "^4.1",
"phpunit/phpunit": "^9.3"
},
"config": {
"optimize-autoloader": true,

3842
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -22,7 +22,7 @@
|
*/
'version' => '1.3.0',
'version' => '1.3.1',
/*
|--------------------------------------------------------------------------

View File

@ -4,45 +4,73 @@
/*
|--------------------------------------------------------------------------
| Mail Driver
| Default Mailer
|--------------------------------------------------------------------------
|
| Laravel supports both SMTP and PHP's "mail" function as drivers for the
| sending of e-mail. You may specify which one you're using throughout
| your application here. By default, Laravel is setup for SMTP mail.
|
| Supported: "smtp", "sendmail", "mailgun", "mandrill", "ses",
| "sparkpost", "postmark", "log", "array"
| This option controls the default mailer that is used to send any email
| messages sent by your application. Alternative mailers may be setup
| and used as needed; however, this mailer will be used by default.
|
*/
'driver' => env('MAIL_DRIVER', 'smtp'),
'default' => env('MAIL_MAILER', 'smtp'),
/*
|--------------------------------------------------------------------------
| SMTP Host Address
| Mailer Configurations
|--------------------------------------------------------------------------
|
| Here you may provide the host address of the SMTP server used by your
| applications. A default option is provided that is compatible with
| the Mailgun mail service which will provide reliable deliveries.
| Here you may configure all of the mailers used by your application plus
| their respective settings. Several examples have been configured for
| you and you are free to add your own as your application requires.
|
| Laravel supports a variety of mail "transport" drivers to be used while
| sending an e-mail. You will specify which one you are using for your
| mailers below. You are free to add additional mailers as required.
|
| Supported: "smtp", "sendmail", "mailgun", "ses",
| "postmark", "log", "array"
|
*/
'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
'mailers' => [
'smtp' => [
'transport' => 'smtp',
'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
'port' => env('MAIL_PORT', 587),
'encryption' => env('MAIL_ENCRYPTION', 'tls'),
'username' => env('MAIL_USERNAME'),
'password' => env('MAIL_PASSWORD'),
'timeout' => null,
'auth_mode' => null,
],
/*
|--------------------------------------------------------------------------
| SMTP Host Port
|--------------------------------------------------------------------------
|
| This is the SMTP port used by your application to deliver e-mails to
| users of the application. Like the host we have set this value to
| stay compatible with the Mailgun e-mail application by default.
|
*/
'ses' => [
'transport' => 'ses',
],
'port' => env('MAIL_PORT', 587),
'mailgun' => [
'transport' => 'mailgun',
],
'postmark' => [
'transport' => 'postmark',
],
'sendmail' => [
'transport' => 'sendmail',
'path' => '/usr/sbin/sendmail -bs',
],
'log' => [
'transport' => 'log',
'channel' => env('MAIL_LOG_CHANNEL'),
],
'array' => [
'transport' => 'array',
],
],
/*
|--------------------------------------------------------------------------
@ -60,47 +88,6 @@
'name' => env('MAIL_FROM_NAME', 'Example'),
],
/*
|--------------------------------------------------------------------------
| E-Mail Encryption Protocol
|--------------------------------------------------------------------------
|
| Here you may specify the encryption protocol that should be used when
| the application send e-mail messages. A sensible default using the
| transport layer security protocol should provide great security.
|
*/
'encryption' => env('MAIL_ENCRYPTION', 'tls'),
/*
|--------------------------------------------------------------------------
| SMTP Server Username
|--------------------------------------------------------------------------
|
| If your SMTP server requires a username for authentication, you should
| set it here. This will get used to authenticate with your server on
| connection. You may also set the "password" value below this one.
|
*/
'username' => env('MAIL_USERNAME'),
'password' => env('MAIL_PASSWORD'),
/*
|--------------------------------------------------------------------------
| Sendmail System Path
|--------------------------------------------------------------------------
|
| When using the "sendmail" driver to send e-mails, we will need to know
| the path to where Sendmail lives on this server. A default path has
| been provided here, which will work well on most of your systems.
|
*/
'sendmail' => '/usr/sbin/sendmail -bs',
/*
|--------------------------------------------------------------------------
| Markdown Mail Settings
@ -120,17 +107,4 @@
],
],
/*
|--------------------------------------------------------------------------
| Log Channel
|--------------------------------------------------------------------------
|
| If you are using the "log" driver, you may specify the logging channel
| if you prefer to keep mail messages separate from other log entries
| for simpler reading. Otherwise, the default channel will be used.
|
*/
'log_channel' => env('MAIL_LOG_CHANNEL'),
];

View File

@ -166,7 +166,7 @@
|
*/
'secure' => env('SESSION_SECURE_COOKIE', false),
'secure' => env('SESSION_SECURE_COOKIE', null),
/*
|--------------------------------------------------------------------------

View File

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddProviderColumnToOauthClientsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('oauth_clients', function (Blueprint $table) {
if (!Schema::hasColumn('oauth_clients', 'provider')) {
$table->string('provider')->after('secret')->nullable();
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('oauth_clients', function (Blueprint $table) {
//
});
}
}

View File

@ -1,28 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
beStrictAboutTestsThatDoNotTestAnything="false">
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
beStrictAboutTestsThatDoNotTestAnything="false"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./app</directory>
</include>
</coverage>
<testsuites>
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory suffix="Test.php">./tests/Feature</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./app</directory>
</whitelist>
</filter>
<php>
<server name="APP_ENV" value="testing"/>
<server name="DB_CONNECTION" value="sqlite"/>

2
public/js/app.js vendored

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
{
"/js/app.js": "/js/app.js?id=bfb57d72d7a233d94944",
"/js/app.js": "/js/app.js?id=889cc936dfe619217313",
"/css/app.css": "/css/app.css?id=808a0baa932d7d3b6ba9",
"/js/manifest.js": "/js/manifest.js?id=3c768977c2574a34506e",
"/js/vendor.js": "/js/vendor.js?id=695918adf494620245ad"

View File

@ -1,5 +1,5 @@
<template>
<form-wrapper :title="$t('auth.forms.login')" :fail="fail" :success="success">
<form-wrapper :title="$t('auth.forms.login')">
<div v-if="$root.appSettings.isDemoApp" class="notification is-info has-text-centered" v-html="$t('auth.forms.welcome_to_demo_app_use_those_credentials')" />
<form @submit.prevent="handleSubmit" @keydown="form.onKeydown($event)">
<form-field :form="form" fieldName="email" inputType="email" :label="$t('auth.forms.email')" autofocus />
@ -7,7 +7,7 @@
<form-buttons :isBusy="form.isBusy" :caption="$t('auth.sign_in')" />
</form>
<p>{{ $t('auth.forms.dont_have_account_yet') }}&nbsp;<router-link :to="{ name: 'register' }" class="is-link">{{ $t('auth.register') }}</router-link></p>
<p v-if="!$root.appSettings.isDemoApp">{{ $t('auth.forms.forgot_your_password') }}&nbsp;<router-link :to="{ name: 'password.request' }" class="is-link">{{ $t('auth.forms.request_password_reset') }}</router-link></p>
<p>{{ $t('auth.forms.forgot_your_password') }}&nbsp;<router-link :to="{ name: 'password.request' }" class="is-link">{{ $t('auth.forms.request_password_reset') }}</router-link></p>
</form-wrapper>
</template>
@ -18,8 +18,6 @@
export default {
data(){
return {
success: '',
fail: '',
form: new Form({
email: '',
password: ''
@ -42,9 +40,11 @@
})
.catch(error => {
if( error.response.status === 401 ) {
this.fail = this.$t('auth.forms.password_do_not_match')
this.$notify({ type: 'is-danger', text: this.$t('auth.forms.password_do_not_match'), duration:-1 })
}
else if( error.response.status !== 422 ) {
this.$router.push({ name: 'genericError', params: { err: error.response } });
}
});
@ -58,6 +58,14 @@
}
next();
},
beforeRouteLeave (to, from, next) {
this.$notify({
clean: true
})
next()
}
}
</script>

View File

@ -1,5 +1,5 @@
<template>
<form-wrapper :title="$t('auth.register')" :fail="fail" :success="success">
<form-wrapper :title="$t('auth.register')">
<form @submit.prevent="handleSubmit" @keydown="form.onKeydown($event)">
<form-field :form="form" fieldName="name" inputType="text" :label="$t('auth.forms.name')" autofocus />
<form-field :form="form" fieldName="email" inputType="email" :label="$t('auth.forms.email')" />
@ -18,8 +18,6 @@
export default {
data(){
return {
success: '',
fail: '',
form: new Form({
name : '',
email : '',
@ -57,9 +55,18 @@
if( data.userCount > 0 ) {
vm.form.isDisabled = true
vm.fail = vm.$t('errors.already_one_user_registered') + ' ' + vm.$t('errors.cannot_register_more_user')
vm.$notify({ type: 'is-danger', text: vm.$t('errors.already_one_user_registered') + ' ' + vm.$t('errors.cannot_register_more_user'), duration:-1 })
}
});
},
beforeRouteLeave (to, from, next) {
this.$notify({
clean: true
})
next()
}
}
</script>

View File

@ -1,5 +1,5 @@
<template>
<form-wrapper :title="$t('auth.forms.reset_password')" :fail="fail" :success="success">
<form-wrapper :title="$t('auth.forms.reset_password')">
<form @submit.prevent="handleSubmit" @keydown="form.onKeydown($event)">
<form-field :form="form" fieldName="email" inputType="email" :label="$t('auth.forms.email')" autofocus />
<form-buttons :isBusy="form.isBusy" :caption="$t('auth.forms.send_password_reset_link')" :showCancelButton="true" cancelLandingView="login" />
@ -14,8 +14,6 @@
export default {
data(){
return {
success: '',
fail: '',
form: new Form({
email: '',
})
@ -27,19 +25,29 @@
this.form.post('/api/password/email', {returnError: true})
.then(response => {
this.success = response.data.status
this.$notify({ type: 'is-success', text: response.data.status, duration:-1 })
})
.catch(error => {
if( error.response.data.requestFailed ) {
this.fail = error.response.data.requestFailed
this.$notify({ type: 'is-danger', text: error.response.data.requestFailed, duration:-1 })
}
else if( error.response.status !== 422 ) {
this.$router.push({ name: 'genericError', params: { err: error.response } });
}
});
}
},
beforeRouteLeave (to, from, next) {
this.$notify({
clean: true
})
next()
}
}
</script>

View File

@ -1,5 +1,5 @@
<template>
<form-wrapper :title="$t('auth.forms.new_password')" :fail="fail" :success="success">
<form-wrapper :title="$t('auth.forms.new_password')">
<form @submit.prevent="handleSubmit" @keydown="form.onKeydown($event)">
<form-field :form="form" fieldName="email" inputType="email" :label="$t('auth.forms.email')" disabled readonly />
<form-field :form="form" fieldName="password" inputType="password" :label="$t('auth.forms.new_password')" />
@ -39,17 +39,27 @@
this.form.post('/api/password/reset', {returnError: true})
.then(response => {
this.success = response.data.status
this.$notify({ type: 'is-success', text: response.data.status, duration:-1 })
})
.catch(error => {
if( error.response.data.resetFailed ) {
this.fail = error.response.data.resetFailed
this.$notify({ type: 'is-danger', text: error.response.data.resetFailed, duration:-1 })
}
else if( error.response.status !== 422 ) {
this.$router.push({ name: 'genericError', params: { err: error.response } });
}
});
}
},
beforeRouteLeave (to, from, next) {
this.$notify({
clean: true
})
next()
}
}
</script>

View File

@ -35,6 +35,7 @@
'forgot_your_password' => 'Forgot your password?',
'request_password_reset' => 'Request a password reset',
'reset_password' => 'Reset password',
'no_reset_password_in_demo' => 'No reset in Demo mode',
'new_password' => 'New password',
'current_password' => [
'label' => 'Current password',

View File

@ -35,6 +35,7 @@
'forgot_your_password' => 'Mot de passe oublié ?',
'request_password_reset' => 'Réinitialiser le mot de passe',
'reset_password' => 'Mot de passe oublié',
'no_reset_password_in_demo' => 'Réinitialisation impossible en mode Démo',
'new_password' => 'Nouveau mot de passe',
'current_password' => [
'label' => 'Mot de passe actuel',

View File

@ -19,7 +19,7 @@
Route::post('checkuser', 'Auth\RegisterController@checkUser');
Route::post('register', 'Auth\RegisterController@register');
Route::post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail');
Route::post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail')->middleware('AvoidResetPassword');
Route::post('password/reset', 'Auth\ResetPasswordController@reset')->name('password.reset');
});

View File

@ -4,6 +4,7 @@
use App\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Password;
use Illuminate\Auth\Notifications\ResetPassword;
use Illuminate\Support\Facades\Notification;
@ -85,4 +86,18 @@ public function testSubmitEmailPasswordRequest()
});
}
/**
* Testing submitting the email password request in Demo mode
*/
public function testSubmitEmailPasswordRequestInDemoMode()
{
Config::set('app.options.isDemoApp', true);
$response = $this->json('POST', '/api/password/email', [
'email' => ''
]);
$response->assertStatus(401);
}
}

View File

@ -1,6 +1,6 @@
<?php
namespace Tests\Unit\Auth;
namespace Tests\Feature\Auth;
use App\User;
use Tests\TestCase;

View File

@ -14,7 +14,7 @@ class RouteTest extends TestCase
*/
public function testLandingViewIsReturned()
{
$response = $this->get(route('landing', ['any' => '']));
$response = $this->get(route('landing', ['any' => '/']));
$response->assertSuccessful()
->assertViewIs('landing');

View File

@ -44,7 +44,7 @@ public function testTwoFAccountDisplay()
$response = $this->actingAs($this->user, 'api')
->json('GET', '/api/twofaccounts/' . $twofaccount->id)
->assertStatus(200)
->assertJson([
->assertJsonFragment([
'service' => 'testTOTP',
'account' => 'test@test.com',
'uri' => 'otpauth://totp/test@test.com?secret=A4GRFHVVRBGY7UIW&issuer=test',
@ -81,7 +81,7 @@ public function testTwoFAccountCreation()
'icon' => 'test.png',
])
->assertStatus(201)
->assertJson([
->assertJsonFragment([
'service' => 'testCreation',
'account' => 'test@example.org',
'uri' => 'otpauth://totp/test@test.com?secret=A4GRFHZVRBGY7UIW&issuer=test',
@ -182,7 +182,7 @@ public function testTwoFAccountTOTPUpdate()
'icon' => 'testUpdate.png',
])
->assertStatus(200)
->assertJson([
->assertJsonFragment([
'id' => 1,
'service' => 'testUpdate',
'account' => 'testUpdate@test.com',
@ -213,7 +213,7 @@ public function testTwoFAccountHOTPUpdate()
'counter' => '5'
])
->assertStatus(200)
->assertJson([
->assertJsonFragment([
'id' => 1,
'service' => 'testUpdate.com',
'account' => 'testUpdate',