From d5a0bda4f082955ed1d24d2b01698e83f7e8ffc6 Mon Sep 17 00:00:00 2001 From: Robert Fink Date: Sat, 19 Apr 2025 15:39:48 -0400 Subject: [PATCH 1/2] Added some security tests --- javascriptapp/README.md | 10 +++++++- javascriptapp/package.json | 3 ++- javascriptapp/tests/security.test.js | 37 ++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 javascriptapp/tests/security.test.js diff --git a/javascriptapp/README.md b/javascriptapp/README.md index 6854c5f..cdecabe 100644 --- a/javascriptapp/README.md +++ b/javascriptapp/README.md @@ -29,9 +29,16 @@ A simple weather application that displays current weather conditions for any ci ## Testing -Run the test suite with coverage report: +Run the quality test suite with coverage report: `npm test` +### Running Security Tests + +This app includes a separate suite of security tests focused on injection attack scenarios. These tests ensure the backend API safely handles potentially malicious input. + +To run the security tests, execute: +`npm run test:security` + ## Running Locally Start the development server: @@ -94,3 +101,4 @@ Jest - Testing framework SuperTest - HTTP testing library ESLint - Code quality tool Prettier - Code formatter + diff --git a/javascriptapp/package.json b/javascriptapp/package.json index 4ea6911..7295c29 100644 --- a/javascriptapp/package.json +++ b/javascriptapp/package.json @@ -8,7 +8,8 @@ "start": "node server.js", "lint": "eslint .", "lint:fix": "eslint --fix .", - "format": "prettier --write ." + "format": "prettier --write .", + "test:security": "jest tests/security.test.js" }, "keywords": [], "author": "", diff --git a/javascriptapp/tests/security.test.js b/javascriptapp/tests/security.test.js new file mode 100644 index 0000000..5154d12 --- /dev/null +++ b/javascriptapp/tests/security.test.js @@ -0,0 +1,37 @@ +const request = require('supertest'); +const app = require('../server'); + +describe('Security Tests - Injection Attacks', () => { + // Test for basic injection attempt in city parameter + it('should handle SQL injection-like input safely', async () => { + const injectionPayload = "'; DROP TABLE cities; --"; + const response = await request(app).get('/api/weather').query({ city: injectionPayload }); + // Expect the server to respond with 404 or 200 but not crash or expose sensitive info + expect([200, 404]).toContain(response.status); + expect(response.body).not.toHaveProperty('error', expect.stringContaining('SQL')); + }); + + // Test for script injection attempt in city parameter + it('should handle script injection input safely', async () => { + const injectionPayload = ""; + const response = await request(app).get('/api/weather').query({ city: injectionPayload }); + // Expect the server to respond with 404 or 200 but not execute or reflect script + expect([200, 404]).toContain(response.status); + expect(response.body).not.toHaveProperty('error', expect.stringContaining('script')); + }); + + // Test for command injection-like input + it('should handle command injection-like input safely', async () => { + const injectionPayload = 'London; rm -rf /'; + const response = await request(app).get('/api/weather').query({ city: injectionPayload }); + expect([200, 404]).toContain(response.status); + expect(response.body).not.toHaveProperty('error', expect.stringContaining('command')); + }); + + // Test missing city parameter returns 400 + it('should return 400 if city parameter is missing', async () => { + const response = await request(app).get('/api/weather'); + expect(response.status).toBe(400); + expect(response.body).toHaveProperty('error', 'Missing city parameter'); + }); +}); From 86822640d837561576373bbc6dafc67f8538e40b Mon Sep 17 00:00:00 2001 From: Robert Fink Date: Sun, 20 Apr 2025 11:37:43 -0400 Subject: [PATCH 2/2] new go app from 2.5 pro --- goapp/.gitignore | 30 ++++++++++ goapp/README.md | 47 +++++++++++++++ goapp/go.mod | 3 + goapp/main.go | 121 +++++++++++++++++++++++++++++++++++++ goapp/main_test.go | 144 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 345 insertions(+) create mode 100644 goapp/.gitignore create mode 100644 goapp/README.md create mode 100644 goapp/go.mod create mode 100644 goapp/main.go create mode 100644 goapp/main_test.go diff --git a/goapp/.gitignore b/goapp/.gitignore new file mode 100644 index 0000000..fef9fd7 --- /dev/null +++ b/goapp/.gitignore @@ -0,0 +1,30 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work +go.work.sum + +# Environment variables file +.env + +# Build directory +/build/ +/dist/ + +# IDE settings folder +.idea/ +.vscode/ diff --git a/goapp/README.md b/goapp/README.md new file mode 100644 index 0000000..955f3bc --- /dev/null +++ b/goapp/README.md @@ -0,0 +1,47 @@ +# Go Stock Data Fetcher + +This application fetches and displays recent stock data for a predefined symbol (currently IBM) using the Alpha Vantage API. + +## Prerequisites + +- Go (version 1.18 or later recommended) +- An Alpha Vantage API Key + +## Setup + +1. **Get API Key:** + * Sign up for a free API key at [Alpha Vantage](https://www.alphavantage.co/support/#api-key). + +2. **Set Environment Variable:** + * Set the `ALPHA_VANTAGE_API_KEY` environment variable to your obtained key. + * **Linux/macOS:** + ```bash + export ALPHA_VANTAGE_API_KEY='YOUR_API_KEY' + ``` + * **Windows (Command Prompt):** + ```cmd + set ALPHA_VANTAGE_API_KEY=YOUR_API_KEY + ``` + * **Windows (PowerShell):** + ```powershell + $env:ALPHA_VANTAGE_API_KEY='YOUR_API_KEY' + ``` + * Replace `YOUR_API_KEY` with the actual key. You might want to add this export command to your shell's profile file (like `.bashrc`, `.zshrc`, or PowerShell profile) for persistence. + +## Running the Application + +1. Navigate to the `goapp` directory in your terminal: + ```bash + cd path/to/example-projects/goapp + ``` +2. Run the application: + ```bash + go run main.go + ``` + +The application will fetch the latest 5-minute interval data for the symbol defined in `main.go` (default: IBM) and print the symbol, last refresh time, and the latest closing price to the console. + +## Note + +- The free tier of Alpha Vantage has usage limits (e.g., 5 requests per minute, 500 per day). If you encounter issues, check if you've hit these limits. +- The application currently uses a hardcoded symbol ("IBM") and interval ("5min"). You can modify these constants in `main.go` if needed. diff --git a/goapp/go.mod b/goapp/go.mod new file mode 100644 index 0000000..5f0497e --- /dev/null +++ b/goapp/go.mod @@ -0,0 +1,3 @@ +module goapp + +go 1.17 diff --git a/goapp/main.go b/goapp/main.go new file mode 100644 index 0000000..437e741 --- /dev/null +++ b/goapp/main.go @@ -0,0 +1,121 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "os" + "time" +) + +// Define structs to match the Alpha Vantage JSON structure +// Note: This structure might need adjustment based on the specific endpoint and data format. +// We are using TIME_SERIES_INTRADAY as an example. +type MetaData struct { + Information string `json:"1. Information"` + Symbol string `json:"2. Symbol"` + LastRefreshed string `json:"3. Last Refreshed"` + Interval string `json:"4. Interval"` + OutputSize string `json:"5. Output Size"` + TimeZone string `json:"6. Time Zone"` +} + +type TimeSeriesEntry struct { + Open string `json:"1. open"` + High string `json:"2. high"` + Low string `json:"3. low"` + Close string `json:"4. close"` + Volume string `json:"5. volume"` +} + +type AlphaVantageResponse struct { + MetaData MetaData `json:"Meta Data"` + TimeSeries5min map[string]TimeSeriesEntry `json:"Time Series (5min)"` // Key is timestamp string +} + +const ( + alphaVantageURL = "https://www.alphavantage.co/query" + function = "TIME_SERIES_INTRADAY" + symbol = "IBM" // Default symbol + interval = "5min" +) + +func main() { + apiKey := os.Getenv("ALPHA_VANTAGE_API_KEY") + if apiKey == "" { + // Use a placeholder if the env var is not set + apiKey = "YOUR_API_KEY_HERE" // Replace with your actual key or keep as placeholder + log.Println("Warning: ALPHA_VANTAGE_API_KEY environment variable not set. Using placeholder.") + } + + // Construct the API URL + // Example URL: https://www.alphavantage.co/query?function=TIME_SERIES_INTRADAY&symbol=IBM&interval=5min&apikey=YOUR_API_KEY_HERE + requestURL := fmt.Sprintf("%s?function=%s&symbol=%s&interval=%s&apikey=%s", + alphaVantageURL, function, symbol, interval, apiKey) + + log.Printf("Fetching data from: %s\n", alphaVantageURL) // Log base URL, not the full one with key + + // Make the HTTP GET request + resp, err := http.Get(requestURL) + if err != nil { + log.Fatalf("Error fetching data: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + bodyBytes, _ := io.ReadAll(resp.Body) + log.Fatalf("API request failed with status %s: %s", resp.Status, string(bodyBytes)) + } + + // Read the response body + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Fatalf("Error reading response body: %v", err) + } + + // Parse the JSON response + var apiResponse AlphaVantageResponse + err = json.Unmarshal(body, &apiResponse) + if err != nil { + // Log the body for debugging if JSON parsing fails + log.Printf("Raw response body:\n%s\n", string(body)) + log.Fatalf("Error parsing JSON response: %v", err) + } + + // Print some information + fmt.Printf("Stock Data for: %s\n", apiResponse.MetaData.Symbol) + fmt.Printf("Last Refreshed: %s\n", apiResponse.MetaData.LastRefreshed) + + // Find the latest entry (map iteration order is not guaranteed, so find the latest timestamp) + var latestTime time.Time + var latestEntry TimeSeriesEntry + first := true + + for timestampStr, entry := range apiResponse.TimeSeries5min { + // Example timestamp format from Alpha Vantage: "2023-10-27 19:55:00" + layout := "2006-01-02 15:04:05" + loc, _ := time.LoadLocation(apiResponse.MetaData.TimeZone) // Use timezone from metadata + currentTime, err := time.ParseInLocation(layout, timestampStr, loc) + if err != nil { + log.Printf("Warning: Could not parse timestamp '%s': %v", timestampStr, err) + continue // Skip if timestamp parsing fails + } + + if first || currentTime.After(latestTime) { + latestTime = currentTime + latestEntry = entry + first = false + } + } + + if !first { // Check if we found at least one entry + fmt.Printf("Latest Price (%s): %s\n", latestTime.Format(time.RFC3339), latestEntry.Close) + } else { + fmt.Println("No time series data found in the response.") + // This might happen if the API returns an error message within a 200 OK response, + // e.g., due to an invalid API key or hitting rate limits. + log.Printf("Raw response body for inspection:\n%s\n", string(body)) + } +} diff --git a/goapp/main_test.go b/goapp/main_test.go new file mode 100644 index 0000000..db14a0c --- /dev/null +++ b/goapp/main_test.go @@ -0,0 +1,144 @@ +package main + +import ( + "encoding/json" + "testing" + "time" +) + +// Sample JSON data mimicking the Alpha Vantage TIME_SERIES_INTRADAY response +const sampleJsonResponse = ` +{ + "Meta Data": { + "1. Information": "Intraday (5min) open, high, low, close prices and volume", + "2. Symbol": "TEST", + "3. Last Refreshed": "2025-04-20 11:20:00", + "4. Interval": "5min", + "5. Output Size": "Compact", + "6. Time Zone": "US/Eastern" + }, + "Time Series (5min)": { + "2025-04-20 11:20:00": { + "1. open": "150.00", + "2. high": "150.50", + "3. low": "149.80", + "4. close": "150.25", + "5. volume": "10000" + }, + "2025-04-20 11:15:00": { + "1. open": "149.80", + "2. high": "150.10", + "3. low": "149.70", + "4. close": "150.00", + "5. volume": "8500" + } + } +} +` + +func TestParseAlphaVantageResponse(t *testing.T) { + var response AlphaVantageResponse + err := json.Unmarshal([]byte(sampleJsonResponse), &response) + + // Test 1: Check if unmarshalling was successful + if err != nil { + t.Fatalf("Failed to unmarshal sample JSON: %v", err) + } + + // Test 2: Check Meta Data fields + expectedSymbol := "TEST" + if response.MetaData.Symbol != expectedSymbol { + t.Errorf("Expected MetaData Symbol to be %s, but got %s", expectedSymbol, response.MetaData.Symbol) + } + + expectedTimeZone := "US/Eastern" + if response.MetaData.TimeZone != expectedTimeZone { + t.Errorf("Expected MetaData Time Zone to be %s, but got %s", expectedTimeZone, response.MetaData.TimeZone) + } + + // Test 3: Check if Time Series data exists and has the correct number of entries + expectedEntries := 2 + if len(response.TimeSeries5min) != expectedEntries { + t.Errorf("Expected %d time series entries, but got %d", expectedEntries, len(response.TimeSeries5min)) + } + + // Test 4: Check specific values in a time series entry + timestampKey := "2025-04-20 11:20:00" + entry, ok := response.TimeSeries5min[timestampKey] + if !ok { + t.Fatalf("Expected time series entry for key %s not found", timestampKey) + } + + expectedClose := "150.25" + if entry.Close != expectedClose { + t.Errorf("Expected close price for %s to be %s, but got %s", timestampKey, expectedClose, entry.Close) + } + + expectedVolume := "10000" + if entry.Volume != expectedVolume { + t.Errorf("Expected volume for %s to be %s, but got %s", timestampKey, expectedVolume, entry.Volume) + } +} + +// Helper function to find the latest entry (copied and adapted from main.go for potential future tests) +// Not directly tested by TestParseAlphaVantageResponse but could be used in other tests. +func findLatestEntry(response AlphaVantageResponse) (time.Time, TimeSeriesEntry, bool) { + var latestTime time.Time + var latestEntry TimeSeriesEntry + first := true + found := false + + layout := "2006-01-02 15:04:05" // Matching the sample data format + loc, err := time.LoadLocation(response.MetaData.TimeZone) + if err != nil { + // Handle error loading location, perhaps default to UTC or log + loc = time.UTC // Fallback + } + + + for timestampStr, entry := range response.TimeSeries5min { + currentTime, err := time.ParseInLocation(layout, timestampStr, loc) + if err != nil { + continue // Skip if timestamp parsing fails + } + + if first || currentTime.After(latestTime) { + latestTime = currentTime + latestEntry = entry + first = false + found = true + } + } + return latestTime, latestEntry, found +} + +// Example of testing the findLatestEntry logic (Optional additional test) +func TestFindLatestEntry(t *testing.T) { + var response AlphaVantageResponse + err := json.Unmarshal([]byte(sampleJsonResponse), &response) + if err != nil { + t.Fatalf("Setup failed: Could not unmarshal sample JSON for TestFindLatestEntry: %v", err) + } + + latestTime, latestEntry, found := findLatestEntry(response) + + if !found { + t.Fatal("Expected to find a latest entry, but none was found.") + } + + expectedTimestampStr := "2025-04-20 11:20:00" + expectedClose := "150.25" + + layout := "2006-01-02 15:04:05" + loc, _ := time.LoadLocation("US/Eastern") // Use timezone from sample + expectedTime, _ := time.ParseInLocation(layout, expectedTimestampStr, loc) + + + if !latestTime.Equal(expectedTime) { + t.Errorf("Expected latest time to be %s, but got %s", expectedTime, latestTime) + } + + if latestEntry.Close != expectedClose { + t.Errorf("Expected latest entry close price to be %s, but got %s", expectedClose, latestEntry.Close) + } +}