diff --git a/.env.example b/.env.example
index b0de2e0..d26a51a 100644
--- a/.env.example
+++ b/.env.example
@@ -12,3 +12,9 @@ SENDER_EMAIL=reports@your-domain.com
# Email Recipients (comma-separated)
REPORT_RECIPIENTS=user1@example.com,user2@example.com
+
+# SSO Configuration (Microsoft Azure AD / MSAL)
+# Set SSO_ENABLED=true for production, false for local development without SSO
+SSO_ENABLED=true
+SSO_TENANT_ID=your-azure-tenant-id
+SSO_CLIENT_ID=your-azure-client-id
diff --git a/AuthMiddleware.php b/AuthMiddleware.php
new file mode 100644
index 0000000..bfa7e0a
--- /dev/null
+++ b/AuthMiddleware.php
@@ -0,0 +1,409 @@
+ssoEnabled = SSO_ENABLED;
+ $this->tenantId = SSO_TENANT_ID;
+ $this->clientId = SSO_CLIENT_ID;
+
+ // Only initialize validator if SSO is enabled
+ if ($this->ssoEnabled) {
+ require_once __DIR__ . '/JWTValidator.php';
+ $this->validator = new JWTValidator($this->tenantId, $this->clientId);
+ }
+ }
+
+ /**
+ * Check if SSO is enabled
+ *
+ * @return bool
+ */
+ public function isSSOEnabled() {
+ return $this->ssoEnabled;
+ }
+
+ /**
+ * Check if user is authenticated
+ *
+ * @return array ['authenticated' => bool, 'user' => array|null, 'error' => string|null]
+ */
+ public function isAuthenticated() {
+ // If SSO is disabled, return authenticated with mock user
+ if (!$this->ssoEnabled) {
+ return [
+ 'authenticated' => true,
+ 'user' => [
+ 'name' => 'Local Developer',
+ 'preferred_username' => 'dev@localhost'
+ ]
+ ];
+ }
+
+ // Get token from cookie
+ $token = $this->getTokenFromCookie();
+ if (!$token) {
+ return ['authenticated' => false, 'error' => 'No authentication token found'];
+ }
+
+ // Validate token
+ $validation = $this->validator->validateToken($token);
+ if (!$validation['valid']) {
+ return ['authenticated' => false, 'error' => $validation['error']];
+ }
+
+ return ['authenticated' => true, 'user' => $validation['payload']];
+ }
+
+ /**
+ * Require authentication - blocks if not authenticated
+ *
+ * @return array User data
+ */
+ public function requireAuth() {
+ // If SSO is disabled, return mock user
+ if (!$this->ssoEnabled) {
+ return [
+ 'name' => 'Local Developer',
+ 'preferred_username' => 'dev@localhost'
+ ];
+ }
+
+ // Check authentication
+ $auth = $this->isAuthenticated();
+
+ if (!$auth['authenticated']) {
+ $this->handleUnauthorized($auth['error'] ?? 'Authentication required');
+ exit;
+ }
+
+ return $auth['user'];
+ }
+
+ /**
+ * Set authentication token (after login)
+ *
+ * @param string $token JWT token from MSAL
+ * @return array ['success' => bool, 'user' => array|null, 'error' => string|null]
+ */
+ public function setAuthToken($token) {
+ if (!$this->ssoEnabled) {
+ return ['success' => false, 'error' => 'SSO is disabled'];
+ }
+
+ // Validate token
+ $validation = $this->validator->validateToken($token);
+ if (!$validation['valid']) {
+ return ['success' => false, 'error' => $validation['error']];
+ }
+
+ // Set httpOnly cookie with security options
+ $cookieOptions = [
+ 'expires' => time() + (24 * 60 * 60), // 24 hours
+ 'path' => '/',
+ 'domain' => '',
+ 'secure' => isset($_SERVER['HTTPS']), // Only over HTTPS in production
+ 'httponly' => true,
+ 'samesite' => 'Lax'
+ ];
+
+ setcookie('auth_token', $token, $cookieOptions);
+
+ return ['success' => true, 'user' => $validation['payload']];
+ }
+
+ /**
+ * Clear authentication token (logout)
+ */
+ public function clearAuthToken() {
+ setcookie('auth_token', '', [
+ 'expires' => time() - 3600,
+ 'path' => '/',
+ 'httponly' => true
+ ]);
+ }
+
+ /**
+ * Get token from cookie
+ *
+ * @return string|null
+ */
+ private function getTokenFromCookie() {
+ return isset($_COOKIE['auth_token']) ? $_COOKIE['auth_token'] : null;
+ }
+
+ /**
+ * Handle unauthorized access
+ *
+ * @param string $error Error message
+ */
+ private function handleUnauthorized($error) {
+ if ($this->isAjaxRequest()) {
+ // For AJAX requests, return JSON
+ header('Content-Type: application/json');
+ http_response_code(401);
+ echo json_encode([
+ 'error' => 'Authentication required',
+ 'message' => $error,
+ 'requiresAuth' => true
+ ]);
+ } else {
+ // For page requests, show login interface
+ $this->showLoginPage($error);
+ }
+ }
+
+ /**
+ * Check if request is AJAX
+ *
+ * @return bool
+ */
+ private function isAjaxRequest() {
+ return isset($_SERVER['HTTP_X_REQUESTED_WITH']) &&
+ strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest';
+ }
+
+ /**
+ * Show login page with MSAL integration
+ *
+ * @param string $error Optional error message
+ */
+ public function showLoginPage($error = '') {
+ ?>
+
+
+
+
+
+ Sign In - VEO3 Usage Report
+
+
+
+
+
+
+
+
+
📊
+
VEO3 Usage Report
+
Video Generation Analytics Dashboard
+
+
+
+
+
+
+
+
+
+
+ Sign in with your Microsoft account to access usage reports
+
+
+
+
+
+
+ tenantId = $tenantId;
+ $this->clientId = $clientId;
+ }
+
+ /**
+ * Validate a JWT token
+ *
+ * @param string $token The JWT token to validate
+ * @return array ['valid' => bool, 'payload' => array|null, 'error' => string|null]
+ */
+ public function validateToken($token) {
+ if (empty($token)) {
+ return ['valid' => false, 'error' => 'Token is empty'];
+ }
+
+ try {
+ // Get public keys from Azure AD
+ $jwks = $this->getJWKS();
+ if (!$jwks) {
+ return ['valid' => false, 'error' => 'Could not retrieve public keys from Azure AD'];
+ }
+
+ // Convert JWKS to Key objects
+ $keys = JWK::parseKeySet($jwks);
+
+ // Decode and validate the JWT
+ $decoded = JWT::decode($token, $keys);
+ $payload = (array) $decoded;
+
+ // Validate claims
+ $validation = $this->validateClaims($payload);
+ if (!$validation['valid']) {
+ return $validation;
+ }
+
+ return ['valid' => true, 'payload' => $payload];
+
+ } catch (\Firebase\JWT\ExpiredException $e) {
+ return ['valid' => false, 'error' => 'Token has expired'];
+ } catch (\Firebase\JWT\SignatureInvalidException $e) {
+ return ['valid' => false, 'error' => 'Token signature is invalid'];
+ } catch (\Firebase\JWT\BeforeValidException $e) {
+ return ['valid' => false, 'error' => 'Token is not yet valid'];
+ } catch (Exception $e) {
+ return ['valid' => false, 'error' => 'JWT validation failed: ' . $e->getMessage()];
+ }
+ }
+
+ /**
+ * Validate JWT claims
+ *
+ * @param array $payload The decoded JWT payload
+ * @return array ['valid' => bool, 'error' => string|null]
+ */
+ private function validateClaims($payload) {
+ $now = time();
+
+ // Check expiration (exp claim)
+ if (isset($payload['exp']) && $payload['exp'] < $now) {
+ return ['valid' => false, 'error' => 'Token has expired'];
+ }
+
+ // Check not-before (nbf claim)
+ if (isset($payload['nbf']) && $payload['nbf'] > $now) {
+ return ['valid' => false, 'error' => 'Token is not yet valid'];
+ }
+
+ // Validate audience (aud claim) - must be our client ID or Microsoft Graph
+ if (isset($payload['aud'])) {
+ $validAudiences = [
+ $this->clientId,
+ '00000003-0000-0000-c000-000000000000', // Microsoft Graph
+ 'https://graph.microsoft.com'
+ ];
+
+ if (!in_array($payload['aud'], $validAudiences)) {
+ return ['valid' => false, 'error' => 'Invalid audience: ' . $payload['aud']];
+ }
+ }
+
+ // Validate issuer (iss claim) - must be from Azure AD tenant
+ if (isset($payload['iss'])) {
+ $validIssuers = [
+ "https://login.microsoftonline.com/{$this->tenantId}/v2.0",
+ "https://login.microsoftonline.com/{$this->tenantId}/",
+ "https://sts.windows.net/{$this->tenantId}/",
+ "https://login.microsoftonline.com/common/v2.0"
+ ];
+
+ if (!in_array($payload['iss'], $validIssuers)) {
+ return ['valid' => false, 'error' => 'Invalid issuer: ' . $payload['iss']];
+ }
+ }
+
+ return ['valid' => true];
+ }
+
+ /**
+ * Get JWKS (JSON Web Key Set) from Azure AD
+ *
+ * @return array|null
+ */
+ private function getJWKS() {
+ // Return cached JWKS if still valid
+ if ($this->jwksCache && (time() - $this->jwksCacheTime) < $this->jwksCacheDuration) {
+ return $this->jwksCache;
+ }
+
+ // Get OpenID configuration from Azure AD
+ $configUrl = "https://login.microsoftonline.com/{$this->tenantId}/v2.0/.well-known/openid-configuration";
+
+ $config = $this->fetchJson($configUrl);
+ if (!$config || !isset($config['jwks_uri'])) {
+ error_log("Failed to get OpenID configuration from: $configUrl");
+ return null;
+ }
+
+ // Fetch JWKS from jwks_uri
+ $jwks = $this->fetchJson($config['jwks_uri']);
+
+ if (!$jwks || !isset($jwks['keys'])) {
+ error_log("Failed to get JWKS from: " . $config['jwks_uri']);
+ return null;
+ }
+
+ // Ensure all keys have the 'alg' parameter
+ foreach ($jwks['keys'] as &$key) {
+ if (!isset($key['alg'])) {
+ // Default to RS256 for RSA keys
+ if (isset($key['kty']) && $key['kty'] === 'RSA') {
+ $key['alg'] = 'RS256';
+ }
+ }
+ }
+
+ // Cache the JWKS
+ $this->jwksCache = $jwks;
+ $this->jwksCacheTime = time();
+
+ return $jwks;
+ }
+
+ /**
+ * Fetch JSON from a URL
+ *
+ * @param string $url
+ * @return array|null
+ */
+ private function fetchJson($url) {
+ $ch = curl_init($url);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
+ curl_setopt($ch, CURLOPT_TIMEOUT, 10);
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
+
+ $response = curl_exec($ch);
+ $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ $curlError = curl_error($ch);
+ curl_close($ch);
+
+ if ($curlError) {
+ error_log("CURL Error fetching $url: $curlError");
+ return null;
+ }
+
+ if ($httpCode !== 200) {
+ error_log("HTTP Error $httpCode fetching $url");
+ return null;
+ }
+
+ $json = json_decode($response, true);
+ if (json_last_error() !== JSON_ERROR_NONE) {
+ error_log("JSON decode error fetching $url: " . json_last_error_msg());
+ return null;
+ }
+
+ return $json;
+ }
+}
diff --git a/README.md b/README.md
index 30d4b14..939a573 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,11 @@
# VEO3 Usage Report System
-Automated reporting system for VEO3 video generation usage analytics.
+Automated reporting system for VEO3 video generation usage analytics with Microsoft Azure AD Single Sign-On.
## Features
- **PHP Web Interface**: Interactive dashboard for viewing usage reports
+- **SSO Authentication**: Microsoft Azure AD (MSAL) integration for secure access
- **Python Automation**: Automated daily/weekly/monthly email reports via SMTP (Mailgun)
- **Granular Analytics**:
- Last 24 hours activity
@@ -15,14 +16,31 @@ Automated reporting system for VEO3 video generation usage analytics.
## Setup
-### PHP Web Reports
+### PHP Web Reports with SSO
-1. Start PHP development server:
+1. **Install PHP dependencies:**
+ ```bash
+ composer install
+ ```
+
+2. **Configure SSO (see [SSO-SETUP.md](SSO-SETUP.md)):**
+ ```bash
+ cp .env.example .env
+ nano .env # Add Azure AD tenant and client IDs
+ ```
+
+3. **For local development (disable SSO):**
+ ```bash
+ # In .env
+ SSO_ENABLED=false
+ ```
+
+4. **Start PHP development server:**
```bash
php -S localhost:8000
```
-2. Access the interfaces:
+5. **Access the interfaces:**
- `http://localhost:8000/webhook_caller.php` - Fetch data from webhook
- `http://localhost:8000/report.php` - View full report
@@ -134,6 +152,15 @@ The Python script:
### Configuration
- `.env.example` - Configuration template
- `.env` - Your configuration (not in git)
+- `config.php` - SSO and session configuration
+- `env_loader.php` - Environment variable loader
+
+### SSO Files
+- `AuthMiddleware.php` - Main authentication class
+- `JWTValidator.php` - JWT token validator
+- `auth.php` - Authentication API endpoint
+- `composer.json` - PHP dependencies
+- `SSO-SETUP.md` - Complete SSO setup guide
### Generated Files
- `webhook_response.json` - Cached webhook data (auto-generated)
diff --git a/SSO-SETUP.md b/SSO-SETUP.md
new file mode 100644
index 0000000..1210dc0
--- /dev/null
+++ b/SSO-SETUP.md
@@ -0,0 +1,318 @@
+# SSO Setup Guide - Microsoft Azure AD (MSAL)
+
+The VEO3 Report System uses Microsoft Azure AD for Single Sign-On authentication. This protects both the web dashboard and webhook caller pages.
+
+## Prerequisites
+
+- Microsoft Azure AD tenant
+- Permission to register applications in Azure AD
+- PHP 7.4 or higher
+- Composer (PHP package manager)
+
+## Setup Steps
+
+### 1. Install PHP Dependencies
+
+```bash
+cd /path/to/veo3-report
+composer install
+```
+
+This installs the `firebase/php-jwt` library required for token validation.
+
+### 2. Register Application in Azure AD
+
+1. **Login to Azure Portal**: https://portal.azure.com
+2. **Navigate to**: Azure Active Directory → App registrations → New registration
+
+3. **Register the application:**
+ - **Name**: VEO3 Usage Report
+ - **Supported account types**: Accounts in this organizational directory only
+ - **Redirect URI**: Web → `https://your-domain.com/report.php` (and add `/webhook_caller.php`)
+ - Click **Register**
+
+4. **Note these values:**
+ - **Application (client) ID**: Copy this
+ - **Directory (tenant) ID**: Copy this
+
+5. **Configure Authentication:**
+ - Go to **Authentication** in left menu
+ - Under **Implicit grant and hybrid flows**:
+ - ✅ Check **ID tokens**
+ - ✅ Check **Access tokens**
+ - Click **Save**
+
+6. **Configure API Permissions:**
+ - Go to **API permissions** in left menu
+ - Should already have **Microsoft Graph** → **User.Read** (delegated)
+ - This is sufficient for SSO
+
+### 3. Configure Environment Variables
+
+Edit your `.env` file:
+
+```bash
+# SSO Configuration
+SSO_ENABLED=true
+SSO_TENANT_ID=your-tenant-id-here
+SSO_CLIENT_ID=your-client-id-here
+```
+
+**For Local Development (disable SSO):**
+```bash
+SSO_ENABLED=false
+```
+
+When SSO is disabled, you'll be logged in as "Local Developer" automatically.
+
+### 4. Deploy to Server
+
+If deploying with the automated script:
+
+```bash
+# Ensure .env is configured
+nano .env
+
+# Run deployment
+sudo ./deploy.sh
+```
+
+The deployment script will:
+- Copy SSO files
+- Install Composer dependencies
+- Set up the systemd service
+
+### 5. Configure Web Server
+
+#### Apache (.htaccess)
+
+```apache
+# Enable PHP
+AddHandler application/x-httpd-php .php
+
+# Security headers
+Header always set X-Frame-Options "SAMEORIGIN"
+Header always set X-Content-Type-Options "nosniff"
+Header always set X-XSS-Protection "1; mode=block"
+
+# Deny access to sensitive files
+
+ Require all denied
+
+
+
+ Require all denied
+
+
+
+ Require all denied
+
+```
+
+#### Nginx
+
+```nginx
+location ~ \.php$ {
+ fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
+ fastcgi_index index.php;
+ fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+ include fastcgi_params;
+}
+
+# Deny access to sensitive files
+location ~ /\. {
+ deny all;
+}
+
+location ~ composer\.(json|lock)$ {
+ deny all;
+}
+
+# Security headers
+add_header X-Frame-Options "SAMEORIGIN" always;
+add_header X-Content-Type-Options "nosniff" always;
+add_header X-XSS-Protection "1; mode=block" always;
+```
+
+## Testing SSO
+
+### 1. Test Locally (SSO Disabled)
+
+```bash
+# In .env
+SSO_ENABLED=false
+
+# Start PHP server
+php -S localhost:8000
+
+# Visit http://localhost:8000/report.php
+# Should show reports without login
+```
+
+### 2. Test with SSO Enabled
+
+```bash
+# In .env
+SSO_ENABLED=true
+SSO_TENANT_ID=your-tenant-id
+SSO_CLIENT_ID=your-client-id
+
+# Visit https://your-domain.com/report.php
+# Should redirect to Microsoft login
+```
+
+## How It Works
+
+### Authentication Flow
+
+1. **User visits protected page** (report.php or webhook_caller.php)
+2. **AuthMiddleware checks** for valid authentication cookie
+3. **If not authenticated:**
+ - Shows Microsoft login page
+ - User authenticates with Azure AD
+ - MSAL returns JWT token
+ - Token sent to `auth.php` for validation
+ - Valid token stored in secure cookie
+ - Page reloads with authenticated session
+4. **If authenticated:**
+ - JWT token validated on each request
+ - User information available in `$user` variable
+
+### Security Features
+
+- ✅ **JWT validation** with Azure AD public keys
+- ✅ **HttpOnly cookies** (not accessible to JavaScript)
+- ✅ **Token expiration** checked (24-hour lifetime)
+- ✅ **Audience validation** (ensures token is for this app)
+- ✅ **Issuer validation** (ensures token from correct Azure tenant)
+- ✅ **SameSite cookie** attribute (CSRF protection)
+- ✅ **Secure cookies** over HTTPS in production
+
+### File Structure
+
+```
+veo3-report/
+├── AuthMiddleware.php # Main authentication class
+├── JWTValidator.php # JWT token validator
+├── auth.php # Authentication API endpoint
+├── config.php # SSO configuration
+├── env_loader.php # .env file loader
+├── report.php # Protected: requires auth
+├── webhook_caller.php # Protected: requires auth
+└── composer.json # PHP dependencies
+```
+
+## Troubleshooting
+
+### "Could not retrieve public keys from Azure AD"
+
+**Cause**: Network issue or invalid tenant ID
+**Fix**:
+- Verify `SSO_TENANT_ID` in `.env`
+- Check server can reach `login.microsoftonline.com`
+- Check PHP error logs: `tail -f /var/log/apache2/error.log`
+
+### "Token signature is invalid"
+
+**Cause**: Token from wrong tenant or client
+**Fix**:
+- Verify `SSO_CLIENT_ID` matches Azure app registration
+- Clear browser cookies and try again
+- Ensure redirect URI matches exactly in Azure
+
+### "Invalid audience"
+
+**Cause**: Token not intended for this application
+**Fix**:
+- Verify `SSO_CLIENT_ID` in `.env`
+- Check Azure app registration client ID
+
+### Login popup blocked
+
+**Cause**: Browser blocking popups
+**Fix**:
+- Allow popups for your domain
+- Try different browser
+
+### Token expired
+
+**Cause**: Token older than 24 hours
+**Fix**:
+- Clear cookies and login again
+- Automatic redirect to login should occur
+
+## Disabling SSO
+
+To temporarily disable SSO (for maintenance or testing):
+
+```bash
+# In .env
+SSO_ENABLED=false
+
+# Restart web server/PHP-FPM
+sudo systemctl restart apache2
+# or
+sudo systemctl restart nginx
+```
+
+When disabled:
+- No login required
+- Mock user "Local Developer" used
+- Full access to all features
+
+## Adding More Protected Pages
+
+To protect additional PHP pages:
+
+```php
+requireAuth();
+
+// User is authenticated, $user contains:
+// - name: Display name
+// - preferred_username: Email address
+?>
+```
+
+## User Logout
+
+Add a logout link:
+
+```html
+Logout
+
+
+```
+
+## Production Checklist
+
+- [ ] Azure app registered with correct redirect URIs
+- [ ] `.env` configured with correct tenant/client IDs
+- [ ] `SSO_ENABLED=true` in `.env`
+- [ ] Composer dependencies installed
+- [ ] HTTPS enabled (required for production)
+- [ ] Security headers configured in web server
+- [ ] Error logs monitored for auth issues
+- [ ] Test login/logout flow
+
+## Support
+
+For SSO issues:
+1. Check PHP error logs
+2. Check browser console for JavaScript errors
+3. Verify Azure app configuration
+4. Test with `SSO_ENABLED=false` to isolate issue
diff --git a/auth.php b/auth.php
new file mode 100644
index 0000000..4e40467
--- /dev/null
+++ b/auth.php
@@ -0,0 +1,118 @@
+ 'Invalid request - action required']);
+ exit;
+}
+
+$action = $input['action'];
+
+// Handle different actions
+switch ($action) {
+ case 'login':
+ handleLogin($auth, $input);
+ break;
+
+ case 'logout':
+ handleLogout($auth);
+ break;
+
+ case 'status':
+ handleStatus($auth);
+ break;
+
+ default:
+ http_response_code(400);
+ echo json_encode(['error' => 'Unknown action: ' . $action]);
+ break;
+}
+
+/**
+ * Handle login action
+ */
+function handleLogin($auth, $input) {
+ if (!$auth->isSSOEnabled()) {
+ http_response_code(400);
+ echo json_encode(['error' => 'SSO is disabled']);
+ return;
+ }
+
+ // Prefer ID token for validation, fallback to access token
+ $token = $input['idToken'] ?? $input['accessToken'] ?? null;
+
+ if (!$token) {
+ http_response_code(400);
+ echo json_encode(['error' => 'Authentication token is required']);
+ return;
+ }
+
+ // Validate and set token
+ $result = $auth->setAuthToken($token);
+
+ if ($result['success']) {
+ echo json_encode([
+ 'success' => true,
+ 'message' => 'Authentication successful',
+ 'user' => [
+ 'name' => $result['user']['name'] ?? 'Unknown',
+ 'email' => $result['user']['preferred_username'] ?? $result['user']['upn'] ?? 'Unknown'
+ ]
+ ]);
+ } else {
+ http_response_code(401);
+ echo json_encode([
+ 'success' => false,
+ 'error' => $result['error']
+ ]);
+ }
+}
+
+/**
+ * Handle logout action
+ */
+function handleLogout($auth) {
+ $auth->clearAuthToken();
+ echo json_encode([
+ 'success' => true,
+ 'message' => 'Logged out successfully'
+ ]);
+}
+
+/**
+ * Handle status check action
+ */
+function handleStatus($auth) {
+ $authStatus = $auth->isAuthenticated();
+
+ if ($authStatus['authenticated']) {
+ echo json_encode([
+ 'authenticated' => true,
+ 'sso_enabled' => $auth->isSSOEnabled(),
+ 'user' => [
+ 'name' => $authStatus['user']['name'] ?? 'Unknown',
+ 'email' => $authStatus['user']['preferred_username'] ?? $authStatus['user']['upn'] ?? 'Unknown'
+ ]
+ ]);
+ } else {
+ http_response_code(401);
+ echo json_encode([
+ 'authenticated' => false,
+ 'sso_enabled' => $auth->isSSOEnabled(),
+ 'error' => $authStatus['error'] ?? 'Not authenticated'
+ ]);
+ }
+}
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..a03a9f7
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,20 @@
+{
+ "name": "veo3-report/usage-analytics",
+ "description": "VEO3 Usage Report System with MSAL Authentication",
+ "type": "project",
+ "require": {
+ "php": ">=7.4",
+ "firebase/php-jwt": "^6.0"
+ },
+ "require-dev": {},
+ "autoload": {
+ "classmap": [
+ "JWTValidator.php",
+ "AuthMiddleware.php"
+ ]
+ },
+ "config": {
+ "optimize-autoloader": true,
+ "sort-packages": true
+ }
+}
diff --git a/config.php b/config.php
new file mode 100644
index 0000000..dff8311
--- /dev/null
+++ b/config.php
@@ -0,0 +1,30 @@
+requireAuth(); // This will redirect to login if not authenticated
+
// Load and parse JSON
if (!file_exists($responseFile)) {
die("Error: Response file not found. Please run webhook_caller.php first.");
diff --git a/webhook_caller.php b/webhook_caller.php
index c5ca2b2..79d9a3a 100644
--- a/webhook_caller.php
+++ b/webhook_caller.php
@@ -1,4 +1,9 @@
requireAuth(); // This will redirect to login if not authenticated
+
// Configuration
$webhookUrl = 'https://hook.us1.make.celonis.com/u8i4yq6rydu8u8g9bfhk0xbajsyckrmj';
$responseFile = 'webhook_response.json';