├── .editorconfig ├── .gitattributes ├── .gitignore ├── .styleci.yml ├── .travis.yml ├── CHANGELOG.md ├── CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── LICENSE.md ├── PULL_REQUEST_TEMPLATE.md ├── README.md ├── composer.json ├── database └── migrations │ └── 2016_11_02_070159_create_tokens_table.php ├── phpunit.xml ├── resources └── images │ ├── .gitkeep │ └── laravel-token-builder-package.png ├── src ├── Abstracts │ └── RepositoryAbstract.php ├── Builder.php ├── Contracts │ └── RepositoryInterface.php ├── Facade │ └── TokenBuilder.php ├── Models │ └── Token.php ├── Provider │ └── TokenBuilderServiceProvider.php ├── Repositories │ └── TokensRepository.php └── Traits │ ├── Concerns │ └── Validation.php │ └── HasTemporaryTokens.php └── tests └── TestCase.php /.editorconfig: -------------------------------------------------------------------------------- 1 | ; This file is for unifying the coding style for different editors and IDEs. 2 | ; More information at http://editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | indent_size = 4 9 | indent_style = space 10 | end_of_line = lf 11 | insert_final_newline = true 12 | trim_trailing_whitespace = true 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | 49 | # PhpStorm project's cache 50 | .idea 51 | 52 | # VSCode project's cache 53 | .vscode 54 | 55 | # Vendor directory 56 | vendor/ 57 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: psr2 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - '7.2' 4 | - '7.3' 5 | - '7.4' 6 | before_script: composer install 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All Notable changes to `tokenable` will be documented in this file. 4 | 5 | Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) principles. 6 | 7 | ## Date - 2019-01-09 8 | 9 | ### Fixed 10 | - Nothing 11 | 12 | ### Added 13 | - Nothing 14 | 15 | ### Deprecated 16 | - Nothing 17 | 18 | ### Fixed 19 | - Nothing 20 | 21 | ### Removed 22 | - Nothing 23 | 24 | ### Security 25 | - Nothing 26 | -------------------------------------------------------------------------------- /CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor 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, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | 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 `khanzadimahdi@gmail.com`. 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 [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are **welcome** and will be fully **credited**. 4 | 5 | We accept contributions via Pull Requests on [Github](https://github.com/shetabit/token-builder). 6 | 7 | ## Pull Requests 8 | 9 | - **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - Check the code style with ``$ composer check-style`` and fix it with ``$ composer fix-style``. 10 | 11 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests. 12 | 13 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. 14 | 15 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option. 16 | 17 | - **Create feature branches** - Don't ask us to pull from your master branch. 18 | 19 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. 20 | 21 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. 22 | 23 | 24 | **Happy coding**! 25 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Detailed description 4 | 5 | Provide a detailed description of the change or addition you are proposing. 6 | 7 | Make it clear if the issue is a bug, an enhancement or just a question. 8 | 9 | ## Context 10 | 11 | Why is this change important to you? How would you use it? 12 | 13 | How can it benefit other users? 14 | 15 | ## Possible implementation 16 | 17 | Not obligatory, but suggest an idea for implementing addition or change. 18 | 19 | ## Your environment 20 | 21 | Include as many relevant details about the environment you experienced the bug in and how to reproduce it. 22 | 23 | * Version used (e.g. PHP 7.2+, HHVM 3): 24 | * Operating system and version (e.g. Ubuntu 19.04, Windows 10): 25 | * Link to your project: 26 | * ... 27 | * ... 28 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Mahdi Khanzadi 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. 22 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | Describe your changes in detail. 6 | 7 | ## Motivation and context 8 | 9 | Why is this change required? What problem does it solve? 10 | 11 | If it fixes an open issue, please link to the issue here (if you write `fixes #num` 12 | or `closes #num`, the issue will be automatically closed when the pull is accepted.) 13 | 14 | ## How has this been tested? 15 | 16 | Please describe in detail how you tested your changes. 17 | 18 | Include details of your testing environment, and the tests you ran to 19 | see how your change affects other areas of the code, etc. 20 | 21 | ## Screenshots (if appropriate) 22 | 23 | ## Types of changes 24 | 25 | What types of changes does your code introduce? Put an `x` in all the boxes that apply: 26 | - [ ] Bug fix (non-breaking change which fixes an issue) 27 | - [ ] New feature (non-breaking change which adds functionality) 28 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 29 | 30 | ## Checklist: 31 | 32 | Go over all the following points, and put an `x` in all the boxes that apply. 33 | 34 | Please, please, please, don't send your pull request until all of the boxes are ticked. Once your pull request is created, it will trigger a build on our [continuous integration](http://www.phptherightway.com/#continuous-integration) server to make sure your [tests and code style pass](https://help.github.com/articles/about-required-status-checks/). 35 | 36 | - [ ] I have read the **[CONTRIBUTING](CONTRIBUTING.md)** document. 37 | - [ ] My pull request addresses exactly one patch/feature. 38 | - [ ] I have created a branch for this patch/feature. 39 | - [ ] Each individual commit in the pull request is meaningful. 40 | - [ ] I have added tests to cover my changes. 41 | - [ ] If my change requires a change to the documentation, I have updated it accordingly. 42 | 43 | If you're unsure about any of these, don't hesitate to ask. We're here to help! 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | # Laravel Token Builder 4 | 5 | 6 | 7 | 8 | This package can generate **random tokens** which **can expires**. 9 | 10 | Generated token can be used for creating **one-time links**, 11 | or **authentication with sms pin** and etc. 12 | 13 | we have 2 things that can **expire** genereted tokens: 14 | 15 | - **time limit** : for example a token can be expired after 2022/05/12 16 | - **usage limit** : for example a token can be expired after using it more than 3 times. 17 | 18 | [Donate me](https://yekpay.me/mahdikhanzadi) if you like this package :sunglasses: :bowtie: 19 | 20 | # List of contents 21 | 22 | - [Available drivers](#list-of-available-drivers) 23 | - [Install](#install) 24 | - [Configure](#configure) 25 | - [How to use](#how-to-use) 26 | - [Generating tokens](#generating-tokens) 27 | - [Adding expiration date](#adding-expiration-date) 28 | - [Adding usage limit](#adding-usage-limit) 29 | - [Validation](#validation) 30 | - [Add relations](#useful-methods) 31 | - [Attach custom data](#useful-methods) 32 | - [Retrieve tokens](#retrieve-tokens) 33 | - [TokenBuilder reference](#tokenbuilder-reference) 34 | - [setUniqueId](#setUniqueId) 35 | - [getUniqueId](#getUniqueId) 36 | - [setData](#setData) 37 | - [getData](#getData) 38 | - [setExpireDate](#setExpireDate) 39 | - [getExpireDate](#getExpireDate) 40 | - [setUsageLimit](#setUsageLimit) 41 | - [getUsageLimit](#getUsageLimit) 42 | - [setType](#setType) 43 | - [getType](#getType) 44 | - [setRelatedItem](#setRelatedItem) 45 | - [getRelatedItem](#getRelatedItem) 46 | - [build](#build) 47 | - [findToken](#findToken) 48 | - [findValidToken](#findValidToken) 49 | - [Token (Eloquent) reference](#token-reference) 50 | - [use](#use) 51 | - [hasUsed](#hasUsed) 52 | - [hasMaxUsageLimit](#hasMaxUsageLimit) 53 | - [hasExpired](#hasExpired) 54 | - [markAsExpired](#markAsExpired) 55 | - [isValid](#isValid) 56 | - [tokenable](#tokenable) 57 | - [Change log](#change-log) 58 | - [Contributing](#contributing) 59 | - [Security](#security) 60 | - [Credits](#credits) 61 | - [License](#license) 62 | 63 | ## Install 64 | 65 | Via Composer 66 | 67 | ``` bash 68 | $ composer require shetabit/token-builder 69 | ``` 70 | 71 | ## Configure 72 | 73 | If you are using `Laravel 5.5` or higher then you don't need to add the provider and alias. 74 | 75 | In your `config/app.php` file add these two lines. 76 | 77 | ```php 78 | # In your providers array. 79 | 'providers' => [ 80 | ... 81 | Shetabit\TokenBuilder\Provider\TokenBuilderServiceProvider::class, 82 | ], 83 | 84 | # In your aliases array. 85 | 'aliases' => [ 86 | ... 87 | 'TokenBuilder' => Shetabit\TokenBuilder\Facade\TokenBuilder::class, 88 | ], 89 | ``` 90 | 91 | then, run the below commands to **publish migrations** and **create tables** 92 | 93 | ```bash 94 | php artisan vendor:publish 95 | 96 | php artisan migrate 97 | ``` 98 | 99 | ## How to use 100 | 101 | you can use `TokenBuilder` facade or `Builder` class to build tokens. 102 | 103 | #### Generating tokens 104 | 105 | In your code, use **facade** like the below : 106 | 107 | ```php 108 | use Shetabit\TokenBuilder\Facade\TokenBuilder; 109 | 110 | // ... 111 | 112 | $tokenObject = $token = TokenBuilder::build(); 113 | ``` 114 | 115 | you can also use `Builder` : 116 | 117 | ```php 118 | use Shetabit\TokenBuilder\Builder; 119 | 120 | // ... 121 | 122 | $builder = new Builder; 123 | 124 | $tokenObject = $builder->build(); 125 | ``` 126 | 127 | each **tokenObject** has a **unique value** that can be used to recognize it from others and we call it `token`. 128 | you can access to tokenObject unique token using `token`, see below example: 129 | 130 | ```php 131 | use Shetabit\TokenBuilder\Facade\TokenBuilder; 132 | 133 | // ... 134 | 135 | // generate and store token in database 136 | $tokenObject = $token = TokenBuilder::build(); 137 | 138 | // show token unique value 139 | echo $tokenObject->token; 140 | ``` 141 | 142 | you can retrieve token and send it to users via email or sms or using it in URLs. 143 | it can be used to **sms verifications** or **one-time login pins** , etc. 144 | 145 | #### Adding expiration date 146 | 147 | you can build a token with auto expiration date. 148 | this kind of token will be expired after the specified date. 149 | 150 | ```php 151 | use Shetabit\TokenBuilder\Facade\TokenBuilder; 152 | 153 | // ... 154 | 155 | // will be expired after 5 minutes 156 | $date = Carbon::now()->addMinutes(5); 157 | $tokenObject = TokenBuilder::setExpireDate($date)->build(); 158 | 159 | echo $tokenObject->token; // show unique token 160 | echo $tokenObject->expired_at; // show expiration datetime 161 | 162 | if ($tokenObject->hasExpired()) { 163 | echo 'token has expired'; 164 | } 165 | ``` 166 | 167 | #### Adding usage limit 168 | 169 | you can add usage limit into your tokens. 170 | a token will be invalid after the usage has exceeded the limit. 171 | 172 | ```php 173 | use Shetabit\TokenBuilder\Facade\TokenBuilder; 174 | 175 | // ... 176 | 177 | // will be expired after 1 usage. 178 | $tokenObject = TokenBuilder::setUsageLimit(1)->build(); 179 | 180 | // use token --> increment the usage counter 181 | $tokenObject->use(); 182 | 183 | echo $tokenObject->token; // show unique token 184 | echo $tokenObject->usage_count; // show total usages count 185 | echo $tokenObject->max_usage_limit; // show max usage limit 186 | 187 | // determine if user has used 188 | if ($tokenObject->hasUsed()) { 189 | echo 'token has used'; 190 | } 191 | 192 | // determin if token has exceed max usage 193 | if ($tokenObject->hasExceedMaxUsage()) { 194 | echo 'token used has exceeded the specified limit'; 195 | } 196 | ``` 197 | 198 | #### Validation 199 | 200 | you can validate a token using `isValid` method. 201 | a token is valid if has not exceeded the specified limit and has not expired yet. 202 | 203 | ```php 204 | use Shetabit\TokenBuilder\Facade\TokenBuilder; 205 | 206 | // ... 207 | 208 | $date = Carbon::now()->addMinutes(5); // will be expired after 5 minutes 209 | $usageLimit = 1; // max usages 210 | $token = TokenBuilder::setExpireDate($date)->setUsageLimit($usageLimit)->build(); 211 | 212 | if ($token->isValid()) { 213 | echo 'token is valid'; 214 | 215 | $token->use(); // use token (increament usage counter) 216 | } else { 217 | echo 'token is not valid any more!'; 218 | } 219 | ``` 220 | 221 | you can use a token by running `use` method. 222 | you can expire a token by running `markAsExpired` method. (this method will update expiration date to current date). 223 | 224 | #### Add relations 225 | 226 | you can add a relation to tokens. this can be done in 2 different ways: 227 | 228 | ```php 229 | use Shetabit\TokenBuilder\Facade\TokenBuilder; 230 | use App\User; 231 | 232 | $user = User::first(); 233 | 234 | /** 235 | * first example: using TokenBuilder 236 | **/ 237 | 238 | $tokenObject = TokenBuilder::setRelatedItem($user)->build(); 239 | 240 | /** 241 | * second example: using a main model 242 | **/ 243 | 244 | $tokenObject = $user->temporaryTokenBuilder()->build(); 245 | 246 | // you can access to token's relation using the tokenable 247 | dd($tokenObject->tokenable); 248 | 249 | if ($tokenObject->tokenable) { 250 | echo $tokenObject->tokenable->email; 251 | } 252 | 253 | ``` 254 | 255 | #### Attach custom data 256 | 257 | you can attach custom data into your token. this data will be stored in json format. 258 | 259 | ```php 260 | use Shetabit\TokenBuilder\Facade\TokenBuilder; 261 | 262 | /** 263 | * first example: using TokenBuilder 264 | **/ 265 | 266 | $data = [ 267 | 'mobile' => '9373620353', 268 | 'name' => 'John Doe', 269 | ]; 270 | 271 | $tokenObject = TokenBuilder::setData($data)->build(); 272 | 273 | echo $tokenObject->data['mobile']; 274 | 275 | ``` 276 | 277 | #### Retrieve tokens 278 | 279 | you can retrieve `tokenObject` using token's unique string 280 | 281 | ```php 282 | $token = 22325651; // your unique token 283 | 284 | // retrieve token object 285 | $tokenObject = TokenBuilder::setUniqueId($token)->findToken(); 286 | 287 | // retrieve token object if it is valid 288 | $tokenObject = TokenBuilder::setUniqueId($token)->findValidToken(); 289 | 290 | // retrieve token object using its relation 291 | $tokenObject = $user->temporaryTokenBuilder()->setUniqueId($token)->findToken(); 292 | 293 | // retrieve token object if it is valid using its relation 294 | $tokenObject = $user->temporaryTokenBuilder()->setUniqueId($token)->findValidToken(); 295 | ``` 296 | 297 | #### TokenBuilder reference 298 | 299 | This is a reference for TokenBuilder methods. 300 | `TokenBuilder` creates a tokenObject (instance of eloquent `Token` model) if you run build method and searches into data base using `findToken` and `findValidToken`. 301 | 302 | - ###### setUniqueId 303 | - 304 | you can build tokens with your custom algoritm and use it as unique id. 305 | 306 | ```php 307 | $token = 'jgaZ1z9'; 308 | $tokenObject = TokenBuilder::setUniqueId($token)->build(); 309 | echo $tokenObject->token; // jgaZ1z9 310 | ``` 311 | 312 | - ###### getUniqueId 313 | - 314 | returns your custom unique id (if not exists, returns `null`). 315 | 316 | - ###### setData 317 | - 318 | you can attach some data into tokens and retrieve them later. 319 | 320 | - ###### getData 321 | 322 | returns attached data. 323 | 324 | - ###### setExpireDate 325 | 326 | sets an expiration date. 327 | 328 | - ###### getExpireDate 329 | 330 | retrieves expiration date. 331 | 332 | - ###### setUsageLimit 333 | 334 | sets usage limit. 335 | 336 | - ###### getUsageLimit 337 | 338 | retrieves usage limit. 339 | 340 | - ###### setType 341 | 342 | sets token type. you can set type for your tokens. its like a scope for your tokens. 343 | 344 | - ###### getType 345 | 346 | retrieves token type. 347 | 348 | - ###### setRelatedItem 349 | 350 | add a relation to token. 351 | 352 | - ###### getRelatedItem 353 | 354 | retrieve current relation. 355 | 356 | - ###### build 357 | 358 | generate a token. 359 | 360 | - ###### findToken 361 | 362 | find token (being valid or not valid is not important) and return `null` if not exists. 363 | 364 | - ###### findValidToken 365 | 366 | find token if it is valid and return `null` if not exists. 367 | 368 | #### Token Reference 369 | 370 | - ###### use 371 | 372 | add 1 to usage counter. (usageCounter = usageCounter + 1) 373 | 374 | - ###### hasUsed 375 | 376 | determine is current token has used and returns a boolean result. 377 | 378 | - ###### hasMaxUsageLimit 379 | 380 | determine if current token max usage is limited and returns a boolean result. 381 | 382 | - ###### hasExpired 383 | 384 | determine if current token has expired or not. 385 | 386 | - ###### markAsExpired 387 | 388 | mark current token as expired. 389 | 390 | **notice:** this method updates `expired_at` field to current time (now). 391 | 392 | - ###### isValid 393 | 394 | determines if current token is valid (must not be expired and must not be exceeded max usage limit). 395 | 396 | - ###### tokenable 397 | 398 | returns the token relation if there is any relation, and returns null if no relation exists. 399 | 400 | ## Change log 401 | 402 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 403 | 404 | ## Contributing 405 | 406 | Please see [CONTRIBUTING](CONTRIBUTING.md) and [CONDUCT](CONDUCT.md) for details. 407 | 408 | ## Security 409 | 410 | If you discover any security related issues, please email khanzadimahdi@gmail.com instead of using the issue tracker. 411 | 412 | ## Credits 413 | 414 | - [Mahdi khanzadi][link-author] 415 | - [All Contributors][link-contributors] 416 | 417 | ## License 418 | 419 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 420 | 421 | 422 | 423 | [link-author]: https://github.com/khanzadimahdi 424 | [link-contributors]: ../../contributors 425 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shetabit/token-builder", 3 | "type": "library", 4 | "description": "Laravel Token Builder", 5 | "keywords": [ 6 | "laravel", 7 | "token", 8 | "token builder", 9 | "onetime token", 10 | "expiring tokens", 11 | "one usage token", 12 | "expire token generator", 13 | "pin generator", 14 | "one time usage pin", 15 | "pin number", 16 | "laravel pin number", 17 | "laravel token", 18 | "laravel token builder", 19 | "laravel pin builder", 20 | "laravel pin generator" 21 | ], 22 | "homepage": "https://github.com/shetabit/token-builder", 23 | "license": "MIT", 24 | "authors": [ 25 | { 26 | "name": "Mahdi Khanzadi", 27 | "email": "khanzadimahdi@gmail.com", 28 | "homepage": "https://github.com/shetabit", 29 | "role": "Developer" 30 | } 31 | ], 32 | "require": { 33 | "php": ">=7.2", 34 | "illuminate/support": "^5.8|6.*|7.*|8.*|9.*|10.*|11.*|12.*" 35 | }, 36 | "require-dev": { 37 | "orchestra/testbench": "^4.4", 38 | "phpunit/phpunit": "^8.4", 39 | "squizlabs/php_codesniffer": "^3.5" 40 | }, 41 | "autoload": { 42 | "psr-4": { 43 | "Shetabit\\TokenBuilder\\": "src" 44 | } 45 | }, 46 | "autoload-dev": { 47 | "psr-4": { 48 | "Shetabit\\TokenBuilder\\Tests\\": "tests" 49 | } 50 | }, 51 | "scripts": { 52 | "test": "phpunit", 53 | "check-style": "phpcs -p --standard=PSR2 --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 src tests", 54 | "fix-style": "phpcbf -p --standard=PSR2 --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 src tests" 55 | }, 56 | "extra": { 57 | "branch-alias": { 58 | "dev-master": "1.0-dev" 59 | }, 60 | "laravel": { 61 | "providers": [ 62 | "Shetabit\\TokenBuilder\\Provider\\TokenBuilderServiceProvider" 63 | ], 64 | "aliases": { 65 | "TokenBuilder": "Shetabit\\TokenBuilder\\Facade\\TokenBuilder" 66 | } 67 | } 68 | }, 69 | "config": { 70 | "sort-packages": true 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /database/migrations/2016_11_02_070159_create_tokens_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->string('token')->index(); 19 | $table->timestamp('expired_at')->nullable(); 20 | $table->integer('usage_count')->default(0); 21 | $table->integer('max_usage_limit')->default(1); 22 | $table->mediumText('data')->nullable(); 23 | $table->string('type')->nullable(); 24 | $table->nullableMorphs('tokenable'); 25 | $table->timestamps(); 26 | $table->softDeletes(); 27 | }); 28 | } 29 | 30 | /** 31 | * Reverse the migrations. 32 | * 33 | * @return void 34 | */ 35 | public function down() 36 | { 37 | Schema::dropIfExists('tokens'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | ./src/ 14 | 15 | 16 | 17 | 18 | ./tests/ 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /resources/images/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shetabit/token-builder/59e087935f18f2389b2222d4d2445bda7df3a500/resources/images/.gitkeep -------------------------------------------------------------------------------- /resources/images/laravel-token-builder-package.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shetabit/token-builder/59e087935f18f2389b2222d4d2445bda7df3a500/resources/images/laravel-token-builder-package.png -------------------------------------------------------------------------------- /src/Abstracts/RepositoryAbstract.php: -------------------------------------------------------------------------------- 1 | makeModel(); 22 | } 23 | 24 | /** 25 | * Retrieve fillable fields 26 | * 27 | * @return mixed 28 | */ 29 | public function getFillable() 30 | { 31 | return $this->model->getFillable(); 32 | } 33 | 34 | /** 35 | * Create new item and store it 36 | * 37 | * @param array $data 38 | * 39 | * @return mixed 40 | */ 41 | public function create(array $data) 42 | { 43 | return $this->model->create($data); 44 | } 45 | 46 | /** 47 | * Find item using its id. 48 | * 49 | * @param $id 50 | * 51 | * @return mixed 52 | */ 53 | public function find($id) 54 | { 55 | return $this->model->find($id); 56 | } 57 | 58 | /** 59 | * Update item using given data. 60 | * 61 | * @param $model 62 | * @param array $data 63 | * 64 | * @return mixed 65 | */ 66 | public function update($model, array $data) 67 | { 68 | foreach ($data as $key => $value) { 69 | $model->{$key} = $value; 70 | } 71 | 72 | $model->save(); 73 | 74 | return $model; 75 | } 76 | 77 | /** 78 | * Delete item. 79 | * 80 | * @param $model 81 | * 82 | * @return mixed 83 | */ 84 | public function delete($model) 85 | { 86 | return $model->delete(); 87 | } 88 | 89 | /** 90 | * Instantiate model 91 | * 92 | * @return Model|\Illuminate\Foundation\Application|\Illuminate\Foundation\Auth\User|mixed 93 | * 94 | * @throws \Exception 95 | */ 96 | public function makeModel() 97 | { 98 | $model = app($this->model()); 99 | 100 | if ($model instanceof Model || $model instanceof \Illuminate\Foundation\Auth\User) { 101 | return $this->model = $model; 102 | } else { 103 | throw new \Exception("Class {$this->model()} must be an instance of Model"); 104 | } 105 | } 106 | 107 | /** 108 | * Call model methods. 109 | * 110 | * @param $method 111 | * @param $arguments 112 | * 113 | * @return mixed 114 | */ 115 | public function __call($method, $arguments) 116 | { 117 | return call_user_func_array( 118 | [ 119 | $this->model, 120 | $method, 121 | ], 122 | $arguments 123 | ); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/Builder.php: -------------------------------------------------------------------------------- 1 | tokensRepository = app(TokensRepository::class); 32 | } 33 | 34 | /** 35 | * Set token 36 | * 37 | * @param $uniqueId 38 | * 39 | * @return $this 40 | */ 41 | public function setUniqueId($uniqueId) 42 | { 43 | $this->uniqueId = $uniqueId; 44 | 45 | return $this; 46 | } 47 | 48 | /** 49 | * Retrieve uniqueId 50 | * 51 | * @return mixed 52 | */ 53 | public function getUniqueId() 54 | { 55 | return $this->uniqueId; 56 | } 57 | 58 | /** 59 | * Set data 60 | * 61 | * @param $token 62 | * 63 | * @return $this 64 | */ 65 | public function setData($data) 66 | { 67 | $this->data = is_array($data) ? $data : func_get_args(); 68 | 69 | return $this; 70 | } 71 | 72 | /** 73 | * Retrieve data 74 | * 75 | * @return array 76 | */ 77 | public function getData(): array 78 | { 79 | return $this->data ?? []; 80 | } 81 | 82 | /** 83 | * Set expiration date 84 | * 85 | * @param Carbon $date 86 | * 87 | * @return $this 88 | */ 89 | public function setExpireDate(Carbon $date) 90 | { 91 | $this->expiredAt = $date; 92 | 93 | return $this; 94 | } 95 | 96 | /** 97 | * Set expiration date 98 | * 99 | * @return mixed 100 | */ 101 | public function getExpireDate() 102 | { 103 | return $this->expiredAt; 104 | } 105 | 106 | /** 107 | * Set max usage count 108 | * 109 | * @param int $number 110 | * 111 | * @return $this 112 | */ 113 | public function setUsageLimit(int $number) 114 | { 115 | $this->usageLimit = $number; 116 | 117 | return $this; 118 | } 119 | 120 | /** 121 | * Retrieve max usage count 122 | * 123 | * @return int 124 | */ 125 | public function getUsageLimit(): int 126 | { 127 | return (int) $this->usageLimit; 128 | } 129 | 130 | /** 131 | * Set type 132 | * 133 | * @param string $type 134 | */ 135 | public function setType(string $type) 136 | { 137 | $this->type = $type; 138 | 139 | return $this; 140 | } 141 | 142 | /** 143 | * Retrieve type 144 | * 145 | * @return mixed 146 | */ 147 | public function getType() 148 | { 149 | return $this->type; 150 | } 151 | 152 | /** 153 | * Set related Eloquent Model instance 154 | * 155 | * @param Model $item 156 | * 157 | * @return $this 158 | */ 159 | public function setRelatedItem(Model $item) 160 | { 161 | $this->relatedItem = $item; 162 | 163 | return $this; 164 | } 165 | 166 | /** 167 | * Retrieve related Eloquent Model instance 168 | * 169 | * @return mixed 170 | */ 171 | public function getRelatedItem() 172 | { 173 | return $this->relatedItem; 174 | } 175 | 176 | /** 177 | * Create a new token 178 | * 179 | * @return Token 180 | */ 181 | public function build($length = 8): Token 182 | { 183 | $tokenData = [ 184 | 'token' => $this->getUniqueId() ?? $this->generateRandomInt($length), 185 | 'expired_at' => $this->getExpireDate(), 186 | 'max_usage_limit' => $this->getUsageLimit(), 187 | 'type' => $this->getType(), 188 | 'data' => $this->getData() 189 | ]; 190 | 191 | if ($this->getRelatedItem()) { 192 | $token = $this->getRelatedItem()->temporaryTokens()->create($tokenData); 193 | } else { 194 | $token = $this->tokensRepository->create($tokenData); 195 | } 196 | 197 | return $token; 198 | } 199 | 200 | /** 201 | * Alias build 202 | * 203 | * @alias build 204 | * 205 | * @return Token 206 | */ 207 | public function create(): Token 208 | { 209 | return $this->build(); 210 | } 211 | 212 | /** 213 | * Retrieve a token. 214 | * 215 | * @return Token|null 216 | */ 217 | public function findToken(): ?Token 218 | { 219 | $token = $this->getUniqueId(); 220 | $type = $this->getType(); 221 | $tokenable = $this->getRelatedItem(); 222 | 223 | return empty($token) ? null : $this->tokensRepository->findToken($token, $type, $tokenable); 224 | } 225 | 226 | /** 227 | * Generate a random token 228 | * 229 | * @return int 230 | */ 231 | private function generateRandomInt($length) 232 | { 233 | return random_int(10 ** ($length - 1) + 1, (10 ** $length) - 1); 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /src/Contracts/RepositoryInterface.php: -------------------------------------------------------------------------------- 1 | 'integer', 46 | 'max_usage_limit' => 'integer', 47 | 'data' => 'json', 48 | ]; 49 | 50 | /** 51 | * Retrieve related model. 52 | * 53 | * @return \Illuminate\Database\Eloquent\Relations\MorphTo 54 | */ 55 | public function tokenable() 56 | { 57 | return $this->morphTo(); 58 | } 59 | 60 | /** 61 | * Mark token as used 62 | * 63 | * @return mixed 64 | */ 65 | public function use() 66 | { 67 | $this->increment('usage_count'); 68 | 69 | return $this; 70 | } 71 | 72 | /** 73 | * Determine if token has used 74 | * 75 | * @return bool 76 | */ 77 | public function hasUsed() 78 | { 79 | return $this->usage_count > 0 ; 80 | } 81 | 82 | /** 83 | * Determine if token has expired 84 | * 85 | * @return bool 86 | */ 87 | public function hasExpired() 88 | { 89 | return $this->expired_at === null ? false : now()->gt($this->expired_at); 90 | } 91 | 92 | /** 93 | * Mark token as expired 94 | * 95 | * @return mixed 96 | */ 97 | public function markAsExpired() 98 | { 99 | $this->forceFill(['expired_at' => now()])->save(); 100 | 101 | return $this; 102 | } 103 | 104 | /** 105 | * Determine usage limit is enabled. 106 | * 107 | * @return bool 108 | */ 109 | public function hasMaxUsageLimit() 110 | { 111 | return $this->max_usage_limit > 0; 112 | } 113 | 114 | /** 115 | * Determine usage limit has exceed. 116 | * 117 | * @return bool 118 | */ 119 | public function hasExceedMaxUsage() 120 | { 121 | $maxUsageLimit = $this->max_usage_limit; 122 | $usageCount = $this->usage_count; 123 | 124 | return $this->hasMaxUsageLimit() && ($usageCount >= $maxUsageLimit); 125 | } 126 | 127 | /** 128 | * Determine token is valid. 129 | * 130 | * @return bool 131 | */ 132 | public function isValid() 133 | { 134 | return !($this->hasExpired() || $this->hasExceedMaxUsage()); 135 | } 136 | 137 | /** 138 | * Filter valid tokens 139 | * 140 | * @param $query 141 | * 142 | * @return mixed 143 | */ 144 | public function scopeValid($query) 145 | { 146 | return $query 147 | ->where(function($query) { // check usage limit 148 | return $query 149 | ->where('max_usage_limit', '=', '0') 150 | ->orWhereRaw('usage_count < max_usage_limit'); 151 | }) 152 | ->where(function($query) { // check expiration time 153 | return $query 154 | ->whereNull('expired_at') 155 | ->orWhere('expired_at', '>', now()); 156 | }); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/Provider/TokenBuilderServiceProvider.php: -------------------------------------------------------------------------------- 1 | publishes( 21 | [ 22 | __DIR__.'/../../database/migrations/' => database_path('migrations') 23 | ], 24 | 'migrations' 25 | ); 26 | } 27 | 28 | /** 29 | * Register any package services. 30 | * 31 | * @return void 32 | */ 33 | public function register() 34 | { 35 | /** 36 | * Bind to service container. 37 | */ 38 | $this->app->bind('shetabit-token-builder', function () { 39 | return new Builder(); 40 | }); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Repositories/TokensRepository.php: -------------------------------------------------------------------------------- 1 | temporaryTokens(); 29 | } else { 30 | $query = $this->model; 31 | } 32 | 33 | return $query 34 | ->where('token', '=', $token) 35 | ->when(!empty($type), function ($query) use ($type) { 36 | return $query->where('type', '=', $type); 37 | }) 38 | ->with('tokenable') 39 | ->first(); 40 | } 41 | 42 | /** 43 | * Retrieve a token if it is valid. 44 | * 45 | * @param $token 46 | * @param $type 47 | * @param $tokenable 48 | * 49 | * @return Token|null 50 | */ 51 | public function findValidToken($token, ?string $type = null, ?Model $tokenable = null) : ?Token 52 | { 53 | if (!empty($tokenable)) { 54 | $query = $tokenable->temporaryTokens(); 55 | } else { 56 | $query = $this->model; 57 | } 58 | 59 | return $query 60 | ->valid() 61 | ->where('token', '=', $token) 62 | ->when(!empty($type), function ($query) use ($type) { 63 | return $query->where('type', '=', $type); 64 | }) 65 | ->with('tokenable') 66 | ->first(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Traits/Concerns/Validation.php: -------------------------------------------------------------------------------- 1 | getUniqueId(); 17 | $type = $this->getType(); 18 | $tokenable = $this->getRelatedItem(); 19 | 20 | return empty($token) ? null : $this->tokensRepository->findValidToken($token, $type, $tokenable); 21 | } 22 | 23 | /** 24 | * Determine if token is valid 25 | * 26 | * @return bool 27 | */ 28 | public function isValid() : bool 29 | { 30 | return (bool) $this->findValidToken(); 31 | } 32 | 33 | /** 34 | * Determine if token is invalid 35 | * 36 | * @return bool 37 | */ 38 | public function isInvalid() : bool 39 | { 40 | return !$this->isValid(); 41 | } 42 | 43 | /** 44 | * Alias for invalid 45 | * 46 | * @alias isInvalid 47 | * 48 | * @return bool 49 | */ 50 | public function isNotValid() : bool 51 | { 52 | return $this->isInvalid(); 53 | } 54 | } -------------------------------------------------------------------------------- /src/Traits/HasTemporaryTokens.php: -------------------------------------------------------------------------------- 1 | morphMany(Token::class, 'tokenable'); 16 | } 17 | 18 | /** 19 | * Token builder factory method. 20 | * 21 | * @return Builder 22 | */ 23 | public function temporaryTokenBuilder() : Builder 24 | { 25 | $tokenBuilder = new Builder; 26 | 27 | $tokenBuilder->setRelatedItem($this); 28 | 29 | return $tokenBuilder; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 |