|
|
@@ -12,6 +12,7 @@ import {
|
|
|
VerificationTokenExpiredError,
|
|
|
} from '../../common/error/errors';
|
|
|
import { ConfigService } from '../../config/config.service';
|
|
|
+import { NativeAuthenticationMethod } from '../../entity/authentication-method/native-authentication-method.entity';
|
|
|
import { User } from '../../entity/user/user.entity';
|
|
|
import { PasswordCiper } from '../helpers/password-cipher/password-ciper';
|
|
|
import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
|
|
|
@@ -31,7 +32,7 @@ export class UserService {
|
|
|
|
|
|
async getUserById(userId: ID): Promise<User | undefined> {
|
|
|
return this.connection.getRepository(User).findOne(userId, {
|
|
|
- relations: ['roles', 'roles.channels'],
|
|
|
+ relations: ['roles', 'roles.channels', 'authenticationMethods'],
|
|
|
});
|
|
|
}
|
|
|
|
|
|
@@ -40,35 +41,45 @@ export class UserService {
|
|
|
where: {
|
|
|
identifier: emailAddress,
|
|
|
},
|
|
|
- relations: ['roles', 'roles.channels'],
|
|
|
+ relations: ['roles', 'roles.channels', 'authenticationMethods'],
|
|
|
});
|
|
|
}
|
|
|
|
|
|
async createCustomerUser(identifier: string, password?: string): Promise<User> {
|
|
|
const user = new User();
|
|
|
+ const authenticationMethod = new NativeAuthenticationMethod();
|
|
|
if (this.configService.authOptions.requireVerification) {
|
|
|
- user.verificationToken = this.verificationTokenGenerator.generateVerificationToken();
|
|
|
+ authenticationMethod.verificationToken = this.verificationTokenGenerator.generateVerificationToken();
|
|
|
user.verified = false;
|
|
|
} else {
|
|
|
user.verified = true;
|
|
|
}
|
|
|
if (password) {
|
|
|
- user.passwordHash = await this.passwordCipher.hash(password);
|
|
|
+ authenticationMethod.passwordHash = await this.passwordCipher.hash(password);
|
|
|
} else {
|
|
|
- user.passwordHash = '';
|
|
|
+ authenticationMethod.passwordHash = '';
|
|
|
}
|
|
|
user.identifier = identifier;
|
|
|
+ authenticationMethod.identifier = identifier;
|
|
|
+ await this.connection.manager.save(authenticationMethod);
|
|
|
const customerRole = await this.roleService.getCustomerRole();
|
|
|
user.roles = [customerRole];
|
|
|
+ user.authenticationMethods = [authenticationMethod];
|
|
|
return this.connection.manager.save(user);
|
|
|
}
|
|
|
|
|
|
async createAdminUser(identifier: string, password: string): Promise<User> {
|
|
|
const user = new User({
|
|
|
- passwordHash: await this.passwordCipher.hash(password),
|
|
|
identifier,
|
|
|
verified: true,
|
|
|
});
|
|
|
+ const authenticationMethod = await this.connection.manager.save(
|
|
|
+ new NativeAuthenticationMethod({
|
|
|
+ identifier,
|
|
|
+ passwordHash: await this.passwordCipher.hash(password),
|
|
|
+ }),
|
|
|
+ );
|
|
|
+ user.authenticationMethods = [authenticationMethod];
|
|
|
return this.connection.manager.save(user);
|
|
|
}
|
|
|
|
|
|
@@ -78,20 +89,27 @@ export class UserService {
|
|
|
}
|
|
|
|
|
|
async setVerificationToken(user: User): Promise<User> {
|
|
|
- user.verificationToken = this.verificationTokenGenerator.generateVerificationToken();
|
|
|
+ const nativeAuthMethod = user.getNativeAuthenticationMethod();
|
|
|
+ nativeAuthMethod.verificationToken = this.verificationTokenGenerator.generateVerificationToken();
|
|
|
user.verified = false;
|
|
|
+ await this.connection.manager.save(nativeAuthMethod);
|
|
|
return this.connection.manager.save(user);
|
|
|
}
|
|
|
|
|
|
async verifyUserByToken(verificationToken: string, password: string): Promise<User | undefined> {
|
|
|
- const user = await this.connection.getRepository(User).findOne({
|
|
|
- where: { verificationToken },
|
|
|
- });
|
|
|
+ const user = await this.connection
|
|
|
+ .getRepository(User)
|
|
|
+ .createQueryBuilder('user')
|
|
|
+ .leftJoinAndSelect('user.authenticationMethods', 'authenticationMethod')
|
|
|
+ .where('authenticationMethod.verificationToken = :verificationToken', { verificationToken })
|
|
|
+ .getOne();
|
|
|
if (user) {
|
|
|
if (this.verificationTokenGenerator.verifyVerificationToken(verificationToken)) {
|
|
|
- user.passwordHash = await this.passwordCipher.hash(password);
|
|
|
- user.verificationToken = null;
|
|
|
+ const nativeAuthMethod = user.getNativeAuthenticationMethod();
|
|
|
+ nativeAuthMethod.passwordHash = await this.passwordCipher.hash(password);
|
|
|
+ nativeAuthMethod.verificationToken = null;
|
|
|
user.verified = true;
|
|
|
+ await this.connection.manager.save(nativeAuthMethod);
|
|
|
return this.connection.getRepository(User).save(user);
|
|
|
} else {
|
|
|
throw new VerificationTokenExpiredError();
|
|
|
@@ -104,18 +122,25 @@ export class UserService {
|
|
|
if (!user) {
|
|
|
return;
|
|
|
}
|
|
|
- user.passwordResetToken = await this.verificationTokenGenerator.generateVerificationToken();
|
|
|
- return this.connection.getRepository(User).save(user);
|
|
|
+ const nativeAuthMethod = user.getNativeAuthenticationMethod();
|
|
|
+ nativeAuthMethod.passwordResetToken = await this.verificationTokenGenerator.generateVerificationToken();
|
|
|
+ await this.connection.manager.save(nativeAuthMethod);
|
|
|
+ return user;
|
|
|
}
|
|
|
|
|
|
async resetPasswordByToken(passwordResetToken: string, password: string): Promise<User | undefined> {
|
|
|
- const user = await this.connection.getRepository(User).findOne({
|
|
|
- where: { passwordResetToken },
|
|
|
- });
|
|
|
+ const user = await this.connection
|
|
|
+ .getRepository(User)
|
|
|
+ .createQueryBuilder('user')
|
|
|
+ .leftJoinAndSelect('user.authenticationMethods', 'authenticationMethod')
|
|
|
+ .where('authenticationMethod.passwordResetToken = :passwordResetToken', { passwordResetToken })
|
|
|
+ .getOne();
|
|
|
if (user) {
|
|
|
if (this.verificationTokenGenerator.verifyVerificationToken(passwordResetToken)) {
|
|
|
- user.passwordHash = await this.passwordCipher.hash(password);
|
|
|
- user.passwordResetToken = null;
|
|
|
+ const nativeAuthMethod = user.getNativeAuthenticationMethod();
|
|
|
+ nativeAuthMethod.passwordHash = await this.passwordCipher.hash(password);
|
|
|
+ nativeAuthMethod.passwordResetToken = null;
|
|
|
+ await this.connection.manager.save(nativeAuthMethod);
|
|
|
return this.connection.getRepository(User).save(user);
|
|
|
} else {
|
|
|
throw new PasswordResetTokenExpiredError();
|
|
|
@@ -124,23 +149,31 @@ export class UserService {
|
|
|
}
|
|
|
|
|
|
async changeIdentifierByToken(token: string): Promise<{ user: User; oldIdentifier: string }> {
|
|
|
- const user = await this.connection.getRepository(User).findOne({
|
|
|
- where: { identifierChangeToken: token },
|
|
|
- });
|
|
|
+ const user = await this.connection
|
|
|
+ .getRepository(User)
|
|
|
+ .createQueryBuilder('user')
|
|
|
+ .leftJoinAndSelect('user.authenticationMethods', 'authenticationMethod')
|
|
|
+ .where('authenticationMethod.identifierChangeToken = :identifierChangeToken', {
|
|
|
+ identifierChangeToken: token,
|
|
|
+ })
|
|
|
+ .getOne();
|
|
|
if (!user) {
|
|
|
throw new IdentifierChangeTokenError();
|
|
|
}
|
|
|
if (!this.verificationTokenGenerator.verifyVerificationToken(token)) {
|
|
|
throw new IdentifierChangeTokenExpiredError();
|
|
|
}
|
|
|
- const pendingIdentifier = user.pendingIdentifier;
|
|
|
+ const nativeAuthMethod = user.getNativeAuthenticationMethod();
|
|
|
+ const pendingIdentifier = nativeAuthMethod.pendingIdentifier;
|
|
|
if (!pendingIdentifier) {
|
|
|
throw new InternalServerError('error.pending-identifier-missing');
|
|
|
}
|
|
|
const oldIdentifier = user.identifier;
|
|
|
user.identifier = pendingIdentifier;
|
|
|
- user.identifierChangeToken = null;
|
|
|
- user.pendingIdentifier = null;
|
|
|
+ nativeAuthMethod.identifier = pendingIdentifier;
|
|
|
+ nativeAuthMethod.identifierChangeToken = null;
|
|
|
+ nativeAuthMethod.pendingIdentifier = null;
|
|
|
+ await this.connection.manager.save(nativeAuthMethod, { reload: false });
|
|
|
await this.connection.getRepository(User).save(user, { reload: false });
|
|
|
return { user, oldIdentifier };
|
|
|
}
|
|
|
@@ -148,21 +181,28 @@ export class UserService {
|
|
|
async updatePassword(userId: ID, currentPassword: string, newPassword: string): Promise<boolean> {
|
|
|
const user = await this.connection
|
|
|
.getRepository(User)
|
|
|
- .findOne(userId, { select: ['id', 'passwordHash'] });
|
|
|
+ .createQueryBuilder('user')
|
|
|
+ .leftJoinAndSelect('user.authenticationMethods', 'authenticationMethods')
|
|
|
+ .addSelect('authenticationMethods.passwordHash')
|
|
|
+ .where('user.id = :id', { id: userId })
|
|
|
+ .getOne();
|
|
|
if (!user) {
|
|
|
throw new InternalServerError(`error.no-active-user-id`);
|
|
|
}
|
|
|
- const matches = await this.passwordCipher.check(currentPassword, user.passwordHash);
|
|
|
+ const nativeAuthMethod = user.getNativeAuthenticationMethod();
|
|
|
+ const matches = await this.passwordCipher.check(currentPassword, nativeAuthMethod.passwordHash);
|
|
|
if (!matches) {
|
|
|
throw new UnauthorizedError();
|
|
|
}
|
|
|
- user.passwordHash = await this.passwordCipher.hash(newPassword);
|
|
|
- await this.connection.getRepository(User).save(user, { reload: false });
|
|
|
+ nativeAuthMethod.passwordHash = await this.passwordCipher.hash(newPassword);
|
|
|
+ await this.connection.manager.save(nativeAuthMethod, { reload: false });
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
async setIdentifierChangeToken(user: User): Promise<User> {
|
|
|
- user.identifierChangeToken = this.verificationTokenGenerator.generateVerificationToken();
|
|
|
- return this.connection.manager.save(user);
|
|
|
+ const nativeAuthMethod = user.getNativeAuthenticationMethod();
|
|
|
+ nativeAuthMethod.identifierChangeToken = this.verificationTokenGenerator.generateVerificationToken();
|
|
|
+ await this.connection.manager.save(nativeAuthMethod);
|
|
|
+ return user;
|
|
|
}
|
|
|
}
|