├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .idea ├── .gitignore ├── codeception.xml ├── deployment.xml ├── inspectionProfiles │ ├── Project_Default.xml │ └── profiles_settings.xml ├── laravel-idea.xml ├── modules.xml ├── php.xml ├── phpspec.xml ├── phpunit.xml ├── tokens-validation.iml └── vcs.xml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── composer.json ├── composer.lock ├── index.php ├── meta-data └── picture.png └── src ├── Actions ├── Authentication │ ├── AuthTokenCookiesHandler.php │ └── AuthTokenGenerator.php ├── Confirmation │ ├── ConfirmationCodeGenerator.php │ ├── ConfirmationUrlBuilder.php │ ├── UserIdAndToken.php │ └── UserIdAndTokenBuilder.php ├── Invitation │ ├── InvitationTokenGenerator.php │ └── InvitationUrlBuilder.php └── UserIdEncrypter.php ├── DefaultConfig.php ├── Laravel ├── Console │ └── Commands │ │ └── TokensValidation │ │ ├── CreateCustomHandler.php │ │ └── stubs │ │ ├── Authentication │ │ ├── AuthTokenCookiesHandler.stub │ │ └── AuthTokenGenerator.stub │ │ ├── Confirmation │ │ ├── ConfirmationCodeGenerator.stub │ │ ├── ConfirmationUrlBuilder.stub │ │ └── UserIdEncrypter.stub │ │ └── Invitation │ │ ├── InvitationTokenGenerator.stub │ │ └── InvitationUrlBuilder.stub ├── Http │ └── Controllers │ │ └── InvitationAnswererController.php ├── Providers │ └── TokensValidationProvider.php ├── config │ └── tokensvalidation.php └── stubs │ └── TokensValidationProvider.php ├── Model ├── Authentication │ ├── AuthToken.php │ ├── AuthTokenBuilder.php │ └── AuthTokenModel.php ├── Confirmation │ ├── ConfirmationToken.php │ ├── ConfirmationTokenBuilder.php │ ├── ConfirmationTokenModel.php │ └── ConfirmationsTokenTypes.php └── Invitation │ ├── Invitation.php │ ├── InvitationBuilder.php │ └── InvitationModel.php ├── Results ├── Authentication │ ├── AuthTokenResponse.php │ └── AuthTokenResponseBuilder.php ├── BaseResults.php ├── BaseResultsBuilder.php ├── Confirmation │ ├── ConfirmationTokenResponse.php │ └── ConfirmationTokenResponseBuilder.php └── Invitation │ ├── InvitationResponse.php │ └── InvitationResponseBuilder.php ├── TokensTypes.php └── TokensValidation.php /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/codeception.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 32 | 33 | -------------------------------------------------------------------------------- /.idea/deployment.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/laravel-idea.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/php.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 82 | -------------------------------------------------------------------------------- /.idea/phpspec.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 11 | 12 | 14 | 15 | 17 | 18 | 20 | 21 | 23 | 24 | 26 | 27 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /.idea/phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/tokens-validation.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | hichem.tab2002@gmail.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to TokensValidation 2 | 3 | We're thrilled that you're interested in contributing to TokensValidation! By following these guidelines, you can help ensure a smooth and collaborative contribution process. 4 | 5 | ## How to Contribute 6 | 7 | 1- Fork the repository on GitHub. 8 | 9 | 2- Clone your forked repository to your local machine: 10 | 11 | ```bash 12 | 13 | git clone https://github.com/HichemTab-tech/tokens-validation.git 14 | 15 | ``` 16 | 17 | 3- Create a new branch for your contribution: 18 | 19 | ```bash 20 | 21 | git checkout -b feature/your-feature-name 22 | 23 | ``` 24 | 25 | 4- Make your changes and commit them: 26 | 27 | ```bash 28 | 29 | git commit -m "Add your descriptive commit message here" 30 | 31 | ``` 32 | 33 | 5- Push your changes to your forked repository: 34 | 35 | ```bash 36 | 37 | git push origin feature/your-feature-name 38 | 39 | ``` 40 | 41 | 6- Open a pull request (PR) on GitHub. Provide a clear and descriptive title, and include details about the changes you've made in the PR description. 42 | 43 | ## Code Style and Standards 44 | Please ensure that your code adheres to the coding standards and style guidelines used in the project. If there are existing code conventions, follow them consistently to maintain code consistency across the project. 45 | 46 | ## Testing 47 | Before submitting your changes, make sure that your new features are tested. 48 | 49 | ## Issues and Bug Reports 50 | If you find a bug or have an issue, please check the existing issues on GitHub to see if it has already been reported. If not, feel free to open a new issue with a clear description and steps to reproduce the problem. 51 | 52 | ## Feature Requests 53 | We welcome new ideas and feature requests! If you have a feature you'd like to propose, open a new issue on GitHub and provide a detailed explanation of the feature's purpose and benefits. 54 | 55 | ## Licensing 56 | By contributing to this project, you agree that your contributions will be licensed under the project's [LICENSE](https://github.com/HichemTab-tech/tokens-validation/blob/master/LICENSE). 57 | 58 | ## Contact 59 | If you have any questions or need further assistance, you can reach out to the maintainers at hichem.tab2002@gmail.com. 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 HichemTab 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ![Logo](https://github.com/HichemTab-tech/tokens-validation/blob/master/meta-data/picture.png) 3 | 4 | 5 | # TokensValidation 6 | 7 | TokensValidation is a PHP library designed to generate and verify authentication and confirmation tokens dynamically. It is ideal for web applications and software that require secure user authentication and authorization. The library generates authentication and confirmation tokens that can be used to log in users, reset passwords, and confirm email addresses. 8 | 9 | ## Table of Contents 10 | 11 | 1. [Installation](#installation) 12 | 2. [Features](#features) 13 | 3. [Usage](#usage) 14 | 1. [Authentication Tokens](#authentication-tokens) 15 | 1. [Using Cookies](#1-using-cookies) 16 | 2. [Handling the Token Yourself](#2-handling-the-token-yourself) 17 | 3. [Overriding the Cookie Handler Methods](#3-overriding-the-cookie-handler-methods) 18 | 2. [Fingerprint](#fingerprint) 19 | 3. [Confirmation Tokens](#confirmation-tokens) 20 | 1. [By URL](#1--by-url) 21 | 2. [By Typing](#2--by-typing) 22 | 3. [WhatFor Field](#whatfor-field) 23 | 4. [Delete after check](#delete-after-check) 24 | 5. [Single Token Per Period](#single-token-per-period) 25 | 4. [Tokens Generator](#tokens-generator) 26 | 5. [Token Expiration](#token-expiration) 27 | 6. [Invitations](#invitations) 28 | 7. [In Laravel](#in-laravel) 29 | 8. [Errors Identification](#errors-identification) 30 | 4. [License](#license) 31 | 32 | 33 | ## Installation 34 | 35 | > ℹ️ **INFO**: This version is compatible with PHP 8 and above. If you are using PHP 7, you must install version [3.2.2](https://github.com/HichemTab-tech/tokens-validation/tree/3.2.2) of this library. 36 | 37 | The TokensValidation library can be installed via Composer by running the following command: 38 | 39 | ```bash 40 | composer require hichemtab-tech/tokens-validation 41 | ``` 42 | 43 | 44 | ## Features 45 | 46 | - Authenticate the user after the browser closed without a password 47 | - Generate custom confirmation codes with expiration delay. 48 | - Create invitation tokens to do some actions. 49 | - Flexibility of usage 50 | - Security & Encryption 51 | 52 | 53 | ## Usage 54 | 55 | 56 | To use the library, you first need to set up the database connection parameters by calling the following methods: 57 | 58 | ```PHP 59 | isValidationSucceed()) { 106 | // log in the user automatically, 107 | //for example : 108 | autoLogin($result->getUserId()); 109 | } 110 | else{ 111 | //throw an error or redirect to log in 112 | } 113 | 114 | ``` 115 | 116 | **TokensValidation::checkAuthToken()** checks the authentication token in the cookie and returns the result. If the validation is successful, a new token is generated and replaced in the cookie. 117 | 118 | ##### check the token without regenerating a new one: 119 | 120 | ```PHP 121 | $result = TokensValidation::checkAuthTokenWithoutRegenerate(); 122 | ``` 123 | This line of code executes a function called TokensValidation::checkAuthTokenWithoutRegenerate() which performs a check on the token to ensure that it is valid, but it does not regenerate a new token to replace the old one. The purpose of this check is to verify that the token is still valid and has not been tampered with. If the token is still valid, the function will return a successful result, which will be stored in the variable "$result". 124 | 125 | It is important to note that this function only checks the token's validity without regenerating it. If the token is invalid or has expired, the function will return an error or failure message, indicating that the user must log in again to obtain a new token. Therefore, this function provides an additional layer of security by ensuring that the token is still valid before proceeding with any further actions that require it. 126 | 127 | ### 128 | 129 | #### 2-Handling the token yourself 130 | 131 | To generate an authentication token and handle it yourself, call the following method: 132 | 133 | ```PHP 134 | $authToken = TokensValidation::createNewAuthToken( 135 | userId: $uid, 136 | usingCookies: false 137 | ); 138 | 139 | echo $authToken->getUserId(); 140 | echo $authToken->getContent(); 141 | ``` 142 | 143 | This method generates a new authentication token for the given user ID and returns the token object without saving the token anywhere to let you handle it as you like. 144 | 145 | To check the authentication token, call the following method and pass the token as argument: 146 | 147 | ```PHP 148 | $result = TokensValidation::checkAuthToken(authToken: $authToken); 149 | if ($result->isValidationSucceed()) { 150 | // log in the user automatically, 151 | //for example : 152 | echo $result->getUserId(); 153 | echo $result->getNewToken()->getContent();// save it in cookies or whatever you want 154 | autoLogin($result->getUserId()); 155 | } 156 | else{ 157 | //throw an error or redirect to log in 158 | } 159 | ``` 160 | 161 | ### 162 | 163 | #### 3-Overriding the cookie handler methods: 164 | 165 | To override the cookies handler methods, create a class that extends the **AuthTokenCookiesHandler** class and implements the **save()**, **get()**, and **delete()** methods. Then, set the **$AuthTokenCookiesHandler** property to the name of your new class. Here's an example: 166 | 167 | ```PHP 168 | use HichemtabTech\TokensValidation\Actions\Authentication\AuthTokenCookiesHandler; 169 | 170 | class MyAuthTokenCookiesHandler extends AuthTokenCookiesHandler 171 | { 172 | public function save(AuthToken $authToken): void 173 | { 174 | //save the $authToken in cookies or what ever you want 175 | } 176 | 177 | public function get(): ?string 178 | { 179 | //get it 180 | } 181 | 182 | public function delete(): void 183 | { 184 | //delete it 185 | } 186 | } 187 | 188 | TokensValidation::$AuthTokenCookiesHandler = MyAuthTokenCookiesHandler::class; 189 | 190 | // this should be called after preparation 191 | 192 | ``` 193 | 194 | 195 | ## Fingerprint 196 | You can add an extra layer of security to authentication tokens by using a browser fingerprint. The library supports fingerprinting by passing a fingerprint string to the createNewAuthToken() method. 197 | 198 | ```PHP 199 | $authToken = TokensValidation::createNewAuthToken( 200 | userId: $uid, 201 | fingerPrint: $somefingerprint 202 | ); 203 | ``` 204 | To check the authentication token with a fingerprint, call the **checkAuthToken()** method with the fingerprint argument. 205 | 206 | ```PHP 207 | $result = TokensValidation::checkAuthToken(authToken: $authToken, fingerPrint: $somefingerprint); 208 | ``` 209 | 210 | - *to generate the fingerprint, you can use for example https://github.com/Valve/fingerprintjs2* 211 | 212 | 213 | 214 | ### Confirmation tokens 215 | 216 | Confirmation tokens are unique codes that can be generated by **TokensValidation** to verify the identity of a user or to confirm their authorization for a specific action. In general, confirmation tokens are used for email verification or password reset purposes, although they can be used for other purposes as well. 217 | 218 | Confirmation processes can be classified into two types: those that require the user to click on a confirmation link *(by URL)* and those that require the user to manually enter a confirmation code *(by typing)*. 219 | 220 | #### 1- By Url 221 | In this case, the library generates a lengthy token that includes an encrypted user ID and is designed to be included in URL parameters. 222 | 223 | ```PHP 224 | $confirmationToken = TokensValidation::createNewConfirmationToken( 225 | userId: $uid, 226 | confirmationType: ConfirmationsTokenTypes::IN_URL 227 | ); 228 | 229 | echo $confirmationToken->getContent();// if you want to view the long confirmation token 230 | echo $confirmationToken->getUrl("http://localhost/email-check");// to get the full prepared url with the base url, http://localhost/email-check 231 | 232 | //you will get the url like this: 233 | //http://localhost/email-check?u=def5020080134ecc82ee1c1c2536ccdb0ec3c50161a9b6ab6f0f24c34730b73174327d2990ef8f583cec5f86ba7c3d44c5a5c0adae17313d09d5479fbe83c33e91f00d3902699507fc16266931be4e0f90382e4614aba6d8&c=nkyZDxMqbnS2oPs 234 | ``` 235 | 236 | You can utilize these lines of code to verify the contents of a URL. 237 | 238 | ```PHP 239 | $result = TokensValidation::checkConfirmationUrl(url: $url); 240 | if ($result->isValidationSucceed()) { 241 | //for example : 242 | echo $result->getUserId(); 243 | //continue the request 244 | } 245 | else{ 246 | //throw an error 247 | } 248 | ``` 249 | 250 | Or you can inject **$_GET** directly: 251 | 252 | ```PHP 253 | $result = TokensValidation::checkConfirmationUrlParamsFromGET(_GET_ARRAY: $_GET); 254 | ``` 255 | 256 | To override the ConfirmationUrl builder methods, create a class that extends the **ConfirmationUrlBuilder** class and implements the **getUrl(ConfirmationToken $confirmationToken, string $baseUrl)**, **getUserIdAndTokenFromUrl(string $url)**, and **getUserIdAndTokenFromGET(array $_GET_ARRAY)** methods. Then, set the $ConfirmationUrlBuilder property to the name of your new class. Here's an example: 257 | 258 | ```PHP 259 | use HichemtabTech\TokensValidation\Actions\Confirmation\ConfirmationUrlBuilder; 260 | use HichemtabTech\TokensValidation\Actions\Confirmation\UserIdAndToken; 261 | use HichemtabTech\TokensValidation\Model\Confirmation\ConfirmationToken; 262 | use Purl\Url; 263 | 264 | class MyConfirmationUrlBuilder extends ConfirmationUrlBuilder 265 | { 266 | public function getUrl(ConfirmationToken $confirmationToken, string $baseUrl): string 267 | { 268 | $url = new Url($baseUrl); 269 | $url->query->set("uid", $confirmationToken->getUserId());// for userId 270 | $url->query->set("token", $confirmationToken->getContent());// for Code 271 | return $url->getUrl(); 272 | } 273 | 274 | public function getUserIdAndTokenFromUrl(string $url): UserIdAndToken 275 | { 276 | $url = new Url($url); 277 | return UserIdAndToken::builder() 278 | ->setUserId($url->query->get("uid")) 279 | ->setToken($url->query->get("token")) 280 | ->build(); 281 | } 282 | 283 | public function getUserIdAndTokenFromGET(array $_GET_ARRAY): UserIdAndToken 284 | { 285 | return UserIdAndToken::builder() 286 | ->setUserId($_GET_ARRAY["uid"]??"") 287 | ->setToken($_GET_ARRAY["token"]??"") 288 | ->build(); 289 | } 290 | } 291 | 292 | TokensValidation::$ConfirmationUrlBuilder = MyConfirmationUrlBuilder::class; 293 | 294 | // this should be called after preparation 295 | ``` 296 | #### 2- By typing 297 | 298 | In this case, the user needs to enter the confirmation token generated by the library into a form field. The library can generate a short confirmation token for convenience: 299 | 300 | ```PHP 301 | $confirmationToken = TokensValidation::createNewConfirmationToken( 302 | userId: $uid, 303 | confirmationType: ConfirmationsTokenTypes::SMALL_CODE 304 | ); 305 | 306 | echo $confirmationToken->getContent(); 307 | 308 | 309 | $result = TokensValidation::checkConfirmationCode(code: $token); 310 | if ($result->isValidationSucceed()) { 311 | //for example : 312 | echo $result->getUserId(); 313 | //continue the request 314 | } 315 | else{ 316 | //throw an error 317 | } 318 | ``` 319 | 320 | #### WhatFor field: 321 | To ensure that each confirmation code is used for its intended purpose, you can include a "**whatFor**" parameter when calling the **createNewConfirmationToken** function. This parameter allows you to specify the purpose of the confirmation code, providing an additional layer of security and accuracy. 322 | 323 | ```PHP 324 | $confirmationToken = TokensValidation::createNewConfirmationToken( 325 | userId: $uid, 326 | confirmationType: ConfirmationsTokenTypes::SMALL_CODE, 327 | whatFor: "email-confirmation" 328 | ); 329 | ``` 330 | 331 | 332 | To check it : 333 | ```PHP 334 | $result = TokensValidation::checkConfirmationCode(code: $token, whatFor: "email-confirmation"); 335 | ``` 336 | 337 | If the "whatFor" parameter does not match the intended purpose of the confirmation code, the validation process will fail. 338 | 339 | #### Delete after check: 340 | In some cases, you may only want to check the token and keep it active like for examples (middleware checks) 341 | you want just to check the token if its valid, then check it later in another position. 342 | This parameter allows you to specify whether the token will be deleted after the validation succeeded or not. 343 | 344 | ```PHP 345 | $confirmationToken = TokensValidation::checkConfirmationCode( 346 | userId: $uid, 347 | confirmationType: ConfirmationsTokenTypes::SMALL_CODE, 348 | whatFor: "email-confirmation", 349 | deleteAfterCheck: false, //true by default 350 | ); 351 | ``` 352 | 353 | #### Single Token Per Period: 354 | To avoid creating multiple confirmation code at the same moment (before expiration), 355 | you can set "**singleTokenPerTime**" parameter to true when calling the **createNewConfirmationToken** function. 356 | This parameter allows TokensValidation to create only one confirmation code per time, 357 | and in case the user requests another code with the same purpose (same whatFor value) 358 | the library returns the existed token only with different expiration date. 359 | 360 | ```PHP 361 | $confirmationToken = TokensValidation::createNewConfirmationToken( 362 | userId: $uid, 363 | confirmationType: ConfirmationsTokenTypes::SMALL_CODE, 364 | whatFor: "email-confirmation", 365 | singleTokenPerTime: true 366 | ); 367 | ``` 368 | 369 | ### Tokens generator 370 | 371 | You can modify the behavior of the token and confirmation code generator by creating a new class that extends the **AuthTokenGenerator::class** and **ConfirmationCodeGenerator::class**. This allows you to customize the functionality of the generator to meet the specific needs of your project. 372 | 373 | ```PHP 374 | TokensValidation::$AuthTokenGenerator = MyAuthTokenGenerator::class; 375 | TokensValidation::$ConfirmationCodeGenerator = MyConfirmationCodeGenerator::class; 376 | TokensValidation::$InvitationTokenGenerator = MyInvitationTokenGenerator::class; 377 | ``` 378 | 379 | ### Token expiration 380 | 381 | By default, tokens generated by the library expire after a period of time (7 days for authentication tokens and 10 minutes for confirmation tokens). You can customize these expiration periods using the **setAuthTokenExpirationDelay()** and **setConfirmationTokenExpirationDelay()** methods, respectively: 382 | 383 | ```PHP 384 | // Set authentication token expiration period to 2 days 385 | TokensValidation::setAuthTokenExpirationDelay(2 * 24 * 60 * 60); // seconds 386 | 387 | // Set confirmation token expiration period to 1 hour 388 | TokensValidation::setConfirmationTokenExpirationDelay(60 * 60); // seconds 389 | 390 | //these lines should be called after preparation. 391 | ``` 392 | 393 | You have the option to provide a custom expiration delay by passing the "expirationDelay" parameter to the function which generates the token for either the confirmation token or authentication token. You can accomplish this by using the following code: 394 | ```PHP 395 | $confirmationToken = TokensValidation::createNewConfirmationToken( 396 | userId: $uid, 397 | expirationDelay: 60*60 // seconds 398 | ); 399 | ``` 400 | 401 | ### Invitations 402 | 403 | The library offers a feature to create an invitation with a token, which can be sent to users via email to authorize them to perform certain actions, such as joining a project or becoming an admin. 404 | In order to utilize this functionality, it is necessary to enable the feature prior to calling the prepare() method. By default, the library includes two enabled features, namely AuthToken and ConfirmationToken. To activate additional features, you should call this before **prepare()**: 405 | 406 | ```PHP 407 | ... 408 | TokensValidation::setFeatures([ 409 | 'AuthTokens', 410 | 'ConfirmationToken', 411 | 'InvitationsTokens' 412 | ]); 413 | ... 414 | TokensValidation::prepare(); 415 | ``` 416 | 417 | Or using config file: 418 | 419 | ```PHP 420 | [ 427 | 'AuthTokens', 428 | 'ConfirmationToken', 429 | 'InvitationsTokens' 430 | ] 431 | ... 432 | ]; 433 | ``` 434 | 435 | #### Create the invitation: 436 | 437 | you can create an invitation by calling this code: 438 | 439 | ```PHP 440 | $invitation = TokensValidation::createInvitation( 441 | userId: $uid, 442 | target_email: "user@example.com", 443 | whatFor: "become-admin", 444 | ); 445 | 446 | echo $invitation->getUrl(); 447 | 448 | ``` 449 | 450 | To adjust the default parameters of the invitation feature in the library, you will need to modify the configuration settings or by calling this code: 451 | 452 | ```PHP 453 | TokensValidation::setInvitationTokenExpirationDelay(60*60*24); 454 | TokensValidation::$InvitationBaseUrl = "http://localhost/invitations"; 455 | TokensValidation::$InvitationTokenGenerator = MyInvitationTokenGenerator::class; 456 | TokensValidation::$InvitationUrlBuilder = MyInvitationUrlBuilder::class; 457 | ``` 458 | 459 | #### Check the invitation: 460 | 461 | Typically, when using the invitation feature for actions such as sign up, the user is required to provide some information. In order to facilitate this process, the library allows for checking of the invitation, which enables the user to access the input page for providing the required information. 462 | 463 | ```PHP 464 | 465 | isValidationSucceed()) { 473 | redirectTo("/errors/invalid-invitation.html"); 474 | } 475 | 476 | ?> 477 | 478 |
479 | 480 | 481 | 482 | 483 |
484 | 485 | ``` 486 | 487 | After the user inputs the required information and submits the form, the token needs to be checked once again. Once the token is verified, it is possible to delete the invitation or mark it as "**accepted**". Here is an example of how this can be achieved: 488 | 489 | ```PHP 490 | ... 491 | 492 | //verify the data entered by the user. 493 | 494 | ... 495 | 496 | $invitation = TokensValidation::checkInvitationToken( 497 | token: $_GET['token'], 498 | whatFor: "administration", 499 | thenAccept: true 500 | ); 501 | 502 | if (!$invitation->isValidationSucceed()) { 503 | die("INVITATION_INVALID"); 504 | } 505 | 506 | ... 507 | 508 | //insert the data entered by the user. 509 | //performe some actions 510 | 511 | echo "invitation accepted"; 512 | 513 | ... 514 | 515 | ``` 516 | The **thenAccept** parameter in the method is utilized to mark the invitation as "accepted" after the token has been checked and verified. This ensures that the invitation is no longer active and cannot be used again in the future. 517 | 518 | ## In Laravel 519 | 520 | You can use a configuration file named config/tokensvalidation.php to configure your library with its parameters. 521 | Here's an example of tokensvalidation.php: 522 | 523 | ```PHP 524 | // you can customize the Classes here 525 | return [ 526 | 'connections' => [ 527 | 'mysql' => [ 528 | 'driver' => 'mysql', 529 | 'host' => 'localhost', 530 | 'database' => 'db', 531 | 'username' => 'root', 532 | 'password' => '', 533 | 'charset' => 'utf8mb4', 534 | 'collation' => 'utf8mb4_unicode_ci', 535 | 'prefix' => '', 536 | ], 537 | ], 538 | 539 | 'AuthTokens' => [ 540 | 'expirationDelay' => 60*60*24*7, 541 | 'AuthTokenGenerator' => MyAuthTokenGenerator::class, 542 | 'AuthTokenCookiesHandler' => MyAuthTokenCookiesHandler::class, 543 | ], 544 | 545 | 'ConfirmationToken' => [ 546 | 'expirationDelay' => 60*10, 547 | 'ConfirmationUrlBuilder' => MyConfirmationUrlBuilder::class, 548 | 'ConfirmationCodeGenerator' => MyConfirmationCodeGenerator::class, 549 | 'UserIdEncrypter' => MyUserIdEncrypter::class 550 | ], 551 | 552 | 'InvitationToken' => [ 553 | 'expirationDelay' => 60*60*24*3, 554 | 'InvitationUrlBuilder' => InvitationUrlBuilder::class, 555 | 'InvitationTokenGenerator' => InvitationTokenGenerator::class, 556 | 'InvitationBaseUrl' => "http://localhost/invitations", 557 | ], 558 | 559 | 'features' => [ 560 | 'AuthTokens', 561 | 'ConfirmationToken', 562 | //'InvitationsTokens' 563 | ] 564 | ]; 565 | ``` 566 | 567 | To prepare or publish your files to a Laravel project, you need to run the following command: 568 | 569 | ```bash 570 | php artisan vendor:publish --provider="HichemtabTech\TokensValidation\Laravel\Providers\TokensValidationProvider" 571 | ``` 572 | 573 | In Laravel project you can generate class to override the default classes of TokensValidation 574 | (ConfirmationUrlBuilder, AuthTokenCookiesHandler...) by running the following command: 575 | 576 | ```bash 577 | php artisan tokens-validation:handler 578 | ``` 579 | 580 | after that, choose what class you want to generate and follow the instruction shown in the console. 581 | 582 | ### Errors identification 583 | 584 | To identify and troubleshoot errors that may occur, you can utilize the **$result->getCause()** function, 585 | which returns a reference code to indicate the specific cause of the error. 586 | This function may return several possible error codes. 587 | ## Error Reference 588 | 589 | | Error | Cause | 590 | |-----------------------|--------------------------------------------------------------------------------------------------------------------------| 591 | | "EXCEPTION" | Means there was an exception during the execution you can check the exception by calling **$result->getException()** | 592 | | "NO_TOKEN_PROVIDED" | When the token parameters is null | 593 | | "TOKEN_INVALID" | When no matching token is found in the database. | 594 | | "TOKEN_EXPIRED" | Expired token | 595 | | "REASON_INVALID" | When attempting to verify a confirmation token and the "whatFor" field does not match the intended purpose of the token. | 596 | | "TOKEN_UID_INVALID" | When the confirmation URL contains an invalid or incorrect user ID. | 597 | | "FINGERPRINT_INVALID" | When the fingerprint field of the authentication token does not match the expected value. | 598 | 599 | 600 | ## License 601 | 602 | [MIT](https://github.com/HichemTab-tech/tokens-validation/blob/master/LICENSE) 603 | 604 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting Security Issues 4 | 5 | If you discover a security issue, please email us at hichem.tab2002@gmail.com to report it. DO NOT create a public GitHub issue. 6 | 7 | ## Supported Versions 8 | 9 | Our security policy applies to the latest release of the project. Keep your installation up to date for the best security experience. 10 | 11 | ## Updates and Patching 12 | 13 | We will provide patches or updates for confirmed security vulnerabilities as quickly as possible. 14 | 15 | ## Attribution 16 | 17 | We appreciate security researchers' efforts and will acknowledge your contribution unless you prefer to remain anonymous. 18 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hichemtab-tech/tokens-validation", 3 | "description": "TokensValidation is a PHP library for secure authentication and authorization in web applications. It generates dynamic tokens for user login, password reset, and email confirmation. The library is ideal for software that requires secure user authentication and authorization.", 4 | 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "HichemTab", 9 | "email": "konanhichemsinshi@gmail.com" 10 | } 11 | ], 12 | "autoload": { 13 | "psr-4": { 14 | "HichemtabTech\\TokensValidation\\": "src/" 15 | } 16 | }, 17 | "require": { 18 | "php": ">=8.0", 19 | "illuminate/database": "^10", 20 | "illuminate/events": "^10", 21 | "illuminate/container": "^10", 22 | "defuse/php-encryption": "^2.3", 23 | "illuminate/http": "^10", 24 | "illuminate/console": "^10" 25 | }, 26 | "extra": { 27 | "laravel": { 28 | "providers": [ 29 | "HichemtabTech\\TokensValidation\\Laravel\\Providers\\TokensValidationProvider" 30 | ], 31 | "config": { 32 | "tokensvalidation": "HichemtabTech\\TokensValidation\\Laravel\\config\\tokensvalidation.php" 33 | }, 34 | "controllers": [ 35 | { 36 | "name": "InvitationAnswererController", 37 | "path": "src/Laravel/Http/Controllers/InvitationAnswererController.php" 38 | } 39 | ] 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | "; 21 | print_r(TokensValidation::$config); 22 | die();*/ 23 | /*try { 24 | $inv = TokensValidation::createInvitation( 25 | userId: "1002", 26 | target_email: "user@example.com", 27 | whatFor: "administration", 28 | ); 29 | 30 | 31 | } catch (Exception $e) { 32 | print_r($e); 33 | die(); 34 | } 35 | print_r([ 36 | $inv->getUrl(), 37 | $inv 38 | ]);*/ 39 | /*try { 40 | $inv = TokensValidation::checkInvitationUrl( 41 | url: "http://localhost/invitations?c=e6dXr7kg5aPO4Olz5RlpXHiww", 42 | whatFor: "administration", 43 | ); 44 | 45 | 46 | } catch (Exception $e) { 47 | print_r($e); 48 | die(); 49 | } 50 | print_r([ 51 | $inv 52 | ]);*/ 53 | 54 | //verify the data entered by the user. 55 | 56 | 57 | /*$invitation = TokensValidation::checkInvitationToken( 58 | token: $_GET['token'], 59 | whatFor: "administration", 60 | thenAccept: true 61 | ); 62 | 63 | if (!$invitation->isValidationSucceed()) { 64 | die("INVITATION_INVALID"); 65 | }*/ 66 | 67 | //insert the data entered by the user. 68 | //performe some actions 69 | echo "invitation accepted"; 70 | 71 | die(); 72 | 73 | -------------------------------------------------------------------------------- /meta-data/picture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HichemTab-tech/tokens-validation/3c51fefb55b0581400f16593b3b5f1466fb6b632/meta-data/picture.png -------------------------------------------------------------------------------- /src/Actions/Authentication/AuthTokenCookiesHandler.php: -------------------------------------------------------------------------------- 1 | getContent(), time()+TokensValidation::getAuthTokenExpirationDelay(), "/"); 20 | } 21 | 22 | /** 23 | * @return string|null 24 | */ 25 | public function get(): ?string 26 | { 27 | $token = htmlspecialchars($_COOKIE[sha1('auth_token')] ?? ""); 28 | return $token != "" ? $token : null; 29 | } 30 | 31 | /** 32 | * @return void 33 | */ 34 | public function delete(): void 35 | { 36 | setcookie(sha1('auth_token'), "", time()+TokensValidation::getAuthTokenExpirationDelay(), "/"); 37 | } 38 | } -------------------------------------------------------------------------------- /src/Actions/Authentication/AuthTokenGenerator.php: -------------------------------------------------------------------------------- 1 | TokensValidation::generateAlphaNumKey(6, true), 23 | ConfirmationsTokenTypes::IN_URL => TokensValidation::generateAlphaNumKey(15), 24 | default => "", 25 | }; 26 | } 27 | } -------------------------------------------------------------------------------- /src/Actions/Confirmation/ConfirmationUrlBuilder.php: -------------------------------------------------------------------------------- 1 | getUserId(); 35 | $r['c'] = $confirmationToken->getContent(); 36 | $q = http_build_query($r); 37 | return $p['scheme'] . '://' . $p['host'] . $p['path'] 38 | . (!empty($q) ? '?' . $q : ''); 39 | } 40 | 41 | /** 42 | * @param string $url 43 | * @return UserIdAndToken 44 | */ 45 | public function getUserIdAndTokenFromUrl(string $url): UserIdAndToken 46 | { 47 | $p = parse_url($url); 48 | parse_str($p['query']??"", $r); 49 | return UserIdAndToken::builder() 50 | ->setUserId($r["u"]??"") 51 | ->setToken($r["c"]??"") 52 | ->build(); 53 | } 54 | 55 | /** 56 | * @param array $_GET_ARRAY 57 | * @return UserIdAndToken 58 | */ 59 | public function getUserIdAndTokenFromGET(array $_GET_ARRAY): UserIdAndToken 60 | { 61 | return UserIdAndToken::builder() 62 | ->setUserId($_GET_ARRAY["u"]??"") 63 | ->setToken($_GET_ARRAY["c"]??"") 64 | ->build(); 65 | } 66 | } -------------------------------------------------------------------------------- /src/Actions/Confirmation/UserIdAndToken.php: -------------------------------------------------------------------------------- 1 | userId = $userId; 26 | $this->token = $token; 27 | } 28 | 29 | /** 30 | * Gets the user ID. 31 | * 32 | * @return string The user ID. 33 | */ 34 | public function getUserId(): string { 35 | return $this->userId; 36 | } 37 | 38 | /** 39 | * Gets the token. 40 | * 41 | * @return string The token. 42 | */ 43 | public function getToken(): string { 44 | return $this->token; 45 | } 46 | 47 | /** 48 | * @return UserIdAndTokenBuilder 49 | */ 50 | public static function builder(): UserIdAndTokenBuilder 51 | { 52 | return new UserIdAndTokenBuilder(); 53 | } 54 | } -------------------------------------------------------------------------------- /src/Actions/Confirmation/UserIdAndTokenBuilder.php: -------------------------------------------------------------------------------- 1 | userId = $userId; 29 | return $this; 30 | } 31 | 32 | /** 33 | * Sets the token. 34 | * 35 | * @param string $token The token. 36 | * @return $this The builder instance. 37 | */ 38 | public function setToken(string $token): self { 39 | $this->token = $token; 40 | return $this; 41 | } 42 | 43 | /** 44 | * Builds and returns a new instance of the UserIdAndToken class. 45 | * 46 | * @return UserIdAndToken The new UserIdAndToken instance. 47 | * @throws RuntimeException If the user ID or token is not set. 48 | */ 49 | public function build(): UserIdAndToken { 50 | if (!$this->userId) { 51 | throw new RuntimeException('User ID must be set.'); 52 | } 53 | 54 | if (!$this->token) { 55 | throw new RuntimeException('Token must be set.'); 56 | } 57 | 58 | return new UserIdAndToken($this->userId, $this->token); 59 | } 60 | } -------------------------------------------------------------------------------- /src/Actions/Invitation/InvitationTokenGenerator.php: -------------------------------------------------------------------------------- 1 | getContent(); 20 | $q = http_build_query($r); 21 | return $p['scheme'] . '://' . $p['host'] . $p['path'] 22 | . (!empty($q) ? '?' . $q : ''); 23 | } 24 | 25 | /** 26 | * @param string $url 27 | * @return string 28 | */ 29 | public function getTokenFromUrl(string $url): string 30 | { 31 | $p = parse_url($url); 32 | parse_str($p['query']??"", $r); 33 | return $r['c']??""; 34 | } 35 | 36 | /** 37 | * @param array $_GET_ARRAY 38 | * @return string 39 | */ 40 | public function getTokenFromGET(array $_GET_ARRAY): string 41 | { 42 | return $_GET_ARRAY["c"]??""; 43 | } 44 | 45 | /** 46 | * @param Request $request 47 | * @return string 48 | */ 49 | public function getTokenFromRequest(Request $request): string 50 | { 51 | return $this->getTokenFromUrl($request->getRequestUri()); 52 | } 53 | } -------------------------------------------------------------------------------- /src/Actions/UserIdEncrypter.php: -------------------------------------------------------------------------------- 1 | saveToAsciiSafeString(), and store it here in $encrypterKey; 20 | * but this action is highly recommned before launching your project because once you change the key every other encryptedUserId will be invalide. 21 | * 22 | * @var string 23 | */ 24 | protected static string $encrypterKey = 'def00000d92d8449abd99476b92ce79555cd81fa28173ed18f0162419b8a7332a00281c2c1e6568d67e3901cfd55fafdd9112d206feebb1d7e8acaf4dc08f0d6ab576a46'; 25 | 26 | /** 27 | * @param $userId 28 | * @return string 29 | * @throws BadFormatException 30 | * @throws EnvironmentIsBrokenException 31 | */ 32 | public function encrypt($userId): string 33 | { 34 | return Crypto::encrypt($userId, Key::loadFromAsciiSafeString(self::$encrypterKey)); 35 | } 36 | 37 | /** 38 | * @param $encryptedUserId 39 | * @return string 40 | * @throws BadFormatException 41 | * @throws EnvironmentIsBrokenException 42 | * @throws WrongKeyOrModifiedCiphertextException 43 | */ 44 | public function decrypt($encryptedUserId): string { 45 | return Crypto::decrypt($encryptedUserId, Key::loadFromAsciiSafeString(self::$encrypterKey)); 46 | } 47 | } -------------------------------------------------------------------------------- /src/DefaultConfig.php: -------------------------------------------------------------------------------- 1 | [ 19 | 'mysql' => [ 20 | 'driver' => 'mysql', 21 | 'host' => TokensValidation::getDBHOST(), 22 | 'database' => TokensValidation::getDBNAME(), 23 | 'username' => TokensValidation::getDBUSER(), 24 | 'password' => TokensValidation::getDBPASS(), 25 | 'charset' => 'utf8mb4', 26 | 'collation' => 'utf8mb4_unicode_ci', 27 | 'prefix' => '', 28 | ], 29 | ], 30 | 31 | 'AuthTokens' => [ 32 | 'expirationDelay' => 60*60*24*7, 33 | 'AuthTokenGenerator' => AuthTokenGenerator::class, 34 | 'AuthTokenCookiesHandler' => AuthTokenCookiesHandler::class, 35 | ], 36 | 37 | 'ConfirmationToken' => [ 38 | 'expirationDelay' => 60*10, 39 | 'ConfirmationUrlBuilder' => ConfirmationUrlBuilder::class, 40 | 'ConfirmationCodeGenerator' => ConfirmationCodeGenerator::class, 41 | 'UserIdEncrypter' => UserIdEncrypter::class, 42 | 'singleTokenPerTime' => false, 43 | ], 44 | 45 | 'InvitationToken' => [ 46 | 'expirationDelay' => 60*60*24*3, 47 | 'InvitationUrlBuilder' => InvitationUrlBuilder::class, 48 | 'InvitationTokenGenerator' => InvitationTokenGenerator::class, 49 | 'InvitationBaseUrl' => "http://localhost/invitations", 50 | ], 51 | 52 | 'features' => [ 53 | 'AuthTokens', 54 | 'ConfirmationToken', 55 | //'InvitationsTokens' 56 | ] 57 | ]; 58 | } 59 | } -------------------------------------------------------------------------------- /src/Laravel/Console/Commands/TokensValidation/CreateCustomHandler.php: -------------------------------------------------------------------------------- 1 | [ 27 | "AuthTokenCookiesHandler", 28 | "AuthTokenGenerator" 29 | ], 30 | "Confirmation" => [ 31 | "ConfirmationCodeGenerator", 32 | "ConfirmationUrlBuilder", 33 | "UserIdEncrypter" 34 | ], 35 | "Invitation" => [ 36 | "InvitationTokenGenerator", 37 | "InvitationUrlBuilder" 38 | ] 39 | ]; 40 | 41 | /** 42 | * Execute the console command. 43 | * 44 | * @return int 45 | */ 46 | public function handle(): int 47 | { 48 | $keys = array_keys(self::lists); 49 | $keys = array_map(function($key) { 50 | return strlen($key); 51 | }, $keys); 52 | $key = max($keys); 53 | $n = $key + 1; 54 | 55 | $options = []; 56 | 57 | // Build the option array without titles 58 | foreach (self::lists as $group => $items) { 59 | foreach ($items as $item) { 60 | $s = $group; 61 | $s .= str_repeat(' ', $n - strlen($group)); 62 | $s .= ' - ' . $item; 63 | $options[] = $s; 64 | } 65 | } 66 | 67 | // Ask the user to choose an option 68 | $choice = $this->choice('Choose what class you want to override:', $options); 69 | $parts = explode('-', $choice); 70 | $head = trim($parts[0]); 71 | $choice = trim($parts[1]); 72 | 73 | $name = $this->ask('name of the class ?', $choice); 74 | if ($name == 'My...') $name = 'My'.$choice; 75 | if ($name == 'Custom...') $name = 'Custom'.$choice; 76 | $stub = File::get(__DIR__."/stubs/$head/$choice.stub"); 77 | $stub = str_replace("//TO*DO", "//TODO", $stub); 78 | $stub = str_replace("{{className}}", $name, $stub); 79 | 80 | 81 | /** @noinspection PhpUndefinedFunctionInspection */ 82 | $path = app_path('Actions/TokensValidation/' . $head . '/' . $name . '.php'); 83 | File::ensureDirectoryExists(dirname($path)); 84 | File::put($path, $stub); 85 | 86 | $this->line("Now, follow these instructions:"); 87 | $this->line("1. Go to the config file named tokensvalidation.php"); 88 | $this->line("2. change the value of the key '".($head == 'Authentication' ? 'AuthTokens' : $head.'Token').".$choice' to 'App\Actions\TokensValidation\\$head\\$name::class'"); 89 | $this->line("3. Clear config cache."); 90 | return CommandAlias::SUCCESS; 91 | } 92 | } -------------------------------------------------------------------------------- /src/Laravel/Console/Commands/TokensValidation/stubs/Authentication/AuthTokenCookiesHandler.stub: -------------------------------------------------------------------------------- 1 | getContent(), time()+TokensValidation::getAuthTokenExpirationDelay(), "/"); 21 | } 22 | 23 | /** 24 | * @return string|null 25 | */ 26 | public function get(): ?string 27 | { 28 | $token = htmlspecialchars($_COOKIE[sha1('auth_token')] ?? ""); 29 | return $token != "" ? $token : null; 30 | } 31 | 32 | /** 33 | * @return void 34 | */ 35 | public function delete(): void 36 | { 37 | setcookie(sha1('auth_token'), "", time()+TokensValidation::getAuthTokenExpirationDelay(), "/"); 38 | } 39 | } -------------------------------------------------------------------------------- /src/Laravel/Console/Commands/TokensValidation/stubs/Authentication/AuthTokenGenerator.stub: -------------------------------------------------------------------------------- 1 | TokensValidation::generateAlphaNumKey(6, true), 24 | ConfirmationsTokenTypes::IN_URL => TokensValidation::generateAlphaNumKey(15), 25 | default => "", 26 | }; 27 | } 28 | } -------------------------------------------------------------------------------- /src/Laravel/Console/Commands/TokensValidation/stubs/Confirmation/ConfirmationUrlBuilder.stub: -------------------------------------------------------------------------------- 1 | getUserId(); 38 | $r['c'] = $confirmationToken->getContent(); 39 | $q = http_build_query($r); 40 | return $p['scheme'] . '://' . $p['host'] . $p['path'] 41 | . (!empty($q) ? '?' . $q : ''); 42 | } 43 | 44 | /** 45 | * @param string $url 46 | * @return UserIdAndToken 47 | */ 48 | public function getUserIdAndTokenFromUrl(string $url): UserIdAndToken 49 | { 50 | $p = parse_url($url); 51 | parse_str($p['query']??"", $r); 52 | return UserIdAndToken::builder() 53 | ->setUserId($r["u"]??"") 54 | ->setToken($r["c"]??"") 55 | ->build(); 56 | } 57 | 58 | /** 59 | * @param array $_GET_ARRAY 60 | * @return UserIdAndToken 61 | */ 62 | public function getUserIdAndTokenFromGET(array $_GET_ARRAY): UserIdAndToken 63 | { 64 | return UserIdAndToken::builder() 65 | ->setUserId($_GET_ARRAY["u"]??"") 66 | ->setToken($_GET_ARRAY["c"]??"") 67 | ->build(); 68 | } 69 | } -------------------------------------------------------------------------------- /src/Laravel/Console/Commands/TokensValidation/stubs/Confirmation/UserIdEncrypter.stub: -------------------------------------------------------------------------------- 1 | getContent(); 21 | $q = http_build_query($r); 22 | return $p['scheme'] . '://' . $p['host'] . $p['path'] 23 | . (!empty($q) ? '?' . $q : ''); 24 | } 25 | 26 | /** 27 | * @param string $url 28 | * @return string 29 | */ 30 | public function getTokenFromUrl(string $url): string 31 | { 32 | $p = parse_url($url); 33 | parse_str($p['query']??"", $r); 34 | return $r['c']??""; 35 | } 36 | 37 | /** 38 | * @param array $_GET_ARRAY 39 | * @return string 40 | */ 41 | public function getTokenFromGET(array $_GET_ARRAY): string 42 | { 43 | return $_GET_ARRAY["c"]??""; 44 | } 45 | 46 | /** 47 | * @param Request $request 48 | * @return string 49 | */ 50 | public function getTokenFromRequest(Request $request): string 51 | { 52 | return $this->getTokenFromUrl($request->getRequestUri()); 53 | } 54 | } -------------------------------------------------------------------------------- /src/Laravel/Http/Controllers/InvitationAnswererController.php: -------------------------------------------------------------------------------- 1 | getRequestUri()); 19 | if ($invitation->isValidationSucceed()) { 20 | return view('invitation-answerer', compact('invitation')); 21 | } 22 | else{ 23 | redirect("errors/invitation-invalid"); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/Laravel/Providers/TokensValidationProvider.php: -------------------------------------------------------------------------------- 1 | publishConfig(); 17 | } 18 | 19 | /** 20 | * Bootstrap services. 21 | * 22 | * @return void 23 | * @noinspection PhpUndefinedFunctionInspection 24 | */ 25 | public function boot(): void 26 | { 27 | $this->mergeConfigFrom(__DIR__.'/../config/tokensvalidation.php', 'tokensvalidation'); 28 | } 29 | 30 | /** @noinspection PhpUndefinedFunctionInspection */ 31 | private function publishConfig(): void 32 | { 33 | $configPath = __DIR__.'/../config/tokensvalidation.php'; 34 | $publishPath = config_path('tokensvalidation.php'); 35 | $this->publishes([$configPath => $publishPath], 'tokensvalidation-config'); 36 | $providerPath = __DIR__.'/../stubs/TokensValidationProvider.php'; 37 | $publishPath2 = app_path('Providers/TokensValidationProvider.php'); 38 | $this->publishes([$providerPath => $publishPath2], 'tokensvalidation-provider'); 39 | $controllerPath = __DIR__.'/../Http/Controllers/InvitationAnswererController.php'; 40 | $publishPath3 = app_path('Http/Controllers/InvitationAnswererController.php'); 41 | $this->publishes([$controllerPath => $publishPath3], 'controllers'); 42 | $consolePath = __DIR__.'/../Console/Commands'; 43 | $publishPath4 = app_path('Console/Commands'); 44 | $this->publishes([$consolePath => $publishPath4], 'hichemtab-tech-tokensvalidation'); 45 | } 46 | } -------------------------------------------------------------------------------- /src/Laravel/config/tokensvalidation.php: -------------------------------------------------------------------------------- 1 | [ 13 | 'mysql' => [ 14 | 'driver' => 'mysql', 15 | 'host' => 'localhost', 16 | 'database' => 'db', 17 | 'username' => 'root', 18 | 'password' => '', 19 | 'charset' => 'utf8mb4', 20 | 'collation' => 'utf8mb4_unicode_ci', 21 | 'prefix' => '', 22 | ], 23 | ], 24 | 25 | 'AuthTokens' => [ 26 | 'expirationDelay' => 60*60*24*7, 27 | 'AuthTokenGenerator' => AuthTokenGenerator::class, 28 | 'AuthTokenCookiesHandler' => AuthTokenCookiesHandler::class, 29 | ], 30 | 31 | 'ConfirmationToken' => [ 32 | 'expirationDelay' => 60*10, 33 | 'ConfirmationUrlBuilder' => ConfirmationUrlBuilder::class, 34 | 'ConfirmationCodeGenerator' => ConfirmationCodeGenerator::class, 35 | 'UserIdEncrypter' => UserIdEncrypter::class, 36 | 'singleTokenPerTime' => false, 37 | ], 38 | 39 | 40 | 'InvitationToken' => [ 41 | 'expirationDelay' => 60*60*24*3, 42 | 'InvitationUrlBuilder' => InvitationUrlBuilder::class, 43 | 'InvitationTokenGenerator' => InvitationTokenGenerator::class, 44 | 'InvitationBaseUrl' => "http://localhost/invitations", 45 | ], 46 | 47 | 'features' => [ 48 | 'AuthTokens', 49 | 'ConfirmationToken', 50 | //'InvitationsTokens' 51 | ] 52 | ]; -------------------------------------------------------------------------------- /src/Laravel/stubs/TokensValidationProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton(TokensValidation::class, function () { 18 | return TokensValidation::class; 19 | }); 20 | } 21 | 22 | /** 23 | * Bootstrap services. 24 | * 25 | * @return void 26 | * @noinspection PhpUndefinedFunctionInspection 27 | */ 28 | public function boot(): void 29 | { 30 | TokensValidation::setConfig(config('tokensvalidation')); 31 | TokensValidation::prepare(); 32 | $this->app->singleton(TokensValidation::class, function () { 33 | return TokensValidation::class; 34 | }); 35 | } 36 | } -------------------------------------------------------------------------------- /src/Model/Authentication/AuthToken.php: -------------------------------------------------------------------------------- 1 | userId = $userId; 49 | $this->type = $type; 50 | $this->content = $content; 51 | $this->expiredAt = $expiredAt; 52 | $this->userAgent = $userAgent; 53 | $this->fingerprint = $fingerprint; 54 | } 55 | 56 | /** 57 | * @return AuthTokenBuilder 58 | */ 59 | public static function builder(): AuthTokenBuilder 60 | { 61 | return new AuthTokenBuilder(); 62 | } 63 | 64 | /** 65 | * @return string 66 | */ 67 | public function getUserId(): string 68 | { 69 | return $this->userId; 70 | } 71 | 72 | /** 73 | * @return int 74 | */ 75 | public function getType(): int 76 | { 77 | return $this->type; 78 | } 79 | 80 | /** 81 | * @return string 82 | */ 83 | public function getContent(): string 84 | { 85 | return $this->content; 86 | } 87 | 88 | /** 89 | * @return DateTime 90 | */ 91 | public function getExpiredAt(): DateTime 92 | { 93 | return $this->expiredAt; 94 | } 95 | 96 | /** 97 | * @return string 98 | */ 99 | public function getUserAgent(): string 100 | { 101 | return $this->userAgent; 102 | } 103 | 104 | /** 105 | * @return string 106 | */ 107 | public function getFingerprint(): string 108 | { 109 | return $this->fingerprint; 110 | } 111 | } -------------------------------------------------------------------------------- /src/Model/Authentication/AuthTokenBuilder.php: -------------------------------------------------------------------------------- 1 | userId = $userId; 49 | return $this; 50 | } 51 | 52 | /** 53 | * @param int $type 54 | * @return $this 55 | */ 56 | public function withType(int $type): self 57 | { 58 | $this->type = $type; 59 | return $this; 60 | } 61 | 62 | /** 63 | * @param string $content 64 | * @return $this 65 | */ 66 | public function withContent(string $content): self 67 | { 68 | $this->content = $content; 69 | return $this; 70 | } 71 | 72 | /** 73 | * @param DateTime $expiredAt 74 | * @return $this 75 | */ 76 | public function withExpiredAt(DateTime $expiredAt): self 77 | { 78 | $this->expiredAt = $expiredAt; 79 | return $this; 80 | } 81 | 82 | /** 83 | * @param string $userAgent 84 | * @return $this 85 | */ 86 | public function withUserAgent(string $userAgent): self 87 | { 88 | $this->userAgent = $userAgent; 89 | return $this; 90 | } 91 | 92 | /** 93 | * @param string $fingerprint 94 | * @return $this 95 | */ 96 | public function withFingerprint(string $fingerprint): self 97 | { 98 | $this->fingerprint = $fingerprint; 99 | return $this; 100 | } 101 | 102 | /** 103 | * @return AuthToken 104 | */ 105 | public function build(): AuthToken 106 | { 107 | return new AuthToken( 108 | $this->userId, 109 | $this->type, 110 | $this->content, 111 | $this->expiredAt, 112 | $this->userAgent, 113 | $this->fingerprint 114 | ); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/Model/Authentication/AuthTokenModel.php: -------------------------------------------------------------------------------- 1 | userId = $userId; 49 | $this->type = $type; 50 | $this->content = $content; 51 | $this->expiredAt = $expiredAt; 52 | $this->whatFor = $whatFor; 53 | } 54 | 55 | /** 56 | * Create a new instance of ConfirmationTokenBuilder 57 | * 58 | * @return ConfirmationTokenBuilder 59 | */ 60 | public static function builder(): ConfirmationTokenBuilder 61 | { 62 | return new ConfirmationTokenBuilder(); 63 | } 64 | 65 | /** 66 | * Get the user ID associated with this token 67 | * 68 | * @return string 69 | */ 70 | public function getUserId(): string 71 | { 72 | return $this->userId; 73 | } 74 | 75 | /** 76 | * Get the type of this token 77 | * 78 | * @return int 79 | */ 80 | public function getType(): int 81 | { 82 | return $this->type; 83 | } 84 | 85 | /** 86 | * Get the content of this token 87 | * 88 | * @return string 89 | */ 90 | public function getContent(): string 91 | { 92 | return $this->content; 93 | } 94 | 95 | /** 96 | * Get the expiration date of this token 97 | * 98 | * @return DateTime 99 | */ 100 | public function getExpiredAt(): DateTime 101 | { 102 | return $this->expiredAt; 103 | } 104 | 105 | /** 106 | * Get a purpose key of this token 107 | * @return string 108 | */ 109 | public function getWhatFor(): string 110 | { 111 | return $this->whatFor; 112 | } 113 | 114 | /** 115 | * @param string $baseUrl 116 | * @return string 117 | */ 118 | public function getUrl(string $baseUrl): string 119 | { 120 | return call_user_func_array([new TokensValidation::$ConfirmationUrlBuilder(), 'getUrl'], [$this, $baseUrl]); 121 | } 122 | } 123 | 124 | 125 | -------------------------------------------------------------------------------- /src/Model/Confirmation/ConfirmationTokenBuilder.php: -------------------------------------------------------------------------------- 1 | userId = $userId; 43 | return $this; 44 | } 45 | 46 | /** 47 | * Sets the type for the token. 48 | * 49 | * @param int $type 50 | * @return $this 51 | */ 52 | public function withType(int $type): self 53 | { 54 | $this->type = $type; 55 | return $this; 56 | } 57 | 58 | /** 59 | * Sets the content for the token. 60 | * 61 | * @param string $content 62 | * @return $this 63 | */ 64 | public function withContent(string $content): self 65 | { 66 | $this->content = $content; 67 | return $this; 68 | } 69 | 70 | /** 71 | * Sets the expiration date and time for the token. 72 | * 73 | * @param DateTime $expiredAt 74 | * @return $this 75 | */ 76 | public function withExpiredAt(DateTime $expiredAt): self 77 | { 78 | $this->expiredAt = $expiredAt; 79 | return $this; 80 | } 81 | 82 | /** 83 | * Sets the purpose key for the token. 84 | * 85 | * @param string $whatFor 86 | * @return $this 87 | */ 88 | public function withWhatFor(string $whatFor): self 89 | { 90 | $this->whatFor = $whatFor; 91 | return $this; 92 | } 93 | 94 | /** 95 | * Builds and returns a new ConfirmationToken object based on the current builder state. 96 | * 97 | * @return ConfirmationToken 98 | */ 99 | public function build(): ConfirmationToken 100 | { 101 | return new ConfirmationToken($this->userId, $this->type, $this->content, $this->expiredAt, $this->whatFor); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/Model/Confirmation/ConfirmationTokenModel.php: -------------------------------------------------------------------------------- 1 | id = $id; 67 | $this->userId = $userId; 68 | $this->target_email = $target_email; 69 | $this->content = $content; 70 | $this->expire_at = $expire_at; 71 | $this->whatFor = $whatFor; 72 | $this->data = $data; 73 | $this->accepted = $accepted; 74 | } 75 | 76 | /** 77 | * Builder method to create an Invitation object. 78 | * 79 | * @param string $userId The user id who created the invitation. 80 | * @return InvitationBuilder 81 | */ 82 | public static function builder(string $userId): InvitationBuilder 83 | { 84 | return new InvitationBuilder($userId); 85 | } 86 | 87 | /** 88 | * Get the unique identifier for the invitation. 89 | * 90 | * @return string The unique identifier for the invitation. 91 | */ 92 | public function getId(): string 93 | { 94 | return $this->id; 95 | } 96 | 97 | /** 98 | * Get the unique identifier for the user who created the invitation. 99 | * 100 | * @return string The unique identifier for the user who created the invitation. 101 | */ 102 | public function getUserId(): string 103 | { 104 | return $this->userId; 105 | } 106 | 107 | /** 108 | * Get the email address of the user who the invitation is being sent to. 109 | * 110 | * @return string The email address of the user who the invitation is being sent to. 111 | */ 112 | public function getTargetEmail(): string 113 | { 114 | return $this->target_email; 115 | } 116 | 117 | /** 118 | * Get the content of the invitation message. 119 | * 120 | * @return string The content of the invitation message. 121 | */ 122 | public function getContent(): string 123 | { 124 | return $this->content; 125 | } 126 | 127 | /** 128 | * Get the date and time when the invitation will expire. 129 | * 130 | * @return DateTime The date and time when the invitation will expire. 131 | */ 132 | public function getExpireAt(): DateTime 133 | { 134 | return $this->expire_at; 135 | } 136 | 137 | /** 138 | * Get the purpose of the invitation. 139 | * 140 | * @return string The purpose of the invitation. 141 | */ 142 | public function getWhatFor(): string 143 | { 144 | return $this->whatFor; 145 | } 146 | 147 | /** 148 | * Get additional data related to the invitation. 149 | * 150 | * @return string Additional data related to the invitation. 151 | */ 152 | public function getData(): string 153 | { 154 | return $this->data; 155 | } 156 | 157 | /** 158 | * Check whether the invitation has been accepted. 159 | * 160 | * @return bool Whether the invitation has been accepted. 161 | */ 162 | public function isAccepted(): bool 163 | { 164 | return $this->accepted; 165 | } 166 | 167 | /** 168 | * Get the generated invitation url 169 | * 170 | * @param string $baseUrl 171 | * @return string 172 | */ 173 | public function getUrl(string $baseUrl = ""): string 174 | { 175 | if ($baseUrl == '') { 176 | $baseUrl = TokensValidation::$InvitationBaseUrl; 177 | } 178 | return call_user_func_array([new TokensValidation::$InvitationUrlBuilder(), 'getUrl'], [$this, $baseUrl]); 179 | } 180 | } -------------------------------------------------------------------------------- /src/Model/Invitation/InvitationBuilder.php: -------------------------------------------------------------------------------- 1 | userId = $userId; 59 | } 60 | 61 | /** 62 | * Sets the ID of the invitation. 63 | * 64 | * @param string $id The invitation ID. 65 | * @return InvitationBuilder 66 | */ 67 | public function withId(string $id): InvitationBuilder 68 | { 69 | $this->id = $id; 70 | return $this; 71 | } 72 | 73 | /** 74 | * Sets the user ID associated with the invitation. 75 | * 76 | * @param string $userId The user ID. 77 | * @return InvitationBuilder 78 | */ 79 | public function withUserId(string $userId): InvitationBuilder 80 | { 81 | $this->userId = $userId; 82 | return $this; 83 | } 84 | 85 | /** 86 | * Sets the email address that the invitation is being sent to. 87 | * 88 | * @param string $targetEmail The email address. 89 | * @return InvitationBuilder 90 | */ 91 | public function withTargetEmail(string $targetEmail): InvitationBuilder 92 | { 93 | $this->targetEmail = $targetEmail; 94 | return $this; 95 | } 96 | 97 | /** 98 | * Sets the message content of the invitation. 99 | * 100 | * @param string $content The message content. 101 | * @return InvitationBuilder 102 | */ 103 | public function withContent(string $content): InvitationBuilder 104 | { 105 | $this->content = $content; 106 | return $this; 107 | } 108 | 109 | /** 110 | * Sets the expiration date and time of the invitation. 111 | * 112 | * @param DateTime $expireAt The expiration date and time. 113 | * @return InvitationBuilder 114 | */ 115 | public function withExpireAt(DateTime $expireAt): InvitationBuilder 116 | { 117 | $this->expireAt = $expireAt; 118 | return $this; 119 | } 120 | 121 | /** 122 | * Sets the purpose of the invitation. 123 | * 124 | * @param string $whatFor The purpose of the invitation. 125 | * @return InvitationBuilder 126 | */ 127 | public function withWhatFor(string $whatFor): InvitationBuilder 128 | { 129 | $this->whatFor = $whatFor; 130 | return $this; 131 | } 132 | 133 | /** 134 | * Sets additional data associated with the invitation. 135 | * 136 | * @param string $data Additional data. 137 | * @return InvitationBuilder 138 | */ 139 | public function withData(string $data): InvitationBuilder 140 | { 141 | $this->data = $data; 142 | return $this; 143 | } 144 | 145 | /** 146 | * Set whether the invitation has been accepted. 147 | * 148 | * @param bool $accepted Whether the invitation has been accepted. 149 | * @return $this 150 | */ 151 | public function withAccepted(bool $accepted): InvitationBuilder 152 | { 153 | $this->accepted = $accepted; 154 | return $this; 155 | } 156 | 157 | /** 158 | * Builds and returns a new Invitation object based on the current builder state. 159 | * 160 | * @return Invitation 161 | */ 162 | public function build(): Invitation 163 | { 164 | return new Invitation($this->id, $this->userId, $this->targetEmail, $this->content, $this->expireAt, $this->whatFor, $this->data, $this->accepted); 165 | } 166 | 167 | } -------------------------------------------------------------------------------- /src/Model/Invitation/InvitationModel.php: -------------------------------------------------------------------------------- 1 | 'datetime', 20 | ]; 21 | } 22 | -------------------------------------------------------------------------------- /src/Results/Authentication/AuthTokenResponse.php: -------------------------------------------------------------------------------- 1 | isValidationSucceed(), $builder->getCause(), $builder->getTokenId()); 35 | $this->userId = $builder->getUserId(); 36 | $this->newToken = $builder->getNewToken(); 37 | } 38 | 39 | /** 40 | * Returns the user ID associated with the token. 41 | * 42 | * @return string|null The user ID. 43 | */ 44 | public function getUserId(): ?string 45 | { 46 | return $this->userId; 47 | } 48 | 49 | /** 50 | * Returns the new authentication token. 51 | * 52 | * @return AuthToken|null The token. 53 | */ 54 | public function getNewToken(): ?AuthToken 55 | { 56 | return $this->newToken; 57 | } 58 | 59 | /** 60 | * @return AuthTokenResponseBuilder 61 | */ 62 | public static function builder(): AuthTokenResponseBuilder 63 | { 64 | return new AuthTokenResponseBuilder(); 65 | } 66 | } -------------------------------------------------------------------------------- /src/Results/Authentication/AuthTokenResponseBuilder.php: -------------------------------------------------------------------------------- 1 | userId = null; 31 | $this->newToken = null; 32 | } 33 | 34 | /** 35 | * Sets the user ID associated with the token. 36 | * 37 | * @param string|null $userId The user ID. 38 | * 39 | * @return AuthTokenResponseBuilder This builder instance. 40 | */ 41 | public function setUserId(?string $userId): self 42 | { 43 | $this->userId = $userId; 44 | return $this; 45 | } 46 | 47 | /** 48 | * Sets the new authentication token. 49 | * 50 | * @param AuthToken|null $newToken The token. 51 | * 52 | * @return AuthTokenResponseBuilder This builder instance. 53 | */ 54 | public function setNewToken(?AuthToken $newToken): self 55 | { 56 | $this->newToken = $newToken; 57 | return $this; 58 | } 59 | 60 | /** 61 | * Builds and returns an instance of the AuthTokenResponse class with the configured settings. 62 | * 63 | * @return AuthTokenResponse The resulting AuthTokenResponse instance. 64 | */ 65 | public function build(): AuthTokenResponse 66 | { 67 | return new AuthTokenResponse($this); 68 | } 69 | 70 | /** 71 | * Returns the user ID associated with the token. 72 | * 73 | * @return string|null The user ID. 74 | */ 75 | public function getUserId(): ?string 76 | { 77 | return $this->userId; 78 | } 79 | 80 | /** 81 | * Returns the new authentication token. 82 | * 83 | * @return AuthToken|null The token. 84 | */ 85 | public function getNewToken(): ?AuthToken 86 | { 87 | return $this->newToken; 88 | } 89 | } -------------------------------------------------------------------------------- /src/Results/BaseResults.php: -------------------------------------------------------------------------------- 1 | validationSucceed = $validationSucceed; 43 | $this->cause = $cause; 44 | $this->tokenId = $tokenId; 45 | $this->exception = $exception; 46 | } 47 | 48 | /** 49 | * Getter method for the validation result. 50 | * 51 | * @return bool Indicates if the validation process succeeded or not. 52 | */ 53 | public function isValidationSucceed(): bool 54 | { 55 | return $this->validationSucceed; 56 | } 57 | 58 | /** 59 | * Getter method for the cause of a validation failure. 60 | * 61 | * @return string|null Contains a message explaining why the validation process failed. 62 | */ 63 | public function getCause(): ?string 64 | { 65 | return $this->cause; 66 | } 67 | 68 | /** 69 | * Getter method for the ID of the token that was validated. 70 | * 71 | * @return string|null Contains the ID of the token that was validated. 72 | */ 73 | public function getTokenId(): ?string 74 | { 75 | return $this->tokenId; 76 | } 77 | 78 | /** 79 | * Getter method for the exception caused a validation failure. 80 | * 81 | * @return Exception|null Contains an exception explaining why the validation process failed. 82 | */ 83 | public function getException(): ?Exception 84 | { 85 | return $this->exception; 86 | } 87 | 88 | /** 89 | * Creates a new BaseResultsBuilder object to build a new BaseResults instance. 90 | * 91 | * @return BaseResultsBuilder The new BaseResultsBuilder instance. 92 | */ 93 | public static function builder(): BaseResultsBuilder 94 | { 95 | return new BaseResultsBuilder(); 96 | } 97 | } -------------------------------------------------------------------------------- /src/Results/BaseResultsBuilder.php: -------------------------------------------------------------------------------- 1 | validationSucceed = false; 25 | $this->cause = null; 26 | $this->tokenId = null; 27 | $this->exception = null; 28 | } 29 | 30 | /** 31 | * Sets the validation success value. 32 | * 33 | * @param bool $validationSucceed Indicates if the validation process succeeded or not. 34 | * @return $this This instance of the BaseResultsBuilder object. 35 | */ 36 | public function setValidationSucceed(bool $validationSucceed): self 37 | { 38 | $this->validationSucceed = $validationSucceed; 39 | return $this; 40 | } 41 | 42 | /** 43 | * Sets the cause of a validation failure. 44 | * 45 | * @param string|null $cause Contains a message explaining why the validation process failed. 46 | * @return $this This instance of the BaseResultsBuilder object. 47 | */ 48 | public function setCause(?string $cause): self 49 | { 50 | $this->cause = $cause; 51 | return $this; 52 | } 53 | 54 | /** 55 | * Sets the ID of the token that was validated. 56 | * 57 | * @param string|null $tokenId Contains the ID of the token that was validated. 58 | * @return $this This instance of the BaseResultsBuilder object. 59 | */ 60 | public function setTokenId(?string $tokenId): self 61 | { 62 | $this->tokenId = $tokenId; 63 | return $this; 64 | } 65 | 66 | /** 67 | * Sets the exception caused a validation failure. 68 | * 69 | * @param Exception|null $exception Contains an exception explaining why the validation process failed. 70 | * @return $this This instance of the BaseResultsBuilder object. 71 | */ 72 | public function setException(?Exception $exception): self 73 | { 74 | $this->exception = $exception; 75 | return $this; 76 | } 77 | 78 | /** 79 | * Builds the BaseResults object with the provided values. 80 | * 81 | * @return BaseResults The resulting BaseResults object. 82 | */ 83 | public function build(): BaseResults 84 | { 85 | return new BaseResults($this->validationSucceed, $this->cause, $this->tokenId, $this->exception); 86 | } 87 | 88 | /** 89 | * @return bool 90 | */ 91 | public function isValidationSucceed(): bool 92 | { 93 | return $this->validationSucceed; 94 | } 95 | 96 | /** 97 | * @return string|null 98 | */ 99 | public function getCause(): ?string 100 | { 101 | return $this->cause; 102 | } 103 | 104 | /** 105 | * @return string|null 106 | */ 107 | public function getTokenId(): ?string 108 | { 109 | return $this->tokenId; 110 | } 111 | } -------------------------------------------------------------------------------- /src/Results/Confirmation/ConfirmationTokenResponse.php: -------------------------------------------------------------------------------- 1 | isValidationSucceed(), $builder->getCause(), $builder->getTokenId()); 24 | $this->userId = $builder->getUserId(); 25 | $this->whatFor = $builder->getWhatFor(); 26 | } 27 | 28 | /** 29 | * Returns the ID of the user associated with this response. 30 | * 31 | * @return string|null The user ID. 32 | */ 33 | public function getUserId(): ?string 34 | { 35 | return $this->userId; 36 | } 37 | 38 | /** 39 | * Sets the ID of the user associated with this response. 40 | * 41 | * @param string|null $userId The user ID. 42 | * @return ConfirmationTokenResponse The updated response object. 43 | */ 44 | public function setUserId(?string $userId): ConfirmationTokenResponse 45 | { 46 | $this->userId = $userId; 47 | return $this; 48 | } 49 | 50 | /** 51 | * Returns the purpose of the confirmation token. 52 | * 53 | * @return string|null The purpose. 54 | */ 55 | public function getWhatFor(): ?string 56 | { 57 | return $this->whatFor; 58 | } 59 | 60 | /** 61 | * Sets the purpose of the confirmation token. 62 | * 63 | * @param string|null $whatFor The purpose. 64 | * @return ConfirmationTokenResponse The updated response object. 65 | */ 66 | public function setWhatFor(?string $whatFor): ConfirmationTokenResponse 67 | { 68 | $this->whatFor = $whatFor; 69 | return $this; 70 | } 71 | 72 | /** 73 | * Creates a new ConfirmationTokenResponse object using the builder pattern. 74 | * 75 | * @return ConfirmationTokenResponseBuilder The builder object. 76 | */ 77 | public static function builder(): ConfirmationTokenResponseBuilder 78 | { 79 | return new ConfirmationTokenResponseBuilder(); 80 | } 81 | } -------------------------------------------------------------------------------- /src/Results/Confirmation/ConfirmationTokenResponseBuilder.php: -------------------------------------------------------------------------------- 1 | userId = null; 26 | $this->whatFor = null; 27 | } 28 | 29 | /** 30 | * @param string $userId 31 | * @return $this 32 | */ 33 | public function withUserId(string $userId): self 34 | { 35 | $this->userId = $userId; 36 | return $this; 37 | } 38 | 39 | /** 40 | * @param string|null $whatFor 41 | * @return $this 42 | */ 43 | public function withWhatFor(?string $whatFor): self 44 | { 45 | $this->whatFor = $whatFor; 46 | return $this; 47 | } 48 | 49 | /** 50 | * @return ConfirmationTokenResponse 51 | */ 52 | public function build(): ConfirmationTokenResponse 53 | { 54 | return new ConfirmationTokenResponse($this); 55 | } 56 | 57 | /** 58 | * @return string|null 59 | */ 60 | public function getUserId(): ?string 61 | { 62 | return $this->userId; 63 | } 64 | 65 | /** 66 | * @return string|null 67 | */ 68 | public function getWhatFor(): ?string 69 | { 70 | return $this->whatFor; 71 | } 72 | } -------------------------------------------------------------------------------- /src/Results/Invitation/InvitationResponse.php: -------------------------------------------------------------------------------- 1 | isValidationSucceed(), $builder->getCause()); 32 | $this->whatFor = $builder->getWhatFor(); 33 | $this->userId = $builder->getUserId(); 34 | $this->target_email = $builder->getTargetEmail(); 35 | $this->data = $builder->getData(); 36 | } 37 | 38 | /** 39 | * Returns the purpose of the invitation. 40 | * 41 | * @return string|null The purpose. 42 | */ 43 | public function getWhatFor(): ?string 44 | { 45 | return $this->whatFor; 46 | } 47 | 48 | /** 49 | * Returns the ID of the user associated with this response. 50 | * 51 | * @return string|null The user ID. 52 | */ 53 | public function getUserId(): ?string 54 | { 55 | return $this->userId; 56 | } 57 | 58 | /** 59 | * Returns the target email of the invitation. 60 | * 61 | * @return string|null The target email. 62 | */ 63 | public function getTargetEmail(): ?string 64 | { 65 | return $this->target_email; 66 | } 67 | 68 | /** 69 | * Returns the data associated with the invitation. 70 | * 71 | * @return string|null The data. 72 | */ 73 | public function getData(): ?string 74 | { 75 | return $this->data; 76 | } 77 | 78 | /** 79 | * Creates a new InvitationResponse object using the builder pattern. 80 | * 81 | * @return InvitationResponseBuilder The builder object. 82 | */ 83 | public static function builder(): InvitationResponseBuilder 84 | { 85 | return new InvitationResponseBuilder(); 86 | } 87 | } -------------------------------------------------------------------------------- /src/Results/Invitation/InvitationResponseBuilder.php: -------------------------------------------------------------------------------- 1 | whatFor = null; 36 | $this->userId = null; 37 | $this->target_email = null; 38 | $this->data = null; 39 | } 40 | 41 | /** 42 | * @param string|null $whatFor 43 | * @return $this 44 | */ 45 | public function withWhatFor(?string $whatFor): self 46 | { 47 | $this->whatFor = $whatFor; 48 | return $this; 49 | } 50 | 51 | /** 52 | * @param string|null $userId 53 | * @return $this 54 | */ 55 | public function withUserId(?string $userId): self 56 | { 57 | $this->userId = $userId; 58 | return $this; 59 | } 60 | 61 | /** 62 | * @param string|null $target_email 63 | * @return $this 64 | */ 65 | public function withTargetEmail(?string $target_email): self 66 | { 67 | $this->target_email = $target_email; 68 | return $this; 69 | } 70 | 71 | /** 72 | * @param string|null $data 73 | * @return $this 74 | */ 75 | public function withData(?string $data): self 76 | { 77 | $this->data = $data; 78 | return $this; 79 | } 80 | 81 | /** 82 | * @return InvitationResponse 83 | */ 84 | public function build(): InvitationResponse 85 | { 86 | return new InvitationResponse($this); 87 | } 88 | 89 | /** 90 | * @return string|null 91 | */ 92 | public function getWhatFor(): ?string 93 | { 94 | return $this->whatFor; 95 | } 96 | 97 | /** 98 | * @return string|null 99 | */ 100 | public function getUserId(): ?string 101 | { 102 | return $this->userId; 103 | } 104 | 105 | /** 106 | * @return string|null 107 | */ 108 | public function getTargetEmail(): ?string 109 | { 110 | return $this->target_email; 111 | } 112 | 113 | /** 114 | * @return string|null 115 | */ 116 | public function getData(): ?string 117 | { 118 | return $this->data; 119 | } 120 | } -------------------------------------------------------------------------------- /src/TokensTypes.php: -------------------------------------------------------------------------------- 1 | addConnection(self::$config['connections']['mysql']); 280 | 281 | // Make this Capsule instance available globally via static methods... (optional) 282 | $capsule->setAsGlobal(); 283 | 284 | // Setup the Eloquent ORM... (optional; unless you've used setEventDispatcher()) 285 | $capsule->bootEloquent(); 286 | 287 | 288 | if (in_array('AuthTokens', self::$features)) { 289 | if (!Capsule::schema()->hasTable('auth_tokens')) { 290 | Capsule::schema()->create('auth_tokens', function (Blueprint $table) { 291 | $table->id(); 292 | $table->string('userId'); 293 | $table->string('content'); 294 | $table->smallInteger('type'); 295 | $table->string('userAgent'); 296 | $table->string('fingerprint'); 297 | $table->timestamp('expire_at')->useCurrent(); 298 | $table->timestamps(); 299 | }); 300 | } 301 | } 302 | 303 | 304 | if (in_array('ConfirmationToken', self::$features)) { 305 | if (!Capsule::schema()->hasTable('confirmation_tokens')) { 306 | Capsule::schema()->create('confirmation_tokens', function (Blueprint $table) { 307 | $table->id(); 308 | $table->string('userId'); 309 | $table->string('content'); 310 | $table->string('whatFor'); 311 | $table->smallInteger('type'); 312 | $table->timestamp('expire_at')->useCurrent(); 313 | $table->timestamps(); 314 | }); 315 | } 316 | } 317 | 318 | if (in_array('InvitationsTokens', self::$features)) { 319 | if (!Capsule::schema()->hasTable('invitation_tokens')) { 320 | Capsule::schema()->create('invitation_tokens', function (Blueprint $table) { 321 | $table->id(); 322 | $table->string('userId'); 323 | $table->string('target_email'); 324 | $table->string('content')->unique(); 325 | $table->timestamp('expire_at'); 326 | $table->string('whatFor'); 327 | $table->string('data'); 328 | $table->smallInteger('accepted')->default(0); 329 | $table->timestamps(); 330 | $table->unique(['whatFor', 'target_email']); 331 | }); 332 | } 333 | } 334 | } 335 | 336 | /** 337 | * @return void 338 | */ 339 | private static function prepareConfig(): void 340 | { 341 | if (isset(self::$config) AND self::$config != null) { 342 | self::$config = array_replace_recursive(DefaultConfig::get(), self::$config); 343 | } 344 | else{ 345 | self::$config = DefaultConfig::get(); 346 | } 347 | 348 | self::$features = self::$config['features']; 349 | 350 | self::$AuthTokenExpirationDelay = self::$config['AuthTokens']['expirationDelay']; 351 | self::$AuthTokenGenerator = self::$config['AuthTokens']['AuthTokenGenerator']; 352 | self::$AuthTokenCookiesHandler = self::$config['AuthTokens']['AuthTokenCookiesHandler']; 353 | 354 | self::$ConfirmationTokenExpirationDelay = self::$config['ConfirmationToken']['expirationDelay']; 355 | self::$ConfirmationUrlBuilder = self::$config['ConfirmationToken']['ConfirmationUrlBuilder']; 356 | self::$ConfirmationCodeGenerator = self::$config['ConfirmationToken']['ConfirmationCodeGenerator']; 357 | self::$UserIdEncrypter = self::$config['ConfirmationToken']['UserIdEncrypter']; 358 | 359 | self::$InvitationTokenExpirationDelay = self::$config['InvitationToken']['expirationDelay']; 360 | self::$InvitationUrlBuilder = self::$config['InvitationToken']['InvitationUrlBuilder']; 361 | self::$InvitationTokenGenerator = self::$config['InvitationToken']['InvitationTokenGenerator']; 362 | self::$InvitationBaseUrl = self::$config['InvitationToken']['InvitationBaseUrl']; 363 | } 364 | 365 | /** 366 | * @param int $type 367 | * @param string $tokenId 368 | * @return void 369 | */ 370 | private static function deleteToken(int $type, string $tokenId): void 371 | { 372 | if ($type == TokensTypes::AUTHENTICATION_BY_TOKEN || $type == TokensTypes::AUTHENTICATION_BY_COOKIE) { 373 | AuthTokenModel::where('id', $tokenId)->delete(); 374 | } 375 | elseif ($type == TokensTypes::CONFIRMATION_CODE) { 376 | ConfirmationTokenModel::where('id', $tokenId)->delete(); 377 | } 378 | } 379 | 380 | /** 381 | * @param string $userId 382 | * @param integer $type 383 | * @param string|null $oldToken 384 | * @param string $fingerPrint 385 | * @param int|null $confirmationType 386 | * @param string $whatFor 387 | * @param float|int $expirationDelay 388 | * @param bool|null $singleTokenPerTime 389 | * @return AuthToken|ConfirmationToken 390 | * @throws Exception 391 | */ 392 | private static function createNewToken(string $userId, int $type, string $oldToken = null, string $fingerPrint = "", int $confirmationType = null, string $whatFor = "default", float|int $expirationDelay = -1, bool|null $singleTokenPerTime = null): ConfirmationToken|AuthToken 393 | { 394 | $datetime = new DateTime(); 395 | if ($type == TokensTypes::AUTHENTICATION_BY_TOKEN || $type == TokensTypes::AUTHENTICATION_BY_COOKIE) { 396 | if ($expirationDelay == -1) { 397 | $delay = self::getAuthTokenExpirationDelay(); 398 | } 399 | elseif ($expirationDelay > 0) { 400 | $delay = $expirationDelay; 401 | } 402 | else{ 403 | throw new Exception("Expiration delay can't be < 0"); 404 | } 405 | $datetime->modify('+'.$delay.' seconds'); 406 | $authToken = AuthToken::builder() 407 | ->withUserId($userId) 408 | ->withType($type) 409 | ->withContent(call_user_func_array([new self::$AuthTokenGenerator(), 'generate'], [$userId])) 410 | ->withExpiredAt($datetime) 411 | ->withUserAgent(($_SERVER['HTTP_USER_AGENT'] ?? '')) 412 | ->withFingerprint($fingerPrint) 413 | ->build(); 414 | if ($oldToken != null) { 415 | AuthTokenModel::where("content", $oldToken)->where('userId', $userId)->update([ 416 | 'userId' => $authToken->getUserId(), 417 | 'content' => $authToken->getContent(), 418 | 'type' => $authToken->getType(), 419 | 'expire_at' => $authToken->getExpiredAt(), 420 | ]); 421 | } 422 | else{ 423 | AuthTokenModel::create([ 424 | 'userId' => $authToken->getUserId(), 425 | 'content' => $authToken->getContent(), 426 | 'type' => $authToken->getType(), 427 | 'expire_at' => $authToken->getExpiredAt(), 428 | 'userAgent' => $authToken->getUserAgent(), 429 | 'fingerprint' => $authToken->getFingerprint() 430 | ]); 431 | } 432 | if ($type == TokensTypes::AUTHENTICATION_BY_COOKIE) { 433 | call_user_func_array([new self::$AuthTokenCookiesHandler(), 'save'], [$authToken]); 434 | } 435 | return $authToken; 436 | } 437 | else{ 438 | if ($confirmationType == null) throw new Exception("confirmationType is null"); 439 | if ($expirationDelay == -1) { 440 | $delay = self::getConfirmationTokenExpirationDelay(); 441 | } 442 | elseif ($expirationDelay > 0) { 443 | $delay = $expirationDelay; 444 | } 445 | else{ 446 | throw new Exception("Expiration delay can't be < 0"); 447 | } 448 | if ($singleTokenPerTime == null) $singleTokenPerTime = self::$config['ConfirmationToken']['singleTokenPerTime']; 449 | $datetime->modify('+'.$delay.' seconds'); 450 | $encryptedUserId = call_user_func_array([new self::$UserIdEncrypter(), 'encrypt'], [$userId]); 451 | $token = ConfirmationToken::builder() 452 | ->withUserId($encryptedUserId) 453 | ->withType($confirmationType) 454 | ->withContent(call_user_func_array([new self::$ConfirmationCodeGenerator(), 'generate'], [$confirmationType])) 455 | ->withExpiredAt($datetime) 456 | ->withWhatFor($whatFor) 457 | ->build(); 458 | if ($singleTokenPerTime) { 459 | $oldToken = ConfirmationTokenModel::where('userId', $userId) 460 | ->where('whatFor', $whatFor) 461 | ->where('type', $confirmationType) 462 | ->where('expire_at', '>', date('Y-m-d H:i:s')) 463 | ->get(); 464 | if ($oldToken != null AND count($oldToken) != 0) { 465 | $oldContent = $oldToken->last()->content; 466 | $oldToken->each(function ($token) { 467 | $token->delete(); 468 | }); 469 | } 470 | } 471 | ConfirmationTokenModel::create([ 472 | 'userId' => $userId, 473 | 'content' => $oldContent??$token->getContent(), 474 | 'type' => $token->getType(), 475 | 'expire_at' => $token->getExpiredAt(), 476 | 'whatFor' => $whatFor 477 | ]); 478 | 479 | 480 | return $token; 481 | } 482 | } 483 | 484 | /** 485 | * @param string $userId 486 | * @param string $fingerPrint 487 | * @param bool $usingCookies 488 | * @param float|int $expirationDelay 489 | * @return AuthToken 490 | * @throws Exception 491 | */ 492 | public static function createNewAuthToken(string $userId, string $fingerPrint = "", bool $usingCookies = false, float|int $expirationDelay = -1): AuthToken 493 | { 494 | return self::createNewToken(userId: $userId, type: $usingCookies ? TokensTypes::AUTHENTICATION_BY_COOKIE : TokensTypes::AUTHENTICATION_BY_TOKEN, fingerPrint: $fingerPrint, expirationDelay: $expirationDelay); 495 | } 496 | 497 | /** 498 | * @param string $userId 499 | * @param int $confirmationType 500 | * @param string $whatFor 501 | * @param float|int $expirationDelay 502 | * @param bool|null $singleTokenPerTime 503 | * @return ConfirmationToken 504 | * @throws Exception 505 | */ 506 | public static function createNewConfirmationToken(string $userId, int $confirmationType = ConfirmationsTokenTypes::SMALL_CODE, string $whatFor = "default", float|int $expirationDelay = -1, bool|null $singleTokenPerTime = null): ConfirmationToken 507 | { 508 | return self::createNewToken( 509 | userId: $userId, 510 | type: TokensTypes::CONFIRMATION_CODE, 511 | confirmationType: $confirmationType, 512 | whatFor: $whatFor, 513 | expirationDelay: $expirationDelay, 514 | singleTokenPerTime: $singleTokenPerTime 515 | ); 516 | } 517 | 518 | /** 519 | * @param integer $limit 520 | * @param boolean $justNumbers 521 | * @return string 522 | */ 523 | public static function generateAlphaNumKey(int $limit = 12, bool $justNumbers = false): string 524 | { 525 | $characters = '0123456789'; 526 | if (!$justNumbers) { 527 | $characters .= 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; 528 | } 529 | $characters = str_split($characters); 530 | for ($i = 0; $i < 3; $i++) { 531 | shuffle($characters); 532 | $characters = array_merge($characters, $characters); 533 | } 534 | $randstring = ''; 535 | for ($i = 0; $i < $limit; $i++) { 536 | $randstring .= $characters[rand(0, count($characters) - 1)]; 537 | } 538 | return $randstring; 539 | } 540 | 541 | /** 542 | * @param string $fingerPrint 543 | * @param string|null $authToken 544 | * @param bool $regenerate 545 | * @return AuthTokenResponse 546 | * @throws Exception 547 | */ 548 | private static function checkAuthToken_(string $fingerPrint, ?string $authToken, bool $regenerate): AuthTokenResponse 549 | { 550 | $authTokenResultBuilder = AuthTokenResponse::builder() 551 | ->setValidationSucceed(false); 552 | if ($authToken == null) { 553 | $token = call_user_func_array([new self::$AuthTokenCookiesHandler(), 'get'], []); 554 | } 555 | if (isset($token) AND $token != null) { 556 | $authTokenModel = AuthTokenModel::where('content', '=', $token)->get(); 557 | if ($authTokenModel != null && count($authTokenModel) != 0) { 558 | $authTokenModel = $authTokenModel[0]; 559 | $authTokenResultBuilder->setUserId($authTokenModel->userId); 560 | if (!self::isExpired(DateTime::createFromFormat('Y-m-d H:i:s', $authTokenModel->expire_at))) { 561 | if ($authTokenModel->fingerprint == '' || $authTokenModel->fingerprint == $fingerPrint) { 562 | $authTokenResultBuilder->setValidationSucceed(true); 563 | $authTokenResultBuilder->setTokenId(strval($authTokenModel->id??"")); 564 | if ($regenerate) { 565 | $newToken = self::regenerateAuthToken($authTokenModel->userId, $token, $authTokenModel->type); 566 | $authTokenResultBuilder->setNewToken($newToken); 567 | } 568 | return $authTokenResultBuilder->build(); 569 | } 570 | else{ 571 | $authTokenResultBuilder->setCause("FINGERPRINT_INVALID"); 572 | } 573 | } 574 | else{ 575 | $authTokenResultBuilder->setCause("TOKEN_EXPIRED"); 576 | } 577 | AuthTokenModel::where('content', '=', $token)->delete(); 578 | } 579 | else{ 580 | $authTokenResultBuilder->setCause("TOKEN_INVALID"); 581 | } 582 | call_user_func_array([new self::$AuthTokenCookiesHandler(), 'delete'], [$authToken]); 583 | } 584 | else{ 585 | $authTokenResultBuilder->setCause("NO_TOKEN_PROVIDED"); 586 | } 587 | return $authTokenResultBuilder->build(); 588 | } 589 | 590 | /** 591 | * @param string $fingerPrint 592 | * @param string|null $authToken 593 | * @return AuthTokenResponse 594 | * @throws Exception 595 | */ 596 | public static function checkAuthTokenWithoutRegenerate(string $fingerPrint = "", ?string $authToken = null): AuthTokenResponse 597 | { 598 | return self::checkAuthToken_($fingerPrint, $authToken, false); 599 | } 600 | 601 | /** 602 | * @param string $fingerPrint 603 | * @param string|null $authToken 604 | * @return AuthTokenResponse 605 | * @throws Exception 606 | */ 607 | public static function checkAuthToken(string $fingerPrint = "", ?string $authToken = null): AuthTokenResponse 608 | { 609 | return self::checkAuthToken_($fingerPrint, $authToken, true); 610 | } 611 | 612 | /** 613 | * @param string $fingerPrint 614 | * @param string|null $authToken 615 | * @return void 616 | * @throws Exception 617 | */ 618 | public static function checkAuthTokenOrDie(string $fingerPrint = "", ?string $authToken = null): void 619 | { 620 | $AuthTokenResponse = self::checkAuthToken_($fingerPrint, $authToken, true); 621 | if (!$AuthTokenResponse->isValidationSucceed()) { 622 | die("AUTHENTICATION_BY_TOKEN_FAILED"); 623 | } 624 | } 625 | 626 | /** 627 | * @param string $tokenId 628 | * @return void 629 | */ 630 | public static function deleteAuthToken(string $tokenId): void 631 | { 632 | self::deleteToken(TokensTypes::AUTHENTICATION_BY_TOKEN, $tokenId); 633 | } 634 | 635 | /** 636 | * @param string $code 637 | * @param string|null $encryptedUserId 638 | * @param string $whatFor 639 | * @param bool $deleteAfterCheck 640 | * @return ConfirmationTokenResponse 641 | */ 642 | public static function checkConfirmationCode(string $code, string $encryptedUserId = null, string $whatFor = "default", bool $deleteAfterCheck = true): ConfirmationTokenResponse 643 | { 644 | $confirmationTokenResultsBuilder = ConfirmationTokenResponse::builder() 645 | ->setValidationSucceed(false); 646 | if ($code != null) { 647 | $modelBuilder = ConfirmationTokenModel::where('content', '=', $code); 648 | if ($encryptedUserId != null) { 649 | try { 650 | $userId = call_user_func_array([new self::$UserIdEncrypter(), 'decrypt'], [$encryptedUserId]); 651 | } catch (Exception $e) { 652 | $confirmationTokenResultsBuilder->setException($e); 653 | $confirmationTokenResultsBuilder->setCause("TOKEN_UID_INVALID"); 654 | return $confirmationTokenResultsBuilder->build(); 655 | } 656 | $modelBuilder->where('userId', '=', $userId); 657 | } 658 | $confirmationTokenModel = $modelBuilder->get(); 659 | if ($confirmationTokenModel != null AND count($confirmationTokenModel) != 0) { 660 | $confirmationTokenModel = $confirmationTokenModel[0]; 661 | $confirmationTokenResultsBuilder->withUserId($confirmationTokenModel->userId); 662 | if (!self::isExpired(DateTime::createFromFormat('Y-m-d H:i:s', $confirmationTokenModel->expire_at))) { 663 | if ($confirmationTokenModel->whatFor == $whatFor || $whatFor == "default") { 664 | $confirmationTokenResultsBuilder->setValidationSucceed(true); 665 | $confirmationTokenResultsBuilder->withWhatFor($whatFor); 666 | $confirmationTokenResultsBuilder->setTokenId(strval($confirmationTokenModel->id??"")); 667 | if ($deleteAfterCheck) { 668 | ConfirmationTokenModel::find($confirmationTokenModel->id)->delete(); 669 | } 670 | return $confirmationTokenResultsBuilder->build(); 671 | } 672 | else{ 673 | $confirmationTokenResultsBuilder->setCause("REASON_INVALID"); 674 | } 675 | } 676 | else{ 677 | $confirmationTokenResultsBuilder->setCause("TOKEN_EXPIRED"); 678 | } 679 | ConfirmationTokenModel::where('content', '=', $code)->delete(); 680 | } 681 | else{ 682 | $confirmationTokenResultsBuilder->setCause("TOKEN_INVALID"); 683 | } 684 | } 685 | else{ 686 | $confirmationTokenResultsBuilder->setCause("NO_TOKEN_PROVIDED"); 687 | } 688 | return $confirmationTokenResultsBuilder->build(); 689 | } 690 | 691 | /** 692 | * @param string $url 693 | * @param string $whatFor 694 | * @param bool $deleteAfterCheck 695 | * @return ConfirmationTokenResponse 696 | */ 697 | public static function checkConfirmationUrl(string $url, string $whatFor = "default", bool $deleteAfterCheck = true): ConfirmationTokenResponse 698 | { 699 | /** @var UserIdAndToken $userIdAndToken */ 700 | $userIdAndToken = call_user_func_array([new TokensValidation::$ConfirmationUrlBuilder(), 'getUserIdAndTokenFromUrl'], [$url]); 701 | if ($userIdAndToken != null) { 702 | return self::checkConfirmationCode($userIdAndToken->getToken(), $userIdAndToken->getUserId(), $whatFor, $deleteAfterCheck); 703 | } 704 | return ConfirmationTokenResponse::builder() 705 | ->setException(new Exception("can't get userIdAndToken")) 706 | ->setValidationSucceed(false) 707 | ->setCause("EXCEPTION") 708 | ->build(); 709 | } 710 | 711 | /** 712 | * @param array $_GET_ARRAY 713 | * @param string $whatFor 714 | * @param bool $deleteAfterCheck 715 | * @return ConfirmationTokenResponse 716 | */ 717 | public static function checkConfirmationUrlParamsFromGET(array $_GET_ARRAY, string $whatFor = "default", bool $deleteAfterCheck = true): ConfirmationTokenResponse 718 | { 719 | /** @var UserIdAndToken $userIdAndToken */ 720 | $userIdAndToken = call_user_func_array([new TokensValidation::$ConfirmationUrlBuilder(), 'getUserIdAndTokenFromGET'], [$_GET_ARRAY]); 721 | if ($userIdAndToken != null) { 722 | return self::checkConfirmationCode($userIdAndToken->getToken(), $userIdAndToken->getUserId(), $whatFor, $deleteAfterCheck); 723 | } 724 | return ConfirmationTokenResponse::builder() 725 | ->setException(new Exception("can't get userIdAndToken")) 726 | ->setValidationSucceed(false) 727 | ->setCause("EXCEPTION") 728 | ->build(); 729 | } 730 | 731 | /** 732 | * @param string $tokenId 733 | * @return void 734 | */ 735 | public static function deleteConfirmationToken(string $tokenId): void 736 | { 737 | self::deleteToken(TokensTypes::CONFIRMATION_CODE, $tokenId); 738 | } 739 | 740 | /** 741 | * @param DateTime $expirationDate 742 | * @return bool 743 | */ 744 | private static function isExpired(DateTime $expirationDate): bool 745 | { 746 | if ($expirationDate < new DateTime()) { 747 | return true; 748 | } 749 | return false; 750 | } 751 | 752 | /** 753 | * @param $userId 754 | * @param $oldToken 755 | * @param $tokenType 756 | * @return AuthToken 757 | * @throws Exception 758 | */ 759 | private static function regenerateAuthToken($userId, $oldToken, $tokenType): AuthToken 760 | { 761 | return self::createNewToken(userId: $userId, type: $tokenType, oldToken: $oldToken); 762 | } 763 | 764 | /** 765 | * @param string $userId 766 | * @param string $target_email 767 | * @param float|int $expirationDelay 768 | * @param string $whatFor 769 | * @param string $data 770 | * @return Invitation 771 | * @throws Exception 772 | */ 773 | public static function createInvitation(string $userId, string $target_email, float|int $expirationDelay = -1, string $whatFor = "default", string $data = ""): Invitation 774 | { 775 | $datetime = new DateTime(); 776 | if ($expirationDelay == -1) { 777 | $delay = self::getInvitationTokenExpirationDelay(); 778 | } 779 | elseif ($expirationDelay > 0) { 780 | $delay = $expirationDelay; 781 | } 782 | else{ 783 | throw new Exception("Expiration delay can't be < 0"); 784 | } 785 | $datetime->modify('+'.$delay.' seconds'); 786 | $invitation = Invitation::builder($userId) 787 | ->withTargetEmail($target_email) 788 | ->withContent(call_user_func_array([new self::$InvitationTokenGenerator(), 'generate'], [])) 789 | ->withExpireAt($datetime) 790 | ->withWhatFor($whatFor) 791 | ->withData($data) 792 | ->build(); 793 | 794 | InvitationModel::upsert( 795 | [ 796 | 'userId' => $invitation->getUserId(), 797 | 'content' => $invitation->getContent(), 798 | 'target_email' => $invitation->getTargetEmail(), 799 | 'expire_at' => $invitation->getExpireAt(), 800 | 'whatFor' => $invitation->getWhatFor(), 801 | 'data' => $invitation->getData() 802 | ], 803 | ['whatFor', 'target_email'] 804 | ); 805 | 806 | return $invitation; 807 | } 808 | 809 | /** 810 | * @param string $token 811 | * @param string $whatFor 812 | * @param bool $thenAccept 813 | * @return InvitationResponse 814 | */ 815 | public static function checkInvitationToken(string $token, string $whatFor = "default", bool $thenAccept = false): InvitationResponse 816 | { 817 | $invitationResultsBuilder = InvitationResponse::builder() 818 | ->setValidationSucceed(false); 819 | if ($token != null) { 820 | $modelBuilder = InvitationModel::where('content', '=', $token)->where('accepted', '=', 0); 821 | $invitationModel = $modelBuilder->get(); 822 | if ($invitationModel != null AND count($invitationModel) != 0) { 823 | $invitationModel = $invitationModel[0]; 824 | $invitationResultsBuilder->withUserId($invitationModel->userId); 825 | if (!self::isExpired(DateTime::createFromFormat('Y-m-d H:i:s', $invitationModel->expire_at))) { 826 | if ($invitationModel->whatFor == $whatFor || $whatFor == "default") { 827 | $invitationResultsBuilder->setTokenId(strval($invitationModel->id??"")); 828 | $invitationResultsBuilder->setValidationSucceed(true); 829 | $invitationResultsBuilder->withData($invitationModel->data); 830 | $invitationResultsBuilder->withTargetEmail($invitationModel->target_email); 831 | if ($thenAccept) { 832 | $invitationModel->update([ 833 | "accepted" => 1 834 | ]); 835 | } 836 | return $invitationResultsBuilder->build(); 837 | } 838 | else{ 839 | $invitationResultsBuilder->setCause("REASON_INVALID"); 840 | } 841 | } 842 | else{ 843 | $invitationResultsBuilder->setCause("TOKEN_EXPIRED"); 844 | } 845 | $invitationModel->delete(); 846 | } 847 | else{ 848 | $invitationResultsBuilder->setCause("TOKEN_INVALID"); 849 | } 850 | } 851 | else{ 852 | $invitationResultsBuilder->setCause("NO_TOKEN_PROVIDED"); 853 | } 854 | return $invitationResultsBuilder->build(); 855 | } 856 | 857 | /** 858 | * @param string $url 859 | * @param string $whatFor 860 | * @param bool $thenAccept 861 | * @return InvitationResponse 862 | */ 863 | public static function checkInvitationUrl(string $url, string $whatFor = "default", bool $thenAccept = false): InvitationResponse 864 | { 865 | return self::checkInvitationToken(call_user_func_array([new TokensValidation::$InvitationUrlBuilder(), 'getTokenFromUrl'], [$url]), $whatFor, $thenAccept); 866 | } 867 | 868 | /** 869 | * @param array $_GET_ARRAY 870 | * @param string $whatFor 871 | * @param bool $thenAccept 872 | * @return InvitationResponse 873 | */ 874 | public static function checkInvitationUrlParamsFromGET(array $_GET_ARRAY, string $whatFor = "default", bool $thenAccept = false): InvitationResponse 875 | { 876 | return self::checkInvitationToken(call_user_func_array([new TokensValidation::$InvitationUrlBuilder(), 'getTokenFromGET'], [$_GET_ARRAY]), $whatFor, $thenAccept); 877 | } 878 | 879 | /** 880 | * @param Request $request 881 | * @param string $whatFor 882 | * @param bool $thenAccept 883 | * @return InvitationResponse 884 | */ 885 | public static function checkInvitationRequest(Request $request, string $whatFor = "default", bool $thenAccept = false): InvitationResponse 886 | { 887 | return self::checkInvitationToken(call_user_func_array([new TokensValidation::$InvitationUrlBuilder(), 'getTokenFromRequest'], [$request]), $whatFor, $thenAccept); 888 | } 889 | } --------------------------------------------------------------------------------