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.

360 lines
12 KiB

  1. <?php
  2. use Abraham\TwitterOAuth\TwitterOAuth;
  3. IndieAuth\Client::$clientID = Config::$base_url;
  4. IndieAuth\Client::$redirectURL = Config::$base_url.'auth/callback';
  5. $app->get('/auth/start', function() use($app) {
  6. $req = $app->request();
  7. $params = $req->params();
  8. $defaultScope = 'create update media';
  9. list($authorizationURL, $error) = IndieAuth\Client::begin($params['me'], $defaultScope);
  10. $me = IndieAuth\Client::normalizeMeURL($params['me']);
  11. // Double check for a micropub endpoint here for debugging purposes
  12. if(!$error) {
  13. $micropubEndpoint = $_SESSION['indieauth']['micropub_endpoint'] = IndieAuth\Client::discoverMicropubEndpoint($me);
  14. if(!$micropubEndpoint) {
  15. $error['error'] = 'missing_micropub_endpoint';
  16. }
  17. }
  18. if($error && in_array($error['error'], ['missing_authorization_endpoint','missing_token_endpoint','missing_micropub_endpoint'])) {
  19. // Display debug info for these particular errors
  20. $micropubEndpoint = $_SESSION['indieauth']['micropub_endpoint'] = IndieAuth\Client::discoverMicropubEndpoint($me);
  21. $tokenEndpoint = $_SESSION['indieauth']['token_endpoint'] = IndieAuth\Client::discoverTokenEndpoint($me);
  22. $authorizationEndpoint = $_SESSION['indieauth']['authorization_endpoint'] = IndieAuth\Client::discoverAuthorizationEndpoint($me);
  23. $html = render('auth_start', array(
  24. 'title' => 'Sign In',
  25. 'me' => $me,
  26. 'authorizing' => $me,
  27. 'meParts' => parse_url($me),
  28. 'tokenEndpoint' => $tokenEndpoint,
  29. 'micropubEndpoint' => $micropubEndpoint,
  30. 'authorizationEndpoint' => $authorizationEndpoint,
  31. 'authorizationURL' => false
  32. ));
  33. $app->response()->body($html);
  34. return;
  35. }
  36. // Handle other errors like connection errors by showing a generic error page
  37. if($error) {
  38. $html = render('auth_error', array(
  39. 'title' => 'Sign In',
  40. 'error' => $error['error'],
  41. 'errorDescription' => $error['error_description'],
  42. ));
  43. $app->response()->body($html);
  44. return;
  45. }
  46. $micropubEndpoint = $_SESSION['indieauth']['micropub_endpoint'] = IndieAuth\Client::discoverMicropubEndpoint($me);
  47. $tokenEndpoint = $_SESSION['indieauth']['token_endpoint'] = IndieAuth\Client::discoverTokenEndpoint($me);
  48. $authorizationEndpoint = $_SESSION['indieauth']['authorization_endpoint'] = IndieAuth\Client::discoverAuthorizationEndpoint($me);
  49. if(k($params, 'redirect')) {
  50. $_SESSION['redirect_after_login'] = $params['redirect'];
  51. }
  52. if(k($params, 'reply')) {
  53. $_SESSION['reply'] = $params['reply'];
  54. }
  55. // If the user has already signed in before and has a micropub access token,
  56. // and the endpoints are all the same, skip the debugging screens and redirect
  57. // immediately to the auth endpoint.
  58. // This will still get a new access token when they finish logging in.
  59. $user = ORM::for_table('users')->where('url', $me)->find_one();
  60. if($user && $user->micropub_access_token
  61. && $user->micropub_endpoint == $micropubEndpoint
  62. && $user->token_endpoint == $tokenEndpoint
  63. && $user->authorization_endpoint == $authorizationEndpoint
  64. && !array_key_exists('restart', $params)) {
  65. // Request whatever scope was previously granted
  66. $authorizationURL = parse_url($authorizationURL);
  67. $authorizationURL['scope'] = $user->micropub_scope;
  68. $authorizationURL = http_build_url($authorizationURL);
  69. $app->redirect($authorizationURL, 302);
  70. } else {
  71. if(k($params, 'dontask') && $params['dontask']) {
  72. // Request whatever scope was previously granted
  73. $authorizationURL = parse_url($authorizationURL);
  74. $authorizationURL['scope'] = $user->micropub_scope ?: $defaultScope;
  75. $authorizationURL = http_build_url($authorizationURL);
  76. $_SESSION['dontask'] = 1;
  77. $app->redirect($authorizationURL, 302);
  78. }
  79. $html = render('auth_start', array(
  80. 'title' => 'Sign In',
  81. 'me' => $me,
  82. 'authorizing' => $me,
  83. 'meParts' => parse_url($me),
  84. 'tokenEndpoint' => $tokenEndpoint,
  85. 'micropubEndpoint' => $micropubEndpoint,
  86. 'authorizationEndpoint' => $authorizationEndpoint,
  87. 'authorizationURL' => $authorizationURL
  88. ));
  89. $app->response()->body($html);
  90. }
  91. });
  92. $app->get('/auth/redirect', function() use($app) {
  93. $req = $app->request();
  94. $params = $req->params();
  95. // Override scope from the form the user selects
  96. if(!isset($params['scope']))
  97. $params['scope'] = '';
  98. $authorizationURL = parse_url($params['authorization_url']);
  99. parse_str($authorizationURL['query'], $query);
  100. $query['scope'] = $params['scope'];
  101. $authorizationURL['query'] = http_build_query($query);
  102. $authorizationURL = http_build_url($authorizationURL);
  103. $app->redirect($authorizationURL);
  104. return;
  105. });
  106. $app->get('/auth/callback', function() use($app) {
  107. $req = $app->request();
  108. $params = $req->params();
  109. list($token, $error) = IndieAuth\Client::complete($params, true);
  110. if($error) {
  111. $html = render('auth_error', [
  112. 'title' => 'Auth Callback',
  113. 'error' => $error['error'],
  114. 'errorDescription' => $error['error_description'],
  115. ]);
  116. $app->response()->body($html);
  117. return;
  118. }
  119. $me = $token['me'];
  120. // Use the discovered endpoints saved in the session
  121. $micropubEndpoint = $_SESSION['indieauth']['micropub_endpoint'];
  122. $tokenEndpoint = $_SESSION['indieauth']['token_endpoint'];
  123. $redirectToDashboardImmediately = false;
  124. // If a valid access token was returned, store the token info in the session and they are signed in
  125. if(k($token['response'], array('me','access_token','scope'))) {
  126. $_SESSION['auth'] = $token['response'];
  127. $_SESSION['me'] = $me = $token['me'];
  128. $user = ORM::for_table('users')->where('url', $me)->find_one();
  129. if($user) {
  130. // Already logged in, update the last login date
  131. $user->last_login = date('Y-m-d H:i:s');
  132. // If they have logged in before and we already have an access token, then redirect to the dashboard now
  133. if($user->micropub_access_token)
  134. $redirectToDashboardImmediately = true;
  135. } else {
  136. // New user! Store the user in the database
  137. $user = ORM::for_table('users')->create();
  138. $user->url = $me;
  139. $user->date_created = date('Y-m-d H:i:s');
  140. }
  141. $user->authorization_endpoint = $_SESSION['indieauth']['authorization_endpoint'];
  142. $user->token_endpoint = $tokenEndpoint;
  143. $user->micropub_endpoint = $micropubEndpoint;
  144. $user->micropub_access_token = $token['response']['access_token'];
  145. $user->micropub_scope = $token['response']['scope'];
  146. $user->micropub_response = json_encode($token['response']);
  147. $user->save();
  148. $_SESSION['user_id'] = $user->id();
  149. // Make a request to the micropub endpoint to discover the syndication targets and media endpoint if any.
  150. // Errors are silently ignored here. The user will be able to retry from the new post interface and get feedback.
  151. get_micropub_config($user, ['q'=>'config']);
  152. }
  153. unset($_SESSION['indieauth']);
  154. if($redirectToDashboardImmediately || k($_SESSION, 'dontask')) {
  155. unset($_SESSION['dontask']);
  156. if(k($_SESSION, 'redirect_after_login')) {
  157. $dest = $_SESSION['redirect_after_login'];
  158. unset($_SESSION['redirect_after_login']);
  159. $app->redirect($dest, 302);
  160. } else {
  161. $query = [];
  162. if(k($_SESSION, 'reply')) {
  163. $query['reply'] = $_SESSION['reply'];
  164. unset($_SESSION['reply']);
  165. }
  166. $app->redirect('/new?' . http_build_query($query), 302);
  167. }
  168. } else {
  169. $tokenResponse = $token['response'];
  170. $parsed = @json_decode($tokenResponse);
  171. if($parsed)
  172. $tokenResponse = json_encode($parsed, JSON_PRETTY_PRINT+JSON_UNESCAPED_SLASHES);
  173. $html = render('auth_callback', array(
  174. 'title' => 'Sign In',
  175. 'me' => $me,
  176. 'authorizing' => $me,
  177. 'meParts' => parse_url($me),
  178. 'tokenEndpoint' => $tokenEndpoint,
  179. 'auth' => $token['auth'],
  180. 'response' => $tokenResponse,
  181. 'curl_error' => (array_key_exists('error', $token) ? $token['error'] : false),
  182. 'destination' => (k($_SESSION, 'redirect_after_login') ?: '/new')
  183. ));
  184. $app->response()->body($html);
  185. }
  186. });
  187. $app->get('/signout', function() use($app) {
  188. unset($_SESSION['auth']);
  189. unset($_SESSION['me']);
  190. unset($_SESSION['auth_state']);
  191. unset($_SESSION['user_id']);
  192. $app->redirect('/', 302);
  193. });
  194. $app->post('/auth/reset', function() use($app) {
  195. if($user=require_login($app, false)) {
  196. revoke_micropub_token($user->micropub_access_token, $user->token_endpoint);
  197. $user->authorization_endpoint = '';
  198. $user->token_endpoint = '';
  199. $user->micropub_endpoint = '';
  200. $user->authorization_endpoint = '';
  201. $user->micropub_media_endpoint = '';
  202. $user->micropub_scope = '';
  203. $user->micropub_access_token = '';
  204. $user->syndication_targets = '';
  205. $user->supported_post_types = '';
  206. $user->save();
  207. unset($_SESSION['auth']);
  208. unset($_SESSION['me']);
  209. unset($_SESSION['auth_state']);
  210. unset($_SESSION['user_id']);
  211. }
  212. $app->redirect('/', 302);
  213. });
  214. $app->post('/auth/twitter', function() use($app) {
  215. if(!Config::$twitterClientID) {
  216. $app->response()['Content-type'] = 'application/json';
  217. $app->response()->body(json_encode(array(
  218. 'result' => 'error'
  219. )));
  220. return;
  221. }
  222. if($user=require_login($app, false)) {
  223. $params = $app->request()->params();
  224. // User just auth'd with twitter, store the access token
  225. $user->twitter_access_token = $params['twitter_token'];
  226. $user->twitter_token_secret = $params['twitter_secret'];
  227. $user->save();
  228. $app->response()['Content-type'] = 'application/json';
  229. $app->response()->body(json_encode(array(
  230. 'result' => 'ok'
  231. )));
  232. } else {
  233. $app->response()['Content-type'] = 'application/json';
  234. $app->response()->body(json_encode(array(
  235. 'result' => 'error'
  236. )));
  237. }
  238. });
  239. function getTwitterLoginURL(&$twitter) {
  240. $request_token = $twitter->oauth('oauth/request_token', [
  241. 'oauth_callback' => Config::$base_url . 'auth/twitter/callback'
  242. ]);
  243. $_SESSION['twitter_auth'] = $request_token;
  244. return $twitter->url('oauth/authorize', ['oauth_token' => $request_token['oauth_token']]);
  245. }
  246. $app->get('/auth/twitter', function() use($app) {
  247. if(!Config::$twitterClientID) {
  248. $app->response()['Content-type'] = 'application/json';
  249. $app->response()->body(json_encode(array(
  250. 'result' => 'error'
  251. )));
  252. return;
  253. }
  254. $params = $app->request()->params();
  255. if($user=require_login($app, false)) {
  256. // If there is an existing Twitter token, check if it is valid
  257. // Otherwise, generate a Twitter login link
  258. $twitter_login_url = false;
  259. if(array_key_exists('login', $params)) {
  260. $twitter = new TwitterOAuth(Config::$twitterClientID, Config::$twitterClientSecret);
  261. $twitter_login_url = getTwitterLoginURL($twitter);
  262. } else {
  263. $twitter = new TwitterOAuth(Config::$twitterClientID, Config::$twitterClientSecret,
  264. $user->twitter_access_token, $user->twitter_token_secret);
  265. if($user->twitter_access_token) {
  266. if($twitter->get('account/verify_credentials')) {
  267. $app->response()['Content-type'] = 'application/json';
  268. $app->response()->body(json_encode(array(
  269. 'result' => 'ok'
  270. )));
  271. return;
  272. } else {
  273. // If the existing twitter token is not valid, generate a login link
  274. $twitter_login_url = getTwitterLoginURL($twitter);
  275. }
  276. } else {
  277. $twitter_login_url = getTwitterLoginURL($twitter);
  278. }
  279. }
  280. $app->response()['Content-type'] = 'application/json';
  281. $app->response()->body(json_encode(array(
  282. 'url' => $twitter_login_url
  283. )));
  284. } else {
  285. $app->response()['Content-type'] = 'application/json';
  286. $app->response()->body(json_encode(array(
  287. 'result' => 'error'
  288. )));
  289. }
  290. });
  291. $app->get('/auth/twitter/callback', function() use($app) {
  292. if($user=require_login($app)) {
  293. $params = $app->request()->params();
  294. $twitter = new TwitterOAuth(Config::$twitterClientID, Config::$twitterClientSecret,
  295. $_SESSION['twitter_auth']['oauth_token'], $_SESSION['twitter_auth']['oauth_token_secret']);
  296. $credentials = $twitter->oauth('oauth/access_token', ['oauth_verifier' => $params['oauth_verifier']]);
  297. $user->twitter_access_token = $credentials['oauth_token'];
  298. $user->twitter_token_secret = $credentials['oauth_token_secret'];
  299. $user->twitter_username = $credentials['screen_name'];
  300. $user->save();
  301. $app->redirect('/settings');
  302. }
  303. });