Skip to content

Commit

Permalink
fixed feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
hugosandsjo committed Sep 17, 2024
2 parents ca8efc2 + 0a8c326 commit 2fb4d0e
Show file tree
Hide file tree
Showing 38 changed files with 1,131 additions and 441 deletions.
52 changes: 52 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,58 @@ and CodePair adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

## [Unreleased]

## [0.1.12] - 2024-09-13

### Changed

- Add missing refresh token settings in `docker-compose-full.yml` by @choidabom in https://github.com/yorkie-team/codepair/pull/344
- Prevent dragging selection of identical characters in CodeMirror by @choidabom in https://github.com/yorkie-team/codepair/pull/345
- Reorder keybinding addition for VIM in CodeMirror6 by @devleejb in https://github.com/yorkie-team/codepair/pull/346

### Fixed

- Fix issue where `jj` could not be entered in Vim mode by @hackerwins in https://github.com/yorkie-team/codepair/pull/347
- Change font color of the panel in VIM mode for improved visibility by @devleejb in https://github.com/yorkie-team/codepair/pull/349

## [0.1.11] - 2024-09-12

### Added

- Add Vim binding support for CodePair using CodeMirror 6 by @choidabom in https://github.com/yorkie-team/codepair/pull/340
- Implement Refresh Token by @xet-a in https://github.com/yorkie-team/codepair/pull/317

### Changed

- Remove unnecessary `LANGCHAIN_ENDPOINT` environment variable due to LangSmith API Key update by @devleejb in https://github.com/yorkie-team/codepair/pull/336

## [0.1.10] - 20204-09-05

### Added

- Add hyperlink creation feature by @choidabom in https://github.com/yorkie-team/codepair/pull/332

### Changed

- Update `yorkie-js-sdk` to 0.4.31 by @devleejb in https://github.com/yorkie-team/codepair/pull/331
- Ensure correct representation of logged-in users in shared document view by @choidabom in https://github.com/yorkie-team/codepair/pull/333
- Bump up `yorkie-js-sdk` to `0.5.0` by @devleejb in https://github.com/yorkie-team/codepair/pull/334

## [0.1.9] - 20204-09-02

### Changed

- Change version of `codemirror`, `yorkie-js-sdk` dependencies by @devleejb in https://github.com/yorkie-team/codepair/pull/329

## [0.1.8] - 2024-09-01

### Changed

- Downgrade `yorkie-js-sdk` to `v0.4.27` by @devleejb in https://github.com/yorkie-team/codepair/pull/327

### Fixed

- Disable retry logic for 401 errors during login by @devleejb in https://github.com/yorkie-team/codepair/pull/325

## [0.1.7] - 2024-08-29

### Added
Expand Down
18 changes: 9 additions & 9 deletions backend/.env.development
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,14 @@ GITHUB_CLIENT_SECRET=55d8a3062aebb7fc25af27930d8556506930b67f
# Example: http://localhost:3000/auth/login/github (For development mode)
GITHUB_CALLBACK_URL=http://localhost:3000/auth/login/github

# JWT_AUTH_SECRET: Secret key for JWT authentication.
# This key is used to sign and verify JWT tokens.
JWT_AUTH_SECRET=you_should_change_this_secret_key_in_production
# JWT_ACCESS_TOKEN_SECRET: Secret key for signing and verifying access tokens.
# JWT_ACCESS_TOKEN_EXPIRATION_TIME: Expiration time for access tokens in seconds.
JWT_ACCESS_TOKEN_SECRET=you_should_change_this_access_token_secret_key_in_production
JWT_ACCESS_TOKEN_EXPIRATION_TIME=86400
# JWT_REFRESH_TOKEN_SECRET: Secret key for signing and verifying refresh tokens.
# JWT_REFRESH_TOKEN_EXPIRATION_TIME: Expiration time for refresh tokens in seconds.
JWT_REFRESH_TOKEN_SECRET=you_should_change_this_refresh_token_secret_key_in_production
JWT_REFRESH_TOKEN_EXPIRATION_TIME=604800

# FRONTEND_BASE_URL: Base URL of the frontend application.
# This URL is used for redirecting after authentication, etc.
Expand Down Expand Up @@ -51,13 +56,8 @@ OPENAI_API_KEY=your_openai_api_key_here

# LANGCHAIN_TRACING_V2: Whether LangSmith monitoring for YorkieIntelligence is needed.
# Set to true if LangSmith monitoring is required.
# If set to false, LANGCHAIN_ENDPOINT, LANGCHAIN_API_KEY, and LANGCHAIN_PROJECT are not required.
# If set to false, LANGCHAIN_API_KEY, and LANGCHAIN_PROJECT are not required.
LANGCHAIN_TRACING_V2=false
# LANGCHAIN_ENDPOINT: LangSmith API URL.
# This URL is used to communicate with LangSmith.
# Example: https://api.smith.langchain.com
# To obtain the LangSmith endpoint, visit LangSmith: https://www.langchain.com/langsmith
LANGCHAIN_ENDPOINT=https://www.langchain.com/langsmith
# LANGCHAIN_API_KEY: LangSmith API Key.
# This key is required for authenticating with LangSmith.
# To obtain an API key, visit LangSmith: https://www.langchain.com/langsmith
Expand Down
13 changes: 8 additions & 5 deletions backend/docker/docker-compose-full.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ services:
environment:
DATABASE_URL: "mongodb://mongo:27017/codepair"
# Environment variables need to be passed to the container
GITHUB_CLIENT_ID: "Iv23li1CysRsALb97SET"
GITHUB_CLIENT_SECRET: "55d8a3062aebb7fc25af27930d8556506930b67f"
GITHUB_CLIENT_ID: "your_github_client_id_here"
GITHUB_CLIENT_SECRET: "your_github_client_secret_here"
GITHUB_CALLBACK_URL: "http://localhost:3000/auth/login/github"
JWT_AUTH_SECRET: "you_should_change_this_secret_key_in_production"
JWT_ACCESS_TOKEN_SECRET: "you_should_change_this_access_token_secret_key_in_production"
JWT_ACCESS_TOKEN_EXPIRATION_TIME: 86400
JWT_REFRESH_TOKEN_SECRET: "you_should_change_this_refresh_token_secret_key_in_production"
JWT_REFRESH_TOKEN_EXPIRATION_TIME: 604800
FRONTEND_BASE_URL: "http://localhost:5173"
YORKIE_API_ADDR: "http://yorkie:8080"
YORKIE_PROJECT_NAME: "default"
Expand All @@ -19,7 +22,7 @@ services:
YORKIE_INTELLIGENCE: "ollama:gemma2:2b"
OLLAMA_HOST_URL: http://yorkie-intelligence:11434
OPENAI_API_KEY: "your_openai_api_key_here"
LANGCHAIN_ENDPOINT: "https://www.langchain.com/langsmith"
LANGCHAIN_TRACING_V2: "false"
LANGCHAIN_API_KEY: "your_langsmith_api_key_here"
LANGCHAIN_PROJECT: "your_langsmith_project_name_here"
FILE_UPLOAD: false
Expand All @@ -34,7 +37,7 @@ services:
- "yorkie:yorkie"

yorkie:
image: "yorkieteam/yorkie:0.4.31"
image: "yorkieteam/yorkie:0.5.0"
command: ["server", "--enable-pprof"]
restart: always
ports:
Expand Down
2 changes: 1 addition & 1 deletion backend/docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: "3.8"

services:
yorkie:
image: "yorkieteam/yorkie:0.4.31"
image: "yorkieteam/yorkie:0.5.0"
command: ["server", "--enable-pprof"]
restart: always
ports:
Expand Down
4 changes: 2 additions & 2 deletions backend/package-lock.json

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

2 changes: 1 addition & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codepair-backend",
"version": "0.1.7",
"version": "0.1.12",
"description": "CodePair Backend",
"author": "yorkie-team",
"private": true,
Expand Down
49 changes: 33 additions & 16 deletions backend/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
import { Controller, Get, HttpRedirectResponse, Redirect, Req, UseGuards } from "@nestjs/common";
import {
Body,
Controller,
Get,
HttpRedirectResponse,
Post,
Redirect,
Req,
UseGuards,
} from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { AuthGuard } from "@nestjs/passport";
import { ApiBody, ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
import { Public } from "src/utils/decorators/auth.decorator";
import { AuthService } from "./auth.service";
import { RefreshTokenRequestDto } from "./dto/refresh-token-request.dto";
import { RefreshTokenResponseDto } from "./dto/refresh-token-response.dto";
import { LoginRequest } from "./types/login-request.type";
import { JwtService } from "@nestjs/jwt";
import { LoginResponse } from "./types/login-response.type";
import { UsersService } from "src/users/users.service";
import { Public } from "src/utils/decorators/auth.decorator";
import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
import { ConfigService } from "@nestjs/config";

@ApiTags("Auth")
@Controller("auth")
export class AuthController {
constructor(
private configService: ConfigService,
private jwtService: JwtService,
private usersService: UsersService
private readonly authService: AuthService,
private configService: ConfigService
) {}

@Public()
Expand All @@ -28,16 +37,24 @@ export class AuthController {
})
@ApiResponse({ type: LoginResponse })
async login(@Req() req: LoginRequest): Promise<HttpRedirectResponse> {
const user = await this.usersService.findOrCreate(
req.user.socialProvider,
req.user.socialUid
);

const accessToken = this.jwtService.sign({ sub: user.id, nickname: user.nickname });
const { accessToken, refreshToken } = await this.authService.loginWithSocialProvider(req);

return {
url: `${this.configService.get("FRONTEND_BASE_URL")}/auth/callback?token=${accessToken}`,
url: `${this.configService.get("FRONTEND_BASE_URL")}/auth/callback?accessToken=${accessToken}&refreshToken=${refreshToken}`,
statusCode: 302,
};
}

@Public()
@Post("refresh")
@UseGuards(AuthGuard("refresh"))
@ApiOperation({
summary: "Refresh Access Token",
description: "Generates a new Access Token using the user's Refresh Token.",
})
@ApiBody({ type: RefreshTokenRequestDto })
@ApiResponse({ type: RefreshTokenResponseDto })
async refresh(@Body() body: RefreshTokenRequestDto): Promise<RefreshTokenResponseDto> {
return await this.authService.getNewAccessToken(body.refreshToken);
}
}
46 changes: 33 additions & 13 deletions backend/src/auth/auth.module.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,47 @@
import { Module } from "@nestjs/common";
import { AuthService } from "./auth.service";
import { ConfigService } from "@nestjs/config";
import { JwtService } from "@nestjs/jwt";
import { UsersModule } from "src/users/users.module";
import { JwtInject } from "src/utils/constants/jwt-inject";
import { AuthController } from "./auth.controller";
import { AuthService } from "./auth.service";
import { GithubStrategy } from "./github.strategy";
import { ConfigService } from "@nestjs/config";
import { JwtModule } from "@nestjs/jwt";
import { JwtRefreshStrategy } from "./jwt-refresh.strategy";
import { JwtStrategy } from "./jwt.strategy";

@Module({
imports: [
UsersModule,
JwtModule.registerAsync({
imports: [UsersModule],
providers: [
AuthService,
GithubStrategy,
JwtStrategy,
JwtRefreshStrategy,
{
provide: JwtInject.ACCESS,
useFactory: async (configService: ConfigService) => {
return new JwtService({
secret: configService.get<string>("JWT_ACCESS_TOKEN_SECRET"),
signOptions: {
expiresIn: `${configService.get("JWT_ACCESS_TOKEN_EXPIRATION_TIME")}s`,
},
});
},
inject: [ConfigService],
},
{
provide: JwtInject.REFRESH,
useFactory: async (configService: ConfigService) => {
return {
global: true,
signOptions: { expiresIn: "24h" },
secret: configService.get<string>("JWT_AUTH_SECRET"),
};
return new JwtService({
secret: configService.get<string>("JWT_REFRESH_TOKEN_SECRET"),
signOptions: {
expiresIn: `${configService.get("JWT_REFRESH_TOKEN_EXPIRATION_TIME")}s`,
},
});
},
inject: [ConfigService],
}),
},
],
providers: [AuthService, GithubStrategy, JwtStrategy],
exports: [JwtInject.ACCESS, JwtInject.REFRESH],
controllers: [AuthController],
})
export class AuthModule {}
48 changes: 47 additions & 1 deletion backend/src/auth/auth.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,64 @@
import { ConfigModule } from "@nestjs/config";
import { JwtService } from "@nestjs/jwt";
import { Test, TestingModule } from "@nestjs/testing";
import { UsersService } from "../users/users.service";
import { AuthService } from "./auth.service";

describe("AuthService", () => {
let service: AuthService;
let jwtService: JwtService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [AuthService],
imports: [ConfigModule.forRoot()],
providers: [
AuthService,
{
provide: UsersService,
useValue: {
findOrCreate: jest
.fn()
.mockResolvedValue({ id: "123", nickname: "testuser" }),
},
},
{
provide: JwtService,
useValue: {
sign: jest.fn().mockReturnValue("signedToken"),
verify: jest.fn().mockReturnValue({ sub: "123", nickname: "testuser" }),
},
},
],
}).compile();

service = module.get<AuthService>(AuthService);
jwtService = module.get<JwtService>(JwtService);
});

it("should be defined", () => {
expect(service).toBeDefined();
});

describe("getNewAccessToken", () => {
it("should generate a new access token using refresh token", async () => {
const newToken = await service.getNewAccessToken("refreshToken");

expect(newToken).toBe("signedToken");
expect(jwtService.verify).toHaveBeenCalledWith("refreshToken");
expect(jwtService.sign).toHaveBeenCalledWith(
{ sub: "123", nickname: "testuser" },
expect.any(Object)
);
});

it("should throw an error if refresh token is invalid", async () => {
jwtService.verify = jest.fn().mockImplementation(() => {
throw new Error("Invalid token");
});

await expect(service.getNewAccessToken("invalidToken")).rejects.toThrow(
"Invalid token"
);
});
});
});
36 changes: 34 additions & 2 deletions backend/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,39 @@
import { Injectable } from "@nestjs/common";
import { Inject, Injectable } from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";
import { UsersService } from "src/users/users.service";
import { JwtInject } from "src/utils/constants/jwt-inject";
import { RefreshTokenResponseDto } from "./dto/refresh-token-response.dto";
import { LoginRequest } from "./types/login-request.type";
import { LoginResponse } from "./types/login-response.type";

@Injectable()
export class AuthService {
constructor(private usersService: UsersService) {}
constructor(
private readonly usersService: UsersService,
@Inject(JwtInject.ACCESS) private readonly jwtAccessService: JwtService,
@Inject(JwtInject.REFRESH) private readonly jwtRefreshService: JwtService
) {}

async loginWithSocialProvider(req: LoginRequest): Promise<LoginResponse> {
const user = await this.usersService.findOrCreate(
req.user.socialProvider,
req.user.socialUid
);

const accessToken = this.jwtAccessService.sign({ sub: user.id, nickname: user.nickname });
const refreshToken = this.jwtRefreshService.sign({ sub: user.id });

return { accessToken, refreshToken };
}

async getNewAccessToken(refreshToken: string): Promise<RefreshTokenResponseDto> {
const payload = this.jwtRefreshService.verify(refreshToken);

const newAccessToken = this.jwtAccessService.sign({
sub: payload.sub,
nickname: payload.nickname,
});

return { newAccessToken };
}
}
Loading

0 comments on commit 2fb4d0e

Please sign in to comment.