refactor /auth/me into /me
This commit is contained in:
1 parent
4b92b2a8df
commit
1a2d11e6de
14 files changed
+226
-110
No files matched your search
@@ -32,7 +32,7 @@ export const AuthVerifier = ({ children }: { children: ReactNode }) => {
|
||||
}
|
||||
const verify = async () => {
|
||||
try {
|
||||
const res = await get<ApiResponse<IUser>>("/auth/me");
|
||||
const res = await get<ApiResponse<IUser>>("/me");
|
||||
const user: IUser = res.data.data;
|
||||
dispatch(setAuthenticated(true));
|
||||
dispatch(setUser(user));
|
||||
|
||||
@@ -21,7 +21,7 @@ const BillingPage = () => {
|
||||
{}
|
||||
);
|
||||
const { response: meResponse } = useGet<ApiResponse<IUser>>(
|
||||
"/auth/me",
|
||||
"/me",
|
||||
{},
|
||||
{ refreshInterval: 10000 }
|
||||
);
|
||||
|
||||
@@ -2,12 +2,12 @@ import Stack from "@mui/material/Stack";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import Divider from "@mui/material/Divider";
|
||||
import Box from "@mui/material/Box";
|
||||
import { StatBox, Table } from "@/components/design-elements";
|
||||
import { Table } from "@/components/design-elements";
|
||||
import type { Header } from "@/components/design-elements/Table";
|
||||
import { useGet } from "@/hooks/UseApi";
|
||||
import { useAppSelector } from "@/hooks/AppHooks";
|
||||
import type { Entitlements } from "@/types/entitlements";
|
||||
import { config } from "@/config/index";
|
||||
|
||||
type PermissionsResponse = {
|
||||
message: string;
|
||||
@@ -32,18 +32,18 @@ export default function DiagnosticsAccessTab() {
|
||||
const { response: permsRes } = useGet<PermissionsResponse>(
|
||||
"/me/permissions",
|
||||
{},
|
||||
{ refreshInterval: 10000 }
|
||||
{ refreshInterval: config.GLOBAL_REFRESH }
|
||||
);
|
||||
const { response: entsRes } = useGet<{ entitlements: Entitlements }>(
|
||||
const { response: entsRes } = useGet<{ message: string; data: Entitlements }>(
|
||||
"/me/entitlements",
|
||||
{},
|
||||
{ refreshInterval: 30000 }
|
||||
{ refreshInterval: config.GLOBAL_REFRESH }
|
||||
);
|
||||
|
||||
const orgPerms = permsRes?.data?.org || [];
|
||||
const teamPerms = permsRes?.data?.team || [];
|
||||
|
||||
const entitlementsObj = entsRes?.entitlements as Entitlements | undefined;
|
||||
const entitlementsObj = entsRes?.data as Entitlements | undefined;
|
||||
const entitlements: EntitlementRow[] = entitlementsObj
|
||||
? (Object.entries(entitlementsObj) as [keyof Entitlements, any][]).map(
|
||||
([key, value]) => ({ id: String(key), key: String(key), value })
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import type { IMeService } from "@/services/business/MeService.js";
|
||||
import ApiError from "@/utils/ApiError.js";
|
||||
|
||||
export interface IMeController {
|
||||
me: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
||||
getEntitlements: (
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => Promise<void>;
|
||||
getPermissions: (
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => Promise<void>;
|
||||
}
|
||||
|
||||
class MeController implements IMeController {
|
||||
private meService: IMeService;
|
||||
|
||||
constructor(meService: IMeService) {
|
||||
this.meService = meService;
|
||||
}
|
||||
|
||||
me = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const user = req.user;
|
||||
if (!user) {
|
||||
throw new ApiError("Unauthorized", 401);
|
||||
}
|
||||
const returnableUser = await this.meService.me(user.sub);
|
||||
res
|
||||
.status(200)
|
||||
.json({ message: "User retrieved successfully", data: returnableUser });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
getEntitlements = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const orgId = req.user?.orgId;
|
||||
if (!orgId) throw new ApiError("Missing org context", 400);
|
||||
const entitlements = await this.meService.getEntitlements(orgId);
|
||||
res.status(200).json({ message: "OK", data: entitlements });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
getPermissions = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const data = await this.meService.getPermissions((req as any).user);
|
||||
res.status(200).json({ message: "OK", data });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default MeController;
|
||||
@@ -16,3 +16,5 @@ export { default as IncidentsController } from "./IncidentsController.js";
|
||||
export { default as SettingsController } from "./SettingsController.js";
|
||||
export { default as BillingController } from "./BillingController.js";
|
||||
export { default as StripeController } from "./StripeController.js";
|
||||
export { default as MeController } from "./MeController.js";
|
||||
export type { IMeController } from "./MeController.js";
|
||||
@@ -36,6 +36,8 @@ const createApp = async () => {
|
||||
})
|
||||
);
|
||||
const routes = initRoutes(controllers, app);
|
||||
// expose services for route-level access (e.g., in me routes)
|
||||
app.set("services", services);
|
||||
// Swagger UI at /api-docs
|
||||
try {
|
||||
// Load OpenAPI doc from project root within server folder
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
StripeController,
|
||||
BillingController,
|
||||
} from "@/controllers/index.js";
|
||||
import MeController from "@/controllers/MeController.js";
|
||||
|
||||
export const initControllers = (services: any) => {
|
||||
const controllers: Record<string, any> = {};
|
||||
@@ -80,6 +81,7 @@ export const initControllers = (services: any) => {
|
||||
controllers.billingController = new BillingController(
|
||||
services.billingService
|
||||
);
|
||||
controllers.meController = new MeController(services.meService);
|
||||
|
||||
return controllers;
|
||||
};
|
||||
@@ -49,7 +49,7 @@ export const initRoutes = (controllers: any, app: Express) => {
|
||||
const profileRoutes = new ProfileRoutes(controllers.profileController);
|
||||
const incidentsRoutes = new IncidentsRoutes(controllers.incidentsController);
|
||||
const settingsRoutes = new SettingsRoutes(controllers.settingsController);
|
||||
const meRoutes = new MeRoutes();
|
||||
const meRoutes = new MeRoutes(controllers.meController);
|
||||
const stripeRoutes = new StripeRoutes(controllers.stripeController);
|
||||
const billingRoutes = new BillingRoutes(controllers.billingController);
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
BillingService,
|
||||
} from "@/services/index.js";
|
||||
import { EntitlementsFactory } from "@/services/system/EntitlementsService.js";
|
||||
import { MeService } from "@/services/index.js";
|
||||
|
||||
export const initServices = async () => {
|
||||
const checkService = new CheckService();
|
||||
@@ -67,6 +68,7 @@ export const initServices = async () => {
|
||||
const jobQueue = await JobQueue.create(jobGenerator);
|
||||
const entitlementsProvider = EntitlementsFactory.create();
|
||||
const authService = new AuthService(jobQueue, entitlementsProvider);
|
||||
const meService = new MeService(entitlementsProvider);
|
||||
const monitorService = new MonitorService(jobQueue);
|
||||
const queueService = new QueueService(jobQueue);
|
||||
const teamService = new TeamService(jobQueue);
|
||||
@@ -92,6 +94,7 @@ export const initServices = async () => {
|
||||
jobGenerator,
|
||||
jobQueue,
|
||||
authService,
|
||||
meService,
|
||||
monitorService,
|
||||
queueService,
|
||||
teamService,
|
||||
|
||||
@@ -35,7 +35,7 @@ class AuthRoutes {
|
||||
this.controller.login
|
||||
);
|
||||
this.router.post("/logout", this.controller.logout);
|
||||
this.router.get("/me", verifyToken, this.controller.me);
|
||||
// this.router.get("/me", verifyToken, this.controller.me);
|
||||
this.router.post("/cleanup", this.controller.cleanup);
|
||||
this.router.post("/cleanup-monitors", this.controller.cleanMonitors);
|
||||
};
|
||||
|
||||
+12
-19
@@ -1,39 +1,32 @@
|
||||
import { Router } from "express";
|
||||
import { verifyToken } from "@/middleware/VerifyToken.js";
|
||||
import { addUserContext } from "@/middleware/AddUserContext.js";
|
||||
import { MeController } from "@/controllers/index.js";
|
||||
|
||||
class MeRoutes {
|
||||
private router;
|
||||
constructor() {
|
||||
private controller: MeController;
|
||||
constructor(meController: MeController) {
|
||||
this.router = Router();
|
||||
this.controller = meController;
|
||||
this.initRoutes();
|
||||
}
|
||||
|
||||
initRoutes = () => {
|
||||
this.router.get("/", verifyToken, this.controller.me);
|
||||
this.router.get(
|
||||
"/entitlements",
|
||||
verifyToken,
|
||||
addUserContext,
|
||||
(req, res) => {
|
||||
const entitlements = req.entitlements || {};
|
||||
return res.json({ entitlements });
|
||||
}
|
||||
this.controller.getEntitlements
|
||||
);
|
||||
|
||||
this.router.get("/permissions", verifyToken, addUserContext, (req, res) => {
|
||||
const orgPerms = req.user?.roles?.orgRole?.permissions || [];
|
||||
const teamPerms = req.user?.roles?.teamRole?.permissions || [];
|
||||
const currentTeamId = req.user?.currentTeamId;
|
||||
return res.json({
|
||||
message: "OK",
|
||||
data: {
|
||||
org: orgPerms,
|
||||
team: Array.isArray(teamPerms)
|
||||
? teamPerms.map((p: string) => ({ teamId: currentTeamId, permission: p }))
|
||||
: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
this.router.get(
|
||||
"/permissions",
|
||||
verifyToken,
|
||||
addUserContext,
|
||||
this.controller.getPermissions
|
||||
);
|
||||
};
|
||||
|
||||
getRouter() {
|
||||
|
||||
@@ -42,7 +42,6 @@ export type LoginData = {
|
||||
export type AuthResult = Partial<ITokenizedUser>;
|
||||
|
||||
export interface IAuthService {
|
||||
me(userId: string): Promise<IUserReturnable>;
|
||||
register(signupData: RegisterData): Promise<{
|
||||
tokenizedUser: ITokenizedUser;
|
||||
returnableUser: IUserReturnable;
|
||||
@@ -69,80 +68,15 @@ class AuthService implements IAuthService {
|
||||
public SERVICE_NAME: string;
|
||||
private jobQueue: IJobQueue;
|
||||
private entitlementsProvider: IEntitlementsProvider;
|
||||
constructor(jobQueue: IJobQueue, entitlementsProvider: IEntitlementsProvider) {
|
||||
constructor(
|
||||
jobQueue: IJobQueue,
|
||||
entitlementsProvider: IEntitlementsProvider
|
||||
) {
|
||||
this.SERVICE_NAME = SERVICE_NAME;
|
||||
this.jobQueue = jobQueue;
|
||||
this.entitlementsProvider = entitlementsProvider;
|
||||
}
|
||||
|
||||
me = async (userId: string) => {
|
||||
// Need to get teamIds
|
||||
const user = await User.findById(userId);
|
||||
if (!user) {
|
||||
throw new ApiError("User not found", 404);
|
||||
}
|
||||
|
||||
// Find OrgMembership
|
||||
const orgMembership = await OrgMembership.findOne({
|
||||
userId: user._id,
|
||||
}).lean();
|
||||
if (!orgMembership) {
|
||||
throw new ApiError("User is not part of any organization");
|
||||
}
|
||||
|
||||
// Find org and roles
|
||||
const org = await Org.findById(orgMembership.orgId).lean();
|
||||
|
||||
if (!org) {
|
||||
throw new ApiError("Organization not found");
|
||||
}
|
||||
|
||||
const orgRoles = await Role.findById(orgMembership.roleId).lean();
|
||||
|
||||
// Get teams
|
||||
const teamMembershipWithRoles = await TeamMembership.find({
|
||||
userId: user._id,
|
||||
})
|
||||
.populate<{ roleId: IRole }>("roleId")
|
||||
.lean();
|
||||
|
||||
const teamIds = teamMembershipWithRoles.map((tm) => tm.teamId.toString());
|
||||
|
||||
const teams = await this.getTeams(teamIds);
|
||||
const teamMap = new Map(teams.map((t) => [t._id.toString(), t]));
|
||||
const returnableTeams = teamMembershipWithRoles.map((tm) => {
|
||||
const team = teamMap.get(tm.teamId.toString());
|
||||
if (!team) {
|
||||
throw new ApiError("Team not found for membership");
|
||||
}
|
||||
return {
|
||||
id: team._id.toString(),
|
||||
name: team.name,
|
||||
permissions: tm.roleId?.permissions ?? [],
|
||||
};
|
||||
});
|
||||
|
||||
const entitlements: Entitlements = await this.entitlementsProvider.getForOrg(
|
||||
org._id.toString()
|
||||
);
|
||||
|
||||
const returnableUser: IUserReturnable = {
|
||||
id: user._id.toString(),
|
||||
email: user.email,
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
org: {
|
||||
name: org.name,
|
||||
planKey: org.planKey,
|
||||
permissions: orgRoles?.permissions || [],
|
||||
},
|
||||
teams: returnableTeams,
|
||||
entitlements,
|
||||
};
|
||||
|
||||
return returnableUser;
|
||||
};
|
||||
|
||||
async register(signupData: RegisterData) {
|
||||
const passwordHash = await hashPassword(signupData.password);
|
||||
|
||||
@@ -263,10 +197,8 @@ class AuthService implements IAuthService {
|
||||
permissions: roles[2]?.permissions || [],
|
||||
};
|
||||
|
||||
const provider = EntitlementsFactory.create();
|
||||
const entitlements: Entitlements = await provider.getForOrg(
|
||||
org._id.toString()
|
||||
);
|
||||
const entitlements: Entitlements =
|
||||
await this.entitlementsProvider.getForOrg(org._id.toString());
|
||||
|
||||
const returnableUser: IUserReturnable = {
|
||||
id: user._id.toString(),
|
||||
@@ -405,10 +337,8 @@ class AuthService implements IAuthService {
|
||||
const orgRole = await Role.findById(orgMembership.roleId);
|
||||
const orgPermissions = orgRole?.permissions || [];
|
||||
|
||||
const provider = EntitlementsFactory.create();
|
||||
const entitlements: Entitlements = await provider.getForOrg(
|
||||
org._id.toString()
|
||||
);
|
||||
const entitlements: Entitlements =
|
||||
await this.entitlementsProvider.getForOrg(org._id.toString());
|
||||
const returnableUser: IUserReturnable = {
|
||||
id: user._id.toString(),
|
||||
email: user.email,
|
||||
@@ -516,9 +446,8 @@ class AuthService implements IAuthService {
|
||||
orgId: orgMembership.orgId.toString(),
|
||||
};
|
||||
|
||||
const entitlements: Entitlements = await this.entitlementsProvider.getForOrg(
|
||||
org._id.toString()
|
||||
);
|
||||
const entitlements: Entitlements =
|
||||
await this.entitlementsProvider.getForOrg(org._id.toString());
|
||||
|
||||
const returnableUser: IUserReturnable = {
|
||||
id: user._id.toString(),
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
import {
|
||||
User,
|
||||
Org,
|
||||
OrgMembership,
|
||||
Team,
|
||||
ITeam,
|
||||
IUserReturnable,
|
||||
TeamMembership,
|
||||
Role,
|
||||
IRole,
|
||||
} from "@/db/models/index.js";
|
||||
import ApiError from "@/utils/ApiError.js";
|
||||
import type { IEntitlementsProvider } from "@/services/system/EntitlementsService.js";
|
||||
import type { Entitlements } from "@/types/entitlements.js";
|
||||
|
||||
export interface IMeService {
|
||||
me(userId: string): Promise<IUserReturnable>;
|
||||
getEntitlements(orgId: string): Promise<Entitlements>;
|
||||
getPermissions(user: any): Promise<{
|
||||
org: string[];
|
||||
team: { teamId: string; permission: string }[];
|
||||
}>;
|
||||
}
|
||||
|
||||
const SERVICE_NAME = "MeService";
|
||||
|
||||
class MeService implements IMeService {
|
||||
public SERVICE_NAME = SERVICE_NAME;
|
||||
private entitlementsProvider: IEntitlementsProvider;
|
||||
|
||||
constructor(entitlementsProvider: IEntitlementsProvider) {
|
||||
this.entitlementsProvider = entitlementsProvider;
|
||||
}
|
||||
|
||||
me = async (userId: string): Promise<IUserReturnable> => {
|
||||
// Need to get teamIds
|
||||
const user = await User.findById(userId);
|
||||
if (!user) {
|
||||
throw new ApiError("User not found", 404);
|
||||
}
|
||||
|
||||
// Find OrgMembership
|
||||
const orgMembership = await OrgMembership.findOne({
|
||||
userId: user._id,
|
||||
}).lean();
|
||||
if (!orgMembership) {
|
||||
throw new ApiError("User is not part of any organization");
|
||||
}
|
||||
|
||||
// Find org and roles
|
||||
const org = await Org.findById(orgMembership.orgId).lean();
|
||||
|
||||
if (!org) {
|
||||
throw new ApiError("Organization not found");
|
||||
}
|
||||
|
||||
const orgRoles = await Role.findById(orgMembership.roleId).lean();
|
||||
|
||||
// Get teams
|
||||
const teamMembershipWithRoles = await TeamMembership.find({
|
||||
userId: user._id,
|
||||
})
|
||||
.populate<{ roleId: IRole }>("roleId")
|
||||
.lean();
|
||||
|
||||
const teamIds = teamMembershipWithRoles.map((tm) => tm.teamId.toString());
|
||||
|
||||
const teams = await Team.find({ _id: { $in: teamIds } }).select(
|
||||
"_id, name"
|
||||
);
|
||||
|
||||
const teamMap = new Map(teams.map((t) => [t._id.toString(), t]));
|
||||
const returnableTeams = teamMembershipWithRoles.map((tm) => {
|
||||
const team = teamMap.get(tm.teamId.toString());
|
||||
if (!team) {
|
||||
throw new ApiError("Team not found for membership");
|
||||
}
|
||||
return {
|
||||
id: team._id.toString(),
|
||||
name: team.name,
|
||||
permissions: tm.roleId?.permissions ?? [],
|
||||
};
|
||||
});
|
||||
|
||||
const entitlements: Entitlements =
|
||||
await this.entitlementsProvider.getForOrg(org._id.toString());
|
||||
|
||||
const returnableUser: IUserReturnable = {
|
||||
id: user._id.toString(),
|
||||
email: user.email,
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
org: {
|
||||
name: org.name,
|
||||
planKey: org.planKey,
|
||||
permissions: orgRoles?.permissions || [],
|
||||
},
|
||||
teams: returnableTeams,
|
||||
entitlements,
|
||||
};
|
||||
|
||||
return returnableUser;
|
||||
};
|
||||
|
||||
getEntitlements = async (orgId: string): Promise<Entitlements> => {
|
||||
return this.entitlementsProvider.getForOrg(orgId);
|
||||
};
|
||||
|
||||
getPermissions = async (user: any) => {
|
||||
const orgPerms: string[] = user?.roles?.orgRole?.permissions || [];
|
||||
const teamPermsArr: string[] = user?.roles?.teamRole?.permissions || [];
|
||||
const currentTeamId: string = user?.currentTeamId || "";
|
||||
return {
|
||||
org: orgPerms,
|
||||
team: Array.isArray(teamPermsArr)
|
||||
? teamPermsArr.map((p) => ({ teamId: currentTeamId, permission: p }))
|
||||
: [],
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export default MeService;
|
||||
@@ -14,6 +14,7 @@ export { default as StatusPageService } from "@/services/business/StatusPageServ
|
||||
export { default as DiagnosticService } from "@/services/business/DiagnosticService.js";
|
||||
export { default as RecoveryService } from "@/services/business/RecoveryService.js";
|
||||
export { default as IncidentService } from "@/services/business/IncidentService.js";
|
||||
export { default as MeService } from "@/services/business/MeService.js";
|
||||
|
||||
export { default as NetworkService } from "@/services/infrastructure/NetworkService.js";
|
||||
export { default as NotificationService } from "@/services/infrastructure/NotificationService.js";
|
||||
|
||||
Reference in new issue
Block a user