phcdevworks_accounts_stytch 0.1.0.pre.1 → 0.2.0.pre.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +43 -4
- data/app/controllers/concerns/error_handler.rb +19 -0
- data/app/controllers/concerns/handle_service_action.rb +17 -0
- data/app/controllers/concerns/organization_setter.rb +11 -0
- data/app/controllers/phcdevworks_accounts_stytch/b2b/authenticate_controller.rb +81 -0
- data/app/controllers/phcdevworks_accounts_stytch/b2b/magic_links_controller.rb +57 -0
- data/app/controllers/phcdevworks_accounts_stytch/b2b/passwords_controller.rb +95 -0
- data/app/controllers/phcdevworks_accounts_stytch/b2c/authenticate_controller.rb +99 -0
- data/app/controllers/phcdevworks_accounts_stytch/b2c/magic_links_controller.rb +90 -0
- data/app/controllers/phcdevworks_accounts_stytch/b2c/passwords_controller.rb +94 -0
- data/app/views/layouts/phcdevworks_accounts_stytch/application.html.erb +2 -0
- data/app/views/phcdevworks_accounts_stytch/b2b/magic_links/invite.html.erb +12 -0
- data/app/views/phcdevworks_accounts_stytch/b2b/magic_links/login_or_signup.html.erb +12 -0
- data/app/views/phcdevworks_accounts_stytch/b2b/passwords/reset_existing_password.html.erb +22 -0
- data/app/views/phcdevworks_accounts_stytch/b2b/passwords/reset_password.html.erb +17 -0
- data/app/views/phcdevworks_accounts_stytch/b2b/passwords/reset_start.html.erb +12 -0
- data/app/views/phcdevworks_accounts_stytch/b2b/passwords/reset_with_session.html.erb +17 -0
- data/app/views/phcdevworks_accounts_stytch/b2c/magic_links/invite.html.erb +12 -0
- data/app/views/phcdevworks_accounts_stytch/b2c/magic_links/login_or_signup.html.erb +12 -0
- data/app/views/phcdevworks_accounts_stytch/b2c/passwords/reset_existing_password.html.erb +22 -0
- data/app/views/phcdevworks_accounts_stytch/b2c/passwords/reset_password.html.erb +17 -0
- data/app/views/phcdevworks_accounts_stytch/b2c/passwords/reset_start.html.erb +12 -0
- data/app/views/phcdevworks_accounts_stytch/b2c/passwords/reset_with_session.html.erb +17 -0
- data/config/routes/b2b.rb +30 -0
- data/config/routes/b2c.rb +27 -0
- data/config/routes.rb +2 -4
- data/lib/phcdevworks_accounts_stytch/authentication/b2b/magic_link_service.rb +63 -0
- data/lib/phcdevworks_accounts_stytch/authentication/b2b/password_service.rb +80 -0
- data/lib/phcdevworks_accounts_stytch/authentication/b2c/magic_link_service.rb +55 -0
- data/lib/phcdevworks_accounts_stytch/authentication/b2c/password_service.rb +76 -0
- data/lib/phcdevworks_accounts_stytch/engine.rb +6 -0
- data/lib/phcdevworks_accounts_stytch/stytch/client.rb +45 -0
- data/lib/phcdevworks_accounts_stytch/stytch/error.rb +26 -0
- data/lib/phcdevworks_accounts_stytch/stytch/method_options.rb +21 -0
- data/lib/phcdevworks_accounts_stytch/stytch/organization.rb +51 -0
- data/lib/phcdevworks_accounts_stytch/stytch/response.rb +44 -0
- data/lib/phcdevworks_accounts_stytch/stytch/success.rb +19 -0
- data/lib/phcdevworks_accounts_stytch/version.rb +1 -1
- data/lib/phcdevworks_accounts_stytch.rb +3 -4
- metadata +37 -13
- data/app/controllers/phcdevworks_accounts_stytch/authentication/login_controller.rb +0 -26
- data/app/controllers/phcdevworks_accounts_stytch/authentication/processor_controller.rb +0 -36
- data/lib/phcdevworks_accounts_stytch/stytch_client.rb +0 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ba334aff9b9da05bcf1eb9b82254ccb41fd0b74c3d657256d48da8d56ef6a13a
|
4
|
+
data.tar.gz: a53b0d2d60349b910aeea9c6e936a81a534eaba7977d07b933b02f200bf443e0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 71c1c67e177fbb66824cd19af5cc8e2f868af3ab7cbff5646034a060636ea7a70bca142caa82cb3c835bbc64cdef10388466c26810c5d25086f2fd3f81e61a9c
|
7
|
+
data.tar.gz: f150aaf721f9f82903e1033879cbba3981119c33a31962d3bddb29331be561010e9352cdbaec2676974e60eae7551a6e9d8abd74d1231f5b552b4cd2d8986916
|
data/README.md
CHANGED
@@ -1,8 +1,47 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# PHCDevworks Accounts Stytch
|
2
|
+
|
3
|
+
![Build Status](https://github.com/phcdevworks/phcdevworks_accounts_stytch/actions/workflows/test.yml/badge.svg)
|
4
|
+
![Gem Version](https://img.shields.io/gem/v/phcdevworks_accounts_stytch.svg)
|
5
|
+
![License](https://img.shields.io/github/license/phcdevworks/phcdevworks_accounts_stytch.svg)
|
6
|
+
[![All Contributors](https://img.shields.io/badge/all_contributors-1-orange.svg?style=flat-square)](#contributors-)
|
7
|
+
![Last Commit](https://img.shields.io/github/last-commit/phcdevworks/phcdevworks_accounts_stytch.svg)
|
8
|
+
![Issues](https://img.shields.io/github/issues/phcdevworks/phcdevworks_accounts_stytch.svg)
|
9
|
+
|
10
|
+
![Forks](https://img.shields.io/github/forks/phcdevworks/phcdevworks_accounts_stytch.svg?style=social)
|
11
|
+
![Stars](https://img.shields.io/github/stars/phcdevworks/phcdevworks_accounts_stytch.svg?style=social)
|
12
|
+
[![Tweet](https://img.shields.io/twitter/url?style=social&url=https%3A%2F%2Fgithub.com%2Fphcdevworks%2Fphcdevworks_accounts_stytch)](https://twitter.com/intent/tweet?text=Check%20out%20this%20authentication%20gem%20for%20Rails%20integrating%20with%20Stytch!%20https://github.com/phcdevworks/phcdevworks_accounts_stytch)
|
13
|
+
|
14
|
+
PHCDevworks Accounts Stytch is an authentication system that integrates with the Stytch API to provide seamless B2B and B2C user authentication for Ruby on Rails apps. The project includes:
|
15
|
+
|
16
|
+
- **Magic Link Authentication**: Users can authenticate via secure magic links sent to their email.
|
17
|
+
- **Password Authentication**: Support for password-based login and reset flows, including registration and session management.
|
18
|
+
- **Password Reset Management**: Users can reset their passwords via email, token, existing password, or session token.
|
19
|
+
- **Organization Support**: The B2B module includes organization-specific user authentication with integration of organization IDs.
|
20
|
+
- **Service Handling**: All service actions are abstracted into service objects that handle interaction with Stytch’s API for better maintainability and testability.
|
21
|
+
- **Error Handling & Logging**: Robust error logging and consistent handling of service responses ensure reliability and traceability of issues.
|
3
22
|
|
4
23
|
## Usage
|
5
|
-
|
24
|
+
|
25
|
+
### 1. Set up Stytch API credentials
|
26
|
+
|
27
|
+
You will need to store your Stytch API credentials securely in your Rails `credentials.yml.enc` file. Follow these steps to open and edit the credentials file:
|
28
|
+
|
29
|
+
1. Ensure you have the `master.key` file located in `config/master.key`.
|
30
|
+
2. Open the encrypted credentials file using the following command in your terminal:
|
31
|
+
```bash
|
32
|
+
rails credentials:edit
|
33
|
+
```
|
34
|
+
3. This will open the credentials file in your default text editor (or VSCode if configured). In the file, add your Stytch API credentials under both b2b and b2c keys, as shown below:
|
35
|
+
```yml
|
36
|
+
stytch:
|
37
|
+
b2b:
|
38
|
+
project_id: <your_b2b_project_id>
|
39
|
+
secret: <your_b2b_secret>
|
40
|
+
b2c:
|
41
|
+
project_id: <your_b2c_project_id>
|
42
|
+
secret: <your_b2c_secret>
|
43
|
+
```
|
44
|
+
4. Save and close the file. Rails will automatically re-encrypt the credentials.
|
6
45
|
|
7
46
|
## Installation
|
8
47
|
Add this line to your application's Gemfile:
|
@@ -22,7 +61,7 @@ $ gem install phcdevworks_accounts_stytch
|
|
22
61
|
```
|
23
62
|
|
24
63
|
## Contributing
|
25
|
-
|
64
|
+
[![contributors](https://contributors-img.web.app/image?repo=phcdevworks/phcdevworks_accounts_stytch)](https://github.com/phcdevworks/phcdevworks_accounts_stytch/graphs/contributors)
|
26
65
|
|
27
66
|
## License
|
28
67
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ErrorHandler
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
def handle_unexpected_error(exception)
|
7
|
+
log_error("Unexpected error: #{exception.message}")
|
8
|
+
render json: { error: 'An unexpected error occurred.' }, status: :internal_server_error
|
9
|
+
end
|
10
|
+
|
11
|
+
def handle_missing_params_error(message)
|
12
|
+
log_error(message)
|
13
|
+
render json: { error: message }, status: :unprocessable_entity
|
14
|
+
end
|
15
|
+
|
16
|
+
def log_error(message)
|
17
|
+
Rails.logger.error(message)
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HandleServiceAction
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
def handle_service_action(action_name)
|
7
|
+
result = yield
|
8
|
+
if result.is_a?(Hash) && result.key?(:message)
|
9
|
+
render json: { message: result[:message], data: result[:data] }, status: :ok
|
10
|
+
else
|
11
|
+
render json: { message: 'Action completed successfully', data: result }, status: :ok
|
12
|
+
end
|
13
|
+
rescue PhcdevworksAccountsStytch::Stytch::Error => e
|
14
|
+
log_error("Stytch API error during #{action_name}: #{e.message}")
|
15
|
+
render json: { error: e.message }, status: :bad_request
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OrganizationSetter
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
def set_organization
|
6
|
+
organization_service = PhcdevworksAccountsStytch::Stytch::Organization.new
|
7
|
+
@organization_id = organization_service.find_organization_id_by_slug(params[:organization_slug])
|
8
|
+
rescue PhcdevworksAccountsStytch::Stytch::Error => e
|
9
|
+
handle_missing_params_error(e.message)
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PhcdevworksAccountsStytch
|
4
|
+
module B2b
|
5
|
+
class AuthenticateController < ApplicationController
|
6
|
+
include ErrorHandler
|
7
|
+
include HandleServiceAction
|
8
|
+
|
9
|
+
def authenticate
|
10
|
+
if magic_link_token_present?
|
11
|
+
handle_magic_link_authentication
|
12
|
+
elsif email_password_and_organization_present?
|
13
|
+
handle_password_authentication
|
14
|
+
else
|
15
|
+
handle_missing_credentials
|
16
|
+
end
|
17
|
+
rescue StandardError => e
|
18
|
+
handle_unexpected_error(e)
|
19
|
+
end
|
20
|
+
|
21
|
+
def process_authenticate
|
22
|
+
if magic_link_token_present?
|
23
|
+
authenticate_with_magic_link
|
24
|
+
elsif email_password_and_organization_present?
|
25
|
+
authenticate_with_password
|
26
|
+
else
|
27
|
+
handle_missing_credentials
|
28
|
+
end
|
29
|
+
rescue StandardError => e
|
30
|
+
handle_unexpected_error(e)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def magic_link_token_present?
|
36
|
+
params[:token].present?
|
37
|
+
end
|
38
|
+
|
39
|
+
def email_password_and_organization_present?
|
40
|
+
params[:email].present? && params[:password].present? && params[:organization_id].present?
|
41
|
+
end
|
42
|
+
|
43
|
+
def handle_magic_link_authentication
|
44
|
+
redirect_to b2b_process_authenticate_path(token: params[:token])
|
45
|
+
end
|
46
|
+
|
47
|
+
def handle_password_authentication
|
48
|
+
redirect_to b2b_process_authenticate_path(email: params[:email], password: params[:password],
|
49
|
+
organization_id: params[:organization_id])
|
50
|
+
end
|
51
|
+
|
52
|
+
def handle_missing_credentials
|
53
|
+
handle_missing_params_error('Magic link token or email, password, and organization ID are required.')
|
54
|
+
end
|
55
|
+
|
56
|
+
def authenticate_with_magic_link
|
57
|
+
handle_service_action(:magic_link_authenticate) do
|
58
|
+
result = magic_link_service.process_authenticate(params[:token])
|
59
|
+
Rails.logger.info("Magic Link Authentication successful: #{result.data}")
|
60
|
+
result
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def authenticate_with_password
|
65
|
+
handle_service_action(:password_authenticate) do
|
66
|
+
result = password_service.authenticate_password(params[:email], params[:password], params[:organization_id])
|
67
|
+
Rails.logger.info("Password Authentication successful: #{result.inspect}")
|
68
|
+
result
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def magic_link_service
|
73
|
+
PhcdevworksAccountsStytch::Authentication::B2b::MagicLinkService.new
|
74
|
+
end
|
75
|
+
|
76
|
+
def password_service
|
77
|
+
PhcdevworksAccountsStytch::Authentication::B2b::PasswordService.new
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PhcdevworksAccountsStytch
|
4
|
+
module B2b
|
5
|
+
class MagicLinksController < ApplicationController
|
6
|
+
include ErrorHandler
|
7
|
+
include OrganizationSetter
|
8
|
+
include HandleServiceAction
|
9
|
+
|
10
|
+
before_action :set_organization, only: %i[process_login_or_signup process_invite]
|
11
|
+
|
12
|
+
def login_or_signup; end
|
13
|
+
|
14
|
+
def process_login_or_signup
|
15
|
+
if missing_login_or_signup_params?
|
16
|
+
handle_missing_params_error('Email and Organization Slug are required.')
|
17
|
+
return
|
18
|
+
end
|
19
|
+
|
20
|
+
handle_service_action(:login_or_signup) do
|
21
|
+
result = service.process_login_or_signup(params[:email], @organization_id)
|
22
|
+
Rails.logger.info("Login or Signup successful: #{result.data}")
|
23
|
+
result
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def invite; end
|
28
|
+
|
29
|
+
def process_invite
|
30
|
+
if missing_invite_params?
|
31
|
+
handle_missing_params_error('Email and Organization Slug are required.')
|
32
|
+
return
|
33
|
+
end
|
34
|
+
|
35
|
+
handle_service_action(:invite) do
|
36
|
+
result = service.process_invite(params[:email], @organization_id, params[:session_token])
|
37
|
+
Rails.logger.info("Invite successful: #{result.data}")
|
38
|
+
result
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def missing_login_or_signup_params?
|
45
|
+
params[:email].blank? || @organization_id.blank?
|
46
|
+
end
|
47
|
+
|
48
|
+
def missing_invite_params?
|
49
|
+
params[:email].blank? || @organization_id.blank?
|
50
|
+
end
|
51
|
+
|
52
|
+
def service
|
53
|
+
PhcdevworksAccountsStytch::Authentication::B2b::MagicLinkService.new
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PhcdevworksAccountsStytch
|
4
|
+
module B2b
|
5
|
+
class PasswordsController < ApplicationController
|
6
|
+
include ErrorHandler
|
7
|
+
include OrganizationSetter
|
8
|
+
include HandleServiceAction
|
9
|
+
|
10
|
+
before_action :set_organization, only: %i[process_reset_start process_reset_existing_password process_reset_with_session]
|
11
|
+
|
12
|
+
def reset_start; end
|
13
|
+
|
14
|
+
def process_reset_start
|
15
|
+
if missing_reset_start_params?
|
16
|
+
handle_missing_params_error('Email and Organization Slug are required.')
|
17
|
+
return
|
18
|
+
end
|
19
|
+
|
20
|
+
handle_service_action(:reset_start) do
|
21
|
+
result = service.reset_start(params[:email], @organization_id)
|
22
|
+
Rails.logger.info("Password Reset Start successful: #{result.data}")
|
23
|
+
result
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def reset_password; end
|
28
|
+
|
29
|
+
def process_reset_password
|
30
|
+
if missing_reset_password_params?
|
31
|
+
handle_missing_params_error('Token and Password are required.')
|
32
|
+
return
|
33
|
+
end
|
34
|
+
|
35
|
+
handle_service_action(:reset_password) do
|
36
|
+
result = service.reset(params[:token], params[:password])
|
37
|
+
Rails.logger.info("Password Reset Successful: #{result.data}")
|
38
|
+
result
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def reset_existing_password; end
|
43
|
+
|
44
|
+
def process_reset_existing_password
|
45
|
+
if missing_existing_password_params?
|
46
|
+
handle_missing_params_error('Email, old password, new password, and organization ID are required.')
|
47
|
+
return
|
48
|
+
end
|
49
|
+
|
50
|
+
handle_service_action(:reset_existing_password) do
|
51
|
+
result = service.reset_existing(params[:email], params[:old_password], params[:new_password], @organization_id)
|
52
|
+
Rails.logger.info("Existing Password Reset Successful: #{result.data}")
|
53
|
+
result
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def reset_with_session; end
|
58
|
+
|
59
|
+
def process_reset_with_session
|
60
|
+
if missing_reset_with_session_params?
|
61
|
+
handle_missing_params_error('Session token, new password, and organization ID are required.')
|
62
|
+
return
|
63
|
+
end
|
64
|
+
|
65
|
+
handle_service_action(:reset_with_session) do
|
66
|
+
result = service.reset_with_session(params[:session_token], params[:password], @organization_id)
|
67
|
+
Rails.logger.info("Session-based Password Reset Successful: #{result.data}")
|
68
|
+
result
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def missing_reset_start_params?
|
75
|
+
params[:email].blank? || @organization_id.blank?
|
76
|
+
end
|
77
|
+
|
78
|
+
def missing_reset_password_params?
|
79
|
+
params[:token].blank? || params[:password].blank?
|
80
|
+
end
|
81
|
+
|
82
|
+
def missing_existing_password_params?
|
83
|
+
params[:email].blank? || params[:old_password].blank? || params[:new_password].blank? || @organization_id.blank?
|
84
|
+
end
|
85
|
+
|
86
|
+
def missing_reset_with_session_params?
|
87
|
+
params[:session_token].blank? || params[:password].blank? || @organization_id.blank?
|
88
|
+
end
|
89
|
+
|
90
|
+
def service
|
91
|
+
PhcdevworksAccountsStytch::Authentication::B2b::PasswordService.new
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PhcdevworksAccountsStytch
|
4
|
+
module B2c
|
5
|
+
class AuthenticateController < ApplicationController
|
6
|
+
def authenticate
|
7
|
+
if magic_link_token_present?
|
8
|
+
handle_magic_link_authentication
|
9
|
+
elsif email_and_password_present?
|
10
|
+
handle_password_authentication
|
11
|
+
else
|
12
|
+
handle_missing_credentials
|
13
|
+
end
|
14
|
+
rescue StandardError => e
|
15
|
+
handle_unexpected_error(e)
|
16
|
+
end
|
17
|
+
|
18
|
+
def process_authenticate
|
19
|
+
if magic_link_token_present?
|
20
|
+
authenticate_with_magic_link
|
21
|
+
elsif email_and_password_present?
|
22
|
+
authenticate_with_password
|
23
|
+
else
|
24
|
+
handle_missing_credentials
|
25
|
+
end
|
26
|
+
rescue StandardError => e
|
27
|
+
handle_unexpected_error(e)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def magic_link_token_present?
|
33
|
+
params[:token].present?
|
34
|
+
end
|
35
|
+
|
36
|
+
def email_and_password_present?
|
37
|
+
params[:email].present? && params[:password].present?
|
38
|
+
end
|
39
|
+
|
40
|
+
def handle_magic_link_authentication
|
41
|
+
redirect_to b2c_process_authenticate_path(token: params[:token])
|
42
|
+
end
|
43
|
+
|
44
|
+
def handle_password_authentication
|
45
|
+
redirect_to b2c_process_authenticate_path(email: params[:email], password: params[:password])
|
46
|
+
end
|
47
|
+
|
48
|
+
def handle_missing_credentials
|
49
|
+
log_error('Missing credentials for authentication')
|
50
|
+
render json: { error: 'Magic link token or email and password are required.' },
|
51
|
+
status: :unprocessable_entity
|
52
|
+
end
|
53
|
+
|
54
|
+
def authenticate_with_magic_link
|
55
|
+
handle_service_action(:magic_link_authenticate) do
|
56
|
+
result = magic_link_service.process_authenticate(params[:token])
|
57
|
+
Rails.logger.info("Magic Link Authentication successful: #{result.data}")
|
58
|
+
result
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def authenticate_with_password
|
63
|
+
handle_service_action(:password_authenticate) do
|
64
|
+
result = password_service.authenticate_password(params[:email], params[:password])
|
65
|
+
Rails.logger.info("Password Authentication successful: #{result.inspect}")
|
66
|
+
result
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def magic_link_service
|
71
|
+
PhcdevworksAccountsStytch::Authentication::B2c::MagicLinkService.new
|
72
|
+
end
|
73
|
+
|
74
|
+
def password_service
|
75
|
+
PhcdevworksAccountsStytch::Authentication::B2c::PasswordService.new
|
76
|
+
end
|
77
|
+
|
78
|
+
def log_error(message)
|
79
|
+
Rails.logger.error(message)
|
80
|
+
end
|
81
|
+
|
82
|
+
def handle_unexpected_error(exception)
|
83
|
+
log_error("Unexpected error during authentication: #{exception.message}")
|
84
|
+
render json: { error: 'An unexpected error occurred.' }, status: :internal_server_error
|
85
|
+
end
|
86
|
+
|
87
|
+
def handle_service_action(action_name)
|
88
|
+
result = yield
|
89
|
+
render json: { message: result.message, data: result.data }, status: :ok
|
90
|
+
rescue PhcdevworksAccountsStytch::Stytch::Error => e
|
91
|
+
log_error("Stytch API error during #{action_name}: #{e.message}")
|
92
|
+
render json: { error: e.message }, status: :bad_request
|
93
|
+
rescue StandardError => e
|
94
|
+
log_error("Unexpected error during #{action_name}: #{e.message}")
|
95
|
+
render json: { error: 'An unexpected error occurred.' }, status: :internal_server_error
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PhcdevworksAccountsStytch
|
4
|
+
module B2c
|
5
|
+
class MagicLinksController < ApplicationController
|
6
|
+
def login_or_signup; end
|
7
|
+
|
8
|
+
def process_login_or_signup
|
9
|
+
if params[:email].blank?
|
10
|
+
log_error('Missing email')
|
11
|
+
render json: { error: 'Email is required.' }, status: :unprocessable_entity
|
12
|
+
return
|
13
|
+
end
|
14
|
+
|
15
|
+
handle_service_action(:login_or_signup) do
|
16
|
+
result = service.process_login_or_signup(params[:email])
|
17
|
+
Rails.logger.info("Login or Signup successful: #{result.data}")
|
18
|
+
result
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def invite; end
|
23
|
+
|
24
|
+
def process_invite
|
25
|
+
if missing_required_params?
|
26
|
+
handle_missing_params_error
|
27
|
+
return
|
28
|
+
end
|
29
|
+
|
30
|
+
handle_invite_action
|
31
|
+
end
|
32
|
+
|
33
|
+
def process_revoke_invite
|
34
|
+
if params[:email].blank?
|
35
|
+
log_error('Missing email')
|
36
|
+
render json: { error: 'Email is required.' }, status: :unprocessable_entity
|
37
|
+
return
|
38
|
+
end
|
39
|
+
|
40
|
+
handle_service_action(:revoke_invite) do
|
41
|
+
result = service.process_revoke_invite(params[:email])
|
42
|
+
Rails.logger.info("Revoke invite successful: #{result.data}")
|
43
|
+
result
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def handle_service_action(action_name)
|
50
|
+
result = yield
|
51
|
+
if result.is_a?(Hash) && result.key?(:message)
|
52
|
+
render json: { message: result[:message], data: result[:data] }, status: :ok
|
53
|
+
else
|
54
|
+
render json: { message: 'Action completed successfully', data: result }, status: :ok
|
55
|
+
end
|
56
|
+
rescue PhcdevworksAccountsStytch::Stytch::Error => e
|
57
|
+
log_error("Stytch API error during #{action_name}: #{e.message}")
|
58
|
+
render json: { error: e.message }, status: :bad_request
|
59
|
+
rescue StandardError => e
|
60
|
+
log_error("Unexpected error during #{action_name}: #{e.message}")
|
61
|
+
render json: { error: 'An unexpected error occurred.' }, status: :internal_server_error
|
62
|
+
end
|
63
|
+
|
64
|
+
def handle_invite_action
|
65
|
+
handle_service_action(:invite) do
|
66
|
+
result = service.process_invite(params[:email])
|
67
|
+
Rails.logger.info("Invite successful: #{result.data}")
|
68
|
+
result
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def handle_missing_params_error
|
73
|
+
log_error('Missing email')
|
74
|
+
render json: { error: 'Email is required.' }, status: :unprocessable_entity
|
75
|
+
end
|
76
|
+
|
77
|
+
def missing_required_params?
|
78
|
+
params[:email].blank?
|
79
|
+
end
|
80
|
+
|
81
|
+
def service
|
82
|
+
PhcdevworksAccountsStytch::Authentication::B2c::MagicLinkService.new
|
83
|
+
end
|
84
|
+
|
85
|
+
def log_error(message)
|
86
|
+
Rails.logger.error(message)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PhcdevworksAccountsStytch
|
4
|
+
module B2c
|
5
|
+
class PasswordsController < ApplicationController
|
6
|
+
include ErrorHandler
|
7
|
+
include HandleServiceAction # Removed OrganizationSetter
|
8
|
+
|
9
|
+
# Removed before_action :set_organization
|
10
|
+
|
11
|
+
def reset_start; end
|
12
|
+
|
13
|
+
def process_reset_start
|
14
|
+
if missing_reset_start_params?
|
15
|
+
handle_missing_params_error('Email is required.')
|
16
|
+
return
|
17
|
+
end
|
18
|
+
|
19
|
+
handle_service_action(:reset_start) do
|
20
|
+
result = service.reset_start(params[:email]) # Removed @organization_id
|
21
|
+
Rails.logger.info("Password Reset Start successful: #{result.data}")
|
22
|
+
result
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def reset_password; end
|
27
|
+
|
28
|
+
def process_reset_password
|
29
|
+
if missing_reset_password_params?
|
30
|
+
handle_missing_params_error('Token and Password are required.')
|
31
|
+
return
|
32
|
+
end
|
33
|
+
|
34
|
+
handle_service_action(:reset_password) do
|
35
|
+
result = service.reset(params[:token], params[:password])
|
36
|
+
Rails.logger.info("Password Reset Successful: #{result.data}")
|
37
|
+
result
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def reset_existing_password; end
|
42
|
+
|
43
|
+
def process_reset_existing_password
|
44
|
+
if missing_existing_password_params?
|
45
|
+
handle_missing_params_error('Email, old password, and new password are required.')
|
46
|
+
return
|
47
|
+
end
|
48
|
+
|
49
|
+
handle_service_action(:reset_existing_password) do
|
50
|
+
result = service.reset_existing(params[:email], params[:old_password], params[:new_password]) # Removed @organization_id
|
51
|
+
Rails.logger.info("Existing Password Reset Successful: #{result.data}")
|
52
|
+
result
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def reset_with_session; end
|
57
|
+
|
58
|
+
def process_reset_with_session
|
59
|
+
if missing_reset_with_session_params?
|
60
|
+
handle_missing_params_error('Session token and new password are required.')
|
61
|
+
return
|
62
|
+
end
|
63
|
+
|
64
|
+
handle_service_action(:reset_with_session) do
|
65
|
+
result = service.reset_with_session(params[:session_token], params[:password]) # Removed @organization_id
|
66
|
+
Rails.logger.info("Session-based Password Reset Successful: #{result.data}")
|
67
|
+
result
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def missing_reset_start_params?
|
74
|
+
params[:email].blank?
|
75
|
+
end
|
76
|
+
|
77
|
+
def missing_reset_password_params?
|
78
|
+
params[:token].blank? || params[:password].blank?
|
79
|
+
end
|
80
|
+
|
81
|
+
def missing_existing_password_params?
|
82
|
+
params[:email].blank? || params[:old_password].blank? || params[:new_password].blank?
|
83
|
+
end
|
84
|
+
|
85
|
+
def missing_reset_with_session_params?
|
86
|
+
params[:session_token].blank? || params[:password].blank?
|
87
|
+
end
|
88
|
+
|
89
|
+
def service
|
90
|
+
PhcdevworksAccountsStytch::Authentication::B2c::PasswordService.new
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<h1>Invite User via Magic Link</h1>
|
2
|
+
|
3
|
+
<%= form_with url: phcdevworks_accounts_stytch.b2b_magic_links_process_invite_path(organization_slug: params[:organization_slug] || 'example-slug'), method: :post do |form| %>
|
4
|
+
<div class="field">
|
5
|
+
<%= form.label :email, "Invitee's Email" %><br>
|
6
|
+
<%= form.email_field :email, required: true %>
|
7
|
+
</div>
|
8
|
+
|
9
|
+
<div class="actions">
|
10
|
+
<%= form.submit "Send Invite" %>
|
11
|
+
</div>
|
12
|
+
<% end %>
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<h1>Login or Sign Up with Magic Link</h1>
|
2
|
+
|
3
|
+
<%= form_with url: phcdevworks_accounts_stytch.b2b_magic_links_process_login_or_signup_path(organization_slug: params[:organization_slug] || 'example-slug'), method: :post do |form| %>
|
4
|
+
<div class="field">
|
5
|
+
<%= form.label :email, "Your Email" %><br>
|
6
|
+
<%= form.email_field :email, required: true %>
|
7
|
+
</div>
|
8
|
+
|
9
|
+
<div class="actions">
|
10
|
+
<%= form.submit "Send Magic Link" %>
|
11
|
+
</div>
|
12
|
+
<% end %>
|