diff --git a/README.md b/README.md index eb07770..da3c5fa 100644 --- a/README.md +++ b/README.md @@ -44,18 +44,10 @@ Include in your code and begin using the library: https://github.com/authy/authy-php -http://forum.sa-mp.com/showthread.php?t=169354 - -http://forum.sa-mp.com/showthread.php?t=166016 ## Usage - +Check out the [example file 1](https://github.com/GiampaoloFalqui/TFA_SAMP/blob/master/tfasamp_example.pwn) and [example file 2](https://github.com/GiampaoloFalqui/TFA_SAMP/blob/master/tfasamp_example2.pwn). ## Testing diff --git a/pawn.json b/pawn.json index d4a885e..d5dae87 100644 --- a/pawn.json +++ b/pawn.json @@ -1,10 +1,11 @@ { "user": "GiampaoloFalqui", "repo": "TFA_SAMP", - "entry": "tfasamp.pwn", - "output": "tfasamp.amx", + "entry": "tfasamp_example2.pwn", + "output": "tfasamp_example2.amx", "dependencies": [ "sampctl/samp-stdlib", - "pawn-lang/YSI-Includes" + "pawn-lang/YSI-Includes", + "Hual/SAMPSON" ] } \ No newline at end of file diff --git a/tfasamp.pwn b/tfasamp.inc similarity index 64% rename from tfasamp.pwn rename to tfasamp.inc index f50b5fe..971e88a 100644 --- a/tfasamp.pwn +++ b/tfasamp.inc @@ -20,9 +20,14 @@ * - TFASAMP_verifyToken(playerid, user_id, token, bool: force = true) * - TFASAMP_setPlayerUserID(playerid, userid) * - TFASAMP_getPlayerUserID(playerid) + * - TFASAMP_getUUID(playerid) + * - TFASAMP_setUUID(playerid, TFA_UUID:uuid[]) + * - TFASAMP_getNotification(playerid, TFA_UUID:uuid[]) * * Available Callbacks: * - TFASAMP_OnTokenVerify(playerid, result) + * - TFASAMP_OnPushNotification(playerid, TFA_UUID:uuid[]) + * - TFASAMP_OnCheckNotification(playerid, status) */ /* @@ -33,9 +38,11 @@ #include #include #include +#include forward public TFASAMP_OnTokenVerify(playerid, result); - +forward public TFASAMP_OnPushNotification(playerid, TFA_UUID:uuid[]); +forward public TFASAMP_OnCheckNotification(playerid, status); /* * Versioning */ @@ -56,6 +63,11 @@ forward public TFASAMP_OnTokenVerify(playerid, result); #define TFASAMP_getConnectionType() TFASAMP_INTERNAL[TYPE] #define TFASAMP_isDebugActive() TFASAMP_INTERNAL[DEBUG_STATUS] +/* + UUID's size +*/ +#define MAX_UUID_SIZE 60 + /* * Variables Declaration */ @@ -75,6 +87,7 @@ enum TFASAMP_E_PLAYER AUTHY_USER_ID, TOKEN_CHECK_STATUS, LAST_CHECK_UNIX, + AUTHY_UUID[MAX_UUID_SIZE], bool: IS_HTTP_PROCESSING, }; @@ -87,6 +100,7 @@ hook OnPlayerConnect(playerid) TFASAMP_PLAYER[playerid][AUTHY_USER_ID] = 0; TFASAMP_PLAYER[playerid][TOKEN_CHECK_STATUS] = 0; TFASAMP_PLAYER[playerid][LAST_CHECK_UNIX] = 0; + format(TFASAMP_PLAYER[playerid][AUTHY_UUID], MAX_UUID_SIZE, ""); TFASAMP_PLAYER[playerid][IS_HTTP_PROCESSING] = false; return 1; } @@ -96,6 +110,7 @@ hook OnPlayerDisconnect(playerid, reason) TFASAMP_PLAYER[playerid][AUTHY_USER_ID] = 0; TFASAMP_PLAYER[playerid][TOKEN_CHECK_STATUS] = 0; TFASAMP_PLAYER[playerid][LAST_CHECK_UNIX] = 0; + format(TFASAMP_PLAYER[playerid][AUTHY_UUID], MAX_UUID_SIZE, ""); TFASAMP_PLAYER[playerid][IS_HTTP_PROCESSING] = false; return 1; } @@ -242,6 +257,66 @@ stock TFASAMP_verifyToken(playerid, user_id, token[], bool: force = true) return true; } +/* + * TFASAMP_pushNotification + * This function sends a notification to the client's device for authorization + * + * playerid = playerid of the player you wish to check the token. + * user_id = userid of the player you wish to check the token (check TFASAMP_createUser_response or your Authy's dashboard for clarifications). + * + * @returns true if the function has been properly executed, false if not. + * Notes: This function will trigger the callback TFASAMP_OnPushNotification. + * Notes: the callback TFASAMP_pushNotification will give you the UUID of the notification which can be used to check the status of the approval later via TFASAMP_getNotification() + * Notes: check function TFASAMP_getNotification for clarification as to where the UUID needs to be used +*/ + +stock TFASAMP_pushNotification(playerid, user_id){ + + if (!TFASAMP_INTERNAL[CONNECTION_PREPARED]) { + return print("TFASAMP: The connection is not prepared yet."), false; + } + + if (TFASAMP_isHTTPProcessing(playerid)) { + return printf("TFASAMP: playerid %d has already an API request in progress, wait please.", playerid), false; + } + + new string[1024]; + format(string, sizeof string, "%sexecute.php?password=%s&command=pushNoti&api=%s&userid=%d", TFASAMP_getHostname(), TFASAMP_getPassword(), TFASAMP_getAPIKey(), user_id); + + TFASAMP_PLAYER[playerid][IS_HTTP_PROCESSING] = true; + HTTP(playerid, HTTP_GET, string, "", "TFASAMP_push_response"); + return true; +} + +/* + * TFASAMP_getNotification + * This function gets the status of the notification which was sent to the client's device + * + * playerid = playerid of the player you wish to check the token. + * uuid = uuid of the player (check TFASAMP_pushNotification, when used the callback TFASAMP_OnPushNotification will return TFA_UUID tagged value) + * + * @returns true if the function has been properly executed, false if not. + * Notes: the callback TFASAMP_OnGetNotification will @return true if the authorization has been approved otherwise false if not or failed. +*/ + +stock TFASAMP_getNotification(playerid, TFA_UUID:uuid[]){ + + if (!TFASAMP_INTERNAL[CONNECTION_PREPARED]) { + return print("TFASAMP: The connection is not prepared yet."), false; + } + + if (TFASAMP_isHTTPProcessing(playerid)) { + return printf("TFASAMP: playerid %d has already an API request in progress, wait please.", playerid), false; + } + + new string[1024]; + format(string, sizeof string, "%sexecute.php?password=%s&command=checkNoti&api=%s&uuid=%s", TFASAMP_getHostname(), TFASAMP_getPassword(), TFASAMP_getAPIKey(), _:uuid); + + TFASAMP_PLAYER[playerid][IS_HTTP_PROCESSING] = true; + HTTP(playerid, HTTP_GET, string, "", "TFASAMP_check_response"); + return 1; +} + /* * TFASAMP_isHTTPProcessing * This function checks if the HTTP server is still processing the request. @@ -295,6 +370,16 @@ stock TFASAMP_getPlayerUserID(playerid) return TFASAMP_PLAYER[playerid][AUTHY_USER_ID]; } +stock TFASAMP_setUUID(playerid, TFA_UUID:_uuid[]){ + format(TFASAMP_PLAYER[playerid][AUTHY_UUID], MAX_UUID_SIZE, "%s", _:_uuid); + return 1; +} + +stock TFASAMP_getUUID(playerid, TFA_UUID:uuid_[], size = MAX_UUID_SIZE){ + strmid(_:uuid_, TFASAMP_PLAYER[playerid][AUTHY_UUID], 0, size, size); + return 1; +} + forward TFASAMP_createUser_response(index, response_code, data[]); public TFASAMP_createUser_response(index, response_code, data[]) { @@ -305,14 +390,16 @@ public TFASAMP_createUser_response(index, response_code, data[]) } TFASAMP_PLAYER[index][IS_HTTP_PROCESSING] = false; + + new JSONNode:jsonData; + jsonData = json_parse_string(data); + new result; + result = json_get_int(jsonData, "userid"); - if (!TFASAMP_IsNumeric(data)) { - return printf("TFASAMP ERROR: %s", data), false; - } else { - TFASAMP_PLAYER[index][AUTHY_USER_ID] = strval(data); + if(!result){ + printf("(ERROR) TFASAMP_createUser_response: User not created!"); } - - return 1; + return result; } forward TFASAMP_verifyToken_response(index, response_code, data[]); @@ -326,24 +413,71 @@ public TFASAMP_verifyToken_response(index, response_code, data[]) TFASAMP_PLAYER[index][IS_HTTP_PROCESSING] = false; - if (!TFASAMP_IsNumeric(data)) { - return printf("TFASAMP ERROR: %s", data), false; - } else { - CallLocalFunction("TFASAMP_OnTokenVerify", "dd", index, strval(data)); + new JSONNode:jsonData; + jsonData = json_parse_string(data); + new result[15]; + json_get_string(jsonData, result, sizeof result, "success"); + + if(!strcmp(result, "true", true) && !isnull(result)){ + CallLocalFunction("TFASAMP_OnTokenVerify", "dd", index, 1); TFASAMP_PLAYER[index][LAST_CHECK_UNIX] = gettime(); - TFASAMP_PLAYER[index][TOKEN_CHECK_STATUS] = strval(data); + TFASAMP_PLAYER[index][TOKEN_CHECK_STATUS] = 1; + } + else{ + CallLocalFunction("TFASAMP_OnTokenVerify", "dd", index, 0); + printf("TFASAMP ERROR: %s", data); + TFASAMP_PLAYER[index][TOKEN_CHECK_STATUS] = 0; + return 0; } return 1; } -stock TFASAMP_IsNumeric(const string[]) -{ - for (new i = 0, j = strlen(string); i < j; i++) { - if (string[i] > '9' || string[i] < '0') { - return 0; - } - } - - return 1; +forward TFASAMP_push_response(index, response_code, data[]); +public TFASAMP_push_response(index, response_code, data[]){ + + if(TFASAMP_isDebugActive()) { + printf("(debug) TFA_push_response: 'index' = '%d'", index); + printf("(debug) TFA_push_response: 'response_code' = '%d'", response_code); + printf("(debug) TFA_push_response: 'data' = '%s'", data); + } + + TFASAMP_PLAYER[index][IS_HTTP_PROCESSING] = false; + + new JSONNode:jsonData; + jsonData = json_parse_string(data); + new result[128]; + json_get_string(jsonData, result, sizeof result, "success"); + + if(!strcmp(result, "true", true)){ + json_get_string(jsonData, result, sizeof result, "uuid"); + CallLocalFunction("TFASAMP_OnPushNotification", "ds", index, result); + } + else{ + CallLocalFunction("TFASAMP_OnPushNotification", "ds", index, "NULL"); + } +} + +forward TFASAMP_check_response(index, response_code, data[]); +public TFASAMP_check_response(index, response_code, data[]){ + + if(TFASAMP_isDebugActive()) { + printf("(debug) TFA_check_response: 'index' = '%d'", index); + printf("(debug) TFA_check_response: 'response_code' = '%d'", response_code); + printf("(debug) TFA_check_response: 'data' = '%s'", data); + } + + TFASAMP_PLAYER[index][IS_HTTP_PROCESSING] = false; + + new JSONNode:jsonData; + jsonData = json_parse_string(data); + new result[128]; + json_get_string(jsonData, result, sizeof result, "status"); + + if(!strcmp(result, "approved", true)){ + CallLocalFunction("TFASAMP_OnCheckNotification", "dd", index, 1); + } + else{ + CallLocalFunction("TFASAMP_OnCheckNotification", "dd", index, 0); + } } \ No newline at end of file diff --git a/tfasamp_example2.pwn b/tfasamp_example2.pwn new file mode 100644 index 0000000..6c5f93c --- /dev/null +++ b/tfasamp_example2.pwn @@ -0,0 +1,81 @@ +#include +#include +#include +#include +#include + +#if !defined isnull + #define isnull(%1) ((%1[0] == 0) || (%1[0] == 1 && %1[1] == 0)) +#endif + +main() {} + +#define DIALOG_SELECT_USERID 1000 +#define DIALOG_VERIFY_PUSH 1001 + +public OnGameModeInit() +{ + TFASAMP_prepareConnection("localhost/", "testing", "api_key", "production", true); + + return 1; +} + +public OnPlayerConnect(playerid) +{ + ShowPlayerDialog(playerid, DIALOG_SELECT_USERID, DIALOG_STYLE_INPUT, "Insert USER-ID", "This is a testing dialog. This phase should be voided for production gamemodes.\nInsert your Authy's USER-ID:", "Continue", ""); + return 1; +} + +public OnDialogResponse(playerid, dialogid, response, listitem, inputtext[]) +{ + switch(dialogid) + { + case DIALOG_SELECT_USERID: + { + if(!response) + ShowPlayerDialog(playerid, DIALOG_SELECT_USERID, DIALOG_STYLE_INPUT, "Insert USER-ID", "This is a testing dialog. This phase should be voided for production gamemodes.\nInsert your Authy's USER-ID:", "Continue", ""); + + SendClientMessage(playerid, 000000, "{FFFFFF}You have written your Authy's user id."); + + TFASAMP_setPlayerUserID(playerid, strval(inputtext)); + TFASAMP_pushNotification(playerid, TFASAMP_getPlayerUserID(playerid)); + + ShowPlayerDialog(playerid, DIALOG_VERIFY_PUSH, DIALOG_STYLE_MSGBOX, "Attention", "Please accept the push notification that has been send in your mobile phone! And then hit accept here.", "Verify", ""); + return true; + } + + case DIALOG_VERIFY_PUSH: + { + SendClientMessage(playerid, 000000, "{FFFFFF}Loading.."); + new TFA_UUID:uuid[60]; + TFASAMP_getUUID(playerid, uuid, sizeof uuid); + + TFASAMP_getNotification(playerid, uuid); + return true; + } + } + + return false; +} + +public TFASAMP_OnPushNotification(playerid, TFA_UUID:uuid[]){ + + TFASAMP_setUUID(playerid, TFA_UUID:uuid); + + return 1; +} + +public TFASAMP_OnCheckNotification(playerid, status){ + + if(status){ + SendClientMessage(playerid, -1, "Push notification successfully approved."); + ShowPlayerDialog(playerid, DIALOG_SELECT_USERID, DIALOG_STYLE_INPUT, "Insert USER-ID", "This is a testing dialog. This phase should be voided for production gamemodes.\nInsert your Authy's USER-ID:", "Continue", ""); + } + else{ + SendClientMessage(playerid, -1, "Push notification not approved."); + TFASAMP_pushNotification(playerid, TFASAMP_getPlayerUserID(playerid)); + ShowPlayerDialog(playerid, DIALOG_VERIFY_PUSH, DIALOG_STYLE_MSGBOX, "Attention", "Please accept the push notification that has been send in your mobile phone! And then hit accept here.", "Verify", ""); + } + + return 1; +} diff --git a/website/class/tfasamp.php b/website/class/tfasamp.php index b3aa429..c492f13 100644 --- a/website/class/tfasamp.php +++ b/website/class/tfasamp.php @@ -228,12 +228,13 @@ public function createUser($email, $cellphone, $areaCode = 1, $returnType = 'raw $result = null; if($requestResult->ok()) { self::logAction(reportingLevel::NOTICE, "(CTFA_SAMP->createUser) user created [mail: '{$email}' - cellphone: '{$cellphone}' - areacode: '{$areaCode}' - id: '{$requestResult->id()}']"); - $result['userid'] = $requestResult->id(); + $result['id'] = $requestResult->id(); } else { foreach($requestResult->errors() as $field => $message) { self::logAction(reportingLevel::ERROR, "(CTFA_SAMP->createUser) {$field} = {$message}"); } $result = $requestResult->errors(); + $result->id = 0; } if ($returnType === 'json') { @@ -242,7 +243,7 @@ public function createUser($email, $cellphone, $areaCode = 1, $returnType = 'raw } return json_encode($result); } else { - return $result['userid']; + return $result; } } @@ -281,12 +282,13 @@ public function verifyToken($userID, $token, $settings = array(), $returnType = $result = null; if($requestResult->ok()) { self::logAction(reportingLevel::NOTICE, "(CTFA_SAMP->verifyToken) token verified successfully [userid: {$userID} - token: {$token}]"); - $result['result'] = 'success'; + $result['success'] = 'true'; } else { foreach($requestResult->errors() as $field => $message) { self::logAction(reportingLevel::ERROR, "(CTFA_SAMP->verifyToken) {$field} = {$message}"); - } + } $result = $requestResult->errors(); + $result->success = "false"; } if ($returnType === 'json') { @@ -299,6 +301,74 @@ public function verifyToken($userID, $token, $settings = array(), $returnType = } } + public function pushNotification($userID, $message, $opt = array("seconds_to_expire"=>"60")){ + + if(! $userID) { + self::logAction(reportingLevel::ERROR, '(CTFA_SAMP->pushNotification) userid is missing.'); + throw new InvalidArgumentException('{"success": "false", "message": "userid is missing"}'); + } + + if(! $message) { + $message = 'Please do the verification!'; + } + + $authyLibrary = new Authy_Api($this->__API, $this->__connectionURL); + $requestResult = $authyLibrary->createApprovalRequest(intval($userID), $message, $opt); + + $result = null; + + if($requestResult->ok()){ + self::logAction(reportingLevel::NOTICE, "(CTFA_SAMP->pushNotification) created notification successfully [userid: {$userID} - uuid: {$requestResult->bodyvar('approval_request')->uuid}]"); + $result['uuid'] = $requestResult->bodyvar('approval_request')->uuid; + $result['success'] = 'true'; + } + else{ + foreach($requestResult->errors() as $field => $message) { + self::logAction(reportingLevel::ERROR, "(CTFA_SAMP->pushNotification) {$field} = {$message}"); + } + $result = $requestResult->errors(); + $result->success = "false"; + } + + if(version_compare(PHP_VERSION, '5.4.0', '>=')) { + return json_encode($result, JSON_PRETTY_PRINT); + } + else + return json_encode($result); + } + + public function checkNotification($uuid){ + + if(!$uuid){ + self::logAction(reportingLevel::ERROR, '(CTFA_SAMP->checkNotification) uuid is missing.'); + throw new InvalidArgumentException('{"status": "NULL", "message": "uuid is missing"}'); + } + + $authyLibrary = new Authy_Api($this->__API, $this->__connectionURL); + $requestResult = $authyLibrary->getApprovalRequest($uuid); + + $result = null; + + if($requestResult->ok()){ + self::logAction(reportingLevel::NOTICE, "(CTFA_SAMP->checkNotification) checked notification successfully [uuid: {$uuid}"); + $result['status'] = $requestResult->bodyvar('approval_request')->status; + } + else{ + foreach($requestResult->errors() as $field => $message) { + self::logAction(reportingLevel::ERROR, "(CTFA_SAMP->checkNotification) {$field} = {$message}"); + } + $result = $requestResult->errors(); + $result->status = "NULL"; + } + + if(version_compare(PHP_VERSION, '5.4.0', '>=')) { + return json_encode($result, JSON_PRETTY_PRINT); + } + else + return json_encode($result); + + } + /** * The logAction method is an internal function that logs errors and notices in a file. * diff --git a/website/execute.php b/website/execute.php index 122f3ee..41c8b95 100644 --- a/website/execute.php +++ b/website/execute.php @@ -48,17 +48,19 @@ /** * This is a demonstration of how it should work with the 'raw' return type. - */ + */ + /* $result = $TFA_SAMP->createUser($args['email'], $args['cellphone'], $args['area_code'], 'raw'); if ($result) { foreach($result as $key => $value) { echo "{$key} => {$value}"; } } - + */ /** * This is a demonstration of how it should work with the 'json' return type. */ + $result = $TFA_SAMP->createUser($args['email'], $args['cellphone'], $args['area_code'], 'json'); if ($result) { echo $result; @@ -77,16 +79,18 @@ /** * This is a demonstration of how it should work with the 'raw' return type. */ + /* $result = $TFA_SAMP->verifyToken($args['userid'], $args['token'], $args['settings'], 'raw'); if ($result) { foreach($result as $key => $value) { echo "{$key} => {$value}"; } } - + */ /** * This is a demonstration of how it should work with the 'json' return type. - */ + */ + $result = $TFA_SAMP->verifyToken($args['userid'], $args['token'], $args['settings'], 'json'); if ($result) { echo $result; @@ -97,6 +101,32 @@ } break; } + + case 'pushNoti':{ + try{ + $args = $_GET + array('userid' => null, 'message' => null, 'opt' => null); + $result = $TFA_SAMP->pushNotification($args['userid'], $args['message'], $args['opt'], 'json'); + if($result){ + echo $result; + } + }catch(Exception $checkError){ + echo $checkError->getMessage(); + } + break; + } + + case 'checkNoti':{ + try{ + $args = $_GET + array('uuid' => null); + $result = $TFA_SAMP->checkNotification($args['uuid']); + if($result){ + echo $result; + } + }catch(Exception $checkError){ + echo $checkError->getMessage(); + } + break; + } default: die('Invalid command.'); } \ No newline at end of file