Tech4biz-channel/Channel-Backend/src/services/auth.service.ts
2026-07-09 09:31:15 +05:30

132 lines
4.5 KiB
TypeScript

import prisma from '../utils/db';
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import { AppError } from '../utils/errors';
export class AuthService {
public async register(data: any) {
const existing = await prisma.user.findUnique({ where: { email: data.email } });
if (existing) throw new AppError('Email already in use', 400);
const passwordHash = await bcrypt.hash(data.password, 10);
const user = await prisma.user.create({
data: {
email: data.email,
passwordHash,
role: data.role || 'PARTNER_USER',
organizationId: data.organizationId || null,
onboardingStatus: data.role === 'ADMIN' ? 'APPROVED' : 'PENDING_ONBOARDING'
}
});
const { passwordHash: _, ...userWithoutPassword } = user;
return userWithoutPassword;
}
public async invitePartner(email: string, organizationId?: string) {
const existing = await prisma.user.findUnique({ where: { email } });
if (existing) throw new AppError('Email already in use', 400);
const crypto = require('crypto');
const inviteToken = crypto.randomBytes(32).toString('hex');
const inviteTokenExp = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours
const user = await prisma.user.create({
data: {
email,
passwordHash: '', // Set on accept
role: 'PARTNER_USER',
organizationId: organizationId || null,
inviteToken,
inviteTokenExp,
onboardingStatus: 'PENDING_ONBOARDING',
mfaEnabled: true,
}
});
// In a real app, send email here
return { inviteToken };
}
public async validateInvite(token: string) {
const user = await prisma.user.findUnique({ where: { inviteToken: token } });
if (!user || !user.inviteTokenExp || user.inviteTokenExp < new Date()) {
throw new AppError('Invalid or expired invite token', 400);
}
return { email: user.email };
}
public async acceptInvite(token: string, passwordString: string) {
const user = await prisma.user.findUnique({ where: { inviteToken: token } });
if (!user || !user.inviteTokenExp || user.inviteTokenExp < new Date()) {
throw new AppError('Invalid or expired invite token', 400);
}
const passwordHash = await bcrypt.hash(passwordString, 10);
const updatedUser = await prisma.user.update({
where: { id: user.id },
data: {
passwordHash,
inviteToken: null,
inviteTokenExp: null,
}
});
const secret = process.env.JWT_SECRET || 'secret';
const accessToken = jwt.sign({ userId: updatedUser.id, role: updatedUser.role }, secret, { expiresIn: '15m' });
const refreshToken = jwt.sign({ userId: updatedUser.id }, secret, { expiresIn: '7d' });
const { passwordHash: _, ...userWithoutPassword } = updatedUser;
return { user: userWithoutPassword, accessToken, refreshToken };
}
public async login(email: string, passwordString: string) {
const user = await prisma.user.findUnique({ where: { email } });
if (!user) {
throw new AppError('Invalid credentials', 401);
}
const isMatch = await bcrypt.compare(passwordString, user.passwordHash);
if (!isMatch) {
throw new AppError('Invalid credentials', 401);
}
const secret = process.env.JWT_SECRET || 'secret';
const accessToken = jwt.sign({ userId: user.id, role: user.role }, secret, { expiresIn: '15m' });
const refreshToken = jwt.sign({ userId: user.id }, secret, { expiresIn: '7d' });
const { passwordHash: _, ...userWithoutPassword } = user;
return { user: userWithoutPassword, accessToken, refreshToken };
}
public async refresh(refreshToken: string) {
const secret = process.env.JWT_SECRET || 'secret';
try {
const decoded = jwt.verify(refreshToken, secret) as any;
const user = await prisma.user.findUnique({ where: { id: decoded.userId } });
if (!user) throw new AppError('Invalid refresh token', 401);
const newAccessToken = jwt.sign({ userId: user.id, role: user.role }, secret, { expiresIn: '15m' });
return { accessToken: newAccessToken };
} catch(err) {
throw new AppError('Invalid or expired refresh token', 401);
}
}
public async listPartners() {
return await prisma.user.findMany({
where: { role: 'PARTNER_USER' },
select: {
id: true,
email: true,
onboardingStatus: true,
mfaEnabled: true,
createdAt: true,
updatedAt: true,
organizationId: true,
},
orderBy: { createdAt: 'desc' },
});
}
}