|
|
@ -0,0 +1,257 @@ |
|
|
|
<?php |
|
|
|
use \Firebase\JWT\JWT; |
|
|
|
|
|
|
|
$app->get('/alexa', function() use($app) { |
|
|
|
render('alexa', array( |
|
|
|
'title' => 'Teacup for Alexa' |
|
|
|
)); |
|
|
|
}); |
|
|
|
|
|
|
|
$app->get('/alexa/auth', function() use($app) { |
|
|
|
$req = $app->request(); |
|
|
|
$params = $req->params(); |
|
|
|
|
|
|
|
$required = ['client_id', 'response_type', 'state', 'redirect_uri']; |
|
|
|
$params_present = array_keys($params); |
|
|
|
|
|
|
|
// Validate Alexa OAuth parameters
|
|
|
|
if(count(array_intersect($required, $params_present)) != count($required)) { |
|
|
|
render('auth_error', array( |
|
|
|
'title' => 'Sign In', |
|
|
|
'error' => 'Missing parameters', |
|
|
|
'errorDescription' => 'One or more required parameters were missing', |
|
|
|
'footer' => false |
|
|
|
)); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// Check that redirect URI is one that is allowed
|
|
|
|
if(!in_array($params['redirect_uri'], Config::$alexaRedirectURIs)) { |
|
|
|
render('auth_error', array( |
|
|
|
'title' => 'Sign In', |
|
|
|
'error' => 'Invalid redirect URI', |
|
|
|
'errorDescription' => 'Alexa sent an invalid redirect URI', |
|
|
|
'footer' => false |
|
|
|
)); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
if($params['client_id'] != Config::$alexaClientID) { |
|
|
|
render('auth_error', array( |
|
|
|
'title' => 'Sign In', |
|
|
|
'error' => 'Invalid Client ID', |
|
|
|
'errorDescription' => 'Alexa sent an invalid client ID', |
|
|
|
'footer' => false |
|
|
|
)); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// Pass through the OAuth parameters
|
|
|
|
render('alexa-auth', [ |
|
|
|
'title' => 'Teacup for Alexa', |
|
|
|
'client_id' => $params['client_id'], |
|
|
|
'response_type' => $params['response_type'], |
|
|
|
'state' => $params['state'], |
|
|
|
'redirect_uri' => $params['redirect_uri'], |
|
|
|
'footer' => false |
|
|
|
]); |
|
|
|
}); |
|
|
|
|
|
|
|
$app->post('/alexa/login', function() use($app) { |
|
|
|
$req = $app->request(); |
|
|
|
$params = $req->params(); |
|
|
|
|
|
|
|
file_put_contents('logs/login.txt', json_encode($params)); |
|
|
|
|
|
|
|
$required = ['code', 'client_id', 'state', 'redirect_uri']; |
|
|
|
$params_present = array_keys($params); |
|
|
|
|
|
|
|
if(count(array_intersect($required, $params_present)) != count($required)) { |
|
|
|
render('auth_error', array( |
|
|
|
'title' => 'Sign In', |
|
|
|
'error' => 'Missing parameters', |
|
|
|
'errorDescription' => 'One or more required parameters were missing', |
|
|
|
'footer' => false |
|
|
|
)); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
$user = ORM::for_table('users') |
|
|
|
->where('device_code', $params['code']) |
|
|
|
->where_gt('device_code_expires', date('Y-m-d H:i:s'))->find_one(); |
|
|
|
|
|
|
|
if(!$user) { |
|
|
|
render('auth_error', array( |
|
|
|
'title' => 'Sign In', |
|
|
|
'error' => 'Invalid code', |
|
|
|
'errorDescription' => 'The code you entered is invalid or has expired', |
|
|
|
'footer' => false |
|
|
|
)); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
$code = JWT::encode(array( |
|
|
|
'user_id' => $user->id, |
|
|
|
'iat' => time(), |
|
|
|
'exp' => time()+300, |
|
|
|
'client_id' => $params['client_id'], |
|
|
|
'state' => $params['state'], |
|
|
|
'redirect_uri' => $params['redirect_uri'], |
|
|
|
), Config::$jwtSecret); |
|
|
|
|
|
|
|
$redirect = $params['redirect_uri'] . '?code=' . $code . '&state=' . $params['state']; |
|
|
|
|
|
|
|
$app->redirect($redirect, 302); |
|
|
|
}); |
|
|
|
|
|
|
|
$app->post('/alexa/token', function() use($app) { |
|
|
|
$req = $app->request(); |
|
|
|
$params = $req->params(); |
|
|
|
// Alexa requests a token given a code generated above
|
|
|
|
|
|
|
|
// Verify the client ID and secret
|
|
|
|
if($params['client_id'] != Config::$alexaClientID |
|
|
|
|| $params['client_secret'] != Config::$alexaClientSecret) { |
|
|
|
$app->response->setStatus(400); |
|
|
|
$app->response()['Content-type'] = 'application/json'; |
|
|
|
$app->response()->body(json_encode([ |
|
|
|
'error' => 'forbidden', |
|
|
|
'error_description' => 'The client ID and secret do not match' |
|
|
|
])); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
if(array_key_exists('code', $params)) { |
|
|
|
$jwt = $params['code']; |
|
|
|
} elseif(array_key_exists('refresh_token', $params)) { |
|
|
|
$jwt = $params['refresh_token']; |
|
|
|
} else { |
|
|
|
$app->response->setStatus(400); |
|
|
|
$app->response()['Content-type'] = 'application/json'; |
|
|
|
$app->response()->body(json_encode([ |
|
|
|
'error' => 'bad_request', |
|
|
|
'error_description' => 'Must provide either an authorization code or refresh token' |
|
|
|
])); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// Validate the JWT
|
|
|
|
try { |
|
|
|
$user = JWT::decode($jwt, Config::$jwtSecret, ['HS256']); |
|
|
|
} catch(Exception $e) { |
|
|
|
$app->response->setStatus(400); |
|
|
|
$app->response()['Content-type'] = 'application/json'; |
|
|
|
$app->response()->body(json_encode([ |
|
|
|
'error' => 'unauthorized', |
|
|
|
'error_description' => 'The authorization code or refresh token was invalid' |
|
|
|
])); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// Generate an access token and refresh token
|
|
|
|
$access_token = JWT::encode([ |
|
|
|
'user_id' => $user->user_id, |
|
|
|
'client_id' => $user->client_id, |
|
|
|
'iat' => time(), |
|
|
|
], Config::$jwtSecret); |
|
|
|
$refresh_token = JWT::encode([ |
|
|
|
'user_id' => $user->user_id, |
|
|
|
'client_id' => $user->client_id, |
|
|
|
'iat' => time(), |
|
|
|
], Config::$jwtSecret); |
|
|
|
|
|
|
|
|
|
|
|
$app->response()['Content-type'] = 'application/json'; |
|
|
|
$app->response()->body(json_encode([ |
|
|
|
'access_token' => $access_token, |
|
|
|
'refresh_token' => $refresh_token |
|
|
|
])); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
$app->post('/alexa/endpoint', function() use($app) { |
|
|
|
|
|
|
|
$input = file_get_contents('php://input'); |
|
|
|
$json = json_decode($input, 'input'); |
|
|
|
|
|
|
|
$alexaRequest = \Alexa\Request\Request::fromData($json); |
|
|
|
|
|
|
|
if($alexaRequest instanceof Alexa\Request\IntentRequest) { |
|
|
|
# file_put_contents('logs/request.txt', $input);
|
|
|
|
|
|
|
|
# Verify the access token
|
|
|
|
try { |
|
|
|
$data = JWT::decode($alexaRequest->user->accessToken, Config::$jwtSecret, ['HS256']); |
|
|
|
} catch(Exception $e) { |
|
|
|
$app->response->setStatus(401); |
|
|
|
$app->response()['Content-type'] = 'application/json'; |
|
|
|
$app->response()->body(json_encode([ |
|
|
|
'error' => 'unauthorized', |
|
|
|
'error_description' => 'The access token was invalid or has expired' |
|
|
|
])); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
$user = ORM::for_table('users')->find_one($data->user_id); |
|
|
|
|
|
|
|
if(!$user) { |
|
|
|
$app->response->setStatus(400); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
$action = $alexaRequest->slots['Action']; |
|
|
|
$food = ucfirst($alexaRequest->slots['Food']); |
|
|
|
|
|
|
|
|
|
|
|
$entry = ORM::for_table('entries')->create(); |
|
|
|
$entry->user_id = $user->id; |
|
|
|
$entry->type = ($action == 'drank' ? 'drink' : 'eat'); |
|
|
|
$entry->content = $food; |
|
|
|
$entry->published = date('Y-m-d H:i:s'); |
|
|
|
$entry->save(); |
|
|
|
|
|
|
|
$text_content = 'Just ' . $action . ': ' . $food; |
|
|
|
|
|
|
|
if($user->micropub_endpoint) { |
|
|
|
$mp_request = array( |
|
|
|
'h' => 'entry', |
|
|
|
'published' => date('Y-m-d H:i:s'), |
|
|
|
'summary' => $text_content |
|
|
|
); |
|
|
|
if($user->enable_array_micropub) { |
|
|
|
$mp_request[$action] = [ |
|
|
|
'type' => 'h-food', |
|
|
|
'properties' => [ |
|
|
|
'name' => $food |
|
|
|
] |
|
|
|
]; |
|
|
|
} else { |
|
|
|
$mp_request['p3k-food'] = $food; |
|
|
|
$mp_request['p3k-type'] = $entry->type; |
|
|
|
} |
|
|
|
|
|
|
|
$r = micropub_post($user->micropub_endpoint, $mp_request, $user->access_token); |
|
|
|
$request = $r['request']; |
|
|
|
$response = $r['response']; |
|
|
|
|
|
|
|
$entry->micropub_response = $response; |
|
|
|
if($response && preg_match('/Location: (.+)/', $response, $match)) { |
|
|
|
$url = $match[1]; |
|
|
|
$entry->micropub_success = 1; |
|
|
|
$entry->canonical_url = $url; |
|
|
|
} else { |
|
|
|
$entry->micropub_success = 0; |
|
|
|
$url = Config::$base_url . $user->url . '/' . $entry->id; |
|
|
|
} |
|
|
|
$entry->save(); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
$response = new \Alexa\Response\Response; |
|
|
|
$response->respond('Got it!') |
|
|
|
->withCard('You '.$action.': '.$food); |
|
|
|
|
|
|
|
$app->response()['Content-type'] = 'application/json'; |
|
|
|
$app->response()->body(json_encode($response->render())); |
|
|
|
} |
|
|
|
}); |