feat: Add login

This commit is contained in:
손승우
2022-11-15 17:39:40 +09:00
parent 8dc60190b3
commit c0e43b7679
18 changed files with 465 additions and 1 deletions

View File

@@ -6,6 +6,8 @@ config.serverless.yml
vanillameta vanillameta
bigquery-key.json bigquery-key.json
test-connect-info.json test-connect-info.json
cert.pem
key.pem
. .
# compiled output # compiled output
/dist /dist

View File

@@ -2822,11 +2822,25 @@
"uuid": "9.0.0" "uuid": "9.0.0"
} }
}, },
"@nestjs/jwt": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-9.0.0.tgz",
"integrity": "sha512-ZsXGY/wMYKzEhymw2+dxiwrHTRKIKrGszx6r2EjQqNLypdXMQu0QrujwZJ8k3+XQV4snmuJwwNakQoA2ILfq8w==",
"requires": {
"@types/jsonwebtoken": "8.5.8",
"jsonwebtoken": "8.5.1"
}
},
"@nestjs/mapped-types": { "@nestjs/mapped-types": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-1.1.0.tgz",
"integrity": "sha512-+2kSly4P1QI+9eGt+/uGyPdEG1hVz7nbpqPHWZVYgoqz8eOHljpXPag+UCVRw9zo2XCu4sgNUIGe8Uk0+OvUQg==" "integrity": "sha512-+2kSly4P1QI+9eGt+/uGyPdEG1hVz7nbpqPHWZVYgoqz8eOHljpXPag+UCVRw9zo2XCu4sgNUIGe8Uk0+OvUQg=="
}, },
"@nestjs/passport": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/@nestjs/passport/-/passport-9.0.0.tgz",
"integrity": "sha512-Gnh8n1wzFPOLSS/94X1sUP4IRAoXTgG4odl7/AO5h+uwscEGXxJFercrZfqdAwkWhqkKWbsntM3j5mRy/6ZQDA=="
},
"@nestjs/platform-express": { "@nestjs/platform-express": {
"version": "9.1.1", "version": "9.1.1",
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-9.1.1.tgz", "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-9.1.1.tgz",
@@ -4052,6 +4066,14 @@
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true "dev": true
}, },
"@types/jsonwebtoken": {
"version": "8.5.8",
"resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.8.tgz",
"integrity": "sha512-zm6xBQpFDIDM6o9r6HSgDeIcLy82TKWctCXEPbJJcXb5AKmi5BNNdLXneixK4lplX3PqIVcwLBCGE/kAGnlD4A==",
"requires": {
"@types/node": "*"
}
},
"@types/keyv": { "@types/keyv": {
"version": "3.1.4", "version": "3.1.4",
"resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz",
@@ -4122,6 +4144,47 @@
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
"dev": true "dev": true
}, },
"@types/passport": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.11.tgz",
"integrity": "sha512-pz1cx9ptZvozyGKKKIPLcVDVHwae4hrH5d6g5J+DkMRRjR3cVETb4jMabhXAUbg3Ov7T22nFHEgaK2jj+5CBpw==",
"dev": true,
"requires": {
"@types/express": "*"
}
},
"@types/passport-jwt": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@types/passport-jwt/-/passport-jwt-3.0.7.tgz",
"integrity": "sha512-qRQ4qlww1Yhs3IaioDKrsDNmKy6gLDLgFsGwpCnc2YqWovO2Oxu9yCQdWHMJafQ7UIuOba4C4/TNXcGkQfEjlQ==",
"dev": true,
"requires": {
"@types/express": "*",
"@types/jsonwebtoken": "*",
"@types/passport-strategy": "*"
}
},
"@types/passport-local": {
"version": "1.0.34",
"resolved": "https://registry.npmjs.org/@types/passport-local/-/passport-local-1.0.34.tgz",
"integrity": "sha512-PSc07UdYx+jhadySxxIYWuv6sAnY5e+gesn/5lkPKfBeGuIYn9OPR+AAEDq73VRUh6NBTpvE/iPE62rzZUslog==",
"dev": true,
"requires": {
"@types/express": "*",
"@types/passport": "*",
"@types/passport-strategy": "*"
}
},
"@types/passport-strategy": {
"version": "0.2.35",
"resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.35.tgz",
"integrity": "sha512-o5D19Jy2XPFoX2rKApykY15et3Apgax00RRLf0RUotPDUsYrQa7x4howLYr9El2mlUApHmCMv5CZ1IXqKFQ2+g==",
"dev": true,
"requires": {
"@types/express": "*",
"@types/passport": "*"
}
},
"@types/prettier": { "@types/prettier": {
"version": "2.7.0", "version": "2.7.0",
"resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.0.tgz", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.0.tgz",
@@ -13676,6 +13739,38 @@
"resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
"integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==" "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw=="
}, },
"passport": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/passport/-/passport-0.6.0.tgz",
"integrity": "sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug==",
"requires": {
"passport-strategy": "1.x.x",
"pause": "0.0.1",
"utils-merge": "^1.0.1"
}
},
"passport-jwt": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.0.tgz",
"integrity": "sha512-BwC0n2GP/1hMVjR4QpnvqA61TxenUMlmfNjYNgK0ZAs0HK4SOQkHcSv4L328blNTLtHq7DbmvyNJiH+bn6C5Mg==",
"requires": {
"jsonwebtoken": "^8.2.0",
"passport-strategy": "^1.0.0"
}
},
"passport-local": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz",
"integrity": "sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==",
"requires": {
"passport-strategy": "1.x.x"
}
},
"passport-strategy": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
"integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA=="
},
"path-browserify": { "path-browserify": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz",
@@ -13802,6 +13897,11 @@
"integrity": "sha512-TX+cz8Jk+ta7IvRy2FAej8rdlbrP0+uBIkP/5DTODez/AuL/vSb30KuAdDxGVREXzn8QfAiu5mJYJ1XjbOhEPA==", "integrity": "sha512-TX+cz8Jk+ta7IvRy2FAej8rdlbrP0+uBIkP/5DTODez/AuL/vSb30KuAdDxGVREXzn8QfAiu5mJYJ1XjbOhEPA==",
"dev": true "dev": true
}, },
"pause": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
"integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
},
"pause-stream": { "pause-stream": {
"version": "0.0.11", "version": "0.0.11",
"resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz",

View File

@@ -29,7 +29,9 @@
"@nestjs/common": "^9.0.0", "@nestjs/common": "^9.0.0",
"@nestjs/config": "^2.2.0", "@nestjs/config": "^2.2.0",
"@nestjs/core": "^9.0.0", "@nestjs/core": "^9.0.0",
"@nestjs/jwt": "^9.0.0",
"@nestjs/mapped-types": "*", "@nestjs/mapped-types": "*",
"@nestjs/passport": "^9.0.0",
"@nestjs/platform-express": "^9.0.0", "@nestjs/platform-express": "^9.0.0",
"@nestjs/swagger": "^6.1.2", "@nestjs/swagger": "^6.1.2",
"@nestjs/typeorm": "^9.0.1", "@nestjs/typeorm": "^9.0.1",
@@ -56,6 +58,9 @@
"nest-winston": "^1.7.0", "nest-winston": "^1.7.0",
"odbc": "^2.4.6", "odbc": "^2.4.6",
"oracledb": "^5.5.0", "oracledb": "^5.5.0",
"passport": "^0.6.0",
"passport-jwt": "^4.0.0",
"passport-local": "^1.0.0",
"pg": "^8.8.0", "pg": "^8.8.0",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
@@ -76,6 +81,8 @@
"@types/express": "^4.17.13", "@types/express": "^4.17.13",
"@types/jest": "28.1.8", "@types/jest": "28.1.8",
"@types/node": "^16.0.0", "@types/node": "^16.0.0",
"@types/passport-jwt": "^3.0.7",
"@types/passport-local": "^1.0.34",
"@types/supertest": "^2.0.11", "@types/supertest": "^2.0.11",
"@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0", "@typescript-eslint/parser": "^5.0.0",

View File

@@ -11,6 +11,8 @@ import { TemplateModule } from './template/template.module';
import { CommonModule } from './common/common.module'; import { CommonModule } from './common/common.module';
import { ComponentModule } from './component/component.module'; import { ComponentModule } from './component/component.module';
import { ConnectionModule } from './connection/connection.module'; import { ConnectionModule } from './connection/connection.module';
import { UserModule } from './user/user.module';
import { AuthModule } from './auth/auth.module';
@Module({ @Module({
imports: [ imports: [
@@ -39,6 +41,9 @@ import { ConnectionModule } from './connection/connection.module';
CommonModule, CommonModule,
ComponentModule, ComponentModule,
ConnectionModule, ConnectionModule,
UserModule,
AuthModule,
UserModule,
], ],
controllers: [AppController], controllers: [AppController],
providers: [AppService], providers: [AppService],

View File

@@ -0,0 +1,22 @@
import { Module } from '@nestjs/common';
import { UserModule } from 'src/user/user.module';
import { AuthService } from './auth.service';
import { JwtModule, JwtService } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { JwtStrategy } from './strategies/jwt.strategy';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from '../user/entities/user.entity.js';
import { UserService } from 'src/user/user.service';
@Module({
imports: [
TypeOrmModule.forFeature([User]),
PassportModule,
JwtModule.register({
secret: process.env.JWT_ACCESS_SECRET,
signOptions: { expiresIn: '30s'}
})
],
providers: [AuthService]
})
export class AuthModule {}

View File

@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AuthService } from './auth.service';
describe('AuthService', () => {
let service: AuthService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [AuthService],
}).compile();
service = module.get<AuthService>(AuthService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@@ -0,0 +1,42 @@
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UserService } from 'src/user/user.service';
import { NestFactory } from '@nestjs/core';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from 'src/user/entities/user.entity';
@Injectable()
export class AuthService {
constructor(
private jwtService: JwtService,
@InjectRepository(User) private readonly userRepository: Repository<User> ){}
async generateAccessToken(email: string) {
console.log(email)
const accesstoken = await this.jwtService.sign({email: email}, {
secret: process.env.ACCESS_SECRET,
expiresIn: `30s`
})
return accesstoken
// accesstoken이 없을때
}
async generateRefreshToken(email: string) {
const refreshtoken = await this.jwtService.sign({email: email}, { secret: process.env.REFRESH_SECRET, expiresIn: "3600s" })
return refreshtoken
// accesstoken이 없을때
}
async setRefreshKey(){
}
async validateUser(email: string, pass: string) {
const user = await this.userRepository.findOne({ where: { email: email } });
if (user && user.password === pass) {
delete user.password;
return user;
}
}
}

View File

@@ -0,0 +1,5 @@
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}

View File

@@ -0,0 +1,19 @@
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: process.env.JWT_ACCESS_SECRET
});
console.log(process.env.JWT_ACCESS_SCRET)
}
async validate(payload: any) {
return { userId: payload.sub, username: payload.username };
}
}

View File

@@ -0,0 +1,16 @@
import { IsNotEmpty, IsNumber, IsString } from 'class-validator';
export class CreateUserDto {
@IsString()
@IsNotEmpty()
username: string;
@IsString()
@IsNotEmpty()
password: string;
@IsString()
@IsNotEmpty()
email: string;
}

View File

@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/swagger';
import { CreateUserDto } from './create-user.dto';
export class UpdateUserDto extends PartialType(CreateUserDto) {}

View File

@@ -0,0 +1,21 @@
import { IsNotEmpty, IsOptional } from 'class-validator';
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn} from 'typeorm';
import { BaseEntity } from '../../common/entities/base.entity';
@Entity()
export class User extends BaseEntity{
@PrimaryGeneratedColumn()
id: number;
@IsNotEmpty()
@Column()
username: string;
@IsNotEmpty()
@Column()
email: string;
@IsNotEmpty()
@Column()
password: string;
}

View File

@@ -0,0 +1,20 @@
import { Test, TestingModule } from '@nestjs/testing';
import { UserController } from './user.controller';
import { UserService } from './user.service';
describe('UserController', () => {
let controller: UserController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [UserController],
providers: [UserService],
}).compile();
controller = module.get<UserController>(UserController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@@ -0,0 +1,64 @@
import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards, UsePipes, ValidationPipe, Res } from '@nestjs/common';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { JwtAuthGuard } from 'src/auth/guards/jwt-auth.guard';
import { AuthService } from 'src/auth/auth.service';
@Controller('user')
export class UserController {
constructor( private readonly userService: UserService,
private authService: AuthService) {}
@Post('signin')
async login(@Res() res, @Body() loginDto: UpdateUserDto){
const findUser = await this.userService.signin(loginDto)
const accessToken = await this.authService.generateAccessToken( findUser.email );
const refreshToken = await this.authService.generateRefreshToken( findUser.email );
res.cookie('jwt_ac', accessToken, {
httpOnly: true,
saemSite: 'none',
secure: true
});
res.cookie('jwt_re', refreshToken, {
httpOnly: true,
saemSite: 'none',
secure: true
})
return res.status(201).send('ok')
}
@UsePipes(ValidationPipe)
@Post('signup')
create(@Body() createUserDto: CreateUserDto) {
return this.userService.create(createUserDto);
}
@UseGuards(JwtAuthGuard)
@Get('userinfo/:email')
findOne(@Param('email') email: string) {
return this.userService.findOne(email);
}
@UseGuards(JwtAuthGuard)
@Patch('change-password')
updatePassword(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
return this.userService.updatePassword(+id, updateUserDto);
}
@UseGuards(JwtAuthGuard)
@Patch('change-username')
updateUsername(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
return this.userService.updateUsername(+id, updateUserDto);
}
@UseGuards(JwtAuthGuard)
@Delete('delete/:id')
deleteUser(@Param('id') id: string) {
return this.userService.deleteUser(+id);
}
}

View File

@@ -0,0 +1,15 @@
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { User } from './entities/user.entity.js';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AuthService } from 'src/auth/auth.service';
import { AuthModule } from 'src/auth/auth.module';
import { JwtModule } from '@nestjs/jwt';
@Module({
imports: [TypeOrmModule.forFeature([User]), AuthModule, JwtModule],
controllers: [UserController],
providers: [UserService, AuthService]
})
export class UserModule {}

View File

@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { UserService } from './user.service';
describe('UserService', () => {
let service: UserService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [UserService],
}).compile();
service = module.get<UserService>(UserService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@@ -0,0 +1,86 @@
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { AuthService } from '../auth/auth.service.js';
import { Repository } from 'typeorm';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { User } from './entities/user.entity';
@Injectable()
export class UserService {
constructor(
@InjectRepository(User) private readonly userRepository: Repository<User>,
private authService: AuthService,
) {}
async signin( loginDto: UpdateUserDto ) {
const { email, password } = loginDto;
const findUser = await this.authService.validateUser(email, password);
if (!findUser) {
throw new UnauthorizedException(`No exist user ${email}`);
}
return findUser
}
async create(createUserDto: CreateUserDto) {
const userInfo = await this.userRepository.findOne({
where: { email: createUserDto.email }
});
if(!userInfo){
const { email, password, username } = createUserDto;
await this.userRepository.save({
email: email,
password: password,
user_name: username
});
}
return 'This action adds a new user';
}
async findOne(email: string) {
const userData = await this.userRepository.findOne({
where: { email: email }
});
if(!userData){
return null
} else {
delete userData.password;
return userData
}
}
async updatePassword(id: number, updateUserDto: UpdateUserDto) {
const updateData = await this.userRepository.findOne({ where: { id: id }});
if(!updateData){
return 'not exist user';
} else {
updateData.password = String(updateUserDto.password);
await this.userRepository.save(updateData)
return `This action update_password a #${id} user`;
}
}
async updateUsername(id: number, updateUserDto: UpdateUserDto) {
const updateData = await this.userRepository.findOne({ where: { id: id }});
if(!updateData){
return 'not exist user';
} else {
updateData.username = String(updateUserDto.username);
await this.userRepository.save(updateData)
return `This action update_name a #${id} user`;
}
}
async deleteUser(id: number) {
const findUser = await this.userRepository.findOne({
where: { id: id }
});
if(!findUser){
return 'not exist user'
} else {
await this.userRepository.delete(findUser.id)
return `This action removes a #${id} user`;
}
}
}

View File

@@ -19,7 +19,7 @@ export function getTestMysqlModule(): DynamicModule {
autoLoadEntities: true, autoLoadEntities: true,
entities: [entityUrl], entities: [entityUrl],
synchronize: false, synchronize: false,
logging: process.env.NODE_ENV == 'dev', logging: true,
retryAttempts: 1, retryAttempts: 1,
}); });
} }