Junie: fix token authentication

This commit is contained in:
2025-06-06 11:26:42 -07:00
parent c6e109e99f
commit 13d009bf4f
11 changed files with 1146 additions and 49 deletions

174
data/auth.go Normal file
View File

@@ -0,0 +1,174 @@
package data
import (
"database/sql"
"fmt"
"github.com/oklog/ulid/v2"
)
// Permission represents a system permission
type Permission struct {
ID string
Resource string
Action string
Description string
}
// AuthorizationService provides methods for checking user permissions
type AuthorizationService struct {
db *sql.DB
}
// NewAuthorizationService creates a new authorization service
func NewAuthorizationService(db *sql.DB) *AuthorizationService {
return &AuthorizationService{db: db}
}
// UserHasPermission checks if a user has a specific permission for a resource and action
func (a *AuthorizationService) UserHasPermission(userID, resource, action string) (bool, error) {
query := `
SELECT COUNT(*) FROM permissions p
JOIN role_permissions rp ON p.id = rp.pid
JOIN user_roles ur ON rp.rid = ur.rid
WHERE ur.uid = ? AND p.resource = ? AND p.action = ?
`
var count int
err := a.db.QueryRow(query, userID, resource, action).Scan(&count)
if err != nil {
return false, fmt.Errorf("failed to check user permission: %w", err)
}
return count > 0, nil
}
// GetUserPermissions returns all permissions for a user based on their roles
func (a *AuthorizationService) GetUserPermissions(userID string) ([]Permission, error) {
query := `
SELECT DISTINCT p.id, p.resource, p.action, p.description FROM permissions p
JOIN role_permissions rp ON p.id = rp.pid
JOIN user_roles ur ON rp.rid = ur.rid
WHERE ur.uid = ?
`
rows, err := a.db.Query(query, userID)
if err != nil {
return nil, fmt.Errorf("failed to get user permissions: %w", err)
}
defer rows.Close()
var permissions []Permission
for rows.Next() {
var perm Permission
if err := rows.Scan(&perm.ID, &perm.Resource, &perm.Action, &perm.Description); err != nil {
return nil, fmt.Errorf("failed to scan permission: %w", err)
}
permissions = append(permissions, perm)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("error iterating permissions: %w", err)
}
return permissions, nil
}
// GetRolePermissions returns all permissions for a specific role
func (a *AuthorizationService) GetRolePermissions(roleID string) ([]Permission, error) {
query := `
SELECT p.id, p.resource, p.action, p.description FROM permissions p
JOIN role_permissions rp ON p.id = rp.pid
WHERE rp.rid = ?
`
rows, err := a.db.Query(query, roleID)
if err != nil {
return nil, fmt.Errorf("failed to get role permissions: %w", err)
}
defer rows.Close()
var permissions []Permission
for rows.Next() {
var perm Permission
if err := rows.Scan(&perm.ID, &perm.Resource, &perm.Action, &perm.Description); err != nil {
return nil, fmt.Errorf("failed to scan permission: %w", err)
}
permissions = append(permissions, perm)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("error iterating permissions: %w", err)
}
return permissions, nil
}
// GrantPermissionToRole grants a permission to a role
func (a *AuthorizationService) GrantPermissionToRole(roleID, permissionID string) error {
// Check if the role-permission relationship already exists
checkQuery := `SELECT COUNT(*) FROM role_permissions WHERE rid = ? AND pid = ?`
var count int
err := a.db.QueryRow(checkQuery, roleID, permissionID).Scan(&count)
if err != nil {
return fmt.Errorf("failed to check role permission: %w", err)
}
if count > 0 {
return nil // Permission already granted
}
// Generate a new ID for the role-permission relationship
id := GenerateID()
// Insert the new role-permission relationship
insertQuery := `INSERT INTO role_permissions (id, rid, pid) VALUES (?, ?, ?)`
_, err = a.db.Exec(insertQuery, id, roleID, permissionID)
if err != nil {
return fmt.Errorf("failed to grant permission to role: %w", err)
}
return nil
}
// RevokePermissionFromRole revokes a permission from a role
func (a *AuthorizationService) RevokePermissionFromRole(roleID, permissionID string) error {
query := `DELETE FROM role_permissions WHERE rid = ? AND pid = ?`
_, err := a.db.Exec(query, roleID, permissionID)
if err != nil {
return fmt.Errorf("failed to revoke permission from role: %w", err)
}
return nil
}
// GetAllPermissions returns all permissions in the system
func (a *AuthorizationService) GetAllPermissions() ([]Permission, error) {
query := `SELECT id, resource, action, description FROM permissions`
rows, err := a.db.Query(query)
if err != nil {
return nil, fmt.Errorf("failed to get permissions: %w", err)
}
defer rows.Close()
var permissions []Permission
for rows.Next() {
var perm Permission
if err := rows.Scan(&perm.ID, &perm.Resource, &perm.Action, &perm.Description); err != nil {
return nil, fmt.Errorf("failed to scan permission: %w", err)
}
permissions = append(permissions, perm)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("error iterating permissions: %w", err)
}
return permissions, nil
}
// GenerateID generates a unique ID for database records
func GenerateID() string {
return ulid.Make().String()
}

223
data/auth_test.go Normal file
View File

@@ -0,0 +1,223 @@
package data
import (
"database/sql"
"os"
"testing"
_ "github.com/mattn/go-sqlite3"
)
func setupTestDB(t *testing.T) (*sql.DB, func()) {
// Create a temporary database for testing
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Fatalf("Failed to open in-memory database: %v", err)
}
// Read the schema file
schemaBytes, err := os.ReadFile("../database/schema.sql")
if err != nil {
t.Fatalf("Failed to read schema file: %v", err)
}
schema := string(schemaBytes)
// Execute the schema
_, err = db.Exec(schema)
if err != nil {
t.Fatalf("Failed to execute schema: %v", err)
}
// Create test data
setupTestData(t, db)
// Return the database and a cleanup function
return db, func() {
db.Close()
}
}
func setupTestData(t *testing.T, db *sql.DB) {
// Create test users
_, err := db.Exec(`INSERT INTO users (id, created, user, password, salt) VALUES
('user1', 1622505600, 'testadmin', 'dummy', 'dummy'),
('user2', 1622505600, 'testoperator', 'dummy', 'dummy'),
('user3', 1622505600, 'testuser', 'dummy', 'dummy')`)
if err != nil {
t.Fatalf("Failed to insert test users: %v", err)
}
// Create test roles (these should already exist from schema.sql)
// But we'll check and insert if needed
var count int
err = db.QueryRow("SELECT COUNT(*) FROM roles WHERE role = 'admin'").Scan(&count)
if err != nil {
t.Fatalf("Failed to check roles: %v", err)
}
if count == 0 {
_, err = db.Exec(`INSERT INTO roles (id, role) VALUES
('role_admin', 'admin'),
('role_db_operator', 'db_operator'),
('role_user', 'user')`)
if err != nil {
t.Fatalf("Failed to insert test roles: %v", err)
}
}
// Assign roles to users
_, err = db.Exec(`INSERT INTO user_roles (id, uid, rid) VALUES
('ur1', 'user1', 'role_admin'),
('ur2', 'user2', 'role_db_operator'),
('ur3', 'user3', 'role_user')`)
if err != nil {
t.Fatalf("Failed to assign roles to users: %v", err)
}
}
func TestUserHasPermission(t *testing.T) {
db, cleanup := setupTestDB(t)
defer cleanup()
authService := NewAuthorizationService(db)
tests := []struct {
name string
userID string
resource string
action string
want bool
}{
{
name: "Admin has database read permission",
userID: "user1",
resource: "database_credentials",
action: "read",
want: true,
},
{
name: "Admin has database write permission",
userID: "user1",
resource: "database_credentials",
action: "write",
want: true,
},
{
name: "DB Operator has database read permission",
userID: "user2",
resource: "database_credentials",
action: "read",
want: true,
},
{
name: "DB Operator does not have database write permission",
userID: "user2",
resource: "database_credentials",
action: "write",
want: false,
},
{
name: "Regular user does not have database read permission",
userID: "user3",
resource: "database_credentials",
action: "read",
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := authService.UserHasPermission(tt.userID, tt.resource, tt.action)
if err != nil {
t.Errorf("AuthorizationService.UserHasPermission() error = %v", err)
return
}
if got != tt.want {
t.Errorf("AuthorizationService.UserHasPermission() = %v, want %v", got, tt.want)
}
})
}
}
func TestGetUserPermissions(t *testing.T) {
db, cleanup := setupTestDB(t)
defer cleanup()
authService := NewAuthorizationService(db)
t.Run("Admin has all permissions", func(t *testing.T) {
permissions, err := authService.GetUserPermissions("user1")
if err != nil {
t.Errorf("AuthorizationService.GetUserPermissions() error = %v", err)
return
}
// Admin should have 4 permissions
if len(permissions) != 4 {
t.Errorf("Admin should have 4 permissions, got %d", len(permissions))
}
// Check for specific permissions
hasDBRead := false
hasDBWrite := false
for _, p := range permissions {
if p.Resource == "database_credentials" && p.Action == "read" {
hasDBRead = true
}
if p.Resource == "database_credentials" && p.Action == "write" {
hasDBWrite = true
}
}
if !hasDBRead {
t.Errorf("Admin should have database_credentials:read permission")
}
if !hasDBWrite {
t.Errorf("Admin should have database_credentials:write permission")
}
})
t.Run("DB Operator has limited permissions", func(t *testing.T) {
permissions, err := authService.GetUserPermissions("user2")
if err != nil {
t.Errorf("AuthorizationService.GetUserPermissions() error = %v", err)
return
}
// DB Operator should have 1 permission
if len(permissions) != 1 {
t.Errorf("DB Operator should have 1 permission, got %d", len(permissions))
}
// Check for specific permissions
hasDBRead := false
hasDBWrite := false
for _, p := range permissions {
if p.Resource == "database_credentials" && p.Action == "read" {
hasDBRead = true
}
if p.Resource == "database_credentials" && p.Action == "write" {
hasDBWrite = true
}
}
if !hasDBRead {
t.Errorf("DB Operator should have database_credentials:read permission")
}
if hasDBWrite {
t.Errorf("DB Operator should not have database_credentials:write permission")
}
})
t.Run("Regular user has no permissions", func(t *testing.T) {
permissions, err := authService.GetUserPermissions("user3")
if err != nil {
t.Errorf("AuthorizationService.GetUserPermissions() error = %v", err)
return
}
// Regular user should have 0 permissions
if len(permissions) != 0 {
t.Errorf("Regular user should have 0 permissions, got %d", len(permissions))
}
})
}

View File

@@ -25,6 +25,26 @@ type User struct {
Roles []string
}
// HasRole checks if the user has a specific role
func (u *User) HasRole(role string) bool {
for _, r := range u.Roles {
if r == role {
return true
}
}
return false
}
// HasPermission checks if the user has a specific permission using the authorization service
func (u *User) HasPermission(authService *AuthorizationService, resource, action string) (bool, error) {
return authService.UserHasPermission(u.ID, resource, action)
}
// GetPermissions returns all permissions for the user using the authorization service
func (u *User) GetPermissions(authService *AuthorizationService) ([]Permission, error) {
return authService.GetUserPermissions(u.ID)
}
type Login struct {
User string `json:"user"`
Password string `json:"password,omitzero"`