Mobile oauth confirmation page, closes CNVS-5455

Mobile styling of oauth confirmation page.
Also refactors mobile login page to use boostrap.

test plan:
1. go to Site Admin > Developer Keys
2. add or use existing key to fill out this URL:
    /login/oauth2/auth?client_id=<ID>&response_type=code&redirect_uri=<URI>
    (URI should be encoded)
3. in a Chrome tab change the User Agent to a mobile device
4. in that same tab go to the url in step 2
    (you may be redirected to login)
5. the oauth confirmation page should look decent
6. click Log In, you should be redirected to URI + ?code=<long code>

Change-Id: I32a4d0638838dd23667ceaf40c02a5be84434f08
Reviewed-on: https://gerrit.instructure.com/21220
Tested-by: Jenkins <jenkins@instructure.com>
Reviewed-by: Joe Tanner <joe@instructure.com>
Product-Review: Joe Tanner <joe@instructure.com>
QA-Review: Joe Tanner <joe@instructure.com>
This commit is contained in:
Joe Tanner 2013-06-05 08:50:18 -06:00
parent 36d8bdb9d6
commit 649c5cb85b
9 changed files with 258 additions and 211 deletions

View File

@ -1454,6 +1454,10 @@ class ApplicationController < ActionController::Base
session[:browser_supported]
end
def mobile_device?
params[:mobile] || request.user_agent.to_s =~ /ipod|iphone|ipad|Android/i
end
def profile_data(profile, viewer, session, includes)
extend Api::V1::UserProfile
extend Api::V1::Course

View File

@ -114,15 +114,15 @@ class PseudonymSessionsController < ApplicationController
end
def maybe_render_mobile_login(status = nil)
if params[:mobile] || request.user_agent.to_s =~ /ipod|iphone|ipad|Android/i
if mobile_device?
@login_handle_name = @domain_root_account.login_handle_name rescue AccountAuthorizationConfig.default_login_handle_name
@login_handle_is_email = @login_handle_name == AccountAuthorizationConfig.default_login_handle_name
@shared_js_vars = {
js_env(
:GOOGLE_ANALYTICS_KEY => Setting.get_cached('google_analytics_key', nil),
:RESET_SENT => t("password_confirmation_sent", "Password confirmation sent. Make sure you check your spam box."),
:RESET_ERROR => t("password_confirmation_error", "Error sending request.")
}
render :template => 'pseudonym_sessions/mobile_login', :layout => false, :status => status
)
render :template => 'pseudonym_sessions/mobile_login', :layout => 'mobile_auth', :status => status
else
@request = request
render :action => 'new', :status => status
@ -594,6 +594,11 @@ class PseudonymSessionsController < ApplicationController
def oauth2_confirm
@provider = Canvas::Oauth::Provider.new(session[:oauth2][:client_id], session[:oauth2][:redirect_uri], session[:oauth2][:scopes])
if mobile_device?
js_env :GOOGLE_ANALYTICS_KEY => Setting.get_cached('google_analytics_key', nil)
render :layout => 'mobile_auth', :action => 'oauth2_confirm_mobile'
end
end
def oauth2_accept

View File

@ -0,0 +1,118 @@
@import "environment";
@import "bootstrap/forms";
@import "bootstrap/buttons";
* {
margin: 0px;
padding: 0px;
}
html, body {
height: 100%;
}
html {
margin: 0;
padding: 0;
background-color: black;
background: url('/images/Money_Noise_tm.png?1348625397'),
-webkit-gradient(radial, 50% 0px, 0, 50% 0px, 0, color-stop(0%, rgba(255, 255, 255, 0.4)), color-stop(100%, transparent)),
-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #2B2B2B), color-stop(100%, black));
background: url('/images/Money_Noise_tm.png?1348625397'),
-webkit-radial-gradient(50% 0px, circle cover, rgba(255, 255, 255, 0.4) 0%, transparent 100%),
-webkit-linear-gradient(top, #2B2B2B, black);
background: url('/images/Money_Noise_tm.png?1348625397'),
radial-gradient(50% -100px, circle cover, rgba(255, 255, 255, 0.6) 0%, transparent 100%),
linear-gradient(top, #2B2B2B, black);
min-height: 420px; /* this is a workaround to be high enough on an iphone */
font-size: 114%;
font-family: Helvetica;
}
.face {
background: #1a1b1a;
border-radius: 1em;
color: white;
}
#f1_container {
position: relative;
margin: 0 auto;
padding: 110px 0;
width: 300px;
height: 198px;
z-index: 1;
background-image: url("/images/canvas_logo_for_mobile_login.png?1");
background-position: center 30px;
background-repeat: no-repeat;
background-size: 270px 48px;
}
#f1_card {
width: 100%;
height: 100%;
}
.face {
position: absolute;
padding: 15px;
}
.face.back {
display: none;
}
.flipped .face.back {
display: block;
}
.flipped .face.front {
display: none;
}
a {
color: lightgray;
}
p {
margin: 15px 0;
}
.btn {
text-align: center;
display: block;
text-decoration: none;
margin-bottom: 15px;
width: 270px;
}
a.btn {
width: 230px;
}
.flip-to-back, .flip-to-front {
margin: 0;
}
input.input-block-level {
display: block;
padding: 10px 15px;
width: 240px;
margin-bottom: 15px;
font-size: 20px;
}
label {
padding: 10px 15px;
font-size: 15px;
}
input::-webkit-input-placeholder {
color: #898989;
}
.error {
background: #faa;
color: #a00;
font-size: 20px;
text-shadow: 0px 1px 1px rgba(255,255,255,0.4);
margin: 15px 0;
padding: 10px 5px;
border-radius: 6px;
text-align: center;
}
.icon_url {
display: none;
}

View File

@ -0,0 +1,70 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title><%= yield :page_title || t("log_in_to_canvas", "Log In To Canvas") %></title>
<meta name="HandheldFriendly" content="True">
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
<script>
ENV = <%= raw js_env.to_json %>;
</script>
<% jammit_css :mobile_auth %>
<%= include_css_bundles %>
</head>
<body>
<div id="f1_container">
<div id="f1_card">
<%= yield %>
</div>
</div>
<script>
// https://gist.github.com/1183357
//scroll to top, hide address bar on mobile devices - 1 for android, 0 for the rest
(function( win ){
var doc = win.document;
// If there's a hash, or addEventListener is undefined, stop here
if( !location.hash || !win.addEventListener ){
//scroll to 1
window.scrollTo( 0, 1 );
var scrollTop = 1,
getScrollTop = function(){
return "scrollTop" in doc.body ? doc.body.scrollTop : 1;
},
//reset to 0 on bodyready, if needed
bodycheck = setInterval(function(){
if( doc.body ){
clearInterval( bodycheck );
scrollTop = getScrollTop();
win.scrollTo( 0, scrollTop === 1 ? 0 : 1 );
}
}, 15 );
win.addEventListener( "load", function(){
setTimeout(function(){
//at load, if user hasn't scrolled more than 20 or so...
if( getScrollTop() < 20 ){
//reset to hide addr bar at onload
win.scrollTo( 0, scrollTop === 1 ? 0 : 1 );
}
}, 0);
}, false );
}
})( this );
// GOOGLE ANALYTICS
var _gaq=[["_setAccount",ENV.GOOGLE_ANALYTICS_KEY],["_trackPageview"]];
(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];g.async=1;
g.src=("https:"==location.protocol?"//ssl":"//www")+".google-analytics.com/ga.js";
s.parentNode.insertBefore(g,s)}(document,"script"));
</script>
</body>
</html>

View File

@ -1,164 +1,26 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title><%= t "log_in_to_canvas", "Log In To Canvas" %></title>
<meta name="HandheldFriendly" content="True">
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
<style>
* {
margin: 0px;
padding: 0px;
}
html, body {
height: 100%;
}
<% form_for :pseudonym_session, :url => login_path, :html => {:id => "login_form", :class => "front face", :novalidate => 'novalidate'} do |f| %>
<input class="input-block-level" <%= 'type=email' if @login_handle_is_email %> name="pseudonym_session[unique_id]" value="<%= params[:pseudonym_session].try(:[], :unique_id) %>" placeholder="<%= @login_handle_name %>">
<input class="input-block-level" type="password" name="pseudonym_session[password]" placeholder="<%= t :password, "Password" %>">
<% if request.post? && flash[:error] %>
<div class="error"><%= flash[:error] %></div>
<% end %>
<button type="submit" class="btn btn-primary btn-large"><%= t('login', 'Login') %></button>
<% if url = params[:canvas_login].blank? && @domain_root_account.try(:forgot_password_external_url) %>
<%= link_to t('dont_know_password', "I don't know my password"), url, :class => 'btn btn-large forgot-password' %>
<% else %>
<a class="btn btn-large btn-inverse flip-to-back"><%= t('dont_know_password', "I don't know my password") %></a>
<% end %>
<% end %>
<% form_for :pseudonym_session, :url => forgot_password_path, :html => {:id => "forgot_password_form", :class => 'back face'} do |f| %>
<label for="pseudonym_session_unique_id_forgot"><%= t('login_handle', "Enter your %{login_handle_name} and we'll send you a link to change your password.", :login_handle_name => @login_handle_name) %></label>
<input class="input-block-level" <%= 'type=email' if @login_handle_is_email %> name="pseudonym_session[unique_id_forgot]" value="<%= @unauthorized_user.try :email %>" placeholder="<%= @login_handle_name %>" id="pseudonym_session_unique_id_forgot" />
<button type="submit" class="btn btn-primary btn-large" data-text-while-loading="<%= t :sending, "Sending..." %>" class="request-password-button"><%= t('buttons.request_password', 'Request Password') %></button>
<a class="btn btn-large btn-inverse flip-to-front"><%= t('back_to_login', "Back to Login") %></a>
<% end %>
<%= # for plugin use
render "shared/login_trailer" %>
html {
margin: 0;
padding: 0;
background-color: black;
background: url('/images/Money_Noise_tm.png?1348625397'),
-webkit-gradient(radial, 50% 0px, 0, 50% 0px, 0, color-stop(0%, rgba(255, 255, 255, 0.4)), color-stop(100%, transparent)),
-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #2B2B2B), color-stop(100%, black));
background: url('/images/Money_Noise_tm.png?1348625397'),
-webkit-radial-gradient(50% 0px, circle cover, rgba(255, 255, 255, 0.4) 0%, transparent 100%),
-webkit-linear-gradient(top, #2B2B2B, black);
background: url('/images/Money_Noise_tm.png?1348625397'),
radial-gradient(50% -100px, circle cover, rgba(255, 255, 255, 0.6) 0%, transparent 100%),
linear-gradient(top, #2B2B2B, black);
min-height: 420px; /* this is a workaround to be high enough on an iphone */
font-size: 114%;
font-family: Helvetica;
}
.face {
background: #1a1b1a;
border-radius: 1em;
color: white;
}
#f1_container {
position: relative;
margin: 0 auto;
padding: 110px 0;
width: 300px;
height: 198px;
z-index: 1;
background-image: url("/images/canvas_logo_for_mobile_login.png?1");
background-position: center 30px;
background-repeat: no-repeat;
background-size: 270px 48px;
}
#f1_card {
width: 100%;
height: 100%;
}
.face {
position: absolute;
width: 100%;
padding: 0;
}
.face.back {
display: none;
}
.flipped .face.back {
display: block;
}
.flipped .face.front {
display: none;
}
input, button, .btn{
display: block;
border: none;
border-radius: 10px;
padding: 10px 15px;
text-shadow: 1px 1px 3px rgba(255,255,255, 0.5);
font-family: Helvetica;
background-color: #e1e1e1;
width: 240px;
margin: 15px 15px;
font-size: 20px;
}
label {
padding: 10px 15px;
display: block;
font-size: 15px;
}
button, .btn{
text-align: center;
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #7ac0f8), color-stop(100%, #2e96e2));
-webkit-box-shadow: black 0px 0px 2px 0px;
border: 2px solid black;
text-shadow: 0px 1px 1px rgba(255,255,255,0.4);
color: #36629b;
width: 272px;
margin: 20px 13px;
}
.flip-to-back, .flip-to-front, .forgot-password {
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #262626), color-stop(100%, #404141));
color: #c2c2c2;
margin-top: 30px;
font-size: 15px;
border-width: 1px;
text-shadow: 0px 1px 1px rgba(0,0,0,0.4);
margin-bottom: 15px;
width: 240px;
}
.forgot-password {
display: block;
text-decoration: none;
}
input::-webkit-input-placeholder {
color: #898989;
}
.error {
background: #faa;
color: #a00;
font-size: 20px;
text-shadow: 0px 1px 1px rgba(255,255,255,0.4);
margin: 15px;
padding: 10px 5px;
border-radius: 10px;
text-align: center;
}
</style>
</head>
<body>
<div id="f1_container">
<div id="f1_card">
<% form_for :pseudonym_session, :url => login_path, :html => {:id => "login_form", :class => "front face", :novalidate => 'novalidate'} do |f| %>
<input <%= 'type=email' if @login_handle_is_email %> name="pseudonym_session[unique_id]" value="<%= params[:pseudonym_session].try(:[], :unique_id) %>" placeholder="<%= @login_handle_name %>">
<input type="password" name="pseudonym_session[password]" placeholder="<%= t :password, "Password" %>">
<% if request.post? && flash[:error] %>
<div class="error"><%= flash[:error] %></div>
<% end %>
<button type="submit"><%= t('login', 'Login') %></button>
<% if url = params[:canvas_login].blank? && @domain_root_account.try(:forgot_password_external_url) %>
<%= link_to t('dont_know_password', "I don't know my password"), url, :class => 'btn forgot-password' %>
<% else %>
<a class="btn flip-to-back"><%= t('dont_know_password', "I don't know my password") %></a>
<% end %>
<% end %>
<% form_for :pseudonym_session, :url => forgot_password_path, :html => {:id => "forgot_password_form", :class => 'back face'} do |f| %>
<label for="pseudonym_session_unique_id_forgot"><%= t('login_handle', "Enter your %{login_handle_name} and we'll send you a link to change your password.", :login_handle_name => @login_handle_name) %></label>
<input <%= 'type=email' if @login_handle_is_email %> name="pseudonym_session[unique_id_forgot]" value="<%= @unauthorized_user.try :email %>" placeholder="<%= @login_handle_name %>" id="pseudonym_session_unique_id_forgot" />
<button type="submit" data-text-while-loading="<%= t :sending, "Sending..." %>" class="request-password-button"><%= t('buttons.request_password', 'Request Password') %></button>
<a class="btn flip-to-front"><%= t('back_to_login', "Back to Login") %></a>
<% end %>
</div>
</div>
<%= render "shared/login_trailer" %>
<script>
var SHARED_JS_VARS = <%= @shared_js_vars.to_json.html_safe %>;
var eventToBindTo = ('ontouchstart' in window) ? 'touchstart' : 'click';
document.addEventListener('DOMContentLoaded', function(){
@ -194,10 +56,10 @@
'&pseudonym_session%5Bunique_id_forgot%5D=' +
encodeURIComponent(uniqueId),
success: function(response){
alert(SHARED_JS_VARS.RESET_SENT);
alert(ENV.RESET_SENT);
},
error: function(){
alert(SHARED_JS_VARS.RESET_ERROR);
alert(ENV.RESET_ERROR);
},
complete: function(){
$button.textContent = originalText;
@ -241,49 +103,4 @@
xhr.send(options.data);
return xhr;
}
// https://gist.github.com/1183357
//scroll to top, hide address bar on mobile devices - 1 for android, 0 for the rest
(function( win ){
var doc = win.document;
// If there's a hash, or addEventListener is undefined, stop here
if( !location.hash || !win.addEventListener ){
//scroll to 1
window.scrollTo( 0, 1 );
var scrollTop = 1,
getScrollTop = function(){
return "scrollTop" in doc.body ? doc.body.scrollTop : 1;
},
//reset to 0 on bodyready, if needed
bodycheck = setInterval(function(){
if( doc.body ){
clearInterval( bodycheck );
scrollTop = getScrollTop();
win.scrollTo( 0, scrollTop === 1 ? 0 : 1 );
}
}, 15 );
win.addEventListener( "load", function(){
setTimeout(function(){
//at load, if user hasn't scrolled more than 20 or so...
if( getScrollTop() < 20 ){
//reset to hide addr bar at onload
win.scrollTo( 0, scrollTop === 1 ? 0 : 1 );
}
}, 0);
}, false );
}
})( this );
// GOOGLE ANALYTICS
var _gaq=[["_setAccount",SHARED_JS_VARS.GOOGLE_ANALYTICS_KEY],["_trackPageview"]];
(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];g.async=1;
g.src=("https:"==location.protocol?"//ssl":"//www")+".google-analytics.com/ga.js";
s.parentNode.insertBefore(g,s)}(document,"script"));
</script>
</body>
</html>

View File

@ -0,0 +1,29 @@
<%
content_for :page_title, t(:page_title, "App Login")
%>
<div class="face">
<h2><%= @provider.app_name %></h2>
<p>
<%= mt 'details.allow_application', "%{app_name} is requesting access to your account.", :app_name => @provider.app_name %>
<br/><br/>
<%= mt 'details.login_name', "You are logging into this app as %{user_name}.", :user_name => link_to(@current_user.short_name, user_profile_url(@current_user), :popup => true) %>
<% if @current_user.email.present? && @current_user.email != @current_user.short_name %>
<br/>
<%= t 'details.email', "Your email address is %{email}.", :email => @current_user.email %>
<% end %>
</p>
<% form_tag oauth2_auth_accept_path do %>
<div class="button_box">
<button type="submit" class="btn btn-large btn-primary"><%= t 'login', 'Log In' %></button>
<%= link_to(t(:cancel, "Cancel"), oauth2_auth_deny_path, :class => "btn btn-large") %>
</div>
<% if true#unless @provider.scopes.blank? %>
<div class="control-group">
<label class="checkbox" for="remember_access">
<%= check_box_tag(:remember_access, :class => "checkbox") %>
<%= t 'remember_auth', 'Remember my authorization for this service' %>
</label>
</div>
<% end %>
<% end %>
</div>

View File

@ -131,4 +131,5 @@ $(document).ready(function() {
<% end %>
</div>
</div>
<%= render "shared/login_trailer" %>
<%= # for plugin use
render "shared/login_trailer" %>

View File

@ -166,5 +166,7 @@ stylesheets:
- public/stylesheets/compiled/syntax.css
locale:
- public/stylesheets/compiled/locale.css
mobile_auth:
- public/stylesheets/compiled/mobile_auth.css
<%= plugin_assets.anchors_yml %>

View File

@ -33,6 +33,7 @@ describe PseudonymSessionsController do
def confirm_mobile_layout
mobile_agents.each do |agent|
controller.js_env.clear
request.env['HTTP_USER_AGENT'] = agent
yield
response.should render_template("pseudonym_sessions/mobile_login")