diff --git a/README.md b/README.md
index 701cdcd..1aa3b9e 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,12 @@
# ctiapp-authproxy
Authentication proxy for Acrobits application.
+This repository contains a PHP proxy used to authenticate against a CTI cloud and return configuration to clients (SIP credentials, phonebook, quick dials). The main logic is in `app/index.php`.
-The application needs a `TOKEN` environment application on startup.
+The application needs the following environment variable on startup:
+- `TOKEN`: a secret token used to validate incoming requests
+- `VALIDATE_LK_URL`: URL to validate user credentials against a remote server
+- `DEBUG`: optional, if set to "true" enables debug logging
Each request must be a POST request containing a JSON object.
The object must have the following fields:
@@ -10,7 +14,50 @@ The object must have the following fields:
- `password`
- `token`: it's a SHA256 hash, it must be the same passed to the application at startup
-Example with curl:
+## Local testing
+
+Below are minimal steps and examples to test the application locally without Docker. These examples assume you have PHP 8+ installed and are running them from the repository root.
+
+1) Set environment variables required by the app (example values):
+
+```bash
+export TOKEN="localtesttoken"
+export VALIDATE_LK_URL="https://httpbin.org/status/200" # This is a fake endpoint for testing
+export DEBUG="true"
+```
+
+2) Start a built-in PHP web server to serve the `app` directory:
+
+```bash
+php -S 127.0.0.1:8000 -t app/
+```
+
+3) Healthcheck (quick):
+
+```bash
+curl -i http://127.0.0.1:8000/index.php/healthcheck
```
-curl -d '{"username": "myuser@demo.example.com", "password": "mypass", "token": "11223344"}' https://ctiapp-authproxy.example.com
+
+4) Test the `login` flow (replace with real or mocked endpoints):
+
+```bash
+curl -i -X POST http://127.0.0.1:8000/index.php \
+ -H "Content-Type: application/json" \
+ -d '{"username":"alice@cti.example.com","password":"secret","token":"localtesttoken","app":"login"}'
+```
+
+5) Test the `contacts` flow:
+
+```bash
+curl -i -X POST http://127.0.0.1:8000/index.php \
+ -H "Content-Type: application/json" \
+ -d '{"username":"alice@cti.example.com","password":"secret","token":"localtesttoken","app":"contacts"}'
+```
+
+6) Test the `quickdial` flow:
+
+```bash
+curl -i -X POST http://127.0.0.1:8000/index.php \
+ -H "Content-Type: application/json" \
+ -d '{"username":"alice@cti.example.com","password":"secret","token":"localtesttoken","app":"quickdial"}'
```
diff --git a/app/index.php b/app/index.php
index 3d66504..c363179 100644
--- a/app/index.php
+++ b/app/index.php
@@ -35,13 +35,10 @@ function makeRequest($username, $token, $url)
if (curl_error($ch)) {
$error = curl_error($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
- curl_close($ch);
error_log("ERROR: cURL failed: $error (HTTP $httpCode) for URL: $url");
return false;
}
-
- curl_close($ch);
-
+
// read response
$jsonResponse = json_decode($response, true);
@@ -77,14 +74,12 @@ function getAuthToken($cloudUsername, $cloudPassword, $cloudDomain)
// Add error handling for curl execution
if ($response === false) {
error_log("ERROR: cURL error during authentication: " . curl_error($ch));
- curl_close($ch);
return false;
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
// close curl and read response
- curl_close($ch);
if ($httpCode !== 401) {
error_log("ERROR: Authentication failed for {$cloudUsername}@{$cloudDomain}. Expected HTTP code 401, got $httpCode");
return false;
@@ -152,7 +147,6 @@ function getSipCredentials($cloudUsername, $cloudPassword, $cloudDomain, $isToke
// exec curl
$lkcheck = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
- curl_close($ch);
// print debug
debug("lkhash validated for {$cloudUsername}@{$cloudDomain}", $cloudDomain);
@@ -185,6 +179,103 @@ function getSipCredentials($cloudUsername, $cloudPassword, $cloudDomain, $isToke
return false;
}
+// helper to perform POST JSON requests and return decoded JSON or false
+function postJsonRequest($url, $payload = [], $headers = [])
+{
+ $ch = curl_init($url);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
+ curl_setopt($ch, CURLOPT_POST, true);
+ curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
+
+ $defaultHeaders = ["Content-Type: application/json"];
+ $allHeaders = array_merge($defaultHeaders, $headers);
+ curl_setopt($ch, CURLOPT_HTTPHEADER, $allHeaders);
+
+ $response = curl_exec($ch);
+
+ if (curl_error($ch) || curl_getinfo($ch, CURLINFO_HTTP_CODE) >= 400 || $response === false) {
+ $error = curl_error($ch);
+ $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ error_log("ERROR: cURL POST request failed: $error (HTTP $httpCode) for URL: $url");
+ return false;
+ }
+
+ $json = json_decode($response, true);
+ if ($json === null && json_last_error() !== JSON_ERROR_NONE) {
+ error_log("ERROR: Failed to decode JSON POST response: " . json_last_error_msg());
+ error_log("ERROR: Response: " . $response);
+ return false;
+ }
+
+ return $json;
+}
+
+// makeCTIRequest: login to CTI API to obtain JWT then run the actual request with Bearer token
+// $cloudDomain: e.g. "cti.gs.nethserver.net"
+// $username, $password: credentials for login
+// $method: HTTP method for actual request (GET, POST,...)
+// $path: path for actual request (e.g. '/api/chat')
+// $body: optional payload for actual request (array)
+function makeCTIRequest($cloudDomain, $username, $password, $method, $path, $body = null)
+{
+ $cloudBaseUrl = "https://$cloudDomain";
+
+ // Step 1: login and get token
+ $loginUrl = rtrim($cloudBaseUrl, '/') . '/api/login';
+ debug("Logging in to $loginUrl as $username", $cloudDomain);
+
+ $loginPayload = [
+ 'username' => $username,
+ 'password' => $password
+ ];
+
+ $loginResp = postJsonRequest($loginUrl, $loginPayload);
+ if ($loginResp === false || !isset($loginResp['token'])) {
+ error_log("ERROR: Failed to login to CTI at $loginUrl");
+ return [ 'code' => -1, 'body' => null ];
+ }
+
+ $token = $loginResp['token'];
+ debug("Received JWT for $username", $cloudDomain);
+
+ // Step 2: perform actual request using Bearer token
+ $requestUrl = rtrim($cloudBaseUrl, '/') . '/' . ltrim($path, '/');
+ $headers = ["Authorization: Bearer $token", 'Accept: application/json', 'Content-Type: application/json'];
+
+ $ch = curl_init($requestUrl);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
+ curl_setopt($ch, CURLOPT_CUSTOMREQUEST, strtoupper($method));
+ curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
+
+ if ($body !== null) {
+ $payload = json_encode($body);
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
+ }
+
+ $response = curl_exec($ch);
+
+ if ($response === false) {
+ $err = curl_error($ch);
+ $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ error_log("ERROR: cURL CTI request failed: $err (HTTP $httpCode) for URL: $requestUrl");
+ return [ 'code' => -1, 'body' => null ];
+ }
+
+ $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+
+ // decode JSON if possible
+ $json = json_decode($response, true);
+ if ($json === null && json_last_error() !== JSON_ERROR_NONE) {
+ // return raw response if not JSON
+ debug("CTI request returned non-JSON or empty response (HTTP $httpCode)", $cloudDomain);
+ return ['code' => $httpCode, 'raw' => $response];
+ }
+
+ return ['code' => $httpCode, 'body' => $json];
+}
+
function handle($data)
{
// check if username, password and token are set
@@ -276,6 +367,28 @@ function handle($data)
$busylamps[] = '' . $busylamp . '';
}
+ // get chat info
+ $chatResponse = makeCTIRequest($cloudDomain, $cloudUsername, $cloudPassword, "GET", "/api/chat");
+ debug("Chat API response: " . $chatResponse["code"], $cloudDomain);
+ if ($chatResponse && $chatResponse["code"] === 200 && isset($chatResponse["body"]["matrix"]["acrobits_url"])) {
+ $fetchPostData = '{ "username" : "%account[cloud_username]% ", "password" : "%account[cloud_password]%", "last_id" : "%last_known_sms_id%", "last_sent_id" : "%last_known_sent_sms_id%", "device" : "%installid%" }';
+ $sendPostData = '{ "from" : "%account[cloud_username]%", "password" : "%account[cloud_password]%", "to" : "%sms_to%", "body" : "%sms_body%", "content_type" : "%content_type%" }';
+ $pushTokenReporterPostData = '{ "username" : "%account[cloud_username]%", "password" : "%account[cloud_password]%", "token_calls" : "%pushTokenIncomingCall%", "token_msgs" : "%pushTokenOther%", "selector" : "%selector%", "appId_calls": "%pushappid_incoming_call%", "appId_msgs" : "%pushappid_other%" }';
+ $chat = "".
+ "" . $chatResponse["body"]["matrix"]["acrobits_url"] . "/api/client/fetch_messages" . "\n".
+ "{$fetchPostData}\n".
+ "application/json\n".
+ "" . $chatResponse["body"]["matrix"]["acrobits_url"] . "/api/client/send_message" . "\n".
+ "{$sendPostData}\n".
+ "application/json\n".
+ "" . $chatResponse["body"]["matrix"]["acrobits_url"] . "/api/client/push_token_report" . "\n".
+ "{$pushTokenReporterPostData}\n".
+ "application/json\n";
+ } else {
+ $chat = "";
+ debug("No chat configuration found for {$cloudUsername}@{$cloudDomain}", $cloudDomain);
+ }
+
// set headers
header("Content-type: text/xml");
@@ -300,6 +413,7 @@ function handle($data)
{$cloudDomain}
tls+sip:
" . implode("", $busylamps) . "
+ $chat
";