Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Authula Project Guidelines

### Code Style Guide
**Authula** is an open-source authentication solution that scales with you. Embed it as a library in your Go app, or run it as a standalone auth server with any tech stack. It simplifies adding robust authentication to backend services, empowering developers to build secure applications faster.

---

## Code Style Guide

- Always write clean code that is easy to read and maintain.
- Follow consistent naming conventions for variables, functions, and structs.
Expand All @@ -14,7 +18,9 @@
- Use interfaces to define behavior and promote decoupling. Never code to implementations. When writing services, make sure they implement an interface of a repository e.g. `UserService` imports `UserRepository`. This ensures that the service can be easily tested and swapped out with different implementations if needed.
- For other services, define interfaces in the `interfaces.go` file within the `services` package and implement them in separate files just like the password service is an interface which has an argon2 implementation. So now it can easily be swapped out for another implementation if needed without changing the rest of the code that depends on it.

# Testing Guidelines
---

## Testing Guidelines

- Write unit tests for as many components as possible to ensure reliability such as repositories, services and handlers as well as plugins.
- Use descriptive names for test cases to clearly indicate their purpose.
Expand All @@ -26,7 +32,9 @@
- Run `make build` to ensure the project builds successfully after changes.
- Then run `make test` to run all tests in the project.

### Documentation Guidelines
---

## Documentation Guidelines

- Keep documentation up to date with code changes.
- Use clear and concise language in documentation.
Expand All @@ -38,7 +46,9 @@
- When updating a feature, ensure that any related documentation is also updated to reflect the changes.
- Create all docs in markdown format and within a top level docs/ directory.

### Security Guidelines
---

## Security Guidelines

- Follow best practices for secure coding to prevent vulnerabilities.
- Regularly review and update dependencies to address security issues.
Expand All @@ -48,6 +58,8 @@
- Take into account the principle of least privilege when designing access controls.
- Always take into consideration edge cases and loopholes that could be exploited by attackers and implement safeguards against them.

### Final Notes
---

## Agent Skills

Always follow the Agent Skills located in the folder `.github/skills/` as it contains all the skills and playbooks you need to follow to make sure you are adhering to the project guidelines and best practices.
49 changes: 49 additions & 0 deletions internal/tests/mock_objects.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package tests

import (
"context"

"github.com/stretchr/testify/mock"

"github.com/Authula/authula/models"
)

type MockLogger struct{}

func (m *MockLogger) Debug(msg string, args ...any) {}
func (m *MockLogger) Info(msg string, args ...any) {}
func (m *MockLogger) Warn(msg string, args ...any) {}
func (m *MockLogger) Error(msg string, args ...any) {}
func (m *MockLogger) Panic(msg string, args ...any) {}
func (m *MockLogger) WithField(key string, value any) models.Logger {
return m
}
func (m *MockLogger) WithFields(fields map[string]any) models.Logger {
return m
}

type MockEventBus struct {
mock.Mock
}

func (m *MockEventBus) Publish(ctx context.Context, event models.Event) error {
args := m.Called(ctx, event)
return args.Error(0)
}

func (m *MockEventBus) Close() error {
args := m.Called()
return args.Error(0)
}

func (m *MockEventBus) Subscribe(topic string, handler models.EventHandler) (models.SubscriptionID, error) {
args := m.Called(topic, handler)
if args.Get(0) == nil {
return 0, args.Error(1)
}
return args.Get(0).(models.SubscriptionID), args.Error(1)
}

func (m *MockEventBus) Unsubscribe(topic string, subscriptionID models.SubscriptionID) {
m.Called(topic, subscriptionID)
}
40 changes: 0 additions & 40 deletions internal/tests/mock_services.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,46 +302,6 @@ func (m *MockMailerService) SendEmail(ctx context.Context, to string, subject st
return args.Error(0)
}

type MockLogger struct{}

func (m *MockLogger) Debug(msg string, args ...any) {}
func (m *MockLogger) Info(msg string, args ...any) {}
func (m *MockLogger) Warn(msg string, args ...any) {}
func (m *MockLogger) Error(msg string, args ...any) {}
func (m *MockLogger) Panic(msg string, args ...any) {}
func (m *MockLogger) WithField(key string, value any) models.Logger {
return m
}
func (m *MockLogger) WithFields(fields map[string]any) models.Logger {
return m
}

type MockEventBus struct {
mock.Mock
}

func (m *MockEventBus) Publish(ctx context.Context, event models.Event) error {
args := m.Called(ctx, event)
return args.Error(0)
}

func (m *MockEventBus) Close() error {
args := m.Called()
return args.Error(0)
}

func (m *MockEventBus) Subscribe(topic string, handler models.EventHandler) (models.SubscriptionID, error) {
args := m.Called(topic, handler)
if args.Get(0) == nil {
return 0, args.Error(1)
}
return args.Get(0).(models.SubscriptionID), args.Error(1)
}

func (m *MockEventBus) Unsubscribe(topic string, subscriptionID models.SubscriptionID) {
m.Called(topic, subscriptionID)
}

type MockServiceRegistry struct {
mock.Mock
}
Expand Down
12 changes: 12 additions & 0 deletions models/plugins_context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package models

const (
ContextAccessControlAssignRole ContextKey = "access_control.assign_role"
)

// Access Control

type AccessControlAssignRoleContext struct {
UserID string
RoleName string
}
59 changes: 22 additions & 37 deletions plugins/access-control/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,28 @@ package accesscontrol
import (
"context"

"github.com/Authula/authula/plugins/access-control/repositories"
"github.com/Authula/authula/plugins/access-control/types"
"github.com/Authula/authula/plugins/access-control/usecases"
)

type API struct {
useCases *usecases.UseCases
rolePermissionRepo repositories.RolePermissionRepository
userAccessRepo repositories.UserAccessRepository
useCases *usecases.UseCases
}

func NewAPI(
useCases *usecases.UseCases,
rolePermissionRepo repositories.RolePermissionRepository,
userAccessRepo repositories.UserAccessRepository,
) *API {
return &API{
useCases: useCases,
rolePermissionRepo: rolePermissionRepo,
userAccessRepo: userAccessRepo,
}
func NewAPI(useCases *usecases.UseCases) *API {
return &API{useCases: useCases}
}

func (a *API) RolePermissionRepository() repositories.RolePermissionRepository {
return a.rolePermissionRepo
}

func (a *API) UserAccessRepository() repositories.UserAccessRepository {
return a.userAccessRepo
}

// Roles and permissions
// Roles

func (a *API) GetAllRoles(ctx context.Context) ([]types.Role, error) {
return a.useCases.GetAllRoles(ctx)
}

func (a *API) GetRoleByName(ctx context.Context, roleName string) (*types.Role, error) {
return a.useCases.GetRoleByName(ctx, roleName)
}

func (a *API) GetRoleByID(ctx context.Context, roleID string) (*types.RoleDetails, error) {
return a.useCases.GetRoleByID(ctx, roleID)
}
Expand All @@ -56,6 +41,8 @@ func (a *API) DeleteRole(ctx context.Context, roleID string) error {
return a.useCases.DeleteRole(ctx, roleID)
}

// Permissions

func (a *API) CreatePermission(ctx context.Context, req types.CreatePermissionRequest) (*types.Permission, error) {
return a.useCases.CreatePermission(ctx, req)
}
Expand All @@ -64,6 +51,10 @@ func (a *API) GetAllPermissions(ctx context.Context) ([]types.Permission, error)
return a.useCases.GetAllPermissions(ctx)
}

func (a *API) GetPermissionByID(ctx context.Context, permissionID string) (*types.Permission, error) {
return a.useCases.GetPermissionByID(ctx, permissionID)
}

func (a *API) GetRolePermissions(ctx context.Context, roleID string) ([]types.UserPermissionInfo, error) {
return a.useCases.GetRolePermissions(ctx, roleID)
}
Expand All @@ -76,6 +67,8 @@ func (a *API) DeletePermission(ctx context.Context, permissionID string) error {
return a.useCases.DeletePermission(ctx, permissionID)
}

// Role permissions

func (a *API) AddPermissionToRole(ctx context.Context, roleID string, permissionID string, grantedByUserID *string) error {
return a.useCases.AddPermissionToRole(ctx, roleID, permissionID, grantedByUserID)
}
Expand All @@ -88,7 +81,7 @@ func (a *API) ReplaceRolePermissions(ctx context.Context, roleID string, permiss
return a.useCases.ReplaceRolePermissions(ctx, roleID, permissionIDs, grantedByUserID)
}

// User roles and permissions
// User roles

func (a *API) GetUserRoles(ctx context.Context, userID string) ([]types.UserRoleInfo, error) {
return a.useCases.GetUserRoles(ctx, userID)
Expand All @@ -106,20 +99,12 @@ func (a *API) ReplaceUserRoles(ctx context.Context, userID string, roleIDs []str
return a.useCases.ReplaceUserRoles(ctx, userID, roleIDs, assignedByUserID)
}

func (a *API) GetUserEffectivePermissions(ctx context.Context, userID string) ([]types.UserPermissionInfo, error) {
return a.useCases.GetUserEffectivePermissions(ctx, userID)
}

// User access and permissions

func (a *API) HasPermissions(ctx context.Context, userID string, requiredPermissions []string) (bool, error) {
return a.useCases.HasPermissions(ctx, userID, requiredPermissions)
}
// User permissions

func (a *API) GetUserWithRolesByID(ctx context.Context, userID string) (*types.UserWithRoles, error) {
return a.useCases.GetUserWithRolesByID(ctx, userID)
func (a *API) GetUserPermissions(ctx context.Context, userID string) ([]types.UserPermissionInfo, error) {
return a.useCases.GetUserPermissions(ctx, userID)
}

func (a *API) GetUserWithPermissionsByID(ctx context.Context, userID string) (*types.UserWithPermissions, error) {
return a.useCases.GetUserWithPermissionsByID(ctx, userID)
func (a *API) HasPermissions(ctx context.Context, userID string, permissionNames []string) (bool, error) {
return a.useCases.HasPermissions(ctx, userID, permissionNames)
}
Loading