[
  {
    "path": ".dockerignore",
    "content": ".git/\nmonitoring/\nnode_modules/\nscreenshots/\ntest/\nbuild/\ndist/\nvagrant/\ndocs/\nlogs/\nDockerfile\n.npmrc\nfrontend/node_modules/\nfrontend/dist/\n.env"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Report a bug in Bulwark\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**Problem Locations**\nDid 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.\n\n**Steps to Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Desktop (please complete the following information):**\n - OS: [e.g. iOS]\n - Browser [e.g. chrome, safari]\n - Version [e.g. 22]\n\n**Smartphone (please complete the following information):**\n - Device: [e.g. iPhone6]\n - OS: [e.g. iOS8.1]\n - Browser [e.g. stock browser, safari]\n - Version [e.g. 22]\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for Bulwark\ntitle: ''\nlabels: enhancement\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/agents/my-agent.agent.md",
    "content": "---\n# Fill in the fields below to create a basic custom agent for your repository.\n# The Copilot CLI can be used for local testing: https://gh.io/customagents/cli\n# To make this agent available, merge this file into the default repository branch.\n# For format details, see: https://gh.io/customagents/config\n\nname: SecBot\ndescription: SecBot will look at security issues and perform required remediations.\n---\n\n# My Agent\n\nSecBot will monitor for security issues and address them by performing remediation steps.\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: 'npm'\n    directory: '/'\n    schedule:\n      interval: 'monthly'\n    target-branch: 'develop'\n\n  - package-ecosystem: 'npm'\n    directory: '/frontend'\n    schedule:\n      interval: 'monthly'\n    target-branch: 'develop'\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "# Pull Request\n\nThank 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.\n\nFixes # (issue)\n\n## Type of change\n\n- [ ] A new feature\n- [ ] A bug fix\n- [ ] Documentation only changes\n- [ ] Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)\n- [ ] A code change that neither fixes a bug nor adds a feature\n- [ ] A code change that improves performance\n- [ ] Adding missing tests or correcting existing tests\n- [ ] Changes that affect the build system or external dependencies\n- [ ] Changes to our CI configuration files and scripts\n- [ ] Other changes that don't modify src or test files\n\nAdd description here\n\n```release-note\n\n```\n\n---\n\n## Checklist\n\n- [ ] My code follows the [contributing](../CONTRIBUTING.md) guidelines of this project\n- [ ] I have performed a self-review of my own code\n- [ ] I have commented my code, particularly in hard-to-understand areas\n- [ ] I have made corresponding changes to the documentation\n- [ ] My changes generate no new warnings\n- [ ] I have added tests that prove my fix is effective or that my feature works\n- [ ] New and existing unit tests pass locally with my changes\n- [ ] Any dependent changes have been merged and published in downstream modules\n"
  },
  {
    "path": ".github/stale.yml",
    "content": "daysUntilStale: 60\ndaysUntilClose: 7\nexemptLabels:\n  - confirmed\n  - security\nstaleLabel: stale\nmarkComment: >\n  This issue has been automatically marked as stale because it has not had\n  recent activity. It will be closed if no further activity occurs. Thank you\n  for your contributions.\n"
  },
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "content": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# You may wish to alter this file to override the set of languages analyzed,\n# or to provide custom queries or build logic.\nname: 'CodeQL'\n\non:\n  push:\n    branches: [master, develop]\n  pull_request:\n    # The branches below must be a subset of the branches above\n    branches: [master]\n  schedule:\n    - cron: '0 2 * * 2'\n\njobs:\n  analyze:\n    name: Analyze\n    runs-on: ubuntu-latest\n\n    strategy:\n      fail-fast: false\n      matrix:\n        # Override automatic language detection by changing the below list\n        # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']\n        language: ['javascript']\n        # Learn more...\n        # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v2\n        with:\n          # We must fetch at least the immediate parents so that if this is\n          # a pull request then we can checkout the head.\n          fetch-depth: 2\n\n      # If this run was triggered by a pull request event, then checkout\n      # the head of the pull request instead of the merge commit.\n      - run: git checkout HEAD^2\n        if: ${{ github.event_name == 'pull_request' }}\n\n      # Initializes the CodeQL tools for scanning.\n      - name: Initialize CodeQL\n        uses: github/codeql-action/init@v1\n        with:\n          languages: ${{ matrix.language }}\n          # If you wish to specify custom queries, you can do so here or in a config file.\n          # By default, queries listed here will override any specified in a config file.\n          # Prefix the list here with \"+\" to use these queries and those in the config file.\n          # queries: ./path/to/local/query, your-org/your-repo/queries@main\n\n      # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).\n      # If this step fails, then you should remove it and run the build manually (see below)\n      - name: Autobuild\n        uses: github/codeql-action/autobuild@v1\n\n      # ℹ️ Command-line programs to run using the OS shell.\n      # 📚 https://git.io/JvXDl\n\n      # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines\n      #    and modify them (or add more) to build your code if your project\n      #    uses a compiled language\n\n      #- run: |\n      #   make bootstrap\n      #   make release\n\n      - name: Perform CodeQL Analysis\n        uses: github/codeql-action/analyze@v1\n"
  },
  {
    "path": ".github/workflows/node.js.yml",
    "content": "# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node\n# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions\n\nname: build\n\non:\n  push:\n    branches: [master]\n  pull_request:\n    branches: [develop]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        node-version: [12.x, 14.x]\n\n    steps:\n      - uses: actions/checkout@v2\n      - name: Use Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v1\n        with:\n          node-version: ${{ matrix.node-version }}\n      - name: Install Packages\n        run: npm install\n      - name: Create Env File\n        run: |\n          touch .env\n          echo JWT_KEY=\"test\" >> .env\n          echo JWT_REFRESH_KEY=\"test\" >> .env\n      - name: Backend Tests\n        run: npm run test:node\n"
  },
  {
    "path": ".gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n#angular\n/frontend/.angular\n\n# compiled output\n/dist\n/tmp\n/out-tsc\n# Only exists if Bazel was run\n/bazel-out\n\n# dependencies\n/node_modules\n\n# profiling files\nchrome-profiler-events.json\nspeed-measure-plugin.json\n\n# database migrations\n/src/database/migration/*\n\n# IDEs and editors\n/.idea\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n.vscode/\n\n# IDE - VSCode\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# misc\n/.sass-cache\n/connect.lock\n/coverage\n/libpeerconnection.log\nnpm-debug.log\nyarn-error.log\ntestem.log\n/typings\n.env\n.env.*\n\n# System Files\n.DS_Store\nThumbs.db\n"
  },
  {
    "path": ".jest/setEnvVars.js",
    "content": "process.env.NODE_ENV = 'test';\nprocess.env.CRYPTO_SECRET = 'test';\nprocess.env.CRYPTO_SALT = 'test';\nprocess.env.JWT_KEY = 'test';\nprocess.env.JWT_REFRESH_KEY = 'test';\nprocess.env.CRYPTO_SALT = 'test';\n"
  },
  {
    "path": ".prettierignore",
    "content": "coverage\ndist\nfrontend/dist\nnode_modules\nfrontend/node_modules\n.prettierignore\nDockerfile\nCHANGELOG.md\n# Ignore all HTML files:\n*.html\n*.sass\n.gitignore\nfrontend/.gitignore"
  },
  {
    "path": ".prettierrc.json",
    "content": "{\n  \"arrowParens\": \"always\",\n  \"bracketSpacing\": true,\n  \"embeddedLanguageFormatting\": \"auto\",\n  \"htmlWhitespaceSensitivity\": \"css\",\n  \"insertPragma\": false,\n  \"jsxBracketSameLine\": false,\n  \"jsxSingleQuote\": false,\n  \"printWidth\": 80,\n  \"proseWrap\": \"preserve\",\n  \"quoteProps\": \"as-needed\",\n  \"requirePragma\": false,\n  \"semi\": true,\n  \"singleQuote\": true,\n  \"tabWidth\": 2,\n  \"trailingComma\": \"es5\",\n  \"useTabs\": false,\n  \"vueIndentScriptAndStyle\": false\n}\n"
  },
  {
    "path": ".versionrc",
    "content": "{\n  \"types\": [\n    {\"type\": \"chore\", \"section\":\"Others\", \"hidden\": false},\n    {\"type\": \"revert\", \"section\":\"Reverts\", \"hidden\": false},\n    {\"type\": \"feat\", \"section\": \"Features\", \"hidden\": false},\n    {\"type\": \"fix\", \"section\": \"Bug Fixes\", \"hidden\": false},\n    {\"type\": \"docs\", \"section\":\"Docs\", \"hidden\": false},\n    {\"type\": \"style\", \"section\":\"Styling\", \"hidden\": false},\n    {\"type\": \"refactor\", \"section\":\"Code Refactoring\", \"hidden\": false},\n    {\"type\": \"perf\", \"section\":\"Performance Improvements\", \"hidden\": false},\n    {\"type\": \"test\", \"section\":\"Tests\", \"hidden\": false},\n    {\"type\": \"build\", \"section\":\"Build System\", \"hidden\": false},\n    {\"type\": \"ci\", \"section\":\"CI\", \"hidden\":false}\n  ]\n}"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nAll notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.\n\n## [7.3.0](https://github.com/softrams/bulwark/compare/v7.2.1...v7.3.0) (2021-06-28)\n\n\n### Features\n\n* **asset.controller.ts:** add open vulnerability modal to asset ([1c0b5b0](https://github.com/softrams/bulwark/commit/1c0b5b028a33be1f5225c9417a06635ec5f21726)), closes [#862](https://github.com/softrams/bulwark/issues/862)\n* **asset.controller.ts:** optimize asset queries. Complete vulnerability table ([7c5729b](https://github.com/softrams/bulwark/commit/7c5729b847f02a5011be5f37f4c6f3758730dca4)), closes [#862](https://github.com/softrams/bulwark/issues/862)\n\n### [7.2.1](https://github.com/softrams/bulwark/compare/v7.2.0...v7.2.1) (2021-06-23)\n\n\n### Bug Fixes\n\n* **app.ts:** fixed missing middleware for update password ([fd2600a](https://github.com/softrams/bulwark/commit/fd2600a77b369c13672d641aff47af9e3190ffe1)), closes [#860](https://github.com/softrams/bulwark/issues/860)\n\n## [7.2.0](https://github.com/softrams/bulwark/compare/v7.1.4...v7.2.0) (2021-06-23)\n\n\n### Features\n\n* **asset.controller.ts:** return number of open vulnerabilities with assets ([8ccbbc7](https://github.com/softrams/bulwark/commit/8ccbbc7c055b3fef92382746a77b2b14a562bf42)), closes [#858](https://github.com/softrams/bulwark/issues/858)\n\n\n### Docs\n\n* **readme.md:** temporarily remove Team section from README ([7b17ee6](https://github.com/softrams/bulwark/commit/7b17ee63f6c330875a78dee4b53002923a6bebd8))\n\n### [7.1.4](https://github.com/softrams/bulwark/compare/v7.1.3...v7.1.4) (2021-06-21)\n\n\n### Docs\n\n* **readme:** readMe gifs in public AWS repo ([3cef86f](https://github.com/softrams/bulwark/commit/3cef86fbb2de61e563928cedd38607066f36f4f5)), closes [#848](https://github.com/softrams/bulwark/issues/848)\n\n\n### Build System\n\n* **dependabot.yml:** update interval ([a592cd3](https://github.com/softrams/bulwark/commit/a592cd305b8c6be3dd5b5dabeaf34e5139b86627))\n\n\n### Others\n\n* **dependabot.yml/readme.md:** removed myself from the README.  Removed myself from dependabot ([c7676cf](https://github.com/softrams/bulwark/commit/c7676cfce5a17c2eaa83db014aa942eedffd6c59))\n* **deps:** bump @ng-select/ng-select from 6.1.0 to 7.0.1 in /frontend ([ee7a3e9](https://github.com/softrams/bulwark/commit/ee7a3e95bcb95a5fe4ff729f1c37ab1e3659ffa1))\n* **deps:** bump core-js from 3.12.1 to 3.15.0 in /frontend ([492702d](https://github.com/softrams/bulwark/commit/492702db13772e1a13e6c68b58e83a4a3590b3f8))\n* **deps:** bump mime-types from 2.1.30 to 2.1.31 ([1af1aab](https://github.com/softrams/bulwark/commit/1af1aab0136b58a6057a4830d976bb2949e1dee0))\n* **deps:** bump nodemailer from 6.6.1 to 6.6.2 ([7e6b9d2](https://github.com/softrams/bulwark/commit/7e6b9d226a8d6d3111517ac4da8840fda6eac124))\n* **deps:** bump primeng from 11.4.2 to 12.0.0 in /frontend ([e8318ff](https://github.com/softrams/bulwark/commit/e8318ffa00b6d55ac74fcb57dc411bf035a47f21))\n* **deps:** bump puppeteer from 9.1.1 to 10.0.0 ([9dde15b](https://github.com/softrams/bulwark/commit/9dde15b626b799575400cfb4dcabee873721df1c))\n* **deps:** bump tslib from 2.2.0 to 2.3.0 in /frontend ([997a635](https://github.com/softrams/bulwark/commit/997a6357f76267dc5f82a503740e43e0bf4761c7))\n* **deps:** bump typeorm from 0.2.32 to 0.2.34 ([d6cf805](https://github.com/softrams/bulwark/commit/d6cf805029cd4a3c3202fcc024e42739ad3ecf14))\n* **deps-dev:** bump @babel/core from 7.14.3 to 7.14.6 ([44a7ac5](https://github.com/softrams/bulwark/commit/44a7ac56a5a10e38f41ad5258b70b4d63132e717))\n* **deps-dev:** bump @babel/preset-env from 7.14.2 to 7.14.5 ([d7af0e2](https://github.com/softrams/bulwark/commit/d7af0e29001224953dfa7042afb8155a8b5bce14))\n* **deps-dev:** bump @babel/preset-typescript from 7.13.0 to 7.14.5 ([b34dbde](https://github.com/softrams/bulwark/commit/b34dbde2451ae49f9a0b48e96efad142656c3cf2))\n* **deps-dev:** bump @types/jasmine from 3.7.4 to 3.7.7 in /frontend ([205a783](https://github.com/softrams/bulwark/commit/205a78373301e9b4a78949ec9666a7214d3fe4fc))\n* **deps-dev:** bump @types/node from 15.6.1 to 15.12.4 in /frontend ([987b2c7](https://github.com/softrams/bulwark/commit/987b2c72843f0fcf8e7e15c2d4f07907e64dafbb))\n* **deps-dev:** bump babel-jest from 26.6.3 to 27.0.2 ([ec3fbbf](https://github.com/softrams/bulwark/commit/ec3fbbf92cde2f45a348312518f534e35cef0db9))\n* **deps-dev:** bump highlight.js from 10.7.2 to 11.0.1 ([19d901c](https://github.com/softrams/bulwark/commit/19d901c43da8cea6a9690d7357f5bbeb5961cc20))\n* **deps-dev:** bump jest from 26.6.3 to 27.0.4 ([c7a96a9](https://github.com/softrams/bulwark/commit/c7a96a986b38b7034c2766583e71728ebb2cd634))\n* **deps-dev:** bump karma from 6.3.2 to 6.3.4 in /frontend ([0eb8f1a](https://github.com/softrams/bulwark/commit/0eb8f1ad69436cca44cf939207e27bf5735ccddd))\n* **deps-dev:** bump prettier from 2.3.0 to 2.3.1 ([8430f66](https://github.com/softrams/bulwark/commit/8430f66c411a1271153781d242821de23deade38))\n* **deps-dev:** bump typescript from 4.2.4 to 4.3.4 ([c5c571c](https://github.com/softrams/bulwark/commit/c5c571c78a25eae6698cba047471deace06a4b67))\n* **ng update @angular/cdk:** ng update @angular/cdk ([03cfc4b](https://github.com/softrams/bulwark/commit/03cfc4b8786f61e84648fbe1594136f7b42cfe77))\n* **ng update @angular/cli:** ng update @angular/cli ([075711d](https://github.com/softrams/bulwark/commit/075711deff26e44659facaeb3b12578d33043b36))\n* **npm audit fix:** npm audit fix ([8d6774f](https://github.com/softrams/bulwark/commit/8d6774f53ef68546445448001d15073d09423c26))\n* **readme.md:** fix conflict ([a05147e](https://github.com/softrams/bulwark/commit/a05147e9c93dce14db0f301a35bbe73a5f04d554))\n* **unit tests:** fixed Jira and Password unit tests ([887e6a7](https://github.com/softrams/bulwark/commit/887e6a7d564f54cbe0d531894cf72f4ddcf88603))\n* **update angular/core:** update angular/core ([e6cb7ff](https://github.com/softrams/bulwark/commit/e6cb7ffecec61b647a6939e74387ff5870c62966))\n\n### [7.1.3](https://github.com/softrams/bulwark/compare/v7.1.2...v7.1.3) (2021-06-04)\n\n### [7.1.2](https://github.com/softrams/bulwark/compare/v7.1.1...v7.1.2) (2021-05-27)\n\n\n### Others\n\n* **package-lock.json:** update package-lock.json to force docker build ([b1904c4](https://github.com/softrams/bulwark/commit/b1904c440009da267a50ee689045e127869844ee))\n\n### [7.1.1](https://github.com/softrams/bulwark/compare/v7.1.0...v7.1.1) (2021-05-26)\n\n\n### Bug Fixes\n\n* **dockerfile:** update `NODE_VERSION` to 14.9.0 ([3ba025f](https://github.com/softrams/bulwark/commit/3ba025f13abccf522e25782cbeeef64ee3ca507d))\n\n## [7.1.0](https://github.com/softrams/bulwark/compare/v7.0.11...v7.1.0) (2021-05-25)\n\n\n### Features\n\n* **assessments.component.html:** add `sortField` and `sortOrder` to assessments table ([f3dbd42](https://github.com/softrams/bulwark/commit/f3dbd427d33a11ebd413c5a11129caa8bea7cce2))\n\n\n### Build System\n\n* **node.js.yml:** remove support for Node version 10.x ([9938b24](https://github.com/softrams/bulwark/commit/9938b2463d409412bb85cbfdbe62d3e8ba2df31e))\n\n\n### Others\n\n* **deps:** bump concurrently from 6.0.2 to 6.1.0 ([817e4e9](https://github.com/softrams/bulwark/commit/817e4e97c07756d0c392867161bcf6946af4b96d))\n* **deps:** bump concurrently from 6.1.0 to 6.2.0 ([826b576](https://github.com/softrams/bulwark/commit/826b576f2493221f0f8b2ab0e16bea016661bbee))\n* **deps:** bump core-js from 3.11.0 to 3.12.1 in /frontend ([8102fd6](https://github.com/softrams/bulwark/commit/8102fd629c391360a7a933fed10b2dcea106510f))\n* **deps:** bump dotenv from 8.2.0 to 10.0.0 ([51d9c8f](https://github.com/softrams/bulwark/commit/51d9c8fa251dc36e5ade49276730a6edbc8cb648))\n* **deps:** bump helmet from 4.5.0 to 4.6.0 ([64c2fb3](https://github.com/softrams/bulwark/commit/64c2fb37377b1db74c900fbbddcfbe0e37b4cc08))\n* **deps:** bump ngx-markdown from 11.1.3 to 12.0.1 in /frontend ([a1bf0e4](https://github.com/softrams/bulwark/commit/a1bf0e459e6f1f44df15ae9af4400e83dfc09825))\n* **deps:** bump nodemailer from 6.5.0 to 6.6.1 ([536c513](https://github.com/softrams/bulwark/commit/536c513f9dde65cd6a7a63656b1cd28864f8972d))\n* **deps:** bump primeng from 11.4.0 to 11.4.2 in /frontend ([868bd72](https://github.com/softrams/bulwark/commit/868bd727fba29181e55612caa12278d967a49b49))\n* **deps:** bump puppeteer from 9.0.0 to 9.1.1 ([ffd5426](https://github.com/softrams/bulwark/commit/ffd5426a9d0970aef00292e00069a468802e7165))\n* **deps:** bump ts-node from 9.1.1 to 10.0.0 ([0e471a9](https://github.com/softrams/bulwark/commit/0e471a9c79326037e7fa48755e0c2a3df04266cd))\n* **deps-dev:** bump @babel/core from 7.13.16 to 7.14.3 ([393f0c6](https://github.com/softrams/bulwark/commit/393f0c6a30cd0e4146b4ccd0d7c318bd15e2a0fa))\n* **deps-dev:** bump @babel/preset-env from 7.13.15 to 7.14.2 ([4ddd3bb](https://github.com/softrams/bulwark/commit/4ddd3bb4927223b1d5015e81e3860b005d503d42))\n* **deps-dev:** bump @commitlint/cli from 12.1.1 to 12.1.4 ([1af5966](https://github.com/softrams/bulwark/commit/1af59660bf9c686ae18b877b66ff8cc9dccd74e9))\n* **deps-dev:** bump @commitlint/config-conventional ([159f930](https://github.com/softrams/bulwark/commit/159f9303bdcf6ab53a7921d22a011b8d64d71c90))\n* **deps-dev:** bump @types/jasmine from 3.6.9 to 3.7.4 in /frontend ([8b1b4da](https://github.com/softrams/bulwark/commit/8b1b4daa1910b1f322c3624bee7d74fc9d0c17cf))\n* **deps-dev:** bump @types/jasminewd2 from 2.0.8 to 2.0.9 in /frontend ([50d58bf](https://github.com/softrams/bulwark/commit/50d58bf9d10886fee6a215c647280db96d987420))\n* **deps-dev:** bump @types/jest from 26.0.22 to 26.0.23 ([778ee66](https://github.com/softrams/bulwark/commit/778ee66f3b655e96b9c83a197b8806d49a956c16))\n* **deps-dev:** bump @types/node from 14.14.41 to 15.6.1 in /frontend ([480221a](https://github.com/softrams/bulwark/commit/480221a7d6f40ad6ca8dab5449f578cfd81217a8))\n* **deps-dev:** bump codelyzer from 6.0.1 to 6.0.2 in /frontend ([da6f92e](https://github.com/softrams/bulwark/commit/da6f92ea07ed06b36996f98666653842525cf5cc))\n* **deps-dev:** bump karma-jasmine-html-reporter in /frontend ([2073d3a](https://github.com/softrams/bulwark/commit/2073d3a0a7e41495c9710fe82935fe99071ef657))\n* **deps-dev:** bump lint-staged from 10.5.4 to 11.0.0 ([87b5b48](https://github.com/softrams/bulwark/commit/87b5b48854c3ef7495c46558ba328f03e8ecc132))\n* **deps-dev:** bump prettier from 2.2.1 to 2.3.0 ([36fe885](https://github.com/softrams/bulwark/commit/36fe885b5d722ebf6c26dce07928a950cb4e824a))\n* **deps-dev:** bump standard-version from 9.2.0 to 9.3.0 ([50ba4aa](https://github.com/softrams/bulwark/commit/50ba4aa3f459f6b2a11ec137d1e875dce2f4b72b))\n* **deps-dev:** bump ts-jest from 26.5.5 to 26.5.6 ([4214046](https://github.com/softrams/bulwark/commit/4214046b1d43f59502e587d71f9c9708a9a5f49a))\n* **deps-dev:** bump ts-node from 9.1.1 to 10.0.0 in /frontend ([e1af4c3](https://github.com/softrams/bulwark/commit/e1af4c3d096e661c561a860ac984afd1edba4754))\n* **frontend/package.json:** update Angular to v12 ([be6ac28](https://github.com/softrams/bulwark/commit/be6ac2829d1b2591a9a33aed4cddc1895d4ecee4))\n* **package-lock.json:** npm audit fix ([c401f46](https://github.com/softrams/bulwark/commit/c401f46cda9e7a8c8bbd41a38c2d54bd391465d8))\n* **package-lock.json:** update lockfileversion to 2 ([63c2460](https://github.com/softrams/bulwark/commit/63c24607d9183200621d583ea56c2946b2773255))\n* **update @angular/cdk:** update @angular/cdk ([e36a9b6](https://github.com/softrams/bulwark/commit/e36a9b632731959b8d76576346e9ce00d5660cdd))\n* **update @angular/cli:** update @angular/cli ([2a1cbde](https://github.com/softrams/bulwark/commit/2a1cbdee1fdaad04df643f604fb1f7da82ddb977))\n* **update @angular/core:** update @angular/core ([e64a3ba](https://github.com/softrams/bulwark/commit/e64a3ba6cff98cd5594f5c7a4ab6de009ea31180))\n\n### [7.0.11](https://github.com/softrams/bulwark/compare/v7.0.10...v7.0.11) (2021-05-10)\n\n\n### Bug Fixes\n\n* **multiple api's:** add additional ACL checks ([2cdc01a](https://github.com/softrams/bulwark/commit/2cdc01abaf6d499951407105d56883956c644e28))\n* **role.utility.ts:** updated role-utility.ts ([f0fc0c5](https://github.com/softrams/bulwark/commit/f0fc0c57e5f0d13d287f7305900448a8bd80f6fe))\n\n\n### Others\n\n* **deps:** bump core-js from 3.10.2 to 3.11.0 in /frontend ([90aa392](https://github.com/softrams/bulwark/commit/90aa392655accf6d6212fc8bbadbd852518d773b))\n* **deps:** bump primeng from 11.3.2 to 11.4.0 in /frontend ([211c294](https://github.com/softrams/bulwark/commit/211c29491912cf95f8c71494f2be77b5397ac498))\n* **deps:** bump puppeteer from 8.0.0 to 9.0.0 ([9dbe9d2](https://github.com/softrams/bulwark/commit/9dbe9d299fb613690b078be01defc290ae9162b6))\n* **deps-dev:** bump @babel/core from 7.13.15 to 7.13.16 ([08fa5a6](https://github.com/softrams/bulwark/commit/08fa5a647322f4dbfb599ecc2ea91f7a0de2e028))\n* **frontend/package.json:** ng update @angular/cli ([58ad735](https://github.com/softrams/bulwark/commit/58ad735626521f4aa1480649a1a155982082dd3a))\n* **frontend/package.json:** update @angular/cdk ([51fe3c1](https://github.com/softrams/bulwark/commit/51fe3c16d111d9dd3d960ae6048fe4096f575782))\n* **frontend/package.json:** update @angular/cli ([b3b0a18](https://github.com/softrams/bulwark/commit/b3b0a18cf41b4ea5ceb14710c6e21f149119f3b6))\n* **frontend/package.json:** update @angular/core ([8f8a247](https://github.com/softrams/bulwark/commit/8f8a24778a158deae7d8b936f815569bb3ddbc1a))\n* **ng update @angular/cdk:** ng update @angular/cdk ([d960688](https://github.com/softrams/bulwark/commit/d9606880878f75aa61ce6d08a426b8f12ef37b65))\n* **package.json:** ng update @angular/core ([431b10e](https://github.com/softrams/bulwark/commit/431b10ec7521e92c7f4aa4358816edce89b31da7))\n* **package.json:** npm audit fix ([6a9f8b1](https://github.com/softrams/bulwark/commit/6a9f8b1b8fef2bb846e3dcd72e12080edee76270))\n\n### [7.0.10](https://github.com/softrams/bulwark/compare/v7.0.9...v7.0.10) (2021-04-23)\n\n\n### Bug Fixes\n\n* **chart.js:** reverting chart.js to v2 ([05b7704](https://github.com/softrams/bulwark/commit/05b77040bee13cd7040a81cacb02f949b9b230a0))\n\n### [7.0.9](https://github.com/softrams/bulwark/compare/v7.0.8...v7.0.9) (2021-04-23)\n\n\n### Bug Fixes\n\n* **frontend/package.json:** update angular CLI ([3107c38](https://github.com/softrams/bulwark/commit/3107c389226dac0ee7037328d4849a91d56d70e9))\n* **password.utility.ts:** update password utility and user.controller for readability ([d6cdebb](https://github.com/softrams/bulwark/commit/d6cdebbe4958c85946f653779d7b0eab13e18b6f)), closes [#753](https://github.com/softrams/bulwark/issues/753)\n* **user.controller.ts:** fixed failing unit tests ([ff08636](https://github.com/softrams/bulwark/commit/ff0863640765c119d8064724359a813069b3b109)), closes [#753](https://github.com/softrams/bulwark/issues/753)\n\n\n### Others\n\n* **deps:** bump @angular-devkit/build-angular in /frontend ([3543a48](https://github.com/softrams/bulwark/commit/3543a48c0026c35cab27188ed6fd457e9983d9d4))\n* **deps:** bump @angular/cdk from 11.2.7 to 11.2.8 in /frontend ([414e270](https://github.com/softrams/bulwark/commit/414e27021febd4befe7e3133cd5e93b931b31f67))\n* **deps:** bump @angular/router from 11.2.8 to 11.2.9 in /frontend ([65b900e](https://github.com/softrams/bulwark/commit/65b900ef4f7db31e9ec18fa1a89579e29b4e687c))\n* **deps:** bump chart.js from 3.0.2 to 3.1.0 in /frontend ([c1c9a97](https://github.com/softrams/bulwark/commit/c1c9a97dfe5761da07f366bfbafdc420bcab473b))\n* **deps:** bump chart.js from 3.1.0 to 3.1.1 in /frontend ([346cb8b](https://github.com/softrams/bulwark/commit/346cb8b1b0912c826f76d5fa2d277e3856280979))\n* **deps:** bump concurrently from 6.0.0 to 6.0.1 ([217bcfe](https://github.com/softrams/bulwark/commit/217bcfec490913dacf44fc045815020fdcb4ab98))\n* **deps:** bump concurrently from 6.0.1 to 6.0.2 ([337ac3a](https://github.com/softrams/bulwark/commit/337ac3aa8d515703f817f996393f92ca29334333))\n* **deps:** bump core-js from 3.10.0 to 3.10.1 in /frontend ([8a321eb](https://github.com/softrams/bulwark/commit/8a321ebeed19ded4609a0ee816b9e173208b468f))\n* **deps:** bump core-js from 3.10.1 to 3.10.2 in /frontend ([7e99aec](https://github.com/softrams/bulwark/commit/7e99aec0503dfeec4f0f449055265ca30c1ba65c))\n* **deps:** bump helmet from 4.4.1 to 4.5.0 ([12275da](https://github.com/softrams/bulwark/commit/12275da6475a19368b0d9444446e0f9594d6bafd))\n* **deps:** bump ngx-markdown from 11.1.2 to 11.1.3 in /frontend ([deec7bf](https://github.com/softrams/bulwark/commit/deec7bf0abf33889382511cbf21c799f7e3c0e66))\n* **deps:** bump primeng from 11.3.1 to 11.3.2 in /frontend ([b296584](https://github.com/softrams/bulwark/commit/b296584376a6cd3c4fd1065d723c320f2a27f069))\n* **deps:** bump tslib from 2.1.0 to 2.2.0 in /frontend ([090415a](https://github.com/softrams/bulwark/commit/090415a6be66454e239469aec7cdb30ca361462a))\n* **deps-dev:** bump @babel/core from 7.13.13 to 7.13.15 ([4d2430d](https://github.com/softrams/bulwark/commit/4d2430dfd4225632179851f1b9f9e19619a57b70))\n* **deps-dev:** bump @babel/preset-env from 7.13.12 to 7.13.15 ([ca01c10](https://github.com/softrams/bulwark/commit/ca01c102cac3176e608b559b6cd5c2ad3fa077c2))\n* **deps-dev:** bump @types/node from 14.14.37 to 14.14.41 in /frontend ([2ae9804](https://github.com/softrams/bulwark/commit/2ae98040ecc6fc6b09bb6e81ac3448188c595a9f))\n* **deps-dev:** bump jasmine-spec-reporter in /frontend ([2da5194](https://github.com/softrams/bulwark/commit/2da51946fcf47a7f8a50dd884603ce50ec7ec59b))\n* **deps-dev:** bump standard-version from 9.1.1 to 9.2.0 ([b1623d9](https://github.com/softrams/bulwark/commit/b1623d951b713eab4f82e086a3518c04e64367b0))\n* **deps-dev:** bump ts-jest from 26.5.4 to 26.5.5 ([371a25b](https://github.com/softrams/bulwark/commit/371a25bc304577dbad5e8cea0b09df7da25623f1))\n* **deps-dev:** bump typescript from 4.2.3 to 4.2.4 ([a0bf85f](https://github.com/softrams/bulwark/commit/a0bf85fe62c951dca8d45099c5818c33be622fbc))\n* **frontend/package.json:** update @angular/cdk ([dd5aab2](https://github.com/softrams/bulwark/commit/dd5aab2904dc4b9be7bfb138fced39ebdd54c8f3))\n* **frontend/package.json:** update @angular/cli ([1ecd2ce](https://github.com/softrams/bulwark/commit/1ecd2ce752d3e0bd5baa8d5dddd9b2e53c89e707))\n* **frontend/package.json:** update @angular/core ([9052f8a](https://github.com/softrams/bulwark/commit/9052f8a084299f3ff81954ba3b931a03dc8e183d))\n* **frontend/package.json:** update global angular version ([9faf260](https://github.com/softrams/bulwark/commit/9faf260f296f49cea163893ce9857cc665f0fbfb))\n\n### [7.0.8](https://github.com/softrams/bulwark/compare/v7.0.7...v7.0.8) (2021-04-13)\n\n\n### Bug Fixes\n\n* **chart.js:** rever chart.js to v2 ([0c59004](https://github.com/softrams/bulwark/commit/0c5900431d92d4a71ff304749af7e84394674dd1))\n\n### [7.0.7](https://github.com/softrams/bulwark/compare/v7.0.6...v7.0.7) (2021-04-06)\n\n\n### Bug Fixes\n\n* **frontend/package.json:** downgrade Typescript. Downgrade Angular ([bd96650](https://github.com/softrams/bulwark/commit/bd96650c4278800f54443f24ec3e4ff597be6cb7))\n\n\n### Others\n\n* **deps:** bump @angular-devkit/build-angular in /frontend ([b6b577f](https://github.com/softrams/bulwark/commit/b6b577f16cccab8d8cf1a5a4229bdfe954a9184d))\n* **deps:** bump @angular-devkit/build-angular in /frontend ([72fa45b](https://github.com/softrams/bulwark/commit/72fa45b18a5cc39023cb1755967bd012d11be457))\n* **deps:** bump @angular-devkit/build-angular in /frontend ([4a94d44](https://github.com/softrams/bulwark/commit/4a94d4423ecd60953639756106f387866c769434))\n* **deps:** bump @angular/animations from 11.0.0 to 11.0.9 in /frontend ([9fbd8e5](https://github.com/softrams/bulwark/commit/9fbd8e53753f8cb0c2ac2275494b20b840c618bd))\n* **deps:** bump @angular/cdk from 11.2.4 to 11.2.5 in /frontend ([2a80c7c](https://github.com/softrams/bulwark/commit/2a80c7cfa7c18178d2c938ea0d0bc95ebe4cf23d))\n* **deps:** bump @angular/cdk from 11.2.5 to 11.2.6 in /frontend ([2aac976](https://github.com/softrams/bulwark/commit/2aac97649670c910782c4324ac0cfab30bc6cf79))\n* **deps:** bump @angular/cli from 11.2.4 to 11.2.5 in /frontend ([ec8d363](https://github.com/softrams/bulwark/commit/ec8d363ffef230deec2640273da8624e2a1afaa8))\n* **deps:** bump @angular/cli from 11.2.5 to 11.2.6 in /frontend ([2566862](https://github.com/softrams/bulwark/commit/25668626ec64ff599ae0414c2aeb7203457f04fb))\n* **deps:** bump @angular/cli from 11.2.6 to 11.2.7 in /frontend ([3b492ba](https://github.com/softrams/bulwark/commit/3b492ba39302894bfa0120bc0cfb1f99861362ac))\n* **deps:** bump @angular/common from 11.0.0 to 11.0.9 in /frontend ([0d79621](https://github.com/softrams/bulwark/commit/0d79621386d914371d0fb416785ef3f07983a12f))\n* **deps:** bump @angular/compiler from 11.0.0 to 11.2.8 in /frontend ([7c5ad50](https://github.com/softrams/bulwark/commit/7c5ad5021efc04fa2d2a6b0784bafa290e8f108a))\n* **deps:** bump @angular/core from 11.0.0 to 11.2.5 in /frontend ([025d6fd](https://github.com/softrams/bulwark/commit/025d6fd82202b3cd2fd96433f2bdfb29d64d243a))\n* **deps:** bump @angular/forms from 11.0.0 to 11.2.7 in /frontend ([5d79f0f](https://github.com/softrams/bulwark/commit/5d79f0fd3991c6c56c5899587728ac97e148a376))\n* **deps:** bump @angular/platform-browser in /frontend ([5fe50eb](https://github.com/softrams/bulwark/commit/5fe50eb8bf30340ffaced73169378afeb8b0ab15))\n* **deps:** bump @angular/platform-browser in /frontend ([9d1d6dd](https://github.com/softrams/bulwark/commit/9d1d6dd452f5b03e30d42c92fdf3cab3b1f68798))\n* **deps:** bump @angular/platform-browser-dynamic in /frontend ([036932f](https://github.com/softrams/bulwark/commit/036932f15f0c8624e1d9a7d1da3b7f523aeac175))\n* **deps:** bump @angular/router from 11.0.0 to 11.2.7 in /frontend ([30d45ca](https://github.com/softrams/bulwark/commit/30d45caf0b72e8ecf98ecbcb4753ca669af9ac4f))\n* **deps:** bump @angular/router from 11.2.7 to 11.2.8 in /frontend ([03c65e7](https://github.com/softrams/bulwark/commit/03c65e7ddf897f40663422c2e3d78f98e59c7e7d))\n* **deps:** bump chart.js from 2.9.4 to 3.0.2 in /frontend ([79e8fa8](https://github.com/softrams/bulwark/commit/79e8fa81ff9a876dd7b20a5f0c56254340c4ee10))\n* **deps:** bump core-js from 3.9.1 to 3.10.0 in /frontend ([67f34d9](https://github.com/softrams/bulwark/commit/67f34d9c28f6595409f9d3e171790136256d7c8e))\n* **deps:** bump mime-types from 2.1.29 to 2.1.30 ([d0545a7](https://github.com/softrams/bulwark/commit/d0545a75e6e4cad7ee80ff0676760ed045353006))\n* **deps:** bump primeng from 11.2.3 to 11.3.1 in /frontend ([f1e1563](https://github.com/softrams/bulwark/commit/f1e15633ea51dd83b366d5f922f3d24b5c66f4b8))\n* **deps:** bump rxjs from 6.6.6 to 6.6.7 in /frontend ([1d9a659](https://github.com/softrams/bulwark/commit/1d9a659bd39c78fa2c3a689653febfeb71f95630))\n* **deps:** bump typeorm from 0.2.31 to 0.2.32 ([a95c62e](https://github.com/softrams/bulwark/commit/a95c62e7c308bf165dad94eaae515a3215b4cd54))\n* **deps:** bump typescript from 4.0.5 to 4.2.3 in /frontend ([c0c6b0e](https://github.com/softrams/bulwark/commit/c0c6b0e653d8788fc8d228b03c65127fc221806e))\n* **deps:** bump zone.js from 0.10.3 to 0.11.4 in /frontend ([516ea44](https://github.com/softrams/bulwark/commit/516ea445faf8d0c0cf4dd8e3067b7abdf52eddcd))\n* **deps-dev:** bump @angular/language-service in /frontend ([b0d9084](https://github.com/softrams/bulwark/commit/b0d90849663f71da7b11f4c88f8a26305deaaaf0))\n* **deps-dev:** bump @angular/language-service in /frontend ([5b28665](https://github.com/softrams/bulwark/commit/5b286652a913b9a49f57f73a35a8dc18f553117b))\n* **deps-dev:** bump @angular/language-service in /frontend ([83d258c](https://github.com/softrams/bulwark/commit/83d258ceb6c599072e798d4ff3f21254b0019f45))\n* **deps-dev:** bump @babel/core from 7.13.10 to 7.13.13 ([d00445a](https://github.com/softrams/bulwark/commit/d00445a43fe181befdc9109eb34d7176b9b6a246))\n* **deps-dev:** bump @babel/preset-env from 7.13.10 to 7.13.12 ([42d18a1](https://github.com/softrams/bulwark/commit/42d18a19f18733d277c5af5f5ac72746a3cca730))\n* **deps-dev:** bump @commitlint/cli from 12.0.1 to 12.1.1 ([d983fe1](https://github.com/softrams/bulwark/commit/d983fe1fb15917481c5ec0f5fa908241d0505075))\n* **deps-dev:** bump @commitlint/config-conventional ([f4ed18a](https://github.com/softrams/bulwark/commit/f4ed18a6127180ee0353543113d5e9b40310c42d))\n* **deps-dev:** bump @types/jasmine from 3.6.6 to 3.6.7 in /frontend ([e43dbf0](https://github.com/softrams/bulwark/commit/e43dbf0689c6de24d4876edb01c576e80e5ebe53))\n* **deps-dev:** bump @types/jasmine from 3.6.7 to 3.6.9 in /frontend ([2137158](https://github.com/softrams/bulwark/commit/21371582d0f8e4812a7084c58fb67c5b3b30765c))\n* **deps-dev:** bump @types/jest from 26.0.20 to 26.0.21 ([44fba40](https://github.com/softrams/bulwark/commit/44fba40f43aaa55c682a066a31a4a6423c94fc04))\n* **deps-dev:** bump @types/jest from 26.0.21 to 26.0.22 ([f6eb459](https://github.com/softrams/bulwark/commit/f6eb4597579c45ce7475631aee493bcd95de7ec9))\n* **deps-dev:** bump @types/node from 14.14.33 to 14.14.34 in /frontend ([eb11d60](https://github.com/softrams/bulwark/commit/eb11d60b459a72f0925f47e2394ded1bbfa12dd3))\n* **deps-dev:** bump @types/node from 14.14.34 to 14.14.35 in /frontend ([7a517b1](https://github.com/softrams/bulwark/commit/7a517b1566c77cc09491b94740b7b6b3757656fa))\n* **deps-dev:** bump @types/node from 14.14.35 to 14.14.37 in /frontend ([ad90266](https://github.com/softrams/bulwark/commit/ad90266b452e5c4ebdb053f5f6cd920d00e8145c))\n* **deps-dev:** bump highlight.js from 10.6.0 to 10.7.1 ([5e4501f](https://github.com/softrams/bulwark/commit/5e4501f463b6291f6fc7dd208e7a906df88366e7))\n* **deps-dev:** bump highlight.js from 10.7.1 to 10.7.2 ([bffc257](https://github.com/softrams/bulwark/commit/bffc257d3af1d7af102fa8e65719495dbf4a28a3))\n* **deps-dev:** bump husky from 5.1.3 to 5.2.0 ([0227507](https://github.com/softrams/bulwark/commit/02275075d135c05d91a183321bdcf97c7afb17eb))\n* **deps-dev:** bump husky from 5.2.0 to 6.0.0 ([672a3e1](https://github.com/softrams/bulwark/commit/672a3e1f9dad542d588c12bd6c2d5928f78e1432))\n* **deps-dev:** bump jasmine-core from 3.6.0 to 3.7.1 in /frontend ([8128fac](https://github.com/softrams/bulwark/commit/8128fac61dad7cbbf2498711c8d84d6250a4557b))\n* **deps-dev:** bump karma from 6.1.1 to 6.2.0 in /frontend ([064857a](https://github.com/softrams/bulwark/commit/064857a24854b4aba7321cfa562df59c740071d5))\n* **deps-dev:** bump karma from 6.2.0 to 6.3.1 in /frontend ([4a537cd](https://github.com/softrams/bulwark/commit/4a537cd4fa7b83ba8ddb7e1cd908a242cf7db46f))\n* **deps-dev:** bump karma from 6.3.1 to 6.3.2 in /frontend ([8c9445b](https://github.com/softrams/bulwark/commit/8c9445b60d2d6b1c54a10b08101662d6f7f553ce))\n* **deps-dev:** bump ts-jest from 26.5.3 to 26.5.4 ([aad407d](https://github.com/softrams/bulwark/commit/aad407d8fe5c64531b5fa8c5dd879e8db6df329a))\n* **frontend/package.json:** angular forced update ([898b100](https://github.com/softrams/bulwark/commit/898b1005921ac4b0b20edb5175f9b4459da17cac))\n* **frontend/package.json:** update @angular/cdk ([a01f024](https://github.com/softrams/bulwark/commit/a01f0247a1859af656815ab164845ad736abda9b))\n* **package.json:** update y18n to fix security vulnerability ([e452c4c](https://github.com/softrams/bulwark/commit/e452c4c547ca303ac3eba57649f77aceb8c88068))\n\n### [7.0.6](https://github.com/softrams/bulwark/compare/v7.0.5...v7.0.6) (2021-03-11)\n\n\n### Bug Fixes\n\n* **puppeteer.utility.ts:** fix format error with Puppeteer ([daaf8d2](https://github.com/softrams/bulwark/commit/daaf8d2deb76035e118faaafe480f5b9d16b5238)), closes [#629](https://github.com/softrams/bulwark/issues/629)\n\n\n### Others\n\n* **deps:** bump @angular-devkit/build-angular in /frontend ([c88e50a](https://github.com/softrams/bulwark/commit/c88e50af2002e99e86395674496db761a6430138))\n* **deps:** bump @angular-devkit/build-angular in /frontend ([3dfa6e0](https://github.com/softrams/bulwark/commit/3dfa6e035a549cb31e63d84bbcb9e893c488f1d5))\n* **deps:** bump @angular/cdk from 11.2.1 to 11.2.2 in /frontend ([a0a3c94](https://github.com/softrams/bulwark/commit/a0a3c94eed9bdfea8743697710d9354f2a7cd5dc))\n* **deps:** bump @angular/cdk from 11.2.2 to 11.2.3 in /frontend ([a9337e1](https://github.com/softrams/bulwark/commit/a9337e1c10fa87e8f61e91b6e0f0abb619d44197))\n* **deps:** bump @angular/cli from 11.2.1 to 11.2.2 in /frontend ([897c052](https://github.com/softrams/bulwark/commit/897c052470fbfc382854e82c750887094aef50f0))\n* **deps:** bump @angular/cli from 11.2.2 to 11.2.3 in /frontend ([fa6f145](https://github.com/softrams/bulwark/commit/fa6f145953930de713512654a64fcfc54963ccec))\n* **deps:** bump bcrypt from 5.0.0 to 5.0.1 ([0c7f08e](https://github.com/softrams/bulwark/commit/0c7f08e8bb54e26f5460a41199871fe2a0cb8ca4))\n* **deps:** bump core-js from 3.9.0 to 3.9.1 in /frontend ([4d474b7](https://github.com/softrams/bulwark/commit/4d474b7e2186a0f7ed721f6f7d8400f22839560e))\n* **deps:** bump jira2md from 2.0.5 to 2.1.0 ([cff79ba](https://github.com/softrams/bulwark/commit/cff79bad62b51b836d9e4d2401ee3912e7077f32))\n* **deps:** bump ngx-markdown from 11.1.0 to 11.1.1 in /frontend ([c7160dc](https://github.com/softrams/bulwark/commit/c7160dc0b17d7de552f18a5f091a3f21a8fe8614))\n* **deps:** bump nodemailer from 6.4.18 to 6.5.0 ([58e85a8](https://github.com/softrams/bulwark/commit/58e85a83da3fcc498311d551cd6dc31faca9f307))\n* **deps:** bump puppeteer from 5.5.0 to 8.0.0 ([a8a60d6](https://github.com/softrams/bulwark/commit/a8a60d66d44be281cbfbb6baf186d7360b01f8ac))\n* **deps:** bump rxjs from 6.6.3 to 6.6.6 in /frontend ([9278b10](https://github.com/softrams/bulwark/commit/9278b101c4b44b278a3e015485b20fa7ddd68b67))\n* **deps-dev:** bump @angular/language-service in /frontend ([df4439d](https://github.com/softrams/bulwark/commit/df4439df1dee6b7514db04551f8449115f0ed03e))\n* **deps-dev:** bump @angular/language-service in /frontend ([9b57502](https://github.com/softrams/bulwark/commit/9b57502039953995af5ce681aa3b54de41fee51d))\n* **deps-dev:** bump @babel/core from 7.12.17 to 7.13.8 ([b748325](https://github.com/softrams/bulwark/commit/b7483259827b2ac61571841cbbf51cd171cfad95))\n* **deps-dev:** bump @babel/preset-env from 7.12.17 to 7.13.8 ([2537815](https://github.com/softrams/bulwark/commit/2537815a5a881f218c7a02923e9cde6a1d8c675b))\n* **deps-dev:** bump @babel/preset-env from 7.13.8 to 7.13.9 ([fa8c224](https://github.com/softrams/bulwark/commit/fa8c2244dcb419de8cf9f380d25000d88778587d))\n* **deps-dev:** bump @babel/preset-typescript from 7.12.17 to 7.13.0 ([ff119ee](https://github.com/softrams/bulwark/commit/ff119eefe5cefdacc918995f3e2f132820bdda96))\n* **deps-dev:** bump @commitlint/cli from 11.0.0 to 12.0.1 ([e27f94d](https://github.com/softrams/bulwark/commit/e27f94d5a46911009511a18cdee24939aa617043))\n* **deps-dev:** bump @commitlint/config-conventional ([6a5dfff](https://github.com/softrams/bulwark/commit/6a5dffff69e07eb6448963f431aa18a31c03656f))\n* **deps-dev:** bump @types/jasmine from 3.6.4 to 3.6.6 in /frontend ([74caec6](https://github.com/softrams/bulwark/commit/74caec62fcec3032a43d3d41464de691934467c5))\n* **deps-dev:** bump @types/node from 14.14.31 to 14.14.32 in /frontend ([bec5d54](https://github.com/softrams/bulwark/commit/bec5d54d346255624ab3230564972dd28fe7f032))\n* **deps-dev:** bump husky from 5.1.0 to 5.1.2 ([f1f788d](https://github.com/softrams/bulwark/commit/f1f788d22df97cc5fb88a0885a5d5241e0379be4))\n* **deps-dev:** bump husky from 5.1.2 to 5.1.3 ([c70aba1](https://github.com/softrams/bulwark/commit/c70aba1d96c51724a983fa463b2378e721513673))\n* **deps-dev:** bump ts-jest from 26.5.1 to 26.5.2 ([acbdebf](https://github.com/softrams/bulwark/commit/acbdebf3613a1eeb25c9e32c56085b74c2c3e9b4))\n* **deps-dev:** bump ts-jest from 26.5.2 to 26.5.3 ([e781e86](https://github.com/softrams/bulwark/commit/e781e86ce1b5067a43d2befd0941e77fe5aa4cb1))\n* **deps-dev:** bump typescript from 4.1.5 to 4.2.2 ([8667aff](https://github.com/softrams/bulwark/commit/8667aff32502288037d054e9a18cead6f353ea12))\n* **deps-dev:** bump typescript from 4.2.2 to 4.2.3 ([20cb44c](https://github.com/softrams/bulwark/commit/20cb44c188967bc96ba16c4fb8aa87abd845b9cd))\n* **package.json:** fixing conflicts ([3555d42](https://github.com/softrams/bulwark/commit/3555d42965c32956d930ccf9a78afa56c72fbbcf))\n\n### [7.0.5](https://github.com/softrams/bulwark/compare/v7.0.4...v7.0.5) (2021-02-25)\n\n\n### Bug Fixes\n\n* **report component:** fix pdf bug ([32f49cf](https://github.com/softrams/bulwark/commit/32f49cf954676990bbe20375effd7fdc852260da)), closes [#630](https://github.com/softrams/bulwark/issues/630)\n\n\n### Others\n\n* **deps:** bump @angular-devkit/build-angular in /frontend ([d0d3aa6](https://github.com/softrams/bulwark/commit/d0d3aa6222b3f42270643b507a2f84b46d6348d8))\n* **deps:** bump @angular-devkit/build-angular in /frontend ([06a7952](https://github.com/softrams/bulwark/commit/06a7952a6c6ded4d252d9eae4e48ca9f200d68ce))\n* **deps:** bump @angular/cdk from 11.1.2 to 11.2.0 in /frontend ([50f4127](https://github.com/softrams/bulwark/commit/50f41271566127f54ce9bcc76044a1078e7823c0))\n* **deps:** bump @angular/cdk from 11.2.0 to 11.2.1 in /frontend ([9183a72](https://github.com/softrams/bulwark/commit/9183a7203289d432ed540f2cfa6227f153f19d4b))\n* **deps:** bump @angular/cli from 11.1.4 to 11.2.0 in /frontend ([5efdefd](https://github.com/softrams/bulwark/commit/5efdefdd07dc0319054bf78784c0c9e3edb9ee7c))\n* **deps:** bump @angular/cli from 11.2.0 to 11.2.1 in /frontend ([4f33044](https://github.com/softrams/bulwark/commit/4f33044754b8591ba79652418300ea1f984449bd))\n* **deps:** bump @ng-select/ng-select from 6.0.0 to 6.1.0 in /frontend ([2ed6465](https://github.com/softrams/bulwark/commit/2ed64654f6a235c50da8901bd74f7e32f21d4622))\n* **deps:** bump concurrently from 5.3.0 to 6.0.0 ([710b652](https://github.com/softrams/bulwark/commit/710b652ddcc0651f8943f25e6fca6461f09e3952))\n* **deps:** bump core-js from 3.8.3 to 3.9.0 in /frontend ([58d28ef](https://github.com/softrams/bulwark/commit/58d28ef00548c2919c8119a3850b3f85e2fa63ff))\n* **deps:** bump mime-types from 2.1.28 to 2.1.29 ([a1b5ad9](https://github.com/softrams/bulwark/commit/a1b5ad9e8d60d95a06e55e400e4765f179a2b694))\n* **deps:** bump ngx-markdown from 11.0.1 to 11.1.0 in /frontend ([589fe38](https://github.com/softrams/bulwark/commit/589fe38acb5facc458c579f75fdada71ca62c0b0))\n* **deps:** bump nodemailer from 6.4.17 to 6.4.18 ([81f845c](https://github.com/softrams/bulwark/commit/81f845ce080047191a3e61c55a6b6b9311ef1f55))\n* **deps:** bump primeng from 11.2.0 to 11.2.1 in /frontend ([ae7815f](https://github.com/softrams/bulwark/commit/ae7815fa8ffe9ac76e581c0e1a696a537a3d07dc))\n* **deps:** bump primeng from 11.2.1 to 11.2.2 in /frontend ([22bc78e](https://github.com/softrams/bulwark/commit/22bc78e64ec67d731c3daccb1509ca561b5a8ce7))\n* **deps:** bump primeng from 11.2.2 to 11.2.3 in /frontend ([0cef0b7](https://github.com/softrams/bulwark/commit/0cef0b7cdda0d0986319823a0c8dfa6069cece78))\n* **deps:** bump typeorm from 0.2.30 to 0.2.31 ([07baf54](https://github.com/softrams/bulwark/commit/07baf546f05187b28d5e03142487ca17ab9a7f88))\n* **deps:** bump typescript from 4.0.5 to 4.0.7 in /frontend ([3d72b16](https://github.com/softrams/bulwark/commit/3d72b16035e92adbe219b0e48a7694c26a8d2ddf))\n* **deps-dev:** bump @angular/language-service in /frontend ([b7c03b0](https://github.com/softrams/bulwark/commit/b7c03b03ec2b769afe5fbc68d5552f7b475ca6f0))\n* **deps-dev:** bump @angular/language-service in /frontend ([b914a9d](https://github.com/softrams/bulwark/commit/b914a9d1f4178f1e37a10a66efe08829e3c8c4e4))\n* **deps-dev:** bump @babel/core from 7.12.13 to 7.12.16 ([0c682df](https://github.com/softrams/bulwark/commit/0c682df0f1ae8b73645f41b18ad5b8dce29014e3))\n* **deps-dev:** bump @babel/core from 7.12.16 to 7.12.17 ([49dd4d4](https://github.com/softrams/bulwark/commit/49dd4d4d41a6b869fda026b8422a8febafaf7f1b))\n* **deps-dev:** bump @babel/preset-env from 7.12.13 to 7.12.16 ([eb6f370](https://github.com/softrams/bulwark/commit/eb6f37013bf27789dd989ae25899345076515c1e))\n* **deps-dev:** bump @babel/preset-env from 7.12.16 to 7.12.17 ([a1ddf66](https://github.com/softrams/bulwark/commit/a1ddf66ea5c72b8db3df7385ca35f4026d88829e))\n* **deps-dev:** bump @babel/preset-typescript from 7.12.13 to 7.12.16 ([0875b9c](https://github.com/softrams/bulwark/commit/0875b9cc5f293bf13542195043d1a826239d6a1a))\n* **deps-dev:** bump @babel/preset-typescript from 7.12.16 to 7.12.17 ([5096b4f](https://github.com/softrams/bulwark/commit/5096b4f2314a3382d0afeb0bcbfd4453bda6e65a))\n* **deps-dev:** bump @types/jasmine from 3.6.3 to 3.6.4 in /frontend ([7b4fb70](https://github.com/softrams/bulwark/commit/7b4fb70051bd7af9cfb91bf58bfe422819bd48d3))\n* **deps-dev:** bump @types/node from 14.14.25 to 14.14.28 in /frontend ([5219d46](https://github.com/softrams/bulwark/commit/5219d46e6f9f0777c9695d968bac298539ee8b8b))\n* **deps-dev:** bump @types/node from 14.14.28 to 14.14.31 in /frontend ([e1ffb08](https://github.com/softrams/bulwark/commit/e1ffb08ac0887bb3c947f71edcbc815bda3a7fb3))\n* **deps-dev:** bump husky from 4.3.8 to 5.0.9 ([760fc1f](https://github.com/softrams/bulwark/commit/760fc1f6e5e8dc05392c54715c7d1e99e51a5a72))\n* **deps-dev:** bump husky from 5.0.9 to 5.1.0 ([b0f7733](https://github.com/softrams/bulwark/commit/b0f7733bfecf5056290129d413d206e06066bd09))\n* **deps-dev:** bump karma from 6.1.0 to 6.1.1 in /frontend ([dab7567](https://github.com/softrams/bulwark/commit/dab75670900e43c74286b084189c942b7bcdbc50))\n* **deps-dev:** bump sqlite3 from 5.0.1 to 5.0.2 ([269859e](https://github.com/softrams/bulwark/commit/269859edef5dca35beb9c9b5c07b6b300cb73ecb))\n* **deps-dev:** bump standard-version from 9.1.0 to 9.1.1 ([4a546fa](https://github.com/softrams/bulwark/commit/4a546fad84f7eabecee3fcb070f95211a4acb25d))\n* **deps-dev:** bump ts-jest from 26.5.0 to 26.5.1 ([f7fc6d3](https://github.com/softrams/bulwark/commit/f7fc6d30c1bd87595b8b1119df83e8b9b3e20118))\n* **deps-dev:** bump typescript from 4.1.3 to 4.1.5 ([979eddb](https://github.com/softrams/bulwark/commit/979eddba0e897528ba7b9ebeeb9ea7fbe35d3253))\n* **package.json:** update master with main ([29bc8b8](https://github.com/softrams/bulwark/commit/29bc8b8a75d9889ace85566dcd8b68070369f5c4))\n\n\n### Build System\n\n* **dependabot.yml:** removing Bill from dependabot config ([c8b2f9d](https://github.com/softrams/bulwark/commit/c8b2f9de379ae554c9ce21366f118e8ef3b8a3b6))\n\n\n### Docs\n\n* **readme.md:** update README.md ([3b778d2](https://github.com/softrams/bulwark/commit/3b778d29424bcbe6f40f8d4d37d3e9a9ff137821))\n\n### [7.0.4](https://github.com/softrams/bulwark/compare/v7.0.3...v7.0.4) (2021-02-10)\n\n\n### Bug Fixes\n\n* **assessment.controller.ts:** fixed authz issue ([7436e41](https://github.com/softrams/bulwark/commit/7436e41cc38fce6e34031aa43d7de81eb82cd1e2))\n\n### [7.0.3](https://github.com/softrams/bulwark/compare/v7.0.2...v7.0.3) (2021-02-10)\n\n\n### Bug Fixes\n\n* **assessment/vulnerability.controller.ts:** fixed authz tester roles issue with vulnerabilities ([a2febf7](https://github.com/softrams/bulwark/commit/a2febf7314b7bc349cc97ba696d106472e5f25da))\n\n### [7.0.2](https://github.com/softrams/bulwark/compare/v7.0.1...v7.0.2) (2021-02-10)\n\n\n### Bug Fixes\n\n* **vulnerability.controller.ts:** fixed tester access error ([ddc7c02](https://github.com/softrams/bulwark/commit/ddc7c02c6419cc63c8182b486bee0152c6b786eb))\n\n### [7.0.1](https://github.com/softrams/bulwark/compare/v7.0.0...v7.0.1) (2021-02-10)\n\n\n### Bug Fixes\n\n* **src:** fixed tester authz. Fixed no organization in system with admin. fixed the config script ([ff4696e](https://github.com/softrams/bulwark/commit/ff4696e88fcb7dcf892f9639dfc7d63d96500595))\n\n## [7.0.0](https://github.com/softrams/bulwark/compare/v6.4.8...v7.0.0) (2021-02-10)\n\n\n### ⚠ BREAKING CHANGES\n\n* **api-key.controller:** New `API_KEY` column for secret key\n* **api-key.controller:** New API_KEY table with many-to-one relation with the User table\n* **package.json:** Added additional process env variables.  Puppeteer functionality has been updated\nto use these new process env variables.\n* **authentication/authorization for admins:** Updated many-to-many relationship between Team and User entities\n* **team.ts, roles-enum.ts, config.controller:** Added Team entity to database\n* **reportaudit.ts:** Added new database table\n\n### Features\n\n* **api-key.controller:** added `SecretKey` column to API_KEY table ([6bdfaca](https://github.com/softrams/bulwark/commit/6bdfaca609d71ea6c64bb844411747e1160d8092)), closes [#483](https://github.com/softrams/bulwark/issues/483)\n* **api-key.controller:** implement API key integration ([bb34a64](https://github.com/softrams/bulwark/commit/bb34a6463b11d6122fefcf19d71b3186e7c3c8e1)), closes [#483](https://github.com/softrams/bulwark/issues/483)\n* **assessment.controller:** wire roles into assessment controller and component ([0660050](https://github.com/softrams/bulwark/commit/06600501d7e9a93acd6d1817ec2d820bd0f657e6)), closes [#484](https://github.com/softrams/bulwark/issues/484)\n* **asset team entities:** add many-to-many relationship between Team and Asset ([5d27d5c](https://github.com/softrams/bulwark/commit/5d27d5cc972380a4d1825f4341df67e3d4850593)), closes [#484](https://github.com/softrams/bulwark/issues/484)\n* **authentication/authorization for admins:** update many-to-many relationship user and teams ([f22e7de](https://github.com/softrams/bulwark/commit/f22e7dee01f667345e558ea96c84ad917b2fb63a)), closes [#484](https://github.com/softrams/bulwark/issues/484)\n* **dashboard.component org-form:** add role checks to org controller api's and angular components ([64de7a0](https://github.com/softrams/bulwark/commit/64de7a0dffd8136af05c254ed7d2941c47851994)), closes [#484](https://github.com/softrams/bulwark/issues/484)\n* **jwt.middleware.ts:** integrate roles into jwt middleware ([54e4647](https://github.com/softrams/bulwark/commit/54e46476441ce6c0cde3e89b80606026694f484a)), closes [#484](https://github.com/softrams/bulwark/issues/484)\n* **organization team entity:** add many-to-one relation ship between Team and Organization Entities ([1dfc435](https://github.com/softrams/bulwark/commit/1dfc43514c37e284b182714b98365325bfc1300a)), closes [#484](https://github.com/softrams/bulwark/issues/484)\n* **report-audit.controller:** implement report auditing function ([7a86b96](https://github.com/softrams/bulwark/commit/7a86b96ca6f68502a795e7623628d0735d2206b3)), closes [#484](https://github.com/softrams/bulwark/issues/484)\n* **reportaudit.ts:** create ReportAudit table.  Implement insert function ([a0e766d](https://github.com/softrams/bulwark/commit/a0e766d1e26b56647ed6b01d3f52ba01cb8a1fe6)), closes [#497](https://github.com/softrams/bulwark/issues/497)\n* **role.utility.ts:** implement role checking for organization ([ff8b5b8](https://github.com/softrams/bulwark/commit/ff8b5b845bfb3f720c5b5b0aa89e7067bccc3d86)), closes [#484](https://github.com/softrams/bulwark/issues/484)\n* **tea-form.component:** initial commit for team-form component ([67a4e6f](https://github.com/softrams/bulwark/commit/67a4e6fd623baa610e8f56512f24a116900c5f68)), closes [#484](https://github.com/softrams/bulwark/issues/484)\n* **team.component:** implement team component ([ad4d045](https://github.com/softrams/bulwark/commit/ad4d045eb7d5d4c301447de06f1a156a48230891)), closes [#484](https://github.com/softrams/bulwark/issues/484)\n* **team.component:** wire team form to api ([5a2fc4e](https://github.com/softrams/bulwark/commit/5a2fc4eb61c2772bb17e0301a42025935f741341)), closes [#484](https://github.com/softrams/bulwark/issues/484)\n* **team.controller:** implement additional Team APIs ([71292f1](https://github.com/softrams/bulwark/commit/71292f132e432c984a5750a21651c36d1d5e1117)), closes [#484](https://github.com/softrams/bulwark/issues/484)\n* **team.controller:** implement API routes for getMyTeams and deleteTeam ([842ca1e](https://github.com/softrams/bulwark/commit/842ca1efa23980ef27d997a36f67960e85aa3db2)), closes [#484](https://github.com/softrams/bulwark/issues/484)\n* **team.controller:** implement route functions for Add/Remove team assets ([6e68127](https://github.com/softrams/bulwark/commit/6e68127aee505d45a523f3ef585d6b31ced75956)), closes [#484](https://github.com/softrams/bulwark/issues/484)\n* **team.controller:** implment updateTeamInfo function ([c7761ea](https://github.com/softrams/bulwark/commit/c7761ea57d83a9e98cbdf9fc479371f729a697a5)), closes [#484](https://github.com/softrams/bulwark/issues/484)\n* **team.controller.ts:** added helper functions to team controller ([13a2cca](https://github.com/softrams/bulwark/commit/13a2ccaa9153e0e45c505efa4cddf7bb919397e1)), closes [#484](https://github.com/softrams/bulwark/issues/484)\n* **team.ts:** remove single asset column ([89e1e84](https://github.com/softrams/bulwark/commit/89e1e840461921486a3984412260d322c9a8a89d)), closes [#484](https://github.com/softrams/bulwark/issues/484)\n* **team.ts, roles-enum.ts, config.controller:** add Team entity to database ([71469cc](https://github.com/softrams/bulwark/commit/71469ccaae2620e0f7b686828579a813d26431f2)), closes [#484](https://github.com/softrams/bulwark/issues/484)\n* **user-management:** update user management component.  Update team component ([4fa4425](https://github.com/softrams/bulwark/commit/4fa4425444f4ca423f7e5abe3867513a2046ffd5)), closes [#484](https://github.com/softrams/bulwark/issues/484)\n* **user-management.component:** initial commit for user-management.component ([7ca32e2](https://github.com/softrams/bulwark/commit/7ca32e2d68a7e4ba5899dde974e5e5b7f26446f5)), closes [#553](https://github.com/softrams/bulwark/issues/553)\n* **user-management.component:** initial commit of user-management template ([b373629](https://github.com/softrams/bulwark/commit/b37362934ddbf9a6a5015fc263715e4da768cac1)), closes [#554](https://github.com/softrams/bulwark/issues/554)\n* **user-management.component:** updated user-management.component template and component ([44bd8ff](https://github.com/softrams/bulwark/commit/44bd8ffb8d41645f5014e7bf86b573f8656625b5)), closes [#553](https://github.com/softrams/bulwark/issues/553)\n* **user-profile.component:** add teams to user profile ([aba66fc](https://github.com/softrams/bulwark/commit/aba66fc231ea1e3f0cbade7a4228bce3e1159294)), closes [#484](https://github.com/softrams/bulwark/issues/484)\n* **user.controller.ts:** implement API to create a user ([37aea71](https://github.com/softrams/bulwark/commit/37aea71654a8245f1e82aa78f8bb601e2de71a6d)), closes [#553](https://github.com/softrams/bulwark/issues/553)\n* **vuln.controller:** wire roles into vuln controller and component ([d4db2fc](https://github.com/softrams/bulwark/commit/d4db2fccea945b9269bb4d3147dd0cf0125f248a)), closes [#484](https://github.com/softrams/bulwark/issues/484)\n\n\n### Bug Fixes\n\n* no Vulns Blank page ([6a4247a](https://github.com/softrams/bulwark/commit/6a4247a7eef8fe7cb5e667720bf6b25da6173067))\n* **app.ts:** cSP error fix ([b6a02f8](https://github.com/softrams/bulwark/commit/b6a02f8c5e6943cbae4735c43d0f6fb3fd96ff13)), closes [#566](https://github.com/softrams/bulwark/issues/566)\n* **app.ts:** fix CSP errors ([594628c](https://github.com/softrams/bulwark/commit/594628c0672cfbe647be21a2fbefe7b7120ef0ff)), closes [#566](https://github.com/softrams/bulwark/issues/566)\n* **docker-compose.yml:** fix docker-compose bug ([58a549f](https://github.com/softrams/bulwark/commit/58a549f397d54fab63352b20b426fdbb4439c090))\n* **fix csp errors:** cSP errors were generated in console ([801c9a3](https://github.com/softrams/bulwark/commit/801c9a3742ffb515a90880147d2d1ab97c67774f))\n* **frontend/package.json:** update script to use different angular envv ([8c2481e](https://github.com/softrams/bulwark/commit/8c2481ec7297033ea27a85690c80e89bed89ebfe)), closes [#567](https://github.com/softrams/bulwark/issues/567)\n* **package.json:** updated package.json script ([3e201eb](https://github.com/softrams/bulwark/commit/3e201eb9b14facd7808874d69604c4beee9c909e)), closes [#567](https://github.com/softrams/bulwark/issues/567)\n* **role.utility.ts:** fixed role bug ([24db31a](https://github.com/softrams/bulwark/commit/24db31af83bf687c74ba10ebef0c7dcbad6f6cc5))\n* **setenv.ts:** implement dynamic port and IP address based on env var ([f52e696](https://github.com/softrams/bulwark/commit/f52e69673cdbf0df0a533437d62f198d8afb69b9)), closes [#567](https://github.com/softrams/bulwark/issues/567)\n* **team-form.component:** fix undefined bug if team does not have org ([64e15f1](https://github.com/softrams/bulwark/commit/64e15f1e168327c900f70e24d7a08ddebc2559be))\n\n\n### Tests\n\n* **api-key.controller.spec.ts:** implement unit tests for API key controller ([48df0ef](https://github.com/softrams/bulwark/commit/48df0efa8561be549e88e4306692f58f5c44a0da)), closes [#483](https://github.com/softrams/bulwark/issues/483)\n* **asset.controller:** implemented roles into asset controller ([a63b8dd](https://github.com/softrams/bulwark/commit/a63b8dd53c4d36483ceed188c717af6d06561c5e)), closes [#484](https://github.com/softrams/bulwark/issues/484)\n* **config.controller.spec.ts:** implement unit tests for Teams ([b95e398](https://github.com/softrams/bulwark/commit/b95e39804f7be517262e4fc6a7961ce1d54b184f)), closes [#484](https://github.com/softrams/bulwark/issues/484)\n* **team.controller:** fix broken unit tests ([ad9c3c9](https://github.com/softrams/bulwark/commit/ad9c3c911456b36b65ed486bd044df7465230d99)), closes [#484](https://github.com/softrams/bulwark/issues/484)\n\n\n### Others\n\n* **.prettierignore:** fix merge conflict ([953def8](https://github.com/softrams/bulwark/commit/953def8b4eb56227d4e486e3357c7193256249b9))\n* **.prettierignore:** fix merge conflicts. fix prettier config error ([baf5767](https://github.com/softrams/bulwark/commit/baf57670053079ddfd52435a2a5e19297ab9efde)), closes [#484](https://github.com/softrams/bulwark/issues/484)\n* **deps:** bump @angular-devkit/build-angular in /frontend ([c2c2184](https://github.com/softrams/bulwark/commit/c2c2184b007eb7b85b33af24db0aba2c938a1be0))\n* **deps:** bump @angular-devkit/build-angular in /frontend ([7ec05d3](https://github.com/softrams/bulwark/commit/7ec05d34b112779549bd41506eb6de17dc8510f4))\n* **deps:** bump @angular-devkit/build-angular in /frontend ([c100c14](https://github.com/softrams/bulwark/commit/c100c147ca781676382a82e86eb3e70112f350f2))\n* **deps:** bump @angular/cdk from 11.0.3 to 11.0.4 in /frontend ([4e2db70](https://github.com/softrams/bulwark/commit/4e2db700078e161eb0a80189801b49bfc765264d))\n* **deps:** bump @angular/cdk from 11.0.4 to 11.1.0 in /frontend ([d4829cc](https://github.com/softrams/bulwark/commit/d4829ccf8c5b4303427696fa4b369793326e7b6b))\n* **deps:** bump @angular/cdk from 11.1.0 to 11.1.2 in /frontend ([63ee378](https://github.com/softrams/bulwark/commit/63ee378b8c750210053cde47a4ac765156a14a05))\n* **deps:** bump @angular/cli from 11.0.6 to 11.0.7 in /frontend ([22fd855](https://github.com/softrams/bulwark/commit/22fd85561ef8d1147f977e63fdd44f85f838861e))\n* **deps:** bump @angular/cli from 11.0.7 to 11.1.1 in /frontend ([df3c6c5](https://github.com/softrams/bulwark/commit/df3c6c561e57fd5eceee1d455e82b44619dd17ab))\n* **deps:** bump @angular/cli from 11.1.1 to 11.1.4 in /frontend ([a3ecf96](https://github.com/softrams/bulwark/commit/a3ecf9652a037a8ed7bb30c5a9b6b6c9c6f258f9))\n* **deps:** bump @ng-select/ng-select from 5.0.15 to 5.1.0 in /frontend ([0d1c7a7](https://github.com/softrams/bulwark/commit/0d1c7a79e2e504e0aa77cc88741f2b31cc29ab3b))\n* **deps:** bump @ng-select/ng-select from 5.1.0 to 6.0.0 in /frontend ([d4fa635](https://github.com/softrams/bulwark/commit/d4fa635cf360959565a62b45d676d3b5c3875cce))\n* **deps:** bump @types/express from 4.17.9 to 4.17.11 ([fcfd3ee](https://github.com/softrams/bulwark/commit/fcfd3eef5ce03f04bc1ca4495308e867f476275a))\n* **deps:** bump class-validator from 0.12.2 to 0.13.1 ([404493a](https://github.com/softrams/bulwark/commit/404493abb16837298ac8f5863d53e22a68e808d9))\n* **deps:** bump core-js from 3.8.2 to 3.8.3 in /frontend ([1aff860](https://github.com/softrams/bulwark/commit/1aff860c3dacbe905bea8bc44e6aede86af343d9))\n* **deps:** bump helmet from 4.3.1 to 4.4.0 ([349d1b6](https://github.com/softrams/bulwark/commit/349d1b68976d36232f3f0be86474dc818b78df5e))\n* **deps:** bump helmet from 4.4.0 to 4.4.1 ([92b5ae9](https://github.com/softrams/bulwark/commit/92b5ae98ba0bdee4a218805a8042a61c2d14538e))\n* **deps:** bump jira2md from 2.0.4 to 2.0.5 ([2e0fd53](https://github.com/softrams/bulwark/commit/2e0fd532ad67c33f545bc225ac5103ecfe0fa3e9))\n* **deps:** bump primeng from 11.0.0 to 11.1.0 in /frontend ([474f4aa](https://github.com/softrams/bulwark/commit/474f4aafec9e3b4c75a5e8621fa8d9fc047bf622))\n* **deps:** bump primeng from 11.1.0 to 11.2.0 in /frontend ([697aa33](https://github.com/softrams/bulwark/commit/697aa33ad9f8eed67f1a2bc09d3e9ba562a131ae))\n* **deps:** bump typeorm from 0.2.29 to 0.2.30 ([a3c091c](https://github.com/softrams/bulwark/commit/a3c091c54ad044980eed8c1e8d7434960a77618e))\n* **deps-dev:** bump @angular/language-service in /frontend ([b104657](https://github.com/softrams/bulwark/commit/b104657d512caaaa0ed3c77f09d78774e534e0b7))\n* **deps-dev:** bump @angular/language-service in /frontend ([5dc85dd](https://github.com/softrams/bulwark/commit/5dc85dd16c9ac7765960e21a8b1b738367be7fd7))\n* **deps-dev:** bump @angular/language-service in /frontend ([f418dd1](https://github.com/softrams/bulwark/commit/f418dd1e0c4d79a2d8651832b099b2b5d18990cf))\n* **deps-dev:** bump @babel/core from 7.12.10 to 7.12.13 ([6d7cf75](https://github.com/softrams/bulwark/commit/6d7cf7516b591881a5b9f3e65e776bc8c5acfd22))\n* **deps-dev:** bump @babel/preset-env from 7.12.11 to 7.12.13 ([042cbb2](https://github.com/softrams/bulwark/commit/042cbb25c6f9b976ec7caff7a3dc52c6c359c39d))\n* **deps-dev:** bump @babel/preset-typescript from 7.12.7 to 7.12.13 ([acaa0e7](https://github.com/softrams/bulwark/commit/acaa0e73b1c28ae2ad30b34d7c14e0ad42d59137))\n* **deps-dev:** bump @types/jasmine from 3.6.2 to 3.6.3 in /frontend ([3eb9afa](https://github.com/softrams/bulwark/commit/3eb9afa34b92a973d875f6d4638f327accf4c5b2))\n* **deps-dev:** bump @types/node from 14.14.20 to 14.14.21 in /frontend ([6d5d746](https://github.com/softrams/bulwark/commit/6d5d7462e132f847a7b13515d263bde71085a061))\n* **deps-dev:** bump @types/node from 14.14.21 to 14.14.22 in /frontend ([8810309](https://github.com/softrams/bulwark/commit/8810309ee41068fcdb5ffbf0346ab72d6204b290))\n* **deps-dev:** bump @types/node from 14.14.22 to 14.14.25 in /frontend ([90f8f93](https://github.com/softrams/bulwark/commit/90f8f937247ea4490909bc444f6fa18a23cf98ad))\n* **deps-dev:** bump highlight.js from 10.5.0 to 10.6.0 ([a0c45be](https://github.com/softrams/bulwark/commit/a0c45be652e787029694d4055f63ce7019d2608d))\n* **deps-dev:** bump husky from 4.3.7 to 4.3.8 ([c2ea443](https://github.com/softrams/bulwark/commit/c2ea443fbbdcea71836db4a32e97add1e4f4f160))\n* **deps-dev:** bump karma from 5.2.3 to 6.0.0 in /frontend ([4e6b2b5](https://github.com/softrams/bulwark/commit/4e6b2b5524a3637798344ce2ddda2caa64d5cc75))\n* **deps-dev:** bump karma from 6.0.0 to 6.0.1 in /frontend ([6c5f2ce](https://github.com/softrams/bulwark/commit/6c5f2ce9aea265d7ddddbe7402eb2dfbcf9444d3))\n* **deps-dev:** bump karma from 6.0.1 to 6.1.0 in /frontend ([48c7b74](https://github.com/softrams/bulwark/commit/48c7b74f473478995251a6854f28b4ee482ffe53))\n* **deps-dev:** bump lint-staged from 10.5.3 to 10.5.4 ([2e8aa42](https://github.com/softrams/bulwark/commit/2e8aa4249744a846e79c286b887ef6651181b87b))\n* **deps-dev:** bump ts-jest from 26.4.4 to 26.5.0 ([9232d26](https://github.com/softrams/bulwark/commit/9232d2657c228f71839baed49915994344d4d910))\n* **fixing merge conflicts:** fix merge conflicts ([a2c2148](https://github.com/softrams/bulwark/commit/a2c21483fd61227a8afa8dac10f9d197d2cfbd97)), closes [#484](https://github.com/softrams/bulwark/issues/484)\n* **global:** merge [#553](https://github.com/softrams/bulwark/issues/553) branch into roles ([28414e1](https://github.com/softrams/bulwark/commit/28414e11348056d7c46ad35ae6c899e95b27b29e)), closes [#484](https://github.com/softrams/bulwark/issues/484)\n* **merge conflicts:** fix merge conflicts ([a431af0](https://github.com/softrams/bulwark/commit/a431af0e9efc8a1d0425c690c615ab4d0c1ab30e)), closes [#567](https://github.com/softrams/bulwark/issues/567)\n* **setenv.ts:** update console.info and documentation ([3aa0e94](https://github.com/softrams/bulwark/commit/3aa0e94b526932fdf70fde6901e5a2ffa33a4a55)), closes [#567](https://github.com/softrams/bulwark/issues/567)\n\n\n### Docs\n\n* **readme.md:** update README.md ([ba3a77d](https://github.com/softrams/bulwark/commit/ba3a77d138bc43e81b119ef0c781c121b3d580fb)), closes [#484](https://github.com/softrams/bulwark/issues/484)\n\n\n### Code Refactoring\n\n* **config.controller:** remove global teams ([ab8bd9b](https://github.com/softrams/bulwark/commit/ab8bd9b8be68aa951c628eda651a5ca446a5edfe)), closes [#484](https://github.com/softrams/bulwark/issues/484)\n\n\n### Build System\n\n* **.gitignore:** delete generated angular env files ([3be81a2](https://github.com/softrams/bulwark/commit/3be81a2395c933efd71e1b6b8453b84ad34fc050)), closes [#567](https://github.com/softrams/bulwark/issues/567)\n\n### [6.4.8](https://github.com/softrams/bulwark/compare/v6.4.7...v6.4.8) (2021-01-14)\n\n\n### Build System\n\n* **dockerfile:** remove unneeded packages ([b6c9de1](https://github.com/softrams/bulwark/commit/b6c9de185359cf3c7028492d780f670484a83f53))\n\n### [6.4.7](https://github.com/softrams/bulwark/compare/v6.4.6...v6.4.7) (2021-01-14)\n\n\n### Build System\n\n* **dockerfile:** add python to container ([6224469](https://github.com/softrams/bulwark/commit/62244692a50beb155073192656f2a03456dc61ff))\n\n### [6.4.6](https://github.com/softrams/bulwark/compare/v6.4.5...v6.4.6) (2021-01-14)\n\n\n### Build System\n\n* **dockerfile:** add rimraf library ([70e63ba](https://github.com/softrams/bulwark/commit/70e63bab5787d5d40f81c402ea2a6653311e2e52))\n\n### [6.4.5](https://github.com/softrams/bulwark/compare/v6.4.4...v6.4.5) (2021-01-14)\n\n\n### Build System\n\n* **dockerfile:** install typescript globally ([eac63c8](https://github.com/softrams/bulwark/commit/eac63c8d4a19668f2c3f6e70445a2f43360bd51f))\n\n### [6.4.4](https://github.com/softrams/bulwark/compare/v6.4.3...v6.4.4) (2021-01-14)\n\n\n### Build System\n\n* **dockerfile:** root !== ROOT ([2f22d20](https://github.com/softrams/bulwark/commit/2f22d20fbc027e01427339fa068c3ab18ffe7f7b))\n\n### [6.4.3](https://github.com/softrams/bulwark/compare/v6.4.2...v6.4.3) (2021-01-14)\n\n### Build System\n\n- **dockerfile:** add USER ROOT ([3bf6e0e](https://github.com/softrams/bulwark/commit/3bf6e0e240db62090fb1adf4b8aa2f4f3fb78284))\n- **dockerfile:** update npm install ([ca447d3](https://github.com/softrams/bulwark/commit/ca447d35d1fc9cae2efe6b8876faa34df9ed1540))\n\n### [6.4.2](https://github.com/softrams/bulwark/compare/v6.4.1...v6.4.2) (2021-01-12)\n\n### Bug Fixes\n\n- **vulnerability.component.html:** fixed Risk and Status undefined error ([bbc407f](https://github.com/softrams/bulwark/commit/bbc407fa03d3294744595dc26af0421f168c5589)), closes [#539](https://github.com/softrams/bulwark/issues/539)\n\n### Others\n\n- **package-lock.json:** commit package-lock.json ([22caf95](https://github.com/softrams/bulwark/commit/22caf95efd36ee10a477b5f5cf756f4697c776fc))\n\n### [6.4.1](https://github.com/softrams/bulwark/compare/v6.4.0...v6.4.1) (2021-01-11)\n\n### Others\n\n- **deps:** bump @angular-devkit/build-angular in /frontend ([b388648](https://github.com/softrams/bulwark/commit/b388648a4314e9efba338c39ccbe0516319f5675))\n- **deps:** bump @angular/cli from 11.0.5 to 11.0.6 in /frontend ([ca9ef69](https://github.com/softrams/bulwark/commit/ca9ef69a6e84607e8c8a47a6ff9ac1ca63e6d803))\n- **deps:** bump @ng-select/ng-select in /frontend ([ade5b50](https://github.com/softrams/bulwark/commit/ade5b508303e8b13b174c918208125cb6c3bcd40))\n- **deps:** bump tslib from 2.0.3 to 2.1.0 in /frontend ([45b481e](https://github.com/softrams/bulwark/commit/45b481eef0f7012f71c2f20da045251201ce75b8))\n- **deps-dev:** bump @angular/language-service in /frontend ([0597a7f](https://github.com/softrams/bulwark/commit/0597a7fea71bdd31c0873630fa52a6f3959601cc))\n- **deps-dev:** bump @types/jest from 26.0.19 to 26.0.20 ([eab0403](https://github.com/softrams/bulwark/commit/eab040333f752b96b970bf7fbb67781f3061dbb0))\n- **deps-dev:** bump @types/node from 14.14.19 to 14.14.20 in /frontend ([6c95f9c](https://github.com/softrams/bulwark/commit/6c95f9c8994fb5d04881846f8465d7b4dcb45383))\n- **deps-dev:** bump husky from 4.3.6 to 4.3.7 ([db63af4](https://github.com/softrams/bulwark/commit/db63af4c13f76386a6a7ad331aa62045ba4edfad))\n- **deps-dev:** bump sqlite3 from 5.0.0 to 5.0.1 ([45e0a8c](https://github.com/softrams/bulwark/commit/45e0a8c4452e994728295e8f0ee4479c1d077b23))\n\n## [6.4.0](https://github.com/softrams/bulwark/compare/v6.3.1...v6.4.0) (2021-01-04)\n\n### Features\n\n- **adminsitration area:** adding Administration Area ([e5d91b2](https://github.com/softrams/bulwark/commit/e5d91b21459a5460ea848d3530085596a15b7cbf)), closes [#491](https://github.com/softrams/bulwark/issues/491)\n- **invite-user.component:** update look and feel of invite-user and settings component ([cdcf572](https://github.com/softrams/bulwark/commit/cdcf572ed6022ba5acd0e477f0580f8013e46349))\n- **package.json:** add prettier pre-commit hook ([7602dfb](https://github.com/softrams/bulwark/commit/7602dfb39f95d0e4349ee8dde8da885508c17b7e)), closes [#509](https://github.com/softrams/bulwark/issues/509)\n- **prettier:** adding prettier ([eb44eb1](https://github.com/softrams/bulwark/commit/eb44eb111f7d028a9282071ac67fe9b2a67b0b5d)), closes [#508](https://github.com/softrams/bulwark/issues/508)\n- **prettier:** update prettier config ([ba2c5f1](https://github.com/softrams/bulwark/commit/ba2c5f188f0248d05231c4b5abe8c68ccf154219)), closes [#509](https://github.com/softrams/bulwark/issues/509)\n- **report component:** color changes to pie chart ([7269d62](https://github.com/softrams/bulwark/commit/7269d6282c46a4284ad58dd008ca73f982c3deae)), closes [#487](https://github.com/softrams/bulwark/issues/487)\n- **report component:** color changes to the pie chart ([707271e](https://github.com/softrams/bulwark/commit/707271e409b3be5a28731fedd6b88fac1a7f860d)), closes [#487](https://github.com/softrams/bulwark/issues/487)\n- **report.component:** implement overall risk sort ([e31c76c](https://github.com/softrams/bulwark/commit/e31c76ce26f8ce22b3af616650414555cf1a1b1b)), closes [#482](https://github.com/softrams/bulwark/issues/482)\n\n### Bug Fixes\n\n- **assessments.component.ts:** fixed build break due to deprecated FilterUtils ([d7c2ae3](https://github.com/softrams/bulwark/commit/d7c2ae32cf274c27e1c55284e4765543d5449dbf))\n\n### Others\n\n- **assessments.component.ts:** remove library and console.log ([0cab4ea](https://github.com/softrams/bulwark/commit/0cab4ea553e6241a89df73d0c04e6c5154b45815))\n- **deps:** bump @angular-devkit/build-angular in /frontend ([b33ce85](https://github.com/softrams/bulwark/commit/b33ce85f8b2e9199c3a12a3c7a8de5cd6f7d0d15))\n- **deps:** bump @angular/cdk from 11.0.1 to 11.0.2 in /frontend ([49da433](https://github.com/softrams/bulwark/commit/49da433b38adb03d86aa0680e85019f01bfae331))\n- **deps:** bump @angular/cdk from 11.0.2 to 11.0.3 in /frontend ([786c8f0](https://github.com/softrams/bulwark/commit/786c8f09d801cd8cd0a0a3758d65a23b121b334b))\n- **deps:** bump @angular/cli from 11.0.3 to 11.0.5 in /frontend ([d9033e4](https://github.com/softrams/bulwark/commit/d9033e4306de09eff073cec9355c097b779c00e5))\n- **deps:** bump @ng-select/ng-select from 5.0.9 to 5.0.11 in /frontend ([b1428ab](https://github.com/softrams/bulwark/commit/b1428ab991b37c18a3805caa237354be2239bf86))\n- **deps:** bump @ng-select/ng-select in /frontend ([1cdc8ab](https://github.com/softrams/bulwark/commit/1cdc8ab676bbc2320eafe04d746d3bb2bce4382f))\n- **deps:** bump core-js from 3.8.1 to 3.8.2 in /frontend ([6c05e06](https://github.com/softrams/bulwark/commit/6c05e06b90ef53ad168bbb7d638d23e62b38515b))\n- **deps:** bump helmet from 4.2.0 to 4.3.1 ([2f0e7fb](https://github.com/softrams/bulwark/commit/2f0e7fbf405dcf4bf758f248fbc6e313a0279d72))\n- **deps:** bump mime-types from 2.1.27 to 2.1.28 ([0c85c5f](https://github.com/softrams/bulwark/commit/0c85c5f9904219fb4dbf89eb935fe4a2208e05e3))\n- **deps:** bump nodemailer from 6.4.16 to 6.4.17 ([3c7456d](https://github.com/softrams/bulwark/commit/3c7456df86e7dcc5c29ab8b6da3034ddaf04f07c))\n- **deps:** bump primeng from 10.0.3 to 11.0.0 in /frontend ([992dc1a](https://github.com/softrams/bulwark/commit/992dc1a6c6d5d12c406f1cfd1fa1ba6eba65a6a4))\n- **deps:** bump ts-node from 9.1.0 to 9.1.1 ([24f3afa](https://github.com/softrams/bulwark/commit/24f3afaec8a4e010e4382ce48243b37a7c3c3ad7))\n- **deps:** bump uuid from 8.3.1 to 8.3.2 ([a9d5a1b](https://github.com/softrams/bulwark/commit/a9d5a1b3854043b2e4c2852a9056103de2f692a0))\n- **deps-dev:** bump @angular/language-service in /frontend ([6b9bb89](https://github.com/softrams/bulwark/commit/6b9bb89cc1ece2f94bd3b1b186ea09159d524314))\n- **deps-dev:** bump @angular/language-service in /frontend ([65f5818](https://github.com/softrams/bulwark/commit/65f5818de0ebe716348a99877e68b7fc8a676387))\n- **deps-dev:** bump @babel/core from 7.12.9 to 7.12.10 ([ba5a326](https://github.com/softrams/bulwark/commit/ba5a32667b6b8c6a7b8a7c54bf2e8cedc3045b91))\n- **deps-dev:** bump @babel/preset-env from 7.12.7 to 7.12.11 ([dde25dd](https://github.com/softrams/bulwark/commit/dde25dde0fd497552ca931c6dbd43c53eab95790))\n- **deps-dev:** bump @types/jest from 26.0.16 to 26.0.19 ([463675c](https://github.com/softrams/bulwark/commit/463675cf2f763b534d16a070a780155c34742373))\n- **deps-dev:** bump @types/node from 14.14.10 to 14.14.13 in /frontend ([424df1c](https://github.com/softrams/bulwark/commit/424df1c5566ac300c8237e90f56794ef06aad476))\n- **deps-dev:** bump @types/node from 14.14.13 to 14.14.14 in /frontend ([5b61ed8](https://github.com/softrams/bulwark/commit/5b61ed8774a1130d954513d3fa788ca4d5ed588e))\n- **deps-dev:** bump @types/node from 14.14.14 to 14.14.16 in /frontend ([d322e2d](https://github.com/softrams/bulwark/commit/d322e2d6316ec92dcdfcef1cf988468f618256f1))\n- **deps-dev:** bump @types/node from 14.14.16 to 14.14.19 in /frontend ([9b41c20](https://github.com/softrams/bulwark/commit/9b41c20204699310a1b65a09a31b6052d5a68765))\n- **deps-dev:** bump highlight.js from 10.4.1 to 10.5.0 ([2fbf9c6](https://github.com/softrams/bulwark/commit/2fbf9c69aace53f3abb55af007c70a6aacf71a46))\n- **deps-dev:** bump husky from 4.3.0 to 4.3.6 ([7266035](https://github.com/softrams/bulwark/commit/7266035a5ecf43389c3d9f2f8b79c39866695cf3))\n- **deps-dev:** bump karma from 5.1.1 to 5.2.3 in /frontend ([a58dbf9](https://github.com/softrams/bulwark/commit/a58dbf9af33270f5d8d9d153ce73cc9e1c74c9dd))\n- **deps-dev:** bump mock-express-response from 0.2.2 to 0.3.0 ([e9588b8](https://github.com/softrams/bulwark/commit/e9588b884b6656aa59058c5de58bd539da61c6f0))\n- **deps-dev:** bump standard-version from 9.0.0 to 9.1.0 ([a497d0e](https://github.com/softrams/bulwark/commit/a497d0e14d1fb8a9adebde6d2da836efde70e48f))\n- **deps-dev:** bump ts-node from 9.0.0 to 9.1.1 in /frontend ([f18666b](https://github.com/softrams/bulwark/commit/f18666b4ccf5c8550ba6e94146673bed50a0d855))\n- **deps-dev:** bump typescript from 4.1.2 to 4.1.3 ([29c5db5](https://github.com/softrams/bulwark/commit/29c5db53093cc51615eacc5ce2c3999987987aac))\n- **report.component:** fix lint issue ([aca3ea9](https://github.com/softrams/bulwark/commit/aca3ea918f1c772d9907e10b8c476595ecfc1976)), closes [#482](https://github.com/softrams/bulwark/issues/482)\n\n### Docs\n\n- **testing.md:** minor changes to testing.md file ([a9c04c0](https://github.com/softrams/bulwark/commit/a9c04c0a3f5e8501dc77c7e20854c3536767f639)), closes [#509](https://github.com/softrams/bulwark/issues/509)\n- **testing.md:** uopdating documentation ([840f4e2](https://github.com/softrams/bulwark/commit/840f4e2f1394b096c57885611e696f7de993afac)), closes [#509](https://github.com/softrams/bulwark/issues/509)\n- **testing.md:** update prettier documentation ([48fbef4](https://github.com/softrams/bulwark/commit/48fbef44c60a7de22fca694ac3a2f0f85651031b)), closes [#509](https://github.com/softrams/bulwark/issues/509)\n\n### [6.3.1](https://github.com/softrams/bulwark/compare/v6.3.0...v6.3.1) (2020-12-12)\n\n### Others\n\n- **deps:** bump ini from 1.3.5 to 1.3.8 ([88ab044](https://github.com/softrams/bulwark/commit/88ab044a4a7a8ace0c8da6b7d7486cd4652e836c))\n\n## [6.3.0](https://github.com/softrams/bulwark/compare/v6.2.6...v6.3.0) (2020-12-10)\n\n### Features\n\n- **report component:** add numbering for list ([e6d3877](https://github.com/softrams/bulwark/commit/e6d3877547c1e2287f1fa4169034994566643a9b)), closes [#478](https://github.com/softrams/bulwark/issues/478) [#478](https://github.com/softrams/bulwark/issues/478)\n- **report component:** add numbers to pie chart ([4b12bfb](https://github.com/softrams/bulwark/commit/4b12bfbc56d48c05ef13ddb12861a180a06a8c12)), closes [#485](https://github.com/softrams/bulwark/issues/485)\n- **report component:** numbered list for reports ([75f6793](https://github.com/softrams/bulwark/commit/75f67934d66c67c1cd6cf38ab048373f11b432ad)), closes [#478](https://github.com/softrams/bulwark/issues/478) [#478](https://github.com/softrams/bulwark/issues/478)\n\n### Others\n\n- **deps:** bump @angular-devkit/build-angular in /frontend ([0df647a](https://github.com/softrams/bulwark/commit/0df647ab9bdb33ad004c96eb613c6a6e9765a495))\n- **deps:** bump @angular/cli from 11.0.2 to 11.0.3 in /frontend ([9f38774](https://github.com/softrams/bulwark/commit/9f38774412d2e383cd0539aca7ce13802fe8d1c6))\n- **deps:** bump core-js from 3.7.0 to 3.8.1 in /frontend ([64bafae](https://github.com/softrams/bulwark/commit/64bafae6afbc50da8eaa9cb4e33c79de2e19ec0c))\n- **deps:** bump ngx-markdown from 11.0.0 to 11.0.1 in /frontend ([72f0e0e](https://github.com/softrams/bulwark/commit/72f0e0e235f710998a55e5a9ddf30429780cb183))\n- **deps:** bump ts-node from 9.0.0 to 9.1.0 ([8b4064c](https://github.com/softrams/bulwark/commit/8b4064c31a53501dcb13597617b7a46212674af8))\n- **deps-dev:** bump @angular/language-service in /frontend ([655443d](https://github.com/softrams/bulwark/commit/655443db8fa41db52cbbcded63faef9f89f9e1e7))\n- **deps-dev:** bump @types/jest from 26.0.15 to 26.0.16 ([56374f8](https://github.com/softrams/bulwark/commit/56374f82f24b82e9237ce06d09e4c3c5faeba3a9))\n- **package.json:** add highlight.js v 10 to dev dependencies ([6db8153](https://github.com/softrams/bulwark/commit/6db815387426aa26a9fa5b49001c9f2f6d6f4973))\n- **package.json:** remove yargs package ([b17ec21](https://github.com/softrams/bulwark/commit/b17ec21f3637364c0d474e63204ab5cf89ccb076))\n\n### [6.2.6](https://github.com/softrams/bulwark/compare/v6.2.5...v6.2.6) (2020-12-02)\n\n### Bug Fixes\n\n- **vuln form component:** fixing the markdown icon ([a7eee64](https://github.com/softrams/bulwark/commit/a7eee640297fbd76ad98aae37b66afd9c8bf5a86)), closes [#468](https://github.com/softrams/bulwark/issues/468)\n\n### Code Refactoring\n\n- **file.utility.ts:** update max file size to 5m ([d3d1a44](https://github.com/softrams/bulwark/commit/d3d1a4408ef127317ac9daf95db1a91415a34f5d))\n\n### [6.2.5](https://github.com/softrams/bulwark/compare/v6.2.4...v6.2.5) (2020-12-01)\n\n### CI\n\n- **docker updates:** docker updates ([1bd423d](https://github.com/softrams/bulwark/commit/1bd423d9700dc0ec9b5c819351ac5b54379855cd))\n\n### Build System\n\n- **@angular/cli migrate:** update @angular/cli to version 10 ([93cd1e2](https://github.com/softrams/bulwark/commit/93cd1e2b8bd589a09df6db52d304e8b1c97de6e1))\n- **angular 11:** upgrade to Angular 11 ([d5e4ea9](https://github.com/softrams/bulwark/commit/d5e4ea9582d01e5d88827ede0d73a2558853e0e5))\n- **dockerfile:** cleanup docker tech debt and refactor for quicker builds ([cc7dcf6](https://github.com/softrams/bulwark/commit/cc7dcf642a92f4e7d0785b356d779c39ec9f80d7))\n\n### Others\n\n- **deps:** bump @angular-devkit/build-angular in /frontend ([d8e134e](https://github.com/softrams/bulwark/commit/d8e134ef70b18dae29d356be0a01449ece788c4c))\n- **deps:** bump @angular/cdk from 10.2.7 to 11.0.1 in /frontend ([6161aa8](https://github.com/softrams/bulwark/commit/6161aa881b3a160a7cf775a470d05f8bff9585ce))\n- **deps:** bump @angular/cli from 11.0.1 to 11.0.2 in /frontend ([32434b9](https://github.com/softrams/bulwark/commit/32434b9ac7128cca0a08380052a241d2b6bc1141))\n- **deps:** bump @ng-select/ng-select from 5.0.8 to 5.0.9 in /frontend ([500c86d](https://github.com/softrams/bulwark/commit/500c86d680f586538635cb4258cbb15733afa130))\n- **deps:** bump @types/express from 4.17.8 to 4.17.9 ([9cb8fef](https://github.com/softrams/bulwark/commit/9cb8fefbe69c4282cd63d8a98fb6681132291847))\n- **deps:** bump ngx-markdown from 10.1.1 to 11.0.0 in /frontend ([47ac561](https://github.com/softrams/bulwark/commit/47ac56140eebff27ba37d6089247304f68d4e68c))\n- **deps:** bump nodemailer from 6.4.15 to 6.4.16 ([d871df5](https://github.com/softrams/bulwark/commit/d871df5af925a136fe52728be754426777c5f504))\n- **deps:** bump primeicons from 4.0.0 to 4.1.0 in /frontend ([1db7906](https://github.com/softrams/bulwark/commit/1db7906a1b2217266bbd76c92d60374733f74132))\n- **deps:** bump puppeteer from 5.4.1 to 5.5.0 ([2205441](https://github.com/softrams/bulwark/commit/2205441f6de1038252a8321b3e86d87604643adb))\n- **deps:** bump yargs from 16.1.0 to 16.1.1 ([63bacea](https://github.com/softrams/bulwark/commit/63baceac6270d593a6e02aad44b064060d54bfae))\n- **deps-dev:** bump @angular/language-service in /frontend ([c20f6b1](https://github.com/softrams/bulwark/commit/c20f6b12d3c1a3c3b1cc43cb993b6d06595f3a3d))\n- **deps-dev:** bump @babel/core from 7.12.3 to 7.12.7 ([f9f61d4](https://github.com/softrams/bulwark/commit/f9f61d4373e71a0e706e37279ac823d35547cec7))\n- **deps-dev:** bump @babel/core from 7.12.7 to 7.12.9 ([2e314f1](https://github.com/softrams/bulwark/commit/2e314f12b61a07a326413f1d6f2beabeea97a7e2))\n- **deps-dev:** bump @babel/preset-env from 7.12.1 to 7.12.7 ([7cfeb91](https://github.com/softrams/bulwark/commit/7cfeb916419d56c1ac0b4b97cfedb4f5eb4aba73))\n- **deps-dev:** bump @babel/preset-typescript from 7.12.1 to 7.12.7 ([3090314](https://github.com/softrams/bulwark/commit/3090314675942148fb1d2332e1ea58051b6d412d))\n- **deps-dev:** bump @types/jasmine from 3.6.1 to 3.6.2 in /frontend ([920851f](https://github.com/softrams/bulwark/commit/920851f62269ee5acd4392b8027c6210236df95a))\n- **deps-dev:** bump @types/node from 14.14.6 to 14.14.8 in /frontend ([51e8005](https://github.com/softrams/bulwark/commit/51e8005fe67e94ac4847b2f9b153df22ba1e42c0))\n- **deps-dev:** bump @types/node from 14.14.8 to 14.14.9 in /frontend ([f0d7650](https://github.com/softrams/bulwark/commit/f0d7650421bd9acc40a91a088307cc0d4cf71f5c))\n- **deps-dev:** bump @types/node from 14.14.9 to 14.14.10 in /frontend ([7a835f7](https://github.com/softrams/bulwark/commit/7a835f7b0beff8612f07d2a0b71e34df72dabee0))\n- **deps-dev:** bump jasmine-spec-reporter in /frontend ([ed06f8e](https://github.com/softrams/bulwark/commit/ed06f8e14cfe0c9599a52545cdb0768501a3d11f))\n- **deps-dev:** bump typescript from 4.0.5 to 4.1.2 ([a5588f4](https://github.com/softrams/bulwark/commit/a5588f490a24b547146ccfd06b3fc95b30625532))\n- **package.json:** fix package.json conflict ([d3ace4e](https://github.com/softrams/bulwark/commit/d3ace4e50c0c4c5299753dc833790550fc4e894d))\n- **package.json:** update `npm run commit` script ([cdf1948](https://github.com/softrams/bulwark/commit/cdf1948dbb2b0df00dd02b050962809bd32f04a6))\n- **stale.yml:** add stale bot configuration ([8c86b2a](https://github.com/softrams/bulwark/commit/8c86b2ae2361b5e46c44f049e9c4403cac8687bb))\n\n### Code Refactoring\n\n- **report component:** fix plurality of day vs days ([861469b](https://github.com/softrams/bulwark/commit/861469b318feb59f5cdc28ce2c1efeb8ed31c665))\n- **report component:** grammar Changes ([f99ab88](https://github.com/softrams/bulwark/commit/f99ab887e5de54fffe1a54e5250e390ad9cb96d0))\n\n### Docs\n\n- **readme.md:** update README ([6441177](https://github.com/softrams/bulwark/commit/6441177020b0f866391ed77e8401420761c7bcac))\n\n### [6.2.4](https://github.com/softrams/bulwark/compare/v6.2.3...v6.2.4) (2020-11-11)\n\n### Bug Fixes\n\n- **docker-run-exec.ts:** update docker-compose.yml with new npm script ([cd81ba7](https://github.com/softrams/bulwark/commit/cd81ba74f5687f168dd25cce9a4147b9ed5b3214)), closes [#434](https://github.com/softrams/bulwark/issues/434)\n- **update docker tag to latest:** no longer using a pinned version in docker-compse.yml ([6505c1c](https://github.com/softrams/bulwark/commit/6505c1cff2e548fa47e4b51ab06f69d5240f9439)), closes [#434](https://github.com/softrams/bulwark/issues/434)\n\n### Others\n\n- **deps:** bump core-js from 3.6.5 to 3.7.0 in /frontend ([2ed9be8](https://github.com/softrams/bulwark/commit/2ed9be8ace1cd606525cceb18c204fd67fafd390))\n- **deps:** bump nodemailer from 6.4.14 to 6.4.15 ([c476958](https://github.com/softrams/bulwark/commit/c476958b1d749c8a26b1b595ae856e2bba084cd9))\n- **deps:** bump password-validator from 5.1.0 to 5.1.1 ([c97982c](https://github.com/softrams/bulwark/commit/c97982c1637961ff15c3b22775614ab858d6954d))\n- **deps-dev:** bump ts-jest from 26.4.3 to 26.4.4 ([efe384b](https://github.com/softrams/bulwark/commit/efe384bf12c611a07cc41fa7d10bf45324a0761e))\n\n### [6.2.3](https://github.com/softrams/bulwark/compare/v6.2.2...v6.2.3) (2020-11-05)\n\n### Docs\n\n- **docker-compose.yml:** update image version to 6.2.3 ([a90bddf](https://github.com/softrams/bulwark/commit/a90bddf2992e68f3317283cda77c5416b7c88120))\n- **testing.md contributing.md:** update testing and contribution documentation ([9b84c2b](https://github.com/softrams/bulwark/commit/9b84c2b6996c31ef7130f26afd8dc4814194b077))\n\n### Others\n\n- **deps:** bump @angular/animations from 10.2.0 to 10.2.1 in /frontend ([52e5888](https://github.com/softrams/bulwark/commit/52e588822b139ef3e536a01a167779bb4f7b53dc))\n- **deps:** bump @angular/animations from 10.2.1 to 10.2.2 in /frontend ([b0e69b8](https://github.com/softrams/bulwark/commit/b0e69b8921fbc4ec34593c9c3d7b48298f065fc0))\n- **deps:** bump @angular/cdk from 10.2.6 to 10.2.7 in /frontend ([59dd1f2](https://github.com/softrams/bulwark/commit/59dd1f28584577d744ef7c36086d772c97dfee5e))\n- **deps:** bump helmet from 4.1.1 to 4.2.0 ([ce2be8b](https://github.com/softrams/bulwark/commit/ce2be8bfd77ababcb800e0f91b49076f18cf1abe))\n- **deps:** bump typeorm from 0.2.28 to 0.2.29 ([191c98e](https://github.com/softrams/bulwark/commit/191c98e01d0cc34ff5deccb1ca8e29dc4f46fa05))\n- **deps-dev:** bump @angular/language-service in /frontend ([7c2854b](https://github.com/softrams/bulwark/commit/7c2854b34aaeb8ab40e22380e68a2f343bf80802))\n- **deps-dev:** bump @angular/language-service in /frontend ([771a992](https://github.com/softrams/bulwark/commit/771a992a413c50174c4ee094eddf1191007029a0))\n- **deps-dev:** bump @types/jasmine from 3.6.0 to 3.6.1 in /frontend ([d050ff0](https://github.com/softrams/bulwark/commit/d050ff0f1040061f229c8ea813bafaec261c6a95))\n- **deps-dev:** bump @types/node from 14.14.5 to 14.14.6 in /frontend ([cb4e421](https://github.com/softrams/bulwark/commit/cb4e42145217270a9d8b76809998ba6f0948a90f))\n- **deps-dev:** bump babel-jest from 26.6.1 to 26.6.2 ([684a36e](https://github.com/softrams/bulwark/commit/684a36edb9167027e5db8f0042a9dc43992154b9))\n- **deps-dev:** bump babel-jest from 26.6.2 to 26.6.3 ([05ff020](https://github.com/softrams/bulwark/commit/05ff020a0b0dcb4b8c2f28ab466a84f66c630272))\n- **deps-dev:** bump jest from 26.6.1 to 26.6.2 ([beed374](https://github.com/softrams/bulwark/commit/beed374f1510bd000b8c2366ac2606357a2ba82c))\n- **deps-dev:** bump jest from 26.6.2 to 26.6.3 ([d6be217](https://github.com/softrams/bulwark/commit/d6be2173339386df7db1d6009e291da66e976558))\n\n### CI\n\n- **codeql-analysis.yml:** update codeql-analysis.yml ([c46b22a](https://github.com/softrams/bulwark/commit/c46b22a14973226b872adeec4a3059b427ee19ee))\n- **dependabot.yml:** update dependabot.yml configuration ([3f2c01b](https://github.com/softrams/bulwark/commit/3f2c01b476141c80d4eb75d010447c007dcb6f10))\n\n### [6.2.2](https://github.com/softrams/bulwark/compare/v6.2.1...v6.2.2) (2020-11-02)\n\n### Docs\n\n- **security.md:** update security policy supported version statement. Update docker-compose.yml ver ([14f6b92](https://github.com/softrams/bulwark/commit/14f6b920af6d72841df273aca6c35e5a94ced819))\n\n### [6.2.1](https://github.com/softrams/bulwark/compare/v6.2.0...v6.2.1) (2020-10-28)\n\n### CI\n\n- **docker-compose.yml:** update docker container version to run Bulwark 6.2.0 ([8618557](https://github.com/softrams/bulwark/commit/86185571bce6adf51db362f2a74960cd531acc44))\n\n## [6.2.0](https://github.com/softrams/bulwark/compare/v6.1.0...v6.2.0) (2020-10-28)\n\n### Features\n\n- **asset-form:** use Prime NG for inputs, buttons and password form components ([e8f8e46](https://github.com/softrams/bulwark/commit/e8f8e46439b505e2f6a0a6656f98331dff20470a)), closes [#369](https://github.com/softrams/bulwark/issues/369)\n- **report component:** added pie chart and radar chart to report ([d955720](https://github.com/softrams/bulwark/commit/d955720db93482dd3f12f34a37a77f4f04005776)), closes [#410](https://github.com/softrams/bulwark/issues/410)\n- **report component:** update report structure ([ec5d06c](https://github.com/softrams/bulwark/commit/ec5d06cc67b8471d829bc64022243bbf2fd13413))\n- **user-profile:** use Prime NG for inputs and cards ([5623cb5](https://github.com/softrams/bulwark/commit/5623cb5f5b65c50037d5b8aeb0e963ff51bd4bca)), closes [#377](https://github.com/softrams/bulwark/issues/377)\n\n### Others\n\n- **deps:** bump @angular/cdk from 10.2.5 to 10.2.6 in /frontend ([3a00265](https://github.com/softrams/bulwark/commit/3a00265dcbf215591949d9ee404e6635141ff4fd))\n- **deps:** bump puppeteer from 5.3.1 to 5.4.0 ([a92cdc5](https://github.com/softrams/bulwark/commit/a92cdc52f35e87a6a788374b59a99af871eb3078))\n- **deps:** bump puppeteer from 5.4.0 to 5.4.1 ([4f83bc8](https://github.com/softrams/bulwark/commit/4f83bc81fbaec0563768c8f0008b3cb50ff5f694))\n- **deps-dev:** bump @types/jasmine from 3.5.14 to 3.6.0 in /frontend ([f596d72](https://github.com/softrams/bulwark/commit/f596d72f615f80e1c146798372d40da9664a9bd3))\n- **deps-dev:** bump @types/node from 14.14.2 to 14.14.3 in /frontend ([1c95507](https://github.com/softrams/bulwark/commit/1c95507b506bfe423cbec697dde0231c1971f9ec))\n- **deps-dev:** bump @types/node from 14.14.3 to 14.14.5 in /frontend ([3c9d0d7](https://github.com/softrams/bulwark/commit/3c9d0d732a2f4f02814efff709888d6c5a1bc95f))\n- **deps-dev:** bump babel-jest from 26.6.0 to 26.6.1 ([f5325f8](https://github.com/softrams/bulwark/commit/f5325f81f03070e5ffb0f5531fabfb66c106b8af))\n- **deps-dev:** bump jest from 26.6.0 to 26.6.1 ([53f64ab](https://github.com/softrams/bulwark/commit/53f64ab91e7db177afb8126471b33ca684b0033d))\n- **deps-dev:** bump ts-jest from 26.4.1 to 26.4.2 ([a2be373](https://github.com/softrams/bulwark/commit/a2be37332696f9c112a7b97821f4e2f93836cc8d))\n- **deps-dev:** bump ts-jest from 26.4.2 to 26.4.3 ([29d1404](https://github.com/softrams/bulwark/commit/29d14044d7ac0bf888f97f06edd1b00d567afd6b))\n- **deps-dev:** bump typescript from 4.0.3 to 4.0.5 ([b16b187](https://github.com/softrams/bulwark/commit/b16b187e53d346b8917de8addae9c02ad01243af))\n- **merge dev into branch:** merge develop into this branch ([9f7b32d](https://github.com/softrams/bulwark/commit/9f7b32dd635f47ed6f3834ebd8bea43bbdcbbe25))\n- **package.json:** updated package.json with additional contributor name ([20d8b47](https://github.com/softrams/bulwark/commit/20d8b478bc780786c8dfb6252422337f6123d737))\n\n## [6.1.0](https://github.com/softrams/bulwark/compare/v6.0.6...v6.1.0) (2020-10-23)\n\n### Features\n\n- email validate with prime ng ([b40f588](https://github.com/softrams/bulwark/commit/b40f588f36435f4a0828c55c76cd042623dd867e))\n- forgot password with prime ng ([36e1d10](https://github.com/softrams/bulwark/commit/36e1d10ce841748e66cbc61e79e0daf2f173e61c))\n- login with prime ng ([eca4924](https://github.com/softrams/bulwark/commit/eca49249a8d933671f6afd6d67276cc53cd34b30))\n- register with prime ng ([2688138](https://github.com/softrams/bulwark/commit/2688138634dd64d5282c22e24ea1c0819a082546))\n- reset password with prime ng ([b1f082c](https://github.com/softrams/bulwark/commit/b1f082cc41012d8a52736689e4401c28e78cbfbe))\n- settings with prime ng ([a830258](https://github.com/softrams/bulwark/commit/a830258a79621b7ec70ab30c908ec9c8646c3e00))\n\n### Bug Fixes\n\n- button scss side effect ([4e0cf29](https://github.com/softrams/bulwark/commit/4e0cf298f3bc6ff43bbb72d884d21f1e0206f25e))\n\n### Others\n\n- **deps:** bump @angular-devkit/build-angular in /frontend ([535777d](https://github.com/softrams/bulwark/commit/535777de4f6c40c17a1e321a7f580130803f69f1))\n- **deps:** bump @angular/animations from 10.1.6 to 10.2.0 in /frontend ([002a996](https://github.com/softrams/bulwark/commit/002a996996f7ca00f0cf50f63fce6c6e768006b7))\n- **deps:** bump @angular/cli from 10.1.7 to 10.2.0 in /frontend ([dc5a468](https://github.com/softrams/bulwark/commit/dc5a4688a3951d7d68fc350c0f51de54fa794634))\n- **deps-dev:** bump @angular/language-service in /frontend ([22879fb](https://github.com/softrams/bulwark/commit/22879fb82a811b539ca6be26bee879de673fba5f))\n- **deps-dev:** bump @babel/core from 7.12.1 to 7.12.3 ([9adb353](https://github.com/softrams/bulwark/commit/9adb3536fbd74dcf36b79683f0f13b1376ce6607))\n- **deps-dev:** bump @types/jest from 26.0.14 to 26.0.15 ([0e3e7f0](https://github.com/softrams/bulwark/commit/0e3e7f006c4ef845ee2b252458a4cf106210653f))\n- **deps-dev:** bump @types/node from 14.11.10 to 14.14.0 in /frontend ([86f9969](https://github.com/softrams/bulwark/commit/86f99697d9e72506162a9567976d2a1d91a8c1c6))\n- **deps-dev:** bump @types/node from 14.11.8 to 14.11.10 in /frontend ([cb2f61c](https://github.com/softrams/bulwark/commit/cb2f61cd2e1e7ba2914c661596583182e923db3c))\n- **deps-dev:** bump @types/node from 14.14.0 to 14.14.2 in /frontend ([84ae92c](https://github.com/softrams/bulwark/commit/84ae92c28921d417b2fddb735a830b6bb5d0c33f))\n- **deps-dev:** bump babel-jest from 26.5.2 to 26.6.0 ([022c89d](https://github.com/softrams/bulwark/commit/022c89de15c2d526e33cf60b4fc1ee14789aa094))\n- **deps-dev:** bump jest from 26.5.3 to 26.6.0 ([81aea5a](https://github.com/softrams/bulwark/commit/81aea5a5334f77e46a533ab3a7777a51450bdc2c))\n\n### [6.0.6](https://github.com/softrams/bulwark/compare/v6.0.5...v6.0.6) (2020-10-21)\n\n### Bug Fixes\n\n- **vuln-form component:** fixed missing delete screenshot icon ([60be8a9](https://github.com/softrams/bulwark/commit/60be8a9f58fa79885384f17d459121dd41c058fa))\n\n### [6.0.5](https://github.com/softrams/bulwark/compare/v6.0.4...v6.0.5) (2020-10-16)\n\n### Others\n\n- **deps:** bump @angular-devkit/build-angular in /frontend ([3ce0d49](https://github.com/softrams/bulwark/commit/3ce0d49bc84ceaee825b22e917e7328e3c8bb8f0))\n- **deps:** bump @angular/animations from 10.1.5 to 10.1.6 in /frontend ([1eea3d3](https://github.com/softrams/bulwark/commit/1eea3d375b816d6bcc69f3a8285124758af2fd47))\n- **deps:** bump @angular/cdk from 10.2.4 to 10.2.5 in /frontend ([ffeacb9](https://github.com/softrams/bulwark/commit/ffeacb910c2fd7fb281cd9798221c923177eca93))\n- **deps:** bump @angular/cli from 10.1.6 to 10.1.7 in /frontend ([8af8735](https://github.com/softrams/bulwark/commit/8af87353f97799dfbe91aeec013375212e5953f7))\n- **deps:** bump nodemailer from 6.4.13 to 6.4.14 ([0f0201f](https://github.com/softrams/bulwark/commit/0f0201f491ca69fddad912b492e3a727911ec41a))\n- **deps:** bump yargs from 16.0.3 to 16.1.0 ([541bbf3](https://github.com/softrams/bulwark/commit/541bbf32b3a02df6655901857d1502070fe40585))\n- **deps-dev:** bump @angular/language-service in /frontend ([94914f5](https://github.com/softrams/bulwark/commit/94914f52c55d69dc48144c0ce5105687e08788b6))\n- **deps-dev:** bump @babel/core from 7.11.6 to 7.12.0 ([0538836](https://github.com/softrams/bulwark/commit/0538836658c07c5854f9d87fe77de61eaac273c8))\n- **deps-dev:** bump @babel/core from 7.12.0 to 7.12.1 ([55c6f86](https://github.com/softrams/bulwark/commit/55c6f86613aec80204ce1f2b1d01b1bef4943503))\n- **deps-dev:** bump @babel/preset-env from 7.11.5 to 7.12.0 ([4a3c1c6](https://github.com/softrams/bulwark/commit/4a3c1c61ff6cca5118d289994196b0519a09d07f))\n- **deps-dev:** bump @babel/preset-env from 7.12.0 to 7.12.1 ([def2233](https://github.com/softrams/bulwark/commit/def2233f2c8c76e7174f68cebc9a1a0c1e621b91))\n- **deps-dev:** bump @babel/preset-typescript from 7.10.4 to 7.12.0 ([2a2861b](https://github.com/softrams/bulwark/commit/2a2861b6407b5df398481eead368578ee5edf3b0))\n- **deps-dev:** bump @babel/preset-typescript from 7.12.0 to 7.12.1 ([a3ed773](https://github.com/softrams/bulwark/commit/a3ed7738028ff4ea26b52a1762291c649ec24a57))\n\n### [6.0.4](https://github.com/softrams/bulwark/compare/v6.0.3...v6.0.4) (2020-10-14)\n\n### Docs\n\n- **readme.md:** updated README.md ([0966f9f](https://github.com/softrams/bulwark/commit/0966f9f6b0a35fb6397b083dda8ed222802136ed))\n\n### [6.0.3](https://github.com/softrams/bulwark/compare/v6.0.2...v6.0.3) (2020-10-13)\n\n### Others\n\n- **release:** 6.0.1 ([0fbf5c2](https://github.com/softrams/bulwark/commit/0fbf5c2b77e3cb045350d9eca757e26fbbc48377))\n- **release:** 6.0.3 ([4a186ba](https://github.com/softrams/bulwark/commit/4a186ba061cdf00480d0cb9c7e6bb51b977ed47e))\n- **release:** 6.0.3 ([b296c2b](https://github.com/softrams/bulwark/commit/b296c2b7529b8047cda3a5bbe14a74ceca996986))\n- **release:** 6.0.5 ([6f50575](https://github.com/softrams/bulwark/commit/6f505752302ed7a539fc7b8cabe4807e4f21e662))\n\n### Docs\n\n- **updated readme:** updated readme ([d9b19b5](https://github.com/softrams/bulwark/commit/d9b19b508fa705e94a5cd6cd26625ae7673c8cee))\n\n### [6.0.1](https://github.com/softrams/bulwark/compare/v6.0.2...v6.0.1) (2020-10-13)\n\n### Others\n\n- **release:** 6.0.3 ([b296c2b](https://github.com/softrams/bulwark/commit/b296c2b7529b8047cda3a5bbe14a74ceca996986))\n\n## [6.0.0](https://github.com/softrams/bulwark/compare/v5.0.0...v6.0.0) (2020-10-13)\n\n### ⚠ BREAKING CHANGES\n\n- **add user entity column for newemail:** added new newEmail column to the user entity\n- **organization and file table:** Updates to the schema. Removed relationship between Organization and File.\n\n### Features\n\n- **add user entity column for newemail:** update user entity with newEmail column ([4aef2fe](https://github.com/softrams/bulwark/commit/4aef2febad142a514529071772c35b7f8fd8c1e2)), closes [#49](https://github.com/softrams/bulwark/issues/49)\n- **assessments:** add custom table filter matchMode to filter Testers array ([0e555b4](https://github.com/softrams/bulwark/commit/0e555b4041657c1abf1d919298cb3beef8259ca2))\n- **create initial seed user:** update initial startup to create default user ([61ddc2a](https://github.com/softrams/bulwark/commit/61ddc2a07b002e5dc53bcf812c126dbe874e707d)), closes [#322](https://github.com/softrams/bulwark/issues/322)\n- **dashboard, organization, vulnerability summary tables:** updated column name and headers ([bd4feac](https://github.com/softrams/bulwark/commit/bd4feacd596bdd78c61eeef0d2968a140e5c7d5f)), closes [#321](https://github.com/softrams/bulwark/issues/321)\n- **docker:** updating Bulwark to run from docker ([b983c52](https://github.com/softrams/bulwark/commit/b983c52db76031eacbcd452b58467d1947fd5935)), closes [#245](https://github.com/softrams/bulwark/issues/245)\n- **dockerfile:** dockerize Bulwark ([fe07262](https://github.com/softrams/bulwark/commit/fe07262fc9f1a33d3f05ec0b801c8b83e0abf4f8))\n- **dockerfile:** dockerize Bulwark ([8ac6fdd](https://github.com/softrams/bulwark/commit/8ac6fdd3baa86cbb35425c193eb40c5861079ea7))\n- **email-validate component:** created the email-validate.component.ts file ([5c1dd87](https://github.com/softrams/bulwark/commit/5c1dd87b602de9f3fd3a97ecd1db8cbf75cd6378)), closes [#49](https://github.com/softrams/bulwark/issues/49)\n- **new api for revoke and validate email request:** new api's with unit tests ([152cbf1](https://github.com/softrams/bulwark/commit/152cbf15961733ee131a36f20e361588561a723c)), closes [#49](https://github.com/softrams/bulwark/issues/49)\n- **organization and file table:** removed relation between Org and File ([05d42a0](https://github.com/softrams/bulwark/commit/05d42a0efb2cdf899e94d11b576450a282af35f1)), closes [#321](https://github.com/softrams/bulwark/issues/321)\n- **user controller:** created API for email update ([bcbab8f](https://github.com/softrams/bulwark/commit/bcbab8f36ddde18cfc0024ad571a5dad2dffcb40)), closes [#49](https://github.com/softrams/bulwark/issues/49)\n- docker ([62ee1ad](https://github.com/softrams/bulwark/commit/62ee1ad858dbb0f202976b6a4b37fc00f5ec141d))\n- docker ([afd7274](https://github.com/softrams/bulwark/commit/afd72741f4e4e3024d2ed750027e30ce68b8c0e3))\n- docker ([a590ecf](https://github.com/softrams/bulwark/commit/a590ecfefddeacbeb894f7c4b7524ab52191a094))\n- dockerize Bulwark ([607db0b](https://github.com/softrams/bulwark/commit/607db0b722bf7597a139cb187f1f96bf32ef6b54))\n\n### Bug Fixes\n\n- **removed migration directory files:** removed migrations ([783ce03](https://github.com/softrams/bulwark/commit/783ce03aa30163fa8828e7b17a36151621c2b3e7))\n\n### Others\n\n- **deps:** bump @angular-devkit/build-angular in /frontend ([5b2edba](https://github.com/softrams/bulwark/commit/5b2edba0de6ac9d6082e759b0e5057790ac9c17f))\n- **deps:** bump @angular-devkit/build-angular in /frontend ([af92bac](https://github.com/softrams/bulwark/commit/af92bac398fcd8e68f109b1c360cd0442cc3ce3f))\n- **deps:** bump @angular/animations from 10.1.4 to 10.1.5 in /frontend ([6f57b2e](https://github.com/softrams/bulwark/commit/6f57b2e9a2871b76f50278c34cfa633b12442740))\n- **deps:** bump @angular/cdk from 10.2.3 to 10.2.4 in /frontend ([40e75d9](https://github.com/softrams/bulwark/commit/40e75d9d2efd88e9acc0f6ff43eba290cd671d2c))\n- **deps:** bump @angular/cli from 10.1.4 to 10.1.5 in /frontend ([58ff160](https://github.com/softrams/bulwark/commit/58ff160bb3fe70e3a7a68567fd5d6655872b9ef6))\n- **deps:** bump @angular/cli from 10.1.5 to 10.1.6 in /frontend ([e573f3d](https://github.com/softrams/bulwark/commit/e573f3de8b22b4fcb9eecfd69e3466d03a009035))\n- **deps:** bump @ng-select/ng-select from 5.0.3 to 5.0.4 in /frontend ([dbcc884](https://github.com/softrams/bulwark/commit/dbcc884a8886591955f992153cd1edee8e8b92b3))\n- **deps:** bump @ng-select/ng-select from 5.0.4 to 5.0.5 in /frontend ([05c1a8f](https://github.com/softrams/bulwark/commit/05c1a8f66a02878944410b8bc44f630d2f664a15))\n- **deps:** bump @ng-select/ng-select from 5.0.5 to 5.0.6 in /frontend ([c73d17f](https://github.com/softrams/bulwark/commit/c73d17f609f5c202e56b5de81ce689115374a40f))\n- **deps:** bump @ng-select/ng-select from 5.0.6 to 5.0.7 in /frontend ([9731080](https://github.com/softrams/bulwark/commit/9731080df713b0867c2da9689442a0b57156b486))\n- **deps:** bump @ng-select/ng-select from 5.0.7 to 5.0.8 in /frontend ([47cb485](https://github.com/softrams/bulwark/commit/47cb485743cc77845a91cd934f61b25c0a53dd30))\n- **deps:** bump jira-client from 6.21.0 to 6.21.1 ([25a5e8b](https://github.com/softrams/bulwark/commit/25a5e8b038dca63957e06ad8c50bbb0a360ac73e))\n- **deps:** bump nodemailer from 6.4.12 to 6.4.13 ([6b389bd](https://github.com/softrams/bulwark/commit/6b389bd4d260c6f10e555957b73b262ac4447fc6))\n- **deps:** bump primeng from 10.0.2 to 10.0.3 in /frontend ([015f652](https://github.com/softrams/bulwark/commit/015f652c6a4fa8d41bcf37517c1448e6f6f9d853))\n- **deps:** bump tslib from 2.0.1 to 2.0.2 in /frontend ([17a8e32](https://github.com/softrams/bulwark/commit/17a8e32676bfc9167742bf058b02912af0d6d43b))\n- **deps:** bump tslib from 2.0.2 to 2.0.3 in /frontend ([9ef5373](https://github.com/softrams/bulwark/commit/9ef53738e894395f5d13ff63b7589242c3f1cde7))\n- **deps:** bump uuid from 8.3.0 to 8.3.1 ([9bbdd11](https://github.com/softrams/bulwark/commit/9bbdd11005095e4b43e2eda174461348089b54d2))\n- **deps-dev:** bump @angular/language-service in /frontend ([9c3a1be](https://github.com/softrams/bulwark/commit/9c3a1be35226761e7472daa7e20f06bd5595da32))\n- **deps-dev:** bump @types/node from 14.11.2 to 14.11.5 in /frontend ([ba63886](https://github.com/softrams/bulwark/commit/ba638868ce66ee1e7fb9e603dd54e6a497bf78f2))\n- **deps-dev:** bump @types/node from 14.11.5 to 14.11.7 in /frontend ([0034d3f](https://github.com/softrams/bulwark/commit/0034d3ffd5ce95bf5d28eef68972220fe4ccd6f6))\n- **deps-dev:** bump @types/node from 14.11.7 to 14.11.8 in /frontend ([534b6c8](https://github.com/softrams/bulwark/commit/534b6c8e54792b56d7f0c3182fd84b4460ac204f))\n- **deps-dev:** bump babel-jest from 26.3.0 to 26.5.2 ([2c0e335](https://github.com/softrams/bulwark/commit/2c0e335612b21dba273d71c0d8251ab33a9298f7))\n- **deps-dev:** bump jest from 26.4.2 to 26.5.0 ([fab2537](https://github.com/softrams/bulwark/commit/fab2537628b3ff3a2d0e4743b7db010aef14f99c))\n- **deps-dev:** bump jest from 26.5.0 to 26.5.2 ([d44551b](https://github.com/softrams/bulwark/commit/d44551bc329d9f500525fa5b101b0ebe98d37ff3))\n- **deps-dev:** bump jest from 26.5.2 to 26.5.3 ([4e31812](https://github.com/softrams/bulwark/commit/4e31812201e88ea8c4a11528d628f3756bc5b1eb))\n\n### Code Refactoring\n\n- **user.ts:** updated user column uuid to always be nullable ([79cd34e](https://github.com/softrams/bulwark/commit/79cd34efa7c15045f6e6f70a212919da1f19d082))\n\n## [5.0.0](https://github.com/softrams/bulwark/compare/v4.0.7...v5.0.0) (2020-10-02)\n\n### ⚠ BREAKING CHANGES\n\n- **settings component:** Added new table for app configurations. updated API's referencing the email\n  service to check for email configuration from database. Removed env vars for email configuration\n  and company name.\n\n### Features\n\n- **assessment/asset summary table:** added filtering to tables ([66b2420](https://github.com/softrams/bulwark/commit/66b242008725c826d094a78a67ea9dae270935e7)), closes [#302](https://github.com/softrams/bulwark/issues/302)\n- **initial commit for primeng:** initial commit for PrimeNG ([988dc2a](https://github.com/softrams/bulwark/commit/988dc2a856a1851b138aadb59d5aaaf6e8d8e6e8)), closes [#302](https://github.com/softrams/bulwark/issues/302)\n- **initial commit of asset and assessment primeng refactor:** initial commit of asset and assessent ([493a3d2](https://github.com/softrams/bulwark/commit/493a3d2c660363bbda6a28650b4e6766d57e6fd1)), closes [#302](https://github.com/softrams/bulwark/issues/302)\n- **primeng refactor:** refactored existing tables to use primeng ([0adc220](https://github.com/softrams/bulwark/commit/0adc220196e06c93b052f327a2163085caa5c8f3)), closes [#302](https://github.com/softrams/bulwark/issues/302)\n- **removed tester multiselect:** temporarily replaced Tester multiselect with sort ([aa332da](https://github.com/softrams/bulwark/commit/aa332dad1d258370b48e03c8a280b7eafc555afd)), closes [#302](https://github.com/softrams/bulwark/issues/302)\n- **settings component:** moved email configuration from env var to application ([778670e](https://github.com/softrams/bulwark/commit/778670e45a75a7c11bdacfd8674a3fa8feb60183)), closes [#260](https://github.com/softrams/bulwark/issues/260)\n- **update primeng table and loading spinner:** updated primeng tables. Refactored loading spinner ([c169e99](https://github.com/softrams/bulwark/commit/c169e999708f49687fa0e9fca6d457f8b790fc00)), closes [#302](https://github.com/softrams/bulwark/issues/302)\n\n### Others\n\n- **ci:** added support for issues ([dadee4e](https://github.com/softrams/bulwark/commit/dadee4e66d5957ac3c8d7e6fe92841ac6ae49eae))\n- **deps:** bump @angular-devkit/build-angular in /frontend ([3189224](https://github.com/softrams/bulwark/commit/3189224a5b21f2c26e1d97ab075379179d0465dd))\n- **deps:** bump @angular-devkit/build-angular in /frontend ([ec14201](https://github.com/softrams/bulwark/commit/ec1420139a6d4d60dec10a2282697dd2ac710e68))\n- **deps:** bump @angular-devkit/build-angular in /frontend ([77ac88c](https://github.com/softrams/bulwark/commit/77ac88c599ccdb630ea4f85c9f9402510754f867))\n- **deps:** bump @angular/animations in /frontend ([b74a9c9](https://github.com/softrams/bulwark/commit/b74a9c9a20cf9b9c82efbea8d509534b09a30a13))\n- **deps:** bump @angular/cdk from 10.2.2 to 10.2.3 in /frontend ([3d0e38d](https://github.com/softrams/bulwark/commit/3d0e38d1f2b572834427e411e2eaecd1fdcc0eac))\n- **deps:** bump @angular/cli from 10.1.1 to 10.1.2 in /frontend ([45cfe34](https://github.com/softrams/bulwark/commit/45cfe34935379f7011829f85bf2eb622b978a6ea))\n- **deps:** bump @angular/cli from 10.1.2 to 10.1.3 in /frontend ([1369d3c](https://github.com/softrams/bulwark/commit/1369d3c8e10ce474f3fa302e434d72663456c438))\n- **deps:** bump @angular/cli from 10.1.3 to 10.1.4 in /frontend ([5a0536e](https://github.com/softrams/bulwark/commit/5a0536e1fa2f7df2da94ee66ae3b5faa2a38465c))\n- **deps:** bump @fortawesome/fontawesome-svg-core in /frontend ([1a54c95](https://github.com/softrams/bulwark/commit/1a54c95e1c0ccc157441e831bf00f9bbc541a253))\n- **deps:** bump @fortawesome/free-brands-svg-icons in /frontend ([830a15c](https://github.com/softrams/bulwark/commit/830a15c0b1a38dfd43198d61475aa558f6e35608))\n- **deps:** bump @fortawesome/free-solid-svg-icons in /frontend ([39bd027](https://github.com/softrams/bulwark/commit/39bd027aff2457bbd3ef695151a14ffe9ca63d04))\n- **deps:** bump @ng-select/ng-select from 5.0.1 to 5.0.3 in /frontend ([797caab](https://github.com/softrams/bulwark/commit/797caab204417f23519b3b4826f4405fe74b9358))\n- **deps:** bump nodemailer from 6.4.11 to 6.4.12 ([a690271](https://github.com/softrams/bulwark/commit/a690271d38061c1d5b2826e10a2c3b5c59b7d96f))\n- **deps:** bump primeng from 10.0.0 to 10.0.2 in /frontend ([a9e5b30](https://github.com/softrams/bulwark/commit/a9e5b30b391602a7ab3a2d044881c20c6e59f8e0))\n- **deps:** bump puppeteer from 5.3.0 to 5.3.1 ([6e77d99](https://github.com/softrams/bulwark/commit/6e77d9948489da1fa7a8a16b24025f6945655d34))\n- **deps:** bump typeorm from 0.2.26 to 0.2.28 ([e723343](https://github.com/softrams/bulwark/commit/e7233435811adbc5fc82ea351c10e22c8fa69e75))\n- **deps-dev:** bump @angular/language-service in /frontend ([7502ade](https://github.com/softrams/bulwark/commit/7502ade9c6e5d1da72a40842236a8c48fd5a4b65))\n- **deps-dev:** bump @angular/language-service in /frontend ([f20a01b](https://github.com/softrams/bulwark/commit/f20a01b67d0b6bc1323c2af3a7a20455afb1e4f8))\n- **deps-dev:** bump @angular/language-service in /frontend ([856bd1c](https://github.com/softrams/bulwark/commit/856bd1c0dca011ca58c69d0e9682da83532a483e))\n- **deps-dev:** bump @types/jest from 26.0.13 to 26.0.14 ([bcd296a](https://github.com/softrams/bulwark/commit/bcd296afb3ae320cbbfb7fdf5ebb9950844d03f5))\n- **deps-dev:** bump @types/node from 14.10.1 to 14.10.2 in /frontend ([0384b4e](https://github.com/softrams/bulwark/commit/0384b4efd1a735b6595997742ff90c4bb026eefb))\n- **deps-dev:** bump @types/node from 14.10.2 to 14.10.3 in /frontend ([d2bbdea](https://github.com/softrams/bulwark/commit/d2bbdea6d567a1d4da2f6f4572f791f829f1d356))\n- **deps-dev:** bump @types/node from 14.10.3 to 14.11.1 in /frontend ([1f00fbf](https://github.com/softrams/bulwark/commit/1f00fbfd82e4205f76458f89e3c73e2876d824ad))\n- **deps-dev:** bump @types/node from 14.11.1 to 14.11.2 in /frontend ([eadd88c](https://github.com/softrams/bulwark/commit/eadd88c66bd3a1842aa25b9f1a393faa295d47a6))\n- **deps-dev:** bump codelyzer from 6.0.0 to 6.0.1 in /frontend ([ee12133](https://github.com/softrams/bulwark/commit/ee121334659fd3ef46db880df9581ad22affafb3))\n- **deps-dev:** bump jasmine-spec-reporter in /frontend ([002fb85](https://github.com/softrams/bulwark/commit/002fb85f7670b166df47418d4ed2db25152dd64e))\n- **deps-dev:** bump karma from 5.2.2 to 5.2.3 in /frontend ([9380ed0](https://github.com/softrams/bulwark/commit/9380ed0e4b6b6bf78b7c6cd16866ab5c743c3793))\n- **deps-dev:** bump ts-jest from 26.3.0 to 26.4.0 ([1da83db](https://github.com/softrams/bulwark/commit/1da83db85b49bf251351019383c39f1e8fda3449))\n- **deps-dev:** bump ts-jest from 26.4.0 to 26.4.1 ([6304980](https://github.com/softrams/bulwark/commit/6304980cf4390b09ee4d88c4d2c0551ac4b8f7fe))\n- **deps-dev:** bump typescript from 4.0.2 to 4.0.3 ([e76deae](https://github.com/softrams/bulwark/commit/e76deaeead62bfd4d3441fbc2c66f8c33cb80c7e))\n- **fixing merge conflicts:** fixed merge conflicts ([ff7e2af](https://github.com/softrams/bulwark/commit/ff7e2af483ab1a6872e002d0aa3b8f98f4f3066f)), closes [#302](https://github.com/softrams/bulwark/issues/302)\n\n### CI\n\n- **node:** added ability to create env file before running tests ([0dc9757](https://github.com/softrams/bulwark/commit/0dc9757c66ff1a9dc2fec8101da1bd5ae4fe8b2b))\n- **node:** added support for backend tests to run on ci ([e31c488](https://github.com/softrams/bulwark/commit/e31c488ee23b21399792d0f0b81a7bb4187433ed))\n- **node:** added support to ci to only run if the backend files have changed ([8071cb3](https://github.com/softrams/bulwark/commit/8071cb3016c80f31ba0f14aece10093c891d8622))\n- **node:** removed double qutoes ([20d1586](https://github.com/softrams/bulwark/commit/20d15866de030cef7c93c6ebffb51ebbb4c09c3e))\n- **node:** removed path filter and set tests to run each time ([76d01f3](https://github.com/softrams/bulwark/commit/76d01f332fc02266ae3954f954fca40588e5f2e0))\n- **user.controller:** updated user import for failed tested ([b56a7fa](https://github.com/softrams/bulwark/commit/b56a7fae4b080da1ff7528c2235d7a829ea9e9f6))\n\n### [4.0.7](https://github.com/softrams/bulwark/compare/v4.0.6...v4.0.7) (2020-10-01)\n\n### [4.0.6](https://github.com/softrams/bulwark/compare/v4.0.5...v4.0.6) (2020-09-14)\n\n### Others\n\n- **deps:** bump @angular-devkit/build-angular in /frontend ([0fa83d3](https://github.com/softrams/bulwark/commit/0fa83d3ca411e2537fcca9de3445a8b35db942dd))\n- **deps:** bump @angular/cli from 10.1.0 to 10.1.1 in /frontend ([edde37f](https://github.com/softrams/bulwark/commit/edde37f79999ea0c35b51629321edd214e109241))\n- **deps:** bump helmet from 4.1.0 to 4.1.1 ([8d641e0](https://github.com/softrams/bulwark/commit/8d641e05c080051186cd23ac28d0a1b08039b675))\n- **deps:** bump jira-client from 6.20.0 to 6.21.0 ([1b22837](https://github.com/softrams/bulwark/commit/1b22837983852429040029ceb9f98c14e5794e02))\n- **deps:** bump node-fetch from 2.6.0 to 2.6.1 ([0875c69](https://github.com/softrams/bulwark/commit/0875c69e2ab4173766642086dd44e5df0cfc2634))\n- **deps:** bump puppeteer from 5.2.1 to 5.3.0 ([52431ad](https://github.com/softrams/bulwark/commit/52431ad6b33bcd434e424d67f1473c47600a7765))\n- **deps:** bump rxjs from 6.6.2 to 6.6.3 in /frontend ([faf866d](https://github.com/softrams/bulwark/commit/faf866dd8d481c8e47bdf4355ceccba8475afcc8))\n- **deps:** bump typeorm from 0.2.25 to 0.2.26 ([0a445e1](https://github.com/softrams/bulwark/commit/0a445e1655015c723df253c5f77841a962beb366))\n- **deps:** bump yargs from 15.4.1 to 16.0.0 ([6b53359](https://github.com/softrams/bulwark/commit/6b53359ba3072d8f804296bfa5f1ea651048ee99))\n- **deps:** bump yargs from 16.0.0 to 16.0.3 ([9a3ed79](https://github.com/softrams/bulwark/commit/9a3ed79ed8df3a6c971c6f5e2e890cebc7f95c31))\n- **deps-dev:** bump @angular/language-service in /frontend ([b2a2943](https://github.com/softrams/bulwark/commit/b2a2943533ce033392a244f1d63c15b316c059a4))\n- **deps-dev:** bump @babel/core from 7.11.5 to 7.11.6 ([c79fd88](https://github.com/softrams/bulwark/commit/c79fd8866710f5a5643d80ba2caf170e327d100a))\n- **deps-dev:** bump @commitlint/cli from 9.1.2 to 11.0.0 ([1abe52f](https://github.com/softrams/bulwark/commit/1abe52f9a59484885e50a273f84398a5df9e830e))\n- **deps-dev:** bump @commitlint/config-conventional ([6806a6d](https://github.com/softrams/bulwark/commit/6806a6d2248dd81c42752763895eefd94d921190))\n- **deps-dev:** bump @types/node from 14.10.0 to 14.10.1 in /frontend ([60e52fe](https://github.com/softrams/bulwark/commit/60e52feabab1fb8a0fc93f0c931a3e18f7f6f2c9))\n- **deps-dev:** bump @types/node from 14.6.3 to 14.6.4 in /frontend ([2f86132](https://github.com/softrams/bulwark/commit/2f861320b4a92a80b7869a4db75e8d0cfc9795ba))\n- **deps-dev:** bump @types/node from 14.6.4 to 14.10.0 in /frontend ([250777c](https://github.com/softrams/bulwark/commit/250777c9f126061edf24e3f315754d2e26051a3f))\n- **deps-dev:** bump husky from 4.2.5 to 4.3.0 ([ddaf553](https://github.com/softrams/bulwark/commit/ddaf553bd0883c4bc06cc0d8eb76e250affd8110))\n- **deps-dev:** bump karma from 5.2.1 to 5.2.2 in /frontend ([ab6e48b](https://github.com/softrams/bulwark/commit/ab6e48b4756964b971b8d609cdb6e25f16a3db33))\n\n### [4.0.5](https://github.com/softrams/bulwark/compare/v4.0.4...v4.0.5) (2020-09-03)\n\n### Bug Fixes\n\n- **ormconfig.js:** updated ormconfig so that it allows to safely run migrations ([cd52a9c](https://github.com/softrams/bulwark/commit/cd52a9cf8a8b06549f0e2e05f6e5931be10127a3)), closes [#262](https://github.com/softrams/bulwark/issues/262)\n- **report generation:** updated puppeteer report generation so that it outputs to the `temp` folder ([38f7bcf](https://github.com/softrams/bulwark/commit/38f7bcf4e2bb4d390a5df65de119af92d577de7a)), closes [#259](https://github.com/softrams/bulwark/issues/259)\n\n### Others\n\n- **deps:** bump @angular/cli from 10.0.8 to 10.1.0 in /frontend ([cc699f3](https://github.com/softrams/bulwark/commit/cc699f3b68e69bfc8249cb86609bd50c758529c0))\n- **deps:** bump @types/express from 4.17.7 to 4.17.8 ([8a5726b](https://github.com/softrams/bulwark/commit/8a5726ba6a2142e44a5a732dc7e9ff04424da595))\n- **deps-dev:** bump @angular/language-service in /frontend ([d3d9580](https://github.com/softrams/bulwark/commit/d3d9580267e46f9b897d1a52c4548ef37ce8b7fd))\n- **deps-dev:** bump @babel/core from 7.11.4 to 7.11.5 ([719c25c](https://github.com/softrams/bulwark/commit/719c25ced2d4361ece72bad730c27077e6531b23))\n- **deps-dev:** bump @babel/preset-env from 7.11.0 to 7.11.5 ([ce52573](https://github.com/softrams/bulwark/commit/ce525733276515de64e73994d3ff2bdf2d73d503))\n- **deps-dev:** bump @types/jest from 26.0.10 to 26.0.12 ([0fc4012](https://github.com/softrams/bulwark/commit/0fc401251a422d729af393a5df97a36cf24d8932))\n- **deps-dev:** bump @types/jest from 26.0.12 to 26.0.13 ([5d7e519](https://github.com/softrams/bulwark/commit/5d7e5193e2188ca7741a6d72c09d491c0f6b1171))\n- **deps-dev:** bump @types/node from 14.6.2 to 14.6.3 in /frontend ([c62143a](https://github.com/softrams/bulwark/commit/c62143a020afb9c9811aa8ec5dde05c49c48f0a2))\n- **deps-dev:** bump karma from 5.1.1 to 5.2.0 in /frontend ([f372b0f](https://github.com/softrams/bulwark/commit/f372b0f7eb5735268fa2f944fe7be55efb227554))\n- **deps-dev:** bump karma from 5.2.0 to 5.2.1 in /frontend ([6524c98](https://github.com/softrams/bulwark/commit/6524c98445c142f20fe04fc31d348d30d7ef85cf))\n\n### [4.0.4](https://github.com/softrams/bulwark/compare/v4.0.3...v4.0.4) (2020-08-31)\n\n### Bug Fixes\n\n- **add temp folder to src:** add `temp` folder to fix Jira screenshot attachments ([746336f](https://github.com/softrams/bulwark/commit/746336fda2d5fc9dfeee3e4ab4d569f251aec8ab)), closes [#248](https://github.com/softrams/bulwark/issues/248)\n\n### [4.0.3](https://github.com/softrams/bulwark/compare/v4.0.2...v4.0.3) (2020-08-31)\n\n### Bug Fixes\n\n- **column-mapper.utility.ts:** fixed typescript build break. made column mapper a string for In ([d2ef14c](https://github.com/softrams/bulwark/commit/d2ef14cb333e89807c8fa92dc1f89303f65b19ad))\n\n### Others\n\n- **deps:** bump @fortawesome/free-solid-svg-icons in /frontend ([10ee5fd](https://github.com/softrams/bulwark/commit/10ee5fdb0480463ac101d75c4045d784b1b90387))\n- **deps-dev:** bump @types/node from 14.6.0 to 14.6.1 in /frontend ([e31dfa0](https://github.com/softrams/bulwark/commit/e31dfa0458d349bc97e6822def324e568094cbb6))\n- **deps-dev:** bump @types/node from 14.6.1 to 14.6.2 in /frontend ([b428221](https://github.com/softrams/bulwark/commit/b42822197284d97455825a3d0af2b9489529987d))\n- **deps-dev:** bump typescript from 3.9.7 to 4.0.2 ([10a17a6](https://github.com/softrams/bulwark/commit/10a17a665f60c314bcd21732b18b6c2278c319b8))\n\n### [4.0.2](https://github.com/softrams/bulwark/compare/v4.0.1...v4.0.2) (2020-08-27)\n\n### Others\n\n- **deps:** bump @angular-devkit/build-angular in /frontend ([bae4ca9](https://github.com/softrams/bulwark/commit/bae4ca9dc29a26aee767519b0d2eda17a356a89b))\n- **deps:** bump @angular/cli from 10.0.7 to 10.0.8 in /frontend ([52dd36b](https://github.com/softrams/bulwark/commit/52dd36ba2c83056cdd6b4b936792ca2ce147ba4b))\n- **deps:** bump jira-client from 6.19.0 to 6.20.0 ([014bd86](https://github.com/softrams/bulwark/commit/014bd868c0a25bd2be62a21f8fd9ba98a344eea3))\n- **deps-dev:** bump @angular/language-service in /frontend ([09938ab](https://github.com/softrams/bulwark/commit/09938abfa8087936cc7b08abae8802020bde8453))\n- **deps-dev:** bump @angular/language-service in /frontend ([94fc0c9](https://github.com/softrams/bulwark/commit/94fc0c95183067e7ca7dd90e5ec8073050b1b186))\n- **deps-dev:** bump @types/jasmine from 3.5.13 to 3.5.14 in /frontend ([5b9aa1d](https://github.com/softrams/bulwark/commit/5b9aa1d9f7a455b3d5d4fe6e7a97dfccf5a08565))\n- **deps-dev:** bump cz-conventional-changelog from 3.2.0 to 3.2.1 ([492dc6b](https://github.com/softrams/bulwark/commit/492dc6b73c432ea2c6204e37a2611f20a1c6de3f))\n- **deps-dev:** bump cz-conventional-changelog from 3.2.1 to 3.3.0 ([7f22e05](https://github.com/softrams/bulwark/commit/7f22e05fc2e001f2250bd938db6c2f244ff8067f))\n- **deps-dev:** bump ts-jest from 26.2.0 to 26.3.0 ([5d9e103](https://github.com/softrams/bulwark/commit/5d9e1037896ac023a016be8ff2b7e5b073100a52))\n- **deps-dev:** bump ts-node from 8.10.2 to 9.0.0 in /frontend ([9a31591](https://github.com/softrams/bulwark/commit/9a31591afa9e47390c3a1e5b6c63535f6596ddf7))\n\n### [4.0.1](https://github.com/softrams/bulwark/compare/v4.0.0...v4.0.1) (2020-08-24)\n\n### Bug Fixes\n\n- **asset and vuln:** fixed update Asset and fixed get Vulnerability by id ([ea4d4f1](https://github.com/softrams/bulwark/commit/ea4d4f1cdb17e52e617acaa1642eeae581b15e17))\n\n## [4.0.0](https://github.com/softrams/bulwark/compare/v3.2.3...v4.0.0) (2020-08-24)\n\n### ⚠ BREAKING CHANGES\n\n- **create jira table:** Added JIRA table\n- **jira utilities:** Breaking changes include updates to the create and update Asset APIs to include new\n  JIRA properties\n- **asset controller:** Updated asset model with jira properties: username, host, api key. Validation\n  broken. Tests broken.\n- **column mapper:** Updated column options\n- **asset archive:** Additional asset column breaks current model validations.\n\n### Features\n\n- **assessment controller:** assessment deletion feature ([b96b977](https://github.com/softrams/bulwark/commit/b96b9771d6df91b4c5cc78bec5cd1c6ad2281472)), closes [#177](https://github.com/softrams/bulwark/issues/177)\n- **assessment tester column:** updated API to get assessments by ID to include testers ([a5e4968](https://github.com/softrams/bulwark/commit/a5e4968aef8a57245b9990499e13ff0dc095ffc1)), closes [#205](https://github.com/softrams/bulwark/issues/205)\n- **asset archive:** implement ability to archive assets ([6e56a43](https://github.com/softrams/bulwark/commit/6e56a43d593a7062096e4895d0a8a98c53066b3e)), closes [#167](https://github.com/softrams/bulwark/issues/167)\n- **asset controller:** updated Asset model with jira properties ([8d4fa08](https://github.com/softrams/bulwark/commit/8d4fa08cdacd400acba47266e13b9f0cbf940fca)), closes [#179](https://github.com/softrams/bulwark/issues/179)\n- **column mapper:** create dynamic column options ([05fdac1](https://github.com/softrams/bulwark/commit/05fdac12ee01e1ce1b0e7b6eaa204a6bc1976f39)), closes [#167](https://github.com/softrams/bulwark/issues/167)\n- **create jira table:** broke apart Asset table and moved JIRA information to JIRA table ([7d31d45](https://github.com/softrams/bulwark/commit/7d31d4576cce96c5e99d8cf7b5e8e9712c3458d0)), closes [#179](https://github.com/softrams/bulwark/issues/179)\n- **jira:** initial commit for JIRA src ([acc78ab](https://github.com/softrams/bulwark/commit/acc78ab6c9fca139aa9b5ffe3b57d68d8d18dfff)), closes [#179](https://github.com/softrams/bulwark/issues/179)\n- **jira issue save update with refactored code:** updated current Jira functions for new table ([3b13445](https://github.com/softrams/bulwark/commit/3b134455c991a05a9daa66d970e4824d56aac6be)), closes [#179](https://github.com/softrams/bulwark/issues/179)\n- **jira update/save:** implement the ability to update/save update ([1d586cf](https://github.com/softrams/bulwark/commit/1d586cf7c0ca12de8a040f4ca65664e8464d3843)), closes [#179](https://github.com/softrams/bulwark/issues/179)\n- **jira utilities:** save/Update Jira Issue from vulnerabilities ([d2e1c81](https://github.com/softrams/bulwark/commit/d2e1c8135941b84d0e3f700e94bfedcc49da494c)), closes [#179](https://github.com/softrams/bulwark/issues/179)\n- **jira utility:** implemented JIRA utility POC ([e2aa328](https://github.com/softrams/bulwark/commit/e2aa328d71ca792ad5034cefe433706982b73acd)), closes [#179](https://github.com/softrams/bulwark/issues/179)\n- **testers column added to assessments summary table:** testers column added to assessment table ([67d5188](https://github.com/softrams/bulwark/commit/67d5188eb7b24b9dec6ef60789b736a15734f8b8)), closes [#205](https://github.com/softrams/bulwark/issues/205)\n- **vulnerability form:** updated confirmation message for jira export ([8012a9a](https://github.com/softrams/bulwark/commit/8012a9a17630d253c257ce0f0c645fb123580317)), closes [#179](https://github.com/softrams/bulwark/issues/179)\n\n### Bug Fixes\n\n- **assessment/vulnerability/report table component:** fixed Jira URL table formatting ([5e053bc](https://github.com/softrams/bulwark/commit/5e053bc73ac3eb3a62e0a9f7e62c1665bc740101)), closes [#225](https://github.com/softrams/bulwark/issues/225)\n- **dashboard component, organization component, and global styles:** moved card style to global scss ([d9e6154](https://github.com/softrams/bulwark/commit/d9e6154c188091b02a0bf12eeb757dae3fbca0b5)), closes [#165](https://github.com/softrams/bulwark/issues/165)\n- **jira screenshots and readme:** fixed jira screenshot attachments. Updated README with new gifs ([c13e9b0](https://github.com/softrams/bulwark/commit/c13e9b0f4c2cd723a08f40f47e75330ff5354ff5))\n- **update csp:** updated CSP ([6ae8c42](https://github.com/softrams/bulwark/commit/6ae8c4220920f147e0f2149cbe8ecd0b2545db40)), closes [#179](https://github.com/softrams/bulwark/issues/179)\n- **update undefined message:** updated jiraId URL to use the jira info from database ([6a864d0](https://github.com/softrams/bulwark/commit/6a864d0bf524e5932648436e2d65891ad458cc83)), closes [#179](https://github.com/softrams/bulwark/issues/179)\n- **user profile component:** fixed user profile form value binding ([1c87039](https://github.com/softrams/bulwark/commit/1c870397518dfe95852a1c9c63911be056c9a45c)), closes [#218](https://github.com/softrams/bulwark/issues/218)\n\n### Tests\n\n- **aet controller spec:** add unit tests for new asset controller methods ([0e2213f](https://github.com/softrams/bulwark/commit/0e2213f9e2350289868456b645e294f5a77951aa)), closes [#167](https://github.com/softrams/bulwark/issues/167)\n- **assessment controller test:** unit test for deleteAssessmentById ([116c0fc](https://github.com/softrams/bulwark/commit/116c0fce41b4b9a3f1cdf11528f5d6e06bfa6350)), closes [#177](https://github.com/softrams/bulwark/issues/177)\n- **jira utility:** unit tests for the jira utility ([2b0e5d7](https://github.com/softrams/bulwark/commit/2b0e5d7fd46135a6ca33ccaa219ed0e4baf1cd5e)), closes [#179](https://github.com/softrams/bulwark/issues/179)\n- **tests: asset, assessment, crypto:** added unit tests for the crypto utility and asset controller ([9f63021](https://github.com/softrams/bulwark/commit/9f63021cf2801f99f9b674b9e13efab6636c2f0d)), closes [#179](https://github.com/softrams/bulwark/issues/179)\n\n### Docs\n\n- **readme:** updated README ([7ca3343](https://github.com/softrams/bulwark/commit/7ca33432dbd6a3cb8ede463da82b9f600f8666c3)), closes [#179](https://github.com/softrams/bulwark/issues/179)\n- **readme/contributing:** updated documentation for the README and CONTRIBUTING ([bef6d47](https://github.com/softrams/bulwark/commit/bef6d47bb810c1e58e5579b26bc95f601c203d92)), closes [#179](https://github.com/softrams/bulwark/issues/179)\n- **security policy:** updated security policy ([c307265](https://github.com/softrams/bulwark/commit/c307265157a3bf2ec03a6eaa9c31280e0dcc4bf8))\n\n### Others\n\n- **deps:** bump @angular-devkit/build-angular in /frontend ([1e83309](https://github.com/softrams/bulwark/commit/1e8330973b3de1b50cd01a3db102726d46db9b77))\n- **deps:** bump @angular-devkit/build-angular in /frontend ([533abf2](https://github.com/softrams/bulwark/commit/533abf27a4f7b86f683254ee618480d5c5064573))\n- **deps:** bump @angular-devkit/build-angular in /frontend ([d2a9b04](https://github.com/softrams/bulwark/commit/d2a9b046a7c346ffc35dc1021170f6981dcc1c2b))\n- **deps:** bump @angular/cli from 10.0.4 to 10.0.5 in /frontend ([2a7ddc4](https://github.com/softrams/bulwark/commit/2a7ddc4aa2ef15fb41eb55078722a94b1a6d3952))\n- **deps:** bump @angular/cli from 10.0.5 to 10.0.6 in /frontend ([1f41210](https://github.com/softrams/bulwark/commit/1f412106b18ed47287a76448ff753e6e7d0d9ffc))\n- **deps:** bump @angular/cli from 10.0.6 to 10.0.7 in /frontend ([e093c72](https://github.com/softrams/bulwark/commit/e093c72fd0c8e80aa58b34c91857d6b0aac5bc99))\n- **deps:** bump @ng-select/ng-select from 4.0.4 to 5.0.0 in /frontend ([2ea1db8](https://github.com/softrams/bulwark/commit/2ea1db8c23e94bf6b05b79195dc93d26761a4a02))\n- **deps:** bump @ng-select/ng-select from 5.0.0 to 5.0.1 in /frontend ([8e04728](https://github.com/softrams/bulwark/commit/8e04728cf09a87878c8b5f2853a0e029176486f6))\n- **deps:** bump concurrently from 5.2.0 to 5.3.0 ([55a0601](https://github.com/softrams/bulwark/commit/55a06012daeead903ec88f683ec7a27626f73fd8))\n- **deps:** bump helmet from 3.23.3 to 4.0.0 ([2f682de](https://github.com/softrams/bulwark/commit/2f682de0fae94e085c4e92b2cef2a24498b28677))\n- **deps:** bump helmet from 4.0.0 to 4.1.0 ([f46bb06](https://github.com/softrams/bulwark/commit/f46bb068c5ad27450584e6573f6f1d6f4e41753c))\n- **deps:** bump jira-client from 6.18.0 to 6.19.0 ([369732f](https://github.com/softrams/bulwark/commit/369732fd6c8288db3125597694afdf3c1f14e6df))\n- **deps:** bump ngx-markdown from 10.1.0 to 10.1.1 in /frontend ([9a83ce2](https://github.com/softrams/bulwark/commit/9a83ce2e7708a4bc7145aed2a27e5df449b794fd))\n- **deps:** bump nodemailer from 6.4.10 to 6.4.11 ([99087f5](https://github.com/softrams/bulwark/commit/99087f5b382499bbbceaeed4b71e42d8e57bf892))\n- **deps:** bump password-validator from 5.0.3 to 5.1.0 ([f47a663](https://github.com/softrams/bulwark/commit/f47a663b9bda58a267ddeda3197a100bc4e31f93))\n- **deps:** bump rxjs from 6.6.0 to 6.6.2 in /frontend ([2c28258](https://github.com/softrams/bulwark/commit/2c28258b8026eaf59c2a0444bf9d6b62fd693098))\n- **deps:** bump ts-node from 8.10.2 to 9.0.0 ([df9971e](https://github.com/softrams/bulwark/commit/df9971e37a7b3c67e35200b2ff91b25f6a4a10c6))\n- **deps:** bump tslib from 2.0.0 to 2.0.1 in /frontend ([f320cf9](https://github.com/softrams/bulwark/commit/f320cf9e4819ece06bf3ddf30b62df9eeefbd610))\n- **deps:** bump uuid from 8.2.0 to 8.3.0 ([70c2ee1](https://github.com/softrams/bulwark/commit/70c2ee16156d3699c795ba3af91312b33c80de36))\n- **deps-dev:** bump @angular/language-service in /frontend ([a5ec3e1](https://github.com/softrams/bulwark/commit/a5ec3e1cb3e22323858c3218d588ed99d6cb514f))\n- **deps-dev:** bump @angular/language-service in /frontend ([5ca9ea8](https://github.com/softrams/bulwark/commit/5ca9ea8ed16a653475e2395bd1461792937dfa4d))\n- **deps-dev:** bump @angular/language-service in /frontend ([ccebf16](https://github.com/softrams/bulwark/commit/ccebf16a604e94b3a0a1ea4dc7155b35347ac42b))\n- **deps-dev:** bump @angular/language-service in /frontend ([9b0fa48](https://github.com/softrams/bulwark/commit/9b0fa48082b5b7285a60388c6d680b3fbfa26dc0))\n- **deps-dev:** bump @angular/language-service in /frontend ([8411cd5](https://github.com/softrams/bulwark/commit/8411cd51860464d7be971c16cd6d22a10a73a8aa))\n- **deps-dev:** bump @angular/language-service in /frontend ([bf6a329](https://github.com/softrams/bulwark/commit/bf6a3290d3dd785da068ec9500d23b52840c1776))\n- **deps-dev:** bump @babel/core from 7.10.5 to 7.11.0 ([296e46a](https://github.com/softrams/bulwark/commit/296e46af651661861900dfd397b09e7ef11fbf40))\n- **deps-dev:** bump @babel/core from 7.11.0 to 7.11.1 ([89d900e](https://github.com/softrams/bulwark/commit/89d900eba373dd1b10589d4555f1e810281e9de8))\n- **deps-dev:** bump @babel/core from 7.11.1 to 7.11.4 ([daa6fef](https://github.com/softrams/bulwark/commit/daa6fef3d295cea1f72292a0cd29ec3a42404efc))\n- **deps-dev:** bump @babel/preset-env from 7.10.4 to 7.11.0 ([598c653](https://github.com/softrams/bulwark/commit/598c653fee8a5d0eac68c924ab4440573d8b2b0c))\n- **deps-dev:** bump @commitlint/config-conventional ([2cc4edd](https://github.com/softrams/bulwark/commit/2cc4edd65bf9b519c2a4d20430797ed7b8a21c94))\n- **deps-dev:** bump @types/jasmine from 3.5.11 to 3.5.12 in /frontend ([6b0a119](https://github.com/softrams/bulwark/commit/6b0a119264d6fcc9c161a5634a21a6b28902b529))\n- **deps-dev:** bump @types/jasmine from 3.5.12 to 3.5.13 in /frontend ([39fd17d](https://github.com/softrams/bulwark/commit/39fd17d21cb2c66f846deacab3cad94428fa8a8e))\n- **deps-dev:** bump @types/jest from 26.0.7 to 26.0.8 ([0e8ca5d](https://github.com/softrams/bulwark/commit/0e8ca5dc95c1fab5ff97f8eabe8e08c049e1d9a5))\n- **deps-dev:** bump @types/jest from 26.0.8 to 26.0.9 ([3aa5c98](https://github.com/softrams/bulwark/commit/3aa5c98f6e459b79d4d4832696853df4b29b2c75))\n- **deps-dev:** bump @types/jest from 26.0.9 to 26.0.10 ([3803bbe](https://github.com/softrams/bulwark/commit/3803bbe0377de9faacb22343e0722772ba01318e))\n- **deps-dev:** bump @types/node from 14.0.25 to 14.0.26 in /frontend ([5252e7f](https://github.com/softrams/bulwark/commit/5252e7f80b4c78154bd2850f0b70200d3af259e0))\n- **deps-dev:** bump @types/node from 14.0.26 to 14.0.27 in /frontend ([a362060](https://github.com/softrams/bulwark/commit/a36206048d1216aa8b7bb88586eef704dd74af10))\n- **deps-dev:** bump @types/node from 14.0.27 to 14.6.0 in /frontend ([a3ba2b6](https://github.com/softrams/bulwark/commit/a3ba2b6c22f059c83490c9c7692836e4c213f5b7))\n- **deps-dev:** bump babel-jest from 26.1.0 to 26.2.1 ([a71be6c](https://github.com/softrams/bulwark/commit/a71be6c6e5f8496a95ae9a258733a14dbc6d3c48))\n- **deps-dev:** bump babel-jest from 26.2.1 to 26.2.2 ([e376fd1](https://github.com/softrams/bulwark/commit/e376fd1d0353c134000857279232b122b65488a3))\n- **deps-dev:** bump babel-jest from 26.2.2 to 26.3.0 ([4e4f147](https://github.com/softrams/bulwark/commit/4e4f147870d4c48a8ae77a71bd70674a41c3dfb8))\n- **deps-dev:** bump jest from 26.1.0 to 26.2.2 ([0e2c2a4](https://github.com/softrams/bulwark/commit/0e2c2a4926907bda1041e7f753b4a01e176e72ce))\n- **deps-dev:** bump jest from 26.2.2 to 26.3.0 ([a58ad02](https://github.com/softrams/bulwark/commit/a58ad02393078692cf707ed07d4a258a8c6d83fb))\n- **deps-dev:** bump jest from 26.3.0 to 26.4.0 ([59f2090](https://github.com/softrams/bulwark/commit/59f2090f62906721d069279fe6e4a34fdeb24f94))\n- **deps-dev:** bump jest from 26.4.0 to 26.4.1 ([f50109e](https://github.com/softrams/bulwark/commit/f50109e977dcb06df39c127210bc166b9bd7406b))\n- **deps-dev:** bump jest from 26.4.1 to 26.4.2 ([bd3a6f6](https://github.com/softrams/bulwark/commit/bd3a6f65e0f381627d69214ca0dc881f40ecf808))\n- **deps-dev:** bump karma from 5.1.0 to 5.1.1 in /frontend ([f96145d](https://github.com/softrams/bulwark/commit/f96145d9dc1ba93892eac309ea903c07df46aaef))\n- **deps-dev:** bump karma-jasmine from 3.3.1 to 4.0.0 in /frontend ([1c7a2bd](https://github.com/softrams/bulwark/commit/1c7a2bd3560df20d2ef60ef5f1d34bf45bdb4783))\n- **deps-dev:** bump karma-jasmine from 4.0.0 to 4.0.1 in /frontend ([94a8c1c](https://github.com/softrams/bulwark/commit/94a8c1c590dac10f99752e839f7c9bd991328481))\n- **deps-dev:** bump standard-version from 8.0.2 to 9.0.0 ([6687d1c](https://github.com/softrams/bulwark/commit/6687d1c537b81af2f8c85e9fb48770137a2938b6))\n- **deps-dev:** bump ts-jest from 26.1.3 to 26.1.4 ([cf8231c](https://github.com/softrams/bulwark/commit/cf8231ced3db1c12b5a658993c0abc53d4a7a1b2))\n- **deps-dev:** bump ts-jest from 26.1.4 to 26.2.0 ([b0794a1](https://github.com/softrams/bulwark/commit/b0794a1e2774dc4548c4fc7dd78d3c7761ca8f46))\n- **deps-dev:** bump tslint from 6.1.2 to 6.1.3 ([010a286](https://github.com/softrams/bulwark/commit/010a286d377393f6157c6db915b5b68c6782517b))\n- **deps-dev:** bump tslint from 6.1.2 to 6.1.3 in /frontend ([97e37f1](https://github.com/softrams/bulwark/commit/97e37f1b390d8e75d1ac4367213425676dcfcd1f))\n- **documentation updates:** updated documentation and added seed file ([7796d1e](https://github.com/softrams/bulwark/commit/7796d1e2fa75115e523f77c3bf409f68b5b110f7)), closes [#179](https://github.com/softrams/bulwark/issues/179)\n- **fix merge conflicts:** fixed merge conflicts ([a433792](https://github.com/softrams/bulwark/commit/a43379292ccc42f26af52b5713ac72f88e548ad1)), closes [#167](https://github.com/softrams/bulwark/issues/167)\n- **fix merge conflicts:** fixed merge conflicts from master ([06da9af](https://github.com/softrams/bulwark/commit/06da9affbf73f3a2c3613ea83955fb73aaa764fb))\n- **package.json:** fixing merge conflicts ([4f1c2b3](https://github.com/softrams/bulwark/commit/4f1c2b373d0392ef17b6c07d9d6676b6c3165eff)), closes [#179](https://github.com/softrams/bulwark/issues/179)\n- **package.jsons:** update jira with develop ([cdb9dfa](https://github.com/softrams/bulwark/commit/cdb9dfac0fc0f12703fbda943a436c21e72e4151)), closes [#179](https://github.com/softrams/bulwark/issues/179)\n- **remove unneeded files:** removed unused and unneeded interfaces ([d6dbd51](https://github.com/softrams/bulwark/commit/d6dbd51b357a7178097b7a3317f61c4e44657552)), closes [#179](https://github.com/softrams/bulwark/issues/179)\n- **update dependencies from develop:** updated dependencies from the Develop branch ([94c7bcc](https://github.com/softrams/bulwark/commit/94c7bccf59d52e93369158de5b5e705ad29f4725)), closes [#179](https://github.com/softrams/bulwark/issues/179)\n- **updated readme:** updated readme and revert workflow name ([614f642](https://github.com/softrams/bulwark/commit/614f64231e45dcf72b8c5a8b81f3d9e52decf142)), closes [#179](https://github.com/softrams/bulwark/issues/179)\n\n### [3.2.3](https://github.com/softrams/bulwark/compare/v3.2.2...v3.2.3) (2020-08-17)\n\n### Bug Fixes\n\n- **ormconfig:** updated ormconfig migrations array ([afba021](https://github.com/softrams/bulwark/commit/afba021856cabfde61d7f833998b9be138dcabb2)), closes [#208](https://github.com/softrams/bulwark/issues/208)\n- **seed initial user fix:** update seed user script so that it uses CLI arguments ([14d0e38](https://github.com/softrams/bulwark/commit/14d0e384228075cb064d794251bcb0d8fff796b1)), closes [#208](https://github.com/softrams/bulwark/issues/208)\n- **updated readme. added initial migration script:** updated the README with updated initial steps ([c954694](https://github.com/softrams/bulwark/commit/c954694a59e1483c61e7302684d53a57f44e55fa)), closes [#208](https://github.com/softrams/bulwark/issues/208)\n\n### [3.2.2](https://github.com/softrams/bulwark/compare/v3.2.1...v3.2.2) (2020-07-24)\n\n### Others\n\n- **deps:** bump @angular-devkit/build-angular in /frontend ([0e7ed13](https://github.com/softrams/bulwark/commit/0e7ed135e28e9529a823a615d1170732bb972ad0))\n- **deps:** bump @angular/cli from 10.0.3 to 10.0.4 in /frontend ([9f28641](https://github.com/softrams/bulwark/commit/9f286413a4ea59a5a0d2319a5cfdaf7874f3189b))\n- **deps:** bump ngx-markdown from 10.0.0 to 10.1.0 in /frontend ([124ade3](https://github.com/softrams/bulwark/commit/124ade35bcdc635ba8b6a7556e2b91be792c1801))\n- **deps:** bump puppeteer from 5.2.0 to 5.2.1 ([21512c8](https://github.com/softrams/bulwark/commit/21512c891e9a719293498f20f0e38314f9bf206f))\n- **deps-dev:** bump @angular/language-service in /frontend ([0a5ea43](https://github.com/softrams/bulwark/commit/0a5ea4390fb120edd73a4282431ab4e17153bba7))\n- **deps-dev:** bump @types/jest from 26.0.5 to 26.0.7 ([efb36ae](https://github.com/softrams/bulwark/commit/efb36ae6f9d6063f30f1f0a6511cd1f3c5add224))\n- **deps-dev:** bump @types/node from 14.0.23 to 14.0.24 in /frontend ([ec93f25](https://github.com/softrams/bulwark/commit/ec93f2539362cc2cf707f3ced68b6a21352bb9ab))\n- **deps-dev:** bump @types/node from 14.0.24 to 14.0.25 in /frontend ([49dbb50](https://github.com/softrams/bulwark/commit/49dbb507e7629d7560af0839f2e321083e4c2abf))\n- **deps-dev:** bump jasmine-core from 3.5.0 to 3.6.0 in /frontend ([84f70ae](https://github.com/softrams/bulwark/commit/84f70ae75044f8ed1da2635900d47b1521136e95))\n\n### [3.2.1](https://github.com/softrams/bulwark/compare/v3.2.0...v3.2.1) (2020-07-20)\n\n### Build System\n\n- **lru-cache:** added lru-cache to remove angular error ([adaa65b](https://github.com/softrams/bulwark/commit/adaa65ba4c4c1451e0dd7100036e293abb1db824)), closes [#111](https://github.com/softrams/bulwark/issues/111)\n\n### Tests\n\n- **assessment controller:** assessment controller unit tests ([c3d7fa8](https://github.com/softrams/bulwark/commit/c3d7fa8f92170657fd4729fa6935af6ed32a6d8c)), closes [#64](https://github.com/softrams/bulwark/issues/64)\n- **email.service.ts:** unit tests for email service ([799132f](https://github.com/softrams/bulwark/commit/799132fa94466abf9ab137920e5d2382cb6124b9)), closes [#136](https://github.com/softrams/bulwark/issues/136)\n- **package.json:** update test node script ([6e32fcc](https://github.com/softrams/bulwark/commit/6e32fccea777d9c6aa670a13457767c0ba5169cd)), closes [#137](https://github.com/softrams/bulwark/issues/137)\n- **password.utility.spec.ts:** unit tests for password utility ([e76fcee](https://github.com/softrams/bulwark/commit/e76fceec53a3d1ee4dd9bb223693c4f1befa7b66)), closes [#64](https://github.com/softrams/bulwark/issues/64)\n- **seed-user.ts:** initial seed user test ([b8ac8b8](https://github.com/softrams/bulwark/commit/b8ac8b8c2eb34b72efe433111e2291c1dc90ceae)), closes [#137](https://github.com/softrams/bulwark/issues/137)\n- **user controller:** user controller unit test ([aa53b54](https://github.com/softrams/bulwark/commit/aa53b5469814ed3158cbb3f2436369dadb5a4e07)), closes [#122](https://github.com/softrams/bulwark/issues/122)\n- **user.controller.spec.ts:** complete unit tests for user controller ([b96af41](https://github.com/softrams/bulwark/commit/b96af417d6b2f49dd95704ead526139c975cffe6)), closes [#122](https://github.com/softrams/bulwark/issues/122)\n\n### Others\n\n- **angular 10:** upgrade to Angular 10 ([3fe70f5](https://github.com/softrams/bulwark/commit/3fe70f5498f6367446bed15dedbf92a725de3e00)), closes [#111](https://github.com/softrams/bulwark/issues/111)\n- **deps:** bump @angular-devkit/build-angular in /frontend ([9aa1a9d](https://github.com/softrams/bulwark/commit/9aa1a9de50bfe870012b3cd0a07870d123f8fc6b))\n- **deps:** bump @angular/cli from 10.0.2 to 10.0.3 in /frontend ([cdf5e50](https://github.com/softrams/bulwark/commit/cdf5e50a63d0a97b192afcee6e64098ea6ff3aec))\n- **deps:** bump @fortawesome/angular-fontawesome in /frontend ([e7b7aa1](https://github.com/softrams/bulwark/commit/e7b7aa1fc8c0f1e35b27fcad222cabb711ea03b2))\n- **deps:** bump @fortawesome/fontawesome-svg-core in /frontend ([a982d4f](https://github.com/softrams/bulwark/commit/a982d4fc8af1d5029a9da26a3b67fc6050e16d6b))\n- **deps:** bump @fortawesome/free-brands-svg-icons in /frontend ([7367d70](https://github.com/softrams/bulwark/commit/7367d708a7f08b334e4dedc6a819423e573ddc32))\n- **deps:** bump ngx-markdown from 9.1.1 to 10.0.0 in /frontend ([de9eae4](https://github.com/softrams/bulwark/commit/de9eae48f4da2f234d26911c4e118be5930920b4))\n- **deps:** bump puppeteer from 5.0.0 to 5.1.0 ([b024375](https://github.com/softrams/bulwark/commit/b0243757789f0341e83eaa1983f782346426f7fd))\n- **deps:** bump puppeteer from 5.1.0 to 5.2.0 ([094b493](https://github.com/softrams/bulwark/commit/094b493ec72e1a5003d5691a2f16d69dd69a096f))\n- **deps:** bump tslib from 1.10.0 to 1.13.0 in /frontend ([7b031cb](https://github.com/softrams/bulwark/commit/7b031cb6bd5268857f0381b9316c87deb371966a))\n- **deps:** bump typescript from 3.9.6 to 3.9.7 in /frontend ([d2fc168](https://github.com/softrams/bulwark/commit/d2fc168e33af7b9c2d24e916c53338a95904093d))\n- **deps-dev:** bump @angular/language-service in /frontend ([a1cafaa](https://github.com/softrams/bulwark/commit/a1cafaa370db2ca41f4ae4d05c9a96b23d367f9c))\n- **deps-dev:** bump @babel/core from 7.10.4 to 7.10.5 ([cff5ac3](https://github.com/softrams/bulwark/commit/cff5ac3365bb67e4907341d2e1cda3e4a39a5304))\n- **deps-dev:** bump @commitlint/cli from 9.0.1 to 9.1.1 ([9670c1a](https://github.com/softrams/bulwark/commit/9670c1a9545c090711639ade18011dae3bfe42b6))\n- **deps-dev:** bump @commitlint/config-conventional ([9f987bf](https://github.com/softrams/bulwark/commit/9f987bfd4ee17e9c2dfdd8e2f67bd192374ca634))\n- **deps-dev:** bump @types/jasmine from 2.8.16 to 3.5.11 in /frontend ([d529063](https://github.com/softrams/bulwark/commit/d5290638d3ca493428664149591e4829bcff6cef))\n- **deps-dev:** bump @types/jest from 26.0.4 to 26.0.5 ([2d442c5](https://github.com/softrams/bulwark/commit/2d442c5629399b815b322ef2c85eb097698ce967))\n- **deps-dev:** bump @types/node from 12.12.34 to 14.0.23 in /frontend ([de699d6](https://github.com/softrams/bulwark/commit/de699d6e9ad978ec4613012a7e260977719ccc84))\n- **deps-dev:** bump codelyzer from 5.2.2 to 6.0.0 in /frontend ([0cbedf9](https://github.com/softrams/bulwark/commit/0cbedf98485b155c6a459b79a70f9e5ad9817771))\n- **deps-dev:** bump karma-coverage-istanbul-reporter in /frontend ([da3906d](https://github.com/softrams/bulwark/commit/da3906d320540c816536c7b77312be6907537992))\n- **deps-dev:** bump protractor from 5.4.2 to 7.0.0 in /frontend ([943642e](https://github.com/softrams/bulwark/commit/943642eb31bebe326d6ea55ac757500192a9a704))\n- **deps-dev:** bump standard-version from 8.0.0 to 8.0.1 ([c1dc9c2](https://github.com/softrams/bulwark/commit/c1dc9c21d351d7220bba87ef660b2565360edfcc))\n- **deps-dev:** bump standard-version from 8.0.0 to 8.0.1 ([5f213bd](https://github.com/softrams/bulwark/commit/5f213bd3532a1857a6425efb48dcf0ad1c8caedf))\n- **deps-dev:** bump standard-version from 8.0.1 to 8.0.2 ([7d9979a](https://github.com/softrams/bulwark/commit/7d9979a45300c976d0ecf12d35f5339f6cc111a3))\n- **deps-dev:** bump ts-jest from 26.1.1 to 26.1.2 ([13563c8](https://github.com/softrams/bulwark/commit/13563c8036df275f3abd665327df3811c98d14f8))\n- **deps-dev:** bump ts-jest from 26.1.2 to 26.1.3 ([aac7b80](https://github.com/softrams/bulwark/commit/aac7b8088eac3cbd1cd1e15c91994b5b1fc1e993))\n- **deps-dev:** bump typescript from 3.9.6 to 3.9.7 ([6ba20aa](https://github.com/softrams/bulwark/commit/6ba20aa27d413b209cabd21d5f62111fdc8f1eb2))\n- **lodash:** update lodash to mitigate low severity vulnerability ([9fa5682](https://github.com/softrams/bulwark/commit/9fa568233c342224c6042b90b7a5f0db7d454c64))\n- **package.json:** move pre-commit hook to hooks ([e2062d6](https://github.com/softrams/bulwark/commit/e2062d639b0a009fca1deb541445f4aada885036))\n- **package.json frontend/package.json:** fix merge conflcits ([620e320](https://github.com/softrams/bulwark/commit/620e32043ac680b9b4edc75abd6dec8ba84e881e)), closes [#111](https://github.com/softrams/bulwark/issues/111)\n- **package.json frontend/package.json:** fix merge conflicts ([244d123](https://github.com/softrams/bulwark/commit/244d1238305041510b570081022d32469ab17877)), closes [#111](https://github.com/softrams/bulwark/issues/111)\n- **package.json frontend/package.json:** merge from develop. Remove sinon and sqlite3 ([8021985](https://github.com/softrams/bulwark/commit/8021985bfdd45e860a3db54e4643f2d3a92815a7)), closes [#64](https://github.com/softrams/bulwark/issues/64)\n- **tslint revert:** revert tslint version ([d3f3d97](https://github.com/softrams/bulwark/commit/d3f3d97da8676039262f6355480d79228fe7d0c7)), closes [#111](https://github.com/softrams/bulwark/issues/111)\n\n## [3.2.0](https://github.com/softrams/bulwark/compare/v3.1.3...v3.2.0) (2020-07-13)\n\n### Features\n\n- **report.component.html:** implemented dynamic report properties ([8a1300e](https://github.com/softrams/bulwark/commit/8a1300eb51f3ed3971c63cf3e3f0aca9ce37b4d4)), closes [#37](https://github.com/softrams/bulwark/issues/37)\n\n### Bug Fixes\n\n- **uuid:** update import statement for uuid ([1e1143f](https://github.com/softrams/bulwark/commit/1e1143f445061f95bab551a55e310fa5862ec737))\n\n### CI\n\n- **node.js.yml:** added linting to CI process ([53f600c](https://github.com/softrams/bulwark/commit/53f600c22ef0127a6702475139e660b20b5b2b2b)), closes [#90](https://github.com/softrams/bulwark/issues/90)\n- **node.js.yml:** initial commit for github action ([7a87e2b](https://github.com/softrams/bulwark/commit/7a87e2b4ee5dcf7fb09e9d2ca66051a61c4a09a6)), closes [#6](https://github.com/softrams/bulwark/issues/6)\n- **removing tslint:** removing tslint to fix build error ([02f3c08](https://github.com/softrams/bulwark/commit/02f3c080c4fc487461e27d146e59ed04f00da14c))\n\n### Others\n\n- **deps:** bump @fortawesome/fontawesome-svg-core in /frontend ([4675399](https://github.com/softrams/bulwark/commit/4675399427ab02caab1b79498169fa42bb3e2765))\n- **deps:** bump @fortawesome/free-brands-svg-icons in /frontend ([17fb885](https://github.com/softrams/bulwark/commit/17fb885eb8361aedd859c9022084414160826815))\n- **deps:** bump @ng-select/ng-select from 4.0.0 to 4.0.4 in /frontend ([be713d0](https://github.com/softrams/bulwark/commit/be713d01a50ca36270948e7e1773968d31579605))\n- **deps:** bump @types/body-parser from 1.17.1 to 1.19.0 ([3a4a706](https://github.com/softrams/bulwark/commit/3a4a70613f56896677d490fa504a95cd338b0999))\n- **deps:** bump @types/express from 4.17.1 to 4.17.7 ([c41a065](https://github.com/softrams/bulwark/commit/c41a0654ad011afffbac43cf1e3d2a4e4dceab48))\n- **deps:** bump bcrypt from 3.0.7 to 5.0.0 ([39f0eeb](https://github.com/softrams/bulwark/commit/39f0eeb7b55d9e98c1252959e3b09cd31869368b))\n- **deps:** bump class-validator from 0.10.2 to 0.12.2 ([c5402ca](https://github.com/softrams/bulwark/commit/c5402ca4a77cc09bbd819183606353fec646ed88))\n- **deps:** bump concurrently from 5.0.0 to 5.2.0 ([7d63245](https://github.com/softrams/bulwark/commit/7d632452c8146c8e2e97ae6b348378feac9b350a))\n- **deps:** bump core-js from 2.6.10 to 3.6.5 in /frontend ([ff968bc](https://github.com/softrams/bulwark/commit/ff968bc39584897fe166869d26f809633cc0b64b))\n- **deps:** bump helmet from 3.21.1 to 3.23.3 ([6af23eb](https://github.com/softrams/bulwark/commit/6af23eb373d5fc5b8c6230db6e4f20680fbec932))\n- **deps:** bump mysql from 2.17.1 to 2.18.1 ([cf1a858](https://github.com/softrams/bulwark/commit/cf1a858263250f63dbc83fb7323613c5cab42598))\n- **deps:** bump ngx-markdown from 8.2.1 to 9.1.1 in /frontend ([2d5574b](https://github.com/softrams/bulwark/commit/2d5574b71f55e5671b96cb78e04f519c253f9ba3))\n- **deps:** bump nodemailer from 6.4.2 to 6.4.10 ([9ba41f3](https://github.com/softrams/bulwark/commit/9ba41f31278bd48fe0def48f8d07fc27f30280ec))\n- **deps:** bump puppeteer from 1.20.0 to 5.0.0 ([ba72380](https://github.com/softrams/bulwark/commit/ba723801dd54a121a33bdf7dbfba4db4c8245e0a))\n- **deps:** bump rxjs from 6.5.4 to 6.6.0 in /frontend ([e28f1ed](https://github.com/softrams/bulwark/commit/e28f1ede03893fe2f12896250c0633c386caeed2))\n- **deps:** bump ts-node from 8.4.1 to 8.10.2 ([12dc1fc](https://github.com/softrams/bulwark/commit/12dc1fc8abe24842869f4aa19f574f59be63e921))\n- **deps:** bump typeorm from 0.2.24 to 0.2.25 ([dd663d9](https://github.com/softrams/bulwark/commit/dd663d98e0444f40d07e08b7fc92086167f3a3f6))\n- **deps:** bump uuid from 3.4.0 to 8.2.0 ([395ae16](https://github.com/softrams/bulwark/commit/395ae16f32cdad745e10ab595e48071b67a3b63f))\n- **deps-dev:** bump @angular/language-service in /frontend ([2b48498](https://github.com/softrams/bulwark/commit/2b48498a81444b9ea851e0b1200a998925886b3c))\n- **deps-dev:** bump @commitlint/cli from 8.3.5 to 9.0.1 ([cfe0308](https://github.com/softrams/bulwark/commit/cfe030877f9607d896852c905dde2c46b2ad8dc4))\n- **deps-dev:** bump @commitlint/config-conventional ([0be3d9b](https://github.com/softrams/bulwark/commit/0be3d9bda8154c9e4a26a332287e6412c4bd983f))\n- **deps-dev:** bump karma from 4.0.1 to 5.1.0 in /frontend ([6e63c60](https://github.com/softrams/bulwark/commit/6e63c606da20e29d802bd344dc0110bdd6cbbcac))\n- **deps-dev:** bump karma-jasmine from 1.1.2 to 3.3.1 in /frontend ([3a25383](https://github.com/softrams/bulwark/commit/3a2538313299bdb7dfecdd8f0bb087908c47ffcb))\n- **deps-dev:** bump nodemon from 1.19.3 to 2.0.4 ([b5ce69b](https://github.com/softrams/bulwark/commit/b5ce69b31822ef59bc9825c8ca4a032385bf182e))\n- **deps-dev:** bump ts-node from 7.0.1 to 8.10.2 in /frontend ([2120370](https://github.com/softrams/bulwark/commit/21203709e9bf19d310887c072db185b935c69982))\n- **deps-dev:** bump tslint from 5.11.0 to 6.1.2 in /frontend ([14e7ab5](https://github.com/softrams/bulwark/commit/14e7ab56a245c533698d0fb5d17dacddcdb8892f))\n- **deps-dev:** bump tslint from 6.1.0 to 6.1.2 ([24b1ed8](https://github.com/softrams/bulwark/commit/24b1ed8db6c8fbf8159b48b2324f583b3ec9e071))\n- **deps-dev:** bump typescript from 3.8.3 to 3.9.6 ([b5cd69e](https://github.com/softrams/bulwark/commit/b5cd69ee478ec9d962c68c3eb998636180f60eef))\n- **release:** 3.1.4 ([69ce27f](https://github.com/softrams/bulwark/commit/69ce27f54795707ead852816050a4f8c35d3a789))\n- **release:** 3.1.5 ([0c3d25a](https://github.com/softrams/bulwark/commit/0c3d25aa2c6a5892b10b43dc985f5b42b4771e6f))\n- **release:** 3.1.6 ([db6137b](https://github.com/softrams/bulwark/commit/db6137b8e0a212f3b900dc5760ef2fdcd2e49ac1))\n\n### [3.1.6](https://github.com/softrams/bulwark/compare/v3.1.5...v3.1.6) (2020-07-11)\n\n### CI\n\n- **node.js.yml:** initial commit for github action ([7a87e2b](https://github.com/softrams/bulwark/commit/7a87e2b4ee5dcf7fb09e9d2ca66051a61c4a09a6)), closes [#6](https://github.com/softrams/bulwark/issues/6)\n\n### [3.1.5](https://github.com/softrams/bulwark/compare/v3.1.4...v3.1.5) (2020-07-11)\n\n### Others\n\n- **deps:** bump @ng-select/ng-select from 4.0.0 to 4.0.4 in /frontend ([be713d0](https://github.com/softrams/bulwark/commit/be713d01a50ca36270948e7e1773968d31579605))\n- **deps:** bump bcrypt from 3.0.7 to 5.0.0 ([39f0eeb](https://github.com/softrams/bulwark/commit/39f0eeb7b55d9e98c1252959e3b09cd31869368b))\n- **deps:** bump class-validator from 0.10.2 to 0.12.2 ([c5402ca](https://github.com/softrams/bulwark/commit/c5402ca4a77cc09bbd819183606353fec646ed88))\n- **deps:** bump core-js from 2.6.10 to 3.6.5 in /frontend ([ff968bc](https://github.com/softrams/bulwark/commit/ff968bc39584897fe166869d26f809633cc0b64b))\n- **deps:** bump ngx-markdown from 8.2.1 to 9.1.1 in /frontend ([2d5574b](https://github.com/softrams/bulwark/commit/2d5574b71f55e5671b96cb78e04f519c253f9ba3))\n- **deps:** bump puppeteer from 1.20.0 to 5.0.0 ([ba72380](https://github.com/softrams/bulwark/commit/ba723801dd54a121a33bdf7dbfba4db4c8245e0a))\n- **deps:** bump rxjs from 6.5.4 to 6.6.0 in /frontend ([e28f1ed](https://github.com/softrams/bulwark/commit/e28f1ede03893fe2f12896250c0633c386caeed2))\n- **deps:** bump ts-node from 8.4.1 to 8.10.2 ([12dc1fc](https://github.com/softrams/bulwark/commit/12dc1fc8abe24842869f4aa19f574f59be63e921))\n- **deps:** bump typeorm from 0.2.24 to 0.2.25 ([dd663d9](https://github.com/softrams/bulwark/commit/dd663d98e0444f40d07e08b7fc92086167f3a3f6))\n- **deps-dev:** bump @commitlint/cli from 8.3.5 to 9.0.1 ([cfe0308](https://github.com/softrams/bulwark/commit/cfe030877f9607d896852c905dde2c46b2ad8dc4))\n- **deps-dev:** bump @commitlint/config-conventional ([0be3d9b](https://github.com/softrams/bulwark/commit/0be3d9bda8154c9e4a26a332287e6412c4bd983f))\n- **deps-dev:** bump karma from 4.0.1 to 5.1.0 in /frontend ([6e63c60](https://github.com/softrams/bulwark/commit/6e63c606da20e29d802bd344dc0110bdd6cbbcac))\n- **deps-dev:** bump karma-jasmine from 1.1.2 to 3.3.1 in /frontend ([3a25383](https://github.com/softrams/bulwark/commit/3a2538313299bdb7dfecdd8f0bb087908c47ffcb))\n- **deps-dev:** bump nodemon from 1.19.3 to 2.0.4 ([b5ce69b](https://github.com/softrams/bulwark/commit/b5ce69b31822ef59bc9825c8ca4a032385bf182e))\n- **deps-dev:** bump tslint from 5.11.0 to 6.1.2 in /frontend ([14e7ab5](https://github.com/softrams/bulwark/commit/14e7ab56a245c533698d0fb5d17dacddcdb8892f))\n- **deps-dev:** bump tslint from 6.1.0 to 6.1.2 ([24b1ed8](https://github.com/softrams/bulwark/commit/24b1ed8db6c8fbf8159b48b2324f583b3ec9e071))\n\n### [3.1.4](https://github.com/softrams/bulwark/compare/v3.1.3...v3.1.4) (2020-07-11)\n\n### [3.1.3](https://github.com/softrams/bulwark/compare/v3.1.2...v3.1.3) (2020-07-10)\n\n### Others\n\n- **fix dependabot config:** fix dependabot config errors ([f4acc17](https://github.com/softrams/bulwark/commit/f4acc17bb797a16214f761239391477795d5ba9d))\n\n### [3.1.2](https://github.com/softrams/bulwark/compare/v3.1.1...v3.1.2) (2020-07-10)\n\n### Others\n\n- **add target branch:** add target branch to dependabot ([8a5732b](https://github.com/softrams/bulwark/commit/8a5732bceb32b6ee44f4b83837112090643b5a59))\n\n### [3.1.1](https://github.com/softrams/bulwark/compare/v3.1.0...v3.1.1) (2020-07-10)\n\n### Others\n\n- **add dependabot config:** add dependabot configuration ([bcf46b1](https://github.com/softrams/bulwark/commit/bcf46b146b2240c4d7269caf3df2ee413771d431)), closes [#65](https://github.com/softrams/bulwark/issues/65)\n\n## [3.1.0](https://github.com/softrams/bulwark/compare/v3.0.2...v3.1.0) (2020-07-09)\n\n### Features\n\n- **user-profile component:** ability to update user password ([9855987](https://github.com/softrams/bulwark/commit/985598765c7e7a4002bc5dd679a2096a1833dbc6)), closes [#50](https://github.com/softrams/bulwark/issues/50)\n- **user-profile.component.ts:** wired API to auth service and user prof ([6b6eb36](https://github.com/softrams/bulwark/commit/6b6eb36b1a361d2c3601e6cb7d4bac24a80b5c0c)), closes [#50](https://github.com/softrams/bulwark/issues/50)\n\n### Bug Fixes\n\n- **babel.config.js:** added new line ([e0617dd](https://github.com/softrams/bulwark/commit/e0617dd8e0d7c5c3b71ec88cc903e22632f5c418))\n- **jest.config.js:** removed tsx and jsx ([a269233](https://github.com/softrams/bulwark/commit/a26923324c0fbff7a32617fb5cc9298ef53df816))\n- **jwt.spec.ts:** added comments ([3b1505b](https://github.com/softrams/bulwark/commit/3b1505bbe7a449a32188f97f1fdbee4f6f1de63e))\n- **jwt.spec.ts:** added process.env to beforeAll to fix the issue ([51ab6e8](https://github.com/softrams/bulwark/commit/51ab6e805df007f3dbc41c9864670b2716ed9afd))\n- **jwt.spec.ts:** readded dotenv.config per PR ([e6ff159](https://github.com/softrams/bulwark/commit/e6ff1590f177c0460976360682e84935a8cc848b))\n- **jwt.spec.ts:** removed need for process.env ([c8ab992](https://github.com/softrams/bulwark/commit/c8ab99274859d9287ff21f9e82416dd2ef1d73db))\n- **package.json:** added needed packages to package.json ([15708b7](https://github.com/softrams/bulwark/commit/15708b7a19eed9a65be054876e4b231d2cee02ca))\n- **testing.md:** fixed a typo issue ([5441e7b](https://github.com/softrams/bulwark/commit/5441e7b43bba629bff0eaef374355e2f45163938))\n- **tsconfig.json:** added spec.ts to the exclude list ([66321e1](https://github.com/softrams/bulwark/commit/66321e1284adc186fdc645a4cdc65f6b74b992f5))\n\n### Code Refactoring\n\n- **user-profile.component.html .spec.ts:** updated element ID ([e91f40c](https://github.com/softrams/bulwark/commit/e91f40c820bd340f0b8f8f1472be7ecf737ba342)), closes [#50](https://github.com/softrams/bulwark/issues/50)\n\n### Docs\n\n- **contributing.md:** added a testing section under the linting section ([332e174](https://github.com/softrams/bulwark/commit/332e174f34a3b1bb996c7af567478b9a33dcfe9f))\n- **contributing.md:** removed testing information and added testing.md ([f3bdc44](https://github.com/softrams/bulwark/commit/f3bdc443cea498a8ce1fcf6f6687475b2c4c3c61))\n- **testing.md:** added a line to open the test coverage ([95d4862](https://github.com/softrams/bulwark/commit/95d48623d0a929bc667b92e0ee6b93484bdb1b54))\n- **testing.md:** removed comment of early stages ([f4ccdb9](https://github.com/softrams/bulwark/commit/f4ccdb92c1ac9dfe30f9a8241f15f7162c058a3e))\n- **testings.md:** added testing doc to run down requirements ([4dc0672](https://github.com/softrams/bulwark/commit/4dc0672e7cd267201ff3126a9363b8758e3fe037))\n\n### [3.0.2](https://github.com/softrams/bulwark/compare/v3.0.1...v3.0.2) (2020-05-28)\n\n### Docs\n\n- **seed-user.ts:** updated documentation ([1797f44](https://github.com/softrams/bulwark/commit/1797f4412cd1cfca0c8fa3ce54709a9aa81b8d34))\n\n### [3.0.1](https://github.com/softrams/bulwark/compare/v3.0.0...v3.0.1) (2020-05-28)\n\n### Bug Fixes\n\n- **seed-user.ts:** update seed user with new fields ([a7881b8](https://github.com/softrams/bulwark/commit/a7881b80d2baf4a017d1cb70661002dce65f20b8)), closes [#57](https://github.com/softrams/bulwark/issues/57)\n\n## [3.0.0](https://github.com/softrams/bulwark/compare/v2.0.1...v3.0.0) (2020-05-28)\n\n### ⚠ BREAKING CHANGES\n\n- **assessment form and report:** ManyToMany relationship has been created between the User and Assessment models.\n  API's have been updated for this change. New API's created to retrieve users.\n\n### Features\n\n- **assessment form and report:** dynamic tester association to asssment ([3bbfc9c](https://github.com/softrams/bulwark/commit/3bbfc9c4d3d3a8f801ef3691298de223cea10dcc)), closes [#52](https://github.com/softrams/bulwark/issues/52)\n\n### Bug Fixes\n\n- **angular datepipe:** added UTC property to datepipe ([701611f](https://github.com/softrams/bulwark/commit/701611f94970502b327bd29f22cd99c090892359)), closes [#3](https://github.com/softrams/bulwark/issues/3)\n\n### Code Refactoring\n\n- **assessment controller:** updated response message. Removed usrId ([c2d2555](https://github.com/softrams/bulwark/commit/c2d2555e48aa86be0e13f1778297d1cdb73efb75)), closes [#52](https://github.com/softrams/bulwark/issues/52)\n\n### [2.0.1](https://github.com/softrams/bulwark/compare/v2.0.0...v2.0.1) (2020-05-20)\n\n### Docs\n\n- **readme.md:** added new env var and fixed typo ([5ccf6ac](https://github.com/softrams/bulwark/commit/5ccf6ac022ee1012141b2518848443de04b5f19c))\n\n## 2.0.0 (2020-05-20)\n\n### ⚠ BREAKING CHANGES\n\n- **register component and api:** The register API was updated to include the missing fields. User validation was\n  also added to the Register API.\n- Update to the login API and jwt middleware\n- **patch and retrieve user:** Added three new columns for the User table: firstName, lastName, title\n\n### Features\n\n- **app interceptor:** original request now is sent after refresh ([5072fce](https://github.com/softrams/bulwark/commit/5072fce3a11dd8a8e51cfa46f6adc4d51ed843f3)), closes [#5](https://github.com/softrams/bulwark/issues/5)\n- implemented Refresh Token ([21522ac](https://github.com/softrams/bulwark/commit/21522ac7373b310629d80b584687dac3d09a646e)), closes [#5](https://github.com/softrams/bulwark/issues/5)\n- **patch and retrieve user:** implemented APIs for user patch and get ([fefd70b](https://github.com/softrams/bulwark/commit/fefd70b64c4fe38136ea3b741cb435aa637c118c)), closes [#4](https://github.com/softrams/bulwark/issues/4)\n- **user profile component:** created user profile template ([d2431af](https://github.com/softrams/bulwark/commit/d2431afcbd67f5adc7612ee100997d4d79a48fce)), closes [#4](https://github.com/softrams/bulwark/issues/4)\n\n### Bug Fixes\n\n- **register component and api:** added missing user fields to registeAPI ([6f73ef3](https://github.com/softrams/bulwark/commit/6f73ef34fa8f34f787dfa265a293301d12673dae))\n\n### Docs\n\n- **contributing.md:** modified pull request process ([8f4adce](https://github.com/softrams/bulwark/commit/8f4adce1993d873e77593b9063997a5134be3d59))\n\n### Tests\n\n- **user-profile component:** added unit tests to the user-profile comp ([91ee0c7](https://github.com/softrams/bulwark/commit/91ee0c7173e48af10ba8451000b8b0b456620b4e)), closes [#4](https://github.com/softrams/bulwark/issues/4)\n\n### Code Refactoring\n\n- **login.component:** remove console.log. Used auth service func ([30eae04](https://github.com/softrams/bulwark/commit/30eae049a9b912de729e13b8a5a93c67baa7bfc3)), closes [#5](https://github.com/softrams/bulwark/issues/5)\n\n### Others\n\n- **angular.json:** turned off google analytics ([1a849c5](https://github.com/softrams/bulwark/commit/1a849c5b0127f01805eb7444aa3668f171281d53))\n- **implemented commitizen and husky:** implented commit mgs standards ([16c3e43](https://github.com/softrams/bulwark/commit/16c3e432e20ad7f7c94a5a857631a9d4b12c8cdc))\n- **implemented husky, commitizen, and pull request template:** commit ([f14b5b2](https://github.com/softrams/bulwark/commit/f14b5b25ef2dcdc3d07c5fb899ea883cc9b51f65)), closes [#43](https://github.com/softrams/bulwark/issues/43)\n- **package.json:** installed standard version ([4390b61](https://github.com/softrams/bulwark/commit/4390b616c3176a77acd98ef2ad9d3d76c900a034)), closes [#42](https://github.com/softrams/bulwark/issues/42)\n- **package.json:** updated contributors and set version to 1 ([0910ebe](https://github.com/softrams/bulwark/commit/0910ebe1bd57bf5f3c5cb4b8b3cd066c7d864c3c))\n"
  },
  {
    "path": "CODEOWNERS",
    "content": "* @softrams/bulwark-core-team\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, sex characteristics, gender identity and expression,\nlevel of experience, education, socio-economic status, nationality, personal\nappearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or\n advances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic\n address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the project team at opensource@softrams.com. All\ncomplaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,\navailable at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see\nhttps://www.contributor-covenant.org/faq\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nWhen 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.\n\n## Contributing to Development\n\nIssues will be labelled with `help wanted` or `good first issue`\n\n- `Help wanted` label indicates tasks where the project team would appreciate community help\n- `Good first issue` label indicates a task that introduce developers to the project, have low complexity, and are isolated\n\n## Version Control\n\nThe project uses git as its version control system and GitHub as the central server and collaboration platform.\n\n### Branching model\n\nBulwark 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.\n\n### Versioning\n\nAny release from master will have a unique version automated by [standard-version](https://github.com/conventional-changelog/standard-version)\n\n`MAJOR.MINOR.PATCH` will be incremented by:\n\n1. `MAJOR` version when breaking changes occur\n2. `MINOR` version with new functionality that is backwards-compatible\n3. `PATCH` version with backwards-compatible bug fixes\n\n## Pull-Request Process\n\n1. All work must be done in a fork off the dev branch\n2. Ensure any install or build dependencies are removed\n3. 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)\n   1. Run `$ npm run commit` when committing changes\n4. The code must comply to the [testing requirements](TESTING.md)\n5. Open a Pull-Request against the `develop` branch\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM softramsdocker/bulwark-base:latest\n\nUSER root\n\n# Environment Arguments for Bulwark\nARG MYSQL_USER\nARG MYSQL_ROOT_PASSWORD\nARG MYSQL_DB_CHECK\nARG DB_PASSWORD\nARG DB_URL\nARG DB_USERNAME\nARG DB_PORT\nARG DB_NAME\nARG DB_TYPE\nARG NODE_ENV\nARG DEV_URL\nARG SERVER_ADDRESS\nARG PORT\nARG JWT_KEY\nARG JWT_REFRESH_KEY\nARG CRYPTO_SECRET\nARG CRYPTO_SALT\n\n# Stage the setup to launch Bulwark\nRUN mkdir -p /bulwark\nCOPY . /bulwark\nWORKDIR \"bulwark\"\n\n# Permissions for Bulwark\nRUN chown -R bulwark:bulwark /bulwark\n\n# DB Wait MySQL Status Up, requires mysql-client and python\nRUN apk add --no-cache --update mysql-client \\\n    python2 \n\n# Runas User\nUSER bulwark\n\n# Bulwark Specific Startup\n# Cleanup NPM to save some space\nRUN npm install \\\n    && rm -rf /bulwark/.npm \n\n# Swap to root and delete python\nUSER root\n# Clean up apk\nRUN apk del python2\n\n# Runas User\nUSER bulwark\n\n# Running Port\nEXPOSE 5000\n\n# Launch Bulwark\nCMD [\"npm\", \"run\", \"start\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Softrams LLC\n\nPermission is hereby granted, free of charge, to any person\nobtaining a copy of this software and associated documentation\nfiles (the \"Software\"), to deal in the Software without\nrestriction, including without limitation the rights to use,\ncopy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the\nSoftware is furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\nOF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\nHOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\nWHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE."
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n  <img width=\"350\" src=\"frontend/src/assets/logo.png\">\n</p>\n\n<p style=\"text-align: center;\">An organizational asset and vulnerability management tool, with Jira integration, designed for generating application security reports.</p>\n\n<p align=\"center\">\n<img src='https://img.shields.io/badge/License-MIT-yellow.svg'>\n<img src='https://github.com/softrams/bulwark/workflows/build/badge.svg'>\n<img src='https://github.com/softrams/bulwark/workflows/CodeQL/badge.svg'>\n<img src='https://img.shields.io/docker/cloud/build/softramsdocker/bulwark'>\n<img src='https://img.shields.io/docker/pulls/softramsdocker/bulwark'>\n</p>\n\n## Features\n\n- Multi-client Vulnerability Management\n- Security Report Generation\n- Jira Integration\n- Team-based Roles Authorization\n- API Key & Management\n- Email Integration\n- Markdown Support\n- User Activation/Deactivation (Admin)\n\n## Note\n\nPlease keep in mind, this project is in early development.\n\n## Demo\n\n![Bulwark Walkthrough Demo](https://github.com/softrams/media/blob/main/bulwark_report_demo.gif)\n\n## Jira Integration\n\n![Bulwark Jira Demo](https://github.com/softrams/media/blob/main/bulwark_jira_demo.gif)\n\n## Launch with Docker\n\n1. Install [Docker](https://www.docker.com/)\n2. Create a `.env` file and supply the following properties:\n\n```\nMYSQL_DATABASE=\"bulwark\"\nMYSQL_PASSWORD=\"bulwark\"\nMYSQL_ROOT_PASSWORD=\"bulwark\"\nMYSQL_USER=\"root\"\nMYSQL_DB_CHECK=\"mysql\"\nDB_PASSWORD=\"bulwark\"\nDB_URL=\"172.16.16.3\"\nDB_ROOT=\"root\"\nDB_USERNAME=\"bulwark\"\nDB_PORT=3306\nDB_NAME=\"bulwark\"\nDB_TYPE=\"mysql\"\nNODE_ENV=\"production\"\nDEV_URL=\"http://localhost:4200\"\nSERVER_ADDRESS=\"http://localhost\"\nPORT=4500\nJWT_KEY=\"changeme\"\nJWT_REFRESH_KEY=\"changeme\"\nCRYPTO_SECRET=\"changeme\"\nCRYPTO_SALT=\"changeme\"\n```\n\nBuild and start Bulwark containers:\n\n```\ndocker-compose up\n```\n\nStart/Stop Bulwark containers:\n\n```\ndocker-compose start\ndocker-compose stop\n```\n\nRemove Bulwark containers:\n\n```\ndocker-compose down\n```\n\nBulwark will be available at [localhost:4500](http://localhost:4500)\n\n## Local Installation\n\n```\n$ git clone (url)\n$ cd bulwark\n$ npm install\n```\n\nRunning `npm install` will install both server-side and client-side modules. Furthermore, it will run the script `npm run config` which will dynamically set the environment variables in addition to updating the [Angular environment](https://angular.io/guide/build).\n\n### Development Mode\n\nSet `NODE_ENV=\"development\"`\n\n```\n$ npm run config\n$ npm run start:dev\n```\n\n### Production Mode\n\nSet `NODE_ENV=\"production\"`\n_Please note: `npm install` will automatically build in production mode_\n\n```\n$ npm run config\n$ npm run build:prod\n$ npm start\n```\n\n### Environment variables\n\nCreate a `.env` file on the root directory. This will be parsed with [dotenv](https://www.npmjs.com/package/dotenv) by the application.\n\n#### `DB_PASSWORD`\n\n`DB_PASSWORD=\"somePassword\"`\n\nSet this variable to database password\n\n#### `DB_USERNAME`\n\n`DB_USERNAME=\"foobar\"`\n\nSet this variable to database user name\n\n#### `DB_URL`\n\n`DB_URL=something-foo-bar.dbnet`\n\nSet this variable to database URL\n\n#### `DB_PORT`\n\n`DB_PORT=3306`\n\nSet this variable to database port\n\n#### `DB_NAME`\n\n`DB_NAME=\"foobar\"`\n\nSet this variable to database connection name\n\n#### `DB_TYPE`\n\n`DB_TYPE=\"mysql\"`\n\nThe application was developed using a MySQL database. See the [typeorm](https://github.com/typeorm/typeorm/blob/master/docs/connection-options.md#common-connection-options) documentation for more database options.\n\n#### `NODE_ENV`\n\n`NODE_ENV=production`\n\nSet this variable to determine node environment\n\n#### `DEV_URL=\"http://localhost:4200\"`\n\nUsed by Angular to build and serve the application\n\n#### `SERVER_ADDRESS=\"http://localhost\"`\n\nUpdate if a different server address is required\n\n#### `PORT=4500`\n\nUpdate if a different server port is required\n\n#### `JWT_KEY`\n\n`JWT_KEY=\"changeMe\"`\n\nSet this variable to the JWT secret\n\n#### `JWT_REFRESH_KEY`\n\n`JWT_REFRESH_KEY=\"changeMe\"`\n\nSet this variable to the refresh JWT secret\n\n#### `CRYPTO_SECRET`\n\n`CRYPTO_SECRET=\"randomValue\"`\n\nSet this variable to the [Scrypt](https://nodejs.org/api/crypto.html#crypto_crypto_scryptsync_password_salt_keylen_options) password.\n\n#### `CRYPTO_SALT`\n\n`CRYPTO_SECRET=\"randomValue\"`\n\nSet this variable to the [Scrypt](https://nodejs.org/api/crypto.html#crypto_crypto_scryptsync_password_salt_keylen_options) salt.\n\n### Empty `.env` file template\n\n```\nDB_PASSWORD=\"\"\nDB_URL=\"\"\nDB_USERNAME=\"\"\nDB_PORT=3306\nDB_NAME=\"\"\nDB_TYPE=\"\"\nNODE_ENV=\"\"\nDEV_URL=\"http://localhost:4200\"\nSERVER_ADDRESS=\"http://localhost\"\nPORT=4500\nJWT_KEY=\"\"\nJWT_REFRESH_KEY=\"\"\nCRYPTO_SECRET=\"\"\nCRYPTO_SALT=\"\"\n```\n\n### Note on M1/M2 Macs\n```\nInstall sqlite3: \nbrew install sqlite3\n\nExport compiler related env variables: \nexport LDFLAGS=\"-L/opt/homebrew/opt/sqlite/lib\"\nexport CPPFLAGS=\"-I/opt/homebrew/opt/sqlite/include\"\nexport PKG_CONFIG_PATH=\"/opt/homebrew/opt/sqlite/lib/pkgconfig\"\nexport NODE_OPTIONS=--openssl-legacy-provider\n\nPrepare for a fresh install:\nrm -rf node_modules\nnpm cache verify\nnpm i --force\n```\n\n### Create Initial Database Migration\n\n1. Create the initial database migration\n\n```\n$ npm run migration:init\n```\n\n2. Run the initial database migration\n\n```\n$ npm run migration:run\n```\n\n## Default credentials\n\nA user account is created on initial startup with the following credentials:\n\n- email: `admin@example.com`\n- password: `changeMe`\n\nUpon first login, update the default user password under the profile section.\n\n## Roles\n\nThe application utilizes least privilege access with team-based authorization. Teams are assigned a role which determines the features available to that specific team. A user will inherit roles from team membership. Administrators have team management access and must assign users to teams. Initially, users are created with no team association and will not have access to any features in the application.\n\nThe three roles include:\n\n1. Admin\n2. Tester\n3. Read-Only\n\nA team can only be associated to a single organization. However, a team can be associated to multiple assets within the same organization. A user can be a member of multiple teams. If a user is assigned to multiple teams of the same organization, the system will choose the highest authorized team.\n\n_Please note: The default user is automatically assigned to the `Administrators` team on initial startup_\n\n### Role Matrix\n\n<table>\n  <tr>\n    <td></td>\n    <th scope=\"col\">Admin</th>\n    <th scope=\"col\">Tester</th>\n    <th scope=\"col\">Read-Only</th>\n  </tr>\n  <tr>\n    <th scope=\"row\">User-Profile Management</th>\n    <td>x</td>\n    <td>x</td>\n    <td>x</td>\n  </tr>\n  <tr>\n    <th scope=\"row\">Team Management</th>\n    <td>x</td>\n    <td></td>\n    <td></td>\n  </tr> \n  <tr>\n    <th scope=\"row\">User Management</th>\n    <td>x</td>\n    <td></td>\n    <td></td>\n  </tr> \n  <tr>\n    <th scope=\"row\">Invite User</th>\n    <td>x</td>\n    <td></td>\n    <td></td>\n  </tr> \n  <tr>\n    <th scope=\"row\">Create User</th>\n    <td>x</td>\n    <td></td>\n    <td></td>\n  </tr> \n  <tr>\n    <th scope=\"row\">Email Settings Management</th>\n    <td>x</td>\n    <td></td>\n    <td></td>\n  </tr> \n  <tr>\n    <th scope=\"row\">Jira Integration</th>\n    <td>x</td>\n    <td></td>\n    <td></td>\n  </tr> \n  <tr>\n    <th scope=\"row\">Organization: Read</th>\n    <td>x</td>\n    <td>x</td>\n    <td>x</td>\n  </tr> \n  <tr>\n    <th scope=\"row\">Organization: Full Write</th>\n    <td>x</td>\n    <td></td>\n    <td></td>\n  </tr> \n  <tr>\n    <th scope=\"row\">Asset: Read</th>\n    <td>x</td>\n    <td>x</td>\n    <td>x</td>\n  </tr> \n  <tr>\n    <th scope=\"row\">Asset: Full Write</th>\n    <td>x</td>\n    <td></td>\n    <td></td>\n  </tr> \n  <tr>\n    <th scope=\"row\">Assessment: Read</th>\n    <td>x</td>\n    <td>x</td>\n    <td>x</td>\n  </tr> \n  <tr>\n    <th scope=\"row\">Assessment: Full Write</th>\n    <td>x</td>\n    <td>x</td>\n    <td></td>\n  </tr> \n  <tr>\n    <th scope=\"row\">Vulnerability: Read</th>\n    <td>x</td>\n    <td>x</td>\n    <td>x</td>\n  </tr> \n  <tr>\n    <th scope=\"row\">Vulnerability: Full Write</th>\n    <td>x</td>\n    <td>x</td>\n    <td></td>\n  </tr> \n  <tr>\n    <th scope=\"row\">Export Vulnerability to Jira</th>\n    <td>x</td>\n    <td>x</td>\n    <td></td>\n  </tr> \n  <tr>\n    <th scope=\"row\">Report Generation</th>\n    <td>x</td>\n    <td>x</td>\n    <td>x</td>\n  </tr> \n</table>\n\n<br>\n\n## API Key & Management\n\nA user may generate a single API key which can be used in place of their authorization token. This API key allows for all actions against the application that the user is authorized for.\n\n### Generating an API key pair\n\n1. Login to the application\n2. Navigate to the `User Profile` section\n3. Select `Generate API Key`\n\nThis action will generate a pair of keys:\n\n1. `Bulwark-Api-Key`\n   1. This is a generated plaintext value to identify the user.\n2. `Bulwark-Secret-Key`\n   1. This is a generated plaintext value to verify the user by comparing a [Bcrypt](https://www.npmjs.com/package/bcrypt) hash stored in the database.\n\n<strong>Write down the generated keys in a safe place. You will not be able to retrieve the keys at a later time.</strong>\n\n### How to use API keys\n\nThe API key pair values must be matched and appended to the following HTTP request headers:\n\n- `Bulwark-Api-Key`\n- `Bulwark-Secret-Key`\n\nExample:\n\n```\nGET /api/assessment/1 HTTP/1.1\nHost: localhost:4500\nAccept: application/json, text/plain, */*\nAccept-Language: en-US,en;q=0.5\nAccept-Encoding: gzip, deflate\nBulwark-Api-Key: {{changeMe}}\nBulwark-Secret-Key: {{changeMe}}\nOrigin: http://localhost:4200\nConnection: close\nReferer: http://localhost:4200/\nPragma: no-cache\nCache-Control: no-cache\n```\n\n## Built With\n\n- [Typeorm](https://typeorm.io/#/) - The ORM used\n- [Angular](https://angular.io/) - The Angular Framework\n- [Express](https://expressjs.com/) - A minimal and flexible Node.js web application framework\n\n## Contributing\n\nPull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. Read the [contribution guidelines](CONTRIBUTING.md) for more information.\n\n## License\n\n[MIT](https://choosealicense.com/licenses/mit/)\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\nSecurity patches will be included in the latest released version.\n\n## Reporting a Vulnerability\n\nFor any vulnerabilities found within Bulwark, please contact <a href=\"mailto:oss-security-reports@softrams.com\">oss-security-reports@softrams.com</a>\n\n### Description\n\nDescribe the nature of the vulnerability.\n\n### Problem Locations\n\nDescribe where the in the application the vulnerability occurs. If possible, add the affected code. For ex:\n\n| Problem Location            | Target             |\n| --------------------------- | ------------------ |\n| foo/bar/admin.js            | Line 101, 106, 200 |\n| localhost:5000/#/admin/{id} | id                 |\n\n### Remediation (optional)\n\nDescribe any suggestions on how to fix the vulnerability.\n"
  },
  {
    "path": "TESTING.md",
    "content": "# Testing Requirements\n\n1. All new and updated **back-end** code should have a corresponding unit tests\n2. All new and existing unit tests should pass locally before opening a Pull-Request\n3. Linting should pass locally before opening a Pull-Request\n\n## Running Front-End Unit Tests (Not Currently Required)\n\n- The Angular unit tests have **not** been implemented so please skip this section\n- Run `npm run test:front` to execute the unit tests via [Karma](https://karma-runner.github.io)\n- Follow the recommended guidelines for Front-End [Testing](https://angular.io/guide/testing)\n\n## Running Back-End Unit Tests\n\n- Run `npm run test:node` to execute the unit tests via [Jest](https://jestjs.io/)\n- Test coverage report can be opened in browser located in: `coverage/lcov-report/index.html`\n- Follow the recommended guidelines for Back-End [Testing](https://jestjs.io/)\n\n## Running All Tests\n\n- Run `npm run test` to execute the unit tests.\n\n## Linting\n\n```\nnpm run lint\n```\n\nIn 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.\n\n### Prettier\n\nBulwark 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.\n"
  },
  {
    "path": "babel.config.js",
    "content": "module.exports = {\n    presets: [\n        ['@babel/preset-env', {targets: {node: 'current'}}],\n        '@babel/preset-typescript',\n    ],\n};\n"
  },
  {
    "path": "buildspec.yaml",
    "content": "version: 0.2\nphases:\n  pre_build:\n    on-failure: ABORT\n    commands:\n      - echo Logging in to Docker Hub...\n      - docker login --username ${DOCKERHUB_USER} --password ${DOCKERHUB_TOKEN}\n      - |\n        if [ -z $CODEBUILD_WEBHOOK_TRIGGER ]\n        then\n         TAG=\"latest\"\n        else\n          if [ \"${CODEBUILD_WEBHOOK_TRIGGER}\" = \"branch/master\" ]\n          then\n            TAG=\"latest\"\n          else\n            echo \"in else ${CODEBUILD_WEBHOOK_TRIGGER}\"\n            TAG=${CODEBUILD_WEBHOOK_TRIGGER/branch\\//}\n          fi\n        fi\n      - echo $TAG\n  build:\n    on-failure: ABORT\n    commands:\n      - echo Build started on `date`\n      - echo Building the Docker image...\n      - echo \"docker build -t softramsdocker/bulwark:${TAG} .\"\n      - docker build -t softramsdocker/bulwark:${TAG} .\n  post_build:\n    commands:\n      - echo Build completed on `date`\n      - echo Pushing the Docker image...\n      - docker push softramsdocker/bulwark:${TAG}"
  },
  {
    "path": "bulwark_base/Dockerfile",
    "content": "# Softrams - Bulwark Reporting Application Dockerized Configuration\n# Maintained by Bill Jones\n\n# Start from Alpine Linux for smaller footprint\nFROM alpine:latest\n\n# Environmental Items\nENV NODE_VERSION=20.14.0 \\\n    TYPESCRIPT_VERSION=10.9.2 \\\n    PUPPETEER_VERSION=23.2.0 \\\n    PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \\\n    PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser\n\n# View template at https://github.com/nodejs/docker-node/blob/master/Dockerfile-alpine.template\n# Setup Bulwark Container User\nRUN addgroup -S bulwark && adduser -S bulwark -G bulwark -s /bin/sh -D bulwark\n\n# Update Image\nRUN apk upgrade --no-cache -U\n\n# Install Required Packages to Build NodeJS and Puppeter Items\nRUN apk add --no-cache --virtual .build-deps-full curl make gcc g++ python3 linux-headers binutils-gold gnupg libstdc++ chromium \\\n fontconfig udev ttf-freefont fontconfig pango-dev libxcursor libxdamage cups-libs dbus-libs libxrandr \\\n libxscrnsaver libc6-compat nss freetype freetype-dev harfbuzz ca-certificates libgcc py-setuptools\n\n# Ingest the GPG Keys from https://github.com/nodejs/node#release-keys\nRUN for server in keys.openpgp.org pool.sks-keyservers.net keyserver.pgp.com ha.pool.sks-keyservers.net; do \\\n    gpg --keyserver $server --recv-keys \\\n      4ED778F539E3634C779C87C6D7062848A1AB005C \\\n      141F07595B7B3FFE74309A937405533BE57C7D57 \\\n      74F12602B6F1C4E913FAA37AD3A89613643B6201 \\\n      DD792F5973C6DE52C432CBDAC77ABFA00DDBF2B7 \\\n      CC68F5A3106FF448322E48ED27F5E38D5B0A215F \\\n      8FCCA13FEF1D0C2E91008E09770F7A9A5AE15600 \\\n      890C08DB8579162FEE0DF9DB8BEAB4DFCF555EF4 \\\n      C82FA3AE1CBEDC6BE46B9360C43CEC45C17AB93C \\\n      108F52B48DB57BB0CC439B2997B01419BD92F80A \\\n      A363A499291CBBC940DD62E41F10027AF002F8B0 && break; \\\n  done\n\n# Perform nodejs installation\n# https://nodejs.org/dist/v14.9.0/node-v14.9.0.tar.gz - URL for Nodejs Source Code\nRUN curl -fsSLO --compressed \"https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION.tar.gz\" \\\n && curl -fsSLO \"https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt\" \\\n && curl -fsSLO \"https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.sig\" \\\n #&& gpg --verify SHASUMS256.txt.sig SHASUMS256.txt \\\n && grep \" node-v$NODE_VERSION.tar.gz\\$\" SHASUMS256.txt | sha256sum -c - \\\n && tar -xf \"node-v$NODE_VERSION.tar.gz\" \\\n && cd \"node-v$NODE_VERSION\" \\\n && ./configure \\\n && make -j$(getconf _NPROCESSORS_ONLN) V= \\\n && make install \\\n && apk del .build-deps-full \\\n && cd .. \\\n && rm -Rf \"node-v$NODE_VERSION\" \\\n && rm \"node-v$NODE_VERSION.tar.gz\" SHASUMS256.txt.sig SHASUMS256.txt \\\n# Cleanup\nRUN rm -f \"node-v$NODE_VERSION\" \\\n  # smoke tests\n  && node --version \\\n  && npm --version \\\n# Setup for launch control of Bulwark\nWORKDIR /\nCOPY bulwark-entrypoint /usr/local/bin/\n\nENTRYPOINT [\"bulwark-entrypoint\"]\n"
  },
  {
    "path": "bulwark_base/buildspec.yaml",
    "content": "version: 0.2\nphases:\n  pre_build:\n    commands:\n      - echo Logging in to Docker Hub...\n      - docker login --username ${DOCKERHUB_USER} --password ${DOCKERHUB_TOKEN}\n  build:\n    on-failure: ABORT\n    commands:\n      - echo Build started on `date`\n      - echo Building the Docker image...\n      - cd bulwark_base\n      - docker build -t softramsdocker/bulwark-base:latest .\n  post_build:\n    commands:\n      - echo Build completed on `date`\n      - echo Pushing the Docker images...\n      - docker push softramsdocker/bulwark-base:latest"
  },
  {
    "path": "bulwark_base/bulwark-entrypoint",
    "content": "#!/bin/sh\n\nexec \"$@\"\n"
  },
  {
    "path": "commitlint.config.js",
    "content": "module.exports = {\n  extends: ['@commitlint/config-conventional']\n};\n"
  },
  {
    "path": "docker-compose.yml",
    "content": "version: '3.8'\nservices:\n  bulwark:\n    image: softramsdocker/bulwark:latest\n    container_name: bulwark\n    environment:\n      MYSQL_USER: '${MYSQL_USER}'\n      MYSQL_ROOT_PASSWORD: '${MYSQL_ROOT_PASSWORD}'\n      MYSQL_DB_CHECK: '${MYSQL_DB_CHECK}'\n      DB_PASSWORD: '${DB_PASSWORD}'\n      DB_URL: '${DB_URL}'\n      DB_USERNAME: '${DB_USERNAME}'\n      DB_PORT: '${DB_PORT}'\n      DB_NAME: '${DB_NAME}'\n      DB_TYPE: '${DB_TYPE}'\n      NODE_ENV: '${NODE_ENV}'\n      DEV_URL: '${DEV_URL}'\n      SERVER_ADDRESS: '${SERVER_ADDRESS}'\n      PORT: '${PORT}'\n      JWT_KEY: '${JWT_KEY}'\n      JWT_REFRESH_KEY: '${JWT_REFRESH_KEY}'\n      CRYPTO_SECRET: '${CRYPTO_SECRET}'\n      CRYPTO_SALT: '${CRYPTO_SALT}'\n    depends_on:\n      - bulwark-db\n    networks:\n      static-network:\n        ipv4_address: 172.16.16.2\n    ports:\n      - '5000:5000'\n    expose:\n      - '5000'\n    stop_grace_period: 1m\n    volumes:\n      - bulwark-temp:/bulwark/src/temp:rw\n    command: >\n      sh -c \"\n      until mysql --host=$${DB_URL} --user=$${MYSQL_USER} --password=$${MYSQL_ROOT_PASSWORD} --database=$${MYSQL_DB_CHECK} -e 'SELECT user FROM user;'; do\n        >&2 echo MySQL is unavailable - sleeping\n        sleep 1\n      done\n      && echo MySQL should be up - starting up Bulwark.\n      && npm run postinstall\n      && echo Initial DB Creation\n      && npm run docker:check\n      && npm run start\"\n\n  bulwark-db:\n    image: mysql:9.0.1  # 7.7.31\n    container_name: bulwark_db\n    environment:\n      MYSQL_DATABASE: '${DB_NAME}'\n      MYSQL_USER: '${DB_USERNAME}'\n      MYSQL_PASSWORD: '${DB_PASSWORD}'\n      MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}'\n    networks:\n      static-network:\n        ipv4_address: 172.16.16.3\n    ports:\n      - '3306:3306'\n    expose:\n      - '3306'\n    volumes:\n      - bulwark-db:/var/lib/mysql:rw\n    restart: always\n\nvolumes:\n  bulwark-db:\n  bulwark-temp:\n\nnetworks:\n  static-network:\n    ipam:\n      config:\n        - subnet: 172.16.16.0/29\n"
  },
  {
    "path": "frontend/.browserslistrc",
    "content": "# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n#\n# For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed\n\n> 0.5%\nlast 2 versions\nFirefox ESR\nnot dead\nnot IE 9-11"
  },
  {
    "path": "frontend/.editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\nquote_type = single\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "frontend/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# compiled output\n/dist\n/tmp\n/out-tsc\n# Only exists if Bazel was run\n/bazel-out\n/src/environments\n\n# dependencies\n/node_modules\n\n# profiling files\nchrome-profiler-events.json\nspeed-measure-plugin.json\n\n# IDEs and editors\n/.idea\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# IDE - VSCode\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# misc\n/.sass-cache\n/connect.lock\n/coverage\n/libpeerconnection.log\nnpm-debug.log\nyarn-error.log\ntestem.log\n/typings\n\n# System Files\n.DS_Store\nThumbs.db\n\n"
  },
  {
    "path": "frontend/README.md",
    "content": "# Frontend\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 7.3.5.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).\n\n## Further help\n\nTo 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).\n"
  },
  {
    "path": "frontend/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"frontend\": {\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"projectType\": \"application\",\n      \"prefix\": \"app\",\n      \"schematics\": {\n        \"@schematics/angular:component\": {\n          \"style\": \"sass\"\n        }\n      },\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"outputPath\": \"dist/frontend\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"src/tsconfig.app.json\",\n            \"assets\": [\"src/favicon.ico\", \"src/assets\"],\n            \"styles\": [\n              \"src/styles.scss\",\n              \"./node_modules/prismjs/themes/prism-okaidia.css\",\n              \"./node_modules/primeng/resources/themes/saga-blue/theme.css\",\n              \"./node_modules/primeng/resources/primeng.min.css\",\n              \"./node_modules/primeicons/primeicons.css\",\n              \"./node_modules/primeflex/primeflex.css\"\n            ],\n            \"scripts\": [\n              \"./node_modules/marked/bin/marked.js\",\n              \"./node_modules/prismjs/prism.js\"\n            ],\n            \"vendorChunk\": true,\n            \"extractLicenses\": false,\n            \"buildOptimizer\": false,\n            \"sourceMap\": true,\n            \"optimization\": false,\n            \"namedChunks\": true\n          },\n          \"configurations\": {\n            \"development\": {\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.dev.ts\"\n                }\n              ]\n            },\n            \"production\": {\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"namedChunks\": false,\n              \"extractLicenses\": true,\n              \"vendorChunk\": false,\n              \"buildOptimizer\": true,\n              \"budgets\": [\n                {\n                  \"type\": \"initial\",\n                  \"maximumWarning\": \"2mb\",\n                  \"maximumError\": \"5mb\"\n                },\n                {\n                  \"type\": \"anyComponentStyle\",\n                  \"maximumWarning\": \"6kb\"\n                }\n              ]\n            }\n          }\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"options\": {\n            \"browserTarget\": \"frontend:build\"\n          },\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"frontend:build:production\"\n            },\n            \"development\": {\n              \"browserTarget\": \"frontend:build:development\"\n            }\n          }\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"frontend:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"src/tsconfig.spec.json\",\n            \"karmaConfig\": \"src/karma.conf.js\",\n            \"styles\": [\"src/styles.scss\"],\n            \"scripts\": [],\n            \"assets\": [\"src/favicon.ico\", \"src/assets\"]\n          }\n        },\n        \"lint\": {\n          \"builder\": \"@angular-devkit/build-angular:tslint\",\n          \"options\": {\n            \"tsConfig\": [\"src/tsconfig.app.json\", \"src/tsconfig.spec.json\"],\n            \"exclude\": [\"**/node_modules/**\"]\n          }\n        }\n      }\n    },\n    \"frontend-e2e\": {\n      \"root\": \"e2e/\",\n      \"projectType\": \"application\",\n      \"prefix\": \"\",\n      \"architect\": {\n        \"e2e\": {\n          \"builder\": \"@angular-devkit/build-angular:protractor\",\n          \"options\": {\n            \"protractorConfig\": \"e2e/protractor.conf.js\",\n            \"devServerTarget\": \"frontend:serve\"\n          },\n          \"configurations\": {\n            \"production\": {\n              \"devServerTarget\": \"frontend:serve:production\"\n            }\n          }\n        },\n        \"lint\": {\n          \"builder\": \"@angular-devkit/build-angular:tslint\",\n          \"options\": {\n            \"tsConfig\": \"e2e/tsconfig.e2e.json\",\n            \"exclude\": [\"**/node_modules/**\"]\n          }\n        }\n      }\n    }\n  },\n  \"defaultProject\": \"frontend\",\n  \"cli\": {\n    \"analytics\": false\n  }\n}\n"
  },
  {
    "path": "frontend/e2e/protractor.conf.js",
    "content": "// Protractor configuration file, see link for more information\n// https://github.com/angular/protractor/blob/master/lib/config.ts\n\nconst { SpecReporter } = require('jasmine-spec-reporter');\n\nexports.config = {\n  allScriptsTimeout: 11000,\n  specs: [\n    './src/**/*.e2e-spec.ts'\n  ],\n  capabilities: {\n    'browserName': 'chrome'\n  },\n  directConnect: true,\n  baseUrl: 'http://localhost:4200/',\n  framework: 'jasmine',\n  jasmineNodeOpts: {\n    showColors: true,\n    defaultTimeoutInterval: 30000,\n    print: function() {}\n  },\n  onPrepare() {\n    require('ts-node').register({\n      project: require('path').join(__dirname, './tsconfig.e2e.json')\n    });\n    jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));\n  }\n};"
  },
  {
    "path": "frontend/e2e/src/app.e2e-spec.ts",
    "content": "import { AppPage } from './app.po';\nimport { browser, logging } from 'protractor';\n\ndescribe('workspace-project App', () => {\n  let page: AppPage;\n\n  beforeEach(() => {\n    page = new AppPage();\n  });\n\n  it('should display welcome message', () => {\n    page.navigateTo();\n    expect(page.getTitleText()).toEqual('Welcome to frontend!');\n  });\n\n  afterEach(async () => {\n    // Assert that there are no errors emitted from the browser\n    const logs = await browser.manage().logs().get(logging.Type.BROWSER);\n    expect(logs).not.toContain(jasmine.objectContaining({\n      level: logging.Level.SEVERE,\n    } as logging.Entry));\n  });\n});\n"
  },
  {
    "path": "frontend/e2e/src/app.po.ts",
    "content": "import { browser, by, element } from 'protractor';\n\nexport class AppPage {\n  navigateTo() {\n    return browser.get(browser.baseUrl) as Promise<any>;\n  }\n\n  getTitleText() {\n    return element(by.css('app-root h1')).getText() as Promise<string>;\n  }\n}\n"
  },
  {
    "path": "frontend/e2e/tsconfig.e2e.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"module\": \"commonjs\",\n    \"target\": \"es6\",\n    \"types\": [\n      \"jasmine\",\n      \"jasminewd2\",\n      \"node\"\n    ]\n  }\n}"
  },
  {
    "path": "frontend/package.json",
    "content": "{\n  \"name\": \"frontend\",\n  \"version\": \"1.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"ng serve\",\n    \"start:prod\": \"ng serve -c production\",\n    \"start:dev\": \"ng serve -c development\",\n    \"build:prod\": \"ng build --configuration=production\",\n    \"build:dev\": \"ng build --configuration=development\",\n    \"test\": \"ng test\",\n    \"lint\": \"ng lint\",\n    \"e2e\": \"ng e2e\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular-devkit/build-angular\": \"~18.0.1\",\n    \"@angular/animations\": \"~18.0.1\",\n    \"@angular/cdk\": \"^18.0.1\",\n    \"@angular/cli\": \"^18.0.1\",\n    \"@angular/common\": \"~18.0.1\",\n    \"@angular/compiler\": \"~18.0.1\",\n    \"@angular/compiler-cli\": \"~18.0.1\",\n    \"@angular/core\": \"~18.0.1\",\n    \"@angular/forms\": \"~18.0.1\",\n    \"@angular/platform-browser\": \"~18.0.1\",\n    \"@angular/platform-browser-dynamic\": \"~18.0.1\",\n    \"@angular/router\": \"~18.0.1\",\n    \"@ng-select/ng-select\": \"^13.2.0\",\n    \"chart.js\": \"^3.6.0\",\n    \"core-js\": \"^3.19.0\",\n    \"jwt-decode\": \"^3.1.2\",\n    \"ngx-markdown\": \"^18.0.0\",\n    \"primeflex\": \"^3.1.0\",\n    \"primeicons\": \"^5.0.0\",\n    \"primeng\": \"^17.18.0\",\n    \"rxjs\": \"~7.8.1\",\n    \"tslib\": \"^2.3.1\",\n    \"zone.js\": \"~0.14.6\"\n  },\n  \"devDependencies\": {\n    \"@angular/language-service\": \"~18.0.1\",\n    \"@types/jasmine\": \"~3.10.1\",\n    \"@types/jasminewd2\": \"^2.0.10\",\n    \"@types/node\": \"20.12.13\",\n    \"codelyzer\": \"^6.0.2\",\n    \"jasmine-core\": \"~4.0.1\",\n    \"jasmine-spec-reporter\": \"~7.0.0\",\n    \"karma\": \"~6.3.16\",\n    \"karma-chrome-launcher\": \"~3.1.0\",\n    \"karma-coverage-istanbul-reporter\": \"~3.0.2\",\n    \"karma-jasmine\": \"~4.0.0\",\n    \"karma-jasmine-html-reporter\": \"^1.7.0\",\n    \"protractor\": \"~7.0.0\",\n    \"ts-node\": \"~10.9.2\",\n    \"tslint\": \"~6.1.0\",\n    \"typescript\": \"~5.4.5\"\n  }\n}\n"
  },
  {
    "path": "frontend/src/app/admin.guard.spec.ts",
    "content": "import { TestBed } from '@angular/core/testing';\n\nimport { AdminGuard } from './admin.guard';\n\ndescribe('AdminGuard', () => {\n  let guard: AdminGuard;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({});\n    guard = TestBed.inject(AdminGuard);\n  });\n\n  it('should be created', () => {\n    expect(guard).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/admin.guard.ts",
    "content": "import { Injectable } from '@angular/core';\nimport {\n  CanActivate,\n  ActivatedRouteSnapshot,\n  RouterStateSnapshot,\n  UrlTree,\n  Router,\n} from '@angular/router';\nimport { Observable } from 'rxjs';\nimport { AuthService } from './auth.service';\nimport { GlobalManagerService } from './global-manager.service';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class AdminGuard implements CanActivate {\n  constructor(\n    private globalManager: GlobalManagerService,\n    private router: Router,\n    private authService: AuthService\n  ) {}\n  canActivate(\n    next: ActivatedRouteSnapshot,\n    state: RouterStateSnapshot\n  ):\n    | Observable<boolean | UrlTree>\n    | Promise<boolean | UrlTree>\n    | boolean\n    | UrlTree {\n    return this.checkLogin();\n  }\n\n  checkLogin(): boolean {\n    if (localStorage.getItem('AUTH_TOKEN')) {\n      this.globalManager.showLogin(true);\n      if (this.authService.isAdmin()) {\n        return true;\n      } else {\n        return false;\n      }\n    } else {\n      // Navigate to the login page with extras\n      this.router.navigate(['/login']);\n      this.globalManager.showLogin(false);\n      return false;\n    }\n  }\n}\n"
  },
  {
    "path": "frontend/src/app/administration/administration.component.html",
    "content": "<h5>Administration</h5>\n<div class=\"container col-12 col-md-8 mx-auto\">\n  <app-settings></app-settings>\n  <br>\n  <app-user-management></app-user-management>\n  <br>\n  <app-apikey-management></app-apikey-management>\n  <br>\n  <app-team></app-team>\n</div>\n"
  },
  {
    "path": "frontend/src/app/administration/administration.component.sass",
    "content": "h5\n\ttext-align: center\n"
  },
  {
    "path": "frontend/src/app/administration/administration.component.spec.ts",
    "content": "import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';\n\nimport { AdministrationComponent } from './administration.component';\n\ndescribe('AdministrationComponent', () => {\n  let component: AdministrationComponent;\n  let fixture: ComponentFixture<AdministrationComponent>;\n\n  beforeEach(waitForAsync(() => {\n    TestBed.configureTestingModule({\n      declarations: [ AdministrationComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(AdministrationComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/administration/administration.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { InviteUserComponent } from './invite-user/invite-user.component';\nimport { SettingsComponent } from './settings/settings.component';\n\n\n@Component({\n  selector: 'app-administration',\n  templateUrl: './administration.component.html',\n  styleUrls: ['./administration.component.sass']\n})\nexport class AdministrationComponent implements OnInit {\n\n  constructor() { }\n\n  ngOnInit() {\n  }\n\n}\n"
  },
  {
    "path": "frontend/src/app/administration/invite-user/invite-user.component.html",
    "content": "<div class=\"container col-6 align-self-center\">\n  <p-card>\n    <div class=\"card-body\">\n      <h6>Invite a User</h6>\n      <div id=\"formContent\">\n        <form [formGroup]=\"inviteForm\" (ngSubmit)=\"onSubmit(inviteForm)\" id=\"userForm\">\n          <div class=\"col-12\">\n            <div class=\"p-d-flex p-flex-column p-flex-md-row\">\n              <label for=\"inviteUser\" class=\"col-12 col-md-4 col-form-label\">User Email:</label>\n              <div class=\"col-12 col-md-8\">\n                <input pInputText formControlName=\"email\" type=\"text\" class=\"fill-width\" id=\"email\" class=\"form-control\"\n                  name=\"email\" placeholder=\"Email\" style=\"margin-bottom: 5px;\" />\n              </div>\n            </div>\n          </div>\n          <input pInputText [disabled]=\"!inviteForm.valid\" type=\"submit\" class=\"btn btn-primary float-right\"\n            value=\"Invite\" />\n          <br />\n        </form>\n      </div>\n    </div>\n  </p-card>\n</div>\n"
  },
  {
    "path": "frontend/src/app/administration/invite-user/invite-user.component.sass",
    "content": ".centerBtn\n\tdisplay: flex\n\tjustify-content: center\n\talign-items: center\n\theight: 40px\n\twidth: 75px\n\tborder: 3px solid green\n\n.fill-width\n\tflex: 1\n"
  },
  {
    "path": "frontend/src/app/administration/invite-user/invite-user.component.spec.ts",
    "content": "import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';\n\nimport { InviteUserComponent } from './invite-user.component';\n\ndescribe('InviteUserComponent', () => {\n  let component: InviteUserComponent;\n  let fixture: ComponentFixture<InviteUserComponent>;\n\n  beforeEach(waitForAsync(() => {\n    TestBed.configureTestingModule({\n      declarations: [ InviteUserComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(InviteUserComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/administration/invite-user/invite-user.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormGroup, FormBuilder, Validators } from '@angular/forms';\nimport { UserService } from '../../user.service';\nimport { Router } from '@angular/router';\nimport { AlertService } from '../../alert/alert.service';\n\n@Component({\n  selector: 'app-invite-user',\n  templateUrl: './invite-user.component.html',\n  styleUrls: ['./invite-user.component.sass'],\n})\nexport class InviteUserComponent implements OnInit {\n  inviteForm: FormGroup;\n  constructor(\n    private fb: FormBuilder,\n    public userService: UserService,\n    public router: Router,\n    public alertService: AlertService\n  ) {\n    this.createForm();\n  }\n\n  ngOnInit(): void {}\n\n  createForm() {\n    this.inviteForm = this.fb.group({\n      email: ['', [Validators.required, Validators.email]],\n    });\n  }\n\n  onSubmit(form) {\n    const email = { email: form.value.email };\n    this.userService.inviteUser(email).subscribe((res: string) => {\n      this.router.navigate(['administration']);\n      this.alertService.success(res);\n    });\n  }\n}\n"
  },
  {
    "path": "frontend/src/app/administration/settings/settings.component.html",
    "content": "<p-card>\n  <div class=\"card-body\">\n    <h4>Application Email Settings</h4>\n    <hr />\n    <div id=\"formContent\">\n      <form [formGroup]=\"settingsForm\" (ngSubmit)=\"onSubmit(settingsForm)\" id=\"userForm\">\n        <div class=\"col-12\">\n          <div class=\"p-d-flex p-flex-column\">\n            <div class=\"p-d-flex p-flex-column\">\n              <span>\n                <i class=\"pi pi-info-circle\"></i><i style=\"font-size: smaller;\"> An app password is a\n                  16-digit passcode that gives a\n                  non-Google app or device permission to access your Google Account. Please follow this <a\n                    href=\"https://support.google.com/mail/answer/185833?hl=en-GB\">link</a> for more\n                  information.</i>\n              </span>\n            </div>\n            <div class=\"p-d-flex p-flex-column p-flex-md-row\">\n              <label for=\"fromEmail\" class=\"col-12 col-md-4 col-form-label\">Gmail Account:</label>\n              <div class=\"col-12 col-md-8\">\n                <input pInputText formControlName=\"fromEmail\" type=\"text\" id=\"fromEmail\" class=\"form-control\"\n                  name=\"fromEmail\" placeholder=\"Email\" style=\"margin-bottom: 5px;\" pInputText />\n              </div>\n            </div>\n\n            <div class=\"p-d-flex p-flex-column p-flex-md-row\">\n              <label for=\"fromEmailPassword\" class=\"col-12 col-md-4 col-form-label\">Gmail App Password:</label>\n              <div class=\"col-12 col-md-8\">\n                <input pInputText formControlName=\"fromEmailPassword\" type=\"password\" id=\"fromEmailPassword\"\n                  class=\"form-control\" name=\"fromEmailPassword\" [placeholder]=\"isEdit ? '':keyPlaceholder\"\n                  style=\"margin-bottom: 5px;\" pInputText />\n              </div>\n            </div>\n          </div>\n          <div class=\"p-d-flex p-flex-column p-flex-md-row\">\n            <label for=\"companyName\" class=\"col-12 col-md-4 col-form-label\">Company Name:</label>\n            <div class=\"col-12 col-md-8\">\n              <input pInputText formControlName=\"companyName\" type=\"text\" id=\"companyName\" class=\"form-control\"\n                name=\"companyName\" placeholder=\"Company Name\" style=\"margin-bottom: 5px;\" pInputText />\n            </div>\n          </div>\n        </div>\n        <input pInputText [disabled]=\"isEdit ? !settingsForm.valid : false\" type=\"submit\"\n          class=\"btn btn-primary float-right\" [value]=\"isEdit ? 'Update' : 'Edit'\" pInputText />\n        <br />\n      </form>\n    </div>\n  </div>\n</p-card>\n"
  },
  {
    "path": "frontend/src/app/administration/settings/settings.component.sass",
    "content": ".centerBtn\n\tdisplay: flex\n\tjustify-content: center\n\talign-items: center\n\theight: 40px\n\twidth: 75px\n\tborder: 3px solid green\n"
  },
  {
    "path": "frontend/src/app/administration/settings/settings.component.spec.ts",
    "content": "import { ComponentFixture, TestBed } from '@angular/core/testing';\n\nimport { SettingsComponent } from './settings.component';\n\ndescribe('SettingsComponent', () => {\n  let component: SettingsComponent;\n  let fixture: ComponentFixture<SettingsComponent>;\n\n  beforeEach(async () => {\n    await TestBed.configureTestingModule({\n      declarations: [ SettingsComponent ]\n    })\n    .compileComponents();\n  });\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(SettingsComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/administration/settings/settings.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormGroup, FormBuilder, Validators } from '@angular/forms';\nimport { AuthService } from '../../auth.service';\nimport { Router, ActivatedRoute } from '@angular/router';\nimport { AlertService } from '../../alert/alert.service';\nimport { Settings } from '../../interfaces/Settings';\nimport { UserService } from '../../user.service';\nimport { AppService } from '../../app.service';\n\n@Component({\n  selector: 'app-settings',\n  templateUrl: './settings.component.html',\n  styleUrls: ['./settings.component.sass'],\n})\nexport class SettingsComponent implements OnInit {\n  settingsForm: FormGroup;\n  isEdit = false;\n  settings: Settings;\n  public keyPlaceholder = '************************';\n  constructor(\n    private fb: FormBuilder,\n    public authService: AuthService,\n    public router: Router,\n    public alertService: AlertService,\n    public activatedRoute: ActivatedRoute,\n    public userService: UserService,\n    public appService: AppService\n  ) {}\n\n  ngOnInit(): void {\n    this.activatedRoute.data.subscribe(({ settings }) => {\n      this.createForm();\n      this.settings = settings;\n      this.rebuildForm();\n    });\n  }\n\n  createForm() {\n    this.settingsForm = this.fb.group({\n      fromEmail: [{ value: '', disabled: !this.isEdit }],\n      fromEmailPassword: [{ value: '', disabled: !this.isEdit }],\n      companyName: [{ value: '', disabled: !this.isEdit }],\n    });\n  }\n\n  rebuildForm() {\n    this.settingsForm.reset({\n      fromEmail: this.settings?.fromEmail,\n      fromEmailPassword: this.settings?.fromEmailPassword,\n      companyName: this.settings?.companyName,\n    });\n  }\n\n  onSubmit(form: FormGroup) {\n    if (!this.isEdit) {\n      this.isEdit = true;\n      this.settingsForm.enable();\n    } else {\n      const settingsInfo = form.value;\n      this.appService.updateConfig(settingsInfo).subscribe((res: string) => {\n        this.alertService.success(res);\n        this.isEdit = false;\n        this.settingsForm.disable();\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "frontend/src/app/alert/alert/alert.component.html",
    "content": "<div *ngFor=\"let alert of alerts\" class=\"{{ cssClass(alert) }} alert-dismissable text-center\">\n  {{ alert.message }}\n  <a class=\"close\" (click)=\"removeAlert(alert)\">&times;</a>\n</div>\n"
  },
  {
    "path": "frontend/src/app/alert/alert/alert.component.sass",
    "content": ""
  },
  {
    "path": "frontend/src/app/alert/alert/alert.component.spec.ts",
    "content": "import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';\n\nimport { AlertComponent } from './alert.component';\n\ndescribe('AlertComponent', () => {\n  let component: AlertComponent;\n  let fixture: ComponentFixture<AlertComponent>;\n\n  beforeEach(waitForAsync(() => {\n    TestBed.configureTestingModule({\n      declarations: [ AlertComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(AlertComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/alert/alert/alert.component.ts",
    "content": "import { Component, OnInit, Input, OnDestroy } from '@angular/core';\nimport { Subscription } from 'rxjs';\nimport { Alert, AlertType } from 'src/app/classes/Alert';\nimport { AlertService } from '../alert.service';\n\n@Component({\n  selector: 'app-alert',\n  templateUrl: './alert.component.html',\n  styleUrls: ['./alert.component.sass']\n})\nexport class AlertComponent implements OnInit, OnDestroy {\n  @Input() id: string;\n\n  alerts: Alert[] = [];\n  subscription: Subscription;\n  constructor(private alertService: AlertService) {}\n\n  ngOnInit() {\n    this.subscription = this.alertService.onAlert(this.id).subscribe((alert) => {\n      if (!alert.message) {\n        // clear alerts when an empty alert is received\n        this.alerts = [];\n        return;\n      }\n      this.alerts.push(alert);\n    });\n  }\n\n  ngOnDestroy() {\n    // unsubscribe to avoid memory leaks\n    this.subscription.unsubscribe();\n  }\n\n  removeAlert(alert: Alert) {\n    // remove specified alert from array\n    this.alerts = this.alerts.filter((x) => x !== alert);\n  }\n\n  cssClass(alert: Alert) {\n    if (!alert) {\n      return;\n    }\n    // return css class based on alert type\n    switch (alert.type) {\n      case AlertType.Success:\n        return 'alert alert-success';\n      case AlertType.Error:\n        return 'alert alert-danger';\n      case AlertType.Info:\n        return 'alert alert-info';\n      case AlertType.Warning:\n        return 'alert alert-warning';\n    }\n  }\n}\n"
  },
  {
    "path": "frontend/src/app/alert/alert.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { AlertComponent } from './alert/alert.component';\nimport { AlertService } from './alert.service';\n\n@NgModule({\n  declarations: [AlertComponent],\n  imports: [CommonModule],\n  exports: [AlertComponent],\n  providers: [AlertService]\n})\nexport class AlertModule {}\n"
  },
  {
    "path": "frontend/src/app/alert/alert.service.spec.ts",
    "content": "import { TestBed } from '@angular/core/testing';\n\nimport { AlertService } from './alert.service';\n\ndescribe('AlertService', () => {\n  beforeEach(() => TestBed.configureTestingModule({}));\n\n  it('should be created', () => {\n    const service: AlertService = TestBed.inject(AlertService);\n    expect(service).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/alert/alert.service.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { Router, NavigationStart } from '@angular/router';\nimport { Alert, AlertType } from '../classes/Alert';\nimport { Subject, Observable } from 'rxjs';\nimport { filter } from 'rxjs/operators';\n\n@Injectable({\n  providedIn: 'root'\n})\nexport class AlertService {\n  private subject = new Subject<Alert>();\n  private keepAfterRouteChange = false;\n\n  constructor(private router: Router) {\n    this.router.events.subscribe((event) => {\n      if (event instanceof NavigationStart) {\n        if (this.keepAfterRouteChange) {\n          // only keep for a single route change\n          this.keepAfterRouteChange = false;\n        } else {\n          // clear alert messages\n          this.clear();\n        }\n      }\n    });\n  }\n\n  // enable subscribing to alerts observable\n  onAlert(alertId?: string): Observable<Alert> {\n    return this.subject.asObservable().pipe(filter((x) => x && x.alertId === alertId));\n  }\n\n  // convenience methods\n  success(message: string, alertId?: string) {\n    this.alert(new Alert({ message, type: AlertType.Success, alertId }));\n  }\n\n  error(message: string, alertId?: string) {\n    this.alert(new Alert({ message, type: AlertType.Error, alertId }));\n  }\n\n  info(message: string, alertId?: string) {\n    this.alert(new Alert({ message, type: AlertType.Info, alertId }));\n  }\n\n  warn(message: string, alertId?: string) {\n    this.alert(new Alert({ message, type: AlertType.Warning, alertId }));\n  }\n\n  clear(alertId?: string) {\n    this.subject.next(new Alert({ alertId }));\n  }\n\n  // main alert method\n  alert(alert: Alert) {\n    this.keepAfterRouteChange = alert.keepAfterRouteChange;\n    this.subject.next(alert);\n  }\n}\n"
  },
  {
    "path": "frontend/src/app/apikey-management/apikey-management.component.html",
    "content": "<p-card>\n  <div class=\"card-body\">\n    <h4>API Key Management</h4>\n    <hr />\n    <p-table [value]=\"apiKeys\" [paginator]=\"true\" [rows]=\"10\" styleClass=\"p-datatable-striped\">\n      <ng-template pTemplate=\"header\">\n        <tr>\n          <th>\n            <div class=\"p-d-flex p-jc-between p-ai-center\">\n              User Email\n              <p-columnFilter type=\"text\" field=\"email\" display=\"menu\"></p-columnFilter>\n            </div>\n          </th>\n          <th>\n            <div class=\"p-d-flex p-jc-between p-ai-center\">\n              Created Date\n            </div>\n          </th>\n          <th>\n            <div class=\"p-d-flex p-jc-between p-ai-center\">\n              Last Updated Date\n            </div>\n          </th>\n          <th></th>\n        </tr>\n      </ng-template>\n      <ng-template pTemplate=\"body\" let-apiKey>\n        <tr>\n          <td>{{apiKey?.user?.email}}</td>\n          <td>{{apiKey?.createdDate | date: 'longDate':'UTC' }}</td>\n          <td>{{apiKey?.lastUpdatedDate | date: 'longDate':'UTC' }}</td>\n          <td>\n            <button (click)=\"deactivateApiKey(apiKey.id)\" class=\"btn btn-dark\" type=\"button\">\n              <i class=\"pi pi-trash\"></i>\n            </button>\n          </td>\n        </tr>\n      </ng-template>\n    </p-table>\n  </div>\n</p-card>\n"
  },
  {
    "path": "frontend/src/app/apikey-management/apikey-management.component.sass",
    "content": ""
  },
  {
    "path": "frontend/src/app/apikey-management/apikey-management.component.spec.ts",
    "content": "import { ComponentFixture, TestBed } from '@angular/core/testing';\n\nimport { ApikeyManagementComponent } from './apikey-management.component';\n\ndescribe('ApikeyManagementComponent', () => {\n  let component: ApikeyManagementComponent;\n  let fixture: ComponentFixture<ApikeyManagementComponent>;\n\n  beforeEach(async () => {\n    await TestBed.configureTestingModule({\n      declarations: [ApikeyManagementComponent],\n    }).compileComponents();\n  });\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(ApikeyManagementComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/apikey-management/apikey-management.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { AlertService } from '../alert/alert.service';\nimport { AuthService } from '../auth.service';\nimport { ApiKey } from '../interfaces/ApiKey';\n\n@Component({\n  selector: 'app-apikey-management',\n  templateUrl: './apikey-management.component.html',\n  styleUrls: ['./apikey-management.component.sass'],\n})\nexport class ApikeyManagementComponent implements OnInit {\n  apiKeys: ApiKey[] = [];\n  constructor(\n    public authService: AuthService,\n    public alertService: AlertService\n  ) {}\n\n  ngOnInit(): void {\n    this.getActiveApiKeys();\n  }\n\n  getActiveApiKeys() {\n    this.authService.getApiKeysInfo().subscribe((res: ApiKey[]) => {\n      this.apiKeys = res;\n    });\n  }\n\n  deactivateApiKey(id: number) {\n    const r = confirm(\n      'This API key may be in use by an external entity. Are you sure you want to delete it?'\n    );\n    if (r) {\n      this.authService.adminDeactivateApiKey(id).subscribe((res: string) => {\n        this.alertService.success(res);\n        this.getActiveApiKeys();\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "frontend/src/app/app-routing.module.ts",
    "content": "import { NgModule, Injectable } from '@angular/core';\nimport {\n  Routes,\n  RouterModule,\n  Resolve,\n  ActivatedRouteSnapshot,\n} from '@angular/router';\n\nimport { DashboardComponent } from '../app/dashboard/dashboard.component';\nimport { AssessmentsComponent } from '../app/assessments/assessments.component';\n\nimport { AppService } from '../app/app.service';\nimport { OrganizationComponent } from './organization/organization.component';\nimport { VulnerabilityComponent } from './vulnerability/vulnerability.component';\nimport { VulnFormComponent } from './vuln-form/vuln-form.component';\nimport { OrgFormComponent } from './org-form/org-form.component';\nimport { AssetFormComponent } from './asset-form/asset-form.component';\nimport { AssessmentFormComponent } from './assessment-form/assessment-form.component';\nimport { ReportComponent } from './report/report.component';\nimport { PageNotFoundComponent } from './page-not-found/page-not-found.component';\nimport { AuthGuard } from './auth.guard';\nimport { AdminGuard } from './admin.guard';\nimport { LoginComponent } from './login/login.component';\nimport { ForgotPasswordComponent } from './forgot-password/forgot-password.component';\nimport { PasswordResetComponent } from './password-reset/password-reset.component';\nimport { InviteUserComponent } from './administration/invite-user/invite-user.component';\nimport { AdministrationComponent } from './administration/administration.component';\nimport { TeamFormComponent } from './team-form/team-form.component';\nimport { RegisterComponent } from './register/register.component';\nimport { UserProfileComponent } from './user-profile/user-profile.component';\nimport { UserFormComponent } from './user-form/user-form.component';\nimport { SettingsComponent } from './administration/settings/settings.component';\nimport { EmailValidateComponent } from './email-validate/email-validate.component';\nimport { UserService } from './user.service';\nimport { forkJoin } from 'rxjs';\nimport { map } from 'rxjs/internal/operators/map';\nimport { TeamService } from './team.service';\n@Injectable()\nexport class AssetsResolver implements Resolve<any> {\n  constructor(private apiService: AppService) {}\n\n  resolve(route: ActivatedRouteSnapshot) {\n    return this.apiService.getOrganizationAssets(route.params.orgId);\n  }\n}\n@Injectable()\nexport class AssetResolver implements Resolve<any> {\n  constructor(private apiService: AppService) {}\n  resolve(route: ActivatedRouteSnapshot) {\n    return this.apiService.getAsset(route.params.assetId, route.params.id);\n  }\n}\n@Injectable()\nexport class AssessmentResolver implements Resolve<any> {\n  constructor(\n    private apiService: AppService,\n    private userService: UserService\n  ) {}\n  resolve(route: ActivatedRouteSnapshot) {\n    if (\n      route.params.assetId &&\n      route.params.assessmentId &&\n      route.params.orgId\n    ) {\n      return forkJoin([\n        this.apiService.getAssessment(\n          route.params.assetId,\n          route.params.assessmentId\n        ),\n        this.userService.getTesters(route.params.orgId),\n      ]).pipe(\n        map((result) => {\n          return {\n            assessment: result[0],\n            testers: result[1],\n          };\n        })\n      );\n    } else {\n      return this.userService.getTesters(route.params.orgId);\n    }\n  }\n}\n@Injectable()\nexport class AssessmentsResolver implements Resolve<any> {\n  constructor(private apiService: AppService) {}\n\n  resolve(route: ActivatedRouteSnapshot) {\n    return this.apiService.getAssessments(route.params.assetId);\n  }\n}\n@Injectable()\nexport class VulnerabilitiesResolver implements Resolve<any> {\n  constructor(private apiService: AppService) {}\n\n  resolve(route: ActivatedRouteSnapshot) {\n    return this.apiService.getVulnerabilities(route.params.assessmentId);\n  }\n}\n@Injectable()\nexport class VulnerabilityResolver implements Resolve<any> {\n  constructor(private apiService: AppService) {}\n\n  resolve(route: ActivatedRouteSnapshot) {\n    return this.apiService.getVulnerability(route.params.vulnId);\n  }\n}\n@Injectable()\nexport class TeamResolver implements Resolve<any> {\n  constructor(private teamService: TeamService) {}\n\n  resolve(route: ActivatedRouteSnapshot) {\n    return this.teamService.getTeamById(route.params.teamId);\n  }\n}\n@Injectable()\nexport class TeamFormResolver implements Resolve<any> {\n  constructor(\n    private appService: AppService,\n    private userService: UserService\n  ) {}\n  resolve(route: ActivatedRouteSnapshot) {\n    return forkJoin([\n      this.appService.getOrganizations(),\n      this.userService.getAllUsers(),\n    ]).pipe(\n      map((result) => {\n        return {\n          organizations: result[0],\n          activeUsers: result[1],\n        };\n      })\n    );\n  }\n}\n@Injectable()\nexport class OrganizationResolver implements Resolve<any> {\n  constructor(private apiService: AppService) {}\n\n  resolve(route: ActivatedRouteSnapshot) {\n    return this.apiService.getOrganizationById(route.params.id);\n  }\n}\n@Injectable()\nexport class ReportResolver implements Resolve<any> {\n  constructor(private apiService: AppService) {}\n\n  resolve(route: ActivatedRouteSnapshot) {\n    return this.apiService.getReport(route.params.assessmentId);\n  }\n}\n@Injectable()\nexport class UserResolver implements Resolve<any> {\n  constructor(private userService: UserService) {}\n\n  resolve() {\n    return this.userService.getUser();\n  }\n}\n@Injectable()\nexport class SettingsResolver implements Resolve<any> {\n  constructor(private apiService: AppService) {}\n\n  resolve(route: ActivatedRouteSnapshot) {\n    return this.apiService.getConfig();\n  }\n}\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'dashboard',\n    pathMatch: 'full',\n  },\n  {\n    path: 'login',\n    component: LoginComponent,\n  },\n  {\n    path: 'forgot-password',\n    component: ForgotPasswordComponent,\n  },\n  {\n    path: 'password-reset/:uuid',\n    component: PasswordResetComponent,\n  },\n  {\n    path: 'register/:uuid',\n    component: RegisterComponent,\n  },\n  {\n    path: 'email/validate/:uuid',\n    component: EmailValidateComponent,\n  },\n  {\n    path: 'user/profile',\n    component: UserProfileComponent,\n    resolve: { user: UserResolver },\n    canActivate: [AuthGuard],\n  },\n  {\n    path: 'settings',\n    component: SettingsComponent,\n    resolve: { settings: SettingsResolver },\n    canActivate: [AuthGuard],\n  },\n  {\n    path: 'invite',\n    component: InviteUserComponent,\n    canActivate: [AuthGuard],\n  },\n  {\n    path: 'administration',\n    component: AdministrationComponent,\n    canActivate: [AdminGuard],\n  },\n  {\n    path: 'administration',\n    component: AdministrationComponent,\n    canActivate: [AdminGuard],\n  },\n  {\n    path: 'administration/team/:teamId',\n    component: TeamFormComponent,\n    canActivate: [AdminGuard],\n    resolve: { result: TeamFormResolver },\n  },\n  {\n    path: 'administration/team',\n    component: TeamFormComponent,\n    canActivate: [AdminGuard],\n    resolve: { result: TeamFormResolver },\n  },\n  {\n    path: 'administration/user/create',\n    canActivate: [AdminGuard],\n    component: UserFormComponent,\n  },\n  {\n    path: 'administration/user/invite',\n    component: InviteUserComponent,\n    canActivate: [AdminGuard],\n  },\n  {\n    path: 'dashboard',\n    component: DashboardComponent,\n    canActivate: [AuthGuard],\n  },\n  {\n    path: 'organization/:orgId/asset/:assetId',\n    component: AssessmentsComponent,\n    resolve: { assessments: AssessmentsResolver },\n    canActivate: [AuthGuard],\n  },\n  {\n    path: 'organization/:orgId',\n    component: OrganizationComponent,\n    resolve: { assets: AssetsResolver },\n    canActivate: [AuthGuard],\n  },\n  {\n    path:\n      'organization/:orgId/asset/:assetId/assessment/:assessmentId/vulnerability',\n    component: VulnerabilityComponent,\n    resolve: { vulnerabilities: VulnerabilitiesResolver },\n    canActivate: [AuthGuard],\n  },\n  {\n    path:\n      'organization/:orgId/asset/:assetId/assessment/:assessmentId/vuln-form/:vulnId',\n    component: VulnFormComponent,\n    resolve: { vulnInfo: VulnerabilityResolver },\n    canActivate: [AuthGuard],\n  },\n  {\n    path:\n      'organization/:orgId/asset/:assetId/assessment/:assessmentId/vuln-form',\n    component: VulnFormComponent,\n    canActivate: [AuthGuard],\n  },\n  {\n    path: 'organization-form',\n    component: OrgFormComponent,\n    canActivate: [AuthGuard],\n  },\n  {\n    path: 'organization-form/:id',\n    component: OrgFormComponent,\n    resolve: { organization: OrganizationResolver },\n    canActivate: [AuthGuard],\n  },\n  {\n    path: 'organization/:id/asset-form',\n    component: AssetFormComponent,\n    canActivate: [AuthGuard],\n  },\n  {\n    path: 'organization/:id/asset-form/:assetId',\n    component: AssetFormComponent,\n    resolve: { asset: AssetResolver },\n    canActivate: [AuthGuard],\n  },\n  {\n    path: 'organization/:orgId/asset/:assetId/assessment',\n    component: AssessmentFormComponent,\n    resolve: { result: AssessmentResolver },\n    canActivate: [AuthGuard],\n  },\n  {\n    path: 'organization/:orgId/asset/:assetId/assessment/:assessmentId',\n    component: AssessmentFormComponent,\n    resolve: { result: AssessmentResolver },\n    canActivate: [AuthGuard],\n  },\n  {\n    path: 'organization/:orgId/asset/:assetId/assessment/:assessmentId/report',\n    component: ReportComponent,\n    resolve: { report: ReportResolver },\n    canActivate: [AuthGuard],\n  },\n  {\n    path:\n      'organization/:orgId/asset/:assetId/assessment/:assessmentId/report/puppeteer',\n    component: ReportComponent,\n    resolve: { report: ReportResolver },\n    canActivate: [AuthGuard],\n  },\n  { path: '**', component: PageNotFoundComponent },\n];\n\n@NgModule({\n  imports: [\n    RouterModule.forRoot(routes, {\n      useHash: true,\n    }),\n  ],\n  exports: [RouterModule],\n  providers: [\n    AssetResolver,\n    AssetsResolver,\n    AssessmentsResolver,\n    VulnerabilitiesResolver,\n    OrganizationResolver,\n    AssessmentResolver,\n    VulnerabilityResolver,\n    ReportResolver,\n    UserResolver,\n    SettingsResolver,\n    TeamFormResolver,\n    TeamResolver,\n  ],\n})\nexport class AppRoutingModule {}\n"
  },
  {
    "path": "frontend/src/app/app.component.html",
    "content": "<app-navbar></app-navbar>\n<div class=\"container-fluid\">\n  <app-alert></app-alert>\n  <router-outlet></router-outlet>\n</div>\n<app-footer></app-footer>\n"
  },
  {
    "path": "frontend/src/app/app.component.scss",
    "content": ""
  },
  {
    "path": "frontend/src/app/app.component.spec.ts",
    "content": "import { TestBed, waitForAsync } from '@angular/core/testing';\nimport { RouterTestingModule } from '@angular/router/testing';\nimport { AppComponent } from './app.component';\n\ndescribe('AppComponent', () => {\n  beforeEach(waitForAsync(() => {\n    TestBed.configureTestingModule({\n      imports: [\n        RouterTestingModule\n      ],\n      declarations: [\n        AppComponent\n      ],\n    }).compileComponents();\n  }));\n\n  it('should create the app', () => {\n    const fixture = TestBed.createComponent(AppComponent);\n    const app = fixture.debugElement.componentInstance;\n    expect(app).toBeTruthy();\n  });\n\n  it(`should have as title 'frontend'`, () => {\n    const fixture = TestBed.createComponent(AppComponent);\n    const app = fixture.debugElement.componentInstance;\n    expect(app.title).toEqual('frontend');\n  });\n\n  it('should render title in a h1 tag', () => {\n    const fixture = TestBed.createComponent(AppComponent);\n    fixture.detectChanges();\n    const compiled = fixture.debugElement.nativeElement;\n    expect(compiled.querySelector('h1').textContent).toContain('Welcome to frontend!');\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-root',\n  templateUrl: './app.component.html',\n  styleUrls: ['./app.component.scss']\n})\nexport class AppComponent {}\n"
  },
  {
    "path": "frontend/src/app/app.interceptor.ts",
    "content": "import { Injectable } from '@angular/core';\nimport {\n  HttpEvent,\n  HttpHandler,\n  HttpInterceptor,\n  HttpRequest,\n} from '@angular/common/http';\nimport { Observable } from 'rxjs';\nimport { finalize, catchError, switchMap } from 'rxjs/operators';\nimport { LoaderService } from './loader.service';\nimport { AlertService } from './alert/alert.service';\nimport { AuthService } from './auth.service';\nimport { environment } from '../environments/environment';\nimport { Router } from '@angular/router';\nimport { Tokens } from './interfaces/Tokens';\n@Injectable()\nexport class AppInterceptor implements HttpInterceptor {\n  constructor(\n    public loaderService: LoaderService,\n    public alertService: AlertService,\n    public authService: AuthService,\n    public router: Router\n  ) {}\n\n  logout() {\n    this.authService.logout();\n    this.router.navigate(['login']);\n  }\n  intercept(\n    req: HttpRequest<any>,\n    next: HttpHandler\n  ): Observable<HttpEvent<any>> {\n    const authToken = this.authService.getUserToken();\n    if (authToken) {\n      req = req.clone({\n        headers: req.headers.set('Authorization', authToken),\n      });\n    }\n    this.loaderService.show();\n    return next.handle(req).pipe(\n      finalize(() => this.loaderService.hide()),\n      catchError((error: any) => {\n        if (error.error instanceof ErrorEvent) {\n          // A client-side or network error occurred. Handle it accordingly.\n          if (environment.production) {\n            console.error('An error occurred:', error.error.message);\n          }\n        } else {\n          // The backend returned an unsuccessful response code.\n          // The response body may contain clues as to what went wrong,\n          if (environment.production) {\n            console.error(\n              `Backend returned code ${error.status}, ` +\n                `body was: ${error.error}`\n            );\n          }\n          switch (error.status) {\n            case 500:\n              error.error = 'Internal Server Error';\n              this.alertService.error(error.error);\n              break;\n            case 403:\n              this.alertService.warn(error.error);\n              break;\n            case 404:\n              this.alertService.warn(error.error);\n              break;\n            case 401:\n              const url = environment.apiUrl;\n              if (error.url === url + '/refresh') {\n                this.logout();\n                this.alertService.error(\n                  'You have been logged out due to inactivity'\n                );\n                break;\n              }\n              return this.authService.refreshSession().pipe(\n                switchMap((tokens: Tokens) => {\n                  this.authService.setTokens(tokens);\n                  req = req.clone({\n                    headers: req.headers.set('Authorization', tokens.token),\n                  });\n                  return next.handle(req);\n                })\n              );\n            case 400:\n              this.alertService.warn(error.error);\n              break;\n            default:\n              error.error = 'Internal Server Error';\n              this.alertService.error(error.error);\n          }\n        }\n        // return an observable with a user-facing error message\n        return [];\n      })\n    );\n  }\n}\n"
  },
  {
    "path": "frontend/src/app/app.module.ts",
    "content": "import { BrowserModule } from '@angular/platform-browser';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { NgModule } from '@angular/core';\nimport { ReactiveFormsModule } from '@angular/forms';\nimport { HTTP_INTERCEPTORS } from '@angular/common/http';\nimport { AppRoutingModule } from './app-routing.module';\nimport { AppComponent } from './app.component';\nimport { NavbarComponent } from './navbar/navbar.component';\nimport { DashboardComponent } from './dashboard/dashboard.component';\nimport { AlertModule } from './alert/alert.module';\nimport { NgSelectModule } from '@ng-select/ng-select';\nimport { AppService } from './app.service';\nimport { LoaderService } from './loader.service';\nimport { AuthGuard } from './auth.guard';\nimport { AppInterceptor } from './app.interceptor';\nimport { HttpClientModule } from '@angular/common/http';\nimport { AssessmentsComponent } from './assessments/assessments.component';\nimport { OrganizationComponent } from './organization/organization.component';\nimport { VulnerabilityComponent } from './vulnerability/vulnerability.component';\nimport { VulnFormComponent } from './vuln-form/vuln-form.component';\nimport { OrgFormComponent } from './org-form/org-form.component';\nimport { AssetFormComponent } from './asset-form/asset-form.component';\nimport { FooterComponent } from './footer/footer.component';\nimport { AssessmentFormComponent } from './assessment-form/assessment-form.component';\nimport { MarkdownModule } from 'ngx-markdown';\nimport { DatePipe } from '@angular/common';\nimport { ReportComponent } from './report/report.component';\nimport { PageNotFoundComponent } from './page-not-found/page-not-found.component';\nimport { LoginComponent } from './login/login.component';\nimport { ForgotPasswordComponent } from './forgot-password/forgot-password.component';\nimport { PasswordResetComponent } from './password-reset/password-reset.component';\nimport { InviteUserComponent } from './administration/invite-user/invite-user.component';\nimport { AdministrationComponent } from './administration/administration.component';\nimport { RegisterComponent } from './register/register.component';\nimport { UserProfileComponent } from './user-profile/user-profile.component';\nimport { SettingsComponent } from './administration/settings/settings.component';\nimport { TableModule } from 'primeng/table';\nimport { InputTextModule } from 'primeng/inputtext';\nimport { MultiSelectModule } from 'primeng/multiselect';\nimport { CalendarModule } from 'primeng/calendar';\nimport { ProgressSpinnerModule } from 'primeng/progressspinner';\nimport { ButtonModule } from 'primeng/button';\nimport { CardModule } from 'primeng/card';\nimport { PasswordModule } from 'primeng/password';\nimport { EmailValidateComponent } from './email-validate/email-validate.component';\nimport { ChartModule } from 'primeng/chart';\nimport { UserManagementComponent } from './user-management/user-management.component';\nimport { TeamComponent } from './team/team.component';\nimport { TeamFormComponent } from './team-form/team-form.component';\nimport { SelectButtonModule } from 'primeng/selectbutton';\nimport { ListboxModule } from 'primeng/listbox';\nimport { UserFormComponent } from './user-form/user-form.component';\nimport { ApikeyManagementComponent } from './apikey-management/apikey-management.component';\nimport { DialogModule } from 'primeng/dialog';\n@NgModule({\n  declarations: [\n    AppComponent,\n    NavbarComponent,\n    DashboardComponent,\n    OrganizationComponent,\n    AssessmentsComponent,\n    VulnerabilityComponent,\n    OrgFormComponent,\n    AssetFormComponent,\n    VulnFormComponent,\n    FooterComponent,\n    AssessmentFormComponent,\n    ReportComponent,\n    PageNotFoundComponent,\n    LoginComponent,\n    ForgotPasswordComponent,\n    PasswordResetComponent,\n    InviteUserComponent,\n    AdministrationComponent,\n    RegisterComponent,\n    UserProfileComponent,\n    SettingsComponent,\n    EmailValidateComponent,\n    UserManagementComponent,\n    TeamComponent,\n    TeamFormComponent,\n    UserFormComponent,\n    ApikeyManagementComponent,\n  ],\n  imports: [\n    BrowserModule,\n    BrowserAnimationsModule,\n    AppRoutingModule,\n    HttpClientModule,\n    ReactiveFormsModule,\n    MarkdownModule.forRoot(),\n    AlertModule,\n    NgSelectModule,\n    TableModule,\n    InputTextModule,\n    MultiSelectModule,\n    CalendarModule,\n    ProgressSpinnerModule,\n    ButtonModule,\n    CardModule,\n    ChartModule,\n    PasswordModule,\n    SelectButtonModule,\n    ListboxModule,\n    DialogModule,\n  ],\n  providers: [\n    AppService,\n    DatePipe,\n    LoaderService,\n    { provide: HTTP_INTERCEPTORS, useClass: AppInterceptor, multi: true },\n    AuthGuard,\n  ],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "frontend/src/app/app.service.spec.ts",
    "content": "import { TestBed } from '@angular/core/testing';\n\nimport { AppService } from './app.service';\n\ndescribe('AppService', () => {\n  beforeEach(() => TestBed.configureTestingModule({}));\n\n  it('should be created', () => {\n    const service: AppService = TestBed.inject(AppService);\n    expect(service).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/app.service.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { HttpClient, HttpErrorResponse } from '@angular/common/http';\nimport { Organization } from './org-form/Organization';\nimport { Asset } from './asset-form/Asset';\nimport { Assessment } from './assessment-form/Assessment';\nimport { DomSanitizer } from '@angular/platform-browser';\nimport { environment } from '../environments/environment';\nimport { User } from './interfaces/User';\nimport { Observable } from 'rxjs';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class AppService {\n  constructor(private http: HttpClient, private sanitizer: DomSanitizer) {}\n  api = environment.apiUrl;\n  /**\n   * Function is responsible for initial retrevial of organizations on dashboard loading\n   * @returns all organization information to the dashboard\n   */\n  getOrganizations() {\n    return this.http\n      .get(`${this.api}/organization`)\n      .toPromise()\n      .then(async (res) => {\n        return res;\n      });\n  }\n\n  /**\n   * Function responsible for retreval of organizations archived status.\n   * @returns Data for organizations that have been archived.\n   */\n  getArchivedOrganizations() {\n    const httpOptions = {\n      responseType: 'blob' as 'json',\n    };\n    return this.http\n      .get(`${this.api}/organization/archive`)\n      .toPromise()\n      .then(async (res) => {\n        return res;\n      });\n  }\n\n  /**\n   * Function is responsible for retreval of images as blogs using the ID association\n   * @param file accepts the id assigned to a file\n   * @returns the image associated with the id\n   */\n  getImageById(file: any) {\n    const httpOptions = {\n      responseType: 'blob' as 'json',\n    };\n    return this.http\n      .get(`${this.api}/file/${file.id}`, httpOptions)\n      .toPromise()\n      .then((res: Blob) => {\n        return this.createObjectUrl(res, file.mimetype);\n      });\n  }\n\n  /**\n   * Function is responsible for retreving an organization based on ID passed\n   * @param id is the ID of the organization being requested\n   * @returns all information related to the organization requested\n   */\n  getOrganizationById(id: number) {\n    return this.http\n      .get(`${this.api}/organization/${id}`)\n      .toPromise()\n      .then((res) => {\n        return res;\n      });\n  }\n\n  /**\n   * Function returns all active assets related to the organization ID\n   * @param id is the ID of the organization\n   * @returns all assets related to the organization passed\n   */\n  getOrganizationAssets(id: number) {\n    return this.http\n      .get(`${this.api}/organization/asset/${id}`)\n      .toPromise()\n      .then((res) => {\n        return res;\n      });\n  }\n\n  /**\n   * Function returns all open vulnerabilities by Asset ID\n   * @param assetId\n   * @returns open asset vulnerabilities\n   */\n  getOpenVulnsByAssetId(assetId: number) {\n    return this.http.get(`${this.api}/asset/${assetId}/open/vulnerabilities`);\n  }\n\n  /**\n   * Function returns all archived assets related to the organization ID\n   * @param id is the ID of the organization\n   * @returns all assets related to the organization passed\n   */\n  getOrganizationArchiveAssets(id: number) {\n    return this.http.get(`${this.api}/organization/${id}/asset/archive`);\n  }\n\n  /**\n   * Function is responsible for archiving an organization by altering it's status\n   * @param id is the organization being passed for archiving\n   * @returns updates the status of the organization and reports the http status returned\n   */\n  archiveOrganization(id: number) {\n    return this.http.patch(`${this.api}/organization/${id}/archive`, null);\n  }\n\n  /**\n   * Function is responsible for unarchving an organization by alterint it's status\n   * @param id is the organization being passed for archiving\n   * @returns updates the status of the organization and reports the http status returned\n   */\n  activateOrganization(id: number) {\n    return this.http.patch(`${this.api}/organization/${id}/activate`, null);\n  }\n\n  /**\n   * Function is responsible for returning all assessements related to an organization\n   * @param id is the organization ID associated with the assessements\n   * @returns all assessments related to the organization\n   */\n  getAssessments(id: number) {\n    return this.http\n      .get(`${this.api}/assessment/${id}`)\n      .toPromise()\n      .then((res) => {\n        return res as object[];\n      });\n  }\n\n  /**\n   * Delete assessment by ID\n   * @returns success/error message\n   */\n  deleteAssessment(assessmentId: number) {\n    return this.http.delete(`${this.api}/assessment/${assessmentId}`);\n  }\n\n  /**\n   * Function is responsible for returning all vulnerabilites related to an assessment\n   * @param assessmentId is the ID associated with the assessment\n   * @returns all vulnerablities related to the assessment\n   */\n  getVulnerabilities(assessmentId: number) {\n    return this.http.get(\n      `${this.api}/assessment/${assessmentId}/vulnerability`\n    );\n  }\n\n  /**\n   * Function is responsible for returning a vulnerablity called by it's ID\n   * @param id associated to the vulnerability requested\n   * @returns all object related data to the vulnerability requested\n   */\n  getVulnerability(id: number) {\n    return this.http.get(`${this.api}/vulnerability/${id}`);\n  }\n\n  exportVulnToJira(vulnId: number) {\n    return this.http.get(`${this.api}/vulnerability/jira/${vulnId}`);\n  }\n\n  exportAssessmentToJira(assessmentId: number) {\n    return this.http.get(`${this.api}/assessment/jira/${assessmentId}`);\n  }\n\n  getConfig() {\n    return this.http.get(`${this.api}/config`);\n  }\n\n  updateConfig(config: FormData) {\n    return this.http.post(`${this.api}/config`, config);\n  }\n\n  /**\n   * Function is responsible for updating a vulnerability by ID\n   * @param id is associated with the requested vulnerability\n   * @param vuln is associated with the form data passed as an object\n   * @returns http status code for the return value\n   */\n  updateVulnerability(id: number, vuln: FormData) {\n    return this.http.patch(`${this.api}/vulnerability/${id}`, vuln);\n  }\n\n  /**\n   * Function is responsible for creating a vulnerability for an assessment\n   * @param vuln contains the form object data for all required fields\n   * @returns http status code of the request\n   */\n  createVuln(vuln: FormData) {\n    return this.http.post(`${this.api}/vulnerability`, vuln);\n  }\n\n  /**\n   * Function is responsible for deletion of a vulnerability\n   * @param vulnId is the ID association to the vulnerability\n   * @returns http status code of the request\n   */\n  deleteVuln(vulnId: number) {\n    return this.http.delete(`${this.api}/vulnerability/${vulnId}`);\n  }\n\n  /**\n   * Function is responsible for creating a new organization\n   * @param org object of the form data passed to the API\n   * @returns http status code of the request\n   */\n  createOrg(org: Organization) {\n    return this.http.post(`${this.api}/organization`, org);\n  }\n\n  /**\n   * Function is responsible for updating an organization\n   * @param id is the organization ID being updated\n   * @param org is the form object data passed to the API\n   * @returns http status code of the request\n   */\n  updateOrg(id: number, org: Organization) {\n    return this.http.patch(`${this.api}/organization/${id}`, org);\n  }\n\n  /**\n   * Function is responsible for creating a new asset tied to an organization\n   * @param asset is the form object data for the new asset\n   * @returns http status code of the request\n   */\n  createAsset(asset: Asset) {\n    return this.http.post(\n      `${this.api}/organization/${asset.organization}/asset`,\n      asset\n    );\n  }\n\n  purgeJira(assetId: number) {\n    return this.http.delete(`${this.api}/asset/jira/${assetId}`);\n  }\n\n  /**\n   * Function is responsible for fetching assets\n   * @param assetId asset ID being requested\n   * @param orgId associated organization ID attached to the asset\n   * @returns https status code of the request\n   */\n  getAsset(assetId: number, orgId: number) {\n    return this.http.get(`${this.api}/organization/${orgId}/asset/${assetId}`);\n  }\n\n  /**\n   * Function is responsible for updating an asset\n   * @param asset is the ID associated to the asset\n   * @returns http status code of the request\n   */\n  updateAsset(asset: Asset) {\n    return this.http.patch(\n      `${this.api}/organization/${asset.organization}/asset/${asset.id}`,\n      asset\n    );\n  }\n\n  /**\n   * Function is responsible for archiving an asset\n   * @param asset is the ID associated to the asset\n   * @returns http status code of the request\n   */\n  archiveAsset(asset: Asset) {\n    return this.http.patch(`${this.api}/asset/archive/${asset.id}`, {});\n  }\n\n  /**\n   * Function is responsible for activating an asset\n   * @param asset is the ID associated to the asset\n   * @returns http status code of the request\n   */\n  activateAsset(asset: Asset) {\n    return this.http.patch(`${this.api}/asset/activate/${asset.id}`, {});\n  }\n\n  /**\n   * Function is responsible for creating new assessments\n   * @param assessment data contained in the assessment form object\n   * @returns http status code of the request\n   */\n  createAssessment(assessment: Assessment) {\n    return this.http.post(`${this.api}/assessment`, assessment);\n  }\n\n  /**\n   * Function is responsible for updating an assessment's data\n   * @param assessment form object data of the assessment\n   * @param assessmentId associated ID of the assessment being altered\n   * @param assetId asset ID attached to the request ties into the assessment ID\n   * @returns http status code of the request\n   */\n  updateAssessment(\n    assessment: Assessment,\n    assessmentId: number,\n    assetId: number\n  ) {\n    return this.http.patch(\n      `${this.api}/asset/${assetId}/assessment/${assessmentId}`,\n      assessment\n    );\n  }\n\n  /**\n   * Function is responsible for retrevial of assessments\n   * @param assetId associated asset ID required\n   * @param assessmentId associated assessment ID required\n   * @returns http status code with object data from the API call\n   */\n  getAssessment(assetId: number, assessmentId: number) {\n    return this.http.get(\n      `${this.api}/asset/${assetId}/assessment/${assessmentId}`\n    );\n  }\n\n  /**\n   * Function is responsible for uploading files, attaching them to the resource requesting it\n   * @param fileToUpload form object data for the files associated in the request\n   * @returns http status code of the request\n   */\n  upload(fileToUpload: File) {\n    const formData: FormData = new FormData();\n    formData.append('file', fileToUpload);\n    return this.http.post(`${this.api}/upload`, formData);\n  }\n\n  /**\n   * Function is responsible for uploading multi-part data associated with files.\n   * @param fileToUpload form object data holding the file objects required\n   * @returns http status code of the request\n   */\n  uploadMultiple(fileToUpload: FormData) {\n    return this.http.post(`${this.api}/upload-multiple`, fileToUpload);\n  }\n\n  /**\n   * Function is responsible for report retrevial\n   * @param assessmentId required ID of the assessment for object data relations\n   * @returns http status request and object data for the report\n   */\n  getReport(assessmentId: number) {\n    return this.http.get(`${this.api}/assessment/${assessmentId}/report`);\n  }\n\n  /**\n   * Function is responsible for report generation\n   * @param orgId requires associated data from the organization ID\n   * @param assetId requires associated data from the asset ID\n   * @param assessmentId requires associated data from the assessment ID\n   * @returns http status code of the request along with a new tab with a generated report\n   */\n  generateReport(orgId: number, assetId: number, assessmentId: number) {\n    const httpOptions = {\n      responseType: 'blob' as 'json',\n    };\n    const generateObject = {\n      orgId,\n      assetId,\n      assessmentId,\n    };\n    return this.http.post(\n      `${this.api}/report/generate`,\n      generateObject,\n      httpOptions\n    );\n  }\n\n  /**\n   * Function is responsible for generating URL's to provide accessable data, reports, images, and\n   * any other downloadble content.\n   * @param file requires the file object to be called\n   * @param [mimetype] requires the mimetype of the data\n   * @returns new URL with the object requested in a sanatized manner\n   */\n  public createObjectUrl(file, mimetype?: string) {\n    // Preview unsaved form\n    const blob = new Blob([file], {\n      type: mimetype || file.type,\n    });\n    const url = window.URL.createObjectURL(blob);\n    return this.sanitizer.bypassSecurityTrustUrl(url);\n  }\n}\n"
  },
  {
    "path": "frontend/src/app/assessment-form/Assessment.ts",
    "content": "import { URL } from 'url';\nimport { User } from '../interfaces/User';\n\nexport class Assessment {\n  constructor(\n    public id: number,\n    public name: string,\n    public executiveSummary: string,\n    public asset: number,\n    public jiraId: string,\n    public testUrl: URL,\n    public prodUrl: URL,\n    public scope: string,\n    public tag: number,\n    public startDate: Date,\n    public endDate: Date,\n    public testers: User[]\n  ) {}\n}"
  },
  {
    "path": "frontend/src/app/assessment-form/assessment-form.component.html",
    "content": "<div class=\"container-fluid\">\n  <form [formGroup]=\"assessmentForm\" (ngSubmit)=\"onSubmit(assessmentForm)\">\n    <div class=\"form-group col-6 mx-auto\">\n      <label for=\"assessmentName\">Assessment Name</label>\n      <input formControlName=\"name\" type=\"text\" class=\"form-control\" id=\"assessmentName\" />\n      <label for=\"executiveSummary\">Executive Summary</label>\n      <textarea formControlName=\"executiveSummary\" type=\"text\" class=\"form-control\" id=\"executiveSummary\"\n        maxlength=\"4000\" rows=\"6\"></textarea>\n      <label for=\"jiraId\">JIRA URL</label>\n      <input formControlName=\"jiraId\" type=\"text\" class=\"form-control\" id=\"jiraId\" />\n      <label for=\"testUrl\">Test URL</label>\n      <input formControlName=\"testUrl\" type=\"text\" class=\"form-control\" id=\"testUrl\" />\n      <label for=\"prodUrl\">Production URL</label>\n      <input formControlName=\"prodUrl\" type=\"text\" class=\"form-control\" id=\"prodUrl\" />\n      <label for=\"scope\">Scope of the Assessment</label>\n      <textarea formControlName=\"scope\" type=\"text\" class=\"form-control\" id=\"scope\"></textarea>\n      <label for=\"tag\">Source Code Tag</label>\n      <input formControlName=\"tag\" type=\"text\" class=\"form-control\" id=\"tag\" />\n      <label for=\"testers\">Testers</label>\n      <ng-select [items]=\"testers\" bindLabel=\"firstName\" labelForId=\"testerList\" [multiple]=\"true\" clearAllText=\"Clear\"\n        formControlName=\"testers\">\n        <ng-template ng-label-tmp let-item=\"item\" let-clear=\"clear\">\n          <span class=\"ng-value-icon right\" (click)=\"clear(item)\">×</span>\n          {{item.firstName}} {{item.lastName}}\n        </ng-template>\n        <ng-template ng-option-tmp let-item=\"item\" let-search=\"searchTerm\">\n          {{item.firstName}} {{item.lastName}}\n        </ng-template>\n      </ng-select>\n      <label for=\"startDate\" class=\"col-form-label\">Start Date</label>\n      <input formControlName=\"startDate\" class=\"form-control\" type=\"date\" id=\"startDate\" />\n      <label for=\"endDate\" class=\"col-form-label\">End Date</label>\n      <input formControlName=\"endDate\" class=\"form-control\" type=\"date\" id=\"endDate\" />\n      <br />\n      <button *ngIf=\"!readOnly\" [disabled]=\"!assessmentForm.valid\" class=\"btn btn-primary float-right\" type=\"submit\"\n        data-toggle=\"tooltip\" data-placement=\"bottom\" title=\"Submit\">\n        Submit\n      </button>\n      <button style=\"margin-right: 5px;\" (click)=\"navigateToAssessments()\" class=\"btn btn-secondary float-right\"\n        type=\"button\" data-toggle=\"tooltip\" data-placement=\"bottom\" title=\"Back to Assessments\">\n        Back to Assessments\n      </button>\n    </div>\n  </form>\n</div>\n"
  },
  {
    "path": "frontend/src/app/assessment-form/assessment-form.component.sass",
    "content": ""
  },
  {
    "path": "frontend/src/app/assessment-form/assessment-form.component.spec.ts",
    "content": "import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';\n\nimport { AssessmentFormComponent } from './assessment-form.component';\n\ndescribe('AssessmentFormComponent', () => {\n  let component: AssessmentFormComponent;\n  let fixture: ComponentFixture<AssessmentFormComponent>;\n\n  beforeEach(waitForAsync(() => {\n    TestBed.configureTestingModule({\n      declarations: [ AssessmentFormComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(AssessmentFormComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/assessment-form/assessment-form.component.ts",
    "content": "import { Component, OnInit, OnChanges } from '@angular/core';\nimport { Router, ActivatedRoute, RouterOutlet } from '@angular/router';\nimport { AppService } from '../app.service';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { Assessment } from './Assessment';\nimport { AlertService } from '../alert/alert.service';\nimport { User } from '../interfaces/User';\n\n@Component({\n  selector: 'app-assessment-form',\n  templateUrl: './assessment-form.component.html',\n  styleUrls: ['./assessment-form.component.sass'],\n})\nexport class AssessmentFormComponent implements OnInit, OnChanges {\n  public assessmentModel: Assessment;\n  public assessmentForm: FormGroup;\n  public assetId: number;\n  public assessmentId: number;\n  public orgId: number;\n  public testers: User[] = [];\n  public readOnly: boolean;\n  constructor(\n    public appService: AppService,\n    private fb: FormBuilder,\n    public route: Router,\n    public activatedRoute: ActivatedRoute,\n    private alertService: AlertService\n  ) {\n    this.createForm();\n  }\n\n  ngOnInit() {\n    this.activatedRoute.data.subscribe(({ result }) => {\n      if (result && result.assessment) {\n        this.readOnly = result.assessment.readOnly;\n        if (this.readOnly) {\n          this.assessmentForm.disable();\n        }\n      }\n      if (result?.assessment?.assessment) {\n        result.assessment.assessment.startDate = this.transformDate(\n          result.assessment.assessment.startDate\n        );\n        result.assessment.assessment.endDate = this.transformDate(\n          result.assessment.assessment.endDate\n        );\n        this.testers = result.testers;\n        this.assessmentForm.patchValue(result.assessment.assessment);\n      } else {\n        this.testers = result;\n      }\n    });\n    this.activatedRoute.params.subscribe((params) => {\n      this.assetId = +params.assetId;\n      this.assessmentId = +params.assessmentId;\n      this.orgId = +params.orgId;\n    });\n  }\n\n  /**\n   * Function responsible to detect changes for the form and rebuild it\n   */\n  ngOnChanges() {\n    this.rebuildForm();\n  }\n\n  /**\n   * Function responsible for formatting the date data in the form for\n   * storage\n   * @param date is the date data from the form\n   * @returns formatted date to be stored\n   */\n  transformDate(date: string) {\n    return date.substring(0, 10);\n  }\n\n  /**\n   * Function responsible for rebuilding the reactive form in Angular\n   */\n  rebuildForm() {\n    this.assessmentForm.reset({\n      name: this.assessmentModel.name,\n      executiveSummary: this.assessmentModel.executiveSummary,\n      jiraId: this.assessmentModel.jiraId,\n      testUrl: this.assessmentModel.testUrl,\n      prodUrl: this.assessmentModel.prodUrl,\n      scope: this.assessmentModel.scope,\n      tag: this.assessmentModel.tag,\n      startDate: this.assessmentModel.startDate,\n      endDate: this.assessmentModel.endDate,\n      testers: this.assessmentModel.testers,\n    });\n  }\n\n  /**\n   * Function responsible for creating the reactive form in Angular\n   */\n  createForm() {\n    this.assessmentForm = this.fb.group({\n      name: ['', [Validators.required]],\n      executiveSummary: ['', Validators.maxLength(4000)],\n      jiraId: ['', []],\n      testUrl: ['', [Validators.required]],\n      prodUrl: ['', [Validators.required]],\n      scope: ['', [Validators.required]],\n      tag: ['', []],\n      startDate: ['', [Validators.required]],\n      endDate: ['', [Validators.required]],\n      testers: ['', [Validators.required]],\n    });\n  }\n\n  /**\n   * Function responsible for handling the form submission and triggers a call\n   * to the createOrUpdateAssessment() function\n   */\n  onSubmit(assessment: FormGroup) {\n    this.assessmentModel = assessment.value;\n    this.createOrUpdateAssessment(this.assessmentModel);\n  }\n\n  /**\n   * Function responsible for handling the form data and creating or updating\n   * an Assessment\n   * @param assessment form object data passed from OnSubmit()\n   */\n  createOrUpdateAssessment(assessment: Assessment) {\n    if (this.assessmentId) {\n      this.appService\n        .updateAssessment(assessment, this.assessmentId, this.assetId)\n        .subscribe((res: string) => {\n          this.navigateToAssessments();\n          this.alertService.success(res);\n        });\n    } else {\n      this.assessmentModel.asset = this.assetId;\n      this.appService\n        .createAssessment(this.assessmentModel)\n        .subscribe((res: string) => {\n          this.navigateToAssessments();\n          this.alertService.success(res);\n        });\n    }\n  }\n\n  /**\n   * Function responsible for navigating the user back to the Assessments View\n   */\n  navigateToAssessments() {\n    this.route.navigate([`organization/${this.orgId}/asset/${this.assetId}`]);\n  }\n}\n"
  },
  {
    "path": "frontend/src/app/assessments/assessments.component.html",
    "content": "<div class=\"container-fluid\">\n  <p-table #assessmentTable [value]=\"assessmentAry\" [columns]=\"cols\" [paginator]=\"true\" [rows]=\"10\"\n    styleClass=\"p-datatable-striped\" [sortField]=\"'id'\" sortOrder=\"-1\">\n\n    <ng-template pTemplate=\"header\" let-columns>\n      <tr>\n        <th *ngFor=\"let col of columns\" [pSortableColumn]=\"col.field\">\n          {{col.header}}\n          <p-sortIcon [field]=\"col.field\"></p-sortIcon>\n        </th>\n        <th scope=\"col\"></th>\n      </tr>\n\n      <tr>\n        <th *ngFor=\"let col of columns\" [ngSwitch]=\"col.field\">\n          <input *ngSwitchCase=\"'id'\" pInputText type=\"text\" placeholder=\"Search by ID\" class=\"p-column-filter\"\n            (input)=\"assessmentTable.filter($event.target.value, col.field, col.filterMatchMode)\">\n\n          <input *ngSwitchCase=\"'name'\" pInputText type=\"text\" placeholder=\"Search by Name\" class=\"p-column-filter\"\n            (input)=\"assessmentTable.filter($event.target.value, col.field, col.filterMatchMode)\">\n\n          <p-multiSelect *ngSwitchCase=\"'testers'\" placeholder=\"All\" styleClass=\"p-column-filter\" optionLabel=\"name\"\n            [options]=\"testers\" (onChange)=\" assessmentTable.filter($event.value, col.field, col.filterMatchMode)\">\n          </p-multiSelect>\n\n          <input *ngSwitchCase=\"'jiraId'\" pInputText type=\"text\" placeholder=\"Search by Jira ID\" class=\"p-column-filter\"\n            (input)=\"assessmentTable.filter($event.target.value, col.field, col.filterMatchMode)\">\n\n          <p-calendar *ngSwitchCase=\"'startDate'\" styleClass=\"p-column-filter\" placeholder=\"Start Date\"\n            dateFormat=\"m-d-yy\" [readonlyInput]=\"true\" [showButtonBar]=\"true\"\n            (onSelect)=\"onDateSelect($event, 'startDate')\"\n            (onClearClick)=\"assessmentTable.filter('', col.field, col.filterMatchMode)\"></p-calendar>\n\n          <p-calendar *ngSwitchCase=\"'endDate'\" styleClass=\"p-column-filter\" placeholder=\"End Date\" dateFormat=\"m-d-yy\"\n            [readonlyInput]=\"true\" [showButtonBar]=\"true\" (onSelect)=\"onDateSelect($event, 'endDate')\"\n            (onClearClick)=\"assessmentTable.filter('', col.field, col.filterMatchMode)\"></p-calendar>\n        </th>\n        <th></th>\n      </tr>\n    </ng-template>\n\n    <ng-template pTemplate=\"body\" let-assessment let-columns=\"columns\">\n      <tr>\n        <td scope=\"row\">{{ assessment.id }}</td>\n        <td>{{ assessment?.name }}</td>\n        <td>\n          <div *ngFor=\"let tester of assessment.testers; let last = last\">\n            {{tester?.firstName}} {{tester?.lastName}}<span *ngIf=\"!last\">,</span>\n          </div>\n        </td>\n        <td>\n          <a target=\"_blank\" [href]=\"assessment?.jiraId\">{{\n            assessment?.jiraId\n            }}</a>\n        </td>\n        <td>{{ assessment?.startDate | date: \"shortDate\":'UTC' }}</td>\n        <td>{{ assessment?.endDate | date: \"shortDate\":'UTC' }}</td>\n        <td>\n          <button class=\"btn btn-secondary\" type=\"button\" (click)=\"navigateToAssessmentById(assessment.id)\"\n            style=\"margin-right: 10px;\" data-toggle=\"tooltip\" data-placement=\"bottom\" title=\"Edit Assessment\">\n            <i *ngIf='readOnly' class=\"pi pi-eye\"></i>\n            <i *ngIf='!readOnly' class=\"pi pi-pencil\"></i>\n          </button>\n          <button style=\"margin-right: 10px;\" class=\"btn btn-primary\" type=\"button\"\n            (click)=\"navigateToVulnerability(assessment.id)\" data-toggle=\"tooltip\" data-placement=\"bottom\"\n            title=\"View Vulnerabilities\">\n            <i class=\"pi pi-ticket\"></i>\n          </button>\n          <button *ngIf=\"!readOnly\" (click)=\"deleteAssessment(assessment)\" class=\"btn btn-dark\" type=\"button\">\n            <i class=\"pi pi-trash\"></i>\n          </button>\n        </td>\n      </tr>\n    </ng-template>\n  </p-table>\n  <button *ngIf=\"!readOnly\" (click)=\"navigateToAssessment()\" type=\"button\" class=\"btn btn-primary float-right\"\n    data-toggle=\"tooltip\" data-placement=\"bottom\" title=\"Create Assessment\">\n    Create Assessment\n  </button>\n  <button (click)=\"navigateToOrganization()\" type=\"button\" class=\"btn btn-secondary float-right\"\n    style=\"margin-right: 5px;\" data-toggle=\"tooltip\" data-placement=\"bottom\" title=\"Back to Organization\">\n    Back to Organization\n  </button>\n</div>\n"
  },
  {
    "path": "frontend/src/app/assessments/assessments.component.sass",
    "content": ""
  },
  {
    "path": "frontend/src/app/assessments/assessments.component.spec.ts",
    "content": "import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';\n\nimport { AssessmentsComponent } from './assessments.component';\n\ndescribe('AssessmentsComponent', () => {\n  let component: AssessmentsComponent;\n  let fixture: ComponentFixture<AssessmentsComponent>;\n\n  beforeEach(waitForAsync(() => {\n    TestBed.configureTestingModule({\n      declarations: [ AssessmentsComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(AssessmentsComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/assessments/assessments.component.ts",
    "content": "import { Component, OnInit, ViewChild } from '@angular/core';\nimport { FilterService, FilterMatchMode } from 'primeng/api';\nimport { AppService } from '../app.service';\nimport { AlertService } from '../alert/alert.service';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { Assessment } from '../assessment-form/Assessment';\nimport { Table } from 'primeng/table';\nimport { User } from '../interfaces/User';\nimport { UserService } from '../user.service';\n\n// User decorated with compound name field\ninterface FormattedUser extends User {\n  name: string;\n}\n\n@Component({\n  selector: 'app-assessments',\n  templateUrl: './assessments.component.html',\n  styleUrls: ['./assessments.component.sass'],\n})\nexport class AssessmentsComponent implements OnInit {\n  assessmentAry: any = [];\n  assetId: number;\n  orgId: number;\n  testers: FormattedUser[];\n  readOnly: boolean;\n  @ViewChild('assessmentTable') table: Table;\n\n  cols = [\n    {\n      field: 'id',\n      filterMatchMode: FilterMatchMode.CONTAINS,\n      header: 'Assessment ID',\n    },\n    {\n      field: 'name',\n      filterMatchMode: FilterMatchMode.CONTAINS,\n      header: 'Assessment Name',\n    },\n    {\n      field: 'testers',\n      filterMatchMode: 'arrayCompare',\n      header: 'Testers',\n    },\n    {\n      field: 'jiraId',\n      filterMatchMode: FilterMatchMode.CONTAINS,\n      header: 'Jira ID',\n    },\n    {\n      field: 'startDate',\n      filterMatchMode: FilterMatchMode.EQUALS,\n      header: 'Start Date',\n    },\n    {\n      field: 'startDate',\n      filterMatchMode: FilterMatchMode.EQUALS,\n      header: 'End Date',\n    },\n  ];\n\n  constructor(\n    public activatedRoute: ActivatedRoute,\n    public router: Router,\n    public appService: AppService,\n    public alertService: AlertService,\n    public userService: UserService,\n    private filterService: FilterService\n  ) {}\n\n  ngOnInit() {\n    this.activatedRoute.data.subscribe(({ assessments }) => {\n      this.readOnly = assessments.readOnly;\n      this.assessmentAry = assessments.assessments;\n    });\n    this.activatedRoute.params.subscribe((params) => {\n      this.assetId = params.assetId;\n      this.orgId = params.orgId;\n    });\n    this.userService.getUsers().subscribe((testers) => {\n      // add a composite 'name' field to the Testers to display in MultiSelect\n      this.testers = testers.map((tester) => ({\n        ...tester,\n        name: this.formatName(tester),\n      }));\n    });\n\n    this.addArrayCompareTableFilter();\n  }\n\n  /**\n   * Create custom table filter \"matchMode\" to compare multiselect filter array values to array of values in a table row field\n   */\n  private addArrayCompareTableFilter() {\n    this.filterService.register(\n      'arrayCompare',\n      (values: User[], selections: FormattedUser[]): boolean => {\n        return values.some((value) => {\n          return !!selections.some(\n            (selection) => selection.name === this.formatName(value)\n          );\n        });\n      }\n    );\n  }\n\n  /**\n   * Function responsible for directing the user to the vulnerability details\n   * @param id of vulnerability to load\n   */\n  navigateToVulnerability(id: number) {\n    this.router.navigate([\n      `organization/${this.orgId}/asset/${this.assetId}/assessment/${id}/vulnerability`,\n    ]);\n  }\n\n  /**\n   * Function responsible for directing the user back to the assessments view\n   * passes organization id to fetch data\n   */\n  navigateToOrganization() {\n    this.router.navigate([`organization/${this.orgId}`]);\n  }\n\n  /**\n   * Function responsible for directing the user to the main Assessment view\n   */\n  navigateToAssessment() {\n    this.router.navigate([\n      `organization/${this.orgId}/asset/${this.assetId}/assessment`,\n    ]);\n  }\n\n  /**\n   * Function responsible for directing the user to an assessment view with provided\n   * ID\n   * @param assessmentId is the ID associated to the assessment to load\n   */\n  navigateToAssessmentById(assessmentId: number) {\n    this.router.navigate([\n      `organization/${this.orgId}/asset/${this.assetId}/assessment/${assessmentId}`,\n    ]);\n  }\n\n  /**\n   * Delete assessment by ID\n   * ID\n   * @param assessmentId is the ID associated to the assessment to load\n   */\n  deleteAssessment(assessment: Assessment) {\n    const r = confirm(`Delete the assessment \"${assessment.name}\"`);\n    if (r === true) {\n      this.appService\n        .deleteAssessment(assessment.id)\n        .subscribe((success: string) => {\n          this.alertService.success(success);\n          this.appService\n            .getAssessments(this.orgId)\n            .then((res) => (this.assessmentAry = res));\n        });\n    }\n  }\n\n  onDateSelect(value, type) {\n    const date = new Date(value);\n    date.setUTCHours(0, 0, 0, 0);\n    if (type === 'startDate') {\n      this.table.filter(date.toISOString(), 'startDate', 'equals');\n    } else if (type === 'endDate') {\n      this.table.filter(date.toISOString(), 'endDate', 'equals');\n    }\n  }\n\n  /**\n   * Format composite name from first and last name fields\n   */\n  private formatName(user) {\n    return `${user.firstName} ${user.lastName}`;\n  }\n}\n"
  },
  {
    "path": "frontend/src/app/asset-form/Asset.ts",
    "content": "import { Jira } from './Jira';\n\nexport interface Asset {\n  id: number;\n  name: string;\n  organization: number;\n  jira?: Jira;\n}\n"
  },
  {
    "path": "frontend/src/app/asset-form/Jira.ts",
    "content": "export interface Jira {\n  id?: number;\n  username?: string;\n  host?: string;\n  url?: string;\n  apiKey?: string;\n}\n"
  },
  {
    "path": "frontend/src/app/asset-form/asset-form.component.html",
    "content": "<div class=\"col-4 mx-auto\">\n  <form [formGroup]=\"assetForm\" (ngSubmit)=\"onSubmit(assetForm)\">\n    <div class=\"form-group\">\n      <label for=\"assetName\">Asset Name</label>\n      <input pInputText formControlName=\"name\" type=\"text\" class=\"form-control\" id=\"assetName\" />\n      <div class=\"card\">\n        <div class=\"card-body\">\n          <div class=\"col-md-12 text-center\">\n            <h5>Jira Integration</h5>\n            <hr />\n          </div>\n          <div formGroupName=\"jira\">\n            <label for=\"username\">Username</label>\n            <input [readonly]=\"!canAddApiKey\" formControlName=\"username\" type=\"text\" class=\"form-control\" id=\"username\"\n              pInputText />\n            <label for=\"host\" style=\"margin-top: 10px\">Host</label>\n            <input pInputText [readonly]=\"!canAddApiKey\" formControlName=\"host\" type=\"text\" class=\"form-control\"\n              id=\"host\" />\n            <label for=\"apiKey\" style=\"margin-top: 10px\">API Key</label>\n            <input pPassword promptLabel=\"Enter the API key\" [readonly]=\"!canAddApiKey\" formControlName=\"apiKey\"\n              type=\"password\" class=\"form-control\" id=\"apiKey\" [placeholder]=\"canAddApiKey ? '':keyPlaceholder\" />\n          </div>\n          <br>\n          <button *ngIf=\"isAdmin\" pButton [disabled]=\"canAddApiKey\" type=\"button\" class=\"float-right btn btn-warning\"\n            (click)=\"purgeJiraInfo()\" label=\"Purge\">\n          </button>\n        </div>\n      </div>\n    </div>\n    <button *ngIf=\"isAdmin\" pButton [disabled]=\"!assetForm.valid\" class=\"btn btn-primary float-right\" type=\"submit\"\n      data-toggle=\"tooltip\" data-placement=\"bottom\" label=\"Submit\">\n    </button>\n    <button pButton (click)=\"navigateToAssets()\" class=\"btn btn-secondary float-right\" type=\"button\"\n      style=\"margin-right: 5px\" data-toggle=\"tooltip\" data-placement=\"bottom\" label=\"Back to Assets\">\n    </button>\n  </form>\n</div>\n"
  },
  {
    "path": "frontend/src/app/asset-form/asset-form.component.sass",
    "content": ""
  },
  {
    "path": "frontend/src/app/asset-form/asset-form.component.spec.ts",
    "content": "import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';\n\nimport { AssetFormComponent } from './asset-form.component';\n\ndescribe('AssetFormComponent', () => {\n  let component: AssetFormComponent;\n  let fixture: ComponentFixture<AssetFormComponent>;\n\n  beforeEach(waitForAsync(() => {\n    TestBed.configureTestingModule({\n      declarations: [ AssetFormComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(AssetFormComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/asset-form/asset-form.component.ts",
    "content": "import { Component, OnInit, OnChanges } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { Asset } from './Asset';\nimport { AppService } from '../app.service';\nimport { Router, ActivatedRoute } from '@angular/router';\nimport { AlertService } from '../alert/alert.service';\nimport { AuthService } from '../auth.service';\n@Component({\n  selector: 'app-asset-form',\n  templateUrl: './asset-form.component.html',\n  styleUrls: ['./asset-form.component.sass'],\n})\nexport class AssetFormComponent implements OnInit, OnChanges {\n  public assetModel: Asset;\n  public assetForm: FormGroup;\n  public orgId: number;\n  public assetId: number;\n  public keyPlaceholder = '************************';\n  public canAddApiKey = true;\n  public isAdmin: boolean;\n  constructor(\n    private fb: FormBuilder,\n    public appService: AppService,\n    public route: Router,\n    public activatedRoute: ActivatedRoute,\n    private alertService: AlertService,\n    public authService: AuthService\n  ) {\n    this.createForm();\n  }\n\n  ngOnInit() {\n    this.activatedRoute.data.subscribe(({ asset }) => {\n      this.isAdmin = this.authService.isAdmin();\n      if (asset) {\n        this.assetModel = asset;\n        if (!this.isAdmin) {\n          this.assetForm.disable();\n        }\n        this.rebuildForm();\n        if (asset.jira) {\n          this.canAddApiKey = false;\n        } else {\n          this.canAddApiKey = true;\n        }\n      }\n    });\n    this.activatedRoute.params.subscribe((params) => {\n      this.orgId = params.id;\n      this.assetId = params.assetId;\n    });\n  }\n\n  /**\n   * Function responsible to detect changes for the form and rebuild it\n   */\n  ngOnChanges() {\n    this.rebuildForm();\n  }\n\n  /**\n   * Function responsible for creating the reactive form in Angular\n   */\n  createForm() {\n    this.assetForm = this.fb.group({\n      name: ['', [Validators.required]],\n      jira: this.fb.group({\n        username: ['', []],\n        host: ['', []],\n        apiKey: ['', []],\n      }),\n    });\n  }\n\n  /**\n   * Function responsible for rebuilding the reactive form in Angular\n   */\n  rebuildForm() {\n    this.assetForm.reset({\n      name: this.assetModel.name,\n      jira: {\n        username: this.assetModel?.jira?.username,\n        host: this.assetModel?.jira?.host,\n        apiKey: this.assetModel?.jira?.apiKey,\n      },\n    });\n  }\n\n  /**\n   * Function responsible for processing the data from the reactive from\n   * @param asset data object holding all the form data\n   */\n  onSubmit(asset: FormGroup) {\n    this.assetModel = asset.value;\n    this.assetModel.organization = this.orgId;\n    this.assetModel.id = this.assetId;\n    if (!this.canAddApiKey) {\n      this.assetModel.jira = null;\n    }\n    this.createOrUpdateAsset(this.assetModel);\n  }\n\n  /**\n   * Function responsible for sending the user back to Assets listing\n   */\n  navigateToAssets() {\n    this.route.navigate([`organization/${this.orgId}`]);\n  }\n\n  purgeJiraInfo() {\n    const r = confirm(`Purge API Key for Asset: \"${this.assetModel.name}\"?`);\n    if (r) {\n      this.appService.purgeJira(this.assetId).subscribe((res: string) => {\n        this.alertService.success(res);\n        this.appService\n          .getAsset(this.assetId, this.orgId)\n          .subscribe((asset: Asset) => {\n            this.assetModel = asset;\n            this.canAddApiKey = true;\n            this.rebuildForm();\n          });\n      });\n    }\n  }\n\n  /**\n   * Function responsible for creating or updating an asset tied to\n   * an organization\n   * @param asset object holding all the asset data\n   */\n  createOrUpdateAsset(asset: Asset) {\n    if (this.assetId) {\n      this.appService.updateAsset(asset).subscribe((res: string) => {\n        this.navigateToAssets();\n        this.alertService.success(res);\n      });\n    } else {\n      this.appService.createAsset(asset).subscribe((res: string) => {\n        this.navigateToAssets();\n        this.alertService.success(res);\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "frontend/src/app/auth.guard.spec.ts",
    "content": "import { TestBed, inject, waitForAsync } from '@angular/core/testing';\n\nimport { AuthGuard } from './auth.guard';\n\ndescribe('AuthGuard', () => {\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      providers: [AuthGuard]\n    });\n  });\n\n  it('should ...', inject([AuthGuard], (guard: AuthGuard) => {\n    expect(guard).toBeTruthy();\n  }));\n});\n"
  },
  {
    "path": "frontend/src/app/auth.guard.ts",
    "content": "import { Injectable } from '@angular/core';\nimport {\n  CanActivate,\n  ActivatedRouteSnapshot,\n  RouterStateSnapshot,\n  UrlTree,\n  Router\n} from '@angular/router';\nimport { Observable } from 'rxjs';\nimport { AuthService } from './auth.service';\nimport { GlobalManagerService } from './global-manager.service';\n\n@Injectable({\n  providedIn: 'root'\n})\nexport class AuthGuard implements CanActivate {\n  constructor(\n    private globalManager: GlobalManagerService,\n    private router: Router\n  ) {}\n  canActivate(\n    next: ActivatedRouteSnapshot,\n    state: RouterStateSnapshot\n  ):\n    | Observable<boolean | UrlTree>\n    | Promise<boolean | UrlTree>\n    | boolean\n    | UrlTree {\n    return this.checkLogin();\n  }\n\n  checkLogin(): boolean {\n    if (localStorage.getItem('AUTH_TOKEN')) {\n      this.globalManager.showLogin(true);\n      return true;\n    } else {\n      // Navigate to the login page with extras\n      this.router.navigate(['/login']);\n      this.globalManager.showLogin(false);\n      return false;\n    }\n  }\n}\n"
  },
  {
    "path": "frontend/src/app/auth.service.spec.ts",
    "content": "import { TestBed } from '@angular/core/testing';\n\nimport { AuthService } from './auth.service';\n\ndescribe('AuthService', () => {\n  beforeEach(() => TestBed.configureTestingModule({}));\n\n  it('should be created', () => {\n    const service: AuthService = TestBed.inject(AuthService);\n    expect(service).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/auth.service.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { environment } from '../environments/environment';\nimport { Tokens } from './interfaces/Tokens';\nimport jwt_decode from 'jwt-decode';\n@Injectable({\n  providedIn: 'root',\n})\nexport class AuthService {\n  constructor(private http: HttpClient) {}\n\n  api = environment.apiUrl;\n  isLoggedIn = false;\n\n  login(creds) {\n    return this.http.post(`${this.api}/login`, creds);\n  }\n\n  logout() {\n    localStorage.removeItem('REFRESH_TOKEN');\n    localStorage.removeItem('AUTH_TOKEN');\n  }\n\n  setTokens(tokens: Tokens) {\n    localStorage.setItem('AUTH_TOKEN', tokens.token);\n    localStorage.setItem('REFRESH_TOKEN', tokens.refreshToken);\n  }\n\n  getUserToken() {\n    return localStorage.getItem('AUTH_TOKEN');\n  }\n\n  getUserFromToken() {\n    return jwt_decode(localStorage.getItem('AUTH_TOKEN'));\n  }\n\n  isAdmin() {\n    const token = this.getUserFromToken();\n    let found = false;\n    // tslint:disable-next-line: no-string-literal\n    found = token['admin'];\n    return found;\n  }\n\n  getRefreshToken() {\n    return localStorage.getItem('REFRESH_TOKEN');\n  }\n\n  forgotPassword(email) {\n    return this.http.patch(`${this.api}/forgot-password`, email);\n  }\n\n  passwordReset(creds) {\n    return this.http.patch(`${this.api}/password-reset`, creds);\n  }\n\n  updatePassword(\n    oldPassword: string,\n    newPassword: string,\n    confirmNewPassword: string\n  ) {\n    return this.http.patch(`${this.api}/user/password`, {\n      oldPassword,\n      newPassword,\n      confirmNewPassword,\n    });\n  }\n\n  updateUserEmail(email: string, newEmail: string) {\n    return this.http.post(`${this.api}/user/email`, {\n      email,\n      newEmail,\n    });\n  }\n\n  validateUserEmailRequest(password: string, uuid: string) {\n    return this.http.post(`${this.api}/user/email/validate`, {\n      password,\n      uuid,\n    });\n  }\n\n  revokeUserEmail() {\n    return this.http.post(`${this.api}/user/email/revoke`, null);\n  }\n\n  refreshSession() {\n    const refreshToken = this.getRefreshToken();\n    return this.http.post(`${this.api}/refresh`, { refreshToken });\n  }\n\n  generateApiKey() {\n    return this.http.post(`${this.api}/user/key`, null);\n  }\n\n  getApiKeyInfo() {\n    return this.http.get(`${this.api}/user/key`);\n  }\n\n  getApiKeysInfo() {\n    return this.http.get(`${this.api}/keys`);\n  }\n\n  deactivateApiKey(id: number) {\n    return this.http.patch(`${this.api}/user/key/${id}`, null);\n  }\n\n  adminDeactivateApiKey(id: number) {\n    return this.http.patch(`${this.api}/key/${id}`, null);\n  }\n}\n"
  },
  {
    "path": "frontend/src/app/classes/Alert.ts",
    "content": "export class Alert {\n  type: AlertType;\n  message: string;\n  alertId: string;\n  keepAfterRouteChange: boolean;\n\n  constructor(init?: Partial<Alert>) {\n    Object.assign(this, init);\n  }\n}\n\nexport enum AlertType {\n  Success,\n  Error,\n  Info,\n  Warning\n}\n"
  },
  {
    "path": "frontend/src/app/classes/ProblemLocation.ts",
    "content": "export class ProblemLocation {\n  constructor(private id: number, public location: string, public target: string) {}\n}\n"
  },
  {
    "path": "frontend/src/app/classes/Resource.ts",
    "content": "export class Resource {\n  constructor(private id: number, public resURL: string) {}\n}\n"
  },
  {
    "path": "frontend/src/app/dashboard/dashboard.component.html",
    "content": "<div class=\"container-fluid\">\n  <p-table #orgTable [value]=\"orgAry\">\n    <ng-template pTemplate=\"header\">\n      <tr>\n        <th pSortableColumn=\"id\">Organization ID<p-sortIcon field=\"id\"></p-sortIcon>\n        </th>\n        <th pSortableColumn=\"id\">Organization Name<p-sortIcon field=\"name\"></p-sortIcon>\n        </th>\n        <th></th>\n      </tr>\n      <tr>\n        <th>\n          <input pInputText type=\"text\" (input)=\"orgTable.filter($event.target.value, 'id', 'equals')\"\n            placeholder=\"Search by ID\" class=\"p-column-filter\">\n        </th>\n        <th>\n          <input pInputText type=\"text\" (input)=\"orgTable.filter($event.target.value, 'name', 'startsWith')\"\n            placeholder=\"Search by Name\" class=\"p-column-filter\">\n        </th>\n        <th></th>\n      </tr>\n    </ng-template>\n    <ng-template pTemplate=\"body\" let-org>\n      <tr>\n        <td>{{org.id}}</td>\n        <td>{{org.name}}</td>\n        <td>\n          <button *ngIf=\"!isArchive\" (click)=\"navigateToOrganization(org.id)\" class=\"btn btn-secondary\"\n            style=\"margin-right: 5px;\" data-toggle=\"tooltip\" data-placement=\"bottom\" title=\"Edit {{ org.name }}\">\n            <i *ngIf=\"isAdmin\" class=\"pi pi-pencil\"></i>\n            <i *ngIf=\"!isAdmin\" class=\"pi pi-eye\"></i>\n          </button>\n          <button *ngIf=\"!isArchive\" (click)=\"navigateToAsset(org.id)\" class=\"btn btn-primary\"\n            style=\"margin-right: 5px;\" data-toggle=\"tooltip\" data-placement=\"bottom\"\n            title=\"View Assets of {{ org.name }}\">\n            <i class=\"pi pi-list\"></i>\n          </button>\n          <button *ngIf=\"!isArchive && isAdmin\" (click)=\"archiveOrganization(org)\" class=\"btn btn-warning\"\n            style=\"margin-right: 5px;\" data-toggle=\"tooltip\" data-placement=\"bottom\" title=\"Archive {{ org.name }}\">\n            <i class=\"pi pi-folder\"></i>\n          </button>\n          <button *ngIf=\"isArchive && isAdmin\" (click)=\"activateOrganization(org)\" class=\"btn btn-warning\"\n            style=\"margin-right: 5px;\" data-toggle=\"tooltip\" data-placement=\"bottom\" title=\"Activate {{ org.name }}\">\n            <i class=\"pi pi-folder-open\"></i>\n          </button>\n        </td>\n      </tr>\n    </ng-template>\n  </p-table>\n  <br>\n  <button (click)=\"navigateToCreate()\" *ngIf=\"!isArchive && isAdmin\" type=\"button\" class=\"btn btn-primary float-right\"\n    data-toggle=\"tooltip\" data-placement=\"bottom\" title=\"Add An Organization\">\n    Add Organization\n  </button>\n  <button style=\"margin-right: 5px;\" *ngIf=\"!isArchive\" (click)=\"getArchivedOrganizations()\" type=\"button\"\n    class=\"btn btn-info float-right\" data-toggle=\"tooltip\" data-placement=\"bottom\" title=\"View Archived Organizations\">\n    View Archive\n  </button>\n  <button style=\"margin-right: 5px;\" *ngIf=\"isArchive\" (click)=\"getOrganizations()\" type=\"button\"\n    class=\"btn btn-info float-right\" data-toggle=\"tooltip\" data-placement=\"bottom\" title=\"View Active Organizations\">\n    View Active\n  </button>\n</div>\n"
  },
  {
    "path": "frontend/src/app/dashboard/dashboard.component.sass",
    "content": ".card-img-top\n    padding: 20px"
  },
  {
    "path": "frontend/src/app/dashboard/dashboard.component.spec.ts",
    "content": "import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';\n\nimport { DashboardComponent } from './dashboard.component';\n\ndescribe('DashboardComponent', () => {\n  let component: DashboardComponent;\n  let fixture: ComponentFixture<DashboardComponent>;\n\n  beforeEach(waitForAsync(() => {\n    TestBed.configureTestingModule({\n      declarations: [ DashboardComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(DashboardComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/dashboard/dashboard.component.ts",
    "content": "import { Component, OnInit, ViewChild } from '@angular/core';\nimport { AppService } from '../app.service';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { Organization } from '../org-form/Organization';\nimport { AlertService } from '../alert/alert.service';\nimport { Table } from 'primeng/table';\nimport { AuthService } from '../auth.service';\n\n@Component({\n  selector: 'app-dashboard',\n  templateUrl: './dashboard.component.html',\n  styleUrls: ['./dashboard.component.sass'],\n})\nexport class DashboardComponent implements OnInit {\n  orgAry: any = [];\n  assetAry: any = [];\n  orgId: number;\n  isArchive = false;\n  isAdmin: boolean;\n  @ViewChild('orgTable') table: Table;\n  constructor(\n    private appService: AppService,\n    public activatedRoute: ActivatedRoute,\n    public router: Router,\n    private alertService: AlertService,\n    public authService: AuthService\n  ) {}\n\n  ngOnInit() {\n    this.getOrganizations();\n  }\n\n  /**\n   * Function responsible for retreving all organizational data\n   */\n  getOrganizations() {\n    this.appService.getOrganizations().then((res) => {\n      this.isArchive = false;\n      this.orgAry = res;\n      this.isAdmin = this.authService.isAdmin();\n    });\n  }\n\n  /**\n   * Function responsible for checking the status of an organization\n   * to determine if it is archived or not\n   */\n  getArchivedOrganizations() {\n    this.appService.getArchivedOrganizations().then((res) => {\n      this.isArchive = true;\n      this.orgAry = res;\n    });\n  }\n\n  /**\n   * Function responsible for navigating to assests tied to an organization\n   * @param id is the ID of the organization tied to the assets\n   */\n  navigateToAsset(id: number) {\n    this.router.navigate([`organization/${id}`]);\n  }\n\n  /**\n   * Function responsible for navigating the user to the organization form to\n   * either create or update an organization\n   */\n  navigateToCreate() {\n    this.router.navigate([`organization-form`]);\n  }\n\n  /**\n   * Function responsible for loading the organization the end user selects\n   * @param id is the associated ID of the organization requested\n   */\n  navigateToOrganization(id: number) {\n    this.router.navigate([`organization-form/${id}`]);\n  }\n\n  /**\n   * Function responsible for archiving an organization by\n   * toggling the associated status\n   * @param org is the ID of the organization to alter\n   */\n  archiveOrganization(org: Organization) {\n    const confirmed = confirm(`Archive the organization \"${org.name}\"?`);\n    if (confirmed) {\n      this.appService.archiveOrganization(org.id).subscribe((res: string) => {\n        this.getOrganizations();\n        this.alertService.success(res);\n      });\n    }\n  }\n\n  /**\n   * Function responsible for altering an organization status back\n   * to active\n   * @param org is the ID of the organization to alter\n   */\n  activateOrganization(org: Organization) {\n    const confirmed = confirm(`Activate the organization \"${org.name}\"?`);\n    if (confirmed) {\n      this.appService.activateOrganization(org.id).subscribe((res: string) => {\n        this.getOrganizations();\n        this.alertService.success(res);\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "frontend/src/app/email-validate/email-validate.component.html",
    "content": "<div class=\"container col-12 col-md-4\">\n  <p-card>\n          <h4>Email Update Validation</h4>\n          <hr />\n          <i class=\"pi pi-info-circle\"></i><i style=\"font-size: smaller;\"> Please enter your password to validate the\n            email update request</i>\n          <br>\n      <div id=\"formContent\">\n        <form [formGroup]=\"emailUpdateForm\" (ngSubmit)=\"onSubmit(emailUpdateForm)\">\n          <input formControlName=\"password\" type=\"password\" id=\"password\" class=\"form-control\" name=\"password\"\n            placeholder=\"Password\" style=\"margin-bottom: 5px;\" pInputText/>\n          <input [disabled]=\"!emailUpdateForm.valid\" type=\"submit\" class=\"btn btn-success float-right\" value=\"Submit\" pInputText/>\n        </form>\n        <div id=\"formFooter\">\n          <div>\n            <a class=\"underlineHover\" href=\"#/login\">Return to login</a>\n          </div>\n      </div>\n    </div>\n  </p-card>\n</div>\n"
  },
  {
    "path": "frontend/src/app/email-validate/email-validate.component.sass",
    "content": ""
  },
  {
    "path": "frontend/src/app/email-validate/email-validate.component.spec.ts",
    "content": "import { ComponentFixture, TestBed } from '@angular/core/testing';\n\nimport { EmailValidateComponent } from './email-validate.component';\n\ndescribe('EmailValidateComponent', () => {\n  let component: EmailValidateComponent;\n  let fixture: ComponentFixture<EmailValidateComponent>;\n\n  beforeEach(async () => {\n    await TestBed.configureTestingModule({\n      declarations: [ EmailValidateComponent ]\n    })\n    .compileComponents();\n  });\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(EmailValidateComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/email-validate/email-validate.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormGroup, FormBuilder, Validators } from '@angular/forms';\nimport { AuthService } from '../auth.service';\nimport { Router, ActivatedRoute } from '@angular/router';\nimport { AlertService } from '../alert/alert.service';\n\n@Component({\n  selector: 'app-email-validate',\n  templateUrl: './email-validate.component.html',\n  styleUrls: ['./email-validate.component.sass'],\n})\nexport class EmailValidateComponent implements OnInit {\n  uuid: string;\n  emailUpdateForm: FormGroup;\n  constructor(\n    private activatedRoute: ActivatedRoute,\n    private fb: FormBuilder,\n    public authService: AuthService,\n    public router: Router,\n    public alertService: AlertService\n  ) {\n    this.uuid = this.activatedRoute.snapshot.paramMap.get('uuid');\n    this.createForm();\n  }\n\n  ngOnInit(): void {}\n\n  createForm() {\n    this.emailUpdateForm = this.fb.group({\n      password: ['', Validators.required],\n    });\n  }\n\n  onSubmit(form) {\n    this.authService\n      .validateUserEmailRequest(form.value.password, this.uuid)\n      .subscribe((res: string) => {\n        this.router.navigate(['login']);\n        this.alertService.success(res);\n      });\n  }\n}\n"
  },
  {
    "path": "frontend/src/app/enums/roles.enum.ts",
    "content": "export const ROLE = {\n  ADMIN: 'Admin',\n  READONLY: 'Read-Only',\n  TESTER: 'Tester',\n};\n"
  },
  {
    "path": "frontend/src/app/footer/footer.component.html",
    "content": "<footer class=\"navbar-light bg-light fixed-bottom\">\n  <div class=\"container\">\n    <span> </span>\n  </div>\n</footer>\n"
  },
  {
    "path": "frontend/src/app/footer/footer.component.sass",
    "content": ""
  },
  {
    "path": "frontend/src/app/footer/footer.component.spec.ts",
    "content": "import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';\n\nimport { FooterComponent } from './footer.component';\n\ndescribe('FooterComponent', () => {\n  let component: FooterComponent;\n  let fixture: ComponentFixture<FooterComponent>;\n\n  beforeEach(waitForAsync(() => {\n    TestBed.configureTestingModule({\n      declarations: [ FooterComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(FooterComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/footer/footer.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\n\n@Component({\n  selector: 'app-footer',\n  templateUrl: './footer.component.html',\n  styleUrls: ['./footer.component.sass']\n})\nexport class FooterComponent implements OnInit {\n\n  constructor() { }\n\n  ngOnInit() {\n  }\n\n}\n"
  },
  {
    "path": "frontend/src/app/forgot-password/forgot-password.component.html",
    "content": "<div class=\"container col-12 col-md-4\">\n  <p-card>\n    <form [formGroup]=\"pwdResetForm\" (ngSubmit)=\"onSubmit(pwdResetForm)\">\n      <input\n        formControlName=\"email\"\n        type=\"text\"\n        id=\"email\"\n        class=\"form-control\"\n        name=\"email\"\n        placeholder=\"Email\"\n        style=\"margin-bottom: 5px;\"\n        pInputText\n      />\n      <input\n        [disabled]=\"!pwdResetForm.valid\"\n        type=\"submit\"\n        class=\"btn btn-success float-right\"\n        value=\"Submit\"\n        pInputText\n      />\n    </form>\n\n    <div id=\"formFooter\">\n      <div>\n        <a class=\"underlineHover\" href=\"#/login\">Back to login</a>\n      </div>\n    </div>\n  </p-card>\n</div>\n"
  },
  {
    "path": "frontend/src/app/forgot-password/forgot-password.component.sass",
    "content": ""
  },
  {
    "path": "frontend/src/app/forgot-password/forgot-password.component.spec.ts",
    "content": "import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';\n\nimport { ForgotPasswordComponent } from './forgot-password.component';\n\ndescribe('ForgotPasswordComponent', () => {\n  let component: ForgotPasswordComponent;\n  let fixture: ComponentFixture<ForgotPasswordComponent>;\n\n  beforeEach(waitForAsync(() => {\n    TestBed.configureTestingModule({\n      declarations: [ ForgotPasswordComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(ForgotPasswordComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/forgot-password/forgot-password.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormGroup, FormBuilder, Validators } from '@angular/forms';\nimport { AuthService } from '../auth.service';\nimport { Router } from '@angular/router';\nimport { AlertService } from '../alert/alert.service';\n\n@Component({\n  selector: 'app-forgot-password',\n  templateUrl: './forgot-password.component.html',\n  styleUrls: ['./forgot-password.component.sass'],\n})\nexport class ForgotPasswordComponent implements OnInit {\n  pwdResetForm: FormGroup;\n  constructor(\n    private fb: FormBuilder,\n    public authService: AuthService,\n    public router: Router,\n    public alertService: AlertService\n  ) {\n    this.createForm();\n  }\n\n  ngOnInit(): void {}\n\n  createForm() {\n    this.pwdResetForm = this.fb.group({\n      email: ['', [Validators.required, Validators.email]],\n    });\n  }\n\n  onSubmit(form) {\n    const email = { email: form.value.email };\n    this.authService.forgotPassword(email).subscribe((res: string) => {\n      this.alertService.success(res);\n      this.pwdResetForm.reset();\n    });\n  }\n}\n"
  },
  {
    "path": "frontend/src/app/global-manager.service.spec.ts",
    "content": "import { TestBed } from '@angular/core/testing';\n\nimport { GlobalManagerService } from './global-manager.service';\n\ndescribe('GlobalManagerService', () => {\n  beforeEach(() => TestBed.configureTestingModule({}));\n\n  it('should be created', () => {\n    const service: GlobalManagerService = TestBed.inject(GlobalManagerService);\n    expect(service).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/global-manager.service.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { BehaviorSubject, Observable } from 'rxjs';\n\n@Injectable({\n  providedIn: 'root'\n})\nexport class GlobalManagerService {\n  private loggedIn: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(\n    null\n  );\n  public loggedIn$: Observable<boolean> = this.loggedIn.asObservable();\n\n  constructor() {}\n\n  showLogin(ifShow: boolean) {\n    this.loggedIn.next(ifShow);\n  }\n}\n"
  },
  {
    "path": "frontend/src/app/interfaces/ApiKey.ts",
    "content": "export interface ApiKey {\n  id: number;\n  createdDate: Date;\n  lastUpdatedDate: Date;\n  active?: boolean;\n  email?: boolean;\n}\n"
  },
  {
    "path": "frontend/src/app/interfaces/App_File.ts",
    "content": "import { SafeUrl } from '@angular/platform-browser';\n\nexport interface AppFile extends File {\n  id: number;\n  buffer: any;\n  encoding: string;\n  fieldName: string;\n  mimetype: string;\n  originalname: string;\n  size: number;\n  imgUrl: SafeUrl;\n}\n"
  },
  {
    "path": "frontend/src/app/interfaces/Organization.ts",
    "content": "export interface Organization {\n  id: number;\n  name: string;\n  status: string;\n}\n"
  },
  {
    "path": "frontend/src/app/interfaces/Screenshot.ts",
    "content": "import { SafeUrl } from '@angular/platform-browser';\n\nexport interface Screenshot {\n    url: SafeUrl;\n    file: any;\n    fileName: string;\n    fileId: number;\n}\n"
  },
  {
    "path": "frontend/src/app/interfaces/Settings.ts",
    "content": "export interface Settings {\n  fromEmail?: string;\n  fromEmailPassword?: string;\n  companyName?: string;\n}\n"
  },
  {
    "path": "frontend/src/app/interfaces/Team.ts",
    "content": "import { Organization } from '../org-form/Organization';\nimport { Asset } from '../asset-form/Asset';\ninterface Role {\n  name: string;\n}\nexport interface Team {\n  id?: number;\n  name: string;\n  organization: Organization;\n  assets?: Asset[];\n  assetIds?: number[];\n  role: any;\n  users: number[];\n}\n"
  },
  {
    "path": "frontend/src/app/interfaces/Tokens.ts",
    "content": "export interface Tokens {\n  token: string;\n  refreshToken: string;\n}\n"
  },
  {
    "path": "frontend/src/app/interfaces/User.ts",
    "content": "import { Team } from './Team';\n\nexport interface User {\n  firstName: string;\n  lastName: string;\n  title: string;\n  password?: string;\n  confirmPassword?: string;\n  email?: string;\n  newEmail?: string;\n  id?: string;\n  active?: boolean;\n  teams?: Team[];\n}\n"
  },
  {
    "path": "frontend/src/app/loader.service.spec.ts",
    "content": "import { TestBed } from '@angular/core/testing';\n\nimport { LoaderService } from './loader.service';\n\ndescribe('LoaderService', () => {\n  beforeEach(() => TestBed.configureTestingModule({}));\n\n  it('should be created', () => {\n    const service: LoaderService = TestBed.inject(LoaderService);\n    expect(service).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/loader.service.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';\nimport { Subject } from 'rxjs/internal/Subject';\n\n@Injectable({\n  providedIn: 'root'\n})\nexport class LoaderService {\n  isLoading = new Subject<boolean>();\n  show() {\n    this.isLoading.next(true);\n  }\n  hide() {\n    this.isLoading.next(false);\n  }\n\n  constructor() {}\n}\n"
  },
  {
    "path": "frontend/src/app/login/login.component.html",
    "content": "<div class=\"container col-md-4 col-12\">\n  <p-card id=\"formContent\" class=\"card\">\n    <!-- Login Form -->\n    <form [formGroup]=\"loginForm\" (ngSubmit)=\"onSubmit(loginForm)\">\n      <input formControlName=\"email\" type=\"text\" id=\"email\" class=\"form-control text-center\" name=\"email\"\n        placeholder=\"Email\" style=\"margin-bottom: 5px;\" pInputText/>\n      <input formControlName=\"password\" type=\"password\" id=\"password\" class=\"form-control text-center\" name=\"password\"\n        placeholder=\"Password\" style=\"margin-bottom: 5px;\" pInputText/>\n      <input [disabled]=\"!loginForm.valid\" type=\"submit\" class=\"btn btn-success float-right\" value=\"Log In\" pInputText/>\n    </form>\n\n    <!-- TODO: Set up email registration -->\n    <div id=\"formFooter\">\n      <div>\n        <a class=\"underlineHover\" href=\"#/forgot-password\">Forgot Password?</a>\n      </div>\n    </div>\n  </p-card>\n</div>"
  },
  {
    "path": "frontend/src/app/login/login.component.sass",
    "content": ""
  },
  {
    "path": "frontend/src/app/login/login.component.spec.ts",
    "content": "import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';\n\nimport { LoginComponent } from './login.component';\n\ndescribe('LoginComponent', () => {\n  let component: LoginComponent;\n  let fixture: ComponentFixture<LoginComponent>;\n\n  beforeEach(waitForAsync(() => {\n    TestBed.configureTestingModule({\n      declarations: [ LoginComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(LoginComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/login/login.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormGroup, FormBuilder, Validators } from '@angular/forms';\nimport { AuthService } from '../auth.service';\nimport { Router } from '@angular/router';\nimport { Tokens } from '../interfaces/Tokens';\n\n@Component({\n  selector: 'app-login',\n  templateUrl: './login.component.html',\n  styleUrls: ['./login.component.sass'],\n})\nexport class LoginComponent implements OnInit {\n  loginForm: FormGroup;\n  submitted = false;\n\n  constructor(\n    private fb: FormBuilder,\n    public authService: AuthService,\n    public router: Router\n  ) {\n    this.createForm();\n  }\n\n  ngOnInit() {}\n\n  createForm() {\n    this.loginForm = this.fb.group({\n      email: ['', [Validators.required, Validators.email]],\n      password: ['', Validators.required],\n    });\n  }\n\n  onSubmit(creds) {\n    const login = { email: creds.value.email, password: creds.value.password };\n    this.authService.login(login).subscribe((tokens: Tokens) => {\n      this.authService.setTokens(tokens);\n      this.router.navigate(['dashboard']);\n    });\n  }\n}\n"
  },
  {
    "path": "frontend/src/app/navbar/navbar.component.html",
    "content": "<nav class=\"navbar navbar-light bg-light\" style=\"margin-bottom: 50px;\">\n  <a class=\"navbar-brand\" href=\"#\">\n    <img src=\"../../assets/logo.png\" width=\"250\" height=\"115\" alt=\"Bulwark Logo\" /></a>\n  <div class=\"mx-auto\">\n    <div *ngIf=\"loaderService.isLoading | async\" role=\"status\">\n      <p-progressSpinner [style]=\"{width: '75px', height: '75px'}\"></p-progressSpinner>\n    </div>\n  </div>\n  <div class=\"nav-item dropdown\" *ngIf=\"loggedIn$ | async as loggedIn\">\n    <a class=\"nav-link dropdown-toggle\" id=\"navbarDropdown\" role=\"button\" data-toggle=\"dropdown\" aria-haspopup=\"true\"\n      aria-expanded=\"false\">\n      Menu\n    </a>\n    <div class=\"dropdown-menu\" aria-labelledby=\"navbarDropdown\">\n      <a class=\"dropdown-item\" href=\"#/user/profile\">Profile</a>\n      <a *ngIf=\"authService.isAdmin()\" class=\"dropdown-item\" href=\"#/administration\">Administration</a>\n      <div class=\"dropdown-divider\"></div>\n      <a class=\"dropdown-item\" (click)=\"logout()\" href=\"\">Logout</a>\n    </div>\n  </div>\n</nav>\n"
  },
  {
    "path": "frontend/src/app/navbar/navbar.component.sass",
    "content": ".dropdown-menu\n  left: -50px\n  margin: .125rem -10px 0\n"
  },
  {
    "path": "frontend/src/app/navbar/navbar.component.spec.ts",
    "content": "import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';\n\nimport { NavbarComponent } from './navbar.component';\n\ndescribe('NavbarComponent', () => {\n  let component: NavbarComponent;\n  let fixture: ComponentFixture<NavbarComponent>;\n\n  beforeEach(waitForAsync(() => {\n    TestBed.configureTestingModule({\n      declarations: [ NavbarComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(NavbarComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/navbar/navbar.component.ts",
    "content": "import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';\nimport { LoaderService } from '../loader.service';\nimport { AuthService } from '../auth.service';\nimport { GlobalManagerService } from '../global-manager.service';\n\n@Component({\n  selector: 'app-navbar',\n  templateUrl: './navbar.component.html',\n  styleUrls: ['./navbar.component.sass'],\n  changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class NavbarComponent implements OnInit {\n  loggedIn$;\n\n  constructor(\n    public loaderService: LoaderService,\n    public authService: AuthService,\n    private globalManager: GlobalManagerService\n  ) {\n    this.loggedIn$ = this.globalManager.loggedIn$;\n  }\n\n  ngOnInit() {}\n\n  logout() {\n    this.authService.logout();\n  }\n}\n"
  },
  {
    "path": "frontend/src/app/org-form/Organization.ts",
    "content": "export class Organization {\n  constructor(public id: number, public name: string) {}\n}\n"
  },
  {
    "path": "frontend/src/app/org-form/org-form.component.html",
    "content": "<div class=\"container col-md-6 col-12\">\n  <form [formGroup]=\"orgForm\" (ngSubmit)=\"onSubmit(orgForm)\">\n    <div class=\"form-group\">\n      <label for=\"orgName\">Organization Name</label>\n      <input formControlName=\"name\" type=\"text\" class=\"form-control\" id=\"orgName\"\n        placeholder=\"Enter in an organization name\" pInputText />\n    </div>\n    <button *ngIf=\"isAdmin\" [disabled]=\"!orgForm.valid\" class=\"btn btn-primary float-right\" type=\"submit\"\n      data-toggle=\"tooltip\" data-placement=\"bottom\" label=\"Submit Data\" pButton>\n    </button>\n    <button (click)=\"navigateToDashboard()\" class=\"btn btn-secondary float-right\" type=\"button\"\n      style=\"margin-right: 5px\" data-toggle=\"tooltip\" data-placement=\"bottom\" label=\"Back to Dashboard\" pButton>\n    </button>\n  </form>\n</div>\n"
  },
  {
    "path": "frontend/src/app/org-form/org-form.component.sass",
    "content": ""
  },
  {
    "path": "frontend/src/app/org-form/org-form.component.spec.ts",
    "content": "import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';\n\nimport { OrgFormComponent } from './org-form.component';\n\ndescribe('OrgFormComponent', () => {\n  let component: OrgFormComponent;\n  let fixture: ComponentFixture<OrgFormComponent>;\n\n  beforeEach(waitForAsync(() => {\n    TestBed.configureTestingModule({\n      declarations: [ OrgFormComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(OrgFormComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/org-form/org-form.component.ts",
    "content": "import { Component, OnInit, OnChanges } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { Organization } from './Organization';\nimport { AppService } from '../app.service';\nimport { Router, ActivatedRoute } from '@angular/router';\nimport { AlertService } from '../alert/alert.service';\nimport { AuthService } from '../auth.service';\n@Component({\n  selector: 'app-org-form',\n  templateUrl: './org-form.component.html',\n  styleUrls: ['./org-form.component.sass'],\n})\nexport class OrgFormComponent implements OnInit, OnChanges {\n  public orgModel: Organization;\n  orgForm: FormGroup;\n  fileToUpload: File = null;\n  orgId: number;\n  isAdmin: boolean;\n  constructor(\n    private fb: FormBuilder,\n    public appService: AppService,\n    public route: Router,\n    public activatedRoute: ActivatedRoute,\n    private alertService: AlertService,\n    public authService: AuthService\n  ) {\n    this.createForm();\n  }\n\n  ngOnInit() {\n    this.activatedRoute.data.subscribe(({ organization }) => {\n      this.isAdmin = this.authService.isAdmin();\n      if (organization) {\n        this.orgModel = organization;\n        if (!this.isAdmin) {\n          this.disableForm();\n        }\n        this.rebuildForm();\n      }\n    });\n    this.activatedRoute.params.subscribe((params) => {\n      this.orgId = params.id;\n    });\n  }\n\n  ngOnChanges() {\n    this.rebuildForm();\n  }\n\n  /**\n   * Function required to create the active form in Angular\n   */\n  createForm() {\n    this.orgForm = this.fb.group({\n      name: ['', [Validators.required]],\n    });\n  }\n\n  /**\n   * Function required to rebuild the form on changes in Angular\n   */\n  rebuildForm() {\n    this.orgForm.reset({\n      name: this.orgModel.name,\n    });\n  }\n\n  disableForm() {\n    this.orgForm.disable();\n  }\n\n  /**\n   * Function required to process the files attached to the form\n   * @param files array of files to work with\n   */\n  handleFileInput(files: FileList) {\n    this.fileToUpload = files.item(0);\n  }\n\n  /**\n   * Function required to process the form data\n   * @param contact form data object holding organization data\n   */\n  onSubmit(contact: FormGroup) {\n    this.orgModel = contact.value;\n    if (this.fileToUpload) {\n      this.appService.upload(this.fileToUpload).subscribe((fileId) => {\n        this.createOrUpdateOrg(this.orgModel);\n      });\n    } else {\n      this.createOrUpdateOrg(this.orgModel);\n    }\n  }\n\n  /**\n   * Function required to create or update an organization based on org ID\n   * navigates the user back to the main dashboard after action is executed\n   * @param org contains organization data object\n   */\n  createOrUpdateOrg(org: Organization) {\n    if (this.orgId) {\n      this.appService\n        .updateOrg(this.orgId, org)\n        .subscribe((success: string) => {\n          this.navigateToDashboard();\n          this.alertService.success(success);\n        });\n    } else {\n      this.appService.createOrg(org).subscribe((success: string) => {\n        this.navigateToDashboard();\n        this.alertService.success(success);\n      });\n    }\n  }\n\n  /**\n   * Function responsible for directing the user back to the main dashboard\n   */\n  navigateToDashboard() {\n    this.route.navigate(['dashboard']);\n  }\n}\n"
  },
  {
    "path": "frontend/src/app/organization/organization.component.html",
    "content": "<div class=\"container-fluid\">\n  <p-table #dt [value]=\"assetAry\" [paginator]=\"true\" [rows]=\"10\" styleClass=\"p-datatable-striped\">\n    <ng-template pTemplate=\"header\">\n      <tr>\n        <th pSortableColumn=\"id\">Asset ID<p-sortIcon field=\"id\"></p-sortIcon>\n        </th>\n        <th pSortableColumn=\"name\">Asset Name<p-sortIcon field=\"name\"></p-sortIcon>\n        </th>\n        <th pSortableColumn=\"jira\">Jira Enabled<p-sortIcon field=\"jira\"></p-sortIcon>\n        </th>\n        <th scope=\"col\">Status</th>\n        <th scope=\"col\"># Open Vulnerabilities</th>\n        <th scope=\"col\"></th>\n      </tr>\n      <tr>\n        <th>\n          <input pInputText type=\"text\" (input)=\"dt.filter($event.target.value, 'id', 'equals')\"\n            placeholder=\"Search by ID\" class=\"p-column-filter\">\n        </th>\n        <th>\n          <input pInputText type=\"text\" (input)=\"dt.filter($event.target.value, 'name', 'startsWith')\"\n            placeholder=\"Search by Name\" class=\"p-column-filter\">\n        </th>\n        <th></th>\n        <th></th>\n        <th></th>\n        <th></th>\n      </tr>\n    </ng-template>\n    <ng-template pTemplate=\"body\" let-asset>\n      <tr>\n        <td scope=\"row\">{{ asset?.id }}</td>\n        <td>{{ asset?.name }}</td>\n        <td>{{asset?.jira?.id ? 'Yes' : 'No'}}</td>\n        <td>{{ asset?.status === 'A' ? 'Active':'Archived' }}</td>\n        <td><a (click)=\"showOpenVulnsModal(asset.id, asset.name)\" href=\"javascript:;\">{{asset?.openVulnCount}}</a></td>\n        <td>\n          <button *ngIf=\"!isArchive\" class=\"btn btn-secondary\" type=\"button\" style=\"margin-right: 10px;\"\n            data-toggle=\"tooltip\" (click)=\"navigateToAsset(asset.id)\" c data-placement=\"bottom\" title=\"Edit Asset\">\n            <i *ngIf=\"isAdmin\" class=\"pi pi-pencil\"></i>\n            <i *ngIf=\"!isAdmin\" class=\"pi pi-eye\"></i>\n          </button>\n          <button *ngIf=\"!isArchive\" (click)=\"navigateToAssessment(asset.id)\" class=\"btn btn-primary\"\n            style=\"margin-right: 10px;\" type=\"button\" data-toggle=\"tooltip\" data-placement=\"bottom\"\n            title=\"View Assessments\">\n            <i class=\"pi pi-list\"></i>\n          </button>\n          <button (click)=\"archiveAsset(asset);\" *ngIf=\"!isArchive && isAdmin\" class=\"btn btn-dark\" type=\"button\"\n            data-toggle=\"tooltip\" data-placement=\"bottom\" title=\"Archive\">\n            <i class=\"pi pi-folder\"></i>\n          </button>\n          <button (click)=\"activateAsset(asset);\" *ngIf=\"isArchive && isAdmin\" class=\"btn btn-dark\" type=\"button\"\n            data-toggle=\"tooltip\" data-placement=\"bottom\" title=\"Activate\">\n            <i class=\"pi pi-folder-open\"></i>\n          </button>\n        </td>\n      </tr>\n    </ng-template>\n  </p-table>\n  <br />\n  <div class=\"float-right\">\n    <button style=\"margin-right: 5px;\" (click)=\"navigateToDashboard()\" type=\"button\" class=\"btn btn-secondary \">\n      Back to Dashboard\n    </button>\n    <button style=\"margin-right: 5px;\" *ngIf=\"!isArchive\" (click)=\"getArchivedAssets()\" type=\"button\"\n      class=\"btn btn-dark \" data-toggle=\"tooltip\" data-placement=\"bottom\" title=\"View Archived Assets\">\n      View Archive\n    </button>\n    <button style=\"margin-right: 5px;\" *ngIf=\"isArchive\" (click)=\"getActiveAssets()\" type=\"button\" class=\"btn btn-dark \"\n      data-toggle=\"tooltip\" data-placement=\"bottom\" title=\"View Active Assets\">\n      View Active\n    </button>\n    <button (click)=\"navigateToCreateAsset()\" *ngIf=\"!isArchive && isAdmin\" type=\"button\" class=\"btn btn-primary \">\n      Add Asset\n    </button>\n    <p-dialog header=\"{{assetNameHeader}} Open Vulnerabilities\" [(visible)]=\"displayOpenVulnModal\" modal=\"true\"\n      [style]=\"{width: '125vw'}\">\n      <p-table #vulnTable [value]=\"openVulns\" [paginator]=\"true\" [rows]=\"10\" styleClass=\"p-datatable-striped\">\n        <ng-template pTemplate=\"header\">\n          <tr>\n            <th pSortableColumn=\"id\">ID<p-sortIcon field=\"id\"></p-sortIcon>\n            </th>\n            <th pSortableColumn=\"name\">Name<p-sortIcon field=\"name\"></p-sortIcon>\n            </th>\n            <th pSortableColumn=\"risk\">Risk<p-sortIcon field=\"risk\"></p-sortIcon>\n            </th>\n            <th pSortableColumn=\"systemic\">Systemic<p-sortIcon field=\"systemic\"></p-sortIcon>\n            </th>\n            <th pSortableColumn=\"cvssScore\">CVSS Score<p-sortIcon field=\"cvssScore\"></p-sortIcon>\n            </th>\n            <th pSortableColumn=\"jiraId\">Jira ID<p-sortIcon field=\"jiraId\"></p-sortIcon>\n            </th>\n            <th></th>\n          </tr>\n          <tr>\n            <th>\n              <input pInputText type=\"text\" (input)=\"vulnTable.filter($event.target.value, 'id', 'equals')\"\n                placeholder=\"Search by ID\" class=\"p-column-filter\">\n            </th>\n            <th>\n              <input pInputText type=\"text\" (input)=\"vulnTable.filter($event.target.value, 'name', 'contains')\"\n                placeholder=\"Search by Name\" class=\"p-column-filter\">\n            </th>\n            <th>\n              <p-multiSelect [options]=\"risks\" placeholder=\"All\" (onChange)=\"onRiskChange($event)\" optionLabel=\"name\"\n                styleClass=\"p-column-filter\">\n                <ng-template let-option pTemplate=\"item\">\n                  <div class=\"p-multiselect-representative-option\">\n                    <span class=\"p-ml-1\">{{option.name}}</span>\n                  </div>\n                </ng-template>\n              </p-multiSelect>\n            </th>\n            <th></th>\n            <th></th>\n            <th></th>\n            <th></th>\n          </tr>\n        </ng-template>\n        <ng-template pTemplate=\"body\" let-vuln>\n          <tr>\n            <td>{{vuln?.id}}</td>\n            <td>{{vuln?.name}}</td>\n            <td>{{vuln?.risk}}</td>\n            <td>{{vuln?.systemic}}</td>\n            <td><a [href]=\"vuln?.cvssUrl\" target=\"_blank\">{{vuln?.cvssScore}}</a></td>\n            <td><a [href]=\"vuln?.jiraId\" target=\"_blank\">{{vuln?.jiraId}}</a></td>\n            <td>\n              <button class=\"btn btn-secondary\" type=\"button\" style=\"margin-right: 10px;\" data-toggle=\"tooltip\"\n                (click)=\"navigateToVulnDetail(vuln.id, vuln.assessment.id)\" data-placement=\"bottom\"\n                title=\"View Vulnerability\">\n                <i class=\"pi pi-eye\"></i>\n              </button>\n            </td>\n          </tr>\n        </ng-template>\n      </p-table>\n    </p-dialog>\n  </div>\n"
  },
  {
    "path": "frontend/src/app/organization/organization.component.sass",
    "content": ""
  },
  {
    "path": "frontend/src/app/organization/organization.component.spec.ts",
    "content": "import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';\n\nimport { OrganizationComponent } from './organization.component';\n\ndescribe('OrganizationComponent', () => {\n  let component: OrganizationComponent;\n  let fixture: ComponentFixture<OrganizationComponent>;\n\n  beforeEach(waitForAsync(() => {\n    TestBed.configureTestingModule({\n      declarations: [ OrganizationComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(OrganizationComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/organization/organization.component.ts",
    "content": "import { Component, OnInit, ViewChild } from '@angular/core';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { AppService } from '../app.service';\nimport { Asset } from '../asset-form/Asset';\nimport { AlertService } from '../alert/alert.service';\nimport { Table } from 'primeng/table';\nimport { AuthService } from '../auth.service';\n@Component({\n  selector: 'app-organization',\n  templateUrl: './organization.component.html',\n  styleUrls: ['./organization.component.sass'],\n})\nexport class OrganizationComponent implements OnInit {\n  assetAry: any = [];\n  orgId: number;\n  assetId: number;\n  org: any;\n  isArchive = false;\n  isAdmin: boolean;\n  displayOpenVulnModal = false;\n  openVulns: any = [];\n  assetNameHeader: string;\n  @ViewChild('dt') table: Table;\n  @ViewChild('vulnTable') vulnTable: Table;\n  risks = [\n    { name: 'Informational' },\n    { name: 'Low' },\n    { name: 'Medium' },\n    { name: 'High' },\n    { name: 'Critical' },\n  ];\n  constructor(\n    public activatedRoute: ActivatedRoute,\n    public router: Router,\n    public appService: AppService,\n    public alertService: AlertService,\n    public authService: AuthService\n  ) {}\n\n  ngOnInit() {\n    this.activatedRoute.data.subscribe(({ assets }) => {\n      this.assetAry = assets;\n      this.isAdmin = this.authService.isAdmin();\n    });\n    this.activatedRoute.params.subscribe((params) => {\n      this.orgId = params.orgId;\n      this.appService\n        .getOrganizationById(this.orgId)\n        .then((org) => (this.org = org));\n    });\n  }\n\n  onRepresentativeChange(event) {\n    this.table.filter(event.value, 'name', 'in');\n  }\n\n  /**\n   * Function responsible retrieving active assets\n   */\n  getActiveAssets() {\n    this.appService.getOrganizationAssets(this.orgId).then((activeAssets) => {\n      this.assetAry = activeAssets;\n      this.isArchive = false;\n    });\n  }\n  /**\n   * Function responsible retrieving archived assets\n   */\n  getArchivedAssets() {\n    this.appService\n      .getOrganizationArchiveAssets(this.orgId)\n      .subscribe((archivedAssets) => {\n        this.assetAry = archivedAssets;\n        this.isArchive = true;\n      });\n  }\n  /**\n   * Function responsible for navigating the user to an Assessment\n   * @param id assessment ID is required\n   */\n  navigateToAssessment(id: number) {\n    this.router.navigate([`organization/${this.orgId}/asset/${id}`]);\n  }\n\n  /**\n   * Function responsible for navigating the user back to the main dashboard\n   */\n  navigateToDashboard() {\n    this.router.navigate([`dashboard`]);\n  }\n\n  /**\n   * Function responsible for navigating the user to the assessment area to create\n   * a new assessment\n   */\n  navigateToCreateAsset() {\n    this.router.navigate([`organization/${this.orgId}/asset-form`]);\n  }\n\n  /**\n   * Function responsible for navigating the user to Asset Area\n   * @param assetId asset ID passed required\n   */\n  navigateToAsset(assetId: number) {\n    this.router.navigate([`organization/${this.orgId}/asset-form/${assetId}`]);\n  }\n\n  /**\n   * Function responsible for navigating the user to the assets open vulnereabilities\n   * @param assetId asset ID passed required\n   */\n  showOpenVulnsModal(assetId: number, assetName: string) {\n    this.assetId = assetId;\n    this.displayOpenVulnModal = true;\n    this.assetNameHeader = assetName;\n    this.openVulns = [];\n    this.appService\n      .getOpenVulnsByAssetId(this.assetId)\n      .subscribe((openVulns) => {\n        this.openVulns = openVulns;\n      });\n  }\n\n  /**\n   * Function responsible for archiving an asset\n   */\n  archiveAsset(asset: Asset) {\n    const confirmed = confirm(`Archive the asset \"${asset.name}\"?`);\n    if (confirmed) {\n      this.appService.archiveAsset(asset).subscribe((res: string) => {\n        this.alertService.success(res);\n        this.getActiveAssets();\n      });\n    }\n  }\n  /**\n   * Function responsible for activating an asset\n   */\n  activateAsset(asset: Asset) {\n    const confirmed = confirm(`Activate the asset \"${asset.name}\"?`);\n    if (confirmed) {\n      this.appService.activateAsset(asset).subscribe((res: string) => {\n        this.alertService.success(res);\n        this.getArchivedAssets();\n      });\n    }\n  }\n\n  onRiskChange(event) {\n    const selectedRiskAry = event.value.map((x) => x.name);\n    this.vulnTable.filter(selectedRiskAry, 'risk', 'in');\n  }\n\n  navigateToVulnDetail(vulnId: number, assessmentId: number) {\n    const url = this.router.serializeUrl(\n      this.router.createUrlTree([\n        `organization/${this.orgId}/asset/${this.assetId}/assessment/${assessmentId}/vuln-form/${vulnId}`,\n      ])\n    );\n    let baseUrl = window.location.href.replace(this.router.url, '');\n\n    window.open(baseUrl + url, '_blank');\n  }\n}\n"
  },
  {
    "path": "frontend/src/app/page-not-found/page-not-found.component.html",
    "content": "<h2 class=\"text-center\">Page not found</h2>\n"
  },
  {
    "path": "frontend/src/app/page-not-found/page-not-found.component.sass",
    "content": ""
  },
  {
    "path": "frontend/src/app/page-not-found/page-not-found.component.spec.ts",
    "content": "import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';\n\nimport { PageNotFoundComponent } from './page-not-found.component';\n\ndescribe('PageNotFoundComponent', () => {\n  let component: PageNotFoundComponent;\n  let fixture: ComponentFixture<PageNotFoundComponent>;\n\n  beforeEach(waitForAsync(() => {\n    TestBed.configureTestingModule({\n      declarations: [ PageNotFoundComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(PageNotFoundComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/page-not-found/page-not-found.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\n\n@Component({\n  selector: 'app-page-not-found',\n  templateUrl: './page-not-found.component.html',\n  styleUrls: ['./page-not-found.component.sass']\n})\nexport class PageNotFoundComponent implements OnInit {\n\n  constructor() { }\n\n  ngOnInit() {\n  }\n\n}\n"
  },
  {
    "path": "frontend/src/app/password-reset/password-reset.component.html",
    "content": "<div class=\"container col-12 col-md-4\">\n  <p-card  id=\"formContent\">\n    <form [formGroup]=\"pwdResetForm\" (ngSubmit)=\"onSubmit(pwdResetForm)\">\n      <input\n        formControlName=\"password\"\n        type=\"password\"\n        id=\"password\"\n        class=\"form-control\"\n        name=\"password\"\n        placeholder=\"Password\"\n        style=\"margin-bottom: 5px;\"\n      />\n      <input\n        formControlName=\"confirmPassword\"\n        type=\"password\"\n        id=\"confirmPassword\"\n        class=\"form-control\"\n        name=\"confirmPassword\"\n        placeholder=\"Confirm Password\"\n        style=\"margin-bottom: 5px;\"\n        pInputText\n      />\n      <input\n        [disabled]=\"!pwdResetForm.valid\"\n        type=\"submit\"\n        class=\"btn btn-success float-right\"\n        value=\"Reset\"\n        pInputText\n      />\n    </form>\n\n    <div id=\"formFooter\">\n      <div>\n        <a class=\"underlineHover\" href=\"#/login\">Return to login</a>\n      </div>\n    </div>\n  </p-card>\n</div>\n"
  },
  {
    "path": "frontend/src/app/password-reset/password-reset.component.sass",
    "content": ""
  },
  {
    "path": "frontend/src/app/password-reset/password-reset.component.spec.ts",
    "content": "import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';\n\nimport { PasswordResetComponent } from './password-reset.component';\n\ndescribe('PasswordResetComponent', () => {\n  let component: PasswordResetComponent;\n  let fixture: ComponentFixture<PasswordResetComponent>;\n\n  beforeEach(waitForAsync(() => {\n    TestBed.configureTestingModule({\n      declarations: [ PasswordResetComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(PasswordResetComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/password-reset/password-reset.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormGroup, FormBuilder, Validators } from '@angular/forms';\nimport { AuthService } from '../auth.service';\nimport { Router, ActivatedRoute } from '@angular/router';\nimport { AlertService } from '../alert/alert.service';\n\n@Component({\n  selector: 'app-password-reset',\n  templateUrl: './password-reset.component.html',\n  styleUrls: ['./password-reset.component.sass'],\n})\nexport class PasswordResetComponent implements OnInit {\n  uuid: string;\n  pwdResetForm: FormGroup;\n  constructor(\n    private activatedRoute: ActivatedRoute,\n    private fb: FormBuilder,\n    public authService: AuthService,\n    public router: Router,\n    public alertService: AlertService\n  ) {\n    this.uuid = this.activatedRoute.snapshot.paramMap.get('uuid');\n    this.createForm();\n  }\n\n  ngOnInit(): void {}\n\n  createForm() {\n    this.pwdResetForm = this.fb.group({\n      password: ['', Validators.required],\n      confirmPassword: ['', Validators.required],\n    });\n  }\n\n  onSubmit(form) {\n    const pwdUpdateObj = {\n      password: form.value.password,\n      confirmPassword: form.value.confirmPassword,\n      uuid: this.uuid,\n    };\n    this.authService.passwordReset(pwdUpdateObj).subscribe((res: string) => {\n      this.alertService.success(res);\n      this.router.navigate(['login']);\n    });\n  }\n}\n"
  },
  {
    "path": "frontend/src/app/register/register.component.html",
    "content": "<div class=\"container col-12 col-md-4\">\n  <p-card>\n      <h4>Bulwark Registration</h4>\n      <hr/>\n      <form [formGroup]=\"registerForm\" (ngSubmit)=\"onSubmit(registerForm)\">\n        <input formControlName=\"firstName\" type=\"text\" id=\"firstName\" class=\"form-control\" name=\"firstName\"\n          placeholder=\"First Name\" style=\"margin-bottom: 5px;\" pInputText />\n        <input formControlName=\"lastName\" type=\"text\" id=\"lastName\" class=\"form-control\" name=\"lastName\"\n          placeholder=\"Last Name\" style=\"margin-bottom: 5px;\" pInputText />\n        <input formControlName=\"title\" type=\"text\" id=\"title\" class=\"form-control\" name=\"title\" placeholder=\"Title\"\n          style=\"margin-bottom: 5px;\" pInputText />\n        <input formControlName=\"password\" type=\"password\" id=\"password\" class=\"form-control\" name=\"password\"\n          placeholder=\"Password\" style=\"margin-bottom: 5px;\" pInputText />\n        <input formControlName=\"confirmPassword\" type=\"password\" id=\"confirmPassword\" class=\"form-control\"\n          name=\"confirmPassword\" placeholder=\"Confirm Password\" style=\"margin-bottom: 5px;\" pInputText />\n        <input [disabled]=\"!registerForm.valid\" type=\"submit\" class=\"btn btn-success float-right\" value=\"Register\" pInputText/>\n      </form>\n      <p-footer>\n      <div id=\"formFooter\">\n        <div>\n          <a class=\"underlineHover\" href=\"#/login\">Return to login</a>\n        </div>\n      </div></p-footer>\n  </p-card>\n</div>\n"
  },
  {
    "path": "frontend/src/app/register/register.component.sass",
    "content": ""
  },
  {
    "path": "frontend/src/app/register/register.component.spec.ts",
    "content": "import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';\n\nimport { RegisterComponent } from './register.component';\n\ndescribe('RegisterComponent', () => {\n  let component: RegisterComponent;\n  let fixture: ComponentFixture<RegisterComponent>;\n\n  beforeEach(waitForAsync(() => {\n    TestBed.configureTestingModule({\n      declarations: [ RegisterComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(RegisterComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/register/register.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormGroup, FormBuilder, Validators } from '@angular/forms';\nimport { UserService } from '../user.service';\nimport { Router, ActivatedRoute } from '@angular/router';\nimport { AlertService } from '../alert/alert.service';\n\n@Component({\n  selector: 'app-register',\n  templateUrl: './register.component.html',\n  styleUrls: ['./register.component.sass'],\n})\nexport class RegisterComponent implements OnInit {\n  uuid: string;\n  registerForm: FormGroup;\n  constructor(\n    private activatedRoute: ActivatedRoute,\n    private fb: FormBuilder,\n    public userService: UserService,\n    public router: Router,\n    public alertService: AlertService\n  ) {\n    this.uuid = this.activatedRoute.snapshot.paramMap.get('uuid');\n    this.createForm();\n  }\n\n  ngOnInit(): void {}\n\n  createForm() {\n    this.registerForm = this.fb.group({\n      firstName: ['', Validators.required],\n      lastName: ['', Validators.required],\n      title: ['', Validators.required],\n      password: ['', Validators.required],\n      confirmPassword: ['', Validators.required],\n    });\n  }\n\n  onSubmit(form) {\n    const registerObj = {\n      firstName: form.value.firstName,\n      lastName: form.value.lastName,\n      title: form.value.title,\n      password: form.value.password,\n      confirmPassword: form.value.confirmPassword,\n      uuid: this.uuid,\n    };\n    this.userService.registerUser(registerObj).subscribe((res: string) => {\n      this.router.navigate(['login']);\n      this.alertService.success(res);\n    });\n  }\n}\n"
  },
  {
    "path": "frontend/src/app/report/report.component.html",
    "content": "<div class=\"container-fluid\">\n  <div class=\"row\" id=\"buttons\" *ngIf=\"showButtons\">\n    <div class=\"col-12\">\n      <div class=\"float-right\">\n        <button (click)=\"navigateToVulns()\" type=\"button\" class=\"btn btn-secondary\" style=\"margin-right: 5px\">\n          Back to Assessments\n        </button>\n        <button (click)=\"generateReport()\" type=\"button\" class=\"btn btn-primary\">\n          Generate Report\n        </button>\n      </div>\n    </div>\n  </div>\n  <br />\n\n  <div>\n    <h1 class=\"text-center\">\n      {{ report?.org?.name }}: {{ report?.asset?.name }} &\n      {{ report?.assessment?.name }} Application Security Report\n    </h1>\n    <hr />\n    <h4 class=\"text-center\">\n      {{ report?.assessment?.startDate | date: 'shortDate':'UTC' }} -\n      {{ report?.assessment?.endDate | date: 'shortDate':'UTC' }}\n    </h4>\n    <p>\n      The {{ report?.companyName }} Application Security Team performed a\n      security assessment on {{ report?.asset?.name }} &\n      {{ report?.assessment?.name }} (the application) on\n      {{ report?.assessment?.startDate | date: 'shortDate':'UTC' }}. The security\n      assessment lasted {{ numOfDays }} {{ pluralDays }} and ended on\n      {{ report?.assessment?.endDate | date: 'shortDate':'UTC' }}. This report\n      contains detailed information about security vulnerabilities found during\n      the assessment.\n    </p>\n    <br />\n    <div *ngIf=\"report.assessment.executiveSummary\">\n      <h4 class=\"text-center\">Executive Summary</h4>\n      <markdown>\n        {{ report?.assessment?.executiveSummary }}\n      </markdown>\n    </div>\n    <br />\n    <div class=\"row\">\n      <div class=\"col-4\">\n        <h4 class=\"text-center\">Application Security Team</h4>\n        <table class=\"table\">\n          <thead>\n            <tr>\n              <th>Name</th>\n              <th>Position</th>\n            </tr>\n          </thead>\n          <tbody *ngFor=\"let tester of report.assessment.testers\">\n            <tr>\n              <td>{{ tester?.firstName }} {{tester?.lastName}}</td>\n              <td>{{ tester?.title }}</td>\n            </tr>\n          </tbody>\n        </table>\n      </div>\n      <div class=\"col-4\">\n        <h4 class=\"text-center\">Overall Risk Severity</h4>\n        <p-chart [responsive]=\"true\" [height]=\"415\" [width]=\"330\" type=\"pie\" [data]=\"pieData\"></p-chart>\n      </div>\n      <div class=\"col-4\">\n        <h4 class=\"text-center\">Status</h4>\n        <p-chart [responsive]=\"true\" [height]=\"475\" [width]=\"330\" type=\"radar\" [data]=\"radarData\"></p-chart>\n      </div>\n    </div>\n    <br />\n    <div *ngIf=\"report && report.vulns && report.vulns.length; else noVulnMessage\">\n      <h3 class=\"text-center\">Summary of Findings</h3>\n      <table class=\"table\">\n        <thead class=\"thead-dark\">\n          <tr>\n            <th scope=\"col\">ID</th>\n            <th scope=\"col\">Finding</th>\n            <th scope=\"col\">Overall Risk Severity</th>\n            <th scope=\"col\">Status</th>\n          </tr>\n        </thead>\n        <tbody *ngFor=\"let vuln of report.vulns\">\n          <tr>\n            <td>{{ vuln?.id }}</td>\n            <td>{{ vuln?.name }}</td>\n            <td>{{ vuln?.risk }}</td>\n            <td>{{ vuln?.status }}</td>\n          </tr>\n        </tbody>\n      </table>\n    </div>\n    <ng-template #noVulnMessage>\n      <h4>No vulnerabilities were found during this assessment.</h4>\n    </ng-template>\n  </div>\n  <div *ngFor=\"let vuln of report?.vulns; last as isLast\">\n    <div class=\"page\">\n      <br />\n      <h3 class=\"text-center\">{{ vuln?.name }}</h3>\n      <table class=\"table\">\n        <thead class=\"thead-dark\">\n          <tr>\n            <th scope=\"col\">ID</th>\n            <th scope=\"col\">Impact</th>\n            <th scope=\"col\">Likelihood</th>\n            <th scope=\"col\">Risk</th>\n            <th scope=\"col\">Systemic</th>\n            <th scope=\"col\">CVSS Score</th>\n            <th scope=\"col\">Status</th>\n          </tr>\n        </thead>\n        <tbody>\n          <tr>\n            <td>{{ vuln?.id }} </td>\n            <td>{{ vuln?.impact }}</td>\n            <td>{{ vuln?.likelihood }}</td>\n            <td>{{ vuln?.risk }}</td>\n            <td>{{ vuln?.systemic }}</td>\n            <td>\n              <a [href]=\"vuln.cvssUrl\" target=\"_blank\">{{ vuln.cvssScore }}</a>\n            </td>\n            <td>{{ vuln.status }}</td>\n          </tr>\n        </tbody>\n      </table>\n      <table class=\"table\" *ngIf=\"vuln.problemLocations && vuln.problemLocations.length\">\n        <thead>\n          <tr>\n            <th scope=\"col\">Problem Location</th>\n            <th scope=\"col\">Target</th>\n          </tr>\n        </thead>\n        <tbody>\n          <tr *ngFor=\"let probLoc of vuln.problemLocations\">\n            <td>{{ probLoc?.location }}</td>\n            <td>{{ probLoc?.target }}</td>\n          </tr>\n        </tbody>\n      </table>\n      <h4>Description</h4>\n      <p>\n        <markdown>{{ vuln?.description }}</markdown>\n      </p>\n      <h4>Detailed Description</h4>\n      <p>\n        <markdown>{{ vuln?.detailedInfo }}</markdown>\n      </p>\n      <h4>Remediation</h4>\n      <p>\n        <markdown>{{ vuln?.remediation }}</markdown>\n      </p>\n      <div *ngIf=\"vuln.resources && vuln.resources.length\">\n        <h4>Resources</h4>\n        <table class=\"table\">\n          <thead>\n            <tr>\n              <th scope=\"col\">Description</th>\n              <th scope=\"col\">URL</th>\n            </tr>\n          </thead>\n          <tbody>\n            <tr *ngFor=\"let resource of vuln?.resources\">\n              <td>{{ resource?.description }}</td>\n              <td>\n                <a [href]=\"resource?.url\" target=\"_blank\">{{ resource?.url }}</a>\n              </td>\n            </tr>\n          </tbody>\n        </table>\n      </div>\n      <div *ngIf=\"vuln.screenshotObjs && vuln.screenshotObjs.length\">\n        <h4>Screenshots</h4>\n        <div *ngFor=\"let screenshotObj of vuln.screenshotObjs\">\n          <div class=\"gallery col-12 mx-auto\">\n            <a target=\"_blank\" [href]=\"screenshotObj.url\">\n              <img [src]=\"screenshotObj.url\" title=\"{{ screenshotObj.name }}\" />\n              <br />\n            </a>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "frontend/src/app/report/report.component.sass",
    "content": "div\n  &.gallery\n    margin: 5px\n    border: 1px solid #ccc\n    width: 80%\n\n\n    &:hover\n      border: 1px solid #777\n\n    img\n      width: 100%\n      height: auto\n\n  &.disc\n    padding: 15px\n    text-align: center\n\n@media print\n  div\n    &.page\n      break-before: page\n  \n"
  },
  {
    "path": "frontend/src/app/report/report.component.spec.ts",
    "content": "import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';\n\nimport { ReportComponent } from './report.component';\n\ndescribe('ReportComponent', () => {\n  let component: ReportComponent;\n  let fixture: ComponentFixture<ReportComponent>;\n\n  beforeEach(waitForAsync(() => {\n    TestBed.configureTestingModule({\n      declarations: [ ReportComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(ReportComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/report/report.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { AppService } from '../app.service';\nimport { Vulnerability } from '../vuln-form/Vulnerability';\n\n@Component({\n  selector: 'app-report',\n  templateUrl: './report.component.html',\n  styleUrls: ['./report.component.sass'],\n})\nexport class ReportComponent implements OnInit {\n  report: any;\n  numOfDays: number;\n  pluralDays: string;\n  orgId: number;\n  assetId: number;\n  assessmentId: number;\n  isLoading = true;\n  urls = [];\n  showButtons = true;\n  pieData: any;\n  radarData: any;\n  constructor(\n    public activatedRoute: ActivatedRoute,\n    public appService: AppService,\n    public router: Router\n  ) {}\n\n  ngOnInit() {\n    const pathAry = this.activatedRoute.snapshot.url.map((obj) => obj.path);\n    if (pathAry.includes('puppeteer')) {\n      this.showButtons = false;\n    }\n    this.activatedRoute.data.subscribe(({ report }) => {\n      this.report = report;\n      this.sortRisk(this.report.vulns);\n      this.numOfDays = Math.floor(\n        (Date.parse(this.report.assessment.endDate) -\n          Date.parse(this.report.assessment.startDate)) /\n          86400000\n      );\n      this.pluralDays =\n        this.numOfDays > 1 || this.numOfDays === 0 ? 'days' : 'day';\n      this.buildPieChart(report.vulns);\n      this.buildRadarChart(report.vulns);\n      for (const vuln of report.vulns) {\n        vuln.screenshotObjs = [];\n        for (const screenshot of vuln.screenshots) {\n          this.appService.getImageById(screenshot).then((url) => {\n            const screenshotObj = {\n              url,\n              name: screenshot.originalname,\n            };\n            this.urls.push(url);\n            vuln.screenshotObjs.push(screenshotObj);\n          });\n        }\n      }\n    });\n    this.activatedRoute.params.subscribe((params) => {\n      this.orgId = params.orgId;\n      this.assetId = params.assetId;\n      this.assessmentId = params.assessmentId;\n    });\n  }\n\n  sortRisk(vulns: Vulnerability[]) {\n    // https://stackoverflow.com/a/14872766\n    const ordering = {};\n    const sortOrder = ['Critical', 'High', 'Medium', 'Low', 'Informational'];\n    for (let i = 0; i < sortOrder.length; i++) {\n      ordering[sortOrder[i]] = i;\n    }\n    vulns.sort((a, b) => {\n      return (\n        ordering[a.risk] - ordering[b.risk] || a.name.localeCompare(b.risk)\n      );\n    });\n  }\n\n  buildPieChart(vulns: Vulnerability[]) {\n    const infoVulns = vulns.filter((x) => x.risk === 'Informational').length;\n    const lowVulns = vulns.filter((x) => x.risk === 'Low').length;\n    const mediumVulns = vulns.filter((x) => x.risk === 'Medium').length;\n    const highVulns = vulns.filter((x) => x.risk === 'High').length;\n    const criticalVulns = vulns.filter((x) => x.risk === 'Critical').length;\n    this.pieData = {\n      labels: [\n        `Informational (${infoVulns})`,\n        `Low (${lowVulns})`,\n        `Medium (${mediumVulns})`,\n        `High (${highVulns})`,\n        `Critical (${criticalVulns})`,\n      ],\n      datasets: [\n        {\n          data: [infoVulns, lowVulns, mediumVulns, highVulns, criticalVulns],\n          backgroundColor: [\n            '#205493',\n            '#2e8540',\n            '#fdb81e',\n            '#981b1e',\n            '#e31c3d',\n          ],\n          hoverBackgroundColor: [\n            '#0071bc',\n            '#4aa564',\n            '#f9c642',\n            '#c32225',\n            '#e94964',\n          ],\n        },\n      ],\n    };\n  }\n  buildRadarChart(vulns: Vulnerability[]) {\n    const open = vulns.filter((x) => x.status === 'Open').length;\n    const resolved = vulns.filter((x) => x.status === 'Resolved').length;\n    const onHold = vulns.filter((x) => x.status === 'On Hold').length;\n    this.radarData = {\n      labels: ['Open', 'Resolved', 'On Hold'],\n      datasets: [\n        {\n          label: 'Finding Status',\n          backgroundColor: 'rgba(179,181,198,0.2)',\n          borderColor: 'rgba(179,181,198,1)',\n          pointBackgroundColor: 'rgba(179,181,198,1)',\n          pointBorderColor: '#fff',\n          pointHoverBackgroundColor: '#fff',\n          pointHoverBorderColor: 'rgba(179,181,198,1)',\n          data: [open, resolved, onHold],\n        },\n      ],\n    };\n  }\n  /**\n   * Function responsible for navigating the user to the Vulnerability Listing\n   */\n  navigateToVulns() {\n    this.router.navigate([\n      `organization/${this.orgId}/asset/${this.assetId}/assessment/${this.assessmentId}/vulnerability`,\n    ]);\n  }\n\n  /**\n   * Function that triggers when viewing vulnerabilities listing for an assessment\n   * Data is passed from the report component object and handled within the function\n   */\n  generateReport() {\n    this.appService\n      .generateReport(this.orgId, this.assetId, this.assessmentId)\n      .subscribe((res: Blob) => {\n        const blob = new Blob([res], {\n          type: res.type,\n        });\n        const url = window.URL.createObjectURL(blob);\n        window.open(url);\n      });\n  }\n}\n"
  },
  {
    "path": "frontend/src/app/team/team.component.html",
    "content": "<p-card>\n  <div class=\"card-body\">\n    <h4>Team Management</h4>\n    <hr />\n    <p-table [value]=\"teams\" dataKey=\"name\" [paginator]=\"true\" [rows]=\"10\" styleClass=\"p-datatable-striped\">\n      <ng-template pTemplate=\"header\">\n        <tr>\n          <th>\n            <div class=\"p-d-flex p-jc-between p-ai-center\">\n              Name\n              <p-columnFilter type=\"text\" field=\"name\" display=\"menu\"></p-columnFilter>\n            </div>\n          </th>\n          <th>\n            <div class=\"p-d-flex p-jc-between p-ai-center\">\n              Organization\n              <p-columnFilter type=\"text\" field=\"organization.name\" display=\"menu\"></p-columnFilter>\n            </div>\n          </th>\n          <th>Assets</th>\n          <th pSortableColumn=\"role\">Role <p-sortIcon field=\"role\"></p-sortIcon>\n          </th>\n          <th pSortableColumn=\"users.length\"># of Members <p-sortIcon field=\"users\"></p-sortIcon>\n          </th>\n          <th></th>\n        </tr>\n      </ng-template>\n      <ng-template pTemplate=\"body\" let-team>\n        <tr>\n          <td>{{team?.name}}</td>\n          <td>{{team?.role !== 'Admin' ? team?.organization?.name : 'N/A'}}</td>\n          <td><span *ngIf=\"team?.role === 'Admin'; else no_org_assets\">N/A</span>\n            <ng-template #no_org_assets><span *ngFor=\"let asset of team.assets; let isLast=last\">{{asset.name}}{{isLast\n                ? '' : ',\n                '}}</span></ng-template>\n          </td>\n          <td>{{team?.role}}</td>\n          <td>{{team?.users?.length}}</td>\n          <td>\n            <button (click)=\"navigateToTeam(team)\" class=\"btn btn-secondary\" type=\"button\" style=\"margin-right: 10px;\"\n              data-toggle=\"tooltip\" data-placement=\"bottom\" title=\"Edit Assessment\">\n              <i class=\"pi pi-pencil\"></i>\n            </button>\n            <button (click)=\"deleteTeam(team)\" class=\"btn btn-dark\" type=\"button\">\n              <i class=\"pi pi-trash\"></i>\n            </button>\n          </td>\n        </tr>\n      </ng-template>\n    </p-table>\n    <br>\n    <button (click)=\"navigateToTeamCreateForm()\" pInputText type=\"button\" class=\"btn btn-primary float-right\">\n      Create Team\n    </button>\n  </div>"
  },
  {
    "path": "frontend/src/app/team/team.component.sass",
    "content": ""
  },
  {
    "path": "frontend/src/app/team/team.component.spec.ts",
    "content": "import { ComponentFixture, TestBed } from '@angular/core/testing';\n\nimport { TeamComponent } from './team.component';\n\ndescribe('TeamComponent', () => {\n  let component: TeamComponent;\n  let fixture: ComponentFixture<TeamComponent>;\n\n  beforeEach(async () => {\n    await TestBed.configureTestingModule({\n      declarations: [TeamComponent],\n    }).compileComponents();\n  });\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(TeamComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/team/team.component.ts",
    "content": "import { Component, OnInit, ViewChild } from '@angular/core';\nimport { AlertService } from '../alert/alert.service';\nimport { Team } from '../interfaces/Team';\nimport { TeamService } from '../team.service';\nimport { UserService } from '../user.service';\nimport { Table } from 'primeng/table';\nimport { Router } from '@angular/router';\n\n@Component({\n  selector: 'app-team',\n  templateUrl: './team.component.html',\n  styleUrls: ['./team.component.sass'],\n})\nexport class TeamComponent implements OnInit {\n  teams: Team[];\n\n  constructor(\n    public userService: UserService,\n    public alertService: AlertService,\n    public teamService: TeamService,\n    public router: Router\n  ) {}\n  @ViewChild('teamTable') table: Table;\n\n  ngOnInit(): void {\n    this.getTeams();\n  }\n\n  getTeams() {\n    this.teamService.getTeams().subscribe((teams) => (this.teams = teams));\n  }\n  navigateToTeamCreateForm() {\n    this.router.navigate([`administration/team`]);\n  }\n\n  deleteTeam(team: Team) {\n    const r = confirm(`Delete the team \"${team.name}\"`);\n    if (r) {\n      this.teamService.deleteTeam(team.id).subscribe((res: string) => {\n        this.alertService.success(res);\n        this.getTeams();\n      });\n    }\n  }\n\n  navigateToTeam(team: Team) {\n    this.router.navigate([`administration/team/${team.id}`]);\n  }\n}\n"
  },
  {
    "path": "frontend/src/app/team-form/team-form.component.html",
    "content": "<div class=\"container col-6 align-self-center\">\n  <p-card>\n    <form [formGroup]=\"teamForm\" (ngSubmit)=\"onSubmit(teamForm)\" id=\"teamForm\">\n      <div>\n        <label>Team Name</label>\n        <input type=\"text\" pInputText formControlName=\"name\" name=\"name\" class=\"form-control\" />\n      </div>\n      <br>\n      <div class=\"form-row\">\n        <div class=\"col-5\">\n          <label>Active Users</label>\n          <ng-select [items]=\"activeUsers\" bindLabel=\"firstName\" labelForId=\"testerList\" [multiple]=\"true\"\n            clearAllText=\"Clear\" formControlName=\"users\">\n            <ng-template ng-label-tmp let-item=\"item\" let-clear=\"clear\">\n              <span class=\"ng-value-icon right\" (click)=\"clear(item)\">×</span>\n              {{item.firstName}} {{item.lastName}}\n            </ng-template>\n            <ng-template ng-option-tmp let-item=\"item\" let-search=\"searchTerm\">\n              {{item.firstName}} {{item.lastName}}\n            </ng-template>\n          </ng-select>\n        </div>\n        <div class=\"col-7\">\n          <label>Role</label>\n          <p-selectButton name=\"roles\" [options]=\"roles\" formControlName=\"role\" optionLabel=\"name\">\n          </p-selectButton>\n        </div>\n      </div>\n      <br>\n      <div class=\"form-row\">\n        <div class=\"col-6\">\n          <label>Organization</label>\n          <p-listbox [options]=\"organizations\" (click)=\"fetchOrgAssets(teamForm.controls['organization'].value)\"\n            formControlName=\"organization\" optionLabel=\"name\"></p-listbox>\n        </div>\n        <div class=\"col-6\">\n          <label>Assets</label>\n          <div *ngIf=\"assets\">\n            <div *ngIf=\"assets.length; else no_org_assets\">\n              <p-listbox [checkbox]=\"true\" [filter]=\"true\" [multiple]=\"true\" [options]=\"assets\" formControlName=\"assets\"\n                optionLabel=\"name\"></p-listbox>\n            </div>\n          </div>\n          <ng-template #no_org_assets>The selected organization has no active assets</ng-template>\n        </div>\n      </div>\n      <br>\n      <button [disabled]=\"!teamForm.valid\" class=\"btn btn-primary float-right\" type=\"submit\" data-toggle=\"tooltip\"\n        data-placement=\"bottom\" title=\"Submit\">\n        {{isCreate ? 'Create Team': 'Update Team'}}\n      </button>\n      <button (click)=\"navigateToAdministration()\" style=\"margin-right: 5px;\" class=\"btn btn-secondary float-right\"\n        type=\"button\" data-toggle=\"tooltip\" data-placement=\"bottom\" title=\"Back to Administration\">\n        Back to Administration\n      </button>\n    </form>\n    <br>\n  </p-card>\n</div>\n"
  },
  {
    "path": "frontend/src/app/team-form/team-form.component.sass",
    "content": ""
  },
  {
    "path": "frontend/src/app/team-form/team-form.component.spec.ts",
    "content": "import { ComponentFixture, TestBed } from '@angular/core/testing';\n\nimport { TeamFormComponent } from './team-form.component';\n\ndescribe('TeamFormComponent', () => {\n  let component: TeamFormComponent;\n  let fixture: ComponentFixture<TeamFormComponent>;\n\n  beforeEach(async () => {\n    await TestBed.configureTestingModule({\n      declarations: [TeamFormComponent],\n    }).compileComponents();\n  });\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(TeamFormComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/team-form/team-form.component.ts",
    "content": "import { Component, OnInit, OnChanges } from '@angular/core';\nimport { ROLE } from '../enums/roles.enum';\nimport { FormGroup, FormBuilder, Validators } from '@angular/forms';\nimport { TeamService } from '../team.service';\nimport { AlertService } from '../alert/alert.service';\nimport { Team } from '../interfaces/Team';\nimport { Organization } from '../org-form/Organization';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { AppService } from '../app.service';\nimport { Asset } from '../asset-form/Asset';\nimport { User } from '../interfaces/User';\ninterface Role {\n  name: string;\n}\n@Component({\n  selector: 'app-team-form',\n  templateUrl: './team-form.component.html',\n  styleUrls: ['./team-form.component.sass'],\n})\nexport class TeamFormComponent implements OnInit, OnChanges {\n  roles: Role[];\n  teamForm: FormGroup;\n  organizations: Organization[];\n  assets: Asset[];\n  activeUsers: User[];\n  teamModel: Team;\n  isCreate = true;\n  teamId: number;\n  constructor(\n    private fb: FormBuilder,\n    public alertService: AlertService,\n    public teamService: TeamService,\n    public appService: AppService,\n    public activatedRoute: ActivatedRoute,\n    public route: Router\n  ) {\n    this.createForm();\n  }\n\n  ngOnInit(): void {\n    this.activatedRoute.data.subscribe(({ result }) => {\n      this.organizations = result.organizations;\n      this.activeUsers = result.activeUsers;\n      if (this.organizations && this.organizations.length) {\n        this.appService\n          .getOrganizationAssets(this.organizations[0].id)\n          .then((assets: Asset[]) => {\n            // tslint:disable-next-line: no-string-literal\n            this.teamForm.controls['organization'].setValue(\n              this.organizations[0]\n            );\n            this.assets = assets;\n          });\n      } else {\n        this.alertService.warn(\n          'Tester and Read-Only teams require an organization and no organizations exist in the system.'\n        );\n      }\n      this.activatedRoute.params.subscribe((param) => {\n        if (param && param.teamId) {\n          this.isCreate = false;\n          this.teamId = param.teamId;\n          this.teamService.getTeamById(param.teamId).subscribe((team: Team) => {\n            const role: Role = {\n              name: team.role,\n            };\n            team.role = role;\n            this.teamForm.patchValue(team);\n            if (team.organization) {\n              this.appService\n                .getOrganizationAssets(team.organization.id)\n                .then((assets: Asset[]) => {\n                  this.assets = assets;\n                });\n            }\n          });\n        }\n      });\n    });\n    this.roles = [\n      { name: ROLE.READONLY },\n      { name: ROLE.TESTER },\n      { name: ROLE.ADMIN },\n    ];\n  }\n\n  ngOnChanges() {\n    this.rebuildForm();\n  }\n\n  createForm() {\n    this.teamForm = this.fb.group({\n      name: ['', [Validators.required]],\n      role: ['', [Validators.required]],\n      users: [''],\n      assets: [''],\n      organization: [''],\n    });\n  }\n\n  rebuildForm() {\n    this.teamForm.reset({\n      name: this.teamModel.name,\n      role: this.teamModel.role,\n      users: this.teamModel.users,\n      assets: this.teamModel.assets,\n      organization: this.teamModel.organization,\n    });\n  }\n\n  onSubmit(form) {\n    if (form.value.users) {\n      form.value.users = form.value.users.map((x) => x.id);\n    }\n    if (form.value.assets) {\n      form.value.assets = form.value.assets.map((x) => x.id);\n    }\n    const team: Team = {\n      name: form.value.name,\n      organization: form.value.organization,\n      role: form.value.role.name,\n      users: form.value.users,\n      assetIds: form.value.assets,\n    };\n    if (this.isCreate) {\n      this.teamService.createTeam(team).subscribe((res: string) => {\n        this.teamForm.reset();\n        this.route.navigate([`administration`]);\n        this.alertService.success(res);\n      });\n    } else {\n      team.id = this.teamId;\n      this.teamService.updateTeam(team).subscribe((res: string) => {\n        this.teamForm.reset();\n        this.route.navigate([`administration`]);\n        this.alertService.success(res);\n      });\n    }\n  }\n\n  navigateToAdministration() {\n    this.route.navigate([`administration`]);\n  }\n\n  fetchOrgAssets(event) {\n    const orgId: number = event.id;\n    this.appService.getOrganizationAssets(orgId).then((assets: Asset[]) => {\n      this.assets = assets;\n    });\n  }\n}\n"
  },
  {
    "path": "frontend/src/app/team.service.spec.ts",
    "content": "import { TestBed } from '@angular/core/testing';\n\nimport { TeamService } from './team.service';\n\ndescribe('TeamService', () => {\n  let service: TeamService;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({});\n    service = TestBed.inject(TeamService);\n  });\n\n  it('should be created', () => {\n    expect(service).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/team.service.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { environment } from '../environments/environment';\nimport { Team } from './interfaces/Team';\n@Injectable({\n  providedIn: 'root',\n})\nexport class TeamService {\n  api = environment.apiUrl;\n  constructor(private http: HttpClient) {}\n\n  getTeams() {\n    return this.http.get<Team[]>(`${this.api}/team`);\n  }\n\n  getTeamById(teamId: number) {\n    return this.http.get<Team>(`${this.api}/team/${teamId}`);\n  }\n\n  createTeam(team: Team) {\n    return this.http.post(`${this.api}/team`, team);\n  }\n\n  updateTeam(team: Team) {\n    return this.http.patch(`${this.api}/team`, team);\n  }\n\n  deleteTeam(teamId: number) {\n    return this.http.delete(`${this.api}/team/${teamId}`);\n  }\n}\n"
  },
  {
    "path": "frontend/src/app/user-form/user-form.component.html",
    "content": "<div class=\"container col-6 align-self-center\">\n  <p-card>\n    <form [formGroup]=\"userForm\" (ngSubmit)=\"onSubmit(userForm)\" id=\"userForm\">\n      <div class=\"p-d-flex p-flex-column\">\n        <div class=\"p-d-flex p-flex-column p-flex-md-row\">\n          <label for=\"firstName\" class=\"col-12 col-md-4 col-form-label\">First Name:</label>\n          <div class=\"col-12 col-md-8\">\n            <input pInputText formControlName=\"firstName\" type=\"text\" class=\"fill-width\" id=\"firstName\"\n              class=\"form-control\" name=\"firstName\" placeholder=\"First Name\" style=\"margin-bottom: 5px;\" />\n          </div>\n        </div>\n        <div class=\"p-d-flex p-flex-column p-flex-md-row\">\n          <label for=\"lastName\" class=\"col-12 col-md-4 col-form-label\">Last Name:</label>\n          <div class=\"col-12 col-md-8\">\n            <input pInputText formControlName=\"lastName\" type=\"text\" class=\"fill-width\" id=\"lastName\"\n              class=\"form-control\" name=\"lastName\" placeholder=\"Last Name\" style=\"margin-bottom: 5px;\" />\n          </div>\n        </div>\n        <div class=\"p-d-flex p-flex-column p-flex-md-row\">\n          <label for=\"email\" class=\"col-12 col-md-4 col-form-label\">Email:</label>\n          <div class=\"col-12 col-md-8\">\n            <input pInputText formControlName=\"email\" type=\"text\" class=\"fill-width\" id=\"email\" class=\"form-control\"\n              name=\"email\" placeholder=\"Email\" style=\"margin-bottom: 5px;\" />\n          </div>\n        </div>\n        <div class=\"p-d-flex p-flex-column p-flex-md-row\">\n          <label for=\"title\" class=\"col-12 col-md-4 col-form-label\">Title:</label>\n          <div class=\"col-12 col-md-8\">\n            <input pInputText formControlName=\"title\" type=\"text\" class=\"fill-width\" id=\"title\" class=\"form-control\"\n              name=\"title\" placeholder=\"Title\" style=\"margin-bottom: 5px;\" />\n          </div>\n        </div>\n        <div class=\"p-d-flex p-flex-column p-flex-md-row\">\n          <label for=\"password\" class=\"col-12 col-md-4 col-form-label\">Password:</label>\n          <div class=\"col-12 col-md-8\">\n            <input pInputText formControlName=\"password\" type=\"password\" class=\"fill-width\" id=\"password\"\n              class=\"form-control\" name=\"password\" placeholder=\"Password\" style=\"margin-bottom: 5px;\" />\n          </div>\n        </div>\n        <div class=\"p-d-flex p-flex-column p-flex-md-row\">\n          <label for=\"confirmPassword\" class=\"col-12 col-md-4 col-form-label\">Confirm Password:</label>\n          <div class=\"col-12 col-md-8\">\n            <input pInputText formControlName=\"confirmPassword\" type=\"password\" class=\"fill-width\" id=\"confirmPassword\"\n              class=\"form-control\" name=\"confirmPassword\" placeholder=\"Confirm Password\" style=\"margin-bottom: 5px;\" />\n          </div>\n        </div>\n      </div>\n      <input pInputText [disabled]=\"!userForm.valid\" type=\"submit\" class=\"btn btn-primary float-right\" value=\"Create\" />\n      <br />\n    </form>\n  </p-card>\n</div>\n"
  },
  {
    "path": "frontend/src/app/user-form/user-form.component.sass",
    "content": ""
  },
  {
    "path": "frontend/src/app/user-form/user-form.component.spec.ts",
    "content": "import { ComponentFixture, TestBed } from '@angular/core/testing';\n\nimport { UserFormComponent } from './user-form.component';\n\ndescribe('UserFormComponent', () => {\n  let component: UserFormComponent;\n  let fixture: ComponentFixture<UserFormComponent>;\n\n  beforeEach(async () => {\n    await TestBed.configureTestingModule({\n      declarations: [UserFormComponent],\n    }).compileComponents();\n  });\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(UserFormComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/user-form/user-form.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FilterMatchMode } from 'primeng/api';\nimport { Table } from 'primeng/table';\nimport { User } from '../interfaces/User';\nimport { UserService } from '../user.service';\nimport { FormGroup, FormBuilder, Validators } from '@angular/forms';\nimport { AlertService } from '../alert/alert.service';\nimport { Router } from '@angular/router';\n\n@Component({\n  selector: 'app-user-form',\n  templateUrl: './user-form.component.html',\n  styleUrls: ['./user-form.component.sass'],\n})\nexport class UserFormComponent implements OnInit {\n  users: User[];\n  userForm: FormGroup;\n  constructor(\n    public userService: UserService,\n    private fb: FormBuilder,\n    public alertService: AlertService,\n    public router: Router\n  ) {}\n\n  ngOnInit(): void {\n    this.createForm();\n  }\n\n  createForm() {\n    this.userForm = this.fb.group({\n      email: ['', [Validators.required, Validators.email]],\n      firstName: ['', [Validators.required]],\n      lastName: ['', [Validators.required]],\n      title: ['', [Validators.required]],\n      password: ['', [Validators.required]],\n      confirmPassword: ['', [Validators.required]],\n    });\n  }\n\n  onSubmit(form) {\n    const user: User = {\n      firstName: form.value.firstName,\n      lastName: form.value.lastName,\n      title: form.value.title,\n      password: form.value.password,\n      confirmPassword: form.value.confirmPassword,\n      email: form.value.email,\n    };\n    this.userService.createUser(user).subscribe((res: string) => {\n      this.alertService.success(res);\n      this.router.navigate(['administration']);\n    });\n  }\n}\n"
  },
  {
    "path": "frontend/src/app/user-management/user-management.component.html",
    "content": "<p-card>\n  <div class=\"card-body\">\n    <h4>User Management</h4>\n    <hr />\n    <p-table [value]=\"users\" [paginator]=\"true\" [rows]=\"10\" styleClass=\"p-datatable-striped\">\n      <ng-template pTemplate=\"header\">\n        <tr>\n          <th>\n            <div class=\"p-d-flex p-jc-between p-ai-center\">\n              Full Name\n              <p-columnFilter type=\"text\" field=\"name\" display=\"menu\"></p-columnFilter>\n            </div>\n          </th>\n          <th>\n            <div class=\"p-d-flex p-jc-between p-ai-center\">\n              Title\n              <p-columnFilter type=\"text\" field=\"title\" display=\"menu\"></p-columnFilter>\n            </div>\n          </th>\n          <th pSortableColumn=\"active\">Status <p-sortIcon field=\"active\"></p-sortIcon>\n          </th>\n          <th>Teams</th>\n          <th>Actions</th>\n        </tr>\n      </ng-template>\n      <ng-template pTemplate=\"body\" let-user>\n        <tr>\n          <td><div class=\"name\"><td class=\"fitwidth\">{{user?.firstName}} {{user?.lastName}}</td></div>\n          <div class=\"hide\"><td class=\"fitwidth\">{{user?.email}}</td></div>\n          <td>{{user?.title}}</td>\n          <td class=\"fitwidth\">{{user?.active ? 'Active': 'Inactive'}}</td>\n          <td><span *ngFor=\"let team of user.teams; let isLast=last\">{{team.name}}{{isLast ? '' : ', '}}</span></td>\n          <td>\n            <button *ngIf=\"user?.active\" (click)=\"deactivateUser(user)\" type=\"button\" class=\"btn btn-danger btn-sm\">\n              Deactivate\n            </button>\n            <button *ngIf=\"!user?.active\" (click)=\"activateUser(user)\" type=\"button\" class=\"btn btn-success btn-sm\">\n              Activate\n            </button>\n          </td>\n        </tr>\n      </ng-template>\n    </p-table>\n    <br>\n    <button (click)=\"navigateToTeamCreateUser()\" pInputText type=\"button\" class=\"btn btn-primary float-right\">\n      Create User\n    </button>\n    <button (click)=\"navigateToTeamInviteUser()\" style=\"margin-right: 5px;\" pInputText type=\"button\"\n      class=\"btn btn-primary float-right\">\n      Invite User\n    </button>\n  </div>\n</p-card>\n"
  },
  {
    "path": "frontend/src/app/user-management/user-management.component.sass",
    "content": "td.fitwidth \n    width: 1px\n    white-space: nowrap\n\n.hide \n  display: none\n\n    \n.name:hover + .hide \n  display: block"
  },
  {
    "path": "frontend/src/app/user-management/user-management.component.spec.ts",
    "content": "import { ComponentFixture, TestBed } from '@angular/core/testing';\n\nimport { UserManagementComponent } from './user-management.component';\n\ndescribe('UserManagementComponent', () => {\n  let component: UserManagementComponent;\n  let fixture: ComponentFixture<UserManagementComponent>;\n\n  beforeEach(async () => {\n    await TestBed.configureTestingModule({\n      declarations: [UserManagementComponent],\n    }).compileComponents();\n  });\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(UserManagementComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/user-management/user-management.component.ts",
    "content": "import { Component, OnInit, ViewChild } from '@angular/core';\nimport { User } from '../interfaces/User';\nimport { UserService } from '../user.service';\nimport { FormGroup, FormBuilder, Validators } from '@angular/forms';\nimport { AlertService } from '../alert/alert.service';\nimport { Router } from '@angular/router';\n\n@Component({\n  selector: 'app-user-management',\n  templateUrl: './user-management.component.html',\n  styleUrls: ['./user-management.component.sass'],\n})\nexport class UserManagementComponent implements OnInit {\n  users: User[];\n  userForm: FormGroup;\n  constructor(\n    public userService: UserService,\n    public alertService: AlertService,\n    public router: Router\n  ) {}\n\n  ngOnInit(): void {\n    this.getUsers();\n  }\n\n  getUsers() {\n    this.userService\n      .getAllUsers()\n      .subscribe((fetchedUsers) => (this.users = fetchedUsers));\n  }\n\n  navigateToTeamCreateUser() {\n    this.router.navigate(['administration/user/create']);\n  }\n\n  navigateToTeamInviteUser() {\n    this.router.navigate(['administration/user/invite']);\n  }\n  /**\n   * Activate the given user\n   * @param user User to activate\n   */\n  activateUser(user: User) {\n    this.userService.activateUser(user.id).subscribe(\n      (message) => {\n        this.alertService.success(message as string);\n        this.getUsers();\n      },\n      (error) => {\n        this.alertService.error(error);\n      }\n    );\n  }\n  /**\n   * Deactivate the given user\n   * @param user User to deactivate\n   */\n  deactivateUser(user: User) {\n    this.userService.deactivateUser(user.id).subscribe(\n      (message) => {\n        this.alertService.success(message as string);\n        this.getUsers();\n      },\n      (error) => {\n        this.alertService.error(error);\n      }\n    );\n  }\n}\n"
  },
  {
    "path": "frontend/src/app/user-profile/user-profile.component.html",
    "content": "<div class=\"container\">\n  <div class=\"col-md-9 mx-auto\">\n    <p-card>\n      <div class=\"card-body\">\n        <div class=\"row\">\n          <div class=\"col-md-12\">\n            <h4>Profile</h4>\n            <hr />\n          </div>\n        </div>\n        <div id=\"formContent\">\n          <form [formGroup]=\"userForm\" (ngSubmit)=\"onSubmit(userForm)\" id=\"userForm\">\n            <div class=\"col-md-12\">\n              <div class=\"form-group row\">\n                <label for=\"firstName\" class=\"col-4 col-form-label\">First Name:</label>\n                <div class=\"col-8\">\n                  <input pInputText formControlName=\"firstName\" type=\"text\" id=\"password\" class=\"form-control\"\n                    name=\"firstName\" placeholder=\"First Name\" style=\"margin-bottom: 5px;\" />\n                </div>\n              </div>\n              <div class=\"form-group row\">\n                <label for=\"lastName\" class=\"col-4 col-form-label\">Last Name:</label>\n                <div class=\"col-8\">\n                  <input pInputText formControlName=\"lastName\" type=\"text\" id=\"lastName\" class=\"form-control\"\n                    name=\"lastName\" placeholder=\"Last Name\" style=\"margin-bottom: 5px;\" />\n                </div>\n              </div>\n              <div class=\"form-group row\">\n                <label for=\"title\" class=\"col-4 col-form-label\">Title:</label>\n                <div class=\"col-8\">\n                  <input pInputText formControlName=\"title\" type=\"text\" id=\"title\" class=\"form-control\" name=\"title\"\n                    placeholder=\"Title\" style=\"margin-bottom: 5px;\" />\n                </div>\n              </div>\n              <input pInputText [disabled]=\"isEdit ? !userForm.valid : false\" type=\"submit\"\n                class=\"btn btn-primary float-right\" [value]=\"isEdit ? 'Update' : 'Edit'\" />\n            </div>\n          </form>\n        </div>\n      </div>\n    </p-card>\n    <br />\n    <p-card>\n      <div class=\"card-body\">\n        <div class=\"row\">\n          <div class=\"col-md-12\">\n            <h4>User Email Configuration</h4>\n            <hr />\n          </div>\n        </div>\n        <div id=\"formContent\">\n          <form [formGroup]=\"emailForm\" (ngSubmit)=\"onEmailFormSubmit(emailForm)\" id=\"emailForm\">\n            <div class=\"col-md-12\">\n              <div class=\"form-group row\">\n                <label for=\"email\" class=\"col-4 col-form-label\">Email:</label>\n                <div class=\"col-8\">\n                  <input pInputText formControlName=\"email\" type=\"email\" id=\"email\" class=\"form-control\" name=\"email\"\n                    style=\"margin-bottom: 5px;\" />\n                </div>\n              </div>\n              <div *ngIf=\"isEmailEdit\" class=\"form-group row\">\n                <label for=\"newEmail\" class=\"col-4 col-form-label\">Confirm New Email:</label>\n                <div class=\"col-8\">\n                  <input pInputText formControlName=\"newEmail\" type=\"email\" id=\"newEmail\" class=\"form-control\"\n                    name=\"newEmail\" placeholder=\"\" style=\"margin-bottom: 5px;\" />\n                  <span *ngIf=\"user.newEmail\">\n                    <i class=\"pi pi-info-circle\"></i><i style=\"font-size: smaller;\"> Please check your email at\n                      <strong>{{user?.newEmail}}</strong> to verify the address.\n                      To revoke this request, please click this <a [routerLink]=\"\"\n                        (click)=\"revokeUpdateEmailRequest()\">link</a></i>\n                  </span>\n                </div>\n              </div>\n              <input pInputText [disabled]=\"isEmailEdit ? !emailForm.valid || user.newEmail : false\" type=\"submit\"\n                class=\"btn btn-primary float-right\" [value]=\"isEmailEdit ? 'Update' : 'Edit'\" />\n            </div>\n          </form>\n        </div>\n      </div>\n    </p-card>\n    <br />\n    <p-card>\n      <div class=\"card-body\">\n        <div class=\"row\">\n          <div class=\"col-md-12\">\n            <h4>Security</h4>\n            <i class=\"pi pi-info-circle\"></i><i style=\"font-size: smaller;\"> Password Requirements: Must be at least 12\n              characters, at least one uppercase characters, at least one lowercase characters,at least one digit, and\n              at least one symbol.</i>\n            <hr />\n          </div>\n        </div>\n        <div id=\"securityFormContent\">\n          <form [formGroup]=\"securityForm\" (ngSubmit)=\"onSecuritySubmit(securityForm)\" id=\"securityForm\">\n            <div class=\"col-md-12\">\n              <div class=\"form-group row\">\n                <label for=\"oldPassword\" class=\"col-4 col-form-label\">Current Password:</label>\n                <div class=\"col-8\">\n                  <input pInputText formControlName=\"oldPassword\" type=\"password\" id=\"oldPassword\" class=\"form-control\"\n                    name=\"oldPassword\" style=\"margin-bottom: 5px;\" />\n                </div>\n              </div>\n              <div class=\"form-group row\">\n                <label for=\"newPassword\" class=\"col-4 col-form-label\">New Password:</label>\n                <div class=\"col-8\">\n                  <input pInputText formControlName=\"newPassword\" type=\"password\" id=\"newPassword\" class=\"form-control\"\n                    name=\"newPassword\" style=\"margin-bottom: 5px;\" />\n                </div>\n              </div>\n              <div class=\"form-group row\">\n                <label for=\"confirmNewPassword\" class=\"col-4 col-form-label\">Confirm New Password:</label>\n                <div class=\"col-8\">\n                  <input pInputText formControlName=\"confirmNewPassword\" type=\"password\" id=\"confirmNewPassword\"\n                    class=\"form-control\" name=\"confirmNewPassword\" style=\"margin-bottom: 5px;\" />\n                </div>\n              </div>\n              <input pInputText [disabled]=\"isSecurityEdit ? !securityForm.valid : false\" type=\"submit\"\n                class=\"btn btn-primary float-right\" [value]=\"isSecurityEdit ? 'Update' : 'Edit'\" />\n            </div>\n          </form>\n        </div>\n      </div>\n    </p-card>\n    <br>\n    <p-card>\n      <div class=\"card-body\">\n        <div class=\"row\">\n          <div class=\"col-md-12\">\n            <h4>Active API Key</h4>\n            <i class=\"pi pi-info-circle\"></i><i style=\"font-size: smaller;\"> A generated API key can be used in place of\n              traditional username/password authentication, allowing for all actions against Bulwark that the user is\n              authorized for.</i>\n            <hr />\n            <div *ngIf=\"userApiKeyInfo; else noApiKey\">\n              <p>\n                ID: {{userApiKeyInfo?.id}}<br>\n                Created on {{userApiKeyInfo?.createdDate | date: 'longDate':'UTC'}}<br>\n                Last used on {{userApiKeyInfo?.lastUpdatedDate | date: 'longDate':'UTC'}}<br>\n                <button (click)=\"deactivateApiKey()\" pInputText type=\"button\" class=\"btn btn-warning float-left\"\n                  data-toggle=\"tooltip\" data-placement=\"bottom\" title=\"Deactivate API Key\">\n                  <i class=\"pi pi-trash\"></i>\n                </button>\n              </p>\n            </div>\n            <ng-template #noApiKey>\n              <p>An active API key does not exist.</p>\n            </ng-template>\n            <button (click)=\"generateApiKey()\" pInputText type=\"button\" class=\"btn btn-primary float-right\">\n              Generate API Key\n            </button>\n          </div>\n        </div>\n      </div>\n    </p-card>\n    <br />\n    <p-card>\n      <div class=\"card-body\">\n        <div class=\"row\">\n          <div class=\"col-md-12\">\n            <h4>Teams</h4>\n            <hr />\n            <p-table [value]=\"user.teams\">\n              <ng-template pTemplate=\"header\">\n                <tr>\n                  <th>Name</th>\n                  <th>Role</th>\n                </tr>\n              </ng-template>\n              <ng-template pTemplate=\"body\" let-team>\n                <tr>\n                  <td>{{team.name}}</td>\n                  <td>{{team.role}}</td>\n                </tr>\n              </ng-template>\n            </p-table>\n          </div>\n        </div>\n      </div>\n    </p-card>\n  </div>\n</div>\n"
  },
  {
    "path": "frontend/src/app/user-profile/user-profile.component.sass",
    "content": ""
  },
  {
    "path": "frontend/src/app/user-profile/user-profile.component.spec.ts",
    "content": "import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';\n\nimport { UserProfileComponent } from './user-profile.component';\nimport { ReactiveFormsModule, ValidationErrors } from '@angular/forms';\nimport { HttpClientModule } from '@angular/common/http';\nimport { AppRoutingModule } from '../app-routing.module';\nimport { By } from '@angular/platform-browser';\nimport { UserService } from '../user.service';\n\nconst userService = {\n  patchUser: () => {},\n};\n\ndescribe('UserProfileComponent', () => {\n  let component: UserProfileComponent;\n  let fixture: ComponentFixture<UserProfileComponent>;\n\n  beforeEach(waitForAsync(() => {\n    TestBed.configureTestingModule({\n      declarations: [UserProfileComponent],\n      imports: [ReactiveFormsModule, HttpClientModule, AppRoutingModule],\n      providers: [{ provide: UserService, useValue: userService }],\n    }).compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(UserProfileComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n\n  it('it should fail when form is empty', () => {\n    expect(component.userForm.valid).toBeFalsy();\n  });\n\n  it('it should fail when First Name is not valid', () => {\n    const firstName = component.userForm.controls.firstName;\n    expect(firstName.valid).toBeFalsy();\n  });\n\n  it('it should pass if First Name has errors', () => {\n    let errors: ValidationErrors;\n    const firstName = component.userForm.controls.firstName;\n    errors = firstName.errors || {};\n    expect(errors).toBeTruthy();\n  });\n\n  it('it should fail when Last Name is not valid', () => {\n    const lastName = component.userForm.controls.lastName;\n    expect(lastName.valid).toBeFalsy();\n  });\n\n  it('it should pass if Last Name has errors', () => {\n    let errors: ValidationErrors;\n    const lastName = component.userForm.controls.lastName;\n    errors = lastName.errors || {};\n    expect(errors).toBeTruthy();\n  });\n\n  it('it should fail when Title is not valid', () => {\n    const title = component.userForm.controls.title;\n    expect(title.valid).toBeFalsy();\n  });\n\n  it('it should pass if First Name has errors', () => {\n    let errors: ValidationErrors;\n    const title = component.userForm.controls.title;\n    errors = title.errors || {};\n    expect(errors).toBeTruthy();\n  });\n\n  it('it should pass if On Submit is executed once', () => {\n    expect(component.isEdit).toBeFalsy();\n    expect(component.userForm.enabled).toBeFalsy();\n    const userForm = fixture.debugElement.query(By.css('#userForm'));\n    userForm.triggerEventHandler('submit', null);\n    expect(component.isEdit).toBeTruthy();\n    expect(component.userForm.enabled).toBeTruthy();\n  });\n\n  it('it should pass if On Submit is executed twice', () => {\n    const mockedUserService = fixture.debugElement.injector.get(UserService);\n    spyOn(mockedUserService, 'patchUser');\n    expect(component.isEdit).toBeFalsy();\n    expect(component.userForm.enabled).toBeFalsy();\n    const userForm = fixture.debugElement.query(By.css('#userForm'));\n    userForm.triggerEventHandler('submit', null);\n    expect(component.isEdit).toBeTruthy();\n    expect(component.userForm.enabled).toBeTruthy();\n    component.userForm.controls.firstName.setValue('Foo');\n    component.userForm.controls.lastName.setValue('Bar');\n    component.userForm.controls.title.setValue('Profressor');\n    expect(component.userForm.valid).toBeTruthy();\n    expect(mockedUserService.patchUser);\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/user-profile/user-profile.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormGroup, FormBuilder, Validators } from '@angular/forms';\nimport { AuthService } from '../auth.service';\nimport { Router, ActivatedRoute } from '@angular/router';\nimport { AlertService } from '../alert/alert.service';\nimport { User } from '../interfaces/User';\nimport { UserService } from '../user.service';\nimport { ApiKey } from '../interfaces/ApiKey';\n\n@Component({\n  selector: 'app-user-profile',\n  templateUrl: './user-profile.component.html',\n  styleUrls: ['./user-profile.component.sass'],\n})\nexport class UserProfileComponent implements OnInit {\n  userForm: FormGroup;\n  securityForm: FormGroup;\n  emailForm: FormGroup;\n  isEdit = false;\n  isSecurityEdit = false;\n  isEmailEdit = false;\n  user: User;\n  userApiKeyInfo: ApiKey;\n  constructor(\n    private fb: FormBuilder,\n    public authService: AuthService,\n    public router: Router,\n    public alertService: AlertService,\n    public activatedRoute: ActivatedRoute,\n    public userService: UserService\n  ) {\n    this.createForms();\n  }\n\n  ngOnInit(): void {\n    this.activatedRoute.data.subscribe(({ user }) => {\n      this.user = user;\n      this.rebuildForm();\n      this.rebuildSecurityForm();\n      this.rebuildEmailForm();\n    });\n    this.getApiKey();\n  }\n\n  createForms() {\n    this.userForm = this.fb.group({\n      firstName: [{ value: '', disabled: !this.isEdit }, Validators.required],\n      lastName: [{ value: '', disabled: !this.isEdit }, Validators.required],\n      title: [{ value: '', disabled: !this.isEdit }, Validators.required],\n    });\n    this.securityForm = this.fb.group({\n      oldPassword: [\n        { value: '', disabled: !this.isSecurityEdit },\n        Validators.required,\n      ],\n      newPassword: [\n        { value: '', disabled: !this.isSecurityEdit },\n        Validators.required,\n      ],\n      confirmNewPassword: [\n        { value: '', disabled: !this.isSecurityEdit },\n        Validators.required,\n      ],\n    });\n    this.emailForm = this.fb.group({\n      email: [\n        { value: '', disabled: !this.isEmailEdit },\n        [Validators.required, Validators.email],\n      ],\n      newEmail: [\n        { value: '', disabled: !this.isEmailEdit },\n        [Validators.required, Validators.email],\n      ],\n    });\n  }\n\n  rebuildForm() {\n    this.userForm.reset({\n      firstName: this.user.firstName,\n      lastName: this.user.lastName,\n      title: this.user.title,\n    });\n  }\n\n  rebuildSecurityForm() {\n    this.securityForm.reset({\n      oldPassword: '',\n      newPassword: '',\n      confirmNewPassword: '',\n    });\n  }\n\n  rebuildEmailForm() {\n    this.emailForm.reset({\n      email: this.user.email,\n      newEmail: '',\n    });\n  }\n  onSubmit(form: FormGroup) {\n    if (!this.isEdit) {\n      this.isEdit = true;\n      this.userForm.enable();\n    } else {\n      const userInfo: User = {\n        firstName: form.value.firstName,\n        lastName: form.value.lastName,\n        title: form.value.title,\n      };\n      this.userService.patchUser(userInfo).subscribe((res: string) => {\n        this.alertService.success(res);\n        this.isEdit = false;\n        this.userForm.disable();\n      });\n    }\n  }\n\n  onSecuritySubmit(form: FormGroup) {\n    if (!this.isSecurityEdit) {\n      this.isSecurityEdit = true;\n      this.securityForm.enable();\n    } else {\n      this.authService\n        .updatePassword(\n          form.value.oldPassword,\n          form.value.newPassword,\n          form.value.confirmNewPassword\n        )\n        .subscribe((res: string) => {\n          this.alertService.success(res);\n          this.isSecurityEdit = false;\n          this.securityForm.disable();\n          this.securityForm.reset();\n        });\n    }\n  }\n\n  onEmailFormSubmit(form: FormGroup) {\n    if (!this.isEmailEdit) {\n      this.isEmailEdit = true;\n      this.emailForm.reset();\n      this.emailForm.enable();\n    } else {\n      this.authService\n        .updateUserEmail(form.value.email, form.value.newEmail)\n        .subscribe((res: string) => {\n          this.alertService.success(res);\n          this.isEmailEdit = false;\n          this.emailForm.disable();\n          this.rebuildEmailForm();\n        });\n    }\n  }\n\n  revokeUpdateEmailRequest() {\n    this.user.newEmail = '';\n    this.authService.revokeUserEmail().subscribe((res: string) => {\n      this.alertService.success(res);\n      this.isEmailEdit = false;\n      this.emailForm.disable();\n      this.rebuildEmailForm();\n    });\n  }\n\n  generateApiKey() {\n    const confirmMessage = this.userApiKeyInfo\n      ? 'Generating a new API key will deactivate the current API key. Do you want to continue?'\n      : 'Are you sure you want to generate a new API key?';\n    const r = confirm(confirmMessage);\n    if (r) {\n      this.authService.generateApiKey().subscribe((res) => {\n        this.alertService.success('API key successfully generated');\n        this.getApiKey();\n        alert(res);\n      });\n    }\n  }\n\n  getApiKey() {\n    this.authService.getApiKeyInfo().subscribe((res: ApiKey) => {\n      this.userApiKeyInfo = res;\n    });\n  }\n\n  deactivateApiKey() {\n    const r = confirm('Are you sure you want to deactivate this API key?');\n    if (r) {\n      this.authService\n        .deactivateApiKey(this.userApiKeyInfo.id)\n        .subscribe((res: string) => {\n          this.alertService.success(res);\n          this.getApiKey();\n        });\n    }\n  }\n}\n"
  },
  {
    "path": "frontend/src/app/user.service.spec.ts",
    "content": "import { TestBed } from '@angular/core/testing';\n\nimport { UserService } from './user.service';\n\ndescribe('UserService', () => {\n  let service: UserService;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({});\n    service = TestBed.inject(UserService);\n  });\n\n  it('should be created', () => {\n    expect(service).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/user.service.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { environment } from '../environments/environment';\nimport { User } from './interfaces/User';\n@Injectable({\n  providedIn: 'root',\n})\nexport class UserService {\n  api = environment.apiUrl;\n  constructor(private http: HttpClient) {}\n\n  registerUser(creds) {\n    return this.http.post(`${this.api}/user/register`, creds);\n  }\n\n  inviteUser(email) {\n    return this.http.post(`${this.api}/user/invite`, email);\n  }\n\n  getUser() {\n    return this.http.get<User>(`${this.api}/user`);\n  }\n\n  getUsers() {\n    return this.http.get<User[]>(`${this.api}/users`);\n  }\n  getTesters(orgId: number) {\n    return this.http.get<User[]>(`${this.api}/testers/${orgId}`);\n  }\n  getAllUsers() {\n    return this.http.get<User[]>(`${this.api}/users/all`);\n  }\n  patchUser(user: User) {\n    return this.http.patch(`${this.api}/user`, user);\n  }\n  createUser(user: User) {\n    return this.http.post(`${this.api}/user`, user);\n  }\n  /**\n   * Activate a user (admin)\n   * @param id user ID\n   */\n  activateUser(id: string | number) {\n    return this.http.patch(`${this.api}/user/activate/${id}`, {});\n  }\n  /**\n   * Deactivate a user (admin)\n   * @param id user ID\n   */\n  deactivateUser(id: string | number) {\n    return this.http.patch(`${this.api}/user/deactivate/${id}`, {});\n  }\n}\n"
  },
  {
    "path": "frontend/src/app/vuln-form/Vulnerability.ts",
    "content": "import { ProblemLocation } from '../classes/ProblemLocation';\nimport { Resource } from '../classes/Resource';\n\nexport class Vulnerability {\n  constructor(\n    public id: number,\n    public impact: string,\n    public likelihood: string,\n    public risk: string,\n    public systemic: string,\n    public status: string,\n    public description: string,\n    public remediation: string,\n    public name: string,\n    public assessment: number,\n    public jiraId: string,\n    public cvssScore: number,\n    public cvssUrl: string,\n    public detailedInfo: string,\n    public screenshots: FormData,\n    public problemLocations: ProblemLocation[],\n    public resources: Resource[]\n  ) {}\n}\n"
  },
  {
    "path": "frontend/src/app/vuln-form/vuln-form.component.html",
    "content": "<div class=\"container-fluid col-6\">\n  <form [formGroup]=\"vulnForm\" name=\"vulnForm\" (ngSubmit)=\"onSubmit(vulnForm)\" class=\"mx-auto\">\n    <div>\n      <label for=\"name\">Vulnerability Name</label>\n      <input formControlName=\"name\" type=\"text\" class=\"form-control form-control-sm\" id=\"name\" />\n      <div *ngIf=\"vulnForm.controls['name'].errors && !vulnForm.controls['name'].pristine\" class=\"alert alert-danger\">\n        Vulnerability name is required\n      </div>\n    </div>\n    <div class=\"form-row\">\n      <div class=\"col-4\">\n        <label for=\"status\">Status</label>\n        <select formControlName=\"status\" class=\"form-control form-control-sm\" id=\"status\">\n          <option>Open</option>\n          <option>Resolved</option>\n          <option>On Hold</option>\n        </select>\n        <div *ngIf=\"vulnForm.controls['name'].errors && !vulnForm.controls['name'].pristine\" class=\"alert alert-danger\">\n          Vulnerability name is required\n        </div>\n      </div>\n      <div class=\"col-4\">\n        <label for=\"name\">Jira URL</label>\n        <input formControlName=\"jiraId\" type=\"text\" class=\"form-control form-control-sm\" id=\"jiraId\" />\n        <div *ngIf=\"vulnForm.controls['name'].errors && !vulnForm.controls['jiraId'].pristine\"\n          class=\"alert alert-danger\">\n          Jira ticket is required\n        </div>\n      </div>\n      <div class=\"col-4\">\n        <label for=\"systemic\">Systemic</label>\n        <select formControlName=\"systemic\" class=\"form-control form-control-sm\" id=\"systemic\">\n          <option>Yes</option>\n          <option>No</option>\n        </select>\n        <div *ngIf=\"vulnForm.controls['systemic'].errors && !vulnForm.controls['systemic'].pristine\"\n          class=\"alert alert-danger\">\n          Systemic value is required\n        </div>\n      </div>\n    </div>\n\n    <div class=\"form-row\">\n      <div class=\"col-4\">\n        <label for=\"impact\">Impact</label>\n        <select formControlName=\"impact\" class=\"form-control form-control-sm\" id=\"impact\">\n          <option>High</option>\n          <option>Medium</option>\n          <option>Low</option>\n        </select>\n        <div *ngIf=\"vulnForm.controls['impact'].errors && !vulnForm.controls['impact'].pristine\"\n          class=\"alert alert-danger\">\n          Impact is not valid\n        </div>\n      </div>\n      <div class=\"col-4\">\n        <label for=\"likelihood\">Likelihood</label>\n        <select formControlName=\"likelihood\" class=\"form-control form-control-sm\" id=\"likelihood\">\n          <option>High</option>\n          <option>Medium</option>\n          <option>Low</option>\n        </select>\n        <div *ngIf=\"vulnForm.controls['likelihood'].errors && !vulnForm.controls['likelihood'].pristine\"\n          class=\"alert alert-danger\">\n          Likelihood is not valid\n        </div>\n      </div>\n      <div class=\"col-4\">\n        <label for=\"risk\">Risk</label>\n        <select formControlName=\"risk\" class=\"form-control form-control-sm\" id=\"risk\">\n          <option>Critical</option>\n          <option>High</option>\n          <option>Medium</option>\n          <option>Low</option>\n          <option>Informational</option>\n        </select>\n        <div *ngIf=\"vulnForm.controls['risk'].errors && !vulnForm.controls['risk'].pristine\" class=\"alert alert-danger\">\n          Risk not valid\n        </div>\n      </div>\n    </div>\n    <div class=\"form-row\">\n      <div class=\"col-6\">\n        <label for=\"cvssScore\">CVSS Score</label>\n        <input formControlName=\"cvssScore\" type=\"text\" class=\"form-control form-control-sm\" id=\"cvssScore\" />\n        <div *ngIf=\"vulnForm.controls['cvssScore'].errors && !vulnForm.controls['cvssScore'].pristine\"\n          class=\"alert alert-danger\">\n          CVSS Score is not valid\n        </div>\n      </div>\n      <div class=\"col-6\">\n        <label for=\"cvssUrl\">CVSS URL</label>\n        <input formControlName=\"cvssUrl\" type=\"text\" class=\"form-control form-control-sm\" id=\"cvssUrl\" />\n        <div *ngIf=\"vulnForm.controls['cvssUrl'].errors && !vulnForm.controls['cvssUrl'].pristine\"\n          class=\"alert alert-danger\">\n          CVSS URL is not valid\n        </div>\n      </div>\n    </div>\n    <br />\n    <div formArrayName=\"problemLocations\">\n      <table class=\"table\">\n        <thead>\n          <tr>\n            <th scope=\"col\">Problem Location</th>\n            <th scope=\"col\">Target</th>\n            <th>\n              <button [disabled]=\"readOnly\" class=\"btn btn-primary\" type=\"button\" (click)=\"addProbLoc()\">\n                <i class=\"pi pi-plus\"></i>\n              </button>\n            </th>\n          </tr>\n        </thead>\n        <tbody *ngFor=\"let probLoc of probLocArr.controls; let i = index\" [formGroupName]=\"i\">\n          <tr>\n            <td><input [readonly]=\"readOnly\" formControlName=\"location\" class=\"form-control\" /></td>\n            <td><input [readonly]=\"readOnly\" formControlName=\"target\" class=\"form-control\" /></td>\n            <td>\n              <button [disabled]=\"readOnly\" class=\"btn btn-secondary\" type=\"button\" (click)=\"deleteProbLoc(i)\">\n                <i class=\"pi pi-trash\"></i>\n              </button>\n            </td>\n          </tr>\n        </tbody>\n      </table>\n    </div>\n\n    <div class=\"row\">\n      <div class=\"col-12\">\n        <label for=\"description\">Description</label>\n        <div class=\"float-right\">\n          <i class=\"pi pi-eye\" (click)=\"toggleDescPreview()\" *ngIf=\"!previewDescription\"></i>\n          <i class=\"pi pi-eye-slash\" (click)=\"toggleDescPreview()\" *ngIf=\"previewDescription\"></i>\n        </div>\n      </div>\n    </div>\n    <div *ngIf=\"!previewDescription\">\n      <textarea formControlName=\"description\" class=\"form-control\" id=\"description\" rows=\"6\"\n        maxlength=\"4000\"></textarea>\n      <div *ngIf=\"vulnForm.controls['description'].errors && !vulnForm.controls['description'].pristine\"\n        class=\"alert alert-danger\">\n        Description is not valid\n      </div>\n    </div>\n    <div *ngIf=\"previewDescription\" class=\"previewBox\">\n      <markdown class=\"variable-binding\" [data]=\"vulnForm.get('description').value\"></markdown>\n    </div>\n\n    <div class=\"row\">\n      <div class=\"col-12\">\n        <label for=\"detailedInfo\">Detailed Information</label>\n        <div class=\"float-right\">\n          <i class=\"pi pi-eye\" (click)=\"toggleDetailedDescPreview()\" *ngIf=\"!previewDetailedDesc\"></i>\n          <i class=\"pi pi-eye-slash\" (click)=\"toggleDetailedDescPreview()\" *ngIf=\"previewDetailedDesc\"></i>\n        </div>\n      </div>\n    </div>\n    <div *ngIf=\"!previewDetailedDesc\">\n      <textarea formControlName=\"detailedInfo\" class=\"form-control\" id=\"detailedInfo\" rows=\"6\"\n        maxlength=\"4000\"></textarea>\n      <div *ngIf=\"vulnForm.controls['detailedInfo'].errors && !vulnForm.controls['detailedInfo'].pristine\"\n        class=\"alert alert-danger\">\n        Detailed information is not valid\n      </div>\n    </div>\n    <div *ngIf=\"previewDetailedDesc\" class=\"previewBox\">\n      <markdown class=\"variable-binding\" [data]=\"vulnForm.get('detailedInfo').value\"></markdown>\n    </div>\n\n    <div class=\"row\">\n      <div class=\"col-12\">\n        <label for=\"remediation\">Remediation</label>\n        <div class=\"float-right\">\n          <i class=\"pi pi-eye\" (click)=\"toggleRemediationPreview()\" *ngIf=\"!previewRemediation\"></i>\n          <i class=\"pi pi-eye-slash\" (click)=\"toggleRemediationPreview()\" *ngIf=\"previewRemediation\"\n            [icon]=\"faEyeSlash\"></i>\n        </div>\n      </div>\n    </div>\n    <div *ngIf=\"!previewRemediation\">\n      <textarea formControlName=\"remediation\" class=\"form-control\" id=\"remediation\" rows=\"6\"\n        maxlength=\"4000\"></textarea>\n      <div *ngIf=\"vulnForm.controls['remediation'].errors && !vulnForm.controls['remediation'].pristine\"\n        class=\"alert alert-danger\">\n        Remediation information is not valid\n      </div>\n    </div>\n    <div *ngIf=\"previewRemediation\" class=\"previewBox\">\n      <markdown class=\"variable-binding\" [data]=\"vulnForm.get('remediation').value\"></markdown>\n    </div>\n\n    <br />\n    <div formArrayName=\"resources\">\n      <table class=\"table\">\n        <thead>\n          <tr>\n            <th scope=\"col\">Description</th>\n            <th scope=\"col\">Resource URL</th>\n            <th>\n              <button [disabled]=\"readOnly\" class=\"btn btn-primary\" type=\"button\" (click)=\"addResource()\">\n                <i class=\"pi pi-plus\"></i>\n              </button>\n            </th>\n          </tr>\n        </thead>\n        <tbody *ngFor=\"let resource of resourceArr.controls; let i = index\" [formGroupName]=\"i\">\n          <tr>\n            <td><input [readonly]=\"readOnly\" formControlName=\"description\" class=\"form-control\"\n                placeholder=\"description\" /></td>\n            <td><input [readonly]=\"readOnly\" formControlName=\"url\" class=\"form-control\" placeholder=\"URL\" /></td>\n            <td>\n              <button [disabled]=\"readOnly\" class=\"btn btn-secondary\" type=\"button\" (click)=\"deleteResource(i)\">\n                <i class=\"pi pi-trash\"></i>\n              </button>\n            </td>\n          </tr>\n        </tbody>\n      </table>\n    </div>\n    <div>\n      <label for=\"screenshot\">Screenshot Upload</label>\n      <input [disabled]=\"readOnly\" name=\"screenshots\" type=\"file\" class=\"form-control-file\" id=\"screenshots\"\n        (change)=\"handleFileInput($event.target.files)\" multiple enctype=\"multipart/form-data\" />\n      <div *ngFor=\"let screenshot of tempScreenshots\">\n        <span>\n          <a [href]=\"screenshot.url\" target=\"_blank\">{{ screenshot?.fileName}}</a>\n          <a>\n            <i (click)=\"deleteScreenshot(screenshot)\" class=\"pi pi-trash\" style=\"margin-left: 5px\"></i>\n          </a>\n        </span>\n      </div>\n    </div>\n    <br />\n    <button *ngIf=\"!readOnly\" [disabled]=\"!vulnForm.valid\" type=\"submit\" class=\"btn btn-primary float-right\">\n      Submit\n    </button>\n    <button *ngIf=\"!readOnly\" type=\"button\" style=\"margin-right: 5px;\" (click)=\"exportToJira()\"\n      [disabled]=\"!this.vulnId\" class=\"btn btn-info float-right\">\n      Export to Jira\n    </button>\n    <button (click)=\"navigateToVulnerabilities()\" type=\"button\" class=\"btn btn-secondary float-right\"\n      style=\"margin-right: 5px;\">\n      Back to Vulnerabilities\n    </button>\n  </form>\n</div>\n"
  },
  {
    "path": "frontend/src/app/vuln-form/vuln-form.component.sass",
    "content": ".previewBox\n  border: 1px solid #ced4da\n  padding: .375rem .75rem\n  border-radius: .25rem"
  },
  {
    "path": "frontend/src/app/vuln-form/vuln-form.component.spec.ts",
    "content": "import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';\n\nimport { VulnFormComponent } from './vuln-form.component';\n\ndescribe('VulnFormComponent', () => {\n  let component: VulnFormComponent;\n  let fixture: ComponentFixture<VulnFormComponent>;\n\n  beforeEach(waitForAsync(() => {\n    TestBed.configureTestingModule({\n      declarations: [ VulnFormComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(VulnFormComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/vuln-form/vuln-form.component.ts",
    "content": "import { Component, OnChanges, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators, FormArray } from '@angular/forms';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { AppService } from '../app.service';\nimport { AlertService } from '../alert/alert.service';\nimport { Vulnerability } from './Vulnerability';\nimport { AppFile } from '../interfaces/App_File';\nimport { Screenshot } from '../interfaces/Screenshot';\n\n@Component({\n  selector: 'app-vuln-form',\n  templateUrl: './vuln-form.component.html',\n  styleUrls: ['./vuln-form.component.sass'],\n})\nexport class VulnFormComponent implements OnChanges, OnInit {\n  vulnModel: Vulnerability;\n  vulnForm: FormGroup;\n  submitted = false;\n  alertType: string;\n  alertMessage: string;\n  orgId: string;\n  assetId: string;\n  assessmentId: string;\n  vulnId: number;\n  vulnFormData: FormData;\n  jiraHost: string;\n  tempScreenshots: Screenshot[] = [];\n  screenshotsToDelete: number[] = [];\n  previewDescription = false;\n  previewDetailedDesc = false;\n  previewRemediation = false;\n  impactAssess = 0;\n  likelihoodAssess = 0;\n  riskAssess = 0;\n  readOnly: boolean;\n  constructor(\n    private appService: AppService,\n    public activatedRoute: ActivatedRoute,\n    public router: Router,\n    private fb: FormBuilder,\n    private alertService: AlertService\n  ) {\n    this.createForm();\n  }\n\n  /**\n   * Specific to the vulnerability form, init is called to retreive all data associated with the vuln-form component\n   * Attach subscriptions to likelihood and impact for dynamic risk attribute in the form\n   */\n  ngOnInit() {\n    this.vulnForm.get('impact').valueChanges.subscribe((value) => {\n      if (this.impactAssess === 0) {\n        if (value === 'High') {\n          this.impactAssess += 3;\n        } else if (value === 'Medium') {\n          this.impactAssess += 2;\n        } else {\n          this.impactAssess += 1;\n        }\n        this.updateRisk();\n      } else {\n        this.impactAssess = 0;\n        if (value === 'High') {\n          this.impactAssess += 3;\n        } else if (value === 'Medium') {\n          this.impactAssess += 2;\n        } else {\n          this.impactAssess += 1;\n        }\n        this.updateRisk();\n      }\n    });\n    this.vulnForm.get('likelihood').valueChanges.subscribe((value) => {\n      if (this.likelihoodAssess === 0) {\n        if (value === 'High') {\n          this.likelihoodAssess += 3;\n        } else if (value === 'Medium') {\n          this.likelihoodAssess += 2;\n        } else {\n          this.likelihoodAssess += 1;\n        }\n        this.updateRisk();\n      } else {\n        this.likelihoodAssess = 0;\n        if (value === 'High') {\n          this.likelihoodAssess += 3;\n        } else if (value === 'Medium') {\n          this.likelihoodAssess += 2;\n        } else {\n          this.likelihoodAssess += 1;\n        }\n        this.updateRisk();\n      }\n    });\n    this.activatedRoute.data.subscribe(({ vulnInfo }) => {\n      this.readOnly = vulnInfo.readOnly;\n      if (this.readOnly) {\n        this.vulnForm.disable();\n      }\n      if (vulnInfo && vulnInfo.jiraHost) {\n        this.jiraHost = vulnInfo.jiraHost;\n      }\n      if (vulnInfo && vulnInfo.vulnerability) {\n        this.vulnModel = vulnInfo.vulnerability;\n        for (const probLoc of vulnInfo.vulnerability.problemLocations) {\n          this.probLocArr.push(\n            this.fb.group({\n              id: probLoc.id,\n              location: probLoc.location,\n              target: probLoc.target,\n            })\n          );\n        }\n        for (const resource of vulnInfo.vulnerability.resources) {\n          this.resourceArr.push(\n            this.fb.group({\n              id: resource.id,\n              description: resource.description,\n              url: resource.url,\n            })\n          );\n        }\n        for (const file of vulnInfo.vulnerability.screenshots) {\n          const existFile: AppFile = file;\n          this.appService.getImageById(existFile).then((url) => {\n            existFile.imgUrl = url;\n            this.previewScreenshot(null, existFile);\n          });\n        }\n        this.vulnForm.patchValue(vulnInfo.vulnerability);\n      }\n    });\n    this.activatedRoute.params.subscribe((params) => {\n      this.orgId = params.orgId;\n      this.assetId = params.assetId;\n      this.assessmentId = params.assessmentId;\n      this.vulnId = params.vulnId;\n    });\n  }\n\n  ngOnChanges() {\n    this.rebuildForm();\n  }\n\n  /**\n   * Function responsible for the generation of the Vulnerability form\n   * this is a requirement for reactive forms in Angular\n   */\n  createForm() {\n    this.vulnForm = this.fb.group({\n      impact: ['', [Validators.required]],\n      likelihood: ['', [Validators.required]],\n      risk: ['', [Validators.required]],\n      systemic: ['', [Validators.required]],\n      status: ['', Validators.required],\n      description: ['', [Validators.required, Validators.maxLength(4000)]],\n      remediation: ['', [Validators.required, Validators.maxLength(4000)]],\n      name: ['', [Validators.required]],\n      jiraId: ['', []],\n      cvssScore: ['', Validators.required],\n      cvssUrl: ['', Validators.required],\n      detailedInfo: ['', [Validators.required, Validators.maxLength(4000)]],\n      problemLocations: this.fb.array([]),\n      resources: this.fb.array([]),\n    });\n  }\n\n  /**\n   * Function is required to rebuild the form when requested it is required\n   * for reactive forms in Angular\n   */\n  rebuildForm() {\n    this.vulnForm.reset({\n      impact: this.vulnModel.impact,\n      likelihood: this.vulnModel.likelihood,\n      risk: this.vulnModel.risk,\n      systemic: this.vulnModel.systemic,\n      status: this.vulnModel.status,\n      description: this.vulnModel.description,\n      remediation: this.vulnModel.remediation,\n      name: this.vulnModel.name,\n      jiraId: this.vulnModel.jiraId,\n      cvssScore: this.vulnModel.cvssScore,\n      cvssUrl: this.vulnModel.cvssUrl,\n      detailedInfo: this.vulnModel.detailedInfo,\n      problemLocations: this.vulnModel.problemLocations,\n      resources: this.vulnModel.resources,\n    });\n  }\n\n  /**\n   * Gets array with all stored problem locations for retrevial by the UI\n   * @return problem location array data to be passed into the form for submission\n   */\n  get probLocArr() {\n    return this.vulnForm.get('problemLocations') as FormArray;\n  }\n\n  /**\n   * Function responsible for initializing the form fields for location and target\n   * needed for pulling data when a vulnerability is edited or a new vulnerability resource\n   * is added by the user with the '+' icon, specific to Problem Location\n   */\n  initProbLocRows(): FormGroup {\n    return this.fb.group({\n      location: '',\n      target: '',\n    });\n  }\n\n  /**\n   * Function responsible for adding content into the ProbLocArr[]\n   * Populates within the reactive form for submission later in the process\n   */\n  addProbLoc() {\n    this.probLocArr.push(this.initProbLocRows());\n  }\n\n  /**\n   * Function responsible for removing content from the ProbLocArr[]\n   * Removes elements from the array by index value\n   * @param index is the ID of the index to be removed from the array\n   */\n  deleteProbLoc(index: number) {\n    this.probLocArr.removeAt(index);\n  }\n\n  /**\n   * Get array with all stored resources required within the Vulnerability form\n   * later used in the form submission call\n   * @return retuns the array of resource locations added into the vulnerability form\n   */\n  get resourceArr() {\n    return this.vulnForm.get('resources') as FormArray;\n  }\n\n  /**\n   * Function responsible for initializing the form fields for description and url\n   * needed for pulling data when a vulnerability is edited or a new vulnerability resource\n   * is added by the user with the '+' icon, specific to Resources\n   */\n  initResourceRows(): FormGroup {\n    return this.fb.group({\n      description: '',\n      url: '',\n    });\n  }\n\n  /**\n   * Function responsible for adding form data to the Resource Array\n   * utilizing the data within the reactive form\n   */\n  addResource() {\n    this.resourceArr.push(this.initResourceRows());\n  }\n\n  /**\n   * Function responsible for deletion of a resource from the form\n   * @param index is the associated value of the array index value to be removed\n   */\n  deleteResource(index: number) {\n    this.resourceArr.removeAt(index);\n  }\n\n  /**\n   * Function is responsible for processing a file array to be used in the form\n   * iterates over all attached files to be processed\n   */\n  handleFileInput(files: FileList) {\n    for (let i = 0; i < files.length; i++) {\n      const file = files.item(i);\n      this.previewScreenshot(file, null);\n    }\n  }\n\n  /**\n   * Function responsible for retreiving an image and processing it back to the UI\n   * renders it back to the browser using the createObjectUrl feature\n   */\n  previewScreenshot(file: File, existFile: AppFile) {\n    // Image from DB\n    if (existFile) {\n      const screenshot: Screenshot = {\n        url: existFile.imgUrl,\n        file: existFile,\n        fileName: existFile.originalname,\n        fileId: existFile.id,\n      };\n      this.tempScreenshots.push(screenshot);\n    } else {\n      const screenshot: Screenshot = {\n        url: this.appService.createObjectUrl(file),\n        file,\n        fileName: file.name,\n        fileId: null,\n      };\n      this.tempScreenshots.push(screenshot);\n    }\n  }\n\n  /**\n   * Function responsible for removal of screenshots from the Vulnerability Form\n   * @param file the associated index of the file to be removed\n   */\n  deleteScreenshot(screenshot: Screenshot) {\n    const index = this.tempScreenshots.indexOf(screenshot);\n    if (index > -1) {\n      this.screenshotsToDelete.push(screenshot.fileId);\n      this.tempScreenshots.splice(index, 1);\n    }\n  }\n\n  /**\n   * Function responsible for populating the screenshot array and removal\n   * of screenshots performed during data entry\n   * @param screenshots object data for screenshots to be processed\n   * @param screenshotsToDelete object data for screenshots to be removed\n   */\n  finalizeScreenshots(\n    screenshots: Screenshot[],\n    screenshotsToDelete: number[]\n  ) {\n    this.vulnFormData.delete('screenshots');\n    if (screenshots.length) {\n      for (const screenshot of screenshots) {\n        this.vulnFormData.append('screenshots', screenshot.file);\n      }\n    }\n    if (screenshotsToDelete.length) {\n      this.vulnFormData.append(\n        'screenshotsToDelete',\n        JSON.stringify(screenshotsToDelete)\n      );\n    }\n  }\n\n  /**\n   * Function to navigate to Vulnerabilities, takes no input from the user\n   */\n  navigateToVulnerabilities() {\n    this.router.navigate([\n      `organization/${this.orgId}/asset/${this.assetId}/assessment/${this.assessmentId}/vulnerability`,\n    ]);\n  }\n\n  /**\n   * Function responsible for handling the form submission objects and data\n   * @param vulnForm form object holding data to be processed\n   */\n  onSubmit(vulnForm: FormGroup) {\n    this.vulnFormData = new FormData();\n    const newScreenshots = this.tempScreenshots.filter(\n      (screenshot) => !screenshot.fileId\n    );\n    this.finalizeScreenshots(newScreenshots, this.screenshotsToDelete);\n    this.vulnModel = vulnForm.value;\n    this.vulnFormData.append('impact', this.vulnModel.impact);\n    this.vulnFormData.append('likelihood', this.vulnModel.likelihood);\n    this.vulnFormData.append('risk', this.vulnModel.risk);\n    this.vulnFormData.append('systemic', this.vulnModel.systemic);\n    this.vulnFormData.append('status', this.vulnModel.status);\n    this.vulnFormData.append('description', this.vulnModel.description);\n    this.vulnFormData.append('remediation', this.vulnModel.remediation);\n    this.vulnFormData.append('jiraId', this.vulnModel.jiraId);\n    this.vulnFormData.append('cvssScore', this.vulnModel.cvssScore.toString());\n    this.vulnFormData.append('cvssUrl', this.vulnModel.cvssUrl);\n    this.vulnFormData.append('detailedInfo', this.vulnModel.detailedInfo);\n    this.vulnFormData.append('assessment', this.assessmentId);\n    this.vulnFormData.append('name', this.vulnModel.name);\n    this.vulnFormData.append(\n      'problemLocations',\n      JSON.stringify(this.vulnModel.problemLocations)\n    );\n    this.vulnFormData.append(\n      'resources',\n      JSON.stringify(this.vulnModel.resources)\n    );\n    this.createOrUpdateVuln(this.vulnFormData);\n  }\n\n  /**\n   * Function responsible for handling the vulnerability form data\n   * processes the data for either creation or updating a vulnerability\n   * @param vuln form object holding all data to be processed\n   */\n  createOrUpdateVuln(vuln: FormData) {\n    if (this.vulnId) {\n      this.appService\n        .updateVulnerability(this.vulnId, vuln)\n        .subscribe((res: string) => {\n          this.navigateToVulnerabilities();\n          this.alertService.success(res);\n        });\n    } else {\n      this.appService.createVuln(vuln).subscribe((res: string) => {\n        this.navigateToVulnerabilities();\n        this.alertService.success(res);\n      });\n    }\n  }\n\n  /**\n   * Function responsible for either showing a preview or hiding a preview\n   * within the form for showing the data in markup or as regular text for\n   * Description\n   */\n  toggleDescPreview() {\n    this.previewDescription = !this.previewDescription;\n  }\n\n  /**\n   * Function responsible for either showing a preview or hiding a preview\n   * within the form showing the data in markup or as regular text for\n   * Detailed Information\n   */\n  toggleDetailedDescPreview() {\n    this.previewDetailedDesc = !this.previewDetailedDesc;\n  }\n\n  /**\n   * Function responsible for either showing a preview or hiding a preview\n   * within the form showing the data in markup or as regular text for\n   * Remediation\n   */\n  toggleRemediationPreview() {\n    this.previewRemediation = !this.previewRemediation;\n  }\n\n  /**\n   * Function responsible for updating the risk value on the vulnerability form\n   * based on impact and likelihood value held within the vuln-form object\n   */\n  updateRisk() {\n    const value: number = this.impactAssess + this.likelihoodAssess;\n    this.riskAssess = value;\n\n    if (value === 6) {\n      this.vulnForm.patchValue({\n        risk: 'Critical',\n      });\n    } else if (value === 5) {\n      this.vulnForm.patchValue({\n        risk: 'High',\n      });\n    } else if (value === 4) {\n      this.vulnForm.patchValue({\n        risk: 'Medium',\n      });\n    } else if (value === 3) {\n      this.vulnForm.patchValue({\n        risk: 'Low',\n      });\n    } else {\n      this.vulnForm.patchValue({\n        risk: 'Informational',\n      });\n    }\n  }\n\n  exportToJira() {\n    const r = confirm(\n      `Export vulnerability \"${this.vulnModel.name}\" to Jira host: ${this.jiraHost}?`\n    );\n    if (r) {\n      if (this.vulnForm.dirty) {\n        this.alertService.warn(\n          'Vulnerability form updates detected.  Please save the vulnerability before exporting to JIRA.'\n        );\n      } else {\n        this.appService\n          .exportVulnToJira(this.vulnId)\n          .subscribe((res: string) => {\n            this.navigateToVulnerabilities();\n            this.alertService.success(res);\n          });\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "frontend/src/app/vulnerability/vulnerability.component.html",
    "content": "<div class=\"container-fluid\">\n  <p-table #vulnTable [value]=\"vulnAry\" [paginator]=\"true\" [rows]=\"10\" styleClass=\"p-datatable-striped\">\n    <ng-template pTemplate=\"header\">\n      <tr>\n        <th pSortableColumn=\"id\">Vulnerability ID<p-sortIcon field=\"id\"></p-sortIcon>\n        </th>\n        <th pSortableColumn=\"name\">Vulnerability Name<p-sortIcon field=\"name\"></p-sortIcon>\n        </th>\n        <th pSortableColumn=\"risk\">Risk<p-sortIcon field=\"risk\"></p-sortIcon>\n        </th>\n        <th pSortableColumn=\"systemic\">Systemic<p-sortIcon field=\"systemic\"></p-sortIcon>\n        </th>\n        <th scope=\"col\">Jira URL</th>\n        <th scope=\"col\">Status</th>\n        <th scope=\"col\">Actions</th>\n      </tr>\n      <tr>\n        <th>\n          <input pInputText type=\"text\" (input)=\"vulnTable.filter($event.target.value, 'id', 'equals')\"\n            placeholder=\"Search by ID\" class=\"p-column-filter\">\n        </th>\n        <th>\n          <input pInputText type=\"text\" (input)=\"vulnTable.filter($event.target.value, 'name', 'startsWith')\"\n            placeholder=\"Search by Name\" class=\"p-column-filter\">\n        </th>\n        <th>\n          <p-multiSelect [options]=\"risks\" placeholder=\"All\" (onChange)=\"onRiskChange($event)\" optionLabel=\"name\"\n            styleClass=\"p-column-filter\">\n            <ng-template let-option pTemplate=\"item\">\n              <div class=\"p-multiselect-representative-option\">\n                <span class=\"p-ml-1\">{{option.name}}</span>\n              </div>\n            </ng-template>\n          </p-multiSelect>\n        </th>\n        <th>\n          <!-- No need for Systemic filter as it's only yes or no-->\n        </th>\n        <th>\n          <input pInputText type=\"text\" (input)=\"vulnTable.filter($event.target.value, 'jiraId', 'contains')\"\n            placeholder=\"Search by Jira ID\" class=\"p-column-filter\">\n        </th>\n        <th>\n          <p-multiSelect [options]=\"statuses\" placeholder=\"All\" (onChange)=\"onStatusChange($event)\" optionLabel=\"name\"\n            styleClass=\"p-column-filter\">\n            <ng-template let-option pTemplate=\"item\">\n              <div class=\"p-multiselect-representative-option\">\n                <span class=\"p-ml-1\">{{option.name}}</span>\n              </div>\n            </ng-template>\n          </p-multiSelect>\n        </th>\n        <th></th>\n      </tr>\n    </ng-template>\n    <ng-template pTemplate=\"body\" let-vuln>\n      <tr>\n        <td scope=\"row\">{{ vuln?.id }}</td>\n        <td>{{ vuln?.name }}</td>\n        <td>{{ vuln?.risk }}</td>\n        <td>{{ vuln?.systemic }}</td>\n        <td>\n          <a target=\"_blank\" [href]=\"vuln?.jiraId\">{{ vuln?.jiraId }}</a>\n        </td>\n        <td>{{ vuln?.status }}</td>\n        <td>\n          <button (click)=\"navigateToVulnerabilityFormById(vuln.id)\" class=\"btn btn-secondary\" type=\"button\"\n            style=\"margin-right: 10px;\">\n            <i *ngIf=\"!readOnly\" class=\"pi pi-pencil\"></i>\n            <i *ngIf=\"readOnly\" class=\"pi pi-eye\"></i>\n          </button>\n          <button *ngIf=\"!readOnly\" (click)=\"deleteVuln(vuln)\" class=\"btn btn-dark\" type=\"button\">\n            <i class=\"pi pi-trash\"></i>\n          </button>\n        </td>\n      </tr>\n    </ng-template>\n  </p-table>\n  <button *ngIf=\"!readOnly\" (click)=\"navigateToVulnerabilityForm()\" type=\"button\" class=\"btn btn-primary float-right\">\n    Create Vulnerability\n  </button>\n  <button (click)=\"navigateToReport()\" type=\"button\" class=\"btn btn-info float-right\" style=\"margin-right: 5px\">\n    Preview Report\n  </button>\n  <button (click)=\"navigateToAssessments()\" type=\"button\" class=\"btn btn-secondary float-right\"\n    style=\"margin-right: 5px\">\n    Back to Assessments\n  </button>\n</div>\n"
  },
  {
    "path": "frontend/src/app/vulnerability/vulnerability.component.sass",
    "content": ""
  },
  {
    "path": "frontend/src/app/vulnerability/vulnerability.component.spec.ts",
    "content": "import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';\n\nimport { VulnerabilityComponent } from './vulnerability.component';\n\ndescribe('VulnerabilityComponent', () => {\n  let component: VulnerabilityComponent;\n  let fixture: ComponentFixture<VulnerabilityComponent>;\n\n  beforeEach(waitForAsync(() => {\n    TestBed.configureTestingModule({\n      declarations: [ VulnerabilityComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(VulnerabilityComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "frontend/src/app/vulnerability/vulnerability.component.ts",
    "content": "import { Component, OnInit, ViewChild } from '@angular/core';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { Vulnerability } from '../vuln-form/Vulnerability';\nimport { AppService } from '../app.service';\nimport { AlertService } from '../alert/alert.service';\nimport { Table } from 'primeng/table';\n\n@Component({\n  selector: 'app-vulnerability',\n  templateUrl: './vulnerability.component.html',\n  styleUrls: ['./vulnerability.component.sass'],\n})\nexport class VulnerabilityComponent implements OnInit {\n  vulnAry: any = [];\n  assetId: number;\n  assessmentId: number;\n  orgId: number;\n  readOnly: boolean;\n  risks = [\n    { name: 'Informational' },\n    { name: 'Low' },\n    { name: 'Medium' },\n    { name: 'High' },\n    { name: 'Critical' },\n  ];\n  statuses = [{ name: 'Open' }, { name: 'Resolved' }, { name: 'On Hold' }];\n  @ViewChild('vulnTable') table: Table;\n\n  constructor(\n    public activatedRoute: ActivatedRoute,\n    public router: Router,\n    public appService: AppService,\n    public alertService: AlertService\n  ) {}\n\n  ngOnInit() {\n    this.activatedRoute.data.subscribe(({ vulnerabilities }) => {\n      this.vulnAry = vulnerabilities.vulnerabilities;\n      this.readOnly = vulnerabilities.readOnly;\n    });\n    this.activatedRoute.params.subscribe((params) => {\n      this.assetId = params.assetId;\n      this.assessmentId = params.assessmentId;\n      this.orgId = params.orgId;\n    });\n  }\n\n  /**\n   * Function to navigate the user to the Vulnerability Form\n   * Takes no arguments, passes the org id, asset id, assessment id, and loads the form\n   */\n  navigateToVulnerabilityForm() {\n    this.router.navigate([\n      `organization/${this.orgId}/asset/${this.assetId}/assessment/${this.assessmentId}/vuln-form`,\n    ]);\n  }\n\n  /**\n   * Function responsible for navigating to a vulnerability\n   * @param vulnId is the ID of the vulnerability requested\n   */\n  navigateToVulnerabilityFormById(vulnId: number) {\n    this.router.navigate([\n      `organization/${this.orgId}/asset/${this.assetId}/assessment/${this.assessmentId}/vuln-form/${vulnId}`,\n    ]);\n  }\n\n  /**\n   * Function responsible for navigating to an assessment, takes no params directly\n   */\n  navigateToAssessments() {\n    this.router.navigate([`organization/${this.orgId}/asset/${this.assetId}`]);\n  }\n\n  /**\n   * Function responsible for navigating to report area, takes no params directly\n   */\n  navigateToReport() {\n    this.router.navigate([\n      `organization/${this.orgId}/asset/${this.assetId}/assessment/${this.assessmentId}/report`,\n    ]);\n  }\n\n  getVulnerabilities() {\n    this.appService.getVulnerabilities(this.assessmentId).subscribe((vulns) => {\n      this.vulnAry = [];\n      this.vulnAry = vulns;\n    });\n  }\n\n  /**\n   * Function responsible for deleting a vulnerability\n   * @param vuln associated data for the vulnerability, cleans up associations\n   * of the vulnerability and the assessment it is assigned to by ID\n   */\n  deleteVuln(vuln: Vulnerability) {\n    const r = confirm(`Delete the vulnerability \"${vuln.name}\"`);\n    if (r === true) {\n      this.appService.deleteVuln(vuln.id).subscribe((success: string) => {\n        this.getVulnerabilities();\n        this.alertService.success(success);\n      });\n    }\n  }\n\n  onRiskChange(event) {\n    const selectedRiskAry = event.value.map((x) => x.name);\n    this.table.filter(selectedRiskAry, 'risk', 'in');\n  }\n\n  onStatusChange(event) {\n    const selectedStatusAry = event.value.map((x) => x.name);\n    this.table.filter(selectedStatusAry, 'status', 'in');\n  }\n}\n"
  },
  {
    "path": "frontend/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "frontend/src/assets/theme/button.scss",
    "content": ".btn-success.p-inputtext {\n  background-color: #28a745;\n  border-color: #28a745;\n  color: white;\n}\n\n.btn-success.p-inputtext:enabled:focus {\n  box-shadow: 0 0 0 0.2rem rgba(72, 180, 97, 0.5);\n  border-color: #28a745;\n}\n"
  },
  {
    "path": "frontend/src/environments/environment.prod.ts",
    "content": "\n          export const environment = {\n             production: true,\n             apiUrl: \"http://localhost:4500/api\",\n          };\n          "
  },
  {
    "path": "frontend/src/environments/environment.ts",
    "content": "// This file can be replaced during build by using the `fileReplacements` array.\n// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.\n// The list of file replacements can be found in `angular.json`.\n\nexport const environment = {\n  production: false,\n  apiUrl: '',\n};\n\n/*\n * For easier debugging in development mode, you can import the following file\n * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.\n *\n * This import should be commented out in production mode because it will have a negative impact\n * on performance if an error is thrown.\n */\n// import 'zone.js/plugins/zone-error';  // Included with Angular CLI.\n"
  },
  {
    "path": "frontend/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>Bulwark</title>\n    <base href=\"/\" />\n    <script\n      src=\"https://code.jquery.com/jquery-3.3.1.min.js\"\n      integrity=\"sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=\"\n      crossorigin=\"anonymous\">\n    </script>\n    <link\n      rel=\"stylesheet\"\n      href=\"https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css\"\n      integrity=\"sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T\"\n      crossorigin=\"anonymous\"\n    />\n    <script\n      src=\"https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js\"\n      integrity=\"sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM\"\n      crossorigin=\"anonymous\"\n    ></script>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n  </head>\n  <body>\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "frontend/src/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage-istanbul-reporter'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    coverageIstanbulReporter: {\n      dir: require('path').join(__dirname, '../coverage/frontend'),\n      reports: ['html', 'lcovonly', 'text-summary'],\n      fixWebpackSourcePaths: true\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false,\n    restartOnFileChange: true\n  });\n};\n"
  },
  {
    "path": "frontend/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic().bootstrapModule(AppModule)\n  .catch(err => console.error(err));\n"
  },
  {
    "path": "frontend/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),\n * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/** IE10 and IE11 requires the following for NgClass support on SVG elements */\n// import 'classlist.js';  // Run `npm install --save classlist.js`.\n\n/**\n * Web Animations `@angular/platform-browser/animations`\n * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.\n * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).\n */\n// import 'web-animations-js';  // Run `npm install --save web-animations-js`.\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags.ts';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "frontend/src/styles.scss",
    "content": "\n@import \"./assets/theme/button.scss\";\n\n.btn-primary.p-button,\n.btn-primary {\n  background-color: #ae0a0a;\n  border-color: #ae0a0a;\n  color: #fff !important;\n}\n\n.btn-secondary.p-button,\n.btn-secondary {\n  color: #fff;\n  background-color: #6c757d !important;\n  border-color: #6c757d !important;\n}\n\n.btn-warning.p-button,\n.btn-warning {\n  color: #fff;\n  background-color: #ffc107 !important;\n  border-color: #ffc107 !important;\n}\n\n.btn-primary.p-button:hover,\n.btn-primary:hover {\n  color: #910a0a;\n  background-color: #910a0a !important;\n  border-color: #ae0a0a !important;\n}\n\n.btn-primary.p-button:active,\n.btn-primary {\n  background-color: #910a0a !important;\n}\n.btn-primary.p-button:focus,\n.btn-primary:focus {\n  box-shadow: 0 0 0 0.2rem #910a0a40 !important;\n}\n\n.btn-primary:disabled {\n  color: #fff;\n  background-color: #ae0a0a;\n  border-color: #ae0a0a;\n}\n\nh3 {\n  color: \"grey\";\n}\n\nbody {\n  padding-bottom: 120px;\n}\n\n.footer {\n  position: absolute;\n  left: 0;\n  bottom: 0;\n  width: 100%;\n  height: 114px;\n}\n\n.ng-valid[required],\n.ng-invalid:not(form) {\n  border-left: 5px solid #ae0a0a; /* red */\n}\n\n.card {\n  margin-top: 10px;\n}\n"
  },
  {
    "path": "frontend/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: any;\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting()\n);\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().map(context);\n"
  },
  {
    "path": "frontend/src/tsconfig.app.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"types\": []\n  },\n  \"files\": [\n    \"main.ts\",\n    \"polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "frontend/src/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/spec\",\n    \"types\": [\n      \"jasmine\",\n      \"node\"\n    ]\n  },\n  \"files\": [\n    \"test.ts\",\n    \"polyfills.ts\"\n  ],\n  \"include\": [\n    \"**/*.spec.ts\",\n    \"**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "frontend/src/tslint.json",
    "content": "{\n  \"extends\": \"../tslint.json\",\n  \"rules\": {\n    \"directive-selector\": [\n      true,\n      \"attribute\",\n      \"app\",\n      \"camelCase\"\n    ],\n    \"component-selector\": [\n      true,\n      \"element\",\n      \"app\",\n      \"kebab-case\"\n    ]\n  }\n}\n"
  },
  {
    "path": "frontend/tsconfig.json",
    "content": "{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"downlevelIteration\": true,\n    \"outDir\": \"./dist/out-tsc\",\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"module\": \"commonjs\",\n    \"moduleResolution\": \"node\",\n    \"types\": [\"node\"],\n    \"experimentalDecorators\": true,\n    \"importHelpers\": true,\n    \"target\": \"es6\",\n    \"typeRoots\": [\n      \"node_modules/@types\"\n    ],\n    \"lib\": [\n      \"es6\",\n      \"dom\",\n      \"es2017\"\n    ],\n    \"paths\": {\n      \"url\": [\"node_modules/@types/node\"]\n    }\n  }\n}"
  },
  {
    "path": "frontend/tslint.json",
    "content": "{\n  \"extends\": \"tslint:recommended\",\n  \"rulesDirectory\": [\n    \"codelyzer\"\n  ],\n  \"rules\": {\n    \"align\": {\n      \"options\": [\n        \"parameters\",\n        \"statements\"\n      ]\n    },\n    \"array-type\": false,\n    \"arrow-parens\": false,\n    \"arrow-return-shorthand\": true,\n    \"curly\": true,\n    \"deprecation\": {\n      \"severity\": \"warn\"\n    },\n    \"eofline\": true,\n    \"import-blacklist\": [\n      true,\n      \"rxjs/Rx\"\n    ],\n    \"import-spacing\": true,\n    \"indent\": {\n      \"options\": [\n        \"spaces\"\n      ]\n    },\n    \"interface-name\": false,\n    \"max-classes-per-file\": false,\n    \"max-line-length\": [\n      true,\n      140\n    ],\n    \"member-access\": false,\n    \"member-ordering\": [\n      true,\n      {\n        \"order\": [\n          \"static-field\",\n          \"instance-field\",\n          \"static-method\",\n          \"instance-method\"\n        ]\n      }\n    ],\n    \"no-consecutive-blank-lines\": false,\n    \"no-console\": [\n      true,\n      \"debug\",\n      \"info\",\n      \"time\",\n      \"timeEnd\",\n      \"trace\"\n    ],\n    \"no-empty\": false,\n    \"no-inferrable-types\": [\n      true,\n      \"ignore-params\"\n    ],\n    \"no-non-null-assertion\": true,\n    \"no-redundant-jsdoc\": true,\n    \"no-switch-case-fall-through\": true,\n    \"no-var-requires\": false,\n    \"object-literal-key-quotes\": [\n      true,\n      \"as-needed\"\n    ],\n    \"object-literal-sort-keys\": false,\n    \"ordered-imports\": false,\n    \"quotemark\": [\n      true,\n      \"single\"\n    ],\n    \"semicolon\": {\n      \"options\": [\n        \"always\"\n      ]\n    },\n    \"space-before-function-paren\": {\n      \"options\": {\n        \"anonymous\": \"never\",\n        \"asyncArrow\": \"always\",\n        \"constructor\": \"never\",\n        \"method\": \"never\",\n        \"named\": \"never\"\n      }\n    },\n    \"trailing-comma\": false,\n    \"no-output-on-prefix\": true,\n    \"no-inputs-metadata-property\": true,\n    \"no-outputs-metadata-property\": true,\n    \"no-host-metadata-property\": true,\n    \"no-input-rename\": true,\n    \"no-output-rename\": true,\n    \"typedef-whitespace\": {\n      \"options\": [\n        {\n          \"call-signature\": \"nospace\",\n          \"index-signature\": \"nospace\",\n          \"parameter\": \"nospace\",\n          \"property-declaration\": \"nospace\",\n          \"variable-declaration\": \"nospace\"\n        },\n        {\n          \"call-signature\": \"onespace\",\n          \"index-signature\": \"onespace\",\n          \"parameter\": \"onespace\",\n          \"property-declaration\": \"onespace\",\n          \"variable-declaration\": \"onespace\"\n        }\n      ]\n    },\n    \"use-lifecycle-interface\": true,\n    \"use-pipe-transform-interface\": true,\n    \"component-class-suffix\": true,\n    \"directive-class-suffix\": true\n  ,    \"variable-name\": {\n      \"options\": [\n        \"ban-keywords\",\n        \"check-format\",\n        \"allow-pascal-case\"\n      ]\n    },\n    \"whitespace\": {\n      \"options\": [\n        \"check-branch\",\n        \"check-decl\",\n        \"check-operator\",\n        \"check-separator\",\n        \"check-type\",\n        \"check-typecast\"\n      ]\n    }\n}\n}\n"
  },
  {
    "path": "jest.config.js",
    "content": "// For a detailed explanation regarding each configuration property, visit:\n// https://jestjs.io/docs/en/configuration.html\n\nmodule.exports = {\n  // Automatically clear mock calls and instances between every test\n  clearMocks: true,\n\n  // The directory where Jest should output its coverage files\n  coverageDirectory: 'coverage',\n  roots: ['<rootDir>/src'],\n  transform: {\n    '^.+\\\\.ts?$': 'ts-jest'\n  },\n  testRegex: '(/__tests__/.*|(\\\\.|/)(test|spec))\\\\.ts?$',\n  collectCoverage: true,\n  collectCoverageFrom: ['src/**/*.ts'],\n  moduleFileExtensions: ['ts', 'js', 'json', 'node'],\n  modulePathIgnorePatterns: ['<rootDir>/src/services', '<rootDir>/src/database', '<rootDir>/src/init'],\n  coveragePathIgnorePatterns: [\n    '<rootDir>/src/interfaces',\n    '<rootDir>/src/classes',\n    '<rootDir>/src/entity',\n    '<rootDir>/src/enums'\n  ],\n  setupFiles: ['<rootDir>/.jest/setEnvVars.js']\n};\n"
  },
  {
    "path": "ormconfig.js",
    "content": "module.exports = {\n  type: process.env.DB_TYPE,\n  host: process.env.DB_URL,\n  port: process.env.DB_PORT,\n  username: process.env.DB_USERNAME,\n  password: process.env.DB_PASSWORD,\n  database: process.env.DB_NAME,\n  entities: [__dirname + '/dist/entity/*.js'],\n  migrations: ['dist/database/migration/*.js'],\n  cli: {\n    migrationsDir: 'src/database/migration'\n  },\n  logging: true,\n  synchronize: false\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"bulwark\",\n  \"version\": \"8.0.0\",\n  \"description\": \"An organizational asset and vulnerability management tool\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test:node\": \"node_modules/.bin/jest --config=jest.config.js --collectCoverage\",\n    \"test:front\": \"cd frontend && npm run-script test\",\n    \"test\": \"npm run-script test:node && npm run-script test:front\",\n    \"start\": \"node dist/app.js\",\n    \"start:dev\": \"concurrently --kill-others \\\"npm run tsc:watch\\\" \\\"npm run ngServe\\\"\",\n    \"ngServe\": \"cd frontend && npm run-script start:dev\",\n    \"tsc:watch\": \"./node_modules/.bin/tsc-watch --onSuccess \\\"npm start\\\"\",\n    \"build:prod\": \"cd frontend && npm run-script build:prod\",\n    \"build:dev\": \"cd frontend && npm run-script build:dev\",\n    \"preinstall\": \"cd frontend && npm install\",\n    \"postinstall\": \"rimraf dist && tsc && npm run config && npm run build:prod\",\n    \"typeorm\": \"ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js\",\n    \"docker:check\": \"node dist/init/docker-run-exec.js\",\n    \"migration:run\": \"typeorm migration:run -d src/data-source.ts\",\n    \"migration:init\": \"typeorm migration:generate -d src/data-source.ts CreateDatabase && rimraf dist && tsc\",\n    \"migration:generate\": \"typeorm migration:generate -d src/data-source.ts Refactor && rimraf dist && tsc\",\n    \"migration:create\": \"typeorm migration:create -d src/data-source.ts newInit && rimraf dist && tsc\",\n    \"migration:revert\": \"typeorm migration:revert -d src/data-source.ts\",\n    \"tsc\": \"rimraf dist && tsc\",\n    \"lint\": \"tslint --project . && cd frontend && npm run-script lint\",\n    \"lint:fix\": \"tslint --fix --project . && cd frontend && npm run-script lint --fix=true\",\n    \"release\": \"standard-version\",\n    \"commit\": \"npx git-cz\",\n    \"config\": \"node dist/init/setEnv.js\"\n  },\n  \"keywords\": [\n    \"web security\",\n    \"web application security\",\n    \"webappsec\",\n    \"owasp\",\n    \"pentest\",\n    \"pentesting\",\n    \"security\",\n    \"vulnerable\",\n    \"vulnerability\"\n  ],\n  \"author\": \"Softrams https://www.softrams.com\",\n  \"contributors\": [\n    \"Bill Jones\",\n    \"Joshua Seidel\",\n    \"Darrell Richards\",\n    \"Alexandre Zanni\",\n    \"Brett Mayen\",\n    \"Boucham Amine\",\n    \"Mark Muth\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/softrams/bulwark.git\"\n  },\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@angular/cli\": \"^18.2.1\",\n    \"@babel/core\": \"^7.25.2\",\n    \"@babel/preset-env\": \"^7.25.4\",\n    \"@babel/preset-typescript\": \"^7.24.7\",\n    \"@commitlint/cli\": \"^19.4.0\",\n    \"@commitlint/config-conventional\": \"^19.2.2\",\n    \"@types/jest\": \"29.5.12\",\n    \"@types/node\": \"^22.5.0\",\n    \"babel-jest\": \"29.7.0\",\n    \"cz-conventional-changelog\": \"^3.3.0\",\n    \"highlight.js\": \">=11.10.0\",\n    \"husky\": \"^9.1.5\",\n    \"jest\": \"29.7.0\",\n    \"lint-staged\": \"^15.2.9\",\n    \"mock-express-request\": \"^0.2.2\",\n    \"mock-express-response\": \"^0.3.0\",\n    \"prettier\": \"3.3.3\",\n    \"rimraf\": \"^6.0.1\",\n    \"sqlite3\": \"^5.1.7\",\n    \"standard-version\": \"^9.5.0\",\n    \"ts-jest\": \"29.2.5\",\n    \"tsc-watch\": \"^6.2.0\",\n    \"tslint\": \"^6.1.3\",\n    \"tslint-config-prettier\": \"^1.18.0\",\n    \"typescript\": \"^5.5.4\"\n  },\n  \"dependencies\": {\n    \"@types/body-parser\": \"^1.19.5\",\n    \"@types/express\": \"^4.17.21\",\n    \"bcrypt\": \"^5.1.1\",\n    \"body-parser\": \"^1.20.2\",\n    \"class-validator\": \"^0.14.1\",\n    \"concurrently\": \"^8.2.2\",\n    \"cors\": \"^2.8.5\",\n    \"dotenv\": \"^16.4.5\",\n    \"express\": \"^4.19.2\",\n    \"helmet\": \"^7.1.0\",\n    \"jira-client\": \"^8.2.2\",\n    \"jira2md\": \"^3.0.1\",\n    \"jsonwebtoken\": \"^9.0.2\",\n    \"mime-types\": \"^2.1.35\",\n    \"multer\": \"^1.4.5-lts.1\",\n    \"mysql\": \"^2.18.1\",\n    \"node-fetch\": \"^2.6.7\",\n    \"nodemailer\": \"^6.9.14\",\n    \"password-validator\": \"^5.3.0\",\n    \"prod\": \"^1.0.1\",\n    \"puppeteer\": \"^23.2.0\",\n    \"reflect-metadata\": \"^0.2.2\",\n    \"ts-node\": \"^10.9.2\",\n    \"tsconfig-paths\": \"^4.2.0\",\n    \"typeorm\": \"^0.3.20\",\n    \"uuid\": \"^10.0.0\"\n  },\n  \"husky\": {\n    \"hooks\": {\n      \"commit-msg\": \"commitlint -E HUSKY_GIT_PARAMS\",\n      \"pre-commit\": \"lint-staged\"\n    }\n  },\n  \"config\": {\n    \"commitizen\": {\n      \"path\": \"./node_modules/cz-conventional-changelog\"\n    }\n  },\n  \"lint-staged\": {\n    \"*\": \"prettier --write\"\n  }\n}\n"
  },
  {
    "path": "src/app.ts",
    "content": "import * as express from 'express';\nimport * as path from 'path';\nimport * as fs from 'fs';\nimport * as dotenv from 'dotenv';\nimport * as bodyParser from 'body-parser';\nimport { AppDataSource } from './data-source';\nconst authController = require('./routes/authentication.controller');\nimport * as userController from './routes/user.controller';\nconst fileUploadController = require('./routes/file-upload.controller');\nimport * as orgController from './routes/organization.controller';\nimport * as assetController from './routes/asset.controller';\nimport * as assessmentController from './routes/assessment.controller';\nimport * as vulnController from './routes/vulnerability.controller';\nimport * as jwtMiddleware from './middleware/jwt.middleware';\nimport { generateReport } from './utilities/puppeteer.utility';\nimport * as configController from './routes/config.controller';\nimport * as teamController from './routes/team.controller';\nimport * as apiKeyController from './routes/api-key.controller';\nconst helmet = require('helmet');\nconst cors = require('cors');\n\n// Environment variables are loaded from .env file in data-source.ts\n\nconst app = express();\napp.use(cors());\napp.use(\n  express.static(path.join(__dirname, '../frontend/dist/frontend'), {\n    etag: false,\n  })\n);\napp.use(helmet());\napp.use(\n  helmet.contentSecurityPolicy({\n    directives: {\n      defaultSrc: [\"'self' blob:\", 'stackpath.bootstrapcdn.com'],\n      scriptSrc: [\n        \"'self'\",\n        'code.jquery.com',\n        'stackpath.bootstrapcdn.com',\n        '${serverIpAddress}',\n      ],\n      styleSrc: [\"'self'\", 'stackpath.bootstrapcdn.com', \"'unsafe-inline'\"],\n    },\n  })\n);\napp.use(bodyParser.json({ limit: '2mb' }));\napp.use(bodyParser.urlencoded({ extended: true }));\nconst serverPort = process.env.PORT || 5000;\nconst serverIpAddress = process.env.SERVER_ADDRESS || '127.0.0.1';\napp.set('port', serverPort);\napp.set('serverIpAddress', serverIpAddress);\n// tslint:disable-next-line: no-console\n\n// Initialize TypeORM connection\nAppDataSource.initialize()\n  .then(async () => {\n    console.info(`Database connection successful`);\n    await configController.initialInsert();\n\n  // Protected Global Routes\n  app.post(\n    '/api/user/email',\n    jwtMiddleware.checkToken,\n    userController.updateUserEmail\n  );\n  app.post(\n    '/api/user/email/revoke',\n    jwtMiddleware.checkToken,\n    userController.revokeEmailRequest\n  );\n  app.patch('/api/user', jwtMiddleware.checkToken, userController.patch);\n  app.post('/api/user', jwtMiddleware.checkToken, userController.create);\n  app.get('/api/user', jwtMiddleware.checkToken, userController.getUser);\n  app.get('/api/user', jwtMiddleware.checkToken, userController.getUser);\n  app.get(\n    '/api/users/all',\n    [jwtMiddleware.checkToken, jwtMiddleware.isAdmin],\n    userController.getAllUsers\n  );\n  app.get('/api/users', jwtMiddleware.checkToken, userController.getUsers);\n  app.get(\n    '/api/testers/:orgId',\n    jwtMiddleware.checkToken,\n    userController.getTesters\n  );\n  app.patch(\n    '/api/user/password',\n    jwtMiddleware.checkToken,\n    userController.updateUserPassword\n  );\n  app.post(\n    '/api/refresh',\n    jwtMiddleware.checkRefreshToken,\n    authController.refreshSession\n  );\n  app.get(\n    '/api/file/:id',\n    jwtMiddleware.checkToken,\n    fileUploadController.getFileById\n  );\n  app.get(\n    '/api/organization',\n    jwtMiddleware.checkToken,\n    orgController.getActiveOrgs\n  );\n  app.get(\n    '/api/organization/archive',\n    jwtMiddleware.checkToken,\n    orgController.getArchivedOrgs\n  );\n  app.get(\n    '/api/organization/:id',\n    jwtMiddleware.checkToken,\n    orgController.getOrgById\n  );\n  app.get(\n    '/api/organization/asset/:id',\n    jwtMiddleware.checkToken,\n    assetController.getOrgAssets\n  );\n  app.get(\n    '/api/organization/:id/asset/archive',\n    jwtMiddleware.checkToken,\n    assetController.getArchivedOrgAssets\n  );\n  app.get(\n    '/api/organization/:id/asset/:assetId',\n    jwtMiddleware.checkToken,\n    assetController.getAssetById\n  );\n  app.get(\n    '/api/asset/:assetId/open/vulnerabilities',\n    jwtMiddleware.checkToken,\n    assetController.getOpenVulnsByAsset\n  );\n  app.get(\n    '/api/assessment/:id',\n    jwtMiddleware.checkToken,\n    assessmentController.getAssessmentsByAssetId\n  );\n  app.get(\n    '/api/assessment/:id/vulnerability',\n    jwtMiddleware.checkToken,\n    assessmentController.getAssessmentVulns\n  );\n  app.post(\n    '/api/assessment',\n    jwtMiddleware.checkToken,\n    assessmentController.createAssessment\n  );\n  app.delete(\n    '/api/assessment/:assessmentId',\n    jwtMiddleware.checkToken,\n    assessmentController.deleteAssessmentById\n  );\n  app.get(\n    '/api/asset/:assetId/assessment/:assessmentId',\n    jwtMiddleware.checkToken,\n    assessmentController.getAssessmentById\n  );\n  app.patch(\n    '/api/asset/:assetId/assessment/:assessmentId',\n    jwtMiddleware.checkToken,\n    assessmentController.updateAssessmentById\n  );\n  app.get(\n    '/api/assessment/:assessmentId/report',\n    jwtMiddleware.checkToken,\n    assessmentController.queryReportDataByAssessment\n  );\n  app.post('/api/report/generate', jwtMiddleware.checkToken, generateReport);\n  app.get(\n    '/api/vulnerability/:vulnId',\n    jwtMiddleware.checkToken,\n    vulnController.getVulnById\n  );\n  app.delete(\n    '/api/vulnerability/:vulnId',\n    jwtMiddleware.checkToken,\n    vulnController.deleteVulnById\n  );\n  app.patch(\n    '/api/vulnerability/:vulnId',\n    jwtMiddleware.checkToken,\n    vulnController.patchVulnById\n  );\n  app.post(\n    '/api/vulnerability',\n    jwtMiddleware.checkToken,\n    vulnController.createVuln\n  );\n  app.get(\n    '/api/vulnerability/jira/:vulnId',\n    jwtMiddleware.checkToken,\n    vulnController.exportToJira\n  );\n  app.get(\n    '/api/user/teams',\n    jwtMiddleware.checkToken,\n    teamController.getMyTeams\n  );\n  app.post(\n    '/api/user/key',\n    jwtMiddleware.checkToken,\n    apiKeyController.generateApiKey\n  );\n  app.get(\n    '/api/user/key',\n    jwtMiddleware.checkToken,\n    apiKeyController.getUserApiKeyInfo\n  );\n  app.patch(\n    '/api/user/key/:id',\n    jwtMiddleware.checkToken,\n    apiKeyController.deleteApiKeyAsUser\n  );\n\n  // Public Routes\n  app.post('/api/user/register', userController.register);\n  app.get('/api/user/verify/:uuid', userController.verify);\n  app.patch('/api/forgot-password', authController.forgotPassword);\n  app.patch('/api/password-reset', authController.resetPassword);\n  app.post('/api/user/email/validate', userController.validateEmailRequest);\n  app.post('/api/login', authController.login);\n\n  // Tester Routes\n  app.post(\n    '/api/upload',\n    jwtMiddleware.checkToken,\n    fileUploadController.uploadFile\n  );\n\n  // Admin Routes\n  app.patch(\n    '/api/organization/:id/asset/:assetId',\n    [jwtMiddleware.checkToken, jwtMiddleware.isAdmin],\n    assetController.updateAssetById\n  );\n  app.post(\n    '/api/organization/:id/asset',\n    [jwtMiddleware.checkToken, jwtMiddleware.isAdmin],\n    assetController.createAsset\n  );\n  app.patch(\n    '/api/asset/archive/:assetId',\n    [jwtMiddleware.checkToken, jwtMiddleware.isAdmin],\n    assetController.archiveAssetById\n  );\n  app.patch(\n    '/api/asset/activate/:assetId',\n    [jwtMiddleware.checkToken, jwtMiddleware.isAdmin],\n    assetController.activateAssetById\n  );\n  app.post(\n    '/api/config',\n    [jwtMiddleware.checkToken, jwtMiddleware.isAdmin],\n    configController.saveConfig\n  );\n  app.get(\n    '/api/config',\n    [jwtMiddleware.checkToken, jwtMiddleware.isAdmin],\n    configController.getConfig\n  );\n  app.post(\n    '/api/user/invite',\n    [jwtMiddleware.checkToken, jwtMiddleware.isAdmin],\n    userController.invite\n  );\n  // Activate user\n  app.patch(\n    '/api/user/activate/:id',\n    [jwtMiddleware.checkToken, jwtMiddleware.isAdmin],\n    userController.activateUser\n  );\n  // Deactivate user\n  app.patch(\n    '/api/user/deactivate/:id',\n    [jwtMiddleware.checkToken, jwtMiddleware.isAdmin],\n    userController.deactivateUser\n  );\n  app.patch(\n    '/api/organization/:id/archive',\n    [jwtMiddleware.checkToken, jwtMiddleware.isAdmin],\n    orgController.archiveOrgById\n  );\n  app.patch(\n    '/api/organization/:id/activate',\n    [jwtMiddleware.checkToken, jwtMiddleware.isAdmin],\n    orgController.activateOrgById\n  );\n  app.patch(\n    '/api/organization/:id',\n    [jwtMiddleware.checkToken, jwtMiddleware.isAdmin],\n    orgController.updateOrgById\n  );\n  app.post(\n    '/api/organization',\n    [jwtMiddleware.checkToken, jwtMiddleware.isAdmin],\n    orgController.createOrg\n  );\n  app.delete(\n    '/api/asset/jira/:assetId',\n    [jwtMiddleware.checkToken, jwtMiddleware.isAdmin],\n    assetController.purgeJiraInfo\n  );\n  app.get(\n    '/api/team/:teamId',\n    [jwtMiddleware.checkToken, jwtMiddleware.isAdmin],\n    teamController.getTeamById\n  );\n  app.get(\n    '/api/team',\n    [jwtMiddleware.checkToken, jwtMiddleware.isAdmin],\n    teamController.getAllTeams\n  );\n  app.post(\n    '/api/team',\n    [jwtMiddleware.checkToken, jwtMiddleware.isAdmin],\n    teamController.createTeam\n  );\n  app.patch(\n    '/api/team',\n    [jwtMiddleware.checkToken, jwtMiddleware.isAdmin],\n    teamController.updateTeamInfo\n  );\n  app.post(\n    '/api/team/member/add',\n    [jwtMiddleware.checkToken, jwtMiddleware.isAdmin],\n    teamController.addTeamMember\n  );\n  app.post(\n    '/api/team/member/remove',\n    [jwtMiddleware.checkToken, jwtMiddleware.isAdmin],\n    teamController.removeTeamMember\n  );\n  app.delete(\n    '/api/team/:teamId',\n    [jwtMiddleware.checkToken, jwtMiddleware.isAdmin],\n    teamController.deleteTeam\n  );\n  app.post(\n    '/api/team/asset/add',\n    [jwtMiddleware.checkToken, jwtMiddleware.isAdmin],\n    teamController.addTeamAsset\n  );\n  app.post(\n    '/api/team/asset/remove',\n    [jwtMiddleware.checkToken, jwtMiddleware.isAdmin],\n    teamController.removeTeamAsset\n  );\n  app.get(\n    '/api/keys',\n    [jwtMiddleware.checkToken, jwtMiddleware.isAdmin],\n    apiKeyController.getAdminApiKeyInfo\n  );\n  app.patch(\n    '/api/key/:id',\n    [jwtMiddleware.checkToken, jwtMiddleware.isAdmin],\n    apiKeyController.deleteApiKeyAsAdmin\n  );\n    \n  // Start the server\n  app.listen(serverPort, () =>\n    console.info(`Server running on ${serverIpAddress}:${serverPort}`)\n  );\n})\n.catch((error) => {\n  console.error('Error during Data Source initialization:', error);\n});\n\nexport { app };\n"
  },
  {
    "path": "src/classes/Report.ts",
    "content": "import { Asset } from '../entity/Asset';\nimport { Organization } from '../entity/Organization';\nimport { Assessment } from '../entity/Assessment';\nimport { Vulnerability } from '../entity/Vulnerability';\n\nexport class Report {\n  public org: Organization;\n  public asset: Asset;\n  public assessment: Assessment;\n  public vulns: Vulnerability[];\n  public companyName: string;\n}\n"
  },
  {
    "path": "src/data-source.ts",
    "content": "import { DataSource } from 'typeorm';\nimport { User } from './entity/User';\nimport { Organization } from './entity/Organization';\nimport { Asset } from './entity/Asset';\nimport { Assessment } from './entity/Assessment';\nimport { Vulnerability } from './entity/Vulnerability';\nimport { File } from './entity/File';\nimport { ProblemLocation } from './entity/ProblemLocation';\nimport { Resource } from './entity/Resource';\nimport { Jira } from './entity/Jira';\nimport { ReportAudit } from './entity/ReportAudit';\nimport { ApiKey } from './entity/ApiKey';\nimport { Config } from './entity/Config';\nimport { Team } from './entity/Team';\nimport * as path from 'path';\nimport * as fs from 'fs';\nimport * as dotenv from 'dotenv';\n\n// Load environment variables\nif (fs.existsSync(path.join(__dirname, '../.env'))) {\n  const envPath = fs.readFileSync(path.join(__dirname, '../.env'));\n  console.log('A .env file has been found and will now be parsed.');\n  const envConfig = dotenv.parse(envPath);\n  if (envConfig) {\n    for (const key in envConfig) {\n      if (envConfig.hasOwnProperty(key)) {\n        process.env[key] = envConfig[key];\n      }\n    }\n    console.log('The provided .env file has been parsed successfully.');\n  }\n}\n\nexport const AppDataSource = new DataSource({\n  type: process.env.DB_TYPE as any || 'mysql',\n  host: process.env.DB_URL || 'localhost',\n  port: parseInt(process.env.DB_PORT || '3306'),\n  username: process.env.DB_USERNAME,\n  password: process.env.DB_PASSWORD,\n  database: process.env.DB_NAME,\n  synchronize: false,\n  logging: process.env.NODE_ENV !== 'production',\n  entities: [\n    User,\n    Organization,\n    Asset,\n    Assessment,\n    Vulnerability,\n    File,\n    ProblemLocation,\n    Resource,\n    Jira,\n    ReportAudit,\n    ApiKey,\n    Config,\n    Team\n  ],\n  migrations: [path.join(__dirname, './migration/*.js')],\n  subscribers: []\n});"
  },
  {
    "path": "src/entity/ApiKey.ts",
    "content": "import {\n  Entity,\n  Column,\n  PrimaryGeneratedColumn,\n  ManyToOne,\n  OneToMany,\n  OneToOne,\n  ManyToMany,\n} from 'typeorm';\nimport { IsDate, IsIn } from 'class-validator';\nimport { User } from './User';\n\n@Entity()\nexport class ApiKey {\n  @PrimaryGeneratedColumn()\n  id: number;\n  @Column()\n  key: string;\n  @Column()\n  secretKey: string;\n  @Column()\n  active: boolean;\n  @Column()\n  @IsDate()\n  lastUpdatedBy: number;\n  @Column()\n  @IsDate()\n  createdDate: Date;\n  @Column()\n  @IsDate()\n  lastUpdatedDate: Date;\n  @ManyToOne((type) => User, (user) => user.apiKey)\n  user: User;\n}\n"
  },
  {
    "path": "src/entity/Assessment.ts",
    "content": "import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, OneToMany, ManyToMany, JoinTable } from 'typeorm';\nimport { Asset } from './Asset';\nimport { Vulnerability } from './Vulnerability';\nimport { IsUrl, IsDate, MaxLength, IsString } from 'class-validator';\nimport { User } from './User';\n\n@Entity()\nexport class Assessment {\n  @PrimaryGeneratedColumn()\n  id: number;\n  @Column()\n  name: string;\n  @Column({ length: 4000 })\n  executiveSummary: string;\n  @Column()\n  jiraId: string;\n  @Column()\n  @IsUrl()\n  testUrl: string;\n  @Column()\n  @IsUrl()\n  prodUrl: string;\n  @Column()\n  scope: string;\n  @Column()\n  @IsString()\n  tag: string;\n  @Column()\n  @IsDate()\n  startDate: Date;\n  @Column()\n  @IsDate()\n  endDate: Date;\n  @ManyToOne((type) => Asset, (asset) => asset.assessment, { onDelete: 'CASCADE' })\n  asset: Asset;\n  @OneToMany((type) => Vulnerability, (vuln) => vuln.assessment)\n  vulnerabilities: Vulnerability[];\n  @ManyToMany((type) => User)\n  @JoinTable()\n  testers: User[];\n}\n"
  },
  {
    "path": "src/entity/Asset.ts",
    "content": "import {\n  Entity,\n  Column,\n  PrimaryGeneratedColumn,\n  ManyToOne,\n  OneToMany,\n  OneToOne,\n  ManyToMany,\n} from 'typeorm';\nimport { Organization } from './Organization';\nimport { Assessment } from './Assessment';\nimport { IsIn } from 'class-validator';\nimport { Jira } from './Jira';\nimport { Team } from './Team';\n\n@Entity()\nexport class Asset {\n  @PrimaryGeneratedColumn()\n  id: number;\n  @Column()\n  name: string;\n  @Column()\n  @IsIn(['A', 'AH'])\n  status: string;\n  @ManyToOne((type) => Organization, (organization) => organization.asset)\n  organization: Organization;\n  @OneToMany((type) => Assessment, (assessment) => assessment.asset)\n  assessment: Assessment[];\n  @OneToOne((type) => Jira, (jira) => jira.asset)\n  jira: Jira;\n  @ManyToMany(() => Team, (team) => team.assets)\n  teams: Team[];\n}\n"
  },
  {
    "path": "src/entity/Config.ts",
    "content": "import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';\nimport { IsEmail } from 'class-validator';\n\n@Entity()\nexport class Config {\n  @PrimaryGeneratedColumn()\n  id: number;\n  @Column({\n    nullable: true\n  })\n  @IsEmail()\n  fromEmail: string;\n  @Column({\n    nullable: true\n  })\n  fromEmailPassword: string;\n  @Column({\n    nullable: true\n  })\n  companyName: string;\n}\n"
  },
  {
    "path": "src/entity/File.ts",
    "content": "import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from 'typeorm';\nimport { Vulnerability } from './Vulnerability';\nimport { DbAwareColumn } from '../utilities/column-mapper.utility';\n\n@Entity()\nexport class File {\n  @PrimaryGeneratedColumn()\n  id: number;\n  @Column()\n  fieldName: string;\n  @Column()\n  originalname: string;\n  @Column()\n  encoding: string;\n  @Column()\n  mimetype: string;\n  @DbAwareColumn({ name: 'buffer', type: 'mediumblob' })\n  buffer: Buffer;\n  @Column()\n  size: number;\n  @ManyToOne((type) => Vulnerability, (vuln) => vuln.screenshots, { onDelete: 'CASCADE' })\n  vulnerability: Vulnerability;\n}\n"
  },
  {
    "path": "src/entity/Jira.ts",
    "content": "import { Entity, Column, PrimaryGeneratedColumn, OneToOne, JoinColumn } from 'typeorm';\nimport { IsUrl, IsNotEmpty } from 'class-validator';\nimport { Asset } from './Asset';\n\n@Entity()\nexport class Jira {\n  @PrimaryGeneratedColumn()\n  id: number;\n  @Column()\n  @IsUrl()\n  @IsNotEmpty()\n  host: string;\n  @Column()\n  @IsNotEmpty()\n  apiKey: string;\n  @Column()\n  @IsNotEmpty()\n  username: string;\n  @OneToOne((type) => Asset, (asset) => asset.jira, { onDelete: 'CASCADE' })\n  @JoinColumn()\n  asset: Asset;\n}\n"
  },
  {
    "path": "src/entity/Organization.ts",
    "content": "import { Entity, Column, PrimaryGeneratedColumn, OneToMany } from 'typeorm';\nimport { Asset } from './Asset';\nimport { IsIn } from 'class-validator';\nimport { Team } from './Team';\n\n@Entity()\nexport class Organization {\n  @PrimaryGeneratedColumn()\n  id: number;\n  @Column()\n  name: string;\n  @Column()\n  @IsIn(['A', 'AH'])\n  status: string;\n  @OneToMany((type) => Asset, (asset) => asset.organization)\n  asset: Asset[];\n  @OneToMany((type) => Team, (team) => team.organization)\n  teams: Team[];\n}\n"
  },
  {
    "path": "src/entity/ProblemLocation.ts",
    "content": "import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from 'typeorm';\nimport { Vulnerability } from './Vulnerability';\n\n@Entity()\nexport class ProblemLocation {\n  @PrimaryGeneratedColumn()\n  id: number;\n  @Column()\n  location: string;\n  @Column()\n  target: string;\n  @ManyToOne((type) => Vulnerability, (vuln) => vuln.problemLocations, { onDelete: 'CASCADE' })\n  vulnerability: Vulnerability;\n}\n"
  },
  {
    "path": "src/entity/ReportAudit.ts",
    "content": "import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from 'typeorm';\nimport { IsDate } from 'class-validator';\n\n@Entity()\nexport class ReportAudit {\n  @PrimaryGeneratedColumn()\n  id: number;\n  @Column()\n  assessmentId: number;\n  @Column()\n  generatedBy: number;\n  @Column()\n  @IsDate()\n  generatedDate: Date;\n}\n"
  },
  {
    "path": "src/entity/Resource.ts",
    "content": "import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from 'typeorm';\nimport { Vulnerability } from './Vulnerability';\n\n@Entity()\nexport class Resource {\n  @PrimaryGeneratedColumn()\n  id: number;\n  @Column()\n  description: string;\n  @Column()\n  url: string;\n  @ManyToOne((type) => Vulnerability, (vuln) => vuln.resources, { onDelete: 'CASCADE' })\n  vulnerability: Vulnerability;\n}\n"
  },
  {
    "path": "src/entity/Team.ts",
    "content": "import {\n  Entity,\n  Column,\n  PrimaryGeneratedColumn,\n  ManyToMany,\n  JoinTable,\n  ManyToOne,\n} from 'typeorm';\nimport { IsDate, IsIn } from 'class-validator';\nimport { User } from './User';\nimport { Organization } from './Organization';\nimport { Asset } from './Asset';\n\n@Entity()\nexport class Team {\n  @PrimaryGeneratedColumn({})\n  id: number;\n  @Column()\n  name: string;\n  @ManyToOne((type) => Organization, (organization) => organization.teams)\n  organization: Organization;\n  @Column()\n  @IsDate()\n  createdDate: Date;\n  @Column({ nullable: true })\n  createdBy: number;\n  @Column()\n  @IsDate()\n  lastUpdatedDate: Date;\n  @Column({ nullable: true })\n  lastUpdatedBy: number;\n  @Column()\n  @IsIn(['Admin', 'Read-Only', 'Tester'])\n  role: string;\n  @ManyToMany(() => User, (user) => user.teams)\n  @JoinTable()\n  users: User[];\n  @ManyToMany(() => Asset, (asset) => asset.teams)\n  @JoinTable()\n  assets: Asset[];\n}\n"
  },
  {
    "path": "src/entity/User.ts",
    "content": "import {\n  Entity,\n  Column,\n  PrimaryGeneratedColumn,\n  ManyToMany,\n  OneToMany,\n} from 'typeorm';\nimport { IsEmail, IsUUID, IsOptional } from 'class-validator';\nimport { dynamicNullable } from '../utilities/column-mapper.utility';\nimport { Team } from '../entity/Team';\nimport { ApiKey } from './ApiKey';\n\n@Entity()\nexport class User {\n  @PrimaryGeneratedColumn({})\n  id: number;\n  @Column({\n    unique: true,\n  })\n  @IsEmail()\n  email: string;\n  @Column({\n    nullable: true,\n  })\n  @IsEmail()\n  newEmail: string;\n  @dynamicNullable()\n  password: string;\n  @Column()\n  active: boolean;\n  @Column({\n    nullable: true,\n  })\n  @IsOptional()\n  @IsUUID()\n  uuid: string;\n  @dynamicNullable()\n  firstName: string;\n  @dynamicNullable()\n  lastName: string;\n  @dynamicNullable()\n  title: string;\n  @ManyToMany(() => Team, (team) => team.users)\n  teams: Team[];\n  @OneToMany((type) => ApiKey, (apiKey) => apiKey.user)\n  apiKey: ApiKey[];\n}\n"
  },
  {
    "path": "src/entity/VulnDictionary.ts",
    "content": "import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';\n\n@Entity()\nexport class VulnDictionary {\n  @PrimaryGeneratedColumn()\n  id: number;\n  @Column()\n  name: string;\n  @Column()\n  description: string;\n}\n"
  },
  {
    "path": "src/entity/Vulnerability.ts",
    "content": "import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, OneToMany } from 'typeorm';\nimport { Assessment } from './Assessment';\nimport { IsUrl, IsIn, MaxLength, IsAlpha, IsDecimal } from 'class-validator';\nimport { File } from './File';\nimport { ProblemLocation } from './ProblemLocation';\nimport { Resource } from './Resource';\n\n@Entity()\nexport class Vulnerability {\n  @PrimaryGeneratedColumn()\n  id: number;\n  @Column()\n  jiraId: string;\n  @Column()\n  @IsIn(['Low', 'Medium', 'High'])\n  impact: string;\n  @Column()\n  @IsIn(['Low', 'Medium', 'High'])\n  likelihood: string;\n  @Column()\n  @IsIn(['Low', 'Medium', 'High', 'Critical', 'Informational'])\n  risk: string;\n  @Column()\n  @IsIn(['Yes', 'No'])\n  systemic: string;\n  @Column({ type: 'decimal', scale: 1, precision: 10 })\n  @IsDecimal()\n  cvssScore: number;\n  @Column()\n  @IsUrl()\n  cvssUrl: string;\n  @Column()\n  @IsIn(['Open', 'Resolved', 'On Hold'])\n  status: string;\n  @Column({ length: 4000 })\n  @MaxLength(4000)\n  description: string;\n  @Column({ length: 4000 })\n  @MaxLength(4000)\n  detailedInfo: string;\n  @Column({ length: 4000 })\n  @MaxLength(4000)\n  remediation: string;\n  @Column()\n  name: string;\n  @ManyToOne((type) => Assessment, (assessment) => assessment.vulnerabilities, { onDelete: 'CASCADE' })\n  assessment: Assessment;\n  @OneToMany((type) => File, (file) => file.vulnerability)\n  screenshots: File[];\n  @OneToMany((type) => ProblemLocation, (problemLocation) => problemLocation.vulnerability)\n  problemLocations: ProblemLocation[];\n  @OneToMany((type) => Resource, (resource) => resource.vulnerability)\n  resources: Resource[];\n}\n"
  },
  {
    "path": "src/enums/message-enum.ts",
    "content": "export const passwordRequirement =\n  'Password Requirements: Must be at least 12 characters' +\n  ', at least one uppercase characters, at least one lowercase characters,' +\n  'at least one digit, and at least one symbol.';\n"
  },
  {
    "path": "src/enums/roles-enum.ts",
    "content": "export const ROLE = {\n  ADMIN: 'Admin',\n  READONLY: 'Read-Only',\n  TESTER: 'Tester'\n};\n"
  },
  {
    "path": "src/enums/status-enum.ts",
    "content": "export const status = {\n  active: 'A',\n  archived: 'AH'\n};\n"
  },
  {
    "path": "src/init/docker-run-exec.ts",
    "content": "import { Connection, createConnection, getManager } from 'typeorm';\nconst exec = require('child_process').exec;\nlet connection: Connection;\nexport const initDbCheck = async () => {\n  connection = await createConnection();\n  const manager = getManager();\n  let migrations;\n  try {\n    migrations = await manager.query('SELECT * FROM migrations');\n  } catch (err) {\n    if (err) {\n      console.error(err);\n    }\n    // tslint:disable-next-line: no-console\n    console.info(\n      'Initial migration not found. Running generating initial migration.'\n    );\n    // If the migrations table does not exist, run the migration:init script\n    // to create schema\n    exec('npm run migration:init', (err, stdout, stderr) => {\n      if (err) {\n        console.error(err);\n        terminate();\n      }\n      // tslint:disable-next-line: no-console\n      console.log(stdout);\n      exec('npm run migration:run', (runErr) => {\n        if (runErr) {\n          console.error(err);\n          terminate();\n        }\n        // tslint:disable-next-line: no-console\n        console.info(\n          'Initial migration has been generated successfully.  Running initial migration.'\n        );\n        // tslint:disable-next-line: no-console\n        console.log(stdout);\n        terminate();\n      });\n    });\n  }\n  if (migrations) {\n    migrations = migrations.map((x) => x.name);\n    // If the CreateDatabase migration exists skip migration:generate\n    // else run the migration:generate script\n    if (migrations[0].includes('CreateDatabase')) {\n      // tslint:disable-next-line: no-console\n      console.info(\n        `Initial migration ${migrations[0]} exists. Skipping migration:init script`\n      );\n    }\n    // If the init migration was already created check to see\n    // if there has been an update to the database\n    // If there was a DB update, run the migration\n    exec('npm run migration:generate', (err, stdout) => {\n      if (err) {\n        // tslint:disable-next-line: no-console\n        console.info('No database updates detected');\n        terminate();\n      }\n      console.log(stdout);\n      // tslint:disable-next-line: no-console\n      exec('npm run migration:run', (runErr, runStdout) => {\n        // tslint:disable-next-line: no-console\n        console.info('Database updates detected. Running generated migrations');\n        if (runErr) {\n          console.error(runErr);\n          terminate();\n        }\n        // tslint:disable-next-line: no-console\n        console.log('Database migration success!');\n        console.log(runStdout);\n        terminate();\n      });\n    });\n  }\n};\n\nconst terminate = () => {\n  connection.close();\n  process.exit();\n};\n\ninitDbCheck();\n"
  },
  {
    "path": "src/init/setEnv.ts",
    "content": "const dotenv = require('dotenv');\nconst fs = require('fs');\nimport * as path from 'path';\nconst { writeFile } = require('fs');\n\n// Grabs .env variables if running locally\nif (fs.existsSync(path.join(__dirname, '../../.env'))) {\n  const envPath = fs.readFileSync(path.join(__dirname, '../../.env'));\n  // tslint:disable-next-line: no-console\n  console.log('A .env file has been found found and will now be parsed.');\n  // https://github.com/motdotla/dotenv#what-happens-to-environment-variables-that-were-already-set\n  const envConfig = dotenv.parse(envPath);\n  if (envConfig) {\n    for (const key in envConfig) {\n      if (envConfig.hasOwnProperty(key)) {\n        process.env[key] = envConfig[key];\n      }\n    }\n    // tslint:disable-next-line: no-console\n    console.log('The provided .env file has been parsed successfully.');\n  }\n}\n// docker-compose will have access to the .env file\nif (process.env.SERVER_ADDRESS && process.env.PORT) {\n  let targetPath: string;\n  let isProduction: boolean;\n  const apiUrl = `${process.env.SERVER_ADDRESS}:${process.env.PORT}/api`;\n\n  if (process.env.NODE_ENV === 'production') {\n    isProduction = true;\n    targetPath = path.join(\n      __dirname,\n      '../../frontend/src/environments/environment.prod.ts'\n    );\n  } else {\n    isProduction = false;\n    targetPath = path.join(\n      __dirname,\n      '../../frontend/src/environments/environment.dev.ts'\n    );\n  }\n\n  // we have access to our environment variables\n  // in the process.env object thanks to dotenv\n  const environmentFileContent = `\n          export const environment = {\n             production: ${isProduction},\n             apiUrl: \"${apiUrl}\",\n          };\n          `;\n\n  // write the content to the respective file\n  writeFile(targetPath, environmentFileContent, (err) => {\n    if (err) {\n      console.error(err);\n    }\n    // tslint:disable-next-line: no-console\n    console.info(\n      `Angular environment has been updated to ${environmentFileContent} located at ${targetPath}`\n    );\n    return;\n  });\n} else {\n  // tslint:disable-next-line: no-console\n  console.info(\n    'Environment variables do not exist.  Skipping `npm run config` step!'\n  );\n}\n"
  },
  {
    "path": "src/interfaces/jira/jira-init.interface.ts",
    "content": "export interface JiraInit {\n  apiKey: string;\n  host: string;\n  username: string;\n}\n"
  },
  {
    "path": "src/interfaces/jira/jira-issue-link.interface.ts",
    "content": "export interface IssueLink {\n  id?: number;\n  outwardIssue?: {\n    key: string;\n  };\n  inwardIssue?: {\n    key: string;\n  };\n  type?: {\n    name: string;\n  };\n}\n"
  },
  {
    "path": "src/interfaces/jira/jira-issue-priority.interface.ts",
    "content": "export interface JiraPriority {\n  id?: number;\n  key?: string;\n  name?: string;\n  colorName?: string;\n}\n"
  },
  {
    "path": "src/interfaces/jira/jira-issue-status.interface.ts",
    "content": "export interface IssueStatus {\n  id: number;\n  name: string;\n  description: string;\n  category: any;\n}\n"
  },
  {
    "path": "src/interfaces/jira/jira-issue-type.interface.ts",
    "content": "export interface IssueType {\n  id?: string;\n  name?: string;\n  description?: string;\n  properties?: any;\n}\n"
  },
  {
    "path": "src/interfaces/jira/jira-issue.interface.ts",
    "content": "import { JiraProject } from './jira-project.interface';\nimport { JiraPriority } from './jira-issue-priority.interface';\nimport { IssueType } from './jira-issue-type.interface';\nimport { IssueStatus } from './jira-issue-status.interface';\nimport { IssueLink } from './jira-issue-link.interface';\n// Add interfaces as you need them\nexport interface JiraIssue {\n  update?: object;\n  fields: {\n    id?: number;\n    key?: string;\n    summary?: string;\n    parent?: {\n      key: string;\n    };\n    subtasks?: JiraIssue[];\n    description?: any;\n    environment?: string;\n    project?: JiraProject;\n    priority?: JiraPriority;\n    assignee?: any;\n    reporter?: any;\n    creator?: any;\n    issuetype?: IssueType;\n    issueStatus?: IssueStatus;\n    created?: Date;\n    updated?: Date;\n    dueDate?: Date;\n    resolution?: any;\n    originalEstimate?: number;\n    remainingEstimate?: number;\n    timeSpent?: number;\n    securityLevel?: any;\n    labels?: string[];\n    versions?: any[];\n    fixVersions?: any[];\n    components?: [];\n    comments?: Comment[];\n    attachments?: [];\n    links?: IssueLink[];\n    properties?: any;\n  };\n}\n"
  },
  {
    "path": "src/interfaces/jira/jira-project.interface.ts",
    "content": "export interface JiraProject {\n  id?: string;\n  key?: string;\n  style?: string;\n  name?: string;\n  projectTypeKey?: string;\n  properties?: any;\n}\n"
  },
  {
    "path": "src/interfaces/jira/jira-result.interface.ts",
    "content": "export interface JiraResult {\n  id: string;\n  key: string;\n  self: string;\n  message: string;\n}\n"
  },
  {
    "path": "src/interfaces/team-info.interface.ts",
    "content": "import { Organization } from '../entity/Organization';\n\nexport interface TeamInfo {\n  id: number;\n  role: string;\n  organization: Organization;\n  asset: number;\n}\n"
  },
  {
    "path": "src/interfaces/user-request.interface.ts",
    "content": "import { Request } from 'express';\nimport { Team } from '../entity/Team';\nimport { File } from '../entity/File';\nexport interface UserRequest extends Request {\n  user: string;\n  fileExtError: string;\n  file: File;\n  files: File[];\n  isAdmin: boolean;\n  userTeams: Team[];\n  userOrgs: number[];\n  userAssets: number[];\n}\n"
  },
  {
    "path": "src/middleware/jwt.middleware.ts",
    "content": "import jwt = require('jsonwebtoken');\nimport { Asset } from '../entity/Asset';\nimport { Organization } from '../entity/Organization';\nimport { UserRequest } from '../interfaces/user-request.interface';\nimport { AppDataSource } from '../data-source';\nimport { User } from '../entity/User';\nimport { ROLE } from '../enums/roles-enum';\nimport { Response } from 'express';\nimport { ApiKey } from '../entity/ApiKey';\nimport { compare } from '../utilities/password.utility';\n\n/**\n * @description Checks for valid token before API logic\n * @param {Request} req\n * @param {Response} res\n */\nexport const checkToken = async (req: UserRequest, res: Response, next) => {\n  if (req.headers.authorization) {\n    const token = req.headers.authorization; // Express headers are auto converted to lowercase\n    jwt.verify(token, process.env.JWT_KEY, async (err, decoded) => {\n      if (err) {\n        return res.status(401).json('Authorization token is not valid');\n      } else {\n        req.user = decoded.userId;\n        await fetchRoles(req);\n        next();\n      }\n    });\n  } else {\n    const apiKey = req.header('Bulwark-Api-Key');\n    const secretApiKey = req.header('Bulwark-Secret-Key');\n    if (apiKey && secretApiKey) {\n      await fetchRoles(req, res, apiKey, secretApiKey, next);\n    } else {\n      return res.status(401).json('Authorization token not supplied');\n    }\n  }\n};\n\n/**\n * @description Checks for valid token before API logic\n * @param {Request} req\n * @param {Response} res\n */\nexport const checkRefreshToken = (req, res, next) => {\n  const token = req.body.refreshToken;\n  if (token) {\n    jwt.verify(token, process.env.JWT_REFRESH_KEY, (err, decoded) => {\n      if (err) {\n        return res.status(401).json('Authorization token is not valid');\n      } else {\n        req.user = decoded.userId;\n        next();\n      }\n    });\n  } else {\n    return res.status(401).json('Refresh token not supplied');\n  }\n};\n\n/**\n * @description Validates Admin user role before executing route\n * @param {Request} req\n * @param {Response} res\n */\nexport const isAdmin = async (req, res, next) => {\n  const userRepository = AppDataSource.getRepository(User);\n  \n  const user = await userRepository.findOne({\n    where: { id: req.user },\n    relations: ['teams']\n  });\n  \n  if (user && user.teams.some((team) => team.role === ROLE.ADMIN)) {\n    next();\n  } else {\n    return res.status(403).json('Authorization is required');\n  }\n};\n\n// Determine if the user is an Administrator\n// If the user is an administrator, return all organizations and assets\n// Else return only the organization and assets associated to team\nexport const fetchRoles = async (\n  req?: UserRequest,\n  res?: Response,\n  apiKey?: string | string[],\n  secretKey?: string | string[],\n  next?\n) => {\n  let user: User;\n  if (apiKey && secretKey && res) {\n    try {\n      const apiKeyInfo = await fetchUserByApiKey(apiKey, secretKey);\n      if (!apiKeyInfo) {\n        return res\n          .status(401)\n          .json('The provided API key or Secret key is not valid');\n      } else {\n        user = apiKeyInfo.user;\n        req.user = user.id.toString();\n        await setUserRoles(req, user);\n        next();\n      }\n    } catch (error) {\n      console.error('Error fetching API key:', error);\n      return res.status(401).json('Authentication failed');\n    }\n  } else {\n    try {\n      user = await fetchUserByJwt(req.user);\n      await setUserRoles(req, user);\n    } catch (error) {\n      console.error('Error fetching user by JWT:', error);\n      if (res) {\n        return res.status(401).json('Authentication failed');\n      }\n    }\n  }\n};\n\nconst setUserRoles = async (req: UserRequest, user: User) => {\n  const organizationRepository = AppDataSource.getRepository(Organization);\n  const assetRepository = AppDataSource.getRepository(Asset);\n  \n  req.userTeams = user.teams;\n  const isAdmin = req.userTeams.some((team) => team.role === ROLE.ADMIN);\n  \n  if (isAdmin) {\n    const orgs = await organizationRepository.find();\n    const assets = await assetRepository.find();\n    \n    req.userOrgs = orgs.map((org) => org.id);\n    req.userAssets = assets.map((asset) => asset.id);\n    req.isAdmin = true;\n  } else {\n    req.isAdmin = false;\n    req.userOrgs = req.userTeams.map((team) => team.organization.id);\n    req.userAssets = [];\n    \n    for (const team of req.userTeams) {\n      if (team.assets && team.assets.length > 0) {\n        const assets = team.assets.map((asset) => asset.id);\n        req.userAssets.push(...assets);\n      }\n    }\n    \n    // Remove duplicate asset IDs\n    req.userAssets = [...new Set(req.userAssets)];\n  }\n};\n\nconst fetchUserByApiKey = async (\n  apiKey: string | string[],\n  secretKey: string | string[]\n) => {\n  const apiKeyRepository = AppDataSource.getRepository(ApiKey);\n  \n  const apiKeyInfo = await apiKeyRepository.findOne({\n    where: { key: apiKey.toString(), active: true },\n    relations: ['user', 'user.teams', 'user.teams.organization', 'user.teams.assets']\n  });\n  \n  if (!apiKeyInfo) {\n    return null;\n  }\n  \n  const valid = await compare(secretKey, apiKeyInfo.secretKey);\n  if (valid) {\n    return apiKeyInfo;\n  } else {\n    return null;\n  }\n};\n\nconst fetchUserByJwt = async (userId: string): Promise<User> => {\n  const userRepository = AppDataSource.getRepository(User);\n  \n  const user = await userRepository.findOne({\n    where: { id: parseInt(userId) },\n    relations: ['teams', 'teams.organization', 'teams.assets']\n  });\n  \n  if (!user) {\n    throw new Error('User not found');\n  }\n  \n  return user;\n};"
  },
  {
    "path": "src/middleware/jwt.spec.ts",
    "content": "require('dotenv').config({ path: '.env' });\nimport jwt = require('jsonwebtoken');\nconst { checkToken, checkRefreshToken } = require('./jwt.middleware');\n\ndescribe('jwt.middleware.ts', () => {\n  // Mocks the Request Object that is returned\n  const mockRequest = () => {\n    const req = {\n      headers: {\n        authorization: 'FakeJWT',\n      },\n      body: {},\n      user: Function,\n    };\n    req.user = jest.fn().mockReturnValue(req);\n    return req;\n  };\n  // Mocks the Response Object that is returned\n  const mockResponse = () => {\n    const res = {\n      status: Function,\n      json: Function,\n    };\n    res.status = jest.fn().mockReturnValue(res);\n    res.json = jest.fn().mockReturnValue(res);\n    return res;\n  };\n  let token: string;\n  let refreshToken: string;\n  beforeAll(() => {\n    token = jwt.sign({ userId: '2222' }, process.env.JWT_KEY, {\n      expiresIn: '15m',\n    });\n    refreshToken = jwt.sign({ userId: '2222' }, process.env.JWT_REFRESH_KEY, {\n      expiresIn: '8h',\n    });\n  });\n\n  test('running checkToken should return a 401 with Authorization', async () => {\n    const req = mockRequest();\n    const res = mockResponse();\n    await checkToken(req, res, null);\n    expect(res.status).toHaveBeenCalledWith(401);\n  });\n\n  test.skip('running checkToken should return a req.user', async () => {\n    const req = mockRequest();\n    const res = mockResponse();\n    req.headers.authorization = token;\n    await checkToken(req, res, jest.fn());\n    expect(req.user).toBe('2222');\n  });\n\n  test('running checkRefreshToken should return a 401 with refreshToken', async () => {\n    const req = mockRequest();\n    const res = mockResponse();\n    req.body = {\n      refreshToken: 'FakeJWT',\n    };\n    await checkRefreshToken(req, res, null);\n    expect(res.status).toHaveBeenCalledWith(401);\n  });\n  test('running checkRefreshToken should return a 401 without refreshToken', async () => {\n    const req = mockRequest();\n    const res = mockResponse();\n    await checkRefreshToken(req, res, null);\n    expect(res.status).toHaveBeenCalledWith(401);\n  });\n\n  test('running checkRefreshToken should return a req.user', async () => {\n    const req = mockRequest();\n    const res = mockResponse();\n    req.body = {\n      refreshToken,\n    };\n    await checkRefreshToken(req, res, jest.fn());\n    expect(req.user).toBe('2222');\n  });\n});\n"
  },
  {
    "path": "src/routes/api-key.controller.spec.ts",
    "content": "import { createConnection, getConnection } from 'typeorm';\nimport * as configController from './config.controller';\nimport MockExpressResponse = require('mock-express-response');\nimport MockExpressRequest = require('mock-express-request');\nimport { User } from '../entity/User';\nimport { Team } from '../entity/Team';\nimport { compare } from '../utilities/password.utility';\nimport { v4 as uuidv4 } from 'uuid';\nimport { Assessment } from '../entity/Assessment';\nimport { Asset } from '../entity/Asset';\nimport { Jira } from '../entity/Jira';\nimport { Organization } from '../entity/Organization';\nimport { ProblemLocation } from '../entity/ProblemLocation';\nimport { ReportAudit } from '../entity/ReportAudit';\nimport { Resource } from '../entity/Resource';\nimport { Vulnerability } from '../entity/Vulnerability';\nimport { Config } from '../entity/Config';\nimport { File } from '../entity/File';\nimport { ApiKey } from '../entity/ApiKey';\nimport {\n  deleteApiKeyAsAdmin,\n  deleteApiKeyAsUser,\n  generateApiKey,\n  getAdminApiKeyInfo,\n  getUserApiKeyInfo,\n} from './api-key.controller';\n\ndescribe('config controller', () => {\n  beforeEach(async () => {\n    return createConnection({\n      type: 'sqlite',\n      database: ':memory:',\n      dropSchema: true,\n      entities: [\n        Config,\n        User,\n        Team,\n        Organization,\n        Asset,\n        Assessment,\n        Vulnerability,\n        ProblemLocation,\n        ReportAudit,\n        Resource,\n        Jira,\n        File,\n        ApiKey,\n      ],\n      synchronize: true,\n      logging: false,\n    });\n  });\n  afterEach(() => {\n    const conn = getConnection();\n    return conn.close();\n  });\n  test('Save API Key to User', async () => {\n    let newUser = new User();\n    newUser.firstName = 'master';\n    newUser.lastName = 'chief';\n    newUser.email = 'testing@jest.com';\n    newUser.active = true;\n    newUser = await getConnection().getRepository(User).save(newUser);\n    const response = new MockExpressResponse();\n    const request = new MockExpressRequest({\n      user: newUser.id,\n    });\n    await generateApiKey(request, response);\n    expect(response.statusCode).toBe(200);\n  });\n\n  test('Save Invalid API Key to User', async () => {\n    let newUser = new User();\n    newUser.firstName = 'master';\n    newUser.lastName = 'chief';\n    newUser.email = 'testing@jest.com';\n    newUser.active = true;\n    newUser = await getConnection().getRepository(User).save(newUser);\n    const response = new MockExpressResponse();\n    const request = new MockExpressRequest({\n      user: newUser.id,\n    });\n    await generateApiKey(request, response);\n    expect(response.statusCode).toBe(200);\n  });\n\n  test('Deactivate All Existing API Keys', async () => {\n    let newUser = new User();\n    newUser.firstName = 'master';\n    newUser.lastName = 'chief';\n    newUser.email = 'testing@jest.com';\n    newUser.active = true;\n    newUser = await getConnection().getRepository(User).save(newUser);\n    const response = new MockExpressResponse();\n    const request = new MockExpressRequest({\n      user: newUser.id,\n    });\n    await generateApiKey(request, response);\n    expect(response.statusCode).toBe(200);\n    const response2 = new MockExpressResponse();\n    const request2 = new MockExpressRequest({\n      user: newUser.id,\n    });\n    await generateApiKey(request2, response2);\n    expect(response2.statusCode).toBe(200);\n  });\n\n  test('Delete API Key as User', async () => {\n    let newUser = new User();\n    newUser.firstName = 'master';\n    newUser.lastName = 'chief';\n    newUser.email = 'testing@jest.com';\n    newUser.active = true;\n    newUser = await getConnection().getRepository(User).save(newUser);\n    const response = new MockExpressResponse();\n    const request = new MockExpressRequest({\n      user: newUser.id,\n    });\n    await generateApiKey(request, response);\n    expect(response.statusCode).toBe(200);\n    newUser = await getConnection()\n      .getRepository(User)\n      .findOne({\n        where: { id: newUser.id },\n        relations: ['apiKey'],\n      });\n    const response2 = new MockExpressResponse();\n    const request2 = new MockExpressRequest({\n      user: newUser.id,\n      params: {\n        id: newUser.apiKey[0].id,\n      },\n    });\n    await deleteApiKeyAsUser(request2, response2);\n    expect(response2.statusCode).toBe(200);\n  });\n\n  test('Delete Invalid API Key as User', async () => {\n    let newUser = new User();\n    newUser.firstName = 'master';\n    newUser.lastName = 'chief';\n    newUser.email = 'testing@jest.com';\n    newUser.active = true;\n    newUser = await getConnection().getRepository(User).save(newUser);\n    const response = new MockExpressResponse();\n    const request = new MockExpressRequest({\n      user: newUser.id,\n      params: {\n        id: 1,\n      },\n    });\n    await deleteApiKeyAsUser(request, response);\n    expect(response.statusCode).toBe(404);\n  });\n\n  test('Delete No API Key as User', async () => {\n    let newUser = new User();\n    newUser.firstName = 'master';\n    newUser.lastName = 'chief';\n    newUser.email = 'testing@jest.com';\n    newUser.active = true;\n    newUser = await getConnection().getRepository(User).save(newUser);\n    const response = new MockExpressResponse();\n    const request = new MockExpressRequest({\n      user: newUser.id,\n      params: {},\n    });\n    await deleteApiKeyAsUser(request, response);\n    expect(response.statusCode).toBe(400);\n  });\n\n  test('Delete API Key as Admin', async () => {\n    let newUser = new User();\n    newUser.firstName = 'master';\n    newUser.lastName = 'chief';\n    newUser.email = 'testing@jest.com';\n    newUser.active = true;\n    newUser = await getConnection().getRepository(User).save(newUser);\n    const response = new MockExpressResponse();\n    const request = new MockExpressRequest({\n      user: newUser.id,\n    });\n    await generateApiKey(request, response);\n    expect(response.statusCode).toBe(200);\n    newUser = await getConnection()\n      .getRepository(User)\n      .findOne({\n        where: { id: newUser.id },\n        relations: ['apiKey'],\n      });\n    const response2 = new MockExpressResponse();\n    const request2 = new MockExpressRequest({\n      user: newUser.id,\n      params: {\n        id: newUser.apiKey[0].id,\n      },\n    });\n    await deleteApiKeyAsAdmin(request2, response2);\n    expect(response2.statusCode).toBe(200);\n  });\n\n  test('Delete Invalid API Key as Admin', async () => {\n    let newUser = new User();\n    newUser.firstName = 'master';\n    newUser.lastName = 'chief';\n    newUser.email = 'testing@jest.com';\n    newUser.active = true;\n    newUser = await getConnection().getRepository(User).save(newUser);\n    const response = new MockExpressResponse();\n    const request = new MockExpressRequest({\n      user: newUser.id,\n      params: {\n        id: 1,\n      },\n    });\n    await deleteApiKeyAsAdmin(request, response);\n    expect(response.statusCode).toBe(404);\n  });\n\n  test('Delete No API Key as Admin', async () => {\n    let newUser = new User();\n    newUser.firstName = 'master';\n    newUser.lastName = 'chief';\n    newUser.email = 'testing@jest.com';\n    newUser.active = true;\n    newUser = await getConnection().getRepository(User).save(newUser);\n    const response = new MockExpressResponse();\n    const request = new MockExpressRequest({\n      user: newUser.id,\n      params: {},\n    });\n    await deleteApiKeyAsAdmin(request, response);\n    expect(response.statusCode).toBe(400);\n  });\n\n  test('Fetch User API Key Info', async () => {\n    let newUser = new User();\n    newUser.firstName = 'master';\n    newUser.lastName = 'chief';\n    newUser.email = 'testing@jest.com';\n    newUser.active = true;\n    newUser = await getConnection().getRepository(User).save(newUser);\n    const response = new MockExpressResponse();\n    const request = new MockExpressRequest({\n      user: newUser.id,\n    });\n    await generateApiKey(request, response);\n    expect(response.statusCode).toBe(200);\n    newUser = await getConnection()\n      .getRepository(User)\n      .findOne({\n        where: { id: newUser.id },\n        relations: ['apiKey'],\n      });\n    const response2 = new MockExpressResponse();\n    const request2 = new MockExpressRequest({\n      user: newUser.id,\n    });\n    await getUserApiKeyInfo(request2, response2);\n    expect(response2.statusCode).toBe(200);\n    expect(response2._getJSON().id).toBe(newUser.apiKey[0].id);\n  });\n\n  test('Fetch User No API Key Info', async () => {\n    let newUser = new User();\n    newUser.firstName = 'master';\n    newUser.lastName = 'chief';\n    newUser.email = 'testing@jest.com';\n    newUser.active = true;\n    newUser = await getConnection().getRepository(User).save(newUser);\n    newUser = await getConnection()\n      .getRepository(User)\n      .findOne({\n        where: { id: newUser.id },\n        relations: ['apiKey'],\n      });\n    const response2 = new MockExpressResponse();\n    const request2 = new MockExpressRequest({\n      user: newUser.id,\n    });\n    await getUserApiKeyInfo(request2, response2);\n    expect(response2.statusCode).toBe(200);\n    expect(response2._getJSON()).toBe(null);\n  });\n\n  test('Fetch Admin API Key Info', async () => {\n    let newUser = new User();\n    newUser.firstName = 'master';\n    newUser.lastName = 'chief';\n    newUser.email = 'testing@jest.com';\n    newUser.active = true;\n    newUser = await getConnection().getRepository(User).save(newUser);\n    const response = new MockExpressResponse();\n    const request = new MockExpressRequest({\n      user: newUser.id,\n    });\n    await generateApiKey(request, response);\n    expect(response.statusCode).toBe(200);\n    const response2 = new MockExpressResponse();\n    const request2 = new MockExpressRequest({\n      user: newUser.id,\n    });\n    await getAdminApiKeyInfo(request2, response2);\n    expect(response2.statusCode).toBe(200);\n    expect(response2._getJSON().length).toBe(1);\n  });\n});\n"
  },
  {
    "path": "src/routes/api-key.controller.ts",
    "content": "import { User } from '../entity/User';\nimport { AppDataSource } from '../data-source';\nimport { UserRequest } from '../interfaces/user-request.interface';\nimport { ApiKey } from '../entity/ApiKey';\nimport * as crypto from 'crypto';\nimport { validate } from 'class-validator';\nimport { Response } from 'express';\nimport { generateHash } from '../utilities/password.utility';\n\n/**\n * @description Generates an active API key. Deactivates deprecated keys\n * @param {UserRequest} req\n * @param {Response} res\n * @returns success message with API key\n */\nexport const generateApiKey = async (req: UserRequest, res: Response) => {\n  try {\n    const userRepository = AppDataSource.getRepository(User);\n    const apiKeyRepository = AppDataSource.getRepository(ApiKey);\n    \n    const user = await userRepository.findOne({ where: { id: +req.user } });\n    \n    if (!user) {\n      return res.status(404).json('User not found');\n    }\n    \n    const buf = crypto.randomBytes(24);\n    const secretBuf = crypto.randomBytes(24);\n    const secretKey = await generateHash(secretBuf.toString('hex'));\n    \n    const apiKey: ApiKey = new ApiKey();\n    apiKey.key = buf.toString('hex');\n    apiKey.secretKey = secretKey;\n    apiKey.createdDate = new Date();\n    apiKey.lastUpdatedDate = new Date();\n    apiKey.lastUpdatedBy = +req.user;\n    apiKey.active = true;\n    apiKey.user = user;\n    \n    await deactivateExistingApiKeys(user);\n    const savedApiKey = await apiKeyRepository.save(apiKey);\n    \n    return res.status(200).json(\n      `Write down the following keys and keep it in a safe place. You will not be able to retrieve the keys at a later time. \n      \n      Bulwark-Api-Key: ${savedApiKey.key}\n      Bulwark-Secret-Key: ${secretBuf.toString('hex')}`\n    );\n  } catch (error) {\n    console.error('Error generating API key:', error);\n    return res.status(500).json('An error occurred while generating the API key');\n  }\n};\n\n/**\n * @description Deactivates all deprecated keys\n * @param {User} user\n * @returns n/a\n */\nconst deactivateExistingApiKeys = async (user: User) => {\n  try {\n    const apiKeyRepository = AppDataSource.getRepository(ApiKey);\n    \n    const activeApiKeys = await apiKeyRepository.find({\n      where: { \n        user: { id: user.id },\n        active: true \n      }\n    });\n    \n    if (activeApiKeys && activeApiKeys.length) {\n      for (const activeApiKey of activeApiKeys) {\n        activeApiKey.active = false;\n        activeApiKey.lastUpdatedDate = new Date();\n        activeApiKey.lastUpdatedBy = user.id;\n        await apiKeyRepository.save(activeApiKey);\n      }\n    }\n  } catch (error) {\n    console.error('Error deactivating existing API keys:', error);\n  }\n};\n\n/**\n * @description Soft deletes API key from user profile menu\n * @param {UserRequest} req\n * @param {Response} res\n * @returns success message\n */\nexport const deleteApiKeyAsUser = async (req: UserRequest, res: Response) => {\n  try {\n    const { id } = req.params;\n    \n    if (!id) {\n      return res.status(400).json('Invalid API key');\n    }\n    \n    const apiKeyRepository = AppDataSource.getRepository(ApiKey);\n    \n    const apiKey = await apiKeyRepository\n      .createQueryBuilder('apiKey')\n      .leftJoinAndSelect('apiKey.user', 'user')\n      .where('apiKey.id = :id', { id })\n      .andWhere('user.id = :userId', { userId: +req.user })\n      .getOne();\n    \n    if (!apiKey) {\n      return res.status(404).json('API key not found');\n    }\n    \n    apiKey.active = false;\n    apiKey.lastUpdatedBy = +req.user;\n    apiKey.lastUpdatedDate = new Date();\n    \n    await apiKeyRepository.save(apiKey);\n    return res.status(200).json('The API key has been deleted');\n  } catch (error) {\n    console.error('Error deleting API key as user:', error);\n    return res.status(500).json('An error occurred while deleting the API key');\n  }\n};\n\n/**\n * @description Soft deletes API key from Admin menu\n * @param {UserRequest} req\n * @param {Response} res\n * @returns success message\n */\nexport const deleteApiKeyAsAdmin = async (req: UserRequest, res: Response) => {\n  try {\n    const { id } = req.params;\n    \n    if (!id) {\n      return res.status(400).json('Invalid API key');\n    }\n    \n    const apiKeyRepository = AppDataSource.getRepository(ApiKey);\n    const apiKey = await apiKeyRepository.findOne({ where: { id: parseInt(id) } });\n    \n    if (!apiKey) {\n      return res.status(404).json('API key not found');\n    }\n    \n    apiKey.active = false;\n    apiKey.lastUpdatedDate = new Date();\n    apiKey.lastUpdatedBy = +req.user;\n    \n    await apiKeyRepository.save(apiKey);\n    return res.status(200).json('The API key has been deactivated');\n  } catch (error) {\n    console.error('Error deleting API key as admin:', error);\n    return res.status(500).json('An error occurred while deleting the API key');\n  }\n};\n\n/**\n * @description Retrieves Active API key metadata for user\n * @param {UserRequest} req\n * @param {Response} res\n * @returns API key information\n */\nexport const getUserApiKeyInfo = async (req: UserRequest, res: Response) => {\n  try {\n    const apiKeyRepository = AppDataSource.getRepository(ApiKey);\n    \n    const apiKeyInfo = await apiKeyRepository\n      .createQueryBuilder('apiKey')\n      .leftJoinAndSelect('apiKey.user', 'user')\n      .where('apiKey.active = true')\n      .andWhere('user.id = :userId', { userId: +req.user })\n      .select(['apiKey.createdDate', 'apiKey.lastUpdatedDate', 'apiKey.id'])\n      .getOne();\n    \n    if (!apiKeyInfo) {\n      return res.status(200).json(null);\n    }\n    \n    return res.status(200).json(apiKeyInfo);\n  } catch (error) {\n    console.error('Error getting user API key info:', error);\n    return res.status(500).json('An error occurred while retrieving API key information');\n  }\n};\n\n/**\n * @description Retrieves API key metadata for admin\n * @param {UserRequest} req\n * @param {Response} res\n * @returns API key information\n */\nexport const getAdminApiKeyInfo = async (req: UserRequest, res: Response) => {\n  try {\n    const apiKeyRepository = AppDataSource.getRepository(ApiKey);\n    \n    const apiKeyInfo = await apiKeyRepository\n      .createQueryBuilder('apiKey')\n      .leftJoinAndSelect('apiKey.user', 'user')\n      .where('apiKey.active = true')\n      .select([\n        'apiKey.id',\n        'apiKey.createdDate',\n        'apiKey.lastUpdatedDate',\n        'user.email',\n      ])\n      .getMany();\n    \n    return res.status(200).json(apiKeyInfo);\n  } catch (error) {\n    console.error('Error getting admin API key info:', error);\n    return res.status(500).json('An error occurred while retrieving API key information');\n  }\n};"
  },
  {
    "path": "src/routes/assessment.controller.spec.ts",
    "content": "import { getConnection } from 'typeorm';\nimport { Assessment } from '../entity/Assessment';\nimport { Asset } from '../entity/Asset';\nimport { Vulnerability } from '../entity/Vulnerability';\nimport { Organization } from '../entity/Organization';\nimport { createConnection } from 'typeorm';\nimport { File } from '../entity/File';\nimport { User } from '../entity/User';\nimport { ProblemLocation } from '../entity/ProblemLocation';\nimport { Resource } from '../entity/Resource';\nimport MockExpressResponse = require('mock-express-response');\nimport MockExpressRequest = require('mock-express-request');\nimport * as assessmentController from './assessment.controller';\nimport { Jira } from '../entity/Jira';\nimport { Team } from '../entity/Team';\nimport { ROLE } from '../enums/roles-enum';\nimport { ApiKey } from '../entity/ApiKey';\n\ndescribe('Assessment Controller', () => {\n  beforeEach(async () => {\n    await createConnection({\n      type: 'sqlite',\n      database: ':memory:',\n      dropSchema: true,\n      entities: [\n        Asset,\n        Organization,\n        File,\n        Vulnerability,\n        Assessment,\n        User,\n        ProblemLocation,\n        Resource,\n        Jira,\n        Team,\n        ApiKey,\n      ],\n      synchronize: true,\n      logging: false,\n      name: 'default',\n    });\n  });\n  afterEach(() => {\n    const conn = getConnection('default');\n    return conn.close();\n  });\n  test('Assessment Delete', async () => {\n    const org: Organization = {\n      id: null,\n      name: 'testOrg',\n      asset: null,\n      status: 'A',\n      teams: null,\n    };\n    const savedOrg = await getConnection()\n      .getRepository(Organization)\n      .save(org);\n    const assessments: Assessment[] = [];\n    const insertAsset: Asset = {\n      id: null,\n      name: 'testAsset',\n      status: 'A',\n      organization: savedOrg,\n      assessment: assessments,\n      jira: null,\n      teams: null,\n    };\n    const savedAsset = await getConnection()\n      .getRepository(Asset)\n      .save(insertAsset);\n    let existUser = new User();\n    existUser.firstName = 'master';\n    existUser.lastName = 'chief';\n    existUser.email = 'testing@jest.com';\n    existUser.active = true;\n    existUser = await getConnection().getRepository(User).save(existUser);\n    const vulns: Vulnerability[] = [];\n    const team: Team = {\n      id: null,\n      name: 'test team',\n      organization: savedOrg,\n      lastUpdatedBy: existUser.id,\n      createdBy: existUser.id,\n      lastUpdatedDate: new Date(),\n      createdDate: new Date(),\n      role: ROLE.TESTER,\n      assets: [savedAsset],\n      users: [existUser],\n    };\n    const savedTeam = await getConnection().getRepository(Team).save(team);\n    const assessment: Assessment = {\n      id: null,\n      name: 'Test Assessment',\n      executiveSummary: 'Lol this is a bad report. Do not read',\n      jiraId: 'not a jira id',\n      testUrl: 'not a url',\n      prodUrl: 'not a url',\n      scope: 'everything',\n      tag: '1.0.0',\n      startDate: new Date(),\n      endDate: new Date(),\n      asset: savedAsset,\n      testers: [existUser],\n      vulnerabilities: vulns,\n    };\n    const savedAssessment = await getConnection()\n      .getRepository(Assessment)\n      .save(assessment);\n    const response = new MockExpressResponse();\n    const request = new MockExpressRequest({\n      params: {\n        assessmentId: 'abc',\n      },\n      userTeams: [savedTeam],\n    });\n    await assessmentController.deleteAssessmentById(request, response);\n    expect(response.statusCode).toBe(400);\n    const response2 = new MockExpressResponse();\n    const request2 = new MockExpressRequest({\n      params: {\n        assessmentId: null,\n      },\n      userTeams: [savedTeam],\n    });\n    await assessmentController.deleteAssessmentById(request2, response2);\n    expect(response2.statusCode).toBe(400);\n    const response3 = new MockExpressResponse();\n    const request3 = new MockExpressRequest({\n      params: {\n        assessmentId: 3,\n      },\n      userTeams: [savedTeam],\n    });\n    await assessmentController.deleteAssessmentById(request3, response3);\n    expect(response3.statusCode).toBe(404);\n    const response4 = new MockExpressResponse();\n    const request4 = new MockExpressRequest({\n      params: {\n        assessmentId: 1,\n      },\n      userTeams: [savedTeam],\n    });\n    await assessmentController.deleteAssessmentById(request4, response4);\n    expect(response4.statusCode).toBe(200);\n  });\n  test('get assessments by asset id', async () => {\n    const org: Organization = {\n      id: null,\n      name: 'testOrg',\n      asset: null,\n      status: 'A',\n      teams: null,\n    };\n    const savedOrg = await getConnection()\n      .getRepository(Organization)\n      .save(org);\n    const assessments: Assessment[] = [];\n    const insertAsset: Asset = {\n      id: null,\n      name: 'testAsset',\n      status: 'A',\n      organization: savedOrg,\n      assessment: assessments,\n      jira: null,\n      teams: null,\n    };\n    const savedAsset = await getConnection()\n      .getRepository(Asset)\n      .save(insertAsset);\n    let existUser = new User();\n    existUser.firstName = 'master';\n    existUser.lastName = 'chief';\n    existUser.email = 'testing@jest.com';\n    existUser.active = true;\n    existUser = await getConnection().getRepository(User).save(existUser);\n    const vulns: Vulnerability[] = [];\n    const team: Team = {\n      id: null,\n      name: 'test team',\n      organization: savedOrg,\n      lastUpdatedBy: existUser.id,\n      createdBy: existUser.id,\n      lastUpdatedDate: new Date(),\n      createdDate: new Date(),\n      role: ROLE.TESTER,\n      assets: [savedAsset],\n      users: [existUser],\n    };\n    const savedTeam = await getConnection().getRepository(Team).save(team);\n    const assessment: Assessment = {\n      id: null,\n      name: 'Test Assessment',\n      executiveSummary: 'Lol this is a bad report. Do not read',\n      jiraId: 'not a jira id',\n      testUrl: 'not a url',\n      prodUrl: 'not a url',\n      scope: 'everything',\n      tag: '1.0.0',\n      startDate: new Date(),\n      endDate: new Date(),\n      asset: savedAsset,\n      testers: [existUser],\n      vulnerabilities: vulns,\n    };\n    const savedAssessment = await getConnection()\n      .getRepository(Assessment)\n      .save(assessment);\n    const response = new MockExpressResponse();\n    const request = new MockExpressRequest({\n      params: {\n        id: savedAsset.id,\n      },\n      userAssets: [1],\n      userTeams: [savedTeam],\n    });\n    await assessmentController.getAssessmentsByAssetId(request, response);\n    expect(response.statusCode).toBe(200);\n    expect(response._getJSON().assessments.length).toBe(1);\n    const response2 = new MockExpressResponse();\n    const request2 = new MockExpressRequest({\n      params: {\n        blah: 1,\n      },\n    });\n    await assessmentController.getAssessmentsByAssetId(request2, response2);\n    expect(response2.statusCode).toBe(400);\n    const response3 = new MockExpressResponse();\n    const request3 = new MockExpressRequest({\n      params: {\n        id: 'abc',\n      },\n    });\n    await assessmentController.getAssessmentsByAssetId(request3, response3);\n    expect(response3.statusCode).toBe(400);\n    const response4 = new MockExpressResponse();\n    const request4 = new MockExpressRequest({\n      params: {\n        id: savedAsset.id,\n      },\n      userAssets: [2],\n      userTeams: [savedTeam],\n    });\n    await assessmentController.getAssessmentsByAssetId(request4, response4);\n    expect(response4.statusCode).toBe(404);\n  });\n});\n"
  },
  {
    "path": "src/routes/assessment.controller.ts",
    "content": "import { UserRequest } from '../interfaces/user-request.interface';\nimport { Response } from 'express';\nimport { AppDataSource } from '../data-source';\nimport { Assessment } from '../entity/Assessment';\nimport { Asset } from '../entity/Asset';\nimport { validate } from 'class-validator';\nimport { Vulnerability } from '../entity/Vulnerability';\nimport { Organization } from '../entity/Organization';\nimport { Report } from '../classes/Report';\nimport { Config } from '../entity/Config';\nimport {\n  hasAssetReadAccess,\n  hasAssetWriteAccess,\n} from '../utilities/role.utility';\nconst userController = require('../routes/user.controller');\n\n/**\n * @description Get assessments by asset ID\n * @param {UserRequest} req\n * @param {Response} res\n * @returns Asset assessments\n */\nexport const getAssessmentsByAssetId = async (\n  req: UserRequest,\n  res: Response\n) => {\n  try {\n    if (!req.params.id) {\n      return res.status(400).json('Invalid Asset request');\n    }\n    \n    if (isNaN(+req.params.id)) {\n      return res.status(400).json('Invalid Asset ID');\n    }\n    \n    const assetRepository = AppDataSource.getRepository(Asset);\n    const assessmentRepository = AppDataSource.getRepository(Assessment);\n    \n    const asset = await assetRepository.findOne({\n      where: { id: +req.params.id },\n      relations: ['organization']\n    });\n    \n    if (!asset) {\n      return res.status(404).json('Asset does not exist');\n    }\n    \n    const assetAccess = await hasAssetReadAccess(req, asset.id);\n    if (!assetAccess) {\n      return res.status(404).json('Asset not found');\n    }\n    \n    const hasTesterAccess = await hasAssetWriteAccess(req, asset.id);\n    \n    const assessments = await assessmentRepository\n      .createQueryBuilder('assessment')\n      .leftJoinAndSelect('assessment.testers', 'tester')\n      .where('assessment.assetId = :assetId', {\n        assetId: asset.id,\n      })\n      .select([\n        'assessment.id',\n        'assessment.name',\n        'assessment.jiraId',\n        'assessment.startDate',\n        'assessment.endDate',\n        'tester.firstName',\n        'tester.lastName',\n      ])\n      .getMany();\n    \n    return res.status(200).json({ assessments, readOnly: !hasTesterAccess });\n  } catch (error) {\n    console.error('Error fetching assessments:', error);\n    return res.status(500).json('An error occurred while fetching assessments');\n  }\n};\n\n/**\n * @description Get all vulnerabilities by assessment\n * @param {UserRequest} req\n * @param {Response} res\n * @returns assessment vulnerabilities\n */\nexport const getAssessmentVulns = async (req: UserRequest, res: Response) => {\n  try {\n    if (!req.params.id) {\n      return res.status(400).json('Invalid Assessment ID');\n    }\n    \n    if (isNaN(+req.params.id)) {\n      return res.status(400).json('Invalid Assessment ID');\n    }\n    \n    const assessmentRepository = AppDataSource.getRepository(Assessment);\n    const vulnerabilityRepository = AppDataSource.getRepository(Vulnerability);\n    \n    const assessment = await assessmentRepository\n      .createQueryBuilder('assessment')\n      .leftJoinAndSelect('assessment.asset', 'asset')\n      .leftJoinAndSelect('asset.organization', 'organization')\n      .where('assessment.id = :assessmentId', { assessmentId: +req.params.id })\n      .select(['assessment', 'asset', 'organization'])\n      .getOne();\n    \n    if (!assessment) {\n      return res.status(404).json('Assessment does not exist');\n    }\n    \n    const hasReadAccess = await hasAssetReadAccess(req, assessment.asset.id);\n    if (!hasReadAccess) {\n      return res.status(404).json('Assessment not found');\n    }\n    \n    const hasTesterAccess = await hasAssetWriteAccess(req, assessment.asset.id);\n    \n    const vulnerabilities = await vulnerabilityRepository.find({\n      where: { assessment: { id: +req.params.id } }\n    });\n    \n    if (!vulnerabilities) {\n      return res.status(404).json('Vulnerabilities do not exist');\n    }\n    \n    return res.status(200).json({ vulnerabilities, readOnly: !hasTesterAccess });\n  } catch (error) {\n    console.error('Error fetching vulnerabilities:', error);\n    return res.status(500).json('An error occurred while fetching vulnerabilities');\n  }\n};\n\n/**\n * @description Create assessment\n * @param {UserRequest} req\n * @param {Response} res c\n * @returns success message\n */\nexport const createAssessment = async (req: UserRequest, res: Response) => {\n  try {\n    if (isNaN(+req.body.asset)) {\n      return res.status(400).json('Asset ID is invalid');\n    }\n    \n    const assetRepository = AppDataSource.getRepository(Asset);\n    const assessmentRepository = AppDataSource.getRepository(Assessment);\n    \n    const asset = await assetRepository.findOne({\n      where: { id: +req.body.asset },\n      relations: ['organization']\n    });\n    \n    if (!asset) {\n      return res.status(404).json('Asset does not exist');\n    }\n    \n    const hasAccess = await hasAssetWriteAccess(req, asset.id);\n    if (!hasAccess) {\n      return res.status(403).json('Authorization is required');\n    }\n    \n    if (!req.body.testers || !req.body.testers.length) {\n      return res.status(400).json('No testers have been selected');\n    }\n    \n    const testers = await userController.getUsersById(req.body.testers);\n    \n    const assessment = new Assessment();\n    assessment.asset = asset;\n    assessment.name = req.body.name;\n    assessment.executiveSummary = req.body.executiveSummary;\n    assessment.jiraId = req.body.jiraId;\n    assessment.testUrl = req.body.testUrl;\n    assessment.prodUrl = req.body.prodUrl;\n    assessment.scope = req.body.scope;\n    assessment.tag = req.body.tag;\n    assessment.startDate = new Date(req.body.startDate);\n    assessment.endDate = new Date(req.body.endDate);\n    assessment.testers = testers;\n    \n    const errors = await validate(assessment);\n    if (errors.length > 0) {\n      console.error('Validation errors:', errors);\n      return res.status(400).send('Assessment form validation failed');\n    }\n    \n    await assessmentRepository.save(assessment);\n    return res.status(200).json('Assessment created successfully');\n  } catch (error) {\n    console.error('Error creating assessment:', error);\n    return res.status(500).json('An error occurred while creating the assessment');\n  }\n};\n\n/**\n * @description Get asset assessment by ID\n * @param {UserRequest} req\n * @param {Response} res\n * @returns assessment\n */\nexport const getAssessmentById = async (req: UserRequest, res: Response) => {\n  try {\n    if (!req.params.assessmentId) {\n      return res.status(400).send('Invalid assessment request');\n    }\n    \n    if (isNaN(+req.params.assessmentId)) {\n      return res.status(400).json('Invalid Assessment ID');\n    }\n    \n    const assessmentRepository = AppDataSource.getRepository(Assessment);\n    \n    const assessment = await assessmentRepository\n      .createQueryBuilder('assessment')\n      .leftJoinAndSelect('assessment.asset', 'asset')\n      .leftJoinAndSelect('asset.organization', 'organization')\n      .leftJoinAndSelect('assessment.testers', 'tester')\n      .where('assessment.id = :assessmentId', {\n        assessmentId: +req.params.assessmentId,\n      })\n      .select([\n        'assessment',\n        'asset',\n        'organization',\n        'tester.firstName',\n        'tester.lastName',\n        'tester.title',\n        'tester.id',\n      ])\n      .getOne();\n    \n    if (!assessment) {\n      return res.status(404).json('Assessment does not exist');\n    }\n    \n    const hasReadAccess = await hasAssetReadAccess(req, assessment.asset.id);\n    if (!hasReadAccess) {\n      return res.status(404).json('Assessment not found');\n    }\n    \n    const hasTesterAccess = await hasAssetWriteAccess(req, assessment.asset.id);\n    \n    return res.status(200).json({ assessment, readOnly: !hasTesterAccess });\n  } catch (error) {\n    console.error('Error fetching assessment:', error);\n    return res.status(500).json('An error occurred while fetching the assessment');\n  }\n};\n\n/**\n * @description Update assessment\n * @param {UserRequest} req\n * @param {Response} res\n * @returns success message\n */\nexport const updateAssessmentById = async (req: UserRequest, res: Response) => {\n  try {\n    if (!req.params.assessmentId) {\n      return res.status(400).send('Invalid assessment request');\n    }\n    \n    if (isNaN(+req.params.assessmentId)) {\n      return res.status(400).json('Invalid Assessment ID');\n    }\n    \n    const assessmentRepository = AppDataSource.getRepository(Assessment);\n    \n    let assessment = await assessmentRepository.findOne({\n      where: { id: +req.params.assessmentId },\n      relations: ['testers', 'asset']\n    });\n    \n    if (!assessment) {\n      return res.status(404).json('Assessment does not exist');\n    }\n    \n    const hasAccess = await hasAssetWriteAccess(req, assessment.asset.id);\n    if (!hasAccess) {\n      return res.status(403).json('Authorization is required');\n    }\n    \n    if (!req.body.testers || !req.body.testers.length) {\n      return res.status(400).json('No testers have been selected');\n    }\n    \n    const assessmentId = assessment.id;\n    const assetId = assessment.asset.id;\n    \n    // Create a new assessment object from the request body\n    const updatedAssessment = {\n      ...req.body,\n      id: assessmentId,\n      asset: { id: assetId },\n      testers: await userController.getUsersById(req.body.testers)\n    };\n    \n    // Skip asset update since we're keeping the original asset\n    delete updatedAssessment.asset;\n    \n    // Validate dates\n    if (new Date(updatedAssessment.startDate) > new Date(updatedAssessment.endDate)) {\n      return res\n        .status(400)\n        .send('The assessment start date cannot be later than the end date');\n    }\n    \n    // Create the updated assessment with all fields\n    Object.assign(assessment, updatedAssessment);\n    \n    const errors = await validate(assessment);\n    if (errors.length > 0) {\n      console.error('Validation errors:', errors);\n      return res.status(400).send('Assessment form validation failed');\n    }\n    \n    await assessmentRepository.save(assessment);\n    return res.status(200).json('Assessment updated successfully');\n  } catch (error) {\n    console.error('Error updating assessment:', error);\n    return res.status(500).json('An error occurred while updating the assessment');\n  }\n};\n\n/**\n * @description Query information for assessment report\n * @param {UserRequest} req\n * @param {Response} res\n * @returns report information\n */\nexport const queryReportDataByAssessment = async (\n  req: UserRequest,\n  res: Response\n) => {\n  try {\n    if (!req.params.assessmentId) {\n      return res.status(400).send('Invalid report request');\n    }\n    \n    if (isNaN(+req.params.assessmentId)) {\n      return res.status(400).json('Invalid Assessment ID');\n    }\n    \n    const assessmentRepository = AppDataSource.getRepository(Assessment);\n    const assetRepository = AppDataSource.getRepository(Asset);\n    const organizationRepository = AppDataSource.getRepository(Organization);\n    const vulnerabilityRepository = AppDataSource.getRepository(Vulnerability);\n    const configRepository = AppDataSource.getRepository(Config);\n    \n    // Get assessment with testers\n    const assessment = await assessmentRepository\n      .createQueryBuilder('assessment')\n      .leftJoinAndSelect('assessment.testers', 'tester')\n      .where('assessment.id = :assessmentId', {\n        assessmentId: +req.params.assessmentId,\n      })\n      .leftJoinAndSelect('assessment.asset', 'asset')\n      .select([\n        'assessment',\n        'asset.id',\n        'tester.firstName',\n        'tester.lastName',\n        'tester.title',\n      ])\n      .getOne();\n    \n    if (!assessment) {\n      return res.status(404).json('Assessment not found');\n    }\n    \n    // Get assessment for asset\n    const assessmentForId = await assessmentRepository.findOne({\n      where: { id: +req.params.assessmentId },\n      relations: ['asset']\n    });\n    \n    const asset = await assetRepository.findOne({\n      where: { id: assessmentForId.asset.id },\n      relations: ['organization']\n    });\n    \n    const hasReadAccess = await hasAssetReadAccess(req, asset.id);\n    if (!hasReadAccess) {\n      return res.status(404).json('Assessment not found');\n    }\n    \n    const organization = await organizationRepository.findOne({\n      where: { id: asset.organization.id }\n    });\n    \n    // Get vulnerabilities with relations\n    const vulnerabilities = await vulnerabilityRepository\n      .createQueryBuilder('vuln')\n      .leftJoinAndSelect('vuln.screenshots', 'screenshot')\n      .leftJoinAndSelect('vuln.problemLocations', 'problemLocation')\n      .leftJoinAndSelect('vuln.resources', 'resource')\n      .where('vuln.assessmentId = :assessmentId', {\n        assessmentId: assessment.id,\n      })\n      .select([\n        'vuln',\n        'screenshot.id',\n        'screenshot.originalname',\n        'screenshot.mimetype',\n        'problemLocation',\n        'resource',\n      ])\n      .getMany();\n    \n    // Get company name from config\n    const config = await configRepository.findOne({\n      where: { id: 1 },\n      select: ['companyName']\n    });\n    \n    // Create report object\n    const report = new Report();\n    report.org = organization;\n    report.asset = asset;\n    report.assessment = assessment;\n    report.vulns = vulnerabilities;\n    \n    if (config && config.companyName) {\n      report.companyName = config.companyName;\n    } else {\n      report.companyName = null;\n    }\n    \n    return res.status(200).json(report);\n  } catch (error) {\n    console.error('Error generating report:', error);\n    return res.status(500).json('An error occurred while generating the report');\n  }\n};\n\n/**\n * @description Delete assessment by ID\n * @param {UserRequest} req vulnID is required\n * @param {Response} res contains JSON object with the success/fail\n * @returns success/error message\n */\nexport const deleteAssessmentById = async (req: UserRequest, res: Response) => {\n  try {\n    if (!req.params.assessmentId) {\n      return res.status(400).send('Invalid assessment ID');\n    }\n    \n    if (isNaN(+req.params.assessmentId)) {\n      return res.status(400).send('Invalid assessment ID');\n    }\n    \n    const assessmentRepository = AppDataSource.getRepository(Assessment);\n    \n    const assessment = await assessmentRepository.findOne({\n      where: { id: +req.params.assessmentId },\n      relations: ['asset']\n    });\n    \n    if (!assessment) {\n      return res.status(404).send('Assessment does not exist.');\n    }\n    \n    const hasAccess = await hasAssetWriteAccess(req, assessment.asset.id);\n    if (!hasAccess) {\n      return res.status(403).json('Authorization is required');\n    }\n    \n    await assessmentRepository.remove(assessment);\n    \n    return res\n      .status(200)\n      .json(\n        `Assessment #${assessment.id}: \"${assessment.name}\" successfully deleted`\n      );\n  } catch (error) {\n    console.error('Error deleting assessment:', error);\n    return res.status(500).json('An error occurred while deleting the assessment');\n  }\n};"
  },
  {
    "path": "src/routes/asset.controller.spec.ts",
    "content": "import { Asset } from '../entity/Asset';\nimport { Organization } from '../entity/Organization';\nimport * as assetController from './asset.controller';\nimport {\n  createConnection,\n  getConnection,\n  Entity,\n  getRepository,\n} from 'typeorm';\nimport { File } from '../entity/File';\nimport { Vulnerability } from '../entity/Vulnerability';\nimport { Assessment } from '../entity/Assessment';\nimport { User } from '../entity/User';\nimport { ProblemLocation } from '../entity/ProblemLocation';\nimport { Resource } from '../entity/Resource';\nimport MockExpressResponse = require('mock-express-response');\nimport MockExpressRequest = require('mock-express-request');\nimport { Jira } from '../entity/Jira';\nimport { Team } from '../entity/Team';\nimport { ApiKey } from '../entity/ApiKey';\ndescribe('Asset Controller', () => {\n  beforeEach(async () => {\n    await createConnection({\n      type: 'sqlite',\n      database: ':memory:',\n      dropSchema: true,\n      entities: [\n        Asset,\n        Organization,\n        File,\n        Vulnerability,\n        Assessment,\n        User,\n        ProblemLocation,\n        Resource,\n        Jira,\n        Team,\n        ApiKey,\n      ],\n      synchronize: true,\n      logging: false,\n      name: 'default',\n    });\n  });\n  afterEach(() => {\n    const conn = getConnection('default');\n    return conn.close();\n  });\n  test('Activate Asset By ID', async () => {\n    const response = new MockExpressResponse();\n    const request = new MockExpressRequest({\n      params: {\n        assetId: null,\n      },\n    });\n    await assetController.activateAssetById(request, response);\n    expect(response.statusCode).toBe(400);\n    const response2 = new MockExpressResponse();\n    const request2 = new MockExpressRequest({\n      params: {\n        assetId: 999,\n      },\n    });\n    await assetController.activateAssetById(request2, response2);\n    expect(response2.statusCode).toBe(404);\n    const org: Organization = {\n      id: null,\n      name: 'testOrg',\n      asset: null,\n      status: 'A',\n      teams: null,\n    };\n    await getConnection().getRepository(Organization).insert(org);\n    const savedOrg = await getConnection()\n      .getRepository(Organization)\n      .findOne({ where: { id: 1 } });\n    const assessments: Assessment[] = [];\n    const insertAsset: Asset = {\n      id: null,\n      name: 'testAsset',\n      status: 'AH',\n      organization: savedOrg,\n      assessment: assessments,\n      jira: null,\n      teams: null,\n    };\n    await getConnection().getRepository(Asset).insert(insertAsset);\n    const response3 = new MockExpressResponse();\n    const request3 = new MockExpressRequest({\n      params: {\n        assetId: 1,\n      },\n    });\n    await assetController.activateAssetById(request3, response3);\n    expect(response3.statusCode).toBe(200);\n  });\n  test('Archive Asset By ID', async () => {\n    const response = new MockExpressResponse();\n    const request = new MockExpressRequest({\n      params: {\n        assetId: null,\n      },\n    });\n    await assetController.archiveAssetById(request, response);\n    expect(response.statusCode).toBe(400);\n    const response2 = new MockExpressResponse();\n    const request2 = new MockExpressRequest({\n      params: {\n        assetId: 999,\n      },\n    });\n    await assetController.archiveAssetById(request2, response2);\n    expect(response2.statusCode).toBe(404);\n    const org: Organization = {\n      id: null,\n      name: 'testOrg',\n      asset: null,\n      status: 'A',\n      teams: null,\n    };\n    await getConnection().getRepository(Organization).insert(org);\n    const savedOrg = await getConnection()\n      .getRepository(Organization)\n      .findOne({ where: { id: 1 } });\n    const assessments: Assessment[] = [];\n    const insertAsset: Asset = {\n      id: null,\n      name: 'testAsset',\n      status: 'A',\n      organization: savedOrg,\n      assessment: assessments,\n      jira: null,\n      teams: null,\n    };\n    await getConnection().getRepository(Asset).insert(insertAsset);\n    const response3 = new MockExpressResponse();\n    const request3 = new MockExpressRequest({\n      params: {\n        assetId: 1,\n      },\n    });\n    await assetController.archiveAssetById(request3, response3);\n    expect(response3.statusCode).toBe(200);\n  });\n  test('Get Active Assets', async () => {\n    const response = new MockExpressResponse();\n    const request = new MockExpressRequest({\n      params: {\n        id: null,\n      },\n    });\n    await assetController.getOrgAssets(request, response);\n    expect(response.statusCode).toBe(400);\n    const request2 = new MockExpressRequest({\n      params: {\n        id: 'abc',\n      },\n    });\n    const response2 = new MockExpressResponse();\n    await assetController.getOrgAssets(request2, response2);\n    expect(response2.statusCode).toBe(400);\n    const org: Organization = {\n      id: null,\n      name: 'testOrg',\n      asset: null,\n      status: 'A',\n      teams: null,\n    };\n    const savedOrg = await getConnection()\n      .getRepository(Organization)\n      .save(org);\n    const assessments: Assessment[] = [];\n    const insertAsset: Asset = {\n      id: null,\n      name: 'testAsset',\n      status: 'A',\n      organization: savedOrg,\n      assessment: assessments,\n      jira: null,\n      teams: null,\n    };\n    const savedAsset = await getConnection()\n      .getRepository(Asset)\n      .save(insertAsset);\n    const request3 = new MockExpressRequest({\n      params: {\n        id: savedOrg.id,\n      },\n      userOrgs: [savedOrg.id],\n      userAssets: [savedAsset.id],\n    });\n    const response3 = new MockExpressResponse();\n    await assetController.getOrgAssets(request3, response3);\n    expect(response3._getJSON()).toHaveLength(1);\n    const request4 = new MockExpressRequest({\n      params: {\n        id: savedOrg.id,\n      },\n    });\n    const response4 = new MockExpressResponse();\n    await assetController.getOrgAssets(request4, response4);\n    expect(response4.statusCode).toBe(404);\n  });\n  test('Get Archived Assets', async () => {\n    const response = new MockExpressResponse();\n    const request = new MockExpressRequest({\n      params: {\n        id: null,\n      },\n    });\n    await assetController.getArchivedOrgAssets(request, response);\n    expect(response.statusCode).toBe(400);\n    const request2 = new MockExpressRequest({\n      params: {\n        id: 'abc',\n      },\n    });\n    const response2 = new MockExpressResponse();\n    await assetController.getArchivedOrgAssets(request2, response2);\n    expect(response2.statusCode).toBe(400);\n    const org: Organization = {\n      id: null,\n      name: 'testOrg',\n      asset: null,\n      status: 'A',\n      teams: null,\n    };\n    await getConnection().getRepository(Organization).insert(org);\n    const savedOrg = await getConnection()\n      .getRepository(Organization)\n      .findOne({ where: { id: 1 } });\n    const assessments: Assessment[] = [];\n    const insertAsset: Asset = {\n      id: null,\n      name: 'testAsset',\n      status: 'AH',\n      organization: savedOrg,\n      assessment: assessments,\n      jira: null,\n      teams: null,\n    };\n    const savedAsset = await getConnection()\n      .getRepository(Asset)\n      .save(insertAsset);\n    const request3 = new MockExpressRequest({\n      params: {\n        id: 1,\n      },\n      userOrgs: [savedOrg.id],\n      userAssets: [savedAsset.id],\n    });\n    const response3 = new MockExpressResponse();\n    await assetController.getArchivedOrgAssets(request3, response3);\n    expect(response3._getJSON()).toHaveLength(1);\n    const request4 = new MockExpressRequest({\n      params: {\n        id: savedOrg.id,\n      },\n    });\n    const response4 = new MockExpressResponse();\n    await assetController.getArchivedOrgAssets(request4, response4);\n    expect(response4.statusCode).toBe(404);\n  });\n  test('Purge jira info success', async () => {\n    const org: Organization = {\n      id: null,\n      name: 'testOrg',\n      asset: null,\n      status: 'A',\n      teams: null,\n    };\n    await getConnection().getRepository(Organization).insert(org);\n    const savedOrg = await getConnection()\n      .getRepository(Organization)\n      .findOne({ where: { id: 1 } });\n    const assessments: Assessment[] = [];\n    const asset: Asset = {\n      id: null,\n      name: 'Test Asset',\n      status: 'A',\n      assessment: assessments,\n      organization: savedOrg,\n      jira: null,\n      teams: null,\n    };\n    await getConnection().getRepository(Asset).insert(asset);\n    let savedAsset = await getConnection().getRepository(Asset).findOne({ where: { id: 1 } });\n    const jira: Jira = {\n      id: null,\n      username: 'test',\n      host: 'test',\n      apiKey: 'test',\n      asset: null,\n    };\n    await getConnection().getRepository(Asset).save(savedAsset);\n    savedAsset = await getConnection().getRepository(Asset).findOne({ where: { id: 1 } });\n    jira.asset = savedAsset;\n    await getConnection().getRepository(Jira).insert(jira);\n    const request = new MockExpressRequest({\n      params: {\n        assetId: 1,\n      },\n    });\n    const response = new MockExpressResponse();\n    await assetController.purgeJiraInfo(request, response);\n    expect(response.statusCode).toBe(200);\n  });\n  test('Purge jira info failure', async () => {\n    const request = new MockExpressRequest({\n      params: {\n        id: 1,\n      },\n    });\n    const response = new MockExpressResponse();\n    await assetController.purgeJiraInfo(request, response);\n    expect(response.statusCode).toBe(400);\n    const request2 = new MockExpressRequest({\n      params: {\n        assetId: 'test',\n      },\n    });\n    const response2 = new MockExpressResponse();\n    await assetController.purgeJiraInfo(request2, response2);\n    expect(response2.statusCode).toBe(400);\n  });\n  test('Create Asset no jira success', async () => {\n    const request = new MockExpressRequest({\n      params: {\n        id: 1,\n      },\n      body: {\n        name: 'test asset',\n        jira: null,\n      },\n    });\n    const response = new MockExpressResponse();\n    const org: Organization = {\n      id: null,\n      name: 'testOrg',\n      asset: null,\n      status: 'A',\n      teams: null,\n    };\n    await getConnection().getRepository(Organization).insert(org);\n    await getConnection().getRepository(Organization).findOne({ where: { id: 1 } });\n    await assetController.createAsset(request, response);\n    expect(response.statusCode).toBe(200);\n  });\n  test('Create Asset with jira success', async () => {\n    const request = new MockExpressRequest({\n      params: {\n        id: 1,\n      },\n      body: {\n        name: 'test asset',\n        jira: {\n          username: 'test',\n          apiKey: 'test',\n          host: 'test',\n        },\n      },\n    });\n    const response = new MockExpressResponse();\n    const org: Organization = {\n      id: null,\n      name: 'testOrg',\n      asset: null,\n      status: 'A',\n      teams: null,\n    };\n    await getConnection().getRepository(Organization).insert(org);\n    const savedOrg = await getConnection()\n      .getRepository(Organization)\n      .findOne({ where: { id: 1 } });\n    await assetController.createAsset(request, response);\n    expect(response.statusCode).toBe(200);\n  });\n  test('Create Asset failure org id not valid', async () => {\n    const request = new MockExpressRequest({\n      params: {\n        badId: 1,\n      },\n      body: {\n        name: 'test asset',\n        jira: {\n          username: 'test',\n          apiKey: 'test',\n          host: 'test',\n        },\n      },\n    });\n    const response = new MockExpressResponse();\n    await assetController.createAsset(request, response);\n    expect(response.statusCode).toBe(400);\n  });\n  test('Create Asset failure org does not exist', async () => {\n    const request = new MockExpressRequest({\n      params: {\n        id: 1,\n      },\n      body: {\n        name: 'test asset',\n        jira: {\n          username: 'test',\n          apiKey: 'test',\n          host: 'test',\n        },\n      },\n    });\n    const response = new MockExpressResponse();\n    await assetController.createAsset(request, response);\n    expect(response.statusCode).toBe(404);\n  });\n  test('Create Asset failure missing name', async () => {\n    const request = new MockExpressRequest({\n      params: {\n        id: 1,\n      },\n      body: {\n        jira: {\n          username: 'test',\n          apiKey: 'test',\n          host: 'test',\n        },\n      },\n    });\n    const response = new MockExpressResponse();\n    const org: Organization = {\n      id: null,\n      name: 'testOrg',\n      asset: null,\n      status: 'A',\n      teams: null,\n    };\n    await getConnection().getRepository(Organization).insert(org);\n    await assetController.createAsset(request, response);\n    expect(response.statusCode).toBe(400);\n  });\n  test('Get asset by id success', async () => {\n    const org: Organization = {\n      id: null,\n      name: 'testOrg',\n      asset: null,\n      status: 'A',\n      teams: null,\n    };\n    await getConnection().getRepository(Organization).insert(org);\n    const savedOrg = await getConnection()\n      .getRepository(Organization)\n      .findOne({ where: { id: 1 } });\n    const assessments: Assessment[] = [];\n    const asset: Asset = {\n      id: null,\n      name: 'Test Asset',\n      status: 'A',\n      assessment: assessments,\n      organization: savedOrg,\n      jira: null,\n      teams: null,\n    };\n    await getConnection().getRepository(Asset).insert(asset);\n    const request = new MockExpressRequest({\n      params: {\n        assetId: 1,\n      },\n      userAssets: [1],\n    });\n    const response = new MockExpressResponse();\n    await assetController.getAssetById(request, response);\n    expect(response.statusCode).toBe(200);\n  });\n  test('Get asset by id failure no access', async () => {\n    const org: Organization = {\n      id: null,\n      name: 'testOrg',\n      asset: null,\n      status: 'A',\n      teams: null,\n    };\n    await getConnection().getRepository(Organization).insert(org);\n    const savedOrg = await getConnection()\n      .getRepository(Organization)\n      .findOne({ where: { id: 1 } });\n    const assessments: Assessment[] = [];\n    const asset: Asset = {\n      id: null,\n      name: 'Test Asset',\n      status: 'A',\n      assessment: assessments,\n      organization: savedOrg,\n      jira: null,\n      teams: null,\n    };\n    await getConnection().getRepository(Asset).insert(asset);\n    const request = new MockExpressRequest({\n      params: {\n        assetId: 1,\n      },\n      userAssets: [2],\n    });\n    const response = new MockExpressResponse();\n    await assetController.getAssetById(request, response);\n    expect(response.statusCode).toBe(404);\n  });\n  test('get asset by id failure', async () => {\n    const request = new MockExpressRequest({\n      params: {\n        id: 1,\n      },\n    });\n    const response = new MockExpressResponse();\n    await assetController.getAssetById(request, response);\n    expect(response.statusCode).toBe(400);\n  });\n  test('Get asset by id failure asset does not exist', async () => {\n    const org: Organization = {\n      id: null,\n      name: 'testOrg',\n      asset: null,\n      status: 'A',\n      teams: null,\n    };\n    const savedOrg = await getConnection()\n      .getRepository(Organization)\n      .save(org);\n    const assessments: Assessment[] = [];\n    const asset: Asset = {\n      id: null,\n      name: 'Test Asset',\n      status: 'A',\n      assessment: assessments,\n      organization: savedOrg,\n      jira: null,\n      teams: null,\n    };\n    const savedAsset = await getConnection().getRepository(Asset).save(asset);\n    const request = new MockExpressRequest({\n      params: {\n        assetId: 2,\n      },\n      userAssets: [2],\n    });\n    const response = new MockExpressResponse();\n    await assetController.getAssetById(request, response);\n    expect(response.statusCode).toBe(404);\n  });\n  test('Get asset by id success delete api key', async () => {\n    const org: Organization = {\n      id: null,\n      name: 'testOrg',\n      asset: null,\n      status: 'A',\n      teams: null,\n    };\n    await getConnection().getRepository(Organization).insert(org);\n    const savedOrg = await getConnection()\n      .getRepository(Organization)\n      .findOne({ where: { id: 1 } });\n    const assessments: Assessment[] = [];\n    const asset: Asset = {\n      id: null,\n      name: 'Test Asset',\n      status: 'A',\n      assessment: assessments,\n      organization: savedOrg,\n      jira: null,\n      teams: null,\n    };\n    await getConnection().getRepository(Asset).insert(asset);\n    const savedAsset = await getConnection().getRepository(Asset).findOne({ where: { id: 1 } });\n    const jira: Jira = {\n      id: null,\n      username: 'test',\n      host: 'test',\n      apiKey: 'test',\n      asset: savedAsset,\n    };\n    await getConnection().getRepository(Jira).insert(jira);\n    const request = new MockExpressRequest({\n      params: {\n        assetId: 1,\n      },\n      userAssets: [1],\n    });\n    const response = new MockExpressResponse();\n    await assetController.getAssetById(request, response);\n    expect(response.statusCode).toBe(200);\n  });\n  test('Update by asset id success', async () => {\n    const org: Organization = {\n      id: null,\n      name: 'testOrg',\n      asset: null,\n      status: 'A',\n      teams: null,\n    };\n    await getConnection().getRepository(Organization).insert(org);\n    const savedOrg = await getConnection()\n      .getRepository(Organization)\n      .findOne({ where: { id: 1 } });\n    const assessments: Assessment[] = [];\n    const asset: Asset = {\n      id: null,\n      name: 'Test Asset',\n      status: 'A',\n      assessment: assessments,\n      organization: savedOrg,\n      jira: null,\n      teams: null,\n    };\n    await getConnection().getRepository(Asset).insert(asset);\n    await getConnection().getRepository(Asset).findOne({ where: { id: 1 } });\n    const request = new MockExpressRequest({\n      params: {\n        assetId: 1,\n      },\n      body: {\n        name: 'updated asset',\n        jira: {\n          id: null,\n          host: 'test',\n          username: 'test',\n          apiKey: 'test',\n        } as Jira,\n      },\n    });\n    const response = new MockExpressResponse();\n    await assetController.updateAssetById(request, response);\n    expect(response.statusCode).toBe(200);\n  });\n  test('Update asset by id failure asset id is not valid', async () => {\n    const request = new MockExpressRequest({\n      params: {\n        id: 1,\n      },\n      body: {\n        name: 'updated asset',\n        jira: {\n          id: null,\n          host: 'test',\n          username: 'test',\n          apiKey: 'test',\n        } as Jira,\n      },\n    });\n    const response = new MockExpressResponse();\n    await assetController.updateAssetById(request, response);\n    expect(response.statusCode).toBe(400);\n  });\n  test('Update asset by id failure asset does not exist', async () => {\n    const request = new MockExpressRequest({\n      params: {\n        assetId: 1,\n      },\n      body: {\n        name: 'updated asset',\n        jira: {\n          id: null,\n          host: 'test',\n          username: 'test',\n          apiKey: 'test',\n        } as Jira,\n      },\n    });\n    const response = new MockExpressResponse();\n    await assetController.updateAssetById(request, response);\n    expect(response.statusCode).toBe(404);\n  });\n  test('Update by asset id failure missing name', async () => {\n    const org: Organization = {\n      id: null,\n      name: 'testOrg',\n      asset: null,\n      status: 'A',\n      teams: null,\n    };\n    await getConnection().getRepository(Organization).insert(org);\n    const savedOrg = await getConnection()\n      .getRepository(Organization)\n      .findOne({ where: { id: 1 } });\n    const assessments: Assessment[] = [];\n    const asset: Asset = {\n      id: null,\n      name: 'Test Asset',\n      status: 'A',\n      assessment: assessments,\n      organization: savedOrg,\n      jira: null,\n      teams: null,\n    };\n    await getConnection().getRepository(Asset).insert(asset);\n    await getConnection().getRepository(Asset).findOne({ where: { id: 1 } });\n    const request = new MockExpressRequest({\n      params: {\n        assetId: 1,\n      },\n      body: {\n        jira: {\n          id: null,\n          host: 'test',\n          username: 'test',\n          apiKey: 'test',\n        } as Jira,\n      },\n    });\n    const response = new MockExpressResponse();\n    await assetController.updateAssetById(request, response);\n    expect(response.statusCode).toBe(400);\n  });\n  test('Update by asset id failure jira integration', async () => {\n    const org: Organization = {\n      id: null,\n      name: 'testOrg',\n      asset: null,\n      status: 'A',\n      teams: null,\n    };\n    await getConnection().getRepository(Organization).insert(org);\n    const savedOrg = await getConnection()\n      .getRepository(Organization)\n      .findOne({ where: { id: 1 } });\n    const assessments: Assessment[] = [];\n    const asset: Asset = {\n      id: null,\n      name: 'Test Asset',\n      status: 'A',\n      assessment: assessments,\n      organization: savedOrg,\n      jira: null,\n      teams: null,\n    };\n    await getConnection().getRepository(Asset).insert(asset);\n    await getConnection().getRepository(Asset).findOne({ where: { id: 1 } });\n    const request = new MockExpressRequest({\n      params: {\n        assetId: 1,\n      },\n      body: {\n        name: 'test',\n        jira: {\n          id: null,\n          host: 1,\n          username: 1,\n          apiKey: 1,\n        },\n      },\n    });\n    const response = new MockExpressResponse();\n    await assetController.updateAssetById(request, response);\n    expect(response.statusCode).toBe(400);\n  });\n  test('Get open vulns by asset', async () => {\n    const org: Organization = {\n      id: null,\n      name: 'testOrg',\n      asset: null,\n      status: 'A',\n      teams: null,\n    };\n    await getConnection().getRepository(Organization).insert(org);\n    const savedOrg = await getConnection()\n      .getRepository(Organization)\n      .findOne({ where: { id: 1 } });\n    const assessments: Assessment[] = [];\n    const asset: Asset = {\n      id: null,\n      name: 'Test Asset',\n      status: 'A',\n      assessment: assessments,\n      organization: savedOrg,\n      jira: null,\n      teams: null,\n    };\n    const savedAsset = await getConnection().getRepository(Asset).save(asset);\n    const request = new MockExpressRequest({\n      params: {\n        assetId: savedAsset.id,\n      },\n      userAssets: [savedAsset.id],\n    });\n    const response = new MockExpressResponse();\n    await assetController.getOpenVulnsByAsset(request, response);\n    expect(response.statusCode).toBe(200);\n  });\n  test('Get open vulns by asset', async () => {\n    const org: Organization = {\n      id: null,\n      name: 'testOrg',\n      asset: null,\n      status: 'A',\n      teams: null,\n    };\n    await getConnection().getRepository(Organization).insert(org);\n    const savedOrg = await getConnection()\n      .getRepository(Organization)\n      .findOne({ where: { id: 1 } });\n    const assessments: Assessment[] = [];\n    const asset: Asset = {\n      id: null,\n      name: 'Test Asset',\n      status: 'A',\n      assessment: assessments,\n      organization: savedOrg,\n      jira: null,\n      teams: null,\n    };\n    const savedAsset = await getConnection().getRepository(Asset).save(asset);\n    const request = new MockExpressRequest({\n      params: {\n        assetId: savedAsset.id,\n      },\n      userAssets: [999],\n    });\n    const response = new MockExpressResponse();\n    await assetController.getOpenVulnsByAsset(request, response);\n    expect(response.statusCode).toBe(404);\n  });\n});\n"
  },
  {
    "path": "src/routes/asset.controller.ts",
    "content": "import { UserRequest } from '../interfaces/user-request.interface';\nimport { Response, Request } from 'express';\nimport { AppDataSource } from '../data-source';\nimport { Asset } from '../entity/Asset';\nimport { validate } from 'class-validator';\nimport { Organization } from '../entity/Organization';\nimport { status } from '../enums/status-enum';\nimport { encrypt } from '../utilities/crypto.utility';\nimport { Jira } from '../entity/Jira';\nimport { hasAssetReadAccess, hasOrgAccess } from '../utilities/role.utility';\nimport { Vulnerability } from '../entity/Vulnerability';\nimport { In } from 'typeorm';\n\n/**\n * @description Get organization assets\n * @param {UserRequest} req\n * @param {Response} res\n * @returns assets\n */\nexport const getOrgAssets = async (req: UserRequest, res: Response) => {\n  try {\n    if (!req.params.id) {\n      return res.status(400).json('Invalid Asset Request');\n    }\n    \n    if (isNaN(+req.params.id)) {\n      return res.status(400).json('Invalid Organization ID');\n    }\n    \n    if (!hasOrgAccess(req, +req.params.id)) {\n      return res.status(404).json('Organization not found');\n    }\n    \n    const assetRepository = AppDataSource.getRepository(Asset);\n    \n    // Build query based on database type\n    const userAssetsPlaceholder = AppDataSource.options.type === 'sqlite' \n      ? req.userAssets \n      : [null, ...req.userAssets];\n    \n    const assets = await assetRepository\n      .createQueryBuilder('asset')\n      .leftJoinAndSelect('asset.jira', 'jira')\n      .where('asset.organizationId = :orgId', {\n        orgId: req.params.id,\n      })\n      .andWhere('asset.status = :status', {\n        status: status.active,\n      })\n      .andWhere('asset.id IN (:...userAssets)', {\n        userAssets: userAssetsPlaceholder,\n      })\n      .select(['asset.id', 'asset.name', 'asset.status', 'jira.id'])\n      .getMany();\n    \n    if (!assets || assets.length === 0) {\n      return res.status(200).json([]);\n    }\n    \n    // Get open vulnerability counts for each asset\n    for (const asset of assets) {\n      asset['openVulnCount'] = await getOpenVulnCountByAsset(asset);\n    }\n    \n    return res.status(200).json(assets);\n  } catch (error) {\n    console.error('Error fetching organization assets:', error);\n    return res.status(500).json('An error occurred while fetching organization assets');\n  }\n};\n\n/**\n * @description Get organization archived assets\n * @param {UserRequest} req\n * @param {Response} res\n * @returns assets\n */\nexport const getArchivedOrgAssets = async (req: UserRequest, res: Response) => {\n  try {\n    if (!req.params.id) {\n      return res.status(400).json('Invalid Asset Request');\n    }\n    \n    if (isNaN(+req.params.id)) {\n      return res.status(400).json('Invalid Organization ID');\n    }\n    \n    if (!hasOrgAccess(req, +req.params.id)) {\n      return res.status(404).json('Organization not found');\n    }\n    \n    const assetRepository = AppDataSource.getRepository(Asset);\n    \n    // Build query based on database type\n    const userAssetsPlaceholder = AppDataSource.options.type === 'sqlite' \n      ? req.userAssets \n      : [null, ...req.userAssets];\n    \n    const assets = await assetRepository\n      .createQueryBuilder('asset')\n      .leftJoinAndSelect('asset.jira', 'jira')\n      .where('asset.organizationId = :orgId', {\n        orgId: req.params.id,\n      })\n      .andWhere('asset.status = :status', {\n        status: status.archived,\n      })\n      .andWhere('asset.id IN (:...userAssets)', {\n        userAssets: userAssetsPlaceholder,\n      })\n      .select(['asset.id', 'asset.name', 'asset.status', 'jira.id'])\n      .getMany();\n    \n    if (!assets || assets.length === 0) {\n      return res.status(200).json([]);\n    }\n    \n    // Get open vulnerability counts for each asset\n    for (const asset of assets) {\n      asset['openVulnCount'] = await getOpenVulnCountByAsset(asset);\n    }\n    \n    return res.status(200).json(assets);\n  } catch (error) {\n    console.error('Error fetching archived assets:', error);\n    return res.status(500).json('An error occurred while fetching archived assets');\n  }\n};\n\n/**\n * @description Gets vulnerability count by asset ID\n * @param {Asset} asset\n * @returns integer\n */\nexport const getOpenVulnCountByAsset = async (asset: Asset): Promise<number> => {\n  try {\n    const vulnerabilityRepository = AppDataSource.getRepository(Vulnerability);\n    \n    const vulnCount = await vulnerabilityRepository\n      .createQueryBuilder('vuln')\n      .leftJoinAndSelect('vuln.assessment', 'assessment')\n      .leftJoinAndSelect('assessment.asset', 'asset')\n      .where('asset.id = :assetId', {\n        assetId: asset.id,\n      })\n      .andWhere('vuln.status = :status', {\n        status: 'Open',\n      })\n      .select(['vuln.id', 'vuln.name', 'assessment.id', 'asset.id'])\n      .getCount();\n    \n    return vulnCount;\n  } catch (error) {\n    console.error(`Error getting vulnerability count for asset ${asset.id}:`, error);\n    return 0;\n  }\n};\n\n/**\n * @description Fetch open vulnerabilities by asset ID\n * @param {UserRequest} req\n * @param {Response} res\n * @returns array of vulnerabilities\n */\nexport const getOpenVulnsByAsset = async (req: UserRequest, res: Response) => {\n  try {\n    const assetAccess = await hasAssetReadAccess(req, +req.params.assetId);\n    if (!assetAccess) {\n      return res.status(404).json('Asset not found');\n    }\n    \n    const vulnerabilityRepository = AppDataSource.getRepository(Vulnerability);\n    \n    const vulns = await vulnerabilityRepository\n      .createQueryBuilder('vuln')\n      .leftJoinAndSelect('vuln.assessment', 'assessment')\n      .leftJoinAndSelect('assessment.asset', 'asset')\n      .where('asset.id = :assetId', {\n        assetId: req.params.assetId,\n      })\n      .andWhere('vuln.status = :status', {\n        status: 'Open',\n      })\n      .select([\n        'vuln.id',\n        'vuln.name',\n        'vuln.risk',\n        'vuln.systemic',\n        'vuln.cvssScore',\n        'vuln.cvssUrl',\n        'assessment.id',\n        'vuln.jiraId',\n      ])\n      .getMany();\n    \n    return res.status(200).json(vulns);\n  } catch (error) {\n    console.error('Error fetching open vulnerabilities:', error);\n    return res.status(500).json('An error occurred while fetching open vulnerabilities');\n  }\n};\n\n/**\n * @description API backend for creating an asset associated by org ID\n * @param {UserRequest} req\n * @param {Response} res\n * @returns success/error message\n */\nexport const createAsset = async (req: UserRequest, res: Response) => {\n  try {\n    if (isNaN(+req.params.id)) {\n      return res.status(400).json('Organization ID is not valid');\n    }\n    \n    const organizationRepository = AppDataSource.getRepository(Organization);\n    const assetRepository = AppDataSource.getRepository(Asset);\n    \n    const org = await organizationRepository.findOne({\n      where: { id: +req.params.id }\n    });\n    \n    if (!org) {\n      return res.status(404).json('Organization does not exist');\n    }\n    \n    if (!req.body.name) {\n      return res.status(400).send('Asset is not valid');\n    }\n    \n    let asset = new Asset();\n    asset.name = req.body.name;\n    asset.organization = org;\n    asset.status = status.active;\n    \n    const errors = await validate(asset);\n    if (errors.length > 0) {\n      console.error('Validation errors:', errors);\n      return res.status(400).send('Asset form validation failed');\n    }\n    \n    asset = await assetRepository.save(asset);\n    \n    // Handle Jira integration if provided\n    if (\n      req.body.jira &&\n      req.body.jira.username &&\n      req.body.jira.host &&\n      req.body.jira.apiKey\n    ) {\n      try {\n        await addJiraIntegration(\n          req.body.jira.username,\n          req.body.jira.host,\n          req.body.jira.apiKey,\n          asset\n        );\n        return res.status(200).json('Asset saved successfully with Jira integration');\n      } catch (err) {\n        return res.status(400).json(err);\n      }\n    } else {\n      return res\n        .status(200)\n        .json(\n          'Asset saved successfully. Unable to integrate Jira. JIRA integration requires username, host, and API key.'\n        );\n    }\n  } catch (error) {\n    console.error('Error creating asset:', error);\n    return res.status(500).json('An error occurred while creating the asset');\n  }\n};\n\n/**\n * @description Purge JIRA by asset ID\n * @param {UserRequest} req\n * @param {Response} res\n * @returns success/error message\n */\nexport const purgeJiraInfo = async (req: Request, res: Response) => {\n  try {\n    if (!req.params.assetId) {\n      return res.status(400).json('Asset ID is not valid');\n    }\n    \n    if (isNaN(+req.params.assetId)) {\n      return res.status(400).json('Asset ID is not valid');\n    }\n    \n    const assetRepository = AppDataSource.getRepository(Asset);\n    const jiraRepository = AppDataSource.getRepository(Jira);\n    \n    const asset = await assetRepository.findOne({\n      where: { id: +req.params.assetId },\n      relations: ['jira']\n    });\n    \n    if (!asset || !asset.jira) {\n      return res.status(404).json('Asset or Jira integration not found');\n    }\n    \n    await jiraRepository.remove(asset.jira);\n    return res.status(200).json('The API Key has been purged successfully');\n  } catch (error) {\n    console.error('Error purging Jira info:', error);\n    return res.status(500).json('An error occurred while purging Jira info');\n  }\n};\n\n/**\n * @description Associates Asset to JIRA integration\n * @param {string} username\n * @param {string} host\n * @param {string} apiKey\n * @param {Asset} asset\n * @returns Promise<Jira>\n */\nconst addJiraIntegration = (\n  username: string,\n  host: string,\n  apiKey: string,\n  asset: Asset\n): Promise<Jira> => {\n  return new Promise(async (resolve, reject) => {\n    try {\n      const assetRepository = AppDataSource.getRepository(Asset);\n      const jiraRepository = AppDataSource.getRepository(Jira);\n      \n      const existingAsset = await assetRepository.findOne({\n        where: { id: asset.id },\n        relations: ['jira']\n      });\n      \n      if (existingAsset.jira) {\n        return reject(\n          `The Asset: ${existingAsset.name} contains an existing Jira integration. Purge the existing Jira integration and try again.`\n        );\n      }\n      \n      const jiraInit: Jira = new Jira();\n      jiraInit.username = username;\n      jiraInit.host = host;\n      jiraInit.asset = asset;\n      \n      try {\n        jiraInit.apiKey = encrypt(apiKey);\n      } catch (err) {\n        return reject(err);\n      }\n      \n      const errors = await validate(jiraInit);\n      if (errors.length > 0) {\n        console.error('Validation errors:', errors);\n        return reject('Jira integration requires username, host, and API key.');\n      }\n      \n      const jiraResult = await jiraRepository.save(jiraInit);\n      resolve(jiraResult);\n    } catch (error) {\n      console.error('Error adding Jira integration:', error);\n      reject('An error occurred while adding Jira integration');\n    }\n  });\n};\n\n/**\n * @description Get asset by ID\n * @param {UserRequest} req\n * @param {Response} res\n * @returns asset\n */\nexport const getAssetById = async (req: UserRequest, res: Response) => {\n  try {\n    if (isNaN(+req.params.assetId)) {\n      return res.status(400).json('Invalid Asset ID');\n    }\n    \n    const assetRepository = AppDataSource.getRepository(Asset);\n    \n    const asset = await assetRepository.findOne({\n      where: { id: +req.params.assetId },\n      relations: ['jira']\n    });\n    \n    if (!asset) {\n      return res.status(404).send('Asset does not exist');\n    }\n    \n    const hasAccess = await hasAssetReadAccess(req, asset.id);\n    if (!hasAccess) {\n      return res.status(404).json('Asset not found');\n    }\n    \n    // Don't return the API key\n    if (asset.jira) {\n      delete asset.jira.apiKey;\n    }\n    \n    return res.status(200).json(asset);\n  } catch (error) {\n    console.error('Error fetching asset:', error);\n    return res.status(500).json('An error occurred while fetching the asset');\n  }\n};\n\n/**\n * @description Update asset by ID\n * @param {UserRequest} req\n * @param {Response} res\n * @return success/error message\n */\nexport const updateAssetById = async (req: UserRequest, res: Response) => {\n  try {\n    if (isNaN(+req.params.assetId) || !req.params.assetId) {\n      return res.status(400).json('Asset ID is not valid');\n    }\n    \n    const assetRepository = AppDataSource.getRepository(Asset);\n    \n    const asset = await assetRepository.findOne({\n      where: { id: +req.params.assetId }\n    });\n    \n    if (!asset) {\n      return res.status(404).json('Asset does not exist');\n    }\n    \n    if (!req.body.name) {\n      return res.status(400).json('Asset name is not valid');\n    }\n    \n    // Handle Jira integration if provided\n    try {\n      if (\n        req.body.jira &&\n        req.body.jira.username &&\n        req.body.jira.host &&\n        req.body.jira.apiKey\n      ) {\n        await addJiraIntegration(\n          req.body.jira.username,\n          req.body.jira.host,\n          req.body.jira.apiKey,\n          asset\n        );\n      }\n    } catch (err) {\n      return res.status(400).json('JIRA integration validation failed');\n    }\n    \n    asset.name = req.body.name;\n    \n    const errors = await validate(asset, { skipMissingProperties: true });\n    if (errors.length > 0) {\n      console.error('Validation errors:', errors);\n      return res.status(400).send('Asset form validation failed');\n    }\n    \n    await assetRepository.save(asset);\n    return res.status(200).json('Asset updated successfully');\n  } catch (error) {\n    console.error('Error updating asset:', error);\n    return res.status(500).json('An error occurred while updating the asset');\n  }\n};\n\n/**\n * @description Archive asset by ID\n * @param {UserRequest} req\n * @param {Response} res\n * @return success/error message\n */\nexport const archiveAssetById = async (req: UserRequest, res: Response) => {\n  try {\n    if (isNaN(+req.params.assetId) || !req.params.assetId) {\n      return res.status(400).json('Asset ID is not valid');\n    }\n    \n    const assetRepository = AppDataSource.getRepository(Asset);\n    \n    const asset = await assetRepository.findOne({\n      where: { id: +req.params.assetId }\n    });\n    \n    if (!asset) {\n      return res.status(404).json('Asset does not exist');\n    }\n    \n    asset.status = status.archived;\n    await assetRepository.save(asset);\n    \n    return res.status(200).json('Asset archived successfully');\n  } catch (error) {\n    console.error('Error archiving asset:', error);\n    return res.status(500).json('An error occurred while archiving the asset');\n  }\n};\n\n/**\n * @description Activate an asset by ID\n * @param {UserRequest} req\n * @param {Response} res\n * @return success/error message\n */\nexport const activateAssetById = async (req: UserRequest, res: Response) => {\n  try {\n    if (isNaN(+req.params.assetId) || !req.params.assetId) {\n      return res.status(400).json('Asset ID is not valid');\n    }\n    \n    const assetRepository = AppDataSource.getRepository(Asset);\n    \n    const asset = await assetRepository.findOne({\n      where: { id: +req.params.assetId }\n    });\n    \n    if (!asset) {\n      return res.status(404).json('Asset does not exist');\n    }\n    \n    asset.status = status.active;\n    await assetRepository.save(asset);\n    \n    return res.status(200).json('Asset activated successfully');\n  } catch (error) {\n    console.error('Error activating asset:', error);\n    return res.status(500).json('An error occurred while activating the asset');\n  }\n};"
  },
  {
    "path": "src/routes/authentication.controller.ts",
    "content": "import { UserRequest } from '../interfaces/user-request.interface';\nimport { AppDataSource } from '../data-source';\nimport { User } from '../entity/User';\nimport { v4 as uuidv4 } from 'uuid';\nimport { Response } from 'express';\nimport jwt = require('jsonwebtoken');\nimport {\n  generateHash,\n  passwordSchema,\n  compare,\n} from '../utilities/password.utility';\nimport { passwordRequirement } from '../enums/message-enum';\nimport * as emailService from '../services/email.service';\nimport { Config } from '../entity/Config';\nimport { ROLE } from '../enums/roles-enum';\n\n/**\n * @description Login to the application\n * @param {UserRequest} req\n * @param {Response} res\n * @returns valid JWT token\n */\nconst login = async (req: UserRequest, res: Response) => {\n  try {\n    const { password, email } = req.body;\n    \n    if (!email || !password) {\n      return res.status(400).json('Email and password are required');\n    }\n    \n    const userRepository = AppDataSource.getRepository(User);\n    \n    const user = await userRepository\n      .createQueryBuilder('user')\n      .leftJoinAndSelect('user.teams', 'team')\n      .where('user.email = :email', {\n        email,\n      })\n      .getOne();\n    \n    if (!user) {\n      return res.status(400).json('Invalid email or password');\n    }\n    \n    if (!user.active) {\n      // Generate new UUID for verification\n      user.uuid = uuidv4();\n      await userRepository.save(user);\n      \n      // Check if email config exists\n      const configRepository = AppDataSource.getRepository(Config);\n      const config = await configRepository.findOne({ where: { id: 1 } });\n      \n      if (!config || !config.fromEmail || !config.fromEmailPassword) {\n        emailService.sendVerificationEmail(user.uuid, user.email);\n        return res\n          .status(400)\n          .json(\n            'This account has not been activated. The email configuration has not been set. Please contact an administrator'\n          );\n      }\n      \n      return res\n        .status(400)\n        .json(\n          'This account has not been activated. Please check for email verification or contact an administrator.'\n        );\n    }\n    \n    // Verify password\n    let valid: boolean;\n    try {\n      valid = await compare(password, user.password);\n    } catch (err) {\n      console.error('Password comparison error:', err);\n      return res.status(400).json('Authentication failed');\n    }\n    \n    if (valid) {\n      const tokens = generateTokens(user);\n      return res.status(200).json(tokens);\n    } else {\n      return res.status(400).json('Invalid email or password');\n    }\n  } catch (error) {\n    console.error('Login error:', error);\n    return res.status(500).json('An error occurred during login');\n  }\n};\n\n/**\n * @description Initiates forgot password process\n * @param {UserRequest} req\n * @param {Response} res\n * @returns Success message\n */\nconst forgotPassword = async (req: UserRequest, res: Response) => {\n  try {\n    const { email } = req.body;\n    \n    if (!email) {\n      return res.status(400).json('Email is invalid');\n    }\n    \n    const userRepository = AppDataSource.getRepository(User);\n    const configRepository = AppDataSource.getRepository(Config);\n    \n    // Always return the same response regardless of whether the user exists\n    // This prevents user enumeration attacks\n    const successMessage = 'A password reset request has been initiated. Please check your email.';\n    \n    const user = await userRepository\n      .createQueryBuilder('user')\n      .where('user.email = :email', {\n        email,\n      })\n      .getOne();\n    \n    if (!user) {\n      return res.status(200).json(successMessage);\n    }\n    \n    // Generate and save UUID for password reset\n    user.uuid = uuidv4();\n    await userRepository.save(user);\n    \n    // Check if email config exists\n    const config = await configRepository.findOne({ where: { id: 1 } });\n    \n    if (!config || !config.fromEmail || !config.fromEmailPassword) {\n      emailService.sendForgotPasswordEmail(user.uuid, user.email);\n      return res\n        .status(400)\n        .json(\n          'Unable to initiate the password reset process. The email configuration has not been set. Please contact an administrator.'\n        );\n    }\n    \n    // Send email and return success\n    emailService.sendForgotPasswordEmail(user.uuid, user.email);\n    return res.status(200).json(successMessage);\n  } catch (error) {\n    console.error('Forgot password error:', error);\n    return res.status(500).json('An error occurred during the password reset process');\n  }\n};\n\n/**\n * @description Reset user password\n * @param {UserRequest} req\n * @param {Response} res\n * @returns Success message\n */\nconst resetPassword = async (req: UserRequest, res: Response) => {\n  try {\n    const { password, confirmPassword, uuid } = req.body;\n    \n    if (!password || !confirmPassword || !uuid) {\n      return res.status(400).json('Missing required fields');\n    }\n    \n    if (password !== confirmPassword) {\n      return res.status(400).json('Passwords do not match');\n    }\n    \n    if (!passwordSchema.validate(password)) {\n      return res.status(400).json(passwordRequirement);\n    }\n    \n    const userRepository = AppDataSource.getRepository(User);\n    \n    const user = await userRepository\n      .createQueryBuilder('user')\n      .where('user.uuid = :uuid', {\n        uuid,\n      })\n      .getOne();\n    \n    if (!user) {\n      return res\n        .status(400)\n        .json(\n          'Unable to reset user password at this time. Please contact an administrator for assistance.'\n        );\n    }\n    \n    user.password = await generateHash(password);\n    user.uuid = null; // Clear the reset token after use\n    \n    await userRepository.save(user);\n    return res.status(200).json('Password updated successfully');\n  } catch (error) {\n    console.error('Reset password error:', error);\n    return res.status(500).json('An error occurred during password reset');\n  }\n};\n\n/**\n * @description Refresh Session\n * @param {UserRequest} req\n * @param {Response} res\n * @returns Tokens\n */\nconst refreshSession = async (req: UserRequest, res: Response) => {\n  try {\n    const userRepository = AppDataSource.getRepository(User);\n    \n    const user = await userRepository\n      .createQueryBuilder('user')\n      .leftJoinAndSelect('user.teams', 'team')\n      .where('user.id = :userId', {\n        userId: req.user,\n      })\n      .getOne();\n    \n    if (!user) {\n      return res.status(404).json('User not found');\n    }\n    \n    const tokens = generateTokens(user);\n    return res.status(200).json(tokens);\n  } catch (error) {\n    console.error('Session refresh error:', error);\n    return res.status(500).json('An error occurred while refreshing the session');\n  }\n};\n\n/**\n * @description Generate Tokens\n * @param {User} user\n * @returns JWT tokens\n */\nconst generateTokens = (user: User) => {\n  const isAdmin = user.teams && user.teams.some((team) => team.role === ROLE.ADMIN);\n  \n  const token = jwt.sign(\n    { userId: user.id, admin: isAdmin },\n    process.env.JWT_KEY,\n    { expiresIn: '15m' }\n  );\n  \n  const refreshToken = jwt.sign(\n    { userId: user.id },\n    process.env.JWT_REFRESH_KEY,\n    { expiresIn: '8h' }\n  );\n  \n  return {\n    token,\n    refreshToken,\n  };\n};\n\nmodule.exports = {\n  login,\n  forgotPassword,\n  resetPassword,\n  refreshSession,\n};"
  },
  {
    "path": "src/routes/config.controller.spec.ts",
    "content": "import { createConnection, getConnection } from 'typeorm';\nimport * as configController from './config.controller';\nimport MockExpressResponse = require('mock-express-response');\nimport MockExpressRequest = require('mock-express-request');\nimport { User } from '../entity/User';\nimport { Team } from '../entity/Team';\nimport { compare } from '../utilities/password.utility';\nimport { v4 as uuidv4 } from 'uuid';\nimport { Assessment } from '../entity/Assessment';\nimport { Asset } from '../entity/Asset';\nimport { Jira } from '../entity/Jira';\nimport { Organization } from '../entity/Organization';\nimport { ProblemLocation } from '../entity/ProblemLocation';\nimport { ReportAudit } from '../entity/ReportAudit';\nimport { Resource } from '../entity/Resource';\nimport { Vulnerability } from '../entity/Vulnerability';\nimport { Config } from '../entity/Config';\nimport { File } from '../entity/File';\nimport { ApiKey } from '../entity/ApiKey';\n\ndescribe('config controller', () => {\n  beforeEach(() => {\n    return createConnection({\n      type: 'sqlite',\n      database: ':memory:',\n      dropSchema: true,\n      entities: [\n        Config,\n        User,\n        Team,\n        Organization,\n        Asset,\n        Assessment,\n        Vulnerability,\n        ProblemLocation,\n        ReportAudit,\n        Resource,\n        Jira,\n        File,\n        ApiKey,\n      ],\n      synchronize: true,\n      logging: false,\n    });\n  });\n  afterEach(() => {\n    const conn = getConnection();\n    return conn.close();\n  });\n  test('initalize config', async () => {\n    await configController.initialInsert();\n    const userAry = await getConnection().getRepository(User).find({});\n    const configAry = await getConnection().getRepository(Config).find({});\n    const teamAry = await getConnection()\n      .getRepository(Team)\n      .find({ relations: ['users'] });\n    expect(userAry.length).toBe(1);\n    expect(configAry.length).toBe(1);\n    expect(userAry[0].id).toBe(1);\n    expect(userAry[0].firstName).toBe('Master');\n    expect(userAry[0].lastName).toBe('Chief');\n    expect(userAry[0].email).toBe('admin@example.com');\n    expect(userAry[0].title).toBe('Spartan 117');\n    expect(userAry[0].active).toBeTruthy();\n    expect(teamAry[0].name).toBe('Administrators');\n    expect(teamAry[0].users[0].email).toBe('admin@example.com');\n    expect(teamAry[0].createdBy && teamAry[0].lastUpdatedBy).toBe(1);\n    const initUsrPw = userAry[0].password;\n    expect(compare('changeMe', initUsrPw)).toBeTruthy();\n  });\n  test('initial user exists. teams do not exist', async () => {\n    const user = new User();\n    user.active = false;\n    user.uuid = uuidv4();\n    user.email = 'testing@jest.com';\n    user.newEmail = null;\n    await getConnection().getRepository(User).save(user);\n    await configController.initialInsert();\n    const teamAry = await getConnection()\n      .getRepository(Team)\n      .find({ relations: ['users'] });\n    expect(teamAry[0].users).toHaveLength(0);\n    expect(teamAry[0].createdBy && teamAry[0].lastUpdatedBy).toBeNull();\n  });\n  test('save configuration success', async () => {\n    const config = new Config();\n    config.fromEmail = 'testingDude@jest.com';\n    config.companyName = 'Test';\n    config.fromEmailPassword = '123';\n    config.id = 1;\n    await getConnection().getRepository(Config).insert(config);\n    const response = new MockExpressResponse();\n    const request = new MockExpressRequest({\n      body: {\n        fromEmail: 'test@jest.com',\n        fromEmailPassword: 'abc123',\n        companyName: 'UNFC',\n      },\n    });\n    await configController.saveConfig(request, response);\n    expect(response.statusCode).toBe(200);\n  });\n  test('save configuration fail validation', async () => {\n    const config = new Config();\n    config.fromEmail = 'testingDude@jest.com';\n    config.companyName = 'Test';\n    config.fromEmailPassword = '123';\n    config.id = 1;\n    await getConnection().getRepository(Config).insert(config);\n    const response = new MockExpressResponse();\n    const request = new MockExpressRequest({\n      body: {\n        fromEmail: 'test',\n        fromEmailPassword: 'abc123',\n        companyName: 'UNFC',\n      },\n    });\n    await configController.saveConfig(request, response);\n    expect(response.statusCode).toBe(400);\n  });\n  test('patch configuration success', async () => {\n    const config = new Config();\n    config.fromEmail = 'testingDude@jest.com';\n    config.companyName = 'Test';\n    config.fromEmailPassword = '123';\n    config.id = 1;\n    await getConnection().getRepository(Config).insert(config);\n    const response = new MockExpressResponse();\n    const request = new MockExpressRequest({\n      body: {\n        fromEmail: 'test@jest.com',\n        fromEmailPassword: 'abc123',\n        companyName: 'UNFC',\n      },\n    });\n    await configController.saveConfig(request, response);\n    expect(response.statusCode).toBe(200);\n  });\n  test('patch configuration fail validation', async () => {\n    const config = new Config();\n    config.fromEmail = 'testingDude@jest.com';\n    config.companyName = 'Test';\n    config.fromEmailPassword = '123';\n    config.id = 1;\n    await getConnection().getRepository(Config).insert(config);\n    const response = new MockExpressResponse();\n    const request = new MockExpressRequest({\n      body: {\n        fromEmail: 'test',\n        fromEmailPassword: 'abc123',\n        companyName: 'UNFC',\n      },\n    });\n    await configController.saveConfig(request, response);\n    expect(response.statusCode).toBe(400);\n  });\n});\n"
  },
  {
    "path": "src/routes/config.controller.ts",
    "content": "import { Response, Request } from 'express';\nimport { Config } from '../entity/Config';\nimport { User } from '../entity/User';\nimport { Team } from '../entity/Team';\nimport { encrypt } from '../utilities/crypto.utility';\nimport { validate } from 'class-validator';\nimport { generateHash } from '../utilities/password.utility';\nimport { ROLE } from '../enums/roles-enum';\nimport { AppDataSource } from '../data-source';\n\nexport const initialInsert = async () => {\n  try {\n    const configRepository = AppDataSource.getRepository(Config);\n    const userRepository = AppDataSource.getRepository(User);\n    const teamRepository = AppDataSource.getRepository(Team);\n    \n    const configAry = await configRepository.find();\n    const usrAry = await userRepository.find();\n    let savedUser: User;\n    const defaultTeamAry = await teamRepository.find();\n    \n    // Create initial configuration if it doesn't exist\n    if (!configAry.length) {\n      const initialConfig = new Config();\n      initialConfig.companyName = null;\n      initialConfig.fromEmail = null;\n      initialConfig.fromEmailPassword = null;\n      await configRepository.save(initialConfig);\n    }\n    \n    // Create initial admin user if no users exist\n    if (!usrAry.length) {\n      const initialUser = new User();\n      initialUser.active = true;\n      initialUser.email = 'admin@example.com';\n      initialUser.firstName = 'Master';\n      initialUser.lastName = 'Chief';\n      initialUser.title = 'Spartan 117';\n      initialUser.password = await generateHash('changeMe');\n      savedUser = await userRepository.save(initialUser);\n    }\n    \n    // Create default admin team if no teams exist\n    if (!defaultTeamAry.length) {\n      const defaultAdminTeam = new Team();\n      defaultAdminTeam.name = 'Administrators';\n      defaultAdminTeam.createdDate = new Date();\n      defaultAdminTeam.lastUpdatedDate = new Date();\n      defaultAdminTeam.createdBy = savedUser ? savedUser.id : null;\n      defaultAdminTeam.lastUpdatedBy = savedUser ? savedUser.id : null;\n      defaultAdminTeam.organization = null;\n      defaultAdminTeam.role = ROLE.ADMIN;\n      \n      if (savedUser) {\n        defaultAdminTeam.users = [savedUser];\n      }\n      \n      await teamRepository.save(defaultAdminTeam);\n    }\n  } catch (error) {\n    console.error('Error during initial setup:', error);\n  }\n};\n\nexport const getConfig = async (req: Request, res: Response) => {\n  try {\n    const configRepository = AppDataSource.getRepository(Config);\n    const existingConfig = await configRepository.findOne({ where: { id: 1 } });\n    \n    if (!existingConfig) {\n      return res.status(404).json('Configuration not found');\n    }\n    \n    // Don't return the password in the response\n    const configResponse = { ...existingConfig };\n    delete configResponse.fromEmailPassword;\n    \n    return res.status(200).json(configResponse);\n  } catch (error) {\n    console.error('Error getting configuration:', error);\n    return res.status(500).json('An error occurred while fetching the configuration');\n  }\n};\n\nexport const saveConfig = async (req: Request, res: Response) => {\n  try {\n    const configRepository = AppDataSource.getRepository(Config);\n    \n    const fromEmail = req.body.fromEmail;\n    const fromEmailPassword = req.body.fromEmailPassword;\n    const companyName = req.body.companyName;\n    \n    const existingConfig = await configRepository.findOne({ where: { id: 1 } });\n    \n    if (!existingConfig) {\n      return res.status(404).json('Configuration not found');\n    }\n    \n    existingConfig.companyName = companyName;\n    existingConfig.fromEmail = fromEmail;\n    \n    if (fromEmailPassword) {\n      const encryptedPassword = encrypt(fromEmailPassword);\n      existingConfig.fromEmailPassword = encryptedPassword;\n    }\n    \n    const errors = await validate(existingConfig, {\n      skipMissingProperties: true,\n    });\n    \n    if (errors.length > 0) {\n      console.error('Validation errors:', errors);\n      return res.status(400).json('Settings validation failed');\n    }\n    \n    await configRepository.save(existingConfig);\n    return res.status(200).json('Settings updated successfully');\n  } catch (error) {\n    console.error('Error saving configuration:', error);\n    return res.status(500).json('An error occurred while saving the configuration');\n  }\n};"
  },
  {
    "path": "src/routes/file-upload.controller.ts",
    "content": "import { upload, uploadArray } from '../utilities/file.utility';\nimport { Response, Request } from 'express';\nimport { UserRequest } from '../interfaces/user-request.interface';\nimport { File } from '../entity/File';\nimport { AppDataSource } from '../data-source';\n\n/**\n * @description Upload a file\n * @param {UserRequest} req\n * @param {Response} res\n * @returns file ID\n */\nconst uploadFile = (req: UserRequest, res: Response) => {\n    // TODO: Virus Scanning\n    upload(req, res, async err => {\n        if (req.fileExtError) {\n            return res.status(400).send(req.fileExtError);\n        }\n        \n        if (err) {\n            console.error('File upload error:', err);\n            if (err.code === 'LIMIT_FILE_SIZE') {\n                return res.status(400).send('Only File size up to 5 MB allowed');\n            }\n            return res.status(400).send('File upload failed');\n        }\n        \n        if (!req.file) {\n            return res.status(400).send('You must provide a file');\n        }\n        \n        try {\n            const fileRepository = AppDataSource.getRepository(File);\n            let file = new File();\n            file = req.file;\n            const newFile = await fileRepository.save(file);\n            return res.json(newFile.id);\n        } catch (error) {\n            console.error('Error saving file:', error);\n            return res.status(500).send('Failed to save file');\n        }\n    });\n};\n\n/**\n * @description Upload multiple files\n * @param {UserRequest} req\n * @param {Response} res\n * @returns file ID\n */\nconst uploadFileArray = (req: UserRequest, res: Response) => {\n    return new Promise((resolve, reject) => {\n        uploadArray(req, res, async err => {\n            if (req.fileExtError) {\n                res.status(400).send(req.fileExtError);\n                return reject(req.fileExtError);\n            }\n            \n            if (err) {\n                console.error('File upload error:', err);\n                if (err.code === 'LIMIT_FILE_SIZE') {\n                    res.status(400).send('Only File size up to 5 MB allowed');\n                    return reject('File size limit exceeded');\n                }\n                res.status(400).send('File upload failed');\n                return reject('File upload failed');\n            }\n            \n            resolve(req);\n        });\n    });\n};\n\n/**\n * @description Get file by ID\n * @param {UserRequest} req\n * @param {Response} res\n * @returns a buffer with the file data\n */\nconst getFileById = async (req: Request, res: Response) => {\n    try {\n        if (!req.params.id) {\n            return res.status(400).json('Invalid File Request');\n        }\n        \n        if (isNaN(+req.params.id)) {\n            return res.status(400).json('Invalid File ID');\n        }\n        \n        const fileRepository = AppDataSource.getRepository(File);\n        const file = await fileRepository.findOne({\n            where: { id: +req.params.id }\n        });\n        \n        if (!file) {\n            return res.status(404).json('File not found');\n        }\n        \n        res.send(file.buffer);\n    } catch (error) {\n        console.error('Error retrieving file:', error);\n        return res.status(500).json('An error occurred while retrieving the file');\n    }\n};\n\nmodule.exports = {\n    uploadFile,\n    uploadFileArray,\n    getFileById\n};"
  },
  {
    "path": "src/routes/organization.controller.ts",
    "content": "import { UserRequest } from '../interfaces/user-request.interface';\nimport { Response } from 'express';\nimport { AppDataSource } from '../data-source';\nimport { Organization } from '../entity/Organization';\nimport { status } from '../enums/status-enum';\nimport { validate } from 'class-validator';\nimport { In } from 'typeorm';\n\n/**\n * @description Get active organizations\n * @param {UserRequest} req\n * @param {Response} res\n * @returns active organizations\n */\nexport const getActiveOrgs = async (req: UserRequest, res: Response) => {\n  try {\n    const organizationRepository = AppDataSource.getRepository(Organization);\n    \n    const orgs = await organizationRepository.find({\n      where: { \n        status: status.active, \n        id: In(req.userOrgs) \n      }\n    });\n    \n    if (!orgs || orgs.length === 0) {\n      return res.status(200).json([]);\n    }\n    \n    return res.status(200).json(orgs);\n  } catch (error) {\n    console.error('Error fetching active organizations:', error);\n    return res.status(500).json('An error occurred while fetching active organizations');\n  }\n};\n\n/**\n * @description Get archived organizations\n * @param {UserRequest} req\n * @param {Response} res\n * @returns archived organizations\n */\nexport const getArchivedOrgs = async (req: UserRequest, res: Response) => {\n  try {\n    const organizationRepository = AppDataSource.getRepository(Organization);\n    \n    const orgs = await organizationRepository.find({\n      where: { \n        status: status.archived, \n        id: In(req.userOrgs) \n      }\n    });\n    \n    if (!orgs || orgs.length === 0) {\n      return res.status(200).json([]);\n    }\n    \n    return res.status(200).json(orgs);\n  } catch (error) {\n    console.error('Error fetching archived organizations:', error);\n    return res.status(500).json('An error occurred while fetching archived organizations');\n  }\n};\n\n/**\n * @description Get organization by ID\n * @param {UserRequest} req\n * @param {Response} res\n * @returns organization\n */\nexport const getOrgById = async (req: UserRequest, res: Response) => {\n  try {\n    if (!req.params.id) {\n      return res.status(400).json('Invalid Organization Request');\n    }\n    \n    if (isNaN(+req.params.id)) {\n      return res.status(400).json('Invalid Organization ID');\n    }\n    \n    // Check if user has access to this organization\n    if (!req.userOrgs.includes(+req.params.id)) {\n      return res.status(404).json('Organization not found');\n    }\n    \n    const organizationRepository = AppDataSource.getRepository(Organization);\n    const org = await organizationRepository.findOne({\n      where: { id: +req.params.id }\n    });\n    \n    if (!org) {\n      return res.status(404).json('Organization does not exist');\n    }\n    \n    return res.status(200).json({ name: org.name });\n  } catch (error) {\n    console.error('Error fetching organization:', error);\n    return res.status(500).json('An error occurred while fetching the organization');\n  }\n};\n\n/**\n * @description Archive organization by ID\n * @param {UserRequest} req\n * @param {Response} res\n * @returns success/error message\n */\nexport const archiveOrgById = async (req: UserRequest, res: Response) => {\n  try {\n    if (isNaN(+req.params.id)) {\n      return res.status(400).json('Invalid Organization ID');\n    }\n    \n    const organizationRepository = AppDataSource.getRepository(Organization);\n    const org = await organizationRepository.findOne({\n      where: { id: +req.params.id }\n    });\n    \n    if (!org) {\n      return res.status(404).json('Organization does not exist');\n    }\n    \n    org.status = status.archived;\n    \n    const errors = await validate(org);\n    if (errors.length > 0) {\n      console.error('Validation errors:', errors);\n      return res.status(400).json('Organization archive validation failed');\n    }\n    \n    await organizationRepository.save(org);\n    return res.status(200).json('Organization archived successfully');\n  } catch (error) {\n    console.error('Error archiving organization:', error);\n    return res.status(500).json('An error occurred while archiving the organization');\n  }\n};\n\n/**\n * @description Activate organization by ID\n * @param {UserRequest} req\n * @param {Response} res\n * @returns success/error\n */\nexport const activateOrgById = async (req: UserRequest, res: Response) => {\n  try {\n    if (isNaN(+req.params.id)) {\n      return res.status(400).json('Organization ID is not valid');\n    }\n    \n    const organizationRepository = AppDataSource.getRepository(Organization);\n    const org = await organizationRepository.findOne({\n      where: { id: +req.params.id }\n    });\n    \n    if (!org) {\n      return res.status(404).json('Organization does not exist');\n    }\n    \n    org.status = status.active;\n    \n    const errors = await validate(org);\n    if (errors.length > 0) {\n      console.error('Validation errors:', errors);\n      return res.status(400).json('Organization activation validation failed');\n    }\n    \n    await organizationRepository.save(org);\n    return res.status(200).json('Organization activated successfully');\n  } catch (error) {\n    console.error('Error activating organization:', error);\n    return res.status(500).json('An error occurred while activating the organization');\n  }\n};\n\n/**\n * @description Update organization by ID\n * @param {UserRequest} req\n * @param {Response} res\n * @returns success/error message\n */\nexport const updateOrgById = async (req: UserRequest, res: Response) => {\n  try {\n    if (isNaN(+req.params.id)) {\n      return res.status(400).json('Organization ID is not valid');\n    }\n    \n    const organizationRepository = AppDataSource.getRepository(Organization);\n    const org = await organizationRepository.findOne({\n      where: { id: +req.params.id }\n    });\n    \n    if (!org) {\n      return res.status(404).json('Organization does not exist');\n    }\n    \n    org.name = req.body.name;\n    \n    const errors = await validate(org);\n    if (errors.length > 0) {\n      console.error('Validation errors:', errors);\n      return res.status(400).send('Organization form validation failed');\n    }\n    \n    await organizationRepository.save(org);\n    return res.status(200).json('Organization updated successfully');\n  } catch (error) {\n    console.error('Error updating organization:', error);\n    return res.status(500).json('An error occurred while updating the organization');\n  }\n};\n\n/**\n * @description Create organization\n * @param {UserRequest} req\n * @param {Response} res\n * @returns success/error message\n */\nexport const createOrg = async (req: UserRequest, res: Response) => {\n  try {\n    const organizationRepository = AppDataSource.getRepository(Organization);\n    \n    const org = new Organization();\n    org.name = req.body.name;\n    org.status = status.active;\n    \n    const errors = await validate(org);\n    if (errors.length > 0) {\n      console.error('Validation errors:', errors);\n      return res.status(400).send('Organization form validation failed');\n    }\n    \n    await organizationRepository.save(org);\n    return res.status(200).json('Organization created successfully');\n  } catch (error) {\n    console.error('Error creating organization:', error);\n    return res.status(500).json('An error occurred while creating the organization');\n  }\n};"
  },
  {
    "path": "src/routes/report-audit.controller.spec.ts",
    "content": "import {\n  createConnection,\n  getConnection,\n  Entity,\n  getRepository,\n} from 'typeorm';\nimport { ReportAudit } from '../entity/ReportAudit';\nimport { insertReportAuditRecord } from './report-audit.controller';\nimport { User } from '../entity/User';\nimport { Assessment } from '../entity/Assessment';\nimport { Organization } from '../entity/Organization';\nimport { Vulnerability } from '../entity/Vulnerability';\nimport { Asset } from '../entity/Asset';\nimport { ProblemLocation } from '../entity/ProblemLocation';\nimport { Resource } from '../entity/Resource';\nimport { File } from '../entity/File';\nimport { Jira } from '../entity/Jira';\nimport { v4 as uuidv4 } from 'uuid';\nimport { generateHash } from '../utilities/password.utility';\nimport MockExpressRequest = require('mock-express-request');\nimport { Team } from '../entity/Team';\nimport { ApiKey } from '../entity/ApiKey';\n\ndescribe('Report Audit Controller', () => {\n  beforeEach(() => {\n    return createConnection({\n      type: 'sqlite',\n      database: ':memory:',\n      dropSchema: true,\n      entities: [\n        ReportAudit,\n        User,\n        File,\n        Assessment,\n        Asset,\n        Organization,\n        Jira,\n        Vulnerability,\n        ProblemLocation,\n        Resource,\n        Team,\n        ApiKey,\n      ],\n      synchronize: true,\n      logging: false,\n    });\n  });\n\n  afterEach(() => {\n    const conn = getConnection();\n    return conn.close();\n  });\n\n  test('insertReportAuditRecord', async () => {\n    const req = new MockExpressRequest();\n    const existUser = new User();\n    existUser.firstName = 'master';\n    existUser.lastName = 'chief';\n    existUser.email = 'testing@jest.com';\n    existUser.active = true;\n    const uuid = uuidv4();\n    existUser.uuid = uuid;\n    existUser.password = await generateHash('TangoDown123!!!');\n    const savedUser = await getConnection().getRepository(User).save(existUser);\n    const assessment: Assessment = {\n      id: null,\n      name: 'testAssessment',\n      executiveSummary: '',\n      jiraId: '',\n      testUrl: '',\n      prodUrl: '',\n      scope: '',\n      tag: '',\n      startDate: new Date(),\n      endDate: new Date(),\n      asset: new Asset(),\n      testers: null,\n      vulnerabilities: null,\n    };\n    const savedAssessment = await getConnection()\n      .getRepository(Assessment)\n      .save(assessment);\n    req.user = savedUser.id;\n    const auditRecord = await insertReportAuditRecord(\n      req.user,\n      savedAssessment.id\n    );\n    const savedAuditRecord = await getConnection()\n      .getRepository(ReportAudit)\n      .findOne({ where: { id: auditRecord.id } });\n    expect(savedAuditRecord.assessmentId).toBe(savedAssessment.id);\n  });\n});\n"
  },
  {
    "path": "src/routes/report-audit.controller.ts",
    "content": "import { AppDataSource } from '../data-source';\nimport { ReportAudit } from '../entity/ReportAudit';\n\n/**\n * @description Insert report generation audit record\n * @param {number} userId - The ID of the user generating the report\n * @param {number} assessmentId - The ID of the assessment for the report\n * @returns {Promise<ReportAudit>} - The created report audit record\n */\nexport const insertReportAuditRecord = (userId: number, assessmentId: number): Promise<ReportAudit> => {\n  return new Promise(async (resolve, reject) => {\n    try {\n      const reportAuditRepository = AppDataSource.getRepository(ReportAudit);\n      \n      // Create the report audit record\n      const reportAudit: ReportAudit = new ReportAudit();\n      reportAudit.generatedBy = userId;\n      reportAudit.assessmentId = assessmentId;\n      reportAudit.generatedDate = new Date();\n      \n      // Save the report audit record\n      const newAudit = await reportAuditRepository.save(reportAudit);\n      \n      console.info(\n        `Report generated by user ID: ${newAudit.generatedBy} for assessment ID: ${newAudit.assessmentId} on ${newAudit.generatedDate}`\n      );\n      \n      resolve(newAudit);\n    } catch (err) {\n      console.error('Error inserting report audit record:', err);\n      reject(err);\n    }\n  });\n};"
  },
  {
    "path": "src/routes/team.controller.spec.ts",
    "content": "import { createConnection, getConnection } from 'typeorm';\nimport { ReportAudit } from '../entity/ReportAudit';\nimport { User } from '../entity/User';\nimport { Assessment } from '../entity/Assessment';\nimport { Organization } from '../entity/Organization';\nimport { Vulnerability } from '../entity/Vulnerability';\nimport { Asset } from '../entity/Asset';\nimport { ProblemLocation } from '../entity/ProblemLocation';\nimport { Resource } from '../entity/Resource';\nimport { File } from '../entity/File';\nimport { Jira } from '../entity/Jira';\nimport { Team } from '../entity/Team';\nimport { v4 as uuidv4 } from 'uuid';\nimport { generateHash } from '../utilities/password.utility';\nimport MockExpressRequest = require('mock-express-request');\nimport MockExpressResponse = require('mock-express-response');\nimport { ROLE } from '../enums/roles-enum';\nimport { status } from '../enums/status-enum';\nimport { ApiKey } from '../entity/ApiKey';\nimport {\n  createTeam,\n  addTeamMember,\n  removeTeamMember,\n  deleteTeam,\n  updateTeamInfo,\n  getMyTeams,\n  addTeamAsset,\n  removeTeamAsset,\n  getAllTeams,\n} from '../routes/team.controller';\n\ndescribe('Team Controller', () => {\n  beforeEach(() => {\n    return createConnection({\n      type: 'sqlite',\n      database: ':memory:',\n      dropSchema: true,\n      entities: [\n        ReportAudit,\n        User,\n        File,\n        Assessment,\n        Asset,\n        Organization,\n        Jira,\n        Vulnerability,\n        ProblemLocation,\n        Resource,\n        Team,\n        ApiKey,\n      ],\n      synchronize: true,\n      logging: false,\n    });\n  });\n\n  afterEach(() => {\n    const conn = getConnection();\n    return conn.close();\n  });\n\n  test('Create Team', async () => {\n    const existUser = new User();\n    existUser.firstName = 'master';\n    existUser.lastName = 'chief';\n    existUser.email = 'testing@jest.com';\n    existUser.active = true;\n    const uuid = uuidv4();\n    existUser.uuid = uuid;\n    existUser.password = await generateHash('TangoDown123!!!');\n    const savedUser = await getConnection().getRepository(User).save(existUser);\n    const newOrg: Organization = {\n      id: null,\n      name: 'Test Org',\n      status: status.active,\n      asset: null,\n      teams: null,\n    };\n    const savedOrg = await getConnection()\n      .getRepository(Organization)\n      .save(newOrg);\n    // create assets\n    const asset: Asset = {\n      organization: savedOrg,\n      name: 'Test Asset 1',\n      status: 'A',\n      id: null,\n      jira: null,\n      assessment: null,\n      teams: null,\n    };\n    const savedAsset = await getConnection().getRepository(Asset).save(asset);\n    const asset2: Asset = {\n      organization: savedOrg,\n      name: 'Test Asset 1',\n      status: 'A',\n      id: null,\n      jira: null,\n      assessment: null,\n      teams: null,\n    };\n    const savedAsset2 = await getConnection().getRepository(Asset).save(asset2);\n    const asset3: Asset = {\n      organization: savedOrg,\n      name: 'Test Asset 1',\n      status: 'A',\n      id: null,\n      jira: null,\n      assessment: null,\n      teams: null,\n    };\n    const savedAsset3 = await getConnection().getRepository(Asset).save(asset3);\n    const assetAry: Asset[] = [];\n    const request = new MockExpressRequest({\n      body: {\n        id: null,\n        name: 'Test Team',\n        organization: savedOrg.id,\n        asset: null,\n        role: ROLE.ADMIN,\n        assetIds: [savedAsset.id, savedAsset2.id],\n        users: [savedUser.id],\n      },\n      user: savedUser.id,\n    });\n    const response = new MockExpressResponse();\n    await createTeam(request, response);\n    expect(response.statusCode).toBe(200);\n    const teams = await getConnection()\n      .getRepository(Team)\n      .find({ relations: ['users', 'assets'] });\n    expect(teams.length).toBe(1);\n    expect(teams[0].users.length).toBe(1);\n    expect(teams[0].assets.length).toBe(2);\n  });\n\n  test('Create Team Failure', async () => {\n    const existUser = new User();\n    existUser.firstName = 'master';\n    existUser.lastName = 'chief';\n    existUser.email = 'testing@jest.com';\n    existUser.active = true;\n    const uuid = uuidv4();\n    existUser.uuid = uuid;\n    existUser.password = await generateHash('TangoDown123!!!');\n    const savedUser = await getConnection().getRepository(User).save(existUser);\n    const newOrg: Organization = {\n      id: null,\n      name: 'Test Org',\n      status: status.active,\n      asset: null,\n      teams: null,\n    };\n    const savedOrg = await getConnection()\n      .getRepository(Organization)\n      .save(newOrg);\n    const badRequest = new MockExpressRequest({\n      params: {\n        id: 1,\n      },\n      body: {\n        id: null,\n        name: 'test',\n        organization: savedOrg.id,\n        asset: null,\n        role: 'not a role',\n        users: [],\n      },\n      user: savedUser.id,\n    });\n    const badResponse = new MockExpressResponse();\n    await createTeam(badRequest, badResponse);\n    expect(badResponse.statusCode).toBe(400);\n    const teams = await getConnection().getRepository(Team).find({});\n    expect(teams.length).toBe(0);\n    const badRequest2 = new MockExpressRequest({\n      params: {\n        id: 1,\n      },\n      body: {\n        id: null,\n        name: 'test',\n        organization: 3,\n        asset: null,\n        role: 'not a role',\n        users: [],\n      },\n      user: savedUser.id,\n    });\n    const badResponse2 = new MockExpressResponse();\n    await createTeam(badRequest2, badResponse2);\n    expect(badResponse2.statusCode).toBe(404);\n  });\n\n  test('add team member', async () => {\n    // create org\n    const newOrg: Organization = {\n      id: null,\n      name: 'Test Org',\n      status: status.active,\n      asset: null,\n      teams: null,\n    };\n    const savedOrg = await getConnection()\n      .getRepository(Organization)\n      .save(newOrg);\n    // user 1\n    const teamMember1 = new User();\n    teamMember1.firstName = 'master';\n    teamMember1.lastName = 'chief';\n    teamMember1.email = 'testing1@jest.com';\n    teamMember1.active = true;\n    const uuid = uuidv4();\n    teamMember1.uuid = uuid;\n    teamMember1.password = await generateHash('TangoDown123!!!');\n    const addedUser1 = await getConnection()\n      .getRepository(User)\n      .save(teamMember1);\n    // user 2\n    const teamMember2 = new User();\n    teamMember2.firstName = 'master';\n    teamMember2.lastName = 'chief';\n    teamMember2.email = 'testing2@jest.com';\n    teamMember2.active = true;\n    const uuid2 = uuidv4();\n    teamMember2.uuid = uuid2;\n    teamMember2.password = await generateHash('TangoDown123!!!');\n    const addedUser2 = await getConnection()\n      .getRepository(User)\n      .save(teamMember2);\n    // user 3\n    const teamMember3 = new User();\n    teamMember3.firstName = 'master';\n    teamMember3.lastName = 'chief';\n    teamMember3.email = 'testing3@jest.com';\n    teamMember3.active = true;\n    const uuid3 = uuidv4();\n    teamMember3.uuid = uuid3;\n    teamMember3.password = await generateHash('TangoDown123!!!');\n    const addedUser3 = await getConnection()\n      .getRepository(User)\n      .save(teamMember3);\n    // create team\n    const bravoTeam = new Team();\n    bravoTeam.name = 'Bravo';\n    bravoTeam.organization = savedOrg;\n    bravoTeam.id = null;\n    bravoTeam.createdDate = new Date();\n    bravoTeam.lastUpdatedDate = new Date();\n    bravoTeam.createdBy = 0;\n    bravoTeam.lastUpdatedBy = 0;\n    bravoTeam.role = ROLE.READONLY;\n    const savedTeam = await getConnection().getRepository(Team).save(bravoTeam);\n    const request = new MockExpressRequest({\n      body: {\n        userIds: [1, 2, 3],\n        teamId: savedTeam.id,\n      },\n    });\n    const response = new MockExpressResponse();\n    await addTeamMember(request, response);\n    expect(response.statusCode).toBe(200);\n    const badRequest2 = new MockExpressRequest({\n      body: {\n        userIds: [1, 2, 3],\n        teamId: 'lol',\n      },\n    });\n    const badResponse2 = new MockExpressResponse();\n    await addTeamMember(badRequest2, badResponse2);\n    expect(badResponse2.statusCode).toBe(404);\n    const badRequest3 = new MockExpressRequest({\n      body: {\n        userIds: [1, 2, 3],\n      },\n    });\n    const badResponse3 = new MockExpressResponse();\n    await addTeamMember(badRequest3, badResponse3);\n    expect(badResponse3.statusCode).toBe(400);\n    const team = await getConnection()\n      .getRepository(Team)\n      .findOne({ where: { id: savedTeam.id }, relations: ['users'] });\n    expect(team.users.length).toBe(3);\n  });\n\n  test('remove team member', async () => {\n    // create org\n    const newOrg: Organization = {\n      id: null,\n      name: 'Test Org',\n      status: status.active,\n      asset: null,\n      teams: null,\n    };\n    const savedOrg = await getConnection()\n      .getRepository(Organization)\n      .save(newOrg);\n    // user 1\n    const teamMember1 = new User();\n    teamMember1.firstName = 'master';\n    teamMember1.lastName = 'chief';\n    teamMember1.email = 'testing1@jest.com';\n    teamMember1.active = true;\n    const uuid = uuidv4();\n    teamMember1.uuid = uuid;\n    teamMember1.password = await generateHash('TangoDown123!!!');\n    const addedUser1 = await getConnection()\n      .getRepository(User)\n      .save(teamMember1);\n    // user 2\n    const teamMember2 = new User();\n    teamMember2.firstName = 'master';\n    teamMember2.lastName = 'chief';\n    teamMember2.email = 'testing2@jest.com';\n    teamMember2.active = true;\n    const uuid2 = uuidv4();\n    teamMember2.uuid = uuid2;\n    teamMember2.password = await generateHash('TangoDown123!!!');\n    const addedUser2 = await getConnection()\n      .getRepository(User)\n      .save(teamMember2);\n    // user 3\n    const teamMember3 = new User();\n    teamMember3.firstName = 'master';\n    teamMember3.lastName = 'chief';\n    teamMember3.email = 'testing3@jest.com';\n    teamMember3.active = true;\n    const uuid3 = uuidv4();\n    teamMember3.uuid = uuid3;\n    teamMember3.password = await generateHash('TangoDown123!!!');\n    const addedUser3 = await getConnection()\n      .getRepository(User)\n      .save(teamMember3);\n    // create team\n    const bravoTeam = new Team();\n    bravoTeam.name = 'Bravo';\n    bravoTeam.organization = savedOrg;\n    bravoTeam.id = null;\n    bravoTeam.createdDate = new Date();\n    bravoTeam.lastUpdatedDate = new Date();\n    bravoTeam.createdBy = 0;\n    bravoTeam.lastUpdatedBy = 0;\n    bravoTeam.role = ROLE.READONLY;\n    const savedTeam = await getConnection().getRepository(Team).save(bravoTeam);\n    let fetchTeam = await getConnection()\n      .getRepository(Team)\n      .findOne({ where: { id: savedTeam.id }, relations: ['users'] });\n    expect(fetchTeam.users.length).toBe(0);\n    fetchTeam.users.push(addedUser1, addedUser2, addedUser3);\n    fetchTeam = await getConnection().getRepository(Team).save(fetchTeam);\n    fetchTeam = await getConnection()\n      .getRepository(Team)\n      .findOne({ where: { id: fetchTeam.id }, relations: ['users'] });\n    expect(fetchTeam.users.length).toBe(3);\n    const request = new MockExpressRequest({\n      body: {\n        userIds: [1],\n        teamId: savedTeam.id,\n      },\n    });\n    const response = new MockExpressResponse();\n    await removeTeamMember(request, response);\n    expect(response.statusCode).toBe(200);\n    fetchTeam = await getConnection()\n      .getRepository(Team)\n      .findOne({ where: { id: fetchTeam.id }, relations: ['users'] });\n    expect(fetchTeam.users.length).toBe(2);\n    const badRequest = new MockExpressRequest({\n      body: {\n        userIds: [1],\n      },\n    });\n    const badResponse = new MockExpressResponse();\n    await removeTeamMember(badRequest, badResponse);\n    expect(badResponse.statusCode).toBe(400);\n    const badRequest2 = new MockExpressRequest({\n      body: {\n        userIds: [1],\n        teamId: 6,\n      },\n    });\n    const badResponse2 = new MockExpressResponse();\n    await removeTeamMember(badRequest2, badResponse2);\n    expect(badResponse2.statusCode).toBe(404);\n    const badRequest3 = new MockExpressRequest({\n      body: {\n        userIds: [2, 6],\n        teamId: savedTeam.id,\n      },\n    });\n    const badResponse3 = new MockExpressResponse();\n    await removeTeamMember(badRequest3, badResponse3);\n    expect(badResponse3.statusCode).toBe(404);\n  });\n\n  test('update team info', async () => {\n    // create user\n    const existUser = new User();\n    existUser.firstName = 'master';\n    existUser.lastName = 'chief';\n    existUser.email = 'testing@jest.com';\n    existUser.active = true;\n    const uuid = uuidv4();\n    existUser.uuid = uuid;\n    existUser.password = await generateHash('TangoDown123!!!');\n    const savedUser = await getConnection().getRepository(User).save(existUser);\n    // create user\n    const existUser2 = new User();\n    existUser2.firstName = 'master';\n    existUser2.lastName = 'chief';\n    existUser2.email = 'testing2@jest.com';\n    existUser2.active = true;\n    const uuid2 = uuidv4();\n    existUser2.uuid = uuid2;\n    existUser2.password = await generateHash('TangoDown123!!!');\n    const savedUser2 = await getConnection()\n      .getRepository(User)\n      .save(existUser2);\n    // create org\n    const newOrg: Organization = {\n      id: null,\n      name: 'Test Org',\n      status: status.active,\n      asset: null,\n      teams: null,\n    };\n    const savedOrg = await getConnection()\n      .getRepository(Organization)\n      .save(newOrg);\n    // create assets\n    const asset: Asset = {\n      organization: savedOrg,\n      name: 'Test Asset 1',\n      status: 'A',\n      id: null,\n      jira: null,\n      assessment: null,\n      teams: null,\n    };\n    const savedAsset = await getConnection().getRepository(Asset).save(asset);\n    const asset2: Asset = {\n      organization: savedOrg,\n      name: 'Test Asset 1',\n      status: 'A',\n      id: null,\n      jira: null,\n      assessment: null,\n      teams: null,\n    };\n    const savedAsset2 = await getConnection().getRepository(Asset).save(asset2);\n    const asset3: Asset = {\n      organization: savedOrg,\n      name: 'Test Asset 1',\n      status: 'A',\n      id: null,\n      jira: null,\n      assessment: null,\n      teams: null,\n    };\n    const savedAsset3 = await getConnection().getRepository(Asset).save(asset3);\n    const assetAry: Asset[] = [];\n    assetAry.push(savedAsset, savedAsset2);\n    // create team\n    const bravoTeam = new Team();\n    bravoTeam.name = 'Bravo';\n    bravoTeam.organization = savedOrg;\n    bravoTeam.id = null;\n    bravoTeam.createdDate = new Date();\n    bravoTeam.lastUpdatedDate = new Date();\n    bravoTeam.createdBy = 0;\n    bravoTeam.lastUpdatedBy = 0;\n    bravoTeam.role = ROLE.READONLY;\n    bravoTeam.assets = assetAry;\n    bravoTeam.users = [savedUser];\n    const savedTeam = await getConnection().getRepository(Team).save(bravoTeam);\n    expect(savedTeam.assets.length).toBe(2);\n    expect(savedTeam.users.length).toBe(1);\n    const request = new MockExpressRequest({\n      body: {\n        name: 'Alpha',\n        organization: savedOrg.id,\n        asset: null,\n        role: ROLE.TESTER,\n        id: savedTeam.id,\n        users: [savedUser, savedUser2],\n        assetIds: [],\n      },\n    });\n    const response = new MockExpressResponse();\n    await updateTeamInfo(request, response);\n    expect(response.statusCode).toBe(200);\n    const updatedTeam = await getConnection()\n      .getRepository(Team)\n      .findOne({ where: { id: savedTeam.id }, relations: ['users', 'assets'] });\n    expect(updatedTeam.name).toBe('Alpha');\n    expect(updatedTeam.role).toBe(ROLE.TESTER);\n    expect(updatedTeam.users.length).toBe(2);\n    expect(updatedTeam.assets.length).toBe(0);\n    const badRequest = new MockExpressRequest({\n      body: {\n        id: savedTeam.id,\n      },\n    });\n    const badResponse = new MockExpressResponse();\n    await updateTeamInfo(badRequest, badResponse);\n    expect(badResponse.statusCode).toBe(400);\n    const badRequest2 = new MockExpressRequest({\n      body: {\n        name: 'Alpha',\n        organization: savedOrg.id,\n        assetIds: [],\n        role: ROLE.TESTER,\n      },\n    });\n    const badResponse2 = new MockExpressResponse();\n    await updateTeamInfo(badRequest2, badResponse2);\n    expect(badResponse2.statusCode).toBe(400);\n    const badRequest3 = new MockExpressRequest({\n      body: {\n        name: 'Alpha',\n        organization: savedOrg.id,\n        assetIds: [],\n        role: ROLE.TESTER,\n        id: 6,\n      },\n    });\n    const badResponse3 = new MockExpressResponse();\n    await updateTeamInfo(badRequest3, badResponse3);\n    expect(badResponse3.statusCode).toBe(404);\n    expect(badResponse2.statusCode).toBe(400);\n    const badRequest4 = new MockExpressRequest({\n      body: {\n        name: 'Alpha',\n        organization: savedOrg.id,\n        assetIds: [],\n        role: 'not a role',\n        id: savedTeam.id,\n      },\n    });\n    const badResponse4 = new MockExpressResponse();\n    await updateTeamInfo(badRequest4, badResponse4);\n    expect(badResponse4.statusCode).toBe(400);\n  });\n\n  test('delete team', async () => {\n    // create org\n    const newOrg: Organization = {\n      id: null,\n      name: 'Test Org',\n      status: status.active,\n      asset: null,\n      teams: null,\n    };\n    const savedOrg = await getConnection()\n      .getRepository(Organization)\n      .save(newOrg);\n    // user 1\n    const teamMember1 = new User();\n    teamMember1.firstName = 'master';\n    teamMember1.lastName = 'chief';\n    teamMember1.email = 'testing1@jest.com';\n    teamMember1.active = true;\n    const uuid = uuidv4();\n    teamMember1.uuid = uuid;\n    teamMember1.password = await generateHash('TangoDown123!!!');\n    const addedUser1 = await getConnection()\n      .getRepository(User)\n      .save(teamMember1);\n    // create team\n    const bravoTeam = new Team();\n    bravoTeam.name = 'Bravo';\n    bravoTeam.organization = savedOrg;\n    bravoTeam.id = null;\n    bravoTeam.createdDate = new Date();\n    bravoTeam.lastUpdatedDate = new Date();\n    bravoTeam.createdBy = 0;\n    bravoTeam.lastUpdatedBy = 0;\n    bravoTeam.role = ROLE.READONLY;\n    const savedTeam = await getConnection().getRepository(Team).save(bravoTeam);\n    const fetchTeam = await getConnection()\n      .getRepository(Team)\n      .findOne({ where: { id: savedTeam.id }, relations: ['users'] });\n    fetchTeam.users.push(addedUser1);\n    const savedTeamWithUser = await getConnection()\n      .getRepository(Team)\n      .save(fetchTeam);\n    const userWithTeam = await getConnection()\n      .getRepository(User)\n      .findOne({ where: { id: addedUser1.id }, relations: ['teams'] });\n    expect(userWithTeam.teams.length).toBe(1);\n    let teams = await getConnection().getRepository(Team).find({});\n    expect(teams.length).toBe(1);\n    const request = new MockExpressRequest({\n      params: {\n        teamId: savedTeamWithUser.id,\n      },\n    });\n    const response = new MockExpressResponse();\n    await deleteTeam(request, response);\n    expect(response.statusCode).toBe(200);\n    teams = await getConnection().getRepository(Team).find({});\n    expect(teams.length).toBe(0);\n    const userNoTeam = await getConnection()\n      .getRepository(User)\n      .findOne({ where: { id: addedUser1.id }, relations: ['teams'] });\n    expect(userNoTeam.teams.length).toBe(0);\n    const badRequest = new MockExpressRequest({\n      params: {},\n    });\n    const badResponse = new MockExpressResponse();\n    await deleteTeam(badRequest, badResponse);\n    expect(badResponse.statusCode).toBe(400);\n    const badRequest2 = new MockExpressRequest({\n      params: {\n        teamId: 34,\n      },\n    });\n    const badResponse2 = new MockExpressResponse();\n    await deleteTeam(badRequest2, badResponse2);\n    expect(badResponse2.statusCode).toBe(404);\n  });\n\n  test('get user teams', async () => {\n    // create org\n    const newOrg: Organization = {\n      id: null,\n      name: 'Test Org',\n      status: status.active,\n      asset: null,\n      teams: null,\n    };\n    const savedOrg = await getConnection()\n      .getRepository(Organization)\n      .save(newOrg);\n    const teamMember1 = new User();\n    teamMember1.firstName = 'master';\n    teamMember1.lastName = 'chief';\n    teamMember1.email = 'testing1@jest.com';\n    teamMember1.active = true;\n    const uuid = uuidv4();\n    teamMember1.uuid = uuid;\n    teamMember1.password = await generateHash('TangoDown123!!!');\n    const addedUser1 = await getConnection()\n      .getRepository(User)\n      .save(teamMember1);\n    // Team 1\n    const bravoTeam = new Team();\n    bravoTeam.name = 'Bravo';\n    bravoTeam.organization = savedOrg;\n    bravoTeam.id = null;\n    bravoTeam.createdDate = new Date();\n    bravoTeam.lastUpdatedDate = new Date();\n    bravoTeam.createdBy = 0;\n    bravoTeam.lastUpdatedBy = 0;\n    bravoTeam.role = ROLE.READONLY;\n    const savedTeam = await getConnection().getRepository(Team).save(bravoTeam);\n    const fetchTeam = await getConnection()\n      .getRepository(Team)\n      .findOne({ where: { id: savedTeam.id }, relations: ['users'] });\n    fetchTeam.users.push(addedUser1);\n    await getConnection().getRepository(Team).save(fetchTeam);\n    // Team 2\n    const alphaTeam = new Team();\n    alphaTeam.name = 'Alpha';\n    alphaTeam.organization = savedOrg;\n    alphaTeam.id = null;\n    alphaTeam.createdDate = new Date();\n    alphaTeam.lastUpdatedDate = new Date();\n    alphaTeam.createdBy = 0;\n    alphaTeam.lastUpdatedBy = 0;\n    alphaTeam.role = ROLE.TESTER;\n    const savedTeam2 = await getConnection()\n      .getRepository(Team)\n      .save(alphaTeam);\n    const fetchTeam2 = await getConnection()\n      .getRepository(Team)\n      .findOne({ where: { id: savedTeam2.id }, relations: ['users'] });\n    fetchTeam2.users.push(addedUser1);\n    await getConnection().getRepository(Team).save(fetchTeam2);\n    const request = new MockExpressRequest({\n      user: addedUser1.id,\n    });\n    const response = new MockExpressResponse();\n    await getMyTeams(request, response);\n    expect(response.statusCode).toBe(200);\n    const userTeams: Team[] = response._getJSON();\n    expect(userTeams[0].name).toBe(bravoTeam.name);\n    expect(userTeams[1].name).toBe(alphaTeam.name);\n  });\n\n  test('add asset to team', async () => {\n    // create org\n    const newOrg: Organization = {\n      id: null,\n      name: 'Test Org',\n      status: status.active,\n      asset: null,\n      teams: null,\n    };\n    const savedOrg = await getConnection()\n      .getRepository(Organization)\n      .save(newOrg);\n    const assessments: Assessment[] = [];\n    const asset1: Asset = {\n      id: null,\n      name: 'testAsset1',\n      status: status.active,\n      organization: savedOrg,\n      assessment: assessments,\n      jira: null,\n      teams: null,\n    };\n    const savedAsset1 = await getConnection().getRepository(Asset).save(asset1);\n    const asset2: Asset = {\n      id: null,\n      name: 'testAsset2',\n      status: status.active,\n      organization: savedOrg,\n      assessment: assessments,\n      jira: null,\n      teams: null,\n    };\n    const savedAsset2 = await getConnection().getRepository(Asset).save(asset2);\n    const asset3: Asset = {\n      id: null,\n      name: 'testAsset3',\n      status: status.active,\n      organization: savedOrg,\n      assessment: assessments,\n      jira: null,\n      teams: null,\n    };\n    const savedAsset3 = await getConnection().getRepository(Asset).save(asset3);\n    // Team 1\n    const team1 = new Team();\n    team1.name = 'Bravo';\n    team1.organization = savedOrg;\n    team1.id = null;\n    team1.createdDate = new Date();\n    team1.lastUpdatedDate = new Date();\n    team1.createdBy = 0;\n    team1.lastUpdatedBy = 0;\n    team1.role = ROLE.READONLY;\n    const savedTeam = await getConnection().getRepository(Team).save(team1);\n    const request = new MockExpressRequest({\n      body: {\n        assetIds: [savedAsset1.id, savedAsset2.id, savedAsset3.id],\n        teamId: savedTeam.id,\n      },\n    });\n    const response = new MockExpressResponse();\n    await addTeamAsset(request, response);\n    expect(response.statusCode).toBe(200);\n    const fetchTeam = await getConnection()\n      .getRepository(Team)\n      .findOne({ where: { id: savedTeam.id }, relations: ['assets'] });\n    expect(fetchTeam.assets.length).toBe(3);\n    const badRequest = new MockExpressRequest({\n      body: {\n        assetIds: [savedAsset1.id, savedAsset2.id, savedAsset3.id],\n      },\n    });\n    const badResponse = new MockExpressResponse();\n    await addTeamAsset(badRequest, badResponse);\n    expect(badResponse.statusCode).toBe(400);\n    const badRequest2 = new MockExpressRequest({\n      body: {\n        assetIds: [savedAsset1.id, savedAsset2.id, savedAsset3.id],\n        teamId: 5,\n      },\n    });\n    const badResponse2 = new MockExpressResponse();\n    await addTeamAsset(badRequest2, badResponse2);\n    expect(badResponse2.statusCode).toBe(404);\n    const badRequest3 = new MockExpressRequest({\n      body: {\n        assetIds: [savedAsset1.id, savedAsset2.id, 5],\n        teamId: savedTeam.id,\n      },\n    });\n    const badResponse3 = new MockExpressResponse();\n    await addTeamAsset(badRequest3, badResponse3);\n    expect(badResponse3.statusCode).toBe(401);\n  });\n\n  test('remove asset from team', async () => {\n    // create org\n    const newOrg: Organization = {\n      id: null,\n      name: 'Test Org',\n      status: status.active,\n      asset: null,\n      teams: null,\n    };\n    const savedOrg = await getConnection()\n      .getRepository(Organization)\n      .save(newOrg);\n    const assessments: Assessment[] = [];\n    const asset1: Asset = {\n      id: null,\n      name: 'testAsset1',\n      status: status.active,\n      organization: savedOrg,\n      assessment: assessments,\n      jira: null,\n      teams: null,\n    };\n    const savedAsset1 = await getConnection().getRepository(Asset).save(asset1);\n    const asset2: Asset = {\n      id: null,\n      name: 'testAsset2',\n      status: status.active,\n      organization: savedOrg,\n      assessment: assessments,\n      jira: null,\n      teams: null,\n    };\n    const savedAsset2 = await getConnection().getRepository(Asset).save(asset2);\n    const asset3: Asset = {\n      id: null,\n      name: 'testAsset3',\n      status: status.active,\n      organization: savedOrg,\n      assessment: assessments,\n      jira: null,\n      teams: null,\n    };\n    const savedAsset3 = await getConnection().getRepository(Asset).save(asset3);\n    // Team 1\n    const team1 = new Team();\n    team1.name = 'Bravo';\n    team1.organization = savedOrg;\n    team1.id = null;\n    team1.createdDate = new Date();\n    team1.lastUpdatedDate = new Date();\n    team1.createdBy = 0;\n    team1.lastUpdatedBy = 0;\n    team1.role = ROLE.READONLY;\n    const savedTeam = await getConnection().getRepository(Team).save(team1);\n    const fetchTeam = await getConnection()\n      .getRepository(Team)\n      .findOne({ where: { id: savedTeam.id }, relations: ['assets'] });\n    fetchTeam.assets.push(savedAsset1, savedAsset2, savedAsset3);\n    await getConnection().getRepository(Team).save(fetchTeam);\n    const fetchTeamWithAssets = await getConnection()\n      .getRepository(Team)\n      .findOne({ where: { id: savedTeam.id }, relations: ['assets'] });\n    expect(fetchTeamWithAssets.assets.length).toBe(3);\n    const request = new MockExpressRequest({\n      body: {\n        assetIds: [savedAsset1.id, savedAsset2.id],\n        teamId: savedTeam.id,\n      },\n    });\n    const response = new MockExpressResponse();\n    await removeTeamAsset(request, response);\n    const fetchTeamWithRemovedAsset = await getConnection()\n      .getRepository(Team)\n      .findOne({ where: { id: savedTeam.id }, relations: ['assets'] });\n    expect(fetchTeamWithRemovedAsset.assets.length).toBe(1);\n\n    const badRequest = new MockExpressRequest({\n      body: {\n        assetIds: [savedAsset1.id, savedAsset2.id, savedAsset3.id],\n      },\n    });\n    const badResponse = new MockExpressResponse();\n    await removeTeamAsset(badRequest, badResponse);\n    expect(badResponse.statusCode).toBe(400);\n    const badRequest2 = new MockExpressRequest({\n      body: {\n        assetIds: [savedAsset1.id, savedAsset2.id, savedAsset3.id],\n        teamId: 5,\n      },\n    });\n    const badResponse2 = new MockExpressResponse();\n    await removeTeamAsset(badRequest2, badResponse2);\n    expect(badResponse2.statusCode).toBe(404);\n    const badRequest3 = new MockExpressRequest({\n      body: {\n        assetIds: [savedAsset1.id, savedAsset2.id, 5],\n        teamId: savedTeam.id,\n      },\n    });\n    const badResponse3 = new MockExpressResponse();\n    await removeTeamAsset(badRequest3, badResponse3);\n    expect(badResponse3.statusCode).toBe(401);\n  });\n\n  test('get all teams', async () => {\n    const request = new MockExpressRequest({});\n    const response = new MockExpressResponse();\n    await getAllTeams(request, response);\n    const fetchedTeams: Team[] = response._getJSON();\n    expect(fetchedTeams.length).toBe(0);\n  });\n});\n"
  },
  {
    "path": "src/routes/team.controller.ts",
    "content": "import { getRepository, In } from 'typeorm';\nimport { Team } from '../entity/Team';\nimport { User } from '../entity/User';\nimport { Response, Request } from 'express';\nimport { validate } from 'class-validator';\nimport { UserRequest } from '../interfaces/user-request.interface';\nimport { Asset } from '../entity/Asset';\nimport { Organization } from '../entity/Organization';\nimport { ROLE } from '../enums/roles-enum';\nimport { AppDataSource } from '../data-source';\n\nexport const getAllTeams = async (req: Request, res: Response) => {\n  try {\n    const teamRepository = AppDataSource.getRepository(Team);\n    const teams = await teamRepository\n      .createQueryBuilder('team')\n      .leftJoinAndSelect('team.users', 'users')\n      .leftJoinAndSelect('team.assets', 'assets')\n      .leftJoinAndSelect('team.organization', 'organization')\n      .select([\n        'team',\n        'assets',\n        'organization',\n        'users.firstName',\n        'users.lastName',\n        'users.title',\n        'users.id',\n      ])\n      .getMany();\n      \n    return res.status(200).json(teams);\n  } catch (error) {\n    console.error('Error getting all teams:', error);\n    return res.status(500).json('An error occurred while fetching teams');\n  }\n};\n\nexport const fetchAssets = async (assetIds: number[]) => {\n  try {\n    const assetRepository = AppDataSource.getRepository(Asset);\n    const assets = await assetRepository.find({\n      where: { id: In(assetIds) }\n    });\n    \n    return assets;\n  } catch (error) {\n    console.error('Error fetching assets:', error);\n    return [];\n  }\n};\n\nexport const fetchUsers = async (userIds: number[]) => {\n  try {\n    const userRepository = AppDataSource.getRepository(User);\n    const users = await userRepository.find({\n      where: { \n        id: In(userIds),\n        active: true \n      }\n    });\n    \n    return users;\n  } catch (error) {\n    console.error('Error fetching users:', error);\n    return [];\n  }\n};\n\nexport const fetchUsersAndUpdateTeam = async (\n  userIds: number[],\n  teamUsers: User[]\n) => {\n  try {\n    if (!userIds) {\n      return [];\n    } else {\n      const newUsers = await fetchUsers(userIds);\n      return newUsers;\n    }\n  } catch (error) {\n    console.error('Error updating team users:', error);\n    return teamUsers || [];\n  }\n};\n\nexport const fetchAssetsAndUpdateTeam = async (\n  assetIds: number[],\n  teamAssets: Asset[]\n) => {\n  try {\n    const newAssets = await fetchAssets(assetIds);\n    return newAssets;\n  } catch (error) {\n    console.error('Error updating team assets:', error);\n    return teamAssets || [];\n  }\n};\n\nexport const getTeamById = async (req: UserRequest, res: Response) => {\n  try {\n    const { teamId } = req.params;\n    \n    if (isNaN(+teamId)) {\n      return res.status(400).json('Invalid Team ID');\n    }\n    \n    const teamRepository = AppDataSource.getRepository(Team);\n    const fetchedTeam = await teamRepository\n      .createQueryBuilder('team')\n      .leftJoinAndSelect('team.users', 'users')\n      .leftJoinAndSelect('team.assets', 'assets')\n      .leftJoinAndSelect('assets.jira', 'jira')\n      .leftJoinAndSelect('team.organization', 'organization')\n      .where('team.id = :teamId', { teamId })\n      .select([\n        'team',\n        'assets',\n        'jira.id',\n        'organization',\n        'users.firstName',\n        'users.lastName',\n        'users.title',\n        'users.id',\n      ])\n      .getOne();\n      \n    if (!fetchedTeam) {\n      return res.status(404).json('Team not found');\n    } else {\n      return res.status(200).json(fetchedTeam);\n    }\n  } catch (error) {\n    console.error('Error getting team by ID:', error);\n    return res.status(500).json('An error occurred while fetching the team');\n  }\n};\n\nexport const createTeam = async (req: UserRequest, res: Response) => {\n  try {\n    const { name, organization, role, assetIds, users } = req.body;\n    const newTeam = new Team();\n    let fetchedOrg: Organization;\n    \n    const organizationRepository = AppDataSource.getRepository(Organization);\n    \n    if (role !== ROLE.ADMIN) {\n      fetchedOrg = await organizationRepository.findOne({\n        where: { id: organization },\n        relations: ['teams']\n      });\n      \n      if (!fetchedOrg) {\n        return res.status(404).json('Organization not found');\n      }\n    } else {\n      fetchedOrg = null;\n    }\n    \n    newTeam.id = null;\n    newTeam.name = name;\n    newTeam.organization = fetchedOrg;\n    newTeam.role = role;\n    newTeam.createdBy = +req.user;\n    newTeam.createdDate = new Date();\n    newTeam.lastUpdatedBy = +req.user;\n    newTeam.lastUpdatedDate = new Date();\n    \n    if (users && users.length) {\n      newTeam.users = await fetchUsers(users);\n    }\n    \n    if (assetIds && assetIds.length) {\n      newTeam.assets = await fetchAssets(assetIds);\n    }\n    \n    const errors = await validate(newTeam);\n    if (errors.length > 0) {\n      console.error('Validation errors:', errors);\n      return res.status(400).json('Submitted Team is Invalid');\n    } else {\n      const teamRepository = AppDataSource.getRepository(Team);\n      await teamRepository.save(newTeam);\n      return res.status(200).json('The team has been successfully created');\n    }\n  } catch (error) {\n    console.error('Error creating team:', error);\n    return res.status(500).json('An error occurred while creating the team');\n  }\n};\n\nexport const addTeamMember = async (req: UserRequest, res: Response) => {\n  try {\n    const { userIds, teamId } = req.body;\n    \n    // Verify team exists\n    if (!teamId) {\n      return res.status(400).json('Invalid Team ID');\n    }\n    \n    const teamRepository = AppDataSource.getRepository(Team);\n    const team = await teamRepository.findOne({\n      where: { id: teamId },\n      relations: ['users']\n    });\n    \n    if (!team) {\n      return res.status(404).json(`A Team with ID ${teamId} does not exist`);\n    }\n    \n    // Verify each user ID links to a valid user\n    team.users = await fetchUsers(userIds);\n    \n    // Save the team once valid users have been pushed to team\n    await teamRepository.save(team);\n    return res.status(200).json('Team membership has been successfully updated');\n  } catch (error) {\n    console.error('Error adding team member:', error);\n    return res.status(500).json('An error occurred while adding team members');\n  }\n};\n\nexport const removeTeamMember = async (req: UserRequest, res: Response) => {\n  try {\n    const userIds = req.body.userIds;\n    const teamId = req.body.teamId;\n    \n    // Verify team exists\n    if (!teamId) {\n      return res.status(400).json('Invalid Team ID');\n    }\n    \n    const teamRepository = AppDataSource.getRepository(Team);\n    const userRepository = AppDataSource.getRepository(User);\n    \n    const team = await teamRepository.findOne({\n      where: { id: teamId },\n      relations: ['users']\n    });\n    \n    if (!team) {\n      return res.status(404).json(`A Team with ID ${teamId} does not exist`);\n    }\n    \n    // Verify each user ID links to a valid user\n    for (const userId of userIds) {\n      const user = await userRepository.findOne({ where: { id: userId } });\n      \n      if (!user) {\n        return res.status(404).json(`A User with ID ${userId} does not exist`);\n      } else {\n        team.users = team.users.filter((userIdx) => userIdx.id !== user.id);\n      }\n    }\n    \n    // Save the team once valid users have been pushed to team\n    await teamRepository.save(team);\n    return res.status(200).json('Team membership has been successfully updated');\n  } catch (error) {\n    console.error('Error removing team member:', error);\n    return res.status(500).json('An error occurred while removing team members');\n  }\n};\n\nexport const updateTeamInfo = async (req: Request, res: Response) => {\n  try {\n    const { name, role, id, users } = req.body;\n    let organization: Organization = req.body.organization;\n    let assetIds: number[] = req.body.assetIds;\n    \n    if (!(name || organization || role)) {\n      return res.status(400).json('Team is invalid');\n    }\n    \n    if (!id) {\n      return res.status(400).json('The Team ID is invalid');\n    }\n    \n    const teamRepository = AppDataSource.getRepository(Team);\n    const organizationRepository = AppDataSource.getRepository(Organization);\n    \n    const team = await teamRepository\n      .createQueryBuilder('team')\n      .leftJoinAndSelect('team.users', 'users')\n      .leftJoinAndSelect('team.assets', 'assets')\n      .leftJoinAndSelect('team.organization', 'organization')\n      .leftJoinAndSelect('organization.asset', 'orgAssets')\n      .where('team.id = :teamId', { teamId: id })\n      .select(['team', 'users', 'assets', 'organization', 'orgAssets'])\n      .getOne();\n      \n    if (!team) {\n      return res.status(404).json(`A Team with ID ${id} does not exist`);\n    }\n    \n    // If the incoming organization has changed\n    // Remove all previous asset associations\n    if (role !== ROLE.ADMIN) {\n      organization = await organizationRepository.findOne({\n        where: { id: +organization },\n        relations: ['teams']\n      });\n      \n      if (!organization) {\n        return res.status(404).json('Organization not found');\n      }\n      \n      if (team.organization && organization && +organization.id !== +team.organization.id) {\n        for (const orgAsset of team.organization.asset) {\n          assetIds = assetIds.filter((x) => x !== orgAsset.id);\n        }\n      }\n    } else {\n      organization = null;\n    }\n    \n    team.users = await fetchUsersAndUpdateTeam(users, team.users);\n    team.assets = await fetchAssetsAndUpdateTeam(assetIds, team.assets);\n    team.name = name;\n    team.organization = organization;\n    team.role = role;\n    \n    const errors = await validate(team);\n    if (errors.length > 0) {\n      console.error('Validation errors:', errors);\n      return res.status(400).json('Submitted Team is Invalid');\n    } else {\n      await teamRepository.save(team);\n      return res.status(200).json('Team has been patched successfully');\n    }\n  } catch (error) {\n    console.error('Error updating team:', error);\n    return res.status(500).json('An error occurred while updating the team');\n  }\n};\n\nexport const deleteTeam = async (req: Request, res: Response) => {\n  try {\n    const { teamId } = req.params;\n    \n    if (!teamId) {\n      return res.status(400).json('Invalid Team ID');\n    }\n    \n    const teamRepository = AppDataSource.getRepository(Team);\n    const team = await teamRepository.findOne({ where: { id: +teamId } });\n    \n    if (!team) {\n      return res.status(404).json(`A Team with ID ${teamId} does not exist`);\n    }\n    \n    await teamRepository.remove(team);\n    return res\n      .status(200)\n      .json(`The Team ${team.name} has been successfully deleted`);\n  } catch (error) {\n    console.error('Error deleting team:', error);\n    return res.status(500).json('An error occurred while deleting the team');\n  }\n};\n\nexport const getMyTeams = async (req: UserRequest, res: Response) => {\n  try {\n    const userRepository = AppDataSource.getRepository(User);\n    const user = await userRepository.findOne({\n      where: { id: +req.user },\n      relations: ['teams']\n    });\n    \n    if (!user) {\n      return res.status(404).json('User not found');\n    }\n    \n    return res.status(200).json(user.teams);\n  } catch (error) {\n    console.error('Error getting my teams:', error);\n    return res.status(500).json('An error occurred while fetching your teams');\n  }\n};\n\nexport const addTeamAsset = async (req: Request, res: Response) => {\n  try {\n    const { assetIds, teamId } = req.body;\n    \n    if (!teamId) {\n      return res.status(400).json('Invalid Team ID');\n    }\n    \n    const teamRepository = AppDataSource.getRepository(Team);\n    const assetRepository = AppDataSource.getRepository(Asset);\n    const organizationRepository = AppDataSource.getRepository(Organization);\n    \n    const team = await teamRepository.findOne({\n      where: { id: teamId },\n      relations: ['assets', 'organization']\n    });\n    \n    if (!team) {\n      return res.status(404).json(`A Team with ID ${teamId} does not exist`);\n    }\n    \n    const org = await organizationRepository.findOne({\n      where: { id: team.organization.id },\n      relations: ['asset']\n    });\n    \n    const orgAssets = org.asset.map((asset) => asset.id);\n    \n    // Verify each asset ID links to a valid asset\n    for (const assetId of assetIds) {\n      // Verify we are only associating assets of that organization\n      if (!orgAssets.includes(assetId)) {\n        return res\n          .status(401)\n          .json(`The Asset with ID ${assetId} is unauthorized`);\n      }\n      \n      const asset = await assetRepository.findOne({ where: { id: assetId } });\n      \n      if (!asset) {\n        return res.status(404).json(`Asset with ID ${assetId} not found`);\n      }\n      \n      team.assets.push(asset);\n    }\n    \n    // Save the team once valid assets have been pushed to team\n    await teamRepository.save(team);\n    return res.status(200).json('Team Assets has been successfully updated');\n  } catch (error) {\n    console.error('Error adding team asset:', error);\n    return res.status(500).json('An error occurred while adding team assets');\n  }\n};\n\nexport const removeTeamAsset = async (req: Request, res: Response) => {\n  try {\n    const { assetIds, teamId } = req.body;\n    \n    if (!teamId) {\n      return res.status(400).json('Invalid Team ID');\n    }\n    \n    const teamRepository = AppDataSource.getRepository(Team);\n    const assetRepository = AppDataSource.getRepository(Asset);\n    const organizationRepository = AppDataSource.getRepository(Organization);\n    \n    const team = await teamRepository.findOne({\n      where: { id: teamId },\n      relations: ['assets', 'organization']\n    });\n    \n    if (!team) {\n      return res.status(404).json(`A Team with ID ${teamId} does not exist`);\n    }\n    \n    const org = await organizationRepository.findOne({\n      where: { id: team.organization.id },\n      relations: ['asset']\n    });\n    \n    const orgAssets = org.asset.map((asset) => asset.id);\n    \n    // Verify each asset ID links to a valid asset\n    for (const assetId of assetIds) {\n      // Verify we are only associating assets of that organization\n      if (!orgAssets.includes(assetId)) {\n        return res\n          .status(401)\n          .json(`The Asset with ID ${assetId} is unauthorized`);\n      }\n      \n      const asset = await assetRepository.findOne({ where: { id: assetId } });\n      \n      if (!asset) {\n        return res.status(404).json(`Asset with ID ${assetId} not found`);\n      }\n      \n      team.assets = team.assets.filter((assetIdx) => assetIdx.id !== asset.id);\n    }\n    \n    // Save the team once assets have been removed\n    await teamRepository.save(team);\n    return res.status(200).json('Team Assets has been successfully updated');\n  } catch (error) {\n    console.error('Error removing team asset:', error);\n    return res.status(500).json('An error occurred while removing team assets');\n  }\n};"
  },
  {
    "path": "src/routes/user.controller.spec.ts",
    "content": "import { createConnection, getConnection } from 'typeorm';\nimport * as userController from './user.controller';\nimport { User } from '../entity/User';\nimport { v4 as uuidv4 } from 'uuid';\nimport { generateHash } from '../utilities/password.utility';\nimport { Config } from '../entity/Config';\nimport MockExpressResponse = require('mock-express-response');\nimport MockExpressRequest = require('mock-express-request');\nimport { Team } from '../entity/Team';\nimport { Organization } from '../entity/Organization';\nimport { Asset } from '../entity/Asset';\nimport { Assessment } from '../entity/Assessment';\nimport { Jira } from '../entity/Jira';\nimport { ProblemLocation } from '../entity/ProblemLocation';\nimport { ReportAudit } from '../entity/ReportAudit';\nimport { Resource } from '../entity/Resource';\nimport { Vulnerability } from '../entity/Vulnerability';\nimport { File } from '../entity/File';\nimport { ApiKey } from '../entity/ApiKey';\n\ndescribe('User Controller', () => {\n  beforeEach(() => {\n    return createConnection({\n      type: 'sqlite',\n      database: ':memory:',\n      dropSchema: true,\n      entities: [\n        Config,\n        User,\n        Team,\n        Organization,\n        Asset,\n        Assessment,\n        Vulnerability,\n        ProblemLocation,\n        ReportAudit,\n        Resource,\n        Jira,\n        File,\n        ApiKey,\n      ],\n      synchronize: true,\n      logging: false,\n    });\n  });\n\n  afterEach(() => {\n    const conn = getConnection();\n    return conn.close();\n  });\n  test('invite user fail', async () => {\n    const config = new Config();\n    config.fromEmail = 'testingDude@jest.com';\n    config.companyName = 'Test';\n    config.fromEmailPassword = null;\n    config.id = 1;\n    await getConnection().getRepository(Config).insert(config);\n    const req = new MockExpressRequest();\n    const res = new MockExpressResponse();\n    req.body = {\n      email: 'testing@jest.com',\n    };\n    await userController.invite(req, res);\n    expect(res.statusCode).toBe(400);\n  });\n  test('invite user failure missing email', async () => {\n    const config = new Config();\n    config.fromEmail = 'testingDude@jest.com';\n    config.companyName = 'Test';\n    config.fromEmailPassword = null;\n    config.id = 1;\n    await getConnection().getRepository(Config).insert(config);\n    const req = new MockExpressRequest();\n    req.body = {};\n    const res = new MockExpressResponse();\n    await userController.invite(req, res);\n    expect(res.statusCode).toBe(400);\n  });\n  test('invite user failure user already exists', async () => {\n    const config = new Config();\n    config.fromEmail = 'testingDude@jest.com';\n    config.companyName = 'Test';\n    config.fromEmailPassword = null;\n    config.id = 1;\n    await getConnection().getRepository(Config).insert(config);\n    const req = new MockExpressRequest();\n    const res = new MockExpressResponse();\n    req.body = {\n      email: 'testing@jest.com',\n    };\n    const existUser = new User();\n    existUser.firstName = 'master';\n    existUser.lastName = 'chief';\n    existUser.email = 'testing@jest.com';\n    existUser.active = true;\n    await getConnection().getRepository(User).insert(existUser);\n    await userController.invite(req, res);\n    expect(res.statusCode).toBe(400);\n  });\n  test('register user failure missing first name', async () => {\n    const req = new MockExpressRequest();\n    const res = new MockExpressResponse();\n    req.body = {\n      email: 'testing@jest.com',\n      title: 'Spartan 117',\n      lastName: 'Chief',\n      password: 'notSecure123',\n    };\n    await userController.register(req, res);\n    expect(res.statusCode).toBe(400);\n  });\n  test('register user failure missing last name', async () => {\n    const req = new MockExpressRequest();\n    req.body = {\n      email: 'testing@jest.com',\n      title: 'Spartan 117',\n      firstName: 'Master',\n      password: 'notSecure123',\n    };\n    const res = new MockExpressResponse();\n    await userController.register(req, res);\n    expect(res.statusCode).toBe(400);\n  });\n  test('register user failure missing title', async () => {\n    const req = new MockExpressRequest();\n    req.body = {\n      email: 'testing@jest.com',\n      lastName: 'Chief',\n      firstName: 'Master',\n      password: 'notSecure123',\n    };\n    const res = new MockExpressResponse();\n    await userController.register(req, res);\n    expect(res.statusCode).toBe(400);\n  });\n  test('register user failure passwords do not match', async () => {\n    const req = new MockExpressRequest();\n    req.body = {\n      email: 'testing@jest.com',\n      lastName: 'Chief',\n      firstName: 'Master',\n      password: 'notSecure123',\n      confirmPassword: 'notSecureAbc',\n      title: 'Spartan 117',\n    };\n    const res = new MockExpressResponse();\n    await userController.register(req, res);\n    expect(res.statusCode).toBe(400);\n  });\n  test('register user failure password validation', async () => {\n    const req = new MockExpressRequest();\n    req.body = {\n      email: 'testing@jest.com',\n      lastName: 'Chief',\n      firstName: 'Master',\n      password: '123',\n      confirmPassword: '123',\n      title: 'Spartan 117',\n    };\n    const res = new MockExpressResponse();\n    await userController.register(req, res);\n    expect(res.statusCode).toBe(400);\n  });\n  test('register user success', async () => {\n    const config = new Config();\n    config.fromEmail = 'testingDude@jest.com';\n    config.companyName = 'Test';\n    config.fromEmailPassword = null;\n    config.id = 1;\n    const user = new User();\n    user.active = false;\n    user.uuid = uuidv4();\n    user.email = 'testing@jest.com';\n    user.newEmail = null;\n    await getConnection().getRepository(User).save(user);\n    await getConnection().getRepository(Config).insert(config);\n    const req = new MockExpressRequest();\n    const invReq = new MockExpressRequest();\n    invReq.body = {\n      email: 'testing@jest.com',\n    };\n    const res = new MockExpressResponse();\n    await userController.invite(invReq, res);\n    const invUser = await getConnection()\n      .getRepository(User)\n      .find({ where: { email: 'testing@jest.com' } });\n    req.body = {\n      email: invUser[0].email,\n      lastName: 'Chief',\n      firstName: 'Master',\n      password: '&3x1GqpeFO61*HJ',\n      confirmPassword: '&3x1GqpeFO61*HJ',\n      title: 'Spartan 117',\n      uuid: invUser[0].uuid,\n    };\n    const res2 = new MockExpressResponse();\n    await userController.register(req, res2);\n    expect(res2.statusCode).toBe(200);\n  });\n  test('verify user failure no uuid', async () => {\n    const mRequest = () => {\n      const req = {\n        params: {},\n        user: Function,\n      };\n      req.user = jest.fn().mockReturnValue(req);\n      return req;\n    };\n    const vReq = new MockExpressRequest();\n    const res = new MockExpressResponse();\n    vReq.params = {};\n    await userController.verify(vReq, res);\n    expect(res.statusCode).toBe(400);\n  });\n  test('verify user success', async () => {\n    const res = new MockExpressResponse();\n    const mRequest = () => {\n      const r = {\n        params: {},\n        user: Function,\n      };\n      r.user = jest.fn().mockReturnValue(r);\n      return r;\n    };\n    const existUser = new User();\n    existUser.firstName = 'master';\n    existUser.lastName = 'chief';\n    existUser.email = 'testing@jest.com';\n    existUser.active = false;\n    const uuid = uuidv4();\n    existUser.uuid = uuid;\n    await getConnection().getRepository(User).insert(existUser);\n    const verifyReq = new MockExpressRequest();\n    verifyReq.params = {\n      uuid,\n    };\n    await userController.verify(verifyReq, res);\n    expect(res.statusCode).toBe(200);\n  });\n  test('verify user failure user does not exist', async () => {\n    const res = new MockExpressResponse();\n    const mRequest = () => {\n      const r = {\n        params: {},\n        user: Function,\n      };\n      r.user = jest.fn().mockReturnValue(r);\n      return r;\n    };\n    const existUser = new User();\n    existUser.firstName = 'master';\n    existUser.lastName = 'chief';\n    existUser.email = 'testing@jest.com';\n    existUser.active = false;\n    const uuid = uuidv4();\n    existUser.uuid = uuid;\n    await getConnection().getRepository(User).insert(existUser);\n    const verifyReq = new MockExpressRequest();\n    const uuid2 = uuidv4();\n    verifyReq.params = {\n      uuid: uuid2,\n    };\n    await userController.verify(verifyReq, res);\n    expect(res.statusCode).toBe(400);\n  });\n  test('Update user password failure passwords do not match', async () => {\n    const req = new MockExpressRequest();\n    const res = new MockExpressResponse();\n    req.body = {\n      oldPassword: 'fakePassword',\n      newPassword: 'fakePassword2',\n      confirmNewPassword: 'fakePasswordDifferent',\n    };\n    await userController.updateUserPassword(req, res);\n    expect(res.statusCode).toBe(400);\n  });\n  test('Update user password failure same password', async () => {\n    const req = new MockExpressRequest();\n    const res = new MockExpressResponse();\n    req.body = {\n      oldPassword: 'fakePassword',\n      newPassword: 'fakePassword',\n      confirmNewPassword: 'fakePassword',\n    };\n    await userController.updateUserPassword(req, res);\n    expect(res.statusCode).toBe(400);\n  });\n  test('Update user password failure password validation', async () => {\n    const req = new MockExpressRequest();\n    const res = new MockExpressResponse();\n    req.body = {\n      oldPassword: '123',\n      newPassword: '234',\n      confirmNewPassword: '234',\n    };\n    await userController.updateUserPassword(req, res);\n    expect(res.statusCode).toBe(400);\n  });\n  test('Update user password success', async () => {\n    const req = new MockExpressRequest();\n    const res = new MockExpressResponse();\n    req.body = {\n      oldPassword: 'TangoDown123!!!',\n      newPassword: '9z4O4^HSvHkt3iU',\n      confirmNewPassword: '9z4O4^HSvHkt3iU',\n    };\n    const existUser = new User();\n    existUser.firstName = 'master';\n    existUser.lastName = 'chief';\n    existUser.email = 'testing@jest.com';\n    existUser.active = true;\n    const uuid = uuidv4();\n    existUser.uuid = uuid;\n    existUser.password = await generateHash('TangoDown123!!!');\n    const userr = await getConnection().getRepository(User).insert(existUser);\n    req.user = userr.identifiers[0].id;\n    await userController.updateUserPassword(req, res);\n    expect(res.statusCode).toBe(200);\n  });\n  test('Update user failure user does not exist', async () => {\n    const req = new MockExpressRequest();\n    const res = new MockExpressResponse();\n    req.body = {\n      oldPassword: 'TangoDown123!!!',\n      newPassword: '9z4O4^HSvHkt3iU',\n      confirmNewPassword: '9z4O4^HSvHkt3iU',\n    };\n    const existUser = new User();\n    existUser.firstName = 'master';\n    existUser.lastName = 'chief';\n    existUser.email = 'testing@jest.com';\n    existUser.active = true;\n    const uuid = uuidv4();\n    existUser.uuid = uuid;\n    existUser.password = await generateHash('TangoDown123!!!');\n    const userr = await getConnection().getRepository(User).insert(existUser);\n    userr.identifiers[0].id = 117;\n    const x: any = 2;\n    req.user = x;\n    await userController.updateUserPassword(req, res);\n    expect(res.statusCode).toBe(400);\n  });\n  test('patch user success', async () => {\n    const req = new MockExpressRequest();\n    const res = new MockExpressResponse();\n    req.body = {\n      firstName: 'Master',\n      lastName: 'Chief',\n      title: 'Spartan 117',\n    };\n    const existUser = new User();\n    existUser.firstName = 'Cortana';\n    existUser.lastName = 'AI';\n    existUser.email = 'testing@jest.com';\n    existUser.title = 'A.I.';\n    existUser.active = true;\n    const uuid = uuidv4();\n    existUser.uuid = uuid;\n    existUser.password = await generateHash('TangoDown123!!!');\n    existUser.newEmail = null;\n    const userr = await getConnection().getRepository(User).insert(existUser);\n    req.user = userr.identifiers[0].id;\n    await userController.patch(req, res);\n    expect(res.statusCode).toBe(200);\n  });\n  test('get user failure user does not exist', async () => {\n    const req = new MockExpressRequest();\n    const res = new MockExpressResponse();\n    const existUser = new User();\n    existUser.firstName = 'Cortana';\n    existUser.lastName = 'AI';\n    existUser.email = 'testing@jest.com';\n    existUser.title = 'A.I.';\n    existUser.active = true;\n    const uuid = uuidv4();\n    existUser.uuid = uuid;\n    existUser.password = await generateHash('TangoDown123!!!');\n    await getConnection().getRepository(User).insert(existUser);\n    const x: any = 2;\n    req.user = x;\n    await userController.getUser(req, res);\n    expect(res.statusCode).toBe(404);\n  });\n  test('get user success', async () => {\n    const req = new MockExpressRequest();\n    const res = new MockExpressResponse();\n    const existUser = new User();\n    existUser.firstName = 'Cortana';\n    existUser.lastName = 'AI';\n    existUser.email = 'testing@jest.com';\n    existUser.title = 'A.I.';\n    existUser.active = true;\n    const uuid = uuidv4();\n    existUser.uuid = uuid;\n    existUser.password = await generateHash('TangoDown123!!!');\n    const userr = await getConnection().getRepository(User).insert(existUser);\n    req.user = userr.identifiers[0].id;\n    await userController.getUser(req, res);\n    expect(res.statusCode).toBe(200);\n  });\n  test('get users success', async () => {\n    const req = new MockExpressRequest();\n    const res = new MockExpressResponse();\n    await userController.getUsers(req, res);\n    expect(res.statusCode).toBe(200);\n  });\n  test('get users by id success', async () => {\n    const req = new MockExpressRequest();\n    const res = new MockExpressResponse();\n    const user1 = new User();\n    user1.firstName = 'Cortana';\n    user1.lastName = 'AI';\n    user1.email = 'testing1@jest.com';\n    user1.title = 'A.I.';\n    user1.active = true;\n    const uuid = uuidv4();\n    user1.uuid = uuid;\n    user1.password = await generateHash('TangoDown123!!!');\n    const user2 = new User();\n    user2.firstName = 'Cortana';\n    user2.lastName = 'AI';\n    user2.email = 'testing2@jest.com';\n    user2.title = 'A.I.';\n    user2.active = true;\n    const uuid2 = uuidv4();\n    user2.uuid = uuid2;\n    user2.password = await generateHash('TangoDown123!!!');\n    const usrIdAry: number[] = [];\n    const insUser1 = await getConnection().getRepository(User).insert(user1);\n    usrIdAry.push(insUser1.identifiers[0].id);\n    const insUser2 = await getConnection().getRepository(User).insert(user2);\n    usrIdAry.push(insUser2.identifiers[0].id);\n    const retUserAry = await userController.getUsersById(usrIdAry);\n    expect(retUserAry).toHaveLength(2);\n  });\n  test('Update user email failure emails do not match', async () => {\n    const req = new MockExpressRequest();\n    const res = new MockExpressResponse();\n    req.body = {\n      email: '123',\n      newEmail: '234',\n    };\n    await userController.updateUserEmail(req, res);\n    expect(res.statusCode).toBe(400);\n  });\n  test('Update user email failure in progress email update request', async () => {\n    const user1 = new User();\n    user1.firstName = 'Cortana';\n    user1.lastName = 'AI';\n    user1.email = 'testing1@jest.com';\n    user1.newEmail = 'alreadySent@lol.com';\n    user1.title = 'A.I.';\n    user1.active = true;\n    const uuid = uuidv4();\n    user1.uuid = uuid;\n    user1.password = await generateHash('TangoDown123!!!');\n    const insUser1 = await getConnection().getRepository(User).insert(user1);\n    const req = new MockExpressRequest();\n    const res = new MockExpressResponse();\n    req.body = {\n      email: 'test@jest.com',\n      newEmail: 'test@jest.com',\n    };\n    req.user = 1;\n    await userController.updateUserEmail(req, res);\n    expect(res.statusCode).toBe(400);\n  });\n  test('Update user email failure email already exists', async () => {\n    const user1 = new User();\n    user1.firstName = 'Cortana';\n    user1.lastName = 'AI';\n    user1.email = 'testing1@jest.com';\n    user1.newEmail = '';\n    user1.title = 'A.I.';\n    user1.active = true;\n    const uuid = uuidv4();\n    user1.uuid = uuid;\n    user1.password = await generateHash('TangoDown123!!!');\n    const user2 = new User();\n    user2.firstName = 'Cortana';\n    user2.lastName = 'AI';\n    user2.email = 'testing2@jest.com';\n    user2.title = 'A.I.';\n    user2.active = true;\n    const uuid2 = uuidv4();\n    user2.uuid = uuid2;\n    user2.password = await generateHash('TangoDown123!!!');\n    const insUser1 = await getConnection().getRepository(User).insert(user1);\n    const insUser2 = await getConnection().getRepository(User).insert(user2);\n    const req = new MockExpressRequest();\n    const res = new MockExpressResponse();\n    req.body = {\n      email: 'testing2@jest.com',\n      newEmail: 'testing2@jest.com',\n    };\n    req.user = 1;\n    await userController.updateUserEmail(req, res);\n    expect(res.statusCode).toBe(400);\n  });\n  test('Update user email failure user entity email validation', async () => {\n    const user1 = new User();\n    user1.firstName = 'Cortana';\n    user1.lastName = 'AI';\n    user1.email = 'testing1@jest.com';\n    user1.newEmail = '';\n    user1.title = 'A.I.';\n    user1.active = true;\n    const uuid = uuidv4();\n    user1.uuid = uuid;\n    user1.password = await generateHash('TangoDown123!!!');\n    const insUser1 = await getConnection().getRepository(User).insert(user1);\n    const req = new MockExpressRequest();\n    const res = new MockExpressResponse();\n    req.body = {\n      email: 'test@',\n      newEmail: 'test@',\n    };\n    req.user = 1;\n    await userController.updateUserEmail(req, res);\n    expect(res.statusCode).toBe(400);\n  });\n  test('revoke email request success', async () => {\n    const req = new MockExpressRequest();\n    const res = new MockExpressResponse();\n    req.user = 1;\n    const user1 = new User();\n    user1.firstName = 'Cortana';\n    user1.lastName = 'AI';\n    user1.email = 'testing1@jest.com';\n    user1.newEmail = 'newEmailToBeRevoked@jest.com';\n    user1.title = 'A.I.';\n    user1.active = true;\n    const uuid = uuidv4();\n    user1.uuid = uuid;\n    user1.password = await generateHash('TangoDown123!!!');\n    const insUser1 = await getConnection().getRepository(User).insert(user1);\n    await userController.revokeEmailRequest(req, res);\n    const checkUser = await getConnection()\n      .getRepository(User)\n      .findOne(req.user);\n    expect(checkUser.uuid).toBeNull();\n    expect(checkUser.newEmail).toBeNull();\n    expect(res.statusCode).toBe(200);\n  });\n  test('revoke email request failure user does not exist', async () => {\n    const req = new MockExpressRequest();\n    const res = new MockExpressResponse();\n    req.user = 1;\n    await userController.revokeEmailRequest(req, res);\n    expect(res.statusCode).toBe(404);\n  });\n  test('validate email request failure missing uuid or password', async () => {\n    const req = new MockExpressRequest();\n    const res = new MockExpressResponse();\n    req.body = {\n      password: 'test',\n      uuid: '',\n    };\n    await userController.validateEmailRequest(req, res);\n    expect(res.statusCode).toBe(400);\n    const req2 = new MockExpressRequest();\n    const res2 = new MockExpressResponse();\n    req2.body = {\n      password: '',\n      uuid: 'test',\n    };\n    await userController.validateEmailRequest(req2, res2);\n    expect(res2.statusCode).toBe(400);\n  });\n  test('validate email request failure user does not exist', async () => {\n    const req = new MockExpressRequest();\n    const res = new MockExpressResponse();\n    const uuid = uuidv4();\n    req.body = {\n      password: 'test',\n      uuid,\n    };\n    await userController.validateEmailRequest(req, res);\n    expect(res.statusCode).toBe(404);\n  });\n  test('validate email request failure invalid password', async () => {\n    const req = new MockExpressRequest();\n    const res = new MockExpressResponse();\n    const uuid = uuidv4();\n    req.body = {\n      password: 'test',\n      uuid,\n    };\n    req.user = 1;\n    const user1 = new User();\n    user1.firstName = 'Cortana';\n    user1.lastName = 'AI';\n    user1.email = 'testing1@jest.com';\n    user1.newEmail = 'newEmailToBeRevoked@jest.com';\n    user1.title = 'A.I.';\n    user1.active = true;\n    user1.uuid = uuid;\n    user1.password = await generateHash('TangoDown123!!!');\n    const insUser1 = await getConnection().getRepository(User).insert(user1);\n    await userController.validateEmailRequest(req, res);\n    expect(res.statusCode).toBe(400);\n  });\n  test('validate email request success', async () => {\n    const req = new MockExpressRequest();\n    const res = new MockExpressResponse();\n    const uuid = uuidv4();\n    req.body = {\n      password: 'TangoDown123!!!',\n      uuid,\n    };\n    req.user = 1;\n    const user1 = new User();\n    user1.firstName = 'Cortana';\n    user1.lastName = 'AI';\n    user1.email = 'testing1@jest.com';\n    user1.newEmail = 'newEmail@jest.com';\n    user1.title = 'A.I.';\n    user1.active = true;\n    user1.uuid = uuid;\n    user1.password = await generateHash('TangoDown123!!!');\n    const insUser1 = await getConnection().getRepository(User).insert(user1);\n    await userController.validateEmailRequest(req, res);\n    const checkUser = await getConnection()\n      .getRepository(User)\n      .findOne(req.user);\n    expect(checkUser.email).toBe('newEmail@jest.com');\n    expect(checkUser.newEmail).toBeNull();\n    expect(checkUser.uuid).toBeNull();\n    expect(res.statusCode).toBe(200);\n  });\n  test('create user', async () => {\n    const req = new MockExpressRequest();\n    const res = new MockExpressResponse();\n    req.body = {\n      email: 'createUser1@jest.com',\n      firstName: 'John',\n      lastName: 'Doe',\n      title: 'Senior Dairy Farmer',\n    };\n    await userController.create(req, res);\n    expect(res.statusCode).toBe(400);\n    const req2 = new MockExpressRequest();\n    const res2 = new MockExpressResponse();\n    req2.body = {\n      email: 'createUser1@jest.com',\n      firstName: 'John',\n      lastName: 'Doe',\n      title: 'Senior Dairy Farmer',\n      password: 'as;dfk23a234adsf',\n      confirmPassword: '123',\n    };\n    await userController.create(req2, res2);\n    expect(res2.statusCode).toBe(400);\n    const req3 = new MockExpressRequest();\n    const res3 = new MockExpressResponse();\n    req3.body = {\n      email: 'createUser1@jest.com',\n      firstName: 'John',\n      lastName: 'Doe',\n      title: 'Senior Dairy Farmer',\n      password: 'weak',\n      confirmPassword: 'weak',\n    };\n    await userController.create(req3, res3);\n    expect(res3.statusCode).toBe(400);\n    const req4 = new MockExpressRequest();\n    const res4 = new MockExpressResponse();\n    req4.body = {\n      email: 'createUser1@jest.com',\n      firstName: 'John',\n      lastName: 'Doe',\n      title: 'Senior Dairy Farmer',\n      password: '&3x1GqpeFO61*HJ',\n      confirmPassword: '&3x1GqpeFO61*HJ',\n    };\n    await userController.create(req4, res4);\n    expect(res4.statusCode).toBe(200);\n    const existUser = new User();\n    existUser.firstName = 'master';\n    existUser.lastName = 'chief';\n    existUser.email = 'createUserExist@jest.com';\n    existUser.active = true;\n    await getConnection().getRepository(User).save(existUser);\n    const req5 = new MockExpressRequest();\n    const res5 = new MockExpressResponse();\n    req5.body = {\n      email: 'createUserExist@jest.com',\n      firstName: 'John',\n      lastName: 'Doe',\n      title: 'Senior Dairy Farmer',\n      password: '&3x1GqpeFO61*HJ',\n      confirmPassword: '&3x1GqpeFO61*HJ',\n    };\n    await userController.create(req5, res5);\n    expect(res5.statusCode).toBe(400);\n    const req6 = new MockExpressRequest();\n    const res6 = new MockExpressResponse();\n    req6.body = {\n      email: 'createUserExist@jest.com',\n      firstName: 'John',\n      lastName: 'Doe',\n      title: 'Senior Dairy Farmer',\n      password: '&3x1GqpeFO61*HJ',\n      confirmPassword: '&3x1GqpeFO61*HJ',\n    };\n    await userController.create(req6, res6);\n    expect(res6.statusCode).toBe(400);\n  });\n});\n"
  },
  {
    "path": "src/routes/user.controller.ts",
    "content": "import { UserRequest } from '../interfaces/user-request.interface';\nimport { AppDataSource } from '../data-source';\nimport { User } from '../entity/User';\nimport { Response, Request } from 'express';\nimport { v4 as uuidv4 } from 'uuid';\nimport { validate } from 'class-validator';\nimport { passwordRequirement } from '../enums/message-enum';\nimport {\n  compare,\n  generateHash,\n  passwordSchema,\n  updatePassword,\n} from '../utilities/password.utility';\nimport * as emailService from '../services/email.service';\nimport { Config } from '../entity/Config';\nimport { ROLE } from '../enums/roles-enum';\nimport { In } from 'typeorm';\n\n/**\n * @description Register user\n * @param {UserRequest} req\n * @param {Response} res\n * @returns success message\n */\nexport const register = async (req: Request, res: Response) => {\n  try {\n    const {\n      firstName,\n      lastName,\n      title,\n      password,\n      confirmPassword,\n      uuid,\n    } = req.body;\n    \n    if (!firstName || !lastName || !title) {\n      return res.status(400).json('Invalid registration form');\n    }\n    \n    if (password !== confirmPassword) {\n      return res.status(400).json('Passwords do not match');\n    }\n    \n    if (!passwordSchema.validate(password)) {\n      return res.status(400).json(passwordRequirement);\n    }\n    \n    const userRepository = AppDataSource.getRepository(User);\n    const user = await userRepository\n      .createQueryBuilder('user')\n      .where('user.uuid = :uuid', {\n        uuid,\n      })\n      .getOne();\n    \n    if (user) {\n      user.password = await generateHash(password);\n      user.uuid = null;\n      user.active = true;\n      user.firstName = firstName;\n      user.lastName = lastName;\n      user.title = title;\n      \n      const errors = await validate(user, { skipMissingProperties: true });\n      if (errors.length > 0) {\n        console.error('Validation errors:', errors);\n        return res.status(400).json('Invalid registration form');\n      } else {\n        await userRepository.save(user);\n        return res.status(200).json('Registration Complete');\n      }\n    } else {\n      return res\n        .status(400)\n        .json(\n          'Unable to register user password at this time. Please contact an administrator for assistance.'\n        );\n    }\n  } catch (error) {\n    console.error('Error during registration:', error);\n    return res.status(500).json('An error occurred during registration');\n  }\n};\n\n/**\n * @description Invite user\n * @param {UserRequest} req\n * @param {Response} res\n * @returns success message\n */\nexport const invite = async (req: Request, res: Response) => {\n  try {\n    const configRepository = AppDataSource.getRepository(Config);\n    const config = await configRepository.findOne({ where: { id: 1 } });\n    \n    if (!config || !config.fromEmail || !config.fromEmailPassword) {\n      return res\n        .status(400)\n        .json(\n          'Failed to invite user. Please set the email configuration in the Settings menu option.'\n        );\n    } else {\n      const { email } = req.body;\n      \n      if (!email) {\n        return res.status(400).json('Email is invalid');\n      }\n      \n      const userRepository = AppDataSource.getRepository(User);\n      const existUser = await userRepository.find({ where: { email } });\n      \n      if (existUser.length) {\n        return res\n          .status(400)\n          .json('A user associated to that email has already been invited');\n      }\n      \n      const user = new User();\n      user.active = false;\n      user.uuid = uuidv4();\n      user.email = email;\n      \n      await userRepository.save(user);\n      emailService.sendInvitationEmail(user.uuid, user.email);\n      \n      return res.status(200).json('User invited successfully');\n    }\n  } catch (error) {\n    console.error('Error inviting user:', error);\n    return res.status(500).json('An error occurred while inviting the user');\n  }\n};\n\n/**\n * @description Create user\n * @param {UserRequest} req\n * @param {Response} res\n * @returns success message\n */\nexport const create = async (req: Request, res: Response) => {\n  try {\n    const {\n      email,\n      firstName,\n      lastName,\n      title,\n      password,\n      confirmPassword,\n    } = req.body;\n    \n    if (\n      !email ||\n      !firstName ||\n      !lastName ||\n      !title ||\n      !password ||\n      !confirmPassword\n    ) {\n      return res.status(400).json('Invalid user form');\n    }\n    \n    if (password !== confirmPassword) {\n      return res.status(400).json('Passwords do not match');\n    }\n    \n    if (!passwordSchema.validate(password)) {\n      return res.status(400).json(passwordRequirement);\n    }\n    \n    const userRepository = AppDataSource.getRepository(User);\n    const existUser = await userRepository\n      .findOne({ where: { email } });\n    \n    if (existUser) {\n      return res\n        .status(400)\n        .json('A user associated to that email has already been created');\n    }\n    \n    const user = new User();\n    user.active = true;\n    user.uuid = uuidv4();\n    user.email = email;\n    user.newEmail = email;\n    user.firstName = firstName;\n    user.lastName = lastName;\n    user.title = title;\n    user.password = await generateHash(password);\n    \n    await userRepository.save(user);\n    return res.status(200).json('User created successfully');\n  } catch (error) {\n    console.error('Error creating user:', error);\n    return res.status(500).json('An error occurred while creating the user');\n  }\n};\n\n/**\n * @description Verifies user by comparing UUID\n * @param {UserRequest} req\n * @param {Response} res\n * @returns Success message\n */\nexport const verify = async (req: Request, res: Response) => {\n  try {\n    if (req.params.uuid) {\n      const userRepository = AppDataSource.getRepository(User);\n      const user = await userRepository\n        .findOne({ where: { uuid: req.params.uuid } });\n      \n      if (user) {\n        user.active = true;\n        user.uuid = null;\n        await userRepository.save(user);\n        return res.status(200).json('Email verification successful');\n      } else {\n        return res\n          .status(400)\n          .json('Email verification failed. User does not exist.');\n      }\n    } else {\n      return res.status(400).json('UUID is undefined');\n    }\n  } catch (error) {\n    console.error('Error during verification:', error);\n    return res.status(500).json('An error occurred during verification');\n  }\n};\n\n/**\n * @description Updates user password\n * @param {UserRequest} req\n * @param {Response} res\n * @returns Success message\n */\nexport const updateUserPassword = async (req: UserRequest, res: Response) => {\n  try {\n    const { oldPassword, newPassword, confirmNewPassword } = req.body;\n    const currentPassword = oldPassword;\n    \n    if (newPassword !== confirmNewPassword) {\n      return res.status(400).json('Passwords do not match');\n    }\n    \n    if (newPassword === currentPassword) {\n      return res\n        .status(400)\n        .json('New password can not be the same as the old password');\n    }\n    \n    if (!passwordSchema.validate(newPassword)) {\n      return res.status(400).json(passwordRequirement);\n    }\n    \n    const userRepository = AppDataSource.getRepository(User);\n    const user = await userRepository.findOne({ where: { id: +req.user } });\n    \n    if (user) {\n      const hashedUserPassword = user.password;\n      try {\n        user.password = await updatePassword(\n          hashedUserPassword,\n          currentPassword,\n          newPassword\n        );\n      } catch (err) {\n        return res.status(400).json(err);\n      }\n      \n      await userRepository.save(user);\n      return res.status(200).json('Password updated successfully');\n    } else {\n      return res\n        .status(400)\n        .json(\n          'Unable to update user password at this time. Please contact an administrator for assistance.'\n        );\n    }\n  } catch (error) {\n    console.error('Error updating password:', error);\n    return res.status(500).json('An error occurred while updating the password');\n  }\n};\n\n/**\n * @description Patch user\n * @param {UserRequest} req\n * @param {Response} res\n * @returns Success message\n */\nexport const patch = async (req: UserRequest, res: Response) => {\n  try {\n    const { firstName, lastName, title } = req.body;\n    const userRepository = AppDataSource.getRepository(User);\n    const user = await userRepository.findOne({ where: { id: +req.user } });\n    \n    if (!user) {\n      return res.status(404).json('User not found');\n    }\n    \n    if (firstName) {\n      user.firstName = firstName;\n    }\n    \n    if (lastName) {\n      user.lastName = lastName;\n    }\n    \n    if (title) {\n      user.title = title;\n    }\n    \n    user.uuid = null;\n    \n    const errors = await validate(user, { skipMissingProperties: true });\n    if (errors.length > 0) {\n      return res.status(400).json(errors);\n    } else {\n      await userRepository.save(user);\n      return res.status(200).json('User patched successfully');\n    }\n  } catch (error) {\n    console.error('Error updating user:', error);\n    return res.status(500).json('An error occurred while updating the user');\n  }\n};\n\n/**\n * @description Get user\n * @param {UserRequest} req\n * @param {Response} res\n * @returns User\n */\nexport const getUser = async (req: UserRequest, res: Response) => {\n  try {\n    const userRepository = AppDataSource.getRepository(User);\n    const user = await userRepository\n      .findOne({ \n        where: { id: +req.user },\n        relations: ['teams']\n      });\n    \n    if (!user) return res.status(404).json('User not found');\n    \n    // Don't return sensitive data\n    const userResponse = { ...user };\n    delete userResponse.active;\n    delete userResponse.password;\n    delete userResponse.uuid;\n    delete userResponse.id;\n    \n    return res.status(200).json(userResponse);\n  } catch (error) {\n    console.error('Error fetching user:', error);\n    return res.status(500).json('An error occurred while fetching the user');\n  }\n};\n\n/**\n * @description Get Users\n * @param {UserRequest} req\n * @param {Response} res\n * @returns user array\n */\nexport const getUsers = async (req: Request, res: Response) => {\n  try {\n    const userRepository = AppDataSource.getRepository(User);\n    const users = await userRepository\n      .createQueryBuilder('user')\n      .where('user.active = true')\n      .select(['user.id', 'user.firstName', 'user.lastName', 'user.title'])\n      .getMany();\n    \n    return res.status(200).json(users);\n  } catch (error) {\n    console.error('Error fetching users:', error);\n    return res.status(500).json('An error occurred while fetching users');\n  }\n};\n\n/**\n * @description Get Testers\n * @param {UserRequest} req\n * @param {Response} res\n * @returns user array\n */\nexport const getTesters = async (req: UserRequest, res: Response) => {\n  try {\n    if (!req.params.orgId) {\n      return res.status(400).json('Invalid Organization ID');\n    }\n    \n    if (isNaN(+req.params.orgId)) {\n      return res.status(400).json('Invalid Organization ID');\n    }\n    \n    // Optional check for org access\n    // if (!req.userOrgs.includes(+req.params.orgId)) {\n    //   return res.status(404).json('Testers not found');\n    // }\n    \n    const userRepository = AppDataSource.getRepository(User);\n    const users = await userRepository\n      .createQueryBuilder('user')\n      .leftJoinAndSelect('user.teams', 'teams')\n      .leftJoinAndSelect('teams.organization', 'organization')\n      .where('user.active = true')\n      .andWhere('teams.role != :role', { role: ROLE.READONLY })\n      .andWhere('teams.organization.id = :orgId', { orgId: +req.params.orgId })\n      .select(['user.id', 'user.firstName', 'user.lastName', 'user.title'])\n      .getMany();\n    \n    return res.status(200).json(users);\n  } catch (error) {\n    console.error('Error fetching testers:', error);\n    return res.status(500).json('An error occurred while fetching testers');\n  }\n};\n\n/**\n * @description Get all active/inactive users\n * @param {UserRequest} req\n * @param {Response} res\n * @returns user array\n */\nexport const getAllUsers = async (req: Request, res: Response) => {\n  try {\n    const userRepository = AppDataSource.getRepository(User);\n    const users = await userRepository\n      .createQueryBuilder('user')\n      .leftJoinAndSelect('user.teams', 'teams')\n      .select([\n        'user.id',\n        'user.firstName',\n        'user.lastName',\n        'user.title',\n        'user.active',\n        'user.email',\n        'teams.name',\n      ])\n      .getMany();\n    \n    return res.status(200).json(users);\n  } catch (error) {\n    console.error('Error fetching all users:', error);\n    return res.status(500).json('An error occurred while fetching all users');\n  }\n};\n\n/**\n * @description Get user IDs and validate users\n * @param {number[]} userIds\n * @returns User array\n */\nexport const getUsersById = async (userIds: number[]) => {\n  try {\n    const userRepository = AppDataSource.getRepository(User);\n    const users = await userRepository.find({\n      where: { id: In(userIds) }\n    });\n    \n    return users;\n  } catch (error) {\n    console.error('Error fetching users by ID:', error);\n    return [];\n  }\n};\n\n/**\n * @description Send email update request\n * @param {UserRequest} req\n * @param {Response} res\n * @returns string\n */\nexport const updateUserEmail = async (req: UserRequest, res: Response) => {\n  try {\n    const email = req.body.email;\n    const newEmail = req.body.newEmail;\n    \n    if (email !== newEmail) {\n      return res\n        .status(400)\n        .json('The new email address and confirmation email address must match');\n    }\n    \n    const userRepository = AppDataSource.getRepository(User);\n    const user = await userRepository.findOne({ where: { id: +req.user } });\n    \n    if (!user) {\n      return res.status(404).json('User not found');\n    }\n    \n    if (user.newEmail) {\n      return res\n        .status(400)\n        .json(\n          'An email update request is already in progress. Please revoke this current request and try again.'\n        );\n    }\n    \n    // Check if email is already taken\n    const existingUsers = await userRepository.find({\n      select: ['email']\n    });\n    \n    const existingEmails = existingUsers.map((x) => x.email);\n    if (existingEmails.includes(email)) {\n      return res.status(400).json('Email is already taken');\n    }\n    \n    user.uuid = uuidv4();\n    user.newEmail = email;\n    \n    const errors = await validate(user);\n    if (errors.length > 0) {\n      console.error('Validation errors:', errors);\n      return res.status(400).json('Email is invalid');\n    } else {\n      await userRepository.save(user);\n      \n      emailService.sendUpdateUserEmail(user, (err, info) => {\n        if (err) {\n          return res\n            .status(400)\n            .json(\n              'There was a problem updating your email address. Please contact an administrator for assistance.'\n            );\n        } else {\n          return res\n            .status(200)\n            .json(`A confirmation email has been sent to ${user.newEmail}`);\n        }\n      });\n    }\n  } catch (error) {\n    console.error('Error updating email:', error);\n    return res.status(500).json('An error occurred while updating the email');\n  }\n};\n\n/**\n * @description Revoke current email update request\n * @param {UserRequest} req\n * @param {Response} res\n * @returns string\n */\nexport const revokeEmailRequest = async (req: UserRequest, res: Response) => {\n  try {\n    const userRepository = AppDataSource.getRepository(User);\n    const user = await userRepository.findOne({ where: { id: +req.user } });\n    \n    if (!user || !user.newEmail) {\n      return res\n        .status(404)\n        .json('An email update request for this user does not exist');\n    }\n    \n    user.newEmail = null;\n    user.uuid = null;\n    \n    await userRepository.save(user);\n    return res\n      .status(200)\n      .json('The email update request has been successfully revoked');\n  } catch (error) {\n    console.error('Error revoking email request:', error);\n    return res.status(500).json('An error occurred while revoking the email request');\n  }\n};\n\n/**\n * @description Validate current email update request\n * @param {UserRequest} req\n * @param {Response} res\n * @returns string\n */\nexport const validateEmailRequest = async (req: UserRequest, res: Response) => {\n  try {\n    if (!req.body.password || !req.body.uuid) {\n      return res.status(400).json('The password or UUID is missing');\n    }\n    \n    const userRepository = AppDataSource.getRepository(User);\n    const user = await userRepository\n      .findOne({ where: { uuid: req.body.uuid } });\n    \n    if (!user) {\n      return res\n        .status(404)\n        .json('A user associated with this UUID does not exist');\n    }\n    \n    const valid = await compare(req.body.password, user.password);\n    if (!valid) {\n      return res.status(400).json('The password is incorrect');\n    } else {\n      user.email = user.newEmail;\n      user.uuid = null;\n      user.newEmail = null;\n      \n      await userRepository.save(user);\n      return res.status(200).json('Your email has been successfully updated');\n    }\n  } catch (error) {\n    console.error('Error validating email request:', error);\n    return res.status(500).json('An error occurred while validating the email request');\n  }\n};\n/**\n * @description Activate user\n * @param {Request} req\n * @param {Response} res\n * @returns Success message\n */\nexport const activateUser = async (req: Request, res: Response) => {\n  try {\n    const userRepository = AppDataSource.getRepository(User);\n    const user = await userRepository.findOne({ where: { id: +req.params.id } });\n    if (!user) {\n      return res.status(404).json('User not found');\n    }\n    user.active = true;\n    await userRepository.save(user);\n    return res.status(200).json('User activated successfully');\n  } catch (error) {\n    console.error('Error activating user:', error);\n    return res.status(500).json('An error occurred while activating the user');\n  }\n};\n/**\n * @description Deactivate user\n * @param {Request} req\n * @param {Response} res\n * @returns Success message\n */\nexport const deactivateUser = async (req: Request, res: Response) => {\n  try {\n    const userRepository = AppDataSource.getRepository(User);\n    const user = await userRepository.findOne({ where: { id: +req.params.id } });\n    if (!user) {\n      return res.status(404).json('User not found');\n    }\n    user.active = false;\n    await userRepository.save(user);\n    return res.status(200).json('User deactivated successfully');\n  } catch (error) {\n    console.error('Error deactivating user:', error);\n    return res.status(500).json('An error occurred while deactivating the user');\n  }\n};"
  },
  {
    "path": "src/routes/vulnerability.controller.ts",
    "content": "import { UserRequest } from '../interfaces/user-request.interface';\nimport { Response } from 'express';\nimport { AppDataSource } from '../data-source';\nimport { Assessment } from '../entity/Assessment';\nimport { validate } from 'class-validator';\nimport { Vulnerability } from '../entity/Vulnerability';\nimport { File } from '../entity/File';\nimport { ProblemLocation } from '../entity/ProblemLocation';\nimport { Resource } from '../entity/Resource';\nimport { exportToJiraIssue } from '../utilities/jira.utility';\nimport { JiraInit } from '../interfaces/jira/jira-init.interface';\nimport { Jira } from '../entity/Jira';\nimport {\n  hasAssetReadAccess,\n  hasAssetWriteAccess,\n} from '../utilities/role.utility';\nconst fileUploadController = require('../routes/file-upload.controller');\n\n/**\n * @description get vulnerability by ID\n * @param {UserRequest} req\n * @param {Response} res contains JSON object with the organization data\n * @returns vulnerability data\n */\nexport const getVulnById = async (req: UserRequest, res: Response) => {\n  try {\n    if (!req.params.vulnId) {\n      return res.status(400).send('Invalid Vulnerability Request');\n    }\n    if (isNaN(+req.params.vulnId)) {\n      return res.status(400).send('Invalid Vulnerability ID');\n    }\n    \n    const vulnerabilityRepository = AppDataSource.getRepository(Vulnerability);\n    const assessmentRepository = AppDataSource.getRepository(Assessment);\n    const jiraRepository = AppDataSource.getRepository(Jira);\n    \n    // Get vulnerability with relations\n    const vuln = await vulnerabilityRepository.findOne({\n      where: { id: +req.params.vulnId },\n      relations: ['screenshots', 'problemLocations', 'resources', 'assessment']\n    });\n    \n    if (!vuln) {\n      return res.status(404).send('Vulnerability does not exist.');\n    }\n    \n    const assessment = await assessmentRepository.findOne({\n      where: { id: vuln.assessment.id },\n      relations: ['asset', 'asset.organization']\n    });\n    \n    const hasReadAccess = await hasAssetReadAccess(req, assessment.asset.id);\n    if (!hasReadAccess) {\n      return res.status(404).json('Vulnerability not found');\n    }\n    \n    const hasAccess = await hasAssetWriteAccess(req, assessment.asset.id);\n    \n    const jira = await jiraRepository.findOne({\n      where: { asset: { id: assessment.asset.id } }\n    });\n    \n    const jiraHost = jira ? jira.host : null;\n    \n    return res\n      .status(200)\n      .json({ vulnerability: vuln, jiraHost, readOnly: !hasAccess });\n  } catch (error) {\n    console.error('Error fetching vulnerability:', error);\n    return res.status(500).json('An error occurred while fetching the vulnerability');\n  }\n};\n\n/**\n * @description Delete vulnerability by ID\n * @param {UserRequest} req vulnID is required\n * @param {Response} res contains JSON object with the success/fail\n * @returns success/error message\n */\nexport const deleteVulnById = async (req: UserRequest, res: Response) => {\n  try {\n    if (!req.params.vulnId) {\n      return res.status(400).send('Invalid vulnerability request');\n    }\n    if (isNaN(+req.params.vulnId)) {\n      return res.status(400).send('Invalid vulnerability ID');\n    }\n    \n    const vulnerabilityRepository = AppDataSource.getRepository(Vulnerability);\n    const assessmentRepository = AppDataSource.getRepository(Assessment);\n    \n    const vuln = await vulnerabilityRepository.findOne({\n      where: { id: +req.params.vulnId },\n      relations: ['assessment']\n    });\n    \n    if (!vuln) {\n      return res.status(404).send('Vulnerability does not exist.');\n    }\n    \n    const assessment = await assessmentRepository.findOne({\n      where: { id: vuln.assessment.id },\n      relations: ['asset']\n    });\n    \n    const hasAccess = await hasAssetWriteAccess(req, assessment.asset.id);\n    if (!hasAccess) {\n      return res.status(403).json('Authorization is required');\n    }\n    \n    await vulnerabilityRepository.remove(vuln);\n    \n    return res\n      .status(200)\n      .json(`Vulnerability #${vuln.id}: \"${vuln.name}\" successfully deleted`);\n  } catch (error) {\n    console.error('Error deleting vulnerability:', error);\n    return res.status(500).json('An error occurred while deleting the vulnerability');\n  }\n};\n\n/**\n * @description Update vulnerability by ID\n * @param {UserRequest} req\n * @param {Response} res\n * @returns success/error message\n */\nexport const patchVulnById = async (req: UserRequest, res: Response) => {\n  try {\n    req = await fileUploadController.uploadFileArray(req, res);\n    \n    if (isNaN(+req.body.assessment) || !req.body.assessment) {\n      return res.status(400).json('Invalid Assessment ID');\n    }\n    \n    const assessmentRepository = AppDataSource.getRepository(Assessment);\n    const vulnerabilityRepository = AppDataSource.getRepository(Vulnerability);\n    const fileRepository = AppDataSource.getRepository(File);\n    const problemLocationRepository = AppDataSource.getRepository(ProblemLocation);\n    const resourceRepository = AppDataSource.getRepository(Resource);\n    \n    const assessment = await assessmentRepository.findOne({\n      where: { id: +req.body.assessment },\n      relations: ['asset']\n    });\n    \n    if (!assessment) {\n      return res.status(404).json('Assessment does not exist');\n    }\n    \n    const hasAccess = await hasAssetWriteAccess(req, assessment.asset.id);\n    if (!hasAccess) {\n      return res.status(403).json('Authorization is required');\n    }\n    \n    if (isNaN(+req.params.vulnId)) {\n      return res.status(400).json('Vulnerability ID is invalid');\n    }\n    \n    const vulnerability = await vulnerabilityRepository.findOne({\n      where: { id: +req.params.vulnId }\n    });\n    \n    if (!vulnerability) {\n      return res.status(404).json('Vulnerability does not exist');\n    }\n    \n    vulnerability.id = +req.params.vulnId;\n    vulnerability.impact = req.body.impact;\n    vulnerability.likelihood = req.body.likelihood;\n    vulnerability.risk = req.body.risk;\n    vulnerability.status = req.body.status;\n    vulnerability.description = req.body.description;\n    vulnerability.remediation = req.body.remediation;\n    vulnerability.jiraId = req.body.jiraId;\n    vulnerability.cvssScore = req.body.cvssScore;\n    vulnerability.cvssUrl = req.body.cvssUrl;\n    vulnerability.detailedInfo = req.body.detailedInfo;\n    vulnerability.assessment = assessment;\n    vulnerability.name = req.body.name;\n    vulnerability.systemic = req.body.systemic;\n    \n    const errors = await validate(vulnerability);\n    if (errors.length > 0) {\n      return res.status(400).send('Vulnerability form validation failed');\n    }\n    \n    await vulnerabilityRepository.save(vulnerability);\n    \n    // Remove deleted files\n    if (req.body.screenshotsToDelete) {\n      try {\n        const existingScreenshots = await fileRepository.find({\n          where: { vulnerability: { id: vulnerability.id } }\n        });\n        \n        const existingScreenshotIds = existingScreenshots.map(\n          (screenshot) => screenshot.id\n        );\n        \n        let screenshotsToDelete = JSON.parse(req.body.screenshotsToDelete);\n        // We only want to remove the files associated to the vulnerability\n        screenshotsToDelete = existingScreenshotIds.filter((value) =>\n          screenshotsToDelete.includes(value)\n        );\n        \n        for (const screenshotId of screenshotsToDelete) {\n          await fileRepository.delete(screenshotId);\n        }\n      } catch (error) {\n        console.error('Error processing deleted screenshots:', error);\n      }\n    }\n    \n    // Save new screenshots\n    if (req.files && req.files.length > 0) {\n      await saveScreenshots(req.files, vulnerability);\n    }\n    \n    // Process problem locations\n    if (req.body.problemLocations && req.body.problemLocations.length) {\n      try {\n        const clientProdLocs = JSON.parse(req.body.problemLocations);\n        const clientProdLocsIds = clientProdLocs.map((value) => value.id).filter(id => id);\n        \n        const existingProbLocs = await problemLocationRepository.find({\n          where: { vulnerability: { id: vulnerability.id } }\n        });\n        \n        const existingProbLocIds = existingProbLocs.map((probLoc) => probLoc.id);\n        const prodLocsToDelete = existingProbLocIds.filter(\n          (value) => !clientProdLocsIds.includes(value)\n        );\n        \n        for (const probLoc of prodLocsToDelete) {\n          await problemLocationRepository.delete(probLoc);\n        }\n        \n        await saveProblemLocations(clientProdLocs, vulnerability);\n      } catch (error) {\n        console.error('Error processing problem locations:', error);\n      }\n    }\n    \n    // Process resources\n    if (req.body.resources && req.body.resources.length) {\n      try {\n        const clientResources = JSON.parse(req.body.resources);\n        const clientResourceIds = clientResources.map((value) => value.id).filter(id => id);\n        \n        const existingResources = await resourceRepository.find({\n          where: { vulnerability: { id: vulnerability.id } }\n        });\n        \n        const existingResourceIds = existingResources.map(\n          (resource) => resource.id\n        );\n        \n        const resourcesToDelete = existingResourceIds.filter(\n          (value) => !clientResourceIds.includes(value)\n        );\n        \n        for (const resource of resourcesToDelete) {\n          await resourceRepository.delete(resource);\n        }\n        \n        await saveResources(clientResources, vulnerability);\n      } catch (error) {\n        console.error('Error processing resources:', error);\n      }\n    }\n    \n    return res.status(200).json('Vulnerability patched successfully');\n  } catch (error) {\n    console.error('Error updating vulnerability:', error);\n    return res.status(500).json('An error occurred while updating the vulnerability');\n  }\n};\n\n/**\n * @description Create vulnerability\n * @param {UserRequest} req\n * @param {Response} res\n * @returns success/error message\n */\nexport const createVuln = async (req: UserRequest, res: Response) => {\n  try {\n    req = await fileUploadController.uploadFileArray(req, res);\n    \n    if (isNaN(+req.body.assessment) || !req.body.assessment) {\n      return res.status(400).json('Invalid Assessment ID');\n    }\n    \n    const assessmentRepository = AppDataSource.getRepository(Assessment);\n    const vulnerabilityRepository = AppDataSource.getRepository(Vulnerability);\n    \n    const assessment = await assessmentRepository.findOne({\n      where: { id: +req.body.assessment },\n      relations: ['asset']\n    });\n    \n    if (!assessment) {\n      return res.status(404).json('Assessment does not exist');\n    }\n    \n    const hasAccess = await hasAssetWriteAccess(req, assessment.asset.id);\n    if (!hasAccess) {\n      return res.status(403).json('Authorization is required');\n    }\n    \n    const vulnerability = new Vulnerability();\n    vulnerability.impact = req.body.impact;\n    vulnerability.likelihood = req.body.likelihood;\n    vulnerability.risk = req.body.risk;\n    vulnerability.status = req.body.status;\n    vulnerability.description = req.body.description;\n    vulnerability.remediation = req.body.remediation;\n    vulnerability.jiraId = req.body.jiraId || '';\n    vulnerability.cvssScore = req.body.cvssScore;\n    vulnerability.cvssUrl = req.body.cvssUrl;\n    vulnerability.detailedInfo = req.body.detailedInfo;\n    vulnerability.assessment = assessment;\n    vulnerability.name = req.body.name;\n    vulnerability.systemic = req.body.systemic;\n    \n    const errors = await validate(vulnerability);\n    if (errors.length > 0) {\n      console.error('Validation errors:', errors);\n      return res.status(400).send('Vulnerability form validation failed');\n    }\n    \n    const savedVulnerability = await vulnerabilityRepository.save(vulnerability);\n    \n    // Save screenshots\n    if (req.files && req.files.length > 0) {\n      await saveScreenshots(req.files, savedVulnerability);\n    }\n    \n    // Save problem locations\n    if (req.body.problemLocations) {\n      try {\n        const problemLocations = JSON.parse(req.body.problemLocations);\n        await saveProblemLocations(problemLocations, savedVulnerability);\n      } catch (error) {\n        console.error('Error saving problem locations:', error);\n      }\n    }\n    \n    // Save resources\n    if (req.body.resources) {\n      try {\n        const resources = JSON.parse(req.body.resources);\n        await saveResources(resources, savedVulnerability);\n      } catch (error) {\n        console.error('Error saving resources:', error);\n      }\n    }\n    \n    return res.status(200).json('Vulnerability saved successfully');\n  } catch (error) {\n    console.error('Error creating vulnerability:', error);\n    return res.status(500).json('An error occurred while creating the vulnerability');\n  }\n};\n\n/**\n * @description Save screenshots to vulnerability\n * @param {File[]} files\n * @param {Vulnerability} vulnerability\n */\nexport const saveScreenshots = async (files: File[], vulnerability: Vulnerability) => {\n  const fileRepository = AppDataSource.getRepository(File);\n  \n  for (const screenshot of files) {\n    try {\n      let file = new File();\n      file = screenshot;\n      file.vulnerability = vulnerability;\n      \n      const fileErrors = await validate(file);\n      if (fileErrors.length > 0) {\n        console.error('File validation failed:', fileErrors);\n        continue;\n      }\n      \n      await fileRepository.save(file);\n    } catch (error) {\n      console.error('Error saving screenshot:', error);\n    }\n  }\n};\n\n/**\n * @description Save problem locations to vulnerability\n * @param {ProblemLocation[]} problemLocations\n * @param {Vulnerability} vulnerability\n */\nexport const saveProblemLocations = async (problemLocations: ProblemLocation[], vulnerability: Vulnerability) => {\n  const problemLocationRepository = AppDataSource.getRepository(ProblemLocation);\n  \n  for (const probLoc of problemLocations) {\n    try {\n      if (probLoc && probLoc.location && probLoc.target) {\n        let problemLocation = new ProblemLocation();\n        \n        // If updating an existing problem location\n        if (probLoc.id) {\n          problemLocation = await problemLocationRepository.findOne({\n            where: { id: probLoc.id }\n          });\n          \n          if (!problemLocation) {\n            problemLocation = new ProblemLocation();\n          }\n        }\n        \n        problemLocation.location = probLoc.location;\n        problemLocation.target = probLoc.target;\n        problemLocation.vulnerability = vulnerability;\n        \n        const plErrors = await validate(problemLocation);\n        if (plErrors.length > 0) {\n          console.error('Problem Location validation failed:', plErrors);\n          continue;\n        }\n        \n        await problemLocationRepository.save(problemLocation);\n      }\n    } catch (error) {\n      console.error('Error saving problem location:', error);\n    }\n  }\n};\n\n/**\n * @description Save resources to vulnerability\n * @param {Resource[]} resources\n * @param {Vulnerability} vulnerability\n */\nexport const saveResources = async (resources: Resource[], vulnerability: Vulnerability) => {\n  const resourceRepository = AppDataSource.getRepository(Resource);\n  \n  for (const resource of resources) {\n    try {\n      if (resource.description && resource.url) {\n        let newResource = new Resource();\n        \n        // If updating an existing resource\n        if (resource.id) {\n          newResource = await resourceRepository.findOne({\n            where: { id: resource.id }\n          });\n          \n          if (!newResource) {\n            newResource = new Resource();\n          }\n        }\n        \n        newResource.description = resource.description;\n        newResource.url = resource.url;\n        newResource.vulnerability = vulnerability;\n        \n        const nrErrors = await validate(newResource);\n        if (nrErrors.length > 0) {\n          console.error('Resource validation failed:', nrErrors);\n          continue;\n        }\n        \n        await resourceRepository.save(newResource);\n      }\n    } catch (error) {\n      console.error('Error saving resource:', error);\n    }\n  }\n};\n\n/**\n * @description Generate JIRA Ticket from Vuln\n * @param {UserRequest} req\n * @param {Response} res\n * @returns JIRA return ID?\n */\nexport const exportToJira = async (req: UserRequest, res: Response) => {\n  try {\n    if (!req.params.vulnId) {\n      return res.status(400).json('Invalid Vulnerability ID');\n    }\n    \n    if (isNaN(+req.params.vulnId)) {\n      return res.status(400).json('Invalid Vulnerability ID');\n    }\n    \n    const vulnerabilityRepository = AppDataSource.getRepository(Vulnerability);\n    const assessmentRepository = AppDataSource.getRepository(Assessment);\n    const jiraRepository = AppDataSource.getRepository(Jira);\n    \n    const vuln = await vulnerabilityRepository.findOne({\n      where: { id: +req.params.vulnId },\n      relations: ['screenshots', 'resources', 'problemLocations', 'assessment']\n    });\n    \n    if (!vuln) {\n      return res.status(404).json('Vulnerability not found');\n    }\n    \n    const assessment = await assessmentRepository.findOne({\n      where: { id: vuln.assessment.id },\n      relations: ['asset']\n    });\n    \n    const hasAccess = await hasAssetWriteAccess(req, assessment.asset.id);\n    if (!hasAccess) {\n      return res.status(403).json('Authorization is required');\n    }\n    \n    const jira = await jiraRepository.findOne({\n      where: { asset: { id: assessment.asset.id } }\n    });\n    \n    if (!assessment.jiraId) {\n      return res\n        .status(400)\n        .json(\n          'Unable to create JIRA ticket. Assessment requires an associated Jira ticket.'\n        );\n    }\n    \n    if (!jira) {\n      return res\n        .status(400)\n        .json(\n          'Unable to create JIRA ticket. Please provide JIRA credentials to the parent Asset.'\n        );\n    }\n    \n    const jiraInit: JiraInit = {\n      apiKey: jira.apiKey,\n      host: jira.host,\n      username: jira.username,\n    };\n    \n    try {\n      const result = await exportToJiraIssue(vuln, jiraInit);\n      vuln.jiraId = `https://${jiraInit.host}/browse/${result.key}`;\n      await vulnerabilityRepository.save(vuln);\n      return res.status(200).json(`${result.message}`);\n    } catch (err) {\n      return res.status(404).json(err);\n    }\n  } catch (error) {\n    console.error('Error exporting to Jira:', error);\n    return res.status(500).json('An error occurred while exporting to Jira');\n  }\n};"
  },
  {
    "path": "src/services/email.service.spec.ts",
    "content": "import nodemailer = require('nodemailer');\nimport * as emailService from './email.service';\n\ndescribe('email service', () => {\n  test('send email failure missing credentials', async () => {\n    const mailOptions = {\n      from: 'pentester3@gmail.com',\n      subject: 'Bulwark - Please confirm your email address',\n      text: `Please confirm your email address\n                  \\n A Bulwark account was created with the email: pentester@gmail.com.\n                  As an extra security measure, please verify this is the correct email address\n                  linked to Bulwark by clicking the link below.\n                  \\n dev/api/user/verify/123`,\n      to: 'pentester2@gmail.com'\n    };\n    await emailService.sendEmail(mailOptions, (err, data) => {\n      expect(err).toBe('Error sending email');\n    });\n  });\n  test('send verification email success', async () => {\n    const uuid = 'abc';\n    const userEmail = 'pentester@gmail.com';\n    const spy = jest.spyOn(emailService, 'sendEmail');\n    await emailService.sendVerificationEmail(uuid, userEmail);\n    expect(spy).toHaveBeenCalled();\n  });\n  test('send forgot password email success', async () => {\n    const uuid = 'abc';\n    const userEmail = 'pentester@gmail.com';\n    const spy = jest.spyOn(emailService, 'sendEmail');\n    await emailService.sendForgotPasswordEmail(uuid, userEmail);\n    expect(spy).toHaveBeenCalled();\n  });\n  test('send invitation email', async () => {\n    const uuid = 'abc';\n    const userEmail = 'pentester@gmail.com';\n    const spy = jest.spyOn(emailService, 'sendEmail');\n    await emailService.sendInvitationEmail(uuid, userEmail);\n    expect(spy).toHaveBeenCalled();\n  });\n});\n"
  },
  {
    "path": "src/services/email.service.ts",
    "content": "import * as nodemailer from 'nodemailer';\nimport { AppDataSource } from '../data-source';\nimport { Config } from '../entity/Config';\nimport { decrypt } from '../utilities/crypto.utility';\nimport { User } from '../entity/User';\n\ninterface MailOptions {\n  from: string;\n  to: string;\n  subject: string;\n  text: string;\n  html?: string;\n}\n\ninterface EmailCallback {\n  (error: string | null, info: string | null): void;\n}\n\n/**\n * @description Send email\n * @param {MailOptions} mailOptions\n * @param {EmailCallback} callback\n */\nexport const sendEmail = async (mailOptions: MailOptions, callback: EmailCallback) => {\n  try {\n    const configRepository = AppDataSource.getRepository(Config);\n    const config = await configRepository.findOne({ where: { id: 1 } });\n    \n    if (!config || !config.fromEmail || !config.fromEmailPassword) {\n      console.error('Missing email configuration');\n      return callback('Missing email configuration', null);\n    }\n    \n    // Decrypt the email password\n    let decryptedEmailPassword: string;\n    try {\n      decryptedEmailPassword = decrypt(config.fromEmailPassword);\n    } catch (error) {\n      console.error('Error decrypting email password:', error);\n      return callback('Error with email configuration', null);\n    }\n    \n    // Create a nodemailer transporter\n    const transporter = nodemailer.createTransport({\n      service: 'Gmail',\n      auth: {\n        user: config.fromEmail,\n        pass: decryptedEmailPassword,\n      },\n    });\n    \n    // Send the email\n    transporter.sendMail(mailOptions, (error, info) => {\n      if (error) {\n        console.error('Email sending error:', error);\n        return callback('Error sending email', null);\n      } else {\n        return callback(null, 'Email sent successfully');\n      }\n    });\n  } catch (error) {\n    console.error('Email service error:', error);\n    return callback('Email service error', null);\n  }\n};\n\n/**\n * @description Prepare account verification email\n * @param {string} uuid\n * @param {string} userEmail\n */\nexport const sendVerificationEmail = (uuid: string, userEmail: string) => {\n  const serverAddress = process.env.SERVER_ADDRESS || 'http://localhost';\n  const serverPort = process.env.PORT || '5000';\n  \n  const mailOptions = {\n    from: process.env.FROM_EMAIL || 'noreply@bulwark.com',\n    subject: 'Bulwark - Please confirm your email address',\n    text: `Please confirm your email address\n\nA Bulwark account was created with the email: ${userEmail}.\nAs an extra security measure, please verify this is the correct email address\nlinked to Bulwark by clicking the link below.\n\n${serverAddress}:${serverPort}/api/user/verify/${uuid}`,\n    to: userEmail,\n  };\n  \n  sendEmail(mailOptions, (err, info) => {\n    if (err) {\n      console.error('Error sending verification email:', err);\n    } else {\n      console.log('Verification email sent successfully');\n    }\n  });\n};\n\n/**\n * @description Prepare password reset email\n * @param {string} uuid\n * @param {string} userEmail\n */\nexport const sendForgotPasswordEmail = (uuid: string, userEmail: string) => {\n  const serverAddress = process.env.SERVER_ADDRESS || 'http://localhost';\n  const serverPort = process.env.PORT || '5000';\n  \n  const mailOptions = {\n    from: process.env.FROM_EMAIL || 'noreply@bulwark.com',\n    subject: 'Bulwark - Forgot Password Request',\n    text: `Forgot password request\n\nA password request has been initiated for the email: ${userEmail}.\nPlease click the link below to initiate the process.\n\n${serverAddress}:${serverPort}/#/password-reset/${uuid}`,\n    to: userEmail,\n  };\n  \n  sendEmail(mailOptions, (err, info) => {\n    if (err) {\n      console.error('Error sending forgot password email:', err);\n    } else {\n      console.log('Forgot password email sent successfully');\n    }\n  });\n};\n\n/**\n * @description Prepare invitation email\n * @param {string} uuid\n * @param {string} userEmail\n */\nexport const sendInvitationEmail = (uuid: string, userEmail: string) => {\n  const serverAddress = process.env.SERVER_ADDRESS || 'http://localhost';\n  const serverPort = process.env.PORT || '5000';\n  \n  const mailOptions = {\n    from: process.env.FROM_EMAIL || 'noreply@bulwark.com',\n    subject: 'Bulwark - Welcome!',\n    text: `You have been invited to Bulwark!\nPlease click the link below to initiate the process.\n\n${serverAddress}:${serverPort}/#/register/${uuid}`,\n    to: userEmail,\n  };\n  \n  sendEmail(mailOptions, (err, info) => {\n    if (err) {\n      console.error('Error sending invitation email:', err);\n    } else {\n      console.log('Invitation email sent successfully');\n    }\n  });\n};\n\n/**\n * @description Prepare email update request\n * @param {User} user\n * @param {EmailCallback} callback\n */\nexport const sendUpdateUserEmail = (user: User, callback: EmailCallback) => {\n  const serverAddress = process.env.SERVER_ADDRESS || 'http://localhost';\n  const serverPort = process.env.PORT || '5000';\n  \n  const mailOptions = {\n    from: process.env.FROM_EMAIL || 'noreply@bulwark.com',\n    subject: 'Bulwark - Email update request',\n    text: `An email update has been requested for Bulwark. Please click the link to confirm this update.\n    \n${serverAddress}:${serverPort}/#/email/validate/${user.uuid}`,\n    to: user.newEmail,\n  };\n  \n  sendEmail(mailOptions, (err, info) => {\n    if (err) {\n      callback(err, null);\n    } else {\n      callback(null, info);\n    }\n  });\n};"
  },
  {
    "path": "src/temp/empty.ts",
    "content": "// The purpose of this file is to ensure the temp directory is created during ts compilation\n"
  },
  {
    "path": "src/utilities/column-mapper.utility.ts",
    "content": "import { Column, ColumnType, ColumnOptions } from 'typeorm';\n\nconst mysqlSqliteTypeMapping: { [key: string]: ColumnType } = {\n  mediumblob: 'blob'\n};\n\nconst resolveDbType = (mySqlType: ColumnType): ColumnType => {\n  const isTestEnv = process.env.NODE_ENV === 'test';\n  if (isTestEnv && mySqlType.toString() in mysqlSqliteTypeMapping) {\n    return mysqlSqliteTypeMapping[mySqlType.toString()];\n  }\n  return mySqlType;\n};\n/**\n * @description Wrapper function for resolving DB Type\n * @param {ColumnOptions} columnOptions\n * @returns Custom Column\n */\nexport const DbAwareColumn = (columnOptions: ColumnOptions) => {\n  if (columnOptions.type) {\n    columnOptions.type = resolveDbType(columnOptions.type);\n  }\n  return Column(columnOptions);\n};\n/**\n * @description Addes nullabe column option if test DB is active\n * @returns Custom Column\n */\nexport const dynamicNullable = () => {\n  const columnOptions: ColumnOptions = {\n    nullable: process.env.NODE_ENV === 'test' ? true : false\n  };\n  return Column(columnOptions);\n};\n"
  },
  {
    "path": "src/utilities/crypto.utility.spec.ts",
    "content": "import { encrypt, decrypt } from './crypto.utility';\n\ndescribe('crypto utility', () => {\n  test('decrypt', () => {\n    const str = encrypt('password');\n    expect(decrypt(str)).toBe('password');\n  });\n});\n"
  },
  {
    "path": "src/utilities/crypto.utility.ts",
    "content": "import * as crypto from 'crypto';\n\n// Check for required environment variables\nif (!process.env.CRYPTO_SECRET || !process.env.CRYPTO_SALT) {\n  console.error('CRYPTO_SECRET and CRYPTO_SALT environment variables must be set!');\n}\n\nconst algorithm = 'aes-256-cbc';\nconst key = crypto.scryptSync(\n  process.env.CRYPTO_SECRET || 'default-secret-key-for-dev-only', \n  process.env.CRYPTO_SALT || 'default-salt-for-dev-only', \n  32\n);\n\n/**\n * @description Encrypt text using AES-256-CBC\n * @param {string} text - The text to encrypt\n * @returns {string} - JSON stringified object with IV and encrypted data\n */\nexport const encrypt = (text: string): string => {\n  try {\n    // Generate a random 16-byte initialization vector\n    const iv = crypto.randomBytes(16);\n    \n    // Create cipher\n    const cipher = crypto.createCipheriv(algorithm, Buffer.from(key), iv);\n    \n    // Encrypt the text\n    let encrypted = cipher.update(text);\n    encrypted = Buffer.concat([encrypted, cipher.final()]);\n    \n    // Return IV and encrypted data\n    return JSON.stringify({ \n      iv: iv.toString('hex'), \n      encryptedData: encrypted.toString('hex') \n    });\n  } catch (error) {\n    console.error('Encryption error:', error);\n    throw new Error('Failed to encrypt data');\n  }\n};\n\n/**\n * @description Decrypt text that was encrypted with encrypt()\n * @param {string} text - JSON stringified object with IV and encrypted data\n * @returns {string} - The decrypted text\n */\nexport const decrypt = (text: string): string => {\n  try {\n    // Parse the JSON string\n    const parsedText = JSON.parse(text);\n    \n    // Convert hex strings to buffers\n    const iv = Buffer.from(parsedText.iv, 'hex');\n    const encryptedText = Buffer.from(parsedText.encryptedData, 'hex');\n    \n    // Create decipher\n    const decipher = crypto.createDecipheriv(algorithm, Buffer.from(key), iv);\n    \n    // Decrypt the text\n    let decrypted = decipher.update(encryptedText);\n    decrypted = Buffer.concat([decrypted, decipher.final()]);\n    \n    return decrypted.toString();\n  } catch (error) {\n    console.error('Decryption error:', error);\n    throw new Error('Failed to decrypt data');\n  }\n};"
  },
  {
    "path": "src/utilities/file.utility.ts",
    "content": "import multer = require('multer');\nimport * as path from 'path';\nimport * as fs from 'fs';\n\n// Create temp directory if it doesn't exist\nconst tempDir = path.join(__dirname, '../temp');\nif (!fs.existsSync(tempDir)) {\n  fs.mkdirSync(tempDir, { recursive: true });\n}\n\nconst maxFileSize = 5000000; // 5 MB\n\nconst fileFilter = (req, file, cb) => {\n  // Ext validation\n  if (!(file.mimetype === 'image/png' || file.mimetype === 'image/jpeg')) {\n    req.fileExtError = 'Only JPEG and PNG file types allowed';\n    cb(null, false);\n  } else {\n    cb(null, true);\n  }\n};\n\nexport const upload = multer({\n  fileFilter,\n  limits: { fileSize: maxFileSize }\n}).single('file');\n\nexport const uploadArray = multer({\n  fileFilter,\n  limits: { fileSize: maxFileSize }\n}).array('screenshots');"
  },
  {
    "path": "src/utilities/jira.utility.spec.ts",
    "content": "import * as jiraUtility from './jira.utility';\nimport { createConnection, getConnection } from 'typeorm';\nimport { Asset } from '../entity/Asset';\nimport { Organization } from '../entity/Organization';\nimport { Vulnerability } from '../entity/Vulnerability';\nimport { Assessment } from '../entity/Assessment';\nimport { User } from '../entity/User';\nimport { ProblemLocation } from '../entity/ProblemLocation';\nimport { Resource } from '../entity/Resource';\nimport { File } from '../entity/File';\nimport { Jira } from '../entity/Jira';\nimport { Team } from '../entity/Team';\nimport { ApiKey } from '../entity/ApiKey';\nimport { Config } from '../entity/Config';\nimport { ReportAudit } from '../entity/ReportAudit';\n// TODO: Figure out how to mock jira-client API calls so that we do not hit the atlassian API's\ndescribe('jira utility db', () => {\n  beforeEach(async () => {\n    await createConnection({\n      type: 'sqlite',\n      database: ':memory:',\n      dropSchema: true,\n      entities: [\n        Config,\n        User,\n        Team,\n        Organization,\n        Asset,\n        Assessment,\n        Vulnerability,\n        ProblemLocation,\n        ReportAudit,\n        Resource,\n        Jira,\n        File,\n        ApiKey,\n      ],\n      synchronize: true,\n      logging: false,\n      name: 'default',\n    });\n  });\n  afterEach(() => {\n    const conn = getConnection('default');\n    return conn.close();\n  });\n  test('map risk to severity informational', () => {\n    let risk = 'Informational';\n    expect(jiraUtility.mapRiskToSeverity(risk)).toBe('Lowest');\n    risk = 'Low';\n    expect(jiraUtility.mapRiskToSeverity(risk)).toBe('Low');\n    risk = 'Medium';\n    expect(jiraUtility.mapRiskToSeverity(risk)).toBe('Medium');\n    risk = 'High';\n    expect(jiraUtility.mapRiskToSeverity(risk)).toBe('High');\n    risk = 'Critical';\n    expect(jiraUtility.mapRiskToSeverity(risk)).toBe('Highest');\n    risk = 'Random';\n    expect(jiraUtility.mapRiskToSeverity(risk)).toBe('');\n  });\n  test('map vuln to jira issue', async () => {\n    const resources: Resource[] = [];\n    const probLoc: ProblemLocation[] = [];\n    const assessment: Assessment = null;\n    const vuln: Vulnerability = {\n      id: 1,\n      jiraId: '',\n      impact: 'low',\n      risk: 'low',\n      likelihood: 'low',\n      systemic: 'Yes',\n      cvssScore: 1.0,\n      status: 'Open',\n      description: 'Test description',\n      detailedInfo: 'Test detailed description',\n      remediation: 'Test remediation',\n      name: 'SQL Injection',\n      resources,\n      problemLocations: probLoc,\n      assessment,\n      cvssUrl: 'http://test.com',\n      screenshots: null,\n    };\n    const firstProbLoc: ProblemLocation = {\n      id: 1,\n      target: '',\n      vulnerability: vuln,\n      location: '',\n    };\n    const resource: Resource = {\n      id: 1,\n      description: 'test',\n      vulnerability: vuln,\n      url: 'http://test.com',\n    };\n    vuln.problemLocations.push(firstProbLoc);\n    vuln.resources.push(resource);\n    expect(\n      await jiraUtility.mapVulnToJiraIssue(vuln, 'test-123')\n    ).toBeDefined();\n  });\n  test('get issue key', () => {\n    const jiraUrl = 'https://bulwark-test.atlassian.net/browse/tst-1';\n    expect(jiraUtility.getIssueKey(jiraUrl)).toBe('tst-1');\n  });\n});\n"
  },
  {
    "path": "src/utilities/jira.utility.ts",
    "content": "import { JiraIssue } from 'src/interfaces/jira/jira-issue.interface';\nimport { Vulnerability } from '../entity/Vulnerability';\nimport { JiraInit } from 'src/interfaces/jira/jira-init.interface';\nimport { decrypt } from './crypto.utility';\nimport { JiraResult } from 'src/interfaces/jira/jira-result.interface';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as mime from 'mime-types';\nimport { IssueLink } from 'src/interfaces/jira/jira-issue-link.interface';\n// Use fetch-node version 2 with CommonJS compatibility\nconst fetch = require('node-fetch');\nconst j2m = require('jira2md');\nconst JiraApi = require('jira-client');\nlet jira = null;\n\n/**\n * @description Entry function to create or update a JIRA ticket associated to a vulnerability\n * @param {JiraInit} jiraInit\n * @param {Vulnerability} vulnerability\n * @returns success: return object errror: error message\n */\n/* istanbul ignore next */\nexport const exportToJiraIssue = (vuln: Vulnerability, jiraInit: JiraInit): Promise<JiraResult> => {\n  return new Promise(async (resolve, reject) => {\n    try {\n      initializeJira(jiraInit);\n      const assessment = vuln.assessment;\n      const parentKey = getIssueKey(assessment.jiraId);\n      let assessmentIssue;\n      \n      try {\n        assessmentIssue = await jira.getIssue(parentKey);\n      } catch (err) {\n        console.error('Error fetching Jira issue:', err);\n        reject(\n          `An error has occurred. The JIRA issue ${getIssueKey(\n            vuln.jiraId\n          )} does not exist. Please update the JIRA URL and try again`\n        );\n        return;\n      }\n      \n      const jiraIssue = await mapVulnToJiraIssue(vuln, assessmentIssue.fields.project.id);\n      \n      if (!vuln.jiraId) {\n        try {\n          const result = await addNewJiraIssue(jiraIssue, parentKey, vuln);\n          resolve(result);\n        } catch (err) {\n          console.error('Error adding new Jira issue:', err);\n          reject(err);\n          return;\n        }\n      } else {\n        try {\n          const result = await updateExistingJiraIssue(\n            jiraIssue,\n            parentKey,\n            vuln,\n            assessmentIssue.fields.project.id,\n            jiraInit\n          );\n          resolve(result);\n        } catch (err) {\n          console.error('Error updating existing Jira issue:', err);\n          reject(err);\n          return;\n        }\n      }\n    } catch (error) {\n      console.error('Error in exportToJiraIssue:', error);\n      reject('An error occurred while exporting to Jira');\n    }\n  });\n};\n\n/**\n * @description Add new Jira issue\n * @param {any} jiraIssue\n * @param {string} parentUrl\n * @param {Vulnerability} vuln\n * @returns Jira result\n */\n/* istanbul ignore next */\nconst addNewJiraIssue = (jiraIssue: any, parentKey: string, vuln: Vulnerability): Promise<JiraResult> => {\n  return new Promise(async (resolve, reject) => {\n    try {\n      let saved: any;\n      try {\n        saved = await jira.addNewIssue(jiraIssue);\n        if (parentKey) {\n          await issueLink(parentKey, saved.key, (err, res) => {\n            if (err) {\n              console.error('Error linking issues:', err);\n            }\n          });\n        }\n      } catch (err) {\n        console.error('Error creating Jira issue:', err);\n        reject('The Jira export has failed.');\n        return;\n      }\n      \n      const returnObj: JiraResult = {\n        id: saved.id,\n        key: saved.key,\n        self: saved.self,\n        message: `The vulnerability for \"${vuln.name}\" has been exported to Jira. Key: ${saved.key}`\n      };\n      \n      if (vuln.screenshots && vuln.screenshots.length > 0) {\n        await attachImages(vuln, returnObj.id);\n      }\n      \n      resolve(returnObj);\n    } catch (error) {\n      console.error('Error in addNewJiraIssue:', error);\n      reject('An error occurred while adding a new Jira issue');\n    }\n  });\n};\n\n/**\n * @description Update existing Jira issue\n * @param {any} jiraIssue\n * @param {string} parentUrl\n * @param {Vulnerability} vuln\n * @returns Jira result\n */\n/* istanbul ignore next */\nconst updateExistingJiraIssue = (\n  jiraIssue: any,\n  parentKey: string,\n  vuln: Vulnerability,\n  projectId: string,\n  jiraInit: JiraInit\n): Promise<JiraResult> => {\n  return new Promise(async (resolve, reject) => {\n    try {\n      let issueKey: string;\n      let existingIssue: any;\n      \n      try {\n        issueKey = getIssueKey(vuln.jiraId);\n        existingIssue = await jira.getIssue(issueKey);\n        const updatedJiraIssue = await mapVulnToJiraIssue(vuln, projectId);\n        await jira.updateIssue(existingIssue.id, updatedJiraIssue);\n        \n        if (parentKey) {\n          await issueLink(parentKey, existingIssue.key, (err, res) => {\n            if (err) {\n              console.error('Error linking issues:', err);\n            }\n          });\n        }\n      } catch (err) {\n        console.error('Error updating Jira issue:', err);\n        reject(\n          `An error has occurred. The JIRA issue ${issueKey} does not exist. Please update the Jira field with a valid URL and try again.`\n        );\n        return;\n      }\n      \n      // Process attachments\n      if (vuln.screenshots && vuln.screenshots.length > 0) {\n        // Delete existing attachments\n        if (existingIssue.fields.attachment && existingIssue.fields.attachment.length > 0) {\n          for (const existScreenshot of existingIssue.fields.attachment) {\n            await deleteIssueAttachment(existScreenshot.id, jiraInit);\n          }\n        }\n        \n        // Add new attachments\n        await attachImages(vuln, existingIssue.id);\n      }\n      \n      const returnObj: JiraResult = {\n        id: existingIssue.id,\n        key: existingIssue.key,\n        self: existingIssue.self,\n        message: `The vulnerability for \"${vuln.name}\" has been updated in JIRA. Key: ${existingIssue.key}`\n      };\n      \n      resolve(returnObj);\n    } catch (error) {\n      console.error('Error in updateExistingJiraIssue:', error);\n      reject('An error occurred while updating the Jira issue');\n    }\n  });\n};\n\n/**\n * @description Links Jira ticket to parent ticket\n * @param callback\n * @param {string} issueKey\n * @param {string} parentUrl\n * @returns success: links jira issue to parent ticket\n */\n/* istanbul ignore next */\nconst issueLink = async (parentUrl: string, issueKey: string, callback) => {\n  try {\n    const parentKey = getIssueKey(parentUrl);\n    const link: IssueLink = {\n      outwardIssue: {\n        key: parentKey\n      },\n      inwardIssue: {\n        key: issueKey\n      },\n      type: {\n        name: 'Blocks'\n      }\n    };\n    \n    try {\n      await jira.issueLink(link);\n      return;\n    } catch (err) {\n      console.error('Error linking Jira issues:', err);\n      callback(`The JIRA Project \"${parentKey}\" does not exist.`);\n    }\n  } catch (error) {\n    console.error('Error in issueLink:', error);\n    callback('An error occurred while linking issues');\n  }\n};\n\n/**\n * @description Deletes Jira ticket attachment\n * @param {string} id\n * @param {JiraInit} jiraInit\n * @returns success: return object errror: error message\n */\n/* istanbul ignore next */\nconst deleteIssueAttachment = async (id: string, jiraInit: JiraInit) => {\n  try {\n    const auth = `${jiraInit.username}:${decrypt(jiraInit.apiKey)}`;\n    const response = await fetch(`https://${jiraInit.host}/rest/api/3/attachment/${id}`, {\n      method: 'DELETE',\n      headers: {\n        Authorization: `Basic ${Buffer.from(auth).toString('base64')}`\n      }\n    });\n    \n    console.info(`Attachment deletion status: ${response.status} ${response.statusText}`);\n  } catch (err) {\n    console.error('Error deleting Jira attachment:', err);\n  }\n};\n\n/**\n * @description Returns Jira issue key\n * @param {string} url\n * @returns string of key\n */\nexport const getIssueKey = (url: string): string => {\n  try {\n    if (!url) {\n      return '';\n    }\n    \n    const ary: string[] = url.split('/');\n    return ary[ary.length - 1];\n  } catch (error) {\n    console.error('Error getting issue key:', error);\n    return '';\n  }\n};\n\n/**\n * @description Initializes Jira\n * @param {JiraInit} jiraInit\n * @returns nothing\n */\n/* istanbul ignore next */\nconst initializeJira = (jiraInit: JiraInit) => {\n  try {\n    jira = new JiraApi({\n      protocol: 'https',\n      host: jiraInit.host,\n      username: jiraInit.username,\n      password: decrypt(jiraInit.apiKey),\n      apiVersion: '3',\n      strictSSL: true\n    });\n  } catch (error) {\n    console.error('Error initializing Jira:', error);\n    throw new Error('Failed to initialize Jira client');\n  }\n};\n\n/**\n * @description Attaches one-to-many images to Jira ticket\n * @param {string} issueId\n * @param {Vulnerability} vulnerability\n * @returns nothing\n */\n/* istanbul ignore next */\nconst attachImages = async (vuln: Vulnerability, issueId: string) => {\n  try {\n    if (!vuln.screenshots || vuln.screenshots.length === 0) {\n      return;\n    }\n    \n    // Create temp directory if it doesn't exist\n    const tempDir = path.join(__dirname, '../temp');\n    if (!fs.existsSync(tempDir)) {\n      fs.mkdirSync(tempDir, { recursive: true });\n    }\n    \n    for (const screenshot of vuln.screenshots) {\n      try {\n        // Get file extension from mimetype\n        const extension = mime.extension(screenshot.mimetype) || 'jpg';\n        const filename = screenshot.originalname || `screenshot_${Date.now()}.${extension}`;\n        const filepath = path.join(tempDir, filename);\n        \n        // Write buffer to temporary file\n        await fs.writeFileSync(filepath, screenshot.buffer);\n        const stream = fs.createReadStream(filepath);\n        \n        // Add attachment to Jira issue\n        await jira.addAttachmentOnIssue(issueId, stream);\n        \n        // Clean up temporary file\n        await fs.unlinkSync(filepath);\n      } catch (err) {\n        console.error('Error attaching image to Jira issue:', err);\n      }\n    }\n  } catch (error) {\n    console.error('Error in attachImages:', error);\n  }\n};\n\n/**\n * @description Maps Vulnerability information to Jira ticket\n * @param {string} projectId\n * @param {Vulnerability} vulnerability\n * @returns JiraIssue object\n */\nexport const mapVulnToJiraIssue = async (vuln: Vulnerability, projectId: string) => {\n  try {\n    const probLocRows = await dynamicProbLocTableRows(vuln);\n    const resourceRows = await dynamicResourceTableRows(vuln);\n    \n    const jiraIssue: JiraIssue = {\n      update: {},\n      fields: {\n        project: {\n          id: projectId.toString()\n        },\n        priority: {\n          name: mapRiskToSeverity(vuln.risk)\n        },\n        summary: vuln.name,\n        description: {\n          type: 'doc',\n          version: 1,\n          content: [\n            {\n              type: 'paragraph',\n              content: [\n                {\n                  type: 'text',\n                  text: `Impact: ${vuln.impact}`,\n                  marks: [\n                    {\n                      type: 'strong'\n                    }\n                  ]\n                }\n              ]\n            },\n            {\n              type: 'paragraph',\n              content: [\n                {\n                  type: 'text',\n                  text: `Likelihood: ${vuln.likelihood}`,\n                  marks: [\n                    {\n                      type: 'strong'\n                    }\n                  ]\n                }\n              ]\n            },\n            {\n              type: 'paragraph',\n              content: [\n                {\n                  type: 'text',\n                  text: `Overall Risk: ${vuln.risk}`,\n                  marks: [\n                    {\n                      type: 'strong'\n                    }\n                  ]\n                }\n              ]\n            },\n            {\n              type: 'paragraph',\n              content: [\n                {\n                  type: 'text',\n                  text: `Systemic: ${vuln.systemic}`,\n                  marks: [\n                    {\n                      type: 'strong'\n                    }\n                  ]\n                }\n              ]\n            },\n            {\n              type: 'paragraph',\n              content: [\n                {\n                  type: 'text',\n                  text: `CVSS Score: ${vuln.cvssScore}`,\n                  marks: [\n                    {\n                      type: 'link',\n                      attrs: {\n                        href: vuln.cvssUrl\n                      }\n                    }\n                  ]\n                }\n              ]\n            },\n            {\n              type: 'table',\n              attrs: {\n                isNumberColumnEnabled: false,\n                layout: 'default'\n              },\n              content: probLocRows\n            },\n            {\n              type: 'paragraph',\n              content: [\n                {\n                  text: j2m.to_jira(vuln.description || ''),\n                  type: 'text'\n                }\n              ]\n            },\n            {\n              type: 'paragraph',\n              content: [\n                {\n                  text: j2m.to_jira(vuln.detailedInfo || ''),\n                  type: 'text'\n                }\n              ]\n            },\n            {\n              type: 'paragraph',\n              content: [\n                {\n                  type: 'text',\n                  text: 'Remediation',\n                  marks: [\n                    {\n                      type: 'strong'\n                    }\n                  ]\n                }\n              ]\n            },\n            {\n              type: 'paragraph',\n              content: [\n                {\n                  text: j2m.to_jira(vuln.remediation || ''),\n                  type: 'text'\n                }\n              ]\n            },\n            {\n              type: 'paragraph',\n              content: [\n                {\n                  type: 'text',\n                  text: 'Resources',\n                  marks: [\n                    {\n                      type: 'strong'\n                    }\n                  ]\n                }\n              ]\n            },\n            {\n              type: 'table',\n              attrs: {\n                isNumberColumnEnabled: false,\n                layout: 'default'\n              },\n              content: resourceRows\n            }\n          ]\n        },\n        issuetype: {\n          name: 'Bug'\n        }\n      }\n    };\n    \n    return jiraIssue;\n  } catch (error) {\n    console.error('Error mapping vulnerability to Jira issue:', error);\n    throw new Error('Failed to map vulnerability to Jira issue');\n  }\n};\n\n/**\n * @description Dynamically creates Jira table for problem locations\n * @param {Vulnerability} vulnerability\n * @returns table rows\n */\nconst dynamicProbLocTableRows = async (vuln: Vulnerability) => {\n  try {\n    const rows = [];\n    rows.push({\n      type: 'tableRow',\n      content: [\n        {\n          type: 'tableHeader',\n          attrs: {},\n          content: [\n            {\n              type: 'paragraph',\n              content: [\n                {\n                  type: 'text',\n                  text: 'Problem Location'\n                }\n              ]\n            }\n          ]\n        },\n        {\n          type: 'tableHeader',\n          attrs: {},\n          content: [\n            {\n              type: 'paragraph',\n              content: [\n                {\n                  type: 'text',\n                  text: 'Target'\n                }\n              ]\n            }\n          ]\n        }\n      ]\n    });\n    \n    if (vuln.problemLocations && vuln.problemLocations.length) {\n      for (const probLoc of vuln.problemLocations) {\n        const row = {\n          type: 'tableRow',\n          content: [\n            {\n              type: 'tableCell',\n              attrs: {},\n              content: [\n                {\n                  type: 'paragraph',\n                  content: [\n                    {\n                      type: 'text',\n                      text: probLoc.location || ''\n                    }\n                  ]\n                }\n              ]\n            },\n            {\n              type: 'tableCell',\n              attrs: {},\n              content: [\n                {\n                  type: 'paragraph',\n                  content: [\n                    {\n                      type: 'text',\n                      text: probLoc.target || ''\n                    }\n                  ]\n                }\n              ]\n            }\n          ]\n        };\n        rows.push(row);\n      }\n    }\n    \n    return rows;\n  } catch (error) {\n    console.error('Error creating problem location table rows:', error);\n    return [];\n  }\n};\n\n/**\n * @description Dynamically creates Jira table for resources\n * @param {Vulnerability} vulnerability\n * @returns table rows\n */\nconst dynamicResourceTableRows = async (vuln: Vulnerability) => {\n  try {\n    const rows = [];\n    rows.push({\n      type: 'tableRow',\n      content: [\n        {\n          type: 'tableHeader',\n          attrs: {},\n          content: [\n            {\n              type: 'paragraph',\n              content: [\n                {\n                  type: 'text',\n                  text: 'Description'\n                }\n              ]\n            }\n          ]\n        },\n        {\n          type: 'tableHeader',\n          attrs: {},\n          content: [\n            {\n              type: 'paragraph',\n              content: [\n                {\n                  type: 'text',\n                  text: 'Resource URL'\n                }\n              ]\n            }\n          ]\n        }\n      ]\n    });\n    \n    if (vuln.resources && vuln.resources.length) {\n      for (const resource of vuln.resources) {\n        const row = {\n          type: 'tableRow',\n          content: [\n            {\n              type: 'tableCell',\n              attrs: {},\n              content: [\n                {\n                  type: 'paragraph',\n                  content: [\n                    {\n                      type: 'text',\n                      text: resource.description || ''\n                    }\n                  ]\n                }\n              ]\n            },\n            {\n              type: 'tableCell',\n              attrs: {},\n              content: [\n                {\n                  type: 'paragraph',\n                  content: [\n                    {\n                      type: 'text',\n                      text: resource.url || '',\n                      marks: [\n                        {\n                          type: 'link',\n                          attrs: {\n                            href: resource.url\n                          }\n                        }\n                      ]\n                    }\n                  ]\n                }\n              ]\n            }\n          ]\n        };\n        rows.push(row);\n      }\n    }\n    \n    return rows;\n  } catch (error) {\n    console.error('Error creating resource table rows:', error);\n    return [];\n  }\n};\n\n/**\n * @description Maps overall risk severity to Jira priority\n * @param {Vulnerability} vulnerability\n * @returns jira priority string\n */\nexport const mapRiskToSeverity = (risk: string) => {\n  switch (risk) {\n    case 'Informational':\n      return 'Lowest';\n    case 'Low':\n      return 'Low';\n    case 'Medium':\n      return 'Medium';\n    case 'High':\n      return 'High';\n    case 'Critical':\n      return 'Highest';\n    default:\n      return '';\n  }\n};"
  },
  {
    "path": "src/utilities/password.utility.spec.ts",
    "content": "import { generateHash, updatePassword, compare } from './password.utility';\n\ndescribe('password utility', () => {\n  test('generateHash to work', async () => {\n    const password = 'qwerty';\n    const hashedPassword = await generateHash(password);\n    expect(hashedPassword).toBeDefined();\n  });\n\n  test('compare hash success', async () => {\n    const oldPassword = await generateHash('qwerty');\n    const currentPassword = 'qwerty';\n    await expect(compare(currentPassword, oldPassword)).resolves.toBeTruthy();\n  });\n\n  test('compare hash failure', async () => {\n    const oldPassword = await generateHash('qwerty');\n    const currentPassword = 'qwerty2';\n    await expect(compare(currentPassword, oldPassword)).resolves.toBeFalsy();\n  });\n\n  test('compare hash bcrypt failure', async () => {\n    await expect(compare(null, 'a')).rejects.toBe('Bcrypt comparison failure');\n  });\n\n  test('password updated successfully', async () => {\n    const currentPassword = 'qwerty';\n    const oldPassword = await generateHash(currentPassword);\n    const newPassword = 'newQwerty';\n    const result = await updatePassword(\n      oldPassword,\n      currentPassword,\n      newPassword\n    );\n    expect(result).toEqual(expect.anything());\n  });\n\n  test('password updated failure', async () => {\n    const currentPassword = 'qwerty2';\n    const oldPassword = await generateHash('1234');\n    const newPassword = 'newQwerty';\n    await expect(\n      updatePassword(oldPassword, currentPassword, newPassword)\n    ).rejects.toBe('The current password is incorrect');\n  });\n});\n"
  },
  {
    "path": "src/utilities/password.utility.ts",
    "content": "import * as bcrypt from 'bcrypt';\n// tslint:disable-next-line: no-var-requires\nconst passwordValidator = require('password-validator');\n\n// Create a password schema with requirements\nexport const passwordSchema = new passwordValidator();\npasswordSchema\n  .is()\n  .min(12) // Minimum length 12\n  .has()\n  .uppercase() // Must have uppercase letters\n  .has()\n  .lowercase() // Must have lowercase letters\n  .has()\n  .digits() // Must have digits\n  .has()\n  .symbols(); // Must have symbols\n\n// Number of salt rounds for bcrypt\nconst saltRounds = 10;\n\n/**\n * @description Generate hash from password\n * @param {string} password - The plaintext password\n * @returns {Promise<string>} - The hashed password\n */\nexport const generateHash = (password: string): Promise<string> => {\n  return new Promise((resolve, reject) => {\n    bcrypt.genSalt(saltRounds, async (err, salt) => {\n      if (err) {\n        console.error('bcrypt salt generation error:', err);\n        reject('Bcrypt hash failed: ' + err);\n      } else {\n        bcrypt.hash(password, salt, async (hashErr, hash: string) => {\n          if (hashErr) {\n            console.error('bcrypt hash error:', hashErr);\n            reject('Bcrypt hash failed: ' + hashErr);\n          } else {\n            resolve(hash);\n          }\n        });\n      }\n    });\n  });\n};\n\n/**\n * @description Update user password\n * @param {string} hashedCurrentPassword - The current hashed password\n * @param {string} currentPassword - The plaintext current password\n * @param {string} newPassword - The plaintext new password\n * @returns {Promise<string>} - The newly hashed password\n */\nexport const updatePassword = (\n  hashedCurrentPassword: string,\n  currentPassword: string,\n  newPassword: string\n): Promise<string> => {\n  return new Promise(async (resolve, reject) => {\n    try {\n      // Verify the current password\n      const valid = await compare(currentPassword, hashedCurrentPassword);\n      \n      if (!valid) {\n        reject('The current password is incorrect');\n        return;\n      }\n      \n      // Generate a hash for the new password\n      const newPasswordHash = await generateHash(newPassword);\n      resolve(newPasswordHash);\n    } catch (error) {\n      console.error('Password update error:', error);\n      reject('An error occurred while updating the password');\n    }\n  });\n};\n\n/**\n * @description Compare password hash\n * @param {string|string[]} currentPassword - The plaintext password to check\n * @param {string} hashedCurrentPassword - The stored hashed password\n * @returns {Promise<boolean>} - Whether the password is valid\n */\nexport const compare = (\n  currentPassword: string | string[],\n  hashedCurrentPassword: string\n): Promise<boolean> => {\n  return new Promise((resolve, reject) => {\n    if (!currentPassword || !hashedCurrentPassword) {\n      reject('Password or hash is missing');\n      return;\n    }\n    \n    bcrypt.compare(currentPassword, hashedCurrentPassword, (err, valid) => {\n      if (err) {\n        console.error('bcrypt comparison error:', err);\n        reject('Bcrypt comparison failure');\n        return;\n      }\n      \n      resolve(valid);\n    });\n  });\n};"
  },
  {
    "path": "src/utilities/puppeteer.utility.ts",
    "content": "import { UserRequest } from '../interfaces/user-request.interface';\nimport { Response } from 'express';\nimport { insertReportAuditRecord } from '../routes/report-audit.controller';\nimport puppeteer from 'puppeteer';\nimport * as path from 'path';\nimport * as fs from 'fs';\nimport { hasAssetReadAccess } from './role.utility';\n\n/**\n * @description API backend for report generation with Puppeteer\n * @param {UserRequest} req orgId, assetId, assessmentId\n * @param {Response} res contains all data associated and generates a\n * new html page with PDF Report\n * @returns a new page generated by Puppeteer with a Report in PDF format\n */\nexport const generateReport = async (req: UserRequest, res: Response) => {\n  try {\n    if (!req.body.orgId || !req.body.assetId || !req.body.assessmentId) {\n      return res.status(400).send('Invalid report parameters');\n    }\n    \n    const hasReadAccess = await hasAssetReadAccess(req, +req.body.assetId);\n    if (!hasReadAccess) {\n      return res\n        .status(404)\n        .json('Failed to generate report. Please contact an administrator.');\n    }\n    \n    const url =\n      process.env.NODE_ENV === 'production'\n        ? `${process.env.SERVER_ADDRESS}:${process.env.PORT}/#/organization/${req.body.orgId}/asset/${req.body.assetId}/assessment/${req.body.assessmentId}/report/puppeteer`\n        : `${process.env.DEV_URL}/#/organization/${req.body.orgId}/asset/${req.body.assetId}/assessment/${req.body.assessmentId}/report/puppeteer`;\n    \n    // Create temp directory if it doesn't exist\n    const tempDir = path.join(__dirname, '../temp');\n    if (!fs.existsSync(tempDir)) {\n      fs.mkdirSync(tempDir, { recursive: true });\n    }\n    \n    const filePath = path.join(tempDir, 'temp_report.pdf');\n    const jwtToken = req.headers.authorization;\n    \n    const browser = await puppeteer.launch({\n      args: ['--no-sandbox', '--disable-setuid-sandbox'],\n      headless: true  // Use standard headless mode for compatibility\n    });\n    \n    const page = await browser.newPage();\n    \n    // Set JWT token in localStorage\n    await page.evaluateOnNewDocument((token) => {\n      localStorage.clear();\n      localStorage.setItem('AUTH_TOKEN', token);\n    }, jwtToken);\n    \n    await page.goto(url, { waitUntil: 'networkidle0' });\n    \n    // Generate PDF\n    await page.pdf({ \n      path: filePath, \n      format: 'a4',\n      printBackground: true,\n      margin: {\n        top: '20px',\n        right: '20px', \n        bottom: '20px',\n        left: '20px'\n      }\n    });\n    \n    await browser.close();\n    \n    // Send file to client\n    const file = fs.createReadStream(filePath);\n    const stat = fs.statSync(filePath);\n    \n    res.setHeader('Content-Length', stat.size);\n    res.setHeader('Content-Type', 'application/pdf');\n    res.setHeader('Content-Disposition', 'attachment; filename=report.pdf');\n    \n    file.pipe(res);\n    \n    // Record the report generation\n    await insertReportAuditRecord(+req.user, req.body.assessmentId);\n    \n    // Delete the temporary file after it's sent\n    file.on('close', () => {\n      fs.unlink(filePath, (err) => {\n        if (err) {\n          console.error('Error removing temporary PDF file:', err);\n        } else {\n          console.info('Temporary PDF file removed');\n        }\n      });\n    });\n    \n  } catch (error) {\n    console.error('Error generating report:', error);\n    return res.status(500).json('An error occurred while generating the report');\n  }\n};"
  },
  {
    "path": "src/utilities/role.utility.ts",
    "content": "import { ROLE } from '../enums/roles-enum';\nimport { UserRequest } from '../interfaces/user-request.interface';\n\n/**\n * @description Check if user has access to an organization\n * @param {UserRequest} req\n * @param {number} rqstdOrgId\n * @returns boolean\n */\nexport const hasOrgAccess = (req: UserRequest, rqstdOrgId: number): boolean => {\n  if (!req.userOrgs || !req.userOrgs.length) {\n    return false;\n  }\n  \n  return req.userOrgs.includes(rqstdOrgId);\n};\n\n/**\n * @description Check if user has read access to an asset\n * @param {UserRequest} req\n * @param {number} assetId\n * @returns Promise<boolean>\n */\nexport const hasAssetReadAccess = async (req: UserRequest, assetId: number): Promise<boolean> => {\n  // Ensure we have userAssets array\n  if (!req.userAssets) {\n    return false;\n  }\n  \n  return req.userAssets.includes(assetId);\n};\n\n/**\n * @description Check if user has write access to an asset\n * @param {UserRequest} req\n * @param {number} assetId\n * @returns Promise<boolean>\n */\nexport const hasAssetWriteAccess = async (req: UserRequest, assetId: number): Promise<boolean> => {\n  // Admins have write access to all assets\n  if (req.isAdmin) {\n    return true;\n  }\n  \n  // If user is not an admin, check if they belong to a tester team with access to this asset\n  if (!req.userTeams || !req.userTeams.length) {\n    return false;\n  }\n  \n  const testerTeams = req.userTeams.filter((team) => team.role === ROLE.TESTER);\n  \n  if (!testerTeams || !testerTeams.length) {\n    return false;\n  }\n  \n  // Check if any of the user's tester teams have access to this asset\n  for (const testerTeam of testerTeams) {\n    if (!testerTeam.assets || !testerTeam.assets.length) {\n      continue;\n    }\n    \n    for (const asset of testerTeam.assets) {\n      if (asset.id === assetId) {\n        return true;\n      }\n    }\n  }\n  \n  return false;\n};"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"lib\": [\"es6\", \"dom\"],\n    \"target\": \"es2017\",\n    \"module\": \"commonjs\",\n    \"moduleResolution\": \"node\",\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"outDir\": \"dist\",\n    \"removeComments\": true,\n    \"baseUrl\": \"./\",\n    \"sourceMap\": true,\n    \"types\": [\"@types/jest\"],\n  },\n  \"exclude\": [\"./frontend\", \"./migration\"],\n  \"include\": [\"src/**/*\"]\n}\n"
  },
  {
    "path": "tslint.json",
    "content": "{\n  \"extends\": [\"tslint:latest\", \"tslint-config-prettier\"],\n  \"rules\": {\n    \"max-line-length\": {\n      \"options\": [120]\n    },\n    \"quotemark\": [true, \"single\", \"avoid-escape\", \"avoid-template\"],\n    \"new-parens\": true,\n    \"no-arg\": true,\n    \"no-bitwise\": true,\n    \"no-conditional-assignment\": true,\n    \"no-consecutive-blank-lines\": false,\n    \"no-console\": {\n      \"severity\": \"warning\",\n      \"options\": [\"debug\", \"info\", \"log\", \"time\", \"timeEnd\", \"trace\"]\n    },\n    \"arrow-parens\": false,\n    \"trailing-comma\": false,\n    \"ordered-imports\": false,\n    \"no-var-requires\": false\n  },\n  \"jsRules\": {\n    \"max-line-length\": {\n      \"options\": [120]\n    }\n  }\n}\n"
  }
]