Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
Brian Cao 2022-12-01 22:06:39 -08:00
commit b40630f9d4
27 changed files with 331 additions and 482 deletions

View File

@ -287,10 +287,10 @@ INSERT INTO "user" (user_id, username, password) VALUES ('41e2b680-648e-4b09-bcd
-- Add Roles
INSERT INTO "role" ("role_id", "name", "description") VALUES (gen_random_uuid(), 'Admin', 'System Admin.');
INSERT INTO "role" ("role_id", "name", "description") (gen_random_uuid(), 'Member', 'Create and maintain websites.');
INSERT INTO "role" ("role_id", "name", "description") (gen_random_uuid(), 'Team Owner', 'Create and maintain the team, memberships, websites, and responsible for billing.');
INSERT INTO "role" ("role_id", "name", "description") (gen_random_uuid(), 'Team Member', 'Create and maintain websites.');
INSERT INTO "role" ("role_id", "name", "description") (gen_random_uuid(), 'Team Guest', 'View Websites.');
INSERT INTO "role" ("role_id", "name", "description") VALUES (gen_random_uuid(), 'Member', 'Create and maintain websites.');
INSERT INTO "role" ("role_id", "name", "description") VALUES (gen_random_uuid(), 'Team Owner', 'Create and maintain the team, memberships, websites, and responsible for billing.');
INSERT INTO "role" ("role_id", "name", "description") VALUES (gen_random_uuid(), 'Team Member', 'Create and maintain websites.');
INSERT INTO "role" ("role_id", "name", "description") VALUES (gen_random_uuid(), 'Team Guest', 'View Websites.');
-- Add Permissions
INSERT INTO "permission" ("permission_id", "name", "description") VALUES (gen_random_uuid(), 'admin', 'System Admin');

View File

@ -14,11 +14,9 @@ model User {
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
isDeleted Boolean @default(false) @map("is_deleted")
groupRole GroupRole[]
groupUser GroupUser[]
userRole UserRole[]
teamUser TeamUser[]
Website Website[]
userRole UserRole[]
teamUser TeamUser[]
Website Website[]
@@map("user")
}
@ -78,96 +76,16 @@ model WebsiteEvent {
@@map("website_event")
}
model Group {
id String @id() @unique() @map("group_id") @db.Uuid
name String @unique() @db.VarChar(255)
description String? @db.VarChar(255)
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
isDeleted Boolean @default(false) @map("is_deleted")
groupRoles GroupRole[]
groupUsers GroupUser[]
@@map("group")
}
model GroupRole {
id String @id() @unique() @map("group_role_id") @db.Uuid
groupId String @map("group_id") @db.Uuid
roleId String @map("role_id") @db.Uuid
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
isDeleted Boolean @default(false) @map("is_deleted")
group Group @relation(fields: [groupId], references: [id])
role Role @relation(fields: [roleId], references: [id])
user User? @relation(fields: [userId], references: [id])
userId String? @db.Uuid
@@map("group_role")
}
model GroupUser {
id String @id() @unique() @map("group_user_id") @db.Uuid
groupId String @map("group_id") @db.Uuid
userId String @map("user_id") @db.Uuid
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
isDeleted Boolean @default(false) @map("is_deleted")
group Group @relation(fields: [groupId], references: [id])
user User @relation(fields: [userId], references: [id])
@@map("group_user")
}
model Permission {
id String @id() @unique() @map("permission_id") @db.Uuid
name String @unique() @db.VarChar(255)
description String? @db.VarChar(255)
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
RolePermission RolePermission[]
@@map("permission")
}
model Role {
id String @id() @unique() @map("role_id") @db.Uuid
name String @unique() @db.VarChar(255)
description String? @db.VarChar(255)
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
groupRoles GroupRole[]
userRoles UserRole[]
RolePermission RolePermission[]
TeamUser TeamUser[]
@@map("role")
}
model RolePermission {
id String @id() @unique() @map("role_permission_id") @db.Uuid
roleId String @map("role_id") @db.Uuid
permissionId String @map("permission_id") @db.Uuid
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
isDeleted Boolean @default(false) @map("is_deleted")
role Role @relation(fields: [roleId], references: [id])
permission Permission @relation(fields: [permissionId], references: [id])
@@unique([roleId, permissionId])
@@map("role_permission")
}
model UserRole {
id String @id() @unique() @map("user_role_id") @db.Uuid
roleId String @map("role_id") @db.Uuid
role String @map("role") @db.VarChar(100)
userId String @map("user_id") @db.Uuid
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
isDeleted Boolean @default(false) @map("is_deleted")
role Role @relation(fields: [roleId], references: [id])
user User @relation(fields: [userId], references: [id])
@@unique([roleId, userId])
@@unique([role, userId])
@@map("user_role")
}
@ -187,13 +105,12 @@ model TeamUser {
id String @id() @unique() @map("team_user_id") @db.Uuid
teamId String @map("team_id") @db.Uuid
userId String @map("user_id") @db.Uuid
roleId String @map("role_id") @db.Uuid
role String @map("role") @db.VarChar(100)
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
isDeleted Boolean @default(false) @map("is_deleted")
team Team @relation(fields: [teamId], references: [id])
user User @relation(fields: [userId], references: [id])
role Role @relation(fields: [roleId], references: [id])
@@map("team_user")
}

View File

@ -1,10 +1,10 @@
import { UserRole } from '@prisma/client';
import debug from 'debug';
import { NextApiRequestAuth } from 'interface/api/nextApi';
import cache from 'lib/cache';
import { SHARE_TOKEN_HEADER, UmamiApi } from 'lib/constants';
import { secret } from 'lib/crypto';
import { parseSecureToken, parseToken } from 'next-basics';
import { getPermissionsByUserId, getTeamUser, getUser } from 'queries';
import { getTeamUser, getUserRoles } from 'queries';
const log = debug('umami:auth');
@ -34,13 +34,6 @@ export function parseShareToken(req) {
}
}
export function hasPermission(
value: UmamiApi.Role | UmamiApi.Permission,
permissions: UmamiApi.Role[] | UmamiApi.Permission[],
) {
return permissions.some(a => a === value);
}
export function isValidToken(token, validation) {
try {
if (typeof validation === 'object') {
@ -56,71 +49,6 @@ export function isValidToken(token, validation) {
return false;
}
export async function allowQuery(
req: NextApiRequestAuth,
type: UmamiApi.AuthType,
typeId?: string,
) {
const { id } = req.query as { id: string };
const { user, shareToken } = req.auth;
if (shareToken) {
return isValidToken(shareToken, { id });
}
if (user?.id) {
if (type === UmamiApi.AuthType.Website) {
const website = await cache.fetchWebsite(typeId ?? id);
if (website && website.userId === user.id) {
return true;
}
if (website.teamId) {
const teamUser = getTeamUser({ userId: user.id, teamId: website.teamId, isDeleted: false });
return teamUser;
}
return false;
} else if (type === UmamiApi.AuthType.User) {
const user = await getUser({ id });
return user && user.id === id;
} else if (type === UmamiApi.AuthType.Team) {
const teamUser = await getTeamUser({
userId: user.id,
teamId: typeId ?? id,
});
return teamUser;
} else if (type === UmamiApi.AuthType.TeamOwner) {
const teamUser = await getTeamUser({
userId: user.id,
teamId: typeId ?? id,
});
return (
teamUser &&
(teamUser.roleId === UmamiApi.Role.TeamOwner || teamUser.roleId === UmamiApi.Role.Admin)
);
}
}
return false;
}
export async function checkPermission(req: NextApiRequestAuth, type: UmamiApi.Permission) {
const {
user: { id: userId },
} = req.auth;
const userRole = await getPermissionsByUserId(userId, type);
return userRole.length > 0;
}
export async function canViewWebsite(userId: string, websiteId: string) {
const website = await cache.fetchWebsite(websiteId);
@ -128,7 +56,13 @@ export async function canViewWebsite(userId: string, websiteId: string) {
return userId === website.userId;
}
return false;
if (website.teamId) {
const teamUser = await getTeamUser({ userId, teamId: website.teamId });
checkPermission(UmamiApi.Permission.websiteUpdate, teamUser.role as keyof UmamiApi.Roles);
}
return checkAdmin(userId);
}
export async function canUpdateWebsite(userId: string, websiteId: string) {
@ -138,5 +72,100 @@ export async function canUpdateWebsite(userId: string, websiteId: string) {
return userId === website.userId;
}
return false;
if (website.teamId) {
const teamUser = await getTeamUser({ userId, teamId: website.teamId });
checkPermission(UmamiApi.Permission.websiteUpdate, teamUser.role as keyof UmamiApi.Roles);
}
return checkAdmin(userId);
}
export async function canDeleteWebsite(userId: string, websiteId: string) {
const website = await cache.fetchWebsite(websiteId);
if (website.userId) {
return userId === website.userId;
}
if (website.teamId) {
const teamUser = await getTeamUser({ userId, teamId: website.teamId });
if (checkPermission(UmamiApi.Permission.websiteDelete, teamUser.role as keyof UmamiApi.Roles)) {
return true;
}
}
return checkAdmin(userId);
}
// To-do: Implement when payments are setup.
export async function canCreateTeam(userId: string) {
return !!userId;
}
// To-do: Implement when payments are setup.
export async function canViewTeam(userId: string, teamId) {
const teamUser = await getTeamUser({ userId, teamId });
return !!teamUser;
}
export async function canUpdateTeam(userId: string, teamId: string) {
const teamUser = await getTeamUser({ userId, teamId });
if (checkPermission(UmamiApi.Permission.teamUpdate, teamUser.role as keyof UmamiApi.Roles)) {
return true;
}
}
export async function canDeleteTeam(userId: string, teamId: string) {
const teamUser = await getTeamUser({ userId, teamId });
if (checkPermission(UmamiApi.Permission.teamDelete, teamUser.role as keyof UmamiApi.Roles)) {
return true;
}
}
export async function canCreateUser(userId: string) {
return checkAdmin(userId);
}
export async function canViewUser(userId: string, viewedUserId: string) {
if (userId === viewedUserId) {
return true;
}
return checkAdmin(userId);
}
export async function canViewUsers(userId: string) {
return checkAdmin(userId);
}
export async function canUpdateUser(userId: string, viewedUserId: string) {
if (userId === viewedUserId) {
return true;
}
return checkAdmin(userId);
}
export async function canUpdateUserRole(userId: string) {
return checkAdmin(userId);
}
export async function canDeleteUser(userId: string) {
return checkAdmin(userId);
}
export async function checkPermission(permission: UmamiApi.Permission, role: keyof UmamiApi.Roles) {
return UmamiApi.Roles[role].permissions.some(a => a === permission);
}
export async function checkAdmin(userId: string, userRoles?: UserRole[]) {
if (!userRoles) {
userRoles = await getUserRoles({ userId });
}
return userRoles.some(a => a.role === UmamiApi.Role.Admin);
}

View File

@ -9,52 +9,56 @@ export namespace UmamiApi {
Website,
User,
Team,
TeamOwner,
}
export enum Permission {
Admin = 'Admin',
WebsiteCreate = 'website:create',
WebsiteRead = 'website:read',
WebsiteUpdate = 'website:update',
WebsiteReset = 'website:reset',
WebsiteDelete = 'website:delete',
TeamCreate = 'team:create',
TeamUpdate = 'team:update',
TeamDelete = 'team:delete',
TeamAddUser = 'team:add-user',
TeamRemoveUser = 'team:remove-user',
all = 'all',
websiteCreate = 'website:create',
websiteUpdate = 'website:update',
websiteDelete = 'website:delete',
teamCreate = 'team:create',
teamUpdate = 'team:update',
teamDelete = 'team:delete',
}
export enum Role {
Admin = 'Admin',
Member = 'Member',
TeamOwner = 'Team Owner',
TeamMember = 'Team Member',
TeamGuest = 'Team Guest,',
Admin = 'admin',
User = 'user',
TeamOwner = 'team-owner',
TeamMember = 'team-member',
TeamGuest = 'team-guest',
}
export const Roles = {
admin: { name: Role.Admin, permissions: [Permission.all] },
member: {
name: Role.User,
permissions: [
Permission.websiteCreate,
Permission.websiteUpdate,
Permission.websiteDelete,
Permission.teamCreate,
],
},
teamOwner: {
name: Role.TeamOwner,
permissions: [
Permission.teamUpdate,
Permission.teamDelete,
Permission.websiteCreate,
Permission.websiteUpdate,
Permission.websiteDelete,
],
},
teamMember: {
name: Role.TeamMember,
permissions: [Permission.websiteCreate, Permission.websiteUpdate, Permission.websiteDelete],
},
teamGuest: { name: Role.TeamGuest, permissions: [] },
};
export type Roles = typeof Roles;
}
export const PERMISSIONS = {
all: 'all',
websiteCreate: 'website:create',
websiteUpdate: 'website:update',
websiteDelete: 'website:delete',
teamCreate: 'team:create',
teamUpdate: 'team:update',
teamDelete: 'team:delete',
};
export const ROLES = {
admin: { name: 'admin', permissions: [PERMISSIONS.all] },
teamOwner: { name: 'team-owner', permissions: [PERMISSIONS.teamUpdate, PERMISSIONS.teamDelete] },
teamMember: {
name: 'team-member',
permissions: [PERMISSIONS.websiteCreate, PERMISSIONS.websiteUpdate, PERMISSIONS.websiteDelete],
},
teamGuest: { name: 'team-guest' },
};
export const CURRENT_VERSION = process.env.currentVersion;
export const AUTH_TOKEN = 'umami.auth';
export const LOCALE_CONFIG = 'umami.locale';

View File

@ -1,7 +1,6 @@
import { Team } from '@prisma/client';
import { NextApiRequestQueryBody } from 'interface/api/nextApi';
import { allowQuery } from 'lib/auth';
import { UmamiApi } from 'lib/constants';
import { canDeleteTeam, canUpdateTeam, canViewTeam } from 'lib/auth';
import { useAuth } from 'lib/middleware';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
@ -21,12 +20,16 @@ export default async (
) => {
await useAuth(req, res);
const {
user: { id: userId },
} = req.auth;
const { id: teamId } = req.query;
if (req.method === 'GET') {
if (!(await allowQuery(req, UmamiApi.AuthType.Team))) {
if (await canViewTeam(userId, teamId)) {
return unauthorized(res);
}
const user = await getTeam({ id: teamId });
return ok(res, user);
@ -35,7 +38,7 @@ export default async (
if (req.method === 'POST') {
const { name } = req.body;
if (!(await allowQuery(req, UmamiApi.AuthType.TeamOwner))) {
if (await canUpdateTeam(userId, teamId)) {
return unauthorized(res, 'You must be the owner of this team.');
}
@ -45,7 +48,7 @@ export default async (
}
if (req.method === 'DELETE') {
if (!(await allowQuery(req, UmamiApi.AuthType.TeamOwner))) {
if (await canDeleteTeam(userId, teamId)) {
return unauthorized(res, 'You must be the owner of this team.');
}

View File

@ -1,6 +1,5 @@
import { NextApiRequestQueryBody } from 'interface/api/nextApi';
import { allowQuery } from 'lib/auth';
import { UmamiApi } from 'lib/constants';
import { canUpdateTeam, canViewTeam } from 'lib/auth';
import { useAuth } from 'lib/middleware';
import { NextApiResponse } from 'next';
import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics';
@ -22,10 +21,13 @@ export default async (
) => {
await useAuth(req, res);
const {
user: { id: userId },
} = req.auth;
const { id: teamId } = req.query;
if (req.method === 'GET') {
if (!(await allowQuery(req, UmamiApi.AuthType.Team))) {
if (await canViewTeam(userId, teamId)) {
return unauthorized(res);
}
@ -35,7 +37,7 @@ export default async (
}
if (req.method === 'POST') {
if (!(await allowQuery(req, UmamiApi.AuthType.TeamOwner))) {
if (await canUpdateTeam(userId, teamId)) {
return unauthorized(res, 'You must be the owner of this team.');
}
@ -54,7 +56,7 @@ export default async (
}
if (req.method === 'DELETE') {
if (!(await allowQuery(req, UmamiApi.AuthType.TeamOwner))) {
if (await canUpdateTeam(userId, teamId)) {
return unauthorized(res, 'You must be the owner of this team.');
}
const { team_user_id } = req.body;

View File

@ -1,6 +1,5 @@
import { NextApiRequestQueryBody } from 'interface/api/nextApi';
import { allowQuery } from 'lib/auth';
import { UmamiApi } from 'lib/constants';
import { canViewTeam } from 'lib/auth';
import { useAuth } from 'lib/middleware';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
@ -21,10 +20,13 @@ export default async (
) => {
await useAuth(req, res);
const {
user: { id: userId },
} = req.auth;
const { id: teamId } = req.query;
if (req.method === 'GET') {
if (!(await allowQuery(req, UmamiApi.AuthType.Team))) {
if (await canViewTeam(userId, teamId)) {
return unauthorized(res);
}

View File

@ -1,9 +1,10 @@
import { Team } from '@prisma/client';
import { NextApiRequestQueryBody } from 'interface/api/nextApi';
import { canCreateTeam } from 'lib/auth';
import { uuid } from 'lib/crypto';
import { useAuth } from 'lib/middleware';
import { NextApiResponse } from 'next';
import { badRequest, methodNotAllowed, ok } from 'next-basics';
import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics';
import { createTeam, getTeam, getTeamsByUserId } from 'queries';
export interface TeamsRequestBody {
name: string;
@ -17,16 +18,20 @@ export default async (
await useAuth(req, res);
const {
user: { id },
user: { id: userId },
} = req.auth;
if (req.method === 'GET') {
const teams = await getTeamsByUserId(id);
const teams = await getTeamsByUserId(userId);
return ok(res, teams);
}
if (req.method === 'POST') {
if (await canCreateTeam(userId)) {
return unauthorized(res);
}
const { name } = req.body;
const team = await getTeam({ name });
@ -36,7 +41,7 @@ export default async (
}
const created = await createTeam({
id: id || uuid(),
id: uuid(),
name,
});

View File

@ -1,6 +1,5 @@
import { NextApiRequestQueryBody } from 'interface/api/nextApi';
import { checkPermission } from 'lib/auth';
import { UmamiApi } from 'lib/constants';
import { canDeleteUser, canUpdateUser, canViewUser, checkAdmin } from 'lib/auth';
import { useAuth } from 'lib/middleware';
import { NextApiResponse } from 'next';
import { badRequest, hashPassword, methodNotAllowed, ok, unauthorized } from 'next-basics';
@ -27,7 +26,7 @@ export default async (
const { id } = req.query;
if (req.method === 'GET') {
if (id !== userId) {
if (await canViewUser(userId, id)) {
return unauthorized(res);
}
@ -37,12 +36,12 @@ export default async (
}
if (req.method === 'POST') {
const { username, password } = req.body;
if (id !== userId) {
if (await canUpdateUser(userId, id)) {
return unauthorized(res);
}
const { username, password } = req.body;
const user = await getUser({ id });
const data: any = {};
@ -52,7 +51,7 @@ export default async (
}
// Only admin can change these fields
if (!(await checkPermission(req, UmamiApi.Permission.Admin))) {
if (username && (await checkAdmin(userId))) {
data.username = username;
}
@ -71,12 +70,12 @@ export default async (
}
if (req.method === 'DELETE') {
if (id === userId) {
return badRequest(res, 'You cannot delete your own user.');
if (canDeleteUser(userId)) {
return unauthorized(res);
}
if (!(await checkPermission(req, UmamiApi.Permission.Admin))) {
return unauthorized(res);
if (id === userId) {
return badRequest(res, 'You cannot delete your own user.');
}
await deleteUser(id);

View File

@ -1,6 +1,5 @@
import { NextApiRequestQueryBody } from 'interface/api/nextApi';
import { allowQuery } from 'lib/auth';
import { UmamiApi } from 'lib/constants';
import { canUpdateUser } from 'lib/auth';
import { useAuth } from 'lib/middleware';
import { NextApiResponse } from 'next';
import {
@ -30,12 +29,15 @@ export default async (
const { current_password, new_password } = req.body;
const { id } = req.query;
if (!(await allowQuery(req, UmamiApi.AuthType.User))) {
return unauthorized(res);
}
const {
user: { id: userId },
} = req.auth;
if (req.method === 'POST') {
if (canUpdateUser(userId, id)) {
return unauthorized(res);
}
const user = await getUser({ id });
if (!checkPassword(current_password, user.password)) {

View File

@ -1,20 +1,17 @@
import { UserRole } from '@prisma/client';
import { NextApiRequestQueryBody } from 'interface/api/nextApi';
import { checkPermission } from 'lib/auth';
import { canUpdateUserRole } from 'lib/auth';
import { UmamiApi } from 'lib/constants';
import { uuid } from 'lib/crypto';
import { useAuth } from 'lib/middleware';
import { NextApiResponse } from 'next';
import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics';
import { createUserRole, deleteUserRole, getUserRole, getUserRoles } from 'queries';
import { deleteUserRole, getUserRole, getUserRoles, updateUserRole } from 'queries';
export interface UserRoleRequestQuery {
id: string;
}
export interface UserRoleRequestBody {
roleId: string;
teamId?: string;
role: UmamiApi.Role;
userRoleId?: string;
}
@ -29,7 +26,7 @@ export default async (
} = req.auth;
const { id } = req.query;
if (id !== userId || !(await checkPermission(req, UmamiApi.Permission.Admin))) {
if (await canUpdateUserRole(userId)) {
return unauthorized(res);
}
@ -40,17 +37,17 @@ export default async (
}
if (req.method === 'POST') {
const { roleId, teamId } = req.body;
const { role } = req.body;
const userRole = getUserRole({ userId: id, roleId, teamId });
const userRole = await getUserRole({ userId: id });
if (userRole) {
if (userRole && userRole.role === role) {
return badRequest(res, 'Role already exists for User.');
} else {
const updated = await updateUserRole({ role }, { id: userRole.id });
return ok(res, updated);
}
const updated = await createUserRole({ id: uuid(), userId: id, roleId, teamId });
return ok(res, updated);
}
if (req.method === 'DELETE') {

View File

@ -1,6 +1,5 @@
import { NextApiRequestQueryBody } from 'interface/api/nextApi';
import { checkPermission } from 'lib/auth';
import { UmamiApi } from 'lib/constants';
import { canCreateUser, canViewUsers } from 'lib/auth';
import { uuid } from 'lib/crypto';
import { useAuth } from 'lib/middleware';
import { NextApiResponse } from 'next';
@ -19,17 +18,25 @@ export default async (
) => {
await useAuth(req, res);
if (!(await checkPermission(req, UmamiApi.Permission.Admin))) {
return unauthorized(res);
}
const {
user: { id: userId },
} = req.auth;
if (req.method === 'GET') {
if (canViewUsers(userId)) {
return unauthorized(res);
}
const users = await getUsers();
return ok(res, users);
}
if (req.method === 'POST') {
if (canCreateUser(userId)) {
return unauthorized(res);
}
const { username, password, id } = req.body;
const user = await getUser({ username });

View File

@ -1,7 +1,6 @@
import { WebsiteActive } from 'interface/api/models';
import { NextApiRequestQueryBody } from 'interface/api/nextApi';
import { allowQuery } from 'lib/auth';
import { UmamiApi } from 'lib/constants';
import { canViewWebsite } from 'lib/auth';
import { useAuth, useCors } from 'lib/middleware';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
@ -18,13 +17,16 @@ export default async (
await useCors(req, res);
await useAuth(req, res);
const {
user: { id: userId },
} = req.auth;
const { id: websiteId } = req.query;
if (req.method === 'GET') {
if (!(await allowQuery(req, UmamiApi.AuthType.Website))) {
if (await canViewWebsite(userId, websiteId)) {
return unauthorized(res);
}
const { id: websiteId } = req.query;
const result = await getActiveVisitors(websiteId);
return ok(res, result);

View File

@ -1,7 +1,6 @@
import { WebsiteMetric } from 'interface/api/models';
import { NextApiRequestQueryBody } from 'interface/api/nextApi';
import { allowQuery } from 'lib/auth';
import { UmamiApi } from 'lib/constants';
import { canViewWebsite } from 'lib/auth';
import { useAuth, useCors } from 'lib/middleware';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
@ -26,13 +25,16 @@ export default async (
await useCors(req, res);
await useAuth(req, res);
const {
user: { id: userId },
} = req.auth;
const { id: websiteId } = req.query;
if (req.method === 'POST') {
if (!(await allowQuery(req, UmamiApi.AuthType.Website))) {
if (canViewWebsite(userId, websiteId)) {
return unauthorized(res);
}
const { id: websiteId } = req.query;
const { start_at, end_at, event_name: eventName, columns, filters } = req.body;
const startDate = new Date(+start_at);

View File

@ -1,7 +1,6 @@
import { WebsiteMetric } from 'interface/api/models';
import { NextApiRequestQueryBody } from 'interface/api/nextApi';
import { allowQuery } from 'lib/auth';
import { UmamiApi } from 'lib/constants';
import { canViewWebsite } from 'lib/auth';
import { useAuth, useCors } from 'lib/middleware';
import moment from 'moment-timezone';
import { NextApiResponse } from 'next';
@ -27,13 +26,16 @@ export default async (
await useCors(req, res);
await useAuth(req, res);
const {
user: { id: userId },
} = req.auth;
const { id: websiteId, start_at, end_at, unit, tz, url, event_name } = req.query;
if (req.method === 'GET') {
if (!(await allowQuery(req, UmamiApi.AuthType.Website))) {
if (canViewWebsite(userId, websiteId)) {
return unauthorized(res);
}
const { id: websiteId, start_at, end_at, unit, tz, url, event_name } = req.query;
if (!moment.tz.zone(tz) || !unitTypes.includes(unit)) {
return badRequest(res);
}

View File

@ -1,6 +1,6 @@
import { Website } from 'interface/api/models';
import { NextApiRequestQueryBody } from 'interface/api/nextApi';
import { canViewWebsite, canUpdateWebsite } from 'lib/auth';
import { canViewWebsite, canUpdateWebsite, canDeleteWebsite } from 'lib/auth';
import { useAuth, useCors } from 'lib/middleware';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, serverError, unauthorized } from 'next-basics';
@ -23,10 +23,13 @@ export default async (
await useCors(req, res);
await useAuth(req, res);
const {
user: { id: userId },
} = req.auth;
const { id: websiteId } = req.query;
if (req.method === 'GET') {
if (!(await canViewWebsite(req.auth.user.id, websiteId))) {
if (!(await canViewWebsite(userId, websiteId))) {
return unauthorized(res);
}
@ -36,7 +39,7 @@ export default async (
}
if (req.method === 'POST') {
if (!(await canUpdateWebsite(req.auth.user.id, websiteId))) {
if (!(await canUpdateWebsite(userId, websiteId))) {
return unauthorized(res);
}
@ -54,6 +57,10 @@ export default async (
}
if (req.method === 'DELETE') {
if (!(await canDeleteWebsite(userId, websiteId))) {
return unauthorized(res);
}
await deleteWebsite(websiteId);
return ok(res);

View File

@ -1,7 +1,7 @@
import { WebsiteMetric } from 'interface/api/models';
import { NextApiRequestQueryBody } from 'interface/api/nextApi';
import { allowQuery } from 'lib/auth';
import { FILTER_IGNORED, UmamiApi } from 'lib/constants';
import { canViewWebsite } from 'lib/auth';
import { FILTER_IGNORED } from 'lib/constants';
import { useAuth, useCors } from 'lib/middleware';
import { NextApiResponse } from 'next';
import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics';
@ -56,24 +56,27 @@ export default async (
await useCors(req, res);
await useAuth(req, res);
const {
user: { id: userId },
} = req.auth;
const {
id: websiteId,
type,
start_at,
end_at,
url,
referrer,
os,
browser,
device,
country,
} = req.query;
if (req.method === 'GET') {
if (!(await allowQuery(req, UmamiApi.AuthType.Website))) {
if (!(await canViewWebsite(userId, websiteId))) {
return unauthorized(res);
}
const {
id: websiteId,
type,
start_at,
end_at,
url,
referrer,
os,
browser,
device,
country,
} = req.query;
const startDate = new Date(+start_at);
const endDate = new Date(+end_at);

View File

@ -1,7 +1,6 @@
import { WebsitePageviews } from 'interface/api/models';
import { NextApiRequestQueryBody } from 'interface/api/nextApi';
import { allowQuery } from 'lib/auth';
import { UmamiApi } from 'lib/constants';
import { canViewWebsite } from 'lib/auth';
import { useAuth, useCors } from 'lib/middleware';
import moment from 'moment-timezone';
import { NextApiResponse } from 'next';
@ -32,25 +31,28 @@ export default async (
await useCors(req, res);
await useAuth(req, res);
const {
user: { id: userId },
} = req.auth;
const {
id: websiteId,
start_at,
end_at,
unit,
tz,
url,
referrer,
os,
browser,
device,
country,
} = req.query;
if (req.method === 'GET') {
if (!(await allowQuery(req, UmamiApi.AuthType.Website))) {
if (!(await canViewWebsite(userId, websiteId))) {
return unauthorized(res);
}
const {
id: websiteId,
start_at,
end_at,
unit,
tz,
url,
referrer,
os,
browser,
device,
country,
} = req.query;
const startDate = new Date(+start_at);
const endDate = new Date(+end_at);

View File

@ -1,6 +1,5 @@
import { NextApiRequestQueryBody } from 'interface/api/nextApi';
import { allowQuery } from 'lib/auth';
import { UmamiApi } from 'lib/constants';
import { canViewWebsite } from 'lib/auth';
import { useAuth, useCors } from 'lib/middleware';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
@ -17,10 +16,13 @@ export default async (
await useCors(req, res);
await useAuth(req, res);
const {
user: { id: userId },
} = req.auth;
const { id: websiteId } = req.query;
if (req.method === 'POST') {
if (!(await allowQuery(req, UmamiApi.AuthType.Website))) {
if (!(await canViewWebsite(userId, websiteId))) {
return unauthorized(res);
}

View File

@ -1,7 +1,6 @@
import { WebsiteStats } from 'interface/api/models';
import { NextApiRequestQueryBody } from 'interface/api/nextApi';
import { allowQuery } from 'lib/auth';
import { UmamiApi } from 'lib/constants';
import { canViewWebsite } from 'lib/auth';
import { useAuth, useCors } from 'lib/middleware';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
@ -27,23 +26,26 @@ export default async (
await useCors(req, res);
await useAuth(req, res);
const {
user: { id: userId },
} = req.auth;
const {
id: websiteId,
start_at,
end_at,
url,
referrer,
os,
browser,
device,
country,
} = req.query;
if (req.method === 'GET') {
if (!(await allowQuery(req, UmamiApi.AuthType.Website))) {
if (!(await canViewWebsite(userId, websiteId))) {
return unauthorized(res);
}
const {
id: websiteId,
start_at,
end_at,
url,
referrer,
os,
browser,
device,
country,
} = req.query;
const startDate = new Date(+start_at);
const endDate = new Date(+end_at);

View File

@ -1,12 +1,11 @@
import { Prisma } from '@prisma/client';
import { NextApiRequestQueryBody } from 'interface/api/nextApi';
import { checkAdmin } from 'lib/auth';
import { uuid } from 'lib/crypto';
import { useAuth, useCors } from 'lib/middleware';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok } from 'next-basics';
import { createWebsite, getAllWebsites, getWebsitesByUserId } from 'queries';
import { checkPermission } from 'lib/auth';
import { UmamiApi } from 'lib/constants';
export interface WebsitesRequestQuery {
include_all?: boolean;
@ -33,7 +32,7 @@ export default async (
if (req.method === 'GET') {
const { include_all } = req.query;
const isAdmin = await checkPermission(req, UmamiApi.Permission.Admin);
const isAdmin = await checkAdmin(userId);
const websites =
isAdmin && include_all ? await getAllWebsites() : await getWebsitesByUserId(userId);
@ -44,7 +43,7 @@ export default async (
if (req.method === 'POST') {
const { name, domain, shareId, teamId } = req.body;
const data: Prisma.WebsiteCreateInput = {
const data: Prisma.WebsiteUncheckedCreateInput = {
id: uuid(),
name,
domain,

View File

@ -1,83 +0,0 @@
import { Prisma, Permission } from '@prisma/client';
import prisma from 'lib/prisma';
export async function createPermission(data: Prisma.PermissionCreateInput): Promise<Permission> {
return prisma.client.permission.create({
data,
});
}
export async function getPermission(where: Prisma.PermissionWhereUniqueInput): Promise<Permission> {
return prisma.client.permission.findUnique({
where,
});
}
export async function getPermissions(where: Prisma.PermissionWhereInput): Promise<Permission[]> {
return prisma.client.permission.findMany({
where,
});
}
export async function getPermissionsByUserId(userId, name?: string): Promise<Permission[]> {
return prisma.client.permission.findMany({
where: {
...(name ? { name } : {}),
RolePermission: {
every: {
role: {
is: {
userRoles: {
every: {
userId,
},
},
},
},
},
},
},
});
}
export async function getPermissionsByTeamId(teamId, name?: string): Promise<Permission[]> {
return prisma.client.permission.findMany({
where: {
...(name ? { name } : {}),
RolePermission: {
every: {
role: {
is: {
TeamUser: {
every: {
teamId,
},
},
},
},
},
},
},
});
}
export async function updatePermission(
data: Prisma.PermissionUpdateInput,
where: Prisma.PermissionWhereUniqueInput,
): Promise<Permission> {
return prisma.client.permission.update({
data,
where,
});
}
export async function deletePermission(permissionId: string): Promise<Permission> {
return prisma.client.permission.update({
data: {
isDeleted: true,
},
where: {
id: permissionId,
},
});
}

View File

@ -1,57 +0,0 @@
import { Prisma, Role } from '@prisma/client';
import prisma from 'lib/prisma';
export async function createRole(data: {
id: string;
name: string;
description: string;
}): Promise<Role> {
return prisma.client.role.create({
data,
});
}
export async function getRole(where: Prisma.RoleWhereUniqueInput): Promise<Role> {
return prisma.client.role.findUnique({
where,
});
}
export async function getRoles(where: Prisma.RoleWhereInput): Promise<Role[]> {
return prisma.client.role.findMany({
where,
});
}
export async function getRolesByUserId(userId: string): Promise<Role[]> {
return prisma.client.role.findMany({
where: {
userRoles: {
every: {
userId,
},
},
},
});
}
export async function updateRole(
data: Prisma.RoleUpdateInput,
where: Prisma.RoleWhereUniqueInput,
): Promise<Role> {
return prisma.client.role.update({
data,
where,
});
}
export async function deleteRole(roleId: string): Promise<Role> {
return prisma.client.role.update({
data: {
isDeleted: true,
},
where: {
id: roleId,
},
});
}

View File

@ -5,14 +5,14 @@ import prisma from 'lib/prisma';
export async function createTeamUser(
userId: string,
teamId: string,
roleId: string,
role: string,
): Promise<TeamUser> {
return prisma.client.teamUser.create({
data: {
id: uuid(),
userId,
teamId,
roleId,
role,
},
});
}

View File

@ -36,9 +36,7 @@ export async function getUser(
id: true,
username: true,
userRole: {
include: {
role: true,
},
select: { role: true },
},
password: includePassword,
},

View File

@ -19,7 +19,10 @@ export async function createWebsite(
});
}
export async function updateWebsite(websiteId, data: Prisma.WebsiteUpdateInput): Promise<Website> {
export async function updateWebsite(
websiteId,
data: Prisma.WebsiteUpdateInput | Prisma.WebsiteUncheckedUpdateInput,
): Promise<Website> {
return prisma.client.website.update({
where: {
id: websiteId,
@ -97,7 +100,9 @@ export async function deleteWebsite(websiteId: string) {
});
}
async function deleteWebsiteRelationalQuery(websiteId,): Promise<[Prisma.BatchPayload, Prisma.BatchPayload, Website]> {
async function deleteWebsiteRelationalQuery(
websiteId,
): Promise<[Prisma.BatchPayload, Prisma.BatchPayload, Website]> {
const { client, transaction } = prisma;
return transaction([

View File

@ -1,5 +1,3 @@
export * from './admin/permission';
export * from './admin/role';
export * from './admin/team';
export * from './admin/teamUser';
export * from './admin/user';