Add ability to revoke recorder tokens

private-asciicasts
Marcin Kulik 9 years ago
parent fd03554e2e
commit e05fbd574f

@ -30,3 +30,7 @@
.actions .actions
margin: 20px 0 0 0 margin: 20px 0 0 0
.edit-page
> .row:last-child
margin-top: 30px

@ -5,10 +5,17 @@ class ApiTokensController < ApplicationController
def create def create
current_user.assign_api_token(params[:api_token]) current_user.assign_api_token(params[:api_token])
redirect_to profile_path(current_user), redirect_to profile_path(current_user),
notice: "Successfully registered your API token. ^5" notice: "Successfully registered your recorder token."
rescue ActiveRecord::RecordInvalid, ApiToken::ApiTokenTakenError rescue ActiveRecord::RecordInvalid, ApiToken::ApiTokenTakenError
render :error render :error
end end
def destroy
api_token = ApiToken.find(params[:id])
authorize api_token
api_token.revoke!
redirect_to edit_user_path, notice: "Token revoked."
end
end end

@ -15,18 +15,18 @@ class UsersController < ApplicationController
end end
def edit def edit
@user = current_user authorize current_user
authorize @user render locals: { page: UserEditPagePresenter.new(current_user) }
end end
def update def update
@user = User.find(current_user.id) authorize current_user
authorize @user user = User.find(current_user.id)
if @user.update_attributes(update_params) if user.update_attributes(update_params)
redirect_to profile_path(@user), notice: 'Account settings saved.' redirect_to profile_path(user), notice: 'Account settings saved.'
else else
render :edit, status: 422 render :edit, status: 422, locals: { page: UserEditPagePresenter.new(user) }
end end
end end

@ -7,6 +7,9 @@ class ApiToken < ActiveRecord::Base
validates :user, :token, presence: true validates :user, :token, presence: true
validates :token, uniqueness: true validates :token, uniqueness: true
scope :active, -> { where(revoked_at: nil) }
scope :revoked, -> { where('revoked_at IS NOT NULL') }
def self.for_token(token) def self.for_token(token)
where(token: token).first where(token: token).first
end end
@ -27,6 +30,10 @@ class ApiToken < ActiveRecord::Base
user.merge_to(target_user) user.merge_to(target_user)
end end
def revoke!
update!(revoked_at: Time.now)
end
private private
def taken? def taken?

@ -56,6 +56,14 @@ class User < ActiveRecord::Base
new(temporary_username: 'anonymous') new(temporary_username: 'anonymous')
end end
def active_api_tokens
api_tokens.active
end
def revoked_api_tokens
api_tokens.revoked
end
def confirmed? def confirmed?
email.present? email.present?
end end

@ -0,0 +1,9 @@
class ApiTokenPolicy < ApplicationPolicy
def destroy?
return false unless user
user.admin? || record.user == user
end
end

@ -0,0 +1,27 @@
class UserEditPagePresenter
attr_reader :user
def initialize(user)
@user = user
end
def active_tokens
sort(user.active_api_tokens)
end
def revoked_tokens
sort(user.revoked_api_tokens)
end
def show_tokens?
!active_tokens.empty? || !revoked_tokens.empty?
end
private
def sort(tokens)
tokens.sort_by { |token| token.created_at }.reverse
end
end

@ -66,9 +66,9 @@ publishing it on asciinema.org.
## `auth` ## `auth`
__Assign local API token to asciinema.org account.__ __Assign local recorder token to asciinema.org account.__
On every machine you install asciinema recorder, you get a new, unique API On every machine you install asciinema recorder, you get a new, unique recorder
token. This command connects this local token with your asciinema.org account, token. This command connects this local token with your asciinema.org account,
and links all asciicasts recorded on this machine with the account. and links all asciicasts recorded on this machine with the account.
@ -79,6 +79,6 @@ URL.
NOTE: it is __necessary__ to do this if you want to __edit or delete__ your NOTE: it is __necessary__ to do this if you want to __edit or delete__ your
recordings on asciinema.org. recordings on asciinema.org.
You can synchronize your `~/.asciinema/config` file (which keeps the API You can synchronize your `~/.asciinema/config` file (which keeps the token)
token) across the machines but that's not necessary. You can assign new across the machines but that's not necessary. You can assign new recorder
tokens to your account from as many machines as you want. tokens to your account from as many machines as you want.

@ -1,8 +1,8 @@
.container .container.edit-page
.row .row
.col-md-9 .col-md-9
= horizontal_form_for @user do |f| = horizontal_form_for page.user do |f|
legend Your settings legend Account settings
= f.input :username = f.input :username
= f.input :email, required: true = f.input :email, required: true
@ -11,3 +11,40 @@
= f.buttons do = f.buttons do
= f.button :submit, 'Save', class: 'btn-primary' = f.button :submit, 'Save', class: 'btn-primary'
= link_to 'Cancel', profile_path(current_user), class: 'btn' = link_to 'Cancel', profile_path(current_user), class: 'btn'
.row
.col-md-12
legend Recorder tokens
- if page.show_tokens?
p The following recorder tokens have been associated with your account:
- unless page.active_tokens.empty?
ul
- page.active_tokens.each do |token|
li
= token.token
' registered
= time_ago_tag token.created_at
' -
= link_to 'Revoke', api_token_path(token), method: :delete
- unless page.revoked_tokens.empty?
ul
- page.revoked_tokens.each do |token|
li.revoked-token
= token.token
' registered
= time_ago_tag token.created_at
' , revoked
= time_ago_tag token.revoked_at
- else
p
| If you want your recordings to be assigned to your profile
you have to register your local recorder token.
p
' There is currently no recorder token associated with your account.
Run
code asciinema auth
| in your terminal to register one.

@ -33,6 +33,7 @@ Rails.application.routes.draw do
get "/login/:token" => "sessions#create", as: :login_token get "/login/:token" => "sessions#create", as: :login_token
get "/logout" => "sessions#destroy" get "/logout" => "sessions#destroy"
resources :api_tokens, only: [:create, :destroy]
get "/connect/:api_token" => "api_tokens#create" get "/connect/:api_token" => "api_tokens#create"
resource :user resource :user

@ -0,0 +1,5 @@
class AddRevokedAtToApiTokens < ActiveRecord::Migration
def change
add_column :api_tokens, :revoked_at, :datetime
end
end

@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20150327171201) do ActiveRecord::Schema.define(version: 20150401161102) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -21,6 +21,7 @@ ActiveRecord::Schema.define(version: 20150327171201) do
t.string "token", null: false t.string "token", null: false
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.datetime "revoked_at"
end end
add_index "api_tokens", ["token"], name: "index_api_tokens_on_token", using: :btree add_index "api_tokens", ["token"], name: "index_api_tokens_on_token", using: :btree

@ -0,0 +1,38 @@
require 'rails_helper'
feature "Recorder tokens management" do
let!(:user) { create(:user) }
scenario 'Listing tokens when user has none' do
login_as user
visit edit_user_path
expect(page).to have_content('asciinema auth')
end
scenario 'Listing tokens when user has some' do
api_token = create(:api_token, user: user)
login_as user
visit edit_user_path
expect(page).to have_content(api_token.token)
expect(page).to have_link('Revoke')
expect(page).to have_no_content('asciinema auth')
end
scenario 'Revoking a token' do
api_token = create(:api_token, user: user)
login_as user
visit edit_user_path
click_on "Revoke"
expect(page).to have_content(api_token.token)
expect(page).to have_no_link('Revoke')
end
end

@ -0,0 +1,27 @@
require 'rails_helper'
describe ApiTokenPolicy do
subject { described_class }
permissions :destroy? do
it "denies access if user is nil" do
expect(subject).not_to permit(nil, ApiToken.new)
end
it "grants access if user is admin" do
user = stub_model(User, admin?: true)
expect(subject).to permit(user, ApiToken.new)
end
it "grants access if user is the owner of the token" do
user = stub_model(User, admin?: false)
expect(subject).to permit(user, ApiToken.new(user: user))
end
it "denies access if user isn't the owner of the token" do
expect(subject).not_to permit(User.new, ApiToken.new(user: User.new))
end
end
end
Loading…
Cancel
Save