Some checks failed
continuous-integration/drone/push Build is failing
- Complete GGZ Ecademy Laravel backend application - RESTful API for learning products, members, filters - Authentication and authorization system - Database migrations and seeders - Custom CRUD generator commands - Email notification system - Integration with frontend applications
199 lines
6.3 KiB
PHP
199 lines
6.3 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Http\Controllers\Response\ApiResponse;
|
|
use Illuminate\Http\Request;
|
|
use App\Services\UserService;
|
|
use App\Services\MemberService;
|
|
use Illuminate\Support\Carbon;
|
|
use App\Http\Requests\User\Login;
|
|
use Illuminate\Support\Facades\Hash;
|
|
use App\Http\Resources\UserLoggedResource;
|
|
use App\Repositories\Role;
|
|
use Redirect;
|
|
use GuzzleHttp\Exception\ClientException;
|
|
use Illuminate\Support\Facades\Mail;
|
|
use Laravel\Sanctum\PersonalAccessToken;
|
|
use Illuminate\Support\Facades\Log;
|
|
class AuthController extends Controller
|
|
{
|
|
private $userService;
|
|
private $memberService;
|
|
|
|
public function __construct(
|
|
UserService $userService,
|
|
MemberService $memberService
|
|
) {
|
|
|
|
$this->userService = $userService;
|
|
$this->memberService = $memberService;
|
|
|
|
$this->middleware('auth:sanctum', [
|
|
'except' => [
|
|
'get_sso_data',
|
|
'login',
|
|
'signup',
|
|
'sso',
|
|
'status',
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function login(Login $request)
|
|
{
|
|
if (empty($request->token)) {
|
|
$user = $this->userService->getByEmailWith($request->email, ['roles']);
|
|
|
|
if (!$user || !Hash::check($request->password, $user->password)) {
|
|
return response(['error' => ['The provided credentials are incorrect.']]);
|
|
}
|
|
|
|
} else {
|
|
$client = new \GuzzleHttp\Client(['base_uri' => static::determineIdpUri()]);
|
|
|
|
try {
|
|
$response = $client->request('POST', 'protocol/openid-connect/token', [
|
|
'form_params' => [
|
|
'code' => $request->token,
|
|
'client_id' => config('sso.client_id'),
|
|
'client_secret' => config('sso.secret'),
|
|
'redirect_uri' => config('sso.redirect_uri'),
|
|
'scope' => 'openid profile',
|
|
'grant_type' => 'authorization_code'
|
|
]
|
|
]);
|
|
|
|
$token = json_decode($response->getBody(), true)['id_token'];
|
|
|
|
$decodedToken = \Firebase\JWT\JWT::decode($token, $this->getJwks(), false);
|
|
|
|
} catch (ClientException $e) {
|
|
$html = 'Login faild with token:' . $request->token;
|
|
Mail::send([], [], function ($message) use ($html) {
|
|
$message
|
|
->to('joris@ggzecademy.nl')
|
|
->subject('Login failed via SSO')
|
|
->setBody($html, 'text/html');
|
|
});
|
|
}
|
|
|
|
|
|
if (empty($decodedToken->kg) || !stristr($decodedToken->kg, 'KG')) {
|
|
return response(['error' => ['The provided credentials are incorrect.']]);
|
|
}
|
|
|
|
$organization_id = str_replace('KG_', '', $decodedToken->kg);
|
|
|
|
$member = $this->memberService->get($organization_id);
|
|
|
|
$user = $this->userService->getByEmailWith($decodedToken->email, ['roles']);
|
|
|
|
if (!$user) {
|
|
$new_user = [
|
|
'first_name' => $decodedToken->given_name,
|
|
'last_name' => $decodedToken->family_name,
|
|
'email' => $decodedToken->email,
|
|
'password' => 'aT@5*Wb*W7gseVhC',
|
|
];
|
|
|
|
$user = $this->userService->save($new_user, false);
|
|
$user->roles()->attach(Role::whereName('user')->firstOrFail());
|
|
}
|
|
|
|
$member->users()->syncWithoutDetaching([$user->id]);
|
|
$member->save();
|
|
}
|
|
|
|
$user->last_login_at = $user->logged_at;
|
|
$user->last_login_ip = $request->getClientIp();
|
|
$user->logged_at = Carbon::now();
|
|
$user->save();
|
|
|
|
$token = $user->createToken('cms-token')->plainTextToken;
|
|
|
|
$response = ['token' => $token];
|
|
|
|
return response($response, 201);
|
|
}
|
|
|
|
private function getJwks(): array
|
|
{
|
|
$jwkData = json_decode(
|
|
file_get_contents(static::determineIdpUri() . 'protocol/openid-connect/certs'),
|
|
true
|
|
);
|
|
|
|
return \Firebase\JWT\JWK::parseKeySet($jwkData);
|
|
}
|
|
|
|
public function logout(Request $request)
|
|
{
|
|
$request->user()->tokens()->delete();
|
|
|
|
return response()->json('Logged-out', 201);
|
|
}
|
|
|
|
public function status(Request $request)
|
|
{
|
|
$bearerToken = $request->bearerToken();
|
|
|
|
if (is_null($bearerToken)) {
|
|
return ApiResponse::error(
|
|
new Error\AuthController\MissingBearerTokenError(),
|
|
);
|
|
} else {
|
|
$accessToken = PersonalAccessToken::findToken($bearerToken);
|
|
|
|
return ApiResponse::success([
|
|
'authenticated' => $accessToken instanceof PersonalAccessToken,
|
|
'user' => $accessToken ? $accessToken->tokenable->email : null,
|
|
]);
|
|
}
|
|
}
|
|
|
|
public function me(Request $request)
|
|
{
|
|
return response()->json(new UserLoggedResource($request->user()));
|
|
}
|
|
|
|
public function sso()
|
|
{
|
|
return Redirect::to(static::buildSsoEndpointUri());
|
|
}
|
|
|
|
private static function buildSsoEndpointUri(): string
|
|
{
|
|
$endpointQuery = http_build_query([
|
|
'client_id' => config('sso.client_id'),
|
|
'redirect_uri' => config('sso.redirect_uri'),
|
|
'scope' => 'openid profile',
|
|
'response_type' => 'code',
|
|
'nonce' => md5(rand()), // TODO: proper nonce handling
|
|
], '', '&', PHP_QUERY_RFC3986);
|
|
$endpointQuery = str_replace('%2B', '+', $endpointQuery);
|
|
|
|
return sprintf('%s?%s', static::determineIdpUri('protocol/openid-connect/auth'), $endpointQuery);
|
|
}
|
|
|
|
private static function determineIdpUri(string $path = ''): string
|
|
{
|
|
$baseUri = rtrim(config('sso.idp_base_uri'), '/');
|
|
|
|
return sprintf('%s/%s', $baseUri, $path);
|
|
}
|
|
|
|
public function get_sso_data() {
|
|
$client = new \GuzzleHttp\Client([
|
|
'base_uri' => static::determineIdpUri(),
|
|
'headers' => [
|
|
'Authorization' => sprintf('Bearer %s', $_GET['token']),
|
|
'Accept' => 'application/json',
|
|
],
|
|
]);
|
|
|
|
$response = $client->request('GET', 'oidc/userinfo.php');
|
|
|
|
return response($response->getBody()->getContents(), 201);
|
|
}
|
|
} |