123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605 |
- <?php
- namespace App\Http\Controllers;
- use Illuminate\Support\Str;
- use Illuminate\Http\Request;
- use App\Services\Account\RemoteAuthService;
- use App\Models\RemoteAuth;
- use App\Profile;
- use App\Instance;
- use App\User;
- use Purify;
- use Illuminate\Support\Facades\Auth;
- use Illuminate\Support\Facades\Hash;
- use Illuminate\Auth\Events\Registered;
- use App\Util\Lexer\RestrictedNames;
- use App\Services\EmailService;
- use App\Services\MediaStorageService;
- use App\Util\ActivityPub\Helpers;
- use InvalidArgumentException;
- class RemoteAuthController extends Controller
- {
- public function start(Request $request)
- {
- abort_unless(config_cache('pixelfed.open_registration') && config('remote-auth.mastodon.enabled'), 404);
- if($request->user()) {
- return redirect('/');
- }
- return view('auth.remote.start');
- }
- public function startRedirect(Request $request)
- {
- return redirect('/login');
- }
- public function getAuthDomains(Request $request)
- {
- abort_unless(config_cache('pixelfed.open_registration') && config('remote-auth.mastodon.enabled'), 404);
- if(config('remote-auth.mastodon.domains.only_custom')) {
- $res = config('remote-auth.mastodon.domains.custom');
- if(!$res || !strlen($res)) {
- return [];
- }
- $res = explode(',', $res);
- return response()->json($res);
- }
- if( config('remote-auth.mastodon.domains.custom') &&
- !config('remote-auth.mastodon.domains.only_default') &&
- strlen(config('remote-auth.mastodon.domains.custom')) > 3 &&
- strpos(config('remote-auth.mastodon.domains.custom'), '.') > -1
- ) {
- $res = config('remote-auth.mastodon.domains.custom');
- if(!$res || !strlen($res)) {
- return [];
- }
- $res = explode(',', $res);
- return response()->json($res);
- }
- $res = config('remote-auth.mastodon.domains.default');
- $res = explode(',', $res);
- return response()->json($res);
- }
- public function redirect(Request $request)
- {
- abort_unless(config_cache('pixelfed.open_registration') && config('remote-auth.mastodon.enabled'), 404);
- $this->validate($request, ['domain' => 'required']);
- $domain = $request->input('domain');
- if(str_starts_with(strtolower($domain), 'http')) {
- $res = [
- 'domain' => $domain,
- 'ready' => false,
- 'action' => 'incompatible_domain'
- ];
- return response()->json($res);
- }
- $validateInstance = Helpers::validateUrl('https://' . $domain . '/?block-check=' . time());
- if(!$validateInstance) {
- $res = [
- 'domain' => $domain,
- 'ready' => false,
- 'action' => 'blocked_domain'
- ];
- return response()->json($res);
- }
- $compatible = RemoteAuthService::isDomainCompatible($domain);
- if(!$compatible) {
- $res = [
- 'domain' => $domain,
- 'ready' => false,
- 'action' => 'incompatible_domain'
- ];
- return response()->json($res);
- }
- if(config('remote-auth.mastodon.domains.only_default')) {
- $defaultDomains = explode(',', config('remote-auth.mastodon.domains.default'));
- if(!in_array($domain, $defaultDomains)) {
- $res = [
- 'domain' => $domain,
- 'ready' => false,
- 'action' => 'incompatible_domain'
- ];
- return response()->json($res);
- }
- }
- if(config('remote-auth.mastodon.domains.only_custom') && config('remote-auth.mastodon.domains.custom')) {
- $customDomains = explode(',', config('remote-auth.mastodon.domains.custom'));
- if(!in_array($domain, $customDomains)) {
- $res = [
- 'domain' => $domain,
- 'ready' => false,
- 'action' => 'incompatible_domain'
- ];
- return response()->json($res);
- }
- }
- $client = RemoteAuthService::getMastodonClient($domain);
- abort_unless($client, 422, 'Invalid mastodon client');
- $request->session()->put('state', $state = Str::random(40));
- $request->session()->put('oauth_domain', $domain);
- $query = http_build_query([
- 'client_id' => $client->client_id,
- 'redirect_uri' => $client->redirect_uri,
- 'response_type' => 'code',
- 'scope' => 'read',
- 'state' => $state,
- ]);
- $request->session()->put('oauth_redirect_to', 'https://' . $domain . '/oauth/authorize?' . $query);
- $dsh = Str::random(17);
- $res = [
- 'domain' => $domain,
- 'ready' => true,
- 'dsh' => $dsh
- ];
- return response()->json($res);
- }
- public function preflight(Request $request)
- {
- if(!$request->filled('d') || !$request->filled('dsh') || !$request->session()->exists('oauth_redirect_to')) {
- return redirect('/login');
- }
- return redirect()->away($request->session()->pull('oauth_redirect_to'));
- }
- public function handleCallback(Request $request)
- {
- $domain = $request->session()->get('oauth_domain');
- if($request->filled('code')) {
- $code = $request->input('code');
- $state = $request->session()->pull('state');
- throw_unless(
- strlen($state) > 0 && $state === $request->state,
- InvalidArgumentException::class,
- 'Invalid state value.'
- );
- $res = RemoteAuthService::getToken($domain, $code);
- if(!$res || !isset($res['access_token'])) {
- $request->session()->regenerate();
- return redirect('/login');
- }
- $request->session()->put('oauth_remote_session_token', $res['access_token']);
- return redirect('/auth/mastodon/getting-started');
- }
- return redirect('/login');
- }
- public function onboarding(Request $request)
- {
- abort_unless(config_cache('pixelfed.open_registration') && config('remote-auth.mastodon.enabled'), 404);
- if($request->user()) {
- return redirect('/');
- }
- return view('auth.remote.onboarding');
- }
- public function sessionCheck(Request $request)
- {
- abort_if($request->user(), 403);
- abort_unless($request->session()->exists('oauth_domain'), 403);
- abort_unless($request->session()->exists('oauth_remote_session_token'), 403);
- $domain = $request->session()->get('oauth_domain');
- $token = $request->session()->get('oauth_remote_session_token');
- $res = RemoteAuthService::getVerifyCredentials($domain, $token);
- abort_if(!$res || !isset($res['acct']), 403, 'Invalid credentials');
- $webfinger = strtolower('@' . $res['acct'] . '@' . $domain);
- $request->session()->put('oauth_masto_webfinger', $webfinger);
- if(config('remote-auth.mastodon.max_uses.enabled')) {
- $limit = config('remote-auth.mastodon.max_uses.limit');
- $uses = RemoteAuthService::lookupWebfingerUses($webfinger);
- if($uses >= $limit) {
- return response()->json([
- 'code' => 200,
- 'msg' => 'Success!',
- 'action' => 'max_uses_reached'
- ]);
- }
- }
- $exists = RemoteAuth::whereDomain($domain)->where('webfinger', $webfinger)->whereNotNull('user_id')->first();
- if($exists && $exists->user_id) {
- return response()->json([
- 'code' => 200,
- 'msg' => 'Success!',
- 'action' => 'redirect_existing_user'
- ]);
- }
- return response()->json([
- 'code' => 200,
- 'msg' => 'Success!',
- 'action' => 'onboard'
- ]);
- }
- public function sessionGetMastodonData(Request $request)
- {
- abort_if($request->user(), 403);
- abort_unless($request->session()->exists('oauth_domain'), 403);
- abort_unless($request->session()->exists('oauth_remote_session_token'), 403);
- $domain = $request->session()->get('oauth_domain');
- $token = $request->session()->get('oauth_remote_session_token');
- $res = RemoteAuthService::getVerifyCredentials($domain, $token);
- $res['_webfinger'] = strtolower('@' . $res['acct'] . '@' . $domain);
- $res['_domain'] = strtolower($domain);
- $request->session()->put('oauth_remasto_id', $res['id']);
- $ra = RemoteAuth::updateOrCreate([
- 'domain' => $domain,
- 'webfinger' => $res['_webfinger'],
- ], [
- 'software' => 'mastodon',
- 'ip_address' => $request->ip(),
- 'bearer_token' => $token,
- 'verify_credentials' => $res,
- 'last_verify_credentials_at' => now(),
- 'last_successful_login_at' => now()
- ]);
- $request->session()->put('oauth_masto_raid', $ra->id);
- return response()->json($res);
- }
- public function sessionValidateUsername(Request $request)
- {
- abort_if($request->user(), 403);
- abort_unless($request->session()->exists('oauth_domain'), 403);
- abort_unless($request->session()->exists('oauth_remote_session_token'), 403);
- $this->validate($request, [
- 'username' => [
- 'required',
- 'min:2',
- 'max:15',
- function ($attribute, $value, $fail) {
- $dash = substr_count($value, '-');
- $underscore = substr_count($value, '_');
- $period = substr_count($value, '.');
- if(ends_with($value, ['.php', '.js', '.css'])) {
- return $fail('Username is invalid.');
- }
- if(($dash + $underscore + $period) > 1) {
- return $fail('Username is invalid. Can only contain one dash (-), period (.) or underscore (_).');
- }
- if (!ctype_alnum($value[0])) {
- return $fail('Username is invalid. Must start with a letter or number.');
- }
- if (!ctype_alnum($value[strlen($value) - 1])) {
- return $fail('Username is invalid. Must end with a letter or number.');
- }
- $val = str_replace(['_', '.', '-'], '', $value);
- if(!ctype_alnum($val)) {
- return $fail('Username is invalid. Username must be alpha-numeric and may contain dashes (-), periods (.) and underscores (_).');
- }
- $restricted = RestrictedNames::get();
- if (in_array(strtolower($value), array_map('strtolower', $restricted))) {
- return $fail('Username cannot be used.');
- }
- }
- ]
- ]);
- $username = strtolower($request->input('username'));
- $exists = User::where('username', $username)->exists();
- return response()->json([
- 'code' => 200,
- 'username' => $username,
- 'exists' => $exists
- ]);
- }
- public function sessionValidateEmail(Request $request)
- {
- abort_if($request->user(), 403);
- abort_unless($request->session()->exists('oauth_domain'), 403);
- abort_unless($request->session()->exists('oauth_remote_session_token'), 403);
- $this->validate($request, [
- 'email' => [
- 'required',
- 'email:strict,filter_unicode,dns,spoof',
- ]
- ]);
- $email = $request->input('email');
- $banned = EmailService::isBanned($email);
- $exists = User::where('email', $email)->exists();
- return response()->json([
- 'code' => 200,
- 'email' => $email,
- 'exists' => $exists,
- 'banned' => $banned
- ]);
- }
- public function sessionGetMastodonFollowers(Request $request)
- {
- abort_unless($request->session()->exists('oauth_domain'), 403);
- abort_unless($request->session()->exists('oauth_remote_session_token'), 403);
- abort_unless($request->session()->exists('oauth_remasto_id'), 403);
- $domain = $request->session()->get('oauth_domain');
- $token = $request->session()->get('oauth_remote_session_token');
- $id = $request->session()->get('oauth_remasto_id');
- $res = RemoteAuthService::getFollowing($domain, $token, $id);
- if(!$res) {
- return response()->json([
- 'code' => 200,
- 'following' => []
- ]);
- }
- $res = collect($res)->filter(fn($acct) => Helpers::validateUrl($acct['url']))->values()->toArray();
- return response()->json([
- 'code' => 200,
- 'following' => $res
- ]);
- }
- public function handleSubmit(Request $request)
- {
- abort_unless($request->session()->exists('oauth_domain'), 403);
- abort_unless($request->session()->exists('oauth_remote_session_token'), 403);
- abort_unless($request->session()->exists('oauth_remasto_id'), 403);
- abort_unless($request->session()->exists('oauth_masto_webfinger'), 403);
- abort_unless($request->session()->exists('oauth_masto_raid'), 403);
- $this->validate($request, [
- 'email' => 'required|email:strict,filter_unicode,dns,spoof',
- 'username' => [
- 'required',
- 'min:2',
- 'max:15',
- 'unique:users,username',
- function ($attribute, $value, $fail) {
- $dash = substr_count($value, '-');
- $underscore = substr_count($value, '_');
- $period = substr_count($value, '.');
- if(ends_with($value, ['.php', '.js', '.css'])) {
- return $fail('Username is invalid.');
- }
- if(($dash + $underscore + $period) > 1) {
- return $fail('Username is invalid. Can only contain one dash (-), period (.) or underscore (_).');
- }
- if (!ctype_alnum($value[0])) {
- return $fail('Username is invalid. Must start with a letter or number.');
- }
- if (!ctype_alnum($value[strlen($value) - 1])) {
- return $fail('Username is invalid. Must end with a letter or number.');
- }
- $val = str_replace(['_', '.', '-'], '', $value);
- if(!ctype_alnum($val)) {
- return $fail('Username is invalid. Username must be alpha-numeric and may contain dashes (-), periods (.) and underscores (_).');
- }
- $restricted = RestrictedNames::get();
- if (in_array(strtolower($value), array_map('strtolower', $restricted))) {
- return $fail('Username cannot be used.');
- }
- }
- ],
- 'password' => 'required|string|min:8|confirmed',
- 'name' => 'nullable|max:30'
- ]);
- $email = $request->input('email');
- $username = $request->input('username');
- $password = $request->input('password');
- $name = $request->input('name');
- $user = $this->createUser([
- 'name' => $name,
- 'username' => $username,
- 'password' => $password,
- 'email' => $email
- ]);
- $raid = $request->session()->pull('oauth_masto_raid');
- $webfinger = $request->session()->pull('oauth_masto_webfinger');
- $token = $user->createToken('Onboarding')->accessToken;
- $ra = RemoteAuth::where('id', $raid)->where('webfinger', $webfinger)->firstOrFail();
- $ra->user_id = $user->id;
- $ra->save();
- return [
- 'code' => 200,
- 'msg' => 'Success',
- 'token' => $token
- ];
- }
- public function storeBio(Request $request)
- {
- abort_unless(config_cache('pixelfed.open_registration') && config('remote-auth.mastodon.enabled'), 404);
- abort_unless($request->user(), 404);
- abort_unless($request->session()->exists('oauth_domain'), 403);
- abort_unless($request->session()->exists('oauth_remote_session_token'), 403);
- abort_unless($request->session()->exists('oauth_remasto_id'), 403);
- $this->validate($request, [
- 'bio' => 'required|nullable|max:500',
- ]);
- $profile = $request->user()->profile;
- $profile->bio = Purify::clean($request->input('bio'));
- $profile->save();
- return [200];
- }
- public function accountToId(Request $request)
- {
- abort_unless(config_cache('pixelfed.open_registration') && config('remote-auth.mastodon.enabled'), 404);
- abort_if($request->user(), 404);
- abort_unless($request->session()->exists('oauth_domain'), 403);
- abort_unless($request->session()->exists('oauth_remote_session_token'), 403);
- abort_unless($request->session()->exists('oauth_remasto_id'), 403);
- $this->validate($request, [
- 'account' => 'required|url'
- ]);
- $account = $request->input('account');
- abort_unless(substr(strtolower($account), 0, 8) === 'https://', 404);
- $host = strtolower(config('pixelfed.domain.app'));
- $domain = strtolower(parse_url($account, PHP_URL_HOST));
- if($domain == $host) {
- $username = Str::of($account)->explode('/')->last();
- $user = User::where('username', $username)->first();
- if($user) {
- return ['id' => (string) $user->profile_id];
- } else {
- return [];
- }
- } else {
- try {
- $profile = Helpers::profileFetch($account);
- if($profile) {
- return ['id' => (string) $profile->id];
- } else {
- return [];
- }
- } catch (\GuzzleHttp\Exception\RequestException $e) {
- return;
- } catch (Exception $e) {
- return [];
- }
- }
- }
- public function storeAvatar(Request $request)
- {
- abort_unless(config_cache('pixelfed.open_registration') && config('remote-auth.mastodon.enabled'), 404);
- abort_unless($request->user(), 404);
- $this->validate($request, [
- 'avatar_url' => 'required|active_url',
- ]);
- $user = $request->user();
- $profile = $user->profile;
- abort_if(!$profile->avatar, 404, 'Missing avatar');
- $avatar = $profile->avatar;
- $avatar->remote_url = $request->input('avatar_url');
- $avatar->save();
- MediaStorageService::avatar($avatar, config_cache('pixelfed.cloud_storage') == false);
- return [200];
- }
- public function finishUp(Request $request)
- {
- abort_unless(config_cache('pixelfed.open_registration') && config('remote-auth.mastodon.enabled'), 404);
- abort_unless($request->user(), 404);
- $currentWebfinger = '@' . $request->user()->username . '@' . config('pixelfed.domain.app');
- $ra = RemoteAuth::where('user_id', $request->user()->id)->firstOrFail();
- RemoteAuthService::submitToBeagle(
- $ra->webfinger,
- $ra->verify_credentials['url'],
- $currentWebfinger,
- $request->user()->url()
- );
- return [200];
- }
- public function handleLogin(Request $request)
- {
- abort_unless(config_cache('pixelfed.open_registration') && config('remote-auth.mastodon.enabled'), 404);
- abort_if($request->user(), 404);
- abort_unless($request->session()->exists('oauth_domain'), 403);
- abort_unless($request->session()->exists('oauth_remote_session_token'), 403);
- abort_unless($request->session()->exists('oauth_masto_webfinger'), 403);
- $domain = $request->session()->get('oauth_domain');
- $wf = $request->session()->get('oauth_masto_webfinger');
- $ra = RemoteAuth::where('webfinger', $wf)->where('domain', $domain)->whereNotNull('user_id')->firstOrFail();
- $user = User::findOrFail($ra->user_id);
- abort_if($user->is_admin || $user->status != null, 422, 'Invalid auth action');
- Auth::loginUsingId($ra->user_id);
- return [200];
- }
- protected function createUser($data)
- {
- event(new Registered($user = User::create([
- 'name' => Purify::clean($data['name']),
- 'username' => $data['username'],
- 'email' => $data['email'],
- 'password' => Hash::make($data['password']),
- 'email_verified_at' => config('remote-auth.mastodon.contraints.skip_email_verification') ? now() : null,
- 'app_register_ip' => request()->ip(),
- 'register_source' => 'mastodon'
- ])));
- $this->guarder()->login($user);
- return $user;
- }
- protected function guarder()
- {
- return Auth::guard();
- }
- }
|