Deno KVでOTPもどき生成ツールを作った話
この記事は、Deno Advent Calendar 2023の23日目です。
作り始めた動機
私は記事を限定公開するときに環境変数に書いて、それが外部から入力されたときに通す形にしていました。
しかしこれでは、一度その内容を知ってしまえば限定公開になりませんし、何よりいちいち手動で環境変数を書き換えるのは面倒です。
そこで、「期間限定にするなら、いわゆるOTPのような感じにすればよいのでは?」と思い、期間限定の文字列を返すAPIとそれを閲覧するデスクトップアプリを作ることにしました。
忙しい人向け
-
Deno KVの
expireIn
を使うことで一定期間で自動削除されるパスワードができる - 各サービスごとにエンドポイントを切ることで、複数のサイトやページに対応できる
- Deno KVはリモートのKVも取れるので、GETのエンドポイントを設けなくてもデータが取れる
- webview_denoを使えば、デスクトップアプリも作れる
- Denoを入れるだけですぐに使えるDeno KVはお手軽でいいぞ!!
APIはザックリとシーケンス図を書いていたので御覧ください。シンプルです。
OTP配信APIの作成
このAPIはPOSTリクエストでOTPを取得し、クエリパラメータなどで外部から取得したOTPと突合するために利用します。
OTPの保存にはDeno KVを利用します。
Deno KVとは
JavaScript/TypeScriptランタイムであるDenoに組み込まれたKey-Value型のデータベースです。
Deno CLIではSQLite、Deno DeployではFoundationDBが使われています。
最近になってセルフホスト版がリリースされ、npmモジュールもリリースされました。
興味がある方は、Node.jsやそれ以外のプロジェクトで試してみてください。
OTPの生成
OTPの生成にあたって、どのようなOTPにするか考えました。
今回はGoogle Authenticatorなどで使われる数字のみにしてみようと思い、crypto.getRandomValues
を使用しています。ランダムな文字列であればcrypto.randomUUID
でも良いかもしれません。
crypto.getRandomValues
で生成したランダムな数字の配列の中から、さらにランダムなIndexで数値を取り出して使う、という形式です。
OTPの保存
OTPの保存は、OTPの取得や更新と合わせて行います。
まず、全体像が以下になります。
まずはDeno KV上のOTPの取得と新規OTPの生成を行ないます。
その後、以前のOTPが空っぽか有効期限切れであれば新規のOTPを保存します。difference
を使い、現在日時との差分を出しています。
Denoは標準ライブラリが非常に豊富で、眺めてみると「これもあるの!?」という驚きがあるので是非使ってみてください。
あとは以前のOTPが新規のOTPと被っていないか確認し、そこも問題なければ以前のOTPを返します。
REST APIの作成
API作成にはHonoを利用します。
この部分は特筆したことをやっていないのと、そこまで量がないので、全てのコードを貼って終わりにしようと思います。
/otp/
の後ろに自由にパスを設定して、そのパスをキーにしてDeno KVに保存する仕組みです。デフォルトで1日保存され、クエリパラメータでの指定で保存時間を延長することができます。
Honoの簡潔さにはいつも助けられています。
OTP表示アプリの作成
配信するAPIを作成したので、次は表示するアプリを作成します。
アプリ作成には、webview_denoというサードパーティのモジュールを利用します。
各OSのWebviewを使って表示しているので、Chromiumが同梱されているElectronより軽量なはずですが、表示に差異が出るようです。
今回はメイン機のWindowsで使うので、Windowsでのコンパイルを目指します。
リモートのKVと接続
当初はGETリクエストで取得しようと思ったのですが、Deno KVには引数にリモートのKV URLを設定するとリモートのKVと繋ぐことができるので、今回はそれを利用してみます。
Deno DeployのProjectsページのKVタブを覗くと、以下のようなセクションがあるはずです。
この中のhttps://api.deno.com/
から始まるURLをDeno.openKv()
の引数にしてください。
なお、これを利用するにはアクセストークンの発行が必要になります。
アカウント設定ページの中に、アクセストークンのセクションがあるはずです。
これは実装には書かず、環境変数にDENO_KV_ACCESS_TOKEN
と記述するだけで読み取られます。
UIの作成
webview_denoでアプリ部分を書き、PreactやTwindでUI部分を書いています。
webview_denoにはいくつかUIの書き方があるのですが、今回は開発時にWebサイトとして表示させたかったため、Web Workerとして読み込みアプリ内部でlocalhostを読みに行く形式です。
当初、Preactを使うにあたってdeno.jsonc
のcompilerOptions
にPreactの設定を書いて、各ファイルの/** @jsx h */
を省略しようと思っていました。
ただ、現状だとcompileコマンドで読み込まれずエラーになるので、妥協して省略せずに書いています。
また、TwindはTwind Intellisenseが上手く動きません。私の場合はtwind.config.ts
を作成しても動きませんでした。
これの回避方法としては身も蓋もないのですが、空っぽのtailwind.config.ts
を作成することでTailwind CSS Intellisenseを動かせます。TwindがTailwind CSSと互換性があるからできることですね。
UI部分はあまり作り込んでいないので、引っかかるところはそれほどありませんでした。
最後に単一実行可能アプリケーションとしてコンパイルします。
デスクトップアプリとしてコンパイル
Denoにはcompileコマンドというものがあり、例えばWindows端末からmacOS向け単一実行可能アプリケーションをコンパイルするなど、端末に左右されないコンパイルをすることができます。
向き先を指定するには--target
をコマンド実行時に指定する必要があり、Windowsはx86_64-pc-windows-msvc
と書くことで指定できます。
deno compile --target x86_64-pc-windows-msvc ./app/main.ts
今回はUIをWeb Workerとして指定していたり、現状だとunstableのDeno KVを利用するので、以下のようになります。
deno compile --unstable --target x86_64-pc-windows-msvc --include ./app/worker.tsx ./app/main.ts
ここに--allow-*
や--deny-*
など認可系フラグが入りますが、大筋はこのようになるはずです。
Windowsのターミナル表示について
Windows向けのコンパイルだと、デフォルトでアプリの背後にターミナルが表示されます。
これに対して、v1.36.0で--no-terminal
が追加されたのですが、現状上手いこと動きません。
現状の結論としては、妥協してターミナルを表示させたままにするか、macOSでコンパイルするかの2択になるのかな、と思っています。
詳しくは別記事に書いたので、よければ御覧ください。
最後に
Deno KVはいいぞ! あとあまり使われてなさそうな機能はIssueが見つかりやすいから報告してみよう! という感じのツール制作記事でした。
次回はuki00aさんの『Denoのまとめ (2023)』です!
Discussion