Initial commit: LACA parking management system
This commit is contained in:
393
backend/src/auth/auth.service.ts
Normal file
393
backend/src/auth/auth.service.ts
Normal file
@@ -0,0 +1,393 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
Injectable,
|
||||
Logger,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { hashSync, compareSync } from 'bcrypt';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import {
|
||||
LoginTicket,
|
||||
OAuth2Client as GoogleOAuth2Client,
|
||||
OAuth2Client,
|
||||
} from 'google-auth-library';
|
||||
import { RedisClientService } from 'src/cache/cache.service';
|
||||
import {
|
||||
EMAIL_EXISTS,
|
||||
EXP_FIRST_LOGIN,
|
||||
PHONE_EXISTS,
|
||||
TENANT_EXISTS,
|
||||
TENANT_NOTFOUND,
|
||||
USER_NOTFOUND,
|
||||
VERIFY_GOOGLE_TOKEN_FAILED,
|
||||
VERIFY_TENANT_FAILED,
|
||||
VERIFY_USER_FAILED,
|
||||
} from 'src/share/eCode';
|
||||
import { AppConfigService } from 'src/config/app/app-config.service';
|
||||
import { UserEntity } from 'src/modules/user/entities/user.entity';
|
||||
import { PrismaService } from 'src/prisma/prisma.service';
|
||||
import { convertShortTimeToSecond } from 'src/share/tool';
|
||||
import { ChangePassword, TenantSignUp, UserConfirmSignUp, UserLogin, UserSignUp } from './dto/login.dto';
|
||||
import { eUserType } from 'src/common/auth/type';
|
||||
import { AuthPayload } from './decorators/auth.payload';
|
||||
import { bool } from 'joi';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
private readonly logger = new Logger(AuthService.name);
|
||||
private readonly oAuth2client: GoogleOAuth2Client;
|
||||
|
||||
constructor(
|
||||
private readonly redisService: RedisClientService,
|
||||
private readonly jwtService: JwtService,
|
||||
private readonly configService: AppConfigService,
|
||||
private prisma: PrismaService,
|
||||
) {
|
||||
// const { clientId, clientSecret } = this.configService.get('googleAuth');
|
||||
this.oAuth2client = new OAuth2Client({
|
||||
clientId: process.env.GOOGLE_CLIENT_ID,
|
||||
clientSecret: process.env.GOOGLE_SECRET,
|
||||
});
|
||||
}
|
||||
|
||||
async generateToken(
|
||||
account: string,
|
||||
dataToken: AuthPayload,
|
||||
first_login = Date.now(),
|
||||
) {
|
||||
const tid = hashSync(account, 10);
|
||||
const payload = {
|
||||
tid,
|
||||
...dataToken,
|
||||
first_login,
|
||||
};
|
||||
|
||||
const payloadRefreshToken = {
|
||||
tid,
|
||||
...dataToken,
|
||||
first_login,
|
||||
};
|
||||
const [access_token, refresh_token] = await Promise.all([
|
||||
this.jwtService.signAsync(payload, {
|
||||
secret: this.configService.jwtAccessSecret,
|
||||
expiresIn: this.configService.jwtExpiresIn,
|
||||
}),
|
||||
this.jwtService.signAsync(payloadRefreshToken, {
|
||||
secret: this.configService.jwtRefreshSecret,
|
||||
expiresIn: this.configService.jwtRefreshIn,
|
||||
}),
|
||||
]);
|
||||
|
||||
return { access_token, refresh_token, tid };
|
||||
}
|
||||
|
||||
async userLoginByGoogleOAuth2(token: string): Promise<{
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
user: UserEntity;
|
||||
}> {
|
||||
try {
|
||||
const googleLoginTicket: LoginTicket =
|
||||
await this.oAuth2client.verifyIdToken({
|
||||
idToken: token,
|
||||
audience: process.env.GOOGLE_CLIENT_ID,
|
||||
});
|
||||
|
||||
const { email } = googleLoginTicket.getPayload();
|
||||
|
||||
let user = await this.prisma.user.findFirst({
|
||||
where: {
|
||||
email,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
user = await this.prisma.user.create({
|
||||
data: {
|
||||
email,
|
||||
fullname: email,
|
||||
},
|
||||
});
|
||||
}
|
||||
const { access_token, refresh_token, tid } = await this.generateToken(
|
||||
user.email,
|
||||
{
|
||||
id: user.id,
|
||||
account: user.email,
|
||||
type: eUserType.USER,
|
||||
containsPassword: Boolean(user.password),
|
||||
},
|
||||
);
|
||||
await this.redisService.markLogin(user.id, tid);
|
||||
return {
|
||||
user: user,
|
||||
access_token,
|
||||
refresh_token,
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
if (error instanceof BadRequestException) {
|
||||
throw error;
|
||||
}
|
||||
throw new UnauthorizedException(VERIFY_GOOGLE_TOKEN_FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
async userLogin(data: UserLogin): Promise<{
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
user: UserEntity;
|
||||
}> {
|
||||
try {
|
||||
const user = await this.prisma.user.findFirst({
|
||||
where: {
|
||||
username: data.username,
|
||||
},
|
||||
});
|
||||
if (!user) {
|
||||
throw new BadRequestException(USER_NOTFOUND);
|
||||
}
|
||||
const { access_token, refresh_token, tid } = await this.generateToken(
|
||||
user.username,
|
||||
{
|
||||
id: user.id,
|
||||
account: user.username,
|
||||
type: eUserType.USER,
|
||||
containsPassword: Boolean(user.password),
|
||||
},
|
||||
);
|
||||
// Check pass
|
||||
if (!compareSync(data.password, user.password)) {
|
||||
throw new UnauthorizedException(VERIFY_TENANT_FAILED);
|
||||
}
|
||||
await this.redisService.markLogin(user.id, tid);
|
||||
return {
|
||||
access_token,
|
||||
refresh_token,
|
||||
user: user,
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
if (error instanceof BadRequestException) {
|
||||
throw error;
|
||||
}
|
||||
throw new UnauthorizedException(VERIFY_USER_FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
async tenantLogin(data: UserLogin): Promise<{
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
}> {
|
||||
try {
|
||||
const tenant = await this.prisma.tenant.findFirst({
|
||||
where: {
|
||||
username: data.username,
|
||||
},
|
||||
});
|
||||
|
||||
if (!tenant) {
|
||||
throw new BadRequestException(TENANT_NOTFOUND);
|
||||
}
|
||||
|
||||
const { access_token, refresh_token, tid } = await this.generateToken(
|
||||
tenant.username,
|
||||
{
|
||||
id: tenant.id,
|
||||
account: tenant.username,
|
||||
type: eUserType.TENANT,
|
||||
containsPassword: Boolean(tenant.password),
|
||||
},
|
||||
);
|
||||
// Check pass
|
||||
if (!compareSync(data.password, tenant.password)) {
|
||||
throw new UnauthorizedException(VERIFY_TENANT_FAILED);
|
||||
}
|
||||
await this.redisService.markLogin(tenant.id, tid);
|
||||
return {
|
||||
access_token,
|
||||
refresh_token,
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
if (error instanceof BadRequestException) {
|
||||
throw error;
|
||||
}
|
||||
throw new UnauthorizedException(VERIFY_TENANT_FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
async refreshToken(refreshToken: string) {
|
||||
let decodedRefreshToken = null;
|
||||
try {
|
||||
decodedRefreshToken = await this.jwtService.verifyAsync(refreshToken, {
|
||||
secret: this.configService.jwtRefreshSecret,
|
||||
});
|
||||
const { id: sub, tid, type } = decodedRefreshToken;
|
||||
if (!(await this.redisService.checkRefreshTokenFound(sub, tid))) {
|
||||
throw new UnauthorizedException('Decoded token not found');
|
||||
}
|
||||
//check first login
|
||||
const dateExp: any = new Date(decodedRefreshToken.first_login);
|
||||
const timeAutoLogout = this.configService.jwtRefreshIn || '30d';
|
||||
dateExp.setSeconds(
|
||||
dateExp.getSeconds() + convertShortTimeToSecond(timeAutoLogout),
|
||||
);
|
||||
|
||||
if (Date.now() > dateExp) {
|
||||
const { sub, tid } = decodedRefreshToken;
|
||||
await this.logout(sub, tid);
|
||||
throw new UnauthorizedException(EXP_FIRST_LOGIN);
|
||||
}
|
||||
|
||||
const user =
|
||||
type == eUserType.USER
|
||||
? await this.prisma.user.findFirstOrThrow({
|
||||
where: {
|
||||
email: decodedRefreshToken.account,
|
||||
},
|
||||
})
|
||||
: await this.prisma.tenant.findFirstOrThrow({
|
||||
where: {
|
||||
username: decodedRefreshToken.account,
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
access_token,
|
||||
refresh_token,
|
||||
tid: _tid,
|
||||
} = await this.generateToken(
|
||||
decodedRefreshToken.account,
|
||||
{
|
||||
id: user.id,
|
||||
account: type == eUserType.USER ? user.email : user.username,
|
||||
type,
|
||||
containsPassword: Boolean(user.password),
|
||||
},
|
||||
decodedRefreshToken.first_login,
|
||||
);
|
||||
|
||||
await this.logout(sub, tid);
|
||||
await this.redisService.markLogin(user.id, _tid);
|
||||
return { access_token, refresh_token };
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
this.logger.error(error.message);
|
||||
throw new UnauthorizedException(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async logout(uid: string, tid: string) {
|
||||
try {
|
||||
if (!tid) return false;
|
||||
await this.redisService.delMarkLogin(uid, tid);
|
||||
return true;
|
||||
} catch (err) {
|
||||
this.logger.error(err.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async signUpTenant(data: TenantSignUp) {
|
||||
const { username, password } = data;
|
||||
delete data.password_confirmation;
|
||||
return await this.prisma.$transaction(async (tx) => {
|
||||
const teannt = await tx.tenant.findFirst({
|
||||
where: {
|
||||
username,
|
||||
},
|
||||
});
|
||||
if (teannt) {
|
||||
throw new BadRequestException(TENANT_EXISTS);
|
||||
}
|
||||
const newTenant = await tx.tenant.create({
|
||||
data: {
|
||||
...data,
|
||||
password: hashSync(password, 10),
|
||||
},
|
||||
});
|
||||
|
||||
return { ...newTenant, password: undefined };
|
||||
});
|
||||
}
|
||||
|
||||
async signUpUser(data: UserSignUp) {
|
||||
const { username, password } = data;
|
||||
delete data.password_confirmation;
|
||||
const userQuery: any[] = [
|
||||
{
|
||||
username: username,
|
||||
},
|
||||
];
|
||||
if (data.phone) {
|
||||
userQuery.push({ phone: data.phone });
|
||||
}
|
||||
if (data.email) {
|
||||
userQuery.push({ email: data.email });
|
||||
}
|
||||
return await this.prisma.$transaction(async (tx) => {
|
||||
const user = await tx.user.findFirst({
|
||||
where: {
|
||||
OR: userQuery,
|
||||
},
|
||||
});
|
||||
if (user) {
|
||||
if (user.username == username) {
|
||||
throw new BadRequestException(TENANT_EXISTS);
|
||||
}
|
||||
if (user.phone == data.phone) {
|
||||
throw new BadRequestException(PHONE_EXISTS);
|
||||
}
|
||||
if (user.email == data.email) {
|
||||
throw new BadRequestException(EMAIL_EXISTS);
|
||||
}
|
||||
throw new BadRequestException(TENANT_EXISTS);
|
||||
}
|
||||
const newUser = await tx.user.create({
|
||||
data: {
|
||||
...data,
|
||||
password: hashSync(password, 10),
|
||||
},
|
||||
});
|
||||
|
||||
return { ...newUser, password: undefined };
|
||||
});
|
||||
}
|
||||
|
||||
async confirmSignUpUser(data: UserConfirmSignUp) {
|
||||
this.logger.log('confirmSignUpUser', data);
|
||||
}
|
||||
|
||||
async changePassword(data: ChangePassword, auth: AuthPayload) {
|
||||
const { old_password, password } = data;
|
||||
return await this.prisma.$transaction(async (tx) => {
|
||||
const user = await tx.user.findFirst({
|
||||
where: {
|
||||
id: +auth.id,
|
||||
},
|
||||
});
|
||||
if (!user) {
|
||||
throw new BadRequestException(USER_NOTFOUND);
|
||||
}
|
||||
if (!user.password?.length) {
|
||||
return;
|
||||
}
|
||||
if (!compareSync(old_password, user.password)) {
|
||||
throw new UnauthorizedException(VERIFY_USER_FAILED);
|
||||
}
|
||||
const newUser = await tx.user.update({
|
||||
where: {
|
||||
id: +auth.id,
|
||||
},
|
||||
data: {
|
||||
password: hashSync(password, 10),
|
||||
},
|
||||
});
|
||||
|
||||
return { ...newUser, password: undefined };
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user