rodauth-openapi 0.1.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.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +76 -0
- data/lib/generators/rodauth/openapi_generator.rb +46 -0
- data/lib/rodauth/openapi/routes/change_login.rb +27 -0
- data/lib/rodauth/openapi/routes/change_password.rb +25 -0
- data/lib/rodauth/openapi/routes/close_account.rb +20 -0
- data/lib/rodauth/openapi/routes/confirm_account.rb +18 -0
- data/lib/rodauth/openapi/routes/create_account.rb +22 -0
- data/lib/rodauth/openapi/routes/email_auth.rb +28 -0
- data/lib/rodauth/openapi/routes/jwt_refresh.rb +13 -0
- data/lib/rodauth/openapi/routes/lockout.rb +30 -0
- data/lib/rodauth/openapi/routes/login.rb +20 -0
- data/lib/rodauth/openapi/routes/logout.rb +16 -0
- data/lib/rodauth/openapi/routes/otp.rb +75 -0
- data/lib/rodauth/openapi/routes/otp_unlock.rb +38 -0
- data/lib/rodauth/openapi/routes/recovery_codes.rb +45 -0
- data/lib/rodauth/openapi/routes/remember.rb +24 -0
- data/lib/rodauth/openapi/routes/reset_password.rb +36 -0
- data/lib/rodauth/openapi/routes/sms_codes.rb +99 -0
- data/lib/rodauth/openapi/routes/two_factor_base.rb +43 -0
- data/lib/rodauth/openapi/routes/verify_account.rb +59 -0
- data/lib/rodauth/openapi/routes/verify_login_change.rb +21 -0
- data/lib/rodauth/openapi/routes/webauthn.rb +103 -0
- data/lib/rodauth/openapi/routes/webauthn_login.rb +29 -0
- data/lib/rodauth/openapi/routes.rb +129 -0
- data/lib/rodauth/openapi.rb +79 -0
- data/lib/rodauth-openapi.rb +1 -0
- data/rodauth-openapi.gemspec +18 -0
- metadata +86 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 0ce74f8234f280f924c119e914ee31e88ee7714ccd10770626de4034edcac7da
|
4
|
+
data.tar.gz: 42f9cd22c79d36a6ae06e4703ce80a0da926908e5bd72f4eeb0a77644db1cf68
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e3797f271d6735174e03beda40c98f2392a338d295dcb15d06d323d52cc8828a1b0c654d798a8c3380039759a7eb769f8c9747ad4a7f0678a0f293e07eaa16de
|
7
|
+
data.tar.gz: bbdd36a6a8ab3d499b4f2af8487291111084e7a51851ef3e5463b861a6118922a20d217e2f5e56c185ca74e8f2a2b01b50d9694cbebd1f6312bf51cb18316b84
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2024 Janko Marohnić
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# Rodauth OpenAPI
|
2
|
+
|
3
|
+
Generates [OpenAPI] documentation for your Rodauth endpoints.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
```sh
|
8
|
+
bundle add rodauth-openapi --group development
|
9
|
+
```
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
The generated OpenAPI documentation can be uploaded to renderers such as [Swagger Editor] or [Redoc].
|
14
|
+
|
15
|
+
### Rails
|
16
|
+
|
17
|
+
Assuming you have Rodauth installed in your Rails application, you can use the generator provided by this gem:
|
18
|
+
|
19
|
+
```sh
|
20
|
+
rails g rodauth:openapi
|
21
|
+
```
|
22
|
+
|
23
|
+
This will generate OpenAPI documentation in YAML format for the default Rodauth configuration and print it to standard output.
|
24
|
+
|
25
|
+
The generator accepts the following options:
|
26
|
+
|
27
|
+
```sh
|
28
|
+
rails g rodauth:openapi --name admin # secondary configuration
|
29
|
+
rails g rodauth:openapi --format json # JSON format
|
30
|
+
rails g rodauth:openapi --json # generate JSON API endpoints
|
31
|
+
rails g rodauth:openapi --no-password # assume account without password
|
32
|
+
rails g rodauth:openapi --save openapi.yml # save to a file
|
33
|
+
```
|
34
|
+
|
35
|
+
### Outside of Rails
|
36
|
+
|
37
|
+
If you're not using Rails, you can generate the OpenAPI documentation programmatically:
|
38
|
+
|
39
|
+
```rb
|
40
|
+
require "rodauth/openapi"
|
41
|
+
|
42
|
+
auth_class = RodauthApp.rodauth # or RodauthApp.rodauth(:admin)
|
43
|
+
open_api = Rodauth::OpenAPI.new(auth_class)
|
44
|
+
|
45
|
+
File.write("openapi.yml", open_api.to_yaml)
|
46
|
+
```
|
47
|
+
|
48
|
+
To generate JSON API endpoints:
|
49
|
+
|
50
|
+
```rb
|
51
|
+
Roduath::OpenAPI.new(auth_class, json: true)
|
52
|
+
```
|
53
|
+
|
54
|
+
To assume the account doesn't have a password:
|
55
|
+
|
56
|
+
```rb
|
57
|
+
Roduath::OpenAPI.new(auth_class, password: false)
|
58
|
+
```
|
59
|
+
|
60
|
+
To generate the documentation in JSON format:
|
61
|
+
|
62
|
+
```rb
|
63
|
+
Roduath::OpenAPI.new(auth_class).to_json
|
64
|
+
```
|
65
|
+
|
66
|
+
## License
|
67
|
+
|
68
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
69
|
+
|
70
|
+
## Code of Conduct
|
71
|
+
|
72
|
+
Everyone interacting in the Rodauth::Openapi project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/janko/rodauth-openapi/blob/main/CODE_OF_CONDUCT.md).
|
73
|
+
|
74
|
+
[OpenAPI]: https://swagger.io/specification/
|
75
|
+
[Swagger Editor]: https://editor.swagger.io/
|
76
|
+
[Redoc]: https://redocly.github.io/redoc/
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require "rodauth/openapi"
|
2
|
+
require "rails/generators"
|
3
|
+
|
4
|
+
module Rodauth
|
5
|
+
module Generators
|
6
|
+
class OpenAPIGenerator < ::Rails::Generators::Base
|
7
|
+
namespace "rodauth:openapi"
|
8
|
+
|
9
|
+
class_option :password, type: :boolean, default: true,
|
10
|
+
desc: "Whether to assume the account has a password"
|
11
|
+
|
12
|
+
class_option :json, type: :boolean,
|
13
|
+
desc: "Whether to generate JSON API documentation"
|
14
|
+
|
15
|
+
class_option :name, type: :string, default: nil,
|
16
|
+
desc: "The configuration name for which to generate documentation"
|
17
|
+
|
18
|
+
class_option :format, type: :string, default: "yaml",
|
19
|
+
desc: "The format to output the documentation in (yaml, json)"
|
20
|
+
|
21
|
+
class_option :save, type: :string, default: nil,
|
22
|
+
desc: "File to save the documentation to (by default prints to stdout)"
|
23
|
+
|
24
|
+
def print_documentation
|
25
|
+
open_api = Rodauth::OpenAPI.new(auth_class, password: options[:password], json: options[:json])
|
26
|
+
documentation = open_api.public_send(:"to_#{options[:format]}")
|
27
|
+
|
28
|
+
if options[:save]
|
29
|
+
File.write(options[:save], documentation)
|
30
|
+
else
|
31
|
+
puts documentation
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def auth_class
|
38
|
+
Rodauth::Rails.app.rodauth!(configuration_name)
|
39
|
+
end
|
40
|
+
|
41
|
+
def configuration_name
|
42
|
+
options[:name]&.to_sym
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Rodauth
|
2
|
+
class OpenAPI
|
3
|
+
Routes.define :change_login, "Change Login" do
|
4
|
+
get :change_login, "View login change page" do
|
5
|
+
redirect_error_response :login_required, "login required"
|
6
|
+
|
7
|
+
html_response "login change form"
|
8
|
+
redirect_error_response :login_required, "login required"
|
9
|
+
redirect_error_response :two_factor_need_authentication, "2FA required" if feature?(:two_factor_base)
|
10
|
+
end
|
11
|
+
|
12
|
+
post :change_login, "Perform login change" do
|
13
|
+
param :password, "Current account password", type: :password, required: true if rodauth.change_login_requires_password?
|
14
|
+
param :login, "Email to set", type: :email, required: true
|
15
|
+
param :login_confirm, "Email confirmation", type: :email, required: true if rodauth.require_login_confirmation?
|
16
|
+
|
17
|
+
error_response :invalid_password, "invalid password" if rodauth.change_login_requires_password?
|
18
|
+
error_response :invalid_field, "login does not meet requirements"
|
19
|
+
error_response :unmatched_field, "logins do not match" if rodauth.require_login_confirmation?
|
20
|
+
error_response :invalid_field, "same as current login"
|
21
|
+
error_response :invalid_field, "already an account with this login"
|
22
|
+
redirect_error_response :login_required, "login required"
|
23
|
+
redirect_error_response :two_factor_need_authentication, "2FA required" if feature?(:two_factor_base)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Rodauth
|
2
|
+
class OpenAPI
|
3
|
+
Routes.define :change_password, "Change Password" do
|
4
|
+
get :change_password, "View change password page" do
|
5
|
+
html_response "change password form"
|
6
|
+
redirect_error_response :login_required, "login required"
|
7
|
+
redirect_error_response :two_factor_need_authentication, "2FA required" if feature?(:two_factor_base)
|
8
|
+
end
|
9
|
+
|
10
|
+
post :change_password, "Perform password change" do
|
11
|
+
param :password, "Current account password", type: :password, example: "oldsecret123", required: true if rodauth.change_password_requires_password?
|
12
|
+
param :new_password, "Password to set", type: :password, example: "newsecret123", required: true
|
13
|
+
param :password_confirm, "Password confirmation", type: :password, example: "newsecret123", required: true if rodauth.require_password_confirmation?
|
14
|
+
|
15
|
+
success_response "successful password change"
|
16
|
+
error_response :invalid_password, "invalid previous password" if rodauth.change_password_requires_password?
|
17
|
+
error_response :invalid_field, "same as existing password"
|
18
|
+
error_response :invalid_field, "invalid password"
|
19
|
+
error_response :unmatched_field, "passwords do not match" if rodauth.require_password_confirmation?
|
20
|
+
redirect_error_response :login_required, "login required"
|
21
|
+
redirect_error_response :two_factor_need_authentication, "2FA required" if feature?(:two_factor_base)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Rodauth
|
2
|
+
class OpenAPI
|
3
|
+
Routes.define :close_account, "Close Account" do
|
4
|
+
get :close_account, "View close account page" do
|
5
|
+
html_response "close account form"
|
6
|
+
redirect_error_response :login_required, "login required"
|
7
|
+
redirect_error_response :two_factor_need_authentication, "2FA required" if feature?(:two_factor_base)
|
8
|
+
end
|
9
|
+
|
10
|
+
post :close_account, "Perform closing account" do
|
11
|
+
param :password, "Current account password", type: :password, required: true if rodauth.close_account_requires_password?
|
12
|
+
|
13
|
+
success_response "account successfully closed"
|
14
|
+
error_response :invalid_password, "invalid password" if rodauth.close_account_requires_password?
|
15
|
+
redirect_error_response :login_required, "login required"
|
16
|
+
redirect_error_response :two_factor_need_authentication, "2FA required" if feature?(:two_factor_base)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Rodauth
|
2
|
+
class OpenAPI
|
3
|
+
Routes.define :confirm_password, "Confirm Password" do
|
4
|
+
get :confirm_password, "View password confirmation page" do
|
5
|
+
html_response "password confirmation form"
|
6
|
+
redirect_error_response :login_required, "login required"
|
7
|
+
end
|
8
|
+
|
9
|
+
post :confirm_password, "Perform password confirmation" do
|
10
|
+
param :password, "Current account password", type: :password, required: true
|
11
|
+
|
12
|
+
success_response "password successfully confirmed"
|
13
|
+
error_response :invalid_password, "invalid password"
|
14
|
+
redirect_error_response :login_required, "login required"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Rodauth
|
2
|
+
class OpenAPI
|
3
|
+
Routes.define :create_account, "Create Account" do
|
4
|
+
get :create_account, "View registration page" do
|
5
|
+
html_response "registration form"
|
6
|
+
end
|
7
|
+
|
8
|
+
post :create_account, "Perform registration" do
|
9
|
+
param :login, "Email address for the account", type: :email, required: true
|
10
|
+
param :login_confirm, "Email address confirmation", type: :email, required: true if rodauth.require_login_confirmation?
|
11
|
+
param :password, "Password to set", type: :password, required: true if rodauth.create_account_set_password?
|
12
|
+
param :password_confirm, "Password confirmation", type: :password, required: true if rodauth.create_account_set_password? && rodauth.require_password_confirmation?
|
13
|
+
|
14
|
+
success_response "successful registration"
|
15
|
+
error_response :invalid_field, "invalid login"
|
16
|
+
error_response :unmatched_field, "logins do not match" if rodauth.require_login_confirmation?
|
17
|
+
error_response :invalid_field, "invalid password" if rodauth.create_account_set_password?
|
18
|
+
error_response :unmatched_field, "passwords do not match" if rodauth.create_account_set_password? && rodauth.require_password_confirmation?
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Rodauth
|
2
|
+
class OpenAPI
|
3
|
+
Routes.define :email_auth, "Email Authentication" do
|
4
|
+
post :email_auth_request, "Perform email authentication request" do
|
5
|
+
param :login, "Email address for the account", type: :email, required: true
|
6
|
+
|
7
|
+
success_response "successfully sent email authentication email"
|
8
|
+
redirect_error_response :no_matching_login, "no matching login"
|
9
|
+
redirect_error_response "email recently sent"
|
10
|
+
end
|
11
|
+
|
12
|
+
get :email_auth, "View email authentication page" do
|
13
|
+
param :email_auth_key, "Email authentication token", type: :token, required: true
|
14
|
+
|
15
|
+
response 302, "token stored in session"
|
16
|
+
html_response "email authentication form"
|
17
|
+
redirect_error_response "invalid email authentication key"
|
18
|
+
end
|
19
|
+
|
20
|
+
post :email_auth, "Perform email authentication" do
|
21
|
+
param :email_auth_key, "Email authentication token", type: :token, required: json
|
22
|
+
|
23
|
+
success_response "successful email authentication"
|
24
|
+
redirect_error_response :invalid_key, "invalid email authentication key"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Rodauth
|
2
|
+
class OpenAPI
|
3
|
+
Routes.define :jwt_refresh, "JWT Refresh" do
|
4
|
+
post :jwt_refresh, "Refresh JWT access token" do
|
5
|
+
param :jwt_refresh_token_key, "JWT refresh token", type: :string, required: true
|
6
|
+
|
7
|
+
json_response({ rodauth.jwt_refresh_token_key => "...", rodauth.jwt_access_token_key => "..." })
|
8
|
+
error_response rodauth.jwt_refresh_without_access_token_status, "no JWT access token provided"
|
9
|
+
error_response "invalid JWT refresh token"
|
10
|
+
end if json
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Rodauth
|
2
|
+
class OpenAPI
|
3
|
+
Routes.define :lockout, "Lockout" do
|
4
|
+
post :unlock_account_request, "Perform account unlock request" do
|
5
|
+
param :login, "Email address for the account", type: :email, required: true
|
6
|
+
|
7
|
+
success_response "successfully sent unlock account email"
|
8
|
+
error_response :no_matching_login, "no matching login"
|
9
|
+
redirect_error_response "email recently sent"
|
10
|
+
end
|
11
|
+
|
12
|
+
get :unlock_account, "View account unlock page" do
|
13
|
+
param :unlock_account_key, "Account unlock token", type: :token, required: true
|
14
|
+
|
15
|
+
response 302, "token stored in session"
|
16
|
+
html_response "account unlock form"
|
17
|
+
redirect_error_response "invalid or expired unlock account key"
|
18
|
+
end
|
19
|
+
|
20
|
+
post :unlock_account, "Perform account unlock" do
|
21
|
+
param :unlock_account_key, "Account unlock token", type: :token, required: json
|
22
|
+
param :password, "Current account password", type: :password, required: true if rodauth.unlock_account_requires_password?
|
23
|
+
|
24
|
+
success_response "account successfully unlocked"
|
25
|
+
redirect_error_response :invalid_key, "invalid or expired unlock account key"
|
26
|
+
error_response :invalid_password, "invalid password" if rodauth.unlock_account_requires_password?
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Rodauth
|
2
|
+
class OpenAPI
|
3
|
+
Routes.define :login, "Login" do
|
4
|
+
get :login, "View Login page" do
|
5
|
+
html_response "login form"
|
6
|
+
end
|
7
|
+
|
8
|
+
post :login, "Perform login" do
|
9
|
+
param :login, "Email address for the account", type: :email, required: true
|
10
|
+
param :password, "Password for the account", type: :password, required: !rodauth.use_multi_phase_login?
|
11
|
+
|
12
|
+
success_response "successful login"
|
13
|
+
error_response :no_matching_login, "no matching login"
|
14
|
+
error_response :unopen_account, "unverified account"
|
15
|
+
error_response :lockout, "account locked out" if feature?(:lockout)
|
16
|
+
error_response :login, "invalid password"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Rodauth
|
2
|
+
class OpenAPI
|
3
|
+
Routes.define :logout, "Logout" do
|
4
|
+
get :logout, "View logout page" do
|
5
|
+
html_response "logout form"
|
6
|
+
end
|
7
|
+
|
8
|
+
post :logout, "Perform logout" do
|
9
|
+
param :global_logout, "Whether to logout all active sessions", type: :boolean if feature?(:active_sessions)
|
10
|
+
param :jwt_refresh_token_key, "JWT refresh token to delete (\"all\" deletes all tokens)", type: :string if feature?(:jwt_refresh)
|
11
|
+
|
12
|
+
success_response "successful logout"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Rodauth
|
2
|
+
class OpenAPI
|
3
|
+
Routes.define :otp, "OTP" do
|
4
|
+
get :otp_auth, "View TOTP authentication page" do
|
5
|
+
html_response "TOTP authentication form"
|
6
|
+
redirect_error_response :login_required, "login required"
|
7
|
+
redirect_error_response :two_factor_already_authenticated, "already authenticated via TOTP"
|
8
|
+
redirect_error_response :two_factor_not_setup, "TOTP not setup"
|
9
|
+
redirect_error_response :lockout, "TOTP locked out"
|
10
|
+
end
|
11
|
+
|
12
|
+
post :otp_auth, "Perform TOTP authentication" do
|
13
|
+
param :otp_auth, "TOTP code", type: :string, required: true
|
14
|
+
|
15
|
+
success_response "successfully authenticated via TOTP"
|
16
|
+
error_response :invalid_key, "invalid TOTP code"
|
17
|
+
redirect_error_response :login_required, "login required"
|
18
|
+
redirect_error_response :two_factor_already_authenticated, "already authenticated via TOTP"
|
19
|
+
redirect_error_response :two_factor_not_setup, "TOTP not setup"
|
20
|
+
redirect_error_response :lockout, "TOTP locked out"
|
21
|
+
end
|
22
|
+
|
23
|
+
get :otp_setup, "View TOTP setup page" do
|
24
|
+
html_response "TOTP setup form"
|
25
|
+
redirect_error_response :login_required, "login required"
|
26
|
+
redirect_error_response :two_factor_need_authentication, "2nd factor required"
|
27
|
+
redirect_error_response "TOTP already setup"
|
28
|
+
end
|
29
|
+
|
30
|
+
post :otp_setup, "Perform TOTP setup" do
|
31
|
+
if json
|
32
|
+
description <<~MARKDOWN
|
33
|
+
In JSON mode, if TOTP secret wasn't provided (and HMACs are used), the response will include a valid TOTP secret that can be included in the subsequent setup request:
|
34
|
+
```json
|
35
|
+
{
|
36
|
+
"#{rodauth.otp_setup_param}": "...",
|
37
|
+
"#{rodauth.otp_setup_raw_param}": "..."
|
38
|
+
}
|
39
|
+
```
|
40
|
+
MARKDOWN
|
41
|
+
end
|
42
|
+
|
43
|
+
param :otp_setup, "TOTP secret", type: :string, required: true
|
44
|
+
param :otp_setup_raw, "TOTP raw secret", type: :string, required: true if rodauth.otp_keys_use_hmac?
|
45
|
+
param :otp_auth, "TOTP code", type: :string, required: true
|
46
|
+
param :password, "Current account password", type: :password, required: true if rodauth.two_factor_modifications_require_password?
|
47
|
+
|
48
|
+
success_response "TOTP successfully setup"
|
49
|
+
error_response :invalid_field, "invalid TOTP secret"
|
50
|
+
error_response :invalid_password, "invalid password" if rodauth.two_factor_modifications_require_password?
|
51
|
+
error_response :invalid_key, "Invalid TOTP code"
|
52
|
+
redirect_error_response :login_required, "login required"
|
53
|
+
redirect_error_response :two_factor_need_authentication, "2nd factor required"
|
54
|
+
redirect_error_response "TOTP already setup"
|
55
|
+
end
|
56
|
+
|
57
|
+
get :otp_disable, "View TOTP disable page" do
|
58
|
+
success_response "TOTP disable form"
|
59
|
+
redirect_error_response :login_required, "login required"
|
60
|
+
redirect_error_response :two_factor_need_authentication, "2nd factor required"
|
61
|
+
redirect_error_response :two_factor_not_setup, "TOTP not setup"
|
62
|
+
end
|
63
|
+
|
64
|
+
post :otp_disable, "Perform TOTP disable" do
|
65
|
+
param :password, "Current account password", type: :password, required: true if rodauth.two_factor_modifications_require_password?
|
66
|
+
|
67
|
+
success_response "TOTP successfully disabled"
|
68
|
+
error_response :invalid_password, "invalid password" if rodauth.two_factor_modifications_require_password?
|
69
|
+
redirect_error_response :login_required, "login required"
|
70
|
+
redirect_error_response :two_factor_need_authentication, "2nd factor required"
|
71
|
+
redirect_error_response :two_factor_not_setup, "TOTP not setup"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Rodauth
|
2
|
+
class OpenAPI
|
3
|
+
Routes.define :otp_unlock, "OTP Unlock" do
|
4
|
+
get :otp_unlock, "View TOTP unlock page" do
|
5
|
+
html_response "TOTP unlock form"
|
6
|
+
redirect_error_response :otp_unlock_not_locked_out, "TOTP not locked out"
|
7
|
+
redirect_error_response :login_required, "login required"
|
8
|
+
redirect_error_response :two_factor_not_setup, "TOTP not setup"
|
9
|
+
end
|
10
|
+
|
11
|
+
post :otp_unlock, "Perform TOTP unlock step" do
|
12
|
+
if json
|
13
|
+
description <<~MARKDOWN
|
14
|
+
In JSON mode, the response of TOTP unlock steps will include progress information:
|
15
|
+
```json
|
16
|
+
{
|
17
|
+
"num_successes": 2,
|
18
|
+
"required_successes": 3,
|
19
|
+
"next_attempt_after": 1728512721,
|
20
|
+
"deadline": 1728512769
|
21
|
+
}
|
22
|
+
```
|
23
|
+
MARKDOWN
|
24
|
+
end
|
25
|
+
|
26
|
+
param :otp_auth, "TOTP code", type: :string, required: true
|
27
|
+
|
28
|
+
success_response "TOTP authentication successful, TOTP successfully unlocked"
|
29
|
+
error_response :otp_unlock_auth_failure, "TOTP code invalid"
|
30
|
+
redirect_error_response :otp_unlock_auth_deadline_passed, "TOTP unlock deadline passed"
|
31
|
+
redirect_error_response :otp_unlock_auth_not_yet_available, "TOTP unlock not yet available"
|
32
|
+
redirect_error_response :otp_unlock_not_locked_out, "TOTP not locked out"
|
33
|
+
redirect_error_response :login_required, "login required"
|
34
|
+
redirect_error_response :two_factor_not_setup, "TOTP not setup"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Rodauth
|
2
|
+
class OpenAPI
|
3
|
+
Routes.define :recovery_codes, "Recovery Codes" do
|
4
|
+
get :recovery_auth, "View recovery code authentication page" do
|
5
|
+
html_response "recovery code authentication form"
|
6
|
+
redirect_error_response :login_required, "login required"
|
7
|
+
redirect_error_response :two_factor_not_setup, "2nd factor not setup"
|
8
|
+
redirect_error_response :two_factor_already_authenticated, "already authenticated via recovery code"
|
9
|
+
end
|
10
|
+
|
11
|
+
post :recovery_auth, "Authenticate via recovery code" do
|
12
|
+
param :recovery_codes, "Recovery code", type: :string, required: true
|
13
|
+
|
14
|
+
success_response "successfully authenticated via recovery code"
|
15
|
+
error_response :invalid_key, "invalid recovery code"
|
16
|
+
redirect_error_response :login_required, "login required"
|
17
|
+
redirect_error_response :two_factor_not_setup, "2nd factor not setup"
|
18
|
+
redirect_error_response :two_factor_already_authenticated, "already authenticated via recovery code"
|
19
|
+
end
|
20
|
+
|
21
|
+
get :recovery_codes, "View recovery codes page" do
|
22
|
+
html_response "recovery codes form"
|
23
|
+
|
24
|
+
redirect_error_response :login_required, "login required"
|
25
|
+
redirect_error_response :two_factor_not_setup, "2nd factor not setup"
|
26
|
+
redirect_error_response :two_factor_need_authentication, "2nd factor required"
|
27
|
+
end
|
28
|
+
|
29
|
+
post :recovery_codes, "See recovery codes" do
|
30
|
+
param :password, "Current account password", type: :password, required: true if rodauth.two_factor_modifications_require_password?
|
31
|
+
param :add_recovery_codes, "Whether to create missing recovery codes", type: :boolean
|
32
|
+
|
33
|
+
if json
|
34
|
+
json_response("available recovery codes", { "codes": ["..."] })
|
35
|
+
else
|
36
|
+
html_response "displayed recovery codes"
|
37
|
+
end
|
38
|
+
error_response :invalid_password, "invalid password" if rodauth.two_factor_modifications_require_password?
|
39
|
+
redirect_error_response :login_required, "login required"
|
40
|
+
redirect_error_response :two_factor_not_setup, "2nd factor not setup"
|
41
|
+
redirect_error_response :two_factor_need_authentication, "2nd factor required"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Rodauth
|
2
|
+
class OpenAPI
|
3
|
+
Routes.define :remember, "Remember" do
|
4
|
+
get :remember, "View remember settings page" do
|
5
|
+
html_response "remember settings form"
|
6
|
+
redirect_error_response :login_required, "login required"
|
7
|
+
redirect_error_response :two_factor_need_authentication, "2FA required" if feature?(:two_factor_base)
|
8
|
+
end
|
9
|
+
|
10
|
+
post :remember, "Change remember settings" do
|
11
|
+
param :remember, "Remember action to perform", type: :string, required: true, enum: [
|
12
|
+
rodauth.remember_remember_param_value,
|
13
|
+
rodauth.remember_forget_param_value,
|
14
|
+
rodauth.remember_disable_param_value,
|
15
|
+
]
|
16
|
+
|
17
|
+
success_response "remember setting successfully changed"
|
18
|
+
error_response :invalid_field, "invalid remember param"
|
19
|
+
redirect_error_response :login_required, "login required"
|
20
|
+
redirect_error_response :two_factor_need_authentication, "2FA required" if feature?(:two_factor_base)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Rodauth
|
2
|
+
class OpenAPI
|
3
|
+
Routes.define :reset_password, "Reset Password" do
|
4
|
+
get :reset_password_request, "View password reset request page" do
|
5
|
+
html_response "password reset request form"
|
6
|
+
end
|
7
|
+
|
8
|
+
post :reset_password_request, "Perform password reset request" do
|
9
|
+
param :login, "Email address for the account", type: :email, required: true
|
10
|
+
|
11
|
+
success_response "successfully sent reset password email"
|
12
|
+
error_response :no_matching_login, "no matching login"
|
13
|
+
error_response :unopen_account, "unverified account"
|
14
|
+
redirect_error_response "email recently sent"
|
15
|
+
end
|
16
|
+
|
17
|
+
get :reset_password, "View password reset page" do
|
18
|
+
param :reset_password_key, "Password reset token", type: :token, required: true
|
19
|
+
|
20
|
+
response 302, "token stored in session"
|
21
|
+
html_response "password reset form"
|
22
|
+
redirect_error_response "invalid or expired password reset key"
|
23
|
+
end
|
24
|
+
|
25
|
+
post :reset_password, "Perform password reset" do
|
26
|
+
param :reset_password_key, "Password reset token", type: :token, required: json
|
27
|
+
|
28
|
+
success_response "successfully reset password"
|
29
|
+
redirect_error_response :invalid_key, "invalid or expired password reset key"
|
30
|
+
error_response :invalid_field, "invalid password"
|
31
|
+
error_response :invalid_field, "same as existing password"
|
32
|
+
error_response :unmatched_field, "passwords do no match" if rodauth.require_password_confirmation?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Rodauth
|
2
|
+
class OpenAPI
|
3
|
+
Routes.define :sms_codes, "SMS Codes" do
|
4
|
+
get :sms_request, "View SMS code request page" do
|
5
|
+
html_response "SMS code request form"
|
6
|
+
redirect_error_response :login_required, "login required"
|
7
|
+
redirect_error_response :two_factor_already_authenticated, "already authenticated via SMS code"
|
8
|
+
redirect_error_response :two_factor_not_setup, "SMS codes not setup"
|
9
|
+
redirect_error_response :lockout, "SMS codes locked out"
|
10
|
+
end
|
11
|
+
|
12
|
+
post :sms_request, "Request SMS code" do
|
13
|
+
success_response "SMS code successfully sent"
|
14
|
+
redirect_error_response :login_required, "login required"
|
15
|
+
redirect_error_response :two_factor_already_authenticated, "already authenticated via SMS code"
|
16
|
+
redirect_error_response :two_factor_not_setup, "SMS codes not setup"
|
17
|
+
redirect_error_response :lockout, "SMS codes locked out"
|
18
|
+
end
|
19
|
+
|
20
|
+
get :sms_auth, "View SMS code authentication page" do
|
21
|
+
html_response "SMS code authentication form"
|
22
|
+
redirect_error_response :invalid_key, "no current SMS code"
|
23
|
+
redirect_error_response :login_required, "login required"
|
24
|
+
redirect_error_response :two_factor_already_authenticated, "already authenticated via SMS code"
|
25
|
+
redirect_error_response :two_factor_not_setup, "SMS codes not setup"
|
26
|
+
redirect_error_response :lockout, "SMS codes locked out"
|
27
|
+
end
|
28
|
+
|
29
|
+
post :sms_auth, "Authenticate via SMS code" do
|
30
|
+
param :sms_code, "SMS code", type: :string, required: true
|
31
|
+
|
32
|
+
success_response "successfully authenticated via SMS code"
|
33
|
+
error_response :invalid_key, "invalid SMS code"
|
34
|
+
redirect_error_response :invalid_key, "no current SMS code"
|
35
|
+
redirect_error_response :login_required, "login required"
|
36
|
+
redirect_error_response :two_factor_already_authenticated, "already authenticated via SMS code"
|
37
|
+
redirect_error_response :two_factor_not_setup, "SMS codes not setup"
|
38
|
+
redirect_error_response :lockout, "SMS codes locked out"
|
39
|
+
end
|
40
|
+
|
41
|
+
get :sms_setup, "View SMS codes setup page" do
|
42
|
+
html_response "SMS codes setup form"
|
43
|
+
redirect_error_response :login_required, "login required"
|
44
|
+
redirect_error_response :two_factor_need_authentication, "2nd factor required"
|
45
|
+
redirect_error_response :two_factor_not_setup, "2nd factor not setup"
|
46
|
+
redirect_error_response :sms_already_setup, "SMS codes already setup"
|
47
|
+
redirect_error_response :sms_needs_confirmation, "SMS phone number needs confirmation"
|
48
|
+
end
|
49
|
+
|
50
|
+
post :sms_setup, "Setup SMS codes" do
|
51
|
+
param :sms_phone, "SMS phone number", type: :string, required: true
|
52
|
+
param :password, "Current account password", type: :password, required: true if rodauth.two_factor_modifications_require_password?
|
53
|
+
|
54
|
+
success_response "SMS codes successfully setup"
|
55
|
+
error_response :invalid_field, "invalid phone number"
|
56
|
+
redirect_error_response :login_required, "login required"
|
57
|
+
redirect_error_response :two_factor_need_authentication, "2nd factor required"
|
58
|
+
redirect_error_response :two_factor_not_setup, "2nd factor not setup"
|
59
|
+
redirect_error_response :sms_already_setup, "SMS codes already setup"
|
60
|
+
redirect_error_response :sms_needs_confirmation, "SMS phone number needs confirmation"
|
61
|
+
end
|
62
|
+
|
63
|
+
get :sms_confirm, "View SMS phone number confirmation page" do
|
64
|
+
html_response "SMS confirmation form"
|
65
|
+
redirect_error_response :login_required, "login required"
|
66
|
+
redirect_error_response :two_factor_need_authentication, "2nd factor required"
|
67
|
+
redirect_error_response :two_factor_not_setup, "2nd factor not setup"
|
68
|
+
redirect_error_response :sms_already_setup, "SMS codes already setup"
|
69
|
+
end
|
70
|
+
|
71
|
+
post :sms_confirm, "Confirm SMS phone number" do
|
72
|
+
param :sms_code, "SMS code", type: :string, required: true
|
73
|
+
|
74
|
+
success_response "SMS phone number successfully confirmed"
|
75
|
+
error_response :invalid_key, "invalid SMS code"
|
76
|
+
redirect_error_response :login_required, "login required"
|
77
|
+
redirect_error_response :two_factor_need_authentication, "2nd factor required"
|
78
|
+
redirect_error_response :two_factor_not_setup, "2nd factor not setup"
|
79
|
+
redirect_error_response :sms_already_setup, "SMS codes already setup"
|
80
|
+
end
|
81
|
+
|
82
|
+
get :sms_disable, "View SMS disable page" do
|
83
|
+
html_response "SMS disable form"
|
84
|
+
redirect_error_response :login_required, "login required"
|
85
|
+
redirect_error_response :two_factor_need_authentication, "2nd factor required"
|
86
|
+
redirect_error_response :two_factor_not_setup, "SMS codes not setup"
|
87
|
+
end
|
88
|
+
|
89
|
+
post :sms_disable, "Disable SMS codes" do
|
90
|
+
param :password, "Current account password", type: :password, required: true if rodauth.two_factor_modifications_require_password?
|
91
|
+
|
92
|
+
success_response "SMS codes successfully disabled"
|
93
|
+
redirect_error_response :login_required, "login required"
|
94
|
+
redirect_error_response :two_factor_need_authentication, "2nd factor required"
|
95
|
+
redirect_error_response :two_factor_not_setup, "SMS codes not setup"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Rodauth
|
2
|
+
class OpenAPI
|
3
|
+
Routes.define :two_factor_base, "Two Factor Base" do
|
4
|
+
route (json ? :post : :get), :two_factor_manage, "View two factor management page" do
|
5
|
+
if json
|
6
|
+
json_response({ "setup_links": ["..."], "remove_links": ["..."] })
|
7
|
+
else
|
8
|
+
html_response "two factor management page"
|
9
|
+
end
|
10
|
+
redirect_error_response :login_required, "login required"
|
11
|
+
redirect_error_response :two_factor_need_authentication, "2nd factor required"
|
12
|
+
end
|
13
|
+
|
14
|
+
route (json ? :post : :get), :two_factor_auth, "View two factor authentication page" do
|
15
|
+
if json
|
16
|
+
json_response({ "auth_links": ["..."] })
|
17
|
+
else
|
18
|
+
html_response "two factor authentication page"
|
19
|
+
end
|
20
|
+
redirect_error_response :login_required, "login required"
|
21
|
+
redirect_error_response :two_factor_not_setup, "2nd factor not setup"
|
22
|
+
redirect_error_response :two_factor_already_authenticated, "2nd factor already authenticated"
|
23
|
+
end
|
24
|
+
|
25
|
+
get :two_factor_disable, "View two factor disable page" do
|
26
|
+
html_response "two factor disable page"
|
27
|
+
redirect_error_response :two_factor_not_setup, "2nd factor not setup"
|
28
|
+
redirect_error_response :login_required, "login required"
|
29
|
+
redirect_error_response :two_factor_need_authentication, "2nd factor required"
|
30
|
+
end
|
31
|
+
|
32
|
+
post :two_factor_disable, "Disable two factor authentication" do
|
33
|
+
param :password, "Current account password", type: :password, required: true if rodauth.two_factor_modifications_require_password?
|
34
|
+
|
35
|
+
success_response "2nd factor successfully disabled"
|
36
|
+
error_response :invalid_password, "invalid password" if rodauth.two_factor_modifications_require_password?
|
37
|
+
redirect_error_response :two_factor_not_setup, "2nd factor not setup"
|
38
|
+
redirect_error_response :login_required, "login required"
|
39
|
+
redirect_error_response :two_factor_need_authentication, "2nd factor required"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Rodauth
|
2
|
+
class OpenAPI
|
3
|
+
Routes.define :verify_account, "Verify Account" do
|
4
|
+
get :verify_account, "View account verification page" do
|
5
|
+
param :verify_account_key, "Account verification token", type: :token, required: true
|
6
|
+
|
7
|
+
response 302, "token stored in session"
|
8
|
+
if feature?(:webauthn_verify_account)
|
9
|
+
html_response "WebAuthn setup form"
|
10
|
+
else
|
11
|
+
html_response "account verification form"
|
12
|
+
end
|
13
|
+
redirect_error_response "invalid verify account key"
|
14
|
+
end
|
15
|
+
|
16
|
+
post :verify_account, "Perform account verification" do
|
17
|
+
if json && feature?(:webauthn_verify_account)
|
18
|
+
description <<~MARKDOWN
|
19
|
+
In JSON mode, if WebAuthn credential data wasn't provided, input credential params will be returned in the response:
|
20
|
+
```json
|
21
|
+
{
|
22
|
+
"#{rodauth.webauthn_setup_param}": { ... },
|
23
|
+
"#{rodauth.webauthn_setup_challenge_param}": "...",
|
24
|
+
"#{rodauth.webauthn_setup_challenge_hmac_param}": "..."
|
25
|
+
}
|
26
|
+
```
|
27
|
+
MARKDOWN
|
28
|
+
end
|
29
|
+
|
30
|
+
param :verify_account_key, "Account verification token", type: :token, required: json
|
31
|
+
param :password, "Password to set", type: :password, required: true if rodauth.verify_account_set_password?
|
32
|
+
param :password_confirm, "Password confirmation", type: :password, required: true if rodauth.verify_account_set_password? && rodauth.require_password_confirmation?
|
33
|
+
if feature?(:webauthn_verify_account)
|
34
|
+
param :webauthn_setup, "WebAuthn credential data", type: :object, required: true
|
35
|
+
param :webauthn_setup_challenge, "WebAuthn credential challenge", type: :string, required: true
|
36
|
+
param :webauthn_setup_challenge_hmac, "WebAuthn credential challenge HMAC", type: :string, required: true
|
37
|
+
end
|
38
|
+
|
39
|
+
success_response "successful account verification"
|
40
|
+
redirect_error_response :invalid_key, "missing or invalid token"
|
41
|
+
error_response :invalid_field, "invalid password" if rodauth.verify_account_set_password?
|
42
|
+
error_response :unmatched_field, "passwords do not match" if rodauth.verify_account_set_password? && rodauth.require_password_confirmation?
|
43
|
+
error_response :invalid_field, "invalid WebAuthn credential data"
|
44
|
+
end
|
45
|
+
|
46
|
+
get :verify_account_resend, "Resend account verification email page" do
|
47
|
+
html_response "resend account verification form"
|
48
|
+
end
|
49
|
+
|
50
|
+
post :verify_account_resend, "Perform resending account verification email" do
|
51
|
+
param :login, "Email address for the account", type: :email, required: true
|
52
|
+
|
53
|
+
success_response "successful resend"
|
54
|
+
redirect_error_response "email recently sent"
|
55
|
+
error_response :no_matching_login, "no matching login"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Rodauth
|
2
|
+
class OpenAPI
|
3
|
+
Routes.define :verify_login_change, "Verify Login Change" do
|
4
|
+
get :verify_login_change, "View login change verification page" do
|
5
|
+
param :verify_login_change_key, "Login change verification token", type: :token, required: true
|
6
|
+
|
7
|
+
response 302, "token stored in session"
|
8
|
+
html_response "login change verification form"
|
9
|
+
redirect_error_response "invalid verify login change key"
|
10
|
+
end
|
11
|
+
|
12
|
+
post :verify_login_change, "Perform login change verification" do
|
13
|
+
param :verify_login_change_key, "Login change verification token", type: :token, required: json
|
14
|
+
|
15
|
+
success_response "successfully verified login change"
|
16
|
+
redirect_error_response :invalid_key, "invalid verify login change key"
|
17
|
+
redirect_error_response :invalid_key, "already an account with this login"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module Rodauth
|
2
|
+
class OpenAPI
|
3
|
+
Routes.define :webauthn, "WebAuthn" do
|
4
|
+
get :webauthn_auth, "View WebAuthn authentication page" do
|
5
|
+
html_response "WebAuthn authentication form"
|
6
|
+
redirect_error_response :login_required, "login required"
|
7
|
+
redirect_error_response :two_factor_already_authenticated, "already authenticated via WebAuthn"
|
8
|
+
redirect_error_response :webauthn_not_setup, "WebAuthn not setup"
|
9
|
+
end
|
10
|
+
|
11
|
+
post :webauthn_auth, "Perform WebAuthn authentication" do
|
12
|
+
if json
|
13
|
+
description <<~MARKDOWN
|
14
|
+
In JSON mode, if credential data wasn't provided, input credential params will be returned in the response:
|
15
|
+
```json
|
16
|
+
{
|
17
|
+
"#{rodauth.webauthn_auth_param}": { ... },
|
18
|
+
"#{rodauth.webauthn_auth_challenge_param}": "...",
|
19
|
+
"#{rodauth.webauthn_auth_challenge_hmac_param}": "..."
|
20
|
+
}
|
21
|
+
```
|
22
|
+
MARKDOWN
|
23
|
+
end
|
24
|
+
|
25
|
+
param :webauthn_auth, "Credential data", type: :object, required: true
|
26
|
+
param :webauthn_auth_challenge, "Credential challenge", type: :string, required: true
|
27
|
+
param :webauthn_auth_challenge_hmac, "Credential challenge HMAC", type: :string, required: true
|
28
|
+
|
29
|
+
success_response "successfully authenticated via WebAuthn"
|
30
|
+
error_response :invalid_key, "invalid credential data"
|
31
|
+
error_response :invalid_field, "invalid credential sign count"
|
32
|
+
redirect_error_response :login_required, "login required"
|
33
|
+
redirect_error_response :two_factor_already_authenticated, "already authenticated via WebAuthn"
|
34
|
+
redirect_error_response :webauthn_not_setup, "WebAuthn not setup"
|
35
|
+
end
|
36
|
+
|
37
|
+
get :webauthn_setup, "View WebAuthn setup page" do
|
38
|
+
html_response "WebAuthn setup form"
|
39
|
+
redirect_error_response :login_required, "login required"
|
40
|
+
redirect_error_response :two_factor_need_authentication, "2nd factor required"
|
41
|
+
end
|
42
|
+
|
43
|
+
post :webauthn_setup, "Perform WebAuthn setup" do
|
44
|
+
if json
|
45
|
+
description <<~MARKDOWN
|
46
|
+
In JSON mode, if credential data wasn't provided, input credential params will be returned in the response:
|
47
|
+
```json
|
48
|
+
{
|
49
|
+
"#{rodauth.webauthn_setup_param}": { ... },
|
50
|
+
"#{rodauth.webauthn_setup_challenge_param}": "...",
|
51
|
+
"#{rodauth.webauthn_setup_challenge_hmac_param}": "..."
|
52
|
+
}
|
53
|
+
```
|
54
|
+
MARKDOWN
|
55
|
+
end
|
56
|
+
|
57
|
+
param :webauthn_setup, "Credential data", type: :object, required: true
|
58
|
+
param :webauthn_setup_challenge, "Credential challenge", type: :string, required: true
|
59
|
+
param :webauthn_setup_challenge_hmac, "Credential challenge HMAC", type: :string, required: true
|
60
|
+
param :password, "Current account password", type: :password, required: true if rodauth.two_factor_modifications_require_password?
|
61
|
+
|
62
|
+
success_response "WebAuthn successfully setup"
|
63
|
+
error_response :invalid_field, "invalid credential data"
|
64
|
+
error_response :invalid_field, "duplicate credential ID"
|
65
|
+
error_response :invalid_password, "invalid password" if rodauth.two_factor_modifications_require_password?
|
66
|
+
redirect_error_response :login_required, "login required"
|
67
|
+
redirect_error_response :two_factor_need_authentication, "2nd factor required"
|
68
|
+
end
|
69
|
+
|
70
|
+
get :webauthn_remove, "View WebAuthn authenticator removal page" do
|
71
|
+
html_response "WebAuthn authenticator removal form"
|
72
|
+
redirect_error_response :login_required, "login required"
|
73
|
+
redirect_error_response :two_factor_need_authentication, "2nd factor required"
|
74
|
+
redirect_error_response :webauthn_not_setup, "WebAuthn not setup"
|
75
|
+
end
|
76
|
+
|
77
|
+
post :webauthn_remove, "Remove WebAuthn authenticator" do
|
78
|
+
if json
|
79
|
+
description <<~MARKDOWN
|
80
|
+
In JSON mode, if credential ID wasn't provided, last usage of every credential will be returned in the response:
|
81
|
+
```json
|
82
|
+
{
|
83
|
+
"#{rodauth.webauthn_remove_param}": {
|
84
|
+
"abc123": "2024-10-10 00:37:10 UTC"
|
85
|
+
}
|
86
|
+
}
|
87
|
+
```
|
88
|
+
MARKDOWN
|
89
|
+
end
|
90
|
+
|
91
|
+
param :webauthn_remove, "Credential ID", type: :string, required: true
|
92
|
+
param :password, "Current account password", type: :password, required: true if rodauth.two_factor_modifications_require_password?
|
93
|
+
|
94
|
+
success_response "WebAuthn authenticator successfully removed"
|
95
|
+
error_response :invalid_field, "invalid credential ID"
|
96
|
+
error_response :invalid_password, "invalid password" if rodauth.two_factor_modifications_require_password?
|
97
|
+
redirect_error_response :login_required, "login required"
|
98
|
+
redirect_error_response :two_factor_need_authentication, "2nd factor required"
|
99
|
+
redirect_error_response :webauthn_not_setup, "WebAuthn not setup"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Rodauth
|
2
|
+
class OpenAPI
|
3
|
+
Routes.define :webauthn_login, "WebAuthn Login" do
|
4
|
+
post :webauthn_login, "Perform WebAuthn login" do
|
5
|
+
if json
|
6
|
+
description <<~MARKDOWN
|
7
|
+
In JSON mode, if credential data wasn't provided, input credential params will be returned in the response:
|
8
|
+
```json
|
9
|
+
{
|
10
|
+
"#{rodauth.webauthn_auth_param}": { ... },
|
11
|
+
"#{rodauth.webauthn_auth_challenge_param}": "...",
|
12
|
+
"#{rodauth.webauthn_auth_challenge_hmac_param}": "..."
|
13
|
+
}
|
14
|
+
```
|
15
|
+
MARKDOWN
|
16
|
+
end
|
17
|
+
|
18
|
+
param :login, "Account email", type: :email, required: !feature?(:webauthn_autofill)
|
19
|
+
param :webauthn_auth, "Credential data", type: :object, required: true
|
20
|
+
|
21
|
+
success_response "successfully logged in via WebAuthn"
|
22
|
+
error_response :invalid_key, "invalid credential data"
|
23
|
+
error_response :invalid_field, "invalid credential sign count"
|
24
|
+
error_response :invalid_field, "credential not found" if feature?(:webauthn_autofill)
|
25
|
+
error_response :no_matching_login, "no matching login"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
module Rodauth
|
2
|
+
class OpenAPI
|
3
|
+
class Routes
|
4
|
+
FEATURES = {}
|
5
|
+
|
6
|
+
def self.define(name, tag, &definition)
|
7
|
+
FEATURES[name] = -> do
|
8
|
+
section tag, name
|
9
|
+
instance_exec(&definition)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :data, :rodauth, :json
|
14
|
+
|
15
|
+
def initialize(data, rodauth:, json:)
|
16
|
+
@data = data
|
17
|
+
@rodauth = rodauth
|
18
|
+
@json = json
|
19
|
+
end
|
20
|
+
|
21
|
+
def section(tag, name)
|
22
|
+
data[:tags] << {
|
23
|
+
name: tag,
|
24
|
+
externalDocs: {
|
25
|
+
description: "Feature documentation",
|
26
|
+
url: "http://rodauth.jeremyevans.net/rdoc/files/doc/#{name}_rdoc.html"
|
27
|
+
}
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
def get(...)
|
32
|
+
route(:get, ...) unless json
|
33
|
+
end
|
34
|
+
|
35
|
+
def post(...)
|
36
|
+
route(:post, ...)
|
37
|
+
end
|
38
|
+
|
39
|
+
def route(verb, name, summary, &block)
|
40
|
+
path = rodauth.send(:"#{name}_path")
|
41
|
+
return if path.nil?
|
42
|
+
|
43
|
+
tag = data[:tags].last[:name]
|
44
|
+
|
45
|
+
data[:paths][path] ||= {}
|
46
|
+
data[:paths][path][verb] = {
|
47
|
+
tags: [tag],
|
48
|
+
summary: summary,
|
49
|
+
responses: {},
|
50
|
+
parameters: [],
|
51
|
+
}
|
52
|
+
|
53
|
+
instance_exec(&block)
|
54
|
+
end
|
55
|
+
|
56
|
+
def description(text)
|
57
|
+
data[:paths].values.last.values.last[:description] = text
|
58
|
+
end
|
59
|
+
|
60
|
+
def param(name, description, type:, example: nil, required: false, enum: nil)
|
61
|
+
example ||= case type
|
62
|
+
when :email then "user@example.com"
|
63
|
+
when :password then "secret123"
|
64
|
+
when :boolean then "true"
|
65
|
+
when :token then "{account_id}#{rodauth.token_separator}{key_hmac}"
|
66
|
+
end
|
67
|
+
|
68
|
+
parameters = data[:paths].values.last.values.last[:parameters]
|
69
|
+
parameters << {
|
70
|
+
name: rodauth.send(:"#{name}_param"),
|
71
|
+
in: "query",
|
72
|
+
description: description,
|
73
|
+
required: required,
|
74
|
+
style: "form",
|
75
|
+
example: example,
|
76
|
+
schema: { type: type == :boolean ? "boolean" : "string", enum: enum }.compact,
|
77
|
+
}.compact
|
78
|
+
end
|
79
|
+
|
80
|
+
def html_response(description)
|
81
|
+
response(200, description)
|
82
|
+
end
|
83
|
+
|
84
|
+
def json_response(description = "", example)
|
85
|
+
response(200, description, content: { "application/json" => { example: example } })
|
86
|
+
end
|
87
|
+
|
88
|
+
def success_response(description)
|
89
|
+
status = json ? 200 : 302
|
90
|
+
response(status, description)
|
91
|
+
end
|
92
|
+
|
93
|
+
def error_response(status = nil, description)
|
94
|
+
status = rodauth.send(:"#{status}_error_status") if status.is_a?(Symbol)
|
95
|
+
if json && (status.nil? || !rodauth.json_response_custom_error_status?)
|
96
|
+
status = rodauth.json_response_error_status
|
97
|
+
end
|
98
|
+
response(status, description)
|
99
|
+
end
|
100
|
+
|
101
|
+
def redirect_error_response(name = nil, description)
|
102
|
+
status = if json
|
103
|
+
if rodauth.json_response_custom_error_status? && name
|
104
|
+
rodauth.send(:"#{name}_error_status")
|
105
|
+
else
|
106
|
+
rodauth.json_response_error_status
|
107
|
+
end
|
108
|
+
else
|
109
|
+
302
|
110
|
+
end
|
111
|
+
response(status, description)
|
112
|
+
end
|
113
|
+
|
114
|
+
def response(status, description = "", **fields)
|
115
|
+
responses = data[:paths].values.last.values.last[:responses]
|
116
|
+
if responses[status]
|
117
|
+
responses[status][:description] = [responses[status][:description], description].join(", ")
|
118
|
+
else
|
119
|
+
responses[status] = { description: description }
|
120
|
+
end
|
121
|
+
responses[status].merge!(fields)
|
122
|
+
end
|
123
|
+
|
124
|
+
def feature?(name)
|
125
|
+
rodauth.features.include?(name)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require "yaml"
|
2
|
+
require "json"
|
3
|
+
require "rodauth/version"
|
4
|
+
require "rodauth/openapi/routes"
|
5
|
+
|
6
|
+
module Rodauth
|
7
|
+
class OpenAPI
|
8
|
+
DOCS_URL = "https://rodauth.jeremyevans.net/documentation.html"
|
9
|
+
SPEC_VERSION = "3.0.1"
|
10
|
+
|
11
|
+
def initialize(auth_class, json: nil, password: true)
|
12
|
+
@auth_class = auth_class
|
13
|
+
@json = json
|
14
|
+
@password = password
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_yaml
|
18
|
+
YAML.dump(JSON.parse(JSON.generate(generate))).lines[1..-1].join
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_json
|
22
|
+
JSON.pretty_generate(generate)
|
23
|
+
end
|
24
|
+
|
25
|
+
def generate
|
26
|
+
data = {
|
27
|
+
openapi: SPEC_VERSION,
|
28
|
+
info: {
|
29
|
+
title: "Rodauth",
|
30
|
+
description: "This lists all the endpoints provided by Rodauth features.",
|
31
|
+
version: Rodauth::VERSION,
|
32
|
+
},
|
33
|
+
externalDocs: {
|
34
|
+
description: "Rodauth documentation",
|
35
|
+
url: DOCS_URL,
|
36
|
+
},
|
37
|
+
tags: [],
|
38
|
+
paths: {},
|
39
|
+
}
|
40
|
+
|
41
|
+
rodauth.features.each do |feature|
|
42
|
+
begin
|
43
|
+
require "rodauth/openapi/routes/#{feature}"
|
44
|
+
rescue LoadError
|
45
|
+
next
|
46
|
+
end
|
47
|
+
|
48
|
+
routes = Routes.new(data, rodauth: rodauth, json: json?)
|
49
|
+
routes.instance_exec(&Routes::FEATURES[feature])
|
50
|
+
end
|
51
|
+
|
52
|
+
# remove tags that don't have any routes
|
53
|
+
all_tags = data[:paths].values.flat_map(&:values).flat_map { |route| route[:tags] }.uniq
|
54
|
+
data[:tags].select! { |tag| all_tags.include?(tag[:name]) }
|
55
|
+
|
56
|
+
data
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def json?
|
62
|
+
@json || rodauth.only_json?
|
63
|
+
end
|
64
|
+
|
65
|
+
def rodauth
|
66
|
+
rodauth = @auth_class.new(scope)
|
67
|
+
rodauth.instance_variable_set(:@has_password, password?)
|
68
|
+
rodauth
|
69
|
+
end
|
70
|
+
|
71
|
+
def password?
|
72
|
+
@password
|
73
|
+
end
|
74
|
+
|
75
|
+
def scope
|
76
|
+
@auth_class.roda_class.new({ "rack.session" => {} })
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require "rodauth/openapi"
|
@@ -0,0 +1,18 @@
|
|
1
|
+
Gem::Specification.new do |spec|
|
2
|
+
spec.name = "rodauth-openapi"
|
3
|
+
spec.version = "0.1.0"
|
4
|
+
spec.authors = ["Janko Marohnić"]
|
5
|
+
spec.email = ["janko.marohnic@gmail.com"]
|
6
|
+
|
7
|
+
spec.summary = %q{Allows dynamically generating OpenAPI documentation based on Rodauth configuration.}
|
8
|
+
spec.description = spec.summary
|
9
|
+
spec.homepage = "https://github.com/janko/rodauth-openapi"
|
10
|
+
spec.license = "MIT"
|
11
|
+
|
12
|
+
spec.required_ruby_version = ">= 2.5"
|
13
|
+
|
14
|
+
spec.files = Dir["README.md", "LICENSE.txt", "lib/**/*", "*.gemspec"]
|
15
|
+
spec.require_paths = ["lib"]
|
16
|
+
|
17
|
+
spec.add_dependency "rodauth"
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rodauth-openapi
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Janko Marohnić
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-10-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rodauth
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description: Allows dynamically generating OpenAPI documentation based on Rodauth
|
28
|
+
configuration.
|
29
|
+
email:
|
30
|
+
- janko.marohnic@gmail.com
|
31
|
+
executables: []
|
32
|
+
extensions: []
|
33
|
+
extra_rdoc_files: []
|
34
|
+
files:
|
35
|
+
- LICENSE.txt
|
36
|
+
- README.md
|
37
|
+
- lib/generators/rodauth/openapi_generator.rb
|
38
|
+
- lib/rodauth-openapi.rb
|
39
|
+
- lib/rodauth/openapi.rb
|
40
|
+
- lib/rodauth/openapi/routes.rb
|
41
|
+
- lib/rodauth/openapi/routes/change_login.rb
|
42
|
+
- lib/rodauth/openapi/routes/change_password.rb
|
43
|
+
- lib/rodauth/openapi/routes/close_account.rb
|
44
|
+
- lib/rodauth/openapi/routes/confirm_account.rb
|
45
|
+
- lib/rodauth/openapi/routes/create_account.rb
|
46
|
+
- lib/rodauth/openapi/routes/email_auth.rb
|
47
|
+
- lib/rodauth/openapi/routes/jwt_refresh.rb
|
48
|
+
- lib/rodauth/openapi/routes/lockout.rb
|
49
|
+
- lib/rodauth/openapi/routes/login.rb
|
50
|
+
- lib/rodauth/openapi/routes/logout.rb
|
51
|
+
- lib/rodauth/openapi/routes/otp.rb
|
52
|
+
- lib/rodauth/openapi/routes/otp_unlock.rb
|
53
|
+
- lib/rodauth/openapi/routes/recovery_codes.rb
|
54
|
+
- lib/rodauth/openapi/routes/remember.rb
|
55
|
+
- lib/rodauth/openapi/routes/reset_password.rb
|
56
|
+
- lib/rodauth/openapi/routes/sms_codes.rb
|
57
|
+
- lib/rodauth/openapi/routes/two_factor_base.rb
|
58
|
+
- lib/rodauth/openapi/routes/verify_account.rb
|
59
|
+
- lib/rodauth/openapi/routes/verify_login_change.rb
|
60
|
+
- lib/rodauth/openapi/routes/webauthn.rb
|
61
|
+
- lib/rodauth/openapi/routes/webauthn_login.rb
|
62
|
+
- rodauth-openapi.gemspec
|
63
|
+
homepage: https://github.com/janko/rodauth-openapi
|
64
|
+
licenses:
|
65
|
+
- MIT
|
66
|
+
metadata: {}
|
67
|
+
post_install_message:
|
68
|
+
rdoc_options: []
|
69
|
+
require_paths:
|
70
|
+
- lib
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '2.5'
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
requirements: []
|
82
|
+
rubygems_version: 3.5.11
|
83
|
+
signing_key:
|
84
|
+
specification_version: 4
|
85
|
+
summary: Allows dynamically generating OpenAPI documentation based on Rodauth configuration.
|
86
|
+
test_files: []
|