mirror of
https://github.com/derf/travelynx
synced 2024-11-13 00:07:09 +00:00
Traewelling: replace legacy password login with OAuth2
This is a breaking change insofar as that traewelling support is no longer provided automatically, but must be enabled by providing a traewelling.de application ID and secret in travelynx.conf. However, as traewelling.de password login is deprecated and wil soon be disabled, travelynx would break either way. So we might or might not see travelynx 2.0.0 in the next days. Automatic token refresh is still todo, but that was the case for password login as well. Closes #64
This commit is contained in:
parent
22627ce851
commit
52c0da3f46
10 changed files with 180 additions and 171 deletions
1
cpanfile
1
cpanfile
|
@ -10,6 +10,7 @@ requires 'List::UtilsBy';
|
||||||
requires 'MIME::Entity';
|
requires 'MIME::Entity';
|
||||||
requires 'Mojolicious';
|
requires 'Mojolicious';
|
||||||
requires 'Mojolicious::Plugin::Authentication';
|
requires 'Mojolicious::Plugin::Authentication';
|
||||||
|
requires 'Mojolicious::Plugin::OAuth2';
|
||||||
requires 'Mojo::Pg';
|
requires 'Mojo::Pg';
|
||||||
requires 'Text::CSV';
|
requires 'Text::CSV';
|
||||||
requires 'Text::Markdown';
|
requires 'Text::Markdown';
|
||||||
|
|
|
@ -1915,6 +1915,15 @@ DISTRIBUTIONS
|
||||||
ExtUtils::MakeMaker 0
|
ExtUtils::MakeMaker 0
|
||||||
Mojolicious 8.0
|
Mojolicious 8.0
|
||||||
perl 5.016
|
perl 5.016
|
||||||
|
Mojolicious-Plugin-OAuth2-2.02
|
||||||
|
pathname: J/JH/JHTHORSEN/Mojolicious-Plugin-OAuth2-2.02.tar.gz
|
||||||
|
provides:
|
||||||
|
Mojolicious::Plugin::OAuth2 2.02
|
||||||
|
Mojolicious::Plugin::OAuth2::Mock undef
|
||||||
|
requirements:
|
||||||
|
ExtUtils::MakeMaker 0
|
||||||
|
IO::Socket::SSL 1.94
|
||||||
|
Mojolicious 8.25
|
||||||
Moo-2.005005
|
Moo-2.005005
|
||||||
pathname: H/HA/HAARG/Moo-2.005005.tar.gz
|
pathname: H/HA/HAARG/Moo-2.005005.tar.gz
|
||||||
provides:
|
provides:
|
||||||
|
|
|
@ -97,7 +97,31 @@
|
||||||
die("Changeme!"),
|
die("Changeme!"),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
# optionally, users can link travelynx and traewelling accounts, and
|
||||||
|
# automatically synchronize check-ins.
|
||||||
|
# To do so, you need to create a travelynx application on
|
||||||
|
# <https://traewelling.de/settings/applications>. The application
|
||||||
|
# must be marked as "Confidential" and have a redirect URL that matches
|
||||||
|
# $base_url/oauth/traewelling, where $base_url refers to the URL configured
|
||||||
|
# above. For instance, travelynx.de uses
|
||||||
|
# 'https://travelynx.de/oauth/traewelling'. An incorrect redirect URL will
|
||||||
|
# cause OAuth2 to fail with unsupported_grant_type.
|
||||||
|
#
|
||||||
|
# Note that the travelynx/traewelling OAuth2 integration does not support
|
||||||
|
# travelynx installations that are reachable on multiple URLs at the
|
||||||
|
# moment -- linking a traewelling account is only possible when accessing
|
||||||
|
# travelynx via the base URL.
|
||||||
traewelling => {
|
traewelling => {
|
||||||
|
|
||||||
|
# Uncomment the following block and insert the application ID and
|
||||||
|
# secret obtained from https://traewelling.de/settings/applications
|
||||||
|
# -> your application -> Edit.
|
||||||
|
|
||||||
|
#oauth => {
|
||||||
|
# id => 1234,
|
||||||
|
# secret => 'mysecret',
|
||||||
|
#}
|
||||||
|
|
||||||
# By default, the "work" or "worker" command does not just update
|
# By default, the "work" or "worker" command does not just update
|
||||||
# real-time data of active journeys, but also performs push and pull
|
# real-time data of active journeys, but also performs push and pull
|
||||||
# synchronization with traewelling for accounts that have configured it.
|
# synchronization with traewelling for accounts that have configured it.
|
||||||
|
@ -110,7 +134,8 @@
|
||||||
# periodically runs "perl index.pl traewelling" (push and pull) or
|
# periodically runs "perl index.pl traewelling" (push and pull) or
|
||||||
# two separate cronjobs that run "perl index.pl traewelling push" and
|
# two separate cronjobs that run "perl index.pl traewelling push" and
|
||||||
# "perl index.pl traewelling pull", respectively.
|
# "perl index.pl traewelling pull", respectively.
|
||||||
## separate_worker => 1,
|
|
||||||
|
# separate_worker => 1,
|
||||||
},
|
},
|
||||||
|
|
||||||
version => qx{git describe --dirty} // 'experimental',
|
version => qx{git describe --dirty} // 'experimental',
|
||||||
|
|
|
@ -100,6 +100,23 @@ sub startup {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if ( my $oa = $self->config->{traewelling}{oauth} ) {
|
||||||
|
$self->plugin(
|
||||||
|
OAuth2 => {
|
||||||
|
providers => {
|
||||||
|
traewelling => {
|
||||||
|
key => $oa->{id},
|
||||||
|
secret => $oa->{secret},
|
||||||
|
authorize_url =>
|
||||||
|
'https://traewelling.de/oauth/authorize?response_type=code',
|
||||||
|
token_url => 'https://traewelling.de/oauth/token',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
$self->sessions->default_expiration( 60 * 60 * 24 * 180 );
|
$self->sessions->default_expiration( 60 * 60 * 24 * 180 );
|
||||||
|
|
||||||
# Starting with v8.11, Mojolicious sends SameSite=Lax Cookies by default.
|
# Starting with v8.11, Mojolicious sends SameSite=Lax Cookies by default.
|
||||||
|
@ -2140,6 +2157,11 @@ sub startup {
|
||||||
$r->post('/login')->to('account#do_login');
|
$r->post('/login')->to('account#do_login');
|
||||||
$r->post('/recover')->to('account#request_password_reset');
|
$r->post('/recover')->to('account#request_password_reset');
|
||||||
|
|
||||||
|
if ( $self->config->{traewelling}{oauth} ) {
|
||||||
|
$r->get('/oauth/traewelling')->to('traewelling#oauth');
|
||||||
|
$r->post('/oauth/traewelling')->to('traewelling#oauth');
|
||||||
|
}
|
||||||
|
|
||||||
if ( not $self->config->{registration}{disabled} ) {
|
if ( not $self->config->{registration}{disabled} ) {
|
||||||
$r->get('/register')->to('account#registration_form');
|
$r->get('/register')->to('account#registration_form');
|
||||||
$r->post('/register')->to('account#register');
|
$r->post('/register')->to('account#register');
|
||||||
|
|
|
@ -1815,6 +1815,25 @@ my @migrations = (
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
# v45 -> v46
|
||||||
|
# Switch to Traewelling OAuth2 authentication.
|
||||||
|
# E-Mail is no longer needed.
|
||||||
|
sub {
|
||||||
|
my ($db) = @_;
|
||||||
|
$db->query(
|
||||||
|
qq{
|
||||||
|
drop view traewelling_str;
|
||||||
|
create view traewelling_str as select
|
||||||
|
user_id, push_sync, pull_sync, errored, token, data,
|
||||||
|
extract(epoch from latest_run) as latest_run_ts
|
||||||
|
from traewelling
|
||||||
|
;
|
||||||
|
alter table traewelling drop column email;
|
||||||
|
update schema_version set version = 46;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
# TODO add 'hafas' column to in_transit (and maybe journeys? undo/redo needs something to work with...)
|
# TODO add 'hafas' column to in_transit (and maybe journeys? undo/redo needs something to work with...)
|
||||||
|
|
|
@ -6,6 +6,65 @@ package Travelynx::Controller::Traewelling;
|
||||||
use Mojo::Base 'Mojolicious::Controller';
|
use Mojo::Base 'Mojolicious::Controller';
|
||||||
use Mojo::Promise;
|
use Mojo::Promise;
|
||||||
|
|
||||||
|
sub oauth {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
if ( $self->param('action')
|
||||||
|
and $self->validation->csrf_protect->has_error('csrf_token') )
|
||||||
|
{
|
||||||
|
$self->render(
|
||||||
|
'bad_request',
|
||||||
|
csrf => 1,
|
||||||
|
status => 400
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$self->render_later;
|
||||||
|
|
||||||
|
my $oa = $self->config->{traewelling}{oauth};
|
||||||
|
|
||||||
|
return $self->oauth2->get_token_p(
|
||||||
|
traewelling => { scope => 'read-statuses write-statuses' } )->then(
|
||||||
|
sub {
|
||||||
|
my ($provider) = @_;
|
||||||
|
if ( not defined $provider ) {
|
||||||
|
|
||||||
|
# OAuth2 plugin performed a redirect, no need to render
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( not $provider or not $provider->{access_token} ) {
|
||||||
|
$self->flash( new_traewelling => 1 );
|
||||||
|
$self->flash( login_error => 'no token received' );
|
||||||
|
$self->redirect_to('/account/traewelling');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
my $uid = $self->current_user->{id};
|
||||||
|
my $token = $provider->{access_token};
|
||||||
|
$self->traewelling->link(
|
||||||
|
uid => $self->current_user->{id},
|
||||||
|
token => $provider->{access_token},
|
||||||
|
expires_in => $provider->{expires_in},
|
||||||
|
);
|
||||||
|
return $self->traewelling_api->get_user_p( $uid, $token )->then(
|
||||||
|
sub {
|
||||||
|
$self->flash( new_traewelling => 1 );
|
||||||
|
$self->redirect_to('/account/traewelling');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)->catch(
|
||||||
|
sub {
|
||||||
|
my ($err) = @_;
|
||||||
|
say "error $err";
|
||||||
|
$self->flash( new_traewelling => 1 );
|
||||||
|
$self->flash( login_error => $err );
|
||||||
|
$self->redirect_to('/account/traewelling');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
sub settings {
|
sub settings {
|
||||||
my ($self) = @_;
|
my ($self) = @_;
|
||||||
|
|
||||||
|
@ -22,38 +81,7 @@ sub settings {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( $self->param('action') and $self->param('action') eq 'login' ) {
|
if ( $self->param('action') and $self->param('action') eq 'logout' ) {
|
||||||
my $email = $self->param('email');
|
|
||||||
my $password = $self->param('password');
|
|
||||||
$self->render_later;
|
|
||||||
$self->traewelling_api->login_p(
|
|
||||||
uid => $uid,
|
|
||||||
email => $email,
|
|
||||||
password => $password
|
|
||||||
)->then(
|
|
||||||
sub {
|
|
||||||
my $traewelling = $self->traewelling->get( uid => $uid );
|
|
||||||
$self->param( sync_source => 'none' );
|
|
||||||
$self->render(
|
|
||||||
'traewelling',
|
|
||||||
traewelling => $traewelling,
|
|
||||||
new_traewelling => 1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
)->catch(
|
|
||||||
sub {
|
|
||||||
my ($err) = @_;
|
|
||||||
$self->render(
|
|
||||||
'traewelling',
|
|
||||||
traewelling => {},
|
|
||||||
new_traewelling => 1,
|
|
||||||
login_error => $err,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
)->wait;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
elsif ( $self->param('action') and $self->param('action') eq 'logout' ) {
|
|
||||||
$self->render_later;
|
$self->render_later;
|
||||||
my $traewelling = $self->traewelling->get( uid => $uid );
|
my $traewelling = $self->traewelling->get( uid => $uid );
|
||||||
$self->traewelling_api->logout_p(
|
$self->traewelling_api->logout_p(
|
||||||
|
|
|
@ -199,84 +199,6 @@ sub get_user_p {
|
||||||
return $promise;
|
return $promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub login_p {
|
|
||||||
my ( $self, %opt ) = @_;
|
|
||||||
|
|
||||||
my $uid = $opt{uid};
|
|
||||||
my $email = $opt{email};
|
|
||||||
my $password = $opt{password};
|
|
||||||
|
|
||||||
my $ua = $self->{user_agent}->request_timeout(20);
|
|
||||||
|
|
||||||
my $request = {
|
|
||||||
login => $email,
|
|
||||||
password => $password,
|
|
||||||
};
|
|
||||||
|
|
||||||
my $promise = Mojo::Promise->new;
|
|
||||||
my $token;
|
|
||||||
|
|
||||||
$ua->post_p(
|
|
||||||
"https://traewelling.de/api/v1/auth/login" => $self->{header},
|
|
||||||
json => $request
|
|
||||||
)->then(
|
|
||||||
sub {
|
|
||||||
my ($tx) = @_;
|
|
||||||
if ( my $err = $tx->error ) {
|
|
||||||
my $err_msg
|
|
||||||
= "v1/auth/login: HTTP $err->{code} $err->{message}";
|
|
||||||
$promise->reject($err_msg);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
my $res = $tx->result->json->{data};
|
|
||||||
$token = $res->{token};
|
|
||||||
my $expiry_dt = $self->parse_datetime( $res->{expires_at} );
|
|
||||||
|
|
||||||
# Fall back to one year expiry
|
|
||||||
$expiry_dt //= DateTime->now( time_zone => 'Europe/Berlin' )
|
|
||||||
->add( years => 1 );
|
|
||||||
$self->{model}->link(
|
|
||||||
uid => $uid,
|
|
||||||
email => $email,
|
|
||||||
token => $token,
|
|
||||||
expires => $expiry_dt
|
|
||||||
);
|
|
||||||
return $self->get_user_p( $uid, $token );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)->then(
|
|
||||||
sub {
|
|
||||||
$promise->resolve;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
)->catch(
|
|
||||||
sub {
|
|
||||||
my ($err) = @_;
|
|
||||||
if ($token) {
|
|
||||||
|
|
||||||
# We have a token, but couldn't complete the login. For now, we
|
|
||||||
# solve this by logging out and invalidating the token.
|
|
||||||
$self->logout_p(
|
|
||||||
uid => $uid,
|
|
||||||
token => $token
|
|
||||||
)->finally(
|
|
||||||
sub {
|
|
||||||
$promise->reject("v1/auth/login: $err");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$promise->reject("v1/auth/login: $err");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
)->wait;
|
|
||||||
|
|
||||||
return $promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub logout_p {
|
sub logout_p {
|
||||||
my ( $self, %opt ) = @_;
|
my ( $self, %opt ) = @_;
|
||||||
|
|
||||||
|
|
|
@ -38,16 +38,15 @@ sub now {
|
||||||
sub link {
|
sub link {
|
||||||
my ( $self, %opt ) = @_;
|
my ( $self, %opt ) = @_;
|
||||||
|
|
||||||
my $log = [ [ $self->now->epoch, "Erfolgreich angemeldet" ] ];
|
my $log = [ [ $self->now->epoch, "Erfolgreich mittels OAuth2 verbunden" ] ];
|
||||||
|
|
||||||
my $data = {
|
my $data = {
|
||||||
log => $log,
|
log => $log,
|
||||||
expires => $opt{expires}->epoch,
|
expires => $self->now->epoch + $opt{expires_in},
|
||||||
};
|
};
|
||||||
|
|
||||||
my $user_entry = {
|
my $user_entry = {
|
||||||
user_id => $opt{uid},
|
user_id => $opt{uid},
|
||||||
email => $opt{email},
|
|
||||||
push_sync => 0,
|
push_sync => 0,
|
||||||
pull_sync => 0,
|
pull_sync => 0,
|
||||||
token => $opt{token},
|
token => $opt{token},
|
||||||
|
@ -59,7 +58,7 @@ sub link {
|
||||||
$user_entry,
|
$user_entry,
|
||||||
{
|
{
|
||||||
on_conflict => \
|
on_conflict => \
|
||||||
'(user_id) do update set email = EXCLUDED.email, token = EXCLUDED.token, push_sync = false, pull_sync = false, data = null, errored = false, latest_run = null'
|
'(user_id) do update set token = EXCLUDED.token, push_sync = false, pull_sync = false, data = null, errored = false, latest_run = null'
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -122,6 +122,7 @@
|
||||||
% }
|
% }
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
% if (config->{traewelling}{oauth}) {
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Träwelling</th>
|
<th scope="row">Träwelling</th>
|
||||||
<td>
|
<td>
|
||||||
|
@ -149,6 +150,7 @@
|
||||||
% }
|
% }
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
% }
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Externe Dienste</th>
|
<th scope="row">Externe Dienste</th>
|
||||||
<td>
|
<td>
|
||||||
|
|
|
@ -4,20 +4,20 @@
|
||||||
|
|
||||||
<h1>Träwelling</h1>
|
<h1>Träwelling</h1>
|
||||||
|
|
||||||
% if (stash('new_traewelling')) {
|
% if (flash('new_traewelling')) {
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col s12">
|
<div class="col s12">
|
||||||
% if ($traewelling->{token}) {
|
% if ($traewelling->{token}) {
|
||||||
<div class="card success-color">
|
<div class="card success-color">
|
||||||
<div class="card-content white-text">
|
<div class="card-content white-text">
|
||||||
<span class="card-title">Träwelling verknüpft</span>
|
<span class="card-title">Träwelling verknüpft</span>
|
||||||
% my $user = $traewelling->{data}{user_name} // $traewelling->{email};
|
% my $user = $traewelling->{data}{user_name} // '???';
|
||||||
<p>Dein travelynx-Account hat nun ein Jahr lang Zugriff auf
|
<p>Dein travelynx-Account hat nun ein Jahr lang Zugriff auf
|
||||||
den Träwelling-Account <b>@<%= $user %></b>.</p>
|
den Träwelling-Account <b>@<%= $user %></b>.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
% }
|
% }
|
||||||
% elsif (my $login_err = stash('login_error')) {
|
% elsif (my $login_err = flash('login_error')) {
|
||||||
<div class="card caution-color">
|
<div class="card caution-color">
|
||||||
<div class="card-content white-text">
|
<div class="card-content white-text">
|
||||||
<span class="card-title">Login-Fehler</span>
|
<span class="card-title">Login-Fehler</span>
|
||||||
|
@ -30,7 +30,7 @@
|
||||||
<div class="card-content white-text">
|
<div class="card-content white-text">
|
||||||
<span class="card-title">Logout-Fehler</span>
|
<span class="card-title">Logout-Fehler</span>
|
||||||
<p>Der Logout bei Träwelling ist fehlgeschlagen: <%= $logout_err %>.
|
<p>Der Logout bei Träwelling ist fehlgeschlagen: <%= $logout_err %>.
|
||||||
Dein Login-Token bei travelynx wurde dennoch gelöscht, so
|
Dein Token bei travelynx wurde dennoch gelöscht, so
|
||||||
dass nun kein Zugriff von travelynx auf Träwelling mehr
|
dass nun kein Zugriff von travelynx auf Träwelling mehr
|
||||||
möglich ist. In den <a
|
möglich ist. In den <a
|
||||||
href="https://traewelling.de/settings">Träwelling-Einstellungen</a>
|
href="https://traewelling.de/settings">Träwelling-Einstellungen</a>
|
||||||
|
@ -73,10 +73,10 @@
|
||||||
<div class="card caution-color">
|
<div class="card caution-color">
|
||||||
<div class="card-content white-text">
|
<div class="card-content white-text">
|
||||||
% if ($traewelling->{expired}) {
|
% if ($traewelling->{expired}) {
|
||||||
<span class="card-title">Login-Token abgelaufen</span>
|
<span class="card-title">Token abgelaufen</span>
|
||||||
% }
|
% }
|
||||||
% else {
|
% else {
|
||||||
<span class="card-title">Login-Token läuft bald ab</span>
|
<span class="card-title">Token läuft bald ab</span>
|
||||||
% }
|
% }
|
||||||
<p>Melde deinen travelynx-Account von Träwelling ab und
|
<p>Melde deinen travelynx-Account von Träwelling ab und
|
||||||
verbinde ihn mit deinem Träwelling-Passwort erneut,
|
verbinde ihn mit deinem Träwelling-Passwort erneut,
|
||||||
|
@ -105,37 +105,19 @@
|
||||||
verknüpfen. Dies erlaubt die automatische Übernahme zukünftiger
|
verknüpfen. Dies erlaubt die automatische Übernahme zukünftiger
|
||||||
Checkins zwischen den beiden Diensten. Träwelling-Checkins in
|
Checkins zwischen den beiden Diensten. Träwelling-Checkins in
|
||||||
Nahverkehrsmittel und Züge außerhalb des deutschen Schienennetzes
|
Nahverkehrsmittel und Züge außerhalb des deutschen Schienennetzes
|
||||||
werden nicht unterstützt und ignoriert. Checkins, die vor dem
|
werden (noch) nicht unterstützt und ignoriert. Checkins, die
|
||||||
Verknüpfen der Accounts stattgefunden haben, werden nicht
|
vor dem Verknüpfen der Accounts stattgefunden haben, werden
|
||||||
synchronisiert. Bei synchronisierten Checkins wird der zugehörige
|
nicht synchronisiert. Bei synchronisierten Checkins wird der
|
||||||
Träwelling-Status von deiner travelynx-Statusseite aus verlinkt.
|
zugehörige Träwelling-Status von deiner travelynx-Statusseite
|
||||||
</p>
|
aus verlinkt.
|
||||||
<p>
|
|
||||||
Mit E-Mail und Passwort wird ein Login über die Träwelling-API
|
|
||||||
durchgeführt. Die E-Mail und das dabei generierte Token werden
|
|
||||||
von travelynx gespeichert. Das Passwort wird ausschließlich für
|
|
||||||
den Login verwendet und nicht gespeichert. Der Login kann jederzeit
|
|
||||||
sowohl auf dieser Seite als auch über die <a
|
|
||||||
href="https://traewelling.de/settings">Träwelling-Einstellungen</a>
|
|
||||||
widerrufen werden. Nach einem Jahr läuft er automatisch ab.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
%= form_for '/account/traewelling' => (method => 'POST') => begin
|
%= form_for '/oauth/traewelling' => (method => 'POST') => begin
|
||||||
%= csrf_field
|
%= csrf_field
|
||||||
<div class="input-field col s12">
|
|
||||||
<i class="material-icons prefix">account_circle</i>
|
|
||||||
%= text_field 'email', id => 'email', class => 'validate', required => undef, maxlength => 250
|
|
||||||
<label for="email">Login (Name oder E-Mail)</label>
|
|
||||||
</div>
|
|
||||||
<div class="input-field col s12">
|
|
||||||
<i class="material-icons prefix">lock</i>
|
|
||||||
%= password_field 'password', id => 'password', class => 'validate', required => undef
|
|
||||||
<label for="password">Passwort</label>
|
|
||||||
</div>
|
|
||||||
<div class="col s12 center-align">
|
<div class="col s12 center-align">
|
||||||
<button class="btn waves-effect waves-light" type="submit" name="action" value="login">
|
<button class="btn waves-effect waves-light" type="submit" name="action" value="connect">
|
||||||
Verknüpfen
|
Verknüpfen
|
||||||
<i class="material-icons right">send</i>
|
<i class="material-icons right">send</i>
|
||||||
</button>
|
</button>
|
||||||
|
@ -154,7 +136,7 @@
|
||||||
% else {
|
% else {
|
||||||
%= $traewelling->{email}
|
%= $traewelling->{email}
|
||||||
% }
|
% }
|
||||||
verknüpft. Der Login-Token läuft <%= $traewelling->{expires_on}->strftime('am %d.%m.%Y um %H:%M Uhr') %> ab.
|
verknüpft. Der Token läuft <%= $traewelling->{expires_on}->strftime('am %d.%m.%Y um %H:%M Uhr') %> ab.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue