You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

256 lines
7.3 KiB

<?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();
$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) {
# 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'];
$action = ($action == 'drank' ? 'drank' : 'ate');
$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, $url)
->endSession(true);
$app->response()['Content-type'] = 'application/json';
$app->response()->body(json_encode($response->render()));
}
});