Compare commits
5 Commits
main
...
868337134d
Author | SHA1 | Date | |
---|---|---|---|
868337134d | |||
9372673bf1 | |||
0eea81b42f | |||
7468303e41 | |||
8745e7d8bc |
39
internal/admin/permissions.go
Normal file
39
internal/admin/permissions.go
Normal 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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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"`
|
||||
|
70
internal/repository/permissions.sql.go
Normal file
70
internal/repository/permissions.sql.go
Normal 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
|
||||
}
|
89
migrations/00013_add_group_role_permission.sql
Normal file
89
migrations/00013_add_group_role_permission.sql
Normal file
@ -0,0 +1,89 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
-- GROUPS
|
||||
CREATE TABLE groups (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT
|
||||
);
|
||||
|
||||
-- ROLES
|
||||
CREATE TABLE roles (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT
|
||||
);
|
||||
|
||||
-- PERMISSIONS
|
||||
CREATE TABLE permissions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
|
||||
name TEXT NOT NULL,
|
||||
scope TEXT NOT NULL,
|
||||
description TEXT,
|
||||
UNIQUE (name, scope)
|
||||
);
|
||||
|
||||
-- USER-GROUPS (many-to-many)
|
||||
CREATE TABLE user_groups (
|
||||
user_id UUID REFERENCES users (id) ON DELETE CASCADE,
|
||||
group_id UUID REFERENCES groups (id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (user_id, group_id)
|
||||
);
|
||||
|
||||
-- GROUP-ROLES (many-to-many)
|
||||
CREATE TABLE group_roles (
|
||||
group_id UUID REFERENCES groups (id) ON DELETE CASCADE,
|
||||
role_id UUID REFERENCES roles (id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (group_id, role_id)
|
||||
);
|
||||
|
||||
-- ROLE-PERMISSIONS (many-to-many)
|
||||
CREATE TABLE role_permissions (
|
||||
role_id UUID REFERENCES roles (id) ON DELETE CASCADE,
|
||||
permission_id UUID REFERENCES permissions (id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (role_id, permission_id)
|
||||
);
|
||||
|
||||
-- USER-ROLES (direct assignment, optional)
|
||||
CREATE TABLE user_roles (
|
||||
user_id UUID REFERENCES users (id) ON DELETE CASCADE,
|
||||
role_id UUID REFERENCES roles (id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (user_id, role_id)
|
||||
);
|
||||
|
||||
-- USER-PERMISSIONS (direct assignment, optional)
|
||||
CREATE TABLE user_permissions (
|
||||
user_id UUID REFERENCES users (id) ON DELETE CASCADE,
|
||||
permission_id UUID REFERENCES permissions (id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (user_id, permission_id)
|
||||
);
|
||||
|
||||
-- GROUP-PERMISSIONS (direct on group, optional)
|
||||
CREATE TABLE group_permissions (
|
||||
group_id UUID REFERENCES groups (id) ON DELETE CASCADE,
|
||||
permission_id UUID REFERENCES permissions (id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (group_id, permission_id)
|
||||
);
|
||||
|
||||
-- +goose StatementEnd
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP TABLE IF EXISTS groups;
|
||||
|
||||
DROP TABLE IF EXISTS roles;
|
||||
|
||||
DROP TABLE IF EXISTS permissions;
|
||||
|
||||
DROP TABLE IF EXISTS user_groups;
|
||||
|
||||
DROP TABLE IF EXISTS group_roles;
|
||||
|
||||
DROP TABLE IF EXISTS role_permissions;
|
||||
|
||||
DROP TABLE IF EXISTS user_roles;
|
||||
|
||||
DROP TABLE IF EXISTS user_permissions;
|
||||
|
||||
DROP TABLE IF EXISTS group_permissions;
|
||||
|
||||
-- +goose StatementEnd
|
32
queries/permissions.sql
Normal file
32
queries/permissions.sql
Normal 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;
|
16
web/src/api/admin/permissions.ts
Normal file
16
web/src/api/admin/permissions.ts
Normal 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;
|
||||
};
|
@ -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">
|
||||
|
@ -78,3 +78,10 @@ export interface DeviceInfo {
|
||||
browser: string;
|
||||
browser_version: string;
|
||||
}
|
||||
|
||||
export interface AppPermission {
|
||||
id: string;
|
||||
name: string;
|
||||
scope: string;
|
||||
description: string;
|
||||
}
|
||||
|
Reference in New Issue
Block a user