Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions violet-server/server/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ export const envValidationSchema = Joi.object({
@Module({
imports: [
ThrottlerModule.forRoot([{
ttl: 1000, // 1초
limit: 1, // 1초에 1번만 요청 가능
ttl: 5000, // 5초
limit: 5, // 5초에 5번까지 요청 가능
}]),

ConfigModule.forRoot({
Expand Down
43 changes: 23 additions & 20 deletions violet-server/server/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { User } from 'src/user/entity/user.entity';
import { AuthService } from './auth.service';
import { AccessTokenGuard } from './guards/access-token.guard';
import { Tokens } from './jwt/jwt.token';
import { Request, Response } from 'express';
import { Request, response, Response } from 'express';
import { ResLoginUser } from './dtos/res-login-user.dto';
import { HmacAuthGuard } from './guards/hmac.guard';
import { DiscordAuthGuard } from './guards/discord.guard';
Expand All @@ -29,21 +29,33 @@ export class AuthController {
constructor(
private readonly configService: ConfigService,
private readonly authService: AuthService,
) {}
) { }

@Post()
@UseGuards(HmacAuthGuard)
@ApiOperation({ summary: 'Login' })
@ApiCreatedResponse({ description: 'jwt token', type: Tokens })
@Redirect('violet://login')
// @Redirect('violet://login')
async logIn(
@Body() dto: UserRegisterDTO,
@Res({ passthrough: true }) res: Response,
): Promise<Tokens> {
const accessExpires = new Date(
Date.now() + Number(this.configService.get<number>('ACCESS_EXPIRES')) * 1000,
);
const refreshExpires = new Date(
Date.now() + Number(this.configService.get<number>('REFRESH_EXPIRES')) * 1000,
);
const { tokens } = await this.authService.verifyUserAndSignJWT(dto);

res.cookie('jwt-access', tokens.accessToken, { httpOnly: true });
res.cookie('jwt-refresh', tokens.refreshToken, { httpOnly: true });
res.cookie('jwt-access', tokens.accessToken, {
expires: accessExpires,
httpOnly: true,
});
res.cookie('jwt-refresh', tokens.refreshToken, {
expires: refreshExpires,
httpOnly: true,
});
return tokens;
}

Expand All @@ -55,33 +67,24 @@ export class AuthController {
@Res({ passthrough: true }) response: Response,
): Promise<ResLoginUser> {
const accessExpires = new Date(
Date.now() + Number(this.configService.get<string>('ACCESS_EXPIRES')),
);
const refreshExpires = new Date(
Date.now() + Number(this.configService.get<string>('REFRESH_EXPIRES')),
Date.now() + Number(this.configService.get<number>('ACCESS_EXPIRES')) * 1000,
);
const refreshToken = req.cookies['jwt-refresh'];
const resRefreshData = await this.authService.refreshTokens(
req.cookies['jwt-refresh'],
refreshToken,
);
const expires = this.authService.getTokenExpires(refreshToken);

response.cookie('jwt-access', resRefreshData.tokens.accessToken, {
expires: accessExpires,
httpOnly: true,
});
// TODO: 이거 안보내줘도 되지 않나?
response.cookie('jwt-refresh', resRefreshData.tokens.refreshToken, {
expires: refreshExpires,
expires: new Date(expires * 1000),
httpOnly: true,
});

response.cookie('refresh-expires', refreshExpires, {
httpOnly: false,
expires: refreshExpires,
});
response.cookie('access-expires', refreshExpires, {
expires: accessExpires,
httpOnly: false,
});

return resRefreshData;
}

Expand Down
45 changes: 24 additions & 21 deletions violet-server/server/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export class AuthService {
private readonly userRepository: UserRepository,
private readonly jwtService: JwtService,
private readonly configService: ConfigService,
) {}
) { }

async verifyUserAndSignJWT(dto: UserRegisterDTO): Promise<ResLoginUser> {
const user = await this.userRepository.findOneBy({
Expand All @@ -45,35 +45,32 @@ export class AuthService {
};
}

async createJWT(userAppId: string): Promise<Tokens> {
const accessExpires = Number(
new Date(
Date.now() + Number(this.configService.get<string>('ACCESS_EXPIRES')),
),
);
const refreshExpires = Number(
new Date(
Date.now() + Number(this.configService.get<string>('REFRESH_EXPIRES')),
),
);
async createJWT(userAppId: string, customExpires?: number): Promise<Tokens> {
const refreshPayload = {
userAppId,
} as any;
const refreshOptions = {
secret: this.configService.get<string>('REFRESH_TOKEN_SECRET_KEY'),
expiresIn: Number(this.configService.get<number>('REFRESH_EXPIRES')),
};
if (customExpires) {
refreshPayload.exp = customExpires;
delete refreshOptions.expiresIn;
}

const [accessToken, refreshToken] = await Promise.all([
this.jwtService.signAsync(
{
userAppId,
},
{
secret: this.configService.get<string>('ACCESS_TOKEN_SECRET_KEY'),
expiresIn: accessExpires,
expiresIn: Number(this.configService.get<number>('ACCESS_EXPIRES')),
},
),
this.jwtService.signAsync(
{
userAppId,
},
{
secret: this.configService.get<string>('REFRESH_TOKEN_SECRET_KEY'),
expiresIn: refreshExpires,
},
refreshPayload,
refreshOptions,
),
]);

Expand All @@ -84,6 +81,11 @@ export class AuthService {
await this.userRepository.update({ userAppId }, { refreshToken });
}

getTokenExpires(token: string): number {
const decoded = this.jwtService.decode(token);
return Number(decoded.exp);
}

async refreshTokens(refreshToken: string): Promise<ResLoginUser> {
const user = await this.userRepository.findOneBy({
refreshToken: refreshToken,
Expand All @@ -93,7 +95,8 @@ export class AuthService {
throw new HttpException('Invalid Token', 401);
}

const tokens = await this.createJWT(user.userAppId);
const expires = this.getTokenExpires(refreshToken);
const tokens = await this.createJWT(user.userAppId, expires);
await this.updateRefreshToken(user.userAppId, tokens.refreshToken);

return { tokens, user };
Expand Down
2 changes: 2 additions & 0 deletions violet-server/server/src/comment/comment.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { AccessTokenGuard } from 'src/auth/guards/access-token.guard';
import { CommentGetDto, CommentGetResponseDto } from './dtos/comment-get.dto';
import { CommonResponseDto } from 'src/common/dtos/common.dto';
import { CommentOwnerGuard } from './guards/comment-owner.guard';
import { Throttle } from '@nestjs/throttler';

@ApiTags('comment')
@Controller('comment')
Expand All @@ -38,6 +39,7 @@ export class CommentController {
return await this.commentService.getComment(dto);
}

@Throttle({ default: { limit: 2, ttl: 60000 } })
@Post('/')
@UsePipes(new ValidationPipe({ transform: true }))
@ApiOperation({ summary: 'Post Comment' })
Expand Down
20 changes: 20 additions & 0 deletions violet-server/server/src/common/filters/empty-body.filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Response } from 'express';

@Catch(HttpException)
export class EmptyBodyHttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const res = ctx.getResponse<Response>();
const status = exception.getStatus();

if (status >= 400 && status < 500) {
res.status(status).end(); // body completely empty
} else {
res.status(status).json({
statusCode: status,
message: exception.message,
});
}
}
}
2 changes: 1 addition & 1 deletion violet-server/server/src/discord/discord.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export class DiscordService {
fields: [
{
name: 'Author',
value: username,
value: username.slice(0, 8),
}
],
timestamp: new Date().toISOString(),
Expand Down
9 changes: 8 additions & 1 deletion violet-server/server/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { setupSwagger } from './common/utils/swagger';
import { logger } from './common/utils/logger';
import * as cookies from 'cookie-parser';
import { ValidationPipe } from '@nestjs/common';
import { EmptyBodyHttpExceptionFilter } from './common/filters/empty-body.filter';

async function bootstrap() {
const app = await NestFactory.create(AppModule, { logger: logger });
Expand All @@ -18,7 +19,13 @@ async function bootstrap() {
credentials: true, // 쿠키와 인증 헤더를 포함한 요청 허용
});

setupSwagger(app);
if (process.env.NODE_ENV === 'prod') {
app.useGlobalFilters(new EmptyBodyHttpExceptionFilter());
}

if (process.env.NODE_ENV === 'dev') {
setupSwagger(app);
}

await app.listen(3000);
}
Expand Down
3 changes: 3 additions & 0 deletions violet-server/server/src/user/entity/user.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,22 @@ export class User extends CoreEntity {

@ApiProperty({
description: 'Discord Id',
required: false,
})
@Column({ nullable: true })
@Index()
discordId?: string;

@ApiProperty({
description: 'Avatar',
required: false,
})
@Column({ nullable: true })
avatar?: string;

@ApiProperty({
description: 'Nickname',
required: false,
})
@Column({ unique: true, nullable: true })
nickname?: string;
Expand Down
18 changes: 9 additions & 9 deletions violet/lib/api/api.swagger.dart
Original file line number Diff line number Diff line change
Expand Up @@ -534,9 +534,9 @@ class User {
required this.updatedAt,
required this.userAppId,
required this.role,
required this.discordId,
required this.avatar,
required this.nickname,
this.discordId,
this.avatar,
this.nickname,
});

factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Expand All @@ -562,11 +562,11 @@ class User {
userRoleFromJson(value, enums.UserRole.user);

@JsonKey(name: 'discordId')
final String discordId;
final String? discordId;
@JsonKey(name: 'avatar')
final String avatar;
final String? avatar;
@JsonKey(name: 'nickname')
final String nickname;
final String? nickname;
static const fromJsonFactory = _$UserFromJson;

@override
Expand Down Expand Up @@ -639,9 +639,9 @@ extension $UserExtension on User {
Wrapped<DateTime>? updatedAt,
Wrapped<String>? userAppId,
Wrapped<enums.UserRole>? role,
Wrapped<String>? discordId,
Wrapped<String>? avatar,
Wrapped<String>? nickname}) {
Wrapped<String?>? discordId,
Wrapped<String?>? avatar,
Wrapped<String?>? nickname}) {
return User(
id: (id != null ? id.value : this.id),
createdAt: (createdAt != null ? createdAt.value : this.createdAt),
Expand Down
6 changes: 3 additions & 3 deletions violet/lib/api/api.swagger.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 9 additions & 7 deletions violet/lib/pages/lab/lab/global_comments.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:mdi/mdi.dart';
import 'package:violet/api/api.swagger.dart';
import 'package:violet/locale/locale.dart';
import 'package:violet/other/dialogs.dart';
import 'package:violet/pages/lab/lab/recent_user_record.dart';
import 'package:violet/pages/segment/card_panel.dart';
import 'package:violet/pages/segment/platform_navigator.dart';
import 'package:violet/server/community/anon.dart';
import 'package:violet/server/violet_v2.dart';
import 'package:violet/settings/settings.dart';

Expand Down Expand Up @@ -163,12 +163,14 @@ class _LabGlobalCommentsState extends State<LabGlobalComments> {
Translations.instance!.trans('comment'));
return;
}
if (!modReply) {
await VioletCommunityAnonymous.postArtistComment(
null, 'global_general', text.text);
} else {
await VioletCommunityAnonymous.postArtistComment(
replyParent, 'global_general', text.text);
await VioletServerV2.postComment(
CommentPostDto(
where: 'general',
body: text.text,
parent: modReply ? replyParent : null,
),
);
if (modReply) {
replyParent = null;
modReply = false;
}
Expand Down
2 changes: 2 additions & 0 deletions violet/lib/pages/splash/splash_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ class _SplashPageState extends State<SplashPage> {
await IsolateDownloader.getInstance();
_changeMessage('init api...');
VioletServerV2.init();
_changeMessage('init session manager...');
await SessionManager.refresh();

// this may be slow down to loading
_changeMessage('check network...');
Expand Down
Loading
Loading