Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ on:

env:
OPENC3_API_PASSWORD: password
OPENC3_API_PORT: 2900

jobs:
openc3-cli:
Expand Down
15 changes: 13 additions & 2 deletions docs.openc3.com/docs/development/json-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ This document provides the information necessary for external applications to in
The HTTP Authorization request header contains the credentials to authenticate a user agent with a server, usually, but not necessarily, after the server has responded with a 401 Unauthorized status and the WWW-Authenticate header.

```
Authorization: <token/password>
Authorization: <token>
```

## JSON-RPC 2.0
Expand Down Expand Up @@ -117,5 +117,16 @@ If developing an interface for the JSON API from another language, the best way
You can also try sending these raw commands from the terminal with a program like `curl`:

```bash
curl -d '{"jsonrpc": "2.0", "method": "tlm", "params": ["INST HEALTH_STATUS TEMP1"], "id": 2, "keyword_params":{"type":"FORMATTED","scope":"DEFAULT"}}' http://localhost:2900/openc3-api/api -H "Authorization: password"
curl -d '{"jsonrpc": "2.0", "method": "tlm", "params": ["INST HEALTH_STATUS TEMP1"], "id": 2, "keyword_params":{"type":"FORMATTED","scope":"DEFAULT"}}' \
-H "Content-Type: application/json" \
-H "Authorization: <token>" \
http://localhost:2900/openc3-api/api
```

Note that you will need a valid session token in the `Authorization` header to access the JSON API. You can retrieve one either by logging in via your browser and copying the value of `localStorage.openc3Token` in the dev tools console, or also with `curl`:

```bash
curl http://localhost:2900/openc3-api/auth/verify \
-H 'Content-Type: application/json' \
-d '{"token": "your-password-here"}'
```
15 changes: 14 additions & 1 deletion openc3-cosmos-cmd-tlm-api/app/controllers/auth_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,20 @@ def token_exists

def verify
begin
if OpenC3::AuthModel.verify_no_service(params[:token])
if OpenC3::AuthModel.verify_no_service(params[:token], no_password: false)
render :plain => OpenC3::AuthModel.generate_session()
else
head :unauthorized
end
rescue StandardError => e
log_error(e)
render json: { status: 'error', message: e.message, type: e.class }, status: 500
end
end

def verify_service
begin
if OpenC3::AuthModel.verify(params[:token], service_only: true)
render :plain => OpenC3::AuthModel.generate_session()
else
head :unauthorized
Expand Down
1 change: 1 addition & 0 deletions openc3-cosmos-cmd-tlm-api/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@

get "/auth/token-exists" => "auth#token_exists"
post "/auth/verify" => "auth#verify"
post "/auth/verify_service" => "auth#verify_service"
post "/auth/set" => "auth#set"

get "/internal/health" => "internal_health#health"
Expand Down
1 change: 1 addition & 0 deletions openc3-cosmos-cmd-tlm-api/spec/rails_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'rspec/rails'
# Add additional requires below this line. Rails is not loaded until this point!
load 'openc3/io/json_rpc.rb' # This is here because we need our as_json to override Rails'

# Requires supporting ruby files with custom matchers and macros, etc, in
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
Expand Down
13 changes: 12 additions & 1 deletion openc3-cosmos-cmd-tlm-api/spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
ENV['OPENC3_REDIS_EPHEMERAL_HOSTNAME'] = '127.0.0.1'
ENV['OPENC3_REDIS_EPHEMERAL_PORT'] = '6380'
# Set some usernames / passwords
ENV['OPENC3_API_PASSWORD'] = 'openc3'
ENV['OPENC3_API_PASSWORD'] = 'password'
ENV['OPENC3_SERVICE_PASSWORD'] = 'openc3service'
ENV['OPENC3_REDIS_USERNAME'] = 'openc3'
ENV['OPENC3_REDIS_PASSWORD'] = 'openc3password'
Expand All @@ -84,6 +84,17 @@

$openc3_scope = ENV['OPENC3_SCOPE']
$openc3_token = ENV['OPENC3_API_PASSWORD']
$openc3_mock_token = 'mock_token'

# Mock the HTTP request for OpenC3Authentication
require 'openc3/utilities/authentication'
OpenC3::OpenC3Authentication.class_eval do
def _make_auth_request(password)
mock_response = Object.new
mock_response.define_singleton_method(:body) { $openc3_mock_token }
mock_response
end
end

def setup_system(targets = ["SYSTEM", "INST", "EMPTY"])
require 'openc3/system'
Expand Down
104 changes: 98 additions & 6 deletions openc3-cosmos-init/plugins/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions openc3-cosmos-script-runner-api/spec/rails_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'rspec/rails'
# Add additional requires below this line. Rails is not loaded until this point!
load 'openc3/io/json_rpc.rb' # This is here because we need our as_json to override Rails'

# Requires supporting ruby files with custom matchers and macros, etc, in
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
Expand Down
11 changes: 11 additions & 0 deletions openc3-cosmos-script-runner-api/spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,17 @@

$openc3_scope = ENV['OPENC3_SCOPE']
$openc3_token = ENV['OPENC3_API_PASSWORD']
$openc3_mock_token = 'mock_token'

# Mock the HTTP request for OpenC3Authentication
require 'openc3/utilities/authentication'
OpenC3::OpenC3Authentication.class_eval do
def _make_auth_request(password)
mock_response = Object.new
mock_response.define_singleton_method(:body) { $openc3_mock_token }
mock_response
end
end

def mock_redis
require 'redis'
Expand Down
16 changes: 11 additions & 5 deletions openc3/lib/openc3/models/auth_model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,20 @@ def self.set?(key = PRIMARY_KEY)
Store.exists(key) == 1
end

def self.verify(token)
# @param no_password [Boolean] enforces use of a session token or service password (default: true)
def self.verify(token, no_password: true, service_only: false)
# Handle a service password - Generally only used by ScriptRunner
# TODO: Replace this with temporary service tokens
service_password = ENV['OPENC3_SERVICE_PASSWORD']
return true if service_password and service_password == token

return verify_no_service(token)
return false if service_only

return verify_no_service(token, no_password: no_password)
end

def self.verify_no_service(token)
# @param no_password [Boolean] enforces use of a session token (default: true)
def self.verify_no_service(token, no_password: true)
return false if token.nil? or token.empty?

time = Time.now
Expand All @@ -64,6 +68,8 @@ def self.verify_no_service(token)
@@session_cache_time = time
return true if @@session_cache[token]

return false if no_password

# Check Direct password
@@token_cache = Store.get(PRIMARY_KEY)
@@token_cache_time = time
Expand All @@ -78,7 +84,7 @@ def self.set(token, old_token, key = PRIMARY_KEY)

if set?(key)
raise "old_token must not be nil or empty" if old_token.nil? or old_token.empty?
raise "old_token incorrect" unless verify(old_token)
raise "old_token incorrect" unless verify_no_service(old_token, no_password: false)
end
Store.set(key, hash(token))
end
Expand All @@ -96,7 +102,7 @@ def self.logout
end

def self.hash(token)
Digest::SHA2.hexdigest token
Digest::SHA256.hexdigest token
Copy link
Copy Markdown
Contributor Author

@ryan-pratt ryan-pratt Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SHA-256 is the default algorithm for the SHA2 family in Ruby's Digest module. We decided to use SHA-256, so this code change just makes that explicit. Users won't need to reset their password.

end
end
end
Loading
Loading