Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"illuminate/validation": "^12.0",
"illuminate/view": "^12.0",
"intonate/tinker-zero": "^1.2",
"laracord/socialite-discord": "dev-next",
"laravel-zero/framework": "^12.0",
"laravel/sanctum": "^4.0",
"react/async": "^4.2",
Expand Down
14 changes: 14 additions & 0 deletions config/discord.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@

'token' => env('DISCORD_TOKEN', ''),

/*
|--------------------------------------------------------------------------
| Discord Client Secret
|--------------------------------------------------------------------------
|
| This is the client secret associated with your Discord application.
| It is used during the OAuth2 authorization process in conjunction
| with the client ID. Keep this secret safe and never expose it
| publicly, as it could be used to impersonate your application.
|
*/

'secret' => env('DISCORD_CLIENT_SECRET', ''),

/*
|--------------------------------------------------------------------------
| Gateway Intents
Expand Down
83 changes: 83 additions & 0 deletions src/Auth/DiscordAuthenticatable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?php

namespace Laracord\Auth;

trait DiscordAuthenticatable
{
/**
* Get the name of the unique identifier for the user.
* Using 'discord_id' as the primary identifier since it's unique.
*
* @return string
*/
public function getAuthIdentifierName()
{
return 'discord_id';
}

/**
* Get the unique identifier for the user.
*
* @return mixed
*/
public function getAuthIdentifier()
{
return $this->discord_id;
}

/**
* Get the name of the password attribute for the user.
* Since there are no passwords, we can return null or an empty string.
*
* @return string
*/
public function getAuthPasswordName()
{
return null;
}

/**
* Get the password for the user.
* Since there are no passwords, return null to ensure no password-based auth.
*
* @return string|null
*/
public function getAuthPassword()
{
return null;
}

/**
* Get the token value for the "remember me" session.
* Not using remember tokens for Discord-only auth.
*
* @return string|null
*/
public function getRememberToken()
{
return null;
}

/**
* Set the token value for the "remember me" session.
* Not using remember tokens for Discord-only auth.
*
* @param string $value
* @return void
*/
public function setRememberToken($value)
{
//
}

/**
* Get the column name for the "remember me" token.
* Not using remember tokens for Discord-only auth.
*
* @return string|null
*/
public function getRememberTokenName()
{
return null;
}
}
101 changes: 101 additions & 0 deletions src/Bot/Concerns/HasDiscordAuth.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php

namespace Laracord\Bot\Concerns;

use Closure;

trait HasDiscordAuth
{
/**
* The scopes that are requested during authentication with Discord.
*/
protected array $discordAuthScopes = ['identify'];


/**
* The URL that the user should be redirected to after successful authentication.
*/
protected string $discordLoggedInRedirect = '/';

/**
* The URL that the user should be redirected to after unsuccessful authentication.
*/
protected string $discordLogInFailedRedirect = '/';

/**
* The closure that should be executed after a successful Discord login.
*/
protected ?Closure $afterDiscordAuthCallback = null;

/**
* Set the discord Oauth2 scopes.
*/
public function withDiscordAuthScopes(array $discordAuthScopes): self
{
$this->discordAuthScopes = $discordAuthScopes;

return $this;
}

/**
* Get the discord Oauth2 scopes.
*/
public function getDiscordAuthScopes(): array
{
return $this->discordAuthScopes;
}

/**
* Set the redirect URL after successful Discord authentication.
*/
public function withDiscordLoggedInRedirect(string $url): self
{
$this->discordLoggedInRedirect = $url;

return $this;
}

/**
* Get the redirect URL after successful Discord authentication.
*/
public function getDiscordLoggedInRedirect(): string
{
return $this->discordLoggedInRedirect;
}

/**
* Set the redirect URL after failed Discord authentication.
*/
public function withDiscordLoginFailedRedirect(string $url): self
{
$this->discordLogInFailedRedirect = $url;

return $this;
}

/**
* Get the redirect URL after failed Discord authentication.
*/
public function getDiscordLoginFailedRedirect(): string
{
return $this->discordLogInFailedRedirect;
}

/**
* Set the closure that should be executed after a successful Discord login.
*/
public function afterDiscordAuthCallback(?Closure $afterDiscordAuthCallback): self
{
$this->afterDiscordAuthCallback = $afterDiscordAuthCallback;

return $this;
}

/**
* Get the closure that should be executed after a successful Discord login.
*/
public function getAfterDiscordAuthCallback(): ?Closure
{
return $this->afterDiscordAuthCallback;
}
}
44 changes: 44 additions & 0 deletions src/Http/Controllers/DiscordCallbackController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

namespace Laracord\Http\Controllers;

use Illuminate\Http\Request;
use Laravel\Socialite\Facades\Socialite;
use Illuminate\Support\Facades\Auth;
use Exception;

class DiscordCallbackController extends Controller
{
public function __invoke(Request $request)
{
try {
$discordUser = Socialite::driver('discord')
->setRequest($request)
->user();

$model = app('bot')->getUserModel();

if (! class_exists($model)) {
return redirect()->to(app('bot')->getDiscordLoginFailedRedirect());
}

$user = $model::updateOrCreate(
['discord_id' => $discordUser->getId()],
[
'username' => $discordUser->getName() ?? $discordUser->getNickname(),
]
);

$afterDiscordAuthCallback = app('bot')->getAfterDiscordAuthCallback();
if (is_callable($afterDiscordAuthCallback)) {
$afterDiscordAuthCallback($user, $discordUser);
}

auth()->login($user);

return redirect()->to(app('bot')->getDiscordLoggedInRedirect());
} catch (Exception) {
return redirect()->to(app('bot')->getDiscordLoginFailedRedirect());
}
}
}
17 changes: 17 additions & 0 deletions src/Http/Controllers/DiscordLoginController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace Laracord\Http\Controllers;

use Illuminate\Http\Request;
use Laravel\Socialite\Facades\Socialite;

class DiscordLoginController extends Controller
{
public function __invoke(Request $request)
{
return Socialite::driver('discord')
->setRequest($request)
->scopes(app('bot')->getDiscordAuthScopes())
->redirect();
}
}
21 changes: 21 additions & 0 deletions src/Http/Routing/UrlGenerator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Laracord\Http\Routing;

use Illuminate\Routing\UrlGenerator as BaseUrlGenerator;
use Illuminate\Contracts\Routing\UrlGenerator as UrlGeneratorContract;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

class UrlGenerator extends BaseUrlGenerator implements UrlGeneratorContract
{
public function __construct(Request $request = null)
{
if (!$request) {
$appUrl = rtrim(config('app.url', 'http://localhost'), '/');
$request = Request::create($appUrl);
}

parent::__construct(Route::getRoutes(), $request);
}
}
1 change: 1 addition & 0 deletions src/Laracord.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class Laracord
Concerns\HasConsole,
Concerns\HasContextMenus,
Concerns\HasDiscord,
Concerns\HasDiscordAuth,
Concerns\HasEvents,
Concerns\HasHooks,
Concerns\HasHttpServer,
Expand Down
38 changes: 37 additions & 1 deletion src/LaracordServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,24 @@
use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Foundation\Console\PackageDiscoverCommand;
use Illuminate\Foundation\PackageManifest as BasePackageManifest;
use Illuminate\Routing\Router;
use Illuminate\Routing\UrlGenerator;
use Illuminate\Support\AggregateServiceProvider;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Storage;
use Laracord\Bot\Hook;
use Laracord\Console\Commands;
use Laracord\Console\Console;
use Laracord\Console\Prompts;
use Laracord\Discord\Message;
use Laracord\Http\Kernel;
use Laracord\Http\Controllers\DiscordCallbackController;
use Laracord\Http\Controllers\DiscordLoginController;
use Laracord\Http\Routing\UrlGenerator as LaracordUrlGenerator;
use LaravelZero\Framework\Components\Database\Provider as DatabaseProvider;
use LaravelZero\Framework\Components\Log\Provider as LogProvider;
use React\EventLoop\Loop;
Expand Down Expand Up @@ -99,6 +106,7 @@ public function register()
$this->registerLoop();
$this->registerConsole();
$this->registerLogger();
$this->registerUrlGenerator();

$this->app->singleton(KernelContract::class, Kernel::class);
$this->app->singleton(Middleware::class, fn () => new Middleware);
Expand All @@ -116,7 +124,22 @@ public function register()

$this->app->singleton(Message::class, fn () => Message::make($bot));

return $this->bot($bot);
return $this->bot($bot)
->registerHook(Hook::AFTER_HTTP_SERVER_START, function (Laracord $bot) {
config([
'services.discord' => [
'client_id' => $bot->discord()->id,
'client_secret' => config('discord.secret'),
'redirect' => route('oauth.discord.callback'),
],
]);
})
->withRoutes(function (Router $router) {
Route::middleware('web')->group(function() {
Route::get('/oauth/discord/login', DiscordLoginController::class)->name('oauth.discord.login');
Route::get('/oauth/discord/callback', DiscordCallbackController::class)->name('oauth.discord.callback');
});
});
}));

$this->app->alias(Laracord::class, 'bot');
Expand Down Expand Up @@ -236,6 +259,19 @@ protected function registerLogger(): void
$this->app->booting(fn () => $this->app->register(LogProvider::class));
}

/**
* Register the URL generator.
*/
protected function registerUrlGenerator(): void
{
$this->app->bind(UrlGenerator::class, function ($app) {
$request = $app->has('request') ? $app->make('request') : null;
return new LaracordUrlGenerator($request);
});

$this->app->alias(UrlGenerator::class, 'url');
}

/**
* Register the default components.
*/
Expand Down