File: oauth_web.psgi

package info (click to toggle)
libtwitter-api-perl 1.0006-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 424 kB
  • sloc: perl: 2,868; makefile: 7
file content (169 lines) | stat: -rw-r--r-- 5,108 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
package WebApp;
# Simple web app example using Plack. To run it:
# plackup examples/oauth_web.psgi

use 5.14.1;
use Moo;
use Encode qw/encode_utf8/;
use HTML::Escape qw/escape_html/;
use Plack::Request;
use Plack::Response;
use Twitter::API;

has twitter_client => (
    is => 'ro',
    default => sub {
        Twitter::API->new_with_traits(
            traits => 'Enchilada',
            # Net::Twitter example app credentials:
             map(tr/A-Za-z/N-ZA-Mn-za-m/r, qw/
                 pbafhzre_xrl i8g3WVYxFglyotakTYBD
                 pbafhzre_frperg 5e31eFZp0ACgOcUpX8ZiaPYt2bNlSYk5rTBZxKZ
            /),
            # To use your own app credentials:
            # consumer_key    => 'your-app-key',
            # consumer_secret => 'your-app-secret',
        );
    },
);

# In a production application, use something like Redis to store request token
# secrets. Twitter expires request tokens after 15 minutes. Your app should
# keep them just a bit longer to ensure you don't discard them before Twitter
# does. Maybe 20 minutes. In this simple demo, we won't worry about expiration.
has secret_cache => (
    is => 'ro',
    default => sub { {} },
);

# We only need it once, so remove it from the cache
sub get_secret { delete $_[0]->secret_cache->{$_[1]} }
sub set_secret { $_[0]->secret_cache->{$_[1]} = $_[2] }

sub uri_for {
    my ( $self, $req, $path ) = @_;

    my $uri = $req->base;
    $uri->path($uri->path . $path);
    return $uri;
}

my %route = (
    '/'               => 'home_page',
    '/oauth_callback' => 'oauth_callback',
);

sub dispatch {
    my ( $self, $req, $res ) = @_;

    my $method = $route{$req->path} // 'not_found';
    $self->$method($req, $res);
}

sub handle_request {
    my ( $self, $env ) = @_;

    my $req = Plack::Request->new($env);
    my $res = Plack::Response->new(200);
    $res->content_type('text/html; charset=utf-8');

    $self->dispatch($req, $res);
    $res->finalize;
}

sub to_app {
    my $self = shift->new;
    sub { $self->handle_request(@_) };
};

sub home_page {
    my ( $self, $req, $res ) = @_;

    my $client = $self->twitter_client;

    # If we have access token/secret, display verify_credentials response
    if ( my $credentials = $req->cookies->{'dont-do-this-at-home'} ) {
        my ( $token, $secret ) = split /\s+/, $credentials;
        my $r = $client->verify_credentials({
            -token        => $token,
            -token_secret => $secret,
        });
        my $body = escape_html(
            $client->json_parser->pretty(1)->encode($r)
        );
        $res->body("<pre>$body</pre>");
    }
    else {
        # Otherwise, prompt the user to authenticate
        my $r = $client->oauth_request_token({
            callback => $self->uri_for($req, 'oauth_callback'),
        });
        my ( $token, $secret ) = @{$r}{qw/oauth_token oauth_token_secret/};

        # Save the request token and secret; we'll need them later.
        $self->set_secret($token, $secret);

        my $url = $client->oauth_authentication_url({
            oauth_token => $token,
        });
        $res->body(qq{<a href="$url">Authenticate with Twitter</a>});
    }
}

sub oauth_callback {
    my ( $self, $req, $res ) = @_;

    my $token    = $req->param('oauth_token');
    my $verifier = $req->param('oauth_verifier');

    if ( $token && $verifier ) {
        # The user authenticated!

        # Get our cached request token secret
        my $secret = $self->get_secret($token) // die 'missing secret';
        my $r = $self->twitter_client->oauth_access_token({
            token        => $token,
            token_secret => $secret,
            verifier     => $verifier,
        });

        # DON'T DO THIS AT HOME!
        #
        # In a production app, you will store the access_token and
        # access_token_secret in a database. They can be used to make Twitter
        # API calls on behalf of the authenticated user. Ideally, you should
        # treat them like you would user names and passwords. Encrypt them.
        #
        # For our simple demo, since we don't have a permanent data store,
        # we'll store them in a session cookie.
        $res->cookies->{'dont-do-this-at-home'} = join ' ',
            $$r{oauth_token}, $$r{oauth_token_secret};

        $res->redirect($self->uri_for($req, ''));
        return;
    }

    my $home = $self->uri_for($req, '');
    if ( $token = $req->param('denied') ) {
        # The user canceled the authentication request and select "return to
        # the application".
        $self->get_secret($token); # discard; no longer valid or useful
        $res->body(qq{You denied us access. <a href="$home">Go home</a>});
        return;
    }

    # /oauth_callback was requested without the expected parameters; let's just
    # redirect to the root page
    $res->redirect($home);
}

sub not_found {
    my ( $self, $req, $res ) = @_;

    my $home = $self->uri_for($req, '');
    $res->status(404);
    $res->body($req->path_info
        . qq{ does not live here, try <a href="$home">the main page</a>});
}

my $app = __PACKAGE__->to_app;