pwned 1.2.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1e197213ac23ae94598dbf91b7a09fadd740db379d5abefdb8ad7c9623d9514b
4
- data.tar.gz: 960c6e4ab1d856480dbc236059abea531dc747f4a1d59a7e7db52534c2958866
3
+ metadata.gz: 4661790082f543ba897baf211da660c7a4f654444121f4ff3ba08542c08c412b
4
+ data.tar.gz: f52a3f3cf36d461e8704a632f97829a5d9f871d9916a55687d4a0b2156b44b75
5
5
  SHA512:
6
- metadata.gz: dede2324974438b89612b443e50c8d7c06fe9b35d3e5c6f36661ad73d28513ebb62eedbc60f0a72d346c935141b96f30b98a6ad21cb2950f45a418c76d33c6b1
7
- data.tar.gz: 32a0659ce0a7b80967ebf68809bf5881623f473ab561dfcba9f131eb86af60b9f0cb4031603c85c295d37f9d7aae10ce03ae3a580c3cc437c486cff099a4f962
6
+ metadata.gz: c114c3ca6e7667d1760ad2ae5dabcc7bf8d14b91e42788f7e36bba716eecd9bef6e1847e93dd12df4f8afed19460d26a068dc22ffb2270ceef8dc342f81690e0
7
+ data.tar.gz: c19d20d765cd57e64468c27a3e8f134e53d8f6e9ae22497c2d94a315a584e2e19b1913d47b37e89c9525ce80d85d10d43437a623d211766aa7242dbe1144e906
@@ -3,21 +3,25 @@ language: ruby
3
3
 
4
4
  env:
5
5
  matrix:
6
- - RAILS_VERSION=4.2.0
7
- - RAILS_VERSION=5.0.0
8
- - RAILS_VERSION=5.1.0
9
- - RAILS_VERSION=5.2.0.rc1
6
+ - RAILS_VERSION=4.2.11.1
7
+ - RAILS_VERSION=5.0.7.2
8
+ - RAILS_VERSION=5.1.7
9
+ - RAILS_VERSION=5.2.3
10
+ - RAILS_VERSION=6.0.0
10
11
 
11
12
  rvm:
12
- - 2.5.0
13
- - 2.4.0
14
- - 2.3.0
13
+ - 2.7
14
+ - 2.6
15
+ - 2.5
16
+ - 2.4
15
17
  - jruby
16
18
  - ruby-head
17
19
 
18
- before_install: gem install bundler -v 1.16.1
20
+ before_install: gem install bundler
19
21
 
20
22
  matrix:
21
23
  allow_failures:
22
24
  - rvm: ruby-head
23
- - env: RAILS_VERSION=5.2.0.rc1
25
+ exclude:
26
+ - rvm: 2.4
27
+ env: RAILS_VERSION=6.0.0
@@ -0,0 +1 @@
1
+ --output-dir docs
@@ -1,26 +1,81 @@
1
1
  # Changelog for `Pwned`
2
2
 
3
- ## Ongoing [☰](https://github.com/philnash/pwned/compare/v1.1.0...master)
3
+ ## Ongoing [☰](https://github.com/philnash/pwned/compare/v2.0.2...master)
4
4
 
5
- ...
5
+ ## 2.1.0 (July 8, 2020) [☰](https://github.com/philnash/pwned/compare/v2.0.2...v2.1.0)
6
6
 
7
- ## 1.2.0 (March 15, 2018) [☰](https://github.com/philnash/pwned/commits/v1.2.0)
7
+ - Minor updates
8
8
 
9
- * Major updates
10
- * Changes `PwnedValidator` to `NotPwnedValidator`, so that the validation looks like `validates :password, not_pwned: true`. `PwnedValidator` now subclasses `NotPwnedValidator` for backwards compatibility with version 1.1.0 but is deprecated.
9
+ - Adds `Pwned::HashedPassword` class which is initializd with a SHA1 hash to
10
+ query the API with so that the lookup can be done in the background without
11
+ storing passwords. Fixes #19, thanks [@paprikati](https://github.com/paprikati).
11
12
 
12
- ## 1.1.0 (March 12, 2018) [☰](https://github.com/philnash/pwned/commits/v1.1.0)
13
+ ## 2.0.2 (May 20, 2020) [☰](https://github.com/philnash/pwned/compare/v2.0.1...v2.0.2)
13
14
 
14
- * Major updates
15
- * Refactors exception handling with built in Ruby method ([PR #1](https://github.com/philnash/pwned/pull/1) thanks [@kpumuk](https://github.com/kpumuk))
16
- * Passwords must be strings, the initializer will raise a `TypeError` unless `password.is_a? String`. ([dbf7697](https://github.com/philnash/pwned/commit/dbf7697e878d87ac74aed1e715cee19b73473369))
17
- * Added Ruby on Rails validator ([PR #3](https://github.com/philnash/pwned/pull/3) & [PR #6](https://github.com/philnash/pwned/pull/6))
18
- * Added simplified accessors `Pwned.pwned?` and `Pwned.pwned_count` ([PR #4](https://github.com/philnash/pwned/pull/4))
15
+ - Minor fix
19
16
 
20
- * Minor updates
21
- * SHA1 is only calculated once
22
- * Frozen string literal to make sure Ruby does not copy strings over and over again
23
- * Removal of `@match_data`, since we only use it to retrieve the counter. Caching the counter instead (all [PR #2](https://github.com/philnash/pwned/pull/2) thanks [@kpumuk](https://github.com/kpumuk))
17
+ - It was found to be possible for reading the lines body of a response to
18
+ result in a `nil` which caused trouble with string concatenation. This
19
+ avoids that scenario. Fixes #18, thanks [@flori](https://github.com/flori).
20
+
21
+ ## 2.0.1 (January 14, 2020) [☰](https://github.com/philnash/pwned/compare/v2.0.0...v2.0.1)
22
+
23
+ - Minor updates
24
+
25
+ - Adds double-splat to ActiveModel::Errors#add calls with options to make Ruby 2.7 happy.
26
+ - Detects presence of Net::HTTPClientException in tests to remove deprecation warning.
27
+
28
+ ## 2.0.0 (October 1, 2019) [☰](https://github.com/philnash/pwned/compare/v1.2.1...v2.0.0)
29
+
30
+ - Major updates
31
+
32
+ - Switches from `open-uri` to `Net::HTTP`. This is a potentially breaking change.
33
+ - `request_options` are now used to configure `Net::HTTP.start`.
34
+ - Rather than using all string keys from `request_options`, HTTP headers are now
35
+ specified in their own `headers` hash. To upgrade, any options intended as
36
+ headers need to be extracted into a `headers` hash, e.g.
37
+
38
+ ```diff
39
+ validates :password, not_pwned: {
40
+ - request_options: { read_timeout: 5, open_timeout: 1, "User-Agent" => "Super fun user agent" }
41
+ + request_options: { read_timeout: 5, open_timeout: 1, headers: { "User-Agent" => "Super fun user agent" } }
42
+ }
43
+
44
+ - password = Pwned::Password.new("password", 'User-Agent' => 'Super fun new user agent')
45
+ + password = Pwned::Password.new("password", headers: { 'User-Agent' => 'Super fun new user agent' }, read_timeout: 10)
46
+ ```
47
+
48
+ - Adds a CLI to let you check passwords on the command line
49
+
50
+ ```bash
51
+ $ pwned password
52
+ Pwned!
53
+ The password has been found in public breaches 3730471 times.
54
+ ```
55
+
56
+ ## 1.2.1 (March 17, 2018) [☰](https://github.com/philnash/pwned/compare/v1.2.0...v1.2.1)
57
+
58
+ - Minor updates
59
+ - Validator no longer raises `TypeError` when password is `nil`
60
+
61
+ ## 1.2.0 (March 15, 2018) [☰](https://github.com/philnash/pwned/compare/v1.1.0...v1.2.0)
62
+
63
+ - Major updates
64
+ - Changes `PwnedValidator` to `NotPwnedValidator`, so that the validation looks like `validates :password, not_pwned: true`. `PwnedValidator` now subclasses `NotPwnedValidator` for backwards compatibility with version 1.1.0 but is deprecated.
65
+
66
+ ## 1.1.0 (March 12, 2018) [☰](https://github.com/philnash/pwned/compare/v1.0.0...v1.1.0)
67
+
68
+ - Major updates
69
+
70
+ - Refactors exception handling with built in Ruby method ([PR #1](https://github.com/philnash/pwned/pull/1) thanks [@kpumuk](https://github.com/kpumuk))
71
+ - Passwords must be strings, the initializer will raise a `TypeError` unless `password.is_a? String`. ([dbf7697](https://github.com/philnash/pwned/commit/dbf7697e878d87ac74aed1e715cee19b73473369))
72
+ - Added Ruby on Rails validator ([PR #3](https://github.com/philnash/pwned/pull/3) & [PR #6](https://github.com/philnash/pwned/pull/6))
73
+ - Added simplified accessors `Pwned.pwned?` and `Pwned.pwned_count` ([PR #4](https://github.com/philnash/pwned/pull/4))
74
+
75
+ - Minor updates
76
+ - SHA1 is only calculated once
77
+ - Frozen string literal to make sure Ruby does not copy strings over and over again
78
+ - Removal of `@match_data`, since we only use it to retrieve the counter. Caching the counter instead (all [PR #2](https://github.com/philnash/pwned/pull/2) thanks [@kpumuk](https://github.com/kpumuk))
24
79
 
25
80
  ## 1.0.0 (March 6, 2018) [☰](https://github.com/philnash/pwned/commits/v1.0.0)
26
81
 
data/README.md CHANGED
@@ -2,9 +2,32 @@
2
2
 
3
3
  An easy, Ruby way to use the Pwned Passwords API.
4
4
 
5
- [![Gem Version](https://badge.fury.io/rb/pwned.svg)](https://rubygems.org/gems/pwned) [![Build Status](https://travis-ci.org/philnash/pwned.svg?branch=master)](https://travis-ci.org/philnash/pwned) [![Maintainability](https://codeclimate.com/github/philnash/pwned/badges/gpa.svg)](https://codeclimate.com/github/philnash/pwned/maintainability)
6
-
7
- [API docs](https://philnash.github.io/pwned/) | [GitHub repo](https://github.com/philnash/pwned)
5
+ [![Gem Version](https://badge.fury.io/rb/pwned.svg)](https://rubygems.org/gems/pwned) [![Build Status](https://travis-ci.org/philnash/pwned.svg?branch=master)](https://travis-ci.org/philnash/pwned) [![Maintainability](https://codeclimate.com/github/philnash/pwned/badges/gpa.svg)](https://codeclimate.com/github/philnash/pwned/maintainability) [![Inline docs](https://inch-ci.org/github/philnash/pwned.svg?branch=master)](https://inch-ci.org/github/philnash/pwned)
6
+
7
+ [API docs](https://www.rubydoc.info/gems/pwned) | [GitHub repo](https://github.com/philnash/pwned)
8
+
9
+ ## Table of Contents
10
+
11
+ - [Pwned](#pwned)
12
+ - [Table of Contents](#table-of-contents)
13
+ - [About](#about)
14
+ - [Installation](#installation)
15
+ - [Usage](#usage)
16
+ - [Plain Ruby](#plain-ruby)
17
+ - [Advanced](#advanced)
18
+ - [ActiveRecord Validator](#activerecord-validator)
19
+ - [I18n](#i18n)
20
+ - [Threshold](#threshold)
21
+ - [Network Error Handling](#network-error-handling)
22
+ - [Custom Request Options](#custom-request-options)
23
+ - [Using Asynchronously](#using-asynchronously)
24
+ - [Devise](#devise)
25
+ - [Command line](#command-line)
26
+ - [How Pwned is Pi?](#how-pwned-is-pi)
27
+ - [Development](#development)
28
+ - [Contributing](#contributing)
29
+ - [License](#license)
30
+ - [Code of Conduct](#code-of-conduct)
8
31
 
9
32
  ## About
10
33
 
@@ -14,6 +37,8 @@ Troy Hunt's [Pwned Passwords API V2](https://haveibeenpwned.com/API/v2#PwnedPass
14
37
 
15
38
  The data from this API is provided by [Have I been pwned?](https://haveibeenpwned.com/). Before using the API, please check [the acceptable uses and license of the API](https://haveibeenpwned.com/API/v2#AcceptableUse).
16
39
 
40
+ Here is a blog post I wrote on [how to use this gem in your Ruby applications to make your users' passwords better](https://www.twilio.com/blog/2018/03/better-passwords-in-ruby-applications-pwned-passwords-api.html).
41
+
17
42
  ## Installation
18
43
 
19
44
  Add this line to your application's Gemfile:
@@ -32,6 +57,14 @@ Or install it yourself as:
32
57
 
33
58
  ## Usage
34
59
 
60
+ There are a few ways you can use this gem:
61
+
62
+ 1. [Plain Ruby](#plain-ruby)
63
+ 2. [Rails](#activerecord-validator)
64
+ 3. [Rails and Devise](#devise)
65
+
66
+ ### Plain Ruby
67
+
35
68
  To test a password against the API, instantiate a `Pwned::Password` object and then ask if it is `pwned?`.
36
69
 
37
70
  ```ruby
@@ -72,10 +105,11 @@ Pwned.pwned_count("password")
72
105
 
73
106
  #### Advanced
74
107
 
75
- You can set options and headers to be used with `open-uri` when making the request to the API. HTTP headers must be string keys and the [other options are available in the `OpenURI::OpenRead` module](https://ruby-doc.org/stdlib-2.5.0/libdoc/open-uri/rdoc/OpenURI/OpenRead.html#method-i-open).
108
+ You can set http request options to be used with `Net::HTTP.start` when making the request to the API. These options are
109
+ documented in the [`Net::HTTP.start` documentation](http://ruby-doc.org/stdlib-2.6.3/libdoc/net/http/rdoc/Net/HTTP.html#method-c-start). The `:headers` option defines defines HTTP headers. These headers must be string keys.
76
110
 
77
111
  ```ruby
78
- password = Pwned::Password.new("password", { 'User-Agent' => 'Super fun new user agent' })
112
+ password = Pwned::Password.new("password", headers: { 'User-Agent' => 'Super fun new user agent' }, read_timeout: 10)
79
113
  ```
80
114
 
81
115
  ### ActiveRecord Validator
@@ -113,7 +147,7 @@ class User < ApplicationRecord
113
147
  end
114
148
  ```
115
149
 
116
- #### Network Errors Handling
150
+ #### Network Error Handling
117
151
 
118
152
  By default the record will be treated as valid when we cannot reach the [haveibeenpwned.com](https://haveibeenpwned.com/) servers. This can be changed with the `:on_error` validator parameter:
119
153
 
@@ -145,17 +179,112 @@ end
145
179
 
146
180
  #### Custom Request Options
147
181
 
148
- You can configure network requests made from the validator using `:request_options` (see [OpenURI::OpenRead#open](http://ruby-doc.org/stdlib-2.5.0/libdoc/open-uri/rdoc/OpenURI/OpenRead.html#method-i-open) for the list of available options, string keys represent custom network request headers, e.g. `"User-Agent"`):
182
+ You can configure network requests made from the validator using `:request_options` (see [Net::HTTP.start](http://ruby-doc.org/stdlib-2.6.3/libdoc/net/http/rdoc/Net/HTTP.html#method-c-start) for the list of available options).
183
+ In addition to these options, HTTP headers can be specified with the `:headers` key, e.g. `"User-Agent"`):
149
184
 
150
185
  ```ruby
151
186
  validates :password, not_pwned: {
152
- request_options: { read_timeout: 5, open_timeout: 1, "User-Agent" => "Super fun user agent" }
187
+ request_options: { read_timeout: 5, open_timeout: 1, headers: { "User-Agent" => "Super fun user agent" } }
153
188
  }
154
189
  ```
155
190
 
156
- ## TODO
191
+ ### Using Asynchronously
192
+
193
+ You may have a use case for hashing the password in advance, and then making the call to the Pwned api later
194
+ (for example if you want to enqueue a job without storing the plaintext password):
195
+
196
+ ```ruby
197
+ hashed_password = Pwned.hash_password(password)
198
+ # some time later
199
+ Pwned::HashPassword.new(hashed_password, request_options).pwned?
200
+ ```
201
+
202
+ ### Devise
203
+
204
+ If you are using Devise I recommend you use the [devise-pwned_password extension](https://github.com/michaelbanfield/devise-pwned_password) which is now powered by this gem.
205
+
206
+ ### Command line
207
+
208
+ The gem provides a command line utility for checking passwords. You can call it from your terminal application like this:
209
+
210
+ ```bash
211
+ $ pwned password
212
+ Pwned!
213
+ The password has been found in public breaches 3645804 times.
214
+ ```
215
+
216
+ If you don't want the password you are checking to be visible, call:
157
217
 
158
- - [ ] Devise plugin
218
+ ```bash
219
+ $ pwned --secret
220
+ ```
221
+
222
+ You will be prompted for the password, but it won't be displayed.
223
+
224
+ ## How Pwned is Pi?
225
+
226
+ [@daz](https://github.com/daz) [shared](https://twitter.com/dazonic/status/1074647842046660609) a fantastic example of using this gem to show how many times the digits of Pi have been used as passwords and leaked.
227
+
228
+ ```ruby
229
+ require 'pwned'
230
+
231
+ PI = '3.14159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111'
232
+
233
+ for n in 1..40
234
+ password = Pwned::Password.new PI[0..(n + 1)]
235
+ str = [ n.to_s.rjust(2) ]
236
+ str << (password.pwned? ? '😡' : '😃')
237
+ str << password.pwned_count.to_s.rjust(4)
238
+ str << password.password
239
+
240
+ puts str.join ' '
241
+ end
242
+ ```
243
+
244
+ The results may, or may not, surprise you.
245
+
246
+ ```
247
+ 1 😡 16 3.1
248
+ 2 😡 238 3.14
249
+ 3 😡 34 3.141
250
+ 4 😡 1345 3.1415
251
+ 5 😡 2552 3.14159
252
+ 6 😡 791 3.141592
253
+ 7 😡 9582 3.1415926
254
+ 8 😡 1591 3.14159265
255
+ 9 😡 637 3.141592653
256
+ 10 😡 873 3.1415926535
257
+ 11 😡 137 3.14159265358
258
+ 12 😡 103 3.141592653589
259
+ 13 😡 65 3.1415926535897
260
+ 14 😡 201 3.14159265358979
261
+ 15 😡 41 3.141592653589793
262
+ 16 😡 57 3.1415926535897932
263
+ 17 😡 28 3.14159265358979323
264
+ 18 😡 29 3.141592653589793238
265
+ 19 😡 1 3.1415926535897932384
266
+ 20 😡 7 3.14159265358979323846
267
+ 21 😡 5 3.141592653589793238462
268
+ 22 😡 2 3.1415926535897932384626
269
+ 23 😡 2 3.14159265358979323846264
270
+ 24 😃 0 3.141592653589793238462643
271
+ 25 😡 3 3.1415926535897932384626433
272
+ 26 😃 0 3.14159265358979323846264338
273
+ 27 😃 0 3.141592653589793238462643383
274
+ 28 😃 0 3.1415926535897932384626433832
275
+ 29 😃 0 3.14159265358979323846264338327
276
+ 30 😃 0 3.141592653589793238462643383279
277
+ 31 😃 0 3.1415926535897932384626433832795
278
+ 32 😃 0 3.14159265358979323846264338327950
279
+ 33 😃 0 3.141592653589793238462643383279502
280
+ 34 😃 0 3.1415926535897932384626433832795028
281
+ 35 😃 0 3.14159265358979323846264338327950288
282
+ 36 😃 0 3.141592653589793238462643383279502884
283
+ 37 😃 0 3.1415926535897932384626433832795028841
284
+ 38 😃 0 3.14159265358979323846264338327950288419
285
+ 39 😃 0 3.141592653589793238462643383279502884197
286
+ 40 😃 0 3.1415926535897932384626433832795028841971
287
+ ```
159
288
 
160
289
  ## Development
161
290
 
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "pwned"
4
+ require "optparse"
5
+ require "io/console"
6
+
7
+ options = {}
8
+ parser = OptionParser.new do |opts|
9
+ opts.banner = <<-USAGE
10
+ Usage: pwned <password>
11
+
12
+ Tests a password against the Pwned Passwords API using the k-anonymity model,
13
+ which avoids sending the entire password to the service.opts
14
+
15
+ If the password has been found in a publicly available breach then this tool
16
+ will report how many times it has been seen. Otherwise the tool will report that
17
+ the password has not been found in a public breach yet.
18
+
19
+ USAGE
20
+
21
+ opts.version = Pwned::VERSION
22
+
23
+ opts.on("-s", "--secret", "Enter password without displaying characters.\n#{" "* 37}Overrides provided arguments.")
24
+ opts.on_tail("-h", "--help", "Show help.")
25
+ opts.on_tail("-v", "--version", "Show version number.\n\n")
26
+ end
27
+
28
+ parser.parse!(ARGV, into: options)
29
+
30
+ if options[:help]
31
+ puts parser.help
32
+ exit
33
+ end
34
+ if options[:version]
35
+ puts parser.ver
36
+ exit
37
+ end
38
+ password_to_test = ARGV.first
39
+ if options[:secret]
40
+ password_to_test = STDIN.getpass("Password: ")
41
+ end
42
+ if !password_to_test || password_to_test.strip == ""
43
+ puts parser.help
44
+ exit
45
+ end
46
+ password = Pwned::Password.new(password_to_test || ARGV.first)
47
+ if password.pwned?
48
+ puts "Pwned!\nThe password has been found in public breaches #{password.pwned_count} times."
49
+ else
50
+ puts "The password has not been found in a public breach."
51
+ end
52
+
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "digest"
3
4
  require "pwned/version"
4
5
  require "pwned/error"
5
6
  require "pwned/password"
7
+ require "pwned/hashed_password"
6
8
 
7
9
  begin
8
10
  # Load Rails and our custom validator
@@ -29,10 +31,10 @@ module Pwned
29
31
  # Pwned.pwned?("pwned::password") #=> false
30
32
  #
31
33
  # @param password [String] The password you want to check against the API.
32
- # @param [Hash] request_options Options that can be passed to +open+ when
34
+ # @param [Hash] request_options Options that can be passed to +Net::HTTP.start+ when
33
35
  # calling the API
34
- # @option request_options [String] 'User-Agent' ("Ruby Pwned::Password #{Pwned::VERSION}")
35
- # The user agent used when making an API request.
36
+ # @option request_options [Symbol] :headers ({ "User-Agent" => "Ruby Pwned::Password #{Pwned::VERSION}" })
37
+ # HTTP headers to include in the request
36
38
  # @return [Boolean] Whether the password appears in the data breaches or not.
37
39
  # @since 1.1.0
38
40
  def self.pwned?(password, request_options={})
@@ -47,14 +49,28 @@ module Pwned
47
49
  # Pwned.pwned_count("pwned::password") #=> 0
48
50
  #
49
51
  # @param password [String] The password you want to check against the API.
50
- # @param [Hash] request_options Options that can be passed to +open+ when
52
+ # @param [Hash] request_options Options that can be passed to +Net::HTTP.start+ when
51
53
  # calling the API
52
- # @option request_options [String] 'User-Agent' ("Ruby Pwned::Password #{Pwned::VERSION}")
53
- # The user agent used when making an API request.
54
+ # @option request_options [Symbol] :headers ({ "User-Agent" => "Ruby Pwned::Password #{Pwned::VERSION}" })
55
+ # HTTP headers to include in the request
54
56
  # @return [Integer] The number of times the password has appeared in the data
55
57
  # breaches.
56
58
  # @since 1.1.0
57
59
  def self.pwned_count(password, request_options={})
58
60
  Pwned::Password.new(password, request_options).pwned_count
59
61
  end
62
+
63
+ ##
64
+ # Returns the full SHA1 hash of the given password in uppercase. This can be safely passed around your code
65
+ # before making the pwned request (e.g. dropped into a queue table).
66
+ #
67
+ # @example
68
+ # Pwned.hash_password("password") #=> 5BAA61E4C9B93F3F0682250B6CF8331B7EE68FD8
69
+ #
70
+ # @param password [String] The password you want to check against the API
71
+ # @return [String] An uppercase SHA1 hash of the password
72
+ # @since 2.1.0
73
+ def self.hash_password(password)
74
+ Digest::SHA1.hexdigest(password).upcase
75
+ end
60
76
  end