Browse Source

refactor(server): Create UserService & move User-related methods into it

Michael Bromley 7 years ago
parent
commit
d5e8cc8801

+ 3 - 1
server/src/api/resolvers/auth.resolver.ts

@@ -6,6 +6,7 @@ import { ConfigService } from '../../config/config.service';
 import { User } from '../../entity/user/user.entity';
 import { AuthService } from '../../service/services/auth.service';
 import { ChannelService } from '../../service/services/channel.service';
+import { UserService } from '../../service/services/user.service';
 import { extractAuthToken } from '../common/extract-auth-token';
 import { RequestContext } from '../common/request-context';
 import { setAuthToken } from '../common/set-auth-token';
@@ -16,6 +17,7 @@ import { Ctx } from '../decorators/request-context.decorator';
 export class AuthResolver {
     constructor(
         private authService: AuthService,
+        private userService: UserService,
         private channelService: ChannelService,
         private configService: ConfigService,
     ) {}
@@ -70,7 +72,7 @@ export class AuthResolver {
     @Allow(Permission.Authenticated)
     async me(@Ctx() ctx: RequestContext) {
         const userId = ctx.activeUserId;
-        const user = userId && (await this.authService.getUserById(userId));
+        const user = userId && (await this.userService.getUserById(userId));
         return user ? this.publiclyAccessibleUser(user) : null;
     }
 

+ 2 - 0
server/src/service/service.module.ts

@@ -33,6 +33,7 @@ import { RoleService } from './services/role.service';
 import { ShippingMethodService } from './services/shipping-method.service';
 import { TaxCategoryService } from './services/tax-category.service';
 import { TaxRateService } from './services/tax-rate.service';
+import { UserService } from './services/user.service';
 import { ZoneService } from './services/zone.service';
 
 const exportedProviders = [
@@ -56,6 +57,7 @@ const exportedProviders = [
     ShippingMethodService,
     TaxCategoryService,
     TaxRateService,
+    UserService,
     ZoneService,
 ];
 

+ 3 - 8
server/src/service/services/administrator.service.ts

@@ -14,6 +14,7 @@ import { PasswordCiper } from '../helpers/password-cipher/password-ciper';
 import { patchEntity } from '../helpers/utils/patch-entity';
 
 import { RoleService } from './role.service';
+import { UserService } from './user.service';
 
 @Injectable()
 export class AdministratorService {
@@ -21,6 +22,7 @@ export class AdministratorService {
         @InjectConnection() private connection: Connection,
         private listQueryBuilder: ListQueryBuilder,
         private passwordCipher: PasswordCiper,
+        private userService: UserService,
         private roleService: RoleService,
     ) {}
 
@@ -46,14 +48,7 @@ export class AdministratorService {
 
     async create(input: CreateAdministratorInput): Promise<Administrator> {
         const administrator = new Administrator(input);
-
-        const user = new User();
-        user.passwordHash = await this.passwordCipher.hash(input.password);
-        user.identifier = input.emailAddress;
-
-        const createdUser = await this.connection.manager.save(user);
-        administrator.user = createdUser;
-
+        administrator.user = await this.userService.createAdminUser(input.emailAddress, input.password);
         let createdAdministrator = await this.connection.manager.save(administrator);
         for (const roleId of input.roleIds) {
             createdAdministrator = await this.assignRole(createdAdministrator.id, roleId);

+ 1 - 8
server/src/service/services/auth.service.ts

@@ -2,7 +2,6 @@ import { Injectable, UnauthorizedException } from '@nestjs/common';
 import { InjectConnection } from '@nestjs/typeorm';
 import * as crypto from 'crypto';
 import * as ms from 'ms';
-import { ID } from 'shared/shared-types';
 import { Connection } from 'typeorm';
 
 import { RequestContext } from '../../api/common/request-context';
@@ -17,7 +16,7 @@ import { PasswordCiper } from '../helpers/password-cipher/password-ciper';
 import { OrderService } from './order.service';
 
 /**
- * The AuthService manages both authenticated and anonymous sessions.
+ * The AuthService manages both authenticated and anonymous Sessions.
  */
 @Injectable()
 export class AuthService {
@@ -124,12 +123,6 @@ export class AuthService {
         }
     }
 
-    async getUserById(userId: ID): Promise<User | undefined> {
-        return this.connection.getRepository(User).findOne(userId, {
-            relations: ['roles', 'roles.channels'],
-        });
-    }
-
     private async createNewAuthenticatedSession(
         ctx: RequestContext,
         user: User,

+ 3 - 12
server/src/service/services/customer.service.ts

@@ -13,21 +13,18 @@ import { ListQueryOptions } from '../../common/types/common-types';
 import { assertFound, normalizeEmailAddress } from '../../common/utils';
 import { Address } from '../../entity/address/address.entity';
 import { Customer } from '../../entity/customer/customer.entity';
-import { User } from '../../entity/user/user.entity';
 import { I18nError } from '../../i18n/i18n-error';
 import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-builder';
-import { PasswordCiper } from '../helpers/password-cipher/password-ciper';
 import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
 import { patchEntity } from '../helpers/utils/patch-entity';
 
-import { RoleService } from './role.service';
+import { UserService } from './user.service';
 
 @Injectable()
 export class CustomerService {
     constructor(
         @InjectConnection() private connection: Connection,
-        private passwordCipher: PasswordCiper,
-        private roleService: RoleService,
+        private userService: UserService,
         private listQueryBuilder: ListQueryBuilder,
     ) {}
 
@@ -73,14 +70,8 @@ export class CustomerService {
         }
 
         if (password) {
-            const user = new User();
-            user.passwordHash = await this.passwordCipher.hash(password);
-            user.identifier = input.emailAddress;
-            user.roles = [await this.roleService.getCustomerRole()];
-            const createdUser = await this.connection.manager.save(user);
-            customer.user = createdUser;
+            customer.user = await this.userService.createCustomerUser(input.emailAddress, password);
         }
-
         return this.connection.getRepository(Customer).save(customer);
     }
 

+ 75 - 0
server/src/service/services/user.service.ts

@@ -0,0 +1,75 @@
+import { Injectable } from '@nestjs/common';
+import { InjectConnection } from '@nestjs/typeorm';
+import * as ms from 'ms';
+import { ID } from 'shared/shared-types';
+import { Connection } from 'typeorm';
+
+import { generatePublicId } from '../../common/generate-public-id';
+import { ConfigService } from '../../config/config.service';
+import { User } from '../../entity/user/user.entity';
+import { PasswordCiper } from '../helpers/password-cipher/password-ciper';
+
+import { RoleService } from './role.service';
+
+@Injectable()
+export class UserService {
+    constructor(
+        @InjectConnection() private connection: Connection,
+        private configService: ConfigService,
+        private roleService: RoleService,
+        private passwordCipher: PasswordCiper,
+    ) {}
+
+    async getUserById(userId: ID): Promise<User | undefined> {
+        return this.connection.getRepository(User).findOne(userId, {
+            relations: ['roles', 'roles.channels'],
+        });
+    }
+
+    async createCustomerUser(identifier: string, password: string): Promise<User> {
+        const user = new User();
+        if (this.configService.authOptions.requireVerification) {
+            user.verificationToken = this.generateVerificationToken();
+            user.verified = false;
+        } else {
+            user.verified = true;
+        }
+        user.passwordHash = await this.passwordCipher.hash(password);
+        user.identifier = identifier;
+        user.roles = [await this.roleService.getCustomerRole()];
+        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,
+        });
+        return this.connection.manager.save(user);
+    }
+
+    /**
+     * Generates a verification token which encodes the time of generation and concatenates it with a
+     * random id.
+     */
+    private generateVerificationToken() {
+        const now = new Date();
+        const base64Now = Buffer.from(now.toJSON()).toString('base64');
+        const id = generatePublicId();
+        return `${base64Now}_${id}`;
+    }
+
+    /**
+     * Checks the age of the verification token to see if it falls within the token duration
+     * as specified in the VendureConfig.
+     */
+    verifyVerificationToken(token: string): boolean {
+        const duration = ms(this.configService.authOptions.verificationTokenDuration);
+        const [generatedOn] = token.split('_');
+        const dateString = Buffer.from(generatedOn, 'base64').toString();
+        const date = new Date(dateString);
+        const elapsed = +new Date() - +date;
+        return elapsed < duration;
+    }
+}