diff options
37 files changed, 700 insertions, 319 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 37b12d997..065ad72e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ - Front end improvements: - Zoom out as much as necessary on body map page, even on mobile. #1958 - Show loading message on initial /around map load #1976 + - Ask for current password/send email on password change. #1974 + - Add minimum password length and common password checking. + - Nicer display of national phone numbers. - Bugfixes: - Fix bug specifying category in URL on /around. #1950 - Fix bug with multiple select-multiples on a page. #1951 @@ -18,6 +21,8 @@ - Fix error sending `requires_inspection` reports. #1961 - Admin improvements: - Admin can anonymize/hide all a user's reports. #1942 #1943 + - Admin can log a user out. #1975 + - Admin can remove a user's account details. #1944 - Superusers can have optional two-factor authentication. #1973 - UK: - Lazy load images in the footer. @@ -36,6 +36,7 @@ requires 'Authen::SASL'; requires 'Cache::Memcached'; requires 'Carp'; requires 'Crypt::Eksblowfish::Bcrypt'; +requires 'Data::Password::Common'; requires 'DateTime'; requires 'DateTime::Format::HTTP'; requires 'DateTime::Format::ISO8601'; @@ -83,7 +84,7 @@ requires 'Net::Domain::TLD', '1.75'; requires 'Net::Facebook::Oauth2', '0.10'; requires 'Net::OAuth'; requires 'Net::Twitter::Lite::WithAPIv1_1', '0.12008'; -requires 'Number::Phone'; +requires 'Number::Phone', '3.4003'; requires 'Path::Class'; requires 'POSIX'; requires 'Readonly'; diff --git a/cpanfile.snapshot b/cpanfile.snapshot index 7b3bb89f5..83d855c98 100644 --- a/cpanfile.snapshot +++ b/cpanfile.snapshot @@ -1433,6 +1433,20 @@ DISTRIBUTIONS Module::Build 0.35 Test::Exception 0 Test::More 0 + Data-Password-Common-0.004 + pathname: D/DA/DAGOLDEN/Data-Password-Common-0.004.tar.gz + provides: + Data::Password::Common 0.004 + requirements: + ExtUtils::MakeMaker 6.17 + File::ShareDir 0 + File::ShareDir::Install 0.03 + IO::File 0 + Search::Dict 0 + Sub::Exporter 0 + autodie 2.00 + strict 0 + warnings 0 Data-Visitor-0.28 pathname: D/DO/DOY/Data-Visitor-0.28.tar.gz provides: @@ -4534,15 +4548,18 @@ DISTRIBUTIONS Carp 0 ExtUtils::MakeMaker 0 POSIX 0 - Number-Phone-3.4002 - pathname: D/DC/DCANTRELL/Number-Phone-3.4002.tar.gz + Number-Phone-3.4003 + pathname: D/DC/DCANTRELL/Number-Phone-3.4003.tar.gz provides: - Number::Phone 3.4002 + Number::Phone 3.4003 Number::Phone::Country 1.93 Number::Phone::Country::Data 1.6 + Number::Phone::Formatter 1.0 + Number::Phone::Formatter::National 1.0 + Number::Phone::Formatter::NationallyPreferredIntl 1.0 Number::Phone::Formatter::Raw undef Number::Phone::Lib 1.0 - Number::Phone::NANP 1.5 + Number::Phone::NANP 1.6000 Number::Phone::NANP::AG 1.1 Number::Phone::NANP::AI 1.1 Number::Phone::NANP::AS 1.1 @@ -4552,7 +4569,7 @@ DISTRIBUTIONS Number::Phone::NANP::CA 1.1 Number::Phone::NANP::DM 1.1 Number::Phone::NANP::DO 1.1 - Number::Phone::NANP::Data 1.20170908113144 + Number::Phone::NANP::Data 1.20180203200229 Number::Phone::NANP::GD 1.1 Number::Phone::NANP::GU 1.1 Number::Phone::NANP::JM 1.1 @@ -4569,252 +4586,252 @@ DISTRIBUTIONS Number::Phone::NANP::VC 1.1 Number::Phone::NANP::VG 1.1 Number::Phone::NANP::VI 1.1 - Number::Phone::StubCountry 1.3 - Number::Phone::StubCountry::AC 1.20170908113147 - Number::Phone::StubCountry::AD 1.20170908113147 - Number::Phone::StubCountry::AE 1.20170908113147 - Number::Phone::StubCountry::AF 1.20170908113147 - Number::Phone::StubCountry::AG 1.20170908113147 - Number::Phone::StubCountry::AI 1.20170908113147 - Number::Phone::StubCountry::AL 1.20170908113147 - Number::Phone::StubCountry::AM 1.20170908113147 - Number::Phone::StubCountry::AO 1.20170908113147 - Number::Phone::StubCountry::AR 1.20170908113147 - Number::Phone::StubCountry::AS 1.20170908113147 - Number::Phone::StubCountry::AT 1.20170908113147 - Number::Phone::StubCountry::AU 1.20170908113147 - Number::Phone::StubCountry::AW 1.20170908113147 - Number::Phone::StubCountry::AX 1.20170908113147 - Number::Phone::StubCountry::AZ 1.20170908113147 - Number::Phone::StubCountry::BA 1.20170908113147 - Number::Phone::StubCountry::BB 1.20170908113147 - Number::Phone::StubCountry::BD 1.20170908113147 - Number::Phone::StubCountry::BE 1.20170908113147 - Number::Phone::StubCountry::BF 1.20170908113147 - Number::Phone::StubCountry::BG 1.20170908113147 - Number::Phone::StubCountry::BH 1.20170908113147 - Number::Phone::StubCountry::BI 1.20170908113147 - Number::Phone::StubCountry::BJ 1.20170908113147 - Number::Phone::StubCountry::BL 1.20170908113147 - Number::Phone::StubCountry::BM 1.20170908113147 - Number::Phone::StubCountry::BN 1.20170908113147 - Number::Phone::StubCountry::BO 1.20170908113147 - Number::Phone::StubCountry::BQ 1.20170908113147 - Number::Phone::StubCountry::BR 1.20170908113147 - Number::Phone::StubCountry::BS 1.20170908113147 - Number::Phone::StubCountry::BT 1.20170908113147 - Number::Phone::StubCountry::BW 1.20170908113147 - Number::Phone::StubCountry::BY 1.20170908113147 - Number::Phone::StubCountry::BZ 1.20170908113147 - Number::Phone::StubCountry::CA 1.20170908113147 - Number::Phone::StubCountry::CC 1.20170908113147 - Number::Phone::StubCountry::CD 1.20170908113147 - Number::Phone::StubCountry::CF 1.20170908113147 - Number::Phone::StubCountry::CG 1.20170908113147 - Number::Phone::StubCountry::CH 1.20170908113147 - Number::Phone::StubCountry::CI 1.20170908113147 - Number::Phone::StubCountry::CK 1.20170908113147 - Number::Phone::StubCountry::CL 1.20170908113147 - Number::Phone::StubCountry::CM 1.20170908113147 - Number::Phone::StubCountry::CN 1.20170908113147 - Number::Phone::StubCountry::CO 1.20170908113148 - Number::Phone::StubCountry::CR 1.20170908113148 - Number::Phone::StubCountry::CU 1.20170908113148 - Number::Phone::StubCountry::CV 1.20170908113148 - Number::Phone::StubCountry::CW 1.20170908113148 - Number::Phone::StubCountry::CX 1.20170908113148 - Number::Phone::StubCountry::CY 1.20170908113148 - Number::Phone::StubCountry::CZ 1.20170908113148 - Number::Phone::StubCountry::DE 1.20170908113148 - Number::Phone::StubCountry::DJ 1.20170908113148 - Number::Phone::StubCountry::DK 1.20170908113148 - Number::Phone::StubCountry::DM 1.20170908113148 - Number::Phone::StubCountry::DO 1.20170908113148 - Number::Phone::StubCountry::DZ 1.20170908113148 - Number::Phone::StubCountry::EC 1.20170908113148 - Number::Phone::StubCountry::EE 1.20170908113148 - Number::Phone::StubCountry::EG 1.20170908113148 - Number::Phone::StubCountry::EH 1.20170908113148 - Number::Phone::StubCountry::ER 1.20170908113148 - Number::Phone::StubCountry::ES 1.20170908113148 - Number::Phone::StubCountry::ET 1.20170908113148 - Number::Phone::StubCountry::FI 1.20170908113148 - Number::Phone::StubCountry::FJ 1.20170908113148 - Number::Phone::StubCountry::FK 1.20170908113148 - Number::Phone::StubCountry::FM 1.20170908113148 - Number::Phone::StubCountry::FO 1.20170908113148 - Number::Phone::StubCountry::FR 1.20170908113148 - Number::Phone::StubCountry::GA 1.20170908113148 - Number::Phone::StubCountry::GB 1.20170908113148 - Number::Phone::StubCountry::GD 1.20170908113148 - Number::Phone::StubCountry::GE 1.20170908113148 - Number::Phone::StubCountry::GF 1.20170908113148 - Number::Phone::StubCountry::GG 1.20170908113148 - Number::Phone::StubCountry::GH 1.20170908113148 - Number::Phone::StubCountry::GI 1.20170908113148 - Number::Phone::StubCountry::GL 1.20170908113148 - Number::Phone::StubCountry::GM 1.20170908113148 - Number::Phone::StubCountry::GN 1.20170908113148 - Number::Phone::StubCountry::GP 1.20170908113148 - Number::Phone::StubCountry::GQ 1.20170908113148 - Number::Phone::StubCountry::GR 1.20170908113148 - Number::Phone::StubCountry::GT 1.20170908113148 - Number::Phone::StubCountry::GU 1.20170908113148 - Number::Phone::StubCountry::GW 1.20170908113148 - Number::Phone::StubCountry::GY 1.20170908113148 - Number::Phone::StubCountry::HK 1.20170908113148 - Number::Phone::StubCountry::HN 1.20170908113148 - Number::Phone::StubCountry::HR 1.20170908113148 - Number::Phone::StubCountry::HT 1.20170908113148 - Number::Phone::StubCountry::HU 1.20170908113148 - Number::Phone::StubCountry::ID 1.20170908113148 - Number::Phone::StubCountry::IE 1.20170908113148 - Number::Phone::StubCountry::IL 1.20170908113148 - Number::Phone::StubCountry::IM 1.20170908113148 - Number::Phone::StubCountry::IN 1.20170908113148 - Number::Phone::StubCountry::IO 1.20170908113148 - Number::Phone::StubCountry::IQ 1.20170908113148 - Number::Phone::StubCountry::IR 1.20170908113148 - Number::Phone::StubCountry::IS 1.20170908113148 - Number::Phone::StubCountry::IT 1.20170908113148 - Number::Phone::StubCountry::JE 1.20170908113148 - Number::Phone::StubCountry::JM 1.20170908113148 - Number::Phone::StubCountry::JO 1.20170908113148 - Number::Phone::StubCountry::JP 1.20170908113148 - Number::Phone::StubCountry::KE 1.20170908113148 - Number::Phone::StubCountry::KG 1.20170908113148 - Number::Phone::StubCountry::KH 1.20170908113148 - Number::Phone::StubCountry::KI 1.20170908113148 - Number::Phone::StubCountry::KM 1.20170908113148 - Number::Phone::StubCountry::KN 1.20170908113148 - Number::Phone::StubCountry::KP 1.20170908113148 - Number::Phone::StubCountry::KR 1.20170908113148 - Number::Phone::StubCountry::KW 1.20170908113148 - Number::Phone::StubCountry::KY 1.20170908113148 - Number::Phone::StubCountry::KZ 1.20170908113148 - Number::Phone::StubCountry::LA 1.20170908113148 - Number::Phone::StubCountry::LB 1.20170908113148 - Number::Phone::StubCountry::LC 1.20170908113148 - Number::Phone::StubCountry::LI 1.20170908113148 - Number::Phone::StubCountry::LK 1.20170908113148 - Number::Phone::StubCountry::LR 1.20170908113148 - Number::Phone::StubCountry::LS 1.20170908113148 - Number::Phone::StubCountry::LT 1.20170908113148 - Number::Phone::StubCountry::LU 1.20170908113148 - Number::Phone::StubCountry::LV 1.20170908113148 - Number::Phone::StubCountry::LY 1.20170908113148 - Number::Phone::StubCountry::MA 1.20170908113148 - Number::Phone::StubCountry::MC 1.20170908113148 - Number::Phone::StubCountry::MD 1.20170908113148 - Number::Phone::StubCountry::ME 1.20170908113148 - Number::Phone::StubCountry::MF 1.20170908113148 - Number::Phone::StubCountry::MG 1.20170908113148 - Number::Phone::StubCountry::MH 1.20170908113148 - Number::Phone::StubCountry::MK 1.20170908113148 - Number::Phone::StubCountry::ML 1.20170908113148 - Number::Phone::StubCountry::MM 1.20170908113148 - Number::Phone::StubCountry::MN 1.20170908113148 - Number::Phone::StubCountry::MO 1.20170908113148 - Number::Phone::StubCountry::MP 1.20170908113148 - Number::Phone::StubCountry::MQ 1.20170908113148 - Number::Phone::StubCountry::MR 1.20170908113148 - Number::Phone::StubCountry::MS 1.20170908113148 - Number::Phone::StubCountry::MT 1.20170908113148 - Number::Phone::StubCountry::MU 1.20170908113148 - Number::Phone::StubCountry::MV 1.20170908113148 - Number::Phone::StubCountry::MW 1.20170908113148 - Number::Phone::StubCountry::MX 1.20170908113148 - Number::Phone::StubCountry::MY 1.20170908113148 - Number::Phone::StubCountry::MZ 1.20170908113148 - Number::Phone::StubCountry::NA 1.20170908113148 - Number::Phone::StubCountry::NC 1.20170908113148 - Number::Phone::StubCountry::NE 1.20170908113148 - Number::Phone::StubCountry::NF 1.20170908113148 - Number::Phone::StubCountry::NG 1.20170908113148 - Number::Phone::StubCountry::NI 1.20170908113148 - Number::Phone::StubCountry::NL 1.20170908113148 - Number::Phone::StubCountry::NO 1.20170908113148 - Number::Phone::StubCountry::NP 1.20170908113148 - Number::Phone::StubCountry::NR 1.20170908113148 - Number::Phone::StubCountry::NU 1.20170908113148 - Number::Phone::StubCountry::NZ 1.20170908113148 - Number::Phone::StubCountry::OM 1.20170908113148 - Number::Phone::StubCountry::PA 1.20170908113148 - Number::Phone::StubCountry::PE 1.20170908113148 - Number::Phone::StubCountry::PF 1.20170908113148 - Number::Phone::StubCountry::PG 1.20170908113148 - Number::Phone::StubCountry::PH 1.20170908113148 - Number::Phone::StubCountry::PK 1.20170908113148 - Number::Phone::StubCountry::PL 1.20170908113148 - Number::Phone::StubCountry::PM 1.20170908113148 - Number::Phone::StubCountry::PR 1.20170908113149 - Number::Phone::StubCountry::PS 1.20170908113149 - Number::Phone::StubCountry::PT 1.20170908113149 - Number::Phone::StubCountry::PW 1.20170908113149 - Number::Phone::StubCountry::PY 1.20170908113149 - Number::Phone::StubCountry::QA 1.20170908113149 - Number::Phone::StubCountry::RE 1.20170908113149 - Number::Phone::StubCountry::RO 1.20170908113149 - Number::Phone::StubCountry::RS 1.20170908113149 - Number::Phone::StubCountry::RU 1.20170908113149 - Number::Phone::StubCountry::RW 1.20170908113149 - Number::Phone::StubCountry::SA 1.20170908113149 - Number::Phone::StubCountry::SB 1.20170908113149 - Number::Phone::StubCountry::SC 1.20170908113149 - Number::Phone::StubCountry::SD 1.20170908113149 - Number::Phone::StubCountry::SE 1.20170908113149 - Number::Phone::StubCountry::SG 1.20170908113149 - Number::Phone::StubCountry::SH 1.20170908113149 - Number::Phone::StubCountry::SI 1.20170908113149 - Number::Phone::StubCountry::SJ 1.20170908113149 - Number::Phone::StubCountry::SK 1.20170908113149 - Number::Phone::StubCountry::SL 1.20170908113149 - Number::Phone::StubCountry::SM 1.20170908113149 - Number::Phone::StubCountry::SN 1.20170908113149 - Number::Phone::StubCountry::SO 1.20170908113149 - Number::Phone::StubCountry::SR 1.20170908113149 - Number::Phone::StubCountry::SS 1.20170908113149 - Number::Phone::StubCountry::ST 1.20170908113149 - Number::Phone::StubCountry::SV 1.20170908113149 - Number::Phone::StubCountry::SX 1.20170908113149 - Number::Phone::StubCountry::SY 1.20170908113149 - Number::Phone::StubCountry::SZ 1.20170908113149 - Number::Phone::StubCountry::TA 1.20170908113149 - Number::Phone::StubCountry::TC 1.20170908113149 - Number::Phone::StubCountry::TD 1.20170908113149 - Number::Phone::StubCountry::TG 1.20170908113149 - Number::Phone::StubCountry::TH 1.20170908113149 - Number::Phone::StubCountry::TJ 1.20170908113149 - Number::Phone::StubCountry::TK 1.20170908113149 - Number::Phone::StubCountry::TL 1.20170908113149 - Number::Phone::StubCountry::TM 1.20170908113149 - Number::Phone::StubCountry::TN 1.20170908113149 - Number::Phone::StubCountry::TO 1.20170908113149 - Number::Phone::StubCountry::TR 1.20170908113149 - Number::Phone::StubCountry::TT 1.20170908113149 - Number::Phone::StubCountry::TV 1.20170908113149 - Number::Phone::StubCountry::TW 1.20170908113149 - Number::Phone::StubCountry::TZ 1.20170908113149 - Number::Phone::StubCountry::UA 1.20170908113149 - Number::Phone::StubCountry::UG 1.20170908113149 - Number::Phone::StubCountry::US 1.20170908113149 - Number::Phone::StubCountry::UY 1.20170908113149 - Number::Phone::StubCountry::UZ 1.20170908113149 - Number::Phone::StubCountry::VA 1.20170908113149 - Number::Phone::StubCountry::VC 1.20170908113149 - Number::Phone::StubCountry::VE 1.20170908113149 - Number::Phone::StubCountry::VG 1.20170908113149 - Number::Phone::StubCountry::VI 1.20170908113149 - Number::Phone::StubCountry::VN 1.20170908113149 - Number::Phone::StubCountry::VU 1.20170908113149 - Number::Phone::StubCountry::WF 1.20170908113149 - Number::Phone::StubCountry::WS 1.20170908113149 - Number::Phone::StubCountry::YE 1.20170908113149 - Number::Phone::StubCountry::YT 1.20170908113149 - Number::Phone::StubCountry::ZA 1.20170908113149 - Number::Phone::StubCountry::ZM 1.20170908113149 - Number::Phone::StubCountry::ZW 1.20170908113149 - Number::Phone::UK 1.67 + Number::Phone::StubCountry 1.4000 + Number::Phone::StubCountry::AC 1.20180203200231 + Number::Phone::StubCountry::AD 1.20180203200231 + Number::Phone::StubCountry::AE 1.20180203200232 + Number::Phone::StubCountry::AF 1.20180203200232 + Number::Phone::StubCountry::AG 1.20180203200232 + Number::Phone::StubCountry::AI 1.20180203200232 + Number::Phone::StubCountry::AL 1.20180203200232 + Number::Phone::StubCountry::AM 1.20180203200232 + Number::Phone::StubCountry::AO 1.20180203200232 + Number::Phone::StubCountry::AR 1.20180203200232 + Number::Phone::StubCountry::AS 1.20180203200232 + Number::Phone::StubCountry::AT 1.20180203200232 + Number::Phone::StubCountry::AU 1.20180203200232 + Number::Phone::StubCountry::AW 1.20180203200232 + Number::Phone::StubCountry::AX 1.20180203200232 + Number::Phone::StubCountry::AZ 1.20180203200232 + Number::Phone::StubCountry::BA 1.20180203200232 + Number::Phone::StubCountry::BB 1.20180203200232 + Number::Phone::StubCountry::BD 1.20180203200232 + Number::Phone::StubCountry::BE 1.20180203200232 + Number::Phone::StubCountry::BF 1.20180203200232 + Number::Phone::StubCountry::BG 1.20180203200232 + Number::Phone::StubCountry::BH 1.20180203200232 + Number::Phone::StubCountry::BI 1.20180203200232 + Number::Phone::StubCountry::BJ 1.20180203200232 + Number::Phone::StubCountry::BL 1.20180203200232 + Number::Phone::StubCountry::BM 1.20180203200232 + Number::Phone::StubCountry::BN 1.20180203200232 + Number::Phone::StubCountry::BO 1.20180203200232 + Number::Phone::StubCountry::BQ 1.20180203200232 + Number::Phone::StubCountry::BR 1.20180203200232 + Number::Phone::StubCountry::BS 1.20180203200232 + Number::Phone::StubCountry::BT 1.20180203200232 + Number::Phone::StubCountry::BW 1.20180203200232 + Number::Phone::StubCountry::BY 1.20180203200232 + Number::Phone::StubCountry::BZ 1.20180203200232 + Number::Phone::StubCountry::CA 1.20180203200232 + Number::Phone::StubCountry::CC 1.20180203200232 + Number::Phone::StubCountry::CD 1.20180203200233 + Number::Phone::StubCountry::CF 1.20180203200233 + Number::Phone::StubCountry::CG 1.20180203200233 + Number::Phone::StubCountry::CH 1.20180203200233 + Number::Phone::StubCountry::CI 1.20180203200233 + Number::Phone::StubCountry::CK 1.20180203200233 + Number::Phone::StubCountry::CL 1.20180203200233 + Number::Phone::StubCountry::CM 1.20180203200233 + Number::Phone::StubCountry::CN 1.20180203200233 + Number::Phone::StubCountry::CO 1.20180203200234 + Number::Phone::StubCountry::CR 1.20180203200234 + Number::Phone::StubCountry::CU 1.20180203200234 + Number::Phone::StubCountry::CV 1.20180203200234 + Number::Phone::StubCountry::CW 1.20180203200234 + Number::Phone::StubCountry::CX 1.20180203200234 + Number::Phone::StubCountry::CY 1.20180203200234 + Number::Phone::StubCountry::CZ 1.20180203200234 + Number::Phone::StubCountry::DE 1.20180203200234 + Number::Phone::StubCountry::DJ 1.20180203200234 + Number::Phone::StubCountry::DK 1.20180203200234 + Number::Phone::StubCountry::DM 1.20180203200234 + Number::Phone::StubCountry::DO 1.20180203200234 + Number::Phone::StubCountry::DZ 1.20180203200234 + Number::Phone::StubCountry::EC 1.20180203200234 + Number::Phone::StubCountry::EE 1.20180203200234 + Number::Phone::StubCountry::EG 1.20180203200234 + Number::Phone::StubCountry::EH 1.20180203200234 + Number::Phone::StubCountry::ER 1.20180203200234 + Number::Phone::StubCountry::ES 1.20180203200234 + Number::Phone::StubCountry::ET 1.20180203200234 + Number::Phone::StubCountry::FI 1.20180203200234 + Number::Phone::StubCountry::FJ 1.20180203200234 + Number::Phone::StubCountry::FK 1.20180203200234 + Number::Phone::StubCountry::FM 1.20180203200234 + Number::Phone::StubCountry::FO 1.20180203200234 + Number::Phone::StubCountry::FR 1.20180203200234 + Number::Phone::StubCountry::GA 1.20180203200234 + Number::Phone::StubCountry::GB 1.20180203200234 + Number::Phone::StubCountry::GD 1.20180203200234 + Number::Phone::StubCountry::GE 1.20180203200234 + Number::Phone::StubCountry::GF 1.20180203200234 + Number::Phone::StubCountry::GG 1.20180203200234 + Number::Phone::StubCountry::GH 1.20180203200234 + Number::Phone::StubCountry::GI 1.20180203200234 + Number::Phone::StubCountry::GL 1.20180203200234 + Number::Phone::StubCountry::GM 1.20180203200234 + Number::Phone::StubCountry::GN 1.20180203200234 + Number::Phone::StubCountry::GP 1.20180203200234 + Number::Phone::StubCountry::GQ 1.20180203200234 + Number::Phone::StubCountry::GR 1.20180203200234 + Number::Phone::StubCountry::GT 1.20180203200234 + Number::Phone::StubCountry::GU 1.20180203200234 + Number::Phone::StubCountry::GW 1.20180203200234 + Number::Phone::StubCountry::GY 1.20180203200234 + Number::Phone::StubCountry::HK 1.20180203200234 + Number::Phone::StubCountry::HN 1.20180203200234 + Number::Phone::StubCountry::HR 1.20180203200234 + Number::Phone::StubCountry::HT 1.20180203200234 + Number::Phone::StubCountry::HU 1.20180203200234 + Number::Phone::StubCountry::ID 1.20180203200234 + Number::Phone::StubCountry::IE 1.20180203200235 + Number::Phone::StubCountry::IL 1.20180203200235 + Number::Phone::StubCountry::IM 1.20180203200235 + Number::Phone::StubCountry::IN 1.20180203200235 + Number::Phone::StubCountry::IO 1.20180203200235 + Number::Phone::StubCountry::IQ 1.20180203200235 + Number::Phone::StubCountry::IR 1.20180203200235 + Number::Phone::StubCountry::IS 1.20180203200235 + Number::Phone::StubCountry::IT 1.20180203200235 + Number::Phone::StubCountry::JE 1.20180203200235 + Number::Phone::StubCountry::JM 1.20180203200235 + Number::Phone::StubCountry::JO 1.20180203200235 + Number::Phone::StubCountry::JP 1.20180203200235 + Number::Phone::StubCountry::KE 1.20180203200235 + Number::Phone::StubCountry::KG 1.20180203200235 + Number::Phone::StubCountry::KH 1.20180203200235 + Number::Phone::StubCountry::KI 1.20180203200235 + Number::Phone::StubCountry::KM 1.20180203200235 + Number::Phone::StubCountry::KN 1.20180203200235 + Number::Phone::StubCountry::KP 1.20180203200235 + Number::Phone::StubCountry::KR 1.20180203200235 + Number::Phone::StubCountry::KW 1.20180203200235 + Number::Phone::StubCountry::KY 1.20180203200235 + Number::Phone::StubCountry::KZ 1.20180203200235 + Number::Phone::StubCountry::LA 1.20180203200235 + Number::Phone::StubCountry::LB 1.20180203200235 + Number::Phone::StubCountry::LC 1.20180203200235 + Number::Phone::StubCountry::LI 1.20180203200235 + Number::Phone::StubCountry::LK 1.20180203200235 + Number::Phone::StubCountry::LR 1.20180203200235 + Number::Phone::StubCountry::LS 1.20180203200235 + Number::Phone::StubCountry::LT 1.20180203200235 + Number::Phone::StubCountry::LU 1.20180203200235 + Number::Phone::StubCountry::LV 1.20180203200235 + Number::Phone::StubCountry::LY 1.20180203200235 + Number::Phone::StubCountry::MA 1.20180203200235 + Number::Phone::StubCountry::MC 1.20180203200235 + Number::Phone::StubCountry::MD 1.20180203200235 + Number::Phone::StubCountry::ME 1.20180203200235 + Number::Phone::StubCountry::MF 1.20180203200235 + Number::Phone::StubCountry::MG 1.20180203200235 + Number::Phone::StubCountry::MH 1.20180203200235 + Number::Phone::StubCountry::MK 1.20180203200235 + Number::Phone::StubCountry::ML 1.20180203200235 + Number::Phone::StubCountry::MM 1.20180203200235 + Number::Phone::StubCountry::MN 1.20180203200235 + Number::Phone::StubCountry::MO 1.20180203200235 + Number::Phone::StubCountry::MP 1.20180203200235 + Number::Phone::StubCountry::MQ 1.20180203200235 + Number::Phone::StubCountry::MR 1.20180203200235 + Number::Phone::StubCountry::MS 1.20180203200235 + Number::Phone::StubCountry::MT 1.20180203200235 + Number::Phone::StubCountry::MU 1.20180203200235 + Number::Phone::StubCountry::MV 1.20180203200235 + Number::Phone::StubCountry::MW 1.20180203200235 + Number::Phone::StubCountry::MX 1.20180203200235 + Number::Phone::StubCountry::MY 1.20180203200235 + Number::Phone::StubCountry::MZ 1.20180203200235 + Number::Phone::StubCountry::NA 1.20180203200235 + Number::Phone::StubCountry::NC 1.20180203200235 + Number::Phone::StubCountry::NE 1.20180203200235 + Number::Phone::StubCountry::NF 1.20180203200235 + Number::Phone::StubCountry::NG 1.20180203200235 + Number::Phone::StubCountry::NI 1.20180203200235 + Number::Phone::StubCountry::NL 1.20180203200235 + Number::Phone::StubCountry::NO 1.20180203200235 + Number::Phone::StubCountry::NP 1.20180203200235 + Number::Phone::StubCountry::NR 1.20180203200235 + Number::Phone::StubCountry::NU 1.20180203200235 + Number::Phone::StubCountry::NZ 1.20180203200235 + Number::Phone::StubCountry::OM 1.20180203200235 + Number::Phone::StubCountry::PA 1.20180203200235 + Number::Phone::StubCountry::PE 1.20180203200235 + Number::Phone::StubCountry::PF 1.20180203200235 + Number::Phone::StubCountry::PG 1.20180203200235 + Number::Phone::StubCountry::PH 1.20180203200235 + Number::Phone::StubCountry::PK 1.20180203200235 + Number::Phone::StubCountry::PL 1.20180203200235 + Number::Phone::StubCountry::PM 1.20180203200235 + Number::Phone::StubCountry::PR 1.20180203200235 + Number::Phone::StubCountry::PS 1.20180203200235 + Number::Phone::StubCountry::PT 1.20180203200235 + Number::Phone::StubCountry::PW 1.20180203200235 + Number::Phone::StubCountry::PY 1.20180203200235 + Number::Phone::StubCountry::QA 1.20180203200235 + Number::Phone::StubCountry::RE 1.20180203200235 + Number::Phone::StubCountry::RO 1.20180203200235 + Number::Phone::StubCountry::RS 1.20180203200235 + Number::Phone::StubCountry::RU 1.20180203200235 + Number::Phone::StubCountry::RW 1.20180203200235 + Number::Phone::StubCountry::SA 1.20180203200236 + Number::Phone::StubCountry::SB 1.20180203200236 + Number::Phone::StubCountry::SC 1.20180203200236 + Number::Phone::StubCountry::SD 1.20180203200236 + Number::Phone::StubCountry::SE 1.20180203200236 + Number::Phone::StubCountry::SG 1.20180203200236 + Number::Phone::StubCountry::SH 1.20180203200236 + Number::Phone::StubCountry::SI 1.20180203200236 + Number::Phone::StubCountry::SJ 1.20180203200236 + Number::Phone::StubCountry::SK 1.20180203200236 + Number::Phone::StubCountry::SL 1.20180203200236 + Number::Phone::StubCountry::SM 1.20180203200236 + Number::Phone::StubCountry::SN 1.20180203200236 + Number::Phone::StubCountry::SO 1.20180203200236 + Number::Phone::StubCountry::SR 1.20180203200236 + Number::Phone::StubCountry::SS 1.20180203200236 + Number::Phone::StubCountry::ST 1.20180203200236 + Number::Phone::StubCountry::SV 1.20180203200236 + Number::Phone::StubCountry::SX 1.20180203200236 + Number::Phone::StubCountry::SY 1.20180203200236 + Number::Phone::StubCountry::SZ 1.20180203200236 + Number::Phone::StubCountry::TA 1.20180203200236 + Number::Phone::StubCountry::TC 1.20180203200236 + Number::Phone::StubCountry::TD 1.20180203200236 + Number::Phone::StubCountry::TG 1.20180203200236 + Number::Phone::StubCountry::TH 1.20180203200236 + Number::Phone::StubCountry::TJ 1.20180203200236 + Number::Phone::StubCountry::TK 1.20180203200236 + Number::Phone::StubCountry::TL 1.20180203200236 + Number::Phone::StubCountry::TM 1.20180203200236 + Number::Phone::StubCountry::TN 1.20180203200236 + Number::Phone::StubCountry::TO 1.20180203200236 + Number::Phone::StubCountry::TR 1.20180203200236 + Number::Phone::StubCountry::TT 1.20180203200236 + Number::Phone::StubCountry::TV 1.20180203200236 + Number::Phone::StubCountry::TW 1.20180203200236 + Number::Phone::StubCountry::TZ 1.20180203200236 + Number::Phone::StubCountry::UA 1.20180203200236 + Number::Phone::StubCountry::UG 1.20180203200236 + Number::Phone::StubCountry::US 1.20180203200236 + Number::Phone::StubCountry::UY 1.20180203200236 + Number::Phone::StubCountry::UZ 1.20180203200236 + Number::Phone::StubCountry::VA 1.20180203200236 + Number::Phone::StubCountry::VC 1.20180203200236 + Number::Phone::StubCountry::VE 1.20180203200236 + Number::Phone::StubCountry::VG 1.20180203200236 + Number::Phone::StubCountry::VI 1.20180203200236 + Number::Phone::StubCountry::VN 1.20180203200236 + Number::Phone::StubCountry::VU 1.20180203200236 + Number::Phone::StubCountry::WF 1.20180203200236 + Number::Phone::StubCountry::WS 1.20180203200236 + Number::Phone::StubCountry::YE 1.20180203200236 + Number::Phone::StubCountry::YT 1.20180203200236 + Number::Phone::StubCountry::ZA 1.20180203200236 + Number::Phone::StubCountry::ZM 1.20180203200236 + Number::Phone::StubCountry::ZW 1.20180203200236 + Number::Phone::UK 1.68 Number::Phone::UK::Data 2.0 Number::Phone::UK::Exchanges 1.20060823121334 Number::Phone::UK::GG 1 diff --git a/perllib/Catalyst/Plugin/FixMyStreet/Session/StoreSessions.pm b/perllib/Catalyst/Plugin/FixMyStreet/Session/StoreSessions.pm new file mode 100644 index 000000000..5e7a3cede --- /dev/null +++ b/perllib/Catalyst/Plugin/FixMyStreet/Session/StoreSessions.pm @@ -0,0 +1,23 @@ +package Catalyst::Plugin::FixMyStreet::Session::StoreSessions; +use Moose::Role; +use namespace::autoclean; + +after set_authenticated => sub { + my $c = shift; + my $sessions = $c->user->get_extra_metadata('sessions'); + push @$sessions, $c->sessionid; + $c->user->set_extra_metadata('sessions', $sessions); + $c->user->update; +}; + +before logout => sub { + my $c = shift; + if (my $user = $c->user) { + my $sessions = $user->get_extra_metadata('sessions'); + $sessions = [ grep { $_ ne $c->sessionid } @$sessions ]; + @$sessions ? $user->set_extra_metadata('sessions', $sessions) : $user->unset_extra_metadata('sessions'); + $user->update; + } +}; + +__PACKAGE__; diff --git a/perllib/FixMyStreet/App.pm b/perllib/FixMyStreet/App.pm index 3108c5c01..008aea595 100644 --- a/perllib/FixMyStreet/App.pm +++ b/perllib/FixMyStreet/App.pm @@ -18,13 +18,14 @@ use URI; use URI::QueryParam; use Catalyst ( - 'Static::Simple', # + 'Static::Simple', 'Unicode::Encoding', 'Session', 'Session::Store::DBIC', 'Session::State::Cookie', # FIXME - we're using our own override atm 'Authentication', 'SmartURI', + 'FixMyStreet::Session::StoreSessions', ); extends 'Catalyst'; diff --git a/perllib/FixMyStreet/App/Controller/Admin.pm b/perllib/FixMyStreet/App/Controller/Admin.pm index a1d301249..85b6204fc 100644 --- a/perllib/FixMyStreet/App/Controller/Admin.pm +++ b/perllib/FixMyStreet/App/Controller/Admin.pm @@ -1423,10 +1423,14 @@ sub user_edit : Path('user_edit') : Args(1) { if ( $c->get_param('submit') and $c->get_param('unban') ) { $c->forward('unban_user', [ $user ]); + } elsif ( $c->get_param('submit') and $c->get_param('logout_everywhere') ) { + $c->forward('user_logout_everywhere', [ $user ]); } elsif ( $c->get_param('submit') and $c->get_param('anon_everywhere') ) { $c->forward('user_anon_everywhere', [ $user ]); } elsif ( $c->get_param('submit') and $c->get_param('hide_everywhere') ) { $c->forward('user_hide_everywhere', [ $user ]); + } elsif ( $c->get_param('submit') and $c->get_param('remove_account') ) { + $c->forward('user_remove_account', [ $user ]); } elsif ( $c->get_param('submit') ) { my $edited = 0; @@ -1756,6 +1760,15 @@ sub ban_user : Private { return 1; } +sub user_logout_everywhere : Private { + my ( $self, $c, $user ) = @_; + my $sessions = $user->get_extra_metadata('sessions'); + foreach (grep { $_ ne $c->sessionid } @$sessions) { + $c->delete_session_data("session:$_"); + } + $c->stash->{status_message} = _('That user has been logged out.'); +} + sub user_anon_everywhere : Private { my ( $self, $c, $user ) = @_; $user->problems->update({anonymous => 1}); @@ -1777,6 +1790,28 @@ sub user_hide_everywhere : Private { $c->stash->{status_message} = _('That user’s reports and updates have been hidden.'); } +# Anonymize and remove name from all problems/updates, disable all alerts. +# Remove their account's email address, phone number, password, etc. +sub user_remove_account : Private { + my ( $self, $c, $user ) = @_; + $c->forward('user_logout_everywhere', [ $user ]); + $user->problems->update({ anonymous => 1, name => '', send_questionnaire => 0 }); + $user->comments->update({ anonymous => 1, name => '' }); + $user->alerts->update({ whendisabled => \'current_timestamp' }); + $user->password('', 1); + $user->update({ + email => 'removed-' . $user->id . '@' . FixMyStreet->config('EMAIL_DOMAIN'), + email_verified => 0, + name => '', + phone => '', + phone_verified => 0, + title => undef, + twitter_id => undef, + facebook_id => undef, + }); + $c->stash->{status_message} = _('That user’s personal details have been removed.'); +} + sub unban_user : Private { my ( $self, $c, $user ) = @_; diff --git a/perllib/FixMyStreet/App/Controller/Auth.pm b/perllib/FixMyStreet/App/Controller/Auth.pm index b2d909f9c..533e6a9be 100644 --- a/perllib/FixMyStreet/App/Controller/Auth.pm +++ b/perllib/FixMyStreet/App/Controller/Auth.pm @@ -5,6 +5,7 @@ use namespace::autoclean; BEGIN { extends 'Catalyst::Controller'; } use Email::Valid; +use Data::Password::Common 'found'; use Digest::HMAC_SHA1 qw(hmac_sha1); use JSON::MaybeXS; use MIME::Base64; @@ -84,6 +85,12 @@ sub sign_in : Private { my $parsed = FixMyStreet::SMS->parse_username($username); if ($parsed->{username} && $password && $c->forward('authenticate', [ $parsed->{type}, $parsed->{username}, $password ])) { + # Upgrade hash count if necessary + my $cost = sprintf("%02d", FixMyStreet::DB::Result::User->cost); + if ($c->user->password !~ /^\$2a\$$cost\$/) { + $c->user->update({ password => $password }); + } + # unless user asked to be remembered limit the session to browser $c->set_session_cookie_expire(0) unless $remember_me; @@ -143,6 +150,11 @@ sub email_sign_in : Private { return; } + my $password = $c->get_param('password_register'); + if ($password) { + return unless $c->forward('/auth/test_password', [ $password ]); + } + # If user registration is disabled then bail out at this point # if there's not already a user with this email address. # NB this uses the same template as a successful sign in to stop @@ -156,8 +168,7 @@ sub email_sign_in : Private { } my $user_params = {}; - $user_params->{password} = $c->get_param('password_register') - if $c->get_param('password_register'); + $user_params->{password} = $password if $password; my $user = $c->model('DB::User')->new( $user_params ); my $token_data = { @@ -356,6 +367,55 @@ sub no_csrf_token : Private { $c->detach('/page_error_400_bad_request', []); } +=item common_password + +Returns 1/0 depending on if password is common or not. + +=cut + +sub common_password : Local : Args(0) { + my ($self, $c) = @_; + + my $password = $c->get_param('password_register'); + + my $return = JSON->true; + if (!$c->cobrand->call_hook('bypass_password_checks') && found($password)) { + $return = _('Please choose a less commonly-used password'); + } + + my $body = JSON->new->utf8->allow_nonref->encode($return); + $c->res->content_type('application/json; charset=utf-8'); + $c->res->body($body); +} + +=item test_password + +Checks a password is not too weak; returns true if okay, +false if weak (and sets stash error). + +=cut + +sub test_password : Private { + my ($self, $c, $password) = @_; + + return 1 if $c->cobrand->call_hook('bypass_password_checks'); + + my @errors; + + my $min_length = $c->cobrand->password_minimum_length; + push @errors, sprintf(_('Please make sure your password is at least %d characters long'), $min_length) + if length($password) < $min_length; + + push @errors, _('Please choose a less commonly-used password') + if found($password); + + if (@errors) { + $c->stash->{field_errors}->{password_register} = join('<br>', @errors); + return 0; + } + return 1; +} + =head2 sign_out Log the user out. Tell them we've done so. diff --git a/perllib/FixMyStreet/App/Controller/Auth/Phone.pm b/perllib/FixMyStreet/App/Controller/Auth/Phone.pm index 8387b9d64..8e3150df9 100644 --- a/perllib/FixMyStreet/App/Controller/Auth/Phone.pm +++ b/perllib/FixMyStreet/App/Controller/Auth/Phone.pm @@ -59,6 +59,11 @@ sub sign_in : Private { return; } + my $password = $c->get_param('password_register'); + if ($password) { + return unless $c->forward('/auth/test_password', [ $password ]); + } + (my $number = $parsed->{phone}->format) =~ s/\s+//g; if ( FixMyStreet->config('SIGNUPS_DISABLED') @@ -70,8 +75,7 @@ sub sign_in : Private { } my $user_params = {}; - $user_params->{password} = $c->get_param('password_register') - if $c->get_param('password_register'); + $user_params->{password} = $password if $password; my $user = $c->model('DB::User')->new( $user_params ); my $token_data = { diff --git a/perllib/FixMyStreet/App/Controller/Auth/Profile.pm b/perllib/FixMyStreet/App/Controller/Auth/Profile.pm index a58d2ddf6..87aff2261 100644 --- a/perllib/FixMyStreet/App/Controller/Auth/Profile.pm +++ b/perllib/FixMyStreet/App/Controller/Auth/Profile.pm @@ -19,7 +19,7 @@ verifying email, phone, password. =cut -sub auto { +sub auto : Private { my ( $self, $c ) = @_; $c->detach( '/auth/redirect' ) unless $c->user; @@ -49,10 +49,20 @@ sub change_password : Path('/auth/change_password') { my $new = $c->get_param('new_password') // ''; my $confirm = $c->get_param('confirm') // ''; + my $password_error; + + # Check existing password, if available + if ($c->user->password) { + my $current = $c->get_param('current_password') // ''; + $c->stash->{current_password} = $current; + $password_error = 'incorrect' unless $c->user->check_password($current); + } + # check for errors - my $password_error = + $password_error ||= !$new && !$confirm ? 'missing' : $new ne $confirm ? 'mismatch' + : !$c->forward('/auth/test_password', [ $new ]) ? 'failed' : ''; if ($password_error) { @@ -62,10 +72,17 @@ sub change_password : Path('/auth/change_password') { return; } - # we should have a usable password - save it to the user - $c->user->obj->update( { password => $new } ); - $c->stash->{password_changed} = 1; - + if ($c->user->password) { + # we should have a usable password - save it to the user + $c->user->obj->update( { password => $new } ); + $c->stash->{password_changed} = 1; + } else { + # Set up arguments for code sign in + $c->set_param('username', $c->user->username); + $c->set_param('password_register', $new); + $c->set_param('r', 'auth/change_password/success'); + $c->detach('/auth/code_sign_in'); + } } =head2 change_email @@ -148,6 +165,12 @@ sub change_phone_success : Path('/auth/change_phone/success') { $c->res->redirect('/my'); } +sub change_password_success : Path('/auth/change_password/success') { + my ( $self, $c ) = @_; + $c->flash->{flash_message} = _('Your password has been changed'); + $c->res->redirect('/my'); +} + sub generate_token : Path('/auth/generate_token') { my ($self, $c) = @_; diff --git a/perllib/FixMyStreet/App/Controller/Report/New.pm b/perllib/FixMyStreet/App/Controller/Report/New.pm index 1083f843b..85652450e 100644 --- a/perllib/FixMyStreet/App/Controller/Report/New.pm +++ b/perllib/FixMyStreet/App/Controller/Report/New.pm @@ -817,8 +817,10 @@ sub process_user : Private { } $c->forward('update_user', [ \%params ]); - $report->user->password( Utils::trim_text( $params{password_register} ) ) - if $params{password_register}; + if ($params{password_register}) { + $c->forward('/auth/test_password', [ $params{password_register} ]); + $report->user->password(Utils::trim_text($params{password_register})); + } return 1; } diff --git a/perllib/FixMyStreet/App/Controller/Report/Update.pm b/perllib/FixMyStreet/App/Controller/Report/Update.pm index 8b96b02d1..24b54d7b5 100644 --- a/perllib/FixMyStreet/App/Controller/Report/Update.pm +++ b/perllib/FixMyStreet/App/Controller/Report/Update.pm @@ -149,11 +149,14 @@ sub process_user : Private { $update->user->name( Utils::trim_text( $params{name} ) ) if $params{name}; - $update->user->password( Utils::trim_text( $params{password_register} ) ) - if $params{password_register}; $update->user->title( Utils::trim_text( $params{fms_extra_title} ) ) if $params{fms_extra_title}; + if ($params{password_register}) { + $c->forward('/auth/test_password', [ $params{password_register} ]); + $update->user->password(Utils::trim_text($params{password_register})); + } + return 1; } diff --git a/perllib/FixMyStreet/Cobrand/Borsetshire.pm b/perllib/FixMyStreet/Cobrand/Borsetshire.pm index 7ddcff469..d9b018d69 100644 --- a/perllib/FixMyStreet/Cobrand/Borsetshire.pm +++ b/perllib/FixMyStreet/Cobrand/Borsetshire.pm @@ -29,4 +29,6 @@ sub send_questionnaires { return 0; } +sub bypass_password_checks { 1 } + 1; diff --git a/perllib/FixMyStreet/Cobrand/Default.pm b/perllib/FixMyStreet/Cobrand/Default.pm index 7888f8ccf..c6ca5c56b 100644 --- a/perllib/FixMyStreet/Cobrand/Default.pm +++ b/perllib/FixMyStreet/Cobrand/Default.pm @@ -59,6 +59,14 @@ sub path_to_email_templates { return $paths; } +=item password_minimum_length + +Returns the minimum length a password can be set to. + +=cut + +sub password_minimum_length { 6 } + =item country Returns the country that this cobrand operates in, as an ISO3166-alpha2 code. diff --git a/perllib/FixMyStreet/Cobrand/Oxfordshire.pm b/perllib/FixMyStreet/Cobrand/Oxfordshire.pm index 23324e763..00f099278 100644 --- a/perllib/FixMyStreet/Cobrand/Oxfordshire.pm +++ b/perllib/FixMyStreet/Cobrand/Oxfordshire.pm @@ -15,8 +15,6 @@ sub is_council_with_case_management { return FixMyStreet->config('STAGING_SITE'); } -sub map_type { 'OSM' } - sub base_url { my $self = shift; return $self->next::method() if FixMyStreet->config('STAGING_SITE'); diff --git a/perllib/FixMyStreet/DB/Result/User.pm b/perllib/FixMyStreet/DB/Result/User.pm index 7e16308b9..a6b927ad1 100644 --- a/perllib/FixMyStreet/DB/Result/User.pm +++ b/perllib/FixMyStreet/DB/Result/User.pm @@ -125,11 +125,15 @@ with 'FixMyStreet::Roles::Extra'; __PACKAGE__->many_to_many( planned_reports => 'user_planned_reports', 'report' ); +sub cost { + FixMyStreet->test_mode ? 1 : 12; +} + __PACKAGE__->add_columns( "password" => { encode_column => 1, encode_class => 'Crypt::Eksblowfish::Bcrypt', - encode_args => { cost => 8 }, + encode_args => { cost => cost() }, encode_check_method => 'check_password', }, ); @@ -150,8 +154,9 @@ sub username { sub phone_display { my $self = shift; return $self->phone unless $self->phone; + my $country = FixMyStreet->config('PHONE_COUNTRY'); my $parsed = FixMyStreet::SMS->parse_username($self->phone); - return $parsed->{phone} ? $parsed->{phone}->format : $self->phone; + return $parsed->{phone} ? $parsed->{phone}->format_for_country($country) : $self->phone; } sub latest_anonymity { diff --git a/perllib/FixMyStreet/Test.pm b/perllib/FixMyStreet/Test.pm index 572ae0a44..aa1a63c21 100644 --- a/perllib/FixMyStreet/Test.pm +++ b/perllib/FixMyStreet/Test.pm @@ -7,6 +7,13 @@ use warnings FATAL => 'all'; use utf8; use Test::More; use mySociety::Locale; + +BEGIN { + use FixMyStreet; + FixMyStreet->test_mode(1); + mySociety::Locale::gettext_domain('FixMyStreet', 1); +} + use FixMyStreet::DB; my $db = FixMyStreet::DB->schema->storage; @@ -19,12 +26,6 @@ sub import { $db->txn_begin; } -BEGIN { - use FixMyStreet; - FixMyStreet->test_mode(1); - mySociety::Locale::gettext_domain('FixMyStreet', 1); -} - END { $db->txn_rollback if $db; } diff --git a/t/app/controller/admin/users.t b/t/app/controller/admin/users.t index e6cf51449..63295e26d 100644 --- a/t/app/controller/admin/users.t +++ b/t/app/controller/admin/users.t @@ -410,4 +410,30 @@ subtest "Hiding user's reports from admin" => sub { is $c, $count_u; }; +subtest "Logging user out" => sub { + my $mech2 = FixMyStreet::TestMech->new; + $mech2->log_in_ok($user->email); + $mech2->logged_in_ok; + + $mech->get_ok( '/admin/user_edit/' . $user->id ); + $mech->submit_form_ok({ button => 'logout_everywhere' }, 'Logging user out'); + $mech2->not_logged_in_ok; +}; + +subtest "Removing account from admin" => sub { + $mech->create_problems_for_body(4, 2237, 'Title'); + my $count_p = FixMyStreet::DB->resultset('Problem')->search({ user_id => $user->id })->count; + my $count_u = FixMyStreet::DB->resultset('Comment')->search({ user_id => $user->id })->count; + $mech->get_ok( '/admin/user_edit/' . $user->id ); + $mech->submit_form_ok({ button => 'remove_account' }, 'Removing account'); + my $c = FixMyStreet::DB->resultset('Problem')->search({ user_id => $user->id, anonymous => 1, name => '' })->count; + is $c, $count_p, 'All reports anon/nameless'; + $c = FixMyStreet::DB->resultset('Comment')->search({ user_id => $user->id, anonymous => 1, name => '' })->count; + is $c, $count_u, 'All updates anon/nameless'; + $user->discard_changes; + is $user->name, '', 'Name gone'; + is $user->password, '', 'Password gone'; + is $user->email, 'removed-' . $user->id . '@example.org', 'Email gone' +}; + done_testing(); diff --git a/t/app/controller/auth.t b/t/app/controller/auth.t index 70b970e2b..8cc7e4154 100644 --- a/t/app/controller/auth.t +++ b/t/app/controller/auth.t @@ -5,7 +5,7 @@ my $mech = FixMyStreet::TestMech->new; my $test_email = 'test@example.com'; my $test_email3 = 'newuser@example.org'; -my $test_password = 'foobar'; +my $test_password = 'foobar123'; END { done_testing(); @@ -277,6 +277,29 @@ subtest "check logging in with token" => sub { $mech->delete_header('Authorization'); }; +subtest 'check password length/common' => sub { + $mech->get_ok('/auth'); + $mech->submit_form_ok({ + form_name => 'general_auth', + fields => { username => $test_email, password_register => 'short' }, + button => 'sign_in_by_code', + }); + $mech->content_contains("Please make sure your password is at least"); + $mech->submit_form_ok({ + form_name => 'general_auth', + fields => { username => $test_email, password_register => 'common' }, + button => 'sign_in_by_code', + }); + $mech->content_contains("Please choose a less commonly-used password"); +}; + +subtest 'check common password AJAX call' => sub { + $mech->post_ok('/auth/common_password', { password_register => 'password' }); + $mech->content_contains("Please choose a less commonly-used password"); + $mech->post_ok('/auth/common_password', { password_register => 'squirblewirble' }); + $mech->content_contains("true"); +}; + subtest "Test two-factor authentication login" => sub { use Auth::GoogleAuth; my $auth = Auth::GoogleAuth->new; diff --git a/t/app/controller/auth_profile.t b/t/app/controller/auth_profile.t index c9daff7ae..4be1be12c 100644 --- a/t/app/controller/auth_profile.t +++ b/t/app/controller/auth_profile.t @@ -8,7 +8,7 @@ LWP::Protocol::PSGI->register($twilio->to_psgi_app, host => 'api.twilio.com'); my $test_email = 'test@example.com'; my $test_email2 = 'test@example.net'; -my $test_password = 'foobar'; +my $test_password = 'foobar123'; END { done_testing(); @@ -75,9 +75,68 @@ subtest "Test change password page" => sub { { form_name => 'change_password', fields => - { new_password => $test_password, confirm => $test_password, }, + { new_password => 'new_password', confirm => 'new_password', }, }, - "change_password with '$test_password' and '$test_password'" + "change_password with 'new_password' and 'new_password'" + ); + is $mech->uri->path, '/auth/change_password', + "still on change password page"; + $mech->content_contains('check your email'); + + $link = $mech->get_link_from_email; + $mech->get_ok($link); + is $mech->uri->path, '/my', "redirected to /my"; + + $mech->content_contains( 'password has been changed', + "found password changed" ); + + $user->discard_changes(); + ok $user->password, "user now has a password"; +}; + +# Change password, when already got one +subtest "Test change password page with current password" => sub { + $mech->get_ok('/auth/change_password'); + + ok my $form = $mech->form_name('change_password'), + "found change password form"; + is_deeply [ sort grep { $_ } map { $_->name } $form->inputs ], # + [ 'confirm', 'current_password', 'new_password', 'token' ], + "check we got expected fields (ie not old_password)"; + + # check the various ways the form can be wrong + for my $test ( + { current => '', new => '', conf => '', err => 'check the passwords', }, + { current => 'new_password', new => '', conf => '', err => 'enter a password', }, + { current => 'new_password', new => 'secret', conf => '', err => 'do not match', }, + { current => 'new_password', new => '', conf => 'secret', err => 'do not match', }, + { current => 'new_password', new => 'secret', conf => 'not_secret', err => 'do not match', }, + ) + { + $mech->get_ok('/auth/change_password'); + $mech->content_lacks( $test->{err}, "did not find expected error" ); + $mech->submit_form_ok( + { + form_name => 'change_password', + fields => + { current_password => $test->{current}, new_password => $test->{new}, confirm => $test->{conf}, }, + }, + "change_password with '$test->{current}', '$test->{new}' and '$test->{conf}'" + ); + $mech->content_contains( $test->{err}, "found expected error" ); + } + + my $user = FixMyStreet::App->model('DB::User')->find( { email => $test_email } ); + ok $user, "got a user"; + + $mech->get_ok('/auth/change_password'); + $mech->submit_form_ok( + { + form_name => 'change_password', + fields => + { current_password => 'new_password', new_password => $test_password, confirm => $test_password }, + }, + "change_password with 'new_password' and '$test_password'" ); is $mech->uri->path, '/auth/change_password', "still on change password page"; @@ -88,6 +147,20 @@ subtest "Test change password page" => sub { ok $user->password, "user now has a password"; }; +subtest 'check password length/common' => sub { + $mech->get_ok('/auth/change_password'); + $mech->submit_form_ok({ + form_name => 'change_password', + fields => { current_password => $test_password, new_password => 'short', confirm => 'short' }, + }); + $mech->content_contains("Please make sure your password is at least"); + $mech->submit_form_ok({ + form_name => 'change_password', + fields => { current_password => $test_password, new_password => 'common', confirm => 'common' }, + }); + $mech->content_contains("Please choose a less commonly-used password"); +}; + subtest "Test change email page" => sub { $mech->create_problems_for_body(1, 2514, 'Title1', { user => FixMyStreet::DB->resultset('User')->find( { email => $test_email } ) } ); diff --git a/t/app/controller/report_new.t b/t/app/controller/report_new.t index f5af6f082..3c120b0b0 100644 --- a/t/app/controller/report_new.t +++ b/t/app/controller/report_new.t @@ -1262,9 +1262,7 @@ for my $test ( is $user->title, $test->{'user_title'}, 'user title correct'; is_deeply $extras, $test->{extra}, 'extra contains correct values'; - $user->problems->delete; - $user->alerts->delete; - $user->delete; + $mech->delete_user($user); }; } @@ -1731,9 +1729,7 @@ subtest "extra google analytics code displayed on email confirmation problem cre $mech->content_contains( "'id': 'report/" . $report->id . "'", 'extra google code present' ); - $user->problems->delete; - $user->alerts->delete; - $user->delete; + $mech->delete_user($user); }; }; diff --git a/t/app/controller/report_new_text.t b/t/app/controller/report_new_text.t index 734b9dbb4..cb07e57ee 100644 --- a/t/app/controller/report_new_text.t +++ b/t/app/controller/report_new_text.t @@ -45,8 +45,8 @@ foreach my $test ( password_register => '', password_sign_in => '', }, changes => { - username => '+44 121 496 0000', - phone => '+44 121 496 0000', + username => '0121 496 0000', + phone => '0121 496 0000', }, errors => [ 'Please enter a mobile number', ], }, diff --git a/t/app/controller/report_update_text.t b/t/app/controller/report_update_text.t index 45b4e78c2..a3b767221 100644 --- a/t/app/controller/report_update_text.t +++ b/t/app/controller/report_update_text.t @@ -95,7 +95,7 @@ for my $test ( password_sign_in => '', }, changes => { - username => '+44 121 496 0000', + username => '0121 496 0000', }, field_errors => [ 'Please enter a mobile number' ] }, diff --git a/templates/web/base/admin/user-form.html b/templates/web/base/admin/user-form.html index 63d4858ab..9dc14c98d 100644 --- a/templates/web/base/admin/user-form.html +++ b/templates/web/base/admin/user-form.html @@ -200,8 +200,10 @@ </p> [% IF user AND NOT user.from_body %] <ul class="no-bullets danger-zone"> + <li><input class="btn-danger" type="submit" name="logout_everywhere" value="[% loc('Log out of all sessions') %]"> <li><input class="btn-danger" type="submit" name="anon_everywhere" value="[% loc('Make anonymous on all reports and updates') %]"> <li><input class="btn-danger" type="submit" name="hide_everywhere" value="[% loc('Hide all reports and updates') %]"> + <li><input class="btn-danger" type="submit" name="remove_account" value="[% loc('Remove account details') %]"> </ul> [% END %] diff --git a/templates/web/base/auth/change_password.html b/templates/web/base/auth/change_password.html index a32dbaf9c..7a38e0134 100644 --- a/templates/web/base/auth/change_password.html +++ b/templates/web/base/auth/change_password.html @@ -1,7 +1,9 @@ [% SET bclass = 'authpage'; SET bclass = 'fullwidthpage' IF password_changed; -INCLUDE 'header.html', title = loc('Change password'), bodyclass = bclass +SET title = loc('Set password'); +SET title = loc('Change password') IF c.user.password; +INCLUDE 'header.html', title = title bodyclass = bclass %] [% IF password_changed %] @@ -13,13 +15,15 @@ INCLUDE 'header.html', title = loc('Change password'), bodyclass = bclass [% ELSE %] -<h1>[% loc('Change password') %]</h1> +<h1>[% title %]</h1> <form action="[% c.uri_for_action('/auth/profile/change_password') %]" method="post" name="change_password" class="fieldset"> <input type="hidden" name="token" value="[% csrf_token %]"> <fieldset> - [% IF password_error; + [% IF password_error == 'failed' %] + <div class="form-error">[% field_errors.password_register %]</div> + [% ELSIF password_error; errors = { missing => loc('Please enter a password'), @@ -31,6 +35,14 @@ INCLUDE 'header.html', title = loc('Change password'), bodyclass = bclass <div class="form-error">[% loc_password_error %]</div> [% END %] +[% IF c.user.password %] + <div class="form-field"> + <label for="current_password">[% loc('Current password:') %]</label> + <input class="form-control" type="password" name="current_password" value="[% current_password | html %]"> + </div> + <hr> +[% END %] + <div class="form-field"> <label for="new_password">[% loc('New password:') %]</label> <input class="form-control" type="password" name="new_password" value="[% new_password | html %]"> @@ -40,7 +52,7 @@ INCLUDE 'header.html', title = loc('Change password'), bodyclass = bclass <input class="form-control" type="password" name="confirm" value="[% confirm | html %]"> </div> <div class="final-submit"> - <input type="submit" class="btn" value="[% loc('Change password') %]"> + <input type="submit" class="btn" value="[% title %]"> </div> </fieldset> diff --git a/templates/web/base/auth/change_phone.html b/templates/web/base/auth/change_phone.html index 27a2f63dd..62e9fa6d6 100644 --- a/templates/web/base/auth/change_phone.html +++ b/templates/web/base/auth/change_phone.html @@ -18,7 +18,7 @@ END [% IF c.user.phone_verified OR (c.user.phone AND NOT verifying) %] [% loc('Your phone number') %]: [% c.user.phone_display %] [% ELSIF c.user.phone %] -[% DEFAULT username = c.user.phone %] +[% DEFAULT username = c.user.phone_display %] [% END %] <form method="post" name="change_phone"> diff --git a/templates/web/base/auth/general.html b/templates/web/base/auth/general.html index 8fc5578c1..1e44bb68e 100644 --- a/templates/web/base/auth/general.html +++ b/templates/web/base/auth/general.html @@ -60,7 +60,9 @@ [% INCLUDE form_sign_in_yes %] <input type="hidden" name="oauth_need_email" value="1"> [% ELSE %] + [% IF NOT field_errors.password_register %] [% INCLUDE form_sign_in_yes %] + [% END %] [% INCLUDE form_sign_in_no %] [% END %] </div> @@ -114,14 +116,22 @@ <input class="form-control" type="text" name="name" value="" placeholder="[% loc('Your name') %]"> <label for="password_register">[% loc('Password (optional)') %]</label> + [% IF field_errors.password_register %] + <p class='form-error'>[% field_errors.password_register %]</p> + [% END %] <div class="general-notes"> <p>[% loc('Providing a name and password is optional, but doing so will allow you to more easily report problems, leave updates and manage your reports.') %]</p> </div> <div class="form-txt-submit-box"> - <input class="form-control" type="password" name="password_register" id="password_register" value="" placeholder="[% loc('Enter a password') %]"> + <input class="form-control js-password-validate" type="password" name="password_register" id="password_register" value="" placeholder="[% loc('Enter a password') %]"> <input class="green-btn" type="submit" name="sign_in_by_code" value="[% loc('Sign in') %]"> </div> + + <div class="general-notes"> + <p>[% tprintf(loc('Your password should include %d or more characters.'), c.cobrand.password_minimum_length) %]</p> + </div> + </div> [% END %] diff --git a/templates/web/base/js/translation_strings.html b/templates/web/base/js/translation_strings.html index ed95335a6..a2e3c16c5 100644 --- a/templates/web/base/js/translation_strings.html +++ b/templates/web/base/js/translation_strings.html @@ -1,4 +1,7 @@ [% FILTER collapse %] +var fixmystreet = fixmystreet || {}; +fixmystreet.password_minimum_length = [% c.cobrand.password_minimum_length %]; + translation_strings = { update: '[% loc('Please enter a message') | replace("'", "\\'") %]', title: '[% loc('Please enter a subject') | replace("'", "\\'") %]', @@ -19,6 +22,9 @@ password_sign_in: { required: '[% loc('Please enter a password') | replace("'", "\\'") %]' }, + password_register: { + short: '[% tprintf(loc('Please make sure your password is at least %d characters long'), c.cobrand.password_minimum_length) | replace("'", "\\'") %]', + }, phone: { required: '[% loc('Please enter your phone number') | replace("'", "\\'") %]' }, diff --git a/templates/web/base/report/_inspect.html b/templates/web/base/report/_inspect.html index 5088332ce..eb2564157 100644 --- a/templates/web/base/report/_inspect.html +++ b/templates/web/base/report/_inspect.html @@ -17,7 +17,7 @@ [% IF permissions.report_inspect AND problem.user.phone %] <p> <strong>[% loc('Phone Reporter:') %]</strong> - <a href="tel:[% problem.user.phone | html %]">[% problem.user.phone | html %]</a> + <a href="tel:[% problem.user.phone | html %]">[% problem.user.phone_display | html %]</a> </p> [% END %] <p> diff --git a/templates/web/base/report/new/form_user_loggedin.html b/templates/web/base/report/new/form_user_loggedin.html index bd4ce1cf7..04a93ef74 100644 --- a/templates/web/base/report/new/form_user_loggedin.html +++ b/templates/web/base/report/new/form_user_loggedin.html @@ -31,7 +31,7 @@ [% IF c.user.phone_verified %] <label for="form_phone">[% loc('Phone number') %]</label> - <input class="form-control" id="form_phone" name="phone" disabled type="text" value="[% c.user.phone | html %]"> + <input class="form-control" id="form_phone" name="phone" disabled type="text" value="[% c.user.phone_display | html %]"> [% END %] [% IF c.user.email_verified %] @@ -65,7 +65,7 @@ [% IF NOT c.user.phone_verified %] <label for="form_phone">[% loc('Phone number (optional)') %]</label> - <input class="form-control" type="text" value="[% report.user.phone | html %]" name="phone" id="form_phone"> + <input class="form-control" type="text" value="[% report.user.phone_display | html %]" name="phone" id="form_phone"> [% END %] [% IF NOT c.user.email_verified %] <label for="form_username">[% loc('Email address (optional)') %]</label> diff --git a/templates/web/base/report/new/form_user_loggedout_by_email.html b/templates/web/base/report/new/form_user_loggedout_by_email.html index e9519f573..975dbe704 100644 --- a/templates/web/base/report/new/form_user_loggedout_by_email.html +++ b/templates/web/base/report/new/form_user_loggedout_by_email.html @@ -36,13 +36,20 @@ </div> <label class="form-focus-hidden" for="password_register">[% loc('Password (optional)') %]</label> - + [% IF field_errors.password_register %] + <p class='form-error'>[% field_errors.password_register %]</p> + [% END %] <div class="general-notes form-focus-hidden"> <p>[% loc('Providing a password is optional, but doing so will allow you to more easily report problems, leave updates and manage your reports.') %]</p> </div> <div class="form-txt-submit-box form-focus-hidden"> - <input class="form-control" type="password" name="password_register" id="password_register" value="" placeholder="[% loc('Enter a password') %]"> + <input class="form-control js-password-validate" type="password" name="password_register" id="password_register" value="" placeholder="[% loc('Enter a password') %]"> <input class="green-btn js-submit_register" type="submit" name="submit_register" value="[% loc('Submit') %]"> </div> + + <div class="general-notes"> + <p>[% tprintf(loc('Your password should include %d or more characters.'), c.cobrand.password_minimum_length) %]</p> + </div> + </div> diff --git a/templates/web/base/report/update/form_user_loggedout_by_email.html b/templates/web/base/report/update/form_user_loggedout_by_email.html index 7d10fe391..d038cdb23 100644 --- a/templates/web/base/report/update/form_user_loggedout_by_email.html +++ b/templates/web/base/report/update/form_user_loggedout_by_email.html @@ -8,14 +8,21 @@ [% INCLUDE 'report/update/form_name.html' %] <label for="password_register">[% loc('Password (optional)') %]</label> + [% IF field_errors.password_register %] + <p class='form-error'>[% field_errors.password_register %]</p> + [% END %] <div class="general-notes"> <p>[% loc('Providing a password is optional, but doing so will allow you to more easily report problems, leave updates and manage your reports.') %]</p> </div> <div class="form-txt-submit-box"> - <input type="password" class="form-control" name="password_register" id="password_register" value="" placeholder="[% loc('Enter a password') %]"> + <input type="password" class="form-control js-password-validate" name="password_register" id="password_register" value="" placeholder="[% loc('Enter a password') %]"> <input class="green-btn js-submit_register" type="submit" name="submit_register" value="[% loc('Post') %]"> </div> + <div class="general-notes"> + <p>[% tprintf(loc('Your password should include %d or more characters.'), c.cobrand.password_minimum_length) %]</p> + </div> + </div> diff --git a/templates/web/bromley/report/new/form_user.html b/templates/web/bromley/report/new/form_user.html index e6749f5ab..cce985c95 100644 --- a/templates/web/bromley/report/new/form_user.html +++ b/templates/web/bromley/report/new/form_user.html @@ -91,15 +91,23 @@ </div> <label class="form-focus-hidden" for="password_register">[% loc('Password (optional)') %]</label> + [% IF field_errors.password_register %] + <p class='form-error'>[% field_errors.password_register %]</p> + [% END %] <div class="general-notes form-focus-hidden"> <p>[% loc('Providing a password is optional, but doing so will allow you to more easily report future problems, leave updates and manage your reports.') %]</p> </div> <div class="form-txt-submit-box form-focus-hidden"> - <input class="form-control" type="password" name="password_register" id="password_register" value="" placeholder="[% loc('Enter a password') %]"> + <input class="form-control js-password-validate" type="password" name="password_register" id="password_register" value="" placeholder="[% loc('Enter a password') %]"> <input class="green-btn js-submit_register" type="submit" name="submit_register" value="[% loc('Submit') %]"> </div> + + <div class="general-notes"> + <p>[% tprintf(loc('Your password should include %d or more characters.'), c.cobrand.password_minimum_length) %]</p> + </div> + </div> <div id="form_sign_in_yes" class="form-box"> diff --git a/templates/web/bromley/report/update-form.html b/templates/web/bromley/report/update-form.html index 45d7aed5e..9778a0db3 100644 --- a/templates/web/bromley/report/update-form.html +++ b/templates/web/bromley/report/update-form.html @@ -98,15 +98,23 @@ [% INCLUDE 'report/update/form_name.html' %] <label for="password_register">[% loc('Password (optional)') %]</label> + [% IF field_errors.password_register %] + <p class='form-error'>[% field_errors.password_register %]</p> + [% END %] <div class="general-notes"> <p>[% loc('Providing a password is optional, but doing so will allow you to more easily report problems, leave updates and manage your reports.') %]</p> </div> <div class="form-txt-submit-box"> - <input type="password" class="form-control" name="password_register" id="password_register" value="" placeholder="[% loc('Enter a password') %]"> + <input type="password" class="form-control js-password-validate" name="password_register" id="password_register" value="" placeholder="[% loc('Enter a password') %]"> <input class="green-btn js-submit_register" type="submit" name="submit_register" value="[% loc('Post') %]"> </div> + + <div class="general-notes"> + <p>[% tprintf(loc('Your password should include %d or more characters.'), c.cobrand.password_minimum_length) %]</p> + </div> + </div> <div id="form_sign_in_yes" class="form-box"> <h5>Confirm my report with my FixMyStreet password</h5> diff --git a/templates/web/fixamingata/report/new/form_user_loggedout.html b/templates/web/fixamingata/report/new/form_user_loggedout.html index 1f7cf2aeb..bbb9864a3 100644 --- a/templates/web/fixamingata/report/new/form_user_loggedout.html +++ b/templates/web/fixamingata/report/new/form_user_loggedout.html @@ -27,23 +27,32 @@ </div> <label for="form_phone">[% loc('Phone number (optional)') %]</label> - <input type="text" class="form-control" value="[% report.user.phone | html %]" name="phone" id="form_phone" placeholder="[% loc('Your phone number') %]"> + <input type="text" class="form-control" value="[% report.user.phone_display | html %]" name="phone" id="form_phone" placeholder="[% loc('Your phone number') %]"> <div class="general-notes form-focus-hidden"> <p>[% loc('We never show your email address or phone number.') %]</p> </div> <label for="password_register">[% loc('Password (optional)') %]</label> + [% IF field_errors.password_register %] + <p class='form-error'>[% field_errors.password_register %]</p> + [% END %] <div class="general-notes form-focus-hidden"> <p>[% loc('Providing a password is optional, but doing so will allow you to more easily report problems, leave updates and manage your reports.') %]</p> </div> <div class="form-txt-submit-box"> - <input type="password" class="form-control" name="password_register" id="password_register" value="" placeholder="[% loc('Enter a password') %]"> + <input type="password" class="form-control js-password-validate" name="password_register" id="password_register" value="" placeholder="[% loc('Enter a password') %]"> <input class="green-btn js-submit_register" type="submit" name="submit_register" value="[% loc('Submit') %]"> </div> + + <div class="general-notes"> + <p>[% tprintf(loc('Your password should include %d or more characters.'), c.cobrand.password_minimum_length) %]</p> + </div> + </div> + <div id="form_sign_in_yes" class="form-box"> <h5>Jag har ett lösenord sedan tidigare:</h5> diff --git a/templates/web/zurich/auth/general.html b/templates/web/zurich/auth/general.html index 899f0ca71..555a72374 100644 --- a/templates/web/zurich/auth/general.html +++ b/templates/web/zurich/auth/general.html @@ -1,16 +1,6 @@ [% INCLUDE 'header.html', title = loc('Sign in or create an account') %] -[% IF username_error; - - # other keys include fqdn, mxcheck if you'd like to write a custom error message - - errors = { - missing_email = loc('Please enter your email'), - other_email = loc('Please check your email address is correct') - }; - - loc_username_error = errors.$username_error || errors.other_email; -END %] +[% loc_username_error = INCLUDE 'auth/_username_error.html' default='email' %] <form action="/auth" method="post" name="general_auth_login" class="validate"> <fieldset> @@ -61,11 +51,18 @@ END %] <input type="text" class="required" name="name" value="" placeholder="[% loc('Your name') %]"> <label for="password_register">[% loc('Password (optional)') %]</label> + [% IF field_errors.password_register %] + <p class='form-error'>[% field_errors.password_register %]</p> + [% END %] <div class="form-txt-submit-box"> - <input type="password" class="required" name="password_register" id="password_register" value="" placeholder="[% loc('Enter a password') %]"> + <input type="password" class="required js-password-validate" name="password_register" id="password_register" value="" placeholder="[% loc('Enter a password') %]"> <input class="green-btn" type="submit" name="sign_in_by_code" value="Registrieren"> </div> + <div class="general-notes"> + <p>[% tprintf(loc('Your password should include %d or more characters.'), c.cobrand.password_minimum_length) %]</p> + </div> + </div> </fieldset> </form> diff --git a/web/cobrands/fixmystreet/fixmystreet.js b/web/cobrands/fixmystreet/fixmystreet.js index a3ac5b71a..0aa01e483 100644 --- a/web/cobrands/fixmystreet/fixmystreet.js +++ b/web/cobrands/fixmystreet/fixmystreet.js @@ -285,6 +285,9 @@ $.extend(fixmystreet.set_up, { if (jQuery.validator) { jQuery.validator.addMethod('validCategory', function(value, element) { return this.optional(element) || value != '-- Pick a category --'; }, translation_strings.category ); + jQuery.validator.addMethod('js-password-validate', function(value, element) { + return !value || value.length >= fixmystreet.password_minimum_length; + }, translation_strings.password_register.short); } var submitted = false; diff --git a/web/js/validation_rules.js b/web/js/validation_rules.js index 5295a53ca..e6d745336 100644 --- a/web/js/validation_rules.js +++ b/web/js/validation_rules.js @@ -1,5 +1,11 @@ validation_rules = { title: { required: true }, detail: { required: true }, - update: { required: true } + update: { required: true }, + password_register: { + remote: { + url: '/auth/common_password', + type: 'post' + } + } }; |