Создание сайтов в Мелекино, ДНР. Расширение Flarum: добавление адреса Web3 в профиль пользователя

 
 

В нашем первом руководстве по Flarum — «Написание расширения Flarum: создание настраиваемого поля „— мы рассказали, как добавить новое настраиваемое поле в профиль пользователя в невероятно быстром и чрезвычайно расширяемом программном обеспечении для форумов с открытым исходным кодом под названием Flarum. Мы добавили поле web3address, учетную запись пользователя Web3.

В этом втором руководстве мы делаем еще один шаг вперед, позволяя пользователям добавлять адрес Web3 в свой профиль.

Примечание ℹ: Экосистема Web3 — это новый интернет с децентрализованным хостингом, собственными данными и устойчивым к цензуре общением. Для знакомства с Web3 см. этот 15-минутный доклад на FOSDEM.

Криптографическое добавление Web3

На данный момент наше пользовательское поле работает, его настраивают пользователи и редактируют администраторы форума. Теперь давайте удостоверимся, что пользователи могут добавлять свой адрес криптографически безопасным способом.

Это означает, что пользователь сможет добавить только тот адрес, владельцем которого он доказал. Вы подтверждаете право собственности на адрес, подписывая сообщение закрытым ключом этого адреса. Только человек, у которого есть закрытый ключ пары открытого и закрытого ключей, считается владельцем этой пары ключей. Открытая часть пары открытого и закрытого ключей — это основа, из которой математически выводится адрес Web3.

Чтобы владеть некоторыми адресами, пользователь должен установить расширение Polkadot JS и создать учетную запись. Пользовательский интерфейс не требует пояснений, но при необходимости здесь есть более подробное руководство.

Поле, которое мы добавили в предыдущем уроке, в настоящее время позволяет пользователям вручную устанавливать любое значение, но это означает, что пользователи могут вводить любой адрес или даже какую-то тарабарщину. Мы хотим, чтобы они добавляли только свои собственные, поэтому мы заменим ее кнопкой, которая будет:

запрашивать разрешение на доступ к расширению браузера, содержащему учетные записи

загрузите учетные записи и предложите раскрывающийся список, чтобы выбрать одну из них

попросите пользователя подписать сообщение с этим адресом и проверить эту подпись

зарегистрируйте эту учетную запись как адрес пользователя Web3

Давайте погрузимся.

Кнопка

Сначала нам нужно изменить поле ввода Web3 на раскрывающееся меню. Давайте создадим components/Web3Dropdown.js:

import Component from „flarum/Component“;

import Dropdown from „flarum/components/Dropdown“;

export default class Web3Dropdown extends Component {

view () {

return (

<Dropdown

buttonClassName="Button"

onclick={this.handleClick.bind (this) }

label="Add Web3 Account"

>

 

) ;

}

handleClick (e) {

console.log („Pick something“) ;

}

}

Мы создаем новый компонент в стиле, Web3Field.jsкоторый мы создали ранее, но теперь мы возвращаем экземпляр компонента Dropdown. Компонент Dropdown — один из нескольких стандартных компонентов JS во Flarum. Вы можете найти полный список здесь. Мы также присваиваем ему класс „Кнопка“, чтобы он соответствовал стилю остальной части форума. По щелчку мы печатаем сообщение.

Компонент представляет собой кнопку с возможностью вызова раскрывающегося списка из переданных элементов, очень похожего на меню „Управление“, которое администратор форума может видеть в профиле пользователя:

простая кнопка

Зависимости

В папку JS нашего расширения мы добавим две зависимости:

yarn add @polkadot/util-crypto @polkadot/util @polkadot/extension-dapp

Примечание ⚠: не забудьте остановить процесс, если вы все еще работаете yarn dev, и не забудьте запустить его снова после установки этих зависимостей!

util-cryptoсодержит некоторые вспомогательные функции для криптографических операций. utilсодержит некоторые основные утилиты, такие как преобразование строк в байты и т. д. (Здесь есть документы для обоих.) extension-dapp— это вспомогательный уровень, который позволяет написанному нами JS взаимодействовать с установленным нами расширением Polkadot JS. (Посетите документы здесь.)

Запрос разрешения и получение учетных записей

Давайте теперь изменим наш раскрывающийся список, чтобы запросить у пользователя разрешение на доступ к его учетным записям Web3:

import { web3Accounts, web3Enable } from „@polkadot/extension-dapp“;

//...

async handleClick (e) {

await web3Enable („Flarum Web3 Address Extension“) ;

const accounts = await web3Accounts () ;

console.log (accounts) ;

}

Обратите внимание, что мы изменили handleClickфункцию на async! Нам нужно это, чтобы иметь возможность awaitпромисов в коде. В противном случае мы застряли бы с вложенными thenвызовами.

Сначала мы вызываем web3Enable, который запрашивает у нас разрешение на доступ к расширению. Затем мы берем все учетные записи пользователя и выводим их в консоль. Если у вас установлено расширение Polkadot JS и загружены некоторые учетные записи, не стесняйтесь попробовать это прямо сейчас.

Разрешить или отклонить

Вывод в консоль

Но что, если у кого-то не установлено расширение? У нас может быть настройка на уровне администратора, которая позволяет нам выбирать, скрывать ли кнопку, если расширения нет рядом, или перенаправлять пользователя на ее URL-адрес, но сейчас давайте выберем последнее:

import { web3Accounts, web3Enable, isWeb3Injected } from „@polkadot/extension-dapp“;

//...

async handleClick (e) {

await web3Enable („Flarum Web3 Address Extension“) ;

if (isWeb3Injected) {

const accounts = await web3Accounts () ;

console.log (accounts) ;

} else {

window.location = „https: //github.com/polkadot-js/extension“;

}

}

Выбор учетной записи

Следующий шаг — позволить пользователю выбрать учетную запись и в то же время попросить его подписать сообщение с ее помощью. После проверки эта подпись неопровержимо доказывает право собственности на учетную запись.

Компонент Dropdown принимает itemsмассив элементов для отображения. Чаще всего это массив Buttonэлементов, где Button — обычный компонент Flarum. Чтобы дать нашему компоненту свойство данных для всего компонента, которым мы можем манипулировать и основывать изменения, мы определяем его в oninit:

oninit () {

this.web3accounts = [];

}

Вместо того, чтобы просто console.logуказать accounts, мы устанавливаем accountsэтот новый атрибут:

this.web3accounts = accounts;

m.redraw () ;

Примечание ⚠: мы используем redrawздесь, чтобы сделать mithril (m) повторную визуализацию нашего компонента. Если мы этого не сделаем, компонент сначала отобразит пустой раскрывающийся список (у него еще нет учетных записей), и ему потребуется еще одно закрытие и открытие раскрывающегося списка, чтобы отобразить учетные записи (что вызывает перерисовку). Мы хотим, чтобы учетные записи появлялись в раскрывающемся списке, как только они загружаются, даже если раскрывающийся список уже открыт и не содержит элементов, так что это поможет. Всякий раз, когда вам нужно применить изменения к вашему компоненту динамически без триггеров пользовательского интерфейса, обычно на основе некоторых удаленных выборок данных или обработки данных, вы можете использовать m.redraw ().

Наконец, мы делаем viewтак, чтобы функция, отвечающая за наш рендеринг, реагировала на это изменение:

view () {

const items = [];

if (this.web3accounts.length) {

for (let i = 0; i < this.web3accounts.length; i++) {

items.push (

<Button

value={this.web3accounts[i].address}

onclick={this.handleAccountSelect}

>

{this.web3accounts[i].address}

{this.web3accounts[i].meta.name

? ` — ${this.web3accounts[i].meta.name}`

: „„}

 

) ;

}

}

return (

<Dropdown

buttonClassName="Button"

onclick={this.handleClick.bind (this) }

label="Set Web3 Account"

>

{items}

 

) ;

}

Сначала мы определяем пустой массив заполнителей. Затем, если в этом компоненте хранится больше нуля web3accounts, мы перебираем их, чтобы создать кнопку для каждой учетной записи со значением, установленным в адрес учетной записи, и меткой, установленной в виде комбинации адреса и метки, определенной в расширении. Наконец, мы передаем эти кнопки в компонент Dropdown.

Нам также нужно импортировать компонент Button:

import Button from „flarum/components/Button“;

Примечание ℹ: обратите внимание, что мы не привязываемся thisк onclickобработчику событий каждой кнопки. Это связано с тем this, что контекст кнопки изменится на родительский раскрывающийся компонент, а не на нажатую кнопку, и сделает получение значения кнопки менее простым.

Далее нам нужно отреагировать на клик пользователя по одному из адресов в меню:

handleAccountSelect () {

console.log (this.value) ;

}

Тестирование покажет, что мы действительно можем выбрать учетную запись Web3 из нашего расширения, и что она регистрируется в консоли после выбора.

настройка учетной записи Web3 на странице профиля

вид из консоли

Проверка учетной записи

Наконец, нам нужно попросить пользователя подписать сообщение. Допустим, сообщение „Чрезвычайная собственность“. Это побудит их ввести пароль во всплывающем окне расширения и вернуть подписанное сообщение.

Во-первых, немного импорта:

import {

web3Accounts,

web3Enable,

isWeb3Injected,

web3FromAddress, // <— this is new

} from „@polkadot/extension-dapp“;

import { stringToHex } from „@polkadot/util“; // <— this is new

web3FromAddress— это удобный метод создания объекта Web3, стандартного объекта для взаимодействия Web3, с заданным адресом в качестве „главного героя“. stringToHexиспользуется для преобразования строки в шестнадцатеричное представление, которое является форматом данных, ожидаемым подписывающей стороной (байты):

async handleAccountSelect () {

const address = this.value;

const web3 = await web3FromAddress (address) ;

const signer = web3.signer;

const hexMessage = stringToHex („Extreme ownership“) ;

try {

const signed = await signer.signRaw ({

type: „bytes“,

data: hexMessage,

address: address,

}) ;

console.log (signed) ;

} catch (e) {

console.log („Signing rejected“) ;

return;

}

}

Сначала мы превращаем функцию в функцию, asyncчтобы мы могли использовать await. Затем мы создаем web3экземпляр из нашего адреса, как описано выше, и извлекаем подписывающую сторону. Подписывающее устройство — это криптографический инструмент, который автоматически извлекает открытый ключ из адреса и подписывает заданное сообщение, представленное в байтах. (Это то, что нам нужно hexMessageдля преобразования нашей строки в байты, представленные в шестнадцатеричном виде.)

Единственный способ получить signedэто подписать; все остальное вызывает ошибку.

Сохранение учетной записи

Наконец, мы следуем тому же процессу, что и раньше, Web3Field.js— передаем адрес в save:

async handleAccountSelect () {

const address = this.value;

const web3 = await web3FromAddress (address) ;

const signer = web3.signer;

const hexMessage = stringToHex („Extreme ownership“) ;

try {

const signed = await signer.signRaw ({

type: „bytes“,

data: hexMessage,

address: address,

}) ;

console.log (signed) ;

const user = app.session.user;

user

.save ({

web3address: address,

})

.then (() => m.redraw ());

} catch (e) {

console.log („Signing rejected“) ;

return;

}

}

Примечание ℹ: мы добавляем m.redraw, чтобы обновить значение на экране после сохранения. Перерисовка вызовет обновление JavaScript расширения и считывает данные из экземпляра User, возвращенного операцией сохранения, показывая наш обновленный адрес, если сохранение прошло успешно.

Проверка на стороне сервера

Это достаточно безопасно. Даже если кто-то взломает наш JS и вставит адрес Web3, который ему не принадлежит, он мало что сможет с этим сделать. Они могут просто представить себя кем-то, кем они не являются. Тем не менее, мы можем обойти это, выполнив некоторую проверку на стороне сервера.

Поскольку в настоящее время нет набора инструментов для PHP для используемых нами криптографических функций, нам нужно проявить творческий подход. В частности, мы напишем сценарий командной строки на TypeScript, который будем вызывать из PHP.

В js/src/forum, создайте scriptsпапку и добавьте файл verify.js:

let util_crypto = require („@polkadot/util-crypto“) ;

util_crypto

.cryptoWaitReady ()

.then (() => {

const verification = util_crypto.signatureVerify (

process.argv[2], // message

process.argv[3], // signature

process.argv[4] // address

) ;

if (verification.isValid === true) {

console.log („OK“) ;

process.exitCode = 0;

} else {

console.error („Verification failed“) ;

process.exitCode = 1;

}

})

.catch (function (e) {

console.error (e.message) ;

process.exit (1) ;

}) ;

Пакет утилит для шифрования содержит вспомогательные методы для всего, что нам нужно. cryptoWaitReadyожидает инициализации криптоопераций — в частности, sr25519, который мы здесь используем, нуждается в части WASM для прогрева. Затем мы проверяем подпись с помощью signatureVerifyфункции, обрабатывая предоставленные аргументы.

Мы можем протестировать это локально (получить значения из полезной нагрузки запроса на сохранение после установки адреса в раскрывающемся списке или путем ручной подписи сообщения „Экстремальное владение“ в пользовательском интерфейсе Polkadot):

$ node src/forum/scripts/verify.js „Extreme ownership“ 0×2cd37e33c18135889f4d4e079e69be6dd32688a6bf80dcf072b4c227a325e94a89de6a80e3b09bea976895b1898c5acb5d28bccd2f8742afaefa9bae43cfed8b 5EFfZ6f4KVutjK6KsvRziSNi1vEVDChzY5CFuCp1aU6jc2nB

> OK

$ node src/forum/scripts/verify.js „Wrong message“ 0×2cd37e33c18135889f4d4e079e69be6dd32688a6bf80dcf072b4c227a325e94a89de6a80e3b09bea976895b1898c5acb5d28bccd2f8742afaefa9bae43cfed8b 5EFfZ6f4KVutjK6KsvRziSNi1vEVDChzY5CFuCp1aU6jc2nB

> Verification failed

Наш скрипт проверки работает.

Примечание ℹ: одно и то же сообщение, подписанное одним и тем же адресом, каждый раз будет давать разные хэши. Не рассчитывайте на то, что они одинаковые. Например, эти три полезных нагрузки являются „Полное владение“, подписанными одним и тем же адресом 3 раза:

// {„web3address“:„5EFfZ6f4KVutjK6KsvRziSNi1vEVDChzY5CFuCp1aU6jc2nB“,„signedMessage“:„0×0c837b9a5ba43e92159dc2ff31d38f0e52c27a9a5b30ff359e8f09dc33f75e04e403a1e461f3abb89060d25a7bdbda58a5ff03392acd1aa91f001feb44d92c85"}““

// {„web3address“:„5EFfZ6f4KVutjK6KsvRziSNi1vEVDChzY5CFuCp1aU6jc2nB“,„signedMessage“:„0×3857b37684ee7dfd67304568812db8d5a18a41b2344b15112266785da7741963bdd02bb3fd92ba78f9f6d5feae5a61cd7f9650f3de977de159902a52ef27d081"}““

// {„web3address“:„5EFfZ6f4KVutjK6KsvRziSNi1vEVDChzY5CFuCp1aU6jc2nB“,„signedMessage“:«0xa66438594adfbe72cca60de5c96255edcfd4210a8b5b306e28d7e5ac8fbad86849311333cdba49ab96de1955a69e28278fb9d71076a2007e770627a9664f4a86"}“»

Нам также нужно изменить наш app.session.user.saveвызов в Dropdownкомпоненте, чтобы он фактически отправлял подписанное сообщение на серверную часть:

user

.save ({

web3address: address,

signedMessage: signed.signature,

})

.then (() => console.log («Saved»));

Когда наше web3addressзначение сохраняется для пользователя, нам нужно перехватить эту операцию, проверить подпись, только если это пользователь делает сохранение, а не администратор, и сохранить, если все в порядке, или отклонить (предпочтительно с сообщением об ошибке), если нет.

Давайте изменим нашу handleфункцию в SaveUserWeb3Address.php:

if (isset ($attributes['web3address’])) {

if (! $isSelf) {

$actor→assertPermission ($canEdit) ;

}

chdir (__DIR__. «/... /... /js») ;

$command = «node src/forum/scripts/verify.js \»Extreme ownership\» «. $attributes['signedMessage’]. ««. $attributes['web3address’]. „2>&1“;

exec ($command, $out, $err) ;

if ($err) {

return false;

}

$user→web3address = $attributes['web3address’];

$user→save () ;

}

Мы добавили строки с 6 по 12: мы меняем каталог на тот, в котором находится наш скрипт проверки. Затем мы формируем вызов скрипта из командной строки, передавая необходимые параметры, и, наконец, если код ошибки $errотличается от falsy (так и будет, 0если все прошло хорошо), мы останавливаем процесс сохранения.

Однако это не позволяет администраторам изменять значение по своему усмотрению, поэтому давайте добавим это. Согласно документам, у an $actorесть isAdminпомощник. Теперь окончательная версия нашего handleметода:

public function handle (Saving $event)

{

$user = $event→user;

$data = $event→data;

$actor = $event→actor;

$isSelf = $actor→id === $user→id;

$canEdit = $actor→can ('edit’, $user) ;

$attributes = Arr: get ($data, 'attributes’, []) ;

if (isset ($attributes['web3address’])) {

if (! $isSelf) {

$actor→assertPermission ($canEdit) ;

}

if (! $actor→isAdmin ()) {

chdir (__DIR__. „/... /... /js“) ;

$command = „node src/forum/scripts/verify.js \“Extreme ownership\» «. $attributes['signedMessage’]. ««. $attributes['web3address’]. „2>&1“;

exec ($command, $out, $err) ;

if ($err) {

return false;

}

}

$user→web3address = $attributes['web3address’];

$user→save () ;

}

}

Ясность ошибки

Последнее, что мы должны сделать, это сделать ошибку более удобной для UX, если проверка адреса не удалась. A return falseне очень полезен; пользовательский интерфейс просто ничего не делал. Поскольку это ошибка проверки (нам не удалось проверить право собственности пользователя на этот адрес), мы можем выдать ValidationException:

if ($err) {

throw new Flarum\Foundation\ValidationException (["Signature could not be verified."]) ;

}

Теперь, если наша проверка не пройдена, мы увидим это в удобном сообщении об ошибке:

сообщение об ошибке

Предостережение перед развертыванием

Поскольку мы находимся в режиме разработки, наше расширение имеет доступ к Node и Yarn и может установить зависимости Polkadot, необходимые для криптографии. Однако в производственной среде нет простого способа автоматического запуска yarn installпакета, установленного Composer, поэтому наш сценарий проверки не будет работать без значительного вмешательства пользователя. Нам нужно verify.jsсобрать сценарий в файл, который может запускаться NodeJS напрямую без менеджеров пакетов. Это по-прежнему означает, что на нашем рабочем сервере должен быть установлен NodeJS, но это все, что ему нужно — по крайней мере, до тех пор, пока используемые нами криптографические функции не появятся в версии PHP.

Чтобы связать наш скрипт, внутри папки JS расширения мы можем запустить:

npx browserify src/forum/scripts/verify.js > dist/verify.js

Это запустит Browserify без его установки, соберет все зависимости и выведет один большой двоичный объект JS, который мы сохраним в файле dist/verify.js. Теперь мы можем зафиксировать этот файл в репозитории расширения и выбрать его, если он существует. На самом деле, мы можем заставить наше расширение определять, находится ли форум в debugрежиме или нет, и ориентироваться на исходный и исходный файлы на основе этого флага:

if (! $actor→isAdmin ()) {

chdir (__DIR__. „/... /... /js“) ;

if (app (\Flarum\Foundation\Config: class) →inDebugMode ()) {

$command = „node src/forum/scripts/verify.js \“Extreme ownership\» «. $attributes['signedMessage’]. ««. $attributes['web3address’]. „2>&1“;

} else {

$command = „node dist/verify.js \“Extreme ownership\» «. $attributes['signedMessage’]. ««. $attributes['web3address’]. „2>&1“;

}

exec ($command, $out, $err) ;

if ($err) {

throw new ValidationException ([»Signature could not be verified."]) ;

}

}

Наш Listener прочитает исходную версию, если inDebugModeвозвращает true, или dist/verify.jsиначе.

Вывод

Пользователи нашего форума теперь могут добавлять свои адреса Web3 в свой профиль. Вы можете найти опубликованное расширение по адресу swader/web3address.

Поскольку некоторые пользователи могут не использовать Chrome или Firefox, и у них не будет доступного расширения, а некоторые могут предпочесть альтернативные методы создания учетных записей, описанные здесь, мы разрешаем администраторам вручную вводить адреса для определенных учетных записей пользователей при условии, что эти пользователи подтвердят право собственности на их адрес. Однако, поскольку это ручной процесс, процесс проверки выходит за рамки данного руководства.

Это закладывает основу для будущего использования этих адресов. Они будут использоваться для выплаты очков участия — крипто-токенов — за активность пользователей на форуме, поощряя оживленные дискуссии и качественное взаимодействие. Мы увидим, как это сделать в следующем руководстве.

3D-печать5GABC-анализAndroidAppleAppStoreAsusCall-центрChatGPTCRMDellDNSDrupalExcelFacebookFMCGGoogleHuaweiInstagramiPhoneLinkedInLinuxMagentoMicrosoftNvidiaOpenCartPlayStationPOS материалPPC-специалистRuTubeSamsungSEO-услугиSMMSnapchatSonyStarlinkTikTokTwitterUbuntuUp-saleViasatVPNWhatsAppWindowsWordPressXiaomiYouTubeZoomАвдеевкаАктивные продажиАкцияАлександровск ЛНРАлмазнаяАлчевскАмвросиевкаАнализ конкурентовАнализ продажАнтимерчандайзингАнтрацитАртемовскАртемовск ЛНРАссортиментная политикаБелгородБелицкоеБелозерскоеБердянскБизнес-идеи (стартапы)БрендБрянкаБукингВахрушевоВендорВидеоВикипедияВирусная рекламаВирусный маркетингВладивостокВнутренние продажиВнутренний маркетингВолгоградВолновахаВоронежГорловкаГорнякГорскоеДебальцевоДебиторкаДебиторская задолженностьДезинтермедитацияДзержинскДивизионная система управленияДизайнДимитровДирект-маркетингДисконтДистрибьюторДистрибьюцияДобропольеДокучаевскДоменДружковкаЕкатеринбургЕнакиевоЖдановкаЗапорожьеЗимогорьеЗолотоеЗоринскЗугрэсИжевскИловайскИрминоКазаньКалининградКировскКировскоеКомсомольскоеКонстантиновкаКонтент-маркетингКонтент-планКопирайтингКраматорскКрасноармейскКрасногоровкаКраснодарКраснодонКраснопартизанскКрасный ЛиманКрасный ЛучКременнаяКураховоКурскЛисичанскЛуганскЛутугиноМакеевкаМариупольМаркетингМаркетинговая информацияМаркетинговые исследованияМаркетинговый каналМаркетинг услугМаркетологМарьинкаМедиаМелекиноМелитопольМенеджментМерчандайзерМерчандайзингМиусинскМолодогвардейскМоскваМоспиноНижний НовгородНиколаевНиколаевкаНишевой маркетингНовоазовскНовогродовкаНоводружескНовосибирскНумерическая дистрибьюцияОдессаОмскОтдел маркетингаПартизанский маркетингПервомайскПеревальскПетровскоеПлата за кликПоисковая оптимизацияПопаснаяПравило ПаретоПривольеПрогнозирование продажПродвижение сайтов в ДонецкеПроизводство видеоПромоПромоушнПрямой маркетингРабота для маркетологаРабота для студентаРазработка приложенийРаспродажаРегиональные продажиРекламаРеклама на асфальтеРемаркетингРетро-бонусРибейтРитейлРовенькиРодинскоеРостов-на-ДонуРубежноеСамараСанкт-ПетербургСаратовСватовоСвердловскСветлодарскСвятогорскСевастопольСеверодонецкСеверскСедовоСейлз промоушнСелидовоСимферопольСинергияСколковоСлавянскСнежноеСоздание сайтов в ДонецкеСоледарСоциальные сетиСочиСтаробельскСтаробешевоСтахановСтимулирование сбытаСуходольскСчастьеТелемаркетингТельмановоТираспольТорговый представительТорезТрейд маркетингТрейд промоушнТюменьУглегорскУгледарУкраинскХабаровскХарцызскХерсонХостингЦелевая аудиторияЦифровой маркетингЧасов ЯрЧелябинскШахтерскЮжно-СахалинскЮнокоммунаровскЯндексЯсиноватая