<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>mush_room.farm</title>
        <link>https://velog.io/</link>
        <description>무럭무럭 버섯농장</description>
        <lastBuildDate>Sun, 04 Dec 2022 06:16:23 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>mush_room.farm</title>
            <url>https://images.velog.io/images/___pepper/profile/00b015f9-8fb2-43bb-b0bc-00636d6992e6/spirited away.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. mush_room.farm. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/___pepper" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Intellij] MAC cmd에서 intelljIDEA 열기]]></title>
            <link>https://velog.io/@___pepper/Intellij-MAC-cmd%EC%97%90%EC%84%9C-intelljIDEA-%EC%97%B4%EA%B8%B0</link>
            <guid>https://velog.io/@___pepper/Intellij-MAC-cmd%EC%97%90%EC%84%9C-intelljIDEA-%EC%97%B4%EA%B8%B0</guid>
            <pubDate>Sun, 04 Dec 2022 06:16:23 GMT</pubDate>
            <description><![CDATA[<ol>
<li>intellij &gt; tools &gt; <strong>Create Command-line Launcher</strong>를 선택한다.
<img src="https://velog.velcdn.com/images/___pepper/post/a64bbad6-4a1f-4ace-9774-4cb9c5a02c0b/image.png" alt=""></li>
<li>값을 변경하지 않고 <strong>OK</strong> 버튼을 눌러 적용한다.
<img src="https://velog.velcdn.com/images/___pepper/post/7eaa32be-cdc5-453f-aa82-d97a35bad462/image.png" alt=""></li>
<li>intellij로 열기를 원하는 디렉토리에서 <strong>idea .</strong>를 입력한다.
(혹은 <strong>idea dir-name</strong>를 통해 원하는 dir를 idea에서 열 수 있다.)</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Nest.js] swagger 적용하기]]></title>
            <link>https://velog.io/@___pepper/Nest.js-swagger-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@___pepper/Nest.js-swagger-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 19 May 2022 07:00:42 GMT</pubDate>
            <description><![CDATA[<h1 id="👉-swagger">👉 swagger?</h1>
<h2 id="👉-nestjs에서-swagger-사용하기">👉 Nest.js에서 swagger 사용하기</h2>
<h3 id="✏️-사용하기-전에">✏️ 사용하기 전에</h3>
<p>Nest.js를 global로 설치한 후, nest 명령어를 이용하여 swagger를 적용해볼 Nest.js 프로젝트를 생성한다.</p>
<pre><code class="language-shell"># Nest.js 설치
$&gt; npm i -g @nestjs/cli

# Nest.js 프로젝트 생성
$&gt; nest new swagger-study</code></pre>
<p>Nest.js 프로젝트를 생성 완료를 했으면 swagger를 사용하기 위해 dependency를 설치해준다.</p>
<pre><code class="language-shell">$&gt; npm install --save @nestjs/swagger swagger-ui-express

# fastify를 사용하는 경우 swagger-ui 대신 fastify-swagger로 설치
$&gt; npm install --save @nestjs/swagger fastify-swagger</code></pre>
<h3 id="✏️-프로젝트에-swagger-적용">✏️ 프로젝트에 swagger 적용</h3>
<p>Swagger를 사용하기 위해서 <strong>main.ts</strong> 파일에 swagger document를 작성한다.</p>
<p><strong>main.ts 👇</strong></p>
<pre><code class="language-typescript">import { NestFactory } from &#39;@nestjs/core&#39;;
import { DocumentBuilder, SwaggerModule } from &#39;@nestjs/swagger&#39;;
import { AppModule } from &#39;./app.module&#39;;

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const config = new DocumentBuilder()
    .setTitle(&#39;Swagger Example&#39;)
    .setDescription(&#39;Swagger study API description&#39;)
    .setVersion(&#39;1.0.0&#39;)
    .addTag(&#39;swagger&#39;)
    .build();

  // config를 바탕으로 swagger document 생성
  const document = SwaggerModule.createDocument(app, config);
  // Swagger UI에 대한 path를 연결함
  // .setup(&#39;swagger ui endpoint&#39;, app, swagger_document)
  SwaggerModule.setup(&#39;api&#39;, app, document);

  await app.listen(3000);
}
bootstrap();</code></pre>
<p>Swagger에 대한 document를 작성한 후에는 SwaggerModule의 <strong>.setup()</strong> 을 호출하여 Swagger UI의 path를 연결해준다.
위와 같은 값으로 설정한 후 로컬로 띄운다면, <strong><code>http://localhost:3000/api</code>로 접속하였을 때 Swagger UI를 확인</strong>할 수 있다.</p>
<p>Swagger document를 바로 main.ts에 작성하지 않고, <strong>별도의 document로 작성한 뒤 main.ts에 mount</strong> 하는 방식으로 진행할 수도 있다.</p>
<p><strong>swagger.documnet.ts 👇</strong></p>
<pre><code class="language-typescript">import { DocumentBuilder, SwaggerModule } from &#39;@nestjs/swagger&#39;;

export class BaseAPIDocument {
  public builder = new DocumentBuilder();

  public initializeOptions() {
    return this.builder
        .setTitle(&#39;Swagger Example&#39;)
        .setDescription(&#39;Swagger study API description&#39;)
        .setVersion(&#39;1.0.0&#39;)
        .addTag(&#39;swagger&#39;)
        .build();
  }
}</code></pre>
<p><strong>main.ts 👇</strong></p>
<pre><code class="language-typescript">import { NestFactory } from &#39;@nestjs/core&#39;;
import { DocumentBuilder, SwaggerModule } from &#39;@nestjs/swagger&#39;;
import { AppModule } from &#39;./app.module&#39;;

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const config = new BaseAPIDocumentation().initializeOptions();
  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup(&#39;api&#39;, app, document);

  await app.listen(3000);
}
bootstrap();</code></pre>
<p>Swagger document에 대한 기본 설정을 마친 후 <code>npm run start</code>를 통해 프로젝트를 실행시킨후 Swagger UI endpoint로 들어가면 다음과 같이 작성한 document에 맞는 값으로 설정된 swagger를 확인할 수 있다.
<img src="https://velog.velcdn.com/images/___pepper/post/0686eea6-53e3-4d16-8e8c-c14ab29b6c4a/image.png" alt=""></p>
<h3 id="✏️-api-별-상세-swagger-작성">✏️ API 별 상세 swagger 작성</h3>
<p>swagger에서는 다양한 decorator를 제공해서 swagger document에 쉽게 api에 대한 설명을 추가할 수 있다.</p>
<h4 id="📁-dto">📁 DTO</h4>
<p>API에서 주고 받을 모델에 대한 명세를 <strong>@ApiProperty</strong> annotation을 통해 swagger document에 작성할 수 있다.
<strong>example</strong> property를 통해 모델의 property에 대한 예시값을, property에 대한 설명을 <strong>description</strong> property를 통해 swagger document에서 보여줄 수 있다.</p>
<pre><code class="language-typescript">import { ApiProperty } from &#39;@nestjs/swagger&#39;;

export class CreateSwaggerDto {
  @ApiProperty({
    example: &#39;swagger&#39;,
    description: &#39;this is name of swagger study&#39;,
  })
  name: string;

  @ApiProperty({
    example: &#39;swagger detail&#39;,
    description: &#39;this is detail of swagger study&#39;,
  })
  detail: string;
}</code></pre>
<p><img src="https://velog.velcdn.com/images/___pepper/post/e8791a4d-c8a8-44e6-a2c1-50608c0840af/image.png" alt=""></p>
<h4 id="📁-endpoint">📁 endpoint</h4>
<ul>
<li><strong>@ApiTags</strong>
swagger에 tag를 생성해준다.
controller에 @ApiTags를 사용하면 해당 controller에 속해있는 모든 api(method)들이 작성한 tag 하위에 나타나게 된다.<pre><code class="language-typescript">import { Controller, Get, Post } from &#39;@nestjs/common&#39;;
import { ApiTags } from &#39;@nestjs/swagger&#39;;
</code></pre>
</li>
</ul>
<p>@ApiTags(&#39;Swagger&#39;) // swagger에 tag를 생성해줌
@Controller(&#39;swagger&#39;)
export class SwaggerController {</p>
<pre><code>  @ApiOperation({ summary: &#39;swagger get endpoint&#39;}) // api 설명
@Get()
getSwagger() {
    return &#39;this is get swagger page&#39;;
}

  @Post()
  postSwagger() {
  return &#39;this is post swagger paoge&#39;;
}</code></pre><p>}</p>
<pre><code>![](https://velog.velcdn.com/images/___pepper/post/f2d05735-fa74-4a65-b226-82e3d950be48/image.png)

- **@ApiOperation**
API 동작에 대한 설명을 추가할 수 있다.
**summary** property를 사용하면 해당 API에 대한 간략한 설명을 작성해볼 수 있다.

```typescript
import { Controller, Get, Post } from &#39;@nestjs/common&#39;;
import { ApiOperation , ApiTags } from &#39;@nestjs/swagger&#39;;

@ApiTags(&#39;Swagger&#39;) // swagger에 tag를 생성해줌
@Controller(&#39;swagger&#39;)
export class SwaggerController {

      @ApiOperation({ summary: &#39;swagger get endpoint&#39;}) // api 설명
    @Get()
    getSwagger() {
        return &#39;this is get swagger page&#39;;
    }

      @ApiOperation({ summary: &#39;swagger post endpoint&#39; })
      @Post()
      postSwagger() {
      return &#39;this is post swagger paoge&#39;;
    }
}</code></pre><p><img src="https://velog.velcdn.com/images/___pepper/post/082712a0-ac82-4b2b-ab31-b5ec7fe552d6/image.png" alt=""></p>
<ul>
<li><strong>@ApiResponse</strong>
API의 Response 값에 대한 예시를 swagger 문서에 표시할 수 있다.<pre><code class="language-typescript">import { Controller, Get, Post } from &#39;@nestjs/common&#39;;
import { ApiOperation, ApiResponse, ApiTags } from &#39;@nestjs/swagger&#39;;
</code></pre>
</li>
</ul>
<p>@ApiTags(&#39;Swagger&#39;)
@Controller(&#39;swagger&#39;)
export class SwaggerController {
  @ApiOperation({ summary: &#39;swagger get endpoint&#39; })
  // response 값에 대한 예시 표시
  @ApiResponse({
    status: 403,
    description: &#39;Forbidden&#39;,
  })
  @Get()
  getSwagger() {
    return &#39;this is get swagger page&#39;;
  }</p>
<p>  @ApiOperation({ summary: &#39;swagger post endpoint&#39; })
  @Post()
  postSwagger() {
    return &#39;this is post swagger page&#39;;
  }
}</p>
<pre><code>![](https://velog.velcdn.com/images/___pepper/post/d85d8222-ff2a-4e2b-b2b2-2f2d3e8fc3b8/image.png)
@ApiResponse를 사용할 때 **schema** property를 이용한다면 response로 반환하는 값에 대한 명세를 swagger document에 표시할 수 있다.

@ApiResponse로 사용할 경우에는 **status**를 지정해서 작성해줘야 하지만 swagger에서 각 status에 따른 api response annotation을 별도로 제공하기 때문에 status에 맞는 annotation 사용을 통해서도 response 예시를 표시할 수 있다.
[📁 제공하는 다양한 status별 annotation들](https://docs.nestjs.com/openapi/operations)
```typescript
import { Controller, Get, Post } from &#39;@nestjs/common&#39;;
import { ApiBody, ApiConsumes, ApiOkResponse, ApiOperation, ApiResponse, ApiTags } from &#39;@nestjs/swagger&#39;;

@ApiTags(&#39;Swagger&#39;)
@Controller(&#39;swagger&#39;)
export class SwaggerController {
  @ApiOperation({ summary: &#39;swagger get endpoint&#39; })
  // response 값에 대한 예시 표시
  @ApiResponse({
    status: 403,
    description: &#39;Forbidden&#39;,
  })
  // status 200에 대한 response 표시를 제공
  @ApiOkResponse({
    description: &#39;Success&#39;,
  })
  @Get()
  getSwagger() {
    return &#39;this is get swagger page&#39;;
  }

  @ApiOperation({ summary: &#39;swagger post endpoint&#39; })
  @Post()
  postSwagger() {
    return &#39;this is post swagger page&#39;;
  }
}</code></pre><p><img src="https://velog.velcdn.com/images/___pepper/post/683b9b51-07e6-4f91-9784-3b0da58db565/image.png" alt=""></p>
<ul>
<li><strong>@ApiQuery</strong>
Query로 받을 값에 대한 명세를 표시할 수 있다.</li>
<li><em>name*</em> property를 통해 query를 받은 변수의 이름을 설정할 수 있고, <strong>type</strong> property를 통해 어떤 type으로 query를 받을 것인지 지정할 수 있다.</li>
<li><em>enum*</em> property를 이용하면 enum에 지정된 값으로 제한이 된다는 것을 표시할 수 있으며, swagger를 통해 실행을 할 때 enum에 해당하는 값으로 api를 실행해볼 수 있다.<pre><code class="language-typescript">import { Body, Controller, Get, Post } from &#39;@nestjs/common&#39;;
import {
 ApiOperation,
 ApiParam,
 ApiTags,
} from &#39;@nestjs/swagger&#39;;
import { CreateSwaggerDto } from &#39;./dto/CreateSwaggerDto.dto&#39;;
</code></pre>
</li>
</ul>
<p>@ApiTags(&#39;Swagger&#39;)
@Controller(&#39;swagger&#39;)
export class SwaggerController {
  @ApiOperation({ summary: &#39;swagger get endpoint&#39; })
  @ApiQuery({
    name: &#39;name&#39;,
    enum: [&#39;enum1&#39;, &#39;enum2&#39;],
  })
  @Get()
  getSwagger() {
    return &#39;this is get swagger page&#39;;
  }
}</p>
<pre><code>![](https://velog.velcdn.com/images/___pepper/post/9b7f4648-d5e6-4b56-899e-4a1a1d95429b/image.png)
![](https://velog.velcdn.com/images/___pepper/post/342a141e-95f1-4a5b-9f94-c5ad0b401638/image.png)

- **@ApiParam**
Param으로 받을 값에 대한 명세를 표시할 수 있다.
**name** property를 이용해서 parameter의 값을 받을 변수의 이름을 지정하고, **type** property를 통해 param의 type을 명시할 수 있다.
```typescript
import { Controller, Get, Post } from &#39;@nestjs/common&#39;;
import { ApiOperation, ApiResponse, ApiTags } from &#39;@nestjs/swagger&#39;;

@ApiTags(&#39;Swagger&#39;)
@Controller(&#39;swagger&#39;)
export class SwaggerController {

@ApiTags(&#39;Swagger&#39;)
@Controller(&#39;swagger&#39;)
export class SwaggerController {
  @ApiOperation({ summary: &#39;swagger get endpoint&#39; })
  @ApiParam({
    name: &#39;swagger param&#39;,
    type: &#39;string&#39;,
  })
  @Get()
  getSwagger() {
    return &#39;this is get swagger page&#39;;
  }
}</code></pre><p><img src="https://velog.velcdn.com/images/___pepper/post/44a8cc71-a63e-4f98-a225-c02d88a8f2f3/image.png" alt=""></p>
<ul>
<li><strong>@ApiBody</strong>
Body로 받을 값에 대한 명세를 표시할 수 있다.</li>
<li><em>description*</em> property를 사용하면 body에 대한 설명을 표시할 수 있고, <strong>type</strong> property를 통해 입력 받는 type을 표시해줄 수 있다.
만일 지정한 type(DTO)에 <strong>@ApiProperty</strong>를 통해 작성해두었다면 입력 받을 type에 대한 세부적인 정보를 swagger document에서 확인할 수 있다.</li>
</ul>
<pre><code class="language-typescript">import { Controller, Get, Post } from &#39;@nestjs/common&#39;;
import { ApiOperation, ApiResponse, ApiTags } from &#39;@nestjs/swagger&#39;;

@ApiTags(&#39;Swagger&#39;)
@Controller(&#39;swagger&#39;)
export class SwaggerController {

  @ApiOperation({ summary: &#39;swagger post endpoint&#39; })
  @ApiConsumes(&#39;multipart/form-data&#39;) // Body를 받을 때의 mime type 설정
  // Body에 대한 명세 설정
  @ApiBody({
    description: &#39;post swagger&#39;,
    type: CreateSwaggerDto,
  })
  @Post()
  postSwagger(@Body createSwaggerDto: CreateSwaggerDto) {
    return &#39;this is post swagger page&#39;;
  }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/___pepper/post/32ff7ff8-afd5-4c1d-9680-c1c7f70d459d/image.png" alt=""></p>
<p>위와 같은 decorator 외에도 다양한 decorator를 사용할 수 있으며, <strong>원하는 기능의 <a href="https://docs.nestjs.com/openapi/operations#advanced-generic-apiresponse">decorator를 커스텀</a>하여 사용</strong>할 수도 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Nest.js] 데이터베이스 migration w/ TypeORM]]></title>
            <link>https://velog.io/@___pepper/Nest.js-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-migration-w-TypeORM</link>
            <guid>https://velog.io/@___pepper/Nest.js-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-migration-w-TypeORM</guid>
            <pubDate>Wed, 18 May 2022 03:26:04 GMT</pubDate>
            <description><![CDATA[<h1 id="👉-migration">👉 migration?</h1>
<p>포괄적인 의미의 migration을 os를 변경하거나 데이터베이스의 종류를 바꾸는 것을 의미한다.
데이터베이스에서 사용하는 migration의 의미는 새로운 테이블을 추가하거나, 속성의 이름을 변경하는 등의 스키마를 변경하는 작업을 의미한다.
이런 데이터베이스의 버전 관리를 위한 migration을 TypeORM을 통해 쉽고 안전하게 진행할 수 있다.</p>
<h2 id="👉-typeorm을-이용한-migration">👉 TypeORM을 이용한 migration</h2>
<p><a href="https://wikidocs.net/158618">📁 참고 문서 : Nestjs로 배우는 백엔드 프로그래밍</a>
<a href="https://typeorm.io/migrations">📁 TypeOrm 공식 문서</a></p>
<h3 id="👉-사용하기-전에">👉 사용하기 전에</h3>
<p>TypeORM을 사용하기 위해서 TypeORM을 설치해준다.
TypeORM migration을 적용하기 위해서는 <strong>typeorm CLI</strong>를 사용해야하므로, typeorm CLI 실행을 위한 <strong>ts-node를 global</strong>로 설치해준다.</p>
<pre><code class="language-shell"># Nest.js에서 TypeORM을 연동시켜주기 위해 사용하는 모듈
$&gt; npm install --save @nestjs/typeorm

# typeorm module
$&gt; npm install --save typeorm

# typeorm CLI 실행을 위해 global로 설치
$&gt; npm i -g ts-node</code></pre>
<p>설치가 완료되었다면 <strong>package.json</strong> 파일에 아래와 같은 코드를 추가하여 프로젝트에서 typeorm CLI를 사용할 수 있도록 한다.</p>
<p><strong>package.json 👇</strong></p>
<pre><code class="language-json">&quot;scripts&quot;: {
    ...
    &quot;typeorm&quot;: &quot;node --require ts-node/register ./node_modules/typeorm/cli.js&quot;
}</code></pre>
<p>다음으로는 migration을 적용할 테이블을 typeorm에게 알려주기 위해 <strong>typeorm.config.js</strong>에 적용할 테이블에 대한 정보를 작성해준다.</p>
<p><strong>typeorm.confing.js 👇</strong></p>
<pre><code class="language-json">{
    ...
  &quot;synchronize&quot;: false, // true로 설정할 경우 서버가 구동될 때마다 테이블이 자동으로 생성됨
  &quot;migrations&quot;: [&quot;dist/migrations/*{.ts,.js}&quot;], // migration 수행할 파일
  &quot;cli&quot;: {
    &quot;migrationsDir&quot;: &quot;src/migrations&quot; // migration 파일을 생성할 디렉토리
  },
  &quot;migrationsTableName&quot;: &quot;migrations&quot; // migration 내용이 기록될 테이블명(default = migration)
}</code></pre>
<h3 id="👉-사용하기">👉 사용하기</h3>
<p>TypeORM에서의 migration 및 version 관리는 migration 파일을 이용해서 한다.
<code>migration:create</code>와 <code>migration:generate</code> 명령어를 통해 migration 파일을 생성할 수 있다.</p>
<pre><code class="language-shell"># migration 내용이 비어있는 새로운 migration 파일 추가 
$&gt; npm run typeorm migration:create
$&gt; npm run typeorm migration:create -- -n &quot;message&quot; # message 추가하여 기록

#  변경된 소스코드를 감지해 migration 파일을 자동 생성
$&gt; npm rum typeorm migration:generate
$&gt; npm rum typeorm migartion:generate -- -n &quot;message&quot;</code></pre>
<p>명령어를 실행하면 <strong>typeorm.config.js</strong> 에서 <strong>migrationsDir</strong>에 설정한 디렉토리 하위에 migration 파일이 생성된다.</p>
<p><strong>생성된 migration 파일 👇</strong></p>
<pre><code class="language-js">import { MigrationInterface, QueryRunner } from &quot;typeorm&quot;;

export class CreateUserTable1640441100470 implements MigrationInterface {
  name = &#39;CreateUserTable1640441100470&#39;

  public async up(queryRunner: QueryRunner): Promise&lt;void&gt; {
    await queryRunner.query(`CREATE TABLE User (id varchar(255) NOT NULL, name varchar(30) NOT NULL, email varchar(60) NOT NULL, password varchar(30) NOT NULL, signupVerifyToken varchar(60) NOT NULL, PRIMARY KEY (id)) ENGINE=InnoDB`);
  }

  public async down(queryRunner: QueryRunner): Promise&lt;void&gt; {
    await queryRunner.query(`DROP TABLE \`User\``);
  }
}</code></pre>
<p>생성된 migration 파일에서 변화된 데이터베이스의 변화를 확인할 수 있다.
<strong>up</strong> 부분에는 데이터베이스의 변화 내용이 <strong>down</strong> 부분에는 원래의 데이터베이스 모습이 작성된다.
만약 <code>migration:create</code>를 이용하여 migration 파일을 생성하였다면 두 함수 모두 비어있을 것이므로 <strong>up</strong>부분에 변화시킬 내용, <strong>down</strong> 부분에 현재의 데이터베이스 모습을 작성해주면 된다.</p>
<p>migration 파일을 생성한다고 데이터베이스에 변화가 적용되는 것은 아니기 때문에 <strong>명령어를 통해 migration을 진행</strong>해주어야한다.</p>
<pre><code class="language-shell"># up으로 작성된 변화를 적용시킴
$&gt; npm run typeorm migration:run

# down에 작성된 버전으로 되돌림
$&gt; npm run typeorm migration:revert</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[openzepplin] ERC 721 개발]]></title>
            <link>https://velog.io/@___pepper/openzepplin-ERC-721-%EA%B0%9C%EB%B0%9C</link>
            <guid>https://velog.io/@___pepper/openzepplin-ERC-721-%EA%B0%9C%EB%B0%9C</guid>
            <pubDate>Sun, 15 May 2022 03:58:27 GMT</pubDate>
            <description><![CDATA[<h1 id="👉-erc721">👉 ERC721</h1>
<p>ERC721은 대체 불가능한 토큰(Non-Fungible Token)으로 흔히 말하는 NFT의 표준이다.
각 토큰은 고유하기 때문에, 각각의 가치를 가지고 있다.</p>
<h2 id="👉-구현">👉 구현</h2>
<p><a href="https://docs.openzeppelin.com/contracts/4.x/erc721">openzepplin</a>을 이용하면 간단하게 구현해볼 수 있다.
실습에 참고한 페이지는 <a href="https://docs.openzeppelin.com/contracts/4.x/erc721">openzepplin 공식 문서</a>와 <a href="https://velog.io/@yoonique_garage/truffleERC721#4-%EC%BB%B4%ED%8C%8C%EC%9D%BC%ED%9B%84-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0">이 velog 글</a>.</p>
<p>개발 환경을 위한 환경 세팅(truffle이나 hardhat을 이용해서)완료했다면 openzepplin 활용을 위해 openzepplin contract를 설치해준다.</p>
<pre><code class="language-shell">$&gt; npm install --save-dev @openzeppelin/contracts</code></pre>
<p>설치가 완료되면 <strong>contracts</strong> 디렉토리에 solidity 파일을 생성해준다.
ERC721는 openzepplin에서 제공하는 ERC721을 상속 받아서 쉽게 구현할 수 있는데, 참고한 두 예제 모두 <strong>ERC721이 아닌 확장형인 ERC721URIStorage를 상속</strong> 받고 있어서 해당 contract를 상속 받아 구현해보았다.
<strong>ERC721URIStorage</strong>는 기존의 ERC721에서 <strong>tokenURI를 별도로 지정</strong>할 수 있는 기능이 추가된 확장형이다.
다만 더 비싼 가스비가 발생한다.</p>
<p><strong>MyNFT.sol 👇</strong></p>
<pre><code class="language-solidity">// SPDX-License-Identifier: MIT
pragma solidity &gt;=0.4.22 &lt;0.9.0;

import &quot;@openzeppelin/contracts/token/ERC721/ERC721.sol&quot;;
import &quot;@openzeppelin/contracts/utils/Counters.sol&quot;;
import &quot;@openzeppelin/contracts/access/Ownable.sol&quot;;
import &quot;@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol&quot;;

// openzeppelin의 ERC721 확장 버젼인 ERC721URIStorage 상속
// ERC721 == NFT
contract MyNFT is ERC721URIStorage, Ownable {
    // 값의 증감만을 하기 위해 사용하는 타입
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    constructor () ERC721(&quot;MyNFT&quot;, &quot;MFT&quot;) {}

    function mintNFT(string memory tokenURI) public onlyOwner returns (uint256) {
        _tokenIds.increment();

        uint256 newItemId = _tokenIds.current();
        _mint(msg.sender, newItemId);
        _setTokenURI(newItemId, tokenURI);

        return newItemId;
    }

}</code></pre>
<p>ERC721 구현을 위해 상속 받은 ERC721URIStorage외에 별도로 상속 받은 <strong>Ownable</strong> 역시 openzepplin에서 제공하는데 Ownalbe을 상속 받으면 contract에 ownership을 주입할 수 있다.
위의 코드에서는 <strong>onlyOwner</strong>라는 modifier를 사용하여 <strong>owner만이 mintNFT라는 함수를 실행</strong>시키도록 사용하였다.
<a href="https://docs.openzeppelin.com/contracts/2.x/api/ownership#Ownable">📂 Ownable 공식 문서</a></p>
<h2 id="👉-배포">👉 배포</h2>
<p>ERC721에 대한 코드가 완성되었다면, compile 후 deploy를 진행해주면 된다.</p>
<h3 id="✏️-truffle">✏️ Truffle</h3>
<p>Truffle을 사용한다면 <strong>truffle-config.js</strong> 파일에 배포를 위한 환경설정을 해준다.</p>
<p>설정 및 ganache workspace 추가는 <a href="https://velog.io/@___pepper/openzepplin-ERC20-%EA%B0%9C%EB%B0%9C#%EF%B8%8F-truffle">ERC20 배포</a>에서 했던 방식과 동일한 순서로 진행해주면 된다.</p>
<p>환경 설정을 완료했다면 <strong>migrations 디렉토리</strong> 하위에 delpoy를 위한 js 파일을 작성해준다.
작성하는 migration 파일은 숫자로 시작하는데, 이 숫자에 따라서 각 파일들이 순차적으로 실행되며 <strong>한 번 씩</strong>만 실행된다.
만일 동일한 파일을 재실행하고 싶을 경우에는 <code>--reset</code> option을 사용하여 migration 파일을 실행하면 된다.</p>
<p><strong>5_initial_mynft.js 👇</strong></p>
<pre><code class="language-js">const MyNFT = artifacts.require(&quot;MyNFT&quot;);

module.exports = function (deployer) {

  deployer.deploy(MyNFT);
};</code></pre>
<p>truffle을 이용하여 compile을 하고 migrate를 이용해 배포를 진행해준다.</p>
<pre><code class="language-shell"># code compile
$&gt; npx truffle compile

# code migrate
$&gt; npx truffle migrate
# or
$&gt; npx truffle migrate --reset</code></pre>
<p>배포된 contract는 <strong>truffle console</strong>에서 실행해볼 수 있다.
truffle console은 <code>npx truffle console</code> 명령어로 열 수 있다.</p>
<p>배포된 contract를 <strong>await</strong>를 이용해 인스턴스로 생성하고, contract에 작성한 함수들을 실행시켜보면 된다.</p>
<pre><code class="language-sh"># 배포된 contract의 instance 생성
truffle(ganache)&gt; mynft = await MyNFT.deployed() </code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Active Record vs. Data Mapper]]></title>
            <link>https://velog.io/@___pepper/Active-Record-vs.-Data-Mapper</link>
            <guid>https://velog.io/@___pepper/Active-Record-vs.-Data-Mapper</guid>
            <pubDate>Fri, 13 May 2022 09:08:42 GMT</pubDate>
            <description><![CDATA[<h1 id="👉-orm">👉 ORM</h1>
<p>ORM은 Object Relational Mapping으로 객체를 관계형 데이터베이스에 연결(mapping)하는 것을 의미한다.
ORM의 구현 방식은 다양하게 존재하는데, <strong>Active Record</strong>와 <strong>Data Mapper</strong>가 이에 해당한다.</p>
<h2 id="👉-active-record">👉 Active Record</h2>
<p><strong>데이터베이스 model이 데이터에 접근</strong></p>
<p>모든 query method들을 model 그 자체에 정의하고, model method를 이용하여 object들을 저장, 갱신, 삭제하도록 한다.
즉, active record는 model을 이용하여 데이터베이스를 조작하기 때문에 SQL을 직접 사용하지 않으면서 데이터 조작이 가능하다.</p>
<p>Active Record는 직관적으로 동작하기 때문에 쉽게 시작하기에 용이하다.</p>
<h3 id="✏️-entity-정의">✏️ Entity 정의</h3>
<p>각 모델들은 <strong>Base Active Record</strong>를 상속받아 구현되어 Base class의 method들을 사용할 수 있다.</p>
<pre><code class="language-ts">@Entity()
export class User extends BaseEntity {

  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  firstName: string;

  @Column()
  lastName: string;

  // model 내부에 query method가 존재함
  static findByName(firstName: string, lastName: string) {
    return this.createQueryBuilder(&quot;user&quot;)
            .where(&quot;user.firstName = :firstName&quot;, { firstName })
            .andWhere(&quot;user.lastName = :lastName&quot;, { lastName })
            .getMany();
  }
}</code></pre>
<h2 id="👉-data-mapper">👉 Data Mapper</h2>
<p><strong>데이터베이스와 model을 분리시켜 mapper가 model과 데이터베이스 사이에 데이터를 이동</strong></p>
<p>모든 query method들을 repository라는 각각의 구분된 class로 나누어 정의하고, repository를 이용하여 object들을 저장, 갱신, 삭제하도록 한다.</p>
<p>Data Mapper를 사용하게 되면 <strong>도메인 객체는 데이터베이스에 어떻게 저장되는지 알 필요가 없다.</strong>
다만 데이터베이스와 통신을 할 때 Active Record를 사용할 때보다 엄격한 방식으로 진행해야한다.</p>
<h3 id="✏️-entity-정의-1">✏️ Entity 정의</h3>
<p>Active Record와 다르게 Data Mapper는 <strong>Base Acrive Record를 상속받지 않</strong>기 때문에 상대적으로 가벼운 객체를 가지게 된다.</p>
<p><strong>Entity 👇</strong></p>
<pre><code class="language-ts">@Entity()
export class User {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    firstName: string;

    @Column()
    lastName: string;
}</code></pre>
<p><strong>Repository 👇</strong></p>
<pre><code class="language-ts">// 데이터베이스에 관련한 로직이 model이 아닌 repository라는 별도의 class에 구현됨
@EntityRepository()
export class UserRepository extends Repository&lt;User&gt; {

  findByName(firstName: string, lastName: string) {
    return this.createQueryBuilder(&quot;user&quot;)
            .where(&quot;user.firstName = :firstName&quot;, { firstName })
            .andWhere(&quot;user.lastName = :lastName&quot;, { lastName })
            .getMany();
  }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Nest.js] TypeORM 사용하기]]></title>
            <link>https://velog.io/@___pepper/Nest.js-TypeORM-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@___pepper/Nest.js-TypeORM-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 13 May 2022 06:56:04 GMT</pubDate>
            <description><![CDATA[<h1 id="👉-orm">👉 ORM?</h1>
<p>객체와 관계형 데이터베이스의 데이터를 자동으로 연결해주는 작업을 의미한다.</p>
<h2 id="👉-nestjs에서-typeorm을-사용하기">👉 Nest.js에서 TypeORM을 사용하기</h2>
<p><a href="https://typeorm.io/">📁 TypeORM 공식문서</a>
<a href="https://docs.nestjs.com/techniques/database">📁 Nest.js에서의 Database 사용 공식 문서</a></p>
<h3 id="✏️-사용하기-전에">✏️ 사용하기 전에</h3>
<p>Nest.js에서 TypeORM</p>
<pre><code class="language-shell"># Nest.js에서 TypeORM을 연동시켜주기 위해 사용하는 모듈
$&gt; npm install --save @nestjs/typeorm

# typeorm module
$&gt; npm install --save typeorm

# 사용하고자 하는 DB의 모듈을 설치해주면 됨(pg는 postgreSql)
$&gt; npm install --save pg</code></pre>
<h3 id="✏️-typeorm-연결하기">✏️ TypeORM 연결하기</h3>
<p>typeORM config 파일을 생성하여 작성</p>
<p><strong>typeorm.config.ts 👇</strong></p>
<pre><code class="language-typescript">export const typeORMConfig: TypeOrmModuleOptions = {
  type: &quot;database type&quot;,
  host: &quot;database host&quot;,
  port: int(database port),
  username: &quot;database username&quot;,
  password: &quot;database password&quot;,
  database: &quot;database name&quot;,
  entities: [database에서 사용하는 entity들,,,],
  sychronize: false
}</code></pre>
<p>sychronize는 production에서는 false로 설정해주어야한다.
그렇지 않을 경우 데이터를 손실할 수 있는 위험이 있다.</p>
<p>TypeORM은 <strong>entity를 이용해서 데이터베이스를 생성</strong>하기 때문에 entity 파일이 어디있는지 설정에 적어주어야한다.</p>
<p>config 파일의 작성이 완료되었다면 <strong>root module</strong> 파일에 typeOrm config를 연결해준다.</p>
<p><strong>app.module.ts 👇</strong></p>
<pre><code class="language-ts">@Module({
  imports: [
    TypeORMModule.forRoot(typeORMConfig),
    ]
})</code></pre>
<p>별도로 config 파일을 만들지 않고 <strong>forRoot</strong> 내에 config 정보들을 작성해주어도 된다.</p>
<pre><code class="language-ts">@Module({
  imports: [
    type: &quot;database type&quot;,
      host: &quot;database host&quot;,
      port: int(database port),
      username: &quot;database username&quot;,
      password: &quot;database password&quot;,
      database: &quot;database name&quot;,
      entities: [database에서 사용하는 entity들,,,],
      sychronize: false
  ]
})</code></pre>
<h3 id="✏️-entity-생성하기">✏️ Entity 생성하기</h3>
<p>TypeORM은 생성한 entity class를 이용하여 database를 생성한다.</p>
<p><strong>MyEntity.ts 👇</strong></p>
<pre><code class="language-ts">// MyEntity class가 Entity임을 알려주는 decorator
@Entity()
export class MyEntity {

  // id column이 entity의 primary key임을 알려줌
  @PrimaryGeneratedColumn()
  id: number

  // entity의 column을 표현
  @Column()
  title: string

  @Column()
  description: string
}</code></pre>
<h3 id="✏️-repository-생성하기">✏️ Repository 생성하기</h3>
<p>Repository는 entity의 생성, 업데이트, 삭제, 불러오기 등을 처리한다.
즉, database에서의 CRUD에 대한 동작을 해주기 때문에 데이터베이스에 관련된 로직들은 repository에 작성해주면 된다.</p>
<p><strong>MyEntityRepository.ts 👇</strong></p>
<pre><code class="language-ts">// class를 사용자 정의 repository로 사용하도록 선언하는 decorator
// 인자로 사용할 Entity class를 넣어줌
@EntityRepository(MyEntity)
export class MyEntityRepository extends Repository&lt;MyEntity&gt; {
  // entity 컨트롤을 위해서는 Repository&lt;&gt;를 extends 해주어야 함
}</code></pre>
<p>생성한 repository를 다른 곳에서 사용할 수 있도록 하기 위해서는 <strong>module에 import</strong>해주어야 한다.
module에 import를 하면 respository를 이용하고 싶은 다른 service들에서 사용이 가능하게 된다.</p>
<p><strong>mydb.module.ts 👇</strong></p>
<pre><code class="language-ts">@Module({
  imports: [
    TypeORMModule.forFeature([MyEntityRepository]),
    ],
  controllers: [MyDBController],
  providers: [MyDBService]
})

export class MyDBModule {}</code></pre>
<h3 id="✏️-데이터베이스-조작하기crud">✏️ 데이터베이스 조작하기(CRUD)</h3>
<p>TypeORM 사용을 위한 앞선 설정들을 마무리했다면 이제 데이터베이스에 접근하여 데이터를 다루어볼 수 있다.
먼저 repository를 사용할 <strong>service에 repository를 종속성 주입(DI, Dependency Injection)</strong> 을 해줘야 사용할 수 있다.</p>
<p><strong>mydb.service.ts 👇</strong></p>
<pre><code class="language-ts">@Injectable()
export class MyDB {
  constructor(
  @InjectRepository(MyEntityRepository) // 사용하지 않아도 정상적으로 동작하는가? 
  private myEntityRepository: MyEntityRepository
  ) {}
}</code></pre>
<p>TypeORM을 연결해서 사용하기 때문에, 데이터베이스 동작(CRUD)을 할 때 <strong>TypeORM에서 제공하는 method들을 이용</strong>하여 간편하게 <strong>데이터베이스 동작을 실행</strong>시킬 수 있다.
<a href="https://typeorm.io/repository-api">📁 TypeORM 제공 methods</a></p>
<p>데이터베이스 동작을 하는 함수를 작성할 때에는 <strong>async / await</strong>를 이용하여 데이터베이스의 작업이 끝난 후 결과 값을 받을 수 있도록 설정한다.
(javascript를 기본적으로 <strong>비동기</strong>로 처리되기 때문에 <strong>동기</strong>로 동작하도록 하기 위해서는 <strong>async / await</strong>를 사용해주어야 한다.)</p>
<h4 id="✏️-create">✏️ CREATE</h4>
<p>create를 이용하여 새로운 entity를 생성하고, 생성한 entity를 <strong>insert, save</strong>를 이용하여 데이터베이스에 정보를 삽입할 수 있다.</p>
<ul>
<li>insert : 데이터에 새로운 entity를 삽입하는데 <strong>이미 존재할 경우 요청이 실패함</strong></li>
<li>save : <strong>값이 존재</strong>하는 경우 entity의 값을 <strong>update</strong>를 하고, <strong>존재하지 않을 경우</strong> 새로운 entity를 <strong>create</strong></li>
</ul>
<p>insert와 save 모두 <strong>여러개의 값을 동시에 데이터베이스에 입력</strong>할 수 있다.</p>
<pre><code class="language-ts">async createEntity(createEntityDto: CreateEntityDto) : Promise &lt;MyEntity&gt; {
  const newEntity = this.myEntityRepository.create({
  ...createEntityDto
})
  /* or
  const { title, description } = createEntityDto
  const newEntity = this.myEntityRepository.create({
      title,
    description
  })
  */

  await this.myEntityRepository.save(newEntity)
  return newEntity
}</code></pre>
<p>create를 사용해서 entity를 생성하지 않고 바로 insert나 save를 이용해 데이터베이스에 정보를 삽입할 수도 있다.</p>
<pre><code class="language-ts">async createEntity(createEntityDto: CreateEntityDto) {
  await this.insert({
    title,
    description
  })
}</code></pre>
<h4 id="✏️-read">✏️ READ</h4>
<p>TypeORM에서 다양한 method들을 제공해주기 때문에 필요한 방식이 있다면 공식 문서를 참고해서 필요 기능을 제공받으면 된다.</p>
<p>만일 특정 조건에 해당하는 정보들 중 가장 첫번째 값을 불러오고 싶다면 <strong>findOne()</strong> 을 사용하면 된다.</p>
<pre><code class="language-ts">async getEntity(id: number) : Promise &lt;MyEntity&gt; {
  // TypeORM의 .findOne() method를 이용하여 id에 해당하는 값을 DB로부터 불러옴
  const result = await this.myEntityRepository.findOne(id)

  /* or
  const result = await this.myEntity.Repository.findOne({
      where: {
    id
    }
  })
  */

  if (!result) {
    // database에 id에 해당하는 값이 존재하지 않을 경우
    throw new NotFoundException(&#39;${id} is not exist&#39;)
  }

  return result
}</code></pre>
<h4 id="✏️-update">✏️ UPDATE</h4>
<p>데이터베이스에 저장되어있는 정보를 갱신하고 싶다면 <strong>update 혹은 save</strong> method을 사용하여 실행할 수 있다.</p>
<p>save를 사용할 경우에는,</p>
<ol>
<li>데이터베이스에서 객체를 찾아오고</li>
<li>객체의 값을 원하는 값으로 변경하고 save를 실행</li>
</ol>
<p>하여 저장된 정보의 값을 변경할 수 있다.</p>
<pre><code class="language-ts">async updateEntity(id: number, updateEntityDto: UpdateEntityDto) {
  const entity = await this.myEntityRepository.findOne(id)

  newEntity = {
    ...entity,
    ...updateEntityDto
  }

  await this.myEntityRepository.save(newEntity)
}</code></pre>
<p>update를 사용한다면, 주어진 option 혹은 id값에 맞는 데이터를 갱신하고자하는 데이터로 변경할 수 있다.</p>
<pre><code class="language-ts">async updateEntity(id: number, updateEntityDto: UpdateEntityDto) {
  await this.myEntityRepository.update(id,{
    ...updateEntityDto
  })
}</code></pre>
<h4 id="✏️-delete">✏️ DELETE</h4>
<p>데이터베이스에 존재하는 데이터는 <strong>remove, delete</strong>를 이용하여 삭제할 수 있다,</p>
<ul>
<li>remove : 삭제하고자 하는 데이터가 존재하지 않을 경우 실패</li>
<li>delete : <strong>값이 존재</strong>하는 경우 <strong>삭제</strong>하고, <strong>존재하지 않을 경우</strong>에는 <strong>아무일도 일어나지 않음</strong></li>
</ul>
<p>remove를 사용할 때에는,</p>
<ol>
<li>데이터베이스에 값이 존재하는지 확인</li>
<li>존재한다면 삭제</li>
</ol>
<p>의 방식으로 진행을 해주는 것이 좋다.</p>
<pre><code class="language-ts">async deleteEntity(id: number) : Promise &lt;MyEntity&gt; {
  const result = await this.myEntityRepository.delete(id)

  // delete return 값 -&gt; DeleteResult {raw : [], affected : 1}
  // delete가 성공할 경우 affected의 값이 1로 옮
  if (result.affected === 0) {
    throw new NotFoundException(&#39;${id} is not exsit)
  }                 
}</code></pre>
<p>데이터베이스 동작에 관련된 사항은 <strong>repository</strong>에서 동작하는 것이기 때문에, 위의 예시에서는 service에 작성해두었으나 해당 로직을 repository로 옮겨서 구현하는 것이 맞다.
repository로 옮길 때에는 <strong>this.myEntityRepository</strong>를 <strong>this</strong>를 이용해 TypeORM method들을 사용하면 된다. </p>
<p>위에서 다룬 메소드 외에도 TypeORM에서 다양한 메소드를 지원하기 때문에, 구현하고 싶은 기능이 있다면 <a href="https://typeorm.io/">공식 문서</a>를 참고해보는 것이 좋다.
<strong>query()</strong> 메소드를 이용하여 <strong>직접 sql query를 실행</strong>하도록 할 수도 있고, <strong>createQueryBuilder()</strong> 를 이용해 <strong>원하는 query를 커스텀하여 사용</strong>할 수도 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[openzepplin] ERC20 개발]]></title>
            <link>https://velog.io/@___pepper/openzepplin-ERC20-%EA%B0%9C%EB%B0%9C</link>
            <guid>https://velog.io/@___pepper/openzepplin-ERC20-%EA%B0%9C%EB%B0%9C</guid>
            <pubDate>Fri, 29 Apr 2022 03:20:08 GMT</pubDate>
            <description><![CDATA[<h1 id="👉-erc20">👉 ERC20</h1>
<p>ERC20은 대체 가능한 토큰(fungible token)으로 이더리움 블록체인 네트워크에서 정한 표준 토큰이다.
한 토큰은 다른 토큰과 정확하게 동일하며, 토큰에 특별한 권리나 행동이 관련되어있지 않다.</p>
<h2 id="👉-구현">👉 구현</h2>
<p><a href="https://docs.openzeppelin.com/contracts/4.x/erc20">openzepplin</a>을 활용하면 간단하게 ERC20을 구현해볼 수 있다.
<a href="https://docs.openzeppelin.com/learn/developing-smart-contracts">openzepplin docs</a>에 <strong>truffle</strong>과 <strong>hardhat</strong> 사용 예제가 모두 있으니 원하는 framework를 이용하여 개발을 진행해볼 수 있다.</p>
<p>개발을 위한 환경 세팅(truffle이나 hardhat을 이용해서) openzeppline을 사용하여 ERC20을 구현하기 위해 일단 openzeppline contract를 설치해준다.</p>
<pre><code class="language-shell">$&gt; npm install --save-dev @openzeppelin/contracts</code></pre>
<p>설치가 완료되었다면 <strong>contracts</strong> 디렉토리에 solidity 파일을 생성해준다.
solidity에서는 <strong>is</strong>를 통해서 상속을 받을 수 있다.
따라서, <code>contranct GLDTOKEN is ERC20</code>으로 openzepplin에서 제공하는 ERC20을 상속 받아 쉽게 구현해볼 수 있다.</p>
<p><strong>GLDToken.sol 👇</strong></p>
<pre><code class="language-solidity">// SPDX-License-Identifier: MIT
pragma solidity &gt;=0.4.22 &lt;0.9.0; // solidity의 버전 지정

import &quot;@openzeppelin/contracts/token/ERC20/ERC20.sol&quot;;

// ERC20을 상속받아서 GLDToken 생성
// ERC20 : 대체 가능한 토큰(fungible token)의 표준
contract GLDToken is ERC20 {
    // 생성자 : 배포될 때 호출됨
    // constructor가 값을 전달 받을 경우 migration.js &gt; deploy 함수에 ,로 구분하여 값을 넣어줌
    constructor(uint256 initialSupply) ERC20(&quot;Gold&quot;, &quot;GLD&quot;){
        _mint(msg.sender, initialSupply);
    }

    // ERC20에 존재하는 decimals()를 override
    function decimals() public view virtual override returns (uint8){
        // 토큰의 양을 나눌 수 있는 소수 자릿수
        return 16;
    }
}</code></pre>
<h2 id="👉-배포">👉 배포</h2>
<p>ERC20에 대한 코드 작성이 완료되었다면, compile 후 deploy를 진행해주면 된다.</p>
<h3 id="✏️-truffle">✏️ Truffle</h3>
<p>Truffle을 사용한다면 먼저 배포를 위한 환경 설정을 해준다.
<strong>truffle-config.js</strong> 파일에서 사용할 solidity version을 맞춰주고, 배포를 할 network 설정을 해준다.</p>
<p><strong>truffle-config.js 👇</strong></p>
<pre><code class="language-js">.
.
.
networks: {
  ganache: {
    host: &quot;127.0.0.1&quot;,
       port: &quot;7545&quot;,
    network_id: &quot;*&quot;,
  },
}
.
.
.</code></pre>
<p><strong>ganache</strong>를 사용하여 진행할 것이기 때문에 <strong>port를 7545</strong>로 설정해주었다.
(기본 설정은 8545로 되어있고 ganache에서 port를 8545로 변경하는 방법도 있다.</p>
<p>Ganache에서도 배포를 위한 workspace를 추가해준다.
<img src="https://velog.velcdn.com/images/___pepper/post/ade04674-3a7a-4223-bb83-5b241ad77b63/image.png" alt="">
<strong>NEW WORKSPACE</strong>로 들어가서,
<img src="https://velog.velcdn.com/images/___pepper/post/d766e2c4-9c22-48ae-aaa7-cc642324d7d6/image.png" alt="">
<strong>Truffle Projects</strong>에 작성한 <strong>truffle-config.js</strong>를 <strong>ADD PROJECT</strong>를 통해 추가해준 후, <strong>SAVE WORKSPACE</strong>를 해주면 배포를 위한 준비가 완료된다.</p>
<p>환경 설정을 완료했다면, <strong>migrations</strong> 디렉토리에 <strong>migration을 위한 js 파일</strong>을 작성해준다.
작성하는 migration 파일은 숫자로 시작하는데, 이 숫자에 따라서 migration이 진행된다.</p>
<p><strong>3_initial_goldtoken.js 👇</strong></p>
<pre><code class="language-js">const GLDToken = artifacts.require(&quot;GLDToken&quot;);

module.exports = function (deployer) {
  // constructor가 받는 변수의 값을 ,로 구분하여 넣어줌
  deployer.deploy(GLDToken, 1000000000000);
};</code></pre>
<p>작성한 solidity code에서 constructor가 실행될 때 매개변수를 받기 때문에, deploy시에 매개변수를 전달해주도록 한다.</p>
<p>truffle을 이용하여 컴파일을 하고 migrate를 통해 배포를 해준다.</p>
<pre><code class="language-shell"># code compile
$&gt; npx truffle complie 

# code migrate
$&gt; npx truffle migrate</code></pre>
<p>migrate 명령어는 각 migration 파일들을 순서대로, <strong>한 번 씩</strong>만 실행하는데, 만일 재실행하고 싶다면 <code>npx truffle migrate --reset</code>의 명령어로 실행하면 된다.</p>
<p>migration 파일이 실행되면서 배포가 진행되고, 배포가 되는 주소, 수수료 등등을 확인해볼 수 있다.
<img src="https://velog.velcdn.com/images/___pepper/post/d7aa591a-9afc-4f2f-bd27-33af3c921324/image.png" alt="">
ganache를 통해서도 배포가 완료되었음을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/___pepper/post/33a38634-e835-4334-bf8d-27f94dabcbdc/image.png" alt=""></p>
<p>배포가 완료된 contract은 <strong>truffle console</strong>에서 실행해볼 수 있다.
truffle console은 <code>npx truffle console</code> 명령어로 열면 된다.</p>
<p>배포된 contract를 <strong>await</strong>를 이용해 인스턴스로 생성하고, contract에 작성한 함수들을 실행하면 된다.</p>
<pre><code class="language-shell"># 배포된 contract instance 생성
trufle(ganache)&gt; goldtoken = await GLDToken.deployed()</code></pre>
<p><img src="https://velog.velcdn.com/images/___pepper/post/2ed052db-6941-4e62-83df-1ef86d324024/image.png" alt=""></p>
<p><del>(hardhat 이용한 배포는 추후 추가,,,,)</del></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[MIME type]]></title>
            <link>https://velog.io/@___pepper/MIME-type</link>
            <guid>https://velog.io/@___pepper/MIME-type</guid>
            <pubDate>Mon, 25 Apr 2022 00:24:30 GMT</pubDate>
            <description><![CDATA[<h1 id="👉-mime">👉 MIME?</h1>
<p>MIME이란 Multipurpose Internet Mail Extension의 약자로 파일 변환을 의미한다.
이메일에 붙일 파일을 텍스트 문자로 변환해서 이메일 시스템을 통해 전달하기 위해 개발되었기 때문에 Internet Mail Extension이라는 이름이 붙었다.
기존과는 달리 ASCII 파일 뿐만아니라 바이너리 파일들의 전송이 필요하게 되면서 텍스트 파일로의 전환이 필요해졌기 때문에 사용을 하게 되었다. </p>
<ul>
<li><strong>Encoding</strong> : 바이너리 파일을 텍스트 파일로 변환</li>
<li><strong>Decoding</strong> : 텍스트 파일을 바이너리 파일로 변환</li>
</ul>
<h2 id="👉-mime-처리-과정">👉 MIME 처리 과정</h2>
<p>MIME로 encoding한 파일들은 <strong>Content-type</strong> 정보를 파일 앞에 담는다.</p>
<p><strong>MIME 형식</strong> : 파일의 종류/파일의 포멧
    ex. image/gif
MIME 형식은 공백, 대/소문자를 구분하지 않는다.(주로 소문자로 작성됨)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TDD] pytest - command line에서 option을 사용해 test 진행하기]]></title>
            <link>https://velog.io/@___pepper/TDD-pytest-command-line%EC%97%90%EC%84%9C-option%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%B4-test-%EC%A7%84%ED%96%89%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@___pepper/TDD-pytest-command-line%EC%97%90%EC%84%9C-option%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%B4-test-%EC%A7%84%ED%96%89%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 12 Apr 2022 07:42:28 GMT</pubDate>
            <description><![CDATA[<h2 id="👉-pytest-option">👉 pytest option</h2>
<p>pytest에서는 command line에서 option을 줌에 따라서 실행할 테스트 함수를 결정해 테스트를 진행할 수 있는 기능을 제공한다.
command line option은 원하는 값으로 커스텀이 가능하며, <strong>decoration을 이용해서 특정 함수에만 적용</strong>하거나 <strong>전체 test 파일에 적용</strong>할 수도 있다.</p>
<h3 id="👉-option-custom">👉 option custom</h3>
<p>custom option을 사용하기 위해서는 설치 파일에 custion option을  사용할 것을 명시해주어야 한다.
<strong>pytest.ini</strong>에 작성해주어도 되고, <strong>setup.cfg</strong>에 적어주거나 poetry를 사용한다면 <strong>poetry.toml</strong>에 작성해주면 된다.</p>
<p><strong>pytest.ini 👇</strong></p>
<pre><code class="language-ini">[pytest]
# single usage
markers = 
    integration: mark a test as a integration test

# multiple usage
markers =
    integration: mark a test as a integration test
    dbconnect: mark a test as a db connection test</code></pre>
<p><strong>poetry.toml 👇</strong></p>
<pre><code class="language-toml">[tool.pytest.ini_options]
# single usage
markers = [
    &quot;integration: mark a test as a integration test&quot;
]

# multiple usage
markers = [
    &quot;integration: mark a test as a integration test&quot;
    &quot;dbconnect: mark a test as a db connection test&quot;
]</code></pre>
<p>설치 파일의 custom option 사용 여부를 알렸다면 이제 pytest의 <strong>최상단 conftest.py</strong>에 option의 추가와 option 여부에 따른 동작을 작성해준다.</p>
<p><strong>conftest.py 👇</strong></p>
<pre><code class="language-python"># option 사용을 위한 설정
def pytest_addoption(parser):
    parser.addoption(
        &quot;--dbconnect&quot;, action=&quot;store_true&quot;, help=&quot;run test connected with db&quot;
    )

# test 수행될 때의 동작 설정
def pytest_runtest_setup(item):
    # dbconnect option을 받지 않은 경우 test skip 설정
    if &quot;dbconnect&quot; in item.keywords and not item.config.getvalue(&quot;dbconnect&quot;):
        pytest.skip(&quot;need --dbconnect option to run the test&quot;)</code></pre>
<p>사용할 option을 지정해주면 <code>pytest --help</code>를 실행시켰을 때, 설정한 옵션이 help에 추가된 것을 확인할 수 있다.</p>
<h3 id="👉-option-적용하기">👉 option 적용하기</h3>
<h4 id="✏️-특정-함수에만-적용하기">✏️ 특정 함수에만 적용하기</h4>
<p>특정 함수에만 적용을 하고 싶다면 해당 옵션을 <strong>decorator</strong>로 사용해주면 된다.</p>
<pre><code class="language-python">import pytest


@pytest.mark.dbconnect
def test_create():
    print(&quot;create test&quot;)</code></pre>
<h4 id="✏️-전체-파일에-적용하기">✏️ 전체 파일에 적용하기</h4>
<p>전체 파일에 적용하고 싶다면 <strong>pytestmark</strong>라는 이름의 변수에 사용할 option을 지정해준다.</p>
<pre><code class="language-python">import pytest

pytestmark = pytest.mark.dbconnect

# use multiple
pytestmark = [
    pytest.mark.dbconntest,
    pytest.mark.integration
]</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS] sam을 이용한 serverless 배포]]></title>
            <link>https://velog.io/@___pepper/AWS-sam%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-serverless-%EB%B0%B0%ED%8F%AC</link>
            <guid>https://velog.io/@___pepper/AWS-sam%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-serverless-%EB%B0%B0%ED%8F%AC</guid>
            <pubDate>Tue, 22 Mar 2022 00:48:56 GMT</pubDate>
            <description><![CDATA[<h1 id="👉-serverless">👉 serverless?</h1>
<p>개발자가 서버를 관리할 필요 없이 app을 빌드하고 실행할 수 있도록하는 클라우드 네이티브 개발 모델이다.
serverless(= 서버가 없음)이지만 실제로 서버가 존재하지 않는 것은 아니다.
다만 serverless의 경우에는 <strong>application이 구동될 때에만 활성화</strong>가 된다.
즉, 이벤트가 발생될 때에만 resource가 할당되며 실행이 종료되면 resource가 비활성화되기 때문에 비용적인 측면에서도 효율성을 가질 수 있다.</p>
<h2 id="👉-sam">👉 SAM</h2>
<p><code>SAM</code>은 <code>Serverless Application Model</code>의 줄임말로 aws에서 제공하는 serverless application 생성를 위한 resource 중 하나이다.
lambda를 이용하여 배포를 진행할 때 <code>CloudFormation</code>을 사용하게 된다면 배포를 할 서비스에 비해 <code>CloudFormation</code>은 방대한데 <code>SAM</code>은 이런 <strong><code>CloudFormation</code>을 간단</strong>하게, web 개발에 쓰이는 것들만 사용할 수 있도록 해준다.
간편하게 생성할 수 있으나 제약 사항이 많다는 단점이 존재한다.</p>
<p>Api gateway 와 lambda를 따로 생성한 뒤 연결하여 같은 기능을 하도록 구현할 수 있으나, SAM을 사용하면 생성 및 연결 작업을 모두 진행해주기 때문에 훨씬 간편하게 구현이 가능하다.</p>
<p><code>SAM</code>을 터미널에서 사용하기 위해서는 <code>aws-sam-cli</code>를 설치해야 사용할 수 있다.</p>
<pre><code class="language-bash">$&gt; brew tap aws/tap
$&gt; brew install aws-sam-cli</code></pre>
<h3 id="✏️-생성하기">✏️ 생성하기</h3>
<pre><code class="language-bash">$&gt; sam init</code></pre>
<p>명령어를 통해 <code>SAM</code> 프로젝트 생성을 시도하면, 먼저 생성을 위한 template를 선택하도록 한다.
<img src="https://images.velog.io/images/___pepper/post/16b0ca60-e530-489a-a305-3888fd9e1cce/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-21%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%204.57.50.png" alt="">
AWS에서 제공하는 template을 사용하고자 하면 어떤 template을 사용할 것인지 선택을 해주고,
<img src="https://images.velog.io/images/___pepper/post/7b213f3d-76d3-4422-8854-d09f3345e68a/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-21%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%204.58.10.png" alt="">
어떤 언어를 사용할 것인지 선택한 후,
<img src="https://images.velog.io/images/___pepper/post/02645a1b-b468-4a7a-b4b4-aa1da6f2f07a/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-21%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%204.59.43.png" alt="">
어떤 패키지 타입을 사용할 것인지 선택과
<img src="https://images.velog.io/images/___pepper/post/73c489f6-a28d-4e09-be09-896099659971/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-21%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%204.59.58.png" alt="">
프로젝트의 이름을 작성해주면 sam project가 생성된다.
<img src="https://images.velog.io/images/___pepper/post/b4f9d063-6b7f-4e55-8428-61c4e6610310/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-21%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%205.00.41.png" alt="">
생성이 완료되면 다음과 같은 구조로 파일들이 생성이 된다.
<img src="https://images.velog.io/images/___pepper/post/7fde7e20-1bc7-4314-8e9e-e48316aa64d8/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-21%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%205.14.13.png" alt="">
이 중 중요한 파일은 <strong>template.yaml</strong> 파일인데, <strong>template.yaml</strong>에는 프로젝트에 대한 정보들이 작성되어있다.
<code>Resources</code> 하위에는 사용자가 접근할 <strong>enpoint</strong>와 접근 시 실행될 <strong>lambda 함수(handler)</strong> 에 대해 적혀있다.</p>
<pre><code class="language-yaml">Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello_world/
      # 실행되는 lambda handler 함수 지정
      Handler: app.lambda_handler
      Runtime: python3.9
      Architectures:
        - x86_64
      Events:
        HelloWorld:
          # AWS Gateway의 REST API를 생성
          Type: Api
          Properties:
            Path: /hello
            Method: get</code></pre>
<p>실행되는 <strong>lambda 함수(handler)</strong> 는 <code>CodeUri에 작성된 dir &gt; app.py</code> 파일에 <code>lambda_handler</code>라는 이름의 함수로 되어있다.
함수가 위치하는 <code>디렉토리</code>는 <code>CodeUri</code>에 작성해주고 <code>Handler</code>에 <code>파일명.함수명</code>으로 handler 함수를 작성해준다.</p>
<p><strong>lambda_hanlder method 👇</strong></p>
<pre><code class="language-python">def lambda_handler(event, context):

    return {
        &quot;statusCode&quot;: 200,
        &quot;body&quot;: json.dumps({
            &quot;message&quot;: &quot;hello world&quot;,
            # &quot;location&quot;: ip.text.replace(&quot;\n&quot;, &quot;&quot;)
        }),
    }</code></pre>
<h3 id="✏️-배포하기">✏️ 배포하기</h3>
<pre><code class="language-bash">$&gt; sam build</code></pre>
<p>우선 위와 같은 명령어를 통해 <code>template.yaml</code>에 적혀진 명세대로 프로젝트를 build한 후,</p>
<pre><code class="language-bash"># 최초 수행 시
$&gt; sam deploy --guided

# 이후 수행 시
$&gt; sam deploy</code></pre>
<p>deploy 명령을 통해 배포를 진행한다.
최초 배포를 진행할 때에는 꼭 <code>--guided</code> 옵션을 사용해주어야한다.
해당 옵션을 사용하면, 프로젝트의 argument들을 설정시켜준 뒤 배포가 진행된다.<img src="https://images.velog.io/images/___pepper/post/63cecf98-2aae-4762-acf4-b63b9d7929c7/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-21%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%206.41.05.png" alt=""></p>
<p>배포가 완료된 후 설정한 <strong>endpoint</strong>로 접근하면, <strong>handler 함수</strong>에 작성해놓은 대로 동작하는 것을 확인할 수 있다.
<img src="https://images.velog.io/images/___pepper/post/2d0e4669-a9ac-404d-b17f-928ddc15d638/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-21%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%206.42.30.png" alt="">
배포가 완료된 <code>SAM</code>의 stack은 <code>aws console &gt; CloudFormation &gt; stack</code>에서 확인할 수 있으며,.<img src="https://images.velog.io/images/___pepper/post/291ea008-d24b-4af6-8eee-2e9c9a55d36a/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-21%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%206.43.31.png" alt="">
<strong>lambda 함수</strong>는 <code>aws console &gt; Lambda &gt; Functions</code>에서 확인할 수 있다.<img src="https://images.velog.io/images/___pepper/post/50d24808-43a9-4f06-9196-0de703d4f252/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-21%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%206.46.24.png" alt=""></p>
<h3 id="✏️-삭제하기">✏️ 삭제하기</h3>
<pre><code class="language-bash">$&gt; sam delete</code></pre>
<p>생성했던 <code>SAM</code>은 delete 명령어를 통해 간단하게 삭제를 할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[K8S] kubectx를 이용한 멀티 클러스터 사용]]></title>
            <link>https://velog.io/@___pepper/K8S-kubectx%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EB%A9%80%ED%8B%B0-%ED%81%B4%EB%9F%AC%EC%8A%A4%ED%84%B0-%EC%82%AC%EC%9A%A9</link>
            <guid>https://velog.io/@___pepper/K8S-kubectx%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EB%A9%80%ED%8B%B0-%ED%81%B4%EB%9F%AC%EC%8A%A4%ED%84%B0-%EC%82%AC%EC%9A%A9</guid>
            <pubDate>Tue, 15 Mar 2022 08:38:23 GMT</pubDate>
            <description><![CDATA[<h1 id="👉-kubectx">👉 kubectx</h1>
<p><strong>kubectx</strong>는 kubernetes에 여러 cluster가 존재할 때 <strong>cluster간의 전환을 손쉽게 할 수 있도록</strong> 도와주는 tool이다.
<a href="https://github.com/ahmetb/kubectx">📁 kubectx github</a></p>
<h2 id="👉-설치하기">👉 설치하기</h2>
<pre><code class="language-bash">$&gt; brew install kubectx</code></pre>
<h2 id="👉-실행하기">👉 실행하기</h2>
<p><img src="https://images.velog.io/images/___pepper/post/fe6e1d84-6b22-48cb-8f8e-2a4acd775f00/kubectx-interactive.gif" alt="">
설치 완료 후 처음으로 <code>kubectx</code>를 실행하면 현재 local에 연결되어있는 cluster들을 확인할 수 있다.<img src="https://images.velog.io/images/___pepper/post/a51f150c-a92e-4f59-8c01-4b902fac1797/SE-19b454f7-4486-4c9c-9706-848d626f1c2a.png" alt="">
검은색으로 표시된 부분이 위치하고 있는 cluster이다.</p>
<h3 id="✏️-cluster-전환">✏️ cluster 전환</h3>
<pre><code class="language-bash">$&gt; kubectx &lt;cluster name&gt;</code></pre>
<p>원하는 클러스터의 이름으로 위와 같은 명령어를 실행한다면 해당 cluster로의 전환을 할 수 있다.<img src="https://images.velog.io/images/___pepper/post/ffae0331-9746-46e1-86ea-e1a3b6c6c8b6/SE-5ae77ff0-8d10-4922-99bc-db86380a5ea8.png" alt="">
바로 이전에 사용했던 cluster로의 전환을 하고 싶다면 <code>-</code>를 사용하면 된다.</p>
<pre><code class="language-bash">$&gt; kubectx -</code></pre>
<p><img src="https://images.velog.io/images/___pepper/post/46bf77df-2cdb-442c-8fe2-2d08c1d6631b/SE-87a91c09-f869-4654-b438-e52349939a9c.png" alt=""></p>
<h3 id="✏️-alias-설정">✏️ alias 설정</h3>
<p>cluster의 이름을 보면 상당히 길어 전환을 이룰 때마다 치기 힘든 경우도 있다.
이럴 경우 <strong>alias</strong>를 설정해준다면 훨씬 쉽게 cluster의 전환을 할 수 있을 것이다.</p>
<pre><code class="language-bash">$&gt; kubectx &lt;alias&gt;=&lt;cluster 이름&gt;</code></pre>
<p><img src="https://images.velog.io/images/___pepper/post/d88400a8-5e77-4963-ba49-8758d21451b8/SE-7c4e320d-d7da-40d4-b9d9-e91f05eaa00a.png" alt=""></p>
<h3 id="✏️-그-외-사용법">✏️ 그 외 사용법</h3>
<p>kubectx를 통한 기본적으로 그리고 자주 이용하는 기능은 위와 같고 추가적인 사용법을 알아보고 싶다면,</p>
<pre><code class="language-bash">$&gt; kubectx --help</code></pre>
<p>를 통해서 확인해볼 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flask] Flask-WTF을 이용한 form 사용하기]]></title>
            <link>https://velog.io/@___pepper/Flask-Flask-WTF%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-form-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@___pepper/Flask-Flask-WTF%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-form-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 11 Mar 2022 09:08:29 GMT</pubDate>
            <description><![CDATA[<h1 id="👉-flask-wtf">👉 Flask-WTF?</h1>
<p>flask에서 form을 관리할 수 있는 기능을 제공해주는 package이다.
form이란 사용자로부터 정보를 입력받는 방식을 말한다.</p>
<h2 id="👉-사용하기-전에">👉 사용하기 전에</h2>
<p>사용하기 위해서 dependency를 추가해준다.</p>
<pre><code class="language-bash">$&gt; poetry add Flask-WTF</code></pre>
<h2 id="👉-flask-wtf으로-회원가입-구현하기">👉 flask-wtf으로 회원가입 구현하기</h2>
<h3 id="✏️-구성하기">✏️ 구성하기</h3>
<p>원하는 form을 사용하기 위해서는 flask-wtf에서 제공하는 <strong>FlaskForm</strong> 클래스를 상속 받아서 클래스를 생성하면 된다.
flask-wtf을 사용함을 통해서 제공받을 수 있는 기능은 다음과 같다.</p>
<ul>
<li><strong>입력 받을 데이터의 형식</strong> : 입력 받을 시 원하는 form을 지정할 수 있음
  ex. 문자열(StringField), 비밀번호(PasswordField), 체크값(BooleanField),,,</li>
<li><strong>데이터 검증</strong> : <code>validators</code>를 사용하여 입력 받은 값에 대한 유효성 검사가 가능
  ex. 길이(Length), 필수 데이터(DataRequired), 동일 값인지 확인(EqualTo),,,</li>
<li><strong>CSRF(Cross-site request forgery) 보호</strong></li>
</ul>
<p><strong>auth/form.py 👇</strong></p>
<pre><code class="language-python">from flask_wtf import FlaskForm
from wtforms import StringField, validators, PasswordField, BooleanField


class RegisterForm(FlaskForm):
    username = StringField(&#39;Username&#39;, [validators.Length(min=4, max=25)])
    # email format에 대한 유효성 검사를 위해서는 추가적으로 email_validator package를 추가해야함
    email = StringField(&#39;Email&#39;, [validators.DataRequired()])
    password = PasswordField(&#39;New Password&#39;, [
        validators.DataRequired(),
        validators.EqualTo(&#39;password_confirm&#39;, message=&#39;Passwords must be matched&#39;),
    ])
    password_confirm = PasswordField(&#39;Repeat Password&#39;)
    accept_tos = BooleanField(&#39;I accept the TOS&#39;, [validators.InputRequired()])</code></pre>
<h3 id="✏️-사용하기">✏️ 사용하기</h3>
<h4 id="✏️-endpoint-생성">✏️ endpoint 생성</h4>
<p>사용할 form을 만들어주었다면 해당 form을 입력 받을 endpoint를 작성해준다.
이 때, 생성한 <strong>form 클래스의 instance를 선언해주고</strong> 해당 인스턴스로부터 데이터를 받아온다.
선언한 form 클래스의 instance는 <strong>template을 보여줄 때 form parameter의 값으로 넘겨줘야</strong> template에서 form으로 받은 값을 사용할 수 있다.</p>
<p><strong>auth/_<em>init_</em>.py 👇</strong></p>
<pre><code class="language-python">from login.auth.form import RegisterForm

@auth.route(&#39;/register&#39;, methods=[&#39;GET&#39;, &#39;POST&#39;])
def register():
    # 회원가입
    form = RegisterForm()

    if request.method == &#39;POST&#39;:
        if form.validate_on_submit():
            username = form.data.get(&#39;username&#39;)
            email = form.data.get(&#39;email&#39;)
            password = form.data.get(&#39;password&#39;)


            user = User()
            user.email = email
            user.name = username
            user.password = password

            db.session.add(user)
            db.session.commit()

            # 알림 메세지를 띄우기 위함 &gt;&gt; 필수X
            flash(&#39;회원 가입이 완료되었습니다.&#39;)
            return redirect(url_for(&#39;index&#39;))
    else:
        flash(&#39;입력한 값을 확인해주세요.&#39;)

    # 생성한 form instance template에 넘겨주기
    return render_template(&#39;auth/register.html&#39;, form=form)</code></pre>
<h4 id="✏️-templatehtml-작성">✏️ template(html) 작성</h4>
<p>flask에서는 <strong>jinja2</strong>를 이용해서 html를 구성할 수 있기 때문에 <code>{{ }}</code> 의 format을 통해서 변수값을 사용해 화면에 표시할 수 있다.</p>
<p><strong>/templates/auth/register.html 👇</strong></p>
<pre><code>&lt;form method=&quot;post&quot;&gt;
  {{ form.csrf_token }}
  &lt;div&gt;
      {{ form.username.label() }}
      {{ form.username() }}
  &lt;/div&gt;
    &lt;div&gt;
        {{ form.email.label() }}
        {{ form.email() }}
    &lt;/div&gt;
    &lt;div&gt;
        {{ form.password.label() }}
        {{ form.password() }}
    &lt;/div&gt;
    &lt;div&gt;
        {{ form.password_confirm.label() }}
        {{ form.password_confirm() }}
    &lt;/div&gt;
    &lt;div&gt;
        {{ form.accept_tos.label() }}
        {{ form.accept_tos() }}
    &lt;/div&gt;
  &lt;div&gt;
    &lt;button type=&quot;submit&quot; class=&quot;btn btn-primary&quot;&gt;Register&lt;/button&gt;
  &lt;/div&gt;
&lt;/form&gt;</code></pre><p>위와 같은 코드로 작성을 했지만 실제로는 다음과 같이 랜더링된다.
<img src="https://images.velog.io/images/___pepper/post/459dc8a9-6aaf-4b1c-99f4-7dd272ded03a/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-11%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%205.42.03.png" alt="">
해당 페이지로 접근할 시 화면에는 다음과 같이 나타나게 된다.
<img src="https://images.velog.io/images/___pepper/post/3b04e254-9d6a-4f19-bf8c-e9d821042dac/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-11%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%205.10.49.png" alt="">
form에서 제공하는 <code>validators</code>를 사용해 유효성 검증을 했기 때문에, 추가적인 과정 없이 설정한 validation과 맞지 않으면 다음과 같이 alert pop up들이 제공된다.
<img src="https://images.velog.io/images/___pepper/post/474c444a-69e8-448f-bad7-f446cbe0f6fa/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-11%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%205.54.02.png" alt=""><img src="https://images.velog.io/images/___pepper/post/6a50d5ed-9537-4d93-aaac-75263aaffa02/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-11%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%205.59.22.png" alt=""></p>
<h4 id="✏️-csrfcross-site-request-forgery-사용">✏️ CSRF(Cross-site request forgery) 사용</h4>
<p>CSRF는 <strong>사용자가 자신의 의지와는 무관하게 공격자가 의도한 행위(수정, 삭제, 등록 등)를 특정 웹사이트에 요청하게 만드는 공격</strong>을 말한다.
flask-WTF을 사용한다면 이런 공격에 대한 방지를 할 수 있다.</p>
<ol>
<li>flask-WTF에서 제공하는 <code>CSRFProtected()</code> instance를 생성<pre><code class="language-python">csrf = CSRFProtected()</code></pre>
</li>
<li>flask app과 연결<pre><code class="language-python">csrf.init_app(app)</code></pre>
</li>
<li>flask app의 secret config값 설정</li>
</ol>
<p>을 통해 간단하게 사용해볼 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[K8S] 간단하게  pod 생성하기]]></title>
            <link>https://velog.io/@___pepper/K8S-%EA%B0%84%EB%8B%A8%ED%95%98%EA%B2%8C-pod-%EC%83%9D%EC%84%B1%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@___pepper/K8S-%EA%B0%84%EB%8B%A8%ED%95%98%EA%B2%8C-pod-%EC%83%9D%EC%84%B1%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 10 Mar 2022 02:46:56 GMT</pubDate>
            <description><![CDATA[<p>보통 kubernetes에 pod을 생성하기 위해서는 <strong>deployment</strong>를 작성하여 pod를 띄운다.</p>
<p>간단하게 하나의 pod를 생성하여 사용하고 싶다면 다음과 같은 방법을 사용하면 된다.</p>
<ol>
<li><p>pod 생성</p>
<pre><code class="language-bash">kubectl run my-pod --image=python -n default -- sleep infinity</code></pre>
<p>pod는 생성하면 주어진 일을 하고 terminate 되기 때문에 command로 <code>sleep infinity</code>를 주어 계속 살아있도록 한다.</p>
</li>
<li><p>pod의 shell 접속</p>
<pre><code class="language-bash">kubectl exec -it my-pod -n default -- bash</code></pre>
<p>생성한 pod의 shell을 열어서 하고 싶은 작업을 수행할 수 있다.
kubectl을 통해 shell을 열지 않고 k9s를 통해서 shell을 열어 진행하는 것도 가능하다.</p>
</li>
<li><p>pod 삭제</p>
<pre><code class="language-bash">kubectl delete pod my-pod -n default</code></pre>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flask] Authlib를 이용한 oauth server 구현 #3]]></title>
            <link>https://velog.io/@___pepper/Flask-Authlib%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-oauth-server-%EA%B5%AC%ED%98%84-3</link>
            <guid>https://velog.io/@___pepper/Flask-Authlib%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-oauth-server-%EA%B5%AC%ED%98%84-3</guid>
            <pubDate>Tue, 08 Mar 2022 02:04:57 GMT</pubDate>
            <description><![CDATA[<h2 id="📁-token-endpoints">📁 Token Endpoints</h2>
<p><a href="https://docs.authlib.org/en/latest/flask/2/endpoints.html">👉 Authlib를 통해 구현할 수 있는 token endpoints</a></p>
<h3 id="✏️-token-revocation-endpoint">✏️ Token Revocation Endpoint</h3>
<p>사용자에게 발급한 token을 만료시키고자 할 때에는 <code>Revocation Endpoint</code>를 사용하면 된다.</p>
<h4 id="✏️-revocation-enpoint-클래스-생성">✏️ Revocation Enpoint 클래스 생성</h4>
<p>Revocation Endpoint를 사용하기 위한 클래스를 생성해주어야 하는데, Authlib에서 제공하는 <strong>RevocationEndpoint</strong>를 이용하면 쉽게 구현할 수 있다.</p>
<pre><code class="language-python">from authlib.oauth2.rfc7009 import RevocationEndpoint as _RevocationEndpoint

from login.database import db
from login.models import Token


class RevocationEndpoint(_RevocationEndpoint):
    # revoke할 token을 찾음
    def query_token(self, token, token_type_hint, client):
        if token_type_hint == &#39;access_token&#39;:
            return db.session.query(Token).filter_by(access_token=token).first()
        elif token_type_hint == &#39;refresh_token&#39;:
            return db.session.query(Token).filter_by(refresh_token=token).first()
        # without token_type_hint
        else:
            tok = db.session.query(Token).filter_by(access_token=token).first()
        if not tok:
            return db.session.query(Token).filter_by(refresh_token=token).first()
        return tok

    # 해당하는 token revoke
    def revoke_token(self, token):
        token.revoked = True
        db.session.add(token)
        db.session.commit()</code></pre>
<h4 id="✏️-flask-app에-연결">✏️ flask app에 연결</h4>
<p>클래스를 생성하였다면 <code>register_endpoint()</code> 함수를 이용해 초기화(flask_app)과 연결시켜주면 된다.</p>
<p><strong>_<em>init_</em>.py 👇</strong></p>
<pre><code class="language-python">oauth_server.register_endpoint(RevokationEndpoint)</code></pre>
<h4 id="✏️-revocation-endpoint-생성">✏️ revocation endpoint 생성</h4>
<p>마지막으로 revocation을 요청할 endpoint를 설정해준다.
endpoint에서는 <code>create_endpoint_response()</code> 함수에 <code>ENDPOINT_NAME</code>을 매개변수로 return 해주도록 하는데, revocation의 경우 <code>ENDPOINT_NAME</code>의 값은 <strong>revocation</strong>이다.</p>
<pre><code class="language-python">@oauth.route(&#39;/token/revoke&#39;, methods=[POST])
def revoke_token():
    return oauth_server.create_endpoint_response(RevocationEndpoint.ENDPOINT_NAME)</code></pre>
<h4 id="✏️-revoke-요청-보내기">✏️ revoke 요청 보내기</h4>
<p>revoke 요청은 설정한 <code>revokation endpoint</code>로,</p>
<ul>
<li>client id</li>
<li>client secret</li>
<li>token</li>
<li>token_type_hint(option)</li>
</ul>
<p>의 값을 <strong>form</strong> 방식으로 POST 요청을 보내면 된다.
요청 시 <strong>client id와 client secret은 basic 인증</strong>을 사용하여 보내준다.<img src="https://images.velog.io/images/___pepper/post/fe17b82b-27e2-4f1a-8121-2eaa5771ee06/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-08%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%2010.19.09.png" alt=""></p>
<p>성공적으로 revoke를 한다면 다음과 같은 응답을 받을 수 있다.<img src="https://images.velog.io/images/___pepper/post/12a74655-3be4-4217-b1b3-ad585c483bf2/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-08%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%2010.19.39.png" alt=""></p>
<p>revoke 후 데이터베이스에서 값을 살펴보면 <strong>False</strong>였던 revoked의 값이
<img src="https://images.velog.io/images/___pepper/post/74dc733e-7f47-4ef0-a2b7-409bcf7e94ae/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-08%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%209.43.42.png" alt="">
<strong>True</strong>로 변경된 것을 확인할 수 있다.<img src="https://images.velog.io/images/___pepper/post/1f1cda48-0ecc-46e8-9b0d-713c03d2c390/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-08%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%2010.05.09.png" alt=""></p>
<h3 id="✏️-token-introspection-endpoint">✏️ Token Introspection Endpoint</h3>
<p>Token에 대한 검증을 하고 싶다면 <code>Introspection Endpoint</code>를 사용하면 된다.</p>
<h4 id="✏️-introspection-endpoint-클래스-생성">✏️ Introspection Endpoint 클래스 생성</h4>
<p>Introspection Endpoint를 사용하기 위한 클래스를 생성해주어야 하는데, Authlib에서 제공하는 <strong>IntrospectionEndpoint</strong>를 이용하면 쉽게 구현할 수 있다.</p>
<pre><code class="language-python">class IntrospectionEndpoint(_IntrospectionEndpoint):
    # 검증할 token 찾음
    def query_token(self, token, token_type_hint, client=None):
        if token_type_hint == &#39;access_token&#39;:
            tok = db.session.query(Token).filter_by(access_token=token).first()
        elif token_type_hint == &#39;refresh_token&#39;:
            tok = db.session.query(Token).filter_by(refresh_token=token).first()
        else:
            # without token_type_hint
            tok = db.session.query(Token).filter_by(access_token=token).first()
            if not tok:
                tok = db.session.query(Token).filter_by(refresh_token=token).first()
        return tok

    def introspect_token(self, token):
        return {
            &#39;active&#39;: True,
            &#39;client_id&#39;: token.client_id,
            &#39;token_type&#39;: token.token_type,
            &#39;username&#39;: token.user.email,
            &#39;scope&#39;: token.get_scope(),
            &#39;sub&#39;: token.user.id,
            &#39;aud&#39;: token.client_id,
            &#39;iss&#39;: &#39;https://server.example.com/&#39;,
            &#39;exp&#39;: token.expires_at,
            &#39;iat&#39;: token.issued_at,
        }

    # token의 유효성 여부에 대한 check
    def check_permission(self, token, client, request):
        # for example, we only allow internal client to access introspection endpoint
        return True
        # return client.client_type == &#39;internal&#39;</code></pre>
<h4 id="✏️-flask-app에-연결-1">✏️ flask app에 연결</h4>
<p>클래스를 생성하였다면 <code>register_endpoint()</code> 함수를 이용해 초기화(flask_app)과 연결시켜주면 된다.</p>
<p><strong>_<em>init_</em>.py 👇</strong></p>
<pre><code class="language-python">oauth_server.register_endpoint(IntrospectionEndpoint)</code></pre>
<h4 id="✏️-introspection-endpoint-생성">✏️ introspection endpoint 생성</h4>
<p>마지막으로 검증을 진행할 endpoint를 설정해준다.
endpoint에서는 <code>create_endpoint_response()</code> 함수에 <code>ENDPOINT_NAME</code>을 매개변수로 return 해주도록 하는데, introspection의 경우 <code>ENDPOINT_NAME</code>의 값은 <strong>intropection</strong>이다.</p>
<pre><code class="language-python">@oauth.route(&#39;/token/revoke&#39;, methods=[POST])
def revoke_token():
    return oauth_server.create_endpoint_response(IntrospectionEndpoint.ENDPOINT_NAME)</code></pre>
<h4 id="✏️-introspection-요청-보내기">✏️ introspection 요청 보내기</h4>
<p>introspection 요청은 설정한 <code>introspection endpoint</code>로,</p>
<ul>
<li>client id</li>
<li>client secret</li>
<li>token</li>
<li>token_type_hint(option)</li>
</ul>
<p>의 값을 <strong>form</strong> 방식으로 POST 요청을 보내면 된다.
요청 시 <strong>client id와 client secret은 basic 인증</strong>을 사용하여 보내준다.<img src="https://images.velog.io/images/___pepper/post/e744198c-51ca-421a-b2d6-8b2df43ee80f/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-08%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%2011.04.10.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flask] Authlib를 이용한 oauth server 구현 #2]]></title>
            <link>https://velog.io/@___pepper/Flask-Authlib%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4-oauth-server-%EA%B5%AC%ED%98%84-2</link>
            <guid>https://velog.io/@___pepper/Flask-Authlib%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4-oauth-server-%EA%B5%AC%ED%98%84-2</guid>
            <pubDate>Mon, 07 Mar 2022 04:57:52 GMT</pubDate>
            <description><![CDATA[<h1 id="👉-authlib">👉 Authlib</h1>
<p>이전글 <a href="https://velog.io/@___pepper/Flask-authlib%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4-oauth-server-%EA%B5%AC%ED%98%84-1">👉 Authlib를 이용한 OAuth server 구현 #1</a></p>
<h2 id="📁-register-grants">📁 Register Grants</h2>
<p>Authlib에서는 다양한 종류의 <code>Authorization Code Grant</code>, <code>Implicit Grant</code>, <code>Resource Owner Password Credentials Grant</code> (= password grant), <code>Refresh Token Grant</code>, <code>Client Credentials Grant</code>, <code>Implicit Grant</code>와 같은 OAuth의 일반적인 grant 방식들을 구현하는 기능을 제공하며, 원하는 인증 방식을 커스텀할 수 있도록 하는 <code>Custom Grant</code> 기능도 제공한다.</p>
<h3 id="✏️-사용할-grants-생성">✏️ 사용할 grants 생성</h3>
<h4 id="✏️-authorization-code-grant">✏️ Authorization Code Grant</h4>
<ul>
<li><strong>Authorization Code</strong>
Authorization Code Grant 방식을 사용한다면 <code>authorization code</code>에 대한 정보 관리가 필요할 것이다.
<code>authorization code</code>는 <code>code</code>(= auth code), <code>redirect uri</code>, <code>response type</code>(code로 고정), <code>scope</code>, <code>client_id</code> 등의 정보로 구성되어 있으며, resource에 접근하는데 사용하는 <strong><code>Token</code>을 서버로부터 발급 받는데 사용</strong>이 된다.<pre><code class="language-python">from authlib.integrations.sqla_oauth2 import OAuth2AuthorizationCodeMixin
</code></pre>
</li>
</ul>
<h1 id="auth-code---code-redirect_uri-response_type-scope-client_id-nonce-auth_time">[auth code] - code, redirect_uri, response_type, scope, client_id, nonce, auth_time</h1>
<h1 id="발급-받은-auth-code에-대한-정보-저장">발급 받은 auth code에 대한 정보 저장</h1>
<p>class AuthorizationCode(db.Model, OAuth2AuthorizationCodeMixin):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(
        db.Integer, db.ForeignKey(User.id, ondelete=&#39;CASCADE&#39;)
    )
    user = db.relationship(&#39;User&#39;)</p>
<pre><code>
Authorization Code Grant는 Authlib에서 제공하는 **AuthorizationCodeGrant** 클래스 상속을 통한 클래스 생성을 통해 적용할 수 있다.

**/oauth/grants.py 👇**
```python
from authlib.oauth2.rfc6749 import grants

from app.database import db
from app.models import User, AuthorizationCode, Token


class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):

    def save_authorization_code(self, code, request):
        client = request.client
        auth_code = AuthorizationCode(
            code=code,
            client_id=client.client_id,
            redirect_uri=request.redirect_uri,
            scope=request.scope,
            user_id=request.user.id,
        )
        db.session.add(auth_code)
        db.session.commit()
        return auth_code

    def query_authorization_code(self, code, client):
        item = db.session.query(AuthorizationCode).filter_by(
            code=code, client_id=client.client_id).first()
        if item and not item.is_expired():
            return item

    def delete_authorization_code(self, authorization_code):
        db.session.delete(authorization_code)
        db.session.commit()

    def authenticate_user(self, authorization_code):
        return db.session.query(User).get(authorization_code.user_id)</code></pre><p>Authlib의 Authorization Code Grant의 <strong>default 인증 방식은 basic, post, none</strong>이다.
원하는 방식으로 지정하고 싶다면 <strong>Authorization Code Grant 클래스 내부에서</strong> <code>TOKEN_ENDPOINT_AUTH_METHODS</code>라는 config 값을 수정하면 된다.</p>
<pre><code>TOKEN_ENDPOINT_AUTH_METHODS = [
&#39;client_secret_basic&#39;, 
&#39;client_secret_post&#39;
]</code></pre><h4 id="✏️-resource-owner-password-credentials-grant">✏️ Resource Owner Password Credentials Grant</h4>
<p>Resource Owner Password Credentials Grant 방식은 Authlib에서 제공하는 <strong>ResourceOwnerPasswordCredentialsGrant</strong> 클래스 상속 받아 구현할 수 있다.</p>
<p><strong>/oauth/grants.py 👇</strong></p>
<pre><code class="language-python">from authlib.oauth2.rfc6749 import grants

from app.database import db
from app.models import User, AuthorizationCode, Token


class PasswordGrant(grants.ResourceOwnerPasswordCredentialsGrant):

    def authenticate_user(self, username, password):
        user = db.session.query(User).filter_by(email=username).first()
        if user.check_password(password):
            return user</code></pre>
<p>Authlib의 Password Grant의 <strong>default 인증 방식은 basic</strong>이다.
원하는 방식을 지정하고 싶다면 <strong>PasswordGrant 클래스 내에서</strong><code>TOKEN_ENDPOINT_AUTH_METHODS</code>라는 config 값을 수정하면 된다.</p>
<pre><code>TOKEN_ENDPOINT_AUTH_METHODS = [
&#39;client_secret_basic&#39;, &#39;
client_secret_post&#39;
]</code></pre><h4 id="✏️-refresh-token-grant">✏️ Refresh Token Grant</h4>
<p>Refresh Token Grant는 Authlib의 <strong>RefreshTokenGrant</strong> 클래스 상속을 통해 구현한 클래스를 사용해 이용할 수 있다.</p>
<p><strong>/oauth/grants.py 👇</strong></p>
<pre><code class="language-python">from authlib.oauth2.rfc6749 import grants

from app.database import db
from app.models import User, AuthorizationCode, Token


class RefreshTokenGrant(grants.RefreshTokenGrant):

    def authenticate_refresh_token(self, refresh_token):
        item = db.session.query(Token).filter_by(refresh_token=refresh_token).first()
        # define is_refresh_token_valid by yourself
        # usually, you should check if refresh token is expired and revoked
        if item and item.is_refresh_token_valid():
            return item

    def authenticate_user(self, credential):
        return db.session.query(User).get(credential.user_id)

    def revoke_old_credential(self, credential):
        credential.revoked = True
        db.session.add(credential)
        db.session.commit()</code></pre>
<p>Refresh Token Grant의 default 인증 방식은 basic이 default이다.
다른 방식의 인증 방식을 설정해주고 싶다면, <strong>Refresh Token Grant 클래스 내부에서</strong> <code>TOKEN_ENDPOINT_AUTH_METHODS</code> config 값을 수정해주면 된다.</p>
<pre><code>TOKEN_ENDPOINT_AUTH_METHODS = [
&#39;client_secret_basic&#39;, 
&#39;client_secret_post&#39;
]</code></pre><p>Authlib의 Refresh Token Grant은 <strong>refresh token을 이용하여 access token을 발급 받을 때 refresh token의 재발급이 이루어지지 않는 것이 default</strong>이다.
이 설정값을 바꾸고 싶다면 <strong>Refresh Token Grant 클래스 내부에서</strong> <code>INCLUDE_NEW_REFRESH_TOKEN</code> config 값을 <strong>True</strong>로 설정해주면 된다.</p>
<pre><code>INCLUDE_NEW_REFRESH_TOKEN = True</code></pre><p>각각의 grant 클래스를 생성했다면, <code>register_grant()</code> 함수를 통해 초기화(flask app과 연결)시켜주면 된다.</p>
<p><strong>_<em>init_</em>.py 👇</strong></p>
<pre><code class="language-python">oauth_server.register_grant(AuthorizationCodeGrant)
oauth_server.register_grant(PasswordGrant)
oauth_server.register_grant(RefreshTokenGrant)</code></pre>
<h3 id="✏️-사용하기">✏️ 사용하기</h3>
<h4 id="✏️-authorization-code-grant-1">✏️ Authorization Code Grant</h4>
<p><code>authorization code</code>를 받아오기 위해서 생성한 <code>authorization endpoint</code>로 요청을 보낸다.
요청을 보낼 때에는,</p>
<ul>
<li>client id</li>
<li>redirect uri : callback 주소</li>
<li>scope</li>
<li>respose type : <strong>code</strong></li>
</ul>
<p>를 담아 GET으로 보낸다.</p>
<p>Authlib의 example에서 기본적으로 제공하는 template을 사용했다면 다음과 같은 페이지가 뜬다.
<img src="https://images.velog.io/images/___pepper/post/2c63a014-0c87-490f-84fc-8b61fa18352e/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-02-25%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%203.53.54.png" alt="">
cosent 후 submit 버튼을 누르면,
<img src="https://images.velog.io/images/___pepper/post/155bb612-50d1-44ee-89cf-b50de3802b64/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-02-25%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%203.33.16.png" alt="">
<code>redirect uri</code>로 보내준 callback 주소로 redirect 되면서 code param 값에 <code>authorization code</code>를 받아오는 것을 확인할 수 있다.
이렇게 받은 <code>authorization code</code>를 이용해서 <code>access token</code>을 받을 수 있다.</p>
<p><code>access token</code>을 받을 때에는 설정한 <code>token endpoint</code>로</p>
<ul>
<li>authorization code</li>
<li>redirect uri</li>
<li>client id</li>
<li>client secret</li>
<li>grant type : <strong>authorization_code</strong></li>
</ul>
<p>를 <strong>form</strong> 방식으로 POST 요청을 보낸다.
성공적으로 token을 받아오면 다음과 같은 결과를 받을 수 있다.
<img src="https://images.velog.io/images/___pepper/post/9a4fcc22-38b3-483a-a9e6-c69223742713/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-02-25%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%206.08.32.png" alt="">
만일 refresh token을 받도록 설정해두었다면 다음과 같은 결과를 받을 수 있을 것이다.
<img src="https://images.velog.io/images/___pepper/post/9f138c30-2f10-409e-8bad-602188a07aea/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-02-25%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%206.23.20.png" alt=""></p>
<h4 id="✏️-password-grant">✏️ Password Grant</h4>
<p>password grant 방식으로 <code>access token</code>을 받을 때에는 설정한 <code>token endpoint</code>로</p>
<ul>
<li>username / password</li>
<li>client id</li>
<li>client secret</li>
<li>grant type : <strong>password</strong></li>
<li>scope</li>
</ul>
<p>를 <strong>form</strong> 방식으로 POST 요청을 보내면 된다.
성공적으로 token을 받아오면 다음과 같은 결과를 받을 수 있다.
<img src="https://images.velog.io/images/___pepper/post/9a12ab81-bca2-40e7-9528-269a4232dcb8/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-07%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%209.42.49.png" alt=""></p>
<h4 id="✏️-refresh-token-grant-1">✏️ Refresh Token Grant</h4>
<p><code>access token</code>이 만료된 경우 <code>access token</code>을 발급 받을 때 같이 받은<code>refresh token</code> 을 이용하여 <code>access token</code>을 재발급 받을 수 있다.</p>
<p><code>access token</code>을 재발급 받을 때에는 설정한 <code>token endpoint</code>로</p>
<ul>
<li>refresh token</li>
<li>client id</li>
<li>client secret</li>
<li>grant type : <strong>refresh_token</strong></li>
</ul>
<p>을 <strong>form</strong> 방식으로 POST 요청을 보내면 된다.
요청이 성공할 경우 다음과 같이 <code>access token</code>을 재발급 받을 수 있다.
<img src="https://images.velog.io/images/___pepper/post/840b1906-f3e1-4336-8fbf-68733149ed24/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-07%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%209.59.00.png" alt=""></p>
<h2 id="📁-resource-server">📁 Resource Server</h2>
<p><code>access token</code>을 발급 받았다면, 발급 받은 <code>access token</code> 을 이용하여 private resource에 접근할 수 있다.</p>
<h3 id="✏️-resource-server-구성">✏️ resource server 구성</h3>
<ol>
<li><strong>Bearer token</strong> 서버 지정</li>
</ol>
<p>server가 받는 token는 <strong>Bearer</strong> 방식으로 전달되어 오기 때문에, server에 <strong>Bearer 방식을 사용한 token임을 알려줄 필요</strong>가 있다.
Bearer validator는 Authlib에서 <strong>BearerTokenValidator</strong>를 통해 제공해주고 있다.
사용할 bearer validator class를 만들었다면, validator를 사용할 <strong>ResourceProtector</strong> 인스턴스를 생성해준다.</p>
<p><strong>/oauth/server.py 👇</strong></p>
<pre><code class="language-python">from authlib.oauth2.rfc6750 import BearerTokenValidator
from authlib.integrations.flask_oauth2 import ResourceProtector

class MyBearerTokenValidator(BearerTokenValidator):
    def authenticate_token(self, token_string):
        return db.session.query(Token).filter_by(access_token=token_string).first()

require_oauth = ResourceProtector()</code></pre>
<p>생성한 <strong>ResourceProctector</strong>의 인스턴스와 사용할 <strong>Bearer Validator</strong>를 연결해주면, token을 받았을 때 oauth server가 받은 <strong>token을 bearer 방식으로 처리</strong>할 수 있게 된다.</p>
<p><strong>_<em>init_</em>.py 👇</strong></p>
<pre><code class="language-python"># class 선언 시
require_oauth.register_token_validator(MyBearerTokenValidator())</code></pre>
<p>별도로 class를 선언하지 않고 <code>create_bearer_token_validator()</code> 함수만을 사용해도 bearer validator를 사용 가능하다.
<code>create_bearer_token_validator()</code>을 이용할 때에는 사용할 데이터베이스의 session과 Token model을 연결해준다.</p>
<pre><code class="language-python"># class 선언하지 않고 사용 시
bearer_cls = create_bearer_token_validator(db_session, Token)
require_oauth.register_token_validator(bearer_cls())</code></pre>
<ol start="2">
<li>private resource endpoint 지정</li>
</ol>
<p>특정 권한의 token이 있어야만 접근할 수 있는 페이지임을 설정하기 위해서는 <code>@require_oauth</code> annotation을 사용하면 된다.
<code>@require_oauth</code>를 사용하면 <code>@require_oauth</code>의 parameter 값으로 주어진 scope를 가진 token으로 접근 했을 때에만 해당 resource를 받아올 수 있다.
만일 parameter의 값으로 아무 값도 주지 않거나 None 값으로 지정한다면 protected 되지 않은 resource로서 처리된다.</p>
<pre><code class="language-python">from authlib.integrations.flask_oauth2 import current_token
from flask import jsonify, Blueprint

from login.oauth.server import require_oauth

api = Blueprint(&#39;api&#39;, __name__)


@api.route(&quot;/me&quot;)
# profile 권한이 있는 token을 이용해야 접근이 가능하도록 설정
@require_oauth(&#39;profile&#39;)
def private_resource():
    user = current_token.user
    return jsonify(id=user.id, email=user.email)

# protected 되지 않은 resource
@require_oauth()
def not_protected_resource():
    return &quot;not protected&quot;

@require_oauth(None)
def not_protected_resource2():
    return &quot;not protected2&quot;</code></pre>
<h3 id="✏️-token을-이용하여-resource-받아오기">✏️ Token을 이용하여 resource 받아오기</h3>
<p>private resource를 얻어오려면 <strong>resource를 받을 수 있는 권한을 가진 token</strong>을 이용하여 <strong>Bearer 방식</strong>으로 <code>Authorization</code> header에 값을 담아서 <code>token endpoint</code>에 GET 요청을 보내면 된다.
<img src="https://images.velog.io/images/___pepper/post/8002cefa-11c1-410b-aa33-1f08f6e77711/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-07%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%201.55.27.png" alt=""></p>
<h4 id="✏️-성공-응답">✏️ 성공 응답</h4>
<p><img src="https://images.velog.io/images/___pepper/post/8f788521-a376-4aa5-a836-31b76f6676b1/SE-5693de05-3ac9-43ae-b276-05dc1189ce97.png" alt=""></p>
<h4 id="✏️-에러-응답">✏️ 에러 응답</h4>
<ul>
<li>잘못된 token을 사용한 경우
<img src="https://images.velog.io/images/___pepper/post/f10b3a43-9ad3-4f92-a923-3aa944c5ccb6/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-07%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%2011.41.19.png" alt=""></li>
<li>Bearer 방식으로 보내지 않을 경우
<img src="https://images.velog.io/images/___pepper/post/4d0ba55c-cc81-4fc9-9ff6-24a221643ab3/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-07%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%2011.20.29.png" alt=""></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flask] ImportError: cannot import name 'json' from 'itsdangerous']]></title>
            <link>https://velog.io/@___pepper/Flask-ImportError-cannot-import-name-json-from-itsdangerous</link>
            <guid>https://velog.io/@___pepper/Flask-ImportError-cannot-import-name-json-from-itsdangerous</guid>
            <pubDate>Thu, 03 Mar 2022 04:53:39 GMT</pubDate>
            <description><![CDATA[<h2 id="👉-발단">👉 발단</h2>
<p><code>flask run</code>을 이용해서 flask app을 실행시키려고 했는데 <strong><span style="background-color:rgb(255,160,122)">ImportError: cannot import name &#39;json&#39; from &#39;itsdangerous&#39;</span></strong> 라는 에러가 발생하였다.
뭔일인가 싶어서 <code>flask</code>라는 명령어만 쳐봤는데에도 동일한 에러문구가 출력되었다.</p>
<h2 id="👉-문제-발생-이유">👉 문제 발생 이유</h2>
<p>구글링을 해보니 가장 상단에서 <a href="https://itsmycode.com/importerror-cannot-import-name-json-from-itsdangerous/">📁 문제 해결에 관련된 내용</a>을 찾을 수 있었다.
링크된 페이지를 읽어보면, 해당 문제는 <strong>flask 1.1.2 혹은 flask 1.1.4</strong>를 이용한 python flask app을 실행할 때 발생할 수 있는 문제라고 한다.
Flask는 <strong>Jinja와 함께 사용되는 MarkupSafe</strong>와 <strong>ItsDangerous</strong>라는 두가지 package에 의존성을 지닌다.
flask 1.1.2는 itdangerous &gt;= 0.24를 필요로 하는데, itsdangerous의 최신 버전(2.10)에서는 JSON API가 deprecated 되어 발생하는 문제로 보인다.</p>
<h2 id="👉-해결">👉 해결</h2>
<p>찾은 페이지에서는 이 문제 해결을 위해 3가지 방식을 제공해주는데, <strong>best way</strong>라는 첫번째 방법으로 시도했다.
 <strong>flask 1.1.2</strong>에서 발생하는 이슈이기 때문에 <strong>flask의 version을 2로 upgrade</strong> 시켜주는 것이 그 방법이다.</p>
<p>pyproject.toml 파일에 있던 flask의 version을 <strong>2.0.2</strong>로 바꾸어주고 <code>poetry update</code>를 실행해서 flask의 version을 변경시켜주었다.
그리고 다시 <code>flask</code> 혹은 <code>flask run</code>을 실행하니 정상적으로 flask app이 실행되었다.</p>
<h3 id="✏️-해결-방법들">✏️ 해결 방법들</h3>
<ol>
<li>flask upgrade : itsdangerous가 같이 upgrade됨<pre><code class="language-shell"># falsk version &gt; 2
pip install flask==2.0.2</code></pre>
</li>
<li>flask를 1.1.4로 upgrade하고 markupsafe를 2.0.1로 downgrade<pre><code class="language-shell">pip install Flask==1.1.4
pip install markupsafe==2.0.1</code></pre>
</li>
<li>itsdangerous downgrade : flask 1.1.2를 계속 사용할 경우<pre><code class="language-shell">pip install itsdangerous==2.0.1</code></pre>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[poetry] M1 mac에서의 cryptography 설치 오류]]></title>
            <link>https://velog.io/@___pepper/poetry-M1-mac%EC%97%90%EC%84%9C%EC%9D%98-cryptography-%EC%84%A4%EC%B9%98-%EC%98%A4%EB%A5%98</link>
            <guid>https://velog.io/@___pepper/poetry-M1-mac%EC%97%90%EC%84%9C%EC%9D%98-cryptography-%EC%84%A4%EC%B9%98-%EC%98%A4%EB%A5%98</guid>
            <pubDate>Wed, 02 Mar 2022 05:57:02 GMT</pubDate>
            <description><![CDATA[<h2 id="👉-발단">👉 발단</h2>
<p>git으로부터 코드를 clone 해온 후 poetry를 이용해서 필요한 package 설치를 시도했다.(<code>poetry install</code>)
인증 관련 코드여서 <strong>cryptography</strong>라는 package를 필요로 하는데 해당 package를 설치하는데에 있어서 계속해서 에러가 발생했다.
<img src="https://images.velog.io/images/___pepper/post/5f684ff2-ec46-42d3-ad87-8b073cc05e9c/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-02%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%202.09.20.png" alt="">
엄청 긴 에러문인데 결국 <strong>cryptography</strong> 설치가 안됐다는 얘기.</p>
<h2 id="👉-문제-발생-이유">👉 문제 발생 이유</h2>
<p>에러문을 읽어보니 openssl을 이용해서 cryptography를 설치해야하는데, <code>poetry install</code> 진행 시에 openssl을 불러오지 못하는 것 같았다.
혹시 일시적인 문제일까 싶어서 코드 삭제 후 재clone을 n번 정도 반복했는데
<img src="https://images.velog.io/images/___pepper/post/071740a5-d0e4-40ec-a73e-e5325de57f32/cs1467163024788514.jpeg" alt="">
그래서 도움을 요청했다.
그리고 받은 <a href="https://ondemand.tistory.com/327">✏️ 동일 문제 발생</a>했던 사람이 적어둔 기록물,,,
<strong>M1 mac</strong>에서 이와 관련한 이슈가 존재하는 듯했다.
<del>M1 진짜 갖다 버려야하나</del> </p>
<h2 id="👉-해결">👉 해결</h2>
<p>일단 <strong>M1 mac</strong> 에러라고 하니 다른 분이 시도한 방식을 그대로 시도해보았다.</p>
<ol>
<li><p>openssl version 확인</p>
<pre><code>$&gt; openssl version</code></pre><p>에러문에도 적혀있듯이 openssl을 불러오지 못해서 cryptography를 설치하고 못하고 있는 것인데 확인을 해보니 일단 LibreSSL이 설치되어있었다.</p>
</li>
<li><p>openssl 설치</p>
<pre><code>$&gt; brew install openssl@1.1 rust</code></pre></li>
</ol>
<p><strong>cryptography를 설치할 때 openssl</strong>가 필요하다고 에러문이 말하고 있으니 brew를 이용해서 openssl을 설치해줬다.
문제 해결하신 분을 보면 rust도 같이 설치해주셨는데 아마 cryptogrphy를 설치할 때 rust를 필요로 하기 때문에 같이 설치한 것으로 생각된다.
아무튼 따라하는 중이기 때문에 나도 동일하게 진행했다.</p>
<ol start="3">
<li>cffi 삭제 후 재설치</li>
</ol>
<pre><code>$&gt; pip uninstall cffi 
$&gt; LDFLAGS=-L$(brew --prefix libffi)/lib CFLAGS=-I$(brew --prefix libffi)/include pip install cffi --no-binary :all:</code></pre><ol start="4">
<li>사용할 openssl 명시 후 cryptography 설치<pre><code>LDFLAGS=&quot;-L$(brew --prefix openssl@1.1)/lib&quot; CFLAGS=&quot;-I$(brew --prefix openssl@1.1)/include&quot; pip install cryptography==3.3.1</code></pre>cryptography 설치를 진행하는데 openssl이 없다고 하니 설치하는데 이용할 openssl을 지정해줘서 cryptography를 설치해줬다.</li>
</ol>
<p>아무튼 그래서 일단 <strong>pip로는 cryptography 설치가 성공적으로 됐다.</strong>
그런데 이상하게도 project로 돌아와서 보니 <strong>cryptography가 dependency에 있지 않다고</strong> 되어있었다.
&quot;아 나 poetry 쓰고 있지^^&quot; 해서 <code>poetry install</code> 을 실행해주었더니, </p>
<pre><code>$&gt; LDFLAGS=&quot;-L$(brew --prefix openssl@1.1)/lib&quot; CFLAGS=&quot;-I$(brew --prefix openssl@1.1)/include&quot; poetry add cryptography</code></pre><p>로 poetry 상에서 dependency를 추가해주었다.
<img src="https://images.velog.io/images/___pepper/post/83427fad-af1d-4068-85d6-9b08afbbbd6b/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-02%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%202.31.28.png" alt="">
그랬더니 이번엔 또 다른 이슈가 발생했다.
<strong>cryptography를 build하는 것에 문제가 발생했다고,,,,</strong></p>
<p>일단 openssl 문제는 해결이 된 것 같고 뭐가뭔지 모르겠어서 <code>poetry add cryptography</code>를 통해 따로 cryptography를 설치하고자 했다.
그랬더니 <strong>cryptography 3.3.1에서 3.3.2로 upgrade하는 과정에서 문제가 발생</strong>한다는 에러문이 나타났다.</p>
<p>버전의 문제인가? 싶어서</p>
<ol>
<li><strong>pyproject.toml</strong>의 cryptography version을 3.3.1로 변경<pre><code>cryptography = &quot;3.3.1&quot;</code></pre></li>
<li>poetry upgrade 진행</li>
</ol>
<p>을 해줬더니 에러 없이 설치가 진행됐다!</p>
<p>아마 cryptography의 버전이 올라가면서 M1과는 맞지 않는 무언가가 생긴 것 같았다.
<code>poetry show | grep cry</code>로 잘 추가되었나 확인해보니 dependency에 잘 추가된 것도 확인할 수 있었다.
아무튼 해결되어서 다행.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[PostgreSQL] "/tmp/.s.PGSQL.5432" failed 에러]]></title>
            <link>https://velog.io/@___pepper/PostgreSQL-tmp.s.PGSQL.5432-failed-%EC%97%90%EB%9F%AC</link>
            <guid>https://velog.io/@___pepper/PostgreSQL-tmp.s.PGSQL.5432-failed-%EC%97%90%EB%9F%AC</guid>
            <pubDate>Tue, 01 Mar 2022 12:50:47 GMT</pubDate>
            <description><![CDATA[<h2 id="👉-발단">👉 발단</h2>
<p>PostgreSql을 사용하려고 데이터베이스 관리 도구에 들어갔는데, localhost의 DB 연결이 되지 않는다는 문구가 떴다.
이상하다 싶어 cmd 창에서 <code>psql</code> 명령어를 실행해보았더니 다음과 같은 에러문이 출력되면서 postgreSql이 실행되지 않았다.<img src="https://images.velog.io/images/___pepper/post/5e371940-8601-485f-b811-45ba0dee1c33/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-02-28%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%201.36.07.png" alt="">
에러 문구를 자세히 읽어보니 <span style="background-color:rgb(255,160,122)"><strong>psql: error: connection to server on socket &quot;/tmp/.s.PGSQL.5432&quot; failed</strong></span> 라고 적힌 것으로 보아 postgresql의 local server가 실행되지 않고 있는 듯 했다.</p>
<h2 id="👉-문제-발생-이유">👉 문제 발생 이유</h2>
<p>사실 아직도 잘 모르겠다.
일단 구글링을 통해 알아낸 것은 <strong>&quot;/tmp/.s.PGSQL.5432&quot; failed</strong>라는 에러가 떴을 때에는 pc 내부 프로세스의 일시적인 충돌로 인한 PostgreSql 서버의 오류 발생으로 인한 에러라고 한다.
이럴 경우에는,</p>
<ol>
<li>postgres 서버를 종료하고<pre><code>$&gt; brew services stop postgres</code></pre></li>
<li>pid를 삭제한 다음</li>
</ol>
<pre><code>$&gt; rm /usr/local/var/postgres/postmaster.pid</code></pre><ol start="3">
<li>postgres 서버를 재실행하면<pre><code>$&gt; brew services start postgres</code></pre>정상 동작한다고 한다.</li>
</ol>
<p>하지만 내 컴퓨터에서는 안먹히는걸?ㅎ<img src="https://images.velog.io/images/___pepper/post/b3018e15-5f5f-4e94-889f-404846ab8f10/bd9d1231eb074.png" alt=""></p>
<h2 id="👉-해결">👉 해결</h2>
<p>아무튼 그래서 생각하기에는 컴퓨터(Mac M1) 환경 설정 및 다양한 파일들을 설치하면서 path가 꼬이게 된 것 같았다.
구글 검색을 하면서 나와있는 모든 시도들을 다해봤는데, 정말 하나도 적용되지 않고 계속 같은 메세지만 출력,,,,^^
일단 path가 꼬인 것은 같으니 postgresql을 삭제해주기로 했다.</p>
<h3 id="✏️-postgresql-삭제">✏️ postgresql 삭제</h3>
<p><a href="https://yohanpro.com/posts/postgresql/error-5432">따라한 방식</a> &gt;&gt; 이분은 삭제하고 재설치를 하니 정상 실행이 됐다고 하는데 일단 내 컴퓨터에서는 소용 없기는 했다.</p>
<ol>
<li>postgres 종료<pre><code>$&gt; brew services stop postgresql</code></pre></li>
<li>postgresql 관련 파일들 삭제<pre><code>$&gt; brew uninstall --force postgresql
$&gt; rm -rf /usr/local/var/postgresql
$&gt; rm -rf .psql_history .psqlrc .psql.local .pgpass .psqlrc.local
$&gt; brew cleanup</code></pre></li>
<li>완전히 지워졌는지 확인<pre><code>$&gt; brew list | grep sql</code></pre></li>
</ol>
<p>일단 삭제 후에 <strong>postgresql</strong>을 재설치했으나 똑같은 오류는 계속 발생했다^^
정말 삭제 후 재설치만 한 열댓번은 한 것 같고, <code>brew upgrade</code>도 시도해보고 아무튼 구글링해서 찾을 수 있는 것들은 모두 다 시도해본 것 같다.
그러다가 <strong>postgresql</strong>이 아니라 <span style="background-color:rgb(255,160,122)"><strong>postgresql@13</strong>으로 특정 package를 지정해서 설치</span>하는 방법을 시도해봤는데, 신기하게도 이 방법을 사용하니깐 에러가 발생하지 않고 정상 동작이 되었다!</p>
<p>아무튼 정상 동작이 되었으니 다행이기는 한데 왜 postgresql로 설치하면 에러가 발생하고 postgresql@13으로 지정해서 사용하면 괜찮은지는 아직 잘 모르겠어서 좀 더 알아보기는 해야할 듯 하다.</p>
<p><strong>진행한 방식 👇</strong></p>
<pre><code>$&gt; brew install postgresql@13
$&gt; brew services start postgresql@13

# 정상적으로 실행되었다면 psql 명령어를 입력하여 잘 동작하는지 확인해 볼 것
$&gt; psql</code></pre><p>사실 이렇게 postgresql을 설치하고도 <strong>psql</strong>이라는 명령어가 안뜨는 오류가 발생했었는데,<code>brew install postgresql</code>로 <strong>postgresql</strong>을 설치하고 <strong>실행은 posgresql@13으로</strong>(<code>brew services start postgresql@13</code>) 하니 괜찮아졌다.
진짜 무슨 연유인지 아직도 모르겠음,,,,
<img src="https://images.velog.io/images/___pepper/post/39bce1d9-3e95-49fd-b9b3-98f3ee9fca21/zzal.webp" alt="">
아시는 분이 계시다면 꼭 알려주셨으면 좋겠어요,,,,</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Docker] 이미지 빌드 cli로 실행하기]]></title>
            <link>https://velog.io/@___pepper/Docker-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%B9%8C%EB%93%9C-cli%EB%A1%9C-%EC%8B%A4%ED%96%89%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@___pepper/Docker-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%B9%8C%EB%93%9C-cli%EB%A1%9C-%EC%8B%A4%ED%96%89%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 24 Feb 2022 07:28:48 GMT</pubDate>
            <description><![CDATA[<h1 id="👉-docker-image">👉 Docker image?</h1>
<p>Docker image는 docker에서 컨테이너 실행을 위해 필요한 모든 파일과 환경 설정들을 포함하고 있는 것을 의미한다.</p>
<ul>
<li>image : 실행되지 않은 상태</li>
<li>container : image가 실행된 상태<h2 id="👉-이미지-빌드">👉 이미지 빌드</h2>
</li>
</ul>
<p>Docker의 이미지는 <strong>Dockerfile</strong>에 적힌 명세대로 생성이 된다.
Dockerfile의 구성/작성에 대해서는 이전에 가볍게 정리해보았었다. <a href="https://velog.io/@___pepper/Container-Docker-Kubernetese#-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0">👉 docker 이미지 빌드</a></p>
<p>Dockerfile로 이미지를 빌드할 때에는 한가지 base에 계속 빌드(single stage build)를 진행하거나 여러 단계로 base를 나누어 빌드(multi stage build)를 진행할 수 있다.</p>
<ul>
<li><strong>single stage build</strong>
실행을 위한 package 파일 생성에 필요한 부가적인 것들을 설치와 package 파일 생성을 <code>FROM</code>에서 상속 받은 하나의 base image에 모두 진행한 뒤 docker 이미지로 생성해낸다.</li>
<li><strong>multi stage build</strong>
<code>FROM</code>에서 상속 받은 base image의 사본들을 생성해 단계별로 사용을 한 뒤, docker 이미지를 만들기 위한 base image에서는 깨끗한 환경에서 package 파일만이 존재할 수 있도록 만들어낸다.</li>
<li><em>multi stage*</em> 방식으로 이미지를 빌드하면 <strong>용량이 작고 깨끗한 docker image를 생성</strong>할 수 있다.</li>
</ul>
<p>** multi stage build를 사용한 Dockerfile 예시👇**</p>
<pre><code>FROM python:3.7.7-alpine3.12 as base

# stage 1
WORKDIR /usr/src/app

# package build에 필요한 환경 설정
.
.
.

# stage 2
FROM base as builder

# package build에 필요한 dependency, library들 설치
.
.
.

# stage 3
FROM base as final

# package build

COPY --from=builder /venv /venv
COPY --from=builder /usr/src/app/dist .
COPY wsgi.py ./wsgi.py
COPY docker/entrypoint.sh ./entrypoint.sh

ENV PATH=&quot;/venv/bin:${PATH}&quot;
RUN . /venv/bin/activate &amp;&amp; pip install *.whl

EXPOSE 5000
ENTRYPOINT [&quot;./entrypoint.sh&quot;]</code></pre><p>DockerFile를 작성하였다면, 다음과 같은 명령어를 통해 docker file로부터 docker image를 빌드해낼 수 있다.</p>
<pre><code class="language-shell"># context directory : build를 시작하는 dir
# 생성하는 이미지에 tag를 붙이고 싶을 때 -t(tag option) 사용
# tag를 미지정할 경우 자동으로 &quot;lastet&quot;가 붙여짐
$&gt; docker build [-t] &lt;ImageName:TagName&gt; &lt;context directory&gt;

$&gt; docker build .

$&gt; docker build -f &lt;docker 파일 위치&gt;</code></pre>
<h2 id="👉-빌드한-docker-image-push하기">👉 빌드한 docker image push하기</h2>
<p>Docker image는 <strong>registry</strong>라는 docker를 사용할 수 있도록하는 외부 저장소에 올려놓고 사용할 수 있다.
Registry에 docker image를 push 해두면 사용하고 싶을 때 언제든 가져다 사용할 수 있다.</p>
<h3 id="✏️-default">✏️ Default</h3>
<p><code>$&gt; docker build</code>를 통해 docker image를 빌드하면 기본적으로 <strong>docker.io(docker hub)</strong> 이름으로 host가 붙여있다고 생각한다.</p>
<h3 id="✏️-aws의-ecr">✏️ AWS의 ECR</h3>
<p>AWS에서는 <strong>ECR(Elastic Container Registry)</strong> 라는 별도의 registry를 제공한다.
AWS resource를 사용할 예정이라면 ECR를 사용하는 편이 더 편리할 것이다.</p>
<p>ECR에 docker image를 push하기 위해서는 <strong>ECR repo의 주소로 docker image에 이름을 붙여줘야</strong>한다.
<strong>ECR repo 주소는</strong> <code>{ecr host 주소}/{image 이름}</code>이고, 이 때 사용하는 <strong>ECR의 host 주소</strong>는 <code>{aws account}.dkr.ecr.{aws region}.amazonaws.com</code>로 고정된다.
AWS account의 값은 AWS console을 통해 확인하거나 <code>aws sts get-caller-identity | jq -r .Account</code> 명령어를 통해 cli로 확인할 수도 있다.</p>
<h4 id="✏️-ecr에-push-해보자">✏️ ECR에 push 해보자!</h4>
<ul>
<li><strong>AWS console</strong>을 이용해서 올리기
AWS console에서 ECR로 들어가 직접 생성한 docker image를 업로드할 수 있다.</li>
<li><strong>cmd</strong>에서 올리기</li>
</ul>
<ol>
<li><p>aws repository 생성</p>
<pre><code>$&gt; aws ecr create-reposiotry --repository-name repo이름</code></pre></li>
<li><p>docker가 aws에 로그인</p>
<pre><code class="language-shell">$&gt; docker login --username AWS --password-stdin &lt;ecr host 주소&gt;</code></pre>
<p>만약 aws의 username과 password를 모른다면 <code>$&gt; aws ecr get-login-password --region region이름</code> 명령어를 통해 알아올 수 있다.</p>
</li>
<li><p>docker push</p>
<pre><code>$&gt; docker push ${ecr repo uri}:${image tag}</code></pre><h2 id="👉-shell-script-활용하기">👉 shell script 활용하기</h2>
<p>위의 과정을 cmd에 하나씩 명령어를 쳐가며 진행을 해도 되지만, <strong>shell script</strong>로 과정을 작성해두고 script file만 실행시켜서 build 후 push까지 진행한다면 <strong>훨씬 간편</strong>할 것이다.</p>
</li>
</ol>
<p><strong>build.sh 👇</strong></p>
<pre><code class="language-bash">#!/bin/bash

# 항상 같은 dir에서 실행될 수 있도록 위치를 build를 시작할 위치로 옮겨줌
SCRIPT_DIR=&quot;$( cd &quot;$( dirname &quot;${BASH_SOURCE[0]}&quot; )&quot; &gt;/dev/null 2&gt;&amp;1 &amp;&amp; pwd )
PROJECT_ROOT=$SCRIPT_DIR
cd &quot;${PROJECT_ROOT}&quot;

AWS_REGION=&quot;aws region&quot;
IMAGE_NAME=&quot;생성할 docker image 이름&quot;
IMAGE_TAG=&quot;latest&quot;

DOCKERCLI=docker
AWSCLI=aws
JQCLI=jq

# build를 진행할 때 필요한 명령어들이 설치되어있는지 여부 확인
# 없어서 error가 발생한다면 종료되도록 setting
set -e
command -v ${AWSCLI} &gt; /dev/null 2&gt;&amp;1    || { echo &gt;&amp;2 &#39;aws-cli not found&#39;; exit 1; }
command -v ${DOCKERCLI} &gt; /dev/null 2&gt;&amp;1 || { echo &gt;&amp;2 &#39;docker not found&#39;; exit 1; }
command -v ${JQCLI} &gt; /dev/null 2&gt;&amp;1     || { echo &gt;&amp;2 &#39;jq not found&#39;; exit 1; }
# setting 해제
set +e

VERSION=$( poetry version -s )
AWS_ACCOUNT_ID=$( ${AWSCLI} sts get-caller-identity | ${JQCLI} -r .Account )    

ECR_HOST_NAME=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com
REPO_URI=$ECR_HOST_NAME/$IMAGE_NAME

# 이미지 빌드
${DOCKERCLI} build -t ${IMAGE_NAME} . -f ./Dockerfile
# 빌드한 이미지에 tag 붙이기
# docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]
${DOCKERCLI} tag &quot;${IMAGE_NAME}:${IMAGE_TAG}&quot; &quot;${IMAGE_NAME}:${VERSION}&quot;
${DOCKERCLI} tag &quot;${IMAGE_NAME}:${IMAGE_TAG}&quot; &quot;${REPO_URI}:${VERSION}&quot;
${DOCKERCLI} tag &quot;${IMAGE_NAME}:${IMAGE_TAG}&quot; &quot;${REPO_URI}:${IMAGE_TAG}&quot;

# docker가 aws 로그인
aws ecr get-login-password --region $AWS_REGION | \
    docker login --username AWS --password-stdin $ECR_HOST_NAME

# docker image push
${DOCKERCLI} push &quot;${REPO_URI}:${VERSION}&quot;
${DOCKERCLI} push &quot;${REPO_URI}:${IMAGE_TAG}&quot;</code></pre>
<p>build.sh를 생성했다면 실행 권한을 추가하고,</p>
<pre><code>$&gt; chmod +x build.sh</code></pre><p>실행시킨다.</p>
<pre><code>$&gt; ./build.sh</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[Flask] Authlib를 이용한 oauth server 구현 #1]]></title>
            <link>https://velog.io/@___pepper/Flask-authlib%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4-oauth-server-%EA%B5%AC%ED%98%84-1</link>
            <guid>https://velog.io/@___pepper/Flask-authlib%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4-oauth-server-%EA%B5%AC%ED%98%84-1</guid>
            <pubDate>Wed, 23 Feb 2022 00:42:37 GMT</pubDate>
            <description><![CDATA[<h2 id="👉-oauth-server-구성-요소">👉 oauth server 구성 요소</h2>
<h3 id="📁-oauth-serverprovider">📁 oauth server(provider)</h3>
<p>Login한 사용자에게 <code>authorization code</code>를 보내주고, <code>authorization code</code>를 받아 <code>access token</code>을 내려준다.
즉, <strong>사용자에 대한 validation</strong>을 수행한다.</p>
<h3 id="📁-oauth-client">📁 oauth client</h3>
<p>client id, (client secret), redirect uri, grant type</p>
<ul>
<li>token : access token, refresh token, expires at, issued at, revoke at</li>
<li>code : authorization code</li>
<li>user : username, password, email<h2 id="👉-authlib">👉 Authlib</h2>
flask auth server(provider)를 제공해준다.
<a href="https://docs.authlib.org/en/latest/flask/2/authorization-server.html">Authlib</a>를 사용하면 flask를 이용해 간단하게 oauth server를 구현해볼 수 있다.</li>
</ul>
<p><a href="https://github.com/authlib/example-oauth2-server">📁 Authlib github 👉 example code</a></p>
<h3 id="📁-authorization-server">📁 Authorization Server</h3>
<h4 id="✏️-user">✏️ User</h4>
<p>Resource Owner로 로그인을 하는 사용자와 관련된 객체이다.
인증을 받음을 통해 제공하는 <strong>서비스를 이용하려고 하는 주체</strong>에 해당한다.</p>
<h4 id="✏️-client">✏️ Client</h4>
<p><code>client_id</code>, <code>client_secret</code>, <code>redirect_uri</code>, <code>grant_type</code> 등의 값 인증 정보를 요청할 때 필요한 값을 가지고 있는 객체로 <strong>서비스 제공자</strong>에 해당한다.
<code>redirect_uri</code>, <code>response type</code> ,<code>grant_type</code>, <code>scope</code>의 값들은 <code>client_metadata</code> column에 json 형태로 저장된다.</p>
<p><strong>client_matadata 👇</strong></p>
<pre><code class="language-python">{
  &quot;client_name&quot;: &quot;인가 받은 client&quot;,
  &quot;client_uri&quot;: &quot;인가 받은 client가 사용하는 주소&quot;,
  &quot;grant_types&quot;: [
    &quot;client가 사용할 수 있는 grant type들&quot;,
    &quot;authorization_code&quot;,
    &quot;password&quot;,
    &quot;refresh_token&quot;
  ],
  &quot;redirect_uris&quot;: [
    &quot;callback 주소&quot;
  ],
  &quot;response_types&quot;: [
    &quot;응답으로 받을 수 있는 값들&quot;
    &quot;code&quot;,
    &quot;password&quot;,
    &quot;token&quot;
  ],
  &quot;scope&quot;: &quot;client에게 인가된 권한들&quot;,
  &quot;skip_consent&quot;: 사용자의 동의를 받을 것인지 여부(true / false),
  &quot;token_endpoint_auth_method&quot;: &quot;token을 인증하는 method 타입(basic / post)&quot;
}</code></pre>
<p>사용자가 로그인을 시도하면 <strong>Client</strong>에서는</p>
<ol>
<li><code>client id</code>와 (필요한 경우)<code>client secret</code>을 <strong>Basic 인증</strong> 방식으로 <strong>OAuth Server</strong>로부터 인증을 받고,</li>
<li>이후 사용할 grant 방식에 따라 사용자 인증을 진행한다.</li>
</ol>
<p>Client 클래스는 Authlib를 이용하면 <strong>OAuth2ClientMixin</strong> 클래스를 제공해주기 때문에, 상속 받아 활용하면 쉽게 구현할 수 있다.</p>
<pre><code class="language-python">from authlib.integrations.sqla_oauth2 import OAuth2ClientMixin

# [client] - client_id, client_secret, client_metadata, expires_at
class Client(db.Model, OAuth2ClientMixin):
    id = Column(Integer, primary_key=True)
    user_id = Column(
        Integer, ForeignKey(User.id, ondelete=&#39;CASCADE&#39;)
    )</code></pre>
<h4 id="✏️-token">✏️ Token</h4>
<p>OAuth는 <strong>Bearer</strong> 방식의 token을 사용한다.
Token은 사용자에 대한 인증이 성공된 경우, 사용자가 resource를 접근하기 위해서 사용된다.
기본적으로 <code>access token</code>, <code>refresh token</code>, <code>client id</code>, <code>expire at</code>, <code>scope</code> 등의 정보로 구성되어있다.</p>
<p>Token 클래스 역시 Authlib에서 제공하는 <strong>OAuth2TokenMixin</strong>을 통해 쉽게 구현이 가능하다.</p>
<pre><code class="language-python">from authlib.integrations.sqla_oauth2 import OAuth2TokenMixin

# [token] - access_token, refresh_token, expires_at, scope, client_id
# 발급 받은 token에 대한 정보 저장
class Token(db.Model, OAuth2TokenMixin):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(
        db.Integer, db.ForeignKey(User.id, ondelete=&#39;CASCADE&#39;)
    )
    user = db.relationship(&#39;User&#39;)</code></pre>
<h4 id="✏️-server">✏️ Server</h4>
<p>Authlib에는 <strong>AuthorizationServer</strong>라는 인증 요청과 응답을 처리하는 클래스를 제공해주고 있다.
간단하게 instance를 생성하고 flask app에 연결시켜주면 활용이 가능하다.</p>
<p><strong>app/oauth/server.py 👇</strong></p>
<pre><code class="language-python">from authlib.integrations.flask_oauth2 import AuthorizationServer

# 인증을 요청한 client 찾기
def query_client(client_id):
    return db.session.query(Client).filter_by(client_id=client_id).first()


# authorization code를 받고 token을 생성하여 저장
def save_token(token_data, request):
    if request.user:
        user_id = request.user.get_user_id()
    else:
        # client_credentials grant_type
        user_id = request.client.user_id
        # or, depending on how you treat client_credentials
        # user_id = None

    token = Token(
        client_id=request.client.client_id,
        user_id=user_id,
        **token_data
    )
    db.session.add(token)
    db.session.commit()


oauth_server = AuthorizationServer()</code></pre>
<p>추가적으로 필요한 로직이 있다면 제공되는 <code>query_client()</code>, <code>save_token()</code>을 수정하고, <strong>AuthorizationServer</strong> instance도 생성을 했다면 flask app을 연결시켜준다.</p>
<p><strong>app/<strong>init</strong>.py 👇</strong></p>
<pre><code class="language-python">from flask import Flask

app = Flask(__name__)

oauth_server.init_app(app, query_client=query_client, save_token=save_token)</code></pre>
<p>config의 값을 변경하지 않아도 정상 동작하지만, 세부적인 설정을 하고 싶다면 config의 값을 변경시켜주면 된다.</p>
<ul>
<li><code>OAUTH2_TOKEN_EXPIRES_IN</code> : 발행하는 token의 유효 기간 설정<pre><code class="language-python">OAUTH2_TOKEN_EXPIRES_IN = {
  &#39;authorization_code&#39;: 864000,
  &#39;implicit&#39;: 3600,
  &#39;password&#39;: 864000,
  &#39;client_credentials&#39;: 864000
}</code></pre>
</li>
<li><code>OAUTH2_ACCESS_TOKEN_GENERATOR</code> : access token에 관한 설정</li>
<li><code>OAUTH2_REFRESH_TOKEN_GENERATOR</code> : refresh token 관련 설정</li>
<li><blockquote>
<p>True / False 값으로 token 생성 시 refresh token을 줄 것인가를 설정할 수 있다. </p>
</blockquote>
</li>
</ul>
<h4 id="✏️-endpoint">✏️ endpoint</h4>
<p>Authorization Server에대한 설정을 마쳤으면, 인증을 받기 위한 <strong>endpoint</strong>를 작성해주면 된다.</p>
<pre><code class="language-python">from flask import Blueprint, request, render_template
from flask_login import current_user, login_required

from login.oauth.server import oauth_server

oauth = Blueprint(&#39;oauth&#39;, __name__)


# authorize code를 받아오는 부분 &gt;&gt; auth code를 받기 위해서는 login을 필요로 함
@oauth.route(&#39;/authorize&#39;, methods=[&#39;GET&#39;, &#39;POST&#39;])
@login_required
def authorize():
        if request.method == &#39;GET&#39;:
        # get으로 들어오는 경우 &gt;&gt; 사용자로부터 consent 받기
        # 사용자에게 consent를 받는 부분
        try:
            # grant = oauth_server.get_consent_grant(end_user=current_user)
            grant = oauth_server.validate_consent_request(end_user=current_user)
            client = grant.client
            scope = client.get_allowed_scope(grant.request.scope)
        except OAuth2Error as error:
            current_app.logger.exception(&#39;oauth-error&#39;)
            return error.error

        # You may add a function to extract scope into a list of scopes
        # with rich information, e.g.
        # scopes = describe_scope(scope)  # returns [{&#39;key&#39;: &#39;email&#39;, &#39;icon&#39;: &#39;...&#39;}]
        return render_template(
            &#39;oauth/authorize.html&#39;,
            grant=grant,
            user=current_user,
            client=client,
            scopes=scope,
        )

    # post로 들어오는 경우 &gt;&gt; 사용자의 동의 여부를 전송
    # form에 동의하시겠습니까? -&gt; 동의했는지의 여부를 서버로 보내줘야하기 때문에
    # client setting &gt;&gt; consent를 받는지 여부에 따라서 달라지는 부분
    # option : skip_content = true 이런 식으로
    confirmed = request.form[&#39;confirm&#39;]
    if confirmed:
        # granted by resource owner
        # 동의한 경우
        return oauth_server.create_authorization_response(grant_user=current_user)
    # 동의하지 않은 경우
    return oauth_server.create_authorization_response(grant_user=None)


# auth code를 받아 token 발급
@oauth.route(&#39;/token&#39;, methods=[&#39;POST&#39;])
def issue_token():
    return oauth_server.create_token_response()
</code></pre>
<p>client는 접근에 대한 인가를 받기 위해 사용자를 <strong>/authorize endpoint</strong>로 보내 <code>authorization code</code>를 받는다.
받은 <code>authorization code</code>를 이용해 <strong>/token endpoint</strong>로 token 요청을 보내면 client는 resource에 대한 접근 권한을 인가받게 된다.</p>
]]></description>
        </item>
    </channel>
</rss>