Passed
Push — master ( 5d28d6...936ae3 )
by Leandro
01:42
created

AuthController.sendConfirmation   A

Complexity

Conditions 1

Size

Total Lines 17
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 17
dl 0
loc 17
rs 9.55
c 0
b 0
f 0
cc 1
1
import { Authorized, Body, Get, JsonController, NotAcceptableError, NotFoundError, Post, QueryParam } from "routing-controllers";
2
import { getCustomRepository, getManager, getRepository } from "typeorm";
3
import { validate } from "class-validator";
4
import { ValidationError } from "../errors/ValidationError";
5
import { comparePassword, createEmail, generateHash, randomInt, randomString } from "../global";
6
import { ResetPassword, Credentials, SignUp } from "../interface";
7
import { User } from "../entities/User";
8
import { DateTime } from "luxon";
9
import { UserRepository } from "../repositories/UserRepository";
10
import { classToPlain } from "class-transformer";
11
import buildUrl from "build-url";
12
13
@JsonController("/auth")
14
export class AuthController {
15
16
  private userRepository: UserRepository;
17
18
  constructor () {
19
    this.userRepository = getCustomRepository(UserRepository);
20
  }
21
22
  @Post("/")
23
  async signin(@Body({required: true}) data: Credentials): Promise<any> {
24
    const errors = await validate(data);
25
    if (errors.length ) {
26
      throw new ValidationError(errors);
27
    }
28
29
    const user = await this.findUser(data.username, true);
30
    if (!user.confirmedAt) {
31
      throw new NotAcceptableError("UserNotConfirmed");
32
    }
33
34
    const isValid = await comparePassword(data.password, user.password);
35
    if (!isValid) {
36
      throw new NotAcceptableError("InvalidUsernameAndPassword");
37
    }
38
39
    return classToPlain(user);
40
  }
41
42
  @Authorized()
43
  @Get("/exists")
44
  async exists(
45
    @QueryParam("username", {required: true}) username: string
46
  ): Promise<boolean> {
47
    const user = await this.findUser(username);
48
49
    if (user && !user.confirmedAt) {
50
      throw new NotAcceptableError("UserNotConfirmed");
51
    }
52
53
    return !!user;
54
  }
55
56
  @Authorized()
57
  @Post("/signup")
58
  async signup(@Body({required: true}) data: SignUp): Promise<null> {
59
    let user = await this.findUser(data.username);
60
61
    if (user) {
62
      throw new NotAcceptableError("UserAlreadyExists");
63
    }
64
65
    const errors = await validate(data);
66
    if (errors.length ) {
67
      throw new ValidationError(errors);
68
    }
69
70
    user = new User();
71
    user.name = data.name;
72
    user.username = data.username;
73
    user.password = await generateHash(data.password);
74
    user.apiKey = randomString();
75
    this.userRepository.save(user);
76
77
    await this.sendConfirmation(user);
78
    return null;
79
  }
80
81
  @Authorized()
82
  @Post("/confirmation")
83
  async confirmation(
84
    @QueryParam("username", {required: true}) username: string,
85
    @QueryParam("token", {required: true}) token: string
86
  ): Promise<null> {
87
    const user = await this.findUser(username, true);
88
89
    if (user.confirmedAt) {
90
      throw new NotAcceptableError("UserAlreadyConfirmed");
91
    }
92
93
    if (user.confirmationToken !== token) {
94
      throw new NotAcceptableError("InvalidToken");
95
    }
96
97
    const confirmationSentAt = DateTime.fromJSDate(user.confirmationSentAt);
98
    const diff = DateTime.now().diff(confirmationSentAt).shiftTo("hours");
99
    if (diff.hours > 3) {
100
      throw new NotAcceptableError("expiredConfirmationToken");
101
    }
102
103
    const repository = getRepository(User);
104
    user.confirmedAt = new Date();
105
    await repository.save(user);
106
    return null;
107
  }
108
109
  @Authorized()
110
  @Post("/resend")
111
  async resend(
112
    @QueryParam("username", {required: true}) username: string
113
  ): Promise<null> {
114
    const user = await this.findUser(username, true);
115
116
    if (user.confirmedAt) {
117
      throw new NotAcceptableError("UserAlreadyConfirmed");
118
    }
119
120
    await this.sendConfirmation(user);
121
    return null;
122
  }
123
124
  @Authorized()
125
  @Post("/recovery")
126
  async recovery(
127
    @QueryParam("username", {required: true}) username: string
128
  ): Promise<null> {
129
    const user = await this.findUser(username, true);
130
131
    user.resetToken = randomString(60);
132
    user.resetSentAt = new Date();
133
    await this.userRepository.save(user);
134
135
    const email = createEmail();
136
    email.send({
137
      message: {to: username},
138
      template: "account-recovery",
139
      locals: {
140
        name: user.name,
141
        link: buildUrl(process.env.APP_URL, {
142
          path: `/reset/${encodeURI(user.resetToken)}`,
143
        }),
144
      },
145
    });
146
147
    return null;
148
  }
149
150
  @Authorized()
151
  @Post("/reset")
152
  async reset(
153
    @Body({required: true}) data: ResetPassword
154
  ): Promise<string> {
155
156
    const errors = await validate(data);
157
    if (errors.length ) {
158
      throw new ValidationError(errors);
159
    }
160
161
    const user = await this.userRepository.findOne({resetToken: data.token});
162
    if (!user) {
163
      throw new NotFoundError("TokenNotFound");
164
    }
165
166
    const resetSentAt = DateTime.fromJSDate(user.resetSentAt);
167
    const diff = DateTime.now().diff(resetSentAt).shiftTo("hours");
168
    if (diff.hours > 24) {
169
      throw new NotAcceptableError("TokenExpired");
170
    }
171
172
    user.password = await generateHash(data.password);
173
    this.userRepository.save(user);
174
175
    return user.username;
176
  }
177
178
  private async findUser(username: string, trhowException?: boolean): Promise<User> {
179
    const repository = getCustomRepository(UserRepository);
180
    const user = await repository.findOneByUsername(username);
181
182
    if (!user && trhowException) {
183
      throw new NotFoundError("UserNotFound");
184
    }
185
186
    return user;
187
  }
188
189
  private async sendConfirmation(user: User) {
190
    user.confirmationToken = randomInt(6);
191
    user.confirmationSentAt = new Date();
192
    await this.userRepository.save(user);
193
194
    const email = createEmail();
195
    email.send({
196
      message: {to: user.username},
197
      template: "account-confirmation",
198
      locals: {
199
        name: user.name,
200
        link: buildUrl(process.env.APP_URL, {
201
          path: "/confirm-signup",
202
          queryParams: {
203
            username: user.username,
204
            token: user.confirmationToken,
205
          },
206
        }),
207
      },
208
    });
209
  }
210
}
211