refactor(auth): replace myth/auth with codeigniter/shield + define new roles
closes #222
This commit is contained in:
parent
c760acc79d
commit
c1287cbe6c
|
@ -29,6 +29,7 @@
|
|||
"bmewburn.vscode-intelephense-client",
|
||||
"bradlc.vscode-tailwindcss",
|
||||
"breezelin.phpstan",
|
||||
"DavidAnson.vscode-markdownlint",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"eamodio.gitlens",
|
||||
"esbenp.prettier-vscode",
|
||||
|
@ -41,6 +42,7 @@
|
|||
"runem.lit-plugin",
|
||||
"streetsidesoftware.code-spell-checker",
|
||||
"stylelint.vscode-stylelint",
|
||||
"wayou.vscode-todo-highlight"
|
||||
"wayou.vscode-todo-highlight",
|
||||
"yzhang.markdown-all-in-one"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -29,6 +29,10 @@ if (! function_exists('view')) {
|
|||
*/
|
||||
function view(string $name, array $data = [], array $options = []): string
|
||||
{
|
||||
if (array_key_exists('theme', $options)) {
|
||||
Theme::setTheme($options['theme']);
|
||||
}
|
||||
|
||||
$path = Theme::path();
|
||||
|
||||
/** @var CodeIgniter\View\View $renderer */
|
||||
|
@ -55,6 +59,8 @@ if (! function_exists('lang')) {
|
|||
*
|
||||
* @param array<int|string, string> $args
|
||||
*
|
||||
* TODO: remove, and escape args when necessary
|
||||
*
|
||||
* @return string|string[]
|
||||
*/
|
||||
function lang(string $line, array $args = [], ?string $locale = null, bool $escape = true): string | array
|
||||
|
|
|
@ -77,7 +77,7 @@ class Email extends BaseConfig
|
|||
/**
|
||||
* Type of mail, either 'text' or 'html'
|
||||
*/
|
||||
public string $mailType = 'text';
|
||||
public string $mailType = 'html';
|
||||
|
||||
/**
|
||||
* Character set (utf-8, iso-8859-1, etc.)
|
||||
|
|
|
@ -9,7 +9,6 @@ use App\Entities\Post;
|
|||
use App\Models\EpisodeModel;
|
||||
use CodeIgniter\Events\Events;
|
||||
use CodeIgniter\Exceptions\FrameworkException;
|
||||
use Modules\Auth\Entities\User;
|
||||
|
||||
/*
|
||||
* --------------------------------------------------------------------
|
||||
|
@ -56,21 +55,6 @@ Events::on('pre_system', static function () {
|
|||
}
|
||||
});
|
||||
|
||||
Events::on('login', static function (User $user): void {
|
||||
helper('auth');
|
||||
// set interact_as_actor_id value
|
||||
$userPodcasts = $user->podcasts;
|
||||
if ($userPodcasts = $user->podcasts) {
|
||||
set_interact_as_actor($userPodcasts[0]->actor_id);
|
||||
}
|
||||
});
|
||||
|
||||
Events::on('logout', static function (User $user): void {
|
||||
helper('auth');
|
||||
// remove user's interact_as_actor session
|
||||
remove_interact_as_actor();
|
||||
});
|
||||
|
||||
/*
|
||||
* --------------------------------------------------------------------
|
||||
* Fediverse events
|
||||
|
|
|
@ -15,8 +15,6 @@ use Modules\Auth\Filters\PermissionFilter;
|
|||
use Modules\Fediverse\Filters\AllowCorsFilter;
|
||||
use Modules\Fediverse\Filters\FediverseFilter;
|
||||
use Modules\PremiumPodcasts\Filters\PodcastUnlockFilter;
|
||||
use Myth\Auth\Filters\LoginFilter;
|
||||
use Myth\Auth\Filters\RoleFilter;
|
||||
|
||||
class Filters extends BaseConfig
|
||||
{
|
||||
|
@ -31,8 +29,6 @@ class Filters extends BaseConfig
|
|||
'honeypot' => Honeypot::class,
|
||||
'invalidchars' => InvalidChars::class,
|
||||
'secureheaders' => SecureHeaders::class,
|
||||
'login' => LoginFilter::class,
|
||||
'role' => RoleFilter::class,
|
||||
'permission' => PermissionFilter::class,
|
||||
'fediverse' => FediverseFilter::class,
|
||||
'allow-cors' => AllowCorsFilter::class,
|
||||
|
@ -86,7 +82,7 @@ class Filters extends BaseConfig
|
|||
parent::__construct();
|
||||
|
||||
$this->filters = [
|
||||
'login' => [
|
||||
'session' => [
|
||||
'before' => [config('Admin')->gateway . '*', config('Analytics')->gateway . '*'],
|
||||
],
|
||||
'podcast-unlock' => [
|
||||
|
|
|
@ -214,7 +214,7 @@ $routes->get('/pages/(:slug)', 'PageController/$1', [
|
|||
$routes->group('@(:podcastHandle)', static function ($routes): void {
|
||||
$routes->post('posts/new', 'PostController::attemptCreate/$1', [
|
||||
'as' => 'post-attempt-create',
|
||||
'filter' => 'permission:podcast-manage_publications',
|
||||
'filter' => 'permission:podcast#.manage-publications',
|
||||
]);
|
||||
// Post
|
||||
$routes->group('posts/(:uuid)', static function ($routes): void {
|
||||
|
@ -251,14 +251,14 @@ $routes->group('@(:podcastHandle)', static function ($routes): void {
|
|||
// Actions
|
||||
$routes->post('action', 'PostController::attemptAction/$1/$2', [
|
||||
'as' => 'post-attempt-action',
|
||||
'filter' => 'permission:podcast-interact_as',
|
||||
'filter' => 'permission:podcast#.interact-as',
|
||||
]);
|
||||
$routes->post(
|
||||
'block-actor',
|
||||
'PostController::attemptBlockActor/$1/$2',
|
||||
[
|
||||
'as' => 'post-attempt-block-actor',
|
||||
'filter' => 'permission:fediverse-block_actors',
|
||||
'filter' => 'permission:fediverse.manage-blocks',
|
||||
],
|
||||
);
|
||||
$routes->post(
|
||||
|
@ -266,12 +266,12 @@ $routes->group('@(:podcastHandle)', static function ($routes): void {
|
|||
'PostController::attemptBlockDomain/$1/$2',
|
||||
[
|
||||
'as' => 'post-attempt-block-domain',
|
||||
'filter' => 'permission:fediverse-block_domains',
|
||||
'filter' => 'permission:fediverse.manage-blocks',
|
||||
],
|
||||
);
|
||||
$routes->post('delete', 'PostController::attemptDelete/$1/$2', [
|
||||
'as' => 'post-attempt-delete',
|
||||
'filter' => 'permission:podcast-manage_publications',
|
||||
'filter' => 'permission:podcast#.manage-publications',
|
||||
]);
|
||||
$routes->get(
|
||||
'remote/(:postAction)',
|
||||
|
|
|
@ -17,7 +17,7 @@ class Security extends BaseConfig
|
|||
*
|
||||
* @var 'cookie'|'session'
|
||||
*/
|
||||
public string $csrfProtection = 'cookie';
|
||||
public string $csrfProtection = 'session';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
|
|
|
@ -11,7 +11,6 @@ use CodeIgniter\Validation\CreditCardRules;
|
|||
use CodeIgniter\Validation\FileRules;
|
||||
use CodeIgniter\Validation\FormatRules;
|
||||
use CodeIgniter\Validation\Rules;
|
||||
use Myth\Auth\Authentication\Passwords\ValidationRules as PasswordRules;
|
||||
|
||||
class Validation extends BaseConfig
|
||||
{
|
||||
|
@ -27,7 +26,6 @@ class Validation extends BaseConfig
|
|||
CreditCardRules::class,
|
||||
AppRules::class,
|
||||
AppFileRules::class,
|
||||
PasswordRules::class,
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
@ -20,12 +20,12 @@ class ActorController extends FediverseActorController
|
|||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $helpers = ['auth', 'svg', 'components', 'misc', 'seo'];
|
||||
protected $helpers = ['svg', 'components', 'misc', 'seo'];
|
||||
|
||||
public function follow(): string
|
||||
{
|
||||
// Prevent analytics hit when authenticated
|
||||
if (! can_user_interact()) {
|
||||
if (! auth()->loggedIn()) {
|
||||
// @phpstan-ignore-next-line
|
||||
$this->registerPodcastWebpageHit($this->actor->podcast->id);
|
||||
}
|
||||
|
|
|
@ -34,5 +34,7 @@ abstract class BaseController extends Controller
|
|||
parent::initController($request, $response, $logger);
|
||||
|
||||
Theme::setTheme('app');
|
||||
|
||||
$this->helpers = array_merge($this->helpers, ['setting']);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ class CreditsController extends BaseController
|
|||
|
||||
$cacheName = implode(
|
||||
'_',
|
||||
array_filter(['page', 'credits', $locale, can_user_interact() ? 'authenticated' : null]),
|
||||
array_filter(['page', 'credits', $locale, auth()->loggedIn() ? 'authenticated' : null]),
|
||||
);
|
||||
|
||||
if (! ($found = cache($cacheName))) {
|
||||
|
|
|
@ -79,7 +79,7 @@ class EpisodeCommentController extends BaseController
|
|||
public function view(): string
|
||||
{
|
||||
// Prevent analytics hit when authenticated
|
||||
if (! can_user_interact()) {
|
||||
if (! auth()->loggedIn()) {
|
||||
$this->registerPodcastWebpageHit($this->podcast->id);
|
||||
}
|
||||
|
||||
|
@ -91,7 +91,8 @@ class EpisodeCommentController extends BaseController
|
|||
"comment#{$this->comment->id}",
|
||||
service('request')
|
||||
->getLocale(),
|
||||
can_user_interact() ? 'authenticated' : null,
|
||||
auth()
|
||||
->loggedIn() ? 'authenticated' : null,
|
||||
]),
|
||||
);
|
||||
|
||||
|
@ -105,7 +106,7 @@ class EpisodeCommentController extends BaseController
|
|||
];
|
||||
|
||||
// if user is logged in then send to the authenticated activity view
|
||||
if (can_user_interact()) {
|
||||
if (auth()->loggedIn()) {
|
||||
helper('form');
|
||||
return view('episode/comment', $data);
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ class EpisodeController extends BaseController
|
|||
public function index(): string
|
||||
{
|
||||
// Prevent analytics hit when authenticated
|
||||
if (! can_user_interact()) {
|
||||
if (! auth()->loggedIn()) {
|
||||
$this->registerPodcastWebpageHit($this->episode->podcast_id);
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,8 @@ class EpisodeController extends BaseController
|
|||
service('request')
|
||||
->getLocale(),
|
||||
is_unlocked($this->podcast->handle) ? 'unlocked' : null,
|
||||
can_user_interact() ? 'authenticated' : null,
|
||||
auth()
|
||||
->loggedIn() ? 'authenticated' : null,
|
||||
]),
|
||||
);
|
||||
|
||||
|
@ -94,7 +95,7 @@ class EpisodeController extends BaseController
|
|||
$this->podcast->id,
|
||||
);
|
||||
|
||||
if (can_user_interact()) {
|
||||
if (auth()->loggedIn()) {
|
||||
helper('form');
|
||||
|
||||
return view('episode/comments', $data);
|
||||
|
@ -115,7 +116,7 @@ class EpisodeController extends BaseController
|
|||
public function activity(): string
|
||||
{
|
||||
// Prevent analytics hit when authenticated
|
||||
if (! can_user_interact()) {
|
||||
if (! auth()->loggedIn()) {
|
||||
$this->registerPodcastWebpageHit($this->episode->podcast_id);
|
||||
}
|
||||
|
||||
|
@ -129,7 +130,8 @@ class EpisodeController extends BaseController
|
|||
service('request')
|
||||
->getLocale(),
|
||||
is_unlocked($this->podcast->handle) ? 'unlocked' : null,
|
||||
can_user_interact() ? 'authenticated' : null,
|
||||
auth()
|
||||
->loggedIn() ? 'authenticated' : null,
|
||||
]),
|
||||
);
|
||||
|
||||
|
@ -144,7 +146,7 @@ class EpisodeController extends BaseController
|
|||
$this->podcast->id,
|
||||
);
|
||||
|
||||
if (can_user_interact()) {
|
||||
if (auth()->loggedIn()) {
|
||||
helper('form');
|
||||
|
||||
return view('episode/activity', $data);
|
||||
|
@ -167,7 +169,7 @@ class EpisodeController extends BaseController
|
|||
header('Content-Security-Policy: frame-ancestors http://*:* https://*:*');
|
||||
|
||||
// Prevent analytics hit when authenticated
|
||||
if (! can_user_interact()) {
|
||||
if (! auth()->loggedIn()) {
|
||||
$this->registerPodcastWebpageHit($this->episode->podcast_id);
|
||||
}
|
||||
|
||||
|
|
|
@ -13,13 +13,20 @@ namespace App\Controllers;
|
|||
use App\Models\PodcastModel;
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use Config\Services;
|
||||
use Exception;
|
||||
|
||||
class HomeController extends BaseController
|
||||
{
|
||||
public function index(): RedirectResponse | string
|
||||
{
|
||||
$db = db_connect();
|
||||
if ($db->getDatabase() === '' || ! $db->tableExists('podcasts')) {
|
||||
$sortOptions = ['activity', 'created_desc', 'created_asc'];
|
||||
$sortBy = in_array($this->request->getGet('sort'), $sortOptions, true) ? $this->request->getGet(
|
||||
'sort'
|
||||
) : 'activity';
|
||||
|
||||
try {
|
||||
$allPodcasts = (new PodcastModel())->getAllPodcasts($sortBy);
|
||||
} catch (Exception) {
|
||||
// Database connection has not been set or could not find the podcasts table
|
||||
// Redirecting to install page because it is likely that Castopod has not been installed yet.
|
||||
// NB: as base_url wouldn't have been defined here, redirect to install wizard manually
|
||||
|
@ -27,13 +34,6 @@ class HomeController extends BaseController
|
|||
return redirect()->to(rtrim(host_url(), '/') . $route);
|
||||
}
|
||||
|
||||
$sortOptions = ['activity', 'created_desc', 'created_asc'];
|
||||
$sortBy = in_array($this->request->getGet('sort'), $sortOptions, true) ? $this->request->getGet(
|
||||
'sort'
|
||||
) : 'activity';
|
||||
|
||||
$allPodcasts = (new PodcastModel())->getAllPodcasts($sortBy);
|
||||
|
||||
// check if there's only one podcast to redirect user to it
|
||||
if (count($allPodcasts) === 1) {
|
||||
return redirect()->route('podcast-activity', [$allPodcasts[0]->handle]);
|
||||
|
|
|
@ -24,7 +24,8 @@ class MapController extends BaseController
|
|||
'map',
|
||||
service('request')
|
||||
->getLocale(),
|
||||
can_user_interact() ? 'authenticated' : null,
|
||||
auth()
|
||||
->loggedIn() ? 'authenticated' : null,
|
||||
]),
|
||||
);
|
||||
|
||||
|
|
|
@ -44,7 +44,8 @@ class PageController extends BaseController
|
|||
$this->page->slug,
|
||||
service('request')
|
||||
->getLocale(),
|
||||
can_user_interact() ? 'authenticated' : null,
|
||||
auth()
|
||||
->loggedIn() ? 'authenticated' : null,
|
||||
]),
|
||||
);
|
||||
|
||||
|
|
|
@ -62,7 +62,7 @@ class PodcastController extends BaseController
|
|||
public function activity(): string
|
||||
{
|
||||
// Prevent analytics hit when authenticated
|
||||
if (! can_user_interact()) {
|
||||
if (! auth()->loggedIn()) {
|
||||
$this->registerPodcastWebpageHit($this->podcast->id);
|
||||
}
|
||||
|
||||
|
@ -75,7 +75,8 @@ class PodcastController extends BaseController
|
|||
service('request')
|
||||
->getLocale(),
|
||||
is_unlocked($this->podcast->handle) ? 'unlocked' : null,
|
||||
can_user_interact() ? 'authenticated' : null,
|
||||
auth()
|
||||
->loggedIn() ? 'authenticated' : null,
|
||||
]),
|
||||
);
|
||||
|
||||
|
@ -87,7 +88,7 @@ class PodcastController extends BaseController
|
|||
];
|
||||
|
||||
// if user is logged in then send to the authenticated activity view
|
||||
if (can_user_interact()) {
|
||||
if (auth()->loggedIn()) {
|
||||
helper('form');
|
||||
|
||||
return view('podcast/activity', $data);
|
||||
|
@ -111,7 +112,7 @@ class PodcastController extends BaseController
|
|||
public function about(): string
|
||||
{
|
||||
// Prevent analytics hit when authenticated
|
||||
if (! can_user_interact()) {
|
||||
if (! auth()->loggedIn()) {
|
||||
$this->registerPodcastWebpageHit($this->podcast->id);
|
||||
}
|
||||
|
||||
|
@ -124,7 +125,8 @@ class PodcastController extends BaseController
|
|||
service('request')
|
||||
->getLocale(),
|
||||
is_unlocked($this->podcast->handle) ? 'unlocked' : null,
|
||||
can_user_interact() ? 'authenticated' : null,
|
||||
auth()
|
||||
->loggedIn() ? 'authenticated' : null,
|
||||
]),
|
||||
);
|
||||
|
||||
|
@ -138,7 +140,7 @@ class PodcastController extends BaseController
|
|||
];
|
||||
|
||||
// // if user is logged in then send to the authenticated activity view
|
||||
if (can_user_interact()) {
|
||||
if (auth()->loggedIn()) {
|
||||
helper('form');
|
||||
|
||||
return view('podcast/about', $data);
|
||||
|
@ -162,7 +164,7 @@ class PodcastController extends BaseController
|
|||
public function episodes(): string
|
||||
{
|
||||
// Prevent analytics hit when authenticated
|
||||
if (! can_user_interact()) {
|
||||
if (! auth()->loggedIn()) {
|
||||
$this->registerPodcastWebpageHit($this->podcast->id);
|
||||
}
|
||||
|
||||
|
@ -191,7 +193,8 @@ class PodcastController extends BaseController
|
|||
service('request')
|
||||
->getLocale(),
|
||||
is_unlocked($this->podcast->handle) ? 'unlocked' : null,
|
||||
can_user_interact() ? 'authenticated' : null,
|
||||
auth()
|
||||
->loggedIn() ? 'authenticated' : null,
|
||||
]),
|
||||
);
|
||||
|
||||
|
@ -264,7 +267,7 @@ class PodcastController extends BaseController
|
|||
),
|
||||
];
|
||||
|
||||
if (can_user_interact()) {
|
||||
if (auth()->loggedIn()) {
|
||||
return view('podcast/episodes', $data);
|
||||
}
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ class PostController extends FediversePostController
|
|||
public function view(): string
|
||||
{
|
||||
// Prevent analytics hit when authenticated
|
||||
if (! can_user_interact()) {
|
||||
if (! auth()->loggedIn()) {
|
||||
$this->registerPodcastWebpageHit($this->podcast->id);
|
||||
}
|
||||
|
||||
|
@ -85,7 +85,8 @@ class PostController extends FediversePostController
|
|||
"post#{$this->post->id}",
|
||||
service('request')
|
||||
->getLocale(),
|
||||
can_user_interact() ? 'authenticated' : null,
|
||||
auth()
|
||||
->loggedIn() ? 'authenticated' : null,
|
||||
]),
|
||||
);
|
||||
|
||||
|
@ -97,7 +98,7 @@ class PostController extends FediversePostController
|
|||
];
|
||||
|
||||
// if user is logged in then send to the authenticated activity view
|
||||
if (can_user_interact()) {
|
||||
if (auth()->loggedIn()) {
|
||||
helper('form');
|
||||
return view('post/post', $data);
|
||||
}
|
||||
|
@ -239,7 +240,7 @@ class PostController extends FediversePostController
|
|||
public function remoteAction(string $action): string
|
||||
{
|
||||
// Prevent analytics hit when authenticated
|
||||
if (! can_user_interact()) {
|
||||
if (! auth()->loggedIn()) {
|
||||
$this->registerPodcastWebpageHit($this->podcast->id);
|
||||
}
|
||||
|
||||
|
|
|
@ -55,10 +55,12 @@ class AddMedia extends Migration
|
|||
],
|
||||
'uploaded_by' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
'unsigned' => true,
|
||||
],
|
||||
'updated_by' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
'unsigned' => true,
|
||||
],
|
||||
'uploaded_at' => [
|
|
@ -18,7 +18,6 @@ class AppSeeder extends Seeder
|
|||
{
|
||||
public function run(): void
|
||||
{
|
||||
$this->call('AuthSeeder');
|
||||
$this->call('CategorySeeder');
|
||||
$this->call('LanguageSeeder');
|
||||
$this->call('PlatformSeeder');
|
||||
|
|
|
@ -1,328 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class PermissionSeeder Inserts permissions
|
||||
*
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Database\Seeds;
|
||||
|
||||
use CodeIgniter\Database\Seeder;
|
||||
|
||||
class AuthSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* @var array<string, string>[]
|
||||
*/
|
||||
protected array $groups = [
|
||||
[
|
||||
'name' => 'superadmin',
|
||||
'description' =>
|
||||
'Somebody who has access to all the castopod instance features',
|
||||
],
|
||||
[
|
||||
'name' => 'podcast_admin',
|
||||
'description' =>
|
||||
'Somebody who has access to all the features within a given podcast',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Build permissions array as a list of:
|
||||
*
|
||||
* ``` context => [ [action, description], [action, description], ... ] ```
|
||||
*
|
||||
* @var array<string, array<string, string|string[]>[]>
|
||||
*/
|
||||
protected array $permissions = [
|
||||
'settings' => [
|
||||
[
|
||||
'name' => 'view',
|
||||
'description' => 'View settings options',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'manage',
|
||||
'description' => 'Update general settings',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
],
|
||||
'users' => [
|
||||
[
|
||||
'name' => 'create',
|
||||
'description' => 'Create a user',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'list',
|
||||
'description' => 'List all users',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'view',
|
||||
'description' => 'View any user info',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'manage_authorizations',
|
||||
'description' => 'Add or remove roles/permissions to a user',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'manage_bans',
|
||||
'description' => 'Ban / unban a user',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'force_pass_reset',
|
||||
'description' =>
|
||||
'Force a user to update his password upon next login',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'delete',
|
||||
'description' =>
|
||||
'Delete user without removing him from database',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'delete_permanently',
|
||||
'description' =>
|
||||
'Delete all occurrences of a user from the database',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
],
|
||||
'pages' => [
|
||||
[
|
||||
'name' => 'manage',
|
||||
'description' => 'List / create / edit / delete pages',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
],
|
||||
'podcasts' => [
|
||||
[
|
||||
'name' => 'create',
|
||||
'description' => 'Add a new podcast',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'import',
|
||||
'description' => 'Import a new podcast from an external feed',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'list',
|
||||
'description' => 'List all podcasts and their episodes',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'view',
|
||||
'description' => 'View any podcast and their contributors list',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'delete',
|
||||
'description' => 'Delete any podcast from the database',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
],
|
||||
'episodes' => [
|
||||
[
|
||||
'name' => 'list',
|
||||
'description' => 'List all episodes of any podcast',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'view',
|
||||
'description' => 'View any episode of any podcast',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
],
|
||||
'podcast' => [
|
||||
[
|
||||
'name' => 'view',
|
||||
'description' => 'View a podcast',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
[
|
||||
'name' => 'edit',
|
||||
'description' => 'Edit a podcast',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
[
|
||||
'name' => 'manage_subscriptions',
|
||||
'description' =>
|
||||
'Add / edit / remove podcast subscriptions',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
[
|
||||
'name' => 'manage_contributors',
|
||||
'description' =>
|
||||
'Add / remove contributors to a podcast and edit their roles',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
[
|
||||
'name' => 'manage_platforms',
|
||||
'description' => 'Set / remove platform links of a podcast',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
[
|
||||
'name' => 'manage_publications',
|
||||
'description' =>
|
||||
'Publish a podcast and publish / unpublish its episodes & posts',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
[
|
||||
'name' => 'interact_as',
|
||||
'description' =>
|
||||
'Interact as the podcast to favourite / share or reply to posts.',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
],
|
||||
'podcast_episodes' => [
|
||||
[
|
||||
'name' => 'list',
|
||||
'description' => 'List all episodes of a podcast',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
[
|
||||
'name' => 'view',
|
||||
'description' => 'View any episode of a podcast',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
[
|
||||
'name' => 'create',
|
||||
'description' => 'Add new episodes for a podcast',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
[
|
||||
'name' => 'edit',
|
||||
'description' => 'Edit an episode of a podcast',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
[
|
||||
'name' => 'delete',
|
||||
'description' =>
|
||||
'Delete all occurrences of an episode of a podcast from the database',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
],
|
||||
'person' => [
|
||||
[
|
||||
'name' => 'create',
|
||||
'description' => 'Add a new person',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'list',
|
||||
'description' => 'List all persons',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'view',
|
||||
'description' => 'View any person',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'edit',
|
||||
'description' => 'Edit a person',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'delete',
|
||||
'description' =>
|
||||
'Delete permanently any person from the database',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
],
|
||||
'fediverse' => [
|
||||
[
|
||||
'name' => 'block_actors',
|
||||
'description' =>
|
||||
'Block fediverse actors from interacting with the instance.',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'block_domains',
|
||||
'description' =>
|
||||
'Block fediverse domains from interacting with the instance.',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$groupId = 0;
|
||||
$dataGroups = [];
|
||||
foreach ($this->groups as $group) {
|
||||
$dataGroups[] = [
|
||||
'id' => ++$groupId,
|
||||
'name' => $group['name'],
|
||||
'description' => $group['description'],
|
||||
];
|
||||
}
|
||||
|
||||
// Map permissions to a format the `auth_permissions` table expects
|
||||
$dataPermissions = [];
|
||||
$dataGroupsPermissions = [];
|
||||
$permissionId = 0;
|
||||
foreach ($this->permissions as $context => $actions) {
|
||||
foreach ($actions as $action) {
|
||||
$dataPermissions[] = [
|
||||
'id' => ++$permissionId,
|
||||
'name' => $context . '-' . $action['name'],
|
||||
'description' => $action['description'],
|
||||
];
|
||||
|
||||
foreach ($action['has_permission'] as $role) {
|
||||
// link permission to specified groups
|
||||
$dataGroupsPermissions[] = [
|
||||
'group_id' => $this->getGroupIdByName($role, $dataGroups),
|
||||
'permission_id' => $permissionId,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->db->table('auth_groups')->countAll() < count($dataPermissions)) {
|
||||
$this->db
|
||||
->table('auth_permissions')
|
||||
->ignore(true)
|
||||
->insertBatch($dataPermissions);
|
||||
}
|
||||
|
||||
if ($this->db->table('auth_groups')->countAll() < count($dataGroups)) {
|
||||
$this->db
|
||||
->table('auth_groups')
|
||||
->ignore(true)
|
||||
->insertBatch($dataGroups);
|
||||
}
|
||||
|
||||
if ($this->db->table('auth_groups_permissions')->countAll() < count($dataGroupsPermissions)) {
|
||||
$this->db
|
||||
->table('auth_groups_permissions')
|
||||
->ignore(true)
|
||||
->insertBatch($dataGroupsPermissions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string|int>[] $dataGroups
|
||||
*/
|
||||
public static function getGroupIdByName(string $name, array $dataGroups): ?int
|
||||
{
|
||||
foreach ($dataGroups as $group) {
|
||||
if ($group['name'] === $name) {
|
||||
return $group['id'];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -18,24 +18,32 @@ class TestSeeder extends Seeder
|
|||
{
|
||||
public function run(): void
|
||||
{
|
||||
helper('setting');
|
||||
|
||||
/**
|
||||
* Inserts an active user with the following credentials: username: admin password: AGUehL3P
|
||||
* Inserts an owner with the following credentials: admin: `admin@example.com` password: `AGUehL3P`
|
||||
*/
|
||||
$this->db->table('users')
|
||||
->insert([
|
||||
'id' => 1,
|
||||
'username' => 'admin',
|
||||
'email' => 'admin@example.com',
|
||||
'password_hash' =>
|
||||
'$2y$10$TXJEHX/djW8jtzgpDVf7dOOCGo5rv1uqtAYWdwwwkttQcDkAeB2.6',
|
||||
'active' => 1,
|
||||
'is_owner' => 1,
|
||||
]);
|
||||
|
||||
$this->db->table('auth_identities')
|
||||
->insert([
|
||||
'id' => 1,
|
||||
'user_id' => 1,
|
||||
'type' => 'email_password',
|
||||
'secret' => 'admin@example.com',
|
||||
'secret2' => '$2y$10$TXJEHX/djW8jtzgpDVf7dOOCGo5rv1uqtAYWdwwwkttQcDkAeB2.6',
|
||||
]);
|
||||
|
||||
$this->db
|
||||
->table('auth_groups_users')
|
||||
->insert([
|
||||
'group_id' => 1,
|
||||
'user_id' => 1,
|
||||
'group' => setting('AuthGroups.mostPowerfulGroup'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,11 +17,11 @@ use App\Entities\Podcast;
|
|||
use App\Models\EpisodeModel;
|
||||
use App\Models\MediaModel;
|
||||
use App\Models\PodcastModel;
|
||||
use App\Models\UserModel;
|
||||
use CodeIgniter\Entity\Entity;
|
||||
use CodeIgniter\Files\File;
|
||||
use CodeIgniter\I18n\Time;
|
||||
use Modules\Auth\Entities\User;
|
||||
use CodeIgniter\Shield\Entities\User;
|
||||
use Modules\Auth\Models\UserModel;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
|
|
|
@ -18,18 +18,18 @@ use App\Models\EpisodeModel;
|
|||
use App\Models\MediaModel;
|
||||
use App\Models\PersonModel;
|
||||
use App\Models\PlatformModel;
|
||||
use App\Models\UserModel;
|
||||
use CodeIgniter\Entity\Entity;
|
||||
use CodeIgniter\Files\File;
|
||||
use CodeIgniter\HTTP\Files\UploadedFile;
|
||||
use CodeIgniter\I18n\Time;
|
||||
use CodeIgniter\Shield\Entities\User;
|
||||
use League\CommonMark\Environment\Environment;
|
||||
use League\CommonMark\Extension\Autolink\AutolinkExtension;
|
||||
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
|
||||
use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
|
||||
use League\CommonMark\Extension\SmartPunct\SmartPunctExtension;
|
||||
use League\CommonMark\MarkdownConverter;
|
||||
use Modules\Auth\Entities\User;
|
||||
use Modules\Auth\Models\UserModel;
|
||||
use Modules\PremiumPodcasts\Entities\Subscription;
|
||||
use Modules\PremiumPodcasts\Models\SubscriptionModel;
|
||||
use RuntimeException;
|
||||
|
@ -100,6 +100,8 @@ class Podcast extends Entity
|
|||
{
|
||||
protected string $link;
|
||||
|
||||
protected string $at_handle;
|
||||
|
||||
protected ?Actor $actor = null;
|
||||
|
||||
protected ?Image $cover = null;
|
||||
|
@ -208,6 +210,11 @@ class Podcast extends Entity
|
|||
'updated_by' => 'integer',
|
||||
];
|
||||
|
||||
public function getAtHandle(): string
|
||||
{
|
||||
return '@' . $this->handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @noRector ReturnTypeDeclarationRector
|
||||
*/
|
||||
|
|
|
@ -1,89 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
use App\Models\ActorModel;
|
||||
use Modules\Auth\Entities\User;
|
||||
use Modules\Fediverse\Entities\Actor;
|
||||
|
||||
if (! function_exists('user')) {
|
||||
/**
|
||||
* Returns the User instance for the current logged in user.
|
||||
*/
|
||||
function user(): ?User
|
||||
{
|
||||
$authenticate = service('authentication');
|
||||
$authenticate->check();
|
||||
return $authenticate->user();
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('set_interact_as_actor')) {
|
||||
/**
|
||||
* Sets the actor id of which the user is acting as
|
||||
*/
|
||||
function set_interact_as_actor(int $actorId): void
|
||||
{
|
||||
$authenticate = service('authentication');
|
||||
$authenticate->check();
|
||||
|
||||
$session = session();
|
||||
$session->set('interact_as_actor_id', $actorId);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('remove_interact_as_actor')) {
|
||||
/**
|
||||
* Removes the actor id of which the user is acting as
|
||||
*/
|
||||
function remove_interact_as_actor(): void
|
||||
{
|
||||
$session = session();
|
||||
$session->remove('interact_as_actor_id');
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('interact_as_actor_id')) {
|
||||
/**
|
||||
* Sets the podcast id of which the user is acting as
|
||||
*/
|
||||
function interact_as_actor_id(): int
|
||||
{
|
||||
$authenticate = service('authentication');
|
||||
$authenticate->check();
|
||||
|
||||
$session = session();
|
||||
return $session->get('interact_as_actor_id');
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('interact_as_actor')) {
|
||||
/**
|
||||
* Get the actor the user is currently interacting as
|
||||
*/
|
||||
function interact_as_actor(): Actor | false
|
||||
{
|
||||
$authenticate = service('authentication');
|
||||
$authenticate->check();
|
||||
|
||||
$session = session();
|
||||
if ($session->has('interact_as_actor_id')) {
|
||||
return model(ActorModel::class, false)->getActorById($session->get('interact_as_actor_id'));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('can_user_interact')) {
|
||||
function can_user_interact(): bool
|
||||
{
|
||||
return (bool) interact_as_actor();
|
||||
}
|
||||
}
|
|
@ -11,7 +11,6 @@ declare(strict_types=1);
|
|||
namespace App\Models;
|
||||
|
||||
use App\Entities\Podcast;
|
||||
use CodeIgniter\Database\Query;
|
||||
use CodeIgniter\HTTP\URI;
|
||||
use CodeIgniter\Model;
|
||||
use phpseclib\Crypt\RSA;
|
||||
|
@ -205,15 +204,14 @@ class PodcastModel extends Model
|
|||
/**
|
||||
* Gets all the podcasts a given user is contributing to
|
||||
*
|
||||
* @param string[] $userPodcastIds
|
||||
* @return Podcast[] podcasts
|
||||
*/
|
||||
public function getUserPodcasts(int $userId): array
|
||||
public function getUserPodcasts(int $userId, array $userPodcastIds): array
|
||||
{
|
||||
$cacheName = "user{$userId}_podcasts";
|
||||
if (! ($found = cache($cacheName))) {
|
||||
$found = $this->select('podcasts.*')
|
||||
->join('podcasts_users', 'podcasts_users.podcast_id = podcasts.id')
|
||||
->where('podcasts_users.user_id', $userId)
|
||||
$found = $userPodcastIds === [] ? [] : $this->whereIn('id', $userPodcastIds)
|
||||
->findAll();
|
||||
|
||||
cache()
|
||||
|
@ -223,76 +221,18 @@ class PodcastModel extends Model
|
|||
return $found;
|
||||
}
|
||||
|
||||
public function addPodcastContributor(int $userId, int $podcastId, int $groupId): Query | bool
|
||||
public function getContributorGroup(int $userId, int $podcastId): int | false
|
||||
{
|
||||
cache()->delete("podcast#{$podcastId}_contributors");
|
||||
|
||||
$data = [
|
||||
'user_id' => $userId,
|
||||
'podcast_id' => $podcastId,
|
||||
'group_id' => $groupId,
|
||||
];
|
||||
|
||||
return $this->db->table('podcasts_users')
|
||||
->insert($data);
|
||||
}
|
||||
|
||||
public function updatePodcastContributor(int $userId, int $podcastId, int $groupId): bool
|
||||
{
|
||||
cache()->delete("podcast#{$podcastId}_contributors");
|
||||
|
||||
return $this->db
|
||||
->table('podcasts_users')
|
||||
->where([
|
||||
'user_id' => $userId,
|
||||
'podcast_id' => $podcastId,
|
||||
])
|
||||
->update([
|
||||
'group_id' => $groupId,
|
||||
]);
|
||||
}
|
||||
|
||||
public function removePodcastContributor(int $userId, int $podcastId): string | bool
|
||||
{
|
||||
cache()->delete("podcast#{$podcastId}_contributors");
|
||||
|
||||
return $this->db
|
||||
->table('podcasts_users')
|
||||
->where([
|
||||
'user_id' => $userId,
|
||||
'podcast_id' => $podcastId,
|
||||
])
|
||||
->delete();
|
||||
}
|
||||
|
||||
public function getContributorGroupId(int $userId, int | string $podcastId): int | false
|
||||
{
|
||||
if (! is_numeric($podcastId)) {
|
||||
// identifier is the podcast name, request must be a join
|
||||
$userPodcast = $this->db
|
||||
->table('podcasts_users')
|
||||
->select('group_id, user_id')
|
||||
->join('podcasts', 'podcasts.id = podcasts_users.podcast_id')
|
||||
->where([
|
||||
'user_id' => $userId,
|
||||
'handle' => $podcastId,
|
||||
])
|
||||
->get()
|
||||
->getResultObject();
|
||||
} else {
|
||||
$userPodcast = $this->db
|
||||
->table('podcasts_users')
|
||||
->select('group_id')
|
||||
->where([
|
||||
'user_id' => $userId,
|
||||
'podcast_id' => $podcastId,
|
||||
])
|
||||
->get()
|
||||
->getResultObject();
|
||||
}
|
||||
$userPodcast = $this->db
|
||||
->table('auth_groups_users')
|
||||
->select('user_id, group')
|
||||
->where('user_id', $userId)
|
||||
->like('group', "podcast#{$podcastId}")
|
||||
->get()
|
||||
->getResultObject();
|
||||
|
||||
return $userPodcast !== []
|
||||
? (int) $userPodcast[0]->group_id
|
||||
? (int) $userPodcast[0]->group
|
||||
: false;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Modules\Auth\Entities\User;
|
||||
use Myth\Auth\Models\UserModel as MythAuthUserModel;
|
||||
|
||||
class UserModel extends MythAuthUserModel
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $returnType = User::class;
|
||||
|
||||
/**
|
||||
* @return User[]
|
||||
*/
|
||||
public function getPodcastContributors(int $podcastId): array
|
||||
{
|
||||
$cacheName = "podcast#{$podcastId}_contributors";
|
||||
if (! ($found = cache($cacheName))) {
|
||||
$found = $this->select('users.*, auth_groups.name as podcast_role')
|
||||
->join('podcasts_users', 'podcasts_users.user_id = users.id')
|
||||
->join('auth_groups', 'auth_groups.id = podcasts_users.group_id')
|
||||
->where('podcasts_users.podcast_id', $podcastId)
|
||||
->findAll();
|
||||
|
||||
cache()
|
||||
->save($cacheName, $found, DECADE);
|
||||
}
|
||||
|
||||
return $found;
|
||||
}
|
||||
|
||||
public function getPodcastContributor(int $userId, int $podcastId): ?User
|
||||
{
|
||||
// @phpstan-ignore-next-line
|
||||
return $this->select('users.*, podcasts_users.podcast_id as podcast_id, auth_groups.name as podcast_role')
|
||||
->join('podcasts_users', 'podcasts_users.user_id = users.id')
|
||||
->join('auth_groups', 'auth_groups.id = podcasts_users.group_id')
|
||||
->where([
|
||||
'users.id' => $userId,
|
||||
'podcast_id' => $podcastId,
|
||||
])
|
||||
->first();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path fill="none" d="M0 0h24v24H0z"/>
|
||||
<path d="M3.783 2.826L12 1l8.217 1.826a1 1 0 0 1 .783.976v9.987a6 6 0 0 1-2.672 4.992L12 23l-6.328-4.219A6 6 0 0 1 3 13.79V3.802a1 1 0 0 1 .783-.976zM12 11a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5zm-4.473 5h8.946a4.5 4.5 0 0 0-8.946 0z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 347 B |
|
@ -0,0 +1,29 @@
|
|||
<?= helper(['components', 'svg']) ?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
|
||||
<title>403 Forbidden</title>
|
||||
<link rel='stylesheet' type='text/css' href='<?= route_to('themes-colors-css') ?>' />
|
||||
<?= service('vite')->asset('styles/index.css', 'css') ?>
|
||||
</head>
|
||||
|
||||
<body class="flex flex-col items-center justify-center min-h-screen px-2 text-center bg-base theme-<?= service('settings')
|
||||
->get('App.theme') ?>">
|
||||
<?= svg('castopod-mascot_confused', 'h-64') ?>
|
||||
<h1 class="mt-4 text-3xl font-bold font-display md:text-4xl lg:text-5xl">403 - Forbidden</h1>
|
||||
|
||||
<p class="mb-6 text-lg text-skin-muted md:text-xl lg:text-2xl">
|
||||
<?php if (isset($message) && $message !== '(null)'): ?>
|
||||
<?= esc($message) ?>
|
||||
<?php else: ?>
|
||||
You do not have sufficient permissions to access that page.
|
||||
<?php endif; ?>
|
||||
</p>
|
||||
<a href="<?= previous_url() ?>" class="inline-flex items-center justify-center px-3 py-1 text-sm font-semibold rounded-full shadow-xs text-accent-contrast focus:ring-accent md:px-4 md:py-2 md:text-base bg-accent-base hover:bg-accent-hover"><?= lang('Common.go_back') ?></a>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -14,7 +14,7 @@
|
|||
<body class="flex flex-col items-center justify-center min-h-screen px-2 text-center bg-base theme-<?= service('settings')
|
||||
->get('App.theme') ?>">
|
||||
<?= svg('castopod-mascot_confused', 'h-64') ?>
|
||||
<h1 class="text-3xl font-bold font-display md:text-4xl lg:text-5xl">404 - File Not Found</h1>
|
||||
<h1 class="mt-4 text-3xl font-bold font-display md:text-4xl lg:text-5xl">404 - File Not Found</h1>
|
||||
|
||||
<p class="mb-6 text-lg text-skin-muted md:text-xl lg:text-2xl">
|
||||
<?php if (isset($message) && $message !== '(null)'): ?>
|
||||
|
|
|
@ -10,14 +10,14 @@
|
|||
<title>Whoops!</title>
|
||||
<link rel='stylesheet' type='text/css' href='<?= route_to('themes-colors-css') ?>' />
|
||||
<?= service('vite')->asset('styles/index.css', 'css') ?>
|
||||
<?php if (service('authentication')->isLoggedIn()): ?>
|
||||
<?php if (auth()->loggedIn()): ?>
|
||||
<?= service('vite')->asset('js/error.ts', 'js') ?>
|
||||
<?php endif; ?>
|
||||
</head>
|
||||
|
||||
<body class="flex flex-col items-center justify-center min-h-screen px-4 bg-base gap-y-12 theme-<?= service('settings')
|
||||
->get('App.theme') ?>">
|
||||
<?php if (service('authentication')->isLoggedIn()): ?>
|
||||
<?php if (auth()->loggedIn()): ?>
|
||||
<div class="flex flex-col items-center justify-center flex-1 gap-6">
|
||||
<div class="flex flex-col items-center">
|
||||
<?= svg('castopod-mascot_confused', 'w-full max-w-xs p-6') ?>
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
"james-heinrich/getid3": "^2.0.x-dev",
|
||||
"whichbrowser/parser": "^v2.1.7",
|
||||
"geoip2/geoip2": "v2.13.0",
|
||||
"myth/auth": "dev-develop",
|
||||
"league/commonmark": "^2.3.5",
|
||||
"vlucas/phpdotenv": "^v5.4.1",
|
||||
"league/html-to-markdown": "^v5.1.0",
|
||||
|
@ -23,7 +22,8 @@
|
|||
"essence/essence": "^3.5.4",
|
||||
"codeigniter4/settings": "^v2.1.0",
|
||||
"chrisjean/php-ico": "^1.0.4",
|
||||
"melbahja/seo": "^v2.1.1"
|
||||
"melbahja/seo": "^v2.1.1",
|
||||
"codeigniter4/shield": "dev-develop"
|
||||
},
|
||||
"require-dev": {
|
||||
"mikey179/vfsstream": "^v1.6.11",
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "caa3b9ff10584fe03c7be1176713b427",
|
||||
"content-hash": "51482dcb24c719550a1f0aa7e7580dfc",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adaures/ipcat-php",
|
||||
|
@ -286,6 +286,70 @@
|
|||
},
|
||||
"time": "2021-11-22T17:30:18+00:00"
|
||||
},
|
||||
{
|
||||
"name": "codeigniter4/shield",
|
||||
"version": "dev-develop",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/codeigniter4/shield.git",
|
||||
"reference": "f4cdfb672b600a032a6f0bfc0b7735411bee0cae"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/codeigniter4/shield/zipball/f4cdfb672b600a032a6f0bfc0b7735411bee0cae",
|
||||
"reference": "f4cdfb672b600a032a6f0bfc0b7735411bee0cae",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"codeigniter4/settings": "^2.0",
|
||||
"php": "^7.4.3 || ^8.0"
|
||||
},
|
||||
"provide": {
|
||||
"codeigniter4/authentication-implementation": "1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"codeigniter4/devkit": "^1.0",
|
||||
"codeigniter4/framework": "^4.2.3",
|
||||
"mockery/mockery": "^1.0"
|
||||
},
|
||||
"default-branch": true,
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/Helpers/auth_helper.php",
|
||||
"src/Helpers/email_helper.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"CodeIgniter\\Shield\\": "src"
|
||||
},
|
||||
"exclude-from-classmap": ["**/Database/Migrations/**"]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": ["MIT"],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Lonnie Ezell",
|
||||
"email": "lonnieje@gmail.com",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "Authentication and Authorization for CodeIgniter 4",
|
||||
"homepage": "https://github.com/codeigniter4/shield",
|
||||
"keywords": [
|
||||
"Authentication",
|
||||
"authorization",
|
||||
"codeigniter",
|
||||
"codeigniter4"
|
||||
],
|
||||
"support": {
|
||||
"docs": "https://github.com/codeigniter4/shield/blob/develop/docs/index.md",
|
||||
"forum": "https://github.com/codeigniter4/shield/discussions",
|
||||
"issues": "https://github.com/codeigniter4/shield/issues",
|
||||
"slack": "https://codeigniterchat.slack.com",
|
||||
"source": "https://github.com/codeigniter4/shield"
|
||||
},
|
||||
"time": "2022-10-05T10:11:44+00:00"
|
||||
},
|
||||
{
|
||||
"name": "composer/ca-bundle",
|
||||
"version": "1.3.4",
|
||||
|
@ -1367,73 +1431,6 @@
|
|||
},
|
||||
"time": "2021-05-10T16:28:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "myth/auth",
|
||||
"version": "dev-develop",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/lonnieezell/myth-auth.git",
|
||||
"reference": "cc94231f5284e9578967aba4796f018809669c84"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/lonnieezell/myth-auth/zipball/cc94231f5284e9578967aba4796f018809669c84",
|
||||
"reference": "cc94231f5284e9578967aba4796f018809669c84",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.4 || ^8.0"
|
||||
},
|
||||
"provide": {
|
||||
"codeigniter4/authentication-implementation": "1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"codeigniter4/codeigniter4-standard": "^1.0",
|
||||
"codeigniter4/devkit": "^1.0",
|
||||
"codeigniter4/framework": "^4.1",
|
||||
"mockery/mockery": "^1.0"
|
||||
},
|
||||
"default-branch": true,
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Myth\\Auth\\": "src"
|
||||
},
|
||||
"exclude-from-classmap": ["**/Database/Migrations/**"]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": ["MIT"],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Lonnie Ezell",
|
||||
"email": "lonnieje@gmail.com",
|
||||
"homepage": "http://newmythmedia.com",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "Flexible authentication/authorization system for CodeIgniter 4.",
|
||||
"homepage": "https://github.com/lonnieezell/myth-auth",
|
||||
"keywords": ["Authentication", "authorization", "codeigniter"],
|
||||
"support": {
|
||||
"issues": "https://github.com/lonnieezell/myth-auth/issues",
|
||||
"source": "https://github.com/lonnieezell/myth-auth/tree/develop"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/lonnieezell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/mgatner",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://www.patreon.com/lonnieezell",
|
||||
"type": "patreon"
|
||||
}
|
||||
],
|
||||
"time": "2022-08-01T17:23:52+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nette/schema",
|
||||
"version": "v1.2.2",
|
||||
|
@ -6684,8 +6681,8 @@
|
|||
"minimum-stability": "stable",
|
||||
"stability-flags": {
|
||||
"james-heinrich/getid3": 20,
|
||||
"myth/auth": 20,
|
||||
"michalsn/codeigniter4-uuid": 20
|
||||
"michalsn/codeigniter4-uuid": 20,
|
||||
"codeigniter4/shield": 20
|
||||
},
|
||||
"prefer-stable": true,
|
||||
"prefer-lowest": false,
|
||||
|
|
|
@ -22,7 +22,6 @@ build:
|
|||
script:
|
||||
- npm run build
|
||||
except:
|
||||
- develop
|
||||
- main
|
||||
- beta
|
||||
- alpha
|
||||
|
@ -40,7 +39,6 @@ build-production:
|
|||
- docs/.vitepress/dist
|
||||
expire_in: 30 mins
|
||||
only:
|
||||
- develop
|
||||
- main
|
||||
- beta
|
||||
- alpha
|
||||
|
@ -72,7 +70,6 @@ deploy:
|
|||
- rsync -avzuh -e "ssh -p $SSH_PORT" $SOURCE_FOLDER $USER@$HOST:$TEMP_DIRECTORY --progress
|
||||
- ssh $USER@$HOST -p $SSH_PORT "rsync -rtv $TEMP_DIRECTORY $DIRECTORY"
|
||||
only:
|
||||
- develop
|
||||
- main
|
||||
- beta
|
||||
- alpha
|
||||
|
|
|
@ -179,6 +179,7 @@ function getGuideSidebarEn() {
|
|||
},
|
||||
{ text: "Security", link: "/getting-started/security" },
|
||||
{ text: "Update", link: "/getting-started/update" },
|
||||
{ text: "Auth", link: "/getting-started/auth" },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -207,6 +208,7 @@ function getGuideSidebarFr() {
|
|||
},
|
||||
{ text: "Sécurité", link: "/fr/getting-started/security" },
|
||||
{ text: "Mise à jour", link: "/fr/getting-started/update" },
|
||||
{ text: "Authentification", link: "/fr/getting-started/auth" },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -235,6 +237,7 @@ function getGuideSidebarPtBR() {
|
|||
},
|
||||
{ text: "Segurança", link: "/pt-BR/getting-started/security" },
|
||||
{ text: "Atualizar", link: "/pt-BR/getting-started/update" },
|
||||
{ text: "Autenticação", link: "/pt-BR/getting-started/auth" },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -263,6 +266,7 @@ function getGuideSidebarNnNO() {
|
|||
},
|
||||
{ text: "Sikkerhet", link: "/nn-NO/getting-started/security" },
|
||||
{ text: "Oppdaterer", link: "/nn-NO/getting-started/update" },
|
||||
{ text: "Autentisering", link: "/pt-BR/getting-started/auth" },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
---
|
||||
title: Authentication & Authorization
|
||||
sidebarDepth: 3
|
||||
---
|
||||
|
||||
# Authentication & Authorization
|
||||
|
||||
Castopod handles authentication and authorization using `codeigniter/shield`
|
||||
coupled with custom rules. Roles and permissions are defined at two levels:
|
||||
|
||||
1. [instance wide](#1-instance-wide-roles-and-permissions)
|
||||
2. [per podcast](#2-per-podcast-roles-and-permissions)
|
||||
|
||||
## 1. Instance wide roles and permissions
|
||||
|
||||
### Instance roles
|
||||
|
||||
<!-- AUTH-INSTANCE-ROLES-LIST:START - Do not remove or modify this section -->
|
||||
|
||||
| role | description | permissions |
|
||||
| ----------- | ----------------------------------- | ------------------------------------------------------------------------------------------ |
|
||||
| Super admin | Has complete control over Castopod. | admin.\*, podcasts.\*, users.manage, persons.manage, pages.manage, fediverse.manage-blocks |
|
||||
| Manager | Manages Castopod's content. | podcasts.create, podcasts.import, persons.manage, pages.manage |
|
||||
| Podcaster | General users of Castopod. | admin.access |
|
||||
|
||||
<!-- AUTH-INSTANCE-ROLES-LIST:END -->
|
||||
|
||||
### Instance permissions
|
||||
|
||||
<!-- AUTH-INSTANCE-PERMISSIONS-LIST:START - Do not remove or modify this section -->
|
||||
|
||||
| permission | description |
|
||||
| ----------------------- | ------------------------------------------------------------------ |
|
||||
| admin.access | Can access the Castopod admin area. |
|
||||
| admin.settings | Can access the Castopod settings. |
|
||||
| users.manage | Can manage Castopod users. |
|
||||
| persons.manage | Can manage persons. |
|
||||
| pages.manage | Can manage pages. |
|
||||
| podcasts.view | Can view all podcasts. |
|
||||
| podcasts.create | Can create new podcasts. |
|
||||
| podcasts.import | Can import podcasts. |
|
||||
| fediverse.manage-blocks | Can block fediverse actors/domains from interacting with Castopod. |
|
||||
|
||||
<!-- AUTH-INSTANCE-PERMISSIONS-LIST:END -->
|
||||
|
||||
## 2. Per podcast roles and permissions
|
||||
|
||||
### Per podcast roles
|
||||
|
||||
<!-- AUTH-PODCAST-ROLES-LIST:START - Do not remove or modify this section -->
|
||||
|
||||
| role | description | permissions |
|
||||
| ------ | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Admin | Has complete control of podcast #{id}. | \* |
|
||||
| Editor | Manages content and publications of podcast #{id}. | view, edit, manage-import, manage-persons, manage-platforms, manage-publications, interact-as, episodes.view, episodes.create, episodes.edit, episodes.delete, episodes.manage-persons, episodes.manage-clips, episodes.manage-publications, episodes.manage-comments |
|
||||
| Author | Manages content of podcast #{id} but cannot publish them. | view, manage-persons, episodes.view, episodes.create, episodes.edit, episodes.manage-persons, episodes.manage-clips |
|
||||
| Guest | General contributor of the podcast #{id}. | view, episodes.view |
|
||||
|
||||
<!-- AUTH-PODCAST-ROLES-LIST:END -->
|
||||
|
||||
### Per podcast permissions
|
||||
|
||||
<!-- AUTH-PODCAST-PERMISSIONS-LIST:START - Do not remove or modify this section -->
|
||||
|
||||
| permission | description |
|
||||
| ---------------------------- | ------------------------------------------------------------------------ |
|
||||
| view | Can view dashboard and analytics of podcast #{id}. |
|
||||
| edit | Can edit podcast #{id}. |
|
||||
| delete | Can delete podcast #{id}. |
|
||||
| manage-import | Can synchronize imported podcast #{id}. |
|
||||
| manage-persons | Can manage subscriptions of podcast #{id}. |
|
||||
| manage-subscriptions | Can manage subscriptions of podcast #{id}. |
|
||||
| manage-contributors | Can manage contributors of podcast #{id}. |
|
||||
| manage-platforms | Can set/remove platform links of podcast #{id}. |
|
||||
| manage-publications | Can publish podcast #{id}. |
|
||||
| interact-as | Can interact as the podcast #{id} to favourite, share or reply to posts. |
|
||||
| episodes.view | Can view dashboard and analytics of podcast #{id}. |
|
||||
| episodes.create | Can create episodes for podcast #{id}. |
|
||||
| episodes.edit | Can edit podcast #{id}. |
|
||||
| episodes.delete | Can delete podcast #{id}. |
|
||||
| episodes.manage-persons | Can manage subscriptions of podcast #{id}. |
|
||||
| episodes.manage-clips | Can manage video clips or soundbites of podcast #{id}. |
|
||||
| episodes.manage-publications | Can publish podcast #{id}. |
|
||||
| episodes.manage-comments | Can create/remove episode comments of podcast #{id}. |
|
||||
|
||||
<!-- AUTH-PODCAST-PERMISSIONS-LIST:END -->
|
|
@ -25,60 +25,60 @@ $routes->group(
|
|||
$routes->group('settings', static function ($routes): void {
|
||||
$routes->get('/', 'SettingsController', [
|
||||
'as' => 'settings-general',
|
||||
'filter' => 'permission:settings-manage',
|
||||
'filter' => 'permission:admin.settings',
|
||||
]);
|
||||
$routes->post('instance', 'SettingsController::attemptInstanceEdit', [
|
||||
'as' => 'settings-instance',
|
||||
'filter' => 'permission:settings-manage',
|
||||
'filter' => 'permission:admin.settings',
|
||||
]);
|
||||
$routes->get('instance-delete-icon', 'SettingsController::deleteIcon', [
|
||||
'as' => 'settings-instance-delete-icon',
|
||||
'filter' => 'permission:settings-manage',
|
||||
'filter' => 'permission:admin.settings',
|
||||
]);
|
||||
$routes->post('instance-images-regenerate', 'SettingsController::regenerateImages', [
|
||||
'as' => 'settings-images-regenerate',
|
||||
'filter' => 'permission:settings-manage',
|
||||
'filter' => 'permission:admin.settings',
|
||||
]);
|
||||
$routes->post('instance-housekeeping-run', 'SettingsController::runHousekeeping', [
|
||||
'as' => 'settings-housekeeping-run',
|
||||
'filter' => 'permission:settings-manage',
|
||||
'filter' => 'permission:admin.settings',
|
||||
]);
|
||||
$routes->get('theme', 'SettingsController::theme', [
|
||||
'as' => 'settings-theme',
|
||||
'filter' => 'permission:settings-manage',
|
||||
'filter' => 'permission:admin.settings',
|
||||
]);
|
||||
$routes->post('theme', 'SettingsController::attemptSetInstanceTheme', [
|
||||
'as' => 'settings-theme',
|
||||
'filter' => 'permission:settings-manage',
|
||||
'filter' => 'permission:admin.settings',
|
||||
]);
|
||||
});
|
||||
$routes->group('persons', static function ($routes): void {
|
||||
$routes->get('/', 'PersonController', [
|
||||
'as' => 'person-list',
|
||||
'filter' => 'permission:person-list',
|
||||
'filter' => 'permission:persons.manage',
|
||||
]);
|
||||
$routes->get('new', 'PersonController::create', [
|
||||
'as' => 'person-create',
|
||||
'filter' => 'permission:person-create',
|
||||
'filter' => 'permission:persons.manage',
|
||||
]);
|
||||
$routes->post('new', 'PersonController::attemptCreate', [
|
||||
'filter' => 'permission:person-create',
|
||||
'filter' => 'permission:persons.manage',
|
||||
]);
|
||||
$routes->group('(:num)', static function ($routes): void {
|
||||
$routes->get('/', 'PersonController::view/$1', [
|
||||
'as' => 'person-view',
|
||||
'filter' => 'permission:person-view',
|
||||
'filter' => 'permission:persons.manage',
|
||||
]);
|
||||
$routes->get('edit', 'PersonController::edit/$1', [
|
||||
'as' => 'person-edit',
|
||||
'filter' => 'permission:person-edit',
|
||||
'filter' => 'permission:persons.manage',
|
||||
]);
|
||||
$routes->post('edit', 'PersonController::attemptEdit/$1', [
|
||||
'filter' => 'permission:person-edit',
|
||||
'filter' => 'permission:persons.manage',
|
||||
]);
|
||||
$routes->add('delete', 'PersonController::delete/$1', [
|
||||
'as' => 'person-delete',
|
||||
'filter' => 'permission:person-delete',
|
||||
'filter' => 'permission:persons.manage',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
@ -89,31 +89,31 @@ $routes->group(
|
|||
]);
|
||||
$routes->get('new', 'PodcastController::create', [
|
||||
'as' => 'podcast-create',
|
||||
'filter' => 'permission:podcasts-create',
|
||||
'filter' => 'permission:podcasts.create',
|
||||
]);
|
||||
$routes->post('new', 'PodcastController::attemptCreate', [
|
||||
'filter' => 'permission:podcasts-create',
|
||||
'filter' => 'permission:podcasts.create',
|
||||
]);
|
||||
$routes->get('import', 'PodcastImportController', [
|
||||
'as' => 'podcast-import',
|
||||
'filter' => 'permission:podcasts-import',
|
||||
'filter' => 'permission:podcasts.import',
|
||||
]);
|
||||
$routes->post('import', 'PodcastImportController::attemptImport', [
|
||||
'filter' => 'permission:podcasts-import',
|
||||
'filter' => 'permission:podcasts.import',
|
||||
]);
|
||||
// Podcast
|
||||
// Use ids in admin area to help permission and group lookups
|
||||
$routes->group('(:num)', static function ($routes): void {
|
||||
$routes->get('/', 'PodcastController::view/$1', [
|
||||
'as' => 'podcast-view',
|
||||
'filter' => 'permission:podcasts-view,podcast-view',
|
||||
'filter' => 'permission:podcast#.view',
|
||||
]);
|
||||
$routes->get('edit', 'PodcastController::edit/$1', [
|
||||
'as' => 'podcast-edit',
|
||||
'filter' => 'permission:podcast-edit',
|
||||
'filter' => 'permission:podcast#.edit',
|
||||
]);
|
||||
$routes->post('edit', 'PodcastController::attemptEdit/$1', [
|
||||
'filter' => 'permission:podcast-edit',
|
||||
'filter' => 'permission:podcast#.edit',
|
||||
]);
|
||||
$routes->get(
|
||||
'publish',
|
||||
|
@ -121,7 +121,7 @@ $routes->group(
|
|||
[
|
||||
'as' => 'podcast-publish',
|
||||
'filter' =>
|
||||
'permission:podcast-manage_publications',
|
||||
'permission:podcast#.manage-publications',
|
||||
],
|
||||
);
|
||||
$routes->post(
|
||||
|
@ -129,7 +129,7 @@ $routes->group(
|
|||
'PodcastController::attemptPublish/$1',
|
||||
[
|
||||
'filter' =>
|
||||
'permission:podcast-manage_publications',
|
||||
'permission:podcast#.manage-publications',
|
||||
],
|
||||
);
|
||||
$routes->get(
|
||||
|
@ -138,7 +138,7 @@ $routes->group(
|
|||
[
|
||||
'as' => 'podcast-publish_edit',
|
||||
'filter' =>
|
||||
'permission:podcast-manage_publications',
|
||||
'permission:podcast#.manage-publications',
|
||||
],
|
||||
);
|
||||
$routes->post(
|
||||
|
@ -146,7 +146,7 @@ $routes->group(
|
|||
'PodcastController::attemptPublishEdit/$1',
|
||||
[
|
||||
'filter' =>
|
||||
'permission:podcast-manage_publications',
|
||||
'permission:podcast#.manage-publications',
|
||||
],
|
||||
);
|
||||
$routes->get(
|
||||
|
@ -155,34 +155,34 @@ $routes->group(
|
|||
[
|
||||
'as' => 'podcast-publish-cancel',
|
||||
'filter' =>
|
||||
'permission:podcast-manage_publications',
|
||||
'permission:podcast#.manage-publications',
|
||||
],
|
||||
);
|
||||
$routes->get('edit/delete-banner', 'PodcastController::deleteBanner/$1', [
|
||||
'as' => 'podcast-banner-delete',
|
||||
'filter' => 'permission:podcast-edit',
|
||||
'filter' => 'permission:podcast#.edit',
|
||||
]);
|
||||
$routes->get('delete', 'PodcastController::delete/$1', [
|
||||
'as' => 'podcast-delete',
|
||||
'filter' => 'permission:podcasts-delete',
|
||||
'filter' => 'permission:podcast#.delete',
|
||||
]);
|
||||
$routes->post('delete', 'PodcastController::attemptDelete/$1', [
|
||||
'filter' => 'permission:podcasts-delete',
|
||||
'filter' => 'permission:podcast#.delete',
|
||||
]);
|
||||
$routes->get('update', 'PodcastImportController::updateImport/$1', [
|
||||
'as' => 'podcast-update-feed',
|
||||
'filter' => 'permission:podcasts-import',
|
||||
'filter' => 'permission:podcast#.manage-import',
|
||||
]);
|
||||
$routes->group('persons', static function ($routes): void {
|
||||
$routes->get('/', 'PodcastPersonController/$1', [
|
||||
'as' => 'podcast-persons-manage',
|
||||
'filter' => 'permission:podcast-edit',
|
||||
'filter' => 'permission:podcast#.manage-persons',
|
||||
]);
|
||||
$routes->post(
|
||||
'/',
|
||||
'PodcastPersonController::attemptAdd/$1',
|
||||
[
|
||||
'filter' => 'permission:podcast-edit',
|
||||
'filter' => 'permission:podcast#.manage-persons',
|
||||
],
|
||||
);
|
||||
$routes->get(
|
||||
|
@ -190,21 +190,21 @@ $routes->group(
|
|||
'PodcastPersonController::remove/$1/$2',
|
||||
[
|
||||
'as' => 'podcast-person-remove',
|
||||
'filter' => 'permission:podcast-edit',
|
||||
'filter' => 'permission:podcast#.manage-persons',
|
||||
],
|
||||
);
|
||||
});
|
||||
$routes->group('analytics', static function ($routes): void {
|
||||
$routes->get('/', 'PodcastController::viewAnalytics/$1', [
|
||||
'as' => 'podcast-analytics',
|
||||
'filter' => 'permission:podcasts-view,podcast-view',
|
||||
'filter' => 'permission:podcast#.view',
|
||||
]);
|
||||
$routes->get(
|
||||
'webpages',
|
||||
'PodcastController::viewAnalyticsWebpages/$1',
|
||||
[
|
||||
'as' => 'podcast-analytics-webpages',
|
||||
'filter' => 'permission:podcasts-view,podcast-view',
|
||||
'filter' => 'permission:podcast#.view',
|
||||
],
|
||||
);
|
||||
$routes->get(
|
||||
|
@ -212,7 +212,7 @@ $routes->group(
|
|||
'PodcastController::viewAnalyticsLocations/$1',
|
||||
[
|
||||
'as' => 'podcast-analytics-locations',
|
||||
'filter' => 'permission:podcasts-view,podcast-view',
|
||||
'filter' => 'permission:podcast#.view',
|
||||
],
|
||||
);
|
||||
$routes->get(
|
||||
|
@ -220,7 +220,7 @@ $routes->group(
|
|||
'PodcastController::viewAnalyticsUniqueListeners/$1',
|
||||
[
|
||||
'as' => 'podcast-analytics-unique-listeners',
|
||||
'filter' => 'permission:podcasts-view,podcast-view',
|
||||
'filter' => 'permission:podcast#.view',
|
||||
],
|
||||
);
|
||||
$routes->get(
|
||||
|
@ -228,7 +228,7 @@ $routes->group(
|
|||
'PodcastController::viewAnalyticsListeningTime/$1',
|
||||
[
|
||||
'as' => 'podcast-analytics-listening-time',
|
||||
'filter' => 'permission:podcasts-view,podcast-view',
|
||||
'filter' => 'permission:podcast#.view',
|
||||
],
|
||||
);
|
||||
$routes->get(
|
||||
|
@ -236,7 +236,7 @@ $routes->group(
|
|||
'PodcastController::viewAnalyticsTimePeriods/$1',
|
||||
[
|
||||
'as' => 'podcast-analytics-time-periods',
|
||||
'filter' => 'permission:podcasts-view,podcast-view',
|
||||
'filter' => 'permission:podcast#.view',
|
||||
],
|
||||
);
|
||||
$routes->get(
|
||||
|
@ -244,7 +244,7 @@ $routes->group(
|
|||
'PodcastController::viewAnalyticsPlayers/$1',
|
||||
[
|
||||
'as' => 'podcast-analytics-players',
|
||||
'filter' => 'permission:podcasts-view,podcast-view',
|
||||
'filter' => 'permission:podcast#.view',
|
||||
],
|
||||
);
|
||||
});
|
||||
|
@ -253,17 +253,17 @@ $routes->group(
|
|||
$routes->get('/', 'EpisodeController::list/$1', [
|
||||
'as' => 'episode-list',
|
||||
'filter' =>
|
||||
'permission:episodes-list,podcast_episodes-list',
|
||||
'permission:podcast#.episodes.view',
|
||||
]);
|
||||
$routes->get('new', 'EpisodeController::create/$1', [
|
||||
'as' => 'episode-create',
|
||||
'filter' => 'permission:podcast_episodes-create',
|
||||
'filter' => 'permission:podcast#.episodes.create',
|
||||
]);
|
||||
$routes->post(
|
||||
'new',
|
||||
'EpisodeController::attemptCreate/$1',
|
||||
[
|
||||
'filter' => 'permission:podcast_episodes-create',
|
||||
'filter' => 'permission:podcast#.episodes.create',
|
||||
],
|
||||
);
|
||||
// Episode
|
||||
|
@ -271,17 +271,17 @@ $routes->group(
|
|||
$routes->get('/', 'EpisodeController::view/$1/$2', [
|
||||
'as' => 'episode-view',
|
||||
'filter' =>
|
||||
'permission:episodes-view,podcast_episodes-view',
|
||||
'permission:podcast#.episodes.view',
|
||||
]);
|
||||
$routes->get('edit', 'EpisodeController::edit/$1/$2', [
|
||||
'as' => 'episode-edit',
|
||||
'filter' => 'permission:podcast_episodes-edit',
|
||||
'filter' => 'permission:podcast#.episodes.edit',
|
||||
]);
|
||||
$routes->post(
|
||||
'edit',
|
||||
'EpisodeController::attemptEdit/$1/$2',
|
||||
[
|
||||
'filter' => 'permission:podcast_episodes-edit',
|
||||
'filter' => 'permission:podcast#.episodes.edit',
|
||||
],
|
||||
);
|
||||
$routes->get(
|
||||
|
@ -290,7 +290,7 @@ $routes->group(
|
|||
[
|
||||
'as' => 'episode-publish',
|
||||
'filter' =>
|
||||
'permission:podcast-manage_publications',
|
||||
'permission:podcast#.episodes.manage-publications',
|
||||
],
|
||||
);
|
||||
$routes->post(
|
||||
|
@ -298,7 +298,7 @@ $routes->group(
|
|||
'EpisodeController::attemptPublish/$1/$2',
|
||||
[
|
||||
'filter' =>
|
||||
'permission:podcast-manage_publications',
|
||||
'permission:podcast#.episodes.manage-publications',
|
||||
],
|
||||
);
|
||||
$routes->get(
|
||||
|
@ -307,7 +307,7 @@ $routes->group(
|
|||
[
|
||||
'as' => 'episode-publish_edit',
|
||||
'filter' =>
|
||||
'permission:podcast-manage_publications',
|
||||
'permission:podcast#.episodes.manage-publications',
|
||||
],
|
||||
);
|
||||
$routes->post(
|
||||
|
@ -315,7 +315,7 @@ $routes->group(
|
|||
'EpisodeController::attemptPublishEdit/$1/$2',
|
||||
[
|
||||
'filter' =>
|
||||
'permission:podcast-manage_publications',
|
||||
'permission:podcast#.episodes.manage-publications',
|
||||
],
|
||||
);
|
||||
$routes->get(
|
||||
|
@ -324,7 +324,7 @@ $routes->group(
|
|||
[
|
||||
'as' => 'episode-publish-cancel',
|
||||
'filter' =>
|
||||
'permission:podcast-manage_publications',
|
||||
'permission:podcast#.episodes.manage-publications',
|
||||
],
|
||||
);
|
||||
$routes->get(
|
||||
|
@ -350,7 +350,7 @@ $routes->group(
|
|||
[
|
||||
'as' => 'episode-unpublish',
|
||||
'filter' =>
|
||||
'permission:podcast-manage_publications',
|
||||
'permission:podcast#.episodes.manage-publications',
|
||||
],
|
||||
);
|
||||
$routes->post(
|
||||
|
@ -358,7 +358,7 @@ $routes->group(
|
|||
'EpisodeController::attemptUnpublish/$1/$2',
|
||||
[
|
||||
'filter' =>
|
||||
'permission:podcast-manage_publications',
|
||||
'permission:podcast#.episodes.manage-publications',
|
||||
],
|
||||
);
|
||||
$routes->get(
|
||||
|
@ -367,7 +367,7 @@ $routes->group(
|
|||
[
|
||||
'as' => 'episode-delete',
|
||||
'filter' =>
|
||||
'permission:podcast_episodes-delete',
|
||||
'permission:podcast#.episodes.delete',
|
||||
],
|
||||
);
|
||||
$routes->post(
|
||||
|
@ -375,7 +375,7 @@ $routes->group(
|
|||
'EpisodeController::attemptDelete/$1/$2',
|
||||
[
|
||||
'filter' =>
|
||||
'permission:podcast_episodes-delete',
|
||||
'permission:podcast#.episodes.delete',
|
||||
],
|
||||
);
|
||||
$routes->get(
|
||||
|
@ -383,7 +383,7 @@ $routes->group(
|
|||
'EpisodeController::transcriptDelete/$1/$2',
|
||||
[
|
||||
'as' => 'transcript-delete',
|
||||
'filter' => 'permission:podcast_episodes-edit',
|
||||
'filter' => 'permission:podcast#.episodes.edit',
|
||||
],
|
||||
);
|
||||
$routes->get(
|
||||
|
@ -391,7 +391,7 @@ $routes->group(
|
|||
'EpisodeController::chaptersDelete/$1/$2',
|
||||
[
|
||||
'as' => 'chapters-delete',
|
||||
'filter' => 'permission:podcast_episodes-edit',
|
||||
'filter' => 'permission:podcast#.episodes.edit',
|
||||
],
|
||||
);
|
||||
$routes->get(
|
||||
|
@ -399,7 +399,7 @@ $routes->group(
|
|||
'SoundbiteController::list/$1/$2',
|
||||
[
|
||||
'as' => 'soundbites-list',
|
||||
'filter' => 'permission:podcast_episodes-edit',
|
||||
'filter' => 'permission:podcast#.episodes.manage-clips',
|
||||
],
|
||||
);
|
||||
$routes->get(
|
||||
|
@ -407,7 +407,7 @@ $routes->group(
|
|||
'SoundbiteController::create/$1/$2',
|
||||
[
|
||||
'as' => 'soundbites-create',
|
||||
'filter' => 'permission:podcast_episodes-edit',
|
||||
'filter' => 'permission:podcast#.episodes.manage-clips',
|
||||
],
|
||||
);
|
||||
$routes->post(
|
||||
|
@ -415,7 +415,7 @@ $routes->group(
|
|||
'SoundbiteController::attemptCreate/$1/$2',
|
||||
[
|
||||
'as' => 'soundbites-create',
|
||||
'filter' => 'permission:podcast_episodes-edit',
|
||||
'filter' => 'permission:podcast#.episodes.manage-clips',
|
||||
],
|
||||
);
|
||||
$routes->get(
|
||||
|
@ -423,7 +423,7 @@ $routes->group(
|
|||
'SoundbiteController::delete/$1/$2/$3',
|
||||
[
|
||||
'as' => 'soundbites-delete',
|
||||
'filter' => 'permission:podcast_episodes-edit',
|
||||
'filter' => 'permission:podcast#.episodes.manage-clips',
|
||||
],
|
||||
);
|
||||
$routes->get(
|
||||
|
@ -431,7 +431,7 @@ $routes->group(
|
|||
'VideoClipsController::list/$1/$2',
|
||||
[
|
||||
'as' => 'video-clips-list',
|
||||
'filter' => 'permission:podcast_episodes-edit',
|
||||
'filter' => 'permission:podcast#.episodes.manage-clips',
|
||||
],
|
||||
);
|
||||
$routes->get(
|
||||
|
@ -439,7 +439,7 @@ $routes->group(
|
|||
'VideoClipsController::create/$1/$2',
|
||||
[
|
||||
'as' => 'video-clips-create',
|
||||
'filter' => 'permission:podcast_episodes-edit',
|
||||
'filter' => 'permission:podcast#.episodes.manage-clips',
|
||||
],
|
||||
);
|
||||
$routes->post(
|
||||
|
@ -447,7 +447,7 @@ $routes->group(
|
|||
'VideoClipsController::attemptCreate/$1/$2',
|
||||
[
|
||||
'as' => 'video-clips-create',
|
||||
'filter' => 'permission:podcast_episodes-edit',
|
||||
'filter' => 'permission:podcast#.episodes.manage-clips',
|
||||
],
|
||||
);
|
||||
$routes->get(
|
||||
|
@ -455,7 +455,7 @@ $routes->group(
|
|||
'VideoClipsController::view/$1/$2/$3',
|
||||
[
|
||||
'as' => 'video-clip',
|
||||
'filter' => 'permission:podcast_episodes-edit',
|
||||
'filter' => 'permission:podcast#.episodes.manage-clips',
|
||||
],
|
||||
);
|
||||
$routes->get(
|
||||
|
@ -463,7 +463,7 @@ $routes->group(
|
|||
'VideoClipsController::retry/$1/$2/$3',
|
||||
[
|
||||
'as' => 'video-clip-retry',
|
||||
'filter' => 'permission:podcast_episodes-edit',
|
||||
'filter' => 'permission:podcast#.episodes.manage-clips',
|
||||
],
|
||||
);
|
||||
$routes->get(
|
||||
|
@ -471,7 +471,7 @@ $routes->group(
|
|||
'VideoClipsController::delete/$1/$2/$3',
|
||||
[
|
||||
'as' => 'video-clip-delete',
|
||||
'filter' => 'permission:podcast_episodes-edit',
|
||||
'filter' => 'permission:podcast#.episodes.manage-clips',
|
||||
],
|
||||
);
|
||||
$routes->get(
|
||||
|
@ -479,20 +479,20 @@ $routes->group(
|
|||
'EpisodeController::embed/$1/$2',
|
||||
[
|
||||
'as' => 'embed-add',
|
||||
'filter' => 'permission:podcast_episodes-edit',
|
||||
'filter' => 'permission:podcast#.episodes.edit',
|
||||
],
|
||||
);
|
||||
$routes->group('persons', static function ($routes): void {
|
||||
$routes->get('/', 'EpisodePersonController/$1/$2', [
|
||||
'as' => 'episode-persons-manage',
|
||||
'filter' => 'permission:podcast_episodes-edit',
|
||||
'filter' => 'permission:podcast#.episodes.manage-persons',
|
||||
]);
|
||||
$routes->post(
|
||||
'/',
|
||||
'EpisodePersonController::attemptAdd/$1/$2',
|
||||
[
|
||||
'filter' =>
|
||||
'permission:podcast_episodes-edit',
|
||||
'permission:podcast#.episodes.manage-persons',
|
||||
],
|
||||
);
|
||||
$routes->get(
|
||||
|
@ -501,7 +501,7 @@ $routes->group(
|
|||
[
|
||||
'as' => 'episode-person-remove',
|
||||
'filter' =>
|
||||
'permission:podcast_episodes-edit',
|
||||
'permission:podcast#.episodes.manage-persons',
|
||||
],
|
||||
);
|
||||
});
|
||||
|
@ -511,7 +511,7 @@ $routes->group(
|
|||
'EpisodeController::attemptCommentCreate/$1/$2',
|
||||
[
|
||||
'as' => 'comment-attempt-create',
|
||||
'filter' => 'permission:podcast-manage_publications',
|
||||
'filter' => 'permission:podcast#.episodes.manage-comments',
|
||||
]
|
||||
);
|
||||
$routes->post(
|
||||
|
@ -519,7 +519,7 @@ $routes->group(
|
|||
'EpisodeController::attemptCommentReply/$1/$2/$3',
|
||||
[
|
||||
'as' => 'comment-attempt-reply',
|
||||
'filter' => 'permission:podcast-manage_publications',
|
||||
'filter' => 'permission:podcast#.episodes.manage-comments',
|
||||
]
|
||||
);
|
||||
$routes->post(
|
||||
|
@ -527,73 +527,19 @@ $routes->group(
|
|||
'EpisodeController::attemptCommentDelete/$1/$2',
|
||||
[
|
||||
'as' => 'comment-attempt-delete',
|
||||
'filter' => 'permission:podcast-manage_publications',
|
||||
'filter' => 'permission:podcast#.episodes.manage-comments',
|
||||
]
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
// Podcast contributors
|
||||
$routes->group('contributors', static function ($routes): void {
|
||||
$routes->get('/', 'ContributorController::list/$1', [
|
||||
'as' => 'contributor-list',
|
||||
'filter' =>
|
||||
'permission:podcasts-view,podcast-manage_contributors',
|
||||
]);
|
||||
$routes->get('add', 'ContributorController::add/$1', [
|
||||
'as' => 'contributor-add',
|
||||
'filter' => 'permission:podcast-manage_contributors',
|
||||
]);
|
||||
$routes->post(
|
||||
'add',
|
||||
'ContributorController::attemptAdd/$1',
|
||||
[
|
||||
'filter' =>
|
||||
'permission:podcast-manage_contributors',
|
||||
],
|
||||
);
|
||||
// Contributor
|
||||
$routes->group('(:num)', static function ($routes): void {
|
||||
$routes->get('/', 'ContributorController::view/$1/$2', [
|
||||
'as' => 'contributor-view',
|
||||
'filter' =>
|
||||
'permission:podcast-manage_contributors',
|
||||
]);
|
||||
$routes->get(
|
||||
'edit',
|
||||
'ContributorController::edit/$1/$2',
|
||||
[
|
||||
'as' => 'contributor-edit',
|
||||
'filter' =>
|
||||
'permission:podcast-manage_contributors',
|
||||
],
|
||||
);
|
||||
$routes->post(
|
||||
'edit',
|
||||
'ContributorController::attemptEdit/$1/$2',
|
||||
[
|
||||
'filter' =>
|
||||
'permission:podcast-manage_contributors',
|
||||
],
|
||||
);
|
||||
$routes->get(
|
||||
'remove',
|
||||
'ContributorController::remove/$1/$2',
|
||||
[
|
||||
'as' => 'contributor-remove',
|
||||
'filter' =>
|
||||
'permission:podcast-manage_contributors',
|
||||
],
|
||||
);
|
||||
});
|
||||
});
|
||||
$routes->group('platforms', static function ($routes): void {
|
||||
$routes->get(
|
||||
'/',
|
||||
'PodcastPlatformController::platforms/$1/podcasting',
|
||||
[
|
||||
'as' => 'platforms-podcasting',
|
||||
'filter' => 'permission:podcast-manage_platforms',
|
||||
'filter' => 'permission:podcast#.manage-platforms',
|
||||
],
|
||||
);
|
||||
$routes->get(
|
||||
|
@ -601,7 +547,7 @@ $routes->group(
|
|||
'PodcastPlatformController::platforms/$1/social',
|
||||
[
|
||||
'as' => 'platforms-social',
|
||||
'filter' => 'permission:podcast-manage_platforms',
|
||||
'filter' => 'permission:podcast#.manage-platforms',
|
||||
],
|
||||
);
|
||||
$routes->get(
|
||||
|
@ -609,7 +555,7 @@ $routes->group(
|
|||
'PodcastPlatformController::platforms/$1/funding',
|
||||
[
|
||||
'as' => 'platforms-funding',
|
||||
'filter' => 'permission:podcast-manage_platforms',
|
||||
'filter' => 'permission:podcast#.manage-platforms',
|
||||
],
|
||||
);
|
||||
$routes->post(
|
||||
|
@ -617,7 +563,7 @@ $routes->group(
|
|||
'PodcastPlatformController::attemptPlatformsUpdate/$1/$2',
|
||||
[
|
||||
'as' => 'platforms-save',
|
||||
'filter' => 'permission:podcast-manage_platforms',
|
||||
'filter' => 'permission:podcast#.manage-platforms',
|
||||
],
|
||||
);
|
||||
$routes->get(
|
||||
|
@ -625,7 +571,7 @@ $routes->group(
|
|||
'PodcastPlatformController::removePodcastPlatform/$1/$2',
|
||||
[
|
||||
'as' => 'podcast-platform-remove',
|
||||
'filter' => 'permission:podcast-manage_platforms',
|
||||
'filter' => 'permission:podcast#.manage-platforms',
|
||||
],
|
||||
);
|
||||
});
|
||||
|
@ -633,12 +579,15 @@ $routes->group(
|
|||
$routes->group('notifications', static function ($routes): void {
|
||||
$routes->get('/', 'NotificationController::list/$1', [
|
||||
'as' => 'notification-list',
|
||||
'filter' => 'permission:podcast#.view',
|
||||
]);
|
||||
$routes->get('(:num)/mark-as-read', 'NotificationController::markAsRead/$1/$2', [
|
||||
'as' => 'notification-mark-as-read',
|
||||
'filter' => 'permission:podcast#.manage-notifications',
|
||||
]);
|
||||
$routes->get('mark-all-as-read', 'NotificationController::markAllAsRead/$1', [
|
||||
'as' => 'notification-mark-all-as-read',
|
||||
'filter' => 'permission:podcast#.manage-notifications',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
@ -653,7 +602,7 @@ $routes->group(
|
|||
'FediverseController::blockedActors',
|
||||
[
|
||||
'as' => 'fediverse-blocked-actors',
|
||||
'filter' => 'permission:fediverse-block_actors',
|
||||
'filter' => 'permission:fediverse.manage-blocks',
|
||||
],
|
||||
);
|
||||
$routes->get(
|
||||
|
@ -661,7 +610,7 @@ $routes->group(
|
|||
'FediverseController::blockedDomains',
|
||||
[
|
||||
'as' => 'fediverse-blocked-domains',
|
||||
'filter' => 'permission:fediverse-block_domains',
|
||||
'filter' => 'permission:fediverse.manage-blocks',
|
||||
],
|
||||
);
|
||||
});
|
||||
|
@ -669,13 +618,14 @@ $routes->group(
|
|||
$routes->group('pages', static function ($routes): void {
|
||||
$routes->get('/', 'PageController::list', [
|
||||
'as' => 'page-list',
|
||||
'filter' => 'permission:pages.manage',
|
||||
]);
|
||||
$routes->get('new', 'PageController::create', [
|
||||
'as' => 'page-create',
|
||||
'filter' => 'permission:pages-manage',
|
||||
'filter' => 'permission:pages.manage',
|
||||
]);
|
||||
$routes->post('new', 'PageController::attemptCreate', [
|
||||
'filter' => 'permission:pages-manage',
|
||||
'filter' => 'permission:pages.manage',
|
||||
]);
|
||||
$routes->group('(:num)', static function ($routes): void {
|
||||
$routes->get('/', 'PageController::view/$1', [
|
||||
|
@ -683,78 +633,16 @@ $routes->group(
|
|||
]);
|
||||
$routes->get('edit', 'PageController::edit/$1', [
|
||||
'as' => 'page-edit',
|
||||
'filter' => 'permission:pages-manage',
|
||||
'filter' => 'permission:pages.manage',
|
||||
]);
|
||||
$routes->post('edit', 'PageController::attemptEdit/$1', [
|
||||
'filter' => 'permission:pages-manage',
|
||||
'filter' => 'permission:pages.manage',
|
||||
]);
|
||||
$routes->get('delete', 'PageController::delete/$1', [
|
||||
'as' => 'page-delete',
|
||||
'filter' => 'permission:pages-manage',
|
||||
'filter' => 'permission:pages.manage',
|
||||
]);
|
||||
});
|
||||
});
|
||||
// Users
|
||||
$routes->group('users', static function ($routes): void {
|
||||
$routes->get('/', 'UserController::list', [
|
||||
'as' => 'user-list',
|
||||
'filter' => 'permission:users-list',
|
||||
]);
|
||||
$routes->get('new', 'UserController::create', [
|
||||
'as' => 'user-create',
|
||||
'filter' => 'permission:users-create',
|
||||
]);
|
||||
$routes->post('new', 'UserController::attemptCreate', [
|
||||
'filter' => 'permission:users-create',
|
||||
]);
|
||||
// User
|
||||
$routes->group('(:num)', static function ($routes): void {
|
||||
$routes->get('/', 'UserController::view/$1', [
|
||||
'as' => 'user-view',
|
||||
'filter' => 'permission:users-view',
|
||||
]);
|
||||
$routes->get('edit', 'UserController::edit/$1', [
|
||||
'as' => 'user-edit',
|
||||
'filter' => 'permission:users-manage_authorizations',
|
||||
]);
|
||||
$routes->post('edit', 'UserController::attemptEdit/$1', [
|
||||
'filter' => 'permission:users-manage_authorizations',
|
||||
]);
|
||||
$routes->get('ban', 'UserController::ban/$1', [
|
||||
'as' => 'user-ban',
|
||||
'filter' => 'permission:users-manage_bans',
|
||||
]);
|
||||
$routes->get('unban', 'UserController::unBan/$1', [
|
||||
'as' => 'user-unban',
|
||||
'filter' => 'permission:users-manage_bans',
|
||||
]);
|
||||
$routes->get(
|
||||
'force-pass-reset',
|
||||
'UserController::forcePassReset/$1',
|
||||
[
|
||||
'as' => 'user-force_pass_reset',
|
||||
'filter' => 'permission:users-force_pass_reset',
|
||||
],
|
||||
);
|
||||
$routes->get('delete', 'UserController::delete/$1', [
|
||||
'as' => 'user-delete',
|
||||
'filter' => 'permission:users-delete',
|
||||
]);
|
||||
});
|
||||
});
|
||||
// My account
|
||||
$routes->group('my-account', static function ($routes): void {
|
||||
$routes->get('/', 'MyAccountController', [
|
||||
'as' => 'my-account',
|
||||
]);
|
||||
$routes->get(
|
||||
'change-password',
|
||||
'MyAccountController::changePassword/$1',
|
||||
[
|
||||
'as' => 'change-password',
|
||||
],
|
||||
);
|
||||
$routes->post('change-password', 'MyAccountController::attemptChange/$1');
|
||||
});
|
||||
},
|
||||
);
|
||||
|
|
|
@ -1,203 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace Modules\Admin\Controllers;
|
||||
|
||||
use App\Entities\Podcast;
|
||||
use App\Models\PodcastModel;
|
||||
use App\Models\UserModel;
|
||||
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use Exception;
|
||||
use Modules\Auth\Authorization\GroupModel;
|
||||
use Modules\Auth\Entities\User;
|
||||
|
||||
class ContributorController extends BaseController
|
||||
{
|
||||
protected Podcast $podcast;
|
||||
|
||||
protected ?User $user;
|
||||
|
||||
public function _remap(string $method, string ...$params): mixed
|
||||
{
|
||||
if ($params === []) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
if (($podcast = (new PodcastModel())->getPodcastById((int) $params[0])) === null) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
$this->podcast = $podcast;
|
||||
|
||||
if (count($params) <= 1) {
|
||||
return $this->{$method}();
|
||||
}
|
||||
|
||||
if (($this->user = (new UserModel())->getPodcastContributor((int) $params[1], (int) $params[0])) !== null) {
|
||||
return $this->{$method}();
|
||||
}
|
||||
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
public function list(): string
|
||||
{
|
||||
$data = [
|
||||
'podcast' => $this->podcast,
|
||||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->title,
|
||||
]);
|
||||
return view('contributor/list', $data);
|
||||
}
|
||||
|
||||
public function view(): string
|
||||
{
|
||||
$data = [
|
||||
'podcast' => $this->podcast,
|
||||
'contributor' => (new UserModel())->getPodcastContributor($this->user->id, $this->podcast->id),
|
||||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->title,
|
||||
1 => $this->user->username,
|
||||
]);
|
||||
return view('contributor/view', $data);
|
||||
}
|
||||
|
||||
public function add(): string
|
||||
{
|
||||
helper('form');
|
||||
|
||||
$users = (new UserModel())->findAll();
|
||||
$userOptions = array_reduce(
|
||||
$users,
|
||||
static function ($result, $user) {
|
||||
$result[$user->id] = $user->username;
|
||||
return $result;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
$roles = (new GroupModel())->getContributorRoles();
|
||||
$roleOptions = array_reduce(
|
||||
$roles,
|
||||
static function ($result, $role) {
|
||||
$result[$role->id] = lang('Contributor.roles.' . $role->name);
|
||||
return $result;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
$data = [
|
||||
'podcast' => $this->podcast,
|
||||
'userOptions' => $userOptions,
|
||||
'roleOptions' => $roleOptions,
|
||||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->title,
|
||||
]);
|
||||
return view('contributor/add', $data);
|
||||
}
|
||||
|
||||
public function attemptAdd(): RedirectResponse
|
||||
{
|
||||
try {
|
||||
(new PodcastModel())->addPodcastContributor(
|
||||
(int) $this->request->getPost('user'),
|
||||
$this->podcast->id,
|
||||
(int) $this->request->getPost('role'),
|
||||
);
|
||||
} catch (Exception) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', [lang('Contributor.messages.alreadyAddedError')]);
|
||||
}
|
||||
|
||||
return redirect()->route('contributor-list', [$this->podcast->id]);
|
||||
}
|
||||
|
||||
public function edit(): string
|
||||
{
|
||||
helper('form');
|
||||
|
||||
$roles = (new GroupModel())->getContributorRoles();
|
||||
$roleOptions = array_reduce(
|
||||
$roles,
|
||||
static function ($result, $role) {
|
||||
$result[$role->id] = lang('Contributor.roles.' . $role->name);
|
||||
return $result;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
$data = [
|
||||
'podcast' => $this->podcast,
|
||||
'user' => $this->user,
|
||||
'contributorGroupId' => (new PodcastModel())->getContributorGroupId(
|
||||
$this->user->id,
|
||||
$this->podcast->id,
|
||||
),
|
||||
'roleOptions' => $roleOptions,
|
||||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->title,
|
||||
1 => $this->user->username,
|
||||
]);
|
||||
return view('contributor/edit', $data);
|
||||
}
|
||||
|
||||
public function attemptEdit(): RedirectResponse
|
||||
{
|
||||
(new PodcastModel())->updatePodcastContributor(
|
||||
$this->user->id,
|
||||
$this->podcast->id,
|
||||
(int) $this->request->getPost('role'),
|
||||
);
|
||||
|
||||
return redirect()->route('contributor-edit', [$this->podcast->id, $this->user->id])->with(
|
||||
'message',
|
||||
lang('Contributor.messages.editSuccess')
|
||||
);
|
||||
}
|
||||
|
||||
public function remove(): RedirectResponse
|
||||
{
|
||||
if ($this->podcast->created_by === $this->user->id) {
|
||||
return redirect()
|
||||
->back()
|
||||
->with('errors', [lang('Contributor.messages.removeOwnerError')]);
|
||||
}
|
||||
|
||||
$podcastModel = new PodcastModel();
|
||||
if (
|
||||
! $podcastModel->removePodcastContributor($this->user->id, $this->podcast->id)
|
||||
) {
|
||||
return redirect()
|
||||
->back()
|
||||
->with('errors', $podcastModel->errors());
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->route('contributor-list', [$this->podcast->id])
|
||||
->with(
|
||||
'message',
|
||||
lang('Contributor.messages.removeSuccess', [
|
||||
'username' => $this->user->username,
|
||||
'podcastTitle' => $this->podcast->title,
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -95,7 +95,7 @@ class EpisodeController extends BaseController
|
|||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->title,
|
||||
0 => $this->podcast->at_handle,
|
||||
]);
|
||||
return view('episode/list', $data);
|
||||
}
|
||||
|
@ -108,7 +108,7 @@ class EpisodeController extends BaseController
|
|||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->title,
|
||||
0 => $this->podcast->at_handle,
|
||||
1 => $this->episode->title,
|
||||
]);
|
||||
return view('episode/view', $data);
|
||||
|
@ -125,7 +125,7 @@ class EpisodeController extends BaseController
|
|||
'nextEpisodeNumber' => (new EpisodeModel())->getNextEpisodeNumber($this->podcast->id, $currentSeasonNumber),
|
||||
];
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->title,
|
||||
0 => $this->podcast->at_handle,
|
||||
]);
|
||||
return view('episode/create', $data);
|
||||
}
|
||||
|
@ -261,7 +261,7 @@ class EpisodeController extends BaseController
|
|||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->title,
|
||||
0 => $this->podcast->at_handle,
|
||||
1 => $this->episode->title,
|
||||
]);
|
||||
return view('episode/edit', $data);
|
||||
|
@ -438,7 +438,7 @@ class EpisodeController extends BaseController
|
|||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->title,
|
||||
0 => $this->podcast->at_handle,
|
||||
1 => $this->episode->title,
|
||||
]);
|
||||
return view('episode/publish', $data);
|
||||
|
@ -551,7 +551,7 @@ class EpisodeController extends BaseController
|
|||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->title,
|
||||
0 => $this->podcast->at_handle,
|
||||
1 => $this->episode->title,
|
||||
]);
|
||||
return view('episode/publish_edit', $data);
|
||||
|
@ -851,7 +851,7 @@ class EpisodeController extends BaseController
|
|||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->title,
|
||||
0 => $this->podcast->at_handle,
|
||||
1 => $this->episode->title,
|
||||
]);
|
||||
return view('episode/delete', $data);
|
||||
|
@ -949,7 +949,7 @@ class EpisodeController extends BaseController
|
|||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->title,
|
||||
0 => $this->podcast->at_handle,
|
||||
1 => $this->episode->title,
|
||||
]);
|
||||
return view('episode/embed', $data);
|
||||
|
|
|
@ -59,7 +59,7 @@ class EpisodePersonController extends BaseController
|
|||
'taxonomyOptions' => (new PersonModel())->getTaxonomyOptions(),
|
||||
];
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->title,
|
||||
0 => $this->podcast->at_handle,
|
||||
1 => $this->episode->title,
|
||||
]);
|
||||
return view('episode/persons', $data);
|
||||
|
|
|
@ -67,7 +67,7 @@ class NotificationController extends BaseController
|
|||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->title,
|
||||
0 => $this->podcast->at_handle,
|
||||
]);
|
||||
|
||||
return view('podcast/notifications', $data);
|
||||
|
|
|
@ -23,7 +23,6 @@ use App\Models\PostModel;
|
|||
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use CodeIgniter\I18n\Time;
|
||||
use Config\Services;
|
||||
use Modules\Analytics\Models\AnalyticsPodcastByCountryModel;
|
||||
use Modules\Analytics\Models\AnalyticsPodcastByEpisodeModel;
|
||||
use Modules\Analytics\Models\AnalyticsPodcastByHourModel;
|
||||
|
@ -56,13 +55,13 @@ class PodcastController extends BaseController
|
|||
|
||||
public function list(): string
|
||||
{
|
||||
if (! has_permission('podcasts-list')) {
|
||||
if (auth()->user()->can('podcasts.view')) {
|
||||
$data = [
|
||||
'podcasts' => (new PodcastModel())->getUserPodcasts((int) user_id()),
|
||||
'podcasts' => (new PodcastModel())->findAll(),
|
||||
];
|
||||
} else {
|
||||
$data = [
|
||||
'podcasts' => (new PodcastModel())->findAll(),
|
||||
'podcasts' => get_user_podcasts(auth()->user()),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -76,7 +75,7 @@ class PodcastController extends BaseController
|
|||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->title,
|
||||
0 => $this->podcast->at_handle,
|
||||
]);
|
||||
return view('podcast/view', $data);
|
||||
}
|
||||
|
@ -88,7 +87,7 @@ class PodcastController extends BaseController
|
|||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->title,
|
||||
0 => $this->podcast->at_handle,
|
||||
]);
|
||||
return view('podcast/analytics/index', $data);
|
||||
}
|
||||
|
@ -100,7 +99,7 @@ class PodcastController extends BaseController
|
|||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->title,
|
||||
0 => $this->podcast->at_handle,
|
||||
]);
|
||||
return view('podcast/analytics/webpages', $data);
|
||||
}
|
||||
|
@ -112,7 +111,7 @@ class PodcastController extends BaseController
|
|||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->title,
|
||||
0 => $this->podcast->at_handle,
|
||||
]);
|
||||
return view('podcast/analytics/locations', $data);
|
||||
}
|
||||
|
@ -124,7 +123,7 @@ class PodcastController extends BaseController
|
|||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->title,
|
||||
0 => $this->podcast->at_handle,
|
||||
]);
|
||||
return view('podcast/analytics/unique_listeners', $data);
|
||||
}
|
||||
|
@ -136,7 +135,7 @@ class PodcastController extends BaseController
|
|||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->title,
|
||||
0 => $this->podcast->at_handle,
|
||||
]);
|
||||
return view('podcast/analytics/listening_time', $data);
|
||||
}
|
||||
|
@ -148,7 +147,7 @@ class PodcastController extends BaseController
|
|||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->title,
|
||||
0 => $this->podcast->at_handle,
|
||||
]);
|
||||
return view('podcast/analytics/time_periods', $data);
|
||||
}
|
||||
|
@ -160,7 +159,7 @@ class PodcastController extends BaseController
|
|||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->title,
|
||||
0 => $this->podcast->at_handle,
|
||||
]);
|
||||
return view('podcast/analytics/players', $data);
|
||||
}
|
||||
|
@ -253,10 +252,11 @@ class PodcastController extends BaseController
|
|||
->with('errors', $podcastModel->errors());
|
||||
}
|
||||
|
||||
$authorize = Services::authorization();
|
||||
$podcastAdminGroup = $authorize->group('podcast_admin');
|
||||
|
||||
$podcastModel->addPodcastContributor(user_id(), $newPodcastId, (int) $podcastAdminGroup->id);
|
||||
// generate podcast roles and permissions
|
||||
// before setting current user as podcast admin
|
||||
config('AuthGroups')
|
||||
->generatePodcastAuthorizations($newPodcastId);
|
||||
add_podcast_group(auth()->user(), (int) $newPodcastId, setting('AuthGroups.mostPowerfulPodcastGroup'));
|
||||
|
||||
// set Podcast categories
|
||||
(new CategoryModel())->setPodcastCategories(
|
||||
|
@ -264,10 +264,6 @@ class PodcastController extends BaseController
|
|||
$this->request->getPost('other_categories') ?? [],
|
||||
);
|
||||
|
||||
// set interact as the newly created podcast actor
|
||||
$createdPodcast = (new PodcastModel())->getPodcastById($newPodcastId);
|
||||
set_interact_as_actor($createdPodcast->actor_id);
|
||||
|
||||
$db->transComplete();
|
||||
|
||||
return redirect()->route('podcast-view', [$newPodcastId])->with(
|
||||
|
@ -290,7 +286,7 @@ class PodcastController extends BaseController
|
|||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->title,
|
||||
0 => $this->podcast->at_handle,
|
||||
]);
|
||||
return view('podcast/edit', $data);
|
||||
}
|
||||
|
@ -444,7 +440,7 @@ class PodcastController extends BaseController
|
|||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->title,
|
||||
0 => $this->podcast->at_handle,
|
||||
]);
|
||||
return view('podcast/delete', $data);
|
||||
}
|
||||
|
@ -576,15 +572,6 @@ class PodcastController extends BaseController
|
|||
}
|
||||
}
|
||||
|
||||
if ($this->podcast->actor_id === interact_as_actor_id()) {
|
||||
//set interact to the most recently created podcast actor
|
||||
$mostRecentPodcast = (new PodcastModel())->orderBy('created_at', 'desc')
|
||||
->first();
|
||||
if ($mostRecentPodcast !== null) {
|
||||
set_interact_as_actor($mostRecentPodcast->actor_id);
|
||||
}
|
||||
}
|
||||
|
||||
$db->transComplete();
|
||||
|
||||
//delete podcast media files and folder
|
||||
|
@ -620,7 +607,7 @@ class PodcastController extends BaseController
|
|||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->title,
|
||||
0 => $this->podcast->at_handle,
|
||||
]);
|
||||
|
||||
return view('podcast/publish', $data);
|
||||
|
@ -754,7 +741,7 @@ class PodcastController extends BaseController
|
|||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->title,
|
||||
0 => $this->podcast->at_handle,
|
||||
]);
|
||||
|
||||
return view('podcast/publish_edit', $data);
|
||||
|
|
|
@ -23,7 +23,6 @@ use App\Models\PlatformModel;
|
|||
use App\Models\PodcastModel;
|
||||
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use Config\Services;
|
||||
use ErrorException;
|
||||
use League\HTMLToMarkdown\HtmlConverter;
|
||||
|
||||
|
@ -201,10 +200,11 @@ class PodcastImportController extends BaseController
|
|||
->with('errors', $podcastModel->errors());
|
||||
}
|
||||
|
||||
$authorize = Services::authorization();
|
||||
$podcastAdminGroup = $authorize->group('podcast_admin');
|
||||
|
||||
$podcastModel->addPodcastContributor(user_id(), $newPodcastId, (int) $podcastAdminGroup->id);
|
||||
// set current user as podcast admin
|
||||
// 1. create new group
|
||||
config('AuthGroups')
|
||||
->generatePodcastAuthorizations($newPodcastId);
|
||||
add_podcast_group(auth()->user(), $newPodcastId, 'admin');
|
||||
|
||||
$podcastsPlatformsData = [];
|
||||
$platformTypes = [
|
||||
|
@ -460,9 +460,7 @@ class PodcastImportController extends BaseController
|
|||
}
|
||||
}
|
||||
|
||||
// set interact as the newly imported podcast actor
|
||||
$importedPodcast = (new PodcastModel())->getPodcastById($newPodcastId);
|
||||
set_interact_as_actor($importedPodcast->actor_id);
|
||||
|
||||
// set podcast publication date
|
||||
$importedPodcast->published_at = $firstEpisodePublicationDate ?? $importedPodcast->created_at;
|
||||
|
|
|
@ -47,7 +47,7 @@ class PodcastPersonController extends BaseController
|
|||
'taxonomyOptions' => (new PersonModel())->getTaxonomyOptions(),
|
||||
];
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->title,
|
||||
0 => $this->podcast->at_handle,
|
||||
]);
|
||||
return view('podcast/persons', $data);
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ class PodcastPlatformController extends BaseController
|
|||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->title,
|
||||
0 => $this->podcast->at_handle,
|
||||
]);
|
||||
|
||||
return view('podcast/platforms', $data);
|
||||
|
|
|
@ -77,7 +77,7 @@ class SoundbiteController extends BaseController
|
|||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->title,
|
||||
0 => $this->podcast->at_handle,
|
||||
1 => $this->episode->title,
|
||||
]);
|
||||
return view('episode/soundbites_list', $data);
|
||||
|
@ -93,7 +93,7 @@ class SoundbiteController extends BaseController
|
|||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->title,
|
||||
0 => $this->podcast->at_handle,
|
||||
1 => $this->episode->title,
|
||||
]);
|
||||
return view('episode/soundbites_new', $data);
|
||||
|
|
|
@ -1,258 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace Modules\Admin\Controllers;
|
||||
|
||||
use App\Models\UserModel;
|
||||
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use Config\Services;
|
||||
use Modules\Auth\Authorization\GroupModel;
|
||||
use Modules\Auth\Entities\User;
|
||||
|
||||
class UserController extends BaseController
|
||||
{
|
||||
protected ?User $user;
|
||||
|
||||
public function _remap(string $method, string ...$params): mixed
|
||||
{
|
||||
if ($params === []) {
|
||||
return $this->{$method}();
|
||||
}
|
||||
|
||||
if ($this->user = (new UserModel())->find($params[0])) {
|
||||
return $this->{$method}();
|
||||
}
|
||||
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
public function list(): string
|
||||
{
|
||||
$data = [
|
||||
'users' => (new UserModel())->findAll(),
|
||||
];
|
||||
|
||||
return view('user/list', $data);
|
||||
}
|
||||
|
||||
public function view(): string
|
||||
{
|
||||
$data = [
|
||||
'user' => $this->user,
|
||||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->user->username,
|
||||
]);
|
||||
return view('user/view', $data);
|
||||
}
|
||||
|
||||
public function create(): string
|
||||
{
|
||||
helper('form');
|
||||
|
||||
$data = [
|
||||
'roles' => (new GroupModel())->getUserRoles(),
|
||||
];
|
||||
|
||||
return view('user/create', $data);
|
||||
}
|
||||
|
||||
public function attemptCreate(): RedirectResponse
|
||||
{
|
||||
$userModel = new UserModel();
|
||||
|
||||
// Validate here first, since some things,
|
||||
// like the password, can only be validated properly here.
|
||||
$rules = array_merge(
|
||||
$userModel->getValidationRules([
|
||||
'only' => ['username'],
|
||||
]),
|
||||
[
|
||||
'email' => 'required|valid_email|is_unique[users.email]',
|
||||
'password' => 'required|strong_password',
|
||||
],
|
||||
);
|
||||
|
||||
if (! $this->validate($rules)) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', $this->validator->getErrors());
|
||||
}
|
||||
|
||||
// Save the user
|
||||
$user = new User($this->request->getPost());
|
||||
|
||||
// Activate user
|
||||
$user->activate();
|
||||
|
||||
// Force user to reset his password on first connection
|
||||
$user->forcePasswordReset();
|
||||
|
||||
if (! $userModel->insert($user)) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', $userModel->errors());
|
||||
}
|
||||
|
||||
// Success!
|
||||
return redirect()
|
||||
->route('user-list')
|
||||
->with('message', lang('User.messages.createSuccess', [
|
||||
'username' => $user->username,
|
||||
]));
|
||||
}
|
||||
|
||||
public function edit(): string
|
||||
{
|
||||
helper('form');
|
||||
|
||||
$roles = (new GroupModel())->getUserRoles();
|
||||
$roleOptions = array_reduce(
|
||||
$roles,
|
||||
static function ($result, $role) {
|
||||
$result[$role->name] = lang('User.roles.' . $role->name);
|
||||
return $result;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
$data = [
|
||||
'user' => $this->user,
|
||||
'roleOptions' => $roleOptions,
|
||||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->user->username,
|
||||
]);
|
||||
return view('user/edit', $data);
|
||||
}
|
||||
|
||||
public function attemptEdit(): RedirectResponse
|
||||
{
|
||||
$authorize = Services::authorization();
|
||||
|
||||
$roles = $this->request->getPost('roles');
|
||||
|
||||
if ($this->user->isOwner) {
|
||||
return redirect()
|
||||
->back()
|
||||
->with('errors', [
|
||||
lang('User.messages.editOwnerError', [
|
||||
'username' => $this->user->username,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
$authorize->setUserGroups($this->user->id, $roles ?? []);
|
||||
|
||||
// Success!
|
||||
return redirect()
|
||||
->route('user-list')
|
||||
->with('message', lang('User.messages.rolesEditSuccess', [
|
||||
'username' => $this->user->username,
|
||||
]));
|
||||
}
|
||||
|
||||
public function forcePassReset(): RedirectResponse
|
||||
{
|
||||
$userModel = new UserModel();
|
||||
$this->user->forcePasswordReset();
|
||||
|
||||
if (! $userModel->update($this->user->id, $this->user)) {
|
||||
return redirect()
|
||||
->back()
|
||||
->with('errors', $userModel->errors());
|
||||
}
|
||||
|
||||
// Success!
|
||||
return redirect()
|
||||
->route('user-list')
|
||||
->with(
|
||||
'message',
|
||||
lang('User.messages.forcePassResetSuccess', [
|
||||
'username' => $this->user->username,
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
public function ban(): RedirectResponse
|
||||
{
|
||||
$authorize = Services::authorization();
|
||||
if ($authorize->inGroup('superadmin', $this->user->id)) {
|
||||
return redirect()
|
||||
->back()
|
||||
->with('errors', [
|
||||
lang('User.messages.banSuperAdminError', [
|
||||
'username' => $this->user->username,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
$userModel = new UserModel();
|
||||
// TODO: add ban reason?
|
||||
$this->user->ban('');
|
||||
|
||||
if (! $userModel->update($this->user->id, $this->user)) {
|
||||
return redirect()
|
||||
->back()
|
||||
->with('errors', $userModel->errors());
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->route('user-list')
|
||||
->with('message', lang('User.messages.banSuccess', [
|
||||
'username' => $this->user->username,
|
||||
]));
|
||||
}
|
||||
|
||||
public function unBan(): RedirectResponse
|
||||
{
|
||||
$userModel = new UserModel();
|
||||
$this->user->unBan();
|
||||
|
||||
if (! $userModel->update($this->user->id, $this->user)) {
|
||||
return redirect()
|
||||
->back()
|
||||
->with('errors', $userModel->errors());
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->route('user-list')
|
||||
->with('message', lang('User.messages.unbanSuccess', [
|
||||
'username' => $this->user->username,
|
||||
]));
|
||||
}
|
||||
|
||||
public function delete(): RedirectResponse
|
||||
{
|
||||
$authorize = Services::authorization();
|
||||
if ($authorize->inGroup('superadmin', $this->user->id)) {
|
||||
return redirect()
|
||||
->back()
|
||||
->with('errors', [
|
||||
lang('User.messages.deleteSuperAdminError', [
|
||||
'username' => $this->user->username,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
(new UserModel())->delete($this->user->id);
|
||||
|
||||
return redirect()
|
||||
->back()
|
||||
->with('message', lang('User.messages.deleteSuccess', [
|
||||
'username' => $this->user->username,
|
||||
]));
|
||||
}
|
||||
}
|
|
@ -82,7 +82,7 @@ class VideoClipsController extends BaseController
|
|||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->title,
|
||||
0 => $this->podcast->at_handle,
|
||||
1 => $this->episode->title,
|
||||
]);
|
||||
return view('episode/video_clips_list', $data);
|
||||
|
@ -99,7 +99,7 @@ class VideoClipsController extends BaseController
|
|||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->title,
|
||||
0 => $this->podcast->at_handle,
|
||||
1 => $this->episode->title,
|
||||
2 => $videoClip->title,
|
||||
]);
|
||||
|
@ -114,7 +114,7 @@ class VideoClipsController extends BaseController
|
|||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->title,
|
||||
0 => $this->podcast->at_handle,
|
||||
1 => $this->episode->title,
|
||||
]);
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ return [
|
|||
'publish-date-edit' => 'edit publication date',
|
||||
'unpublish' => 'unpublish',
|
||||
'delete' => 'delete',
|
||||
'remove' => 'remove',
|
||||
'fediverse' => 'fediverse',
|
||||
'block-lists' => 'block lists',
|
||||
'users' => 'users',
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
return [
|
||||
'edit_roles' => "Edit {username}'s roles",
|
||||
'forcePassReset' => 'Force pass reset',
|
||||
'ban' => 'Ban',
|
||||
'unban' => 'Unban',
|
||||
'delete' => 'Delete',
|
||||
'create' => 'New user',
|
||||
'view' => "{username}'s info",
|
||||
'all_users' => 'All users',
|
||||
'list' => [
|
||||
'user' => 'User',
|
||||
'roles' => 'Roles',
|
||||
'banned' => 'Banned?',
|
||||
],
|
||||
'form' => [
|
||||
'email' => 'Email',
|
||||
'username' => 'Username',
|
||||
'password' => 'Password',
|
||||
'new_password' => 'New Password',
|
||||
'roles' => 'Roles',
|
||||
'permissions' => 'Permissions',
|
||||
'submit_create' => 'Create user',
|
||||
'submit_edit' => 'Save',
|
||||
'submit_password_change' => 'Change!',
|
||||
],
|
||||
'roles' => [
|
||||
'superadmin' => 'Super admin',
|
||||
],
|
||||
'messages' => [
|
||||
'createSuccess' =>
|
||||
'User created successfully! {username} will be prompted with a password reset upon first authentication.',
|
||||
'rolesEditSuccess' =>
|
||||
"{username}'s roles have been successfully updated.",
|
||||
'forcePassResetSuccess' =>
|
||||
'{username} will be prompted with a password reset upon next visit.',
|
||||
'banSuccess' => '{username} has been banned.',
|
||||
'unbanSuccess' => '{username} has been unbanned.',
|
||||
'editOwnerError' =>
|
||||
'{username} is the instance owner, you cannot edit its roles.',
|
||||
'banSuperAdminError' =>
|
||||
'{username} is a superadmin, one does not simply ban a superadmin…',
|
||||
'deleteSuperAdminError' =>
|
||||
'{username} is a superadmin, one does not simply delete a superadmin…',
|
||||
'deleteSuccess' => '{username} has been deleted.',
|
||||
],
|
||||
];
|
|
@ -1,56 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
return [
|
||||
'edit_roles' => "Edit {username}'s roles",
|
||||
'forcePassReset' => 'Force pass reset',
|
||||
'ban' => 'Ban',
|
||||
'unban' => 'Unban',
|
||||
'delete' => 'Delete',
|
||||
'create' => 'New user',
|
||||
'view' => "{username}'s info",
|
||||
'all_users' => 'All users',
|
||||
'list' => [
|
||||
'user' => 'User',
|
||||
'roles' => 'Roles',
|
||||
'banned' => 'Banned?',
|
||||
],
|
||||
'form' => [
|
||||
'email' => 'Email',
|
||||
'username' => 'Username',
|
||||
'password' => 'Password',
|
||||
'new_password' => 'New Password',
|
||||
'roles' => 'Roles',
|
||||
'permissions' => 'Permissions',
|
||||
'submit_create' => 'Create user',
|
||||
'submit_edit' => 'Save',
|
||||
'submit_password_change' => 'Change!',
|
||||
],
|
||||
'roles' => [
|
||||
'superadmin' => 'Super admin',
|
||||
],
|
||||
'messages' => [
|
||||
'createSuccess' =>
|
||||
'User created successfully! {username} will be prompted with a password reset upon first authentication.',
|
||||
'rolesEditSuccess' =>
|
||||
"{username}'s roles have been successfully updated.",
|
||||
'forcePassResetSuccess' =>
|
||||
'{username} will be prompted with a password reset upon next visit.',
|
||||
'banSuccess' => '{username} has been banned.',
|
||||
'unbanSuccess' => '{username} has been unbanned.',
|
||||
'editOwnerError' =>
|
||||
'{username} is the instance owner, you cannot edit its roles.',
|
||||
'banSuperAdminError' =>
|
||||
'{username} is a superadmin, one does not simply ban a superadmin…',
|
||||
'deleteSuperAdminError' =>
|
||||
'{username} is a superadmin, one does not simply delete a superadmin…',
|
||||
'deleteSuccess' => '{username} has been deleted.',
|
||||
],
|
||||
];
|
|
@ -1,56 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
return [
|
||||
'edit_roles' => "Edit {username}'s roles",
|
||||
'forcePassReset' => 'Force pass reset',
|
||||
'ban' => 'Ban',
|
||||
'unban' => 'Unban',
|
||||
'delete' => 'Delete',
|
||||
'create' => 'New user',
|
||||
'view' => "{username}'s info",
|
||||
'all_users' => 'All users',
|
||||
'list' => [
|
||||
'user' => 'User',
|
||||
'roles' => 'Roles',
|
||||
'banned' => 'Banned?',
|
||||
],
|
||||
'form' => [
|
||||
'email' => 'Email',
|
||||
'username' => 'Username',
|
||||
'password' => 'Password',
|
||||
'new_password' => 'New Password',
|
||||
'roles' => 'Roles',
|
||||
'permissions' => 'Permissions',
|
||||
'submit_create' => 'Create user',
|
||||
'submit_edit' => 'Save',
|
||||
'submit_password_change' => 'Change!',
|
||||
],
|
||||
'roles' => [
|
||||
'superadmin' => 'Super admin',
|
||||
],
|
||||
'messages' => [
|
||||
'createSuccess' =>
|
||||
'User created successfully! {username} will be prompted with a password reset upon first authentication.',
|
||||
'rolesEditSuccess' =>
|
||||
"{username}'s roles have been successfully updated.",
|
||||
'forcePassResetSuccess' =>
|
||||
'{username} will be prompted with a password reset upon next visit.',
|
||||
'banSuccess' => '{username} has been banned.',
|
||||
'unbanSuccess' => '{username} has been unbanned.',
|
||||
'editOwnerError' =>
|
||||
'{username} is the instance owner, you cannot edit its roles.',
|
||||
'banSuperAdminError' =>
|
||||
'{username} is a superadmin, one does not simply ban a superadmin…',
|
||||
'deleteSuperAdminError' =>
|
||||
'{username} is a superadmin, one does not simply delete a superadmin…',
|
||||
'deleteSuccess' => '{username} has been deleted.',
|
||||
],
|
||||
];
|
|
@ -1,56 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
return [
|
||||
'edit_roles' => "Edit {username}'s roles",
|
||||
'forcePassReset' => 'Force pass reset',
|
||||
'ban' => 'Ban',
|
||||
'unban' => 'Unban',
|
||||
'delete' => 'Delete',
|
||||
'create' => 'New user',
|
||||
'view' => "{username}'s info",
|
||||
'all_users' => 'All users',
|
||||
'list' => [
|
||||
'user' => 'User',
|
||||
'roles' => 'Roles',
|
||||
'banned' => 'Banned?',
|
||||
],
|
||||
'form' => [
|
||||
'email' => 'Email',
|
||||
'username' => 'Username',
|
||||
'password' => 'Password',
|
||||
'new_password' => 'New Password',
|
||||
'roles' => 'Roles',
|
||||
'permissions' => 'Permissions',
|
||||
'submit_create' => 'Create user',
|
||||
'submit_edit' => 'Save',
|
||||
'submit_password_change' => 'Change!',
|
||||
],
|
||||
'roles' => [
|
||||
'superadmin' => 'Super admin',
|
||||
],
|
||||
'messages' => [
|
||||
'createSuccess' =>
|
||||
'User created successfully! {username} will be prompted with a password reset upon first authentication.',
|
||||
'rolesEditSuccess' =>
|
||||
"{username}'s roles have been successfully updated.",
|
||||
'forcePassResetSuccess' =>
|
||||
'{username} will be prompted with a password reset upon next visit.',
|
||||
'banSuccess' => '{username} has been banned.',
|
||||
'unbanSuccess' => '{username} has been unbanned.',
|
||||
'editOwnerError' =>
|
||||
'{username} is the instance owner, you cannot edit its roles.',
|
||||
'banSuperAdminError' =>
|
||||
'{username} is a superadmin, one does not simply ban a superadmin…',
|
||||
'deleteSuperAdminError' =>
|
||||
'{username} is a superadmin, one does not simply delete a superadmin…',
|
||||
'deleteSuccess' => '{username} has been deleted.',
|
||||
],
|
||||
];
|
|
@ -1,56 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
return [
|
||||
'edit_roles' => "Edit {username}'s roles",
|
||||
'forcePassReset' => 'Force pass reset',
|
||||
'ban' => 'Ban',
|
||||
'unban' => 'Unban',
|
||||
'delete' => 'Delete',
|
||||
'create' => 'New user',
|
||||
'view' => "{username}'s info",
|
||||
'all_users' => 'All users',
|
||||
'list' => [
|
||||
'user' => 'User',
|
||||
'roles' => 'Roles',
|
||||
'banned' => 'Banned?',
|
||||
],
|
||||
'form' => [
|
||||
'email' => 'Email',
|
||||
'username' => 'Username',
|
||||
'password' => 'Password',
|
||||
'new_password' => 'New Password',
|
||||
'roles' => 'Roles',
|
||||
'permissions' => 'Permissions',
|
||||
'submit_create' => 'Create user',
|
||||
'submit_edit' => 'Save',
|
||||
'submit_password_change' => 'Change!',
|
||||
],
|
||||
'roles' => [
|
||||
'superadmin' => 'Super admin',
|
||||
],
|
||||
'messages' => [
|
||||
'createSuccess' =>
|
||||
'User created successfully! {username} will be prompted with a password reset upon first authentication.',
|
||||
'rolesEditSuccess' =>
|
||||
"{username}'s roles have been successfully updated.",
|
||||
'forcePassResetSuccess' =>
|
||||
'{username} will be prompted with a password reset upon next visit.',
|
||||
'banSuccess' => '{username} has been banned.',
|
||||
'unbanSuccess' => '{username} has been unbanned.',
|
||||
'editOwnerError' =>
|
||||
'{username} is the instance owner, you cannot edit its roles.',
|
||||
'banSuperAdminError' =>
|
||||
'{username} is a superadmin, one does not simply ban a superadmin…',
|
||||
'deleteSuperAdminError' =>
|
||||
'{username} is a superadmin, one does not simply delete a superadmin…',
|
||||
'deleteSuccess' => '{username} has been deleted.',
|
||||
],
|
||||
];
|
|
@ -1,56 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
return [
|
||||
'edit_roles' => "Edit {username}'s roles",
|
||||
'forcePassReset' => 'Force pass reset',
|
||||
'ban' => 'Ban',
|
||||
'unban' => 'Unban',
|
||||
'delete' => 'Delete',
|
||||
'create' => 'New user',
|
||||
'view' => "{username}'s info",
|
||||
'all_users' => 'All users',
|
||||
'list' => [
|
||||
'user' => 'User',
|
||||
'roles' => 'Roles',
|
||||
'banned' => 'Banned?',
|
||||
],
|
||||
'form' => [
|
||||
'email' => 'Email',
|
||||
'username' => 'Username',
|
||||
'password' => 'Password',
|
||||
'new_password' => 'New Password',
|
||||
'roles' => 'Roles',
|
||||
'permissions' => 'Permissions',
|
||||
'submit_create' => 'Create user',
|
||||
'submit_edit' => 'Save',
|
||||
'submit_password_change' => 'Change!',
|
||||
],
|
||||
'roles' => [
|
||||
'superadmin' => 'Super admin',
|
||||
],
|
||||
'messages' => [
|
||||
'createSuccess' =>
|
||||
'User created successfully! {username} will be prompted with a password reset upon first authentication.',
|
||||
'rolesEditSuccess' =>
|
||||
"{username}'s roles have been successfully updated.",
|
||||
'forcePassResetSuccess' =>
|
||||
'{username} will be prompted with a password reset upon next visit.',
|
||||
'banSuccess' => '{username} has been banned.',
|
||||
'unbanSuccess' => '{username} has been unbanned.',
|
||||
'editOwnerError' =>
|
||||
'{username} is the instance owner, you cannot edit its roles.',
|
||||
'banSuperAdminError' =>
|
||||
'{username} is a superadmin, one does not simply ban a superadmin…',
|
||||
'deleteSuperAdminError' =>
|
||||
'{username} is a superadmin, one does not simply delete a superadmin…',
|
||||
'deleteSuccess' => '{username} has been deleted.',
|
||||
],
|
||||
];
|
|
@ -1,56 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
return [
|
||||
'edit_roles' => "Edit {username}'s roles",
|
||||
'forcePassReset' => 'Force pass reset',
|
||||
'ban' => 'Ban',
|
||||
'unban' => 'Unban',
|
||||
'delete' => 'Delete',
|
||||
'create' => 'New user',
|
||||
'view' => "{username}'s info",
|
||||
'all_users' => 'All users',
|
||||
'list' => [
|
||||
'user' => 'User',
|
||||
'roles' => 'Roles',
|
||||
'banned' => 'Banned?',
|
||||
],
|
||||
'form' => [
|
||||
'email' => 'Email',
|
||||
'username' => 'Username',
|
||||
'password' => 'Password',
|
||||
'new_password' => 'New Password',
|
||||
'roles' => 'Roles',
|
||||
'permissions' => 'Permissions',
|
||||
'submit_create' => 'Create user',
|
||||
'submit_edit' => 'Save',
|
||||
'submit_password_change' => 'Change!',
|
||||
],
|
||||
'roles' => [
|
||||
'superadmin' => 'Super admin',
|
||||
],
|
||||
'messages' => [
|
||||
'createSuccess' =>
|
||||
'User created successfully! {username} will be prompted with a password reset upon first authentication.',
|
||||
'rolesEditSuccess' =>
|
||||
"{username}'s roles have been successfully updated.",
|
||||
'forcePassResetSuccess' =>
|
||||
'{username} will be prompted with a password reset upon next visit.',
|
||||
'banSuccess' => '{username} has been banned.',
|
||||
'unbanSuccess' => '{username} has been unbanned.',
|
||||
'editOwnerError' =>
|
||||
'{username} is the instance owner, you cannot edit its roles.',
|
||||
'banSuperAdminError' =>
|
||||
'{username} is a superadmin, one does not simply ban a superadmin…',
|
||||
'deleteSuperAdminError' =>
|
||||
'{username} is a superadmin, one does not simply delete a superadmin…',
|
||||
'deleteSuccess' => '{username} has been deleted.',
|
||||
],
|
||||
];
|
|
@ -1,56 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
return [
|
||||
'edit_roles' => "Edit {username}'s roles",
|
||||
'forcePassReset' => 'Force pass reset',
|
||||
'ban' => 'Ban',
|
||||
'unban' => 'Unban',
|
||||
'delete' => 'Delete',
|
||||
'create' => 'New user',
|
||||
'view' => "{username}'s info",
|
||||
'all_users' => 'All users',
|
||||
'list' => [
|
||||
'user' => 'User',
|
||||
'roles' => 'Roles',
|
||||
'banned' => 'Banned?',
|
||||
],
|
||||
'form' => [
|
||||
'email' => 'Email',
|
||||
'username' => 'Username',
|
||||
'password' => 'Password',
|
||||
'new_password' => 'New Password',
|
||||
'roles' => 'Roles',
|
||||
'permissions' => 'Permissions',
|
||||
'submit_create' => 'Create user',
|
||||
'submit_edit' => 'Save',
|
||||
'submit_password_change' => 'Change!',
|
||||
],
|
||||
'roles' => [
|
||||
'superadmin' => 'Super admin',
|
||||
],
|
||||
'messages' => [
|
||||
'createSuccess' =>
|
||||
'User created successfully! {username} will be prompted with a password reset upon first authentication.',
|
||||
'rolesEditSuccess' =>
|
||||
"{username}'s roles have been successfully updated.",
|
||||
'forcePassResetSuccess' =>
|
||||
'{username} will be prompted with a password reset upon next visit.',
|
||||
'banSuccess' => '{username} has been banned.',
|
||||
'unbanSuccess' => '{username} has been unbanned.',
|
||||
'editOwnerError' =>
|
||||
'{username} is the instance owner, you cannot edit its roles.',
|
||||
'banSuperAdminError' =>
|
||||
'{username} is a superadmin, one does not simply ban a superadmin…',
|
||||
'deleteSuperAdminError' =>
|
||||
'{username} is a superadmin, one does not simply delete a superadmin…',
|
||||
'deleteSuccess' => '{username} has been deleted.',
|
||||
],
|
||||
];
|
|
@ -20,9 +20,9 @@ class Analytics extends BaseConfig
|
|||
* @var array<string, string>
|
||||
*/
|
||||
public array $routeFilters = [
|
||||
'analytics-full-data' => 'permission:podcasts-view,podcast-view',
|
||||
'analytics-data' => 'permission:podcasts-view,podcast-view',
|
||||
'analytics-filtered-data' => 'permission:podcasts-view,podcast-view',
|
||||
'analytics-full-data' => 'permission:podcast#.view',
|
||||
'analytics-data' => 'permission:podcast#.view',
|
||||
'analytics-filtered-data' => 'permission:podcast#.view',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Auth;
|
||||
|
||||
use CodeIgniter\Router\RouteCollection;
|
||||
use CodeIgniter\Shield\Auth as ShieldAuth;
|
||||
|
||||
class Auth extends ShieldAuth
|
||||
{
|
||||
/**
|
||||
* Will set the routes in your application to use
|
||||
* the Shield auth routes.
|
||||
*
|
||||
* Usage (in Config/Routes.php):
|
||||
* - auth()->routes($routes);
|
||||
* - auth()->routes($routes, ['except' => ['login', 'register']])
|
||||
*/
|
||||
public function routes(RouteCollection &$routes, array $config = []): void
|
||||
{
|
||||
$authRoutes = config('AuthRoutes')
|
||||
->routes;
|
||||
|
||||
$routes->group(config('Auth')->gateway, [
|
||||
'namespace' => 'Modules\Auth\Controllers',
|
||||
], static function (RouteCollection $routes) use ($authRoutes, $config): void {
|
||||
foreach ($authRoutes as $name => $row) {
|
||||
if (! isset($config['except']) || ! in_array($name, $config['except'], true)) {
|
||||
foreach ($row as $params) {
|
||||
$options = isset($params[3])
|
||||
? [
|
||||
'as' => $params[3],
|
||||
]
|
||||
: null;
|
||||
$routes->{$params[0]}($params[1], $params[2], $options);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Auth\Authorization;
|
||||
|
||||
use Myth\Auth\Authorization\FlatAuthorization as MythAuthFlatAuthorization;
|
||||
|
||||
class FlatAuthorization extends MythAuthFlatAuthorization
|
||||
{
|
||||
/**
|
||||
* The group model to use. Usually the class noted below (or an extension thereof) but can be any compatible
|
||||
* CodeIgniter Model.
|
||||
*
|
||||
* @var PermissionModel
|
||||
*/
|
||||
protected $permissionModel;
|
||||
|
||||
/**
|
||||
* Checks a group to see if they have the specified permission.
|
||||
*/
|
||||
public function groupHasPermission(int | string $permission, int $groupId): bool
|
||||
{
|
||||
// Get the Permission ID
|
||||
$permissionId = $this->getPermissionID($permission);
|
||||
|
||||
if (! is_numeric($permissionId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->permissionModel->doesGroupHavePermission($groupId, $permissionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes user part of given groups.
|
||||
*
|
||||
* @param array<string, string> $groups Either collection of ID or names
|
||||
*/
|
||||
public function setUserGroups(int $userId, array $groups = []): bool
|
||||
{
|
||||
// remove user from all groups before resetting it in new groups
|
||||
$this->groupModel->removeUserFromAllGroups($userId);
|
||||
|
||||
if ($groups === []) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($groups as $group) {
|
||||
$this->addUserToGroup($userId, $group);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Auth\Authorization;
|
||||
|
||||
use Myth\Auth\Authorization\GroupModel as MythAuthGroupModel;
|
||||
|
||||
class GroupModel extends MythAuthGroupModel
|
||||
{
|
||||
/**
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function getContributorRoles(): array
|
||||
{
|
||||
return $this->select('auth_groups.*')
|
||||
->like('name', 'podcast_', 'after')
|
||||
->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function getUserRoles(): array
|
||||
{
|
||||
return $this->select('auth_groups.*')
|
||||
->notLike('name', 'podcast_', 'after')
|
||||
->findAll();
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Auth\Authorization;
|
||||
|
||||
use Myth\Auth\Authorization\PermissionModel as MythAuthPermissionModel;
|
||||
|
||||
class PermissionModel extends MythAuthPermissionModel
|
||||
{
|
||||
/**
|
||||
* Checks to see if a user, or one of their groups, has a specific permission.
|
||||
*/
|
||||
public function doesGroupHavePermission(int $groupId, int $permissionId): bool
|
||||
{
|
||||
// Check group permissions and take advantage of caching
|
||||
$groupPerms = $this->getPermissionsForGroup($groupId);
|
||||
|
||||
return count($groupPerms) &&
|
||||
array_key_exists($permissionId, $groupPerms);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all permissions for a group in a way that can be easily used to check against:
|
||||
*
|
||||
* [ id => name, id => name ]
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function getPermissionsForGroup(int $groupId): array
|
||||
{
|
||||
$cacheName = "group{$groupId}_permissions";
|
||||
if (! ($found = cache($cacheName))) {
|
||||
$groupPermissions = $this->db
|
||||
->table('auth_groups_permissions')
|
||||
->select('id, auth_permissions.name')
|
||||
->join('auth_permissions', 'auth_permissions.id = permission_id', 'inner')
|
||||
->where('group_id', $groupId)
|
||||
->get()
|
||||
->getResultObject();
|
||||
|
||||
$found = [];
|
||||
foreach ($groupPermissions as $row) {
|
||||
$found[$row->id] = strtolower($row->name);
|
||||
}
|
||||
|
||||
cache()
|
||||
->save($cacheName, $found, 300);
|
||||
}
|
||||
|
||||
return $found;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,184 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Auth\Commands;
|
||||
|
||||
use Closure;
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
use CodeIgniter\View\Table;
|
||||
use Config\Services;
|
||||
use League\HTMLToMarkdown\Converter\TableConverter;
|
||||
use League\HTMLToMarkdown\HtmlConverter;
|
||||
use Modules\Auth\Config\AuthGroups;
|
||||
|
||||
class RolesDoc extends BaseCommand
|
||||
{
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private const COMMENT_BLOCK_IDS = [
|
||||
'instance_roles' => 'AUTH-INSTANCE-ROLES-LIST',
|
||||
'instance_permissions' => 'AUTH-INSTANCE-PERMISSIONS-LIST',
|
||||
'podcast_roles' => 'AUTH-PODCAST-ROLES-LIST',
|
||||
'podcast_permissions' => 'AUTH-PODCAST-PERMISSIONS-LIST',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $group = 'auth';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'auth:generate-doc';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Generates the html table references for roles and permissions in the docs.';
|
||||
|
||||
public function run(array $params): void
|
||||
{
|
||||
// loop over all files in path
|
||||
$defaultFile = glob(ROOTPATH . 'docs/src/getting-started/auth.md');
|
||||
$localizedFiles = glob(ROOTPATH . 'docs/src/**/getting-started/auth.md') ?? [];
|
||||
$files = array_merge($defaultFile, $localizedFiles);
|
||||
CLI::write(implode(', ', $files));
|
||||
|
||||
if ($files === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($files as $file) {
|
||||
$locale = $this->detectLocaleFromPath($file);
|
||||
$language = Services::language();
|
||||
$language->setLocale($locale);
|
||||
|
||||
$authGroups = new AuthGroups();
|
||||
|
||||
$fileContents = file_get_contents($file);
|
||||
|
||||
foreach (self::COMMENT_BLOCK_IDS as $key => $block_id) {
|
||||
$pattern = '/(<!--\s' . $block_id . ':START.*-->)[\S\s]*(<!--\s' . $block_id . ':END.*-->)/';
|
||||
|
||||
$handleInjectMethod = 'handle' . str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $key)));
|
||||
|
||||
$fileContents = $this->{$handleInjectMethod}($authGroups, $fileContents, $pattern);
|
||||
}
|
||||
|
||||
// Write the contents back to the file
|
||||
file_put_contents($file, $fileContents);
|
||||
}
|
||||
}
|
||||
|
||||
protected function handleInstanceRoles($authGroups, string $fileContents, string $pattern): string
|
||||
{
|
||||
$instanceMatrix = $authGroups->matrix;
|
||||
return $this->renderCommentBlock(
|
||||
$fileContents,
|
||||
$pattern,
|
||||
['role', 'description', 'permissions'],
|
||||
$authGroups->instanceGroups,
|
||||
static function ($table, $key, $value) use ($instanceMatrix): void {
|
||||
$table->addRow($value['title'], $value['description'], implode(', ', $instanceMatrix[$key]));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
protected function handleInstancePermissions($authGroups, string $fileContents, string $pattern): string
|
||||
{
|
||||
return $this->renderCommentBlock(
|
||||
$fileContents,
|
||||
$pattern,
|
||||
['permission', 'description'],
|
||||
$authGroups->instancePermissions,
|
||||
static function ($table, $key, $value): void {
|
||||
$table->addRow($key, $value);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
protected function handlePodcastRoles($authGroups, string $fileContents, string $pattern): string
|
||||
{
|
||||
$podcastMatrix = $authGroups->podcastMatrix;
|
||||
return $this->renderCommentBlock(
|
||||
$fileContents,
|
||||
$pattern,
|
||||
['role', 'description', 'permissions'],
|
||||
$authGroups->podcastGroups,
|
||||
static function ($table, $key, $value) use ($podcastMatrix): void {
|
||||
$table->addRow($value['title'], $value['description'], implode(', ', $podcastMatrix[$key]));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
protected function handlePodcastPermissions($authGroups, string $fileContents, string $pattern): string
|
||||
{
|
||||
return $this->renderCommentBlock(
|
||||
$fileContents,
|
||||
$pattern,
|
||||
['permission', 'description'],
|
||||
$authGroups->podcastPermissions,
|
||||
static function ($table, $key, $value): void {
|
||||
$table->addRow($key, $value);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private function renderCommentBlock(
|
||||
string $fileContents,
|
||||
string $pattern,
|
||||
array $tableHeading,
|
||||
array $data,
|
||||
Closure $callback
|
||||
): string {
|
||||
// check if it has the start and end comments to insert roles table
|
||||
// looking for <AUTH-INSTANCE-ROLES-LIST:START> and <AUTH-INSTANCE-ROLES-LIST:END>
|
||||
|
||||
$hasInstanceInsertComments = preg_match($pattern, $fileContents);
|
||||
|
||||
if (! $hasInstanceInsertComments) {
|
||||
return $fileContents;
|
||||
}
|
||||
|
||||
// prepare role table
|
||||
$table = new Table();
|
||||
$table->setHeading($tableHeading);
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
$callback($table, $key, $value);
|
||||
}
|
||||
|
||||
$converter = new HtmlConverter();
|
||||
$converter->getEnvironment()
|
||||
->addConverter(new TableConverter());
|
||||
$markdownTable = $converter->convert($table->generate());
|
||||
|
||||
// insert table between block comments
|
||||
$newFileContents = preg_replace(
|
||||
$pattern,
|
||||
'${1}' . PHP_EOL . PHP_EOL . $markdownTable . PHP_EOL . PHP_EOL . '${2}',
|
||||
$fileContents
|
||||
);
|
||||
|
||||
if ($newFileContents === null) {
|
||||
return $fileContents;
|
||||
}
|
||||
|
||||
return $newFileContents;
|
||||
}
|
||||
|
||||
private function detectLocaleFromPath($filePath): string
|
||||
{
|
||||
preg_match('~docs\/src\/(?:([a-z]{2}(?:-[A-Za-z]{2,})?)\/)getting-started\/auth\.md~', $filePath, $match);
|
||||
|
||||
if ($match === []) {
|
||||
return 'en';
|
||||
}
|
||||
|
||||
return $match[1];
|
||||
}
|
||||
}
|
|
@ -4,46 +4,99 @@ declare(strict_types=1);
|
|||
|
||||
namespace Modules\Auth\Config;
|
||||
|
||||
use Myth\Auth\Config\Auth as MythAuthConfig;
|
||||
use CodeIgniter\Shield\Authentication\Actions\ActionInterface;
|
||||
use CodeIgniter\Shield\Config\Auth as ShieldAuth;
|
||||
use Modules\Auth\Models\UserModel;
|
||||
|
||||
class Auth extends MythAuthConfig
|
||||
class Auth extends ShieldAuth
|
||||
{
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Views used by Auth Controllers
|
||||
* --------------------------------------------------------------------------
|
||||
* ////////////////// AUTHENTICATION //////////////////
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public $views = [
|
||||
public array $views = [
|
||||
'login' => 'login',
|
||||
'register' => 'register',
|
||||
'forgot' => 'forgot',
|
||||
'reset' => 'reset',
|
||||
'emailForgot' => 'emails/forgot',
|
||||
'emailActivation' => 'emails/activation',
|
||||
'layout' => '_layout',
|
||||
'action_email_2fa' => 'email_2fa_show',
|
||||
'action_email_2fa_verify' => 'email_2fa_verify',
|
||||
'action_email_2fa_email' => 'emails/email_2fa_email',
|
||||
'action_email_activate_show' => 'email_activate_show',
|
||||
'action_email_activate_email' => 'emails/email_activate_email',
|
||||
'magic-link-login' => 'magic_link_form',
|
||||
'magic-link-message' => 'magic_link_message',
|
||||
'magic-link-email' => 'emails/magic_link_email',
|
||||
'magic-link-set-password' => 'magic_link_set_password',
|
||||
'welcome-email' => 'emails/welcome_email',
|
||||
];
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Layout for the views to extend
|
||||
* --------------------------------------------------------------------------
|
||||
* --------------------------------------------------------------------
|
||||
* Redirect urLs
|
||||
* --------------------------------------------------------------------
|
||||
* The default URL that a user will be redirected to after
|
||||
* various auth actions. If you need more flexibility you can
|
||||
* override the `getUrl()` method to apply any logic you may need.
|
||||
*
|
||||
* @var string
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public $viewLayout = '_layout';
|
||||
public array $redirects = [
|
||||
'register' => '/',
|
||||
'login' => '/',
|
||||
'logout' => 'login',
|
||||
];
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Allow User Registration
|
||||
* --------------------------------------------------------------------------
|
||||
* When enabled (default) any unregistered user may apply for a new
|
||||
* account. If you disable registration you may need to ensure your
|
||||
* controllers and views know not to offer registration.
|
||||
* --------------------------------------------------------------------
|
||||
* Authentication Actions
|
||||
* --------------------------------------------------------------------
|
||||
* Specifies the class that represents an action to take after
|
||||
* the user logs in or registers a new account at the site.
|
||||
*
|
||||
* @var bool
|
||||
* You must register actions in the order of the actions to be performed.
|
||||
*
|
||||
* Available actions with Shield:
|
||||
* - register: 'CodeIgniter\Shield\Authentication\Actions\EmailActivator'
|
||||
* - login: 'CodeIgniter\Shield\Authentication\Actions\Email2FA'
|
||||
*
|
||||
* @var array<string, class-string<ActionInterface>|null>
|
||||
*/
|
||||
public $allowRegistration = false;
|
||||
public array $actions = [
|
||||
'register' => null,
|
||||
'login' => null,
|
||||
];
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------
|
||||
* Allow Registration
|
||||
* --------------------------------------------------------------------
|
||||
* Determines whether users can register for the site.
|
||||
*/
|
||||
public bool $allowRegistration = true;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------
|
||||
* Welcome Link Lifetime
|
||||
* --------------------------------------------------------------------
|
||||
* Specifies the amount of time, in seconds, that a welcome link is valid.
|
||||
* You can use Time Constants or any desired number.
|
||||
*/
|
||||
public int $welcomeLinkLifetime = 48 * HOUR;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------
|
||||
* User Provider
|
||||
* --------------------------------------------------------------------
|
||||
* The name of the class that handles user persistence.
|
||||
* By default, this is the included UserModel, which
|
||||
* works with any of the database engines supported by CodeIgniter.
|
||||
* You can change it as long as they adhere to the
|
||||
* CodeIgniter\Shield\Models\UserModel.
|
||||
*
|
||||
* @var class-string<UserModel>
|
||||
*/
|
||||
public string $userProvider = UserModel::class;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
|
@ -52,4 +105,28 @@ class Auth extends MythAuthConfig
|
|||
* Defines a base route for all authentication related pages
|
||||
*/
|
||||
public string $gateway = 'cp-auth';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$adminGateway = config('Admin')
|
||||
->gateway;
|
||||
|
||||
$this->redirects = [
|
||||
'register' => $adminGateway,
|
||||
'login' => $adminGateway,
|
||||
'logout' => $adminGateway,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL that a user should be redirected to after a successful login.
|
||||
*
|
||||
* Redirects to the set-password form if magicLogin
|
||||
*/
|
||||
public function loginRedirect(): string
|
||||
{
|
||||
$url = session('magicLogin') ? route_to('magic-link-set-password') : setting('Auth.redirects')['login'];
|
||||
|
||||
return $this->getUrl($url);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,285 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Auth\Config;
|
||||
|
||||
use App\Models\PodcastModel;
|
||||
use CodeIgniter\Shield\Config\AuthGroups as ShieldAuthGroups;
|
||||
|
||||
class AuthGroups extends ShieldAuthGroups
|
||||
{
|
||||
/**
|
||||
* --------------------------------------------------------------------
|
||||
* Default Group
|
||||
* --------------------------------------------------------------------
|
||||
* The group that a newly registered user is added to.
|
||||
*/
|
||||
public string $defaultGroup = 'podcaster';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------
|
||||
* Most powerful Group
|
||||
* --------------------------------------------------------------------
|
||||
* The group that a has the most permissions.
|
||||
*/
|
||||
public string $mostPowerfulGroup = 'superadmin';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------
|
||||
* Default Podcast Group
|
||||
* --------------------------------------------------------------------
|
||||
* The group that a newly registered user is added to.
|
||||
*/
|
||||
public string $defaultPodcastGroup = 'guest';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------
|
||||
* Most powerful Podcast Group
|
||||
* --------------------------------------------------------------------
|
||||
* The group that a has the most permissions on a podcast.
|
||||
*/
|
||||
public string $mostPowerfulPodcastGroup = 'admin';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------
|
||||
* Groups
|
||||
* --------------------------------------------------------------------
|
||||
* The available authentication systems, listed
|
||||
* with alias and class name. These can be referenced
|
||||
* by alias in the auth helper:
|
||||
* auth('api')->attempt($credentials);
|
||||
*
|
||||
* @var array<string, array<string, string>>
|
||||
*/
|
||||
public array $groups = [];
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------
|
||||
* Permissions
|
||||
* --------------------------------------------------------------------
|
||||
* The available permissions in the system. Each system is defined
|
||||
* where the key is the
|
||||
*
|
||||
* If a permission is not listed here it cannot be used.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public array $permissions = [];
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------
|
||||
* Permissions Matrix
|
||||
* --------------------------------------------------------------------
|
||||
* Maps permissions to groups.
|
||||
* @var array<string, array<string>>
|
||||
*/
|
||||
public array $matrix = [];
|
||||
|
||||
/**
|
||||
* @var array<string, array<string, string>>
|
||||
*/
|
||||
public array $instanceGroups = [];
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public array $instancePermissions = [];
|
||||
|
||||
/**
|
||||
* @var array<string, array<string, string>>
|
||||
*/
|
||||
public array $podcastGroups = [];
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public array $podcastPermissions = [];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
public array $instanceBaseGroups = ['superadmin', 'manager', 'podcaster'];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
public array $instanceBasePermissions = [
|
||||
'admin.access',
|
||||
'admin.settings',
|
||||
'users.manage',
|
||||
'persons.manage',
|
||||
'pages.manage',
|
||||
'podcasts.view',
|
||||
'podcasts.create',
|
||||
'podcasts.import',
|
||||
'fediverse.manage-blocks',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<string, array<string>>
|
||||
*/
|
||||
public array $instanceMatrix = [
|
||||
'superadmin' => [
|
||||
'admin.*',
|
||||
'podcasts.*',
|
||||
'users.manage',
|
||||
'persons.manage',
|
||||
'pages.manage',
|
||||
'fediverse.manage-blocks',
|
||||
],
|
||||
'manager' => ['podcasts.create', 'podcasts.import', 'persons.manage', 'pages.manage'],
|
||||
'podcaster' => ['admin.access'],
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
public array $podcastBaseGroups = ['admin', 'editor', 'author', 'guest'];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
public array $podcastBasePermissions = [
|
||||
'view',
|
||||
'edit',
|
||||
'delete',
|
||||
'manage-import',
|
||||
'manage-persons',
|
||||
'manage-subscriptions',
|
||||
'manage-contributors',
|
||||
'manage-platforms',
|
||||
'manage-publications',
|
||||
'interact-as',
|
||||
'episodes.view',
|
||||
'episodes.create',
|
||||
'episodes.edit',
|
||||
'episodes.delete',
|
||||
'episodes.manage-persons',
|
||||
'episodes.manage-clips',
|
||||
'episodes.manage-publications',
|
||||
'episodes.manage-comments',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<string, string[]>
|
||||
*/
|
||||
public array $podcastMatrix = [
|
||||
'admin' => ['*'],
|
||||
'editor' => [
|
||||
'view',
|
||||
'edit',
|
||||
'manage-import',
|
||||
'manage-persons',
|
||||
'manage-platforms',
|
||||
'manage-publications',
|
||||
'interact-as',
|
||||
'episodes.view',
|
||||
'episodes.create',
|
||||
'episodes.edit',
|
||||
'episodes.delete',
|
||||
'episodes.manage-persons',
|
||||
'episodes.manage-clips',
|
||||
'episodes.manage-publications',
|
||||
'episodes.manage-comments',
|
||||
],
|
||||
'author' => [
|
||||
'view',
|
||||
'manage-persons',
|
||||
'episodes.view',
|
||||
'episodes.create',
|
||||
'episodes.edit',
|
||||
'episodes.manage-persons',
|
||||
'episodes.manage-clips',
|
||||
],
|
||||
'guest' => ['view', 'episodes.view'],
|
||||
];
|
||||
|
||||
/**
|
||||
* Fill groups, permissions and matrix based on
|
||||
*/
|
||||
public function __construct($locale = null)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
foreach ($this->instanceBaseGroups as $group) {
|
||||
$this->instanceGroups[$group] = [
|
||||
'title' => lang("Auth.instance_groups.{$group}.title"),
|
||||
'description' => lang("Auth.instance_groups.{$group}.description"),
|
||||
];
|
||||
}
|
||||
|
||||
$this->groups = $this->instanceGroups;
|
||||
|
||||
foreach ($this->instanceBasePermissions as $permission) {
|
||||
$this->instancePermissions[$permission] = lang("Auth.instance_permissions.{$permission}");
|
||||
$this->permissions[$permission] = lang("Auth.instance_permissions.{$permission}");
|
||||
}
|
||||
|
||||
$this->matrix = $this->instanceMatrix;
|
||||
|
||||
$this->generateBasePodcastAuthorizations();
|
||||
|
||||
/**
|
||||
* For each podcast, include podcast groups, permissions, and matrix into $groups, $permissions, and $matrix
|
||||
* attributes.
|
||||
*/
|
||||
$podcasts = (new PodcastModel())->findAll();
|
||||
foreach ($podcasts as $podcast) {
|
||||
$this->generatePodcastAuthorizations($podcast->id, $locale);
|
||||
}
|
||||
}
|
||||
|
||||
public function generateBasePodcastAuthorizations(): void
|
||||
{
|
||||
foreach ($this->podcastBaseGroups as $group) {
|
||||
$this->podcastGroups[$group] = [
|
||||
'title' => lang("Auth.podcast_groups.{$group}.title", [
|
||||
'id' => '{id}',
|
||||
]),
|
||||
'description' => lang("Auth.podcast_groups.{$group}.description", [
|
||||
'id' => '{id}',
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($this->podcastBasePermissions as $permission) {
|
||||
$this->podcastPermissions[$permission] = lang("Auth.podcast_permissions.{$permission}", [
|
||||
'id' => '{id}',
|
||||
]);
|
||||
$this->permissions[$permission] = lang("Auth.podcast_permissions.{$permission}", [
|
||||
'id' => '{id}',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function generatePodcastAuthorizations(int $podcastId): void
|
||||
{
|
||||
foreach ($this->podcastBaseGroups as $group) {
|
||||
$podcastGroup = 'podcast#' . $podcastId . '-' . $group;
|
||||
$this->groups[$podcastGroup] = [
|
||||
'title' => lang("Auth.podcast_groups.{$group}.title", [
|
||||
'id' => $podcastId,
|
||||
]),
|
||||
'description' => lang("Auth.podcast_groups.{$group}.description", [
|
||||
'id' => $podcastId,
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($this->podcastBasePermissions as $permission) {
|
||||
$podcastPermission = 'podcast#' . $podcastId . '.' . $permission;
|
||||
$this->permissions[$podcastPermission] = lang("Auth.podcast_permissions.{$permission}", [
|
||||
'id' => $podcastId,
|
||||
]);
|
||||
}
|
||||
|
||||
foreach ($this->podcastMatrix as $group => $permissionWildcards) {
|
||||
$podcastGroup = 'podcast#' . $podcastId . '-' . $group;
|
||||
foreach ($permissionWildcards as $permissionWildcard) {
|
||||
$podcastPermissionWildcard = 'podcast#' . $podcastId . '.' . $permissionWildcard;
|
||||
$this->matrix[$podcastGroup][] = $podcastPermissionWildcard;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Auth\Config;
|
||||
|
||||
use CodeIgniter\Shield\Config\AuthRoutes as ShieldAuthRoutes;
|
||||
|
||||
class AuthRoutes extends ShieldAuthRoutes
|
||||
{
|
||||
public array $routes = [
|
||||
'register' => [
|
||||
['get', 'register', 'RegisterController::registerView', 'register'],
|
||||
['post', 'register', 'RegisterController::registerAction'],
|
||||
],
|
||||
'login' => [
|
||||
['get', 'login', 'LoginController::loginView', 'login'],
|
||||
['post', 'login', 'LoginController::loginAction'],
|
||||
],
|
||||
'magic-link' => [
|
||||
[
|
||||
'get',
|
||||
'login/magic-link',
|
||||
'MagicLinkController::loginView',
|
||||
'magic-link', // Route name
|
||||
],
|
||||
['post', 'login/magic-link', 'MagicLinkController::loginAction'],
|
||||
[
|
||||
'get',
|
||||
'login/verify-magic-link',
|
||||
'MagicLinkController::verify',
|
||||
'verify-magic-link', // Route name
|
||||
],
|
||||
],
|
||||
'logout' => [['get', 'logout', 'LoginController::logoutAction', 'logout']],
|
||||
'auth-actions' => [
|
||||
['get', 'auth/a/show', 'ActionController::show', 'auth-action-show'],
|
||||
['post', 'auth/a/handle', 'ActionController::handle', 'auth-action-handle'],
|
||||
['post', 'auth/a/verify', 'ActionController::verify', 'auth-action-verify'],
|
||||
],
|
||||
];
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Auth\Config;
|
||||
|
||||
use CodeIgniter\Events\Events;
|
||||
use CodeIgniter\Shield\Entities\User;
|
||||
|
||||
Events::on('logout', static function (User $user): void {
|
||||
helper('auth');
|
||||
// remove user's interact_as_actor session
|
||||
remove_interact_as_actor();
|
||||
});
|
|
@ -6,52 +6,134 @@ namespace Modules\Auth\Config;
|
|||
|
||||
$routes = service('routes');
|
||||
|
||||
/**
|
||||
* Overwriting Myth:auth routes file
|
||||
*/
|
||||
service('auth')
|
||||
->routes($routes);
|
||||
|
||||
// Admin routes for users and podcast contributors
|
||||
$routes->group(
|
||||
config('Auth')
|
||||
config('Admin')
|
||||
->gateway,
|
||||
[
|
||||
'namespace' => 'Modules\Auth\Controllers',
|
||||
],
|
||||
static function ($routes): void {
|
||||
// Login/out
|
||||
$routes->get('login', 'AuthController::login', [
|
||||
'as' => 'login',
|
||||
$routes->get('magic-link-set-password', 'MagicLinkController::setPasswordView', [
|
||||
'as' => 'magic-link-set-password',
|
||||
]);
|
||||
$routes->post('login', 'AuthController::attemptLogin');
|
||||
$routes->get('logout', 'AuthController::logout', [
|
||||
'as' => 'logout',
|
||||
]);
|
||||
// Registration
|
||||
$routes->get('register', 'AuthController::register', [
|
||||
'as' => 'register',
|
||||
]);
|
||||
$routes->post('register', 'AuthController::attemptRegister');
|
||||
// Activation
|
||||
$routes->get('activate-account', 'AuthController::activateAccount', [
|
||||
'as' => 'activate-account',
|
||||
]);
|
||||
$routes->get(
|
||||
'resend-activate-account',
|
||||
'AuthController::resendActivateAccount',
|
||||
[
|
||||
'as' => 'resend-activate-account',
|
||||
],
|
||||
);
|
||||
// Forgot/Resets
|
||||
$routes->get('forgot', 'AuthController::forgotPassword', [
|
||||
'as' => 'forgot',
|
||||
]);
|
||||
$routes->post('forgot', 'AuthController::attemptForgot');
|
||||
$routes->get('reset-password', 'AuthController::resetPassword', [
|
||||
'as' => 'reset-password',
|
||||
]);
|
||||
$routes->post('reset-password', 'AuthController::attemptReset');
|
||||
// interacting as an actor
|
||||
$routes->post('interact-as-actor', 'AuthController::attemptInteractAsActor', [
|
||||
$routes->post('magic-link-set-password', 'MagicLinkController::setPasswordAction');
|
||||
|
||||
$routes->post('interact-as-actor', 'InteractController::attemptInteractAsActor', [
|
||||
'as' => 'interact-as-actor',
|
||||
]);
|
||||
|
||||
// Users
|
||||
$routes->group('users', static function ($routes): void {
|
||||
$routes->get('/', 'UserController::list', [
|
||||
'as' => 'user-list',
|
||||
'filter' => 'permission:users.manage',
|
||||
]);
|
||||
$routes->get('new', 'UserController::create', [
|
||||
'as' => 'user-create',
|
||||
'filter' => 'permission:users.manage',
|
||||
]);
|
||||
$routes->post('new', 'UserController::attemptCreate', [
|
||||
'filter' => 'permission:users.manage',
|
||||
]);
|
||||
// User
|
||||
$routes->group('(:num)', static function ($routes): void {
|
||||
$routes->get('/', 'UserController::view/$1', [
|
||||
'as' => 'user-view',
|
||||
'filter' => 'permission:users.manage',
|
||||
]);
|
||||
$routes->get('edit', 'UserController::edit/$1', [
|
||||
'as' => 'user-edit',
|
||||
'filter' => 'permission:users.manage',
|
||||
]);
|
||||
$routes->post('edit', 'UserController::attemptEdit/$1', [
|
||||
'filter' => 'permission:users.manage',
|
||||
]);
|
||||
$routes->get('delete', 'UserController::delete/$1', [
|
||||
'as' => 'user-delete',
|
||||
'filter' => 'permission:users.manage',
|
||||
]);
|
||||
$routes->post('delete', 'UserController::attemptDelete/$1', [
|
||||
'as' => 'user-delete',
|
||||
'filter' => 'permission:users.manage',
|
||||
]);
|
||||
});
|
||||
});
|
||||
// My account
|
||||
$routes->group('my-account', static function ($routes): void {
|
||||
$routes->get('/', 'MyAccountController', [
|
||||
'as' => 'my-account',
|
||||
]);
|
||||
$routes->get('change-password', 'MyAccountController::changePassword', [
|
||||
'as' => 'change-password',
|
||||
],);
|
||||
$routes->post('change-password', 'MyAccountController::attemptChange');
|
||||
});
|
||||
|
||||
// Podcast contributors
|
||||
$routes->group('podcasts/(:num)/contributors', static function ($routes): void {
|
||||
$routes->get('/', 'ContributorController::list/$1', [
|
||||
'as' => 'contributor-list',
|
||||
'filter' =>
|
||||
'permission:podcast#.manage-contributors',
|
||||
]);
|
||||
$routes->get('add', 'ContributorController::add/$1', [
|
||||
'as' => 'contributor-add',
|
||||
'filter' => 'permission:podcast#.manage-contributors',
|
||||
]);
|
||||
$routes->post(
|
||||
'add',
|
||||
'ContributorController::attemptAdd/$1',
|
||||
[
|
||||
'filter' =>
|
||||
'permission:podcast#.manage-contributors',
|
||||
],
|
||||
);
|
||||
// Contributor
|
||||
$routes->group('(:num)', static function ($routes): void {
|
||||
$routes->get('/', 'ContributorController::view/$1/$2', [
|
||||
'as' => 'contributor-view',
|
||||
'filter' =>
|
||||
'permission:podcast#.manage-contributors',
|
||||
]);
|
||||
$routes->get(
|
||||
'edit',
|
||||
'ContributorController::edit/$1/$2',
|
||||
[
|
||||
'as' => 'contributor-edit',
|
||||
'filter' =>
|
||||
'permission:podcast#.manage-contributors',
|
||||
],
|
||||
);
|
||||
$routes->post(
|
||||
'edit',
|
||||
'ContributorController::attemptEdit/$1/$2',
|
||||
[
|
||||
'filter' =>
|
||||
'permission:podcast#.manage-contributors',
|
||||
],
|
||||
);
|
||||
$routes->get(
|
||||
'remove',
|
||||
'ContributorController::remove/$1/$2',
|
||||
[
|
||||
'as' => 'contributor-remove',
|
||||
'filter' =>
|
||||
'permission:podcast#.manage-contributors',
|
||||
],
|
||||
);
|
||||
$routes->post(
|
||||
'remove',
|
||||
'ContributorController::attemptRemove/$1/$2',
|
||||
[
|
||||
'filter' =>
|
||||
'permission:podcast#.manage-contributors',
|
||||
],
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
|
@ -4,87 +4,23 @@ declare(strict_types=1);
|
|||
|
||||
namespace Modules\Auth\Config;
|
||||
|
||||
use App\Models\UserModel;
|
||||
use CodeIgniter\Config\BaseService;
|
||||
use CodeIgniter\Model;
|
||||
use Modules\Auth\Authorization\FlatAuthorization;
|
||||
use Modules\Auth\Authorization\GroupModel;
|
||||
use Modules\Auth\Authorization\PermissionModel;
|
||||
use Myth\Auth\Models\LoginModel;
|
||||
use CodeIgniter\Shield\Authentication\Authentication;
|
||||
use Config\Services as BaseService;
|
||||
use Modules\Auth\Auth;
|
||||
|
||||
/**
|
||||
* Services Configuration file.
|
||||
*
|
||||
* Services are simply other classes/libraries that the system uses to do its job. This is used by CodeIgniter to allow
|
||||
* the core of the framework to be swapped out easily without affecting the usage within the rest of your application.
|
||||
*
|
||||
* This file holds any application-specific services, or service overrides that you might need. An example has been
|
||||
* included with the general method format you should use for your service methods. For more examples, see the core
|
||||
* Services file at system/Config/Services.php.
|
||||
*/
|
||||
class Services extends BaseService
|
||||
{
|
||||
/**
|
||||
* @return mixed
|
||||
* The base auth class
|
||||
*/
|
||||
public static function authentication(
|
||||
string $lib = 'local',
|
||||
Model $userModel = null,
|
||||
Model $loginModel = null,
|
||||
bool $getShared = true
|
||||
) {
|
||||
public static function auth(bool $getShared = true): Auth
|
||||
{
|
||||
if ($getShared) {
|
||||
return self::getSharedInstance('authentication', $lib, $userModel, $loginModel);
|
||||
return self::getSharedInstance('auth');
|
||||
}
|
||||
|
||||
// config() checks first in app/Config
|
||||
$config = config('Auth');
|
||||
|
||||
$class = $config->authenticationLibs[$lib];
|
||||
|
||||
$instance = new $class($config);
|
||||
|
||||
if ($userModel === null) {
|
||||
$userModel = new UserModel();
|
||||
}
|
||||
|
||||
if ($loginModel === null) {
|
||||
$loginModel = new LoginModel();
|
||||
}
|
||||
|
||||
return $instance->setUserModel($userModel)
|
||||
->setLoginModel($loginModel);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed|$this
|
||||
*/
|
||||
public static function authorization(
|
||||
Model $groupModel = null,
|
||||
Model $permissionModel = null,
|
||||
Model $userModel = null,
|
||||
bool $getShared = true
|
||||
) {
|
||||
if ($getShared) {
|
||||
return self::getSharedInstance('authorization', $groupModel, $permissionModel, $userModel);
|
||||
}
|
||||
|
||||
if ($groupModel === null) {
|
||||
$groupModel = new GroupModel();
|
||||
}
|
||||
|
||||
if ($permissionModel === null) {
|
||||
$permissionModel = new PermissionModel();
|
||||
}
|
||||
|
||||
/* @phpstan-ignore-next-line */
|
||||
$instance = new FlatAuthorization($groupModel, $permissionModel);
|
||||
|
||||
if ($userModel === null) {
|
||||
$userModel = new UserModel();
|
||||
}
|
||||
|
||||
/* @phpstan-ignore-next-line */
|
||||
return $instance->setUserModel($userModel);
|
||||
return new Auth(new Authentication($config));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Auth\Controllers;
|
||||
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use CodeIgniter\Shield\Controllers\ActionController as ShieldActionController;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use ViewThemes\Theme;
|
||||
|
||||
/**
|
||||
* Class ActionController
|
||||
*
|
||||
* A generic controller to handle Authentication Actions.
|
||||
*/
|
||||
class ActionController extends ShieldActionController
|
||||
{
|
||||
public function initController(
|
||||
RequestInterface $request,
|
||||
ResponseInterface $response,
|
||||
LoggerInterface $logger
|
||||
): void {
|
||||
parent::initController($request, $response, $logger);
|
||||
|
||||
Theme::setTheme('auth');
|
||||
}
|
||||
}
|
|
@ -1,204 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace Modules\Auth\Controllers;
|
||||
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use Modules\Auth\Entities\User;
|
||||
use Myth\Auth\Controllers\AuthController as MythAuthController;
|
||||
use ViewThemes\Theme;
|
||||
|
||||
class AuthController extends MythAuthController
|
||||
{
|
||||
/**
|
||||
* An array of helpers to be automatically loaded upon class instantiation.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $helpers = ['components'];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
Theme::setTheme('auth');
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to register a new user.
|
||||
*/
|
||||
public function attemptRegister(): RedirectResponse
|
||||
{
|
||||
// Check if registration is allowed
|
||||
if (! $this->config->allowRegistration) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('error', lang('Auth.registerDisabled'));
|
||||
}
|
||||
|
||||
$users = model('UserModel');
|
||||
|
||||
// Validate here first, since some things,
|
||||
// like the password, can only be validated properly here.
|
||||
$rules = [
|
||||
'username' =>
|
||||
'required|alpha_numeric_space|min_length[3]|is_unique[users.username]',
|
||||
'email' => 'required|valid_email|is_unique[users.email]',
|
||||
'password' => 'required|strong_password',
|
||||
];
|
||||
|
||||
if (! $this->validate($rules)) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', service('validation')->getErrors());
|
||||
}
|
||||
|
||||
// Save the user
|
||||
$allowedPostFields = array_merge(['password'], $this->config->validFields, $this->config->personalFields);
|
||||
$user = new User($this->request->getPost($allowedPostFields));
|
||||
|
||||
$this->config->requireActivation === null
|
||||
? $user->activate()
|
||||
: $user->generateActivateHash();
|
||||
|
||||
// Ensure default group gets assigned if set
|
||||
if ($this->config->defaultUserGroup !== null) {
|
||||
$users = $users->withGroup($this->config->defaultUserGroup);
|
||||
}
|
||||
|
||||
if (! $users->save($user)) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', $users->errors());
|
||||
}
|
||||
|
||||
if ($this->config->requireActivation !== null) {
|
||||
$activator = service('activator');
|
||||
$sent = $activator->send($user);
|
||||
|
||||
if (! $sent) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('error', $activator->error() ?? lang('Auth.unknownError'));
|
||||
}
|
||||
|
||||
// Success!
|
||||
return redirect()
|
||||
->route('login')
|
||||
->with('message', lang('Auth.activationSuccess'));
|
||||
}
|
||||
|
||||
// Success!
|
||||
return redirect()
|
||||
->route('login')
|
||||
->with('message', lang('Auth.registerSuccess'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the code with the email and saves the new password, if they all pass validation.
|
||||
*/
|
||||
public function attemptReset(): RedirectResponse
|
||||
{
|
||||
if ($this->config->activeResetter === null) {
|
||||
return redirect()
|
||||
->route('login')
|
||||
->with('error', lang('Auth.forgotDisabled'));
|
||||
}
|
||||
|
||||
$users = model('UserModel');
|
||||
|
||||
// First things first - log the reset attempt.
|
||||
$users->logResetAttempt(
|
||||
$this->request->getPost('email'),
|
||||
$this->request->getPost('token'),
|
||||
$this->request->getIPAddress(),
|
||||
(string) $this->request->getUserAgent(),
|
||||
);
|
||||
|
||||
$rules = [
|
||||
'token' => 'required',
|
||||
'email' => 'required|valid_email',
|
||||
'password' => 'required|strong_password',
|
||||
];
|
||||
|
||||
if (! $this->validate($rules)) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', $users->errors());
|
||||
}
|
||||
|
||||
$user = $users
|
||||
->where('email', $this->request->getPost('email'))
|
||||
->where('reset_hash', $this->request->getPost('token'))
|
||||
->first();
|
||||
|
||||
if ($user === null) {
|
||||
return redirect()
|
||||
->back()
|
||||
->with('error', lang('Auth.forgotNoUser'));
|
||||
}
|
||||
|
||||
// Reset token still valid?
|
||||
if (
|
||||
$user->reset_expires !== null &&
|
||||
time() > $user->reset_expires->getTimestamp()
|
||||
) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('error', lang('Auth.resetTokenExpired'));
|
||||
}
|
||||
|
||||
// Success! Save the new password, and cleanup the reset hash.
|
||||
$user->password = $this->request->getPost('password');
|
||||
$user->reset_hash = null;
|
||||
$user->reset_at = date('Y-m-d H:i:s');
|
||||
$user->reset_expires = null;
|
||||
$user->force_pass_reset = false;
|
||||
$users->save($user);
|
||||
|
||||
helper('auth');
|
||||
|
||||
// set interact_as_actor_id value
|
||||
$userPodcasts = $user->podcasts;
|
||||
if ($userPodcasts = $user->podcasts) {
|
||||
set_interact_as_actor($userPodcasts[0]->actor_id);
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->route('login')
|
||||
->with('message', lang('Auth.resetSuccess'));
|
||||
}
|
||||
|
||||
public function attemptInteractAsActor(): RedirectResponse
|
||||
{
|
||||
$rules = [
|
||||
'actor_id' => 'required|numeric',
|
||||
];
|
||||
|
||||
if (! $this->validate($rules)) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', service('validation')->getErrors());
|
||||
}
|
||||
|
||||
helper('auth');
|
||||
|
||||
set_interact_as_actor((int) $this->request->getPost('actor_id'));
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,243 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2022 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace Modules\Auth\Controllers;
|
||||
|
||||
use App\Entities\Podcast;
|
||||
use App\Models\PodcastModel;
|
||||
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use CodeIgniter\Shield\Entities\User;
|
||||
use Modules\Admin\Controllers\BaseController;
|
||||
use Modules\Auth\Models\UserModel;
|
||||
|
||||
class ContributorController extends BaseController
|
||||
{
|
||||
protected Podcast $podcast;
|
||||
|
||||
protected ?User $contributor;
|
||||
|
||||
public function _remap(string $method, string ...$params): mixed
|
||||
{
|
||||
if ($params === []) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
if (($podcast = (new PodcastModel())->getPodcastById((int) $params[0])) === null) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
$this->podcast = $podcast;
|
||||
|
||||
if (count($params) <= 1) {
|
||||
return $this->{$method}();
|
||||
}
|
||||
|
||||
if (($this->contributor = (new UserModel())->getPodcastContributor(
|
||||
(int) $params[1],
|
||||
(int) $params[0]
|
||||
)) !== null) {
|
||||
return $this->{$method}();
|
||||
}
|
||||
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
public function list(): string
|
||||
{
|
||||
$data = [
|
||||
'podcast' => $this->podcast,
|
||||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->at_handle,
|
||||
]);
|
||||
return view('contributor/list', $data);
|
||||
}
|
||||
|
||||
public function view(): string
|
||||
{
|
||||
$data = [
|
||||
'podcast' => $this->podcast,
|
||||
'contributor' => (new UserModel())->getPodcastContributor($this->contributor->id, $this->podcast->id),
|
||||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->at_handle,
|
||||
1 => $this->contributor->username,
|
||||
]);
|
||||
return view('contributor/view', $data);
|
||||
}
|
||||
|
||||
public function add(): string
|
||||
{
|
||||
helper('form');
|
||||
|
||||
$users = (new UserModel())->findAll();
|
||||
$contributorOptions = array_reduce(
|
||||
$users,
|
||||
static function ($result, $user) {
|
||||
$result[$user->id] = $user->username;
|
||||
return $result;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
$roles = setting('AuthGroups.podcastBaseGroups');
|
||||
$roleOptions = [];
|
||||
array_walk(
|
||||
$roles,
|
||||
static function ($role, $key) use (&$roleOptions): array {
|
||||
$roleOptions[$role] = lang('Auth.podcast_groups.' . $role . '.title');
|
||||
return $roleOptions;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
$data = [
|
||||
'podcast' => $this->podcast,
|
||||
'contributorOptions' => $contributorOptions,
|
||||
'roleOptions' => $roleOptions,
|
||||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->at_handle,
|
||||
]);
|
||||
return view('contributor/add', $data);
|
||||
}
|
||||
|
||||
public function attemptAdd(): RedirectResponse
|
||||
{
|
||||
$user = (new UserModel())->find((int) $this->request->getPost('user'));
|
||||
|
||||
if (get_podcast_group($user, $this->podcast->id)) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', [lang('Contributor.messages.alreadyAddedError')]);
|
||||
}
|
||||
|
||||
add_podcast_group($user, $this->podcast->id, $this->request->getPost('role'));
|
||||
|
||||
return redirect()->route('contributor-list', [$this->podcast->id]);
|
||||
}
|
||||
|
||||
public function edit(): string|RedirectResponse
|
||||
{
|
||||
helper('form');
|
||||
|
||||
$roles = setting('AuthGroups.podcastBaseGroups');
|
||||
$roleOptions = [];
|
||||
array_walk(
|
||||
$roles,
|
||||
static function ($role) use (&$roleOptions): array {
|
||||
$roleOptions[$role] = lang('Auth.podcast_groups.' . $role . '.title');
|
||||
return $roleOptions;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
$contributorGroup = get_podcast_group($this->contributor, $this->podcast->id);
|
||||
|
||||
if ($contributorGroup === null) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', [lang('Contributor.messages.notAddedError')]);
|
||||
}
|
||||
|
||||
$data = [
|
||||
'podcast' => $this->podcast,
|
||||
'contributor' => $this->contributor,
|
||||
'contributorGroup' => $contributorGroup,
|
||||
'roleOptions' => $roleOptions,
|
||||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->at_handle,
|
||||
1 => $this->contributor->username,
|
||||
]);
|
||||
return view('contributor/edit', $data);
|
||||
}
|
||||
|
||||
public function attemptEdit(): RedirectResponse
|
||||
{
|
||||
// forbid updating a podcast owner
|
||||
if ($this->podcast->created_by === $this->contributor->id) {
|
||||
return redirect()
|
||||
->back()
|
||||
->with('errors', [lang('Contributor.messages.editOwnerError')]);
|
||||
}
|
||||
|
||||
$group = $this->request->getPost('role');
|
||||
|
||||
set_podcast_group($this->contributor, $this->podcast->id, $group);
|
||||
|
||||
cache()
|
||||
->delete("podcast#{$this->podcast->id}_contributors");
|
||||
|
||||
return redirect()->route('contributor-list', [$this->podcast->id])->with(
|
||||
'message',
|
||||
lang('Contributor.messages.editSuccess')
|
||||
);
|
||||
}
|
||||
|
||||
public function remove(): string
|
||||
{
|
||||
helper('form');
|
||||
|
||||
$data = [
|
||||
'podcast' => $this->podcast,
|
||||
'contributor' => $this->contributor,
|
||||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->podcast->at_handle,
|
||||
1 => $this->contributor->username,
|
||||
]);
|
||||
return view('contributor/delete', $data);
|
||||
}
|
||||
|
||||
public function attemptRemove(): RedirectResponse
|
||||
{
|
||||
if ($this->podcast->created_by === $this->contributor->id) {
|
||||
return redirect()
|
||||
->back()
|
||||
->with('errors', [lang('Contributor.messages.removeOwnerError')]);
|
||||
}
|
||||
|
||||
$rules = [
|
||||
'understand' => 'required',
|
||||
];
|
||||
|
||||
if (! $this->validate($rules)) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', $this->validator->getErrors());
|
||||
}
|
||||
|
||||
cache()
|
||||
->delete("podcast#{$this->podcast->id}_contributors");
|
||||
|
||||
// remove contributor from podcast group
|
||||
$this->contributor->removeGroup(get_podcast_group($this->contributor, $this->podcast->id));
|
||||
|
||||
return redirect()
|
||||
->route('contributor-list', [$this->podcast->id])
|
||||
->with(
|
||||
'message',
|
||||
lang('Contributor.messages.removeSuccess', [
|
||||
'username' => $this->contributor->username,
|
||||
'podcastTitle' => $this->podcast->title,
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Auth\Controllers;
|
||||
|
||||
use CodeIgniter\Controller;
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
|
||||
/**
|
||||
* Class ActionController
|
||||
*
|
||||
* A generic controller to handle Authentication Actions.
|
||||
*/
|
||||
class InteractController extends Controller
|
||||
{
|
||||
public function attemptInteractAsActor(): RedirectResponse
|
||||
{
|
||||
$rules = [
|
||||
'actor_id' => 'required|numeric',
|
||||
];
|
||||
|
||||
if (! $this->validate($rules)) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', service('validation')->getErrors());
|
||||
}
|
||||
|
||||
helper('auth');
|
||||
|
||||
set_interact_as_actor((int) $this->request->getPost('actor_id'));
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Auth\Controllers;
|
||||
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use CodeIgniter\Shield\Controllers\LoginController as ShieldLoginController;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use ViewThemes\Theme;
|
||||
|
||||
class LoginController extends ShieldLoginController
|
||||
{
|
||||
public function initController(
|
||||
RequestInterface $request,
|
||||
ResponseInterface $response,
|
||||
LoggerInterface $logger
|
||||
): void {
|
||||
parent::initController($request, $response, $logger);
|
||||
|
||||
Theme::setTheme('auth');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Auth\Controllers;
|
||||
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use CodeIgniter\Shield\Controllers\MagicLinkController as ShieldMagicLinkController;
|
||||
use Modules\Auth\Models\UserModel;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use ViewThemes\Theme;
|
||||
|
||||
/**
|
||||
* Handles "Magic Link" logins - an email-based no-password login protocol. This works much like password reset would,
|
||||
* but Shield provides this in place of password reset. It can also be used on it's own without an email/password login
|
||||
* strategy.
|
||||
*/
|
||||
class MagicLinkController extends ShieldMagicLinkController
|
||||
{
|
||||
public function initController(
|
||||
RequestInterface $request,
|
||||
ResponseInterface $response,
|
||||
LoggerInterface $logger
|
||||
): void {
|
||||
parent::initController($request, $response, $logger);
|
||||
|
||||
Theme::setTheme('auth');
|
||||
}
|
||||
|
||||
public function setPasswordView(): string | RedirectResponse
|
||||
{
|
||||
if (! session('magicLogin')) {
|
||||
return redirect()->to(config('Auth')->loginRedirect());
|
||||
}
|
||||
|
||||
return view(setting('Auth.views')['magic-link-set-password']);
|
||||
}
|
||||
|
||||
public function setPasswordAction(): RedirectResponse
|
||||
{
|
||||
$rules = [
|
||||
'new_password' => 'required|strong_password',
|
||||
];
|
||||
|
||||
$userModel = new UserModel();
|
||||
if (! $this->validate($rules)) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', $userModel->errors());
|
||||
}
|
||||
|
||||
// set new password to user
|
||||
auth()
|
||||
->user()
|
||||
->password = $this->request->getPost('new_password');
|
||||
|
||||
if (! $userModel->update(auth()->user()->id, auth()->user())) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', $userModel->errors());
|
||||
}
|
||||
|
||||
// remove magic login session to reinstate normal check
|
||||
if (session('magicLogin')) {
|
||||
session()->removeTempdata('magicLogin');
|
||||
}
|
||||
|
||||
// Success!
|
||||
return redirect()->to(config('Auth')->loginRedirect())
|
||||
->with('message', lang('MyAccount.messages.passwordChangeSuccess'));
|
||||
}
|
||||
}
|
|
@ -8,11 +8,11 @@ declare(strict_types=1);
|
|||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace Modules\Admin\Controllers;
|
||||
namespace Modules\Auth\Controllers;
|
||||
|
||||
use App\Models\UserModel;
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use Config\Services;
|
||||
use Modules\Admin\Controllers\BaseController;
|
||||
use Modules\Auth\Models\UserModel;
|
||||
|
||||
class MyAccountController extends BaseController
|
||||
{
|
||||
|
@ -30,16 +30,12 @@ class MyAccountController extends BaseController
|
|||
|
||||
public function attemptChange(): RedirectResponse
|
||||
{
|
||||
$auth = Services::authentication();
|
||||
$userModel = new UserModel();
|
||||
|
||||
// Validate here first, since some things,
|
||||
// like the password, can only be validated properly here.
|
||||
$rules = [
|
||||
'password' => 'required',
|
||||
'new_password' => 'required|strong_password|differs[password]',
|
||||
];
|
||||
|
||||
$userModel = new UserModel();
|
||||
if (! $this->validate($rules)) {
|
||||
return redirect()
|
||||
->back()
|
||||
|
@ -47,23 +43,28 @@ class MyAccountController extends BaseController
|
|||
->with('errors', $userModel->errors());
|
||||
}
|
||||
|
||||
// check credentials with the old password if logged in without magic link
|
||||
$credentials = [
|
||||
'email' => user()
|
||||
'email' => auth()
|
||||
->user()
|
||||
->email,
|
||||
'password' => $this->request->getPost('password'),
|
||||
];
|
||||
|
||||
if (! $auth->validate($credentials)) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
$validCreds = auth()
|
||||
->check($credentials);
|
||||
|
||||
if (! $validCreds->isOK()) {
|
||||
return redirect()->back()
|
||||
->with('error', lang('MyAccount.messages.wrongPasswordError'));
|
||||
}
|
||||
|
||||
user()
|
||||
// set new password to user
|
||||
auth()
|
||||
->user()
|
||||
->password = $this->request->getPost('new_password');
|
||||
|
||||
if (! $userModel->update(user_id(), user())) {
|
||||
if (! $userModel->update(auth()->user()->id, auth()->user())) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Auth\Controllers;
|
||||
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use CodeIgniter\Shield\Controllers\RegisterController as ShieldRegisterController;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use ViewThemes\Theme;
|
||||
|
||||
/**
|
||||
* Class RegisterController
|
||||
*
|
||||
* Handles displaying registration form, and handling actual registration flow.
|
||||
*/
|
||||
class RegisterController extends ShieldRegisterController
|
||||
{
|
||||
public function initController(
|
||||
RequestInterface $request,
|
||||
ResponseInterface $response,
|
||||
LoggerInterface $logger
|
||||
): void {
|
||||
parent::initController($request, $response, $logger);
|
||||
|
||||
Theme::setTheme('auth');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,276 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2022 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace Modules\Auth\Controllers;
|
||||
|
||||
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use CodeIgniter\I18n\Time;
|
||||
use CodeIgniter\Shield\Authentication\Authenticators\Session;
|
||||
use CodeIgniter\Shield\Entities\User;
|
||||
use CodeIgniter\Shield\Exceptions\ValidationException;
|
||||
use CodeIgniter\Shield\Models\UserIdentityModel;
|
||||
use Modules\Admin\Controllers\BaseController;
|
||||
use Modules\Auth\Models\UserModel;
|
||||
|
||||
class UserController extends BaseController
|
||||
{
|
||||
protected ?User $user;
|
||||
|
||||
public function _remap(string $method, string ...$params): mixed
|
||||
{
|
||||
if ($params === []) {
|
||||
return $this->{$method}();
|
||||
}
|
||||
|
||||
if ($this->user = (new UserModel())->find($params[0])) {
|
||||
return $this->{$method}();
|
||||
}
|
||||
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
public function list(): string
|
||||
{
|
||||
$data = [
|
||||
'users' => (new UserModel())->findAll(),
|
||||
];
|
||||
|
||||
return view('user/list', $data);
|
||||
}
|
||||
|
||||
public function view(): string
|
||||
{
|
||||
$data = [
|
||||
'user' => $this->user,
|
||||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->user->username,
|
||||
]);
|
||||
return view('user/view', $data);
|
||||
}
|
||||
|
||||
public function create(): string
|
||||
{
|
||||
helper('form');
|
||||
|
||||
$roles = setting('AuthGroups.instanceGroups');
|
||||
$roleOptions = [];
|
||||
array_walk(
|
||||
$roles,
|
||||
static function ($role, $key) use (&$roleOptions): array {
|
||||
$roleOptions[$key] = $role['title'];
|
||||
return $roleOptions;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
$data = [
|
||||
'roleOptions' => $roleOptions,
|
||||
];
|
||||
|
||||
return view('user/create', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the user with the provided username and email. The password is set as a random string and a magic link is
|
||||
* sent to the user to allow them setting their password.
|
||||
*/
|
||||
public function attemptCreate(): RedirectResponse
|
||||
{
|
||||
helper('text');
|
||||
|
||||
$db = db_connect();
|
||||
$db->transStart();
|
||||
|
||||
$userModel = new UserModel();
|
||||
|
||||
// Save the user
|
||||
$email = $this->request->getPost('email');
|
||||
$user = new User([
|
||||
'username' => $this->request->getPost('username'),
|
||||
'email' => $email,
|
||||
// set a random password
|
||||
// user will be prompted to change it on first magic link login.
|
||||
'password' => random_string('alnum', 32),
|
||||
]);
|
||||
try {
|
||||
$userModel->save($user);
|
||||
} catch (ValidationException) {
|
||||
return redirect()->back()
|
||||
->withInput()
|
||||
->with('errors', $userModel->errors());
|
||||
}
|
||||
|
||||
$user = $userModel->findById($userModel->getInsertID());
|
||||
$user->addGroup($this->request->getPost('role'));
|
||||
|
||||
// **** SEND WELCOME LINK FOR FIRST LOGIN ****
|
||||
|
||||
/** @var UserIdentityModel $identityModel */
|
||||
$identityModel = model(UserIdentityModel::class);
|
||||
|
||||
// Delete any previous magic-link identities
|
||||
$identityModel->deleteIdentitiesByType($user, Session::ID_TYPE_MAGIC_LINK);
|
||||
|
||||
// Generate the code and save it as an identity
|
||||
$token = random_string('crypto', 20);
|
||||
|
||||
$identityModel->insert([
|
||||
'user_id' => $user->id,
|
||||
'type' => Session::ID_TYPE_MAGIC_LINK,
|
||||
'secret' => $token,
|
||||
'expires' => Time::now()->addSeconds(setting('Auth.welcomeLinkLifetime'))->format('Y-m-d H:i:s'),
|
||||
]);
|
||||
|
||||
// Send the user an email with the code
|
||||
$email = emailer()
|
||||
->setFrom(setting('Email.fromEmail'), setting('Email.fromName') ?? '');
|
||||
$email->setTo($user->email);
|
||||
$email->setSubject(lang('Auth.welcomeSubject', [
|
||||
'siteName' => setting('App.siteName'),
|
||||
], null, false));
|
||||
$email->setMessage(view(setting('Auth.views')['welcome-email'], [
|
||||
'token' => $token,
|
||||
], [
|
||||
'theme' => 'auth',
|
||||
]));
|
||||
|
||||
if (! $email->send(false)) {
|
||||
log_message('error', $email->printDebugger(['headers']));
|
||||
|
||||
return redirect()->back()
|
||||
->with('error', lang('Auth.unableSendEmailToUser', [$user->email]));
|
||||
}
|
||||
|
||||
// Clear the email
|
||||
$email->clear();
|
||||
|
||||
$db->transComplete();
|
||||
|
||||
// Success!
|
||||
return redirect()
|
||||
->route('user-list')
|
||||
->with('message', lang('User.messages.createSuccess', [
|
||||
'username' => $user->username,
|
||||
]));
|
||||
}
|
||||
|
||||
public function edit(): string
|
||||
{
|
||||
helper('form');
|
||||
|
||||
$roles = setting('AuthGroups.instanceGroups');
|
||||
$roleOptions = [];
|
||||
array_walk(
|
||||
$roles,
|
||||
static function ($role, $key) use (&$roleOptions): array {
|
||||
$roleOptions[$key] = $role['title'];
|
||||
return $roleOptions;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
$data = [
|
||||
'user' => $this->user,
|
||||
'roleOptions' => $roleOptions,
|
||||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->user->username,
|
||||
]);
|
||||
return view('user/edit', $data);
|
||||
}
|
||||
|
||||
public function attemptEdit(): RedirectResponse
|
||||
{
|
||||
// The instance owner is a superadmin and the only user that cannot be demoted.
|
||||
if ((bool) $this->user->is_owner) {
|
||||
return redirect()
|
||||
->back()
|
||||
->with('errors', [
|
||||
lang('User.messages.editOwnerError', [
|
||||
'username' => $this->user->username,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
$group = $this->request->getPost('role');
|
||||
|
||||
set_instance_group($this->user, $group);
|
||||
|
||||
// Success!
|
||||
return redirect()
|
||||
->route('user-list')
|
||||
->with('message', lang('User.messages.roleEditSuccess', [
|
||||
'username' => $this->user->username,
|
||||
]));
|
||||
}
|
||||
|
||||
public function delete(): string
|
||||
{
|
||||
helper(['form']);
|
||||
|
||||
$data = [
|
||||
'user' => $this->user,
|
||||
];
|
||||
|
||||
replace_breadcrumb_params([
|
||||
0 => $this->user->username,
|
||||
]);
|
||||
return view('user/delete', $data);
|
||||
}
|
||||
|
||||
public function attemptDelete(): RedirectResponse
|
||||
{
|
||||
// You cannot delete the instance owner.
|
||||
if ((bool) $this->user->is_owner) {
|
||||
return redirect()
|
||||
->back()
|
||||
->with('errors', [
|
||||
lang('User.messages.deleteOwnerError', [
|
||||
'username' => $this->user->username,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
// You cannot delete a superadmin
|
||||
// superadmin has to be demoted before being deleted
|
||||
if ($this->user->inGroup(setting('AuthGroups.mostPowerfulPodcastGroup'))) {
|
||||
return redirect()
|
||||
->back()
|
||||
->with('errors', [
|
||||
lang('User.messages.deleteSuperAdminError', [
|
||||
'username' => $this->user->username,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
$rules = [
|
||||
'understand' => 'required',
|
||||
];
|
||||
|
||||
if (! $this->validate($rules)) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', $this->validator->getErrors());
|
||||
}
|
||||
|
||||
(new UserModel())->delete($this->user->id, true);
|
||||
|
||||
return redirect()
|
||||
->route('user-list')
|
||||
->with('message', lang('User.messages.deleteSuccess', [
|
||||
'username' => $this->user->username,
|
||||
]));
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class AddPodcastUsers Creates podcast_users table in database
|
||||
*
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace Modules\Auth\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class AddPodcastsUsers extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$this->forge->addField([
|
||||
'podcast_id' => [
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'user_id' => [
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'group_id' => [
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
]);
|
||||
$this->forge->addPrimaryKey(['user_id', 'podcast_id']);
|
||||
$this->forge->addForeignKey('user_id', 'users', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('group_id', 'auth_groups', 'id', '', 'CASCADE');
|
||||
$this->forge->createTable('podcasts_users');
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropTable('podcasts_users');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
// Add custom column for shield
|
||||
class AddCustomColumnForUser extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$fields = [
|
||||
'is_owner' => [
|
||||
'type' => 'TINYINT',
|
||||
'constraint' => 1,
|
||||
'default' => 0,
|
||||
'null' => false,
|
||||
],
|
||||
];
|
||||
|
||||
$this->forge->addColumn('users', $fields);
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$fields = ['is_owner'];
|
||||
$this->forge->dropColumn('users', $fields);
|
||||
}
|
||||
}
|
|
@ -1,302 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class PermissionSeeder Inserts permissions
|
||||
*
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace Modules\Auth\Database\Seeds;
|
||||
|
||||
use CodeIgniter\Database\Seeder;
|
||||
|
||||
class AuthSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* @var array<string, string>[]
|
||||
*/
|
||||
protected array $groups = [
|
||||
[
|
||||
'name' => 'superadmin',
|
||||
'description' =>
|
||||
'Somebody who has access to all the castopod instance features',
|
||||
],
|
||||
[
|
||||
'name' => 'podcast_admin',
|
||||
'description' =>
|
||||
'Somebody who has access to all the features within a given podcast',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Build permissions array as a list of:
|
||||
*
|
||||
* ``` context => [ [action, description], [action, description], ... ] ```
|
||||
*
|
||||
* @var array<string, array<string, string|array>[]>
|
||||
*/
|
||||
protected array $permissions = [
|
||||
'users' => [
|
||||
[
|
||||
'name' => 'create',
|
||||
'description' => 'Create a user',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'list',
|
||||
'description' => 'List all users',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'view',
|
||||
'description' => 'View any user info',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'manage_authorizations',
|
||||
'description' => 'Add or remove roles/permissions to a user',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'manage_bans',
|
||||
'description' => 'Ban / unban a user',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'force_pass_reset',
|
||||
'description' =>
|
||||
'Force a user to update his password upon next login',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'delete',
|
||||
'description' =>
|
||||
'Delete user without removing him from database',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'delete_permanently',
|
||||
'description' =>
|
||||
'Delete all occurrences of a user from the database',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
],
|
||||
'pages' => [
|
||||
[
|
||||
'name' => 'manage',
|
||||
'description' => 'List / create / edit / delete pages',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
],
|
||||
'podcasts' => [
|
||||
[
|
||||
'name' => 'create',
|
||||
'description' => 'Add a new podcast',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'import',
|
||||
'description' => 'Import a new podcast from an external feed',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'list',
|
||||
'description' => 'List all podcasts and their episodes',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'view',
|
||||
'description' => 'View any podcast and their contributors list',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'delete',
|
||||
'description' => 'Delete any podcast from the database',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
],
|
||||
'episodes' => [
|
||||
[
|
||||
'name' => 'list',
|
||||
'description' => 'List all episodes of any podcast',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'view',
|
||||
'description' => 'View any episode of any podcast',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
],
|
||||
'podcast' => [
|
||||
[
|
||||
'name' => 'view',
|
||||
'description' => 'View a podcast',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
[
|
||||
'name' => 'edit',
|
||||
'description' => 'Edit a podcast',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
[
|
||||
'name' => 'manage_contributors',
|
||||
'description' =>
|
||||
'Add / remove contributors to a podcast and edit their roles',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
[
|
||||
'name' => 'manage_platforms',
|
||||
'description' => 'Set / remove platform links of a podcast',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
[
|
||||
'name' => 'manage_publications',
|
||||
'description' =>
|
||||
'Publish / unpublish episodes & posts of a podcast',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
[
|
||||
'name' => 'interact_as',
|
||||
'description' =>
|
||||
'Interact as the podcast to favourite / share or reply to posts.',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
],
|
||||
'podcast_episodes' => [
|
||||
[
|
||||
'name' => 'list',
|
||||
'description' => 'List all episodes of a podcast',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
[
|
||||
'name' => 'view',
|
||||
'description' => 'View any episode of a podcast',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
[
|
||||
'name' => 'create',
|
||||
'description' => 'Add new episodes for a podcast',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
[
|
||||
'name' => 'edit',
|
||||
'description' => 'Edit an episode of a podcast',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
[
|
||||
'name' => 'delete',
|
||||
'description' =>
|
||||
'Delete all occurrences of an episode of a podcast from the database',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
],
|
||||
'person' => [
|
||||
[
|
||||
'name' => 'create',
|
||||
'description' => 'Add a new person',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'list',
|
||||
'description' => 'List all persons',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'view',
|
||||
'description' => 'View any person',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'edit',
|
||||
'description' => 'Edit a person',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'delete',
|
||||
'description' =>
|
||||
'Delete permanently any person from the database',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
],
|
||||
'fediverse' => [
|
||||
[
|
||||
'name' => 'block_actors',
|
||||
'description' =>
|
||||
'Block fediverse actors from interacting with the instance.',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'block_domains',
|
||||
'description' =>
|
||||
'Block fediverse domains from interacting with the instance.',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$groupId = 0;
|
||||
$dataGroups = [];
|
||||
foreach ($this->groups as $group) {
|
||||
$dataGroups[] = [
|
||||
'id' => ++$groupId,
|
||||
'name' => $group['name'],
|
||||
'description' => $group['description'],
|
||||
];
|
||||
}
|
||||
|
||||
// Map permissions to a format the `auth_permissions` table expects
|
||||
$dataPermissions = [];
|
||||
$dataGroupsPermissions = [];
|
||||
$permissionId = 0;
|
||||
foreach ($this->permissions as $context => $actions) {
|
||||
foreach ($actions as $action) {
|
||||
$dataPermissions[] = [
|
||||
'id' => ++$permissionId,
|
||||
'name' => $context . '-' . $action['name'],
|
||||
'description' => $action['description'],
|
||||
];
|
||||
|
||||
foreach ($action['has_permission'] as $role) {
|
||||
// link permission to specified groups
|
||||
$dataGroupsPermissions[] = [
|
||||
'group_id' => $this->getGroupIdByName($role, $dataGroups),
|
||||
'permission_id' => $permissionId,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->db
|
||||
->table('auth_permissions')
|
||||
->ignore(true)
|
||||
->insertBatch($dataPermissions);
|
||||
$this->db
|
||||
->table('auth_groups')
|
||||
->ignore(true)
|
||||
->insertBatch($dataGroups);
|
||||
$this->db
|
||||
->table('auth_groups_permissions')
|
||||
->ignore(true)
|
||||
->insertBatch($dataGroupsPermissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string|int>[] $dataGroups
|
||||
*/
|
||||
public static function getGroupIdByName(string $name, array $dataGroups): ?int
|
||||
{
|
||||
foreach ($dataGroups as $group) {
|
||||
if ($group['name'] === $name) {
|
||||
return $group['id'];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace Modules\Auth\Entities;
|
||||
|
||||
use App\Entities\Podcast;
|
||||
use App\Models\PodcastModel;
|
||||
use App\Models\UserModel;
|
||||
use Modules\Fediverse\Models\NotificationModel;
|
||||
use Myth\Auth\Entities\User as MythAuthUser;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $username
|
||||
* @property string $email
|
||||
* @property string $password
|
||||
* @property bool $active
|
||||
* @property bool $force_pass_reset
|
||||
* @property int|null $podcast_id
|
||||
* @property string|null $podcast_role
|
||||
*
|
||||
* @property Podcast[] $podcasts All podcasts the user is contributing to
|
||||
* @property int[] $actorIdsWithUnreadNotifications Ids of the user's actors that have unread notifications
|
||||
*/
|
||||
class User extends MythAuthUser
|
||||
{
|
||||
public bool $is_owner;
|
||||
|
||||
/**
|
||||
* @var Podcast[]|null
|
||||
*/
|
||||
protected ?array $podcasts = null;
|
||||
|
||||
/**
|
||||
* @var int[]|null
|
||||
*/
|
||||
protected ?array $actorIdsWithUnreadNotifications = null;
|
||||
|
||||
/**
|
||||
* Array of field names and the type of value to cast them as when they are accessed.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'id' => 'integer',
|
||||
'active' => 'boolean',
|
||||
'force_pass_reset' => 'boolean',
|
||||
'podcast_id' => '?integer',
|
||||
'podcast_role' => '?string',
|
||||
];
|
||||
|
||||
public function getIsOwner(): bool
|
||||
{
|
||||
$firstUser = (new UserModel())->first();
|
||||
|
||||
if (! $firstUser instanceof self) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->username === $firstUser->username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the podcasts the user is contributing to
|
||||
*
|
||||
* @return Podcast[]
|
||||
*/
|
||||
public function getPodcasts(): array
|
||||
{
|
||||
if ($this->id === null) {
|
||||
throw new RuntimeException('Users must be created before getting podcasts.');
|
||||
}
|
||||
|
||||
if ($this->podcasts === null) {
|
||||
$this->podcasts = (new PodcastModel())->getUserPodcasts($this->id);
|
||||
}
|
||||
|
||||
return $this->podcasts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ids of the user's actors that have unread notifications
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
public function getActorIdsWithUnreadNotifications(): array
|
||||
{
|
||||
if ($this->getPodcasts() === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$unreadNotifications = (new NotificationModel())->whereIn(
|
||||
'target_actor_id',
|
||||
array_column($this->getPodcasts(), 'actor_id')
|
||||
)
|
||||
->where('read_at', null)
|
||||
->findAll();
|
||||
|
||||
return array_column($unreadNotifications, 'target_actor_id');
|
||||
}
|
||||
}
|
|
@ -8,8 +8,8 @@ use App\Models\PodcastModel;
|
|||
use CodeIgniter\Filters\FilterInterface;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use CodeIgniter\Shield\Exceptions\RuntimeException;
|
||||
use Config\Services;
|
||||
use Myth\Auth\Exceptions\PermissionException;
|
||||
|
||||
class PermissionFilter implements FilterInterface
|
||||
{
|
||||
|
@ -24,62 +24,49 @@ class PermissionFilter implements FilterInterface
|
|||
*/
|
||||
public function before(RequestInterface $request, $params = null)
|
||||
{
|
||||
helper('auth');
|
||||
|
||||
if ($params === null) {
|
||||
if (empty($params)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$authenticate = Services::authentication();
|
||||
|
||||
// if no user is logged in then send to the login form
|
||||
if (! $authenticate->check()) {
|
||||
session()->set('redirect_url', current_url());
|
||||
return redirect('login');
|
||||
if (! function_exists('auth')) {
|
||||
helper('auth');
|
||||
}
|
||||
|
||||
helper('misc');
|
||||
$authorize = Services::authorization();
|
||||
$router = Services::router();
|
||||
$routerParams = $router->params();
|
||||
$result = false;
|
||||
if (! auth()->loggedIn()) {
|
||||
return redirect()->to('login');
|
||||
}
|
||||
|
||||
$result = true;
|
||||
|
||||
// Check if user has at least one of the permissions
|
||||
foreach ($params as $permission) {
|
||||
// check if permission is for a specific podcast
|
||||
if (
|
||||
(str_starts_with($permission, 'podcast-') ||
|
||||
str_starts_with($permission, 'podcast_episodes-')) &&
|
||||
$routerParams !== []
|
||||
) {
|
||||
if (
|
||||
($groupId = (new PodcastModel())->getContributorGroupId(
|
||||
$authenticate->id(),
|
||||
$routerParams[0],
|
||||
)) &&
|
||||
$authorize->groupHasPermission($permission, $groupId)
|
||||
) {
|
||||
$result = true;
|
||||
break;
|
||||
// does permission is specific to a podcast?
|
||||
if (str_contains($permission, '#')) {
|
||||
$router = Services::router();
|
||||
$routerParams = $router->params();
|
||||
|
||||
// get podcast id
|
||||
$podcastId = null;
|
||||
if (is_numeric($routerParams[0])) {
|
||||
$podcastId = (int) $routerParams[0];
|
||||
} else {
|
||||
$podcast = (new PodcastModel())->getPodcastByHandle($routerParams[0]);
|
||||
if ($podcast !== null) {
|
||||
$podcastId = $podcast->id;
|
||||
}
|
||||
}
|
||||
|
||||
if ($podcastId !== null) {
|
||||
$permission = str_replace('#', '#' . $podcastId, $permission);
|
||||
}
|
||||
} elseif (
|
||||
$authorize->hasPermission($permission, $authenticate->id())
|
||||
) {
|
||||
$result = true;
|
||||
break;
|
||||
}
|
||||
|
||||
$result = $result && auth()
|
||||
->user()
|
||||
->can($permission);
|
||||
}
|
||||
|
||||
if (! $result) {
|
||||
if ($authenticate->silent()) {
|
||||
$redirectURL = session('redirect_url') ?? '/';
|
||||
unset($_SESSION['redirect_url']);
|
||||
return redirect()
|
||||
->to($redirectURL)
|
||||
->with('error', lang('Auth.notEnoughPrivilege'));
|
||||
}
|
||||
|
||||
throw new PermissionException(lang('Auth.notEnoughPrivilege'));
|
||||
throw new RuntimeException(lang('Auth.notEnoughPrivilege'), 403);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,296 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
use App\Entities\Podcast;
|
||||
use App\Models\ActorModel;
|
||||
use App\Models\PodcastModel;
|
||||
use CodeIgniter\Shield\Entities\User;
|
||||
use Modules\Auth\Auth;
|
||||
use Modules\Fediverse\Entities\Actor;
|
||||
use Modules\Fediverse\Models\NotificationModel;
|
||||
|
||||
if (! function_exists('auth')) {
|
||||
/**
|
||||
* Provides convenient access to the main Auth class for CodeIgniter Shield.
|
||||
*
|
||||
* @param string|null $alias Authenticator alias
|
||||
*/
|
||||
function auth(?string $alias = null): Auth
|
||||
{
|
||||
/** @var Auth $auth */
|
||||
$auth = service('auth');
|
||||
|
||||
return $auth->setAuthenticator($alias);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('set_interact_as_actor')) {
|
||||
/**
|
||||
* Sets the actor id of which the user is acting as
|
||||
*/
|
||||
function set_interact_as_actor(int $actorId): void
|
||||
{
|
||||
if (auth()->loggedIn()) {
|
||||
session()
|
||||
->set('interact_as_actor_id', $actorId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('remove_interact_as_actor')) {
|
||||
/**
|
||||
* Removes the actor id of which the user is acting as
|
||||
*/
|
||||
function remove_interact_as_actor(): void
|
||||
{
|
||||
session()->remove('interact_as_actor_id');
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('interact_as_actor_id')) {
|
||||
/**
|
||||
* Sets the podcast id of which the user is acting as
|
||||
*/
|
||||
function interact_as_actor_id(): ?int
|
||||
{
|
||||
return session()->get('interact_as_actor_id');
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('interact_as_actor')) {
|
||||
/**
|
||||
* Get the actor the user is currently interacting as
|
||||
*/
|
||||
function interact_as_actor(): Actor | false
|
||||
{
|
||||
if (! auth()->loggedIn()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$session = session();
|
||||
if (! $session->has('interact_as_actor_id')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return model(ActorModel::class, false)->getActorById($session->get('interact_as_actor_id'));
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('can_user_interact')) {
|
||||
function can_user_interact(): bool
|
||||
{
|
||||
return (bool) interact_as_actor();
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('add_podcast_group')) {
|
||||
function add_podcast_group(User $user, int $podcastId, string $group): User
|
||||
{
|
||||
$podcastGroup = 'podcast#' . $podcastId . '-' . $group;
|
||||
|
||||
return $user->addGroup($podcastGroup);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('get_instance_group')) {
|
||||
function get_instance_group(User $user): ?string
|
||||
{
|
||||
$instanceGroups = array_filter($user->getGroups() ?? [], static function ($group): bool {
|
||||
return ! str_starts_with($group, 'podcast#');
|
||||
});
|
||||
|
||||
if ($instanceGroups === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$instanceGroup = array_shift($instanceGroups);
|
||||
|
||||
// Verify that a user belongs to one group only!
|
||||
if ($instanceGroups !== []) {
|
||||
// remove any other group the user belongs to
|
||||
$user->removeGroup(...$instanceGroups);
|
||||
}
|
||||
|
||||
return $instanceGroup;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('set_instance_group')) {
|
||||
function set_instance_group(User $user, string $group): User
|
||||
{
|
||||
// remove old instance group
|
||||
if (get_instance_group($user)) {
|
||||
$user->removeGroup(get_instance_group($user));
|
||||
}
|
||||
|
||||
// set new group
|
||||
return $user->addGroup($group);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('get_podcast_group')) {
|
||||
function get_podcast_group(User $user, int $podcastId): ?string
|
||||
{
|
||||
$podcastGroups = array_filter($user->getGroups() ?? [], static function ($group) use ($podcastId): bool {
|
||||
return str_starts_with($group, "podcast#{$podcastId}");
|
||||
});
|
||||
|
||||
if ($podcastGroups === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$podcastGroup = array_shift($podcastGroups);
|
||||
|
||||
// Verify that a user belongs to one group only!
|
||||
if ($podcastGroups !== []) {
|
||||
// remove any other group the user belongs to
|
||||
$user->removeGroup(...$podcastGroups);
|
||||
}
|
||||
|
||||
// strip the `podcast#{id}.` prefix when returning group
|
||||
return substr($podcastGroup, strlen('podcast#' . $podcastId . '-'));
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('set_podcast_group')) {
|
||||
function set_podcast_group(User $user, int $podcastId, string $group): User
|
||||
{
|
||||
// remove old instance group
|
||||
$user->removeGroup("podcast#{$podcastId}-" . get_podcast_group($user, $podcastId));
|
||||
|
||||
// set new group
|
||||
return add_podcast_group($user, $podcastId, $group);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('get_podcast_groups')) {
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
function get_user_podcast_ids(User $user): array
|
||||
{
|
||||
$podcastGroups = array_filter($user->getGroups() ?? [], static function ($group): bool {
|
||||
return str_starts_with($group, 'podcast#');
|
||||
});
|
||||
|
||||
$userPodcastIds = [];
|
||||
// extract all podcast ids from groups
|
||||
foreach ($podcastGroups as $podcastGroup) {
|
||||
$userPodcastIds[] = substr($podcastGroup, strpos($podcastGroup, '#') + 1, 1);
|
||||
}
|
||||
|
||||
return $userPodcastIds;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('can_podcast')) {
|
||||
function can_podcast(User $user, int $podcastId, string $permission): bool
|
||||
{
|
||||
return $user->can('podcast#' . $podcastId . '.' . $permission);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('get_user_podcasts')) {
|
||||
/**
|
||||
* Returns the podcasts the user is contributing to
|
||||
*
|
||||
* @return Podcast[]
|
||||
*/
|
||||
function get_user_podcasts(User $user): array
|
||||
{
|
||||
return (new PodcastModel())->getUserPodcasts($user->id, get_user_podcast_ids($user));
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('get_podcasts_user_can_interact_with')) {
|
||||
/**
|
||||
* @return Podcast[]
|
||||
*/
|
||||
function get_podcasts_user_can_interact_with(User $user): array
|
||||
{
|
||||
$userPodcasts = (new PodcastModel())->getUserPodcasts($user->id, get_user_podcast_ids($user));
|
||||
|
||||
$hasInteractAsPrivilege = interact_as_actor_id() === null;
|
||||
|
||||
if ($userPodcasts === []) {
|
||||
if ($hasInteractAsPrivilege) {
|
||||
remove_interact_as_actor();
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
$isInteractAsPrivilegeLost = true;
|
||||
$podcastsUserCanInteractWith = [];
|
||||
foreach ($userPodcasts as $userPodcast) {
|
||||
if (can_podcast($user, $userPodcast->id, 'interact-as')) {
|
||||
if (interact_as_actor_id() === $userPodcast->actor_id) {
|
||||
$isInteractAsPrivilegeLost = false;
|
||||
}
|
||||
|
||||
$podcastsUserCanInteractWith[] = $userPodcast;
|
||||
}
|
||||
}
|
||||
|
||||
if ($podcastsUserCanInteractWith === []) {
|
||||
if (interact_as_actor_id() !== null) {
|
||||
remove_interact_as_actor();
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
// check if user has lost the interact as privilege for current podcast actor.
|
||||
// --> Remove interact as if there's no podcast actor to interact as
|
||||
// or set the first podcast actor the user can interact as
|
||||
if ($isInteractAsPrivilegeLost) {
|
||||
set_interact_as_actor($podcastsUserCanInteractWith[0]->actor_id);
|
||||
}
|
||||
|
||||
return $podcastsUserCanInteractWith;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('get_actor_ids_with_unread_notifications')) {
|
||||
/**
|
||||
* Returns the ids of the user's actors that have unread notifications
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
function get_actor_ids_with_unread_notifications(User $user): array
|
||||
{
|
||||
if (($userPodcasts = get_user_podcasts($user)) === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$unreadNotifications = (new NotificationModel())->whereIn(
|
||||
'target_actor_id',
|
||||
array_column($userPodcasts, 'actor_id')
|
||||
)
|
||||
->where('read_at', null)
|
||||
->findAll();
|
||||
|
||||
return array_column($unreadNotifications, 'target_actor_id');
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('get_group_title')) {
|
||||
/**
|
||||
* @return array<'title'|'description', string>
|
||||
*/
|
||||
function get_group_info(string $group, ?int $podcastId = null): array
|
||||
{
|
||||
if ($podcastId === null) {
|
||||
return setting('AuthGroups.instanceGroups')[$group];
|
||||
}
|
||||
|
||||
return setting('AuthGroups.podcastGroups')[$group];
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue