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.

185 lines
6.1 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. use DB;
  7. class IndieAuth extends BaseController
  8. {
  9. private function _redirectURI() {
  10. return env('BASE_URL') . 'auth/callback';
  11. }
  12. public function start(Request $request) {
  13. $me = \IndieAuth\Client::normalizeMeURL($request->input('me'));
  14. if(!$me) {
  15. return view('auth/error', ['error' => 'Invalid URL']);
  16. }
  17. $state = \IndieAuth\Client::generateStateParameter();
  18. if(preg_match('/https?:\/\/github\.com\/[^ \/]+/', $me)) {
  19. $authorizationURL = 'https://github.com/login/oauth/authorize'
  20. . '?client_id=' . env('GITHUB_ID')
  21. . '&state=' . $state;
  22. session([
  23. 'auth_state' => $state,
  24. 'attempted_me' => $me,
  25. ]);
  26. } else {
  27. $authorizationEndpoint = \IndieAuth\Client::discoverAuthorizationEndpoint($me);
  28. $tokenEndpoint = \IndieAuth\Client::discoverTokenEndpoint($me);
  29. session([
  30. 'auth_state' => $state,
  31. 'attempted_me' => $me,
  32. 'authorization_endpoint' => $authorizationEndpoint,
  33. 'token_endpoint' => $tokenEndpoint
  34. ]);
  35. // If the user specified only an authorization endpoint, use that
  36. if(!$authorizationEndpoint) {
  37. // Otherwise, fall back to indieauth.com
  38. $authorizationEndpoint = env('DEFAULT_AUTH_ENDPOINT');
  39. }
  40. $authorizationURL = \IndieAuth\Client::buildAuthorizationURL($authorizationEndpoint, $me, $this->_redirectURI(), env('BASE_URL'), $state);
  41. }
  42. return redirect($authorizationURL);
  43. }
  44. public function callback(Request $request) {
  45. if(!session('auth_state') || !session('attempted_me')) {
  46. return view('auth/error', ['error' => 'Missing state information. Start over.']);
  47. }
  48. if($request->input('error')) {
  49. return view('auth/error', ['error' => $request->input('error')]);
  50. }
  51. if(session('auth_state') != $request->input('state')) {
  52. return view('auth/error', ['error' => 'State did not match. Start over.']);
  53. }
  54. $tokenEndpoint = false;
  55. if(session('token_endpoint')) {
  56. $tokenEndpoint = session('token_endpoint');
  57. } else if(session('authorization_endpoint')) {
  58. $authorizationEndpoint = session('authorization_endpoint');
  59. } else {
  60. $authorizationEndpoint = env('DEFAULT_AUTH_ENDPOINT');
  61. }
  62. if($tokenEndpoint) {
  63. $token = \IndieAuth\Client::getAccessToken($tokenEndpoint, $request->input('code'), session('attempted_me'), $this->_redirectURI(), env('BASE_URL'), $request->input('state'));
  64. } else {
  65. $token = \IndieAuth\Client::verifyIndieAuthCode($authorizationEndpoint, $request->input('code'), session('attempted_me'), $this->_redirectURI(), env('BASE_URL'), $request->input('state'));
  66. }
  67. if($token && array_key_exists('me', $token)) {
  68. session()->flush();
  69. session(['me' => $token['me']]);
  70. $this->_userLoggedIn($token['me']);
  71. }
  72. return redirect('/');
  73. }
  74. public function github(Request $request) {
  75. if(!session('auth_state') || !session('attempted_me')) {
  76. return view('auth/error', ['error' => 'Missing state information. Start over.']);
  77. }
  78. if($request->input('error')) {
  79. return view('auth/error', ['error' => $request->input('error')]);
  80. }
  81. if(session('auth_state') != $request->input('state')) {
  82. return view('auth/error', ['error' => 'State did not match. Start over.']);
  83. }
  84. if(!$request->input('code')) {
  85. return view('auth/error', ['error' => 'An unknown error occurred']);
  86. }
  87. $client = new GuzzleHttp\Client([
  88. 'http_errors' => false
  89. ]);
  90. $res = $client->post('https://github.com/login/oauth/access_token', [
  91. 'form_params' => [
  92. 'client_id' => env('GITHUB_ID'),
  93. 'client_secret' => env('GITHUB_SECRET'),
  94. // 'redirect_uri' => env('BASE_URL') . 'auth/github',
  95. 'code' => $request->input('code'),
  96. 'state' => session('auth_state')
  97. ],
  98. 'headers' => [
  99. 'Accept' => 'application/json'
  100. ]
  101. ]);
  102. if($res->getStatusCode() == 200) {
  103. $body = $res->getBody();
  104. $data = json_decode($body);
  105. if($data) {
  106. if(property_exists($data, 'access_token')) {
  107. // Now check the username of the user that just logged in
  108. $res = $client->get('https://api.github.com/user', [
  109. 'headers' => [
  110. 'Authorization' => 'token ' . $data->access_token
  111. ]
  112. ]);
  113. if($res->getStatusCode() == 200) {
  114. $data = json_decode($res->getBody());
  115. if(property_exists($data, 'login')) {
  116. session()->flush();
  117. $me = 'https://github.com/' . $data->login;
  118. session(['me' => $me]);
  119. $this->_userLoggedIn($me);
  120. return redirect('/');
  121. } else {
  122. return view('auth/error', ['error' => 'Login failed']);
  123. }
  124. } else {
  125. return view('auth/error', ['error' => 'Login failed']);
  126. }
  127. } else {
  128. $err = '';
  129. if(property_exists($data, 'error_description')) {
  130. $err = ': ' . $data->error_description;
  131. }
  132. return view('auth/error', ['error' => 'Login failed' . $err]);
  133. }
  134. } else {
  135. return view('auth/error', ['error' => 'Error parsing response body from GitHub']);
  136. }
  137. } else {
  138. return view('auth/error', ['error' => 'Could not verify login from GitHub: ' . $res->getBody()]);
  139. }
  140. }
  141. private function _userLoggedIn($url) {
  142. // Create the user record if it doesn't exist yet
  143. $user = DB::table('users')->where('url','=',$url)->first();
  144. if($user) {
  145. DB::update('UPDATE users SET last_login = ?', [date('Y-m-d H:i:s')]);
  146. session(['user_id' => $user->id]);
  147. } else {
  148. DB::insert('INSERT INTO users (url, created_at, last_login) VALUES(?,?,?)', [$url, date('Y-m-d H:i:s'), date('Y-m-d H:i:s')]);
  149. $user = DB::select('SELECT *
  150. FROM users
  151. WHERE url = ?', [$url]);
  152. session(['user_id' => $user->id]);
  153. }
  154. }
  155. public function logout(Request $request) {
  156. session()->flush();
  157. return redirect('/');
  158. }
  159. }