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.

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