のらねこの気まま暮らし

技術系だけど、Qiita向きではないポエムとかを書くめったに更新されないヤツ

Amon2::Plugin::Web::Auth::PathというPluginを書いた

ソースはこちら。トルネで録画したJOJOの映像が乱れまくって辛いです。
https://github.com/rymizuki/p5-Amon2-Web-Auth-Path

頑張って短くした前置き

Amon2にはWeb::Authという認証をサポートするプラグインがあります。が、それらは外部の認証自身のアプリケーションで使うためのツールであり、アプリケーションのページ単位での認証をおこなってくれません。
例えば、トップページは認証をかけない、マイページはかける。お問い合わせページはかけない、という細かい制御が難しい。
BEFORE_DISPATCHでPATH毎、あるいはController毎に管理するとか、Routerのany使うとかやりようはありますが、アプリの規模が膨らめば膨らむ程コードも膨らむ。可読性も下がるしあまりよろしくない。


Amon2::Plugin::Web::Auth::PathはPATH単位でBEFORE_DISPATCHを走らせて、認証の処理をContextから分離したものです。Pluginのinit時にpathsをArrayRefで渡すか、moduleを渡すかの選択ができます。
少ない認証だけでしたら、pathsでも十分なのですが、僕の要求ではWebContextの行数が倍に膨れ上がる複雑な処理だったので、出来ればそこは切り出したい、と思って外部Moduleを呼べるようにしました。ついでにDSLなんかをつくってみました。

そんなわけで、
Router::Simpleでの実装は、@takuji31さんのAmon2::Web::Authorizerを参考にさせて頂きました。

https://github.com/takuji31/Amon2-Web-Authorizer


処理の中で、認証が通ったか通ってないかの判断を明示的にするために$auth->success,$auth->failedのメソッドを呼ぶといいんじゃない?とid:karupaneruraにアドバイスいただいてまんま実装しました。

つかいかた

package YourApp::Web;
use Amon2::Lite;

get '/' => sub {
    my $c = shift;
};

get '/mypage' => sub {
    my $c = shift;
};

__PACKAGE__->load_plugins(
    'Web::Auth' => +{
        module => 'Twitter',
        ...
    },
    'Web::Auth::Path' => sub {
        paths => [
            '/'          => sub { $_[1]->success },
            qr{^/auth}   => sub { $_[1]->success },
            qr{^/mypage} => sub {
                my ($c, $auth,) = @_;
                if ($c->session->get('is_login')) {
                    $auth->success;
                } else {
                    $auth->failed;
                    # redirect to Auth::Site::Twitter
                    return $c->redirect('/auth/twitter/authenticate');
                },
            },
        ],
    },
);

1;


callbackにはWebContext, Plack::Util::initline_objectのインスタンス, Router::Simpleのmatchが渡されます。
callbackの中では$auth->succes/$auth->failedを呼びましょう。呼ばないと死にます。Appが。
callbackの返り値があればそのままreturnするので(Plack::Responseだけに固定したほうが良いのだろうか)$c->redirect, $c->render等ご自由に。after_dispatchにも影響します。controllerが走らないだけなの。

Router::Simpleつかってるのでパスの指定はRouter::Simpleのままつかえます。

モジュールを使う場合

こんなモジュールを書いて

package MyApp::Auth::Path::App;
use Amon2::Auth::Path::Declare;

path '/' => sub { $_[1]->success };

1;

WebContextではこんな感じでパラメータ渡してやる。

__PACKAGE__->load_plugins(
    'Web::Auth::Path', {
        module => 'MyApp::Auth::Path::App',
    },
);

以下はpathsと同様。

デバッグでしか使ってないけどとりあえず残した

initのoptionにon_success,on_errorのcallbackが渡せます。
$auth->success/failedの結果を受け取って、responseの返却をするまえに走らせるHookです。




とまあ、こんなかんじで。
とりあえず簡単なテストは追加したので、ひとまずはこれでよいかなーと。

さて、怠惰な休日を謳歌しましょう。