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.

166 lines
5.4 KiB

  1. <?php
  2. namespace App\Http\Controllers;
  3. use Laravel\Lumen\Routing\Controller as BaseController;
  4. use Illuminate\Http\Request;
  5. use GuzzleHttp;
  6. class IndieAuth extends BaseController
  7. {
  8. private function _redirectURI() {
  9. return env('BASE_URL') . 'auth/callback';
  10. }
  11. public function start(Request $request) {
  12. $me = \IndieAuth\Client::normalizeMeURL($request->input('me'));
  13. if(!$me) {
  14. return view('auth/error', ['error' => 'Invalid URL']);
  15. }
  16. $state = \IndieAuth\Client::generateStateParameter();
  17. if(preg_match('/https?:\/\/github\.com\/[^ \/]+/', $me)) {
  18. $authorizationURL = 'https://github.com/login/oauth/authorize'
  19. . '?client_id=' . env('GITHUB_ID')
  20. . '&state=' . $state;
  21. session([
  22. 'auth_state' => $state,
  23. 'attempted_me' => $me,
  24. ]);
  25. } else {
  26. $authorizationEndpoint = \IndieAuth\Client::discoverAuthorizationEndpoint($me);
  27. $tokenEndpoint = \IndieAuth\Client::discoverTokenEndpoint($me);
  28. session([
  29. 'auth_state' => $state,
  30. 'attempted_me' => $me,
  31. 'authorization_endpoint' => $authorizationEndpoint,
  32. 'token_endpoint' => $tokenEndpoint
  33. ]);
  34. // If the user specified only an authorization endpoint, use that
  35. if(!$authorizationEndpoint) {
  36. // Otherwise, fall back to indieauth.com
  37. $authorizationEndpoint = env('DEFAULT_AUTH_ENDPOINT');
  38. }
  39. $authorizationURL = \IndieAuth\Client::buildAuthorizationURL($authorizationEndpoint, $me, $this->_redirectURI(), env('BASE_URL'), $state);
  40. }
  41. return redirect($authorizationURL);
  42. }
  43. public function callback(Request $request) {
  44. if(!session('auth_state') || !session('attempted_me')) {
  45. return view('auth/error', ['error' => 'Missing state information. Start over.']);
  46. }
  47. if($request->input('error')) {
  48. return view('auth/error', ['error' => $request->input('error')]);
  49. }
  50. if(session('auth_state') != $request->input('state')) {
  51. return view('auth/error', ['error' => 'State did not match. Start over.']);
  52. }
  53. $tokenEndpoint = false;
  54. if(session('token_endpoint')) {
  55. $tokenEndpoint = session('token_endpoint');
  56. } else if(session('authorization_endpoint')) {
  57. $authorizationEndpoint = session('authorization_endpoint');
  58. } else {
  59. $authorizationEndpoint = env('DEFAULT_AUTH_ENDPOINT');
  60. }
  61. if($tokenEndpoint) {
  62. $token = \IndieAuth\Client::getAccessToken($tokenEndpoint, $request->input('code'), session('attempted_me'), $this->_redirectURI(), env('BASE_URL'), $request->input('state'));
  63. } else {
  64. $token = \IndieAuth\Client::verifyIndieAuthCode($authorizationEndpoint, $request->input('code'), session('attempted_me'), $this->_redirectURI(), env('BASE_URL'), $request->input('state'));
  65. }
  66. if($token && array_key_exists('me', $token)) {
  67. session()->flush();
  68. session(['me' => $token['me']]);
  69. }
  70. return redirect('/');
  71. }
  72. public function github(Request $request) {
  73. if(!session('auth_state') || !session('attempted_me')) {
  74. return view('auth/error', ['error' => 'Missing state information. Start over.']);
  75. }
  76. if($request->input('error')) {
  77. return view('auth/error', ['error' => $request->input('error')]);
  78. }
  79. if(session('auth_state') != $request->input('state')) {
  80. return view('auth/error', ['error' => 'State did not match. Start over.']);
  81. }
  82. if(!$request->input('code')) {
  83. return view('auth/error', ['error' => 'An unknown error occurred']);
  84. }
  85. $client = new GuzzleHttp\Client([
  86. 'http_errors' => false
  87. ]);
  88. $res = $client->post('https://github.com/login/oauth/access_token', [
  89. 'form_params' => [
  90. 'client_id' => env('GITHUB_ID'),
  91. 'client_secret' => env('GITHUB_SECRET'),
  92. // 'redirect_uri' => env('BASE_URL') . 'auth/github',
  93. 'code' => $request->input('code'),
  94. 'state' => session('auth_state')
  95. ],
  96. 'headers' => [
  97. 'Accept' => 'application/json'
  98. ]
  99. ]);
  100. if($res->getStatusCode() == 200) {
  101. $body = $res->getBody();
  102. $data = json_decode($body);
  103. if($data) {
  104. if(property_exists($data, 'access_token')) {
  105. // Now check the username of the user that just logged in
  106. $res = $client->get('https://api.github.com/user', [
  107. 'headers' => [
  108. 'Authorization' => 'token ' . $data->access_token
  109. ]
  110. ]);
  111. if($res->getStatusCode() == 200) {
  112. $data = json_decode($res->getBody());
  113. if(property_exists($data, 'login')) {
  114. session()->flush();
  115. session(['me' => 'https://github.com/' . $data->login]);
  116. return redirect('/');
  117. } else {
  118. return view('auth/error', ['error' => 'Login failed']);
  119. }
  120. } else {
  121. return view('auth/error', ['error' => 'Login failed']);
  122. }
  123. } else {
  124. $err = '';
  125. if(property_exists($data, 'error_description')) {
  126. $err = ': ' . $data->error_description;
  127. }
  128. return view('auth/error', ['error' => 'Login failed' . $err]);
  129. }
  130. } else {
  131. return view('auth/error', ['error' => 'Error parsing response body from GitHub']);
  132. }
  133. } else {
  134. return view('auth/error', ['error' => 'Could not verify login from GitHub: ' . $res->getBody()]);
  135. }
  136. }
  137. public function logout(Request $request) {
  138. session()->flush();
  139. return redirect('/');
  140. }
  141. }