【Rails】自作WebアプリからAPIを叩いてMastodonアカウント作成

表題について個人開発のWebアプリ**「[まちかどルート](https://machiroute.herokuapp.com/)」**v5.3への実装メモ。プログラミングに入門して8ヶ月弱。Mastodonのアカウント作成APIに挑戦してみました。

<img width="320" alt="machiroute.herokuapp.com_(iPhone 5_SE).png" src="https://qiita-image-store.s3.amazonaws.com/0/335872/18a5ff31-8d9a-7ab5-1129-e054848362d1.png">

# MastodonとのOauth連携について

**まちかどルート**では開発スタート当初から**SNS「Mastodon」**のアカウントでログイン認証できるようにしてあります。

MastodonとのOauth連携についてここでは詳しく書きません。

たとえばこちらの記事などを参考にしています。

**RailsなサービスでMastodonとのOauth連携を実装する**
https://qiita.com/foloinfo/items/48389328da43d73ae7a0

# アカウント作成用のAPIが用意された

今回の実装に至ったきっかけは、Mastodonのv2.7.0から[アカウント作成用のAPI](https://github.com/tootsuite/mastodon/pull/9572)が追加されたことでした。さらに[mastodon-api v2.0.0](https://github.com/tootsuite/mastodon-api/releases/tag/v2.0.0)というgemにも`Mastodon::REST::Client#create_account`が追加されたんです。

これらによって自作のWebアプリからの新規アカウント作成プロセスをかなり簡略化できるのではないかと思い、「よしやってみよう」となりました。

なお、APIに関するドキュメントは[こちら](https://www.rubydoc.info/gems/mastodon-api/Mastodon/REST/Accounts#create_account-instance_method)にあります。

# view : アカウント作成フォーム

```erb
<p>新規アカウント登録</p>
<%= form_with url: '/auth/create/account', method: :post, local: true do |f| %>
  <input type="text" name="username" placeholder="ユーザー名(英数字&空白なし)">
  <input type="email" name="email" placeholder="メールアドレス">
  <input type="password" name="password" placeholder="パスワード">
  <input type="hidden" name="agreement" value="true">
  <input type="hidden" name="locale" value="ja">
  <%= button_tag :type => "submit", data: { disable_with: "登録中..." } do %>
    <span>登録</span>
  <% end %>
<% end %>
```

前述のドキュメントにあるとおり、Mastodonのアカウント作成では`username` `email` `password` `agreement` `locale` という5つのパラメータが必要なので、それらの入力フォームを作ります。

`agreement` のデータ型はBooleanなのでここでは `true` としてあります。たとえば「利用規約に同意する」といったチェックボックスを用意し、ユーザーが同意してチェックしたら `true` にする、という用途に使えるんでしょうね。

# controller : sessions_controller.rb

```ruby:/app/controllers/sessions_controller.rb
def create

if params[:username] # ここで新規アカウント作成or通常のログインかを判断

  # APIを叩くときに必要となるMastodonインスタンスの管理者情報を抽出
  # ここでは私が管理しているMastodonインスタンス「アナザーギルド」を例にしています
  @mastodon_master = User.where(uid: 'townsguild@another-guild.com').order(created_at: :desc).first

  # アカウント作成フォームで入力された5つのパラメータを格納
  username = params[:username]
  email = params[:email]
  password = params[:password]
  agreement = params[:agreement]
  locale = params[:locale]

  # 5つのパラメータをparamsにハッシュとして格納
  params = { "username" => username, "email" => email, "password" => password, "agreement" => agreement, "locale" => locale }

  # APIを叩けるようにするためのclient生成
  client = Mastodon::REST::Client.new(base_url: "https://another-guild.com", bearer_token: @mastodon_master.token)

  # すでに登録済みのアカウントかどうかを判断するための例外処理 begin～rescue
  begin
    # ここでアカウント作成APIを叩きます
    @new_user = client.create_account(params)
  rescue => e
    flash[:error] = "登録できませんでした。ユーザー名またはメールアドレスはすでに存在します"
    redirect_to root_path
    return
  end

  # アカウントが無事に作成されたらuserモデルに新規レコード作成&保存
  uid = username + '@another-guild.com'
  @user = User.find_or_create_by(uid: uid) do |user|
    user.provider = 'mastodon'
    user.token = @new_user.access_token
  end

  flash[:notice] = "登録したメールアドレスにメールが届きます。確認のうえ、まちかどルートにログインしてください"

end

end
```

# ルーティング : routes.rb

```ruby:/config/routes.rb
post "/auth/create/account" => "sessions#create"
```
アカウント作成フォームから `sessions_controller.rb` の `create` に誘導するためのルーティングとしてこの1行を追記します。

# Rack Middlewareへの例外処理

以上のように書くと簡単なようですけど、じぶんにとっては難題でした。それに拍車をかけるように大きな壁が...。というのもアカウント作成までうまくいき、Mastodonから確認メールが送られてくるのですが

![{5B04FA28-6269-472C-85BD-62AF33653AB4}.png](https://qiita-image-store.s3.amazonaws.com/0/335872/0a7a0f6b-7d5b-dc7a-85f9-4a81aa972a7f.png)

確認メールにある「確認し まちかどルート に戻る」ボタンを押すと

```
NoMethodError (undefined method `split' for nil:NilClass):
  
omniauth-mastodon (0.9.3) lib/omniauth/strategies/mastodon.rb:82:in `set_options_from_identifier'
omniauth-mastodon (0.9.3) lib/omniauth/strategies/mastodon.rb:41:in `callback_phase'
...
```

というエラーが出てしまったんです。

いろいろ調べたりしたのですがスキル不足でどうにもならず、無理やり下記のような対策をしました。

具体的には、今回のケースではRailsのcontrollerで起きた問題ではなくgemの[omniauth-mastodon](https://github.com/tootsuite/omniauth-mastodon)つまりRack middlewareで発生した例外であるため `exceptions_app` というものを利用しました。

```ruby:/config/initializers/exceptions_app.rb
Rails.application.configure do
  config.exceptions_app = self.routes
end
```

```ruby:/app/controllers/errors_controller.rb
class ErrorsController < ActionController::Base
  def not_found
    redirect_to root_path
  end
end
```

```ruby:/config/routes.rb
  get '500', to: 'errors#not_found'
```

# あとがき

最後の例外処理があまりにも無理やりな感じがして、なんともしまらないメモになってしまいました。まだまだ未熟なのでこれからも学びつづけながらアウトプットしていきます。

### 余談ですが

Mastodonインスタンスの管理ページにある「サイト設定」で「新規登録を受け付ける」にチェックが入っていないと下記のエラーが発生してアカウント作成APIを受け付けません。

```
Mastodon::Error::Forbidden (Mastodon::Error::Forbidden):
```

これに気づくだけでも時間を要してしまいました。

けれど自作のWebアプリからの新規アカウント作成プロセスを簡略化したい！という目的はある程度まで達成できたと思います。TwitterやFacebookとかと違って(ないですよね？)アカウント作成APIまで用意しているMastodon。今後も期待しつつそのほかのAPIにも機会があったらまた挑戦したいです。