Now accounts are federalized

This commit is contained in:
Ghostie 2024-12-26 20:36:50 -05:00
parent 0fc124b55b
commit fddfe65b7b
11 changed files with 286 additions and 2 deletions

View File

@ -0,0 +1,19 @@
<?php
namespace App\Http\Controllers\AP;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\User;
use App\Models\Actor;
class APActorController extends Controller
{
public function user (User $user)
{
$actor = $user->actor ()->get ();
$response = Actor::build_response ($actor->first ());
return response ()->json ($response)->header ("Content-Type", "application/activity+json");
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Http\Controllers\AP;
use App\Models\User;
use App\Models\Actor;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use App\Http\Controllers\Controller;
class APInboxController extends Controller
{
public function inbox (User $user)
{
Log::info ("APInboxController@index");
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Http\Controllers\AP;
use App\Models\User;
use App\Models\Actor;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use App\Http\Controllers\Controller;
class APOutboxController extends Controller
{
public function outbox (User $user)
{
Log::info ("APOutboxController@index");
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace App\Http\Controllers\AP;
use App\Models\User;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class APWebfingerController extends Controller
{
public function webfinger ()
{
$resource = request ()->input ("resource");
if (!isset ($resource)) {
return response ()->json ([ "error" => "missing resource parameter" ], 400);
}
$host = parse_url ($resource, PHP_URL_HOST);
$user = explode (":", $resource);
if (count ($user) != 2) {
return response ()->json ([ "error" => "invalid resource parameter" ], 400);
}
$user = $user[1];
$user = explode ("@", $user);
if (count ($user) != 2) {
return response ()->json ([ "error" => "invalid resource parameter" ], 400);
}
$user = $user[0];
$actual_user = User::where ("name", $user)->first ();
$webfinger = [
"subject" => $resource,
"links" => [
[
"rel" => "self",
"type" => "application/activity+json",
"href" => $actual_user->actor ()->first ()->actor_id
]
]
];
return response ()->json ($webfinger)->header ("Content-Type", "application/jrd+json");
}
}

View File

@ -3,6 +3,8 @@
namespace App\Http\Controllers;
use App\Models\User;
use App\Models\Actor;
use Illuminate\Http\Request;
class UserController extends Controller
@ -26,6 +28,8 @@ class UserController extends Controller
]);
$user = User::create ($incoming_fields);
$actor = new Actor ();
$actor->create_from_user ($user);
auth ()->login ($user);
return redirect ()->route ("home")->with ("success", "You have successfuly signed up!");

106
app/Models/Actor.php Normal file
View File

@ -0,0 +1,106 @@
<?php
namespace App\Models;
use App\Models\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Config;
class Actor extends Model
{
protected $fillable = [
"user_id",
"type",
"actor_id",
"following",
"followers",
"liked",
"inbox",
"outbox",
"sharedInbox",
"preferredUsername",
"name",
"summary",
"public_key",
"private_key"
];
public function create_from_user (User $user)
{
$app_url = Config::get ("app.url");
$config = [
"private_key_bits" => 4096,
"private_key_type" => OPENSSL_KEYTYPE_RSA
];
$res = openssl_pkey_new ($config);
openssl_pkey_export ($res, $private_key);
$public_key = openssl_pkey_get_details ($res);
return $this->create ([
"user_id" => $user->id,
"type" => "Person",
"actor_id" => $app_url . "/ap/v1/user/" . $user->name,
"following" => $app_url . "/ap/v1/user/" . $user->name . "/following",
"followers" => $app_url . "/ap/v1/user/" . $user->name . "/followers",
"liked" => $app_url . "/ap/v1/user/" . $user->name . "/liked",
"inbox" => $app_url . "/ap/v1/user/" . $user->name . "/inbox",
"outbox" => $app_url . "/ap/v1/user/" . $user->name . "/outbox",
"sharedInbox" => $app_url . "/ap/v1/inbox",
"preferredUsername" => $user->name,
"name" => $user->name,
"summary" => "",
"public_key" => $public_key["key"],
"private_key" => $private_key
]);
}
public static function build_response (Actor $actor)
{
return [
"@context" => [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1"
],
"id" => $actor->actor_id,
"type" => $actor->type,
"following" => $actor->following,
"followers" => $actor->followers,
"liked" => $actor->liked,
"inbox" => $actor->inbox,
"outbox" => $actor->outbox,
"sharedInbox" => $actor->sharedInbox,
"preferredUsername" => $actor->preferredUsername,
"name" => $actor->name,
"summary" => $actor->summary,
"publicKey" => [
"id" => $actor->actor_id . "#main-key",
"owner" => $actor->actor_id,
"publicKeyPem" => $actor->public_key
]
];
}
}

View File

@ -53,4 +53,9 @@ class User extends Authenticatable
return $value ? "/storage/avatars/" . $value : "/resources/img/default.jpg";
});
}
public function actor ()
{
return $this->hasOne (Actor::class);
}
}

View File

@ -0,0 +1,50 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('actors', function (Blueprint $table) {
$table->id();
$table->foreignId ("user_id")->nullable ()->constrained ()->onDelete ("cascade");
$table->string ("type")->nullable ();
$table->string ("actor_id")->unique ();
$table->string ("following")->nullable ();
$table->string ("followers")->nullable ();
$table->string ("liked")->nullable ();
$table->string ("inbox")->nullable ();
$table->string ("outbox")->nullable ();
$table->string ("sharedInbox")->nullable ();
$table->string ("preferredUsername")->nullable ();
$table->string ("name")->nullable ();
$table->string ("summary")->nullable ();
$table->string ("public_key")->nullable ();
$table->string ("private_key")->nullable ();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('actors');
}
};

View File

@ -22,12 +22,12 @@
<form action="#" method="POST">
@csrf
<input type="text" name="name" placeholder="Username" required><br>
<input type="text" name="name" placeholder="Username" value="{{ old ('name') }}" required><br>
@error('username')
<div class="error">{{ $message }}</div>
@enderror
<input type="email" name="email" placeholder="Email" required><br>
<input type="email" name="email" placeholder="Email" value="{{ old ('email') }}" required><br>
@error('email')
<div class="error">{{ $message }}</div>
@enderror

17
routes/api.php Normal file
View File

@ -0,0 +1,17 @@
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\AP\APActorController;
use App\Http\Controllers\AP\APWebfingerController;
use App\Http\Controllers\AP\APInboxController;
use App\Http\Controllers\AP\APOutboxController;
Route::get ("/.well-known/webfinger", [ APWebfingerController::class, "webfinger" ])->name ("ap.webfinger");
Route::prefix ("/ap/v1")->group (function () {
Route::get ("/user/{user:name}", [ APActorController::class, "user" ])->name ("ap.user");
Route::post ("/user/{user:name}/inbox", [ APInboxController::class, "inbox" ])->name ("ap.inbox");
Route::post ("/user/{user:name}/outbox", [ APOutboxController::class, "outbox" ])->name ("ap.outbox");
});

View File

@ -13,3 +13,5 @@ Route::get ("/auth/signup", [ UserController::class, "signup" ])->name ("signup"
Route::get ("/auth/logout", [ UserController::class, "logout" ])->name ("logout")->middleware ("auth");
Route::post ("/auth/signup", [ UserController::class, "do_signup" ])->middleware ("guest");
Route::post ("/auth/login", [ UserController::class, "do_login" ])->middleware ("guest");
require __DIR__ . "/api.php";