├── .dockerignore ├── .editorconfig ├── .eslintrc.js ├── .github ├── dependabot.yml └── workflows │ ├── app-deployement.yml │ └── dockerhub-push.yml ├── .gitignore ├── .husky ├── pre-commit └── pre-push ├── .log ├── ti-15454.log ├── ti-18421.log ├── ti-19611.log ├── ti-21177.log ├── ti-21349.log ├── ti-22011.log ├── ti-22484.log ├── ti-22812.log ├── ti-78950.log ├── ti-91862.log ├── ti-93985.log └── tsserver.log ├── .vscode └── launch.json ├── CNAME ├── Dockerfile ├── LICENSE.md ├── README.md ├── SECURITY.md ├── craco.config.js ├── package.json ├── public ├── favicon.ico └── index.html ├── src ├── assets │ └── svg │ │ ├── arrow_right.svg │ │ ├── chevron_up.svg │ │ ├── clear.svg │ │ ├── close.svg │ │ ├── copy.svg │ │ ├── cross.svg │ │ ├── delete.svg │ │ ├── download.svg │ │ ├── filter.svg │ │ ├── filter_selected.svg │ │ ├── loader.svg │ │ ├── logo.svg │ │ ├── plus.svg │ │ ├── refresh.svg │ │ ├── side_by_side.svg │ │ ├── switch.svg │ │ ├── theme_blue_button.svg │ │ ├── theme_dark_button.svg │ │ ├── theme_synth_button.svg │ │ └── up_down.svg ├── breakpoints.scss ├── components │ ├── appLoader │ │ ├── index.tsx │ │ └── styles.scss │ ├── customHost │ │ ├── index.tsx │ │ └── styles.scss │ ├── detailedRequest │ │ ├── index.tsx │ │ └── styles.scss │ ├── header │ │ ├── index.tsx │ │ └── styles.scss │ ├── notificationsPopup │ │ ├── index.tsx │ │ └── styles.scss │ ├── requestsTable │ │ ├── index.tsx │ │ └── styles.scss │ ├── resetPopup │ │ ├── index.tsx │ │ └── styles.scss │ ├── tabSwitcher │ │ ├── index.tsx │ │ └── styles.scss │ └── toggleBtn │ │ ├── index.tsx │ │ └── styles.scss ├── globalStyles.js ├── helpers │ ├── fallback-loaders.js │ └── styles.scss ├── index.tsx ├── lib │ ├── index.test.ts │ ├── index.ts │ ├── localStorage │ │ ├── index.test.ts │ │ └── index.ts │ ├── notify │ │ └── index.ts │ ├── types │ │ ├── data │ │ │ └── index.ts │ │ ├── discord.ts │ │ ├── filter.ts │ │ ├── id.ts │ │ ├── protocol │ │ │ └── index.ts │ │ ├── slack.ts │ │ ├── storedData.ts │ │ ├── tab.ts │ │ ├── telegram.ts │ │ └── view.ts │ └── utils │ │ ├── index.test.ts │ │ └── index.ts ├── pages │ ├── homePage │ │ ├── index.tsx │ │ ├── requestDetailsWrapper.tsx │ │ ├── requestsTableWrapper.tsx │ │ └── styles.scss │ └── termsPage │ │ └── index.tsx ├── react-app-env.d.ts ├── reportWebVitals.ts ├── styles.scss ├── theme.ts ├── vitals.js └── xid-js.d.ts ├── tailwind.config.js ├── tsconfig.json └── yarn.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /.pnp 3 | .pnp.js 4 | 5 | /coverage 6 | 7 | /build 8 | 9 | .DS_Store 10 | .env.local 11 | .env.development.local 12 | .env.test.local 13 | .env.production.local 14 | 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | .log 19 | .log/ 20 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 2 9 | trim_trailing_whitespace = true 10 | max_line_length = 100 11 | 12 | [*.md] 13 | trim_trailing_whitespace = falsȩ̧ -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | es2021: true, 6 | }, 7 | settings: { 8 | "import/resolver": { 9 | node: { 10 | paths: ["src"], 11 | extensions: [".js", ".ts", ".tsx", ".d.ts"], 12 | }, 13 | }, 14 | }, 15 | extends: ["plugin:react/recommended", "airbnb", "prettier"], 16 | parser: "@typescript-eslint/parser", 17 | parserOptions: { 18 | ecmaFeatures: { 19 | jsx: true, 20 | }, 21 | ecmaVersion: 12, 22 | sourceType: "module", 23 | }, 24 | plugins: ["react", "@typescript-eslint", "fp-ts"], 25 | rules: { 26 | "react/jsx-filename-extension": [2, { extensions: [".js", ".jsx", ".ts", ".tsx"] }], 27 | camelcase: ["error", { allow: ["aes_key", "up_and_down", "side_by_side"] }], 28 | "react/jsx-props-no-spreading": "off", 29 | "no-use-before-define": "off", 30 | "@typescript-eslint/no-use-before-define": ["error"], 31 | "import/extensions": "off", 32 | "no-redeclare": "off", // Needed for type declarations 33 | "react/prop-types": "off", 34 | "no-undef": "off", 35 | "no-unused-vars": "off", 36 | "@typescript-eslint/no-unused-vars": "error", 37 | "react/require-default-props": "off", 38 | "no-nested-ternary": "off", 39 | "no-underscore-dangle": "off", 40 | "fp-ts/no-lib-imports": "error", 41 | "import/order": [ 42 | "error", 43 | { 44 | groups: ["builtin", "external", "internal"], 45 | pathGroups: [ 46 | { 47 | pattern: "react", 48 | group: "external", 49 | position: "before", 50 | }, 51 | ], 52 | pathGroupsExcludedImportTypes: ["react"], 53 | "newlines-between": "always", 54 | alphabetize: { 55 | order: "asc", 56 | caseInsensitive: true, 57 | }, 58 | }, 59 | ], 60 | }, 61 | }; 62 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | 9 | # Maintain dependencies for GitHub Actions 10 | - package-ecosystem: "github-actions" 11 | directory: "/" 12 | schedule: 13 | interval: "weekly" 14 | target-branch: "dev" 15 | commit-message: 16 | prefix: "chore" 17 | include: "scope" 18 | 19 | # Maintain dependencies for npm modules 20 | - package-ecosystem: "npm" 21 | directory: "/" 22 | schedule: 23 | interval: "weekly" 24 | target-branch: "dev" 25 | commit-message: 26 | prefix: "chore" 27 | include: "scope" 28 | 29 | # Maintain dependencies for docker 30 | - package-ecosystem: "docker" 31 | directory: "/" 32 | schedule: 33 | interval: "weekly" 34 | target-branch: "dev" 35 | commit-message: 36 | prefix: "chore" 37 | include: "scope" -------------------------------------------------------------------------------- /.github/workflows/app-deployement.yml: -------------------------------------------------------------------------------- 1 | name: React app deployement 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | workflow_dispatch: 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | env: 13 | CI: false 14 | steps: 15 | - uses: actions/checkout@v2 16 | with: 17 | fetch-depth: 0 18 | token: ${{ secrets.GITHUB_TOKEN }} 19 | 20 | - run : git config --global user.name github-actions 21 | - run : git config --global user.email github-actions@github.com 22 | - run : yarn install 23 | - run : yarn build 24 | - run : cp CNAME build 25 | - run : git --work-tree build add --all && git commit -m "Automatic Deploy action run by github-actions" 26 | - run : git push origin HEAD:gh-pages --force -------------------------------------------------------------------------------- /.github/workflows/dockerhub-push.yml: -------------------------------------------------------------------------------- 1 | name: 🌥 Docker Push 2 | 3 | on: 4 | workflow_run: 5 | workflows: ["React app deployement"] 6 | types: 7 | - completed 8 | workflow_dispatch: 9 | 10 | jobs: 11 | docker: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Git Checkout 15 | uses: actions/checkout@v3 16 | 17 | - name: Get Github tag 18 | id: meta 19 | run: | 20 | echo "::set-output name=tag::$(curl --silent "https://api.github.com/repos/projectdiscovery/interactsh-web/releases/latest" | jq -r .tag_name)" 21 | - name: Set up QEMU 22 | uses: docker/setup-qemu-action@v1 23 | 24 | - name: Set up Docker Buildx 25 | uses: docker/setup-buildx-action@v1 26 | 27 | - name: Login to DockerHub 28 | uses: docker/login-action@v1 29 | with: 30 | username: ${{ secrets.DOCKER_USERNAME }} 31 | password: ${{ secrets.DOCKER_TOKEN }} 32 | 33 | - name: Build and push 34 | uses: docker/build-push-action@v2 35 | with: 36 | context: . 37 | platforms: linux/amd64,linux/arm64 38 | push: true 39 | tags: projectdiscovery/interactsh-web:latest,projectdiscovery/interactsh-web:${{ steps.meta.outputs.tag }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | .log 25 | .log/ 26 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn test --watchAll=false 5 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn build 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "pwa-chrome", 9 | "request": "launch", 10 | "name": "Launch Chrome against localhost", 11 | "url": "http://localhost:3000", 12 | "webRoot": "${workspaceFolder}" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | app.interactsh.com -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16 2 | WORKDIR /app 3 | COPY package.json ./ 4 | COPY yarn.lock ./ 5 | COPY ./ ./ 6 | RUN yarn install 7 | CMD ["yarn", "start"] -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 ProjectDiscovery 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # interactsh-web 2 | 3 | [Interactsh-web](https://github.com/projectdiscovery/interactsh-web) is a free and open-source web client that displays [Interactsh](https://github.com/projectdiscovery/interactsh) interactions in a well-managed dashboard in your browser. It uses the **browser's local storage** to store and display interactions. By default, the web client is configured to use - **interachsh.com**, a cloud-hosted interactsh server, and supports other self-hosted public/authencaited interactsh servers as well. 4 | 5 | A hosted instance of **interactsh-web** client is available at https://app.interactsh.com 6 | 7 | interactsh-web 8 | 9 | ## Configuring Self-Hosted Interactsh Server 10 | 11 | - Navigate to hosted interactsh-web client at https://app.interactsh.com 12 | - Click on `oast.fun` link at top bar 13 | - Submit domain name running self-hosted interactsh server, optionally token (for protected server) 14 | 15 | Here is an example configuring self-hosted interactsh server with web-client: 16 | 17 | https://user-images.githubusercontent.com/8293321/163819390-b2677f3b-4c31-4439-b258-33b8bee87bf1.mp4 18 | 19 | ## Build from Source 20 | 21 | 22 | 23 | 24 | 35 | 36 |
25 | 26 | Note: 27 | ---- 28 | 29 | In order to run the local version of the web client, **acao-url** flag should be pointed to **localhost** while running interactsh server to avoid CORS errors. for example, 30 | 31 | ``` 32 | interactsh-server -acao-url http://localhost:3000 33 | ``` 34 |
37 | 38 | ### Using Yarn 39 | 40 | ``` 41 | git clone https://github.com/projectdiscovery/interactsh-web 42 | cd interactsh-web 43 | yarn install 44 | yarn start 45 | ``` 46 | 47 | ### Using Docker 48 | 49 | ``` 50 | docker pull projectdiscovery/interactsh-web 51 | docker run -it -p 3000:3000 projectdiscovery/interactsh-web 52 | ``` 53 | 54 | Once successfully started, you can access web dashboard at [localhost:3000](http://localhost:3000) 55 | 56 | ----- 57 | 58 | ### Custom configuration 59 | 60 | You can set a custom configuration when deploying this project. 61 | If you want to avoid the registration of your server host and token, you can give the below environnement variable to your docker-compose / server. 62 | 63 | For this, just specify 64 | - `REACT_APP_HOST` for the host (default: "oast.fun") 65 | - `REACT_APP_TOKEN` for the custom token (default: "") 66 | - `REACT_APP_CIDL` for the custom correlation id length (default: 20) 67 | - `REACT_APP_CIDN` for the custom correlation nonce length (default: 13) 68 | 69 |
70 | 71 | **interactsh-web** is made with 🖤 by the [projectdiscovery](https://projectdiscovery.io) team. 72 | 73 |
-------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | DO NOT CREATE AN ISSUE to report a security problem. Instead, please send an email to security@projectdiscovery.io, and we will acknowledge it within 3 working days. 6 | -------------------------------------------------------------------------------- /craco.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | style: { 3 | postcss: { 4 | plugins: [require("tailwindcss"), require("autoprefixer")], 5 | }, 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "i", 3 | "version": "0.1.0", 4 | "private": true, 5 | "engines": { 6 | "npm": ">=8.0.0 <9.0.0", 7 | "node": ">=16.0.0 <=18.20.7" 8 | }, 9 | "dependencies": { 10 | "@ant-design/icons": "^4.7.0", 11 | "@babakness/exhaustive-type-checking": "^0.1.3", 12 | "@craco/craco": "^6.2.0", 13 | "@headlessui/react": "^1.5.0", 14 | "@morphic-ts/batteries": "^3.0.0", 15 | "@tailwindcss/forms": "^0.3.3", 16 | "@tailwindcss/typography": "^0.4.1", 17 | "@testing-library/jest-dom": "^5.11.4", 18 | "@testing-library/react": "^11.1.0", 19 | "@testing-library/user-event": "^13.5.0", 20 | "@types/jest": "^26.0.15", 21 | "@types/node": "^12.0.0", 22 | "@types/node-rsa": "^1.1.1", 23 | "@types/prismjs": "^1.16.6", 24 | "@types/react": "^17.0.0", 25 | "@types/react-dom": "^17.0.0", 26 | "@types/react-router-dom": "^5.1.8", 27 | "@types/react-transition-group": "^4.4.2", 28 | "@types/validator": "^13.1.4", 29 | "@types/webpack-env": "^1.16.2", 30 | "@vercel/analytics": "^0.1.11", 31 | "antd": "3.3.0", 32 | "autoprefixer": "^9", 33 | "date-fns": "^2.23.0", 34 | "dayjs": "^1.10.6", 35 | "eslint-plugin-fp-ts": "^0.2.1", 36 | "fp-ts": "^2.11.1", 37 | "fp-ts-contrib": "^0.1.26", 38 | "fp-ts-local-storage": "^1.0.3", 39 | "fp-ts-std": "^0.11.0", 40 | "headlessui": "^0.0.0", 41 | "husky": "^7.0.4", 42 | "io-ts": "^2.2.16", 43 | "io-ts-numbers": "^1.0.3", 44 | "io-ts-types": "^0.5.16", 45 | "is-xid": "^1.0.3", 46 | "jest-fast-check": "^1.0.2", 47 | "js-file-download": "^0.4.12", 48 | "monocle-ts": "^2.3.10", 49 | "morphic-ts": "^0.8.0-rc.2", 50 | "newtype-ts": "^0.3.4", 51 | "node-rsa": "^1.1.1", 52 | "postcss": "^7", 53 | "prettier": "^2.3.2", 54 | "prismjs": "^1.24.1", 55 | "react": "^17.0.2", 56 | "react-content-loader": "^6.0.3", 57 | "react-dom": "^17.0.2", 58 | "react-error-boundary": "^3.1.3", 59 | "react-ga": "^3.3.0", 60 | "react-router-dom": "^5.2.0", 61 | "react-scripts": "4.0.3", 62 | "react-transition-group": "^4.4.2", 63 | "sass": "^1.35.2", 64 | "styled-components": "5.2.1", 65 | "tailwindcss": "npm:@tailwindcss/postcss7-compat", 66 | "ts-pattern": "^3.2.4", 67 | "typescript": "^4.3.5", 68 | "uuid": "^8.3.2", 69 | "validator": "^13.7.0", 70 | "web-vitals": "^2.1.0", 71 | "xid-js": "^1.0.1", 72 | "zbase32": "github:projectdiscovery/zbase32" 73 | }, 74 | "scripts": { 75 | "start": "PORT=3000 craco start", 76 | "build": "NODE_OPTIONS=--openssl-legacy-provider craco build", 77 | "test": "craco test", 78 | "eject": "craco eject", 79 | "lint:fix": "eslint --fix src" 80 | }, 81 | "eslintConfig": { 82 | "extends": [ 83 | "react-app", 84 | "react-app/jest" 85 | ] 86 | }, 87 | "browserslist": { 88 | "production": [ 89 | ">0.2%", 90 | "not dead", 91 | "not op_mini all" 92 | ], 93 | "development": [ 94 | "last 1 chrome version", 95 | "last 1 firefox version", 96 | "last 1 safari version" 97 | ] 98 | }, 99 | "devDependencies": { 100 | "@types/jest-axe": "^3.5.2", 101 | "@types/styled-components": "^5.1.12", 102 | "@types/uuid": "^8.3.3", 103 | "@typescript-eslint/eslint-plugin": "^4.25.0", 104 | "@typescript-eslint/parser": "^4.25.0", 105 | "eslint-config-airbnb": "^18.2.1", 106 | "eslint-config-prettier": "^8.3.0", 107 | "eslint-plugin-prettier": "^3.4.0", 108 | "eslint-plugin-react": "^7.24.0", 109 | "fast-check": "^2.17.0" 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectdiscovery/interactsh-web/3a8c85a9c9d512e85b394c4a897450d09bb6847a/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Interactsh | Web Client 9 | 10 | 14 | 18 | 33 | 50 | 56 | 65 | 66 | 67 | 68 | 69 | 70 |
71 |
72 |
73 | 74 | 75 | -------------------------------------------------------------------------------- /src/assets/svg/arrow_right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | icon-arrow-right 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/assets/svg/chevron_up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | keyboard-arrow-up-sharp-24px 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/assets/svg/clear.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | auto-delete-outlined-20px 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/assets/svg/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svg/copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | copy.1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/assets/svg/cross.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | cross 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/assets/svg/delete.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | delete.1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/assets/svg/download.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | internet-download 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/assets/svg/filter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Shape Copy 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/assets/svg/filter_selected.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Shape Copy 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/assets/svg/loader.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/assets/svg/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Httpx-Blk 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/svg/plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | plus.2 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/assets/svg/refresh.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Path 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/assets/svg/side_by_side.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Group 24 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | ̧ -------------------------------------------------------------------------------- /src/assets/svg/switch.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | settings-power-sharp-24px 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/assets/svg/theme_blue_button.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Group 28 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/assets/svg/theme_dark_button.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Group 25 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/assets/svg/theme_synth_button.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Group 26 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/assets/svg/up_down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | horizontal-split-24px 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/breakpoints.scss: -------------------------------------------------------------------------------- 1 | @mixin for-size($range) { 2 | $small: 600px; 3 | $medium: 768px; 4 | $large: 992px; 5 | $extra-large: 1200px; 6 | @if $range == xsmall { 7 | @media only screen and (max-width: #{$small }) { 8 | @content; 9 | } 10 | } @else if $range == small { 11 | @media only screen and (min-width: $small) { 12 | @content; 13 | } 14 | } @else if $range == medium { 15 | @media only screen and (min-width: $medium) { 16 | @content; 17 | } 18 | } @else if $range == large { 19 | @media only screen and (min-width: $large) { 20 | @content; 21 | } 22 | } @else if $range == xlarge { 23 | @media only screen and (min-width: $extra-large) { 24 | @content; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/components/appLoader/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { ReactComponent as Logo } from "../../assets/svg/logo.svg"; 4 | 5 | import "./styles.scss"; 6 | 7 | interface AppLoaderP { 8 | isRegistered: boolean; 9 | mode: string; 10 | } 11 | 12 | const AppLoader = ({ isRegistered, mode }: AppLoaderP) => ( 13 |
17 |
18 | {mode === "loading" ? ( 19 | <> 20 | {" "} 21 | 22 | interact.sh 23 | 24 | 25 | ) : ( 26 | "Server Unavailable..." 27 | )} 28 |
29 |
30 | ); 31 | export default AppLoader; 32 | -------------------------------------------------------------------------------- /src/components/appLoader/styles.scss: -------------------------------------------------------------------------------- 1 | @import "../../breakpoints.scss"; 2 | @include for-size(xsmall) { 3 | } 4 | 5 | @include for-size(small) { 6 | } 7 | 8 | @include for-size(medium) { 9 | } 10 | 11 | @include for-size(large) { 12 | } 13 | 14 | @include for-size(xlarge) { 15 | } 16 | 17 | .loader_container { 18 | position: fixed; 19 | inset: 0; 20 | height: 100vh; 21 | width: 100vw; 22 | background: #000 !important; 23 | display: flex; 24 | visibility: hidden; 25 | align-items: center; 26 | justify-content: center; 27 | opacity: 0; 28 | transition: opacity 0.2s ease-out, visibility 0.2s ease-in; 29 | 30 | &:before { 31 | content: ""; 32 | position: absolute; 33 | opacity: 0.7; 34 | top: 0; 35 | left: 0; 36 | height: 100%; 37 | width: 100%; 38 | --s: 5rem; 39 | 40 | --m: 0.2rem; 41 | 42 | --v1: #000 119.5deg, #0000 120.5deg; 43 | --v2: #4d0343 119.5deg, #0000 120.5deg; 44 | background: conic-gradient( 45 | at var(--m) calc(var(--s) * 0.5777), 46 | transparent 270deg, 47 | #4d0343 0deg 48 | ), 49 | conic-gradient( 50 | at calc(100% - var(--m)) calc(var(--s) * 0.5777), 51 | #4d0343 90deg, 52 | transparent 0deg 53 | ), 54 | conic-gradient(from -60deg at 50% calc(var(--s) * 0.8662), var(--v1)), 55 | conic-gradient(from -60deg at 50% calc(var(--s) * 0.8662 + 2 * var(--m)), var(--v2)), 56 | conic-gradient(from 120deg at 50% calc(var(--s) * 1.4435 + 3 * var(--m)), var(--v1)), 57 | conic-gradient(from 120deg at 50% calc(var(--s) * 1.4435 + var(--m)), var(--v2)), 58 | linear-gradient(90deg, #000 calc(50% - var(--m)), #4d0343 0 calc(50% + var(--m)), #000 0); 59 | background-size: calc(var(--s) + 2 * var(--m)) calc(var(--s) * 1.732 + 3 * var(--m)); 60 | -webkit-mask: linear-gradient( 61 | -60deg, 62 | #0000 calc(20% - 4 * var(--s)), 63 | #000, 64 | #0000 calc(80% + 4 * var(--s)) 65 | ) 66 | (right / 300%) 100% no-repeat; 67 | animation: f 4s linear infinite alternate; 68 | } 69 | 70 | .loader_content { 71 | font-family: "Nunito Sans", sans-serif; 72 | font-size: 5.2rem; 73 | font-family: "Nunito Sans", sans-serif; 74 | color: #fff; 75 | font-weight: 100; 76 | display: flex; 77 | align-items: center; 78 | span { 79 | margin-left: 1rem; 80 | span { 81 | font-weight: 600; 82 | } 83 | } 84 | svg { 85 | height: 10rem; 86 | width: 10rem; 87 | } 88 | } 89 | } 90 | 91 | @keyframes f { 92 | 100% { 93 | -webkit-mask-position: left; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/components/customHost/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | 3 | import { ReactComponent as ArrowRightIcon } from "assets/svg/arrow_right.svg"; 4 | import { ReactComponent as CloseIcon } from "assets/svg/close.svg"; 5 | import { ReactComponent as LoadingIcon } from "assets/svg/loader.svg"; 6 | import "./styles.scss"; 7 | import { register } from "lib"; 8 | 9 | import { defaultStoredData, getStoredData, writeStoredData } from "../../lib/localStorage"; 10 | 11 | interface CustomHostP { 12 | handleCloseDialog: () => void; 13 | } 14 | 15 | const CustomHost = ({ handleCloseDialog }: CustomHostP) => { 16 | const data = getStoredData(); 17 | const { host, token, correlationIdLength, correlationIdNonceLength } = data; 18 | const [isDeleteConfirmationVisible, setIsDeleteConfirmationVisible] = useState(false); 19 | const [isLoading, setIsLoading] = useState(false); 20 | const [errorText, setErrorText] = useState(""); 21 | const [inputValue, setInputValue] = useState(host === "oast.fun" ? "" : host); 22 | const [tokenInputValue, setTokenInputValue] = useState(token === "" ? "" : token); 23 | const [correlationIdLengthInputValue, setCorrelationIdLengthValue] = useState(correlationIdLength === 20 ? 20 : correlationIdLength); 24 | const [correlationIdNonceLengthInputValue, setCorrelationIdNonceLengthValue] = useState(correlationIdNonceLength === 13 ? 13 : correlationIdNonceLength); 25 | 26 | const handleDeleteConfirmationVisibility = () => { 27 | setIsDeleteConfirmationVisible(!isDeleteConfirmationVisible); 28 | }; 29 | 30 | const handleInput: React.ChangeEventHandler = (e) => { 31 | switch (e.target.name) { 32 | case "custom_host": 33 | setInputValue(e.target.value); 34 | break; 35 | case "token": 36 | setTokenInputValue(e.target.value); 37 | break; 38 | case "cidl": 39 | setCorrelationIdLengthValue(parseInt(e.target.value, 10)); 40 | break; 41 | case "cidn": 42 | setCorrelationIdNonceLengthValue(parseInt(e.target.value, 10)); 43 | break; 44 | default: 45 | break; 46 | } 47 | }; 48 | 49 | const handleConfirm = () => { 50 | if ( 51 | (inputValue !== "" && inputValue !== "oast.fun" && host !== inputValue) || 52 | (inputValue !== "" && inputValue !== "oast.fun" && tokenInputValue !== token) 53 | ) { 54 | setIsLoading(true); 55 | const oldData = getStoredData(); 56 | setTimeout(() => { 57 | writeStoredData({...getStoredData(), 58 | correlationIdLength: correlationIdLengthInputValue, 59 | correlationIdNonceLength: correlationIdNonceLengthInputValue 60 | }); 61 | register( 62 | inputValue.replace(/(^\w+:|^)\/\//, ""), 63 | tokenInputValue, 64 | inputValue !== host && tokenInputValue === token, 65 | inputValue === host && tokenInputValue !== token 66 | ) 67 | .then((d) => { 68 | localStorage.clear(); 69 | writeStoredData(d); 70 | setIsLoading(false); 71 | handleCloseDialog(); 72 | setErrorText(""); 73 | window.location.reload(); 74 | }) 75 | .catch((err) => { 76 | if (err.message === "auth failed") { 77 | setIsLoading(false); 78 | setErrorText("Authentication failed, token not valid."); 79 | } else { 80 | setIsLoading(false); 81 | setErrorText( 82 | "We were unable to establish a connection with your server; please try again by clicking on confirm." 83 | ); 84 | } 85 | writeStoredData({...oldData}); 86 | }); 87 | }, 30); 88 | } 89 | }; 90 | 91 | const handleDelete = () => { 92 | setIsLoading(true); 93 | const oldData = getStoredData(); 94 | setTimeout(() => { 95 | writeStoredData({...getStoredData(), 96 | correlationIdLength: defaultStoredData.correlationIdLength, 97 | correlationIdNonceLength: defaultStoredData.correlationIdNonceLength 98 | }); 99 | register(defaultStoredData.host, defaultStoredData.token, true, false) 100 | .then((d) => { 101 | localStorage.clear(); 102 | writeStoredData(d); 103 | setIsLoading(false); 104 | handleCloseDialog(); 105 | setErrorText(""); 106 | window.location.reload(); 107 | }) 108 | .catch((err) => { 109 | if (err.message === "auth failed") { 110 | setIsLoading(false); 111 | setErrorText("Authentication failed, token not valid."); 112 | } else { 113 | setIsLoading(false); 114 | setErrorText( 115 | "We were unable to establish a connection with your server; please try again by clicking on confirm." 116 | ); 117 | } 118 | writeStoredData({...oldData}); 119 | }); 120 | }, 30); 121 | }; 122 | 123 | return ( 124 |
125 | {isDeleteConfirmationVisible ? ( 126 |
127 |
128 | Remove Custom Host 129 | 130 |
131 | 132 | Please confirm the action, this action can’t be undone and all the client data will be 133 | delete immediately. 134 | 135 |
136 | 144 |
145 |
146 | ) : ( 147 |
148 |
149 | Custom Host 150 | 151 |
152 | 153 | You can point your self hosted oast.fun server below to connect with this web client. 154 | 155 | 162 | 170 |
171 | Correlation Id Length (cidl) 172 | 182 |
183 |
184 | Correlation Id Nonce Length (cidn) 185 | 195 |
196 | {errorText !== "" &&
{errorText}
} 197 |
198 | {host !== "oast.fun" && ( 199 | 206 | )} 207 | 216 |
217 |
218 | )} 219 |
220 | ); 221 | }; 222 | 223 | export default CustomHost; 224 | -------------------------------------------------------------------------------- /src/components/customHost/styles.scss: -------------------------------------------------------------------------------- 1 | @import "../../breakpoints.scss"; 2 | @include for-size(xsmall) { 3 | } 4 | 5 | @include for-size(small) { 6 | } 7 | 8 | @include for-size(medium) { 9 | } 10 | 11 | @include for-size(large) { 12 | } 13 | 14 | @include for-size(xlarge) { 15 | } 16 | 17 | .backdrop_container { 18 | width: 100vw; 19 | height: 100vh; 20 | position: absolute; 21 | top: 0; 22 | left: 0; 23 | z-index: 1000; 24 | background: rgba(36, 13, 44, 0.2); 25 | display: flex; 26 | align-items: center; 27 | justify-content: center; 28 | 29 | .dialog_box { 30 | background: #000000; 31 | border-radius: 0.6rem; 32 | width: 45%; 33 | padding: 3.5rem 4rem; 34 | display: flex; 35 | flex-direction: column; 36 | 37 | .header { 38 | display: flex; 39 | margin-bottom: 2rem; 40 | padding: 0; 41 | span { 42 | font-size: 2.2rem; 43 | color: #ffffff; 44 | letter-spacing: 0; 45 | } 46 | svg { 47 | margin-left: auto; 48 | height: 3rem; 49 | cursor: pointer; 50 | } 51 | } 52 | & > span { 53 | width: 100%; 54 | font-size: 1.6rem; 55 | color: #ffffff; 56 | letter-spacing: 0; 57 | margin-bottom: 3.5rem; 58 | } 59 | input { 60 | font-size: 1.5rem; 61 | color: #6588ff; 62 | letter-spacing: 0; 63 | text-align: left; 64 | height: 4rem; 65 | padding: 1rem; 66 | border: 1px solid #1c1c1c; 67 | background: transparent; 68 | border-radius: 0.6rem; 69 | margin-bottom: 2rem; 70 | &::placeholder { 71 | color: #3c3c3c; 72 | } 73 | &:focus { 74 | outline: none; 75 | box-shadow: none; 76 | border: 1px solid #6588ff; 77 | } 78 | &:disabled { 79 | background: #101010; 80 | border: 1px solid #101010; 81 | } 82 | } 83 | .error { 84 | font-size: 1.5rem; 85 | color: #ff5151; 86 | letter-spacing: 0; 87 | text-align: left; 88 | margin-bottom: 2rem; 89 | } 90 | .buttons { 91 | display: flex; 92 | font-weight: 600; 93 | margin-top: 1rem; 94 | button { 95 | &:disabled { 96 | opacity: 0.5; 97 | } 98 | } 99 | .delete_button { 100 | display: flex; 101 | align-items: center; 102 | justify-content: center; 103 | background: #ff5151; 104 | border-radius: 0.6rem; 105 | font-size: 1.4rem; 106 | color: #ffffff; 107 | letter-spacing: 0; 108 | text-align: left; 109 | height: 3.5rem; 110 | margin-right: 2rem; 111 | flex: 1; 112 | transition: all 0.1s linear; 113 | svg { 114 | margin-left: 1.5rem; 115 | } 116 | } 117 | .remove_button { 118 | display: flex; 119 | align-items: center; 120 | justify-content: center; 121 | background: rgba(216, 216, 216, 0.19); 122 | border-radius: 0.6rem; 123 | font-size: 1.4rem; 124 | color: #ffffff; 125 | letter-spacing: 0; 126 | text-align: left; 127 | height: 3.5rem; 128 | margin-right: 2rem; 129 | flex: 1; 130 | transition: all 0.1s linear; 131 | svg { 132 | margin-left: 1.5rem; 133 | } 134 | } 135 | .submit_button { 136 | flex: 2; 137 | display: flex; 138 | align-items: center; 139 | justify-content: center; 140 | background: #3254c5; 141 | border-radius: 0.6rem; 142 | font-size: 1.4rem; 143 | color: #ffffff; 144 | letter-spacing: 0; 145 | text-align: left; 146 | height: 3.5rem; 147 | transition: all 0.1s linear; 148 | svg { 149 | margin-left: 1.5rem; 150 | } 151 | } 152 | } 153 | .advanced_options { 154 | display: flex; 155 | align-items: left; 156 | justify-content: left; 157 | span { 158 | width: 250px; 159 | font-size: 1.6rem; 160 | color: #ffffff; 161 | letter-spacing: 0; 162 | margin-top: 0.7rem; 163 | } 164 | input { 165 | margin-left: 1rem; 166 | } 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/components/detailedRequest/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | 3 | import Prism from "prismjs"; 4 | import "prismjs/themes/prism-dark.css"; 5 | import "prismjs/components/prism-http"; 6 | import "prismjs/components/prism-dns-zone-file"; 7 | 8 | import { copyDataToClipboard } from "lib"; 9 | import Protocol from "lib/types/protocol"; 10 | import "./styles.scss"; 11 | import View from "lib/types/view"; 12 | 13 | import { ReactComponent as CopyIcon } from "../../assets/svg/copy.svg"; 14 | 15 | interface DetailedRequestP { 16 | title: string; 17 | data: string; 18 | view: View; 19 | protocol: Protocol; 20 | } 21 | 22 | const DetailedRequest = ({ title, data, view, protocol }: DetailedRequestP) => { 23 | useEffect(() => { 24 | Prism.highlightAll(); 25 | }, [data]); 26 | 27 | return ( 28 |
35 | {title} 36 |
37 | 40 |
41 |
42 |             {`${data}`}
51 |           
52 |
53 |
54 |
55 | ); 56 | }; 57 | 58 | export default DetailedRequest; 59 | -------------------------------------------------------------------------------- /src/components/detailedRequest/styles.scss: -------------------------------------------------------------------------------- 1 | @import "../../breakpoints.scss"; 2 | @include for-size(xsmall) { 3 | } 4 | 5 | @include for-size(small) { 6 | } 7 | 8 | @include for-size(medium) { 9 | } 10 | 11 | @include for-size(large) { 12 | } 13 | 14 | @include for-size(xlarge) { 15 | } 16 | 17 | .detailed_request_container { 18 | display: flex; 19 | flex-direction: column; 20 | margin-bottom: 3rem; 21 | height: 100%; 22 | & > span { 23 | font-size: 1.4rem; 24 | color: #ffffff; 25 | margin-bottom: 1rem; 26 | } 27 | .body { 28 | border: 1px solid rgba(151, 151, 151, 0.25); 29 | border-radius: 0.6rem; 30 | padding: 2rem 1.5rem; 31 | font-family: Menlo-Regular; 32 | font-size: 1.2rem; 33 | color: #fcc28c; 34 | line-height: 2.8rem; 35 | position: relative; 36 | height: calc(100% - 3rem); 37 | & > .pre_wrapper { 38 | height: 100%; 39 | width: 100%; 40 | overflow-y: scroll; 41 | & > .default { 42 | color: #fcc28c; 43 | } 44 | & > pre { 45 | text-shadow: none !important; 46 | box-shadow: none !important; 47 | border: none !important; 48 | background: transparent !important; 49 | & > code { 50 | text-shadow: none !important; 51 | } 52 | } 53 | } 54 | .copy_button { 55 | position: absolute; 56 | top: -1.4rem; 57 | right: 2.8rem; 58 | background: rgba(216, 216, 216, 0.14); 59 | font-size: 1.5rem; 60 | cursor: pointer; 61 | color: #ffffff; 62 | padding: 0 0.6rem; 63 | border-radius: 0.6rem; 64 | display: flex; 65 | align-items: center; 66 | user-select: none; 67 | &:active { 68 | transition: all 0.1s linear; 69 | background: rgba(216, 216, 216, 0.25); 70 | } 71 | & > svg { 72 | margin-left: 1rem; 73 | height: 1.7rem; 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/components/header/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | 3 | import { NotificationOutlined } from "@ant-design/icons"; 4 | import { matchConfig } from "@babakness/exhaustive-type-checking"; 5 | 6 | import { ReactComponent as DeleteIcon } from "assets/svg/delete.svg"; 7 | import { ReactComponent as DownloadIcon } from "assets/svg/download.svg"; 8 | import { ReactComponent as SwitchIcon } from "assets/svg/switch.svg"; 9 | import { ReactComponent as ThemeBlueButtonIcon } from "assets/svg/theme_blue_button.svg"; 10 | import { ReactComponent as ThemeDarkButtonIcon } from "assets/svg/theme_dark_button.svg"; 11 | import { ReactComponent as ThemeSynthButtonIcon } from "assets/svg/theme_synth_button.svg"; 12 | import NotificationsPopup from "components/notificationsPopup"; 13 | import ResetPopup from "components/resetPopup"; 14 | import ToggleBtn from "components/toggleBtn"; 15 | import { handleDataExport } from "lib"; 16 | import { getStoredData, writeStoredData } from "lib/localStorage"; 17 | import { ThemeName, showThemeName } from "theme"; 18 | import "./styles.scss"; 19 | 20 | import CustomHost from "../customHost"; 21 | 22 | const themeIcon = matchConfig()({ 23 | dark: () => , 24 | synth: () => , 25 | blue: () => , 26 | }); 27 | 28 | interface HeaderP { 29 | handleThemeSelection: (t: ThemeName) => void; 30 | theme: ThemeName; 31 | host: string; 32 | handleAboutPopupVisibility: () => void; 33 | isResetPopupDialogVisible: boolean; 34 | isNotificationsDialogVisible: boolean; 35 | isCustomHostDialogVisible: boolean; 36 | handleResetPopupDialogVisibility: () => void; 37 | handleNotificationsDialogVisibility: () => void; 38 | handleCustomHostDialogVisibility: () => void; 39 | } 40 | 41 | const Header = ({ 42 | handleThemeSelection, 43 | theme, 44 | host, 45 | handleAboutPopupVisibility, 46 | isResetPopupDialogVisible, 47 | isNotificationsDialogVisible, 48 | handleResetPopupDialogVisibility, 49 | handleNotificationsDialogVisibility, 50 | isCustomHostDialogVisible, 51 | handleCustomHostDialogVisibility, 52 | }: HeaderP) => { 53 | const [isSelectorVisible, setIsSelectorVisible] = useState(false); 54 | 55 | const handleThemeSwitchesVisibility = () => { 56 | setIsSelectorVisible(!isSelectorVisible); 57 | }; 58 | 59 | const setTheme = (t: ThemeName) => () => handleThemeSelection(t); 60 | 61 | const isThemeSelected = (t: ThemeName) => ThemeName.eq.equals(t, theme); 62 | const themeButtonStyle = (t: ThemeName) => 63 | `${isSelectorVisible && "__selector_visible"} ${isThemeSelected(t) && "__selected"} ${ 64 | !isSelectorVisible && "__without_bg" 65 | }`; 66 | 67 | const ThemeButton = ({ theme: t }: { theme: ThemeName }) => ( 68 | 72 | ); 73 | 74 | const data = getStoredData(); 75 | const [inputData, setInputData] = useState({ 76 | responseExport: data.responseExport 77 | }); 78 | 79 | const handleToggleBtn = (e: any) => { 80 | 81 | const currentStoredData = getStoredData(); 82 | 83 | setInputData({ ...inputData, responseExport: e.target.checked}); 84 | writeStoredData({ ...currentStoredData, responseExport: e.target.checked}); 85 | }; 86 | 87 | return ( 88 |