Mojolicious-Plugin-Fondation-Auth-Token

 view release on metacpan or  search on metacpan

lib/Mojolicious/Plugin/Fondation/Auth/Token.pm  view on Meta::CPAN

package Mojolicious::Plugin::Fondation::Auth::Token;
$Mojolicious::Plugin::Fondation::Auth::Token::VERSION = '0.03';
# ABSTRACT: Personal Access Token authentication for Fondation

use Mojo::Base 'Mojolicious::Plugin', -signatures;
use Digest::SHA qw(sha256_hex);
use Crypt::URandom qw(urandom);
use Future;

sub fondation_meta {
    return {
        dependencies => [
            'Fondation::Model::DBIx::Async',
            'Fondation::Auth',
            'Fondation::Problem',
        ],
        defaults => {
            models => {
                api_token => {
                    source  => 'ApiToken',
                    backend => undef,
                },
            },
        },
    };
}

sub register ($self, $app, $config) {

    # ── around_dispatch: validate Bearer, set current_user, flag stash ──

    $app->hook(around_dispatch => sub ($next, $c) {
        my $token = _extract_bearer($c) or return $next->();

        my $hash = sha256_hex($token);

        $c->model('api_token')->search(
            { token_hash => $hash },
            { rows => 1 },
        )->first->then(sub ($api_token) {
            unless ($api_token) {
                $c->stash('fondation.bearer_invalid' => 1);
                return $next->();
            }

            my $user_id = $api_token->user_id;

            return $c->model('user')->find($user_id)->then(sub ($user) {
                unless ($user) {
                    $app->log->error(
                        "[Auth::Token] User $user_id not found for valid token");
                    $c->stash('fondation.bearer_invalid' => 1);
                    return $next->();
                }
                $c->current_user({ $user->get_columns });
                $c->stash('fondation._bearer_auth' => 1);

                return $api_token->update(
                    { last_used_at => \'datetime(\'now\')' }
                )->then(sub {
                    _load_grants($c);
                })->then(sub { $next->() });
            });
        })->on_fail(sub ($err) {
            $app->log->error("[Auth::Token] Token lookup failed: $err");
            $c->stash('fondation.bearer_invalid' => 1);
            $next->();
        })->retain;
    });

    # ── around_action: enforce Bearer opt-in ──────────────────────────
    # ── Route is matched, check endpoint requires ────────────────────

    $app->hook(around_action => sub ($next, $c, $action, $captures) {
        return $next->() unless $c->stash('fondation._bearer_auth');

        my $endpoint = $c->match->endpoint;
        my $allows   = 0;
        if ($endpoint) {
            my $requires = $endpoint->{requires} // [];
            my %conds;
            for (my $i = 0; $i < @$requires; $i += 2) {
                $conds{$requires->[$i]} = $requires->[$i+1] // 1;
            }
            $allows = 1 if $conds{'fondation.bearer'};
        }

        unless ($allows) {
            delete $c->stash->{'current_user'};
            $c->problem(



( run in 0.307 second using v1.01-cache-2.11-cpan-ad19def0cd9 )