diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 6f10fbae22..e7cc018ee5 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -16,6 +16,7 @@ on: env: OPENC3_API_PASSWORD: password + OPENC3_API_PORT: 2900 jobs: openc3-cli: diff --git a/docs.openc3.com/docs/development/json-api.md b/docs.openc3.com/docs/development/json-api.md index 625a2177b2..1f445f484d 100644 --- a/docs.openc3.com/docs/development/json-api.md +++ b/docs.openc3.com/docs/development/json-api.md @@ -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: +Authorization: ``` ## JSON-RPC 2.0 @@ -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: " \ +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"}' ``` diff --git a/openc3-cosmos-cmd-tlm-api/app/controllers/auth_controller.rb b/openc3-cosmos-cmd-tlm-api/app/controllers/auth_controller.rb index 9918d0ff43..a5fc0a4d21 100644 --- a/openc3-cosmos-cmd-tlm-api/app/controllers/auth_controller.rb +++ b/openc3-cosmos-cmd-tlm-api/app/controllers/auth_controller.rb @@ -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 diff --git a/openc3-cosmos-cmd-tlm-api/config/routes.rb b/openc3-cosmos-cmd-tlm-api/config/routes.rb index 87049369c7..79e236ef72 100644 --- a/openc3-cosmos-cmd-tlm-api/config/routes.rb +++ b/openc3-cosmos-cmd-tlm-api/config/routes.rb @@ -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" diff --git a/openc3-cosmos-cmd-tlm-api/spec/rails_helper.rb b/openc3-cosmos-cmd-tlm-api/spec/rails_helper.rb index 3b9148140a..faa06d4cd2 100644 --- a/openc3-cosmos-cmd-tlm-api/spec/rails_helper.rb +++ b/openc3-cosmos-cmd-tlm-api/spec/rails_helper.rb @@ -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 diff --git a/openc3-cosmos-cmd-tlm-api/spec/spec_helper.rb b/openc3-cosmos-cmd-tlm-api/spec/spec_helper.rb index 5c2f34a246..779b291479 100644 --- a/openc3-cosmos-cmd-tlm-api/spec/spec_helper.rb +++ b/openc3-cosmos-cmd-tlm-api/spec/spec_helper.rb @@ -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' @@ -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' diff --git a/openc3-cosmos-init/plugins/pnpm-lock.yaml b/openc3-cosmos-init/plugins/pnpm-lock.yaml index 5bfe88adfb..9ebb17e522 100644 --- a/openc3-cosmos-init/plugins/pnpm-lock.yaml +++ b/openc3-cosmos-init/plugins/pnpm-lock.yaml @@ -784,8 +784,8 @@ importers: specifier: 3.7.3 version: 3.7.3 vite: - specifier: 6.4.1 - version: 6.4.1(sass@1.94.2) + specifier: 7.2.1 + version: 7.2.1(sass@1.94.2) packages/openc3-tool-base: dependencies: @@ -912,14 +912,14 @@ importers: version: 4.1.0(vue@3.5.25) devDependencies: '@vitejs/plugin-vue': - specifier: 5.2.3 - version: 5.2.3(vite@6.4.1(sass@1.94.2))(vue@3.5.25) + specifier: 6.0.1 + version: 6.0.1(vite@7.2.1(sass@1.94.2))(vue@3.5.25) prettier: specifier: 3.7.3 version: 3.7.3 vite: - specifier: 6.4.1 - version: 6.4.1(sass@1.94.2) + specifier: 7.2.1 + version: 7.2.1(sass@1.94.2) packages: @@ -1361,6 +1361,9 @@ packages: resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@rolldown/pluginutils@1.0.0-beta.29': + resolution: {integrity: sha512-NIJgOsMjbxAXvoGq/X0gD7VPMQ8j9g0BiDaNjVNVjvl+iKXxL3Jre0v31RmBYeLEmkbj2s02v8vFTbUXi5XS2Q==} + '@rollup/rollup-android-arm-eabi@4.45.1': resolution: {integrity: sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA==} cpu: [arm] @@ -1506,6 +1509,13 @@ packages: vite: ^5.0.0 || ^6.0.0 vue: ^3.2.25 + '@vitejs/plugin-vue@6.0.1': + resolution: {integrity: sha512-+MaE752hU0wfPFJEUAIxqw18+20euHHdxVtMvbFcOEpjEyfqXH/5DCoTHiVJ0J29EhTJdoTkjEv5YBKU9dnoTw==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 + vue: ^3.2.25 + '@vue-flow/background@1.3.2': resolution: {integrity: sha512-eJPhDcLj1wEo45bBoqTXw1uhl0yK2RaQGnEINqvvBsAFKh/camHJd5NPmOdS1w+M9lggc9igUewxaEd3iCQX2w==} peerDependencies: @@ -2040,6 +2050,15 @@ packages: picomatch: optional: true + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -2590,6 +2609,10 @@ packages: resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} engines: {node: '>=12.0.0'} + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -2669,6 +2692,46 @@ packages: yaml: optional: true + vite@7.2.1: + resolution: {integrity: sha512-qTl3VF7BvOupTR85Zc561sPEgxyUSNSvTQ9fit7DEMP7yPgvvIGm5Zfa1dOM+kOwWGNviK9uFM9ra77+OjK7lQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + vue-component-type-helpers@2.2.12: resolution: {integrity: sha512-YbGqHZ5/eW4SnkPNR44mKVc6ZKQoRs/Rux1sxC6rdwXb4qpbOSYfDr9DsTHolOTGmIKgM9j141mZbBeg05R1pw==} @@ -3136,6 +3199,8 @@ snapshots: '@pkgr/core@0.2.9': {} + '@rolldown/pluginutils@1.0.0-beta.29': {} + '@rollup/rollup-android-arm-eabi@4.45.1': optional: true @@ -3218,6 +3283,12 @@ snapshots: vite: 6.4.1(sass@1.94.2) vue: 3.5.25 + '@vitejs/plugin-vue@6.0.1(vite@7.2.1(sass@1.94.2))(vue@3.5.25)': + dependencies: + '@rolldown/pluginutils': 1.0.0-beta.29 + vite: 7.2.1(sass@1.94.2) + vue: 3.5.25 + '@vue-flow/background@1.3.2(@vue-flow/core@1.48.0(vue@3.5.25))(vue@3.5.25)': dependencies: '@vue-flow/core': 1.48.0(vue@3.5.25) @@ -3802,6 +3873,10 @@ snapshots: optionalDependencies: picomatch: 4.0.3 + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -4305,6 +4380,11 @@ snapshots: fdir: 6.4.6(picomatch@4.0.3) picomatch: 4.0.3 + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -4353,6 +4433,18 @@ snapshots: fsevents: 2.3.3 sass: 1.94.2 + vite@7.2.1(sass@1.94.2): + dependencies: + esbuild: 0.25.8 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.45.1 + tinyglobby: 0.2.15 + optionalDependencies: + fsevents: 2.3.3 + sass: 1.94.2 + vue-component-type-helpers@2.2.12: {} vue-demi@0.14.10(vue@3.5.25): diff --git a/openc3-cosmos-script-runner-api/spec/rails_helper.rb b/openc3-cosmos-script-runner-api/spec/rails_helper.rb index 96139e5b90..3320b7f5e5 100644 --- a/openc3-cosmos-script-runner-api/spec/rails_helper.rb +++ b/openc3-cosmos-script-runner-api/spec/rails_helper.rb @@ -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 diff --git a/openc3-cosmos-script-runner-api/spec/spec_helper.rb b/openc3-cosmos-script-runner-api/spec/spec_helper.rb index 49c9e70177..151e1042ab 100644 --- a/openc3-cosmos-script-runner-api/spec/spec_helper.rb +++ b/openc3-cosmos-script-runner-api/spec/spec_helper.rb @@ -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' diff --git a/openc3/lib/openc3/models/auth_model.rb b/openc3/lib/openc3/models/auth_model.rb index 7047cd1065..1d73d219d0 100644 --- a/openc3/lib/openc3/models/auth_model.rb +++ b/openc3/lib/openc3/models/auth_model.rb @@ -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 @@ -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 @@ -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 @@ -96,7 +102,7 @@ def self.logout end def self.hash(token) - Digest::SHA2.hexdigest token + Digest::SHA256.hexdigest token end end end diff --git a/openc3/lib/openc3/utilities/authentication.rb b/openc3/lib/openc3/utilities/authentication.rb index 7a0c5c16c0..122a97d6e2 100644 --- a/openc3/lib/openc3/utilities/authentication.rb +++ b/openc3/lib/openc3/utilities/authentication.rb @@ -33,16 +33,40 @@ class OpenC3AuthenticationRetryableError < OpenC3AuthenticationError; end # OpenC3 COSMOS Core authentication code class OpenC3Authentication def initialize() - @token = ENV['OPENC3_API_PASSWORD'] - if @token.nil? + password = ENV['OPENC3_API_PASSWORD'] + if password.nil? raise OpenC3AuthenticationError, "Authentication requires environment variable OPENC3_API_PASSWORD" end + @service = password == ENV['OPENC3_SERVICE_PASSWORD'] + response = _make_auth_request(password) + @token = response.body + if @token.nil? or @token.empty? + raise OpenC3AuthenticationError, "Authentication failed. Please check the password in the environment variable OPENC3_API_PASSWORD" + end end # Load the token from the environment def token(include_bearer: true) @token end + + def _make_auth_request(password) + Faraday.new.post(_generate_auth_url, '{"token": "' + password + '"}', {'Content-Type' => 'application/json'}) + end + + def _generate_auth_url + schema = ENV['OPENC3_API_SCHEMA'] || 'http' + hostname = ENV['OPENC3_API_HOSTNAME'] || (ENV['OPENC3_DEVEL'] ? '127.0.0.1' : 'openc3-cosmos-cmd-tlm-api') + port = ENV['OPENC3_API_PORT'] || '2901' + port = port.to_i + endpoint = if @service + "auth/verify_service" + else + "auth/verify" + end + return "#{schema}://#{hostname}:#{port}/openc3-api/#{endpoint}" + end + end # OpenC3 enterprise Keycloak authentication code diff --git a/openc3/lib/openc3/utilities/authorization.rb b/openc3/lib/openc3/utilities/authorization.rb index 94e855c5a9..80fff520da 100644 --- a/openc3/lib/openc3/utilities/authorization.rb +++ b/openc3/lib/openc3/utilities/authorization.rb @@ -43,7 +43,7 @@ def authorize(permission: nil, target_name: nil, packet_name: nil, interface_nam if $openc3_authorize raise AuthError.new("Token is required") unless token unless OpenC3::AuthModel.verify(token) - raise AuthError.new("Password is invalid") + raise AuthError.new("Token is invalid") end end return "anonymous" diff --git a/openc3/python/openc3/environment.py b/openc3/python/openc3/environment.py index c49634e3ee..ae59d718ea 100644 --- a/openc3/python/openc3/environment.py +++ b/openc3/python/openc3/environment.py @@ -28,6 +28,7 @@ _openc3_script_api_timeout = "OPENC3_SCRIPT_API_TIMEOUT" _openc3_scope = "OPENC3_SCOPE" _openc3_api_password = "OPENC3_API_PASSWORD" +_openc3_service_password = "OPENC3_SERVICE_PASSWORD" _openc3_log_level = "OPENC3_LOG_LEVEL" _openc3_no_store = "OPENC3_NO_STORE" _openc3_user_agent = "OPENC3_USER_AGENT" @@ -152,6 +153,7 @@ def get_env_bool(key: str, default: bool = False) -> bool: OPENC3_SCOPE = os.environ.get(_openc3_scope, "DEFAULT") OPENC3_API_PASSWORD = os.environ.get(_openc3_api_password) +OPENC3_SERVICE_PASSWORD = os.environ.get(_openc3_service_password) OPENC3_LOG_LEVEL = os.environ.get(_openc3_log_level, "INFO") OPENC3_NO_STORE = os.environ.get(_openc3_no_store) OPENC3_API_USER = os.environ.get(_openc3_api_user) diff --git a/openc3/python/openc3/utilities/authentication.py b/openc3/python/openc3/utilities/authentication.py index 2b15d439f7..b73f601805 100644 --- a/openc3/python/openc3/utilities/authentication.py +++ b/openc3/python/openc3/utilities/authentication.py @@ -34,13 +34,26 @@ class OpenC3AuthenticationRetryableError(OpenC3AuthenticationError): # OpenC3 COSMOS Core authentication code class OpenC3Authentication: def __init__(self): - self._token = OPENC3_API_PASSWORD - if not self._token: + password = OPENC3_API_PASSWORD + if not password: raise OpenC3AuthenticationError("Authentication requires environment variable OPENC3_API_PASSWORD") + self.service = password == OPENC3_SERVICE_PASSWORD + response = Session().post(self._generate_auth_url(), json={"token": password}, headers={"Content-Type": "application/json"}) + self._token = response.text + if not self._token: + raise OpenC3AuthenticationError("Authentication failed. Please check the password in the environment variable OPENC3_API_PASSWORD") def token(self, include_bearer=True): return self._token + def _generate_auth_url(self): + schema = OPENC3_API_SCHEMA or "http" + hostname = OPENC3_API_HOSTNAME or ("127.0.0.1" if OPENC3_DEVEL else "openc3-cosmos-cmd-tlm-api") + port = OPENC3_API_PORT or "2901" + port = int(port) + endpoint = "auth/verify_service" if self.service else "auth/verify" + return f"{schema}://{hostname}:{port}/openc3-api/{endpoint}" + # OpenC3 enterprise Keycloak authentication code class OpenC3KeycloakAuthentication(OpenC3Authentication): diff --git a/openc3/spec/spec_helper.rb b/openc3/spec/spec_helper.rb index 5d81dac2e8..f36cf04e36 100644 --- a/openc3/spec/spec_helper.rb +++ b/openc3/spec/spec_helper.rb @@ -88,8 +88,19 @@ module OpenC3 # Create a easy alias to the base of the spec directory SPEC_DIR = File.dirname(__FILE__) $openc3_scope = ENV['OPENC3_SCOPE'] -$openc3_token = ENV['OPENC3_API_PASSWORD'] +$openc3_password = ENV['OPENC3_API_PASSWORD'] $openc3_authorize = false +$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 require 'openc3/utilities/store_queued' @@ -216,7 +227,8 @@ def mock_redis OpenC3::StoreQueued.instance_variable_set(:@instance, nil) OpenC3::EphemeralStoreQueued.instance_variable_set(:@instance, nil) require 'openc3/models/auth_model' - OpenC3::AuthModel.set($openc3_token, nil) + OpenC3::AuthModel.set($openc3_password, nil) + $openc3_token = OpenC3::AuthModel.generate_session() redis end @@ -313,6 +325,17 @@ def kill_leftover_threads c.max_formatted_output_length = nil # Prevent RSpec from doing truncation end + # Mock AuthModel verification methods to accept mocked tokens + config.before(:each) do + allow(OpenC3::AuthModel).to receive(:verify) do |token, **kwargs| + token == $openc3_token || token == $openc3_mock_token || token == ENV['OPENC3_SERVICE_PASSWORD'] || token == ENV['OPENC3_API_PASSWORD'] + end + + allow(OpenC3::AuthModel).to receive(:verify_no_service) do |token, **kwargs| + token == $openc3_token || token == $openc3_mock_token || token == ENV['OPENC3_API_PASSWORD'] + end + end + # Store standard output global and CONSTANT since we will mess with them config.before(:all) do $saved_stdout_global = $stdout diff --git a/openc3/spec/utilities/authentication_spec.rb b/openc3/spec/utilities/authentication_spec.rb index f894d7ff58..1a9fc6516b 100644 --- a/openc3/spec/utilities/authentication_spec.rb +++ b/openc3/spec/utilities/authentication_spec.rb @@ -32,15 +32,9 @@ module OpenC3 it "initializes with OPENC3_API_PASSWORD" do ENV['OPENC3_API_PASSWORD'] = 'test_password' auth = OpenC3Authentication.new - expect(auth.token).to eq('test_password') - end - end - - describe "token" do - it "returns the token from environment" do - ENV['OPENC3_API_PASSWORD'] = 'my_token' - auth = OpenC3Authentication.new - expect(auth.token).to eq('my_token') + # Initialization calls a method that uses Faraday to get a token from the server. + # It's mocked to return $openc3_mock_token for unit testing. + expect(auth.token).to eq($openc3_mock_token) end end end