├── .dockerignore ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml ├── pull_request_template.md ├── stale.yml └── workflows │ ├── codeql-analysis.yml │ └── node.js.yml ├── .gitignore ├── .jest └── setEnvVars.js ├── .prettierignore ├── .prettierrc.json ├── .versionrc ├── CHANGELOG.md ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── SECURITY.md ├── TESTING.md ├── babel.config.js ├── buildspec.yaml ├── bulwark_base ├── Dockerfile ├── buildspec.yaml └── bulwark-entrypoint ├── commitlint.config.js ├── docker-compose.yml ├── frontend ├── .browserslistrc ├── .editorconfig ├── .gitignore ├── README.md ├── angular.json ├── e2e │ ├── protractor.conf.js │ ├── src │ │ ├── app.e2e-spec.ts │ │ └── app.po.ts │ └── tsconfig.e2e.json ├── package-lock.json ├── package.json ├── src │ ├── app │ │ ├── admin.guard.spec.ts │ │ ├── admin.guard.ts │ │ ├── administration │ │ │ ├── administration.component.html │ │ │ ├── administration.component.sass │ │ │ ├── administration.component.spec.ts │ │ │ ├── administration.component.ts │ │ │ ├── invite-user │ │ │ │ ├── invite-user.component.html │ │ │ │ ├── invite-user.component.sass │ │ │ │ ├── invite-user.component.spec.ts │ │ │ │ └── invite-user.component.ts │ │ │ └── settings │ │ │ │ ├── settings.component.html │ │ │ │ ├── settings.component.sass │ │ │ │ ├── settings.component.spec.ts │ │ │ │ └── settings.component.ts │ │ ├── alert │ │ │ ├── alert.module.ts │ │ │ ├── alert.service.spec.ts │ │ │ ├── alert.service.ts │ │ │ └── alert │ │ │ │ ├── alert.component.html │ │ │ │ ├── alert.component.sass │ │ │ │ ├── alert.component.spec.ts │ │ │ │ └── alert.component.ts │ │ ├── apikey-management │ │ │ ├── apikey-management.component.html │ │ │ ├── apikey-management.component.sass │ │ │ ├── apikey-management.component.spec.ts │ │ │ └── apikey-management.component.ts │ │ ├── app-routing.module.ts │ │ ├── app.component.html │ │ ├── app.component.scss │ │ ├── app.component.spec.ts │ │ ├── app.component.ts │ │ ├── app.interceptor.ts │ │ ├── app.module.ts │ │ ├── app.service.spec.ts │ │ ├── app.service.ts │ │ ├── assessment-form │ │ │ ├── Assessment.ts │ │ │ ├── assessment-form.component.html │ │ │ ├── assessment-form.component.sass │ │ │ ├── assessment-form.component.spec.ts │ │ │ └── assessment-form.component.ts │ │ ├── assessments │ │ │ ├── assessments.component.html │ │ │ ├── assessments.component.sass │ │ │ ├── assessments.component.spec.ts │ │ │ └── assessments.component.ts │ │ ├── asset-form │ │ │ ├── Asset.ts │ │ │ ├── Jira.ts │ │ │ ├── asset-form.component.html │ │ │ ├── asset-form.component.sass │ │ │ ├── asset-form.component.spec.ts │ │ │ └── asset-form.component.ts │ │ ├── auth.guard.spec.ts │ │ ├── auth.guard.ts │ │ ├── auth.service.spec.ts │ │ ├── auth.service.ts │ │ ├── classes │ │ │ ├── Alert.ts │ │ │ ├── ProblemLocation.ts │ │ │ └── Resource.ts │ │ ├── dashboard │ │ │ ├── dashboard.component.html │ │ │ ├── dashboard.component.sass │ │ │ ├── dashboard.component.spec.ts │ │ │ └── dashboard.component.ts │ │ ├── email-validate │ │ │ ├── email-validate.component.html │ │ │ ├── email-validate.component.sass │ │ │ ├── email-validate.component.spec.ts │ │ │ └── email-validate.component.ts │ │ ├── enums │ │ │ └── roles.enum.ts │ │ ├── footer │ │ │ ├── footer.component.html │ │ │ ├── footer.component.sass │ │ │ ├── footer.component.spec.ts │ │ │ └── footer.component.ts │ │ ├── forgot-password │ │ │ ├── forgot-password.component.html │ │ │ ├── forgot-password.component.sass │ │ │ ├── forgot-password.component.spec.ts │ │ │ └── forgot-password.component.ts │ │ ├── global-manager.service.spec.ts │ │ ├── global-manager.service.ts │ │ ├── interfaces │ │ │ ├── ApiKey.ts │ │ │ ├── App_File.ts │ │ │ ├── Organization.ts │ │ │ ├── Screenshot.ts │ │ │ ├── Settings.ts │ │ │ ├── Team.ts │ │ │ ├── Tokens.ts │ │ │ └── User.ts │ │ ├── loader.service.spec.ts │ │ ├── loader.service.ts │ │ ├── login │ │ │ ├── login.component.html │ │ │ ├── login.component.sass │ │ │ ├── login.component.spec.ts │ │ │ └── login.component.ts │ │ ├── navbar │ │ │ ├── navbar.component.html │ │ │ ├── navbar.component.sass │ │ │ ├── navbar.component.spec.ts │ │ │ └── navbar.component.ts │ │ ├── org-form │ │ │ ├── Organization.ts │ │ │ ├── org-form.component.html │ │ │ ├── org-form.component.sass │ │ │ ├── org-form.component.spec.ts │ │ │ └── org-form.component.ts │ │ ├── organization │ │ │ ├── organization.component.html │ │ │ ├── organization.component.sass │ │ │ ├── organization.component.spec.ts │ │ │ └── organization.component.ts │ │ ├── page-not-found │ │ │ ├── page-not-found.component.html │ │ │ ├── page-not-found.component.sass │ │ │ ├── page-not-found.component.spec.ts │ │ │ └── page-not-found.component.ts │ │ ├── password-reset │ │ │ ├── password-reset.component.html │ │ │ ├── password-reset.component.sass │ │ │ ├── password-reset.component.spec.ts │ │ │ └── password-reset.component.ts │ │ ├── register │ │ │ ├── register.component.html │ │ │ ├── register.component.sass │ │ │ ├── register.component.spec.ts │ │ │ └── register.component.ts │ │ ├── report │ │ │ ├── report.component.html │ │ │ ├── report.component.sass │ │ │ ├── report.component.spec.ts │ │ │ └── report.component.ts │ │ ├── team-form │ │ │ ├── team-form.component.html │ │ │ ├── team-form.component.sass │ │ │ ├── team-form.component.spec.ts │ │ │ └── team-form.component.ts │ │ ├── team.service.spec.ts │ │ ├── team.service.ts │ │ ├── team │ │ │ ├── team.component.html │ │ │ ├── team.component.sass │ │ │ ├── team.component.spec.ts │ │ │ └── team.component.ts │ │ ├── user-form │ │ │ ├── user-form.component.html │ │ │ ├── user-form.component.sass │ │ │ ├── user-form.component.spec.ts │ │ │ └── user-form.component.ts │ │ ├── user-management │ │ │ ├── user-management.component.html │ │ │ ├── user-management.component.sass │ │ │ ├── user-management.component.spec.ts │ │ │ └── user-management.component.ts │ │ ├── user-profile │ │ │ ├── user-profile.component.html │ │ │ ├── user-profile.component.sass │ │ │ ├── user-profile.component.spec.ts │ │ │ └── user-profile.component.ts │ │ ├── user.service.spec.ts │ │ ├── user.service.ts │ │ ├── vuln-form │ │ │ ├── Vulnerability.ts │ │ │ ├── vuln-form.component.html │ │ │ ├── vuln-form.component.sass │ │ │ ├── vuln-form.component.spec.ts │ │ │ └── vuln-form.component.ts │ │ └── vulnerability │ │ │ ├── vulnerability.component.html │ │ │ ├── vulnerability.component.sass │ │ │ ├── vulnerability.component.spec.ts │ │ │ └── vulnerability.component.ts │ ├── assets │ │ ├── .gitkeep │ │ ├── logo.png │ │ ├── overallRiskSeverity.png │ │ └── theme │ │ │ └── button.scss │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── karma.conf.js │ ├── main.ts │ ├── polyfills.ts │ ├── styles.scss │ ├── test.ts │ ├── tsconfig.app.json │ ├── tsconfig.spec.json │ └── tslint.json ├── tsconfig.json └── tslint.json ├── jest.config.js ├── ormconfig.js ├── package-lock.json ├── package.json ├── src ├── app.ts ├── classes │ └── Report.ts ├── data-source.ts ├── entity │ ├── ApiKey.ts │ ├── Assessment.ts │ ├── Asset.ts │ ├── Config.ts │ ├── File.ts │ ├── Jira.ts │ ├── Organization.ts │ ├── ProblemLocation.ts │ ├── ReportAudit.ts │ ├── Resource.ts │ ├── Team.ts │ ├── User.ts │ ├── VulnDictionary.ts │ └── Vulnerability.ts ├── enums │ ├── message-enum.ts │ ├── roles-enum.ts │ └── status-enum.ts ├── init │ ├── docker-run-exec.ts │ └── setEnv.ts ├── interfaces │ ├── jira │ │ ├── jira-init.interface.ts │ │ ├── jira-issue-link.interface.ts │ │ ├── jira-issue-priority.interface.ts │ │ ├── jira-issue-status.interface.ts │ │ ├── jira-issue-type.interface.ts │ │ ├── jira-issue.interface.ts │ │ ├── jira-project.interface.ts │ │ └── jira-result.interface.ts │ ├── team-info.interface.ts │ └── user-request.interface.ts ├── middleware │ ├── jwt.middleware.ts │ └── jwt.spec.ts ├── routes │ ├── api-key.controller.spec.ts │ ├── api-key.controller.ts │ ├── assessment.controller.spec.ts │ ├── assessment.controller.ts │ ├── asset.controller.spec.ts │ ├── asset.controller.ts │ ├── authentication.controller.ts │ ├── config.controller.spec.ts │ ├── config.controller.ts │ ├── file-upload.controller.ts │ ├── organization.controller.ts │ ├── report-audit.controller.spec.ts │ ├── report-audit.controller.ts │ ├── team.controller.spec.ts │ ├── team.controller.ts │ ├── user.controller.spec.ts │ ├── user.controller.ts │ └── vulnerability.controller.ts ├── services │ ├── email.service.spec.ts │ └── email.service.ts ├── temp │ └── empty.ts └── utilities │ ├── column-mapper.utility.ts │ ├── crypto.utility.spec.ts │ ├── crypto.utility.ts │ ├── file.utility.ts │ ├── jira.utility.spec.ts │ ├── jira.utility.ts │ ├── password.utility.spec.ts │ ├── password.utility.ts │ ├── puppeteer.utility.ts │ └── role.utility.ts ├── tsconfig.json └── tslint.json /.dockerignore: -------------------------------------------------------------------------------- 1 | .git/ 2 | monitoring/ 3 | node_modules/ 4 | screenshots/ 5 | test/ 6 | build/ 7 | dist/ 8 | vagrant/ 9 | docs/ 10 | logs/ 11 | Dockerfile 12 | .npmrc 13 | frontend/node_modules/ 14 | frontend/dist/ 15 | .env -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a bug in Bulwark 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Problem Locations** 14 | Did you find this issue while using Bulwark? Supply the URL and payload that caused the issue. Furthermore, supply the relative path and line number of the affected source code. 15 | 16 | **Steps to Reproduce** 17 | Steps to reproduce the behavior: 18 | 1. Go to '...' 19 | 2. Click on '....' 20 | 3. Scroll down to '....' 21 | 4. See error 22 | 23 | **Expected behavior** 24 | A clear and concise description of what you expected to happen. 25 | 26 | **Screenshots** 27 | If applicable, add screenshots to help explain your problem. 28 | 29 | **Desktop (please complete the following information):** 30 | - OS: [e.g. iOS] 31 | - Browser [e.g. chrome, safari] 32 | - Version [e.g. 22] 33 | 34 | **Smartphone (please complete the following information):** 35 | - Device: [e.g. iPhone6] 36 | - OS: [e.g. iOS8.1] 37 | - Browser [e.g. stock browser, safari] 38 | - Version [e.g. 22] 39 | 40 | **Additional context** 41 | Add any other context about the problem here. 42 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for Bulwark 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'npm' 4 | directory: '/' 5 | schedule: 6 | interval: 'monthly' 7 | target-branch: 'develop' 8 | 9 | - package-ecosystem: 'npm' 10 | directory: '/frontend' 11 | schedule: 12 | interval: 'monthly' 13 | target-branch: 'develop' 14 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Pull Request 2 | 3 | Thank you for your contribution to the Bulwark. Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. 4 | 5 | Fixes # (issue) 6 | 7 | ## Type of change 8 | 9 | - [ ] A new feature 10 | - [ ] A bug fix 11 | - [ ] Documentation only changes 12 | - [ ] Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) 13 | - [ ] A code change that neither fixes a bug nor adds a feature 14 | - [ ] A code change that improves performance 15 | - [ ] Adding missing tests or correcting existing tests 16 | - [ ] Changes that affect the build system or external dependencies 17 | - [ ] Changes to our CI configuration files and scripts 18 | - [ ] Other changes that don't modify src or test files 19 | 20 | Add description here 21 | 22 | ```release-note 23 | 24 | ``` 25 | 26 | --- 27 | 28 | ## Checklist 29 | 30 | - [ ] My code follows the [contributing](../CONTRIBUTING.md) guidelines of this project 31 | - [ ] I have performed a self-review of my own code 32 | - [ ] I have commented my code, particularly in hard-to-understand areas 33 | - [ ] I have made corresponding changes to the documentation 34 | - [ ] My changes generate no new warnings 35 | - [ ] I have added tests that prove my fix is effective or that my feature works 36 | - [ ] New and existing unit tests pass locally with my changes 37 | - [ ] Any dependent changes have been merged and published in downstream modules 38 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | daysUntilStale: 60 2 | daysUntilClose: 7 3 | exemptLabels: 4 | - confirmed 5 | - security 6 | staleLabel: stale 7 | markComment: > 8 | This issue has been automatically marked as stale because it has not had 9 | recent activity. It will be closed if no further activity occurs. Thank you 10 | for your contributions. 11 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | name: 'CodeQL' 7 | 8 | on: 9 | push: 10 | branches: [master, develop] 11 | pull_request: 12 | # The branches below must be a subset of the branches above 13 | branches: [master] 14 | schedule: 15 | - cron: '0 2 * * 2' 16 | 17 | jobs: 18 | analyze: 19 | name: Analyze 20 | runs-on: ubuntu-latest 21 | 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | # Override automatic language detection by changing the below list 26 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] 27 | language: ['javascript'] 28 | # Learn more... 29 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 30 | 31 | steps: 32 | - name: Checkout repository 33 | uses: actions/checkout@v2 34 | with: 35 | # We must fetch at least the immediate parents so that if this is 36 | # a pull request then we can checkout the head. 37 | fetch-depth: 2 38 | 39 | # If this run was triggered by a pull request event, then checkout 40 | # the head of the pull request instead of the merge commit. 41 | - run: git checkout HEAD^2 42 | if: ${{ github.event_name == 'pull_request' }} 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v1 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v1 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v1 72 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: build 5 | 6 | on: 7 | push: 8 | branches: [master] 9 | pull_request: 10 | branches: [develop] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | matrix: 18 | node-version: [12.x, 14.x] 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v1 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | - name: Install Packages 27 | run: npm install 28 | - name: Create Env File 29 | run: | 30 | touch .env 31 | echo JWT_KEY="test" >> .env 32 | echo JWT_REFRESH_KEY="test" >> .env 33 | - name: Backend Tests 34 | run: npm run test:node 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | #angular 4 | /frontend/.angular 5 | 6 | # compiled output 7 | /dist 8 | /tmp 9 | /out-tsc 10 | # Only exists if Bazel was run 11 | /bazel-out 12 | 13 | # dependencies 14 | /node_modules 15 | 16 | # profiling files 17 | chrome-profiler-events.json 18 | speed-measure-plugin.json 19 | 20 | # database migrations 21 | /src/database/migration/* 22 | 23 | # IDEs and editors 24 | /.idea 25 | .project 26 | .classpath 27 | .c9/ 28 | *.launch 29 | .settings/ 30 | *.sublime-workspace 31 | .vscode/ 32 | 33 | # IDE - VSCode 34 | .vscode/* 35 | !.vscode/settings.json 36 | !.vscode/tasks.json 37 | !.vscode/launch.json 38 | !.vscode/extensions.json 39 | .history/* 40 | 41 | # misc 42 | /.sass-cache 43 | /connect.lock 44 | /coverage 45 | /libpeerconnection.log 46 | npm-debug.log 47 | yarn-error.log 48 | testem.log 49 | /typings 50 | .env 51 | .env.* 52 | 53 | # System Files 54 | .DS_Store 55 | Thumbs.db 56 | -------------------------------------------------------------------------------- /.jest/setEnvVars.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = 'test'; 2 | process.env.CRYPTO_SECRET = 'test'; 3 | process.env.CRYPTO_SALT = 'test'; 4 | process.env.JWT_KEY = 'test'; 5 | process.env.JWT_REFRESH_KEY = 'test'; 6 | process.env.CRYPTO_SALT = 'test'; 7 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | coverage 2 | dist 3 | frontend/dist 4 | node_modules 5 | frontend/node_modules 6 | .prettierignore 7 | Dockerfile 8 | CHANGELOG.md 9 | # Ignore all HTML files: 10 | *.html 11 | *.sass 12 | .gitignore 13 | frontend/.gitignore -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "bracketSpacing": true, 4 | "embeddedLanguageFormatting": "auto", 5 | "htmlWhitespaceSensitivity": "css", 6 | "insertPragma": false, 7 | "jsxBracketSameLine": false, 8 | "jsxSingleQuote": false, 9 | "printWidth": 80, 10 | "proseWrap": "preserve", 11 | "quoteProps": "as-needed", 12 | "requirePragma": false, 13 | "semi": true, 14 | "singleQuote": true, 15 | "tabWidth": 2, 16 | "trailingComma": "es5", 17 | "useTabs": false, 18 | "vueIndentScriptAndStyle": false 19 | } 20 | -------------------------------------------------------------------------------- /.versionrc: -------------------------------------------------------------------------------- 1 | { 2 | "types": [ 3 | {"type": "chore", "section":"Others", "hidden": false}, 4 | {"type": "revert", "section":"Reverts", "hidden": false}, 5 | {"type": "feat", "section": "Features", "hidden": false}, 6 | {"type": "fix", "section": "Bug Fixes", "hidden": false}, 7 | {"type": "docs", "section":"Docs", "hidden": false}, 8 | {"type": "style", "section":"Styling", "hidden": false}, 9 | {"type": "refactor", "section":"Code Refactoring", "hidden": false}, 10 | {"type": "perf", "section":"Performance Improvements", "hidden": false}, 11 | {"type": "test", "section":"Tests", "hidden": false}, 12 | {"type": "build", "section":"Build System", "hidden": false}, 13 | {"type": "ci", "section":"CI", "hidden":false} 14 | ] 15 | } -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @softrams/bulwark-core-team 2 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at opensource@softrams.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue with the owners of this repository before making a change. 4 | 5 | ## Contributing to Development 6 | 7 | Issues will be labelled with `help wanted` or `good first issue` 8 | 9 | - `Help wanted` label indicates tasks where the project team would appreciate community help 10 | - `Good first issue` label indicates a task that introduce developers to the project, have low complexity, and are isolated 11 | 12 | ## Version Control 13 | 14 | The project uses git as its version control system and GitHub as the central server and collaboration platform. 15 | 16 | ### Branching model 17 | 18 | Bulwark is maintained in a simplified [Gitflow](https://jeffkreeftmeijer.com/git-flow/) fashion, where all active development happens on the develop branch while master is used to maintain stable versions. Tasks with higher complexity, prototypes, and experiments will occur in feature branches. 19 | 20 | ### Versioning 21 | 22 | Any release from master will have a unique version automated by [standard-version](https://github.com/conventional-changelog/standard-version) 23 | 24 | `MAJOR.MINOR.PATCH` will be incremented by: 25 | 26 | 1. `MAJOR` version when breaking changes occur 27 | 2. `MINOR` version with new functionality that is backwards-compatible 28 | 3. `PATCH` version with backwards-compatible bug fixes 29 | 30 | ## Pull-Request Process 31 | 32 | 1. All work must be done in a fork off the dev branch 33 | 2. Ensure any install or build dependencies are removed 34 | 3. All Git commits within a PR must be conventional commits using [commitizen](https://github.com/commitizen/cz-cli) and enforced by [husky](https://github.com/typicode/husky) 35 | 1. Run `$ npm run commit` when committing changes 36 | 4. The code must comply to the [testing requirements](TESTING.md) 37 | 5. Open a Pull-Request against the `develop` branch 38 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM softramsdocker/bulwark-base:latest 2 | 3 | USER root 4 | 5 | # Environment Arguments for Bulwark 6 | ARG MYSQL_USER 7 | ARG MYSQL_ROOT_PASSWORD 8 | ARG MYSQL_DB_CHECK 9 | ARG DB_PASSWORD 10 | ARG DB_URL 11 | ARG DB_USERNAME 12 | ARG DB_PORT 13 | ARG DB_NAME 14 | ARG DB_TYPE 15 | ARG NODE_ENV 16 | ARG DEV_URL 17 | ARG SERVER_ADDRESS 18 | ARG PORT 19 | ARG JWT_KEY 20 | ARG JWT_REFRESH_KEY 21 | ARG CRYPTO_SECRET 22 | ARG CRYPTO_SALT 23 | 24 | # Stage the setup to launch Bulwark 25 | RUN mkdir -p /bulwark 26 | COPY . /bulwark 27 | WORKDIR "bulwark" 28 | 29 | # Permissions for Bulwark 30 | RUN chown -R bulwark:bulwark /bulwark 31 | 32 | # DB Wait MySQL Status Up, requires mysql-client and python 33 | RUN apk add --no-cache --update mysql-client \ 34 | python2 35 | 36 | # Runas User 37 | USER bulwark 38 | 39 | # Bulwark Specific Startup 40 | # Cleanup NPM to save some space 41 | RUN npm install \ 42 | && rm -rf /bulwark/.npm 43 | 44 | # Swap to root and delete python 45 | USER root 46 | # Clean up apk 47 | RUN apk del python2 48 | 49 | # Runas User 50 | USER bulwark 51 | 52 | # Running Port 53 | EXPOSE 5000 54 | 55 | # Launch Bulwark 56 | CMD ["npm", "run", "start"] 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Softrams LLC 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Security patches will be included in the latest released version. 6 | 7 | ## Reporting a Vulnerability 8 | 9 | For any vulnerabilities found within Bulwark, please contact oss-security-reports@softrams.com 10 | 11 | ### Description 12 | 13 | Describe the nature of the vulnerability. 14 | 15 | ### Problem Locations 16 | 17 | Describe where the in the application the vulnerability occurs. If possible, add the affected code. For ex: 18 | 19 | | Problem Location | Target | 20 | | --------------------------- | ------------------ | 21 | | foo/bar/admin.js | Line 101, 106, 200 | 22 | | localhost:5000/#/admin/{id} | id | 23 | 24 | ### Remediation (optional) 25 | 26 | Describe any suggestions on how to fix the vulnerability. 27 | -------------------------------------------------------------------------------- /TESTING.md: -------------------------------------------------------------------------------- 1 | # Testing Requirements 2 | 3 | 1. All new and updated **back-end** code should have a corresponding unit tests 4 | 2. All new and existing unit tests should pass locally before opening a Pull-Request 5 | 3. Linting should pass locally before opening a Pull-Request 6 | 7 | ## Running Front-End Unit Tests (Not Currently Required) 8 | 9 | - The Angular unit tests have **not** been implemented so please skip this section 10 | - Run `npm run test:front` to execute the unit tests via [Karma](https://karma-runner.github.io) 11 | - Follow the recommended guidelines for Front-End [Testing](https://angular.io/guide/testing) 12 | 13 | ## Running Back-End Unit Tests 14 | 15 | - Run `npm run test:node` to execute the unit tests via [Jest](https://jestjs.io/) 16 | - Test coverage report can be opened in browser located in: `coverage/lcov-report/index.html` 17 | - Follow the recommended guidelines for Back-End [Testing](https://jestjs.io/) 18 | 19 | ## Running All Tests 20 | 21 | - Run `npm run test` to execute the unit tests. 22 | 23 | ## Linting 24 | 25 | ``` 26 | npm run lint 27 | ``` 28 | 29 | In case your PR is failing from style guide issues try running `npm run lint:fix` - this will fix all syntax or code style issues automatically without breaking your code. 30 | 31 | ### Prettier 32 | 33 | Bulwark uses [Prettier](https://prettier.io/) for opinionating code formatting. It is recommended to run it from your editor. Use the following [steps](https://prettier.io/docs/en/editors.html) to integrate Prettier into your editor. 34 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ['@babel/preset-env', {targets: {node: 'current'}}], 4 | '@babel/preset-typescript', 5 | ], 6 | }; 7 | -------------------------------------------------------------------------------- /buildspec.yaml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | phases: 3 | pre_build: 4 | on-failure: ABORT 5 | commands: 6 | - echo Logging in to Docker Hub... 7 | - docker login --username ${DOCKERHUB_USER} --password ${DOCKERHUB_TOKEN} 8 | - | 9 | if [ -z $CODEBUILD_WEBHOOK_TRIGGER ] 10 | then 11 | TAG="latest" 12 | else 13 | if [ "${CODEBUILD_WEBHOOK_TRIGGER}" = "branch/master" ] 14 | then 15 | TAG="latest" 16 | else 17 | echo "in else ${CODEBUILD_WEBHOOK_TRIGGER}" 18 | TAG=${CODEBUILD_WEBHOOK_TRIGGER/branch\//} 19 | fi 20 | fi 21 | - echo $TAG 22 | build: 23 | on-failure: ABORT 24 | commands: 25 | - echo Build started on `date` 26 | - echo Building the Docker image... 27 | - echo "docker build -t softramsdocker/bulwark:${TAG} ." 28 | - docker build -t softramsdocker/bulwark:${TAG} . 29 | post_build: 30 | commands: 31 | - echo Build completed on `date` 32 | - echo Pushing the Docker image... 33 | - docker push softramsdocker/bulwark:${TAG} -------------------------------------------------------------------------------- /bulwark_base/Dockerfile: -------------------------------------------------------------------------------- 1 | # Softrams - Bulwark Reporting Application Dockerized Configuration 2 | # Maintained by Bill Jones 3 | 4 | # Start from Alpine Linux for smaller footprint 5 | FROM alpine:latest 6 | 7 | # Environmental Items 8 | ENV NODE_VERSION=20.14.0 \ 9 | TYPESCRIPT_VERSION=10.9.2 \ 10 | PUPPETEER_VERSION=23.2.0 \ 11 | PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \ 12 | PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser 13 | 14 | # View template at https://github.com/nodejs/docker-node/blob/master/Dockerfile-alpine.template 15 | # Setup Bulwark Container User 16 | RUN addgroup -S bulwark && adduser -S bulwark -G bulwark -s /bin/sh -D bulwark 17 | 18 | # Update Image 19 | RUN apk upgrade --no-cache -U 20 | 21 | # Install Required Packages to Build NodeJS and Puppeter Items 22 | RUN apk add --no-cache --virtual .build-deps-full curl make gcc g++ python3 linux-headers binutils-gold gnupg libstdc++ chromium \ 23 | fontconfig udev ttf-freefont fontconfig pango-dev libxcursor libxdamage cups-libs dbus-libs libxrandr \ 24 | libxscrnsaver libc6-compat nss freetype freetype-dev harfbuzz ca-certificates libgcc py-setuptools 25 | 26 | # Ingest the GPG Keys from https://github.com/nodejs/node#release-keys 27 | RUN for server in keys.openpgp.org pool.sks-keyservers.net keyserver.pgp.com ha.pool.sks-keyservers.net; do \ 28 | gpg --keyserver $server --recv-keys \ 29 | 4ED778F539E3634C779C87C6D7062848A1AB005C \ 30 | 141F07595B7B3FFE74309A937405533BE57C7D57 \ 31 | 74F12602B6F1C4E913FAA37AD3A89613643B6201 \ 32 | DD792F5973C6DE52C432CBDAC77ABFA00DDBF2B7 \ 33 | CC68F5A3106FF448322E48ED27F5E38D5B0A215F \ 34 | 8FCCA13FEF1D0C2E91008E09770F7A9A5AE15600 \ 35 | 890C08DB8579162FEE0DF9DB8BEAB4DFCF555EF4 \ 36 | C82FA3AE1CBEDC6BE46B9360C43CEC45C17AB93C \ 37 | 108F52B48DB57BB0CC439B2997B01419BD92F80A \ 38 | A363A499291CBBC940DD62E41F10027AF002F8B0 && break; \ 39 | done 40 | 41 | # Perform nodejs installation 42 | # https://nodejs.org/dist/v14.9.0/node-v14.9.0.tar.gz - URL for Nodejs Source Code 43 | RUN curl -fsSLO --compressed "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION.tar.gz" \ 44 | && curl -fsSLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt" \ 45 | && curl -fsSLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.sig" \ 46 | #&& gpg --verify SHASUMS256.txt.sig SHASUMS256.txt \ 47 | && grep " node-v$NODE_VERSION.tar.gz\$" SHASUMS256.txt | sha256sum -c - \ 48 | && tar -xf "node-v$NODE_VERSION.tar.gz" \ 49 | && cd "node-v$NODE_VERSION" \ 50 | && ./configure \ 51 | && make -j$(getconf _NPROCESSORS_ONLN) V= \ 52 | && make install \ 53 | && apk del .build-deps-full \ 54 | && cd .. \ 55 | && rm -Rf "node-v$NODE_VERSION" \ 56 | && rm "node-v$NODE_VERSION.tar.gz" SHASUMS256.txt.sig SHASUMS256.txt \ 57 | # Cleanup 58 | RUN rm -f "node-v$NODE_VERSION" \ 59 | # smoke tests 60 | && node --version \ 61 | && npm --version \ 62 | # Setup for launch control of Bulwark 63 | WORKDIR / 64 | COPY bulwark-entrypoint /usr/local/bin/ 65 | 66 | ENTRYPOINT ["bulwark-entrypoint"] 67 | -------------------------------------------------------------------------------- /bulwark_base/buildspec.yaml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | phases: 3 | pre_build: 4 | commands: 5 | - echo Logging in to Docker Hub... 6 | - docker login --username ${DOCKERHUB_USER} --password ${DOCKERHUB_TOKEN} 7 | build: 8 | on-failure: ABORT 9 | commands: 10 | - echo Build started on `date` 11 | - echo Building the Docker image... 12 | - cd bulwark_base 13 | - docker build -t softramsdocker/bulwark-base:latest . 14 | post_build: 15 | commands: 16 | - echo Build completed on `date` 17 | - echo Pushing the Docker images... 18 | - docker push softramsdocker/bulwark-base:latest -------------------------------------------------------------------------------- /bulwark_base/bulwark-entrypoint: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | exec "$@" 4 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'] 3 | }; 4 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | bulwark: 4 | image: softramsdocker/bulwark:latest 5 | container_name: bulwark 6 | environment: 7 | MYSQL_USER: '${MYSQL_USER}' 8 | MYSQL_ROOT_PASSWORD: '${MYSQL_ROOT_PASSWORD}' 9 | MYSQL_DB_CHECK: '${MYSQL_DB_CHECK}' 10 | DB_PASSWORD: '${DB_PASSWORD}' 11 | DB_URL: '${DB_URL}' 12 | DB_USERNAME: '${DB_USERNAME}' 13 | DB_PORT: '${DB_PORT}' 14 | DB_NAME: '${DB_NAME}' 15 | DB_TYPE: '${DB_TYPE}' 16 | NODE_ENV: '${NODE_ENV}' 17 | DEV_URL: '${DEV_URL}' 18 | SERVER_ADDRESS: '${SERVER_ADDRESS}' 19 | PORT: '${PORT}' 20 | JWT_KEY: '${JWT_KEY}' 21 | JWT_REFRESH_KEY: '${JWT_REFRESH_KEY}' 22 | CRYPTO_SECRET: '${CRYPTO_SECRET}' 23 | CRYPTO_SALT: '${CRYPTO_SALT}' 24 | depends_on: 25 | - bulwark-db 26 | networks: 27 | static-network: 28 | ipv4_address: 172.16.16.2 29 | ports: 30 | - '5000:5000' 31 | expose: 32 | - '5000' 33 | stop_grace_period: 1m 34 | volumes: 35 | - bulwark-temp:/bulwark/src/temp:rw 36 | command: > 37 | sh -c " 38 | until mysql --host=$${DB_URL} --user=$${MYSQL_USER} --password=$${MYSQL_ROOT_PASSWORD} --database=$${MYSQL_DB_CHECK} -e 'SELECT user FROM user;'; do 39 | >&2 echo MySQL is unavailable - sleeping 40 | sleep 1 41 | done 42 | && echo MySQL should be up - starting up Bulwark. 43 | && npm run postinstall 44 | && echo Initial DB Creation 45 | && npm run docker:check 46 | && npm run start" 47 | 48 | bulwark-db: 49 | image: mysql:9.0.1 # 7.7.31 50 | container_name: bulwark_db 51 | environment: 52 | MYSQL_DATABASE: '${DB_NAME}' 53 | MYSQL_USER: '${DB_USERNAME}' 54 | MYSQL_PASSWORD: '${DB_PASSWORD}' 55 | MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}' 56 | networks: 57 | static-network: 58 | ipv4_address: 172.16.16.3 59 | ports: 60 | - '3306:3306' 61 | expose: 62 | - '3306' 63 | volumes: 64 | - bulwark-db:/var/lib/mysql:rw 65 | restart: always 66 | 67 | volumes: 68 | bulwark-db: 69 | bulwark-temp: 70 | 71 | networks: 72 | static-network: 73 | ipam: 74 | config: 75 | - subnet: 172.16.16.0/29 76 | -------------------------------------------------------------------------------- /frontend/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # 5 | # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed 6 | 7 | > 0.5% 8 | last 2 versions 9 | Firefox ESR 10 | not dead 11 | not IE 9-11 -------------------------------------------------------------------------------- /frontend/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | quote_type = single 11 | 12 | [*.md] 13 | max_line_length = off 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | /src/environments 10 | 11 | # dependencies 12 | /node_modules 13 | 14 | # profiling files 15 | chrome-profiler-events.json 16 | speed-measure-plugin.json 17 | 18 | # IDEs and editors 19 | /.idea 20 | .project 21 | .classpath 22 | .c9/ 23 | *.launch 24 | .settings/ 25 | *.sublime-workspace 26 | 27 | # IDE - VSCode 28 | .vscode/* 29 | !.vscode/settings.json 30 | !.vscode/tasks.json 31 | !.vscode/launch.json 32 | !.vscode/extensions.json 33 | .history/* 34 | 35 | # misc 36 | /.sass-cache 37 | /connect.lock 38 | /coverage 39 | /libpeerconnection.log 40 | npm-debug.log 41 | yarn-error.log 42 | testem.log 43 | /typings 44 | 45 | # System Files 46 | .DS_Store 47 | Thumbs.db 48 | 49 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # Frontend 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 7.3.5. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 28 | -------------------------------------------------------------------------------- /frontend/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './src/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: require('path').join(__dirname, './tsconfig.e2e.json') 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; -------------------------------------------------------------------------------- /frontend/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('Welcome to frontend!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | } as logging.Entry)); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /frontend/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText() { 9 | return element(by.css('app-root h1')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /frontend/e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "commonjs", 6 | "target": "es6", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "1.0.0", 4 | "type": "module", 5 | "scripts": { 6 | "ng": "ng", 7 | "start": "ng serve", 8 | "start:prod": "ng serve -c production", 9 | "start:dev": "ng serve -c development", 10 | "build:prod": "ng build --configuration=production", 11 | "build:dev": "ng build --configuration=development", 12 | "test": "ng test", 13 | "lint": "ng lint", 14 | "e2e": "ng e2e" 15 | }, 16 | "private": true, 17 | "dependencies": { 18 | "@angular-devkit/build-angular": "~18.0.1", 19 | "@angular/animations": "~18.0.1", 20 | "@angular/cdk": "^18.0.1", 21 | "@angular/cli": "^18.0.1", 22 | "@angular/common": "~18.0.1", 23 | "@angular/compiler": "~18.0.1", 24 | "@angular/compiler-cli": "~18.0.1", 25 | "@angular/core": "~18.0.1", 26 | "@angular/forms": "~18.0.1", 27 | "@angular/platform-browser": "~18.0.1", 28 | "@angular/platform-browser-dynamic": "~18.0.1", 29 | "@angular/router": "~18.0.1", 30 | "@ng-select/ng-select": "^13.2.0", 31 | "chart.js": "^3.6.0", 32 | "core-js": "^3.19.0", 33 | "jwt-decode": "^3.1.2", 34 | "ngx-markdown": "^18.0.0", 35 | "primeflex": "^3.1.0", 36 | "primeicons": "^5.0.0", 37 | "primeng": "^17.18.0", 38 | "rxjs": "~7.8.1", 39 | "tslib": "^2.3.1", 40 | "zone.js": "~0.14.6" 41 | }, 42 | "devDependencies": { 43 | "@angular/language-service": "~18.0.1", 44 | "@types/jasmine": "~3.10.1", 45 | "@types/jasminewd2": "^2.0.10", 46 | "@types/node": "20.12.13", 47 | "codelyzer": "^6.0.2", 48 | "jasmine-core": "~4.0.1", 49 | "jasmine-spec-reporter": "~7.0.0", 50 | "karma": "~6.3.16", 51 | "karma-chrome-launcher": "~3.1.0", 52 | "karma-coverage-istanbul-reporter": "~3.0.2", 53 | "karma-jasmine": "~4.0.0", 54 | "karma-jasmine-html-reporter": "^1.7.0", 55 | "protractor": "~7.0.0", 56 | "ts-node": "~10.9.2", 57 | "tslint": "~6.1.0", 58 | "typescript": "~5.4.5" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /frontend/src/app/admin.guard.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { AdminGuard } from './admin.guard'; 4 | 5 | describe('AdminGuard', () => { 6 | let guard: AdminGuard; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | guard = TestBed.inject(AdminGuard); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(guard).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /frontend/src/app/admin.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { 3 | CanActivate, 4 | ActivatedRouteSnapshot, 5 | RouterStateSnapshot, 6 | UrlTree, 7 | Router, 8 | } from '@angular/router'; 9 | import { Observable } from 'rxjs'; 10 | import { AuthService } from './auth.service'; 11 | import { GlobalManagerService } from './global-manager.service'; 12 | 13 | @Injectable({ 14 | providedIn: 'root', 15 | }) 16 | export class AdminGuard implements CanActivate { 17 | constructor( 18 | private globalManager: GlobalManagerService, 19 | private router: Router, 20 | private authService: AuthService 21 | ) {} 22 | canActivate( 23 | next: ActivatedRouteSnapshot, 24 | state: RouterStateSnapshot 25 | ): 26 | | Observable 27 | | Promise 28 | | boolean 29 | | UrlTree { 30 | return this.checkLogin(); 31 | } 32 | 33 | checkLogin(): boolean { 34 | if (localStorage.getItem('AUTH_TOKEN')) { 35 | this.globalManager.showLogin(true); 36 | if (this.authService.isAdmin()) { 37 | return true; 38 | } else { 39 | return false; 40 | } 41 | } else { 42 | // Navigate to the login page with extras 43 | this.router.navigate(['/login']); 44 | this.globalManager.showLogin(false); 45 | return false; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /frontend/src/app/administration/administration.component.html: -------------------------------------------------------------------------------- 1 | Administration 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /frontend/src/app/administration/administration.component.sass: -------------------------------------------------------------------------------- 1 | h5 2 | text-align: center 3 | -------------------------------------------------------------------------------- /frontend/src/app/administration/administration.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { AdministrationComponent } from './administration.component'; 4 | 5 | describe('AdministrationComponent', () => { 6 | let component: AdministrationComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(waitForAsync(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ AdministrationComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(AdministrationComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /frontend/src/app/administration/administration.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { InviteUserComponent } from './invite-user/invite-user.component'; 3 | import { SettingsComponent } from './settings/settings.component'; 4 | 5 | 6 | @Component({ 7 | selector: 'app-administration', 8 | templateUrl: './administration.component.html', 9 | styleUrls: ['./administration.component.sass'] 10 | }) 11 | export class AdministrationComponent implements OnInit { 12 | 13 | constructor() { } 14 | 15 | ngOnInit() { 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /frontend/src/app/administration/invite-user/invite-user.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Invite a User 5 | 6 | 7 | 8 | 9 | User Email: 10 | 11 | 13 | 14 | 15 | 16 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /frontend/src/app/administration/invite-user/invite-user.component.sass: -------------------------------------------------------------------------------- 1 | .centerBtn 2 | display: flex 3 | justify-content: center 4 | align-items: center 5 | height: 40px 6 | width: 75px 7 | border: 3px solid green 8 | 9 | .fill-width 10 | flex: 1 11 | -------------------------------------------------------------------------------- /frontend/src/app/administration/invite-user/invite-user.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { InviteUserComponent } from './invite-user.component'; 4 | 5 | describe('InviteUserComponent', () => { 6 | let component: InviteUserComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(waitForAsync(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ InviteUserComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(InviteUserComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /frontend/src/app/administration/invite-user/invite-user.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormGroup, FormBuilder, Validators } from '@angular/forms'; 3 | import { UserService } from '../../user.service'; 4 | import { Router } from '@angular/router'; 5 | import { AlertService } from '../../alert/alert.service'; 6 | 7 | @Component({ 8 | selector: 'app-invite-user', 9 | templateUrl: './invite-user.component.html', 10 | styleUrls: ['./invite-user.component.sass'], 11 | }) 12 | export class InviteUserComponent implements OnInit { 13 | inviteForm: FormGroup; 14 | constructor( 15 | private fb: FormBuilder, 16 | public userService: UserService, 17 | public router: Router, 18 | public alertService: AlertService 19 | ) { 20 | this.createForm(); 21 | } 22 | 23 | ngOnInit(): void {} 24 | 25 | createForm() { 26 | this.inviteForm = this.fb.group({ 27 | email: ['', [Validators.required, Validators.email]], 28 | }); 29 | } 30 | 31 | onSubmit(form) { 32 | const email = { email: form.value.email }; 33 | this.userService.inviteUser(email).subscribe((res: string) => { 34 | this.router.navigate(['administration']); 35 | this.alertService.success(res); 36 | }); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /frontend/src/app/administration/settings/settings.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Application Email Settings 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | An app password is a 12 | 16-digit passcode that gives a 13 | non-Google app or device permission to access your Google Account. Please follow this link for more 15 | information. 16 | 17 | 18 | 19 | Gmail Account: 20 | 21 | 23 | 24 | 25 | 26 | 27 | Gmail App Password: 28 | 29 | 32 | 33 | 34 | 35 | 36 | Company Name: 37 | 38 | 40 | 41 | 42 | 43 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /frontend/src/app/administration/settings/settings.component.sass: -------------------------------------------------------------------------------- 1 | .centerBtn 2 | display: flex 3 | justify-content: center 4 | align-items: center 5 | height: 40px 6 | width: 75px 7 | border: 3px solid green 8 | -------------------------------------------------------------------------------- /frontend/src/app/administration/settings/settings.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SettingsComponent } from './settings.component'; 4 | 5 | describe('SettingsComponent', () => { 6 | let component: SettingsComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ SettingsComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(SettingsComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /frontend/src/app/administration/settings/settings.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormGroup, FormBuilder, Validators } from '@angular/forms'; 3 | import { AuthService } from '../../auth.service'; 4 | import { Router, ActivatedRoute } from '@angular/router'; 5 | import { AlertService } from '../../alert/alert.service'; 6 | import { Settings } from '../../interfaces/Settings'; 7 | import { UserService } from '../../user.service'; 8 | import { AppService } from '../../app.service'; 9 | 10 | @Component({ 11 | selector: 'app-settings', 12 | templateUrl: './settings.component.html', 13 | styleUrls: ['./settings.component.sass'], 14 | }) 15 | export class SettingsComponent implements OnInit { 16 | settingsForm: FormGroup; 17 | isEdit = false; 18 | settings: Settings; 19 | public keyPlaceholder = '************************'; 20 | constructor( 21 | private fb: FormBuilder, 22 | public authService: AuthService, 23 | public router: Router, 24 | public alertService: AlertService, 25 | public activatedRoute: ActivatedRoute, 26 | public userService: UserService, 27 | public appService: AppService 28 | ) {} 29 | 30 | ngOnInit(): void { 31 | this.activatedRoute.data.subscribe(({ settings }) => { 32 | this.createForm(); 33 | this.settings = settings; 34 | this.rebuildForm(); 35 | }); 36 | } 37 | 38 | createForm() { 39 | this.settingsForm = this.fb.group({ 40 | fromEmail: [{ value: '', disabled: !this.isEdit }], 41 | fromEmailPassword: [{ value: '', disabled: !this.isEdit }], 42 | companyName: [{ value: '', disabled: !this.isEdit }], 43 | }); 44 | } 45 | 46 | rebuildForm() { 47 | this.settingsForm.reset({ 48 | fromEmail: this.settings?.fromEmail, 49 | fromEmailPassword: this.settings?.fromEmailPassword, 50 | companyName: this.settings?.companyName, 51 | }); 52 | } 53 | 54 | onSubmit(form: FormGroup) { 55 | if (!this.isEdit) { 56 | this.isEdit = true; 57 | this.settingsForm.enable(); 58 | } else { 59 | const settingsInfo = form.value; 60 | this.appService.updateConfig(settingsInfo).subscribe((res: string) => { 61 | this.alertService.success(res); 62 | this.isEdit = false; 63 | this.settingsForm.disable(); 64 | }); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /frontend/src/app/alert/alert.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { AlertComponent } from './alert/alert.component'; 4 | import { AlertService } from './alert.service'; 5 | 6 | @NgModule({ 7 | declarations: [AlertComponent], 8 | imports: [CommonModule], 9 | exports: [AlertComponent], 10 | providers: [AlertService] 11 | }) 12 | export class AlertModule {} 13 | -------------------------------------------------------------------------------- /frontend/src/app/alert/alert.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { AlertService } from './alert.service'; 4 | 5 | describe('AlertService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: AlertService = TestBed.inject(AlertService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /frontend/src/app/alert/alert.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Router, NavigationStart } from '@angular/router'; 3 | import { Alert, AlertType } from '../classes/Alert'; 4 | import { Subject, Observable } from 'rxjs'; 5 | import { filter } from 'rxjs/operators'; 6 | 7 | @Injectable({ 8 | providedIn: 'root' 9 | }) 10 | export class AlertService { 11 | private subject = new Subject(); 12 | private keepAfterRouteChange = false; 13 | 14 | constructor(private router: Router) { 15 | this.router.events.subscribe((event) => { 16 | if (event instanceof NavigationStart) { 17 | if (this.keepAfterRouteChange) { 18 | // only keep for a single route change 19 | this.keepAfterRouteChange = false; 20 | } else { 21 | // clear alert messages 22 | this.clear(); 23 | } 24 | } 25 | }); 26 | } 27 | 28 | // enable subscribing to alerts observable 29 | onAlert(alertId?: string): Observable { 30 | return this.subject.asObservable().pipe(filter((x) => x && x.alertId === alertId)); 31 | } 32 | 33 | // convenience methods 34 | success(message: string, alertId?: string) { 35 | this.alert(new Alert({ message, type: AlertType.Success, alertId })); 36 | } 37 | 38 | error(message: string, alertId?: string) { 39 | this.alert(new Alert({ message, type: AlertType.Error, alertId })); 40 | } 41 | 42 | info(message: string, alertId?: string) { 43 | this.alert(new Alert({ message, type: AlertType.Info, alertId })); 44 | } 45 | 46 | warn(message: string, alertId?: string) { 47 | this.alert(new Alert({ message, type: AlertType.Warning, alertId })); 48 | } 49 | 50 | clear(alertId?: string) { 51 | this.subject.next(new Alert({ alertId })); 52 | } 53 | 54 | // main alert method 55 | alert(alert: Alert) { 56 | this.keepAfterRouteChange = alert.keepAfterRouteChange; 57 | this.subject.next(alert); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /frontend/src/app/alert/alert/alert.component.html: -------------------------------------------------------------------------------- 1 | 2 | {{ alert.message }} 3 | × 4 | 5 | -------------------------------------------------------------------------------- /frontend/src/app/alert/alert/alert.component.sass: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softrams/bulwark/6d27b9b36bfa6d20c0bad6c923b4896e1702366b/frontend/src/app/alert/alert/alert.component.sass -------------------------------------------------------------------------------- /frontend/src/app/alert/alert/alert.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { AlertComponent } from './alert.component'; 4 | 5 | describe('AlertComponent', () => { 6 | let component: AlertComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(waitForAsync(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ AlertComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(AlertComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /frontend/src/app/alert/alert/alert.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, OnDestroy } from '@angular/core'; 2 | import { Subscription } from 'rxjs'; 3 | import { Alert, AlertType } from 'src/app/classes/Alert'; 4 | import { AlertService } from '../alert.service'; 5 | 6 | @Component({ 7 | selector: 'app-alert', 8 | templateUrl: './alert.component.html', 9 | styleUrls: ['./alert.component.sass'] 10 | }) 11 | export class AlertComponent implements OnInit, OnDestroy { 12 | @Input() id: string; 13 | 14 | alerts: Alert[] = []; 15 | subscription: Subscription; 16 | constructor(private alertService: AlertService) {} 17 | 18 | ngOnInit() { 19 | this.subscription = this.alertService.onAlert(this.id).subscribe((alert) => { 20 | if (!alert.message) { 21 | // clear alerts when an empty alert is received 22 | this.alerts = []; 23 | return; 24 | } 25 | this.alerts.push(alert); 26 | }); 27 | } 28 | 29 | ngOnDestroy() { 30 | // unsubscribe to avoid memory leaks 31 | this.subscription.unsubscribe(); 32 | } 33 | 34 | removeAlert(alert: Alert) { 35 | // remove specified alert from array 36 | this.alerts = this.alerts.filter((x) => x !== alert); 37 | } 38 | 39 | cssClass(alert: Alert) { 40 | if (!alert) { 41 | return; 42 | } 43 | // return css class based on alert type 44 | switch (alert.type) { 45 | case AlertType.Success: 46 | return 'alert alert-success'; 47 | case AlertType.Error: 48 | return 'alert alert-danger'; 49 | case AlertType.Info: 50 | return 'alert alert-info'; 51 | case AlertType.Warning: 52 | return 'alert alert-warning'; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /frontend/src/app/apikey-management/apikey-management.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | API Key Management 4 | 5 | 6 | 7 | 8 | 9 | 10 | User Email 11 | 12 | 13 | 14 | 15 | 16 | Created Date 17 | 18 | 19 | 20 | 21 | Last Updated Date 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | {{apiKey?.user?.email}} 30 | {{apiKey?.createdDate | date: 'longDate':'UTC' }} 31 | {{apiKey?.lastUpdatedDate | date: 'longDate':'UTC' }} 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /frontend/src/app/apikey-management/apikey-management.component.sass: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softrams/bulwark/6d27b9b36bfa6d20c0bad6c923b4896e1702366b/frontend/src/app/apikey-management/apikey-management.component.sass -------------------------------------------------------------------------------- /frontend/src/app/apikey-management/apikey-management.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ApikeyManagementComponent } from './apikey-management.component'; 4 | 5 | describe('ApikeyManagementComponent', () => { 6 | let component: ApikeyManagementComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ApikeyManagementComponent], 12 | }).compileComponents(); 13 | }); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(ApikeyManagementComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /frontend/src/app/apikey-management/apikey-management.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { AlertService } from '../alert/alert.service'; 3 | import { AuthService } from '../auth.service'; 4 | import { ApiKey } from '../interfaces/ApiKey'; 5 | 6 | @Component({ 7 | selector: 'app-apikey-management', 8 | templateUrl: './apikey-management.component.html', 9 | styleUrls: ['./apikey-management.component.sass'], 10 | }) 11 | export class ApikeyManagementComponent implements OnInit { 12 | apiKeys: ApiKey[] = []; 13 | constructor( 14 | public authService: AuthService, 15 | public alertService: AlertService 16 | ) {} 17 | 18 | ngOnInit(): void { 19 | this.getActiveApiKeys(); 20 | } 21 | 22 | getActiveApiKeys() { 23 | this.authService.getApiKeysInfo().subscribe((res: ApiKey[]) => { 24 | this.apiKeys = res; 25 | }); 26 | } 27 | 28 | deactivateApiKey(id: number) { 29 | const r = confirm( 30 | 'This API key may be in use by an external entity. Are you sure you want to delete it?' 31 | ); 32 | if (r) { 33 | this.authService.adminDeactivateApiKey(id).subscribe((res: string) => { 34 | this.alertService.success(res); 35 | this.getActiveApiKeys(); 36 | }); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /frontend/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /frontend/src/app/app.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softrams/bulwark/6d27b9b36bfa6d20c0bad6c923b4896e1702366b/frontend/src/app/app.component.scss -------------------------------------------------------------------------------- /frontend/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, waitForAsync } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { AppComponent } from './app.component'; 4 | 5 | describe('AppComponent', () => { 6 | beforeEach(waitForAsync(() => { 7 | TestBed.configureTestingModule({ 8 | imports: [ 9 | RouterTestingModule 10 | ], 11 | declarations: [ 12 | AppComponent 13 | ], 14 | }).compileComponents(); 15 | })); 16 | 17 | it('should create the app', () => { 18 | const fixture = TestBed.createComponent(AppComponent); 19 | const app = fixture.debugElement.componentInstance; 20 | expect(app).toBeTruthy(); 21 | }); 22 | 23 | it(`should have as title 'frontend'`, () => { 24 | const fixture = TestBed.createComponent(AppComponent); 25 | const app = fixture.debugElement.componentInstance; 26 | expect(app.title).toEqual('frontend'); 27 | }); 28 | 29 | it('should render title in a h1 tag', () => { 30 | const fixture = TestBed.createComponent(AppComponent); 31 | fixture.detectChanges(); 32 | const compiled = fixture.debugElement.nativeElement; 33 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to frontend!'); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /frontend/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.scss'] 7 | }) 8 | export class AppComponent {} 9 | -------------------------------------------------------------------------------- /frontend/src/app/app.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { AppService } from './app.service'; 4 | 5 | describe('AppService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: AppService = TestBed.inject(AppService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /frontend/src/app/assessment-form/Assessment.ts: -------------------------------------------------------------------------------- 1 | import { URL } from 'url'; 2 | import { User } from '../interfaces/User'; 3 | 4 | export class Assessment { 5 | constructor( 6 | public id: number, 7 | public name: string, 8 | public executiveSummary: string, 9 | public asset: number, 10 | public jiraId: string, 11 | public testUrl: URL, 12 | public prodUrl: URL, 13 | public scope: string, 14 | public tag: number, 15 | public startDate: Date, 16 | public endDate: Date, 17 | public testers: User[] 18 | ) {} 19 | } -------------------------------------------------------------------------------- /frontend/src/app/assessment-form/assessment-form.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Assessment Name 5 | 6 | Executive Summary 7 | 9 | JIRA URL 10 | 11 | Test URL 12 | 13 | Production URL 14 | 15 | Scope of the Assessment 16 | 17 | Source Code Tag 18 | 19 | Testers 20 | 22 | 23 | × 24 | {{item.firstName}} {{item.lastName}} 25 | 26 | 27 | {{item.firstName}} {{item.lastName}} 28 | 29 | 30 | Start Date 31 | 32 | End Date 33 | 34 | 35 | 37 | Submit 38 | 39 | 41 | Back to Assessments 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /frontend/src/app/assessment-form/assessment-form.component.sass: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softrams/bulwark/6d27b9b36bfa6d20c0bad6c923b4896e1702366b/frontend/src/app/assessment-form/assessment-form.component.sass -------------------------------------------------------------------------------- /frontend/src/app/assessment-form/assessment-form.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { AssessmentFormComponent } from './assessment-form.component'; 4 | 5 | describe('AssessmentFormComponent', () => { 6 | let component: AssessmentFormComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(waitForAsync(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ AssessmentFormComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(AssessmentFormComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /frontend/src/app/assessments/assessments.component.sass: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softrams/bulwark/6d27b9b36bfa6d20c0bad6c923b4896e1702366b/frontend/src/app/assessments/assessments.component.sass -------------------------------------------------------------------------------- /frontend/src/app/assessments/assessments.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { AssessmentsComponent } from './assessments.component'; 4 | 5 | describe('AssessmentsComponent', () => { 6 | let component: AssessmentsComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(waitForAsync(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ AssessmentsComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(AssessmentsComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /frontend/src/app/asset-form/Asset.ts: -------------------------------------------------------------------------------- 1 | import { Jira } from './Jira'; 2 | 3 | export interface Asset { 4 | id: number; 5 | name: string; 6 | organization: number; 7 | jira?: Jira; 8 | } 9 | -------------------------------------------------------------------------------- /frontend/src/app/asset-form/Jira.ts: -------------------------------------------------------------------------------- 1 | export interface Jira { 2 | id?: number; 3 | username?: string; 4 | host?: string; 5 | url?: string; 6 | apiKey?: string; 7 | } 8 | -------------------------------------------------------------------------------- /frontend/src/app/asset-form/asset-form.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Asset Name 5 | 6 | 7 | 8 | 9 | Jira Integration 10 | 11 | 12 | 13 | Username 14 | 16 | Host 17 | 19 | API Key 20 | 22 | 23 | 24 | 26 | 27 | 28 | 29 | 30 | 32 | 33 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /frontend/src/app/asset-form/asset-form.component.sass: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softrams/bulwark/6d27b9b36bfa6d20c0bad6c923b4896e1702366b/frontend/src/app/asset-form/asset-form.component.sass -------------------------------------------------------------------------------- /frontend/src/app/asset-form/asset-form.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { AssetFormComponent } from './asset-form.component'; 4 | 5 | describe('AssetFormComponent', () => { 6 | let component: AssetFormComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(waitForAsync(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ AssetFormComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(AssetFormComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /frontend/src/app/auth.guard.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { AuthGuard } from './auth.guard'; 4 | 5 | describe('AuthGuard', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [AuthGuard] 9 | }); 10 | }); 11 | 12 | it('should ...', inject([AuthGuard], (guard: AuthGuard) => { 13 | expect(guard).toBeTruthy(); 14 | })); 15 | }); 16 | -------------------------------------------------------------------------------- /frontend/src/app/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { 3 | CanActivate, 4 | ActivatedRouteSnapshot, 5 | RouterStateSnapshot, 6 | UrlTree, 7 | Router 8 | } from '@angular/router'; 9 | import { Observable } from 'rxjs'; 10 | import { AuthService } from './auth.service'; 11 | import { GlobalManagerService } from './global-manager.service'; 12 | 13 | @Injectable({ 14 | providedIn: 'root' 15 | }) 16 | export class AuthGuard implements CanActivate { 17 | constructor( 18 | private globalManager: GlobalManagerService, 19 | private router: Router 20 | ) {} 21 | canActivate( 22 | next: ActivatedRouteSnapshot, 23 | state: RouterStateSnapshot 24 | ): 25 | | Observable 26 | | Promise 27 | | boolean 28 | | UrlTree { 29 | return this.checkLogin(); 30 | } 31 | 32 | checkLogin(): boolean { 33 | if (localStorage.getItem('AUTH_TOKEN')) { 34 | this.globalManager.showLogin(true); 35 | return true; 36 | } else { 37 | // Navigate to the login page with extras 38 | this.router.navigate(['/login']); 39 | this.globalManager.showLogin(false); 40 | return false; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /frontend/src/app/auth.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { AuthService } from './auth.service'; 4 | 5 | describe('AuthService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: AuthService = TestBed.inject(AuthService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /frontend/src/app/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { environment } from '../environments/environment'; 4 | import { Tokens } from './interfaces/Tokens'; 5 | import jwt_decode from 'jwt-decode'; 6 | @Injectable({ 7 | providedIn: 'root', 8 | }) 9 | export class AuthService { 10 | constructor(private http: HttpClient) {} 11 | 12 | api = environment.apiUrl; 13 | isLoggedIn = false; 14 | 15 | login(creds) { 16 | return this.http.post(`${this.api}/login`, creds); 17 | } 18 | 19 | logout() { 20 | localStorage.removeItem('REFRESH_TOKEN'); 21 | localStorage.removeItem('AUTH_TOKEN'); 22 | } 23 | 24 | setTokens(tokens: Tokens) { 25 | localStorage.setItem('AUTH_TOKEN', tokens.token); 26 | localStorage.setItem('REFRESH_TOKEN', tokens.refreshToken); 27 | } 28 | 29 | getUserToken() { 30 | return localStorage.getItem('AUTH_TOKEN'); 31 | } 32 | 33 | getUserFromToken() { 34 | return jwt_decode(localStorage.getItem('AUTH_TOKEN')); 35 | } 36 | 37 | isAdmin() { 38 | const token = this.getUserFromToken(); 39 | let found = false; 40 | // tslint:disable-next-line: no-string-literal 41 | found = token['admin']; 42 | return found; 43 | } 44 | 45 | getRefreshToken() { 46 | return localStorage.getItem('REFRESH_TOKEN'); 47 | } 48 | 49 | forgotPassword(email) { 50 | return this.http.patch(`${this.api}/forgot-password`, email); 51 | } 52 | 53 | passwordReset(creds) { 54 | return this.http.patch(`${this.api}/password-reset`, creds); 55 | } 56 | 57 | updatePassword( 58 | oldPassword: string, 59 | newPassword: string, 60 | confirmNewPassword: string 61 | ) { 62 | return this.http.patch(`${this.api}/user/password`, { 63 | oldPassword, 64 | newPassword, 65 | confirmNewPassword, 66 | }); 67 | } 68 | 69 | updateUserEmail(email: string, newEmail: string) { 70 | return this.http.post(`${this.api}/user/email`, { 71 | email, 72 | newEmail, 73 | }); 74 | } 75 | 76 | validateUserEmailRequest(password: string, uuid: string) { 77 | return this.http.post(`${this.api}/user/email/validate`, { 78 | password, 79 | uuid, 80 | }); 81 | } 82 | 83 | revokeUserEmail() { 84 | return this.http.post(`${this.api}/user/email/revoke`, null); 85 | } 86 | 87 | refreshSession() { 88 | const refreshToken = this.getRefreshToken(); 89 | return this.http.post(`${this.api}/refresh`, { refreshToken }); 90 | } 91 | 92 | generateApiKey() { 93 | return this.http.post(`${this.api}/user/key`, null); 94 | } 95 | 96 | getApiKeyInfo() { 97 | return this.http.get(`${this.api}/user/key`); 98 | } 99 | 100 | getApiKeysInfo() { 101 | return this.http.get(`${this.api}/keys`); 102 | } 103 | 104 | deactivateApiKey(id: number) { 105 | return this.http.patch(`${this.api}/user/key/${id}`, null); 106 | } 107 | 108 | adminDeactivateApiKey(id: number) { 109 | return this.http.patch(`${this.api}/key/${id}`, null); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /frontend/src/app/classes/Alert.ts: -------------------------------------------------------------------------------- 1 | export class Alert { 2 | type: AlertType; 3 | message: string; 4 | alertId: string; 5 | keepAfterRouteChange: boolean; 6 | 7 | constructor(init?: Partial) { 8 | Object.assign(this, init); 9 | } 10 | } 11 | 12 | export enum AlertType { 13 | Success, 14 | Error, 15 | Info, 16 | Warning 17 | } 18 | -------------------------------------------------------------------------------- /frontend/src/app/classes/ProblemLocation.ts: -------------------------------------------------------------------------------- 1 | export class ProblemLocation { 2 | constructor(private id: number, public location: string, public target: string) {} 3 | } 4 | -------------------------------------------------------------------------------- /frontend/src/app/classes/Resource.ts: -------------------------------------------------------------------------------- 1 | export class Resource { 2 | constructor(private id: number, public resURL: string) {} 3 | } 4 | -------------------------------------------------------------------------------- /frontend/src/app/dashboard/dashboard.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Organization ID 6 | 7 | Organization Name 8 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | {{org.id}} 26 | {{org.name}} 27 | 28 | 30 | 31 | 32 | 33 | 36 | 37 | 38 | 40 | 41 | 42 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 53 | Add Organization 54 | 55 | 57 | View Archive 58 | 59 | 61 | View Active 62 | 63 | 64 | -------------------------------------------------------------------------------- /frontend/src/app/dashboard/dashboard.component.sass: -------------------------------------------------------------------------------- 1 | .card-img-top 2 | padding: 20px -------------------------------------------------------------------------------- /frontend/src/app/dashboard/dashboard.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { DashboardComponent } from './dashboard.component'; 4 | 5 | describe('DashboardComponent', () => { 6 | let component: DashboardComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(waitForAsync(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ DashboardComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(DashboardComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /frontend/src/app/email-validate/email-validate.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Email Update Validation 4 | 5 | Please enter your password to validate the 6 | email update request 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | Return to login 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /frontend/src/app/email-validate/email-validate.component.sass: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softrams/bulwark/6d27b9b36bfa6d20c0bad6c923b4896e1702366b/frontend/src/app/email-validate/email-validate.component.sass -------------------------------------------------------------------------------- /frontend/src/app/email-validate/email-validate.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { EmailValidateComponent } from './email-validate.component'; 4 | 5 | describe('EmailValidateComponent', () => { 6 | let component: EmailValidateComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ EmailValidateComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(EmailValidateComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /frontend/src/app/email-validate/email-validate.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormGroup, FormBuilder, Validators } from '@angular/forms'; 3 | import { AuthService } from '../auth.service'; 4 | import { Router, ActivatedRoute } from '@angular/router'; 5 | import { AlertService } from '../alert/alert.service'; 6 | 7 | @Component({ 8 | selector: 'app-email-validate', 9 | templateUrl: './email-validate.component.html', 10 | styleUrls: ['./email-validate.component.sass'], 11 | }) 12 | export class EmailValidateComponent implements OnInit { 13 | uuid: string; 14 | emailUpdateForm: FormGroup; 15 | constructor( 16 | private activatedRoute: ActivatedRoute, 17 | private fb: FormBuilder, 18 | public authService: AuthService, 19 | public router: Router, 20 | public alertService: AlertService 21 | ) { 22 | this.uuid = this.activatedRoute.snapshot.paramMap.get('uuid'); 23 | this.createForm(); 24 | } 25 | 26 | ngOnInit(): void {} 27 | 28 | createForm() { 29 | this.emailUpdateForm = this.fb.group({ 30 | password: ['', Validators.required], 31 | }); 32 | } 33 | 34 | onSubmit(form) { 35 | this.authService 36 | .validateUserEmailRequest(form.value.password, this.uuid) 37 | .subscribe((res: string) => { 38 | this.router.navigate(['login']); 39 | this.alertService.success(res); 40 | }); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /frontend/src/app/enums/roles.enum.ts: -------------------------------------------------------------------------------- 1 | export const ROLE = { 2 | ADMIN: 'Admin', 3 | READONLY: 'Read-Only', 4 | TESTER: 'Tester', 5 | }; 6 | -------------------------------------------------------------------------------- /frontend/src/app/footer/footer.component.html: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /frontend/src/app/footer/footer.component.sass: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softrams/bulwark/6d27b9b36bfa6d20c0bad6c923b4896e1702366b/frontend/src/app/footer/footer.component.sass -------------------------------------------------------------------------------- /frontend/src/app/footer/footer.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { FooterComponent } from './footer.component'; 4 | 5 | describe('FooterComponent', () => { 6 | let component: FooterComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(waitForAsync(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ FooterComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(FooterComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /frontend/src/app/footer/footer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-footer', 5 | templateUrl: './footer.component.html', 6 | styleUrls: ['./footer.component.sass'] 7 | }) 8 | export class FooterComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/app/forgot-password/forgot-password.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 21 | 22 | 23 | 24 | 25 | Back to login 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /frontend/src/app/forgot-password/forgot-password.component.sass: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softrams/bulwark/6d27b9b36bfa6d20c0bad6c923b4896e1702366b/frontend/src/app/forgot-password/forgot-password.component.sass -------------------------------------------------------------------------------- /frontend/src/app/forgot-password/forgot-password.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { ForgotPasswordComponent } from './forgot-password.component'; 4 | 5 | describe('ForgotPasswordComponent', () => { 6 | let component: ForgotPasswordComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(waitForAsync(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ ForgotPasswordComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ForgotPasswordComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /frontend/src/app/forgot-password/forgot-password.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormGroup, FormBuilder, Validators } from '@angular/forms'; 3 | import { AuthService } from '../auth.service'; 4 | import { Router } from '@angular/router'; 5 | import { AlertService } from '../alert/alert.service'; 6 | 7 | @Component({ 8 | selector: 'app-forgot-password', 9 | templateUrl: './forgot-password.component.html', 10 | styleUrls: ['./forgot-password.component.sass'], 11 | }) 12 | export class ForgotPasswordComponent implements OnInit { 13 | pwdResetForm: FormGroup; 14 | constructor( 15 | private fb: FormBuilder, 16 | public authService: AuthService, 17 | public router: Router, 18 | public alertService: AlertService 19 | ) { 20 | this.createForm(); 21 | } 22 | 23 | ngOnInit(): void {} 24 | 25 | createForm() { 26 | this.pwdResetForm = this.fb.group({ 27 | email: ['', [Validators.required, Validators.email]], 28 | }); 29 | } 30 | 31 | onSubmit(form) { 32 | const email = { email: form.value.email }; 33 | this.authService.forgotPassword(email).subscribe((res: string) => { 34 | this.alertService.success(res); 35 | this.pwdResetForm.reset(); 36 | }); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /frontend/src/app/global-manager.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { GlobalManagerService } from './global-manager.service'; 4 | 5 | describe('GlobalManagerService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: GlobalManagerService = TestBed.inject(GlobalManagerService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /frontend/src/app/global-manager.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { BehaviorSubject, Observable } from 'rxjs'; 3 | 4 | @Injectable({ 5 | providedIn: 'root' 6 | }) 7 | export class GlobalManagerService { 8 | private loggedIn: BehaviorSubject = new BehaviorSubject( 9 | null 10 | ); 11 | public loggedIn$: Observable = this.loggedIn.asObservable(); 12 | 13 | constructor() {} 14 | 15 | showLogin(ifShow: boolean) { 16 | this.loggedIn.next(ifShow); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /frontend/src/app/interfaces/ApiKey.ts: -------------------------------------------------------------------------------- 1 | export interface ApiKey { 2 | id: number; 3 | createdDate: Date; 4 | lastUpdatedDate: Date; 5 | active?: boolean; 6 | email?: boolean; 7 | } 8 | -------------------------------------------------------------------------------- /frontend/src/app/interfaces/App_File.ts: -------------------------------------------------------------------------------- 1 | import { SafeUrl } from '@angular/platform-browser'; 2 | 3 | export interface AppFile extends File { 4 | id: number; 5 | buffer: any; 6 | encoding: string; 7 | fieldName: string; 8 | mimetype: string; 9 | originalname: string; 10 | size: number; 11 | imgUrl: SafeUrl; 12 | } 13 | -------------------------------------------------------------------------------- /frontend/src/app/interfaces/Organization.ts: -------------------------------------------------------------------------------- 1 | export interface Organization { 2 | id: number; 3 | name: string; 4 | status: string; 5 | } 6 | -------------------------------------------------------------------------------- /frontend/src/app/interfaces/Screenshot.ts: -------------------------------------------------------------------------------- 1 | import { SafeUrl } from '@angular/platform-browser'; 2 | 3 | export interface Screenshot { 4 | url: SafeUrl; 5 | file: any; 6 | fileName: string; 7 | fileId: number; 8 | } 9 | -------------------------------------------------------------------------------- /frontend/src/app/interfaces/Settings.ts: -------------------------------------------------------------------------------- 1 | export interface Settings { 2 | fromEmail?: string; 3 | fromEmailPassword?: string; 4 | companyName?: string; 5 | } 6 | -------------------------------------------------------------------------------- /frontend/src/app/interfaces/Team.ts: -------------------------------------------------------------------------------- 1 | import { Organization } from '../org-form/Organization'; 2 | import { Asset } from '../asset-form/Asset'; 3 | interface Role { 4 | name: string; 5 | } 6 | export interface Team { 7 | id?: number; 8 | name: string; 9 | organization: Organization; 10 | assets?: Asset[]; 11 | assetIds?: number[]; 12 | role: any; 13 | users: number[]; 14 | } 15 | -------------------------------------------------------------------------------- /frontend/src/app/interfaces/Tokens.ts: -------------------------------------------------------------------------------- 1 | export interface Tokens { 2 | token: string; 3 | refreshToken: string; 4 | } 5 | -------------------------------------------------------------------------------- /frontend/src/app/interfaces/User.ts: -------------------------------------------------------------------------------- 1 | import { Team } from './Team'; 2 | 3 | export interface User { 4 | firstName: string; 5 | lastName: string; 6 | title: string; 7 | password?: string; 8 | confirmPassword?: string; 9 | email?: string; 10 | newEmail?: string; 11 | id?: string; 12 | active?: boolean; 13 | teams?: Team[]; 14 | } 15 | -------------------------------------------------------------------------------- /frontend/src/app/loader.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { LoaderService } from './loader.service'; 4 | 5 | describe('LoaderService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: LoaderService = TestBed.inject(LoaderService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /frontend/src/app/loader.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; 3 | import { Subject } from 'rxjs/internal/Subject'; 4 | 5 | @Injectable({ 6 | providedIn: 'root' 7 | }) 8 | export class LoaderService { 9 | isLoading = new Subject(); 10 | show() { 11 | this.isLoading.next(true); 12 | } 13 | hide() { 14 | this.isLoading.next(false); 15 | } 16 | 17 | constructor() {} 18 | } 19 | -------------------------------------------------------------------------------- /frontend/src/app/login/login.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Forgot Password? 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /frontend/src/app/login/login.component.sass: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softrams/bulwark/6d27b9b36bfa6d20c0bad6c923b4896e1702366b/frontend/src/app/login/login.component.sass -------------------------------------------------------------------------------- /frontend/src/app/login/login.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { LoginComponent } from './login.component'; 4 | 5 | describe('LoginComponent', () => { 6 | let component: LoginComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(waitForAsync(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ LoginComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(LoginComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /frontend/src/app/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormGroup, FormBuilder, Validators } from '@angular/forms'; 3 | import { AuthService } from '../auth.service'; 4 | import { Router } from '@angular/router'; 5 | import { Tokens } from '../interfaces/Tokens'; 6 | 7 | @Component({ 8 | selector: 'app-login', 9 | templateUrl: './login.component.html', 10 | styleUrls: ['./login.component.sass'], 11 | }) 12 | export class LoginComponent implements OnInit { 13 | loginForm: FormGroup; 14 | submitted = false; 15 | 16 | constructor( 17 | private fb: FormBuilder, 18 | public authService: AuthService, 19 | public router: Router 20 | ) { 21 | this.createForm(); 22 | } 23 | 24 | ngOnInit() {} 25 | 26 | createForm() { 27 | this.loginForm = this.fb.group({ 28 | email: ['', [Validators.required, Validators.email]], 29 | password: ['', Validators.required], 30 | }); 31 | } 32 | 33 | onSubmit(creds) { 34 | const login = { email: creds.value.email, password: creds.value.password }; 35 | this.authService.login(login).subscribe((tokens: Tokens) => { 36 | this.authService.setTokens(tokens); 37 | this.router.navigate(['dashboard']); 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /frontend/src/app/navbar/navbar.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | Menu 13 | 14 | 15 | Profile 16 | Administration 17 | 18 | Logout 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /frontend/src/app/navbar/navbar.component.sass: -------------------------------------------------------------------------------- 1 | .dropdown-menu 2 | left: -50px 3 | margin: .125rem -10px 0 4 | -------------------------------------------------------------------------------- /frontend/src/app/navbar/navbar.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { NavbarComponent } from './navbar.component'; 4 | 5 | describe('NavbarComponent', () => { 6 | let component: NavbarComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(waitForAsync(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ NavbarComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(NavbarComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /frontend/src/app/navbar/navbar.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; 2 | import { LoaderService } from '../loader.service'; 3 | import { AuthService } from '../auth.service'; 4 | import { GlobalManagerService } from '../global-manager.service'; 5 | 6 | @Component({ 7 | selector: 'app-navbar', 8 | templateUrl: './navbar.component.html', 9 | styleUrls: ['./navbar.component.sass'], 10 | changeDetection: ChangeDetectionStrategy.OnPush 11 | }) 12 | export class NavbarComponent implements OnInit { 13 | loggedIn$; 14 | 15 | constructor( 16 | public loaderService: LoaderService, 17 | public authService: AuthService, 18 | private globalManager: GlobalManagerService 19 | ) { 20 | this.loggedIn$ = this.globalManager.loggedIn$; 21 | } 22 | 23 | ngOnInit() {} 24 | 25 | logout() { 26 | this.authService.logout(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /frontend/src/app/org-form/Organization.ts: -------------------------------------------------------------------------------- 1 | export class Organization { 2 | constructor(public id: number, public name: string) {} 3 | } 4 | -------------------------------------------------------------------------------- /frontend/src/app/org-form/org-form.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Organization Name 5 | 7 | 8 | 10 | 11 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /frontend/src/app/org-form/org-form.component.sass: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softrams/bulwark/6d27b9b36bfa6d20c0bad6c923b4896e1702366b/frontend/src/app/org-form/org-form.component.sass -------------------------------------------------------------------------------- /frontend/src/app/org-form/org-form.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { OrgFormComponent } from './org-form.component'; 4 | 5 | describe('OrgFormComponent', () => { 6 | let component: OrgFormComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(waitForAsync(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ OrgFormComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(OrgFormComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /frontend/src/app/organization/organization.component.sass: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softrams/bulwark/6d27b9b36bfa6d20c0bad6c923b4896e1702366b/frontend/src/app/organization/organization.component.sass -------------------------------------------------------------------------------- /frontend/src/app/organization/organization.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { OrganizationComponent } from './organization.component'; 4 | 5 | describe('OrganizationComponent', () => { 6 | let component: OrganizationComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(waitForAsync(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ OrganizationComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(OrganizationComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /frontend/src/app/page-not-found/page-not-found.component.html: -------------------------------------------------------------------------------- 1 | Page not found 2 | -------------------------------------------------------------------------------- /frontend/src/app/page-not-found/page-not-found.component.sass: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softrams/bulwark/6d27b9b36bfa6d20c0bad6c923b4896e1702366b/frontend/src/app/page-not-found/page-not-found.component.sass -------------------------------------------------------------------------------- /frontend/src/app/page-not-found/page-not-found.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { PageNotFoundComponent } from './page-not-found.component'; 4 | 5 | describe('PageNotFoundComponent', () => { 6 | let component: PageNotFoundComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(waitForAsync(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ PageNotFoundComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(PageNotFoundComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /frontend/src/app/page-not-found/page-not-found.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-page-not-found', 5 | templateUrl: './page-not-found.component.html', 6 | styleUrls: ['./page-not-found.component.sass'] 7 | }) 8 | export class PageNotFoundComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/app/password-reset/password-reset.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 23 | 30 | 31 | 32 | 33 | 34 | Return to login 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /frontend/src/app/password-reset/password-reset.component.sass: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softrams/bulwark/6d27b9b36bfa6d20c0bad6c923b4896e1702366b/frontend/src/app/password-reset/password-reset.component.sass -------------------------------------------------------------------------------- /frontend/src/app/password-reset/password-reset.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { PasswordResetComponent } from './password-reset.component'; 4 | 5 | describe('PasswordResetComponent', () => { 6 | let component: PasswordResetComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(waitForAsync(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ PasswordResetComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(PasswordResetComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /frontend/src/app/password-reset/password-reset.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormGroup, FormBuilder, Validators } from '@angular/forms'; 3 | import { AuthService } from '../auth.service'; 4 | import { Router, ActivatedRoute } from '@angular/router'; 5 | import { AlertService } from '../alert/alert.service'; 6 | 7 | @Component({ 8 | selector: 'app-password-reset', 9 | templateUrl: './password-reset.component.html', 10 | styleUrls: ['./password-reset.component.sass'], 11 | }) 12 | export class PasswordResetComponent implements OnInit { 13 | uuid: string; 14 | pwdResetForm: FormGroup; 15 | constructor( 16 | private activatedRoute: ActivatedRoute, 17 | private fb: FormBuilder, 18 | public authService: AuthService, 19 | public router: Router, 20 | public alertService: AlertService 21 | ) { 22 | this.uuid = this.activatedRoute.snapshot.paramMap.get('uuid'); 23 | this.createForm(); 24 | } 25 | 26 | ngOnInit(): void {} 27 | 28 | createForm() { 29 | this.pwdResetForm = this.fb.group({ 30 | password: ['', Validators.required], 31 | confirmPassword: ['', Validators.required], 32 | }); 33 | } 34 | 35 | onSubmit(form) { 36 | const pwdUpdateObj = { 37 | password: form.value.password, 38 | confirmPassword: form.value.confirmPassword, 39 | uuid: this.uuid, 40 | }; 41 | this.authService.passwordReset(pwdUpdateObj).subscribe((res: string) => { 42 | this.alertService.success(res); 43 | this.router.navigate(['login']); 44 | }); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /frontend/src/app/register/register.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Bulwark Registration 4 | 5 | 6 | 8 | 10 | 12 | 14 | 16 | 17 | 18 | 19 | 20 | 21 | Return to login 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /frontend/src/app/register/register.component.sass: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softrams/bulwark/6d27b9b36bfa6d20c0bad6c923b4896e1702366b/frontend/src/app/register/register.component.sass -------------------------------------------------------------------------------- /frontend/src/app/register/register.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { RegisterComponent } from './register.component'; 4 | 5 | describe('RegisterComponent', () => { 6 | let component: RegisterComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(waitForAsync(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ RegisterComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(RegisterComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /frontend/src/app/register/register.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormGroup, FormBuilder, Validators } from '@angular/forms'; 3 | import { UserService } from '../user.service'; 4 | import { Router, ActivatedRoute } from '@angular/router'; 5 | import { AlertService } from '../alert/alert.service'; 6 | 7 | @Component({ 8 | selector: 'app-register', 9 | templateUrl: './register.component.html', 10 | styleUrls: ['./register.component.sass'], 11 | }) 12 | export class RegisterComponent implements OnInit { 13 | uuid: string; 14 | registerForm: FormGroup; 15 | constructor( 16 | private activatedRoute: ActivatedRoute, 17 | private fb: FormBuilder, 18 | public userService: UserService, 19 | public router: Router, 20 | public alertService: AlertService 21 | ) { 22 | this.uuid = this.activatedRoute.snapshot.paramMap.get('uuid'); 23 | this.createForm(); 24 | } 25 | 26 | ngOnInit(): void {} 27 | 28 | createForm() { 29 | this.registerForm = this.fb.group({ 30 | firstName: ['', Validators.required], 31 | lastName: ['', Validators.required], 32 | title: ['', Validators.required], 33 | password: ['', Validators.required], 34 | confirmPassword: ['', Validators.required], 35 | }); 36 | } 37 | 38 | onSubmit(form) { 39 | const registerObj = { 40 | firstName: form.value.firstName, 41 | lastName: form.value.lastName, 42 | title: form.value.title, 43 | password: form.value.password, 44 | confirmPassword: form.value.confirmPassword, 45 | uuid: this.uuid, 46 | }; 47 | this.userService.registerUser(registerObj).subscribe((res: string) => { 48 | this.router.navigate(['login']); 49 | this.alertService.success(res); 50 | }); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /frontend/src/app/report/report.component.sass: -------------------------------------------------------------------------------- 1 | div 2 | &.gallery 3 | margin: 5px 4 | border: 1px solid #ccc 5 | width: 80% 6 | 7 | 8 | &:hover 9 | border: 1px solid #777 10 | 11 | img 12 | width: 100% 13 | height: auto 14 | 15 | &.disc 16 | padding: 15px 17 | text-align: center 18 | 19 | @media print 20 | div 21 | &.page 22 | break-before: page 23 | 24 | -------------------------------------------------------------------------------- /frontend/src/app/report/report.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { ReportComponent } from './report.component'; 4 | 5 | describe('ReportComponent', () => { 6 | let component: ReportComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(waitForAsync(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ ReportComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ReportComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /frontend/src/app/team-form/team-form.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Team Name 6 | 7 | 8 | 9 | 10 | 11 | Active Users 12 | 14 | 15 | × 16 | {{item.firstName}} {{item.lastName}} 17 | 18 | 19 | {{item.firstName}} {{item.lastName}} 20 | 21 | 22 | 23 | 24 | Role 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | Organization 33 | 35 | 36 | 37 | Assets 38 | 39 | 40 | 42 | 43 | 44 | The selected organization has no active assets 45 | 46 | 47 | 48 | 50 | {{isCreate ? 'Create Team': 'Update Team'}} 51 | 52 | 54 | Back to Administration 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /frontend/src/app/team-form/team-form.component.sass: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softrams/bulwark/6d27b9b36bfa6d20c0bad6c923b4896e1702366b/frontend/src/app/team-form/team-form.component.sass -------------------------------------------------------------------------------- /frontend/src/app/team-form/team-form.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { TeamFormComponent } from './team-form.component'; 4 | 5 | describe('TeamFormComponent', () => { 6 | let component: TeamFormComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [TeamFormComponent], 12 | }).compileComponents(); 13 | }); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(TeamFormComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /frontend/src/app/team.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { TeamService } from './team.service'; 4 | 5 | describe('TeamService', () => { 6 | let service: TeamService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(TeamService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /frontend/src/app/team.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { environment } from '../environments/environment'; 4 | import { Team } from './interfaces/Team'; 5 | @Injectable({ 6 | providedIn: 'root', 7 | }) 8 | export class TeamService { 9 | api = environment.apiUrl; 10 | constructor(private http: HttpClient) {} 11 | 12 | getTeams() { 13 | return this.http.get(`${this.api}/team`); 14 | } 15 | 16 | getTeamById(teamId: number) { 17 | return this.http.get(`${this.api}/team/${teamId}`); 18 | } 19 | 20 | createTeam(team: Team) { 21 | return this.http.post(`${this.api}/team`, team); 22 | } 23 | 24 | updateTeam(team: Team) { 25 | return this.http.patch(`${this.api}/team`, team); 26 | } 27 | 28 | deleteTeam(teamId: number) { 29 | return this.http.delete(`${this.api}/team/${teamId}`); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /frontend/src/app/team/team.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Team Management 4 | 5 | 6 | 7 | 8 | 9 | 10 | Name 11 | 12 | 13 | 14 | 15 | 16 | Organization 17 | 18 | 19 | 20 | Assets 21 | Role 22 | 23 | # of Members 24 | 25 | 26 | 27 | 28 | 29 | 30 | {{team?.name}} 31 | {{team?.role !== 'Admin' ? team?.organization?.name : 'N/A'}} 32 | N/A 33 | {{asset.name}}{{isLast 34 | ? '' : ', 35 | '}} 36 | 37 | {{team?.role}} 38 | {{team?.users?.length}} 39 | 40 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | Create Team 54 | 55 | -------------------------------------------------------------------------------- /frontend/src/app/team/team.component.sass: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softrams/bulwark/6d27b9b36bfa6d20c0bad6c923b4896e1702366b/frontend/src/app/team/team.component.sass -------------------------------------------------------------------------------- /frontend/src/app/team/team.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { TeamComponent } from './team.component'; 4 | 5 | describe('TeamComponent', () => { 6 | let component: TeamComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [TeamComponent], 12 | }).compileComponents(); 13 | }); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(TeamComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /frontend/src/app/team/team.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewChild } from '@angular/core'; 2 | import { AlertService } from '../alert/alert.service'; 3 | import { Team } from '../interfaces/Team'; 4 | import { TeamService } from '../team.service'; 5 | import { UserService } from '../user.service'; 6 | import { Table } from 'primeng/table'; 7 | import { Router } from '@angular/router'; 8 | 9 | @Component({ 10 | selector: 'app-team', 11 | templateUrl: './team.component.html', 12 | styleUrls: ['./team.component.sass'], 13 | }) 14 | export class TeamComponent implements OnInit { 15 | teams: Team[]; 16 | 17 | constructor( 18 | public userService: UserService, 19 | public alertService: AlertService, 20 | public teamService: TeamService, 21 | public router: Router 22 | ) {} 23 | @ViewChild('teamTable') table: Table; 24 | 25 | ngOnInit(): void { 26 | this.getTeams(); 27 | } 28 | 29 | getTeams() { 30 | this.teamService.getTeams().subscribe((teams) => (this.teams = teams)); 31 | } 32 | navigateToTeamCreateForm() { 33 | this.router.navigate([`administration/team`]); 34 | } 35 | 36 | deleteTeam(team: Team) { 37 | const r = confirm(`Delete the team "${team.name}"`); 38 | if (r) { 39 | this.teamService.deleteTeam(team.id).subscribe((res: string) => { 40 | this.alertService.success(res); 41 | this.getTeams(); 42 | }); 43 | } 44 | } 45 | 46 | navigateToTeam(team: Team) { 47 | this.router.navigate([`administration/team/${team.id}`]); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /frontend/src/app/user-form/user-form.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | First Name: 7 | 8 | 10 | 11 | 12 | 13 | Last Name: 14 | 15 | 17 | 18 | 19 | 20 | Email: 21 | 22 | 24 | 25 | 26 | 27 | Title: 28 | 29 | 31 | 32 | 33 | 34 | Password: 35 | 36 | 38 | 39 | 40 | 41 | Confirm Password: 42 | 43 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /frontend/src/app/user-form/user-form.component.sass: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softrams/bulwark/6d27b9b36bfa6d20c0bad6c923b4896e1702366b/frontend/src/app/user-form/user-form.component.sass -------------------------------------------------------------------------------- /frontend/src/app/user-form/user-form.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { UserFormComponent } from './user-form.component'; 4 | 5 | describe('UserFormComponent', () => { 6 | let component: UserFormComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [UserFormComponent], 12 | }).compileComponents(); 13 | }); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(UserFormComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /frontend/src/app/user-form/user-form.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FilterMatchMode } from 'primeng/api'; 3 | import { Table } from 'primeng/table'; 4 | import { User } from '../interfaces/User'; 5 | import { UserService } from '../user.service'; 6 | import { FormGroup, FormBuilder, Validators } from '@angular/forms'; 7 | import { AlertService } from '../alert/alert.service'; 8 | import { Router } from '@angular/router'; 9 | 10 | @Component({ 11 | selector: 'app-user-form', 12 | templateUrl: './user-form.component.html', 13 | styleUrls: ['./user-form.component.sass'], 14 | }) 15 | export class UserFormComponent implements OnInit { 16 | users: User[]; 17 | userForm: FormGroup; 18 | constructor( 19 | public userService: UserService, 20 | private fb: FormBuilder, 21 | public alertService: AlertService, 22 | public router: Router 23 | ) {} 24 | 25 | ngOnInit(): void { 26 | this.createForm(); 27 | } 28 | 29 | createForm() { 30 | this.userForm = this.fb.group({ 31 | email: ['', [Validators.required, Validators.email]], 32 | firstName: ['', [Validators.required]], 33 | lastName: ['', [Validators.required]], 34 | title: ['', [Validators.required]], 35 | password: ['', [Validators.required]], 36 | confirmPassword: ['', [Validators.required]], 37 | }); 38 | } 39 | 40 | onSubmit(form) { 41 | const user: User = { 42 | firstName: form.value.firstName, 43 | lastName: form.value.lastName, 44 | title: form.value.title, 45 | password: form.value.password, 46 | confirmPassword: form.value.confirmPassword, 47 | email: form.value.email, 48 | }; 49 | this.userService.createUser(user).subscribe((res: string) => { 50 | this.alertService.success(res); 51 | this.router.navigate(['administration']); 52 | }); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /frontend/src/app/user-management/user-management.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | User Management 4 | 5 | 6 | 7 | 8 | 9 | 10 | Full Name 11 | 12 | 13 | 14 | 15 | 16 | Title 17 | 18 | 19 | 20 | Status 21 | 22 | Teams 23 | Actions 24 | 25 | 26 | 27 | 28 | {{user?.firstName}} {{user?.lastName}} 29 | {{user?.email}} 30 | {{user?.title}} 31 | {{user?.active ? 'Active': 'Inactive'}} 32 | {{team.name}}{{isLast ? '' : ', '}} 33 | 34 | 35 | Deactivate 36 | 37 | 38 | Activate 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | Create User 47 | 48 | 50 | Invite User 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /frontend/src/app/user-management/user-management.component.sass: -------------------------------------------------------------------------------- 1 | td.fitwidth 2 | width: 1px 3 | white-space: nowrap 4 | 5 | .hide 6 | display: none 7 | 8 | 9 | .name:hover + .hide 10 | display: block -------------------------------------------------------------------------------- /frontend/src/app/user-management/user-management.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { UserManagementComponent } from './user-management.component'; 4 | 5 | describe('UserManagementComponent', () => { 6 | let component: UserManagementComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [UserManagementComponent], 12 | }).compileComponents(); 13 | }); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(UserManagementComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /frontend/src/app/user-management/user-management.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewChild } from '@angular/core'; 2 | import { User } from '../interfaces/User'; 3 | import { UserService } from '../user.service'; 4 | import { FormGroup, FormBuilder, Validators } from '@angular/forms'; 5 | import { AlertService } from '../alert/alert.service'; 6 | import { Router } from '@angular/router'; 7 | 8 | @Component({ 9 | selector: 'app-user-management', 10 | templateUrl: './user-management.component.html', 11 | styleUrls: ['./user-management.component.sass'], 12 | }) 13 | export class UserManagementComponent implements OnInit { 14 | users: User[]; 15 | userForm: FormGroup; 16 | constructor( 17 | public userService: UserService, 18 | public alertService: AlertService, 19 | public router: Router 20 | ) {} 21 | 22 | ngOnInit(): void { 23 | this.getUsers(); 24 | } 25 | 26 | getUsers() { 27 | this.userService 28 | .getAllUsers() 29 | .subscribe((fetchedUsers) => (this.users = fetchedUsers)); 30 | } 31 | 32 | navigateToTeamCreateUser() { 33 | this.router.navigate(['administration/user/create']); 34 | } 35 | 36 | navigateToTeamInviteUser() { 37 | this.router.navigate(['administration/user/invite']); 38 | } 39 | /** 40 | * Activate the given user 41 | * @param user User to activate 42 | */ 43 | activateUser(user: User) { 44 | this.userService.activateUser(user.id).subscribe( 45 | (message) => { 46 | this.alertService.success(message as string); 47 | this.getUsers(); 48 | }, 49 | (error) => { 50 | this.alertService.error(error); 51 | } 52 | ); 53 | } 54 | /** 55 | * Deactivate the given user 56 | * @param user User to deactivate 57 | */ 58 | deactivateUser(user: User) { 59 | this.userService.deactivateUser(user.id).subscribe( 60 | (message) => { 61 | this.alertService.success(message as string); 62 | this.getUsers(); 63 | }, 64 | (error) => { 65 | this.alertService.error(error); 66 | } 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /frontend/src/app/user-profile/user-profile.component.sass: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softrams/bulwark/6d27b9b36bfa6d20c0bad6c923b4896e1702366b/frontend/src/app/user-profile/user-profile.component.sass -------------------------------------------------------------------------------- /frontend/src/app/user.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { UserService } from './user.service'; 4 | 5 | describe('UserService', () => { 6 | let service: UserService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(UserService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /frontend/src/app/user.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { environment } from '../environments/environment'; 4 | import { User } from './interfaces/User'; 5 | @Injectable({ 6 | providedIn: 'root', 7 | }) 8 | export class UserService { 9 | api = environment.apiUrl; 10 | constructor(private http: HttpClient) {} 11 | 12 | registerUser(creds) { 13 | return this.http.post(`${this.api}/user/register`, creds); 14 | } 15 | 16 | inviteUser(email) { 17 | return this.http.post(`${this.api}/user/invite`, email); 18 | } 19 | 20 | getUser() { 21 | return this.http.get(`${this.api}/user`); 22 | } 23 | 24 | getUsers() { 25 | return this.http.get(`${this.api}/users`); 26 | } 27 | getTesters(orgId: number) { 28 | return this.http.get(`${this.api}/testers/${orgId}`); 29 | } 30 | getAllUsers() { 31 | return this.http.get(`${this.api}/users/all`); 32 | } 33 | patchUser(user: User) { 34 | return this.http.patch(`${this.api}/user`, user); 35 | } 36 | createUser(user: User) { 37 | return this.http.post(`${this.api}/user`, user); 38 | } 39 | /** 40 | * Activate a user (admin) 41 | * @param id user ID 42 | */ 43 | activateUser(id: string | number) { 44 | return this.http.patch(`${this.api}/user/activate/${id}`, {}); 45 | } 46 | /** 47 | * Deactivate a user (admin) 48 | * @param id user ID 49 | */ 50 | deactivateUser(id: string | number) { 51 | return this.http.patch(`${this.api}/user/deactivate/${id}`, {}); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /frontend/src/app/vuln-form/Vulnerability.ts: -------------------------------------------------------------------------------- 1 | import { ProblemLocation } from '../classes/ProblemLocation'; 2 | import { Resource } from '../classes/Resource'; 3 | 4 | export class Vulnerability { 5 | constructor( 6 | public id: number, 7 | public impact: string, 8 | public likelihood: string, 9 | public risk: string, 10 | public systemic: string, 11 | public status: string, 12 | public description: string, 13 | public remediation: string, 14 | public name: string, 15 | public assessment: number, 16 | public jiraId: string, 17 | public cvssScore: number, 18 | public cvssUrl: string, 19 | public detailedInfo: string, 20 | public screenshots: FormData, 21 | public problemLocations: ProblemLocation[], 22 | public resources: Resource[] 23 | ) {} 24 | } 25 | -------------------------------------------------------------------------------- /frontend/src/app/vuln-form/vuln-form.component.sass: -------------------------------------------------------------------------------- 1 | .previewBox 2 | border: 1px solid #ced4da 3 | padding: .375rem .75rem 4 | border-radius: .25rem -------------------------------------------------------------------------------- /frontend/src/app/vuln-form/vuln-form.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { VulnFormComponent } from './vuln-form.component'; 4 | 5 | describe('VulnFormComponent', () => { 6 | let component: VulnFormComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(waitForAsync(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ VulnFormComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(VulnFormComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /frontend/src/app/vulnerability/vulnerability.component.sass: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softrams/bulwark/6d27b9b36bfa6d20c0bad6c923b4896e1702366b/frontend/src/app/vulnerability/vulnerability.component.sass -------------------------------------------------------------------------------- /frontend/src/app/vulnerability/vulnerability.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { VulnerabilityComponent } from './vulnerability.component'; 4 | 5 | describe('VulnerabilityComponent', () => { 6 | let component: VulnerabilityComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(waitForAsync(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ VulnerabilityComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(VulnerabilityComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /frontend/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softrams/bulwark/6d27b9b36bfa6d20c0bad6c923b4896e1702366b/frontend/src/assets/.gitkeep -------------------------------------------------------------------------------- /frontend/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softrams/bulwark/6d27b9b36bfa6d20c0bad6c923b4896e1702366b/frontend/src/assets/logo.png -------------------------------------------------------------------------------- /frontend/src/assets/overallRiskSeverity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softrams/bulwark/6d27b9b36bfa6d20c0bad6c923b4896e1702366b/frontend/src/assets/overallRiskSeverity.png -------------------------------------------------------------------------------- /frontend/src/assets/theme/button.scss: -------------------------------------------------------------------------------- 1 | .btn-success.p-inputtext { 2 | background-color: #28a745; 3 | border-color: #28a745; 4 | color: white; 5 | } 6 | 7 | .btn-success.p-inputtext:enabled:focus { 8 | box-shadow: 0 0 0 0.2rem rgba(72, 180, 97, 0.5); 9 | border-color: #28a745; 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | 2 | export const environment = { 3 | production: true, 4 | apiUrl: "http://localhost:4500/api", 5 | }; 6 | -------------------------------------------------------------------------------- /frontend/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false, 7 | apiUrl: '', 8 | }; 9 | 10 | /* 11 | * For easier debugging in development mode, you can import the following file 12 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 13 | * 14 | * This import should be commented out in production mode because it will have a negative impact 15 | * on performance if an error is thrown. 16 | */ 17 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 18 | -------------------------------------------------------------------------------- /frontend/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softrams/bulwark/6d27b9b36bfa6d20c0bad6c923b4896e1702366b/frontend/src/favicon.ico -------------------------------------------------------------------------------- /frontend/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Bulwark 6 | 7 | 12 | 18 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /frontend/src/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../coverage/frontend'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /frontend/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /frontend/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags.ts'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | /*************************************************************************************************** 56 | * Zone JS is required by default for Angular itself. 57 | */ 58 | import 'zone.js'; // Included with Angular CLI. 59 | 60 | 61 | /*************************************************************************************************** 62 | * APPLICATION IMPORTS 63 | */ 64 | -------------------------------------------------------------------------------- /frontend/src/styles.scss: -------------------------------------------------------------------------------- 1 | 2 | @import "./assets/theme/button.scss"; 3 | 4 | .btn-primary.p-button, 5 | .btn-primary { 6 | background-color: #ae0a0a; 7 | border-color: #ae0a0a; 8 | color: #fff !important; 9 | } 10 | 11 | .btn-secondary.p-button, 12 | .btn-secondary { 13 | color: #fff; 14 | background-color: #6c757d !important; 15 | border-color: #6c757d !important; 16 | } 17 | 18 | .btn-warning.p-button, 19 | .btn-warning { 20 | color: #fff; 21 | background-color: #ffc107 !important; 22 | border-color: #ffc107 !important; 23 | } 24 | 25 | .btn-primary.p-button:hover, 26 | .btn-primary:hover { 27 | color: #910a0a; 28 | background-color: #910a0a !important; 29 | border-color: #ae0a0a !important; 30 | } 31 | 32 | .btn-primary.p-button:active, 33 | .btn-primary { 34 | background-color: #910a0a !important; 35 | } 36 | .btn-primary.p-button:focus, 37 | .btn-primary:focus { 38 | box-shadow: 0 0 0 0.2rem #910a0a40 !important; 39 | } 40 | 41 | .btn-primary:disabled { 42 | color: #fff; 43 | background-color: #ae0a0a; 44 | border-color: #ae0a0a; 45 | } 46 | 47 | h3 { 48 | color: "grey"; 49 | } 50 | 51 | body { 52 | padding-bottom: 120px; 53 | } 54 | 55 | .footer { 56 | position: absolute; 57 | left: 0; 58 | bottom: 0; 59 | width: 100%; 60 | height: 114px; 61 | } 62 | 63 | .ng-valid[required], 64 | .ng-invalid:not(form) { 65 | border-left: 5px solid #ae0a0a; /* red */ 66 | } 67 | 68 | .card { 69 | margin-top: 10px; 70 | } 71 | -------------------------------------------------------------------------------- /frontend/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /frontend/src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "main.ts", 9 | "polyfills.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /frontend/src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "test.ts", 12 | "polyfills.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts", 16 | "**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /frontend/src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "app", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "app", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "downlevelIteration": true, 6 | "outDir": "./dist/out-tsc", 7 | "sourceMap": true, 8 | "declaration": false, 9 | "module": "commonjs", 10 | "moduleResolution": "node", 11 | "types": ["node"], 12 | "experimentalDecorators": true, 13 | "importHelpers": true, 14 | "target": "es6", 15 | "typeRoots": [ 16 | "node_modules/@types" 17 | ], 18 | "lib": [ 19 | "es6", 20 | "dom", 21 | "es2017" 22 | ], 23 | "paths": { 24 | "url": ["node_modules/@types/node"] 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /frontend/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rulesDirectory": [ 4 | "codelyzer" 5 | ], 6 | "rules": { 7 | "align": { 8 | "options": [ 9 | "parameters", 10 | "statements" 11 | ] 12 | }, 13 | "array-type": false, 14 | "arrow-parens": false, 15 | "arrow-return-shorthand": true, 16 | "curly": true, 17 | "deprecation": { 18 | "severity": "warn" 19 | }, 20 | "eofline": true, 21 | "import-blacklist": [ 22 | true, 23 | "rxjs/Rx" 24 | ], 25 | "import-spacing": true, 26 | "indent": { 27 | "options": [ 28 | "spaces" 29 | ] 30 | }, 31 | "interface-name": false, 32 | "max-classes-per-file": false, 33 | "max-line-length": [ 34 | true, 35 | 140 36 | ], 37 | "member-access": false, 38 | "member-ordering": [ 39 | true, 40 | { 41 | "order": [ 42 | "static-field", 43 | "instance-field", 44 | "static-method", 45 | "instance-method" 46 | ] 47 | } 48 | ], 49 | "no-consecutive-blank-lines": false, 50 | "no-console": [ 51 | true, 52 | "debug", 53 | "info", 54 | "time", 55 | "timeEnd", 56 | "trace" 57 | ], 58 | "no-empty": false, 59 | "no-inferrable-types": [ 60 | true, 61 | "ignore-params" 62 | ], 63 | "no-non-null-assertion": true, 64 | "no-redundant-jsdoc": true, 65 | "no-switch-case-fall-through": true, 66 | "no-var-requires": false, 67 | "object-literal-key-quotes": [ 68 | true, 69 | "as-needed" 70 | ], 71 | "object-literal-sort-keys": false, 72 | "ordered-imports": false, 73 | "quotemark": [ 74 | true, 75 | "single" 76 | ], 77 | "semicolon": { 78 | "options": [ 79 | "always" 80 | ] 81 | }, 82 | "space-before-function-paren": { 83 | "options": { 84 | "anonymous": "never", 85 | "asyncArrow": "always", 86 | "constructor": "never", 87 | "method": "never", 88 | "named": "never" 89 | } 90 | }, 91 | "trailing-comma": false, 92 | "no-output-on-prefix": true, 93 | "no-inputs-metadata-property": true, 94 | "no-outputs-metadata-property": true, 95 | "no-host-metadata-property": true, 96 | "no-input-rename": true, 97 | "no-output-rename": true, 98 | "typedef-whitespace": { 99 | "options": [ 100 | { 101 | "call-signature": "nospace", 102 | "index-signature": "nospace", 103 | "parameter": "nospace", 104 | "property-declaration": "nospace", 105 | "variable-declaration": "nospace" 106 | }, 107 | { 108 | "call-signature": "onespace", 109 | "index-signature": "onespace", 110 | "parameter": "onespace", 111 | "property-declaration": "onespace", 112 | "variable-declaration": "onespace" 113 | } 114 | ] 115 | }, 116 | "use-lifecycle-interface": true, 117 | "use-pipe-transform-interface": true, 118 | "component-class-suffix": true, 119 | "directive-class-suffix": true 120 | , "variable-name": { 121 | "options": [ 122 | "ban-keywords", 123 | "check-format", 124 | "allow-pascal-case" 125 | ] 126 | }, 127 | "whitespace": { 128 | "options": [ 129 | "check-branch", 130 | "check-decl", 131 | "check-operator", 132 | "check-separator", 133 | "check-type", 134 | "check-typecast" 135 | ] 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | // For a detailed explanation regarding each configuration property, visit: 2 | // https://jestjs.io/docs/en/configuration.html 3 | 4 | module.exports = { 5 | // Automatically clear mock calls and instances between every test 6 | clearMocks: true, 7 | 8 | // The directory where Jest should output its coverage files 9 | coverageDirectory: 'coverage', 10 | roots: ['/src'], 11 | transform: { 12 | '^.+\\.ts?$': 'ts-jest' 13 | }, 14 | testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.ts?$', 15 | collectCoverage: true, 16 | collectCoverageFrom: ['src/**/*.ts'], 17 | moduleFileExtensions: ['ts', 'js', 'json', 'node'], 18 | modulePathIgnorePatterns: ['/src/services', '/src/database', '/src/init'], 19 | coveragePathIgnorePatterns: [ 20 | '/src/interfaces', 21 | '/src/classes', 22 | '/src/entity', 23 | '/src/enums' 24 | ], 25 | setupFiles: ['/.jest/setEnvVars.js'] 26 | }; 27 | -------------------------------------------------------------------------------- /ormconfig.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | type: process.env.DB_TYPE, 3 | host: process.env.DB_URL, 4 | port: process.env.DB_PORT, 5 | username: process.env.DB_USERNAME, 6 | password: process.env.DB_PASSWORD, 7 | database: process.env.DB_NAME, 8 | entities: [__dirname + '/dist/entity/*.js'], 9 | migrations: ['dist/database/migration/*.js'], 10 | cli: { 11 | migrationsDir: 'src/database/migration' 12 | }, 13 | logging: true, 14 | synchronize: false 15 | }; 16 | -------------------------------------------------------------------------------- /src/classes/Report.ts: -------------------------------------------------------------------------------- 1 | import { Asset } from '../entity/Asset'; 2 | import { Organization } from '../entity/Organization'; 3 | import { Assessment } from '../entity/Assessment'; 4 | import { Vulnerability } from '../entity/Vulnerability'; 5 | 6 | export class Report { 7 | public org: Organization; 8 | public asset: Asset; 9 | public assessment: Assessment; 10 | public vulns: Vulnerability[]; 11 | public companyName: string; 12 | } 13 | -------------------------------------------------------------------------------- /src/data-source.ts: -------------------------------------------------------------------------------- 1 | import { DataSource } from 'typeorm'; 2 | import { User } from './entity/User'; 3 | import { Organization } from './entity/Organization'; 4 | import { Asset } from './entity/Asset'; 5 | import { Assessment } from './entity/Assessment'; 6 | import { Vulnerability } from './entity/Vulnerability'; 7 | import { File } from './entity/File'; 8 | import { ProblemLocation } from './entity/ProblemLocation'; 9 | import { Resource } from './entity/Resource'; 10 | import { Jira } from './entity/Jira'; 11 | import { ReportAudit } from './entity/ReportAudit'; 12 | import { ApiKey } from './entity/ApiKey'; 13 | import { Config } from './entity/Config'; 14 | import { Team } from './entity/Team'; 15 | import * as path from 'path'; 16 | import * as fs from 'fs'; 17 | import * as dotenv from 'dotenv'; 18 | 19 | // Load environment variables 20 | if (fs.existsSync(path.join(__dirname, '../.env'))) { 21 | const envPath = fs.readFileSync(path.join(__dirname, '../.env')); 22 | console.log('A .env file has been found and will now be parsed.'); 23 | const envConfig = dotenv.parse(envPath); 24 | if (envConfig) { 25 | for (const key in envConfig) { 26 | if (envConfig.hasOwnProperty(key)) { 27 | process.env[key] = envConfig[key]; 28 | } 29 | } 30 | console.log('The provided .env file has been parsed successfully.'); 31 | } 32 | } 33 | 34 | export const AppDataSource = new DataSource({ 35 | type: process.env.DB_TYPE as any || 'mysql', 36 | host: process.env.DB_URL || 'localhost', 37 | port: parseInt(process.env.DB_PORT || '3306'), 38 | username: process.env.DB_USERNAME, 39 | password: process.env.DB_PASSWORD, 40 | database: process.env.DB_NAME, 41 | synchronize: false, 42 | logging: process.env.NODE_ENV !== 'production', 43 | entities: [ 44 | User, 45 | Organization, 46 | Asset, 47 | Assessment, 48 | Vulnerability, 49 | File, 50 | ProblemLocation, 51 | Resource, 52 | Jira, 53 | ReportAudit, 54 | ApiKey, 55 | Config, 56 | Team 57 | ], 58 | migrations: [path.join(__dirname, './migration/*.js')], 59 | subscribers: [] 60 | }); -------------------------------------------------------------------------------- /src/entity/ApiKey.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Entity, 3 | Column, 4 | PrimaryGeneratedColumn, 5 | ManyToOne, 6 | OneToMany, 7 | OneToOne, 8 | ManyToMany, 9 | } from 'typeorm'; 10 | import { IsDate, IsIn } from 'class-validator'; 11 | import { User } from './User'; 12 | 13 | @Entity() 14 | export class ApiKey { 15 | @PrimaryGeneratedColumn() 16 | id: number; 17 | @Column() 18 | key: string; 19 | @Column() 20 | secretKey: string; 21 | @Column() 22 | active: boolean; 23 | @Column() 24 | @IsDate() 25 | lastUpdatedBy: number; 26 | @Column() 27 | @IsDate() 28 | createdDate: Date; 29 | @Column() 30 | @IsDate() 31 | lastUpdatedDate: Date; 32 | @ManyToOne((type) => User, (user) => user.apiKey) 33 | user: User; 34 | } 35 | -------------------------------------------------------------------------------- /src/entity/Assessment.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, OneToMany, ManyToMany, JoinTable } from 'typeorm'; 2 | import { Asset } from './Asset'; 3 | import { Vulnerability } from './Vulnerability'; 4 | import { IsUrl, IsDate, MaxLength, IsString } from 'class-validator'; 5 | import { User } from './User'; 6 | 7 | @Entity() 8 | export class Assessment { 9 | @PrimaryGeneratedColumn() 10 | id: number; 11 | @Column() 12 | name: string; 13 | @Column({ length: 4000 }) 14 | executiveSummary: string; 15 | @Column() 16 | jiraId: string; 17 | @Column() 18 | @IsUrl() 19 | testUrl: string; 20 | @Column() 21 | @IsUrl() 22 | prodUrl: string; 23 | @Column() 24 | scope: string; 25 | @Column() 26 | @IsString() 27 | tag: string; 28 | @Column() 29 | @IsDate() 30 | startDate: Date; 31 | @Column() 32 | @IsDate() 33 | endDate: Date; 34 | @ManyToOne((type) => Asset, (asset) => asset.assessment, { onDelete: 'CASCADE' }) 35 | asset: Asset; 36 | @OneToMany((type) => Vulnerability, (vuln) => vuln.assessment) 37 | vulnerabilities: Vulnerability[]; 38 | @ManyToMany((type) => User) 39 | @JoinTable() 40 | testers: User[]; 41 | } 42 | -------------------------------------------------------------------------------- /src/entity/Asset.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Entity, 3 | Column, 4 | PrimaryGeneratedColumn, 5 | ManyToOne, 6 | OneToMany, 7 | OneToOne, 8 | ManyToMany, 9 | } from 'typeorm'; 10 | import { Organization } from './Organization'; 11 | import { Assessment } from './Assessment'; 12 | import { IsIn } from 'class-validator'; 13 | import { Jira } from './Jira'; 14 | import { Team } from './Team'; 15 | 16 | @Entity() 17 | export class Asset { 18 | @PrimaryGeneratedColumn() 19 | id: number; 20 | @Column() 21 | name: string; 22 | @Column() 23 | @IsIn(['A', 'AH']) 24 | status: string; 25 | @ManyToOne((type) => Organization, (organization) => organization.asset) 26 | organization: Organization; 27 | @OneToMany((type) => Assessment, (assessment) => assessment.asset) 28 | assessment: Assessment[]; 29 | @OneToOne((type) => Jira, (jira) => jira.asset) 30 | jira: Jira; 31 | @ManyToMany(() => Team, (team) => team.assets) 32 | teams: Team[]; 33 | } 34 | -------------------------------------------------------------------------------- /src/entity/Config.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; 2 | import { IsEmail } from 'class-validator'; 3 | 4 | @Entity() 5 | export class Config { 6 | @PrimaryGeneratedColumn() 7 | id: number; 8 | @Column({ 9 | nullable: true 10 | }) 11 | @IsEmail() 12 | fromEmail: string; 13 | @Column({ 14 | nullable: true 15 | }) 16 | fromEmailPassword: string; 17 | @Column({ 18 | nullable: true 19 | }) 20 | companyName: string; 21 | } 22 | -------------------------------------------------------------------------------- /src/entity/File.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from 'typeorm'; 2 | import { Vulnerability } from './Vulnerability'; 3 | import { DbAwareColumn } from '../utilities/column-mapper.utility'; 4 | 5 | @Entity() 6 | export class File { 7 | @PrimaryGeneratedColumn() 8 | id: number; 9 | @Column() 10 | fieldName: string; 11 | @Column() 12 | originalname: string; 13 | @Column() 14 | encoding: string; 15 | @Column() 16 | mimetype: string; 17 | @DbAwareColumn({ name: 'buffer', type: 'mediumblob' }) 18 | buffer: Buffer; 19 | @Column() 20 | size: number; 21 | @ManyToOne((type) => Vulnerability, (vuln) => vuln.screenshots, { onDelete: 'CASCADE' }) 22 | vulnerability: Vulnerability; 23 | } 24 | -------------------------------------------------------------------------------- /src/entity/Jira.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, PrimaryGeneratedColumn, OneToOne, JoinColumn } from 'typeorm'; 2 | import { IsUrl, IsNotEmpty } from 'class-validator'; 3 | import { Asset } from './Asset'; 4 | 5 | @Entity() 6 | export class Jira { 7 | @PrimaryGeneratedColumn() 8 | id: number; 9 | @Column() 10 | @IsUrl() 11 | @IsNotEmpty() 12 | host: string; 13 | @Column() 14 | @IsNotEmpty() 15 | apiKey: string; 16 | @Column() 17 | @IsNotEmpty() 18 | username: string; 19 | @OneToOne((type) => Asset, (asset) => asset.jira, { onDelete: 'CASCADE' }) 20 | @JoinColumn() 21 | asset: Asset; 22 | } 23 | -------------------------------------------------------------------------------- /src/entity/Organization.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, PrimaryGeneratedColumn, OneToMany } from 'typeorm'; 2 | import { Asset } from './Asset'; 3 | import { IsIn } from 'class-validator'; 4 | import { Team } from './Team'; 5 | 6 | @Entity() 7 | export class Organization { 8 | @PrimaryGeneratedColumn() 9 | id: number; 10 | @Column() 11 | name: string; 12 | @Column() 13 | @IsIn(['A', 'AH']) 14 | status: string; 15 | @OneToMany((type) => Asset, (asset) => asset.organization) 16 | asset: Asset[]; 17 | @OneToMany((type) => Team, (team) => team.organization) 18 | teams: Team[]; 19 | } 20 | -------------------------------------------------------------------------------- /src/entity/ProblemLocation.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from 'typeorm'; 2 | import { Vulnerability } from './Vulnerability'; 3 | 4 | @Entity() 5 | export class ProblemLocation { 6 | @PrimaryGeneratedColumn() 7 | id: number; 8 | @Column() 9 | location: string; 10 | @Column() 11 | target: string; 12 | @ManyToOne((type) => Vulnerability, (vuln) => vuln.problemLocations, { onDelete: 'CASCADE' }) 13 | vulnerability: Vulnerability; 14 | } 15 | -------------------------------------------------------------------------------- /src/entity/ReportAudit.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from 'typeorm'; 2 | import { IsDate } from 'class-validator'; 3 | 4 | @Entity() 5 | export class ReportAudit { 6 | @PrimaryGeneratedColumn() 7 | id: number; 8 | @Column() 9 | assessmentId: number; 10 | @Column() 11 | generatedBy: number; 12 | @Column() 13 | @IsDate() 14 | generatedDate: Date; 15 | } 16 | -------------------------------------------------------------------------------- /src/entity/Resource.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from 'typeorm'; 2 | import { Vulnerability } from './Vulnerability'; 3 | 4 | @Entity() 5 | export class Resource { 6 | @PrimaryGeneratedColumn() 7 | id: number; 8 | @Column() 9 | description: string; 10 | @Column() 11 | url: string; 12 | @ManyToOne((type) => Vulnerability, (vuln) => vuln.resources, { onDelete: 'CASCADE' }) 13 | vulnerability: Vulnerability; 14 | } 15 | -------------------------------------------------------------------------------- /src/entity/Team.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Entity, 3 | Column, 4 | PrimaryGeneratedColumn, 5 | ManyToMany, 6 | JoinTable, 7 | ManyToOne, 8 | } from 'typeorm'; 9 | import { IsDate, IsIn } from 'class-validator'; 10 | import { User } from './User'; 11 | import { Organization } from './Organization'; 12 | import { Asset } from './Asset'; 13 | 14 | @Entity() 15 | export class Team { 16 | @PrimaryGeneratedColumn({}) 17 | id: number; 18 | @Column() 19 | name: string; 20 | @ManyToOne((type) => Organization, (organization) => organization.teams) 21 | organization: Organization; 22 | @Column() 23 | @IsDate() 24 | createdDate: Date; 25 | @Column({ nullable: true }) 26 | createdBy: number; 27 | @Column() 28 | @IsDate() 29 | lastUpdatedDate: Date; 30 | @Column({ nullable: true }) 31 | lastUpdatedBy: number; 32 | @Column() 33 | @IsIn(['Admin', 'Read-Only', 'Tester']) 34 | role: string; 35 | @ManyToMany(() => User, (user) => user.teams) 36 | @JoinTable() 37 | users: User[]; 38 | @ManyToMany(() => Asset, (asset) => asset.teams) 39 | @JoinTable() 40 | assets: Asset[]; 41 | } 42 | -------------------------------------------------------------------------------- /src/entity/User.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Entity, 3 | Column, 4 | PrimaryGeneratedColumn, 5 | ManyToMany, 6 | OneToMany, 7 | } from 'typeorm'; 8 | import { IsEmail, IsUUID, IsOptional } from 'class-validator'; 9 | import { dynamicNullable } from '../utilities/column-mapper.utility'; 10 | import { Team } from '../entity/Team'; 11 | import { ApiKey } from './ApiKey'; 12 | 13 | @Entity() 14 | export class User { 15 | @PrimaryGeneratedColumn({}) 16 | id: number; 17 | @Column({ 18 | unique: true, 19 | }) 20 | @IsEmail() 21 | email: string; 22 | @Column({ 23 | nullable: true, 24 | }) 25 | @IsEmail() 26 | newEmail: string; 27 | @dynamicNullable() 28 | password: string; 29 | @Column() 30 | active: boolean; 31 | @Column({ 32 | nullable: true, 33 | }) 34 | @IsOptional() 35 | @IsUUID() 36 | uuid: string; 37 | @dynamicNullable() 38 | firstName: string; 39 | @dynamicNullable() 40 | lastName: string; 41 | @dynamicNullable() 42 | title: string; 43 | @ManyToMany(() => Team, (team) => team.users) 44 | teams: Team[]; 45 | @OneToMany((type) => ApiKey, (apiKey) => apiKey.user) 46 | apiKey: ApiKey[]; 47 | } 48 | -------------------------------------------------------------------------------- /src/entity/VulnDictionary.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; 2 | 3 | @Entity() 4 | export class VulnDictionary { 5 | @PrimaryGeneratedColumn() 6 | id: number; 7 | @Column() 8 | name: string; 9 | @Column() 10 | description: string; 11 | } 12 | -------------------------------------------------------------------------------- /src/entity/Vulnerability.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, OneToMany } from 'typeorm'; 2 | import { Assessment } from './Assessment'; 3 | import { IsUrl, IsIn, MaxLength, IsAlpha, IsDecimal } from 'class-validator'; 4 | import { File } from './File'; 5 | import { ProblemLocation } from './ProblemLocation'; 6 | import { Resource } from './Resource'; 7 | 8 | @Entity() 9 | export class Vulnerability { 10 | @PrimaryGeneratedColumn() 11 | id: number; 12 | @Column() 13 | jiraId: string; 14 | @Column() 15 | @IsIn(['Low', 'Medium', 'High']) 16 | impact: string; 17 | @Column() 18 | @IsIn(['Low', 'Medium', 'High']) 19 | likelihood: string; 20 | @Column() 21 | @IsIn(['Low', 'Medium', 'High', 'Critical', 'Informational']) 22 | risk: string; 23 | @Column() 24 | @IsIn(['Yes', 'No']) 25 | systemic: string; 26 | @Column({ type: 'decimal', scale: 1, precision: 10 }) 27 | @IsDecimal() 28 | cvssScore: number; 29 | @Column() 30 | @IsUrl() 31 | cvssUrl: string; 32 | @Column() 33 | @IsIn(['Open', 'Resolved', 'On Hold']) 34 | status: string; 35 | @Column({ length: 4000 }) 36 | @MaxLength(4000) 37 | description: string; 38 | @Column({ length: 4000 }) 39 | @MaxLength(4000) 40 | detailedInfo: string; 41 | @Column({ length: 4000 }) 42 | @MaxLength(4000) 43 | remediation: string; 44 | @Column() 45 | name: string; 46 | @ManyToOne((type) => Assessment, (assessment) => assessment.vulnerabilities, { onDelete: 'CASCADE' }) 47 | assessment: Assessment; 48 | @OneToMany((type) => File, (file) => file.vulnerability) 49 | screenshots: File[]; 50 | @OneToMany((type) => ProblemLocation, (problemLocation) => problemLocation.vulnerability) 51 | problemLocations: ProblemLocation[]; 52 | @OneToMany((type) => Resource, (resource) => resource.vulnerability) 53 | resources: Resource[]; 54 | } 55 | -------------------------------------------------------------------------------- /src/enums/message-enum.ts: -------------------------------------------------------------------------------- 1 | export const passwordRequirement = 2 | 'Password Requirements: Must be at least 12 characters' + 3 | ', at least one uppercase characters, at least one lowercase characters,' + 4 | 'at least one digit, and at least one symbol.'; 5 | -------------------------------------------------------------------------------- /src/enums/roles-enum.ts: -------------------------------------------------------------------------------- 1 | export const ROLE = { 2 | ADMIN: 'Admin', 3 | READONLY: 'Read-Only', 4 | TESTER: 'Tester' 5 | }; 6 | -------------------------------------------------------------------------------- /src/enums/status-enum.ts: -------------------------------------------------------------------------------- 1 | export const status = { 2 | active: 'A', 3 | archived: 'AH' 4 | }; 5 | -------------------------------------------------------------------------------- /src/init/docker-run-exec.ts: -------------------------------------------------------------------------------- 1 | import { Connection, createConnection, getManager } from 'typeorm'; 2 | const exec = require('child_process').exec; 3 | let connection: Connection; 4 | export const initDbCheck = async () => { 5 | connection = await createConnection(); 6 | const manager = getManager(); 7 | let migrations; 8 | try { 9 | migrations = await manager.query('SELECT * FROM migrations'); 10 | } catch (err) { 11 | if (err) { 12 | console.error(err); 13 | } 14 | // tslint:disable-next-line: no-console 15 | console.info( 16 | 'Initial migration not found. Running generating initial migration.' 17 | ); 18 | // If the migrations table does not exist, run the migration:init script 19 | // to create schema 20 | exec('npm run migration:init', (err, stdout, stderr) => { 21 | if (err) { 22 | console.error(err); 23 | terminate(); 24 | } 25 | // tslint:disable-next-line: no-console 26 | console.log(stdout); 27 | exec('npm run migration:run', (runErr) => { 28 | if (runErr) { 29 | console.error(err); 30 | terminate(); 31 | } 32 | // tslint:disable-next-line: no-console 33 | console.info( 34 | 'Initial migration has been generated successfully. Running initial migration.' 35 | ); 36 | // tslint:disable-next-line: no-console 37 | console.log(stdout); 38 | terminate(); 39 | }); 40 | }); 41 | } 42 | if (migrations) { 43 | migrations = migrations.map((x) => x.name); 44 | // If the CreateDatabase migration exists skip migration:generate 45 | // else run the migration:generate script 46 | if (migrations[0].includes('CreateDatabase')) { 47 | // tslint:disable-next-line: no-console 48 | console.info( 49 | `Initial migration ${migrations[0]} exists. Skipping migration:init script` 50 | ); 51 | } 52 | // If the init migration was already created check to see 53 | // if there has been an update to the database 54 | // If there was a DB update, run the migration 55 | exec('npm run migration:generate', (err, stdout) => { 56 | if (err) { 57 | // tslint:disable-next-line: no-console 58 | console.info('No database updates detected'); 59 | terminate(); 60 | } 61 | console.log(stdout); 62 | // tslint:disable-next-line: no-console 63 | exec('npm run migration:run', (runErr, runStdout) => { 64 | // tslint:disable-next-line: no-console 65 | console.info('Database updates detected. Running generated migrations'); 66 | if (runErr) { 67 | console.error(runErr); 68 | terminate(); 69 | } 70 | // tslint:disable-next-line: no-console 71 | console.log('Database migration success!'); 72 | console.log(runStdout); 73 | terminate(); 74 | }); 75 | }); 76 | } 77 | }; 78 | 79 | const terminate = () => { 80 | connection.close(); 81 | process.exit(); 82 | }; 83 | 84 | initDbCheck(); 85 | -------------------------------------------------------------------------------- /src/init/setEnv.ts: -------------------------------------------------------------------------------- 1 | const dotenv = require('dotenv'); 2 | const fs = require('fs'); 3 | import * as path from 'path'; 4 | const { writeFile } = require('fs'); 5 | 6 | // Grabs .env variables if running locally 7 | if (fs.existsSync(path.join(__dirname, '../../.env'))) { 8 | const envPath = fs.readFileSync(path.join(__dirname, '../../.env')); 9 | // tslint:disable-next-line: no-console 10 | console.log('A .env file has been found found and will now be parsed.'); 11 | // https://github.com/motdotla/dotenv#what-happens-to-environment-variables-that-were-already-set 12 | const envConfig = dotenv.parse(envPath); 13 | if (envConfig) { 14 | for (const key in envConfig) { 15 | if (envConfig.hasOwnProperty(key)) { 16 | process.env[key] = envConfig[key]; 17 | } 18 | } 19 | // tslint:disable-next-line: no-console 20 | console.log('The provided .env file has been parsed successfully.'); 21 | } 22 | } 23 | // docker-compose will have access to the .env file 24 | if (process.env.SERVER_ADDRESS && process.env.PORT) { 25 | let targetPath: string; 26 | let isProduction: boolean; 27 | const apiUrl = `${process.env.SERVER_ADDRESS}:${process.env.PORT}/api`; 28 | 29 | if (process.env.NODE_ENV === 'production') { 30 | isProduction = true; 31 | targetPath = path.join( 32 | __dirname, 33 | '../../frontend/src/environments/environment.prod.ts' 34 | ); 35 | } else { 36 | isProduction = false; 37 | targetPath = path.join( 38 | __dirname, 39 | '../../frontend/src/environments/environment.dev.ts' 40 | ); 41 | } 42 | 43 | // we have access to our environment variables 44 | // in the process.env object thanks to dotenv 45 | const environmentFileContent = ` 46 | export const environment = { 47 | production: ${isProduction}, 48 | apiUrl: "${apiUrl}", 49 | }; 50 | `; 51 | 52 | // write the content to the respective file 53 | writeFile(targetPath, environmentFileContent, (err) => { 54 | if (err) { 55 | console.error(err); 56 | } 57 | // tslint:disable-next-line: no-console 58 | console.info( 59 | `Angular environment has been updated to ${environmentFileContent} located at ${targetPath}` 60 | ); 61 | return; 62 | }); 63 | } else { 64 | // tslint:disable-next-line: no-console 65 | console.info( 66 | 'Environment variables do not exist. Skipping `npm run config` step!' 67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /src/interfaces/jira/jira-init.interface.ts: -------------------------------------------------------------------------------- 1 | export interface JiraInit { 2 | apiKey: string; 3 | host: string; 4 | username: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/interfaces/jira/jira-issue-link.interface.ts: -------------------------------------------------------------------------------- 1 | export interface IssueLink { 2 | id?: number; 3 | outwardIssue?: { 4 | key: string; 5 | }; 6 | inwardIssue?: { 7 | key: string; 8 | }; 9 | type?: { 10 | name: string; 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /src/interfaces/jira/jira-issue-priority.interface.ts: -------------------------------------------------------------------------------- 1 | export interface JiraPriority { 2 | id?: number; 3 | key?: string; 4 | name?: string; 5 | colorName?: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/interfaces/jira/jira-issue-status.interface.ts: -------------------------------------------------------------------------------- 1 | export interface IssueStatus { 2 | id: number; 3 | name: string; 4 | description: string; 5 | category: any; 6 | } 7 | -------------------------------------------------------------------------------- /src/interfaces/jira/jira-issue-type.interface.ts: -------------------------------------------------------------------------------- 1 | export interface IssueType { 2 | id?: string; 3 | name?: string; 4 | description?: string; 5 | properties?: any; 6 | } 7 | -------------------------------------------------------------------------------- /src/interfaces/jira/jira-issue.interface.ts: -------------------------------------------------------------------------------- 1 | import { JiraProject } from './jira-project.interface'; 2 | import { JiraPriority } from './jira-issue-priority.interface'; 3 | import { IssueType } from './jira-issue-type.interface'; 4 | import { IssueStatus } from './jira-issue-status.interface'; 5 | import { IssueLink } from './jira-issue-link.interface'; 6 | // Add interfaces as you need them 7 | export interface JiraIssue { 8 | update?: object; 9 | fields: { 10 | id?: number; 11 | key?: string; 12 | summary?: string; 13 | parent?: { 14 | key: string; 15 | }; 16 | subtasks?: JiraIssue[]; 17 | description?: any; 18 | environment?: string; 19 | project?: JiraProject; 20 | priority?: JiraPriority; 21 | assignee?: any; 22 | reporter?: any; 23 | creator?: any; 24 | issuetype?: IssueType; 25 | issueStatus?: IssueStatus; 26 | created?: Date; 27 | updated?: Date; 28 | dueDate?: Date; 29 | resolution?: any; 30 | originalEstimate?: number; 31 | remainingEstimate?: number; 32 | timeSpent?: number; 33 | securityLevel?: any; 34 | labels?: string[]; 35 | versions?: any[]; 36 | fixVersions?: any[]; 37 | components?: []; 38 | comments?: Comment[]; 39 | attachments?: []; 40 | links?: IssueLink[]; 41 | properties?: any; 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /src/interfaces/jira/jira-project.interface.ts: -------------------------------------------------------------------------------- 1 | export interface JiraProject { 2 | id?: string; 3 | key?: string; 4 | style?: string; 5 | name?: string; 6 | projectTypeKey?: string; 7 | properties?: any; 8 | } 9 | -------------------------------------------------------------------------------- /src/interfaces/jira/jira-result.interface.ts: -------------------------------------------------------------------------------- 1 | export interface JiraResult { 2 | id: string; 3 | key: string; 4 | self: string; 5 | message: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/interfaces/team-info.interface.ts: -------------------------------------------------------------------------------- 1 | import { Organization } from '../entity/Organization'; 2 | 3 | export interface TeamInfo { 4 | id: number; 5 | role: string; 6 | organization: Organization; 7 | asset: number; 8 | } 9 | -------------------------------------------------------------------------------- /src/interfaces/user-request.interface.ts: -------------------------------------------------------------------------------- 1 | import { Request } from 'express'; 2 | import { Team } from '../entity/Team'; 3 | import { File } from '../entity/File'; 4 | export interface UserRequest extends Request { 5 | user: string; 6 | fileExtError: string; 7 | file: File; 8 | files: File[]; 9 | isAdmin: boolean; 10 | userTeams: Team[]; 11 | userOrgs: number[]; 12 | userAssets: number[]; 13 | } 14 | -------------------------------------------------------------------------------- /src/middleware/jwt.spec.ts: -------------------------------------------------------------------------------- 1 | require('dotenv').config({ path: '.env' }); 2 | import jwt = require('jsonwebtoken'); 3 | const { checkToken, checkRefreshToken } = require('./jwt.middleware'); 4 | 5 | describe('jwt.middleware.ts', () => { 6 | // Mocks the Request Object that is returned 7 | const mockRequest = () => { 8 | const req = { 9 | headers: { 10 | authorization: 'FakeJWT', 11 | }, 12 | body: {}, 13 | user: Function, 14 | }; 15 | req.user = jest.fn().mockReturnValue(req); 16 | return req; 17 | }; 18 | // Mocks the Response Object that is returned 19 | const mockResponse = () => { 20 | const res = { 21 | status: Function, 22 | json: Function, 23 | }; 24 | res.status = jest.fn().mockReturnValue(res); 25 | res.json = jest.fn().mockReturnValue(res); 26 | return res; 27 | }; 28 | let token: string; 29 | let refreshToken: string; 30 | beforeAll(() => { 31 | token = jwt.sign({ userId: '2222' }, process.env.JWT_KEY, { 32 | expiresIn: '15m', 33 | }); 34 | refreshToken = jwt.sign({ userId: '2222' }, process.env.JWT_REFRESH_KEY, { 35 | expiresIn: '8h', 36 | }); 37 | }); 38 | 39 | test('running checkToken should return a 401 with Authorization', async () => { 40 | const req = mockRequest(); 41 | const res = mockResponse(); 42 | await checkToken(req, res, null); 43 | expect(res.status).toHaveBeenCalledWith(401); 44 | }); 45 | 46 | test.skip('running checkToken should return a req.user', async () => { 47 | const req = mockRequest(); 48 | const res = mockResponse(); 49 | req.headers.authorization = token; 50 | await checkToken(req, res, jest.fn()); 51 | expect(req.user).toBe('2222'); 52 | }); 53 | 54 | test('running checkRefreshToken should return a 401 with refreshToken', async () => { 55 | const req = mockRequest(); 56 | const res = mockResponse(); 57 | req.body = { 58 | refreshToken: 'FakeJWT', 59 | }; 60 | await checkRefreshToken(req, res, null); 61 | expect(res.status).toHaveBeenCalledWith(401); 62 | }); 63 | test('running checkRefreshToken should return a 401 without refreshToken', async () => { 64 | const req = mockRequest(); 65 | const res = mockResponse(); 66 | await checkRefreshToken(req, res, null); 67 | expect(res.status).toHaveBeenCalledWith(401); 68 | }); 69 | 70 | test('running checkRefreshToken should return a req.user', async () => { 71 | const req = mockRequest(); 72 | const res = mockResponse(); 73 | req.body = { 74 | refreshToken, 75 | }; 76 | await checkRefreshToken(req, res, jest.fn()); 77 | expect(req.user).toBe('2222'); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /src/routes/report-audit.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createConnection, 3 | getConnection, 4 | Entity, 5 | getRepository, 6 | } from 'typeorm'; 7 | import { ReportAudit } from '../entity/ReportAudit'; 8 | import { insertReportAuditRecord } from './report-audit.controller'; 9 | import { User } from '../entity/User'; 10 | import { Assessment } from '../entity/Assessment'; 11 | import { Organization } from '../entity/Organization'; 12 | import { Vulnerability } from '../entity/Vulnerability'; 13 | import { Asset } from '../entity/Asset'; 14 | import { ProblemLocation } from '../entity/ProblemLocation'; 15 | import { Resource } from '../entity/Resource'; 16 | import { File } from '../entity/File'; 17 | import { Jira } from '../entity/Jira'; 18 | import { v4 as uuidv4 } from 'uuid'; 19 | import { generateHash } from '../utilities/password.utility'; 20 | import MockExpressRequest = require('mock-express-request'); 21 | import { Team } from '../entity/Team'; 22 | import { ApiKey } from '../entity/ApiKey'; 23 | 24 | describe('Report Audit Controller', () => { 25 | beforeEach(() => { 26 | return createConnection({ 27 | type: 'sqlite', 28 | database: ':memory:', 29 | dropSchema: true, 30 | entities: [ 31 | ReportAudit, 32 | User, 33 | File, 34 | Assessment, 35 | Asset, 36 | Organization, 37 | Jira, 38 | Vulnerability, 39 | ProblemLocation, 40 | Resource, 41 | Team, 42 | ApiKey, 43 | ], 44 | synchronize: true, 45 | logging: false, 46 | }); 47 | }); 48 | 49 | afterEach(() => { 50 | const conn = getConnection(); 51 | return conn.close(); 52 | }); 53 | 54 | test('insertReportAuditRecord', async () => { 55 | const req = new MockExpressRequest(); 56 | const existUser = new User(); 57 | existUser.firstName = 'master'; 58 | existUser.lastName = 'chief'; 59 | existUser.email = 'testing@jest.com'; 60 | existUser.active = true; 61 | const uuid = uuidv4(); 62 | existUser.uuid = uuid; 63 | existUser.password = await generateHash('TangoDown123!!!'); 64 | const savedUser = await getConnection().getRepository(User).save(existUser); 65 | const assessment: Assessment = { 66 | id: null, 67 | name: 'testAssessment', 68 | executiveSummary: '', 69 | jiraId: '', 70 | testUrl: '', 71 | prodUrl: '', 72 | scope: '', 73 | tag: '', 74 | startDate: new Date(), 75 | endDate: new Date(), 76 | asset: new Asset(), 77 | testers: null, 78 | vulnerabilities: null, 79 | }; 80 | const savedAssessment = await getConnection() 81 | .getRepository(Assessment) 82 | .save(assessment); 83 | req.user = savedUser.id; 84 | const auditRecord = await insertReportAuditRecord( 85 | req.user, 86 | savedAssessment.id 87 | ); 88 | const savedAuditRecord = await getConnection() 89 | .getRepository(ReportAudit) 90 | .findOne({ where: { id: auditRecord.id } }); 91 | expect(savedAuditRecord.assessmentId).toBe(savedAssessment.id); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /src/routes/report-audit.controller.ts: -------------------------------------------------------------------------------- 1 | import { AppDataSource } from '../data-source'; 2 | import { ReportAudit } from '../entity/ReportAudit'; 3 | 4 | /** 5 | * @description Insert report generation audit record 6 | * @param {number} userId - The ID of the user generating the report 7 | * @param {number} assessmentId - The ID of the assessment for the report 8 | * @returns {Promise} - The created report audit record 9 | */ 10 | export const insertReportAuditRecord = (userId: number, assessmentId: number): Promise => { 11 | return new Promise(async (resolve, reject) => { 12 | try { 13 | const reportAuditRepository = AppDataSource.getRepository(ReportAudit); 14 | 15 | // Create the report audit record 16 | const reportAudit: ReportAudit = new ReportAudit(); 17 | reportAudit.generatedBy = userId; 18 | reportAudit.assessmentId = assessmentId; 19 | reportAudit.generatedDate = new Date(); 20 | 21 | // Save the report audit record 22 | const newAudit = await reportAuditRepository.save(reportAudit); 23 | 24 | console.info( 25 | `Report generated by user ID: ${newAudit.generatedBy} for assessment ID: ${newAudit.assessmentId} on ${newAudit.generatedDate}` 26 | ); 27 | 28 | resolve(newAudit); 29 | } catch (err) { 30 | console.error('Error inserting report audit record:', err); 31 | reject(err); 32 | } 33 | }); 34 | }; -------------------------------------------------------------------------------- /src/services/email.service.spec.ts: -------------------------------------------------------------------------------- 1 | import nodemailer = require('nodemailer'); 2 | import * as emailService from './email.service'; 3 | 4 | describe('email service', () => { 5 | test('send email failure missing credentials', async () => { 6 | const mailOptions = { 7 | from: 'pentester3@gmail.com', 8 | subject: 'Bulwark - Please confirm your email address', 9 | text: `Please confirm your email address 10 | \n A Bulwark account was created with the email: pentester@gmail.com. 11 | As an extra security measure, please verify this is the correct email address 12 | linked to Bulwark by clicking the link below. 13 | \n dev/api/user/verify/123`, 14 | to: 'pentester2@gmail.com' 15 | }; 16 | await emailService.sendEmail(mailOptions, (err, data) => { 17 | expect(err).toBe('Error sending email'); 18 | }); 19 | }); 20 | test('send verification email success', async () => { 21 | const uuid = 'abc'; 22 | const userEmail = 'pentester@gmail.com'; 23 | const spy = jest.spyOn(emailService, 'sendEmail'); 24 | await emailService.sendVerificationEmail(uuid, userEmail); 25 | expect(spy).toHaveBeenCalled(); 26 | }); 27 | test('send forgot password email success', async () => { 28 | const uuid = 'abc'; 29 | const userEmail = 'pentester@gmail.com'; 30 | const spy = jest.spyOn(emailService, 'sendEmail'); 31 | await emailService.sendForgotPasswordEmail(uuid, userEmail); 32 | expect(spy).toHaveBeenCalled(); 33 | }); 34 | test('send invitation email', async () => { 35 | const uuid = 'abc'; 36 | const userEmail = 'pentester@gmail.com'; 37 | const spy = jest.spyOn(emailService, 'sendEmail'); 38 | await emailService.sendInvitationEmail(uuid, userEmail); 39 | expect(spy).toHaveBeenCalled(); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /src/temp/empty.ts: -------------------------------------------------------------------------------- 1 | // The purpose of this file is to ensure the temp directory is created during ts compilation 2 | -------------------------------------------------------------------------------- /src/utilities/column-mapper.utility.ts: -------------------------------------------------------------------------------- 1 | import { Column, ColumnType, ColumnOptions } from 'typeorm'; 2 | 3 | const mysqlSqliteTypeMapping: { [key: string]: ColumnType } = { 4 | mediumblob: 'blob' 5 | }; 6 | 7 | const resolveDbType = (mySqlType: ColumnType): ColumnType => { 8 | const isTestEnv = process.env.NODE_ENV === 'test'; 9 | if (isTestEnv && mySqlType.toString() in mysqlSqliteTypeMapping) { 10 | return mysqlSqliteTypeMapping[mySqlType.toString()]; 11 | } 12 | return mySqlType; 13 | }; 14 | /** 15 | * @description Wrapper function for resolving DB Type 16 | * @param {ColumnOptions} columnOptions 17 | * @returns Custom Column 18 | */ 19 | export const DbAwareColumn = (columnOptions: ColumnOptions) => { 20 | if (columnOptions.type) { 21 | columnOptions.type = resolveDbType(columnOptions.type); 22 | } 23 | return Column(columnOptions); 24 | }; 25 | /** 26 | * @description Addes nullabe column option if test DB is active 27 | * @returns Custom Column 28 | */ 29 | export const dynamicNullable = () => { 30 | const columnOptions: ColumnOptions = { 31 | nullable: process.env.NODE_ENV === 'test' ? true : false 32 | }; 33 | return Column(columnOptions); 34 | }; 35 | -------------------------------------------------------------------------------- /src/utilities/crypto.utility.spec.ts: -------------------------------------------------------------------------------- 1 | import { encrypt, decrypt } from './crypto.utility'; 2 | 3 | describe('crypto utility', () => { 4 | test('decrypt', () => { 5 | const str = encrypt('password'); 6 | expect(decrypt(str)).toBe('password'); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /src/utilities/crypto.utility.ts: -------------------------------------------------------------------------------- 1 | import * as crypto from 'crypto'; 2 | 3 | // Check for required environment variables 4 | if (!process.env.CRYPTO_SECRET || !process.env.CRYPTO_SALT) { 5 | console.error('CRYPTO_SECRET and CRYPTO_SALT environment variables must be set!'); 6 | } 7 | 8 | const algorithm = 'aes-256-cbc'; 9 | const key = crypto.scryptSync( 10 | process.env.CRYPTO_SECRET || 'default-secret-key-for-dev-only', 11 | process.env.CRYPTO_SALT || 'default-salt-for-dev-only', 12 | 32 13 | ); 14 | 15 | /** 16 | * @description Encrypt text using AES-256-CBC 17 | * @param {string} text - The text to encrypt 18 | * @returns {string} - JSON stringified object with IV and encrypted data 19 | */ 20 | export const encrypt = (text: string): string => { 21 | try { 22 | // Generate a random 16-byte initialization vector 23 | const iv = crypto.randomBytes(16); 24 | 25 | // Create cipher 26 | const cipher = crypto.createCipheriv(algorithm, Buffer.from(key), iv); 27 | 28 | // Encrypt the text 29 | let encrypted = cipher.update(text); 30 | encrypted = Buffer.concat([encrypted, cipher.final()]); 31 | 32 | // Return IV and encrypted data 33 | return JSON.stringify({ 34 | iv: iv.toString('hex'), 35 | encryptedData: encrypted.toString('hex') 36 | }); 37 | } catch (error) { 38 | console.error('Encryption error:', error); 39 | throw new Error('Failed to encrypt data'); 40 | } 41 | }; 42 | 43 | /** 44 | * @description Decrypt text that was encrypted with encrypt() 45 | * @param {string} text - JSON stringified object with IV and encrypted data 46 | * @returns {string} - The decrypted text 47 | */ 48 | export const decrypt = (text: string): string => { 49 | try { 50 | // Parse the JSON string 51 | const parsedText = JSON.parse(text); 52 | 53 | // Convert hex strings to buffers 54 | const iv = Buffer.from(parsedText.iv, 'hex'); 55 | const encryptedText = Buffer.from(parsedText.encryptedData, 'hex'); 56 | 57 | // Create decipher 58 | const decipher = crypto.createDecipheriv(algorithm, Buffer.from(key), iv); 59 | 60 | // Decrypt the text 61 | let decrypted = decipher.update(encryptedText); 62 | decrypted = Buffer.concat([decrypted, decipher.final()]); 63 | 64 | return decrypted.toString(); 65 | } catch (error) { 66 | console.error('Decryption error:', error); 67 | throw new Error('Failed to decrypt data'); 68 | } 69 | }; -------------------------------------------------------------------------------- /src/utilities/file.utility.ts: -------------------------------------------------------------------------------- 1 | import multer = require('multer'); 2 | import * as path from 'path'; 3 | import * as fs from 'fs'; 4 | 5 | // Create temp directory if it doesn't exist 6 | const tempDir = path.join(__dirname, '../temp'); 7 | if (!fs.existsSync(tempDir)) { 8 | fs.mkdirSync(tempDir, { recursive: true }); 9 | } 10 | 11 | const maxFileSize = 5000000; // 5 MB 12 | 13 | const fileFilter = (req, file, cb) => { 14 | // Ext validation 15 | if (!(file.mimetype === 'image/png' || file.mimetype === 'image/jpeg')) { 16 | req.fileExtError = 'Only JPEG and PNG file types allowed'; 17 | cb(null, false); 18 | } else { 19 | cb(null, true); 20 | } 21 | }; 22 | 23 | export const upload = multer({ 24 | fileFilter, 25 | limits: { fileSize: maxFileSize } 26 | }).single('file'); 27 | 28 | export const uploadArray = multer({ 29 | fileFilter, 30 | limits: { fileSize: maxFileSize } 31 | }).array('screenshots'); -------------------------------------------------------------------------------- /src/utilities/password.utility.spec.ts: -------------------------------------------------------------------------------- 1 | import { generateHash, updatePassword, compare } from './password.utility'; 2 | 3 | describe('password utility', () => { 4 | test('generateHash to work', async () => { 5 | const password = 'qwerty'; 6 | const hashedPassword = await generateHash(password); 7 | expect(hashedPassword).toBeDefined(); 8 | }); 9 | 10 | test('compare hash success', async () => { 11 | const oldPassword = await generateHash('qwerty'); 12 | const currentPassword = 'qwerty'; 13 | await expect(compare(currentPassword, oldPassword)).resolves.toBeTruthy(); 14 | }); 15 | 16 | test('compare hash failure', async () => { 17 | const oldPassword = await generateHash('qwerty'); 18 | const currentPassword = 'qwerty2'; 19 | await expect(compare(currentPassword, oldPassword)).resolves.toBeFalsy(); 20 | }); 21 | 22 | test('compare hash bcrypt failure', async () => { 23 | await expect(compare(null, 'a')).rejects.toBe('Bcrypt comparison failure'); 24 | }); 25 | 26 | test('password updated successfully', async () => { 27 | const currentPassword = 'qwerty'; 28 | const oldPassword = await generateHash(currentPassword); 29 | const newPassword = 'newQwerty'; 30 | const result = await updatePassword( 31 | oldPassword, 32 | currentPassword, 33 | newPassword 34 | ); 35 | expect(result).toEqual(expect.anything()); 36 | }); 37 | 38 | test('password updated failure', async () => { 39 | const currentPassword = 'qwerty2'; 40 | const oldPassword = await generateHash('1234'); 41 | const newPassword = 'newQwerty'; 42 | await expect( 43 | updatePassword(oldPassword, currentPassword, newPassword) 44 | ).rejects.toBe('The current password is incorrect'); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /src/utilities/password.utility.ts: -------------------------------------------------------------------------------- 1 | import * as bcrypt from 'bcrypt'; 2 | // tslint:disable-next-line: no-var-requires 3 | const passwordValidator = require('password-validator'); 4 | 5 | // Create a password schema with requirements 6 | export const passwordSchema = new passwordValidator(); 7 | passwordSchema 8 | .is() 9 | .min(12) // Minimum length 12 10 | .has() 11 | .uppercase() // Must have uppercase letters 12 | .has() 13 | .lowercase() // Must have lowercase letters 14 | .has() 15 | .digits() // Must have digits 16 | .has() 17 | .symbols(); // Must have symbols 18 | 19 | // Number of salt rounds for bcrypt 20 | const saltRounds = 10; 21 | 22 | /** 23 | * @description Generate hash from password 24 | * @param {string} password - The plaintext password 25 | * @returns {Promise} - The hashed password 26 | */ 27 | export const generateHash = (password: string): Promise => { 28 | return new Promise((resolve, reject) => { 29 | bcrypt.genSalt(saltRounds, async (err, salt) => { 30 | if (err) { 31 | console.error('bcrypt salt generation error:', err); 32 | reject('Bcrypt hash failed: ' + err); 33 | } else { 34 | bcrypt.hash(password, salt, async (hashErr, hash: string) => { 35 | if (hashErr) { 36 | console.error('bcrypt hash error:', hashErr); 37 | reject('Bcrypt hash failed: ' + hashErr); 38 | } else { 39 | resolve(hash); 40 | } 41 | }); 42 | } 43 | }); 44 | }); 45 | }; 46 | 47 | /** 48 | * @description Update user password 49 | * @param {string} hashedCurrentPassword - The current hashed password 50 | * @param {string} currentPassword - The plaintext current password 51 | * @param {string} newPassword - The plaintext new password 52 | * @returns {Promise} - The newly hashed password 53 | */ 54 | export const updatePassword = ( 55 | hashedCurrentPassword: string, 56 | currentPassword: string, 57 | newPassword: string 58 | ): Promise => { 59 | return new Promise(async (resolve, reject) => { 60 | try { 61 | // Verify the current password 62 | const valid = await compare(currentPassword, hashedCurrentPassword); 63 | 64 | if (!valid) { 65 | reject('The current password is incorrect'); 66 | return; 67 | } 68 | 69 | // Generate a hash for the new password 70 | const newPasswordHash = await generateHash(newPassword); 71 | resolve(newPasswordHash); 72 | } catch (error) { 73 | console.error('Password update error:', error); 74 | reject('An error occurred while updating the password'); 75 | } 76 | }); 77 | }; 78 | 79 | /** 80 | * @description Compare password hash 81 | * @param {string|string[]} currentPassword - The plaintext password to check 82 | * @param {string} hashedCurrentPassword - The stored hashed password 83 | * @returns {Promise} - Whether the password is valid 84 | */ 85 | export const compare = ( 86 | currentPassword: string | string[], 87 | hashedCurrentPassword: string 88 | ): Promise => { 89 | return new Promise((resolve, reject) => { 90 | if (!currentPassword || !hashedCurrentPassword) { 91 | reject('Password or hash is missing'); 92 | return; 93 | } 94 | 95 | bcrypt.compare(currentPassword, hashedCurrentPassword, (err, valid) => { 96 | if (err) { 97 | console.error('bcrypt comparison error:', err); 98 | reject('Bcrypt comparison failure'); 99 | return; 100 | } 101 | 102 | resolve(valid); 103 | }); 104 | }); 105 | }; -------------------------------------------------------------------------------- /src/utilities/role.utility.ts: -------------------------------------------------------------------------------- 1 | import { ROLE } from '../enums/roles-enum'; 2 | import { UserRequest } from '../interfaces/user-request.interface'; 3 | 4 | /** 5 | * @description Check if user has access to an organization 6 | * @param {UserRequest} req 7 | * @param {number} rqstdOrgId 8 | * @returns boolean 9 | */ 10 | export const hasOrgAccess = (req: UserRequest, rqstdOrgId: number): boolean => { 11 | if (!req.userOrgs || !req.userOrgs.length) { 12 | return false; 13 | } 14 | 15 | return req.userOrgs.includes(rqstdOrgId); 16 | }; 17 | 18 | /** 19 | * @description Check if user has read access to an asset 20 | * @param {UserRequest} req 21 | * @param {number} assetId 22 | * @returns Promise 23 | */ 24 | export const hasAssetReadAccess = async (req: UserRequest, assetId: number): Promise => { 25 | // Ensure we have userAssets array 26 | if (!req.userAssets) { 27 | return false; 28 | } 29 | 30 | return req.userAssets.includes(assetId); 31 | }; 32 | 33 | /** 34 | * @description Check if user has write access to an asset 35 | * @param {UserRequest} req 36 | * @param {number} assetId 37 | * @returns Promise 38 | */ 39 | export const hasAssetWriteAccess = async (req: UserRequest, assetId: number): Promise => { 40 | // Admins have write access to all assets 41 | if (req.isAdmin) { 42 | return true; 43 | } 44 | 45 | // If user is not an admin, check if they belong to a tester team with access to this asset 46 | if (!req.userTeams || !req.userTeams.length) { 47 | return false; 48 | } 49 | 50 | const testerTeams = req.userTeams.filter((team) => team.role === ROLE.TESTER); 51 | 52 | if (!testerTeams || !testerTeams.length) { 53 | return false; 54 | } 55 | 56 | // Check if any of the user's tester teams have access to this asset 57 | for (const testerTeam of testerTeams) { 58 | if (!testerTeam.assets || !testerTeam.assets.length) { 59 | continue; 60 | } 61 | 62 | for (const asset of testerTeam.assets) { 63 | if (asset.id === assetId) { 64 | return true; 65 | } 66 | } 67 | } 68 | 69 | return false; 70 | }; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["es6", "dom"], 4 | "target": "es2017", 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "emitDecoratorMetadata": true, 8 | "experimentalDecorators": true, 9 | "outDir": "dist", 10 | "removeComments": true, 11 | "baseUrl": "./", 12 | "sourceMap": true, 13 | "types": ["@types/jest"], 14 | }, 15 | "exclude": ["./frontend", "./migration"], 16 | "include": ["src/**/*"] 17 | } 18 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:latest", "tslint-config-prettier"], 3 | "rules": { 4 | "max-line-length": { 5 | "options": [120] 6 | }, 7 | "quotemark": [true, "single", "avoid-escape", "avoid-template"], 8 | "new-parens": true, 9 | "no-arg": true, 10 | "no-bitwise": true, 11 | "no-conditional-assignment": true, 12 | "no-consecutive-blank-lines": false, 13 | "no-console": { 14 | "severity": "warning", 15 | "options": ["debug", "info", "log", "time", "timeEnd", "trace"] 16 | }, 17 | "arrow-parens": false, 18 | "trailing-comma": false, 19 | "ordered-imports": false, 20 | "no-var-requires": false 21 | }, 22 | "jsRules": { 23 | "max-line-length": { 24 | "options": [120] 25 | } 26 | } 27 | } 28 | --------------------------------------------------------------------------------