From 89e9154499f67fd8c63e1098b3e50b317c690dd0 Mon Sep 17 00:00:00 2001 From: Azul Date: Mon, 5 May 2014 12:22:52 +0200 Subject: custom error pages for 404 and 500 errors --- app/controllers/errors_controller.rb | 9 +++++++++ app/views/errors/not_found.html.haml | 7 +++++++ app/views/errors/server_error.html.haml | 7 +++++++ 3 files changed, 23 insertions(+) create mode 100644 app/controllers/errors_controller.rb create mode 100644 app/views/errors/not_found.html.haml create mode 100644 app/views/errors/server_error.html.haml (limited to 'app') diff --git a/app/controllers/errors_controller.rb b/app/controllers/errors_controller.rb new file mode 100644 index 0000000..bf9329c --- /dev/null +++ b/app/controllers/errors_controller.rb @@ -0,0 +1,9 @@ +class ErrorsController < ApplicationController + + def not_found + end + + def server_error + end + +end diff --git a/app/views/errors/not_found.html.haml b/app/views/errors/not_found.html.haml new file mode 100644 index 0000000..22b6a55 --- /dev/null +++ b/app/views/errors/not_found.html.haml @@ -0,0 +1,7 @@ +.hero-unit + %h1 Page not found. + %h2 The page you were looking for doesn't exist. + %p.lead You may have mistyped the address or the page may have moved. + %a.btn.btn-primary.btn-large{href:'/'} + %i.icon-home.icon-white + Home diff --git a/app/views/errors/server_error.html.haml b/app/views/errors/server_error.html.haml new file mode 100644 index 0000000..173cdad --- /dev/null +++ b/app/views/errors/server_error.html.haml @@ -0,0 +1,7 @@ +.hero-unit + %h1 Ouch! + %h2 We ran into a server error. + %p.lead The problem has been logged and we will look into it. + %a.btn.btn-primary.btn-large{href:'/'} + %i.icon-home.icon-white + Home -- cgit v1.2.3 From 97e30cc136a72092abba19f1b601ad9d5ebd5257 Mon Sep 17 00:00:00 2001 From: Azul Date: Mon, 5 May 2014 12:29:21 +0200 Subject: i18n for error pages --- app/views/errors/not_found.html.haml | 8 ++++---- app/views/errors/server_error.html.haml | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) (limited to 'app') diff --git a/app/views/errors/not_found.html.haml b/app/views/errors/not_found.html.haml index 22b6a55..75cb889 100644 --- a/app/views/errors/not_found.html.haml +++ b/app/views/errors/not_found.html.haml @@ -1,7 +1,7 @@ .hero-unit - %h1 Page not found. - %h2 The page you were looking for doesn't exist. - %p.lead You may have mistyped the address or the page may have moved. + %h1=t :not_found_title + %h2=t :not_found_subtitle + %p.lead=t :not_found_lead %a.btn.btn-primary.btn-large{href:'/'} %i.icon-home.icon-white - Home + =t :home diff --git a/app/views/errors/server_error.html.haml b/app/views/errors/server_error.html.haml index 173cdad..68baf20 100644 --- a/app/views/errors/server_error.html.haml +++ b/app/views/errors/server_error.html.haml @@ -1,7 +1,7 @@ .hero-unit - %h1 Ouch! - %h2 We ran into a server error. - %p.lead The problem has been logged and we will look into it. + %h1=t :server_error_title + %h2=t :server_error_subtitle + %p.lead=t :server_error_lead %a.btn.btn-primary.btn-large{href:'/'} %i.icon-home.icon-white - Home + =t :home -- cgit v1.2.3 From b298cea527f74f682d24defee360e0f45f47d125 Mon Sep 17 00:00:00 2001 From: Azul Date: Tue, 6 May 2014 09:50:46 +0200 Subject: little bit of documentation --- app/controllers/errors_controller.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/controllers/errors_controller.rb b/app/controllers/errors_controller.rb index bf9329c..6c659e6 100644 --- a/app/controllers/errors_controller.rb +++ b/app/controllers/errors_controller.rb @@ -1,9 +1,10 @@ +# We render http errors ourselves so we can customize them class ErrorsController < ApplicationController - + # 404 def not_found end + # 500 def server_error end - end -- cgit v1.2.3 From e94b9471c0bc30cd6a1a5bf5b6b22b746d242e31 Mon Sep 17 00:00:00 2001 From: Azul Date: Fri, 9 May 2014 16:11:20 +0200 Subject: calculate cert fingerprints to store for leap_mx stelfox.net/blog/2014/04/calculating-rsa-key-fingerprints-in-ruby/ --- app/models/client_certificate.rb | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'app') diff --git a/app/models/client_certificate.rb b/app/models/client_certificate.rb index 76b07a2..63de9e1 100644 --- a/app/models/client_certificate.rb +++ b/app/models/client_certificate.rb @@ -43,8 +43,16 @@ class ClientCertificate self.key.to_pem + self.cert.to_pem end + def fingerprint + OpenSSL::Digest::SHA1.hexdigest(openssl_cert.to_der).scan(/../).join(':') + end + private + def openssl_cert + cert.openssl_body + end + def self.root_ca @root_ca ||= begin crt = File.read(APP_CONFIG[:client_ca_cert]) -- cgit v1.2.3 From 5dd6c1529f8f4fc5089c71b0a44e360acaea900d Mon Sep 17 00:00:00 2001 From: Azul Date: Thu, 15 May 2014 11:04:56 +0200 Subject: fix Email so User.new.valid? does not crash Email.new(nil) now returns an invalid email rather than crashing. --- app/models/email.rb | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'app') diff --git a/app/models/email.rb b/app/models/email.rb index a9a503f..4090275 100644 --- a/app/models/email.rb +++ b/app/models/email.rb @@ -7,6 +7,11 @@ class Email < String :message => "needs to be a valid email address" } + # Make sure we can call Email.new(nil) and get an invalid email address + def initialize(s) + super(s.to_s) + end + def to_partial_path "emails/email" end -- cgit v1.2.3 From 71dcf3f4e5d423b78b47f675297fc98b28ef3442 Mon Sep 17 00:00:00 2001 From: Azul Date: Thu, 15 May 2014 11:17:47 +0200 Subject: SmtpCertsController, routes and tests --- app/controllers/v1/smtp_certs_controller.rb | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 app/controllers/v1/smtp_certs_controller.rb (limited to 'app') diff --git a/app/controllers/v1/smtp_certs_controller.rb b/app/controllers/v1/smtp_certs_controller.rb new file mode 100644 index 0000000..001425d --- /dev/null +++ b/app/controllers/v1/smtp_certs_controller.rb @@ -0,0 +1,21 @@ +class V1::SmtpCertsController < ApplicationController + + before_filter :require_login + before_filter :require_email_account + + # GET /cert + def show + @cert = ClientCertificate.new prefix: current_user.email_address + render text: @cert.to_s, content_type: 'text/plain' + end + + protected + + def require_email_account + access_denied unless service_level.provides? 'email' + end + + def service_level + current_user.effective_service_level + end +end -- cgit v1.2.3 From 17b67aeda81dee2273ce1161ac7292a328c3efaa Mon Sep 17 00:00:00 2001 From: Azul Date: Thu, 15 May 2014 16:29:49 +0200 Subject: store cert fingerprint with main user identity --- app/controllers/v1/smtp_certs_controller.rb | 2 ++ app/models/identity.rb | 1 + 2 files changed, 3 insertions(+) (limited to 'app') diff --git a/app/controllers/v1/smtp_certs_controller.rb b/app/controllers/v1/smtp_certs_controller.rb index 001425d..258b391 100644 --- a/app/controllers/v1/smtp_certs_controller.rb +++ b/app/controllers/v1/smtp_certs_controller.rb @@ -6,6 +6,8 @@ class V1::SmtpCertsController < ApplicationController # GET /cert def show @cert = ClientCertificate.new prefix: current_user.email_address + current_user.identity.cert_fingerprints << @cert.fingerprint + current_user.identity.save render text: @cert.to_s, content_type: 'text/plain' end diff --git a/app/models/identity.rb b/app/models/identity.rb index ad8c01e..2f8d4eb 100644 --- a/app/models/identity.rb +++ b/app/models/identity.rb @@ -8,6 +8,7 @@ class Identity < CouchRest::Model::Base property :address, LocalEmail property :destination, Email property :keys, HashWithIndifferentAccess + property :cert_fingerprints, [String] validate :unique_forward validate :alias_available -- cgit v1.2.3 From e8ba98df64cb537e85de8624c0ebb08c4135ccca Mon Sep 17 00:00:00 2001 From: Azul Date: Mon, 19 May 2014 14:50:16 +0200 Subject: minor: fix tests --- app/controllers/v1/smtp_certs_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/controllers/v1/smtp_certs_controller.rb b/app/controllers/v1/smtp_certs_controller.rb index 258b391..533a19a 100644 --- a/app/controllers/v1/smtp_certs_controller.rb +++ b/app/controllers/v1/smtp_certs_controller.rb @@ -3,7 +3,7 @@ class V1::SmtpCertsController < ApplicationController before_filter :require_login before_filter :require_email_account - # GET /cert + # GET /1/smtp_cert def show @cert = ClientCertificate.new prefix: current_user.email_address current_user.identity.cert_fingerprints << @cert.fingerprint -- cgit v1.2.3 From 3a84578cf33685800c9216cfb4da12ea1fb0032f Mon Sep 17 00:00:00 2001 From: Azul Date: Mon, 19 May 2014 15:07:02 +0200 Subject: store fingerprints with timestamp Only storing the date as that should suffice for normal expiry and is less useful for identifying users by timestamps --- app/controllers/v1/smtp_certs_controller.rb | 18 ++++++++++++++++-- app/models/identity.rb | 12 +++++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) (limited to 'app') diff --git a/app/controllers/v1/smtp_certs_controller.rb b/app/controllers/v1/smtp_certs_controller.rb index 533a19a..fcc00b8 100644 --- a/app/controllers/v1/smtp_certs_controller.rb +++ b/app/controllers/v1/smtp_certs_controller.rb @@ -2,22 +2,36 @@ class V1::SmtpCertsController < ApplicationController before_filter :require_login before_filter :require_email_account + before_filter :fetch_identity # GET /1/smtp_cert def show @cert = ClientCertificate.new prefix: current_user.email_address - current_user.identity.cert_fingerprints << @cert.fingerprint - current_user.identity.save + @identity.register_cert(@cert) + @identity.save render text: @cert.to_s, content_type: 'text/plain' end protected + # + # Filters + # + def require_email_account access_denied unless service_level.provides? 'email' end + def fetch_identity + @identity = current_user.identity + end + + # + # Helper methods + # + def service_level current_user.effective_service_level end + end diff --git a/app/models/identity.rb b/app/models/identity.rb index 2f8d4eb..a4225e7 100644 --- a/app/models/identity.rb +++ b/app/models/identity.rb @@ -8,7 +8,7 @@ class Identity < CouchRest::Model::Base property :address, LocalEmail property :destination, Email property :keys, HashWithIndifferentAccess - property :cert_fingerprints, [String] + property :cert_fingerprints, Hash validate :unique_forward validate :alias_available @@ -108,6 +108,16 @@ class Identity < CouchRest::Model::Base write_attribute('keys', keys.merge(type => key.to_s)) end + def cert_fingerprints + read_attribute('cert_fingerprints') || Hash.new + end + + def register_cert(cert) + today = DateTime.now.to_date.to_s + write_attribute 'cert_fingerprints', + cert_fingerprints.merge(cert.fingerprint => today) + end + # for LoginFormatValidation def login self.address.handle -- cgit v1.2.3 From d2f4bd40342675717c6681e4ce845c316468c8b1 Mon Sep 17 00:00:00 2001 From: elijah Date: Tue, 20 May 2014 13:33:30 -0700 Subject: better detection if price link should be shown in the footer --- app/helpers/core_helper.rb | 7 +++++++ app/views/layouts/_footer.html.haml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) (limited to 'app') diff --git a/app/helpers/core_helper.rb b/app/helpers/core_helper.rb index 4126906..46e8fa4 100644 --- a/app/helpers/core_helper.rb +++ b/app/helpers/core_helper.rb @@ -10,4 +10,11 @@ module CoreHelper render 'common/home_page_buttons' end + # + # returns true if the configured service levels contain a level with a price attached + # + def paid_service_level? + APP_CONFIG[:service_levels].present? && APP_CONFIG[:service_levels].detect{|k,v| v['rate'].present?} + end + end diff --git a/app/views/layouts/_footer.html.haml b/app/views/layouts/_footer.html.haml index 340d36c..de53667 100644 --- a/app/views/layouts/_footer.html.haml +++ b/app/views/layouts/_footer.html.haml @@ -6,5 +6,5 @@ = link_to icon('info-sign') + t(:about), about_path - if lookup_context.exists?('pages/contact') = link_to icon('comment') + t(:contact), contact_path - - if APP_CONFIG[:service_levels].present? + - if paid_service_level? = link_to icon('shopping-cart') + t(:pricing), pricing_path -- cgit v1.2.3 From 00d5adc90ccadc7f4a2a0d54a5a31a1ad02f05be Mon Sep 17 00:00:00 2001 From: Azul Date: Mon, 26 May 2014 09:31:36 +0200 Subject: change from GET to POST for certs We create them. let's reflect that in the verb. --- app/controllers/v1/certs_controller.rb | 8 ++++++++ app/controllers/v1/smtp_certs_controller.rb | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/controllers/v1/certs_controller.rb b/app/controllers/v1/certs_controller.rb index 73409ef..b6d1d0b 100644 --- a/app/controllers/v1/certs_controller.rb +++ b/app/controllers/v1/certs_controller.rb @@ -3,7 +3,15 @@ class V1::CertsController < ApplicationController before_filter :require_login, :unless => :anonymous_certs_allowed? # GET /cert + # deprecated - we actually create a new cert and that can + # be reflected in the action. GET /cert will eventually go + # away and be replaced by POST /cert def show + create + end + + # POST /cert + def create @cert = ClientCertificate.new(:prefix => service_level.cert_prefix) render text: @cert.to_s, content_type: 'text/plain' end diff --git a/app/controllers/v1/smtp_certs_controller.rb b/app/controllers/v1/smtp_certs_controller.rb index fcc00b8..377a49c 100644 --- a/app/controllers/v1/smtp_certs_controller.rb +++ b/app/controllers/v1/smtp_certs_controller.rb @@ -4,8 +4,8 @@ class V1::SmtpCertsController < ApplicationController before_filter :require_email_account before_filter :fetch_identity - # GET /1/smtp_cert - def show + # POST /1/smtp_cert + def create @cert = ClientCertificate.new prefix: current_user.email_address @identity.register_cert(@cert) @identity.save -- cgit v1.2.3 From 5764daae090227bf4c5967900b708392c967be47 Mon Sep 17 00:00:00 2001 From: Azul Date: Thu, 1 May 2014 10:45:57 +0200 Subject: hash token with sha512 against timing attacs #3398 --- .../controller_extension/token_authentication.rb | 4 ++-- app/models/token.rb | 13 +++++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) (limited to 'app') diff --git a/app/controllers/controller_extension/token_authentication.rb b/app/controllers/controller_extension/token_authentication.rb index 6e0a6ce..b0ed624 100644 --- a/app/controllers/controller_extension/token_authentication.rb +++ b/app/controllers/controller_extension/token_authentication.rb @@ -2,8 +2,8 @@ module ControllerExtension::TokenAuthentication extend ActiveSupport::Concern def token - @token ||= authenticate_with_http_token do |token_id, options| - Token.find(token_id) + @token ||= authenticate_with_http_token do |token, options| + Token.find_by_token(token) end end diff --git a/app/models/token.rb b/app/models/token.rb index e759ee3..ff2ad12 100644 --- a/app/models/token.rb +++ b/app/models/token.rb @@ -1,3 +1,5 @@ +require 'digest/sha2' + class Token < CouchRest::Model::Base use_database :tokens @@ -11,10 +13,16 @@ class Token < CouchRest::Model::Base validates :user_id, presence: true + attr_accessor :token + design do view :by_last_seen_at end + def self.find_by_token(token) + self.find Digest::SHA512.hexdigest(token) + end + def self.expires_after APP_CONFIG[:auth] && APP_CONFIG[:auth][:token_expires_after] end @@ -31,7 +39,7 @@ class Token < CouchRest::Model::Base end def to_s - id + token end def authenticate @@ -65,7 +73,8 @@ class Token < CouchRest::Model::Base def initialize(*args) super if new_record? - self.id = SecureRandom.urlsafe_base64(32).gsub(/^_*/, '') + self.token = SecureRandom.urlsafe_base64(32).gsub(/^_*/, '') + self.id = Digest::SHA512.hexdigest(self.token) self.last_seen_at = Time.now end end -- cgit v1.2.3 From 730e31017109994c24db431fde12f575ed5c1467 Mon Sep 17 00:00:00 2001 From: Azul Date: Tue, 20 May 2014 09:13:25 +0200 Subject: FlashResponder will automagically add flash messages --- app/views/layouts/_messages.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/views/layouts/_messages.html.haml b/app/views/layouts/_messages.html.haml index 7ff985f..18be54f 100644 --- a/app/views/layouts/_messages.html.haml +++ b/app/views/layouts/_messages.html.haml @@ -1,6 +1,6 @@ #messages - flash.each do |name, msg| - if msg.is_a?(String) - %div{:class => "alert alert-#{name == :notice ? "success" : "error"}"} + %div{:class => "alert alert-#{name == :notice ? "success" : name}"} %a.close{"data-dismiss" => "alert"} × = content_tag :div, format_flash(msg), :id => "flash_#{name}" -- cgit v1.2.3 From c10f9311678ff2183443bc03e153b30d3b68ff74 Mon Sep 17 00:00:00 2001 From: Azul Date: Tue, 20 May 2014 13:09:59 +0200 Subject: Controller#flash_for instead of FlashResponder FlashResponder added a flash before responding. However at the point of responding objects have already been saved. So there is no way to test if they were changed. Now instead we can call flash_for resource before resource.save and it will add the flash messages only if the resource was actually changed. --- app/controllers/controller_extension/flash.rb | 33 +++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 app/controllers/controller_extension/flash.rb (limited to 'app') diff --git a/app/controllers/controller_extension/flash.rb b/app/controllers/controller_extension/flash.rb new file mode 100644 index 0000000..6a62351 --- /dev/null +++ b/app/controllers/controller_extension/flash.rb @@ -0,0 +1,33 @@ +module ControllerExtension::Flash + extend ActiveSupport::Concern + + protected + + def flash_for(resource, options = {}) + return unless resource.changed? + message = flash_message_for(resource) + type = flash_type(resource) + if message.present? + flash[type] = [message, flash[type]].join(' ') + end + end + + def flash_message_for(resource) + I18n.t flash_i18n_key(resource), + scope: :flash, + cascade: true, + resource: resource.class.model_name.human + end + + def flash_i18n_key(resource) + namespace = [action_name] + namespace += controller_path.split('/') + namespace << flash_type(resource) + namespace.join(".") + end + + def flash_type(resource) + resource.valid? ? :success : :error + end + +end -- cgit v1.2.3 From a337088f4d6d12d1ea26f494f4ca078cff4b4070 Mon Sep 17 00:00:00 2001 From: Azul Date: Tue, 20 May 2014 13:20:25 +0200 Subject: remove unused bold helper and instead sanitize flash --- app/controllers/application_controller.rb | 10 ---------- app/helpers/application_helper.rb | 3 ++- 2 files changed, 2 insertions(+), 11 deletions(-) (limited to 'app') diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 35d6cb4..a4560e2 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -23,16 +23,6 @@ class ApplicationController < ActionController::Base json: {error: "The server failed to process your request. We'll look into it."} end - # - # Allows us to pass through bold text to flash messages. See format_flash() for where this is reversed. - # - # TODO: move to core - # - def bold(str) - "[b]#{str}[/b]" - end - helper_method :bold - ## ## LOCALE ## diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 90e649a..6de5e1b 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -40,8 +40,9 @@ module ApplicationHelper end end + # fairly strict sanitation for flash messages def format_flash(msg) - html_escape(msg).gsub('[b]', '').gsub('[/b]', '').html_safe + sanitize(msg, tags: %w(em strong b br), attributes: []) end end -- cgit v1.2.3 From 560eb039f4778257559395583e1233d052d44127 Mon Sep 17 00:00:00 2001 From: Azul Date: Tue, 20 May 2014 13:50:32 +0200 Subject: flash_for with_errors option displays error messages --- app/controllers/controller_extension/flash.rb | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) (limited to 'app') diff --git a/app/controllers/controller_extension/flash.rb b/app/controllers/controller_extension/flash.rb index 6a62351..8bc9ee7 100644 --- a/app/controllers/controller_extension/flash.rb +++ b/app/controllers/controller_extension/flash.rb @@ -5,10 +5,15 @@ module ControllerExtension::Flash def flash_for(resource, options = {}) return unless resource.changed? + add_flash_message_for resource + add_flash_errors_for resource if options[:with_errors] + end + + def add_flash_message_for(resource) message = flash_message_for(resource) - type = flash_type(resource) + type = flash_type_for(resource) if message.present? - flash[type] = [message, flash[type]].join(' ') + flash[type] = message end end @@ -22,12 +27,17 @@ module ControllerExtension::Flash def flash_i18n_key(resource) namespace = [action_name] namespace += controller_path.split('/') - namespace << flash_type(resource) + namespace << flash_type_for(resource) namespace.join(".") end - def flash_type(resource) + def flash_type_for(resource) resource.valid? ? :success : :error end + def add_flash_errors_for(resource) + return if resource.valid? + flash[:error] += "
" + flash[:error] += resource.errors.full_messages.join(".
") + end end -- cgit v1.2.3 From 19bce0f114180f355f0df367cf6d21bd957734a6 Mon Sep 17 00:00:00 2001 From: Azul Date: Thu, 22 May 2014 14:57:29 +0200 Subject: tickets: structure i18n --- app/views/layouts/_header.html.haml | 2 +- app/views/layouts/_navigation.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/views/layouts/_header.html.haml b/app/views/layouts/_header.html.haml index a1dd47a..fbc46b3 100644 --- a/app/views/layouts/_header.html.haml +++ b/app/views/layouts/_header.html.haml @@ -4,7 +4,7 @@ %li{:class => ("active" if controller?('users', 'overviews') || params[:user_id])} = link_to t(:users), users_path %li{:class => ("active" if controller?('tickets') && !params[:user_id])} - = link_to t(:tickets), tickets_path + = link_to t(".tickets", cascade: true), tickets_path %li = link_to t(:logout), logout_path, :method => :delete - if @user && @show_navigation diff --git a/app/views/layouts/_navigation.html.haml b/app/views/layouts/_navigation.html.haml index b81c43d..94f71f7 100644 --- a/app/views/layouts/_navigation.html.haml +++ b/app/views/layouts/_navigation.html.haml @@ -2,6 +2,6 @@ = link_to_navigation t(:overview), @user, :active => (controller?(:users) && action?(:show)) = link_to_navigation t(:account_settings), edit_user_path(@user), :active => (controller?(:users) && !action?(:show)) - # will want link for identity settings - = link_to_navigation t(:support_tickets), auto_tickets_path, :active => controller?(:tickets) + = link_to_navigation t(".tickets"), auto_tickets_path, :active => controller?(:tickets) = link_to_navigation t(:billing_settings), billing_top_link(@user), :active => controller?(:customer, :payments, :subscriptions, :credit_card_info) if APP_CONFIG[:billing] = link_to_navigation t(:logout), logout_path, :method => :delete -- cgit v1.2.3 From 4085e3fabef6427cd3f8be9b61c209bd82eaa595 Mon Sep 17 00:00:00 2001 From: Azul Date: Sat, 24 May 2014 10:33:31 +0200 Subject: navigation works with empty locale selected Just in case some translation keys are not present things should still work and make sense. So translation keys should be picked in a meaningful way and scoped rather than prefixed. For example overview.account will turn into "Account" if no translation is present while "overview_account" will turn into "Overview Account". We usually want the former. --- app/views/common/_action_buttons.html.haml | 8 +++---- app/views/common/_download_button.html.haml | 2 +- app/views/users/_overview.html.haml | 24 +++++++++++++++++++ app/views/users/new.html.haml | 2 +- app/views/users/show.html.haml | 37 ++++++----------------------- 5 files changed, 37 insertions(+), 36 deletions(-) create mode 100644 app/views/users/_overview.html.haml (limited to 'app') diff --git a/app/views/common/_action_buttons.html.haml b/app/views/common/_action_buttons.html.haml index c74fcd1..d00cf74 100644 --- a/app/views/common/_action_buttons.html.haml +++ b/app/views/common/_action_buttons.html.haml @@ -2,10 +2,10 @@ .row-fluid.second .login.span4 %span.link= link_to(icon('ok-sign', icon_color) + t(:login), login_path, :class => 'btn') - %span.info= t(:login_info) + %span.info= t(:login_info, default: "") .signup.span4 %span.link= link_to(icon('user', icon_color) + t(:signup), signup_path, :class => 'btn') - %span.info= t(:signup_info) + %span.info= t(:signup_info, default: "") .help.span4 - %span.link= link_to(icon('question-sign', icon_color) + t(:get_help), new_ticket_path, :class => 'btn') - %span.info= t(:help_info) + %span.link= link_to(icon('question-sign', icon_color) + t(:support_tickets), new_ticket_path, :class => 'btn') + %span.info= t(:support_info, default: "") diff --git a/app/views/common/_download_button.html.haml b/app/views/common/_download_button.html.haml index e57c56b..d6d7bde 100644 --- a/app/views/common/_download_button.html.haml +++ b/app/views/common/_download_button.html.haml @@ -4,5 +4,5 @@ .download.span8 = link_to client_download_url, class: "btn btn-large btn-primary" do = big_icon('download') - = t(:download_client) + = t(:download_bitmask) .span2 diff --git a/app/views/users/_overview.html.haml b/app/views/users/_overview.html.haml new file mode 100644 index 0000000..e38fdc8 --- /dev/null +++ b/app/views/users/_overview.html.haml @@ -0,0 +1,24 @@ +.overview + + %h2.first= t(".welcome", username: @user.login, cascade: true) + + - if admin? + %p + = t(:created) + = @user.created_at + %br + = t(:updated) + = @user.updated_at + %br + = t(:enabled) + = @user.enabled? + + %p= t(:overview_intro, default: "") + + %ul.unstyled + %li= icon('user') + link_to(t(".account"), edit_user_path(@user)) + - # %li= icon('envelope') + link_to(t(:overview_email), {insert path for user identities, presuambly} + %li= icon('question-sign') + link_to(t(".tickets"), user_tickets_path(@user)) + %li= icon('shopping-cart') + link_to(t(".billing"), billing_top_link(@user)) if APP_CONFIG[:billing] + + diff --git a/app/views/users/new.html.haml b/app/views/users/new.html.haml index bc36068..41a9d55 100644 --- a/app/views/users/new.html.haml +++ b/app/views/users/new.html.haml @@ -17,5 +17,5 @@ = f.input :login, :label => t(:username), :required => false, :input_html => { :id => :srp_username } = f.input :password, :required => false, :validate => true, :input_html => { :id => :srp_password } = f.input :password_confirmation, :required => false, :validate => true, :input_html => { :id => :srp_password_confirmation } - = f.button :wrapped, value: t(:signup), cancel: home_path + = f.button :wrapped, cancel: home_path diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index ddc33ab..6760099 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -1,30 +1,7 @@ -.overview - - %h2.first= t(:overview_welcome, :username => @user.login) - - - if admin? - %p - = t(:created) - = @user.created_at - %br - = t(:updated) - = @user.updated_at - %br - = t(:enabled) - = @user.enabled? - - %p= t(:overview_intro) - - %ul.unstyled - %li= icon('user') + link_to(t(:overview_account), edit_user_path(@user)) - - # %li= icon('envelope') + link_to(t(:overview_email), {insert path for user identities, presuambly} - %li= icon('question-sign') + link_to(t(:overview_tickets), user_tickets_path(@user)) - %li= icon('shopping-cart') + link_to(t(:overview_billing), billing_top_link(@user)) if APP_CONFIG[:billing] - - - .container-fluid - .row-fluid - %h4 To use bitmask services: - = link_to client_download_url, class: "btn btn-primary" do - %i.icon-arrow-down.icon-white - = t(:download_client) += render 'overview' +.container-fluid + .row-fluid + %h4 To use bitmask services: + = link_to client_download_url, class: "btn btn-primary" do + %i.icon-arrow-down.icon-white + = t(:download_bitmask) -- cgit v1.2.3 From bbeb4b629dc38d82b3b3200706dd25b8def8892e Mon Sep 17 00:00:00 2001 From: Azul Date: Sat, 24 May 2014 13:39:10 +0200 Subject: sorting translation keys some --- app/views/errors/not_found.html.haml | 6 +++--- app/views/errors/server_error.html.haml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'app') diff --git a/app/views/errors/not_found.html.haml b/app/views/errors/not_found.html.haml index 75cb889..c7fec22 100644 --- a/app/views/errors/not_found.html.haml +++ b/app/views/errors/not_found.html.haml @@ -1,7 +1,7 @@ .hero-unit - %h1=t :not_found_title - %h2=t :not_found_subtitle - %p.lead=t :not_found_lead + %h1=t 'errors.title.page_not_found' + %h2=t 'errors.subtitle.page_not_found', default: '' + %p.lead=t 'errors.text.page_not_found', default: '' %a.btn.btn-primary.btn-large{href:'/'} %i.icon-home.icon-white =t :home diff --git a/app/views/errors/server_error.html.haml b/app/views/errors/server_error.html.haml index 68baf20..a4133da 100644 --- a/app/views/errors/server_error.html.haml +++ b/app/views/errors/server_error.html.haml @@ -1,7 +1,7 @@ .hero-unit - %h1=t :server_error_title - %h2=t :server_error_subtitle - %p.lead=t :server_error_lead + %h1=t 'errors.title.server_error' + %h2=t 'errors.subtitle.server_error', default: '' + %p.lead=t 'errors.text.server_error', default: '' %a.btn.btn-primary.btn-large{href:'/'} %i.icon-home.icon-white =t :home -- cgit v1.2.3 From 863863ff1fb6c9ab13b44248417ae1c5e57987af Mon Sep 17 00:00:00 2001 From: Azul Date: Tue, 27 May 2014 10:14:40 +0200 Subject: remove icon_color variable - yagni --- app/views/common/_action_buttons.html.haml | 6 +++--- app/views/common/_home_page_buttons.html.haml | 4 +--- 2 files changed, 4 insertions(+), 6 deletions(-) (limited to 'app') diff --git a/app/views/common/_action_buttons.html.haml b/app/views/common/_action_buttons.html.haml index d00cf74..398d384 100644 --- a/app/views/common/_action_buttons.html.haml +++ b/app/views/common/_action_buttons.html.haml @@ -1,11 +1,11 @@ .home-buttons .row-fluid.second .login.span4 - %span.link= link_to(icon('ok-sign', icon_color) + t(:login), login_path, :class => 'btn') + %span.link= link_to icon('ok-sign') + t(:login), login_path, :class => 'btn' %span.info= t(:login_info, default: "") .signup.span4 - %span.link= link_to(icon('user', icon_color) + t(:signup), signup_path, :class => 'btn') + %span.link= link_to icon('user') + t(:signup), signup_path, :class => 'btn' %span.info= t(:signup_info, default: "") .help.span4 - %span.link= link_to(icon('question-sign', icon_color) + t(:support_tickets), new_ticket_path, :class => 'btn') + %span.link= link_to icon('question-sign') + t(:get_help), new_ticket_path, :class => 'btn' %span.info= t(:support_info, default: "") diff --git a/app/views/common/_home_page_buttons.html.haml b/app/views/common/_home_page_buttons.html.haml index 8c47983..fc6348e 100644 --- a/app/views/common/_home_page_buttons.html.haml +++ b/app/views/common/_home_page_buttons.html.haml @@ -1,8 +1,6 @@ -- icon_color = :black - = render 'common/download_button' - if local_assigns[:divider] .row-fluid .span12 = render local_assigns[:divider] -= render 'common/action_buttons', icon_color: icon_color += render 'common/action_buttons' -- cgit v1.2.3 From cc59ce53e52bf48d97de16d66012e8309bf98fe8 Mon Sep 17 00:00:00 2001 From: Azul Date: Tue, 27 May 2014 16:32:50 +0200 Subject: add btn helper for link_to with .btn Also translates the first arg if it's a symbol and adds more btn- classes if given as html_options[:type] --- app/views/common/_action_buttons.html.haml | 6 +++--- app/views/common/_download_button.html.haml | 2 +- app/views/pages/pricing.html.haml | 2 +- app/views/users/_destroy_account.html.haml | 6 +++--- app/views/users/show.html.haml | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) (limited to 'app') diff --git a/app/views/common/_action_buttons.html.haml b/app/views/common/_action_buttons.html.haml index 398d384..266abe1 100644 --- a/app/views/common/_action_buttons.html.haml +++ b/app/views/common/_action_buttons.html.haml @@ -1,11 +1,11 @@ .home-buttons .row-fluid.second .login.span4 - %span.link= link_to icon('ok-sign') + t(:login), login_path, :class => 'btn' + %span.link= btn icon('ok-sign') + t(:login), login_path %span.info= t(:login_info, default: "") .signup.span4 - %span.link= link_to icon('user') + t(:signup), signup_path, :class => 'btn' + %span.link= btn icon('user') + t(:signup), signup_path %span.info= t(:signup_info, default: "") .help.span4 - %span.link= link_to icon('question-sign') + t(:get_help), new_ticket_path, :class => 'btn' + %span.link= btn icon('question-sign') + t(:get_help), new_ticket_path %span.info= t(:support_info, default: "") diff --git a/app/views/common/_download_button.html.haml b/app/views/common/_download_button.html.haml index d6d7bde..9c26860 100644 --- a/app/views/common/_download_button.html.haml +++ b/app/views/common/_download_button.html.haml @@ -2,7 +2,7 @@ .row-fluid.first .span2 .download.span8 - = link_to client_download_url, class: "btn btn-large btn-primary" do + = btn client_download_url, type: [:large, :primary] do = big_icon('download') = t(:download_bitmask) .span2 diff --git a/app/views/pages/pricing.html.haml b/app/views/pages/pricing.html.haml index e339d27..983501e 100644 --- a/app/views/pages/pricing.html.haml +++ b/app/views/pages/pricing.html.haml @@ -1,5 +1,5 @@ %h1= t(:pricing) -%p= link_to(icon('user') + t(:signup), signup_path, :class => 'btn') +%p= btn icon('user') + t(:signup), signup_path - levels = APP_CONFIG[:service_levels] - if levels diff --git a/app/views/users/_destroy_account.html.haml b/app/views/users/_destroy_account.html.haml index 445f3c4..be003ce 100644 --- a/app/views/users/_destroy_account.html.haml +++ b/app/views/users/_destroy_account.html.haml @@ -8,20 +8,20 @@ - else = t(:admin_destroy_account, :username => @user.login) %p= t(:destroy_account_info) -= link_to user_path(@user), :method => :delete, :confirm => t(:are_you_sure), :class => "btn btn-danger" do += btn user_path(@user), :method => :delete, :confirm => t(:are_you_sure), :type => "danger" do %i.icon-remove.icon-white = t(:destroy_my_account) - if @user != current_user and @user.enabled? %legend = t(:deactivate_account, :username => @user.login) %p= t(:deactivate_description) - = link_to deactivate_user_path(@user), :method => :post, :class => "btn btn-warning" do + = btn deactivate_user_path(@user), :method => :post, :type => "warning" do %i.icon-pause.icon-white = t(:deactivate) - elsif @user != current_user and !@user.enabled? %legend = t(:enable_account, :username => @user.login) %p= t(:enable_description) - = link_to enable_user_path(@user), :method => :post, :class => "btn btn-warning" do + = btn enable_user_path(@user), :method => :post, :type => "warning" do %i.icon-ok.icon-white = t(:enable) diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 6760099..da8e467 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -2,6 +2,6 @@ .container-fluid .row-fluid %h4 To use bitmask services: - = link_to client_download_url, class: "btn btn-primary" do + = btn client_download_url, type: "primary" do %i.icon-arrow-down.icon-white = t(:download_bitmask) -- cgit v1.2.3 From 8ca32588c969ee9eca986da8cf1de92b39ce3576 Mon Sep 17 00:00:00 2001 From: Azul Date: Tue, 27 May 2014 17:52:26 +0200 Subject: move users key into layouts scope so it does not conflict with users scope --- app/views/layouts/_header.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/views/layouts/_header.html.haml b/app/views/layouts/_header.html.haml index fbc46b3..e827f60 100644 --- a/app/views/layouts/_header.html.haml +++ b/app/views/layouts/_header.html.haml @@ -2,7 +2,7 @@ %ul.nav.nav-tabs = # this navigation isn't quite right. also, we will want to active for an identity controller once it is added. %li{:class => ("active" if controller?('users', 'overviews') || params[:user_id])} - = link_to t(:users), users_path + = link_to t(".users"), users_path %li{:class => ("active" if controller?('tickets') && !params[:user_id])} = link_to t(".tickets", cascade: true), tickets_path %li -- cgit v1.2.3 From df1c2438fcfe39edfb46546be8fcee5021f95fc3 Mon Sep 17 00:00:00 2001 From: Azul Date: Wed, 28 May 2014 09:26:17 +0200 Subject: destroy_btn helper method --- app/helpers/link_helper.rb | 49 ++++++++++++++++++++++++++++++ app/views/users/_destroy_account.html.haml | 2 +- 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 app/helpers/link_helper.rb (limited to 'app') diff --git a/app/helpers/link_helper.rb b/app/helpers/link_helper.rb new file mode 100644 index 0000000..55e392b --- /dev/null +++ b/app/helpers/link_helper.rb @@ -0,0 +1,49 @@ +module LinkHelper + + # + # markup for bootstrap button + # + # takes same arguments as link_to and adds a 'btn' class. + # In addition: + # * the name will be translated if it is a symbol + # * html_options[:type] will be converted into a btn-type class + # + # example: + # btn :home, home_path, type: [:large, :primary] + # + def btn(*args, &block) + html_options = extract_html_options!(args, &block) + type = Array(html_options.delete(:type)) + type.map! {|t| "btn-#{t}"} + html_options[:class] = concat_classes(html_options[:class], 'btn', type) + args[0] = t(args[0]) if args[0].is_a?(Symbol) + link_to *args, html_options, &block + end + + def destroy_btn(*args, &block) + html_options = extract_html_options!(args, &block) + confirmation = t "#{controller_symbol}.confirm.destroy.are_you_sure", + cascade: true + html_options.merge! method: :delete, confirm: confirmation + btn *args, html_options, &block + end + + # + # concat_classes will combine classes in a fairly flexible way. + # it can handle nil, arrays, space separated strings + # it returns a space separated string of classes. + def concat_classes(*classes) + classes.compact! + classes.map {|c| c.respond_to?(:split) ? c.split(' ') : c } + classes.flatten! + classes.join ' ' + end + + def extract_html_options!(args) + if args.count > 2 or args.count > 1 && block_given? + args.extract_options! + else + {} + end + end +end diff --git a/app/views/users/_destroy_account.html.haml b/app/views/users/_destroy_account.html.haml index be003ce..a2c4ddd 100644 --- a/app/views/users/_destroy_account.html.haml +++ b/app/views/users/_destroy_account.html.haml @@ -8,7 +8,7 @@ - else = t(:admin_destroy_account, :username => @user.login) %p= t(:destroy_account_info) -= btn user_path(@user), :method => :delete, :confirm => t(:are_you_sure), :type => "danger" do += destroy_btn user_path(@user), :type => "danger" do %i.icon-remove.icon-white = t(:destroy_my_account) - if @user != current_user and @user.enabled? -- cgit v1.2.3 From 154d32bbc7cfe21d83141ff2c9a3d805165231b8 Mon Sep 17 00:00:00 2001 From: Azul Date: Wed, 28 May 2014 10:45:14 +0200 Subject: use Identity for testing login availability We create an identity alongside each user. Make sure the identity is valid when creating the user. This also ensures that the login picked is available because otherwise the identities address would not be available anymore. --- app/models/identity.rb | 30 ++++++++++++------------------ app/models/user.rb | 13 ++++++------- 2 files changed, 18 insertions(+), 25 deletions(-) (limited to 'app') diff --git a/app/models/identity.rb b/app/models/identity.rb index a4225e7..2be396c 100644 --- a/app/models/identity.rb +++ b/app/models/identity.rb @@ -10,8 +10,9 @@ class Identity < CouchRest::Model::Base property :keys, HashWithIndifferentAccess property :cert_fingerprints, Hash - validate :unique_forward validate :alias_available + validates :destination, presence: true, + uniqueness: {scope: :address} validate :address_local_email validate :destination_email @@ -44,13 +45,12 @@ class Identity < CouchRest::Model::Base end - def self.for(user, attributes = {}) - find_for(user, attributes) || build_for(user, attributes) + def self.for(user) + find_for(user) || build_for(user) end - def self.find_for(user, attributes = {}) - attributes.reverse_merge! attributes_from_user(user) - find_by_address_and_destination [attributes[:address], attributes[:destination]] + def self.find_for(user) + find_by_user_id(user.id) if user && user.persisted? end def self.build_for(user, attributes = {}) @@ -125,23 +125,17 @@ class Identity < CouchRest::Model::Base protected - def unique_forward - same = Identity.find_by_address_and_destination([address, destination]) - if same && same != self - errors.add :base, "This alias already exists" - end - end - def alias_available - same = Identity.find_by_address(address) - if same && same.user != self.user - errors.add :base, "This email has already been taken" + same_address = Identity.by_address.key(address) + if same_address.detect { |other| other.user !=self.user } + errors.add :address, :taken end end def address_local_email - return if address.valid? #this ensures it is LocalEmail - self.errors.add(:address, address.errors.messages[:email].first) #assumes only one error + return if address.valid? + # we only hand on the first error for now. + self.errors.add(:address, address.errors.messages.values.first) end def destination_email diff --git a/app/models/user.rb b/app/models/user.rb index 6678de6..6b4d1a9 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -24,7 +24,7 @@ class User < CouchRest::Model::Base :uniqueness => true, :if => :serverside? - validate :login_is_unique_alias + validate :identity_is_valid validates :password_salt, :password_verifier, :format => { :with => /\A[\dA-Fa-f]+\z/, :message => "Only hex numbers allowed" } @@ -161,12 +161,11 @@ class User < CouchRest::Model::Base # Validation Functions ## - def login_is_unique_alias - alias_identity = Identity.find_by_address(self.email_address) - return if alias_identity.blank? - if alias_identity.user != self - errors.add(:login, "has already been taken") - end + def identity_is_valid + refresh_identity + return if identity.valid? + # hand on the first error only for now + self.errors.add(:login, identity.errors.messages.values.first) end def password -- cgit v1.2.3 From 5c8ab9298cc4705de508a3f3f9d9d6370a01ff5e Mon Sep 17 00:00:00 2001 From: Azul Date: Wed, 28 May 2014 11:43:50 +0200 Subject: minor: beautify handle lookup in etc/passwd some --- app/models/local_email.rb | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'app') diff --git a/app/models/local_email.rb b/app/models/local_email.rb index 2b4c65e..ded7baf 100644 --- a/app/models/local_email.rb +++ b/app/models/local_email.rb @@ -58,11 +58,9 @@ class LocalEmail < Email end def handle_in_passwd? - begin - !!Etc.getpwnam(handle) - rescue ArgumentError - # handle was not found - return false - end + Etc.getpwnam(handle).present? + rescue ArgumentError + # handle was not found + return false end end -- cgit v1.2.3 From 682b4060cb86c52ffda638f4f9a837f107540610 Mon Sep 17 00:00:00 2001 From: Azul Date: Wed, 28 May 2014 11:44:12 +0200 Subject: ensure identity is cleared on user.reload - fixes test --- app/models/pgp_key.rb | 3 ++- app/models/user.rb | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) (limited to 'app') diff --git a/app/models/pgp_key.rb b/app/models/pgp_key.rb index 66f8660..3384f4c 100644 --- a/app/models/pgp_key.rb +++ b/app/models/pgp_key.rb @@ -25,9 +25,10 @@ class PgpKey # allow comparison with plain keyblock strings. def ==(other) + return false if (self.present? != other.present?) self.equal?(other) or # relax the comparison on line ends. - self.to_s.tr_s("\n\r", '') == other.tr_s("\r\n", '') + self.to_s.tr_s("\n\r", '') == other.tr_s("\n\r", '') end protected diff --git a/app/models/user.rb b/app/models/user.rb index 6b4d1a9..33508b5 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -42,6 +42,11 @@ class User < CouchRest::Model::Base view :by_created_at end # end of design + def reload + super + @identity = nil + end + def to_json(options={}) { :login => login, -- cgit v1.2.3 From 6fea83763f07add7d3bd07e3843b75aaf61e19b4 Mon Sep 17 00:00:00 2001 From: Azul Date: Wed, 28 May 2014 12:20:49 +0200 Subject: bring back the alias functionality in Identities --- app/models/account.rb | 1 + app/models/identity.rb | 23 +++++++++++++---------- 2 files changed, 14 insertions(+), 10 deletions(-) (limited to 'app') diff --git a/app/models/account.rb b/app/models/account.rb index cf998e4..bffa288 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -18,6 +18,7 @@ class Account def self.create(attrs) @user = User.create(attrs).tap do |user| Identity.create_for user + user.refresh_identity end end diff --git a/app/models/identity.rb b/app/models/identity.rb index 2be396c..0d25bae 100644 --- a/app/models/identity.rb +++ b/app/models/identity.rb @@ -11,8 +11,7 @@ class Identity < CouchRest::Model::Base property :cert_fingerprints, Hash validate :alias_available - validates :destination, presence: true, - uniqueness: {scope: :address} + validates :destination, uniqueness: {scope: :address} validate :address_local_email validate :destination_email @@ -45,12 +44,14 @@ class Identity < CouchRest::Model::Base end - def self.for(user) - find_for(user) || build_for(user) + def self.for(user, attributes = {}) + find_for(user, attributes) || build_for(user, attributes) end - def self.find_for(user) - find_by_user_id(user.id) if user && user.persisted? + def self.find_for(user, attributes = {}) + attributes.reverse_merge! attributes_from_user(user) + id = find_by_address_and_destination attributes.values_at(:address, :destination) + return id if id && id.user == user end def self.build_for(user, attributes = {}) @@ -67,7 +68,9 @@ class Identity < CouchRest::Model::Base def self.disable_all_for(user) Identity.by_user_id.key(user.id).each do |identity| identity.disable - identity.save + # if the identity is not unique anymore because the destination + # was reset to nil we destroy it. + identity.save || identity.destroy end end @@ -127,15 +130,15 @@ class Identity < CouchRest::Model::Base def alias_available same_address = Identity.by_address.key(address) - if same_address.detect { |other| other.user !=self.user } + if same_address.detect { |other| other.user != self.user } errors.add :address, :taken end end def address_local_email - return if address.valid? + return if address.valid? #this ensures it is a valid local email address # we only hand on the first error for now. - self.errors.add(:address, address.errors.messages.values.first) + self.errors.add(:address, address.errors.messages[:email].first) end def destination_email -- cgit v1.2.3 From 09dfa583eca69a3925c384c67c3d98cd8c69b360 Mon Sep 17 00:00:00 2001 From: Azul Date: Wed, 28 May 2014 12:28:07 +0200 Subject: allow changing the user_id on an identity we set it to nil when we disable it --- app/models/identity.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/models/identity.rb b/app/models/identity.rb index 0d25bae..a8eaba6 100644 --- a/app/models/identity.rb +++ b/app/models/identity.rb @@ -130,7 +130,7 @@ class Identity < CouchRest::Model::Base def alias_available same_address = Identity.by_address.key(address) - if same_address.detect { |other| other.user != self.user } + if same_address.detect { |other| other != self && other.user != self.user } errors.add :address, :taken end end -- cgit v1.2.3 From 016e61ce9ab44cf58355e843b0c0d0085d373fc7 Mon Sep 17 00:00:00 2001 From: Azul Date: Thu, 29 May 2014 09:38:53 +0200 Subject: catch corner cases of account creation Users now always check if their identity is valid. We need to make sure this works if the user is a new record and once it has been persisted. While the user is a new record the identity will have no user_id. Old identities that are left to block the login of a user who canceled their account also have a blank user_id. They still should render the new identity invalid so the user can't be saved with a login that has been reserved. Once the user has been persisted we set the user_id on the identity and save it too when creating an Account. This allows us to create a plain user and save it and it will still have an in memory identity only. But the default is to create the user by means of creating an account so an identity will be created as well. --- app/models/account.rb | 9 ++++++--- app/models/identity.rb | 13 +++++++++---- 2 files changed, 15 insertions(+), 7 deletions(-) (limited to 'app') diff --git a/app/models/account.rb b/app/models/account.rb index bffa288..32ed445 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -16,10 +16,13 @@ class Account # Returns the user record so it can be used in views. def self.create(attrs) - @user = User.create(attrs).tap do |user| - Identity.create_for user - user.refresh_identity + @user = User.create(attrs) + if @user.persisted? + identity = @user.identity + identity.user_id = @user.id + identity.save end + return @user end def update(attrs) diff --git a/app/models/identity.rb b/app/models/identity.rb index a8eaba6..25be971 100644 --- a/app/models/identity.rb +++ b/app/models/identity.rb @@ -129,8 +129,12 @@ class Identity < CouchRest::Model::Base protected def alias_available - same_address = Identity.by_address.key(address) - if same_address.detect { |other| other != self && other.user != self.user } + blocking_identities = Identity.by_address.key(address).all + blocking_identities.delete self + if self.user + blocking_identities.reject! { |other| other.user == self.user } + end + if blocking_identities.any? errors.add :address, :taken end end @@ -138,13 +142,14 @@ class Identity < CouchRest::Model::Base def address_local_email return if address.valid? #this ensures it is a valid local email address # we only hand on the first error for now. - self.errors.add(:address, address.errors.messages[:email].first) + self.errors.add(:address, address.errors.messages.values.first) end def destination_email return if destination.nil? # this identity is disabled return if destination.valid? # this ensures it is Email - self.errors.add(:destination, destination.errors.messages[:email].first) #assumes only one error #TODO + # we only hand on the first error for now. + self.errors.add(:destination, destination.errors.messages.values.first) end end -- cgit v1.2.3 From e0d31118d6e4110d2c280afa9415cfe9def29deb Mon Sep 17 00:00:00 2001 From: Azul Date: Thu, 29 May 2014 10:04:07 +0200 Subject: hand on errors from Email to Identity to User errors.each iterates through all errors for all attrbibutes nicely. --- app/models/identity.rb | 10 ++++++---- app/models/user.rb | 6 +++--- 2 files changed, 9 insertions(+), 7 deletions(-) (limited to 'app') diff --git a/app/models/identity.rb b/app/models/identity.rb index 25be971..f2727c8 100644 --- a/app/models/identity.rb +++ b/app/models/identity.rb @@ -141,15 +141,17 @@ class Identity < CouchRest::Model::Base def address_local_email return if address.valid? #this ensures it is a valid local email address - # we only hand on the first error for now. - self.errors.add(:address, address.errors.messages.values.first) + address.errors.each do |attribute, error| + self.errors.add(:address, error) + end end def destination_email return if destination.nil? # this identity is disabled return if destination.valid? # this ensures it is Email - # we only hand on the first error for now. - self.errors.add(:destination, destination.errors.messages.values.first) + destination.errors.each do |attribute, error| + self.errors.add(:destination, error) + end end end diff --git a/app/models/user.rb b/app/models/user.rb index 33508b5..84a795e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -167,10 +167,10 @@ class User < CouchRest::Model::Base ## def identity_is_valid - refresh_identity return if identity.valid? - # hand on the first error only for now - self.errors.add(:login, identity.errors.messages.values.first) + identity.errors.each do |attribute, error| + self.errors.add(:login, error) + end end def password -- cgit v1.2.3 From 85e066920568c19b788b8789c4659092224bb517 Mon Sep 17 00:00:00 2001 From: Azul Date: Thu, 29 May 2014 10:37:31 +0200 Subject: ensure User#reload returns self --- app/models/user.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/models/user.rb b/app/models/user.rb index 84a795e..f8b9ddc 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -43,8 +43,8 @@ class User < CouchRest::Model::Base end # end of design def reload - super @identity = nil + super end def to_json(options={}) -- cgit v1.2.3 From bbe7b3b7deb2b44d34f7c39dda2c3db284e2bf10 Mon Sep 17 00:00:00 2001 From: Azul Date: Thu, 29 May 2014 11:19:21 +0200 Subject: clearify identity validations Identity.new.valid? should not crash. So validate presence where needed and skip the other validations if the value is absent. --- app/models/identity.rb | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) (limited to 'app') diff --git a/app/models/identity.rb b/app/models/identity.rb index f2727c8..2f6241c 100644 --- a/app/models/identity.rb +++ b/app/models/identity.rb @@ -10,7 +10,9 @@ class Identity < CouchRest::Model::Base property :keys, HashWithIndifferentAccess property :cert_fingerprints, Hash - validate :alias_available + validates :address, presence: true + validate :address_available + validates :destination, presence: true, if: :enabled? validates :destination, uniqueness: {scope: :address} validate :address_local_email validate :destination_email @@ -94,7 +96,11 @@ class Identity < CouchRest::Model::Base end def enabled? - self.destination && self.user_id + self.user_id + end + + def disabled? + !enabled? end def disable @@ -123,12 +129,12 @@ class Identity < CouchRest::Model::Base # for LoginFormatValidation def login - self.address.handle + address.handle if address.present? end protected - def alias_available + def address_available blocking_identities = Identity.by_address.key(address).all blocking_identities.delete self if self.user @@ -140,15 +146,18 @@ class Identity < CouchRest::Model::Base end def address_local_email - return if address.valid? #this ensures it is a valid local email address + # caught by presence validation + return if address.blank? + return if address.valid? address.errors.each do |attribute, error| self.errors.add(:address, error) end end def destination_email - return if destination.nil? # this identity is disabled - return if destination.valid? # this ensures it is Email + # caught by presence validation or this identity is disabled + return if destination.blank? + return if destination.valid? destination.errors.each do |attribute, error| self.errors.add(:destination, error) end -- cgit v1.2.3 From ab49a72b52575f3b9fdf13fee47e99dfb82e2a3d Mon Sep 17 00:00:00 2001 From: Azul Date: Thu, 29 May 2014 14:57:23 +0200 Subject: html5:
instead of
--- app/controllers/controller_extension/flash.rb | 4 ++-- app/views/pages/terms-of-service.en.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'app') diff --git a/app/controllers/controller_extension/flash.rb b/app/controllers/controller_extension/flash.rb index 8bc9ee7..1642141 100644 --- a/app/controllers/controller_extension/flash.rb +++ b/app/controllers/controller_extension/flash.rb @@ -37,7 +37,7 @@ module ControllerExtension::Flash def add_flash_errors_for(resource) return if resource.valid? - flash[:error] += "
" - flash[:error] += resource.errors.full_messages.join(".
") + flash[:error] += "
" + flash[:error] += resource.errors.full_messages.join(".
") end end diff --git a/app/views/pages/terms-of-service.en.md b/app/views/pages/terms-of-service.en.md index 7b57027..93490b7 100644 --- a/app/views/pages/terms-of-service.en.md +++ b/app/views/pages/terms-of-service.en.md @@ -3,8 +3,8 @@ This document is our Terms of Service, which describes what activities are allowed, under what conditions we may terminate your account, and asserts our limited liability. It applies to all interactions with **<%=APP_CONFIG[:domain]%>**. Your use of **<%=APP_CONFIG[:domain]%>** services will constitute your agreement to these Terms of Service.

- Summary:
- (1) If you do anything truly evil, we will terminate your account.
+ Summary:
+ (1) If you do anything truly evil, we will terminate your account.
(2) We are not liable for any damages related to the use of this service.

-- cgit v1.2.3