Laravel AT Protocol Client (alpha & unstable)

Merge branch 'dev'

+13 -2
README.md
··· 531 531 $table->text('access_token'); // JWT access token 532 532 $table->text('refresh_token'); // Single-use refresh token 533 533 $table->timestamp('expires_at'); // Token expiration time 534 + $table->json('scope')->nullable(); // Granted OAuth scopes 534 535 $table->timestamps(); 535 536 }); 536 537 ``` ··· 564 565 expiresAt: $record->expires_at, 565 566 handle: $record->handle, 566 567 issuer: $record->issuer, 568 + scope: $record->scope ?? [], 567 569 ); 568 570 } 569 571 ··· 577 579 'access_token' => $token->accessJwt, 578 580 'refresh_token' => $token->refreshJwt, 579 581 'expires_at' => $token->expiresAt, 582 + 'scope' => $token->scope, 580 583 ] 581 584 ); 582 585 } ··· 587 590 'access_token' => $token->accessJwt, 588 591 'refresh_token' => $token->refreshJwt, 589 592 'expires_at' => $token->expiresAt, 590 - // Preserve handle and issuer, or update if provided 591 593 'handle' => $token->handle, 592 594 'issuer' => $token->issuer, 595 + 'scope' => $token->scope, 593 596 ]); 594 597 } 595 598 ··· 618 621 'access_token', 619 622 'refresh_token', 620 623 'expires_at', 624 + 'scope', 621 625 ]; 622 626 623 627 protected $casts = [ 624 628 'expires_at' => 'datetime', 629 + 'scope' => 'array', 625 630 ]; 626 631 627 632 protected $hidden = [ ··· 707 712 | `accessToken` | JWT for API authentication (short-lived) | 708 713 | `refreshToken` | Token to get new access tokens (single-use!) | 709 714 | `expiresAt` | When the access token expires | 715 + | `scope` | Array of granted scopes (e.g., `['atproto', 'transition:generic']`) | 710 716 711 717 ### Handling Token Refresh Events 712 718 ··· 736 742 use SocialDept\AtpClient\Facades\Atp; 737 743 738 744 Event::listen(OAuthUserAuthenticated::class, function (OAuthUserAuthenticated $event) { 739 - // $event->token contains: did, accessJwt, refreshJwt, handle, issuer, expiresAt 745 + // $event->token contains: did, accessJwt, refreshJwt, handle, issuer, expiresAt, scope 746 + 747 + // Check granted scopes 748 + if (in_array('atproto', $event->token->scope)) { 749 + // User granted AT Protocol access 750 + } 740 751 741 752 // Fetch the user's profile 742 753 $client = Atp::as($event->token->did);
+4 -1
src/Data/AccessToken.php
··· 11 11 public readonly \DateTimeInterface $expiresAt, 12 12 public readonly ?string $handle = null, 13 13 public readonly ?string $issuer = null, 14 + public readonly array $scope = [], 14 15 ) {} 15 16 16 17 /** ··· 30 31 expiresAt: now()->addSeconds($data['expires_in'] ?? 300), 31 32 handle: $handle, 32 33 issuer: $issuer, 34 + scope: isset($data['scope']) ? explode(' ', $data['scope']) : [], 33 35 ); 34 36 } 35 37 36 - // Legacy createSession format 38 + // Legacy createSession format (app passwords have full access) 37 39 return new self( 38 40 accessJwt: $data['accessJwt'], 39 41 refreshJwt: $data['refreshJwt'], ··· 41 43 expiresAt: now()->addSeconds($data['expiresIn'] ?? 300), 42 44 handle: $data['handle'] ?? $handle, 43 45 issuer: $issuer, 46 + scope: ['atproto', 'transition:generic', 'transition:email'], 44 47 ); 45 48 } 46 49 }
+1
src/Data/Credentials.php
··· 11 11 public readonly \DateTimeInterface $expiresAt, 12 12 public readonly ?string $handle = null, 13 13 public readonly ?string $issuer = null, 14 + public readonly array $scope = [], 14 15 ) {} 15 16 16 17 public function isExpired(): bool
+1
src/Providers/ArrayCredentialProvider.php
··· 29 29 expiresAt: $token->expiresAt, 30 30 handle: $token->handle, 31 31 issuer: $token->issuer, 32 + scope: $token->scope, 32 33 ); 33 34 } 34 35
+10
src/Session/Session.php
··· 53 53 return $this->credentials->expiresIn(); 54 54 } 55 55 56 + public function scopes(): array 57 + { 58 + return $this->credentials->scope; 59 + } 60 + 61 + public function hasScope(string $scope): bool 62 + { 63 + return in_array($scope, $this->credentials->scope, true); 64 + } 65 + 56 66 public function withCredentials(Credentials $credentials): self 57 67 { 58 68 return new self($credentials, $this->dpopKey, $this->pdsEndpoint);