Skip to content

Commit

Permalink
Switch to session token based auth
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanmelt committed Sep 30, 2024
1 parent a34e61a commit b5ab34f
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 13 deletions.
6 changes: 4 additions & 2 deletions openc3-cosmos-cmd-tlm-api/app/controllers/auth_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,12 @@ def token_exists
def verify
begin
if OpenC3::AuthModel.verify(params[:token])
head :ok
render :plain => OpenC3::AuthModel.generate_session()
else
head :unauthorized
end
rescue StandardError => e
OpenC3::Logger.error(e.formatted)
render :json => { :status => 'error', :message => e.message, 'type' => e.class }, :status => 500
end
end
Expand All @@ -48,8 +49,9 @@ def set
# Set throws an exception if it fails for any reason
OpenC3::AuthModel.set(params[:token], params[:old_token])
OpenC3::Logger.info("Password changed", user: username())
head :ok
render :plain => OpenC3::AuthModel.generate_session()
rescue StandardError => e
OpenC3::Logger.error(e.formatted)
render :json => { :status => 'error', :message => e.message, 'type' => e.class }, :status => 500
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ def active()
end

def logout()
OpenC3::AuthModel.logout
head :ok
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,12 +140,12 @@ export default {
showReset: function () {
this.reset = true
},
login: function () {
localStorage.openc3Token = this.password
login: function (response) {
localStorage.openc3Token = response.data
const redirect = new URLSearchParams(window.location.search).get(
'redirect',
)
if (redirect[0] === '/' && redirect[1] !== '/') {
if (redirect.startsWith('/tools/')) {
// Valid relative redirect URL
window.location = decodeURI(redirect)
} else {
Expand All @@ -161,7 +161,7 @@ export default {
...this.options,
})
.then((response) => {
this.login()
this.login(response)
})
.catch((error) => {
this.alert = 'Incorrect password'
Expand All @@ -177,7 +177,9 @@ export default {
token: this.password,
},
...this.options,
}).then(this.login)
}).then((response) => {
this.login(response)
})
},
},
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def delete_temp

def body
return unless authorization('script_view')
scope, name = sanitize_params([:scope, :name])
scope, name = sanitize_params([:scope, :name], :allow_forward_slash => true)
return unless scope

file = Script.body(scope, name)
Expand Down Expand Up @@ -126,15 +126,15 @@ def run

def lock
return unless authorization('script_edit')
scope, name = sanitize_params([:scope, :name])
scope, name = sanitize_params([:scope, :name], :allow_forward_slash => true)
return unless scope
Script.lock(scope, name, username())
render status: 200
end

def unlock
return unless authorization('script_edit')
scope, name = sanitize_params([:scope, :name])
scope, name = sanitize_params([:scope, :name], :allow_forward_slash => true)
return unless scope
locked_by = Script.locked?(scope, name)
Script.unlock(scope, name) if username() == locked_by
Expand All @@ -143,7 +143,7 @@ def unlock

def destroy
return unless authorization('script_edit')
scope, name = sanitize_params([:scope, :name])
scope, name = sanitize_params([:scope, :name], :allow_forward_slash => true)
return unless scope
Script.destroy(scope, name)
OpenC3::Logger.info("Script destroyed: #{name}", scope: scope, user: username())
Expand Down
33 changes: 31 additions & 2 deletions openc3/lib/openc3/models/auth_model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,22 @@
# if purchased from OpenC3, Inc.

require 'digest'
require 'securerandom'
require 'openc3/utilities/store'

module OpenC3
class AuthModel
PRIMARY_KEY = 'OPENC3__TOKEN'
SESSIONS_KEY = 'OPENC3__SESSIONS'

TOKEN_CACHE_TIMEOUT = 5
SESSION_CACHE_TIMEOUT = 5
@@token_cache = nil
@@token_cache_time = nil
@@session_cache = nil
@@session_cache_time = nil

MIN_TOKEN_LENGTH = 8

def self.set?(key = PRIMARY_KEY)
Store.exists(key) == 1
Expand All @@ -38,14 +45,23 @@ def self.set?(key = PRIMARY_KEY)
def self.verify(token)
return false if token.nil? or token.empty?

time = Time.now
return true if @@session_cache and (time - @@session_cache_time) < SESSION_CACHE_TIMEOUT and @@session_cache[token]
token_hash = hash(token)
return true if @@token_cache and (Time.now - @@token_cache_time) < TOKEN_CACHE_TIMEOUT and @@token_cache == token_hash
return true if @@token_cache and (time - @@token_cache_time) < TOKEN_CACHE_TIMEOUT and @@token_cache == token_hash

# Check sessions
@@session_cache = Store.hgetall(SESSIONS_KEY)
@@session_cache_time = time
return true if @@session_cache[token]

# Check Direct password
@@token_cache = Store.get(PRIMARY_KEY)
@@token_cache_time = Time.now
@@token_cache_time = time
return true if @@token_cache == token_hash

# 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

Expand All @@ -54,6 +70,7 @@ def self.verify(token)

def self.set(token, old_token, key = PRIMARY_KEY)
raise "token must not be nil or empty" if token.nil? or token.empty?
raise "token must be at least 8 characters" if token.length < MIN_TOKEN_LENGTH

if set?(key)
raise "old_token must not be nil or empty" if old_token.nil? or old_token.empty?
Expand All @@ -62,6 +79,18 @@ def self.set(token, old_token, key = PRIMARY_KEY)
Store.set(key, hash(token))
end

def self.generate_session
token = SecureRandom.urlsafe_base64(nil, false)
Store.hset(SESSIONS_KEY, token, Time.now.iso8601)
return token
end

def self.logout
Store.del(SESSIONS_KEY)
@@sessions_cache = nil
@@sessions_cache_time = nil
end

def self.hash(token)
Digest::SHA2.hexdigest token
end
Expand Down

0 comments on commit b5ab34f

Please sign in to comment.