Skip to content

Commit

Permalink
latest code + slight README update
Browse files Browse the repository at this point in the history
  • Loading branch information
swiftyspiffy committed Jan 3, 2021
1 parent 2ad9de0 commit 848725f
Show file tree
Hide file tree
Showing 40 changed files with 816 additions and 112 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.DS_Store
17 changes: 16 additions & 1 deletion .htaccess
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
Order Deny,Allow
Deny from 204.12.217.130 95.216.5.232

RewriteEngine On
RewriteCond %{SERVER_PORT} 80
RewriteRule ^(.*)$ https://twitchtokengenerator.com/$1 [R,L]
RewriteRule ^(.*)$ https://twitchtokengenerator.com/$1 [R,L]
<Files 403.shtml>
order allow,deny
allow from all
</Files>

deny from 81.92.203.76
deny from 81.92.203.0/24
deny from 84.66.181.27
deny from 94.109.185.22
deny from 90.108.238.38
deny from 73.245.107.0
deny from 180.150.32.18
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,9 @@ An API exists on TwitchTokenGenerator allowing the creation of tokens and implem
4. Occasionally you will find that your Twitch access token has expired. This is new as of Twitch's oAuth2 implementation. To refresh, use the "refresh" token that you received in step 3 and hit the /api/refresh/ endpoint to get a new token.
- Example refresh: `https://twitchtokengenerator.com/api/refresh/{refresh_token}`

### Logging and Website Attacks
Over the last year or two, the website has seen significant increase in traffic, and with that has come additional bot attacks and malicious users. I've added significantly more logging, as well as attempts at detecting such behavior. I've also added mitigations including scrambling the website code, recaptcha, and a few other methods to try to prevent malicious users from generating access and refresh tokens to perform large scale chat spam attacks on Twitch, and mass followings of streamers.
- That is to say, if you use this website to mass generate tokens for hundreds and thousands of accounts, and these accounts are used to attack Twitch streamers, I will (and have) shared in bulk, accounts that I feel are suspicious with Twitch's safety team.

### License
MIT License. &copy; 2018 Cole
MIT License. &copy; 2021 Cole
31 changes: 28 additions & 3 deletions api/create.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,27 @@
exit(json_encode(array("success" => false, "error" => 14, "message" => "invalid title")));

$unique = randStrGen(20);
$dao->insertAPI($unique, $title, $scopes, $_SERVER['REMOTE_ADDR']);
$recaptchaNow = rand(0,1);
if(count($redirectUrl) > 0) {
$dao->insertAPI($unique, $title, $scopes, $_SERVER['REMOTE_ADDR'], $redirectUrl, $recaptchaNow);
} else {
$dao->insertAPI($unique, $title, $scopes, $_SERVER['REMOTE_ADDR'], "", $recaptchaNow);
}

exit(json_encode(array("success" => true, 'id' => $unique, "message" => "https://twitchtokengenerator.com/api/".$unique)));

function validateScopes($scopes, $validScopes) {

$scopes = explode(' ', $scopes);
$validScopeNames = getValidScopes($validScopes);
$scopes = getScopesFromIds($scopes, $validScopes);
$checkScopes = array();
if (strpos($scopes, ' ') !== false)
$checkScopes = explode(" ", $scopes);
else
array_push($checkScopes, $scopes);

foreach($checkScopes as $scope) {
if(!in_array($scope, $validScopes))
if(!in_array($scope, $validScopeNames))
return false;
}

Expand All @@ -38,4 +45,22 @@ function randStrGen($len){
}
return $result;
}

function getScopesFromIds($scopes, $rawScopes) {
$scopeNames = array();
foreach($rawScopes as $rawScope) {
if(in_array($rawScope['id'], $scopes)) {
array_push($scopeNames, $rawScope['scope']);
}
}
return $scopeNames;
}

function getValidScopes($rawScopes) {
$validScopes = array();
foreach($rawScopes as $rawScope) {
array_push($validScopes, $rawScope['scope']);
}
return $validScopes;
}
?>
35 changes: 35 additions & 0 deletions api/found.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php
include("../config.php");
include("../twitchtv.php");
?>

<title>Twitch Token Generator by swiftyspiffy - Redirecting...</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/js/bootstrap.min.js"></script>

<br>
<div class="container">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title text-center">Twitch Token Generator - Redirecting...</h3>
</div>
<div class="panel-body">
<center>
<span>Redirecting you to authorize on Twitch for <b> <? echo $title; ?> </b>. <br>If you are not redirected in 5 seconds, <a style="font-weight: bold; font-size: 120%;" href="<? echo $url; ?>">click here</a>.
<br><br>
Thanks for using TwitchTokenGenerator.com!
</span>
</center>
</div>
</div>
</div>

<script>
var url = "<? echo $url; ?>";

setInterval(function(){ window.location.href = url; }, 3000);
</script>
37 changes: 31 additions & 6 deletions api/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@
}

switch($action) {
case "verify":
if(count($parts) != 2) {
header('Content-Type: application/json');
exit(json_encode(array('success' => false, 'error' => 64, 'message' => 'Unexpected argument count')));
}
$identifier = $parts[1];
include("recaptcha.php");
break;
case "waiting_texts":
$results = $dao->getWaitingTexts();
header('Content-Type: application/json');
Expand Down Expand Up @@ -54,19 +62,34 @@
}
$result = $dao->revokeToken($args[0]);
if($result) {
$dao->insertRevokeRequest(1, $_SERVER['REMOTE_ADDR']);
header('Content-Type: application/json');
exit(json_encode(array('success' => true)));
} else {
$dao->insertRevokeRequest(0, $_SERVER['REMOTE_ADDR']);
header('Content-Type: application/json');
exit(json_encode(array('success' => false, 'error' => 45, 'message' => 'Invalid oauth access token provided.')));
}
case "create":
if(count($args) != 2) {
header('Content-Type: application/json');
exit(json_encode(array('success' => false, 'error' => 14, 'message' => 'Only two arguments are to be provided: title (base64encoded), scopes (with + as delimeter)')));
}
$redirectUrl = "";
switch(count($args)) {
case 2:
break;
case 3:
$redirectUrl = base64_decode($args[2]);
if(!filter_var($redirectUrl, FILTER_VALIDATE_URL)) {
header('Content-Type: application/json');
exit(json_encode(array('success' => false, 'error' => 114, 'message' => 'URL does not appear to be valid. It needs to be base64 encoded, and it needs to start with https:// or http://')));
}
break;
default:
header('Content-Type: application/json');
exit(json_encode(array('success' => false, 'error' => 14, 'message' => 'Only two or three arguments are to be provided: title (base64encoded), scopes (with + as delimeter), redirectUrl (base64encoded, OPTIONAL)')));
break;
}
$title = base64_decode($args[0]);
$scopes = $args[1];

include("create.php");
break;
case "status":
Expand All @@ -86,8 +109,10 @@
$scopes = str_replace(" ", "+", $resp['scopes']);
$unique = $action;
$state = base64_encode(json_encode(array('action' => 'api', 'id' => $unique)));
$url = "https://api.twitch.tv/kraken/oauth2/authorize?response_type=code&client_id=zkxgn9qm9y3kzrb1p0px68qa69t3ae&redirect_uri=https://twitchtokengenerator.com/api/success&scope=".$scopes."&state=".$state."&force_verify=true";
exit(header("Location: ".$url));
$url = "https://api.twitch.tv/kraken/oauth2/authorize?response_type=code&client_id=".API_CLIENT_ID."&redirect_uri=https://twitchtokengenerator.com/api/success&scope=".$scopes."&state=".$state."&force_verify=true";
$ip = $_SERVER['REMOTE_ADDR'];
$title = $resp['title'];
include("found.php");
break;
case 1:
include("used.php");
Expand Down
144 changes: 144 additions & 0 deletions api/recaptcha.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<?

if(isset($_POST['robot_identifier'])) {
$id = $_POST['robot_identifier'];
$data = $dao->getApiRecaptchaStatus($id);
if($data['error'] != "0") {
header('Content-Type: application/json');
exit(json_encode(array('success' => false, 'message' => 'Unknown API workflow identifier')));
}

$captcha = $_POST['g-recaptcha-response'];
$ip = $_SERVER['REMOTE_ADDR'];

if(!isValid($captcha, $ip))
exit(json_encode(array('success' => false, 'message' => "reCaptcha was not valid!")));

switch($data['recaptcha_status']) {
case "0":
if(strlen($data['token']) == 0) {
// send to twitch to auth
$url = getTwitchAuthUrl($data['scopes'], $id);
exit(header("Location: ".$url));
} else {
// send to success
$dao->updateApiRecaptchaStatus($id, "2");
$title = $data['title'];
include("success_after_verify.php");
exit();
}
case "1":
// authing now
$dao->updateApiRecaptchaStatus($id, "2");
$url = getTwitchAuthUrl($data['scopes'], $id);
exit(header("Location: ".$url));
default:
header('Content-Type: application/json');
exit(json_encode(array('success' => false, 'message' => 'Unknown API workflow identifier')));
break;
}
} else {
$data = $dao->getApiRecaptchaStatus($identifier);
if(data['error'] != "0") {
header('Content-Type: application/json');
exit(json_encode(array('success' => false, 'message' => 'Unknown API workflow identifier')));
}
if($data['recaptcha_status'] == "0" && strlen($data['token']) == 0) {
// go to twitch
$url = getTwitchAuthUrl($data['scopes'], $identifier);
exit(header("Location: ".$url));
}
exit("status: ".$data['recaptcha_status'].", token: '".$data['token']."'");
if($data['recaptcha_status'] == "2") {
// go to success page
$title = $data['title'];
include("success_after_verify.php");
exit();
}
if($data['recaptcha_status'] == "1" && strlen($data['token']) > 0) {
header('Content-Type: application/json');
exit(json_encode(array('success' => false, 'message' => 'Invalid API workflow state')));
}
}

?>

<style>
.g-recaptcha{
margin: 15px auto !important;
width: auto !important;
height: auto !important;
text-align: -webkit-center;
text-align: -moz-center;
text-align: -o-center;
text-align: -ms-center;
}
</style>

<script>
function recaptchaSuccess() {
$("#robot_form").submit();
}
</script>

<title>Twitch Token Generator by swiftyspiffy - Recaptcha</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
<script src='https://www.google.com/recaptcha/api.js'></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/js/bootstrap.min.js"></script>

<br>
<div class="container">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title text-center">Twitch Token Generator - Recaptcha</h3>
</div>
<div class="panel-body">
<center>
<span>Thank you for using TwitchTokenGenerator! Please complete the reCaptcha below!</span>
</center>
<form id="robot_form" action="verify" method="post">
<input type="hidden" id="robot_identifier" name="robot_identifier" value="<? echo $identifier; ?>"></input>
<div class="g-recaptcha" data-callback="recaptchaSuccess" style="padding-left: 23%" data-sitekey="6LeaCF0UAAAAAMG7-HRJ1Oq_aneLPdQQNN0r9_no"></div>
</form>
</div>
</div>
</div>

<?
function getTwitchAuthUrl($scopes, $id) {
$url = "https://api.twitch.tv/kraken/oauth2/authorize?response_type=code&client_id=".API_CLIENT_ID."&redirect_uri=https://twitchtokengenerator.com/api/success&scope=".str_replace(" ", "+", $scopes);
$state = base64_encode(json_encode(array('action' => 'api', 'id' => $id)));;

return $url."&state=".$state."&force_verify=true";
}

function isValid($captcha, $ip) {
try {

$url = 'https://www.google.com/recaptcha/api/siteverify';
$data = ['secret' => RECAPTCHA_SECRET,
'response' => $captcha,
'remoteip' => $ip];

$options = [
'http' => [
'header' => "Content-type: application/x-www-form-urlencoded\r\n",
'method' => 'POST',
'content' => http_build_query($data)
]
];

$context = stream_context_create($options);
$result = file_get_contents($url, false, $context);
return json_decode($result)->success;
}
catch (Exception $e) {
$dao->insertError("internal.php", "isValid", "failed to verify captcha with google");
return null;
}
}

?>
2 changes: 1 addition & 1 deletion api/refresh.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
if($resp['success']) {
$username = $dao->getUsername($resp['access']);
$dao->insertRefreshRequest(1, $username, $_SERVER['REMOTE_ADDR']);
exit(json_encode(array('success' => true, 'token' => $resp['access'], 'refresh' => $resp['refresh'])));
exit(json_encode(array('success' => true, 'token' => $resp['access'], 'refresh' => $resp['refresh'], 'client_id' => API_CLIENT_ID)));
} else {
$dao->insertRefreshRequest(0, "", $_SERVER['REMOTE_ADDR']);
exit(json_encode(array('success' => false, 'error' => 22, 'message' => 'Invalid refresh token received.')));
Expand Down
10 changes: 8 additions & 2 deletions api/status.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<?php
header('Content-Type: application/json');

// expire api workflows after 24 hours
// TODO: Evalulate if this makes sense (maybe shorten to 12)
$expireAfterHours = 24;
if(!isset($id)) {
exit(json_encode(array('success' => false, 'error' => 1, 'message' => 'No id provided.')));
Expand All @@ -12,13 +14,17 @@
} else {
switch($resp['status']) {
case "0":
// user has not authorized using API workflow link
exit(json_encode(array('success' => false, 'id' => $id, 'error' => 3, 'message' => 'Not authorized yet')));
case "1":
// expire API after first access to prevent accidental token leaks, then return authorization data
$dao->expireAPI($id);
exit(json_encode(array('success' => true, 'id' => $id, 'scopes' => $resp['scopes'], 'token' => $resp['token'], 'refresh' => $resp['refresh'], 'username' => $resp['username'], 'user_id' => $resp['user_id'])));
exit(json_encode(array('success' => true, 'id' => $id, 'scopes' => $resp['scopes'], 'token' => $resp['token'], 'refresh' => $resp['refresh'], 'username' => $resp['username'], 'user_id' => $resp['user_id'], 'client_id' => API_CLIENT_ID)));
case "2":
// workflow has already been accessed, reject request
exit(json_encode(array('success' => false, 'id' => $id, 'error' => 4, 'message' => 'API instance has already expired (already activated). Create a new one.')));
case "3":
case "3":
// expire token after period of time to prevent accidental token leaks
exit(json_encode(array('success' => false, 'id' => $id, 'error' => 6, 'message' => 'API instance has already expired (hit time limit: \''.$expireAfterHours.'\' hour(s)). Create a new one.')));
default:
exit(json_encode(array('success' => false, 'id' => $id, 'error' => 5, 'message' => 'Unknown status: '.$resp['status'])));
Expand Down
Loading

0 comments on commit 848725f

Please sign in to comment.