3 | {{ __('Forgot your password? No problem. Just let us know your email address and we will email you a password reset link that will allow you to choose a new one.') }}
4 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/resources/js/bootstrap.js:
--------------------------------------------------------------------------------
1 | /**
2 | * We'll load the axios HTTP library which allows us to easily issue requests
3 | * to our Laravel back-end. This library automatically handles sending the
4 | * CSRF token as a header based on the value of the "XSRF" token cookie.
5 | */
6 |
7 | import axios from 'axios';
8 | window.axios = axios;
9 |
10 | window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
11 |
12 | /**
13 | * Echo exposes an expressive API for subscribing to channels and listening
14 | * for events that are broadcast by Laravel. Echo and event broadcasting
15 | * allows your team to easily build robust real-time web applications.
16 | */
17 |
18 | // import Echo from 'laravel-echo';
19 |
20 | // import Pusher from 'pusher-js';
21 | // window.Pusher = Pusher;
22 |
23 | // window.Echo = new Echo({
24 | // broadcaster: 'pusher',
25 | // key: import.meta.env.VITE_PUSHER_APP_KEY,
26 | // cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER ?? 'mt1',
27 | // wsHost: import.meta.env.VITE_PUSHER_HOST ? import.meta.env.VITE_PUSHER_HOST : `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`,
28 | // wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80,
29 | // wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443,
30 | // forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https',
31 | // enabledTransports: ['ws', 'wss'],
32 | // });
33 |
--------------------------------------------------------------------------------
/resources/views/auth/verify-email.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ __('Thanks for signing up! Before getting started, could you verify your email address by clicking on the link we just emailed to you? If you didn\'t receive the email, we will gladly send you another.') }}
4 |
8 | {{ __('Once your account is deleted, all of its resources and data will be permanently deleted. Before deleting your account, please download any data or information that you wish to retain.') }}
9 |
10 |
11 |
12 | {{ __('Delete Account') }}
16 |
17 |
18 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/resources/views/auth/register.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
52 |
53 |
--------------------------------------------------------------------------------
/config/broadcasting.php:
--------------------------------------------------------------------------------
1 | env('BROADCAST_DRIVER', 'null'),
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Broadcast Connections
23 | |--------------------------------------------------------------------------
24 | |
25 | | Here you may define all of the broadcast connections that will be used
26 | | to broadcast events to other systems or over websockets. Samples of
27 | | each available type of connection are provided inside this array.
28 | |
29 | */
30 |
31 | 'connections' => [
32 |
33 | 'pusher' => [
34 | 'driver' => 'pusher',
35 | 'key' => env('PUSHER_APP_KEY'),
36 | 'secret' => env('PUSHER_APP_SECRET'),
37 | 'app_id' => env('PUSHER_APP_ID'),
38 | 'options' => [
39 | 'cluster' => env('PUSHER_APP_CLUSTER'),
40 | 'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com',
41 | 'port' => env('PUSHER_PORT', 443),
42 | 'scheme' => env('PUSHER_SCHEME', 'https'),
43 | 'encrypted' => true,
44 | 'useTLS' => env('PUSHER_SCHEME', 'https') === 'https',
45 | ],
46 | 'client_options' => [
47 | // Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
48 | ],
49 | ],
50 |
51 | 'ably' => [
52 | 'driver' => 'ably',
53 | 'key' => env('ABLY_KEY'),
54 | ],
55 |
56 | 'redis' => [
57 | 'driver' => 'redis',
58 | 'connection' => 'default',
59 | ],
60 |
61 | 'log' => [
62 | 'driver' => 'log',
63 | ],
64 |
65 | 'null' => [
66 | 'driver' => 'null',
67 | ],
68 |
69 | ],
70 |
71 | ];
72 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/NewPasswordController.php:
--------------------------------------------------------------------------------
1 | $request]);
23 | }
24 |
25 | /**
26 | * Handle an incoming new password request.
27 | *
28 | * @throws \Illuminate\Validation\ValidationException
29 | */
30 | public function store(Request $request): RedirectResponse
31 | {
32 | $request->validate([
33 | 'token' => ['required'],
34 | 'email' => ['required', 'email'],
35 | 'password' => ['required', 'confirmed', Rules\Password::defaults()],
36 | ]);
37 |
38 | // Here we will attempt to reset the user's password. If it is successful we
39 | // will update the password on an actual user model and persist it to the
40 | // database. Otherwise we will parse the error and return the response.
41 | $status = Password::reset(
42 | $request->only('email', 'password', 'password_confirmation', 'token'),
43 | function ($user) use ($request) {
44 | $user->forceFill([
45 | 'password' => Hash::make($request->password),
46 | 'remember_token' => Str::random(60),
47 | ])->save();
48 |
49 | event(new PasswordReset($user));
50 | }
51 | );
52 |
53 | // If the password was successfully reset, we will redirect the user back to
54 | // the application's home authenticated view. If there is an error we can
55 | // redirect them back to where they came from with their error message.
56 | return $status == Password::PASSWORD_RESET
57 | ? redirect()->route('login')->with('status', __($status))
58 | : back()->withInput($request->only('email'))
59 | ->withErrors(['email' => __($status)]);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/config/sanctum.php:
--------------------------------------------------------------------------------
1 | explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
19 | '%s%s',
20 | 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
21 | Sanctum::currentApplicationUrlWithPort()
22 | ))),
23 |
24 | /*
25 | |--------------------------------------------------------------------------
26 | | Sanctum Guards
27 | |--------------------------------------------------------------------------
28 | |
29 | | This array contains the authentication guards that will be checked when
30 | | Sanctum is trying to authenticate a request. If none of these guards
31 | | are able to authenticate the request, Sanctum will use the bearer
32 | | token that's present on an incoming request for authentication.
33 | |
34 | */
35 |
36 | 'guard' => ['web'],
37 |
38 | /*
39 | |--------------------------------------------------------------------------
40 | | Expiration Minutes
41 | |--------------------------------------------------------------------------
42 | |
43 | | This value controls the number of minutes until an issued token will be
44 | | considered expired. If this value is null, personal access tokens do
45 | | not expire. This won't tweak the lifetime of first-party sessions.
46 | |
47 | */
48 |
49 | 'expiration' => null,
50 |
51 | /*
52 | |--------------------------------------------------------------------------
53 | | Sanctum Middleware
54 | |--------------------------------------------------------------------------
55 | |
56 | | When authenticating your first-party SPA with Sanctum you may need to
57 | | customize some of the middleware Sanctum uses while processing the
58 | | request. You may change the middleware listed below as required.
59 | |
60 | */
61 |
62 | 'middleware' => [
63 | 'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
64 | 'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class,
65 | ],
66 |
67 | ];
68 |
--------------------------------------------------------------------------------
/app/Http/Requests/Auth/LoginRequest.php:
--------------------------------------------------------------------------------
1 |
26 | */
27 | public function rules(): array
28 | {
29 | return [
30 | 'email' => ['required', 'string', 'email'],
31 | 'password' => ['required', 'string'],
32 | ];
33 | }
34 |
35 | /**
36 | * Attempt to authenticate the request's credentials.
37 | *
38 | * @throws \Illuminate\Validation\ValidationException
39 | */
40 | public function authenticate(): void
41 | {
42 | $this->ensureIsNotRateLimited();
43 |
44 | if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) {
45 | RateLimiter::hit($this->throttleKey());
46 |
47 | throw ValidationException::withMessages([
48 | 'email' => trans('auth.failed'),
49 | ]);
50 | }
51 |
52 | RateLimiter::clear($this->throttleKey());
53 | }
54 |
55 | /**
56 | * Ensure the login request is not rate limited.
57 | *
58 | * @throws \Illuminate\Validation\ValidationException
59 | */
60 | public function ensureIsNotRateLimited(): void
61 | {
62 | if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
63 | return;
64 | }
65 |
66 | event(new Lockout($this));
67 |
68 | $seconds = RateLimiter::availableIn($this->throttleKey());
69 |
70 | throw ValidationException::withMessages([
71 | 'email' => trans('auth.throttle', [
72 | 'seconds' => $seconds,
73 | 'minutes' => ceil($seconds / 60),
74 | ]),
75 | ]);
76 | }
77 |
78 | /**
79 | * Get the rate limiting throttle key for the request.
80 | */
81 | public function throttleKey(): string
82 | {
83 | return Str::transliterate(Str::lower($this->input('email')).'|'.$this->ip());
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/config/filesystems.php:
--------------------------------------------------------------------------------
1 | env('FILESYSTEM_DISK', 'local'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Filesystem Disks
21 | |--------------------------------------------------------------------------
22 | |
23 | | Here you may configure as many filesystem "disks" as you wish, and you
24 | | may even configure multiple disks of the same driver. Defaults have
25 | | been set up for each driver as an example of the required values.
26 | |
27 | | Supported Drivers: "local", "ftp", "sftp", "s3"
28 | |
29 | */
30 |
31 | 'disks' => [
32 |
33 | 'local' => [
34 | 'driver' => 'local',
35 | 'root' => storage_path('app'),
36 | 'throw' => false,
37 | ],
38 |
39 | 'public' => [
40 | 'driver' => 'local',
41 | 'root' => storage_path('app/public'),
42 | 'url' => env('APP_URL').'/storage',
43 | 'visibility' => 'public',
44 | 'throw' => false,
45 | ],
46 |
47 | 's3' => [
48 | 'driver' => 's3',
49 | 'key' => env('AWS_ACCESS_KEY_ID'),
50 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
51 | 'region' => env('AWS_DEFAULT_REGION'),
52 | 'bucket' => env('AWS_BUCKET'),
53 | 'url' => env('AWS_URL'),
54 | 'endpoint' => env('AWS_ENDPOINT'),
55 | 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
56 | 'throw' => false,
57 | ],
58 |
59 | ],
60 |
61 | /*
62 | |--------------------------------------------------------------------------
63 | | Symbolic Links
64 | |--------------------------------------------------------------------------
65 | |
66 | | Here you may configure the symbolic links that will be created when the
67 | | `storage:link` Artisan command is executed. The array keys should be
68 | | the locations of the links and the values should be their targets.
69 | |
70 | */
71 |
72 | 'links' => [
73 | public_path('storage') => storage_path('app/public'),
74 | ],
75 |
76 | ];
77 |
--------------------------------------------------------------------------------
/routes/auth.php:
--------------------------------------------------------------------------------
1 | group(function () {
15 | Route::get('register', [RegisteredUserController::class, 'create'])
16 | ->name('register');
17 |
18 | Route::post('register', [RegisteredUserController::class, 'store']);
19 |
20 | Route::get('login', [AuthenticatedSessionController::class, 'create'])
21 | ->name('login');
22 |
23 | Route::post('login', [AuthenticatedSessionController::class, 'store']);
24 |
25 | Route::get('forgot-password', [PasswordResetLinkController::class, 'create'])
26 | ->name('password.request');
27 |
28 | Route::post('forgot-password', [PasswordResetLinkController::class, 'store'])
29 | ->name('password.email');
30 |
31 | Route::get('reset-password/{token}', [NewPasswordController::class, 'create'])
32 | ->name('password.reset');
33 |
34 | Route::post('reset-password', [NewPasswordController::class, 'store'])
35 | ->name('password.store');
36 | });
37 |
38 | Route::middleware('auth')->group(function () {
39 | Route::get('verify-email', EmailVerificationPromptController::class)
40 | ->name('verification.notice');
41 |
42 | Route::get('verify-email/{id}/{hash}', VerifyEmailController::class)
43 | ->middleware(['signed', 'throttle:6,1'])
44 | ->name('verification.verify');
45 |
46 | Route::post('email/verification-notification', [EmailVerificationNotificationController::class, 'store'])
47 | ->middleware('throttle:6,1')
48 | ->name('verification.send');
49 |
50 | Route::get('confirm-password', [ConfirmablePasswordController::class, 'show'])
51 | ->name('password.confirm');
52 |
53 | Route::post('confirm-password', [ConfirmablePasswordController::class, 'store']);
54 |
55 | Route::put('password', [PasswordController::class, 'update'])->name('password.update');
56 |
57 | Route::post('logout', [AuthenticatedSessionController::class, 'destroy'])
58 | ->name('logout');
59 | });
60 |
--------------------------------------------------------------------------------
/resources/views/components/application-logo.blade.php:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/tests/Feature/ProfileTest.php:
--------------------------------------------------------------------------------
1 | create();
16 |
17 | $response = $this
18 | ->actingAs($user)
19 | ->get('/profile');
20 |
21 | $response->assertOk();
22 | }
23 |
24 | public function test_profile_information_can_be_updated(): void
25 | {
26 | $user = User::factory()->create();
27 |
28 | $response = $this
29 | ->actingAs($user)
30 | ->patch('/profile', [
31 | 'name' => 'Test User',
32 | 'email' => 'test@example.com',
33 | ]);
34 |
35 | $response
36 | ->assertSessionHasNoErrors()
37 | ->assertRedirect('/profile');
38 |
39 | $user->refresh();
40 |
41 | $this->assertSame('Test User', $user->name);
42 | $this->assertSame('test@example.com', $user->email);
43 | $this->assertNull($user->email_verified_at);
44 | }
45 |
46 | public function test_email_verification_status_is_unchanged_when_the_email_address_is_unchanged(): void
47 | {
48 | $user = User::factory()->create();
49 |
50 | $response = $this
51 | ->actingAs($user)
52 | ->patch('/profile', [
53 | 'name' => 'Test User',
54 | 'email' => $user->email,
55 | ]);
56 |
57 | $response
58 | ->assertSessionHasNoErrors()
59 | ->assertRedirect('/profile');
60 |
61 | $this->assertNotNull($user->refresh()->email_verified_at);
62 | }
63 |
64 | public function test_user_can_delete_their_account(): void
65 | {
66 | $user = User::factory()->create();
67 |
68 | $response = $this
69 | ->actingAs($user)
70 | ->delete('/profile', [
71 | 'password' => 'password',
72 | ]);
73 |
74 | $response
75 | ->assertSessionHasNoErrors()
76 | ->assertRedirect('/');
77 |
78 | $this->assertGuest();
79 | $this->assertNull($user->fresh());
80 | }
81 |
82 | public function test_correct_password_must_be_provided_to_delete_account(): void
83 | {
84 | $user = User::factory()->create();
85 |
86 | $response = $this
87 | ->actingAs($user)
88 | ->from('/profile')
89 | ->delete('/profile', [
90 | 'password' => 'wrong-password',
91 | ]);
92 |
93 | $response
94 | ->assertSessionHasErrorsIn('userDeletion', 'password')
95 | ->assertRedirect('/profile');
96 |
97 | $this->assertNotNull($user->fresh());
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/app/Http/Kernel.php:
--------------------------------------------------------------------------------
1 |
15 | */
16 | protected $middleware = [
17 | // \App\Http\Middleware\TrustHosts::class,
18 | \App\Http\Middleware\TrustProxies::class,
19 | \Illuminate\Http\Middleware\HandleCors::class,
20 | \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
21 | \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
22 | \App\Http\Middleware\TrimStrings::class,
23 | \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
24 | ];
25 |
26 | /**
27 | * The application's route middleware groups.
28 | *
29 | * @var array>
30 | */
31 | protected $middlewareGroups = [
32 | 'web' => [
33 | \App\Http\Middleware\EncryptCookies::class,
34 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
35 | \Illuminate\Session\Middleware\StartSession::class,
36 | \Illuminate\View\Middleware\ShareErrorsFromSession::class,
37 | \App\Http\Middleware\VerifyCsrfToken::class,
38 | \Illuminate\Routing\Middleware\SubstituteBindings::class,
39 | ],
40 |
41 | 'api' => [
42 | // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
43 | \Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
44 | \Illuminate\Routing\Middleware\SubstituteBindings::class,
45 | ],
46 | ];
47 |
48 | /**
49 | * The application's middleware aliases.
50 | *
51 | * Aliases may be used instead of class names to conveniently assign middleware to routes and groups.
52 | *
53 | * @var array
54 | */
55 | protected $middlewareAliases = [
56 | 'auth' => \App\Http\Middleware\Authenticate::class,
57 | 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
58 | 'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
59 | 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
60 | 'can' => \Illuminate\Auth\Middleware\Authorize::class,
61 | 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
62 | 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
63 | 'precognitive' => \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
64 | 'signed' => \App\Http\Middleware\ValidateSignature::class,
65 | 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
66 | 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
67 | 'custom_domains' => \App\Http\Middleware\CustomDomains::class,
68 | ];
69 | }
70 |
--------------------------------------------------------------------------------
/resources/views/profile/partials/update-profile-information-form.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ __('Profile Information') }}
5 |
6 |
7 |
8 | {{ __("Update your account's profile information and email address.") }}
9 |
10 |
11 |
12 |
15 |
16 |
70 |
71 |
--------------------------------------------------------------------------------
/app/Http/Controllers/ProfileController.php:
--------------------------------------------------------------------------------
1 | $request->user(),
22 | ]);
23 | }
24 |
25 | public function show(Request $request): View
26 | {
27 | return view('profile.show', [
28 | 'user' => $request->user(),
29 | ]);
30 | }
31 |
32 | /**
33 | * Update the user's profile information.
34 | */
35 | public function update(ProfileUpdateRequest $request): RedirectResponse
36 | {
37 | $current_domain = $request->user()->custom_domain;
38 | $request->user()->fill($request->validated());
39 |
40 | if ($request->user()->isDirty('email')) {
41 | $request->user()->email_verified_at = null;
42 | }
43 |
44 | $apx = new Approximated;
45 |
46 | // These nested ifs below are gross but easier to understand for the example
47 |
48 | // did the custom domain field change?
49 | if($request->filled('custom_domain')){
50 | // was there a current domain to update in our DB?
51 | // If yes, we want to update on Approximated, not create.
52 | if ($current_domain) {
53 | // double check the vhost exists on Approximated
54 | $vhost_check = $apx->get_vhost($current_domain);
55 | if($vhost_check['success']){
56 | // It exists, update it
57 | $apx->update_vhost($current_domain, ['incoming_address' => $request->input('custom_domain')]);
58 | }else{
59 | // It doesn't exist, create it
60 | $apx->create_vhost($request->user()->incoming_address, env('APP_PRIMARY_DOMAIN'));
61 | }
62 | }else{
63 | // No previous custom domain, create one
64 | $apx->create_vhost($request->user()->incoming_address, env('APP_PRIMARY_DOMAIN'));
65 | }
66 | }elseif($current_domain){
67 | // They've blanked the custom domain, and there was one previously.
68 | // Delete the vhost on Approximated.
69 | $apx->delete_vhost($current_domain);
70 | }
71 |
72 | $request->user()->save();
73 |
74 |
75 | return Redirect::route('profile.edit')->with('status', 'profile-updated');
76 | }
77 |
78 | /**
79 | * Delete the user's account.
80 | */
81 | public function destroy(Request $request): RedirectResponse
82 | {
83 | $request->validateWithBag('userDeletion', [
84 | 'password' => ['required', 'current_password'],
85 | ]);
86 |
87 | $user = $request->user();
88 |
89 | Auth::logout();
90 |
91 | $user->delete();
92 |
93 | $request->session()->invalidate();
94 | $request->session()->regenerateToken();
95 |
96 | return Redirect::to('/');
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/resources/views/components/modal.blade.php:
--------------------------------------------------------------------------------
1 | @props([
2 | 'name',
3 | 'show' => false,
4 | 'maxWidth' => '2xl'
5 | ])
6 |
7 | @php
8 | $maxWidth = [
9 | 'sm' => 'sm:max-w-sm',
10 | 'md' => 'sm:max-w-md',
11 | 'lg' => 'sm:max-w-lg',
12 | 'xl' => 'sm:max-w-xl',
13 | '2xl' => 'sm:max-w-2xl',
14 | ][$maxWidth];
15 | @endphp
16 |
17 |
51 |
62 |
63 |
64 |
65 |
75 | {{ $slot }}
76 |
77 |
78 |
--------------------------------------------------------------------------------
/config/cache.php:
--------------------------------------------------------------------------------
1 | env('CACHE_DRIVER', 'file'),
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Cache Stores
23 | |--------------------------------------------------------------------------
24 | |
25 | | Here you may define all of the cache "stores" for your application as
26 | | well as their drivers. You may even define multiple stores for the
27 | | same cache driver to group types of items stored in your caches.
28 | |
29 | | Supported drivers: "apc", "array", "database", "file",
30 | | "memcached", "redis", "dynamodb", "octane", "null"
31 | |
32 | */
33 |
34 | 'stores' => [
35 |
36 | 'apc' => [
37 | 'driver' => 'apc',
38 | ],
39 |
40 | 'array' => [
41 | 'driver' => 'array',
42 | 'serialize' => false,
43 | ],
44 |
45 | 'database' => [
46 | 'driver' => 'database',
47 | 'table' => 'cache',
48 | 'connection' => null,
49 | 'lock_connection' => null,
50 | ],
51 |
52 | 'file' => [
53 | 'driver' => 'file',
54 | 'path' => storage_path('framework/cache/data'),
55 | 'lock_path' => storage_path('framework/cache/data'),
56 | ],
57 |
58 | 'memcached' => [
59 | 'driver' => 'memcached',
60 | 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
61 | 'sasl' => [
62 | env('MEMCACHED_USERNAME'),
63 | env('MEMCACHED_PASSWORD'),
64 | ],
65 | 'options' => [
66 | // Memcached::OPT_CONNECT_TIMEOUT => 2000,
67 | ],
68 | 'servers' => [
69 | [
70 | 'host' => env('MEMCACHED_HOST', '127.0.0.1'),
71 | 'port' => env('MEMCACHED_PORT', 11211),
72 | 'weight' => 100,
73 | ],
74 | ],
75 | ],
76 |
77 | 'redis' => [
78 | 'driver' => 'redis',
79 | 'connection' => 'cache',
80 | 'lock_connection' => 'default',
81 | ],
82 |
83 | 'dynamodb' => [
84 | 'driver' => 'dynamodb',
85 | 'key' => env('AWS_ACCESS_KEY_ID'),
86 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
87 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
88 | 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
89 | 'endpoint' => env('DYNAMODB_ENDPOINT'),
90 | ],
91 |
92 | 'octane' => [
93 | 'driver' => 'octane',
94 | ],
95 |
96 | ],
97 |
98 | /*
99 | |--------------------------------------------------------------------------
100 | | Cache Key Prefix
101 | |--------------------------------------------------------------------------
102 | |
103 | | When utilizing the APC, database, memcached, Redis, or DynamoDB cache
104 | | stores there might be other applications using the same cache. For
105 | | that reason, you may prefix every cache key to avoid collisions.
106 | |
107 | */
108 |
109 | 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'),
110 |
111 | ];
112 |
--------------------------------------------------------------------------------
/config/queue.php:
--------------------------------------------------------------------------------
1 | env('QUEUE_CONNECTION', 'sync'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Queue Connections
21 | |--------------------------------------------------------------------------
22 | |
23 | | Here you may configure the connection information for each server that
24 | | is used by your application. A default configuration has been added
25 | | for each back-end shipped with Laravel. You are free to add more.
26 | |
27 | | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
28 | |
29 | */
30 |
31 | 'connections' => [
32 |
33 | 'sync' => [
34 | 'driver' => 'sync',
35 | ],
36 |
37 | 'database' => [
38 | 'driver' => 'database',
39 | 'table' => 'jobs',
40 | 'queue' => 'default',
41 | 'retry_after' => 90,
42 | 'after_commit' => false,
43 | ],
44 |
45 | 'beanstalkd' => [
46 | 'driver' => 'beanstalkd',
47 | 'host' => 'localhost',
48 | 'queue' => 'default',
49 | 'retry_after' => 90,
50 | 'block_for' => 0,
51 | 'after_commit' => false,
52 | ],
53 |
54 | 'sqs' => [
55 | 'driver' => 'sqs',
56 | 'key' => env('AWS_ACCESS_KEY_ID'),
57 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
58 | 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
59 | 'queue' => env('SQS_QUEUE', 'default'),
60 | 'suffix' => env('SQS_SUFFIX'),
61 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
62 | 'after_commit' => false,
63 | ],
64 |
65 | 'redis' => [
66 | 'driver' => 'redis',
67 | 'connection' => 'default',
68 | 'queue' => env('REDIS_QUEUE', 'default'),
69 | 'retry_after' => 90,
70 | 'block_for' => null,
71 | 'after_commit' => false,
72 | ],
73 |
74 | ],
75 |
76 | /*
77 | |--------------------------------------------------------------------------
78 | | Job Batching
79 | |--------------------------------------------------------------------------
80 | |
81 | | The following options configure the database and table that store job
82 | | batching information. These options can be updated to any database
83 | | connection and table which has been defined by your application.
84 | |
85 | */
86 |
87 | 'batching' => [
88 | 'database' => env('DB_CONNECTION', 'mysql'),
89 | 'table' => 'job_batches',
90 | ],
91 |
92 | /*
93 | |--------------------------------------------------------------------------
94 | | Failed Queue Jobs
95 | |--------------------------------------------------------------------------
96 | |
97 | | These options configure the behavior of failed queue job logging so you
98 | | can control which database and table are used to store the jobs that
99 | | have failed. You may change them to any database / table you wish.
100 | |
101 | */
102 |
103 | 'failed' => [
104 | 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
105 | 'database' => env('DB_CONNECTION', 'mysql'),
106 | 'table' => 'failed_jobs',
107 | ],
108 |
109 | ];
110 |
--------------------------------------------------------------------------------
/config/mail.php:
--------------------------------------------------------------------------------
1 | env('MAIL_MAILER', 'smtp'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Mailer Configurations
21 | |--------------------------------------------------------------------------
22 | |
23 | | Here you may configure all of the mailers used by your application plus
24 | | their respective settings. Several examples have been configured for
25 | | you and you are free to add your own as your application requires.
26 | |
27 | | Laravel supports a variety of mail "transport" drivers to be used while
28 | | sending an e-mail. You will specify which one you are using for your
29 | | mailers below. You are free to add additional mailers as required.
30 | |
31 | | Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
32 | | "postmark", "log", "array", "failover"
33 | |
34 | */
35 |
36 | 'mailers' => [
37 | 'smtp' => [
38 | 'transport' => 'smtp',
39 | 'url' => env('MAIL_URL'),
40 | 'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
41 | 'port' => env('MAIL_PORT', 587),
42 | 'encryption' => env('MAIL_ENCRYPTION', 'tls'),
43 | 'username' => env('MAIL_USERNAME'),
44 | 'password' => env('MAIL_PASSWORD'),
45 | 'timeout' => null,
46 | 'local_domain' => env('MAIL_EHLO_DOMAIN'),
47 | ],
48 |
49 | 'ses' => [
50 | 'transport' => 'ses',
51 | ],
52 |
53 | 'mailgun' => [
54 | 'transport' => 'mailgun',
55 | // 'client' => [
56 | // 'timeout' => 5,
57 | // ],
58 | ],
59 |
60 | 'postmark' => [
61 | 'transport' => 'postmark',
62 | // 'client' => [
63 | // 'timeout' => 5,
64 | // ],
65 | ],
66 |
67 | 'sendmail' => [
68 | 'transport' => 'sendmail',
69 | 'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
70 | ],
71 |
72 | 'log' => [
73 | 'transport' => 'log',
74 | 'channel' => env('MAIL_LOG_CHANNEL'),
75 | ],
76 |
77 | 'array' => [
78 | 'transport' => 'array',
79 | ],
80 |
81 | 'failover' => [
82 | 'transport' => 'failover',
83 | 'mailers' => [
84 | 'smtp',
85 | 'log',
86 | ],
87 | ],
88 | ],
89 |
90 | /*
91 | |--------------------------------------------------------------------------
92 | | Global "From" Address
93 | |--------------------------------------------------------------------------
94 | |
95 | | You may wish for all e-mails sent by your application to be sent from
96 | | the same address. Here, you may specify a name and address that is
97 | | used globally for all e-mails that are sent by your application.
98 | |
99 | */
100 |
101 | 'from' => [
102 | 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
103 | 'name' => env('MAIL_FROM_NAME', 'Example'),
104 | ],
105 |
106 | /*
107 | |--------------------------------------------------------------------------
108 | | Markdown Mail Settings
109 | |--------------------------------------------------------------------------
110 | |
111 | | If you are using Markdown based email rendering, you may configure your
112 | | theme and component paths here, allowing you to customize the design
113 | | of the emails. Or, you may simply stick with the Laravel defaults!
114 | |
115 | */
116 |
117 | 'markdown' => [
118 | 'theme' => 'default',
119 |
120 | 'paths' => [
121 | resource_path('views/vendor/mail'),
122 | ],
123 | ],
124 |
125 | ];
126 |
--------------------------------------------------------------------------------
/config/auth.php:
--------------------------------------------------------------------------------
1 | [
17 | 'guard' => 'web',
18 | 'passwords' => 'users',
19 | ],
20 |
21 | /*
22 | |--------------------------------------------------------------------------
23 | | Authentication Guards
24 | |--------------------------------------------------------------------------
25 | |
26 | | Next, you may define every authentication guard for your application.
27 | | Of course, a great default configuration has been defined for you
28 | | here which uses session storage and the Eloquent user provider.
29 | |
30 | | All authentication drivers have a user provider. This defines how the
31 | | users are actually retrieved out of your database or other storage
32 | | mechanisms used by this application to persist your user's data.
33 | |
34 | | Supported: "session"
35 | |
36 | */
37 |
38 | 'guards' => [
39 | 'web' => [
40 | 'driver' => 'session',
41 | 'provider' => 'users',
42 | ],
43 | ],
44 |
45 | /*
46 | |--------------------------------------------------------------------------
47 | | User Providers
48 | |--------------------------------------------------------------------------
49 | |
50 | | All authentication drivers have a user provider. This defines how the
51 | | users are actually retrieved out of your database or other storage
52 | | mechanisms used by this application to persist your user's data.
53 | |
54 | | If you have multiple user tables or models you may configure multiple
55 | | sources which represent each model / table. These sources may then
56 | | be assigned to any extra authentication guards you have defined.
57 | |
58 | | Supported: "database", "eloquent"
59 | |
60 | */
61 |
62 | 'providers' => [
63 | 'users' => [
64 | 'driver' => 'eloquent',
65 | 'model' => App\Models\User::class,
66 | ],
67 |
68 | // 'users' => [
69 | // 'driver' => 'database',
70 | // 'table' => 'users',
71 | // ],
72 | ],
73 |
74 | /*
75 | |--------------------------------------------------------------------------
76 | | Resetting Passwords
77 | |--------------------------------------------------------------------------
78 | |
79 | | You may specify multiple password reset configurations if you have more
80 | | than one user table or model in the application and you want to have
81 | | separate password reset settings based on the specific user types.
82 | |
83 | | The expiry time is the number of minutes that each reset token will be
84 | | considered valid. This security feature keeps tokens short-lived so
85 | | they have less time to be guessed. You may change this as needed.
86 | |
87 | | The throttle setting is the number of seconds a user must wait before
88 | | generating more password reset tokens. This prevents the user from
89 | | quickly generating a very large amount of password reset tokens.
90 | |
91 | */
92 |
93 | 'passwords' => [
94 | 'users' => [
95 | 'provider' => 'users',
96 | 'table' => 'password_reset_tokens',
97 | 'expire' => 60,
98 | 'throttle' => 60,
99 | ],
100 | ],
101 |
102 | /*
103 | |--------------------------------------------------------------------------
104 | | Password Confirmation Timeout
105 | |--------------------------------------------------------------------------
106 | |
107 | | Here you may define the amount of seconds before a password confirmation
108 | | times out and the user is prompted to re-enter their password via the
109 | | confirmation screen. By default, the timeout lasts for three hours.
110 | |
111 | */
112 |
113 | 'password_timeout' => 10800,
114 |
115 | ];
116 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Laravel Custom Domains Example
2 | This is an example repo to help you understand how you could implement custom domains easily as a feature in your Laravel app using [Approximated](https://approximated.app).
3 |
4 | ## How it works
5 | The example app is just a basic fresh Laravel app, with the Breeze auth starter added.
6 |
7 | The example purpose of this app is to display a public user profile page on a custom domain.
8 |
9 | When you register at /register, you'll be logged in and can enter a custom domain there.
10 |
11 | On save it'll create a virtual host in Approximated and provide the DNS info for pointing the domain. On change it'll update the virtual host or delete it (if cleared).
12 |
13 | The flow of a request in this app:
14 | - Hits web.php router
15 | - Hits a route group that matches the request domain with a primary domain pulled from your env
16 | - Any requests with a domain that doesn't match will go to a second route group with a custom domains middleware
17 | - If either the request header `apx-incoming-host` or the request host matches a custom domain in the database:
18 | - it'll continue and merge in the custom domain and the matching user to the request
19 | - in this example we only have one route that goes to PublicProfileController's show method, and loads up the public profile
20 | - If it doesn't match a custom domain in the database, or the primary domain, it 404s
21 |
22 | ## Trying it out
23 | This assumes that you have NPM installed, and the usual things for a laravel install (php, composer, etc.)
24 |
25 | 1. Copy .env.example to .env
26 | 2. Set the APP_PRIMARY_DOMAIN in .env to localhost or whatever you're putting in your browser to get to your dev env
27 | 3. Set APPROXIMATED_API_KEY in .env to your API key from the Approximated dashboard
28 | 4. If you don't have SSL for your dev env, remove GEN_HTTPS_URLS from .env
29 | 5. Run `npm run dev` and `php artisan serve` from the project root folder
30 | 6. Open your browser to the dev env and go to /register to create an acount
31 | 7. After account creation you'll be logged in to a dashboard with a form.
32 | 8. In the custom domain field, enter the domain of your dev env (localhost, for instance) and click save.
33 | 9. To test the custom domain in a local environment:
34 | - Change the APP_PRIMARY_DOMAIN in .env to anything else temporarily, then reload the page.
35 | - This makes the router not match that as the primary domain, so it'll attempt it as a custom domain.
36 | - You should see a public profile for that user with a few example pages (it's pretty basic, just an example)
37 | - To get back to the main app dashboard again, just change the APP_PRIMARY_DOMAIN back.
38 |
39 | ## Files to check out
40 | - [routes/web.php](routes/web.php) - for the route groups
41 | - [app/Http/Middleware/CustomDomains.php](app/Http/Middleware/CustomDomains.php) - to see how custom domains are matched to users
42 | - [app/Http/Kernel.php](app/Http/Kernel.php) - aliased the middleware here
43 | - [app/Http/Controllers/ProfileController.php](app/Http/Controllers/ProfileController.php) - the default laravel breeze profile controller, modified to create/update/delete the custom domain on update.
44 | - [app/Http/Controllers/PublicProfileController.php](app/Http/Controllers/PublicProfileController.php) - loads up a public profile for this example
45 | - [resources/views/public_profile/show.blade.php](resources/views/public_profile/show.blade.php) - view that extends up separate layout from default
46 | - [resources/views/layouts/public_profile_layout.blade.php](resources/views/layouts/public_profile_layout.blade.php) - a separate layout for custom domains
47 |
48 | ## Assets and CORS
49 | These work out of the box and required no changes in this example app, because they're all relatively pathed in the rendered html.
50 |
51 | If your app is linking to assets with absolute paths/URLs, changing it to a relative path should fix any CORS issues.
52 |
53 | ## Digging Deeper
54 | The key parts of this are the router and the custom domains middleware.
55 |
56 | *The router* has two route groups, one for requests matching the app's primary domain, and one for any other domains.
57 |
58 | *The custom domains middleware* is only applied to requests that make it to that second route group.
59 | It checks if the hostname or a header `apx-incoming-host` matches a user's custom domain. If it does, it loads the public profile by default.
60 |
61 | The public profile controller is just a regular controller, and the view is just a regular view. The only difference is that the view extends a different layout from the rest of the app for this example.
62 |
--------------------------------------------------------------------------------
/config/logging.php:
--------------------------------------------------------------------------------
1 | env('LOG_CHANNEL', 'stack'),
22 |
23 | /*
24 | |--------------------------------------------------------------------------
25 | | Deprecations Log Channel
26 | |--------------------------------------------------------------------------
27 | |
28 | | This option controls the log channel that should be used to log warnings
29 | | regarding deprecated PHP and library features. This allows you to get
30 | | your application ready for upcoming major versions of dependencies.
31 | |
32 | */
33 |
34 | 'deprecations' => [
35 | 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
36 | 'trace' => false,
37 | ],
38 |
39 | /*
40 | |--------------------------------------------------------------------------
41 | | Log Channels
42 | |--------------------------------------------------------------------------
43 | |
44 | | Here you may configure the log channels for your application. Out of
45 | | the box, Laravel uses the Monolog PHP logging library. This gives
46 | | you a variety of powerful log handlers / formatters to utilize.
47 | |
48 | | Available Drivers: "single", "daily", "slack", "syslog",
49 | | "errorlog", "monolog",
50 | | "custom", "stack"
51 | |
52 | */
53 |
54 | 'channels' => [
55 | 'stack' => [
56 | 'driver' => 'stack',
57 | 'channels' => ['single'],
58 | 'ignore_exceptions' => false,
59 | ],
60 |
61 | 'single' => [
62 | 'driver' => 'single',
63 | 'path' => storage_path('logs/laravel.log'),
64 | 'level' => env('LOG_LEVEL', 'debug'),
65 | 'replace_placeholders' => true,
66 | ],
67 |
68 | 'daily' => [
69 | 'driver' => 'daily',
70 | 'path' => storage_path('logs/laravel.log'),
71 | 'level' => env('LOG_LEVEL', 'debug'),
72 | 'days' => 14,
73 | 'replace_placeholders' => true,
74 | ],
75 |
76 | 'slack' => [
77 | 'driver' => 'slack',
78 | 'url' => env('LOG_SLACK_WEBHOOK_URL'),
79 | 'username' => 'Laravel Log',
80 | 'emoji' => ':boom:',
81 | 'level' => env('LOG_LEVEL', 'critical'),
82 | 'replace_placeholders' => true,
83 | ],
84 |
85 | 'papertrail' => [
86 | 'driver' => 'monolog',
87 | 'level' => env('LOG_LEVEL', 'debug'),
88 | 'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
89 | 'handler_with' => [
90 | 'host' => env('PAPERTRAIL_URL'),
91 | 'port' => env('PAPERTRAIL_PORT'),
92 | 'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'),
93 | ],
94 | 'processors' => [PsrLogMessageProcessor::class],
95 | ],
96 |
97 | 'stderr' => [
98 | 'driver' => 'monolog',
99 | 'level' => env('LOG_LEVEL', 'debug'),
100 | 'handler' => StreamHandler::class,
101 | 'formatter' => env('LOG_STDERR_FORMATTER'),
102 | 'with' => [
103 | 'stream' => 'php://stderr',
104 | ],
105 | 'processors' => [PsrLogMessageProcessor::class],
106 | ],
107 |
108 | 'syslog' => [
109 | 'driver' => 'syslog',
110 | 'level' => env('LOG_LEVEL', 'debug'),
111 | 'facility' => LOG_USER,
112 | 'replace_placeholders' => true,
113 | ],
114 |
115 | 'errorlog' => [
116 | 'driver' => 'errorlog',
117 | 'level' => env('LOG_LEVEL', 'debug'),
118 | 'replace_placeholders' => true,
119 | ],
120 |
121 | 'null' => [
122 | 'driver' => 'monolog',
123 | 'handler' => NullHandler::class,
124 | ],
125 |
126 | 'emergency' => [
127 | 'path' => storage_path('logs/laravel.log'),
128 | ],
129 | ],
130 |
131 | ];
132 |
--------------------------------------------------------------------------------
/resources/views/layouts/navigation.blade.php:
--------------------------------------------------------------------------------
1 |
101 |
--------------------------------------------------------------------------------
/config/database.php:
--------------------------------------------------------------------------------
1 | env('DB_CONNECTION', 'mysql'),
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Database Connections
23 | |--------------------------------------------------------------------------
24 | |
25 | | Here are each of the database connections setup for your application.
26 | | Of course, examples of configuring each database platform that is
27 | | supported by Laravel is shown below to make development simple.
28 | |
29 | |
30 | | All database work in Laravel is done through the PHP PDO facilities
31 | | so make sure you have the driver for your particular database of
32 | | choice installed on your machine before you begin development.
33 | |
34 | */
35 |
36 | 'connections' => [
37 |
38 | 'sqlite' => [
39 | 'driver' => 'sqlite',
40 | 'url' => env('DATABASE_URL'),
41 | 'database' => env('DB_DATABASE', database_path('database.sqlite')),
42 | 'prefix' => '',
43 | 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
44 | ],
45 |
46 | 'mysql' => [
47 | 'driver' => 'mysql',
48 | 'url' => env('DATABASE_URL'),
49 | 'host' => env('DB_HOST', '127.0.0.1'),
50 | 'port' => env('DB_PORT', '3306'),
51 | 'database' => env('DB_DATABASE', 'forge'),
52 | 'username' => env('DB_USERNAME', 'forge'),
53 | 'password' => env('DB_PASSWORD', ''),
54 | 'unix_socket' => env('DB_SOCKET', ''),
55 | 'charset' => 'utf8mb4',
56 | 'collation' => 'utf8mb4_unicode_ci',
57 | 'prefix' => '',
58 | 'prefix_indexes' => true,
59 | 'strict' => true,
60 | 'engine' => null,
61 | 'options' => extension_loaded('pdo_mysql') ? array_filter([
62 | PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
63 | ]) : [],
64 | ],
65 |
66 | 'pgsql' => [
67 | 'driver' => 'pgsql',
68 | 'url' => env('DATABASE_URL'),
69 | 'host' => env('DB_HOST', '127.0.0.1'),
70 | 'port' => env('DB_PORT', '5432'),
71 | 'database' => env('DB_DATABASE', 'forge'),
72 | 'username' => env('DB_USERNAME', 'forge'),
73 | 'password' => env('DB_PASSWORD', ''),
74 | 'charset' => 'utf8',
75 | 'prefix' => '',
76 | 'prefix_indexes' => true,
77 | 'search_path' => 'public',
78 | 'sslmode' => 'prefer',
79 | ],
80 |
81 | 'sqlsrv' => [
82 | 'driver' => 'sqlsrv',
83 | 'url' => env('DATABASE_URL'),
84 | 'host' => env('DB_HOST', 'localhost'),
85 | 'port' => env('DB_PORT', '1433'),
86 | 'database' => env('DB_DATABASE', 'forge'),
87 | 'username' => env('DB_USERNAME', 'forge'),
88 | 'password' => env('DB_PASSWORD', ''),
89 | 'charset' => 'utf8',
90 | 'prefix' => '',
91 | 'prefix_indexes' => true,
92 | // 'encrypt' => env('DB_ENCRYPT', 'yes'),
93 | // 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'),
94 | ],
95 |
96 | ],
97 |
98 | /*
99 | |--------------------------------------------------------------------------
100 | | Migration Repository Table
101 | |--------------------------------------------------------------------------
102 | |
103 | | This table keeps track of all the migrations that have already run for
104 | | your application. Using this information, we can determine which of
105 | | the migrations on disk haven't actually been run in the database.
106 | |
107 | */
108 |
109 | 'migrations' => 'migrations',
110 |
111 | /*
112 | |--------------------------------------------------------------------------
113 | | Redis Databases
114 | |--------------------------------------------------------------------------
115 | |
116 | | Redis is an open source, fast, and advanced key-value store that also
117 | | provides a richer body of commands than a typical key-value system
118 | | such as APC or Memcached. Laravel makes it easy to dig right in.
119 | |
120 | */
121 |
122 | 'redis' => [
123 |
124 | 'client' => env('REDIS_CLIENT', 'phpredis'),
125 |
126 | 'options' => [
127 | 'cluster' => env('REDIS_CLUSTER', 'redis'),
128 | 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
129 | ],
130 |
131 | 'default' => [
132 | 'url' => env('REDIS_URL'),
133 | 'host' => env('REDIS_HOST', '127.0.0.1'),
134 | 'username' => env('REDIS_USERNAME'),
135 | 'password' => env('REDIS_PASSWORD'),
136 | 'port' => env('REDIS_PORT', '6379'),
137 | 'database' => env('REDIS_DB', '0'),
138 | ],
139 |
140 | 'cache' => [
141 | 'url' => env('REDIS_URL'),
142 | 'host' => env('REDIS_HOST', '127.0.0.1'),
143 | 'username' => env('REDIS_USERNAME'),
144 | 'password' => env('REDIS_PASSWORD'),
145 | 'port' => env('REDIS_PORT', '6379'),
146 | 'database' => env('REDIS_CACHE_DB', '1'),
147 | ],
148 |
149 | ],
150 |
151 | ];
152 |
--------------------------------------------------------------------------------
/app/Services/Approximated.php:
--------------------------------------------------------------------------------
1 | true,
20 | * "data" => [
21 | * "id": 445922,
22 | * "incoming_address": "acustomdomain.com",
23 | * "target_address": "myapp.com",
24 | * "target_ports": "443",
25 | * "user_message": "In order to connect your domain, you'll need to have a DNS A record that points acustomdomain.com at..."
26 | * ]
27 | * ]
28 | * @return array
29 | * [
30 | * "success" => false,
31 | * "errors" => [
32 | * "incoming_address" => [
33 | * "This incoming address has already been created on the reverse proxy server you selected.",
34 | * ],
35 | * ],
36 | * ]
37 | */
38 | public function create_vhost($incoming_address, $target_address, $opts = [])
39 | {
40 | // see https://approximated.app/docs/#create-virtual-host for optional fields
41 | $data = array_merge($opts, [
42 | 'incoming_address' => $incoming_address,
43 | 'target_address' => $target_address,
44 | ]);
45 |
46 | $response = Http::withHeaders(['api-key' => env('APPROXIMATED_API_KEY')])
47 | ->post($this->api_url . "/vhosts", $data);
48 |
49 |
50 | return $this->handle_response($response);
51 | }
52 |
53 | /**
54 | * Updates a virtual host.
55 | * Any fields not passed into options will remain the same.
56 | *
57 | * @param datatype $current_incoming_address The current incoming address.
58 | * @param array $opts Optional fields. See https://approximated.app/docs/#update-virtual-host for more details.
59 | * @return array
60 | * [
61 | * "success" => true,
62 | * "data" => [
63 | * "apx_hit" => true, // requests are reaching the cluster
64 | * "created_at" => "2023-04-03T17:59:28", // UTC timezone
65 | * "dns_pointed_at" => "213.188.210.168", // DNS for the incoming_address
66 | * "has_ssl" => true,
67 | * "id" => 405455,
68 | * "incoming_address" => "adifferentcustomdomain.com",
69 | * "is_resolving" => true, // is this returning a response
70 | * "last_monitored_humanized" => "1 hour ago",
71 | * "last_monitored_unix": 1687194590,
72 | * "ssl_active_from" => "2023-06-02T20:19:15", // UTC timezone
73 | * "ssl_active_until" => "2023-08-31T20:19:14", // UTC timezone, auto-renews
74 | * "status": "ACTIVE_SSL",
75 | * "status_message" => "Active with SSL",
76 | * "target_address" => "myapp.com",
77 | * "target_ports" => "443"
78 | * ]
79 | * ]
80 | * @return array
81 | * [
82 | * "success" => false,
83 | * "errors" => [
84 | * "incoming_address" => [
85 | * "This incoming address has already been created on the reverse proxy server you selected.",
86 | * ],
87 | * ],
88 | * ]
89 | */
90 | public function update_vhost($current_incoming_address, $opts = [])
91 | {
92 | // see https://approximated.app/docs/#update-virtual-host for optional fields
93 | $data = array_merge($opts, [
94 | 'current_incoming_address' => $current_incoming_address
95 | ]);
96 |
97 | $response = Http::withHeaders(['api-key' => env('APPROXIMATED_API_KEY')])
98 | ->post($this->api_url . "/vhosts/update/by/incoming", $data);
99 |
100 |
101 | return $this->handle_response($response);
102 | }
103 |
104 | /**
105 | * Gets a virtual host.
106 | *
107 | * @param datatype $incoming_address The incoming address.
108 | * @return array
109 | * [
110 | * "data" => [
111 | * "apx_hit" => true, // requests are reaching the cluster
112 | * "created_at" => "2023-04-03T17:59:28", // UTC timezone
113 | * "dns_pointed_at" => "213.188.210.168", // DNS for the incoming_address
114 | * "has_ssl" => true,
115 | * "id" => 405455,
116 | * "incoming_address" => "adifferentcustomdomain.com",
117 | * "is_resolving" => true, // is this returning a response
118 | * "last_monitored_humanized" => "1 hour ago",
119 | * "last_monitored_unix": 1687194590,
120 | * "ssl_active_from" => "2023-06-02T20:19:15", // UTC timezone
121 | * "ssl_active_until" => "2023-08-31T20:19:14", // UTC timezone, auto-renews
122 | * "status": "ACTIVE_SSL",
123 | * "status_message" => "Active with SSL",
124 | * "target_address" => "myapp.com",
125 | * "target_ports" => "443"
126 | * ]
127 | * ]
128 | */
129 | public function get_vhost($incoming_address)
130 | {
131 | $response = Http::withHeaders(['api-key' => env('APPROXIMATED_API_KEY')])
132 | ->get($this->api_url . "/vhosts/by/incoming/".$incoming_address);
133 |
134 |
135 | return $this->handle_response($response);
136 | }
137 |
138 | /**
139 | * Deletes a vhost by incoming address.
140 | *
141 | * @param mixed $incoming_address The incoming address of the vhost to be deleted.
142 | * @return array ["success" => true, "data" => null]
143 | */
144 | public function delete_vhost($incoming_address)
145 | {
146 | $response = Http::withHeaders(['api-key' => env('APPROXIMATED_API_KEY')])
147 | ->delete($this->api_url . "/vhosts/by/incoming/".$incoming_address);
148 |
149 |
150 | return $this->handle_response($response);
151 | }
152 |
153 | private function handle_response($response) {
154 | // return success and the vhost data if successful
155 | if($response->successful()) {
156 | return array_merge(["success" => true], $response->json() || ["data" => null]);
157 | }
158 |
159 | if($response->notFound()){
160 | return ["success" => false, "data" => "Not Found."];
161 | }
162 |
163 | // There's an issue with the data we sent (duplicate incoming address, missing fields, etc.)
164 | // returns success as false and the data will be the errors.
165 | if($response->unprocessableEntity()){
166 | return array_merge(["success" => false], $response->json());
167 | }
168 |
169 | // otherwise throw if there was an error
170 | $response->throw();
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/config/app.php:
--------------------------------------------------------------------------------
1 | env('APP_NAME', 'Laravel'),
20 |
21 | /*
22 | |--------------------------------------------------------------------------
23 | | Application Environment
24 | |--------------------------------------------------------------------------
25 | |
26 | | This value determines the "environment" your application is currently
27 | | running in. This may determine how you prefer to configure various
28 | | services the application utilizes. Set this in your ".env" file.
29 | |
30 | */
31 |
32 | 'env' => env('APP_ENV', 'production'),
33 |
34 | /*
35 | |--------------------------------------------------------------------------
36 | | Application Debug Mode
37 | |--------------------------------------------------------------------------
38 | |
39 | | When your application is in debug mode, detailed error messages with
40 | | stack traces will be shown on every error that occurs within your
41 | | application. If disabled, a simple generic error page is shown.
42 | |
43 | */
44 |
45 | 'debug' => (bool) env('APP_DEBUG', false),
46 |
47 | /*
48 | |--------------------------------------------------------------------------
49 | | Application URL
50 | |--------------------------------------------------------------------------
51 | |
52 | | This URL is used by the console to properly generate URLs when using
53 | | the Artisan command line tool. You should set this to the root of
54 | | your application so that it is used when running Artisan tasks.
55 | |
56 | */
57 |
58 | 'url' => env('APP_URL', 'http://localhost'),
59 |
60 | 'asset_url' => env('ASSET_URL'),
61 |
62 | /*
63 | |--------------------------------------------------------------------------
64 | | Application Timezone
65 | |--------------------------------------------------------------------------
66 | |
67 | | Here you may specify the default timezone for your application, which
68 | | will be used by the PHP date and date-time functions. We have gone
69 | | ahead and set this to a sensible default for you out of the box.
70 | |
71 | */
72 |
73 | 'timezone' => 'UTC',
74 |
75 | /*
76 | |--------------------------------------------------------------------------
77 | | Application Locale Configuration
78 | |--------------------------------------------------------------------------
79 | |
80 | | The application locale determines the default locale that will be used
81 | | by the translation service provider. You are free to set this value
82 | | to any of the locales which will be supported by the application.
83 | |
84 | */
85 |
86 | 'locale' => 'en',
87 |
88 | /*
89 | |--------------------------------------------------------------------------
90 | | Application Fallback Locale
91 | |--------------------------------------------------------------------------
92 | |
93 | | The fallback locale determines the locale to use when the current one
94 | | is not available. You may change the value to correspond to any of
95 | | the language folders that are provided through your application.
96 | |
97 | */
98 |
99 | 'fallback_locale' => 'en',
100 |
101 | /*
102 | |--------------------------------------------------------------------------
103 | | Faker Locale
104 | |--------------------------------------------------------------------------
105 | |
106 | | This locale will be used by the Faker PHP library when generating fake
107 | | data for your database seeds. For example, this will be used to get
108 | | localized telephone numbers, street address information and more.
109 | |
110 | */
111 |
112 | 'faker_locale' => 'en_US',
113 |
114 | /*
115 | |--------------------------------------------------------------------------
116 | | Encryption Key
117 | |--------------------------------------------------------------------------
118 | |
119 | | This key is used by the Illuminate encrypter service and should be set
120 | | to a random, 32 character string, otherwise these encrypted strings
121 | | will not be safe. Please do this before deploying an application!
122 | |
123 | */
124 |
125 | 'key' => env('APP_KEY'),
126 |
127 | 'cipher' => 'AES-256-CBC',
128 |
129 | /*
130 | |--------------------------------------------------------------------------
131 | | Maintenance Mode Driver
132 | |--------------------------------------------------------------------------
133 | |
134 | | These configuration options determine the driver used to determine and
135 | | manage Laravel's "maintenance mode" status. The "cache" driver will
136 | | allow maintenance mode to be controlled across multiple machines.
137 | |
138 | | Supported drivers: "file", "cache"
139 | |
140 | */
141 |
142 | 'maintenance' => [
143 | 'driver' => 'file',
144 | // 'store' => 'redis',
145 | ],
146 |
147 | /*
148 | |--------------------------------------------------------------------------
149 | | Autoloaded Service Providers
150 | |--------------------------------------------------------------------------
151 | |
152 | | The service providers listed here will be automatically loaded on the
153 | | request to your application. Feel free to add your own services to
154 | | this array to grant expanded functionality to your applications.
155 | |
156 | */
157 |
158 | 'providers' => ServiceProvider::defaultProviders()->merge([
159 | /*
160 | * Package Service Providers...
161 | */
162 |
163 | /*
164 | * Application Service Providers...
165 | */
166 | App\Providers\AppServiceProvider::class,
167 | App\Providers\AuthServiceProvider::class,
168 | // App\Providers\BroadcastServiceProvider::class,
169 | App\Providers\EventServiceProvider::class,
170 | App\Providers\RouteServiceProvider::class,
171 | ])->toArray(),
172 |
173 | /*
174 | |--------------------------------------------------------------------------
175 | | Class Aliases
176 | |--------------------------------------------------------------------------
177 | |
178 | | This array of class aliases will be registered when this application
179 | | is started. However, feel free to register as many as you wish as
180 | | the aliases are "lazy" loaded so they don't hinder performance.
181 | |
182 | */
183 |
184 | 'aliases' => Facade::defaultAliases()->merge([
185 | // 'Example' => App\Facades\Example::class,
186 | ])->toArray(),
187 |
188 | ];
189 |
--------------------------------------------------------------------------------
/config/session.php:
--------------------------------------------------------------------------------
1 | env('SESSION_DRIVER', 'file'),
22 |
23 | /*
24 | |--------------------------------------------------------------------------
25 | | Session Lifetime
26 | |--------------------------------------------------------------------------
27 | |
28 | | Here you may specify the number of minutes that you wish the session
29 | | to be allowed to remain idle before it expires. If you want them
30 | | to immediately expire on the browser closing, set that option.
31 | |
32 | */
33 |
34 | 'lifetime' => env('SESSION_LIFETIME', 120),
35 |
36 | 'expire_on_close' => false,
37 |
38 | /*
39 | |--------------------------------------------------------------------------
40 | | Session Encryption
41 | |--------------------------------------------------------------------------
42 | |
43 | | This option allows you to easily specify that all of your session data
44 | | should be encrypted before it is stored. All encryption will be run
45 | | automatically by Laravel and you can use the Session like normal.
46 | |
47 | */
48 |
49 | 'encrypt' => false,
50 |
51 | /*
52 | |--------------------------------------------------------------------------
53 | | Session File Location
54 | |--------------------------------------------------------------------------
55 | |
56 | | When using the native session driver, we need a location where session
57 | | files may be stored. A default has been set for you but a different
58 | | location may be specified. This is only needed for file sessions.
59 | |
60 | */
61 |
62 | 'files' => storage_path('framework/sessions'),
63 |
64 | /*
65 | |--------------------------------------------------------------------------
66 | | Session Database Connection
67 | |--------------------------------------------------------------------------
68 | |
69 | | When using the "database" or "redis" session drivers, you may specify a
70 | | connection that should be used to manage these sessions. This should
71 | | correspond to a connection in your database configuration options.
72 | |
73 | */
74 |
75 | 'connection' => env('SESSION_CONNECTION'),
76 |
77 | /*
78 | |--------------------------------------------------------------------------
79 | | Session Database Table
80 | |--------------------------------------------------------------------------
81 | |
82 | | When using the "database" session driver, you may specify the table we
83 | | should use to manage the sessions. Of course, a sensible default is
84 | | provided for you; however, you are free to change this as needed.
85 | |
86 | */
87 |
88 | 'table' => 'sessions',
89 |
90 | /*
91 | |--------------------------------------------------------------------------
92 | | Session Cache Store
93 | |--------------------------------------------------------------------------
94 | |
95 | | While using one of the framework's cache driven session backends you may
96 | | list a cache store that should be used for these sessions. This value
97 | | must match with one of the application's configured cache "stores".
98 | |
99 | | Affects: "apc", "dynamodb", "memcached", "redis"
100 | |
101 | */
102 |
103 | 'store' => env('SESSION_STORE'),
104 |
105 | /*
106 | |--------------------------------------------------------------------------
107 | | Session Sweeping Lottery
108 | |--------------------------------------------------------------------------
109 | |
110 | | Some session drivers must manually sweep their storage location to get
111 | | rid of old sessions from storage. Here are the chances that it will
112 | | happen on a given request. By default, the odds are 2 out of 100.
113 | |
114 | */
115 |
116 | 'lottery' => [2, 100],
117 |
118 | /*
119 | |--------------------------------------------------------------------------
120 | | Session Cookie Name
121 | |--------------------------------------------------------------------------
122 | |
123 | | Here you may change the name of the cookie used to identify a session
124 | | instance by ID. The name specified here will get used every time a
125 | | new session cookie is created by the framework for every driver.
126 | |
127 | */
128 |
129 | 'cookie' => env(
130 | 'SESSION_COOKIE',
131 | Str::slug(env('APP_NAME', 'laravel'), '_').'_session'
132 | ),
133 |
134 | /*
135 | |--------------------------------------------------------------------------
136 | | Session Cookie Path
137 | |--------------------------------------------------------------------------
138 | |
139 | | The session cookie path determines the path for which the cookie will
140 | | be regarded as available. Typically, this will be the root path of
141 | | your application but you are free to change this when necessary.
142 | |
143 | */
144 |
145 | 'path' => '/',
146 |
147 | /*
148 | |--------------------------------------------------------------------------
149 | | Session Cookie Domain
150 | |--------------------------------------------------------------------------
151 | |
152 | | Here you may change the domain of the cookie used to identify a session
153 | | in your application. This will determine which domains the cookie is
154 | | available to in your application. A sensible default has been set.
155 | |
156 | */
157 |
158 | 'domain' => env('SESSION_DOMAIN'),
159 |
160 | /*
161 | |--------------------------------------------------------------------------
162 | | HTTPS Only Cookies
163 | |--------------------------------------------------------------------------
164 | |
165 | | By setting this option to true, session cookies will only be sent back
166 | | to the server if the browser has a HTTPS connection. This will keep
167 | | the cookie from being sent to you when it can't be done securely.
168 | |
169 | */
170 |
171 | 'secure' => env('SESSION_SECURE_COOKIE'),
172 |
173 | /*
174 | |--------------------------------------------------------------------------
175 | | HTTP Access Only
176 | |--------------------------------------------------------------------------
177 | |
178 | | Setting this value to true will prevent JavaScript from accessing the
179 | | value of the cookie and the cookie will only be accessible through
180 | | the HTTP protocol. You are free to modify this option if needed.
181 | |
182 | */
183 |
184 | 'http_only' => true,
185 |
186 | /*
187 | |--------------------------------------------------------------------------
188 | | Same-Site Cookies
189 | |--------------------------------------------------------------------------
190 | |
191 | | This option determines how your cookies behave when cross-site requests
192 | | take place, and can be used to mitigate CSRF attacks. By default, we
193 | | will set this value to "lax" since this is a secure default value.
194 | |
195 | | Supported: "lax", "strict", "none", null
196 | |
197 | */
198 |
199 | 'same_site' => 'lax',
200 |
201 | ];
202 |
--------------------------------------------------------------------------------