safire 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +1 -0
- data/.rubocop.yml +62 -0
- data/.tool-versions +1 -0
- data/CHANGELOG.md +35 -0
- data/CODE_OF_CONDUCT.md +17 -0
- data/CONTRIBUTION.md +283 -0
- data/Gemfile +26 -0
- data/Gemfile.lock +186 -0
- data/LICENSE +201 -0
- data/README.md +159 -0
- data/ROADMAP.md +54 -0
- data/Rakefile +26 -0
- data/docs/.gitignore +5 -0
- data/docs/404.html +25 -0
- data/docs/Gemfile +37 -0
- data/docs/Gemfile.lock +195 -0
- data/docs/_config.yml +103 -0
- data/docs/_includes/footer_custom.html +6 -0
- data/docs/_includes/head_custom.html +14 -0
- data/docs/_sass/custom/custom.scss +108 -0
- data/docs/adr/ADR-001-activesupport-dependency.md +50 -0
- data/docs/adr/ADR-002-facade-and-forwardable.md +79 -0
- data/docs/adr/ADR-003-protocol-vs-client-type.md +67 -0
- data/docs/adr/ADR-004-clientconfig-immutability-and-entity-masking.md +59 -0
- data/docs/adr/ADR-005-per-client-http-ownership.md +58 -0
- data/docs/adr/ADR-006-lazy-discovery.md +83 -0
- data/docs/adr/ADR-007-https-only-redirects-and-localhost-exception.md +59 -0
- data/docs/adr/ADR-008-warn-return-false-for-compliance-validation.md +74 -0
- data/docs/adr/index.md +22 -0
- data/docs/advanced.md +284 -0
- data/docs/configuration/client-setup.md +158 -0
- data/docs/configuration/index.md +60 -0
- data/docs/configuration/logging.md +86 -0
- data/docs/index.md +64 -0
- data/docs/installation.md +96 -0
- data/docs/security.md +256 -0
- data/docs/smart-on-fhir/confidential-asymmetric/authorization.md +72 -0
- data/docs/smart-on-fhir/confidential-asymmetric/index.md +162 -0
- data/docs/smart-on-fhir/confidential-asymmetric/token-exchange.md +250 -0
- data/docs/smart-on-fhir/confidential-symmetric/authorization.md +75 -0
- data/docs/smart-on-fhir/confidential-symmetric/index.md +69 -0
- data/docs/smart-on-fhir/confidential-symmetric/token-exchange.md +215 -0
- data/docs/smart-on-fhir/discovery/capability-checks.md +142 -0
- data/docs/smart-on-fhir/discovery/index.md +96 -0
- data/docs/smart-on-fhir/discovery/metadata.md +147 -0
- data/docs/smart-on-fhir/index.md +72 -0
- data/docs/smart-on-fhir/post-based-authorization.md +190 -0
- data/docs/smart-on-fhir/public-client/authorization.md +112 -0
- data/docs/smart-on-fhir/public-client/index.md +80 -0
- data/docs/smart-on-fhir/public-client/token-exchange.md +249 -0
- data/docs/troubleshooting/auth-errors.md +124 -0
- data/docs/troubleshooting/client-errors.md +130 -0
- data/docs/troubleshooting/index.md +99 -0
- data/docs/troubleshooting/token-errors.md +99 -0
- data/docs/udap.md +78 -0
- data/lib/safire/client.rb +195 -0
- data/lib/safire/client_config.rb +169 -0
- data/lib/safire/client_config_builder.rb +72 -0
- data/lib/safire/entity.rb +26 -0
- data/lib/safire/errors.rb +247 -0
- data/lib/safire/http_client.rb +87 -0
- data/lib/safire/jwt_assertion.rb +237 -0
- data/lib/safire/middleware/https_only_redirects.rb +39 -0
- data/lib/safire/pkce.rb +39 -0
- data/lib/safire/protocols/behaviours.rb +54 -0
- data/lib/safire/protocols/smart.rb +378 -0
- data/lib/safire/protocols/smart_metadata.rb +231 -0
- data/lib/safire/version.rb +4 -0
- data/lib/safire.rb +54 -0
- data/safire.gemspec +36 -0
- metadata +184 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: Client Setup
|
|
4
|
+
parent: Configuration
|
|
5
|
+
nav_order: 1
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Client Setup
|
|
9
|
+
|
|
10
|
+
{: .no_toc }
|
|
11
|
+
|
|
12
|
+
## Table of contents
|
|
13
|
+
{: .no_toc .text-delta }
|
|
14
|
+
|
|
15
|
+
1. TOC
|
|
16
|
+
{:toc}
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Creating a Client
|
|
21
|
+
|
|
22
|
+
Pass configuration as a Hash — Safire wraps it in a `ClientConfig` automatically:
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
client = Safire::Client.new(
|
|
26
|
+
base_url: 'https://fhir.example.com/r4',
|
|
27
|
+
client_id: 'my_client_id',
|
|
28
|
+
redirect_uri: 'https://myapp.com/callback',
|
|
29
|
+
scopes: ['openid', 'profile', 'patient/*.read']
|
|
30
|
+
)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
If you need to reuse the same configuration across multiple clients or inspect it before use, create a `ClientConfig` explicitly:
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
config = Safire::ClientConfig.new(
|
|
37
|
+
base_url: 'https://fhir.example.com/r4',
|
|
38
|
+
client_id: 'my_client_id',
|
|
39
|
+
redirect_uri: 'https://myapp.com/callback',
|
|
40
|
+
scopes: ['openid', 'profile', 'patient/*.read']
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
client = Safire::Client.new(config)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Protocol and Client Type
|
|
49
|
+
|
|
50
|
+
`protocol:` and `client_type:` are keyword arguments to `Safire::Client.new`. They are independent of each other.
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
client = Safire::Client.new(config, protocol: :smart, client_type: :confidential_symmetric)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Protocol
|
|
57
|
+
|
|
58
|
+
Selects the authorization protocol. Defaults to `:smart`.
|
|
59
|
+
|
|
60
|
+
| Value | Status | Description |
|
|
61
|
+
|-------|--------|-------------|
|
|
62
|
+
| `:smart` | Implemented | SMART App Launch 2.2.0 |
|
|
63
|
+
| `:udap` | Planned | UDAP Security 1.0 — accepted by the validator, raises `NotImplementedError` until implemented |
|
|
64
|
+
|
|
65
|
+
For UDAP, `client_type:` is ignored — UDAP clients always authenticate with a JWT signed by their private key.
|
|
66
|
+
|
|
67
|
+
### Client Type
|
|
68
|
+
|
|
69
|
+
Selects the SMART authentication method. Applies only when `protocol: :smart`. Defaults to `:public`.
|
|
70
|
+
|
|
71
|
+
| Value | Extra config required | Authentication |
|
|
72
|
+
|-------|-----------------------|----------------|
|
|
73
|
+
| `:public` | None | PKCE; `client_id` in request body |
|
|
74
|
+
| `:confidential_symmetric` | `client_secret` | HTTP Basic auth |
|
|
75
|
+
| `:confidential_asymmetric` | `private_key`, `kid` | JWT assertion (RS384/ES384) |
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
# Public (default)
|
|
79
|
+
client = Safire::Client.new(config)
|
|
80
|
+
|
|
81
|
+
# Confidential symmetric
|
|
82
|
+
client = Safire::Client.new(
|
|
83
|
+
{ **base_config, client_secret: ENV.fetch('SMART_CLIENT_SECRET') },
|
|
84
|
+
client_type: :confidential_symmetric
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Confidential asymmetric
|
|
88
|
+
client = Safire::Client.new(
|
|
89
|
+
{
|
|
90
|
+
**base_config,
|
|
91
|
+
private_key: OpenSSL::PKey::RSA.new(File.read(ENV.fetch('SMART_PRIVATE_KEY_PATH'))),
|
|
92
|
+
kid: ENV.fetch('SMART_KEY_ID'),
|
|
93
|
+
jwks_uri: ENV.fetch('SMART_JWKS_URI') # optional
|
|
94
|
+
},
|
|
95
|
+
client_type: :confidential_asymmetric
|
|
96
|
+
)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
You can also change `client_type` after initialization — useful when selecting a type based on server capabilities discovered at runtime:
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
client = Safire::Client.new(config)
|
|
103
|
+
metadata = client.server_metadata
|
|
104
|
+
|
|
105
|
+
client.client_type = :confidential_asymmetric if metadata.supports_asymmetric_auth?
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
For a decision guide on which client type to use, see [SMART on FHIR — Choosing a Client Type]({{ site.baseurl }}/smart-on-fhir/).
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## URI Validation
|
|
113
|
+
|
|
114
|
+
All URI parameters are validated at initialization. Safire raises `Safire::Errors::ConfigurationError` for any violation:
|
|
115
|
+
|
|
116
|
+
- URIs must be well-formed (scheme + host required)
|
|
117
|
+
- URIs must use `https` — required by SMART App Launch 2.2.0
|
|
118
|
+
- **Exception:** `http` is permitted for `localhost` and `127.0.0.1` (local development only)
|
|
119
|
+
|
|
120
|
+
The following attributes are validated:
|
|
121
|
+
|
|
122
|
+
| Attribute | Validated when |
|
|
123
|
+
|-----------|----------------|
|
|
124
|
+
| `base_url` | Always |
|
|
125
|
+
| `redirect_uri` | Always |
|
|
126
|
+
| `issuer` | When provided (defaults to `base_url`) |
|
|
127
|
+
| `authorization_endpoint` | When provided |
|
|
128
|
+
| `token_endpoint` | When provided |
|
|
129
|
+
| `jwks_uri` | When provided |
|
|
130
|
+
|
|
131
|
+
If you need to bypass discovery and provide endpoints directly, set `authorization_endpoint` and `token_endpoint` in your config. Safire will use them as-is instead of fetching `/.well-known/smart-configuration`.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Credential Protection
|
|
136
|
+
|
|
137
|
+
`ClientConfig` prevents `client_secret` and `private_key` from leaking in logs or REPL output.
|
|
138
|
+
|
|
139
|
+
`#to_hash` replaces sensitive fields with `'[FILTERED]'`:
|
|
140
|
+
|
|
141
|
+
```ruby
|
|
142
|
+
config.to_hash[:client_secret] # => "[FILTERED]"
|
|
143
|
+
config.to_hash[:base_url] # => "https://fhir.example.com"
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`#inspect` is overridden to mask sensitive fields and omit `nil` attributes, so REPL sessions and error messages never expose credentials:
|
|
147
|
+
|
|
148
|
+
```ruby
|
|
149
|
+
config.inspect
|
|
150
|
+
# => "#<Safire::ClientConfig base_url: \"https://fhir.example.com\", client_id: \"my_client_id\", client_secret: \"[FILTERED]\", ...>"
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Next Steps
|
|
156
|
+
|
|
157
|
+
- [Logging]({{ site.baseurl }}/configuration/logging/) — configure Safire's logger and HTTP request logging
|
|
158
|
+
- [SMART on FHIR Workflows]({{ site.baseurl }}/smart-on-fhir/) — step-by-step authorization flow guides
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: Configuration
|
|
4
|
+
nav_order: 3
|
|
5
|
+
has_children: true
|
|
6
|
+
permalink: /configuration/
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Configuration
|
|
10
|
+
|
|
11
|
+
Safire is configured in two places:
|
|
12
|
+
|
|
13
|
+
- **Client configuration** — the FHIR server URL, credentials, and OAuth parameters passed to `Safire::Client.new`
|
|
14
|
+
- **Global configuration** — the logger, log level, and HTTP logging behaviour set once via `Safire.configure`
|
|
15
|
+
|
|
16
|
+
## Architecture Overview
|
|
17
|
+
|
|
18
|
+
`Safire::Client` is the public entry point. It owns a `ClientConfig` (validated at construction) and lazily builds a protocol implementation when first used. See [ADR-002]({% link adr/ADR-002-facade-and-forwardable.md %}) for the facade design rationale, [ADR-003]({% link adr/ADR-003-protocol-vs-client-type.md %}) for the `protocol:` / `client_type:` design, and [ADR-006]({% link adr/ADR-006-lazy-discovery.md %}) for the lazy discovery design.
|
|
19
|
+
|
|
20
|
+
```mermaid
|
|
21
|
+
flowchart TD
|
|
22
|
+
A["Safire::Client.new(config, protocol: :smart, client_type: :public)"]
|
|
23
|
+
B["Safire::ClientConfig\n— validates URIs\n— masks sensitive attrs"]
|
|
24
|
+
C{protocol:}
|
|
25
|
+
D["Protocols::Smart\n— reads attrs from ClientConfig\n— owns HTTPClient"]
|
|
26
|
+
E["SmartMetadata\n(lazy — fetched on first use)"]
|
|
27
|
+
F["GET /.well-known/\nsmart-configuration"]
|
|
28
|
+
|
|
29
|
+
A -->|"resolves config"| B
|
|
30
|
+
A -->|"validates protocol + client_type"| C
|
|
31
|
+
C -->|":smart (default)"| D
|
|
32
|
+
C -->|":udap (planned)"| G["Protocols::Udap\n(future)"]
|
|
33
|
+
D -->|"lazily fetches"| E
|
|
34
|
+
E -->|"HTTP"| F
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Quick Reference
|
|
38
|
+
|
|
39
|
+
`protocol:` and `client_type:` are keyword arguments to `Safire::Client.new`. All other parameters are keys in the configuration hash (or `Safire::ClientConfig` attributes).
|
|
40
|
+
|
|
41
|
+
| Parameter | Type | Required | Default | Description |
|
|
42
|
+
|-----------|------|----------|---------|-------------|
|
|
43
|
+
| `base_url` | String | Yes | — | FHIR server base URL |
|
|
44
|
+
| `client_id` | String | Yes | — | OAuth2 client identifier |
|
|
45
|
+
| `redirect_uri` | String | Yes | — | Registered callback URL |
|
|
46
|
+
| `protocol:` | Symbol | No | `:smart` | Authorization protocol — `:smart` or `:udap` |
|
|
47
|
+
| `client_type:` | Symbol | No | `:public` | SMART client type — `:public`, `:confidential_symmetric`, or `:confidential_asymmetric` |
|
|
48
|
+
| `client_secret` | String | No | — | Required for `:confidential_symmetric` |
|
|
49
|
+
| `private_key` | OpenSSL::PKey / String | No | — | RSA/EC private key; required for `:confidential_asymmetric` |
|
|
50
|
+
| `kid` | String | No | — | Key ID matching the public key registered with the server |
|
|
51
|
+
| `jwt_algorithm` | String | No | auto | `RS384` or `ES384`; auto-detected from key type |
|
|
52
|
+
| `jwks_uri` | String | No | — | URL to client's public JWKS, included as `jku` in JWT header |
|
|
53
|
+
| `scopes` | Array | No | — | Default scopes for authorization requests |
|
|
54
|
+
| `authorization_endpoint` | String | No | — | Override the discovered authorization endpoint |
|
|
55
|
+
| `token_endpoint` | String | No | — | Override the discovered token endpoint |
|
|
56
|
+
|
|
57
|
+
## In This Section
|
|
58
|
+
|
|
59
|
+
- [Client Setup]({{ site.baseurl }}/configuration/client-setup/) — creating a client, protocol and client type selection, URI rules, and credential protection
|
|
60
|
+
- [Logging]({{ site.baseurl }}/configuration/logging/) — global logger setup, HTTP request logging, log levels, and environment variables
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: Logging
|
|
4
|
+
parent: Configuration
|
|
5
|
+
nav_order: 2
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Logging
|
|
9
|
+
|
|
10
|
+
{: .no_toc }
|
|
11
|
+
|
|
12
|
+
## Table of contents
|
|
13
|
+
{: .no_toc .text-delta }
|
|
14
|
+
|
|
15
|
+
1. TOC
|
|
16
|
+
{:toc}
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Global Logger Setup
|
|
21
|
+
|
|
22
|
+
Configure Safire's logger once at application startup via `Safire.configure`:
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
# config/initializers/safire.rb
|
|
26
|
+
Safire.configure do |config|
|
|
27
|
+
config.logger = Rails.logger
|
|
28
|
+
config.log_level = Rails.env.development? ? Logger::DEBUG : Logger::INFO
|
|
29
|
+
config.log_http = true # default
|
|
30
|
+
end
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
By default, Safire logs to `$stdout` at `Logger::INFO`.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Log Levels
|
|
38
|
+
|
|
39
|
+
| Level | Behaviour |
|
|
40
|
+
|-------|-----------|
|
|
41
|
+
| `Logger::DEBUG` | Verbose — all Safire internal operations |
|
|
42
|
+
| `Logger::INFO` | Standard — normal operation events (default) |
|
|
43
|
+
| `Logger::WARN` | Compliance warnings and non-critical issues only |
|
|
44
|
+
| `Logger::ERROR` | Errors only |
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## HTTP Request Logging
|
|
49
|
+
|
|
50
|
+
When `log_http` is `true` (the default), Safire logs each outbound HTTP request and response. Sensitive data is automatically filtered:
|
|
51
|
+
|
|
52
|
+
- The `Authorization` header is replaced with `[FILTERED]`
|
|
53
|
+
- Request and response **bodies are never logged** — tokens and credentials are never captured
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
Safire.configure do |config|
|
|
57
|
+
config.log_http = false # disable if not needed in production
|
|
58
|
+
end
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Environment Variables
|
|
64
|
+
|
|
65
|
+
### `SAFIRE_LOGGER`
|
|
66
|
+
|
|
67
|
+
By default Safire logs to `$stdout`. Set `SAFIRE_LOGGER` to a file path to redirect output:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
SAFIRE_LOGGER=/var/log/safire.log
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
This only affects the **default logger**. If you set `config.logger` in `Safire.configure`, `SAFIRE_LOGGER` is ignored entirely.
|
|
74
|
+
|
|
75
|
+
| `SAFIRE_LOGGER` set? | `config.logger` set? | Log destination |
|
|
76
|
+
|----------------------|----------------------|-----------------|
|
|
77
|
+
| No | No | `$stdout` |
|
|
78
|
+
| Yes | No | File at that path |
|
|
79
|
+
| Either | Yes | Your custom logger |
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Next Steps
|
|
84
|
+
|
|
85
|
+
- [Client Setup]({{ site.baseurl }}/configuration/client-setup/) — client parameters, protocol, and credential protection
|
|
86
|
+
- [Troubleshooting]({{ site.baseurl }}/troubleshooting/) — common issues and solutions
|
data/docs/index.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: Home
|
|
4
|
+
nav_order: 1
|
|
5
|
+
permalink: /
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Safire Documentation
|
|
9
|
+
|
|
10
|
+
[](https://badge.fury.io/rb/safire)
|
|
11
|
+
[](https://github.com/vanessuniq/safire/actions)
|
|
12
|
+
|
|
13
|
+
A lean Ruby gem implementing **[SMART on FHIR](https://hl7.org/fhir/smart-app-launch/)** and **[UDAP](https://hl7.org/fhir/us/udap-security/)** protocols for clients.
|
|
14
|
+
|
|
15
|
+
## Quick Navigation
|
|
16
|
+
|
|
17
|
+
| Section | Description |
|
|
18
|
+
|---------|-------------|
|
|
19
|
+
| [Getting Started]({{ site.baseurl }}/installation/) | Install Safire and quick start guide |
|
|
20
|
+
| [Configuration]({{ site.baseurl }}/configuration/) | All configuration options and parameters |
|
|
21
|
+
| [SMART on FHIR]({{ site.baseurl }}/smart-on-fhir/) | Discovery, Public clients, Confidential clients |
|
|
22
|
+
| [UDAP]({{ site.baseurl }}/udap/) | UDAP protocol overview and planned support |
|
|
23
|
+
| [Security Guide]({{ site.baseurl }}/security/) | HTTPS, credential protection, token storage, key rotation |
|
|
24
|
+
| [Advanced Examples]({{ site.baseurl }}/advanced/) | Caching, multi-server, token management, complete Rails integration |
|
|
25
|
+
| [Troubleshooting]({{ site.baseurl }}/troubleshooting/) | Common issues and solutions |
|
|
26
|
+
| [Safire API Docs]({{ site.baseurl }}/api/){:target="_blank"} | Complete YARD documentation |
|
|
27
|
+
|
|
28
|
+
## Features
|
|
29
|
+
|
|
30
|
+
### SMART on FHIR App Launch
|
|
31
|
+
|
|
32
|
+
- Discovery (`/.well-known/smart-configuration`)
|
|
33
|
+
- Public Client (PKCE)
|
|
34
|
+
- Confidential Symmetric Client (client_secret + Basic Auth)
|
|
35
|
+
- Confidential Asymmetric Client (private_key_jwt with RS384/ES384)
|
|
36
|
+
- POST-Based Authorization
|
|
37
|
+
|
|
38
|
+
### UDAP
|
|
39
|
+
|
|
40
|
+
> Planned. See [ROADMAP.md](https://github.com/vanessuniq/safire/blob/main/ROADMAP.md) for details (coming soon).
|
|
41
|
+
|
|
42
|
+
## Demo Application
|
|
43
|
+
|
|
44
|
+
A Sinatra-based demo app is included to help you explore Safire's features:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
bin/demo
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Visit http://localhost:4567 to test SMART discovery, authorization flows, and token management.
|
|
51
|
+
|
|
52
|
+
See [`examples/sinatra_app/README.md`](https://github.com/vanessuniq/safire/tree/main/examples/sinatra_app) for details.
|
|
53
|
+
|
|
54
|
+
## Community
|
|
55
|
+
|
|
56
|
+
- [GitHub Repository](https://github.com/vanessuniq/safire)
|
|
57
|
+
- [Issue Tracker](https://github.com/vanessuniq/safire/issues)
|
|
58
|
+
- [Architecture Decision Records]({{ site.baseurl }}/adr/) — design decisions and rationale
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
*Last updated: {{ site.time | date: '%B %d, %Y' }}*
|
|
63
|
+
|
|
64
|
+
*Parts of this project were developed with AI assistance (Claude Code) and reviewed by maintainers.*
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: Installation
|
|
4
|
+
nav_order: 2
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Installation
|
|
8
|
+
|
|
9
|
+
{: .no_toc }
|
|
10
|
+
|
|
11
|
+
## Table of contents
|
|
12
|
+
{: .no_toc .text-delta }
|
|
13
|
+
|
|
14
|
+
1. TOC
|
|
15
|
+
{:toc}
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
**Requirements:** Ruby ≥ 4.0.2. OpenSSL is bundled with Ruby — no separate install needed.
|
|
22
|
+
|
|
23
|
+
Add to your Gemfile:
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
gem 'safire'
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Then run:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
bundle install
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Or install directly:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
gem install safire
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Development Setup
|
|
44
|
+
|
|
45
|
+
Clone the repo and set up the development environment:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
git clone https://github.com/vanessuniq/safire.git
|
|
49
|
+
cd safire
|
|
50
|
+
bin/setup # Install dependencies
|
|
51
|
+
bundle exec rspec # Run tests to verify
|
|
52
|
+
bin/console # Interactive prompt
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
To serve the docs site locally:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
bin/docs # Generate YARD API docs
|
|
59
|
+
cd docs && bundle install && bundle exec jekyll serve
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Then visit `http://localhost:4000/safire/`.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Verify
|
|
67
|
+
|
|
68
|
+
A quick smoke test to confirm the gem is installed and SMART discovery is working:
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
require 'safire'
|
|
72
|
+
|
|
73
|
+
client = Safire::Client.new(
|
|
74
|
+
base_url: 'https://launch.smarthealthit.org/v/r4/sim/eyJoIjoiMSJ9/fhir',
|
|
75
|
+
client_id: 'test',
|
|
76
|
+
redirect_uri: 'https://example.com/callback',
|
|
77
|
+
scopes: ['openid', 'profile', 'patient/*.read']
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
metadata = client.server_metadata
|
|
81
|
+
puts metadata.authorization_endpoint
|
|
82
|
+
# => https://launch.smarthealthit.org/.../auth/authorize
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
If you see an authorization endpoint URL, the gem is working. For troubleshooting, see the [Troubleshooting Guide]({{ site.baseurl }}/troubleshooting/).
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Next Steps
|
|
90
|
+
|
|
91
|
+
| | |
|
|
92
|
+
|-|-|
|
|
93
|
+
| [Configuration]({{ site.baseurl }}/configuration/) | Client credentials, logging, and protocol selection |
|
|
94
|
+
| [SMART on FHIR]({{ site.baseurl }}/smart-on-fhir/) | Authorization flows for public and confidential clients |
|
|
95
|
+
| [Security Guide]({{ site.baseurl }}/security/) | HTTPS requirements, credential protection, token storage |
|
|
96
|
+
| [API Reference]({{ site.baseurl }}/api/){:target="_blank"} | Complete YARD documentation |
|