feat: add optional guest access with role-based permissions

Implement opt-in guest access system allowing unauthenticated users to
view forum content with configurable permissions.

Features Added:
  - Guest access toggle in admin settings for forum-wide control
  - Guest role with configurable category-level permissions
(view/post/reply)
  - Role type system (admin, moderator, default, guest, custom) for
enhanced access control
  - Public page support in routing with automatic redirect to login when
disabled
  - Public settings API endpoint for unauthenticated access to forum
metadata
  - Guest role permissions UI in admin panel with clear capability
restrictions
  - Database migration with automatic role type assignment and guest
role seeding

Security & Permission Improvements:
  - Permission middleware now validates permissions on public pages
instead of skipping checks
  - Admin/moderator roles have full access; guest/default roles
restricted from moderation
  - Guest role cannot be assigned to authenticated users

Breaking Changes:
  - Forum title/subtitle moved from system config to app config
(auto-migrated)
  - Permission middleware behavior changed for PublicPage routes (now
checks permissions)
This commit is contained in:
2025-11-24 18:58:12 +02:00
parent 5dc7d3b7c1
commit d2baf2a813
49 changed files with 2963 additions and 962 deletions

View File

@@ -22,6 +22,7 @@ use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\ApiRoute;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\IRequest;
@@ -54,6 +55,7 @@ class ThreadController extends OCSController {
* 200: Threads returned
*/
#[NoAdminRequired]
#[PublicPage]
#[ApiRoute(verb: 'GET', url: '/api/threads')]
public function index(): DataResponse {
try {
@@ -86,6 +88,7 @@ class ThreadController extends OCSController {
* 200: Threads returned
*/
#[NoAdminRequired]
#[PublicPage]
#[RequirePermission('canView', resourceType: 'category', resourceIdParam: 'categoryId')]
#[ApiRoute(verb: 'GET', url: '/api/categories/{categoryId}/threads')]
public function byCategory(int $categoryId, int $limit = 50, int $offset = 0): DataResponse {
@@ -119,6 +122,7 @@ class ThreadController extends OCSController {
* 200: Threads returned
*/
#[NoAdminRequired]
#[PublicPage]
#[ApiRoute(verb: 'GET', url: '/api/users/{authorId}/threads')]
public function byAuthor(string $authorId, int $limit = 50, int $offset = 0): DataResponse {
try {
@@ -147,6 +151,7 @@ class ThreadController extends OCSController {
* 200: Thread returned
*/
#[NoAdminRequired]
#[PublicPage]
#[RequirePermission('canView', resourceType: 'category', resourceIdFromThreadId: 'id')]
#[ApiRoute(verb: 'GET', url: '/api/threads/{id}')]
public function show(int $id, string $incrementView = '1'): DataResponse {
@@ -179,6 +184,7 @@ class ThreadController extends OCSController {
* 200: Thread returned
*/
#[NoAdminRequired]
#[PublicPage]
#[ApiRoute(verb: 'GET', url: '/api/threads/slug/{slug}')]
public function bySlug(string $slug, string $incrementView = '1'): DataResponse {
try {