Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117878663
D4549.1775341596.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
28 KB
Referenced Files
None
Subscribers
None
D4549.1775341596.diff
View Options
diff --git a/src/app/Http/AuthSession.php b/src/app/Http/AuthSession.php
new file mode 100644
--- /dev/null
+++ b/src/app/Http/AuthSession.php
@@ -0,0 +1,326 @@
+<?php
+
+namespace App\Http;
+
+use Illuminate\Contracts\Session\Session as SessionInterface;
+use Illuminate\Support\Facades\Cache;
+
+class AuthSession implements SessionInterface
+{
+ /** @var string $prefix The cache key prefix */
+ protected string $prefix;
+
+ /** @var int $ttl The cache TTL in seconds */
+ protected int $ttl;
+
+ /** @var array $data The session data */
+ protected array $data = [];
+
+ /**
+ * Constructor.
+ *
+ * @param string $prefix
+ * @param int $ttl
+ */
+ public function __construct(string $prefix = '', int $ttl = 600)
+ {
+ $this->prefix = $prefix;
+ $this->ttl = $ttl;
+ }
+
+ /**
+ * Get the name of the session.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ throw new \Exception("Not implemented");
+ }
+
+ /**
+ * Set the name of the session.
+ *
+ * @param string $name
+ * @return void
+ */
+ public function setName($name)
+ {
+ throw new \Exception("Not implemented");
+ }
+
+ /**
+ * Get the current session ID.
+ *
+ * @return string
+ */
+ public function getId()
+ {
+ throw new \Exception("Not implemented");
+ }
+
+ /**
+ * Set the session ID.
+ *
+ * @param string $id
+ * @return void
+ */
+ public function setId($id)
+ {
+ throw new \Exception("Not implemented");
+ }
+
+ /**
+ * Start the session, reading the data from a handler.
+ *
+ * @return bool
+ */
+ public function start()
+ {
+ throw new \Exception("Not implemented");
+ }
+
+ /**
+ * Save the session data to storage.
+ *
+ * @return void
+ */
+ public function save($prefix = null)
+ {
+ foreach ($this->data as $key => $value) {
+ Cache::put($prefix . ':' . $key, $value, $this->ttl);
+ }
+ }
+
+ /**
+ * Get all of the session data.
+ *
+ * @return array
+ */
+ public function all()
+ {
+ throw new \Exception("Not implemented");
+ }
+
+ /**
+ * Checks if a key exists.
+ *
+ * @param string|array $key
+ * @return bool
+ */
+ public function exists($key)
+ {
+ throw new \Exception("Not implemented");
+ }
+
+ /**
+ * Checks if a key is present and not null.
+ *
+ * @param string|array $key
+ * @return bool
+ */
+ public function has($key)
+ {
+ throw new \Exception("Not implemented");
+ }
+
+ /**
+ * Get an item from the session.
+ *
+ * @param string $key
+ * @param mixed $default
+ * @return mixed
+ */
+ public function get($key, $default = null)
+ {
+ if (array_key_exists($key, $this->data)) {
+ return $this->data[$key];
+ }
+
+ if (strlen($this->prefix)) {
+ return Cache::get($this->prefix . ':' . $key) ?? $default;
+ }
+
+ return $default;
+ }
+
+ /**
+ * Get the value of a given key and then forget it.
+ *
+ * @param string $key
+ * @param mixed $default
+ * @return mixed
+ */
+ public function pull($key, $default = null)
+ {
+ if (array_key_exists($key, $this->data)) {
+ $value = $this->data[$key];
+ unset($this->data[$key]);
+ return $value;
+ }
+
+ if (strlen($this->prefix)) {
+ return Cache::pull($this->prefix . ':' . $key) ?? $default;
+ }
+
+ return $default;
+ }
+
+ /**
+ * Put a key / value pair or array of key / value pairs in the session.
+ *
+ * @param string|array $key
+ * @param mixed $value
+ * @return void
+ */
+ public function put($key, $value = null)
+ {
+ $this->data[$key] = $value;
+ }
+
+ /**
+ * Get the CSRF token value.
+ *
+ * @return string
+ */
+ public function token()
+ {
+ throw new \Exception("Not implemented");
+ }
+
+ /**
+ * Regenerate the CSRF token value.
+ *
+ * @return void
+ */
+ public function regenerateToken()
+ {
+ throw new \Exception("Not implemented");
+ }
+
+ /**
+ * Remove an item from the session, returning its value.
+ *
+ * @param string $key
+ * @return mixed
+ */
+ public function remove($key)
+ {
+ throw new \Exception("Not implemented");
+ }
+
+ /**
+ * Remove one or many items from the session.
+ *
+ * @param string|array $keys
+ * @return void
+ */
+ public function forget($keys)
+ {
+ throw new \Exception("Not implemented");
+ }
+
+ /**
+ * Remove all of the items from the session.
+ *
+ * @return void
+ */
+ public function flush()
+ {
+ throw new \Exception("Not implemented");
+ }
+
+ /**
+ * Flush the session data and regenerate the ID.
+ *
+ * @return bool
+ */
+ public function invalidate()
+ {
+ throw new \Exception("Not implemented");
+ }
+
+ /**
+ * Generate a new session identifier.
+ *
+ * @param bool $destroy
+ * @return bool
+ */
+ public function regenerate($destroy = false)
+ {
+ throw new \Exception("Not implemented");
+ }
+
+ /**
+ * Generate a new session ID for the session.
+ *
+ * @param bool $destroy
+ * @return bool
+ */
+ public function migrate($destroy = false)
+ {
+ throw new \Exception("Not implemented");
+ }
+
+ /**
+ * Determine if the session has been started.
+ *
+ * @return bool
+ */
+ public function isStarted()
+ {
+ throw new \Exception("Not implemented");
+ }
+
+ /**
+ * Get the previous URL from the session.
+ *
+ * @return string|null
+ */
+ public function previousUrl()
+ {
+ throw new \Exception("Not implemented");
+ }
+
+ /**
+ * Set the "previous" URL in the session.
+ *
+ * @param string $url
+ * @return void
+ */
+ public function setPreviousUrl($url)
+ {
+ throw new \Exception("Not implemented");
+ }
+
+ /**
+ * Get the session handler instance.
+ *
+ * @return \SessionHandlerInterface
+ */
+ public function getHandler()
+ {
+ throw new \Exception("Not implemented");
+ }
+
+ /**
+ * Determine if the session handler needs a request.
+ *
+ * @return bool
+ */
+ public function handlerNeedsRequest()
+ {
+ throw new \Exception("Not implemented");
+ }
+
+ /**
+ * Set the request on the handler instance.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @return void
+ */
+ public function setRequestOnHandler($request)
+ {
+ throw new \Exception("Not implemented");
+ }
+}
diff --git a/src/app/Http/Controllers/API/AuthController.php b/src/app/Http/Controllers/API/AuthController.php
--- a/src/app/Http/Controllers/API/AuthController.php
+++ b/src/app/Http/Controllers/API/AuthController.php
@@ -2,6 +2,7 @@
namespace App\Http\Controllers\API;
+use App\Http\AuthSession;
use App\Http\Controllers\Controller;
use App\User;
use Illuminate\Http\Request;
@@ -9,6 +10,7 @@
use Illuminate\Support\Facades\Validator;
use Laravel\Passport\TokenRepository;
use Laravel\Passport\RefreshTokenRepository;
+use Laravel\Socialite\Facades\Socialite;
class AuthController extends Controller
{
@@ -127,6 +129,165 @@
]);
}
+ /**
+ * OAuth callback. Handles return from the provider's site.
+ *
+ * @param string $provider OAuth provider
+ *
+ * @return \Illuminate\View\View
+ */
+ public function oAuthCallback(string $provider)
+ {
+ $env = \App\Utils::uiEnv();
+ $request = request();
+
+ if ($error = $request->input('error')) {
+ \Log::warning(ucfirst($provider) . " OAuth error ({$error}): " . $request->input('error_description'));
+ // TODO: Display a localized error instead the OAuth error code from the provider
+ $env['userError'] = $error;
+ return view($env['view'])->with('env', $env);
+ }
+
+ // Use our own session handler. Normally we don't use sessions, but we need
+ // it here, as not all OAuth drivers support stateless(), e.g. Twitter v2
+ $request->setLaravelSession($session = new AuthSession($request->input('state')));
+
+ try {
+ \config(["services.{$provider}.redirect" => \App\Utils::serviceUrl("oauth/callback/{$provider}")]);
+
+ $oauth_user = Socialite::driver($provider)->user();
+ } catch (\Throwable $error) {
+ \Log::error($error); // FIXME: warning?
+ // TODO: A better (localized) error message
+ $env['userError'] = 'External authentication failed!';
+ return view($env['view'])->with('env', $env);
+ }
+
+ // \Log::info(print_r($oauth_user, true));
+
+ if ($login_property = \config("services.{$provider}.login_property")) {
+ $email = strtolower((string) $oauth_user->{$login_property});
+ if (!strpos($email, '@')) {
+ // FIXME: Note that an email address built this way is not a valid address
+ $email .= '@' . $provider;
+ }
+ } else {
+ $email = $oauth_user->getEmail();
+ if (!$email) {
+ // TODO: localized error
+ $env['userError'] = 'External authentication failed!';
+ return view($env['view'])->with('env', $env);
+ }
+ }
+
+ // TODO: Sanity check on $email: max length, chars, make sure it's a valid email?
+
+ $user = User::where('email', $email)->first();
+
+ if (!$user) {
+ $user = new User();
+ $user->email = $email;
+ $user->role = User::ROLE_GUEST;
+ }
+
+ if ($user->role !== User::ROLE_GUEST) {
+ \Log::warning(ucfirst($provider) . " OAuth error: Local user");
+ // TODO: A better (localized) error message
+ $env['userError'] = 'External authentication is not for local users!';
+ return view($env['view'])->with('env', $env);
+ }
+
+ // Set fake password so we can use logonResponse() below
+ // TODO: Find a way to create tokens without this hassle with a password
+ $user->password = $password = bin2hex(random_bytes(16));
+ $user->save();
+
+ $settings = [
+ 'oauth_provider' => $provider,
+ 'oauth_name' => $oauth_user->getName(),
+ 'oauth_id' => $oauth_user->getId(),
+ // 'oauth_token' => $oauth_user->token,
+ // 'oauth_refresh_token' => $oauth_user->refreshToken,
+ // 'oauth_expires_in' => $oauth_user->expiresIn,
+ ];
+
+ // $user->setSettings($settings);
+
+ // Log-in the user, get tokens
+ $response = self::logonResponse($user, $password);
+ $response = $response->getData(true);
+
+ // TODO: Direct the user to the page that was requested initially
+ $response['redirect'] = 'dashboard';
+
+ $env['userResponse'] = $response;
+
+ // The logonResponse() call above will reset the current request
+ // reference making an issue with assets, etc.
+ // We have to reset it to the original request we're handling here.
+ \app('url')->setRequest($request);
+
+ return view($env['view'])->with('env', $env);
+ }
+
+ /**
+ * OAuth configuration for the client-side.
+ *
+ * @return array List of available providers
+ */
+ public static function oAuthConfig(): array
+ {
+ $providers = [
+ 'facebook' => !empty(\config('services.facebook.client_id')),
+ 'github' => !empty(\config('services.github.client_id')),
+ 'google' => !empty(\config('services.google.client_id')),
+ 'twitter' => !empty(\config('services.twitter.client_id')),
+ // TODO: For providers like keycloak we'd need another approach. Consider:
+ // 1. What if you want to use more than one Keycloak-based service?
+ // 2. The button label should probably not be "Keycloak" in any case.
+ // 3. The button icon should be configurable
+ 'keycloak' => !empty(\config('services.keycloak.client_id')),
+ ];
+
+ return array_keys(array_filter($providers));
+ }
+
+ /**
+ * Creates an redirect URL to the OAuth provider's site.
+ *
+ * @param string $provider OAuth provider
+ *
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function oAuthRedirect(string $provider)
+ {
+ // Use our own session handler. Normally we don't use sessions, but we need
+ // it here, as not all OAuth drivers support stateless(), e.g. Twitter v2
+ request()->setLaravelSession($session = new AuthSession());
+
+ try {
+ \config(["services.{$provider}.redirect" => \App\Utils::serviceUrl("oauth/callback/{$provider}")]);
+
+ $url = Socialite::driver($provider)->redirect()->getTargetUrl();
+
+ // Save the session data to the cache
+ if (preg_match('/[?&]state=([^&]+)/', $url, $matches)) {
+ $session->save($matches[1]);
+ } else {
+ // error
+ }
+ } catch (\Throwable $error) {
+ \Log::error($error);
+ // TODO: Better error (localized) message
+ return response()->json(['status' => 'error', 'message' => "Redirect to OAuth provider failed"], 500);
+ }
+
+ return response()->json([
+ 'status' => 'success',
+ 'redirect' => $url,
+ ]);
+ }
+
/**
* Refresh a token.
*
diff --git a/src/app/Http/Controllers/API/V4/UsersController.php b/src/app/Http/Controllers/API/V4/UsersController.php
--- a/src/app/Http/Controllers/API/V4/UsersController.php
+++ b/src/app/Http/Controllers/API/V4/UsersController.php
@@ -164,6 +164,10 @@
]
);
+ if ($user->role === User::ROLE_GUEST) {
+ return [];
+ }
+
// Check if the user is a controller of his wallet
$isController = $user->canDelete($user);
@@ -351,17 +355,28 @@
{
$response = array_merge($user->toArray(), self::objectState($user));
- $wallet = $user->wallet();
-
- // IsLocked flag to lock the user to the Wallet page only
- $response['isLocked'] = (!$user->isActive() && ($plan = $wallet->plan()) && $plan->mode == Plan::MODE_MANDATE);
-
// Settings
$response['settings'] = [];
foreach ($user->settings()->whereIn('key', self::USER_SETTINGS)->get() as $item) {
$response['settings'][$item->key] = $item->value;
}
+ if ($user->role === User::ROLE_GUEST) {
+ // TODO: For now we set some required properties to empty/dummy value
+ // This will very likely change in the future
+ $response['wallets'] = [];
+ $response['accounts'] = [];
+ $response['statusInfo'] = ['skus' => [], 'process' => [], 'isDone' => true];
+ $response['isGuest'] = true;
+
+ return $response;
+ }
+
+ $wallet = $user->wallet();
+
+ // IsLocked flag to lock the user to the Wallet page only
+ $response['isLocked'] = (!$user->isActive() && ($plan = $wallet->plan()) && $plan->mode == Plan::MODE_MANDATE);
+
// Status info
$response['statusInfo'] = self::statusInfo($user);
diff --git a/src/app/Http/Kernel.php b/src/app/Http/Kernel.php
--- a/src/app/Http/Kernel.php
+++ b/src/app/Http/Kernel.php
@@ -49,6 +49,10 @@
// 'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
+
+ 'session' => [
+ \Illuminate\Session\Middleware\StartSession::class,
+ ],
];
/**
diff --git a/src/app/Observers/UserObserver.php b/src/app/Observers/UserObserver.php
--- a/src/app/Observers/UserObserver.php
+++ b/src/app/Observers/UserObserver.php
@@ -61,6 +61,11 @@
// Note: This is a single multi-insert query
$user->settings()->insert(array_values($settings));
+ // Don't need wallet, nor jobs for an external user
+ if ($user->role === User::ROLE_GUEST) {
+ return;
+ }
+
$user->wallets()->create();
// Create user record in the backend (LDAP and IMAP)
diff --git a/src/app/Providers/EventServiceProvider.php b/src/app/Providers/EventServiceProvider.php
--- a/src/app/Providers/EventServiceProvider.php
+++ b/src/app/Providers/EventServiceProvider.php
@@ -2,9 +2,6 @@
namespace App\Providers;
-use Illuminate\Support\Facades\Event;
-use Illuminate\Auth\Events\Registered;
-use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider
@@ -12,11 +9,12 @@
/**
* The event listener mappings for the application.
*
- * @var array<class-string, array<int, class-string>>
+ * @var array<class-string, array<int, string>>
*/
protected $listen = [
- Registered::class => [
- SendEmailVerificationNotification::class,
+ \SocialiteProviders\Manager\SocialiteWasCalled::class => [
+ // Extra providers
+ \SocialiteProviders\Keycloak\KeycloakExtendSocialite::class . '@handle',
],
];
diff --git a/src/app/User.php b/src/app/User.php
--- a/src/app/User.php
+++ b/src/app/User.php
@@ -61,6 +61,10 @@
// a restricted user
public const STATUS_RESTRICTED = 1 << 7;
+ public const ROLE_ADMIN = 'admin';
+ public const ROLE_GUEST = 'guest';
+ public const ROLE_RESELLER = 'reseller';
+
/** @var int The allowed states for this object used in StatusPropertyTrait */
private int $allowed_states = self::STATUS_NEW |
self::STATUS_ACTIVE |
diff --git a/src/app/Utils.php b/src/app/Utils.php
--- a/src/app/Utils.php
+++ b/src/app/Utils.php
@@ -486,6 +486,8 @@
$env['languages'] = \App\Http\Controllers\ContentController::locales();
$env['menu'] = \App\Http\Controllers\ContentController::menu();
+ $env['oauthProviders'] = \App\Http\Controllers\API\AuthController::oAuthConfig();
+
return $env;
}
diff --git a/src/composer.json b/src/composer.json
--- a/src/composer.json
+++ b/src/composer.json
@@ -25,17 +25,19 @@
"laravel/horizon": "^5.9",
"laravel/octane": "^2.0",
"laravel/passport": "^11.3",
+ "laravel/socialite": "^5.8",
"laravel/tinker": "^2.8",
+ "lcobucci/jwt": "^5.0",
"league/flysystem-aws-s3-v3": "^3.0",
"mlocati/spf-lib": "^3.1",
"mollie/laravel-mollie": "^2.22",
"pear/crypt_gpg": "^1.6.6",
"predis/predis": "^2.0",
"sabre/vobject": "^4.5",
+ "socialiteproviders/keycloak": "^5.3",
"spatie/laravel-translatable": "^6.5",
"spomky-labs/otphp": "~10.0.0",
- "stripe/stripe-php": "^10.7",
- "lcobucci/jwt": "^5.0"
+ "stripe/stripe-php": "^10.7"
},
"require-dev": {
"code-lts/doctum": "^5.5.1",
diff --git a/src/config/services.php b/src/config/services.php
--- a/src/config/services.php
+++ b/src/config/services.php
@@ -34,6 +34,17 @@
'secret' => env('SPARKPOST_SECRET'),
],
+ 'openexchangerates' => [
+ 'api_key' => env('OPENEXCHANGERATES_API_KEY', null),
+ ],
+
+
+ /*
+ |--------------------------------------------------------------------------
+ | Payment Providers
+ |--------------------------------------------------------------------------
+ */
+
'payment_provider' => env('PAYMENT_PROVIDER', 'mollie'),
'mollie' => [
@@ -52,9 +63,11 @@
'api_verify_tls' => env('COINBASE_VERIFY_TLS', true),
],
- 'openexchangerates' => [
- 'api_key' => env('OPENEXCHANGERATES_API_KEY', null),
- ],
+ /*
+ |--------------------------------------------------------------------------
+ | Kolab Services
+ |--------------------------------------------------------------------------
+ */
'dav' => [
'uri' => env('DAV_URI', 'https://proxy/'),
@@ -70,5 +83,43 @@
'webmail' => [
'uri' => env('WEBMAIL_URI', 'http://roundcube/roundcubemail/'),
- ]
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | OAuth Providers
+ |--------------------------------------------------------------------------
+ */
+
+ 'facebook' => [
+ 'client_id' => env('FACEBOOK_CLIENT_ID'),
+ 'client_secret' => env('FACEBOOK_CLIENT_SECRET'),
+ 'login_property' => 'nickname',
+ ],
+
+ 'github' => [
+ 'client_id' => env('GITHUB_CLIENT_ID'),
+ 'client_secret' => env('GITHUB_CLIENT_SECRET'),
+ 'login_property' => 'nickname',
+ ],
+
+ 'google' => [
+ 'client_id' => env('GOOGLE_CLIENT_ID'),
+ 'client_secret' => env('GOOGLE_CLIENT_SECRET'),
+ ],
+
+ 'keycloak' => [
+ 'client_id' => env('KEYCLOAK_CLIENT_ID'),
+ 'client_secret' => env('KEYCLOAK_CLIENT_SECRET'),
+ 'base_url' => env('KEYCLOAK_BASE_URL'), // Specify your keycloak server URL here
+ 'realms' => env('KEYCLOAK_REALM') // Specify your keycloak realm
+ ],
+
+ 'twitter' => [
+ 'client_id' => env('TWITTER_CLIENT_ID'),
+ 'client_secret' => env('TWITTER_CLIENT_SECRET'),
+ 'oauth' => 2, // enable the twitter-oauth-2 driver
+ 'login_property' => 'nickname',
+ ],
+
];
diff --git a/src/resources/vue/Dashboard.vue b/src/resources/vue/Dashboard.vue
--- a/src/resources/vue/Dashboard.vue
+++ b/src/resources/vue/Dashboard.vue
@@ -3,7 +3,7 @@
<status-component :status="status" @status-update="statusUpdate"></status-component>
<div id="dashboard-nav">
- <router-link class="card link-settings" :to="{ name: 'settings' }">
+ <router-link v-if="!$root.authInfo.isGuest" class="card link-settings" :to="{ name: 'settings' }">
<svg-icon icon="user-gear"></svg-icon><span>{{ $t('dashboard.myaccount') }}</span>
</router-link>
<router-link v-if="status.enableDomains" class="card link-domains" :to="{ name: 'domains' }">
@@ -37,7 +37,7 @@
<router-link v-if="status.enableSettings" class="card link-policies" :to="{ name: 'policies' }">
<svg-icon icon="shield-halved"></svg-icon><span>{{ $t('dashboard.policies') }}</span>
</router-link>
- <a v-if="webmailURL" class="card link-webmail" :href="webmailURL">
+ <a v-if="webmailURL && !$root.authInfo.isGuest" class="card link-webmail" :href="webmailURL">
<svg-icon icon="envelope"></svg-icon><span>{{ $t('dashboard.webmail') }}</span>
</a>
<router-link v-if="status.enableCompanionapps" class="card link-companionapp" :to="{ name: 'companions' }">
diff --git a/src/resources/vue/Login.vue b/src/resources/vue/Login.vue
--- a/src/resources/vue/Login.vue
+++ b/src/resources/vue/Login.vue
@@ -40,11 +40,13 @@
<router-link v-if="$root.isUser && $root.hasRoute('password-reset')" :to="{ name: 'password-reset' }" id="forgot-password">{{ $t('login.forgot_password') }}</router-link>
<a v-if="webmailURL && $root.isUser" :href="webmailURL" id="webmail">{{ $t('login.webmail') }}</a>
</div>
+ <o-auth-providers id="logon-oauth" class="mt-4"></o-auth-providers>
</div>
</template>
<script>
import { library } from '@fortawesome/fontawesome-svg-core'
+ import OAuthProviders from './Widgets/OAuthProviders'
library.add(
require('@fortawesome/free-solid-svg-icons/faKey').definition,
@@ -53,6 +55,9 @@
)
export default {
+ components: {
+ OAuthProviders
+ },
props: {
dashboard: { type: Boolean, default: true }
},
diff --git a/src/resources/vue/Page.vue b/src/resources/vue/Page.vue
--- a/src/resources/vue/Page.vue
+++ b/src/resources/vue/Page.vue
@@ -10,7 +10,27 @@
}
},
mounted() {
- let page = this.$root.pageName()
+ const page = this.$root.pageName()
+ const path = this.$route.path
+
+ // Returning from a OAuth provider site
+ if (path && path.startsWith('/oauth/callback')) {
+ if (window.config.userResponse) {
+ // User successfully OAuth-enticated
+ this.$root.loginUser(window.config.userResponse)
+ return
+ } else {
+ // Error
+ if (window.config.userError) {
+ this.content = '<p class="alert alert-danger">' + window.config.userError + '</p>'
+ delete window.config.userError
+ }
+
+ // TODO: Possibly go to the logon page or the original page
+ }
+
+ return
+ }
// Redirect / to /dashboard, if root page is not defined
if (page == '404' && this.$route.path == '/') {
@@ -22,7 +42,12 @@
.then(response => {
this.content = response.data
})
- .catch(this.$root.errorHandler)
+ .catch(error => {
+ // Skip the error handler if the route changed in meantime i.e. we're already on another page
+ if (this.$route.path == path) {
+ this.$root.errorHandler(error)
+ }
+ })
},
methods: {
clickHandler(event) {
diff --git a/src/resources/vue/Widgets/OAuthProviders.vue b/src/resources/vue/Widgets/OAuthProviders.vue
new file mode 100644
--- /dev/null
+++ b/src/resources/vue/Widgets/OAuthProviders.vue
@@ -0,0 +1,57 @@
+<template>
+ <div v-if="oauthProviders.length" class="oauth-providers">
+ <btn v-for="provider in oauthProviders" :key="provider" @click="click(provider)"
+ :class="`btn-outline-secondary oauth-${provider}`"
+ :icon="['fab', providerName(provider)]"
+ :is-loading="isLoading == provider"
+ >
+ {{ providerName(provider, true) }}
+ </btn>
+ </div>
+</template>
+
+<script>
+ import { library } from '@fortawesome/fontawesome-svg-core'
+
+ library.add(
+ require('@fortawesome/free-brands-svg-icons/faFacebook').definition,
+ require('@fortawesome/free-brands-svg-icons/faGithub').definition,
+ require('@fortawesome/free-brands-svg-icons/faGoogle').definition,
+ require('@fortawesome/free-brands-svg-icons/faTwitter').definition
+ )
+
+ export default {
+ data() {
+ return {
+ isLoading: null,
+ oauthProviders: window.config.oauthProviders
+ }
+ },
+ methods: {
+ click(provider) {
+ this.isLoading = provider
+
+ axios.post('/api/auth/oauth/' + provider)
+ .then(response => {
+ location.href = response.data.redirect
+
+ // Browser needs some time to jump to load the requested page
+ // let's wait a few seconds before we hide the loading icon
+ setTimeout(() => { this.isLoading = null }, 3000)
+ })
+ .catch(() => {
+ this.isLoading = null
+ })
+ },
+ providerName(provider, uc) {
+ let name = provider.split('-')[0]
+
+ if (uc) {
+ name = name.charAt(0).toUpperCase() + name.slice(1)
+ }
+
+ return name
+ }
+ }
+ }
+</script>
diff --git a/src/routes/api.php b/src/routes/api.php
--- a/src/routes/api.php
+++ b/src/routes/api.php
@@ -22,6 +22,7 @@
],
function () {
Route::post('login', [API\AuthController::class, 'login']);
+ Route::post('oauth/{provider}', API\AuthController::class . '@oAuthRedirect');
Route::group(
['middleware' => 'auth:api'],
diff --git a/src/routes/web.php b/src/routes/web.php
--- a/src/routes/web.php
+++ b/src/routes/web.php
@@ -54,5 +54,7 @@
'as' => 'tokens.destroy',
]);
});
+
+ Route::get('callback/{provider}', Controllers\API\AuthController::class . '@oAuthCallback');
}
);
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Apr 4, 10:26 PM (13 h, 28 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18831425
Default Alt Text
D4549.1775341596.diff (28 KB)
Attached To
Mode
D4549: [WIP] Third party authentication
Attached
Detach File
Event Timeline