belt 0.3.14 → 0.3.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +36 -0
- data/lib/belt/cli/auth_command.rb +135 -9
- data/lib/belt/cli/destroy_command.rb +1 -2
- data/lib/belt/cli/frontend_registry.rb +1 -2
- data/lib/belt/cli/generate_command.rb +1 -2
- data/lib/belt/cli/new_command.rb +1 -2
- data/lib/belt/version.rb +1 -1
- data/lib/templates/generate/auth/cognito.tf.erb +11 -0
- data/lib/templates/generate/auth/frontend/auth.js +22 -9
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 905825783d5bdb3201165a5d28cbc3b01b5e64d1169bce94958840332bff5845
|
|
4
|
+
data.tar.gz: 17eea521ef730f560451370e2b4e556448a697919649879203d3f05e1979f202
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f4e65fe4b40d4f075783b8abbc7793293b618c8638d94e5aff1adc3e23dc6e4e1fadfc8841213e89bb7db1e14c86a9767f99ac0ebaf2c3580a4007b653922dce
|
|
7
|
+
data.tar.gz: e73a504ff45c5d8946ddaa606ffc20f1584b11a287d1564dcd90c59e2ade80af446694b6d2e616d867fe5bd28dd712e33e45a3eaa2a4c523baee53f54a3b22a1
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.16
|
|
4
|
+
|
|
5
|
+
### Enhancement
|
|
6
|
+
|
|
7
|
+
- **`belt generate auth --ses-email` for custom email domains**: By default,
|
|
8
|
+
Cognito sends verification emails from `no-reply@verificationemail.com`.
|
|
9
|
+
The new `--ses-email` flag configures Cognito to use Amazon SES instead,
|
|
10
|
+
allowing you to send from your own domain (e.g., `noreply@yourapp.com`).
|
|
11
|
+
|
|
12
|
+
The generator creates:
|
|
13
|
+
- `cognito_variables.tf` with variables for SES email ARN, from address, and
|
|
14
|
+
optional reply-to address
|
|
15
|
+
- `email_configuration` block in `cognito.tf` with `DEVELOPER` sending mode
|
|
16
|
+
- Example variable values in environment tfvars files
|
|
17
|
+
|
|
18
|
+
Prerequisites:
|
|
19
|
+
1. Verify your domain or email address in Amazon SES
|
|
20
|
+
2. Move out of SES sandbox for production use
|
|
21
|
+
|
|
22
|
+
Usage: `belt g auth --ses-email`
|
|
23
|
+
|
|
24
|
+
## 0.3.15
|
|
25
|
+
|
|
26
|
+
### Bug Fix
|
|
27
|
+
|
|
28
|
+
- **`belt generate auth` NEW_PASSWORD_REQUIRED challenge sent `email` even when
|
|
29
|
+
the user already had it**: The 0.3.14 fix always passed `{ email }` to
|
|
30
|
+
`completeNewPasswordChallenge`. That fixed pools where `email` was missing, but
|
|
31
|
+
broke the common case where the user already has `email` set (e.g. created via
|
|
32
|
+
`admin-create-user` with an email) — Cognito rejects that with a 400
|
|
33
|
+
`NotAuthorizedException: Cannot modify an already provided email`. The template
|
|
34
|
+
now reads the `requiredAttributes` list Cognito provides in the
|
|
35
|
+
`newPasswordRequired` callback and supplies `email` **only when Cognito actually
|
|
36
|
+
requires it**. Users with email already set complete the challenge without
|
|
37
|
+
re-sending it; users missing email still get it supplied. Handles both cases.
|
|
38
|
+
|
|
3
39
|
## 0.3.14
|
|
4
40
|
|
|
5
41
|
### Bug Fix
|
|
@@ -21,10 +21,11 @@ module Belt
|
|
|
21
21
|
|
|
22
22
|
force = args.delete('--force') || args.delete('-f')
|
|
23
23
|
signup = args.delete('--signup')
|
|
24
|
+
ses_email = args.delete('--ses-email')
|
|
24
25
|
frontend_name = FrontendRegistry.extract_flag!(args, '--frontend')
|
|
25
26
|
pools = parse_pools(args)
|
|
26
27
|
|
|
27
|
-
new(pools: pools, force: force, signup: signup, frontend_name: frontend_name).generate
|
|
28
|
+
new(pools: pools, force: force, signup: signup, ses_email: ses_email, frontend_name: frontend_name).generate
|
|
28
29
|
end
|
|
29
30
|
|
|
30
31
|
def self.destroy(_args)
|
|
@@ -39,6 +40,7 @@ module Belt
|
|
|
39
40
|
|
|
40
41
|
Options:
|
|
41
42
|
--signup Allow public user registration (generates frontend views)
|
|
43
|
+
--ses-email Use Amazon SES for emails (custom from address/domain)
|
|
42
44
|
--frontend NAME Target frontend when several exist
|
|
43
45
|
--force, -f Overwrite existing cognito.tf (skip collision check)
|
|
44
46
|
|
|
@@ -49,6 +51,7 @@ module Belt
|
|
|
49
51
|
Examples:
|
|
50
52
|
belt g auth # Admin-only, single pool
|
|
51
53
|
belt g auth --signup # Public signup with frontend views
|
|
54
|
+
belt g auth --ses-email # Custom email domain via SES
|
|
52
55
|
belt g auth web # Named pool: "web"
|
|
53
56
|
belt g auth web mobile # Two pools
|
|
54
57
|
belt g auth --force # Overwrite existing
|
|
@@ -71,6 +74,19 @@ module Belt
|
|
|
71
74
|
frontend/src/pages/auth/Login.jsx Login page
|
|
72
75
|
frontend/src/components/ProtectedRoute.jsx Route guard component
|
|
73
76
|
|
|
77
|
+
With --ses-email:
|
|
78
|
+
Uses Amazon SES instead of Cognito's default email service. Allows sending
|
|
79
|
+
from your custom domain (e.g., noreply@yourapp.com) instead of verificationemail.com.
|
|
80
|
+
|
|
81
|
+
Prerequisites:
|
|
82
|
+
1. Verify your domain or email address in Amazon SES
|
|
83
|
+
2. Request production access (move out of SES sandbox) for production use
|
|
84
|
+
|
|
85
|
+
After generation, set these variables in your tfvars:
|
|
86
|
+
cognito_ses_email_arn = "arn:aws:ses:us-east-1:123456789:identity/yourapp.com"
|
|
87
|
+
cognito_from_email = "noreply@yourapp.com"
|
|
88
|
+
cognito_reply_to_email = "support@yourapp.com" # optional
|
|
89
|
+
|
|
74
90
|
It also patches:
|
|
75
91
|
infrastructure/modules/app/main.tf Adds cognito_user_pool_arns to conveyor_belt
|
|
76
92
|
|
|
@@ -89,10 +105,12 @@ module Belt
|
|
|
89
105
|
names.map(&:downcase).map { |n| n.gsub(/[^a-z0-9_]/, '_') }
|
|
90
106
|
end
|
|
91
107
|
|
|
92
|
-
def initialize(pools:, force: false, signup: false,
|
|
108
|
+
def initialize(pools:, force: false, signup: false, ses_email: false, frontend_name: nil,
|
|
109
|
+
skip_frontend_resolve: false)
|
|
93
110
|
@pool_names = pools
|
|
94
111
|
@force = force
|
|
95
112
|
@signup = signup
|
|
113
|
+
@ses_email = ses_email
|
|
96
114
|
@app_name = detect_app_name
|
|
97
115
|
@pools = build_pool_metadata
|
|
98
116
|
@frontend = skip_frontend_resolve ? nil : resolve_frontend(frontend_name)
|
|
@@ -104,6 +122,7 @@ module Belt
|
|
|
104
122
|
|
|
105
123
|
write_cognito_tf
|
|
106
124
|
write_cognito_outputs_tf
|
|
125
|
+
write_cognito_variables_tf if @ses_email
|
|
107
126
|
patch_main_tf
|
|
108
127
|
patch_env_outputs
|
|
109
128
|
generate_frontend_auth if frontend?
|
|
@@ -114,14 +133,29 @@ module Belt
|
|
|
114
133
|
|
|
115
134
|
def print_next_steps
|
|
116
135
|
puts "\nNext steps:"
|
|
117
|
-
|
|
136
|
+
step = 1
|
|
137
|
+
|
|
138
|
+
if @ses_email
|
|
139
|
+
puts " #{step}. Configure SES email variables in your tfvars files:"
|
|
140
|
+
puts ''
|
|
141
|
+
puts ' # Verify your domain in SES first, then add:'
|
|
142
|
+
puts ' cognito_ses_email_arn = "arn:aws:ses:REGION:ACCOUNT:identity/yourdomain.com"'
|
|
143
|
+
puts ' cognito_from_email = "noreply@yourdomain.com"'
|
|
144
|
+
puts ' cognito_reply_to_email = "support@yourdomain.com" # optional'
|
|
145
|
+
puts ''
|
|
146
|
+
step += 1
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
puts " #{step}. Add auth: :cognito to your routes namespace:"
|
|
118
150
|
puts ''
|
|
119
151
|
puts ' namespace :api, auth: :cognito do'
|
|
120
152
|
puts ' # your resources...'
|
|
121
153
|
puts ' end'
|
|
122
154
|
puts ''
|
|
155
|
+
step += 1
|
|
156
|
+
|
|
123
157
|
if frontend?
|
|
124
|
-
puts "
|
|
158
|
+
puts " #{step}. Wire auth into your #{@frontend.src_dir}/App.jsx:"
|
|
125
159
|
puts ''
|
|
126
160
|
puts " import Login from './pages/auth/Login'"
|
|
127
161
|
puts " import ProtectedRoute from './components/ProtectedRoute'"
|
|
@@ -132,12 +166,11 @@ module Belt
|
|
|
132
166
|
puts ' // Wrap protected routes:'
|
|
133
167
|
puts ' <Route path="/*" element={<ProtectedRoute><YourApp /></ProtectedRoute>} />'
|
|
134
168
|
puts ''
|
|
135
|
-
|
|
136
|
-
print_create_user_step(4) unless @signup
|
|
137
|
-
else
|
|
138
|
-
puts ' 2. Deploy: belt deploy'
|
|
139
|
-
print_create_user_step(3) unless @signup
|
|
169
|
+
step += 1
|
|
140
170
|
end
|
|
171
|
+
puts " #{step}. Deploy: belt deploy"
|
|
172
|
+
step += 1
|
|
173
|
+
print_create_user_step(step) unless @signup
|
|
141
174
|
end
|
|
142
175
|
|
|
143
176
|
def print_create_user_step(step_num)
|
|
@@ -154,6 +187,7 @@ module Belt
|
|
|
154
187
|
|
|
155
188
|
cognito_tf = File.join(MODULE_DIR, 'cognito.tf')
|
|
156
189
|
cognito_outputs_tf = File.join(MODULE_DIR, 'cognito_outputs.tf')
|
|
190
|
+
cognito_variables_tf = File.join(MODULE_DIR, 'cognito_variables.tf')
|
|
157
191
|
|
|
158
192
|
if File.exist?(cognito_tf)
|
|
159
193
|
FileUtils.rm(cognito_tf)
|
|
@@ -167,6 +201,12 @@ module Belt
|
|
|
167
201
|
puts " remove #{cognito_outputs_tf}"
|
|
168
202
|
end
|
|
169
203
|
|
|
204
|
+
if File.exist?(cognito_variables_tf)
|
|
205
|
+
FileUtils.rm(cognito_variables_tf)
|
|
206
|
+
removed << cognito_variables_tf
|
|
207
|
+
puts " remove #{cognito_variables_tf}"
|
|
208
|
+
end
|
|
209
|
+
|
|
170
210
|
unpatch_main_tf
|
|
171
211
|
removed << File.join(MODULE_DIR, 'main.tf') if @main_tf_patched
|
|
172
212
|
|
|
@@ -275,6 +315,92 @@ module Belt
|
|
|
275
315
|
puts " create #{dest}"
|
|
276
316
|
end
|
|
277
317
|
|
|
318
|
+
def write_cognito_variables_tf
|
|
319
|
+
dest = File.join(MODULE_DIR, 'cognito_variables.tf')
|
|
320
|
+
content = <<~HCL
|
|
321
|
+
# Cognito SES email configuration variables
|
|
322
|
+
# Generated by: belt generate auth --ses-email
|
|
323
|
+
#
|
|
324
|
+
# Set these in your environment tfvars (e.g., infrastructure/dev/terraform.tfvars):
|
|
325
|
+
# cognito_ses_email_arn = "arn:aws:ses:us-east-1:123456789:identity/yourapp.com"
|
|
326
|
+
# cognito_from_email = "noreply@yourapp.com"
|
|
327
|
+
# cognito_reply_to_email = "support@yourapp.com" # optional
|
|
328
|
+
|
|
329
|
+
variable "cognito_ses_email_arn" {
|
|
330
|
+
description = "ARN of verified SES email address or domain for Cognito emails"
|
|
331
|
+
type = string
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
variable "cognito_from_email" {
|
|
335
|
+
description = "From email address for Cognito emails (e.g., noreply@yourapp.com)"
|
|
336
|
+
type = string
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
variable "cognito_reply_to_email" {
|
|
340
|
+
description = "Reply-to email address for Cognito emails (optional)"
|
|
341
|
+
type = string
|
|
342
|
+
default = ""
|
|
343
|
+
}
|
|
344
|
+
HCL
|
|
345
|
+
|
|
346
|
+
File.write(dest, content)
|
|
347
|
+
puts " create #{dest}"
|
|
348
|
+
|
|
349
|
+
# Also patch the environment tfvars examples
|
|
350
|
+
patch_env_vars_for_ses
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def patch_env_vars_for_ses
|
|
354
|
+
# Find environment directories (e.g., infrastructure/dev/, infrastructure/prod/)
|
|
355
|
+
env_dirs = Dir.glob('infrastructure/*/terraform.tfvars')
|
|
356
|
+
.reject { |f| f.include?('modules') }
|
|
357
|
+
|
|
358
|
+
env_dirs.each do |tfvars_file|
|
|
359
|
+
content = File.read(tfvars_file)
|
|
360
|
+
next if content.include?('cognito_ses_email_arn')
|
|
361
|
+
|
|
362
|
+
domain = detect_domain_from_tfvars(tfvars_file)
|
|
363
|
+
ses_vars = build_ses_vars_block(domain)
|
|
364
|
+
|
|
365
|
+
File.write(tfvars_file, content + ses_vars)
|
|
366
|
+
env_name = File.basename(File.dirname(tfvars_file))
|
|
367
|
+
puts " update #{tfvars_file} (added SES email variables for #{env_name})"
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
def detect_domain_from_tfvars(tfvars_file)
|
|
372
|
+
return nil unless File.exist?(tfvars_file)
|
|
373
|
+
|
|
374
|
+
match = File.read(tfvars_file).match(/^\s*domain\s*=\s*"([^"]+)"/)
|
|
375
|
+
match[1] if match
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def build_ses_vars_block(domain)
|
|
379
|
+
if domain
|
|
380
|
+
# User has a domain configured — provide smart defaults
|
|
381
|
+
<<~HCL
|
|
382
|
+
|
|
383
|
+
# Cognito SES email configuration (generated by: belt g auth --ses-email)
|
|
384
|
+
# Sends emails from your domain instead of verificationemail.com
|
|
385
|
+
# Prerequisites: 1) Verify domain in SES 2) Request SES production access for prod
|
|
386
|
+
cognito_ses_email_arn = "arn:aws:ses:REGION:ACCOUNT:identity/#{domain}"
|
|
387
|
+
cognito_from_email = "noreply@#{domain}"
|
|
388
|
+
# cognito_reply_to_email = "support@#{domain}"
|
|
389
|
+
HCL
|
|
390
|
+
else
|
|
391
|
+
# No domain configured — provide placeholder examples
|
|
392
|
+
<<~HCL
|
|
393
|
+
|
|
394
|
+
# Cognito SES email configuration (generated by: belt g auth --ses-email)
|
|
395
|
+
# Uncomment and configure to send emails from your domain instead of verificationemail.com
|
|
396
|
+
# Prerequisites: 1) Verify domain in SES 2) Request SES production access for prod
|
|
397
|
+
# cognito_ses_email_arn = "arn:aws:ses:REGION:ACCOUNT:identity/yourdomain.com"
|
|
398
|
+
# cognito_from_email = "noreply@yourdomain.com"
|
|
399
|
+
# cognito_reply_to_email = "support@yourdomain.com"
|
|
400
|
+
HCL
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
|
|
278
404
|
def patch_main_tf
|
|
279
405
|
main_tf = File.join(MODULE_DIR, 'main.tf')
|
|
280
406
|
return unless File.exist?(main_tf)
|
|
@@ -135,7 +135,7 @@ module Belt
|
|
|
135
135
|
HELP
|
|
136
136
|
end
|
|
137
137
|
|
|
138
|
-
#
|
|
138
|
+
# -- keyword options for destroy flags
|
|
139
139
|
def initialize(generator, name, fields, force: false, skip_terraform: false, frontend_name: nil)
|
|
140
140
|
@generator = generator
|
|
141
141
|
@name = name&.downcase&.gsub(/[^a-z0-9_]/, '_')
|
|
@@ -150,7 +150,6 @@ module Belt
|
|
|
150
150
|
@removed = []
|
|
151
151
|
@updated = []
|
|
152
152
|
end
|
|
153
|
-
# rubocop:enable Metrics/ParameterLists
|
|
154
153
|
|
|
155
154
|
def destroy
|
|
156
155
|
case @generator
|
|
@@ -15,7 +15,7 @@ module Belt
|
|
|
15
15
|
:url_output, :cloudfront_domain_output
|
|
16
16
|
attr_accessor :default, :path
|
|
17
17
|
|
|
18
|
-
#
|
|
18
|
+
# -- keyword options from YAML config
|
|
19
19
|
def initialize(name:, path:, dist: nil, bucket_output: nil, distribution_output: nil,
|
|
20
20
|
url_output: nil, cloudfront_domain_output: nil, default: false)
|
|
21
21
|
@name = name.to_s
|
|
@@ -28,7 +28,6 @@ module Belt
|
|
|
28
28
|
@url_output = url_output || default_output('url')
|
|
29
29
|
@cloudfront_domain_output = cloudfront_domain_output
|
|
30
30
|
end
|
|
31
|
-
# rubocop:enable Metrics/ParameterLists
|
|
32
31
|
|
|
33
32
|
def default?
|
|
34
33
|
@default
|
|
@@ -278,7 +278,7 @@ module Belt
|
|
|
278
278
|
puts "\n#{info[:notes]}" if info[:notes]
|
|
279
279
|
end
|
|
280
280
|
|
|
281
|
-
#
|
|
281
|
+
# -- keyword options for generator flags
|
|
282
282
|
def initialize(generator, name, fields, skip_views: false, force: false, frontend_name: nil)
|
|
283
283
|
@generator = generator
|
|
284
284
|
@name = name.downcase.gsub(/[^a-z0-9_]/, '_')
|
|
@@ -293,7 +293,6 @@ module Belt
|
|
|
293
293
|
@class_name = Belt::Inflector.classify(@singular_name)
|
|
294
294
|
@references, @regular_fields = @fields.partition { |f| f[:type] == 'references' }
|
|
295
295
|
end
|
|
296
|
-
# rubocop:enable Metrics/ParameterLists
|
|
297
296
|
|
|
298
297
|
def generate
|
|
299
298
|
case @generator
|
data/lib/belt/cli/new_command.rb
CHANGED
|
@@ -82,7 +82,7 @@ module Belt
|
|
|
82
82
|
).generate
|
|
83
83
|
end
|
|
84
84
|
|
|
85
|
-
#
|
|
85
|
+
# -- keyword options for generator flags
|
|
86
86
|
def initialize(app_name, frontend: nil, bucket: nil, environments: nil, domain: nil, verbose: false)
|
|
87
87
|
@app_name = app_name.gsub(/[^a-z0-9_-]/i, '_').downcase
|
|
88
88
|
@module_name = @app_name.split(/[-_]/).map(&:capitalize).join
|
|
@@ -94,7 +94,6 @@ module Belt
|
|
|
94
94
|
@resolved_bucket = nil
|
|
95
95
|
@state_setup_succeeded = false
|
|
96
96
|
end
|
|
97
|
-
# rubocop:enable Metrics/ParameterLists
|
|
98
97
|
|
|
99
98
|
def generate
|
|
100
99
|
if Dir.exist?(@app_name)
|
data/lib/belt/version.rb
CHANGED
|
@@ -30,6 +30,17 @@ resource "aws_cognito_user_pool" "<%= pool[:name] %>" {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
<% if @ses_email -%>
|
|
34
|
+
# SES email configuration — sends from your verified domain
|
|
35
|
+
# Requires: verified domain or email in SES, out of sandbox for prod
|
|
36
|
+
email_configuration {
|
|
37
|
+
email_sending_account = "DEVELOPER"
|
|
38
|
+
source_arn = var.cognito_ses_email_arn
|
|
39
|
+
from_email_address = var.cognito_from_email
|
|
40
|
+
reply_to_email_address = var.cognito_reply_to_email != "" ? var.cognito_reply_to_email : null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
<% end -%>
|
|
33
44
|
schema {
|
|
34
45
|
name = "email"
|
|
35
46
|
attribute_data_type = "String"
|
|
@@ -37,41 +37,54 @@ export function signIn(username, password) {
|
|
|
37
37
|
resolve({ success: true })
|
|
38
38
|
},
|
|
39
39
|
onFailure: (err) => reject(err),
|
|
40
|
-
newPasswordRequired: () => {
|
|
41
|
-
// Stash the user
|
|
40
|
+
newPasswordRequired: (_userAttributes, requiredAttributes) => {
|
|
41
|
+
// Stash the user + the attributes Cognito actually requires so
|
|
42
|
+
// completeNewPassword can continue the SRP session and supply exactly
|
|
43
|
+
// what the pool asks for — no more, no less.
|
|
42
44
|
pendingUser = cognitoUser
|
|
45
|
+
pendingRequiredAttributes = requiredAttributes || []
|
|
43
46
|
resolve({ challenge: 'NEW_PASSWORD_REQUIRED' })
|
|
44
47
|
}
|
|
45
48
|
})
|
|
46
49
|
})
|
|
47
50
|
}
|
|
48
51
|
|
|
49
|
-
// Holds the CognitoUser mid-challenge for the NEW_PASSWORD_REQUIRED flow
|
|
52
|
+
// Holds the CognitoUser mid-challenge for the NEW_PASSWORD_REQUIRED flow,
|
|
53
|
+
// plus the list of attributes Cognito flagged as required for the challenge.
|
|
50
54
|
let pendingUser = null
|
|
55
|
+
let pendingRequiredAttributes = []
|
|
51
56
|
|
|
52
57
|
export function completeNewPassword(email, newPassword) {
|
|
53
58
|
if (!pendingUser) {
|
|
54
59
|
return Promise.reject(new Error('No pending password challenge. Sign in again.'))
|
|
55
60
|
}
|
|
56
61
|
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
+
// Only supply attributes Cognito actually asks for in this challenge.
|
|
63
|
+
// - If the pool requires `email` and the user doesn't have it yet, it shows
|
|
64
|
+
// up in requiredAttributes → we send it (username is the email). Skipping
|
|
65
|
+
// it would fail with "Invalid attributes given, email is missing".
|
|
66
|
+
// - If the user already has `email` (e.g. admin-created with it), it is NOT
|
|
67
|
+
// in requiredAttributes → we must NOT send it, or Cognito rejects with
|
|
68
|
+
// "Cannot modify an already provided email".
|
|
69
|
+
const attributes = {}
|
|
70
|
+
if (pendingRequiredAttributes.includes('email')) {
|
|
71
|
+
attributes.email = email
|
|
72
|
+
}
|
|
62
73
|
|
|
63
74
|
return new Promise((resolve, reject) => {
|
|
64
|
-
pendingUser.completeNewPasswordChallenge(newPassword,
|
|
75
|
+
pendingUser.completeNewPasswordChallenge(newPassword, attributes, {
|
|
65
76
|
onSuccess: (session) => {
|
|
66
77
|
setTokens({
|
|
67
78
|
IdToken: session.getIdToken().getJwtToken(),
|
|
68
79
|
RefreshToken: session.getRefreshToken().getToken()
|
|
69
80
|
})
|
|
70
81
|
pendingUser = null
|
|
82
|
+
pendingRequiredAttributes = []
|
|
71
83
|
resolve({ success: true })
|
|
72
84
|
},
|
|
73
85
|
onFailure: (err) => {
|
|
74
86
|
pendingUser = null
|
|
87
|
+
pendingRequiredAttributes = []
|
|
75
88
|
reject(err)
|
|
76
89
|
}
|
|
77
90
|
})
|