rodauth-openapi 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|