This commit is contained in:
Milo Schwartz 2024-10-06 18:09:12 -04:00
commit 3e2085fbb3
No known key found for this signature in database
57 changed files with 2604 additions and 2104 deletions

View file

@ -1,3 +1,6 @@
{ {
"extends": ["next/core-web-vitals", "next/typescript"] "extends": [
"next/core-web-vitals",
"next/typescript"
]
} }

11
bruno/Auth/login.bru Normal file
View file

@ -0,0 +1,11 @@
meta {
name: login
type: http
seq: 1
}
get {
url:
body: none
auth: none
}

11
bruno/Orgs/listOrgs.bru Normal file
View file

@ -0,0 +1,11 @@
meta {
name: listOrgs
type: http
seq: 1
}
get {
url:
body: none
auth: none
}

View file

@ -0,0 +1,11 @@
meta {
name: listResourcesByOrg
type: http
seq: 1
}
get {
url:
body: none
auth: none
}

View file

@ -0,0 +1,16 @@
meta {
name: listResourcesBySite
type: http
seq: 2
}
get {
url: http://localhost:3000/api/v1/site/1/resources?limit=10&offset=0
body: none
auth: none
}
params:query {
limit: 10
offset: 0
}

11
bruno/Sites/listSites.bru Normal file
View file

@ -0,0 +1,11 @@
meta {
name: listSites
type: http
seq: 1
}
get {
url:
body: none
auth: none
}

View file

@ -0,0 +1,16 @@
meta {
name: listTargets
type: http
seq: 1
}
get {
url: http://localhost:3000/api/v1/resource/web.main.localhost/targets?limit=10&offset=0
body: none
auth: none
}
params:query {
limit: 10
offset: 0
}

9
bruno/bruno.json Normal file
View file

@ -0,0 +1,9 @@
{
"version": "1",
"name": "Pangolin",
"type": "collection",
"ignore": [
"node_modules",
".git"
]
}

96
server/auth/actions.ts Normal file
View file

@ -0,0 +1,96 @@
import { Request } from 'express';
import { db } from '@server/db';
import { userActions, roleActions, userOrgs } from '@server/db/schema';
import { and, eq } from 'drizzle-orm';
import createHttpError from 'http-errors';
import HttpCode from '@server/types/HttpCode';
export enum ActionsEnum {
createOrg = "createOrg",
deleteOrg = "deleteOrg",
getOrg = "getOrg",
listOrgs = "listOrgs",
updateOrg = "updateOrg",
createSite = "createSite",
deleteSite = "deleteSite",
getSite = "getSite",
listSites = "listSites",
updateSite = "updateSite",
createResource = "createResource",
deleteResource = "deleteResource",
getResource = "getResource",
listResources = "listResources",
updateResource = "updateResource",
createTarget = "createTarget",
deleteTarget = "deleteTarget",
getTarget = "getTarget",
listTargets = "listTargets",
updateTarget = "updateTarget",
getUser = "getUser",
deleteUser = "deleteUser",
listUsers = "listUsers"
}
export async function checkUserActionPermission(actionId: string, req: Request): Promise<boolean> {
const userId = req.user?.id;
if (!userId) {
throw createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated');
}
if (!req.userOrgId) {
throw createHttpError(HttpCode.BAD_REQUEST, 'Organization ID is required');
}
try {
let userOrgRoleId = req.userOrgRoleId;
// If userOrgRoleId is not available on the request, fetch it
if (userOrgRoleId === undefined) {
const userOrgRole = await db.select()
.from(userOrgs)
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, req.userOrgId)))
.limit(1);
if (userOrgRole.length === 0) {
throw createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization');
}
userOrgRoleId = userOrgRole[0].roleId;
}
// Check if the user has direct permission for the action in the current org
const userActionPermission = await db.select()
.from(userActions)
.where(
and(
eq(userActions.userId, userId),
eq(userActions.actionId, actionId),
eq(userActions.orgId, req.userOrgId)
)
)
.limit(1);
if (userActionPermission.length > 0) {
return true;
}
// If no direct permission, check role-based permission
const roleActionPermission = await db.select()
.from(roleActions)
.where(
and(
eq(roleActions.actionId, actionId),
eq(roleActions.roleId, userOrgRoleId),
eq(roleActions.orgId, req.userOrgId)
)
)
.limit(1);
return roleActionPermission.length > 0;
} catch (error) {
console.error('Error checking user action permission:', error);
throw createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error checking action permission');
}
}

40
server/auth/limits.ts Normal file
View file

@ -0,0 +1,40 @@
import { db } from '@server/db';
import { limitsTable } from '@server/db/schema';
import { and, eq } from 'drizzle-orm';
import createHttpError from 'http-errors';
import HttpCode from '@server/types/HttpCode';
interface CheckLimitOptions {
orgId: number;
limitName: string;
currentValue: number;
increment?: number;
}
export async function checkOrgLimit({ orgId, limitName, currentValue, increment = 0 }: CheckLimitOptions): Promise<boolean> {
try {
const limit = await db.select()
.from(limitsTable)
.where(
and(
eq(limitsTable.orgId, orgId),
eq(limitsTable.name, limitName)
)
)
.limit(1);
if (limit.length === 0) {
throw createHttpError(HttpCode.NOT_FOUND, `Limit "${limitName}" not found for organization`);
}
const limitValue = limit[0].value;
// Check if the current value plus the increment is within the limit
return (currentValue + increment) <= limitValue;
} catch (error) {
if (error instanceof Error) {
throw createHttpError(HttpCode.INTERNAL_SERVER_ERROR, `Error checking limit: ${error.message}`);
}
throw createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Unknown error occurred while checking limit');
}
}

View file

@ -107,7 +107,7 @@ export const userOrgs = sqliteTable("userOrgs", {
orgId: integer("orgId") orgId: integer("orgId")
.notNull() .notNull()
.references(() => orgs.orgId), .references(() => orgs.orgId),
role: text("role").notNull(), // e.g., 'admin', 'member', etc. roleId: integer("roleId").notNull().references(() => roles.roleId),
}); });
export const emailVerificationCodes = sqliteTable("emailVerificationCodes", { export const emailVerificationCodes = sqliteTable("emailVerificationCodes", {
@ -130,7 +130,7 @@ export const passwordResetTokens = sqliteTable("passwordResetTokens", {
}); });
export const actions = sqliteTable("actions", { export const actions = sqliteTable("actions", {
actionId: integer("actionId").primaryKey({ autoIncrement: true }), actionId: text("actionId").primaryKey(),
name: text("name").notNull(), name: text("name").notNull(),
description: text("description"), description: text("description"),
}); });
@ -146,22 +146,28 @@ export const roleActions = sqliteTable("roleActions", {
roleId: integer("roleId") roleId: integer("roleId")
.notNull() .notNull()
.references(() => roles.roleId, { onDelete: "cascade" }), .references(() => roles.roleId, { onDelete: "cascade" }),
actionId: integer("actionId") actionId: text("actionId")
.notNull() .notNull()
.references(() => actions.actionId, { onDelete: "cascade" }), .references(() => actions.actionId, { onDelete: "cascade" }),
orgId: integer("orgId")
.notNull()
.references(() => orgs.orgId, { onDelete: "cascade" }),
}); });
export const userActions = sqliteTable("userActions", { export const userActions = sqliteTable("userActions", {
userId: text("userId") userId: text("userId")
.notNull() .notNull()
.references(() => users.id, { onDelete: "cascade" }), .references(() => users.id, { onDelete: "cascade" }),
actionId: integer("actionId") actionId: text("actionId")
.notNull() .notNull()
.references(() => actions.actionId, { onDelete: "cascade" }), .references(() => actions.actionId, { onDelete: "cascade" }),
orgId: integer("orgId")
.notNull()
.references(() => orgs.orgId, { onDelete: "cascade" }),
}); });
export const roleSites = sqliteTable("roleActions", { export const roleSites = sqliteTable("roleSites", {
roleId: integer("role]Id") roleId: integer("roleId")
.notNull() .notNull()
.references(() => roles.roleId, { onDelete: "cascade" }), .references(() => roles.roleId, { onDelete: "cascade" }),
siteId: integer("siteId") siteId: integer("siteId")
@ -169,8 +175,8 @@ export const roleSites = sqliteTable("roleActions", {
.references(() => sites.siteId, { onDelete: "cascade" }), .references(() => sites.siteId, { onDelete: "cascade" }),
}); });
export const userSites = sqliteTable("userActions", { export const userSites = sqliteTable("userSites", {
userId: text("user]Id") userId: text("userId")
.notNull() .notNull()
.references(() => users.id, { onDelete: "cascade" }), .references(() => users.id, { onDelete: "cascade" }),
siteId: integer("siteId") siteId: integer("siteId")
@ -178,24 +184,32 @@ export const userSites = sqliteTable("userActions", {
.references(() => sites.siteId, { onDelete: "cascade" }), .references(() => sites.siteId, { onDelete: "cascade" }),
}); });
export const roleResources = sqliteTable("roleActions", { export const roleResources = sqliteTable("roleResources", {
roleId: integer("role]Id") roleId: integer("roleId")
.notNull() .notNull()
.references(() => roles.roleId, { onDelete: "cascade" }), .references(() => roles.roleId, { onDelete: "cascade" }),
resourceId: integer("resourceId") resourceId: text("resourceId")
.notNull() .notNull()
.references(() => resources.resourceId, { onDelete: "cascade" }), .references(() => resources.resourceId, { onDelete: "cascade" }),
}); });
export const userResources = sqliteTable("userActions", { export const userResources = sqliteTable("userResources", {
userId: text("user]Id") userId: text("userId")
.notNull() .notNull()
.references(() => users.id, { onDelete: "cascade" }), .references(() => users.id, { onDelete: "cascade" }),
resourceId: integer("resourceId") resourceId: text("resourceId")
.notNull() .notNull()
.references(() => resources.resourceId, { onDelete: "cascade" }), .references(() => resources.resourceId, { onDelete: "cascade" }),
}); });
export const limitsTable = sqliteTable("limits", {
limitId: integer("limitId").primaryKey({ autoIncrement: true }),
orgId: integer("orgId").references(() => orgs.orgId, { onDelete: "cascade" }),
name: text("name").notNull(),
value: integer("value").notNull(),
description: text("description"),
});
// Define the model types for type inference // Define the model types for type inference
export type Org = InferSelectModel<typeof orgs>; export type Org = InferSelectModel<typeof orgs>;
export type User = InferSelectModel<typeof users>; export type User = InferSelectModel<typeof users>;
@ -218,3 +232,4 @@ export type RoleSite = InferSelectModel<typeof roleSites>;
export type UserSite = InferSelectModel<typeof userSites>; export type UserSite = InferSelectModel<typeof userSites>;
export type RoleResource = InferSelectModel<typeof roleResources>; export type RoleResource = InferSelectModel<typeof roleResources>;
export type UserResource = InferSelectModel<typeof userResources>; export type UserResource = InferSelectModel<typeof userResources>;
export type Limit = InferSelectModel<typeof limitsTable>;

View file

@ -85,8 +85,9 @@ declare global {
namespace Express { namespace Express {
interface Request { interface Request {
user?: User; user?: User;
userOrgRole?: string; userOrgRoleId?: number;
userOrgs?: number[]; userOrgId?: number;
userOrgIds?: number[];
} }
} }
} }

View file

@ -15,13 +15,13 @@ export async function getUserOrgs(req: Request, res: Response, next: NextFunctio
try { try {
const userOrganizations = await db.select({ const userOrganizations = await db.select({
orgId: userOrgs.orgId, orgId: userOrgs.orgId,
role: userOrgs.role, roleId: userOrgs.roleId,
}) })
.from(userOrgs) .from(userOrgs)
.where(eq(userOrgs.userId, userId)); .where(eq(userOrgs.userId, userId));
req.userOrgs = userOrganizations.map(org => org.orgId); req.userOrgIds = userOrganizations.map(org => org.orgId);
// req.userOrgRoles = userOrganizations.reduce((acc, org) => { // req.userOrgRoleIds = userOrganizations.reduce((acc, org) => {
// acc[org.orgId] = org.role; // acc[org.orgId] = org.role;
// return acc; // return acc;
// }, {} as Record<number, string>); // }, {} as Record<number, string>);

View file

@ -26,7 +26,8 @@ export function verifyOrgAccess(req: Request, res: Response, next: NextFunction)
next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization')); next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization'));
} else { } else {
// User has access, attach the user's role to the request for potential future use // User has access, attach the user's role to the request for potential future use
req.userOrgRole = result[0].role; req.userOrgRoleId = result[0].roleId;
req.userOrgId = orgId;
next(); next();
} }
}) })

View file

@ -1,6 +1,6 @@
import { Request, Response, NextFunction } from 'express'; import { Request, Response, NextFunction } from 'express';
import { db } from '@server/db'; import { db } from '@server/db';
import { resources, userOrgs } from '@server/db/schema'; import { resources, userOrgs, userResources, roleResources } from '@server/db/schema';
import { and, eq } from 'drizzle-orm'; import { and, eq } from 'drizzle-orm';
import createHttpError from 'http-errors'; import createHttpError from 'http-errors';
import HttpCode from '@server/types/HttpCode'; import HttpCode from '@server/types/HttpCode';
@ -13,42 +13,66 @@ export async function verifyResourceAccess(req: Request, res: Response, next: Ne
return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated')); return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated'));
} }
try {
// Get the resource
const resource = await db.select() const resource = await db.select()
.from(resources) .from(resources)
.where(eq(resources.resourceId, resourceId)) .where(eq(resources.resourceId, resourceId))
.limit(1); .limit(1);
if (resource.length === 0) { if (resource.length === 0) {
return next( return next(createHttpError(HttpCode.NOT_FOUND, `Resource with ID ${resourceId} not found`));
createHttpError(
HttpCode.NOT_FOUND,
`resource with ID ${resourceId} not found`
)
);
} }
if (!resource[0].orgId) { if (!resource[0].orgId) {
return next( return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, `Resource with ID ${resourceId} does not have an organization ID`));
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
`resource with ID ${resourceId} does not have an organization ID`
)
);
} }
db.select() // Get user's role ID in the organization
const userOrgRole = await db.select()
.from(userOrgs) .from(userOrgs)
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, resource[0].orgId))) .where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, resource[0].orgId)))
.then((result) => { .limit(1);
if (result.length === 0) {
next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization')); if (userOrgRole.length === 0) {
} else { return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization'));
// User has access, attach the user's role to the request for potential future use }
req.userOrgRole = result[0].role;
next(); const userOrgRoleId = userOrgRole[0].roleId;
req.userOrgRoleId = userOrgRoleId;
req.userOrgId = resource[0].orgId;
// Check role-based resource access first
const roleResourceAccess = await db.select()
.from(roleResources)
.where(
and(
eq(roleResources.resourceId, resourceId),
eq(roleResources.roleId, userOrgRoleId)
)
)
.limit(1);
if (roleResourceAccess.length > 0) {
// User's role has access to the resource
return next();
}
// If role doesn't have access, check user-specific resource access
const userResourceAccess = await db.select()
.from(userResources)
.where(and(eq(userResources.userId, userId), eq(userResources.resourceId, resourceId)))
.limit(1);
if (userResourceAccess.length > 0) {
// User has direct access to the resource
return next();
}
// If we reach here, the user doesn't have access to the resource
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this resource'));
} catch (error) {
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying resource access'));
} }
})
.catch((error) => {
next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying organization access'));
});
} }

View file

@ -1,7 +1,7 @@
import { Request, Response, NextFunction } from 'express'; import { Request, Response, NextFunction } from 'express';
import { db } from '@server/db'; import { db } from '@server/db';
import { sites, userOrgs } from '@server/db/schema'; import { sites, userOrgs, userSites, roleSites, roles } from '@server/db/schema';
import { and, eq } from 'drizzle-orm'; import { and, eq, or } from 'drizzle-orm';
import createHttpError from 'http-errors'; import createHttpError from 'http-errors';
import HttpCode from '@server/types/HttpCode'; import HttpCode from '@server/types/HttpCode';
@ -14,45 +14,66 @@ export async function verifySiteAccess(req: Request, res: Response, next: NextFu
} }
if (isNaN(siteId)) { if (isNaN(siteId)) {
return next(createHttpError(HttpCode.BAD_REQUEST, 'Invalid organization ID')); return next(createHttpError(HttpCode.BAD_REQUEST, 'Invalid site ID'));
} }
const site = await db.select() try {
.from(sites) // Get the site
.where(eq(sites.siteId, siteId)) const site = await db.select().from(sites).where(eq(sites.siteId, siteId)).limit(1);
.limit(1);
if (site.length === 0) { if (site.length === 0) {
return next( return next(createHttpError(HttpCode.NOT_FOUND, `Site with ID ${siteId} not found`));
createHttpError(
HttpCode.NOT_FOUND,
`Site with ID ${siteId} not found`
)
);
} }
if (!site[0].orgId) { if (!site[0].orgId) {
return next( return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, `Site with ID ${siteId} does not have an organization ID`));
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
`Site with ID ${siteId} does not have an organization ID`
)
);
} }
db.select() // Get user's role ID in the organization
const userOrgRole = await db.select()
.from(userOrgs) .from(userOrgs)
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, site[0].orgId))) .where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, site[0].orgId)))
.then((result) => { .limit(1);
if (result.length === 0) {
next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization')); if (userOrgRole.length === 0) {
} else { return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization'));
// User has access, attach the user's role to the request for potential future use }
req.userOrgRole = result[0].role;
next(); const userOrgRoleId = userOrgRole[0].roleId;
req.userOrgRoleId = userOrgRoleId;
req.userOrgId = site[0].orgId;
// Check role-based site access first
const roleSiteAccess = await db.select()
.from(roleSites)
.where(
and(
eq(roleSites.siteId, siteId),
eq(roleSites.roleId, userOrgRoleId)
)
)
.limit(1);
if (roleSiteAccess.length > 0) {
// User's role has access to the site
return next();
}
// If role doesn't have access, check user-specific site access
const userSiteAccess = await db.select()
.from(userSites)
.where(and(eq(userSites.userId, userId), eq(userSites.siteId, siteId)))
.limit(1);
if (userSiteAccess.length > 0) {
// User has direct access to the site
return next();
}
// If we reach here, the user doesn't have access to the site
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this site'));
} catch (error) {
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying site access'));
} }
})
.catch((error) => {
next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying organization access'));
});
} }

View file

@ -73,7 +73,8 @@ export async function verifyTargetAccess(req: Request, res: Response, next: Next
next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization')); next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization'));
} else { } else {
// User has access, attach the user's role to the request for potential future use // User has access, attach the user's role to the request for potential future use
req.userOrgRole = result[0].role; req.userOrgRoleId = result[0].roleId;
req.userOrgId = resource[0].orgId!;
next(); next();
} }
}) })

View file

@ -3,6 +3,8 @@ import { DrizzleError, eq } from 'drizzle-orm';
import { sites, resources, targets, exitNodes } from '@server/db/schema'; import { sites, resources, targets, exitNodes } from '@server/db/schema';
import db from '@server/db'; import db from '@server/db';
import logger from '@server/logger'; import logger from '@server/logger';
import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors';
export const getConfig = async (req: Request, res: Response, next: NextFunction): Promise<void> => { export const getConfig = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try { try {
@ -55,11 +57,7 @@ export const getConfig = async (req: Request, res: Response, next: NextFunction)
res.json(config); res.json(config);
} catch (error) { } catch (error) {
logger.error('Error querying database:', error); logger.error('Error querying database:', error);
if (error instanceof DrizzleError) { return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
res.status(500).json({ error: 'Database query error', message: error.message });
} else {
next(error);
}
} }
}; };

View file

@ -3,6 +3,9 @@ import { DrizzleError, eq } from 'drizzle-orm';
import { sites, resources, targets, exitNodes } from '@server/db/schema'; import { sites, resources, targets, exitNodes } from '@server/db/schema';
import db from '@server/db'; import db from '@server/db';
import logger from '@server/logger'; import logger from '@server/logger';
import createHttpError from 'http-errors';
import HttpCode from '@server/types/HttpCode';
import response from "@server/utils/response";
interface PeerBandwidth { interface PeerBandwidth {
publicKey: string; publicKey: string;
@ -10,7 +13,7 @@ interface PeerBandwidth {
bytesOut: number; bytesOut: number;
} }
export const receiveBandwidth = async (req: Request, res: Response, next: NextFunction): Promise<void> => { export const receiveBandwidth = async (req: Request, res: Response, next: NextFunction): Promise<any> => {
try { try {
const bandwidthData: PeerBandwidth[] = req.body; const bandwidthData: PeerBandwidth[] = req.body;
@ -43,10 +46,16 @@ export const receiveBandwidth = async (req: Request, res: Response, next: NextFu
} }
res.status(200).json({ message: 'Bandwidth data updated successfully' }); return response(res, {
data: {},
success: true,
error: false,
message: "Organization retrieved successfully",
status: HttpCode.OK,
});
} catch (error) { } catch (error) {
logger.error('Error updating bandwidth data:', error); logger.error('Error updating bandwidth data:', error);
res.status(500).json({ error: 'Internal server error' }); return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
} }
}; };

View file

@ -5,6 +5,8 @@ import { orgs } from '@server/db/schema';
import response from "@server/utils/response"; import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode'; import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors'; import createHttpError from 'http-errors';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
const createOrgSchema = z.object({ const createOrgSchema = z.object({
name: z.string().min(1).max(255), name: z.string().min(1).max(255),
@ -25,7 +27,7 @@ export async function createOrg(req: Request, res: Response, next: NextFunction)
); );
} }
const userOrgIds = req.userOrgs; const userOrgIds = req.userOrgIds;
if (userOrgIds && userOrgIds.length > MAX_ORGS) { if (userOrgIds && userOrgIds.length > MAX_ORGS) {
return next( return next(
createHttpError( createHttpError(
@ -35,6 +37,12 @@ export async function createOrg(req: Request, res: Response, next: NextFunction)
); );
} }
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.createOrg, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
const { name, domain } = parsedBody.data; const { name, domain } = parsedBody.data;
const newOrg = await db.insert(orgs).values({ const newOrg = await db.insert(orgs).values({
@ -42,16 +50,15 @@ export async function createOrg(req: Request, res: Response, next: NextFunction)
domain, domain,
}).returning(); }).returning();
return res.status(HttpCode.CREATED).send( return response(res, {
response(res, {
data: newOrg[0], data: newOrg[0],
success: true, success: true,
error: false, error: false,
message: "Organization created successfully", message: "Organization created successfully",
status: HttpCode.CREATED, status: HttpCode.CREATED,
}) });
);
} catch (error) { } catch (error) {
next(error); logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
} }
} }

View file

@ -6,6 +6,8 @@ import { eq } from 'drizzle-orm';
import response from "@server/utils/response"; import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode'; import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors'; import createHttpError from 'http-errors';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
const deleteOrgSchema = z.object({ const deleteOrgSchema = z.object({
orgId: z.string().transform(Number).pipe(z.number().int().positive()) orgId: z.string().transform(Number).pipe(z.number().int().positive())
@ -25,6 +27,12 @@ export async function deleteOrg(req: Request, res: Response, next: NextFunction)
const { orgId } = parsedParams.data; const { orgId } = parsedParams.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.deleteOrg, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
const deletedOrg = await db.delete(orgs) const deletedOrg = await db.delete(orgs)
.where(eq(orgs.orgId, orgId)) .where(eq(orgs.orgId, orgId))
.returning(); .returning();
@ -38,16 +46,15 @@ export async function deleteOrg(req: Request, res: Response, next: NextFunction)
); );
} }
return res.status(HttpCode.OK).send( return response(res, {
response(res, {
data: null, data: null,
success: true, success: true,
error: false, error: false,
message: "Organization deleted successfully", message: "Organization deleted successfully",
status: HttpCode.OK, status: HttpCode.OK,
}) });
);
} catch (error) { } catch (error) {
next(error); logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
} }
} }

View file

@ -6,6 +6,8 @@ import { eq } from 'drizzle-orm';
import response from "@server/utils/response"; import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode'; import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors'; import createHttpError from 'http-errors';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
const getOrgSchema = z.object({ const getOrgSchema = z.object({
orgId: z.string().transform(Number).pipe(z.number().int().positive()) orgId: z.string().transform(Number).pipe(z.number().int().positive())
@ -25,6 +27,12 @@ export async function getOrg(req: Request, res: Response, next: NextFunction): P
const { orgId } = parsedParams.data; const { orgId } = parsedParams.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.getOrg, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
const org = await db.select() const org = await db.select()
.from(orgs) .from(orgs)
.where(eq(orgs.orgId, orgId)) .where(eq(orgs.orgId, orgId))
@ -39,16 +47,15 @@ export async function getOrg(req: Request, res: Response, next: NextFunction): P
); );
} }
return res.status(HttpCode.OK).send( return response(res, {
response(res, {
data: org[0], data: org[0],
success: true, success: true,
error: false, error: false,
message: "Organization retrieved successfully", message: "Organization retrieved successfully",
status: HttpCode.OK, status: HttpCode.OK,
}) });
);
} catch (error) { } catch (error) {
next(error); logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
} }
} }

View file

@ -6,6 +6,8 @@ import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode'; import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors'; import createHttpError from 'http-errors';
import { sql, inArray } from 'drizzle-orm'; import { sql, inArray } from 'drizzle-orm';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
const listOrgsSchema = z.object({ const listOrgsSchema = z.object({
limit: z.string().optional().transform(Number).pipe(z.number().int().positive().default(10)), limit: z.string().optional().transform(Number).pipe(z.number().int().positive().default(10)),
@ -26,8 +28,14 @@ export async function listOrgs(req: Request, res: Response, next: NextFunction):
const { limit, offset } = parsedQuery.data; const { limit, offset } = parsedQuery.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.listOrgs, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
// Use the userOrgs passed from the middleware // Use the userOrgs passed from the middleware
const userOrgIds = req.userOrgs; const userOrgIds = req.userOrgIds;
if (!userOrgIds || userOrgIds.length === 0) { if (!userOrgIds || userOrgIds.length === 0) {
return res.status(HttpCode.OK).send( return res.status(HttpCode.OK).send(
@ -62,11 +70,10 @@ export async function listOrgs(req: Request, res: Response, next: NextFunction):
// // Add the user's role for each organization // // Add the user's role for each organization
// const organizationsWithRoles = organizations.map(org => ({ // const organizationsWithRoles = organizations.map(org => ({
// ...org, // ...org,
// userRole: req.userOrgRoles[org.orgId], // userRole: req.userOrgRoleIds[org.orgId],
// })); // }));
return res.status(HttpCode.OK).send( return response(res, {
response(res, {
data: { data: {
organizations, organizations,
pagination: { pagination: {
@ -79,9 +86,9 @@ export async function listOrgs(req: Request, res: Response, next: NextFunction):
error: false, error: false,
message: "Organizations retrieved successfully", message: "Organizations retrieved successfully",
status: HttpCode.OK, status: HttpCode.OK,
}) });
);
} catch (error) { } catch (error) {
next(error); logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
} }
} }

View file

@ -6,6 +6,8 @@ import { eq } from 'drizzle-orm';
import response from "@server/utils/response"; import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode'; import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors'; import createHttpError from 'http-errors';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
const updateOrgParamsSchema = z.object({ const updateOrgParamsSchema = z.object({
orgId: z.string().transform(Number).pipe(z.number().int().positive()) orgId: z.string().transform(Number).pipe(z.number().int().positive())
@ -43,6 +45,13 @@ export async function updateOrg(req: Request, res: Response, next: NextFunction)
const { orgId } = parsedParams.data; const { orgId } = parsedParams.data;
const updateData = parsedBody.data; const updateData = parsedBody.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.updateOrg, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
const updatedOrg = await db.update(orgs) const updatedOrg = await db.update(orgs)
.set(updateData) .set(updateData)
.where(eq(orgs.orgId, orgId)) .where(eq(orgs.orgId, orgId))
@ -57,16 +66,15 @@ export async function updateOrg(req: Request, res: Response, next: NextFunction)
); );
} }
return res.status(HttpCode.OK).send( return response(res, {
response(res, {
data: updatedOrg[0], data: updatedOrg[0],
success: true, success: true,
error: false, error: false,
message: "Organization updated successfully", message: "Organization updated successfully",
status: HttpCode.OK, status: HttpCode.OK,
}) });
);
} catch (error) { } catch (error) {
next(error); logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
} }
} }

View file

@ -5,6 +5,8 @@ import { resources } from '@server/db/schema';
import response from "@server/utils/response"; import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode'; import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors'; import createHttpError from 'http-errors';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
const createResourceParamsSchema = z.object({ const createResourceParamsSchema = z.object({
siteId: z.number().int().positive(), siteId: z.number().int().positive(),
@ -45,6 +47,12 @@ export async function createResource(req: Request, res: Response, next: NextFunc
const { siteId, orgId } = parsedParams.data; const { siteId, orgId } = parsedParams.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.createResource, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
// Generate a unique resourceId // Generate a unique resourceId
const resourceId = "subdomain" // TODO: create the subdomain here const resourceId = "subdomain" // TODO: create the subdomain here
@ -57,16 +65,15 @@ export async function createResource(req: Request, res: Response, next: NextFunc
subdomain, subdomain,
}).returning(); }).returning();
return res.status(HttpCode.CREATED).send(
response(res, { response(res, {
data: newResource[0], data: newResource[0],
success: true, success: true,
error: false, error: false,
message: "Resource created successfully", message: "Resource created successfully",
status: HttpCode.CREATED, status: HttpCode.CREATED,
}) });
);
} catch (error) { } catch (error) {
next(error); logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
} }
} }

View file

@ -6,6 +6,8 @@ import { eq } from 'drizzle-orm';
import response from "@server/utils/response"; import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode'; import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors'; import createHttpError from 'http-errors';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
// Define Zod schema for request parameters validation // Define Zod schema for request parameters validation
const deleteResourceSchema = z.object({ const deleteResourceSchema = z.object({
@ -27,6 +29,12 @@ export async function deleteResource(req: Request, res: Response, next: NextFunc
const { resourceId } = parsedParams.data; const { resourceId } = parsedParams.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.deleteResource, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
// Delete the resource from the database // Delete the resource from the database
const deletedResource = await db.delete(resources) const deletedResource = await db.delete(resources)
.where(eq(resources.resourceId, resourceId)) .where(eq(resources.resourceId, resourceId))
@ -41,16 +49,15 @@ export async function deleteResource(req: Request, res: Response, next: NextFunc
); );
} }
return res.status(HttpCode.OK).send( return response(res, {
response(res, {
data: null, data: null,
success: true, success: true,
error: false, error: false,
message: "Resource deleted successfully", message: "Resource deleted successfully",
status: HttpCode.OK, status: HttpCode.OK,
}) });
);
} catch (error) { } catch (error) {
next(error); logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
} }
} }

View file

@ -6,6 +6,8 @@ import { eq } from 'drizzle-orm';
import response from "@server/utils/response"; import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode'; import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors'; import createHttpError from 'http-errors';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
// Define Zod schema for request parameters validation // Define Zod schema for request parameters validation
const getResourceSchema = z.object({ const getResourceSchema = z.object({
@ -27,6 +29,12 @@ export async function getResource(req: Request, res: Response, next: NextFunctio
const { resourceId } = parsedParams.data; const { resourceId } = parsedParams.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.getResource, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
// Fetch the resource from the database // Fetch the resource from the database
const resource = await db.select() const resource = await db.select()
.from(resources) .from(resources)
@ -42,16 +50,15 @@ export async function getResource(req: Request, res: Response, next: NextFunctio
); );
} }
return res.status(HttpCode.OK).send( return response(res, {
response(res, {
data: resource[0], data: resource[0],
success: true, success: true,
error: false, error: false,
message: "Resource retrieved successfully", message: "Resource retrieved successfully",
status: HttpCode.OK, status: HttpCode.OK,
}) });
);
} catch (error) { } catch (error) {
next(error); logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
} }
} }

View file

@ -1,11 +1,13 @@
import { Request, Response, NextFunction } from 'express'; import { Request, Response, NextFunction } from 'express';
import { z } from 'zod'; import { z } from 'zod';
import { db } from '@server/db'; import { db } from '@server/db';
import { resources, sites } from '@server/db/schema'; import { resources, sites, userResources, roleResources } from '@server/db/schema';
import response from "@server/utils/response"; import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode'; import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors'; import createHttpError from 'http-errors';
import { sql, eq } from 'drizzle-orm'; import { sql, eq, and, or, inArray } from 'drizzle-orm';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
const listResourcesParamsSchema = z.object({ const listResourcesParamsSchema = z.object({
siteId: z.coerce.number().int().positive().optional(), siteId: z.coerce.number().int().positive().optional(),
@ -19,30 +21,49 @@ const listResourcesSchema = z.object({
offset: z.coerce.number().int().nonnegative().default(0), offset: z.coerce.number().int().nonnegative().default(0),
}); });
export async function listResources(req: Request, res: Response, next: NextFunction): Promise<any> { interface RequestWithOrgAndRole extends Request {
userOrgRoleId?: number;
orgId?: number;
}
export async function listResources(req: RequestWithOrgAndRole, res: Response, next: NextFunction): Promise<any> {
try { try {
const parsedQuery = listResourcesSchema.safeParse(req.query); const parsedQuery = listResourcesSchema.safeParse(req.query);
if (!parsedQuery.success) { if (!parsedQuery.success) {
return next( return next(createHttpError(HttpCode.BAD_REQUEST, parsedQuery.error.errors.map(e => e.message).join(', ')));
createHttpError(
HttpCode.BAD_REQUEST,
parsedQuery.error.errors.map(e => e.message).join(', ')
)
);
} }
const { limit, offset } = parsedQuery.data; const { limit, offset } = parsedQuery.data;
const parsedParams = listResourcesParamsSchema.safeParse(req.params); const parsedParams = listResourcesParamsSchema.safeParse(req.params);
if (!parsedParams.success) { if (!parsedParams.success) {
return next( return next(createHttpError(HttpCode.BAD_REQUEST, parsedParams.error.errors.map(e => e.message).join(', ')));
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map(e => e.message).join(', ')
)
);
} }
const { siteId, orgId } = parsedParams.data; const { siteId, orgId } = parsedParams.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.listResources, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
if (orgId && orgId !== req.orgId) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization'));
}
// Get the list of resources the user has access to
const accessibleResources = await db
.select({ resourceId: sql<string>`COALESCE(${userResources.resourceId}, ${roleResources.resourceId})` })
.from(userResources)
.fullJoin(roleResources, eq(userResources.resourceId, roleResources.resourceId))
.where(
or(
eq(userResources.userId, req.user!.id),
eq(roleResources.roleId, req.userOrgRoleId!)
)
);
const accessibleResourceIds = accessibleResources.map(resource => resource.resourceId);
let baseQuery: any = db let baseQuery: any = db
.select({ .select({
resourceId: resources.resourceId, resourceId: resources.resourceId,
@ -51,24 +72,28 @@ export async function listResources(req: Request, res: Response, next: NextFunct
siteName: sites.name, siteName: sites.name,
}) })
.from(resources) .from(resources)
.leftJoin(sites, eq(resources.siteId, sites.siteId)); .leftJoin(sites, eq(resources.siteId, sites.siteId))
.where(inArray(resources.resourceId, accessibleResourceIds));
let countQuery: any = db.select({ count: sql<number>`cast(count(*) as integer)` }).from(resources); let countQuery: any = db
.select({ count: sql<number>`cast(count(*) as integer)` })
.from(resources)
.where(inArray(resources.resourceId, accessibleResourceIds));
if (siteId) { if (siteId) {
baseQuery = baseQuery.where(eq(resources.siteId, siteId)); baseQuery = baseQuery.where(eq(resources.siteId, siteId));
countQuery = countQuery.where(eq(resources.siteId, siteId)); countQuery = countQuery.where(eq(resources.siteId, siteId));
} else if (orgId) { } else {
baseQuery = baseQuery.where(eq(resources.orgId, orgId)); // If orgId is provided, it's already checked to match req.orgId
countQuery = countQuery.where(eq(resources.orgId, orgId)); baseQuery = baseQuery.where(eq(resources.orgId, req.orgId!));
countQuery = countQuery.where(eq(resources.orgId, req.orgId!));
} }
const resourcesList = await baseQuery.limit(limit).offset(offset); const resourcesList = await baseQuery.limit(limit).offset(offset);
const totalCountResult = await countQuery; const totalCountResult = await countQuery;
const totalCount = totalCountResult[0].count; const totalCount = totalCountResult[0].count;
return res.status(HttpCode.OK).send( return response(res, {
response(res, {
data: { data: {
resources: resourcesList, resources: resourcesList,
pagination: { pagination: {
@ -81,9 +106,9 @@ export async function listResources(req: Request, res: Response, next: NextFunct
error: false, error: false,
message: "Resources retrieved successfully", message: "Resources retrieved successfully",
status: HttpCode.OK, status: HttpCode.OK,
}) });
);
} catch (error) { } catch (error) {
next(error); logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
} }
} }

View file

@ -6,6 +6,8 @@ import { eq } from 'drizzle-orm';
import response from "@server/utils/response"; import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode'; import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors'; import createHttpError from 'http-errors';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
// Define Zod schema for request parameters validation // Define Zod schema for request parameters validation
const updateResourceParamsSchema = z.object({ const updateResourceParamsSchema = z.object({
@ -47,6 +49,12 @@ export async function updateResource(req: Request, res: Response, next: NextFunc
const { resourceId } = parsedParams.data; const { resourceId } = parsedParams.data;
const updateData = parsedBody.data; const updateData = parsedBody.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.updateResource, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
// Update the resource in the database // Update the resource in the database
const updatedResource = await db.update(resources) const updatedResource = await db.update(resources)
.set(updateData) .set(updateData)
@ -62,16 +70,15 @@ export async function updateResource(req: Request, res: Response, next: NextFunc
); );
} }
return res.status(HttpCode.OK).send( return response(res, {
response(res, {
data: updatedResource[0], data: updatedResource[0],
success: true, success: true,
error: false, error: false,
message: "Resource updated successfully", message: "Resource updated successfully",
status: HttpCode.OK, status: HttpCode.OK,
}) });
);
} catch (error) { } catch (error) {
next(error); logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
} }
} }

View file

@ -6,6 +6,8 @@ import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode'; import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors'; import createHttpError from 'http-errors';
import fetch from 'node-fetch'; import fetch from 'node-fetch';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
const API_BASE_URL = "http://localhost:3000"; const API_BASE_URL = "http://localhost:3000";
@ -49,6 +51,12 @@ export async function createSite(req: Request, res: Response, next: NextFunction
const { orgId } = parsedParams.data; const { orgId } = parsedParams.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.createSite, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
// Create new site in the database // Create new site in the database
const newSite = await db.insert(sites).values({ const newSite = await db.insert(sites).values({
orgId, orgId,
@ -58,17 +66,16 @@ export async function createSite(req: Request, res: Response, next: NextFunction
subnet, subnet,
}).returning(); }).returning();
return res.status(HttpCode.CREATED).send( return response(res, {
response(res, {
data: newSite[0], data: newSite[0],
success: true, success: true,
error: false, error: false,
message: "Site created successfully", message: "Site created successfully",
status: HttpCode.CREATED, status: HttpCode.CREATED,
}) });
);
} catch (error) { } catch (error) {
next(error); logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
} }
} }
@ -88,10 +95,9 @@ async function addPeer(peer: string) {
} }
const data: any = await response.json(); const data: any = await response.json();
console.log('Peer added successfully:', data.status); logger.info('Peer added successfully:', data.status);
return data; return data;
} catch (error: any) { } catch (error: any) {
console.error('Error adding peer:', error.message);
throw error; throw error;
} }
} }

View file

@ -6,6 +6,8 @@ import { eq } from 'drizzle-orm';
import response from "@server/utils/response"; import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode'; import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors'; import createHttpError from 'http-errors';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
const API_BASE_URL = "http://localhost:3000"; const API_BASE_URL = "http://localhost:3000";
@ -30,6 +32,12 @@ export async function deleteSite(req: Request, res: Response, next: NextFunction
const { siteId } = parsedParams.data; const { siteId } = parsedParams.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.deleteSite, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
// Delete the site from the database // Delete the site from the database
const deletedSite = await db.delete(sites) const deletedSite = await db.delete(sites)
.where(eq(sites.siteId, siteId)) .where(eq(sites.siteId, siteId))
@ -44,17 +52,16 @@ export async function deleteSite(req: Request, res: Response, next: NextFunction
); );
} }
return res.status(HttpCode.OK).send( return response(res, {
response(res, {
data: null, data: null,
success: true, success: true,
error: false, error: false,
message: "Site deleted successfully", message: "Site deleted successfully",
status: HttpCode.OK, status: HttpCode.OK,
}) });
);
} catch (error) { } catch (error) {
next(error); logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
} }
} }

View file

@ -6,6 +6,8 @@ import { eq } from 'drizzle-orm';
import response from "@server/utils/response"; import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode'; import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors'; import createHttpError from 'http-errors';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
// Define Zod schema for request parameters validation // Define Zod schema for request parameters validation
const getSiteSchema = z.object({ const getSiteSchema = z.object({
@ -27,6 +29,12 @@ export async function getSite(req: Request, res: Response, next: NextFunction):
const { siteId } = parsedParams.data; const { siteId } = parsedParams.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.updateSite, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
// Fetch the site from the database // Fetch the site from the database
const site = await db.select() const site = await db.select()
.from(sites) .from(sites)
@ -42,16 +50,15 @@ export async function getSite(req: Request, res: Response, next: NextFunction):
); );
} }
return res.status(HttpCode.OK).send( return response(res, {
response(res, {
data: site[0], data: site[0],
success: true, success: true,
error: false, error: false,
message: "Site retrieved successfully", message: "Site retrieved successfully",
status: HttpCode.OK, status: HttpCode.OK,
}) });
);
} catch (error) { } catch (error) {
next(error); logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
} }
} }

View file

@ -1,11 +1,13 @@
import { Request, Response, NextFunction } from 'express'; import { Request, Response, NextFunction } from 'express';
import { z } from 'zod'; import { z } from 'zod';
import { db } from '@server/db'; import { db } from '@server/db';
import { sites, orgs, exitNodes } from '@server/db/schema'; import { sites, orgs, exitNodes, userSites, roleSites } from '@server/db/schema';
import response from "@server/utils/response"; import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode'; import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors'; import createHttpError from 'http-errors';
import { sql, eq } from 'drizzle-orm'; import { sql, eq, and, or, inArray } from 'drizzle-orm';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
const listSitesParamsSchema = z.object({ const listSitesParamsSchema = z.object({
orgId: z.string().optional().transform(Number).pipe(z.number().int().positive()), orgId: z.string().optional().transform(Number).pipe(z.number().int().positive()),
@ -20,27 +22,39 @@ export async function listSites(req: Request, res: Response, next: NextFunction)
try { try {
const parsedQuery = listSitesSchema.safeParse(req.query); const parsedQuery = listSitesSchema.safeParse(req.query);
if (!parsedQuery.success) { if (!parsedQuery.success) {
return next( return next(createHttpError(HttpCode.BAD_REQUEST, parsedQuery.error.errors.map(e => e.message).join(', ')));
createHttpError(
HttpCode.BAD_REQUEST,
parsedQuery.error.errors.map(e => e.message).join(', ')
)
);
} }
const { limit, offset } = parsedQuery.data; const { limit, offset } = parsedQuery.data;
const parsedParams = listSitesParamsSchema.safeParse(req.params); const parsedParams = listSitesParamsSchema.safeParse(req.params);
if (!parsedParams.success) { if (!parsedParams.success) {
return next( return next(createHttpError(HttpCode.BAD_REQUEST, parsedParams.error.errors.map(e => e.message).join(', ')));
createHttpError( }
HttpCode.BAD_REQUEST, const { orgId } = parsedParams.data;
parsedParams.error.errors.map(e => e.message).join(', ')
) // Check if the user has permission to list sites
); const hasPermission = await checkUserActionPermission(ActionsEnum.listSites, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
} }
const { orgId } = parsedParams.data; if (orgId && orgId !== req.userOrgId) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization'));
}
const accessibleSites = await db
.select({ siteId: sql<number>`COALESCE(${userSites.siteId}, ${roleSites.siteId})` })
.from(userSites)
.fullJoin(roleSites, eq(userSites.siteId, roleSites.siteId))
.where(
or(
eq(userSites.userId, req.user!.id),
eq(roleSites.roleId, req.userOrgRoleId!)
)
);
const accessibleSiteIds = accessibleSites.map(site => site.siteId);
let baseQuery: any = db let baseQuery: any = db
.select({ .select({
@ -56,9 +70,12 @@ export async function listSites(req: Request, res: Response, next: NextFunction)
}) })
.from(sites) .from(sites)
.leftJoin(orgs, eq(sites.orgId, orgs.orgId)) .leftJoin(orgs, eq(sites.orgId, orgs.orgId))
.leftJoin(exitNodes, eq(sites.exitNode, exitNodes.exitNodeId)); .where(inArray(sites.siteId, accessibleSiteIds));
let countQuery: any = db.select({ count: sql<number>`cast(count(*) as integer)` }).from(sites); let countQuery: any = db
.select({ count: sql<number>`cast(count(*) as integer)` })
.from(sites)
.where(inArray(sites.siteId, accessibleSiteIds));
if (orgId) { if (orgId) {
baseQuery = baseQuery.where(eq(sites.orgId, orgId)); baseQuery = baseQuery.where(eq(sites.orgId, orgId));
@ -69,8 +86,7 @@ export async function listSites(req: Request, res: Response, next: NextFunction)
const totalCountResult = await countQuery; const totalCountResult = await countQuery;
const totalCount = totalCountResult[0].count; const totalCount = totalCountResult[0].count;
return res.status(HttpCode.OK).send( return response(res, {
response(res, {
data: { data: {
sites: sitesList, sites: sitesList,
pagination: { pagination: {
@ -83,9 +99,9 @@ export async function listSites(req: Request, res: Response, next: NextFunction)
error: false, error: false,
message: "Sites retrieved successfully", message: "Sites retrieved successfully",
status: HttpCode.OK, status: HttpCode.OK,
}) });
);
} catch (error) { } catch (error) {
next(error); logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
} }
} }

View file

@ -6,6 +6,8 @@ import { eq } from 'drizzle-orm';
import response from "@server/utils/response"; import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode'; import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors'; import createHttpError from 'http-errors';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
// Define Zod schema for request parameters validation // Define Zod schema for request parameters validation
const updateSiteParamsSchema = z.object({ const updateSiteParamsSchema = z.object({
@ -52,6 +54,12 @@ export async function updateSite(req: Request, res: Response, next: NextFunction
const { siteId } = parsedParams.data; const { siteId } = parsedParams.data;
const updateData = parsedBody.data; const updateData = parsedBody.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.updateSite, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
// Update the site in the database // Update the site in the database
const updatedSite = await db.update(sites) const updatedSite = await db.update(sites)
.set(updateData) .set(updateData)
@ -67,16 +75,15 @@ export async function updateSite(req: Request, res: Response, next: NextFunction
); );
} }
return res.status(HttpCode.OK).send( return response(res, {
response(res, {
data: updatedSite[0], data: updatedSite[0],
success: true, success: true,
error: false, error: false,
message: "Site updated successfully", message: "Site updated successfully",
status: HttpCode.OK, status: HttpCode.OK,
}) });
);
} catch (error) { } catch (error) {
next(error); logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
} }
} }

View file

@ -5,6 +5,8 @@ import { targets } from '@server/db/schema';
import response from "@server/utils/response"; import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode'; import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors'; import createHttpError from 'http-errors';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
const createTargetParamsSchema = z.object({ const createTargetParamsSchema = z.object({
resourceId: z.string().uuid(), resourceId: z.string().uuid(),
@ -44,21 +46,26 @@ export async function createTarget(req: Request, res: Response, next: NextFuncti
const { resourceId } = parsedParams.data; const { resourceId } = parsedParams.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.createTarget, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
const newTarget = await db.insert(targets).values({ const newTarget = await db.insert(targets).values({
resourceId, resourceId,
...targetData ...targetData
}).returning(); }).returning();
return res.status(HttpCode.CREATED).send( return response(res, {
response(res, {
data: newTarget[0], data: newTarget[0],
success: true, success: true,
error: false, error: false,
message: "Target created successfully", message: "Target created successfully",
status: HttpCode.CREATED, status: HttpCode.CREATED,
}) });
);
} catch (error) { } catch (error) {
next(error); logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
} }
} }

View file

@ -6,6 +6,8 @@ import { eq } from 'drizzle-orm';
import response from "@server/utils/response"; import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode'; import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors'; import createHttpError from 'http-errors';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
const deleteTargetSchema = z.object({ const deleteTargetSchema = z.object({
targetId: z.string().transform(Number).pipe(z.number().int().positive()) targetId: z.string().transform(Number).pipe(z.number().int().positive())
@ -25,6 +27,12 @@ export async function deleteTarget(req: Request, res: Response, next: NextFuncti
const { targetId } = parsedParams.data; const { targetId } = parsedParams.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.deleteTarget, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
const deletedTarget = await db.delete(targets) const deletedTarget = await db.delete(targets)
.where(eq(targets.targetId, targetId)) .where(eq(targets.targetId, targetId))
.returning(); .returning();
@ -38,16 +46,15 @@ export async function deleteTarget(req: Request, res: Response, next: NextFuncti
); );
} }
return res.status(HttpCode.OK).send( return response(res, {
response(res, {
data: null, data: null,
success: true, success: true,
error: false, error: false,
message: "Target deleted successfully", message: "Target deleted successfully",
status: HttpCode.OK, status: HttpCode.OK,
}) });
);
} catch (error) { } catch (error) {
next(error); logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
} }
} }

View file

@ -6,6 +6,8 @@ import { eq } from 'drizzle-orm';
import response from "@server/utils/response"; import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode'; import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors'; import createHttpError from 'http-errors';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
const getTargetSchema = z.object({ const getTargetSchema = z.object({
targetId: z.string().transform(Number).pipe(z.number().int().positive()) targetId: z.string().transform(Number).pipe(z.number().int().positive())
@ -25,6 +27,12 @@ export async function getTarget(req: Request, res: Response, next: NextFunction)
const { targetId } = parsedParams.data; const { targetId } = parsedParams.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.getTarget, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
const target = await db.select() const target = await db.select()
.from(targets) .from(targets)
.where(eq(targets.targetId, targetId)) .where(eq(targets.targetId, targetId))
@ -39,16 +47,15 @@ export async function getTarget(req: Request, res: Response, next: NextFunction)
); );
} }
return res.status(HttpCode.OK).send( return response(res, {
response(res, {
data: target[0], data: target[0],
success: true, success: true,
error: false, error: false,
message: "Target retrieved successfully", message: "Target retrieved successfully",
status: HttpCode.OK, status: HttpCode.OK,
}) });
);
} catch (error) { } catch (error) {
next(error); logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
} }
} }

View file

@ -6,6 +6,8 @@ import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode'; import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors'; import createHttpError from 'http-errors';
import { sql, eq } from 'drizzle-orm'; import { sql, eq } from 'drizzle-orm';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
const listTargetsParamsSchema = z.object({ const listTargetsParamsSchema = z.object({
resourceId: z.string().optional() resourceId: z.string().optional()
@ -42,6 +44,12 @@ export async function listTargets(req: Request, res: Response, next: NextFunctio
const { resourceId } = parsedParams.data; const { resourceId } = parsedParams.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.listTargets, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
let baseQuery: any = db let baseQuery: any = db
.select({ .select({
targetId: targets.targetId, targetId: targets.targetId,
@ -66,8 +74,7 @@ export async function listTargets(req: Request, res: Response, next: NextFunctio
const totalCountResult = await countQuery; const totalCountResult = await countQuery;
const totalCount = totalCountResult[0].count; const totalCount = totalCountResult[0].count;
return res.status(HttpCode.OK).send( return response(res, {
response(res, {
data: { data: {
targets: targetsList, targets: targetsList,
pagination: { pagination: {
@ -80,9 +87,9 @@ export async function listTargets(req: Request, res: Response, next: NextFunctio
error: false, error: false,
message: "Targets retrieved successfully", message: "Targets retrieved successfully",
status: HttpCode.OK, status: HttpCode.OK,
}) });
);
} catch (error) { } catch (error) {
next(error); logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
} }
} }

View file

@ -6,6 +6,8 @@ import { eq } from 'drizzle-orm';
import response from "@server/utils/response"; import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode'; import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors'; import createHttpError from 'http-errors';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
const updateTargetParamsSchema = z.object({ const updateTargetParamsSchema = z.object({
targetId: z.string().transform(Number).pipe(z.number().int().positive()) targetId: z.string().transform(Number).pipe(z.number().int().positive())
@ -46,6 +48,12 @@ export async function updateTarget(req: Request, res: Response, next: NextFuncti
const { targetId } = parsedParams.data; const { targetId } = parsedParams.data;
const updateData = parsedBody.data; const updateData = parsedBody.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.updateTarget, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
const updatedTarget = await db.update(targets) const updatedTarget = await db.update(targets)
.set(updateData) .set(updateData)
.where(eq(targets.targetId, targetId)) .where(eq(targets.targetId, targetId))
@ -60,16 +68,15 @@ export async function updateTarget(req: Request, res: Response, next: NextFuncti
); );
} }
return res.status(HttpCode.OK).send( return response(res, {
response(res, {
data: updatedTarget[0], data: updatedTarget[0],
success: true, success: true,
error: false, error: false,
message: "Target updated successfully", message: "Target updated successfully",
status: HttpCode.OK, status: HttpCode.OK,
}) });
);
} catch (error) { } catch (error) {
next(error); logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
} }
} }

View file

@ -6,6 +6,8 @@ import { eq } from 'drizzle-orm';
import response from "@server/utils/response"; import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode'; import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors'; import createHttpError from 'http-errors';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
const deleteUserSchema = z.object({ const deleteUserSchema = z.object({
userId: z.string().uuid() userId: z.string().uuid()
@ -25,6 +27,12 @@ export async function deleteUser(req: Request, res: Response, next: NextFunction
const { userId } = parsedParams.data; const { userId } = parsedParams.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.deleteUser, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
const deletedUser = await db.delete(users) const deletedUser = await db.delete(users)
.where(eq(users.id, userId)) .where(eq(users.id, userId))
.returning(); .returning();
@ -38,16 +46,15 @@ export async function deleteUser(req: Request, res: Response, next: NextFunction
); );
} }
return res.status(HttpCode.OK).send( return response(res, {
response(res, {
data: null, data: null,
success: true, success: true,
error: false, error: false,
message: "User deleted successfully", message: "User deleted successfully",
status: HttpCode.OK, status: HttpCode.OK,
}) });
);
} catch (error) { } catch (error) {
next(error); logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
} }
} }

View file

@ -6,6 +6,8 @@ import { eq } from 'drizzle-orm';
import response from "@server/utils/response"; import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode'; import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors'; import createHttpError from 'http-errors';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
const getUserSchema = z.object({ const getUserSchema = z.object({
userId: z.string().uuid() userId: z.string().uuid()
@ -25,6 +27,12 @@ export async function getUser(req: Request, res: Response, next: NextFunction):
const { userId } = parsedParams.data; const { userId } = parsedParams.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.getUser, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
const user = await db.select() const user = await db.select()
.from(users) .from(users)
.where(eq(users.id, userId)) .where(eq(users.id, userId))
@ -42,16 +50,15 @@ export async function getUser(req: Request, res: Response, next: NextFunction):
// Remove passwordHash from the response // Remove passwordHash from the response
const { passwordHash: _, ...userWithoutPassword } = user[0]; const { passwordHash: _, ...userWithoutPassword } = user[0];
return res.status(HttpCode.OK).send( return response(res, {
response(res, {
data: userWithoutPassword, data: userWithoutPassword,
success: true, success: true,
error: false, error: false,
message: "User retrieved successfully", message: "User retrieved successfully",
status: HttpCode.OK, status: HttpCode.OK,
}) });
);
} catch (error) { } catch (error) {
next(error); logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
} }
} }

View file

@ -6,6 +6,8 @@ import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode'; import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors'; import createHttpError from 'http-errors';
import { sql } from 'drizzle-orm'; import { sql } from 'drizzle-orm';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
const listUsersSchema = z.object({ const listUsersSchema = z.object({
limit: z.string().optional().transform(Number).pipe(z.number().int().positive().default(10)), limit: z.string().optional().transform(Number).pipe(z.number().int().positive().default(10)),
@ -26,6 +28,12 @@ export async function listUsers(req: Request, res: Response, next: NextFunction)
const { limit, offset } = parsedQuery.data; const { limit, offset } = parsedQuery.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.listUsers, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
const usersList = await db.select() const usersList = await db.select()
.from(users) .from(users)
.limit(limit) .limit(limit)
@ -39,8 +47,7 @@ export async function listUsers(req: Request, res: Response, next: NextFunction)
// Remove passwordHash from each user object // Remove passwordHash from each user object
const usersWithoutPassword = usersList.map(({ passwordHash, ...userWithoutPassword }) => userWithoutPassword); const usersWithoutPassword = usersList.map(({ passwordHash, ...userWithoutPassword }) => userWithoutPassword);
return res.status(HttpCode.OK).send( return response(res, {
response(res, {
data: { data: {
users: usersWithoutPassword, users: usersWithoutPassword,
pagination: { pagination: {
@ -53,9 +60,9 @@ export async function listUsers(req: Request, res: Response, next: NextFunction)
error: false, error: false,
message: "Users retrieved successfully", message: "Users retrieved successfully",
status: HttpCode.OK, status: HttpCode.OK,
}) });
);
} catch (error) { } catch (error) {
next(error); logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
} }
} }

View file

@ -5,5 +5,5 @@ import { Session } from "lucia";
export interface AuthenticatedRequest extends Request { export interface AuthenticatedRequest extends Request {
user: User; user: User;
session: Session; session: Session;
userOrgRole?: string; userOrgRoleId?: number;
} }

View file

@ -2,11 +2,7 @@ import LoginForm from "@app/components/LoginForm";
import { verifySession } from "@app/lib/verifySession"; import { verifySession } from "@app/lib/verifySession";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
export async function Page({ export async function Page() {
searchParams,
}: {
searchParams: { [key: string]: string | string[] | undefined };
}) {
const { user } = await verifySession(); const { user } = await verifySession();
if (user) { if (user) {
@ -15,9 +11,10 @@ export async function Page({
return ( return (
<> <>
<LoginForm redirect={searchParams.redirect as string} /> <LoginForm redirect={"test"} />
</> </>
); );
} }
export default Page; export default Page;

View file

@ -1,6 +1,7 @@
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
@layer base { @layer base {
:root { :root {
--background: 37 100% 100%; --background: 37 100% 100%;
@ -24,6 +25,7 @@
--ring: 37 8% 51%; --ring: 37 8% 51%;
--radius: 0rem; --radius: 0rem;
} }
.dark { .dark {
--background: 37 50% 10%; --background: 37 50% 10%;
--foreground: 37 5% 100%; --foreground: 37 5% 100%;
@ -47,10 +49,12 @@
--radius: 0rem; --radius: 0rem;
} }
} }
@layer base { @layer base {
* { * {
@apply border-border; @apply border-border;
} }
body { body {
@apply bg-background text-foreground; @apply bg-background text-foreground;
} }

View file

@ -1,6 +1,10 @@
{ {
"compilerOptions": { "compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"], "lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
"strict": true, "strict": true,
@ -14,9 +18,15 @@
"incremental": true, "incremental": true,
"baseUrl": "src", "baseUrl": "src",
"paths": { "paths": {
"@server/*": ["../server/*"], "@server/*": [
"@app/*": ["*"], "../server/*"
"@/*": ["./*"] ],
"@app/*": [
"*"
],
"@/*": [
"./*"
]
}, },
"plugins": [ "plugins": [
{ {
@ -24,6 +34,13 @@
} }
] ]
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "include": [
"exclude": ["node_modules"] "next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
]
} }