動かざることバグの如し

近づきたいよ 君の理想に

Next.jsでCSRF対策を簡単に実装する方法

環境

  • Nextjs 13
  • edge-csrf 1.0.3-rc1
  • TypeScript 5

やりたいこと

今日は、Next.jsの開発について書こうと思います。Next.jsは、開発がとても便利であることがよく知られています。

しかし、CSRFトークンの機能がRailsのようにネイティブで用意されていないため、自分で実装する必要があります。

これは面倒ですが、edge-csrfというライブラリを使えば簡単にCSRFトークンの実装ができます。先日、私自身もこのライブラリを使ってCSRFトークンの実装を行い、スムーズに開発を進めることができました。

今回はそのメモ

https://github.com/amorey/edge-csrf

インストール

npm install edge-csrf
yarn add edge-csrf

ミドルウェアの追加

APIのエンドポイントごとにCSRFの実装するのは流石に面倒。そこでNextjsのミドルウェア機能を使う。これを使うとAPIリクエストごとに自動でCSRFトークンをチェックしてくれる

プロジェクト直下にmiddleware.tsを作成 人によってはsrc直下になるのかな

import csrf from 'edge-csrf';
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

// initalize protection function
const csrfProtect = csrf({
  cookie: {
    secure: process.env.NODE_ENV === 'production',
  },
});

export async function middleware(request: NextRequest) {
  const response = NextResponse.next();

  // csrf protection
  const csrfError = await csrfProtect(request, response);

  // check result
  if (csrfError) {
      return new NextResponse('invalid csrf token', { status: 403 });
  }
    
  return response;
}

フロントエンドの実装

あとは以下のようなフォームを pages/form.ts とかに作成して

import type { NextPage, GetServerSideProps } from 'next';
import React from 'react';

type Props = {
  csrfToken: string;
};

export const getServerSideProps: GetServerSideProps = async ({ res }) => {
  const csrfToken = res.getHeader('x-csrf-token') || 'missing';
  return { props: { csrfToken } };
}

const FormPage: NextPage<Props> = ({ csrfToken }) => {
  return (
    <form action="/api/form-handler" method="post">
      <input type="hidden" value={csrfToken}>
      <input type="text" name="my-input">
      <input type="submit">
    </form>
  );
}

export default FormPage;

バックエンドの実装

pages/api/form-handler.tsを以下のように用意すると

import type { NextApiRequest, NextApiResponse } from 'next';

type Data = {
  status: string
};

export default function handler(req: NextApiRequest, res: NextApiResponse<Data>) {
  // this code won't execute unless CSRF token passes validation 
  res.status(200).json({ status: 'success' });
}

確認

<input type="hidden" value={csrfToken}>

のようにセットされていればフォームの送信は成功するが

<input type="hidden" value='invalid_token'>

のようになっていると送信に失敗するはず

またcurlで試しても失敗すればおk

❯ curl -XPOST http://localhost:3000/api/form-handler/
invalid csrf token

オプション

クッキーや挙動の設定は公式ドキュメント参照

サンプル

公式Gitにサンプルプロジェクトがあるのでこれが一番参考になるかも

https://github.com/amorey/edge-csrf/tree/main/example-ts

自分用にMantineのFormを利用したバージョンのサンプルも作成した

https://github.com/thr3a/nextjs-csrf-example

薬忘れずに飲んでる奴は偉い

今日は、毎日薬を欠かさず飲む人がすごいと思った。

俺は、朝昼晩の3回飲まなきゃいけない薬があるけど、毎日どこかしら忘れてしまう。

特に朝は時間がなくて、飲めないこともある。

でも、効果が切れて体調が悪くなってから「あ、飲んでないな」と気づくパターンが続いている。

みんなはどうやって、毎日薬を飲み忘れずに飲んでいるんだろうか。不思議で仕方がない。今後は、忘れないようにリマインダーアプリを使ってみようと思う。

kube-prometheus-stack インストールメモ

環境

やりたいこと

  • kubeadmで構築した自宅k8s環境にモニタリングを入れたい
  • podリソースとか監視とかしたい
  • 調べた感じkube-prometheus-stackがいいっぽい
  • PrometheusとGrafanaとか一括でインストールしてくれる

やり方

まずはnamespace作成

kubectl create namespace monitoring

helmでインストールする

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack -n monitoring

デプロイメントの確認

❯ kubectl get deployment -n monitoring
NAME                                       READY   UP-TO-DATE   AVAILABLE   AGE
kube-prometheus-stack-grafana              1/1     1            1           61s
kube-prometheus-stack-kube-state-metrics   1/1     1            1           61s
kube-prometheus-stack-operator             1/1     1            1           61s

Serviceの確認

❯ kubectl get svc -n monitoring
NAME                                             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
alertmanager-operated                            ClusterIP   None            <none>        9093/TCP,9094/TCP,9094/UDP   67s
kube-prometheus-stack-alertmanager               ClusterIP   10.96.101.95    <none>        9093/TCP                     77s
kube-prometheus-stack-grafana                    ClusterIP   10.96.67.32     <none>        80/TCP                       77s
kube-prometheus-stack-kube-state-metrics         ClusterIP   10.96.1.62      <none>        8080/TCP                     77s
kube-prometheus-stack-operator                   ClusterIP   10.96.236.225   <none>        443/TCP                      77s
kube-prometheus-stack-prometheus                 ClusterIP   10.96.252.238   <none>        9090/TCP                     77s
kube-prometheus-stack-prometheus-node-exporter   ClusterIP   10.96.107.171   <none>        9100/TCP                     77s
prometheus-operated                              ClusterIP   None            <none>        9090/TCP                     66s

参考リンク

Eslintのrecommended-requiring-type-checkingで有効になるルール項目一覧

環境

  • @typescript-eslint/eslint-plugin v5.56
  • typescript v5

概要

今日は、Typescriptにおいてeslintのチェックがよく使われるため、有用なEslintのルールについて知っておくことが重要なことであると言えます。特に 「recommended-requiring-type-checking」を有効にすることが推奨されているため、これが有効になるEslintルールを調べ、その一覧を共有します。

有効になるルール

ルールの一覧自体は実はソース見たほうが早い

https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/src/configs/recommended-requiring-type-checking.ts

でそのルールが以下

  • @typescript-eslint/await-thenable: then メソッドを持たない値に対して await を使わないようにする
  • @typescript-eslint/no-floating-promises: promise を返す関数やメソッドの呼び出しに対して、適切なエラーハンドリングを行うようにする
  • @typescript-eslint/no-for-in-array: 配列に対して for-in ループを使わないようにする
  • no-implied-eval: off (このルールは TypeScript ではサポートされていません)
  • @typescript-eslint/no-implied-eval: eval や setTimeout などの関数に文字列を渡さないようにする
  • @typescript-eslint/no-misused-promises: promise を返す関数やメソッドを、boolean の代わりに使わないようにする
  • @typescript-eslint/no-unnecessary-type-assertion: 不要な型アサーションや非nullアサーションを使わないようにする
  • @typescript-eslint/no-unsafe-argument: 型安全でない引数を関数やコンストラクターに渡さないようにする
  • @typescript-eslint/no-unsafe-assignment: 型安全でない代入や分割代入を行わないようにする
  • @typescript-eslint/no-unsafe-call: 型安全でない関数やメソッドの呼び出しを行わないようにする
  • @typescript-eslint/no-unsafe-member-access: 型安全でないプロパティやメソッドへのアクセスを行わないようにする
  • @typescript-eslint/no-unsafe-return: 型安全でない値を返さないようにする
  • require-await: off (このルールは TypeScript ではサポートされておらず、@typescript-eslint/require-await ルールと競合します)
  • @typescript-eslint/require-await: async 関数が await を含まず、promise を返さず、try-catch-finally ブロックも含まれておらず、コメントも含まれておらず、async キーワードが不要である場合はエラーとする

TypeScriptでDiscordBotを作ってみる

やりたいこと

Discordは便利なコミュニケーションアプリケーションであり、Discord botを開発することは、プログラミングに興味を持っている人にとって魅力的なプロジェクトの一つです。 今回は、Discord botをTypeScriptで作成し、!pingコマンドを送信すると、pong!が返ってくるようにする作業手順について説明します。

事前準備

このプロジェクトを完了するには、以下の環境が必要です。

  • TypeScript
  • node.js 18

TypeScriptとnode.jsをインストールするには、それぞれの公式Webサイトから入手することができます。 また、Discord bot開発の初心者である場合は、Discord Developer Portalから閲覧できる公式APIを把握することが重要です。

作業手順

以下の手順に沿って、Discord botの作成を進めていきます。

1. discord.jsをインストールする

discord.jsをインストールすることで、JavaScript/TypeScriptでDiscord botを作成することができます。以下のコマンドを使用して、discord.jsをインストールしましょう。

npm install discord.js

2. ソースコードを記述する

以下のTypeScriptコードを記述し、ファイル名をdiscord_bot.tsとしましょう。これにより、discord.jsをインポートし、botとして動作することが設定されます。また、@types/discord.jsをインストールすることで、TypeScriptでDiscord botを作成することができます。

import { Message, Client } from 'discord.js';

const client = new Client({
  intents: ['Guilds', 'MessageContent', 'GuildMessages'],
});

client.once('ready', () => {
  console.log('Ready!');
});

client.on('messageCreate', async (message: Message) => {
  if (message.author.bot) return;
  if (message.content.startsWith('!ping')) {
    message.channel.send('pong!');
  }
});

client.login(process.env.TOKEN);

3. Discord Developer Portalトークンを取得する

BotをDiscord上で動かすためには、Discord Developer PortalBotトークンを取得し、環境変数に設定する必要があります。 設定方法は以下の記事を参考にしてみてください。

Discord Botを作る手順

export TOKEN=xxxx のように環境変数を設定し、以下のコマンドを使用して、Botを実行しましょう。

node discord_bot.ts

4. BotをDiscordに招待する

最後に、BotをDiscordサーバーに招待する必要があります。Botを管理しているサーバーにアクセスし、Applicationsセクション、OAuth2タブに移動します。 スコープで以下の項目をチェックしたら、選択された権限を付与するためにURLが生成されます。URLを使用して、Botをサーバーに追加します。

  • bot
  • messages.read
  • message.create

5. Botをテストする

Botを実行し、Discordチャンネルに!pingというテキストを送信してみましょう。Botからpong!という返信が返ってくれば、Botが正常に動作していることを確認できます。

コードの説明

以下に、discord_bot.ts内の主要コードの説明を示します。

const client = new Client({
  intents: ['Guilds', 'MessageContent', 'GuildMessages'],
});

clientは、Discord APIにアクセスするために使用されるクラスです。 インスタンスを作成すると、BOTの状態と操作を管理できます。

client.once('ready', () => {
  console.log('Ready!');
});

client.once()は、Discord APIに接続した直後の処理を指定することができます。このコードでは、BOTが正常に実行されたことを確認するために、ログにReady!と出力します。

client.on('messageCreate', async (message: Message) => {
  if (message.author.bot) return;
  if (message.content.startsWith('!ping')) {
    message.channel.send('pong!');
  }
});

client.on()は、Discordチャットにメッセージが投稿された場合に実行される関数を割り当てることができます。このコードでは、受信したメッセージが!pingから始まる場合に、pong!というメッセージを返信するように処理されます。

注意点

  • 環境変数の管理には十分注意してください。外部に公開してはいけない情報を含む可能性があります。
  • DiscordのAPIには多くのユースケースがあり、今回紹介したコードは初心者向けの非常にシンプルで基本的なものです。より高度なDiscord botを作成するためには、APIの各種コンポーネントについて調べる必要があるかもしれません。