add registration and emails
This commit is contained in:
parent
70859f6871
commit
aaa546bccd
@ -20,6 +20,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@godaddy/terminus": "^4.1.2",
|
"@godaddy/terminus": "^4.1.2",
|
||||||
|
"@nest-modules/mailer": "^1.1.3",
|
||||||
"@nestjs/common": "^6.5.2",
|
"@nestjs/common": "^6.5.2",
|
||||||
"@nestjs/core": "^6.5.2",
|
"@nestjs/core": "^6.5.2",
|
||||||
"@nestjs/jwt": "^6.1.1",
|
"@nestjs/jwt": "^6.1.1",
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { UserModule } from "./user/user.module"
|
|||||||
import { FormModule } from "./form/form.module"
|
import { FormModule } from "./form/form.module"
|
||||||
import { AuthModule } from './auth/auth.module';
|
import { AuthModule } from './auth/auth.module';
|
||||||
import { MailModule } from "./mail/mail.module"
|
import { MailModule } from "./mail/mail.module"
|
||||||
|
import { MailerModule } from "@nest-modules/mailer"
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -17,6 +18,12 @@ import { MailModule } from "./mail/mail.module"
|
|||||||
TerminusModule.forRootAsync({
|
TerminusModule.forRootAsync({
|
||||||
useClass: TerminusOptionsService,
|
useClass: TerminusOptionsService,
|
||||||
}),
|
}),
|
||||||
|
MailerModule.forRoot({
|
||||||
|
transport: 'smtp://localhost:1025',
|
||||||
|
defaults: {
|
||||||
|
from:'"OhMyForm" <noreply@ohmyform.com>',
|
||||||
|
}
|
||||||
|
}),
|
||||||
UserModule,
|
UserModule,
|
||||||
FormModule,
|
FormModule,
|
||||||
AuthModule,
|
AuthModule,
|
||||||
|
|||||||
@ -1,12 +1,20 @@
|
|||||||
import { ApiModelProperty } from "@nestjs/swagger"
|
import { ApiModelProperty } from "@nestjs/swagger"
|
||||||
|
import { IsEmail, IsNotEmpty, Validate } from "class-validator"
|
||||||
|
import { UsernameAlreadyInUse } from "../../user/validators/UsernameAlreadyInUse"
|
||||||
|
import { EmailAlreadyInUse } from "../../user/validators/EmailAlreadyInUse"
|
||||||
|
|
||||||
export class RegisterDto {
|
export class RegisterDto {
|
||||||
@ApiModelProperty()
|
@ApiModelProperty()
|
||||||
|
@IsNotEmpty()
|
||||||
|
@Validate(UsernameAlreadyInUse)
|
||||||
readonly username: string;
|
readonly username: string;
|
||||||
|
|
||||||
@ApiModelProperty()
|
@ApiModelProperty()
|
||||||
|
@IsNotEmpty()
|
||||||
readonly password: string;
|
readonly password: string;
|
||||||
|
|
||||||
@ApiModelProperty()
|
@ApiModelProperty()
|
||||||
|
@Validate(EmailAlreadyInUse)
|
||||||
|
@IsEmail()
|
||||||
readonly email: string;
|
readonly email: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,7 +24,7 @@ export class PasswordService {
|
|||||||
).toString('base64');
|
).toString('base64');
|
||||||
}
|
}
|
||||||
|
|
||||||
hash (password): Promise<String> {
|
hash (password): Promise<string> {
|
||||||
return bcrypt.hash(password, 4)
|
return bcrypt.hash(password, 4)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +1,30 @@
|
|||||||
import { Injectable } from "@nestjs/common"
|
import { Injectable } from "@nestjs/common"
|
||||||
import { MailService } from "../../mail/services/mail.service"
|
import { MailService } from "../../mail/services/mail.service"
|
||||||
|
import { User } from "../../user/models/user.model"
|
||||||
|
import { PasswordService } from "./password.service"
|
||||||
|
import { UserService } from "../../user/services/user.service"
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RegisterService {
|
export class RegisterService {
|
||||||
constructor(private readonly mailService: MailService) {}
|
constructor(
|
||||||
|
private readonly mailService: MailService,
|
||||||
|
private readonly passwordService: PasswordService,
|
||||||
|
private readonly userService: UserService
|
||||||
|
) {}
|
||||||
|
|
||||||
async register (username: string, email: string, password: string): Promise<void> {
|
async register (username: string, email: string, password: string): Promise<void> {
|
||||||
// TODO actually create user
|
// TODO actually create user
|
||||||
|
|
||||||
|
let user = new User()
|
||||||
|
user.email = email
|
||||||
|
user.username = username
|
||||||
|
user.passwordHash = await this.passwordService.hash(password)
|
||||||
|
|
||||||
|
await this.userService.save(user)
|
||||||
|
|
||||||
await this.mailService.sendEmail(
|
await this.mailService.sendEmail(
|
||||||
{
|
{
|
||||||
template: 'auth/register.hbs',
|
template: 'auth/register',
|
||||||
to: email
|
to: email
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -34,5 +34,5 @@ export class RatingField {
|
|||||||
@arrayProp({
|
@arrayProp({
|
||||||
items: String
|
items: String
|
||||||
})
|
})
|
||||||
validShapes: [String];
|
validShapes: [string];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import providers from './mail.providers'
|
import providers from './mail.providers'
|
||||||
import exportList from './mail.exports'
|
import exportList from './mail.exports'
|
||||||
|
import { HandlebarsAdapter, MailerModule } from "@nest-modules/mailer"
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [],
|
imports: [
|
||||||
|
],
|
||||||
providers,
|
providers,
|
||||||
exports: exportList,
|
exports: exportList,
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,10 +1,35 @@
|
|||||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
import {Inject, Injectable, NotFoundException} from '@nestjs/common';
|
||||||
import { OptionsDto } from "../dto/options.dto"
|
import { OptionsDto } from "../dto/options.dto"
|
||||||
|
import { MailerService } from '@nest-modules/mailer';
|
||||||
|
import * as Handlebars from 'handlebars'
|
||||||
|
import * as fs from 'fs';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MailService {
|
export class MailService {
|
||||||
// TODO
|
constructor(
|
||||||
|
private readonly nodeMailer: MailerService
|
||||||
|
) {}
|
||||||
|
|
||||||
async sendEmail(options:OptionsDto, placeholders:any): Promise<boolean> {
|
async sendEmail(options:OptionsDto, placeholders:any): Promise<boolean> {
|
||||||
|
const template = fs.readFileSync(`${__dirname}/../../../views/en/mail/${options.template}.hbs`, 'UTF-8');
|
||||||
|
|
||||||
|
const parts = Handlebars
|
||||||
|
.compile(template)
|
||||||
|
(placeholders)
|
||||||
|
.split('---')
|
||||||
|
|
||||||
|
const mail:any = {
|
||||||
|
to: options.to,
|
||||||
|
subject: parts[0],
|
||||||
|
text: parts[1],
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parts.length > 2) {
|
||||||
|
mail.html = parts[2]
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.nodeMailer.sendMail(mail)
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +0,0 @@
|
|||||||
Hi,
|
|
||||||
|
|
||||||
if you have not requested a new password you can ignore this email. To set a new password for your account
|
|
||||||
just follow this link: {{ recover }}
|
|
||||||
|
|
||||||
See you soon
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
Welcome to OhMyForm!
|
|
||||||
|
|
||||||
please confirm your account by following the following link: {{ confirm }}
|
|
||||||
|
|
||||||
enjoy!
|
|
||||||
@ -2,6 +2,7 @@ import {NestFactory, Reflector} from '@nestjs/core';
|
|||||||
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
import { ClassSerializerInterceptor, ValidationPipe } from '@nestjs/common';
|
import { ClassSerializerInterceptor, ValidationPipe } from '@nestjs/common';
|
||||||
|
import { useContainer } from "class-validator"
|
||||||
const pkg = require('../package.json')
|
const pkg = require('../package.json')
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
@ -10,6 +11,8 @@ async function bootstrap() {
|
|||||||
// app.enableCors({ origin: '*' });
|
// app.enableCors({ origin: '*' });
|
||||||
// app.getHttpAdapter().options('*', cors());
|
// app.getHttpAdapter().options('*', cors());
|
||||||
|
|
||||||
|
useContainer(app.select(AppModule), { fallbackOnErrors: true });
|
||||||
|
|
||||||
app.useGlobalPipes(new ValidationPipe({
|
app.useGlobalPipes(new ValidationPipe({
|
||||||
disableErrorMessages: false,
|
disableErrorMessages: false,
|
||||||
transform: true,
|
transform: true,
|
||||||
|
|||||||
@ -1,7 +1,11 @@
|
|||||||
import {arrayProp, prop, Typegoose} from 'typegoose';
|
import {arrayProp, pre, prop, Typegoose} from 'typegoose';
|
||||||
import { IsString } from 'class-validator';
|
import { IsString } from 'class-validator';
|
||||||
import {Exclude} from "class-transformer"
|
import {Exclude} from "class-transformer"
|
||||||
|
|
||||||
|
@pre<User>('save', (next) => {
|
||||||
|
this.lastModified = new Date()
|
||||||
|
next()
|
||||||
|
})
|
||||||
export class User extends Typegoose {
|
export class User extends Typegoose {
|
||||||
@Exclude()
|
@Exclude()
|
||||||
readonly _id: string;
|
readonly _id: string;
|
||||||
@ -10,13 +14,13 @@ export class User extends Typegoose {
|
|||||||
trim: true,
|
trim: true,
|
||||||
default: ''
|
default: ''
|
||||||
})
|
})
|
||||||
readonly firstName: string;
|
firstName: string;
|
||||||
|
|
||||||
@prop({
|
@prop({
|
||||||
trim: true,
|
trim: true,
|
||||||
default: ''
|
default: ''
|
||||||
})
|
})
|
||||||
readonly lastName: string;
|
lastName: string;
|
||||||
|
|
||||||
@prop({
|
@prop({
|
||||||
trim: true,
|
trim: true,
|
||||||
@ -28,7 +32,7 @@ export class User extends Typegoose {
|
|||||||
],
|
],
|
||||||
required: [true, 'Email is required']
|
required: [true, 'Email is required']
|
||||||
})
|
})
|
||||||
readonly email: string;
|
email: string;
|
||||||
|
|
||||||
@prop({
|
@prop({
|
||||||
unique: true,
|
unique: true,
|
||||||
@ -39,33 +43,33 @@ export class User extends Typegoose {
|
|||||||
],
|
],
|
||||||
required: [true, 'Username is required']
|
required: [true, 'Username is required']
|
||||||
})
|
})
|
||||||
readonly username: string;
|
username: string;
|
||||||
|
|
||||||
@prop({
|
@prop({
|
||||||
default: ''
|
default: ''
|
||||||
})
|
})
|
||||||
readonly passwordHash: string;
|
passwordHash: string;
|
||||||
|
|
||||||
@prop()
|
@prop()
|
||||||
readonly salt: string;
|
salt: string;
|
||||||
|
|
||||||
@prop({
|
@prop({
|
||||||
default: 'local'
|
default: 'local'
|
||||||
})
|
})
|
||||||
readonly provider: string;
|
provider: string;
|
||||||
|
|
||||||
@arrayProp({
|
@arrayProp({
|
||||||
items: String,
|
items: String,
|
||||||
enum: ['user', 'admin', 'superuser'],
|
enum: ['user', 'admin', 'superuser'],
|
||||||
default: ['user']
|
default: ['user']
|
||||||
})
|
})
|
||||||
readonly roles: [string];
|
roles: [string];
|
||||||
|
|
||||||
@prop({
|
@prop({
|
||||||
enum: ['en', 'fr', 'es', 'it', 'de'],
|
enum: ['en', 'fr', 'es', 'it', 'de'],
|
||||||
default: 'en',
|
default: 'en',
|
||||||
})
|
})
|
||||||
readonly language: string;
|
language: string;
|
||||||
|
|
||||||
@prop({
|
@prop({
|
||||||
default: Date.now
|
default: Date.now
|
||||||
@ -76,18 +80,18 @@ export class User extends Typegoose {
|
|||||||
readonly lastModified: Date;
|
readonly lastModified: Date;
|
||||||
|
|
||||||
@prop()
|
@prop()
|
||||||
readonly resetPasswordToken: string;
|
resetPasswordToken: string;
|
||||||
|
|
||||||
@prop()
|
@prop()
|
||||||
readonly resetPasswordExpires: Date;
|
resetPasswordExpires: Date;
|
||||||
|
|
||||||
@prop()
|
@prop()
|
||||||
readonly token: string;
|
token: string;
|
||||||
|
|
||||||
@prop({
|
@prop({
|
||||||
unique: true,
|
unique: true,
|
||||||
index: true,
|
index: true,
|
||||||
sparse: true
|
sparse: true
|
||||||
})
|
})
|
||||||
readonly apiKey: string;
|
apiKey: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,4 +28,13 @@ export class UserService {
|
|||||||
async findById(id: string): Promise<User> {
|
async findById(id: string): Promise<User> {
|
||||||
return await this.userModel.findById(id).exec()
|
return await this.userModel.findById(id).exec()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async findOneBy(conditions): Promise<User> {
|
||||||
|
return await this.userModel.findOne(conditions).exec()
|
||||||
|
}
|
||||||
|
|
||||||
|
async save(user: User): Promise<User> {
|
||||||
|
let model = new this.userModel(user)
|
||||||
|
return await model.save()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,9 @@
|
|||||||
import { UserService } from "./services/user.service"
|
import { UserService } from "./services/user.service"
|
||||||
|
import { UsernameAlreadyInUse } from "./validators/UsernameAlreadyInUse"
|
||||||
|
import { EmailAlreadyInUse } from "./validators/EmailAlreadyInUse"
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
UserService
|
UserService,
|
||||||
|
UsernameAlreadyInUse,
|
||||||
|
EmailAlreadyInUse,
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,5 +1,9 @@
|
|||||||
import { UserService } from "./services/user.service"
|
import { UserService } from "./services/user.service"
|
||||||
|
import { UsernameAlreadyInUse } from "./validators/UsernameAlreadyInUse"
|
||||||
|
import { EmailAlreadyInUse } from "./validators/EmailAlreadyInUse"
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
UserService
|
UserService,
|
||||||
|
UsernameAlreadyInUse,
|
||||||
|
EmailAlreadyInUse,
|
||||||
]
|
]
|
||||||
|
|||||||
20
api/src/user/validators/EmailAlreadyInUse.ts
Normal file
20
api/src/user/validators/EmailAlreadyInUse.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { ValidationArguments, ValidatorConstraint } from "class-validator"
|
||||||
|
import { Inject, Injectable } from "@nestjs/common"
|
||||||
|
import { UserService } from "../services/user.service"
|
||||||
|
|
||||||
|
@ValidatorConstraint({ name: 'EmailAlreadyInUse', async: true })
|
||||||
|
@Injectable()
|
||||||
|
export class EmailAlreadyInUse {
|
||||||
|
constructor(
|
||||||
|
@Inject('UserService') private readonly userService: UserService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async validate(text: string) {
|
||||||
|
const user = await this.userService.findOneBy({email: text});
|
||||||
|
return !user;
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultMessage(args: ValidationArguments) {
|
||||||
|
return 'User with this email already exists.';
|
||||||
|
}
|
||||||
|
}
|
||||||
20
api/src/user/validators/UsernameAlreadyInUse.ts
Normal file
20
api/src/user/validators/UsernameAlreadyInUse.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { ValidationArguments, ValidatorConstraint } from "class-validator"
|
||||||
|
import { Inject, Injectable } from "@nestjs/common"
|
||||||
|
import { UserService } from "../services/user.service"
|
||||||
|
|
||||||
|
@ValidatorConstraint({ name: 'UsernameAlreadyInUse', async: true })
|
||||||
|
@Injectable()
|
||||||
|
export class UsernameAlreadyInUse {
|
||||||
|
constructor(
|
||||||
|
@Inject('UserService') private readonly userService: UserService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async validate(text: string) {
|
||||||
|
const user = await this.userService.findOneBy({username: text});
|
||||||
|
return !user;
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultMessage(args: ValidationArguments) {
|
||||||
|
return 'User with this username already exists.';
|
||||||
|
}
|
||||||
|
}
|
||||||
19
api/views/en/mail/auth/recover.hbs
Normal file
19
api/views/en/mail/auth/recover.hbs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
Password Reset Request
|
||||||
|
---
|
||||||
|
Hi,
|
||||||
|
|
||||||
|
if you have not requested a new password you can ignore this email. To set a new password for your account
|
||||||
|
just follow this link: {{ recover }}
|
||||||
|
|
||||||
|
See you soon
|
||||||
|
---
|
||||||
|
<h1>Hi, </h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
if you have not requested a new password you can ignore this email. To set a new password for your account
|
||||||
|
just follow this link: <a href="{{ recover }}">{{ recover }}</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
See you soon
|
||||||
|
</p>
|
||||||
14
api/views/en/mail/auth/register.hbs
Normal file
14
api/views/en/mail/auth/register.hbs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
Welcome to OhMyForm
|
||||||
|
---
|
||||||
|
Welcome to OhMyForm!
|
||||||
|
|
||||||
|
please confirm your account by following the following link: {{ confirm }}
|
||||||
|
|
||||||
|
enjoy!
|
||||||
|
---
|
||||||
|
<h1>Welcome to OhMyForm!</h1>
|
||||||
|
<p>
|
||||||
|
please confirm your account by following the following link: <a href="{{ confirm }}">{{ confirm }}</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>enjoy!</p>
|
||||||
Loading…
x
Reference in New Issue
Block a user