Jump to content

Nuclear General

Clients
  • Joined

  • Last visited

  1. Oh, gotcha. That’s kool! 👍
  2. Will this extension be required when using the API to upload attachments or anything related to files?
  3. That’s what I as asking about, and your answer was a little confusing 🫤 But we got the answer though, a few replies later, lol 😂
  4. I am trying to figure out some of the extensions that ICS has to offer, and I really don’t see any documentation on version 5 really. Only old topics from 4.6 and older, ranging in excess of at least 10 year old content. So, is there a section regarding any actual developer documentation covering version 5? I see developer blogs and stuff, but that isn’t of any use. I am curious about the FileSystem extension. What locations can it access on the file system? Is it limited to just the “uploads” folder, or can it be used elsewhere on the file system?
  5. Just a little curious, where is the Community Expert feature located in the ACP? Or is just for cloud hosted customers?
  6. OMG!! 😲😲 I think I've finally gotten it! 😁 And I'm tired and exhausted now lol 😴 I am now able to see the access tokens appear in the ACP of the installation that provides the API, and save that information in the database settings. But for now, I'm going to bed 🛌 haha 😂
  7. If I'm understanding correctly, I've made some changes, and I still can't make a connection to save the details. I'm getting a little brain wrecked from trying to get this to work for almost a week. Application.php /** * @brief API Configuration */ protected static $apiConfig = [ 'key' => 'c41be4bf148ed229e2b86473c82c0ac3', 'base_url' => 'https://localhost/invisionapi/api', 'oauth' => [ 'scope' => 'profile email messages members', 'authorize_url' => 'https://localhost/invisionapi/oauth/authorize', 'token_url' => 'https://localhost/invisionapi/oauth/token' ], ]; /** * @brief Get OAuth Configuration * * @return array */ public static function getOAuthConfig(): array { return array_merge(static::$apiConfig['oauth'], [ 'client_id' => Settings::i()->api_client_id, 'client_secret' => Settings::i()->api_client_secret, 'redirect_uri' => Settings::i()->api_client_callback, ]); } /** * @brief Get API Base URL * * @return string */ public static function getApiBaseUrl(): string { return static::$apiConfig['base_url']; } /** * @brief Make OAuth Request * * @param string $endpoint The API endpoint path * @param string $method HTTP method (GET, POST, etc) * @param array $parameters Request parameters * @param string|null $accessToken OAuth access token (optional, will use stored token if not provided) * @return array Response data * @throws \IPS\Http\Request\Exception */ public static function oauth(string $endpoint, string $method = 'GET', array $parameters = [], ?string $accessToken = NULL): array { // Get access token if not provided if (!$accessToken) { $accessToken = static::getOAuthAccessToken(); if (!$accessToken) { throw new \Exception('No access token available'); } } // Build the full URL $url = Url::external(static::getApiBaseUrl() . '/' . ltrim($endpoint, '/')); if ($method === 'GET' && !empty($parameters)) { $url = $url->setQueryString($parameters); } try { $clientId = Settings::i()->api_client_id; $request = $url->request() ->setHeaders([ 'Accept' => 'application/json', 'User-Agent' => 'Invision-Agent', 'Authorization' => 'Bearer ' . $accessToken, 'Client' => $clientId ]); // Make the request if ($method === 'GET') { $response = $request->get(); } else { $response = $request->$method($parameters); } // Get the raw response content $content = (string) $response; $decoded = json_decode($content, TRUE); // Return empty array if decode failed or response is null return $decoded ?: []; } catch (\Exception $e) { // Try to refresh token if unauthorized if ($e->getCode() === 401) { static::refreshOAuthToken(); return static::oauth($endpoint, $method, $parameters); } return []; // Return empty array instead of throwing } } /** * @brief Store OAuth tokens * * @param array $tokenData Array containing token information (access_token, refresh_token, expires_in) * @return void */ public static function storeOAuthTokens(array $tokenData): void { // Calculate expiration timestamp $expiresAt = time() + ($tokenData['expires_in'] ?? 3600); // Update settings using the proper method \IPS\Settings::i()->changeValues([ 'oauth_access_token' => $tokenData['access_token'], 'oauth_refresh_token' => $tokenData['refresh_token'] ?? NULL, 'oauth_expires_at' => $expiresAt ]); } /** * @brief Get stored OAuth access token * * @return string|null */ public static function getOAuthAccessToken(): ?string { $settings = Settings::i(); if (!$settings->oauth_access_token) { return NULL; } // Check if token is expired if ($settings->oauth_expires_at && $settings->oauth_expires_at < time()) { try { static::refreshOAuthToken(); } catch (\Exception $e) { return NULL; } } return $settings->oauth_access_token; } /** * @brief Refresh OAuth token * * @return void * @throws \Exception */ protected static function refreshOAuthToken(): void { $settings = Settings::i(); if (!$settings->oauth_refresh_token) { throw new \Exception('No refresh token available'); } $config = static::getOAuthConfig(); $response = Url::external($config['token_url']) ->request() ->post([ 'grant_type' => 'refresh_token', 'client_id' => $config['client_id'], 'client_secret' => $config['client_secret'], 'refresh_token' => $settings->oauth_refresh_token ]); $data = json_decode($response, TRUE); if (!isset($data['access_token'])) { throw new \Exception('Invalid token response'); } // Update stored tokens using proper method static::storeOAuthTokens([ 'access_token' => $data['access_token'], 'refresh_token' => $data['refresh_token'] ?? NULL, 'expires_in' => $data['expires_in'] ?? 3600 ]); } /** * @brief Start OAuth flow * * @param \IPS\Http\Url $returnUrl URL to return to after authentication * @return void * @throws \Exception */ public static function startOAuthFlow(\IPS\Http\Url $returnUrl): void { $config = static::getOAuthConfig(); // Store return URL in admin session data $_SESSION['oauth_return_url'] = (string)$returnUrl; // Generate and store state parameter $state = \IPS\Session::i()->csrfKey; $_SESSION['oauth_state'] = $state; // Build authorization URL $authUrl = Url::external($config['authorize_url']) ->setQueryString([ 'client_id' => $config['client_id'], 'redirect_uri' => $config['redirect_uri'], 'response_type' => 'code', 'scope' => $config['scope'], 'state' => $state ]); \IPS\Output::i()->redirect($authUrl); } /** * @brief Handle OAuth callback * * @param string $code Authorization code * @param string $state CSRF state parameter * @return void * @throws \Exception */ public static function handleOAuthCallback(string $code, string $state): void { $config = static::getOAuthConfig(); // Exchange code for access token $response = Url::external($config['token_url']) ->request() ->post([ 'grant_type' => 'authorization_code', 'client_id' => $config['client_id'], 'client_secret' => $config['client_secret'], 'redirect_uri' => $config['redirect_uri'], 'code' => $code ]); $data = json_decode($response, TRUE); if (!isset($data['access_token'])) { throw new \Exception('Invalid token response'); } // Store tokens using proper method static::storeOAuthTokens([ 'access_token' => $data['access_token'], 'refresh_token' => $data['refresh_token'] ?? NULL, 'expires_in' => $data['expires_in'] ?? 3600 ]); }userInfo.php /** * @brief Manage method - shows the login form or redirects to OAuth * * @return void */ protected function manage(): void { // Check if we already have a valid token $accessToken = Application::getOAuthAccessToken(); if ($accessToken) { Output::i()->redirect(\IPS\Http\Url::internal('app=myapp&module=userinfo&controller=userinfo&do=showInfo')); } // If not logged in, show the OAuth login button Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__myapp_userinfo_userinfo'); Output::i()->output = \IPS\Theme::i()->getTemplate('userinfo', 'myapp', 'admin')->loginForm(); } /** * @brief Start OAuth flow * * @return void */ protected function oauth(): void { $state = \IPS\Session::i()->csrfKey; $_SESSION['oauth_state'] = $state; try { Application::startOAuthFlow( \IPS\Http\Url::internal('app=myapp&module=userinfo&controller=userinfo&do=showInfo'), $state )->csrf(); } catch (\Exception $e) { Output::i()->error('OAuth authentication failed: ' . $e->getMessage(), '500'); } } /** * @brief OAuth callback handler * * @return void */ protected function callback(): void { try { if (!Request::i()->code) { throw new \Exception('No authorization code received'); } if (Request::i()->error) { $error = Request::i()->error_description ?: Request::i()->error; throw new \Exception('OAuth error: ' . $error); } // Verify state parameter matches CSRF key if (Request::i()->state !== \IPS\Session::i()->csrfKey) { throw new \Exception('Invalid state parameter'); } Application::handleOAuthCallback( Request::i()->code, Request::i()->state ); // Get the stored return URL or use default $returnUrl = isset($_SESSION['oauth_return_url']) ? \IPS\Http\Url::createFromString($_SESSION['oauth_return_url']) : \IPS\Http\Url::internal('app=myapp&module=userinfo&controller=userinfo&do=showInfo', 'admin'); // Add debug logging //if (\IPS\IN_DEV || \IPS\DEBUG_LOG) { \IPS\Log::debug( "OAuth Callback Debug:\n" . "Received State: " . (Request::i()->state ?: 'NULL') . "\n" . "Session CSRF Key: " . \IPS\Session::i()->csrfKey . "\n" . "Request Parameters: " . json_encode(Request::i(), JSON_PRETTY_PRINT) . "\n" . "Session Data: " . json_encode($_SESSION, JSON_PRETTY_PRINT), 'oauth_callback' ); //} // Clear the stored return URL unset($_SESSION['oauth_return_url']); // Redirect to return URL Output::i()->redirect($returnUrl); } catch (\Exception $e) { if (\IPS\IN_DEV || \IPS\DEBUG_LOG) { \IPS\Log::debug("OAuth Callback Error: " . $e->getMessage() . "\n" . $e->getTraceAsString(), 'oauth_callback'); } // Redirect back to login form with error Output::i()->redirect( \IPS\Http\Url::internal('app=myapp&module=userinfo&controller=userinfo', 'admin') ->setQueryString('error', $e->getMessage()) ); } } /** * @brief Show user information * * @return void */ protected function showInfo(): void { $accessToken = Application::getOAuthAccessToken(); if (!$accessToken) { Output::i()->redirect(\IPS\Http\Url::internal('app=myapp&module=userinfo&controller=userinfo')); } try { // Fetch user information using the OAuth token $userInfo = Application::oauth('user', 'GET', [], $accessToken); Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__myapp_userinfo_userinfo'); Output::i()->output = \IPS\Theme::i()->getTemplate('userinfo', 'myapp', 'admin')->userInfo($userInfo); } catch (\Exception $e) { Output::i()->error('Could not fetch user information: ' . $e->getMessage(), '500'); } } /** * @brief Logout * * @return void */ protected function logout(): void { // Clear stored OAuth tokens using proper method \IPS\Settings::i()->changeValues([ 'marketplace_oauth_access_token' => NULL, 'marketplace_oauth_refresh_token' => NULL, 'marketplace_oauth_expires_at' => NULL ]); Output::i()->redirect(\IPS\Http\Url::internal('app=myapp&module=userinfo&controller=userinfo')); }
  8. Just for a little more context, I am trying to use the REST API with OAuth, to retrieve information from another ICS 5 community.
  9. I'm not trying to connect two communities together. I am trying to display information from another community inside the ACP of the application I am creating, by using an Authenticated session from OAuth, displaying relative information from the user who logged in.
  10. I tried updating the oauth() and callback() in the userinfo.php to this. I noticed I had the wrong endpoint for the user info. Changed 'user' to 'core/me' in the showInfo(). I still get a csrf error. I really don't see what could be wrong.. it should be saving the info recieved into the settings, and it should be giving me debug logs, but I am also not getting the debig logs recoreded in the ACP. /** * @brief Start OAuth flow * * @return void */ protected function oauth(): void { // Start the session if not already started if (session_status() !== PHP_SESSION_ACTIVE) { session_start(); } // Generate and store state parameter $state = \IPS\Session::i()->csrfKey; $_SESSION['oauth_state'] = $state; try { Application::startOAuthFlow( \IPS\Http\Url::internal('app=myapp&module=userinfo&controller=userinfo&do=showInfo'), $state ); } catch (\Exception $e) { \IPS\Log::debug("OAuth Start Error: " . $e->getMessage(), 'oauth_callback'); Output::i()->error('OAuth authentication failed: ' . $e->getMessage(), '500'); } } /** * @brief OAuth callback handler * * @return void */ protected function callback(): void { // Start the session if not already started if (session_status() !== PHP_SESSION_ACTIVE) { session_start(); } try { if (!Request::i()->code) { throw new \Exception('No authorization code received'); } if (Request::i()->error) { $error = Request::i()->error_description ?: Request::i()->error; throw new \Exception('OAuth error: ' . $error); } // Verify state parameter matches stored state if (!isset($_SESSION['oauth_state']) || Request::i()->state !== $_SESSION['oauth_state']) { \IPS\Log::debug( "State mismatch:\nReceived: " . Request::i()->state . "\nStored: " . ($_SESSION['oauth_state'] ?? 'NOT SET'), 'oauth_callback' ); throw new \Exception('Invalid state parameter'); } Application::handleOAuthCallback( Request::i()->code, Request::i()->state ); // Get the stored return URL or use default $returnUrl = isset($_SESSION['oauth_return_url']) ? \IPS\Http\Url::createFromString($_SESSION['oauth_return_url']) : \IPS\Http\Url::internal('app=myapp&module=userinfo&controller=userinfo&do=showInfo', 'admin'); // Add debug logging \IPS\Log::debug( "OAuth Callback Debug:\n" . "Received State: " . (Request::i()->state ?: 'NULL') . "\n" . "Session State: " . ($_SESSION['oauth_state'] ?? 'NULL') . "\n" . "Request Parameters: " . json_encode(Request::i(), JSON_PRETTY_PRINT) . "\n" . "Session Data: " . json_encode($_SESSION, JSON_PRETTY_PRINT), 'oauth_callback' ); // Clear the stored OAuth state and return URL unset($_SESSION['oauth_state']); unset($_SESSION['oauth_return_url']); // Redirect to return URL Output::i()->redirect($returnUrl); } catch (\Exception $e) { if (\IPS\IN_DEV || \IPS\DEBUG_LOG) { \IPS\Log::debug("OAuth Callback Error: " . $e->getMessage() . "\n" . $e->getTraceAsString(), 'oauth_callback'); } // Redirect back to login form with error Output::i()->redirect( \IPS\Http\Url::internal('app=myapp&module=userinfo&controller=userinfo', 'admin') ->setQueryString('error', $e->getMessage()) ); } } /** * @brief Show user information * * @return void */ protected function showInfo(): void { $accessToken = Application::getOAuthAccessToken(); if (!$accessToken) { Output::i()->redirect(\IPS\Http\Url::internal('app=myapp&module=userinfo&controller=userinfo')); } try { // Fetch user information using the OAuth token $userInfo = Application::oauth('core/me', 'GET', [], $accessToken); Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__myapp_userinfo_userinfo'); Output::i()->output = \IPS\Theme::i()->getTemplate('userinfo', 'myapp', 'admin')->userInfo($userInfo); } catch (\Exception $e) { Output::i()->error('Could not fetch user information: ' . $e->getMessage(), '500'); } }
  11. Bump.. can anyone help with this please? 🙏
  12. Hello, It's been a long time since I've done some php coding, and just getting back into it. I am currently trying to create an OAuth connection to another ICS installation on a local install. For basics on testing, I am only trying to show user info after authenticated. Here is my setup: Application.php /** * @brief API Configuration */ protected static $apiConfig = [ 'key' => 'c41be4bf148ed229e2b86473c82c0ac3', 'base_url' => 'https://localhost/invisionapi/api', 'oauth' => [ 'scope' => 'profile email messages members', 'authorize_url' => 'https://localhost/invisionapi/oauth/authorize', 'token_url' => 'https://localhost/invisionapi/oauth/token' ], ]; /** * @brief Get OAuth Configuration * * @return array */ public static function getOAuthConfig(): array { return array_merge(static::$apiConfig['oauth'], [ 'client_id' => Settings::i()->api_client_id, 'client_secret' => Settings::i()->api_client_secret, 'redirect_uri' => Settings::i()->api_client_callback, ]); } /** * @brief Get API Base URL * * @return string */ public static function getApiBaseUrl(): string { return static::$apiConfig['base_url']; } /** * @brief Make OAuth Request * * @param string $endpoint The API endpoint path * @param string $method HTTP method (GET, POST, etc) * @param array $parameters Request parameters * @param string|null $accessToken OAuth access token (optional, will use stored token if not provided) * @return array Response data * @throws \IPS\Http\Request\Exception */ public static function oauth(string $endpoint, string $method = 'GET', array $parameters = [], ?string $accessToken = NULL): array { // Get access token if not provided if (!$accessToken) { $accessToken = static::getOAuthAccessToken(); if (!$accessToken) { throw new \Exception('No access token available'); } } // Build the full URL $url = Url::external(static::getApiBaseUrl() . '/' . ltrim($endpoint, '/')); if ($method === 'GET' && !empty($parameters)) { $url = $url->setQueryString($parameters); } try { $clientId = Settings::i()->api_client_id; $request = $url->request() ->setHeaders([ 'Accept' => 'application/json', 'User-Agent' => 'Invision-Agent', 'Authorization' => 'Bearer ' . $accessToken, 'Client' => $clientId ]); // Make the request if ($method === 'GET') { $response = $request->get(); } else { $response = $request->$method($parameters); } // Get the raw response content $content = (string) $response; $decoded = json_decode($content, TRUE); // Return empty array if decode failed or response is null return $decoded ?: []; } catch (\Exception $e) { // Try to refresh token if unauthorized if ($e->getCode() === 401) { static::refreshOAuthToken(); return static::oauth($endpoint, $method, $parameters); } return []; // Return empty array instead of throwing } } /** * @brief Store OAuth tokens * * @param array $tokenData Array containing token information (access_token, refresh_token, expires_in) * @return void */ public static function storeOAuthTokens(array $tokenData): void { // Calculate expiration timestamp $expiresAt = time() + ($tokenData['expires_in'] ?? 3600); // Store the tokens and expiration Db::i()->update('core_sys_conf_settings', array( 'conf_value' => $tokenData['access_token'] ), array('conf_key=?', 'oauth_access_token')); Db::i()->update('core_sys_conf_settings', array( 'conf_value' => $tokenData['refresh_token'] ), array('conf_key=?', 'oauth_refresh_token')); Db::i()->update('core_sys_conf_settings', array( 'conf_value' => $expiresAt ), array('conf_key=?', 'oauth_expires_at')); // Update runtime cache Settings::i()->oauth_access_token = $tokenData['access_token']; Settings::i()->oauth_refresh_token = $tokenData['refresh_token']; Settings::i()->oauth_expires_at = $expiresAt; // Clear settings cache unset(Store::i()->settings); } /** * @brief Get stored OAuth access token * * @return string|null */ public static function getOAuthAccessToken(): ?string { $settings = Settings::i(); if (!$settings->oauth_access_token) { return NULL; } // Check if token is expired if ($settings->oauth_expires_at && $settings->oauth_expires_at < time()) { try { static::refreshOAuthToken(); } catch (\Exception $e) { return NULL; } } return $settings->oauth_access_token; } /** * @brief Refresh OAuth token * * @return void * @throws \Exception */ protected static function refreshOAuthToken(): void { $settings = Settings::i(); if (!$settings->oauth_refresh_token) { throw new \Exception('No refresh token available'); } $config = static::getOAuthConfig(); $response = Url::external($config['token_url']) ->request() ->post([ 'grant_type' => 'refresh_token', 'client_id' => $config['client_id'], 'client_secret' => $config['client_secret'], 'refresh_token' => $settings->oauth_refresh_token ]); $data = json_decode($response, TRUE); if (!isset($data['access_token'])) { throw new \Exception('Invalid token response'); } // Update stored tokens $settings->oauth_access_token = $data['access_token']; $settings->oauth_refresh_token = $data['refresh_token'] ?? $settings->oauth_refresh_token; $settings->oauth_expires_at = time() + ($data['expires_in'] ?? 3600); $settings->save(); } /** * @brief Start OAuth flow * * @param \IPS\Http\Url $returnUrl URL to return to after authentication * @return void * @throws \Exception */ public static function startOAuthFlow(\IPS\Http\Url $returnUrl): void { $config = static::getOAuthConfig(); // Store return URL in admin session data $_SESSION['oauth_return_url'] = (string)$returnUrl; // Generate and store state parameter $state = \IPS\Session::i()->csrfKey; $_SESSION['oauth_state'] = $state; // Build authorization URL $authUrl = Url::external($config['authorize_url']) ->setQueryString([ 'client_id' => $config['client_id'], 'redirect_uri' => $config['redirect_uri'], 'response_type' => 'code', 'scope' => $config['scope'], 'state' => $state ]); \IPS\Output::i()->redirect($authUrl); } /** * @brief Handle OAuth callback * * @param string $code Authorization code * @param string $state CSRF state parameter * @return void * @throws \Exception */ public static function handleOAuthCallback(string $code, string $state): void { $config = static::getOAuthConfig(); // Exchange code for access token $response = Url::external($config['token_url']) ->request() ->post([ 'grant_type' => 'authorization_code', 'client_id' => $config['client_id'], 'client_secret' => $config['client_secret'], 'redirect_uri' => $config['redirect_uri'], 'code' => $code ]); $data = json_decode($response, TRUE); if (!isset($data['access_token'])) { throw new \Exception('Invalid token response'); } // Store tokens $settings = Settings::i(); $settings->oauth_access_token = $data['access_token']; $settings->oauth_refresh_token = $data['refresh_token'] ?? NULL; $settings->oauth_expires_at = time() + ($data['expires_in'] ?? 3600); $settings->save(); }userInfo.php /** * @brief Manage method - shows the login form or redirects to OAuth * * @return void */ protected function manage(): void { // Check if we already have a valid token $accessToken = Application::getOAuthAccessToken(); if ($accessToken) { Output::i()->redirect(\IPS\Http\Url::internal('app=myapp&module=userinfo&controller=userinfo&do=showInfo')); } // If not logged in, show the OAuth login button Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__myapp_userinfo_userinfo'); Output::i()->output = \IPS\Theme::i()->getTemplate('userinfo', 'myapp', 'admin')->loginForm(); } /** * @brief Start OAuth flow * * @return void */ protected function oauth(): void { // Generate random state parameter $state = bin2hex(random_bytes(16)); $_SESSION['oauth_state'] = $state; try { Application::startOAuthFlow( \IPS\Http\Url::internal('app=myapp&module=userinfo&controller=userinfo&do=showInfo'), $state ); } catch (\Exception $e) { Output::i()->error('OAuth authentication failed: ' . $e->getMessage(), '500'); } } /** * @brief OAuth callback handler * * @return void */ protected function callback(): void { try { if (!Request::i()->code) { throw new \Exception('No authorization code received'); } if (Request::i()->error) { $error = Request::i()->error_description ?: Request::i()->error; throw new \Exception('OAuth error: ' . $error); } // Verify state parameter matches CSRF key if (Request::i()->state !== \IPS\Session::i()->csrfKey) { throw new \Exception('Invalid state parameter'); } Application::handleOAuthCallback( Request::i()->code, Request::i()->state ); // Get the stored return URL or use default $returnUrl = isset($_SESSION['oauth_return_url']) ? \IPS\Http\Url::createFromString($_SESSION['oauth_return_url']) : \IPS\Http\Url::internal('app=myapp&module=userinfo&controller=userinfo&do=showInfo', 'admin'); // Add debug logging //if (\IPS\IN_DEV || \IPS\DEBUG_LOG) { \IPS\Log::debug( "OAuth Callback Debug:\n" . "Received State: " . (Request::i()->state ?: 'NULL') . "\n" . "Session CSRF Key: " . \IPS\Session::i()->csrfKey . "\n" . "Request Parameters: " . json_encode(Request::i(), JSON_PRETTY_PRINT) . "\n" . "Session Data: " . json_encode($_SESSION, JSON_PRETTY_PRINT), 'oauth_callback' ); //} // Clear the stored return URL unset($_SESSION['oauth_return_url']); // Redirect to return URL Output::i()->redirect($returnUrl); } catch (\Exception $e) { if (\IPS\IN_DEV || \IPS\DEBUG_LOG) { \IPS\Log::debug("OAuth Callback Error: " . $e->getMessage() . "\n" . $e->getTraceAsString(), 'oauth_callback'); } // Redirect back to login form with error Output::i()->redirect( \IPS\Http\Url::internal('app=myapp&module=userinfo&controller=userinfo', 'admin') ->setQueryString('error', $e->getMessage()) ); } } /** * @brief Show user information * * @return void */ protected function showInfo(): void { $accessToken = Application::getOAuthAccessToken(); if (!$accessToken) { Output::i()->redirect(\IPS\Http\Url::internal('app=myapp&module=userinfo&controller=userinfo')); } try { // Fetch user information using the OAuth token $userInfo = Application::oauth('user', 'GET', [], $accessToken); Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__myapp_userinfo_userinfo'); Output::i()->output = \IPS\Theme::i()->getTemplate('userinfo', 'myapp', 'admin')->userInfo($userInfo); } catch (\Exception $e) { Output::i()->error('Could not fetch user information: ' . $e->getMessage(), '500'); } } /** * @brief Logout * * @return void */ protected function logout(): void { // Clear stored OAuth tokens $settings = \IPS\Settings::i(); $settings->oauth_access_token = NULL; $settings->oauth_refresh_token = NULL; $settings->oauth_expires_at = NULL; $settings->save(); Output::i()->redirect(\IPS\Http\Url::internal('app=myapp&module=userinfo&controller=userinfo')); }loginForm.phtml <ips:template parameters="$error=NULL" /> <div class="ipsBox ipsShadow"> <div class="ipsBox_container"> <h2 class="ipsType_sectionTitle">Connect to Localhost API</h2> {{if !\IPS\Settings::i()->api_client_id || !\IPS\Settings::i()->api_client_secret || !\IPS\Settings::i()->api_client_callback}} <div class="ipsMessage ipsMessage_warning"> <strong>OAuth Configuration Required</strong> <p>Please configure the OAuth client settings in the <a href="{url="app=myapp&module=dashboard&controller=dashboard&do=settings"}">Localhost API Settings</a> before connecting your account.</p> </div> {{else}} {{if $error}} <div class="ipsMessage ipsMessage_error"> {$error} </div> {{endif}} <p class="ipsType_normal">Connect your Localhost API account to access additional features and manage your Localhost API presence.</p> <form action="{url="app=myapp&module=userinfo&controller=userinfo&do=oauth"}" method="post"> <input type="hidden" name="csrfKey" value="{expression="\IPS\Session::i()->csrfKey"}" /> <div class="ipsBox_controls ipsPad_half ipsClearfix"> <button type="submit" class="ipsButton ipsButton_primary ipsButton_large" {{if !\IPS\Settings::i()->api_client_id || !\IPS\Settings::i()->api_client_secret || !\IPS\Settings::i()->api_client_callback}}disabled{{endif}}> <i class="fa fa-sign-in"></i> Connect with Localhost API </button> </div> </form> {{endif}} </div> </div>I can get all the way to here: After I click "Continue with NuclearGeneral", I am presented with the redirection url configured in settings and oauth client on localhost api, and this csrf error is displayed: I have a debug log in the callback() method, but the log is never entered. I have tried several attempts, all at which don't let me authenticate and show the user info. What am I missing here? Any help very much appreciated. -Donald
  13. Nuclear General started following onlyME
  14. Nuclear General started following DawPi
  15.    Nuclear General reacted to a post in a topic: Hump Day: A Refresh Has Arrived!
  16.    Nuclear General reacted to a post in a topic: Hump Day: A Refresh Has Arrived!
  17.    Nuclear General reacted to a post in a topic: Hump Day: A Refresh Has Arrived!
  18.    Pjo reacted to a post in a topic: oooh, new portal!
  19.    SeNioR- reacted to a post in a topic: oooh, new portal!
  20. The new portal looks good.. but I did notice that the renewal price has gone up by $100 dollars. Is there a reason for this? I have tried to look around for a topic about it, but cannot find one anywhere on the site or forums about the increased price to $310 a year. That seems a little too steap now..
  21.    SeNioR- reacted to a post in a topic: Upgrade from IP. 3.4.x to IPS4.0 (updated)
  22. I had a different experience when upgrading from 3.4.9 to 4.x.x. I had to upgrade to 4.4.10 first, and then upgrade to 4.5.x afterwards. 4.5.x would not work properly for me when converting my database to the new format they implemented in 4.x. I tried several attempts and all failed with 4.5.x but 4.4.10 worked (almost) flawlessly, but it did get me 3/4 of the way to upgrade to 4.5.x, and then the last upgrade of 4.5.4 months ago.
  23. Hello, What version of MySQL are you using? Is it MariaDB 5.x.x or MariaDB 10.x.x? MariaDB 5.x.x uses ROW_FORMAT= 'COMPACT' , or 'REDUNDANT'. MariaDB 10.x.x uses ROW_FORMAT= 'COMPACT' , 'REDUNDANT' , 'DYNAMIC' , or 'COMPRESSED'. The ROW_FORMAT can be easily changed with a simple MySQL Statement. I'll show you how. 1) First, make a backup of your Database, i.e.: use phpMyAdmin, go to your database and use the OPERATIONS tab and use the "Copy Database To" section to create an exact copy, name what it whatever you'd like. Then run the following query on your copied database (Change the "DATABASE_NAME_HERE" to your copied database name) : This code will print out everything that has a table engine of "InnoDB". SELECT CONCAT('ALTER TABLE ', table_schema, '.', table_name, ' ENGINE=InnoDB;') AS sql_statements FROM information_schema.tables AS tb WHERE table_schema = 'DATABASE_NAME_HERE' AND `ENGINE` = 'InnoDB' AND `TABLE_TYPE` = 'BASE TABLE' ORDER BY table_name ASC; It will print out something similar to this: ALTER TABLE DATABASE_NAME_HERE.ibf_advertise_advertisements ENGINE=InnoDB; ALTER TABLE DATABASE_NAME_HERE.ibf_advertise_hits ENGINE=InnoDB; ALTER TABLE DATABASE_NAME_HERE.ibf_advertise_paypal ENGINE=InnoDB; ALTER TABLE DATABASE_NAME_HERE.ibf_autowelcome_members ENGINE=InnoDB; ALTER TABLE DATABASE_NAME_HERE.ibf_axenserverlist_servers ENGINE=InnoDB; ALTER TABLE DATABASE_NAME_HERE.ibf_bbcode_mediatag ENGINE=InnoDB; ALTER TABLE DATABASE_NAME_HERE.ibf_blog_blogs ENGINE=InnoDB; ALTER TABLE DATABASE_NAME_HERE.ibf_blog_categories ENGINE=InnoDB; ALTER TABLE DATABASE_NAME_HERE.ibf_blog_comments ENGINE=InnoDB; ALTER TABLE DATABASE_NAME_HERE.ibf_blog_entries ENGINE=InnoDB; In the below picture, you will get this: (As stated above) Click the +OPTIONS link, you will now see this: Make sure you click the "Full texts" radio button, leaving everything else alone, then click on 'GO'. You will now see this appear: It will show only 25 rows per page by default, but you can change that by doing this: After you have all your rows appear, copy all the text to a text editor, and do a FIND & REPLACE option for: Find: Engine=InnoDB Replace with: (If MariaDB 5.x.x) ROW_FORMAT=REDUNDANT Replace with: (If MariaDB 10.x.x) ROW_FORMAT=DYNAMIC Example: ALTER TABLE tibtech.ibf_advertise_advertisements ROW_FORMAT=DYNAMIC; ALTER TABLE tibtech.ibf_advertise_hits ROW_FORMAT=DYNAMIC; ALTER TABLE tibtech.ibf_advertise_paypal ROW_FORMAT=DYNAMIC; ALTER TABLE tibtech.ibf_autowelcome_members ROW_FORMAT=DYNAMIC; ALTER TABLE tibtech.ibf_axenserverlist_servers ROW_FORMAT=DYNAMIC; ALTER TABLE tibtech.ibf_bbcode_mediatag ROW_FORMAT=DYNAMIC; ALTER TABLE tibtech.ibf_blog_blogs ROW_FORMAT=DYNAMIC; ALTER TABLE tibtech.ibf_blog_categories ROW_FORMAT=DYNAMIC; ALTER TABLE tibtech.ibf_blog_comments ROW_FORMAT=DYNAMIC; ALTER TABLE tibtech.ibf_blog_entries ROW_FORMAT=DYNAMIC; Once the FIND & REPLACE has been done, copy all your edits and go to your database SQL tab in phpMyAdmin, and paste it in there and run SQL Statements. Your tables with now have the proper ROW_FORMAT that will satisfy Invision Community Suite and the error will go away. I hope this helps you and others with this annoying issue. Best of luck, -Donald