Something went wrong. Try again.
Laravel AT Protocol Client (alpha & unstable)
Something went wrong. Try again.
5.0 kB · 170 lines
PHP
at dev
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171<?php
namespace SocialDept\AtpClient\Session;
use Illuminate\Support\Facades\Http;use SocialDept\AtpClient\Auth\DPoPKeyManager;use SocialDept\AtpClient\Auth\TokenRefresher;use SocialDept\AtpClient\Contracts\CredentialProvider;use SocialDept\AtpClient\Contracts\KeyStore;use SocialDept\AtpClient\Data\AccessToken;use SocialDept\AtpClient\Events\SessionAuthenticated;use SocialDept\AtpClient\Events\SessionRefreshing;use SocialDept\AtpClient\Events\SessionUpdated;use SocialDept\AtpClient\Exceptions\AuthenticationException;use SocialDept\AtpClient\Exceptions\HandleResolutionException;use SocialDept\AtpClient\Exceptions\SessionExpiredException;use SocialDept\AtpResolver\Facades\Resolver;use SocialDept\AtpResolver\Support\Identity;
class SessionManager{ protected array $sessions = [];
public function __construct( protected CredentialProvider $credentials, protected TokenRefresher $refresher, protected DPoPKeyManager $dpopManager, protected KeyStore $keyStore, protected int $refreshThreshold = 300, // 5 minutes ) {}
/** * Resolve an actor (handle or DID) to a DID. * * @throws HandleResolutionException */ protected function resolveToDid(string $actor): string { // If already a DID, return as-is if (Identity::isDid($actor)) { return $actor; }
// Resolve handle to DID $did = Resolver::handleToDid($actor);
if (! $did) { throw new HandleResolutionException($actor); }
return $did; }
/** * Get or create session for an actor. */ public function session(string $actor): Session { $did = $this->resolveToDid($actor);
if (! isset($this->sessions[$did])) { $this->sessions[$did] = $this->createSession($did); }
return $this->sessions[$did]; }
/** * Ensure session is valid, refresh if needed. */ public function ensureValid(string $actor): Session { $session = $this->session($actor);
// Check if token needs refresh if ($session->expiresIn() < $this->refreshThreshold) { $session = $this->refreshSession($session); }
return $session; }
/** * Create session from app password. */ public function fromAppPassword( string $actor, string $password ): Session { $did = $this->resolveToDid($actor); $pdsEndpoint = Resolver::resolvePds($did);
$response = Http::post($pdsEndpoint.'/xrpc/com.atproto.server.createSession', [ 'identifier' => $actor, 'password' => $password, ]);
if ($response->failed()) { throw new AuthenticationException('Login failed'); }
$token = AccessToken::fromResponse($response->json(), $actor, $pdsEndpoint);
// Store credentials using DID as key $this->credentials->storeCredentials($did, $token);
event(new SessionAuthenticated($token));
return $this->createSession($did); }
/** * Create session from credentials */ protected function createSession(string $did): Session { $creds = $this->credentials->getCredentials($did);
if (! $creds) { throw new SessionExpiredException("No credentials found for {$did}"); }
// Get or create DPoP key $sessionId = 'session_'.hash('sha256', $creds->did); $dpopKey = $this->keyStore->get($sessionId);
if (! $dpopKey) { $dpopKey = $this->dpopManager->generateKey($sessionId); }
// Use stored issuer if available, otherwise resolve PDS endpoint $pdsEndpoint = $creds->issuer ?? Resolver::resolvePds($creds->did);
return new Session($creds, $dpopKey, $pdsEndpoint); }
/** * Refresh session tokens */ protected function refreshSession(Session $session): Session { $did = $session->did();
// Fire event before refresh (allows developers to invalidate old token) event(new SessionRefreshing($session));
$newToken = $this->refresher->refresh( refreshToken: $session->refreshToken(), pdsEndpoint: $session->pdsEndpoint(), dpopKey: $session->dpopKey(), handle: $session->handle(), authType: $session->authType(), );
// Update credentials (CRITICAL: refresh tokens are single-use) $this->credentials->updateCredentials($did, $newToken);
// Fire event after successful refresh event(new SessionUpdated($session, $newToken));
// Update session $newCreds = $this->credentials->getCredentials($did); $newSession = $session->withCredentials($newCreds);
// Update cached session $this->sessions[$did] = $newSession;
return $newSession; }}