├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .gitlab-ci.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── changelog.md ├── composer.json ├── config └── awesio-auth.php ├── database ├── factories │ ├── TwoFactorFactory.php │ ├── UserFactory.php │ └── UserSocialFactory.php └── migrations │ ├── create_countries_table.php.stub │ ├── create_two_factor_table.php.stub │ └── create_users_social_table.php.stub ├── docs └── index.md ├── phpunit.xml.dist ├── src ├── Auth.php ├── AuthServiceProvider.php ├── Contracts │ └── Auth.php ├── Controllers │ ├── Controller.php │ ├── ForgotPasswordController.php │ ├── LoginController.php │ ├── RegisterController.php │ ├── ResetPasswordController.php │ ├── SocialLoginController.php │ ├── Traits │ │ ├── AuthenticatesUsersWith2FA.php │ │ └── RedirectsTo.php │ ├── TwoFactorController.php │ ├── TwoFactorLoginController.php │ └── VerificationController.php ├── Facades │ └── Auth.php ├── Listeners │ └── EventSubscriber.php ├── Middlewares │ └── SocialAuthentication.php ├── Models │ ├── Country.php │ ├── Traits │ │ ├── HasSocialAuthentication.php │ │ ├── HasTwoFactorAuthentication.php │ │ ├── SendsEmailVerification.php │ │ └── SendsPasswordReset.php │ ├── TwoFactor.php │ └── UserSocial.php ├── Repositories │ ├── Contracts │ │ └── UserRepository.php │ └── EloquentUserRepository.php ├── Requests │ ├── TwoFactorStoreRequest.php │ └── TwoFactorVerifyRequest.php ├── Rules │ ├── ValidPhone.php │ └── ValidTwoFactorToken.php ├── Seeds │ └── CountryTableSeeder.php ├── Services │ ├── AuthyTwoFactor.php │ ├── Contracts │ │ ├── SocialProvidersManager.php │ │ └── TwoFactor.php │ └── SocialiteProvidersManager.php └── helpers.php ├── tests ├── Feature │ ├── AuthRoutesTest.php │ ├── ForgotPasswordTest.php │ ├── LoginTest.php │ ├── RegisterTest.php │ ├── ResetPasswordTest.php │ ├── SocialLoginTest.php │ ├── TwoFactorLoginTest.php │ ├── TwoFactorTest.php │ └── VerificationTest.php ├── Stubs │ ├── TwoFactor.php │ └── User.php ├── TestCase.php └── Unit │ ├── Listeners │ └── EventSubscriberTest.php │ ├── Models │ └── UserSocialTest.php │ ├── Repositories │ └── UserRepositoryTest.php │ └── Services │ └── AuthyTest.php ├── views ├── auth │ ├── login.blade.php │ ├── passwords │ │ ├── email.blade.php │ │ └── reset.blade.php │ ├── register.blade.php │ └── verify.blade.php ├── layouts │ └── app.blade.php └── twofactor │ ├── index.blade.php │ └── verify.blade.php └── xdebug.ini /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | patreon: awesdotio 4 | open_collective: awesdotio -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Tell us what happens instead of the expected behavior 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | Your issue may already be reported! 11 | Please search on the [issue tracker](../) before creating one. 12 | 13 | ## Expected Behavior 14 | 15 | 16 | 17 | ## Current Behavior 18 | 19 | 20 | 21 | ## Possible Solution 22 | 23 | 24 | 25 | ## Steps to Reproduce (for bugs) 26 | 27 | 28 | 1. 29 | 2. 30 | 3. 31 | 4. 32 | 33 | ## Context 34 | 35 | 36 | 37 | ## Your Environment 38 | 39 | * Version used: 40 | * Browser Name and version: 41 | * Operating System and version (desktop or mobile): 42 | * Link to your project: 43 | 44 | ## System GA 45 | [![Analytics](https://ga-beacon.appspot.com/UA-134431636-1/awes-io/issues)](https://github.com/awes-io/issues) 46 | -------------------------------------------------------------------------------- /.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 | Your issue may already be reported! 11 | Please search on the [issue tracker](../) before creating one. 12 | 13 | ## Expected Behavior 14 | 15 | 16 | 17 | ## Current Behavior 18 | 19 | 20 | 21 | ## Possible Solution 22 | 23 | 24 | 25 | 26 | ## System GA 27 | [![Analytics](https://ga-beacon.appspot.com/UA-134431636-1/awes-io/issues)](https://github.com/awes-io/issues) 28 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | A similar PR may already be submitted! 2 | Please search among the [Pull request](../) before creating one. 3 | 4 | Thanks for submitting a pull request! Please provide enough information so that others can review your pull request: 5 | 6 | 7 | ## Summary 8 | 9 | 10 | 11 | This PR fixes/implements the following **bugs/features** 12 | 13 | * [ ] Bug 1 14 | * [ ] Bug 2 15 | * [ ] Feature 1 16 | * [ ] Feature 2 17 | * [ ] Breaking changes 18 | 19 | 20 | 21 | Explain the **motivation** for making this change. What existing problem does the pull request solve? 22 | 23 | 24 | 25 | ## Test plan (required) 26 | 27 | Demonstrate the code is solid. Example: The exact commands you ran and their output, screenshots / videos if the pull request changes UI. 28 | 29 | 30 | 31 | ## Code formatting 32 | 33 | 34 | 35 | ## Closing issues 36 | 37 | 38 | Fixes # 39 | 40 | ## System GA 41 | [![Analytics](https://ga-beacon.appspot.com/UA-134431636-1/awes-io/awes-io)](https://github.com/awes-io/issues) 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | composer.lock 3 | .idea 4 | /tests/report 5 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: awescodehub/php7.2-fpm-gitlab 2 | 3 | cache: 4 | paths: 5 | - vendor/ 6 | 7 | before_script: 8 | # Install all project dependencies 9 | - composer install 10 | 11 | # Run our tests 12 | test: 13 | only: 14 | - master 15 | - dev 16 | script: 17 | - vendor/bin/phpunit --configuration phpunit.xml.dist --coverage-text --colors=never 18 | coverage: '/^\s*Methods:\s*\d+.\d+\%\s*\(\d+\/\d+\)/' 19 | -------------------------------------------------------------------------------- /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 contact@awes.io. 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 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Awes.io 2 | 3 | Want to contribute to Awes.io? We provide a Contribution Guide to help you get started. 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.2-fpm 2 | 3 | RUN apt-get update && apt-get install -y \ 4 | locales git unzip curl openssl ssh libz-dev \ 5 | libfreetype6-dev libmcrypt-dev libxml2-dev 6 | 7 | RUN pecl install igbinary xdebug \ 8 | && docker-php-ext-install -j$(nproc) mbstring zip bcmath soap \ 9 | && docker-php-ext-enable igbinary xdebug 10 | 11 | RUN dpkg-reconfigure locales \ 12 | && locale-gen C.UTF-8 \ 13 | && /usr/sbin/update-locale LANG=C.UTF-8 14 | 15 | # intl 16 | RUN apt-get update \ 17 | && apt-get install -y libicu-dev \ 18 | && docker-php-ext-configure intl \ 19 | && docker-php-ext-install intl 20 | 21 | RUN echo 'en_US.UTF-8 UTF-8' >> /etc/locale.gen && locale-gen 22 | 23 | ENV LC_ALL C.UTF-8 24 | ENV LANG en_US.UTF-8 25 | ENV LANGUAGE en_US.UTF-8 26 | 27 | RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer 28 | 29 | COPY ./xdebug.ini /usr/local/etc/php/conf.d/xdebug.ini 30 | 31 | RUN sed -i "s/xdebug.remote_autostart=0/xdebug.remote_autostart=1/" /usr/local/etc/php/conf.d/xdebug.ini && \ 32 | sed -i "s/xdebug.remote_enable=0/xdebug.remote_enable=1/" /usr/local/etc/php/conf.d/xdebug.ini && \ 33 | sed -i "s/xdebug.cli_color=0/xdebug.cli_color=1/" /usr/local/etc/php/conf.d/xdebug.ini 34 | 35 | # Clean up 36 | RUN apt-get clean && \ 37 | rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \ 38 | rm /var/log/lastlog /var/log/faillog 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017-present, Awescode GmbH (www.awescode.de) 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Awes.io logo 4 | 5 |

6 | 7 |

Authentication

8 | 9 |

Laravel Authentication package with built-in two-factor (Authy) and social authentication (Socialite).

10 | 11 |

12 | 13 | Coverage report 14 | 15 | 16 | Last version 17 | 18 | 19 | Build status 20 | 21 | 22 | Downloads 23 | 24 | 25 | License 26 | 27 | 28 | CDN Ready 29 | 30 | 31 | laravel 32 | 33 | 34 | Last commit 35 | 36 | 37 | Analytics 38 | 39 | 40 | Hosted by Package Kit 41 | 42 | 43 | Patreon 44 | 45 |

46 | 47 | ## 48 |

49 | Laravel Authentication 50 |

51 | 52 | 53 | ## Table of Contents 54 | 55 | - Installation 56 | - Configuration 57 | - Social and two-factor authentication 58 | - Email verification & resetting passwords 59 | - Usage 60 | - Testing 61 | 62 | ## Installation 63 | 64 | Via Composer 65 | 66 | ``` bash 67 | $ composer require awes-io/auth 68 | ``` 69 | 70 | The package will automatically register itself. 71 | 72 | You can publish migrations: 73 | 74 | ```bash 75 | php artisan vendor:publish --provider="AwesIO\Auth\AuthServiceProvider" --tag="migrations" 76 | ``` 77 | 78 | After migrations have been published you can create required db tables by running: 79 | 80 | ```bash 81 | php artisan migrate 82 | ``` 83 | 84 | Publish views: 85 | 86 | ```bash 87 | php artisan vendor:publish --provider="AwesIO\Auth\AuthServiceProvider" --tag="views" 88 | ``` 89 | 90 | ## Configuration 91 | 92 | Publish config file: 93 | 94 | ```bash 95 | php artisan vendor:publish --provider="AwesIO\Auth\AuthServiceProvider" --tag="config" 96 | ``` 97 | 98 | You can disable additional features by commenting them out: 99 | 100 | ```php 101 | 'enabled' => [ 102 | 'social', 103 | // 'two_factor', 104 | // 'email_verification', 105 | ], 106 | ``` 107 | 108 | Add new socialite services: 109 | 110 | ```php 111 | 'services' => [ 112 | 'github' => [ 113 | 'name' => 'GitHub' 114 | ], 115 | ... 116 | ], 117 | 'github' => [ 118 | 'client_id' => env('GITHUB_CLIENT_ID'), 119 | ... 120 | ], 121 | ``` 122 | 123 | And configure redirect paths: 124 | 125 | ```php 126 | 'redirects' => [ 127 | 'login' => '/twofactor', 128 | 'reset_password' => '/', 129 | ... 130 | ], 131 | ``` 132 | 133 | ### Social and two-factor authentication 134 | 135 | Several .env variables required if additional modules were enabled in config: 136 | 137 | ```php 138 | # SOCIALITE GITHUB 139 | GITHUB_CLIENT_ID= 140 | GITHUB_CLIENT_SECRET= 141 | GITHUB_REDIRECT_URL=http://auth.test/login/github/callback 142 | 143 | # TWO FACTOR AUTHY 144 | AUTHY_SECRET= 145 | ``` 146 | 147 | If you enabled social and/or two factor authentication add respective traits to User model class: 148 | 149 | ```php 150 | use AwesIO\Auth\Models\Traits\HasSocialAuthentication; 151 | use AwesIO\Auth\Models\Traits\HasTwoFactorAuthentication; 152 | 153 | class User extends Authenticatable 154 | { 155 | use HasSocialAuthentication, HasTwoFactorAuthentication; 156 | } 157 | ``` 158 | 159 | ### Email verification & resetting passwords 160 | 161 | To use email verification functionality and to reset passwords, add `SendsEmailVerification` and `SendsPasswordReset` traits: 162 | 163 | ```php 164 | use AwesIO\Auth\Models\Traits\SendsPasswordReset; 165 | use AwesIO\Auth\Models\Traits\SendsEmailVerification; 166 | 167 | class User extends Authenticatable 168 | { 169 | use SendsEmailVerification, SendsPasswordReset; 170 | } 171 | ``` 172 | 173 | ## Usage 174 | 175 | Add to routes/web.php: 176 | 177 | ```php 178 | AwesAuth::routes(); 179 | ``` 180 | 181 | You can disable registration: 182 | 183 | ```php 184 | AwesAuth::routes(['register' => false]); 185 | ``` 186 | 187 | Package will register several routes. 188 | 189 | ##### Besides default authentication routes, it will add: 190 | * Socialite routes 191 | * `'login.social'` 192 | * `'login/{service}/callback'` 193 | * Two factor authentication setup routes 194 | * `'twofactor.index'` 195 | * `'twofactor.store'` 196 | * `'twofactor.destroy'` 197 | * `'twofactor.verify'` 198 | * Two factor authentication login routes 199 | * `'login.twofactor.index'` 200 | * `'login.twofactor.verify'` 201 | * Email verification routes 202 | * `'verification.resend'` 203 | * `'verification.code.verify'` 204 | * `'verification.code'` 205 | * `'verification.verify'` 206 | 207 | ## Testing 208 | 209 | You can run the tests with: 210 | 211 | ```bash 212 | composer test 213 | ``` 214 | 215 | ## Contributing 216 | 217 | Please see [contributing.md](contributing.md) for details and a todolist. 218 | 219 | ## Credits 220 | 221 | - [Galymzhan Begimov](https://github.com/begimov) 222 | - [All Contributors](contributing.md) 223 | 224 | ## License 225 | 226 | [MIT](http://opensource.org/licenses/MIT) 227 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `LocalizationHelper` will be documented in this file. 4 | 5 | ## Version 1.0 6 | 7 | ### Added 8 | - Everything 9 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "awes-io/auth", 3 | "description": "Laravel Authentication package with built-in two-factor (Authy) and social authentication (Socialite).", 4 | "type": "library", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Awescode GmbH", 9 | "email": "info@awescode.de", 10 | "homepage": "https://www.awescode.de", 11 | "role": "Owner" 12 | }, 13 | { 14 | "name": "Galymzhan Begimov", 15 | "email": "begimov@gmail.com", 16 | "homepage": "https://github.com/begimov" 17 | } 18 | ], 19 | "support": { 20 | "email": "support@awescode.de" 21 | }, 22 | "homepage": "https://github.com/awes-io/auth", 23 | "keywords": ["laravel", "auth", "authentication", "authorisation", "authorization", "registration", "two factor authentication", "2fa", "authy", "socialite"], 24 | "require": { 25 | "illuminate/support": "~5|~6", 26 | "laravel/socialite": "^4.0", 27 | "guzzlehttp/guzzle": "^6.3" 28 | 29 | }, 30 | "require-dev": { 31 | "phpunit/phpunit": "~7.0", 32 | "mockery/mockery": "^1.1", 33 | "orchestra/testbench": "~3.0", 34 | "sempro/phpunit-pretty-print": "^1.0" 35 | }, 36 | "autoload": { 37 | "psr-4": { 38 | "AwesIO\\Auth\\": "src/" 39 | } 40 | }, 41 | "autoload-dev": { 42 | "psr-4": { 43 | "AwesIO\\Auth\\Tests\\": "tests/" 44 | } 45 | }, 46 | "scripts": { 47 | "test": "vendor/bin/phpunit --colors=always --configuration phpunit.xml.dist --debug" 48 | }, 49 | "extra": { 50 | "laravel": { 51 | "providers": [ 52 | "AwesIO\\Auth\\AuthServiceProvider" 53 | ], 54 | "aliases": { 55 | "AwesAuth": "AwesIO\\Auth\\Facades\\Auth" 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /config/awesio-auth.php: -------------------------------------------------------------------------------- 1 | [ 11 | 'social', 'two_factor', 'email_verification' 12 | ], 13 | 14 | /* 15 | |-------------------------------------------------------------------------- 16 | | Socialite related parameters 17 | |-------------------------------------------------------------------------- 18 | */ 19 | 'socialite' => [ 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Available socialite services 24 | |-------------------------------------------------------------------------- 25 | */ 26 | 'services' => [ 27 | 'github' => [ 28 | 'name' => 'GitHub' 29 | ] 30 | ], 31 | 32 | /* 33 | |-------------------------------------------------------------------------- 34 | | Services credentials 35 | |-------------------------------------------------------------------------- 36 | */ 37 | 'github' => [ 38 | 'client_id' => env('GITHUB_CLIENT_ID'), 39 | 'client_secret' => env('GITHUB_CLIENT_SECRET'), 40 | 'redirect' => env('GITHUB_REDIRECT_URL'), 41 | ], 42 | 43 | ], 44 | 45 | /* 46 | |-------------------------------------------------------------------------- 47 | | Two factor authentication parameters 48 | |-------------------------------------------------------------------------- 49 | */ 50 | 'two_factor' => [ 51 | 52 | 'authy' => [ 53 | 'secret' => env('AUTHY_SECRET') 54 | ] 55 | ], 56 | 57 | /* 58 | |-------------------------------------------------------------------------- 59 | | Redirects 60 | |-------------------------------------------------------------------------- 61 | */ 62 | 'redirects' => [ 63 | 'login' => '/twofactor', 64 | 'register' => '/twofactor', 65 | 'reset_password' => '/', 66 | 'social_login' => '/twofactor', 67 | 'twofactor_login' => '/twofactor', 68 | 'email_verification' => '/twofactor', 69 | 'forgot_password' => '/password/reset', 70 | 'twofactor' => '/', 71 | ], 72 | 73 | 'mailables' => [ 74 | // 'email_verification' => AwesIO\Mail\Mail\EmailConfirmation::class, 75 | // 'reset_password' => AwesIO\Mail\Mail\ResetPassword::class, 76 | ] 77 | ]; 78 | -------------------------------------------------------------------------------- /database/factories/TwoFactorFactory.php: -------------------------------------------------------------------------------- 1 | define(\AwesIO\Auth\Models\TwoFactor::class, function (Faker $faker) { 17 | return [ 18 | 'phone' => '999 99 99', 19 | 'dial_code' => '+7' 20 | ]; 21 | }); 22 | -------------------------------------------------------------------------------- /database/factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | define(\AwesIO\Auth\Tests\Stubs\User::class, function (Faker $faker) { 17 | return [ 18 | 'name' => $faker->name, 19 | 'email' => $faker->unique()->safeEmail, 20 | 'email_verified_at' => now(), 21 | 'password' => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret 22 | 'remember_token' => str_random(10), 23 | ]; 24 | }); 25 | -------------------------------------------------------------------------------- /database/factories/UserSocialFactory.php: -------------------------------------------------------------------------------- 1 | define(\AwesIO\Auth\Models\UserSocial::class, function (Faker $faker) { 17 | return [ 18 | 'social_id' => uniqid(), 19 | 'user_id' => 1, 20 | 'service' => 'github', 21 | ]; 22 | }); 23 | -------------------------------------------------------------------------------- /database/migrations/create_countries_table.php.stub: -------------------------------------------------------------------------------- 1 | increments('id'); 17 | $table->string('name'); 18 | $table->string('code', 2); 19 | $table->string('dial_code'); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function down() 29 | { 30 | Schema::drop('countries'); 31 | } 32 | } -------------------------------------------------------------------------------- /database/migrations/create_two_factor_table.php.stub: -------------------------------------------------------------------------------- 1 | increments('id'); 17 | $table->integer('user_id')->unsigned()->index(); 18 | $table->string('identifier')->nullable(); 19 | $table->string('phone'); 20 | $table->string('dial_code'); 21 | $table->boolean('verified')->default(false); 22 | $table->timestamps(); 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::drop('two_factor'); 36 | } 37 | } -------------------------------------------------------------------------------- /database/migrations/create_users_social_table.php.stub: -------------------------------------------------------------------------------- 1 | increments('id'); 17 | $table->integer('user_id')->unsigned()->index(); 18 | $table->string('social_id'); 19 | $table->string('service'); 20 | $table->timestamps(); 21 | 22 | $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | * 29 | * @return void 30 | */ 31 | public function down() 32 | { 33 | Schema::drop('users_social'); 34 | } 35 | } -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 |

Authentication

2 | 3 |

Laravel Authentication package with built-in two-factor (Authy) and social authentication (Socialite).

4 | 5 | ## Table of Contents 6 | 7 | - Installation 8 | - Configuration 9 | - Social and two-factor authentication 10 | - Email verification & resetting passwords 11 | - Usage 12 | - Testing 13 | 14 | ## Installation 15 | 16 | Via Composer 17 | 18 | ``` bash 19 | $ composer require awes-io/auth 20 | ``` 21 | 22 | The package will automatically register itself. 23 | 24 | You can publish migrations: 25 | 26 | ```bash 27 | php artisan vendor:publish --provider="AwesIO\Auth\AuthServiceProvider" --tag="migrations" 28 | ``` 29 | 30 | After migrations have been published you can create required db tables by running: 31 | 32 | ```bash 33 | php artisan migrate 34 | ``` 35 | 36 | Publish views: 37 | 38 | ```bash 39 | php artisan vendor:publish --provider="AwesIO\Auth\AuthServiceProvider" --tag="views" 40 | ``` 41 | 42 | ## Configuration 43 | 44 | Publish config file: 45 | 46 | ```bash 47 | php artisan vendor:publish --provider="AwesIO\Auth\AuthServiceProvider" --tag="config" 48 | ``` 49 | 50 | You can disable additional features by commenting them out: 51 | 52 | ```php 53 | 'enabled' => [ 54 | 'social', 55 | // 'two_factor', 56 | // 'email_verification', 57 | ], 58 | ``` 59 | 60 | Add new socialite services: 61 | 62 | ```php 63 | 'services' => [ 64 | 'github' => [ 65 | 'name' => 'GitHub' 66 | ], 67 | ... 68 | ], 69 | 'github' => [ 70 | 'client_id' => env('GITHUB_CLIENT_ID'), 71 | ... 72 | ], 73 | ``` 74 | 75 | And configure redirect paths: 76 | 77 | ```php 78 | 'redirects' => [ 79 | 'login' => '/twofactor', 80 | 'reset_password' => '/', 81 | ... 82 | ], 83 | ``` 84 | 85 | ### Social and two-factor authentication 86 | 87 | Several .env variables required if additional modules were enabled in config: 88 | 89 | ```php 90 | # SOCIALITE GITHUB 91 | GITHUB_CLIENT_ID= 92 | GITHUB_CLIENT_SECRET= 93 | GITHUB_REDIRECT_URL=http://auth.test/login/github/callback 94 | 95 | # TWO FACTOR AUTHY 96 | AUTHY_SECRET= 97 | ``` 98 | 99 | If you enabled social and/or two factor authentication add respective traits to User model class: 100 | 101 | ```php 102 | use AwesIO\Auth\Models\Traits\HasSocialAuthentication; 103 | use AwesIO\Auth\Models\Traits\HasTwoFactorAuthentication; 104 | 105 | class User extends Authenticatable 106 | { 107 | use HasSocialAuthentication, HasTwoFactorAuthentication; 108 | } 109 | ``` 110 | 111 | ### Email verification & resetting passwords 112 | 113 | To use email verification functionality and to reset passwords, add `SendsEmailVerification` and `SendsPasswordReset` traits: 114 | 115 | ```php 116 | use AwesIO\Auth\Models\Traits\SendsPasswordReset; 117 | use AwesIO\Auth\Models\Traits\SendsEmailVerification; 118 | 119 | class User extends Authenticatable 120 | { 121 | use SendsEmailVerification, SendsPasswordReset; 122 | } 123 | ``` 124 | 125 | ## Usage 126 | 127 | Add to routes/web.php: 128 | 129 | ```php 130 | AwesAuth::routes(); 131 | ``` 132 | 133 | You can disable registration: 134 | 135 | ```php 136 | AwesAuth::routes(['register' => false]); 137 | ``` 138 | 139 | Package will register several routes. 140 | 141 | ##### Besides default authentication routes, it will add: 142 | * Socialite routes 143 | * `'login.social'` 144 | * `'login/{service}/callback'` 145 | * Two factor authentication setup routes 146 | * `'twofactor.index'` 147 | * `'twofactor.store'` 148 | * `'twofactor.destroy'` 149 | * `'twofactor.verify'` 150 | * Two factor authentication login routes 151 | * `'login.twofactor.index'` 152 | * `'login.twofactor.verify'` 153 | * Email verification routes 154 | * `'verification.resend'` 155 | * `'verification.code.verify'` 156 | * `'verification.code'` 157 | * `'verification.verify'` 158 | 159 | ## Testing 160 | 161 | You can run the tests with: 162 | 163 | ```bash 164 | composer test 165 | ``` 166 | 167 | ## Contributing 168 | 169 | Please see [contributing.md](contributing.md) for details and a todolist. 170 | 171 | ## Credits 172 | 173 | - [Galymzhan Begimov](https://github.com/begimov) 174 | - [All Contributors](contributing.md) 175 | 176 | ## License 177 | 178 | [MIT](http://opensource.org/licenses/MIT) 179 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./tests/ 15 | 16 | 17 | 18 | 19 | src/ 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/Auth.php: -------------------------------------------------------------------------------- 1 | router = $router; 16 | } 17 | 18 | /** 19 | * Register routes. 20 | * 21 | * @return void 22 | */ 23 | public function routes(array $params = []) 24 | { 25 | // Authentication Routes... 26 | $this->loginRoutes(); 27 | 28 | // Registration Routes... 29 | if ($params['register'] ?? true) { 30 | $this->registrationRoutes(); 31 | } 32 | 33 | // Reset password Routes... 34 | $this->resetPasswordRoutes(); 35 | 36 | // Socialite authentication Routes... 37 | if ($this->isSocialEnabled()) { 38 | $this->socialiteRoutes(); 39 | } 40 | 41 | // Two factor authentication Routes... 42 | if ($this->isTwoFactorEnabled()) { 43 | $this->twoFactorRoutes(); 44 | } 45 | 46 | // Email verification Routes... 47 | if ($this->isEmailVerificationEnabled()) { 48 | $this->emailVerificationRoutes(); 49 | } 50 | } 51 | 52 | /** 53 | * Check if socialite authentication eneabled in config 54 | * 55 | * @return boolean 56 | */ 57 | public function isSocialEnabled() 58 | { 59 | return in_array('social', config('awesio-auth.enabled')); 60 | } 61 | 62 | /** 63 | * Check if two factor authentication eneabled in config 64 | * 65 | * @return boolean 66 | */ 67 | public function isTwoFactorEnabled() 68 | { 69 | return in_array('two_factor', config('awesio-auth.enabled')); 70 | } 71 | 72 | /** 73 | * Check if two factor authentication eneabled in config 74 | * 75 | * @return boolean 76 | */ 77 | public function isEmailVerificationEnabled() 78 | { 79 | return in_array('email_verification', config('awesio-auth.enabled')); 80 | } 81 | 82 | /** 83 | * Login routes. 84 | * 85 | * @return void 86 | */ 87 | protected function loginRoutes() 88 | { 89 | $this->router->get( 90 | 'login', 91 | '\AwesIO\Auth\Controllers\LoginController@showLoginForm' 92 | )->name('login'); 93 | 94 | $this->router->post( 95 | 'login', 96 | '\AwesIO\Auth\Controllers\LoginController@login' 97 | ); 98 | 99 | $this->router->any( 100 | 'logout', 101 | '\AwesIO\Auth\Controllers\LoginController@logout' 102 | )->name('logout'); 103 | } 104 | 105 | /** 106 | * Registration routes. 107 | * 108 | * @return void 109 | */ 110 | protected function registrationRoutes() 111 | { 112 | $this->router->get( 113 | 'register', 114 | '\AwesIO\Auth\Controllers\RegisterController@showRegistrationForm' 115 | )->name('register'); 116 | 117 | $this->router->post( 118 | 'register', 119 | '\AwesIO\Auth\Controllers\RegisterController@register' 120 | ); 121 | } 122 | 123 | /** 124 | * Reset password routes. 125 | * 126 | * @return void 127 | */ 128 | protected function resetPasswordRoutes() 129 | { 130 | $this->router->get( 131 | 'password/reset', 132 | '\AwesIO\Auth\Controllers\ForgotPasswordController@showLinkRequestForm' 133 | )->name('password.request'); 134 | 135 | $this->router->post( 136 | 'password/email', 137 | '\AwesIO\Auth\Controllers\ForgotPasswordController@sendResetLinkEmail' 138 | )->name('password.email'); 139 | 140 | $this->router->get( 141 | 'password/reset/{token}', 142 | '\AwesIO\Auth\Controllers\ResetPasswordController@showResetForm' 143 | )->name('password.reset'); 144 | 145 | $this->router->post( 146 | 'password/reset', 147 | '\AwesIO\Auth\Controllers\ResetPasswordController@reset' 148 | )->name('password.update'); 149 | } 150 | 151 | /** 152 | * Socialite routes. 153 | * 154 | * @return void 155 | */ 156 | protected function socialiteRoutes() 157 | { 158 | $this->router->middleware(['guest', SocialAuthentication::class]) 159 | ->group(function () { 160 | 161 | $this->router->get( 162 | 'login/{service}', 163 | '\AwesIO\Auth\Controllers\SocialLoginController@redirect' 164 | )->name('login.social'); 165 | 166 | $this->router->get( 167 | 'login/{service}/callback', 168 | '\AwesIO\Auth\Controllers\SocialLoginController@callback' 169 | ); 170 | }); 171 | } 172 | 173 | /** 174 | * Two factor routes. 175 | * 176 | * @return void 177 | */ 178 | protected function twoFactorRoutes() 179 | { 180 | // Setting up and verifying 2FA routes 181 | $this->router->middleware(['auth']) 182 | ->group(function () { 183 | 184 | $this->router->get( 185 | 'twofactor', 186 | '\AwesIO\Auth\Controllers\TwoFactorController@index' 187 | )->name('twofactor.index'); 188 | 189 | $this->router->post( 190 | 'twofactor', 191 | '\AwesIO\Auth\Controllers\TwoFactorController@store' 192 | )->name('twofactor.store'); 193 | 194 | $this->router->post( 195 | 'twofactor/verify', 196 | '\AwesIO\Auth\Controllers\TwoFactorController@verify' 197 | )->name('twofactor.verify'); 198 | 199 | $this->router->delete( 200 | 'twofactor', 201 | '\AwesIO\Auth\Controllers\TwoFactorController@destroy' 202 | )->name('twofactor.destroy'); 203 | }); 204 | 205 | // 2FA login routes 206 | $this->router->middleware(['guest']) 207 | ->group(function () { 208 | 209 | $this->router->get( 210 | 'login/twofactor/verify', 211 | '\AwesIO\Auth\Controllers\TwoFactorLoginController@index' 212 | )->name('login.twofactor.index'); 213 | 214 | $this->router->post( 215 | 'login/twofactor/verify', 216 | '\AwesIO\Auth\Controllers\TwoFactorLoginController@verify' 217 | )->name('login.twofactor.verify'); 218 | }); 219 | } 220 | 221 | /** 222 | * Email verification routes. 223 | * 224 | * @return void 225 | */ 226 | protected function emailVerificationRoutes() 227 | { 228 | $this->router->get( 229 | 'email/verify', 230 | '\AwesIO\Auth\Controllers\VerificationController@show' 231 | )->name('verification.code'); 232 | 233 | $this->router->post( 234 | 'email/verify', 235 | '\AwesIO\Auth\Controllers\VerificationController@verifyCode' 236 | )->name('verification.code.verify'); 237 | 238 | $this->router->get( 239 | 'email/verify/{id}', 240 | '\AwesIO\Auth\Controllers\VerificationController@verify' 241 | )->name('verification.verify'); 242 | 243 | $this->router->get( 244 | 'email/resend', 245 | '\AwesIO\Auth\Controllers\VerificationController@resend' 246 | )->name('verification.resend')->middleware('throttle:1,0.2'); 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /src/AuthServiceProvider.php: -------------------------------------------------------------------------------- 1 | loadViewsFrom(__DIR__.'/../views', 'awesio-auth'); 29 | 30 | $this->loadMigrationsFrom(__DIR__.'/../database/migrations'); 31 | 32 | $this->publishes([ 33 | __DIR__.'/../config/awesio-auth.php' => config_path('awesio-auth.php'), 34 | ], 'config'); 35 | 36 | $this->bootMigrationsPublishing(); 37 | 38 | $this->publishes([ 39 | __DIR__.'/../views' => resource_path('views/vendor/awesio-auth'), 40 | ], 'views'); 41 | 42 | Event::subscribe(EventSubscriber::class); 43 | } 44 | 45 | /** 46 | * Register any application services. 47 | * 48 | * @return void 49 | */ 50 | public function register() 51 | { 52 | $this->mergeConfigFrom(__DIR__.'/../config/awesio-auth.php', 'awesio-auth'); 53 | 54 | $this->app->singleton(AuthContract::class, Auth::class); 55 | 56 | $this->registerRepositories(); 57 | 58 | $this->registerServices(); 59 | 60 | $this->registerHelpers(); 61 | } 62 | 63 | /** 64 | * Register and bind package repositories 65 | * 66 | * @return void 67 | */ 68 | protected function registerRepositories() 69 | { 70 | $this->app->bind(UserRepository::class, EloquentUserRepository::class); 71 | } 72 | 73 | /** 74 | * Register and bind package services 75 | * 76 | * @return void 77 | */ 78 | protected function registerServices() 79 | { 80 | $this->app->bind(SocialProvidersManager::class, SocialiteProvidersManager::class); 81 | 82 | $this->app->singleton(TwoFactor::class, function () { 83 | return new AuthyTwoFactor(new Client()); 84 | }); 85 | } 86 | 87 | /** 88 | * Prepare migrations for publication 89 | * 90 | * @return void 91 | */ 92 | protected function bootMigrationsPublishing() 93 | { 94 | $this->publishes([ 95 | __DIR__.'/../database/migrations/create_countries_table.php.stub' 96 | => database_path('migrations/'.date('Y_m_d_His', time()).'_create_countries_table.php'), 97 | __DIR__.'/../database/migrations/create_two_factor_table.php.stub' 98 | => database_path('migrations/'.date('Y_m_d_His', time()).'_create_two_factor_table.php'), 99 | __DIR__.'/../database/migrations/create_users_social_table.php.stub' 100 | => database_path('migrations/'.date('Y_m_d_His', time()).'_create_users_social_table.php'), 101 | ], 'migrations'); 102 | } 103 | 104 | /** 105 | * Register helpers file 106 | */ 107 | public function registerHelpers() 108 | { 109 | if (file_exists($file = __DIR__ . '/helpers.php')) { 110 | require $file; 111 | } 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /src/Contracts/Auth.php: -------------------------------------------------------------------------------- 1 | middleware('guest'); 33 | } 34 | 35 | /** 36 | * Display the form to request a password reset link. 37 | * 38 | * @return \Illuminate\Http\Response 39 | */ 40 | public function showLinkRequestForm() 41 | { 42 | return view('awesio-auth::auth.passwords.email'); 43 | } 44 | 45 | /** 46 | * Get the response for a successful password reset link. 47 | * 48 | * @param \Illuminate\Http\Request $request 49 | * @param string $response 50 | * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse 51 | */ 52 | protected function sendResetLinkResponse(Request $request, $response) 53 | { 54 | if ($request->ajax()) { 55 | return $this->ajaxRedirectTo($request); 56 | } 57 | 58 | return redirect($this->redirectPath()) 59 | ->with('status', trans($response)); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Controllers/LoginController.php: -------------------------------------------------------------------------------- 1 | middleware('guest')->except('logout'); 29 | } 30 | 31 | /** 32 | * Show the application's login form. 33 | * 34 | * @return \Illuminate\Http\Response 35 | */ 36 | public function showLoginForm() 37 | { 38 | return view('awesio-auth::auth.login'); 39 | } 40 | 41 | /** 42 | * The user has been authenticated. 43 | * 44 | * @param \Illuminate\Http\Request $request 45 | * @param mixed $user 46 | * @return mixed 47 | */ 48 | protected function authenticated(Request $request, $user) 49 | { 50 | if ($this->isTwoFactorEnabled($user)) { 51 | return $this->handleTwoFactorAuthentication($request, $user); 52 | } 53 | 54 | if ($request->ajax()) { 55 | return $this->ajaxRedirectTo($request); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Controllers/RegisterController.php: -------------------------------------------------------------------------------- 1 | middleware('guest'); 33 | 34 | $this->users = $users; 35 | } 36 | 37 | /** 38 | * Show the application registration form. 39 | * 40 | * @return \Illuminate\Http\Response 41 | */ 42 | public function showRegistrationForm() 43 | { 44 | return view('awesio-auth::auth.register'); 45 | } 46 | 47 | /** 48 | * Get a validator for an incoming registration request. 49 | * 50 | * @param array $data 51 | * @return \Illuminate\Contracts\Validation\Validator 52 | */ 53 | protected function validator(array $data) 54 | { 55 | return Validator::make($data, [ 56 | 'name' => ['required', 'string', 'max:255'], 57 | 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], 58 | 'password' => ['required', 'string', 'min:6', 'confirmed'], 59 | ]); 60 | } 61 | 62 | /** 63 | * Create a new user instance after a valid registration. 64 | * 65 | * @param array $data 66 | * @return \App\User 67 | */ 68 | protected function create(array $data) 69 | { 70 | return $this->users->store([ 71 | 'name' => $data['name'], 72 | 'email' => $data['email'], 73 | 'password' => Hash::make($data['password']), 74 | ]); 75 | } 76 | 77 | /** 78 | * The user has been registered. 79 | * 80 | * @param \Illuminate\Http\Request $request 81 | * @param mixed $user 82 | * @return mixed 83 | */ 84 | protected function registered(Request $request, $user) 85 | { 86 | if ($request->ajax()) { 87 | return $this->ajaxRedirectTo($request); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Controllers/ResetPasswordController.php: -------------------------------------------------------------------------------- 1 | middleware('guest'); 39 | } 40 | 41 | /** 42 | * Display the password reset view for the given token. 43 | * 44 | * If no token is present, display the link request form. 45 | * 46 | * @param \Illuminate\Http\Request $request 47 | * @param string|null $token 48 | * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View 49 | */ 50 | public function showResetForm(Request $request, $token = null) 51 | { 52 | return view('awesio-auth::auth.passwords.reset')->with( 53 | ['token' => $token, 'email' => $request->email] 54 | ); 55 | } 56 | 57 | /** 58 | * Get the response for a successful password reset. 59 | * 60 | * @param \Illuminate\Http\Request $request 61 | * @param string $response 62 | * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse 63 | */ 64 | protected function sendResetResponse(Request $request, $response) 65 | { 66 | if ($request->ajax()) { 67 | return $this->ajaxRedirectTo($request); 68 | } 69 | 70 | return redirect($this->redirectPath()) 71 | ->with('status', trans($response)); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Controllers/SocialLoginController.php: -------------------------------------------------------------------------------- 1 | users = $users; 49 | 50 | $this->provider = $provider; 51 | } 52 | 53 | /** 54 | * Redirect to OAuth provider authentication page 55 | * 56 | * @param \Illuminate\Http\Request $request 57 | * @param string $service 58 | * @return void 59 | */ 60 | public function redirect(Request $request, $service) 61 | { 62 | return $this->provider 63 | ->buildProvider($service) 64 | ->redirect(); 65 | } 66 | 67 | /** 68 | * Obtain the user information from OAuth provider 69 | * 70 | * @param \Illuminate\Http\Request $request 71 | * @param string $service 72 | * @return void 73 | */ 74 | public function callback(Request $request, $service) 75 | { 76 | $serviceUser = $this->provider 77 | ->buildProvider($service) 78 | ->user(); 79 | 80 | $user = $this->users 81 | ->getUserBySocial($serviceUser, $service); 82 | 83 | if (! $user) { 84 | $user = $this->createUser($serviceUser); 85 | } 86 | 87 | if (! $user->hasSocial($service)) { 88 | $this->createUsersSocial($user, $serviceUser, $service); 89 | } 90 | 91 | Auth::login($user); 92 | 93 | return $this->authenticated($request, $user) 94 | ?: redirect()->intended($this->redirectPath()); 95 | } 96 | 97 | /** 98 | * Create new user using service credentials 99 | * 100 | * @param \Laravel\Socialite\Two\User $serviceUser 101 | * @return \App\User 102 | */ 103 | protected function createUser($serviceUser) 104 | { 105 | return $this->users->store([ 106 | 'password' => bcrypt(str_random(40)), 107 | 'name' => $serviceUser->getName() ?: $serviceUser->getNickname(), 108 | 'email' => $serviceUser->getEmail() 109 | ]); 110 | } 111 | 112 | /** 113 | * Create new user's social record using service credentials 114 | * 115 | * @param \App\User $user 116 | * @param \Laravel\Socialite\Two\User $serviceUser 117 | * @param string $service 118 | * @return \AwesIO\Auth\Models\UserSocial 119 | */ 120 | protected function createUsersSocial($user, $serviceUser, $service) 121 | { 122 | return $user->social()->create([ 123 | 'social_id' => $serviceUser->getId(), 124 | 'service' => $service 125 | ]); 126 | } 127 | 128 | /** 129 | * The user has been authenticated. 130 | * 131 | * @param \Illuminate\Http\Request $request 132 | * @param mixed $user 133 | * @return mixed 134 | */ 135 | protected function authenticated(Request $request, $user) 136 | { 137 | if ($this->isTwoFactorEnabled($user)) { 138 | return $this->handleTwoFactorAuthentication($request, $user); 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/Controllers/Traits/AuthenticatesUsersWith2FA.php: -------------------------------------------------------------------------------- 1 | isTwoFactorEnabled(); 14 | } 15 | 16 | /** 17 | * Handle 2FA, stores user data in session, logs out and redirects 18 | * 19 | * @param Request $request 20 | * @param $user 21 | * @return \Illuminate\Http\Response 22 | */ 23 | protected function handleTwoFactorAuthentication(Request $request, $user) 24 | { 25 | $request->session()->put('two_factor', (object) [ 26 | 'user_id' => $user->id, 27 | 'remember' => $request->has('remember') 28 | ]); 29 | 30 | Auth::logout(); 31 | 32 | if ($request->ajax()) { 33 | return response()->json([ 34 | 'redirectUrl' => route('login.twofactor.index', [], false) 35 | ]); 36 | } 37 | 38 | return redirect()->route('login.twofactor.index'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Controllers/Traits/RedirectsTo.php: -------------------------------------------------------------------------------- 1 | 'login', 14 | 'AwesIO\Auth\Controllers\RegisterController' => 'register', 15 | 'AwesIO\Auth\Controllers\ResetPasswordController' => 'reset_password', 16 | 'AwesIO\Auth\Controllers\SocialLoginController' => 'social_login', 17 | 'AwesIO\Auth\Controllers\TwoFactorLoginController' => 'twofactor_login', 18 | 'AwesIO\Auth\Controllers\VerificationController' => 'email_verification', 19 | 'AwesIO\Auth\Controllers\ForgotPasswordController' => 'forgot_password', 20 | 'AwesIO\Auth\Controllers\TwoFactorController' => 'twofactor', 21 | ]; 22 | 23 | /** 24 | * Redirect to path which set in config or to default one 25 | * 26 | * @return string 27 | */ 28 | protected function redirectTo() 29 | { 30 | return $this->getRedirectToUrl() 31 | ?: (property_exists($this, 'redirectTo') ? $this->redirectTo : '/'); 32 | } 33 | 34 | protected function ajaxRedirectTo($request) 35 | { 36 | return response()->json([ 37 | 'redirectUrl' => redirect() 38 | ->intended($this->getRedirectToUrl()) 39 | ->getTargetUrl() 40 | ]); 41 | } 42 | 43 | protected function getRedirectToUrl() 44 | { 45 | return config('awesio-auth.redirects.' . $this->redirectMappings[get_class($this)]); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Controllers/TwoFactorController.php: -------------------------------------------------------------------------------- 1 | user()->isTwoFactorPending()) { 32 | $qrCode = app()->make(TwoFactor::class)->qrCode(auth()->user()); 33 | } 34 | 35 | // $countries = Country::all(); 36 | 37 | return view('awesio-auth::twofactor.index', compact('countries', 'qrCode')); 38 | } 39 | 40 | /** 41 | * Store new user's two factor record 42 | * 43 | * @param \AwesIO\Auth\Requests\TwoFactorStoreRequest $request 44 | * @param \AwesIO\Auth\Services\Contracts\TwoFactor $twoFactor 45 | * @return void 46 | */ 47 | public function store(TwoFactorStoreRequest $request, TwoFactor $twoFactor) 48 | { 49 | $codeAndPhone = preg_split('/\s/', $request->phone, 2); 50 | 51 | $user = $request->user(); 52 | 53 | $user->twoFactor()->create([ 54 | 'phone' => $codeAndPhone[1], 55 | 'dial_code' => $codeAndPhone[0], 56 | ]); 57 | 58 | if ($response = $twoFactor->register($user)) { 59 | $user->twoFactor()->update([ 60 | 'identifier' => $response->user->id 61 | ]); 62 | } 63 | return $this->twofactored($request) ?: back(); 64 | } 65 | 66 | /** 67 | * Verify two factor token 68 | * 69 | * @param \AwesIO\Auth\Requests\TwoFactorVerifyRequest $request 70 | * @return \Illuminate\Http\Response 71 | */ 72 | public function verify(TwoFactorVerifyRequest $request) 73 | { 74 | $request->user()->twoFactor()->update([ 75 | 'verified' => true 76 | ]); 77 | 78 | return $this->twofactored($request) ?: back(); 79 | } 80 | 81 | /** 82 | * Remove user from two factor service and application's db 83 | * 84 | * @param \Illuminate\Http\Request $request 85 | * @param \AwesIO\Auth\Services\Contracts\TwoFactor $twoFactor 86 | * @return void 87 | */ 88 | public function destroy(Request $request, TwoFactor $twoFactor) 89 | { 90 | if ($twoFactor->remove($user = $request->user())) { 91 | 92 | $user->twoFactor()->delete(); 93 | } 94 | return $this->twofactored($request) ?: back(); 95 | } 96 | 97 | protected function twofactored(Request $request) 98 | { 99 | if ($request->ajax()) { 100 | return $this->ajaxRedirectTo($request); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/Controllers/TwoFactorLoginController.php: -------------------------------------------------------------------------------- 1 | user()->id, session('two_factor')->remember); 41 | 42 | session()->forget('two_factor'); 43 | 44 | return $this->authenticated($request) 45 | ?: redirect()->intended($this->redirectPath()); 46 | } 47 | 48 | /** 49 | * The user has been authenticated. 50 | * 51 | * @param \Illuminate\Http\Request $request 52 | * @param mixed $user 53 | * @return mixed 54 | */ 55 | protected function authenticated(Request $request) 56 | { 57 | if ($request->ajax()) { 58 | return $this->ajaxRedirectTo($request); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Controllers/VerificationController.php: -------------------------------------------------------------------------------- 1 | middleware('auth'); 42 | $this->middleware('signed')->only('verify'); 43 | $this->middleware('throttle:6,1')->only('verify', 'resend'); 44 | } 45 | 46 | /** 47 | * Show the email verification by code page. 48 | * 49 | * @param \Illuminate\Http\Request $request 50 | * @return \Illuminate\Http\Response 51 | */ 52 | public function show(Request $request) 53 | { 54 | return $request->user()->hasVerifiedEmail() 55 | ? redirect($this->redirectPath()) 56 | : view('awesio-auth::auth.verify'); 57 | } 58 | 59 | public function verifyCode(Request $request) 60 | { 61 | $code = Session::get('email_verification'); 62 | 63 | if (! $this->isValidCode($code, $request->code)) { 64 | abort(403, "Verification code is invalid or has been expired"); 65 | } 66 | 67 | Session::forget('email_verification'); 68 | 69 | $request->user()->markEmailAsVerified(); 70 | } 71 | 72 | /** 73 | * Mark the authenticated user's email address as verified. 74 | * 75 | * @param \Illuminate\Http\Request $request 76 | * @return \Illuminate\Http\Response 77 | * @throws \Illuminate\Auth\Access\AuthorizationException 78 | */ 79 | public function verify(Request $request) 80 | { 81 | if ($request->route('id') != $request->user()->getKey()) { 82 | throw new AuthorizationException; 83 | } 84 | 85 | if ($request->user()->hasVerifiedEmail()) { 86 | return redirect($this->redirectPath()); 87 | } 88 | 89 | if ($request->user()->markEmailAsVerified()) { 90 | event(new Verified($request->user())); 91 | } 92 | 93 | return redirect($this->redirectPath())->with('verified', true); 94 | } 95 | 96 | /** 97 | * Resend the email verification notification. 98 | * 99 | * @param \Illuminate\Http\Request $request 100 | * @return \Illuminate\Http\Response 101 | */ 102 | public function resend(Request $request) 103 | { 104 | if ($request->user()->hasVerifiedEmail()) { 105 | return redirect($this->redirectPath()); 106 | } 107 | 108 | $request->user()->sendEmailVerificationNotification(); 109 | 110 | // return back()->with('resent', true); 111 | } 112 | 113 | protected function isValidCode($stored, $recieved) 114 | { 115 | return !empty($stored) 116 | && $stored['code'] == $recieved 117 | && now()->lt($stored['expire']); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/Facades/Auth.php: -------------------------------------------------------------------------------- 1 | listen( 13 | 'Illuminate\Auth\Events\Registered', 14 | static::class.'@handleRegistered' 15 | ); 16 | } 17 | 18 | public function handleRegistered($event) 19 | { 20 | if (AwesAuth::isEmailVerificationEnabled() 21 | && ! $event->user->hasVerifiedEmail()) { 22 | 23 | $event->user->sendEmailVerificationNotification(); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/Middlewares/SocialAuthentication.php: -------------------------------------------------------------------------------- 1 | isServiceAvailable($request->service)) { 19 | return redirect()->route('login'); 20 | } 21 | return $next($request); 22 | } 23 | 24 | /** 25 | * Check if socialite service is enabled in config 26 | * 27 | * @param string $service 28 | * @return boolean 29 | */ 30 | protected function isServiceAvailable($service) 31 | { 32 | return in_array( 33 | strtolower($service), 34 | array_keys(config('awesio-auth.socialite.services')) 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Models/Country.php: -------------------------------------------------------------------------------- 1 | hasMany(UserSocial::class); 17 | } 18 | 19 | /** 20 | * Check if has any \AwesIO\Auth\Models\UserSocial relationships. 21 | * 22 | * @param string $service 23 | * @return boolean 24 | */ 25 | public function hasSocial($service) 26 | { 27 | return $this->social->where('service', $service)->count() > 0; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Models/Traits/HasTwoFactorAuthentication.php: -------------------------------------------------------------------------------- 1 | hasOne(TwoFactor::class); 17 | } 18 | 19 | /** 20 | * Check if two-factor auth is enabled 21 | * 22 | * @return boolean 23 | */ 24 | public function isTwoFactorEnabled() 25 | { 26 | return (bool) optional($this->twoFactor)->isVerified(); 27 | } 28 | 29 | /** 30 | * Check if two-factor verification pending 31 | * 32 | * @return boolean 33 | */ 34 | public function isTwoFactorPending() 35 | { 36 | if (! $this->twoFactor) { 37 | return false; 38 | } 39 | return ! $this->twoFactor->isVerified(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Models/Traits/SendsEmailVerification.php: -------------------------------------------------------------------------------- 1 | generateVerificationCode(); 19 | 20 | $expire = now()->addMinutes(60); 21 | 22 | if ($mailable = config('awesio-auth.mailables.email_verification')) { 23 | 24 | VerifyEmail::toMailUsing( 25 | function ($notifiable) use ($mailable, $code, $expire) { 26 | 27 | $url = URL::temporarySignedRoute( 28 | 'verification.verify', 29 | $expire, 30 | ['id' => $notifiable->getKey()] 31 | ); 32 | 33 | return (new $mailable($code, $url)) 34 | ->to($notifiable->email); 35 | } 36 | ); 37 | } 38 | Session::put( 39 | 'email_verification', 40 | ['code' => $code, 'expire' => $expire] 41 | ); 42 | 43 | $this->notify(new VerifyEmail); 44 | } 45 | 46 | /** 47 | * Generate six digits verification code 48 | * 49 | * @throws \Exception 50 | */ 51 | public function generateVerificationCode() 52 | { 53 | $code = ''; 54 | 55 | for ($i = 0; $i < 6; $i++) { 56 | $code .= random_int(0, 9); 57 | } 58 | 59 | return $code; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Models/Traits/SendsPasswordReset.php: -------------------------------------------------------------------------------- 1 | to($notifiable->email); 27 | } 28 | ); 29 | } 30 | $this->notify(new ResetPasswordNotification($token)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Models/TwoFactor.php: -------------------------------------------------------------------------------- 1 | user->twoFactor)->delete(); 31 | }); 32 | } 33 | 34 | /** 35 | * Define an inverse \App\User relationship. 36 | * 37 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo 38 | */ 39 | public function user() 40 | { 41 | return $this->belongsTo(getModelForGuard(config('auth.defaults.guard'))); 42 | } 43 | 44 | /** 45 | * Determine if two factor auth is verified 46 | * 47 | * @return boolean 48 | */ 49 | public function isVerified() 50 | { 51 | return $this->verified; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Models/UserSocial.php: -------------------------------------------------------------------------------- 1 | belongsTo(getModelForGuard(config('auth.defaults.guard'))); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Repositories/Contracts/UserRepository.php: -------------------------------------------------------------------------------- 1 | getEmail()) 19 | ->orWhereHas('social', 20 | function ($query) use ($serviceUser, $service) { 21 | $query->where('social_id', $serviceUser->getId())->where('service', $service); 22 | } 23 | )->first(); 24 | } 25 | 26 | /** 27 | * Create new user 28 | * 29 | * @param array $data 30 | * @return 31 | */ 32 | public function store(array $data) 33 | { 34 | return getModelForGuard(config('auth.defaults.guard'))::create($data); 35 | } 36 | } -------------------------------------------------------------------------------- /src/Requests/TwoFactorStoreRequest.php: -------------------------------------------------------------------------------- 1 | ['required', new ValidPhone], 29 | // 'dial_code' => 'required|exists:countries,dial_code', 30 | ]; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Requests/TwoFactorVerifyRequest.php: -------------------------------------------------------------------------------- 1 | twoFactor = $twoFactor; 21 | } 22 | 23 | /** 24 | * Determine if the user is authorized to make this request. 25 | * 26 | * @return bool 27 | */ 28 | public function authorize() 29 | { 30 | return true; 31 | } 32 | 33 | /** 34 | * Get the validation rules that apply to the request. 35 | * 36 | * @return array 37 | */ 38 | public function rules() 39 | { 40 | if ($data = session('two_factor')) { 41 | $this->setUserResolver($this->userResolver($data)); 42 | } 43 | 44 | return [ 45 | 'token' => [ 46 | 'required', 47 | new ValidTwoFactorToken($this->user(), $this->twoFactor) 48 | ], 49 | ]; 50 | } 51 | 52 | /** 53 | * Get custom user resolver 54 | * 55 | * @param object $data 56 | * @return \Closure 57 | */ 58 | protected function userResolver($data) 59 | { 60 | return function () use ($data) { 61 | return getModelForGuard(config('auth.defaults.guard'))::find($data->user_id); 62 | }; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Rules/ValidPhone.php: -------------------------------------------------------------------------------- 1 | user = $user; 30 | 31 | $this->twoFactor = $twoFactor; 32 | } 33 | 34 | /** 35 | * Determine if the validation rule passes. 36 | * 37 | * @param string $attribute 38 | * @param mixed $value 39 | * @return bool 40 | */ 41 | public function passes($attribute, $value) 42 | { 43 | return $this->twoFactor->verifyToken($this->user, $value); 44 | } 45 | 46 | /** 47 | * Get the validation error message. 48 | * 49 | * @return string 50 | */ 51 | public function message() 52 | { 53 | return 'Invalid two factor token'; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Seeds/CountryTableSeeder.php: -------------------------------------------------------------------------------- 1 | 'Germany', 20 | 'code' => 'DE', 21 | 'dial_code' => '49', 22 | ], 23 | [ 24 | 'name' => 'Russia', 25 | 'code' => 'RU', 26 | 'dial_code' => '7', 27 | ], 28 | [ 29 | 'name' => 'Ukraine', 30 | 'code' => 'UA', 31 | 'dial_code' => '380', 32 | ], 33 | ]; 34 | 35 | Country::insert($countries); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Services/AuthyTwoFactor.php: -------------------------------------------------------------------------------- 1 | client = $client; 20 | } 21 | 22 | /** 23 | * Register user on Authy 24 | * 25 | * @param $user 26 | * @return boolean|object 27 | */ 28 | public function register($user) 29 | { 30 | try { 31 | $response = $this->client->request( 32 | 'POST', 33 | 'https://api.authy.com/protected/json/users/new?api_key=' 34 | . config('awesio-auth.two_factor.authy.secret'), [ 35 | 'form_params' => $this->getUserRegistrationPayload($user) 36 | ] 37 | ); 38 | } catch (\Exception $e) { 39 | return false; 40 | } 41 | return json_decode($response->getBody()); 42 | } 43 | 44 | /** 45 | * Verify if 2FA token is valid 46 | * 47 | * @param $user 48 | * @param string $token 49 | * @return boolean 50 | */ 51 | public function verifyToken($user, $token) 52 | { 53 | try { 54 | $response = $this->client->request( 55 | 'GET', 56 | 'https://api.authy.com/protected/json/verify/' 57 | . $token . '/' . $user->twoFactor->identifier 58 | . '?force=true&api_key=' . config('awesio-auth.two_factor.authy.secret') 59 | ); 60 | } catch (\Exception $e) { 61 | return false; 62 | } 63 | 64 | $response = json_decode($response->getBody()); 65 | 66 | return $response->token === 'is valid'; 67 | } 68 | 69 | /** 70 | * Remove user from Authy 71 | * 72 | * @param $user 73 | * @return boolean 74 | */ 75 | public function remove($user) 76 | { 77 | try { 78 | $response = $this->client->request( 79 | 'POST', 80 | 'https://api.authy.com/protected/json/users/delete/' 81 | . $user->twoFactor->identifier 82 | . '?api_key=' . config('awesio-auth.two_factor.authy.secret') 83 | ); 84 | } catch (\Exception $e) { 85 | return false; 86 | } 87 | return true; 88 | } 89 | 90 | /** 91 | * Request QR code link. 92 | * 93 | * @param array $user 94 | * 95 | * @return mixed 96 | */ 97 | public function qrCode($user) 98 | { 99 | try { 100 | $response = $this->client->request( 101 | 'POST', 102 | 'https://api.authy.com/protected/json/users/' 103 | . $user->twoFactor->identifier 104 | . '/secret?api_key=' . config('awesio-auth.two_factor.authy.secret') 105 | ); 106 | } catch (\Exception $e) { 107 | return false; 108 | } 109 | return json_decode($response->getBody()); 110 | } 111 | 112 | /** 113 | * Get data needed for user registration on Authy 114 | * 115 | * @param $user 116 | * @return array 117 | */ 118 | protected function getUserRegistrationPayload($user) 119 | { 120 | return [ 121 | 'user' => [ 122 | 'email' => $user->email, 123 | 'cellphone' => $user->twoFactor->phone, 124 | 'country_code' => $user->twoFactor->dial_code 125 | ] 126 | ]; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/Services/Contracts/SocialProvidersManager.php: -------------------------------------------------------------------------------- 1 | providerClassName(ucfirst($service)), 20 | $this->getConfig($service) 21 | ); 22 | } 23 | 24 | /** 25 | * Generate provider full class name 26 | * 27 | * @param string $prefix 28 | * @return string 29 | */ 30 | protected function providerClassName($prefix) 31 | { 32 | return "\Laravel\Socialite\Two\\{$prefix}Provider"; 33 | } 34 | 35 | /** 36 | * Get provider credentials from package config 37 | * 38 | * @param string $service 39 | * @return array 40 | */ 41 | protected function getConfig($service) 42 | { 43 | return config('awesio-auth.socialite.' . $service); 44 | } 45 | } -------------------------------------------------------------------------------- /src/helpers.php: -------------------------------------------------------------------------------- 1 | map(function ($guard) { 13 | if (! isset($guard['provider'])) { 14 | return; 15 | } 16 | return config("auth.providers.{$guard['provider']}.model"); 17 | })->get($guard); 18 | } 19 | } -------------------------------------------------------------------------------- /tests/Feature/AuthRoutesTest.php: -------------------------------------------------------------------------------- 1 | get('login'); 14 | 15 | $response->assertStatus(200); 16 | } 17 | 18 | /** @test */ 19 | public function it_has_post_login_route() 20 | { 21 | $response = $this->post('login'); 22 | 23 | $response->assertStatus(302); 24 | } 25 | 26 | /** @test */ 27 | public function it_has_logout_route() 28 | { 29 | $response = $this->post('logout'); 30 | 31 | $response->assertStatus(302); 32 | } 33 | 34 | /** @test */ 35 | public function it_has_get_registration_route() 36 | { 37 | $response = $this->get('register'); 38 | 39 | $response->assertStatus(200); 40 | } 41 | 42 | /** @test */ 43 | public function it_has_post_registration_route() 44 | { 45 | $response = $this->post('register'); 46 | 47 | $response->assertStatus(302); 48 | } 49 | 50 | /** @test */ 51 | public function it_has_login_social_route() 52 | { 53 | $response = $this->get('login/service'); 54 | 55 | $response->assertStatus(302); 56 | } 57 | 58 | /** @test */ 59 | public function it_has_login_social_callback_route() 60 | { 61 | $response = $this->get('login/service/callback'); 62 | 63 | $response->assertStatus(302); 64 | } 65 | 66 | /** @test */ 67 | public function it_has_two_factor_index_route() 68 | { 69 | $response = $this->get('twofactor'); 70 | 71 | $response->assertStatus(302); 72 | } 73 | 74 | /** @test */ 75 | public function it_has_two_factor_store_route() 76 | { 77 | $response = $this->post('twofactor'); 78 | 79 | $response->assertStatus(302); 80 | } 81 | 82 | /** @test */ 83 | public function it_has_two_factor_verify_route() 84 | { 85 | $response = $this->post('twofactor/verify'); 86 | 87 | $response->assertStatus(302); 88 | } 89 | 90 | /** @test */ 91 | public function it_has_two_factor_delete_route() 92 | { 93 | $response = $this->delete('twofactor'); 94 | 95 | $response->assertStatus(302); 96 | } 97 | 98 | /** @test */ 99 | public function it_has_login_two_factor_index_route() 100 | { 101 | $response = $this->get('login/twofactor/verify'); 102 | 103 | $response->assertStatus(200); 104 | } 105 | 106 | /** @test */ 107 | public function it_has_login_verification_code_route() 108 | { 109 | $response = $this->get('email/verify'); 110 | 111 | $response->assertStatus(302); 112 | } 113 | 114 | /** @test */ 115 | public function it_has_login_verification_code_verify_route() 116 | { 117 | $response = $this->post('email/verify'); 118 | 119 | $response->assertStatus(302); 120 | } 121 | 122 | /** @test */ 123 | public function it_has_login_verification_verify_route() 124 | { 125 | $response = $this->get('email/verify/1'); 126 | 127 | $response->assertStatus(302); 128 | } 129 | 130 | /** @test */ 131 | public function it_has_login_verification_resend_route() 132 | { 133 | $response = $this->get('email/resend'); 134 | 135 | $response->assertStatus(302); 136 | } 137 | } -------------------------------------------------------------------------------- /tests/Feature/ForgotPasswordTest.php: -------------------------------------------------------------------------------- 1 | get('password/reset') 13 | ->assertViewIs('awesio-auth::auth.passwords.email'); 14 | } 15 | } -------------------------------------------------------------------------------- /tests/Feature/LoginTest.php: -------------------------------------------------------------------------------- 1 | get('login') 15 | ->assertViewIs('awesio-auth::auth.login'); 16 | } 17 | 18 | /** @test */ 19 | public function email_is_required() 20 | { 21 | $this->json('POST', 'login') 22 | ->assertJsonValidationErrors(['email']); 23 | } 24 | 25 | /** @test */ 26 | public function password_is_required() 27 | { 28 | $this->json('POST', 'login') 29 | ->assertJsonValidationErrors(['password']); 30 | } 31 | 32 | /** @test */ 33 | public function it_can_login_user() 34 | { 35 | $user = factory(User::class)->create(); 36 | 37 | $this->json('POST', 'login', [ 38 | 'email' => $user->email, 39 | 'password' => 'secret' 40 | ]); 41 | 42 | $this->assertAuthenticatedAs($user); 43 | } 44 | 45 | /** @test */ 46 | public function it_cant_login_without_valid_password() 47 | { 48 | $user = factory(User::class)->create(); 49 | 50 | $this->json('POST', 'login', [ 51 | 'email' => $user->email, 52 | 'password' => 'password' 53 | ]); 54 | 55 | $this->assertGuest(); 56 | } 57 | 58 | /** @test */ 59 | public function it_responds_with_json_to_ajax_request_after_successful_login() 60 | { 61 | $user = factory(User::class)->create(); 62 | 63 | $this->json('POST', 'login', [ 64 | 'email' => $user->email, 65 | 'password' => 'secret' 66 | ], ['X-Requested-With' => 'XMLHttpRequest'] 67 | )->assertExactJson([ 68 | 'redirectUrl' => 'http://localhost' . config('awesio-auth.redirects.login') 69 | ]); 70 | } 71 | 72 | /** @test */ 73 | public function it_handles_two_factor_authentication_and_redirects() 74 | { 75 | $user = factory(User::class)->create(); 76 | 77 | factory(TwoFactor::class)->create([ 78 | 'user_id' => $user->id, 79 | 'verified' => 1 80 | ]); 81 | 82 | $this->json('POST', 'login', [ 83 | 'email' => $user->email, 84 | 'password' => 'secret' 85 | ])->assertRedirect('/login/twofactor/verify'); 86 | 87 | $this->assertGuest(); 88 | } 89 | } -------------------------------------------------------------------------------- /tests/Feature/RegisterTest.php: -------------------------------------------------------------------------------- 1 | get('register') 14 | ->assertViewIs('awesio-auth::auth.register'); 15 | } 16 | 17 | /** @test */ 18 | public function name_is_required() 19 | { 20 | $this->json('POST', 'register') 21 | ->assertJsonValidationErrors(['name']); 22 | } 23 | 24 | /** @test */ 25 | public function email_is_required() 26 | { 27 | $this->json('POST', 'register') 28 | ->assertJsonValidationErrors(['email']); 29 | } 30 | 31 | /** @test */ 32 | public function password_is_required() 33 | { 34 | $this->json('POST', 'register') 35 | ->assertJsonValidationErrors(['password']); 36 | } 37 | 38 | /** @test */ 39 | public function it_can_register_user() 40 | { 41 | Event::fake(); 42 | 43 | $this->post('register', [ 44 | 'name' => $name = uniqid(), 45 | 'email' => 'email@email.com', 46 | 'password' => $password = uniqid(), 47 | 'password_confirmation' => $password, 48 | ]); 49 | 50 | $this->assertDatabaseHas('users', [ 51 | 'id' => 1, 52 | 'name' => $name, 53 | ]); 54 | } 55 | } -------------------------------------------------------------------------------- /tests/Feature/ResetPasswordTest.php: -------------------------------------------------------------------------------- 1 | get('password/reset') 16 | ->assertViewIs('awesio-auth::auth.passwords.email'); 17 | } 18 | 19 | /** @test */ 20 | public function it_returns_view_on_password_reset() 21 | { 22 | $this->get('password/reset/token') 23 | ->assertViewIs('awesio-auth::auth.passwords.reset'); 24 | } 25 | 26 | /** @test */ 27 | public function it_sends_password_reset_email() 28 | { 29 | $user = factory(User::class)->create(); 30 | 31 | Notification::fake(); 32 | 33 | $this->post('password/email', ['email' => $user->email]); 34 | 35 | Notification::assertSentTo($user, ResetPasswordNotification::class); 36 | } 37 | } -------------------------------------------------------------------------------- /tests/Feature/SocialLoginTest.php: -------------------------------------------------------------------------------- 1 | get('login/github') 19 | ->assertSee('Redirecting to https://github.com/login/oauth/'); 20 | } 21 | 22 | /** @test */ 23 | public function it_creates_new_user_on_callback() 24 | { 25 | $name = uniqid(); 26 | 27 | $email = uniqid(); 28 | 29 | $serviceUserMock = $this->mock('serviceUser', function ($mock) use ($name, $email) { 30 | $mock->shouldReceive('getName') 31 | ->once() 32 | ->andReturn($name); 33 | $mock->shouldReceive('getEmail') 34 | ->twice() 35 | ->andReturn($email); 36 | $mock->shouldReceive('getId') 37 | ->twice() 38 | ->andReturn(uniqid()); 39 | }); 40 | 41 | $serviceMock = $this->mock('service', function ($mock) use ($serviceUserMock) { 42 | $mock->shouldReceive('user') 43 | ->andReturn($serviceUserMock); 44 | }); 45 | 46 | $this->mock(SocialProvidersManager::class, function ($mock) use ($serviceMock) { 47 | $mock->shouldReceive('buildProvider') 48 | ->once() 49 | ->with('github') 50 | ->andReturn($serviceMock); 51 | }); 52 | 53 | $this->get('login/github/callback'); 54 | 55 | $this->assertDatabaseHas('users', [ 56 | 'name' => $name, 57 | 'email' => $email, 58 | ]); 59 | } 60 | 61 | /** @test */ 62 | public function it_creates_user_social_on_callback() 63 | { 64 | $socialId = uniqid(); 65 | 66 | $serviceUserMock = $this->mock('serviceUser', function ($mock) use ($socialId) { 67 | $mock->shouldReceive('getName') 68 | ->once() 69 | ->andReturn(uniqid()); 70 | $mock->shouldReceive('getEmail') 71 | ->twice() 72 | ->andReturn(uniqid()); 73 | $mock->shouldReceive('getId') 74 | ->twice() 75 | ->andReturn($socialId); 76 | }); 77 | 78 | $serviceMock = $this->mock('service', function ($mock) use ($serviceUserMock) { 79 | $mock->shouldReceive('user') 80 | ->andReturn($serviceUserMock); 81 | }); 82 | 83 | $this->mock(SocialProvidersManager::class, function ($mock) use ($serviceMock) { 84 | $mock->shouldReceive('buildProvider') 85 | ->once() 86 | ->with('github') 87 | ->andReturn($serviceMock); 88 | }); 89 | 90 | $this->get('login/github/callback'); 91 | 92 | $this->assertDatabaseHas('users_social', [ 93 | 'service' => 'github', 94 | 'social_id' => $socialId, 95 | ]); 96 | } 97 | 98 | /** @test */ 99 | public function it_authenticates_user_on_callback() 100 | { 101 | $serviceUserMock = $this->mock('serviceUser', function ($mock) { 102 | $mock->shouldReceive('getName') 103 | ->once() 104 | ->andReturn(uniqid()); 105 | $mock->shouldReceive('getEmail') 106 | ->twice() 107 | ->andReturn(uniqid()); 108 | $mock->shouldReceive('getId') 109 | ->twice() 110 | ->andReturn(uniqid()); 111 | }); 112 | 113 | $serviceMock = $this->mock('service', function ($mock) use ($serviceUserMock) { 114 | $mock->shouldReceive('user') 115 | ->andReturn($serviceUserMock); 116 | }); 117 | 118 | $this->mock(SocialProvidersManager::class, function ($mock) use ($serviceMock) { 119 | $mock->shouldReceive('buildProvider') 120 | ->once() 121 | ->with('github') 122 | ->andReturn($serviceMock); 123 | }); 124 | 125 | $this->get('login/github/callback'); 126 | 127 | $this->assertAuthenticated(); 128 | } 129 | } -------------------------------------------------------------------------------- /tests/Feature/TwoFactorLoginTest.php: -------------------------------------------------------------------------------- 1 | get('login/twofactor/verify') 14 | ->assertViewIs('awesio-auth::twofactor.verify'); 15 | } 16 | 17 | /** @test */ 18 | public function it_verifies_two_factor_auth() 19 | { 20 | $user = factory(User::class)->create(); 21 | 22 | $this->withSession(['two_factor' => (object)[ 23 | 'user_id' => $user->id, 24 | 'remember' => true 25 | ]])->post('login/twofactor/verify', [ 26 | 'token' => uniqid() 27 | ])->assertStatus(302); 28 | } 29 | } -------------------------------------------------------------------------------- /tests/Feature/TwoFactorTest.php: -------------------------------------------------------------------------------- 1 | artisan('db:seed', ['--class' => 'AwesIO\Auth\Seeds\CountryTableSeeder']); 15 | } 16 | 17 | /** @test */ 18 | public function it_returns_view_on_index() 19 | { 20 | $user = factory(User::class)->create(); 21 | 22 | $this->actingAs($user)->post('twofactor', [ 23 | 'phone' => ($code = '+7') . ' ' . ($phone = '999 999-99-99'), 24 | ]); 25 | 26 | $this->actingAs($user)->get('twofactor') 27 | ->assertViewIs('awesio-auth::twofactor.index')->assertViewHas('qrCode'); 28 | } 29 | 30 | /** @test */ 31 | public function it_stores_new_user_two_factor_record() 32 | { 33 | $user = factory(User::class)->create(); 34 | 35 | $this->actingAs($user)->post('twofactor', [ 36 | 'phone' => ($code = '+7') . ' ' . ($phone = '999 999-99-99'), 37 | ]); 38 | 39 | $this->assertDatabaseHas('two_factor', [ 40 | 'user_id' => $user->id, 41 | 'phone' => $phone, 42 | 'dial_code' => $code 43 | ]); 44 | } 45 | 46 | /** @test */ 47 | public function it_verifies_user_two_factor_record() 48 | { 49 | $user = factory(User::class)->create(); 50 | 51 | $this->actingAs($user)->post('twofactor', [ 52 | 'phone' => ($code = '+7') . ' ' . ($phone = '999 999-99-99'), 53 | ]); 54 | 55 | $this->assertDatabaseHas('two_factor', [ 56 | 'user_id' => $user->id, 57 | 'phone' => $phone, 58 | 'dial_code' => $code, 59 | 'verified' => 0 60 | ]); 61 | 62 | $this->actingAs($user)->post('twofactor/verify', [ 63 | 'token' => uniqid() 64 | ]); 65 | 66 | $this->assertDatabaseHas('two_factor', [ 67 | 'user_id' => $user->id, 68 | 'phone' => $phone, 69 | 'dial_code' => $code, 70 | 'verified' => 1 71 | ]); 72 | } 73 | 74 | /** @test */ 75 | public function it_destroys_user_two_factor_record() 76 | { 77 | $user = factory(User::class)->create(); 78 | 79 | $this->actingAs($user)->post('twofactor', [ 80 | 'phone' => ($code = '+7') . ' ' . ($phone = '999 999-99-99'), 81 | ]); 82 | 83 | $this->assertDatabaseHas('two_factor', [ 84 | 'user_id' => $user->id, 85 | 'phone' => $phone, 86 | 'dial_code' => $code, 87 | ]); 88 | 89 | $this->actingAs($user)->delete('twofactor', [], array('HTTP_X-Requested-With' => 'XMLHttpRequest')); 90 | 91 | $this->assertDatabaseMissing('two_factor', [ 92 | 'user_id' => $user->id, 93 | 'phone' => $phone, 94 | 'dial_code' => $code, 95 | ]); 96 | } 97 | } -------------------------------------------------------------------------------- /tests/Feature/VerificationTest.php: -------------------------------------------------------------------------------- 1 | create([ 17 | 'email_verified_at' => null 18 | ]); 19 | 20 | $this->actingAs($user)->get('email/verify') 21 | ->assertViewIs('awesio-auth::auth.verify'); 22 | } 23 | 24 | /** @test */ 25 | public function it_verifies_email_using_code() 26 | { 27 | $user = factory(User::class)->create([ 28 | 'email_verified_at' => null 29 | ]); 30 | 31 | $this->assertDatabaseHas('users', [ 32 | 'id' => $user->id, 33 | 'email_verified_at' => null 34 | ]); 35 | 36 | $code = $user->generateVerificationCode(); 37 | 38 | $this->actingAs($user)->withSession( 39 | ['email_verification' => 40 | [ 41 | 'code' => $code, 'expire' => now()->addMinutes(60) 42 | ] 43 | ] 44 | )->post('email/verify', ['code' => $code]); 45 | 46 | $this->assertDatabaseMissing('users', [ 47 | 'id' => $user->id, 48 | 'email_verified_at' => null 49 | ]); 50 | } 51 | 52 | /** @test */ 53 | public function it_fails_email_verification_if_code_not_stored() 54 | { 55 | $user = factory(User::class)->create([ 56 | 'email_verified_at' => null 57 | ]); 58 | 59 | $code = $user->generateVerificationCode(); 60 | 61 | $this->actingAs($user)->post('email/verify', ['code' => $code]) 62 | ->assertStatus(403); 63 | 64 | $this->assertDatabaseHas('users', [ 65 | 'id' => $user->id, 66 | 'email_verified_at' => null 67 | ]); 68 | } 69 | 70 | /** @test */ 71 | public function it_fails_email_verification_if_code_is_not_valid() 72 | { 73 | $user = factory(User::class)->create([ 74 | 'email_verified_at' => null 75 | ]); 76 | 77 | $code = $user->generateVerificationCode(); 78 | 79 | $this->actingAs($user)->withSession( 80 | ['email_verification' => 81 | [ 82 | 'code' => $user->generateVerificationCode(), 83 | 'expire' => now()->addMinutes(60) 84 | ] 85 | ] 86 | )->post('email/verify', ['code' => $code])->assertStatus(403); 87 | 88 | $this->assertDatabaseHas('users', [ 89 | 'id' => $user->id, 90 | 'email_verified_at' => null 91 | ]); 92 | } 93 | 94 | /** @test */ 95 | public function it_fails_email_verification_if_code_is_expired() 96 | { 97 | $user = factory(User::class)->create([ 98 | 'email_verified_at' => null 99 | ]); 100 | 101 | $code = $user->generateVerificationCode(); 102 | 103 | $this->actingAs($user)->withSession( 104 | ['email_verification' => 105 | [ 106 | 'code' => $code, 'expire' => now()->subMinute() 107 | ] 108 | ] 109 | )->post('email/verify', ['code' => $code])->assertStatus(403); 110 | 111 | $this->assertDatabaseHas('users', [ 112 | 'id' => $user->id, 113 | 'email_verified_at' => null 114 | ]); 115 | } 116 | 117 | /** @test */ 118 | public function it_verifies_email_using_link() 119 | { 120 | $user = factory(User::class)->create([ 121 | 'email_verified_at' => null 122 | ]); 123 | 124 | $this->assertDatabaseHas('users', [ 125 | 'id' => $user->id, 126 | 'email_verified_at' => null 127 | ]); 128 | 129 | $url = URL::temporarySignedRoute( 130 | 'verification.verify', 131 | $expire = now()->addMinutes(60), 132 | ['id' => $user->getKey()] 133 | ); 134 | 135 | $this->actingAs($user)->get($url); 136 | 137 | $this->assertDatabaseMissing('users', [ 138 | 'id' => $user->id, 139 | 'email_verified_at' => null 140 | ]); 141 | } 142 | 143 | /** @test */ 144 | public function it_fails_email_verification_if_link_is_not_valid() 145 | { 146 | $user = factory(User::class)->create([ 147 | 'email_verified_at' => null 148 | ]); 149 | 150 | $url = URL::temporarySignedRoute( 151 | 'verification.verify', 152 | $expire = now()->addMinutes(60), 153 | ['id' => $user->getKey()] 154 | ); 155 | 156 | $this->actingAs($user)->get($url . 1)->assertStatus(403); 157 | 158 | $this->assertDatabaseHas('users', [ 159 | 'id' => $user->id, 160 | 'email_verified_at' => null 161 | ]); 162 | } 163 | 164 | /** @test */ 165 | public function it_fails_email_verification_using_link_if_id_is_invalid() 166 | { 167 | $user = factory(User::class)->create([ 168 | 'email_verified_at' => null 169 | ]); 170 | 171 | $url = URL::temporarySignedRoute( 172 | 'verification.verify', 173 | $expire = now()->addMinutes(60), 174 | ['id' => $user->getKey() + 1] 175 | ); 176 | 177 | $this->actingAs($user)->get($url)->assertStatus(403); 178 | 179 | $this->assertDatabaseHas('users', [ 180 | 'id' => $user->id, 181 | 'email_verified_at' => null 182 | ]); 183 | } 184 | 185 | /** @test */ 186 | public function it_resends_verification_email() 187 | { 188 | $user = factory(User::class)->create([ 189 | 'email_verified_at' => null 190 | ]); 191 | 192 | Notification::fake(); 193 | 194 | $this->actingAs($user)->get('email/resend'); 195 | 196 | Notification::assertSentTo($user, VerifyEmail::class); 197 | } 198 | } -------------------------------------------------------------------------------- /tests/Stubs/TwoFactor.php: -------------------------------------------------------------------------------- 1 | id = uniqid(); 32 | $respose->user = $user; 33 | return $respose; 34 | } 35 | 36 | /** 37 | * Verify if 2FA token is valid 38 | * 39 | * @param \App\User $user 40 | * @param string $token 41 | * @return boolean 42 | */ 43 | public function verifyToken($user, $token) 44 | { 45 | return true; 46 | } 47 | 48 | /** 49 | * Remove user from Authy 50 | * 51 | * @param \App\User $user 52 | * @return boolean 53 | */ 54 | public function remove($user) 55 | { 56 | return true; 57 | } 58 | 59 | /** 60 | * Request QR code link. 61 | * 62 | * @param string $authy_id User's id stored in your database 63 | * @param array $opts Array of options, for example: array("qr_size" => 300) 64 | * 65 | * @return mixed 66 | */ 67 | public function qrCode($user) 68 | { 69 | $respose = new \stdClass(); 70 | $respose->qr_code = uniqid(); 71 | return $respose; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tests/Stubs/User.php: -------------------------------------------------------------------------------- 1 | loadLaravelMigrations(['--database' => 'testing']); 24 | 25 | $this->assignRouteActionMiddlewares(); 26 | 27 | $this->withFactories(__DIR__ . '/../database/factories'); 28 | 29 | $this->app->singleton(TwoFactorContract::class, TwoFactor::class); 30 | } 31 | 32 | /** 33 | * Define environment setup. 34 | * 35 | * @param \Illuminate\Foundation\Application $app 36 | * @return void 37 | */ 38 | protected function getEnvironmentSetUp($app) 39 | { 40 | $app['config']->set('app.debug', env('APP_DEBUG', true)); 41 | 42 | $app['config']->set('auth.providers.users.model', User::class); 43 | 44 | $this->setUpDatabase($app); 45 | 46 | $app->register( \Laravel\Socialite\SocialiteServiceProvider::class); 47 | } 48 | 49 | /** 50 | * Load package service provider 51 | * @param \Illuminate\Foundation\Application $app 52 | * @return array 53 | */ 54 | protected function getPackageProviders($app) 55 | { 56 | return [ 57 | AuthServiceProvider::class 58 | ]; 59 | } 60 | 61 | /** 62 | * Load package alias 63 | * @param \Illuminate\Foundation\Application $app 64 | * @return array 65 | */ 66 | protected function getPackageAliases($app) 67 | { 68 | return [ 69 | 'AwesAuth' => Auth::class, 70 | ]; 71 | } 72 | 73 | protected function assignRouteActionMiddlewares() 74 | { 75 | $actions = [ 76 | 'AwesIO\Auth\Controllers\LoginController@showLoginForm', 77 | 'AwesIO\Auth\Controllers\LoginController@login', 78 | 'AwesIO\Auth\Controllers\LoginController@logout', 79 | 'AwesIO\Auth\Controllers\RegisterController@showRegistrationForm', 80 | 'AwesIO\Auth\Controllers\TwoFactorLoginController@index', 81 | 'AwesIO\Auth\Controllers\SocialLoginController@redirect', 82 | 'AwesIO\Auth\Controllers\SocialLoginController@callback', 83 | 'AwesIO\Auth\Controllers\TwoFactorController@index', 84 | 'AwesIO\Auth\Controllers\ForgotPasswordController@showLinkRequestForm', 85 | 'AwesIO\Auth\Controllers\ResetPasswordController@showResetForm', 86 | 'AwesIO\Auth\Controllers\VerificationController@show', 87 | ]; 88 | 89 | $middlwares = ['web']; 90 | 91 | foreach ($actions as $action) { 92 | app('router')->getRoutes()->getByAction($action) 93 | ->middleware($middlwares); 94 | } 95 | } 96 | 97 | protected function setUpDatabase($app) 98 | { 99 | $builder = $app['db']->connection()->getSchemaBuilder(); 100 | 101 | $builder->create('two_factor', function (Blueprint $table) { 102 | $table->increments('id'); 103 | $table->integer('user_id')->unsigned()->index(); 104 | $table->string('identifier')->nullable(); 105 | $table->string('phone'); 106 | $table->string('dial_code'); 107 | $table->boolean('verified')->default(false); 108 | $table->timestamps(); 109 | }); 110 | 111 | $builder->create('countries', function (Blueprint $table) { 112 | $table->increments('id'); 113 | $table->string('name'); 114 | $table->string('code', 2); 115 | $table->string('dial_code'); 116 | }); 117 | 118 | $builder->create('users_social', function (Blueprint $table) { 119 | $table->increments('id'); 120 | $table->integer('user_id')->unsigned()->index(); 121 | $table->string('social_id'); 122 | $table->string('service'); 123 | $table->timestamps(); 124 | 125 | $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); 126 | }); 127 | } 128 | } -------------------------------------------------------------------------------- /tests/Unit/Listeners/EventSubscriberTest.php: -------------------------------------------------------------------------------- 1 | mock(User::class, function ($mock) { 20 | $mock->shouldReceive('hasVerifiedEmail') 21 | ->once() 22 | ->andReturn(false); 23 | $mock->shouldReceive('sendEmailVerificationNotification') 24 | ->once() 25 | ->andReturn(true); 26 | }); 27 | 28 | $event = new Registered($user); 29 | 30 | $subscriber = new EventSubscriber(); 31 | 32 | $subscriber->handleRegistered($event); 33 | } 34 | 35 | /** @test */ 36 | public function it_doesnt_send_verification_email_if_user_already_verified_it() 37 | { 38 | $this->app['config']->set('app.debug', env('APP_DEBUG', true)); 39 | 40 | $user = $this->mock(User::class, function ($mock) { 41 | $mock->shouldReceive('hasVerifiedEmail') 42 | ->once() 43 | ->andReturn(true); 44 | }); 45 | 46 | $event = new Registered($user); 47 | 48 | $subscriber = new EventSubscriber(); 49 | 50 | $subscriber->handleRegistered($event); 51 | } 52 | 53 | /** @test */ 54 | public function it_doesnt_send_verification_email_if_its_disabled() 55 | { 56 | $this->app['config']->set('awesio-auth.enabled', []); 57 | 58 | $user = $this->mock(User::class, function ($mock) { 59 | $mock->shouldNotReceive('hasVerifiedEmail'); 60 | $mock->shouldNotReceive('sendEmailVerificationNotification'); 61 | }); 62 | 63 | $event = new Registered($user); 64 | 65 | $subscriber = new EventSubscriber(); 66 | 67 | $subscriber->handleRegistered($event); 68 | } 69 | } -------------------------------------------------------------------------------- /tests/Unit/Models/UserSocialTest.php: -------------------------------------------------------------------------------- 1 | create(); 18 | 19 | $social = factory(UserSocial::class)->create([ 20 | 'user_id' => $user->id 21 | ]); 22 | 23 | $this->assertInstanceOf(User::class, $social->user); 24 | } 25 | } -------------------------------------------------------------------------------- /tests/Unit/Repositories/UserRepositoryTest.php: -------------------------------------------------------------------------------- 1 | create(); 20 | 21 | $mock = $this->mock(SocialUser::class, function ($mock) use ($user) { 22 | $mock->shouldReceive('getEmail') 23 | ->once() 24 | ->andReturn($user->email); 25 | $mock->shouldReceive('getId') 26 | ->once() 27 | ->andReturn(uniqid()); 28 | }); 29 | 30 | $repository = new EloquentUserRepository(); 31 | 32 | $this->assertInstanceOf(User::class, $repository->getUserBySocial($mock, 'github')); 33 | } 34 | 35 | /** @test */ 36 | public function it_returns_user_by_social_id() 37 | { 38 | $user = factory(User::class)->create(); 39 | 40 | DB::table('users_social')->insert([ 41 | 'user_id' => $user->id, 42 | 'service' => $sevice = 'github', 43 | 'social_id' => $socialId = uniqid() 44 | ]); 45 | 46 | $mock = $this->mock(SocialUser::class, function ($mock) use ($socialId) { 47 | $mock->shouldReceive('getEmail') 48 | ->once() 49 | ->andReturn(uniqid()); 50 | $mock->shouldReceive('getId') 51 | ->once() 52 | ->andReturn($socialId); 53 | }); 54 | 55 | $repository = new EloquentUserRepository(); 56 | 57 | $this->assertInstanceOf(User::class, $repository->getUserBySocial($mock, $sevice)); 58 | } 59 | } -------------------------------------------------------------------------------- /tests/Unit/Services/AuthyTest.php: -------------------------------------------------------------------------------- 1 | create(); 21 | 22 | factory(TwoFactor::class)->create([ 23 | 'user_id' => $user->id 24 | ]); 25 | 26 | $mock = $this->mock(Client::class, function ($mock) use ($user) { 27 | $mock->shouldReceive('request') 28 | ->with( 29 | 'POST', 30 | 'https://api.authy.com/protected/json/users/new?api_key=', 31 | ['form_params' => $this->getUserRegistrationPayload($user)] 32 | ) 33 | ->once() 34 | ->andThrow(\Exception::class); 35 | }); 36 | 37 | $authy = new AuthyTwoFactor($mock); 38 | 39 | $this->assertFalse($authy->register($user)); 40 | } 41 | 42 | /** @test */ 43 | public function it_registers_new_user() 44 | { 45 | 46 | $user = factory(User::class)->create(); 47 | 48 | factory(TwoFactor::class)->create([ 49 | 'user_id' => $user->id 50 | ]); 51 | 52 | $mock = $this->mock(Client::class, function ($mock) use ($user) { 53 | $mock->shouldReceive('request') 54 | ->with( 55 | 'POST', 56 | 'https://api.authy.com/protected/json/users/new?api_key=', 57 | ['form_params' => $this->getUserRegistrationPayload($user)] 58 | ) 59 | ->once() 60 | ->andReturn(new Response); 61 | }); 62 | 63 | $authy = new AuthyTwoFactor($mock); 64 | 65 | $this->isNull($authy->register($user)); 66 | } 67 | 68 | /** @test */ 69 | public function it_verifies_token_as_valid() 70 | { 71 | $user = factory(User::class)->create(); 72 | 73 | factory(TwoFactor::class)->create([ 74 | 'user_id' => $user->id 75 | ]); 76 | 77 | $token = uniqid(); 78 | 79 | $response = $this->mock(Response::class, function ($mock) { 80 | $mock->shouldReceive('getBody')->once()->andReturn(json_encode(['token' => 'is valid'])); 81 | }); 82 | 83 | $mock = $this->mock(Client::class, function ($mock) use ($response, $token, $user) { 84 | $mock->shouldReceive('request') 85 | ->with( 86 | 'GET', 87 | 'https://api.authy.com/protected/json/verify/' 88 | . $token . '/' . $user->twoFactor->identifier . '?force=true&api_key=' 89 | ) 90 | ->once() 91 | ->andReturn($response); 92 | }); 93 | 94 | $authy = new AuthyTwoFactor($mock); 95 | 96 | $this->assertTrue($authy->verifyToken($user, $token)); 97 | } 98 | 99 | /** @test */ 100 | public function it_verifies_token_as_invalid() 101 | { 102 | $user = factory(User::class)->create(); 103 | 104 | factory(TwoFactor::class)->create([ 105 | 'user_id' => $user->id 106 | ]); 107 | 108 | $token = uniqid(); 109 | 110 | $response = $this->mock(Response::class, function ($mock) { 111 | $mock->shouldReceive('getBody')->once()->andReturn(json_encode(['token' => 'is not valid'])); 112 | }); 113 | 114 | $mock = $this->mock(Client::class, function ($mock) use ($response, $token, $user) { 115 | $mock->shouldReceive('request') 116 | ->with( 117 | 'GET', 118 | 'https://api.authy.com/protected/json/verify/' 119 | . $token . '/' . $user->twoFactor->identifier . '?force=true&api_key=' 120 | ) 121 | ->once() 122 | ->andReturn($response); 123 | }); 124 | 125 | $authy = new AuthyTwoFactor($mock); 126 | 127 | $this->assertFalse($authy->verifyToken($user, $token)); 128 | } 129 | 130 | /** @test */ 131 | public function it_verifies_token_as_invalid_if_smth_wrong() 132 | { 133 | $user = factory(User::class)->create(); 134 | 135 | factory(TwoFactor::class)->create([ 136 | 'user_id' => $user->id 137 | ]); 138 | 139 | $token = uniqid(); 140 | 141 | $mock = $this->mock(Client::class, function ($mock) use ($token, $user) { 142 | $mock->shouldReceive('request') 143 | ->with( 144 | 'GET', 145 | 'https://api.authy.com/protected/json/verify/' 146 | . $token . '/' . $user->twoFactor->identifier . '?force=true&api_key=' 147 | ) 148 | ->once() 149 | ->andThrow(\Exception::class); 150 | }); 151 | 152 | $authy = new AuthyTwoFactor($mock); 153 | 154 | $this->assertFalse($authy->verifyToken($user, $token)); 155 | } 156 | 157 | /** @test */ 158 | public function it_removes_user_from_two_factor_service() 159 | { 160 | $user = factory(User::class)->create(); 161 | 162 | factory(TwoFactor::class)->create([ 163 | 'user_id' => $user->id 164 | ]); 165 | 166 | $mock = $this->mock(Client::class, function ($mock) use ($user) { 167 | $mock->shouldReceive('request') 168 | ->with( 169 | 'POST', 170 | 'https://api.authy.com/protected/json/users/delete/' 171 | . $user->twoFactor->identifier . '?api_key=' 172 | ) 173 | ->once() 174 | ->andReturn(null); 175 | }); 176 | 177 | $authy = new AuthyTwoFactor($mock); 178 | 179 | $this->assertTrue($authy->remove($user)); 180 | } 181 | 182 | /** @test */ 183 | public function it_doesnt_remove_user_from_two_factor_service() 184 | { 185 | $user = factory(User::class)->create(); 186 | 187 | factory(TwoFactor::class)->create([ 188 | 'user_id' => $user->id 189 | ]); 190 | 191 | $mock = $this->mock(Client::class, function ($mock) use ($user) { 192 | $mock->shouldReceive('request') 193 | ->with( 194 | 'POST', 195 | 'https://api.authy.com/protected/json/users/delete/' 196 | . $user->twoFactor->identifier . '?api_key=' 197 | ) 198 | ->once() 199 | ->andThrow(\Exception::class); 200 | }); 201 | 202 | $authy = new AuthyTwoFactor($mock); 203 | 204 | $this->assertFalse($authy->remove($user)); 205 | } 206 | 207 | /** @test */ 208 | public function it_returns_link_to_qr_code() 209 | { 210 | $user = factory(User::class)->create(); 211 | 212 | factory(TwoFactor::class)->create([ 213 | 'user_id' => $user->id 214 | ]); 215 | 216 | $mock = $this->mock(Client::class, function ($mock) use ($user) { 217 | $mock->shouldReceive('request') 218 | ->with( 219 | 'POST', 220 | 'https://api.authy.com/protected/json/users/' 221 | . $user->twoFactor->identifier . '/secret?api_key=' 222 | ) 223 | ->once() 224 | ->andReturn(new Response); 225 | }); 226 | 227 | $authy = new AuthyTwoFactor($mock); 228 | 229 | $this->isNull($authy->qrCode($user)); 230 | } 231 | 232 | /** @test */ 233 | public function it_doesnt_return_link_to_qr_code() 234 | { 235 | $user = factory(User::class)->create(); 236 | 237 | factory(TwoFactor::class)->create([ 238 | 'user_id' => $user->id 239 | ]); 240 | 241 | $mock = $this->mock(Client::class, function ($mock) use ($user) { 242 | $mock->shouldReceive('request') 243 | ->with( 244 | 'POST', 245 | 'https://api.authy.com/protected/json/users/' 246 | . $user->twoFactor->identifier . '/secret?api_key=' 247 | ) 248 | ->once() 249 | ->andThrow(\Exception::class); 250 | }); 251 | 252 | $authy = new AuthyTwoFactor($mock); 253 | 254 | $this->assertFalse($authy->qrCode($user)); 255 | } 256 | 257 | /** 258 | * Get data needed for user registration on Authy 259 | * 260 | * @param $user 261 | * @return array 262 | */ 263 | protected function getUserRegistrationPayload($user) 264 | { 265 | return [ 266 | 'user' => [ 267 | 'email' => $user->email, 268 | 'cellphone' => $user->twoFactor->phone, 269 | 'country_code' => $user->twoFactor->dial_code 270 | ] 271 | ]; 272 | } 273 | } -------------------------------------------------------------------------------- /views/auth/login.blade.php: -------------------------------------------------------------------------------- 1 | @extends('awesio-auth::layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
AWESIO {{ __('Login') }}
9 | 10 |
11 |
12 | @csrf 13 | 14 |
15 | 16 | 17 |
18 | 19 | 20 | @if ($errors->has('email')) 21 | 22 | {{ $errors->first('email') }} 23 | 24 | @endif 25 |
26 |
27 | 28 |
29 | 30 | 31 |
32 | 33 | 34 | @if ($errors->has('password')) 35 | 36 | {{ $errors->first('password') }} 37 | 38 | @endif 39 |
40 |
41 | 42 |
43 |
44 |
45 | 46 | 47 | 50 |
51 |
52 |
53 | 54 |
55 |
56 | 59 | 60 | @if (Route::has('password.request')) 61 | 62 | {{ __('Forgot Your Password?') }} 63 | 64 | @endif 65 |
66 |
67 |
68 |
69 |
70 | 71 | @if(AwesAuth::isSocialEnabled()) 72 |
73 |
74 | @foreach (config('awesio-auth.socialite.services') as $item) 75 | 76 | {{ $item['name'] }} 77 | 78 | @endforeach 79 |
80 |
81 | @endif 82 | 83 |
84 |
85 |
86 | @endsection 87 | -------------------------------------------------------------------------------- /views/auth/passwords/email.blade.php: -------------------------------------------------------------------------------- 1 | @extends('awesio-auth::layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
AWESIO {{ __('Reset Password') }}
9 | 10 |
11 | @if (session('status')) 12 | 15 | @endif 16 | 17 |
18 | @csrf 19 | 20 |
21 | 22 | 23 |
24 | 25 | 26 | @if ($errors->has('email')) 27 | 28 | {{ $errors->first('email') }} 29 | 30 | @endif 31 |
32 |
33 | 34 |
35 |
36 | 39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | @endsection 48 | -------------------------------------------------------------------------------- /views/auth/passwords/reset.blade.php: -------------------------------------------------------------------------------- 1 | @extends('awesio-auth::layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
AWESIO {{ __('Reset Password') }}
9 | 10 |
11 |
12 | @csrf 13 | 14 | 15 | 16 |
17 | 18 | 19 |
20 | 21 | 22 | @if ($errors->has('email')) 23 | 24 | {{ $errors->first('email') }} 25 | 26 | @endif 27 |
28 |
29 | 30 |
31 | 32 | 33 |
34 | 35 | 36 | @if ($errors->has('password')) 37 | 38 | {{ $errors->first('password') }} 39 | 40 | @endif 41 |
42 |
43 | 44 |
45 | 46 | 47 |
48 | 49 |
50 |
51 | 52 |
53 |
54 | 57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | @endsection 66 | -------------------------------------------------------------------------------- /views/auth/register.blade.php: -------------------------------------------------------------------------------- 1 | @extends('awesio-auth::layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
AWESIO {{ __('Register') }}
9 | 10 |
11 |
12 | @csrf 13 | 14 |
15 | 16 | 17 |
18 | 19 | 20 | @if ($errors->has('name')) 21 | 22 | {{ $errors->first('name') }} 23 | 24 | @endif 25 |
26 |
27 | 28 |
29 | 30 | 31 |
32 | 33 | 34 | @if ($errors->has('email')) 35 | 36 | {{ $errors->first('email') }} 37 | 38 | @endif 39 |
40 |
41 | 42 |
43 | 44 | 45 |
46 | 47 | 48 | @if ($errors->has('password')) 49 | 50 | {{ $errors->first('password') }} 51 | 52 | @endif 53 |
54 |
55 | 56 |
57 | 58 | 59 |
60 | 61 |
62 |
63 | 64 |
65 |
66 | 69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | @endsection 78 | -------------------------------------------------------------------------------- /views/auth/verify.blade.php: -------------------------------------------------------------------------------- 1 | @extends('awesio-auth::layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
{{ __('Verify email by code') }}
9 | 10 |
11 |
12 | @csrf 13 | 14 |
15 | 16 | 17 |
18 | 19 | 20 | @if ($errors->has('code')) 21 | 22 | {{ $errors->first('code') }} 23 | 24 | @endif 25 |
26 |
27 | 28 |
29 |
30 | 33 |
34 |
35 |
36 |
37 |
38 | 39 |
40 |
41 |
42 | @endsection 43 | -------------------------------------------------------------------------------- /views/layouts/app.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | {{ config('app.name', 'Laravel') }} 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 74 | 75 | @auth 76 | @if (!auth()->user()->hasVerifiedEmail()) 77 |
78 | Before proceeding, please check your email for a verification code and link. 79 | If you did not receive the email click here to request another. 80 |
81 | @endif 82 | @endauth 83 | 84 |
85 | @yield('content') 86 |
87 |
88 | 89 | 90 | -------------------------------------------------------------------------------- /views/twofactor/index.blade.php: -------------------------------------------------------------------------------- 1 | @extends('awesio-auth::layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
AWESIO Two Factor
9 | 10 |
11 | @if (auth()->user()->isTwoFactorEnabled()) 12 | 13 |
Two factor auth is enabled
14 |
15 | @csrf 16 | @method('DELETE') 17 |
18 |
19 | 22 |
23 |
24 |
25 | 26 | @else 27 | @if (auth()->user()->isTwoFactorPending()) 28 | 29 |
30 | @csrf 31 |
32 | 33 | 34 |
35 | 36 | 37 | @if ($errors->has('token')) 38 | 39 | {{ $errors->first('token') }} 40 | 41 | @endif 42 |
43 |
44 |
45 |
46 | 49 |
50 |
51 |
52 | 53 |
54 | 55 |
56 | @csrf 57 | @method('DELETE') 58 |
59 |
60 | 63 |
64 |
65 |
66 | 67 | @else 68 | 69 |
70 | @csrf 71 | 72 | {{--
73 | 74 | 75 |
76 | 81 | 82 | @if ($errors->has('dial_code')) 83 | 84 | {{ $errors->first('dial_code') }} 85 | 86 | @endif 87 |
88 |
--}} 89 | 90 |
91 | 92 | 93 |
94 | 95 | 96 | @if ($errors->has('phone')) 97 | 98 | {{ $errors->first('phone') }} 99 | 100 | @endif 101 |
102 |
103 | 104 |
105 |
106 | 109 |
110 |
111 |
112 | 113 | @endif 114 | 115 | @endif 116 |
117 |
118 | 119 |
120 |
121 |
122 | @endsection 123 | -------------------------------------------------------------------------------- /views/twofactor/verify.blade.php: -------------------------------------------------------------------------------- 1 | @extends('awesio-auth::layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
AWESIO Two Factor VERIFY
9 | 10 |
11 | 12 |
13 | @csrf 14 |
15 | 16 | 17 |
18 | 19 | 20 | @if ($errors->has('token')) 21 | 22 | {{ $errors->first('token') }} 23 | 24 | @endif 25 |
26 |
27 |
28 |
29 | 32 |
33 |
34 |
35 | 36 |
37 |
38 | 39 |
40 |
41 |
42 | @endsection 43 | -------------------------------------------------------------------------------- /xdebug.ini: -------------------------------------------------------------------------------- 1 | ; NOTE: The actual debug.so extention is NOT SET HERE but rather (/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini) 2 | 3 | xdebug.remote_connect_back=0 4 | xdebug.remote_port=9009 5 | xdebug.remote_host=172.19.1.15 6 | xdebug.idekey=PHPSTORM 7 | 8 | xdebug.remote_autostart=0 9 | xdebug.remote_enable=0 10 | xdebug.cli_color=0 11 | xdebug.profiler_enable=0 12 | xdebug.profiler_output_dir="~/xdebug/phpstorm/tmp/profiling" 13 | 14 | xdebug.remote_handler=dbgp 15 | xdebug.remote_mode=req 16 | 17 | xdebug.var_display_max_children=-1 18 | xdebug.var_display_max_data=-1 19 | xdebug.var_display_max_depth=-1 20 | --------------------------------------------------------------------------------