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
8.8 KiB

  1. <?php
  2. IndieAuth\Client::$clientID = Config::$base_url;
  3. IndieAuth\Client::$redirectURL = Config::$base_url.'auth/callback';
  4. $app->get('/auth/start', function() use($app) {
  5. $req = $app->request();
  6. $params = $req->params();
  7. $defaultScope = 'create update media profile';
  8. list($authorizationURL, $error) = IndieAuth\Client::begin($params['me'], $defaultScope);
  9. $me = IndieAuth\Client::normalizeMeURL($params['me']);
  10. // Double check for a micropub endpoint here for debugging purposes
  11. if(!$error) {
  12. $micropubEndpoint = $_SESSION['indieauth']['micropub_endpoint'] = IndieAuth\Client::discoverMicropubEndpoint($me);
  13. if(!$micropubEndpoint) {
  14. $error['error'] = 'missing_micropub_endpoint';
  15. }
  16. }
  17. if($error && in_array($error['error'], ['missing_authorization_endpoint','missing_token_endpoint','missing_micropub_endpoint'])) {
  18. // Display debug info for these particular errors
  19. $micropubEndpoint = $_SESSION['indieauth']['micropub_endpoint'] = IndieAuth\Client::discoverMicropubEndpoint($me);
  20. $tokenEndpoint = $_SESSION['indieauth']['token_endpoint'] = IndieAuth\Client::discoverTokenEndpoint($me);
  21. $authorizationEndpoint = $_SESSION['indieauth']['authorization_endpoint'] = IndieAuth\Client::discoverAuthorizationEndpoint($me);
  22. $html = render('auth_start', array(
  23. 'title' => 'Sign In',
  24. 'me' => $me,
  25. 'authorizing' => $me,
  26. 'meParts' => parse_url($me),
  27. 'tokenEndpoint' => $tokenEndpoint,
  28. 'micropubEndpoint' => $micropubEndpoint,
  29. 'authorizationEndpoint' => $authorizationEndpoint,
  30. 'authorizationURL' => false
  31. ));
  32. $app->response()->body($html);
  33. return;
  34. }
  35. // Handle other errors like connection errors by showing a generic error page
  36. if($error) {
  37. $html = render('auth_error', array(
  38. 'title' => 'Sign In',
  39. 'error' => $error['error'],
  40. 'errorDescription' => $error['error_description'],
  41. ));
  42. $app->response()->body($html);
  43. return;
  44. }
  45. $micropubEndpoint = $_SESSION['indieauth']['micropub_endpoint'] = IndieAuth\Client::discoverMicropubEndpoint($me);
  46. $tokenEndpoint = $_SESSION['indieauth']['token_endpoint'] = IndieAuth\Client::discoverTokenEndpoint($me);
  47. $authorizationEndpoint = $_SESSION['indieauth']['authorization_endpoint'] = IndieAuth\Client::discoverAuthorizationEndpoint($me);
  48. if(k($params, 'redirect')) {
  49. $_SESSION['redirect_after_login'] = $params['redirect'];
  50. }
  51. if(k($params, 'reply')) {
  52. $_SESSION['reply'] = $params['reply'];
  53. }
  54. // If the user has already signed in before and has a micropub access token,
  55. // and the endpoints are all the same, skip the debugging screens and redirect
  56. // immediately to the auth endpoint.
  57. // This will still get a new access token when they finish logging in.
  58. $user = ORM::for_table('users')->where('url', $me)->find_one();
  59. if($user && $user->micropub_access_token
  60. && $user->micropub_endpoint == $micropubEndpoint
  61. && $user->token_endpoint == $tokenEndpoint
  62. && $user->authorization_endpoint == $authorizationEndpoint
  63. && !array_key_exists('restart', $params)) {
  64. // Request whatever scope was previously granted
  65. $authorizationURL = parse_url($authorizationURL);
  66. $authorizationURL['scope'] = $user->micropub_scope;
  67. $authorizationURL = http_build_url($authorizationURL);
  68. $app->redirect($authorizationURL, 302);
  69. } else {
  70. if(k($params, 'dontask') && $params['dontask']) {
  71. // Request whatever scope was previously granted
  72. $authorizationURL = parse_url($authorizationURL);
  73. $authorizationURL['scope'] = $user->micropub_scope ?: $defaultScope;
  74. $authorizationURL = http_build_url($authorizationURL);
  75. $_SESSION['dontask'] = 1;
  76. $app->redirect($authorizationURL, 302);
  77. }
  78. $html = render('auth_start', array(
  79. 'title' => 'Sign In',
  80. 'me' => $me,
  81. 'authorizing' => $me,
  82. 'meParts' => parse_url($me),
  83. 'tokenEndpoint' => $tokenEndpoint,
  84. 'micropubEndpoint' => $micropubEndpoint,
  85. 'authorizationEndpoint' => $authorizationEndpoint,
  86. 'authorizationURL' => $authorizationURL
  87. ));
  88. $app->response()->body($html);
  89. }
  90. });
  91. $app->get('/auth/redirect', function() use($app) {
  92. $req = $app->request();
  93. $params = $req->params();
  94. // Override scope from the form the user selects
  95. if(!isset($params['scope']))
  96. $params['scope'] = '';
  97. $authorizationURL = parse_url($params['authorization_url']);
  98. parse_str($authorizationURL['query'], $query);
  99. $query['scope'] = $params['scope'];
  100. $authorizationURL['query'] = http_build_query($query);
  101. $authorizationURL = http_build_url($authorizationURL);
  102. $app->redirect($authorizationURL);
  103. return;
  104. });
  105. $app->get('/auth/callback', function() use($app) {
  106. $req = $app->request();
  107. $params = $req->params();
  108. list($token, $error) = IndieAuth\Client::complete($params, true);
  109. if($error) {
  110. $html = render('auth_error', [
  111. 'title' => 'Auth Callback',
  112. 'error' => $error['error'],
  113. 'errorDescription' => $error['error_description'],
  114. ]);
  115. $app->response()->body($html);
  116. return;
  117. }
  118. $me = $token['me'];
  119. // Use the discovered endpoints saved in the session
  120. $micropubEndpoint = $_SESSION['indieauth']['micropub_endpoint'];
  121. $tokenEndpoint = $_SESSION['indieauth']['token_endpoint'];
  122. $redirectToDashboardImmediately = false;
  123. // If a valid access token was returned, store the token info in the session and they are signed in
  124. if(k($token['response'], array('me','access_token','scope'))) {
  125. $_SESSION['auth'] = $token['response'];
  126. $_SESSION['me'] = $me = $token['me'];
  127. $user = ORM::for_table('users')->where('url', $me)->find_one();
  128. if($user) {
  129. // Already logged in, update the last login date
  130. $user->last_login = date('Y-m-d H:i:s');
  131. // If they have logged in before and we already have an access token, then redirect to the dashboard now
  132. if($user->micropub_access_token)
  133. $redirectToDashboardImmediately = true;
  134. } else {
  135. // New user! Store the user in the database
  136. $user = ORM::for_table('users')->create();
  137. $user->url = $me;
  138. $user->date_created = date('Y-m-d H:i:s');
  139. }
  140. $user->authorization_endpoint = $_SESSION['indieauth']['authorization_endpoint'];
  141. $user->token_endpoint = $tokenEndpoint;
  142. $user->micropub_endpoint = $micropubEndpoint;
  143. $user->micropub_access_token = $token['response']['access_token'];
  144. if(is_numeric($token['response']['expires_in'])) {
  145. $expiration = time() + $token['response']['expires_in'];
  146. $user->micropub_token_expiration = date('Y-m-d H:i:s', $expiration);
  147. }
  148. $user->micropub_scope = $token['response']['scope'];
  149. $user->micropub_response = $token['raw_response'];
  150. $user->save();
  151. $_SESSION['user_id'] = $user->id();
  152. // Make a request to the micropub endpoint to discover the syndication targets and media endpoint if any.
  153. // Errors are silently ignored here. The user will be able to retry from the new post interface and get feedback.
  154. get_micropub_config($user, ['q'=>'config']);
  155. }
  156. unset($_SESSION['indieauth']);
  157. if($redirectToDashboardImmediately || k($_SESSION, 'dontask')) {
  158. unset($_SESSION['dontask']);
  159. if(k($_SESSION, 'redirect_after_login')) {
  160. $dest = $_SESSION['redirect_after_login'];
  161. unset($_SESSION['redirect_after_login']);
  162. $app->redirect($dest, 302);
  163. } else {
  164. $query = [];
  165. if(k($_SESSION, 'reply')) {
  166. $query['reply'] = $_SESSION['reply'];
  167. unset($_SESSION['reply']);
  168. }
  169. $app->redirect('/new?' . http_build_query($query), 302);
  170. }
  171. } else {
  172. $html = render('auth_callback', array(
  173. 'title' => 'Sign In',
  174. 'me' => $me,
  175. 'authorizing' => $me,
  176. 'meParts' => parse_url($me),
  177. 'tokenEndpoint' => $tokenEndpoint,
  178. 'auth' => $token['response'],
  179. 'response' => $token['raw_response'],
  180. 'curl_error' => (array_key_exists('error', $token) ? $token['error'] : false),
  181. 'destination' => (k($_SESSION, 'redirect_after_login') ?: '/new')
  182. ));
  183. $app->response()->body($html);
  184. }
  185. });
  186. $app->get('/signout', function() use($app) {
  187. unset($_SESSION['auth']);
  188. unset($_SESSION['me']);
  189. unset($_SESSION['auth_state']);
  190. unset($_SESSION['user_id']);
  191. $app->redirect('/', 302);
  192. });
  193. $app->post('/auth/reset', function() use($app) {
  194. if($user=require_login($app, false)) {
  195. revoke_micropub_token($user->micropub_access_token, $user->token_endpoint);
  196. $user->authorization_endpoint = '';
  197. $user->token_endpoint = '';
  198. $user->micropub_endpoint = '';
  199. $user->authorization_endpoint = '';
  200. $user->micropub_media_endpoint = '';
  201. $user->micropub_scope = '';
  202. $user->micropub_access_token = '';
  203. $user->micropub_token_expiration = '';
  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. });