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.

251 lines
8.6 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. $user->micropub_scope = $token['response']['scope'];
  145. $user->micropub_response = $token['raw_response'];
  146. $user->save();
  147. $_SESSION['user_id'] = $user->id();
  148. // Make a request to the micropub endpoint to discover the syndication targets and media endpoint if any.
  149. // Errors are silently ignored here. The user will be able to retry from the new post interface and get feedback.
  150. get_micropub_config($user, ['q'=>'config']);
  151. }
  152. unset($_SESSION['indieauth']);
  153. if($redirectToDashboardImmediately || k($_SESSION, 'dontask')) {
  154. unset($_SESSION['dontask']);
  155. if(k($_SESSION, 'redirect_after_login')) {
  156. $dest = $_SESSION['redirect_after_login'];
  157. unset($_SESSION['redirect_after_login']);
  158. $app->redirect($dest, 302);
  159. } else {
  160. $query = [];
  161. if(k($_SESSION, 'reply')) {
  162. $query['reply'] = $_SESSION['reply'];
  163. unset($_SESSION['reply']);
  164. }
  165. $app->redirect('/new?' . http_build_query($query), 302);
  166. }
  167. } else {
  168. $html = render('auth_callback', array(
  169. 'title' => 'Sign In',
  170. 'me' => $me,
  171. 'authorizing' => $me,
  172. 'meParts' => parse_url($me),
  173. 'tokenEndpoint' => $tokenEndpoint,
  174. 'auth' => $token['response'],
  175. 'response' => $token['raw_response'],
  176. 'curl_error' => (array_key_exists('error', $token) ? $token['error'] : false),
  177. 'destination' => (k($_SESSION, 'redirect_after_login') ?: '/new')
  178. ));
  179. $app->response()->body($html);
  180. }
  181. });
  182. $app->get('/signout', function() use($app) {
  183. unset($_SESSION['auth']);
  184. unset($_SESSION['me']);
  185. unset($_SESSION['auth_state']);
  186. unset($_SESSION['user_id']);
  187. $app->redirect('/', 302);
  188. });
  189. $app->post('/auth/reset', function() use($app) {
  190. if($user=require_login($app, false)) {
  191. revoke_micropub_token($user->micropub_access_token, $user->token_endpoint);
  192. $user->authorization_endpoint = '';
  193. $user->token_endpoint = '';
  194. $user->micropub_endpoint = '';
  195. $user->authorization_endpoint = '';
  196. $user->micropub_media_endpoint = '';
  197. $user->micropub_scope = '';
  198. $user->micropub_access_token = '';
  199. $user->syndication_targets = '';
  200. $user->supported_post_types = '';
  201. $user->save();
  202. unset($_SESSION['auth']);
  203. unset($_SESSION['me']);
  204. unset($_SESSION['auth_state']);
  205. unset($_SESSION['user_id']);
  206. }
  207. $app->redirect('/', 302);
  208. });