Add fbos_version as a param to token creation

pull/553/head
Rick Carlino 2017-12-05 16:17:22 -06:00
parent 694bcddce3
commit 1bc9cf98e5
15 changed files with 75 additions and 31 deletions

View File

@ -166,11 +166,20 @@ private
end
EXPECTED_VER = Gem::Version::new('5.0.0')
# Try to extract FarmBot OS version from user agent.
# If none found, return lowest allowable version + 1 "tiny" bump to prevent
# lockouts.
def fbos_version
when_farmbot_os do
Gem::Version::new(pretty_ua.upcase.split("/").last.split(" ").first)
end || EXPECTED_VER.bump
end
# This is how we lock old versions of FBOS out of the API:
def check_fbos_version
when_farmbot_os do
semver = pretty_ua.upcase.split("/").last.split(" ").first
bad_version unless Gem::Version::new(semver) >= EXPECTED_VER
bad_version unless fbos_version >= EXPECTED_VER
end
end

View File

@ -8,7 +8,8 @@ module Api
# Give you the same token, but reloads all claims except `exp`
def show
mutate Auth::ReloadToken.run(jwt: request.headers["Authorization"])
mutate Auth::ReloadToken.run(jwt: request.headers["Authorization"],
fbos_version: fbos_version)
end
def create
@ -48,7 +49,9 @@ module Api
credentials: user[:credentials],
agree_to_terms: !!user[:agree_to_terms],
host: $API_URL,
aud: guess_aud_claim })
aud: guess_aud_claim,
fbos_version: fbos_version
})
else
render json: {error: NO_USER_ATTR}, status: 422
end

View File

@ -20,7 +20,8 @@ class SessionToken < AbstractJwtToken
iat: Time.now.to_i,
exp: EXPIRY.from_now.to_i,
iss: $API_URL,
aud: AbstractJwtToken::UNKNOWN_AUD)
aud: AbstractJwtToken::UNKNOWN_AUD,
fbos_version:) # Gem::Version
unless user.verified?
Rollbar.info("Verification Error", email: user.email)
@ -42,9 +43,10 @@ class SessionToken < AbstractJwtToken
vhost: VHOST }])
end
def self.as_json(user, aud)
{ token: SessionToken.issue_to(user,
iss: $API_URL, aud: aud),
def self.as_json(user, aud, fbos_version)
{ token: SessionToken.issue_to(user, iss: $API_URL,
aud: aud,
fbos_version: fbos_version),
user: user }
end
end

View File

@ -5,6 +5,7 @@ module Auth
required do
string :email
string :password
model :fbos_version, class: Gem::Version
end
optional do
@ -24,7 +25,7 @@ module Auth
def execute
@user.update_attributes(agreed_to_terms_at: Time.now) if agree_to_terms
SessionToken.as_json(@user, aud)
SessionToken.as_json(@user, aud, fbos_version)
end
private

View File

@ -9,6 +9,7 @@ module Auth
required do
string :credentials
model :fbos_version, class: Gem::Version
end
def validate
@ -22,7 +23,7 @@ module Auth
end
def execute
SessionToken.as_json(user, AbstractJwtToken::BOT_AUD)
SessionToken.as_json(user, AbstractJwtToken::BOT_AUD, fbos_version)
end
private

View File

@ -5,7 +5,10 @@ module Auth
attr_reader :user
BAD_SUB = "Please log out and try again."
required { string :jwt }
required do
string :jwt
model :fbos_version, class: Gem::Version
end
def validate
@user = User.find_by_email_or_id(claims["sub"])
@ -14,8 +17,9 @@ module Auth
def execute
security_criticial_danger = claims["exp"] # Stop infinite sessions
token = SessionToken.issue_to(user,
aud: claims["aud"],
exp: security_criticial_danger)
aud: claims["aud"],
exp: security_criticial_danger,
fbos_version: fbos_version)
return { token: token }
end

View File

@ -16,8 +16,9 @@ module PasswordResets
def execute
user.update_attributes!(password: password,
password_confirmation: password_confirmation)
Auth::CreateToken.run!(email: user.email,
password: password)
Auth::CreateToken.run!(email: user.email,
password: password,
fbos_version: Gem::Version.new("999.9.9"),)
rescue JWT::ExpiredSignature
add_error :reset, :too_old, OLD_TOKEN
end

View File

@ -6,7 +6,8 @@ module Users
user.update_attributes!(confirmed_at: Time.now,
email: user.unconfirmed_email,
unconfirmed_email: nil)
SessionToken.as_json(user, AbstractJwtToken::HUMAN_AUD)
fbos_vers = Gem::Version.new("99.9.9") # Not relevant here, stubbing out.
SessionToken.as_json(user, AbstractJwtToken::HUMAN_AUD, fbos_vers)
end
end
end

View File

@ -9,7 +9,9 @@ module Users
def execute
user.confirmed_at = Time.now
user.save!
SessionToken.as_json(user.reload, AbstractJwtToken::HUMAN_AUD)
SessionToken.as_json(user.reload,
AbstractJwtToken::HUMAN_AUD,
Gem::Version.new("99.9.9"))
end
private

View File

@ -19,15 +19,16 @@ describe Api::PasswordResetsController do
end
it 'resets password using a reset token' do
params = { password: "xpassword123",
password_confirmation: "xpassword123",
id: PasswordResetToken
params = {password: "xpassword123",
password_confirmation: "xpassword123",
fbos_version: Gem::Version.new("999.9.9"),
id: PasswordResetToken
.issue_to(user)
.encoded }
put :update, params: params
expect(user
.reload
.valid_password?(params[:password])).to eq(true)
.reload
.valid_password?(params[:password])).to eq(true)
expect(response.status).to eq(200)
expect(json.keys).to include(:token)
expect(json.keys).to include(:user)

View File

@ -8,8 +8,10 @@ describe Api::PointsController do
FactoryBot.create(:user, device: device, password: "password123")
end
let(:auth_token) do
Auth::CreateToken
.run!(email: user.email, password: "password123")[:token].encoded
params = {email: user.email,
password: "password123",
fbos_version: Gem::Version.new("999.9.9")}
Auth::CreateToken.run!(params)[:token].encoded
end
it 'lists points' do

View File

@ -6,7 +6,9 @@ describe Api::TokensController do
describe '#show' do
let(:user) { FactoryBot.create(:user, password: "password") }
let(:auth_token) { SessionToken.issue_to(user) }
let(:auth_token) do
SessionToken.issue_to(user, fbos_version: Gem::Version.new("9.9.9"))
end
it 'creates a new token' do
request.headers["Authorization"] = "bearer #{auth_token.encoded}"

View File

@ -30,12 +30,18 @@ describe SessionToken do
end
it 'issues a token to a user' do
SessionToken.issue_to(user, iat: 000, exp: 456, iss: "//lycos.com:9867")
SessionToken.issue_to(user, iat: 000,
exp: 456,
iss: "//lycos.com:9867",
fbos_version: Gem::Version.new("9.9.9"))
end
it "doesn't honor expired tokens" do
user.update_attributes!(confirmed_at: Time.now)
token = SessionToken.issue_to(user, iat: 000, exp: 1, iss: "//lycos.com:9867")
token = SessionToken.issue_to(user, iat: 000,
exp: 1,
iss: "//lycos.com:9867",
fbos_version: Gem::Version.new("9.9.9"))
result = Auth::FromJWT.run(jwt: token.encoded)
expect(result.success?).to be(false)
expect(result.errors.values.first.message)
@ -46,7 +52,10 @@ describe SessionToken do
it "doesn't mint tokens for unverified users" do
user.update_attributes!(confirmed_at: nil)
expect {
SessionToken.issue_to(user, iat: 000, exp: 1, iss: "//lycos.com:9867")
SessionToken.issue_to(user, iat: 000,
exp: 1,
iss: "//lycos.com:9867",
fbos_version: Gem::Version.new("9.9.9"))
}.to raise_error(Errors::Forbidden)
end
else

View File

@ -12,7 +12,8 @@ describe Auth::FromJWT do
end
it 'rejects bad credentials' do
results = Auth::CreateTokenFromCredentials.run(credentials: "FOO" )
results = Auth::CreateTokenFromCredentials
.run(credentials: "FOO", fbos_version: Gem::Version.new("999.9.9"))
expect(results.success?).to eq(false)
expect(results.errors.message_list)
.to include(Auth::CreateTokenFromCredentials::BAD_KEY)
@ -23,7 +24,8 @@ describe Auth::FromJWT do
user = FactoryBot.create(:user, password: pw)
email = user.email
creds = fake_credentials(email, pw)
results = Auth::CreateTokenFromCredentials.run!(credentials: creds)
results = Auth::CreateTokenFromCredentials
.run!(credentials: creds, fbos_version: Gem::Version.new("999.9.9"))
expect(results[:token]).to be_kind_of(SessionToken)
expect(results[:user]).to eq(user)
end

View File

@ -1,8 +1,12 @@
require "spec_helper"
describe Auth::FromJWT do
FAKE_VERS = Gem::Version.new("99.9.9")
let(:user) { FactoryBot.create(:user) }
let(:token) { SessionToken.issue_to(user).encoded }
let(:token) do
SessionToken.issue_to(user, fbos_version: FAKE_VERS).encoded
end
fake = -> (sub) {
AbstractJwtToken.new([{ sub: sub,
iat: Time.now.to_i,