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.

117 lines
4.6 KiB

  1. <?php
  2. use Symfony\Component\HttpFoundation\Request;
  3. use Symfony\Component\HttpFoundation\Response;
  4. use \Firebase\JWT\JWT;
  5. class Auth {
  6. public function login(Request $request, Response $response) {
  7. $response->setContent(view('login', [
  8. 'title' => 'Sign In to Telegraph',
  9. 'return_to' => $request->get('return_to')
  10. ]));
  11. return $response;
  12. }
  13. public function login_start(Request $request, Response $response) {
  14. if(!$request->get('url') || !($me = IndieAuth\Client::normalizeMeURL($request->get('url')))) {
  15. $response->setContent(view('login', [
  16. 'title' => 'Sign In to Telegraph',
  17. 'error' => 'Invalid URL',
  18. 'error_description' => 'The URL you entered, "<strong>' . htmlspecialchars($request->get('url')) . '</strong>" is not valid.'
  19. ]));
  20. return $response;
  21. }
  22. $authorizationEndpoint = IndieAuth\Client::discoverAuthorizationEndpoint($me);
  23. $state = JWT::encode([
  24. 'me' => $me,
  25. 'authorization_endpoint' => $authorizationEndpoint,
  26. 'return_to' => $request->get('return_to'),
  27. 'time' => time(),
  28. 'exp' => time()+300 // verified by the JWT library
  29. ], Config::$secretKey);
  30. if($authorizationEndpoint) {
  31. // If the user specified only an authorization endpoint, use that
  32. $authorizationURL = IndieAuth\Client::buildAuthorizationURL($authorizationEndpoint, $me, self::_buildRedirectURI(), Config::$clientID, $state);
  33. } else {
  34. // Otherwise, fall back to indieauth.com
  35. $authorizationURL = IndieAuth\Client::buildAuthorizationURL(Config::$defaultAuthorizationEndpoint, $me, self::_buildRedirectURI(), Config::$clientID, $state);
  36. }
  37. $response->setStatusCode(302);
  38. $response->headers->set('Location', $authorizationURL);
  39. return $response;
  40. }
  41. public function login_callback(Request $request, Response $response) {
  42. if(!$request->get('state') || !$request->get('code') || !$request->get('me')) {
  43. $response->setContent(view('login', [
  44. 'title' => 'Sign In to Telegraph',
  45. 'error' => 'Missing Parameters',
  46. 'error_description' => 'The auth server did not return the necessary parameters, <code>state</code> and <code>code</code> and <code>me</code>.'
  47. ]));
  48. return $response;
  49. }
  50. // Validate the "state" parameter to ensure this request originated at this client
  51. try {
  52. $state = JWT::decode($request->get('state'), Config::$secretKey, ['HS256']);
  53. if(!$state) {
  54. $response->setContent(view('login', [
  55. 'title' => 'Sign In to Telegraph',
  56. 'error' => 'Invalid State',
  57. 'error_description' => 'The <code>state</code> parameter was not valid.'
  58. ]));
  59. return $response;
  60. }
  61. } catch(Exception $e) {
  62. $response->setContent(view('login', [
  63. 'title' => 'Sign In to Telegraph',
  64. 'error' => 'Invalid State',
  65. 'error_description' => 'The <code>state</code> parameter was invalid:<br>'.htmlspecialchars($e->getMessage())
  66. ]));
  67. return $response;
  68. }
  69. // Discover the authorization endpoint from the "me" that was returned by the auth server
  70. // This allows the auth server to return a different URL than the user originally entered,
  71. // for example if the user enters multiusersite.example the auth server can return multiusersite.example/alice
  72. if($state->authorization_endpoint) { // only discover the auth endpoint if one was originally found, otherwise use our fallback
  73. $authorizationEndpoint = IndieAuth\Client::discoverAuthorizationEndpoint($request->get('me'));
  74. } else {
  75. $authorizationEndpoint = Config::$defaultAuthorizationEndpoint;
  76. }
  77. // Verify the code with the auth server
  78. $token = IndieAuth\Client::verifyIndieAuthCode($authorizationEndpoint, $request->get('code'), $request->get('me'), self::_buildRedirectURI(), Config::$clientID, $request->get('state'), true);
  79. if(!array_key_exists('auth', $token) || !array_key_exists('me', $token['auth'])) {
  80. // The auth server didn't return a "me" URL
  81. $response->setContent(view('login', [
  82. 'title' => 'Sign In to Telegraph',
  83. 'error' => 'Invalid Auth Server Response',
  84. 'error_description' => 'The authorization server did not return a valid response:<br>'.htmlspecialchars(json_encode($token))
  85. ]));
  86. return $response;
  87. }
  88. // Create or load the user
  89. session_start();
  90. $_SESSION['me'] = $token['auth']['me'];
  91. $response->setStatusCode(302);
  92. $response->headers->set('Location', ($state->return_to ?: '/dashboard'));
  93. return $response;
  94. }
  95. private static function _buildRedirectURI() {
  96. return 'http' . (Config::$ssl ? 's' : '') . '://' . $_SERVER['SERVER_NAME'] . '/login/callback';
  97. }
  98. }