From 515ea5a9c1397f216fcb7e8b63e4dade50d57cd5 Mon Sep 17 00:00:00 2001 From: Tanvir Ahmed Date: Mon, 6 Apr 2026 17:38:03 +0000 Subject: [PATCH 1/2] chore: Implemented an external service for the plugin to expose methods that can be used by other plugins --- models/services.go | 19 +++++++----------- .../access-control/access_control_service.go | 20 +++++++++++++++++++ plugins/access-control/plugin.go | 3 +++ services/access_control.go | 7 +++++++ 4 files changed, 37 insertions(+), 12 deletions(-) create mode 100644 plugins/access-control/access_control_service.go create mode 100644 services/access_control.go diff --git a/models/services.go b/models/services.go index 9a599590..b24b19de 100644 --- a/models/services.go +++ b/models/services.go @@ -9,20 +9,15 @@ const ( ServiceSession ServiceID = "session_service" ServiceVerification ServiceID = "verification_service" ServiceToken ServiceID = "token_service" + ServicePassword ServiceID = "password_service" - // EMAIL - ServicePassword ServiceID = "password_service" - ServiceMailer ServiceID = "mailer_service" - - // JWT - ServiceJWT ServiceID = "jwt_service" - - // CONFIG - ServiceConfigManager ServiceID = "config_manager_service" - ServiceAdmin ServiceID = "admin_service" - - // STORAGE + // Plugins + ServiceAccessControl ServiceID = "access_control_service" + ServiceAdmin ServiceID = "admin_service" ServiceSecondaryStorage ServiceID = "secondary_storage_service" + ServiceMailer ServiceID = "mailer_service" + ServiceJWT ServiceID = "jwt_service" + ServiceConfigManager ServiceID = "config_manager_service" ) func (id ServiceID) String() string { diff --git a/plugins/access-control/access_control_service.go b/plugins/access-control/access_control_service.go new file mode 100644 index 00000000..bcf320f4 --- /dev/null +++ b/plugins/access-control/access_control_service.go @@ -0,0 +1,20 @@ +package accesscontrol + +import "context" + +type AccessControlService struct { + api *API +} + +func NewAccessControlService(api *API) *AccessControlService { + return &AccessControlService{api: api} +} + +func (s *AccessControlService) RoleExists(ctx context.Context, roleName string) (bool, error) { + role, err := s.api.GetRoleByName(ctx, roleName) + if err != nil { + return false, err + } + + return role != nil && role.ID != "", nil +} diff --git a/plugins/access-control/plugin.go b/plugins/access-control/plugin.go index 3a94b312..5c67e992 100644 --- a/plugins/access-control/plugin.go +++ b/plugins/access-control/plugin.go @@ -63,6 +63,9 @@ func (p *AccessControlPlugin) Init(ctx *models.PluginContext) error { ) p.Api = NewAPI(useCases) + accessControlService := NewAccessControlService(p.Api) + ctx.ServiceRegistry.Register(models.ServiceAccessControl.String(), accessControlService) + return nil } diff --git a/services/access_control.go b/services/access_control.go new file mode 100644 index 00000000..9e76bcf6 --- /dev/null +++ b/services/access_control.go @@ -0,0 +1,7 @@ +package services + +import "context" + +type AccessControlService interface { + RoleExists(ctx context.Context, roleName string) (bool, error) +} From 8b49ce2e3d5c3d95b54f982c28f553e8f285a9ce Mon Sep 17 00:00:00 2001 From: Tanvir Ahmed Date: Mon, 6 Apr 2026 17:55:28 +0000 Subject: [PATCH 2/2] chore: Moved the access control service into the services folder --- config.example.toml | 210 +----------------- .../access-control/access_control_service.go | 20 -- plugins/access-control/plugin.go | 3 +- .../services/access_control_service.go | 20 ++ 4 files changed, 23 insertions(+), 230 deletions(-) delete mode 100644 plugins/access-control/access_control_service.go create mode 100644 plugins/access-control/services/access_control_service.go diff --git a/config.example.toml b/config.example.toml index 4870ccd6..288530a8 100644 --- a/config.example.toml +++ b/config.example.toml @@ -142,215 +142,7 @@ buffer_size = 100 [plugins] -# --- Config Manager Plugin (allows runtime configuration changes via the Admin API) --- - -# [plugins.config_manager] -# enabled = true - -# --- Access Control Plugin (role based access control) --- - -# [plugins.access_control] -# enabled = true - -# --- Admin Plugin (user, state, and impersonation operations) --- - -# [plugins.admin] -# enabled = true -# impersonation_max_expires_in = "15m" - -# --- Secondary Storage Plugin (provides secondary storage backends for other plugins) --- - -[plugins.secondary_storage] -enabled = true -provider = "memory" # Options: "memory", "database", "redis" - -[plugins.secondary_storage.memory] -cleanup_interval = "1m" - -# [plugins.secondary_storage.database] -# cleanup_interval = "1m" - -# [plugins.secondary_storage.redis] -# SECURITY NOTE: It is recommended to set the 'url' via the -# REDIS_URL environment variable rather than hardcoding it here. -# url = "" -# max_retries = 3 -# pool_size = 10 -# pool_timeout = "30s" - -# --- Email Plugin (handles email sending via SMTP or Resend) --- - -[plugins.email] -enabled = true -provider = "smtp" # Options: "smtp", "resend" -# Can also be set via FROM_ADDRESS environment variable -from_address = "" -tls_mode = "starttls" # Options: "off", "starttls", "tls" -# Optional: Fallback provider if primary fails -# fallback_provider = "resend" - -# SMTP Configuration (when provider = "smtp") -# [plugins.email.smtp] -# host = "" -# port = # Options: 25, 465, 587, 2525, -# user = "" -# pass = "" - -# Resend Configuration (when provider = "resend") -# [plugins.email.resend] -# NOTE: it is recommended to set the Resend API key via the RESEND_API_KEY environment variable rather than hardcoding it here. -# api_key = "" - -# --- CSRF Plugin (for protecting against CSRF attacks) --- - -[plugins.csrf] -enabled = true -cookie_name = "authula_csrf_token" -header_name = "X-AUTHULA-CSRF-TOKEN" -max_age = "24h" -secure = false -same_site = "lax" # Options: "lax", "strict", "none" (Note: HttpOnly=false and Secure=true are hardcoded for Double-Submit Cookie pattern) -# Go 1.25+ Header-based CSRF Protection (Recommended) -# When enabled, validates cross-origin requests using Sec-Fetch-Site and Origin/Host headers. -# This provides defense-in-depth alongside token validation. -# Disabled by default for specific clients (mobile apps, CLI tools, webhooks). -enable_header_protection = false - -# --- Email/Password Plugin --- - -[plugins.email_password] -enabled = true -min_password_length = 8 -max_password_length = 128 -disable_sign_up = false -require_email_verification = true -auto_sign_in = true -send_email_on_sign_up = true -send_email_on_sign_in = false -email_verification_expires_in = "24h" -password_reset_expires_in = "1h" -request_email_change_expires_in = "1h" - -# --- OAuth2 Plugin (for social logins) --- - -# [plugins.oauth2] -# enabled = true - -# SECURITY NOTE: It is recommended to set the 'client_secret' for each of these via their -# respective environment variables as shown in the .env.example file rather than hardcoding it here. -# [plugins.oauth2.providers.discord] -# enabled = true -# client_id = "your-client-id" -# client_secret = "your-client-secret" -# redirect_url = "http://localhost:8080/auth/oauth2/callback/discord" -# scopes = [] - -# [plugins.oauth2.providers.github] -# enabled = true -# client_id = "your-client-id" -# client_secret = "your-client-secret" -# redirect_url = "http://localhost:8080/auth/oauth2/callback/github" -# scopes = [] - -# [plugins.oauth2.providers.google] -# enabled = true -# client_id = "your-client-id" -# client_secret = "your-client-secret" -# redirect_url = "http://localhost:8080/auth/oauth2/callback/google" -# scopes = [] - -# Example: Additional fields for a custom OAuth2 provider -# auth_url = "https://provider.com/oauth2/authorize" -# token_url = "https://provider.com/oauth2/token" -# user_info_url = "https://provider.com/oauth2/userinfo" -# user_id_field = "sub" -# email_field = "email" -# name_field = "name" -# picture_field = "picture" - -# Example: Generic OAuth2 Provider Configuration -# You can add any custom OAuth2 provider by specifying a new section under [plugins.oauth2.providers.] -# Replace with your desired provider key (e.g., "mycustom") -# The following is a template for a generic OAuth2 provider: -# -# [plugins.oauth2.providers.mycustom] -# enabled = true -# client_id = "your-client-id" -# client_secret = "your-client-secret" -# redirect_url = "http://localhost:8080/auth/oauth2/callback/mycustom" -# auth_url = "https://provider.com/oauth2/authorize" -# token_url = "https://provider.com/oauth2/token" -# user_info_url = "https://provider.com/oauth2/userinfo" -# scopes = ["profile", "email"] -# # Optional: Custom field mappings if your provider returns non-standard fields -# user_id_field = "sub" -# email_field = "email_address" -# name_field = "full_name" -# picture_field = "avatar_url" - -# --- Session Plugin (use for SSR apps) --- - -# Cookie-based session authentication that stores session tokens as HTTP-only cookies -# Note: Client IP address and User-Agent are automatically captured from the request and -# stored in the session record for security audit trails and session management features. -[plugins.session] -enabled = true - -# --- JWT Plugin (use for SPAs and mobile apps) --- - -# [plugins.jwt] -# enabled = true -# algorithm = "eddsa" -# key_rotation_interval = "720h" -# key_rotation_grace_period = "1h" -# expires_in = "15m" -# refresh_expires_in = "168h" -# jwks_cache_ttl = "5m" -# refresh_grace_period = "1m" - -# --- Bearer Plugin (use for SPAs and mobile apps) --- - -# [plugins.bearer] -# enabled = true -# header_name = "Authorization" - -# --- Rate Limit Plugin (to prevent abuse and brute-force attacks) --- - -# [plugins.ratelimit] -# enabled = true -# window = "1m" -# max = 100 -# prefix = "ratelimit:" -# provider = "memory" # Options: "memory", "redis", "database" -# [plugins.ratelimit.custom_rules] -# "/path/to/your/endpoint" = { disabled = false, window = "1m", max = 5, prefix = "" } -# -# Optional: Configure cleanup intervals for storage providers -# [plugins.ratelimit.memory] -# cleanup_interval = "1m" # How often to remove expired entries (default: 1m) -# -# [plugins.ratelimit.database] -# cleanup_interval = "1m" # How often to remove expired entries from database (default: 1m) - -# --- Magic Link Plugin (Passwordless Authentication) --- - -# [plugins.magic_link] -# enabled = true -# expires_in = "15m" -# disable_sign_up = false - -# --- TOTP Plugin (Time-based One-Time Password) --- - -# [plugins.totp] -# enabled = true -# skip_verification_on_enable = false -# backup_code_count = 10 -# trusted_device_duration = "720h" -# trusted_devices_auto_cleanup = true -# trusted_devices_cleanup_interval = "1h" -# pending_token_expiry = "10m" -# secure_cookie = false -# same_site = "lax" # Options: "lax", "strict", "none" +# Follow the docs for plugin-specific configuration options under the "Plugins" section. # ----------------------------------- # - Route-to-Plugin Mappings (Standalone Mode) diff --git a/plugins/access-control/access_control_service.go b/plugins/access-control/access_control_service.go deleted file mode 100644 index bcf320f4..00000000 --- a/plugins/access-control/access_control_service.go +++ /dev/null @@ -1,20 +0,0 @@ -package accesscontrol - -import "context" - -type AccessControlService struct { - api *API -} - -func NewAccessControlService(api *API) *AccessControlService { - return &AccessControlService{api: api} -} - -func (s *AccessControlService) RoleExists(ctx context.Context, roleName string) (bool, error) { - role, err := s.api.GetRoleByName(ctx, roleName) - if err != nil { - return false, err - } - - return role != nil && role.ID != "", nil -} diff --git a/plugins/access-control/plugin.go b/plugins/access-control/plugin.go index 5c67e992..a87f4294 100644 --- a/plugins/access-control/plugin.go +++ b/plugins/access-control/plugin.go @@ -54,6 +54,8 @@ func (p *AccessControlPlugin) Init(ctx *models.PluginContext) error { userRolesService := services.NewUserRolesService(userRolesRepo, rolesRepo) userPermissionsService := services.NewUserPermissionsService(userPermissionsRepo) + accessControlService := services.NewAccessControlService(rolesService) + useCases := usecases.NewAccessControlUseCases( usecases.NewRolesUseCase(rolesService), usecases.NewPermissionsUseCase(permissionsService), @@ -63,7 +65,6 @@ func (p *AccessControlPlugin) Init(ctx *models.PluginContext) error { ) p.Api = NewAPI(useCases) - accessControlService := NewAccessControlService(p.Api) ctx.ServiceRegistry.Register(models.ServiceAccessControl.String(), accessControlService) return nil diff --git a/plugins/access-control/services/access_control_service.go b/plugins/access-control/services/access_control_service.go new file mode 100644 index 00000000..85699ad1 --- /dev/null +++ b/plugins/access-control/services/access_control_service.go @@ -0,0 +1,20 @@ +package services + +import "context" + +type AccessControlService struct { + rolesService *RolesService +} + +func NewAccessControlService(rolesService *RolesService) *AccessControlService { + return &AccessControlService{rolesService: rolesService} +} + +func (s *AccessControlService) RoleExists(ctx context.Context, roleName string) (bool, error) { + role, err := s.rolesService.GetRoleByName(ctx, roleName) + if err != nil { + return false, err + } + + return role != nil && role.ID != "", nil +}