Separated laravel app to its own folder (#540)

This commit is contained in:
Julien Nahum
2024-08-26 18:24:56 +02:00
committed by GitHub
parent 39b8df5eed
commit 5bd1dda504
546 changed files with 124 additions and 143 deletions

View File

@@ -0,0 +1,120 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\License;
use App\Models\User;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Http;
class AppSumoAuthController extends Controller
{
use AuthenticatesUsers;
public function handleCallback(Request $request)
{
if (! $code = $request->code) {
return response()->json(['message' => 'Healthy'], 200);
}
$accessToken = $this->retrieveAccessToken($code);
$license = $this->fetchOrCreateLicense($accessToken);
// If user connected, attach license
if (Auth::check()) {
return $this->attachLicense($license);
}
// otherwise start login flow by passing the encrypted license key id
if (is_null($license->user_id)) {
return redirect(front_url('/register?appsumo_license='.encrypt($license->id)));
}
return redirect(front_url('/register?appsumo_error=1'));
}
private function retrieveAccessToken(string $requestCode): string
{
return Http::withHeaders([
'Content-type' => 'application/json',
])->post('https://appsumo.com/openid/token/', [
'grant_type' => 'authorization_code',
'code' => $requestCode,
'redirect_uri' => route('appsumo.callback'),
'client_id' => config('services.appsumo.client_id'),
'client_secret' => config('services.appsumo.client_secret'),
])->throw()->json('access_token');
}
private function fetchOrCreateLicense(string $accessToken): License
{
// Fetch license from API
$licenseKey = Http::get('https://appsumo.com/openid/license_key/?access_token='.$accessToken)
->throw()
->json('license_key');
// Fetch or create license model
$license = License::where('license_provider', 'appsumo')->where('license_key', $licenseKey)->first();
if (! $license) {
$licenseData = Http::withHeaders([
'X-AppSumo-Licensing-Key' => config('services.appsumo.api_key'),
])->get('https://api.licensing.appsumo.com/v2/licenses/'.$licenseKey)->json();
// Create new license
$license = License::create([
'license_key' => $licenseKey,
'license_provider' => 'appsumo',
'status' => $licenseData['status'] === 'active' ? License::STATUS_ACTIVE : License::STATUS_INACTIVE,
'meta' => $licenseData,
]);
}
return $license;
}
private function attachLicense(License $license)
{
if (! Auth::check()) {
throw new AuthenticationException('User not authenticated');
}
// Attach license if not already attached
if (is_null($license->user_id)) {
$license->user_id = Auth::id();
$license->save();
return redirect(front_url('/home?appsumo_connect=1'));
}
// Licensed already attached
return redirect(front_url('/home?appsumo_error=1'));
}
/**
* @return string|null
*
* Returns null if no license found
* Returns true if license was found and attached
* Returns false if there was an error (license not found or already attached)
*/
public static function registerWithLicense(User $user, ?string $licenseHash): ?bool
{
if (! $licenseHash) {
return null;
}
$licenseId = decrypt($licenseHash);
$license = License::find($licenseId);
if ($license && is_null($license->user_id)) {
$license->user_id = $user->id;
$license->save();
return true;
}
return false;
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Http\Request;
class ForgotPasswordController extends Controller
{
use SendsPasswordResetEmails;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
/**
* Get the response for a successful password reset link.
*
* @param string $response
* @return \Illuminate\Http\RedirectResponse
*/
protected function sendResetLinkResponse(Request $request, $response)
{
return ['status' => trans($response)];
}
/**
* Get the response for a failed password reset link.
*
* @param string $response
* @return \Illuminate\Http\RedirectResponse
*/
protected function sendResetLinkFailedResponse(Request $request, $response)
{
return response()->json(['email' => trans($response)], 400);
}
}

View File

@@ -0,0 +1,109 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Exceptions\VerifyEmailException;
use App\Http\Controllers\Controller;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
class LoginController extends Controller
{
use AuthenticatesUsers;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
}
/**
* Attempt to log the user into the application.
*
* @return bool
*/
protected function attemptLogin(Request $request)
{
$token = $this->guard()->attempt($this->credentials($request));
if (! $token) {
return false;
}
$user = $this->guard()->user();
if ($user instanceof MustVerifyEmail && ! $user->hasVerifiedEmail()) {
return false;
}
$this->guard()->setToken($token);
return true;
}
/**
* Get the needed authorization credentials from the request.
*
* @return array
*/
protected function credentials(Request $request)
{
return [
$this->username() => strtolower($request->get($this->username())),
'password' => $request->password,
];
}
/**
* Send the response after the user was authenticated.
*
* @return \Illuminate\Http\JsonResponse
*/
protected function sendLoginResponse(Request $request)
{
$this->clearLoginAttempts($request);
$token = (string) $this->guard()->getToken();
$expiration = $this->guard()->getPayload()->get('exp');
return response()->json([
'token' => $token,
'token_type' => 'bearer',
'expires_in' => $expiration - time(),
]);
}
/**
* Get the failed login response instance.
*
* @return \Illuminate\Http\JsonResponse
*
* @throws \Illuminate\Validation\ValidationException
*/
protected function sendFailedLoginResponse(Request $request)
{
$user = $this->guard()->user();
if ($user instanceof MustVerifyEmail && ! $user->hasVerifiedEmail()) {
throw VerifyEmailException::forUser($user);
}
throw ValidationException::withMessages([
$this->username() => [trans('auth.failed')],
]);
}
/**
* Log the user out of the application.
*
* @return \Illuminate\Http\Response
*/
public function logout(Request $request)
{
$this->guard()->logout();
}
}

View File

@@ -0,0 +1,146 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Integrations\OAuth\OAuthProviderService;
use App\Models\OAuthProvider;
use App\Models\User;
use App\Models\Workspace;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
class OAuthController extends Controller
{
use AuthenticatesUsers;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
config([
'services.github.redirect' => route('oauth.callback', 'github'),
]);
}
/**
* Redirect the user to the provider authentication page.
*
* @param string $provider
* @return \Illuminate\Http\RedirectResponse
*/
public function redirect(OAuthProviderService $provider)
{
return response()->json([
'url' => $provider->getDriver()->setRedirectUrl(config('services.google.auth_redirect'))->getRedirectUrl()
]);
}
/**
* Obtain the user information from the provider.
*
* @param string $driver
* @return \Illuminate\Http\Response
*/
public function handleCallback(OAuthProviderService $provider)
{
try {
$driverUser = $provider->getDriver()->setRedirectUrl(config('services.google.auth_redirect'))->getUser();
} catch (\Exception $e) {
return $this->error([
"message" => "OAuth service failed to authenticate: " . $e->getMessage()
]);
}
$user = $this->findOrCreateUser($provider, $driverUser);
if (!$user) {
return $this->error([
"message" => "User not found."
]);
}
if ($user->has_registered) {
return $this->error([
"message" => "This email is already registered. Please sign in with your password."
]);
}
$this->guard()->setToken(
$token = $this->guard()->login($user)
);
return response()->json([
'token' => $token,
'token_type' => 'bearer',
'expires_in' => $this->guard()->getPayload()->get('exp') - time(),
'new_user' => $user->new_user
]);
}
/**
* @p aram \Laravel\Socialite\Contracts\User $socialiteUser
* @return \App\Models\User | null
*/
protected function findOrCreateUser($provider, $socialiteUser)
{
$oauthProvider = OAuthProvider::where('provider', $provider)
->where('provider_user_id', $socialiteUser->getId())
->first();
if ($oauthProvider) {
$oauthProvider->update([
'access_token' => $socialiteUser->token,
'refresh_token' => $socialiteUser->refreshToken,
]);
return $oauthProvider->user;
}
if (!$provider->getDriver()->canCreateUser()) {
return null;
}
$email = strtolower($socialiteUser->getEmail());
$user = User::whereEmail($email)->first();
if ($user) {
$user->has_registered = true;
return $user;
}
$user = User::create([
'name' => $socialiteUser->getName(),
'email' => $email,
'email_verified_at' => now(),
]);
// Create and sync workspace
$workspace = Workspace::create([
'name' => 'My Workspace',
'icon' => '🧪',
]);
$user->workspaces()->sync([
$workspace->id => [
'role' => User::ROLE_ADMIN,
],
], false);
$user->new_user = true;
OAuthProvider::create(
[
'user_id' => $user->id,
'provider' => $provider,
'provider_user_id' => $socialiteUser->getId(),
'access_token' => $socialiteUser->token,
'refresh_token' => $socialiteUser->refreshToken,
'name' => $socialiteUser->getName(),
'email' => $socialiteUser->getEmail(),
]
);
return $user;
}
}

View File

@@ -0,0 +1,139 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Http\Resources\UserResource;
use App\Models\User;
use App\Models\UserInvite;
use App\Models\Workspace;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
class RegisterController extends Controller
{
use RegistersUsers;
private ?bool $appsumoLicense = null;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
/**
* The user has been registered.
*
* @param \App\User $user
* @return \Illuminate\Http\JsonResponse
*/
protected function registered(Request $request, User $user)
{
if ($user instanceof MustVerifyEmail) {
return response()->json(['status' => trans('verification.sent')]);
}
return response()->json(array_merge(
(new UserResource($user))->toArray($request),
[
'appsumo_license' => $this->appsumoLicense,
]
));
}
/**
* Get a validator for an incoming registration request.
*
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'name' => 'required|max:255',
'email' => 'required|email:filter|max:255|unique:users|indisposable',
'password' => 'required|min:6|confirmed',
'hear_about_us' => 'required|string',
'agree_terms' => ['required', Rule::in([true])],
'appsumo_license' => ['nullable'],
'invite_token' => ['nullable', 'string'],
], [
'agree_terms' => 'Please agree with the terms and conditions.',
]);
}
/**
* Create a new user instance after a valid registration.
*/
protected function create(array $data)
{
$this->checkRegistrationAllowed($data);
[$workspace, $role] = $this->getWorkspaceAndRole($data);
$user = User::create([
'name' => $data['name'],
'email' => strtolower($data['email']),
'password' => bcrypt($data['password']),
'hear_about_us' => $data['hear_about_us'],
]);
// Add relation with user
$user->workspaces()->sync([
$workspace->id => [
'role' => $role,
],
], false);
$this->appsumoLicense = AppSumoAuthController::registerWithLicense($user, $data['appsumo_license'] ?? null);
return $user;
}
private function checkRegistrationAllowed(array $data)
{
if (config('app.self_hosted') && !array_key_exists('invite_token', $data) && (app()->environment() !== 'testing')) {
response()->json(['message' => 'Registration is not allowed in self host mode'], 400)->throwResponse();
}
}
private function getWorkspaceAndRole(array $data)
{
if (!array_key_exists('invite_token', $data)) {
return [
Workspace::create([
'name' => 'My Workspace',
'icon' => '🧪',
]),
User::ROLE_ADMIN
];
}
$userInvite = UserInvite::where('email', $data['email'])
->where('token', $data['invite_token'])
->first();
if (!$userInvite) {
response()->json(['message' => 'Invite token is invalid.'], 400)->throwResponse();
}
if ($userInvite->hasExpired()) {
response()->json(['message' => 'Invite token has expired.'], 400)->throwResponse();
}
if ($userInvite->status == UserInvite::ACCEPTED_STATUS) {
response()->json(['message' => 'Invite is already accepted.'], 400)->throwResponse();
}
$userInvite->markAsAccepted();
return [
$userInvite->workspace,
$userInvite->role,
];
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Http\Request;
class ResetPasswordController extends Controller
{
use ResetsPasswords;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
/**
* Get the response for a successful password reset.
*
* @param string $response
* @return \Illuminate\Http\RedirectResponse
*/
protected function sendResetResponse(Request $request, $response)
{
return ['status' => trans($response)];
}
/**
* Get the response for a failed password reset.
*
* @param string $response
* @return \Illuminate\Http\RedirectResponse
*/
protected function sendResetFailedResponse(Request $request, $response)
{
return response()->json(['email' => trans($response)], 400);
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Http\Resources\UserResource;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class UserController extends Controller
{
/**
* Get authenticated user.
*/
public function current(Request $request)
{
return new UserResource($request->user());
}
public function deleteAccount()
{
$this->middleware('auth');
if (Auth::user()->admin) {
return $this->error([
'message' => 'Cannot delete an admin. Stay with us 🙏',
]);
}
Auth::user()->delete();
return $this->success([
'message' => 'User deleted.',
]);
}
}

View File

@@ -0,0 +1,80 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Auth\Events\Verified;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\URL;
use Illuminate\Validation\ValidationException;
class VerificationController extends Controller
{
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('throttle:6,1')->only('verify', 'resend');
}
/**
* Mark the user's email address as verified.
*
* @param \App\User $user
* @return \Illuminate\Http\JsonResponse
*/
public function verify(Request $request, User $user)
{
if (! URL::hasValidSignature($request)) {
return response()->json([
'status' => trans('verification.invalid'),
], 400);
}
if ($user->hasVerifiedEmail()) {
return response()->json([
'status' => trans('verification.already_verified'),
], 400);
}
$user->markEmailAsVerified();
event(new Verified($user));
return response()->json([
'status' => trans('verification.verified'),
]);
}
/**
* Resend the email verification notification.
*
* @return \Illuminate\Http\JsonResponse
*/
public function resend(Request $request)
{
$this->validate($request, ['email' => 'required|email']);
$user = User::where('email', $request->email)->first();
if (is_null($user)) {
throw ValidationException::withMessages([
'email' => [trans('verification.user')],
]);
}
if ($user->hasVerifiedEmail()) {
throw ValidationException::withMessages([
'email' => [trans('verification.already_verified')],
]);
}
$user->sendEmailVerificationNotification();
return response()->json(['status' => trans('verification.sent')]);
}
}