Update user APIs and queries to embed role + list permissions.

This commit is contained in:
Kailash Nadh 2024-06-24 00:08:37 +05:30
parent f632dfbce1
commit d1184de18d
7 changed files with 108 additions and 66 deletions

View file

@ -54,10 +54,10 @@ async function initConfig(app) {
// one of campaigns:get, campaigns:manage etc. are present.
if (perm.endsWith('*')) {
const group = `${perm.split(':')[0]}:`;
return profile.permissions.some((p) => p.startsWith(group));
return profile.role.permissions.some((p) => p.startsWith(group));
}
return profile.permissions.includes(perm);
return profile.role.permissions.includes(perm);
};
// Set the page title after i18n has loaded.

View file

@ -198,7 +198,7 @@ export default Vue.extend({
},
mounted() {
this.form = { ...this.form, ...this.$props.data };
this.form = { ...this.form, ...this.$props.data, roleId: this.$props.data.role.id };
this.$api.getRoles();

View file

@ -52,8 +52,8 @@
<b-table-column v-slot="props" field="status" :label="$tc('users.role')" header-class="cy-status" sortable
:td-attrs="$utils.tdID">
<b-tag :class="props.row.roleId === 1 ? 'enabled' : ''">
{{ props.row.roleName }}
<b-tag :class="props.row.role.id === 1 ? 'enabled' : ''">
{{ props.row.role.name }}
</b-tag>
<b-tag v-if="props.row.type === 'api'" class="primary">
<b-icon icon="code" />

View file

@ -2,6 +2,7 @@ package core
import (
"database/sql"
"encoding/json"
"net/http"
"github.com/knadh/listmonk/internal/utils"
@ -13,47 +14,19 @@ import (
// GetUsers retrieves all users.
func (c *Core) GetUsers() ([]models.User, error) {
out := []models.User{}
if err := c.q.GetUsers.Select(&out, 0); err != nil {
return nil, echo.NewHTTPError(http.StatusInternalServerError,
c.i18n.Ts("globals.messages.errorFetching", "name", "{globals.terms.users}", "error", pqErrMsg(err)))
}
for n, u := range out {
if u.Password.String != "" {
u.HasPassword = true
u.PasswordLogin = true
// u.Password = null.String{}
out[n] = u
}
if u.Type == models.UserTypeAPI {
out[n].Email = null.String{}
}
}
return out, nil
out, err := c.getUsers(0, "", "")
return out, err
}
// GetUser retrieves a specific user based on any one given identifier.
func (c *Core) GetUser(id int, username, email string) (models.User, error) {
var out models.User
if err := c.q.GetUser.Get(&out, id, username, email); err != nil {
return out, echo.NewHTTPError(http.StatusInternalServerError,
out, err := c.getUsers(id, username, email)
if err != nil {
return models.User{}, echo.NewHTTPError(http.StatusInternalServerError,
c.i18n.Ts("globals.messages.errorFetching", "name", "{globals.terms.users}", "error", pqErrMsg(err)))
}
if out.Password.String != "" {
out.HasPassword = true
out.PasswordLogin = true
}
out.PermissionsMap = make(map[string]struct{})
for _, p := range out.Permissions {
out.PermissionsMap[p] = struct{}{}
}
return out, nil
return out[0], nil
}
// CreateUser creates a new user.
@ -146,9 +119,56 @@ func (c *Core) LoginUser(username, password string) (models.User, error) {
c.i18n.Ts("globals.messages.errorFetching", "name", "{globals.terms.users}", "error", pqErrMsg(err)))
}
out.PermissionsMap = make(map[string]struct{})
for _, p := range out.Permissions {
out.PermissionsMap[p] = struct{}{}
return out, nil
}
func (c *Core) getUsers(id int, username, email string) ([]models.User, error) {
out := []models.User{}
if err := c.q.GetUsers.Select(&out, id, username, email); err != nil {
return nil, echo.NewHTTPError(http.StatusInternalServerError,
c.i18n.Ts("globals.messages.errorFetching", "name", "{globals.terms.users}", "error", pqErrMsg(err)))
}
for n, u := range out {
if u.Password.String != "" {
u.HasPassword = true
u.PasswordLogin = true
}
if u.Type == models.UserTypeAPI {
u.Email = null.String{}
}
// Unmarshall the raw list perms map.
var listPerms []models.ListPermission
if u.ListsPermsRaw != nil {
if err := json.Unmarshal(u.ListsPermsRaw, &listPerms); err != nil {
c.log.Printf("error unmarshalling list permissions for role %d: %v", u.ID, err)
}
}
u.Role.ID = u.RoleID
u.Role.Name = u.RoleName
u.Role.Permissions = u.RolePerms
u.Role.Lists = listPerms
u.RoleID = 0
// Prepare lookup maps.
u.PermissionsMap = make(map[string]struct{})
for _, p := range u.RolePerms {
u.PermissionsMap[p] = struct{}{}
}
u.ListPermissionsMap = make(map[int]map[string]struct{})
for _, p := range listPerms {
u.ListPermissionsMap[p.ID] = make(map[string]struct{})
for _, perm := range p.Permissions {
u.ListPermissionsMap[p.ID][perm] = struct{}{}
}
}
out[n] = u
}
return out, nil

View file

@ -151,21 +151,31 @@ type User struct {
Username string `db:"username" json:"username"`
// For API users, this is the plaintext API token.
Password null.String `db:"password" json:"password,omitempty"`
PasswordLogin bool `db:"password_login" json:"password_login"`
Email null.String `db:"email" json:"email"`
Name string `db:"name" json:"name"`
Type string `db:"type" json:"type"`
RoleID int `db:"role_id" json:"role_id"`
RoleName string `db:"role_name" json:"role_name"`
Permissions pq.StringArray `db:"permissions" json:"permissions"`
Status string `db:"status" json:"status"`
Avatar null.String `db:"-" json:"avatar"`
Password null.String `db:"password" json:"password,omitempty"`
PasswordLogin bool `db:"password_login" json:"password_login"`
Email null.String `db:"email" json:"email"`
Name string `db:"name" json:"name"`
Type string `db:"type" json:"type"`
Status string `db:"status" json:"status"`
Avatar null.String `db:"-" json:"avatar"`
LoggedInAt null.Time `db:"loggedin_at" json:"loggedin_at"`
PermissionsMap map[string]struct{} `db:"-" json:"-"`
LoggedInAt null.Time `db:"loggedin_at" json:"loggedin_at"`
// Filled post-retrieval.
Role struct {
ID int `db:"-" json:"id"`
Name string `db:"-" json:"name"`
Permissions []string `db:"-" json:"permissions"`
Lists []ListPermission `db:"-" json:"lists"`
} `db:"-" json:"role"`
HasPassword bool `db:"-" json:"-"`
RoleID int `db:"role_id" json:"role_id,omitempty"`
RoleName string `db:"role_name" json:"-"`
RolePerms pq.StringArray `db:"role_permissions" json:"-"`
ListsPermsRaw json.RawMessage `db:"list_permissions" json:"-"`
PermissionsMap map[string]struct{} `db:"-" json:"-"`
ListPermissionsMap map[int]map[string]struct{} `db:"-" json:"-"`
HasPassword bool `db:"-" json:"-"`
}
type ListPermission struct {

View file

@ -113,7 +113,6 @@ type Queries struct {
UpdateUserProfile *sqlx.Stmt `query:"update-user-profile"`
DeleteUsers *sqlx.Stmt `query:"delete-users"`
GetUsers *sqlx.Stmt `query:"get-users"`
GetUser *sqlx.Stmt `query:"get-user"`
GetAPITokens *sqlx.Stmt `query:"get-api-tokens"`
LoginUser *sqlx.Stmt `query:"login-user"`

View file

@ -1061,22 +1061,35 @@ WITH u AS (
)
DELETE FROM users WHERE id = ALL($1) AND (SELECT num FROM u) > 0;
-- name: get-user
SELECT users.*, r.name as role_name, r.permissions FROM users
LEFT JOIN user_roles r ON (r.id = users.role_id)
-- name: get-users
WITH u AS (
SELECT * FROM users
WHERE
(
CASE
WHEN $1::INT != 0 THEN users.id = $1
WHEN $2::TEXT != '' THEN username = $2
WHEN $3::TEXT != '' THEN email = $3
ELSE TRUE
END
);
)
),
role AS (
SELECT id, name, permissions FROM user_roles WHERE id IN (SELECT role_id FROM users)
),
listPerms AS (
SELECT ur.parent_id, JSONB_AGG(JSONB_BUILD_OBJECT('id', ur.list_id, 'name', lists.name, 'permissions', ur.permissions)) AS listPerms
FROM user_roles ur
LEFT JOIN lists ON(lists.id = ur.list_id)
WHERE ur.parent_id IS NOT NULL GROUP BY ur.parent_id
),
roleInfo AS (
SELECT role.id AS role_id, role.name AS role_name, role.permissions AS role_permissions, COALESCE(l.listPerms, '[]'::JSONB) AS "list_permissions"
FROM role
LEFT JOIN listPerms l ON role.id = l.parent_id
)
SELECT u.*, ri.* FROM u JOIN roleInfo ri ON u.role_id = ri.role_id;
-- name: get-users
SELECT users.*, r.name as role_name, r.permissions FROM users
LEFT JOIN user_roles r ON (r.id = users.role_id)
WHERE $1=0 OR users.id=$1 ORDER BY created_at;
-- name: get-api-tokens
SELECT username, password FROM users WHERE status='enabled' AND type='api';
@ -1098,14 +1111,14 @@ UPDATE users SET name=$2, email=$3,
WITH mainroles AS (
SELECT ur.* FROM user_roles ur WHERE ur.parent_id IS NULL
),
listroles AS (
listPerms AS (
SELECT ur.parent_id, JSONB_AGG(JSONB_BUILD_OBJECT('id', ur.list_id, 'name', lists.name, 'permissions', ur.permissions)) AS listPerms
FROM user_roles ur
LEFT JOIN lists ON(lists.id = ur.list_id)
WHERE ur.parent_id IS NOT NULL GROUP BY ur.parent_id
)
SELECT p.*, COALESCE(l.listPerms, '[]'::JSONB) AS "list_permissions" FROM mainroles p
LEFT JOIN listroles l ON p.id = l.parent_id;
LEFT JOIN listPerms l ON p.id = l.parent_id;
-- name: create-role
INSERT INTO user_roles (name, permissions, created_at, updated_at) VALUES($1, $2, NOW(), NOW()) RETURNING *;