Configurable refresh token lifetimes.

This commit is contained in:
flash 2024-07-27 03:42:33 +00:00
parent dd968eae44
commit 61dad487f6
5 changed files with 39 additions and 23 deletions

View file

@ -0,0 +1,9 @@
<?php
use Index\Data\IDbConnection;
use Index\Data\Migration\IDbMigration;
final class ConfigurableRefreshLifetime_20240727_025524 implements IDbMigration {
public function migrate(IDbConnection $conn): void {
$conn->execute('ALTER TABLE hau_apps ADD COLUMN app_refresh_lifetime INT UNSIGNED NULL DEFAULT NULL AFTER app_type');
}
}

View file

@ -11,6 +11,7 @@ class AppInfo {
private string $website, private string $website,
private bool $trusted, private bool $trusted,
private string $type, private string $type,
private ?int $refreshLifetime,
private string $clientId, private string $clientId,
private string $clientSecret, private string $clientSecret,
private int $created, private int $created,
@ -26,11 +27,12 @@ class AppInfo {
website: $result->getString(3), website: $result->getString(3),
trusted: $result->getBoolean(4), trusted: $result->getBoolean(4),
type: $result->getString(5), type: $result->getString(5),
clientId: $result->getString(6), refreshLifetime: $result->getIntegerOrNull(6),
clientSecret: $result->getString(7), clientId: $result->getString(7),
created: $result->getInteger(8), clientSecret: $result->getString(8),
updated: $result->getInteger(9), created: $result->getInteger(9),
deleted: $result->getIntegerOrNull(10), updated: $result->getInteger(10),
deleted: $result->getIntegerOrNull(11),
); );
} }
@ -71,6 +73,16 @@ class AppInfo {
return strcasecmp($this->type, 'confidential') === 0; return strcasecmp($this->type, 'confidential') === 0;
} }
public function getRefreshTokenLifetime(): ?int {
return $this->refreshLifetime;
}
public function shouldIssueRefreshToken(): bool {
if($this->refreshLifetime === null)
return $this->isConfidential();
return $this->refreshLifetime > 0;
}
public function getClientId(): string { public function getClientId(): string {
return $this->clientId; return $this->clientId;
} }

View file

@ -28,7 +28,7 @@ class AppsData {
throw new InvalidArgumentException('you must specify either $appId or $clientId'); throw new InvalidArgumentException('you must specify either $appId or $clientId');
$values = []; $values = [];
$query = 'SELECT app_id, app_name, app_summary, app_website, app_trusted, app_type, app_client_id, app_client_secret, UNIX_TIMESTAMP(app_created), UNIX_TIMESTAMP(app_updated), UNIX_TIMESTAMP(app_deleted) FROM hau_apps'; $query = 'SELECT app_id, app_name, app_summary, app_website, app_trusted, app_type, app_refresh_lifetime, app_client_id, app_client_secret, UNIX_TIMESTAMP(app_created), UNIX_TIMESTAMP(app_updated), UNIX_TIMESTAMP(app_deleted) FROM hau_apps';
$query .= sprintf(' WHERE %s = ?', $hasAppId ? 'app_id' : 'app_client_id'); $query .= sprintf(' WHERE %s = ?', $hasAppId ? 'app_id' : 'app_client_id');
if($hasDeleted) if($hasDeleted)
$query .= sprintf(' AND app_deleted %s NULL', $deleted ? 'IS NOT' : 'IS'); $query .= sprintf(' AND app_deleted %s NULL', $deleted ? 'IS NOT' : 'IS');

View file

@ -27,18 +27,16 @@ class OAuth2Context {
return $this->devices; return $this->devices;
} }
public function createRefreshFromAccessInfo( public function createRefresh(
OAuth2AccessInfo $accessInfo, AppInfo $appInfo,
?string $token = null, OAuth2AccessInfo $accessInfo
?int $lifetime = null
): OAuth2RefreshInfo { ): OAuth2RefreshInfo {
return $this->tokens->createRefresh( return $this->tokens->createRefresh(
$accessInfo->getAppId(), $accessInfo->getAppId(),
$accessInfo, $accessInfo,
$accessInfo->getUserId(), userId: $accessInfo->getUserId(),
$token, scope: $accessInfo->getScope(),
$accessInfo->getScope(), lifetime: $appInfo->getRefreshTokenLifetime()
$lifetime
); );
} }

View file

@ -595,9 +595,8 @@ final class OAuth2Routes extends RouteHandler {
if($scope === $authoriseInfo->getScope()) if($scope === $authoriseInfo->getScope())
unset($scope); unset($scope);
// this should probably check something else if($appInfo->shouldIssueRefreshToken())
if($appInfo->isConfidential()) $refreshInfo = $this->oauth2Ctx->createRefresh($appInfo, $accessInfo);
$refreshInfo = $this->oauth2Ctx->createRefreshFromAccessInfo($accessInfo);
} elseif($type === 'refresh_token') { } elseif($type === 'refresh_token') {
$tokensData = $this->oauth2Ctx->getTokensData(); $tokensData = $this->oauth2Ctx->getTokensData();
try { try {
@ -639,9 +638,8 @@ final class OAuth2Routes extends RouteHandler {
unset($refreshInfo); unset($refreshInfo);
// this should probably check something else if($appInfo->shouldIssueRefreshToken())
if($appInfo->isConfidential()) $refreshInfo = $this->oauth2Ctx->createRefresh($appInfo, $accessInfo);
$refreshInfo = $this->oauth2Ctx->createRefreshFromAccessInfo($accessInfo);
} elseif($type === 'client_credentials') { } elseif($type === 'client_credentials') {
if(!$appInfo->isConfidential()) { if(!$appInfo->isConfidential()) {
$response->setStatusCode(400); $response->setStatusCode(400);
@ -723,9 +721,8 @@ final class OAuth2Routes extends RouteHandler {
if($scope === $deviceInfo->getScope()) if($scope === $deviceInfo->getScope())
unset($scope); unset($scope);
// this should probably check something else if($appInfo->shouldIssueRefreshToken())
if($appInfo->isConfidential()) $refreshInfo = $this->oauth2Ctx->createRefresh($appInfo, $accessInfo);
$refreshInfo = $this->oauth2Ctx->createRefreshFromAccessInfo($accessInfo);
} else { } else {
$response->setStatusCode(400); $response->setStatusCode(400);
return self::error('unsupported_grant_type', 'Requested grant type is not supported by this server.'); return self::error('unsupported_grant_type', 'Requested grant type is not supported by this server.');