diff --git a/.github/.keep b/.github/.keep new file mode 100644 index 0000000..e69de29 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..471e568 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,33 @@ +## โœ๏ธ Description + +- + +## ๐Ÿ”— Related Issues / Tickets + +Fixes # +Closes # +Relates to # + +## ๐Ÿงญ Type of Change + +- [ ] โœจ Feature +- [ ] ๐Ÿ› Bug fix +- [ ] โ™ป๏ธ Refactor +- [ ] ๐Ÿงน Chore +- [ ] ๐Ÿ“š Docs +- [ ] โšก Performance +- [ ] โœ… Tests +- [ ] ๐Ÿค– CI/CD +- [ ] ๐Ÿ” Security +- [ ] ๐Ÿงจ Breaking change (adds non-backward-compatible behavior) + +## ๐Ÿงช How Has This Been Tested? + +**Environment:** OS / Browser / Runtime / DB +**Steps to test:** +1. +2. +3. +**Expected result:** +**Screenshots / Recordings (if UI):** +- diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 0000000..02e88a0 --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,35 @@ +# .github/workflows/pre-commit.yml +name: pre-commit +on: + push: + branches: ["**"] + pull_request: + branches: ["**"] + +jobs: + run: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + cache: "pip" + + # (Optional) speeds up subsequent runs + - name: Cache pre-commit envs + uses: actions/cache@v4 + with: + path: ~/.cache/pre-commit + key: ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} + + - name: Install pre-commit + run: pip install pre-commit detect-secrets + + - name: Run pre-commit + run: pre-commit run --all-files --show-diff-on-failure diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b12832e --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# Python virtual environments +.venv/ +venv/ +env/ + +# Environment variables +.env +.env.* + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.pyc +*.pyd +*.pyo + +# Editor directories and files +.vscode/ +.idea/ +*.swp +*~ + +# Test and coverage files +.pytest_cache/ +.coverage +htmlcov/ + +# Logs and databases +*.log +*.sqlite3 +*.db + +# OS-specific files +.DS_Store +Thumbs.db diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..e76ade0 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,38 @@ +# .pre-commit-config.yaml +repos: + # Hygiene + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-merge-conflict + - id: check-added-large-files + - id: pretty-format-json + args: ["--autofix", "--no-sort-keys", "--indent", "2"] + exclude: '\.ipynb$' + + # Secret scanning (requires a committed baseline) + - repo: https://github.com/Yelp/detect-secrets + rev: v1.5.0 + hooks: + - id: detect-secrets + args: + - --baseline + - .secrets.baseline + exclude: '(node_modules/|\.venv/|\.git/)' + + # Python lint/format + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.6.9 + hooks: + - id: ruff + - id: ruff-format + + # Python security + - repo: https://github.com/PyCQA/bandit + rev: 1.7.9 + hooks: + - id: bandit + args: ["-q", "-ll", "-x", "tests"] diff --git a/.secrets.baseline b/.secrets.baseline new file mode 100644 index 0000000..5ba12b5 --- /dev/null +++ b/.secrets.baseline @@ -0,0 +1,137 @@ +{ + "version": "1.5.0", + "plugins_used": [ + { + "name": "ArtifactoryDetector" + }, + { + "name": "AWSKeyDetector" + }, + { + "name": "AzureStorageKeyDetector" + }, + { + "name": "Base64HighEntropyString", + "limit": 4.5 + }, + { + "name": "BasicAuthDetector" + }, + { + "name": "CloudantDetector" + }, + { + "name": "DiscordBotTokenDetector" + }, + { + "name": "GitHubTokenDetector" + }, + { + "name": "GitLabTokenDetector" + }, + { + "name": "HexHighEntropyString", + "limit": 3.0 + }, + { + "name": "IbmCloudIamDetector" + }, + { + "name": "IbmCosHmacDetector" + }, + { + "name": "IPPublicDetector" + }, + { + "name": "JwtTokenDetector" + }, + { + "name": "KeywordDetector", + "keyword_exclude": "" + }, + { + "name": "MailchimpDetector" + }, + { + "name": "NpmDetector" + }, + { + "name": "OpenAIDetector" + }, + { + "name": "PrivateKeyDetector" + }, + { + "name": "PypiTokenDetector" + }, + { + "name": "SendGridDetector" + }, + { + "name": "SlackDetector" + }, + { + "name": "SoftlayerDetector" + }, + { + "name": "SquareOAuthDetector" + }, + { + "name": "StripeDetector" + }, + { + "name": "TelegramBotTokenDetector" + }, + { + "name": "TwilioKeyDetector" + } + ], + "filters_used": [ + { + "path": "detect_secrets.filters.allowlist.is_line_allowlisted" + }, + { + "path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies", + "min_level": 2 + }, + { + "path": "detect_secrets.filters.heuristic.is_indirect_reference" + }, + { + "path": "detect_secrets.filters.heuristic.is_likely_id_string" + }, + { + "path": "detect_secrets.filters.heuristic.is_lock_file" + }, + { + "path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string" + }, + { + "path": "detect_secrets.filters.heuristic.is_potential_uuid" + }, + { + "path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign" + }, + { + "path": "detect_secrets.filters.heuristic.is_sequential_string" + }, + { + "path": "detect_secrets.filters.heuristic.is_swagger_file" + }, + { + "path": "detect_secrets.filters.heuristic.is_templated_secret" + } + ], + "results": { + "starter-code-simple/README.md": [ + { + "type": "Secret Keyword", + "filename": "starter-code-simple/README.md", + "hashed_secret": "cbfdac6008f9cab4083784cbd1874f76618d2a97", + "is_verified": false, + "line_number": 44 + } + ] + }, + "generated_at": "2025-09-26T03:08:06Z" +} diff --git a/README.md b/README.md index 74c245a..f420fea 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Professional Git Workflows โ€” Student Guide ## Overview -**Format:** In-class breakout exercises + after-class individual assignment -**Language:** Python +**Format:** In-class breakout exercises + after-class individual assignment +**Language:** Python **Skills:** Professional Git workflows, code reviews, merge conflicts, security --- @@ -10,7 +10,7 @@ ## Learning Objectives By completing this assignment, you will: - Design custom Git workflows that fit team needs -- Write professional pull requests and provide constructive code reviews +- Write professional pull requests and provide constructive code reviews - Resolve merge conflicts systematically and safely - Implement security best practices and catch common vulnerabilities - Set up automated quality gates with branch protection and hooks @@ -29,7 +29,7 @@ By completing this assignment, you will: ### After Class (Your Assignment) - Take the provided starter code in `starter-code-simple/` and create your own repository from it. - You can either: - 1) Fork/clone this repository and push to a new repo you own, or + 1) Fork/clone this repository and push to a new repo you own, or 2) If you received a GitHub Classroom link, accept it to create your student repo, then copy the starter code into that repo and complete all steps there. - Then complete all professionalization steps below using your own repository. @@ -43,7 +43,7 @@ Transform the provided basic Python API into a professionally configured reposit #### Starter Code You'll receive a basic Flask API with intentional security issues: - User authentication system -- Basic CRUD operations +- Basic CRUD operations - Configuration management - Simple database integration @@ -103,7 +103,7 @@ You'll review code with multiple security issues including: **๐Ÿ”ด SECURITY: [Issue Type]** **Line X:** [Specific problem description] **Impact:** [What could go wrong] -**Suggestion:** +**Suggestion:** ```python # Instead of this vulnerable code: old_code_example() @@ -256,4 +256,4 @@ Submit via your repository: ``` 4. Create a working branch. Implement changes via pull requests with reviews (ask a peer to review). 5. Add security scanning, pre-commit hooks, CI, and docs. Configure branch protection on `main`. -6. Complete the three breakout exercises as practice; then finalize your repository and written components. \ No newline at end of file +6. Complete the three breakout exercises as practice; then finalize your repository and written components. diff --git a/breakout-exercises/code_review_exercise.md b/breakout-exercises/code_review_exercise.md deleted file mode 100644 index bc31b44..0000000 --- a/breakout-exercises/code_review_exercise.md +++ /dev/null @@ -1,113 +0,0 @@ -# Code Review Exercise - In-Class Breakout - -**Duration:** 15 minutes -**Format:** Individual work with team discussion -**Objective:** Practice identifying security vulnerabilities and writing professional code review comments - ---- - -## The Code to Review - -You're reviewing this Python authentication code that has multiple security issues. Your job is to find them and provide professional feedback. - -```python -# auth_system.py - Find the security issues -import requests -import sqlite3 -import hashlib - -API_KEY = "sk-live-1234567890abcdef" -DATABASE_URL = "postgresql://admin:password123@localhost/prod" -DEBUG_MODE = True - -def authenticate_user(username, password): - conn = sqlite3.connect("users.db") - query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'" - - result = conn.execute(query).fetchone() - - print(f"Login attempt: {username}:{password}") - - response = requests.post("https://api.auth.com/verify", - data={"user": username, "key": API_KEY}) - - return response.json() - -def reset_password(user_id, new_password): - conn = sqlite3.connect("users.db") - query = f"UPDATE users SET password='{new_password}' WHERE id={user_id}" - conn.execute(query) - conn.commit() - -def hash_password(password): - return hashlib.md5(password.encode()).hexdigest() - -def admin_check(user_id): - if user_id == 1 or user_id == "admin": - return True - return False -``` - ---- - -## Your Task (10 minutes individual work) - -**Find and document at least 6 security issues** using this professional format: - -```markdown -## Code Review Comments - -**๐Ÿ”ด SECURITY: [Issue Type]** -**Line X:** [Specific problem description] -**Impact:** [What could go wrong if exploited] -**Suggestion:** -```python -# Instead of this vulnerable code: -vulnerable_example() - -# Use this secure approach: -secure_example() -``` -**Priority:** Critical/High/Medium/Low -``` - ---- - -## Hints - -- Look for hardcoded secrets and credentials -- Check for SQL injection risks in string-formatted queries -- Watch for logging of sensitive data -- Ensure passwords are hashed with modern algorithms -- Validate inputs and handle errors -- Avoid insecure external API usage without checking responses -- Beware of hidden backdoors and insecure defaults - ---- - -## Team Discussion (5 minutes) - -**Share with your breakout room:** -1. Which issues did you find? -2. Which ones did you miss? -3. How would you prioritize fixing them? -4. What was challenging about writing professional review comments? - -**Discussion Questions:** -- What makes a code review comment helpful vs. just critical? -- How do you balance being thorough with being constructive? -- What would you want to see in a review of your own code? - ---- - ---- - -## Real-World Application - -These are the exact types of issues you'll encounter in professional code reviews: -- **Hardcoded secrets** appear in ~15% of repositories -- **SQL injection** remains a top security vulnerability -- **Weak password hashing** affects millions of users -- **Missing input validation** leads to data breaches - -The review skills you practice here directly apply to protecting your team's production systems. \ No newline at end of file diff --git a/breakout-exercises/crisis_management_exercise.md b/breakout-exercises/crisis_management_exercise.md deleted file mode 100644 index 9b26121..0000000 --- a/breakout-exercises/crisis_management_exercise.md +++ /dev/null @@ -1,169 +0,0 @@ -# Git Crisis Management Exercise - Theory Breakout - -**Duration:** 15 minutes -**Format:** Group discussion (no commands run) -**Objective:** Think through how to respond to Git disasters under time pressure - ---- - -## ๐Ÿšจ EMERGENCY SCENARIO - -**ALERT:** You've just discovered that API keys were accidentally committed to your main branch **3 commits ago**. The repository is **PUBLIC** and the keys are **currently active** in your production system. - -**Timeline:** This happened 2 hours ago. You need to respond immediately. - ---- - -## Your Emergency Response (10 minutes, discussion) - -### Phase 1: Immediate Damage Control (First 3 minutes) - -**CRITICAL: What's your first action and why?** - -Choose the correct first step: -- [ ] A) Remove the secrets from the current code -- [ ] B) Make the repository private -- [ ] C) Rotate/revoke the compromised credentials immediately -- [ ] D) Delete the problematic commits - -**Why is this the right first step?** -_________________________________ - -### Phase 2: Git History Cleanup (Next 4 minutes) - -**Situation Assessment:** -```bash -git log --oneline -5 -a1b2c3d Fix user authentication bug -e4f5g6h Update README documentation -i7j8k9l Add production configuration โ† API keys are in this commit! -m1n2o3p Add user management features -q4r5s6t Initial project setup -``` - -**Discuss your Git recovery approach:** - -**Option A: Safe Revert (if others might have pulled)** -```bash -git revert i7j8k9l -# Creates new commit that removes the secrets -``` - -**Option B: History Rewrite (if you're sure no one else pulled)** -```bash -git rebase -i HEAD~3 -# Remove the problematic commit entirely -``` - -**Which option would you choose and why?** -_________________________________ - -### Phase 3: Prevention Implementation (Last 3 minutes) - -**Set up prevention measures:** - -1. **Update .gitignore:** -```bash -# Add to .gitignore -.env -config/secrets/ -*.key -credentials.json -``` - -2. **Create pre-commit hook (example):** -```bash -#!/bin/bash -# Check for secrets before commit -if grep -r "api_key\s*=" . ; then - echo "โŒ API key found! Use environment variables." - exit 1 -fi -``` - -3. **Document the incident:** -What would you write in your incident report? -_________________________________ - ---- - -## Team Discussion (5 minutes) - -**Share with your breakout room:** - -### Crisis Response Questions: -1. **Speed vs. Safety:** When would you choose history rewrite vs. revert? -2. **Communication:** Who would you notify during this incident? -3. **Prevention:** What other security measures could prevent this? - -### Git Command Practice: -1. **Have you used `git revert` vs `git rebase -i` before?** -2. **What's the difference between `git reset` and `git revert`?** -3. **When is `git push --force` acceptable?** - -### Real-World Experience: -1. **Has anyone experienced a similar incident?** -2. **What security practices does your workplace use?** -3. **How would your team handle credential rotation?** - ---- - ---- - -## Common Mistakes to Avoid - -โŒ **Wrong:** Trying to fix Git history before rotating credentials -โœ… **Right:** Rotate credentials first, then clean history - -โŒ **Wrong:** Using `git push --force` without checking with team -โœ… **Right:** Use `git push --force-with-lease` or coordinate with team - -โŒ **Wrong:** Only removing secrets from current code -โœ… **Right:** Remove from Git history too (if possible) - -โŒ **Wrong:** Not implementing prevention measures -โœ… **Right:** Set up hooks and scanning to prevent recurrence - ---- - -## Git Recovery Commands Reference - -```bash -# Find lost commits -git reflog - -# See what changed in a commit -git show - -# Undo last commit, keep changes -git reset --soft HEAD~1 - -# Undo last commit, lose changes -git reset --hard HEAD~1 - -# Undo specific commit safely -git revert - -# Interactive rebase to edit history -git rebase -i HEAD~N - -# Check if it's safe to force push -git push --force-with-lease -``` - ---- - -## Real-World Context - -**This scenario is extremely common:** -- GitHub's secret scanning finds **millions** of exposed credentials -- **Average detection time:** 20 days for exposed secrets -- **Real impact:** AWS bills of $50,000+ from compromised keys -- **Legal implications:** GDPR fines, compliance violations - -**Your response time matters:** -- **Under 1 hour:** Minimal impact if caught early -- **1-24 hours:** Moderate risk, require monitoring -- **Over 24 hours:** High risk, assume compromise - -The skills you practice here directly protect production systems and prevent security incidents that can cost companies millions of dollars. \ No newline at end of file diff --git a/breakout-exercises/merge_conflict_exercise.md b/breakout-exercises/merge_conflict_exercise.md deleted file mode 100644 index 360368d..0000000 --- a/breakout-exercises/merge_conflict_exercise.md +++ /dev/null @@ -1,181 +0,0 @@ -# Merge Conflict Exercise - Theory Breakout - -**Duration:** 15 minutes -**Format:** Group discussion (no coding) -**Objective:** Reason about intelligent merge conflict resolution strategies - ---- - -## Scenario Setup - -Two developers worked on the same Python API file simultaneously: - -**Branch A (Authentication):** Added login validation and JWT tokens -**Branch B (Database):** Added database integration and user persistence - -You need to merge both branches intelligently, preserving the best of both features. - ---- - -## Branch A: Authentication Features - -```python -from flask import Flask, request, jsonify -import jwt -import bcrypt -from datetime import datetime, timedelta - -app = Flask(__name__) -app.config['SECRET_KEY'] = 'your-secret-key' - -@app.route('/users', methods=['POST']) -def create_user(): - data = request.get_json() - - # Input validation added - if not data.get('username') or len(data.get('username', '')) < 3: - return jsonify({"error": "Username must be at least 3 characters"}), 400 - - if not data.get('password') or len(data.get('password', '')) < 8: - return jsonify({"error": "Password must be at least 8 characters"}), 400 - - # Secure password hashing - password_hash = bcrypt.hashpw(data['password'].encode('utf-8'), bcrypt.gensalt()) - - return jsonify({ - "id": 1, - "username": data['username'], - "message": "User created successfully" - }) - -@app.route('/login', methods=['POST']) -def login(): - data = request.get_json() - username = data.get('username') - password = data.get('password') - - # Validate credentials (simplified for demo) - if username and password: - # Generate JWT token - token = jwt.encode({ - 'username': username, - 'exp': datetime.utcnow() + timedelta(hours=24) - }, app.config['SECRET_KEY']) - - return jsonify({"token": token, "message": "Login successful"}) - - return jsonify({"error": "Invalid credentials"}), 401 -``` - ---- - -## Branch B: Database Features - -```python -from flask import Flask, request, jsonify -from sqlalchemy import create_engine, Column, Integer, String -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker - -app = Flask(__name__) - -# Database setup -engine = create_engine('sqlite:///users.db') -Base = declarative_base() -Session = sessionmaker(bind=engine) - -class User(Base): - __tablename__ = 'users' - id = Column(Integer, primary_key=True) - username = Column(String(50), unique=True, nullable=False) - password = Column(String(100), nullable=False) - -Base.metadata.create_all(engine) - -@app.route('/users', methods=['GET']) -def get_users(): - session = Session() - users = session.query(User).all() - result = [{"id": u.id, "username": u.username} for u in users] - session.close() - return jsonify({"users": result}) - -@app.route('/users', methods=['POST']) -def create_user(): - data = request.get_json() - session = Session() - - user = User( - username=data.get('username'), - password=data.get('password') # This needs the secure hashing from Branch A! - ) - - session.add(user) - session.commit() - - result = {"id": user.id, "username": user.username} - session.close() - return jsonify(result) -``` - ---- - -## Discussion Challenge (15 minutes) - -### Step 1: Analyze Both Branches (3 minutes) -**What does each branch accomplish?** - -**Branch A (Authentication):** -- Input validation for user creation -- Secure password hashing with bcrypt -- JWT token generation for login -- Proper error handling - -**Branch B (Database):** -- SQLAlchemy ORM for data persistence -- User model with database schema -- Get users endpoint -- Session management - -### Step 2: Plan an Integration Strategy (3 minutes) -Discuss how you would combine these features in principle. Identify risks, ordering, and validation points. - -Target outcomes: -- โœ… Use database persistence from Branch B -- โœ… Include input validation from Branch A -- โœ… Use secure password hashing from Branch A -- โœ… Have JWT authentication from Branch A -- โœ… Keep all endpoints working - -### Step 3: Outline an Approach (7 minutes) -Whiteboard or outline the sequence of steps you would take to perform the merge (branches to create, tests to run, order of resolving conflicts, checkpoints for validation). No coding required. - -### Step 4: What Would You Test? (2 minutes) -Walk through what you would test after resolving conflicts: -- Does user creation validate input AND save to database? -- Does password hashing work with database storage? -- Are all imports included? -- Do all endpoints still work? - ---- - -## Discussion Questions (Team sharing) - -1. **What was your strategy for combining the features?** -2. **What conflicts would you expect and how would you resolve them?** -3. **What would you test to make sure your merge works?** -4. **How is an intelligent merge different from just "picking one side"?** - ---- - ---- - -## Real-World Application - -This mirrors actual development scenarios: -- **Feature teams** often work on overlapping code -- **Good merges** preserve everyone's work -- **Smart resolution** is better than random conflict picking -- **Testing** your merge prevents broken deployments - -The skills you practice here prevent the "it worked on my machine" problems that break production systems. \ No newline at end of file diff --git a/starter-code-simple/README.md b/starter-code-simple/README.md index b079fca..1431257 100644 --- a/starter-code-simple/README.md +++ b/starter-code-simple/README.md @@ -65,4 +65,4 @@ Create a new repository you own (or use your GitHub Classroom repo with admin ac 2. Add security scanning and pre-commit hooks 3. Fix all security vulnerabilities 4. Add professional documentation (README, PR template, contributing) -5. Create a CI workflow to run tests, lint, and security scans \ No newline at end of file +5. Create a CI workflow to run tests, lint, and security scans diff --git a/starter-code-simple/app.py b/starter-code-simple/app.py index 3d01862..ccea3ab 100644 --- a/starter-code-simple/app.py +++ b/starter-code-simple/app.py @@ -2,80 +2,100 @@ # This code has intentional security flaws for educational purposes from flask import Flask, request, jsonify +from dotenv import load_dotenv import sqlite3 -import hashlib +import bcrypt +import os + +load_dotenv() # take environment variables from .env. + app = Flask(__name__) # Security Issue: Hardcoded secrets -DATABASE_URL = "postgresql://admin:password123@localhost/prod" -API_SECRET = "sk-live-1234567890abcdef" +DATABASE_URL = os.getenv("DATABASE_URL") +API_SECRET = os.getenv("API_SECRET") + def get_db_connection(): - return sqlite3.connect('users.db') + return sqlite3.connect("users.db") -@app.route('/health') + +@app.route("/health") def health_check(): return jsonify({"status": "healthy", "database": DATABASE_URL}) -@app.route('/users', methods=['GET']) + +@app.route("/users", methods=["GET"]) def get_users(): conn = get_db_connection() - users = conn.execute('SELECT id, username FROM users').fetchall() + users = conn.execute("SELECT id, username FROM users").fetchall() conn.close() return jsonify({"users": [{"id": u[0], "username": u[1]} for u in users]}) -@app.route('/users', methods=['POST']) + +@app.route("/users", methods=["POST"]) def create_user(): data = request.get_json() - username = data.get('username') - password = data.get('password') - + username = data.get("username") + password = data.get("password") + # Security Issue: Weak password hashing - hashed_password = hashlib.md5(password.encode()).hexdigest() - + hashed_password = bcrypt.hashpw( + password.encode(), bcrypt.gensalt(rounds=12) + ).decode() + conn = get_db_connection() # Security Issue: SQL injection vulnerability conn.execute( - f"INSERT INTO users (username, password) VALUES ('{username}', '{hashed_password}')" + "INSERT INTO users (username, password) VALUES (?, ?)", + (username, hashed_password), ) conn.commit() conn.close() - + # Security Issue: Logging sensitive information - print(f"Created user: {username} with password: {password}") return jsonify({"message": "User created", "username": username}) -@app.route('/login', methods=['POST']) + +@app.route("/login", methods=["POST"]) def login(): data = request.get_json() - username = data.get('username') - password = data.get('password') - - hashed_password = hashlib.md5(password.encode()).hexdigest() - + username = data.get("username") + password = data.get("password") + + hashed_password = bcrypt.hashpw( + password.encode(), bcrypt.gensalt(rounds=12) + ).decode() + conn = get_db_connection() # Security Issue: SQL injection vulnerability - query = f"SELECT * FROM users WHERE username='{username}' AND password='{hashed_password}'" - user = conn.execute(query).fetchone() + user = conn.execute( + "SELECT * FROM users WHERE username = ? AND password = ?", + (username, hashed_password), + ).fetchone() conn.close() - + if user: return jsonify({"message": "Login successful", "user_id": user[0]}) return jsonify({"message": "Invalid credentials"}), 401 + def init_db(): conn = get_db_connection() - conn.execute(''' + conn.execute( + """ CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY, username TEXT UNIQUE NOT NULL, password TEXT NOT NULL ) - ''') + """ + ) conn.commit() conn.close() -if __name__ == '__main__': + +if __name__ == "__main__": init_db() - app.run(debug=True) \ No newline at end of file + app.run() diff --git a/starter-code-simple/requirements.txt b/starter-code-simple/requirements.txt index 04752bd..4be88a2 100644 --- a/starter-code-simple/requirements.txt +++ b/starter-code-simple/requirements.txt @@ -1,2 +1,2 @@ Flask==2.3.2 -requests==2.31.0 \ No newline at end of file +requests==2.31.0