Created
June 16, 2016 17:11
-
-
Save s1037989/066324f5efe2eb83141f332d1ce3d054 to your computer and use it in GitHub Desktop.
Ama OAuth
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package TimeClock::Model::OAuth2; | |
use Mojo::Base -base; | |
use UUID::Tiny ':std'; | |
has 'pg'; | |
sub store { | |
my $self = shift; | |
if ( $#_ == 1 ) { | |
#warn "Store Form #1"; | |
my ($id, $provider) = @_; | |
my $r = $self->pg->db->query('select id from providers where id = ? and provider = ?', $id, $provider)->hash; | |
return ref $r ? $r->{id} : undef; | |
} elsif ( $#_ == 0 ) { | |
#warn "Store Form #2"; | |
my ($provider_id) = @_; | |
my $r = $self->pg->db->query('select id from providers where provider_id = ?', $provider_id)->hash; | |
return ref $r ? $r->{id} : uuid_to_string(create_uuid(UUID_V4)); | |
} elsif ( $#_ > 1 ) { | |
#warn "Store Form #3"; | |
my ($id, $provider, $json, $mapped) = @_; | |
unless ( $self->pg->db->query('select id from users where id = ?', $id)->rows ) { | |
$self->pg->db->query('insert into users (id, email, first_name, last_name) values (?, ?, ?, ?)', $id, $mapped->{email}, $mapped->{first_name}, $mapped->{last_name}); | |
} | |
unless ( $self->pg->db->query('select id from providers where provider_id = ?', $mapped->{id})->rows ) { | |
$self->pg->db->query('insert into providers (id, provider_id, provider) values (?, ?, ?)', $id, $mapped->{id}, $provider); | |
} | |
} else { | |
#warn "Store Form Unknown"; | |
} | |
} | |
sub find { shift->pg->db->query('select * from users where id = ?', shift)->hash } | |
1; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package Mojolicious::Plugin::OAuth2Accounts; | |
use Mojo::Base 'Mojolicious::Plugin'; | |
our $VERSION = '0.01'; | |
has providers => sub { | |
return { | |
mocked => { | |
args => { | |
scope => 'user_about_me email', | |
}, | |
fetch_user_url => '/mocked/me?access_token=%token%', | |
map => { | |
error => '/err/0', | |
id => '/i', | |
email => '/e', | |
first_name => '/f', | |
last_name => '/l', | |
}, | |
}, | |
facebook => { | |
args => { | |
scope => 'user_about_me email', | |
}, | |
fetch_user_url => 'https://graph.facebook.com/v2.6/me?access_token=%token%', | |
map => { | |
error => '/error/message', | |
id => '/id', | |
email => '/email', | |
name => '/name', | |
first_name => '/first_name', | |
last_name => '/last_name', | |
}, | |
}, | |
google => { | |
args => { | |
scope => 'profile email' , | |
}, | |
fetch_user_url => 'https://www.googleapis.com/plusDomains/v1/people/me?access_token=%token%', | |
map => { | |
error => '/error/message', | |
id => '/id', | |
email => '/emails/value', | |
first_name => '/name/givenName', | |
last_name => '/name/familyName', | |
}, | |
}, | |
} | |
}; | |
sub register { | |
my ($self, $app, $config) = @_; | |
my $oauth2_config = {}; | |
my $providers = $self->providers; | |
foreach my $provider (keys %{$config->{providers}}) { | |
if (exists $providers->{$provider}) { | |
foreach my $key (keys %{$config->{providers}->{$provider}}) { | |
$providers->{$provider}->{$key} = $config->{providers}->{$provider}->{$key}; | |
} | |
} | |
else { | |
$providers->{$provider} = $config->{providers}->{$provider}; | |
} | |
} | |
$self->providers($providers); | |
$app->plugin("OAuth2" => { fix_get_token => 1, %{$config->{providers}} }); | |
$app->routes->get('/logout' => sub { | |
my $c = shift; | |
my $token = $c->session('token') || {}; | |
delete $c->session->{$_} foreach keys %{$c->session}; | |
$token->{$_} = {} foreach keys %$token; | |
$c->session(token => $token); | |
$c->redirect_to($config->{on_logout}); | |
}); | |
$app->routes->get('/login/:provider' => {provider => ''} => sub { | |
my $c = shift; | |
return $c->render($c->session('id') ? 'logout' : 'login') unless $c->param('provider'); | |
return $c->redirect_to('connectprovider', {provider => $c->param('provider')}) unless $c->session('id'); | |
$c->redirect_to($config->{on_success}); | |
})->name('login'); | |
$app->routes->get("/mocked/me" => sub { | |
my $c = shift; | |
my $access_token = $c->param('access_token'); | |
return $c->render(json => {err => ['Invalid access token']}) unless $access_token eq 'fake_token'; | |
$c->render(json => { i => 123, f => 'a', l => 'a', e => '[email protected]' }); | |
}); | |
$app->routes->get("/connect/:provider" => sub { | |
my $c = shift; | |
$c->session('token' => {}) unless $c->session('token'); | |
my $provider = $c->param('provider'); | |
my $token = $c->session('token'); | |
my ($success, $error, $connect) = ($config->{on_success}, $config->{on_error}, $config->{on_connect}); | |
my ($args, $fetch_user_url, $map) = ($self->providers->{$provider}->{args}, $self->providers->{$provider}->{fetch_user_url}, {%{$self->providers->{$provider}->{map}}}); | |
$c->delay( | |
sub { | |
my $delay = shift; | |
# Only get the token from $provider if the current one isn't expired | |
if ( $token->{$provider} && $token->{$provider}->{access_token} && $token->{$provider}->{expires_at} && time < $token->{$provider}->{expires_at} ) { | |
my $cb = $delay->begin; | |
$c->$cb(undef, $token->{$provider}); | |
} else { | |
my $args = {redirect_uri => $c->url_for('connectprovider', {provider => $provider})->userinfo(undef)->to_abs, %$args}; | |
$args->{redirect_uri} =~ s/^http/https/; | |
$c->oauth2->get_token($provider => $args, $delay->begin); | |
} | |
}, | |
sub { | |
my ($delay, $err, $data) = @_; | |
# If already connected to $provider, no reason to go through this again | |
# All this does is pull down basic info / email and store locally | |
return $c->redirect_to($success) if $connect->($c, $c->session('id'), $provider); # on_connect Form #1 | |
unless ( $data->{access_token} ) { | |
$c->flash(error => "Could not obtain access token: $err"); | |
return $c->redirect_to($error); | |
} | |
$token->{$provider} = $data; | |
$token->{$provider}->{expires_at} = time + ($token->{$provider}->{expires_in}||3600); | |
$c->session(token => $token); | |
$c->ua->get($self->_fetch_user_url($fetch_user_url, $token->{$provider}->{access_token}), sub { | |
my ($ua, $tx) = @_; | |
return $c->reply->exception("No JSON response") unless $tx->res->json; | |
my $json = Mojo::JSON::Pointer->new($tx->res->json); | |
if ( my $error_message = $json->get(delete $map->{error}) ) { | |
$c->flash(error => $error_message); | |
return $c->redirect_to($error); | |
} | |
$c->session(id => $connect->($c, $json->get($map->{id}))) unless $c->session('id'); # on_connect Form #2 | |
$connect->($c, $c->session('id'), $provider, $tx->res->json, {map { $_ => $json->get($map->{$_}) } keys %$map}); # on_connect Form #3 | |
$c->redirect_to($success); | |
}); | |
}, | |
); | |
}); | |
} | |
sub _fetch_user_url { | |
my ($self, $fetch_user_url, $token) = @_; | |
$fetch_user_url =~ s/%token%/$token/g; | |
return $fetch_user_url; | |
} | |
1; | |
__END__ | |
=encoding utf8 | |
=head1 NAME | |
Mojolicious::Plugin::OAuth2Accounts - Mojolicious Plugin | |
=head1 SYNOPSIS | |
# Mojolicious | |
$self->plugin('OAuth2Accounts'); | |
# Mojolicious::Lite | |
plugin 'OAuth2Accounts'; | |
=head1 DESCRIPTION | |
L<Mojolicious::Plugin::OAuth2Accounts> is a L<Mojolicious> plugin. | |
=head1 METHODS | |
L<Mojolicious::Plugin::OAuth2Accounts> inherits all methods from | |
L<Mojolicious::Plugin> and implements the following new ones. | |
=head2 register | |
$plugin->register(Mojolicious->new); | |
Register plugin in L<Mojolicious> application. | |
=head1 SEE ALSO | |
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>. | |
=cut |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use Mojolicious::Lite; | |
#use lib '/var/mojo/lib/Mojolicious-Plugin-OAuth2Accounts/lib'; | |
use lib 'lib'; | |
use Mojo::Pg; | |
use TimeClock::Model::OAuth2; | |
use TimeClock::Model::Timeclock; | |
my $config = plugin 'Config'; | |
helper 'pg' => sub { state $pg = Mojo::Pg->new(shift->config('pg')) }; | |
helper 'model.oauth2' => sub { state $users = TimeClock::Model::OAuth2->new(pg => shift->pg) }; | |
helper 'model.timeclock' => sub { state $timeclock = TimeClock::Model::Timeclock->new(pg => shift->pg) }; | |
app->sessions->default_expiration(86400*365*10); | |
app->pg->migrations->from_data->migrate; | |
plugin "OAuth2Accounts" => { | |
on_logout => '/', | |
on_success => 'timeclock', | |
on_error => 'login', | |
on_connect => sub { shift->model->oauth2->store(@_) }, | |
providers => $config->{oauth2}, | |
}; | |
get '/' => sub { | |
my $c = shift; | |
return $c->redirect_to('login', {provider => ''}) unless $c->session('id'); | |
$c->stash(user => $c->model->oauth2->find($c->session('id'))); | |
$c->stash(timeclock => $c->model->timeclock); | |
} => 'timeclock'; | |
post '/' => sub { | |
my $c = shift; | |
return $c->reply->not_found unless $c->session('id'); | |
if ( $c->param('clock') eq 'in' ) { | |
$c->model->timeclock->clock_in($c->session('id'), $c->param('lat'), $c->param('lon')); | |
} elsif ( $c->param('clock') eq 'out' ) { | |
$c->model->timeclock->clock_out($c->session('id'), $c->param('lat'), $c->param('lon')); | |
} | |
$c->redirect_to('timeclock'); | |
} => 'timeclock'; | |
get '/status' => sub { | |
my $c = shift; | |
return $c->redirect_to('timeclock') unless $c->session('id') && $c->model->oauth2->find($c->session('id'))->{admin}; | |
$c->stash(timeclock => $c->model->timeclock); | |
}; | |
get '/history/:user' => sub { | |
my $c = shift; | |
return $c->redirect_to('timeclock') unless $c->session('id'); | |
return $c->reply->not_found unless $c->param('user') eq $c->session('id') || $c->model->oauth2->find($c->session('id'))->{admin}; | |
$c->stash(user => $c->param('user')); | |
$c->stash(timeclock => $c->model->timeclock); | |
}; | |
get '/pay/:user/:ids' => sub { | |
my $c = shift; | |
return $c->redirect_to('timeclock') unless $c->session('id') && $c->model->oauth2->find($c->session('id'))->{admin}; | |
$c->model->timeclock->pay(split /,/, $c->param('ids')); | |
$c->stash(user => $c->param('user')); | |
$c->stash(timeclock => $c->model->timeclock); | |
$c->redirect_to('historyuser', {user => $c->param('user')}); | |
} => 'payuser'; | |
app->start; | |
__DATA__ | |
@@ layouts/default.html.ep | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<style> | |
* { font-size: 48px } | |
</style> | |
<title><%= title %></title> | |
</head> | |
<body><%= content %></body> | |
</html> | |
@@ historyuser.html.ep | |
% layout 'default'; | |
% title 'User History'; | |
% my $week; | |
<%= link_to "All user status" => 'status' %><hr /> | |
% my $user_tc = $timeclock->user($user); | |
<%= "$user_tc->{first_name} $user_tc->{last_name}" %><br /> | |
<table> | |
<tr><th>week</th><th>Sunday</th><th>Monday</th><th>Tuesday</th><th>Wednesday</th><th>Thursday</th><th>Friday</th><th>Saturday</th><th>Total</th></tr> | |
% my $history = $timeclock->history($user); | |
% foreach my $week ( sort { $b cmp $a } keys %$history ) { | |
<tr> | |
<td><%= $week %> | |
% my $week_time; | |
% my $unpaid; | |
% my @unpaid; | |
% foreach my $dow ( qw/0 1 2 3 4 5 6/ ) { | |
<td> | |
% my $day_time; | |
% foreach my $e ( @{$history->{$week}->{$dow}} ) { | |
% $day_time = $day_time ? $day_time->add_duration($e->{duration}->clone) : $e->{duration}->clone; | |
% $week_time = $week_time ? $week_time->add_duration($e->{duration}->clone) : $e->{duration}->clone; | |
% $unpaid = $unpaid ? $unpaid->add_duration($e->{duration}->clone) : $e->{duration}->clone unless $e->{paid}; | |
% push @unpaid, $e->{id} if !$e->{paid}; | |
<%= link_to($e->{time_in}->hms => "https://www.google.com/maps/place//\@$e->{time_in_lat},$e->{time_in_lon},17z/data=!3m1!4b1!4m2!3m1!1s0x0:0x0") %> - <%= $e->{time_out} ? link_to($e->{time_out}->hms => "https://www.google.com/maps/place//\@$e->{time_in_lat},$e->{time_in_lon},17z/data=!3m1!4b1!4m2!3m1!1s0x0:0x0") : 'Active' %> (<%= $e->{paid} ? $timeclock->duration($e->{duration}) : link_to $timeclock->duration($e->{duration}) => 'payuser', {user => $user, ids => $e->{id}} %>)<br /> | |
% } | |
% if ( $day_time ) { | |
<b><%= $timeclock->duration($day_time) %></b> | |
% } | |
</td> | |
% } | |
<td> | |
% if ( $unpaid ) { | |
<b><%= link_to $timeclock->duration($unpaid, 1) => 'payuser', {user => $user, ids => join(',', @unpaid)} %></b><br /> | |
% } | |
% if ( $week_time ) { | |
<b><%= $timeclock->duration($week_time, 1) %></b><br /> | |
% } | |
</td> | |
</tr> | |
% } | |
</table> | |
@@ login.html.ep | |
<%= link_to 'Login with facebook' => 'login', {provider => 'facebook'} %><br /> | |
<%= link_to 'Login with google' => 'login', {provider => 'google'} %><br /> | |
@@ logout.html.ep | |
<%= link_to 'Logout' => 'logout' %><br /> | |
@@ status.html.ep | |
% layout 'default'; | |
% title 'All users'; | |
<%= link_to 'My timeclock' => 'timeclock' %><hr /> | |
% foreach my $user ( @{$timeclock->users} ) { | |
% my $status = $timeclock->status($user->{id}); | |
% my $user_tc = $timeclock->user($user->{id}); | |
Name: <%= link_to "$user_tc->{first_name} $user_tc->{last_name}" => 'historyuser', {user => $user->{id}} %><br /> | |
Status: <%== $status ? "Active since ".link_to($status->{time_in}->datetime => "https://www.google.com/maps/place//\@$status->{time_in_lat},$status->{time_in_lon},17z/data=!3m1!4b1!4m2!3m1!1s0x0:0x0")." (".$timeclock->duration($status->{duration}).")" : 'Not active' %><hr /> | |
% } | |
@@ timeclock.html.ep | |
% layout 'default'; | |
% title 'My Timeclock'; | |
<script src="http://maps.googleapis.com/maps/api/js?key=AIzaSyDKw1I9ZlI-piCBp2zXSuviBDVRjju-aYI&sensor=true&libraries=adsense"></script> | |
<script src="http://ctrlq.org/common/js/jquery.min.js"></script> | |
<script> | |
if ( "geolocation" in navigator ) { | |
navigator.geolocation.getCurrentPosition(function(position){ | |
var lat = position.coords.latitude.toFixed(5); | |
var lon = position.coords.longitude.toFixed(5); | |
console.log(lat, lon); | |
$('#lat').attr('value', lat); | |
$('#lon').attr('value', lon); | |
$('#div_lat').text(lat); | |
$('#div_lon').text(lon); | |
}, null, {enableHighAccuracy: true, timeout: 5000, maximumAge: 1000}); | |
} | |
</script> | |
% if ( $user->{admin} ) { | |
<%= link_to Admin => 'status' %><hr /> | |
% } | |
Name: <%= link_to $user->{id} => 'historyuser', {user => $user->{id}} %><br /> | |
<%= dumper $user %> | |
%= form_for timeclock => (method => 'POST') => begin | |
%= hidden_field 'lat' => '', id => 'lat' | |
%= hidden_field 'lon' => '', id => 'lon' | |
% if ( my $status = $timeclock->status(session 'id') ) { | |
<%= $status->{time_in} %> (<%= $timeclock->duration($status->{duration}) %>)<br /> | |
%= hidden_field clock => 'out' | |
%= submit_button 'Clock out' | |
% } else { | |
Not active <br /> | |
%= hidden_field clock => 'in' | |
%= submit_button 'Clock in' | |
% } | |
% end | |
@@ migrations | |
-- 1 up | |
create table if not exists users ( | |
id text primary key, | |
email text, | |
first_name text, | |
last_name text, | |
admin integer, | |
created timestamptz not null default now() | |
); | |
create table if not exists providers ( | |
id text, | |
provider_id text, | |
provider text, | |
created timestamptz not null default now(), | |
PRIMARY KEY (id, provider_id, provider) | |
); | |
create table if not exists timeclock ( | |
id serial primary key, | |
user_id text, | |
time_in timestamptz, | |
time_out timestamptz, | |
time_in_lat text, | |
time_in_lon text, | |
time_out_lat text, | |
time_out_lon text, | |
paid integer | |
); | |
CREATE OR REPLACE FUNCTION round_duration(TIMESTAMPTZ, TIMESTAMPTZ) | |
RETURNS INTERVAL AS $$ | |
SELECT date_trunc('hour', age(case when $2 is not null then $2 else now() end, $1)) + INTERVAL '15 min' * ROUND(date_part('minute', age(case when $2 is not null then $2 else now() end, $1)) / 15.0) | |
$$ LANGUAGE SQL; | |
-- 1 down | |
drop table if exists providers; | |
drop table if exists users; | |
drop table if exists timeclock; | |
-- 2 up | |
alter table users add column disable timestamptz; | |
-- 2 down | |
alter table users drop column disable; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment