belt 0.2.18 → 0.2.19
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/lib/belt/cli/auth_command.rb +76 -11
- data/lib/belt/route_dsl.rb +1 -1
- data/lib/belt/version.rb +1 -1
- data/lib/templates/generate/auth/cognito.tf.erb +11 -0
- data/lib/templates/generate/auth/frontend/ConfirmEmail.jsx +55 -0
- data/lib/templates/generate/auth/frontend/Login.jsx +81 -0
- data/lib/templates/generate/auth/frontend/ProtectedRoute.jsx +9 -0
- data/lib/templates/generate/auth/frontend/SignUp.jsx +58 -0
- data/lib/templates/generate/auth/frontend/apiClient.js +34 -0
- data/lib/templates/generate/auth/frontend/auth.js +80 -0
- metadata +7 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7f706cafa79246d178c2aee6bfb05536bcb5f03111a24f13624e769ef7d9916a
|
|
4
|
+
data.tar.gz: a99d09c6d472d4826acf78dee6a4fded952c8745071b63c6ea4f54dbfd6b0aef
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2009deb03f2f725c4e960e73abf7745b8a9fdd5899ea76178b3942f21c504fe76338912ba57f05b913db3a18494f635c508e4696a3c347e40ddcb973e4c61541
|
|
7
|
+
data.tar.gz: b84417ce5814e266715b5dc54b4a965261f8011fee552f9f594c82137751565ff29b20f17444b2be986abfd79c02059b935ae5aa97fcbdbbfdcc79ffafa0421b
|
|
@@ -19,9 +19,10 @@ module Belt
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
force = args.delete('--force') || args.delete('-f')
|
|
22
|
+
signup = args.delete('--signup')
|
|
22
23
|
pools = parse_pools(args)
|
|
23
24
|
|
|
24
|
-
new(pools: pools, force: force).generate
|
|
25
|
+
new(pools: pools, force: force, signup: signup).generate
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
def self.destroy(_args)
|
|
@@ -35,6 +36,7 @@ module Belt
|
|
|
35
36
|
Usage: belt generate auth [pool_names...] [options]
|
|
36
37
|
|
|
37
38
|
Options:
|
|
39
|
+
--signup Allow public user registration (generates frontend views)
|
|
38
40
|
--force, -f Overwrite existing cognito.tf (skip collision check)
|
|
39
41
|
|
|
40
42
|
Arguments:
|
|
@@ -42,26 +44,38 @@ module Belt
|
|
|
42
44
|
If omitted, generates a single pool named "main".
|
|
43
45
|
|
|
44
46
|
Examples:
|
|
45
|
-
belt g auth #
|
|
46
|
-
belt g auth
|
|
47
|
-
belt g auth web
|
|
48
|
-
belt g auth web
|
|
47
|
+
belt g auth # Admin-only, single pool
|
|
48
|
+
belt g auth --signup # Public signup with frontend views
|
|
49
|
+
belt g auth web # Named pool: "web"
|
|
50
|
+
belt g auth web mobile # Two pools
|
|
49
51
|
belt g auth --force # Overwrite existing
|
|
50
52
|
|
|
51
53
|
What this generates:
|
|
52
54
|
infrastructure/modules/app/cognito.tf User pool + client resources
|
|
53
55
|
infrastructure/modules/app/cognito_outputs.tf Pool ID, ARN, and client ID outputs
|
|
54
56
|
|
|
57
|
+
With --signup (when frontend/ exists):
|
|
58
|
+
frontend/src/lib/auth.js Auth module (signIn, signUp, etc.)
|
|
59
|
+
frontend/src/lib/apiClient.js API client with Authorization header
|
|
60
|
+
frontend/src/pages/auth/Login.jsx Login page
|
|
61
|
+
frontend/src/pages/auth/SignUp.jsx Registration page
|
|
62
|
+
frontend/src/pages/auth/ConfirmEmail.jsx Email verification page
|
|
63
|
+
frontend/src/components/ProtectedRoute.jsx Route guard component
|
|
64
|
+
|
|
65
|
+
Without --signup (admin-only, default):
|
|
66
|
+
frontend/src/lib/auth.js Auth module (signIn only)
|
|
67
|
+
frontend/src/lib/apiClient.js API client with Authorization header
|
|
68
|
+
frontend/src/pages/auth/Login.jsx Login page
|
|
69
|
+
frontend/src/components/ProtectedRoute.jsx Route guard component
|
|
70
|
+
|
|
55
71
|
It also patches:
|
|
56
72
|
infrastructure/modules/app/main.tf Adds cognito_user_pool_arns to conveyor_belt
|
|
57
73
|
|
|
58
74
|
After running:
|
|
59
75
|
1. Review the generated Cognito config in cognito.tf
|
|
60
|
-
2.
|
|
61
|
-
3. Run `belt
|
|
62
|
-
|
|
63
|
-
To remove:
|
|
64
|
-
belt destroy auth
|
|
76
|
+
2. Add auth: :cognito to your routes namespace
|
|
77
|
+
3. Run `belt deploy` to create the user pool
|
|
78
|
+
4. Create your account (admin-only): aws cognito-idp admin-create-user ...
|
|
65
79
|
HELP
|
|
66
80
|
end
|
|
67
81
|
|
|
@@ -72,9 +86,10 @@ module Belt
|
|
|
72
86
|
names.map(&:downcase).map { |n| n.gsub(/[^a-z0-9_]/, '_') }
|
|
73
87
|
end
|
|
74
88
|
|
|
75
|
-
def initialize(pools:, force: false)
|
|
89
|
+
def initialize(pools:, force: false, signup: false)
|
|
76
90
|
@pool_names = pools
|
|
77
91
|
@force = force
|
|
92
|
+
@signup = signup
|
|
78
93
|
@app_name = detect_app_name
|
|
79
94
|
@pools = build_pool_metadata
|
|
80
95
|
end
|
|
@@ -86,12 +101,14 @@ module Belt
|
|
|
86
101
|
write_cognito_tf
|
|
87
102
|
write_cognito_outputs_tf
|
|
88
103
|
patch_main_tf
|
|
104
|
+
generate_frontend_auth if frontend?
|
|
89
105
|
|
|
90
106
|
puts "\n✓ Auth generated!"
|
|
91
107
|
puts "\nNext steps:"
|
|
92
108
|
puts ' 1. Review infrastructure/modules/app/cognito.tf'
|
|
93
109
|
puts ' 2. Customize password policy, MFA, or Lambda triggers as needed'
|
|
94
110
|
puts ' 3. Run `belt apply <env>` to deploy'
|
|
111
|
+
puts ' 4. Create your account: aws cognito-idp admin-create-user ...' unless @signup
|
|
95
112
|
end
|
|
96
113
|
|
|
97
114
|
def remove
|
|
@@ -134,6 +151,54 @@ module Belt
|
|
|
134
151
|
end
|
|
135
152
|
end
|
|
136
153
|
|
|
154
|
+
def frontend?
|
|
155
|
+
Dir.exist?('frontend/src')
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def generate_frontend_auth
|
|
159
|
+
frontend_template_dir = File.join(TEMPLATE_DIR, 'frontend')
|
|
160
|
+
|
|
161
|
+
# Generate auth lib files
|
|
162
|
+
lib_dir = 'frontend/src/lib'
|
|
163
|
+
FileUtils.mkdir_p(lib_dir)
|
|
164
|
+
copy_frontend_file(frontend_template_dir, 'auth.js', File.join(lib_dir, 'auth.js'))
|
|
165
|
+
copy_frontend_file(frontend_template_dir, 'apiClient.js', File.join(lib_dir, 'apiClient.js'))
|
|
166
|
+
|
|
167
|
+
pages_dir = 'frontend/src/pages/auth'
|
|
168
|
+
FileUtils.mkdir_p(pages_dir)
|
|
169
|
+
copy_frontend_file(frontend_template_dir, 'Login.jsx', File.join(pages_dir, 'Login.jsx'))
|
|
170
|
+
if @signup
|
|
171
|
+
# Generate auth pages
|
|
172
|
+
copy_frontend_file(frontend_template_dir, 'SignUp.jsx', File.join(pages_dir, 'SignUp.jsx'))
|
|
173
|
+
copy_frontend_file(frontend_template_dir, 'ConfirmEmail.jsx', File.join(pages_dir, 'ConfirmEmail.jsx'))
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Generate ProtectedRoute component
|
|
177
|
+
components_dir = 'frontend/src/components'
|
|
178
|
+
FileUtils.mkdir_p(components_dir)
|
|
179
|
+
copy_frontend_file(frontend_template_dir, 'ProtectedRoute.jsx',
|
|
180
|
+
File.join(components_dir, 'ProtectedRoute.jsx'))
|
|
181
|
+
|
|
182
|
+
install_cognito_sdk
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def copy_frontend_file(template_dir, filename, dest)
|
|
186
|
+
src = File.join(template_dir, filename)
|
|
187
|
+
FileUtils.cp(src, dest)
|
|
188
|
+
puts " create #{dest}"
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def install_cognito_sdk
|
|
192
|
+
puts "\n Installing @aws-sdk/client-cognito-identity-provider..."
|
|
193
|
+
success = system('npm', 'install', '@aws-sdk/client-cognito-identity-provider',
|
|
194
|
+
'--prefix', 'frontend', '--no-fund', '--no-audit', '--silent')
|
|
195
|
+
if success
|
|
196
|
+
puts ' ✓ npm dependency installed'
|
|
197
|
+
else
|
|
198
|
+
puts ' ⚠ npm install failed — run: cd frontend && npm install @aws-sdk/client-cognito-identity-provider'
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
137
202
|
def check_collision!
|
|
138
203
|
cognito_tf = File.join(MODULE_DIR, 'cognito.tf')
|
|
139
204
|
return unless File.exist?(cognito_tf)
|
data/lib/belt/route_dsl.rb
CHANGED
|
@@ -156,7 +156,7 @@ module Belt
|
|
|
156
156
|
def initialize(name, options = {})
|
|
157
157
|
@name = name.to_s
|
|
158
158
|
@routes = []
|
|
159
|
-
@default_auth = options[:auth] || :
|
|
159
|
+
@default_auth = options[:auth] || :none
|
|
160
160
|
@default_lambda = options[:lambda] || name
|
|
161
161
|
@default_cors = options.fetch(:cors, true)
|
|
162
162
|
@default_tables = Array(options[:tables] || [])
|
data/lib/belt/version.rb
CHANGED
|
@@ -13,6 +13,14 @@ resource "aws_cognito_user_pool" "<%= pool[:name] %>" {
|
|
|
13
13
|
require_uppercase = true
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
# Admin-only signup — users cannot self-register.
|
|
17
|
+
# Remove this block to allow public sign-up.
|
|
18
|
+
<% unless @signup -%>
|
|
19
|
+
admin_create_user_config {
|
|
20
|
+
allow_admin_create_user_only = true
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
<% end -%>
|
|
16
24
|
auto_verified_attributes = ["email"]
|
|
17
25
|
|
|
18
26
|
account_recovery_setting {
|
|
@@ -43,6 +51,9 @@ resource "aws_cognito_user_pool_client" "<%= pool[:name] %>" {
|
|
|
43
51
|
|
|
44
52
|
explicit_auth_flows = [
|
|
45
53
|
"ALLOW_USER_SRP_AUTH",
|
|
54
|
+
<% if @signup -%>
|
|
55
|
+
"ALLOW_USER_PASSWORD_AUTH",
|
|
56
|
+
<% end -%>
|
|
46
57
|
"ALLOW_REFRESH_TOKEN_AUTH"
|
|
47
58
|
]
|
|
48
59
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import { confirmSignUp } from '../lib/auth'
|
|
3
|
+
|
|
4
|
+
export default function ConfirmEmail() {
|
|
5
|
+
const params = new URLSearchParams(window.location.search)
|
|
6
|
+
const [email, setEmail] = useState(params.get('email') || '')
|
|
7
|
+
const [code, setCode] = useState('')
|
|
8
|
+
const [error, setError] = useState('')
|
|
9
|
+
const [loading, setLoading] = useState(false)
|
|
10
|
+
const [confirmed, setConfirmed] = useState(false)
|
|
11
|
+
|
|
12
|
+
async function handleSubmit(e) {
|
|
13
|
+
e.preventDefault()
|
|
14
|
+
setError('')
|
|
15
|
+
setLoading(true)
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
await confirmSignUp(email, code)
|
|
19
|
+
setConfirmed(true)
|
|
20
|
+
} catch (err) {
|
|
21
|
+
setError(err.message || 'Confirmation failed')
|
|
22
|
+
} finally {
|
|
23
|
+
setLoading(false)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (confirmed) {
|
|
28
|
+
return (
|
|
29
|
+
<div className="auth-page">
|
|
30
|
+
<div className="auth-form">
|
|
31
|
+
<h2>Email Verified! ✓</h2>
|
|
32
|
+
<p>Your account is ready.</p>
|
|
33
|
+
<a href="/login" className="auth-btn">Sign In</a>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div className="auth-page">
|
|
41
|
+
<form className="auth-form" onSubmit={handleSubmit}>
|
|
42
|
+
<h2>Verify Email</h2>
|
|
43
|
+
<p>Enter the code sent to your email.</p>
|
|
44
|
+
{error && <div className="auth-error">{error}</div>}
|
|
45
|
+
<input type="email" placeholder="Email" value={email}
|
|
46
|
+
onChange={e => setEmail(e.target.value)} required />
|
|
47
|
+
<input type="text" placeholder="Verification code" value={code}
|
|
48
|
+
onChange={e => setCode(e.target.value)} required autoFocus />
|
|
49
|
+
<button type="submit" disabled={loading}>
|
|
50
|
+
{loading ? 'Verifying...' : 'Verify'}
|
|
51
|
+
</button>
|
|
52
|
+
</form>
|
|
53
|
+
</div>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import { signIn, completeNewPassword } from '../lib/auth'
|
|
3
|
+
|
|
4
|
+
export default function Login({ onLogin }) {
|
|
5
|
+
const [email, setEmail] = useState('')
|
|
6
|
+
const [password, setPassword] = useState('')
|
|
7
|
+
const [newPassword, setNewPassword] = useState('')
|
|
8
|
+
const [error, setError] = useState('')
|
|
9
|
+
const [challenge, setChallenge] = useState(null)
|
|
10
|
+
const [loading, setLoading] = useState(false)
|
|
11
|
+
|
|
12
|
+
async function handleSubmit(e) {
|
|
13
|
+
e.preventDefault()
|
|
14
|
+
setError('')
|
|
15
|
+
setLoading(true)
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const result = await signIn(email, password)
|
|
19
|
+
if (result.challenge === 'NEW_PASSWORD_REQUIRED') {
|
|
20
|
+
setChallenge(result)
|
|
21
|
+
} else {
|
|
22
|
+
onLogin()
|
|
23
|
+
}
|
|
24
|
+
} catch (err) {
|
|
25
|
+
setError(err.message || 'Sign in failed')
|
|
26
|
+
} finally {
|
|
27
|
+
setLoading(false)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function handleNewPassword(e) {
|
|
32
|
+
e.preventDefault()
|
|
33
|
+
setError('')
|
|
34
|
+
setLoading(true)
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
await completeNewPassword(email, newPassword, challenge.session)
|
|
38
|
+
onLogin()
|
|
39
|
+
} catch (err) {
|
|
40
|
+
setError(err.message || 'Password change failed')
|
|
41
|
+
} finally {
|
|
42
|
+
setLoading(false)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (challenge) {
|
|
47
|
+
return (
|
|
48
|
+
<div className="auth-page">
|
|
49
|
+
<form className="auth-form" onSubmit={handleNewPassword}>
|
|
50
|
+
<h2>Set New Password</h2>
|
|
51
|
+
<p>Please set a permanent password for your account.</p>
|
|
52
|
+
{error && <div className="auth-error">{error}</div>}
|
|
53
|
+
<input type="password" placeholder="New password" value={newPassword}
|
|
54
|
+
onChange={e => setNewPassword(e.target.value)} required autoFocus />
|
|
55
|
+
<button type="submit" disabled={loading}>
|
|
56
|
+
{loading ? 'Saving...' : 'Set Password'}
|
|
57
|
+
</button>
|
|
58
|
+
</form>
|
|
59
|
+
</div>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div className="auth-page">
|
|
65
|
+
<form className="auth-form" onSubmit={handleSubmit}>
|
|
66
|
+
<h2>Sign In</h2>
|
|
67
|
+
{error && <div className="auth-error">{error}</div>}
|
|
68
|
+
<input type="email" placeholder="Email" value={email}
|
|
69
|
+
onChange={e => setEmail(e.target.value)} required autoFocus />
|
|
70
|
+
<input type="password" placeholder="Password" value={password}
|
|
71
|
+
onChange={e => setPassword(e.target.value)} required />
|
|
72
|
+
<button type="submit" disabled={loading}>
|
|
73
|
+
{loading ? 'Signing in...' : 'Sign In'}
|
|
74
|
+
</button>
|
|
75
|
+
<p className="auth-link">
|
|
76
|
+
Don't have an account? <a href="/signup">Sign up</a>
|
|
77
|
+
</p>
|
|
78
|
+
</form>
|
|
79
|
+
</div>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import { signUp } from '../lib/auth'
|
|
3
|
+
|
|
4
|
+
export default function SignUp() {
|
|
5
|
+
const [email, setEmail] = useState('')
|
|
6
|
+
const [password, setPassword] = useState('')
|
|
7
|
+
const [error, setError] = useState('')
|
|
8
|
+
const [loading, setLoading] = useState(false)
|
|
9
|
+
const [needsConfirmation, setNeedsConfirmation] = useState(false)
|
|
10
|
+
|
|
11
|
+
async function handleSubmit(e) {
|
|
12
|
+
e.preventDefault()
|
|
13
|
+
setError('')
|
|
14
|
+
setLoading(true)
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const result = await signUp(email, password)
|
|
18
|
+
if (result.needsConfirmation) setNeedsConfirmation(true)
|
|
19
|
+
} catch (err) {
|
|
20
|
+
setError(err.message || 'Sign up failed')
|
|
21
|
+
} finally {
|
|
22
|
+
setLoading(false)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (needsConfirmation) {
|
|
27
|
+
return (
|
|
28
|
+
<div className="auth-page">
|
|
29
|
+
<div className="auth-form">
|
|
30
|
+
<h2>Check Your Email</h2>
|
|
31
|
+
<p>We sent a verification code to <strong>{email}</strong>.</p>
|
|
32
|
+
<a href={`/confirm?email=${encodeURIComponent(email)}`} className="auth-btn">
|
|
33
|
+
Enter Code
|
|
34
|
+
</a>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div className="auth-page">
|
|
42
|
+
<form className="auth-form" onSubmit={handleSubmit}>
|
|
43
|
+
<h2>Create Account</h2>
|
|
44
|
+
{error && <div className="auth-error">{error}</div>}
|
|
45
|
+
<input type="email" placeholder="Email" value={email}
|
|
46
|
+
onChange={e => setEmail(e.target.value)} required autoFocus />
|
|
47
|
+
<input type="password" placeholder="Password (8+ characters)" value={password}
|
|
48
|
+
onChange={e => setPassword(e.target.value)} required minLength={8} />
|
|
49
|
+
<button type="submit" disabled={loading}>
|
|
50
|
+
{loading ? 'Creating...' : 'Create Account'}
|
|
51
|
+
</button>
|
|
52
|
+
<p className="auth-link">
|
|
53
|
+
Already have an account? <a href="/login">Sign in</a>
|
|
54
|
+
</p>
|
|
55
|
+
</form>
|
|
56
|
+
</div>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { getToken } from './auth'
|
|
2
|
+
|
|
3
|
+
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3000'
|
|
4
|
+
|
|
5
|
+
export async function apiClient(path, options = {}) {
|
|
6
|
+
const { method = 'GET', body, headers = {} } = options
|
|
7
|
+
|
|
8
|
+
const token = getToken()
|
|
9
|
+
const config = {
|
|
10
|
+
method,
|
|
11
|
+
headers: {
|
|
12
|
+
Accept: 'application/json',
|
|
13
|
+
...(token ? { Authorization: token } : {}),
|
|
14
|
+
...headers
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (body !== undefined && body !== null) {
|
|
19
|
+
config.headers['Content-Type'] = 'application/json'
|
|
20
|
+
config.body = JSON.stringify(body)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const response = await fetch(`${API_URL}${path}`, config)
|
|
24
|
+
const data = await response.json()
|
|
25
|
+
|
|
26
|
+
if (response.status === 401) {
|
|
27
|
+
window.location.href = '/login'
|
|
28
|
+
throw new Error('Unauthorized')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!response.ok) throw new Error(data.error || `Request failed: ${response.status}`)
|
|
32
|
+
|
|
33
|
+
return data
|
|
34
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CognitoIdentityProviderClient,
|
|
3
|
+
InitiateAuthCommand,
|
|
4
|
+
RespondToAuthChallengeCommand,
|
|
5
|
+
SignUpCommand,
|
|
6
|
+
ConfirmSignUpCommand
|
|
7
|
+
} from '@aws-sdk/client-cognito-identity-provider'
|
|
8
|
+
|
|
9
|
+
const REGION = import.meta.env.VITE_AWS_REGION
|
|
10
|
+
const CLIENT_ID = import.meta.env.VITE_COGNITO_CLIENT_ID
|
|
11
|
+
|
|
12
|
+
const client = new CognitoIdentityProviderClient({ region: REGION })
|
|
13
|
+
|
|
14
|
+
let idToken = null
|
|
15
|
+
let refreshToken = null
|
|
16
|
+
|
|
17
|
+
export function getToken() { return idToken }
|
|
18
|
+
export function isAuthenticated() { return idToken !== null }
|
|
19
|
+
|
|
20
|
+
export async function signIn(username, password) {
|
|
21
|
+
const response = await client.send(new InitiateAuthCommand({
|
|
22
|
+
AuthFlow: 'USER_PASSWORD_AUTH',
|
|
23
|
+
ClientId: CLIENT_ID,
|
|
24
|
+
AuthParameters: { USERNAME: username, PASSWORD: password }
|
|
25
|
+
}))
|
|
26
|
+
|
|
27
|
+
if (response.ChallengeName === 'NEW_PASSWORD_REQUIRED') {
|
|
28
|
+
return { challenge: 'NEW_PASSWORD_REQUIRED', session: response.Session }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
setTokens(response.AuthenticationResult)
|
|
32
|
+
return { success: true }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function signUp(email, password) {
|
|
36
|
+
await client.send(new SignUpCommand({
|
|
37
|
+
ClientId: CLIENT_ID,
|
|
38
|
+
Username: email,
|
|
39
|
+
Password: password
|
|
40
|
+
}))
|
|
41
|
+
return { needsConfirmation: true }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function confirmSignUp(email, code) {
|
|
45
|
+
await client.send(new ConfirmSignUpCommand({
|
|
46
|
+
ClientId: CLIENT_ID,
|
|
47
|
+
Username: email,
|
|
48
|
+
ConfirmationCode: code
|
|
49
|
+
}))
|
|
50
|
+
return { success: true }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function completeNewPassword(username, newPassword, session) {
|
|
54
|
+
const response = await client.send(new RespondToAuthChallengeCommand({
|
|
55
|
+
ChallengeName: 'NEW_PASSWORD_REQUIRED',
|
|
56
|
+
ClientId: CLIENT_ID,
|
|
57
|
+
Session: session,
|
|
58
|
+
ChallengeResponses: { USERNAME: username, NEW_PASSWORD: newPassword }
|
|
59
|
+
}))
|
|
60
|
+
setTokens(response.AuthenticationResult)
|
|
61
|
+
return { success: true }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function signOut() {
|
|
65
|
+
idToken = null
|
|
66
|
+
refreshToken = null
|
|
67
|
+
localStorage.removeItem('idToken')
|
|
68
|
+
localStorage.removeItem('refreshToken')
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function setTokens(authResult) {
|
|
72
|
+
idToken = authResult.IdToken
|
|
73
|
+
refreshToken = authResult.RefreshToken
|
|
74
|
+
localStorage.setItem('idToken', idToken)
|
|
75
|
+
if (refreshToken) localStorage.setItem('refreshToken', refreshToken)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Restore on page load
|
|
79
|
+
const stored = localStorage.getItem('idToken')
|
|
80
|
+
if (stored) idToken = stored
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: belt
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.19
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stowzilla
|
|
@@ -144,6 +144,12 @@ files:
|
|
|
144
144
|
- lib/templates/frontend_infra/frontend.tf.erb
|
|
145
145
|
- lib/templates/generate/auth/cognito.tf.erb
|
|
146
146
|
- lib/templates/generate/auth/cognito_outputs.tf.erb
|
|
147
|
+
- lib/templates/generate/auth/frontend/ConfirmEmail.jsx
|
|
148
|
+
- lib/templates/generate/auth/frontend/Login.jsx
|
|
149
|
+
- lib/templates/generate/auth/frontend/ProtectedRoute.jsx
|
|
150
|
+
- lib/templates/generate/auth/frontend/SignUp.jsx
|
|
151
|
+
- lib/templates/generate/auth/frontend/apiClient.js
|
|
152
|
+
- lib/templates/generate/auth/frontend/auth.js
|
|
147
153
|
- lib/templates/generate/controller.rb.erb
|
|
148
154
|
- lib/templates/generate/model.rb.erb
|
|
149
155
|
- lib/templates/module/dns.tf.erb
|