crypt_ident 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.rubocop.yml +29 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +7 -0
  6. data/.yardopts +16 -0
  7. data/CODE_OF_CONDUCT.md +74 -0
  8. data/Gemfile +6 -0
  9. data/Gemfile.lock +263 -0
  10. data/Guardfile +26 -0
  11. data/HISTORY.md +22 -0
  12. data/LICENSE.txt +21 -0
  13. data/README.md +548 -0
  14. data/Rakefile +93 -0
  15. data/bin/_guard-core +29 -0
  16. data/bin/bundle +105 -0
  17. data/bin/byebug +29 -0
  18. data/bin/code_climate_reek +29 -0
  19. data/bin/coderay +29 -0
  20. data/bin/commonmarker +29 -0
  21. data/bin/console +14 -0
  22. data/bin/erubis +29 -0
  23. data/bin/flay +29 -0
  24. data/bin/flog +29 -0
  25. data/bin/github-markup +29 -0
  26. data/bin/guard +29 -0
  27. data/bin/inch +29 -0
  28. data/bin/kwalify +29 -0
  29. data/bin/listen +29 -0
  30. data/bin/pry +29 -0
  31. data/bin/rackup +29 -0
  32. data/bin/rake +29 -0
  33. data/bin/redcarpet +29 -0
  34. data/bin/reek +29 -0
  35. data/bin/rubocop +29 -0
  36. data/bin/ruby-parse +29 -0
  37. data/bin/ruby-rewrite +29 -0
  38. data/bin/ruby_parse +29 -0
  39. data/bin/ruby_parse_extract_error +29 -0
  40. data/bin/sequel +29 -0
  41. data/bin/setup +34 -0
  42. data/bin/sparkr +29 -0
  43. data/bin/term_cdiff +29 -0
  44. data/bin/term_colortab +29 -0
  45. data/bin/term_decolor +29 -0
  46. data/bin/term_display +29 -0
  47. data/bin/term_mandel +29 -0
  48. data/bin/term_snow +29 -0
  49. data/bin/thor +29 -0
  50. data/bin/yard +29 -0
  51. data/bin/yardoc +29 -0
  52. data/bin/yri +29 -0
  53. data/config.reek +19 -0
  54. data/crypt_ident.gemspec +80 -0
  55. data/docs/CryptIdent.html +2276 -0
  56. data/docs/_index.html +116 -0
  57. data/docs/class_list.html +51 -0
  58. data/docs/css/common.css +1 -0
  59. data/docs/css/full_list.css +58 -0
  60. data/docs/css/style.css +496 -0
  61. data/docs/file.CODE_OF_CONDUCT.html +145 -0
  62. data/docs/file.HISTORY.html +91 -0
  63. data/docs/file.LICENSE.html +70 -0
  64. data/docs/file.README.html +692 -0
  65. data/docs/file_list.html +71 -0
  66. data/docs/frames.html +17 -0
  67. data/docs/index.html +692 -0
  68. data/docs/js/app.js +292 -0
  69. data/docs/js/full_list.js +216 -0
  70. data/docs/js/jquery.js +4 -0
  71. data/docs/method_list.html +115 -0
  72. data/docs/top-level-namespace.html +110 -0
  73. data/lib/crypt_ident.rb +13 -0
  74. data/lib/crypt_ident/change_password.rb +184 -0
  75. data/lib/crypt_ident/config.rb +47 -0
  76. data/lib/crypt_ident/generate_reset_token.rb +212 -0
  77. data/lib/crypt_ident/reset_password.rb +207 -0
  78. data/lib/crypt_ident/session_expired.rb +91 -0
  79. data/lib/crypt_ident/sign_in.rb +189 -0
  80. data/lib/crypt_ident/sign_out.rb +96 -0
  81. data/lib/crypt_ident/sign_up.rb +160 -0
  82. data/lib/crypt_ident/update_session_expiry.rb +125 -0
  83. data/lib/crypt_ident/version.rb +6 -0
  84. data/scripts/build-gem-list.rb +91 -0
  85. metadata +547 -0
@@ -0,0 +1,71 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
5
+ <meta charset="utf-8" />
6
+
7
+ <link rel="stylesheet" href="css/full_list.css" type="text/css" media="screen" charset="utf-8" />
8
+
9
+ <link rel="stylesheet" href="css/common.css" type="text/css" media="screen" charset="utf-8" />
10
+
11
+
12
+
13
+ <script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
14
+
15
+ <script type="text/javascript" charset="utf-8" src="js/full_list.js"></script>
16
+
17
+
18
+ <title>File List</title>
19
+ <base id="base_target" target="_parent" />
20
+ </head>
21
+ <body>
22
+ <div id="content">
23
+ <div class="fixed_header">
24
+ <h1 id="full_list_header">File List</h1>
25
+ <div id="full_list_nav">
26
+
27
+ <span><a target="_self" href="class_list.html">
28
+ Classes
29
+ </a></span>
30
+
31
+ <span><a target="_self" href="method_list.html">
32
+ Methods
33
+ </a></span>
34
+
35
+ <span><a target="_self" href="file_list.html">
36
+ Files
37
+ </a></span>
38
+
39
+ </div>
40
+
41
+ <div id="search">Search: <input type="text" /></div>
42
+ </div>
43
+
44
+ <ul id="full_list" class="file">
45
+
46
+
47
+ <li id="object_README" class="odd">
48
+ <div class="item"><span class="object_link"><a href="index.html" title="README">README</a></span></div>
49
+ </li>
50
+
51
+
52
+ <li id="object_CODE_OF_CONDUCT" class="even">
53
+ <div class="item"><span class="object_link"><a href="file.CODE_OF_CONDUCT.html" title="CODE_OF_CONDUCT">CODE_OF_CONDUCT</a></span></div>
54
+ </li>
55
+
56
+
57
+ <li id="object_HISTORY" class="odd">
58
+ <div class="item"><span class="object_link"><a href="file.HISTORY.html" title="HISTORY">HISTORY</a></span></div>
59
+ </li>
60
+
61
+
62
+ <li id="object_LICENSE" class="even">
63
+ <div class="item"><span class="object_link"><a href="file.LICENSE.html" title="LICENSE">LICENSE</a></span></div>
64
+ </li>
65
+
66
+
67
+
68
+ </ul>
69
+ </div>
70
+ </body>
71
+ </html>
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>crypt_ident Module Documentation</title>
6
+ </head>
7
+ <script type="text/javascript" charset="utf-8">
8
+ var match = unescape(window.location.hash).match(/^#!(.+)/);
9
+ var name = match ? match[1] : 'index.html';
10
+ name = name.replace(/^(\w+):\/\//, '').replace(/^\/\//, '');
11
+ window.top.location = name;
12
+ </script>
13
+ <noscript>
14
+ <h1>Oops!</h1>
15
+ <h2>YARD requires JavaScript!</h2>
16
+ </noscript>
17
+ </html>
@@ -0,0 +1,692 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>
7
+ File: README
8
+
9
+ &mdash; crypt_ident Module Documentation
10
+
11
+ </title>
12
+
13
+ <link rel="stylesheet" href="css/style.css" type="text/css" charset="utf-8" />
14
+
15
+ <link rel="stylesheet" href="css/common.css" type="text/css" charset="utf-8" />
16
+
17
+ <script type="text/javascript" charset="utf-8">
18
+ pathId = "README";
19
+ relpath = '';
20
+ </script>
21
+
22
+
23
+ <script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
24
+
25
+ <script type="text/javascript" charset="utf-8" src="js/app.js"></script>
26
+
27
+
28
+ </head>
29
+ <body>
30
+ <div class="nav_wrap">
31
+ <iframe id="nav" src="class_list.html?1"></iframe>
32
+ <div id="resizer"></div>
33
+ </div>
34
+
35
+ <div id="main" tabindex="-1">
36
+ <div id="header">
37
+ <div id="menu">
38
+
39
+ <a href="_index.html">Index</a> &raquo;
40
+ <span class="title">File: README</span>
41
+
42
+ </div>
43
+
44
+ <div id="search">
45
+
46
+ <a class="full_list_link" id="class_list_link"
47
+ href="class_list.html">
48
+
49
+ <svg width="24" height="24">
50
+ <rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
51
+ <rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
52
+ <rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
53
+ </svg>
54
+ </a>
55
+
56
+ </div>
57
+ <div class="clear"></div>
58
+ </div>
59
+
60
+ <div id="content"><div id='filecontents'><h1>CryptIdent</h1>
61
+
62
+ <p>Yet another fairly basic authentication Gem. (Authorisation, and batteries, sold separately.)</p>
63
+
64
+ <p>This is initially tied to Hanami 1.3.0+; specifically, it assumes that user entities have an API compatible with <code>Hanami::Entity</code> for accessing the field/attribute values listed below in <a href="#databaserepository-setup"><em>Database/Repository Setup</em></a> (which itself assumes a Repository API compatible with that of Hanami 1.3&#39;s Repository classes). The Gem is mostly a thin layer around <a href="https://github.com/codahale/bcrypt-ruby">BCrypt</a> that, in conjunction with Hanami entities or work-alikes, supports the most common use cases for password-based authentication:</p>
65
+
66
+ <ol>
67
+ <li><a href="#registration">Registration</a>;</li>
68
+ <li><a href="#signing-in">Signing in</a>;</li>
69
+ <li><a href="#signing-out">Signing out</a>;</li>
70
+ <li><a href="#password-change">Password change</a>;</li>
71
+ <li><a href="#password-reset">Password reset</a>; and</li>
72
+ <li><a href="#session-management-overview">Session expiration management</a>.</li>
73
+ </ol>
74
+
75
+ <p>It <em>does not</em> implement features such as</p>
76
+
77
+ <ol>
78
+ <li>Password-strength testing;</li>
79
+ <li>Password occurrence in a <a href="https://www.passwordrandom.com/most-popular-passwords">list of most popular (and easily hacked) passwords</a>; or</li>
80
+ <li>Password ageing (requiring password changes after a period of time).</li>
81
+ </ol>
82
+
83
+ <p>These either violate current best-practice recommendations from security leaders (e.g., NIST and others no longer recommend password ageing as a defence against cracking) or have other Gems that focus on the features in question (e.g., <a href="https://github.com/bdmac/strong_password"><code>bdmac/strong_password</code></a>).</p>
84
+
85
+ <p><strong>NOTE:</strong> One feature of this Gem is that most of the <a href="#configuration">configuration</a> <em>should</em> Work Just Fine for most use cases. However, you <strong>must</strong> explicitly initialise the <code>:repository</code> configuration item prior to using the configuration data for the <code>repository</code> or the <code>guest_user</code> entries. We <strong>recommend</strong> assigning this once, during application startup when other configuration setup is being completed.</p>
86
+
87
+ <h1>Installation</h1>
88
+
89
+ <p>Add this line to your application&#39;s Gemfile:</p>
90
+
91
+ <pre class="code ruby"><code class="ruby"><span class='id identifier rubyid_gem'>gem</span> <span class='tstring'><span class='tstring_beg'>&#39;</span><span class='tstring_content'>crypt_ident</span><span class='tstring_end'>&#39;</span></span>
92
+ </code></pre>
93
+
94
+ <p>And then execute:</p>
95
+
96
+ <pre class="code ruby"><code class="ruby">$ bundle
97
+ </code></pre>
98
+
99
+ <p>Or install it yourself as:</p>
100
+
101
+ <pre class="code ruby"><code class="ruby">$ gem install crypt_ident
102
+ </code></pre>
103
+
104
+ <h1>Usage</h1>
105
+
106
+ <h2>Database/Repository Setup</h2>
107
+
108
+ <p>In this README and in the API Documentation, the Repository used to read and update the underlying database table is named <code>UserRepository</code> and, per Hanami conventions, the Entity is named <code>User</code>. <em>The code itself enforces no such assumption;</em> so long as you assign your Repository class to <code>CryptIdent.config.repository</code> (see <a href="#configuration"><em>Configuration</em></a> below), and it follows Hanami conventions for Entity names, this <em>should</em> Just Work. (Please <a href="https://github.com/jdickey/crypt_ident/issues/new">open an issue</a> if you prove otherwise.)</p>
109
+
110
+ <p>We assume that the Repository object</p>
111
+
112
+ <ol>
113
+ <li>Has a <em>class method</em> named <code>.entity</code> that returns the <em>class</em> of the Entity used by that Repository (e.g., <code>User</code> for a <code>UserRepository</code>). (This is to match <code>Hanami::Repository</code>.)</li>
114
+ <li>Has a class method named <code>.guest_user</code> that returns an Entity with a descriptive name (e.g., &quot;Guest User&quot;) and is otherwise invalid for persistence (e.g., it has an invalid <code>id</code> attribute); and</li>
115
+ <li>Implements the &quot;usual&quot; common methods (<code>#create</code>, <code>#update</code>, <code>#delete</code>, etc) conforming to an interface compatible with <a href="https://github.com/hanami/model/blob/master/lib/hanami/repository.rb"><code>Hanami::Repository</code></a>. This interface is suitably generic and sufficiently widely implemented, even in ORMs such as ActiveRecord that make no claim to implementing the <a href="https://8thlight.com/blog/mike-ebert/2013/03/23/the-repository-pattern.html">Repository Pattern</a>.</li>
116
+ </ol>
117
+
118
+ <p>The database table for that Repository <strong>must</strong> have the following fields, in any order within the schema:</p>
119
+
120
+ <table><thead>
121
+ <tr>
122
+ <th style="text-align: left">Field</th>
123
+ <th>Type</th>
124
+ <th>Description</th>
125
+ </tr>
126
+ </thead><tbody>
127
+ <tr>
128
+ <td style="text-align: left"><code>name</code></td>
129
+ <td>string</td>
130
+ <td>The name of an individual User to be Authenticated</td>
131
+ </tr>
132
+ <tr>
133
+ <td style="text-align: left"><code>email</code></td>
134
+ <td>string</td>
135
+ <td>The Email Address for that User, to be used for Password Recovery, for example.</td>
136
+ </tr>
137
+ <tr>
138
+ <td style="text-align: left"><code>password_hash</code></td>
139
+ <td>text</td>
140
+ <td>The <em>encrypted</em> Password associated with that User.</td>
141
+ </tr>
142
+ <tr>
143
+ <td style="text-align: left"><code>password_reset_expires_at</code></td>
144
+ <td>timestamp without time zone</td>
145
+ <td>Defaults to <code>nil</code>; set this to the Expiry Time (<code>Time.now + config.reset_expiry</code>) when responding to a Password Reset request (e.g., by email). The <code>token</code> (below) will expire at this time (see <em>Configuration</em>, below).</td>
146
+ </tr>
147
+ <tr>
148
+ <td style="text-align: left"><code>token</code></td>
149
+ <td>text</td>
150
+ <td>Defaults to <code>nil</code>. A Password Reset Token; a URL-safe secure random number (see <a href="https://ruby-doc.org/stdlib-2.5.1/libdoc/securerandom/rdoc/Random/Formatter.html#method-i-urlsafe_base64">standard-library documentation</a>) used to uniquely identify a Password Reset request.</td>
151
+ </tr>
152
+ </tbody></table>
153
+
154
+ <p>For examples of this, examine the <code>test/support/fake_repository.rb</code> and <code>test/support/unit_test_model_and_repo_classes.rb</code> files, and the unit (<code>test/crypt_ident/*</code>) and integration (<code>test/integration/*</code>) tests.</p>
155
+
156
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
157
+
158
+ <h2>User Entity</h2>
159
+
160
+ <p>As mentioned in the <em>Database/Repository Setup</em> section above, <code>CryptIdent</code> makes no assumption about the class constant/name of the Entity persisted to and retrieved from the Repository, other than it follow Hanami conventions. (In this and related documents, we refer to that Entity class as <code>User</code>.) In addition to attributes matching the fields specified in the previous section, (which <code>Hanami::Entity</code> and most analogous ORM Entities expose by default), the Entity <strong>must</strong> respond to the <code>#guest?</code> message, returning <code>true</code> if it is the Guest User (as returned by <code>UserRepository#guest_user</code>), or <code>false</code> otherwise. It <em>may</em> have other methods as appropriate to the client code.</p>
161
+
162
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
163
+
164
+ <h2>Configuration</h2>
165
+
166
+ <p>The currently-configurable details for <code>CryptIdent</code> are as follows:</p>
167
+
168
+ <table><thead>
169
+ <tr>
170
+ <th style="text-align: left">Key</th>
171
+ <th>Default</th>
172
+ <th>Description</th>
173
+ </tr>
174
+ </thead><tbody>
175
+ <tr>
176
+ <td style="text-align: left"><code>:error_key</code></td>
177
+ <td><code>:error</code></td>
178
+ <td>Modify this setting if you want to use a different key for flash messages reporting unsuccessful actions.</td>
179
+ </tr>
180
+ <tr>
181
+ <td style="text-align: left"><code>:guest_user</code></td>
182
+ <td>Return value from <code>repository</code> <code>.guest_user</code> method</td>
183
+ <td>This value is used for the session variable <code>session[:current_user]</code> when no User has <a href="#signing-in">signed in</a>, or after a previously Authenticated User has <a href="#signing-out">signed out</a>. If your application <em>does not</em> make use of the <a href="https://en.wikipedia.org/wiki/Null_object_pattern">Null Object pattern</a>, you would assign <code>nil</code> to this configuration setting. (See <a href="https://robots.thoughtbot.com/rails-refactoring-example-introduce-null-object">this Thoughtbot post</a> for a good discussion of Null Objects in Ruby.)</td>
184
+ </tr>
185
+ <tr>
186
+ <td style="text-align: left"><code>:hashing_cost</code></td>
187
+ <td>8</td>
188
+ <td>This is the <a href="https://github.com/codahale/bcrypt-ruby#cost-factors">hashing cost</a> used to <em>encrypt a password</em> and is applied at the hashed-password-creation step; it <strong>does not</strong> modify the default cost for the encryption engine. <strong>Note that</strong> any change to this value <strong>will</strong> invalidate and make useless all existing Encrypted Password stored values.</td>
189
+ </tr>
190
+ <tr>
191
+ <td style="text-align: left"><code>:repository</code></td>
192
+ <td><code>UserRepository.new</code></td>
193
+ <td>Modify this if your user records are in a different (or namespaced) class.</td>
194
+ </tr>
195
+ <tr>
196
+ <td style="text-align: left"><code>:reset_expiry</code></td>
197
+ <td>86400 (24 hours in seconds)</td>
198
+ <td>Number of seconds from the time a password-reset request token is stored before it becomes invalid.</td>
199
+ </tr>
200
+ <tr>
201
+ <td style="text-align: left"><code>:session_expiry</code></td>
202
+ <td>900 (15 minutes, in seconds)</td>
203
+ <td>Number of seconds <em>from either</em> the time that a User is successfully Authenticated <em>or</em> the <code>update_session_expiry</code> method is called <em>before</em> a call to <code>session_expired?</code> will return <code>true</code>.</td>
204
+ </tr>
205
+ <tr>
206
+ <td style="text-align: left"><code>:success_key</code></td>
207
+ <td><code>:success</code></td>
208
+ <td>Modify this setting if you want to use a different key for flash messages reporting successful actions.</td>
209
+ </tr>
210
+ <tr>
211
+ <td style="text-align: left"><code>:token_bytes</code></td>
212
+ <td>24</td>
213
+ <td>Number of bytes of random data to generate when building a password-reset token. See <code>token</code> in the <a href="#databaserepository-setup"><em>Database/Repository Setup</em></a> section, above.</td>
214
+ </tr>
215
+ </tbody></table>
216
+
217
+ <p>For example</p>
218
+
219
+ <pre class="code ruby"><code class="ruby"> <span class='id identifier rubyid_include'>include</span> <span class='const'><span class='object_link'><a href="CryptIdent.html" title="CryptIdent (module)">CryptIdent</a></span></span>
220
+
221
+ <span class='const'><span class='object_link'><a href="CryptIdent.html" title="CryptIdent (module)">CryptIdent</a></span></span><span class='period'>.</span><span class='id identifier rubyid_configure'>configure</span> <span class='kw'>do</span> <span class='op'>|</span><span class='id identifier rubyid_config'>config</span><span class='op'>|</span>
222
+ <span class='id identifier rubyid_config'>config</span><span class='period'>.</span><span class='id identifier rubyid_repository'>repository</span> <span class='op'>=</span> <span class='const'>MainApp</span><span class='op'>::</span><span class='const'>Repositories</span><span class='op'>::</span><span class='const'>User</span><span class='period'>.</span><span class='id identifier rubyid_new'>new</span> <span class='comment'># note: *not* a Hanami recommended practice!
223
+ </span> <span class='id identifier rubyid_config'>config</span><span class='period'>.</span><span class='id identifier rubyid_error_key'>error_key</span> <span class='op'>=</span> <span class='symbol'>:alert</span>
224
+ <span class='id identifier rubyid_config'>config</span><span class='period'>.</span><span class='id identifier rubyid_hashing_cost'>hashing_cost</span> <span class='op'>=</span> <span class='int'>6</span> <span class='comment'># less secure and less resource-intensive
225
+ </span> <span class='id identifier rubyid_config'>config</span><span class='period'>.</span><span class='id identifier rubyid_token_bytes'>token_bytes</span> <span class='op'>=</span> <span class='int'>20</span>
226
+ <span class='id identifier rubyid_config'>config</span><span class='period'>.</span><span class='id identifier rubyid_reset_expiry'>reset_expiry</span> <span class='op'>=</span> <span class='int'>7200</span> <span class='comment'># two hours; &quot;we run a tight ship here&quot;
227
+ </span> <span class='id identifier rubyid_config'>config</span><span class='period'>.</span><span class='id identifier rubyid_guest_user'>guest_user</span> <span class='op'>=</span> <span class='const'>MainApp</span><span class='op'>::</span><span class='const'>Repositories</span><span class='op'>::</span><span class='const'>User</span><span class='period'>.</span><span class='id identifier rubyid_new'>new</span><span class='period'>.</span><span class='id identifier rubyid_guest_user'>guest_user</span>
228
+ <span class='kw'>end</span>
229
+ </code></pre>
230
+
231
+ <p>would change the configuration as you would expect whenever that code was run. (We <strong>recommend</strong> that this be done inside the <code>controller.prepare</code> block of your Hanami <code>web</code> (or equivalent) app&#39;s <code>application.rb</code> file.)</p>
232
+
233
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
234
+
235
+ <h2>Introductory Notes on Workflows</h2>
236
+
237
+ <h3>Interfaces</h3>
238
+
239
+ <p>The methods employed directly by these use cases use <a href="https://dry-rb.org/gems/dry-matcher/result-matcher/">Result matchers</a> and <a href="https://dry-rb.org/gems/dry-monads/1.0/result/"><code>Result</code> monads</a> to provide a <em>consistent, fluent, explicit, and understandable</em> mechanism for detecting and handling success and failure.</p>
240
+
241
+ <p>Each method (with two exceptions, noted in their documentation) <em>requires</em> a block, to which a <code>result</code> indicating success or failure is yielded. That block <strong>must</strong> in turn define blocks for <strong>both</strong> <code>result.success</code> and <code>result.failure</code> to handle success and failure results, respectively. Each of the two blocks takes parameters which the method uses to communicate either the successful result (and possible supporting information), or the reason for failure, along with supporting information. Not all failure cases use all parameters to the <code>result.failure</code> block. Any that are not relevant may be safely ignored (and <strong>should</strong> by convention have a value of <code>:unassigned</code> yielded to the <code>result.failure</code> block).</p>
242
+
243
+ <p>The active configuration <strong>is not</strong> passed as a parameter to either the <code>success</code> or <code>failure</code> blocks; it is always accessible as <code>CryptIdent.config</code>, and is based on the <a href="https://dry-rb.org/gems/dry-configurable/"><code>dry-configurable</code></a> Gem.</p>
244
+
245
+ <p>For further discussion of this, see the documentation of the individual methods in the <a href="docs/CryptIdent.html">API Reference</a>.</p>
246
+
247
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
248
+
249
+ <h3>Session Handling Not Automatic</h3>
250
+
251
+ <p>If you&#39;ve set up your <code>controller.prepare</code> block as <strong>recommended</strong> in the preceding section, <code>CryptIdent</code> is loaded and configured but <em>does not</em> implement session-handling &quot;out of the box&quot;; as with <a href="https://github.com/sebastjan-hribar/tachiban#session-handling">other libraries</a>, it must be implemented <em>by you</em> as described in the <a href="#session-expiration"><em>Session Expiration</em></a> description below.</p>
252
+
253
+ <h3>Code Samples in Integration Tests are Authoritative</h3>
254
+
255
+ <p>Integration tests, in <code>test/integration/*_test.rb</code>, are the authoritative documentation-through-working-code of each method and workflow supported by this Gem. Only minimal code snippets are included here to help explain use cases. However, the <a href="docs/CryptIdent.html">API Reference</a> provides a more conventionally-documented reference to each <code>CryptIdent</code> method; any discrepancies between the integration tests, documented API, and/or code snippets there and here should be regarded as a bug (and a <a href="https://github.com/jdickey/crypt_ident/issues/">report</a> filed if not already filed.</p>
256
+
257
+ <h3>Terminology and the project Ubiquitous Language</h3>
258
+
259
+ <p>Finally, a note on terminology. Terms that have meaning (e.g., <em>Guest User</em>) within this module&#39;s domain language, or <a href="https://www.martinfowler.com/bliki/UbiquitousLanguage.html">Ubiquitous Language</a>, <strong>must</strong> be capitalised, at least on first use within a paragraph. This is to stress to the reader that, while these terms may have &quot;obvious&quot; meanings, their use within this module and its related documents (including this one) <strong>must</strong> be consistent, specific, and rigorous in their meaning. In the <a href="docs/CryptIdent.html">API Documentation</a>, each of these terms <strong>must</strong> be listed in the <em>Ubiquitous Language Terms</em> section under each method description in which they are used. (If you find any omissions, inconsistencies, or other errors, please open a <a href="https://github.com/jdickey/conversagence-hanami/issues/new">new issue</a> if it has not already been <a href="https://github.com/jdickey/conversagence-hanami/issues">reported</a>.)</p>
260
+
261
+ <p>After the first usage in a paragraph, the term <strong>may</strong> be used less strictly; e.g., by referring to a <em>Clear-Text Password</em> simply as a <em>password</em> <em>if</em> doing so does not introduce ambiguity or confusion. The reader should feel free to <a href="https://github.com/jdickey/crypt_ident/issues">open an issue report</a> for any lapses of consistency or clarity. (Thank you!)</p>
262
+
263
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
264
+
265
+ <h2>Use-Case Workflows</h2>
266
+
267
+ <h3>Registration</h3>
268
+
269
+ <h4>Overview</h4>
270
+
271
+ <p>Method involved:</p>
272
+
273
+ <pre class="code ruby"><code class="ruby"> <span class='kw'>module</span> <span class='const'><span class='object_link'><a href="CryptIdent.html" title="CryptIdent (module)">CryptIdent</a></span></span>
274
+ <span class='kw'>def</span> <span class='id identifier rubyid_sign_up'>sign_up</span><span class='lparen'>(</span><span class='id identifier rubyid_attribs'>attribs</span><span class='comma'>,</span> <span class='label'>current_user:</span><span class='rparen'>)</span>
275
+ <span class='comment'># ...
276
+ </span> <span class='kw'>end</span>
277
+ <span class='kw'>end</span>
278
+ </code></pre>
279
+
280
+ <p>This is the first of our use cases that involves calling a function which expects a block to be supplied. If one isn&#39;t, then a <code>LocalJumpError</code> will be raised. If either the <code>success</code> or <code>failure</code> blocks are omitted within that block, then a <code>Dry::Matcher::NonExhaustiveMatchError</code> will be raised. (It <em>is</em> permissible to completely omit the parameters to a <code>success</code> or <code>failure</code> block; e.g., for the <code>#sign_out</code> method which does not support reporting a failure.)</p>
281
+
282
+ <p>The <code>attribs</code> parameter is a Hash-like object such as a <code>Hanami::Action::Params</code> instance. It <strong>must</strong> have a <code>:name</code> entry, as well as any other keys and matching field values required by the Entity which will be created from the <code>params</code> values, <em>other than</em> a <code>:password_hash</code> key. It also <strong>must not</strong> have a <code>:password</code> entry; if one is supplied, it will be <em>ignored</em>. This is to support our standard workflow of having newly-Registered Users be initially assigned a Clear-Text Password of random text, then immediately starting the <a href="#password-reset">Password Reset</a> workflow to further validate their supplied email address.</p>
283
+
284
+ <p>Pass in the value of the <code>session[:current_user]</code> session variable as the <code>:current_user</code> parameter. This <strong>must</strong> be an Entity value rather than an <code>id</code> value. Supplying a value of <code>nil</code> is permitted, and is equivalent to specifying the Guest User (see <a href="#database-repository-setup"><em>Database/Repository Setup</em></a>).</p>
285
+
286
+ <p>As described <a href="#interfaces">earlier</a>, this method <strong>requires</strong> a block which accepts a <code>result</code> parameter. The block <strong>must</strong> define <em>both</em> <code>result.success</code> and <code>result.failure</code> blocks, passing each a block which itself takes appropriate parameters. These will be further described below.</p>
287
+
288
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
289
+
290
+ <h4>Success, aka Golden Path</h4>
291
+
292
+ <p>If the <code>params</code> include all values required by the underlying schema, including a valid <code>name</code> attribute that does not exist in the underlying data store, then it (with a <code>password_hash</code> attribute created from a random-text Clear-Text Password) will be persisted to the Repository specified by <code>repo:</code> (or to the Repository specified by the <a href="#configuratino"><em>Configuration</em></a> if the <code>repo:</code> value is <code>nil</code>). That User Entity will be passed to the <code>result.success</code> block as the <code>user:</code> parameter.</p>
293
+
294
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
295
+
296
+ <h4>Error Conditions</h4>
297
+
298
+ <h5>Authenticated User as <code>current_user:</code> Parameter</h5>
299
+
300
+ <p>If the specified <code>current_user:</code> parameter is a valid User Entity other than the Guest User, then that is presumed to be the Current User of the application. Authenticated Users are prohibited from creating other Users, and so the <code>result.failure</code> block will be called with a <code>code:</code> of <code>:current_user_exists</code>.</p>
301
+
302
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
303
+
304
+ <h5>Specified <code>:name</code> Attribute Already Used for an Existing User</h5>
305
+
306
+ <p>If there is no improper value for the <code>current_user:</code> parameter, and if the specified <code>:name</code> attribute exists in a record within the Repository, then the <code>result.failure</code> block will be called with a <code>:code</code> of <code>:user_already_created</code>.</p>
307
+
308
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
309
+
310
+ <h5>Record Could Not be Created Within Repository</h5>
311
+
312
+ <p>If neither of the earlier conditions apply, but the Repository method <code>#create</code> returned an error, then the <code>result.failure</code> block will be called with a <code>:code</code> of <code>:user_creation_failed</code>.</p>
313
+
314
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
315
+
316
+ <h3>Signing In</h3>
317
+
318
+ <h4>Overview</h4>
319
+
320
+ <p>Method involved:</p>
321
+
322
+ <pre class="code ruby"><code class="ruby"> <span class='kw'>module</span> <span class='const'><span class='object_link'><a href="CryptIdent.html" title="CryptIdent (module)">CryptIdent</a></span></span>
323
+ <span class='kw'>def</span> <span class='id identifier rubyid_sign_in'>sign_in</span><span class='lparen'>(</span><span class='id identifier rubyid_user'>user</span><span class='comma'>,</span> <span class='id identifier rubyid_password'>password</span><span class='comma'>,</span> <span class='label'>current_user:</span> <span class='kw'>nil</span><span class='rparen'>)</span>
324
+ <span class='comment'># ...
325
+ </span> <span class='kw'>end</span>
326
+ <span class='kw'>end</span>
327
+ </code></pre>
328
+
329
+ <p>Once a User has been <a href="#registration">Registered</a> and <a href="#password-reset">Reset their Password</a>, Signing In is a matter of retrieving that user&#39;s Entity (containing a <code>password_hash</code> attribute) and calling <code>#sign_in</code> passing in that Entity, the purported Clear-Text Password, and the currently Authenticated User (if any), then using the <code>result</code> passed to the yielded block to determine and respond to the success or failure of the call.</p>
330
+
331
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
332
+
333
+ <h4>Successfully Signing In</h4>
334
+
335
+ <p>So long as no User is currently Authenticated in the Session (as shown by the <code>session[:current_user]</code> having a value of either <code>nil</code> or the Guest User), supplying a User Entity and the correct Clear-Text Password for that User to a call to <code>#sign_in</code> will cause the block for the <code>#sign_in</code> method call to yield the <em>same</em> User Entity to the <code>result.success</code> block, indicating success.</p>
336
+
337
+ <p>Note that this process is unchanged if the passed-in <code>current_user</code> is <em>the same as</em> the User Entity attempting Authentication. It is up to client code to determine how to proceed if Authentication fails in this case.</p>
338
+
339
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
340
+
341
+ <h4>Error Conditions</h4>
342
+
343
+ <h5>Incorrect Password Supplied</h5>
344
+
345
+ <p>While no Authenticated Member currently exists (as shown by the <code>session[:current_user]</code> having a value of either <code>nil</code> or the Guest User), supplying a User Entity and an <em>incorrect</em> Clear-Text Password for that User to a call to <code>#sign_in</code> will yield a call to the block&#39;s <code>result.failure</code> block with a <code>code:</code> of <code>:invalid_password</code>.</p>
346
+
347
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
348
+
349
+ <h5>Authenticated User Exists</h5>
350
+
351
+ <p>If the passed-in <code>current_user</code> is a User Entity <em>other than</em> the specified <code>user</code> Entity <em>or</em> the Guest User, no match will be attempted, and the method will yield a call to the block&#39;s <code>result.failure</code> block with a <code>code:</code> value of <code>:illegal_current_user</code>.</p>
352
+
353
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
354
+
355
+ <h5>Guest User Attempts Authentication</h5>
356
+
357
+ <p>While no Authenticated Member currently exists (as shown by the <code>session[:current_user]</code> having a value of either <code>nil</code> or the Guest User), supplying <em>the Guest User</em> as the User Entity to be Authenticated will yield a call to the block&#39;s <code>result.failure</code> block with a <code>code:</code> value of <code>:user_is_guest</code>.</p>
358
+
359
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
360
+
361
+ <h4>Other Notes</h4>
362
+
363
+ <p>This method <strong>does not</strong> interact with a Repository, and therefore doesn&#39;t need to account for an invalid User Name parameter, for instance. Nor does it directly modify session data, although the associated Controller Action Class code <strong>must</strong> set <code>session[:current_user]</code> and <code>session[:start_time]</code> as below. This is to support extraction of this code (along with anything else not using <code>Hanami::Controller</code>-dependent input validation, redirects, flash messages, etc) to an Interactor, into which would be explicitly passed <code>session[:current_user]</code>.</p>
364
+
365
+ <p>On <em>success</em>, the Controller Action Class calling code <strong>must</strong> set:</p>
366
+
367
+ <ul>
368
+ <li><code>session[:start_time]</code> to the current time as returned by <code>Time.now</code>; and</li>
369
+ <li><code>session[:current_user]</code> to the <em>Entity</em> (not the ID value from the Repository) for the newly-Authenticated User. This is to eliminate repeated reads of the Repository.</li>
370
+ </ul>
371
+
372
+ <p>On <em>failure</em>, the Controller Action Class calling code <strong>must</strong> set:</p>
373
+
374
+ <ul>
375
+ <li><code>session[:start_time]</code> to some sufficiently-past time to <em>always</em> trigger <code>#session_expired?</code>; <code>Hanami::Utils::Kernel.Time(0)</code> does this quite well, returning midnight GMT on 1 January 1970, converted to local time.</li>
376
+ <li><code>session[:current_user]</code> to <code>nil</code> or to the Guest User (see <a href="#configuration"><em>Configuration</em></a>).</li>
377
+ </ul>
378
+
379
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
380
+
381
+ <h3>Signing Out</h3>
382
+
383
+ <h4>Overview</h4>
384
+
385
+ <p>Method involved:</p>
386
+
387
+ <pre class="code ruby"><code class="ruby"><span class='kw'>module</span> <span class='const'><span class='object_link'><a href="CryptIdent.html" title="CryptIdent (module)">CryptIdent</a></span></span>
388
+ <span class='kw'>def</span> <span class='id identifier rubyid_sign_out'>sign_out</span><span class='lparen'>(</span><span class='label'>current_user:</span><span class='rparen'>)</span>
389
+ <span class='kw'>end</span>
390
+ <span class='kw'>end</span>
391
+ </code></pre>
392
+
393
+ <p>Signing out any previously Authenticated User is straightforward: call the <code>sign_out</code> method, passing in that User as the <code>current_user:</code> parameter. As with the earlier methods, this method also <strong>requires</strong> a block which accepts a <code>result</code> parameter and has <code>result.success</code> and <code>result.failure</code> calls/blocks. No parameters are yielded to either block.</p>
394
+
395
+ <p>Note that, as of Release 0.2.0, the method simply passes control to the (required) block, in whose <code>result.success</code> call block you can delete or reset <code>session[:current_user]</code> and <code>session[:start_time]</code>. We <strong>recommend</strong> reset values of:</p>
396
+
397
+ <ul>
398
+ <li><code>CryptIdent.config.guest_user</code> for <code>session[:current_user]</code> and</li>
399
+ <li><code>Hanami::Utils::Kernel.Time(0)</code> for <code>session[:start_time]</code>, which will set the timestamp to 1 January 1970 at midnight &mdash; a value which should <em>far</em> exceed your session-expiry limit if you decide not to simply delete the previous values by assigning <code>nil</code> to them.</li>
400
+ </ul>
401
+
402
+ <p>The required <code>result.failure</code> block can simply be skipped, as</p>
403
+
404
+ <pre class="code ruby"><code class="ruby"> <span class='id identifier rubyid_result'>result</span><span class='period'>.</span><span class='id identifier rubyid_failure'>failure</span> <span class='lbrace'>{</span> <span class='kw'>next</span> <span class='rbrace'>}</span>
405
+ </code></pre>
406
+
407
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
408
+
409
+ <h3>Password Change</h3>
410
+
411
+ <h4>Overview</h4>
412
+
413
+ <p>Method involved:</p>
414
+
415
+ <pre class="code ruby"><code class="ruby"> <span class='kw'>module</span> <span class='const'><span class='object_link'><a href="CryptIdent.html" title="CryptIdent (module)">CryptIdent</a></span></span>
416
+ <span class='kw'>def</span> <span class='id identifier rubyid_change_password'>change_password</span><span class='lparen'>(</span><span class='id identifier rubyid_user'>user</span><span class='comma'>,</span> <span class='id identifier rubyid_current_password'>current_password</span><span class='comma'>,</span> <span class='id identifier rubyid_new_password'>new_password</span><span class='rparen'>)</span>
417
+ <span class='comment'># ...
418
+ </span> <span class='kw'>end</span>
419
+ <span class='kw'>end</span>
420
+ </code></pre>
421
+
422
+ <p>To change an Authenticated User&#39;s password, an Entity for that User, the current Clear-Text Password, and the new Clear-Text Password are required.</p>
423
+
424
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
425
+
426
+ <h4>Successfully Changing the Password</h4>
427
+
428
+ <p>If all parameters are valid and the updated User is successfully persisted, the method calls the <strong>required</strong> block with a <code>result</code> whose <code>result.success</code> matcher is yielded a <code>user:</code> parameter with the updated User as its value. From that point, the User is able to Sign In using the User Name and updated Clear-Text Password.</p>
429
+
430
+ <p>Client code <strong>must</strong> take care not to try to Authenticate using the Encrypted Password in the Entity passed in to this method, as it is no longer current. Either retain the returned User Entity from the method, or read it again from the Repository.</p>
431
+
432
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
433
+
434
+ <h4>Error Conditions</h4>
435
+
436
+ <h5>Specified User is Guest User</h5>
437
+
438
+ <p>If the passed-in <code>user</code> is the Guest User (or <code>nil</code>), the method calls the <strong>required</strong> block with a <code>result</code> whose <code>result.failure</code> matcher is yielded a <code>code:</code> of <code>:invalid_user</code>. No new Entity with updated values is created; no changes are made to the Repository.</p>
439
+
440
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
441
+
442
+ <h5>Invalid Current Clear-Text Password</h5>
443
+
444
+ <p>If the specified Current Clear-Text Password cannot Authenticate against the encrypted value within the <code>user</code> Entity, the method calls the <strong>required</strong> block with a <code>result</code> whose <code>result.failure</code> matcher is yielded a <code>code:</code> of <code>:bad_password</code>. No new Entity with updated values is created; no changes are made to the Repository.</p>
445
+
446
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
447
+
448
+ <h3>Generate Password Reset Token and Password Reset: Introduction</h3>
449
+
450
+ <p>Password Reset Tokens are useful for verifying that the person requesting a Password Reset for an existing User is sufficiently likely to be the person who Registered that User or, if not, that no compromise or other harm is done.</p>
451
+
452
+ <p>Typically, this is done by sending a link through email or other such medium to the address previously associated with the User purportedly requesting the Password Reset. <code>CryptIdent</code> <em>does not</em> automate generation or sending of the email message. What it <em>does</em> provide is a method to generate a new Password Reset Token to be embedded into such a message, often in the form of an HTML anchor link within an email that you construct. It also provides another method (<code>#reset_password</code>) to actually change the password given a valid, correct token.</p>
453
+
454
+ <p>It also implements an expiry system, such that if the confirmation of the Password Reset request is not completed within a <a href="#Configuration">configurable</a> time, that the Token is no longer valid (and cannot be later reused by unauthorised persons).</p>
455
+
456
+ <p><strong>Note that</strong> multiple successful calls to generate a new Password Reset Token for a single User overwrite the data generated by previous calls, invalidating the previously-generated Tokens and resetting the expiry.</p>
457
+
458
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
459
+
460
+ <h3>Generate Password Reset Token</h3>
461
+
462
+ <p>Method involved:</p>
463
+
464
+ <pre class="code ruby"><code class="ruby"><span class='kw'>module</span> <span class='const'><span class='object_link'><a href="CryptIdent.html" title="CryptIdent (module)">CryptIdent</a></span></span>
465
+ <span class='kw'>def</span> <span class='id identifier rubyid_generate_reset_token'>generate_reset_token</span><span class='lparen'>(</span><span class='id identifier rubyid_user_name'>user_name</span><span class='comma'>,</span> <span class='label'>current_user:</span> <span class='kw'>nil</span><span class='rparen'>)</span>
466
+ <span class='comment'># ...
467
+ </span> <span class='kw'>end</span>
468
+ <span class='kw'>end</span>
469
+ </code></pre>
470
+
471
+ <h4>Successfully Generating a Token</h4>
472
+
473
+ <p>Given a <code>user_name</code> parameter that specifies an existing User Name, and a <code>current_user:</code> parameter that is either <code>nil</code> or the Guest User, the method calls the <strong>required</strong> block with a <code>result</code> whose <code>result.success</code> matcher is yielded a <code>user:</code> parameter with a User Entity as its value. That User will be an Entity whose <code>name</code> matches the specified <code>user_name</code> parameter, with (new) values for the <code>token</code> and <code>password_reset_expires_at</code> attributes. The <code>token</code> attribute uniquely identifies the Password Reset request, and the <code>password_reset_expires_at</code> attribute is based on both the current (server-local) time when the updated User Entity was persisted to the Repository, and the <code>:reset_expiry</code> attribute of the configuration.</p>
474
+
475
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
476
+
477
+ <h4>Error Conditions</h4>
478
+
479
+ <h5>Authenticated User Exists</h5>
480
+
481
+ <p>If the specified <code>current_user:</code> parameter is a valid User Entity other than the Guest User, then that is presumed to be the Current User of the Application. Authenticated Users are prohibited from requesting Password Resets for other Users; if they wish to change their <em>own</em> Clear-Text Password, there&#39;s a <a href="#password-change">method</a> for that.</p>
482
+
483
+ <p>In this case, the <strong>required</strong> block will be passed a <code>result</code> whose <code>result.failure</code> matcher is yielded a <code>code:</code> parameter of <code>:user_logged_in</code>, a <code>current_user:</code> parameter matching the passed-in User Entity, and a <code>name:</code> parameter of <code>:unassigned</code> (which must be included in the block parameters but can be ignored thereafter).</p>
484
+
485
+ <h5>Named User Not Found in Repository</h5>
486
+
487
+ <p>If the specified <code>user_name</code> parameter value does not match the <code>name</code> of any User in the Repository, then the <strong>required</strong> block will be passed a <code>result</code> whose <code>result.failure</code> matcher is yielded a <code>code:</code> parameter of <code>:user_not_found</code>, a <code>current_user:</code> parameter of the Guest User, and a <code>name:</code> parameter whose value is the passed-in <code>user_name</code> value.</p>
488
+
489
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
490
+
491
+ <h3>Password Reset</h3>
492
+
493
+ <h4>Overview</h4>
494
+
495
+ <p>Method involved:</p>
496
+
497
+ <pre class="code ruby"><code class="ruby"><span class='kw'>module</span> <span class='const'><span class='object_link'><a href="CryptIdent.html" title="CryptIdent (module)">CryptIdent</a></span></span>
498
+ <span class='kw'>def</span> <span class='id identifier rubyid_reset_password'>reset_password</span><span class='lparen'>(</span><span class='id identifier rubyid_token'>token</span><span class='comma'>,</span> <span class='id identifier rubyid_new_password'>new_password</span><span class='comma'>,</span> <span class='label'>current_user:</span> <span class='kw'>nil</span><span class='rparen'>)</span>
499
+ <span class='comment'># ...
500
+ </span> <span class='kw'>end</span>
501
+ <span class='kw'>end</span>
502
+ </code></pre>
503
+
504
+ <p>Calling <code>#reset_password</code> is different than calling <code>#change_password</code> in one vital respect: with <code>#change_password</code>, the User involved <strong>must</strong> be the Current User (as presumed by passing the appropriate User Entity in as the <code>current_user:</code> parameter), whereas <code>#reset_password</code> <strong>must not</strong> be called with <em>any</em> User other than the Guest User as the <code>current_user:</code> parameter (and, again presumably, the Current User for the session). How can we assure ourselves that the request is legitimate for a specific User? By use of the Token generated by a previous call to <code>#generate_reset_token</code>, which is used <em>in place of</em> a User Name for this request.</p>
505
+
506
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
507
+
508
+ <h4>Successfully Resetting a Password</h4>
509
+
510
+ <p>To successfully perform a Password Reset, supply a valid, non-expired Token along with a new Clear-Text Password to the <code>#reset_password</code> method. Once the Token is found in the configuration-default Repository, and is verified not to have Expired, then the Repository will be updated with a record for that User where the <code>password_hash</code> field has been updated to reflect the new Clear-Text Password, and the <code>token</code> and <code>password_reset_expires_at</code> fields will be set to <code>nil</code>.</p>
511
+
512
+ <p>If all the preceding is successful and the updated User is successfully persisted, the method calls the <strong>required</strong> block with a <code>result</code> whose <code>result.success</code> matcher is yielded a <code>user:</code> parameter with the updated User as its value. From that point, the User is able to Sign In using the User Name and updated Clear-Text Password.</p>
513
+
514
+ <p>Client code <strong>must</strong> take care not to try to Authenticate using the Encrypted Password in the Entity passed in to this method, as it is no longer current. Either retain the returned User Entity from the method, or read it again from the Repository.</p>
515
+
516
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
517
+
518
+ <h4>Error Conditions</h4>
519
+
520
+ <h5>Expired Token</h5>
521
+
522
+ <p>If the passed-in <code>token</code> parameter matches the <code>token</code> field of a record in the Repository <em>and</em> that Token is determined to have Expired, then this method calls the <strong>required</strong> block with a <code>result</code> whose <code>result.failure</code> matcher is yielded a <code>code:</code> parameter of <code>:expired_token</code> and a <code>token:</code> parameter that has the same value as the passed-in <code>token</code> parameter.</p>
523
+
524
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
525
+
526
+ <h5>Token Not Found</h5>
527
+
528
+ <p>If the passed-in <code>token</code> parameter <em>does not</em> match the <code>token</code> field of any record in the Repository, then this method calls the <strong>required</strong> block with a <code>result</code> whose <code>result.failure</code> matcher is yielded a <code>code:</code> parameter of <code>:token_not_found</code> and a <code>token:</code> parameter that has the same value as the passed-in <code>token</code> parameter.</p>
529
+
530
+ <h5>Invalid Current User</h5>
531
+
532
+ <p>If the passed-in <code>current_user:</code> parameter <em>is not</em> either the default <code>nil</code> or the Guest User, then this method calls the <strong>required</strong> block with a <code>result</code> whose <code>result.failure</code> matcher is yielded a <code>code:</code> parameter of <code>:invalid_current_user</code> and a <code>token:</code> parameter that has the same value as the passed-in <code>token</code> parameter.</p>
533
+
534
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
535
+
536
+ <h3>Session Management Overview</h3>
537
+
538
+ <p>Session management is a necessary part of implementing authentication (and authorisation) within an app. However, it&#39;s not something that an authentication <em>library</em> can fully implement without making the client application excessively inflexible and brittle.</p>
539
+
540
+ <p><code>CryptIdent</code> has two convenience methods which <em>help in</em> implementing session-expiration logic; these make use of the <code>session_expiry</code> <a href="#configuration">configuration value</a>.</p>
541
+
542
+ <ul>
543
+ <li><code>CryptIdent#update_session_expiry</code> returns a <code>Hash</code> whose <code>:expires_at</code> value the current time <em>plus</em> the number of seconds specified by the <code>session_expiry</code> configuration value. This can be used to update the corresponding <code>session</code> data which defines the session-expiry time;</li>
544
+ <li><code>CryptIdent#session_expired?</code> returns <code>true</code> if the current time is not less than the session-expiry time; it returns <code>false</code> otherwise.</li>
545
+ </ul>
546
+
547
+ <p>Example code which uses these methods is illustrated below, as a shared-code module that may be included in your controllers&#39; action classes:</p>
548
+
549
+ <pre class="code ruby"><code class="ruby"><span class='comment'># apps/web/controllers/handle_session.rb
550
+ </span>
551
+ <span class='kw'>module</span> <span class='const'>Web</span>
552
+ <span class='kw'>module</span> <span class='const'>HandleSession</span>
553
+ <span class='id identifier rubyid_include'>include</span> <span class='const'><span class='object_link'><a href="CryptIdent.html" title="CryptIdent (module)">CryptIdent</a></span></span>
554
+
555
+ <span class='kw'>def</span> <span class='kw'>self</span><span class='period'>.</span><span class='id identifier rubyid_included'>included</span><span class='lparen'>(</span><span class='id identifier rubyid_other'>other</span><span class='rparen'>)</span>
556
+ <span class='id identifier rubyid_other'>other</span><span class='period'>.</span><span class='id identifier rubyid_class_eval'>class_eval</span> <span class='kw'>do</span>
557
+ <span class='id identifier rubyid_before'>before</span> <span class='symbol'>:validate_session</span>
558
+ <span class='kw'>end</span>
559
+ <span class='kw'>end</span>
560
+
561
+ <span class='id identifier rubyid_private'>private</span>
562
+
563
+ <span class='kw'>def</span> <span class='id identifier rubyid_validate_session'>validate_session</span>
564
+ <span class='id identifier rubyid_updates'>updates</span> <span class='op'>=</span> <span class='id identifier rubyid_update_session_expiry'>update_session_expiry</span><span class='lparen'>(</span><span class='id identifier rubyid_session'>session</span><span class='rparen'>)</span>
565
+ <span class='kw'>if</span> <span class='op'>!</span><span class='id identifier rubyid_session_expired?'>session_expired?</span><span class='lparen'>(</span><span class='id identifier rubyid_session'>session</span><span class='rparen'>)</span>
566
+ <span class='id identifier rubyid_session'>session</span><span class='lbracket'>[</span><span class='symbol'>:expires_at</span><span class='rbracket'>]</span> <span class='op'>=</span> <span class='id identifier rubyid_updates'>updates</span><span class='lbracket'>[</span><span class='symbol'>:expires_at</span><span class='rbracket'>]</span>
567
+ <span class='kw'>return</span>
568
+ <span class='kw'>end</span>
569
+
570
+ <span class='ivar'>@redirect_url</span> <span class='op'>||=</span> <span class='id identifier rubyid_routes'>routes</span><span class='period'>.</span><span class='id identifier rubyid_root_path'>root_path</span>
571
+ <span class='id identifier rubyid_config'>config</span> <span class='op'>=</span> <span class='const'><span class='object_link'><a href="CryptIdent.html" title="CryptIdent (module)">CryptIdent</a></span></span><span class='period'>.</span><span class='id identifier rubyid_config'>config</span>
572
+ <span class='id identifier rubyid_session'>session</span><span class='lbracket'>[</span><span class='symbol'>:current_user</span><span class='rbracket'>]</span> <span class='op'>=</span> <span class='id identifier rubyid_config'>config</span><span class='period'>.</span><span class='id identifier rubyid_guest_user'>guest_user</span>
573
+ <span class='id identifier rubyid_session'>session</span><span class='lbracket'>[</span><span class='symbol'>:expires_at</span><span class='rbracket'>]</span> <span class='op'>=</span> <span class='id identifier rubyid_updates'>updates</span><span class='lbracket'>[</span><span class='symbol'>:expires_at</span><span class='rbracket'>]</span>
574
+ <span class='id identifier rubyid_error_message'>error_message</span> <span class='op'>=</span> <span class='tstring'><span class='tstring_beg'>&#39;</span><span class='tstring_content'>Your session has expired. You have been signed out.</span><span class='tstring_end'>&#39;</span></span>
575
+ <span class='id identifier rubyid_flash'>flash</span><span class='lbracket'>[</span><span class='id identifier rubyid_config'>config</span><span class='period'>.</span><span class='id identifier rubyid_error_key'>error_key</span><span class='rbracket'>]</span> <span class='op'>=</span> <span class='id identifier rubyid_error_message'>error_message</span>
576
+ <span class='id identifier rubyid_redirect_to'>redirect_to</span> <span class='ivar'>@redirect_url</span>
577
+ <span class='kw'>end</span>
578
+ <span class='kw'>end</span>
579
+ <span class='kw'>end</span>
580
+ </code></pre>
581
+
582
+ <p>This code should be fairly self-explanatory. Including the module adds the private <code>#validate_session</code> method to the client controller action class, adding a call to that method before the action class&#39; <code>#call</code> method is entered. (One can argue that this violates the spirit if not the letter of the <a href="https://guides.hanamirb.org/actions/control-flow/">Hanami Guide&#39;s</a> warning not to &quot;use callbacks for model domain logic operations&quot;. We would argue that this callback&#39;s functionality is common to essentially all client applications and, by providing a reference example, allows individual project teams to modify it as required for their use.) If the session-expiry time has been previously set and is not before the current time, then that session-expiry time is reset based on the current time, and no further action is taken. Otherwise:</p>
583
+
584
+ <ol>
585
+ <li>The <code>current_user</code> setting in the session data is overwritten with the <a href="#configuration"><code>config.guest_user</code></a> value (defaulting to <code>nil</code>);</li>
586
+ <li>A flash error message is set, which <strong>should</strong> be rendered within the controller action&#39;s view; and</li>
587
+ <li>Control is redirected to the path or URL specified by <code>@redirect_url</code>, defaulting to the root path (<code>/</code>).</li>
588
+ </ol>
589
+
590
+ <p>This code will be instantly familiar to anyone coming from another framework like Rails, where the conventional way to ensure authentication before a controller action is executed is to add a <code>:before</code> hook. Adding this module to the controller action class is also justifiable Hanami, since it depends on and interacts with session data. (Just don&#39;t let any actual domain logic <a href="http://hanamirb.org/guides/1.2/actions/control-flow/#proc">taint</a> your controller callbacks; that&#39;s begging for difficult-to-debug problems going forward.</p>
591
+
592
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
593
+
594
+ <h3>Session Expired</h3>
595
+
596
+ <p>Method involved:</p>
597
+
598
+ <pre class="code ruby"><code class="ruby"><span class='kw'>module</span> <span class='const'><span class='object_link'><a href="CryptIdent.html" title="CryptIdent (module)">CryptIdent</a></span></span>
599
+ <span class='kw'>def</span> <span class='id identifier rubyid_session_expired?'>session_expired?</span><span class='lparen'>(</span><span class='id identifier rubyid_session_data'>session_data</span><span class='op'>=</span><span class='lbrace'>{</span><span class='rbrace'>}</span><span class='rparen'>)</span>
600
+ <span class='comment'># ...
601
+ </span> <span class='kw'>end</span>
602
+ <span class='kw'>end</span>
603
+ </code></pre>
604
+
605
+ <p>This is one of two methods in <code>CryptIdent</code> (the other being <a href="#update-session-expiry"><code>#update_session_expiry</code></a>, below) which <em>does not</em> follow the <code>result</code>/success/failure <a href="#interfaces">monad workflow</a>. Like that method:</p>
606
+
607
+ <ul>
608
+ <li>there is no success/failure division in the workflow;</li>
609
+ <li>calling this method only makes sense if there is an Authenticated User;</li>
610
+ <li>it is intended for use in session-management code as described in the <a href="#session-management-overview">Overview</a> above.</li>
611
+ </ul>
612
+
613
+ <p>This method checks the passed-in <code>session_data[:start_time]</code> value against the current time. If the difference is <em>greater than</em> the <a href="#configuration">configured</a> <em>Session Expiry</em> value, then the method returns <code>true</code>; otherwise, it returns <code>false</code>. No change is attempted to the contents of the passed-in <code>session_data</code>.</p>
614
+
615
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
616
+
617
+ <h3>Update Session Expiry</h3>
618
+
619
+ <h4>Overview</h4>
620
+
621
+ <p>Method involved:</p>
622
+
623
+ <pre class="code ruby"><code class="ruby"><span class='kw'>module</span> <span class='const'><span class='object_link'><a href="CryptIdent.html" title="CryptIdent (module)">CryptIdent</a></span></span>
624
+ <span class='kw'>def</span> <span class='id identifier rubyid_update_session_expiry'>update_session_expiry</span><span class='lparen'>(</span><span class='id identifier rubyid_session_data'>session_data</span><span class='op'>=</span><span class='lbrace'>{</span><span class='rbrace'>}</span><span class='rparen'>)</span>
625
+ <span class='comment'># ...
626
+ </span> <span class='kw'>end</span>
627
+ <span class='kw'>end</span>
628
+ </code></pre>
629
+
630
+ <p>This is one of two methods in <code>CryptIdent</code> (the other being <a href="#session-expired"><code>#session_expired?</code></a>, above) which <em>does not</em> follow the <code>result</code>/success/failure <a href="#interfaces">monad workflow</a>. This is because there is no success/failure division in the workflow. Calling the method only makes sense if there is an Authenticated User, but <em>all this method does</em> is return a <code>Hash</code> as defined below.</p>
631
+
632
+ <p>It is intended for use in session-management code as described in the <a href="#session-management-overview">Overview</a> above.</p>
633
+
634
+ <h4>Parameter</h4>
635
+
636
+ <p>The parameter, <code>session_data</code>, is a Hash-like object which <strong>should</strong> have existing entries for <code>:current_user</code> (defaulting to the Guest User if not found) and for <code>:expires_at</code> (defaulting to the <a href="https://en.wikipedia.org/wiki/Unix_time">epoch</a> if not found).</p>
637
+
638
+ <h4>Return</h4>
639
+
640
+ <p>The return value is a <code>Hash</code> which:</p>
641
+
642
+ <ol>
643
+ <li><code>:current_user</code> value is the same as the passed-in parameter&#39;s <code>:current_user</code> value <em>if</em> that is a Registered User, or the Guest User if it isn&#39;t; and</li>
644
+ <li><code>start_time</code> value is a <code>Time</code> instance based on the current time when called.</li>
645
+ </ol>
646
+
647
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
648
+
649
+ <h1>API Documentation</h1>
650
+
651
+ <p>See <a href="/CryptIdent.html">the Documentation Index</a>.</p>
652
+
653
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
654
+
655
+ <h1>Development</h1>
656
+
657
+ <p>After checking out the repo, run <code>bin/setup</code> to install dependencies. If you use <a href="https://github.com/rbenv/rbenv"><code>rbenv</code></a> and <a href="https://github.com/jf/rbenv-gemset"><code>rbenv-gemset</code></a>, the <code>setup</code> script will create a new Gemset (in <code>./tmp/gemset</code>) to keep your system Gem repository pristine. Then, run <code>bin/rake test</code> to run the tests, or <code>bin/rake</code> without arguments to run tests and all static-analysis tools (<a href="https://github.com/seattlerb/flog">Flog</a>, <a href="https://github.com/seattlerb/flay">Flay</a>, <a href="https://github.com/troessner/reek">Reek</a>, and <a href="https://github.com/rubocop-hq/rubocop/">RuboCop</a>). Running <code>bin/rake inch</code> will let <a href="http://trivelop.de/inch/">Inch</a> comment on the amount of internal documentation in the project.</p>
658
+
659
+ <p>You can also run <code>bin/console</code> for an interactive prompt that will allow you to experiment.</p>
660
+
661
+ <p>To install this gem onto your local machine, run <code>bin/rake install</code> or <code>bundle exec rake install</code>. To release a new version, update the version number in <code>version.rb</code>, and then run <code>bin/rake release</code> or <code>bundle exec rake release</code>, which will create a Git tag for the version, push Git commits and tags, and push the <code>.gem</code> file to <a href="https://rubygems.org">rubygems.org</a>.</p>
662
+
663
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
664
+
665
+ <h1>Contributing</h1>
666
+
667
+ <p>Bug reports and pull requests are welcome on GitHub at <a href="https://github.com/jdickey/crypt_ident">https://github.com/jdickey/crypt_ident</a>. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the <a href="http://contributor-covenant.org">Contributor Covenant</a> code of conduct.</p>
668
+
669
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
670
+
671
+ <h1>Copyright and License</h1>
672
+
673
+ <p>This Gem, its source code, and all supporting documents and artefacts are Copyright &copy;2019 by Jeff Dickey. They are available as open source under the terms of the <a href="https://opensource.org/licenses/MIT">MIT License</a>.</p>
674
+
675
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
676
+
677
+ <h1>Code of Conduct</h1>
678
+
679
+ <p>Everyone interacting in the CryptIdent project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the <a href="https://github.com/jdickey/crypt_ident/blob/master/CODE_OF_CONDUCT.md">code of conduct</a>.</p>
680
+
681
+ <p><sub style="font-size: 0.75rem;"><a href="#CryptIdent">Back to Top</a></sub></p>
682
+ </div></div>
683
+
684
+ <div id="footer">
685
+ Generated on Sat Feb 16 04:17:25 2019 by
686
+ <a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
687
+ 0.9.18 (ruby-2.6.0).
688
+ </div>
689
+
690
+ </div>
691
+ </body>
692
+ </html>