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