[HELP!!!] URL 단축기 만들기 - 14-1. 디스코드 로그인 구현하기

URL 단축기 2023년 1월 3일
💡
이 글은 작성자 본인마저 실패해서 헬프 외치면서 올린 글이에요.
H..E..L....P........
안될 것 같다면 인증쪽을 직접 구현해보거나 express로 돌아갈게요...
Documentation | NestJS - A progressive Node.js framework
Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Progamming), FP (Functional Programming), and FRP (Functional Reactive Programming).

이 글은 위 글 기반으로 만들어졌어요!

그래서 인증은 passport 기반으로 처리될 예정이에요!(디스코드, JWT 사용)

passport 설치하기

yarn add @nestjs/passport passport passport-discord

passport 패키지와 passport-discord 패키지를 설치해줬어요.

passport는 인증 기능?을 담당하고 passport-discord 는 passport에서 디스코드 로그인을 할수 있게 만들어주는 역할을 해요.

인증 모듈 만들기

nest g mo auth

이러면 auth.module.ts 가 생성될거에요. 이제 디스코드 로그인으로 넘어가 볼게요.

그 전에... 설정 파일을 좀 건드려볼게요.

설정 파일 수정하기

{
  "database": {
    "host": "localhost"
  },
  "auth": {
    "discord": {
      "clientId": "",
      "clientSecret": "",
      "redirectUri": ""
    }
  }
}
config.example.json

config.example.json을 수정해 주세요.

discord 부분은 config.json 으로 옮겨서 값을 채워넣어 주세요.

설정 타입도 수정해 줄게요.

export type Config = {
  database: EdgeDB.ConnectOptions
  auth: AuthConfig
}

export type AuthConfig = {
  discord: {
    clientId: string
    clientSecret: string
    redirectUri: string
  }
}

인증 서비스 만들기

코드를 생성해주세요.

nest g s auth

그리고 유저 모듈을 import 해주세요.

// ...
imports: [UsersModule],
// ...

그리고 유저 모듈에서 users service를 export 해주세요.

// ...
exports: [UsersService],
// ...

이렇게 하면 auth 모듈에서 users 모듈을 사용할 수 있게 돼요. 그리고 빼먹은게 하나 있어서 유저 모듈을 수정해줄게요. changeUsername 메서드를 만들어 줬어요.

그 전에 쿼리를 하나 추가해 줄게요. 이름은 changeUsername.edgeql 이에요.

select (
  update User filter .userId = <str>$id set {
    username := <str>$username
  }
) {
  userId,
  username,
  admin
}
/**
 * Changes username for a user
 * @param id the id of user to change username
 * @param username the username to change for the user
 */
async changeUsername(id: string, username: string): Promise<User> {
  await this.findUserById(id) // check if user exists

  const user = await changeUsername(this.db, { id, username })

  return {
    id: user.userId,
    admin: user.admin,
    username: user.username,
  }
}

테스트 코드도 추가해 줄게요.

it('change username', async () => {
  await expect(service.changeUsername(userId, 'Test User!')).resolves.toEqual(
    {
      id: '-1',
      username: 'Test User!',
      admin: false,
    },
  )
})

이제 인증 서비스 코드를 써줄게요.

일단 코드를 이렇게 써줬는데요.

import { Injectable } from '@nestjs/common'
import { UsersService } from '../users/users.service'
import { User } from '../users/types'

@Injectable()
export class AuthService {
  constructor(private usersService: UsersService) {}

  async findOrCreateUser(id: string, username: string): Promise<User> {
    try {
      let user = await this.usersService.findUserById(id)

      if (user.username != username) {
        user = await this.usersService.changeUsername(id, username)
      }

      return user
    } catch {
      return await this.usersService.createUser(id, username)
    }
  }
}

테스트 코드도 써볼게요.

import { Test, TestingModule } from '@nestjs/testing'
import { AuthService } from './auth.service'
import { UsersModule } from '../users/users.module'
import { edgedbClient } from '../database/client'

describe('AuthService', () => {
  let service: AuthService

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      imports: [UsersModule],
      providers: [AuthService],
    }).compile()

    service = module.get<AuthService>(AuthService)
  })

  afterAll(async () => {
    await edgedbClient.execute(`delete User filter .userId = "-2";`)
  })

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

  it('should create a user', async () => {
    await expect(
      service.findOrCreateUser('-2', 'Test User 2'),
    ).resolves.toEqual({
      id: '-2',
      username: 'Test User 2',
      admin: false,
    })
  })

  it('should edit a user', async () => {
    await expect(
      service.findOrCreateUser('-2', 'Test User 2@'),
    ).resolves.toEqual({
      id: '-2',
      username: 'Test User 2@',
      admin: false,
    })
  })
})

유저 서비스 작성이 끝났어요. 아마도요.

디스코드 strategy 만들기

아까 설치한 passport-discord 패키지를 이용해 디스코드 로그인을 위한 strategy를 만들어 볼거에요.

auth 폴더 아래에 discord.strategy.ts 를 만들어 주세요.

import { PassportStrategy } from '@nestjs/passport'
import { Strategy } from 'passport-discord'
import { config } from '../utils/config'
import { AuthService } from './auth.service'
import { Injectable, UnauthorizedException } from '@nestjs/common'

@Injectable()
export class DiscordStrategy extends PassportStrategy(Strategy) {
  constructor(private authService: AuthService) {
    super({
      clientID: config.auth.discord.clientId,
      clientSecret: config.auth.discord.clientSecret,
      callbackURL: config.auth.discord.redirectUri,
      scope: 'identify',
    })
  }

  async validate(
    accessToken: string,
    refreshToken: string,
    profile: { id: string; username: string },
  ) {
    try {
      const user = await this.authService.findOrCreateUser(
        profile.id,
        profile.username,
      )

      return user
    } catch {
      throw new UnauthorizedException()
    }
  }
}

이건 뭐라 설명을 못하겠어요...

인증 컨트롤러 만들기

항상 그랬듯이 코드 생성부터 할게요.

nest g co auth

일단 작동하는지 확인하려고 로그만 찍어봤어요.

import { Controller, Request, Get, UseGuards } from '@nestjs/common'
import { AuthGuard } from '@nestjs/passport'
import { FastifyRequest } from 'fastify'

@Controller('auth')
export class AuthController {
  @Get('/login')
  @UseGuards(AuthGuard('discord'))
  async login(@Request() req: FastifyRequest) {
    console.log(req.context)
  }
}

Fastify 지원을 위한 발악

열심히 만들다가...돌려보니까...작동은 안하더라고요..?

그래서 일단 열심히 삽질을 했어요

관련 패키지를 설치해 줄게요

yarn add @fastify/session @fastify/cookie

그리고 package.json을 수정해 주세요

"passport": "npm:@fastify/passport@2.2.0"

패키지 다시 설치..

yarn

main.ts를 수정...

import { NestFactory } from '@nestjs/core'
import { ValidationPipe } from '@nestjs/common'
import { AppModule } from './app.module'
import {
  NestFastifyApplication,
  FastifyAdapter,
} from '@nestjs/platform-fastify'
import passport from 'passport'
import fastifySession from '@fastify/session'
import fastifyCookie from '@fastify/cookie'
import { config } from './utils/config'

async function bootstrap() {
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter(),
  )

  await app.register(fastifyCookie)

  await app.register(fastifySession, {
    secret: config.auth.secret,
  })

  await app.register(passport.initialize())

  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
    }),
  )

  await app.listen(3000)
}
bootstrap()

이렇게 했더니

Error: @fastify/session tried to access @fastify/cookie, but it isn't declared in its dependencies; this makes the require call ambiguous and unsound.

이건 일단 무시(?) 할수있는 방법으로 진행해줄게요.

.yarnrc.yml에 아래 코드를 추가해 주세요.

pnpMode: loose

그러고 다시 설치...

yarn

그러고 실행하면...아마도 작동을...할..걸요?

실행을...해보니....

이러면서 작동을 안하더라고요...

그렇게 저는 해결을 못했습니다......

누군가 도와주기를 바라면서 저는 도망가도록 할...게요...네....ㅋㅋㅋㅋ....

GitHub - paringparing/url-shortener: something
something. Contribute to paringparing/url-shortener development by creating an account on GitHub.

태그

파링

바보