Skip to content

Commit

Permalink
0.2.5 #58
Browse files Browse the repository at this point in the history
0.2.5
  • Loading branch information
raymondanythings authored Dec 31, 2023
2 parents a70eb3d + 10d31e2 commit 9266ef2
Show file tree
Hide file tree
Showing 8 changed files with 161 additions and 12 deletions.
2 changes: 2 additions & 0 deletions global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ declare namespace NodeJS {
readonly GOOGLE_AUTH_CLIENT_ID: string;
readonly GOOGLE_REDIRECT_URL: string;
readonly OOGLE_AUTH_CLIENT_SECRET: string;
readonly KAKAO_CLIENT_ID: string;
readonly KAKAO_REDIRECT_URL: string;
readonly SALT: string;
readonly ROUND: string;
readonly EXPIRESTOKEN: string;
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "shinnyang",
"version": "0.2.4",
"version": "0.2.5",
"description": "",
"author": {
"name": "Medici",
Expand Down
2 changes: 2 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import { MailsModule } from './mails/mails.module';
GOOGLE_AUTH_CLIENT_ID: Joi.string().required(),
GOOGLE_AUTH_CLIENT_SECRET: Joi.string().required(),
GOOGLE_REDIRECT_URL: Joi.string().required(),
KAKAO_CLIENT_ID: Joi.string().required(),
KAKAO_REDIRECT_URL: Joi.string().required(),
DB_HOST: Joi.string().required(),
DB_PORT: Joi.string().required(),
DB_USER: Joi.string().required(),
Expand Down
1 change: 1 addition & 0 deletions src/oauth/dtos/service-provider.dto.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export enum ServiceProvider {
GOOGLE = 'google',
KAKAO = 'kakao',
}
16 changes: 14 additions & 2 deletions src/oauth/oauth.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import { Controller, Get, Param, Post, Query, Res } from '@nestjs/common';
import {
BadRequestException,
Controller,
Get,
Param,
Post,
Query,
Res,
} from '@nestjs/common';
import { OauthService } from './oauth.service';
import { Response } from 'express';
import {
Expand Down Expand Up @@ -40,11 +48,15 @@ export class OauthController {
@Param('serviceName', new ParseExplicitEnumPipe(ServiceProvider))
serviceName: ServiceProvider,
@Query('code') code: string,
// @Query('state') state: string,
): Promise<JWT> {
switch (serviceName) {
case ServiceProvider.GOOGLE:
return await this.oauthService.userFromGoogle(code);
case ServiceProvider.KAKAO:
return await this.oauthService.userFromKakao(code);
default:
break;
}
throw new BadRequestException();
}
}
103 changes: 96 additions & 7 deletions src/oauth/oauth.service.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,39 @@
import { AuthService } from './../auth/auth.service';
import { UserService } from './../users/user.service';
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
import axios from 'axios';
import { GoogleUserInfo } from './dtos/google.dto';
import { URL } from 'url';
import { InjectDataSource } from '@nestjs/typeorm';
import { DataSource } from 'typeorm';
import axios from 'axios';
import { JWT } from 'src/auth/dtos/jwt.dto';
import { User } from 'src/users/entities/user.entity';
import { DataSource } from 'typeorm';
import { URL } from 'url';
import { AuthService } from './../auth/auth.service';
import { UserService } from './../users/user.service';
import { GoogleUserInfo } from './dtos/google.dto';
import { ServiceProvider } from './dtos/service-provider.dto';
import { JWT } from 'src/auth/dtos/jwt.dto';

export interface KakaoUser {
id: number;
connected_at: string;
properties: Properties;
kakao_account: KakaoAccount;
}

export interface Properties {
nickname: string;
}

export interface KakaoAccount {
profile_nickname_needs_agreement: boolean;
profile: Profile;
has_email: boolean;
email_needs_agreement: boolean;
is_email_valid: boolean;
is_email_verified: boolean;
email: string;
}

export interface Profile {
nickname: string;
}

@Injectable()
export class OauthService {
Expand All @@ -35,6 +60,15 @@ export class OauthService {
);
url.searchParams.set('access_type', 'offline');
return url;
case ServiceProvider.KAKAO:
const kakaoURL = new URL('https://kauth.kakao.com/oauth/authorize');
kakaoURL.searchParams.set('client_id', process.env.KAKAO_CLIENT_ID);
kakaoURL.searchParams.set('response_type', 'code');
kakaoURL.searchParams.set(
'redirect_uri',
process.env.KAKAO_REDIRECT_URL,
);
return kakaoURL;
default:
break;
}
Expand Down Expand Up @@ -79,4 +113,59 @@ export class OauthService {
throw new BadRequestException('invalid request: ' + err?.message || '');
}
}
async userFromKakao(code: string): Promise<JWT> {
try {
const response = await axios.post<{
access_token: string;
token_type: string;
refresh_token: string;
expires_in: number;
scope: string;
refresh_token_expires_in: number;
}>(
'https://kauth.kakao.com/oauth/token ',
{
code,
grant_type: 'authorization_code',
client_id: process.env.KAKAO_CLIENT_ID,
redirect_uri: process.env.KAKAO_REDIRECT_URL,
},
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
},
);

if (!response.data['access_token']) {
throw new BadRequestException('Access-Token을 받아오지 못 했습니다.');
}
// 회원정보 가져오기
const userUrl = 'https://kapi.kakao.com/v2/user/me';
const userResponse = await axios.get<KakaoUser>(userUrl, {
params: {
access_token: response.data['access_token'],
},
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
},
});
const kakaoUser = userResponse.data;
let user = await this.userService.findByUserEmail(
kakaoUser.kakao_account.email,
);
if (!user) {
user = await this.userService.create({
email: kakaoUser.kakao_account.email,
});
}
const token = this.authService.sign(user.id);
user.refresh = token.refresh;
await this.dataSource.getRepository(User).save(user);
return new JWT(token);
} catch (err) {
this.logger.error(err.message);
throw new BadRequestException('invalid request: ' + err?.message || '');
}
}
}
7 changes: 5 additions & 2 deletions src/users/dtos/set-nickname.dto.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, MaxLength } from 'class-validator';
import { IsString } from 'class-validator';
import { MaxByteLength } from '../validator/max-length-byte.validator';

export class ChangeNicknameDTO {
@ApiProperty({ description: '번경할 닉네임', default: '츄츄' })
@IsString()
@MaxLength(6)
@MaxByteLength(12, {
message: '닉네임이 너무 길어요.',
})
nickname: string;
}
40 changes: 40 additions & 0 deletions src/users/validator/max-length-byte.validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { registerDecorator, ValidationOptions } from 'class-validator';

const LINE_FEED = 10; // '\n'

export function getByteLength(decimal: number) {
return decimal >> 7 || LINE_FEED === decimal ? 2 : 1;
}

export function getLimitedByteText(inputText: string, maxByte: number) {
const characters = inputText.split('');

return (
characters.reduce((acc, cur) => {
const decimal = cur.charCodeAt(0);
const byte = getByteLength(decimal); // 글자 한 개가 몇 바이트 길이인지 구해주기
return acc + byte;
}, 0) <= maxByte
);
}

export function MaxByteLength(
max: number,
validationOptions?: ValidationOptions,
) {
return function (object: object, propertyName: string) {
registerDecorator({
name: 'MaxByteLength',
target: object.constructor,
async: false,
propertyName: propertyName,
constraints: [], // * 아래 validate 의 args.contraints로 넘어감
options: validationOptions,
validator: {
validate(value: any): boolean {
return getLimitedByteText(value, max);
},
},
});
};
}

0 comments on commit 9266ef2

Please sign in to comment.