Skip to content

Commit

Permalink
feat: sync authentication token with tbp-consumer-api (#3)
Browse files Browse the repository at this point in the history
## Motivation

The Consumer API needs to get some verification token to validate if the
user should send the metrics heartbeat.

Now the platform also syncs the user token which will expires together.
  • Loading branch information
danielhe4rt authored Aug 19, 2024
1 parent 6049777 commit 21d8fb9
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 46 deletions.
19 changes: 19 additions & 0 deletions app/Clients/Consumer/ConsumerClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Clients\Consumer;

use App\Clients\Consumer\DTO\UserTokenDTO;
use App\Models\Settings\Settings;
use App\Models\User;
use ChrisReedIO\Socialment\Models\ConnectedAccount;
Expand Down Expand Up @@ -53,4 +54,22 @@ public function updateUser(User $user): void
throw new ConsumerClientException('Failed to update user settings');
}
}

public function sendUserToken(UserTokenDTO $tokenDTO): void
{
$uri = $this->baseVersionedUrl.'/authenticate';

$payload = [
'user_id' => $tokenDTO->userId,
'token' => $tokenDTO->authDTO->accessToken,
];

$response = $this->client->post($uri, [
'json' => $payload,
]);

if ($response->getStatusCode() !== 201) {
throw new ConsumerClientException('Failed to send user token');
}
}
}
21 changes: 21 additions & 0 deletions app/Clients/Consumer/DTO/UserTokenDTO.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace App\Clients\Consumer\DTO;

use App\DTO\AuthorizationDTO;

readonly class UserTokenDTO
{
public function __construct(
public AuthorizationDTO $authDTO,
public int $userId,
) {}

public static function factory(AuthorizationDTO $authenticationDTO, int $userId): UserTokenDTO
{
return new UserTokenDTO(
authDTO: $authenticationDTO,
userId: $userId,
);
}
}
21 changes: 21 additions & 0 deletions app/DTO/AuthenticationDTO.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace App\DTO;

use App\Models\User;

readonly class AuthenticationDTO
{
public function __construct(
public AuthorizationDTO $authorization,
public User $user,
) {}

public static function factory(AuthorizationDTO $authorization, User $user): AuthenticationDTO
{
return new AuthenticationDTO(
authorization: $authorization,
user: $user,
);
}
}
31 changes: 31 additions & 0 deletions app/DTO/AuthorizationDTO.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace App\DTO;

use Carbon\Carbon;

readonly class AuthorizationDTO implements \JsonSerializable
{
public function __construct(
public string $accessToken,
public Carbon $expiresAt,
public string $token = 'Bearer',
) {}

public static function factory($accessToken, $expiresAt): AuthorizationDTO
{
return new AuthorizationDTO(
accessToken: $accessToken,
expiresAt: $expiresAt,
);
}

public function jsonSerialize(): array
{
return [
'access_token' => $this->accessToken,
'token_type' => $this->token,
'expires_at' => $this->expiresAt->toIso8601String(),
];
}
}
105 changes: 61 additions & 44 deletions app/Http/Controllers/Api/V1/OAuthController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,48 @@

namespace App\Http\Controllers\Api\V1;

use App\Clients\Consumer\ConsumerClient;
use App\Clients\Consumer\DTO\UserTokenDTO;
use App\DTO\AuthenticationDTO;
use App\DTO\AuthorizationDTO;
use App\Http\Controllers\Controller;
use ChrisReedIO\Socialment\Exceptions\AbortedLoginException;
use ChrisReedIO\Socialment\Facades\Socialment;
use ChrisReedIO\Socialment\Models\ConnectedAccount;
use Illuminate\Http\Request;
use Laravel\Socialite\Facades\Socialite;

class OAuthController extends Controller
{
public function authenticateWithOAuth(Request $request, string $provider)
public function __construct(private readonly ConsumerClient $consumerClient) {}

public function authenticateWithOAuth(string $provider)
{
$socialUser = Socialite::driver($provider)
->stateless()
->user();

$tokenExpiration = $socialUser->expiresIn
? now()->addSeconds($socialUser->expiresIn)
: null;
$response = $this->registerAndAuthenticate($provider, $socialUser);
$this->consumerClient->sendUserToken(UserTokenDTO::factory(
$response->authorization,
$response->user->id
));

// Create a user or log them in...
$connectedAccount = ConnectedAccount::firstOrNew([
'provider' => $provider,
'provider_user_id' => $socialUser->getId(),
], [
'name' => $socialUser->getName(),
'nickname' => $socialUser->getNickname(),
'email' => $socialUser->getEmail(),
'avatar' => $socialUser->getAvatar(),
'token' => $socialUser->token,
'refresh_token' => $socialUser->refreshToken,
'expires_at' => $tokenExpiration,
]);
return response()->json($response);

if (! $connectedAccount->exists) {
// Check for an existing user with this email
// Create a new user if one doesn't exist
$user = Socialment::createUser($connectedAccount);
}

if ($user === null) {
throw new AbortedLoginException('This account is not authorized to log in.');
}
public function registerAndAuthenticate(string $provider, $socialUser): AuthenticationDTO
{
$tokenExpiration = $socialUser->expiresIn
? now()->addSeconds($socialUser->expiresIn)
: null;

// Associate the user and save this connected account
$connectedAccount->user()->associate($user)->save();
} else {
// Update the connected account with the latest data
$connectedAccount->update([
return \DB::transaction(function () use ($provider, $socialUser, $tokenExpiration) {
// Create a user or log them in...
$connectedAccount = ConnectedAccount::firstOrNew([
'provider' => $provider,
'provider_user_id' => $socialUser->getId(),
], [
'name' => $socialUser->getName(),
'nickname' => $socialUser->getNickname(),
'email' => $socialUser->getEmail(),
Expand All @@ -57,20 +52,42 @@ public function authenticateWithOAuth(Request $request, string $provider)
'refresh_token' => $socialUser->refreshToken,
'expires_at' => $tokenExpiration,
]);
}
// AccessToken
$accessToken = $connectedAccount
->user
->createToken('authToken', ['*'], $tokenExpiration)
->plainTextToken;

return response()->json([
'authorization' => [
'access_token' => $accessToken,
'token_type' => 'Bearer',
'expires_at' => $tokenExpiration,
],
'user' => $connectedAccount->user->load(['settings' => fn ($query) => $query->with('occupation'), 'accounts']),
]);
if (! $connectedAccount->exists) {
// Check for an existing user with this email
// Create a new user if one doesn't exist
$user = Socialment::createUser($connectedAccount);

if ($user === null) {
throw new AbortedLoginException('This account is not authorized to log in.');
}

// Associate the user and save this connected account
$connectedAccount->user()->associate($user)->save();
} else {
// Update the connected account with the latest data
$connectedAccount->update([
'name' => $socialUser->getName(),
'nickname' => $socialUser->getNickname(),
'email' => $socialUser->getEmail(),
'avatar' => $socialUser->getAvatar(),
'token' => $socialUser->token,
'refresh_token' => $socialUser->refreshToken,
'expires_at' => $tokenExpiration,
]);
}
// AccessToken
$accessToken = $connectedAccount
->user
->createToken('authToken', ['*'], $tokenExpiration)
->plainTextToken;

$user = $connectedAccount->user()->with(['settings.occupation', 'accounts'])->first();

return AuthenticationDTO::factory(
AuthorizationDTO::factory($accessToken, $tokenExpiration),
$user,
);
});
}
}
2 changes: 1 addition & 1 deletion app/Models/Settings/Settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class Settings extends Model
'is_developer' => 'boolean',
];

public function getPronounsAttribute()
public function getPronounsAttribute(): array
{
return config('extension.pronouns.'.$this->attributes['pronouns']);
}
Expand Down
2 changes: 1 addition & 1 deletion config/extension.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
return [
'pronouns' => [
'none' => [
'name' => 'n/d',
'name' => 'None',
'translation_key' => 'None',
'slug' => 'none',
],
Expand Down

0 comments on commit 21d8fb9

Please sign in to comment.