From d64f6b19df99009fb31aa08cb1f0c8b0c46d04d5 Mon Sep 17 00:00:00 2001 From: DJP Date: Tue, 6 Jan 2026 11:13:21 -0500 Subject: [PATCH] Add SSO authentication and update UI styling - Added index.php with SSO authentication from NANO-RESEARCH - Added AuthMiddleware, JWTValidator, and auth endpoints - Updated logo to 25% size and left-aligned - Changed fonts to Montserrat - Added .htaccess for directory index --- .DS_Store | Bin 6148 -> 6148 bytes .env.example copy | 13 ++ .gitignore | 7 + .htaccess | 1 + AUTH_README.md | 302 +++++++++++++++++++++++++++++++++ AuthMiddleware.php | 409 +++++++++++++++++++++++++++++++++++++++++++++ JWTValidator.php | 201 ++++++++++++++++++++++ auth-test.php | 123 ++++++++++++++ auth.php | 118 +++++++++++++ composer.json | 20 +++ env_loader.php | 55 ++++++ index.php | 119 +++++++++++++ ui.html | 5 +- ui.html.backup | 384 ++++++++++++++++++++++++++++++++++++++++++ 14 files changed, 1756 insertions(+), 1 deletion(-) create mode 100644 .env.example copy create mode 100644 .htaccess create mode 100644 AUTH_README.md create mode 100644 AuthMiddleware.php create mode 100644 JWTValidator.php create mode 100644 auth-test.php create mode 100644 auth.php create mode 100644 composer.json create mode 100644 env_loader.php create mode 100644 index.php create mode 100644 ui.html.backup diff --git a/.DS_Store b/.DS_Store index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..73cbd78469920862e9afb784bb58b804e9893826 100644 GIT binary patch delta 175 zcmZoMXfc=|&e%RNQEZ}~q9_vs0|O%ig8&0V4nrz~8$%|Do|vc}36kJuC}v1x$Ym%% zmSiaQ%*jtq%E?axssQR^;0NLVP=H%kN^x>dQht8U#zbZI$p#`Uo4Gl7I2hYECVpq0 X%rBxS2(nNCh!cPq3^qH89A*Xp`3fk= delta 68 zcmZoMXfc=|&Zs)EP;8=}A_oHyFfuR*Z2Tz3K8bPGW_At%4o20DAHOqC<`+>E1WGX^ QfYbm1h~2Q+QRFZ)03Q1eX8-^I diff --git a/.env.example copy b/.env.example copy new file mode 100644 index 0000000..dbafee3 --- /dev/null +++ b/.env.example copy @@ -0,0 +1,13 @@ +# MSAL Authentication Configuration +# Set SSO_ENABLED=true to require Microsoft login +# Set SSO_ENABLED=false for local development (uses mock user) +SSO_ENABLED=false + +# Azure AD Configuration (required when SSO_ENABLED=true) +# Get these values from your Azure AD App Registration +SSO_TENANT_ID=your-azure-tenant-id-here +SSO_CLIENT_ID=your-azure-application-client-id-here + +# Example values (replace with your actual Azure AD credentials): +# SSO_TENANT_ID=e519c2e6-bc6d-4fdf-8d9c-923c2f002385 +# SSO_CLIENT_ID=9079054c-9620-4757-a256-23413042f1ef diff --git a/.gitignore b/.gitignore index 2258f1d..fcf1751 100644 --- a/.gitignore +++ b/.gitignore @@ -70,3 +70,10 @@ build/ .env .env.local .env.*.local + +# PHP Composer +vendor/ +composer.lock + +# PHP Configuration (contains sensitive data) +config.php diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..e557138 --- /dev/null +++ b/.htaccess @@ -0,0 +1 @@ +DirectoryIndex index.php index.html diff --git a/AUTH_README.md b/AUTH_README.md new file mode 100644 index 0000000..27405d1 --- /dev/null +++ b/AUTH_README.md @@ -0,0 +1,302 @@ +# MSAL Authentication Setup Guide + +## Overview +PencilAutomator now includes Microsoft Authentication Library (MSAL) / Azure AD Single Sign-On (SSO) authentication. The authentication can be **toggled on/off** via environment variable for seamless testing and deployment. + +--- + +## Quick Start + +### Local Development (No Authentication) +```bash +# 1. Ensure .env file exists with: +SSO_ENABLED=false + +# 2. Run the app normally in MAMP or your PHP server +# All users get mock "Local Developer" credentials +# No login required +``` + +### Production (with SSO) +```bash +# 1. Update .env file: +SSO_ENABLED=true +SSO_TENANT_ID=your-azure-tenant-id +SSO_CLIENT_ID=your-azure-application-id + +# 2. Deploy to server +# 3. Users must login with Microsoft account +``` + +--- + +## Installation Steps + +### 1. Install Dependencies +```bash +cd /Users/daveporter/Desktop/CODING-2024/pencil_automator +composer install +``` + +This installs the Firebase JWT library required for token validation. + +### 2. Configure Environment +```bash +# Copy example file +cp .env.example .env + +# Edit .env and set: +SSO_ENABLED=false # Start with authentication disabled +``` + +### 3. Azure AD Setup (When Enabling SSO) + +#### Create Azure AD App Registration: +1. Go to [Azure Portal](https://portal.azure.com) +2. Navigate to: **Azure Active Directory** → **App registrations** → **New registration** +3. Set name: "PencilAutomator" +4. Set redirect URI: `https://your-server-url.com/path/to/pencil_automator/index.php` +5. Click **Register** + +#### Get Credentials: +1. Copy **Application (client) ID** → This is your `SSO_CLIENT_ID` +2. Copy **Directory (tenant) ID** → This is your `SSO_TENANT_ID` +3. Go to **Authentication** → Enable **ID tokens** checkbox +4. Go to **API permissions** → Add: `openid`, `profile`, `email` + +#### Update .env: +```bash +SSO_ENABLED=true +SSO_TENANT_ID=e519c2e6-bc6d-4fdf-8d9c-923c2f002385 +SSO_CLIENT_ID=9079054c-9620-4757-a256-23413042f1ef +``` + +--- + +## File Structure + +### New Files Created: +``` +/pencil_automator/ +├── composer.json # PHP dependencies (Firebase JWT) +├── .env # Environment config (gitignored) +├── .env.example # Template for environment variables +├── env_loader.php # Loads .env file +├── JWTValidator.php # JWT token validation logic +├── AuthMiddleware.php # Auth orchestrator + login UI +├── auth.php # Auth API endpoint +├── auth-test.php # Debugging page +├── config.php # Configuration (gitignored) +├── AUTH_README.md # This file +└── vendor/ # Composer dependencies (gitignored) +``` + +### Modified Files: +``` +.gitignore # Added .env, vendor/, config.php +``` + +--- + +## How It Works + +### When SSO_ENABLED=false (Testing Mode) +1. User visits app +2. AuthMiddleware returns mock "Local Developer" user +3. No login page shown +4. All features work normally +5. Perfect for local testing + +### When SSO_ENABLED=true (Production Mode) +1. User visits app +2. AuthMiddleware checks for `auth_token` cookie +3. If no token → Show MSAL login page +4. User clicks "Sign In with Microsoft" +5. MSAL popup opens for Azure AD login +6. User authenticates +7. Token sent to `auth.php` for validation +8. JWT validated against Azure AD public keys +9. Token stored in httpOnly cookie (24 hours) +10. User redirected to app +11. Logout button visible in header + +--- + +## Testing + +### Test Authentication Status +Visit: `http://your-server/auth-test.php` + +Shows: +- SSO configuration (enabled/disabled) +- Tenant ID and Client ID +- Current authentication status +- User information +- Cookie presence + +### Test Locally (SSO Disabled) +```bash +# 1. Set SSO_ENABLED=false in .env +# 2. Open app in MAMP or PHP server +# 3. Should see "Welcome, Local Developer" (if SSO was previously enabled) +# 4. App functions normally +# 5. No login/logout buttons +``` + +### Test on Server (SSO Enabled) +```bash +# NOTE: Cannot test locally - Azure AD requires exact redirect URI match + +# 1. Deploy to production server +# 2. Set SSO_ENABLED=true in .env on server +# 3. Add Azure AD credentials to .env +# 4. Visit app URL +# 5. Should see login page +# 6. Click "Sign In with Microsoft" +# 7. Complete Microsoft login +# 8. Should redirect to app +# 9. Should see "Welcome, [Your Name]" and logout button +``` + +--- + +## Security Features + +✅ **httpOnly Cookies** - Prevents XSS attacks (JavaScript can't access token) +✅ **SameSite=Lax** - Prevents CSRF attacks +✅ **Secure Flag** - Cookie only sent over HTTPS in production +✅ **JWT Validation** - Cryptographic verification of tokens +✅ **Expiration Check** - Validates `exp` claim +✅ **Not-Before Check** - Validates `nbf` claim +✅ **Audience Validation** - Ensures token is for our app +✅ **Issuer Validation** - Ensures token from Azure AD +✅ **JWKS Verification** - Uses Azure AD public keys +✅ **24-Hour Expiration** - Tokens expire after 1 day + +--- + +## Troubleshooting + +### Login Page Shows But Can't Login +- Check Azure AD app registration has correct redirect URI +- Ensure `SSO_TENANT_ID` and `SSO_CLIENT_ID` are correct +- Check browser console for MSAL errors +- Visit `auth-test.php` to verify configuration + +### "Authentication Required" Error +- Check `auth_token` cookie exists (browser dev tools) +- Token may have expired (24-hour limit) +- Try logging out and back in +- Check `auth-test.php` for token status + +### SSO Not Disabling +- Verify `.env` has `SSO_ENABLED=false` (not "false" in quotes) +- Clear browser cookies +- Restart PHP server/MAMP +- Check `auth-test.php` shows "SSO Enabled: NO" + +### Token Validation Failing +- Check server can reach Azure AD endpoints +- Verify tenant ID and client ID match Azure AD +- Check token hasn't expired +- Review `error_log` for JWT validation details + +--- + +## API Endpoints + +### Login +```http +POST /auth.php +Content-Type: application/json + +{ + "action": "login", + "idToken": "eyJ0eXAiOiJKV1QiLCJhbGci...", + "accessToken": "eyJ0eXAiOiJKV1QiLCJhbGci..." +} +``` + +### Logout +```http +POST /auth.php +Content-Type: application/json + +{ + "action": "logout" +} +``` + +### Status Check +```http +POST /auth.php +Content-Type: application/json + +{ + "action": "status" +} +``` + +--- + +## Maintenance + +### Rotating Credentials +1. Update Azure AD app registration +2. Update `.env` with new credentials +3. No code changes needed +4. Existing sessions remain valid until cookie expires + +### Disabling SSO Temporarily +```bash +# In .env: +SSO_ENABLED=false + +# Immediately disables SSO for all users +# No restart needed +# Users get mock "Local Developer" access +``` + +### Monitoring +- Check `error_log` for authentication failures +- Monitor Azure AD sign-in logs +- Track failed login attempts +- Review token validation errors + +--- + +## Production Checklist + +Before enabling SSO in production: + +- [ ] Composer dependencies installed (`vendor/` directory exists) +- [ ] `.env` file configured with Azure AD credentials +- [ ] Azure AD app registration created +- [ ] Redirect URI matches production URL exactly +- [ ] ID tokens enabled in Azure AD app +- [ ] API permissions added (`openid`, `profile`, `email`) +- [ ] HTTPS enabled on production server +- [ ] `auth-test.php` shows correct configuration +- [ ] Test login/logout flow works +- [ ] Error logging enabled + +--- + +## Support + +For issues with: +- **MSAL errors**: Check [MSAL.js documentation](https://github.com/AzureAD/microsoft-authentication-library-for-js) +- **Azure AD setup**: Check [Azure AD app registration guide](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app) +- **JWT validation**: Check Firebase JWT library logs in `error_log` +- **Configuration**: Run `auth-test.php` to see current setup + +--- + +## Important Notes + +- **Cannot test MSAL locally** - Azure AD requires exact URL match +- **Testing happens on server** after deployment +- **SSO toggle allows testing without auth** before enabling +- **httpOnly cookies** mean token not accessible via JavaScript +- **24-hour token expiration** - users must re-login daily +- **Mock user** (`dev@localhost`) used when SSO disabled diff --git a/AuthMiddleware.php b/AuthMiddleware.php new file mode 100644 index 0000000..b2434b9 --- /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 - PencilAutomator + + + + + + + +
+ +

PencilAutomator

+ + + +
+ +
+ + + + +
+ Sign in with your Microsoft account to access the plugin +
+
+ + + + + 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/auth-test.php b/auth-test.php new file mode 100644 index 0000000..8e85c5c --- /dev/null +++ b/auth-test.php @@ -0,0 +1,123 @@ +isAuthenticated(); +?> + + + + + + Auth Test - PencilAutomator + + + +

🔐 Authentication Test Page

+ +
+

SSO Configuration

+

SSO Enabled:

+

Tenant ID:

+

Client ID:

+
+ +
+

Authentication Status

+

Authenticated: + + + +

+ + +

User Name:

+

Email:

+ +

Error:

+ +
+ +
+

Cookie Information

+

Auth Token Cookie: + + + +

+
+ + +
+

User Payload (Debug)

+
+
+ + +
+

Actions

+

← Back to Application

+ +

Logout

+ +
+ + + + 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..5a73eb6 --- /dev/null +++ b/composer.json @@ -0,0 +1,20 @@ +{ + "name": "pencil-automator/figma-plugin", + "description": "Figma Plugin 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 + } +} \ No newline at end of file diff --git a/env_loader.php b/env_loader.php new file mode 100644 index 0000000..1be77a4 --- /dev/null +++ b/env_loader.php @@ -0,0 +1,55 @@ + 'User', 'preferred_username' => 'user@localhost']; +$ssoEnabled = false; + +try { + if (file_exists(__DIR__ . '/AuthMiddleware.php')) { + require_once 'AuthMiddleware.php'; + $auth = new AuthMiddleware(); + $user = $auth->requireAuth(); // Blocks if not authenticated + $ssoEnabled = $auth->isSSOEnabled(); + } +} catch (Exception $e) { + // Log error but don't block app + error_log("Auth error (app will continue without auth): " . $e->getMessage()); +} + +// Read and serve the UI HTML +$uiContent = file_get_contents(__DIR__ . '/ui.html'); + +// Extract the body content from ui.html +preg_match('/(.*?)<\/body>/s', $uiContent, $bodyMatches); +$bodyContent = $bodyMatches[1] ?? ''; + +// Extract the style content from ui.html +preg_match('/ + + + +
+ + +
+ + + + + + + + + + + diff --git a/ui.html b/ui.html index 855625c..18d1c7a 100644 --- a/ui.html +++ b/ui.html @@ -2,9 +2,12 @@ + + + + + + +
+ PencilAutomator +
+ + +
+ +
+

Drag & Drop .xlsx here
or click to browse

+ +
+
+
+ + +
+ + + +
+
+ + +
+ +
+ + + +
+
+ + +
+ +
+
+ + + + + + + + \ No newline at end of file