diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml new file mode 100644 index 0000000..e807196 --- /dev/null +++ b/.github/workflows/ios-ci.yml @@ -0,0 +1,30 @@ +name: iOS CI + +on: + pull_request: + push: + branches: + - main + workflow_dispatch: + +jobs: + build-and-test: + name: Build and Test (iOS) + runs-on: macos-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Xcode + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: latest-stable + + - name: Build and test + run: | + xcodebuild \ + -project ios/awsary.xcodeproj \ + -scheme "awsary (iOS)" \ + -destination "platform=iOS Simulator,name=iPhone 14" \ + clean test diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..d612e6a --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,35 @@ +# AGENTS + +## Scope +This guidance applies to the entire repository unless a nested `AGENTS.md` overrides it. + +## Project overview +This repository contains multiple components (e.g., iOS app, website, Terraform, utilities). Verify which directory you are working in before making changes. + +## General workflow +- Favor small, targeted changes that align with existing patterns. +- Run relevant tests or linters for the area you changed when feasible. +- Document any skipped tests and the reason. + +## Coding conventions +- Prefer clear, descriptive names consistent with surrounding code. +- Keep formatting consistent with existing files in each subproject. +- Avoid introducing new dependencies without justification. +- Prefer native Swift and SwiftUI with Apple public APIs. Only suggest third-party code when necessary or with significant advantages, and ask a human before adding any external SDK or codebase. + +## iOS (Swift/Xcode) +- Follow existing Swift style and naming conventions. +- Use `@MainActor` or concurrency annotations consistently with neighboring code. +- Avoid force-unwrapping unless the codebase already uses it and it is safe. + +## Web (website) +- Follow existing linting/formatting rules. +- Keep accessibility in mind (labels, semantics, contrast). + +## Infrastructure (terraform) +- Use existing module patterns and naming conventions. +- Keep variables and outputs documented. + +## PR/commit notes +- Summarize user-visible changes. +- Call out any migrations, configuration changes, or manual steps. diff --git a/bug_fixes_summary.md b/bug_fixes_summary.md new file mode 100644 index 0000000..8cae23b --- /dev/null +++ b/bug_fixes_summary.md @@ -0,0 +1,98 @@ +# Bug Fixes Summary + +## Overview +This document summarizes the 3 critical bugs identified and fixed in the AWSary codebase, including security vulnerabilities, logic errors, and resource management issues. + +## Bug #1: Security Vulnerability in CloudFront Configuration +**Severity:** HIGH +**Type:** Security Vulnerability +**Location:** `terraform/cloudfront_cdn.tf` line 51 + +### Description +The CloudFront distribution was configured to allow both HTTP and HTTPS traffic (`viewer_protocol_policy = "allow-all"`), which exposes user data to potential interception and violates security best practices. + +### Impact +- Sensitive data could be transmitted over unencrypted HTTP connections +- Vulnerability to man-in-the-middle attacks +- Non-compliance with security standards requiring HTTPS + +### Fix Applied +Changed `viewer_protocol_policy` from `"allow-all"` to `"redirect-to-https"` to ensure all HTTP traffic is automatically redirected to HTTPS. + +```diff +- viewer_protocol_policy = "allow-all" ++ viewer_protocol_policy = "redirect-to-https" +``` + +## Bug #2: Logic Error in OpenAI Script +**Severity:** MEDIUM +**Type:** Logic Error +**Location:** `utils/open_ai.py` lines 18-19 + +### Description +The script contained a typo in the key name `shortDesctiption` instead of `shortDescription`, causing the logic to fail when checking if an OpenAI description already exists. + +### Impact +- Script would crash or behave unexpectedly when accessing non-existent key +- Logic condition `if "#" not in item['shortDesctiption']` would always fail +- Potential KeyError exceptions +- Inefficient processing of already-processed items + +### Fix Applied +1. Fixed the typo: `shortDesctiption` → `shortDescription` +2. Added safe key access using `item.get('shortDescription', '')` to prevent KeyError +3. Fixed typo in print statement: `OpenAIn` → `OpenAI` + +```diff +- if "#" not in item['shortDesctiption'] : +- print("Checking OpenAIn for : " + item['name']) +- item['shortDesctiption'] = chat_completion.choices[0].message.content ++ if "#" not in item.get('shortDescription', '') : ++ print("Checking OpenAI for : " + item['name']) ++ item['shortDescription'] = chat_completion.choices[0].message.content +``` + +## Bug #3: Resource Management Bug in Polly Script +**Severity:** MEDIUM +**Type:** Resource Management / Memory Leak +**Location:** `utils/polly.py` line 14 + +### Description +The script opened file handles without using proper context management (`with` statement), which could cause resource leaks or file corruption if exceptions occurred. + +### Impact +- File handles could remain open if exceptions occur +- Potential resource exhaustion on systems with limited file descriptors +- Risk of file corruption or incomplete writes +- Poor adherence to Python best practices + +### Fix Applied +Replaced manual file opening/closing with context manager (`with` statement) to ensure proper resource cleanup. + +```diff +- file = open('speech/' + item['name'].replace(' ','_') + '_Brian_' + 'en-GB' + '.mp3', 'wb') +- file.write(response['AudioStream'].read()) +- file.close() ++ filename = 'speech/' + item['name'].replace(' ','_') + '_Brian_' + 'en-GB' + '.mp3' ++ with open(filename, 'wb') as file: ++ file.write(response['AudioStream'].read()) +``` + +## Additional Observations +During the codebase review, several other potential improvements were identified: + +1. **OpenAI API Version**: The script uses the deprecated `openai.ChatCompletion.create()` API format (pre-v1.0) +2. **API Key Security**: Hardcoded placeholder API key in the code +3. **Error Handling**: Missing try-catch blocks for API calls and file operations +4. **DynamoDB Pagination**: Scripts don't handle pagination for large DynamoDB tables + +## Recommendations +1. Implement comprehensive error handling throughout the Python scripts +2. Update OpenAI API calls to use the current v1.0+ format +3. Move API keys to environment variables or secure configuration +4. Add pagination support for DynamoDB scan operations +5. Implement logging for better debugging and monitoring +6. Add input validation for all user-provided data + +## Conclusion +The three bugs fixed represent significant improvements to the codebase's security, reliability, and maintainability. The CloudFront security fix is particularly critical as it prevents potential data exposure through unencrypted connections. \ No newline at end of file diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 0000000..7979e00 --- /dev/null +++ b/ios/.gitignore @@ -0,0 +1 @@ +.sentryclirc \ No newline at end of file diff --git a/ios/AWSary/AWSaryApp.swift b/ios/AWSary/AWSaryApp.swift index bb298b6..f67bda1 100644 --- a/ios/AWSary/AWSaryApp.swift +++ b/ios/AWSary/AWSaryApp.swift @@ -6,13 +6,50 @@ // import SwiftUI +import Sentry + import SwiftData import RevenueCat @main struct awsaryApp: App { - init() { + SentrySDK.start { options in + options.dsn = "https://477881ef4da534dc2a5d623681bb8ff1@o4509860037001216.ingest.de.sentry.io/4509860041982032" + + // for DEV + options.debug = true // Enabled debug when first installing is always helpful + options.environment = "dev" + + // for PROD + //options.debug = false + //options.environment = "production" + + // Adds IP for users. + // For more information, visit: https://docs.sentry.io/platforms/apple/data-management/data-collected/ + options.sendDefaultPii = true + + // Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring. + // We recommend adjusting this value in production. + options.tracesSampleRate = 1.0 + + // Configure profiling. Visit https://docs.sentry.io/platforms/apple/profiling/ to learn more. + options.configureProfiling = { + $0.sessionSampleRate = 1.0 // We recommend adjusting this value in production. + $0.lifecycle = .trace + } + + // Uncomment the following lines to add more data to your events + // options.attachScreenshot = true // This adds a screenshot to the error events + // options.attachViewHierarchy = true // This adds the view hierarchy to the error events + + // Enable experimental logging features + options.experimental.enableLogs = true + } + // Remove the next line after confirming that your Sentry integration is working. +// SentrySDK.capture(message: "This app uses Sentry! :)") + + // RevenueCat Purchases.logLevel = .debug Purchases.configure(withAPIKey: Constants.apiKey) diff --git a/ios/Shared/MainViews/DetailsView.swift b/ios/Shared/MainViews/DetailsView.swift index dbfb52d..5a8aa49 100644 --- a/ios/Shared/MainViews/DetailsView.swift +++ b/ios/Shared/MainViews/DetailsView.swift @@ -38,20 +38,39 @@ struct DetailsView: View { AWSserviceImagePlaceHolderView(service: service, showLabel: awsServiceLogoWithLabel) VStack{ Text(service.longName).font(Font.title) - HStack{ - Image("Arch_Amazon-Polly_64") - .resizable() - .scaledToFit() - .frame(width: 40) - .cornerRadius(8.0) - Text("Pronunciation by Amazon Polly").lineLimit(2).font(.caption2) - }.onTapGesture { - playServiceName(url: "https://cdn.awsary.com/audio/\(service.imageURL.replacingOccurrences(of: "https://static.tig.pt/awsary/logos/Arch_", with: "").replacingOccurrences(of: "_64.svg", with: "").replacingOccurrences(of: "Amazon-", with: "").replacingOccurrences(of: "AWS-", with: ""))-Joanna-en-US.mp3") - } + HStack{ + ZStack{ + RoundedRectangle(cornerRadius: 8) + .foregroundStyle(Color.green) + .frame(width: 40, height: 40) + Image(systemName: "document.on.document") + } + .onTapGesture { + UIPasteboard.general.string = service.shortDesctiption + } + .onDrag({ + let itemProvider = NSItemProvider(object: service.shortDesctiption as NSString) + return itemProvider + }) + + HStack{ + Image("Arch_Amazon-Polly_64") + .resizable() + .scaledToFit() + .frame(width: 40) + .cornerRadius(8.0) + Text("Pronunciation by Polly").lineLimit(2).font(.caption2) + }.onTapGesture { + playServiceName(url: "https://cdn.awsary.com/audio/\(service.imageURL.replacingOccurrences(of: "https://static.tig.pt/awsary/logos/Arch_", with: "").replacingOccurrences(of: "_64.svg", with: "").replacingOccurrences(of: "Amazon-", with: "").replacingOccurrences(of: "AWS-", with: ""))-Joanna-en-US.mp3") + } + } + .padding(.leading, 10) } } Spacer() - Markdown(service.shortDesctiption).padding() + Markdown(service.shortDesctiption) + .textSelection(.enabled) + .padding() Spacer() HStack{ // VStack(alignment: .leading){ diff --git a/ios/awsary.xcodeproj/project.pbxproj b/ios/awsary.xcodeproj/project.pbxproj index 1a6b466..6a3df26 100644 --- a/ios/awsary.xcodeproj/project.pbxproj +++ b/ios/awsary.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 341195612E55196E00EEF821 /* (null) in Frameworks */ = {isa = PBXBuildFile; }; + 341195622E55196E00EEF821 /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 5A6A20744D284CAE9275952C /* Sentry */; }; + 341195632E55196E00EEF821 /* (null) in Frameworks */ = {isa = PBXBuildFile; }; 34571B5E2E05BA83008B8BC2 /* RevenueCat in Frameworks */ = {isa = PBXBuildFile; productRef = 34571B5D2E05BA83008B8BC2 /* RevenueCat */; }; 34571B602E05BA83008B8BC2 /* RevenueCatUI in Frameworks */ = {isa = PBXBuildFile; productRef = 34571B5F2E05BA83008B8BC2 /* RevenueCatUI */; }; 34571B622E062761008B8BC2 /* RevenueCat in Frameworks */ = {isa = PBXBuildFile; productRef = 34571B612E062761008B8BC2 /* RevenueCat */; }; @@ -25,6 +28,7 @@ 34DDF78C2E00BDFD00CDD25C /* SystemSetting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34DDF78B2E00BDFD00CDD25C /* SystemSetting.swift */; }; 34DDF78D2E00BDFD00CDD25C /* SystemSetting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34DDF78B2E00BDFD00CDD25C /* SystemSetting.swift */; }; 34DDF78E2E00BE3D00CDD25C /* SystemSetting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34DDF78B2E00BDFD00CDD25C /* SystemSetting.swift */; }; + CEA15FC538DF40739D0F32EE /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 5A6A20744D284CAE9275952C /* Sentry */; }; EE00843328820C8C003BA190 /* AWSaryApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE00842328820C8B003BA190 /* AWSaryApp.swift */; }; EE00843428820C8C003BA190 /* AWSaryApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE00842328820C8B003BA190 /* AWSaryApp.swift */; }; EE00843528820C8C003BA190 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE00842428820C8B003BA190 /* OnboardingView.swift */; }; @@ -168,10 +172,12 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 341195622E55196E00EEF821 /* Sentry in Frameworks */, 34571B602E05BA83008B8BC2 /* RevenueCatUI in Frameworks */, EE29922F29A3CB4E0071030A /* MarkdownUI in Frameworks */, EEE7348F2889A72000718ACC /* YouTubePlayerKit in Frameworks */, 34571B5E2E05BA83008B8BC2 /* RevenueCat in Frameworks */, + 341195612E55196E00EEF821 /* (null) in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -180,6 +186,7 @@ buildActionMask = 2147483647; files = ( EEB93EC52A7AFCC300373D29 /* StoreKit.framework in Frameworks */, + 341195632E55196E00EEF821 /* (null) in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -191,6 +198,7 @@ EE4D8DB72B9C5E7B00A96E5C /* YouTubePlayerKit in Frameworks */, EE4D8DB92B9C5E7B00A96E5C /* MarkdownUI in Frameworks */, 34571B622E062761008B8BC2 /* RevenueCat in Frameworks */, + CEA15FC538DF40739D0F32EE /* Sentry in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -391,6 +399,7 @@ EE00842728820C8C003BA190 /* Frameworks */, EE00842828820C8C003BA190 /* Resources */, EE4D8DA02B9C5BC800A96E5C /* Embed App Clips */, + 422720F8868246E1A43E2959 /* Upload Debug Symbols to Sentry */, ); buildRules = ( ); @@ -403,6 +412,7 @@ EE29922E29A3CB4E0071030A /* MarkdownUI */, 34571B5D2E05BA83008B8BC2 /* RevenueCat */, 34571B5F2E05BA83008B8BC2 /* RevenueCatUI */, + 5A6A20744D284CAE9275952C /* Sentry */, ); productName = "awsary (iOS)"; productReference = EE00842A28820C8C003BA190 /* AWSary.app */; @@ -482,6 +492,7 @@ EEE7348D2889A72000718ACC /* XCRemoteSwiftPackageReference "YouTubePlayerKit" */, EE29922D29A3CB4E0071030A /* XCRemoteSwiftPackageReference "swift-markdown-ui" */, 34571B5C2E05BA83008B8BC2 /* XCRemoteSwiftPackageReference "purchases-ios-spm" */, + 563142E007D54F51B3CE99D7 /* XCRemoteSwiftPackageReference "sentry-cocoa" */, ); productRefGroup = EE00842B28820C8C003BA190 /* Products */; projectDirPath = ""; @@ -529,6 +540,24 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 422720F8868246E1A43E2959 /* Upload Debug Symbols to Sentry */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${TARGET_NAME}", + ); + name = "Upload Debug Symbols to Sentry"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# This script is responsible for uploading debug symbols and source context for Sentry.\nif which sentry-cli >/dev/null; then\n export SENTRY_ORG=tigpt\n export SENTRY_PROJECT=apple-ios\n ERROR=$(sentry-cli debug-files upload --include-sources \"$DWARF_DSYM_FOLDER_PATH\" 2>&1 >/dev/null)\n if [ ! $? -eq 0 ]; then\n echo \"warning: sentry-cli - $ERROR\"\n fi\nelse\n echo \"warning: sentry-cli not installed, download from https://github.com/getsentry/sentry-cli/releases\"\nfi\n"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ EE00842628820C8C003BA190 /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -758,7 +787,9 @@ CODE_SIGN_ENTITLEMENTS = "AWSary/resources/awsary+iOS.entitlements"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_PREVIEWS = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = AWSary/resources/Info.plist; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; @@ -773,7 +804,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.11; + MARKETING_VERSION = 1.12; PRODUCT_BUNDLE_IDENTIFIER = pt.tig.awsary; PRODUCT_NAME = AWSary; SDKROOT = iphoneos; @@ -791,7 +822,9 @@ CODE_SIGN_ENTITLEMENTS = "AWSary/resources/awsary+iOS.entitlements"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_PREVIEWS = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = AWSary/resources/Info.plist; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; @@ -806,7 +839,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.11; + MARKETING_VERSION = 1.12; PRODUCT_BUNDLE_IDENTIFIER = pt.tig.awsary; PRODUCT_NAME = AWSary; SDKROOT = iphoneos; @@ -905,7 +938,7 @@ "@executable_path/Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.11; + MARKETING_VERSION = 1.12; PRODUCT_BUNDLE_IDENTIFIER = pt.tig.awsary.Clip; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -944,7 +977,7 @@ "@executable_path/Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.11; + MARKETING_VERSION = 1.12; PRODUCT_BUNDLE_IDENTIFIER = pt.tig.awsary.Clip; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -1006,6 +1039,14 @@ minimumVersion = 5.29.0; }; }; + 563142E007D54F51B3CE99D7 /* XCRemoteSwiftPackageReference "sentry-cocoa" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/getsentry/sentry-cocoa/"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 8.0.0; + }; + }; EE29922D29A3CB4E0071030A /* XCRemoteSwiftPackageReference "swift-markdown-ui" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/gonzalezreal/swift-markdown-ui"; @@ -1045,6 +1086,11 @@ package = 34571B5C2E05BA83008B8BC2 /* XCRemoteSwiftPackageReference "purchases-ios-spm" */; productName = RevenueCatUI; }; + 5A6A20744D284CAE9275952C /* Sentry */ = { + isa = XCSwiftPackageProductDependency; + package = 563142E007D54F51B3CE99D7 /* XCRemoteSwiftPackageReference "sentry-cocoa" */; + productName = Sentry; + }; EE29922E29A3CB4E0071030A /* MarkdownUI */ = { isa = XCSwiftPackageProductDependency; package = EE29922D29A3CB4E0071030A /* XCRemoteSwiftPackageReference "swift-markdown-ui" */; diff --git a/ios/awsary.xcodeproj/xcshareddata/xcschemes/awsary (iOS).xcscheme b/ios/awsary.xcodeproj/xcshareddata/xcschemes/awsary (iOS).xcscheme index 77b0caa..26e1d42 100644 --- a/ios/awsary.xcodeproj/xcshareddata/xcschemes/awsary (iOS).xcscheme +++ b/ios/awsary.xcodeproj/xcshareddata/xcschemes/awsary (iOS).xcscheme @@ -1,6 +1,6 @@