diff --git a/www/src/Controller/UserController.php b/www/src/Controller/UserController.php index 4cad267..39908ed 100644 --- a/www/src/Controller/UserController.php +++ b/www/src/Controller/UserController.php @@ -6,6 +6,7 @@ use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpClient\HttpClient; use App\Service\MediaFunctions; use App\Service\UserFunctions; @@ -18,6 +19,143 @@ public function test(): Response { return $this->render("test.html.twig", []); } + #[Route("/mastodon_login", name: "mastodon_login")] + public function mastodon_login(): Response { + // Any ?get requests + $request = Request::createFromGlobals(); + $mastodon = $request->query->get("mastodon"); + $server = $request->query->get("server"); + $code = $request->query->get("code"); + + $userFunctions = new UserFunctions(); + + // For HTTP requests + $userAgent = "openbenches/0.1"; + $domain = $_ENV["DOMAIN"]; + + // Has the user sent a Mastodon server? + if (isset( $mastodon )) { + // If so, extract the server's address + if ( filter_var( $mastodon, FILTER_VALIDATE_URL ) ) { + $server = parse_url( $mastodon, PHP_URL_HOST ); + } else { + return $this->render("mastodon_login.html.twig", [ "error" => "No valid URl found." ]); + } + } else if ( isset( $server ) ) { + // If a server has been sent, use that + } else { + // Nothing sent. Display the regular login screen + return $this->render("mastodon_login.html.twig", []); + } + + // Get the credentials associated with this server + $credentials = $userFunctions->getMastodonAppDetails( $server ); + + if ( !isset( $code ) ) { + // If there is no existing app. Create a new one + if ( !$credentials ) { + $client = HttpClient::create(); + $response = $client->request("POST", "https://{$server}/api/v1/apps", [ + "headers" => [ + "User-Agent" => $userAgent, + ], + "body" => [ + 'client_name' => "Login to " . $_ENV["NAME"], + 'redirect_uris' => "{$domain}mastodon_login?server={$server}&", + 'scopes' => "read:accounts", + 'website' => "{$domain}" + ] + ]); + + // If an error occurred + if ( 200 !== $response->getStatusCode() ) { + return $this->render("mastodon_login.html.twig", [ "error" => "Please check the domain and try again." ]); + } + + // Get the response + $content = $response->toArray(); + $client_id = $content["client_id"]; + $client_secret = $content["client_secret"]; + // Create the app in the database + $userFunctions->addMastodonAppDetails( $server, $client_id, $client_secret ); + } else { + // Use the stored app credentials + $client_id = $credentials["client_id"]; + $client_secret = $credentials["client_secret"]; + } + + // Redirect the user to the login URl on their provided server + $login_URl = "https://{$server}/oauth/authorize". + "?client_id={$client_id}" . + "&scope=read:accounts" . + "&redirect_uri={$domain}mastodon_login%3Fserver={$server}%26" . + "&response_type=code"; + + return $this->redirect( $login_URl, 301 ); + die(); + } + + // A code has been provided + // Read the previously saved credentials for the server + $client_id = $credentials["client_id"]; + $client_secret = $credentials["client_secret"]; + + // Get the Bearer token + $client = HttpClient::create(); + $response = $client->request("POST", "https://{$server}/oauth/token", [ + "headers" => [ + "User-Agent" => $userAgent, + ], + "body" => [ + "client_id" => $client_id, + "client_secret" => $client_secret, + "redirect_uri" => "{$domain}mastodon_login?server={$server}&", + "grant_type" => "authorization_code", + "code" => $code, + "scope" => "read:accounts" + ] + ]); + + // If an error occurred + if ( 200 !== $response->getStatusCode() ) { + return $this->render("mastodon_login.html.twig", [ "error" => "An error occurred fetching the Authorization code." ]); + } + + // Get the token + $content = $response->toArray(); + $access_token = $content["access_token"]; + + // Verify the user's credentials + $client = HttpClient::create(); + $response = $client->request("GET", "https://{$server}/api/v1/accounts/verify_credentials", [ + "headers" => [ + "User-Agent" => $userAgent, + "Authorization" => "Bearer {$access_token}" + ] + ]); + + // If an error occurred + if ( 200 !== $response->getStatusCode() ) { + return $this->render("mastodon_login.html.twig", [ "error" => "Your details could not be verified." ]); + } + + // Get the user's details + $content = $response->toArray(); + + // Save the user to the database + $mastodon_username = $content["username"]; + $mastodon_id = $content["url"]; + $userFunctions->addUser( $mastodon_username, "mastodon", $mastodon_id ); + + // Add the user to the session in a HORRIBLE hack + $_SESSION["_sf2_attributes"]["auth0_session"]["user"]["nickname"] = "@{$mastodon_username}@{$server}"; + $_SESSION["_sf2_attributes"]["auth0_session"]["user"]["sub"] = "mastodon|$mastodon_id"; + $_SESSION["_sf2_attributes"]["auth0_session"]["user"]["picture"] = $content["avatar"]; + + // Redirect the newly logged in user to the add page + return $this->redirect( "/add/logged_in", 301 ); + } + #[Route("/user", name: "no_user")] public function no_user(): Response { return $this->render("user.html.twig", [ diff --git a/www/src/Service/UserFunctions.php b/www/src/Service/UserFunctions.php index c0e8f00..eac1d8f 100644 --- a/www/src/Service/UserFunctions.php +++ b/www/src/Service/UserFunctions.php @@ -85,6 +85,9 @@ public function getUserAvatar( $provider, $providerID, $name ) { case "wikipedia" : $user_avatar = "/images/svg/wikipedia.svg"; break; + case "mastodon" : + $user_avatar = "/images/svg/mastodon.svg"; + break; default : $user_avatar = null; } @@ -126,6 +129,9 @@ public function getUserURL( $provider, $providerID, $name ): string { $userURL = "https://twitter.com/intent/user?user_id={$providerID}"; break; } + case "mastodon" : + $userURL = "{$providerID}"; + break; default : $userURL = ""; } @@ -297,4 +303,44 @@ public function addUser( $username=null, $provider=null, $providerID=null ): int // Get the ID of the row which was just inserted return $conn->lastInsertId(); } + + public function getMastodonAppDetails( $domainName ) { + $dsnParser = new DsnParser(); + $connectionParams = $dsnParser->parse( $_ENV['DATABASE_URL'] ); + $conn = DriverManager::getConnection($connectionParams); + + // Get user's name and details + $sql = "SELECT `domain_name`, `client_id`, `client_secret` + FROM `mastodon_apps` + WHERE domain_name = ? + LIMIT 0 , 1"; + $stmt = $conn->prepare($sql); + $stmt->bindValue(1, $domainName); + $results = $stmt->executeQuery(); + $results_array = $results->fetchAssociative(); + return $results_array; + } + + public function addMastodonAppDetails( $domainName, $client_id, $client_secret ) { + $dsnParser = new DsnParser(); + $connectionParams = $dsnParser->parse( $_ENV['DATABASE_URL'] ); + $conn = DriverManager::getConnection($connectionParams); + + // Add the new app + $sql = "INSERT INTO `mastodon_apps` + (`domain_name`, `client_id`, `client_secret`) + VALUES + (?, ?, ?)"; + + $stmt = $conn->prepare($sql); + $stmt->bindValue(1, $domainName); + $stmt->bindValue(2, $client_id); + $stmt->bindValue(3, $client_secret); + + // Run the query + $results = $stmt->executeQuery(); + + // Get the ID of the row which was just inserted + return true; + } } \ No newline at end of file diff --git a/www/templates/mastodon_login.html.twig b/www/templates/mastodon_login.html.twig new file mode 100644 index 0000000..80008fc --- /dev/null +++ b/www/templates/mastodon_login.html.twig @@ -0,0 +1,14 @@ +{% extends "base.html.twig" %} + +{% block title %}Log in with Mastodon{% endblock %} + +{% block body %} +{% if error is defined %} +