added ability to link accounts with `account🔗token` + `account🔗add` & switch between them with `i:am`/`we:are` bangtags; remove links with `account🔗del:USERNAME` or `account🔗clear`; list links with `account🔗list`
parent
647ac0f86a
commit
da389a664b
|
@ -8,6 +8,7 @@ class Auth::SessionsController < Devise::SessionsController
|
|||
skip_before_action :require_no_authentication, only: [:create]
|
||||
skip_before_action :check_user_permissions, only: [:destroy]
|
||||
prepend_before_action :authenticate_with_two_factor, if: :two_factor_enabled?, only: [:create]
|
||||
prepend_before_action :switch_user
|
||||
prepend_before_action :set_pack
|
||||
before_action :set_instance_presenter, only: [:new]
|
||||
before_action :set_body_classes
|
||||
|
@ -52,6 +53,10 @@ class Auth::SessionsController < Devise::SessionsController
|
|||
params.require(:user).permit(:email, :password, :otp_attempt)
|
||||
end
|
||||
|
||||
def switch_params
|
||||
params.permit(:switch_to)
|
||||
end
|
||||
|
||||
def after_sign_in_path_for(resource)
|
||||
last_url = stored_location_for(:user)
|
||||
|
||||
|
@ -107,6 +112,15 @@ class Auth::SessionsController < Devise::SessionsController
|
|||
render :two_factor
|
||||
end
|
||||
|
||||
def switch_user
|
||||
return unless switch_params[:switch_to].present? && current_user.present?
|
||||
target_user = User.find_by(id: switch_params[:switch_to])
|
||||
return unless target_user.present? && current_user.in?(target_user.linked_users)
|
||||
self.resource = target_user
|
||||
sign_in(target_user)
|
||||
return root_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_pack
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
import { updateNotifications, expandNotifications } from './notifications';
|
||||
import { fetchFilters } from './filters';
|
||||
import { getLocale } from 'mastodon/locales';
|
||||
import { resetCompose } from 'flavours/glitch/actions/compose';
|
||||
|
||||
const { messages } = getLocale();
|
||||
|
||||
|
@ -40,6 +41,14 @@ export function connectTimelineStream (timelineId, path, pollingRefresh = null,
|
|||
case 'filters_changed':
|
||||
dispatch(fetchFilters());
|
||||
break;
|
||||
case 'switch_accounts':
|
||||
dispatch(resetCompose());
|
||||
window.location.href = `/auth/sign_in?switch_to=${data.payload}`
|
||||
break;
|
||||
case 'refresh':
|
||||
dispatch(resetCompose());
|
||||
window.location.reload();
|
||||
break;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -7,6 +7,7 @@ class Bangtags
|
|||
def initialize(status)
|
||||
@status = status
|
||||
@account = status.account
|
||||
@user = @account.user
|
||||
@parent_status = Status.find(status.in_reply_to_id) if status.in_reply_to_id
|
||||
|
||||
@crunch_newlines = false
|
||||
|
@ -58,7 +59,7 @@ class Bangtags
|
|||
# list of post-processing commands
|
||||
@post_cmds = []
|
||||
# hash of bangtag variables
|
||||
@vars = account.user.vars
|
||||
@vars = @user.vars
|
||||
# keep track of what variables we're appending the value of between chunks
|
||||
@vore_stack = []
|
||||
# keep track of what type of nested components are active so we can !end them in order
|
||||
|
@ -348,7 +349,7 @@ class Bangtags
|
|||
chunk = TagManager.instance.url_for(@parent_status)
|
||||
when 'tag', 'untag'
|
||||
chunk = nil
|
||||
next unless @parent_status.account.id == @account.id || @account.user.admin?
|
||||
next unless @parent_status.account.id == @account.id || @user.admin?
|
||||
tags = cmd[2..-1].map {|t| t.gsub(':', '.')}
|
||||
if cmd[1].downcase == 'tag'
|
||||
add_tags(@parent_status, *tags)
|
||||
|
@ -376,7 +377,7 @@ class Bangtags
|
|||
plain.gsub!(/ dot /i, '.')
|
||||
chunk = plain.scan(/[\w\-]+\.[\w\-]+(?:\.[\w\-]+)*/).uniq.join(' ')
|
||||
when 'noreplies', 'noats', 'close'
|
||||
next unless @parent_status.account.id == @account.id || @account.user.admin?
|
||||
next unless @parent_status.account.id == @account.id || @user.admin?
|
||||
@parent_status.reject_replies = true
|
||||
@parent_status.save
|
||||
Rails.cache.delete("statuses/#{@parent_status.id}")
|
||||
|
@ -490,6 +491,7 @@ class Bangtags
|
|||
end
|
||||
else
|
||||
who = cmd[0]
|
||||
next if switch_account(who.strip)
|
||||
name = who.downcase.gsub(/\s+/, '').strip
|
||||
description = cmd[1..-1].join(':').strip
|
||||
if description.blank?
|
||||
|
@ -677,7 +679,7 @@ class Bangtags
|
|||
chunk = chunk.join
|
||||
when 'admin'
|
||||
chunk = nil
|
||||
next unless @account.user.admin?
|
||||
next unless @user.admin?
|
||||
next if cmd[1].nil?
|
||||
@status.visibility = :local
|
||||
@status.local_only = true
|
||||
|
@ -710,6 +712,78 @@ class Bangtags
|
|||
@tf_cmds.push(cmd)
|
||||
@component_stack.push(:tf)
|
||||
end
|
||||
when 'account'
|
||||
chunk = nil
|
||||
cmd.shift
|
||||
c = cmd.shift
|
||||
next if c.nil?
|
||||
@status.visibility = :direct
|
||||
@status.local_only = true
|
||||
@status.content_type = 'text/markdown'
|
||||
@chunks << "\n# <code>#!</code><code>account:#{c.downcase}</code>:\n<hr />\n"
|
||||
output = []
|
||||
case c.downcase
|
||||
when 'link'
|
||||
c = cmd.shift
|
||||
next if c.nil?
|
||||
case c.downcase
|
||||
when 'add'
|
||||
target = cmd.shift
|
||||
token = cmd.shift
|
||||
if target.blank? || token.blank?
|
||||
output << "\u274c Missing account parameter." if target.blank?
|
||||
output << "\u274c Missing token parameter." if token.blank?
|
||||
break
|
||||
end
|
||||
target_acct = Account.find_local(target)
|
||||
if target_acct&.user.nil? || target_acct.id == @account.id
|
||||
output << "\u274c Invalid account."
|
||||
break
|
||||
end
|
||||
unless token == target_acct.user.vars['_account:link:token']
|
||||
output << "\u274c Invalid token."
|
||||
break
|
||||
end
|
||||
target_acct.user.vars['_account:link:token'] = nil
|
||||
target_acct.user.save
|
||||
LinkedUser.find_or_create_by!(user_id: @user.id, target_user_id: target_acct.user.id)
|
||||
LinkedUser.find_or_create_by!(user_id: target_acct.user.id, target_user_id: @user.id)
|
||||
output << "\u2705 Linked with <strong>@\u200c#{target}</strong>."
|
||||
when 'del', 'delete'
|
||||
cmd.each do |target|
|
||||
target_acct = Account.find_local(target)
|
||||
next if target_acct&.user.nil? || target_acct.id == @account.id
|
||||
LinkedUser.where(user_id: @user.id, target_user_id: target_acct.user.id).destroy_all
|
||||
LinkedUser.where(user_id: target_acct.user.id, target_user_id: @user.id).destroy_all
|
||||
output << "\u2705 <strong>@\u200c#{target}</strong> unlinked."
|
||||
end
|
||||
when 'clear', 'delall', 'deleteall'
|
||||
LinkedUser.where(target_user_id: @user.id).destroy_all
|
||||
LinkedUser.where(user_id: @user.id).destroy_all
|
||||
output << "\u2705 Cleared all links."
|
||||
when 'token'
|
||||
@vars['_account:link:token'] = SecureRandom.urlsafe_base64(32)
|
||||
output << "Account link token is:"
|
||||
output << "<code>#{@vars['_account:link:token']}</code>"
|
||||
output << ''
|
||||
output << "On the local account you want to link, paste:"
|
||||
output << "<code>#!account:link:add:#{@account.username}:#{@vars['_account:link:token']}</code>"
|
||||
output << ''
|
||||
output << 'The token can only be used once.'
|
||||
output << ''
|
||||
output << "\xe2\x9a\xa0\xef\xb8\x8f <strong>This grants full access to your account! Be careful!</strong>"
|
||||
when 'list'
|
||||
@user.linked_users.find_each do |linked_user|
|
||||
if linked_user&.account.nil?
|
||||
link.destroy
|
||||
else
|
||||
output << "\u2705 <strong>@\u200c#{linked_user.account.username}</strong>"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
output = ['<em>No action.</em>'] if output.blank?
|
||||
chunk = output.join("\n") + "\n"
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -741,7 +815,7 @@ class Bangtags
|
|||
@vars['_tf:head:full'] = c + parts.count
|
||||
chunk = parts.join(' ')
|
||||
when 'admin'
|
||||
next unless @account.user.admin?
|
||||
next unless @user.admin?
|
||||
next if tf_cmd[1].nil? || chunk.start_with?('`admin:')
|
||||
output = []
|
||||
action = tf_cmd[1].downcase
|
||||
|
@ -817,7 +891,7 @@ class Bangtags
|
|||
|
||||
postprocess_before_save
|
||||
|
||||
account.user.save
|
||||
@user.save
|
||||
|
||||
text = @chunks.join
|
||||
text.gsub!(/\n\n+/, "\n") if @crunch_newlines
|
||||
|
@ -848,7 +922,7 @@ class Bangtags
|
|||
@vars.delete("_media:#{media_idx}:desc")
|
||||
end
|
||||
when 'admin'
|
||||
next unless @account.user.admin?
|
||||
next unless @user.admin?
|
||||
next if post_cmd[1].nil?
|
||||
case post_cmd[1]
|
||||
when 'eval'
|
||||
|
@ -879,9 +953,9 @@ class Bangtags
|
|||
next
|
||||
end
|
||||
|
||||
name = @account.user.vars['_they:are']
|
||||
name = @user.vars['_they:are']
|
||||
if name.present?
|
||||
footer = "#{@account.user.vars["_they:are:#{name}"]} from @#{@account.username}"
|
||||
footer = "#{@user.vars["_they:are:#{name}"]} from @#{@account.username}"
|
||||
else
|
||||
footer = "@#{@account.username}"
|
||||
end
|
||||
|
@ -935,6 +1009,13 @@ class Bangtags
|
|||
from_status.save
|
||||
end
|
||||
|
||||
def switch_account(target_acct)
|
||||
target_acct = Account.find_local(target_acct)
|
||||
return false unless target_acct&.user.present? && target_acct.user.in?(@user.linked_users)
|
||||
Redis.current.publish("timeline:#{@account.id}", Oj.dump(event: :switch_accounts, payload: target_acct.user.id))
|
||||
true
|
||||
end
|
||||
|
||||
def html_entities
|
||||
@html_entities ||= HTMLEntities.new
|
||||
end
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
# == Schema Information
|
||||
#
|
||||
# Table name: linked_users
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# user_id :bigint(8)
|
||||
# target_user_id :bigint(8)
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
|
||||
class LinkedUser < ApplicationRecord
|
||||
belongs_to :user, inverse_of: :linked_users
|
||||
belongs_to :target_user, class_name: 'User'
|
||||
|
||||
validates :user_id, uniqueness: { scope: :target_user_id }
|
||||
end
|
|
@ -306,6 +306,14 @@ class Status < ApplicationRecord
|
|||
update_status_stat!(key => [public_send(key) - 1, 0].max)
|
||||
end
|
||||
|
||||
def session=(value)
|
||||
@session = value
|
||||
end
|
||||
|
||||
def session
|
||||
@session || nil
|
||||
end
|
||||
|
||||
after_create_commit :increment_counter_caches
|
||||
after_destroy_commit :decrement_counter_caches
|
||||
|
||||
|
|
|
@ -75,6 +75,9 @@ class User < ApplicationRecord
|
|||
has_many :applications, class_name: 'Doorkeeper::Application', as: :owner
|
||||
has_many :backups, inverse_of: :user
|
||||
|
||||
has_many :user_links, class_name: 'LinkedUser', foreign_key: :target_user_id, dependent: :destroy, inverse_of: :user
|
||||
has_many :linked_users, through: :user_links, source: :user
|
||||
|
||||
has_one :invite_request, class_name: 'UserInviteRequest', inverse_of: :user, dependent: :destroy
|
||||
accepts_nested_attributes_for :invite_request, reject_if: ->(attributes) { attributes['text'].blank? }
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
class CreateLinkedUsers < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
create_table :linked_users do |t|
|
||||
t.references :user, foreign_key: { on_delete: :cascade }
|
||||
t.references :target_user, foreign_key: { to_table: 'users', on_delete: :cascade }
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
add_index :linked_users , [:user_id, :target_user_id], :unique => true
|
||||
end
|
||||
end
|
14
db/schema.rb
14
db/schema.rb
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 2019_08_05_064643) do
|
||||
ActiveRecord::Schema.define(version: 2019_08_05_203816) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
@ -361,6 +361,16 @@ ActiveRecord::Schema.define(version: 2019_08_05_064643) do
|
|||
t.index ["user_id"], name: "index_invites_on_user_id"
|
||||
end
|
||||
|
||||
create_table "linked_users", force: :cascade do |t|
|
||||
t.bigint "user_id"
|
||||
t.bigint "target_user_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["target_user_id"], name: "index_linked_users_on_target_user_id"
|
||||
t.index ["user_id", "target_user_id"], name: "index_linked_users_on_user_id_and_target_user_id", unique: true
|
||||
t.index ["user_id"], name: "index_linked_users_on_user_id"
|
||||
end
|
||||
|
||||
create_table "list_accounts", force: :cascade do |t|
|
||||
t.bigint "list_id", null: false
|
||||
t.bigint "account_id", null: false
|
||||
|
@ -827,6 +837,8 @@ ActiveRecord::Schema.define(version: 2019_08_05_064643) do
|
|||
add_foreign_key "identities", "users", name: "fk_bea040f377", on_delete: :cascade
|
||||
add_foreign_key "imports", "accounts", name: "fk_6db1b6e408", on_delete: :cascade
|
||||
add_foreign_key "invites", "users", on_delete: :cascade
|
||||
add_foreign_key "linked_users", "users", column: "target_user_id", on_delete: :cascade
|
||||
add_foreign_key "linked_users", "users", on_delete: :cascade
|
||||
add_foreign_key "list_accounts", "accounts", on_delete: :cascade
|
||||
add_foreign_key "list_accounts", "follows", on_delete: :cascade
|
||||
add_foreign_key "list_accounts", "lists", on_delete: :cascade
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Fabricator(:linked_user) do
|
||||
user nil
|
||||
target_user nil
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe LinkedUser, type: :model do
|
||||
pending "add some examples to (or delete) #{__FILE__}"
|
||||
end
|
Loading…
Reference in New Issue