Files
Goa-gel-fullstack/backend/src/modules/auth/auth.service.ts

213 lines
6.1 KiB
TypeScript
Raw Normal View History

import { Injectable, UnauthorizedException, Logger } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { DepartmentsService } from '../departments/departments.service';
import { ApplicantsService } from '../applicants/applicants.service';
import { UsersService } from '../users/users.service';
import { HashUtil } from '../../common/utils';
import { UserRole } from '../../common/enums';
import { ERROR_CODES } from '../../common/constants';
import { JwtPayload } from '../../common/interfaces/request-context.interface';
import { LoginDto, DigiLockerLoginDto, EmailPasswordLoginDto } from './dto';
import * as bcrypt from 'bcrypt';
@Injectable()
export class AuthService {
private readonly logger = new Logger(AuthService.name);
constructor(
private readonly jwtService: JwtService,
private readonly departmentsService: DepartmentsService,
private readonly applicantsService: ApplicantsService,
private readonly usersService: UsersService,
) {}
/**
* Validate department API key and return JWT token
*/
async validateDepartmentApiKey(
apiKey: string,
departmentCode: string,
): Promise<{ accessToken: string; department: { id: string; code: string; name: string } }> {
const department = await this.departmentsService.findByCode(departmentCode);
if (!department) {
throw new UnauthorizedException({
code: ERROR_CODES.INVALID_API_KEY,
message: 'Invalid department code',
});
}
if (!department.isActive) {
throw new UnauthorizedException({
code: ERROR_CODES.INVALID_API_KEY,
message: 'Department is inactive',
});
}
const isValidApiKey = await HashUtil.comparePassword(apiKey, department.apiKeyHash || '');
if (!isValidApiKey) {
throw new UnauthorizedException({
code: ERROR_CODES.INVALID_API_KEY,
message: 'Invalid API key',
});
}
const payload: JwtPayload = {
sub: department.id,
role: UserRole.DEPARTMENT,
departmentCode: department.code,
};
return {
accessToken: this.jwtService.sign(payload),
department: {
id: department.id,
code: department.code,
name: department.name,
},
};
}
/**
* Mock DigiLocker login (for POC)
* In production, this would integrate with actual DigiLocker OAuth
*/
async digiLockerLogin(
dto: DigiLockerLoginDto,
): Promise<{ accessToken: string; applicant: { id: string; name: string; email: string } }> {
let applicant = await this.applicantsService.findByDigilockerId(dto.digilockerId);
// Auto-create applicant if not exists (mock behavior)
if (!applicant) {
this.logger.log(`Creating new applicant for DigiLocker ID: ${dto.digilockerId}`);
applicant = await this.applicantsService.create({
digilockerId: dto.digilockerId,
name: dto.name || 'DigiLocker User',
email: dto.email || `${dto.digilockerId}@digilocker.gov.in`,
phone: dto.phone,
});
}
if (!applicant.isActive) {
throw new UnauthorizedException({
code: ERROR_CODES.UNAUTHORIZED,
message: 'Applicant account is inactive',
});
}
const payload: JwtPayload = {
sub: applicant.id,
email: applicant.email,
role: UserRole.APPLICANT,
};
return {
accessToken: this.jwtService.sign(payload),
applicant: {
id: applicant.id,
name: applicant.name,
email: applicant.email,
},
};
}
/**
* Validate JWT token and return payload
*/
async validateJwtPayload(payload: JwtPayload): Promise<JwtPayload> {
if (payload.role === UserRole.DEPARTMENT && payload.departmentCode) {
const department = await this.departmentsService.findByCode(payload.departmentCode);
if (!department || !department.isActive) {
throw new UnauthorizedException({
code: ERROR_CODES.INVALID_TOKEN,
message: 'Department no longer valid',
});
}
} else if (payload.role === UserRole.APPLICANT) {
const applicant = await this.applicantsService.findById(payload.sub);
if (!applicant || !applicant.isActive) {
throw new UnauthorizedException({
code: ERROR_CODES.INVALID_TOKEN,
message: 'Applicant account no longer valid',
});
}
}
return payload;
}
/**
* Email/Password login for all user types (Admin, Department, Citizen)
*/
async emailPasswordLogin(
dto: EmailPasswordLoginDto,
): Promise<{
accessToken: string;
user: {
id: string;
email: string;
name: string;
role: string;
walletAddress: string;
departmentId?: string;
};
}> {
const user = await this.usersService.findByEmailWithDepartment(dto.email);
if (!user) {
throw new UnauthorizedException({
code: ERROR_CODES.INVALID_CREDENTIALS,
message: 'Invalid email or password',
});
}
if (!user.isActive) {
throw new UnauthorizedException({
code: ERROR_CODES.UNAUTHORIZED,
message: 'User account is inactive',
});
}
const isValidPassword = await bcrypt.compare(dto.password, user.passwordHash);
if (!isValidPassword) {
throw new UnauthorizedException({
code: ERROR_CODES.INVALID_CREDENTIALS,
message: 'Invalid email or password',
});
}
// Update last login
await this.usersService.updateLastLogin(user.id);
const payload: JwtPayload = {
sub: user.id,
email: user.email,
role: user.role as UserRole,
departmentCode: (user as any).department?.code,
};
return {
accessToken: this.jwtService.sign(payload),
user: {
id: user.id,
email: user.email,
name: user.name,
role: user.role,
walletAddress: user.walletAddress || '',
departmentId: user.departmentId,
},
};
}
/**
* Generate admin token (for internal use)
*/
generateAdminToken(adminId: string): string {
const payload: JwtPayload = {
sub: adminId,
role: UserRole.ADMIN,
};
return this.jwtService.sign(payload);
}
}