ApiAuthenticationController.php 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. <?php
  2. namespace App\Http\Controllers\Auth;
  3. use App\Facades\Webauthn;
  4. use App\Http\Controllers\Controller;
  5. use App\Http\Requests\ApiAuthenticationLoginRequest;
  6. use App\Http\Requests\ApiAuthenticationMfaRequest;
  7. use App\Models\User;
  8. use App\Models\Username;
  9. use Illuminate\Contracts\Encryption\DecryptException;
  10. use Illuminate\Support\Carbon;
  11. use Illuminate\Support\Facades\Cache;
  12. use Illuminate\Support\Facades\Crypt;
  13. use Illuminate\Support\Facades\Hash;
  14. use PragmaRX\Google2FA\Google2FA;
  15. class ApiAuthenticationController extends Controller
  16. {
  17. public function __construct()
  18. {
  19. $this->middleware('throttle:3,1');
  20. }
  21. public function login(ApiAuthenticationLoginRequest $request)
  22. {
  23. $user = Username::firstWhere('username', $request->username)?->user;
  24. if (! $user || ! Hash::check($request->password, $user->password)) {
  25. return response()->json([
  26. 'error' => 'The provided credentials are incorrect',
  27. ], 401);
  28. }
  29. // Check if user has 2FA enabled, if needs OTP then return mfa_key
  30. if ($user->two_factor_enabled) {
  31. return response()->json([
  32. 'message' => "OTP required, please make a request to /api/auth/mfa with the 'mfa_key', 'otp' and 'device_name' including a 'X-CSRF-TOKEN' header",
  33. 'mfa_key' => Crypt::encryptString($user->id.'|'.config('anonaddy.secret').'|'.Carbon::now()->addMinutes(5)->getTimestamp()),
  34. 'csrf_token' => csrf_token(),
  35. ], 422);
  36. } elseif (Webauthn::enabled($user)) {
  37. // If WebAuthn is enabled then return currently unsupported message
  38. return response()->json([
  39. 'error' => 'WebAuthn authentication is not currently supported from the extension or mobile apps, please use an API key to login instead',
  40. ], 403);
  41. }
  42. // If the user doesn't use 2FA then return the new API key
  43. return response()->json([
  44. 'api_key' => explode('|', $user->createToken($request->device_name)->plainTextToken, 2)[1],
  45. ]);
  46. }
  47. public function mfa(ApiAuthenticationMfaRequest $request)
  48. {
  49. try {
  50. $mfaKey = Crypt::decryptString($request->mfa_key);
  51. } catch (DecryptException $e) {
  52. return response()->json([
  53. 'error' => 'Invalid mfa_key',
  54. ], 401);
  55. }
  56. $parts = explode('|', $mfaKey, 3);
  57. $user = User::find($parts[0]);
  58. if (! $user || $parts[1] !== config('anonaddy.secret')) {
  59. return response()->json([
  60. 'error' => 'Invalid mfa_key',
  61. ], 401);
  62. }
  63. // Check if the mfa_key has expired
  64. if (Carbon::now()->getTimestamp() > $parts[2]) {
  65. return response()->json([
  66. 'error' => 'mfa_key expired, please request a new one at /api/auth/login',
  67. ], 401);
  68. }
  69. $google2fa = new Google2FA();
  70. $lastTimeStamp = Cache::get('2fa_ts:'.$user->id);
  71. $timestamp = $google2fa->verifyKeyNewer($user->two_factor_secret, $request->otp, $lastTimeStamp);
  72. if (! $timestamp) {
  73. return response()->json([
  74. 'error' => 'The \'One Time Password\' typed was wrong',
  75. ], 401);
  76. }
  77. if (is_int($timestamp)) {
  78. Cache::put('2fa_ts:'.$user->id, $timestamp, now()->addMinutes(5));
  79. }
  80. return response()->json([
  81. 'api_key' => explode('|', $user->createToken($request->device_name)->plainTextToken, 2)[1],
  82. ]);
  83. }
  84. }