Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,23 @@ AWS_USE_PATH_STYLE_ENDPOINT=false

VITE_APP_NAME="${APP_NAME}"

# AI Provider Keys
OPENAI_API_KEY=
ANTHROPIC_API_KEY=
GEMINI_API_KEY=
# AI Provider Keys (Required for functionality)
# Get your OpenAI API key from: https://platform.openai.com/api-keys
OPENAI_API_KEY=your-openai-api-key-here

# Realtime API Configuration
VITE_OPENAI_API_KEY="${OPENAI_API_KEY}"
VITE_REALTIME_RELAY_URL=wss://localhost:8080/realtime

# NativePHP/Electron Configuration
# Change this to your own app identifier (reverse domain format)
NATIVEPHP_APP_ID=com.yourcompany.yourapp
# NATIVEPHP_APP_VERSION=DEBUG

# Apple Developer Code Signing (Required for macOS distribution)
# Only needed if you want to build signed, distributable macOS apps
# Get these from your Apple Developer Account: https://developer.apple.com/account/
# IMPORTANT: Never commit real values to version control!
[email protected]
NATIVEPHP_APPLE_ID_PASS=your-app-specific-password
NATIVEPHP_APPLE_TEAM_ID=YOUR_10_CHAR_TEAM_ID
21 changes: 21 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,24 @@ yarn-error.log
/.zed
/dist
.DS_Store

# Security & Certificates - Never commit these!
*.cer
*.p12
*.mobileprovision
*.keychain
*.keychain-db
**/certificates/
**/signing/
.env.local
.env.*.local
*.pem
*.key.pub
keychain_password.txt
apple_certificates/

# Build artifacts that may contain sensitive data
/build/certificates/
/build/signing/
/build/*.cer
/build/*.p12
55 changes: 53 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,67 @@ Unsure where to begin contributing? You can start by looking through these issue
php artisan migrate --database=nativephp
```

5. **Add your OpenAI API key to `.env`:**
5. **Add your API keys to `.env`:**
```
OPENAI_API_KEY=your-api-key-here
OPENAI_API_KEY=your-openai-api-key-here
NATIVEPHP_APP_ID=com.yourcompany.yourapp
```

6. **Run the development server:**
```bash
composer dev
```

## 🔐 Code Signing & Security

### For Contributors (Development)

**You don't need Apple Developer certificates for contributing!** The project automatically creates unsigned builds for development, which are perfect for testing your changes.

If you see warnings like this, it's completely normal:
```
⚠️ No code signing certificate found - creating unsigned build
💡 To create signed builds, install an Apple Developer certificate in Keychain
```

### Security Guidelines

**❌ NEVER commit these:**
- `.env` file with real credentials
- Certificate files (`.cer`, `.p12`)
- Apple Developer credentials
- Real API keys

**✅ SAFE to commit:**
- `.env.example` (template only)
- Source code
- Build scripts (auto-detect certificates)
- Documentation

### Security Hardening

This project follows security best practices:

**Minimal Entitlements**: The `entitlements.plist` file uses the principle of least privilege, requesting only:
- `com.apple.security.device.audio-input` - For audio input access
- `com.apple.security.device.microphone` - For microphone access
- `com.apple.security.device.screen-capture` - Required for ScreenCaptureKit system audio capture

**Removed Dangerous Entitlements**: We explicitly avoid these high-risk permissions:
- `com.apple.security.cs.allow-jit` - Unnecessary JIT compilation
- `com.apple.security.cs.allow-unsigned-executable-memory` - Memory injection risk
- `com.apple.security.cs.disable-executable-page-protection` - Memory corruption risk
- `com.apple.security.cs.disable-library-validation` - Library injection risk

### Before Committing

Always verify no sensitive data:
```bash
git status
git diff --cached
grep -r "your-actual-api-key" . --exclude-dir=vendor
```

## Pull Request Process

1. **Before submitting:**
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,6 @@ This project is licensed under the [MIT License](LICENSE) with the [Commons Clau
- ✅ **Modify it** to fit your needs
- ✅ **Self-host it** on your own infrastructure
- ❌ **Cannot sell** as a hosted service or SaaS product
- ❌ **Cannot resell** without a commercial agreement
- ❌ **Cannot resell**

For commercial licensing inquiries, please reach out via [Discord](https://discord.gg/PhPMPrxcKw) or create an issue.
For any questions, please reach out via [Discord](https://discord.gg/PhPMPrxcKw).
50 changes: 44 additions & 6 deletions app/Http/Middleware/CheckOnboarding.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,26 @@
namespace App\Http\Middleware;

use App\Services\ApiKeyService;
use App\Services\OnboardingStateService;
use App\Services\AudioCapturePermissionService;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class CheckOnboarding
{
protected ApiKeyService $apiKeyService;
protected OnboardingStateService $onboardingService;
protected AudioCapturePermissionService $permissionService;

public function __construct(ApiKeyService $apiKeyService)
{
public function __construct(
ApiKeyService $apiKeyService,
OnboardingStateService $onboardingService,
AudioCapturePermissionService $permissionService
) {
$this->apiKeyService = $apiKeyService;
$this->onboardingService = $onboardingService;
$this->permissionService = $permissionService;
}

/**
Expand All @@ -23,14 +32,21 @@ public function __construct(ApiKeyService $apiKeyService)
*/
public function handle(Request $request, Closure $next): Response
{
// Routes that should be accessible without API key
// Routes that should be accessible without completed onboarding
$excludedRoutes = [
'onboarding',
'api-keys.edit',
'api-keys.update',
'api-keys.destroy',
'api.openai.status',
'api.openai.api-key.store',
'api.onboarding.status',
'api.onboarding.step',
'api.onboarding.complete',
'api.permissions.screen-recording.status',
'api.permissions.screen-recording.request',
'api.permissions.screen-recording.check',
'api.open-external',
'appearance',
];

Expand All @@ -44,10 +60,32 @@ public function handle(Request $request, Closure $next): Response
return $next($request);
}

// Check if API key exists
if (!$this->apiKeyService->hasApiKey()) {
// If not on onboarding page and no API key, redirect to onboarding
// Check if API key exists (Step 1)
$hasApiKey = $this->apiKeyService->hasApiKey();

// Check current system permissions (Step 2) - Always check real system state
$permissionCheck = $this->permissionService->checkScreenRecordingPermission();
$hasPermission = $permissionCheck['granted'] ?? false;

// Check if complete onboarding is finished (Step 3)
$isOnboardingComplete = $this->onboardingService->isOnboardingComplete();

// If any step is missing, redirect to onboarding
if (!$hasApiKey || !$hasPermission || !$isOnboardingComplete) {
// If not on onboarding page, redirect to onboarding
if ($request->route() && $request->route()->getName() !== 'onboarding') {
// Update onboarding state based on what's missing
if (!$hasApiKey) {
// No API key - handle API key removal
$this->onboardingService->handleApiKeyRemoval();
} else if (!$hasPermission) {
// API key exists but no permission - handle permission revocation
$this->onboardingService->handlePermissionRevocation();
} else if (!$isOnboardingComplete) {
// Steps 1&2 complete but step 3 incomplete - handle incomplete onboarding
$this->onboardingService->handleIncompleteOnboarding();
}

return redirect()->route('onboarding');
}
}
Expand Down
82 changes: 77 additions & 5 deletions app/Providers/NativeAppServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Illuminate\Support\Facades\Schema;
use Native\Laravel\Contracts\ProvidesPhpIni;
use Native\Laravel\Facades\Window;
use Native\Laravel\Facades\Screen;

class NativeAppServiceProvider implements ProvidesPhpIni
{
Expand All @@ -17,25 +18,29 @@ class NativeAppServiceProvider implements ProvidesPhpIni
*/
public function boot(): void
{
// Get responsive window dimensions (75% of screen size)
$dimensions = $this->getResponsiveWindowDimensions();

// Create an overlay window for sales assistant
Window::open()
->route('realtime-agent')
->width(1200)
->height(700)
->minWidth(400)
->minHeight(500)
->width($dimensions['width'])
->height($dimensions['height'])
->minWidth($dimensions['minWidth'])
->minHeight($dimensions['minHeight'])
->titleBarStyle('hidden')
->transparent()
->backgroundColor('#00000000')
->resizable()
->position(50, 50)
->position(100)
->webPreferences([
'nodeIntegration' => true,
'contextIsolation' => false,
'webSecurity' => false,
'backgroundThrottling' => false,
'sandbox' => false,
])
->rememberState()
// Set window to floating panel level for better screen protection
->alwaysOnTop(false);

Expand Down Expand Up @@ -77,4 +82,71 @@ protected function seedDatabaseIfNeeded(): void
// The app will still work without seed data
}
}

/**
* Get responsive window dimensions based on screen size.
* Returns 75% of screen dimensions with sensible minimums.
*/
protected function getResponsiveWindowDimensions(): array
{
try {
// Get primary display dimensions
$displays = null;
try {
$displays = Screen::displays();
} catch (\Throwable $e) {
// Screen facade not available in non-native environment
throw new \Exception('Screen information not available');
}

// Handle case where displays() returns null (non-native environment)
if ($displays === null || empty($displays)) {
throw new \Exception('Screen information not available');
}

$primaryDisplay = $displays[0] ?? null;

if (!$primaryDisplay) {
// Fallback to reasonable defaults if screen info unavailable
return [
'width' => 1280,
'height' => 720,
'minWidth' => 400,
'minHeight' => 500,
];
}

// Get screen dimensions from workArea (excludes taskbars/docks)
$screenWidth = $primaryDisplay['workArea']['width'];
$screenHeight = $primaryDisplay['workArea']['height'];

// Calculate 75% of screen dimensions
$width = (int) ($screenWidth * 0.75);
$height = (int) ($screenHeight * 0.75);

// Use fixed minimum dimensions (original values)
$minWidth = 400;
$minHeight = 500;

// Ensure we don't exceed reasonable maximums (90% of screen)
$maxWidth = (int) ($screenWidth * 0.9);
$maxHeight = (int) ($screenHeight * 0.9);

return [
'width' => min($width, $maxWidth),
'height' => min($height, $maxHeight),
'minWidth' => $minWidth,
'minHeight' => $minHeight,
];

} catch (\Exception $e) {
// Fallback to reasonable defaults if anything fails
return [
'width' => 1280,
'height' => 720,
'minWidth' => 400,
'minHeight' => 500,
];
}
}
}
Loading