feat: Read-only users (#1955)

* Introduce isViewer field

* Update policies

* Make users read-only feature

* Remove not demoting current user validation

* Update tests

* Catch the unhandled promise rejection

* Hide unnecessary ui elements for read-only user

* Update app/scenes/Settings/People.js

Co-authored-by: Tom Moor <tom.moor@gmail.com>

* Remove redundant logic for admin only policies

* Use can logic

* Update snapshot

* Remove lint error

* Update snapshot

* Minor fix

* Update app/menus/UserMenu.js

Co-authored-by: Tom Moor <tom.moor@gmail.com>

* Update server/api/users.js

Co-authored-by: Tom Moor <tom.moor@gmail.com>

* Update app/components/DocumentListItem.js

Co-authored-by: Tom Moor <tom.moor@gmail.com>

* Update app/stores/UsersStore.js

Co-authored-by: Tom Moor <tom.moor@gmail.com>

* Use useCurrentTeam hook in functional component

* Update translation

* Update ternary

* Remove punctuation

* Move the functions to User model

* Update share policy and shareMenu

* Rename makeAdmin to promote

* Create updateCounts function and Rank enum

* Update tests

* Remove enum

* Use async await, remove enum and create computed accessor

* Remove unused variable

* Fix lint issues

* Hide templates

* Create shared/types and use rank type from it

* Delete shared/utils/rank type file

Co-authored-by: Tom Moor <tom.moor@gmail.com>
This commit is contained in:
Saumya Pandey
2021-04-12 08:09:17 +05:30
committed by GitHub
parent cdc7f61fa1
commit bc4fe05147
34 changed files with 508 additions and 189 deletions

View File

@ -7,7 +7,7 @@ import uuid from "uuid";
import { languages } from "../../shared/i18n";
import { ValidationError } from "../errors";
import { sendEmail } from "../mailer";
import { DataTypes, sequelize, encryptedFields } from "../sequelize";
import { DataTypes, sequelize, encryptedFields, Op } from "../sequelize";
import { DEFAULT_AVATAR_HOST } from "../utils/avatars";
import { publicS3Endpoint, uploadToS3FromUrl } from "../utils/s3";
import { Star, Team, Collection, NotificationSetting, ApiKey } from ".";
@ -25,6 +25,11 @@ const User = sequelize.define(
name: DataTypes.STRING,
avatarUrl: { type: DataTypes.STRING, allowNull: true },
isAdmin: DataTypes.BOOLEAN,
isViewer: {
type: DataTypes.BOOLEAN,
defaultValue: false,
allowNull: false,
},
service: { type: DataTypes.STRING, allowNull: true },
serviceId: { type: DataTypes.STRING, allowNull: true, unique: true },
jwtSecret: encryptedFields().vault("jwtSecret"),
@ -277,6 +282,7 @@ User.getCounts = async function (teamId: string) {
SELECT
COUNT(CASE WHEN "suspendedAt" IS NOT NULL THEN 1 END) as "suspendedCount",
COUNT(CASE WHEN "isAdmin" = true THEN 1 END) as "adminCount",
COUNT(CASE WHEN "isViewer" = true THEN 1 END) as "viewerCount",
COUNT(CASE WHEN "lastActiveAt" IS NULL THEN 1 END) as "invitedCount",
COUNT(CASE WHEN "suspendedAt" IS NULL AND "lastActiveAt" IS NOT NULL THEN 1 END) as "activeCount",
COUNT(*) as count
@ -295,10 +301,48 @@ User.getCounts = async function (teamId: string) {
return {
active: parseInt(counts.activeCount),
admins: parseInt(counts.adminCount),
viewers: parseInt(counts.viewerCount),
all: parseInt(counts.count),
invited: parseInt(counts.invitedCount),
suspended: parseInt(counts.suspendedCount),
};
};
User.prototype.demote = async function (
teamId: string,
to: "Member" | "Viewer"
) {
const res = await User.findAndCountAll({
where: {
teamId,
isAdmin: true,
id: {
[Op.ne]: this.id,
},
},
limit: 1,
});
if (res.count >= 1) {
if (to === "Member") {
return this.update({ isAdmin: false, isViewer: false });
} else if (to === "Viewer") {
return this.update({ isAdmin: false, isViewer: true });
}
} else {
throw new ValidationError("At least one admin is required");
}
};
User.prototype.promote = async function () {
return this.update({ isAdmin: true, isViewer: false });
};
User.prototype.activate = async function () {
return this.update({
suspendedById: null,
suspendedAt: null,
});
};
export default User;