odac 0.9.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.
Files changed (213) hide show
  1. package/.editorconfig +21 -0
  2. package/.github/workflows/auto-pr-description.yml +49 -0
  3. package/.github/workflows/release.yml +32 -0
  4. package/.github/workflows/test-coverage.yml +58 -0
  5. package/.husky/pre-commit +2 -0
  6. package/.kiro/steering/code-style.md +56 -0
  7. package/.kiro/steering/product.md +20 -0
  8. package/.kiro/steering/structure.md +77 -0
  9. package/.kiro/steering/tech.md +87 -0
  10. package/.prettierrc +10 -0
  11. package/.releaserc.js +134 -0
  12. package/AGENTS.md +84 -0
  13. package/CHANGELOG.md +181 -0
  14. package/CODE_OF_CONDUCT.md +83 -0
  15. package/CONTRIBUTING.md +63 -0
  16. package/LICENSE +661 -0
  17. package/README.md +57 -0
  18. package/SECURITY.md +26 -0
  19. package/bin/candy +10 -0
  20. package/bin/candypack +10 -0
  21. package/cli/index.js +3 -0
  22. package/cli/src/Cli.js +348 -0
  23. package/cli/src/Connector.js +93 -0
  24. package/cli/src/Monitor.js +416 -0
  25. package/core/Candy.js +87 -0
  26. package/core/Commands.js +239 -0
  27. package/core/Config.js +1094 -0
  28. package/core/Lang.js +52 -0
  29. package/core/Log.js +43 -0
  30. package/core/Process.js +26 -0
  31. package/docs/backend/01-overview/01-whats-in-the-candy-box.md +9 -0
  32. package/docs/backend/01-overview/02-super-handy-helper-functions.md +9 -0
  33. package/docs/backend/01-overview/03-development-server.md +79 -0
  34. package/docs/backend/02-structure/01-typical-project-layout.md +39 -0
  35. package/docs/backend/03-config/00-configuration-overview.md +214 -0
  36. package/docs/backend/03-config/01-database-connection.md +60 -0
  37. package/docs/backend/03-config/02-static-route-mapping-optional.md +20 -0
  38. package/docs/backend/03-config/03-request-timeout.md +11 -0
  39. package/docs/backend/03-config/04-environment-variables.md +227 -0
  40. package/docs/backend/03-config/05-early-hints.md +352 -0
  41. package/docs/backend/04-routing/01-basic-page-routes.md +28 -0
  42. package/docs/backend/04-routing/02-controller-less-view-routes.md +43 -0
  43. package/docs/backend/04-routing/03-api-and-data-routes.md +20 -0
  44. package/docs/backend/04-routing/04-authentication-aware-routes.md +48 -0
  45. package/docs/backend/04-routing/05-advanced-routing.md +14 -0
  46. package/docs/backend/04-routing/06-error-pages.md +101 -0
  47. package/docs/backend/04-routing/07-cron-jobs.md +149 -0
  48. package/docs/backend/05-controllers/01-how-to-build-a-controller.md +17 -0
  49. package/docs/backend/05-controllers/02-your-trusty-candy-assistant.md +20 -0
  50. package/docs/backend/05-controllers/03-controller-classes.md +93 -0
  51. package/docs/backend/05-forms/01-custom-forms.md +395 -0
  52. package/docs/backend/05-forms/02-automatic-database-insert.md +297 -0
  53. package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +96 -0
  54. package/docs/backend/06-request-and-response/02-sending-a-response-replying-to-the-user.md +40 -0
  55. package/docs/backend/07-views/01-the-view-directory.md +73 -0
  56. package/docs/backend/07-views/02-rendering-a-view.md +179 -0
  57. package/docs/backend/07-views/03-template-syntax.md +181 -0
  58. package/docs/backend/07-views/03-variables.md +328 -0
  59. package/docs/backend/07-views/04-request-data.md +231 -0
  60. package/docs/backend/07-views/05-conditionals.md +290 -0
  61. package/docs/backend/07-views/06-loops.md +353 -0
  62. package/docs/backend/07-views/07-translations.md +358 -0
  63. package/docs/backend/07-views/08-backend-javascript.md +398 -0
  64. package/docs/backend/07-views/09-comments.md +297 -0
  65. package/docs/backend/08-database/01-database-connection.md +99 -0
  66. package/docs/backend/08-database/02-using-mysql.md +322 -0
  67. package/docs/backend/09-validation/01-the-validator-service.md +424 -0
  68. package/docs/backend/10-authentication/01-user-logins-with-authjs.md +53 -0
  69. package/docs/backend/10-authentication/02-foiling-villains-with-csrf-protection.md +55 -0
  70. package/docs/backend/10-authentication/03-register.md +134 -0
  71. package/docs/backend/10-authentication/04-candy-register-forms.md +676 -0
  72. package/docs/backend/10-authentication/05-session-management.md +159 -0
  73. package/docs/backend/10-authentication/06-candy-login-forms.md +596 -0
  74. package/docs/backend/11-mail/01-the-mail-service.md +42 -0
  75. package/docs/backend/12-streaming/01-streaming-overview.md +300 -0
  76. package/docs/backend/13-utilities/01-candy-var.md +504 -0
  77. package/docs/frontend/01-overview/01-introduction.md +146 -0
  78. package/docs/frontend/02-ajax-navigation/01-quick-start.md +608 -0
  79. package/docs/frontend/02-ajax-navigation/02-configuration.md +370 -0
  80. package/docs/frontend/02-ajax-navigation/03-advanced-usage.md +519 -0
  81. package/docs/frontend/03-forms/01-form-handling.md +420 -0
  82. package/docs/frontend/04-api-requests/01-get-post.md +443 -0
  83. package/docs/frontend/05-streaming/01-client-streaming.md +163 -0
  84. package/docs/index.json +452 -0
  85. package/docs/server/01-installation/01-quick-install.md +19 -0
  86. package/docs/server/01-installation/02-manual-installation-via-npm.md +9 -0
  87. package/docs/server/02-get-started/01-core-concepts.md +7 -0
  88. package/docs/server/02-get-started/02-basic-commands.md +57 -0
  89. package/docs/server/02-get-started/03-cli-reference.md +276 -0
  90. package/docs/server/02-get-started/04-cli-quick-reference.md +102 -0
  91. package/docs/server/03-service/01-start-a-new-service.md +57 -0
  92. package/docs/server/03-service/02-delete-a-service.md +48 -0
  93. package/docs/server/04-web/01-create-a-website.md +36 -0
  94. package/docs/server/04-web/02-list-websites.md +9 -0
  95. package/docs/server/04-web/03-delete-a-website.md +29 -0
  96. package/docs/server/05-subdomain/01-create-a-subdomain.md +32 -0
  97. package/docs/server/05-subdomain/02-list-subdomains.md +33 -0
  98. package/docs/server/05-subdomain/03-delete-a-subdomain.md +41 -0
  99. package/docs/server/06-ssl/01-renew-an-ssl-certificate.md +34 -0
  100. package/docs/server/07-mail/01-create-a-mail-account.md +23 -0
  101. package/docs/server/07-mail/02-delete-a-mail-account.md +20 -0
  102. package/docs/server/07-mail/03-list-mail-accounts.md +20 -0
  103. package/docs/server/07-mail/04-change-account-password.md +23 -0
  104. package/eslint.config.mjs +120 -0
  105. package/framework/index.js +4 -0
  106. package/framework/src/Auth.js +309 -0
  107. package/framework/src/Candy.js +81 -0
  108. package/framework/src/Config.js +79 -0
  109. package/framework/src/Env.js +60 -0
  110. package/framework/src/Lang.js +57 -0
  111. package/framework/src/Mail.js +83 -0
  112. package/framework/src/Mysql.js +575 -0
  113. package/framework/src/Request.js +301 -0
  114. package/framework/src/Route/Cron.js +128 -0
  115. package/framework/src/Route/Internal.js +439 -0
  116. package/framework/src/Route.js +455 -0
  117. package/framework/src/Server.js +15 -0
  118. package/framework/src/Stream.js +163 -0
  119. package/framework/src/Token.js +37 -0
  120. package/framework/src/Validator.js +271 -0
  121. package/framework/src/Var.js +211 -0
  122. package/framework/src/View/EarlyHints.js +190 -0
  123. package/framework/src/View/Form.js +600 -0
  124. package/framework/src/View.js +513 -0
  125. package/framework/web/candy.js +838 -0
  126. package/jest.config.js +22 -0
  127. package/locale/de-DE.json +80 -0
  128. package/locale/en-US.json +79 -0
  129. package/locale/es-ES.json +80 -0
  130. package/locale/fr-FR.json +80 -0
  131. package/locale/pt-BR.json +80 -0
  132. package/locale/ru-RU.json +80 -0
  133. package/locale/tr-TR.json +85 -0
  134. package/locale/zh-CN.json +80 -0
  135. package/package.json +86 -0
  136. package/server/index.js +5 -0
  137. package/server/src/Api.js +88 -0
  138. package/server/src/DNS.js +940 -0
  139. package/server/src/Hub.js +535 -0
  140. package/server/src/Mail.js +571 -0
  141. package/server/src/SSL.js +180 -0
  142. package/server/src/Server.js +27 -0
  143. package/server/src/Service.js +248 -0
  144. package/server/src/Subdomain.js +64 -0
  145. package/server/src/Web/Firewall.js +170 -0
  146. package/server/src/Web/Proxy.js +134 -0
  147. package/server/src/Web.js +451 -0
  148. package/server/src/mail/imap.js +1091 -0
  149. package/server/src/mail/server.js +32 -0
  150. package/server/src/mail/smtp.js +786 -0
  151. package/test/cli/Cli.test.js +36 -0
  152. package/test/core/Candy.test.js +234 -0
  153. package/test/core/Commands.test.js +538 -0
  154. package/test/core/Config.test.js +1435 -0
  155. package/test/core/Lang.test.js +250 -0
  156. package/test/core/Process.test.js +156 -0
  157. package/test/framework/Route.test.js +239 -0
  158. package/test/framework/View/EarlyHints.test.js +282 -0
  159. package/test/scripts/check-coverage.js +132 -0
  160. package/test/server/Api.test.js +647 -0
  161. package/test/server/Client.test.js +338 -0
  162. package/test/server/DNS.test.js +2050 -0
  163. package/test/server/DNS.test.js.bak +2084 -0
  164. package/test/server/Log.test.js +73 -0
  165. package/test/server/Mail.account.test_.js +460 -0
  166. package/test/server/Mail.init.test_.js +411 -0
  167. package/test/server/Mail.test_.js +1340 -0
  168. package/test/server/SSL.test_.js +1491 -0
  169. package/test/server/Server.test.js +765 -0
  170. package/test/server/Service.test_.js +1127 -0
  171. package/test/server/Subdomain.test.js +440 -0
  172. package/test/server/Web/Firewall.test.js +175 -0
  173. package/test/server/Web.test_.js +1562 -0
  174. package/test/server/__mocks__/acme-client.js +17 -0
  175. package/test/server/__mocks__/bcrypt.js +50 -0
  176. package/test/server/__mocks__/child_process.js +389 -0
  177. package/test/server/__mocks__/crypto.js +432 -0
  178. package/test/server/__mocks__/fs.js +450 -0
  179. package/test/server/__mocks__/globalCandy.js +227 -0
  180. package/test/server/__mocks__/http-proxy.js +105 -0
  181. package/test/server/__mocks__/http.js +575 -0
  182. package/test/server/__mocks__/https.js +272 -0
  183. package/test/server/__mocks__/index.js +249 -0
  184. package/test/server/__mocks__/mail/server.js +100 -0
  185. package/test/server/__mocks__/mail/smtp.js +31 -0
  186. package/test/server/__mocks__/mailparser.js +81 -0
  187. package/test/server/__mocks__/net.js +369 -0
  188. package/test/server/__mocks__/node-forge.js +328 -0
  189. package/test/server/__mocks__/os.js +320 -0
  190. package/test/server/__mocks__/path.js +291 -0
  191. package/test/server/__mocks__/selfsigned.js +8 -0
  192. package/test/server/__mocks__/server/src/mail/server.js +100 -0
  193. package/test/server/__mocks__/server/src/mail/smtp.js +31 -0
  194. package/test/server/__mocks__/smtp-server.js +106 -0
  195. package/test/server/__mocks__/sqlite3.js +394 -0
  196. package/test/server/__mocks__/testFactories.js +299 -0
  197. package/test/server/__mocks__/testHelpers.js +363 -0
  198. package/test/server/__mocks__/tls.js +229 -0
  199. package/watchdog/index.js +3 -0
  200. package/watchdog/src/Watchdog.js +156 -0
  201. package/web/config.json +5 -0
  202. package/web/controller/page/about.js +27 -0
  203. package/web/controller/page/index.js +34 -0
  204. package/web/package.json +18 -0
  205. package/web/public/assets/css/style.css +1835 -0
  206. package/web/public/assets/js/app.js +96 -0
  207. package/web/route/www.js +19 -0
  208. package/web/skeleton/main.html +22 -0
  209. package/web/view/content/about.html +65 -0
  210. package/web/view/content/home.html +205 -0
  211. package/web/view/footer/main.html +11 -0
  212. package/web/view/head/main.html +5 -0
  213. package/web/view/header/main.html +14 -0
@@ -0,0 +1,424 @@
1
+ ## ✅ The `Validator` Service
2
+
3
+ The Validator service provides a fluent, chainable API for validating user input. It's automatically available in your controllers through `Candy.Validator`.
4
+
5
+ #### Basic Usage
6
+
7
+ The validator uses a method-chaining pattern:
8
+
9
+ ```javascript
10
+ const validator = Candy.Validator
11
+ validator.post('email').check('required|email').message('Valid email required')
12
+ validator.post('password').check('required|minlen:8').message('Password must be at least 8 characters')
13
+
14
+ if (await validator.error()) {
15
+ return validator.result('Validation failed')
16
+ }
17
+ ```
18
+
19
+ #### Available Methods
20
+
21
+ - `post(key)` - Validate a POST field
22
+ - `get(key)` - Validate a GET field
23
+ - `var(name, value)` - Validate a custom variable
24
+ - `file(name)` - Validate a file upload
25
+ - `check(rules)` - Define validation rules (pipe-separated)
26
+ - `message(text)` - Set custom error message
27
+ - `error()` - Returns true if validation failed (async)
28
+ - `result(message, data)` - Returns formatted result object (async)
29
+ - `success(callback)` - Returns success result with data
30
+ - `brute(maxAttempts)` - Enable brute force protection (default: 5 attempts)
31
+
32
+ #### Validation Rules
33
+
34
+ **Type Validation:**
35
+ - `required` - Field cannot be empty
36
+ - `accepted` - Must be 1, 'on', 'yes', or true
37
+ - `numeric` - Must be a number
38
+ - `alpha` - Only alphabetic characters
39
+ - `alphaspace` - Alphabetic characters and spaces
40
+ - `alphanumeric` - Alphanumeric characters only
41
+ - `alphanumericspace` - Alphanumeric characters and spaces
42
+ - `username` - Alphanumeric username (no spaces or special chars)
43
+ - `email` - Valid email address
44
+ - `ip` - Valid IP address
45
+ - `float` - Floating point number
46
+ - `mac` - Valid MAC address
47
+ - `domain` - Valid domain name
48
+ - `url` - Valid URL
49
+ - `array` - Must be an array
50
+ - `date` - Valid date format
51
+
52
+ **Length Validation:**
53
+ - `len:X` - Exact length must be X
54
+ - `minlen:X` - Minimum length of X characters
55
+ - `maxlen:X` - Maximum length of X characters
56
+
57
+ **Value Validation:**
58
+ - `min:X` - Minimum value of X
59
+ - `max:X` - Maximum value of X
60
+ - `equal:value` - Must equal specific value
61
+ - `not:value` - Must not equal specific value
62
+ - `same:field` - Must match another field
63
+ - `different:field` - Must differ from another field
64
+
65
+ **Date Validation:**
66
+ - `mindate:date` - Must be after specified date
67
+ - `maxdate:date` - Must be before specified date
68
+
69
+ **String Validation:**
70
+ - `in:substring` - Must contain substring
71
+ - `notin:substring` - Must not contain substring
72
+ - `regex:pattern` - Must match regex pattern
73
+
74
+ **Security:**
75
+ - `xss` - Check for HTML tags (XSS protection)
76
+ - `usercheck` - User must be authenticated
77
+ - `user:field` - Must match authenticated user's field value
78
+
79
+ **Inverse Rules:**
80
+ Use `!` prefix to invert any rule: `!required`, `!email`, etc.
81
+
82
+ #### Example: User Registration
83
+
84
+ ```javascript
85
+ module.exports = async function (Candy) {
86
+ const validator = Candy.Validator
87
+
88
+ validator.post('username').check('required|username|minlen:4|maxlen:20').message('Username must be 4-20 alphanumeric characters')
89
+ validator.post('email').check('required|email').message('Valid email address required')
90
+ validator.post('password').check('required|minlen:8').message('Password must be at least 8 characters')
91
+ validator.post('password_confirm').check('required|same:password').message('Passwords must match')
92
+
93
+ if (await validator.error()) {
94
+ return validator.result('Validation failed')
95
+ }
96
+
97
+ return validator.success('User registered successfully')
98
+ }
99
+ ```
100
+
101
+ #### Example: Login with Brute Force Protection
102
+
103
+ ```javascript
104
+ module.exports = async function (Candy) {
105
+ const validator = Candy.Validator
106
+
107
+ validator.post('email').check('required|email').message('Email required')
108
+ validator.post('password').check('required').message('Password required')
109
+ validator.brute(5)
110
+
111
+ if (await validator.error()) {
112
+ return validator.result('Login failed')
113
+ }
114
+
115
+ return validator.success({token: 'abc123'})
116
+ }
117
+ ```
118
+
119
+ #### Example: Custom Variable Validation
120
+
121
+ ```javascript
122
+ module.exports = async function (Candy) {
123
+ const validator = Candy.Validator
124
+ const customValue = calculateSomething()
125
+
126
+ validator.var('calculated_value', customValue).check('numeric|min:100|max:1000').message('Value must be between 100 and 1000')
127
+
128
+ if (await validator.error()) {
129
+ return validator.result('Invalid calculation')
130
+ }
131
+
132
+ return validator.success('Calculation valid')
133
+ }
134
+ ```
135
+
136
+ #### Multiple Checks Per Field
137
+
138
+ You can chain multiple `check()` calls for the same field, each with its own specific error message. The validator will return the first error it encounters:
139
+
140
+ ```javascript
141
+ module.exports = async function (Candy) {
142
+ const validator = Candy.Validator
143
+
144
+ validator
145
+ .post('password')
146
+ .check('required').message('Password is required')
147
+ .check('minlen:8').message('Password must be at least 8 characters')
148
+ .check('maxlen:50').message('Password cannot exceed 50 characters')
149
+ .check('regex:[A-Z]').message('Password must contain at least one uppercase letter')
150
+ .check('regex:[0-9]').message('Password must contain at least one number')
151
+
152
+ if (await validator.error()) {
153
+ return validator.result('Validation failed')
154
+ }
155
+
156
+ return validator.success('Password is strong')
157
+ }
158
+ ```
159
+
160
+ #### Example: Complex Form Validation
161
+
162
+ ```javascript
163
+ module.exports = async function (Candy) {
164
+ const validator = Candy.Validator
165
+
166
+ validator
167
+ .post('username')
168
+ .check('required').message('Username is required')
169
+ .check('username').message('Username can only contain letters and numbers')
170
+ .check('minlen:3').message('Username must be at least 3 characters')
171
+ .check('maxlen:20').message('Username cannot exceed 20 characters')
172
+
173
+ validator
174
+ .post('email')
175
+ .check('required').message('Email is required')
176
+ .check('email').message('Please enter a valid email address')
177
+
178
+ validator
179
+ .post('age')
180
+ .check('required').message('Age is required')
181
+ .check('numeric').message('Age must be a number')
182
+ .check('min:18').message('You must be at least 18 years old')
183
+ .check('max:120').message('Please enter a valid age')
184
+
185
+ validator
186
+ .post('website')
187
+ .check('!required').message('Website is optional')
188
+ .check('url').message('Please enter a valid URL')
189
+
190
+ validator
191
+ .post('bio')
192
+ .check('maxlen:500').message('Bio cannot exceed 500 characters')
193
+ .check('xss').message('Bio contains invalid HTML tags')
194
+
195
+ if (await validator.error()) {
196
+ return validator.result('Please fix the errors')
197
+ }
198
+
199
+ return validator.success('Profile updated successfully')
200
+ }
201
+ ```
202
+
203
+ #### Example: Date Range Validation
204
+
205
+ ```javascript
206
+ module.exports = async function (Candy) {
207
+ const validator = Candy.Validator
208
+
209
+ validator
210
+ .post('start_date')
211
+ .check('required').message('Start date is required')
212
+ .check('date').message('Invalid date format')
213
+ .check('mindate:2024-01-01').message('Start date must be after January 1, 2024')
214
+
215
+ validator
216
+ .post('end_date')
217
+ .check('required').message('End date is required')
218
+ .check('date').message('Invalid date format')
219
+ .check('maxdate:2025-12-31').message('End date must be before December 31, 2025')
220
+
221
+ if (await validator.error()) {
222
+ return validator.result('Invalid date range')
223
+ }
224
+
225
+ return validator.success('Date range is valid')
226
+ }
227
+ ```
228
+
229
+ #### Example: Conditional Validation with Custom Variables
230
+
231
+ ```javascript
232
+ module.exports = async function (Candy) {
233
+ const validator = Candy.Validator
234
+ const userRole = Candy.Auth.user('role')
235
+ const userCredits = Candy.Auth.user('credits') || 0
236
+
237
+ validator.post('title').check('required').message('Title is required')
238
+ validator.post('content').check('required').message('Content is required')
239
+
240
+ if (userRole !== 'premium') {
241
+ validator
242
+ .var('user_credits', userCredits)
243
+ .check('numeric').message('Invalid credits value')
244
+ .check('min:10').message('You need at least 10 credits to publish')
245
+
246
+ validator
247
+ .post('content')
248
+ .check('maxlen:1000').message('Free users are limited to 1000 characters')
249
+ }
250
+
251
+ validator
252
+ .var('role_check', userRole)
253
+ .check('!equal:banned').message('Your account has been banned')
254
+
255
+ if (await validator.error()) {
256
+ return validator.result('Validation failed')
257
+ }
258
+
259
+ return validator.success('Article published')
260
+ }
261
+ ```
262
+
263
+ #### Example: User Authentication Validation
264
+
265
+ ```javascript
266
+ module.exports = async function (Candy) {
267
+ const validator = Candy.Validator
268
+
269
+ validator
270
+ .post('current_password')
271
+ .check('required').message('Current password is required')
272
+ .check('user:password').message('Current password is incorrect')
273
+
274
+ validator
275
+ .post('new_password')
276
+ .check('required').message('New password is required')
277
+ .check('minlen:8').message('New password must be at least 8 characters')
278
+ .check('different:current_password').message('New password must be different from current')
279
+
280
+ if (await validator.error()) {
281
+ return validator.result('Password change failed')
282
+ }
283
+
284
+ return validator.success('Password changed successfully')
285
+ }
286
+ ```
287
+
288
+ #### Example: Boolean Function Results
289
+
290
+ You can use boolean values directly in `check()` for custom validation logic:
291
+
292
+ ```javascript
293
+ module.exports = async function (Candy) {
294
+ const validator = Candy.Validator
295
+ const userId = await Candy.request('user_id')
296
+
297
+ const isOwner = await checkIfUserOwnsResource(userId)
298
+ const hasPermission = await checkUserPermission('edit')
299
+ const isWithinLimit = await checkDailyLimit(userId)
300
+
301
+ validator.post('title').check('required').message('Title is required')
302
+
303
+ validator
304
+ .var('ownership', null)
305
+ .check(isOwner).message('You do not own this resource')
306
+
307
+ validator
308
+ .var('permission', null)
309
+ .check(hasPermission).message('You do not have permission to edit')
310
+
311
+ validator
312
+ .var('limit', null)
313
+ .check(isWithinLimit).message('You have reached your daily edit limit')
314
+
315
+ if (await validator.error()) {
316
+ return validator.result('Validation failed')
317
+ }
318
+
319
+ return validator.success('Resource updated')
320
+ }
321
+ ```
322
+
323
+ #### Example: Chained Validation (Single Statement)
324
+
325
+ ```javascript
326
+ module.exports = async function (Candy) {
327
+ return await Candy.Validator
328
+ .post('email').check('required').message('Email required').check('email').message('Invalid email')
329
+ .post('password').check('required').message('Password required').check('minlen:8').message('Min 8 chars')
330
+ .post('age').check('required').message('Age required').check('numeric').message('Must be number').check('min:18').message('Must be 18+')
331
+ .success('Registration successful')
332
+ }
333
+ ```
334
+
335
+ #### Example: Admin-Only Action with User Check
336
+
337
+ ```javascript
338
+ module.exports = async function (Candy) {
339
+ const validator = Candy.Validator
340
+
341
+ validator
342
+ .var('auth_check', null)
343
+ .check('usercheck').message('You must be logged in')
344
+
345
+ validator
346
+ .var('admin_role', null)
347
+ .check('user:role').message('Admin access required')
348
+ .check('equal:admin').message('Only admins can perform this action')
349
+
350
+ validator.post('action').check('required').message('Action is required')
351
+
352
+ if (await validator.error()) {
353
+ return validator.result('Access denied')
354
+ }
355
+
356
+ return validator.success('Action completed')
357
+ }
358
+ ```
359
+
360
+ #### Frontend Integration
361
+
362
+ When using `Candy.form()` on the frontend, validation errors are automatically displayed:
363
+
364
+ **Automatic Error Display:**
365
+ - Each field's error message appears below the input with attribute `[candy-form-error="fieldname"]`
366
+ - Invalid inputs get the `_candy_error` CSS class automatically
367
+ - Errors fade out when the user focuses on the input
368
+
369
+ **Success Messages:**
370
+ - Success messages appear in elements with `[candy-form-success]` attribute
371
+ - Automatically fades in when validation passes
372
+
373
+ **Auto-Redirect:**
374
+ - If you pass a URL as the second parameter to `Candy.form()`, successful submissions automatically redirect:
375
+ ```javascript
376
+ Candy.form('myForm', '/dashboard') // Redirects to /dashboard on success
377
+ ```
378
+
379
+ **Example HTML:**
380
+ ```html
381
+ <form candy-form="register" action="/api/register" method="POST">
382
+ <input type="email" name="email" placeholder="Email">
383
+ <span candy-form-error="email"></span>
384
+
385
+ <input type="password" name="password" placeholder="Password">
386
+ <span candy-form-error="password"></span>
387
+
388
+ <button type="submit">Register</button>
389
+
390
+ <span candy-form-success></span>
391
+ </form>
392
+
393
+ <script>
394
+ Candy.form('register', '/dashboard') // Auto-redirect on success
395
+ </script>
396
+ ```
397
+
398
+ #### Response Format
399
+
400
+ The `result()` method returns a standardized response:
401
+
402
+ **Success:**
403
+ ```json
404
+ {
405
+ "result": {
406
+ "success": true,
407
+ "message": "Operation successful"
408
+ },
409
+ "data": null
410
+ }
411
+ ```
412
+
413
+ **Error:**
414
+ ```json
415
+ {
416
+ "result": {
417
+ "success": false
418
+ },
419
+ "errors": {
420
+ "email": "Valid email address required",
421
+ "password": "Password must be at least 8 characters"
422
+ }
423
+ }
424
+ ```
@@ -0,0 +1,53 @@
1
+ ## 🔐 User Logins with `Auth.js`
2
+
3
+ The `Candy.Auth` service is your bouncer, managing who gets in and who stays out. It handles user login sessions for you.
4
+
5
+ #### Letting a User In
6
+
7
+ `Candy.Auth.login(userId, userData)`
8
+
9
+ * `userId`: A unique ID for the user (like their database ID).
10
+ * `userData`: An object with any user info you want to remember, like their username or role.
11
+
12
+ When you call this, `Auth` creates a secure session for the user.
13
+
14
+ #### Checking the Guest List
15
+
16
+ * `Candy.Auth.isLogin()`: Is the current user logged in? Returns `true` or `false`.
17
+ * `Candy.Auth.getId()`: Gets the ID of the logged-in user.
18
+ * `Candy.Auth.get('some-key')`: Grabs a specific piece of info from the `userData` you stored.
19
+
20
+ #### Showing a User Out
21
+
22
+ * `Candy.Auth.logout()`: Ends the user's session and logs them out.
23
+
24
+ #### Example: A Login Flow
25
+ ```javascript
26
+ // Controller for your login form
27
+ module.exports = async function (Candy) {
28
+ const { username, password } = Candy.Request.post;
29
+
30
+ // IMPORTANT: You need to write your own code to find the user in your database!
31
+ const user = await yourDatabase.findUser(username, password);
32
+
33
+ if (user) {
34
+ // User is valid! Log them in.
35
+ Candy.Auth.login(user.id, { username: user.username });
36
+ return Candy.direct('/dashboard'); // Send them to their dashboard
37
+ } else {
38
+ // Bad credentials, send them back to the login page
39
+ return Candy.direct('/login?error=1');
40
+ }
41
+ }
42
+
43
+ // A protected dashboard page
44
+ module.exports = function (Candy) {
45
+ // If they're not logged in, kick them back to the login page.
46
+ if (!Candy.Auth.isLogin()) {
47
+ return Candy.direct('/login');
48
+ }
49
+
50
+ const username = Candy.Auth.get('username');
51
+ return `Welcome back, ${username}!`;
52
+ }
53
+ ```
@@ -0,0 +1,55 @@
1
+ ## 🛡️ Foiling Villains with CSRF Protection
2
+
3
+ Cross-Site Request Forgery (CSRF) is a scary-sounding attack where a bad guy tries to trick your users into submitting forms they didn't mean to. The `Candy.Token` service is your shield against this!
4
+
5
+ #### How it Works
6
+
7
+ The idea is simple:
8
+ 1. When you show a form, you generate a secret, one-time-use token.
9
+ 2. You put this token in a hidden field in the form.
10
+ 3. When the user submits the form, you check if the token they sent back matches the one you generated.
11
+
12
+ If they don't match, it's a trap!
13
+
14
+ #### Generating and Checking Tokens
15
+
16
+ * `Candy.Token.get()`: Creates a new secret token.
17
+ * `Candy.Token.check(theToken)`: Checks if `theToken` is valid.
18
+
19
+ #### Example: Securing a Form
20
+
21
+ **1. Add the token to your form view:**
22
+ ```html
23
+ <form action="/some-action" method="post">
24
+ <!-- Add the secret token here! -->
25
+ <input type="hidden" name="csrf_token" value="{{ csrfToken }}">
26
+
27
+ <!-- ... your other form fields ... -->
28
+ <button type="submit">Submit</button>
29
+ </form>
30
+ ```
31
+
32
+ **2. Your controller that shows the form:**
33
+ ```javascript
34
+ module.exports = function (Candy) {
35
+ // Get a token and pass it to the view
36
+ const token = Candy.Token.get();
37
+ return Candy.View.render('your_form_view', { csrfToken: token });
38
+ }
39
+ ```
40
+
41
+ **3. Your controller that handles the form submission:**
42
+ ```javascript
43
+ module.exports = function (Candy) {
44
+ const submittedToken = Candy.Request.post.csrf_token;
45
+
46
+ // Check the token!
47
+ if (!Candy.Token.check(submittedToken)) {
48
+ // If it's bad, stop right here.
49
+ return Candy.return('Invalid CSRF Token!').status(403);
50
+ }
51
+
52
+ // If we get here, the token was good!
53
+ // ...you can now safely process the form...
54
+ }
55
+ ```
@@ -0,0 +1,134 @@
1
+ # User Registration
2
+
3
+ The `Candy.Auth.register()` method provides a secure and user-friendly way to create new user accounts with automatic password hashing, duplicate checking, and optional auto-login.
4
+
5
+ ## Basic Usage
6
+
7
+ ```javascript
8
+ module.exports = async function (Candy) {
9
+ const result = await Candy.Auth.register({
10
+ email: 'user@example.com',
11
+ username: 'johndoe',
12
+ password: 'securePassword123',
13
+ name: 'John Doe'
14
+ })
15
+
16
+ if (result.success) {
17
+ return {message: 'Registration successful', user: result.user}
18
+ } else {
19
+ return {error: result.error}
20
+ }
21
+ }
22
+ ```
23
+
24
+ ## Advanced Options
25
+
26
+ ```javascript
27
+ const result = await Candy.Auth.register(
28
+ {
29
+ email: 'user@example.com',
30
+ username: 'johndoe',
31
+ password: 'securePassword123',
32
+ name: 'John Doe',
33
+ role: 'user'
34
+ },
35
+ {
36
+ passwordField: 'password', // Field name for password (default: 'password')
37
+ uniqueFields: ['email', 'username'], // Fields to check for duplicates (default: ['email'])
38
+ autoLogin: true // Auto-login after registration (default: true)
39
+ }
40
+ )
41
+ ```
42
+
43
+ ## Response Format
44
+
45
+ ### Success Response
46
+
47
+ ```javascript
48
+ {
49
+ success: true,
50
+ user: {
51
+ id: 123,
52
+ email: 'user@example.com',
53
+ username: 'johndoe',
54
+ // ... other user fields
55
+ }
56
+ }
57
+ ```
58
+
59
+ ### Error Response
60
+
61
+ ```javascript
62
+ {
63
+ success: false,
64
+ error: 'email already exists',
65
+ field: 'email' // Only present for duplicate field errors
66
+ }
67
+ ```
68
+
69
+ ## Features
70
+
71
+ - **Automatic Password Hashing**: Passwords are automatically hashed using bcrypt
72
+ - **Duplicate Prevention**: Checks for existing users with the same email/username
73
+ - **Auto-Login**: Optionally logs in the user immediately after registration
74
+ - **Flexible Configuration**: Customize password field name and unique fields
75
+ - **Detailed Error Messages**: Returns specific error information for better UX
76
+
77
+ ## Example Controller
78
+
79
+ ```javascript
80
+ module.exports = async function (Candy) {
81
+ const validator = Candy.Validator
82
+
83
+ // Validate input
84
+ validator.post('email').check('required|email').message('A valid email is required')
85
+ validator.post('username').check('required|minlen:4').message('Username must be at least 4 characters')
86
+ validator.post('password').check('required|minlen:8').message('Password must be at least 8 characters')
87
+
88
+ if (await validator.error()) {
89
+ return validator.result('Please fix the errors below')
90
+ }
91
+
92
+ // Get validated data
93
+ const email = await Candy.request('email')
94
+ const username = await Candy.request('username')
95
+ const password = await Candy.request('password')
96
+ const name = await Candy.request('name')
97
+
98
+ // Register user
99
+ const result = await Candy.Auth.register(
100
+ {email, username, password, name},
101
+ {uniqueFields: ['email', 'username']}
102
+ )
103
+
104
+ if (result.success) {
105
+ // User is now registered and logged in
106
+ return Candy.direct('/dashboard')
107
+ } else {
108
+ // Show error message
109
+ return {error: result.error}
110
+ }
111
+ }
112
+ ```
113
+
114
+ ## Configuration
115
+
116
+ Make sure your `config.json` has the auth configuration:
117
+
118
+ ```json
119
+ {
120
+ "auth": {
121
+ "table": "users",
122
+ "key": "id",
123
+ "token": "user_tokens"
124
+ }
125
+ }
126
+ ```
127
+
128
+ ## Security Notes
129
+
130
+ - Passwords are automatically hashed with bcrypt before storage
131
+ - The system automatically detects already-hashed passwords (bcrypt pattern) to prevent double-hashing
132
+ - Never store plain text passwords
133
+ - Use HTTPS in production to protect credentials in transit
134
+ - Consider adding rate limiting to prevent brute force attacks