├── public ├── favicon.ico ├── robots.txt ├── css │ ├── style.css │ └── admin.css ├── images │ ├── eye.png │ ├── logo.png │ ├── eye-off.png │ └── favicon.png ├── mix-manifest.json ├── .htaccess ├── web.config ├── index.php └── svg │ └── 404.svg ├── vendor └── .gitkeep ├── database ├── .gitignore ├── factories │ ├── RoleFactory.php │ ├── ProviderFactory.php │ ├── OauthClientFactory.php │ ├── UserRoleFactory.php │ └── UserFactory.php ├── migrations │ ├── 2018_04_13_143608_create_password_resets_table.php │ ├── 2018_04_13_143303_role.php │ ├── 2019_03_05_152925_create_providers_table.php │ ├── 2019_07_16_093832_alter_user_table.php │ ├── 2019_10_03_094707_create_table_clients.php │ ├── 2019_08_02_090547_delete_user_roles_on_delete_role.php │ ├── 2019_08_29_143421_create_verification_tokens_table.php │ ├── 2018_04_13_143711_create_user_verification_table.php │ ├── 2018_04_13_143446_user_role.php │ ├── 2018_11_09_090028_create_sessions_table.php │ └── 2018_04_13_143152_user.php └── seeds │ └── DatabaseSeeder.php ├── resources ├── views │ ├── vendor │ │ └── l5-swagger │ │ │ └── .gitkeep │ ├── admin │ │ ├── users.blade.php │ │ ├── create-provider.blade.php │ │ ├── create-role.blade.php │ │ └── oauth-clients.blade.php │ ├── auth │ │ ├── login.blade.php │ │ ├── logged.blade.php │ │ ├── complete-registration-form.blade.php │ │ └── register.blade.php │ ├── emails │ │ └── users │ │ │ └── registered.blade.php │ ├── base.blade.php │ └── mail │ │ ├── complete-registration.blade.php │ │ └── base.blade.php ├── js │ ├── event-bus.js │ ├── app.js │ ├── bootstrap.js │ └── components │ │ ├── LoginForm.vue │ │ ├── Paginator.vue │ │ ├── NotificationComponent.vue │ │ ├── CompleteRegistrationForm.vue │ │ └── Chip.vue ├── images │ ├── eye.png │ ├── logo.png │ ├── eye-off.png │ ├── favicon.png │ └── eye-slash-solid.svg ├── assets │ ├── images │ │ └── logo.png │ ├── sass │ │ ├── _variables.scss │ │ └── app.scss │ └── js │ │ ├── components │ │ └── ExampleComponent.vue │ │ ├── app.js │ │ └── bootstrap.js ├── sass │ ├── _variables.scss │ └── app.scss └── lang │ ├── en │ ├── pagination.php │ ├── passwords.php │ └── auth.php │ └── it │ └── auth.php ├── bootstrap ├── cache │ └── .gitignore └── app.php ├── storage ├── logs │ └── .gitignore ├── app │ ├── public │ │ └── .gitignore │ └── .gitignore └── framework │ ├── testing │ └── .gitignore │ ├── views │ └── .gitignore │ ├── cache │ ├── .gitignore │ └── data │ │ └── .gitignore │ ├── sessions │ └── .gitignore │ └── .gitignore ├── .gitattributes ├── app ├── Constants │ └── Role.php ├── Models │ ├── Role.php │ ├── OauthClient.php │ ├── VerificationToken.php │ ├── Client.php │ ├── UserRole.php │ ├── Provider.php │ └── User.php ├── Repositories │ ├── ClientRoleRepository.php │ ├── UserRepositoryInterface.php │ ├── BaseRepository.php │ ├── ClientRepositoryInterface.php │ ├── ClientRepository.php │ ├── RepositoryInterface.php │ ├── RoleRepository.php │ ├── UserRoleRepository.php │ ├── ProviderRepository.php │ ├── VerificationTokenRepository.php │ ├── UserRepository.php │ └── OauthClientsRepository.php ├── Http │ ├── Middleware │ │ ├── EncryptCookies.php │ │ ├── VerifyCsrfToken.php │ │ ├── TrimStrings.php │ │ ├── Localization.php │ │ ├── TrustProxies.php │ │ ├── RedirectIfUnauthenticated.php │ │ ├── CheckRole.php │ │ ├── RedirectIfAuthenticated.php │ │ ├── Authenticated.php │ │ └── CheckClientRole.php │ ├── Controllers │ │ ├── Controller.php │ │ ├── JwtAuth │ │ │ ├── SessionManager.php │ │ │ └── VerificationController.php │ │ ├── ClientRoleController.php │ │ └── Manage │ │ │ └── OauthClientsController.php │ ├── Resources │ │ ├── UserResource.php │ │ ├── RoleResource.php │ │ └── OauthClientsResource.php │ ├── Requests │ │ ├── UserRoleRequest.php │ │ └── LoginRequest.php │ ├── Services │ │ └── Mailer.php │ └── Kernel.php ├── Exceptions │ ├── SqlException.php │ └── Handler.php ├── Events │ ├── LogoutEvent.php │ └── LoginEvent.php ├── Providers │ ├── BroadcastServiceProvider.php │ ├── EventServiceProvider.php │ ├── AuthServiceProvider.php │ ├── RouteServiceProvider.php │ └── AppServiceProvider.php ├── Services │ ├── Interfaces │ │ └── IAccountService.php │ └── AccountService.php ├── Listeners │ ├── LogLoginListener.php │ ├── RegistrationListener.php │ └── LogoutProvidersListener.php ├── Mail │ └── UserRegistered.php ├── Console │ └── Kernel.php └── swagger.php ├── tests ├── TestCase.php ├── Unit │ └── ExampleTest.php ├── Integration │ ├── RoleTest.php │ ├── ProviderWithoutMiddlewareTest.php │ ├── UserWithoutMiddlewareTest.php │ ├── VerificationTest.php │ ├── RoleWithoutMiddlewareTest.php │ └── ProviderTest.php ├── CreatesApplication.php ├── Utility │ └── UserUtility.php └── Feature │ ├── ProviderWithoutMiddlewareTest.php │ └── LoginWithoutMiddlewareTest.php ├── CONTRIBUTING.md ├── .gitignore ├── routes ├── api.php ├── channels.php ├── console.php ├── idp.php └── web.php ├── .github ├── pull_request_template.md └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── config ├── hashing.php ├── view.php ├── services.php ├── broadcasting.php ├── logging.php ├── filesystems.php ├── queue.php ├── cache.php └── auth.php ├── .travis.yml ├── server.php ├── webpack.mix.js ├── .env.example ├── LICENSE ├── package.json ├── phpunit.xml ├── composer.json ├── artisan ├── readme.md └── CODE_OF_CONDUCT.md /public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite 2 | -------------------------------------------------------------------------------- /resources/views/vendor/l5-swagger/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bootstrap/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /storage/app/public/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !public/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /storage/framework/testing/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !data/ 3 | !.gitignore -------------------------------------------------------------------------------- /storage/framework/cache/data/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /public/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #ffffff; 3 | } 4 | -------------------------------------------------------------------------------- /resources/js/event-bus.js: -------------------------------------------------------------------------------- 1 | window.Vue = require('vue'); 2 | 3 | export const EventBus = new Vue(); -------------------------------------------------------------------------------- /public/images/eye.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZanichelliEditore/laravel-jwt-idp/HEAD/public/images/eye.png -------------------------------------------------------------------------------- /public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZanichelliEditore/laravel-jwt-idp/HEAD/public/images/logo.png -------------------------------------------------------------------------------- /public/mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/js/app.js": "/js/app.js", 3 | "/css/app.css": "/css/app.css" 4 | } 5 | -------------------------------------------------------------------------------- /resources/images/eye.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZanichelliEditore/laravel-jwt-idp/HEAD/resources/images/eye.png -------------------------------------------------------------------------------- /public/images/eye-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZanichelliEditore/laravel-jwt-idp/HEAD/public/images/eye-off.png -------------------------------------------------------------------------------- /public/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZanichelliEditore/laravel-jwt-idp/HEAD/public/images/favicon.png -------------------------------------------------------------------------------- /resources/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZanichelliEditore/laravel-jwt-idp/HEAD/resources/images/logo.png -------------------------------------------------------------------------------- /resources/images/eye-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZanichelliEditore/laravel-jwt-idp/HEAD/resources/images/eye-off.png -------------------------------------------------------------------------------- /resources/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZanichelliEditore/laravel-jwt-idp/HEAD/resources/images/favicon.png -------------------------------------------------------------------------------- /resources/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZanichelliEditore/laravel-jwt-idp/HEAD/resources/assets/images/logo.png -------------------------------------------------------------------------------- /resources/views/admin/users.blade.php: -------------------------------------------------------------------------------- 1 | @extends('admin.home') 2 | 3 | @section('content') 4 | 5 | 6 | 7 | @endsection -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.css linguist-vendored 3 | *.scss linguist-vendored 4 | *.js linguist-vendored 5 | CHANGELOG.md export-ignore 6 | -------------------------------------------------------------------------------- /storage/framework/.gitignore: -------------------------------------------------------------------------------- 1 | config.php 2 | routes.php 3 | schedule-* 4 | compiled.php 5 | services.json 6 | events.scanned.php 7 | routes.scanned.php 8 | down 9 | -------------------------------------------------------------------------------- /resources/views/admin/create-provider.blade.php: -------------------------------------------------------------------------------- 1 | @extends('admin.home') 2 | 3 | @section('content') 4 | 5 | 6 | 7 | @endsection -------------------------------------------------------------------------------- /resources/views/admin/create-role.blade.php: -------------------------------------------------------------------------------- 1 | @extends('admin.home') 2 | 3 | @section('content') 4 | 5 | 6 | 7 | @endsection -------------------------------------------------------------------------------- /app/Constants/Role.php: -------------------------------------------------------------------------------- 1 | 6 | 7 | @endsection -------------------------------------------------------------------------------- /resources/views/auth/logged.blade.php: -------------------------------------------------------------------------------- 1 | @extends('base') 2 | 3 | @section('content') 4 |

{{ auth()->user()->name }} @lang('auth.label-logged')

5 | @endsection -------------------------------------------------------------------------------- /resources/sass/_variables.scss: -------------------------------------------------------------------------------- 1 | 2 | // Body 3 | $body-bg: #f5f8fa; 4 | 5 | // Typography 6 | $font-family-sans-serif: "Raleway", sans-serif; 7 | $font-size-base: 0.9rem; 8 | $line-height-base: 1.6; 9 | -------------------------------------------------------------------------------- /resources/views/admin/oauth-clients.blade.php: -------------------------------------------------------------------------------- 1 | @extends('admin.home') 2 | 3 | @section('content') 4 | 5 | 6 | 7 | 8 | @endsection -------------------------------------------------------------------------------- /resources/assets/sass/_variables.scss: -------------------------------------------------------------------------------- 1 | 2 | // Body 3 | $body-bg: #f5f8fa; 4 | 5 | // Typography 6 | $font-family-sans-serif: "Raleway", sans-serif; 7 | $font-size-base: 0.9rem; 8 | $line-height-base: 1.6; 9 | -------------------------------------------------------------------------------- /resources/views/auth/complete-registration-form.blade.php: -------------------------------------------------------------------------------- 1 | @extends('base') 2 | 3 | @section('content') 4 | 5 | 6 | 7 | @endsection -------------------------------------------------------------------------------- /database/factories/RoleFactory.php: -------------------------------------------------------------------------------- 1 | define(App\Models\Role::class, function () { 6 | return [ 7 | 'name' => Str::random(20), 8 | ]; 9 | }); 10 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | hasOne('App\Models\Client', 'oauth_client_id'); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /resources/assets/sass/app.scss: -------------------------------------------------------------------------------- 1 | 2 | // Fonts 3 | @import url("https://fonts.googleapis.com/css?family=Raleway:300,400,600"); 4 | 5 | // Variables 6 | @import "variables"; 7 | 8 | // Bootstrap 9 | @import '~bootstrap/scss/bootstrap'; 10 | 11 | .navbar-laravel { 12 | background-color: #fff; 13 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04); 14 | } 15 | -------------------------------------------------------------------------------- /resources/sass/app.scss: -------------------------------------------------------------------------------- 1 | 2 | // Fonts 3 | @import url("https://fonts.googleapis.com/css?family=Raleway:300,400,600"); 4 | 5 | // Variables 6 | @import "variables"; 7 | 8 | // Bootstrap 9 | @import '../../node_modules/bootstrap/scss/bootstrap'; 10 | 11 | .navbar-laravel { 12 | background-color: #fff; 13 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04); 14 | } 15 | -------------------------------------------------------------------------------- /database/factories/ProviderFactory.php: -------------------------------------------------------------------------------- 1 | define(App\Models\Provider::class, function () { 6 | return [ 7 | 'domain' => Str::random(20), 8 | 'username' => encrypt(Str::random(20)), 9 | 'password' => encrypt(Str::random(20)), 10 | 'logoutUrl' => Str::random(20), 11 | ]; 12 | }); -------------------------------------------------------------------------------- /resources/views/emails/users/registered.blade.php: -------------------------------------------------------------------------------- 1 | @component('mail::message') 2 | # Verify your e-mail 3 | 4 | {{ $user->name }}, thanks for sign up on Laravel IDP. 5 | 6 | @component('mail::button', ['url' => route('verification-account', ['code' => $verificationCode])]) 7 | Verify e-mail 8 | @endcomponent 9 | 10 | Thanks,
11 | {{ config('app.name') }} 12 | @endcomponent 13 | -------------------------------------------------------------------------------- /app/Repositories/ClientRoleRepository.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Http/Middleware/EncryptCookies.php: -------------------------------------------------------------------------------- 1 | json('GET', '/v1/roles'); 17 | $response->assertStatus(401); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | hasOne('App\Models\OauthClient', 'id'); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Repositories/BaseRepository.php: -------------------------------------------------------------------------------- 1 | delete(); 23 | } 24 | } -------------------------------------------------------------------------------- /routes/api.php: -------------------------------------------------------------------------------- 1 | user = $user; 16 | } 17 | 18 | /** 19 | * Returns the logged-out user 20 | * @return User 21 | */ 22 | public function getUser(){ 23 | return $this->user; 24 | } 25 | 26 | } 27 | 28 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Pull Request Process 2 | 3 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a 4 | build. 5 | 2. Update the README.md with details of changes to the interface, this includes new environment 6 | variables, exposed ports, useful file locations and container parameters. 7 | 3. You may merge the Pull Request in once you have the sign-off of two other developers, or if you 8 | do not have permission to do that, you may request the second reviewer to merge it for you. 9 | -------------------------------------------------------------------------------- /app/Providers/BroadcastServiceProvider.php: -------------------------------------------------------------------------------- 1 | roles); 19 | 20 | return $user; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/CreatesApplication.php: -------------------------------------------------------------------------------- 1 | make(Kernel::class)->bootstrap(); 20 | 21 | Hash::driver('bcrypt')->setRounds(4); 22 | 23 | return $app; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/Http/Controllers/JwtAuth/SessionManager.php: -------------------------------------------------------------------------------- 1 | where('user_id', '=', $userId) 23 | ->delete(); 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /app/Http/Resources/RoleResource.php: -------------------------------------------------------------------------------- 1 | $this->id, 20 | 'roleId' => $this->role->id, 21 | 'roleName' => $this->role->name 22 | ]; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /database/factories/OauthClientFactory.php: -------------------------------------------------------------------------------- 1 | define(OauthClient::class, function (Faker $faker) { 10 | return [ 11 | 'user_id' => null, 12 | 'name' => $faker->name, 13 | 'secret' => Str::random(20), 14 | 'redirect' => 'http://localhost:8081/auth/callback', 15 | 'personal_access_client' => 0, 16 | 'password_client' => 0, 17 | 'revoked' => 0 18 | ]; 19 | }); 20 | -------------------------------------------------------------------------------- /config/hashing.php: -------------------------------------------------------------------------------- 1 | 'bcrypt', 19 | 20 | ]; 21 | -------------------------------------------------------------------------------- /resources/lang/en/pagination.php: -------------------------------------------------------------------------------- 1 | '« Previous', 17 | 'next' => 'Next »', 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /routes/channels.php: -------------------------------------------------------------------------------- 1 | id === (int) $id; 18 | }); 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 7.4 5 | 6 | services: 7 | - mysql 8 | 9 | before_script: 10 | - mysql -e 'CREATE DATABASE IF NOT EXISTS zanichelli_idp;' 11 | - cp .env.example .env 12 | - composer self-update 13 | - composer install --no-interaction 14 | - php artisan key:generate 15 | - php artisan migrate:fresh --seed --no-interaction 16 | - php artisan passport:install 17 | - php artisan jwt:secret --no-interaction 18 | 19 | script: 20 | - vendor/bin/phpunit --coverage-clover=coverage.xml 21 | 22 | after_success: 23 | - bash <(curl -s https://codecov.io/bash) -t ab59f408-dd5d-405d-9695-14dc5aa3b1da 24 | -------------------------------------------------------------------------------- /server.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | $uri = urldecode( 11 | parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) 12 | ); 13 | 14 | // This file allows us to emulate Apache's "mod_rewrite" functionality from the 15 | // built-in PHP web server. This provides a convenient way to test a Laravel 16 | // application without having installed a "real" web server software here. 17 | if ($uri !== '/' && file_exists(__DIR__.'/public'.$uri)) { 18 | return false; 19 | } 20 | 21 | require_once __DIR__.'/public/index.php'; 22 | -------------------------------------------------------------------------------- /webpack.mix.js: -------------------------------------------------------------------------------- 1 | let mix = require('laravel-mix'); 2 | 3 | /* 4 | |-------------------------------------------------------------------------- 5 | | Mix Asset Management 6 | |-------------------------------------------------------------------------- 7 | | 8 | | Mix provides a clean, fluent API for defining some Webpack build steps 9 | | for your Laravel application. By default, we are compiling the Sass 10 | | file for the application as well as bundling up all the JS files. 11 | | 12 | */ 13 | 14 | mix.js('resources/js/app.js', 'public/js') 15 | .sass('resources/sass/app.scss', 'public/css'); 16 | 17 | if (mix.inProduction()) { 18 | mix.version(); 19 | } -------------------------------------------------------------------------------- /app/Http/Resources/OauthClientsResource.php: -------------------------------------------------------------------------------- 1 | $this->id, 19 | "oauth_name" => $this->name, 20 | "roles" => $this->client ? json_decode($this->client->roles) : json_decode("[]") 21 | ]; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/Models/UserRole.php: -------------------------------------------------------------------------------- 1 | belongsTo('App\Models\Role', 'role_id'); 22 | } 23 | 24 | /** 25 | * @return mixed 26 | */ 27 | public function user(){ 28 | return $this->belongsTo('App\Models\User', 'user_id'); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /routes/console.php: -------------------------------------------------------------------------------- 1 | comment(Inspiring::quote()); 19 | })->describe('Display an inspiring quote'); 20 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | Options -MultiViews -Indexes 4 | 5 | 6 | RewriteEngine On 7 | 8 | # Handle Authorization Header 9 | RewriteCond %{HTTP:Authorization} . 10 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 11 | 12 | # Redirect Trailing Slashes If Not A Folder... 13 | RewriteCond %{REQUEST_FILENAME} !-d 14 | RewriteCond %{REQUEST_URI} (.+)/$ 15 | RewriteRule ^ %1 [L,R=301] 16 | 17 | # Handle Front Controller... 18 | RewriteCond %{REQUEST_FILENAME} !-d 19 | RewriteCond %{REQUEST_FILENAME} !-f 20 | RewriteRule ^ index.php [L] 21 | 22 | -------------------------------------------------------------------------------- /app/Http/Middleware/RedirectIfUnauthenticated.php: -------------------------------------------------------------------------------- 1 | check()) { 23 | return $next($request); 24 | } 25 | 26 | return redirect('loginForm'); 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /database/factories/UserRoleFactory.php: -------------------------------------------------------------------------------- 1 | define(App\Models\UserRole::class, function (Faker $faker) { 17 | return [ 18 | 'role_id' => 1, 19 | 'user_id' => 1 20 | ]; 21 | }); 22 | -------------------------------------------------------------------------------- /app/Http/Requests/UserRoleRequest.php: -------------------------------------------------------------------------------- 1 | 'required|integer|exists:roles,id', 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /resources/assets/js/components/ExampleComponent.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 24 | -------------------------------------------------------------------------------- /tests/Utility/UserUtility.php: -------------------------------------------------------------------------------- 1 | 'ADMIN_IDP']); 19 | $user = factory(User::class)->create([ 20 | 'is_verified' => true 21 | ]); 22 | 23 | factory(UserRole::class)->create([ 24 | 'user_id' => $user->id, 25 | 'role_id' => $role->id, 26 | ]); 27 | 28 | return $user; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /app/Repositories/ClientRepositoryInterface.php: -------------------------------------------------------------------------------- 1 | 'required|string|max:255', 28 | 'password' => 'required|string', 29 | ]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /resources/assets/js/app.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * First we will load all of this project's JavaScript dependencies which 4 | * includes Vue and other libraries. It is a great starting point when 5 | * building robust, powerful web applications using Vue and Laravel. 6 | */ 7 | 8 | require('./bootstrap'); 9 | 10 | window.Vue = require('vue'); 11 | 12 | /** 13 | * Next, we will create a fresh Vue application instance and attach it to 14 | * the page. Then, you may begin adding components to this application 15 | * or customize the JavaScript scaffolding to fit your unique needs. 16 | */ 17 | 18 | Vue.component('example-component', require('./components/ExampleComponent.vue')); 19 | 20 | const app = new Vue({ 21 | el: '#app' 22 | }); 23 | -------------------------------------------------------------------------------- /public/css/admin.css: -------------------------------------------------------------------------------- 1 | #nav-bar { 2 | height: 74px; 3 | z-index: 100; 4 | } 5 | 6 | #side-menu { 7 | -webkit-box-shadow: 2px 0px 4px 0px rgba(0,0,0,0.26); 8 | -moz-box-shadow: 2px 0px 4px 0px rgba(0,0,0,0.26); 9 | box-shadow: 2px 0px 4px 0px rgba(0,0,0,0.26); 10 | width: 300px; 11 | margin-top: 73px !important; 12 | background-color: #eeeeee; 13 | } 14 | 15 | #content { 16 | padding-top: 120px; 17 | margin-left: 300px; 18 | } 19 | 20 | .no-text-decoration:hover { 21 | text-decoration: none; 22 | } 23 | 24 | .menu-item:hover { 25 | background-color: #e0e0e0; 26 | } 27 | 28 | body { 29 | position: relative; 30 | min-width: 800px; 31 | overflow-x: hidden; 32 | } 33 | 34 | #content { 35 | /*min-width: 600px;*/ 36 | } -------------------------------------------------------------------------------- /app/Services/Interfaces/IAccountService.php: -------------------------------------------------------------------------------- 1 | [ 16 | 'App\Listeners\LogLoginListener', 17 | ], 18 | 'App\Events\LogoutEvent' => [ 19 | 'App\Listeners\LogoutProvidersListener' 20 | ] 21 | ]; 22 | 23 | /** 24 | * Register any events for your application. 25 | * 26 | * @return void 27 | */ 28 | public function boot() { 29 | parent::boot(); 30 | 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /app/Http/Middleware/CheckRole.php: -------------------------------------------------------------------------------- 1 | check()) { 22 | return redirect('loginForm'); 23 | } 24 | 25 | $user = auth()->user(); 26 | 27 | foreach ($user->roles as $role) { 28 | if ($role->role->name === $mandatoryRole) { 29 | return $next($request); 30 | } 31 | } 32 | 33 | return redirect('authenticated'); 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /database/migrations/2018_04_13_143608_create_password_resets_table.php: -------------------------------------------------------------------------------- 1 | string('email')->index(); 18 | $table->string('token'); 19 | $table->timestamp('created_at')->nullable(); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function down(){ 29 | Schema::dropIfExists('password_resets'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Listeners/LogLoginListener.php: -------------------------------------------------------------------------------- 1 | getUser(); 19 | $ip = $event->getIp(); 20 | 21 | $message = json_encode([ 22 | 'ip' => $ip, 23 | 'userId' => $user->id, 24 | 'action' => 'Login', 25 | 'url' => route('login') 26 | ]); 27 | 28 | Log::info($message); 29 | } 30 | 31 | public function failed(LoginEvent $event, $exception){ 32 | // Quando si verifica un problema con la coda e non viene processato 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /database/migrations/2018_04_13_143303_role.php: -------------------------------------------------------------------------------- 1 | engine = 'InnoDB'; 17 | $table->charset = 'utf8'; 18 | $table->collation = 'utf8_unicode_ci'; 19 | 20 | $table->increments('id'); 21 | $table->string('name', 20)->unique(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down(){ 31 | Schema::dropIfExists('roles'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /resources/lang/en/passwords.php: -------------------------------------------------------------------------------- 1 | 'Passwords must be at least six characters and match the confirmation.', 17 | 'reset' => 'Your password has been reset!', 18 | 'sent' => 'We have e-mailed your password reset link!', 19 | 'token' => 'This password reset token is invalid.', 20 | 'user' => "We can't find a user with that e-mail address.", 21 | 22 | ]; 23 | -------------------------------------------------------------------------------- /database/migrations/2019_03_05_152925_create_providers_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 17 | $table->string('domain')->unique(); 18 | $table->string('username'); 19 | $table->string('password'); 20 | $table->string('logoutUrl'); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | * 27 | * @return void 28 | */ 29 | public function down() { 30 | Schema::dropIfExists('providers'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/Integration/ProviderWithoutMiddlewareTest.php: -------------------------------------------------------------------------------- 1 | create(); 21 | $response = $this->json('GET', '/v1/providers'); 22 | $response->assertStatus(200) 23 | ->assertJsonStructure([ 24 | [ 25 | 'id', 26 | 'domain', 27 | 'username', 28 | 'password', 29 | 'logoutUrl' 30 | ] 31 | ]); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Repositories/ClientRepository.php: -------------------------------------------------------------------------------- 1 | update($data); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/Events/LoginEvent.php: -------------------------------------------------------------------------------- 1 | user = $user; 28 | $this->ip = $ip; 29 | } 30 | 31 | /** 32 | * Returns the current logged user 33 | * @return User 34 | */ 35 | public function getUser(){ 36 | return $this->user; 37 | } 38 | 39 | /** 40 | * Returns the request of the user 41 | * @return string 42 | */ 43 | public function getIp(){ 44 | return $this->ip; 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /app/Http/Middleware/RedirectIfAuthenticated.php: -------------------------------------------------------------------------------- 1 | check()) { 21 | return $next($request); 22 | } 23 | 24 | $redirectUrl = $request->input('redirect'); 25 | 26 | if (empty($redirectUrl)) { 27 | return redirect('authenticated'); 28 | } 29 | $token = auth()->getToken(); 30 | $url = $redirectUrl . '?token=' . $token; 31 | 32 | return redirect()->away($url); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/Repositories/RepositoryInterface.php: -------------------------------------------------------------------------------- 1 | unique('email'); 18 | $table->string('surname')->nullable()->change(); 19 | }); 20 | } 21 | 22 | /** 23 | * Reverse the migrations. 24 | * 25 | * @return void 26 | */ 27 | public function down() 28 | { 29 | Schema::table('users', function (Blueprint $table) { 30 | $table->dropUnique('users_email_unique'); 31 | $table->string('surname')->nullable(false)->change(); 32 | }); 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /database/factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | define(App\Models\User::class, function (Faker $faker) { 18 | return [ 19 | 'name' => $faker->name, 20 | 'surname' => $faker->name, 21 | 'email' => $faker->unique()->safeEmail, 22 | 'password' => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret 23 | 'remember_token' => Str::random(10), 24 | 'is_verified' => false, 25 | ]; 26 | }); 27 | -------------------------------------------------------------------------------- /database/migrations/2019_10_03_094707_create_table_clients.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->string('oauth_client_id')->foreign('oauth_client_id')->references('id')->on('oauth_clients')->onDelete('cascade'); 19 | $table->string('roles')->default('[]'); 20 | $table->timestamps(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | * 27 | * @return void 28 | */ 29 | public function down() 30 | { 31 | Schema::dropIfExists('clients'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /resources/images/eye-slash-solid.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/Listeners/RegistrationListener.php: -------------------------------------------------------------------------------- 1 | getUser(); 32 | $verificationCode = $event->getVerificationCode(); 33 | 34 | Mail::to($user)->send(new UserRegistered($user, $verificationCode)); 35 | } 36 | 37 | public function failed(RegistrationEvent $event, $exception){ 38 | // error queue 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /app/Mail/UserRegistered.php: -------------------------------------------------------------------------------- 1 | user = $user; 24 | $this->verificationCode = $verificationCode; 25 | } 26 | 27 | /** 28 | * Build the message. 29 | * 30 | * @return $this 31 | */ 32 | public function build(){ 33 | return $this->markdown('emails.users.registered')->with([ 34 | 'user' => $this->user, 35 | 'verificationCode' => $this->verificationCode 36 | ]); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /public/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /app/Providers/AuthServiceProvider.php: -------------------------------------------------------------------------------- 1 | 'App\Policies\ModelPolicy', 18 | ]; 19 | 20 | /** 21 | * Register any authentication / authorization services. 22 | * 23 | * @return void 24 | */ 25 | public function boot() { 26 | 27 | $this->registerPolicies(); 28 | 29 | Passport::routes(); 30 | 31 | Passport::tokensExpireIn(now()->addMinutes(2)); 32 | 33 | Passport::tokensCan([ 34 | 'manage-user' => 'Create and manage IDP users', 35 | 'manage-idp' => 'Create and manage providers', 36 | ]); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/Console/Kernel.php: -------------------------------------------------------------------------------- 1 | command('inspire') 28 | // ->hourly(); 29 | } 30 | 31 | /** 32 | * Register the commands for the application. 33 | * 34 | * @return void 35 | */ 36 | protected function commands() 37 | { 38 | $this->load(__DIR__.'/Commands'); 39 | 40 | require base_path('routes/console.php'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /database/migrations/2019_08_02_090547_delete_user_roles_on_delete_role.php: -------------------------------------------------------------------------------- 1 | dropForeign('user_roles_role_id_foreign'); 18 | $table->foreign('role_id')->references('id')->on('roles')->onDelete('cascade'); 19 | }); 20 | } 21 | 22 | /** 23 | * Reverse the migrations. 24 | * 25 | * @return void 26 | */ 27 | public function down() 28 | { 29 | Schema::table('user_roles', function (Blueprint $table) { 30 | $table->dropForeign('user_roles_role_id_foreign'); 31 | $table->foreign('role_id')->references('id')->on('roles'); 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /database/migrations/2019_08_29_143421_create_verification_tokens_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->string('token')->unique(); 19 | $table->unsignedInteger('user_id'); 20 | $table->boolean('is_valid')->default(true); 21 | $table->timestamps(); 22 | 23 | $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | * 30 | * @return void 31 | */ 32 | public function down() 33 | { 34 | Schema::dropIfExists('verification_tokens'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /database/migrations/2018_04_13_143711_create_user_verification_table.php: -------------------------------------------------------------------------------- 1 | engine = 'InnoDB'; 18 | $table->charset = 'utf8'; 19 | $table->collation = 'utf8_unicode_ci'; 20 | 21 | $table->increments('id'); 22 | $table->unsignedInteger('user_id'); 23 | $table->string('verification_code'); 24 | 25 | $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); 26 | }); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | * 32 | * @return void 33 | */ 34 | public function down(){ 35 | Schema::dropIfExists('user_verifications'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Repositories/RoleRepository.php: -------------------------------------------------------------------------------- 1 | update($data); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /database/migrations/2018_04_13_143446_user_role.php: -------------------------------------------------------------------------------- 1 | engine = 'InnoDB'; 19 | $table->charset = 'utf8'; 20 | $table->collation = 'utf8_unicode_ci'; 21 | 22 | $table->increments('id'); 23 | $table->unsignedInteger('user_id'); 24 | $table->unsignedInteger('role_id'); 25 | 26 | $table->foreign('user_id')->references('id')->on('users'); 27 | $table->foreign('role_id')->references('id')->on('roles'); 28 | }); 29 | } 30 | 31 | /** 32 | * Reverse the migrations. 33 | * 34 | * @return void 35 | */ 36 | public function down(){ 37 | Schema::dropIfExists('user_roles'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /database/migrations/2018_11_09_090028_create_sessions_table.php: -------------------------------------------------------------------------------- 1 | string('id')->unique(); 18 | $table->unsignedInteger('user_id')->nullable(); 19 | $table->string('ip_address', 45)->nullable(); 20 | $table->text('user_agent')->nullable(); 21 | $table->text('payload'); 22 | $table->integer('last_activity'); 23 | 24 | $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); 25 | }); 26 | } 27 | 28 | /** 29 | * Reverse the migrations. 30 | * 31 | * @return void 32 | */ 33 | public function down() 34 | { 35 | Schema::dropIfExists('sessions'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/swagger.php: -------------------------------------------------------------------------------- 1 | [ 17 | resource_path('views'), 18 | ], 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Compiled View Path 23 | |-------------------------------------------------------------------------- 24 | | 25 | | This option determines where all the compiled Blade templates will be 26 | | stored for your application. Typically, this is within the storage 27 | | directory. However, as usual, you are free to change this value. 28 | | 29 | */ 30 | 31 | 'compiled' => realpath(storage_path('framework/views')), 32 | 33 | ]; 34 | -------------------------------------------------------------------------------- /tests/Integration/UserWithoutMiddlewareTest.php: -------------------------------------------------------------------------------- 1 | create([ 22 | 'is_verified' => true 23 | ]); 24 | $response = $this->json('GET', '/v1/users/' . $user->id); 25 | $response->assertStatus(200); 26 | } 27 | 28 | 29 | /** 30 | * Find not exists user 31 | * @test 32 | * @return void 33 | */ 34 | public function notFoundUserTest() 35 | { 36 | $user = factory(User::class)->create([ 37 | 'is_verified' => true 38 | ]); 39 | User::destroy($user->id); 40 | $response = $this->json('GET', '/v1/users/' . $user->id); 41 | $response->assertStatus(404); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Zanichelli Editore S.p.A. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /config/services.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'domain' => env('MAILGUN_DOMAIN'), 19 | 'secret' => env('MAILGUN_SECRET'), 20 | ], 21 | 22 | 'ses' => [ 23 | 'key' => env('SES_KEY'), 24 | 'secret' => env('SES_SECRET'), 25 | 'region' => 'us-east-1', 26 | ], 27 | 28 | 'sparkpost' => [ 29 | 'secret' => env('SPARKPOST_SECRET'), 30 | ], 31 | 32 | 'stripe' => [ 33 | 'model' => App\Models\User::class, 34 | 'key' => env('STRIPE_KEY'), 35 | 'secret' => env('STRIPE_SECRET'), 36 | ], 37 | 38 | ]; 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "npm run development", 5 | "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", 6 | "watch": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", 7 | "watch-poll": "npm run watch -- --watch-poll", 8 | "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js", 9 | "prod": "npm run production", 10 | "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js" 11 | }, 12 | "devDependencies": { 13 | "axios": "^0.19", 14 | "bootstrap": "^4.0.0", 15 | "popper.js": "^1.12", 16 | "cross-env": "^5.1", 17 | "jquery": "^3.2", 18 | "laravel-mix": "^2.0", 19 | "lodash": "^4.17.4", 20 | "vue": "^2.5.7" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /database/migrations/2018_04_13_143152_user.php: -------------------------------------------------------------------------------- 1 | nullable(); 11 | * 12 | * @return void 13 | */ 14 | public function up() 15 | { 16 | 17 | Schema::create('users', function (Blueprint $table) { 18 | 19 | $table->engine = 'InnoDB'; 20 | $table->charset = 'utf8'; 21 | $table->collation = 'utf8_unicode_ci'; 22 | 23 | $table->increments('id'); 24 | $table->string('email', 60)->nullable(); 25 | $table->string('password')->nullable(); 26 | $table->boolean('is_verified')->default(false); 27 | $table->string('name', 50); 28 | $table->string('surname', 50); 29 | $table->rememberToken(); 30 | $table->timestamp('created_at')->useCurrent(); 31 | $table->timestamp('updated_at')->nullable(); 32 | }); 33 | } 34 | 35 | /** 36 | * Reverse the migrations. 37 | * 38 | * @return void 39 | */ 40 | public function down() 41 | { 42 | Schema::dropIfExists('users'); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/Http/Controllers/ClientRoleController.php: -------------------------------------------------------------------------------- 1 | clientRoleRepository = $clientRoleRepository; 15 | } 16 | 17 | /** 18 | * @OA\Get( 19 | * path="/v1/client-roles", 20 | * summary="list of client roles", 21 | * description="Returns the entire list of client roles", 22 | * operationId="all", 23 | * tags={"ClientRoles"}, 24 | * security={{"web":{}}}, 25 | * @OA\Response( 26 | * response=200, 27 | * description="Operation successful", 28 | * @OA\MediaType( 29 | * mediaType="application/json", 30 | * ) 31 | * ), 32 | * @OA\Response( 33 | * response=401, 34 | * description="Unauthorized", 35 | * @OA\MediaType( 36 | * mediaType="application/json", 37 | * ) 38 | * ) 39 | * ) 40 | */ 41 | public function all(){ 42 | 43 | return $this->clientRoleRepository->all(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/Exceptions/Handler.php: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | ./tests/Feature 14 | 15 | 16 | ./tests/Integration 17 | 18 | 19 | 20 | ./tests/Unit 21 | 22 | 23 | 24 | 25 | ./app 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/Models/User.php: -------------------------------------------------------------------------------- 1 | hasMany('App\Models\UserRole', 'user_id'); 37 | } 38 | 39 | 40 | /** 41 | * Get the identifier that will be stored in the subject claim of the JWT. 42 | * 43 | * @return mixed 44 | */ 45 | public function getJWTIdentifier() 46 | { 47 | return $this->getKey(); 48 | } 49 | 50 | /** 51 | * Return a key value array, containing any custom claims to be added to the JWT. 52 | * 53 | * @return array 54 | */ 55 | public function getJWTCustomClaims() 56 | { 57 | return array(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /resources/lang/it/auth.php: -------------------------------------------------------------------------------- 1 | 'Grazie per esserti registrato! Controlla la tua e-mail per completare la registrazione.', 6 | 'err-verification' => 'Utente non verificato. Accedi alla tua casella di posta per verificare il tuo account.', 7 | 'err-login' => 'E-mail o password errati.', 8 | 'err-jwt' => 'Errore durante il login. Riprovare più tardi', 9 | 'err-fields' => 'Rimepire correttamente tutti i campi obbligatori', 10 | 11 | 'label-email' => 'Indirizzo e-mail', 12 | 'label-enter-email' => 'Inserisci la tua e-mail', 13 | 'label-username' => 'Username', 14 | 'label-enter-username' => 'Inserisci il tuo username', 15 | 'label-enter-password' => 'Inserisci una password sicura', 16 | 'label-confirm-password' => 'Conferma la password', 17 | 'label-repeat-password' => 'Ripeti la password', 18 | 'label-name' => 'Nome', 19 | 'label-surname' => 'Cognome', 20 | 'label-enter-name' => 'Inserisci il nome', 21 | 'label-enter-surname' => 'Inserisci il cognome', 22 | 'label-sign-up' => 'Registrati', 23 | 'label-login' => 'Accedi', 24 | 'label-logout' => 'Esci', 25 | 'label-logged' => 'sei autenticato', 26 | 27 | 'user-error' => 'Utente non trovato', 28 | 'token-expired' => 'Token scaduto', 29 | 'token-invalid' => 'Token invalido', 30 | 'token-error' => 'Token invalido', 31 | 'token-absent' => 'Token assente' 32 | ]; -------------------------------------------------------------------------------- /app/Repositories/UserRoleRepository.php: -------------------------------------------------------------------------------- 1 | get(); 40 | } 41 | 42 | /** 43 | * Returns a list of all @see user-roles. 44 | * 45 | * @return array 46 | */ 47 | public function all() 48 | { 49 | return UserRole::all(); 50 | } 51 | 52 | /** 53 | * Updates a user-role by id. 54 | * 55 | * @param array $data 56 | * @param int $id 57 | * @return int 58 | */ 59 | public function update(int $id, array $data) 60 | { 61 | return UserRole::find($id)->update($data); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/Feature/ProviderWithoutMiddlewareTest.php: -------------------------------------------------------------------------------- 1 | json('POST', 'v2/login', [ 27 | 'username' => $user->email, 28 | 'password' => 'secret' 29 | ]); 30 | 31 | $cookie = ['token' => json_decode($response->getContent())->token]; 32 | $body = [ 33 | 'domain' => Str::random(85), 34 | 'username' => 'user', 35 | 'password' => 'secret', 36 | 'logoutUrl' => 'valid' 37 | ]; 38 | 39 | $mock = Mockery::mock(ProviderRepository::class)->makePartial() 40 | ->shouldReceive(['create'=> null]) 41 | ->once() 42 | ->getMock(); 43 | $this->app->instance('App\Repositories\ProviderRepository', $mock); 44 | 45 | $response = $this->json('POST', '/admin/providers', $body, $cookie); 46 | $response->assertStatus(500); 47 | } 48 | } -------------------------------------------------------------------------------- /app/Repositories/ProviderRepository.php: -------------------------------------------------------------------------------- 1 | $data['domain'], 23 | 'username' => encrypt($data['username']), 24 | 'password' => encrypt($data['password']), 25 | 'logoutUrl' => $logoutUrl 26 | ]); 27 | } 28 | 29 | /** 30 | * Finds a @see Provider by id. 31 | * 32 | * @param integer $id 33 | * @return Provider 34 | */ 35 | public function find(int $id) 36 | { 37 | return Provider::find($id); 38 | } 39 | 40 | /** 41 | * Returns a list of all @see Provider 42 | * 43 | * @return array 44 | */ 45 | public function all() 46 | { 47 | return Provider::all(); 48 | } 49 | 50 | /** 51 | * Updates a provider by id. 52 | * 53 | * @param array $data 54 | * @param int $id 55 | * @return int 56 | */ 57 | public function update(int $id, array $data) 58 | { 59 | return Provider::where('id', $id)->update($data); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/Repositories/VerificationTokenRepository.php: -------------------------------------------------------------------------------- 1 | update($data); 50 | } 51 | 52 | /** 53 | * Find Verification token by token param 54 | * 55 | * @param string $token 56 | * @return VerificationToken 57 | */ 58 | public function retrieveByToken(string $token) 59 | { 60 | return VerificationToken::where('token', $token)->first(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/Http/Middleware/Authenticated.php: -------------------------------------------------------------------------------- 1 | checkOrFail()) { 27 | return response()->json([ 28 | 'message' => __('auth.user-error') 29 | ], 404); 30 | } 31 | 32 | } catch (TokenExpiredException $e) { 33 | return response()->json([ 34 | 'message' => __('auth.token-expired') 35 | ], 403); 36 | } catch (TokenBlacklistedException $e) { 37 | return response()->json([ 38 | 'message' => __('auth.token-invalid') 39 | ], 403); 40 | } catch (TokenInvalidException $e) { 41 | return response()->json([ 42 | 'message' => __('auth.token-error') 43 | ], 400); 44 | } catch (JWTException $e) { 45 | return response()->json([ 46 | 'message' => __('auth.token-absent') 47 | ], 400); 48 | } 49 | 50 | return $next($request); 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "laravel/laravel", 3 | "description": "The Laravel Framework.", 4 | "keywords": [ 5 | "framework", 6 | "laravel" 7 | ], 8 | "license": "MIT", 9 | "type": "project", 10 | "require": { 11 | "darkaonline/l5-swagger": "^6.0", 12 | "doctrine/dbal": "^2.9", 13 | "fideloper/proxy": "^4.0", 14 | "laravel/framework": "^6.0", 15 | "laravel/passport": "^7.0", 16 | "laravel/tinker": "^1.0", 17 | "predis/predis": "^1.1", 18 | "tymon/jwt-auth": "1.*", 19 | "zircote/swagger-php": "3.*" 20 | }, 21 | "require-dev": { 22 | "filp/whoops": "^2.0", 23 | "fzaninotto/faker": "^1.4", 24 | "mockery/mockery": "^1.0", 25 | "nunomaduro/collision": "^2.0", 26 | "phpunit/phpunit": "^7.0" 27 | }, 28 | "autoload": { 29 | "classmap": [ 30 | "database/seeds", 31 | "database/factories" 32 | ], 33 | "psr-4": { 34 | "App\\": "app/" 35 | } 36 | }, 37 | "autoload-dev": { 38 | "psr-4": { 39 | "Tests\\": "tests/" 40 | } 41 | }, 42 | "extra": { 43 | "laravel": {} 44 | }, 45 | "scripts": { 46 | "post-root-package-install": "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"", 47 | "post-create-project-cmd": "@php artisan key:generate", 48 | "post-autoload-dump": [ 49 | "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", 50 | "@php artisan package:discover" 51 | ] 52 | }, 53 | "config": { 54 | "preferred-install": "dist", 55 | "sort-packages": true, 56 | "optimize-autoloader": true 57 | }, 58 | "minimum-stability": "dev", 59 | "prefer-stable": true 60 | } 61 | -------------------------------------------------------------------------------- /resources/views/base.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Zanichelli 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 |
23 |
24 | 38 |
39 | 40 | @yield('content') 41 |
42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/Repositories/UserRepository.php: -------------------------------------------------------------------------------- 1 | $data['email'], 20 | 'name' => $data['name'], 21 | 'surname' => isset($data['surname']) ? $data['surname'] : null, 22 | 'password' => isset($data['password']) ? bcrypt($data['password']) : null, 23 | 'is_verified' => false, 24 | ]); 25 | } 26 | 27 | /** 28 | * Finds a @see User by id. 29 | * 30 | * @param integer $id 31 | * @return User 32 | */ 33 | public function find(int $id) 34 | { 35 | return User::find($id); 36 | } 37 | 38 | /** 39 | * Returns a paginated list of all @see User. 40 | * 41 | * @param null $query 42 | * @return array 43 | */ 44 | public function all($query = null) 45 | { 46 | if (empty($query)) { 47 | return User::paginate(10); 48 | } 49 | 50 | return User::where('email', 'like', '%' . $query . '%') 51 | ->paginate(10); 52 | } 53 | 54 | /** 55 | * Updates a @see User. 56 | * 57 | * @param array $data 58 | * @param int $id 59 | * @return int 60 | */ 61 | public function update(int $id, array $data) 62 | { 63 | return User::where('id', $id)->update($data); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | singleton( 30 | Illuminate\Contracts\Http\Kernel::class, 31 | App\Http\Kernel::class 32 | ); 33 | 34 | $app->singleton( 35 | Illuminate\Contracts\Console\Kernel::class, 36 | App\Console\Kernel::class 37 | ); 38 | 39 | $app->singleton( 40 | Illuminate\Contracts\Debug\ExceptionHandler::class, 41 | App\Exceptions\Handler::class 42 | ); 43 | 44 | /* 45 | |-------------------------------------------------------------------------- 46 | | Return The Application 47 | |-------------------------------------------------------------------------- 48 | | 49 | | This script returns the application instance. The instance is given to 50 | | the calling script so we can separate the building of the instances 51 | | from the actual running of the application and sending responses. 52 | | 53 | */ 54 | 55 | return $app; 56 | -------------------------------------------------------------------------------- /config/broadcasting.php: -------------------------------------------------------------------------------- 1 | env('BROADCAST_DRIVER', 'null'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Broadcast Connections 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may define all of the broadcast connections that will be used 26 | | to broadcast events to other systems or over websockets. Samples of 27 | | each available type of connection are provided inside this array. 28 | | 29 | */ 30 | 31 | 'connections' => [ 32 | 33 | 'pusher' => [ 34 | 'driver' => 'pusher', 35 | 'key' => env('PUSHER_APP_KEY'), 36 | 'secret' => env('PUSHER_APP_SECRET'), 37 | 'app_id' => env('PUSHER_APP_ID'), 38 | 'options' => [ 39 | 'cluster' => env('PUSHER_APP_CLUSTER'), 40 | 'encrypted' => true, 41 | ], 42 | ], 43 | 44 | 'redis' => [ 45 | 'driver' => 'redis', 46 | 'connection' => 'default', 47 | ], 48 | 49 | 'log' => [ 50 | 'driver' => 'log', 51 | ], 52 | 53 | 'null' => [ 54 | 'driver' => 'null', 55 | ], 56 | 57 | ], 58 | 59 | ]; 60 | -------------------------------------------------------------------------------- /artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | make(Illuminate\Contracts\Console\Kernel::class); 34 | 35 | $status = $kernel->handle( 36 | $input = new Symfony\Component\Console\Input\ArgvInput, 37 | new Symfony\Component\Console\Output\ConsoleOutput 38 | ); 39 | 40 | /* 41 | |-------------------------------------------------------------------------- 42 | | Shutdown The Application 43 | |-------------------------------------------------------------------------- 44 | | 45 | | Once Artisan has finished running, we will fire off the shutdown events 46 | | so that any final work may be done by the application before we shut 47 | | down the process. This is the last thing to happen to the request. 48 | | 49 | */ 50 | 51 | $kernel->terminate($input, $status); 52 | 53 | exit($status); 54 | -------------------------------------------------------------------------------- /app/Listeners/LogoutProvidersListener.php: -------------------------------------------------------------------------------- 1 | providerRepository = $providerRepository; 32 | } 33 | 34 | /** 35 | * Logout the user from every registered providers. 36 | * 37 | * @param LogoutEvent $event 38 | */ 39 | public function handle(LogoutEvent $event) { 40 | 41 | $user = $event->getUser(); 42 | 43 | $providers = $this->providerRepository->all(); 44 | 45 | if (!count($providers)) { 46 | return; 47 | } 48 | 49 | $client = new Client(); 50 | 51 | foreach ($providers as $provider) { 52 | try { 53 | $client->get($provider->logoutUrl, [ 54 | 'query' => [ 55 | 'id' => $user->id 56 | ], 57 | 'auth' => [ 58 | decrypt($provider->username), 59 | decrypt($provider->password) 60 | ] 61 | ]); 62 | } catch (RequestException $e) { 63 | Log::error($e->getMessage()); 64 | } 65 | } 66 | 67 | } 68 | 69 | } 70 | 71 | -------------------------------------------------------------------------------- /resources/lang/en/auth.php: -------------------------------------------------------------------------------- 1 | 'These credentials do not match our records.', 17 | 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', 18 | 19 | 'registration-success' => 'Thanks for signing up! Please check your email to complete your registration.', 20 | 'err-verification' => 'User not verified. Check your e-mail box to verify your account.', 21 | 'err-login' => 'Wrong e-mail or password.', 22 | 'err-jwt' => 'Error during login. Try later.', 23 | 'err-fields' => 'Fill correctly all required fields', 24 | 25 | 'label-email' => 'E-mail address', 26 | 'label-enter-email' => 'Enter e-mail', 27 | 'label-username' => 'Username', 28 | 'label-enter-username' => 'Enter username', 29 | 'label-enter-password' => 'Enter a secure password', 30 | 'label-confirm-password' => 'Confirm password', 31 | 'label-repeat-password' => 'Repeat password', 32 | 'label-name' => 'Name', 33 | 'label-surname' => 'Surname', 34 | 'label-enter-name' => 'Enter name', 35 | 'label-enter-surname' => 'Enter surname', 36 | 'label-sign-up' => 'Sign up', 37 | 'label-login' => 'Login', 38 | 'label-logout' => 'Logout', 39 | 'label-logged' => 'is logged', 40 | 41 | 'user-error' => 'User not found', 42 | 'token-expired' => 'Token expired', 43 | 'token-invalid' => 'Token blacklisted', 44 | 'token-error' => 'Invalid token', 45 | 'token-absent' => 'Token absent' 46 | ]; 47 | -------------------------------------------------------------------------------- /resources/js/bootstrap.js: -------------------------------------------------------------------------------- 1 | 2 | window._ = require('lodash'); 3 | window.Popper = require('popper.js').default; 4 | 5 | /** 6 | * We'll load jQuery and the Bootstrap jQuery plugin which provides support 7 | * for JavaScript based Bootstrap features such as modals and tabs. This 8 | * code may be modified to fit the specific needs of your application. 9 | */ 10 | 11 | try { 12 | window.$ = window.jQuery = require('jquery'); 13 | 14 | require('bootstrap'); 15 | } catch (e) {} 16 | 17 | /** 18 | * We'll load the axios HTTP library which allows us to easily issue requests 19 | * to our Laravel back-end. This library automatically handles sending the 20 | * CSRF token as a header based on the value of the "XSRF" token cookie. 21 | */ 22 | 23 | window.axios = require('axios'); 24 | 25 | window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; 26 | 27 | /** 28 | * Next we will register the CSRF Token as a common header with Axios so that 29 | * all outgoing HTTP requests automatically have it attached. This is just 30 | * a simple convenience so we don't have to attach every token manually. 31 | */ 32 | 33 | let token = document.head.querySelector('meta[name="csrf-token"]'); 34 | 35 | if (token) { 36 | window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content; 37 | } else { 38 | console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token'); 39 | } 40 | 41 | /** 42 | * Echo exposes an expressive API for subscribing to channels and listening 43 | * for events that are broadcast by Laravel. Echo and event broadcasting 44 | * allows your team to easily build robust real-time web applications. 45 | */ 46 | 47 | // import Echo from 'laravel-echo' 48 | 49 | // window.Pusher = require('pusher-js'); 50 | 51 | // window.Echo = new Echo({ 52 | // broadcaster: 'pusher', 53 | // key: process.env.MIX_PUSHER_APP_KEY, 54 | // cluster: process.env.MIX_PUSHER_APP_CLUSTER, 55 | // encrypted: true 56 | // }); 57 | -------------------------------------------------------------------------------- /resources/assets/js/bootstrap.js: -------------------------------------------------------------------------------- 1 | 2 | window._ = require('lodash'); 3 | window.Popper = require('popper.js').default; 4 | 5 | /** 6 | * We'll load jQuery and the Bootstrap jQuery plugin which provides support 7 | * for JavaScript based Bootstrap features such as modals and tabs. This 8 | * code may be modified to fit the specific needs of your application. 9 | */ 10 | 11 | try { 12 | window.$ = window.jQuery = require('jquery'); 13 | 14 | require('bootstrap'); 15 | } catch (e) {} 16 | 17 | /** 18 | * We'll load the axios HTTP library which allows us to easily issue requests 19 | * to our Laravel back-end. This library automatically handles sending the 20 | * CSRF token as a header based on the value of the "XSRF" token cookie. 21 | */ 22 | 23 | window.axios = require('axios'); 24 | 25 | window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; 26 | 27 | /** 28 | * Next we will register the CSRF Token as a common header with Axios so that 29 | * all outgoing HTTP requests automatically have it attached. This is just 30 | * a simple convenience so we don't have to attach every token manually. 31 | */ 32 | 33 | let token = document.head.querySelector('meta[name="csrf-token"]'); 34 | 35 | if (token) { 36 | window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content; 37 | } else { 38 | console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token'); 39 | } 40 | 41 | /** 42 | * Echo exposes an expressive API for subscribing to channels and listening 43 | * for events that are broadcast by Laravel. Echo and event broadcasting 44 | * allows your team to easily build robust real-time web applications. 45 | */ 46 | 47 | // import Echo from 'laravel-echo' 48 | 49 | // window.Pusher = require('pusher-js'); 50 | 51 | // window.Echo = new Echo({ 52 | // broadcaster: 'pusher', 53 | // key: process.env.MIX_PUSHER_APP_KEY, 54 | // cluster: process.env.MIX_PUSHER_APP_CLUSTER, 55 | // encrypted: true 56 | // }); 57 | -------------------------------------------------------------------------------- /app/Http/Services/Mailer.php: -------------------------------------------------------------------------------- 1 | retrieveToken(); 22 | 23 | $client = new Client; 24 | $response = $client->request('POST', env('URL_SENDY') . '/api/v1/emails', 25 | [ 26 | 'headers' => [ 27 | 'Accept' => 'application/json', 28 | 'Content-Type' => 'application/json', 29 | 'Authorization' => 'Bearer ' . $token, 30 | ], 31 | 'body' => json_encode( 32 | [ 33 | 'to' => $to, 34 | 'from' => env('EMAIL_FROM'), 35 | 'subject' => $subject, 36 | 'body' => $body 37 | 38 | ] 39 | ) 40 | ]); 41 | return json_decode((string) $response->getBody(), true)['message']; 42 | } 43 | 44 | /** 45 | * Function to retrieve authenticated token 46 | * 47 | * @return string token from oauth route 48 | */ 49 | public function retrieveToken() 50 | { 51 | $client = new Client; 52 | 53 | $response = $client->post(env('URL_SENDY') . '/oauth/token', [ 54 | 'form_params' => [ 55 | 'grant_type' => 'client_credentials', 56 | 'client_id' => env('CLIENT_ID_SENDY'), 57 | 'client_secret' => env('CLIENT_SECRET_SENDY'), 58 | 'scope' => '', 59 | ], 60 | ]); 61 | 62 | return json_decode((string) $response->getBody(), true)['access_token']; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | define('LARAVEL_START', microtime(true)); 11 | 12 | /* 13 | |-------------------------------------------------------------------------- 14 | | Register The Auto Loader 15 | |-------------------------------------------------------------------------- 16 | | 17 | | Composer provides a convenient, automatically generated class loader for 18 | | our application. We just need to utilize it! We'll simply require it 19 | | into the script here so that we don't have to worry about manual 20 | | loading any of our classes later on. It feels great to relax. 21 | | 22 | */ 23 | 24 | require __DIR__.'/../vendor/autoload.php'; 25 | 26 | /* 27 | |-------------------------------------------------------------------------- 28 | | Turn On The Lights 29 | |-------------------------------------------------------------------------- 30 | | 31 | | We need to illuminate PHP development, so let us turn on the lights. 32 | | This bootstraps the framework and gets it ready for use, then it 33 | | will load up this application so that we can run it and send 34 | | the responses back to the browser and delight our users. 35 | | 36 | */ 37 | 38 | $app = require_once __DIR__.'/../bootstrap/app.php'; 39 | 40 | /* 41 | |-------------------------------------------------------------------------- 42 | | Run The Application 43 | |-------------------------------------------------------------------------- 44 | | 45 | | Once we have the application, we can handle the incoming request 46 | | through the kernel, and send the associated response back to 47 | | the client's browser allowing them to enjoy the creative 48 | | and wonderful application we have prepared for them. 49 | | 50 | */ 51 | 52 | $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); 53 | 54 | $response = $kernel->handle( 55 | $request = Illuminate\Http\Request::capture() 56 | ); 57 | 58 | $response->send(); 59 | 60 | $kernel->terminate($request, $response); 61 | -------------------------------------------------------------------------------- /resources/views/mail/complete-registration.blade.php: -------------------------------------------------------------------------------- 1 | @extends('mail.base') 2 | @section('title', 'Completa la registrazione') 3 | 4 | @section('body') 5 |
6 |

Completa la registrazione

7 |

Hai ricevuto questa e-mail in seguito alla registrazione da parte di un collaboratore Zanichelli.

8 | 9 |

Di seguito trovi i tuoi dati.

10 |
11 |
12 |
13 |

Username

14 |
15 |
16 |

{{ $user->email }}

17 |
18 |
19 |
20 |
21 |

Nome

22 |
23 |
24 |

{{ $user->name }}

25 |
26 |
27 |
28 |
29 |

Cognome

30 |
31 |
32 |

{{ $user->surname }}

33 |
34 |
35 |
36 | 37 |

Se non hai richiesto tu la registrazione ignora questa e-mail, altrimenti clicca sul bottone sottostante per completare la registrazione.

38 | 39 | Completa la registrazione 41 |

Se il bottone non dovesse funzionare, copia e incolla sul tuo browser il seguente link:

42 | {{route('complete-registration', ['token' => $token])}} 43 |
44 | @endsection -------------------------------------------------------------------------------- /app/Http/Middleware/CheckClientRole.php: -------------------------------------------------------------------------------- 1 | server = $server; 37 | $this->clientRepository = $clientRepository; 38 | } 39 | /** 40 | * Handle an incoming request. 41 | * 42 | * @param \Illuminate\Http\Request $request 43 | * @param \Closure $next 44 | * @param string $roles 45 | * @return mixed 46 | */ 47 | public function handle($request, Closure $next, string $roles) 48 | { 49 | $psr = (new DiactorosFactory)->createRequest($request); 50 | 51 | try { 52 | $psr = $this->server->validateAuthenticatedRequest($psr); 53 | $clientReqId = $psr->getAttribute('oauth_client_id'); 54 | $client = $this->clientRepository->find($clientReqId); 55 | $check = !empty($client->roles) && !array_diff([$roles], json_decode($client->roles)); 56 | } catch (OAuthServerException $e) { 57 | throw new AuthenticationException; 58 | } 59 | 60 | if (!$check) { 61 | return response()->json([ 62 | 'message' => 'You are not authorized to use this resource' 63 | ], 403); 64 | } 65 | return $next($request); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /config/logging.php: -------------------------------------------------------------------------------- 1 | env('LOG_CHANNEL', 'syslog'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Log Channels 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may configure the log channels for your application. Out of 24 | | the box, Laravel uses the Monolog PHP logging library. This gives 25 | | you a variety of powerful log handlers / formatters to utilize. 26 | | 27 | | Available Drivers: "single", "daily", "slack", "syslog", 28 | | "errorlog", "custom", "stack" 29 | | 30 | */ 31 | 32 | 'channels' => [ 33 | 'stack' => [ 34 | 'driver' => 'stack', 35 | 'channels' => ['single'], 36 | ], 37 | 38 | 'single' => [ 39 | 'driver' => 'single', 40 | 'path' => storage_path('logs/laravel.log'), 41 | 'level' => 'debug', 42 | ], 43 | 44 | 'daily' => [ 45 | 'driver' => 'daily', 46 | 'path' => storage_path('logs/laravel.log'), 47 | 'level' => 'debug', 48 | 'days' => 7, 49 | ], 50 | 51 | 'slack' => [ 52 | 'driver' => 'slack', 53 | 'url' => env('LOG_SLACK_WEBHOOK_URL'), 54 | 'username' => 'Laravel Log', 55 | 'emoji' => ':boom:', 56 | 'level' => 'critical', 57 | ], 58 | 59 | 'syslog' => [ 60 | 'driver' => 'syslog', 61 | 'level' => 'debug', 62 | ], 63 | 64 | 'errorlog' => [ 65 | 'driver' => 'errorlog', 66 | 'level' => 'debug', 67 | ], 68 | ], 69 | 70 | ]; 71 | -------------------------------------------------------------------------------- /app/Providers/RouteServiceProvider.php: -------------------------------------------------------------------------------- 1 | mapApiRoutes(); 39 | 40 | $this->mapWebRoutes(); 41 | 42 | $this->mapIdpRoutes(); 43 | 44 | // 45 | } 46 | 47 | /** 48 | * Define the "web" routes for the application. 49 | * 50 | * These routes all receive session state, CSRF protection, etc. 51 | * 52 | * @return void 53 | */ 54 | protected function mapWebRoutes() 55 | { 56 | Route::middleware('web') 57 | ->namespace($this->namespace) 58 | ->group(base_path('routes/web.php')); 59 | } 60 | 61 | /** 62 | * Define the "api" routes for the application. 63 | * 64 | * These routes are typically stateless. 65 | * 66 | * @return void 67 | */ 68 | protected function mapApiRoutes() 69 | { 70 | Route::prefix('api') 71 | ->middleware('api') 72 | ->namespace($this->namespace) 73 | ->group(base_path('routes/api.php')); 74 | } 75 | 76 | /** 77 | * Define the "idp" routes for the application. 78 | * 79 | * These routes are typically stateless. 80 | * 81 | * @return void 82 | */ 83 | protected function mapIdpRoutes() 84 | { 85 | Route::namespace($this->namespace) 86 | ->group(base_path('routes/idp.php')); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /app/Repositories/OauthClientsRepository.php: -------------------------------------------------------------------------------- 1 | $word) { 32 | if ($word) { 33 | $searchWord = '%' . $word . '%'; 34 | $words[$key] = "(name LIKE ? or roles like ? )" ; 35 | array_push($params, $searchWord, $searchWord); 36 | } 37 | } 38 | $searchTerm = implode(' and ', $words); 39 | 40 | $result = OauthClient::join('clients', 'oauth_clients.id', '=', 'clients.oauth_client_id' ) 41 | ->select('oauth_clients.*') 42 | ->whereRaw($searchTerm, $params) 43 | ->paginate(10); 44 | 45 | return OauthClientsResource::collection($result); 46 | } 47 | 48 | /** 49 | * @purpose 50 | * 51 | * Create new Oauth Clients 52 | * @param array $data 53 | * @return OauthClient 54 | */ 55 | 56 | public function create(array $data) 57 | { 58 | return OauthClient::create($data); 59 | } 60 | 61 | /** 62 | * @purpose 63 | * 64 | * Find an Oauth Client by id 65 | * @param int $id 66 | * @return OauthClient 67 | */ 68 | 69 | public function find(int $id) 70 | { 71 | return OauthClient::find($id); 72 | } 73 | 74 | /** 75 | * Update OauthClient 76 | * @param int &id array $data 77 | * @param array $data 78 | * @return OauthClient 79 | */ 80 | 81 | public function update(int $id, array $data) 82 | { 83 | return OauthClient::where('id', $id)->update($data); 84 | } 85 | } -------------------------------------------------------------------------------- /config/filesystems.php: -------------------------------------------------------------------------------- 1 | env('FILESYSTEM_DRIVER', 'local'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Default Cloud Filesystem Disk 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Many applications store files both locally and in the cloud. For this 24 | | reason, you may specify a default "cloud" driver here. This driver 25 | | will be bound as the Cloud disk implementation in the container. 26 | | 27 | */ 28 | 29 | 'cloud' => env('FILESYSTEM_CLOUD', 's3'), 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Filesystem Disks 34 | |-------------------------------------------------------------------------- 35 | | 36 | | Here you may configure as many filesystem "disks" as you wish, and you 37 | | may even configure multiple disks of the same driver. Defaults have 38 | | been setup for each driver as an example of the required options. 39 | | 40 | | Supported Drivers: "local", "ftp", "sftp", "s3", "rackspace" 41 | | 42 | */ 43 | 44 | 'disks' => [ 45 | 46 | 'local' => [ 47 | 'driver' => 'local', 48 | 'root' => storage_path('app'), 49 | ], 50 | 51 | 'public' => [ 52 | 'driver' => 'local', 53 | 'root' => storage_path('app/public'), 54 | 'url' => env('APP_URL').'/storage', 55 | 'visibility' => 'public', 56 | ], 57 | 58 | 's3' => [ 59 | 'driver' => 's3', 60 | 'key' => env('AWS_ACCESS_KEY_ID'), 61 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 62 | 'region' => env('AWS_DEFAULT_REGION'), 63 | 'bucket' => env('AWS_BUCKET'), 64 | 'url' => env('AWS_URL'), 65 | ], 66 | 67 | ], 68 | 69 | ]; 70 | -------------------------------------------------------------------------------- /resources/views/auth/register.blade.php: -------------------------------------------------------------------------------- 1 | @extends('base') 2 | 3 | @section('content') 4 | 5 | @if ($errors->any()) 6 |
7 | 12 |
13 | @endif 14 | 15 |
16 |
17 | {{ csrf_field() }} 18 |
19 | 20 | 22 |
23 |
24 | 25 | 27 |
28 |
29 | 30 | 32 |
33 |
34 | 35 | 37 |
38 |
39 | 40 | 42 |
43 | 46 |
47 |
48 | 49 | @endsection -------------------------------------------------------------------------------- /routes/idp.php: -------------------------------------------------------------------------------- 1 | group(function () { 19 | 20 | Route::middleware(['api', 'authenticated'])->get('user', 'JwtAuth\LoginController@userByToken'); 21 | Route::middleware(['api', 'authenticated'])->get('loginWithToken', 'JwtAuth\LoginController@userByToken'); // TODO da cancellare dopo allineamento 22 | Route::middleware(['api', 'authenticated'])->get('logout', 'JwtAuth\LoginController@logout'); 23 | 24 | Route::middleware('web')->get('roles/{id}/user-roles', 'Manage\UserRoleController@getRoles')->where(['id' => '[0-9]+']); 25 | Route::middleware('web')->get('client-roles', 'ClientRoleController@all'); 26 | Route::post('complete-registration', 'JwtAuth\VerificationController@verify'); 27 | 28 | // Routes to manage users 29 | Route::middleware(['client', 'checkclientrole:manager'])->group(function () { 30 | 31 | Route::post('user', 'Manage\UserController@create'); 32 | 33 | Route::post('users/{id}/user-roles', 'Manage\UserRoleController@create')->where(['id' => '[0-9]+']); 34 | 35 | Route::delete('user-role/{id}', 'Manage\UserRoleController@delete')->where(['id' => '[0-9]+']); 36 | 37 | }); 38 | 39 | // Routes to manage idp 40 | Route::middleware(['client', 'checkclientrole:admin'])->group(function () { 41 | 42 | Route::post('providers', 'Manage\ProviderController@create'); 43 | 44 | Route::post('roles', 'Manage\RoleController@create'); 45 | 46 | Route::delete('roles/{id}', 'Manage\RoleController@delete')->where(['id' => '[0-9]+']); 47 | }); 48 | 49 | Route::middleware('client')->group(function () { 50 | 51 | Route::get('roles', 'Manage\RoleController@all'); 52 | 53 | Route::get('providers', 'Manage\ProviderController@all'); 54 | 55 | Route::get('users/{id}', 'Manage\UserController@find')->where('id', '[0-9]+'); 56 | 57 | Route::get('users/{id}/user-roles', 'Manage\UserRoleController@getUserRole')->where(['id' => '[0-9]+']); 58 | }); 59 | }); 60 | 61 | Route::prefix('v2')->group(function () { 62 | 63 | Route::middleware('web')->post('login', 'JwtAuth\LoginController@login')->name('login'); 64 | }); 65 | -------------------------------------------------------------------------------- /routes/web.php: -------------------------------------------------------------------------------- 1 | back(); 24 | }); 25 | 26 | Route::middleware('guest') 27 | ->get('loginForm', 'JwtAuth\LoginController@showLoginForm') 28 | ->name('loginForm'); 29 | 30 | Route::get('logout', 'JwtAuth\LoginController@logout')->name('logout'); 31 | 32 | Route::middleware('web.authenticated') 33 | ->get('authenticated', 'JwtAuth\LoginController@authenticated') 34 | ->name('authenticated'); 35 | 36 | Route::get('complete-registration', function () { 37 | return view('auth.complete-registration-form'); 38 | })->name('complete-registration'); 39 | 40 | /********* ADMIN ROUTES ************/ 41 | 42 | Route::prefix('admin')->middleware('role:ADMIN_IDP')->group(function () { 43 | 44 | Route::get('/', function () { 45 | return redirect()->route('users-panel'); 46 | })->name('admin-board'); 47 | 48 | Route::post('users', 'Manage\UserController@create'); 49 | 50 | Route::get('users-panel', function () { 51 | return view('admin.users'); 52 | })->name('users-panel'); 53 | 54 | Route::post('providers', 'Manage\ProviderController@create'); 55 | 56 | Route::get('create-provider', function () { 57 | return view('admin.create-provider'); 58 | })->name('create-provider'); 59 | 60 | Route::get('oauth-clients', function () { 61 | return view('admin.oauth-clients'); 62 | })->name('oauth-clients'); 63 | 64 | Route::get('oauth-clients-all', 'Manage\OauthClientsController@all'); 65 | Route::put('update-roles', 'Manage\OauthClientsController@updateClientRoles'); 66 | 67 | Route::get('roles', 'Manage\RoleController@all'); 68 | Route::post('roles', 'Manage\RoleController@create'); 69 | Route::delete('roles/{id}', 'Manage\RoleController@delete')->where(['id' => '[0-9]+']); 70 | 71 | Route::get('users', 'Manage\UserController@all'); 72 | 73 | Route::get('manage-role', function () { 74 | return view('admin.create-role'); 75 | })->name('manage-role'); 76 | }); 77 | -------------------------------------------------------------------------------- /app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->when(LogoutProvidersListener::class) 44 | ->needs(RepositoryInterface::class) 45 | ->give(ProviderRepository::class); 46 | 47 | $this->app->bind(UserRepositoryInterface::class, UserRepository::class); 48 | 49 | $this->app->when(ProviderController::class) 50 | ->needs(RepositoryInterface::class) 51 | ->give(ProviderRepository::class); 52 | 53 | $this->app->when(RoleController::class) 54 | ->needs(RepositoryInterface::class) 55 | ->give(RoleRepository::class); 56 | 57 | $this->app->when(UserRoleController::class) 58 | ->needs(RepositoryInterface::class) 59 | ->give(UserRoleRepository::class); 60 | 61 | $this->app->bind(ClientRepositoryInterface::class, ClientRepository::class); 62 | 63 | $this->app->when(UserController::class) 64 | ->needs(RepositoryInterface::class) 65 | ->give(VerificationTokenRepository::class); 66 | 67 | $this->app->when(VerificationController::class) 68 | ->needs(RepositoryInterface::class) 69 | ->give(VerificationTokenRepository::class); 70 | 71 | $this->app->when(OauthClientsController::class) 72 | ->needs(OauthClientsRepository::class) 73 | ->give(OauthClientsRepository::class); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /database/seeds/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | insert([ 17 | 'email' => 'mario.rossi@example.com', 18 | 'password' => Hash::make('secret'), 19 | 'is_verified' => true, 20 | 'name' => 'Mario', 21 | 'surname' => 'Rossi' 22 | ]); 23 | 24 | DB::table('roles')->insert([ 25 | 'name' => 'USER' 26 | ]); 27 | 28 | DB::table('roles')->insert([ 29 | 'name' => 'ADMIN' 30 | ]); 31 | 32 | DB::table('user_roles')->insert([ 33 | 'user_id' => 1, 34 | 'role_id' => 1 35 | ]); 36 | 37 | DB::table('user_roles')->insert([ 38 | 'user_id' => 1, 39 | 'role_id' => 2 40 | ]); 41 | 42 | DB::table('clients')->insert([ 43 | 'oauth_client_id' => 1, 44 | 'roles' => '["manager"]', 45 | ]); 46 | 47 | DB::table('clients')->insert([ 48 | 'oauth_client_id' => 2, 49 | 'roles' => '["manager", "admin"]', 50 | ]); 51 | 52 | DB::table('clients')->insert([ 53 | 'oauth_client_id' => 3, 54 | 'roles' => '[]', 55 | ]); 56 | 57 | // STEP: seeding passport oauth_clients 58 | DB::table('oauth_clients')->insert([ 59 | 'user_id' => 1, 60 | 'name' => 'manager', 61 | 'secret' => 'HkZ5sCBaAKRH0B5CIlBGjNIQazfYDxi4EDth3ANa', 62 | 'redirect' => 'http://localhost:8000/auth/callback', 63 | 'personal_access_client' => 0, 64 | 'password_client' => 0, 65 | 'revoked' => 0 66 | ]); 67 | 68 | DB::table('oauth_clients')->insert([ 69 | 'user_id' => 2, 70 | 'name' => 'admin', 71 | 'secret' => '6ZWpCgKPYc93TbgKHKnZMiULFStw88lIvquDQETQ', 72 | 'redirect' => 'http://localhost:8000/auth/callback', 73 | 'personal_access_client' => 0, 74 | 'password_client' => 0, 75 | 'revoked' => 0 76 | ]); 77 | 78 | DB::table('oauth_clients')->insert([ 79 | 'user_id' => 3, 80 | 'name' => 'client', 81 | 'secret' => 'mydhRDjLRMNuubmmHfs8u2DURLEc91qoc6fS58kT', 82 | 'redirect' => 'http://localhost:8000/auth/callback', 83 | 'personal_access_client' => 0, 84 | 'password_client' => 0, 85 | 'revoked' => 0 86 | ]); 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /tests/Feature/LoginWithoutMiddlewareTest.php: -------------------------------------------------------------------------------- 1 | create([ 26 | 'is_verified' => true 27 | ]); 28 | 29 | $loginController = new LoginController(); 30 | 31 | $request = new LoginRequest([], [], [], [], [], ['REMOTE_ADDR' => '127.0.0.1']); 32 | 33 | $request->merge([ 34 | 'username' => $user->email, 35 | 'password' => 'secret' 36 | ]); 37 | 38 | $response = $loginController->login($request); 39 | $this->assertEquals(200, $response->status()); 40 | $this->assertContains('user', $response->getContent()); 41 | } 42 | 43 | /** 44 | * @test 45 | * @return void 46 | */ 47 | public function loginNotVerifiedUserTest() 48 | { 49 | $user = factory(User::class)->create([ 50 | 'is_verified' => false 51 | ]); 52 | 53 | $loginController = new LoginController(); 54 | 55 | $request = new LoginRequest([], [], [], [], [], ['REMOTE_ADDR' => '127.0.0.1']); 56 | 57 | $request->merge([ 58 | 'username' => $user->email, 59 | 'password' => 'secret' 60 | ]); 61 | 62 | $response = $loginController->login($request); 63 | $this->assertEquals(403, $response->status()); 64 | } 65 | 66 | /** 67 | * Logout without provider register on system 68 | * @test 69 | * @return void 70 | */ 71 | public function logoutWithoutProviderTest() 72 | { 73 | $user = factory(User::class)->create([ 74 | 'is_verified' => true 75 | ]); 76 | $response = $this->json('POST', 'v2/login', [ 77 | 'username' => $user->email, 78 | 'password' => 'secret' 79 | ]); 80 | $cookies = ['token' => json_decode($response->getContent())->token]; 81 | 82 | $mock = Mockery::mock(ProviderRepository::class)->makePartial() 83 | ->shouldReceive(['all' => []]) 84 | ->withAnyArgs() 85 | ->once() 86 | ->getMock(); 87 | $this->app->instance('App\Repositories\ProviderRepository', $mock); 88 | 89 | $response = $this->get('/v1/logout', [], $cookies); 90 | $response->assertStatus(302)->assertRedirect('/loginForm'); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/Http/Controllers/Manage/OauthClientsController.php: -------------------------------------------------------------------------------- 1 | oauthClientsRepository = $oauthClientsRepository; 28 | $this->clientRoleRepository = $clientRoleRepository; 29 | $this->clientRepository = $clientRepository; 30 | } 31 | 32 | /** 33 | * @purpose 34 | * 35 | * Return a resource of all oauth clients 36 | * @param Request $request 37 | * @return void 38 | */ 39 | 40 | public function all(Request $request) 41 | { 42 | $query = $request->input('q'); 43 | 44 | return $this->oauthClientsRepository->all($query); 45 | } 46 | 47 | /** 48 | * @purpose 49 | * 50 | * Update the roles of single oauth client 51 | * @param Request $request 52 | * @return void 53 | */ 54 | 55 | public function updateClientRoles(Request $request) { 56 | 57 | $admittedRoles = $this->clientRoleRepository->all(); 58 | 59 | $validatedData = $request->validate([ 60 | 'clientId' => 'required|integer', 61 | 'roles' => ['array', 62 | Rule::in($admittedRoles) 63 | ] 64 | ]); 65 | 66 | $id = $validatedData['clientId']; 67 | $roles = json_encode($validatedData['roles']); 68 | 69 | $data = ["oauth_client_id" => $id, 70 | "roles" => $roles 71 | ]; 72 | 73 | $oauthClient = $this->oauthClientsRepository->find($id); 74 | 75 | if (empty($oauthClient)) { 76 | return response()->json([ 77 | 'message' => 'Error during updating roles' 78 | ], 500); 79 | } 80 | 81 | if(empty($oauthClient->client)){ 82 | if (!$this->clientRepository->create($data)) { 83 | return response()->json([ 84 | 'message' => 'Error during updating roles' 85 | ], 500); 86 | } 87 | } 88 | 89 | $oauthClient->client = $this->clientRepository->update($id, $data); 90 | 91 | return response()->json([], Response::HTTP_NO_CONTENT); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/ZanichelliEditore/laravel-jwt-idp.svg?branch=master)](https://travis-ci.org/ZanichelliEditore/laravel-jwt-idp) 2 | [![codecov](https://codecov.io/gh/ZanichelliEditore/laravel-jwt-idp/branch/master/graph/badge.svg)](https://codecov.io/gh/ZanichelliEditore/laravel-jwt-idp) 3 | 4 | # Laravel JWT IDP 5 | 6 | ## What is it? 7 | 8 | This project is a basic identity provider developed using Laravel Framework. The user 9 | authentication is based on JWT standard. It is possible use a single sign-on point to log-in 10 | users of several application. 11 | 12 | ## How can I use it? 13 | 14 | It is possible to integrate the single sign-on in an existing project in few steps. Protect your route 15 | with a middleware that checks if the user exists in the session. If the user is not in the session 16 | redirect the user to the IDP login. After the login success the IDP will redirect the user to the 17 | application passing a token. The application must use that token to retrieve the user data. 18 | 19 | ### Setup project 20 | 21 | - Clone the project 22 | - Duplicate the file .env.example and change the name in .env 23 | - In the root the project execute `composer install` 24 | - In the project root execute the command `php artisan key:generate` 25 | - Set a secret key (for jwt authentication) executing from command line `php artisan jwt:secret` 26 | - Create passport keys (for api authentication) executing from command line `php artisan passport:install` 27 | - Compile vuejs view with _**yarn**_ and use `yarn dev` (or use `npm run dev` with _**npm**_) 28 | 29 | ### Routes 30 | 31 | GET Requests 32 | 33 | - **/loginForm** shows the IDP login form. Parameter: "redirect". 34 | - **/v1/user** retrieve the user data by token. Parameters: "token" 35 | - **/v1/logout** logout 36 | 37 | POST Requests 38 | 39 | - **/v2/login** login the user into the application. Parameters: "username" and "password". 40 | 41 | ### Views 42 | 43 | There is 1 default views: login form. 44 | There is also an admin section (**/admin**) through which you can manage idp system; the view is available for "ADMIN_IDP" user-role. 45 | 46 | ### Database 47 | 48 | The IDP manages users using 3 table: users, users_roles, roles. 49 | In the users tables are stored basic users data like email, password, 50 | name, surname, is_verified. Each user can have a role or many roles associated; 51 | it can be usefull in a context with RBAC (Role-based access control). 52 | 53 | ### User structure 54 | 55 | ```json 56 | { 57 | "user": { 58 | "id": 1, 59 | "email": "mario.rossi@example.com", 60 | "is_verified": 1, 61 | "name": "Mario", 62 | "surname": "Rossi", 63 | "created_at": "2018-09-14 12:30:20", 64 | "updated_at": null, 65 | "roles": [ 66 | { 67 | "roleId": 1, 68 | "roleName": "USER" 69 | }, 70 | { 71 | "roleId": 2, 72 | "roleName": "ADMIN" 73 | } 74 | ] 75 | } 76 | } 77 | ``` 78 | -------------------------------------------------------------------------------- /config/queue.php: -------------------------------------------------------------------------------- 1 | env('QUEUE_DRIVER', 'sync'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Queue Connections 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may configure the connection information for each server that 24 | | is used by your application. A default configuration has been added 25 | | for each back-end shipped with Laravel. You are free to add more. 26 | | 27 | | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null" 28 | | 29 | */ 30 | 31 | 'connections' => [ 32 | 33 | 'sync' => [ 34 | 'driver' => 'sync', 35 | ], 36 | 37 | 'database' => [ 38 | 'driver' => 'database', 39 | 'table' => 'jobs', 40 | 'queue' => 'default', 41 | 'retry_after' => 90, 42 | ], 43 | 44 | 'beanstalkd' => [ 45 | 'driver' => 'beanstalkd', 46 | 'host' => 'localhost', 47 | 'queue' => 'default', 48 | 'retry_after' => 90, 49 | ], 50 | 51 | 'sqs' => [ 52 | 'driver' => 'sqs', 53 | 'key' => env('SQS_KEY', 'your-public-key'), 54 | 'secret' => env('SQS_SECRET', 'your-secret-key'), 55 | 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), 56 | 'queue' => env('SQS_QUEUE', 'your-queue-name'), 57 | 'region' => env('SQS_REGION', 'us-east-1'), 58 | ], 59 | 60 | 'redis' => [ 61 | 'driver' => 'redis', 62 | 'connection' => 'default', 63 | 'queue' => 'default', 64 | 'retry_after' => 90, 65 | 'block_for' => null, 66 | ], 67 | 68 | ], 69 | 70 | /* 71 | |-------------------------------------------------------------------------- 72 | | Failed Queue Jobs 73 | |-------------------------------------------------------------------------- 74 | | 75 | | These options configure the behavior of failed queue job logging so you 76 | | can control which database and table are used to store the jobs that 77 | | have failed. You may change them to any database / table you wish. 78 | | 79 | */ 80 | 81 | 'failed' => [ 82 | 'database' => env('DB_CONNECTION', 'mysql'), 83 | 'table' => 'failed_jobs', 84 | ], 85 | 86 | ]; 87 | -------------------------------------------------------------------------------- /tests/Integration/VerificationTest.php: -------------------------------------------------------------------------------- 1 | '', 'password' => '']; 22 | $response = $this->json('POST', '/v1/complete-registration', $data); 23 | $response->assertStatus(422) 24 | ->assertJson([ 25 | 'message' => 'Invalid parameters.' 26 | ]); 27 | 28 | 29 | $user = User::create([ 30 | 'email' => Str::random(30) . '@example.com', 31 | 'name' => 'myName2', 32 | 'surname' => 'mySurname2', 33 | 'is_verified' => true 34 | ]); 35 | 36 | // Invalid parameters for empty password 37 | $token2 = VerificationToken::create([ 38 | 'user_id' => $user->id, 39 | 'token' => Str::random(50) 40 | ]); 41 | 42 | $data = ['token' => $token2->token, 'password' => ' ']; 43 | $response = $this->json('POST', '/v1/complete-registration', $data); 44 | $response->assertStatus(422) 45 | ->assertJson([ 46 | 'message' => 'Invalid parameters.' 47 | ]); 48 | 49 | // Invalid Token with empty field 50 | $data = ['token' => 'a', 'password' => 'testpassword']; 51 | $response = $this->json('POST', '/v1/complete-registration', $data); 52 | $response->assertStatus(422) 53 | ->assertJson([ 54 | 'message' => 'Invalid token' 55 | ]); 56 | 57 | 58 | // Invalid Token with token set not valid 59 | $token = VerificationToken::create([ 60 | 'user_id' => $user->id, 61 | 'token' => Str::random(50), 62 | 'is_valid' => 0 63 | ]); 64 | 65 | $data = ['token' => $token->token, 'password' => 'testpassword']; 66 | $response = $this->json('POST', '/v1/complete-registration', $data); 67 | $response->assertStatus(422) 68 | ->assertJson([ 69 | 'message' => 'Invalid token' 70 | ]); 71 | } 72 | 73 | /** 74 | * @test 75 | * @return void 76 | */ 77 | public function validationTokenTest() 78 | { 79 | $user = User::create([ 80 | 'email' => Str::random(30) . '@example.com', 81 | 'name' => 'myName2', 82 | 'surname' => 'mySurname2', 83 | 'is_verified' => true 84 | ]); 85 | 86 | 87 | $token = VerificationToken::create([ 88 | 'user_id' => $user->id, 89 | 'token' => Str::random(50) 90 | ]); 91 | 92 | $data = ['token' => $token->token, 'password' => 'testpassword']; 93 | $response = $this->json('POST', '/v1/complete-registration', $data); 94 | $response->assertStatus(204); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /config/cache.php: -------------------------------------------------------------------------------- 1 | env('CACHE_DRIVER', 'redis'), 21 | 22 | /* 23 | |-------------------------------------------------------------------------- 24 | | Cache Stores 25 | |-------------------------------------------------------------------------- 26 | | 27 | | Here you may define all of the cache "stores" for your application as 28 | | well as their drivers. You may even define multiple stores for the 29 | | same cache driver to group types of items stored in your caches. 30 | | 31 | */ 32 | 33 | 'stores' => [ 34 | 35 | 'apc' => [ 36 | 'driver' => 'apc', 37 | ], 38 | 39 | 'array' => [ 40 | 'driver' => 'array', 41 | ], 42 | 43 | 'database' => [ 44 | 'driver' => 'database', 45 | 'table' => 'cache', 46 | 'connection' => null, 47 | ], 48 | 49 | 'file' => [ 50 | 'driver' => 'file', 51 | 'path' => storage_path('framework/cache/data'), 52 | ], 53 | 54 | 'memcached' => [ 55 | 'driver' => 'memcached', 56 | 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'), 57 | 'sasl' => [ 58 | env('MEMCACHED_USERNAME'), 59 | env('MEMCACHED_PASSWORD'), 60 | ], 61 | 'options' => [ 62 | // Memcached::OPT_CONNECT_TIMEOUT => 2000, 63 | ], 64 | 'servers' => [ 65 | [ 66 | 'host' => env('MEMCACHED_HOST', '127.0.0.1'), 67 | 'port' => env('MEMCACHED_PORT', 11211), 68 | 'weight' => 100, 69 | ], 70 | ], 71 | ], 72 | 73 | 'redis' => [ 74 | 'driver' => 'redis', 75 | 'connection' => 'default', 76 | ], 77 | 78 | ], 79 | 80 | /* 81 | |-------------------------------------------------------------------------- 82 | | Cache Key Prefix 83 | |-------------------------------------------------------------------------- 84 | | 85 | | When utilizing a RAM based store such as APC or Memcached, there might 86 | | be other applications utilizing the same cache. So, we'll specify a 87 | | value to get prefixed to all our keys so we can avoid collisions. 88 | | 89 | */ 90 | 91 | 'prefix' => env( 92 | 'CACHE_PREFIX', 93 | Str::slug(env('APP_NAME', 'laravel'), '_').'_cache' 94 | ), 95 | 96 | ]; 97 | -------------------------------------------------------------------------------- /app/Services/AccountService.php: -------------------------------------------------------------------------------- 1 | $email, 48 | 'password' => Hash::make($password), 49 | 'name' => $name, 50 | 'surname' => $surname 51 | ]); 52 | 53 | $verificationCode = VerificationCode::create([ 54 | 'user_id' => $user->id, 55 | 'verification_code' => Str::random(30) 56 | ]); 57 | } catch (Exception $e){ 58 | Log::error($e->getMessage()); 59 | DB::rollBack(); 60 | 61 | throw new SqlException($e->getMessage()); 62 | } 63 | 64 | DB::commit(); 65 | 66 | event(new RegistrationEvent($user, $verificationCode->verification_code)); 67 | } 68 | 69 | /** 70 | * @param string $verificationCode 71 | * @throws SqlException 72 | */ 73 | public function verifyUser(string $verificationCode){ 74 | // TODO decidere i messaggi degli errori 75 | 76 | try { 77 | 78 | $userVerification = VerificationCode::where('verification_code', $verificationCode)->first(); 79 | 80 | if(empty($userVerification)){ 81 | throw new Exception('Non trovato il codice'); 82 | } 83 | 84 | $user = User::where('id', $userVerification->user_id)->first(); 85 | $user->is_verified = 1; 86 | 87 | if(!$user->save()){ 88 | throw new Exception('Non è salvato'); 89 | } 90 | 91 | if(!$userVerification->delete()){ 92 | throw new Exception('Non è cancellato'); 93 | } 94 | 95 | } catch (Exception $e){ 96 | Log::error($e->getMessage()); 97 | throw new SqlException($e->getMessage()); 98 | } 99 | 100 | } 101 | 102 | } -------------------------------------------------------------------------------- /app/Http/Kernel.php: -------------------------------------------------------------------------------- 1 | [ 36 | \App\Http\Middleware\EncryptCookies::class, 37 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, 38 | \Illuminate\Session\Middleware\StartSession::class, 39 | // \Illuminate\Session\Middleware\AuthenticateSession::class, 40 | \Illuminate\View\Middleware\ShareErrorsFromSession::class, 41 | // \App\Http\Middleware\VerifyCsrfToken::class, 42 | \Illuminate\Routing\Middleware\SubstituteBindings::class, 43 | \App\Http\Middleware\Localization::class, 44 | ], 45 | 46 | 'api' => [ 47 | 'throttle:60,1', 48 | 'bindings', 49 | ], 50 | ]; 51 | 52 | /** 53 | * The application's route middleware. 54 | * 55 | * These middleware may be assigned to groups or used individually. 56 | * 57 | * @var array 58 | */ 59 | protected $routeMiddleware = [ 60 | 'auth' => \Illuminate\Auth\Middleware\Authenticate::class, 61 | 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 62 | 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, 63 | 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 64 | 'can' => \Illuminate\Auth\Middleware\Authorize::class, 65 | 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 66 | 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 67 | 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 68 | 69 | 'client' => CheckClientCredentials::class, 70 | 'scopes' => \Laravel\Passport\Http\Middleware\CheckScopes::class, 71 | 'scope' => \Laravel\Passport\Http\Middleware\CheckForAnyScope::class, 72 | 'checkclientrole' => CheckClientRole::class, 73 | 'role' => CheckRole::class, 74 | 'authenticated' => Authenticated::class, 75 | 76 | 'web.authenticated' => RedirectIfUnauthenticated::class, 77 | ]; 78 | } 79 | -------------------------------------------------------------------------------- /resources/js/components/LoginForm.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 85 | 86 | -------------------------------------------------------------------------------- /tests/Integration/RoleWithoutMiddlewareTest.php: -------------------------------------------------------------------------------- 1 | create(); 23 | $response = $this->json('GET', '/v1/roles'); 24 | $response->assertStatus(200) 25 | ->assertJsonStructure([ 26 | [ 27 | 'id', 28 | 'name' 29 | ] 30 | ]); 31 | } 32 | 33 | /** 34 | * Create a new role. 35 | * @test 36 | * @return void 37 | */ 38 | public function createRoleTest() 39 | { 40 | $role = factory(Role::class)->make(); 41 | $role->id = 1; 42 | 43 | $repositoryMock = Mockery::mock(RoleRepository::class)->makePartial() 44 | ->shouldReceive(['create'=> collect(['name' => $role->name])]) 45 | ->once() 46 | ->andReturn($role) 47 | ->getMock(); 48 | 49 | $this->app->instance('App\Repositories\RoleRepository', $repositoryMock); 50 | 51 | $response = $this->json('POST', '/v1/roles', ['name' => $role->name]); 52 | $response->assertStatus(201) 53 | ->assertJsonStructure([ 54 | 'id', 55 | 'name' 56 | ]); 57 | } 58 | 59 | /** 60 | * Existing role test. 61 | * @test 62 | * @return void 63 | */ 64 | public function existingRoleTest() 65 | { 66 | $role = factory(Role::class)->create(); 67 | 68 | $response = $this->json('POST', '/v1/roles', ['name' => $role->name]); 69 | $response->assertStatus(422); 70 | 71 | $role->delete(); 72 | } 73 | 74 | /** 75 | * Existing role test. 76 | * @test 77 | * @return void 78 | */ 79 | public function deletingInexistentRole() 80 | { 81 | $response = $this->json('DELETE', '/v1/roles/99999999999999'); 82 | $response->assertStatus(404); 83 | } 84 | 85 | /** 86 | * Delete role test. 87 | * @test 88 | * @return void 89 | */ 90 | public function deletingRole() 91 | { 92 | $role = factory(Role::class)->create(); 93 | 94 | $response = $this->json('DELETE', '/v1/roles/' . $role->id); 95 | $response->assertStatus(204); 96 | } 97 | 98 | /** 99 | * Delete role test. 100 | * @test 101 | * @return void 102 | */ 103 | public function errorOnDeletingRole() 104 | { 105 | $role = factory(Role::class)->create(); 106 | 107 | $repositoryMock = Mockery::mock(RoleRepository::class)->makePartial() 108 | ->shouldReceive(['delete' => false]) 109 | ->once() 110 | ->getMock(); 111 | 112 | $this->app->instance('App\Repositories\RoleRepository', $repositoryMock); 113 | 114 | $response = $this->json('DELETE', '/v1/roles/' . $role->id); 115 | $response->assertStatus(500); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /resources/js/components/Paginator.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 97 | 98 | -------------------------------------------------------------------------------- /resources/js/components/NotificationComponent.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 79 | 80 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at developers@zanichelli.it. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /resources/js/components/CompleteRegistrationForm.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 109 | 110 | -------------------------------------------------------------------------------- /resources/js/components/Chip.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 86 | 87 | -------------------------------------------------------------------------------- /config/auth.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'guard' => 'jwt', 18 | 'passwords' => 'users', 19 | ], 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Authentication Guards 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Next, you may define every authentication guard for your application. 27 | | Of course, a great default configuration has been defined for you 28 | | here which uses session storage and the Eloquent user provider. 29 | | 30 | | All authentication drivers have a user provider. This defines how the 31 | | users are actually retrieved out of your database or other storage 32 | | mechanisms used by this application to persist your user's data. 33 | | 34 | | Supported: "session", "token" 35 | | 36 | */ 37 | 38 | 'guards' => [ 39 | 40 | 'web' => [ 41 | 'driver' => 'session', 42 | 'provider' => 'users', 43 | ], 44 | 45 | 'api' => [ 46 | 'driver' => 'passport', 47 | 'provider' => 'users', 48 | ], 49 | 50 | 'jwt' => [ 51 | 'driver' => 'jwt', 52 | 'provider' => 'users' 53 | ] 54 | ], 55 | 56 | /* 57 | |-------------------------------------------------------------------------- 58 | | User Providers 59 | |-------------------------------------------------------------------------- 60 | | 61 | | All authentication drivers have a user provider. This defines how the 62 | | users are actually retrieved out of your database or other storage 63 | | mechanisms used by this application to persist your user's data. 64 | | 65 | | If you have multiple user tables or models you may configure multiple 66 | | sources which represent each model / table. These sources may then 67 | | be assigned to any extra authentication guards you have defined. 68 | | 69 | | Supported: "database", "eloquent" 70 | | 71 | */ 72 | 73 | 'providers' => [ 74 | 75 | 'users' => [ 76 | 'driver' => 'eloquent', 77 | 'model' => App\Models\User::class, 78 | ], 79 | 80 | // 'users' => [ 81 | // 'driver' => 'database', 82 | // 'table' => 'users', 83 | // ], 84 | ], 85 | 86 | /* 87 | |-------------------------------------------------------------------------- 88 | | Resetting Passwords 89 | |-------------------------------------------------------------------------- 90 | | 91 | | You may specify multiple password reset configurations if you have more 92 | | than one user table or model in the application and you want to have 93 | | separate password reset settings based on the specific user types. 94 | | 95 | | The expire time is the number of minutes that the reset token should be 96 | | considered valid. This security feature keeps tokens short-lived so 97 | | they have less time to be guessed. You may change this as needed. 98 | | 99 | */ 100 | 101 | 'passwords' => [ 102 | 'users' => [ 103 | 'provider' => 'users', 104 | 'table' => 'password_resets', 105 | 'expire' => 60, 106 | ], 107 | ], 108 | 109 | ]; 110 | -------------------------------------------------------------------------------- /public/svg/404.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/Http/Controllers/JwtAuth/VerificationController.php: -------------------------------------------------------------------------------- 1 | verificationTokenRepository = $verificationTokenRepository; 24 | $this->userRepository = $userRepository; 25 | } 26 | 27 | /** 28 | * @OA\Post( 29 | * path="/v1/complete-registration", 30 | * summary="Active user", 31 | * description="Activate user using token received in the email", 32 | * operationId="VerificationController.verify", 33 | * tags={"JWT Auth"}, 34 | * @OA\RequestBody( 35 | * @OA\MediaType( 36 | * mediaType="application/x-www-form-urlencoded", 37 | * @OA\Schema( 38 | * type="object", 39 | * @OA\Property( 40 | * property="token", 41 | * description="Token", 42 | * type="string" 43 | * ), 44 | * @OA\Property( 45 | * property="password", 46 | * description="User password", 47 | * type="string", 48 | * format="password" 49 | * ) 50 | * ) 51 | * ) 52 | * ), 53 | * @OA\Response( 54 | * response=204, 55 | * description="Operation successful", 56 | * @OA\MediaType( 57 | * mediaType="application/json", 58 | * ) 59 | * ), 60 | * @OA\Response( 61 | * response=422, 62 | * description="Invalid data", 63 | * @OA\MediaType( 64 | * mediaType="application/json", 65 | * ) 66 | * ), 67 | * @OA\Response( 68 | * response=500, 69 | * description="General error", 70 | * @OA\MediaType( 71 | * mediaType="application/json", 72 | * ) 73 | * ) 74 | * ) 75 | */ 76 | public function verify(Request $request) 77 | { 78 | $validator = $this->validator($request->only('token', 'password')); 79 | if ($validator->fails()) { 80 | return response()->json([ 81 | 'message' => 'Invalid parameters.' 82 | ], 422); 83 | } 84 | 85 | $token = $request->input('token'); 86 | $password = $request->input('password'); 87 | 88 | $hashedPassword = Hash::make($password); 89 | 90 | $verificationToken = $this->verificationTokenRepository->retrieveByToken($token); 91 | 92 | if (empty($verificationToken) || !$verificationToken->is_valid) { 93 | return response()->json([ 94 | 'message' => 'Invalid token' 95 | ], 422); 96 | } 97 | 98 | $user = $this->userRepository->find($verificationToken->user_id); 99 | 100 | DB::beginTransaction(); 101 | 102 | $user->is_verified = true; 103 | $user->password = $hashedPassword; 104 | 105 | $verificationToken->is_valid = false; 106 | 107 | if (!$user->save() || !$verificationToken->save()) { 108 | DB::rollBack(); 109 | return response()->json([ 110 | 'message' => 'Error during user activation' 111 | ], 500); 112 | } 113 | 114 | DB::commit(); 115 | 116 | return response()->json([], 204); 117 | } 118 | 119 | private function validator($parameters) 120 | { 121 | return Validator::make($parameters, [ 122 | 'token' => 'required|string', 123 | 'password' => 'required|min:5' 124 | ]); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /tests/Integration/ProviderTest.php: -------------------------------------------------------------------------------- 1 | json('GET', '/v1/providers'); 20 | $response->assertStatus(401); 21 | } 22 | 23 | /** 24 | * Create provider for unauthorized user. 25 | * @test 26 | * @return void 27 | */ 28 | public function providerCreateUnauthorizedTest() 29 | { 30 | $response = $this->json('POST', '/v1/providers'); 31 | $response->assertStatus(401); 32 | } 33 | 34 | /** 35 | * Error validation in create provider 36 | * @test 37 | * @return void 38 | */ 39 | public function providerCreateValidationErrorByAdminTest() 40 | { 41 | $user = UserUtility::getAdmin(); 42 | 43 | $response = $this->json('POST', 'v2/login', [ 44 | 'username' => $user->email, 45 | 'password' => 'secret' 46 | ]); 47 | 48 | $cookie = ['token' => json_decode($response->getContent())->token]; 49 | $response = $this->json('POST', '/admin/providers', [], $cookie); 50 | $response->assertStatus(422)->assertJsonStructure([ 51 | 'message', 52 | 'errors' => [ 53 | 'domain', 54 | 'username', 55 | 'password' 56 | ] 57 | ]); 58 | 59 | $body = [ 60 | 'domain' => 123, 61 | 'username' => 123, 62 | 'password' => 123, 63 | 'logoutUrl' => 123 64 | ]; 65 | $response = $this->json('POST', '/admin/providers', $body, $cookie); 66 | $response->assertStatus(422)->assertJsonStructure([ 67 | 'message', 68 | 'errors' => [ 69 | 'domain', 70 | 'username', 71 | 'password', 72 | 'logoutUrl' 73 | ] 74 | ]); 75 | 76 | $body = [ 77 | 'domain' => Str::random(256), 78 | 'username' => Str::random(51), 79 | 'password' => Str::random(51), 80 | 'logoutUrl' => Str::random(256) 81 | ]; 82 | $response = $this->json('POST', '/admin/providers', $body, $cookie); 83 | $response->assertStatus(422)->assertJsonStructure([ 84 | 'message', 85 | 'errors' => [ 86 | 'domain', 87 | 'username', 88 | 'password', 89 | 'logoutUrl' 90 | ] 91 | ]); 92 | 93 | $provider = factory(Provider::class)->create(); 94 | $body = [ 95 | 'domain' => $provider->domain, 96 | 'username' => 'valid', 97 | 'password' => 'a', 98 | 'logoutUrl' => 'valid' 99 | ]; 100 | $response = $this->json('POST', '/admin/providers', $body, $cookie); 101 | $response->assertStatus(422)->assertJsonStructure([ 102 | 'message', 103 | 'errors' => [ 104 | 'domain', 105 | 'password', 106 | ] 107 | ]); 108 | } 109 | 110 | /** 111 | * Create provider by admin. 112 | * @test 113 | * @return void 114 | */ 115 | public function providerCreateByAdminTest() 116 | { 117 | $user = UserUtility::getAdmin(); 118 | 119 | $response = $this->json('POST', 'v2/login', [ 120 | 'username' => $user->email, 121 | 'password' => 'secret' 122 | ]); 123 | 124 | $cookie = ['token' => json_decode($response->getContent())->token]; 125 | $body = [ 126 | 'domain' => Str::random(85), 127 | 'username' => 'user', 128 | 'password' => 'secret', 129 | 'logoutUrl' => 'valid' 130 | ]; 131 | $response = $this->json('POST', '/admin/providers', $body, $cookie); 132 | $response->assertStatus(201) 133 | ->assertJsonStructure([ 134 | 'provider' => [ 135 | 'id', 136 | 'logoutUrl', 137 | 'domain', 138 | 'username', 139 | 'password' 140 | ] 141 | ]); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /resources/views/mail/base.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | @yield('title') 5 | 6 | 145 | 146 | 147 |
148 |
149 |
150 |

ZANICHELLI

151 |
152 | 153 | @yield('body') 154 | 155 | 158 |
159 |
160 | 161 | --------------------------------------------------------------------------------