├── .eslintignore ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── help.md │ ├── report-a-bug.md │ └── request-a-feature.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── do-spaces-workflow.yml │ ├── gh-pages-workflow.yml │ └── test-workflow.yml ├── .gitignore ├── .nvmrc ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── posthtml.config.js ├── src ├── dns-lookup │ ├── data │ │ ├── dmarc.ts │ │ ├── ns_regexp.ts │ │ ├── record_key_help.ts │ │ ├── record_tutorials │ │ │ ├── cloudflare.ts │ │ │ ├── digitalocean.ts │ │ │ ├── godaddy.ts │ │ │ ├── googledomains.ts │ │ │ ├── index.ts │ │ │ ├── namecheap.ts │ │ │ ├── namecheap_registrar.ts │ │ │ ├── network_solutions.ts │ │ │ └── porkbun.ts │ │ ├── records.ts │ │ ├── registrar_regexp.ts │ │ ├── txt.ts │ │ └── vueified_records.ts │ ├── i18n │ │ ├── en │ │ │ ├── common.ts │ │ │ ├── data │ │ │ │ ├── dmarc.ts │ │ │ │ ├── index.ts │ │ │ │ ├── record_key_help.ts │ │ │ │ ├── records.ts │ │ │ │ └── txt.ts │ │ │ ├── index.ts │ │ │ └── templates │ │ │ │ ├── app.ts │ │ │ │ ├── clipboard_modal.ts │ │ │ │ ├── dmarc_explainer.ts │ │ │ │ ├── dns_diff.ts │ │ │ │ ├── dodns.ts │ │ │ │ ├── index.ts │ │ │ │ ├── propagation_modal.ts │ │ │ │ ├── record_selection_modal.ts │ │ │ │ ├── records.ts │ │ │ │ ├── truncated_record.ts │ │ │ │ └── whois.ts │ │ └── index.ts │ ├── index.html │ ├── mount.js │ ├── plain_text_reports.ts │ ├── scss │ │ └── style.scss │ ├── standardise_records.ts │ ├── table.ts │ ├── templates │ │ ├── app.vue │ │ ├── clipboard_modal.vue │ │ ├── dmarc_explainer_modal.vue │ │ ├── dns_diff.vue │ │ ├── dodns.vue │ │ ├── propagation_modal.vue │ │ ├── record.vue │ │ ├── record_base.vue │ │ ├── record_jumps.vue │ │ ├── record_selection_modal.vue │ │ ├── skeletons │ │ │ ├── dodns.vue │ │ │ ├── record.vue │ │ │ └── record_jumps.vue │ │ ├── truncated_record.vue │ │ └── whois.vue │ └── utils │ │ ├── geoJS.ts │ │ ├── googleDNS.ts │ │ └── whoisJS.ts ├── shared │ ├── assets │ │ ├── dns-bottom.svg │ │ ├── dns-top.svg │ │ ├── spf-bottom.svg │ │ └── spf-top.svg │ ├── i18n │ │ ├── en │ │ │ ├── common.ts │ │ │ ├── index.ts │ │ │ └── templates │ │ │ │ ├── error_modal.ts │ │ │ │ ├── footer.ts │ │ │ │ └── index.ts │ │ └── index.ts │ ├── templates │ │ ├── error_modal.vue │ │ ├── footer.vue │ │ ├── header.vue │ │ └── landing.vue │ └── utils │ │ ├── backoffFetch.ts │ │ ├── cfDNS.ts │ │ ├── sanitize.ts │ │ ├── skeletonStyle.ts │ │ └── validateDomain.ts ├── spf-explainer │ ├── data │ │ ├── explanations.ts │ │ └── long_descriptions.ts │ ├── i18n │ │ ├── en │ │ │ ├── data │ │ │ │ ├── explanations.ts │ │ │ │ ├── index.ts │ │ │ │ └── long_descriptions.ts │ │ │ ├── index.ts │ │ │ └── templates │ │ │ │ ├── app.ts │ │ │ │ ├── eval_notif.ts │ │ │ │ ├── index.ts │ │ │ │ ├── no_spf_records.ts │ │ │ │ ├── part_explanation.ts │ │ │ │ └── spf_base.ts │ │ └── index.ts │ ├── index.html │ ├── mount.js │ ├── scss │ │ └── style.scss │ ├── templates │ │ ├── all_part_explanations.vue │ │ ├── app.vue │ │ ├── eval_notif.vue │ │ ├── no_spf_records.vue │ │ ├── part_explanation.vue │ │ ├── skeletons │ │ │ ├── record.vue │ │ │ └── small_spf_skeleton.vue │ │ ├── spf.vue │ │ └── spf_base.vue │ └── utils │ │ ├── line_generator.ts │ │ ├── line_spawn.ts │ │ ├── spf_records.ts │ │ └── spf_sandbox.ts └── static │ ├── README.md │ ├── dns-lookup.png │ ├── dns-lookup.svg │ ├── robots.txt │ ├── spf-explainer.png │ └── spf-explainer.svg ├── tsconfig.json └── user-flow-dns-lookup.md /.eslintignore: -------------------------------------------------------------------------------- 1 | **/*.js 2 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | "plugin:@typescript-eslint/recommended", 4 | "plugin:vue/recommended", 5 | ], 6 | parserOptions: { 7 | ecmaVersion: 2018, 8 | parser: "@typescript-eslint/parser", 9 | sourceType: "module", 10 | }, 11 | rules: { 12 | "linebreak-style": ["error", "unix"], 13 | semi: ["error", "never"], 14 | "@typescript-eslint/no-non-null-assertion": 0, 15 | "@typescript-eslint/no-explicit-any": 0, 16 | "@typescript-eslint/explicit-function-return-type": 0, 17 | "@typescript-eslint/explicit-module-boundary-types": 0, 18 | "require-atomic-updates": 0, 19 | "no-undef": 0, 20 | "vue/require-v-for-key": 0, 21 | "vue/require-default-prop": 0, 22 | "vue/no-v-html": 0, 23 | "vue/max-attributes-per-line": 0, 24 | "vue/html-self-closing": 0, 25 | "vue/html-indent": ["error", 4], 26 | "vue/script-indent": ["error", 4, { 27 | baseIndent: 1, 28 | }], 29 | "@typescript-eslint/indent": 0, 30 | "vue/no-unused-vars": 0, 31 | "vue/multi-word-component-names": 0, 32 | "vue/no-reserved-component-names": 0, 33 | "@typescript-eslint/no-object-literal-type-assertion": 0, 34 | }, 35 | } 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/help.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Help ❓ 3 | about: Encountered a problem with the tool? 4 | --- 5 | 6 | 15 | 16 | ## Tool relating to this issue 17 | 18 | 19 | ## Information 20 | 22 | 23 | ## Help request 24 | 25 | ### Problem 26 | 27 | 28 | ### What I have tried 29 | 30 | 31 | ### Screenshots 32 | 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/report-a-bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Report a bug 🐛 3 | about: Report a bug with the tool. Only use this if you're 100% sure there's something wrong, otherwise, try "Help". 4 | --- 5 | 6 | 19 | 20 | ## Tool relating to this issue 21 | 22 | 23 | ## Information 24 | 26 | 27 | ## Details 28 | 29 | ### Description 30 | 31 | 32 | ### Steps to reproduce 33 | 34 | 35 | ### Expected behavior 36 | 37 | 38 | ### Screenshots 39 | 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/request-a-feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Request a feature 🆕 3 | about: Suggest a new feature that you would like in the tool! 4 | --- 5 | 6 | 31 | 32 | ## Tool relating to this issue 33 | 34 | 35 | ## Feature request 36 | 37 | ### Feature description 38 | 39 | 40 | ### How the feature is useful 41 | 42 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Type of Change 2 | 3 | 4 | - **Build Scripts:** 5 | - **Shared Source:** 6 | - **Tool Source:** 7 | - **Something else:** 8 | 9 | ## What issue does this relate to? 10 | 11 | 12 | ### What should this PR do? 13 | 14 | 15 | ### What are the acceptance criteria? 16 | 17 | 18 | -------------------------------------------------------------------------------- /.github/workflows/do-spaces-workflow.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to DigitalOcean Spaces 2 | 3 | on: push 4 | 5 | permissions: 6 | contents: write 7 | 8 | jobs: 9 | deploy-spaces: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v3 15 | 16 | - name: Use Node.js 17 | uses: actions/setup-node@v3 18 | with: 19 | node-version-file: .nvmrc 20 | cache: npm 21 | 22 | - name: Install dependencies, test, and build 23 | run: | 24 | npm ci 25 | npm test 26 | npm run build 27 | 28 | - name: Deploy DNS lookup commit to DigitalOcean Spaces 29 | run: aws s3 sync ./dist/dns-lookup s3://${{ secrets.SPACES_BUCKET }}/commits/dns-lookup/${{ github.sha }} --endpoint=https://${{ secrets.SPACES_REGION }}.digitaloceanspaces.com --acl public-read --content-encoding utf8 30 | env: 31 | AWS_ACCESS_KEY_ID: ${{ secrets.SPACES_ACCESS_KEY_ID }} 32 | AWS_SECRET_ACCESS_KEY: ${{ secrets.SPACES_SECRET_ACCESS_KEY }} 33 | AWS_DEFAULT_REGION: ${{ secrets.SPACES_REGION }} 34 | 35 | - name: Deploy SPF explainer commit to DigitalOcean Spaces 36 | run: aws s3 sync ./dist/spf-explainer s3://${{ secrets.SPACES_BUCKET }}/commits/spf-explainer/${{ github.sha }} --endpoint=https://${{ secrets.SPACES_REGION }}.digitaloceanspaces.com --acl public-read --content-encoding utf8 37 | env: 38 | AWS_ACCESS_KEY_ID: ${{ secrets.SPACES_ACCESS_KEY_ID }} 39 | AWS_SECRET_ACCESS_KEY: ${{ secrets.SPACES_SECRET_ACCESS_KEY }} 40 | AWS_DEFAULT_REGION: ${{ secrets.SPACES_REGION }} 41 | 42 | - name: Leave a comment on commit 43 | run: npm run deploy:spaces:comment 44 | env: 45 | REPO_NAME: ${{ github.repository }} 46 | COMMIT_SHA: ${{ github.sha }} 47 | GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} 48 | SPACES_REGION: ${{ secrets.SPACES_REGION }} 49 | SPACES_BUCKET: ${{ secrets.SPACES_BUCKET }} 50 | -------------------------------------------------------------------------------- /.github/workflows/gh-pages-workflow.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | permissions: 9 | contents: write 10 | 11 | concurrency: 12 | group: gh-pages-workflow 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | deploy-pages: 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v3 22 | 23 | - name: Use Node.js 24 | uses: actions/setup-node@v3 25 | with: 26 | node-version-file: .nvmrc 27 | cache: npm 28 | 29 | - name: Install dependencies, test, and build 30 | run: | 31 | npm ci 32 | npm test 33 | npm run build 34 | 35 | - name: Deploy master to GitHub Pages 36 | uses: JamesIves/github-pages-deploy-action@v4 37 | with: 38 | folder: dist 39 | clean: true 40 | single-commit: true 41 | -------------------------------------------------------------------------------- /.github/workflows/test-workflow.yml: -------------------------------------------------------------------------------- 1 | name: Test and Build 2 | 3 | on: pull_request 4 | 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | test-build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v3 15 | 16 | - name: Use Node.js 17 | uses: actions/setup-node@v3 18 | with: 19 | node-version-file: .nvmrc 20 | cache: npm 21 | 22 | - name: Install dependencies 23 | run: npm ci 24 | 25 | - name: Run tests 26 | run: npm test 27 | 28 | - name: Build tool 29 | run: npm run build 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .cache/ 3 | dist/ 4 | dev/ 5 | .idea/ 6 | .vscode/ 7 | build/base.html 8 | build/svg/ 9 | .DS_Store 10 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v20.9.0 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Pull Requests 4 | 5 | ### Creating a Pull Request 6 | 7 | This application has been designed so that people can easily expand it. 8 | To request us to review code that you create, you will need to create a pull request. 9 | Creating a pull request is described in 10 | [this tutorial](https://www.digitalocean.com/community/tutorials/how-to-create-a-pull-request-on-github). 11 | 12 | ### Linting 13 | 14 | Before creating a pull request to this application, you will want to lint it first. 15 | This is because linting is a check that is ran when a pull request is made and cannot be merged in if it fails. 16 | 17 | To lint, simply run `npm test`. This will lint all the TS, Vue & SCSS files within the app. 18 | 19 | If there are any errors that can be automatically be fixed with the TS & Vue files, you can execute 20 | `npm run test:ts-vue:fix` to automatically do that. 21 | 22 | This project enforces LF line styles, 4 spaces and no semi-colons. 23 | The linting will fail if this is not followed. 24 | 25 | ### File Location/Types 26 | 27 | Please see [README: Source Structure](README.md#source-structure) for information on how files should be organised. 28 | 29 | ## Issue Creation 30 | 31 | In the event that you have a issue using the tool or have a suggest for a change but don't want to contribute code, 32 | we are more than happy to help. 33 | Make sure that when you create your issue, it follows the format for the type of issue you select 34 | (it has individual templates for each issue type). 35 | 36 | Issue template types include the following: 37 | - Bug Reporting 38 | - Feature Requests 39 | - Help Requests 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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 2019 DigitalOcean 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Community DNS Tools 2 | 3 | A set of browser-based DNS tools for DigitalOcean Community. 4 | 5 | --- 6 | 7 | 8 | 9 | 10 | 11 | ## DNS Lookup 12 | 13 | A simple browser-based tool to perform DNS lookups. Type a domain, search, and instantly get results. 14 | 15 | ### [➡️ Use now](https://www.digitalocean.com/community/tools/dns) 16 | 17 | --- 18 | 19 | 20 | 21 | 22 | 23 | ## SPF Explainer 24 | 25 | A tool that explains a domain's SPF records. Search a domain and either explore its records or evaluate an IP for mail sending. 26 | 27 | ### [➡️ Use now](https://www.digitalocean.com/community/tools/spf) 28 | 29 | ## Development/Building 30 | 31 | To setup the build/develop environment, you will need to run `npm i` with Node 12+ installed. This will install the 32 | dependencies to allow you to build the project. 33 | 34 | To develop for the DNS tool run `npm run dev:tools:dns-lookup`, and to develop for the SPF explainer run 35 | `npm run dev:tools:spf-explainer`.\ 36 | This will start a development server that will automatically reload the codebase when changes occur. 37 | 38 | If you wish to host these tools on a service, simply run `npm run build`. This will run all the necessary build scripts 39 | automatically to build all the tools present in the source folder.\ 40 | You can then take the `dist` folder and put it on your web server/bucket. The `dist` folder will contain the folders 41 | `dns-lookup` and `spf-explainer` which will each have their respective tools inside. 42 | 43 | GitHub Actions is setup to do this automatically for this repository to deploy to gh-pages. 44 | It is also configured to deploy each PR commit to DigitalOcean Spaces for PR previews. 45 | 46 | ## Source Structure 47 | 48 | ### [`src`](./src) 49 | 50 | All the source for the tools is located within the [`src`](./src) directory. 51 | 52 | In this directory, there is the [`src/shared`](./src/shared) directory which contains centralised assets and source for 53 | the tools, such as the main Community styling which is located in [`src/shared/scss`](./src/shared/scss) and the 54 | generic templates used by all tools in [`src/shared/templates`](./src/shared/templates). 55 | 56 | Within this directory are also the main tool source directories ([`src/dns-lookup`](./src/dns-lookup) & 57 | [`src/spf-explainer`](./src/spf-explainer)).\ 58 | These directories contain the specific source for that tool, which includes custom templates and style inheritance from 59 | the centralised styles. 60 | 61 | Anything that is data which is used in a tool should be stored in `src//data`. 62 | Any helper functions should be stored in `src//utils`. 63 | Vue templates should be stored in `src//templates` with a name that makes sense for what it does. 64 | The `src//index.html` file should only be used to handle basic head information and initialise the app. 65 | 66 | ### [`build`](./build) 67 | 68 | The [`build`](./build) directory contains all the scripts needed to successfully build the tools into a minimal number 69 | of assets. 70 | 71 | [`build/cleanDist.js`](./build/cleanDist.js) is a simple script that creates the `dist` directory if it does not exist 72 | and then ensures that it is completely empty so that the build script has a fresh beginning. 73 | 74 | [`build/fetchTemplate.js`](./build/fetchTemplate.js) handles pulling down the blank DigitalOcean Community template 75 | page, converting it to be a PostHTML-Extend template and save it to `build/base.html`. 76 | 77 | [`build/buildSVGs.js`](./build/buildSVGs.js) takes all SVG files located in [`src/shared/assets`](./src/shared/assets) 78 | and converts them to JS strings (saved to `build/svg/.svg.js`) that allow Vue/Parcel to include the SVGs inline. 79 | 80 | [`build/buildTool.js`](./build/buildTool.js) is the main script file for the build process a tool in [`src`](./src). 81 | This builds out the `mount.js` file first, then compiles the `scss/style.scss` file to `style.css`, both using Parcel. 82 | Finally, the script uses PostHTML to bundle the `index.html` file, making use of the generate DigitalOcean Community 83 | template at `build/base.html`. 84 | 85 | ## Contributing 86 | 87 | If you are contributing, please read the [contributing file](CONTRIBUTING.md) before submitting your pull requests. 88 | 89 | ## Thanks 90 | 91 | Thanks to [Cloudflare](https://cloudflare.com) for their great WHOIS/DNS-over-HTTPS APIs. 92 | You can learn more about the importance of DNS-over-HTTPS and how to use it [here](https://developers.cloudflare.com/1.1.1.1/dns-over-https/). 93 | 94 | Thanks to [Matthew Gall](https://twitter.com/matthewgall) for his wonderful [WHOIS API.](https://whoisjs.com/) 95 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dns-tool", 3 | "version": "1.0.0", 4 | "description": "A set of simple DNS tools for DigitalOcean Community.", 5 | "license": "Apache-2.0", 6 | "private": true, 7 | "engines": { 8 | "node": "20.9.0" 9 | }, 10 | "scripts": { 11 | "build": "npm run build:clean && npm run build:svg && npm run build:template && npm run build:static && npm run build:tools", 12 | "build:clean": "do-vue clean", 13 | "build:svg": "do-vue svgs src/shared/assets", 14 | "build:template": "do-vue template", 15 | "build:static": "copyfiles --up 2 ./src/static/{*,**/*} dist", 16 | "build:tools": "do-vue tool src/dns-lookup dist/dns-lookup && do-vue tool src/spf-explainer dist/spf-explainer", 17 | "deploy:spaces:comment": "do-vue comment dns-lookup spf-explainer", 18 | "dev:prep": "npm run build:svg && npm run build:template && npm run dev:static", 19 | "dev:static": "copyfiles --up 2 ./src/static/{*,**/*} dev", 20 | "dev:tools:spf-explainer": "npm run dev:prep && do-vue dev src/spf-explainer dev/spf-explainer 8001", 21 | "dev:tools:dns-lookup": "npm run dev:prep && do-vue dev src/dns-lookup dev/dns-lookup 8002", 22 | "test": "npm run test:ts-vue && npm run test:scss", 23 | "test:ts-vue": "tsc -noEmit && eslint './src/**/*.{vue,ts}'", 24 | "test:scss": "stylelint ./src/**/*.scss --config node_modules/do-bulma/.stylelintrc.json", 25 | "test:ts-vue:fix": "tsc && eslint './src/**/*.{vue,ts}' --fix" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "git+https://github.com/do-community/dns-tool.git" 30 | }, 31 | "keywords": [ 32 | "dns" 33 | ], 34 | "author": "DigitalOcean", 35 | "bugs": { 36 | "url": "https://github.com/do-community/dns-tool/issues" 37 | }, 38 | "homepage": "https://github.com/do-community/dns-tool#readme", 39 | "dependencies": { 40 | "abortcontroller-polyfill": "^1.7.5", 41 | "babel-polyfill": "^6.26.0", 42 | "cidr-matcher": "^2.1.1", 43 | "do-bulma": "github:do-community/do-bulma", 44 | "do-vue": "github:do-community/do-vue", 45 | "vue": "^3.4.15", 46 | "vue-tippy": "^6.4.1", 47 | "web-whois": "0.0.6" 48 | }, 49 | "devDependencies": { 50 | "@types/cidr-matcher": "^2.1.2", 51 | "@typescript-eslint/eslint-plugin": "^6.19.0", 52 | "@typescript-eslint/parser": "^6.19.0", 53 | "@vue/component-compiler-utils": "^3.3.0", 54 | "copyfiles": "^2.4.1", 55 | "eslint": "^8.56.0", 56 | "eslint-plugin-vue": "^9.20.1", 57 | "postcss": "^8.4.34", 58 | "posthtml": "^0.16.6", 59 | "posthtml-extend": "^0.6.5", 60 | "sass": "^1.70.0", 61 | "stylelint": "^16.2.0", 62 | "stylelint-config-standard-scss": "^13.0.0", 63 | "stylelint-order": "^6.0.4", 64 | "typescript": "^5.3.3", 65 | "vue-template-compiler": "^2.7.16" 66 | }, 67 | "overrides": { 68 | "do-vue": { 69 | "pretty-checkbox-vue": { 70 | "vue": "^3.0.0" 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /posthtml.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "plugins": { 3 | "posthtml-extend": { 4 | "root": "./src" 5 | }, 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/dns-lookup/data/dmarc.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import i18n from "../i18n" 18 | 19 | export default { 20 | v: i18n.data.dmarc.v, 21 | ruf: i18n.data.dmarc.ruf, 22 | rua: i18n.data.dmarc.rua, 23 | pct: i18n.data.dmarc.pct, 24 | p: i18n.data.dmarc.p, 25 | aspf: i18n.data.dmarc.aspf, 26 | adkim: i18n.data.dmarc.adkim, 27 | sp: i18n.data.dmarc.sp, 28 | fo: i18n.data.dmarc.fo, 29 | rf: i18n.data.dmarc.rf, 30 | ri: i18n.data.dmarc.ri, 31 | } 32 | -------------------------------------------------------------------------------- /src/dns-lookup/data/ns_regexp.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | const digitalOceanRegex = /.*digitalocean\.com\.*/ 18 | const cloudflareRegex = /.*cloudflare\.com\.*/ 19 | const namecheapRegex = /.*registrar-servers\.com.*/ 20 | const goDaddyRegex = /.*godaddy\.com.*/ 21 | 22 | // Defines regex checks for providers and then ways to change records with that provider. 23 | const records = new Map() 24 | records.set(digitalOceanRegex, "digitalocean") 25 | records.set(cloudflareRegex, "cloudflare") 26 | records.set(namecheapRegex, "namecheap") 27 | records.set(goDaddyRegex, "godaddy") 28 | export default records 29 | -------------------------------------------------------------------------------- /src/dns-lookup/data/record_key_help.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import i18n from "../i18n" 18 | 19 | export default { 20 | TTL: i18n.data.recordKeyHelp.TTL, 21 | } 22 | -------------------------------------------------------------------------------- /src/dns-lookup/data/record_tutorials/cloudflare.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | export default "https://support.cloudflare.com/hc/en-us/articles/360019093151-Managing-DNS-records-in-Cloudflare" 18 | -------------------------------------------------------------------------------- /src/dns-lookup/data/record_tutorials/digitalocean.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | export default { 18 | A: "https://www.digitalocean.com/docs/networking/dns/how-to/manage-records/#a-records", 19 | AAAA: "https://www.digitalocean.com/docs/networking/dns/how-to/manage-records/#aaaa-records", 20 | CNAME: "https://www.digitalocean.com/docs/networking/dns/how-to/manage-records/#cname-records", 21 | MX: "https://www.digitalocean.com/docs/networking/dns/how-to/manage-records/#mx-records", 22 | TXT: "https://www.digitalocean.com/docs/networking/dns/how-to/manage-records/#txt-records", 23 | NS: "https://www.digitalocean.com/docs/networking/dns/how-to/manage-records/#ns-records", 24 | SRV: "https://www.digitalocean.com/docs/networking/dns/how-to/manage-records/#srv-records", 25 | CAA: "https://www.digitalocean.com/docs/networking/dns/how-to/manage-records/#caa-records", 26 | } 27 | -------------------------------------------------------------------------------- /src/dns-lookup/data/record_tutorials/godaddy.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | export default "https://uk.godaddy.com/help/manage-dns-zone-files-680" 18 | -------------------------------------------------------------------------------- /src/dns-lookup/data/record_tutorials/googledomains.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | export default "https://support.google.com/domains/answer/3290309?hl=en-GB" 18 | -------------------------------------------------------------------------------- /src/dns-lookup/data/record_tutorials/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import digitalocean from "./digitalocean" 18 | import cloudflare from "./cloudflare" 19 | import namecheap from "./namecheap" 20 | import godaddy from "./godaddy" 21 | import googledomains from "./googledomains" 22 | import porkbun from "./porkbun" 23 | import networksolutions from "./network_solutions" 24 | import namecheapregistrar from "./namecheap_registrar" 25 | 26 | export default { 27 | cloudflare, digitalocean, namecheap, godaddy, googledomains, porkbun, networksolutions, namecheapregistrar, 28 | } 29 | -------------------------------------------------------------------------------- /src/dns-lookup/data/record_tutorials/namecheap.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | export default { 18 | A: "https://www.namecheap.com/support/knowledgebase/article.aspx/319/2237/how-can-i-set-up-an-a-address-record-for-my-domain", 19 | AAAA: "", 20 | } 21 | -------------------------------------------------------------------------------- /src/dns-lookup/data/record_tutorials/namecheap_registrar.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | export default "https://www.namecheap.com/support/knowledgebase/article.aspx/767/10/how-to-change-dns-for-a-domain" 18 | -------------------------------------------------------------------------------- /src/dns-lookup/data/record_tutorials/network_solutions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | export default "https://www.networksolutions.com/support/how-to-manage-advanced-dns-records/" 18 | -------------------------------------------------------------------------------- /src/dns-lookup/data/record_tutorials/porkbun.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | export default "https://kb.porkbun.com/article/22-how-to-change-your-nameservers" 18 | -------------------------------------------------------------------------------- /src/dns-lookup/data/records.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import i18n from "../i18n" 18 | 19 | export default { 20 | A: { 21 | info: i18n.data.records.A, 22 | url: "https://kb.pressable.com/article/dns-record-types-explained/", 23 | expectsHost: true, 24 | }, 25 | TXT: { 26 | info: i18n.data.records.TXT, 27 | url: "https://support.google.com/a/answer/2716800?hl=en", 28 | additionalDataParsing: (data: string) => data.startsWith("\"") ? data.substr(1).slice(0, -1) : data, 29 | }, 30 | MX: { 31 | info: i18n.data.records.MX, 32 | url: "https://en.wikipedia.org/wiki/MX_record", 33 | additionalDataParsing: (data: string) => data.endsWith(".") ? data.slice(0, -1) : data, 34 | expectsHost: true, 35 | }, 36 | AAAA: { 37 | info: i18n.data.records.AAAA, 38 | url: "https://help.fasthosts.co.uk/app/answers/detail/a_id/1548/~/dns-aaaa-records", 39 | expectsHost: true, 40 | }, 41 | CNAME: { 42 | info: i18n.data.records.CNAME, 43 | url: "https://support.google.com/a/answer/112037?hl=en", 44 | expectsHost: true, 45 | }, 46 | CAA: { 47 | info: i18n.data.records.CAA, 48 | url: "https://www.digitalocean.com/docs/networking/dns/how-to/caa/", 49 | }, 50 | NS: { 51 | info: i18n.data.records.NS, 52 | url: "https://www.cloudflare.com/learning/dns/dns-records/dns-ns-record/", 53 | additionalDataParsing: (data: string) => data.endsWith(".") ? data.slice(0, -1) : data, 54 | expectsHost: true, 55 | }, 56 | SRV: { 57 | info: i18n.data.records.SRV, 58 | url: "https://en.wikipedia.org/wiki/SRV_record", 59 | }, 60 | DMARC: { 61 | info: i18n.data.records.DMARC, 62 | url: "https://dmarc.org/overview/", 63 | }, 64 | SSHFP: { 65 | info: i18n.data.records.SSHFP, 66 | url: "https://en.wikipedia.org/wiki/SSHFP_record", 67 | }, 68 | TLSA: { 69 | info: i18n.data.records.TLSA, 70 | url: "https://simpledns.com/help/tlsa-records", 71 | }, 72 | } 73 | -------------------------------------------------------------------------------- /src/dns-lookup/data/registrar_regexp.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | const googleDomainsRegex = /.*domains\.google\.com.*/ 18 | const porkbunRegex = /.*porkbun\.com.*/ 19 | const networkSolutionsRegex = /.*networksolutions\.com.*/ 20 | const namecheapRegex = /.*namecheap\.com.*/ 21 | 22 | // Defines regex checks for providers and then ways to change records with that provider. 23 | const records = new Map() 24 | records.set(googleDomainsRegex, "googledomains") 25 | records.set(porkbunRegex, "porkbun") 26 | records.set(networkSolutionsRegex, "networksolutions") 27 | records.set(namecheapRegex, "namecheapregistrar") 28 | 29 | export default records 30 | -------------------------------------------------------------------------------- /src/dns-lookup/data/txt.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import i18n from "../i18n" 18 | 19 | export default { 20 | "google-site-verification": i18n.data.txt.google, 21 | "mailru-verification": i18n.data.txt.mailru, 22 | "MS": i18n.data.txt.o365, 23 | "keybase-site-verification": i18n.data.txt.keybase, 24 | "_globalsign-domain-verification": i18n.data.txt.globalsign, 25 | "globalsign-domain-verification": i18n.data.txt.globalsign, 26 | "bugcrowd-verification": i18n.data.txt.bugcrowd, 27 | "status-page-domain-verification": i18n.data.txt.statuspage, 28 | "segment-site-verification": i18n.data.txt.segment, 29 | "logmein-verification-code": i18n.data.txt.logmein, 30 | "facebook-domain-verification": i18n.data.txt.facebook, 31 | "yandex-verification": i18n.data.txt.yandex, 32 | "loaderio": i18n.data.txt.loaderio, 33 | "v=spf1": i18n.data.txt.spf, 34 | } 35 | -------------------------------------------------------------------------------- /src/dns-lookup/data/vueified_records.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import recordsDataset from "./records" 18 | 19 | const setRecords = [] 20 | for (const recordKey in recordsDataset) { 21 | const record = (recordsDataset as any)[recordKey] 22 | setRecords.push({ 23 | name: recordKey, 24 | description: record.info, 25 | url: record.url, 26 | expectsHost: Boolean(record.expectsHost), 27 | }) 28 | } 29 | export default setRecords 30 | -------------------------------------------------------------------------------- /src/dns-lookup/i18n/en/common.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | close: "close", 3 | records: "Records", 4 | none: "None", 5 | } as {[key: string]: string} 6 | -------------------------------------------------------------------------------- /src/dns-lookup/i18n/en/data/dmarc.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | v: `This dictates the DMARC protocol version that is being used. This is normally located at the start of a record and set to DMARC1.`, 3 | ruf: "This is a comma separated list of e-mails where forensic reports should be sent.", 4 | rua: "This is a comma separated list of e-mails where aggregate reports should be sent.", 5 | pct: "This defines the percentage of e-mail which will have this policy applied.", 6 | p: `This defines how domain administrators want e-mail treated on the primary domain that fail the DMARC validation checks. The options are none (treat the same as usual), quarantine (mark as spam) or reject (reject the message).`, 7 | aspf: `If this is set to strict s mode the domain which e-mails are being sent from must exactly pass SPF record validation. If not, it will default to relaxed r mode where any sub-domain can inherit the root domain's SPF records.`, 8 | adkim: `By default this is set to relaxed r mode where any sub-domain can inherit the root domain's DKIM records. If this is set to strict s mode, the domain which e-mails are being sent from must exactly pass DKIM record validation.`, 9 | sp: `This defines how domain administrators want e-mail treated on sub-domains that fail the DMARC validation checks. The options are none (treat the same as usual), quarantine (mark as spam) or reject (reject the message).`, 10 | fo: `This option allows you to configure when failure reports should be sent. The available options are 0 which is the default and specifies that a report should be sent if an email fails SPF and DKIM checks, 1 to send a report if SPF or DKIM checks fail, d to send a report for only DKIM failures and s to send a report for only SPF failures.`, 11 | rf: `This is the format reports are sent in. It currently only has one supported value, afrf, which is the default.`, 12 | ri: `The requested interval between DMARC reports being sent in seconds. This defaults to 86400.`, 13 | } as {[key: string]: string} 14 | -------------------------------------------------------------------------------- /src/dns-lookup/i18n/en/data/index.ts: -------------------------------------------------------------------------------- 1 | import txt from "./txt" 2 | import dmarc from "./dmarc" 3 | import records from "./records" 4 | import recordKeyHelp from "./record_key_help" 5 | 6 | export default { 7 | txt, records, recordKeyHelp, dmarc, 8 | } as {[key: string]: {[key: string]: string}} 9 | -------------------------------------------------------------------------------- /src/dns-lookup/i18n/en/data/record_key_help.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | TTL: "Time-to-live is a value in seconds that indicates how long a record should be cached for before being checked again", 3 | } as {[key: string]: string} 4 | -------------------------------------------------------------------------------- /src/dns-lookup/i18n/en/data/records.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | A: "A records are the most basic type of DNS record and are used to point a domain or subdomain to an IPv4 address.\nThese do NOT act as MX records, to receive e-mail, you need to set a MX record.", 3 | TXT: "TXT records are a type of DNS record that contains text information for sources outside of your domain.", 4 | MX: "A mail exchanger record (MX record) specifies the mail server responsible for accepting email messages on behalf of a domain name.", 5 | AAAA: "AAAA records behave the same as A records but for IPv6.\nThey are used to point a domain or subdomain to a IPv6 address.", 6 | CNAME: "CNAME records are a DNS record that allows one domain to be mapped as an alias to another canonical domain name.", 7 | CAA: "CAA records allow domain owners to specify which Certificate Authorities (CAs) are permitted to issue certificates.", 8 | NS: "NS stands for \"name server\" and this record indicates which DNS server is authoritative for that domain (which server contains the actual DNS records).\nA domain will often have multiple NS records which can indicate primary and backup name servers for that domain.", 9 | SRV: "A Service record (SRV record) is a specification of data in the Domain Name System defining the location, i.e. the hostname and port number, of servers for specified services.", 10 | DMARC: "The goal of DMARC is to build on the system of senders and receivers collaborating to improve mail authentication practices of senders and enable receivers to reject unauthenticated messages.", 11 | SSHFP: "This is used to identify which SSH keys belong to the domain.", 12 | TLSA: "TLSA records are used to specify the keys used in a domain's TLS servers.", 13 | } as {[key: string]: string} 14 | -------------------------------------------------------------------------------- /src/dns-lookup/i18n/en/data/txt.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | globalsign: "This TXT record is used so that GlobalSign can verify that they are issuing certificates to the domain owner.", 3 | o365: "This TXT record is used for Office 365 domain verification.", 4 | keybase: "This TXT record is commonly used to verify that a Keybase user is in ownership of a domain.", 5 | spf: "This is a SPF record which is used to identify trusted sources for transmitted e-mail.\nThis makes it easier for receivers to tell if a e-mail is spam.", 6 | loaderio: "This is used so that Loader.io can verify the domain owner.", 7 | yandex: "This is used so that Yandex can verify the domain owner.", 8 | facebook: "This is used so that Facebook can verify the domain owner.", 9 | logmein: "This is used so that LogMeIn can verify the domain owner.", 10 | segment: "This is used so that Segment.com can verify the domain owner.", 11 | statuspage: "This is used so that Statuspage.io can verify the domain owner.", 12 | bugcrowd: "This TXT record is used so that Bugcrowd can verify the domain owner.", 13 | mailru: "This TXT record is used so that mail.ru can verify the domain owner.", 14 | google: "This TXT record is used so that Google can verify the domain owner.", 15 | } as {[key: string]: string} 16 | -------------------------------------------------------------------------------- /src/dns-lookup/i18n/en/index.ts: -------------------------------------------------------------------------------- 1 | import templates from "./templates" 2 | import data from "./data" 3 | import common from "./common" 4 | 5 | export default { templates, data, common } as any 6 | -------------------------------------------------------------------------------- /src/dns-lookup/i18n/en/templates/app.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | title: "DNS Lookup", 3 | description: `A simple browser-based tool to perform DNS lookups.
Type a domain, search, and instantly get results.`, 4 | searchButton: "Search DNS Records", 5 | textRecords: "Download/Copy Records", 6 | domain: "Domain name", 7 | } as {[key: string]: string} 8 | -------------------------------------------------------------------------------- /src/dns-lookup/i18n/en/templates/clipboard_modal.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | clipboardResult: "Clipboard Result", 3 | thisHasBeenCopied: "Copied to your clipboard!", 4 | } as {[key: string]: string} 5 | -------------------------------------------------------------------------------- /src/dns-lookup/i18n/en/templates/dmarc_explainer.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | title: "DMARC Explanations", 3 | intro: `

A DMARC record is a normal TXT DNS records that is created at _dmarc.hostname. instead of just the root hostname. 4 | This record can contain any of the following "tags" formatted as tag=value and separated by semi-colons.

5 |

An example of a DMARC TXT record would be:

_dmarc.hostname. 300 IN TXT "v=DMARC1;p=reject;pct=100;rua=mailto:postmaster@hostname"

`, 6 | learnMore: "Learn more about DMARC at", 7 | } as {[key: string]: string} 8 | -------------------------------------------------------------------------------- /src/dns-lookup/i18n/en/templates/dns_diff.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | title: "Propagation Differences", 3 | cfDns: "Cloudflare DNS", 4 | gDns: "Google DNS", 5 | host: "Host", 6 | XRecords: "({record} Records)", 7 | } as {[key: string]: string} 8 | -------------------------------------------------------------------------------- /src/dns-lookup/i18n/en/templates/dodns.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | using: "This domain is using DigitalOcean DNS.", 3 | notUsing: "This domain is not using DigitalOcean DNS.", 4 | learnMore: "Learn more about DigitalOcean DNS.", 5 | subdomain: "This is a sub-domain and does not have any NS records.", 6 | } as {[key: string]: string} 7 | -------------------------------------------------------------------------------- /src/dns-lookup/i18n/en/templates/index.ts: -------------------------------------------------------------------------------- 1 | import app from "./app" 2 | import dodns from "./dodns" 3 | import records from "./records" 4 | import truncatedRecord from "./truncated_record" 5 | import whois from "./whois" 6 | import propagationModal from "./propagation_modal" 7 | import recordSelectionModal from "./record_selection_modal" 8 | import dnsDiff from "./dns_diff" 9 | import clipboardModal from "./clipboard_modal" 10 | import dmarcExplainer from "./dmarc_explainer" 11 | 12 | export default { 13 | app, dodns, records, truncatedRecord, 14 | whois, propagationModal, recordSelectionModal, 15 | dnsDiff, clipboardModal, dmarcExplainer, 16 | } as {[key: string]: {[key: string]: any}} 17 | -------------------------------------------------------------------------------- /src/dns-lookup/i18n/en/templates/propagation_modal.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | tutorial: ` 3 | If you recently changed a DNS value, there is a good chance it will not have propagated to all your users yet. 4 | DNS propagation is the process of updating DNS values across all DNS servers and waiting for users caches to invalidate. 5 |

6 | Due to various caches at user and DNS server levels, it may take some time for all your users to see the new records. 7 |

8 | If you use a fast updating DNS such as {link|Cloudflare DNS|https://1.1.1.1}, it may be possible to get the new values locally by flushing your DNS cache. 9 | Here is how you do it on several operating systems: 10 |
11 | Windows: Press the Windows Key and R together. You should get a "Run" box. Type ipconfig /flushdns in the box and press OK. You should see a brief flash of a black box. Your cache should then be invalidated. 12 |

13 | Linux: Linux depends on the distribution. For Debian based distributions, run sudo systemd-resolve --flush-caches. 14 |

15 | macOS: Open Terminal and run the command sudo killall -HUP mDNSResponder. 16 |

17 | iPadOS: Turning off and on Airplane mode will flush your DNS cache. 18 |
19 | If you are still unable to get the correct results, you can check the propagation status using a {link|propagation checker|{{DEEPLINK}}}. 20 | `, 21 | title: "DNS Propagation", 22 | } as {[key: string]: string} 23 | -------------------------------------------------------------------------------- /src/dns-lookup/i18n/en/templates/record_selection_modal.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | downloadRecords: "Download Records", 3 | downloadTextForm: "Download Records In Text Form", 4 | copyTextForm: "Copy Records In Text Form", 5 | downloadMd: "Download Records In Markdown", 6 | copyMd: "Copy Records In Markdown", 7 | } as {[key: string]: string} 8 | -------------------------------------------------------------------------------- /src/dns-lookup/i18n/en/templates/records.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | learnMore: "Learn more", 3 | noRecords: "Could not find any records of this type.", 4 | propagation: "Why do I get different values on my local system?", 5 | learnHow: "Learn how to set {record} records with your DNS/registrar.", 6 | propagationNote: "Google DNS is reporting different values. This potentially means that your values have not propagated fully yet.", 7 | srvTlsaFormat: `Expecting to see {a} {record} record here? Make sure you're looking at the right sub-domain. 8 |
{record} record names are normally formatted as follows: _<{sub}>._<protocol>.name.`, 9 | dmarcMechanisms: "What are the mechanisms supported in DMARC records?", 10 | } as {[key: string]: string} 11 | -------------------------------------------------------------------------------- /src/dns-lookup/i18n/en/templates/truncated_record.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | showMore: "Show more...", 3 | showLess: "Show less...", 4 | } as {[key: string]: string} 5 | -------------------------------------------------------------------------------- /src/dns-lookup/i18n/en/templates/whois.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | loading: "Loading WHOIS data...", 3 | abuseContact: "Abuse Contact", 4 | owner: "Owner", 5 | notSpecified: "Not Specified", 6 | } as {[key: string]: string} 7 | -------------------------------------------------------------------------------- /src/dns-lookup/i18n/index.ts: -------------------------------------------------------------------------------- 1 | import en from "./en" 2 | 3 | const lang = "en" 4 | const packs = { en } as any 5 | 6 | export default packs[lang] 7 | -------------------------------------------------------------------------------- /src/dns-lookup/index.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | DNS Lookup | DigitalOcean 19 | 20 | 21 |
22 | 23 |
24 |
25 | -------------------------------------------------------------------------------- /src/dns-lookup/mount.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import "./scss/style" 18 | 19 | import { createApp } from "vue" 20 | import App from "./templates/app.vue" 21 | import i18n from "./i18n" 22 | 23 | document.head.title = i18n.templates.app.title 24 | 25 | createApp(App).mount("#app") 26 | -------------------------------------------------------------------------------- /src/dns-lookup/plain_text_reports.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import recordsDataset from "./data/records" 18 | 19 | export const reports = new Map() 20 | 21 | const format = (data: any) => { 22 | const formattedParts: string[] = [] 23 | for (const key of Object.keys(data)) { 24 | const value = data[key] 25 | formattedParts.push(`${key[0].toUpperCase()}${key.substr(1)}: ${value}`) 26 | } 27 | return formattedParts.join(" | ") 28 | } 29 | 30 | export const generateTextReport = (allowedRecords: string[]) => { 31 | let report = "" 32 | for (const key of Object.keys(recordsDataset)) { 33 | const value = reports.get(key) 34 | if (!value) continue 35 | if (!allowedRecords.includes(key)) { 36 | continue 37 | } 38 | let textValue = " None" 39 | if (value.Answer) { 40 | textValue = "" 41 | for (const answerIndex in value.Answer) { 42 | const answer = value.Answer[answerIndex] 43 | textValue += `\n[${answerIndex}] ${format(answer)}` 44 | } 45 | } 46 | report += `${key} Records:${textValue}\n-----------\n` 47 | } 48 | return report 49 | } 50 | 51 | export const generateMdReport = (allowedRecords: string[]) => { 52 | let report = "" 53 | for (const key of Object.keys(recordsDataset)) { 54 | const value = reports.get(key) 55 | if (!value) continue 56 | if (!allowedRecords.includes(key)) { 57 | continue 58 | } 59 | let textValue = "No records present.\n" 60 | if (value.Answer) { 61 | const unformattedKeys = Object.keys(value.Answer[0]) 62 | const keys: string[] = [] 63 | const dividers: string[] = [] 64 | for (const key of unformattedKeys) { 65 | const keyFormat = `${key.substr(0, 1).toUpperCase()}${key.substr(1)}` 66 | keys.push(keyFormat) 67 | dividers.push("-".repeat(keyFormat.length + 2)) 68 | } 69 | textValue = `| ${keys.join(" | ")} |\n|${dividers.join("|")}|\n` 70 | for (const answer of value.Answer) { 71 | const parts = [] 72 | for (const part of Object.values(answer)) { 73 | parts.push(String(part)) 74 | } 75 | textValue += `| ${parts.join(" | ")} |\n` 76 | } 77 | } 78 | report += `# ${key} Records\n\n${textValue}\n` 79 | } 80 | return report 81 | } 82 | -------------------------------------------------------------------------------- /src/dns-lookup/scss/style.scss: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // stylelint-disable-next-line import-notation 18 | @import url("https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.3.0/css/flag-icon.min.css"); 19 | 20 | $header: #005ff8; 21 | 22 | @import "~do-bulma/src/style"; 23 | @import "~tippy.js/dist/tippy.css"; 24 | 25 | .do-bulma { 26 | &.landing { 27 | + .helpfulness-cont { 28 | display: none; 29 | } 30 | } 31 | 32 | h3.title.is-3 { 33 | @include font-bold; 34 | } 35 | 36 | hr { 37 | margin: ($margin * 2) 0; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/dns-lookup/standardise_records.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Defines stuff for SSHFP. 18 | const sshfpAlgorithm = [ 19 | "Reserved for future use", 20 | "RSA", 21 | "Diffie-Hellman", 22 | "ECDSA", 23 | "ED25519", 24 | ] 25 | const sshfpFingerprint = [ 26 | "Reserved for future use", 27 | "SHA-1", 28 | "SHA-256", 29 | ] 30 | 31 | // Defines stuff for TLSA. 32 | const tlsaUsage = [ 33 | "CA constraint", 34 | "Service certificate constraint", 35 | "Trust anchor assertion", 36 | "Domain-issued certificate", 37 | ] 38 | const tlsaSelector = [ 39 | "Full TLS certificate", 40 | "The SubjectPublicKeyInfo of a TLS certificate", 41 | ] 42 | const tlsaMatchingType = [ 43 | "CA constraint", 44 | "Service certificate constraint", 45 | "Trust anchor assertion", 46 | ] 47 | 48 | // Defines the main function. 49 | export default (key: string, json: any, txtRecordFragments: any, recordsJoined: any, txtSplit: RegExp) => { 50 | // Handle edgecases for different record types. 51 | if (key === "MX") { 52 | for (const record of json.Answer) { 53 | const dataSplit = record.data.split(" ") 54 | if (dataSplit.length === 2) { 55 | record.data = dataSplit[1] 56 | record.priority = Number(dataSplit[0]) 57 | } 58 | } 59 | } else if (key === "DMARC") { 60 | const newRecords = [] 61 | for (const record of json.Answer) { 62 | const recordData = record.data.startsWith("\"") ? record.data.substr(1).slice(0, -1) : record.data 63 | const dataSplit = recordData.split(";") 64 | for (const newSplit of dataSplit) { 65 | if (newSplit === "") continue 66 | 67 | newRecords.push({ 68 | name: record.name, 69 | TTL: record.TTL, 70 | data: newSplit, 71 | type: undefined, 72 | }) 73 | } 74 | } 75 | json.Answer = newRecords 76 | } else if (key === "SSHFP") { 77 | const newRecords = [] 78 | for (const record of json.Answer) { 79 | const dataSplit = record.data.split(" ") 80 | newRecords.push({ 81 | name: record.name, 82 | algorithm: sshfpAlgorithm[Number(dataSplit[0])] || "Unknown", 83 | "Fingerprint Type": sshfpFingerprint[Number(dataSplit[1])] || "Unknown", 84 | fingerprint: dataSplit[2], 85 | TTL: record.TTL, 86 | type: undefined, 87 | }) 88 | } 89 | json.Answer = newRecords 90 | } else if (key === "TLSA") { 91 | const newRecords = [] 92 | for (const record of json.Answer) { 93 | const dataSplit = record.data.split(" ") 94 | newRecords.push({ 95 | name: record.name, 96 | usage: tlsaUsage[Number(dataSplit[0])] || "Unknown", 97 | selector: tlsaSelector[Number(dataSplit[1])] || "Unknown", 98 | "Matching Type": tlsaMatchingType[Number(dataSplit[2])] || "Unknown", 99 | fingerprint: dataSplit[3], 100 | TTL: record.TTL, 101 | type: undefined, 102 | }) 103 | } 104 | json.Answer = newRecords 105 | } else if (key === "TXT") { 106 | for (const record of json.Answer) { 107 | const recordDataSplit = record.data.split(txtSplit) 108 | if (recordDataSplit.length > 1) { 109 | const consumableRecord = `${recordDataSplit[0].substr(1).startsWith("_") ? recordDataSplit[0].substr(2) : recordDataSplit[0].substr(1)}%${record.name}%${record.TTL}` 110 | if (txtRecordFragments[consumableRecord]) { 111 | (txtRecordFragments as any)[consumableRecord] += `\n${record.data}` 112 | } else { 113 | txtRecordFragments[consumableRecord] = record.data 114 | } 115 | delete json.Answer[record] 116 | continue 117 | } 118 | } 119 | } else if (key === "SRV") { 120 | for (const record of json.Answer) { 121 | const dataSplit = record.data.split(" ").reverse() 122 | record.priority = dataSplit.pop() 123 | record.weight = dataSplit.pop() 124 | record.port = dataSplit.pop() 125 | record.target = dataSplit.reverse().join(" ") 126 | delete record.data 127 | } 128 | } 129 | 130 | // Sorts them by either priority or TTL. 131 | json.Answer.sort((a: any, b: any) => { 132 | if (a.priority) { 133 | return a.priority - b.priority 134 | } 135 | if (a.TTL) { 136 | return b.TTL - a.TTL 137 | } 138 | }) 139 | 140 | // Capitalise the keys. 141 | for (const record of json.Answer) { 142 | delete record.type 143 | const recordKeys = Object.keys(record) 144 | for (const recordKeyOrigin of recordKeys) { 145 | const recordKey = `${recordKeyOrigin[0].toUpperCase()}${recordKeyOrigin.substr(1)}` 146 | if (recordsJoined[recordKey]) { 147 | recordsJoined[recordKey].push(record[recordKeyOrigin]) 148 | } else { 149 | recordsJoined[recordKey] = [record[recordKeyOrigin]] 150 | } 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/dns-lookup/table.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Gets the largest record part. 18 | export const getLargestRecordPart = (parts: string[]) => { 19 | let largestRecordPart = 0 20 | for (const part of parts) { 21 | if ((part as string).length > largestRecordPart) { 22 | largestRecordPart = (part as string).length 23 | } 24 | } 25 | return largestRecordPart 26 | } 27 | -------------------------------------------------------------------------------- /src/dns-lookup/templates/app.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 76 | 77 | 201 | -------------------------------------------------------------------------------- /src/dns-lookup/templates/clipboard_modal.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 27 | 28 | 58 | -------------------------------------------------------------------------------- /src/dns-lookup/templates/dmarc_explainer_modal.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 31 | 32 | 54 | -------------------------------------------------------------------------------- /src/dns-lookup/templates/dns_diff.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 40 | 41 | 66 | -------------------------------------------------------------------------------- /src/dns-lookup/templates/dodns.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 33 | 34 | 82 | -------------------------------------------------------------------------------- /src/dns-lookup/templates/propagation_modal.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 27 | 28 | 69 | -------------------------------------------------------------------------------- /src/dns-lookup/templates/record_base.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 37 | 38 | 90 | -------------------------------------------------------------------------------- /src/dns-lookup/templates/record_jumps.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 34 | 35 | 57 | -------------------------------------------------------------------------------- /src/dns-lookup/templates/record_selection_modal.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 36 | 37 | 102 | -------------------------------------------------------------------------------- /src/dns-lookup/templates/skeletons/dodns.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 23 | 24 | 33 | -------------------------------------------------------------------------------- /src/dns-lookup/templates/skeletons/record.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 34 | 35 | 50 | -------------------------------------------------------------------------------- /src/dns-lookup/templates/skeletons/record_jumps.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 24 | 25 | 42 | -------------------------------------------------------------------------------- /src/dns-lookup/templates/truncated_record.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 25 | 26 | 48 | -------------------------------------------------------------------------------- /src/dns-lookup/templates/whois.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 56 | 57 | 111 | -------------------------------------------------------------------------------- /src/dns-lookup/utils/geoJS.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import backoffFetch from "../../shared/utils/backoffFetch" 18 | 19 | export default async (ip: string) => { 20 | return await backoffFetch( 21 | `https://get.geojs.io/v1/ip/geo/${ip}.json`, 22 | { 23 | headers: { 24 | Accept: "application/json", 25 | }, 26 | } 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /src/dns-lookup/utils/googleDNS.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import backoffFetch from "../../shared/utils/backoffFetch" 18 | 19 | export default async (name: string, type: string) => { 20 | return await backoffFetch( 21 | `https://dns.google/resolve?name=${encodeURIComponent(name)}&type=${encodeURIComponent(type)}`, 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /src/dns-lookup/utils/whoisJS.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 18 | // @ts-ignore 19 | import * as whois from "web-whois" 20 | 21 | export default async (query: string) => { 22 | return await whois(encodeURIComponent(query), true) 23 | } 24 | -------------------------------------------------------------------------------- /src/shared/i18n/en/common.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | close: "close", 3 | searchButton: "Search", 4 | searchPlaceholder: "Enter a domain to get started", 5 | invalidDomain: "The domain you entered isn't valid.", 6 | } as {[key: string]: string} 7 | -------------------------------------------------------------------------------- /src/shared/i18n/en/index.ts: -------------------------------------------------------------------------------- 1 | import templates from "./templates" 2 | import common from "./common" 3 | 4 | export default { common, templates } as any 5 | -------------------------------------------------------------------------------- /src/shared/i18n/en/templates/error_modal.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | title: "Uh oh, something went wrong...", 3 | } as {[key: string]: string} 4 | -------------------------------------------------------------------------------- /src/shared/i18n/en/templates/footer.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | oss: `This tool is {link|open-source on GitHub|https://github.com/do-community/dns-tool} under the {link|Apache-2.0|https://github.com/do-community/dns-tool/blob/master/LICENSE} license! We welcome feedback and contributions.`, 3 | } as {[key: string]: string} 4 | -------------------------------------------------------------------------------- /src/shared/i18n/en/templates/index.ts: -------------------------------------------------------------------------------- 1 | import footer from "./footer" 2 | import errorModal from "./error_modal" 3 | 4 | export default { footer, errorModal } as {[key: string]: {[key: string]: string}} 5 | -------------------------------------------------------------------------------- /src/shared/i18n/index.ts: -------------------------------------------------------------------------------- 1 | import en from "./en" 2 | 3 | const lang = "en" 4 | const packs = { en } as any 5 | 6 | export default packs[lang] 7 | -------------------------------------------------------------------------------- /src/shared/templates/error_modal.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 22 | 23 | 47 | -------------------------------------------------------------------------------- /src/shared/templates/footer.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 20 | 21 | 37 | -------------------------------------------------------------------------------- /src/shared/templates/header.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 46 | 47 | 80 | -------------------------------------------------------------------------------- /src/shared/templates/landing.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 44 | 45 | 78 | -------------------------------------------------------------------------------- /src/shared/utils/backoffFetch.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Defines the maximum backoff. 18 | const maxBackoff = 512 19 | 20 | // Defines the abort controller. 21 | let controller: AbortController | undefined 22 | 23 | // Remakes the controller. 24 | export const remakeController = () => { 25 | if (controller) controller.abort() 26 | controller = new AbortController() 27 | } 28 | 29 | // Creates the controller initially. 30 | remakeController() 31 | 32 | // A fetch client that will behave exactly like fetch except it will backoff for 429/5XX errors. 33 | export default (input: RequestInfo, init?: RequestInit): Promise => new Promise(async (res, rej) => { 34 | // Defines the current backoff. 35 | let currentBackoff = 1 36 | 37 | // Loop until the promise is resolved/rejected or it is inactive. 38 | for (;;) { 39 | // Get the fetch response. 40 | let r: Response 41 | try { 42 | if (init === undefined) { 43 | init = { 44 | signal: controller!.signal, 45 | } 46 | } else { 47 | init.signal = controller!.signal 48 | } 49 | r = await fetch(input, init) 50 | } catch (e) { 51 | // Something really bad with the network/CORS has happened. Pass through this exception. 52 | if ((e as Error).name === "AbortError") console.info(`Request to ${input} was aborted.`) 53 | return rej(e) 54 | } 55 | 56 | // If it is not a 429/5XX, let the function deal with this. 57 | if (r.status !== 429 && Math.floor(r.status / 100) !== 5) return res(r) 58 | 59 | // Defines the amount of time to backoff. 60 | let backoff: number 61 | const createBackoffTime = () => { 62 | currentBackoff *= 2 63 | if (currentBackoff > maxBackoff) { 64 | console.warn(`Given up backing off for ${input}! Returning the response to the function.`) 65 | return res(r) 66 | } 67 | return currentBackoff 68 | } 69 | const h = r.headers.get("Retry-After") 70 | if (h) { 71 | const headerParsed = Number(h) 72 | if (isNaN(headerParsed)) { 73 | // We will try parsing as a date. 74 | try { 75 | const d = new Date(h) 76 | if (isNaN(d.getTime())) throw new Error() 77 | // Is a date! Get difference between current date and this date. 78 | backoff = Math.floor((d.getTime() - (new Date()).getTime()) / 1000) 79 | } catch (_) { 80 | // Nope! This is not a date. 81 | const b = createBackoffTime() 82 | if (!b) return 83 | backoff = b 84 | } 85 | } 86 | else { 87 | backoff = headerParsed 88 | } 89 | } else { 90 | const b = createBackoffTime() 91 | if (!b) return 92 | backoff = b 93 | } 94 | 95 | // Create the console warning. 96 | console.warn(`${input} has returned a status ${r.status}. Backing off for ${backoff} second(s).`) 97 | 98 | // Wait for the backoff period. 99 | await new Promise(x => setTimeout(x, backoff * 1000)) 100 | } 101 | }) 102 | -------------------------------------------------------------------------------- /src/shared/utils/cfDNS.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import backoffFetch from "./backoffFetch" 18 | 19 | export default async (name: string, type: string) => { 20 | return await backoffFetch( 21 | `https://cloudflare-dns.com/dns-query?name=${encodeURIComponent(name)}&type=${encodeURIComponent(type)}`, 22 | { 23 | headers: { 24 | Accept: "application/dns-json", 25 | }, 26 | } 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /src/shared/utils/sanitize.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | export default (text: string) => { 18 | const map = [ 19 | [//g, ">"], 21 | [/'/g, "'"], 22 | [/"/g, """], 23 | ] 24 | map.forEach(item => { 25 | text = text.replace(item[0], item[1] as string) 26 | }) 27 | return text 28 | } 29 | -------------------------------------------------------------------------------- /src/shared/utils/skeletonStyle.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | export const getRandomInt = (min: number, max: number) => { 18 | min = Math.ceil(min) 19 | max = Math.floor(max) 20 | return Math.floor(Math.random() * (max - min + 1)) + min 21 | } 22 | 23 | export const getSkeletonStyle = (min: number, max: number, size = 1) => { 24 | return { 25 | height: `${size}em`, 26 | margin: `${size * .35}em 0`, 27 | background: `hsl(0, 0%, ${getRandomInt(80, 90)}%)`, 28 | borderRadius: `${size * .5}em`, 29 | width: `${getRandomInt(min, max)}px`, 30 | opacity: `${getRandomInt(20, 30)/100}`, 31 | animationDelay: `${getRandomInt(0, 600)}ms`, 32 | animationDuration: `${getRandomInt(900, 1200)}ms`, 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/shared/utils/validateDomain.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import cfDNS from "./cfDNS" 18 | import sanitize from "./sanitize" 19 | import i18n from "../i18n" 20 | 21 | const stripHttps = /(https*:\/\/)*(.+)*/ 22 | const isHostname = /.*\.[a-z]+/ 23 | 24 | export default async (name: string) => { 25 | // Strip http(s) from the input 26 | const regexpExec = stripHttps.exec(name.toLowerCase()) 27 | if (regexpExec === null) return [null, i18n.common.invalidDomain] 28 | 29 | // Attempt to determine the hostname 30 | const text = regexpExec[2] ? regexpExec[2].replace(/\//g, "") : "" 31 | if (!text.match(isHostname)) return [null, i18n.common.invalidDomain] 32 | 33 | // Talk to Cloudflare to validate the domain exists 34 | const domainLookup = await cfDNS(text, "NULL") 35 | let json 36 | try { 37 | json = await domainLookup.json() 38 | } catch { 39 | // Sometimes Cloudflare's DNS sends invalid JSON in the event that it is invalid. 40 | // That has happened here. 41 | return [null, i18n.common.invalidDomain] 42 | } 43 | if (json.Status !== 0) { 44 | let msg = i18n.common.invalidDomain 45 | if (json.Comment) msg += `

${sanitize(json.Comment.toString())}` 46 | return [null, msg] 47 | } 48 | 49 | // It's legit 50 | return [text, null] 51 | } 52 | -------------------------------------------------------------------------------- /src/spf-explainer/data/explanations.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import i18n from "../i18n" 18 | 19 | const explanationMap = new Map() 20 | 21 | explanationMap.set(/^v=spf1$/, i18n.data.explanations.v) 22 | explanationMap.set([/^include:(.+)$/, true], i18n.data.explanations.include) 23 | explanationMap.set(/^ip4:(.+)$/, i18n.data.explanations.ip4) 24 | explanationMap.set(/^ip6:(.+)$/, i18n.data.explanations.ip6) 25 | explanationMap.set([/^redirect[:=](.+)$/, true], i18n.data.explanations.redirect) 26 | explanationMap.set(/^exists:%\{i\}\.(.+)$/, i18n.data.explanations.exists) 27 | explanationMap.set(/^ptr:(.+)$/, i18n.data.explanations.ptrNotRoot) 28 | explanationMap.set(/^ptr$/, i18n.data.explanations.ptrRoot) 29 | explanationMap.set([/^a$/, false, "A"], i18n.data.explanations.aRoot) 30 | explanationMap.set([/^a:(.+)$/, false, "A"], i18n.data.explanations.aNotRoot) 31 | explanationMap.set([/^mx$/, false, "MX"], i18n.data.explanations.mxRoot) 32 | explanationMap.set([/^mx:(.+)$/, false, "MX"], i18n.data.explanations.mxNotRoot) 33 | explanationMap.set(/^~all$/, i18n.data.explanations.softFail) 34 | explanationMap.set(/^-all$/, i18n.data.explanations.hardFail) 35 | explanationMap.set(/^\+all$/, i18n.data.explanations.noFail) 36 | explanationMap.set(/^\?all$/, i18n.data.explanations.neutral) 37 | 38 | export default explanationMap 39 | -------------------------------------------------------------------------------- /src/spf-explainer/data/long_descriptions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import i18n from "../i18n" 18 | 19 | export default { 20 | a: i18n.data.longDescriptions.a, 21 | v: i18n.data.longDescriptions.v, 22 | mx: i18n.data.longDescriptions.mx, 23 | include: i18n.data.longDescriptions.include, 24 | ptr: i18n.data.longDescriptions.ptr, 25 | ip4: i18n.data.longDescriptions.ip4, 26 | ip6: i18n.data.longDescriptions.ip6, 27 | } 28 | -------------------------------------------------------------------------------- /src/spf-explainer/i18n/en/data/explanations.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | const ptrStart = "PTR records are not a good way of looking up an IP address in a SPF record since they are slow and sometimes ignored. This means that it will check the PTR record of " 18 | const aStart = "This means that any IP address that is a A record of " 19 | const mxStart = "This means that any IP address that is a MX record of " 20 | 21 | export default { 22 | v: "This is the SPF record version.", 23 | unknown: "This part is unknown.", 24 | include: `This includes all SPF records at {1}:`, 25 | ip4: `This means that IPv4 addresses in the range??s?? of {R} are allowed to send e-mails from any domains using this record.`, 26 | ip6: `This means that IPv6 addresses in the range??s?? of {R} are allowed to send e-mails from any domains using this record.`, 27 | redirect: `This is used when 2 websites have the exact same infrastructure and the administrator wants all requests directed to one domain. In this case, that would be {1}.`, 28 | exists: `This is used to check if a IP address is allowed to send e-mails by checking if there is a NULL record at <IP address>.{1}.`, 29 | ptrRoot: ptrStart + "this domain to see if the IP address is allowed to send e-mails.", 30 | ptrNotRoot: ptrStart + `{1} to see if the IP address is allowed to send e-mails.`, 31 | aRoot: aStart + "this domain is allowed to send e-mails.", 32 | aNotRoot: aStart + `{1} is allowed to send e-mails.`, 33 | mxRoot: mxStart + "this domain is allowed to send e-mails.", 34 | mxNotRoot: mxStart + `{1} is allowed to send e-mails.`, 35 | hardFail: "This means only the IP's here or in inherited records are allowed to send e-mails.", 36 | softFail: "If the IP address sending the e-mail is not in this record, the e-mail should be accepted but marked with a warning. This means that the e-mail will likely be marked as spam.", 37 | noFail: "This tag allows any server to send e-mails from your domain.", 38 | neutral: "This will neither pass or fail emails, just act like the record is not there. Therefore, this doesn't affect the ability to send.", 39 | } as {[key: string]: string} 40 | -------------------------------------------------------------------------------- /src/spf-explainer/i18n/en/data/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import explanations from "./explanations" 18 | import longDescriptions from "./long_descriptions" 19 | 20 | export default { explanations, longDescriptions } as {[key: string]: {[key: string]: string}} 21 | -------------------------------------------------------------------------------- /src/spf-explainer/i18n/en/data/long_descriptions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | export default { 18 | ip4: "This means that any IPv4 addresses specified in this mechanism will be accepted and not marked as spam.", 19 | ip6: "This means that any IPv6 addresses specified in this mechanism will be accepted and not marked as spam.", 20 | a: "This means that any A records specified in this mechanism (or the A records for the current record if none are specified) will be accepted and not marked as spam.", 21 | v: `This just defines the record version. This is commonly just spf1.`, 22 | mx: "This means that any MX records specified in this mechanism (or the MX records for the current record if none are specified) will be accepted and not marked as spam.", 23 | include: "This will include the records at the hostname given. This can be recursive so it can go down multiple levels. Anytime that a mechanism is used that would be bound to the domain in the included record, it will be bound to the domain of the included record.", 24 | ptr: "The PTR record is deprecated since it is slow and relies on the name servers inbetween the domains to work properly.", 25 | } as {[key: string]: string} 26 | -------------------------------------------------------------------------------- /src/spf-explainer/i18n/en/index.ts: -------------------------------------------------------------------------------- 1 | import templates from "./templates" 2 | import data from "./data" 3 | 4 | export default { templates, data } as any 5 | -------------------------------------------------------------------------------- /src/spf-explainer/i18n/en/templates/app.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | title: "SPF Explainer", 3 | description: `A tool that explains a domain’s SPF records. Search a domain and either explore its records or evaluate an IP for mail sending.`, 4 | eval: "Evaluate", 5 | whatDoTheyDo: "What are all the SPF mechanisms and what do they all do?", 6 | fetchError: "An error occurred whilst attempting to fetch the SPF records.", 7 | } as {[key: string]: string} 8 | -------------------------------------------------------------------------------- /src/spf-explainer/i18n/en/templates/eval_notif.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | title: "Evaluation Result", 3 | goThrough: "E-mails will be able to send from {{IP}} successfully, as this domain.", 4 | ignored: "E-mails sent from {{IP}}, as this domain, should be ignored.", 5 | softFail: "E-mails sent from {{IP}}, as this domain, will be accepted but will likely be marked as spam since they will be flagged.", 6 | neutral: "The SPF record will neither pass or fail emails, just act as if the record is not there. Therefore, this won't affect the ability to send.", 7 | } as {[key: string]: string} 8 | -------------------------------------------------------------------------------- /src/spf-explainer/i18n/en/templates/index.ts: -------------------------------------------------------------------------------- 1 | import app from "./app" 2 | import spfBase from "./spf_base" 3 | import partExplanation from "./part_explanation" 4 | import noSpfRecords from "./no_spf_records" 5 | import evalNotif from "./eval_notif" 6 | 7 | export default { app, spfBase, partExplanation, noSpfRecords, evalNotif } as {[key: string]: {[key: string]: string}} 8 | -------------------------------------------------------------------------------- /src/spf-explainer/i18n/en/templates/no_spf_records.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | title: "No SPF Records Found", 3 | description: "No SPF records were found on the domain specified. This means that e-mails will only be allowed from the specified MX records on this domain.", 4 | } as {[key: string]: string} 5 | -------------------------------------------------------------------------------- /src/spf-explainer/i18n/en/templates/part_explanation.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | mechanism: "SPF Mechanism ($)", 3 | allMechanisms: "All SPF Mechanisms", 4 | } as {[key: string]: string} 5 | -------------------------------------------------------------------------------- /src/spf-explainer/i18n/en/templates/spf_base.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | results: "SPF records for {hostname}:", 3 | } as {[key: string]: string} 4 | -------------------------------------------------------------------------------- /src/spf-explainer/i18n/index.ts: -------------------------------------------------------------------------------- 1 | import en from "./en" 2 | 3 | const lang = "en" 4 | const packs = { en } as any 5 | 6 | export default packs[lang] 7 | -------------------------------------------------------------------------------- /src/spf-explainer/index.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | SPF Explainer | DigitalOcean 19 | 20 | 21 |

22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/spf-explainer/mount.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import "./scss/style" 18 | 19 | import { createApp } from "vue" 20 | import App from "./templates/app.vue" 21 | import i18n from "./i18n" 22 | 23 | document.head.title = i18n.templates.app.title 24 | 25 | createApp(App).mount("#app") 26 | -------------------------------------------------------------------------------- /src/spf-explainer/scss/style.scss: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | $header: #0071fe; 18 | 19 | @import "~do-bulma/src/style"; 20 | 21 | .do-bulma { 22 | &.landing { 23 | + .helpfulness-cont { 24 | display: none; 25 | } 26 | } 27 | 28 | .main.container { 29 | h5.title.is-5 { 30 | line-height: 1.35; 31 | } 32 | 33 | .record-group { 34 | padding-left: $margin * 2; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/spf-explainer/templates/all_part_explanations.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 24 | 25 | 48 | -------------------------------------------------------------------------------- /src/spf-explainer/templates/app.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 87 | 88 | 212 | -------------------------------------------------------------------------------- /src/spf-explainer/templates/eval_notif.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 28 | 29 | 63 | -------------------------------------------------------------------------------- /src/spf-explainer/templates/no_spf_records.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 22 | 23 | 47 | -------------------------------------------------------------------------------- /src/spf-explainer/templates/part_explanation.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 22 | 23 | 48 | -------------------------------------------------------------------------------- /src/spf-explainer/templates/skeletons/record.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 50 | 51 | 65 | -------------------------------------------------------------------------------- /src/spf-explainer/templates/skeletons/small_spf_skeleton.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 31 | 32 | 75 | -------------------------------------------------------------------------------- /src/spf-explainer/templates/spf.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 65 | 66 | 276 | -------------------------------------------------------------------------------- /src/spf-explainer/templates/spf_base.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 35 | 36 | 73 | -------------------------------------------------------------------------------- /src/spf-explainer/utils/line_generator.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Defines the main gap size. 18 | const mainGapSize = 8 19 | 20 | // The line multiplier. 21 | const lineMultiplier = 0.05 22 | 23 | // Defines the line color. 24 | const lineColor = "#000000" 25 | 26 | // Check if 2 numbers are in the range of x (a/b are compared). 27 | const within = (x: number, a: number, b: number) => { 28 | let larger, smaller 29 | if (a > b) { 30 | larger = a 31 | smaller = b 32 | } else { 33 | larger = b 34 | smaller = a 35 | } 36 | return x >= (larger - smaller) 37 | } 38 | 39 | // Adds the offset. 40 | const addOffset = (y: number) => { 41 | return y + window.pageYOffset 42 | } 43 | 44 | // Renders a claw for the final part. It should look a bit like this: 45 | // |-- (line 20px) 46 | // | 47 | // | (line is height of object) 48 | // | 49 | // |-- (line 20px) 50 | const drawClaw = (x: number, top: number, bottom: number, generator: LineGenerator, svg: SVGSVGElement) => { 51 | // Draw the core middle line. 52 | generator.drawLine(svg, lineColor, x, x, top, bottom) 53 | 54 | // Draw the claws. 55 | generator.drawLine(svg, lineColor, x, x + 20, top, top) 56 | generator.drawLine(svg, lineColor, x, x + 20, bottom, bottom) 57 | } 58 | 59 | // A lock. Only one line generator can run at once. 60 | // While initialising a line generator does block, it's also doing a bunch of things where there is a opportunity for another function to be ran inbetween, causing a race condition. 61 | let lock = false 62 | 63 | // The main line generator class. 64 | export default class LineGenerator { 65 | public a!: HTMLElement 66 | public b!: HTMLElement 67 | public lineDiv!: HTMLElement 68 | public destroyed: boolean 69 | public downX: number | undefined 70 | public visible: boolean 71 | 72 | public constructor(a: HTMLElement, b: HTMLElement) { 73 | this.a = a 74 | this.b = b 75 | this.downX = Math.floor(window.innerWidth * lineMultiplier) 76 | this.destroyed = false 77 | this.visible = false 78 | const f = () => { 79 | if (!lock) this.setup() 80 | else setTimeout(f, 5) 81 | } 82 | f() 83 | } 84 | 85 | private _createContainer() { 86 | const div = document.createElement("div") 87 | div.style.position = "absolute" 88 | div.style.top = "0" 89 | div.style.left = "0" 90 | div.style.overflow = "show" 91 | div.style.pointerEvents = "none" 92 | document.body.appendChild(div) 93 | return div 94 | } 95 | 96 | private _createInnerSvg(container: HTMLDivElement) { 97 | const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg") 98 | svg.setAttribute("overflow", "visible") 99 | container.appendChild(svg) 100 | return svg 101 | } 102 | 103 | public drawLine(svg: SVGSVGElement, stroke: string, x1: number, x2: number, y1: number, y2: number) { 104 | const line = document.createElementNS("http://www.w3.org/2000/svg", "line") 105 | line.setAttribute("stroke", stroke) 106 | line.setAttribute("stroke-width", "1") 107 | line.setAttribute("x1", x1.toString()) 108 | line.setAttribute("x2", x2.toString()) 109 | line.setAttribute("y1", addOffset(y1).toString()) 110 | line.setAttribute("y2", addOffset(y2).toString()) 111 | svg.appendChild(line) 112 | } 113 | 114 | public setup() { 115 | // Locks the initialiser. 116 | lock = true 117 | 118 | // Gets the positions of A/B. 119 | const aRect = this.a.getBoundingClientRect() as DOMRect 120 | const bRect = this.b.getBoundingClientRect() 121 | const aTop = aRect.top 122 | const bTop = bRect.top + Math.floor(bRect.height / 2) 123 | 124 | // Makes the div container. 125 | const container = this._createContainer() 126 | this.lineDiv = container 127 | 128 | // Makes the inner SVG. 129 | const svg = this._createInnerSvg(container) 130 | 131 | // Gets the left side of B. 132 | const bLeft = bRect.left - mainGapSize 133 | 134 | try { 135 | // If bLeft is less than 0, return. 136 | if (0 > bLeft) return 137 | 138 | // Gets the best pathway to point A. 139 | const aParentRect = this.a.parentElement!.getBoundingClientRect() as DOMRect 140 | let aBestY = aTop - 4 141 | let aTouch = aTop 142 | let top = true 143 | if (!within(3, aParentRect.y, aRect.y) || aRect.width > 1000) { 144 | aTouch = aRect.bottom 145 | aBestY = aRect.bottom + 4 146 | top = false 147 | } 148 | 149 | // The best X for A. 150 | const aBestX = aRect.x + (aRect.width > 1000 ? 20 : Math.floor(aRect.width / 2)) 151 | 152 | // Draw a line from "aBestY" to "aTouch". 153 | this.drawLine(svg, lineColor, aBestX, aBestX, top ? aBestY : aTouch, top ? aTouch : aBestY) 154 | 155 | // Draw a line from "this.downX" to "aBestX" using "aBestY". 156 | this.drawLine(svg, lineColor, this.downX!, aBestX, aBestY, aBestY) 157 | 158 | // Draws the claw. 159 | drawClaw(bLeft, bRect.top - mainGapSize, bRect.bottom + mainGapSize, this, svg) 160 | 161 | // bLeft being greater than bTop means there isn't enough room. Return and destroy. 162 | if (this.downX! > bLeft) return this.destroy() 163 | 164 | // Draw a line from "bTop" to "bLeft". 165 | this.drawLine(svg, lineColor, this.downX!, bLeft, bTop, bTop) 166 | 167 | // Draw the down line. 168 | this.drawLine(svg, lineColor, this.downX!, this.downX!, aBestY, bTop) 169 | } finally { 170 | // Unlocks the generator. 171 | lock = false 172 | } 173 | } 174 | 175 | public destroy() { 176 | if (this.destroyed) return 177 | if (this.lineDiv) this.lineDiv.remove() 178 | this.destroyed = true 179 | this.downX = undefined 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/spf-explainer/utils/line_spawn.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Imports the line generator. 18 | import LineGenerator from "./line_generator" 19 | 20 | // Defines the active line. 21 | let activeLine: LineGenerator | undefined 22 | 23 | // Freezes line spawing. 24 | let freezeSpawning = false 25 | export const setFreezeSpawning = (t: boolean) => { freezeSpawning = t } 26 | 27 | // Destroys the line. 28 | const destroy = () => { 29 | if (activeLine) activeLine.destroy() 30 | activeLine = undefined 31 | } 32 | 33 | // Spawns the line. 34 | export const spawnLine = (lineRef: HTMLElement[] | undefined) => { 35 | if (freezeSpawning) return 36 | destroy() 37 | if (lineRef) activeLine = new LineGenerator(lineRef[0], lineRef[1]) 38 | } 39 | 40 | // Despawn the line on resize. 41 | window.addEventListener("resize", destroy) 42 | -------------------------------------------------------------------------------- /src/spf-explainer/utils/spf_records.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import cfDNS from "../../shared/utils/cfDNS" 18 | 19 | const getSPFRecords = async (domain: string): Promise => { 20 | const res = await cfDNS(domain, "TXT") 21 | if (!res.ok) throw new Error("Failed to fetch SPF records") 22 | 23 | const json = await res.json() 24 | if (json.Status !== 0 || !json.Answer) throw new Error("Failed to fetch SPF records") 25 | 26 | const records = [] 27 | for (const answer of json.Answer) { 28 | // If the answer starts with a quote, we need to extract the actual answer 29 | if (answer.data.startsWith("\"")) { 30 | // Extract all the quoted sections in the string 31 | const matches = answer.data.matchAll(/"([^"]*)"/g) 32 | if (matches) answer.data = [...matches].map(match => match[1]).join("") 33 | } 34 | 35 | if (answer.data.startsWith("v=spf1")) records.push(answer.data) 36 | } 37 | 38 | return records 39 | } 40 | 41 | export default getSPFRecords 42 | -------------------------------------------------------------------------------- /src/spf-explainer/utils/spf_sandbox.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 DigitalOcean 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import CIDRMatcher from "cidr-matcher" 18 | 19 | // Defines a SPF rule. 20 | class SPFRule { 21 | // Defines public values in the class. 22 | public rule: boolean | undefined 23 | public matcher: CIDRMatcher 24 | 25 | // Constructs the rule 26 | // Ensures that any bare IP addresses are converted to CIDR ranges. 27 | public constructor(rule: boolean | undefined, ips: string[]) { 28 | this.rule = rule 29 | this.matcher = new CIDRMatcher(ips.map(ip => ip.includes("/") ? ip : (/^\d+\.\d+\.\d+\.\d+$/.test(ip) ? `${ip}/32` : `${ip}/128`))) 30 | } 31 | } 32 | 33 | // Defines the SPF sandbox. 34 | class SPFSandbox { 35 | // Defines the rules and listeners. 36 | private _rules: SPFRule[] 37 | private _listeners: (() => void)[] 38 | 39 | // Constructs the sandbox. 40 | public constructor() { 41 | this._rules = [] 42 | this._listeners = [] 43 | } 44 | 45 | // Wipes all current rules and listeners. 46 | public wipe() { 47 | this._rules = [] 48 | this._listeners = [] 49 | } 50 | 51 | // Imports a SPF string. 52 | // THIS DOES NOT IMPORT INCLUDES FROM THE STRING! THEY ARE EXPECTED TO BE INCLUDED! 53 | public import(spf: Record, action: boolean | undefined) { 54 | const ips = new Set() 55 | for (const v4 of spf.ip4 || []) ips.add(v4[0][1]) 56 | for (const v6 of spf.ip6 || []) ips.add(v6[0][1]) 57 | this._rules.push(new SPFRule(action, [...ips])) 58 | this._listeners.forEach(listener => listener()) 59 | } 60 | 61 | // Evals the IP address/range given. 62 | // Returns null if the IP is allowed, true for a hard fail, false for a soft fail, undefined for a neutral fail. 63 | public eval(ip: string) { 64 | let hardFail: undefined | boolean 65 | 66 | for (const rule of this._rules) { 67 | if (rule.matcher.contains(ip)) return null 68 | hardFail = rule.rule 69 | } 70 | 71 | return hardFail 72 | } 73 | 74 | // Listen for imports. 75 | public listen(listener: () => void) { 76 | this._listeners.push(listener) 77 | } 78 | 79 | // Defines if the sandbox is empty. 80 | public empty() { 81 | return this._rules.length === 0 82 | } 83 | } 84 | 85 | // Exports the sandbox. 86 | export default new SPFSandbox() 87 | -------------------------------------------------------------------------------- /src/static/README.md: -------------------------------------------------------------------------------- 1 | # Community DNS Tools 2 | 3 | A set of browser-based DNS tools for DigitalOcean Community. 4 | 5 | --- 6 | 7 | 8 | 9 | 10 | 11 | ## DNS Lookup 12 | 13 | A simple browser-based tool to perform DNS lookups. Type a domain, search, and instantly get results. 14 | 15 | ### [➡️ Use now](https://www.digitalocean.com/community/tools/dns) 16 | 17 | --- 18 | 19 | 20 | 21 | 22 | 23 | ## SPF Explainer 24 | 25 | A tool that explains a domain's SPF records. Search a domain and either explore its records or evaluate an IP for mail sending. 26 | 27 | ### [➡️ Use now](https://www.digitalocean.com/community/tools/spf) 28 | -------------------------------------------------------------------------------- /src/static/dns-lookup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/do-community/dns-tool/abf1f57932cf0d064d7bd61cbe667210d9f1be69/src/static/dns-lookup.png -------------------------------------------------------------------------------- /src/static/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / 3 | -------------------------------------------------------------------------------- /src/static/spf-explainer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/do-community/dns-tool/abf1f57932cf0d064d7bd61cbe667210d9f1be69/src/static/spf-explainer.png -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2018", 5 | "allowJs": true, 6 | "esModuleInterop": true, 7 | "noImplicitAny": true, 8 | "noEmit": false, 9 | "strict": true 10 | }, 11 | "include": [ 12 | "src/**/*" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /user-flow-dns-lookup.md: -------------------------------------------------------------------------------- 1 | # User Flow (DNS Lookup) 2 | 3 | The goal for this website is to allow users to lookup DNS records. For example, if a user wants to lookup a A record to check that users can access their droplet from their domain properly, they would go to the DNS lookup page, search the record, get the record and close the page. If the user wanted to share a record type with other people, they could click the link button next to the record type and then copy the URL to their current page. When someone was linked it, they would be pointed to that exact record type for that domain. 4 | 5 | When you visit the page, you should be directed to the page with the textbox active if the domain is not part of the URL query argument. This is so that you can easily just start typing: 6 | 7 | ![](https://i.imgur.com/ab9kZK7.png) 8 | 9 | It should be clear to the user that it will only search the (sub-)domain they type in. It will not search other sub-domains. This is by design due to how DNS servers return results. 10 | 11 | Pressing the "Search DNS Records" button or hitting ENTER in the text box will call a JavaScript function which will allow for the searching of records. If the user has a domain as part of the URL query, the textbox should not be focused, the textbox should be populated with the HTTP query and it should act like the "Search DNS Records" button has been clicked. 12 | 13 | Currently, before the user searches, there is a skeleton of what the page will look like. This gets replaced with the actual values after a search: 14 | 15 | ![](https://freethewump.us/wwmwyktqe.png) 16 | 17 | When searching, the button should go into a loading state. This makes it obvious to the user that the page is loading: 18 | 19 | ![](https://freethewump.us/mkioropyv.png) 20 | 21 | If the domain is invalid, a error of some description should be displayed and stop the operation. This should be very visible for the user, for example by using an alert prompt: 22 | 23 | ![](https://i.imgur.com/vuu5Ch2.png) 24 | 25 | When the user searches, results should feel instant. Since we are using Cloudflare DNS, the only bottleneck here should be the speed of the connection which the user is on. For parts which can be slower such as WHOIS, we should have it say `Loading ...` while it loads the information: 26 | 27 | ![](https://i.imgur.com/eFtiw5R.png) 28 | 29 | This should **NOT** hold up the loading of the page. 30 | 31 | The page should say near the top if the user is using DigitalOcean DNS. We could also use this space to show FAQ's. 32 | 33 | The page will try and get the following records: 34 | - A records 35 | - AAAA records 36 | - TXT records 37 | - MX records 38 | - NS records 39 | - DMARC records 40 | - CAA records 41 | - SRV records 42 | 43 | At the top of the page, it should allow users to jump to different parts. This means that users do not need to scroll: 44 | 45 | ![](https://freethewump.us/qagkyhgbn.png) 46 | 47 | They should all be in a very standardised form. For IP addresses, we should show the owner underneath with a obvious hyperlink. Clicking the link expands/collapses more information about the host: 48 | 49 | ![](https://i.imgur.com/9l0kk9w.png) 50 | 51 | ![](https://i.imgur.com/PyGiRqo.png) 52 | 53 | Hovering over the flag should return more information about the country and town (if applicable): 54 | 55 | ![](https://i.imgur.com/67QiiHt.png) 56 | 57 | For hostnames, we should try and resolve them to IP addresses and then do the above. 58 | 59 | For TXT records, they should be split in a reasonable place with a `Show more`/`Show less` hyperlink to expand/collapse it. Additionally, common TXT records should have a description under them and be grouped by the Name/TTL: 60 | 61 | ![](https://i.imgur.com/GIzLWNf.png) 62 | 63 | There is a modal related to DNS propagation (located in `./src/templates/propagation_modal.vue`). This has a toggle function which should be executable by a link/button clickable by the end user: 64 | 65 | ![](https://freethewump.us/jrsbzmngj.png) 66 | ![](https://freethewump.us/ovlqpsbfb.png) 67 | 68 | At the bottom, it should credit the API's we use. There should also be a "Back to Top" button so that the user can easily jump to the top. 69 | 70 | ![](https://freethewump.us/xmcmwaqsx.png) 71 | --------------------------------------------------------------------------------