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.

184 lines
5.8 KiB

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