Token issuance table (DRAFT)

pull/757/head
Rick Carlino 2018-03-28 16:28:21 -05:00
parent 43bd7a7a1c
commit ef3259f664
11 changed files with 87 additions and 11 deletions

View File

@ -2,14 +2,16 @@ module Api
class TokensController < Api::AbstractController
skip_before_action :authenticate_user!, only: :create
skip_before_action :check_fbos_version, only: :create
before_action :clean_out_old_tokens
CREDS = Auth::CreateTokenFromCredentials
NO_CREDS = Auth::CreateToken
NO_USER_ATTR = "API requets need a `user` attribute that is a JSON object."
# Give you the same token, but reloads all claims except `exp`
def show
mutate Auth::ReloadToken.run(jwt: request.headers["Authorization"],
fbos_version: fbos_version)
mutate Auth::ReloadToken
.run(jwt: request.headers["Authorization"], fbos_version: fbos_version)
end
def create
@ -39,6 +41,12 @@ module Api
request.xhr?
end
# Every time a token is created, sweep the old TokenIssuances out of the
# database.
def clean_out_old_tokens
TokenIssuance.where("exp < ?", Time.now.to_i).destroy_all
end
def if_properly_formatted
user = params.as_json.deep_symbolize_keys.fetch(:user, {})
# If data handling for this method gets any more complicated,

View File

@ -36,14 +36,17 @@ module Api
.as_json
.merge!(params.as_json["user"] || {})
.deep_symbolize_keys
jti = RequestStore
.store
.dig(:jwt, :jti) || "NONE FOUND"
{email: user[:email],
name: user[:name],
password: user[:password],
password_confirmation: user[:password_confirmation],
new_password: user[:new_password],
new_password_confirmation: user[:new_password_confirmation],
agree_to_terms: user[:agree_to_terms]}
agree_to_terms: user[:agree_to_terms],
jti: jti}
end
end
end

View File

@ -26,10 +26,12 @@ class SessionToken < AbstractJwtToken
raise Errors::Forbidden, MUST_VERIFY
end
url = CalculateUpgrade.run!(version: fbos_version)
jti = SecureRandom.uuid
TokenIssuance.create!(device_id: user.device.id, exp: exp, jti: jti)
self.new([{ aud: aud,
sub: user.id,
iat: iat,
jti: SecureRandom.uuid,
jti: jti,
iss: iss,
exp: exp,
mqtt: MQTT,

View File

@ -0,0 +1,3 @@
class TokenIssuance < ApplicationRecord
belongs_to :device
end

View File

@ -10,11 +10,21 @@ module Auth
RequestStore.store[:jwt] = claims.deep_symbolize_keys
u = User.includes(:device).find(claims["sub"])
Device.current = u.device
TokenIssuance.find_by!(jti: claims["jti"])
u
rescue JWT::DecodeError, ActiveRecord::RecordNotFound
add_error :jwt, :decode_error, Auth::ReloadToken::BAD_SUB
end
# Triggers ActiveRecord::RecordNotFound if TokenIssuance is expired or
# missing.
def check_it(claims)
device_id = claims["bot"].gsub("device_", "").to_i
TokenIssuance
.where("exp > ?", Time.now.to_i)
.find_by!(jti: claims["jti"], bot: device_id)
end
def just_the_token
# Token auth requires the `authorization` header to be in the format of:
# "Authorization: Bearer <INSERT_TOKEN_HERE>"

View File

@ -10,6 +10,9 @@ module Users
string :password
string :new_password
string :new_password_confirmation
# Lock everyone out except for the person who requested
# the password change.
string :old_token_jti
end
def validate
@ -24,12 +27,23 @@ module Users
excludable = [:user]
excludable.push(:email) unless skip_email_stuff
user.update_attributes!(inputs.except(:user, :email))
SendFactoryResetJob.perform_later(user.device) if inputs[:password]
if inputs[:password]
SendFactoryResetJob.perform_later(user.device)
delete_all_tokens_except_this_one
end
user.reload
end
private
def delete_all_tokens_except_this_one
TokenIssuance
.where(device_id: user.device.id)
.where
.not(jti: old_token_jti)
.destroy_all
end
# Self hosted users will often not have an email server.
# We can update emails immediately in those circumstances.
def skip_email_stuff

View File

@ -0,0 +1,11 @@
class CreateTokenIssuances < ActiveRecord::Migration[5.1]
def change
create_table :token_issuances do |t|
t.references :device, foreign_key: true, null: false
t.integer :exp, null: false
t.string :jti, null: false, limit: 45
t.timestamps
end
end
end

View File

@ -0,0 +1,11 @@
class DropTokenExpirations < ActiveRecord::Migration[5.1]
def change
drop_table :token_expirations do |t|
t.string :sub
t.integer :exp
t.string :jti
t.datetime :created_at, null: false
t.datetime :updated_at, null: false
end
end
end

View File

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20180326160853) do
ActiveRecord::Schema.define(version: 20180328212540) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -358,12 +358,13 @@ ActiveRecord::Schema.define(version: 20180326160853) do
t.index ["device_id"], name: "index_sequences_on_device_id"
end
create_table "token_expirations", id: :serial, force: :cascade do |t|
t.string "sub"
t.integer "exp"
t.string "jti"
create_table "token_issuances", force: :cascade do |t|
t.bigint "device_id", null: false
t.integer "exp", null: false
t.string "jti", limit: 45, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["device_id"], name: "index_token_issuances_on_device_id"
end
create_table "tool_slots", id: :serial, force: :cascade do |t|
@ -466,5 +467,6 @@ ActiveRecord::Schema.define(version: 20180326160853) do
add_foreign_key "primary_nodes", "sequences"
add_foreign_key "sensor_readings", "devices"
add_foreign_key "sensors", "devices"
add_foreign_key "token_issuances", "devices"
add_foreign_key "tool_slots", "tools"
end

View File

@ -0,0 +1,7 @@
FactoryBot.define do
factory :token_issuance do
device
exp { (Time.now + 4.days).to_i }
jti { SecureRandom.uuid }
end
end

View File

@ -0,0 +1,5 @@
require 'spec_helper'
RSpec.describe TokenIssuance, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end