From f0975fbfc3627e717f9398933db2022fa2f109bb Mon Sep 17 00:00:00 2001 From: Steven Clouston Date: Thu, 28 Nov 2024 10:52:44 +1300 Subject: [PATCH 01/21] Change apiBase to apiURL --- README.md | 2 +- lib/Authsignal/AuthsignalClient.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ae40d02..b7c4684 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ An example setting the client to use the AU region. Authsignal::setApiHostname("https://au.signal.authsignal.com"); ``` -Alternatively, an environment variable can be used to set the base URL: +Alternatively, an environment variable can be used to set the API URL: ```bash AUTHSIGNAL_SERVER_API_ENDPOINT=https://au.signal.authsignal.com/v1 diff --git a/lib/Authsignal/AuthsignalClient.php b/lib/Authsignal/AuthsignalClient.php index ed9dbd8..4c8f866 100644 --- a/lib/Authsignal/AuthsignalClient.php +++ b/lib/Authsignal/AuthsignalClient.php @@ -6,9 +6,9 @@ public static function apiUrl($path='') { $apiEndpoint = getenv('AUTHSIGNAL_SERVER_API_ENDPOINT'); if ( !$apiEndpoint ) { - $apiBase = Authsignal::$apiHostname; + $apiURL = Authsignal::$apiHostname; $apiVersion = Authsignal::getApiVersion(); - $apiEndpoint = $apiBase.'/'.$apiVersion; + $apiEndpoint = $apiURL.'/'.$apiVersion; } return $apiEndpoint.$path; } From cd998be127a70e80099b42f61756ff8eefa77e82 Mon Sep 17 00:00:00 2001 From: Steven Clouston Date: Fri, 29 Nov 2024 13:49:32 +1300 Subject: [PATCH 02/21] Add updateAction function --- lib/Authsignal/Authsignal.php | 19 +++++++++++++++++++ lib/Authsignal/AuthsignalRequestTransport.php | 3 +++ 2 files changed, 22 insertions(+) diff --git a/lib/Authsignal/Authsignal.php b/lib/Authsignal/Authsignal.php index 11854dd..55ff4a4 100644 --- a/lib/Authsignal/Authsignal.php +++ b/lib/Authsignal/Authsignal.php @@ -210,4 +210,23 @@ public static function validateChallenge(string $token, ?string $userId = null, return $response; } + + /** + * Update Action + * Updates the state of an action for a user + * @param string $userId The userId of the user to update the action for + * @param string $action The action code to update + * @param string $idempotencyKey The idempotency key for the action + * @param array $attributes Additional attributes for the action + * @return array The Authsignal response + */ + public static function updateAction(string $userId, string $action, string $idempotencyKey, array $attributes) + { + $request = new AuthsignalClient(); + $path = "/users/" . urlencode($userId) . "/actions/" . urlencode($action) . "/" . urlencode($idempotencyKey); + + list($response, $request) = $request->send($path, $attributes, 'patch'); + return $response; + } + } diff --git a/lib/Authsignal/AuthsignalRequestTransport.php b/lib/Authsignal/AuthsignalRequestTransport.php index c68e508..b2a125f 100644 --- a/lib/Authsignal/AuthsignalRequestTransport.php +++ b/lib/Authsignal/AuthsignalRequestTransport.php @@ -50,6 +50,9 @@ public function send($method, $url, $payload) { case 'put': curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "PUT"); break; + case 'patch': + curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "PATCH"); + break; case 'delete': curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "DELETE"); break; From 89cd1a823fdab19c17c61cd0cded56b1593ac4c9 Mon Sep 17 00:00:00 2001 From: Steven Clouston Date: Fri, 29 Nov 2024 15:20:51 +1300 Subject: [PATCH 03/21] Make error handling consistent with general Authsignal SDK conventions --- README.md | 15 +++++++++++++++ lib/Authsignal/AuthsignalClient.php | 30 ++++++++++++++++------------- lib/Authsignal/Errors.php | 23 +++++++++++++++------- 3 files changed, 48 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index b7c4684..c3a9b31 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,21 @@ Authsignal's server side signal API has five main calls `track`, `getAction`, `g For more details on these api calls, refer to our [official PHP SDK docs](https://docs.authsignal.com/sdks/server/php#trackaction). +### Response & Error handling + +Example: + +```php +$result = Authsignal::updateAction( + userId: $userId, + action: $action, + idempotencyKey: "invalidKey", + attributes: ['state' => 'CHALLENGE_FAILED'] +); + +# PHP Fatal error: Uncaught AuthsignalNotFoundError: 404 - not_found +``` + ## License The library is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). diff --git a/lib/Authsignal/AuthsignalClient.php b/lib/Authsignal/AuthsignalClient.php index 4c8f866..f59276f 100644 --- a/lib/Authsignal/AuthsignalClient.php +++ b/lib/Authsignal/AuthsignalClient.php @@ -13,35 +13,39 @@ public static function apiUrl($path='') return $apiEndpoint.$path; } - public function handleApiError($response, $status) + public function handleApiError($response, $statusCode) { - $type = $response['error'] ?? null; - $msg = $response['errorDescription'] ?? null; - switch ($status) { + $errorCode = $response['errorCode'] ?? null; + $errorDescription = $response['errorDescription'] ?? null; + switch ($statusCode) { case 400: - throw new AuthsignalBadRequest($msg, $type, $status); + throw new AuthsignalBadRequest($statusCode, $errorCode, $errorDescription); case 401: - throw new AuthsignalUnauthorizedError($msg, $type, $status); + throw new AuthsignalUnauthorizedError($statusCode, $errorCode, $errorDescription); case 403: - throw new AuthsignalForbiddenError($msg, $type, $status); + throw new AuthsignalForbiddenError($statusCode, $errorCode, $errorDescription); case 404: - throw new AuthsignalNotFoundError($msg, $type, $status); + throw new AuthsignalNotFoundError($statusCode, $errorCode, $errorDescription); case 422: // Handle subtype errors - switch($type) { + switch($errorCode) { case 'invalid_request_token': - throw new AuthsignalInvalidRequestTokenError($msg, $type, $status); + throw new AuthsignalInvalidRequestTokenError($statusCode, $errorCode, $errorDescription); default: - throw new AuthsignalInvalidParametersError($msg, $type, $status); + throw new AuthsignalInvalidParametersError($statusCode, $errorCode, $errorDescription); } default: - throw new AuthsignalApiError($msg, $type, $status); + throw new AuthsignalApiError($statusCode, $errorCode, $errorDescription); } } public function handleRequestError($request) { - throw new AuthsignalRequestError("$request->rError: $request->rMessage"); + $statusCode = $request->rStatus; // HTTP statusCode code + $errorCode = $request->rError; // Error code + $errorDescription = $request->rMessage; // Error message + + throw new AuthsignalRequestError($statusCode, $errorCode, $errorDescription); } public function handleResponse($request) diff --git a/lib/Authsignal/Errors.php b/lib/Authsignal/Errors.php index 2867d92..466ac3f 100644 --- a/lib/Authsignal/Errors.php +++ b/lib/Authsignal/Errors.php @@ -2,7 +2,21 @@ class AuthsignalError extends Exception { - + public function __construct($statusCode, $errorCode, $errorDescription = null, $previous = null) + { + $message = $this->formatMessage($statusCode, $errorCode, $errorDescription); + parent::__construct($message, $statusCode, $previous); + } + + private function formatMessage($statusCode, $errorCode, $errorDescription = null) + { + return "$statusCode - " . $this->formatDescription($errorCode, $errorDescription); + } + + private function formatDescription($errorCode, $errorDescription = null) + { + return $errorDescription && strlen($errorDescription) > 0 ? $errorDescription : $errorCode; + } } class AuthsignalRequestError extends AuthsignalError @@ -22,12 +36,7 @@ class AuthsignalCurlOptionError extends AuthsignalError class AuthsignalApiError extends AuthsignalError { - public function __construct($msg, $type = null, $status = null) - { - parent::__construct($msg); - $this->type = $type; - $this->httpStatus = $status; - } + } class AuthsignalBadRequest extends AuthsignalApiError From 6e21f59c44678d0a79b154619e870db87a714419 Mon Sep 17 00:00:00 2001 From: Steven Clouston Date: Fri, 29 Nov 2024 15:50:02 +1300 Subject: [PATCH 04/21] Update track to use named params within array --- lib/Authsignal/Authsignal.php | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/Authsignal/Authsignal.php b/lib/Authsignal/Authsignal.php index 55ff4a4..2bc3d3b 100644 --- a/lib/Authsignal/Authsignal.php +++ b/lib/Authsignal/Authsignal.php @@ -65,17 +65,20 @@ public static function setApiVersion($apiVersion) /** * Track an action - * @param string $userId The userId of the user you are tracking the action for - * @param string $action The action code that you are tracking - * @param Array $payload An array of attributes to track. - * @return Array The authsignal response + * + * @param array $params An associative array of parameters: + * - string 'userId': The userId of the user you are tracking the action for + * - string 'action': The action code that you are tracking + * - array 'attributes': An array of attributes to track + * @return array The authsignal response */ - public static function track(string $userId, string $action, Array $payload) + public static function track(array $params) { $request = new AuthsignalClient(); - $userId = urlencode($userId); - $action = urlencode($action); - list($response, $request) = $request->send("/users/{$userId}/actions/{$action}", $payload, 'post'); + $userId = urlencode($params['userId']); + $action = urlencode($params['action']); + $attributes = $params['attributes']; + list($response, $request) = $request->send("/users/{$userId}/actions/{$action}", $attributes, 'post'); return $response; } From 55141bbeeee06e9e4430b7910b12861efdf1d26c Mon Sep 17 00:00:00 2001 From: Steven Clouston Date: Fri, 29 Nov 2024 16:07:40 +1300 Subject: [PATCH 05/21] Use single associative array parameter instead of positional params --- lib/Authsignal/Authsignal.php | 97 +++++++++++++++++++---------------- 1 file changed, 52 insertions(+), 45 deletions(-) diff --git a/lib/Authsignal/Authsignal.php b/lib/Authsignal/Authsignal.php index 2bc3d3b..363f845 100644 --- a/lib/Authsignal/Authsignal.php +++ b/lib/Authsignal/Authsignal.php @@ -85,16 +85,18 @@ public static function track(array $params) /** * Get an action - * @param string $userId The userId of the user you are tracking the action for - * @param string $action The action code that you are tracking - * @param string $idempotencyKey The action code that you are tracking + * @param array $params An associative array of parameters: + * - string 'userId': The userId of the user you are tracking the action for + * - string 'action': The action code that you are tracking + * - string 'idempotencyKey': The idempotency key for the action * @return Array The authsignal response */ - public static function getAction(string $userId, string $action, string $idempotencyKey) + public static function getAction(array $params) { $request = new AuthsignalClient(); - $userId = urlencode($userId); - $action = urlencode($action); + $userId = urlencode($params['userId']); + $action = urlencode($params['action']); + $idempotencyKey = urlencode($params['idempotencyKey']); list($response, $request) = $request->send("/users/{$userId}/actions/{$action}/{$idempotencyKey}", array(), 'get'); return $response; @@ -102,27 +104,28 @@ public static function getAction(string $userId, string $action, string $idempot /** * Get a user - * @param string $userId The userId of the user you are tracking the action for - * @param string $redirectUrl The redirectUrl if using the redirect flow (optional) + * @param array $params An associative array of parameters: + * - string 'userId': The userId of the user you are tracking the action for + * - string|null 'redirectUrl': The redirectUrl if using the redirect flow (optional) * @return Array The authsignal response */ - public static function getUser(string $userId, string $redirectUrl = null) + public static function getUser(array $params) { $request = new AuthsignalClient(); - $userId = urlencode($userId); + $userId = urlencode($params['userId']); + $redirectUrl = isset($params['redirectUrl']) ? urlencode($params['redirectUrl']) : null; - $redirectUrl = empty($redirectUrl) ? null : urlencode($redirectUrl); - $path = empty($redirectUrl) ? "/users/{$userId}" : "/users/{$userId}?redirectUrl={$redirectUrl}"; list($response, $request) = $request->send($path, null, 'get'); return $response; } - public static function updateUser(string $userId, array $data) + public static function updateUser(array $params) { $request = new AuthsignalClient(); - $userId = urlencode($userId); + $userId = urlencode($params['userId']); + $data = $params['data']; $path = "/users/{$userId}"; list($response, $request) = $request->send($path, $data, 'post'); return $response; @@ -131,14 +134,16 @@ public static function updateUser(string $userId, array $data) /** * Enroll Authenticators - * @param string $userId The userId of the user you are tracking the action for - * @param Array $authenticator The authenticator object + * @param array $params An associative array of parameters: + * - string 'userId': The userId of the user you are tracking the action for + * - array 'authenticator': The authenticator object * @return Array The authsignal response */ - public static function enrollVerifiedAuthenticator(string $userId, Array $authenticator) + public static function enrollVerifiedAuthenticator(array $params) { $request = new AuthsignalClient(); - $userId = urlencode($userId); + $userId = urlencode($params['userId']); + $authenticator = $params['authenticator']; list($response, $request) = $request->send("/users/{$userId}/authenticators", $authenticator, 'post'); return $response; @@ -146,13 +151,14 @@ public static function enrollVerifiedAuthenticator(string $userId, Array $authen /** * Delete a user - * @param string $userId The userId of the user you want to delete + * @param array $params An associative array of parameters: + * - string 'userId': The userId of the user you want to delete * @return Array The authsignal response */ - public static function deleteUser(string $userId) + public static function deleteUser(array $params) { $request = new AuthsignalClient(); - $userId = urlencode($userId); + $userId = urlencode($params['userId']); $path = "/users/{$userId}"; list($response, $request) = $request->send($path, null, 'delete'); return $response; @@ -160,21 +166,22 @@ public static function deleteUser(string $userId) /** * Delete a user authenticator - * @param string $userId The userId of the user - * @param string $userAuthenticatorId The userAuthenticatorId of the authenticator + * @param array $params An associative array of parameters: + * - string 'userId': The userId of the user + * - string 'userAuthenticatorId': The userAuthenticatorId of the authenticator * @return Array The authsignal response - */ - public static function deleteAuthenticator(string $userId, string $userAuthenticatorId) { - if (empty($userId)) { + */ + public static function deleteAuthenticator(array $params) { + if (empty($params['userId'])) { throw new InvalidArgumentException('user_id cannot be empty'); } - if (empty($userAuthenticatorId)) { + if (empty($params['userAuthenticatorId'])) { throw new InvalidArgumentException('user_authenticator_id cannot be empty'); } - $userId = urlencode($userId); - $userAuthenticatorId = urlencode($userAuthenticatorId); + $userId = urlencode($params['userId']); + $userAuthenticatorId = urlencode($params['userAuthenticatorId']); $path = "/users/{$userId}/authenticators/{$userAuthenticatorId}"; $request = new AuthsignalClient(); @@ -189,20 +196,20 @@ public static function deleteAuthenticator(string $userId, string $userAuthentic /** * Validate Challenge - * Validates the token returned on a challenge response, this is a critical security measure - * also performs a back-end call to validate the state - * @param string|null $userId The userId of the user you are tracking the action for - * @param string $token The JWT token string returned on a challenge response + * @param array $params An associative array of parameters: + * - string 'token': The JWT token string returned on a challenge response + * - string|null 'userId': The userId of the user you are tracking the action for (optional) + * - string|null 'action': The action code that you are tracking (optional) * @return Array The authsignal response */ - public static function validateChallenge(string $token, ?string $userId = null, ?string $action = null) + public static function validateChallenge(array $params) { $request = new AuthsignalClient(); $payload = [ - 'userId' => $userId, - 'action' => $action, - 'token' => $token + 'userId' => $params['userId'] ?? null, + 'action' => $params['action'] ?? null, + 'token' => $params['token'] ]; list($response, $request) = $request->send("/validate", $payload, 'post'); @@ -216,19 +223,19 @@ public static function validateChallenge(string $token, ?string $userId = null, /** * Update Action - * Updates the state of an action for a user - * @param string $userId The userId of the user to update the action for - * @param string $action The action code to update - * @param string $idempotencyKey The idempotency key for the action - * @param array $attributes Additional attributes for the action + * @param array $params An associative array of parameters: + * - string 'userId': The userId of the user to update the action for + * - string 'action': The action code to update + * - string 'idempotencyKey': The idempotency key for the action + * - array 'attributes': Additional attributes for the action * @return array The Authsignal response */ - public static function updateAction(string $userId, string $action, string $idempotencyKey, array $attributes) + public static function updateAction(array $params) { $request = new AuthsignalClient(); - $path = "/users/" . urlencode($userId) . "/actions/" . urlencode($action) . "/" . urlencode($idempotencyKey); + $path = "/users/" . urlencode($params['userId']) . "/actions/" . urlencode($params['action']) . "/" . urlencode($params['idempotencyKey']); - list($response, $request) = $request->send($path, $attributes, 'patch'); + list($response, $request) = $request->send($path, $params['attributes'], 'patch'); return $response; } From c38469ea761b42a6adbaf7895ab489a232fd95c3 Mon Sep 17 00:00:00 2001 From: Steven Clouston Date: Fri, 29 Nov 2024 16:12:27 +1300 Subject: [PATCH 06/21] Remove redirectUrl from getUser function --- lib/Authsignal/Authsignal.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/Authsignal/Authsignal.php b/lib/Authsignal/Authsignal.php index 363f845..3d9361e 100644 --- a/lib/Authsignal/Authsignal.php +++ b/lib/Authsignal/Authsignal.php @@ -106,16 +106,14 @@ public static function getAction(array $params) * Get a user * @param array $params An associative array of parameters: * - string 'userId': The userId of the user you are tracking the action for - * - string|null 'redirectUrl': The redirectUrl if using the redirect flow (optional) * @return Array The authsignal response */ public static function getUser(array $params) { $request = new AuthsignalClient(); $userId = urlencode($params['userId']); - $redirectUrl = isset($params['redirectUrl']) ? urlencode($params['redirectUrl']) : null; - $path = empty($redirectUrl) ? "/users/{$userId}" : "/users/{$userId}?redirectUrl={$redirectUrl}"; + $path = "/users/{$userId}"; list($response, $request) = $request->send($path, null, 'get'); return $response; From 630982dbcffc3c9a8ddc2c114812ee3186c76611 Mon Sep 17 00:00:00 2001 From: Steven Clouston Date: Fri, 29 Nov 2024 16:28:10 +1300 Subject: [PATCH 07/21] Replace data with attributes --- lib/Authsignal/Authsignal.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Authsignal/Authsignal.php b/lib/Authsignal/Authsignal.php index 3d9361e..9651253 100644 --- a/lib/Authsignal/Authsignal.php +++ b/lib/Authsignal/Authsignal.php @@ -123,9 +123,9 @@ public static function updateUser(array $params) { $request = new AuthsignalClient(); $userId = urlencode($params['userId']); - $data = $params['data']; + $attributes = $params['attributes']; $path = "/users/{$userId}"; - list($response, $request) = $request->send($path, $data, 'post'); + list($response, $request) = $request->send($path, $attributes, 'post'); return $response; } From 180666029fb782cd412bae163280fabe301b3b1b Mon Sep 17 00:00:00 2001 From: Steven Clouston Date: Fri, 29 Nov 2024 16:38:16 +1300 Subject: [PATCH 08/21] Add getAuthenticators method --- lib/Authsignal/Authsignal.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lib/Authsignal/Authsignal.php b/lib/Authsignal/Authsignal.php index 9651253..7945823 100644 --- a/lib/Authsignal/Authsignal.php +++ b/lib/Authsignal/Authsignal.php @@ -237,4 +237,21 @@ public static function updateAction(array $params) return $response; } + /** + * Get Authenticators + * @param array $params An associative array of parameters: + * - string 'userId': The userId of the user whose authenticators you want to retrieve + * @return array The list of user authenticators + * @throws AuthsignalApiException if the request fails + */ + public static function getAuthenticators(array $params) + { + $request = new AuthsignalClient(); + $userId = urlencode($params['userId']); + $path = "/users/{$userId}/authenticators"; + + list($response, $request) = $request->send($path, null, 'get'); + return $response; + } + } From 7a0fc5d9b3e466bc5f437a00f3cb64817ee55e9e Mon Sep 17 00:00:00 2001 From: Steven Clouston Date: Fri, 29 Nov 2024 16:47:07 +1300 Subject: [PATCH 09/21] Fix tests --- test/AuthsignalTest.php | 150 +++++++++++++++++++++++++++++----------- 1 file changed, 109 insertions(+), 41 deletions(-) diff --git a/test/AuthsignalTest.php b/test/AuthsignalTest.php index 1f19871..969f289 100644 --- a/test/AuthsignalTest.php +++ b/test/AuthsignalTest.php @@ -1,31 +1,28 @@ - start(); + self::$server->start(); Authsignal::setApiHostname(self::$server->getServerRoot()); } static function tearDownAfterClass(): void { - self::$server->stop(); - } + self::$server->stop(); + } - public function testSetApiKey() - { + public function testSetApiKey() { $this->assertEquals('secret', Authsignal::getApiKey()); } @@ -37,27 +34,30 @@ public function testTrackAction() { self::$server->setResponseOfPath('/v1/users/123%3Atest/actions/signIn', new Response(json_encode($mockedResponse))); - $payload = array( - "redirectUrl" => "https://www.yourapp.com/back_to_your_app", - "email" => "test@email", - "deviceId" => "123", - "userAgent" => "Mozilla/5.0 (platform; rv:geckoversion) Gecko/geckotrail Firefox/firefoxversion", - "ipAddress" => "1.1.1.1", - "custom" => array( - "yourCustomBoolean" => true, - "yourCustomString" => true, - "yourCustomNumber" => 1.12 - )); - - $response = Authsignal::track(userId: "123:test", - action: "signIn", - payload: $payload); + $params = array( + "userId" => "123:test", + "action" => "signIn", + "attributes" => array( + "redirectUrl" => "https://www.yourapp.com/back_to_your_app", + "email" => "test@email", + "deviceId" => "123", + "userAgent" => "Mozilla/5.0 (platform; rv:geckoversion) Gecko/geckotrail Firefox/firefoxversion", + "ipAddress" => "1.1.1.1", + "custom" => array( + "yourCustomBoolean" => true, + "yourCustomString" => true, + "yourCustomNumber" => 1.12 + ) + ) + ); + + $response = Authsignal::track($params); $this->assertEquals($response["state"], "ALLOW"); $this->assertEquals($response["idempotencyKey"], $mockedResponse["idempotencyKey"]); } - public function testgetAction() { + public function testGetAction() { // Mock response $mockedResponse = array("state" => "ALLOW", "idempotencyKey" => "5924a649-b5d3-4baf-a4ab-4b812dde97a0", @@ -67,22 +67,32 @@ public function testgetAction() { self::$server->setResponseOfPath("/v1/users/123%3Atest/actions/signIn/5924a649-b5d3-4baf-a4ab-4b812dde97a04", new Response(json_encode($mockedResponse))); - $response = Authsignal::getAction(userId: "123:test", - action: "signIn", - idempotencyKey: "5924a649-b5d3-4baf-a4ab-4b812dde97a04"); + $params = array( + "userId" => "123:test", + "action" => "signIn", + "idempotencyKey" => "5924a649-b5d3-4baf-a4ab-4b812dde97a04" + ); + + $response = Authsignal::getAction($params); $this->assertEquals($response["state"], "ALLOW"); $this->assertEquals($response["idempotencyKey"], $mockedResponse["idempotencyKey"]); $this->assertEquals($response["stateUpdatedAt"], $mockedResponse["stateUpdatedAt"]); } - public function testgetUser() { + public function testGetUser() { $mockedResponse = array("isEnrolled" => false, "accessToken" => "xxxx", "url" => "wwwww"); self::$server->setResponseOfPath("/v1/users/123%3Atest", new Response(json_encode($mockedResponse))); - $response = Authsignal::getUser(userId: "123:test", redirectUrl: "https://www.example.com/"); + + $params = array( + "userId" => "123:test", + "redirectUrl" => "https://www.example.com/" + ); + + $response = Authsignal::getUser($params); $this->assertEquals($response["isEnrolled"], $mockedResponse["isEnrolled"]); $this->assertEquals($response["url"], $mockedResponse["url"]); @@ -100,9 +110,15 @@ public function testEnrollVerifiedAuthenticator() { self::$server->setResponseOfPath("/v1/users/123%3Atest/authenticators", new Response(json_encode($mockedResponse))); - $response = Authsignal::enrollVerifiedAuthenticator(userId: "123:test", - authenticator: array("oobChannel" => "SMS" - ,"phoneNumber" => "+6427000000")); + $params = array( + "userId" => "123:test", + "authenticator" => array( + "oobChannel" => "SMS", + "phoneNumber" => "+6427000000" + ) + ); + + $response = Authsignal::enrollVerifiedAuthenticator($params); $this->assertEquals($response["authenticator"]["userAuthenticatorId"], $mockedResponse["authenticator"]["userAuthenticatorId"]); } @@ -133,7 +149,12 @@ public function testValidateChallenge() { ]; $token = JWT::encode($testTokenPayload, $key, 'HS256'); - $response = Authsignal::validateChallenge(userId: "123:test", token: $token); + $params = array( + "userId" => "123:test", + "token" => $token + ); + + $response = Authsignal::validateChallenge($params); $this->assertEquals($response['isValid'], "true"); } @@ -147,7 +168,7 @@ public function testValidateChallengeOptionalUserId() { "action" => "signIn", "verificationMethod" => "AUTHENTICATOR_APP"); - self::$server->setResponseOfPath("/v1/validate", new Response(json_encode($mockedResponse))); + self::$server->setResponseOfPath("/v1/validate", new Response(json_encode($mockedResponse))); $key = "secret"; $testTokenPayload = [ @@ -163,7 +184,11 @@ public function testValidateChallengeOptionalUserId() { ]; $token = JWT::encode($testTokenPayload, $key, 'HS256'); - $response = Authsignal::validateChallenge(token: $token); + $params = array( + "token" => $token + ); + + $response = Authsignal::validateChallenge($params); $this->assertEquals($response["isValid"], "true"); } @@ -190,7 +215,12 @@ public function testValidateChallengeInvalidAction() { ]; $token = JWT::encode($testTokenPayload, $key, 'HS256'); - $response = Authsignal::validateChallenge(token: $token, action: "malicious_action"); + $params = array( + "token" => $token, + "action" => "malicious_action" + ); + + $response = Authsignal::validateChallenge($params); $this->assertEquals($response["isValid"], false); $this->assertEquals($response["error"], "Action is invalid."); @@ -201,7 +231,8 @@ public function testDeleteUser() { self::$server->setResponseOfPath("/v1/users/1234", new Response(json_encode($mockedResponse), [], 200)); - $response = Authsignal::deleteUser("1234"); + $params = array("userId" => "1234"); + $response = Authsignal::deleteUser($params); $this->assertEquals($response["success"], true); } @@ -211,7 +242,11 @@ public function testDeleteAuthenticator() { self::$server->setResponseOfPath("/v1/users/123%3Atest/authenticators/456%3Atest", new Response(json_encode($mockedResponse), [], 200)); - $response = Authsignal::deleteAuthenticator("123:test", "456:test"); + $params = array( + "userId" => "123:test", + "userAuthenticatorId" => "456:test" + ); + $response = Authsignal::deleteAuthenticator($params); $this->assertArrayHasKey("success", $response); $this->assertEquals($response["success"], true); @@ -225,13 +260,46 @@ public function testUpdateUser() { self::$server->setResponseOfPath("/v1/users/550e8400-e29b-41d4-a716-446655440000", new Response(json_encode($mockedResponse))); - $data = array( - "email" => "updated_email", + $params = array( + "userId" => "550e8400-e29b-41d4-a716-446655440000", + "attributes" => array( + "email" => "updated_email", + ) ); - $response = Authsignal::updateUser("550e8400-e29b-41d4-a716-446655440000", $data); + $response = Authsignal::updateUser($params); $this->assertEquals($response["userId"], $mockedResponse["userId"]); $this->assertEquals($response["email"], $mockedResponse["email"]); } + + public function testGetAuthenticators() { + $mockedResponse = array( + array( + "userAuthenticatorId" => "authenticator_id_1", + "authenticatorType" => "SMS", + "isDefault" => true, + "phoneNumber" => "+6427000000" + ), + array( + "userAuthenticatorId" => "authenticator_id_2", + "authenticatorType" => "EMAIL", + "isDefault" => false, + "email" => "user@example.com" + ) + ); + + self::$server->setResponseOfPath("/v1/users/123%3Atest/authenticators", new Response(json_encode($mockedResponse))); + + $params = array( + "userId" => "123:test" + ); + + $response = Authsignal::getAuthenticators($params); + + $this->assertIsArray($response); + $this->assertCount(2, $response); + $this->assertEquals($response[0]["userAuthenticatorId"], $mockedResponse[0]["userAuthenticatorId"]); + $this->assertEquals($response[1]["userAuthenticatorId"], $mockedResponse[1]["userAuthenticatorId"]); + } } \ No newline at end of file From 73b37e1aa61a1184970d1a37f71cb5da3c22fa16 Mon Sep 17 00:00:00 2001 From: Steven Clouston Date: Fri, 29 Nov 2024 17:09:34 +1300 Subject: [PATCH 10/21] Add updateAction test --- test/AuthsignalTest.php | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/AuthsignalTest.php b/test/AuthsignalTest.php index 969f289..5645703 100644 --- a/test/AuthsignalTest.php +++ b/test/AuthsignalTest.php @@ -302,4 +302,29 @@ public function testGetAuthenticators() { $this->assertEquals($response[0]["userAuthenticatorId"], $mockedResponse[0]["userAuthenticatorId"]); $this->assertEquals($response[1]["userAuthenticatorId"], $mockedResponse[1]["userAuthenticatorId"]); } + + public function testUpdateAction() { + $mockedResponse = array( + "userId" => "123:test", + "action" => "signIn", + "idempotencyKey" => "5924a649-b5d3-4baf-a4ab-4b812dde97a0", + "state" => "CHALLENGE_FAILED" + ); + + self::$server->setResponseOfPath("/v1/users/123%3Atest/actions/signIn/5924a649-b5d3-4baf-a4ab-4b812dde97a0", new Response(json_encode($mockedResponse))); + + $params = array( + "userId" => "123:test", + "action" => "signIn", + "idempotencyKey" => "5924a649-b5d3-4baf-a4ab-4b812dde97a0", + "attributes" => array("state" => "CHALLENGE_FAILED") + ); + + $response = Authsignal::updateAction($params); + + $this->assertEquals($response["userId"], $mockedResponse["userId"]); + $this->assertEquals($response["action"], $mockedResponse["action"]); + $this->assertEquals($response["idempotencyKey"], $mockedResponse["idempotencyKey"]); + $this->assertEquals($response["state"], $mockedResponse["state"]); + } } \ No newline at end of file From 32b154b8ee2be71679b4cdca18bf7aa1c6ef4e7d Mon Sep 17 00:00:00 2001 From: Steven Clouston Date: Mon, 2 Dec 2024 09:40:19 +1300 Subject: [PATCH 11/21] Add PHPDoc comment to Update User --- lib/Authsignal/Authsignal.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/Authsignal/Authsignal.php b/lib/Authsignal/Authsignal.php index 7945823..2df4b47 100644 --- a/lib/Authsignal/Authsignal.php +++ b/lib/Authsignal/Authsignal.php @@ -119,6 +119,13 @@ public static function getUser(array $params) return $response; } + /** + * Update User + * @param array $params An associative array of parameters: + * - string 'userId': The userId of the user to update + * - array 'attributes': The attributes to update for the user + * @return array The authsignal response + */ public static function updateUser(array $params) { $request = new AuthsignalClient(); From eef447b354088b53ab3c40c6808aecb78b35c248 Mon Sep 17 00:00:00 2001 From: Steven Clouston Date: Mon, 2 Dec 2024 09:49:57 +1300 Subject: [PATCH 12/21] Change order of functions to match Node SDK --- lib/Authsignal/Authsignal.php | 142 +++++++++++++++++----------------- 1 file changed, 71 insertions(+), 71 deletions(-) diff --git a/lib/Authsignal/Authsignal.php b/lib/Authsignal/Authsignal.php index 2df4b47..5402b85 100644 --- a/lib/Authsignal/Authsignal.php +++ b/lib/Authsignal/Authsignal.php @@ -63,45 +63,6 @@ public static function setApiVersion($apiVersion) self::$apiVersion = $apiVersion; } - /** - * Track an action - * - * @param array $params An associative array of parameters: - * - string 'userId': The userId of the user you are tracking the action for - * - string 'action': The action code that you are tracking - * - array 'attributes': An array of attributes to track - * @return array The authsignal response - */ - public static function track(array $params) - { - $request = new AuthsignalClient(); - $userId = urlencode($params['userId']); - $action = urlencode($params['action']); - $attributes = $params['attributes']; - list($response, $request) = $request->send("/users/{$userId}/actions/{$action}", $attributes, 'post'); - - return $response; - } - - /** - * Get an action - * @param array $params An associative array of parameters: - * - string 'userId': The userId of the user you are tracking the action for - * - string 'action': The action code that you are tracking - * - string 'idempotencyKey': The idempotency key for the action - * @return Array The authsignal response - */ - public static function getAction(array $params) - { - $request = new AuthsignalClient(); - $userId = urlencode($params['userId']); - $action = urlencode($params['action']); - $idempotencyKey = urlencode($params['idempotencyKey']); - list($response, $request) = $request->send("/users/{$userId}/actions/{$action}/{$idempotencyKey}", array(), 'get'); - - return $response; - } - /** * Get a user * @param array $params An associative array of parameters: @@ -135,42 +96,60 @@ public static function updateUser(array $params) list($response, $request) = $request->send($path, $attributes, 'post'); return $response; } - /** - * Enroll Authenticators + * Delete a user * @param array $params An associative array of parameters: - * - string 'userId': The userId of the user you are tracking the action for - * - array 'authenticator': The authenticator object + * - string 'userId': The userId of the user you want to delete * @return Array The authsignal response */ - public static function enrollVerifiedAuthenticator(array $params) + public static function deleteUser(array $params) { $request = new AuthsignalClient(); $userId = urlencode($params['userId']); - $authenticator = $params['authenticator']; - list($response, $request) = $request->send("/users/{$userId}/authenticators", $authenticator, 'post'); - + $path = "/users/{$userId}"; + list($response, $request) = $request->send($path, null, 'delete'); return $response; } + /** - * Delete a user + * Get Authenticators * @param array $params An associative array of parameters: - * - string 'userId': The userId of the user you want to delete + * - string 'userId': The userId of the user whose authenticators you want to retrieve + * @return array The list of user authenticators + * @throws AuthsignalApiException if the request fails + */ + public static function getAuthenticators(array $params) + { + $request = new AuthsignalClient(); + $userId = urlencode($params['userId']); + $path = "/users/{$userId}/authenticators"; + + list($response, $request) = $request->send($path, null, 'get'); + return $response; + } + + + /** + * Enroll Authenticators + * @param array $params An associative array of parameters: + * - string 'userId': The userId of the user you are tracking the action for + * - array 'authenticator': The authenticator object * @return Array The authsignal response */ - public static function deleteUser(array $params) + public static function enrollVerifiedAuthenticator(array $params) { $request = new AuthsignalClient(); $userId = urlencode($params['userId']); - $path = "/users/{$userId}"; - list($response, $request) = $request->send($path, null, 'delete'); + $authenticator = $params['authenticator']; + list($response, $request) = $request->send("/users/{$userId}/authenticators", $authenticator, 'post'); + return $response; } /** - * Delete a user authenticator + * Delete an authenticator * @param array $params An associative array of parameters: * - string 'userId': The userId of the user * - string 'userAuthenticatorId': The userAuthenticatorId of the authenticator @@ -199,6 +178,26 @@ public static function deleteAuthenticator(array $params) { } } + /** + * Track an action + * + * @param array $params An associative array of parameters: + * - string 'userId': The userId of the user you are tracking the action for + * - string 'action': The action code that you are tracking + * - array 'attributes': An array of attributes to track + * @return array The authsignal response + */ + public static function track(array $params) + { + $request = new AuthsignalClient(); + $userId = urlencode($params['userId']); + $action = urlencode($params['action']); + $attributes = $params['attributes']; + list($response, $request) = $request->send("/users/{$userId}/actions/{$action}", $attributes, 'post'); + + return $response; + } + /** * Validate Challenge * @param array $params An associative array of parameters: @@ -226,6 +225,25 @@ public static function validateChallenge(array $params) return $response; } + /** + * Get an action + * @param array $params An associative array of parameters: + * - string 'userId': The userId of the user you are tracking the action for + * - string 'action': The action code that you are tracking + * - string 'idempotencyKey': The idempotency key for the action + * @return Array The authsignal response + */ + public static function getAction(array $params) + { + $request = new AuthsignalClient(); + $userId = urlencode($params['userId']); + $action = urlencode($params['action']); + $idempotencyKey = urlencode($params['idempotencyKey']); + list($response, $request) = $request->send("/users/{$userId}/actions/{$action}/{$idempotencyKey}", array(), 'get'); + + return $response; + } + /** * Update Action * @param array $params An associative array of parameters: @@ -243,22 +261,4 @@ public static function updateAction(array $params) list($response, $request) = $request->send($path, $params['attributes'], 'patch'); return $response; } - - /** - * Get Authenticators - * @param array $params An associative array of parameters: - * - string 'userId': The userId of the user whose authenticators you want to retrieve - * @return array The list of user authenticators - * @throws AuthsignalApiException if the request fails - */ - public static function getAuthenticators(array $params) - { - $request = new AuthsignalClient(); - $userId = urlencode($params['userId']); - $path = "/users/{$userId}/authenticators"; - - list($response, $request) = $request->send($path, null, 'get'); - return $response; - } - } From 1ef8ff579751e6199df136d9190b30e5ad55bf31 Mon Sep 17 00:00:00 2001 From: Steven Clouston Date: Mon, 2 Dec 2024 09:52:32 +1300 Subject: [PATCH 13/21] Change updateUser to use patch instead of post --- lib/Authsignal/Authsignal.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Authsignal/Authsignal.php b/lib/Authsignal/Authsignal.php index 5402b85..c6a64e1 100644 --- a/lib/Authsignal/Authsignal.php +++ b/lib/Authsignal/Authsignal.php @@ -93,7 +93,7 @@ public static function updateUser(array $params) $userId = urlencode($params['userId']); $attributes = $params['attributes']; $path = "/users/{$userId}"; - list($response, $request) = $request->send($path, $attributes, 'post'); + list($response, $request) = $request->send($path, $attributes, 'patch'); return $response; } From 495fb9b8b49ef724a3029d7eb52fe57af60e57dc Mon Sep 17 00:00:00 2001 From: Steven Clouston Date: Mon, 2 Dec 2024 10:01:21 +1300 Subject: [PATCH 14/21] Reorder tests to match order of SDK functions --- test/AuthsignalTest.php | 303 +++++++++++++++------------------------- 1 file changed, 116 insertions(+), 187 deletions(-) diff --git a/test/AuthsignalTest.php b/test/AuthsignalTest.php index 5645703..4ac2521 100644 --- a/test/AuthsignalTest.php +++ b/test/AuthsignalTest.php @@ -22,81 +22,85 @@ static function tearDownAfterClass(): void { self::$server->stop(); } - public function testSetApiKey() { - $this->assertEquals('secret', Authsignal::getApiKey()); - } - - public function testTrackAction() { - // Mock response - $mockedResponse = array("state" => "ALLOW", - "idempotencyKey" => "5924a649-b5d3-4baf-a4ab-4b812dde97a0", - "ruleIds" => []); + public function testGetUser() { + $mockedResponse = array("isEnrolled" => false, + "accessToken" => "xxxx", + "url" => "wwwww"); - self::$server->setResponseOfPath('/v1/users/123%3Atest/actions/signIn', new Response(json_encode($mockedResponse))); + self::$server->setResponseOfPath("/v1/users/123%3Atest", new Response(json_encode($mockedResponse))); $params = array( "userId" => "123:test", - "action" => "signIn", - "attributes" => array( - "redirectUrl" => "https://www.yourapp.com/back_to_your_app", - "email" => "test@email", - "deviceId" => "123", - "userAgent" => "Mozilla/5.0 (platform; rv:geckoversion) Gecko/geckotrail Firefox/firefoxversion", - "ipAddress" => "1.1.1.1", - "custom" => array( - "yourCustomBoolean" => true, - "yourCustomString" => true, - "yourCustomNumber" => 1.12 - ) - ) + "redirectUrl" => "https://www.example.com/" ); - $response = Authsignal::track($params); - - $this->assertEquals($response["state"], "ALLOW"); - $this->assertEquals($response["idempotencyKey"], $mockedResponse["idempotencyKey"]); + $response = Authsignal::getUser($params); + + $this->assertEquals($response["isEnrolled"], $mockedResponse["isEnrolled"]); + $this->assertEquals($response["url"], $mockedResponse["url"]); } - public function testGetAction() { - // Mock response - $mockedResponse = array("state" => "ALLOW", - "idempotencyKey" => "5924a649-b5d3-4baf-a4ab-4b812dde97a0", - "stateUpdatedAt" => "2022-07-25T03:19:00.316Z", - "createdAt" => "2022-07-25T03:19:00.316Z", - "ruleIds" => []); - - self::$server->setResponseOfPath("/v1/users/123%3Atest/actions/signIn/5924a649-b5d3-4baf-a4ab-4b812dde97a04", new Response(json_encode($mockedResponse))); - + public function testUpdateUser() { + $mockedResponse = array( + "userId" => "550e8400-e29b-41d4-a716-446655440000", + "email" => "updated_email", + ); + + self::$server->setResponseOfPath("/v1/users/550e8400-e29b-41d4-a716-446655440000", new Response(json_encode($mockedResponse))); + $params = array( - "userId" => "123:test", - "action" => "signIn", - "idempotencyKey" => "5924a649-b5d3-4baf-a4ab-4b812dde97a04" + "userId" => "550e8400-e29b-41d4-a716-446655440000", + "attributes" => array( + "email" => "updated_email", + ) ); + + $response = Authsignal::updateUser($params); + + $this->assertEquals($response["userId"], $mockedResponse["userId"]); + $this->assertEquals($response["email"], $mockedResponse["email"]); + } - $response = Authsignal::getAction($params); - - $this->assertEquals($response["state"], "ALLOW"); - $this->assertEquals($response["idempotencyKey"], $mockedResponse["idempotencyKey"]); - $this->assertEquals($response["stateUpdatedAt"], $mockedResponse["stateUpdatedAt"]); + public function testDeleteUser() { + $mockedResponse = array("success" => true); + + self::$server->setResponseOfPath("/v1/users/1234", new Response(json_encode($mockedResponse), [], 200)); + + $params = array("userId" => "1234"); + $response = Authsignal::deleteUser($params); + + $this->assertEquals($response["success"], true); } - public function testGetUser() { - $mockedResponse = array("isEnrolled" => false, - "accessToken" => "xxxx", - "url" => "wwwww"); + public function testGetAuthenticators() { + $mockedResponse = array( + array( + "userAuthenticatorId" => "authenticator_id_1", + "authenticatorType" => "SMS", + "isDefault" => true, + "phoneNumber" => "+6427000000" + ), + array( + "userAuthenticatorId" => "authenticator_id_2", + "authenticatorType" => "EMAIL", + "isDefault" => false, + "email" => "user@example.com" + ) + ); - self::$server->setResponseOfPath("/v1/users/123%3Atest", new Response(json_encode($mockedResponse))); + self::$server->setResponseOfPath("/v1/users/123%3Atest/authenticators", new Response(json_encode($mockedResponse))); $params = array( - "userId" => "123:test", - "redirectUrl" => "https://www.example.com/" + "userId" => "123:test" ); - $response = Authsignal::getUser($params); - - $this->assertEquals($response["isEnrolled"], $mockedResponse["isEnrolled"]); - $this->assertEquals($response["url"], $mockedResponse["url"]); - } + $response = Authsignal::getAuthenticators($params); + + $this->assertIsArray($response); + $this->assertCount(2, $response); + $this->assertEquals($response[0]["userAuthenticatorId"], $mockedResponse[0]["userAuthenticatorId"]); + $this->assertEquals($response[1]["userAuthenticatorId"], $mockedResponse[1]["userAuthenticatorId"]); + } public function testEnrollVerifiedAuthenticator() { $mockedResponse = array( @@ -123,47 +127,57 @@ public function testEnrollVerifiedAuthenticator() { $this->assertEquals($response["authenticator"]["userAuthenticatorId"], $mockedResponse["authenticator"]["userAuthenticatorId"]); } - public function testValidateChallenge() { - $mockedResponse = array("state" => "CHALLENGE_SUCCEEDED", - "idempotencyKey" => "5924a649-b5d3-4baf-a4ab-4b812dde97a0", - "stateUpdatedAt" => "2022-07-25T03:19:00.316Z", - "userId" => "123:test", - "isValid" => "true", - "action" => "signIn", - "verificationMethod" => "AUTHENTICATOR_APP"); + public function testDeleteAuthenticator() { + $mockedResponse = array("success" => true); + + self::$server->setResponseOfPath("/v1/users/123%3Atest/authenticators/456%3Atest", new Response(json_encode($mockedResponse), [], 200)); + + $params = array( + "userId" => "123:test", + "userAuthenticatorId" => "456:test" + ); + $response = Authsignal::deleteAuthenticator($params); + + $this->assertArrayHasKey("success", $response); + $this->assertEquals($response["success"], true); + } - self::$server->setResponseOfPath("/v1/validate", new Response(json_encode($mockedResponse))); + public function testTrackAction() { + // Mock response + $mockedResponse = array("state" => "ALLOW", + "idempotencyKey" => "5924a649-b5d3-4baf-a4ab-4b812dde97a0", + "ruleIds" => []); - $key = "secret"; - $testTokenPayload = [ - 'iss' => 'http://example.org', - 'aud' => 'http://example.com', - 'iat' => 1356999524, - 'nbf' => 1357000000, - 'other' => [ - 'userId' => "123:test", - 'state' => "CHALLENGE_SUCCEEDED", - 'action' => 'signIn', - 'idempotencyKey' => "5924a649-b5d3-4baf-a4ab-4b812dde97a0", - ] - ]; - $token = JWT::encode($testTokenPayload, $key, 'HS256'); + self::$server->setResponseOfPath('/v1/users/123%3Atest/actions/signIn', new Response(json_encode($mockedResponse))); $params = array( "userId" => "123:test", - "token" => $token + "action" => "signIn", + "attributes" => array( + "redirectUrl" => "https://www.yourapp.com/back_to_your_app", + "email" => "test@email", + "deviceId" => "123", + "userAgent" => "Mozilla/5.0 (platform; rv:geckoversion) Gecko/geckotrail Firefox/firefoxversion", + "ipAddress" => "1.1.1.1", + "custom" => array( + "yourCustomBoolean" => true, + "yourCustomString" => true, + "yourCustomNumber" => 1.12 + ) + ) ); - $response = Authsignal::validateChallenge($params); + $response = Authsignal::track($params); - $this->assertEquals($response['isValid'], "true"); + $this->assertEquals($response["state"], "ALLOW"); + $this->assertEquals($response["idempotencyKey"], $mockedResponse["idempotencyKey"]); } - public function testValidateChallengeOptionalUserId() { + public function testValidateChallenge() { $mockedResponse = array("state" => "CHALLENGE_SUCCEEDED", "idempotencyKey" => "5924a649-b5d3-4baf-a4ab-4b812dde97a0", "stateUpdatedAt" => "2022-07-25T03:19:00.316Z", - "userId" => null, + "userId" => "123:test", "isValid" => "true", "action" => "signIn", "verificationMethod" => "AUTHENTICATOR_APP"); @@ -177,6 +191,7 @@ public function testValidateChallengeOptionalUserId() { 'iat' => 1356999524, 'nbf' => 1357000000, 'other' => [ + 'userId' => "123:test", 'state' => "CHALLENGE_SUCCEEDED", 'action' => 'signIn', 'idempotencyKey' => "5924a649-b5d3-4baf-a4ab-4b812dde97a0", @@ -185,122 +200,36 @@ public function testValidateChallengeOptionalUserId() { $token = JWT::encode($testTokenPayload, $key, 'HS256'); $params = array( + "userId" => "123:test", "token" => $token ); $response = Authsignal::validateChallenge($params); - $this->assertEquals($response["isValid"], "true"); + $this->assertEquals($response['isValid'], "true"); } - public function testValidateChallengeInvalidAction() { - $mockedResponse = array( - "isValid" => false, - "error" => "Action is invalid." - ); - - self::$server->setResponseOfPath("/v1/validate", new Response(json_encode($mockedResponse))); - - $key = "secret"; - $testTokenPayload = [ - 'iss' => 'http://example.org', - 'aud' => 'http://example.com', - 'iat' => 1356999524, - 'nbf' => 1357000000, - 'other' => [ - 'state' => "CHALLENGE_SUCCEEDED", - 'action' => 'signIn', - 'idempotencyKey' => "5924a649-b5d3-4baf-a4ab-4b812dde97a0", - ] - ]; - $token = JWT::encode($testTokenPayload, $key, 'HS256'); - - $params = array( - "token" => $token, - "action" => "malicious_action" - ); - - $response = Authsignal::validateChallenge($params); - - $this->assertEquals($response["isValid"], false); - $this->assertEquals($response["error"], "Action is invalid."); - } + public function testGetAction() { + // Mock response + $mockedResponse = array("state" => "ALLOW", + "idempotencyKey" => "5924a649-b5d3-4baf-a4ab-4b812dde97a0", + "stateUpdatedAt" => "2022-07-25T03:19:00.316Z", + "createdAt" => "2022-07-25T03:19:00.316Z", + "ruleIds" => []); - public function testDeleteUser() { - $mockedResponse = array("success" => true); - - self::$server->setResponseOfPath("/v1/users/1234", new Response(json_encode($mockedResponse), [], 200)); - - $params = array("userId" => "1234"); - $response = Authsignal::deleteUser($params); - - $this->assertEquals($response["success"], true); - } + self::$server->setResponseOfPath("/v1/users/123%3Atest/actions/signIn/5924a649-b5d3-4baf-a4ab-4b812dde97a04", new Response(json_encode($mockedResponse))); - public function testDeleteAuthenticator() { - $mockedResponse = array("success" => true); - - self::$server->setResponseOfPath("/v1/users/123%3Atest/authenticators/456%3Atest", new Response(json_encode($mockedResponse), [], 200)); - $params = array( "userId" => "123:test", - "userAuthenticatorId" => "456:test" - ); - $response = Authsignal::deleteAuthenticator($params); - - $this->assertArrayHasKey("success", $response); - $this->assertEquals($response["success"], true); - } - - public function testUpdateUser() { - $mockedResponse = array( - "userId" => "550e8400-e29b-41d4-a716-446655440000", - "email" => "updated_email", - ); - - self::$server->setResponseOfPath("/v1/users/550e8400-e29b-41d4-a716-446655440000", new Response(json_encode($mockedResponse))); - - $params = array( - "userId" => "550e8400-e29b-41d4-a716-446655440000", - "attributes" => array( - "email" => "updated_email", - ) - ); - - $response = Authsignal::updateUser($params); - - $this->assertEquals($response["userId"], $mockedResponse["userId"]); - $this->assertEquals($response["email"], $mockedResponse["email"]); - } - - public function testGetAuthenticators() { - $mockedResponse = array( - array( - "userAuthenticatorId" => "authenticator_id_1", - "authenticatorType" => "SMS", - "isDefault" => true, - "phoneNumber" => "+6427000000" - ), - array( - "userAuthenticatorId" => "authenticator_id_2", - "authenticatorType" => "EMAIL", - "isDefault" => false, - "email" => "user@example.com" - ) - ); - - self::$server->setResponseOfPath("/v1/users/123%3Atest/authenticators", new Response(json_encode($mockedResponse))); - - $params = array( - "userId" => "123:test" + "action" => "signIn", + "idempotencyKey" => "5924a649-b5d3-4baf-a4ab-4b812dde97a04" ); - $response = Authsignal::getAuthenticators($params); + $response = Authsignal::getAction($params); - $this->assertIsArray($response); - $this->assertCount(2, $response); - $this->assertEquals($response[0]["userAuthenticatorId"], $mockedResponse[0]["userAuthenticatorId"]); - $this->assertEquals($response[1]["userAuthenticatorId"], $mockedResponse[1]["userAuthenticatorId"]); + $this->assertEquals($response["state"], "ALLOW"); + $this->assertEquals($response["idempotencyKey"], $mockedResponse["idempotencyKey"]); + $this->assertEquals($response["stateUpdatedAt"], $mockedResponse["stateUpdatedAt"]); } public function testUpdateAction() { From 044908513c81714f763b4fc5f53069db1b04b9d6 Mon Sep 17 00:00:00 2001 From: Steven Clouston Date: Fri, 6 Dec 2024 13:55:27 +1300 Subject: [PATCH 15/21] Make attributes optional in track request --- lib/Authsignal/Authsignal.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/Authsignal/Authsignal.php b/lib/Authsignal/Authsignal.php index c6a64e1..7bfd7a9 100644 --- a/lib/Authsignal/Authsignal.php +++ b/lib/Authsignal/Authsignal.php @@ -184,7 +184,7 @@ public static function deleteAuthenticator(array $params) { * @param array $params An associative array of parameters: * - string 'userId': The userId of the user you are tracking the action for * - string 'action': The action code that you are tracking - * - array 'attributes': An array of attributes to track + * - array 'attributes': An array of attributes to track (optional) * @return array The authsignal response */ public static function track(array $params) @@ -192,8 +192,11 @@ public static function track(array $params) $request = new AuthsignalClient(); $userId = urlencode($params['userId']); $action = urlencode($params['action']); - $attributes = $params['attributes']; - list($response, $request) = $request->send("/users/{$userId}/actions/{$action}", $attributes, 'post'); + $attributes = isset($params['attributes']) ? $params['attributes'] : []; + + $requestBody = ['attributes' => $attributes]; + + list($response, $request) = $request->send("/users/{$userId}/actions/{$action}", $requestBody, 'post'); return $response; } From bd8645b95908df82c15bc046e9bb964f42e4fcf4 Mon Sep 17 00:00:00 2001 From: Steven Clouston Date: Fri, 6 Dec 2024 14:50:20 +1300 Subject: [PATCH 16/21] Replace authenticator with attributes param --- lib/Authsignal/Authsignal.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Authsignal/Authsignal.php b/lib/Authsignal/Authsignal.php index 7bfd7a9..038e882 100644 --- a/lib/Authsignal/Authsignal.php +++ b/lib/Authsignal/Authsignal.php @@ -135,15 +135,15 @@ public static function getAuthenticators(array $params) * Enroll Authenticators * @param array $params An associative array of parameters: * - string 'userId': The userId of the user you are tracking the action for - * - array 'authenticator': The authenticator object + * - array 'attributes': The authenticator object * @return Array The authsignal response */ public static function enrollVerifiedAuthenticator(array $params) { $request = new AuthsignalClient(); $userId = urlencode($params['userId']); - $authenticator = $params['authenticator']; - list($response, $request) = $request->send("/users/{$userId}/authenticators", $authenticator, 'post'); + $attributes = $params['attributes']; + list($response, $request) = $request->send("/users/{$userId}/authenticators", $attributes, 'post'); return $response; } From 68a9d90d970781cc17d4a5c3d569d10f38799752 Mon Sep 17 00:00:00 2001 From: Steven Clouston Date: Fri, 6 Dec 2024 16:33:36 +1300 Subject: [PATCH 17/21] Update AuthsignalTest.php --- test/AuthsignalTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/AuthsignalTest.php b/test/AuthsignalTest.php index 4ac2521..0b32e1f 100644 --- a/test/AuthsignalTest.php +++ b/test/AuthsignalTest.php @@ -116,7 +116,7 @@ public function testEnrollVerifiedAuthenticator() { $params = array( "userId" => "123:test", - "authenticator" => array( + "attributes" => array( "oobChannel" => "SMS", "phoneNumber" => "+6427000000" ) From f2bc012d7ac4f3c7e816983411b6d2b607398c07 Mon Sep 17 00:00:00 2001 From: Steven Clouston Date: Mon, 9 Dec 2024 09:46:32 +1300 Subject: [PATCH 18/21] Replace apiKey with apiSecretKey --- README.md | 2 +- lib/Authsignal/Authsignal.php | 10 +++++----- lib/Authsignal/AuthsignalClient.php | 2 +- lib/Authsignal/AuthsignalRequestTransport.php | 2 +- test/AuthsignalTest.php | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index c3a9b31..aefdee7 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Check out our [official PHP SDK documentation](https://docs.authsignal.com/sdks/ Initialize the Authsignal SDK, ensuring you do not hard code the Authsignal Secret Key, always keep this safe. ```php -Authsignal::setApiKey('secretKey'); +Authsignal::setApiSecretKey('secretKey'); ``` You can find your `secretKey` in the [Authsignal Portal](https://portal.authsignal.com/organisations/tenants/api). diff --git a/lib/Authsignal/Authsignal.php b/lib/Authsignal/Authsignal.php index 038e882..fefb4e1 100644 --- a/lib/Authsignal/Authsignal.php +++ b/lib/Authsignal/Authsignal.php @@ -7,7 +7,7 @@ abstract class Authsignal { const VERSION = '3.0.1'; - public static $apiKey; + public static $apiSecretKey; public static $apiHostname = 'https://signal.authsignal.com'; @@ -19,14 +19,14 @@ abstract class Authsignal CURLOPT_TIMEOUT, CURLOPT_TIMEOUT_MS); - public static function getApiKey() + public static function getApiSecretKey() { - return self::$apiKey; + return self::$apiSecretKey; } - public static function setApiKey($apiKey) + public static function setApiSecretKey($apiSecretKey) { - self::$apiKey = $apiKey; + self::$apiSecretKey = $apiSecretKey; } public static function setApiHostname($hostname) diff --git a/lib/Authsignal/AuthsignalClient.php b/lib/Authsignal/AuthsignalClient.php index f59276f..95c3a41 100644 --- a/lib/Authsignal/AuthsignalClient.php +++ b/lib/Authsignal/AuthsignalClient.php @@ -68,7 +68,7 @@ public function handleResponse($request) public function preCheck() { - $key = Authsignal::getApiKey(); + $key = Authsignal::getApiSecretKey(); if (empty($key)) { throw new AuthsignalConfigurationError(); } diff --git a/lib/Authsignal/AuthsignalRequestTransport.php b/lib/Authsignal/AuthsignalRequestTransport.php index b2a125f..5c84341 100644 --- a/lib/Authsignal/AuthsignalRequestTransport.php +++ b/lib/Authsignal/AuthsignalRequestTransport.php @@ -69,7 +69,7 @@ public function send($method, $url, $payload) { // Set our default options. $curlOptions[CURLOPT_URL] = $url; - $curlOptions[CURLOPT_USERPWD] = Authsignal::getApiKey() . ":"; + $curlOptions[CURLOPT_USERPWD] = Authsignal::getApiSecretKey() . ":"; $curlOptions[CURLOPT_RETURNTRANSFER] = true; $curlOptions[CURLOPT_CONNECTTIMEOUT] = 3; $curlOptions[CURLOPT_TIMEOUT] = 10; diff --git a/test/AuthsignalTest.php b/test/AuthsignalTest.php index 0b32e1f..2093f2c 100644 --- a/test/AuthsignalTest.php +++ b/test/AuthsignalTest.php @@ -11,7 +11,7 @@ class AuthsignalTest extends PHPUnit\Framework\TestCase { protected static $server; public static function setUpBeforeClass(): void { - Authsignal::setApiKey('secret'); + Authsignal::setApiSecretKey('secret'); self::$server = new MockWebServer; self::$server->start(); From e56fa2e8d04b7a3159c9407617cd2a964f53c16b Mon Sep 17 00:00:00 2001 From: Steven Clouston Date: Mon, 9 Dec 2024 09:56:48 +1300 Subject: [PATCH 19/21] Rename setApiHostname to setApiUrl --- README.md | 4 ++-- lib/Authsignal/Authsignal.php | 6 +++--- lib/Authsignal/AuthsignalClient.php | 2 +- test/AuthsignalTest.php | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index aefdee7..231347a 100644 --- a/README.md +++ b/README.md @@ -33,12 +33,12 @@ Authsignal has multiple api hosting regions. To view your hostname for your tena | AU (Sydney) | https://au.signal.authsignal.com/v1 | | EU (Dublin) | https://eu.signal.authsignal.com/v1 | -You can set the hostname via the following code. If the `setApiHostname` function is not called, the api call defaults to the main Authsignal US region hostname `https://signal.authsignal.com` +You can set the hostname via the following code. If the `setApiUrl` function is not called, the api call defaults to the main Authsignal US region hostname `https://signal.authsignal.com` An example setting the client to use the AU region. ```php -Authsignal::setApiHostname("https://au.signal.authsignal.com"); +Authsignal::setApiUrl("https://au.signal.authsignal.com"); ``` Alternatively, an environment variable can be used to set the API URL: diff --git a/lib/Authsignal/Authsignal.php b/lib/Authsignal/Authsignal.php index fefb4e1..4dd67c3 100644 --- a/lib/Authsignal/Authsignal.php +++ b/lib/Authsignal/Authsignal.php @@ -9,7 +9,7 @@ abstract class Authsignal public static $apiSecretKey; - public static $apiHostname = 'https://signal.authsignal.com'; + public static $apiUrl = 'https://signal.authsignal.com'; public static $apiVersion = 'v1'; @@ -29,9 +29,9 @@ public static function setApiSecretKey($apiSecretKey) self::$apiSecretKey = $apiSecretKey; } - public static function setApiHostname($hostname) + public static function setApiUrl($apiUrl) { - self::$apiHostname = $hostname; + self::$apiUrl = $apiUrl; } public static function setCurlOpts($curlOpts) diff --git a/lib/Authsignal/AuthsignalClient.php b/lib/Authsignal/AuthsignalClient.php index 95c3a41..3559161 100644 --- a/lib/Authsignal/AuthsignalClient.php +++ b/lib/Authsignal/AuthsignalClient.php @@ -6,7 +6,7 @@ public static function apiUrl($path='') { $apiEndpoint = getenv('AUTHSIGNAL_SERVER_API_ENDPOINT'); if ( !$apiEndpoint ) { - $apiURL = Authsignal::$apiHostname; + $apiURL = Authsignal::$apiUrl; $apiVersion = Authsignal::getApiVersion(); $apiEndpoint = $apiURL.'/'.$apiVersion; } diff --git a/test/AuthsignalTest.php b/test/AuthsignalTest.php index 2093f2c..859e9ca 100644 --- a/test/AuthsignalTest.php +++ b/test/AuthsignalTest.php @@ -15,7 +15,7 @@ public static function setUpBeforeClass(): void { self::$server = new MockWebServer; self::$server->start(); - Authsignal::setApiHostname(self::$server->getServerRoot()); + Authsignal::setApiUrl(self::$server->getServerRoot()); } static function tearDownAfterClass(): void { From e412484da7ca9f712f8c4b04314f13266b155bdc Mon Sep 17 00:00:00 2001 From: Steven Clouston Date: Mon, 9 Dec 2024 10:44:16 +1300 Subject: [PATCH 20/21] Remove `setApiVersion` and `getApiVersion`. Rename env var AUTHSIGNAL_SERVER_API_ENDPOINT to AUTHSIGNAL_API_URL --- README.md | 4 ++-- lib/Authsignal/Authsignal.php | 12 ------------ lib/Authsignal/AuthsignalClient.php | 11 +++-------- 3 files changed, 5 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 231347a..5bc8e76 100644 --- a/README.md +++ b/README.md @@ -38,13 +38,13 @@ You can set the hostname via the following code. If the `setApiUrl` function is An example setting the client to use the AU region. ```php -Authsignal::setApiUrl("https://au.signal.authsignal.com"); +Authsignal::setApiUrl("https://au.signal.authsignal.com/v1"); ``` Alternatively, an environment variable can be used to set the API URL: ```bash -AUTHSIGNAL_SERVER_API_ENDPOINT=https://au.signal.authsignal.com/v1 +AUTHSIGNAL_API_URL=https://au.signal.authsignal.com/v1 ``` ## Usage diff --git a/lib/Authsignal/Authsignal.php b/lib/Authsignal/Authsignal.php index 4dd67c3..087739e 100644 --- a/lib/Authsignal/Authsignal.php +++ b/lib/Authsignal/Authsignal.php @@ -11,8 +11,6 @@ abstract class Authsignal public static $apiUrl = 'https://signal.authsignal.com'; - public static $apiVersion = 'v1'; - private static $curlOpts = array(); private static $validCurlOpts = array(CURLOPT_CONNECTTIMEOUT, CURLOPT_CONNECTTIMEOUT_MS, @@ -53,16 +51,6 @@ public static function getCurlOpts() return self::$curlOpts; } - public static function getApiVersion() - { - return self::$apiVersion; - } - - public static function setApiVersion($apiVersion) - { - self::$apiVersion = $apiVersion; - } - /** * Get a user * @param array $params An associative array of parameters: diff --git a/lib/Authsignal/AuthsignalClient.php b/lib/Authsignal/AuthsignalClient.php index 3559161..be1363c 100644 --- a/lib/Authsignal/AuthsignalClient.php +++ b/lib/Authsignal/AuthsignalClient.php @@ -2,15 +2,10 @@ class AuthsignalClient { - public static function apiUrl($path='') + public static function apiUrl($path = '') { - $apiEndpoint = getenv('AUTHSIGNAL_SERVER_API_ENDPOINT'); - if ( !$apiEndpoint ) { - $apiURL = Authsignal::$apiUrl; - $apiVersion = Authsignal::getApiVersion(); - $apiEndpoint = $apiURL.'/'.$apiVersion; - } - return $apiEndpoint.$path; + $apiEndpoint = getenv('AUTHSIGNAL_API_URL') ?: Authsignal::$apiUrl; + return $apiEndpoint . $path; } public function handleApiError($response, $statusCode) From 406b6378aa45e1795a976a82634f6cb474daadbe Mon Sep 17 00:00:00 2001 From: Steven Clouston Date: Mon, 9 Dec 2024 10:47:04 +1300 Subject: [PATCH 21/21] Fix tests --- test/AuthsignalTest.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/test/AuthsignalTest.php b/test/AuthsignalTest.php index 859e9ca..11af91e 100644 --- a/test/AuthsignalTest.php +++ b/test/AuthsignalTest.php @@ -27,7 +27,7 @@ public function testGetUser() { "accessToken" => "xxxx", "url" => "wwwww"); - self::$server->setResponseOfPath("/v1/users/123%3Atest", new Response(json_encode($mockedResponse))); + self::$server->setResponseOfPath("/users/123%3Atest", new Response(json_encode($mockedResponse))); $params = array( "userId" => "123:test", @@ -46,7 +46,7 @@ public function testUpdateUser() { "email" => "updated_email", ); - self::$server->setResponseOfPath("/v1/users/550e8400-e29b-41d4-a716-446655440000", new Response(json_encode($mockedResponse))); + self::$server->setResponseOfPath("/users/550e8400-e29b-41d4-a716-446655440000", new Response(json_encode($mockedResponse))); $params = array( "userId" => "550e8400-e29b-41d4-a716-446655440000", @@ -64,7 +64,7 @@ public function testUpdateUser() { public function testDeleteUser() { $mockedResponse = array("success" => true); - self::$server->setResponseOfPath("/v1/users/1234", new Response(json_encode($mockedResponse), [], 200)); + self::$server->setResponseOfPath("/users/1234", new Response(json_encode($mockedResponse), [], 200)); $params = array("userId" => "1234"); $response = Authsignal::deleteUser($params); @@ -88,7 +88,7 @@ public function testGetAuthenticators() { ) ); - self::$server->setResponseOfPath("/v1/users/123%3Atest/authenticators", new Response(json_encode($mockedResponse))); + self::$server->setResponseOfPath("/users/123%3Atest/authenticators", new Response(json_encode($mockedResponse))); $params = array( "userId" => "123:test" @@ -112,7 +112,7 @@ public function testEnrollVerifiedAuthenticator() { "createdAt"=> "2022-07-25T03:31:36.219Z", "oobChannel"=> "SMS")); - self::$server->setResponseOfPath("/v1/users/123%3Atest/authenticators", new Response(json_encode($mockedResponse))); + self::$server->setResponseOfPath("/users/123%3Atest/authenticators", new Response(json_encode($mockedResponse))); $params = array( "userId" => "123:test", @@ -130,7 +130,7 @@ public function testEnrollVerifiedAuthenticator() { public function testDeleteAuthenticator() { $mockedResponse = array("success" => true); - self::$server->setResponseOfPath("/v1/users/123%3Atest/authenticators/456%3Atest", new Response(json_encode($mockedResponse), [], 200)); + self::$server->setResponseOfPath("/users/123%3Atest/authenticators/456%3Atest", new Response(json_encode($mockedResponse), [], 200)); $params = array( "userId" => "123:test", @@ -148,7 +148,7 @@ public function testTrackAction() { "idempotencyKey" => "5924a649-b5d3-4baf-a4ab-4b812dde97a0", "ruleIds" => []); - self::$server->setResponseOfPath('/v1/users/123%3Atest/actions/signIn', new Response(json_encode($mockedResponse))); + self::$server->setResponseOfPath('/users/123%3Atest/actions/signIn', new Response(json_encode($mockedResponse))); $params = array( "userId" => "123:test", @@ -182,7 +182,7 @@ public function testValidateChallenge() { "action" => "signIn", "verificationMethod" => "AUTHENTICATOR_APP"); - self::$server->setResponseOfPath("/v1/validate", new Response(json_encode($mockedResponse))); + self::$server->setResponseOfPath("/validate", new Response(json_encode($mockedResponse))); $key = "secret"; $testTokenPayload = [ @@ -217,7 +217,7 @@ public function testGetAction() { "createdAt" => "2022-07-25T03:19:00.316Z", "ruleIds" => []); - self::$server->setResponseOfPath("/v1/users/123%3Atest/actions/signIn/5924a649-b5d3-4baf-a4ab-4b812dde97a04", new Response(json_encode($mockedResponse))); + self::$server->setResponseOfPath("/users/123%3Atest/actions/signIn/5924a649-b5d3-4baf-a4ab-4b812dde97a04", new Response(json_encode($mockedResponse))); $params = array( "userId" => "123:test", @@ -240,7 +240,7 @@ public function testUpdateAction() { "state" => "CHALLENGE_FAILED" ); - self::$server->setResponseOfPath("/v1/users/123%3Atest/actions/signIn/5924a649-b5d3-4baf-a4ab-4b812dde97a0", new Response(json_encode($mockedResponse))); + self::$server->setResponseOfPath("/users/123%3Atest/actions/signIn/5924a649-b5d3-4baf-a4ab-4b812dde97a0", new Response(json_encode($mockedResponse))); $params = array( "userId" => "123:test",