diff --git a/README.md b/README.md index ae40d02..5bc8e76 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). @@ -33,18 +33,18 @@ 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/v1"); ``` -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 +AUTHSIGNAL_API_URL=https://au.signal.authsignal.com/v1 ``` ## Usage @@ -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/Authsignal.php b/lib/Authsignal/Authsignal.php index 11854dd..087739e 100644 --- a/lib/Authsignal/Authsignal.php +++ b/lib/Authsignal/Authsignal.php @@ -7,11 +7,9 @@ abstract class Authsignal { const VERSION = '3.0.1'; - public static $apiKey; + public static $apiSecretKey; - public static $apiHostname = 'https://signal.authsignal.com'; - - public static $apiVersion = 'v1'; + public static $apiUrl = 'https://signal.authsignal.com'; private static $curlOpts = array(); private static $validCurlOpts = array(CURLOPT_CONNECTTIMEOUT, @@ -19,19 +17,19 @@ 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) + public static function setApiUrl($apiUrl) { - self::$apiHostname = $hostname; + self::$apiUrl = $apiUrl; } public static function setCurlOpts($curlOpts) @@ -53,125 +51,109 @@ public static function getCurlOpts() return self::$curlOpts; } - public static function getApiVersion() - { - return self::$apiVersion; - } - - public static function setApiVersion($apiVersion) - { - self::$apiVersion = $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. + * Get a user + * @param array $params An associative array of parameters: + * - string 'userId': The userId of the user you are tracking the action for * @return Array The authsignal response */ - public static function track(string $userId, string $action, Array $payload) + public static function getUser(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']); + + $path = "/users/{$userId}"; + list($response, $request) = $request->send($path, null, 'get'); + return $response; } /** - * 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 - * @return Array The authsignal 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 getAction(string $userId, string $action, string $idempotencyKey) + public static function updateUser(array $params) { - $request = new AuthsignalClient(); - $userId = urlencode($userId); - $action = urlencode($action); - list($response, $request) = $request->send("/users/{$userId}/actions/{$action}/{$idempotencyKey}", array(), 'get'); - - return $response; + $request = new AuthsignalClient(); + $userId = urlencode($params['userId']); + $attributes = $params['attributes']; + $path = "/users/{$userId}"; + list($response, $request) = $request->send($path, $attributes, 'patch'); + return $response; } /** - * 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) + * Delete a user + * @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 getUser(string $userId, string $redirectUrl = null) + public static function deleteUser(array $params) { $request = new AuthsignalClient(); - $userId = urlencode($userId); - - $redirectUrl = empty($redirectUrl) ? null : urlencode($redirectUrl); - - $path = empty($redirectUrl) ? "/users/{$userId}" : "/users/{$userId}?redirectUrl={$redirectUrl}"; - list($response, $request) = $request->send($path, null, 'get'); - + $userId = urlencode($params['userId']); + $path = "/users/{$userId}"; + list($response, $request) = $request->send($path, null, 'delete'); return $response; } - public static function updateUser(string $userId, array $data) - { - $request = new AuthsignalClient(); - $userId = urlencode($userId); - $path = "/users/{$userId}"; - list($response, $request) = $request->send($path, $data, 'post'); - return $response; - } - /** - * Enroll Authenticators - * @param string $userId The userId of the user you are tracking the action for - * @param Array $authenticator The authenticator object - * @return Array The authsignal 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 enrollVerifiedAuthenticator(string $userId, Array $authenticator) + public static function getAuthenticators(array $params) { $request = new AuthsignalClient(); - $userId = urlencode($userId); - list($response, $request) = $request->send("/users/{$userId}/authenticators", $authenticator, 'post'); - - return $response; + $userId = urlencode($params['userId']); + $path = "/users/{$userId}/authenticators"; + + list($response, $request) = $request->send($path, null, 'get'); + return $response; } - /** - * Delete a user - * @param string $userId The userId of the user you want to delete + + /** + * Enroll Authenticators + * @param array $params An associative array of parameters: + * - string 'userId': The userId of the user you are tracking the action for + * - array 'attributes': The authenticator object * @return Array The authsignal response */ - public static function deleteUser(string $userId) + public static function enrollVerifiedAuthenticator(array $params) { $request = new AuthsignalClient(); - $userId = urlencode($userId); - $path = "/users/{$userId}"; - list($response, $request) = $request->send($path, null, 'delete'); + $userId = urlencode($params['userId']); + $attributes = $params['attributes']; + list($response, $request) = $request->send("/users/{$userId}/authenticators", $attributes, 'post'); + return $response; } /** - * Delete a user authenticator - * @param string $userId The userId of the user - * @param string $userAuthenticatorId The userAuthenticatorId of the 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 * @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(); @@ -184,22 +166,45 @@ public static function deleteAuthenticator(string $userId, string $userAuthentic } } + /** + * 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 (optional) + * @return array The authsignal response + */ + public static function track(array $params) + { + $request = new AuthsignalClient(); + $userId = urlencode($params['userId']); + $action = urlencode($params['action']); + $attributes = isset($params['attributes']) ? $params['attributes'] : []; + + $requestBody = ['attributes' => $attributes]; + + list($response, $request) = $request->send("/users/{$userId}/actions/{$action}", $requestBody, 'post'); + + return $response; + } + /** * 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'); @@ -210,4 +215,41 @@ public static function validateChallenge(string $token, ?string $userId = null, 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: + * - 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(array $params) + { + $request = new AuthsignalClient(); + $path = "/users/" . urlencode($params['userId']) . "/actions/" . urlencode($params['action']) . "/" . urlencode($params['idempotencyKey']); + + list($response, $request) = $request->send($path, $params['attributes'], 'patch'); + return $response; + } } diff --git a/lib/Authsignal/AuthsignalClient.php b/lib/Authsignal/AuthsignalClient.php index ed9dbd8..be1363c 100644 --- a/lib/Authsignal/AuthsignalClient.php +++ b/lib/Authsignal/AuthsignalClient.php @@ -2,46 +2,45 @@ class AuthsignalClient { - public static function apiUrl($path='') + public static function apiUrl($path = '') { - $apiEndpoint = getenv('AUTHSIGNAL_SERVER_API_ENDPOINT'); - if ( !$apiEndpoint ) { - $apiBase = Authsignal::$apiHostname; - $apiVersion = Authsignal::getApiVersion(); - $apiEndpoint = $apiBase.'/'.$apiVersion; - } - return $apiEndpoint.$path; + $apiEndpoint = getenv('AUTHSIGNAL_API_URL') ?: Authsignal::$apiUrl; + 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) @@ -64,7 +63,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 c68e508..5c84341 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; @@ -66,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/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 diff --git a/test/AuthsignalTest.php b/test/AuthsignalTest.php index 1f19871..11af91e 100644 --- a/test/AuthsignalTest.php +++ b/test/AuthsignalTest.php @@ -1,92 +1,106 @@ - start(); + self::$server->start(); - Authsignal::setApiHostname(self::$server->getServerRoot()); + Authsignal::setApiUrl(self::$server->getServerRoot()); } static function tearDownAfterClass(): void { - self::$server->stop(); - } - - public function testSetApiKey() - { - $this->assertEquals('secret', Authsignal::getApiKey()); + self::$server->stop(); } - 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("/users/123%3Atest", 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 - )); + $params = array( + "userId" => "123:test", + "redirectUrl" => "https://www.example.com/" + ); - $response = Authsignal::track(userId: "123:test", - action: "signIn", - payload: $payload); + $response = Authsignal::getUser($params); + + $this->assertEquals($response["isEnrolled"], $mockedResponse["isEnrolled"]); + $this->assertEquals($response["url"], $mockedResponse["url"]); + } - $this->assertEquals($response["state"], "ALLOW"); - $this->assertEquals($response["idempotencyKey"], $mockedResponse["idempotencyKey"]); + public function testUpdateUser() { + $mockedResponse = array( + "userId" => "550e8400-e29b-41d4-a716-446655440000", + "email" => "updated_email", + ); + + self::$server->setResponseOfPath("/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 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("/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 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" + ) + ); - $response = Authsignal::getAction(userId: "123:test", - action: "signIn", - idempotencyKey: "5924a649-b5d3-4baf-a4ab-4b812dde97a04"); + self::$server->setResponseOfPath("/users/123%3Atest/authenticators", new Response(json_encode($mockedResponse))); - $this->assertEquals($response["state"], "ALLOW"); - $this->assertEquals($response["idempotencyKey"], $mockedResponse["idempotencyKey"]); - $this->assertEquals($response["stateUpdatedAt"], $mockedResponse["stateUpdatedAt"]); - } + $params = array( + "userId" => "123:test" + ); - public function testgetUser() { - $mockedResponse = array("isEnrolled" => false, - "accessToken" => "xxxx", - "url" => "wwwww"); + $response = Authsignal::getAuthenticators($params); - self::$server->setResponseOfPath("/v1/users/123%3Atest", new Response(json_encode($mockedResponse))); - $response = Authsignal::getUser(userId: "123:test", redirectUrl: "https://www.example.com/"); - - $this->assertEquals($response["isEnrolled"], $mockedResponse["isEnrolled"]); - $this->assertEquals($response["url"], $mockedResponse["url"]); - } + $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( @@ -98,15 +112,67 @@ 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", + "attributes" => array( + "oobChannel" => "SMS", + "phoneNumber" => "+6427000000" + ) + ); - $response = Authsignal::enrollVerifiedAuthenticator(userId: "123:test", - authenticator: array("oobChannel" => "SMS" - ,"phoneNumber" => "+6427000000")); + $response = Authsignal::enrollVerifiedAuthenticator($params); $this->assertEquals($response["authenticator"]["userAuthenticatorId"], $mockedResponse["authenticator"]["userAuthenticatorId"]); } + public function testDeleteAuthenticator() { + $mockedResponse = array("success" => true); + + self::$server->setResponseOfPath("/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 testTrackAction() { + // Mock response + $mockedResponse = array("state" => "ALLOW", + "idempotencyKey" => "5924a649-b5d3-4baf-a4ab-4b812dde97a0", + "ruleIds" => []); + + self::$server->setResponseOfPath('/users/123%3Atest/actions/signIn', 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 + ) + ) + ); + + $response = Authsignal::track($params); + + $this->assertEquals($response["state"], "ALLOW"); + $this->assertEquals($response["idempotencyKey"], $mockedResponse["idempotencyKey"]); + } + public function testValidateChallenge() { $mockedResponse = array("state" => "CHALLENGE_SUCCEEDED", "idempotencyKey" => "5924a649-b5d3-4baf-a4ab-4b812dde97a0", @@ -116,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 = [ @@ -133,105 +199,61 @@ 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"); } - public function testValidateChallengeOptionalUserId() { - $mockedResponse = array("state" => "CHALLENGE_SUCCEEDED", + public function testGetAction() { + // Mock response + $mockedResponse = array("state" => "ALLOW", "idempotencyKey" => "5924a649-b5d3-4baf-a4ab-4b812dde97a0", "stateUpdatedAt" => "2022-07-25T03:19:00.316Z", - "userId" => null, - "isValid" => "true", - "action" => "signIn", - "verificationMethod" => "AUTHENTICATOR_APP"); + "createdAt" => "2022-07-25T03:19:00.316Z", + "ruleIds" => []); - self::$server->setResponseOfPath("/v1/validate", new Response(json_encode($mockedResponse))); + self::$server->setResponseOfPath("/users/123%3Atest/actions/signIn/5924a649-b5d3-4baf-a4ab-4b812dde97a04", 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( + "userId" => "123:test", + "action" => "signIn", + "idempotencyKey" => "5924a649-b5d3-4baf-a4ab-4b812dde97a04" + ); - $response = Authsignal::validateChallenge(token: $token); + $response = Authsignal::getAction($params); - $this->assertEquals($response["isValid"], "true"); + $this->assertEquals($response["state"], "ALLOW"); + $this->assertEquals($response["idempotencyKey"], $mockedResponse["idempotencyKey"]); + $this->assertEquals($response["stateUpdatedAt"], $mockedResponse["stateUpdatedAt"]); } - public function testValidateChallengeInvalidAction() { + public function testUpdateAction() { $mockedResponse = array( - "isValid" => false, - "error" => "Action is invalid." + "userId" => "123:test", + "action" => "signIn", + "idempotencyKey" => "5924a649-b5d3-4baf-a4ab-4b812dde97a0", + "state" => "CHALLENGE_FAILED" ); - - 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'); - - $response = Authsignal::validateChallenge(token: $token, action: "malicious_action"); - - $this->assertEquals($response["isValid"], false); - $this->assertEquals($response["error"], "Action is invalid."); - } - public function testDeleteUser() { - $mockedResponse = array("success" => true); - - self::$server->setResponseOfPath("/v1/users/1234", new Response(json_encode($mockedResponse), [], 200)); - - $response = Authsignal::deleteUser("1234"); - - $this->assertEquals($response["success"], true); - } + self::$server->setResponseOfPath("/users/123%3Atest/actions/signIn/5924a649-b5d3-4baf-a4ab-4b812dde97a0", 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)); - - $response = Authsignal::deleteAuthenticator("123:test", "456:test"); - - $this->assertArrayHasKey("success", $response); - $this->assertEquals($response["success"], true); - } - - public function testUpdateUser() { - $mockedResponse = array( - "userId" => "550e8400-e29b-41d4-a716-446655440000", - "email" => "updated_email", + $params = array( + "userId" => "123:test", + "action" => "signIn", + "idempotencyKey" => "5924a649-b5d3-4baf-a4ab-4b812dde97a0", + "attributes" => array("state" => "CHALLENGE_FAILED") ); - - self::$server->setResponseOfPath("/v1/users/550e8400-e29b-41d4-a716-446655440000", new Response(json_encode($mockedResponse))); - - $data = array( - "email" => "updated_email", - ); - - $response = Authsignal::updateUser("550e8400-e29b-41d4-a716-446655440000", $data); - + + $response = Authsignal::updateAction($params); + $this->assertEquals($response["userId"], $mockedResponse["userId"]); - $this->assertEquals($response["email"], $mockedResponse["email"]); + $this->assertEquals($response["action"], $mockedResponse["action"]); + $this->assertEquals($response["idempotencyKey"], $mockedResponse["idempotencyKey"]); + $this->assertEquals($response["state"], $mockedResponse["state"]); } } \ No newline at end of file