├── .github ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── build.yml │ ├── clippy.yml │ ├── dependabot-auto-approve.yml │ ├── dependabot-auto-merge.yml │ ├── fmt.yml │ ├── release.yml │ └── tests.yml ├── .gitignore ├── CHANGELOG ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── assets └── basic-auth-workflow.png ├── rustfmt.toml └── src ├── credentials.rs ├── error.rs └── lib.rs /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 28 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Please see the documentation for all configuration options: 2 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: 'cargo' 7 | directory: '/' 8 | schedule: 9 | interval: 'weekly' 10 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - name: Cache .cargo and target 16 | uses: actions/cache@v2 17 | with: 18 | path: | 19 | ~/.cargo 20 | ./target 21 | key: ${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.toml') }} 22 | restore-keys: | 23 | ${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.lock') }} 24 | ${{ runner.os }}-cargo-build 25 | 26 | - name: cargo build 27 | uses: actions-rs/cargo@v1 28 | with: 29 | command: build 30 | args: --release --locked 31 | -------------------------------------------------------------------------------- /.github/workflows/clippy.yml: -------------------------------------------------------------------------------- 1 | name: clippy 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | clippy: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - uses: actions-rs/toolchain@v1 16 | with: 17 | profile: minimal 18 | toolchain: stable 19 | override: true 20 | components: clippy 21 | 22 | - name: Cache .cargo and target 23 | uses: actions/cache@v2 24 | with: 25 | path: | 26 | ~/.cargo 27 | ./target 28 | key: ${{ runner.os }}-cargo-clippy-${{ hashFiles('**/Cargo.lock') }} 29 | restore-keys: | 30 | ${{ runner.os }}-cargo-clippy-${{ hashFiles('**/Cargo.lock') }} 31 | ${{ runner.os }}-cargo-clippy 32 | 33 | - name: cargo clippy 34 | uses: actions-rs/cargo@v1 35 | with: 36 | command: clippy 37 | args: -- -D warnings 38 | -------------------------------------------------------------------------------- /.github/workflows/dependabot-auto-approve.yml: -------------------------------------------------------------------------------- 1 | name: Dependabot auto-approve 2 | on: pull_request_target 3 | 4 | permissions: 5 | pull-requests: write 6 | 7 | jobs: 8 | dependabot: 9 | runs-on: ubuntu-latest 10 | if: ${{ github.actor == 'dependabot[bot]' }} 11 | steps: 12 | - name: Dependabot metadata 13 | id: metadata 14 | uses: dependabot/fetch-metadata@v1.1.1 15 | with: 16 | github-token: "${{ secrets.GITHUB_TOKEN }}" 17 | - name: Approve a PR 18 | run: gh pr review --approve "$PR_URL" 19 | env: 20 | PR_URL: ${{github.event.pull_request.html_url}} 21 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 22 | -------------------------------------------------------------------------------- /.github/workflows/dependabot-auto-merge.yml: -------------------------------------------------------------------------------- 1 | name: Dependabot auto-merge 2 | on: pull_request_target 3 | 4 | permissions: 5 | pull-requests: write 6 | contents: write 7 | 8 | jobs: 9 | dependabot: 10 | runs-on: ubuntu-latest 11 | if: ${{ github.actor == 'dependabot[bot]' }} 12 | steps: 13 | - name: Dependabot metadata 14 | id: metadata 15 | uses: dependabot/fetch-metadata@v1.1.1 16 | with: 17 | github-token: "${{ secrets.GITHUB_TOKEN }}" 18 | - name: Enable auto-merge for Dependabot PRs 19 | if: ${{steps.metadata.outputs.update-type == 'version-update:semver-patch'}} 20 | run: gh pr merge --auto --merge "$PR_URL" 21 | env: 22 | PR_URL: ${{github.event.pull_request.html_url}} 23 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 24 | -------------------------------------------------------------------------------- /.github/workflows/fmt.yml: -------------------------------------------------------------------------------- 1 | name: fmt 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | fmt: 10 | name: fmt 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v2 16 | 17 | - uses: actions-rs/toolchain@v1 18 | with: 19 | profile: minimal 20 | toolchain: stable 21 | override: true 22 | components: rustfmt 23 | 24 | - name: Cache .cargo and target 25 | uses: actions/cache@v2 26 | with: 27 | path: | 28 | ~/.cargo 29 | ./target 30 | key: ${{ runner.os }}-cargo-fmt-${{ hashFiles('**/Cargo.lock') }} 31 | restore-keys: | 32 | ${{ runner.os }}-cargo-fmt-${{ hashFiles('**/Cargo.lock') }} 33 | ${{ runner.os }}-cargo-fmt 34 | 35 | - name: Run fmt 36 | uses: actions-rs/cargo@v1 37 | with: 38 | command: fmt 39 | args: --all -- --check 40 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | create-release: 10 | name: Create Release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v2 15 | 16 | - name: Create Release 17 | id: create_release 18 | uses: actions/create-release@v1 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 21 | with: 22 | tag_name: ${{ github.ref }} 23 | release_name: ${{ github.ref }} 24 | body: '' 25 | draft: false 26 | prerelease: false 27 | 28 | publish-crate: 29 | name: Publish to crates.io 30 | runs-on: ubuntu-latest 31 | steps: 32 | - uses: actions/checkout@v1 33 | 34 | - uses: actions-rs/toolchain@v1 35 | with: 36 | profile: minimal 37 | toolchain: stable 38 | - run: cargo login ${CRATES_IO_TOKEN} 39 | env: 40 | CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} 41 | 42 | - name: publish crate 43 | run: cargo publish 44 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - name: Cache .cargo and target 16 | uses: actions/cache@v2 17 | with: 18 | path: | 19 | ~/.cargo 20 | ./target 21 | key: ${{ runner.os }}-cargo-test-${{ hashFiles('**/Cargo.lock') }} 22 | restore-keys: | 23 | ${{ runner.os }}-cargo-test-${{ hashFiles('**/Cargo.lock') }} 24 | ${{ runner.os }}-cargo-test 25 | 26 | - name: cargo test 27 | uses: actions-rs/cargo@v1 28 | with: 29 | command: test 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Application Related # 2 | ####################### 3 | .vscode/ 4 | /target 5 | 6 | # Compiled source # 7 | ################### 8 | *.com 9 | *.class 10 | *.dll 11 | *.exe 12 | *.o 13 | *.so 14 | bundle 15 | 16 | # Packages # 17 | ############ 18 | # it's better to unpack these files and commit the raw source 19 | # git has its own built in compression methods 20 | *.7z 21 | *.dmg 22 | *.gz 23 | *.iso 24 | *.jar 25 | *.rar 26 | *.tar 27 | *.zip 28 | 29 | # Logs and databases # 30 | ###################### 31 | *.log 32 | *.sqlite 33 | 34 | # OS generated files # 35 | ###################### 36 | .DS_Store 37 | .DS_Store? 38 | ._* 39 | .Spotlight-V100 40 | .Trashes 41 | ehthumbs.db 42 | Thumbs.db -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [0.3.1] - 2021-08-15 8 | ### Fixed 9 | - CI workflows issues due to missing conditional publishing triggerer and out of 10 | date `Cargo.lock` file 11 | 12 | ## [0.3.0] - 2021-08-15 13 | ### Added 14 | - Support for `FromStr` for `Credentials` `struct` (https://github.com/EstebanBorai/http-auth-basic/pull/5) 15 | 16 | ### Improved 17 | - Use `split_once()` instead of `split()` for faster split procedure by avoiding 18 | unnecessary allocations (https://github.com/EstebanBorai/http-auth-basic/pull/6) 19 | 20 | ## [0.2.2] - 2021-07-13 21 | ### Fixed 22 | - Fix missing author and year on Apache License 23 | 24 | ## [0.2.1] - 2021-07-13 25 | ### Fixed 26 | - Fix corrupted Cargo.lock file 27 | 28 | ## [0.2.0] - 2021-07-13 29 | ### Chore 30 | - Update license from GPL V3 to either MIT/Apache to follow Rust ecosystem 31 | 32 | ## [0.1.3] - 2021-04-26 33 | ### Fixed 34 | - Issue where passwords containing a colon caused `AuthBasicError::InvalidAuthorizationHeader` (https://github.com/EstebanBorai/http-auth-basic/pull/3) 35 | 36 | #### Braking Change 37 | - Replace `InvalidUTF8Value` with `InvalidUtf8Value` to satisfy Rust conventions 38 | 39 | ### Added 40 | - Support for RFC 2617 spec (https://github.com/EstebanBorai/http-auth-basic/pull/3) 41 | 42 | ## [0.1.2] - 2020-09-16 43 | ### Added 44 | - Build Badge 45 | - Clippy and Fmt Badge 46 | - Tests Badge 47 | 48 | ## [0.1.1] - 2020-09-15 49 | ### Added 50 | - Badges to README 51 | - Logo to README 52 | - Usecase description 53 | 54 | ## [0.1.0] - 2020-08-30 55 | ### Added 56 | - `Credentials` struct 57 | - Capability to encode and decode 58 | - Create `Credentials` instance from `Authorization` header value 59 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at estebanborai@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to http-auth-basic 2 | 3 | * [Code of Conduct](#code-of-conduct) 4 | * [Pull Requests](#pull-requests) 5 | * [Developer's Certificate of Origin 1.1](#developers-certificate-of-origin) 6 | 7 | ## [Code of Conduct](./CODE_OF_CONDUCT.md) 8 | 9 | The http-auth-basic project has a [Code of Conduct](./CODE_OF_CONDUCT.md). All 10 | individuals participating in the http-auth-basic repo will be expected to abide by the Code of Conduct. 11 | Violating the Code of Conduct will result in action ranging from a conversation 12 | about behavior to being permanently banned from the http-auth-basic repository. 13 | 14 | ### The Spirit of the law 15 | 16 | Not all interactions that require remediation are clear violations 17 | of the Code of Conduct. Project maintainers will take appropriate 18 | action, when neccessary, to ensure the http-auth-basic community is a space 19 | where individuals can comfortably collaborate and bring their 20 | entire selves. Unfortunately, if bringing your entire self is 21 | infringing on others from doing the same, you may be asked to leave. 22 | 23 | ## Pull Requests 24 | 25 | If you would like to make a change open a Pull Request. 26 | 27 | Project maintainers will do their best to review and land code 28 | in a reasonable time frame. 29 | 30 | All Pull Requests require CI to be green to land. 31 | 32 | It is possible that some Pull Requests may be rejected. The project 33 | maintainers will make best effort to explain why a Pull Request is 34 | rejected in a timely manner. 35 | 36 | ## Developer's Certificate of Origin 1.1 37 | 38 | By making a contribution to this project, I certify that: 39 | 40 | * (a) The contribution was created in whole or in part by me and I 41 | have the right to submit it under the open source license 42 | indicated in the file; or 43 | 44 | * (b) The contribution is based upon previous work that, to the best 45 | of my knowledge, is covered under an appropriate open source 46 | license and I have the right under that license to submit that 47 | work with modifications, whether created in whole or in part 48 | by me, under the same open source license (unless I am 49 | permitted to submit under a different license), as indicated 50 | in the file; or 51 | 52 | * (c) The contribution was provided directly to me by some other 53 | person who certified (a), (b) or (c) and I have not modified 54 | it. 55 | 56 | * (d) I understand and agree that this project and the contribution 57 | are public and that a record of the contribution (including all 58 | personal information I submit with it, including my sign-off) is 59 | maintained indefinitely and may be redistributed consistent with 60 | this project or the open source license(s) involved. 61 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "base64" 7 | version = "0.22.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 10 | 11 | [[package]] 12 | name = "http-auth-basic" 13 | version = "0.3.5" 14 | dependencies = [ 15 | "base64", 16 | ] 17 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "http-auth-basic" 3 | version = "0.3.5" 4 | authors = ["Esteban Borai "] 5 | 6 | edition = "2021" 7 | license = "MIT OR Apache-2.0" 8 | description = "HTTP Basic Authentication Scheme (RFC 7617 and RFC 2617 compilant, base64-encoded credentials) for Rust applications" 9 | readme = "README.md" 10 | repository = "https://github.com/EstebanBorai/http-auth-basic" 11 | categories = ["authentication", "encoding", "web-programming", "web-programming::http-server", "web-programming::http-client"] 12 | 13 | [lib] 14 | name = "http_auth_basic" 15 | path = "src/lib.rs" 16 | 17 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 18 | 19 | [dependencies] 20 | base64 = "0.22.1" 21 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2021 Esteban Borai and Contributors 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Esteban Borai and Contributors 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 | 8 |
9 |

http-auth-basic

10 |

11 | HTTP Basic Authentication Scheme (RFC 7617 and RFC 2617 compilant, base64-encoded credentials) for Rust applications 12 |

13 |
14 | 15 |
16 | 17 | [![Crates.io](https://img.shields.io/crates/v/http-auth-basic.svg)](https://crates.io/crates/http-auth-basic) 18 | [![Documentation](https://docs.rs/http-auth-basic/badge.svg)](https://docs.rs/http-auth-basic) 19 | ![Build](https://github.com/EstebanBorai/http-auth-basic/workflows/build/badge.svg) 20 | ![Clippy](https://github.com/EstebanBorai/http-auth-basic/workflows/clippy/badge.svg) 21 | ![Fmt](https://github.com/EstebanBorai/http-auth-basic/workflows/fmt/badge.svg) 22 | ![Release](https://github.com/EstebanBorai/http-auth-basic/workflows/release/badge.svg) 23 | ![Tests](https://github.com/EstebanBorai/http-auth-basic/workflows/tests/badge.svg) 24 | 25 |
26 | 27 | ## Description 28 | 29 | The "Basic" Hypertext Transfer Protocol (HTTP) authentication scheme, transmits credentials as user-id/password pairs, encoded using Base64. 30 | 31 |
32 | 33 |
34 | 35 | The server will gather the credentials from the base64 encoded header value, and will validate them 36 | to authenticate the user in question. 37 | 38 | This crate covers the credentials encoding and decoding. The `Credentials` struct provides two fields 39 | `user_id` and `password`, these are filled with they raw values. 40 | 41 | ## Usage 42 | 43 | Decoding a basic authorization value and creating a `Credentials` struct 44 | from it 45 | 46 | ```rust 47 | use http_auth_basic::Credentials; 48 | 49 | let auth_header_value = String::from("Basic dXNlcm5hbWU6cGFzc3dvcmQ="); 50 | let credentials = Credentials::from_header(auth_header_value).unwrap(); 51 | 52 | assert_eq!(credentials.user_id, String::from("username")); 53 | assert_eq!(credentials.password, String::from("password")); 54 | ``` 55 | 56 | Encoding `Credentials` into a basic authorization header value. 57 | 58 | ```rust 59 | use http_auth_basic::Credentials; 60 | 61 | let credentials = Credentials::new("username", "password"); 62 | let credentials = credentials.as_http_header(); 63 | 64 | assert_eq!(String::from("Basic dXNlcm5hbWU6cGFzc3dvcmQ="), credentials); 65 | ``` 66 | 67 | ## Release 68 | 69 | ```bash 70 | git tag -a v0.1.0 -m "Release Message" 71 | git push origin main --follow-tags 72 | ``` 73 | 74 | ## Contributing 75 | 76 | Every contribution to this project is welcome! Feel free to open a pull request or an issue. 77 | 78 | ## References 79 | 80 | - [MDN The general HTTP Authentication Framework](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication) 81 | - [RFC7617](https://tools.ietf.org/html/rfc7617) 82 | - [RFC2617](https://tools.ietf.org/html/rfc2617) 83 | 84 | ## License 85 | 86 | Distributed under the terms of both the MIT license and the Apache License (Version 2.0) 87 | -------------------------------------------------------------------------------- /assets/basic-auth-workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoBorai/http-auth-basic/5f07a7ec001128510ea5f835648e4212c1e36fb0/assets/basic-auth-workflow.png -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # Use verbose output. 2 | # Default: false 3 | # verbose = 4 | 5 | # Do not reformat out of line modules. 6 | # Default: false 7 | # skip_children = 8 | 9 | # Lines to format; this is not supported in rustfmt.toml, 10 | # and can only be specified via the --file-lines option. 11 | # file_lines = 12 | 13 | # Maximum width of each line. 14 | # Default: 100 15 | # max_width = 16 | 17 | # Ideal width of each line. 18 | # Default: 80 19 | # max_width = 20 | 21 | # Number of spaces per tab. 22 | # Default: 4 23 | # tab_spaces = 4 24 | 25 | # Maximum width of the args of a function call before 26 | # falling back to vertical formatting. 27 | # Default: 60 28 | # fn_call_width = 29 | 30 | # Maximum width in the body of a struct lit before falling back to vertical formatting. 31 | # Default: 16 32 | # struct_lit_width = 33 | 34 | # Maximum width in the body of a struct variant before falling back to vertical formatting. 35 | # Default: 35 36 | # struct_variant_width = 37 | 38 | # Always print the abi for extern items. 39 | # Default: true 40 | # force_explicit_abi = 41 | 42 | # Unix or Windows line endings. 43 | # Values: Windows | Unix | Native 44 | # Default: Unix 45 | # newline_style = 46 | 47 | # Brace style for functions. 48 | # Values: AlwaysNextLine | PreferSameLine | SameLineWhere 49 | # Default: SameLineWhere 50 | # fn_brace_style = 51 | 52 | # Brace style for structs and enums. 53 | # Values: AlwaysNextLine | PreferSameLine | SameLineWhere 54 | # Default: SameLineWhere 55 | # item_brace_style = 56 | 57 | # Brace style for control flow construct. 58 | # Values: AlwaysSameLine | ClosingNextLine | AlwaysNextLine 59 | # Default: AlwaysSameLine 60 | # control_brace_style = 61 | 62 | # Put empty-body implementations on a single line. 63 | # Default: true 64 | # impl_empty_single_line = 65 | 66 | # Put empty-body functions on a single line. 67 | # Default: true 68 | # fn fn_empty_single_line = 69 | 70 | # Put single-expression functions on a single line. 71 | # Default: false 72 | # fn_single_line = 73 | 74 | # Location of return type in function declaration. 75 | # Values: WithArgs | WithWhereClause 76 | # Default: WithArgs 77 | # fn_return_indent = 78 | 79 | # If function argument parenthesis goes on a newline. 80 | # Default: true 81 | # fn_args_paren_newline = 82 | 83 | # Argument density in functions. 84 | # Values: Compressed | Tall | CompressedIfEmpty | Vertical 85 | # Default: Tall 86 | # fn_args_density = 87 | 88 | # Layout of function arguments. 89 | # Values: Visual | Block | BlockAlways 90 | # Default: Visual 91 | # fn_args_layout = 92 | 93 | # Indent on function arguments. 94 | # Values: Inherit | Tabbed | Visual 95 | # Default: Visual 96 | # fn_arg_indent = 97 | 98 | # Determines if '+' or '=' are wrapped in spaces in the punctuation of types. 99 | # Values: Compressed | Wide 100 | # Default: Wide 101 | # type_punctuation_density = 102 | 103 | # Density of a where clause. 104 | # Values: Compressed | Tall | CompressedIfEmpty | Vertical 105 | # Default: CompressedIfEmpty 106 | # where_density = 107 | 108 | # Indentation of a where clause. 109 | # Values: Inherit | Tabbed | Visual 110 | # Default: Tabbed 111 | # where_indent = 112 | 113 | # Element layout inside a where clause. 114 | # Values: Vertical | Horizontal | HorizontalVertical | Mixed 115 | # Default: Vertical 116 | # where_layout = 117 | 118 | # Indentation style of a where predicate. 119 | # Values: Inherit | Tabbed | Visual 120 | # Default: Visual 121 | # where_pred_indent = 122 | 123 | # Put a trailing comma on where clauses. 124 | # Default: false 125 | # where_trailing_comma = 126 | 127 | # Indentation of generics. 128 | # Values: Inherit | Tabbed | Visual 129 | # Default: Visual 130 | # generics_indent = 131 | 132 | # If there is a trailing comma on structs. 133 | # Values: Always | Never | Vertical 134 | # Default: Vertical 135 | # struct_trailing_comma = 136 | 137 | # If there is a trailing comma on literal structs. 138 | # Values: Always | Never | Vertical 139 | # Default: Vertical 140 | # struct_lit_trailing_comma = 141 | 142 | # Style of struct definition. 143 | # Values: Visual | Block 144 | # Default: Block 145 | # struct_lit_style = 146 | 147 | # Multiline style on literal structs. 148 | # Values: PreferSingle | ForceMulti 149 | # Default: PreferSingle 150 | # struct_lit_multiline_style = 151 | 152 | # Put a trailing comma on enum declarations. 153 | # Default: true 154 | # enum_trailing_comma = 155 | 156 | # Report all, none or unnumbered occurrences of TODO in source file comments. 157 | # Values: Always | Unnumbered | Never 158 | # Default: Never 159 | # report_todo = 160 | 161 | # Report all, none or unnumbered occurrences of FIXME in source file comments. 162 | # Values: Always | Unnumbered | Never 163 | # Default: Never 164 | # report_fixme = 165 | 166 | # Indent on chain base. 167 | # Values: Inherit | Tabbed | Visual 168 | # Default: Tabbed 169 | # chain_base_indent = 170 | 171 | # Indentation of chain. 172 | # Values: Inherit | Tabbed | Visual 173 | # Default: Tabbed 174 | # chain_indent = 175 | 176 | # Allow last call in method chain to break the line. 177 | # Default: true 178 | # chains_overflow_last = 179 | 180 | # Reorder import statements alphabetically. 181 | # Default: false 182 | # reorder_imports = 183 | 184 | # Reorder lists of names in import statements alphabetically. 185 | # Default: false 186 | # reorder_imported_names = 187 | 188 | # Maximum line length for single line if-else expressions. 189 | # A value of zero means always break if-else expressions. 190 | # Default: 50 191 | # single_line_if_else_max_width = 192 | 193 | # Format string literals where necessary. 194 | # Default: true 195 | # format_strings = 196 | 197 | # Always format string literals. 198 | # Default: false 199 | # force_format_strings = 200 | 201 | # Retain some formatting characteristics from the source code. 202 | # Default: true 203 | # take_source_hints = 204 | 205 | # Use tab characters for indentation, spaces for alignment. 206 | # Default: false 207 | # hard_tabs = 208 | 209 | # Break comments to fit on the line. 210 | # Default: false 211 | # wrap_comments = 212 | 213 | # Convert /* */ comments to // comments where possible. 214 | # Default: false 215 | # normalize_comments = 216 | 217 | # Wrap multiline match arms in blocks. 218 | # Default: true 219 | # wrap_match_arms = 220 | 221 | # Put a trailing comma after a block based match arm (non-block arms are not affected). 222 | # Default: false 223 | # match_block_trailing_comma = 224 | 225 | # Put a trailing comma after a wildcard arm. 226 | # Default: true 227 | # match_wildcard_trailing_comma = 228 | 229 | # How many lines a closure must have before it is block indented. 230 | # -1 means never use block indent. 231 | # Type: 232 | # Default: 5 233 | # closure_block_indent_threshold = 234 | 235 | # Leave a space before the colon in a type annotation. 236 | # Default: false 237 | # space_before_type_annotation = 238 | 239 | # Leave a space after the colon in a type annotation. 240 | # Default: true 241 | # space_after_type_annotation_colon = 242 | 243 | # Leave a space before the colon in a trait or lifetime bound. 244 | # Default: false 245 | # space_before_bound = 246 | 247 | # Leave a space after the colon in a trait or lifetime bound. 248 | # Default: true 249 | # space_after_bound_colon = 250 | 251 | # Put spaces around the .. and ... range operators. 252 | # Default: false 253 | # spaces_around_ranges = 254 | 255 | # Put spaces within non-empty generic arguments. 256 | # Default: false 257 | # spaces_within_angle_brackets = 258 | 259 | # Put spaces within non-empty square brackets. 260 | # Default: false 261 | # spaces_within_square_brackets = 262 | 263 | # Put spaces within non-empty parentheses. 264 | # Default: false 265 | # spaces_within_parens = 266 | 267 | # Replace uses of the try! macro by the ? shorthand. 268 | # Default: false 269 | # use_try_shorthand = 270 | 271 | # What Write Mode to use when none is supplied: Replace, Overwrite, Display, Diff, Coverage. 272 | # Values: Replace | Overwrite | Display | Diff | Coverage | Plain | Checkstyle 273 | # Default: Replace 274 | # write_mode = 275 | 276 | # Replace strings of _ wildcards by a single .. in tuple patterns. 277 | # Default: false 278 | # condense_wildcard_suffices = 279 | -------------------------------------------------------------------------------- /src/credentials.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use base64::{prelude::BASE64_STANDARD, Engine}; 4 | 5 | use crate::error::AuthBasicError; 6 | 7 | /// A `struct` to represent the `user_id` and `password` fields 8 | /// from an _Authorization Basic_ header value 9 | #[derive(Debug, PartialEq)] 10 | pub struct Credentials { 11 | pub user_id: String, 12 | pub password: String, 13 | } 14 | 15 | impl Credentials { 16 | /// Create a new `Credentials` instance 17 | /// this is equivalent to writing: 18 | /// 19 | /// ``` 20 | /// use http_auth_basic::Credentials; 21 | /// 22 | /// let credentials = Credentials { 23 | /// user_id: String::from("Foo"), 24 | /// password: String::from("Bar"), 25 | /// }; 26 | /// 27 | /// ``` 28 | /// 29 | pub fn new(user_id: &str, password: &str) -> Self { 30 | Self { 31 | user_id: user_id.to_string(), 32 | password: password.to_string(), 33 | } 34 | } 35 | 36 | /// Creates a `Credentials` instance from a base64 `String` 37 | /// which must encode user credentials as `username:password` 38 | pub fn decode(auth_header_value: String) -> Result { 39 | let decoded = BASE64_STANDARD.decode(auth_header_value)?; 40 | let as_utf8 = String::from_utf8(decoded)?; 41 | 42 | // RFC 2617 provides support for passwords with colons 43 | if let Some((user_id, password)) = as_utf8.split_once(':') { 44 | return Ok(Self::new(user_id, password)); 45 | } 46 | 47 | Err(AuthBasicError::InvalidAuthorizationHeader) 48 | } 49 | 50 | /// Encode a `Credentials` instance into a base64 `String` 51 | pub fn encode(&self) -> String { 52 | let credentials = format!("{}:{}", self.user_id, self.password); 53 | 54 | BASE64_STANDARD.encode(credentials.as_bytes()) 55 | } 56 | 57 | /// Creates a `Credentials` instance from an HTTP Authorization header 58 | /// which schema is a valid `Basic` HTTP Authorization Schema. 59 | pub fn from_header(auth_header: String) -> Result { 60 | // check if its a valid basic auth header 61 | if let Some((auth_type, encoded_credentials)) = auth_header.split_once(' ') { 62 | if encoded_credentials.contains(' ') { 63 | // Invalid authorization token received 64 | return Err(AuthBasicError::InvalidAuthorizationHeader); 65 | } 66 | 67 | // Check the provided authorization header 68 | // to be a "Basic" authorization header 69 | if auth_type.to_lowercase() != "basic" { 70 | return Err(AuthBasicError::InvalidScheme(auth_type.to_string())); 71 | } 72 | 73 | let credentials = Credentials::decode(encoded_credentials.to_string())?; 74 | 75 | return Ok(credentials); 76 | } 77 | 78 | Err(AuthBasicError::InvalidAuthorizationHeader) 79 | } 80 | 81 | /// Creates a HTTP Authorization header value for the basic schema 82 | /// from the `Credentials` instance 83 | pub fn as_http_header(&self) -> String { 84 | let as_base64 = self.encode(); 85 | 86 | format!("Basic {}", as_base64) 87 | } 88 | } 89 | 90 | impl FromStr for Credentials { 91 | type Err = AuthBasicError; 92 | 93 | /// Creates a `Credentials` instance from either a base64 `&str` 94 | /// which must encode user credentials as `username:password` 95 | /// or an HTTP Authorization header which schema is a 96 | /// valid `Basic` HTTP Authorization Schema. 97 | fn from_str(s: &str) -> Result { 98 | if s.contains(' ') { 99 | return Self::from_header(s.into()); 100 | } 101 | 102 | Self::decode(s.into()) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use base64::DecodeError; 2 | use std::fmt; 3 | use std::string::FromUtf8Error; 4 | 5 | /// Authorization Header Error 6 | #[derive(Debug)] 7 | pub enum AuthBasicError { 8 | /// The HTTP Authorization header value is invalid 9 | InvalidAuthorizationHeader, 10 | /// The HTTP Authorization header contains a valid 11 | /// value but the scheme is other than `Basic` 12 | InvalidScheme(String), 13 | /// The value expected as a base64 encoded `String` is not 14 | /// encoded correctly 15 | InvalidBase64Value(String), 16 | /// The provided binary is not a valid UTF-8 character 17 | InvalidUtf8Value(String), 18 | } 19 | 20 | impl fmt::Display for AuthBasicError { 21 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 22 | match self { 23 | AuthBasicError::InvalidAuthorizationHeader => write!( 24 | f, 25 | "Invalid value provided for the HTTP Authorization header" 26 | ), 27 | AuthBasicError::InvalidScheme(scheme) => { 28 | write!(f, "The scheme provided ({}) is not Basic", scheme) 29 | } 30 | AuthBasicError::InvalidBase64Value(message) => { 31 | write!(f, "The value have an invalid base64 encoding\n{}", message) 32 | } 33 | AuthBasicError::InvalidUtf8Value(message) => { 34 | write!(f, "Invalid UTF-8 Provided\n{}", message) 35 | } 36 | } 37 | } 38 | } 39 | 40 | impl From for AuthBasicError { 41 | fn from(decode_error: DecodeError) -> Self { 42 | AuthBasicError::InvalidBase64Value(decode_error.to_string()) 43 | } 44 | } 45 | 46 | impl From for AuthBasicError { 47 | fn from(utf8_err: FromUtf8Error) -> Self { 48 | AuthBasicError::InvalidUtf8Value(utf8_err.to_string()) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # http_auth_basic 2 | //! 3 | //! HTTP Basic Authentication Scheme (RFC 7617 base64-encoded credentials) for Rust applications 4 | //! 5 | //! # Example 6 | //! 7 | //! Decoding a basic authorization value and creating a `Credentials` struct 8 | //! from it 9 | //! 10 | //! ``` 11 | //! use http_auth_basic::Credentials; 12 | //! 13 | //! let auth_header_value = String::from("Basic dXNlcm5hbWU6cGFzc3dvcmQ="); 14 | //! let credentials = Credentials::from_header(auth_header_value).unwrap(); 15 | //! 16 | //! assert_eq!(credentials.user_id, String::from("username")); 17 | //! assert_eq!(credentials.password, String::from("password")); 18 | //! ``` 19 | //! 20 | //! Encoding `Credentials` into a basic authorization header value. 21 | //! 22 | //! ``` 23 | //! use http_auth_basic::Credentials; 24 | //! 25 | //! let credentials = Credentials::new("username", "password"); 26 | //! let credentials = credentials.as_http_header(); 27 | //! 28 | //! assert_eq!(String::from("Basic dXNlcm5hbWU6cGFzc3dvcmQ="), credentials); 29 | //! ``` 30 | //! 31 | mod credentials; 32 | mod error; 33 | 34 | pub use credentials::*; 35 | pub use error::*; 36 | 37 | #[cfg(test)] 38 | mod tests { 39 | use std::str::FromStr; 40 | 41 | use super::*; 42 | 43 | #[test] 44 | fn it_creates_credentials_from_value() { 45 | let auth_header_value = String::from("dXNlcm5hbWU6cGFzc3dvcmQ="); 46 | let credentials = Credentials::decode(auth_header_value).unwrap(); 47 | 48 | assert_eq!(credentials.user_id, String::from("username")); 49 | assert_eq!(credentials.password, String::from("password")); 50 | } 51 | 52 | #[test] 53 | fn it_creates_credentials_from_value_with_colon() { 54 | let auth_header_value = String::from("dXNlcm5hbWU6OnBhc3M6d29yZDo="); 55 | let credentials = Credentials::decode(auth_header_value).unwrap(); 56 | 57 | assert_eq!(credentials.user_id, String::from("username")); 58 | assert_eq!(credentials.password, String::from(":pass:word:")); 59 | } 60 | 61 | #[test] 62 | fn it_encodes_credentials() { 63 | let credentials = Credentials::new("username", "password"); 64 | let credentials = credentials.encode(); 65 | 66 | assert_eq!(String::from("dXNlcm5hbWU6cGFzc3dvcmQ="), credentials); 67 | } 68 | 69 | #[test] 70 | fn it_creates_credentials_from_header_value() { 71 | let auth_header_value = String::from("Basic dXNlcm5hbWU6cGFzc3dvcmQ="); 72 | let credentials = Credentials::from_header(auth_header_value).unwrap(); 73 | 74 | assert_eq!(credentials.user_id, String::from("username")); 75 | assert_eq!(credentials.password, String::from("password")); 76 | } 77 | 78 | #[test] 79 | fn it_creates_credentials_from_header_value_with_colon() { 80 | let auth_header_value = String::from("Basic dXNlcm5hbWU6OnBhc3M6d29yZDo="); 81 | let credentials = Credentials::from_header(auth_header_value).unwrap(); 82 | 83 | assert_eq!(credentials.user_id, String::from("username")); 84 | assert_eq!(credentials.password, String::from(":pass:word:")); 85 | } 86 | 87 | #[test] 88 | fn it_creates_header_value() { 89 | let credentials = Credentials::new("username", "password"); 90 | let credentials = credentials.as_http_header(); 91 | 92 | assert_eq!(String::from("Basic dXNlcm5hbWU6cGFzc3dvcmQ="), credentials); 93 | } 94 | 95 | #[test] 96 | fn it_creates_header_value_with_colon() { 97 | let credentials = Credentials::new("username", ":pass:word:"); 98 | let credentials = credentials.as_http_header(); 99 | 100 | assert_eq!( 101 | String::from("Basic dXNlcm5hbWU6OnBhc3M6d29yZDo="), 102 | credentials 103 | ); 104 | } 105 | 106 | #[test] 107 | fn it_creates_credentials_from_str_value() { 108 | let auth_header_value_str = "dXNlcm5hbWU6cGFzc3dvcmQ="; 109 | let credentials = Credentials::from_str(auth_header_value_str).unwrap(); 110 | 111 | assert_eq!(credentials.user_id, String::from("username")); 112 | assert_eq!(credentials.password, String::from("password")); 113 | } 114 | 115 | #[test] 116 | fn it_creates_credentials_from_str_header() { 117 | let auth_header_str = "Basic dXNlcm5hbWU6cGFzc3dvcmQ="; 118 | let credentials = Credentials::from_str(auth_header_str).unwrap(); 119 | 120 | assert_eq!(credentials.user_id, String::from("username")); 121 | assert_eq!(credentials.password, String::from("password")); 122 | } 123 | 124 | #[test] 125 | fn from_header_returns_err_when_input_has_no_whitespace() { 126 | let auth_header_value = String::from("BasicdXNlcm5hbWU6OnBhc3M6d29yZDo="); 127 | let credentials = Credentials::from_header(auth_header_value); 128 | 129 | assert!(credentials.is_err()); 130 | } 131 | 132 | #[test] 133 | fn from_header_returns_err_when_input_contains_multiple_whitespaces() { 134 | let auth_header_value = String::from("Basic dXNlcm5hbWU6On Bhc3M6d29yZDo="); 135 | let credentials = Credentials::from_header(auth_header_value); 136 | 137 | assert!(credentials.is_err()); 138 | } 139 | 140 | #[test] 141 | fn from_header_returns_err_when_input_is_not_basic_auth() { 142 | let auth_header_value = String::from("Bearer dXNlcm5hbWU6OnBhc3M6d29yZDo="); 143 | let credentials = Credentials::from_header(auth_header_value); 144 | 145 | assert!(credentials.is_err()); 146 | } 147 | 148 | #[test] 149 | fn decode_returns_err_when_input_has_no_colons() { 150 | // base64::encode("usernamepassword") = "dXNlcm5hbWVwYXNzd29yZA=="" 151 | let auth_header_value = String::from("dXNlcm5hbWVwYXNzd29yZA=="); 152 | let credentials = Credentials::from_header(auth_header_value); 153 | 154 | assert!(credentials.is_err()); 155 | } 156 | } 157 | --------------------------------------------------------------------------------