commit
cc64ab64ec
45 changed files with 666 additions and 99 deletions
1
app/assets/images/user/confirmation-email.svg
Normal file
1
app/assets/images/user/confirmation-email.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg width="81" height="86" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><g stroke-linecap="round" stroke-linejoin="round" stroke="#0069CC" stroke-width="2"><path d="M9.203 31.348L1.083 37h-.045L1 77.334C1 81.606 4.538 85 8.865 85h62.92c4.325 0 8.296-3.168 8.296-7.44V37m-.068.125L71 31"/><path d="M2.459 37.212l38.014 25.92 38.014-26.211M3.384 82.25L29.967 56m47.595 26.25L50.979 56"/></g><path d="M40.04 63.132L9.08 41.505V5.307a4 4 0 0 1 4-4H67a4 4 0 0 1 4 4v36.96L40.04 63.132z" stroke="#0069CC" stroke-width="2" fill="#EFF6FF" stroke-linecap="round" stroke-linejoin="round"/><path d="M40.87 45.53c-3.103 0-5.81-.63-8.12-1.89-2.31-1.26-4.09-3.022-5.337-5.285-1.249-2.263-1.873-4.877-1.873-7.84 0-3.197.665-5.973 1.995-8.33s3.156-4.165 5.477-5.425c2.322-1.26 4.953-1.89 7.893-1.89 3.057 0 5.682.59 7.875 1.767 2.193 1.179 3.856 2.76 4.987 4.743 1.132 1.983 1.698 4.165 1.698 6.545 0 2.147-.327 3.955-.98 5.425-.653 1.47-1.528 2.572-2.625 3.307a6.398 6.398 0 0 1-3.64 1.103c-1.26 0-2.263-.268-3.01-.805-.747-.537-1.19-1.283-1.33-2.24h-.07c-1.33 2.03-3.068 3.045-5.215 3.045-1.68 0-3.004-.566-3.973-1.698-.968-1.131-1.452-2.665-1.452-4.602 0-1.657.315-3.18.945-4.568.63-1.388 1.51-2.49 2.642-3.307 1.132-.817 2.421-1.225 3.868-1.225 1.003 0 1.89.227 2.66.682.77.456 1.295 1.068 1.575 1.838h.07l.385-2.17h3.255l-1.575 8.785c-.093.49-.14.992-.14 1.505 0 .747.157 1.29.472 1.627.316.339.811.508 1.488.508.537 0 1.08-.24 1.627-.718.549-.478 1.01-1.254 1.383-2.327s.56-2.462.56-4.165c0-1.937-.432-3.675-1.295-5.215s-2.158-2.765-3.885-3.675c-1.727-.91-3.827-1.365-6.3-1.365-2.31 0-4.38.507-6.213 1.522-1.831 1.016-3.272 2.491-4.322 4.428-1.05 1.937-1.575 4.235-1.575 6.895 0 2.45.484 4.59 1.452 6.422a10.232 10.232 0 0 0 4.165 4.253c1.809 1.003 3.961 1.505 6.458 1.505 1.843 0 3.424-.24 4.742-.718C46.931 41.5 47.917 40.84 48.57 40h4.235c-.957 1.68-2.467 3.022-4.533 4.025-2.065 1.003-4.532 1.505-7.402 1.505zm-1.575-10.395c1.027 0 1.913-.315 2.66-.945.747-.63 1.312-1.447 1.698-2.45a8.813 8.813 0 0 0 .577-3.185c0-1.167-.274-2.053-.822-2.66-.549-.607-1.301-.91-2.258-.91-.98 0-1.826.315-2.537.945-.712.63-1.255 1.44-1.628 2.433a8.624 8.624 0 0 0-.56 3.062c0 1.143.262 2.047.787 2.713.526.665 1.22.997 2.083.997z" fill="#0069CC"/></g></svg>
|
After Width: | Height: | Size: 2.2 KiB |
|
@ -15,4 +15,5 @@ $light-green: lighten($green, 25%);
|
|||
$dark-green: darken($green, 20%);
|
||||
$orange: #F28900;
|
||||
$orange-bg: lighten($orange, 35%);
|
||||
$yellow: #FEF3B8;
|
||||
$light-yellow: #FFFFDE;
|
||||
|
|
64
app/assets/stylesheets/new_design/confirmations.scss
Normal file
64
app/assets/stylesheets/new_design/confirmations.scss
Normal file
|
@ -0,0 +1,64 @@
|
|||
@import "colors";
|
||||
@import "constants";
|
||||
|
||||
.devise-confirmations {
|
||||
.one-column-centered {
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.confirmation-icon,
|
||||
.confirmation-preamble,
|
||||
.confirmation-instructions,
|
||||
.confirmation-separator {
|
||||
font-size: 1.15em;
|
||||
margin-bottom: $default-padding * 3;
|
||||
}
|
||||
|
||||
.confirmation-icon {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.confirmation-instructions {
|
||||
color: #000000;
|
||||
background-color: $yellow;
|
||||
margin-left: -15px;
|
||||
margin-right: -15px;
|
||||
padding: 15px 20px 17px 20px;
|
||||
}
|
||||
|
||||
.confirmation-separator {
|
||||
height: 1px;
|
||||
margin-left: -12px;
|
||||
margin-right: -12px;
|
||||
border: none;
|
||||
border-top: 1px solid #DDDDDD;
|
||||
}
|
||||
|
||||
.confirmation-resend {
|
||||
p {
|
||||
margin-bottom: $default-padding;
|
||||
}
|
||||
|
||||
.form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
input,
|
||||
button {
|
||||
margin-bottom: $default-spacer;
|
||||
}
|
||||
|
||||
input[type=email] {
|
||||
width: auto;
|
||||
flex-grow: 1;
|
||||
margin-right: $default-spacer;
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
30
app/assets/stylesheets/new_design/link-sent.scss
Normal file
30
app/assets/stylesheets/new_design/link-sent.scss
Normal file
|
@ -0,0 +1,30 @@
|
|||
@import "constants";
|
||||
@import "colors";
|
||||
|
||||
#link-sent {
|
||||
padding-top: 2 * $default-padding;
|
||||
padding-bottom: 2 * $default-padding;
|
||||
text-align: center;
|
||||
max-width: 600px;
|
||||
|
||||
b {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: left;
|
||||
margin: 6 * $default-spacer auto;
|
||||
}
|
||||
|
||||
p.mail {
|
||||
color: #000000;
|
||||
background-color: $yellow;
|
||||
padding: $default-padding;
|
||||
}
|
||||
|
||||
p.help {
|
||||
border-top: 1px solid $grey;
|
||||
padding-top: 6 * $default-spacer;
|
||||
margin-bottom: 2 * $default-spacer;
|
||||
}
|
||||
}
|
|
@ -22,7 +22,7 @@ footer {
|
|||
.footer-columns {
|
||||
@extend %horizontal-list;
|
||||
justify-content: flex-start;
|
||||
margin: 0 -20px;
|
||||
margin: 0 -15px;
|
||||
}
|
||||
|
||||
.footer-column {
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
.brouillon,
|
||||
.en-construction,
|
||||
.en-instruction {
|
||||
max-width: 600px;
|
||||
max-width: 650px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
require 'zxcvbn'
|
||||
|
||||
class Administrateurs::ActivateController < ApplicationController
|
||||
include TrustedDeviceConcern
|
||||
|
||||
layout "new_application"
|
||||
|
||||
def new
|
||||
@administrateur = Administrateur.find_inactive_by_token(params[:token])
|
||||
|
||||
if !@administrateur
|
||||
if @administrateur
|
||||
# the administrateur activates its account from an email
|
||||
trust_device
|
||||
else
|
||||
flash.alert = "Le lien de validation d'administrateur a expiré, #{helpers.contact_link('contactez-nous', tags: 'lien expiré')} pour obtenir un nouveau lien."
|
||||
redirect_to root_path
|
||||
end
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
class Gestionnaires::ActivateController < ApplicationController
|
||||
include TrustedDeviceConcern
|
||||
|
||||
layout "new_application"
|
||||
|
||||
def new
|
||||
@gestionnaire = Gestionnaire.with_reset_password_token(params[:token])
|
||||
|
||||
if !@gestionnaire
|
||||
if @gestionnaire
|
||||
# the gestionnaire activates its account from an email
|
||||
trust_device
|
||||
else
|
||||
flash.alert = "Le lien de validation du compte instructeur a expiré, #{helpers.contact_link('contactez-nous', tags: 'lien expiré')} pour obtenir un nouveau lien."
|
||||
redirect_to root_path
|
||||
end
|
||||
|
|
|
@ -6,5 +6,19 @@ module Manager
|
|||
flash[:notice] = "Instructeur réinvité."
|
||||
redirect_to manager_gestionnaire_path(gestionnaire)
|
||||
end
|
||||
|
||||
def enable_feature
|
||||
gestionnaire = Gestionnaire.find(params[:id])
|
||||
|
||||
params[:features].each do |key, enable|
|
||||
if enable
|
||||
gestionnaire.enable_feature(key.to_sym)
|
||||
else
|
||||
gestionnaire.disable_feature(key.to_sym)
|
||||
end
|
||||
end
|
||||
|
||||
head :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,9 +4,10 @@ class Users::ConfirmationsController < Devise::ConfirmationsController
|
|||
layout "new_application"
|
||||
|
||||
# GET /resource/confirmation/new
|
||||
# def new
|
||||
# super
|
||||
# end
|
||||
def new
|
||||
# Allow displaying the user email in the message
|
||||
self.resource = resource_class.new(email: user_email_param)
|
||||
end
|
||||
|
||||
# POST /resource/confirmation
|
||||
# def create
|
||||
|
@ -20,6 +21,10 @@ class Users::ConfirmationsController < Devise::ConfirmationsController
|
|||
|
||||
# protected
|
||||
|
||||
def user_email_param
|
||||
params.permit(user: :email).dig(:user, :email)
|
||||
end
|
||||
|
||||
# The path used after resending confirmation instructions.
|
||||
# def after_resending_confirmation_instructions_path_for(resource_name)
|
||||
# super(resource_name)
|
||||
|
|
|
@ -22,18 +22,20 @@ class Users::RegistrationsController < Devise::RegistrationsController
|
|||
|
||||
# POST /resource
|
||||
def create
|
||||
user = User.find_by(email: params[:user][:email])
|
||||
if user.present?
|
||||
if user.confirmed?
|
||||
UserMailer.new_account_warning(user).deliver_later
|
||||
# Handle existing user trying to sign up again
|
||||
existing_user = User.find_by(email: params[:user][:email])
|
||||
if existing_user.present?
|
||||
if existing_user.confirmed?
|
||||
UserMailer.new_account_warning(existing_user).deliver_later
|
||||
flash.notice = t('devise.registrations.signed_up_but_unconfirmed')
|
||||
return redirect_to root_path
|
||||
else
|
||||
user.resend_confirmation_instructions
|
||||
existing_user.resend_confirmation_instructions
|
||||
return redirect_to after_inactive_sign_up_path_for(existing_user)
|
||||
end
|
||||
flash.notice = t('devise.registrations.signed_up_but_unconfirmed')
|
||||
redirect_to root_path
|
||||
else
|
||||
super
|
||||
end
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
# GET /resource/edit
|
||||
|
@ -78,7 +80,8 @@ class Users::RegistrationsController < Devise::RegistrationsController
|
|||
# end
|
||||
|
||||
# The path used after sign up for inactive accounts.
|
||||
# def after_inactive_sign_up_path_for(resource)
|
||||
# super(resource)
|
||||
# end
|
||||
def after_inactive_sign_up_path_for(resource)
|
||||
flash.discard(:notice) # Remove devise's default message (as we have a custom page to explain it)
|
||||
new_confirmation_path(resource, :user => { email: resource.email })
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
class Users::SessionsController < Sessions::SessionsController
|
||||
include TrustedDeviceConcern
|
||||
include ActionView::Helpers::DateHelper
|
||||
|
||||
layout "new_application"
|
||||
|
||||
# GET /resource/sign_in
|
||||
|
@ -23,13 +26,22 @@ class Users::SessionsController < Sessions::SessionsController
|
|||
current_user.update(loged_in_with_france_connect: '')
|
||||
end
|
||||
|
||||
if user_signed_in?
|
||||
if gestionnaire_signed_in?
|
||||
if trusted_device? || !current_gestionnaire.feature_enabled?(:enable_email_login_token)
|
||||
set_flash_message :notice, :signed_in
|
||||
redirect_to gestionnaire_procedures_path
|
||||
else
|
||||
gestionnaire = current_gestionnaire
|
||||
login_token = gestionnaire.login_token!
|
||||
GestionnaireMailer.send_login_token(gestionnaire, login_token).deliver_later
|
||||
|
||||
[:user, :gestionnaire, :administrateur].each { |role| sign_out(role) }
|
||||
|
||||
redirect_to link_sent_path(email: gestionnaire.email)
|
||||
end
|
||||
elsif user_signed_in?
|
||||
set_flash_message :notice, :signed_in
|
||||
redirect_to after_sign_in_path_for(:user)
|
||||
elsif gestionnaire_signed_in?
|
||||
location = stored_location_for(:gestionnaire) || gestionnaire_procedures_path
|
||||
redirect_to location
|
||||
elsif administrateur_signed_in?
|
||||
redirect_to admin_path
|
||||
else
|
||||
flash.alert = 'Mauvais couple login / mot de passe'
|
||||
new
|
||||
|
@ -37,6 +49,10 @@ class Users::SessionsController < Sessions::SessionsController
|
|||
end
|
||||
end
|
||||
|
||||
def link_sent
|
||||
@email = params[:email]
|
||||
end
|
||||
|
||||
# DELETE /resource/sign_out
|
||||
def destroy
|
||||
if gestionnaire_signed_in?
|
||||
|
@ -68,6 +84,27 @@ class Users::SessionsController < Sessions::SessionsController
|
|||
redirect_to new_user_session_path
|
||||
end
|
||||
|
||||
def sign_in_by_link
|
||||
gestionnaire = Gestionnaire.find(params[:id])
|
||||
if gestionnaire&.login_token_valid?(params[:jeton])
|
||||
trust_device
|
||||
flash.notice = "Merci d’avoir confirmé votre connexion. Votre navigateur est maintenant authentifié pour #{TRUSTED_DEVICE_PERIOD.to_i / ActiveSupport::Duration::SECONDS_PER_DAY} jours."
|
||||
|
||||
user = User.find_by(email: gestionnaire.email)
|
||||
administrateur = Administrateur.find_by(email: gestionnaire.email)
|
||||
[user, gestionnaire, administrateur].compact.each { |resource| sign_in(resource) }
|
||||
|
||||
if administrateur.present?
|
||||
redirect_to admin_procedures_path
|
||||
else
|
||||
redirect_to gestionnaire_procedures_path
|
||||
end
|
||||
else
|
||||
flash[:alert] = 'Votre lien est invalide ou expiré, veuillez-vous reconnecter.'
|
||||
redirect_to new_user_session_path
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def error_procedure
|
||||
|
@ -92,7 +129,6 @@ class Users::SessionsController < Sessions::SessionsController
|
|||
resource.remember_me = remember_me
|
||||
sign_in resource
|
||||
resource.force_sync_credentials
|
||||
set_flash_message :notice, :signed_in
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,7 +14,8 @@ class GestionnaireDashboard < Administrate::BaseDashboard
|
|||
updated_at: Field::DateTime,
|
||||
current_sign_in_at: Field::DateTime,
|
||||
dossiers: Field::HasMany,
|
||||
procedures: Field::HasMany
|
||||
procedures: Field::HasMany,
|
||||
features: FeaturesField
|
||||
}.freeze
|
||||
|
||||
# COLLECTION_ATTRIBUTES
|
||||
|
@ -35,7 +36,8 @@ class GestionnaireDashboard < Administrate::BaseDashboard
|
|||
:id,
|
||||
:email,
|
||||
:current_sign_in_at,
|
||||
:created_at
|
||||
:created_at,
|
||||
:features
|
||||
].freeze
|
||||
|
||||
# FORM_ATTRIBUTES
|
||||
|
|
|
@ -34,4 +34,12 @@ class GestionnaireMailer < ApplicationMailer
|
|||
|
||||
mail(to: recipient.email, subject: subject)
|
||||
end
|
||||
|
||||
def send_login_token(gestionnaire, login_token)
|
||||
@gestionnaire_id = gestionnaire.id
|
||||
@login_token = login_token
|
||||
subject = "Connexion sécurisée à demarches-simplifiees.fr"
|
||||
|
||||
mail(to: gestionnaire.email, subject: subject)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -19,11 +19,17 @@ class Administration < ApplicationRecord
|
|||
if administrateur.save
|
||||
AdministrationMailer.new_admin_email(administrateur, self).deliver_later
|
||||
administrateur.invite!(id)
|
||||
|
||||
User.create({
|
||||
email: email,
|
||||
password: password,
|
||||
confirmed_at: Time.zone.now
|
||||
})
|
||||
|
||||
Gestionnaire.create({
|
||||
email: email,
|
||||
password: password
|
||||
})
|
||||
end
|
||||
|
||||
administrateur
|
||||
|
|
25
app/models/concerns/trusted_device_concern.rb
Normal file
25
app/models/concerns/trusted_device_concern.rb
Normal file
|
@ -0,0 +1,25 @@
|
|||
module TrustedDeviceConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
TRUSTED_DEVICE_COOKIE_NAME = :trusted_device
|
||||
TRUSTED_DEVICE_PERIOD = 1.month
|
||||
|
||||
def trust_device
|
||||
cookies.encrypted[TRUSTED_DEVICE_COOKIE_NAME] = {
|
||||
value: JSON.generate({ created_at: Time.zone.now }),
|
||||
expires: TRUSTED_DEVICE_PERIOD,
|
||||
httponly: true
|
||||
}
|
||||
end
|
||||
|
||||
def trusted_device?
|
||||
trusted_device_cookie.present? &&
|
||||
Time.zone.now - TRUSTED_DEVICE_PERIOD < JSON.parse(trusted_device_cookie)['created_at']
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def trusted_device_cookie
|
||||
cookies.encrypted[TRUSTED_DEVICE_COOKIE_NAME]
|
||||
end
|
||||
end
|
|
@ -1,6 +1,7 @@
|
|||
class Gestionnaire < ApplicationRecord
|
||||
include CredentialsSyncableConcern
|
||||
include EmailSanitizableConcern
|
||||
include ActiveRecord::SecureToken
|
||||
|
||||
devise :database_authenticatable, :registerable, :async,
|
||||
:recoverable, :rememberable, :trackable, :validatable
|
||||
|
@ -144,6 +145,20 @@ class Gestionnaire < ApplicationRecord
|
|||
Dossier.where(id: dossiers_id_with_notifications(dossiers)).group(:procedure_id).count
|
||||
end
|
||||
|
||||
def login_token!
|
||||
login_token = Gestionnaire.generate_unique_secure_token
|
||||
encrypted_login_token = BCrypt::Password.create(login_token)
|
||||
update(encrypted_login_token: encrypted_login_token, login_token_created_at: Time.zone.now)
|
||||
login_token
|
||||
end
|
||||
|
||||
def login_token_valid?(login_token)
|
||||
BCrypt::Password.new(encrypted_login_token) == login_token
|
||||
30.minutes.ago < login_token_created_at
|
||||
rescue BCrypt::Errors::InvalidHash
|
||||
false
|
||||
end
|
||||
|
||||
def dossiers_id_with_notifications(dossiers)
|
||||
dossiers = dossiers.followed_by(self)
|
||||
|
||||
|
@ -190,6 +205,23 @@ class Gestionnaire < ApplicationRecord
|
|||
GestionnaireMailer.invite_gestionnaire(self, reset_password_token).deliver_later
|
||||
end
|
||||
|
||||
def feature_enabled?(feature)
|
||||
Flipflop.feature_set.feature(feature)
|
||||
features[feature.to_s]
|
||||
end
|
||||
|
||||
def disable_feature(feature)
|
||||
Flipflop.feature_set.feature(feature)
|
||||
features.delete(feature.to_s)
|
||||
save
|
||||
end
|
||||
|
||||
def enable_feature(feature)
|
||||
Flipflop.feature_set.feature(feature)
|
||||
features[feature.to_s] = true
|
||||
save
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def annotations_hash(demande, annotations_privees, avis, messagerie)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
require Rails.root.join('lib', 'percentile')
|
||||
|
||||
class Procedure < ApplicationRecord
|
||||
MAX_DUREE_CONSERVATION = 36
|
||||
|
||||
|
@ -304,16 +306,16 @@ class Procedure < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def mean_traitement_time
|
||||
mean_time(:en_construction_at, :processed_at)
|
||||
def usual_traitement_time
|
||||
percentile_time(:en_construction_at, :processed_at, 90)
|
||||
end
|
||||
|
||||
def mean_verification_time
|
||||
mean_time(:en_construction_at, :en_instruction_at)
|
||||
def usual_verification_time
|
||||
percentile_time(:en_construction_at, :en_instruction_at, 90)
|
||||
end
|
||||
|
||||
def mean_instruction_time
|
||||
mean_time(:en_instruction_at, :processed_at)
|
||||
def usual_instruction_time
|
||||
percentile_time(:en_instruction_at, :processed_at, 90)
|
||||
end
|
||||
|
||||
PATH_AVAILABLE = :available
|
||||
|
@ -421,14 +423,14 @@ class Procedure < ApplicationRecord
|
|||
true
|
||||
end
|
||||
|
||||
def mean_time(start_attribute, end_attribute)
|
||||
def percentile_time(start_attribute, end_attribute, p)
|
||||
times = dossiers
|
||||
.state_termine
|
||||
.pluck(start_attribute, end_attribute)
|
||||
.map { |(start_date, end_date)| end_date - start_date }
|
||||
|
||||
if times.present?
|
||||
times.sum.fdiv(times.size).ceil
|
||||
times.percentile(p).ceil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
:ruby
|
||||
url = if field.resource.class.name == 'Gestionnaire'
|
||||
enable_feature_manager_gestionnaire_path(field.resource.id)
|
||||
else
|
||||
enable_feature_manager_administrateur_path(field.resource.id)
|
||||
end
|
||||
|
||||
%table#features
|
||||
- Flipflop.feature_set.features.each do |feature|
|
||||
- if !feature.group || feature.group.key != :production
|
||||
%tr
|
||||
%td= feature.title
|
||||
%td
|
||||
= check_box_tag "enable-feature", "enable", field.data[feature.name], data: { url: enable_feature_manager_administrateur_path(field.resource.id), key: feature.key }
|
||||
= check_box_tag "enable-feature", "enable", field.data[feature.name], data: { url: url, key: feature.key }
|
||||
|
|
12
app/views/gestionnaire_mailer/send_login_token.html.haml
Normal file
12
app/views/gestionnaire_mailer/send_login_token.html.haml
Normal file
|
@ -0,0 +1,12 @@
|
|||
%p
|
||||
Bonjour,
|
||||
|
||||
%p
|
||||
Veuillez cliquer sur le lien suivant pour vous connecter sur le site demarches-simplifiees.fr :
|
||||
= link_to(sign_in_by_link_url(@gestionnaire_id, jeton: @login_token), sign_in_by_link_url(@gestionnaire_id, jeton: @login_token))
|
||||
|
||||
%p
|
||||
Bonne journée,
|
||||
|
||||
%p
|
||||
L'équipe demarches-simplifiees.fr
|
|
@ -31,10 +31,10 @@
|
|||
%strong votre dossier passera directement en instruction
|
||||
|
||||
/ FIXME: remove the custom procedure switch at some point
|
||||
- if dossier.procedure.mean_verification_time && show_time_means
|
||||
- if dossier.procedure.usual_verification_time && show_time_means
|
||||
- cache(dossier.procedure, expires_in: 1.week) do
|
||||
%p
|
||||
Le temps moyen de vérification pour cette démarche est de #{distance_of_time_in_words(dossier.procedure.mean_verification_time)}.
|
||||
Habituellement, les dossiers de cette démarche sont vérifiés dans un délai de #{distance_of_time_in_words(dossier.procedure.usual_verification_time)}.
|
||||
|
||||
- elsif dossier.en_instruction?
|
||||
.en-instruction
|
||||
|
@ -46,10 +46,10 @@
|
|||
avec le résultat.
|
||||
|
||||
/ FIXME: remove the custom procedure switch at some point
|
||||
- if dossier.procedure.mean_instruction_time && show_time_means
|
||||
- if dossier.procedure.usual_instruction_time && show_time_means
|
||||
- cache(dossier.procedure, expires_in: 1.week) do
|
||||
%p
|
||||
Le temps moyen d’instruction pour cette démarche est de #{distance_of_time_in_words(dossier.procedure.mean_instruction_time)}.
|
||||
Habituellement, les dossiers de cette démarche sont traités dans un délai de #{distance_of_time_in_words(dossier.procedure.usual_instruction_time)}.
|
||||
|
||||
- elsif dossier.accepte?
|
||||
.accepte
|
||||
|
|
|
@ -1,17 +1,31 @@
|
|||
- content_for(:title, 'Renvoyer les instructions de confirmation de compte')
|
||||
- content_for(:title, 'Confirmer votre adresse email')
|
||||
|
||||
- content_for :footer do
|
||||
= render partial: 'root/footer'
|
||||
|
||||
.container.devise-container
|
||||
.container.devise-container.devise-confirmations
|
||||
.one-column-centered
|
||||
= devise_error_messages!
|
||||
|
||||
= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { class: 'form' }) do |f|
|
||||
%img.confirmation-icon{ src: image_url("user/confirmation-email.svg"), alt: "" }
|
||||
|
||||
%h1 Renvoyer les instructions de confirmation de compte
|
||||
%p.confirmation-preamble
|
||||
= succeed '.' do
|
||||
Avant d’effectuer votre démarche, nous avons besoin de vérifier votre adresse
|
||||
- if resource.email.present?
|
||||
%strong= resource.email
|
||||
|
||||
= f.label :email, 'Email'
|
||||
= f.email_field :email, autofocus: true
|
||||
%p.confirmation-instructions
|
||||
Ouvrez votre boîte email, et
|
||||
%strong cliquez sur le lien d’activation
|
||||
dans le message que vous avez reçu.
|
||||
|
||||
= f.submit 'Renvoyer les instructions de confirmation', class: 'button primary'
|
||||
%hr.confirmation-separator
|
||||
|
||||
.confirmation-resend
|
||||
%p Si vous n’avez pas reçu notre message, nous pouvons vous le renvoyer.
|
||||
|
||||
= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { class: 'form' }) do |f|
|
||||
= f.label :email, 'Email'
|
||||
= f.email_field :email, placeholder: 'Email', class: 'small', autofocus: true
|
||||
= f.submit 'Renvoyer un email de confirmation', class: 'button'
|
||||
|
|
14
app/views/users/sessions/link_sent.html.haml
Normal file
14
app/views/users/sessions/link_sent.html.haml
Normal file
|
@ -0,0 +1,14 @@
|
|||
- content_for(:title, 'Lien de connexion par email')
|
||||
|
||||
- content_for :footer do
|
||||
= render partial: 'root/footer'
|
||||
|
||||
#link-sent.container
|
||||
= image_tag('user/confirmation-email.svg')
|
||||
%h1 Encore une petite étape :)
|
||||
|
||||
%p.mail
|
||||
Ouvrez votre boite email <b>#{@email}</b> puis cliquez sur le lien d'activation du message <b>Connexion sécurisée à demarches-simplifiees.fr</b>.
|
||||
|
||||
%p.help
|
||||
En cas de difficultés, nous restons joignables sur #{link_to 'contact@demarches-simplifiees.fr', 'mailto:contact@demarches-simplifiees.fr'}.
|
|
@ -20,6 +20,7 @@ Flipflop.configure do
|
|||
feature :web_hook
|
||||
feature :publish_draft
|
||||
feature :support_form
|
||||
feature :enable_email_login_token
|
||||
|
||||
group :production do
|
||||
feature :remote_storage,
|
||||
|
|
|
@ -26,6 +26,7 @@ Rails.application.routes.draw do
|
|||
|
||||
resources :gestionnaires, only: [:index, :show] do
|
||||
post 'reinvite', on: :member
|
||||
put 'enable_feature', on: :member
|
||||
end
|
||||
|
||||
resources :dossiers, only: [:show]
|
||||
|
@ -88,6 +89,8 @@ Rails.application.routes.draw do
|
|||
devise_scope :user do
|
||||
get '/users/sign_in/demo' => redirect("/users/sign_in")
|
||||
get '/users/no_procedure' => 'users/sessions#no_procedure'
|
||||
get 'connexion-par-jeton/:id' => 'users/sessions#sign_in_by_link', as: 'sign_in_by_link'
|
||||
get 'lien-envoye/:email' => 'users/sessions#link_sent', constraints: { email: /.*/ }, as: 'link_sent'
|
||||
end
|
||||
|
||||
devise_scope :gestionnaire do
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
class AddEncryptedLoginTokenColumnToGestionnaire < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
add_column :gestionnaires, :encrypted_login_token, :text
|
||||
add_column :gestionnaires, :login_token_created_at, :datetime
|
||||
end
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
class AddFeaturesColumnToGestionnaires < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
add_column :gestionnaires, :features, :jsonb, null: false, default: {}
|
||||
end
|
||||
end
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 2018_10_30_141238) do
|
||||
ActiveRecord::Schema.define(version: 2018_11_08_151929) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
@ -365,6 +365,9 @@ ActiveRecord::Schema.define(version: 2018_10_30_141238) do
|
|||
t.string "last_sign_in_ip"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.text "encrypted_login_token"
|
||||
t.datetime "login_token_created_at"
|
||||
t.jsonb "features", default: {}, null: false
|
||||
t.index ["email"], name: "index_gestionnaires_on_email", unique: true
|
||||
t.index ["reset_password_token"], name: "index_gestionnaires_on_reset_password_token", unique: true
|
||||
end
|
||||
|
|
31
lib/percentile.rb
Normal file
31
lib/percentile.rb
Normal file
|
@ -0,0 +1,31 @@
|
|||
# Adapted from https://github.com/thirtysixthspan/descriptive_statistics
|
||||
|
||||
# Copyright (c) 2010-2014 Derrick Parkhurst (derrick.parkhurst@gmail.com)
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
|
||||
class Array
|
||||
def percentile(p)
|
||||
values = self.sort
|
||||
|
||||
if values.empty?
|
||||
return []
|
||||
elsif values.size == 1
|
||||
return values.first
|
||||
elsif p == 100
|
||||
return values.last
|
||||
end
|
||||
|
||||
rank = p / 100.0 * (values.size - 1)
|
||||
lower, upper = values[rank.floor, 2]
|
||||
lower + (upper - lower) * (rank - rank.floor)
|
||||
end
|
||||
end
|
15
lib/tasks/2018_10_30_admin_has_gestionnaire.rake
Normal file
15
lib/tasks/2018_10_30_admin_has_gestionnaire.rake
Normal file
|
@ -0,0 +1,15 @@
|
|||
namespace :'2018_10_30_admin_has_gestionnaire' do
|
||||
task run: :environment do
|
||||
admin_without_gestionnaire_ids = Administrateur
|
||||
.find_by_sql('SELECT administrateurs.id FROM administrateurs LEFT OUTER JOIN gestionnaires ON gestionnaires.email = administrateurs.email WHERE gestionnaires.email IS NULL')
|
||||
.pluck(:id)
|
||||
|
||||
admin_without_gestionnaire_ids.each do |admin_id|
|
||||
admin = Administrateur.find(admin_id)
|
||||
g = Gestionnaire.new
|
||||
g.email = admin.email
|
||||
g.encrypted_password = admin.encrypted_password
|
||||
g.save(validate: false)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,8 @@
|
|||
namespace :'activate_trusted_device_for_a-f' do
|
||||
task run: :environment do
|
||||
letters_a_to_f = ('a'..'f').to_a
|
||||
Gestionnaire
|
||||
.where("substr(email, 1, 1) IN (?)", letters_a_to_f)
|
||||
.update_all(features: { "enable_email_login_token" => true })
|
||||
end
|
||||
end
|
20
spec/controllers/administrateur/activate_controller_spec.rb
Normal file
20
spec/controllers/administrateur/activate_controller_spec.rb
Normal file
|
@ -0,0 +1,20 @@
|
|||
describe Administrateurs::ActivateController, type: :controller do
|
||||
describe '#new' do
|
||||
let(:admin) { create(:administrateur) }
|
||||
let(:token) { admin.send(:set_reset_password_token) }
|
||||
|
||||
before { allow(controller).to receive(:trust_device) }
|
||||
|
||||
context 'when the token is ok' do
|
||||
before { get :new, params: { token: token } }
|
||||
|
||||
it { expect(controller).to have_received(:trust_device) }
|
||||
end
|
||||
|
||||
context 'when the token is bad' do
|
||||
before { get :new, params: { token: 'bad' } }
|
||||
|
||||
it { expect(controller).not_to have_received(:trust_device) }
|
||||
end
|
||||
end
|
||||
end
|
20
spec/controllers/gestionnaires/activate_controller_spec.rb
Normal file
20
spec/controllers/gestionnaires/activate_controller_spec.rb
Normal file
|
@ -0,0 +1,20 @@
|
|||
describe Gestionnaires::ActivateController, type: :controller do
|
||||
describe '#new' do
|
||||
let(:gestionnaire) { create(:gestionnaire) }
|
||||
let(:token) { gestionnaire.send(:set_reset_password_token) }
|
||||
|
||||
before { allow(controller).to receive(:trust_device) }
|
||||
|
||||
context 'when the token is ok' do
|
||||
before { get :new, params: { token: token } }
|
||||
|
||||
it { expect(controller).to have_received(:trust_device) }
|
||||
end
|
||||
|
||||
context 'when the token is bad' do
|
||||
before { get :new, params: { token: 'bad' } }
|
||||
|
||||
it { expect(controller).not_to have_received(:trust_device) }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -74,8 +74,7 @@ describe Users::RegistrationsController, type: :controller do
|
|||
subject
|
||||
end
|
||||
|
||||
it { expect(response).to redirect_to(root_path) }
|
||||
it { expect(flash.notice).to eq(I18n.t('devise.registrations.signed_up_but_unconfirmed')) }
|
||||
it { expect(response).to redirect_to(new_user_confirmation_path(user: { email: user[:email] })) }
|
||||
it { expect(UserMailer).not_to have_received(:new_account_warning) }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -27,7 +27,7 @@ describe Users::SessionsController, type: :controller do
|
|||
let(:password) { 'un super mot de passe' }
|
||||
|
||||
let(:user) { create(:user, email: email, password: password) }
|
||||
let(:gestionnaire) { create(:gestionnaire, email: email, password: password) }
|
||||
let(:gestionnaire) { create(:gestionnaire, :with_trusted_device, email: email, password: password) }
|
||||
let(:administrateur) { create(:administrateur, email: email, password: password) }
|
||||
|
||||
it 'signs user in' do
|
||||
|
@ -41,18 +41,39 @@ describe Users::SessionsController, type: :controller do
|
|||
|
||||
it 'signs gestionnaire in' do
|
||||
post :create, params: { user: { email: gestionnaire.email, password: gestionnaire.password } }
|
||||
expect(@response.redirect?).to be(true)
|
||||
|
||||
expect(subject).to redirect_to link_sent_path(email: gestionnaire.email)
|
||||
expect(subject.current_user).to be(nil)
|
||||
expect(subject.current_gestionnaire).to eq(gestionnaire)
|
||||
expect(subject.current_gestionnaire).to be(nil)
|
||||
expect(subject.current_administrateur).to be(nil)
|
||||
end
|
||||
|
||||
it 'signs administrateur in' do
|
||||
post :create, params: { user: { email: administrateur.email, password: administrateur.password } }
|
||||
expect(@response.redirect?).to be(true)
|
||||
expect(subject.current_user).to be(nil)
|
||||
expect(subject.current_gestionnaire).to be(nil)
|
||||
expect(subject.current_administrateur).to eq(administrateur)
|
||||
context 'when the device is trusted' do
|
||||
before do
|
||||
allow(controller).to receive(:trusted_device?).and_return(true)
|
||||
post :create, params: { user: { email: gestionnaire.email, password: gestionnaire.password } }
|
||||
end
|
||||
|
||||
it 'directly log the gestionnaire' do
|
||||
expect(subject).to redirect_to gestionnaire_procedures_path
|
||||
expect(subject.current_user).to be(nil)
|
||||
expect(subject.current_gestionnaire).to eq(gestionnaire)
|
||||
expect(subject.current_administrateur).to be(nil)
|
||||
end
|
||||
end
|
||||
|
||||
context 'signs administrateur in' do
|
||||
# an admin has always an gestionnaire role
|
||||
before { gestionnaire }
|
||||
|
||||
it 'signs administrateur in' do
|
||||
post :create, params: { user: { email: administrateur.email, password: administrateur.password } }
|
||||
|
||||
expect(subject).to redirect_to link_sent_path(email: gestionnaire.email)
|
||||
expect(subject.current_user).to be(nil)
|
||||
expect(subject.current_gestionnaire).to be(nil)
|
||||
expect(subject.current_administrateur).to eq(nil)
|
||||
end
|
||||
end
|
||||
|
||||
context {
|
||||
|
@ -63,10 +84,16 @@ describe Users::SessionsController, type: :controller do
|
|||
|
||||
it 'signs user + gestionnaire + administrateur in' do
|
||||
post :create, params: { user: { email: administrateur.email, password: administrateur.password } }
|
||||
expect(@response.redirect?).to be(true)
|
||||
expect(subject.current_user).to eq(user)
|
||||
expect(subject.current_gestionnaire).to eq(gestionnaire)
|
||||
expect(subject.current_administrateur).to eq(administrateur)
|
||||
|
||||
expect(subject).to redirect_to link_sent_path(email: gestionnaire.email)
|
||||
|
||||
# TODO: fix me
|
||||
# Strange behaviour: sign_out(:user) does not work in spec
|
||||
# but seems to work in live
|
||||
# expect(controller.current_user).to be(nil)
|
||||
|
||||
expect(subject.current_gestionnaire).to be(nil)
|
||||
expect(subject.current_administrateur).to be(nil)
|
||||
expect(user.reload.loged_in_with_france_connect).to be(nil)
|
||||
end
|
||||
}
|
||||
|
@ -219,4 +246,75 @@ describe Users::SessionsController, type: :controller do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#sign_in_by_link' do
|
||||
context 'when the gestionnaire has non other account' do
|
||||
let(:gestionnaire) { create(:gestionnaire) }
|
||||
before do
|
||||
allow(controller).to receive(:trust_device)
|
||||
post :sign_in_by_link, params: { id: gestionnaire.id, login_token: login_token }
|
||||
end
|
||||
|
||||
context 'when the token is valid' do
|
||||
let(:login_token) { gestionnaire.login_token! }
|
||||
|
||||
it { is_expected.to redirect_to gestionnaire_procedures_path }
|
||||
it { expect(controller.current_gestionnaire).to eq(gestionnaire) }
|
||||
it { expect(controller).to have_received(:trust_device) }
|
||||
end
|
||||
|
||||
context 'when the token is invalid' do
|
||||
let(:login_token) { 'invalid_token' }
|
||||
|
||||
it { is_expected.to redirect_to new_user_session_path }
|
||||
it { expect(controller.current_gestionnaire).to be_nil }
|
||||
it { expect(controller).not_to have_received(:trust_device) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the gestionnaire has an user and admin account' do
|
||||
let(:email) { 'unique@plop.com' }
|
||||
let(:password) { 'un super mot de passe' }
|
||||
|
||||
let!(:user) { create(:user, email: email, password: password) }
|
||||
let!(:gestionnaire) { create(:gestionnaire, email: email, password: password) }
|
||||
let!(:administrateur) { create(:administrateur, email: email, password: password) }
|
||||
|
||||
before do
|
||||
post :sign_in_by_link, params: { id: gestionnaire.id, login_token: login_token }
|
||||
end
|
||||
|
||||
context 'when the token is valid' do
|
||||
let(:login_token) { gestionnaire.login_token! }
|
||||
|
||||
it { expect(controller.current_gestionnaire).to eq(gestionnaire) }
|
||||
it { expect(controller.current_administrateur).to eq(administrateur) }
|
||||
it { expect(controller.current_user).to eq(user) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#trust_device and #trusted_device?' do
|
||||
subject { controller.trusted_device? }
|
||||
|
||||
context 'when the trusted cookie is not present' do
|
||||
it { is_expected.to be false }
|
||||
end
|
||||
|
||||
context 'when the cookie is outdated' do
|
||||
before do
|
||||
Timecop.freeze(Time.zone.now - TrustedDeviceConcern::TRUSTED_DEVICE_PERIOD - 1.minute)
|
||||
controller.trust_device
|
||||
Timecop.return
|
||||
end
|
||||
|
||||
it { is_expected.to be false }
|
||||
end
|
||||
|
||||
context 'when the cookie is ok' do
|
||||
before { controller.trust_device }
|
||||
|
||||
it { is_expected.to be true }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,4 +4,10 @@ FactoryBot.define do
|
|||
email { generate(:gestionnaire_email) }
|
||||
password { 'password' }
|
||||
end
|
||||
|
||||
trait :with_trusted_device do
|
||||
after(:create) do |gestionnaire|
|
||||
gestionnaire.update(features: { "enable_email_login_token" => true })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,20 +1,26 @@
|
|||
require 'spec_helper'
|
||||
|
||||
feature 'Administrator connection' do
|
||||
let(:admin) { create(:administrateur) }
|
||||
include ActiveJob::TestHelper
|
||||
|
||||
let(:email) { 'admin1@admin.com' }
|
||||
let(:password) { 'mon chien aime les bananes' }
|
||||
let!(:admin) { create(:administrateur, email: email, password: password) }
|
||||
let!(:gestionnaire) { create(:gestionnaire, :with_trusted_device, email: email, password: password) }
|
||||
|
||||
before do
|
||||
visit new_administrateur_session_path
|
||||
end
|
||||
|
||||
scenario 'administrator is on sign in page' do
|
||||
expect(page).to have_css('#new_user')
|
||||
end
|
||||
|
||||
context "admin fills form and log in" do
|
||||
before do
|
||||
page.find_by_id('user_email').set admin.email
|
||||
page.find_by_id('user_password').set admin.password
|
||||
page.click_on 'Se connecter'
|
||||
sign_in_with(email, password, true)
|
||||
end
|
||||
|
||||
scenario 'a menu button is available' do
|
||||
expect(page).to have_css('#admin_menu')
|
||||
end
|
||||
|
|
|
@ -4,7 +4,7 @@ feature 'The gestionnaire part' do
|
|||
include ActiveJob::TestHelper
|
||||
|
||||
let(:password) { 'secret_password' }
|
||||
let!(:gestionnaire) { create(:gestionnaire, password: password) }
|
||||
let!(:gestionnaire) { create(:gestionnaire, :with_trusted_device, password: password) }
|
||||
|
||||
let!(:procedure) { create(:procedure, :published, gestionnaires: [gestionnaire]) }
|
||||
let!(:dossier) { create(:dossier, state: Dossier.states.fetch(:en_construction), procedure: procedure) }
|
||||
|
@ -116,7 +116,7 @@ feature 'The gestionnaire part' do
|
|||
|
||||
log_out
|
||||
|
||||
log_in(gestionnaire.email, password)
|
||||
log_in(gestionnaire.email, password, check_email: false)
|
||||
|
||||
click_on procedure.libelle
|
||||
click_on dossier.user.email
|
||||
|
@ -173,14 +173,13 @@ feature 'The gestionnaire part' do
|
|||
expect(page).to have_text("Dossier envoyé")
|
||||
end
|
||||
|
||||
def log_in(email, password)
|
||||
def log_in(email, password, check_email: true)
|
||||
visit '/'
|
||||
click_on 'Connexion'
|
||||
expect(page).to have_current_path(new_user_session_path)
|
||||
|
||||
fill_in 'user_email', with: email
|
||||
fill_in 'user_password', with: password
|
||||
click_on 'Se connecter'
|
||||
sign_in_with(email, password, check_email)
|
||||
|
||||
expect(page).to have_current_path(gestionnaire_procedures_path)
|
||||
end
|
||||
|
||||
|
@ -196,7 +195,7 @@ feature 'The gestionnaire part' do
|
|||
end
|
||||
|
||||
def test_mail(to, content)
|
||||
mail = ActionMailer::Base.deliveries.first
|
||||
mail = ActionMailer::Base.deliveries.last
|
||||
expect(mail.to).to match([to])
|
||||
expect(mail.body.parts.map(&:to_s)).to all(include(content))
|
||||
end
|
||||
|
|
|
@ -23,7 +23,7 @@ describe 'Dossier details:' do
|
|||
visit dossier_path(dossier)
|
||||
end
|
||||
|
||||
it { expect(page).to have_text("Le temps moyen de vérification pour cette démarche est de 10 jours.") }
|
||||
it { expect(page).to have_text("Habituellement, les dossiers de cette démarche sont vérifiés dans un délai de 10 jours.") }
|
||||
end
|
||||
|
||||
context "when the dossier is in instruction" do
|
||||
|
@ -34,7 +34,7 @@ describe 'Dossier details:' do
|
|||
visit dossier_path(dossier)
|
||||
end
|
||||
|
||||
it { expect(page).to have_text("Le temps moyen d’instruction pour cette démarche est de 2 mois.") }
|
||||
it { expect(page).to have_text("Habituellement, les dossiers de cette démarche sont traités dans un délai de 2 mois.") }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ feature 'Invitations' do
|
|||
|
||||
# Create the account
|
||||
sign_up_with invite.email, user_password
|
||||
expect(page).to have_content("lien d'activation")
|
||||
expect(page).to have_content('lien d’activation')
|
||||
|
||||
# Confirm the account
|
||||
# (The user should be redirected to the dossier they was invited on)
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
require 'spec_helper'
|
||||
|
||||
feature 'Signin up:' do
|
||||
let(:user_email) { generate :user_email }
|
||||
let(:user_password) { 'testpassword' }
|
||||
|
||||
scenario 'a new user can sign-up' do
|
||||
visit root_path
|
||||
click_on 'Connexion'
|
||||
click_on 'Créer un compte'
|
||||
|
||||
sign_up_with 'testuser@exemple.fr'
|
||||
expect(page).to have_content "Nous vous avons envoyé un email contenant un lien d'activation"
|
||||
sign_up_with user_email, user_password
|
||||
expect(page).to have_content "nous avons besoin de vérifier votre adresse #{user_email}"
|
||||
|
||||
click_confirmation_link_for 'testuser@exemple.fr'
|
||||
click_confirmation_link_for user_email
|
||||
expect(page).to have_content 'Votre compte a été activé'
|
||||
expect(page).to have_current_path dossiers_path
|
||||
end
|
||||
|
@ -25,12 +28,37 @@ feature 'Signin up:' do
|
|||
expect(page).to have_current_path new_user_session_path
|
||||
click_on 'Créer un compte'
|
||||
|
||||
sign_up_with 'testuser@exemple.fr'
|
||||
expect(page).to have_content "Nous vous avons envoyé un email contenant un lien d'activation"
|
||||
sign_up_with user_email, user_password
|
||||
expect(page).to have_content "nous avons besoin de vérifier votre adresse #{user_email}"
|
||||
|
||||
click_confirmation_link_for 'testuser@exemple.fr'
|
||||
click_confirmation_link_for user_email
|
||||
expect(page).to have_content 'Votre compte a été activé'
|
||||
expect(page).to have_content procedure.libelle
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a user is not confirmed yet' do
|
||||
before do
|
||||
visit root_path
|
||||
click_on 'Connexion'
|
||||
click_on 'Créer un compte'
|
||||
|
||||
sign_up_with user_email, user_password
|
||||
end
|
||||
|
||||
# Ideally, when signing-in with an unconfirmed account,
|
||||
# the user would be redirected to the "resend email confirmation" page.
|
||||
#
|
||||
# However the check for unconfirmed accounts is made by Warden every time a page is loaded –
|
||||
# and much earlier than SessionsController#create.
|
||||
#
|
||||
# For now only test the default behavior (an error message is displayed).
|
||||
scenario 'they get an error message' do
|
||||
visit root_path
|
||||
click_on 'Connexion'
|
||||
|
||||
sign_in_with user_email, user_password
|
||||
expect(page).to have_content 'Vous devez confirmer votre adresse email pour continuer'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,4 +7,8 @@ class GestionnaireMailerPreview < ActionMailer::Preview
|
|||
def send_dossier
|
||||
GestionnaireMailer.send_dossier(Gestionnaire.first, Dossier.first, Gestionnaire.last)
|
||||
end
|
||||
|
||||
def send_login_token
|
||||
GestionnaireMailer.send_login_token(Gestionnaire.first, "token")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -20,6 +20,12 @@ describe Administration, type: :model do
|
|||
expect(user).to be_present
|
||||
end
|
||||
|
||||
it 'creates a corresponding gestionnaire account for the email' do
|
||||
subject
|
||||
gestionnaire = Gestionnaire.find_by(email: valid_email)
|
||||
expect(gestionnaire).to be_present
|
||||
end
|
||||
|
||||
context 'when there already is a user account with the same email' do
|
||||
before { create(:user, email: valid_email) }
|
||||
it 'still creates an admin account' do
|
||||
|
|
|
@ -705,22 +705,34 @@ describe Procedure do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#mean_instruction_time' do
|
||||
describe '#usual_instruction_time' do
|
||||
let(:procedure) { create(:procedure) }
|
||||
|
||||
context 'when there is only one dossier' do
|
||||
let(:dossier) { create(:dossier, procedure: procedure) }
|
||||
|
||||
context 'which is termine' do
|
||||
before do
|
||||
dossier.accepte!
|
||||
processed_date = Time.zone.parse('12/12/2012')
|
||||
instruction_date = processed_date - 1.day
|
||||
dossier.update(en_instruction_at: instruction_date, processed_at: processed_date)
|
||||
end
|
||||
|
||||
it { expect(procedure.mean_instruction_time).to eq(1.day.to_i) }
|
||||
before do
|
||||
processed_delays.each do |delay|
|
||||
dossier = create :dossier, :accepte, procedure: procedure
|
||||
instruction_date = 1.month.ago
|
||||
processed_date = instruction_date + delay
|
||||
dossier.update!(en_instruction_at: instruction_date, processed_at: processed_date)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are several processed dossiers' do
|
||||
let(:processed_delays) { [1.day, 2.days, 2.days, 2.days, 2.days, 3.days, 3.days, 3.days, 3.days, 12.days] }
|
||||
|
||||
it 'returns a time representative of the dossier instruction delay' do
|
||||
expect(procedure.usual_instruction_time).to be_between(3.days, 4.days)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is only one processed dossier' do
|
||||
let(:processed_delays) { [1.day] }
|
||||
it { expect(procedure.usual_instruction_time).to eq(1.day) }
|
||||
end
|
||||
|
||||
context 'where there is no processed dossier' do
|
||||
let(:processed_delays) { [] }
|
||||
it { expect(procedure.usual_instruction_time).to be_nil }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,10 +17,21 @@ module FeatureHelpers
|
|||
dossier
|
||||
end
|
||||
|
||||
def sign_in_with(email, password)
|
||||
def sign_in_with(email, password, sign_in_by_link = false)
|
||||
fill_in :user_email, with: email
|
||||
fill_in :user_password, with: password
|
||||
click_on 'Se connecter'
|
||||
|
||||
perform_enqueued_jobs do
|
||||
click_on 'Se connecter'
|
||||
end
|
||||
|
||||
if sign_in_by_link
|
||||
mail = ActionMailer::Base.deliveries.last
|
||||
message = mail.body.parts.join(&:to_s)
|
||||
login_token = message[/connexion-par-jeton\/(.*)/, 1]
|
||||
|
||||
visit sign_in_by_link_path(login_token)
|
||||
end
|
||||
end
|
||||
|
||||
def sign_up_with(email, password = 'testpassword')
|
||||
|
|
Loading…
Reference in a new issue