Skip to content

Commit

Permalink
Merge branch 'main' into feat/frontend/online-status/avatar
Browse files Browse the repository at this point in the history
  • Loading branch information
kotto5 authored Feb 17, 2024
2 parents fb2427c + c98b566 commit e62a2ed
Show file tree
Hide file tree
Showing 33 changed files with 131 additions and 69 deletions.
14 changes: 12 additions & 2 deletions backend/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
Body,
Controller,
Delete,
Get,
HttpCode,
Post,
Expand All @@ -16,14 +17,14 @@ import {
ApiTags,
} from '@nestjs/swagger';
import type { User } from '@prisma/client';
import { Response } from 'express';
import { CurrentUser } from 'src/common/decorators/current-user.decorator';
import { AuthService } from './auth.service';
import { LoginDto } from './dto/login.dto';
import { TwoFactorAuthenticationDto } from './dto/twoFactorAuthentication.dto';
import { TwoFactorAuthenticationEnableDto } from './dto/twoFactorAuthenticationEnable.dto';
import { AuthEntity } from './entity/auth.entity';
import { JwtGuardWithout2FA } from './jwt-auth.guard';
import { Response } from 'express';
import { JwtAuthGuard, JwtGuardWithout2FA } from './jwt-auth.guard';

const constants = {
loginUrl: ((): string => {
Expand Down Expand Up @@ -121,4 +122,13 @@ export class AuthController {
) {
return this.authService.twoFactorAuthenticate(dto, user.id);
}

@Delete('2fa/disable')
@HttpCode(200)
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOkResponse()
async disable2FA(@CurrentUser() user: User) {
return this.authService.disableTwoFactorAuthentication(user.id);
}
}
15 changes: 15 additions & 0 deletions backend/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,21 @@ export class AuthService {
});
}

disableTwoFactorAuthentication(userId: number) {
return this.prisma.$transaction(async (prisma) => {
const user = await prisma.user.findUnique({ where: { id: userId } });
if (!user.twoFactorEnabled) {
throw new ConflictException('2FA secret is not enabled');
}
await prisma.user.update({
where: { id: user.id },
data: {
twoFactorEnabled: false,
},
});
});
}

async twoFactorAuthenticate(dto: TwoFactorAuthenticationDto, userId: number) {
const user = await this.prisma.user.findUnique({ where: { id: userId } });
if (!user.twoFactorEnabled) {
Expand Down
6 changes: 3 additions & 3 deletions backend/src/chat/chat.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ import {
import { Server, Socket } from 'socket.io';
import { RoomDeletedEvent } from 'src/common/events/room-deleted.event';
import { RoomEnteredEvent } from 'src/common/events/room-entered.event';
import { RoomLeftEvent } from 'src/common/events/room-left.event';
import { RoomMuteEvent } from 'src/common/events/room-mute.event';
import { RoomUnmuteEvent } from 'src/common/events/room-unmute.event';
import { RoomLeftEvent } from 'src/common/events/room-left.event';
import { RoomUpdateRoleEvent } from 'src/common/events/room-update-role.event';
import { ChatService, UserStatus } from './chat.service';
import { MuteService } from 'src/room/mute/mute.service';
import { v4 } from 'uuid';
import { ChatService, UserStatus } from './chat.service';
import { CreateMessageDto } from './dto/create-message.dto';
import { MessageEntity } from './entities/message.entity';
import { v4 } from 'uuid';

@WebSocketGateway({
cors: {
Expand Down
2 changes: 1 addition & 1 deletion backend/src/chat/chat.module.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Module } from '@nestjs/common';
import { AuthModule } from 'src/auth/auth.module';
import { MuteService } from 'src/room/mute/mute.service';
import { PrismaModule } from 'src/prisma/prisma.module';
import { MuteService } from 'src/room/mute/mute.service';
import { UserService } from '../user/user.service';
import { ChatController } from './chat.controller';
import { ChatGateway } from './chat.gateway';
Expand Down
2 changes: 1 addition & 1 deletion backend/src/room/guards/enter-room.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import {
ForbiddenException,
Injectable,
} from '@nestjs/common';
import { RoomService } from '../room.service';
import { compare } from 'bcrypt';
import { RoomService } from '../room.service';

@Injectable()
export class EnterRoomGuard implements CanActivate {
Expand Down
8 changes: 4 additions & 4 deletions backend/src/room/mute/mute.controller.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import {
Controller,
Get,
Put,
Body,
Controller,
Delete,
Get,
Param,
ParseIntPipe,
Put,
UseGuards,
} from '@nestjs/common';
import { CreateMuteDto } from './dto/create-mute.dto';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { JwtAuthGuard } from 'src/auth/jwt-auth.guard';
import { AdminGuard } from '../guards/admin.guard';
import { CreateMuteDto } from './dto/create-mute.dto';
import { MuteService } from './mute.service';

@ApiTags('mute')
Expand Down
2 changes: 1 addition & 1 deletion backend/src/room/mute/mute.module.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Module } from '@nestjs/common';
import { PrismaModule } from 'src/prisma/prisma.module';
import { MuteService } from './mute.service';
import { MuteController } from './mute.controller';
import { MuteService } from './mute.service';

@Module({
controllers: [MuteController],
Expand Down
2 changes: 1 addition & 1 deletion backend/src/room/mute/mute.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import {
Injectable,
NotFoundException,
} from '@nestjs/common';
import { CreateMuteDto } from './dto/create-mute.dto';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { RoomMuteEvent } from 'src/common/events/room-mute.event';
import { RoomUnmuteEvent } from 'src/common/events/room-unmute.event';
import { PrismaService } from 'src/prisma/prisma.service';
import { CreateMuteDto } from './dto/create-mute.dto';

@Injectable()
export class MuteService {
Expand Down
2 changes: 1 addition & 1 deletion backend/src/room/room.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
} from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { Role, User } from '@prisma/client';
import { hash } from 'bcrypt';
import { RoomCreatedEvent } from 'src/common/events/room-created.event';
import { RoomDeletedEvent } from 'src/common/events/room-deleted.event';
import { RoomEnteredEvent } from 'src/common/events/room-entered.event';
Expand All @@ -16,7 +17,6 @@ import { UpdateUserOnRoomDto } from './dto/update-UserOnRoom.dto';
import { UpdateRoomDto } from './dto/update-room.dto';
import { UserOnRoomEntity } from './entities/UserOnRoom.entity';
import { RoomEntity } from './entities/room.entity';
import { hash } from 'bcrypt';

@Injectable()
export class RoomService {
Expand Down
11 changes: 11 additions & 0 deletions backend/test/auth.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,5 +77,16 @@ describe('AuthController (e2e)', () => {
it('[GET /user/me] should return 200 if 2FA is enabled and code is provided', async () => {
await app.getMe(user.accessToken).expect(200);
});

it('[DELETE /auth/2fa/disable] should disable 2FA', async () => {
await app.disableTwoFactorAuthentication(user.accessToken).expect(200);
});

it('[POST /auth/2fa/enable] should re-enable 2FA', async () => {
const code = authenticator.generate(secret);
await app
.enableTwoFactorAuthentication(code, user.accessToken)
.expect(200);
});
});
});
4 changes: 2 additions & 2 deletions backend/test/chat-gateway.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Role } from '@prisma/client';
import { INestApplication } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { Role } from '@prisma/client';
import { Socket, io } from 'socket.io-client';
import { AppModule } from 'src/app.module';
import { MessageEntity } from 'src/chat/entities/message.entity';
import { CreateRoomDto } from 'src/room/dto/create-room.dto';
import { constants } from './constants';
import { TestApp, UserEntityWithAccessToken } from './utils/app';
import { CreateRoomDto } from 'src/room/dto/create-room.dto';

async function createNestApp(): Promise<INestApplication> {
const moduleFixture: TestingModule = await Test.createTestingModule({
Expand Down
2 changes: 1 addition & 1 deletion backend/test/online-status.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { INestApplication } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { Socket, io } from 'socket.io-client';
import { AppModule } from 'src/app.module';
import { UserStatus } from 'src/chat/chat.service';
import { TestApp, UserEntityWithAccessToken } from './utils/app';
import { expectOnlineStatusResponse } from './utils/matcher';
import { UserStatus } from 'src/chat/chat.service';

type UserAndSocket = {
user: UserEntityWithAccessToken;
Expand Down
5 changes: 5 additions & 0 deletions backend/test/utils/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ export class TestApp {
.set('Authorization', `Bearer ${accessToken}`)
.send({ code });

disableTwoFactorAuthentication = (accessToken: string) =>
request(this.app.getHttpServer())
.delete('/auth/2fa/disable')
.set('Authorization', `Bearer ${accessToken}`);

twoFactorAuthenticate = (code: string, accessToken: string) =>
request(this.app.getHttpServer())
.post('/auth/2fa/authenticate')
Expand Down
6 changes: 3 additions & 3 deletions frontend/app/explore-rooms/explore-rooms.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"use client";

import RoomCard from "./room-card";
import { useCallback, useEffect } from "react";
import { useRouter } from "next/navigation";
import { DeleteRoomEvent, RoomEntity } from "@/app/lib/dtos";
import { chatSocket as socket } from "@/socket";
import { useRouter } from "next/navigation";
import { useCallback, useEffect } from "react";
import RoomCard from "./room-card";

export function ExploreRooms({ rooms }: { rooms: RoomEntity[] }) {
const router = useRouter();
Expand Down
19 changes: 14 additions & 5 deletions frontend/app/lib/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,21 +106,30 @@ export async function updateUser(
prevState: string | undefined,
formData: FormData,
) {
const { user_id, ...updateData } = Object.fromEntries(formData.entries());
const res = await fetch(`${process.env.API_URL}/user/${user_id}`, {
const userId = await getCurrentUserId();
const newNickname = formData.get("name");
const newEmail = formData.get("email");
const res = await fetch(`${process.env.API_URL}/user/${userId}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + getAccessToken(),
},
body: JSON.stringify(updateData),
body: JSON.stringify({ name: newNickname, email: newEmail }),
});
const data = await res.json();
if (res.status === 401) {
redirect("/login");
} else if (res.status === 409) {
console.error("updateUser error: ", data);
return "The name or email is already in use";
}
if (!res.ok) {
return "Error";
console.error("updateUser error: ", data);
return data.message;
} else {
revalidatePath("/user");
revalidatePath(`/user/${user_id}`);
revalidatePath(`/user/${userId}`);
return "Success";
}
}
Expand Down
8 changes: 3 additions & 5 deletions frontend/app/lib/client-socket-provider.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
"use client";
import { ToastAction } from "@/components/ui/toast";
import { useToast } from "@/components/ui/use-toast";
import { chatSocket } from "@/socket";
import { chatSocket, chatSocket as socket } from "@/socket";
import Link from "next/link";
import { useCallback, useEffect } from "react";
import { usePathname, useRouter } from "next/navigation";
import { useEffect } from "react";
import { useAuthContext } from "./client-auth";
import {
DenyEvent,
Expand All @@ -12,9 +13,6 @@ import {
MessageEvent,
PublicUserEntity,
} from "./dtos";
import { chatSocket as socket } from "@/socket";
import { useRouter } from "next/navigation";
import { usePathname } from "next/navigation";

export default function SocketProvider() {
const { toast } = useToast();
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/lib/hooks/useInviteToGame.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { useCallback, useState } from "react";
import { chatSocket as socket } from "@/socket";
import { useCallback, useState } from "react";

export const useInviteToGame = (userId: number) => {
const [isInvitingToGame, setIsInvitingToGame] = useState(false);
Expand Down
7 changes: 3 additions & 4 deletions frontend/app/lib/hooks/useKick.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
"use client";

import { kickUserOnRoom } from "@/app/lib/actions";
import type { LeaveRoomEvent, UserOnRoomEntity } from "@/app/lib/dtos";
import type { LeaveRoomEvent } from "@/app/lib/dtos";
import { toast } from "@/components/ui/use-toast";
import { useCallback, useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { chatSocket as socket } from "@/socket";
import { usePathname } from "next/navigation";
import { usePathname, useRouter } from "next/navigation";
import { useCallback, useEffect, useState } from "react";

const showKickErrorToast = () => {
toast({
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/lib/hooks/useMute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import { muteUser, unmuteUser } from "@/app/lib/actions";
import type { PublicUserEntity } from "@/app/lib/dtos";
import { toast } from "@/components/ui/use-toast";
import { useCallback, useEffect, useState } from "react";
import { chatSocket as socket } from "@/socket";
import { useCallback, useEffect, useState } from "react";

interface MuteEvent {
userId: number;
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/lib/hooks/useOnlineStatus.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { createContext, useEffect, useState } from "react";
import { chatSocket } from "@/socket";
import { createContext, useEffect, useState } from "react";

export const OnlineContext = createContext<{ [key: number]: number }>({});

Expand Down
2 changes: 1 addition & 1 deletion frontend/app/lib/hooks/useUpdateRole.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import { updateRoomUser } from "@/app/lib/actions";
import type { UserOnRoomEntity } from "@/app/lib/dtos";
import { toast } from "@/components/ui/use-toast";
import { useCallback, useEffect, useState } from "react";
import { chatSocket as socket } from "@/socket";
import { useCallback, useEffect, useState } from "react";

type Role = "ADMINISTRATOR" | "MEMBER";

Expand Down
2 changes: 1 addition & 1 deletion frontend/app/onlineProviders.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { OnlineContext, useOnlineStatus } from "./lib/hooks/useOnlineStatus";
import SocketProvider from "./lib/client-socket-provider";
import { OnlineContext, useOnlineStatus } from "./lib/hooks/useOnlineStatus";

export function OnlineProviders({ children }: { children: React.ReactNode }) {
const onlineStatus = useOnlineStatus();
Expand Down
4 changes: 2 additions & 2 deletions frontend/app/pong/JoinRoomForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import {
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useState } from "react";
import MatchButton from "./MatchButton";
import { v4 } from "uuid";
import { Separator } from "@/components/ui/separator";
import MatchButton from "./MatchButton";

export default function JoinRoomForm({}) {
return (
Expand Down
2 changes: 0 additions & 2 deletions frontend/app/room/[id]/mute-menu.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
"use client";

import { muteUser, unmuteUser } from "@/app/lib/actions";
import {
ContextMenuItem,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
} from "@/components/ui/context-menu";
import { useState } from "react";

export default function MuteMenu({
isMuted,
Expand Down
Loading

0 comments on commit e62a2ed

Please sign in to comment.