# Conflicts:
#	public/iso-3166-2.json
This commit is contained in:
Mike Cao 2023-04-13 22:30:22 -07:00
commit e286994397
17 changed files with 47 additions and 37 deletions

View File

@ -10,6 +10,7 @@ export const labels = defineMessages({
leave: { id: 'label.leave', defaultMessage: 'Leave' },
users: { id: 'label.users', defaultMessage: 'Users' },
createUser: { id: 'label.create-user', defaultMessage: 'Create user' },
deleteUser: { id: 'label.delete-users', defaultMessage: 'Delete user' },
username: { id: 'label.username', defaultMessage: 'Username' },
password: { id: 'label.password', defaultMessage: 'Password' },
role: { id: 'label.role', defaultMessage: 'Role' },

View File

@ -16,7 +16,9 @@ import useMessages from 'hooks/useMessages';
export default function UserEditForm({ userId, data, onSave }) {
const { formatMessage, labels, messages } = useMessages();
const { post, useMutation } = useApi();
const { mutate, error } = useMutation(({ username }) => post(`/users/${userId}`, { username }));
const { mutate, error } = useMutation(({ username, password, role }) =>
post(`/users/${userId}`, { username, password, role }),
);
const handleSubmit = async data => {
mutate(data, {

View File

@ -52,7 +52,7 @@ export default function UsersTable({ data = [], onDelete }) {
</Icon>
<Text>{formatMessage(labels.delete)}</Text>
</Button>
<Modal>
<Modal title={formatMessage(labels.deleteUser)}>
{close => (
<UserDeleteForm
userId={row.id}

View File

@ -13,7 +13,7 @@ import useMessages from 'hooks/useMessages';
const CONFIRM_VALUE = 'DELETE';
export default function WebsiteDeleteForm({ websiteId, onSave, onClose }) {
const { formatMessage, labels, messages } = useMessages();
const { formatMessage, labels, messages, FormattedMessage } = useMessages();
const { del, useMutation } = useApi();
const { mutate, error } = useMutation(data => del(`/websites/${websiteId}`, data));
@ -28,7 +28,12 @@ export default function WebsiteDeleteForm({ websiteId, onSave, onClose }) {
return (
<Form onSubmit={handleSubmit} error={error}>
<p>{formatMessage(messages.deleteWebsite, { confirmation: CONFIRM_VALUE })}</p>
<p>
<FormattedMessage
{...messages.deleteWebsite}
values={{ confirmation: <b>{CONFIRM_VALUE}</b> }}
/>
</p>
<FormRow label={formatMessage(labels.confirm)}>
<FormInput name="confirmation" rules={{ validate: value => value === CONFIRM_VALUE }}>
<TextField autoComplete="off" />

View File

@ -116,7 +116,7 @@
"message.confirm-delete": "Are you sure you want to delete {target}?",
"message.confirm-leave": "Are you sure you want to leave {target}?",
"message.confirm-reset": "Are you sure you want to reset {target}'s statistics?",
"message.delete-website": "Delete website",
"message.delete-website": "To delete this website, type {confirmation} in the box below to confirm.",
"message.delete-website-warning": "All associated data will be deleted as well.",
"message.error": "Something went wrong.",
"message.event-log": "{event} on {url}",

View File

@ -95,17 +95,11 @@ export async function canViewWebsite({ user, shareToken }: Auth, websiteId: stri
return false;
}
export async function canCreateWebsite({ user }: Auth, teamId?: string) {
export async function canCreateWebsite({ user }: Auth) {
if (user.isAdmin) {
return true;
}
if (teamId) {
const teamUser = await getTeamUser(teamId, user.id);
return hasPermission(teamUser?.role, PERMISSIONS.websiteCreate);
}
return hasPermission(user.role, PERMISSIONS.websiteCreate);
}

View File

@ -43,6 +43,7 @@ export interface User {
id: string;
username: string;
password?: string;
role: string;
createdAt?: Date;
}

View File

@ -45,7 +45,10 @@ export default async (
const token = createSecureToken({ userId: user.id }, secret());
return ok(res, { token, user });
return ok(res, {
token,
user: { id: user.id, username: user.username, createdAt: user.createdAt },
});
}
return unauthorized(res, 'message.incorrect-username-password');

View File

@ -23,9 +23,9 @@ export default async (
return unauthorized(res);
}
const websites = await deleteTeamWebsite(teamId, websiteId);
await deleteTeamWebsite(teamId, websiteId);
return ok(res, websites);
return ok(res);
}
return methodNotAllowed(res);

View File

@ -10,7 +10,6 @@ export interface TeamWebsiteRequestQuery {
}
export interface TeamWebsiteRequestBody {
teamWebsiteId?: string;
websiteIds?: string[];
}
@ -21,9 +20,6 @@ export default async (
await useAuth(req, res);
const { id: teamId } = req.query;
const {
user: { id: userId },
} = req.auth;
if (req.method === 'GET') {
if (!(await canViewTeam(req.auth, teamId))) {

View File

@ -1,4 +1,4 @@
import { NextApiRequestQueryBody, User } from 'lib/types';
import { NextApiRequestQueryBody, Roles, User } from 'lib/types';
import { canDeleteUser, canUpdateUser, canViewUser } from 'lib/auth';
import { useAuth } from 'lib/middleware';
import { NextApiResponse } from 'next';
@ -12,6 +12,7 @@ export interface UserRequestQuery {
export interface UserRequestBody {
username: string;
password: string;
role: Roles;
}
export default async (
@ -40,17 +41,20 @@ export default async (
return unauthorized(res);
}
const { username, password } = req.body;
const { username, password, role } = req.body;
const user = await getUser({ id });
const data: any = {};
// Only admin can change these fields
if (password && isAdmin) {
if (password) {
data.password = hashPassword(password);
}
if (role && isAdmin) {
data.role = role;
}
// Only admin can change these fields
if (username && isAdmin) {
data.username = username;

View File

@ -41,15 +41,17 @@ export default async (
const { name, domain, shareId } = req.body;
let website;
try {
await updateWebsite(websiteId, { name, domain, shareId });
website = await updateWebsite(websiteId, { name, domain, shareId });
} catch (e: any) {
if (e.message.includes('Unique constraint') && e.message.includes('share_id')) {
return serverError(res, 'That share ID is already taken.');
}
}
return ok(res);
return ok(res, website);
}
if (req.method === 'DELETE') {

View File

@ -10,7 +10,6 @@ export interface WebsitesRequestBody {
name: string;
domain: string;
shareId: string;
teamId?: string;
}
export default async (
@ -31,9 +30,9 @@ export default async (
}
if (req.method === 'POST') {
const { name, domain, shareId, teamId } = req.body;
const { name, domain, shareId } = req.body;
if (!(await canCreateWebsite(req.auth, teamId))) {
if (!(await canCreateWebsite(req.auth))) {
return unauthorized(res);
}
@ -44,11 +43,7 @@ export default async (
shareId,
};
if (teamId) {
data.teamId = teamId;
} else {
data.userId = userId;
}
data.userId = userId;
const website = await createWebsite(data);

View File

@ -10,7 +10,7 @@ export default function LogoutPage({ disabled }) {
useEffect(() => {
async function logout() {
await post('/logout');
await post('/auth/logout');
}
if (!disabled) {

View File

@ -758,7 +758,15 @@
"message.delete-website": [
{
"type": 0,
"value": "Delete website"
"value": "To delete this website, type "
},
{
"type": 1,
"value": "confirmation"
},
{
"type": 0,
"value": " in the box below to confirm."
}
],
"message.delete-website-warning": [

View File

@ -17,6 +17,7 @@ export async function getUser(
username: true,
password: includePassword,
role: true,
createdAt: true,
},
});
}

View File

@ -13,7 +13,6 @@ export async function getEventMetrics(
endDate: Date;
timezone: string;
unit: string;
column: string;
filters: {
url: string;
eventName: string;
@ -40,7 +39,6 @@ async function relationalQuery(
endDate: Date;
timezone: string;
unit: string;
column: string;
filters: {
url: string;
eventName: string;