From c91fa693627a3211007b5203e51484fccb3ad1da Mon Sep 17 00:00:00 2001
From: flashwave <me@flash.moe>
Date: Wed, 23 Apr 2025 21:56:33 +0000
Subject: [PATCH] Altered default token lifetimes and also made them
 configurable.

---
 src/OAuth2/OAuth2AccessInfo.php  |  3 +-
 src/OAuth2/OAuth2Context.php     | 69 ++++++++++++++++++++++++++------
 src/OAuth2/OAuth2DeviceInfo.php  |  4 +-
 src/OAuth2/OAuth2RefreshInfo.php |  3 ++
 4 files changed, 63 insertions(+), 16 deletions(-)

diff --git a/src/OAuth2/OAuth2AccessInfo.php b/src/OAuth2/OAuth2AccessInfo.php
index 0fe0d7b9..bd899fda 100644
--- a/src/OAuth2/OAuth2AccessInfo.php
+++ b/src/OAuth2/OAuth2AccessInfo.php
@@ -5,7 +5,8 @@ use Carbon\CarbonImmutable;
 use Index\Db\DbResult;
 
 class OAuth2AccessInfo {
-    public const int DEFAULT_LIFETIME = 3600;
+    public const int LIFETIME_PUBLIC = 60 * 60; // 1 hour for public clients
+    public const int LIFETIME_CONFIDENTIAL = 3 * 60 * 60; // 3 hours for confidential clients
 
     public function __construct(
         public private(set) string $id,
diff --git a/src/OAuth2/OAuth2Context.php b/src/OAuth2/OAuth2Context.php
index e0b522f1..865e096e 100644
--- a/src/OAuth2/OAuth2Context.php
+++ b/src/OAuth2/OAuth2Context.php
@@ -15,6 +15,34 @@ class OAuth2Context {
     public private(set) OAuth2DevicesData $devices;
     public private(set) OAuth2Keys $keys;
 
+    public string $userInfoWebsiteProfileField {
+        get => $this->config->getString('userinfo_website_profile_field');
+    }
+
+    public string $deviceVerificationUri {
+        get => $this->config->getString('device.verification_uri');
+    }
+
+    public string $deviceVerificationCompleteUriFormat {
+        get => $this->config->getString('device.verification_uri_complete');
+    }
+
+    public int $accessTokenLifetimePublic {
+        get => $this->config->getInteger('access_lifetime_public', OAuth2AccessInfo::LIFETIME_PUBLIC);
+    }
+
+    public int $accessTokenLifetimeConfidential {
+        get => $this->config->getInteger('access_lifetime_confidential', OAuth2AccessInfo::LIFETIME_CONFIDENTIAL);
+    }
+
+    public int $refreshTokenLifetimePublic {
+        get => $this->config->getInteger('refresh_lifetime_public', OAuth2RefreshInfo::LIFETIME_PUBLIC);
+    }
+
+    public int $refreshTokenLifetimeConfidential {
+        get => $this->config->getInteger('refresh_lifetime_confidential', OAuth2RefreshInfo::LIFETIME_CONFIDENTIAL);
+    }
+
     public function __construct(
         private Config $config,
         DbConnection $dbConn,
@@ -27,8 +55,26 @@ class OAuth2Context {
         $this->keys = new OAuth2Keys($config->getArray('keys'));
     }
 
-    public string $userInfoWebsiteProfileField {
-        get => $this->config->getString('userinfo_website_profile_field');
+    public function formatDeviceVerificationCompleteUri(string $code): string {
+        return sprintf($this->deviceVerificationCompleteUriFormat, $code);
+    }
+
+    public function getAccessTokenLifetime(AppInfo $appInfo): int {
+        if($appInfo->accessTokenLifetime !== null)
+            return $appInfo->accessTokenLifetime;
+
+        return $appInfo->confidential
+            ? $this->accessTokenLifetimeConfidential
+            : $this->accessTokenLifetimePublic;
+    }
+
+    public function getRefreshTokenLifetime(AppInfo $appInfo): int {
+        if($appInfo->refreshTokenLifetime !== null)
+            return $appInfo->refreshTokenLifetime;
+
+        return $appInfo->confidential
+            ? $this->refreshTokenLifetimeConfidential
+            : $this->refreshTokenLifetimePublic;
     }
 
     /**
@@ -63,7 +109,7 @@ class OAuth2Context {
             $appInfo,
             userInfo: $userInfo,
             scope: $scope,
-            lifetime: $appInfo->accessTokenLifetime,
+            lifetime: $this->getAccessTokenLifetime($appInfo),
         );
     }
 
@@ -76,7 +122,7 @@ class OAuth2Context {
             $appInfo,
             userInfo: $accessInfo->userId,
             scope: $accessInfo->scope,
-            lifetime: $appInfo->refreshTokenLifetime,
+            lifetime: $this->getRefreshTokenLifetime($appInfo),
         );
     }
 
@@ -181,16 +227,16 @@ class OAuth2Context {
         $result = [
             'device_code' => $deviceInfo->code,
             'user_code' => $userCode,
-            'verification_uri' => $this->config->getString('device.verification_uri'),
-            'verification_uri_complete' => sprintf($this->config->getString('device.verification_uri_complete'), $userCode),
+            'verification_uri' => $this->deviceVerificationUri,
+            'verification_uri_complete' => $this->formatDeviceVerificationCompleteUri($userCode),
         ];
 
         $expiresIn = $deviceInfo->remainingLifetime;
-        if($expiresIn < OAuth2DeviceInfo::DEFAULT_LIFETIME)
+        if($expiresIn < OAuth2DeviceInfo::LIFETIME)
             $result['expires_in'] = $expiresIn;
 
         $interval = $deviceInfo->interval;
-        if($interval > OAuth2DeviceInfo::DEFAULT_POLL_INTERVAL)
+        if($interval > OAuth2DeviceInfo::POLL_INTERVAL)
             $result['interval'] = $interval;
 
         return $result;
@@ -212,14 +258,11 @@ class OAuth2Context {
         ?string $scope = null
     ): array {
         $result = [
-            'access_token' => $accessInfo->token,
             'token_type' => 'Bearer',
+            'access_token' => $accessInfo->token,
+            'expires_in' => $accessInfo->remainingLifetime,
         ];
 
-        $expiresIn = $accessInfo->remainingLifetime;
-        if($expiresIn < OAuth2AccessInfo::DEFAULT_LIFETIME)
-            $result['expires_in'] = $expiresIn;
-
         if($scope !== null)
             $result['scope'] = $scope;
 
diff --git a/src/OAuth2/OAuth2DeviceInfo.php b/src/OAuth2/OAuth2DeviceInfo.php
index 41e4bb90..d6d30328 100644
--- a/src/OAuth2/OAuth2DeviceInfo.php
+++ b/src/OAuth2/OAuth2DeviceInfo.php
@@ -5,8 +5,8 @@ use Carbon\CarbonImmutable;
 use Index\Db\DbResult;
 
 class OAuth2DeviceInfo {
-    public const int DEFAULT_LIFETIME = 600;
-    public const int DEFAULT_POLL_INTERVAL = 5;
+    public const int LIFETIME = 10 * 60;
+    public const int POLL_INTERVAL = 5;
 
     public function __construct(
         public private(set) string $id,
diff --git a/src/OAuth2/OAuth2RefreshInfo.php b/src/OAuth2/OAuth2RefreshInfo.php
index c0259e5e..172dc614 100644
--- a/src/OAuth2/OAuth2RefreshInfo.php
+++ b/src/OAuth2/OAuth2RefreshInfo.php
@@ -5,6 +5,9 @@ use Carbon\CarbonImmutable;
 use Index\Db\DbResult;
 
 class OAuth2RefreshInfo {
+    public const int LIFETIME_PUBLIC = 7 * 24 * 60 * 60; // 7 days for public clients
+    public const int LIFETIME_CONFIDENTIAL = 90 * 24 * 60 * 60; // 90 days for confidential clients
+
     public function __construct(
         public private(set) string $id,
         public private(set) ?string $appId,