1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2024-12-01 12:24:28 +01:00

Merge branch 'dev' for release 5.4.15

This commit is contained in:
Du Peng 2022-08-01 20:03:43 +02:00
commit 3d8d56bf3f
34 changed files with 127 additions and 79 deletions

View File

@ -2,6 +2,13 @@
## next release ## next release
## v5.4.15 2022 August 1
- Improved security: adds redis-session-store to store session
- Improved security: makes rmagick, minimagick and mime types less explicit
- Improved security: add complexity check of the user password
- Improved security: prevents users enumeration attacks by not giving any info about the validity of the email
## v5.4.14 2022 August 1 ## v5.4.14 2022 August 1
- Added a test for multiple reservations on the same space slot - Added a test for multiple reservations on the same space slot

View File

@ -144,3 +144,5 @@ gem 'tzinfo-data'
# compilation of dynamic stylesheets (home page & theme) # compilation of dynamic stylesheets (home page & theme)
gem 'sassc', '= 2.1.0' gem 'sassc', '= 2.1.0'
gem 'redis-session-store'

View File

@ -351,6 +351,9 @@ GEM
activesupport activesupport
i18n i18n
redis (4.6.0) redis (4.6.0)
redis-session-store (0.11.4)
actionpack (>= 3, < 8)
redis (>= 3, < 5)
regexp_parser (2.5.0) regexp_parser (2.5.0)
repost (0.3.2) repost (0.3.2)
responders (2.4.1) responders (2.4.1)
@ -542,6 +545,7 @@ DEPENDENCIES
rails_12factor rails_12factor
rb-readline rb-readline
recurrence recurrence
redis-session-store
repost repost
responders (~> 2.0) responders (~> 2.0)
rolify rolify

View File

@ -2,6 +2,17 @@
# Devise controller to handle validation of email addresses # Devise controller to handle validation of email addresses
class ConfirmationsController < Devise::ConfirmationsController class ConfirmationsController < Devise::ConfirmationsController
# POST /resource/confirmation
def create
self.resource = resource_class.send_confirmation_instructions(resource_params)
yield resource if block_given?
if successfully_sent?(resource)
respond_with({}, location: after_resending_confirmation_instructions_path_for(resource_name))
end
end
# The path used after confirmation. # The path used after confirmation.
def after_confirmation_path_for(_resource_name, resource) def after_confirmation_path_for(_resource_name, resource)
signed_in_root_path(resource) signed_in_root_path(resource)

View File

@ -9,8 +9,6 @@ class PasswordsController < Devise::PasswordsController
if successfully_sent?(resource) if successfully_sent?(resource)
respond_with({}, location: after_sending_reset_password_instructions_path_for(resource_name)) respond_with({}, location: after_sending_reset_password_instructions_path_for(resource_name))
else
head 404
end end
end end

View File

@ -504,14 +504,8 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco
controller: ['$scope', '$uibModalInstance', '$http', function ($scope, $uibModalInstance, $http) { controller: ['$scope', '$uibModalInstance', '$http', function ($scope, $uibModalInstance, $http) {
$scope.user = { email: '' }; $scope.user = { email: '' };
$scope.sendReset = function () { $scope.sendReset = function () {
$scope.alerts = [];
return $http.post('/users/password.json', { user: $scope.user }).then(function () { return $http.post('/users/password.json', { user: $scope.user }).then(function () {
$uibModalInstance.close(); $uibModalInstance.close();
}).catch(function () {
$scope.alerts.push({
msg: _t('app.public.common.your_email_address_is_unknown'),
type: 'danger'
});
}); });
}; };
}] }]
@ -526,14 +520,8 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco
controller: ['$scope', '$uibModalInstance', '$http', function ($scope, $uibModalInstance, $http) { controller: ['$scope', '$uibModalInstance', '$http', function ($scope, $uibModalInstance, $http) {
$scope.user = { email: '' }; $scope.user = { email: '' };
$scope.submitConfirmationNewForm = function () { $scope.submitConfirmationNewForm = function () {
$scope.alerts = [];
return $http.post('/users/confirmation.json', { user: $scope.user }).then(function () { return $http.post('/users/confirmation.json', { user: $scope.user }).then(function () {
$uibModalInstance.close(); $uibModalInstance.close();
}).catch(function (res) {
$scope.alerts.push({
msg: res.data.errors.email[0],
type: 'danger'
});
}); });
}; };
}] }]

View File

@ -4,7 +4,6 @@
<h1 translate>{{ 'app.public.common.confirm_my_account' }}</h1> <h1 translate>{{ 'app.public.common.confirm_my_account' }}</h1>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<uib-alert ng-repeat="alert in alerts" type="{{alert.type}}" close="closeAlert($index)">{{alert.msg}}</uib-alert>
<div class="panel panel-default bg-light"> <div class="panel panel-default bg-light">
<div class="panel-body"> <div class="panel-body">
<p translate>{{ 'app.public.common.you_will_receive_confirmation_instructions_by_email' }}</p> <p translate>{{ 'app.public.common.you_will_receive_confirmation_instructions_by_email' }}</p>

View File

@ -142,7 +142,7 @@
class="form-control" class="form-control"
id="user_password" id="user_password"
placeholder="{{ 'app.shared.user.new_password' | translate }}" placeholder="{{ 'app.shared.user.new_password' | translate }}"
ng-minlength="8" ng-minlength="12"
required/> required/>
</div> </div>
<span class="help-block" ng-show="userForm['user[password]'].$dirty && userForm['user[password]'].$error.required" translate>{{ 'app.shared.user.password_is_required' }}</span> <span class="help-block" ng-show="userForm['user[password]'].$dirty && userForm['user[password]'].$error.required" translate>{{ 'app.shared.user.password_is_required' }}</span>
@ -158,7 +158,7 @@
class="form-control" class="form-control"
id="user_password_confirmation" id="user_password_confirmation"
placeholder="{{ 'app.shared.user.confirmation_of_new_password' | translate }}" placeholder="{{ 'app.shared.user.confirmation_of_new_password' | translate }}"
ng-minlength="8" ng-minlength="12"
required required
match="user.password"/> match="user.password"/>
</div> </div>

View File

@ -18,7 +18,7 @@
class="form-control" class="form-control"
placeholder="{{ 'app.public.common.your_new_password' | translate }}" placeholder="{{ 'app.public.common.your_new_password' | translate }}"
required required
ng-minlength="8"> ng-minlength="12">
</div> </div>
<span class="help-block" ng-show="passwordEditForm.password.$dirty && passwordEditForm.password.$error.required" translate>{{ 'app.public.common.password_is_required' }}</span> <span class="help-block" ng-show="passwordEditForm.password.$dirty && passwordEditForm.password.$error.required" translate>{{ 'app.public.common.password_is_required' }}</span>
<span class="help-block" ng-show="passwordEditForm.password.$dirty && passwordEditForm.password.$error.minlength" translate>{{ 'app.public.common.password_is_too_short' }}</span> <span class="help-block" ng-show="passwordEditForm.password.$dirty && passwordEditForm.password.$error.minlength" translate>{{ 'app.public.common.password_is_too_short' }}</span>
@ -35,7 +35,7 @@
class="form-control" class="form-control"
placeholder="{{ 'app.public.common.type_your_password_again' | translate }}" placeholder="{{ 'app.public.common.type_your_password_again' | translate }}"
required required
ng-minlength="8" ng-minlength="12"
match="user.password"> match="user.password">
</div> </div>
<span class="help-block" ng-show="passwordEditForm.password_confirmation.$dirty && passwordEditForm.password_confirmation.$error.required" translate>{{ 'app.public.common.password_confirmation_is_required' }}</span> <span class="help-block" ng-show="passwordEditForm.password_confirmation.$dirty && passwordEditForm.password_confirmation.$error.required" translate>{{ 'app.public.common.password_confirmation_is_required' }}</span>

View File

@ -4,7 +4,6 @@
<h1 translate>{{ 'app.public.common.password_forgotten' }}</h1> <h1 translate>{{ 'app.public.common.password_forgotten' }}</h1>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<uib-alert ng-repeat="alert in alerts" type="{{alert.type}}" close="closeAlert($index)">{{alert.msg}}</uib-alert>
<div class="panel panel-default bg-light"> <div class="panel panel-default bg-light">
<div class="panel-body"> <div class="panel-body">
<form name="passwordNewForm" class="form-horizontal" ng-keydown="passwordNewForm.$valid && $event.which == 13 && sendReset()"> <form name="passwordNewForm" class="form-horizontal" ng-keydown="passwordNewForm.$valid && $event.which == 13 && sendReset()">

View File

@ -96,7 +96,7 @@
class="form-control" class="form-control"
placeholder="{{ 'app.public.common.your_password' | translate }}" placeholder="{{ 'app.public.common.your_password' | translate }}"
required required
ng-minlength="8"> ng-minlength="12">
</div> </div>
<span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span>
<span class="help-block" ng-show="signupForm.password.$dirty && signupForm.password.$error.required" translate>{{ 'app.public.common.password_is_required' }}</span> <span class="help-block" ng-show="signupForm.password.$dirty && signupForm.password.$error.required" translate>{{ 'app.public.common.password_is_required' }}</span>

View File

@ -161,7 +161,7 @@ module SingleSignOnConcern
user.set_data_from_sso_mapping(key, value) user.set_data_from_sso_mapping(key, value)
end end
logger.debug 'generating a new password' logger.debug 'generating a new password'
user.password = Devise.friendly_token[0, 20] user.password = SecurePassword.generate
end end
end end
end end

View File

@ -78,6 +78,7 @@ class User < ApplicationRecord
validate :cgu_must_accept, if: :new_record? validate :cgu_must_accept, if: :new_record?
validates :username, presence: true, uniqueness: true, length: { maximum: 30 } validates :username, presence: true, uniqueness: true, length: { maximum: 30 }
validate :password_complexity
scope :active, -> { where(is_active: true) } scope :active, -> { where(is_active: true) }
scope :without_subscription, -> { includes(statistic_profile: [:subscriptions]).where(subscriptions: { statistic_profile_id: nil }) } scope :without_subscription, -> { includes(statistic_profile: [:subscriptions]).where(subscriptions: { statistic_profile_id: nil }) }
@ -347,4 +348,10 @@ class User < ApplicationRecord
last_name: last_name last_name: last_name
) )
end end
def password_complexity
return if password.blank? || SecurePassword.is_secured?(password)
errors.add I18n.t("app.public.common.password_is_too_weak"), I18n.t("app.public.common.password_is_too_weak_explanations")
end
end end

View File

@ -128,7 +128,7 @@ class Members::MembersService
def password(params) def password(params)
if !params[:password] && !params[:password_confirmation] if !params[:password] && !params[:password_confirmation]
Devise.friendly_token.first(8) SecurePassword.generate
else else
params[:password] params[:password]
end end

View File

@ -0,0 +1,18 @@
class SecurePassword
LOWER_LETTERS = ('a'..'z').to_a
UPPER_LETTERS = ('A'..'Z').to_a
DIGITS = ('0'..'9').to_a
SPECIAL_CHARS = ["!", "#", "$", "%", "&", "(", ")", "*", "+", ",", "-", ".", "/", ":", ";", "<", "=", ">", "?", "@", "[", "]", "^", "_", "{", "|", "}", "~", "'", "`", '"']
def self.generate
(LOWER_LETTERS.shuffle.first(4) + UPPER_LETTERS.shuffle.first(4) + DIGITS.shuffle.first(4) + SPECIAL_CHARS.shuffle.first(4)).shuffle.join
end
def self.is_secured?(password)
password_as_array = password.split("")
password_as_array.any? {|c| c.in? LOWER_LETTERS } &&
password_as_array.any? {|c| c.in? UPPER_LETTERS } &&
password_as_array.any? {|c| c.in? DIGITS } &&
password_as_array.any? {|c| c.in? SPECIAL_CHARS }
end
end

View File

@ -3,7 +3,7 @@
# helpers for managing users with special roles # helpers for managing users with special roles
class UserService class UserService
def self.create_partner(params) def self.create_partner(params)
generated_password = Devise.friendly_token.first(8) generated_password = SecurePassword.generate
group_id = Group.first.id group_id = Group.first.id
user = User.new( user = User.new(
email: params[:email], email: params[:email],
@ -31,7 +31,7 @@ class UserService
end end
def self.create_admin(params) def self.create_admin(params)
generated_password = Devise.friendly_token.first(8) generated_password = SecurePassword.generate
admin = User.new(params.merge(password: generated_password)) admin = User.new(params.merge(password: generated_password))
admin.send :set_slug admin.send :set_slug
@ -52,7 +52,7 @@ class UserService
end end
def self.create_manager(params) def self.create_manager(params)
generated_password = Devise.friendly_token.first(8) generated_password = SecurePassword.generate
manager = User.new(params.merge(password: generated_password)) manager = User.new(params.merge(password: generated_password))
manager.send :set_slug manager.send :set_slug

View File

@ -134,7 +134,7 @@ Devise.setup do |config|
# ==> Configuration for :validatable # ==> Configuration for :validatable
# Range for password length. # Range for password length.
config.password_length = 8..128 config.password_length = 12..128
# Email regex used to validate email formats. It simply asserts that # Email regex used to validate email formats. It simply asserts that
# one (and only one) @ exists in the given string. This is mainly # one (and only one) @ exists in the given string. This is mainly

View File

@ -2,6 +2,14 @@
# Be sure to restart your server when you modify this file. # Be sure to restart your server when you modify this file.
Rails.application.config.session_store :cookie_store, redis_host = ENV['REDIS_HOST'] || 'localhost'
Rails.application.config.session_store :redis_session_store,
redis: {
expire_after: 14.days, # cookie expiration
ttl: 14.days, # Redis expiration, defaults to 'expire_after'
key_prefix: 'fabmanager:session:',
url: "redis://#{redis_host}:6379",
},
key: '_Fab-manager_session', key: '_Fab-manager_session',
secure: (Rails.env.production? || Rails.env.staging?) && !Rails.application.secrets.allow_insecure_http secure: (Rails.env.production? || Rails.env.staging?) && !Rails.application.secrets.allow_insecure_http

View File

@ -71,7 +71,9 @@ de:
email_is_required: "E-Mail-Adresse ist erforderlich." email_is_required: "E-Mail-Adresse ist erforderlich."
your_password: "Passwort" your_password: "Passwort"
password_is_required: "Passwort ist erforderlich." password_is_required: "Passwort ist erforderlich."
password_is_too_short: "Paßwort ist zu kurz (mindestens 8 Zeichen)" password_is_too_short: "Password is too short (minimum 12 characters)"
password_is_too_weak: "Password is too weak:"
password_is_too_weak_explanations: "minimum 12 characters, at least one uppercase letter, one lowercase letter, one number and one special character"
type_your_password_again: "Passwort erneut eingeben" type_your_password_again: "Passwort erneut eingeben"
password_confirmation_is_required: "Passwortbestätigung ist erforderlich." password_confirmation_is_required: "Passwortbestätigung ist erforderlich."
password_does_not_match_with_confirmation: "Die beiden Passworteingaben sind nicht identisch." password_does_not_match_with_confirmation: "Die beiden Passworteingaben sind nicht identisch."
@ -101,7 +103,7 @@ de:
used_for_reservation: "Diese Daten werden im Fall einer Buchungsänderung verwendet" used_for_reservation: "Diese Daten werden im Fall einer Buchungsänderung verwendet"
used_for_profile: "Diese Daten werden nur auf deinem Profil angezeigt" used_for_profile: "Diese Daten werden nur auf deinem Profil angezeigt"
public_profile: "Sie werden über ein öffentliches Profil verfügen, andere Nutzer können Sie in ihren Projekten einbinden" public_profile: "Sie werden über ein öffentliches Profil verfügen, andere Nutzer können Sie in ihren Projekten einbinden"
you_will_receive_confirmation_instructions_by_email_detailed: "In wenigen Minuten wirst Du eine Email erhalten mit der du deine Anmeldung bestätigen kannst." you_will_receive_confirmation_instructions_by_email_detailed: "If your e-mail address is valid, you will receive an email with instructions about how to confirm your account in a few minutes."
#password modification modal #password modification modal
change_your_password: "Passwort ändern" change_your_password: "Passwort ändern"
your_new_password: "Ihr neues Passwort" your_new_password: "Ihr neues Passwort"
@ -117,8 +119,7 @@ de:
#confirmation modal #confirmation modal
you_will_receive_confirmation_instructions_by_email: "Sie erhalten eine Bestätigungsanleitung per E-Mail." you_will_receive_confirmation_instructions_by_email: "Sie erhalten eine Bestätigungsanleitung per E-Mail."
#forgotten password modal #forgotten password modal
your_email_address_is_unknown: "Ihre E-Mail-Adresse ist unbekannt." you_will_receive_in_a_moment_an_email_with_instructions_to_reset_your_password: "If your e-mail address is valid, you will receive in a moment an e-mail with instructions to reset your password."
you_will_receive_in_a_moment_an_email_with_instructions_to_reset_your_password: "Sie erhalten in Kürze eine E-Mail mit Anweisungen zum Zurücksetzen Ihres Passworts."
#Fab-manager's version #Fab-manager's version
version: "Version:" version: "Version:"
upgrade_fabmanager: "Fab-Manager aktualisieren" upgrade_fabmanager: "Fab-Manager aktualisieren"

View File

@ -71,7 +71,9 @@ en:
email_is_required: "E-mail address is required." email_is_required: "E-mail address is required."
your_password: "Your password" your_password: "Your password"
password_is_required: "Password is required." password_is_required: "Password is required."
password_is_too_short: "Password is too short (minimum 8 characters)" password_is_too_short: "Password is too short (minimum 12 characters)"
password_is_too_weak: "Password is too weak:"
password_is_too_weak_explanations: "minimum 12 characters, at least one uppercase letter, one lowercase letter, one number and one special character"
type_your_password_again: "Type your password again" type_your_password_again: "Type your password again"
password_confirmation_is_required: "Password confirmation is required." password_confirmation_is_required: "Password confirmation is required."
password_does_not_match_with_confirmation: "Password does not match with confirmation." password_does_not_match_with_confirmation: "Password does not match with confirmation."
@ -101,7 +103,7 @@ en:
used_for_reservation: "This data will be used in case of change on one of your bookings" used_for_reservation: "This data will be used in case of change on one of your bookings"
used_for_profile: "This data will only be displayed on your profile" used_for_profile: "This data will only be displayed on your profile"
public_profile: "You will have a public profile and other users will be able to associate you in their projects" public_profile: "You will have a public profile and other users will be able to associate you in their projects"
you_will_receive_confirmation_instructions_by_email_detailed: "You will receive an email with instructions about how to confirm your account in a few minutes." you_will_receive_confirmation_instructions_by_email_detailed: "If your e-mail address is valid, you will receive an email with instructions about how to confirm your account in a few minutes."
#password modification modal #password modification modal
change_your_password: "Change your password" change_your_password: "Change your password"
your_new_password: "Your new password" your_new_password: "Your new password"
@ -117,8 +119,7 @@ en:
#confirmation modal #confirmation modal
you_will_receive_confirmation_instructions_by_email: "You will receive confirmation instructions by email." you_will_receive_confirmation_instructions_by_email: "You will receive confirmation instructions by email."
#forgotten password modal #forgotten password modal
your_email_address_is_unknown: "Your e-mail address is unknown." you_will_receive_in_a_moment_an_email_with_instructions_to_reset_your_password: "If your e-mail address is valid, you will receive in a moment an e-mail with instructions to reset your password."
you_will_receive_in_a_moment_an_email_with_instructions_to_reset_your_password: "You will receive in a moment, an e-mail with instructions to reset your password."
#Fab-manager's version #Fab-manager's version
version: "Version:" version: "Version:"
upgrade_fabmanager: "Upgrade Fab-manager" upgrade_fabmanager: "Upgrade Fab-manager"

View File

@ -71,7 +71,9 @@ es:
email_is_required: "El e-mail es obligatorio." email_is_required: "El e-mail es obligatorio."
your_password: "Su contraseña" your_password: "Su contraseña"
password_is_required: "La contraseña es obligatoria." password_is_required: "La contraseña es obligatoria."
password_is_too_short: "La contraseña es demasiado corta (minimo 8 caracteres)" password_is_too_short: "Password is too short (minimum 12 characters)"
password_is_too_weak: "Password is too weak:"
password_is_too_weak_explanations: "minimum 12 characters, at least one uppercase letter, one lowercase letter, one number and one special character"
type_your_password_again: "Escriba su contraseña otra vez" type_your_password_again: "Escriba su contraseña otra vez"
password_confirmation_is_required: "Confirmar su contraseña es obligatorio." password_confirmation_is_required: "Confirmar su contraseña es obligatorio."
password_does_not_match_with_confirmation: "Las contraseñas no coinciden." password_does_not_match_with_confirmation: "Las contraseñas no coinciden."
@ -101,7 +103,7 @@ es:
used_for_reservation: "This data will be used in case of change on one of your bookings" used_for_reservation: "This data will be used in case of change on one of your bookings"
used_for_profile: "This data will only be displayed on your profile" used_for_profile: "This data will only be displayed on your profile"
public_profile: "You will have a public profile and other users will be able to associate you in their projects" public_profile: "You will have a public profile and other users will be able to associate you in their projects"
you_will_receive_confirmation_instructions_by_email_detailed: "Recibirá un correo electrónico con instrucciones sobre cómo confirmar su cuenta en unos minutos." you_will_receive_confirmation_instructions_by_email_detailed: "If your e-mail address is valid, you will receive an email with instructions about how to confirm your account in a few minutes."
#password modification modal #password modification modal
change_your_password: "Cambiar contraseña" change_your_password: "Cambiar contraseña"
your_new_password: "Nueva contraseña" your_new_password: "Nueva contraseña"
@ -117,8 +119,7 @@ es:
#confirmation modal #confirmation modal
you_will_receive_confirmation_instructions_by_email: "Recibirá las instrucciones de confirmación por email." you_will_receive_confirmation_instructions_by_email: "Recibirá las instrucciones de confirmación por email."
#forgotten password modal #forgotten password modal
your_email_address_is_unknown: "Se desconoce su email." you_will_receive_in_a_moment_an_email_with_instructions_to_reset_your_password: "If your e-mail address is valid, you will receive in a moment an e-mail with instructions to reset your password."
you_will_receive_in_a_moment_an_email_with_instructions_to_reset_your_password: "En un momento recibirá las instrucciones para restablecer su contraseña en su mail."
#Fab-manager's version #Fab-manager's version
version: "Versión:" version: "Versión:"
upgrade_fabmanager: "Upgrade Fab-manager" upgrade_fabmanager: "Upgrade Fab-manager"

View File

@ -71,7 +71,9 @@ fr:
email_is_required: "L'adresse de courriel est requise." email_is_required: "L'adresse de courriel est requise."
your_password: "Votre mot de passe" your_password: "Votre mot de passe"
password_is_required: "Le mot de passe est requis." password_is_required: "Le mot de passe est requis."
password_is_too_short: "Le mot de passe est trop court (au moins 8 caractères)" password_is_too_short: "Le mot de passe est trop court (au moins 12 caractères)"
password_is_too_weak: "La sécurité du mot de passe trop faible :"
password_is_too_weak_explanations: "12 caractères minimum, au moins une lettre majuscule, une lettre minuscule, un chiffre et un caractère spécial"
type_your_password_again: "Ressaisissez votre mot de passe" type_your_password_again: "Ressaisissez votre mot de passe"
password_confirmation_is_required: "La confirmation du mot de passe est requise." password_confirmation_is_required: "La confirmation du mot de passe est requise."
password_does_not_match_with_confirmation: "Le mot de passe ne concorde pas avec la confirmation." password_does_not_match_with_confirmation: "Le mot de passe ne concorde pas avec la confirmation."
@ -101,7 +103,7 @@ fr:
used_for_reservation: "Cette donnée sera utilisée en cas de changement sur une de vos réservations" used_for_reservation: "Cette donnée sera utilisée en cas de changement sur une de vos réservations"
used_for_profile: "Cette donnée sera seulement affichée sur votre profil" used_for_profile: "Cette donnée sera seulement affichée sur votre profil"
public_profile: "Vous aurez un profil public et les autres utilisateurs pourront vous associer à leurs projets" public_profile: "Vous aurez un profil public et les autres utilisateurs pourront vous associer à leurs projets"
you_will_receive_confirmation_instructions_by_email_detailed: "Vous allez recevoir dans quelques minutes un email comportant des instructions pour confirmer votre compte." you_will_receive_confirmation_instructions_by_email_detailed: "Si votre adresse e-mail est valide, vous recevrez dans quelques minutes un e-mail comportant des instructions pour confirmer votre compte."
#password modification modal #password modification modal
change_your_password: "Modifier votre mot de passe" change_your_password: "Modifier votre mot de passe"
your_new_password: "Votre nouveau mot de passe" your_new_password: "Votre nouveau mot de passe"
@ -117,8 +119,7 @@ fr:
#confirmation modal #confirmation modal
you_will_receive_confirmation_instructions_by_email: "Vous recevrez les instructions de confirmation par email." you_will_receive_confirmation_instructions_by_email: "Vous recevrez les instructions de confirmation par email."
#forgotten password modal #forgotten password modal
your_email_address_is_unknown: "Votre adresse de courriel est inconnue." you_will_receive_in_a_moment_an_email_with_instructions_to_reset_your_password: "Si votre adresse e-mail est valide, vous recevrez dans un instant un e-mail contenant les instructions pour réinitialiser votre mot de passe."
you_will_receive_in_a_moment_an_email_with_instructions_to_reset_your_password: "Vous allez recevoir sous quelques minutes un courriel vous indiquant comment réinitialiser votre mot de passe."
#Fab-manager's version #Fab-manager's version
version: "Version :" version: "Version :"
upgrade_fabmanager: "Mettez à jour Fab-manager" upgrade_fabmanager: "Mettez à jour Fab-manager"

View File

@ -71,7 +71,9 @@
email_is_required: "E-post adresse er påkrevd." email_is_required: "E-post adresse er påkrevd."
your_password: "Ditt passord" your_password: "Ditt passord"
password_is_required: "Passord må fylles ut." password_is_required: "Passord må fylles ut."
password_is_too_short: "Passordet er for kort (minimum 8 tegn)" password_is_too_short: "Password is too short (minimum 12 characters)"
password_is_too_weak: "Password is too weak:"
password_is_too_weak_explanations: "minimum 12 characters, at least one uppercase letter, one lowercase letter, one number and one special character"
type_your_password_again: "Skriv inn passordet igjen" type_your_password_again: "Skriv inn passordet igjen"
password_confirmation_is_required: "Passordbekreftelse er påkrevd." password_confirmation_is_required: "Passordbekreftelse er påkrevd."
password_does_not_match_with_confirmation: "Passordet stemmer ikke med bekreftelsen." password_does_not_match_with_confirmation: "Passordet stemmer ikke med bekreftelsen."
@ -101,7 +103,7 @@
used_for_reservation: "Disse dataene vil bli brukt i tilfelle endring på en av dine bestillinger" used_for_reservation: "Disse dataene vil bli brukt i tilfelle endring på en av dine bestillinger"
used_for_profile: "Disse dataene vil bare bli vist i profilen din" used_for_profile: "Disse dataene vil bare bli vist i profilen din"
public_profile: "Du vil ha en offentlig profil og andre brukere vil kunne knytte deg til deres prosjekter" public_profile: "Du vil ha en offentlig profil og andre brukere vil kunne knytte deg til deres prosjekter"
you_will_receive_confirmation_instructions_by_email_detailed: "Du vil om noen minutter motta en e-post med instruksjoner om hvordan du bekrefter din e-postadresse." you_will_receive_confirmation_instructions_by_email_detailed: "If your e-mail address is valid, you will receive an email with instructions about how to confirm your account in a few minutes."
#password modification modal #password modification modal
change_your_password: "Endre passord" change_your_password: "Endre passord"
your_new_password: "Ditt nye passord" your_new_password: "Ditt nye passord"
@ -117,8 +119,7 @@
#confirmation modal #confirmation modal
you_will_receive_confirmation_instructions_by_email: "Du vil motta instruksjoner om bekreftelse via e-post." you_will_receive_confirmation_instructions_by_email: "Du vil motta instruksjoner om bekreftelse via e-post."
#forgotten password modal #forgotten password modal
your_email_address_is_unknown: "E-postadressen er ukjent." you_will_receive_in_a_moment_an_email_with_instructions_to_reset_your_password: "If your e-mail address is valid, you will receive in a moment an e-mail with instructions to reset your password."
you_will_receive_in_a_moment_an_email_with_instructions_to_reset_your_password: "Vi har sendt deg en e-post med instruksjoner for å resette passordet ditt."
#Fab-manager's version #Fab-manager's version
version: "Versjon:" version: "Versjon:"
upgrade_fabmanager: "Oppgrader Fab-manager" upgrade_fabmanager: "Oppgrader Fab-manager"

View File

@ -71,7 +71,9 @@ pt:
email_is_required: "E-mail é obrigatório." email_is_required: "E-mail é obrigatório."
your_password: "Sua senha" your_password: "Sua senha"
password_is_required: "Senha é obrigatório." password_is_required: "Senha é obrigatório."
password_is_too_short: "Senha muito curta (mínimo 8 caracteres)" password_is_too_short: "Password is too short (minimum 12 characters)"
password_is_too_weak: "Password is too weak:"
password_is_too_weak_explanations: "minimum 12 characters, at least one uppercase letter, one lowercase letter, one number and one special character"
type_your_password_again: "Digite sua senha novamente" type_your_password_again: "Digite sua senha novamente"
password_confirmation_is_required: "Confirmação de senha é obrigatório." password_confirmation_is_required: "Confirmação de senha é obrigatório."
password_does_not_match_with_confirmation: "A senha não é igual ao da confirmação." password_does_not_match_with_confirmation: "A senha não é igual ao da confirmação."
@ -101,7 +103,7 @@ pt:
used_for_reservation: "Estes dados serão utilizados em caso de alteração em uma das suas reservas" used_for_reservation: "Estes dados serão utilizados em caso de alteração em uma das suas reservas"
used_for_profile: "Estes dados serão exibidos apenas no seu perfil" used_for_profile: "Estes dados serão exibidos apenas no seu perfil"
public_profile: "Você terá um perfil público e outros usuários poderão associá-lo em seus projetos" public_profile: "Você terá um perfil público e outros usuários poderão associá-lo em seus projetos"
you_will_receive_confirmation_instructions_by_email_detailed: "Você receberá um email com instruções sobre como confirmar sua conta em alguns minutos." you_will_receive_confirmation_instructions_by_email_detailed: "If your e-mail address is valid, you will receive an email with instructions about how to confirm your account in a few minutes."
#password modification modal #password modification modal
change_your_password: "Mudar sua senha" change_your_password: "Mudar sua senha"
your_new_password: "Sua nova senha" your_new_password: "Sua nova senha"
@ -117,8 +119,7 @@ pt:
#confirmation modal #confirmation modal
you_will_receive_confirmation_instructions_by_email: "Você receberá instruções de confirmação por e-mail." you_will_receive_confirmation_instructions_by_email: "Você receberá instruções de confirmação por e-mail."
#forgotten password modal #forgotten password modal
your_email_address_is_unknown: "Seu e-mail não está cadastrado." you_will_receive_in_a_moment_an_email_with_instructions_to_reset_your_password: "If your e-mail address is valid, you will receive in a moment an e-mail with instructions to reset your password."
you_will_receive_in_a_moment_an_email_with_instructions_to_reset_your_password: "Você irá receber um e-mail com as instruções para resetar sua senha."
#Fab-manager's version #Fab-manager's version
version: "Versão:" version: "Versão:"
upgrade_fabmanager: "Atualizar Fab-manager" upgrade_fabmanager: "Atualizar Fab-manager"

View File

@ -71,7 +71,9 @@ zu:
email_is_required: "crwdns8907:0crwdne8907:0" email_is_required: "crwdns8907:0crwdne8907:0"
your_password: "crwdns8909:0crwdne8909:0" your_password: "crwdns8909:0crwdne8909:0"
password_is_required: "crwdns8911:0crwdne8911:0" password_is_required: "crwdns8911:0crwdne8911:0"
password_is_too_short: "crwdns8913:0crwdne8913:0" password_is_too_short: "crwdns24030:0crwdne24030:0"
password_is_too_weak: "crwdns24032:0crwdne24032:0"
password_is_too_weak_explanations: "crwdns24034:0crwdne24034:0"
type_your_password_again: "crwdns8915:0crwdne8915:0" type_your_password_again: "crwdns8915:0crwdne8915:0"
password_confirmation_is_required: "crwdns8917:0crwdne8917:0" password_confirmation_is_required: "crwdns8917:0crwdne8917:0"
password_does_not_match_with_confirmation: "crwdns8919:0crwdne8919:0" password_does_not_match_with_confirmation: "crwdns8919:0crwdne8919:0"
@ -101,7 +103,7 @@ zu:
used_for_reservation: "crwdns8959:0crwdne8959:0" used_for_reservation: "crwdns8959:0crwdne8959:0"
used_for_profile: "crwdns8961:0crwdne8961:0" used_for_profile: "crwdns8961:0crwdne8961:0"
public_profile: "crwdns8963:0crwdne8963:0" public_profile: "crwdns8963:0crwdne8963:0"
you_will_receive_confirmation_instructions_by_email_detailed: "crwdns19598:0crwdne19598:0" you_will_receive_confirmation_instructions_by_email_detailed: "crwdns24036:0crwdne24036:0"
#password modification modal #password modification modal
change_your_password: "crwdns8965:0crwdne8965:0" change_your_password: "crwdns8965:0crwdne8965:0"
your_new_password: "crwdns8967:0crwdne8967:0" your_new_password: "crwdns8967:0crwdne8967:0"
@ -117,8 +119,7 @@ zu:
#confirmation modal #confirmation modal
you_will_receive_confirmation_instructions_by_email: "crwdns19602:0crwdne19602:0" you_will_receive_confirmation_instructions_by_email: "crwdns19602:0crwdne19602:0"
#forgotten password modal #forgotten password modal
your_email_address_is_unknown: "crwdns8983:0crwdne8983:0" you_will_receive_in_a_moment_an_email_with_instructions_to_reset_your_password: "crwdns24038:0crwdne24038:0"
you_will_receive_in_a_moment_an_email_with_instructions_to_reset_your_password: "crwdns8985:0crwdne8985:0"
#Fab-manager's version #Fab-manager's version
version: "crwdns8987:0crwdne8987:0" version: "crwdns8987:0crwdne8987:0"
upgrade_fabmanager: "crwdns19604:0crwdne19604:0" upgrade_fabmanager: "crwdns19604:0crwdne19604:0"

View File

@ -19,9 +19,9 @@ de:
extension_whitelist_error: "Sie sind nicht berechtigt, %{extension} Dateien hochzuladen, erlaubt sind die Typen: %{allowed_types}" extension_whitelist_error: "Sie sind nicht berechtigt, %{extension} Dateien hochzuladen, erlaubt sind die Typen: %{allowed_types}"
extension_blacklist_error: "Sie sind nicht berechtigt, %{extension} Dateien hochzuladen. Unerlaubte Typen: %{prohibited_types}" extension_blacklist_error: "Sie sind nicht berechtigt, %{extension} Dateien hochzuladen. Unerlaubte Typen: %{prohibited_types}"
content_type_whitelist_error: "Sie sind nicht berechtigt, %{content_type} Dateien hochzuladen, erlaubt sind die Typen: %{allowed_types}" content_type_whitelist_error: "Sie sind nicht berechtigt, %{content_type} Dateien hochzuladen, erlaubt sind die Typen: %{allowed_types}"
rmagick_processing_error: "Fehler beim Bearbeiten mit rmagick, vielleicht ist es kein Bild? Original Fehler: %{e}" rmagick_processing_error: "Failed to manipulate with rmagick, maybe it is not an image?"
mime_types_processing_error: "Fehler beim Verarbeiten der Datei mit MIME::Typen, möglicherweise kein gültiger Inhaltstyp? Original Fehler: %{e}" mime_types_processing_error: "Failed to process file with MIME::Types, maybe not valid content-type?"
mini_magick_processing_error: "Fehler beim Bearbeiten mit MiniMagick, vielleicht ist es kein Bild? Original Fehler: %{e}" mini_magick_processing_error: "Failed to manipulate the file, maybe it is not an image?"
wrong_size: "hat die falsche Größe (sollte %{file_size} sein)" wrong_size: "hat die falsche Größe (sollte %{file_size} sein)"
size_too_small: "ist zu klein (sollte mindestens %{file_size} sein)" size_too_small: "ist zu klein (sollte mindestens %{file_size} sein)"
size_too_big: "ist zu groß (sollte höchstens %{file_size} sein)" size_too_big: "ist zu groß (sollte höchstens %{file_size} sein)"

View File

@ -19,9 +19,9 @@ en:
extension_whitelist_error: "You are not allowed to upload %{extension} files, allowed types: %{allowed_types}" extension_whitelist_error: "You are not allowed to upload %{extension} files, allowed types: %{allowed_types}"
extension_blacklist_error: "You are not allowed to upload %{extension} files, prohibited types: %{prohibited_types}" extension_blacklist_error: "You are not allowed to upload %{extension} files, prohibited types: %{prohibited_types}"
content_type_whitelist_error: "You are not allowed to upload %{content_type} files, allowed types: %{allowed_types}" content_type_whitelist_error: "You are not allowed to upload %{content_type} files, allowed types: %{allowed_types}"
rmagick_processing_error: "Failed to manipulate with rmagick, maybe it is not an image? Original Error: %{e}" rmagick_processing_error: "Failed to manipulate with rmagick, maybe it is not an image?"
mime_types_processing_error: "Failed to process file with MIME::Types, maybe not valid content-type? Original Error: %{e}" mime_types_processing_error: "Failed to process file with MIME::Types, maybe not valid content-type?"
mini_magick_processing_error: "Failed to manipulate with MiniMagick, maybe it is not an image? Original Error: %{e}" mini_magick_processing_error: "Failed to manipulate the file, maybe it is not an image?"
wrong_size: "is the wrong size (should be %{file_size})" wrong_size: "is the wrong size (should be %{file_size})"
size_too_small: "is too small (should be at least %{file_size})" size_too_small: "is too small (should be at least %{file_size})"
size_too_big: "is too big (should be at most %{file_size})" size_too_big: "is too big (should be at most %{file_size})"

View File

@ -19,9 +19,9 @@ es:
extension_whitelist_error: "No puede subir archivos de extensión %{extension}, tipos permitidos: %{allowed_types}" extension_whitelist_error: "No puede subir archivos de extensión %{extension}, tipos permitidos: %{allowed_types}"
extension_blacklist_error: "No puede subir archivos de extensión %{extension}, tipos prohibidos: %{prohibited_types}" extension_blacklist_error: "No puede subir archivos de extensión %{extension}, tipos prohibidos: %{prohibited_types}"
content_type_whitelist_error: "No puede subir archivos de tipo %{content_type}, tipos permitidos: %{allowed_types}" content_type_whitelist_error: "No puede subir archivos de tipo %{content_type}, tipos permitidos: %{allowed_types}"
rmagick_processing_error: "Error al manipular con rmagick, ¿Está seguro de que el archivo es una imagen? Error original: %{e}" rmagick_processing_error: "Failed to manipulate with rmagick, maybe it is not an image?"
mime_types_processing_error: "Error al procesar archivo con MIME::Types, puede ser que el contenido no sea válido. Error original: %{e}" mime_types_processing_error: "Failed to process file with MIME::Types, maybe not valid content-type?"
mini_magick_processing_error: "Error al editar con MiniMagick, ¿Está seguro de que el archivo es una imagen? Error original: %{e}" mini_magick_processing_error: "Failed to manipulate the file, maybe it is not an image?"
wrong_size: "es de tamaño incorrecto (debería ser de %{file_size})" wrong_size: "es de tamaño incorrecto (debería ser de %{file_size})"
size_too_small: "es demasiado pequeño (debería ser de minimo %{file_size})" size_too_small: "es demasiado pequeño (debería ser de minimo %{file_size})"
size_too_big: "es demasiado grande (deberia ser de maximo %{file_size})" size_too_big: "es demasiado grande (deberia ser de maximo %{file_size})"

View File

@ -19,9 +19,9 @@ fr:
extension_whitelist_error: "Vous n'êtes pas autorisé à envoyer des fichiers %{extension}, les types autorisés sont : %{allowed_types}" extension_whitelist_error: "Vous n'êtes pas autorisé à envoyer des fichiers %{extension}, les types autorisés sont : %{allowed_types}"
extension_blacklist_error: "Vous n'êtes pas autorisé à envoyer des fichiers %{extension}, les types interdits sont : %{prohibited_types}" extension_blacklist_error: "Vous n'êtes pas autorisé à envoyer des fichiers %{extension}, les types interdits sont : %{prohibited_types}"
content_type_whitelist_error: "Vous n'êtes pas autorisé à envoyer des fichiers %{content_type}, les types autorisés sont : %{allowed_types}" content_type_whitelist_error: "Vous n'êtes pas autorisé à envoyer des fichiers %{content_type}, les types autorisés sont : %{allowed_types}"
rmagick_processing_error: "La manipulation avec rmagick a échoué, peut-être ne s'agit-il pas d'une image ? Erreur d'origine : %{e}" rmagick_processing_error: "Impossible de manipuler avec rmagick, peut-être n'est-ce pas une image ?"
mime_types_processing_error: "Le traitement avec MIME::Types a échoué, le content-type est-il correct ? Erreur d'origine : %{e}" mime_types_processing_error: "Impossible de traiter le fichier avec MIME::Types , peut-être pas de type de contenu valide ?"
mini_magick_processing_error: "La manipulation avec MiniMagick a échoué, peut-être ne s'agit-il pas d'une image ? Erreur d'origine : %{e}" mini_magick_processing_error: "Impossible de manipuler le fichier, peut-être n'est-ce pas une image ?"
wrong_size: "ne fait pas la bonne taille (doit comporter %{file_size})" wrong_size: "ne fait pas la bonne taille (doit comporter %{file_size})"
size_too_small: "est trop petit (au moins %{file_size})" size_too_small: "est trop petit (au moins %{file_size})"
size_too_big: "est trop grand (pas plus de %{file_size})" size_too_big: "est trop grand (pas plus de %{file_size})"

View File

@ -19,9 +19,9 @@
extension_whitelist_error: "Det er ikk tillatt å laste opp %{extension} filer. Tillatte filtyper er: %{allowed_types}" extension_whitelist_error: "Det er ikk tillatt å laste opp %{extension} filer. Tillatte filtyper er: %{allowed_types}"
extension_blacklist_error: "Det er ikk tillatt å laste opp %{extension} filer. Ikke tillate filtyper er: %{prohibited_types}" extension_blacklist_error: "Det er ikk tillatt å laste opp %{extension} filer. Ikke tillate filtyper er: %{prohibited_types}"
content_type_whitelist_error: "Det er ikk tillatt å laste opp %{content_type} filer. Tillatte filtyper er: %{allowed_types}" content_type_whitelist_error: "Det er ikk tillatt å laste opp %{content_type} filer. Tillatte filtyper er: %{allowed_types}"
rmagick_processing_error: "Kan ikke manipulere med rmagick, kanskje er det ikke et bilde? Opprinnelig feil: %{e}" rmagick_processing_error: "Failed to manipulate with rmagick, maybe it is not an image?"
mime_types_processing_error: "Kunne ikke behandle filen med MIME::Types, kanskje ikke gyldig innholdstype? Opprinnelig feil: %{e}" mime_types_processing_error: "Failed to process file with MIME::Types, maybe not valid content-type?"
mini_magick_processing_error: "Kunne ikke manipulere med MiniMagick, kanskje er det ikke et bilde? Opprinnelig feil: %{e}" mini_magick_processing_error: "Failed to manipulate the file, maybe it is not an image?"
wrong_size: "har feil filstørrelse (skal være %{file_size})" wrong_size: "har feil filstørrelse (skal være %{file_size})"
size_too_small: "er for liten (må være minst %{file_size})" size_too_small: "er for liten (må være minst %{file_size})"
size_too_big: "er for stor (må være maks %{file_size})" size_too_big: "er for stor (må være maks %{file_size})"

View File

@ -19,9 +19,9 @@ pt:
extension_whitelist_error: "Você não tem permissão para fazer o upload de arquivos com esta extensão %{extension}, tipos permitidos: %{allowed_types}" extension_whitelist_error: "Você não tem permissão para fazer o upload de arquivos com esta extensão %{extension}, tipos permitidos: %{allowed_types}"
extension_blacklist_error: "Você não tem permissão para carregar arquivos %{extension}, tipos proibidos: %{prohibited_types}" extension_blacklist_error: "Você não tem permissão para carregar arquivos %{extension}, tipos proibidos: %{prohibited_types}"
content_type_whitelist_error: "Você não tem permissão para enviar arquivos %{content_type}, tipos permitidos: %{allowed_types}" content_type_whitelist_error: "Você não tem permissão para enviar arquivos %{content_type}, tipos permitidos: %{allowed_types}"
rmagick_processing_error: "Falha ao manipular com rmagick, talvez não seja uma imagem? Erro original: %{e}" rmagick_processing_error: "Failed to manipulate with rmagick, maybe it is not an image?"
mime_types_processing_error: "Falha ao processar arquivo com MIME::Types, talvez o conteúdo não seja válido? Erro original: %{e}" mime_types_processing_error: "Failed to process file with MIME::Types, maybe not valid content-type?"
mini_magick_processing_error: "Falha na manipulação com MiniMagick, talvez isso não seja uma imagem? Erro original: %{e}" mini_magick_processing_error: "Failed to manipulate the file, maybe it is not an image?"
wrong_size: "é o tamanho errado (deveria ser %{file_size})" wrong_size: "é o tamanho errado (deveria ser %{file_size})"
size_too_small: "é muito pequeno (deve ser pelo menos %{file_size})" size_too_small: "é muito pequeno (deve ser pelo menos %{file_size})"
size_too_big: "é muito grande (deve ser no máximo %{file_size})" size_too_big: "é muito grande (deve ser no máximo %{file_size})"

View File

@ -19,9 +19,9 @@ zu:
extension_whitelist_error: "crwdns20842:0%{extension}crwdnd20842:0%{allowed_types}crwdne20842:0" extension_whitelist_error: "crwdns20842:0%{extension}crwdnd20842:0%{allowed_types}crwdne20842:0"
extension_blacklist_error: "crwdns20844:0%{extension}crwdnd20844:0%{prohibited_types}crwdne20844:0" extension_blacklist_error: "crwdns20844:0%{extension}crwdnd20844:0%{prohibited_types}crwdne20844:0"
content_type_whitelist_error: "crwdns20846:0%{content_type}crwdnd20846:0%{allowed_types}crwdne20846:0" content_type_whitelist_error: "crwdns20846:0%{content_type}crwdnd20846:0%{allowed_types}crwdne20846:0"
rmagick_processing_error: "crwdns3175:0%{e}crwdne3175:0" rmagick_processing_error: "crwdns24024:0crwdne24024:0"
mime_types_processing_error: "crwdns3177:0%{e}crwdne3177:0" mime_types_processing_error: "crwdns24026:0crwdne24026:0"
mini_magick_processing_error: "crwdns3179:0%{e}crwdne3179:0" mini_magick_processing_error: "crwdns24028:0crwdne24028:0"
wrong_size: "crwdns3181:0%{file_size}crwdne3181:0" wrong_size: "crwdns3181:0%{file_size}crwdne3181:0"
size_too_small: "crwdns3183:0%{file_size}crwdne3183:0" size_too_small: "crwdns3183:0%{file_size}crwdne3183:0"
size_too_big: "crwdns3185:0%{file_size}crwdne3185:0" size_too_big: "crwdns3185:0%{file_size}crwdne3185:0"

View File

@ -1,6 +1,6 @@
{ {
"name": "fab-manager", "name": "fab-manager",
"version": "5.4.14", "version": "5.4.15",
"description": "Fab-manager is the FabLab management solution. It provides a comprehensive, web-based, open-source tool to simplify your administrative tasks and your marker's projects.", "description": "Fab-manager is the FabLab management solution. It provides a comprehensive, web-based, open-source tool to simplify your administrative tasks and your marker's projects.",
"keywords": [ "keywords": [
"fablab", "fablab",

View File

@ -4,7 +4,7 @@ require 'test_helper'
class UserTest < ActiveSupport::TestCase class UserTest < ActiveSupport::TestCase
test 'must create wallet and profiles after create user' do test 'must create wallet and profiles after create user' do
u = User.create(username: 'user', email: 'userwallet@fabmanager.com', password: 'testpassword', password_confirmation: 'testpassword', u = User.create(username: 'user', email: 'userwallet@fabmanager.com', password: 'Testpassword1$', password_confirmation: 'Testpassword1$',
profile_attributes: { first_name: 'user', last_name: 'wallet', phone: '0123456789' }, profile_attributes: { first_name: 'user', last_name: 'wallet', phone: '0123456789' },
statistic_profile_attributes: { gender: true, birthday: 18.years.ago }) statistic_profile_attributes: { gender: true, birthday: 18.years.ago })
assert u.wallet.present? assert u.wallet.present?