Skip to content

Commit

Permalink
Experimental Mastodon Support
Browse files Browse the repository at this point in the history
For #331
  • Loading branch information
Terence Eden committed Nov 30, 2024
1 parent 0d9c43e commit 578da2d
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 0 deletions.
138 changes: 138 additions & 0 deletions www/src/Controller/UserController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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", [
Expand Down
46 changes: 46 additions & 0 deletions www/src/Service/UserFunctions.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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 = "";
}
Expand Down Expand Up @@ -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;
}
}
14 changes: 14 additions & 0 deletions www/templates/mastodon_login.html.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{% extends "base.html.twig" %}

{% block title %}Log in with Mastodon{% endblock %}

{% block body %}
{% if error is defined %}
<h2>{{ error }}</h2>
{% endif %}
<form method="get">
<label for="mastodon">What is your Mastodon server?</label><br>
<input type="url" id="mastodon" name="mastodon" placeholder="https://example.social/" required><br>
<button>Authorise me!</button>
</form>
{% endblock %}

0 comments on commit 578da2d

Please sign in to comment.