sign_in_service 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +40 -0
- data/.ruby-version +1 -0
- data/Gemfile +25 -0
- data/Gemfile.lock +115 -0
- data/LICENSE.txt +16 -0
- data/README.md +88 -0
- data/Rakefile +12 -0
- data/docs/endpoints/authorize.md +62 -0
- data/docs/endpoints/logout.md +31 -0
- data/docs/endpoints/refresh.md +46 -0
- data/docs/endpoints/revoke_all_sessions.md +22 -0
- data/docs/endpoints/revoke_token.md +23 -0
- data/docs/endpoints/token.md +48 -0
- data/lib/sign_in_service/client/authorize.rb +83 -0
- data/lib/sign_in_service/client/config.rb +41 -0
- data/lib/sign_in_service/client/session.rb +99 -0
- data/lib/sign_in_service/client.rb +68 -0
- data/lib/sign_in_service/error.rb +113 -0
- data/lib/sign_in_service/response/raise_error.rb +16 -0
- data/lib/sign_in_service/sts/config.rb +19 -0
- data/lib/sign_in_service/sts/token.rb +54 -0
- data/lib/sign_in_service/sts.rb +64 -0
- data/lib/sign_in_service/version.rb +5 -0
- data/lib/sign_in_service.rb +4 -0
- data/sign_in_service.gemspec +36 -0
- metadata +99 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 468897d228e6fcb4e60136303880ae7cc81ef29f134a270b5935e136971f4fd0
|
4
|
+
data.tar.gz: 952139fe857083c0765a3db19f50981d124abefefd2fb2153e3081d60c7a3a06
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 85c5094f9a5366f63ebb4d02d1a782ac68e00be274ad46ee434f69332eb05ad11c8894cdb012d7aeccccebb5dac52b095f2ad6c59d2265799aad012f2a131742
|
7
|
+
data.tar.gz: a47ddae9310aa566e650f4219409fa3f5a7d9a3f3dc194078e9aca22bfbe4561bb6528fdd2120efb1bc547d2ed3d59747e9163190c9f28309a629419cb9aa955
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require:
|
2
|
+
- rubocop-rspec
|
3
|
+
- rubocop-rake
|
4
|
+
|
5
|
+
AllCops:
|
6
|
+
NewCops: disable
|
7
|
+
|
8
|
+
Metrics/MethodLength:
|
9
|
+
Max: 20
|
10
|
+
|
11
|
+
Style/Documentation:
|
12
|
+
Enabled: false
|
13
|
+
|
14
|
+
Metrics/CyclomaticComplexity:
|
15
|
+
Enabled: false
|
16
|
+
|
17
|
+
Metrics/BlockLength:
|
18
|
+
Enabled: false
|
19
|
+
|
20
|
+
Metrics/AbcSize:
|
21
|
+
Enabled: false
|
22
|
+
|
23
|
+
Layout/MultilineMethodCallIndentation:
|
24
|
+
EnforcedStyle: indented
|
25
|
+
|
26
|
+
# RSpec Cops
|
27
|
+
RSpec/ExampleLength:
|
28
|
+
Enabled: false
|
29
|
+
|
30
|
+
RSpec/MultipleMemoizedHelpers:
|
31
|
+
Enabled: false
|
32
|
+
|
33
|
+
RSpec/NestedGroups:
|
34
|
+
Enabled: false
|
35
|
+
|
36
|
+
RSpec/DescribeClass:
|
37
|
+
Enabled: false
|
38
|
+
|
39
|
+
RSpec/MultipleExpectations:
|
40
|
+
Enabled: false
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
3.3
|
data/Gemfile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
git_source(:github) { |repo_name| "https://github.com/#{repo_name}.git" }
|
4
|
+
|
5
|
+
source 'https://rubygems.org'
|
6
|
+
|
7
|
+
gem 'faraday'
|
8
|
+
gem 'jwt'
|
9
|
+
gem 'rake'
|
10
|
+
|
11
|
+
group :test do
|
12
|
+
gem 'rack-test'
|
13
|
+
gem 'rspec'
|
14
|
+
end
|
15
|
+
|
16
|
+
group :development, :test do
|
17
|
+
gem 'pry-byebug'
|
18
|
+
gem 'rubocop', require: false
|
19
|
+
gem 'rubocop-rake', require: false
|
20
|
+
gem 'rubocop-rspec', require: false
|
21
|
+
gem 'vcr', require: false
|
22
|
+
gem 'webmock', require: false
|
23
|
+
end
|
24
|
+
|
25
|
+
gemspec
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
sign_in_service (0.4.0)
|
5
|
+
faraday (~> 2.7)
|
6
|
+
jwt (~> 2.8)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
addressable (2.8.6)
|
12
|
+
public_suffix (>= 2.0.2, < 6.0)
|
13
|
+
ast (2.4.2)
|
14
|
+
base64 (0.2.0)
|
15
|
+
bigdecimal (3.1.8)
|
16
|
+
byebug (11.1.3)
|
17
|
+
coderay (1.1.3)
|
18
|
+
crack (1.0.0)
|
19
|
+
bigdecimal
|
20
|
+
rexml
|
21
|
+
diff-lcs (1.5.1)
|
22
|
+
faraday (2.11.0)
|
23
|
+
faraday-net_http (>= 2.0, < 3.4)
|
24
|
+
logger
|
25
|
+
faraday-net_http (3.3.0)
|
26
|
+
net-http
|
27
|
+
hashdiff (1.1.0)
|
28
|
+
json (2.7.2)
|
29
|
+
jwt (2.8.2)
|
30
|
+
base64
|
31
|
+
language_server-protocol (3.17.0.3)
|
32
|
+
logger (1.6.0)
|
33
|
+
method_source (1.0.0)
|
34
|
+
net-http (0.4.1)
|
35
|
+
uri
|
36
|
+
parallel (1.25.1)
|
37
|
+
parser (3.3.4.0)
|
38
|
+
ast (~> 2.4.1)
|
39
|
+
racc
|
40
|
+
pry (0.14.2)
|
41
|
+
coderay (~> 1.1)
|
42
|
+
method_source (~> 1.0)
|
43
|
+
pry-byebug (3.10.1)
|
44
|
+
byebug (~> 11.0)
|
45
|
+
pry (>= 0.13, < 0.15)
|
46
|
+
public_suffix (5.0.5)
|
47
|
+
racc (1.8.1)
|
48
|
+
rack (3.0.9.1)
|
49
|
+
rack-test (2.1.0)
|
50
|
+
rack (>= 1.3)
|
51
|
+
rainbow (3.1.1)
|
52
|
+
rake (13.2.1)
|
53
|
+
regexp_parser (2.9.2)
|
54
|
+
rexml (3.3.6)
|
55
|
+
strscan
|
56
|
+
rspec (3.13.0)
|
57
|
+
rspec-core (~> 3.13.0)
|
58
|
+
rspec-expectations (~> 3.13.0)
|
59
|
+
rspec-mocks (~> 3.13.0)
|
60
|
+
rspec-core (3.13.0)
|
61
|
+
rspec-support (~> 3.13.0)
|
62
|
+
rspec-expectations (3.13.0)
|
63
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
64
|
+
rspec-support (~> 3.13.0)
|
65
|
+
rspec-mocks (3.13.0)
|
66
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
67
|
+
rspec-support (~> 3.13.0)
|
68
|
+
rspec-support (3.13.0)
|
69
|
+
rubocop (1.65.1)
|
70
|
+
json (~> 2.3)
|
71
|
+
language_server-protocol (>= 3.17.0)
|
72
|
+
parallel (~> 1.10)
|
73
|
+
parser (>= 3.3.0.2)
|
74
|
+
rainbow (>= 2.2.2, < 4.0)
|
75
|
+
regexp_parser (>= 2.4, < 3.0)
|
76
|
+
rexml (>= 3.2.5, < 4.0)
|
77
|
+
rubocop-ast (>= 1.31.1, < 2.0)
|
78
|
+
ruby-progressbar (~> 1.7)
|
79
|
+
unicode-display_width (>= 2.4.0, < 3.0)
|
80
|
+
rubocop-ast (1.32.0)
|
81
|
+
parser (>= 3.3.1.0)
|
82
|
+
rubocop-rake (0.6.0)
|
83
|
+
rubocop (~> 1.0)
|
84
|
+
rubocop-rspec (3.0.4)
|
85
|
+
rubocop (~> 1.61)
|
86
|
+
ruby-progressbar (1.13.0)
|
87
|
+
strscan (3.1.0)
|
88
|
+
unicode-display_width (2.5.0)
|
89
|
+
uri (0.13.0)
|
90
|
+
vcr (6.2.0)
|
91
|
+
webmock (3.23.1)
|
92
|
+
addressable (>= 2.8.0)
|
93
|
+
crack (>= 0.3.2)
|
94
|
+
hashdiff (>= 0.4.0, < 2.0.0)
|
95
|
+
|
96
|
+
PLATFORMS
|
97
|
+
ruby
|
98
|
+
x86_64-linux
|
99
|
+
|
100
|
+
DEPENDENCIES
|
101
|
+
faraday
|
102
|
+
jwt
|
103
|
+
pry-byebug
|
104
|
+
rack-test
|
105
|
+
rake
|
106
|
+
rspec
|
107
|
+
rubocop
|
108
|
+
rubocop-rake
|
109
|
+
rubocop-rspec
|
110
|
+
sign_in_service!
|
111
|
+
vcr
|
112
|
+
webmock
|
113
|
+
|
114
|
+
BUNDLED WITH
|
115
|
+
2.4.9
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
As a work of the United States Government, this project is in the public domain within the United States.
|
2
|
+
|
3
|
+
Additionally, we waive copyright and related rights in the work worldwide through the CC0 1.0 Universal public domain dedication.
|
4
|
+
|
5
|
+
CC0 1.0 Universal Summary
|
6
|
+
This is a human-readable summary of the Legal Code (read the full text).
|
7
|
+
|
8
|
+
No Copyright
|
9
|
+
The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law.
|
10
|
+
|
11
|
+
You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission.
|
12
|
+
|
13
|
+
Other Information
|
14
|
+
In no way are the patent or trademark rights of any person affected by CC0, nor are the rights that other persons may have in the work or in how the work is used, such as publicity or privacy rights.
|
15
|
+
|
16
|
+
Unless expressly stated otherwise, the person who associated a work with this deed makes no warranties about the work, and disclaims liability for all uses of the work, to the fullest extent permitted by applicable law. When using or citing the work, you should not imply endorsement by the author or the affirmer.
|
data/README.md
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
Note: This repo is managed by the VA.gov Identity team. Please reference our main product page here for contact information and questions.
|
2
|
+
|
3
|
+
# SignInService Ruby Client
|
4
|
+
|
5
|
+
The SignInService Ruby client provides a simple and convenient way to interact with the SignInService API for handling OAuth flows.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add gem to your Gemfile
|
10
|
+
```ruby
|
11
|
+
gem 'sign_in_service', :git => 'git://github.com/department-of-veterans-affairs/sign-in-service-rb.git'
|
12
|
+
```
|
13
|
+
|
14
|
+
```bash
|
15
|
+
bundle install
|
16
|
+
```
|
17
|
+
## Client Configuration
|
18
|
+
|
19
|
+
Configure the SignInService client with your base URL, client ID, and authentication type in an initializer:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
require 'sign_in_service'
|
23
|
+
|
24
|
+
SignInService::Client.configure do |config|
|
25
|
+
config.base_url = 'https://your_sign_in_service_url'
|
26
|
+
config.client_id = 'your_client_id'
|
27
|
+
config.auth_type = :cookie # or :api
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
### Auth Types: Cookie vs API
|
32
|
+
The SignInService client supports two authentication types: Cookie and API.
|
33
|
+
|
34
|
+
#### Cookie Authentication
|
35
|
+
With Cookie authentication, tokens are returned in the `Set-Cookie` headers of the response. This approach is typically used in web applications where cookies can be stored and managed directly by the browser.
|
36
|
+
|
37
|
+
#### API Authentication
|
38
|
+
With API authentication, tokens are returned in the response body. This approach is typically used in non-web applications or scenarios where the application handles the tokens directly, such as mobile apps, desktop apps, or server-side scripts.
|
39
|
+
|
40
|
+
### Endpoints
|
41
|
+
|
42
|
+
#### Authorization
|
43
|
+
- [Authorize](docs/endpoints/authorize.md) - Initiate the OAuth flow
|
44
|
+
- [Token](docs/endpoints/token.md) - Exchange authorization code for session tokens
|
45
|
+
|
46
|
+
#### Session Management
|
47
|
+
- [Refresh](docs/endpoints/refresh.md) - Refresh session tokens.
|
48
|
+
- [Logout](docs/endpoints/logout.md) - Log out the user and revoke tokens.
|
49
|
+
- [Revoke Token](docs/endpoints/revoke_token.md) - Revoke a sessions tokens.
|
50
|
+
- [Revoke All Sessions](docs/endpoints/revoke_all_sessions.md) - Revoke all sessions associated with a user
|
51
|
+
|
52
|
+
|
53
|
+
## STS Configuration
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
|
57
|
+
SignInService::Sts.configure do |config|
|
58
|
+
config.base_url = 'https://api.va.gov' # Sign-in service URL
|
59
|
+
config.service_account_id = 'your_client_id'
|
60
|
+
config.issuer = 'your_client_secret',
|
61
|
+
config.scopes = ["https://api.va.gov/v0/some-path"]
|
62
|
+
config.user_attributes = ['icn'] # optional
|
63
|
+
config.private_key_path = 'path/to/private_key.pem'
|
64
|
+
end
|
65
|
+
```
|
66
|
+
|
67
|
+
Or create a new instance of the STS client with the configuration (overrides the global configuration):
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
sts_client = SignInService::Sts.new(
|
71
|
+
base_url: 'https://api.va.gov',
|
72
|
+
service_account_id: 'your_client_id',
|
73
|
+
issuer: 'your_client_secret',
|
74
|
+
scopes: ["https://api.va.gov/v0/some-path"],
|
75
|
+
user_attributes: ['icn'], # optional
|
76
|
+
private_key_path: 'path/to/private_key.pem'
|
77
|
+
)
|
78
|
+
```
|
79
|
+
|
80
|
+
#### Token - `/v0/sign_in/token`
|
81
|
+
|
82
|
+
When requesting a token you need to pass in a user_identifier to be used as the subject of the token. This can be an ICN, email, or any other unique identifier.
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
sts = SignInService::Sts.new(user_identifier: '1234567890') # And other configuration options not in the global configuration
|
86
|
+
|
87
|
+
token = sts.request_token
|
88
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
# Authorize
|
2
|
+
|
3
|
+
The Authorize Endpoint is responsible for initiating the OAuth 2.0 authorization flow. You can generate an authorization URL, or use the authorization endpoint.
|
4
|
+
|
5
|
+
## Generating Authorize URL
|
6
|
+
The `authorize_url` method is used to generate a URL.
|
7
|
+
|
8
|
+
### Parameters
|
9
|
+
|
10
|
+
- `type` (required): The CSP type.
|
11
|
+
- `acr` (required): Level of authentication requested.
|
12
|
+
- `code_challenge` (required): Used by SignInService to verify requests.
|
13
|
+
- `state` (optional): Optional string that can be returned in the callback.
|
14
|
+
|
15
|
+
### Example
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
client = SignInService::Client.new
|
19
|
+
|
20
|
+
type = "your_csp_type"
|
21
|
+
acr = "your_acr"
|
22
|
+
code_challenge = "your_code_challenge"
|
23
|
+
state = "your_state" # Optional
|
24
|
+
|
25
|
+
auth_url = client.authorize_uri(type: type, acr: acr, code_challenge: code_challenge, state: state)
|
26
|
+
```
|
27
|
+
|
28
|
+
## Handling Responses
|
29
|
+
The `authorize_uri` method returns an authorization URL as a string. You can use this URL to redirect users to the SignInService authorization page.
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
redirect_to uri
|
33
|
+
```
|
34
|
+
|
35
|
+
## Authorize Endpoint
|
36
|
+
The `authorize` method is used to interact with the Authorize Endpoint
|
37
|
+
|
38
|
+
### Parameters
|
39
|
+
- `type` (required): The CSP type.
|
40
|
+
- `acr` (required): Level of authentication requested.
|
41
|
+
- `code_challenge` (required): Used by SignInService to verify requests.
|
42
|
+
- `state` (optional): Optional string that can be returned in the callback.
|
43
|
+
|
44
|
+
### Example Usage
|
45
|
+
```ruby
|
46
|
+
client = SignInService::Client.new
|
47
|
+
|
48
|
+
# Required parameters
|
49
|
+
type = "your_csp_type"
|
50
|
+
acr = "your_acr"
|
51
|
+
code_challenge = "your_code_challenge"
|
52
|
+
state = "your_state" # Optional
|
53
|
+
|
54
|
+
response = client.authorize(type: type, acr: acr, code_challenge: code_challenge, state: state)
|
55
|
+
```
|
56
|
+
|
57
|
+
## Handling Responses
|
58
|
+
The `authorize` method returns a Faraday::Response object, which contains an HTML body used to redirect to the CSP
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
render response.body
|
62
|
+
```
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# Logout
|
2
|
+
The Logout Endpoint is responsible for logging out the user and revoking tokens.
|
3
|
+
## Usage
|
4
|
+
|
5
|
+
##### Parameters
|
6
|
+
|
7
|
+
- `access_token` (required): The access token for the associated user.
|
8
|
+
- `anti_csrf_token` (optional): An optional token for client-side CSRF protection.
|
9
|
+
|
10
|
+
##### Example Usage
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
client = SignInService::Client.new
|
14
|
+
|
15
|
+
access_token = "your_id_token_hint"
|
16
|
+
anti_csrf_token = "your_anti_csrf_token" # Optional
|
17
|
+
|
18
|
+
response = client.logout(access_token: access_token, anti_csrf_token: anti_csrf_token)
|
19
|
+
```
|
20
|
+
|
21
|
+
#### Handling Responses
|
22
|
+
The `logout` method returns a Faraday::Response object.
|
23
|
+
|
24
|
+
In the case of successful logout from login.gov you can expect an HTTP status code of 302. You will need to
|
25
|
+
redirect to the location header and expect a callback
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
redirect_to response.headers['location'] if response.status == 302
|
29
|
+
```
|
30
|
+
|
31
|
+
In the case of id.me you will receive a 200 with an empty body
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# Refresh Token
|
2
|
+
|
3
|
+
The Refresh Token Endpoint is responsible for refreshing session tokens.
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
### Parameters
|
8
|
+
- `refresh_token` (required): The refresh token associated with the user's session.
|
9
|
+
- `anti_csrf_token` (optional): An optional token for client-side CSRF protection.
|
10
|
+
|
11
|
+
### Example Usage
|
12
|
+
``` ruby
|
13
|
+
client = SignInService::Client.new
|
14
|
+
|
15
|
+
refresh_token = "your_refresh_token"
|
16
|
+
anti_csrf_token = "your_anti_csrf_token" # Optional
|
17
|
+
|
18
|
+
response = client.refresh_token(refresh_token: refresh_token, anti_csrf_token: anti_csrf_token)
|
19
|
+
```
|
20
|
+
|
21
|
+
## Handling Responses
|
22
|
+
The `refresh_token` method returns a Faraday::Response object.
|
23
|
+
|
24
|
+
### `:cookie` auth type
|
25
|
+
Tokens are returned in the `Set-Cookie` headers
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
response.headers['set-cookie']
|
29
|
+
|
30
|
+
# vagov_access_token=..., vagov_refresh_token=..., vagov_anti_csrf_token=..., vagov_info_token=...
|
31
|
+
```
|
32
|
+
|
33
|
+
### `:api` auth type
|
34
|
+
Tokens are returned in the response body
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
JSON.parse(response.body)
|
38
|
+
|
39
|
+
# {
|
40
|
+
# "data": {
|
41
|
+
# "access_token": "<accessTokenHash>", // JWT eyJhbGci0... etc
|
42
|
+
# "refresh_token": "<refreshTokenHash>", // v1:secure+data+AZX9...
|
43
|
+
# "anti_csrf_token": "<antiCsrfTokenHash>" // be5aac9...
|
44
|
+
# }
|
45
|
+
# }
|
46
|
+
```
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Revoke All Sessions
|
2
|
+
The Revoke All Sessions Endpoint is responsible for revoking all sessions associated with a user.
|
3
|
+
|
4
|
+
## Usage
|
5
|
+
|
6
|
+
##### Parameters
|
7
|
+
|
8
|
+
- `access_token` (required): The access token associated with the user's session.
|
9
|
+
|
10
|
+
##### Example Usage
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
client = SignInService::Client.new
|
14
|
+
|
15
|
+
access_token access_token = "your_access_token"
|
16
|
+
|
17
|
+
response = client.revoke_all_sessions(access_token: access_token)
|
18
|
+
```
|
19
|
+
|
20
|
+
#### Handling Responses
|
21
|
+
|
22
|
+
The `revoke_all_sessions` method returns a Faraday::Response object. If successful, the response contains an HTTP status code of 200.
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Revoke Token
|
2
|
+
|
3
|
+
The Revoke Token Endpoint is responsible for revoking a session.
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
### Parameters
|
8
|
+
|
9
|
+
- `refresh_token` (required): The refresh token associated with the user's session.
|
10
|
+
- `anti_csrf_token` (optional): A token for client-side CSRF protection.
|
11
|
+
|
12
|
+
### Example Usage
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
client = SignInService::Client.new
|
16
|
+
refresh_token refresh_token = "your_refresh_token"
|
17
|
+
anti_csrf_token = "your_anti_csrf_token" (optional)
|
18
|
+
|
19
|
+
response = client.revoke_token(refresh_token: refresh_token, anti_csrf_token: anti_csrf_token)
|
20
|
+
```
|
21
|
+
|
22
|
+
## Handling Responses
|
23
|
+
The `revoke_token` method returns a Faraday::Response object. If successful, the response contains an HTTP status code of 200 and an empty body.
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# Token
|
2
|
+
The Token Endpoint is responsible for exchanging an authorization code for session tokens.
|
3
|
+
|
4
|
+
## Usage
|
5
|
+
|
6
|
+
### Parameters
|
7
|
+
|
8
|
+
- `code` (required): The code received from the authorize callback.
|
9
|
+
- `code_verifier` (required): The string stored client-side from the code_challenge.
|
10
|
+
|
11
|
+
### Example
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
client = SignInService::Client.new
|
15
|
+
|
16
|
+
# Assuming you have a valid authorization code and code_verifier
|
17
|
+
code = "your_authorization_code"
|
18
|
+
code_verifier = "your_code_verifier"
|
19
|
+
|
20
|
+
response = client.get_token(code: code, code_verifier: code_verifier)
|
21
|
+
```
|
22
|
+
|
23
|
+
## Handling Responses
|
24
|
+
The `get_token` method returns a Faraday::Response object.
|
25
|
+
|
26
|
+
### `:cookie` auth type
|
27
|
+
Tokens are returned in the `Set-Cookie` headers
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
response.headers['set-cookie']
|
31
|
+
|
32
|
+
# vagov_access_token=..., vagov_refresh_token=..., vagov_anti_csrf_token=..., vagov_info_token=...
|
33
|
+
```
|
34
|
+
|
35
|
+
### `:api` auth type
|
36
|
+
Tokens are returned in the response body
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
JSON.parse(response.body)
|
40
|
+
|
41
|
+
# {
|
42
|
+
# "data": {
|
43
|
+
# "access_token": "<accessTokenHash>", // JWT eyJhbGci0... etc
|
44
|
+
# "refresh_token": "<refreshTokenHash>", // v1:secure+data+AZX9...
|
45
|
+
# "anti_csrf_token": "<antiCsrfTokenHash>" // be5aac9...
|
46
|
+
# }
|
47
|
+
# }
|
48
|
+
```
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SignInService
|
4
|
+
class Client
|
5
|
+
module Authorize
|
6
|
+
AUTHORIZE_PATH = '/v0/sign_in/authorize'
|
7
|
+
TOKEN_PATH = '/v0/sign_in/token'
|
8
|
+
|
9
|
+
##
|
10
|
+
# Generates an authorization URI.
|
11
|
+
# It is used in the OAuth 2.0 authorization code flow.
|
12
|
+
#
|
13
|
+
# @param type [String] The CSP type
|
14
|
+
# @param acr [String] Level of authentication requested
|
15
|
+
# @param code_challenge [String] Used by SiS to verify requests
|
16
|
+
# @param state [String] Optional string that can be returned in callback
|
17
|
+
#
|
18
|
+
# @return [String] URI to authorize client
|
19
|
+
#
|
20
|
+
|
21
|
+
def authorize_uri(type:, acr:, code_challenge: nil, state: nil)
|
22
|
+
uri = URI.join(base_url, AUTHORIZE_PATH)
|
23
|
+
params = {
|
24
|
+
acr:,
|
25
|
+
client_id:,
|
26
|
+
code_challenge:,
|
27
|
+
code_challenge_method:,
|
28
|
+
state:,
|
29
|
+
type:
|
30
|
+
}.compact
|
31
|
+
|
32
|
+
uri.query = URI.encode_www_form(params)
|
33
|
+
|
34
|
+
uri.to_s
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Makes call to authorize path.
|
39
|
+
# For :api auth_type applications
|
40
|
+
#
|
41
|
+
# @param type [String] The CSP type
|
42
|
+
# @param acr [String] Level of authentication requested
|
43
|
+
# @param code_challenge [String] Used by SiS to verify requests
|
44
|
+
# @param state [String] Optional string that can be returned in callback
|
45
|
+
#
|
46
|
+
# @return [Faraday::Response] Contains 'code' parameter used to exchange for token
|
47
|
+
#
|
48
|
+
def authorize(type:, acr:, code_challenge:, state: nil)
|
49
|
+
params = {
|
50
|
+
acr:,
|
51
|
+
client_id:,
|
52
|
+
code_challenge:,
|
53
|
+
code_challenge_method:,
|
54
|
+
state:,
|
55
|
+
type:
|
56
|
+
}.compact
|
57
|
+
|
58
|
+
connection.get(AUTHORIZE_PATH, params)
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# Exchange code for session tokens
|
63
|
+
#
|
64
|
+
# @param code [String] The code received form the authorize callback
|
65
|
+
# @param code_verifier [String] String stored client side from code_challenge
|
66
|
+
#
|
67
|
+
# @return [Faraday::Response] Response with tokens in header or body
|
68
|
+
#
|
69
|
+
def get_token(code:, code_verifier: nil, client_assertion: nil)
|
70
|
+
params = {
|
71
|
+
code:,
|
72
|
+
code_verifier:,
|
73
|
+
grant_type:,
|
74
|
+
client_assertion:
|
75
|
+
}.compact
|
76
|
+
|
77
|
+
params[:client_assertion_type] = client_assertion_type unless client_assertion.nil?
|
78
|
+
|
79
|
+
connection.post(TOKEN_PATH, params)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SignInService
|
4
|
+
class Client
|
5
|
+
module Config
|
6
|
+
class << self
|
7
|
+
DEFAULT_BASE_URL = 'http://localhost:3000'
|
8
|
+
DEFAULT_CLIENT_ID = 'sample'
|
9
|
+
DEFAULT_AUTH_TYPE = :cookie
|
10
|
+
DEFAULT_AUTH_FLOW = :pkce
|
11
|
+
|
12
|
+
attr_writer :base_url, :client_id, :auth_type, :auth_flow
|
13
|
+
|
14
|
+
def base_url
|
15
|
+
@base_url || DEFAULT_BASE_URL
|
16
|
+
end
|
17
|
+
|
18
|
+
def client_id
|
19
|
+
@client_id || DEFAULT_CLIENT_ID
|
20
|
+
end
|
21
|
+
|
22
|
+
def auth_type
|
23
|
+
@auth_type || DEFAULT_AUTH_TYPE
|
24
|
+
end
|
25
|
+
|
26
|
+
def auth_flow
|
27
|
+
@auth_flow || DEFAULT_AUTH_FLOW
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_h
|
31
|
+
{
|
32
|
+
base_url:,
|
33
|
+
client_id:,
|
34
|
+
auth_type:,
|
35
|
+
auth_flow:
|
36
|
+
}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SignInService
|
4
|
+
class Client
|
5
|
+
module Session
|
6
|
+
LOGOUT_PATH = '/v0/sign_in/logout'
|
7
|
+
REFRESH_PATH = '/v0/sign_in/refresh'
|
8
|
+
REVOKE_PATH = '/v0/sign_in/revoke'
|
9
|
+
REVOKE_ALL_PATH = '/v0/sign_in/revoke_all_sessions'
|
10
|
+
|
11
|
+
##
|
12
|
+
# Destroys the user session associated with the access token.
|
13
|
+
#
|
14
|
+
# @param access_token [String] Access token of session to logout of
|
15
|
+
# @param anti_csrf_token [String] Optional token if enabled on client
|
16
|
+
#
|
17
|
+
# @return [Faraday::Response] Empty body with a 200 status
|
18
|
+
#
|
19
|
+
def logout(access_token:, anti_csrf_token: nil)
|
20
|
+
connection.get(LOGOUT_PATH) do |req|
|
21
|
+
req.params[:client_id] = client_id
|
22
|
+
if cookie_auth?
|
23
|
+
req.headers = cookie_header({ access_token:, anti_csrf_token: })
|
24
|
+
else
|
25
|
+
req.params[:anti_csrf_token] = anti_csrf_token
|
26
|
+
req.headers = api_header(access_token)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# Refresh session tokens
|
33
|
+
#
|
34
|
+
# @param refresh_token [String] URI-encoded refresh token
|
35
|
+
# @param anti_csrf_token [String] Optional token if enabled on client
|
36
|
+
#
|
37
|
+
# @return [Faraday::Response] Response with tokens in header or body
|
38
|
+
#
|
39
|
+
def refresh_token(refresh_token:, anti_csrf_token: nil)
|
40
|
+
connection.post(REFRESH_PATH) do |req|
|
41
|
+
if cookie_auth?
|
42
|
+
req.headers = cookie_header({ refresh_token:, anti_csrf_token: })
|
43
|
+
else
|
44
|
+
req.params[:refresh_token] = refresh_token
|
45
|
+
req.params[:anti_csrf_token] = anti_csrf_token if anti_csrf_token
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# Revokes a user session
|
52
|
+
#
|
53
|
+
# @param refresh_token [String] URI-encoded refresh token
|
54
|
+
# @param anti_csrf_token [String] Optional token if enabled on client
|
55
|
+
#
|
56
|
+
# @return [Faraday::Response] Empty body with a 200 status
|
57
|
+
#
|
58
|
+
def revoke_token(refresh_token:, anti_csrf_token:)
|
59
|
+
connection.post(REVOKE_PATH) do |req|
|
60
|
+
req.params[:refresh_token] = CGI.escape(refresh_token)
|
61
|
+
req.params[:anti_csrf_token] = CGI.escape(anti_csrf_token)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
##
|
66
|
+
# Revokes all sessions associated with a user
|
67
|
+
#
|
68
|
+
# @param access_token [String] Access token of session
|
69
|
+
#
|
70
|
+
# @return [Faraday::Response] Empty body with a 200 status
|
71
|
+
#
|
72
|
+
def revoke_all_sessions(access_token:)
|
73
|
+
connection.get(REVOKE_ALL_PATH) do |req|
|
74
|
+
req.headers = if cookie_auth?
|
75
|
+
cookie_header({ access_token: })
|
76
|
+
else
|
77
|
+
api_header(access_token)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def cookie_header(tokens)
|
85
|
+
{
|
86
|
+
Cookie: tokens.map do |name, value|
|
87
|
+
"#{COOKIE_TOKEN_PREFIX}_#{name}=#{CGI.escape(value)}" unless value.nil?
|
88
|
+
end.join(';')
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
92
|
+
def api_header(access_token)
|
93
|
+
{
|
94
|
+
Authorization: "Bearer #{CGI.escape(access_token)}"
|
95
|
+
}
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'faraday'
|
4
|
+
|
5
|
+
require_relative 'client/authorize'
|
6
|
+
require_relative 'client/session'
|
7
|
+
require_relative 'client/config'
|
8
|
+
require_relative 'response/raise_error'
|
9
|
+
|
10
|
+
module SignInService
|
11
|
+
class Client
|
12
|
+
include SignInService::Client::Config
|
13
|
+
include SignInService::Client::Authorize
|
14
|
+
include SignInService::Client::Session
|
15
|
+
|
16
|
+
COOKIE_TOKEN_PREFIX = 'vagov'
|
17
|
+
AUTH_TYPES = [COOKIE_AUTH = :cookie, API_AUTH = :api].freeze
|
18
|
+
AUTH_FLOWS = [PKCE_FLOW = :pkce, JWT_FLOW = :jwt].freeze
|
19
|
+
|
20
|
+
class << self
|
21
|
+
def configure
|
22
|
+
yield Config
|
23
|
+
end
|
24
|
+
|
25
|
+
def config
|
26
|
+
Config
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_accessor :base_url, :client_id, :auth_type, :auth_flow
|
31
|
+
|
32
|
+
def initialize(**options)
|
33
|
+
@base_url = options[:base_url] || Config.base_url
|
34
|
+
@client_id = options[:client_id] || Config.client_id
|
35
|
+
@auth_type = options[:auth_type] || Config.auth_type
|
36
|
+
@auth_flow = options[:auth_flow] || Config.auth_flow
|
37
|
+
end
|
38
|
+
|
39
|
+
def grant_type
|
40
|
+
'authorization_code'
|
41
|
+
end
|
42
|
+
|
43
|
+
def code_challenge_method
|
44
|
+
'S256'
|
45
|
+
end
|
46
|
+
|
47
|
+
def client_assertion_type
|
48
|
+
'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
|
49
|
+
end
|
50
|
+
|
51
|
+
def connection
|
52
|
+
@connection ||= Faraday.new(base_url) do |conn|
|
53
|
+
conn.request :json
|
54
|
+
conn.response :json, content_type: /\bjson$/
|
55
|
+
conn.adapter Faraday.default_adapter
|
56
|
+
conn.use SignInService::Response::RaiseError
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def api_auth?
|
61
|
+
auth_type.to_sym == API_AUTH
|
62
|
+
end
|
63
|
+
|
64
|
+
def cookie_auth?
|
65
|
+
auth_type.to_sym == COOKIE_AUTH
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SignInService
|
4
|
+
class Error < StandardError
|
5
|
+
attr_reader :response, :response_headers, :response_body, :errors
|
6
|
+
|
7
|
+
def self.from_response(response)
|
8
|
+
status = response[:status].to_i
|
9
|
+
|
10
|
+
klass = case status
|
11
|
+
when 400 then BadRequest
|
12
|
+
when 401 then Unauthorized
|
13
|
+
when 403 then Forbidden
|
14
|
+
when 404 then NotFound
|
15
|
+
when 422 then UnprocessableEntity
|
16
|
+
when 400..499 then ClientError
|
17
|
+
when 500 then InternalServerError
|
18
|
+
when 501 then NotImplemented
|
19
|
+
when 502 then BadGateway
|
20
|
+
when 503 then ServiceUnavailable
|
21
|
+
when 500..599 then ServerError
|
22
|
+
end
|
23
|
+
|
24
|
+
klass&.new(response)
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(response)
|
28
|
+
@response = response
|
29
|
+
@response_headers = response[:response_headers]
|
30
|
+
@response_body = parsed_response_body
|
31
|
+
@errors = fetch_errors
|
32
|
+
super(error_message)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def fetch_errors
|
38
|
+
case parsed_response_body
|
39
|
+
when Hash
|
40
|
+
parsed_response_body[:errors]
|
41
|
+
when String
|
42
|
+
parsed_response_body
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def error_message
|
47
|
+
case errors
|
48
|
+
when Hash
|
49
|
+
format_errors(errors)
|
50
|
+
when String
|
51
|
+
errors
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def format_errors(errors)
|
56
|
+
case errors
|
57
|
+
when Hash
|
58
|
+
errors.flat_map do |key, values|
|
59
|
+
values.map { |message| "#{key} #{message}" }
|
60
|
+
end.join(', ')
|
61
|
+
when Array
|
62
|
+
errors.join(', ')
|
63
|
+
when String
|
64
|
+
errors
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def parsed_response_body
|
69
|
+
@parsed_response_body ||= if response_headers && response_headers['content-type'] =~ /json/
|
70
|
+
begin
|
71
|
+
JSON.parse(response[:body], symbolize_names: true)
|
72
|
+
rescue JSON::ParserError
|
73
|
+
{}
|
74
|
+
end
|
75
|
+
else
|
76
|
+
response[:body]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Raised on errors in the 400-499 range
|
82
|
+
class ClientError < Error; end
|
83
|
+
|
84
|
+
# Raised when SignInService returns a 400 HTTP status code
|
85
|
+
class BadRequest < ClientError; end
|
86
|
+
|
87
|
+
# Raised when SignInService returns a 401 HTTP status code
|
88
|
+
class Unauthorized < ClientError; end
|
89
|
+
|
90
|
+
# Raised when SignInService returns a 403 HTTP status code
|
91
|
+
class Forbidden < ClientError; end
|
92
|
+
|
93
|
+
# Raised when SignInService returns a 404 HTTP status code
|
94
|
+
class NotFound < ClientError; end
|
95
|
+
|
96
|
+
# Raised when SignInService returns a 422 HTTP status code
|
97
|
+
class UnprocessableEntity < ClientError; end
|
98
|
+
|
99
|
+
# Raised on SignInService in the 500-599 range
|
100
|
+
class ServerError < Error; end
|
101
|
+
|
102
|
+
# Raised when SignInService returns a 500 HTTP status code
|
103
|
+
class InternalServerError < ServerError; end
|
104
|
+
|
105
|
+
# Raised when SignInService returns a 501 HTTP status code
|
106
|
+
class NotImplemented < ServerError; end
|
107
|
+
|
108
|
+
# Raised when SignInService returns a 502 HTTP status code
|
109
|
+
class BadGateway < ServerError; end
|
110
|
+
|
111
|
+
# Raised when SignInService returns a 503 HTTP status code
|
112
|
+
class ServiceUnavailable < ServerError; end
|
113
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'faraday'
|
4
|
+
require 'sign_in_service/error'
|
5
|
+
|
6
|
+
module SignInService
|
7
|
+
module Response
|
8
|
+
class RaiseError < Faraday::Middleware
|
9
|
+
def on_complete(response)
|
10
|
+
return unless (error = SignInService::Error.from_response(response))
|
11
|
+
|
12
|
+
raise error
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SignInService
|
4
|
+
class Sts
|
5
|
+
module Config
|
6
|
+
class << self
|
7
|
+
DEFAULT_STS_BASE_URL = 'https://staging-api.va.gov/v0/sign_in'
|
8
|
+
|
9
|
+
attr_accessor :issuer, :scopes, :service_account_id, :user_attributes, :private_key_path
|
10
|
+
|
11
|
+
attr_writer :base_url
|
12
|
+
|
13
|
+
def base_url
|
14
|
+
@base_url || DEFAULT_STS_BASE_URL
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SignInService
|
4
|
+
class Sts
|
5
|
+
module Token
|
6
|
+
TOKEN_PATH = '/v0/sign_in/token'
|
7
|
+
ACCESS_TOKEN_DURATION = 300
|
8
|
+
JWT_ENCODE_ALGORITHM = 'RS256'
|
9
|
+
|
10
|
+
def token
|
11
|
+
params = { grant_type:, assertion: }
|
12
|
+
response = connection.post(TOKEN_PATH, params)
|
13
|
+
|
14
|
+
response.body.dig('data', 'access_token')
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def assertion
|
20
|
+
JWT.encode(assertion_payload, private_key, JWT_ENCODE_ALGORITHM)
|
21
|
+
end
|
22
|
+
|
23
|
+
def assertion_payload
|
24
|
+
{
|
25
|
+
iss: issuer,
|
26
|
+
sub: user_identifier,
|
27
|
+
aud:,
|
28
|
+
iat:,
|
29
|
+
exp:,
|
30
|
+
jti:,
|
31
|
+
scopes:,
|
32
|
+
service_account_id:,
|
33
|
+
user_attributes:
|
34
|
+
}.compact
|
35
|
+
end
|
36
|
+
|
37
|
+
def aud
|
38
|
+
"#{base_url}#{TOKEN_PATH}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def iat
|
42
|
+
@iat ||= Time.now.to_i
|
43
|
+
end
|
44
|
+
|
45
|
+
def exp
|
46
|
+
iat + ACCESS_TOKEN_DURATION
|
47
|
+
end
|
48
|
+
|
49
|
+
def jti
|
50
|
+
SecureRandom.hex
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'faraday'
|
4
|
+
require 'jwt'
|
5
|
+
|
6
|
+
require_relative 'sts/config'
|
7
|
+
require_relative 'sts/token'
|
8
|
+
|
9
|
+
module SignInService
|
10
|
+
class Sts
|
11
|
+
include Config
|
12
|
+
include Token
|
13
|
+
|
14
|
+
REQUIRED_ATTRIBUTES = %i[user_identifier issuer service_account_id private_key_path base_url].freeze
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def configure
|
18
|
+
yield Config
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :user_identifier, :issuer, :scopes, :service_account_id,
|
23
|
+
:user_attributes, :private_key_path, :base_url
|
24
|
+
|
25
|
+
def initialize(user_identifier:, **options)
|
26
|
+
@user_identifier = user_identifier
|
27
|
+
@issuer = options[:issuer] || Config.issuer
|
28
|
+
@scopes = options[:scopes] || Config.scopes || []
|
29
|
+
@service_account_id = options[:service_account_id] || Config.service_account_id
|
30
|
+
@user_attributes = options[:user_attributes] || Config.user_attributes
|
31
|
+
@private_key_path = options[:private_key_path] || Config.private_key_path
|
32
|
+
@base_url = options[:base_url] || Config.base_url
|
33
|
+
|
34
|
+
validate_arguments!
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def connection
|
40
|
+
@connection ||= Faraday.new(base_url) do |conn|
|
41
|
+
conn.adapter Faraday.default_adapter
|
42
|
+
conn.request :json
|
43
|
+
conn.response :json, content_type: /\bjson$/
|
44
|
+
conn.use SignInService::Response::RaiseError
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def grant_type
|
49
|
+
'urn:ietf:params:oauth:grant-type:jwt-bearer'
|
50
|
+
end
|
51
|
+
|
52
|
+
def private_key
|
53
|
+
OpenSSL::PKey::RSA.new(File.read(private_key_path))
|
54
|
+
end
|
55
|
+
|
56
|
+
def validate_arguments!
|
57
|
+
REQUIRED_ATTRIBUTES.each do |attribute|
|
58
|
+
raise ArgumentError, "missing required attribute: #{attribute}" if send(attribute).nil?
|
59
|
+
end
|
60
|
+
|
61
|
+
raise ArgumentError, 'scopes must be an array' unless scopes.is_a?(Array)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lib/sign_in_service/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'sign_in_service'
|
7
|
+
spec.version = SignInService::VERSION
|
8
|
+
spec.authors = ['Riley Anderson']
|
9
|
+
spec.email = ['riley.anderson@oddball.io']
|
10
|
+
|
11
|
+
spec.summary = 'Wrapper for the VA SignInService API'
|
12
|
+
spec.homepage = 'https://github.com/department-of-veterans-affairs/sign-in-service-rb'
|
13
|
+
spec.license = 'CC0-1.0'
|
14
|
+
spec.required_ruby_version = '>= 3.3'
|
15
|
+
|
16
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
17
|
+
spec.metadata['source_code_uri'] = 'https://github.com/department-of-veterans-affairs/sign-in-service-rb'
|
18
|
+
|
19
|
+
# Specify which files should be added to the gem when it is released.
|
20
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
21
|
+
spec.files = Dir.chdir(__dir__) do
|
22
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
23
|
+
(File.expand_path(f) == __FILE__) || f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
spec.bindir = 'exe'
|
27
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
28
|
+
spec.require_paths = ['lib']
|
29
|
+
|
30
|
+
# Uncomment to register a new dependency of your gem
|
31
|
+
spec.add_dependency 'faraday', '~> 2.7'
|
32
|
+
spec.add_dependency 'jwt', '~> 2.8'
|
33
|
+
|
34
|
+
# For more information and examples about making a new gem, check out our
|
35
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
36
|
+
end
|
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sign_in_service
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Riley Anderson
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-08-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: faraday
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.7'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: jwt
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.8'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.8'
|
41
|
+
description:
|
42
|
+
email:
|
43
|
+
- riley.anderson@oddball.io
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- ".rspec"
|
49
|
+
- ".rubocop.yml"
|
50
|
+
- ".ruby-version"
|
51
|
+
- Gemfile
|
52
|
+
- Gemfile.lock
|
53
|
+
- LICENSE.txt
|
54
|
+
- README.md
|
55
|
+
- Rakefile
|
56
|
+
- docs/endpoints/authorize.md
|
57
|
+
- docs/endpoints/logout.md
|
58
|
+
- docs/endpoints/refresh.md
|
59
|
+
- docs/endpoints/revoke_all_sessions.md
|
60
|
+
- docs/endpoints/revoke_token.md
|
61
|
+
- docs/endpoints/token.md
|
62
|
+
- lib/sign_in_service.rb
|
63
|
+
- lib/sign_in_service/client.rb
|
64
|
+
- lib/sign_in_service/client/authorize.rb
|
65
|
+
- lib/sign_in_service/client/config.rb
|
66
|
+
- lib/sign_in_service/client/session.rb
|
67
|
+
- lib/sign_in_service/error.rb
|
68
|
+
- lib/sign_in_service/response/raise_error.rb
|
69
|
+
- lib/sign_in_service/sts.rb
|
70
|
+
- lib/sign_in_service/sts/config.rb
|
71
|
+
- lib/sign_in_service/sts/token.rb
|
72
|
+
- lib/sign_in_service/version.rb
|
73
|
+
- sign_in_service.gemspec
|
74
|
+
homepage: https://github.com/department-of-veterans-affairs/sign-in-service-rb
|
75
|
+
licenses:
|
76
|
+
- CC0-1.0
|
77
|
+
metadata:
|
78
|
+
homepage_uri: https://github.com/department-of-veterans-affairs/sign-in-service-rb
|
79
|
+
source_code_uri: https://github.com/department-of-veterans-affairs/sign-in-service-rb
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '3.3'
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
requirements: []
|
95
|
+
rubygems_version: 3.5.11
|
96
|
+
signing_key:
|
97
|
+
specification_version: 4
|
98
|
+
summary: Wrapper for the VA SignInService API
|
99
|
+
test_files: []
|