From c1287cbe6c8cad9fcbd29fb4433d56132691034c Mon Sep 17 00:00:00 2001 From: Yassine Doghri Date: Sat, 15 Oct 2022 11:22:08 +0000 Subject: [PATCH] refactor(auth): replace myth/auth with codeigniter/shield + define new roles closes #222 --- .devcontainer/devcontainer.json | 4 +- app/Common.php | 6 + app/Config/Email.php | 2 +- app/Config/Events.php | 16 - app/Config/Filters.php | 6 +- app/Config/Routes.php | 10 +- app/Config/Security.php | 2 +- app/Config/Validation.php | 2 - app/Controllers/ActorController.php | 4 +- app/Controllers/BaseController.php | 2 + app/Controllers/CreditsController.php | 2 +- app/Controllers/EpisodeCommentController.php | 7 +- app/Controllers/EpisodeController.php | 16 +- app/Controllers/HomeController.php | 18 +- app/Controllers/MapController.php | 3 +- app/Controllers/PageController.php | 3 +- app/Controllers/PodcastController.php | 21 +- app/Controllers/PostController.php | 9 +- ...ia.php => 2021-05-29-120000_add_media.php} | 2 + ...p => 2021-05-29-152000_add_categories.php} | 0 ...hp => 2021-05-30-101000_add_languages.php} | 0 ...php => 2021-05-30-101500_add_podcasts.php} | 0 ...php => 2021-06-05-170000_add_episodes.php} | 0 ...hp => 2021-06-05-190000_add_platforms.php} | 0 ...1-06-05-200000_add_podcasts_platforms.php} | 0 ...es.php => 2021-08-17-150000_add_pages.php} | 0 ...-09-29-150000_add_podcasts_categories.php} | 0 ....php => 2021-12-25-120000_add_persons.php} | 0 ...021-12-25-130000_add_podcasts_persons.php} | 0 ...021-12-25-140000_add_episodes_persons.php} | 0 ...=> 2021-12-25-150000_add_credits_view.php} | 0 ...-02-23-100000_add_episode_id_to_posts.php} | 0 ...-03-09-113000_add_created_by_to_posts.php} | 0 app/Database/Seeds/AppSeeder.php | 1 - app/Database/Seeds/AuthSeeder.php | 328 ------------------ app/Database/Seeds/TestSeeder.php | 20 +- app/Entities/Clip/BaseClip.php | 4 +- app/Entities/Podcast.php | 11 +- app/Helpers/auth_helper.php | 89 ----- app/Models/PodcastModel.php | 84 +---- app/Models/UserModel.php | 55 --- app/Resources/icons/shield-user.svg | 4 + app/Views/errors/html/error_403.php | 29 ++ app/Views/errors/html/error_404.php | 2 +- app/Views/errors/html/production.php | 4 +- composer.json | 4 +- composer.lock | 137 ++++---- docs/.gitlab-ci.yml | 3 - docs/.vitepress/config.ts | 4 + docs/src/getting-started/auth.md | 86 +++++ modules/Admin/Config/Routes.php | 292 +++++----------- .../Controllers/ContributorController.php | 203 ----------- .../Admin/Controllers/EpisodeController.php | 16 +- .../Controllers/EpisodePersonController.php | 2 +- .../Controllers/NotificationController.php | 2 +- .../Admin/Controllers/PodcastController.php | 53 ++- .../Controllers/PodcastImportController.php | 12 +- .../Controllers/PodcastPersonController.php | 2 +- .../Controllers/PodcastPlatformController.php | 2 +- .../Admin/Controllers/SoundbiteController.php | 4 +- modules/Admin/Controllers/UserController.php | 258 -------------- .../Controllers/VideoClipsController.php | 6 +- modules/Admin/Language/en/Breadcrumb.php | 1 + modules/Admin/Language/id/User.php | 56 --- modules/Admin/Language/it/User.php | 56 --- modules/Admin/Language/nl/User.php | 56 --- modules/Admin/Language/oc/User.php | 56 --- modules/Admin/Language/pt/User.php | 56 --- modules/Admin/Language/ru/User.php | 56 --- modules/Admin/Language/sk/User.php | 56 --- modules/Admin/Language/sv/User.php | 56 --- modules/Analytics/Config/Analytics.php | 6 +- modules/Auth/Auth.php | 42 +++ .../Auth/Authorization/FlatAuthorization.php | 54 --- modules/Auth/Authorization/GroupModel.php | 30 -- .../Auth/Authorization/PermissionModel.php | 53 --- modules/Auth/Commands/RolesDoc.php | 184 ++++++++++ modules/Auth/Config/Auth.php | 123 +++++-- modules/Auth/Config/AuthGroups.php | 285 +++++++++++++++ modules/Auth/Config/AuthRoutes.php | 42 +++ modules/Auth/Config/Events.php | 14 + modules/Auth/Config/Routes.php | 158 +++++++-- modules/Auth/Config/Services.php | 80 +---- modules/Auth/Controllers/ActionController.php | 29 ++ modules/Auth/Controllers/AuthController.php | 204 ----------- .../Controllers/ContributorController.php | 243 +++++++++++++ .../Auth/Controllers/InteractController.php | 36 ++ modules/Auth/Controllers/LoginController.php | 24 ++ .../Auth/Controllers/MagicLinkController.php | 76 ++++ .../Controllers/MyAccountController.php | 31 +- .../Auth/Controllers/RegisterController.php | 29 ++ modules/Auth/Controllers/UserController.php | 276 +++++++++++++++ .../2020-07-03-191500_add_podcasts_users.php | 46 --- ...020-12-29-100000_add_is_owner_to_users.php | 31 ++ modules/Auth/Database/Seeds/.gitkeep | 0 modules/Auth/Database/Seeds/AuthSeeder.php | 302 ---------------- modules/Auth/Entities/User.php | 109 ------ modules/Auth/Filters/PermissionFilter.php | 77 ++-- modules/Auth/Helpers/auth_helper.php | 296 ++++++++++++++++ .../Language/ar/Contributor.php | 0 .../{Admin => Auth}/Language/ar/MyAccount.php | 0 modules/{Admin => Auth}/Language/ar/User.php | 5 +- .../Language/br/Contributor.php | 0 .../{Admin => Auth}/Language/br/MyAccount.php | 0 modules/{Admin => Auth}/Language/br/User.php | 5 +- .../Language/ca/Contributor.php | 0 .../{Admin => Auth}/Language/ca/MyAccount.php | 0 modules/{Admin => Auth}/Language/ca/User.php | 5 +- .../Language/de/Contributor.php | 0 .../{Admin => Auth}/Language/de/MyAccount.php | 0 modules/{Admin => Auth}/Language/de/User.php | 5 +- .../Language/el/Contributor.php | 0 .../{Admin => Auth}/Language/el/MyAccount.php | 0 .../Language/gd => Auth/Language/el}/User.php | 5 +- modules/Auth/Language/en/Auth.php | 89 +++++ .../sv => Auth/Language/en}/Contributor.php | 10 +- .../{Admin => Auth}/Language/en/MyAccount.php | 0 modules/Auth/Language/en/User.php | 60 ++++ .../Language/es/Contributor.php | 0 .../{Admin => Auth}/Language/es/MyAccount.php | 0 modules/{Admin => Auth}/Language/es/User.php | 5 +- .../en => Auth/Language/fa}/Contributor.php | 0 .../{Admin => Auth}/Language/fa/MyAccount.php | 0 .../Language/el => Auth/Language/fa}/User.php | 5 +- .../Language/fr/Contributor.php | 0 .../{Admin => Auth}/Language/fr/MyAccount.php | 0 modules/{Admin => Auth}/Language/fr/User.php | 5 +- .../fa => Auth/Language/gd}/Contributor.php | 0 .../{Admin => Auth}/Language/gd/MyAccount.php | 0 .../Language/fa => Auth/Language/gd}/User.php | 5 +- .../Language/gl/Contributor.php | 0 .../{Admin => Auth}/Language/gl/MyAccount.php | 0 modules/{Admin => Auth}/Language/gl/User.php | 5 +- .../gd => Auth/Language/id}/Contributor.php | 0 .../{Admin => Auth}/Language/id/MyAccount.php | 0 .../Language/en => Auth/Language/id}/User.php | 5 +- .../Language/it/Contributor.php | 0 .../{Admin => Auth}/Language/it/MyAccount.php | 0 modules/Auth/Language/it/User.php | 53 +++ .../Language/nl/Contributor.php | 0 .../{Admin => Auth}/Language/nl/MyAccount.php | 0 modules/Auth/Language/nl/User.php | 53 +++ .../Language/nn-NO/Contributor.php | 0 .../Language/nn-NO/MyAccount.php | 0 .../{Admin => Auth}/Language/nn-NO/User.php | 5 +- .../id => Auth/Language/oc}/Contributor.php | 0 .../{Admin => Auth}/Language/oc/MyAccount.php | 0 modules/Auth/Language/oc/User.php | 53 +++ .../Language/pl/Contributor.php | 0 .../{Admin => Auth}/Language/pl/MyAccount.php | 0 modules/{Admin => Auth}/Language/pl/User.php | 5 +- .../Language/pt-BR/Contributor.php | 0 .../Language/pt-BR/MyAccount.php | 0 .../{Admin => Auth}/Language/pt-BR/User.php | 5 +- .../oc => Auth/Language/pt}/Contributor.php | 0 .../{Admin => Auth}/Language/pt/MyAccount.php | 0 modules/Auth/Language/pt/User.php | 53 +++ .../pt => Auth/Language/ru}/Contributor.php | 0 .../{Admin => Auth}/Language/ru/MyAccount.php | 0 modules/Auth/Language/ru/User.php | 53 +++ .../Language/sk/Contributor.php | 0 .../{Admin => Auth}/Language/sk/MyAccount.php | 0 modules/Auth/Language/sk/User.php | 53 +++ .../ru => Auth/Language/sv}/Contributor.php | 0 .../{Admin => Auth}/Language/sv/MyAccount.php | 0 modules/Auth/Language/sv/User.php | 53 +++ .../Language/zh-Hans/Contributor.php | 0 .../Language/zh-Hans/MyAccount.php | 0 .../{Admin => Auth}/Language/zh-Hans/User.php | 5 +- modules/Auth/Models/UserModel.php | 50 +++ .../Install/Controllers/InstallController.php | 64 ++-- modules/Install/Language/en/Install.php | 2 +- modules/PremiumPodcasts/Config/Routes.php | 26 +- .../Controllers/SubscriptionController.php | 12 +- .../Filters/PodcastUnlockFilter.php | 5 +- phpstan.neon | 4 +- themes/cp_admin/_partials/_nav_header.php | 27 +- themes/cp_admin/_partials/_user_info.php | 20 +- themes/cp_admin/contributor/add.php | 3 +- themes/cp_admin/contributor/delete.php | 37 ++ themes/cp_admin/contributor/edit.php | 8 +- themes/cp_admin/contributor/list.php | 10 +- themes/cp_admin/my_account/view.php | 3 +- themes/cp_admin/podcast/list.php | 2 +- themes/cp_admin/user/create.php | 19 +- themes/cp_admin/user/delete.php | 35 ++ themes/cp_admin/user/edit.php | 14 +- themes/cp_admin/user/list.php | 32 +- themes/cp_admin/user/view.php | 6 + themes/cp_app/_admin_navbar.php | 48 ++- themes/cp_app/embed.php | 2 +- themes/cp_app/home.php | 2 +- themes/cp_app/pages/_layout.php | 2 +- themes/cp_app/pages/map.php | 2 +- themes/cp_auth/_layout.php | 3 +- themes/cp_auth/email_2fa_show.php | 38 ++ themes/cp_auth/email_2fa_verify.php | 36 ++ themes/cp_auth/email_activate_show.php | 28 ++ themes/cp_auth/emails/activation.php | 11 - themes/cp_auth/emails/email_2fa_email.php | 1 + .../cp_auth/emails/email_activate_email.php | 3 + themes/cp_auth/emails/forgot.php | 13 - themes/cp_auth/emails/magic_link_email.php | 5 + themes/cp_auth/emails/welcome_email.php | 10 + themes/cp_auth/forgot.php | 24 -- themes/cp_auth/login.php | 41 ++- themes/cp_auth/magic_link_form.php | 24 ++ themes/cp_auth/magic_link_message.php | 13 + themes/cp_auth/magic_link_set_password.php | 26 ++ themes/cp_auth/register.php | 34 +- themes/cp_auth/reset.php | 38 -- themes/cp_install/_layout.php | 3 +- themes/cp_install/create_superadmin.php | 10 +- 213 files changed, 3372 insertions(+), 3210 deletions(-) rename app/Database/Migrations/{2020-05-29-120000_add_media.php => 2021-05-29-120000_add_media.php} (96%) rename app/Database/Migrations/{2020-05-29-152000_add_categories.php => 2021-05-29-152000_add_categories.php} (100%) rename app/Database/Migrations/{2020-05-30-101000_add_languages.php => 2021-05-30-101000_add_languages.php} (100%) rename app/Database/Migrations/{2020-05-30-101500_add_podcasts.php => 2021-05-30-101500_add_podcasts.php} (100%) rename app/Database/Migrations/{2020-06-05-170000_add_episodes.php => 2021-06-05-170000_add_episodes.php} (100%) rename app/Database/Migrations/{2020-06-05-190000_add_platforms.php => 2021-06-05-190000_add_platforms.php} (100%) rename app/Database/Migrations/{2020-06-05-200000_add_podcasts_platforms.php => 2021-06-05-200000_add_podcasts_platforms.php} (100%) rename app/Database/Migrations/{2020-08-17-150000_add_pages.php => 2021-08-17-150000_add_pages.php} (100%) rename app/Database/Migrations/{2020-09-29-150000_add_podcasts_categories.php => 2021-09-29-150000_add_podcasts_categories.php} (100%) rename app/Database/Migrations/{2020-12-25-120000_add_persons.php => 2021-12-25-120000_add_persons.php} (100%) rename app/Database/Migrations/{2020-12-25-130000_add_podcasts_persons.php => 2021-12-25-130000_add_podcasts_persons.php} (100%) rename app/Database/Migrations/{2020-12-25-140000_add_episodes_persons.php => 2021-12-25-140000_add_episodes_persons.php} (100%) rename app/Database/Migrations/{2020-12-25-150000_add_credits_view.php => 2021-12-25-150000_add_credits_view.php} (100%) rename app/Database/Migrations/{2021-02-23-100000_add_episode_id_to_posts.php => 2022-02-23-100000_add_episode_id_to_posts.php} (100%) rename app/Database/Migrations/{2021-03-09-113000_add_created_by_to_posts.php => 2022-03-09-113000_add_created_by_to_posts.php} (100%) delete mode 100644 app/Database/Seeds/AuthSeeder.php delete mode 100644 app/Helpers/auth_helper.php delete mode 100644 app/Models/UserModel.php create mode 100644 app/Resources/icons/shield-user.svg create mode 100644 app/Views/errors/html/error_403.php create mode 100644 docs/src/getting-started/auth.md delete mode 100644 modules/Admin/Controllers/ContributorController.php delete mode 100644 modules/Admin/Controllers/UserController.php delete mode 100644 modules/Admin/Language/id/User.php delete mode 100644 modules/Admin/Language/it/User.php delete mode 100644 modules/Admin/Language/nl/User.php delete mode 100644 modules/Admin/Language/oc/User.php delete mode 100644 modules/Admin/Language/pt/User.php delete mode 100644 modules/Admin/Language/ru/User.php delete mode 100644 modules/Admin/Language/sk/User.php delete mode 100644 modules/Admin/Language/sv/User.php create mode 100644 modules/Auth/Auth.php delete mode 100644 modules/Auth/Authorization/FlatAuthorization.php delete mode 100644 modules/Auth/Authorization/GroupModel.php delete mode 100644 modules/Auth/Authorization/PermissionModel.php create mode 100644 modules/Auth/Commands/RolesDoc.php create mode 100644 modules/Auth/Config/AuthGroups.php create mode 100644 modules/Auth/Config/AuthRoutes.php create mode 100644 modules/Auth/Config/Events.php create mode 100644 modules/Auth/Controllers/ActionController.php delete mode 100644 modules/Auth/Controllers/AuthController.php create mode 100644 modules/Auth/Controllers/ContributorController.php create mode 100644 modules/Auth/Controllers/InteractController.php create mode 100644 modules/Auth/Controllers/LoginController.php create mode 100644 modules/Auth/Controllers/MagicLinkController.php rename modules/{Admin => Auth}/Controllers/MyAccountController.php (73%) create mode 100644 modules/Auth/Controllers/RegisterController.php create mode 100644 modules/Auth/Controllers/UserController.php delete mode 100644 modules/Auth/Database/Migrations/2020-07-03-191500_add_podcasts_users.php create mode 100644 modules/Auth/Database/Migrations/2020-12-29-100000_add_is_owner_to_users.php delete mode 100644 modules/Auth/Database/Seeds/.gitkeep delete mode 100644 modules/Auth/Database/Seeds/AuthSeeder.php delete mode 100644 modules/Auth/Entities/User.php create mode 100644 modules/Auth/Helpers/auth_helper.php rename modules/{Admin => Auth}/Language/ar/Contributor.php (100%) rename modules/{Admin => Auth}/Language/ar/MyAccount.php (100%) rename modules/{Admin => Auth}/Language/ar/User.php (90%) rename modules/{Admin => Auth}/Language/br/Contributor.php (100%) rename modules/{Admin => Auth}/Language/br/MyAccount.php (100%) rename modules/{Admin => Auth}/Language/br/User.php (90%) rename modules/{Admin => Auth}/Language/ca/Contributor.php (100%) rename modules/{Admin => Auth}/Language/ca/MyAccount.php (100%) rename modules/{Admin => Auth}/Language/ca/User.php (88%) rename modules/{Admin => Auth}/Language/de/Contributor.php (100%) rename modules/{Admin => Auth}/Language/de/MyAccount.php (100%) rename modules/{Admin => Auth}/Language/de/User.php (88%) rename modules/{Admin => Auth}/Language/el/Contributor.php (100%) rename modules/{Admin => Auth}/Language/el/MyAccount.php (100%) rename modules/{Admin/Language/gd => Auth/Language/el}/User.php (89%) create mode 100644 modules/Auth/Language/en/Auth.php rename modules/{Admin/Language/sv => Auth/Language/en}/Contributor.php (70%) rename modules/{Admin => Auth}/Language/en/MyAccount.php (100%) create mode 100644 modules/Auth/Language/en/User.php rename modules/{Admin => Auth}/Language/es/Contributor.php (100%) rename modules/{Admin => Auth}/Language/es/MyAccount.php (100%) rename modules/{Admin => Auth}/Language/es/User.php (88%) rename modules/{Admin/Language/en => Auth/Language/fa}/Contributor.php (100%) rename modules/{Admin => Auth}/Language/fa/MyAccount.php (100%) rename modules/{Admin/Language/el => Auth/Language/fa}/User.php (89%) rename modules/{Admin => Auth}/Language/fr/Contributor.php (100%) rename modules/{Admin => Auth}/Language/fr/MyAccount.php (100%) rename modules/{Admin => Auth}/Language/fr/User.php (89%) rename modules/{Admin/Language/fa => Auth/Language/gd}/Contributor.php (100%) rename modules/{Admin => Auth}/Language/gd/MyAccount.php (100%) rename modules/{Admin/Language/fa => Auth/Language/gd}/User.php (89%) rename modules/{Admin => Auth}/Language/gl/Contributor.php (100%) rename modules/{Admin => Auth}/Language/gl/MyAccount.php (100%) rename modules/{Admin => Auth}/Language/gl/User.php (88%) rename modules/{Admin/Language/gd => Auth/Language/id}/Contributor.php (100%) rename modules/{Admin => Auth}/Language/id/MyAccount.php (100%) rename modules/{Admin/Language/en => Auth/Language/id}/User.php (89%) rename modules/{Admin => Auth}/Language/it/Contributor.php (100%) rename modules/{Admin => Auth}/Language/it/MyAccount.php (100%) create mode 100644 modules/Auth/Language/it/User.php rename modules/{Admin => Auth}/Language/nl/Contributor.php (100%) rename modules/{Admin => Auth}/Language/nl/MyAccount.php (100%) create mode 100644 modules/Auth/Language/nl/User.php rename modules/{Admin => Auth}/Language/nn-NO/Contributor.php (100%) rename modules/{Admin => Auth}/Language/nn-NO/MyAccount.php (100%) rename modules/{Admin => Auth}/Language/nn-NO/User.php (89%) rename modules/{Admin/Language/id => Auth/Language/oc}/Contributor.php (100%) rename modules/{Admin => Auth}/Language/oc/MyAccount.php (100%) create mode 100644 modules/Auth/Language/oc/User.php rename modules/{Admin => Auth}/Language/pl/Contributor.php (100%) rename modules/{Admin => Auth}/Language/pl/MyAccount.php (100%) rename modules/{Admin => Auth}/Language/pl/User.php (89%) rename modules/{Admin => Auth}/Language/pt-BR/Contributor.php (100%) rename modules/{Admin => Auth}/Language/pt-BR/MyAccount.php (100%) rename modules/{Admin => Auth}/Language/pt-BR/User.php (89%) rename modules/{Admin/Language/oc => Auth/Language/pt}/Contributor.php (100%) rename modules/{Admin => Auth}/Language/pt/MyAccount.php (100%) create mode 100644 modules/Auth/Language/pt/User.php rename modules/{Admin/Language/pt => Auth/Language/ru}/Contributor.php (100%) rename modules/{Admin => Auth}/Language/ru/MyAccount.php (100%) create mode 100644 modules/Auth/Language/ru/User.php rename modules/{Admin => Auth}/Language/sk/Contributor.php (100%) rename modules/{Admin => Auth}/Language/sk/MyAccount.php (100%) create mode 100644 modules/Auth/Language/sk/User.php rename modules/{Admin/Language/ru => Auth/Language/sv}/Contributor.php (100%) rename modules/{Admin => Auth}/Language/sv/MyAccount.php (100%) create mode 100644 modules/Auth/Language/sv/User.php rename modules/{Admin => Auth}/Language/zh-Hans/Contributor.php (100%) rename modules/{Admin => Auth}/Language/zh-Hans/MyAccount.php (100%) rename modules/{Admin => Auth}/Language/zh-Hans/User.php (90%) create mode 100644 modules/Auth/Models/UserModel.php create mode 100644 themes/cp_admin/contributor/delete.php create mode 100644 themes/cp_admin/user/delete.php create mode 100644 themes/cp_auth/email_2fa_show.php create mode 100644 themes/cp_auth/email_2fa_verify.php create mode 100644 themes/cp_auth/email_activate_show.php delete mode 100644 themes/cp_auth/emails/activation.php create mode 100644 themes/cp_auth/emails/email_2fa_email.php create mode 100644 themes/cp_auth/emails/email_activate_email.php delete mode 100644 themes/cp_auth/emails/forgot.php create mode 100644 themes/cp_auth/emails/magic_link_email.php create mode 100644 themes/cp_auth/emails/welcome_email.php delete mode 100644 themes/cp_auth/forgot.php create mode 100644 themes/cp_auth/magic_link_form.php create mode 100644 themes/cp_auth/magic_link_message.php create mode 100644 themes/cp_auth/magic_link_set_password.php delete mode 100644 themes/cp_auth/reset.php diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index fbd4eb36..afed9ca1 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -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" ] } diff --git a/app/Common.php b/app/Common.php index 1f3b17c2..6876d968 100644 --- a/app/Common.php +++ b/app/Common.php @@ -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 $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 diff --git a/app/Config/Email.php b/app/Config/Email.php index 2f651dca..c8713ee8 100644 --- a/app/Config/Email.php +++ b/app/Config/Email.php @@ -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.) diff --git a/app/Config/Events.php b/app/Config/Events.php index 004e1453..07332c3d 100644 --- a/app/Config/Events.php +++ b/app/Config/Events.php @@ -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 diff --git a/app/Config/Filters.php b/app/Config/Filters.php index 54fa52b3..fc706492 100644 --- a/app/Config/Filters.php +++ b/app/Config/Filters.php @@ -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' => [ diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 01530037..803c0625 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -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)', diff --git a/app/Config/Security.php b/app/Config/Security.php index 0b986a41..29b51d2f 100644 --- a/app/Config/Security.php +++ b/app/Config/Security.php @@ -17,7 +17,7 @@ class Security extends BaseConfig * * @var 'cookie'|'session' */ - public string $csrfProtection = 'cookie'; + public string $csrfProtection = 'session'; /** * -------------------------------------------------------------------------- diff --git a/app/Config/Validation.php b/app/Config/Validation.php index ae8d9e69..d9157b1a 100644 --- a/app/Config/Validation.php +++ b/app/Config/Validation.php @@ -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, ]; /** diff --git a/app/Controllers/ActorController.php b/app/Controllers/ActorController.php index 9e2138e1..3ac8547c 100644 --- a/app/Controllers/ActorController.php +++ b/app/Controllers/ActorController.php @@ -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); } diff --git a/app/Controllers/BaseController.php b/app/Controllers/BaseController.php index fd82bc08..fba12dd8 100644 --- a/app/Controllers/BaseController.php +++ b/app/Controllers/BaseController.php @@ -34,5 +34,7 @@ abstract class BaseController extends Controller parent::initController($request, $response, $logger); Theme::setTheme('app'); + + $this->helpers = array_merge($this->helpers, ['setting']); } } diff --git a/app/Controllers/CreditsController.php b/app/Controllers/CreditsController.php index 7bfe5b84..1d41afd2 100644 --- a/app/Controllers/CreditsController.php +++ b/app/Controllers/CreditsController.php @@ -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))) { diff --git a/app/Controllers/EpisodeCommentController.php b/app/Controllers/EpisodeCommentController.php index b0617336..9aaf2b5a 100644 --- a/app/Controllers/EpisodeCommentController.php +++ b/app/Controllers/EpisodeCommentController.php @@ -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); } diff --git a/app/Controllers/EpisodeController.php b/app/Controllers/EpisodeController.php index 3d30b4e5..6b9c5cf0 100644 --- a/app/Controllers/EpisodeController.php +++ b/app/Controllers/EpisodeController.php @@ -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); } diff --git a/app/Controllers/HomeController.php b/app/Controllers/HomeController.php index 724a274a..b2f28b55 100644 --- a/app/Controllers/HomeController.php +++ b/app/Controllers/HomeController.php @@ -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]); diff --git a/app/Controllers/MapController.php b/app/Controllers/MapController.php index 2a8c06b0..cf64312e 100644 --- a/app/Controllers/MapController.php +++ b/app/Controllers/MapController.php @@ -24,7 +24,8 @@ class MapController extends BaseController 'map', service('request') ->getLocale(), - can_user_interact() ? 'authenticated' : null, + auth() + ->loggedIn() ? 'authenticated' : null, ]), ); diff --git a/app/Controllers/PageController.php b/app/Controllers/PageController.php index 7fc1424a..27f71e95 100644 --- a/app/Controllers/PageController.php +++ b/app/Controllers/PageController.php @@ -44,7 +44,8 @@ class PageController extends BaseController $this->page->slug, service('request') ->getLocale(), - can_user_interact() ? 'authenticated' : null, + auth() + ->loggedIn() ? 'authenticated' : null, ]), ); diff --git a/app/Controllers/PodcastController.php b/app/Controllers/PodcastController.php index 4e2450f4..06377d4b 100644 --- a/app/Controllers/PodcastController.php +++ b/app/Controllers/PodcastController.php @@ -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); } diff --git a/app/Controllers/PostController.php b/app/Controllers/PostController.php index 3db4ab27..b2bf8e8d 100644 --- a/app/Controllers/PostController.php +++ b/app/Controllers/PostController.php @@ -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); } diff --git a/app/Database/Migrations/2020-05-29-120000_add_media.php b/app/Database/Migrations/2021-05-29-120000_add_media.php similarity index 96% rename from app/Database/Migrations/2020-05-29-120000_add_media.php rename to app/Database/Migrations/2021-05-29-120000_add_media.php index 177827c5..732f51e2 100644 --- a/app/Database/Migrations/2020-05-29-120000_add_media.php +++ b/app/Database/Migrations/2021-05-29-120000_add_media.php @@ -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' => [ diff --git a/app/Database/Migrations/2020-05-29-152000_add_categories.php b/app/Database/Migrations/2021-05-29-152000_add_categories.php similarity index 100% rename from app/Database/Migrations/2020-05-29-152000_add_categories.php rename to app/Database/Migrations/2021-05-29-152000_add_categories.php diff --git a/app/Database/Migrations/2020-05-30-101000_add_languages.php b/app/Database/Migrations/2021-05-30-101000_add_languages.php similarity index 100% rename from app/Database/Migrations/2020-05-30-101000_add_languages.php rename to app/Database/Migrations/2021-05-30-101000_add_languages.php diff --git a/app/Database/Migrations/2020-05-30-101500_add_podcasts.php b/app/Database/Migrations/2021-05-30-101500_add_podcasts.php similarity index 100% rename from app/Database/Migrations/2020-05-30-101500_add_podcasts.php rename to app/Database/Migrations/2021-05-30-101500_add_podcasts.php diff --git a/app/Database/Migrations/2020-06-05-170000_add_episodes.php b/app/Database/Migrations/2021-06-05-170000_add_episodes.php similarity index 100% rename from app/Database/Migrations/2020-06-05-170000_add_episodes.php rename to app/Database/Migrations/2021-06-05-170000_add_episodes.php diff --git a/app/Database/Migrations/2020-06-05-190000_add_platforms.php b/app/Database/Migrations/2021-06-05-190000_add_platforms.php similarity index 100% rename from app/Database/Migrations/2020-06-05-190000_add_platforms.php rename to app/Database/Migrations/2021-06-05-190000_add_platforms.php diff --git a/app/Database/Migrations/2020-06-05-200000_add_podcasts_platforms.php b/app/Database/Migrations/2021-06-05-200000_add_podcasts_platforms.php similarity index 100% rename from app/Database/Migrations/2020-06-05-200000_add_podcasts_platforms.php rename to app/Database/Migrations/2021-06-05-200000_add_podcasts_platforms.php diff --git a/app/Database/Migrations/2020-08-17-150000_add_pages.php b/app/Database/Migrations/2021-08-17-150000_add_pages.php similarity index 100% rename from app/Database/Migrations/2020-08-17-150000_add_pages.php rename to app/Database/Migrations/2021-08-17-150000_add_pages.php diff --git a/app/Database/Migrations/2020-09-29-150000_add_podcasts_categories.php b/app/Database/Migrations/2021-09-29-150000_add_podcasts_categories.php similarity index 100% rename from app/Database/Migrations/2020-09-29-150000_add_podcasts_categories.php rename to app/Database/Migrations/2021-09-29-150000_add_podcasts_categories.php diff --git a/app/Database/Migrations/2020-12-25-120000_add_persons.php b/app/Database/Migrations/2021-12-25-120000_add_persons.php similarity index 100% rename from app/Database/Migrations/2020-12-25-120000_add_persons.php rename to app/Database/Migrations/2021-12-25-120000_add_persons.php diff --git a/app/Database/Migrations/2020-12-25-130000_add_podcasts_persons.php b/app/Database/Migrations/2021-12-25-130000_add_podcasts_persons.php similarity index 100% rename from app/Database/Migrations/2020-12-25-130000_add_podcasts_persons.php rename to app/Database/Migrations/2021-12-25-130000_add_podcasts_persons.php diff --git a/app/Database/Migrations/2020-12-25-140000_add_episodes_persons.php b/app/Database/Migrations/2021-12-25-140000_add_episodes_persons.php similarity index 100% rename from app/Database/Migrations/2020-12-25-140000_add_episodes_persons.php rename to app/Database/Migrations/2021-12-25-140000_add_episodes_persons.php diff --git a/app/Database/Migrations/2020-12-25-150000_add_credits_view.php b/app/Database/Migrations/2021-12-25-150000_add_credits_view.php similarity index 100% rename from app/Database/Migrations/2020-12-25-150000_add_credits_view.php rename to app/Database/Migrations/2021-12-25-150000_add_credits_view.php diff --git a/app/Database/Migrations/2021-02-23-100000_add_episode_id_to_posts.php b/app/Database/Migrations/2022-02-23-100000_add_episode_id_to_posts.php similarity index 100% rename from app/Database/Migrations/2021-02-23-100000_add_episode_id_to_posts.php rename to app/Database/Migrations/2022-02-23-100000_add_episode_id_to_posts.php diff --git a/app/Database/Migrations/2021-03-09-113000_add_created_by_to_posts.php b/app/Database/Migrations/2022-03-09-113000_add_created_by_to_posts.php similarity index 100% rename from app/Database/Migrations/2021-03-09-113000_add_created_by_to_posts.php rename to app/Database/Migrations/2022-03-09-113000_add_created_by_to_posts.php diff --git a/app/Database/Seeds/AppSeeder.php b/app/Database/Seeds/AppSeeder.php index 4be06109..2c52a18d 100644 --- a/app/Database/Seeds/AppSeeder.php +++ b/app/Database/Seeds/AppSeeder.php @@ -18,7 +18,6 @@ class AppSeeder extends Seeder { public function run(): void { - $this->call('AuthSeeder'); $this->call('CategorySeeder'); $this->call('LanguageSeeder'); $this->call('PlatformSeeder'); diff --git a/app/Database/Seeds/AuthSeeder.php b/app/Database/Seeds/AuthSeeder.php deleted file mode 100644 index 32181d18..00000000 --- a/app/Database/Seeds/AuthSeeder.php +++ /dev/null @@ -1,328 +0,0 @@ -[] - */ - 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[]> - */ - 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[] $dataGroups - */ - public static function getGroupIdByName(string $name, array $dataGroups): ?int - { - foreach ($dataGroups as $group) { - if ($group['name'] === $name) { - return $group['id']; - } - } - - return null; - } -} diff --git a/app/Database/Seeds/TestSeeder.php b/app/Database/Seeds/TestSeeder.php index e37198db..57a52219 100644 --- a/app/Database/Seeds/TestSeeder.php +++ b/app/Database/Seeds/TestSeeder.php @@ -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'), ]); } } diff --git a/app/Entities/Clip/BaseClip.php b/app/Entities/Clip/BaseClip.php index efe88b4e..51ccc0a5 100644 --- a/app/Entities/Clip/BaseClip.php +++ b/app/Entities/Clip/BaseClip.php @@ -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 diff --git a/app/Entities/Podcast.php b/app/Entities/Podcast.php index 22e5d3b1..efcdb88d 100644 --- a/app/Entities/Podcast.php +++ b/app/Entities/Podcast.php @@ -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 */ diff --git a/app/Helpers/auth_helper.php b/app/Helpers/auth_helper.php deleted file mode 100644 index 3c1bc815..00000000 --- a/app/Helpers/auth_helper.php +++ /dev/null @@ -1,89 +0,0 @@ -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(); - } -} diff --git a/app/Models/PodcastModel.php b/app/Models/PodcastModel.php index 78d52672..300ffdf3 100644 --- a/app/Models/PodcastModel.php +++ b/app/Models/PodcastModel.php @@ -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; } diff --git a/app/Models/UserModel.php b/app/Models/UserModel.php deleted file mode 100644 index 13685b89..00000000 --- a/app/Models/UserModel.php +++ /dev/null @@ -1,55 +0,0 @@ -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(); - } -} diff --git a/app/Resources/icons/shield-user.svg b/app/Resources/icons/shield-user.svg new file mode 100644 index 00000000..37cf8289 --- /dev/null +++ b/app/Resources/icons/shield-user.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/Views/errors/html/error_403.php b/app/Views/errors/html/error_403.php new file mode 100644 index 00000000..0a8d31b4 --- /dev/null +++ b/app/Views/errors/html/error_403.php @@ -0,0 +1,29 @@ + + + + + + + + + 403 Forbidden + ' /> + asset('styles/index.css', 'css') ?> + + + + +

403 - Forbidden

+ +

+ + + + You do not have sufficient permissions to access that page. + +

+ + + + diff --git a/app/Views/errors/html/error_404.php b/app/Views/errors/html/error_404.php index ed288ac1..bbf30689 100644 --- a/app/Views/errors/html/error_404.php +++ b/app/Views/errors/html/error_404.php @@ -14,7 +14,7 @@ -

404 - File Not Found

+

404 - File Not Found

diff --git a/app/Views/errors/html/production.php b/app/Views/errors/html/production.php index 1ddf8306..0b6bc13b 100644 --- a/app/Views/errors/html/production.php +++ b/app/Views/errors/html/production.php @@ -10,14 +10,14 @@ Whoops! ' /> asset('styles/index.css', 'css') ?> - isLoggedIn()): ?> + loggedIn()): ?> asset('js/error.ts', 'js') ?> - isLoggedIn()): ?> + loggedIn()): ?>

diff --git a/composer.json b/composer.json index 318e1240..1519add3 100644 --- a/composer.json +++ b/composer.json @@ -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", diff --git a/composer.lock b/composer.lock index 0eaf6f04..253d0e08 100644 --- a/composer.lock +++ b/composer.lock @@ -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, diff --git a/docs/.gitlab-ci.yml b/docs/.gitlab-ci.yml index d7f739a2..f9c992b7 100644 --- a/docs/.gitlab-ci.yml +++ b/docs/.gitlab-ci.yml @@ -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 diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index c76d8335..f74cf504 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -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" }, ], }, { diff --git a/docs/src/getting-started/auth.md b/docs/src/getting-started/auth.md new file mode 100644 index 00000000..00eeb819 --- /dev/null +++ b/docs/src/getting-started/auth.md @@ -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 + + + +| 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 | + + + +### Instance permissions + + + +| 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. | + + + +## 2. Per podcast roles and permissions + +### Per podcast roles + + + +| 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 | + + + +### Per podcast permissions + + + +| 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}. | + + diff --git a/modules/Admin/Config/Routes.php b/modules/Admin/Config/Routes.php index ad767a0d..7bf22166 100644 --- a/modules/Admin/Config/Routes.php +++ b/modules/Admin/Config/Routes.php @@ -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'); - }); }, ); diff --git a/modules/Admin/Controllers/ContributorController.php b/modules/Admin/Controllers/ContributorController.php deleted file mode 100644 index 71dfc073..00000000 --- a/modules/Admin/Controllers/ContributorController.php +++ /dev/null @@ -1,203 +0,0 @@ -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, - ]), - ); - } -} diff --git a/modules/Admin/Controllers/EpisodeController.php b/modules/Admin/Controllers/EpisodeController.php index 6c14d7f3..821c7bd7 100644 --- a/modules/Admin/Controllers/EpisodeController.php +++ b/modules/Admin/Controllers/EpisodeController.php @@ -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); diff --git a/modules/Admin/Controllers/EpisodePersonController.php b/modules/Admin/Controllers/EpisodePersonController.php index ee46c04d..0e0e8625 100644 --- a/modules/Admin/Controllers/EpisodePersonController.php +++ b/modules/Admin/Controllers/EpisodePersonController.php @@ -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); diff --git a/modules/Admin/Controllers/NotificationController.php b/modules/Admin/Controllers/NotificationController.php index f4bc5558..ea04b6c3 100644 --- a/modules/Admin/Controllers/NotificationController.php +++ b/modules/Admin/Controllers/NotificationController.php @@ -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); diff --git a/modules/Admin/Controllers/PodcastController.php b/modules/Admin/Controllers/PodcastController.php index 7e9726af..4acc02fa 100644 --- a/modules/Admin/Controllers/PodcastController.php +++ b/modules/Admin/Controllers/PodcastController.php @@ -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); diff --git a/modules/Admin/Controllers/PodcastImportController.php b/modules/Admin/Controllers/PodcastImportController.php index 39b0f09a..f7515a4b 100644 --- a/modules/Admin/Controllers/PodcastImportController.php +++ b/modules/Admin/Controllers/PodcastImportController.php @@ -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; diff --git a/modules/Admin/Controllers/PodcastPersonController.php b/modules/Admin/Controllers/PodcastPersonController.php index 722ba28a..1c5d8213 100644 --- a/modules/Admin/Controllers/PodcastPersonController.php +++ b/modules/Admin/Controllers/PodcastPersonController.php @@ -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); } diff --git a/modules/Admin/Controllers/PodcastPlatformController.php b/modules/Admin/Controllers/PodcastPlatformController.php index 08b8f961..61936420 100644 --- a/modules/Admin/Controllers/PodcastPlatformController.php +++ b/modules/Admin/Controllers/PodcastPlatformController.php @@ -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); diff --git a/modules/Admin/Controllers/SoundbiteController.php b/modules/Admin/Controllers/SoundbiteController.php index 0016de24..e389e645 100644 --- a/modules/Admin/Controllers/SoundbiteController.php +++ b/modules/Admin/Controllers/SoundbiteController.php @@ -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); diff --git a/modules/Admin/Controllers/UserController.php b/modules/Admin/Controllers/UserController.php deleted file mode 100644 index 3dc70d44..00000000 --- a/modules/Admin/Controllers/UserController.php +++ /dev/null @@ -1,258 +0,0 @@ -{$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, - ])); - } -} diff --git a/modules/Admin/Controllers/VideoClipsController.php b/modules/Admin/Controllers/VideoClipsController.php index 49878800..02ab95cf 100644 --- a/modules/Admin/Controllers/VideoClipsController.php +++ b/modules/Admin/Controllers/VideoClipsController.php @@ -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, ]); diff --git a/modules/Admin/Language/en/Breadcrumb.php b/modules/Admin/Language/en/Breadcrumb.php index f3269bfa..823ccd65 100644 --- a/modules/Admin/Language/en/Breadcrumb.php +++ b/modules/Admin/Language/en/Breadcrumb.php @@ -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', diff --git a/modules/Admin/Language/id/User.php b/modules/Admin/Language/id/User.php deleted file mode 100644 index 585d6799..00000000 --- a/modules/Admin/Language/id/User.php +++ /dev/null @@ -1,56 +0,0 @@ - "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.', - ], -]; diff --git a/modules/Admin/Language/it/User.php b/modules/Admin/Language/it/User.php deleted file mode 100644 index 585d6799..00000000 --- a/modules/Admin/Language/it/User.php +++ /dev/null @@ -1,56 +0,0 @@ - "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.', - ], -]; diff --git a/modules/Admin/Language/nl/User.php b/modules/Admin/Language/nl/User.php deleted file mode 100644 index 585d6799..00000000 --- a/modules/Admin/Language/nl/User.php +++ /dev/null @@ -1,56 +0,0 @@ - "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.', - ], -]; diff --git a/modules/Admin/Language/oc/User.php b/modules/Admin/Language/oc/User.php deleted file mode 100644 index 585d6799..00000000 --- a/modules/Admin/Language/oc/User.php +++ /dev/null @@ -1,56 +0,0 @@ - "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.', - ], -]; diff --git a/modules/Admin/Language/pt/User.php b/modules/Admin/Language/pt/User.php deleted file mode 100644 index 585d6799..00000000 --- a/modules/Admin/Language/pt/User.php +++ /dev/null @@ -1,56 +0,0 @@ - "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.', - ], -]; diff --git a/modules/Admin/Language/ru/User.php b/modules/Admin/Language/ru/User.php deleted file mode 100644 index 585d6799..00000000 --- a/modules/Admin/Language/ru/User.php +++ /dev/null @@ -1,56 +0,0 @@ - "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.', - ], -]; diff --git a/modules/Admin/Language/sk/User.php b/modules/Admin/Language/sk/User.php deleted file mode 100644 index 585d6799..00000000 --- a/modules/Admin/Language/sk/User.php +++ /dev/null @@ -1,56 +0,0 @@ - "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.', - ], -]; diff --git a/modules/Admin/Language/sv/User.php b/modules/Admin/Language/sv/User.php deleted file mode 100644 index 585d6799..00000000 --- a/modules/Admin/Language/sv/User.php +++ /dev/null @@ -1,56 +0,0 @@ - "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.', - ], -]; diff --git a/modules/Analytics/Config/Analytics.php b/modules/Analytics/Config/Analytics.php index ab263a08..6caea6b0 100644 --- a/modules/Analytics/Config/Analytics.php +++ b/modules/Analytics/Config/Analytics.php @@ -20,9 +20,9 @@ class Analytics extends BaseConfig * @var array */ 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', ]; /** diff --git a/modules/Auth/Auth.php b/modules/Auth/Auth.php new file mode 100644 index 00000000..09e32581 --- /dev/null +++ b/modules/Auth/Auth.php @@ -0,0 +1,42 @@ +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); + } + } + } + }); + } +} diff --git a/modules/Auth/Authorization/FlatAuthorization.php b/modules/Auth/Authorization/FlatAuthorization.php deleted file mode 100644 index 933a2728..00000000 --- a/modules/Auth/Authorization/FlatAuthorization.php +++ /dev/null @@ -1,54 +0,0 @@ -getPermissionID($permission); - - if (! is_numeric($permissionId)) { - return false; - } - - return $this->permissionModel->doesGroupHavePermission($groupId, $permissionId); - } - - /** - * Makes user part of given groups. - * - * @param array $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; - } -} diff --git a/modules/Auth/Authorization/GroupModel.php b/modules/Auth/Authorization/GroupModel.php deleted file mode 100644 index 74618542..00000000 --- a/modules/Auth/Authorization/GroupModel.php +++ /dev/null @@ -1,30 +0,0 @@ -select('auth_groups.*') - ->like('name', 'podcast_', 'after') - ->findAll(); - } - - /** - * @return mixed[] - */ - public function getUserRoles(): array - { - return $this->select('auth_groups.*') - ->notLike('name', 'podcast_', 'after') - ->findAll(); - } -} diff --git a/modules/Auth/Authorization/PermissionModel.php b/modules/Auth/Authorization/PermissionModel.php deleted file mode 100644 index 01106c10..00000000 --- a/modules/Auth/Authorization/PermissionModel.php +++ /dev/null @@ -1,53 +0,0 @@ -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 - */ - 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; - } -} diff --git a/modules/Auth/Commands/RolesDoc.php b/modules/Auth/Commands/RolesDoc.php new file mode 100644 index 00000000..5326c2b1 --- /dev/null +++ b/modules/Auth/Commands/RolesDoc.php @@ -0,0 +1,184 @@ + + */ + 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\s]*()/'; + + $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 and + + $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]; + } +} diff --git a/modules/Auth/Config/Auth.php b/modules/Auth/Config/Auth.php index 11ffea8a..e9017374 100644 --- a/modules/Auth/Config/Auth.php +++ b/modules/Auth/Config/Auth.php @@ -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 */ - 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 */ - 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|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 + */ + 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); + } } diff --git a/modules/Auth/Config/AuthGroups.php b/modules/Auth/Config/AuthGroups.php new file mode 100644 index 00000000..cfbcbada --- /dev/null +++ b/modules/Auth/Config/AuthGroups.php @@ -0,0 +1,285 @@ +attempt($credentials); + * + * @var array> + */ + 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 + */ + public array $permissions = []; + + /** + * -------------------------------------------------------------------- + * Permissions Matrix + * -------------------------------------------------------------------- + * Maps permissions to groups. + * @var array> + */ + public array $matrix = []; + + /** + * @var array> + */ + public array $instanceGroups = []; + + /** + * @var array + */ + public array $instancePermissions = []; + + /** + * @var array> + */ + public array $podcastGroups = []; + + /** + * @var array + */ + 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> + */ + 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 + */ + 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; + } + } + } +} diff --git a/modules/Auth/Config/AuthRoutes.php b/modules/Auth/Config/AuthRoutes.php new file mode 100644 index 00000000..314203fa --- /dev/null +++ b/modules/Auth/Config/AuthRoutes.php @@ -0,0 +1,42 @@ + [ + ['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'], + ], + ]; +} diff --git a/modules/Auth/Config/Events.php b/modules/Auth/Config/Events.php new file mode 100644 index 00000000..36b0cf2a --- /dev/null +++ b/modules/Auth/Config/Events.php @@ -0,0 +1,14 @@ +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', + ], + ); + }); + }); } ); diff --git a/modules/Auth/Config/Services.php b/modules/Auth/Config/Services.php index 71b8129c..f3dd45f7 100644 --- a/modules/Auth/Config/Services.php +++ b/modules/Auth/Config/Services.php @@ -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)); } } diff --git a/modules/Auth/Controllers/ActionController.php b/modules/Auth/Controllers/ActionController.php new file mode 100644 index 00000000..e9db4ab3 --- /dev/null +++ b/modules/Auth/Controllers/ActionController.php @@ -0,0 +1,29 @@ +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(); - } -} diff --git a/modules/Auth/Controllers/ContributorController.php b/modules/Auth/Controllers/ContributorController.php new file mode 100644 index 00000000..d40f0581 --- /dev/null +++ b/modules/Auth/Controllers/ContributorController.php @@ -0,0 +1,243 @@ +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, + ]), + ); + } +} diff --git a/modules/Auth/Controllers/InteractController.php b/modules/Auth/Controllers/InteractController.php new file mode 100644 index 00000000..b917149b --- /dev/null +++ b/modules/Auth/Controllers/InteractController.php @@ -0,0 +1,36 @@ + '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(); + } +} diff --git a/modules/Auth/Controllers/LoginController.php b/modules/Auth/Controllers/LoginController.php new file mode 100644 index 00000000..75163cd6 --- /dev/null +++ b/modules/Auth/Controllers/LoginController.php @@ -0,0 +1,24 @@ +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')); + } +} diff --git a/modules/Admin/Controllers/MyAccountController.php b/modules/Auth/Controllers/MyAccountController.php similarity index 73% rename from modules/Admin/Controllers/MyAccountController.php rename to modules/Auth/Controllers/MyAccountController.php index 8ffa0247..afa368ca 100644 --- a/modules/Admin/Controllers/MyAccountController.php +++ b/modules/Auth/Controllers/MyAccountController.php @@ -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() diff --git a/modules/Auth/Controllers/RegisterController.php b/modules/Auth/Controllers/RegisterController.php new file mode 100644 index 00000000..b54c0d9c --- /dev/null +++ b/modules/Auth/Controllers/RegisterController.php @@ -0,0 +1,29 @@ +{$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, + ])); + } +} diff --git a/modules/Auth/Database/Migrations/2020-07-03-191500_add_podcasts_users.php b/modules/Auth/Database/Migrations/2020-07-03-191500_add_podcasts_users.php deleted file mode 100644 index b6535c4a..00000000 --- a/modules/Auth/Database/Migrations/2020-07-03-191500_add_podcasts_users.php +++ /dev/null @@ -1,46 +0,0 @@ -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'); - } -} diff --git a/modules/Auth/Database/Migrations/2020-12-29-100000_add_is_owner_to_users.php b/modules/Auth/Database/Migrations/2020-12-29-100000_add_is_owner_to_users.php new file mode 100644 index 00000000..aa587ba4 --- /dev/null +++ b/modules/Auth/Database/Migrations/2020-12-29-100000_add_is_owner_to_users.php @@ -0,0 +1,31 @@ + [ + '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); + } +} diff --git a/modules/Auth/Database/Seeds/.gitkeep b/modules/Auth/Database/Seeds/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/Auth/Database/Seeds/AuthSeeder.php b/modules/Auth/Database/Seeds/AuthSeeder.php deleted file mode 100644 index 976c904b..00000000 --- a/modules/Auth/Database/Seeds/AuthSeeder.php +++ /dev/null @@ -1,302 +0,0 @@ -[] - */ - 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[]> - */ - 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[] $dataGroups - */ - public static function getGroupIdByName(string $name, array $dataGroups): ?int - { - foreach ($dataGroups as $group) { - if ($group['name'] === $name) { - return $group['id']; - } - } - - return null; - } -} diff --git a/modules/Auth/Entities/User.php b/modules/Auth/Entities/User.php deleted file mode 100644 index 312bde68..00000000 --- a/modules/Auth/Entities/User.php +++ /dev/null @@ -1,109 +0,0 @@ - - */ - 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'); - } -} diff --git a/modules/Auth/Filters/PermissionFilter.php b/modules/Auth/Filters/PermissionFilter.php index 19824798..44d7b785 100644 --- a/modules/Auth/Filters/PermissionFilter.php +++ b/modules/Auth/Filters/PermissionFilter.php @@ -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); } } diff --git a/modules/Auth/Helpers/auth_helper.php b/modules/Auth/Helpers/auth_helper.php new file mode 100644 index 00000000..f382e3f4 --- /dev/null +++ b/modules/Auth/Helpers/auth_helper.php @@ -0,0 +1,296 @@ +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]; + } +} diff --git a/modules/Admin/Language/ar/Contributor.php b/modules/Auth/Language/ar/Contributor.php similarity index 100% rename from modules/Admin/Language/ar/Contributor.php rename to modules/Auth/Language/ar/Contributor.php diff --git a/modules/Admin/Language/ar/MyAccount.php b/modules/Auth/Language/ar/MyAccount.php similarity index 100% rename from modules/Admin/Language/ar/MyAccount.php rename to modules/Auth/Language/ar/MyAccount.php diff --git a/modules/Admin/Language/ar/User.php b/modules/Auth/Language/ar/User.php similarity index 90% rename from modules/Admin/Language/ar/User.php rename to modules/Auth/Language/ar/User.php index f2a33409..7607ad97 100644 --- a/modules/Admin/Language/ar/User.php +++ b/modules/Auth/Language/ar/User.php @@ -10,7 +10,6 @@ declare(strict_types=1); return [ 'edit_roles' => "Edit {username}'s roles", - 'forcePassReset' => 'Force pass reset', 'ban' => 'Ban', 'unban' => 'Unban', 'delete' => 'احذف', @@ -39,10 +38,8 @@ return [ 'messages' => [ 'createSuccess' => 'User created successfully! {username} will be prompted with a password reset upon first authentication.', - 'rolesEditSuccess' => + 'roleEditSuccess' => "{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' => diff --git a/modules/Admin/Language/br/Contributor.php b/modules/Auth/Language/br/Contributor.php similarity index 100% rename from modules/Admin/Language/br/Contributor.php rename to modules/Auth/Language/br/Contributor.php diff --git a/modules/Admin/Language/br/MyAccount.php b/modules/Auth/Language/br/MyAccount.php similarity index 100% rename from modules/Admin/Language/br/MyAccount.php rename to modules/Auth/Language/br/MyAccount.php diff --git a/modules/Admin/Language/br/User.php b/modules/Auth/Language/br/User.php similarity index 90% rename from modules/Admin/Language/br/User.php rename to modules/Auth/Language/br/User.php index 314e676a..1fa37c2a 100644 --- a/modules/Admin/Language/br/User.php +++ b/modules/Auth/Language/br/User.php @@ -10,7 +10,6 @@ declare(strict_types=1); return [ 'edit_roles' => "Kemm rolloù {username}", - 'forcePassReset' => 'Force pass reset', 'ban' => 'Ban', 'unban' => 'Unban', 'delete' => 'Dilemel', @@ -39,10 +38,8 @@ return [ 'messages' => [ 'createSuccess' => 'User created successfully! {username} will be prompted with a password reset upon first authentication.', - 'rolesEditSuccess' => + 'roleEditSuccess' => "{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' => diff --git a/modules/Admin/Language/ca/Contributor.php b/modules/Auth/Language/ca/Contributor.php similarity index 100% rename from modules/Admin/Language/ca/Contributor.php rename to modules/Auth/Language/ca/Contributor.php diff --git a/modules/Admin/Language/ca/MyAccount.php b/modules/Auth/Language/ca/MyAccount.php similarity index 100% rename from modules/Admin/Language/ca/MyAccount.php rename to modules/Auth/Language/ca/MyAccount.php diff --git a/modules/Admin/Language/ca/User.php b/modules/Auth/Language/ca/User.php similarity index 88% rename from modules/Admin/Language/ca/User.php rename to modules/Auth/Language/ca/User.php index 9e2f172a..d0065c45 100644 --- a/modules/Admin/Language/ca/User.php +++ b/modules/Auth/Language/ca/User.php @@ -10,7 +10,6 @@ declare(strict_types=1); return [ 'edit_roles' => "Editar els rols de {username}", - 'forcePassReset' => 'Força el restabliment de la contrasenya', 'ban' => 'Bandejar', 'unban' => 'Re-admetre', 'delete' => 'Eliminar', @@ -39,10 +38,8 @@ return [ 'messages' => [ 'createSuccess' => 'S\'ha creat l\'usuari! Es demanarà a {username} un restabliment de la contrasenya durant la primera autenticació.', - 'rolesEditSuccess' => + 'roleEditSuccess' => "S'han actualitzat correctament els rols de {username}.", - 'forcePassResetSuccess' => - 'Es demanarà a {username} un restabliment de contrasenya durant la següent visita.', 'banSuccess' => '{username} ha estat bandejat.', 'unbanSuccess' => '{username} ha estat desbandejat.', 'editOwnerError' => diff --git a/modules/Admin/Language/de/Contributor.php b/modules/Auth/Language/de/Contributor.php similarity index 100% rename from modules/Admin/Language/de/Contributor.php rename to modules/Auth/Language/de/Contributor.php diff --git a/modules/Admin/Language/de/MyAccount.php b/modules/Auth/Language/de/MyAccount.php similarity index 100% rename from modules/Admin/Language/de/MyAccount.php rename to modules/Auth/Language/de/MyAccount.php diff --git a/modules/Admin/Language/de/User.php b/modules/Auth/Language/de/User.php similarity index 88% rename from modules/Admin/Language/de/User.php rename to modules/Auth/Language/de/User.php index a4d261be..f6337ed4 100644 --- a/modules/Admin/Language/de/User.php +++ b/modules/Auth/Language/de/User.php @@ -10,7 +10,6 @@ declare(strict_types=1); return [ 'edit_roles' => "Bearbeite {username}'s Rollen", - 'forcePassReset' => 'Erzwinge Pass-Zurücksetzung', 'ban' => 'Bannen', 'unban' => 'Entbannen', 'delete' => 'Löschen', @@ -39,10 +38,8 @@ return [ 'messages' => [ 'createSuccess' => 'Benutzer wurde erfolgreich erstellt! {username} wird bei der ersten Authentifizierung zu einer Passwortzurücksetzung aufgefordert.', - 'rolesEditSuccess' => + 'roleEditSuccess' => "{username}'s Rollen wurden erfolgreich aktualisiert.", - 'forcePassResetSuccess' => - '{username} wird beim nächsten Besuch zu einem Zurücksetzen des Passworts aufgefordert.', 'banSuccess' => '{username} wurde gebannt.', 'unbanSuccess' => '{username} wurde entbannt.', 'editOwnerError' => diff --git a/modules/Admin/Language/el/Contributor.php b/modules/Auth/Language/el/Contributor.php similarity index 100% rename from modules/Admin/Language/el/Contributor.php rename to modules/Auth/Language/el/Contributor.php diff --git a/modules/Admin/Language/el/MyAccount.php b/modules/Auth/Language/el/MyAccount.php similarity index 100% rename from modules/Admin/Language/el/MyAccount.php rename to modules/Auth/Language/el/MyAccount.php diff --git a/modules/Admin/Language/gd/User.php b/modules/Auth/Language/el/User.php similarity index 89% rename from modules/Admin/Language/gd/User.php rename to modules/Auth/Language/el/User.php index 585d6799..fe3a9e1a 100644 --- a/modules/Admin/Language/gd/User.php +++ b/modules/Auth/Language/el/User.php @@ -10,7 +10,6 @@ declare(strict_types=1); return [ 'edit_roles' => "Edit {username}'s roles", - 'forcePassReset' => 'Force pass reset', 'ban' => 'Ban', 'unban' => 'Unban', 'delete' => 'Delete', @@ -39,10 +38,8 @@ return [ 'messages' => [ 'createSuccess' => 'User created successfully! {username} will be prompted with a password reset upon first authentication.', - 'rolesEditSuccess' => + 'roleEditSuccess' => "{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' => diff --git a/modules/Auth/Language/en/Auth.php b/modules/Auth/Language/en/Auth.php new file mode 100644 index 00000000..6928cf9b --- /dev/null +++ b/modules/Auth/Language/en/Auth.php @@ -0,0 +1,89 @@ + [ + 'owner' => [ + 'title' => 'Instance Owner', + 'description' => 'The Castopod owner.', + ], + 'superadmin' => [ + 'title' => 'Super admin', + 'description' => 'Has complete control over Castopod.', + ], + 'manager' => [ + 'title' => 'Manager', + 'description' => 'Manages Castopod\'s content.', + ], + 'podcaster' => [ + 'title' => 'Podcaster', + 'description' => 'General users of Castopod.', + ], + ], + 'instance_permissions' => [ + '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.', + ], + 'podcast_groups' => [ + 'owner' => [ + 'title' => 'Podcast Owner', + 'description' => 'The podcast owner.', + ], + 'admin' => [ + 'title' => 'Admin', + 'description' => 'Has complete control of podcast #{id}.', + ], + 'editor' => [ + 'title' => 'Editor', + 'description' => 'Manages content and publications of podcast #{id}.', + ], + 'author' => [ + 'title' => 'Author', + 'description' => 'Manages content of podcast #{id} but cannot publish them.', + ], + 'guest' => [ + 'title' => 'Guest', + 'description' => 'General contributor of the podcast #{id}.', + ], + ], + 'podcast_permissions' => [ + '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 dashboards and analytics of podcast #{id}\'s episodes.', + 'episodes.create' => 'Can create episodes for podcast #{id}.', + 'episodes.edit' => 'Can edit episodes of podcast #{id}.', + 'episodes.delete' => 'Can delete episodes of podcast #{id}.', + 'episodes.manage-persons' => 'Can manage episode persons of podcast #{id}.', + 'episodes.manage-clips' => 'Can manage video clips or soundbites of podcast #{id}.', + 'episodes.manage-publications' => 'Can publish/unpublish episodes and posts of podcast #{id}.', + 'episodes.manage-comments' => 'Can create/remove episode comments of podcast #{id}.', + ], + 'notEnoughPrivilege' => 'You do not have sufficient permissions to access that page.', + 'set_password' => 'Set your password', + + // Welcome email + 'welcomeSubject' => 'You\'ve been invited to {siteName}', + 'emailWelcomeMailBody' => 'An account was created for you on {domain}, click on the login link below to set your password. The link is valid for {numberOfHours} hours after this email was sent.', +]; diff --git a/modules/Admin/Language/sv/Contributor.php b/modules/Auth/Language/en/Contributor.php similarity index 70% rename from modules/Admin/Language/sv/Contributor.php rename to modules/Auth/Language/en/Contributor.php index d0f3b93d..c70badc0 100644 --- a/modules/Admin/Language/sv/Contributor.php +++ b/modules/Auth/Language/en/Contributor.php @@ -28,10 +28,16 @@ return [ 'submit_add' => 'Add contributor', 'submit_edit' => 'Update role', ], - 'roles' => [ - 'podcast_admin' => 'Podcast admin', + 'delete_form' => [ + 'title' => 'Remove {contributor}', + 'disclaimer' => + 'You are about to remove {contributor} from contributors. They will not be able to access "{podcastTitle}" anymore.', + 'understand' => 'I understand, I want to remove {contributor} from "{podcastTitle}"', + 'submit' => 'Remove', ], 'messages' => [ + 'editSuccess' => 'Role successfully changed!', + 'editOwnerError' => "You can't edit the podcast owner!", 'removeOwnerError' => "You can't remove the podcast owner!", 'removeSuccess' => 'You have successfully removed {username} from {podcastTitle}', diff --git a/modules/Admin/Language/en/MyAccount.php b/modules/Auth/Language/en/MyAccount.php similarity index 100% rename from modules/Admin/Language/en/MyAccount.php rename to modules/Auth/Language/en/MyAccount.php diff --git a/modules/Auth/Language/en/User.php b/modules/Auth/Language/en/User.php new file mode 100644 index 00000000..32ec560c --- /dev/null +++ b/modules/Auth/Language/en/User.php @@ -0,0 +1,60 @@ + "Edit {username}'s role", + 'ban' => 'Ban', + 'unban' => 'Unban', + 'delete' => 'Delete', + 'create' => 'New user', + 'view' => "{username}'s info", + 'all_users' => 'All users', + 'list' => [ + 'user' => 'User', + 'role' => 'Role', + 'banned' => 'Banned?', + ], + 'form' => [ + 'email' => 'Email', + 'username' => 'Username', + 'password' => 'Password', + 'new_password' => 'New Password', + 'role' => 'Role', + 'roles' => 'Roles', + 'permissions' => 'Permissions', + 'submit_create' => 'Create user', + 'submit_edit' => 'Save', + 'submit_password_change' => 'Change!', + ], + 'delete_form' => [ + 'title' => 'Delete {user}', + 'disclaimer' => + "You are about to delete {user} permanently. They will not be able to access the admin area anymore.", + 'understand' => 'I understand, I want to delete {user} permanently', + 'submit' => 'Delete', + ], + 'messages' => [ + 'createSuccess' => + 'User created successfully! A welcome email was sent to {username} with a login link, they will be prompted with a password reset upon first authentication.', + 'roleEditSuccess' => + "{username}'s roles have been successfully updated.", + 'banSuccess' => '{username} has been banned.', + 'unbanSuccess' => '{username} has been unbanned.', + 'editOwnerError' => + '{username} is the instance owner, one does not simply touch the owner…', + 'banSuperAdminError' => + '{username} is a superadmin, one does not simply ban a superadmin…', + 'deleteOwnerError' => + '{username} is the instance owner, one does not simply delete the owner…', + 'deleteSuperAdminError' => + '{username} is a superadmin, one does not simply delete a superadmin…', + 'deleteSuccess' => '{username} has been deleted.', + ], +]; diff --git a/modules/Admin/Language/es/Contributor.php b/modules/Auth/Language/es/Contributor.php similarity index 100% rename from modules/Admin/Language/es/Contributor.php rename to modules/Auth/Language/es/Contributor.php diff --git a/modules/Admin/Language/es/MyAccount.php b/modules/Auth/Language/es/MyAccount.php similarity index 100% rename from modules/Admin/Language/es/MyAccount.php rename to modules/Auth/Language/es/MyAccount.php diff --git a/modules/Admin/Language/es/User.php b/modules/Auth/Language/es/User.php similarity index 88% rename from modules/Admin/Language/es/User.php rename to modules/Auth/Language/es/User.php index 1b37eec2..cf00d948 100644 --- a/modules/Admin/Language/es/User.php +++ b/modules/Auth/Language/es/User.php @@ -10,7 +10,6 @@ declare(strict_types=1); return [ 'edit_roles' => "Editar rol de {username}", - 'forcePassReset' => 'Forzar el reseteo de la contraseña', 'ban' => 'Banear', 'unban' => 'Desbanear', 'delete' => 'Borrar', @@ -39,10 +38,8 @@ return [ 'messages' => [ 'createSuccess' => '¡Usuario creado con éxito! Se le pedirá a {username} que restablezca la contraseña en la primera autenticación.', - 'rolesEditSuccess' => + 'roleEditSuccess' => "Los roles de {username} se han actualizado correctamente.", - 'forcePassResetSuccess' => - 'Se pedirá a {username} que restablezca su contraseña en la próxima visita.', 'banSuccess' => '{username} ha sido baneado.', 'unbanSuccess' => '{username} ha sido desbaneado.', 'editOwnerError' => diff --git a/modules/Admin/Language/en/Contributor.php b/modules/Auth/Language/fa/Contributor.php similarity index 100% rename from modules/Admin/Language/en/Contributor.php rename to modules/Auth/Language/fa/Contributor.php diff --git a/modules/Admin/Language/fa/MyAccount.php b/modules/Auth/Language/fa/MyAccount.php similarity index 100% rename from modules/Admin/Language/fa/MyAccount.php rename to modules/Auth/Language/fa/MyAccount.php diff --git a/modules/Admin/Language/el/User.php b/modules/Auth/Language/fa/User.php similarity index 89% rename from modules/Admin/Language/el/User.php rename to modules/Auth/Language/fa/User.php index 585d6799..fe3a9e1a 100644 --- a/modules/Admin/Language/el/User.php +++ b/modules/Auth/Language/fa/User.php @@ -10,7 +10,6 @@ declare(strict_types=1); return [ 'edit_roles' => "Edit {username}'s roles", - 'forcePassReset' => 'Force pass reset', 'ban' => 'Ban', 'unban' => 'Unban', 'delete' => 'Delete', @@ -39,10 +38,8 @@ return [ 'messages' => [ 'createSuccess' => 'User created successfully! {username} will be prompted with a password reset upon first authentication.', - 'rolesEditSuccess' => + 'roleEditSuccess' => "{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' => diff --git a/modules/Admin/Language/fr/Contributor.php b/modules/Auth/Language/fr/Contributor.php similarity index 100% rename from modules/Admin/Language/fr/Contributor.php rename to modules/Auth/Language/fr/Contributor.php diff --git a/modules/Admin/Language/fr/MyAccount.php b/modules/Auth/Language/fr/MyAccount.php similarity index 100% rename from modules/Admin/Language/fr/MyAccount.php rename to modules/Auth/Language/fr/MyAccount.php diff --git a/modules/Admin/Language/fr/User.php b/modules/Auth/Language/fr/User.php similarity index 89% rename from modules/Admin/Language/fr/User.php rename to modules/Auth/Language/fr/User.php index c5d33a12..27283ed9 100644 --- a/modules/Admin/Language/fr/User.php +++ b/modules/Auth/Language/fr/User.php @@ -10,7 +10,6 @@ declare(strict_types=1); return [ 'edit_roles' => "Modifier les rôles de {username}", - 'forcePassReset' => 'Forcer la réinitialisation du mot de passe', 'ban' => 'Bloquer', 'unban' => 'Débloquer', 'delete' => 'Supprimer', @@ -39,10 +38,8 @@ return [ 'messages' => [ 'createSuccess' => 'Utilisateur créé avec succès ! {username} devra modifier son mot de passe à la première authentification.', - 'rolesEditSuccess' => + 'roleEditSuccess' => "Les rôles de {username} ont été mis à jour avec succès.", - 'forcePassResetSuccess' => - '{username} devra modifier son mot de passe à la prochaine visite.', 'banSuccess' => '{username} a été bloqué.', 'unbanSuccess' => '{username} a été débloqué.', 'editOwnerError' => diff --git a/modules/Admin/Language/fa/Contributor.php b/modules/Auth/Language/gd/Contributor.php similarity index 100% rename from modules/Admin/Language/fa/Contributor.php rename to modules/Auth/Language/gd/Contributor.php diff --git a/modules/Admin/Language/gd/MyAccount.php b/modules/Auth/Language/gd/MyAccount.php similarity index 100% rename from modules/Admin/Language/gd/MyAccount.php rename to modules/Auth/Language/gd/MyAccount.php diff --git a/modules/Admin/Language/fa/User.php b/modules/Auth/Language/gd/User.php similarity index 89% rename from modules/Admin/Language/fa/User.php rename to modules/Auth/Language/gd/User.php index 585d6799..fe3a9e1a 100644 --- a/modules/Admin/Language/fa/User.php +++ b/modules/Auth/Language/gd/User.php @@ -10,7 +10,6 @@ declare(strict_types=1); return [ 'edit_roles' => "Edit {username}'s roles", - 'forcePassReset' => 'Force pass reset', 'ban' => 'Ban', 'unban' => 'Unban', 'delete' => 'Delete', @@ -39,10 +38,8 @@ return [ 'messages' => [ 'createSuccess' => 'User created successfully! {username} will be prompted with a password reset upon first authentication.', - 'rolesEditSuccess' => + 'roleEditSuccess' => "{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' => diff --git a/modules/Admin/Language/gl/Contributor.php b/modules/Auth/Language/gl/Contributor.php similarity index 100% rename from modules/Admin/Language/gl/Contributor.php rename to modules/Auth/Language/gl/Contributor.php diff --git a/modules/Admin/Language/gl/MyAccount.php b/modules/Auth/Language/gl/MyAccount.php similarity index 100% rename from modules/Admin/Language/gl/MyAccount.php rename to modules/Auth/Language/gl/MyAccount.php diff --git a/modules/Admin/Language/gl/User.php b/modules/Auth/Language/gl/User.php similarity index 88% rename from modules/Admin/Language/gl/User.php rename to modules/Auth/Language/gl/User.php index 4bdfcab9..dd84c53d 100644 --- a/modules/Admin/Language/gl/User.php +++ b/modules/Auth/Language/gl/User.php @@ -10,7 +10,6 @@ declare(strict_types=1); return [ 'edit_roles' => "Editar os roles de {username}", - 'forcePassReset' => 'Forzar restablecemento do contrasinal', 'ban' => 'Vetar', 'unban' => 'Retirar veto', 'delete' => 'Eliminar', @@ -39,10 +38,8 @@ return [ 'messages' => [ 'createSuccess' => 'Usuaria creada correctamente! Váiselle pedir a {username} que cambie o seu contrasinal após o primeiro acceso.', - 'rolesEditSuccess' => + 'roleEditSuccess' => "Os roles de {username} actualizáronse correctamente.", - 'forcePassResetSuccess' => - 'Solicitarase que {username} restableza o contrasinal na seguinte visita.', 'banSuccess' => '{username} foi vetada.', 'unbanSuccess' => 'Retirouse o veto sobre {username}.', 'editOwnerError' => diff --git a/modules/Admin/Language/gd/Contributor.php b/modules/Auth/Language/id/Contributor.php similarity index 100% rename from modules/Admin/Language/gd/Contributor.php rename to modules/Auth/Language/id/Contributor.php diff --git a/modules/Admin/Language/id/MyAccount.php b/modules/Auth/Language/id/MyAccount.php similarity index 100% rename from modules/Admin/Language/id/MyAccount.php rename to modules/Auth/Language/id/MyAccount.php diff --git a/modules/Admin/Language/en/User.php b/modules/Auth/Language/id/User.php similarity index 89% rename from modules/Admin/Language/en/User.php rename to modules/Auth/Language/id/User.php index 585d6799..fe3a9e1a 100644 --- a/modules/Admin/Language/en/User.php +++ b/modules/Auth/Language/id/User.php @@ -10,7 +10,6 @@ declare(strict_types=1); return [ 'edit_roles' => "Edit {username}'s roles", - 'forcePassReset' => 'Force pass reset', 'ban' => 'Ban', 'unban' => 'Unban', 'delete' => 'Delete', @@ -39,10 +38,8 @@ return [ 'messages' => [ 'createSuccess' => 'User created successfully! {username} will be prompted with a password reset upon first authentication.', - 'rolesEditSuccess' => + 'roleEditSuccess' => "{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' => diff --git a/modules/Admin/Language/it/Contributor.php b/modules/Auth/Language/it/Contributor.php similarity index 100% rename from modules/Admin/Language/it/Contributor.php rename to modules/Auth/Language/it/Contributor.php diff --git a/modules/Admin/Language/it/MyAccount.php b/modules/Auth/Language/it/MyAccount.php similarity index 100% rename from modules/Admin/Language/it/MyAccount.php rename to modules/Auth/Language/it/MyAccount.php diff --git a/modules/Auth/Language/it/User.php b/modules/Auth/Language/it/User.php new file mode 100644 index 00000000..fe3a9e1a --- /dev/null +++ b/modules/Auth/Language/it/User.php @@ -0,0 +1,53 @@ + "Edit {username}'s roles", + '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.', + 'roleEditSuccess' => + "{username}'s roles have been successfully updated.", + '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.', + ], +]; diff --git a/modules/Admin/Language/nl/Contributor.php b/modules/Auth/Language/nl/Contributor.php similarity index 100% rename from modules/Admin/Language/nl/Contributor.php rename to modules/Auth/Language/nl/Contributor.php diff --git a/modules/Admin/Language/nl/MyAccount.php b/modules/Auth/Language/nl/MyAccount.php similarity index 100% rename from modules/Admin/Language/nl/MyAccount.php rename to modules/Auth/Language/nl/MyAccount.php diff --git a/modules/Auth/Language/nl/User.php b/modules/Auth/Language/nl/User.php new file mode 100644 index 00000000..fe3a9e1a --- /dev/null +++ b/modules/Auth/Language/nl/User.php @@ -0,0 +1,53 @@ + "Edit {username}'s roles", + '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.', + 'roleEditSuccess' => + "{username}'s roles have been successfully updated.", + '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.', + ], +]; diff --git a/modules/Admin/Language/nn-NO/Contributor.php b/modules/Auth/Language/nn-NO/Contributor.php similarity index 100% rename from modules/Admin/Language/nn-NO/Contributor.php rename to modules/Auth/Language/nn-NO/Contributor.php diff --git a/modules/Admin/Language/nn-NO/MyAccount.php b/modules/Auth/Language/nn-NO/MyAccount.php similarity index 100% rename from modules/Admin/Language/nn-NO/MyAccount.php rename to modules/Auth/Language/nn-NO/MyAccount.php diff --git a/modules/Admin/Language/nn-NO/User.php b/modules/Auth/Language/nn-NO/User.php similarity index 89% rename from modules/Admin/Language/nn-NO/User.php rename to modules/Auth/Language/nn-NO/User.php index 0a51b30d..a3ec4bfe 100644 --- a/modules/Admin/Language/nn-NO/User.php +++ b/modules/Auth/Language/nn-NO/User.php @@ -10,7 +10,6 @@ declare(strict_types=1); return [ 'edit_roles' => "Endre rollene til {username}", - 'forcePassReset' => 'Tving passordnullstilling', 'ban' => 'Steng ute', 'unban' => 'Slepp inn att', 'delete' => 'Slett', @@ -39,10 +38,8 @@ return [ 'messages' => [ 'createSuccess' => 'Brukaren er oppretta! {username} vil få spørsmål om å endra passord fyrste gong hen loggar inn.', - 'rolesEditSuccess' => + 'roleEditSuccess' => "Rollene til {username} er oppdaterte.", - 'forcePassResetSuccess' => - '{username} vil bli beden om å endra passord neste gong hen loggar inn.', 'banSuccess' => '{username} er utestengd.', 'unbanSuccess' => '{username} fekk sleppa inn att.', 'editOwnerError' => diff --git a/modules/Admin/Language/id/Contributor.php b/modules/Auth/Language/oc/Contributor.php similarity index 100% rename from modules/Admin/Language/id/Contributor.php rename to modules/Auth/Language/oc/Contributor.php diff --git a/modules/Admin/Language/oc/MyAccount.php b/modules/Auth/Language/oc/MyAccount.php similarity index 100% rename from modules/Admin/Language/oc/MyAccount.php rename to modules/Auth/Language/oc/MyAccount.php diff --git a/modules/Auth/Language/oc/User.php b/modules/Auth/Language/oc/User.php new file mode 100644 index 00000000..fe3a9e1a --- /dev/null +++ b/modules/Auth/Language/oc/User.php @@ -0,0 +1,53 @@ + "Edit {username}'s roles", + '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.', + 'roleEditSuccess' => + "{username}'s roles have been successfully updated.", + '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.', + ], +]; diff --git a/modules/Admin/Language/pl/Contributor.php b/modules/Auth/Language/pl/Contributor.php similarity index 100% rename from modules/Admin/Language/pl/Contributor.php rename to modules/Auth/Language/pl/Contributor.php diff --git a/modules/Admin/Language/pl/MyAccount.php b/modules/Auth/Language/pl/MyAccount.php similarity index 100% rename from modules/Admin/Language/pl/MyAccount.php rename to modules/Auth/Language/pl/MyAccount.php diff --git a/modules/Admin/Language/pl/User.php b/modules/Auth/Language/pl/User.php similarity index 89% rename from modules/Admin/Language/pl/User.php rename to modules/Auth/Language/pl/User.php index 7db87b44..7c2d0789 100644 --- a/modules/Admin/Language/pl/User.php +++ b/modules/Auth/Language/pl/User.php @@ -10,7 +10,6 @@ declare(strict_types=1); return [ 'edit_roles' => "Edytuj role użytkownika {username}", - 'forcePassReset' => 'Wymuś resetowanie hasła', 'ban' => 'Zablokuj', 'unban' => 'Odblokuj', 'delete' => 'Usuń', @@ -39,10 +38,8 @@ return [ 'messages' => [ 'createSuccess' => 'Pomyślnie utworzono użytkownika! {username} zostanie poproszony o zresetowanie hasła przy pierwszym uwierzytelnieniu.', - 'rolesEditSuccess' => + 'roleEditSuccess' => "Role {username} zostały pomyślnie zaktualizowane.", - 'forcePassResetSuccess' => - '{username} zostanie poproszony o zresetowanie hasła przy następnej wizycie.', 'banSuccess' => '{username} został zablokowany.', 'unbanSuccess' => '{username} został odblokowany.', 'editOwnerError' => diff --git a/modules/Admin/Language/pt-BR/Contributor.php b/modules/Auth/Language/pt-BR/Contributor.php similarity index 100% rename from modules/Admin/Language/pt-BR/Contributor.php rename to modules/Auth/Language/pt-BR/Contributor.php diff --git a/modules/Admin/Language/pt-BR/MyAccount.php b/modules/Auth/Language/pt-BR/MyAccount.php similarity index 100% rename from modules/Admin/Language/pt-BR/MyAccount.php rename to modules/Auth/Language/pt-BR/MyAccount.php diff --git a/modules/Admin/Language/pt-BR/User.php b/modules/Auth/Language/pt-BR/User.php similarity index 89% rename from modules/Admin/Language/pt-BR/User.php rename to modules/Auth/Language/pt-BR/User.php index d42b9fc6..aff24883 100644 --- a/modules/Admin/Language/pt-BR/User.php +++ b/modules/Auth/Language/pt-BR/User.php @@ -10,7 +10,6 @@ declare(strict_types=1); return [ 'edit_roles' => "Editar cargos de {username}", - 'forcePassReset' => 'Forçar redefinição da senha', 'ban' => 'Banir', 'unban' => 'Desbanir', 'delete' => 'Excluir', @@ -39,10 +38,8 @@ return [ 'messages' => [ 'createSuccess' => 'Usuário criado com sucesso! {username} terá que alterar sua senha na primeira autenticação.', - 'rolesEditSuccess' => + 'roleEditSuccess' => "Cargos de {username} foram atualizados com sucesso.", - 'forcePassResetSuccess' => - '{username} precisará alterar sua senha na próxima visita.', 'banSuccess' => '{username} foi banido.', 'unbanSuccess' => '{username} foi desbanido.', 'editOwnerError' => diff --git a/modules/Admin/Language/oc/Contributor.php b/modules/Auth/Language/pt/Contributor.php similarity index 100% rename from modules/Admin/Language/oc/Contributor.php rename to modules/Auth/Language/pt/Contributor.php diff --git a/modules/Admin/Language/pt/MyAccount.php b/modules/Auth/Language/pt/MyAccount.php similarity index 100% rename from modules/Admin/Language/pt/MyAccount.php rename to modules/Auth/Language/pt/MyAccount.php diff --git a/modules/Auth/Language/pt/User.php b/modules/Auth/Language/pt/User.php new file mode 100644 index 00000000..fe3a9e1a --- /dev/null +++ b/modules/Auth/Language/pt/User.php @@ -0,0 +1,53 @@ + "Edit {username}'s roles", + '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.', + 'roleEditSuccess' => + "{username}'s roles have been successfully updated.", + '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.', + ], +]; diff --git a/modules/Admin/Language/pt/Contributor.php b/modules/Auth/Language/ru/Contributor.php similarity index 100% rename from modules/Admin/Language/pt/Contributor.php rename to modules/Auth/Language/ru/Contributor.php diff --git a/modules/Admin/Language/ru/MyAccount.php b/modules/Auth/Language/ru/MyAccount.php similarity index 100% rename from modules/Admin/Language/ru/MyAccount.php rename to modules/Auth/Language/ru/MyAccount.php diff --git a/modules/Auth/Language/ru/User.php b/modules/Auth/Language/ru/User.php new file mode 100644 index 00000000..fe3a9e1a --- /dev/null +++ b/modules/Auth/Language/ru/User.php @@ -0,0 +1,53 @@ + "Edit {username}'s roles", + '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.', + 'roleEditSuccess' => + "{username}'s roles have been successfully updated.", + '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.', + ], +]; diff --git a/modules/Admin/Language/sk/Contributor.php b/modules/Auth/Language/sk/Contributor.php similarity index 100% rename from modules/Admin/Language/sk/Contributor.php rename to modules/Auth/Language/sk/Contributor.php diff --git a/modules/Admin/Language/sk/MyAccount.php b/modules/Auth/Language/sk/MyAccount.php similarity index 100% rename from modules/Admin/Language/sk/MyAccount.php rename to modules/Auth/Language/sk/MyAccount.php diff --git a/modules/Auth/Language/sk/User.php b/modules/Auth/Language/sk/User.php new file mode 100644 index 00000000..fe3a9e1a --- /dev/null +++ b/modules/Auth/Language/sk/User.php @@ -0,0 +1,53 @@ + "Edit {username}'s roles", + '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.', + 'roleEditSuccess' => + "{username}'s roles have been successfully updated.", + '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.', + ], +]; diff --git a/modules/Admin/Language/ru/Contributor.php b/modules/Auth/Language/sv/Contributor.php similarity index 100% rename from modules/Admin/Language/ru/Contributor.php rename to modules/Auth/Language/sv/Contributor.php diff --git a/modules/Admin/Language/sv/MyAccount.php b/modules/Auth/Language/sv/MyAccount.php similarity index 100% rename from modules/Admin/Language/sv/MyAccount.php rename to modules/Auth/Language/sv/MyAccount.php diff --git a/modules/Auth/Language/sv/User.php b/modules/Auth/Language/sv/User.php new file mode 100644 index 00000000..fe3a9e1a --- /dev/null +++ b/modules/Auth/Language/sv/User.php @@ -0,0 +1,53 @@ + "Edit {username}'s roles", + '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.', + 'roleEditSuccess' => + "{username}'s roles have been successfully updated.", + '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.', + ], +]; diff --git a/modules/Admin/Language/zh-Hans/Contributor.php b/modules/Auth/Language/zh-Hans/Contributor.php similarity index 100% rename from modules/Admin/Language/zh-Hans/Contributor.php rename to modules/Auth/Language/zh-Hans/Contributor.php diff --git a/modules/Admin/Language/zh-Hans/MyAccount.php b/modules/Auth/Language/zh-Hans/MyAccount.php similarity index 100% rename from modules/Admin/Language/zh-Hans/MyAccount.php rename to modules/Auth/Language/zh-Hans/MyAccount.php diff --git a/modules/Admin/Language/zh-Hans/User.php b/modules/Auth/Language/zh-Hans/User.php similarity index 90% rename from modules/Admin/Language/zh-Hans/User.php rename to modules/Auth/Language/zh-Hans/User.php index cb3e0ed6..f76f603d 100644 --- a/modules/Admin/Language/zh-Hans/User.php +++ b/modules/Auth/Language/zh-Hans/User.php @@ -10,7 +10,6 @@ declare(strict_types=1); return [ 'edit_roles' => "编辑 {username} 的角色", - 'forcePassReset' => '强制重置', 'ban' => '封禁', 'unban' => '取消封禁', 'delete' => '删除', @@ -39,10 +38,8 @@ return [ 'messages' => [ 'createSuccess' => '用户创建成功!{username} 将在首次验证时提醒重置密码。', - 'rolesEditSuccess' => + 'roleEditSuccess' => "{username} 的角色已更新。", - 'forcePassResetSuccess' => - '下次访问时 {username} 将被提醒重置密码。', 'banSuccess' => '{username} 已被封禁。', 'unbanSuccess' => '{username} 已解除封禁。', 'editOwnerError' => diff --git a/modules/Auth/Models/UserModel.php b/modules/Auth/Models/UserModel.php new file mode 100644 index 00000000..82ba5177 --- /dev/null +++ b/modules/Auth/Models/UserModel.php @@ -0,0 +1,50 @@ +select('users.*') + ->join('auth_groups_users', 'users.id = auth_groups_users.user_id') + ->like('auth_groups_users.group', "podcast#{$podcastId}") + ->findAll(); + } + + public function getPodcastContributor(int $contributorId, int $podcastId): ?User + { + return $this->select('users.*') + ->join('auth_groups_users', 'users.id = auth_groups_users.user_id') + ->where('users.id', $contributorId) + ->like('auth_groups_users.group', "podcast#{$podcastId}") + ->first(); + } +} diff --git a/modules/Install/Controllers/InstallController.php b/modules/Install/Controllers/InstallController.php index 0f80f081..c5705c9f 100644 --- a/modules/Install/Controllers/InstallController.php +++ b/modules/Install/Controllers/InstallController.php @@ -10,18 +10,18 @@ declare(strict_types=1); namespace Modules\Install\Controllers; -use App\Models\UserModel; use CodeIgniter\Controller; use CodeIgniter\Database\Exceptions\DatabaseException; use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\HTTP\RedirectResponse; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; +use CodeIgniter\Shield\Entities\User; use Config\Database; use Config\Services; use Dotenv\Dotenv; use Dotenv\Exception\ValidationException; -use Modules\Auth\Entities\User; +use Modules\Auth\Models\UserModel; use Psr\Log\LoggerInterface; use Throwable; use ViewThemes\Theme; @@ -31,7 +31,7 @@ class InstallController extends Controller /** * @var string[] */ - protected $helpers = ['form', 'components', 'svg', 'misc']; + protected $helpers = ['form', 'components', 'svg', 'misc', 'setting']; /** * Constructor. @@ -117,10 +117,11 @@ class InstallController extends Controller try { $db = db_connect(); - // Check if superadmin has been created, meaning migrations and seeds have passed + // Check if instance owner has been created, meaning install was completed if ( $db->tableExists('users') && - (new UserModel())->countAll() > 0 + (new UserModel())->where('is_owner', true) + ->first() !== null ) { // if so, show a 404 page throw PageNotFoundException::forPageNotFound(); @@ -249,7 +250,7 @@ class InstallController extends Controller $migrations->setNamespace('CodeIgniter\Settings') ->latest(); - $migrations->setNamespace('Myth\Auth') + $migrations->setNamespace('CodeIgniter\Shield') ->latest(); $migrations->setNamespace('Modules\Fediverse') ->latest(); @@ -293,48 +294,25 @@ class InstallController extends Controller { $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(); - - $db = db_connect(); - - $db->transStart(); - if (! ($userId = $userModel->insert($user, true))) { - $db->transRollback(); - - return redirect() - ->back() + $user = new User([ + 'username' => $this->request->getPost('username'), + 'email' => $this->request->getPost('email'), + 'password' => $this->request->getPost('password'), + 'is_owner' => true, + ]); + try { + $userModel->save($user); + } catch (ValidationException) { + return redirect()->back() ->withInput() ->with('errors', $userModel->errors()); } - // add newly created user to superadmin group - $authorization = Services::authorization(); - $authorization->addUserToGroup($userId, 'superadmin'); + $user = $userModel->findById($userModel->getInsertID()); - $db->transComplete(); + // set newly created user as most powerful instance group (superadmin) + $user->addGroup(setting('AuthGroups.mostPowerfulGroup')); // Success! // set redirect_url session as admin area to go to after login @@ -342,7 +320,7 @@ class InstallController extends Controller ->set('redirect_url', route_to('admin')); return redirect() - ->route('login') + ->route('admin') ->with('message', lang('Install.messages.createSuperAdminSuccess')); } diff --git a/modules/Install/Language/en/Install.php b/modules/Install/Language/en/Install.php index 1f66ef11..45d26085 100644 --- a/modules/Install/Language/en/Install.php +++ b/modules/Install/Language/en/Install.php @@ -46,7 +46,7 @@ return [ ], 'next' => 'Next', 'submit' => 'Finish install', - 'create_superadmin' => 'Create your superadmin account', + 'create_superadmin' => 'Create your Super Admin account', 'email' => 'Email', 'username' => 'Username', 'password' => 'Password', diff --git a/modules/PremiumPodcasts/Config/Routes.php b/modules/PremiumPodcasts/Config/Routes.php index 2859b596..a9620794 100644 --- a/modules/PremiumPodcasts/Config/Routes.php +++ b/modules/PremiumPodcasts/Config/Routes.php @@ -20,30 +20,30 @@ $routes->group( $routes->get('/', 'SubscriptionController::list/$1', [ 'as' => 'subscription-list', 'filter' => - 'permission:podcasts-view,podcast-manage_subscriptions', + 'permission:podcast#.manage-subscriptions', ]); $routes->get('add', 'SubscriptionController::add/$1', [ 'as' => 'subscription-add', - 'filter' => 'permission:podcast-manage_subscriptions', + 'filter' => 'permission:podcast#.manage-subscriptions', ]); $routes->post( 'add', 'SubscriptionController::attemptAdd/$1', [ 'filter' => - 'permission:podcast-manage_subscriptions', + 'permission:podcast#.manage-subscriptions', ], ); $routes->post('save-link', 'SubscriptionController::attemptLinkSave/$1', [ 'as' => 'subscription-link-save', - 'filter' => 'permission:podcast-manage_subscriptions', + 'filter' => 'permission:podcast#.manage-subscriptions', ]); // Subscription $routes->group('(:num)', static function ($routes): void { $routes->get('/', 'SubscriptionController::view/$1/$2', [ 'as' => 'subscription-view', 'filter' => - 'permission:podcast-manage_subscriptions', + 'permission:podcast#.manage-subscriptions', ]); $routes->get( 'edit', @@ -51,7 +51,7 @@ $routes->group( [ 'as' => 'subscription-edit', 'filter' => - 'permission:podcast-manage_subscriptions', + 'permission:podcast#.manage-subscriptions', ], ); $routes->post( @@ -60,7 +60,7 @@ $routes->group( [ 'as' => 'subscription-edit', 'filter' => - 'permission:podcast-manage_subscriptions', + 'permission:podcast#.manage-subscriptions', ], ); $routes->get( @@ -69,7 +69,7 @@ $routes->group( [ 'as' => 'subscription-regenerate-token', 'filter' => - 'permission:podcast-manage_subscriptions', + 'permission:podcast#.manage-subscriptions', ] ); $routes->get( @@ -78,7 +78,7 @@ $routes->group( [ 'as' => 'subscription-suspend', 'filter' => - 'permission:podcast-manage_subscriptions', + 'permission:podcast#.manage-subscriptions', ], ); $routes->post( @@ -86,7 +86,7 @@ $routes->group( 'SubscriptionController::attemptSuspend/$1/$2', [ 'filter' => - 'permission:podcast-manage_subscriptions', + 'permission:podcast#.manage-subscriptions', ], ); $routes->get( @@ -95,7 +95,7 @@ $routes->group( [ 'as' => 'subscription-resume', 'filter' => - 'permission:podcast-manage_subscriptions', + 'permission:podcast#.manage-subscriptions', ], ); $routes->get( @@ -104,7 +104,7 @@ $routes->group( [ 'as' => 'subscription-delete', 'filter' => - 'permission:podcast-manage_subscriptions', + 'permission:podcast#.manage-subscriptions', ], ); $routes->post( @@ -112,7 +112,7 @@ $routes->group( 'SubscriptionController::attemptDelete/$1/$2', [ 'filter' => - 'permission:podcast-manage_subscriptions', + 'permission:podcast#.manage-subscriptions', ], ); }); diff --git a/modules/PremiumPodcasts/Controllers/SubscriptionController.php b/modules/PremiumPodcasts/Controllers/SubscriptionController.php index 055de5b8..c29bac8c 100644 --- a/modules/PremiumPodcasts/Controllers/SubscriptionController.php +++ b/modules/PremiumPodcasts/Controllers/SubscriptionController.php @@ -58,7 +58,7 @@ class SubscriptionController extends BaseController helper('form'); replace_breadcrumb_params([ - 0 => $this->podcast->title, + 0 => $this->podcast->at_handle, ]); return view('subscription/list', $data); } @@ -106,7 +106,7 @@ class SubscriptionController extends BaseController ]; replace_breadcrumb_params([ - 0 => $this->podcast->title, + 0 => $this->podcast->at_handle, 1 => '#' . $this->subscription->id, ]); return view('subscription/view', $data); @@ -121,7 +121,7 @@ class SubscriptionController extends BaseController ]; replace_breadcrumb_params([ - 0 => $this->podcast->title, + 0 => $this->podcast->at_handle, ]); return view('subscription/add', $data); } @@ -247,7 +247,7 @@ class SubscriptionController extends BaseController ]; replace_breadcrumb_params([ - 0 => $this->podcast->title, + 0 => $this->podcast->at_handle, 1 => '#' . $this->subscription->id, ]); return view('subscription/edit', $data); @@ -315,7 +315,7 @@ class SubscriptionController extends BaseController ]; replace_breadcrumb_params([ - 0 => $this->podcast->title, + 0 => $this->podcast->at_handle, 1 => '#' . $this->subscription->id, ]); return view('subscription/suspend', $data); @@ -410,7 +410,7 @@ class SubscriptionController extends BaseController ]; replace_breadcrumb_params([ - 0 => $this->podcast->title, + 0 => $this->podcast->at_handle, 1 => '#' . $this->subscription->id, ]); return view('subscription/delete', $data); diff --git a/modules/PremiumPodcasts/Filters/PodcastUnlockFilter.php b/modules/PremiumPodcasts/Filters/PodcastUnlockFilter.php index 74468fa2..a4a1d3f8 100644 --- a/modules/PremiumPodcasts/Filters/PodcastUnlockFilter.php +++ b/modules/PremiumPodcasts/Filters/PodcastUnlockFilter.php @@ -11,7 +11,6 @@ use CodeIgniter\HTTP\ResponseInterface; use CodeIgniter\Router\Router; use Config\App; use Modules\PremiumPodcasts\PremiumPodcasts; -use Myth\Auth\Authentication\AuthenticationBase; class PodcastUnlockFilter implements FilterInterface { @@ -48,9 +47,7 @@ class PodcastUnlockFilter implements FilterInterface } // no need to go through the unlock form if user is connected - /** @var AuthenticationBase $auth */ - $auth = service('authentication'); - if ($auth->isLoggedIn()) { + if (auth()->loggedIn()) { return; } diff --git a/phpstan.neon b/phpstan.neon index cbedb485..8e79ac06 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -9,10 +9,12 @@ parameters: scanDirectories: - app/Helpers - modules/Analytics/Helpers + - modules/Auth/Helpers - modules/Fediverse/Helpers - modules/PremiumPodcasts/Helpers - vendor/codeigniter4/framework/system/Helpers - - vendor/myth/auth/src/Helpers + - vendor/codeigniter4/settings/src/Helpers + - vendor/codeigniter4/shield/src/Helpers excludePaths: - app/Libraries/Router.php - app/Views/* diff --git a/themes/cp_admin/_partials/_nav_header.php b/themes/cp_admin/_partials/_nav_header.php index b3711aa9..d3ec1f3c 100644 --- a/themes/cp_admin/_partials/_nav_header.php +++ b/themes/cp_admin/_partials/_nav_header.php @@ -1,3 +1,7 @@ +user()); ?> +
@@ -34,11 +38,11 @@ ], ]; - if (user()->podcasts !== []) { - foreach (user()->podcasts as $userPodcast) { + if ($userPodcasts !== []) { + foreach ($userPodcasts as $userPodcast) { $userPodcastTitle = esc($userPodcast->title); - $unreadNotificationDotDisplayClass = in_array($userPodcast->actor_id, user()->actorIdsWithUnreadNotifications, true) ? '' : 'hidden'; + $unreadNotificationDotDisplayClass = in_array($userPodcast->actor_id, $actorIdsWithUnreadNotifications, true) ? '' : 'hidden'; $items[] = [ 'type' => 'link', @@ -66,7 +70,7 @@ } ?> - +
podcasts as $userPodcast) { + foreach ($userPodcasts as $userPodcast) { $checkMark = interact_as_actor_id() === $userPodcast->actor_id ? icon('check', 'ml-2 bg-accent-base text-accent-contrast rounded-full') : ''; $userPodcastTitle = esc($userPodcast->title); @@ -96,7 +99,7 @@ } $interactAsText = lang('Common.choose_interact'); - $route = route_to('interact-as-actor'); + $interactAsRoute = route_to('interact-as-actor'); $csrfField = csrf_field(); $menuItems = [ @@ -120,14 +123,14 @@ ], ]; - if (user()->podcasts !== []) { + if ($userPodcasts !== []) { $menuItems = array_merge([ [ 'type' => 'html', 'content' => esc(<< {$interactAsText} -
+ {$csrfField} {$interactButtons}
diff --git a/themes/cp_admin/_partials/_user_info.php b/themes/cp_admin/_partials/_user_info.php index 61871f6a..6bb3aebb 100644 --- a/themes/cp_admin/_partials/_user_info.php +++ b/themes/cp_admin/_partials/_user_info.php @@ -1,11 +1,3 @@ -
-
- -
-
- email ?> -
-
@@ -14,12 +6,20 @@ username) ?>
+
+
+ +
+
+ email ?> +
+
- roles) ?> + getGroups()) ?>
@@ -27,6 +27,6 @@
- permissions) ?> + getPermissions()) ?>
diff --git a/themes/cp_admin/contributor/add.php b/themes/cp_admin/contributor/add.php index ef2325bb..91b3a94a 100644 --- a/themes/cp_admin/contributor/add.php +++ b/themes/cp_admin/contributor/add.php @@ -18,7 +18,7 @@ as="Select" name="user" label="" - options="" + options="" placeholder="" required="true" /> @@ -28,6 +28,7 @@ label="" options="" placeholder="" + selected="" required="true" /> diff --git a/themes/cp_admin/contributor/delete.php b/themes/cp_admin/contributor/delete.php new file mode 100644 index 00000000..d1f73379 --- /dev/null +++ b/themes/cp_admin/contributor/delete.php @@ -0,0 +1,37 @@ +extend('_layout') ?> + +section('title') ?> + $contributor->username, +]) ?> +endSection() ?> + +section('pageTitle') ?> + $contributor->username, +]) ?> +endSection() ?> + +section('content') ?> + +
+ + + $contributor->username, + 'podcastTitle' => $podcast->title, +]) ?> + + $contributor->username, + 'podcastTitle' => $podcast->title, +]) ?> + +
+ + +
+ +
+ +endSection() ?> diff --git a/themes/cp_admin/contributor/edit.php b/themes/cp_admin/contributor/edit.php index edfed841..ca854d96 100644 --- a/themes/cp_admin/contributor/edit.php +++ b/themes/cp_admin/contributor/edit.php @@ -1,25 +1,25 @@ extend('_layout') ?> section('title') ?> -username)]) ?> +username)]) ?> endSection() ?> section('pageTitle') ?> -username)]) ?> +username)]) ?> endSection() ?> section('content') ?> -
+ diff --git a/themes/cp_admin/contributor/list.php b/themes/cp_admin/contributor/list.php index 6ad44137..9da3de4d 100644 --- a/themes/cp_admin/contributor/list.php +++ b/themes/cp_admin/contributor/list.php @@ -25,8 +25,14 @@ ], [ 'header' => lang('Contributor.list.role'), - 'cell' => function ($contributor): string { - return lang('Contributor.roles.' . $contributor->podcast_role); + 'cell' => function ($contributor, $podcast): string { + $role = get_group_info(get_podcast_group($contributor, $podcast->id), $podcast->id)['title']; + + if ($podcast->created_by === $contributor->id) { + $role = '
' . icon('shield-user') . '' . $role . '
'; + } + + return $role; }, ], [ diff --git a/themes/cp_admin/my_account/view.php b/themes/cp_admin/my_account/view.php index c329ea71..5d5c1101 100644 --- a/themes/cp_admin/my_account/view.php +++ b/themes/cp_admin/my_account/view.php @@ -12,7 +12,8 @@ section('content') ?> user(), + 'user' => auth() + ->user(), ]) ?> endSection() ?> diff --git a/themes/cp_admin/podcast/list.php b/themes/cp_admin/podcast/list.php index 6736877e..28cbd916 100644 --- a/themes/cp_admin/podcast/list.php +++ b/themes/cp_admin/podcast/list.php @@ -17,7 +17,7 @@ section('content') ?>
- + $podcast, diff --git a/themes/cp_admin/user/create.php b/themes/cp_admin/user/create.php index d81377df..eb6b769b 100644 --- a/themes/cp_admin/user/create.php +++ b/themes/cp_admin/user/create.php @@ -14,6 +14,11 @@ + + - - diff --git a/themes/cp_admin/user/delete.php b/themes/cp_admin/user/delete.php new file mode 100644 index 00000000..70c915de --- /dev/null +++ b/themes/cp_admin/user/delete.php @@ -0,0 +1,35 @@ +extend('_layout') ?> + +section('title') ?> + $user->username, +]) ?> +endSection() ?> + +section('pageTitle') ?> + $user->username, +]) ?> +endSection() ?> + +section('content') ?> + +
+ + + $user->username, +]) ?> + + $user->username, +]) ?> + +
+ + +
+ +
+ +endSection() ?> diff --git a/themes/cp_admin/user/edit.php b/themes/cp_admin/user/edit.php index 2981b9f3..de91027d 100644 --- a/themes/cp_admin/user/edit.php +++ b/themes/cp_admin/user/edit.php @@ -1,13 +1,13 @@ extend('_layout') ?> section('title') ?> - esc($user->username), ]) ?> endSection() ?> section('pageTitle') ?> - esc($user->username), ]) ?> endSection() ?> @@ -19,12 +19,12 @@ + selected="" + required="true" /> diff --git a/themes/cp_admin/user/list.php b/themes/cp_admin/user/list.php index ce977792..9e87eed7 100644 --- a/themes/cp_admin/user/list.php +++ b/themes/cp_admin/user/list.php @@ -28,50 +28,30 @@ }, ], [ - 'header' => lang('User.list.roles'), + 'header' => lang('User.list.role'), 'cell' => function ($user) { - if ($user->isOwner) { - return 'owner, ' . implode(',', $user->roles); + $role = get_group_info(get_instance_group($user))['title']; + + if ((bool) $user->is_owner) { + $role = '
' . icon('shield-user') . '' . $role . '
'; } - return implode(',', $user->roles) . '' . lang('User.edit_roles', [ + return $role . '' . lang('User.edit_role', [ 'username' => esc($user->username), ]) . ''; }, ], - [ - 'header' => lang('User.list.banned'), - 'cell' => function ($user) { - return $user->isBanned() - ? lang('Common.yes') - : lang('Common.no'); - }, - ], [ 'header' => lang('Common.actions'), 'cell' => function ($user) { return '' . ''; }, ], diff --git a/themes/cp_admin/user/view.php b/themes/cp_admin/user/view.php index 5213a16a..3ce71ba1 100644 --- a/themes/cp_admin/user/view.php +++ b/themes/cp_admin/user/view.php @@ -6,6 +6,12 @@ ]) ?> endSection() ?> +section('pageTitle') ?> + esc($user->username), +]) ?> +endSection() ?> + section('content') ?> diff --git a/themes/cp_app/_admin_navbar.php b/themes/cp_app/_admin_navbar.php index 15f542f4..f17ea0d8 100644 --- a/themes/cp_app/_admin_navbar.php +++ b/themes/cp_app/_admin_navbar.php @@ -1,3 +1,7 @@ +user()); ?> +
@@ -12,7 +16,7 @@
@@ -28,11 +32,11 @@ ], ]; - if (user()->podcasts !== []) { - foreach (user()->podcasts as $userPodcast) { + if ($userPodcasts !== []) { + foreach ($userPodcasts as $userPodcast) { $userPodcastTitle = esc($userPodcast->title); - $unreadNotificationDotDisplayClass = in_array($userPodcast->actor_id, user()->actorIdsWithUnreadNotifications, true) ? '' : 'hidden'; + $unreadNotificationDotDisplayClass = in_array($userPodcast->actor_id, $actorIdsWithUnreadNotifications, true) ? '' : 'hidden'; $items[] = [ 'type' => 'link', @@ -60,7 +64,7 @@ } ?> - + podcasts as $userPodcast) { - $checkMark = interact_as_actor_id() === $userPodcast->actor_id ? icon('check', 'ml-2 bg-accent-base text-accent-contrast rounded-full') : ''; - $userPodcastTitle = esc($userPodcast->title); + foreach ($userPodcasts as $userPodcast) { + if (can_podcast(auth()->user(), $userPodcast->id, 'interact-as')) { + $checkMark = interact_as_actor_id() === $userPodcast->actor_id ? icon('check', 'ml-2 bg-accent-base text-accent-contrast rounded-full') : ''; + $userPodcastTitle = esc($userPodcast->title); - $interactButtons .= << -
{$userPodcastTitle}{$checkMark}
- - CODE_SAMPLE; + $interactButtons .= << +
{$userPodcastTitle}{$checkMark}
+ + CODE_SAMPLE; + } } $interactAsText = lang('Common.choose_interact'); - $route = route_to('interact-as-actor'); + $interactAsRoute = route_to('interact-as-actor'); $csrfField = csrf_field(); $menuItems = [ @@ -113,14 +123,14 @@ ], ]; - if (user()->podcasts !== []) { + if ($userPodcasts !== []) { $menuItems = array_merge([ [ 'type' => 'html', 'content' => esc(<< {$interactAsText} -
+ {$csrfField} {$interactButtons}
diff --git a/themes/cp_app/embed.php b/themes/cp_app/embed.php index 26621ea0..072160b9 100644 --- a/themes/cp_app/embed.php +++ b/themes/cp_app/embed.php @@ -45,7 +45,7 @@ style="--vm-player-box-shadow:0; --vm-player-theme: hsl(var(--color-accent-base)); --vm-control-focus-color: hsl(var(--color-accent-contrast)); --vm-control-spacing: 4px; --vm-menu-item-focus-bg: hsl(var(--color-background-highlight)); --vm-control-icon-size: 24px; " > - audio->file_url : $episode->audio_analytics_url . + loggedIn() ? $episode->audio->file_url : $episode->audio_analytics_url . (isset($_SERVER['HTTP_REFERER']) ? '?_from=' . parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST) diff --git a/themes/cp_app/home.php b/themes/cp_app/home.php index c6f62d4c..b04f2994 100644 --- a/themes/cp_app/home.php +++ b/themes/cp_app/home.php @@ -32,7 +32,7 @@ - check()): ?> + loggedIn()): ?> include('_admin_navbar') ?> diff --git a/themes/cp_app/pages/_layout.php b/themes/cp_app/pages/_layout.php index f0eb353f..0e3134fe 100644 --- a/themes/cp_app/pages/_layout.php +++ b/themes/cp_app/pages/_layout.php @@ -34,7 +34,7 @@ - check()): ?> + loggedIn()): ?> include('_admin_navbar') ?> diff --git a/themes/cp_app/pages/map.php b/themes/cp_app/pages/map.php index 8f61b83a..228f1488 100644 --- a/themes/cp_app/pages/map.php +++ b/themes/cp_app/pages/map.php @@ -37,7 +37,7 @@ - check()): ?> + loggedIn()): ?> include('_admin_navbar') ?> diff --git a/themes/cp_auth/_layout.php b/themes/cp_auth/_layout.php index 7cbee616..166dff03 100644 --- a/themes/cp_auth/_layout.php +++ b/themes/cp_auth/_layout.php @@ -1,6 +1,7 @@ - + diff --git a/themes/cp_auth/email_2fa_show.php b/themes/cp_auth/email_2fa_show.php new file mode 100644 index 00000000..ddedc530 --- /dev/null +++ b/themes/cp_auth/email_2fa_show.php @@ -0,0 +1,38 @@ +extend(config('Auth')->views['layout']) ?> + +section('title') ?> endSection() ?> + +section('content') ?> + +
+
+
+
+ +

+ + +
+ + +
+ + + +
+ + value="email) ?>" required /> +
+ +
+ +
+ +
+
+
+
+ +endSection() ?> diff --git a/themes/cp_auth/email_2fa_verify.php b/themes/cp_auth/email_2fa_verify.php new file mode 100644 index 00000000..e9dad7f4 --- /dev/null +++ b/themes/cp_auth/email_2fa_verify.php @@ -0,0 +1,36 @@ +extend(config('Auth')->views['layout']) ?> + +section('title') ?> endSection() ?> + +section('content') ?> + +
+
+
+
+ +

+ + +
+ + +
+ + + +
+ +
+ +
+ +
+ +
+
+
+
+ +endSection() ?> diff --git a/themes/cp_auth/email_activate_show.php b/themes/cp_auth/email_activate_show.php new file mode 100644 index 00000000..d0d93d73 --- /dev/null +++ b/themes/cp_auth/email_activate_show.php @@ -0,0 +1,28 @@ + +extend(config('Auth')->views['layout']) ?> + +section('title') ?>endSection() ?> + +section('content') ?> + +

+ +
+ + + + + + + + +endSection() ?> diff --git a/themes/cp_auth/emails/activation.php b/themes/cp_auth/emails/activation.php deleted file mode 100644 index 4dc506c6..00000000 --- a/themes/cp_auth/emails/activation.php +++ /dev/null @@ -1,11 +0,0 @@ -

This is activation email for your account on .

- -

To activate your account use this URL.

- -

Activate account.

- -
- -

If you did not registered on this website, you can safely ignore this email.

diff --git a/themes/cp_auth/emails/email_2fa_email.php b/themes/cp_auth/emails/email_2fa_email.php new file mode 100644 index 00000000..bbc010c5 --- /dev/null +++ b/themes/cp_auth/emails/email_2fa_email.php @@ -0,0 +1 @@ +

diff --git a/themes/cp_auth/emails/email_activate_email.php b/themes/cp_auth/emails/email_activate_email.php new file mode 100644 index 00000000..9686df46 --- /dev/null +++ b/themes/cp_auth/emails/email_activate_email.php @@ -0,0 +1,3 @@ +

+ +

diff --git a/themes/cp_auth/emails/forgot.php b/themes/cp_auth/emails/forgot.php deleted file mode 100644 index e6f199aa..00000000 --- a/themes/cp_auth/emails/forgot.php +++ /dev/null @@ -1,13 +0,0 @@ -

Someone requested a password reset at this email address for .

- -

To reset the password use this code or URL and follow the instructions.

- -

Your Code:

- -

Visit the Reset Form.

- -
- -

If you did not request a password reset, you can safely ignore this email.

diff --git a/themes/cp_auth/emails/magic_link_email.php b/themes/cp_auth/emails/magic_link_email.php new file mode 100644 index 00000000..d6f6a943 --- /dev/null +++ b/themes/cp_auth/emails/magic_link_email.php @@ -0,0 +1,5 @@ +

+ + + +

diff --git a/themes/cp_auth/emails/welcome_email.php b/themes/cp_auth/emails/welcome_email.php new file mode 100644 index 00000000..13da1138 --- /dev/null +++ b/themes/cp_auth/emails/welcome_email.php @@ -0,0 +1,10 @@ +

+ current_domain(), + 'numberOfHours' => setting('Auth.welcomeLinkLifetime') / 3600, +]) ?>

+ + + + +

diff --git a/themes/cp_auth/forgot.php b/themes/cp_auth/forgot.php deleted file mode 100644 index 57756758..00000000 --- a/themes/cp_auth/forgot.php +++ /dev/null @@ -1,24 +0,0 @@ - -extend($config->viewLayout) ?> - -section('title') ?> - -endSection() ?> - - -section('content') ?> - -

- -
- - - - - - -endSection() ?> diff --git a/themes/cp_auth/login.php b/themes/cp_auth/login.php index ba6fd06e..34d2849e 100644 --- a/themes/cp_auth/login.php +++ b/themes/cp_auth/login.php @@ -1,29 +1,38 @@ -extend($config->viewLayout) ?> +extend(config('Auth')->views['layout']) ?> -section('title') ?> - -endSection() ?> +section('title') ?>endSection() ?> section('content') ?> -
+ + type="email" + inputmode="email" + autocomplete="email" + autofocus="autofocus" + /> - + + + + + + endSection() ?> @@ -32,14 +41,12 @@ section('footer') ?>
- allowRegistration): ?> - - - + +

+ + +

+
endSection() ?> diff --git a/themes/cp_auth/magic_link_form.php b/themes/cp_auth/magic_link_form.php new file mode 100644 index 00000000..b4f9c978 --- /dev/null +++ b/themes/cp_auth/magic_link_form.php @@ -0,0 +1,24 @@ + +extend(config('Auth')->views['layout']) ?> + +section('title') ?> endSection() ?> + +section('content') ?> + +
+ + + + + + + +endSection() ?> diff --git a/themes/cp_auth/magic_link_message.php b/themes/cp_auth/magic_link_message.php new file mode 100644 index 00000000..ade1b651 --- /dev/null +++ b/themes/cp_auth/magic_link_message.php @@ -0,0 +1,13 @@ +extend(config('Auth')->views['layout']) ?> + +section('title') ?> endSection() ?> + +section('content') ?> + +

+ +

+ + + +endSection() ?> diff --git a/themes/cp_auth/magic_link_set_password.php b/themes/cp_auth/magic_link_set_password.php new file mode 100644 index 00000000..64d85614 --- /dev/null +++ b/themes/cp_auth/magic_link_set_password.php @@ -0,0 +1,26 @@ + +extend(config('Auth')->views['layout']) ?> + +section('title') ?> + +endSection() ?> + + +section('content') ?> + +
+ + + + + + + + +endSection() ?> diff --git a/themes/cp_auth/register.php b/themes/cp_auth/register.php index 292f25ba..c8eeccc2 100644 --- a/themes/cp_auth/register.php +++ b/themes/cp_auth/register.php @@ -1,5 +1,5 @@ -extend($config->viewLayout) ?> +extend(config('Auth')->views['layout']) ?> section('title') ?> @@ -8,19 +8,22 @@ section('content') ?> -
+ - - + + + + @@ -41,10 +53,10 @@

+) ?>">

endSection() ?> diff --git a/themes/cp_auth/reset.php b/themes/cp_auth/reset.php deleted file mode 100644 index 2b9199a9..00000000 --- a/themes/cp_auth/reset.php +++ /dev/null @@ -1,38 +0,0 @@ - -extend($config->viewLayout) ?> - -section('title') ?> - -endSection() ?> - -section('content') ?> - -

- - - - - - - - - - - - - - -endSection() ?> diff --git a/themes/cp_install/_layout.php b/themes/cp_install/_layout.php index 0e72bbdb..6d35f502 100644 --- a/themes/cp_install/_layout.php +++ b/themes/cp_install/_layout.php @@ -1,5 +1,6 @@ - + diff --git a/themes/cp_install/create_superadmin.php b/themes/cp_install/create_superadmin.php index 25aeb06b..9fe28fa7 100644 --- a/themes/cp_install/create_superadmin.php +++ b/themes/cp_install/create_superadmin.php @@ -11,14 +11,14 @@