Files
neonoverlordandClaude Fable 5.1 d59ef91092
build / build (push) Failing after 1m56s
public-facing scrub: no private tracker links, no names, no internal doc pointers
bawnet repos are forward facing: a bit of code we made and use, available. References to
the private ops/features tracker and ops/psn-docker README become plain words, a named
operator quote is gone, the bugs link points at this repo's own issues.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-19 20:30:25 -07:00

94 lines
3.2 KiB
PHP

<?php
/**
* Nextcloud - GoToSocial
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*/
namespace OCA\GoToSocial\Controller;
use OCA\GoToSocial\AppInfo\Application;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\ContentSecurityPolicy;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Services\IAppConfig;
use OCP\IConfig;
use OCP\IRequest;
/**
* The Social page: Pinnacle inside the Nextcloud frame.
*
* SINCE 1.1.0 (the fixed-instance build): Pinnacle is no
* longer bundled here. It ships INSIDE the node GoToSocial image and GTS
* serves it at <instance>/pinnacle/ (patch/gotosocial 0004). This app keeps
* only what Nextcloud is for: the dashboard widgets, and this one framed page
* so a widget click still lands on that exact status/account/notification
* inside Nextcloud (?path=/statuses/<id>).
*
* Removed with the bundle: the /client/* file server, its CSP carve-out, the
* NC-theming colour injection (Pinnacle now carries social.bawnet.io's amber),
* and the auth bridge (NC could inject its token only into pages NC served —
* Pinnacle now does its own login once, top-level, and the browser keeps it).
*
* Framing works because Pinnacle's own CSP allows it: frame-ancestors 'self'
* https://cloud.bawnet.io (bawnet/pinnacle build-gts, pinnacle-headers.json).
*/
class ClientController extends Controller {
public function __construct(
string $appName,
IRequest $request,
private IAppConfig $appConfig,
private IConfig $config,
private ?string $userId,
) {
parent::__construct($appName, $request);
}
/**
* The GoToSocial instance this user is connected to (their own setting,
* else the admin default) — the same resolution the widgets use.
*/
private function gtsUrl(): string {
$adminUrl = $this->appConfig->getAppValueString('oauth_instance_url', lazy: true);
if ($this->userId === null) {
return $adminUrl;
}
return $this->config->getUserValue($this->userId, Application::APP_ID, 'url', $adminUrl) ?: $adminUrl;
}
#[NoAdminRequired]
#[NoCSRFRequired]
public function page(string $path = ''): TemplateResponse {
$scheme = parse_url($this->gtsUrl(), PHP_URL_SCHEME) ?: 'https';
$host = parse_url($this->gtsUrl(), PHP_URL_HOST);
$port = parse_url($this->gtsUrl(), PHP_URL_PORT);
$origin = $host ? $scheme . '://' . $host . ($port ? ':' . $port : '') : '';
$clientUrl = $origin . '/pinnacle/';
// deep link from the dashboard widgets: ?path=/statuses/<id> lands the
// framed client on that route. Strict allowlist: absolute client-route
// paths only, no traversal, no scheme, no query — anything else falls
// back to the client root.
if ($path !== ''
&& preg_match('#^/[A-Za-z0-9_@.~/-]*$#', $path)
&& !str_contains($path, '..')
&& !str_contains($path, '//')) {
$clientUrl .= ltrim($path, '/');
}
$response = new TemplateResponse(Application::APP_ID, 'clientPage', [
'clientUrl' => $clientUrl,
]);
$csp = new ContentSecurityPolicy();
if ($origin !== '') {
$csp->addAllowedFrameDomain($origin);
}
$response->setContentSecurityPolicy($csp);
return $response;
}
}