Compare commits

...

3 Commits

Author SHA1 Message Date
868337134d feat: get permissions call 2025-06-24 12:59:10 +02:00
9372673bf1 test: get user permissions endpoint 2025-06-24 12:58:29 +02:00
0eea81b42f feat: update repo with group, roles and permissions 2025-06-24 12:58:14 +02:00
8 changed files with 222 additions and 0 deletions

View File

@ -0,0 +1,39 @@
package admin
import (
"encoding/json"
"log"
"net/http"
"gitea.local/admin/hspguard/internal/repository"
"gitea.local/admin/hspguard/internal/util"
"gitea.local/admin/hspguard/internal/web"
"github.com/google/uuid"
)
func (h *AdminHandler) GetPermissions(w http.ResponseWriter, r *http.Request) {
userId, ok := util.GetRequestUserId(r.Context())
if !ok {
web.Error(w, "failed to get user id from auth session", http.StatusInternalServerError)
return
}
permissions, err := h.repo.GetUserPermissions(r.Context(), uuid.MustParse(userId))
if err != nil {
log.Println("ERR: Failed to list permissions from db:", err)
web.Error(w, "failed to get user permissions", http.StatusInternalServerError)
return
}
if len(permissions) == 0 {
permissions = make([]repository.Permission, 0)
}
encoder := json.NewEncoder(w)
w.Header().Set("Content-Type", "application/json")
if err := encoder.Encode(permissions); err != nil {
web.Error(w, "failed to encode response", http.StatusInternalServerError)
}
}

View File

@ -41,6 +41,8 @@ func (h *AdminHandler) RegisterRoutes(router chi.Router) {
r.Get("/service-sessions", h.GetServiceSessions)
r.Patch("/service-sessions/revoke/{id}", h.RevokeUserSession)
r.Get("/permissions", h.GetPermissions)
})
router.Get("/api-services/client/{client_id}", h.GetApiServiceCID)

View File

@ -25,6 +25,40 @@ type ApiService struct {
IconUrl *string `json:"icon_url"`
}
type Group struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
Description *string `json:"description"`
}
type GroupPermission struct {
GroupID uuid.UUID `json:"group_id"`
PermissionID uuid.UUID `json:"permission_id"`
}
type GroupRole struct {
GroupID uuid.UUID `json:"group_id"`
RoleID uuid.UUID `json:"role_id"`
}
type Permission struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
Scope string `json:"scope"`
Description *string `json:"description"`
}
type Role struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
Description *string `json:"description"`
}
type RolePermission struct {
RoleID uuid.UUID `json:"role_id"`
PermissionID uuid.UUID `json:"permission_id"`
}
type ServiceSession struct {
ID uuid.UUID `json:"id"`
ServiceID uuid.UUID `json:"service_id"`
@ -60,6 +94,21 @@ type User struct {
Verified bool `json:"verified"`
}
type UserGroup struct {
UserID uuid.UUID `json:"user_id"`
GroupID uuid.UUID `json:"group_id"`
}
type UserPermission struct {
UserID uuid.UUID `json:"user_id"`
PermissionID uuid.UUID `json:"permission_id"`
}
type UserRole struct {
UserID uuid.UUID `json:"user_id"`
RoleID uuid.UUID `json:"role_id"`
}
type UserSession struct {
ID uuid.UUID `json:"id"`
UserID uuid.UUID `json:"user_id"`

View File

@ -0,0 +1,70 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// source: permissions.sql
package repository
import (
"context"
"github.com/google/uuid"
)
const getUserPermissions = `-- name: GetUserPermissions :many
SELECT DISTINCT p.id,p.name,p.scope,p.description
FROM permissions p
LEFT JOIN role_permissions rp_user
ON p.id = rp_user.permission_id
LEFT JOIN user_roles ur
ON rp_user.role_id = ur.role_id AND ur.user_id = $1
LEFT JOIN user_groups ug
ON ug.user_id = $1
LEFT JOIN group_roles gr
ON ug.group_id = gr.group_id
LEFT JOIN role_permissions rp_group
ON gr.role_id = rp_group.role_id AND rp_group.permission_id = p.id
LEFT JOIN user_permissions up
ON up.user_id = $1 AND up.permission_id = p.id
LEFT JOIN group_permissions gp
ON gp.group_id = ug.group_id AND gp.permission_id = p.id
WHERE ur.user_id IS NOT NULL
OR gr.group_id IS NOT NULL
OR up.user_id IS NOT NULL
OR gp.group_id IS NOT NULL
ORDER BY p.scope
`
// From roles assigned directly to the user
// From roles assigned to user's groups
// Direct permissions to user
// Direct permissions to user's groups
func (q *Queries) GetUserPermissions(ctx context.Context, userID uuid.UUID) ([]Permission, error) {
rows, err := q.db.Query(ctx, getUserPermissions, userID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Permission
for rows.Next() {
var i Permission
if err := rows.Scan(
&i.ID,
&i.Name,
&i.Scope,
&i.Description,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}

32
queries/permissions.sql Normal file
View File

@ -0,0 +1,32 @@
-- name: GetUserPermissions :many
SELECT DISTINCT p.id,p.name,p.scope,p.description
FROM permissions p
-- From roles assigned directly to the user
LEFT JOIN role_permissions rp_user
ON p.id = rp_user.permission_id
LEFT JOIN user_roles ur
ON rp_user.role_id = ur.role_id AND ur.user_id = $1
-- From roles assigned to user's groups
LEFT JOIN user_groups ug
ON ug.user_id = $1
LEFT JOIN group_roles gr
ON ug.group_id = gr.group_id
LEFT JOIN role_permissions rp_group
ON gr.role_id = rp_group.role_id AND rp_group.permission_id = p.id
-- Direct permissions to user
LEFT JOIN user_permissions up
ON up.user_id = $1 AND up.permission_id = p.id
-- Direct permissions to user's groups
LEFT JOIN group_permissions gp
ON gp.group_id = ug.group_id AND gp.permission_id = p.id
WHERE ur.user_id IS NOT NULL
OR gr.group_id IS NOT NULL
OR up.user_id IS NOT NULL
OR gp.group_id IS NOT NULL
ORDER BY p.scope;

View File

@ -0,0 +1,16 @@
import type { AppPermission } from "@/types";
import { axios, handleApiError } from "..";
export type FetchPermissionsResponse = AppPermission[];
export const getPermissionsApi =
async (): Promise<FetchPermissionsResponse> => {
const response = await axios.get<FetchPermissionsResponse>(
"/api/v1/admin/permissions",
);
if (response.status !== 200 && response.status !== 201)
throw await handleApiError(response);
return response.data;
};

View File

@ -1,3 +1,4 @@
import { getPermissionsApi } from "@/api/admin/permissions";
import Breadcrumbs from "@/components/ui/breadcrumbs";
import { Button } from "@/components/ui/button";
import Avatar from "@/feature/Avatar";
@ -15,6 +16,12 @@ const AdminUsersPage: FC = () => {
fetchUsers();
}, [fetchUsers]);
useEffect(() => {
getPermissionsApi().then((res) => {
console.log("permissions response:", res);
});
}, []);
return (
<div className="relative flex flex-col items-stretch w-full">
<div className="p-4">

View File

@ -78,3 +78,10 @@ export interface DeviceInfo {
browser: string;
browser_version: string;
}
export interface AppPermission {
id: string;
name: string;
scope: string;
description: string;
}