mirror of
https://github.com/mastodon/mastodon.git
synced 2024-08-20 21:08:15 -07:00
Remove unused E2EE messaging code
This commit is contained in:
parent
6d2ed0dcba
commit
189ee96857
131 changed files with 25 additions and 1557 deletions
1
Gemfile
1
Gemfile
|
@ -47,7 +47,6 @@ gem 'color_diff', '~> 0.1'
|
|||
gem 'csv', '~> 3.2'
|
||||
gem 'discard', '~> 1.2'
|
||||
gem 'doorkeeper', '~> 5.6'
|
||||
gem 'ed25519', '~> 1.3'
|
||||
gem 'fast_blank', '~> 1.0'
|
||||
gem 'fastimage'
|
||||
gem 'hiredis', '~> 0.6'
|
||||
|
|
|
@ -212,7 +212,6 @@ GEM
|
|||
railties (>= 5)
|
||||
dotenv (3.1.2)
|
||||
drb (2.2.1)
|
||||
ed25519 (1.3.0)
|
||||
elasticsearch (7.17.10)
|
||||
elasticsearch-api (= 7.17.10)
|
||||
elasticsearch-transport (= 7.17.10)
|
||||
|
@ -936,7 +935,6 @@ DEPENDENCIES
|
|||
discard (~> 1.2)
|
||||
doorkeeper (~> 5.6)
|
||||
dotenv
|
||||
ed25519 (~> 1.3)
|
||||
email_spec
|
||||
fabrication (~> 2.30)
|
||||
faker (~> 3.2)
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ActivityPub::ClaimsController < ActivityPub::BaseController
|
||||
skip_before_action :authenticate_user!
|
||||
|
||||
before_action :require_account_signature!
|
||||
before_action :set_claim_result
|
||||
|
||||
def create
|
||||
render json: @claim_result, serializer: ActivityPub::OneTimeKeySerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_claim_result
|
||||
@claim_result = ::Keys::ClaimService.new.call(@account.id, params[:id])
|
||||
end
|
||||
end
|
|
@ -22,8 +22,6 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
|
|||
@items = @items.map { |item| item.distributable? ? item : ActivityPub::TagManager.instance.uri_for(item) }
|
||||
when 'tags'
|
||||
@items = for_signed_account { @account.featured_tags }
|
||||
when 'devices'
|
||||
@items = @account.devices
|
||||
else
|
||||
not_found
|
||||
end
|
||||
|
@ -31,7 +29,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
|
|||
|
||||
def set_size
|
||||
case params[:id]
|
||||
when 'featured', 'devices', 'tags'
|
||||
when 'featured', 'tags'
|
||||
@size = @items.size
|
||||
else
|
||||
not_found
|
||||
|
@ -42,7 +40,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
|
|||
case params[:id]
|
||||
when 'featured'
|
||||
@type = :ordered
|
||||
when 'devices', 'tags'
|
||||
when 'tags'
|
||||
@type = :unordered
|
||||
else
|
||||
not_found
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Crypto::DeliveriesController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :crypto }
|
||||
before_action :require_user!
|
||||
before_action :set_current_device
|
||||
|
||||
def create
|
||||
devices.each do |device_params|
|
||||
DeliverToDeviceService.new.call(current_account, @current_device, device_params)
|
||||
end
|
||||
|
||||
render_empty
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_current_device
|
||||
@current_device = Device.find_by!(access_token: doorkeeper_token)
|
||||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:device)
|
||||
params.permit(device: [:account_id, :device_id, :type, :body, :hmac])
|
||||
end
|
||||
|
||||
def devices
|
||||
Array(resource_params[:device])
|
||||
end
|
||||
end
|
|
@ -1,47 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Crypto::EncryptedMessagesController < Api::BaseController
|
||||
LIMIT = 80
|
||||
|
||||
before_action -> { doorkeeper_authorize! :crypto }
|
||||
before_action :require_user!
|
||||
before_action :set_current_device
|
||||
|
||||
before_action :set_encrypted_messages, only: :index
|
||||
after_action :insert_pagination_headers, only: :index
|
||||
|
||||
def index
|
||||
render json: @encrypted_messages, each_serializer: REST::EncryptedMessageSerializer
|
||||
end
|
||||
|
||||
def clear
|
||||
@current_device.encrypted_messages.up_to(params[:up_to_id]).delete_all
|
||||
render_empty
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_current_device
|
||||
@current_device = Device.find_by!(access_token: doorkeeper_token)
|
||||
end
|
||||
|
||||
def set_encrypted_messages
|
||||
@encrypted_messages = @current_device.encrypted_messages.to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
|
||||
end
|
||||
|
||||
def next_path
|
||||
api_v1_crypto_encrypted_messages_url pagination_params(max_id: pagination_max_id) if records_continue?
|
||||
end
|
||||
|
||||
def prev_path
|
||||
api_v1_crypto_encrypted_messages_url pagination_params(min_id: pagination_since_id) unless @encrypted_messages.empty?
|
||||
end
|
||||
|
||||
def pagination_collection
|
||||
@encrypted_messages
|
||||
end
|
||||
|
||||
def records_continue?
|
||||
@encrypted_messages.size == limit_param(LIMIT)
|
||||
end
|
||||
end
|
|
@ -1,25 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Crypto::Keys::ClaimsController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :crypto }
|
||||
before_action :require_user!
|
||||
before_action :set_claim_results
|
||||
|
||||
def create
|
||||
render json: @claim_results, each_serializer: REST::Keys::ClaimResultSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_claim_results
|
||||
@claim_results = devices.filter_map { |device_params| ::Keys::ClaimService.new.call(current_account, device_params[:account_id], device_params[:device_id]) }
|
||||
end
|
||||
|
||||
def resource_params
|
||||
params.permit(device: [:account_id, :device_id])
|
||||
end
|
||||
|
||||
def devices
|
||||
Array(resource_params[:device])
|
||||
end
|
||||
end
|
|
@ -1,17 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Crypto::Keys::CountsController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :crypto }
|
||||
before_action :require_user!
|
||||
before_action :set_current_device
|
||||
|
||||
def show
|
||||
render json: { one_time_keys: @current_device.one_time_keys.count }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_current_device
|
||||
@current_device = Device.find_by!(access_token: doorkeeper_token)
|
||||
end
|
||||
end
|
|
@ -1,26 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Crypto::Keys::QueriesController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :crypto }
|
||||
before_action :require_user!
|
||||
before_action :set_accounts
|
||||
before_action :set_query_results
|
||||
|
||||
def create
|
||||
render json: @query_results, each_serializer: REST::Keys::QueryResultSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_accounts
|
||||
@accounts = Account.where(id: account_ids).includes(:devices)
|
||||
end
|
||||
|
||||
def set_query_results
|
||||
@query_results = @accounts.filter_map { |account| ::Keys::QueryService.new.call(account) }
|
||||
end
|
||||
|
||||
def account_ids
|
||||
Array(params[:id]).map(&:to_i)
|
||||
end
|
||||
end
|
|
@ -1,29 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Crypto::Keys::UploadsController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :crypto }
|
||||
before_action :require_user!
|
||||
|
||||
def create
|
||||
device = Device.find_or_initialize_by(access_token: doorkeeper_token)
|
||||
|
||||
device.transaction do
|
||||
device.account = current_account
|
||||
device.update!(resource_params[:device])
|
||||
|
||||
if resource_params[:one_time_keys].present? && resource_params[:one_time_keys].is_a?(Enumerable)
|
||||
resource_params[:one_time_keys].each do |one_time_key_params|
|
||||
device.one_time_keys.create!(one_time_key_params)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
render json: device, serializer: REST::Keys::DeviceSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def resource_params
|
||||
params.permit(device: [:device_id, :name, :fingerprint_key, :identity_key], one_time_keys: [:key_id, :key, :signature])
|
||||
end
|
||||
end
|
|
@ -23,23 +23,6 @@ module ContextHelper
|
|||
indexable: { 'toot' => 'http://joinmastodon.org/ns#', 'indexable' => 'toot:indexable' },
|
||||
memorial: { 'toot' => 'http://joinmastodon.org/ns#', 'memorial' => 'toot:memorial' },
|
||||
voters_count: { 'toot' => 'http://joinmastodon.org/ns#', 'votersCount' => 'toot:votersCount' },
|
||||
olm: {
|
||||
'toot' => 'http://joinmastodon.org/ns#',
|
||||
'Device' => 'toot:Device',
|
||||
'Ed25519Signature' => 'toot:Ed25519Signature',
|
||||
'Ed25519Key' => 'toot:Ed25519Key',
|
||||
'Curve25519Key' => 'toot:Curve25519Key',
|
||||
'EncryptedMessage' => 'toot:EncryptedMessage',
|
||||
'publicKeyBase64' => 'toot:publicKeyBase64',
|
||||
'deviceId' => 'toot:deviceId',
|
||||
'claim' => { '@type' => '@id', '@id' => 'toot:claim' },
|
||||
'fingerprintKey' => { '@type' => '@id', '@id' => 'toot:fingerprintKey' },
|
||||
'identityKey' => { '@type' => '@id', '@id' => 'toot:identityKey' },
|
||||
'devices' => { '@type' => '@id', '@id' => 'toot:devices' },
|
||||
'messageFranking' => 'toot:messageFranking',
|
||||
'messageType' => 'toot:messageType',
|
||||
'cipherText' => 'toot:cipherText',
|
||||
},
|
||||
suspended: { 'toot' => 'http://joinmastodon.org/ns#', 'suspended' => 'toot:suspended' },
|
||||
}.freeze
|
||||
|
||||
|
|
|
@ -8,44 +8,11 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||
|
||||
dereference_object!
|
||||
|
||||
case @object['type']
|
||||
when 'EncryptedMessage'
|
||||
create_encrypted_message
|
||||
else
|
||||
create_status
|
||||
end
|
||||
create_status
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_encrypted_message
|
||||
return reject_payload! if non_matching_uri_hosts?(@account.uri, object_uri) || @options[:delivered_to_account_id].blank?
|
||||
|
||||
target_account = Account.find(@options[:delivered_to_account_id])
|
||||
target_device = target_account.devices.find_by(device_id: @object.dig('to', 'deviceId'))
|
||||
|
||||
return if target_device.nil?
|
||||
|
||||
target_device.encrypted_messages.create!(
|
||||
from_account: @account,
|
||||
from_device_id: @object.dig('attributedTo', 'deviceId'),
|
||||
type: @object['messageType'],
|
||||
body: @object['cipherText'],
|
||||
digest: @object.dig('digest', 'digestValue'),
|
||||
message_franking: message_franking.to_token
|
||||
)
|
||||
end
|
||||
|
||||
def message_franking
|
||||
MessageFranking.new(
|
||||
hmac: @object.dig('digest', 'digestValue'),
|
||||
original_franking: @object['messageFranking'],
|
||||
source_account_id: @account.id,
|
||||
target_account_id: @options[:delivered_to_account_id],
|
||||
timestamp: Time.now.utc
|
||||
)
|
||||
end
|
||||
|
||||
def create_status
|
||||
return reject_payload! if unsupported_object_type? || non_matching_uri_hosts?(@account.uri, object_uri) || tombstone_exists? || !related_to_local_activity?
|
||||
|
||||
|
|
|
@ -20,8 +20,6 @@ class InlineRenderer
|
|||
serializer = REST::AnnouncementSerializer
|
||||
when :reaction
|
||||
serializer = REST::ReactionSerializer
|
||||
when :encrypted_message
|
||||
serializer = REST::EncryptedMessageSerializer
|
||||
else
|
||||
return
|
||||
end
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Vacuum::SystemKeysVacuum
|
||||
def perform
|
||||
vacuum_expired_system_keys!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def vacuum_expired_system_keys!
|
||||
SystemKey.expired.delete_all
|
||||
end
|
||||
end
|
|
@ -44,7 +44,6 @@
|
|||
# hide_collections :boolean
|
||||
# avatar_storage_schema_version :integer
|
||||
# header_storage_schema_version :integer
|
||||
# devices_url :string
|
||||
# suspension_origin :integer
|
||||
# sensitized_at :datetime
|
||||
# trendable :boolean
|
||||
|
@ -55,11 +54,12 @@
|
|||
|
||||
class Account < ApplicationRecord
|
||||
self.ignored_columns += %w(
|
||||
subscription_expires_at
|
||||
secret
|
||||
devices_url
|
||||
hub_url
|
||||
remote_url
|
||||
salmon_url
|
||||
hub_url
|
||||
secret
|
||||
subscription_expires_at
|
||||
trust_level
|
||||
)
|
||||
|
||||
|
|
|
@ -7,9 +7,6 @@ module Account::Associations
|
|||
# Local users
|
||||
has_one :user, inverse_of: :account, dependent: :destroy
|
||||
|
||||
# E2EE
|
||||
has_many :devices, dependent: :destroy, inverse_of: :account
|
||||
|
||||
# Timelines
|
||||
has_many :statuses, inverse_of: :account, dependent: :destroy
|
||||
has_many :favourites, inverse_of: :account, dependent: :destroy
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: devices
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# access_token_id :bigint(8)
|
||||
# account_id :bigint(8)
|
||||
# device_id :string default(""), not null
|
||||
# name :string default(""), not null
|
||||
# fingerprint_key :text default(""), not null
|
||||
# identity_key :text default(""), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
|
||||
class Device < ApplicationRecord
|
||||
belongs_to :access_token, class_name: 'Doorkeeper::AccessToken'
|
||||
belongs_to :account
|
||||
|
||||
has_many :one_time_keys, dependent: :destroy, inverse_of: :device
|
||||
has_many :encrypted_messages, dependent: :destroy, inverse_of: :device
|
||||
|
||||
validates :name, :fingerprint_key, :identity_key, presence: true
|
||||
validates :fingerprint_key, :identity_key, ed25519_key: true
|
||||
|
||||
before_save :invalidate_associations, if: -> { device_id_changed? || fingerprint_key_changed? || identity_key_changed? }
|
||||
|
||||
private
|
||||
|
||||
def invalidate_associations
|
||||
one_time_keys.destroy_all
|
||||
encrypted_messages.destroy_all
|
||||
end
|
||||
end
|
|
@ -1,49 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: encrypted_messages
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# device_id :bigint(8)
|
||||
# from_account_id :bigint(8)
|
||||
# from_device_id :string default(""), not null
|
||||
# type :integer default(0), not null
|
||||
# body :text default(""), not null
|
||||
# digest :text default(""), not null
|
||||
# message_franking :text default(""), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
|
||||
class EncryptedMessage < ApplicationRecord
|
||||
self.inheritance_column = nil
|
||||
|
||||
include Paginable
|
||||
include Redisable
|
||||
|
||||
scope :up_to, ->(id) { where(arel_table[:id].lteq(id)) }
|
||||
|
||||
belongs_to :device
|
||||
belongs_to :from_account, class_name: 'Account'
|
||||
|
||||
around_create Mastodon::Snowflake::Callbacks
|
||||
|
||||
after_commit :push_to_streaming_api
|
||||
|
||||
private
|
||||
|
||||
def push_to_streaming_api
|
||||
return if destroyed? || !subscribed_to_timeline?
|
||||
|
||||
PushEncryptedMessageWorker.perform_async(id)
|
||||
end
|
||||
|
||||
def subscribed_to_timeline?
|
||||
redis.exists?("subscribed:#{streaming_channel}")
|
||||
end
|
||||
|
||||
def streaming_channel
|
||||
"timeline:#{device.account_id}:#{device.device_id}"
|
||||
end
|
||||
end
|
|
@ -1,19 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MessageFranking
|
||||
attr_reader :hmac, :source_account_id, :target_account_id,
|
||||
:timestamp, :original_franking
|
||||
|
||||
def initialize(attributes = {})
|
||||
@hmac = attributes[:hmac]
|
||||
@source_account_id = attributes[:source_account_id]
|
||||
@target_account_id = attributes[:target_account_id]
|
||||
@timestamp = attributes[:timestamp]
|
||||
@original_franking = attributes[:original_franking]
|
||||
end
|
||||
|
||||
def to_token
|
||||
crypt = ActiveSupport::MessageEncryptor.new(SystemKey.current_key, serializer: Oj)
|
||||
crypt.encrypt_and_sign(self)
|
||||
end
|
||||
end
|
|
@ -1,22 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: one_time_keys
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# device_id :bigint(8)
|
||||
# key_id :string default(""), not null
|
||||
# key :text default(""), not null
|
||||
# signature :text default(""), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
|
||||
class OneTimeKey < ApplicationRecord
|
||||
belongs_to :device
|
||||
|
||||
validates :key_id, :key, :signature, presence: true
|
||||
validates :key, ed25519_key: true
|
||||
validates :signature, ed25519_signature: { message: :key, verify_key: ->(one_time_key) { one_time_key.device.fingerprint_key } }
|
||||
end
|
|
@ -1,41 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: system_keys
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# key :binary
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
class SystemKey < ApplicationRecord
|
||||
ROTATION_PERIOD = 1.week.freeze
|
||||
|
||||
before_validation :set_key
|
||||
|
||||
scope :expired, ->(now = Time.now.utc) { where(arel_table[:created_at].lt(now - (ROTATION_PERIOD * 3))) }
|
||||
|
||||
class << self
|
||||
def current_key
|
||||
previous_key = order(id: :asc).last
|
||||
|
||||
if previous_key && previous_key.created_at >= ROTATION_PERIOD.ago
|
||||
previous_key.key
|
||||
else
|
||||
create.key
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_key
|
||||
return if key.present?
|
||||
|
||||
cipher = OpenSSL::Cipher.new('AES-256-GCM')
|
||||
cipher.encrypt
|
||||
|
||||
self.key = cipher.random_key
|
||||
end
|
||||
end
|
|
@ -5,8 +5,6 @@ class ActivityPub::ActivitySerializer < ActivityPub::Serializer
|
|||
case model.class.name
|
||||
when 'Status'
|
||||
ActivityPub::NoteSerializer
|
||||
when 'DeliverToDeviceService::EncryptedMessage'
|
||||
ActivityPub::EncryptedMessageSerializer
|
||||
else
|
||||
super
|
||||
end
|
||||
|
|
|
@ -7,7 +7,7 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
|
|||
context :security
|
||||
|
||||
context_extensions :manually_approves_followers, :featured, :also_known_as,
|
||||
:moved_to, :property_value, :discoverable, :olm, :suspended,
|
||||
:moved_to, :property_value, :discoverable, :suspended,
|
||||
:memorial, :indexable
|
||||
|
||||
attributes :id, :type, :following, :followers,
|
||||
|
@ -21,7 +21,6 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
|
|||
has_many :virtual_tags, key: :tag
|
||||
has_many :virtual_attachments, key: :attachment
|
||||
|
||||
attribute :devices, unless: :instance_actor?
|
||||
attribute :moved_to, if: :moved?
|
||||
attribute :also_known_as, if: :also_known_as?
|
||||
attribute :suspended, if: :suspended?
|
||||
|
@ -71,10 +70,6 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
|
|||
object.instance_actor? ? instance_actor_inbox_url : account_inbox_url(object)
|
||||
end
|
||||
|
||||
def devices
|
||||
account_collection_url(object, :devices)
|
||||
end
|
||||
|
||||
def outbox
|
||||
object.instance_actor? ? instance_actor_outbox_url : account_outbox_url(object)
|
||||
end
|
||||
|
|
|
@ -14,8 +14,6 @@ class ActivityPub::CollectionSerializer < ActivityPub::Serializer
|
|||
case model.class.name
|
||||
when 'Status'
|
||||
ActivityPub::NoteSerializer
|
||||
when 'Device'
|
||||
ActivityPub::DeviceSerializer
|
||||
when 'FeaturedTag'
|
||||
ActivityPub::HashtagSerializer
|
||||
when 'ActivityPub::CollectionPresenter'
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ActivityPub::DeviceSerializer < ActivityPub::Serializer
|
||||
context_extensions :olm
|
||||
|
||||
include RoutingHelper
|
||||
|
||||
class FingerprintKeySerializer < ActivityPub::Serializer
|
||||
attributes :type, :public_key_base64
|
||||
|
||||
def type
|
||||
'Ed25519Key'
|
||||
end
|
||||
|
||||
def public_key_base64
|
||||
object.fingerprint_key
|
||||
end
|
||||
end
|
||||
|
||||
class IdentityKeySerializer < ActivityPub::Serializer
|
||||
attributes :type, :public_key_base64
|
||||
|
||||
def type
|
||||
'Curve25519Key'
|
||||
end
|
||||
|
||||
def public_key_base64
|
||||
object.identity_key
|
||||
end
|
||||
end
|
||||
|
||||
attributes :device_id, :type, :name, :claim
|
||||
|
||||
has_one :fingerprint_key, serializer: FingerprintKeySerializer
|
||||
has_one :identity_key, serializer: IdentityKeySerializer
|
||||
|
||||
def type
|
||||
'Device'
|
||||
end
|
||||
|
||||
def claim
|
||||
account_claim_url(object.account, id: object.device_id)
|
||||
end
|
||||
|
||||
def fingerprint_key
|
||||
object
|
||||
end
|
||||
|
||||
def identity_key
|
||||
object
|
||||
end
|
||||
end
|
|
@ -1,61 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ActivityPub::EncryptedMessageSerializer < ActivityPub::Serializer
|
||||
context :security
|
||||
|
||||
context_extensions :olm
|
||||
|
||||
class DeviceSerializer < ActivityPub::Serializer
|
||||
attributes :type, :device_id
|
||||
|
||||
def type
|
||||
'Device'
|
||||
end
|
||||
|
||||
def device_id
|
||||
object
|
||||
end
|
||||
end
|
||||
|
||||
class DigestSerializer < ActivityPub::Serializer
|
||||
attributes :type, :digest_algorithm, :digest_value
|
||||
|
||||
def type
|
||||
'Digest'
|
||||
end
|
||||
|
||||
def digest_algorithm
|
||||
'http://www.w3.org/2000/09/xmldsig#hmac-sha256'
|
||||
end
|
||||
|
||||
def digest_value
|
||||
object
|
||||
end
|
||||
end
|
||||
|
||||
attributes :type, :message_type, :cipher_text, :message_franking
|
||||
|
||||
has_one :attributed_to, serializer: DeviceSerializer
|
||||
has_one :to, serializer: DeviceSerializer
|
||||
has_one :digest, serializer: DigestSerializer
|
||||
|
||||
def type
|
||||
'EncryptedMessage'
|
||||
end
|
||||
|
||||
def attributed_to
|
||||
object.source_device.device_id
|
||||
end
|
||||
|
||||
def to
|
||||
object.target_device_id
|
||||
end
|
||||
|
||||
def message_type
|
||||
object.type
|
||||
end
|
||||
|
||||
def cipher_text
|
||||
object.body
|
||||
end
|
||||
end
|
|
@ -1,35 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ActivityPub::OneTimeKeySerializer < ActivityPub::Serializer
|
||||
context :security
|
||||
|
||||
context_extensions :olm
|
||||
|
||||
class SignatureSerializer < ActivityPub::Serializer
|
||||
attributes :type, :signature_value
|
||||
|
||||
def type
|
||||
'Ed25519Signature'
|
||||
end
|
||||
|
||||
def signature_value
|
||||
object.signature
|
||||
end
|
||||
end
|
||||
|
||||
attributes :key_id, :type, :public_key_base64
|
||||
|
||||
has_one :signature, serializer: SignatureSerializer
|
||||
|
||||
def type
|
||||
'Curve25519Key'
|
||||
end
|
||||
|
||||
def public_key_base64
|
||||
object.key
|
||||
end
|
||||
|
||||
def signature
|
||||
object
|
||||
end
|
||||
end
|
|
@ -1,19 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class REST::EncryptedMessageSerializer < ActiveModel::Serializer
|
||||
attributes :id, :account_id, :device_id,
|
||||
:type, :body, :digest, :message_franking,
|
||||
:created_at
|
||||
|
||||
def id
|
||||
object.id.to_s
|
||||
end
|
||||
|
||||
def account_id
|
||||
object.from_account_id.to_s
|
||||
end
|
||||
|
||||
def device_id
|
||||
object.from_device_id
|
||||
end
|
||||
end
|
|
@ -1,9 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class REST::Keys::ClaimResultSerializer < ActiveModel::Serializer
|
||||
attributes :account_id, :device_id, :key_id, :key, :signature
|
||||
|
||||
def account_id
|
||||
object.account.id.to_s
|
||||
end
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class REST::Keys::DeviceSerializer < ActiveModel::Serializer
|
||||
attributes :device_id, :name, :identity_key,
|
||||
:fingerprint_key
|
||||
end
|
|
@ -1,11 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class REST::Keys::QueryResultSerializer < ActiveModel::Serializer
|
||||
attributes :account_id
|
||||
|
||||
has_many :devices, serializer: REST::Keys::DeviceSerializer
|
||||
|
||||
def account_id
|
||||
object.account.id.to_s
|
||||
end
|
||||
end
|
|
@ -108,7 +108,6 @@ class ActivityPub::ProcessAccountService < BaseService
|
|||
|
||||
def set_immediate_attributes!
|
||||
@account.featured_collection_url = @json['featured'] || ''
|
||||
@account.devices_url = @json['devices'] || ''
|
||||
@account.display_name = @json['name'] || ''
|
||||
@account.note = @json['summary'] || ''
|
||||
@account.locked = @json['manuallyApprovesFollowers'] || false
|
||||
|
|
|
@ -13,7 +13,6 @@ class DeleteAccountService < BaseService
|
|||
conversation_mutes
|
||||
conversations
|
||||
custom_filters
|
||||
devices
|
||||
domain_blocks
|
||||
featured_tags
|
||||
follow_requests
|
||||
|
@ -40,7 +39,6 @@ class DeleteAccountService < BaseService
|
|||
conversation_mutes
|
||||
conversations
|
||||
custom_filters
|
||||
devices
|
||||
domain_blocks
|
||||
featured_tags
|
||||
follow_requests
|
||||
|
|
|
@ -1,78 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class DeliverToDeviceService < BaseService
|
||||
include Payloadable
|
||||
|
||||
class EncryptedMessage < ActiveModelSerializers::Model
|
||||
attributes :source_account, :target_account, :source_device,
|
||||
:target_device_id, :type, :body, :digest,
|
||||
:message_franking
|
||||
end
|
||||
|
||||
def call(source_account, source_device, options = {})
|
||||
@source_account = source_account
|
||||
@source_device = source_device
|
||||
@target_account = Account.find(options[:account_id])
|
||||
@target_device_id = options[:device_id]
|
||||
@body = options[:body]
|
||||
@type = options[:type]
|
||||
@hmac = options[:hmac]
|
||||
|
||||
set_message_franking!
|
||||
|
||||
if @target_account.local?
|
||||
deliver_to_local!
|
||||
else
|
||||
deliver_to_remote!
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_message_franking!
|
||||
@message_franking = message_franking.to_token
|
||||
end
|
||||
|
||||
def deliver_to_local!
|
||||
target_device = @target_account.devices.find_by!(device_id: @target_device_id)
|
||||
|
||||
target_device.encrypted_messages.create!(
|
||||
from_account: @source_account,
|
||||
from_device_id: @source_device.device_id,
|
||||
type: @type,
|
||||
body: @body,
|
||||
digest: @hmac,
|
||||
message_franking: @message_franking
|
||||
)
|
||||
end
|
||||
|
||||
def deliver_to_remote!
|
||||
ActivityPub::DeliveryWorker.perform_async(
|
||||
Oj.dump(serialize_payload(ActivityPub::ActivityPresenter.from_encrypted_message(encrypted_message), ActivityPub::ActivitySerializer)),
|
||||
@source_account.id,
|
||||
@target_account.inbox_url
|
||||
)
|
||||
end
|
||||
|
||||
def message_franking
|
||||
MessageFranking.new(
|
||||
source_account_id: @source_account.id,
|
||||
target_account_id: @target_account.id,
|
||||
hmac: @hmac,
|
||||
timestamp: Time.now.utc
|
||||
)
|
||||
end
|
||||
|
||||
def encrypted_message
|
||||
EncryptedMessage.new(
|
||||
source_account: @source_account,
|
||||
target_account: @target_account,
|
||||
source_device: @source_device,
|
||||
target_device_id: @target_device_id,
|
||||
type: @type,
|
||||
body: @body,
|
||||
digest: @hmac,
|
||||
message_franking: @message_franking
|
||||
)
|
||||
end
|
||||
end
|
|
@ -1,79 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Keys::ClaimService < BaseService
|
||||
HEADERS = { 'Content-Type' => 'application/activity+json' }.freeze
|
||||
|
||||
class Result < ActiveModelSerializers::Model
|
||||
attributes :account, :device_id, :key_id,
|
||||
:key, :signature
|
||||
|
||||
def initialize(account, device_id, key_attributes = {})
|
||||
super(
|
||||
account: account,
|
||||
device_id: device_id,
|
||||
key_id: key_attributes[:key_id],
|
||||
key: key_attributes[:key],
|
||||
signature: key_attributes[:signature],
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def call(source_account, target_account_id, device_id)
|
||||
@source_account = source_account
|
||||
@target_account = Account.find(target_account_id)
|
||||
@device_id = device_id
|
||||
|
||||
if @target_account.local?
|
||||
claim_local_key!
|
||||
else
|
||||
claim_remote_key!
|
||||
end
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def claim_local_key!
|
||||
device = @target_account.devices.find_by(device_id: @device_id)
|
||||
key = nil
|
||||
|
||||
ApplicationRecord.transaction do
|
||||
key = device.one_time_keys.order(Arel.sql('random()')).first!
|
||||
key.destroy!
|
||||
end
|
||||
|
||||
@result = Result.new(@target_account, @device_id, key)
|
||||
end
|
||||
|
||||
def claim_remote_key!
|
||||
query_result = QueryService.new.call(@target_account)
|
||||
device = query_result.find(@device_id)
|
||||
|
||||
return unless device.present? && device.valid_claim_url?
|
||||
|
||||
json = fetch_resource_with_post(device.claim_url)
|
||||
|
||||
return unless json.present? && json['publicKeyBase64'].present?
|
||||
|
||||
@result = Result.new(@target_account, @device_id, key_id: json['id'], key: json['publicKeyBase64'], signature: json.dig('signature', 'signatureValue'))
|
||||
rescue HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error => e
|
||||
Rails.logger.debug { "Claiming one-time key for #{@target_account.acct}:#{@device_id} failed: #{e}" }
|
||||
nil
|
||||
end
|
||||
|
||||
def fetch_resource_with_post(uri)
|
||||
build_post_request(uri).perform do |response|
|
||||
raise Mastodon::UnexpectedResponseError, response unless response_successful?(response) || response_error_unsalvageable?(response)
|
||||
|
||||
body_to_json(response.body_with_limit) if response.code == 200
|
||||
end
|
||||
end
|
||||
|
||||
def build_post_request(uri)
|
||||
Request.new(:post, uri).tap do |request|
|
||||
request.on_behalf_of(@source_account)
|
||||
request.add_headers(HEADERS)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,79 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Keys::QueryService < BaseService
|
||||
include JsonLdHelper
|
||||
|
||||
class Result < ActiveModelSerializers::Model
|
||||
attributes :account, :devices
|
||||
|
||||
def initialize(account, devices)
|
||||
super(
|
||||
account: account,
|
||||
devices: devices || [],
|
||||
)
|
||||
end
|
||||
|
||||
def find(device_id)
|
||||
@devices.find { |device| device.device_id == device_id }
|
||||
end
|
||||
end
|
||||
|
||||
class Device < ActiveModelSerializers::Model
|
||||
attributes :device_id, :name, :identity_key, :fingerprint_key
|
||||
|
||||
def initialize(attributes = {})
|
||||
super(
|
||||
device_id: attributes[:device_id],
|
||||
name: attributes[:name],
|
||||
identity_key: attributes[:identity_key],
|
||||
fingerprint_key: attributes[:fingerprint_key],
|
||||
)
|
||||
@claim_url = attributes[:claim_url]
|
||||
end
|
||||
|
||||
def valid_claim_url?
|
||||
return false if @claim_url.blank?
|
||||
|
||||
begin
|
||||
parsed_url = Addressable::URI.parse(@claim_url).normalize
|
||||
rescue Addressable::URI::InvalidURIError
|
||||
return false
|
||||
end
|
||||
|
||||
%w(http https).include?(parsed_url.scheme) && parsed_url.host.present?
|
||||
end
|
||||
end
|
||||
|
||||
def call(account)
|
||||
@account = account
|
||||
|
||||
if @account.local?
|
||||
query_local_devices!
|
||||
else
|
||||
query_remote_devices!
|
||||
end
|
||||
|
||||
Result.new(@account, @devices)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def query_local_devices!
|
||||
@devices = @account.devices.map { |device| Device.new(device) }
|
||||
end
|
||||
|
||||
def query_remote_devices!
|
||||
return if @account.devices_url.blank?
|
||||
|
||||
json = fetch_resource(@account.devices_url)
|
||||
|
||||
return if json['items'].blank?
|
||||
|
||||
@devices = as_array(json['items']).map do |device|
|
||||
Device.new(device_id: device['id'], name: device['name'], identity_key: device.dig('identityKey', 'publicKeyBase64'), fingerprint_key: device.dig('fingerprintKey', 'publicKeyBase64'), claim_url: device['claim'])
|
||||
end
|
||||
rescue HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error => e
|
||||
Rails.logger.debug { "Querying devices for #{@account.acct} failed: #{e}" }
|
||||
nil
|
||||
end
|
||||
end
|
|
@ -1,19 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Ed25519KeyValidator < ActiveModel::EachValidator
|
||||
def validate_each(record, attribute, value)
|
||||
return if value.blank?
|
||||
|
||||
key = Base64.decode64(value)
|
||||
|
||||
record.errors.add(attribute, I18n.t('crypto.errors.invalid_key')) unless verified?(key)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def verified?(key)
|
||||
Ed25519.validate_key_bytes(key)
|
||||
rescue ArgumentError
|
||||
false
|
||||
end
|
||||
end
|
|
@ -1,29 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Ed25519SignatureValidator < ActiveModel::EachValidator
|
||||
def validate_each(record, attribute, value)
|
||||
return if value.blank?
|
||||
|
||||
verify_key = Ed25519::VerifyKey.new(Base64.decode64(option_to_value(record, :verify_key)))
|
||||
signature = Base64.decode64(value)
|
||||
message = option_to_value(record, :message)
|
||||
|
||||
record.errors.add(attribute, I18n.t('crypto.errors.invalid_signature')) unless verified?(verify_key, signature, message)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def verified?(verify_key, signature, message)
|
||||
verify_key.verify(signature, message)
|
||||
rescue Ed25519::VerifyError, ArgumentError
|
||||
false
|
||||
end
|
||||
|
||||
def option_to_value(record, key)
|
||||
if options[key].is_a?(Proc)
|
||||
options[key].call(record)
|
||||
else
|
||||
record.public_send(options[key])
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,16 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class PushEncryptedMessageWorker
|
||||
include Sidekiq::Worker
|
||||
include Redisable
|
||||
|
||||
def perform(encrypted_message_id)
|
||||
encrypted_message = EncryptedMessage.find(encrypted_message_id)
|
||||
message = InlineRenderer.render(encrypted_message, nil, :encrypted_message)
|
||||
timeline_id = "timeline:#{encrypted_message.device.account_id}:#{encrypted_message.device.device_id}"
|
||||
|
||||
redis.publish(timeline_id, Oj.dump(event: :encrypted_message, payload: message))
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
true
|
||||
end
|
||||
end
|
|
@ -118,8 +118,7 @@ Doorkeeper.configure do
|
|||
:'admin:write:domain_blocks',
|
||||
:'admin:write:ip_blocks',
|
||||
:'admin:write:email_domain_blocks',
|
||||
:'admin:write:canonical_email_blocks',
|
||||
:crypto
|
||||
:'admin:write:canonical_email_blocks'
|
||||
|
||||
# Change the way client credentials are retrieved from the request object.
|
||||
# By default it retrieves first from the `HTTP_AUTHORIZATION` header, then
|
||||
|
|
|
@ -19,7 +19,6 @@ ActiveSupport::Inflector.inflections(:en) do |inflect|
|
|||
inflect.acronym 'CLI'
|
||||
inflect.acronym 'DeepL'
|
||||
inflect.acronym 'DSL'
|
||||
inflect.acronym 'Ed25519'
|
||||
inflect.acronym 'JsonLd'
|
||||
inflect.acronym 'OEmbed'
|
||||
inflect.acronym 'OStatus'
|
||||
|
|
|
@ -984,10 +984,6 @@ an:
|
|||
hint_html: "<strong>Tip:</strong> No tornaremos a preguntar-te per la clau entre la siguient hora."
|
||||
invalid_password: Clau incorrecta
|
||||
prompt: Confirmar clau pa seguir
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: no ye una clau Ed25519 u Curve25519 valida
|
||||
invalid_signature: no ye una sinyatura Ed25519 valida
|
||||
date:
|
||||
formats:
|
||||
default: "%d %b %Y"
|
||||
|
|
|
@ -1191,10 +1191,6 @@ ar:
|
|||
hint_html: "<strong>توصية:</strong> لن نطلب منك ثانية كلمتك السرية في غضون الساعة اللاحقة."
|
||||
invalid_password: الكلمة السرية خاطئة
|
||||
prompt: أكِّد الكلمة السرية للمواصلة
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: ليس بمفتاح Ed25519 أو Curve25519 صالح
|
||||
invalid_signature: ليس بتوقيع Ed25519 صالح
|
||||
date:
|
||||
formats:
|
||||
default: "%d %b %Y"
|
||||
|
|
|
@ -499,10 +499,6 @@ ast:
|
|||
hint_html: "<strong>Conseyu:</strong> nun vamos volver pidite la contraseña hasta dientro d'una hora."
|
||||
invalid_password: La contraseña nun ye válida
|
||||
prompt: Confirma la contraseña pa siguir
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: nun ye una clave ed25519 o curve25519 válida
|
||||
invalid_signature: nun ye una clave ed25519 válida
|
||||
datetime:
|
||||
distance_in_words:
|
||||
about_x_hours: "%{count} h"
|
||||
|
|
|
@ -1165,10 +1165,6 @@ be:
|
|||
hint_html: "<strong>Парада:</strong> Мы не будзем запытваць ваш пароль зноўку на працягу наступнай гадзіны."
|
||||
invalid_password: Няправільны пароль
|
||||
prompt: Пацвердзіць пароль, каб працягнуць
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: гэта не сапраўдны Ed25519 або Curve25519 ключ
|
||||
invalid_signature: гэта не сапраўдная Ed25519 сігнатура
|
||||
date:
|
||||
formats:
|
||||
default: "%d.%m.%Y"
|
||||
|
|
|
@ -1135,10 +1135,6 @@ bg:
|
|||
hint_html: "<strong>Съвет</strong>: няма да ви питаме пак за паролата през следващия час."
|
||||
invalid_password: Невалидна парола
|
||||
prompt: Потвърдете паролата, за да продължите
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: не е валиден ключ Ed25519 или Curve25519
|
||||
invalid_signature: не е валиден подпис Ed25519
|
||||
date:
|
||||
formats:
|
||||
default: "%b %d, %Y"
|
||||
|
|
|
@ -1135,10 +1135,6 @@ ca:
|
|||
hint_html: "<strong>Pista:</strong> No et preguntarem un altre cop la teva contrasenya en la pròxima hora."
|
||||
invalid_password: Contrasenya no vàlida
|
||||
prompt: Confirmi la contrasenya per a continuar
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: no és una clau Ed25519 o Curve25519 vàlida
|
||||
invalid_signature: no és una signatura Ed25519 vàlida
|
||||
date:
|
||||
formats:
|
||||
default: "%b %d, %Y"
|
||||
|
|
|
@ -627,10 +627,6 @@ ckb:
|
|||
hint_html: "<strong>خاڵ:</strong> ئیمە لە کاتژمێری داهاتوو تێپەروشەت لێداوا ناکەین."
|
||||
invalid_password: تێپەروشە دروست نیە
|
||||
prompt: دڵنیابوون لە نهێنوشە بۆ بەردەوامبوون
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: کلیلی باوڕپێکراو Ed25519 یان Curve25519 دروست نییە
|
||||
invalid_signature: واژووی Ed25519 بڕوادار نییە
|
||||
date:
|
||||
formats:
|
||||
default: "%b %d, %Y"
|
||||
|
|
|
@ -587,10 +587,6 @@ co:
|
|||
hint_html: "<strong>Astuzia:</strong> Ùn avemu micca da dumandavvi stu codice per l'ore chì vene."
|
||||
invalid_password: Chjave d'accessu micca curretta
|
||||
prompt: Cunfirmà a chjave d'accessu per cuntinuvà
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: ùn hè micca una chjave Ed25519 o Curve25519 valida
|
||||
invalid_signature: ùn hè micca una firma Ed25519 valida
|
||||
date:
|
||||
formats:
|
||||
default: "%d %b %Y"
|
||||
|
|
|
@ -1166,10 +1166,6 @@ cs:
|
|||
hint_html: "<strong>Tip:</strong> Po dobu jedné hodiny vás o heslo nebudeme znovu žádat."
|
||||
invalid_password: Neplatné heslo
|
||||
prompt: Pokračujte potvrzením hesla
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: není platný klíč Ed25519 nebo Curve25519
|
||||
invalid_signature: není platný podpis typu Ed25519
|
||||
date:
|
||||
formats:
|
||||
default: "%d. %b %Y"
|
||||
|
|
|
@ -1204,10 +1204,6 @@ cy:
|
|||
hint_html: "<strong>Awgrym:</strong> Fyddwn ni ddim yn gofyn i chi am eich cyfrinair eto am yr awr nesaf."
|
||||
invalid_password: Cyfrinair annilys
|
||||
prompt: Cadarnhewch gyfrinair i barhau
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: ddim yn allwedd Ed25519 na Curve25519 dilys
|
||||
invalid_signature: ddim yn llofnod Ed25519 dilys
|
||||
date:
|
||||
formats:
|
||||
default: "%b %d %Y"
|
||||
|
|
|
@ -1134,10 +1134,6 @@ da:
|
|||
hint_html: "<strong>Tip:</strong> Du bliver ikke anmodet om din adgangskode igen den næste time."
|
||||
invalid_password: Ugyldig adgangskode
|
||||
prompt: Bekræft adgangskode for at fortsætte
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: er ikke en gyldig Ed25519- eller Curve25519-nøgle
|
||||
invalid_signature: er ikke en gylidig Ed25519-signatur
|
||||
date:
|
||||
formats:
|
||||
default: "%d. %b %Y"
|
||||
|
|
|
@ -1135,10 +1135,6 @@ de:
|
|||
hint_html: "<strong>Hinweis:</strong> Wir werden dich für die nächste Stunde nicht erneut nach deinem Passwort fragen."
|
||||
invalid_password: Ungültiges Passwort
|
||||
prompt: Bestätige mit deinem Passwort, um fortzufahren
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: ist kein gültiger Ed25519- oder Curve25519-Schlüssel
|
||||
invalid_signature: ist keine gültige Ed25519-Signatur
|
||||
date:
|
||||
formats:
|
||||
default: "%d. %b %Y"
|
||||
|
|
|
@ -1058,10 +1058,6 @@ el:
|
|||
hint_html: "<strong>Συμβουλή:</strong> Δεν θα σου ζητήσουμε τον κωδικό ασφαλείας σου ξανά για την επόμενη ώρα."
|
||||
invalid_password: Μη έγκυρο συνθηματικό
|
||||
prompt: Επιβεβαίωση συνθηματικού για συνέχεια
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: δεν είναι έγκυρο κλειδί Ed25519 ή Curve25519
|
||||
invalid_signature: δεν είναι έγκυρη υπογραφή Ed25519
|
||||
date:
|
||||
formats:
|
||||
default: "%b %d, %Y"
|
||||
|
|
|
@ -1135,10 +1135,6 @@ en-GB:
|
|||
hint_html: "<strong>Tip:</strong> We won't ask you for your password again for the next hour."
|
||||
invalid_password: Invalid password
|
||||
prompt: Confirm password to continue
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: is not a valid Ed25519 or Curve25519 key
|
||||
invalid_signature: is not a valid Ed25519 signature
|
||||
date:
|
||||
formats:
|
||||
default: "%b %d, %Y"
|
||||
|
|
|
@ -1135,10 +1135,6 @@ en:
|
|||
hint_html: "<strong>Tip:</strong> We won't ask you for your password again for the next hour."
|
||||
invalid_password: Invalid password
|
||||
prompt: Confirm password to continue
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: is not a valid Ed25519 or Curve25519 key
|
||||
invalid_signature: is not a valid Ed25519 signature
|
||||
date:
|
||||
formats:
|
||||
default: "%b %d, %Y"
|
||||
|
|
|
@ -1077,10 +1077,6 @@ eo:
|
|||
hint_html: "<strong>Konsileto:</strong> Ni ne demandos pri via pasvorto ĝis 1 horo."
|
||||
invalid_password: Nevalida pasvorto
|
||||
prompt: Konfirmi pasvorton por daŭrigi
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: 올바른 Ed25519 혹은 Curve25519 키가 아닙니다
|
||||
invalid_signature: 올바른 Ed25519 시그니처가 아닙니다
|
||||
date:
|
||||
formats:
|
||||
default: "%Y-%b-%d"
|
||||
|
|
|
@ -1135,10 +1135,6 @@ es-AR:
|
|||
hint_html: "<strong>Dato:</strong> No volveremos a preguntarte por la contraseña durante la siguiente hora."
|
||||
invalid_password: Contraseña no válida
|
||||
prompt: Confirmar contraseña para seguir
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: no es una clave Ed25519 o Curve25519 válida
|
||||
invalid_signature: no es una firma Ed25519 válida
|
||||
date:
|
||||
formats:
|
||||
default: "%d de %b de %Y"
|
||||
|
|
|
@ -1135,10 +1135,6 @@ es-MX:
|
|||
hint_html: "<strong>Tip:</strong> No volveremos a preguntarte por la contraseña durante la siguiente hora."
|
||||
invalid_password: Contraseña incorrecta
|
||||
prompt: Confirmar contraseña para seguir
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: no es una clave Ed25519 o Curve25519 válida
|
||||
invalid_signature: no es una firma Ed25519 válida
|
||||
date:
|
||||
formats:
|
||||
default: "%d %b %Y"
|
||||
|
|
|
@ -1135,10 +1135,6 @@ es:
|
|||
hint_html: "<strong>Tip:</strong> No volveremos a preguntarte por la contraseña durante la siguiente hora."
|
||||
invalid_password: Contraseña incorrecta
|
||||
prompt: Confirmar contraseña para seguir
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: no es una clave Ed25519 o Curve25519 válida
|
||||
invalid_signature: no es una firma Ed25519 válida
|
||||
date:
|
||||
formats:
|
||||
default: "%d %b %Y"
|
||||
|
|
|
@ -1130,10 +1130,6 @@ et:
|
|||
hint_html: "<strong>Nõuanne:</strong> Me ei küsi salasõna uuesti järgmise tunni jooksul."
|
||||
invalid_password: Vigane salasõna
|
||||
prompt: Jätkamiseks salasõna veelkord
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: ei ole õige Ed25519 ega Curve25519 võti
|
||||
invalid_signature: ei ole õige Ed25519 allkiri
|
||||
date:
|
||||
formats:
|
||||
default: "%d. %b %Y"
|
||||
|
|
|
@ -1129,10 +1129,6 @@ eu:
|
|||
hint_html: "<strong>Oharra:</strong> Ez dizugu pasahitza berriro eskatuko ordu batez."
|
||||
invalid_password: Pasahitz baliogabea
|
||||
prompt: Berretsi pasahitza jarraitzeko
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: ez da baliozko Ed25519 edo Curve25519 gakoa
|
||||
invalid_signature: ez da baliozko Ed25519 sinadura
|
||||
date:
|
||||
formats:
|
||||
default: "%Y(e)ko %b %d"
|
||||
|
|
|
@ -949,10 +949,6 @@ fa:
|
|||
hint_html: "<strong>نکته:</strong> ما در یک ساعت آینده گذرواژهتان را از شما نخواهیم پرسید."
|
||||
invalid_password: گذرواژه نامعتبر
|
||||
prompt: برای ادامه گذرواژهتان را تأیید کنید
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: یک کلید معتبر Ed25519 یا Curve25519 نیست
|
||||
invalid_signature: یک امضای معتبر Ed25519 نیست
|
||||
date:
|
||||
formats:
|
||||
default: "%d %b %Y"
|
||||
|
|
|
@ -1135,10 +1135,6 @@ fi:
|
|||
hint_html: "<strong>Vihje:</strong> Emme pyydä sinulta salasanaa uudelleen seuraavan tunnin aikana."
|
||||
invalid_password: Virheellinen salasana
|
||||
prompt: Vahvista salasanasi jatkaaksesi
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: ei ole kelvollinen Ed25519- tai Curve25519-avain
|
||||
invalid_signature: ei ole kelvollinen Ed25519-allekirjoitus
|
||||
date:
|
||||
formats:
|
||||
default: "%b %d, %Y"
|
||||
|
|
|
@ -1135,10 +1135,6 @@ fo:
|
|||
hint_html: "<strong>Góð ráð:</strong> vit spyrja teg ikki aftur um loyniorðið næsta tíman."
|
||||
invalid_password: Skeivt loyniorð
|
||||
prompt: Vátta loyniorð fyri at halda fram
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: er ikki ein gildur Ed25519 ella Curve25519 lykil
|
||||
invalid_signature: er ikki ein gildug Ed25519 undirskrift
|
||||
date:
|
||||
formats:
|
||||
default: "%b %d, %Y"
|
||||
|
|
|
@ -1126,10 +1126,6 @@ fr-CA:
|
|||
hint_html: "<strong>Astuce :</strong> Nous ne vous demanderons plus votre mot de passe pour la prochaine heure."
|
||||
invalid_password: Mot de passe invalide
|
||||
prompt: Confirmez votre mot de passe pour continuer
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: n’est pas une clé Ed25519 ou Curve25519 valide
|
||||
invalid_signature: n’est pas une signature Ed25519 valide
|
||||
date:
|
||||
formats:
|
||||
default: "%d %b %Y"
|
||||
|
|
|
@ -1126,10 +1126,6 @@ fr:
|
|||
hint_html: "<strong>Astuce :</strong> Nous ne vous demanderons plus votre mot de passe pour la prochaine heure."
|
||||
invalid_password: Mot de passe invalide
|
||||
prompt: Confirmez votre mot de passe pour continuer
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: n’est pas une clé Ed25519 ou Curve25519 valide
|
||||
invalid_signature: n’est pas une signature Ed25519 valide
|
||||
date:
|
||||
formats:
|
||||
default: "%d %b %Y"
|
||||
|
|
|
@ -1135,10 +1135,6 @@ fy:
|
|||
hint_html: "<strong>Tip:</strong> Wy freegje jo it kommende oere net mear nei jo wachtwurd."
|
||||
invalid_password: Unjildich wachtwurd
|
||||
prompt: Befêstigje wachtwurd om troch te gean
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: is gjin jildige Ed25519- of Curve25519-kaai
|
||||
invalid_signature: is gjin jildige Ed25519-hantekening
|
||||
date:
|
||||
formats:
|
||||
default: "%d %b %Y"
|
||||
|
|
|
@ -1189,10 +1189,6 @@ ga:
|
|||
hint_html: "<strong>Leid:</strong> Ní iarrfaimid do phasfhocal ort arís go ceann uair an chloig eile."
|
||||
invalid_password: Pasfhocal neamhbhailí
|
||||
prompt: Deimhnigh an pasfhocal chun leanúint ar aghaidh
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: nach eochair bhailí Ed25519 nó Curve25519 í
|
||||
invalid_signature: nach síniú bailí Ed25519 é
|
||||
date:
|
||||
formats:
|
||||
default: "%b %d, %Y"
|
||||
|
|
|
@ -1162,10 +1162,6 @@ gd:
|
|||
hint_html: "<strong>Gliocas:</strong> Chan iarr sinn am facal-faire agad ort a-rithist fad uair a thìde."
|
||||
invalid_password: Facal-faire mì-dhligheach
|
||||
prompt: Dearbh am facal-faire airson leantainn air adhart
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: "– chan e iuchair Ed25519 no Curve25519 dhligheach a th’ ann"
|
||||
invalid_signature: "– chan e soidhneadh Ed25519 dligheach a th’ ann"
|
||||
date:
|
||||
formats:
|
||||
default: "%d %b %Y"
|
||||
|
|
|
@ -1135,10 +1135,6 @@ gl:
|
|||
hint_html: "<strong>Nota:</strong> Non che pediremos o contrasinal na seguinte hora."
|
||||
invalid_password: Contrasinal incorrecto
|
||||
prompt: Confirma o contrasinal para continuar
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: non é unha chave Ed25519 ou Curve25519 válida
|
||||
invalid_signature: non é unha sinatura Ed25519 válida
|
||||
date:
|
||||
formats:
|
||||
default: "%d %b, %Y"
|
||||
|
|
|
@ -1171,10 +1171,6 @@ he:
|
|||
hint_html: "<strong>טיפ:</strong> לא נבקש את סיסמתך שוב בשעה הקרובה."
|
||||
invalid_password: סיסמה שגויה
|
||||
prompt: יש לאשר את הסיסמה כדי להמשיך
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: זהו לא מפתח Ed25519 או Curve25519 קביל
|
||||
invalid_signature: היא לא חתימת Ed25519 קבילה
|
||||
date:
|
||||
formats:
|
||||
default: "%b %d, %Y"
|
||||
|
|
|
@ -1135,10 +1135,6 @@ hu:
|
|||
hint_html: "<strong>Hasznos:</strong> Nem fogjuk megint a jelszavadat kérdezni a következő órában."
|
||||
invalid_password: Érvénytelen jelszó
|
||||
prompt: Add meg a jelszót a folytatáshoz
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: érvénytelen Ed25519 vagy Curve25519 kulcs
|
||||
invalid_signature: érvénytelen Ed25519 aláírás
|
||||
date:
|
||||
formats:
|
||||
default: "%Y. %b %d."
|
||||
|
|
|
@ -505,10 +505,6 @@ hy:
|
|||
confirm: Շարունակել
|
||||
invalid_password: Անվաւեր ծածկագիր
|
||||
prompt: Շարունակելու համար մուտքագրիր ծածկագիրդ
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: անվաւեր Ed25519 կամ Curve25519 բանալի
|
||||
invalid_signature: անվաւեր Ed25519 բանալի
|
||||
date:
|
||||
formats:
|
||||
default: "%b %d, %Y"
|
||||
|
|
|
@ -1132,10 +1132,6 @@ ia:
|
|||
hint_html: "<strong>Consilio:</strong> Nos non te demandara tu contrasigno de novo in le proxime hora."
|
||||
invalid_password: Contrasigno non valide
|
||||
prompt: Confirma le contrasigno pro continuar
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: non es un clave Ed25519 o Curve25519 valide
|
||||
invalid_signature: non es un signatura Ed25519 valide
|
||||
date:
|
||||
formats:
|
||||
default: "%d %b %Y"
|
||||
|
|
|
@ -967,10 +967,6 @@ id:
|
|||
hint_html: "<strong>Tip:</strong> Kami tidak akan meminta kata sandi Anda lagi untuk beberapa jam ke depan."
|
||||
invalid_password: Kata sandi tidak valid
|
||||
prompt: Konfirmasi kata sandi untuk melanjutkan
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: bukan kunci Ed25519 atau Curve25519 yang valid
|
||||
invalid_signature: bukan tanda tangan Ed25519 yang valid
|
||||
date:
|
||||
formats:
|
||||
default: "%d %b %Y"
|
||||
|
|
|
@ -1127,10 +1127,6 @@ ie:
|
|||
hint_html: "<strong>Nota</strong>: On ne va petir tui passa-parol denov por li venient hor."
|
||||
invalid_password: Ínvalid passa-parol
|
||||
prompt: Confirmar passa-parol por avansar
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: ne es un valid clave Ed25519 o Curve25519
|
||||
invalid_signature: ne es un valid signatura Ed25519
|
||||
date:
|
||||
formats:
|
||||
default: "%d.%m.%Y"
|
||||
|
|
|
@ -1102,10 +1102,6 @@ io:
|
|||
hint_html: "<strong>Guidilo:</strong> Ni ne demandos vua pasvorto itere til 1 horo."
|
||||
invalid_password: Nevalida pasvorto
|
||||
prompt: Konfirmez pasvorto por avancar
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: ne esas valida klefo Ed25519 o Curve25519
|
||||
invalid_signature: ne esas valida parafo Ed25519
|
||||
date:
|
||||
formats:
|
||||
default: "%d %b, %Y"
|
||||
|
|
|
@ -1139,10 +1139,6 @@ is:
|
|||
hint_html: "<strong>Ábending:</strong> Við munum ekki spyrja þig um lykilorðið aftur næstu klukkustundina."
|
||||
invalid_password: Ógilt lykilorð
|
||||
prompt: Staðfestu lykilorðið til að halda áfram
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: er ekki gildur Ed25519 eða Curve25519-lykill
|
||||
invalid_signature: er ekki gild Ed25519 undirritun
|
||||
date:
|
||||
formats:
|
||||
default: "%d. %b, %Y"
|
||||
|
|
|
@ -1137,10 +1137,6 @@ it:
|
|||
hint_html: "<strong>Suggerimento:</strong> Non ti chiederemo di nuovo la tua password per la prossima ora."
|
||||
invalid_password: Password non valida
|
||||
prompt: Conferma la tua password per continuare
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: non è una chiave Ed25519 o Curve25519 valida
|
||||
invalid_signature: non è una firma Ed25519 valida
|
||||
date:
|
||||
formats:
|
||||
default: "%d %b %Y"
|
||||
|
|
|
@ -1114,10 +1114,6 @@ ja:
|
|||
hint_html: 以後1時間はパスワードの再入力を求めません
|
||||
invalid_password: パスワードが間違っています
|
||||
prompt: 続行するにはパスワードを入力してください
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: 有効なEd25519またはCurve25519キーではありません
|
||||
invalid_signature: 有効なEd25519署名ではありません
|
||||
date:
|
||||
formats:
|
||||
default: "%Y年%m月%d日"
|
||||
|
|
|
@ -549,9 +549,6 @@ kab:
|
|||
confirm: Kemmel
|
||||
invalid_password: Yir awal uffir
|
||||
prompt: Sentem awal uffir send ad tkemleḍ
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: maci d tasarut tameɣtut n Ed25519 neɣ Curve25519
|
||||
date:
|
||||
formats:
|
||||
default: "%d %b %Y"
|
||||
|
|
|
@ -1118,10 +1118,6 @@ ko:
|
|||
hint_html: "<strong>팁:</strong> 한 시간 동안 다시 암호를 묻지 않을 것입니다."
|
||||
invalid_password: 잘못된 암호
|
||||
prompt: 계속하려면 암호를 확인하세요
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: 유효하지 않은 Ed25519 또는 Curve25519 키
|
||||
invalid_signature: 유효하지 않은 Ed25519 서명
|
||||
date:
|
||||
formats:
|
||||
default: "%Y-%m-%d"
|
||||
|
|
|
@ -981,10 +981,6 @@ ku:
|
|||
hint_html: "<strong>Nîşe:</strong>Ji bo demjimêreke din em ê borînpeyva te careke din ji te nexwazin."
|
||||
invalid_password: Borînpeyva nederbasdar
|
||||
prompt: Ji bo bidomî borînpeyvê bipejirîne
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: ed25519 ne derbasdare ne jî Curve25519 kilîta
|
||||
invalid_signature: Ed25519 ne îmzeyek derbasdar e
|
||||
date:
|
||||
formats:
|
||||
default: "%b%d%Y"
|
||||
|
|
|
@ -1127,10 +1127,6 @@ lad:
|
|||
hint_html: "<strong>Konsejo:</strong> No retornaremos a demandarte por el kod durante la sigiente ora."
|
||||
invalid_password: Kod inkorekto
|
||||
prompt: Konfirma kod para segir
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: no es una yave Ed25519 o Curve25519 valida
|
||||
invalid_signature: no es una firma Ed25519 valida
|
||||
date:
|
||||
formats:
|
||||
default: "%d %b %Y"
|
||||
|
|
|
@ -1136,10 +1136,6 @@ lv:
|
|||
hint_html: "<strong>Padoms:</strong> Nākamās stundas laikā mēs tev vairs neprasīsim paroli."
|
||||
invalid_password: Nepareiza parole
|
||||
prompt: Lai turpinātu, apstiprini paroli
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: nav derīga Ed25519 vai Curve25519 atslēga
|
||||
invalid_signature: nav derīgs Ed25519 paraksts
|
||||
date:
|
||||
formats:
|
||||
default: "%b %d, %Y"
|
||||
|
|
|
@ -1089,10 +1089,6 @@ ms:
|
|||
hint_html: "<strong>Petua:</strong> Kami tidak akan meminta kata laluan anda lagi untuk sejam berikutnya."
|
||||
invalid_password: Kata laluan tidak sah
|
||||
prompt: Sahkan kata laluan untuk teruskan
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: bukan kunci Ed25519 atau Curve25519 yang sah
|
||||
invalid_signature: bukan tandatangan Ed25519 yang sah
|
||||
date:
|
||||
formats:
|
||||
default: "%b %d, %Y"
|
||||
|
|
|
@ -1081,10 +1081,6 @@ my:
|
|||
hint_html: "<strong>အကြံပြုချက် -</strong> နောက်နာရီများတွင် သင့်စကားဝှက်ကို ထပ်မံတောင်းဆိုမည်မဟုတ်ပါ။"
|
||||
invalid_password: စကားဝှက် မမှန်ပါ
|
||||
prompt: ဆက်လက်လုပ်ဆောင်ရန်အတွက် စကားဝှက်အတည်ပြုပါ
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: မှန်ကန်သော Ed25519 သို့မဟုတ် Curve25519 ကီး မဟုတ်ပါ။
|
||||
invalid_signature: မှန်ကန်သော Ed25519 လက်မှတ်မဟုတ်ပါ
|
||||
date:
|
||||
formats:
|
||||
default: "%b %d, %Y"
|
||||
|
|
|
@ -1135,10 +1135,6 @@ nl:
|
|||
hint_html: "<strong>Tip:</strong> We vragen jou het komende uur niet meer naar jouw wachtwoord."
|
||||
invalid_password: Ongeldig wachtwoord
|
||||
prompt: Bevestig wachtwoord om door te gaan
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: is geen geldige Ed25519- of Curve25519-sleutel
|
||||
invalid_signature: is geen geldige Ed25519-handtekening
|
||||
date:
|
||||
formats:
|
||||
default: "%d %b %Y"
|
||||
|
|
|
@ -1132,10 +1132,6 @@ nn:
|
|||
hint_html: "<strong>Tips:</strong> Vi skal ikkje spørja deg om passordet ditt igjen i laupet av den neste timen."
|
||||
invalid_password: Ugyldig passord
|
||||
prompt: Stadfest passord for å halda fram
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: er ikkje ein gild Ed25519 eller Curve25519 nykel
|
||||
invalid_signature: er ikkje ein gild Ed25519-signatur
|
||||
date:
|
||||
formats:
|
||||
default: "%d. %b, %Y"
|
||||
|
|
|
@ -1120,10 +1120,6 @@
|
|||
hint_html: "<strong>Tips:</strong> Vi ber deg ikke om passordet ditt igjen i løpet av neste time."
|
||||
invalid_password: Ugyldig passord
|
||||
prompt: Bekreft passordet for å fortsette
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: er ikke en gyldig Ed25519- eller Curve25519-nøkkel
|
||||
invalid_signature: er ikke en gyldig Ed25519-signatur
|
||||
date:
|
||||
formats:
|
||||
default: "%d. %b, %Y"
|
||||
|
|
|
@ -1171,10 +1171,6 @@ pl:
|
|||
hint_html: "<strong>Informacja:</strong> Nie będziemy prosić Cię o ponowne podanie hasła przez następną godzinę."
|
||||
invalid_password: Nieprawidłowe hasło
|
||||
prompt: Potwierdź hasło, aby kontynuować
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: nie jest prawidłowym kluczem Ed25519 lub Curve25519
|
||||
invalid_signature: nie jest prawidłowym podpisem Ed25519
|
||||
date:
|
||||
formats:
|
||||
default: "%d. %b %Y"
|
||||
|
|
|
@ -1130,10 +1130,6 @@ pt-BR:
|
|||
hint_html: "<strong>Dica:</strong> Não pediremos novamente sua senha pela próxima hora."
|
||||
invalid_password: Senha inválida
|
||||
prompt: Confirme sua senha para continuar
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: não é uma chave Ed25519 ou Curve25519 válida
|
||||
invalid_signature: não é uma assinatura Ed25519 válida
|
||||
date:
|
||||
formats:
|
||||
default: "%d %b, %Y"
|
||||
|
|
|
@ -1135,10 +1135,6 @@ pt-PT:
|
|||
hint_html: "<strong>Dica:</strong> Não vamos pedir novamente a sua palavra-passe durante a próxima hora."
|
||||
invalid_password: Palavra-passe inválida
|
||||
prompt: Confirmar a sua palavra-passe para continuar
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: não é uma chave Ed25519 ou Curve25519 válida
|
||||
invalid_signature: não é uma assinatura Ed25519 válida
|
||||
date:
|
||||
formats:
|
||||
default: "%d %b %Y"
|
||||
|
|
|
@ -1155,10 +1155,6 @@ ru:
|
|||
hint_html: "<strong>Подсказка</strong>: мы не будем спрашивать пароль повторно в течение часа."
|
||||
invalid_password: Неверный пароль
|
||||
prompt: Введите пароль для продолжения
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: не является допустимым Ed25519 или Curve25519 ключом
|
||||
invalid_signature: не является допустимой Ed25519 подписью
|
||||
date:
|
||||
formats:
|
||||
default: "%d %b %Y"
|
||||
|
|
|
@ -709,10 +709,6 @@ sc:
|
|||
hint_html: "<strong>Cussìgiu:</strong> No t'amus a torrare a dimandare sa crae in s'ora imbeniente."
|
||||
invalid_password: Sa crae no est vàlida
|
||||
prompt: Cunfirma sa crae pro sighire
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: no est una crae Ed25519 o Curve25519 vàlida
|
||||
invalid_signature: no est una firma Ed25519 vàlida
|
||||
date:
|
||||
formats:
|
||||
default: "%d %b, %Y"
|
||||
|
|
|
@ -971,10 +971,6 @@ sco:
|
|||
hint_html: "<strong>wee tip:</strong> We wullnae ask ye fir yer passwird again fir the neist oor."
|
||||
invalid_password: Passwird isnae valid
|
||||
prompt: Confirm yer passwird fir tae continue
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: isnae a valid Ed25519 or Curve25519 key
|
||||
invalid_signature: isnae a valid Ed25519 signature
|
||||
date:
|
||||
formats:
|
||||
default: "%b %d, %Y"
|
||||
|
|
|
@ -852,10 +852,6 @@ si:
|
|||
hint_html: "<strong>ඉඟිය:</strong> අපි ඉදිරි පැය සඳහා නැවත ඔබගේ මුරපදය ඔබෙන් නොඉල්ලමු."
|
||||
invalid_password: මුරපදය වැරදිය
|
||||
prompt: ඉදිරියට යාමට මුරපදය තහවුරු කරන්න
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: වලංගු Ed25519 හෝ Curve25519 යතුරක් නොවේ
|
||||
invalid_signature: වලංගු Ed25519 අත්සනක් නොවේ
|
||||
date:
|
||||
formats:
|
||||
default: "%Y %b %d"
|
||||
|
|
|
@ -1168,10 +1168,6 @@ sl:
|
|||
hint_html: "<strong>Namig:</strong> naslednjo uro vas ne bomo več vprašali po vašem geslu."
|
||||
invalid_password: Neveljavno geslo
|
||||
prompt: Potrdite geslo za nadaljevanje
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: ni veljaven ključ Ed25519 ali Curve25519
|
||||
invalid_signature: ni veljaven podpis Ed25519
|
||||
date:
|
||||
formats:
|
||||
default: "%d %b %Y"
|
||||
|
|
|
@ -1129,10 +1129,6 @@ sq:
|
|||
hint_html: "<strong>Ndihmëz:</strong> S’do t’ju pyesim për fjalëkalimin tuaj sërish, për një orë."
|
||||
invalid_password: Fjalëkalim i pavlefshëm
|
||||
prompt: Që të vazhdohet, ripohoni fjalëkalimin
|
||||
crypto:
|
||||
errors:
|
||||
invalid_key: s’është kyç Ed25519 ose Curve25519 i vlefshëm
|
||||
invalid_signature: s’është nënshkrim Ed25519 i vlefshëm
|
||||
date:
|
||||
formats:
|
||||
default: "%d %b, %Y"
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue