-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
staging Implement social auth api and refactor code base
- Loading branch information
1 parent
b788cb3
commit 64eb680
Showing
20 changed files
with
708 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<?php | ||
|
||
namespace App\Enums; | ||
|
||
use App\Traits\EnumToArrayTrait; | ||
|
||
enum AuthProviderType: string | ||
{ | ||
use EnumToArrayTrait; | ||
|
||
case WALLET = 'wallet'; | ||
case GOOGLE = 'google'; | ||
case TWITTER = 'twitter'; | ||
case DISCORD = 'discord'; | ||
case GITHUB = 'github'; | ||
} |
144 changes: 144 additions & 0 deletions
144
application/app/Http/Controllers/API/AuthController.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
<?php | ||
|
||
namespace App\Http\Controllers\API; | ||
|
||
use App\Enums\AuthProviderType; | ||
use App\Http\Controllers\Controller; | ||
use App\Models\Project; | ||
use App\Models\ProjectAccount; | ||
use App\Traits\GEOBlockTrait; | ||
use App\Traits\IPHelperTrait; | ||
use Illuminate\Http\JsonResponse; | ||
use Illuminate\Http\RedirectResponse; | ||
use Illuminate\Http\Request; | ||
use Illuminate\Support\Facades\Cache; | ||
use Laravel\Socialite\Facades\Socialite; | ||
use Throwable; | ||
|
||
class AuthController extends Controller | ||
{ | ||
use IPHelperTrait, GEOBlockTrait; | ||
|
||
public function providers(): JsonResponse | ||
{ | ||
// Return supported auth providers | ||
return response() | ||
->json(AuthProviderType::values()); | ||
} | ||
|
||
public function init(string $publicApiKey, string $authProvider, Request $request): RedirectResponse|JsonResponse | ||
{ | ||
// Validate requested auth provider | ||
if (!in_array($authProvider, AuthProviderType::values(), true)) { | ||
return response()->json([ | ||
'error' => __('Bad Request'), | ||
'reason' => __(':currentProvider provider is not valid, supported providers are: :supportedProviders', [ | ||
'currentProvider' => $authProvider, | ||
'supportedProviders' => implode(', ', AuthProviderType::values()), | ||
]), | ||
], 400); | ||
} | ||
|
||
// Check if reference is provided in the request | ||
if (empty($request->get('reference'))) { | ||
return response()->json([ | ||
'error' => __('Bad Request'), | ||
'reason' => __('The reference query string parameter is missing or empty'), | ||
], 400); | ||
} | ||
|
||
// Load project by public api key | ||
$project = Project::query() | ||
->where('public_api_key', $publicApiKey) | ||
->first(); | ||
|
||
// Check if project exists | ||
if (!$project) { | ||
return response()->json([ | ||
'error' => __('Unauthorized'), | ||
'reason' => __('Invalid project public api key'), | ||
], 401); | ||
} | ||
|
||
// Check if this request should be geo-blocked | ||
if ($this->isGEOBlocked($project, $request)) { | ||
return response()->json([ | ||
'error' => __('Unauthorized'), | ||
'reason' => __('Accept not permitted'), | ||
], 401); | ||
} | ||
|
||
// Setup project account | ||
$projectAccount = ProjectAccount::query() | ||
->where('project_id', $project->id) | ||
->where('reference', $request->get('reference')) | ||
->where('auth_provider', $authProvider) | ||
->first(); | ||
if (!$projectAccount) { | ||
$projectAccount = new ProjectAccount; | ||
$projectAccount->fill([ | ||
'project_id' => $project->id, | ||
'reference' => $request->get('reference'), | ||
'auth_provider' => $authProvider, | ||
]); | ||
$projectAccount->save(); | ||
} | ||
|
||
// Attach project account to hashed user ip in cache for 5 minutes (needed during social auth callback) | ||
Cache::put( | ||
sprintf('project-account:%s', md5($this->getIP($request))), | ||
$projectAccount->id, | ||
300, | ||
); | ||
|
||
// TODO: Handle wallet auth differently | ||
|
||
// Redirect to social auth | ||
return Socialite::driver($authProvider) | ||
->redirect(); | ||
} | ||
|
||
public function check(Request $request): JsonResponse | ||
{ | ||
try { | ||
|
||
// Load project account by reference | ||
$projectAccount = ProjectAccount::query() | ||
->where('reference', $request->get('reference')) | ||
->first(); | ||
if (!$projectAccount) { | ||
return response()->json([ | ||
'error' => __('Not Found'), | ||
'reason' => __('Could not find project account by reference'), | ||
], 404); | ||
} | ||
|
||
// Success | ||
return response()->json([ | ||
'reference' => $projectAccount->reference, | ||
'auth_provider' => $projectAccount->auth_provider, | ||
'auth_provider_id' => $projectAccount->auth_provider_id, | ||
'auth_name' => $projectAccount->auth_name, | ||
'auth_email' => $projectAccount->auth_email, | ||
'auth_avatar' => $projectAccount->auth_avatar, | ||
'auth_country_code' => $projectAccount->auth_country_code, | ||
'authenticated_at' => $projectAccount->authenticated_at->toDateTimeString(), | ||
'is_authenticated' => !empty($projectAccount->authenticated_at), | ||
]); | ||
|
||
} catch (Throwable $exception) { | ||
|
||
// Log exception | ||
$this->logException('Failed to handle auth check', $exception, [ | ||
'request' => $request->toArray(), | ||
]); | ||
|
||
// Handle error | ||
return response()->json([ | ||
'error' => __('Internal Server Error'), | ||
'reason' => __('An unknown error occurred, please notify server administrator'), | ||
], 500); | ||
|
||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
81 changes: 81 additions & 0 deletions
81
application/app/Http/Controllers/SocialAuthCallbackController.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
<?php | ||
|
||
namespace App\Http\Controllers; | ||
|
||
use App\Enums\AuthProviderType; | ||
use App\Models\ProjectAccount; | ||
use App\Traits\GEOBlockTrait; | ||
use App\Traits\IPHelperTrait; | ||
use App\Traits\LogExceptionTrait; | ||
use Illuminate\Http\Request; | ||
use Illuminate\Support\Facades\Cache; | ||
use Laravel\Socialite\Facades\Socialite; | ||
use Throwable; | ||
|
||
class SocialAuthCallbackController extends Controller | ||
{ | ||
use LogExceptionTrait, IPHelperTrait, GEOBlockTrait; | ||
|
||
public function handle(string $authProvider, Request $request): void | ||
{ | ||
try { | ||
|
||
// Validate requested auth provider | ||
if (!in_array($authProvider, AuthProviderType::values(), true)) { | ||
exit(__(':currentProvider provider is not valid, supported providers are: :supportedProviders', [ | ||
'currentProvider' => $authProvider, | ||
'supportedProviders' => implode(', ', AuthProviderType::values()), | ||
])); | ||
} | ||
|
||
// Retrieve project account | ||
$projectAccountId = Cache::get(sprintf('project-account:%s', md5($this->getIP($request)))); | ||
if (empty($projectAccountId)) { | ||
exit(__('Login attempt session expired, please try again')); | ||
} | ||
$projectAccount = ProjectAccount::query() | ||
->where('id', $projectAccountId) | ||
->with('project') | ||
->first(); | ||
if (!$projectAccount) { | ||
exit(__('Login attempt session not found, please try again')); | ||
} | ||
|
||
// Check if this request should be geo-blocked | ||
if ($this->isGEOBlocked($projectAccount->project, $request)) { | ||
exit(__('Accept not permitted')); | ||
} | ||
|
||
// Load social user | ||
$socialUser = null; | ||
try { | ||
$socialUser = Socialite::driver($authProvider)->user(); | ||
} catch (Throwable) {} | ||
if (!$socialUser) { | ||
exit(__('Failed to authenticate via :authProvider, please try again', ['authProvider' => $authProvider])); | ||
} | ||
|
||
// Update project account with social account info | ||
$projectAccount->update([ | ||
'auth_provider_id' => $socialUser->id, | ||
'auth_name' => $socialUser->getName(), | ||
'auth_email' => $socialUser->getEmail(), | ||
'auth_avatar' => $socialUser->getAvatar(), | ||
'auth_country_code' => $this->getIPCountryCode($request), | ||
'authenticated_at' => now(), | ||
]); | ||
|
||
// Success | ||
exit(__('You have successfully logged in via :authProvider', ['authProvider' => $authProvider])); | ||
|
||
} catch (Throwable $exception) { | ||
|
||
// Handle exception | ||
$this->logException('Failed to handle social auth callback', $exception); | ||
|
||
// Display generic error message | ||
exit(__('Failed to authenticate via :authProvider, please try again', ['authProvider' => $authProvider])); | ||
|
||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
<?php | ||
|
||
namespace App\Http\Middleware; | ||
|
||
use App\Models\Project; | ||
use Closure; | ||
use Illuminate\Http\JsonResponse; | ||
use Illuminate\Http\Request; | ||
use Symfony\Component\HttpFoundation\Response; | ||
|
||
class ProjectAPIKeyAuth | ||
{ | ||
/** | ||
* Handle an incoming request. | ||
* | ||
* @param Request $request | ||
* @param Closure $next | ||
* @return Response|JsonResponse | ||
*/ | ||
public function handle(Request $request, Closure $next): Response|JsonResponse | ||
{ | ||
$project = Project::query() | ||
->where('public_api_key', $request->header('x-public-api-key')) | ||
->first(); | ||
|
||
if ($project && $project->private_api_key === $request->header('x-private-api-key')) { | ||
return $next($request); | ||
} | ||
|
||
return response()->json([ | ||
'error' => __('Unauthorized'), | ||
'reason' => __('Invalid project public/private api key'), | ||
], 401); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.