Laravel AT Protocol Client (alpha & unstable)
at v0.0.45 6.8 kB view raw
1<?php 2 3namespace SocialDept\AtpClient; 4 5use Illuminate\Routing\Router; 6use Illuminate\Support\Facades\Route; 7use Illuminate\Support\ServiceProvider; 8use SocialDept\AtpClient\Auth\ClientAssertionManager; 9use SocialDept\AtpClient\Auth\ClientMetadataManager; 10use SocialDept\AtpClient\Auth\DPoPKeyManager; 11use SocialDept\AtpClient\Auth\DPoPNonceManager; 12use SocialDept\AtpClient\Auth\OAuthEngine; 13use SocialDept\AtpClient\Auth\ScopeChecker; 14use SocialDept\AtpClient\Auth\ScopeGate; 15use SocialDept\AtpClient\Auth\TokenRefresher; 16use SocialDept\AtpClient\Enums\ScopeEnforcementLevel; 17use SocialDept\AtpClient\Http\Middleware\RequiresScopeMiddleware; 18use SocialDept\AtpClient\Console\GenerateOAuthKeyCommand; 19use SocialDept\AtpClient\Console\MakeAtpClientCommand; 20use SocialDept\AtpClient\Console\MakeAtpRequestCommand; 21use SocialDept\AtpClient\Contracts\CredentialProvider; 22use SocialDept\AtpClient\Contracts\KeyStore; 23use SocialDept\AtpClient\Http\Controllers\ClientMetadataController; 24use SocialDept\AtpClient\Http\Controllers\JwksController; 25use SocialDept\AtpClient\Http\DPoPClient; 26use SocialDept\AtpClient\Session\SessionManager; 27use SocialDept\AtpClient\Storage\EncryptedFileKeyStore; 28use SocialDept\AtpClient\Client\Public\AtpPublicClient; 29 30class AtpClientServiceProvider extends ServiceProvider 31{ 32 /** 33 * Register any package services. 34 */ 35 public function register(): void 36 { 37 $this->mergeConfigFrom(__DIR__.'/../config/client.php', 'atp-client'); 38 39 // Register contracts 40 $this->app->singleton(CredentialProvider::class, function ($app) { 41 $provider = config('client.credential_provider'); 42 43 return new $provider(); 44 }); 45 46 $this->app->singleton(KeyStore::class, function ($app) { 47 return new EncryptedFileKeyStore( 48 storage_path('app/atp-keys') 49 ); 50 }); 51 52 // Register core services 53 $this->app->singleton(ClientMetadataManager::class); 54 $this->app->singleton(ClientAssertionManager::class); 55 $this->app->singleton(DPoPKeyManager::class); 56 $this->app->singleton(DPoPNonceManager::class); 57 $this->app->singleton(DPoPClient::class); 58 $this->app->singleton(TokenRefresher::class); 59 $this->app->singleton(SessionManager::class, function ($app) { 60 return new SessionManager( 61 credentials: $app->make(CredentialProvider::class), 62 refresher: $app->make(TokenRefresher::class), 63 dpopManager: $app->make(DPoPKeyManager::class), 64 keyStore: $app->make(KeyStore::class), 65 refreshThreshold: config('client.session.refresh_threshold', 300), 66 ); 67 }); 68 $this->app->singleton(OAuthEngine::class); 69 $this->app->singleton(ScopeChecker::class, function ($app) { 70 return new ScopeChecker( 71 config('atp-client.scope_enforcement', ScopeEnforcementLevel::Permissive) 72 ); 73 }); 74 75 // Register ScopeGate for AtpScope facade 76 $this->app->singleton('atp-scope', function ($app) { 77 return new ScopeGate( 78 $app->make(SessionManager::class), 79 $app->make(ScopeChecker::class), 80 ); 81 }); 82 83 // Register main client facade accessor 84 $this->app->bind('atp-client', function ($app) { 85 return new class($app) 86 { 87 protected $app; 88 89 protected ?CredentialProvider $defaultProvider = null; 90 91 public function __construct($app) 92 { 93 $this->app = $app; 94 } 95 96 public function as(string $actor): AtpClient 97 { 98 return new AtpClient( 99 $this->app->make(SessionManager::class), 100 $actor 101 ); 102 } 103 104 public function login(string $actor, string $password): AtpClient 105 { 106 $this->app->make(SessionManager::class) 107 ->fromAppPassword($actor, $password); 108 109 return $this->as($actor); 110 } 111 112 public function oauth(): OAuthEngine 113 { 114 return $this->app->make(OAuthEngine::class); 115 } 116 117 public function setDefaultProvider(CredentialProvider $provider): void 118 { 119 $this->defaultProvider = $provider; 120 $this->app->instance(CredentialProvider::class, $provider); 121 } 122 123 public function public(?string $service = null): AtpPublicClient 124 { 125 return new AtpPublicClient( 126 $service ?? config('atp-client.public.service_url', 'https://public.api.bsky.app') 127 ); 128 } 129 }; 130 }); 131 } 132 133 /** 134 * Perform post-registration booting of services. 135 */ 136 public function boot(): void 137 { 138 if ($this->app->runningInConsole()) { 139 $this->publishes([ 140 __DIR__.'/../config/client.php' => config_path('client.php'), 141 ], 'atp-client-config'); 142 143 $this->commands([ 144 GenerateOAuthKeyCommand::class, 145 MakeAtpClientCommand::class, 146 MakeAtpRequestCommand::class, 147 ]); 148 } 149 150 $this->registerRoutes(); 151 $this->registerMiddleware(); 152 } 153 154 /** 155 * Register middleware aliases 156 */ 157 protected function registerMiddleware(): void 158 { 159 /** @var Router $router */ 160 $router = $this->app->make(Router::class); 161 $router->aliasMiddleware('atp.scope', RequiresScopeMiddleware::class); 162 } 163 164 /** 165 * Register OAuth metadata routes 166 */ 167 protected function registerRoutes(): void 168 { 169 if (config('client.oauth.disabled')) { 170 return; 171 } 172 173 $prefix = config('client.oauth.prefix', '/atp/oauth/'); 174 175 Route::prefix($prefix)->group(function () { 176 Route::get('client-metadata.json', ClientMetadataController::class) 177 ->name('atp.oauth.client-metadata'); 178 179 Route::get('jwks.json', JwksController::class) 180 ->name('atp.oauth.jwks'); 181 }); 182 183 // Register recommended client id convention (see: https://atproto.com/guides/oauth#clients) 184 Route::get('oauth-client-metadata.json', ClientMetadataController::class) 185 ->name('atp.oauth.json'); 186 } 187 188 /** 189 * Get the services provided by the provider. 190 * 191 * @return array<string> 192 */ 193 public function provides(): array 194 { 195 return ['atp-client', 'atp-scope']; 196 } 197}