diff --git a/app/Http/Controllers/AdminInviteController.php b/app/Http/Controllers/AdminInviteController.php index fb67801976..144a2e34ae 100644 --- a/app/Http/Controllers/AdminInviteController.php +++ b/app/Http/Controllers/AdminInviteController.php @@ -6,6 +6,7 @@ use App\Models\AdminInvite; use App\Profile; use App\User; +use Purify; use App\Util\Lexer\RestrictedNames; use Illuminate\Foundation\Auth\RegistersUsers; use Illuminate\Support\Facades\Auth; @@ -17,178 +18,226 @@ class AdminInviteController extends Controller { - public function __construct() - { - abort_if(!config('instance.admin_invites.enabled'), 404); - } - - public function index(Request $request, $code) - { - if($request->user()) { - return redirect('/'); - } - return view('invite.admin_invite', compact('code')); - } - - public function apiVerifyCheck(Request $request) - { - $this->validate($request, [ - 'token' => 'required', - ]); - - $invite = AdminInvite::whereInviteCode($request->input('token'))->first(); - abort_if(!$invite, 404); - abort_if($invite->expires_at && $invite->expires_at->lt(now()), 400, 'Invite has expired.'); - abort_if($invite->max_uses && $invite->uses >= $invite->max_uses, 400, 'Maximum invites reached.'); - $res = [ - 'message' => $invite->message, - 'max_uses' => $invite->max_uses, - 'sev' => $invite->skip_email_verification - ]; - return response()->json($res); - } - - public function apiUsernameCheck(Request $request) - { - $this->validate($request, [ - 'token' => 'required', - 'username' => 'required' - ]); - - $invite = AdminInvite::whereInviteCode($request->input('token'))->first(); - abort_if(!$invite, 404); - abort_if($invite->expires_at && $invite->expires_at->lt(now()), 400, 'Invite has expired.'); - abort_if($invite->max_uses && $invite->uses >= $invite->max_uses, 400, 'Maximum invites reached.'); - - $usernameRules = [ - 'required', - 'min:2', - 'max:15', - 'unique:users', - 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.'); - } - }, - ]; - - $rules = ['username' => $usernameRules]; - $validator = Validator::make($request->all(), $rules); - - if($validator->fails()) { - return response()->json($validator->errors(), 400); - } - - return response()->json([]); - } - - public function apiEmailCheck(Request $request) - { - $this->validate($request, [ - 'token' => 'required', - 'email' => 'required' - ]); - - $invite = AdminInvite::whereInviteCode($request->input('token'))->first(); - abort_if(!$invite, 404); - abort_if($invite->expires_at && $invite->expires_at->lt(now()), 400, 'Invite has expired.'); - abort_if($invite->max_uses && $invite->uses >= $invite->max_uses, 400, 'Maximum invites reached.'); - - $emailRules = [ - 'required', - 'string', - 'email', - 'max:255', - 'unique:users', - function ($attribute, $value, $fail) { - $banned = EmailService::isBanned($value); - if($banned) { - return $fail('Email is invalid.'); - } - }, - ]; - - $rules = ['email' => $emailRules]; - $validator = Validator::make($request->all(), $rules); - - if($validator->fails()) { - return response()->json($validator->errors(), 400); - } - - return response()->json([]); - } - - public function apiRegister(Request $request) - { - $this->validate($request, [ - 'token' => 'required', - 'username' => 'required', - 'name' => 'nullable', - 'email' => 'required|email', - 'password' => 'required', - 'password_confirm' => 'required' - ]); - - $invite = AdminInvite::whereInviteCode($request->input('token'))->firstOrFail(); - abort_if($invite->expires_at && $invite->expires_at->lt(now()), 400, 'Invite expired'); - abort_if($invite->max_uses && $invite->uses >= $invite->max_uses, 400, 'Maximum invites reached.'); - - $invite->uses = $invite->uses + 1; - - event(new Registered($user = User::create([ - 'name' => $request->input('name') ?? $request->input('username'), - 'username' => $request->input('username'), - 'email' => $request->input('email'), - 'password' => Hash::make($request->input('password')), - ]))); - - sleep(5); - - $invite->used_by = array_merge($invite->used_by ?? [], [[ - 'user_id' => $user->id, - 'username' => $user->username - ]]); - $invite->save(); - - if($invite->skip_email_verification) { - $user->email_verified_at = now(); - $user->save(); - } - - if(Auth::attempt([ - 'email' => $request->input('email'), - 'password' => $request->input('password') - ])) { - $request->session()->regenerate(); - return redirect()->intended('/'); - } else { - return response()->json([], 400); - } - } + public function __construct() + { + abort_if(!config('instance.admin_invites.enabled'), 404); + } + + public function index(Request $request, $code) + { + if($request->user()) { + return redirect('/'); + } + return view('invite.admin_invite', compact('code')); + } + + public function apiVerifyCheck(Request $request) + { + $this->validate($request, [ + 'token' => 'required', + ]); + + $invite = AdminInvite::whereInviteCode($request->input('token'))->first(); + abort_if(!$invite, 404); + abort_if($invite->expires_at && $invite->expires_at->lt(now()), 400, 'Invite has expired.'); + abort_if($invite->max_uses && $invite->uses >= $invite->max_uses, 400, 'Maximum invites reached.'); + $res = [ + 'message' => $invite->message, + 'max_uses' => $invite->max_uses, + 'sev' => $invite->skip_email_verification + ]; + return response()->json($res); + } + + public function apiUsernameCheck(Request $request) + { + $this->validate($request, [ + 'token' => 'required', + 'username' => 'required' + ]); + + $invite = AdminInvite::whereInviteCode($request->input('token'))->first(); + abort_if(!$invite, 404); + abort_if($invite->expires_at && $invite->expires_at->lt(now()), 400, 'Invite has expired.'); + abort_if($invite->max_uses && $invite->uses >= $invite->max_uses, 400, 'Maximum invites reached.'); + + $usernameRules = [ + 'required', + 'min:2', + 'max:15', + 'unique:users', + 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.'); + } + }, + ]; + + $rules = ['username' => $usernameRules]; + $validator = Validator::make($request->all(), $rules); + + if($validator->fails()) { + return response()->json($validator->errors(), 400); + } + + return response()->json([]); + } + + public function apiEmailCheck(Request $request) + { + $this->validate($request, [ + 'token' => 'required', + 'email' => 'required' + ]); + + $invite = AdminInvite::whereInviteCode($request->input('token'))->first(); + abort_if(!$invite, 404); + abort_if($invite->expires_at && $invite->expires_at->lt(now()), 400, 'Invite has expired.'); + abort_if($invite->max_uses && $invite->uses >= $invite->max_uses, 400, 'Maximum invites reached.'); + + $emailRules = [ + 'required', + 'string', + 'email', + 'max:255', + 'unique:users', + function ($attribute, $value, $fail) { + $banned = EmailService::isBanned($value); + if($banned) { + return $fail('Email is invalid.'); + } + }, + ]; + + $rules = ['email' => $emailRules]; + $validator = Validator::make($request->all(), $rules); + + if($validator->fails()) { + return response()->json($validator->errors(), 400); + } + + return response()->json([]); + } + + public function apiRegister(Request $request) + { + $this->validate($request, [ + 'token' => 'required', + 'username' => [ + 'required', + 'min:2', + 'max:15', + 'unique:users', + 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.'); + } + }, + ], + 'name' => 'nullable|string|max:'.config('pixelfed.max_name_length'), + 'email' => [ + 'required', + 'string', + 'email', + 'max:255', + 'unique:users', + function ($attribute, $value, $fail) { + $banned = EmailService::isBanned($value); + if($banned) { + return $fail('Email is invalid.'); + } + }, + ], + 'password' => 'required', + 'password_confirm' => 'required' + ]); + + $invite = AdminInvite::whereInviteCode($request->input('token'))->firstOrFail(); + abort_if($invite->expires_at && $invite->expires_at->lt(now()), 400, 'Invite expired'); + abort_if($invite->max_uses && $invite->uses >= $invite->max_uses, 400, 'Maximum invites reached.'); + + $invite->uses = $invite->uses + 1; + + event(new Registered($user = User::create([ + 'name' => Purify::clean($request->input('name')) ?? $request->input('username'), + 'username' => $request->input('username'), + 'email' => $request->input('email'), + 'password' => Hash::make($request->input('password')), + ]))); + + sleep(5); + + $invite->used_by = array_merge($invite->used_by ?? [], [[ + 'user_id' => $user->id, + 'username' => $user->username + ]]); + $invite->save(); + + if($invite->skip_email_verification) { + $user->email_verified_at = now(); + $user->save(); + } + + if(Auth::attempt([ + 'email' => $request->input('email'), + 'password' => $request->input('password') + ])) { + $request->session()->regenerate(); + return redirect()->intended('/'); + } else { + return response()->json([], 400); + } + } } diff --git a/app/Http/Controllers/Api/ApiV1Dot1Controller.php b/app/Http/Controllers/Api/ApiV1Dot1Controller.php index 43364312bf..cf90684efd 100644 --- a/app/Http/Controllers/Api/ApiV1Dot1Controller.php +++ b/app/Http/Controllers/Api/ApiV1Dot1Controller.php @@ -20,6 +20,8 @@ use App\Services\AccountService; use App\Services\StatusService; use App\Services\ProfileStatusService; +use App\Services\PublicTimelineService; +use App\Services\NetworkTimelineService; use App\Util\Lexer\RestrictedNames; use App\Services\EmailService; use Illuminate\Support\Str; @@ -29,640 +31,741 @@ use App\Mail\PasswordChange; use App\Mail\ConfirmAppEmail; use App\Http\Resources\StatusStateless; +use App\Jobs\StatusPipeline\StatusDelete; class ApiV1Dot1Controller extends Controller { - protected $fractal; - - public function __construct() - { - $this->fractal = new Fractal\Manager(); - $this->fractal->setSerializer(new ArraySerializer()); - } - - public function json($res, $code = 200, $headers = []) - { - return response()->json($res, $code, $headers, JSON_UNESCAPED_SLASHES); - } - - public function error($msg, $code = 400, $extra = [], $headers = []) - { - $res = [ - "msg" => $msg, - "code" => $code - ]; - return response()->json(array_merge($res, $extra), $code, $headers, JSON_UNESCAPED_SLASHES); - } - - public function report(Request $request) - { - $user = $request->user(); - - abort_if(!$user, 403); - abort_if($user->status != null, 403); - - $report_type = $request->input('report_type'); - $object_id = $request->input('object_id'); - $object_type = $request->input('object_type'); - - $types = [ - 'spam', - 'sensitive', - 'abusive', - 'underage', - 'violence', - 'copyright', - 'impersonation', - 'scam', - 'terrorism' - ]; - - if (!$report_type || !$object_id || !$object_type) { - return $this->error("Invalid or missing parameters", 400, ["error_code" => "ERROR_INVALID_PARAMS"]); - } - - if (!in_array($report_type, $types)) { - return $this->error("Invalid report type", 400, ["error_code" => "ERROR_TYPE_INVALID"]); - } - - if ($object_type === "user" && $object_id == $user->profile_id) { - return $this->error("Cannot self report", 400, ["error_code" => "ERROR_NO_SELF_REPORTS"]); - } - - $rpid = null; - - switch ($object_type) { - case 'post': - $object = Status::find($object_id); - if (!$object) { - return $this->error("Invalid object id", 400, ["error_code" => "ERROR_INVALID_OBJECT_ID"]); - } - $object_type = 'App\Status'; - $exists = Report::whereUserId($user->id) - ->whereObjectId($object->id) - ->whereObjectType('App\Status') - ->count(); - - $rpid = $object->profile_id; - break; - - case 'user': - $object = Profile::find($object_id); - if (!$object) { - return $this->error("Invalid object id", 400, ["error_code" => "ERROR_INVALID_OBJECT_ID"]); - } - $object_type = 'App\Profile'; - $exists = Report::whereUserId($user->id) - ->whereObjectId($object->id) - ->whereObjectType('App\Profile') - ->count(); - $rpid = $object->id; - break; - - default: - return $this->error("Invalid report type", 400, ["error_code" => "ERROR_REPORT_OBJECT_TYPE_INVALID"]); - break; - } - - if ($exists !== 0) { - return $this->error("Duplicate report", 400, ["error_code" => "ERROR_REPORT_DUPLICATE"]); - } - - if ($object->profile_id == $user->profile_id) { - return $this->error("Cannot self report", 400, ["error_code" => "ERROR_NO_SELF_REPORTS"]); - } - - $report = new Report; - $report->profile_id = $user->profile_id; - $report->user_id = $user->id; - $report->object_id = $object->id; - $report->object_type = $object_type; - $report->reported_profile_id = $rpid; - $report->type = $report_type; - $report->save(); - - $res = [ - "msg" => "Successfully sent report", - "code" => 200 - ]; - return $this->json($res); - } - - /** - * DELETE /api/v1.1/accounts/avatar - * - * @return \App\Transformer\Api\AccountTransformer - */ - public function deleteAvatar(Request $request) - { - $user = $request->user(); - - abort_if(!$user, 403); - abort_if($user->status != null, 403); - - $avatar = $user->profile->avatar; - - if( $avatar->media_path == 'public/avatars/default.png' || - $avatar->media_path == 'public/avatars/default.jpg' - ) { - return AccountService::get($user->profile_id); - } - - if(is_file(storage_path('app/' . $avatar->media_path))) { - @unlink(storage_path('app/' . $avatar->media_path)); - } - - $avatar->media_path = 'public/avatars/default.jpg'; - $avatar->change_count = $avatar->change_count + 1; - $avatar->save(); - - Cache::forget('avatar:' . $user->profile_id); - Cache::forget("avatar:{$user->profile_id}"); - Cache::forget('user:account:id:'.$user->id); - AccountService::del($user->profile_id); - - return AccountService::get($user->profile_id); - } - - /** - * GET /api/v1.1/accounts/{id}/posts - * - * @return \App\Transformer\Api\StatusTransformer - */ - public function accountPosts(Request $request, $id) - { - $user = $request->user(); - - abort_if(!$user, 403); - abort_if($user->status != null, 403); - - $account = AccountService::get($id); - - if(!$account || $account['username'] !== $request->input('username')) { - return $this->json([]); - } - - $posts = ProfileStatusService::get($id); - - if(!$posts) { - return $this->json([]); - } - - $res = collect($posts) - ->map(function($id) { - return StatusService::get($id); - }) - ->filter(function($post) { - return $post && isset($post['account']); - }) - ->toArray(); - - return $this->json($res); - } - - /** - * POST /api/v1.1/accounts/change-password - * - * @return \App\Transformer\Api\AccountTransformer - */ - public function accountChangePassword(Request $request) - { - $user = $request->user(); - abort_if(!$user, 403); - abort_if($user->status != null, 403); - - $this->validate($request, [ - 'current_password' => 'bail|required|current_password', - 'new_password' => 'required|min:' . config('pixelfed.min_password_length', 8), - 'confirm_password' => 'required|same:new_password' - ],[ - 'current_password' => 'The password you entered is incorrect' - ]); - - $user->password = bcrypt($request->input('new_password')); - $user->save(); - - $log = new AccountLog; - $log->user_id = $user->id; - $log->item_id = $user->id; - $log->item_type = 'App\User'; - $log->action = 'account.edit.password'; - $log->message = 'Password changed'; - $log->link = null; - $log->ip_address = $request->ip(); - $log->user_agent = $request->userAgent(); - $log->save(); - - Mail::to($request->user())->send(new PasswordChange($user)); - - return $this->json(AccountService::get($user->profile_id)); - } - - /** - * GET /api/v1.1/accounts/login-activity - * - * @return array - */ - public function accountLoginActivity(Request $request) - { - $user = $request->user(); - abort_if(!$user, 403); - abort_if($user->status != null, 403); - $agent = new Agent(); - $currentIp = $request->ip(); - - $activity = AccountLog::whereUserId($user->id) - ->whereAction('auth.login') - ->orderBy('created_at', 'desc') - ->groupBy('ip_address') - ->limit(10) - ->get() - ->map(function($item) use($agent, $currentIp) { - $agent->setUserAgent($item->user_agent); - return [ - 'id' => $item->id, - 'action' => $item->action, - 'ip' => $item->ip_address, - 'ip_current' => $item->ip_address === $currentIp, - 'is_mobile' => $agent->isMobile(), - 'device' => $agent->device(), - 'browser' => $agent->browser(), - 'platform' => $agent->platform(), - 'created_at' => $item->created_at->format('c') - ]; - }); - - return $this->json($activity); - } - - /** - * GET /api/v1.1/accounts/two-factor - * - * @return array - */ - public function accountTwoFactor(Request $request) - { - $user = $request->user(); - abort_if(!$user, 403); - abort_if($user->status != null, 403); - - $res = [ - 'active' => (bool) $user->{'2fa_enabled'}, - 'setup_at' => $user->{'2fa_setup_at'} - ]; - return $this->json($res); - } - - /** - * GET /api/v1.1/accounts/emails-from-pixelfed - * - * @return array - */ - public function accountEmailsFromPixelfed(Request $request) - { - $user = $request->user(); - abort_if(!$user, 403); - abort_if($user->status != null, 403); - $from = config('mail.from.address'); - - $emailVerifications = EmailVerification::whereUserId($user->id) - ->orderByDesc('id') - ->where('created_at', '>', now()->subDays(14)) - ->limit(10) - ->get() - ->map(function($mail) use($user, $from) { - return [ - 'type' => 'Email Verification', - 'subject' => 'Confirm Email', - 'to_address' => $user->email, - 'from_address' => $from, - 'created_at' => str_replace('@', 'at', $mail->created_at->format('M j, Y @ g:i:s A')) - ]; - }) - ->toArray(); - - $passwordResets = DB::table('password_resets') - ->whereEmail($user->email) - ->where('created_at', '>', now()->subDays(14)) - ->orderByDesc('created_at') - ->limit(10) - ->get() - ->map(function($mail) use($user, $from) { - return [ - 'type' => 'Password Reset', - 'subject' => 'Reset Password Notification', - 'to_address' => $user->email, - 'from_address' => $from, - 'created_at' => str_replace('@', 'at', now()->parse($mail->created_at)->format('M j, Y @ g:i:s A')) - ]; - }) - ->toArray(); - - $passwordChanges = AccountLog::whereUserId($user->id) - ->whereAction('account.edit.password') - ->where('created_at', '>', now()->subDays(14)) - ->orderByDesc('created_at') - ->limit(10) - ->get() - ->map(function($mail) use($user, $from) { - return [ - 'type' => 'Password Change', - 'subject' => 'Password Change', - 'to_address' => $user->email, - 'from_address' => $from, - 'created_at' => str_replace('@', 'at', now()->parse($mail->created_at)->format('M j, Y @ g:i:s A')) - ]; - }) - ->toArray(); - - $res = collect([]) - ->merge($emailVerifications) - ->merge($passwordResets) - ->merge($passwordChanges) - ->sortByDesc('created_at') - ->values(); - - return $this->json($res); - } - - - /** - * GET /api/v1.1/accounts/apps-and-applications - * - * @return array - */ - public function accountApps(Request $request) - { - $user = $request->user(); - abort_if(!$user, 403); - abort_if($user->status != null, 403); - - $res = $user->tokens->sortByDesc('created_at')->take(10)->map(function($token, $key) { - return [ - 'id' => $key + 1, - 'did' => encrypt($token->id), - 'name' => $token->client->name, - 'scopes' => $token->scopes, - 'revoked' => $token->revoked, - 'created_at' => str_replace('@', 'at', now()->parse($token->created_at)->format('M j, Y @ g:i:s A')), - 'expires_at' => str_replace('@', 'at', now()->parse($token->expires_at)->format('M j, Y @ g:i:s A')) - ]; - }); - - return $this->json($res); - } - - public function inAppRegistrationPreFlightCheck(Request $request) - { - return [ - 'open' => config('pixelfed.open_registration'), - 'iara' => config('pixelfed.allow_app_registration') - ]; - } - - public function inAppRegistration(Request $request) - { - abort_if($request->user(), 404); - abort_unless(config('pixelfed.open_registration'), 404); - abort_unless(config('pixelfed.allow_app_registration'), 404); - abort_unless($request->hasHeader('X-PIXELFED-APP'), 403); - $this->validate($request, [ - 'email' => [ - 'required', - 'string', - 'email', - 'max:255', - 'unique:users', - function ($attribute, $value, $fail) { - $banned = EmailService::isBanned($value); - if($banned) { - return $fail('Email is invalid.'); - } - }, - ], - 'username' => [ - 'required', - 'min:2', - 'max:15', - 'unique:users', - 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', - ]); - - $email = $request->input('email'); - $username = $request->input('username'); - $password = $request->input('password'); - - if(config('database.default') == 'pgsql') { - $username = strtolower($username); - $email = strtolower($email); - } - - $user = new User; - $user->name = $username; - $user->username = $username; - $user->email = $email; - $user->password = Hash::make($password); - $user->register_source = 'app'; - $user->app_register_ip = $request->ip(); - $user->app_register_token = Str::random(32); - $user->save(); - - $rtoken = Str::random(mt_rand(64, 70)); - - $verify = new EmailVerification(); - $verify->user_id = $user->id; - $verify->email = $user->email; - $verify->user_token = $user->app_register_token; - $verify->random_token = $rtoken; - $verify->save(); - - $appUrl = url('/api/v1.1/auth/iarer?ut=' . $user->app_register_token . '&rt=' . $rtoken); - - Mail::to($user->email)->send(new ConfirmAppEmail($verify, $appUrl)); - - return response()->json([ - 'success' => true, - ]); - } - - public function inAppRegistrationEmailRedirect(Request $request) - { - $this->validate($request, [ - 'ut' => 'required', - 'rt' => 'required' - ]); - $ut = $request->input('ut'); - $rt = $request->input('rt'); - $url = 'pixelfed://confirm-account/'. $ut . '?rt=' . $rt; - return redirect()->away($url); - } - - public function inAppRegistrationConfirm(Request $request) - { - abort_if($request->user(), 404); - abort_unless(config('pixelfed.open_registration'), 404); - abort_unless(config('pixelfed.allow_app_registration'), 404); - abort_unless($request->hasHeader('X-PIXELFED-APP'), 403); - $this->validate($request, [ - 'user_token' => 'required', - 'random_token' => 'required', - 'email' => 'required' - ]); - - $verify = EmailVerification::whereEmail($request->input('email')) - ->whereUserToken($request->input('user_token')) - ->whereRandomToken($request->input('random_token')) - ->first(); - - if(!$verify) { - return response()->json(['error' => 'Invalid tokens'], 403); - } - - if($verify->created_at->lt(now()->subHours(24))) { - $verify->delete(); - return response()->json(['error' => 'Invalid tokens'], 403); - } - - $user = User::findOrFail($verify->user_id); - $user->email_verified_at = now(); - $user->last_active_at = now(); - $user->save(); - - $token = $user->createToken('Pixelfed'); - - return response()->json([ - 'access_token' => $token->accessToken - ]); - } - - public function archive(Request $request, $id) - { - abort_if(!$request->user(), 403); - - $status = Status::whereNull('in_reply_to_id') - ->whereNull('reblog_of_id') - ->whereProfileId($request->user()->profile_id) - ->findOrFail($id); - - if($status->scope === 'archived') { - return [200]; - } - - $archive = new StatusArchived; - $archive->status_id = $status->id; - $archive->profile_id = $status->profile_id; - $archive->original_scope = $status->scope; - $archive->save(); - - $status->scope = 'archived'; - $status->visibility = 'draft'; - $status->save(); - StatusService::del($status->id, true); - AccountService::syncPostCount($status->profile_id); - - return [200]; - } - - - public function unarchive(Request $request, $id) - { - abort_if(!$request->user(), 403); - - $status = Status::whereNull('in_reply_to_id') - ->whereNull('reblog_of_id') - ->whereProfileId($request->user()->profile_id) - ->findOrFail($id); - - if($status->scope !== 'archived') { - return [200]; - } - - $archive = StatusArchived::whereStatusId($status->id) - ->whereProfileId($status->profile_id) - ->firstOrFail(); - - $status->scope = $archive->original_scope; - $status->visibility = $archive->original_scope; - $status->save(); - $archive->delete(); - StatusService::del($status->id, true); - AccountService::syncPostCount($status->profile_id); - - return [200]; - } - - public function archivedPosts(Request $request) - { - abort_if(!$request->user(), 403); - - $statuses = Status::whereProfileId($request->user()->profile_id) - ->whereScope('archived') - ->orderByDesc('id') - ->cursorPaginate(10); - - return StatusStateless::collection($statuses); - } - - public function placesById(Request $request, $id, $slug) - { - abort_if(!$request->user(), 403); - - $place = Place::whereSlug($slug)->findOrFail($id); - - $posts = Cache::remember('pf-api:v1.1:places-by-id:' . $place->id, 3600, function() use($place) { - return Status::wherePlaceId($place->id) - ->whereNull('uri') - ->whereScope('public') - ->orderByDesc('created_at') - ->limit(60) - ->pluck('id'); - }); - - $posts = $posts->map(function($id) { - return StatusService::get($id); - }) - ->filter() - ->values(); - - return ['place' => [ - 'id' => $place->id, - 'name' => $place->name, - 'slug' => $place->slug, - 'country' => $place->country, - 'lat' => $place->lat, - 'long' => $place->long - ], 'posts' => $posts]; - } + protected $fractal; + + public function __construct() + { + $this->fractal = new Fractal\Manager(); + $this->fractal->setSerializer(new ArraySerializer()); + } + + public function json($res, $code = 200, $headers = []) + { + return response()->json($res, $code, $headers, JSON_UNESCAPED_SLASHES); + } + + public function error($msg, $code = 400, $extra = [], $headers = []) + { + $res = [ + "msg" => $msg, + "code" => $code + ]; + return response()->json(array_merge($res, $extra), $code, $headers, JSON_UNESCAPED_SLASHES); + } + + public function report(Request $request) + { + $user = $request->user(); + + abort_if(!$user, 403); + abort_if($user->status != null, 403); + + $report_type = $request->input('report_type'); + $object_id = $request->input('object_id'); + $object_type = $request->input('object_type'); + + $types = [ + 'spam', + 'sensitive', + 'abusive', + 'underage', + 'violence', + 'copyright', + 'impersonation', + 'scam', + 'terrorism' + ]; + + if (!$report_type || !$object_id || !$object_type) { + return $this->error("Invalid or missing parameters", 400, ["error_code" => "ERROR_INVALID_PARAMS"]); + } + + if (!in_array($report_type, $types)) { + return $this->error("Invalid report type", 400, ["error_code" => "ERROR_TYPE_INVALID"]); + } + + if ($object_type === "user" && $object_id == $user->profile_id) { + return $this->error("Cannot self report", 400, ["error_code" => "ERROR_NO_SELF_REPORTS"]); + } + + $rpid = null; + + switch ($object_type) { + case 'post': + $object = Status::find($object_id); + if (!$object) { + return $this->error("Invalid object id", 400, ["error_code" => "ERROR_INVALID_OBJECT_ID"]); + } + $object_type = 'App\Status'; + $exists = Report::whereUserId($user->id) + ->whereObjectId($object->id) + ->whereObjectType('App\Status') + ->count(); + + $rpid = $object->profile_id; + break; + + case 'user': + $object = Profile::find($object_id); + if (!$object) { + return $this->error("Invalid object id", 400, ["error_code" => "ERROR_INVALID_OBJECT_ID"]); + } + $object_type = 'App\Profile'; + $exists = Report::whereUserId($user->id) + ->whereObjectId($object->id) + ->whereObjectType('App\Profile') + ->count(); + $rpid = $object->id; + break; + + default: + return $this->error("Invalid report type", 400, ["error_code" => "ERROR_REPORT_OBJECT_TYPE_INVALID"]); + break; + } + + if ($exists !== 0) { + return $this->error("Duplicate report", 400, ["error_code" => "ERROR_REPORT_DUPLICATE"]); + } + + if ($object->profile_id == $user->profile_id) { + return $this->error("Cannot self report", 400, ["error_code" => "ERROR_NO_SELF_REPORTS"]); + } + + $report = new Report; + $report->profile_id = $user->profile_id; + $report->user_id = $user->id; + $report->object_id = $object->id; + $report->object_type = $object_type; + $report->reported_profile_id = $rpid; + $report->type = $report_type; + $report->save(); + + $res = [ + "msg" => "Successfully sent report", + "code" => 200 + ]; + return $this->json($res); + } + + /** + * DELETE /api/v1.1/accounts/avatar + * + * @return \App\Transformer\Api\AccountTransformer + */ + public function deleteAvatar(Request $request) + { + $user = $request->user(); + + abort_if(!$user, 403); + abort_if($user->status != null, 403); + + $avatar = $user->profile->avatar; + + if( $avatar->media_path == 'public/avatars/default.png' || + $avatar->media_path == 'public/avatars/default.jpg' + ) { + return AccountService::get($user->profile_id); + } + + if(is_file(storage_path('app/' . $avatar->media_path))) { + @unlink(storage_path('app/' . $avatar->media_path)); + } + + $avatar->media_path = 'public/avatars/default.jpg'; + $avatar->change_count = $avatar->change_count + 1; + $avatar->save(); + + Cache::forget('avatar:' . $user->profile_id); + Cache::forget("avatar:{$user->profile_id}"); + Cache::forget('user:account:id:'.$user->id); + AccountService::del($user->profile_id); + + return AccountService::get($user->profile_id); + } + + /** + * GET /api/v1.1/accounts/{id}/posts + * + * @return \App\Transformer\Api\StatusTransformer + */ + public function accountPosts(Request $request, $id) + { + $user = $request->user(); + + abort_if(!$user, 403); + abort_if($user->status != null, 403); + + $account = AccountService::get($id); + + if(!$account || $account['username'] !== $request->input('username')) { + return $this->json([]); + } + + $posts = ProfileStatusService::get($id); + + if(!$posts) { + return $this->json([]); + } + + $res = collect($posts) + ->map(function($id) { + return StatusService::get($id); + }) + ->filter(function($post) { + return $post && isset($post['account']); + }) + ->toArray(); + + return $this->json($res); + } + + /** + * POST /api/v1.1/accounts/change-password + * + * @return \App\Transformer\Api\AccountTransformer + */ + public function accountChangePassword(Request $request) + { + $user = $request->user(); + abort_if(!$user, 403); + abort_if($user->status != null, 403); + + $this->validate($request, [ + 'current_password' => 'bail|required|current_password', + 'new_password' => 'required|min:' . config('pixelfed.min_password_length', 8), + 'confirm_password' => 'required|same:new_password' + ],[ + 'current_password' => 'The password you entered is incorrect' + ]); + + $user->password = bcrypt($request->input('new_password')); + $user->save(); + + $log = new AccountLog; + $log->user_id = $user->id; + $log->item_id = $user->id; + $log->item_type = 'App\User'; + $log->action = 'account.edit.password'; + $log->message = 'Password changed'; + $log->link = null; + $log->ip_address = $request->ip(); + $log->user_agent = $request->userAgent(); + $log->save(); + + Mail::to($request->user())->send(new PasswordChange($user)); + + return $this->json(AccountService::get($user->profile_id)); + } + + /** + * GET /api/v1.1/accounts/login-activity + * + * @return array + */ + public function accountLoginActivity(Request $request) + { + $user = $request->user(); + abort_if(!$user, 403); + abort_if($user->status != null, 403); + $agent = new Agent(); + $currentIp = $request->ip(); + + $activity = AccountLog::whereUserId($user->id) + ->whereAction('auth.login') + ->orderBy('created_at', 'desc') + ->groupBy('ip_address') + ->limit(10) + ->get() + ->map(function($item) use($agent, $currentIp) { + $agent->setUserAgent($item->user_agent); + return [ + 'id' => $item->id, + 'action' => $item->action, + 'ip' => $item->ip_address, + 'ip_current' => $item->ip_address === $currentIp, + 'is_mobile' => $agent->isMobile(), + 'device' => $agent->device(), + 'browser' => $agent->browser(), + 'platform' => $agent->platform(), + 'created_at' => $item->created_at->format('c') + ]; + }); + + return $this->json($activity); + } + + /** + * GET /api/v1.1/accounts/two-factor + * + * @return array + */ + public function accountTwoFactor(Request $request) + { + $user = $request->user(); + abort_if(!$user, 403); + abort_if($user->status != null, 403); + + $res = [ + 'active' => (bool) $user->{'2fa_enabled'}, + 'setup_at' => $user->{'2fa_setup_at'} + ]; + return $this->json($res); + } + + /** + * GET /api/v1.1/accounts/emails-from-pixelfed + * + * @return array + */ + public function accountEmailsFromPixelfed(Request $request) + { + $user = $request->user(); + abort_if(!$user, 403); + abort_if($user->status != null, 403); + $from = config('mail.from.address'); + + $emailVerifications = EmailVerification::whereUserId($user->id) + ->orderByDesc('id') + ->where('created_at', '>', now()->subDays(14)) + ->limit(10) + ->get() + ->map(function($mail) use($user, $from) { + return [ + 'type' => 'Email Verification', + 'subject' => 'Confirm Email', + 'to_address' => $user->email, + 'from_address' => $from, + 'created_at' => str_replace('@', 'at', $mail->created_at->format('M j, Y @ g:i:s A')) + ]; + }) + ->toArray(); + + $passwordResets = DB::table('password_resets') + ->whereEmail($user->email) + ->where('created_at', '>', now()->subDays(14)) + ->orderByDesc('created_at') + ->limit(10) + ->get() + ->map(function($mail) use($user, $from) { + return [ + 'type' => 'Password Reset', + 'subject' => 'Reset Password Notification', + 'to_address' => $user->email, + 'from_address' => $from, + 'created_at' => str_replace('@', 'at', now()->parse($mail->created_at)->format('M j, Y @ g:i:s A')) + ]; + }) + ->toArray(); + + $passwordChanges = AccountLog::whereUserId($user->id) + ->whereAction('account.edit.password') + ->where('created_at', '>', now()->subDays(14)) + ->orderByDesc('created_at') + ->limit(10) + ->get() + ->map(function($mail) use($user, $from) { + return [ + 'type' => 'Password Change', + 'subject' => 'Password Change', + 'to_address' => $user->email, + 'from_address' => $from, + 'created_at' => str_replace('@', 'at', now()->parse($mail->created_at)->format('M j, Y @ g:i:s A')) + ]; + }) + ->toArray(); + + $res = collect([]) + ->merge($emailVerifications) + ->merge($passwordResets) + ->merge($passwordChanges) + ->sortByDesc('created_at') + ->values(); + + return $this->json($res); + } + + /** + * GET /api/v1.1/accounts/apps-and-applications + * + * @return array + */ + public function accountApps(Request $request) + { + $user = $request->user(); + abort_if(!$user, 403); + abort_if($user->status != null, 403); + + $res = $user->tokens->sortByDesc('created_at')->take(10)->map(function($token, $key) { + return [ + 'id' => $key + 1, + 'did' => encrypt($token->id), + 'name' => $token->client->name, + 'scopes' => $token->scopes, + 'revoked' => $token->revoked, + 'created_at' => str_replace('@', 'at', now()->parse($token->created_at)->format('M j, Y @ g:i:s A')), + 'expires_at' => str_replace('@', 'at', now()->parse($token->expires_at)->format('M j, Y @ g:i:s A')) + ]; + }); + + return $this->json($res); + } + + public function inAppRegistrationPreFlightCheck(Request $request) + { + return [ + 'open' => config('pixelfed.open_registration'), + 'iara' => config('pixelfed.allow_app_registration') + ]; + } + + public function inAppRegistration(Request $request) + { + abort_if($request->user(), 404); + abort_unless(config('pixelfed.open_registration'), 404); + abort_unless(config('pixelfed.allow_app_registration'), 404); + abort_unless($request->hasHeader('X-PIXELFED-APP'), 403); + $this->validate($request, [ + 'email' => [ + 'required', + 'string', + 'email', + 'max:255', + 'unique:users', + function ($attribute, $value, $fail) { + $banned = EmailService::isBanned($value); + if($banned) { + return $fail('Email is invalid.'); + } + }, + ], + 'username' => [ + 'required', + 'min:2', + 'max:15', + 'unique:users', + 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', + ]); + + $email = $request->input('email'); + $username = $request->input('username'); + $password = $request->input('password'); + + if(config('database.default') == 'pgsql') { + $username = strtolower($username); + $email = strtolower($email); + } + + $user = new User; + $user->name = $username; + $user->username = $username; + $user->email = $email; + $user->password = Hash::make($password); + $user->register_source = 'app'; + $user->app_register_ip = $request->ip(); + $user->app_register_token = Str::random(32); + $user->save(); + + $rtoken = Str::random(mt_rand(64, 70)); + + $verify = new EmailVerification(); + $verify->user_id = $user->id; + $verify->email = $user->email; + $verify->user_token = $user->app_register_token; + $verify->random_token = $rtoken; + $verify->save(); + + $appUrl = url('/api/v1.1/auth/iarer?ut=' . $user->app_register_token . '&rt=' . $rtoken); + + Mail::to($user->email)->send(new ConfirmAppEmail($verify, $appUrl)); + + return response()->json([ + 'success' => true, + ]); + } + + public function inAppRegistrationEmailRedirect(Request $request) + { + $this->validate($request, [ + 'ut' => 'required', + 'rt' => 'required' + ]); + $ut = $request->input('ut'); + $rt = $request->input('rt'); + $url = 'pixelfed://confirm-account/'. $ut . '?rt=' . $rt; + return redirect()->away($url); + } + + public function inAppRegistrationConfirm(Request $request) + { + abort_if($request->user(), 404); + abort_unless(config('pixelfed.open_registration'), 404); + abort_unless(config('pixelfed.allow_app_registration'), 404); + abort_unless($request->hasHeader('X-PIXELFED-APP'), 403); + $this->validate($request, [ + 'user_token' => 'required', + 'random_token' => 'required', + 'email' => 'required' + ]); + + $verify = EmailVerification::whereEmail($request->input('email')) + ->whereUserToken($request->input('user_token')) + ->whereRandomToken($request->input('random_token')) + ->first(); + + if(!$verify) { + return response()->json(['error' => 'Invalid tokens'], 403); + } + + if($verify->created_at->lt(now()->subHours(24))) { + $verify->delete(); + return response()->json(['error' => 'Invalid tokens'], 403); + } + + $user = User::findOrFail($verify->user_id); + $user->email_verified_at = now(); + $user->last_active_at = now(); + $user->save(); + + $token = $user->createToken('Pixelfed'); + + return response()->json([ + 'access_token' => $token->accessToken + ]); + } + + public function archive(Request $request, $id) + { + abort_if(!$request->user(), 403); + + $status = Status::whereNull('in_reply_to_id') + ->whereNull('reblog_of_id') + ->whereProfileId($request->user()->profile_id) + ->findOrFail($id); + + if($status->scope === 'archived') { + return [200]; + } + + $archive = new StatusArchived; + $archive->status_id = $status->id; + $archive->profile_id = $status->profile_id; + $archive->original_scope = $status->scope; + $archive->save(); + + $status->scope = 'archived'; + $status->visibility = 'draft'; + $status->save(); + StatusService::del($status->id, true); + AccountService::syncPostCount($status->profile_id); + + return [200]; + } + + public function unarchive(Request $request, $id) + { + abort_if(!$request->user(), 403); + + $status = Status::whereNull('in_reply_to_id') + ->whereNull('reblog_of_id') + ->whereProfileId($request->user()->profile_id) + ->findOrFail($id); + + if($status->scope !== 'archived') { + return [200]; + } + + $archive = StatusArchived::whereStatusId($status->id) + ->whereProfileId($status->profile_id) + ->firstOrFail(); + + $status->scope = $archive->original_scope; + $status->visibility = $archive->original_scope; + $status->save(); + $archive->delete(); + StatusService::del($status->id, true); + AccountService::syncPostCount($status->profile_id); + + return [200]; + } + + public function archivedPosts(Request $request) + { + abort_if(!$request->user(), 403); + + $statuses = Status::whereProfileId($request->user()->profile_id) + ->whereScope('archived') + ->orderByDesc('id') + ->cursorPaginate(10); + + return StatusStateless::collection($statuses); + } + + public function placesById(Request $request, $id, $slug) + { + abort_if(!$request->user(), 403); + + $place = Place::whereSlug($slug)->findOrFail($id); + + $posts = Cache::remember('pf-api:v1.1:places-by-id:' . $place->id, 3600, function() use($place) { + return Status::wherePlaceId($place->id) + ->whereNull('uri') + ->whereScope('public') + ->orderByDesc('created_at') + ->limit(60) + ->pluck('id'); + }); + + $posts = $posts->map(function($id) { + return StatusService::get($id); + }) + ->filter() + ->values(); + + return [ + 'place' => + [ + 'id' => $place->id, + 'name' => $place->name, + 'slug' => $place->slug, + 'country' => $place->country, + 'lat' => $place->lat, + 'long' => $place->long + ], + 'posts' => $posts]; + } + + public function moderatePost(Request $request, $id) + { + abort_if(!$request->user(), 403); + abort_if($request->user()->is_admin != true, 403); + + $this->validate($request, [ + 'action' => 'required|in:cw,mark-public,mark-unlisted,mark-private,mark-spammer,delete' + ]); + + $action = $request->input('action'); + $status = Status::find($id); + + if(!$status) { + return response()->json(['error' => 'Cannot find status'], 400); + } + + if($status->uri == null) { + if($status->profile->user && $status->profile->user->is_admin) { + return response()->json(['error' => 'Cannot moderate admin accounts'], 400); + } + } + + if($action == 'mark-spammer') { + $status->profile->update([ + 'unlisted' => true, + 'cw' => true, + 'no_autolink' => true + ]); + + Status::whereProfileId($status->profile_id) + ->get() + ->each(function($s) { + if(in_array($s->scope, ['public', 'unlisted'])) { + $s->scope = 'private'; + $s->visibility = 'private'; + } + $s->is_nsfw = true; + $s->save(); + StatusService::del($s->id, true); + }); + + Cache::forget('pf:bouncer_v0:exemption_by_pid:' . $status->profile_id); + Cache::forget('pf:bouncer_v0:recent_by_pid:' . $status->profile_id); + Cache::forget('admin-dash:reports:spam-count'); + } else if ($action == 'cw') { + $state = $status->is_nsfw; + $status->is_nsfw = !$state; + $status->save(); + StatusService::del($status->id); + } else if ($action == 'mark-public') { + $state = $status->scope; + $status->scope = 'public'; + $status->visibility = 'public'; + $status->save(); + StatusService::del($status->id, true); + if($state !== 'public') { + if($status->uri) { + NetworkTimelineService::add($status->id); + } else { + PublicTimelineService::add($status->id); + } + } + } else if ($action == 'mark-unlisted') { + $state = $status->scope; + $status->scope = 'unlisted'; + $status->visibility = 'unlisted'; + $status->save(); + StatusService::del($status->id); + if($state == 'public') { + PublicTimelineService::del($status->id); + NetworkTimelineService::del($status->id); + } + } else if ($action == 'mark-private') { + $state = $status->scope; + $status->scope = 'private'; + $status->visibility = 'private'; + $status->save(); + StatusService::del($status->id); + if($state == 'public') { + PublicTimelineService::del($status->id); + NetworkTimelineService::del($status->id); + } + } else if ($action == 'delete') { + PublicTimelineService::del($status->id); + NetworkTimelineService::del($status->id); + Cache::forget('_api:statuses:recent_9:' . $status->profile_id); + Cache::forget('profile:status_count:' . $status->profile_id); + Cache::forget('profile:embed:' . $status->profile_id); + StatusService::del($status->id, true); + Cache::forget('profile:status_count:'.$status->profile_id); + StatusDelete::dispatch($status); + return []; + } + + Cache::forget('_api:statuses:recent_9:'.$status->profile_id); + + return StatusService::get($status->id, false); + } } diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php index ff0e647ae3..f38892c8a7 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -4,6 +4,7 @@ use App\Http\Controllers\Controller; use App\User; +use Purify; use App\Util\Lexer\RestrictedNames; use Illuminate\Foundation\Auth\RegistersUsers; use Illuminate\Support\Facades\Hash; @@ -157,7 +158,7 @@ protected function create(array $data) } return User::create([ - 'name' => $data['name'], + 'name' => Purify::clean($data['name']), 'username' => $data['username'], 'email' => $data['email'], 'password' => Hash::make($data['password']), diff --git a/routes/api.php b/routes/api.php index 652440de3a..a4d0c412a2 100644 --- a/routes/api.php +++ b/routes/api.php @@ -191,6 +191,7 @@ }); Route::group(['prefix' => 'admin'], function() use($middleware) { + Route::post('moderate/post/{id}', 'Api\ApiV1Dot1Controller@moderatePost')->middleware($middleware); Route::get('supported', 'Api\AdminApiController@supported')->middleware($middleware); Route::get('stats', 'Api\AdminApiController@getStats')->middleware($middleware);