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.

355 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 profile';
  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 = $token['raw_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. $html = render('auth_callback', array(
  170. 'title' => 'Sign In',
  171. 'me' => $me,
  172. 'authorizing' => $me,
  173. 'meParts' => parse_url($me),
  174. 'tokenEndpoint' => $tokenEndpoint,
  175. 'auth' => $token['response'],
  176. 'response' => $token['raw_response'],
  177. 'curl_error' => (array_key_exists('error', $token) ? $token['error'] : false),
  178. 'destination' => (k($_SESSION, 'redirect_after_login') ?: '/new')
  179. ));
  180. $app->response()->body($html);
  181. }
  182. });
  183. $app->get('/signout', function() use($app) {
  184. unset($_SESSION['auth']);
  185. unset($_SESSION['me']);
  186. unset($_SESSION['auth_state']);
  187. unset($_SESSION['user_id']);
  188. $app->redirect('/', 302);
  189. });
  190. $app->post('/auth/reset', function() use($app) {
  191. if($user=require_login($app, false)) {
  192. revoke_micropub_token($user->micropub_access_token, $user->token_endpoint);
  193. $user->authorization_endpoint = '';
  194. $user->token_endpoint = '';
  195. $user->micropub_endpoint = '';
  196. $user->authorization_endpoint = '';
  197. $user->micropub_media_endpoint = '';
  198. $user->micropub_scope = '';
  199. $user->micropub_access_token = '';
  200. $user->syndication_targets = '';
  201. $user->supported_post_types = '';
  202. $user->save();
  203. unset($_SESSION['auth']);
  204. unset($_SESSION['me']);
  205. unset($_SESSION['auth_state']);
  206. unset($_SESSION['user_id']);
  207. }
  208. $app->redirect('/', 302);
  209. });
  210. $app->post('/auth/twitter', function() use($app) {
  211. if(!Config::$twitterClientID) {
  212. $app->response()['Content-type'] = 'application/json';
  213. $app->response()->body(json_encode(array(
  214. 'result' => 'error'
  215. )));
  216. return;
  217. }
  218. if($user=require_login($app, false)) {
  219. $params = $app->request()->params();
  220. // User just auth'd with twitter, store the access token
  221. $user->twitter_access_token = $params['twitter_token'];
  222. $user->twitter_token_secret = $params['twitter_secret'];
  223. $user->save();
  224. $app->response()['Content-type'] = 'application/json';
  225. $app->response()->body(json_encode(array(
  226. 'result' => 'ok'
  227. )));
  228. } else {
  229. $app->response()['Content-type'] = 'application/json';
  230. $app->response()->body(json_encode(array(
  231. 'result' => 'error'
  232. )));
  233. }
  234. });
  235. function getTwitterLoginURL(&$twitter) {
  236. $request_token = $twitter->oauth('oauth/request_token', [
  237. 'oauth_callback' => Config::$base_url . 'auth/twitter/callback'
  238. ]);
  239. $_SESSION['twitter_auth'] = $request_token;
  240. return $twitter->url('oauth/authorize', ['oauth_token' => $request_token['oauth_token']]);
  241. }
  242. $app->get('/auth/twitter', function() use($app) {
  243. if(!Config::$twitterClientID) {
  244. $app->response()['Content-type'] = 'application/json';
  245. $app->response()->body(json_encode(array(
  246. 'result' => 'error'
  247. )));
  248. return;
  249. }
  250. $params = $app->request()->params();
  251. if($user=require_login($app, false)) {
  252. // If there is an existing Twitter token, check if it is valid
  253. // Otherwise, generate a Twitter login link
  254. $twitter_login_url = false;
  255. if(array_key_exists('login', $params)) {
  256. $twitter = new TwitterOAuth(Config::$twitterClientID, Config::$twitterClientSecret);
  257. $twitter_login_url = getTwitterLoginURL($twitter);
  258. } else {
  259. $twitter = new TwitterOAuth(Config::$twitterClientID, Config::$twitterClientSecret,
  260. $user->twitter_access_token, $user->twitter_token_secret);
  261. if($user->twitter_access_token) {
  262. if($twitter->get('account/verify_credentials')) {
  263. $app->response()['Content-type'] = 'application/json';
  264. $app->response()->body(json_encode(array(
  265. 'result' => 'ok'
  266. )));
  267. return;
  268. } else {
  269. // If the existing twitter token is not valid, generate a login link
  270. $twitter_login_url = getTwitterLoginURL($twitter);
  271. }
  272. } else {
  273. $twitter_login_url = getTwitterLoginURL($twitter);
  274. }
  275. }
  276. $app->response()['Content-type'] = 'application/json';
  277. $app->response()->body(json_encode(array(
  278. 'url' => $twitter_login_url
  279. )));
  280. } else {
  281. $app->response()['Content-type'] = 'application/json';
  282. $app->response()->body(json_encode(array(
  283. 'result' => 'error'
  284. )));
  285. }
  286. });
  287. $app->get('/auth/twitter/callback', function() use($app) {
  288. if($user=require_login($app)) {
  289. $params = $app->request()->params();
  290. $twitter = new TwitterOAuth(Config::$twitterClientID, Config::$twitterClientSecret,
  291. $_SESSION['twitter_auth']['oauth_token'], $_SESSION['twitter_auth']['oauth_token_secret']);
  292. $credentials = $twitter->oauth('oauth/access_token', ['oauth_verifier' => $params['oauth_verifier']]);
  293. $user->twitter_access_token = $credentials['oauth_token'];
  294. $user->twitter_token_secret = $credentials['oauth_token_secret'];
  295. $user->twitter_username = $credentials['screen_name'];
  296. $user->save();
  297. $app->redirect('/settings');
  298. }
  299. });