From e75de81e3981aa0a18ae39d359d61b81d271632e Mon Sep 17 00:00:00 2001 From: Senad Uka Date: Sun, 17 Nov 2024 19:41:30 +0100 Subject: [PATCH] SUrvey now works --- application/controllers/advanced.go | 96 ++++ application/controllers/signup.go | 4 +- application/layouts/main.html | 5 +- application/server.go | 1 + application/static/css/main.css | 4 + application/static/js/formHandling.js | 164 ++++++ application/static/js/signup.js | 44 -- application/views/advanced.html | 198 +++++++ application/views/signup.html | 736 ++++++++++++++++++++++---- db/advancedProfile.go | 43 ++ db/basicProfile.go | 48 +- db/db.go | 53 +- db/utils.go | 15 +- 13 files changed, 1210 insertions(+), 201 deletions(-) create mode 100644 application/controllers/advanced.go create mode 100644 application/static/css/main.css create mode 100644 application/static/js/formHandling.js delete mode 100644 application/static/js/signup.js create mode 100644 application/views/advanced.html create mode 100644 db/advancedProfile.go diff --git a/application/controllers/advanced.go b/application/controllers/advanced.go new file mode 100644 index 0000000..8da6ead --- /dev/null +++ b/application/controllers/advanced.go @@ -0,0 +1,96 @@ +package controllers + +import ( + "html/template" + "log" + "net/http" + "net/url" + "os" + "path/filepath" + "risklet/db" +) + +func Advanced(w http.ResponseWriter, r *http.Request) { + if r.Method == "GET" { + handleAdvancedGet(w, r) + } else if r.Method == "POST" { + handleAdvancedPost(w, r) + } else { + http.Error(w, "Method not allowed.", http.StatusMethodNotAllowed) + return + } +} + +func handleAdvancedPost(w http.ResponseWriter, r *http.Request) { + if err := r.ParseForm(); err != nil { + log.Println("Error processing form: ", err) + handleAdvancedGet(w, r) + } + company := createCompany(r.PostForm) + companyId, err := db.InsertCompany(company) + if err != nil { + log.Println("Error inserting company into database ", err) + handleAdvancedGet(w, r) + } + + advancedProfile := createAdvancedProfile(companyId, r.PostForm) + + _, err = db.InsertAdvancedProfile(advancedProfile) + if err != nil { + log.Println("Error inserting into database ", err) + handleAdvancedGet(w, r) + } +} + +func handleAdvancedGet(w http.ResponseWriter, r *http.Request) { + companyId := r.PathValue("companyId") + + lp := filepath.Join("application", "layouts", "main.html") + fp := filepath.Join("application", "views", "advanced.html") + + log.Println("Hitting Advanced") + + // Return a 404 if the template doesn't exist + info, err := os.Stat(fp) + if err != nil { + if os.IsNotExist(err) { + http.NotFound(w, r) + return + } + } + + // Return a 404 if the request is for a directory + if info.IsDir() { + http.NotFound(w, r) + return + } + + tmpl, err := template.ParseFiles(lp, fp) + if err != nil { + // Log the detailed error + log.Print(err.Error()) + // Return a generic "Internal Server Error" message + http.Error(w, http.StatusText(500), 500) + return + } + + err = tmpl.ExecuteTemplate(w, "main.html", companyId) + if err != nil { + log.Print(err.Error()) + http.Error(w, http.StatusText(500), 500) + } +} + +func createAdvancedProfile(companyId int, f url.Values) db.AdvancedProfile { + return db.AdvancedProfile{ + CompanyId: companyId, + GeographicDistribution: f.Get("GeographicDistribution"), + CustomerConcentration: f.Get("CustomerConcentration"), + ProductServicePortfolio: f.Get("ProductServicePortfolio"), + OrganizationalCulture: f.Get("OrganizationalCulture"), + SupplierDiversity: f.Get("SupplierDiversity"), + TechnologicalInfrastructure: f.Get("TechnologicalInfrastructure"), + IntellectualProperty: f.Get("IntellectualProperty"), + ManagementTeamExperience: f.Get("ManagementTeamExperience"), + } +} diff --git a/application/controllers/signup.go b/application/controllers/signup.go index c372f8c..ef7762e 100644 --- a/application/controllers/signup.go +++ b/application/controllers/signup.go @@ -41,13 +41,15 @@ func handlePost(w http.ResponseWriter, r *http.Request) { handleGet(w, r) } + http.Redirect(w, r, "/thankyou", http.StatusSeeOther) } func handleGet(w http.ResponseWriter, r *http.Request) { lp := filepath.Join("application", "layouts", "main.html") fp := filepath.Join("application", "views", "signup.html") - log.Println("Hitting Signup") + // add a CSP header to allow only same-origin scripts + w.Header().Set("Content-Security-Policy", "script-src 'unsafe-eval' 'self'") // Return a 404 if the template doesn't exist info, err := os.Stat(fp) diff --git a/application/layouts/main.html b/application/layouts/main.html index 1dde195..a4c07a4 100644 --- a/application/layouts/main.html +++ b/application/layouts/main.html @@ -4,8 +4,9 @@ Hello, World! - + + +
{{block "content" .}} {{end}} +
{{block "bottom" .}} {{end}} diff --git a/application/server.go b/application/server.go index e5d1cbc..2338a05 100644 --- a/application/server.go +++ b/application/server.go @@ -9,5 +9,6 @@ func SetupAppServer() { fs := http.FileServer(http.Dir("./application/static")) http.Handle("GET /static/", http.StripPrefix("/static/", fs)) http.HandleFunc("/signup/", controllers.Signup) + http.HandleFunc("/advanced/{companyString}", controllers.Advanced) http.HandleFunc("/", controllers.Index) } diff --git a/application/static/css/main.css b/application/static/css/main.css new file mode 100644 index 0000000..266a822 --- /dev/null +++ b/application/static/css/main.css @@ -0,0 +1,4 @@ + +body { + font-family: 'Jost', sans-serif; +} \ No newline at end of file diff --git a/application/static/js/formHandling.js b/application/static/js/formHandling.js new file mode 100644 index 0000000..1adef60 --- /dev/null +++ b/application/static/js/formHandling.js @@ -0,0 +1,164 @@ +document.addEventListener('DOMContentLoaded', (event) => { + const form = document.querySelector('form'); + const formElements = form.elements; + + // Load saved form state + /* loadFormState(formElements); + + // Save form state on change + form.addEventListener('change', () => { + saveFormState(formElements); + }); */ + + setUpNavigation() +}); + +function nextQuestion() { + document.currentQuestion++; + hideNavElementsAndQuestions(); + showQuestion(`q${document.currentQuestion}`); + setButtonVisiblity('back', true); + setButtonVisiblity('next', true); + if (document.currentQuestion === document.lastQuestion) { + setButtonVisiblity('next', false); + setButtonVisiblity('submit', true); + } + setNextButtonAvailability(); +} + +function previousQuestion() { + if (document.currentQuestion > 0) { + document.currentQuestion--; + hideNavElementsAndQuestions(); + showQuestion(`q${document.currentQuestion}`); + setButtonVisiblity('next', true); + setButtonVisiblity('submit', false); + document.nextEnabled = true; + } + setButtonVisiblity('back', document.currentQuestion !== 0); + setNextButtonAvailability(); +} + +function setUpNavigation() { + const questions = document.querySelectorAll('.question'); + + document.currentQuestion = 0; + document.nextEnabled = false; + document.lastQuestion = questions.length - 1; + + hideNavElementsAndQuestions(); + showQuestion(`q${document.currentQuestion}`); + setButtonVisiblity('next', true); + + + const nextButton = document.getElementById('next'); + const backButton = document.getElementById('back'); + nextButton.addEventListener('click', nextQuestion); + backButton.addEventListener('click', previousQuestion); + + setNextButtonAvailability(); + + // check if next button should be enabled on every input, checkbox and radio button bellow class of .question change + const inputs = document.querySelectorAll('.question input, .question select, .question textarea'); + inputs.forEach(input => { + input.addEventListener('change', setNextButtonAvailability); + }); + + +} + +function setNextButtonAvailability() { + console.log('Setting next button availability'); + // check if current question is answered + // and then enable the next button, disable it otherwise + const currentQuestion = document.getElementById(`q${document.currentQuestion}`); + const nextButton = document.getElementById('next'); + const submitButton = document.getElementById('submit'); + // check if any input in the current question is checked, or filled in case it is a text input + let nextEnabled = false; + const inputs = currentQuestion.querySelectorAll('input, select, textarea'); + for (let input of inputs) { + // if the input is not visible, skip it + if (input.checkVisibility() === false) { + continue; + } + if (input.type === 'checkbox' || input.type === 'radio') { + if (input.checked) { + nextEnabled = true; + break; + } + } else { + if (input.value) { + nextEnabled = true; + break; + } + } + } + nextButton.disabled = !nextEnabled; + submitButton.disabled = !nextEnabled; +} + +function saveFormState(elements) { + const formState = {}; + for (let element of elements) { + if (element.name) { + if (element.type === 'select-multiple') { + formState[element.name] = Array.from(element.selectedOptions).map(option => option.value); + } else if (element.type === 'checkbox' || element.type === 'radio') { + formState[element.name] = element.checked ? element.value : formState[element.name] || null; + } else { + formState[element.name] = element.value; + } + } + } + localStorage.setItem('formState', JSON.stringify(formState)); +} + +function loadFormState(elements) { + const formState = JSON.parse(localStorage.getItem('formState')); + if (formState) { + for (let element of elements) { + if (element.name && formState[element.name] !== undefined) { + if (element.type === 'select-multiple') { + Array.from(element.options).forEach(option => { + option.selected = formState[element.name].includes(option.value); + }); + } else if (element.type === 'checkbox' || element.type === 'radio') { + element.checked = formState[element.name] === element.value; + } else { + element.value = formState[element.name]; + } + } + } + } +} + +function hideNavElementsAndQuestions() { + const questions = document.querySelectorAll('.question'); + questions.forEach(question => { + // add bootstrap hidden class to the element + question.classList.add('d-none'); + }); + const nextButton = document.getElementById('next'); + const backButton = document.getElementById('back'); + const submitButton = document.getElementById('submit'); + nextButton.classList.add('d-none'); + backButton.classList.add('d-none'); + submitButton.classList.add('d-none'); +} + +function showQuestion(questionId) { + const question = document.getElementById(questionId); + question.classList.remove('d-none'); +} + +function setButtonVisiblity(buttonId, visible) { + const button = document.getElementById(buttonId); + if (visible) { + button.classList.remove('d-none'); + } else { + button.classList.add('d-none'); + } +} + + diff --git a/application/static/js/signup.js b/application/static/js/signup.js deleted file mode 100644 index 7207f6e..0000000 --- a/application/static/js/signup.js +++ /dev/null @@ -1,44 +0,0 @@ -document.addEventListener('DOMContentLoaded', (event) => { - const form = document.querySelector('form'); - const formElements = form.elements; - - // Load saved form state - loadFormState(formElements); - - // Save form state on change - form.addEventListener('change', () => { - saveFormState(formElements); - }); -}); - -function saveFormState(elements) { - const formState = {}; - for (let element of elements) { - if (element.name) { - if (element.type === 'select-multiple') { - formState[element.name] = Array.from(element.selectedOptions).map(option => option.value); - } else { - formState[element.name] = element.value; - } - } - } - localStorage.setItem('formState', JSON.stringify(formState)); -} - -function loadFormState(elements) { - const formState = JSON.parse(localStorage.getItem('formState')); - if (formState) { - for (let element of elements) { - if (element.name && formState[element.name]) { - if (element.type === 'select-multiple') { - Array.from(element.options).forEach(option => { - option.selected = formState[element.name].includes(option.value); - }); - } else { - element.value = formState[element.name]; - } - } - } - } -} - diff --git a/application/views/advanced.html b/application/views/advanced.html new file mode 100644 index 0000000..c7a56ec --- /dev/null +++ b/application/views/advanced.html @@ -0,0 +1,198 @@ +{{define "content"}} +
+
+
+

Advanced Risk Assessment

+
+
+ +
+ +
+ + Determines exposure to different cybersecurity regulations. +
+ + +
+ +
+ + Assesses potential impact of data breaches. +
+ + +
+ +
+ + Defines data protection requirements. +
+ + +
+ +
+ + Indicates the variety of systems requiring protection. +
+ + +
+ +
+ + Assesses third-party cybersecurity risks. +
+ + +
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ Determines specific cybersecurity controls. +
+ + +
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ Evaluates cybersecurity needs based on IP ownership. +
+ + +
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ Identifies required compliance frameworks. +
+ + +
+ +
+ + Evaluates potential for cascade failures. +
+ + +
+ +
+ + Determines the scope of remote access security requirements. +
+ +
+ + +
+
+
+
+
+{{end}} + +{{define "bottom"}} + +{{end}} diff --git a/application/views/signup.html b/application/views/signup.html index 19ea0dd..2b127e8 100644 --- a/application/views/signup.html +++ b/application/views/signup.html @@ -2,74 +2,180 @@
-

Sign Up

+

Risk Assessment Questions

+
-
- + +
+ +
- Name of the Organization that will appear in the report. + Name of the Organization that will appear in the + report.
-
- + + +
+ +
- Email of the person responsible for using Risklet. Report and magic link for log in will be sent to this email. + Email of the person responsible for using Risklet. Report + and + magic link for login will be sent to this email.
+ -
- - - Helps determine the scale of IT infrastructure and security needs based on user volume. +
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ Helps determine the scale of IT infrastructure and security + needs based on user volume.
-
- - - Indicates available resources for cybersecurity investments and helps assess risk appetite. +
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ Indicates available resources for cybersecurity investments + and + helps assess risk appetite.
-
- - - Reveals the complexity of your technology landscape and potential attack surface. +
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ Reveals the complexity of your technology landscape and + potential attack surface.
-
- - - Identifies mandatory security controls and compliance requirements that must be implemented. +
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ Identifies mandatory security controls and compliance + requirements that must be implemented.
-
- +
+ +
- Determines industry-specific threats, regulations, and security best practices applicable to your business. + Determines industry-specific threats, regulations, and + security + best practices applicable to your business.
-
- - +
+ +
+
Not dependent at all Heavily dependent
- Measures the potential business impact of IT disruptions and helps prioritize security investments. + Measures the potential business impact of IT disruptions + and + helps prioritize security investments.
-
- - - Assesses the potential impact of data breaches and determines required security controls. +
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ Assesses the potential impact of data breaches and + determines + required security controls.
-
- - - Helps understand the complexity and vulnerability points in your technical environment. +
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ Helps understand the complexity and vulnerability points in + your + technical environment.
-
- - - Evaluates remote access security requirements and potential exposure to external threats. +
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ Evaluates remote access security requirements and potential + exposure to external threats.
-
- - - Assesses supply chain risk and the need for vendor security management. +
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ Assesses supply chain risk and the need for vendor security + management.
-
- - - Determines the need for secure development practices and application security measures. +
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ Determines the need for secure development practices and + application security measures.
- + + + + +
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ Determines exposure to different cybersecurity + regulations. +
+ + +
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ Assesses potential impact of data breaches. +
+ + + +
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ Defines data protection requirements. +
+ + + +
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ Indicates the variety of systems requiring + protection. +
+ + +
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ Assesses third-party cybersecurity risks. +
+ + +
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ Determines specific cybersecurity controls. +
+ + +
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ Evaluates cybersecurity needs based on IP + ownership. +
+ + +
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ Identifies required compliance frameworks. +
+ + +
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ Evaluates potential for cascade failures. +
+ + +
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ Determines the scope of remote access security + requirements. +
+ +
+ + + +
@@ -178,5 +704,5 @@ {{end}} {{define "bottom"}} - -{{end}} \ No newline at end of file + +{{end}} diff --git a/db/advancedProfile.go b/db/advancedProfile.go new file mode 100644 index 0000000..0a85da0 --- /dev/null +++ b/db/advancedProfile.go @@ -0,0 +1,43 @@ +package db + +type AdvancedProfile struct { + CompanyId int + GeographicDistribution string + CustomerConcentration string + ProductServicePortfolio string + OrganizationalCulture string + SupplierDiversity string + TechnologicalInfrastructure string + IntellectualProperty string + ManagementTeamExperience string +} + +// InsertAdvancedProfile inserts a new record into the AdvancedProfile table +func InsertAdvancedProfile(profile AdvancedProfile) (int, error) { + query := ` + INSERT INTO AdvancedProfile ( + CompanyId, GeographicDistribution, CustomerConcentration, ProductServicePortfolio, OrganizationalCulture, + SupplierDiversity, TechnologicalInfrastructure, IntellectualProperty, ManagementTeamExperience + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + RETURNING id + ` + + stmt, err := db.Prepare(query) + if err != nil { + return -2, err + } + defer stmt.Close() + + id := 0 + err = stmt.QueryRow( + profile.CompanyId, profile.GeographicDistribution, profile.CustomerConcentration, profile.ProductServicePortfolio, + profile.OrganizationalCulture, profile.SupplierDiversity, profile.TechnologicalInfrastructure, profile.IntellectualProperty, + profile.ManagementTeamExperience, + ).Scan(&id) + + if err != nil { + return -1, err + } + + return id, nil +} diff --git a/db/basicProfile.go b/db/basicProfile.go index 0d349fe..5b5c1d6 100644 --- a/db/basicProfile.go +++ b/db/basicProfile.go @@ -1,24 +1,36 @@ package db type BasicProfile struct { - CompanyId int - Employees string - Revenue string - Applications string - Compliance string - Industry string - ITDependency string - DataSensitivity string - DataVolume string - NetworkSegmentation string - LegacySystems string - IoTIntegration string - RemoteWork string - BYOD string - VPN string - API string - VendorAccess string - InternalDev string + CompanyId int // Company ID (foreign key reference) + Employees string // Current employee headcount + Revenue string // Annual revenue range + Applications string // Critical business applications + Compliance string // Regulatory frameworks + Industry string // Primary industry sector + ITDependency string // Technology dependency + DataSensitivity string // Sensitive data level + DataVolume string // Data volume (if applicable) + NetworkSegmentation string // Network infrastructure model + LegacySystems string // Legacy systems (if applicable) + IoTIntegration string // IoT integration (if applicable) + RemoteWork string // Remote work details + BYOD string // Bring Your Own Device policy + VPN string // VPN usage policy + API string // API integration (if applicable) + VendorAccess string // Third-party vendor access + InternalDev string // Internal software development activities + + // New fields from the advanced form + GeoScope string // Geographic operational scope + CustomerBase string // Customer base distribution + CustomerType string // Primary customer type + ProductPortfolio string // Product/service portfolio + SupplierBase string // Supplier base structure + ITInfrastructure string // IT infrastructure model (comma-separated values) + IPProtection string // Intellectual property protection (comma-separated values) + SensitiveData string // Sensitive data types (comma-separated values) + IntegrationLevel string // Integration level of business systems + RemotePolicy string // Remote work policy } // InsertBasicProfile inserts a new record into the BasicProfile table diff --git a/db/db.go b/db/db.go index bb41825..35613a3 100644 --- a/db/db.go +++ b/db/db.go @@ -26,7 +26,8 @@ func InitDB() { } func createTables() { - companyTable := ` + tables := []string{ + ` CREATE TABLE IF NOT EXISTS Company ( id INTEGER PRIMARY KEY AUTOINCREMENT, UUID TEXT NOT NULL, @@ -34,9 +35,9 @@ func createTables() { TaxId TEXT NOT NULL, Email TEXT NOT NULL, Password TEXT NOT NULL - );` + );`, - basicProfileTable := ` + ` CREATE TABLE IF NOT EXISTS BasicProfile ( id INTEGER PRIMARY KEY AUTOINCREMENT, CompanyId INTEGER, @@ -57,37 +58,33 @@ func createTables() { API TEXT, VendorAccess TEXT, InternalDev TEXT, + GeoScope TEXT, -- Geographic operational scope + CustomerBase TEXT, -- Customer base distribution + CustomerType TEXT, -- Primary customer type + ProductPortfolio TEXT, -- Product/service portfolio + SupplierBase TEXT, -- Supplier base structure + ITInfrastructure TEXT, -- IT infrastructure model (comma-separated values) + IPProtection TEXT, -- Intellectual property protection (comma-separated values) + SensitiveData TEXT, -- Sensitive data types (comma-separated values) + IntegrationLevel TEXT, -- Integration level of business systems + RemotePolicy TEXT, -- Remote work policy FOREIGN KEY (CompanyId) REFERENCES Company(id) - );` + );`, - advancedProfileTable := ` - CREATE TABLE IF NOT EXISTS AdvancedProfile ( + `CREATE TABLE IF NOT EXISTS Session ( id INTEGER PRIMARY KEY AUTOINCREMENT, - CompanyId INTEGER, - GeographicDistribution TEXT, - CustomerConcentration TEXT, - ProductServicePortfolio TEXT, - OrganizationalCulture TEXT, - SupplierDiversity TEXT, - TechnologicalInfrastructure TEXT, - IntellectualProperty TEXT, - ManagementTeamExperience TEXT, - FOREIGN KEY (CompanyId) REFERENCES Company(id) - );` + key TEXT NOT NULL, + value TEXT NOT NULL + );`, - _, err := db.Exec(companyTable) - if err != nil { - log.Fatalf("Error creating Company table: %v", err) + `CREATE INDEX IF NOT EXISTS idx_session_key ON Session(key);`, } - _, err = db.Exec(basicProfileTable) - if err != nil { - log.Fatalf("Error creating BasicProfile table: %v", err) - } - - _, err = db.Exec(advancedProfileTable) - if err != nil { - log.Fatalf("Error creating AdvancedProfile table: %v", err) + for _, table := range tables { + _, err := db.Exec(table) + if err != nil { + log.Fatalf("Error creating table: %v", err) + } } } diff --git a/db/utils.go b/db/utils.go index b3ea3ab..b8b1a9b 100644 --- a/db/utils.go +++ b/db/utils.go @@ -1,16 +1,23 @@ package db import ( - "math/rand" + "crypto/rand" + "log" + "math/big" ) -var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") +var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890") func GenerateRandomString() string { - const n = 25 + const n = 38 b := make([]rune, n) for i := range b { - b[i] = letters[rand.Intn(len(letters))] + num, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters)))) + if err != nil { + log.Println("Error generating random string: ", err) + continue + } + b[i] = letters[num.Int64()] } return string(b) }