> For the complete documentation index, see [llms.txt](https://wfsys.gitbook.io/wt-practice-mobile/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wfsys.gitbook.io/wt-practice-mobile/main/lesson_authentication.md).

# Урок 2. Аутентификация пользователей в программе

Так как мобильное приложение расширяет возможности десктопного или web-приложения, то оно должно поддерживать многопользовательский режим. При реализации процесса аутентификации пользователей в мобильном приложение важно в первую очередь ответить на вопрос: кто будет пользоваться этим приложением?

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

В качестве учебного проекта будем делать мобильное приложение для внутренних пользователей. Регистрацию внешних пользователей рассмотрим в дополнительных материалах. <mark style="background-color:orange;">Т.к. в таком случае нужно регистрироваться на стороннем сервисе, предоставляющем услуги по отправке уведомлений на устройство</mark>

{% hint style="info" %}
Аутентификация - процедура проверки подлинности, например проверка подлинности пользователя путем сравнения введенного им пароля с паролем, сохраненным в базе данных.

Авторизация - предоставление определенному лицу или группе лиц прав на выполнение определенных действий.
{% endhint %}

При реализации экрана входа нам понадобятся иконки. Скачайте архив с изображениями и разархивируйте его в папку проекта **\Template\Projects\1. Template\MobileForms\Images**.

{% file src="/files/c8YhhQNptxsU1RW22uKE" %}

Далее вспомним, как хранятся данные пользователей в базе данных и процесс аутентификации пользователей в программе.

## Список пользователей <a href="#users-list" id="users-list"></a>

### База данных <a href="#database" id="database"></a>

В базе данных в таблице **public.user** хранится общий список пользователей системы:

<figure><img src="/files/ADpiUwVRdJNGHTaex4bH" alt=""><figcaption></figcaption></figure>

Описание полей таблицы:

* user\_id - идентификатор глобального пользователя;
* user\_name - логин глобального пользователя;
* user\_full\_name - полное имя глобального пользователя;
* person - признак, определяющий, является ли данный глобальный пользователь реальным пользователем;
* enabled - признак, определяющий, является ли данный глобальный пользователь включенным;
* language\_id - идентификатор языка глобального пользователя;
* time\_zone\_info\_id - идентификатор временной зоны глобального пользователя;
* user\_password - хеш пароля глобального пользователя.

В таблице есть записи системных пользователей:

* *Служба Workflow Engine* ($workflow\_engine$) - системный пользователь службы Workflow Engine, от имени которого совершаются некоторые автоматические действия с данными в базе данных;
* *WS. Гость* (WS\_GUEST) - системный пользователь, под которым запускаются клиентское приложение для десктопа и мобильное приложение.

Пользователи *Администратор*, *Пользователь 1*, *Пользователь 2* и *Пользователь 3* - реальные пользователи, под которыми можно работать в программе.

Таблица **template.user** содержит идентификаторы пользователей, которые имеют доступ к бизнес-процессу Template, а так же флаг *access\_to\_mobile\_app* доступа к мобильному приложению.

<figure><img src="/files/UMS2m61JLMbN9XShWQ8e" alt=""><figcaption></figcaption></figure>

Так же в эту таблицу можно добавить поля для индивидуальных пользовательских настроек, например, для доступа к какому-нибудь отчету.

{% hint style="info" %}
Здесь стоит сказать о том, что WT-приложение может объединять несколько программ для разных бизнес-процессов, например, для автомойки и шиномонтажа.

Каждая программа будет иметь свой набор xml-файлов форм и свой серверный xml-файл с запросами. И, в большинстве случаев, серверная часть у них будет одна - нет смысла специально разделать серверные части, если в этом нет потребности. А значит, и базу данных можно использовать одну, но для каждого бизнес-процесса делать отдельную схему, чтобы разделить таблицы и функции.

Несмотря на то, что автомойка и шиномонтаж разные программы, какие-то пользователи могут иметь доступ к обеим программам, например, администраторы. Таким образом, в таблице *public.user* будут храниться логин и пароль, а так же пользовательские настройки, общие для автомойки и шиномонтажа. А в таблицах *carwash.user* (автомойка) и *tireservice.user* (шиномонтажа) будут храниться настройки пользователей, характерные для каждой программы. Например, доступ к какому-нибудь отчету.
{% endhint %}

## Аутентификация - теория <a href="#authentication-theory" id="authentication-theory"></a>

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

По умолчанию все запросы к серверу подписываются гостевой учеткой WS\_GUEST (находится в группе пользователей GuestGroup), логин и пароль этой учетки зашиты в мобильное приложение WS и не могут быть изменены. В одном из будущих уроков рассмотрим, как персонализировать свое приложение для публикации в магазинах приложений и уделим внимание настройке конфига.

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

<div><figure><img src="/files/E1m0STZCBvGKcH6rFLc1" alt="" width="311"><figcaption></figcaption></figure> <figure><img src="/files/3lODYp9LpW0MyAz3VtVi" alt="" width="311"><figcaption></figcaption></figure></div>

После того, как пользователь выбрал учетную запись и ввел пароль, мобильное приложение хеширует пароль безопасным алгоритмом *SHA-512* и отправляет на сервер запрос с данными в зашифрованном виде.

Когда запрос приходит на сервер, он попадает сначала в веб-сервер Kestrel, на котором запущена веб-служба, а затем перенаправляется в серверное приложение.

{% hint style="info" %}
Kestrel представляет кроссплатформенный веб-сервер и по умолчанию включается в проект ASP.NET Core.
{% endhint %}

На сервере механизм аутентификации и авторизации реализован с помощью JWT-токенов. Когда Workflow Engine получает запрос на аутентификацию пользователя, полученные логин и хеш пароля сверяются с теми, которые хранятся в таблице *public.user* в базе данных. Если логин и хеш пароля совпали - генерируется JWT-токен, который возвращается клиентскому приложению вместе с временем жизни этого токена и одноразовым токеном для повторной генерации основного JWT-токена.

{% hint style="info" %}
JWT (или JSON Web Token) представляет собой веб-стандарт, который определяет способ передачи данных о пользователе в формате JSON в зашифрованном виде.
{% endhint %}

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

## Аутентификация - практика <a href="#authentication-practice" id="authentication-practice"></a>

### Список пользователей

Для начала скорректируем таблицу пользователей (template.user), добавив признак доступа к мобильному приложению - не все пользователи WT-программы могут иметь доступ к мобильному приложению:

```sql
ALTER TABLE template."user"
  ADD COLUMN access_to_mobile_app boolean NOT NULL DEFAULT false;
```

Выполним запрос, чтобы дать доступ всем активным пользователям:

```sql
UPDATE template."user"
SET
  access_to_mobile_app = true
FROM
  (SELECT
      user_id
    FROM
      template.user_info
    WHERE
      user_info.person AND NOT user_info.archive) AS user_info
WHERE
  "user".user_id = user_info.user_id;
```

Скорректируем представление template.user\_info, добавив в него новое поле:

```sql
CREATE OR REPLACE VIEW template.user_info AS 
 SELECT tu.user_id,
    pu.user_name,
    pu.user_full_name,
    pu.person,
    NOT pu.enabled AS archive,
    pu.user_id AS public_user_id,
    pu.language_id,
    l.code AS language_code,
    access_to_mobile_app
   FROM template."user" tu
     JOIN "user" pu ON tu.public_user_id = pu.user_id
     JOIN language l ON l.language_id = pu.language_id;
```

Добавим SQL-запрос в серверный xml-файл

{% code title="Template.xml" %}

```xml
<SqlQuery Name="AppUserLoginSelectSqlQuery">
  <Text>
    SELECT
      user_info.user_id AS "UserId",
      user_info.user_name AS "UserName",
      user_info.user_full_name AS "UserFullName"
    FROM
      template.user_info
      JOIN template.user_group USING(user_id)
      JOIN template."group" USING (group_id)
    WHERE
      user_info.person AND NOT user_info.archive AND
      ("group".name != 'GuestGroup' OR "group".name ISNULL) AND
      access_to_mobile_app;
  </Text>
</SqlQuery>
```

{% endcode %}

В существующий BaseViewPermission добавим новый запрос. Это разрешение по умолчанию добавляется во все группы пользователей. Напомним, что в учебном проекте используются [динамические права доступа](https://wfsys.gitbook.io/wt-knowledge-base/platform-wt/access-rights#dynamic-permissions), когда пользователи-администраторы через интерфейс программы назначают права группам пользователей.

### Экран входа <a href="#login-screen" id="login-screen"></a>

Первым делом переименуем существующие файлы: TemplateEmptyStart.xml в TemplateLogin.xml (не забудьте внести правки в таблицу public.mobile\_app), а из TemplateEmptySettings.xml удалим *Empty*. Так же внесем изменения в атрибут `Name` тэга `<Form>` обоих файлов форм:  Для экрана входа укажем TemplateLoginForm, а для экрана настроек - TemplateSettingsForm.

Давайте перейдем в редактор и откроем xml-файл стартового экрана (TemplateLogin.xml).

В файле экрана входа создайте первичное соединение с данными **UserPrimaryGetDataConnection** для получения списка пользователей из sql-запроса AppUserLoginSelectSqlQuery.

Заменим код объекта LogoPictureBox следующим кодом:

{% code title="TemplateLogin.xml" %}

```xml
<MyObject Name="LogoPictureBox" Type="PictureBox" Assembly="BaseControls">
  <Top>
    <DataTypeFormat Type="IntegerDataType" Format="N0">
      <Calculate>
        <Expression>{0} * 0.2</Expression>
        <Items>
          <Item>
            <Object Name="ContentPanel">
              <Property Name="Height" />
            </Object>
          </Item>
        </Items>
      </Calculate>
    </DataTypeFormat>
  </Top>
  <Left>30</Left>
  <Height>200</Height>
  <Width>
    <Formula>
      <Minus DataType="IntegerDataType">
        <Item>
          <Object Name="ContentPanel">
            <Property Name="Width" />
          </Object>
        </Item>
        <Item>60</Item>
      </Minus>
    </Formula>
  </Width>
  <Image>
    <Switch>
      <Case>
        <When>
          <Condition Name="AppThemeDarkEqualCondition" />
        </When>
        <Then>Images\LogoRuDark.png</Then>
      </Case>
      <Case>Images\LogoRuLight.png</Case>
    </Switch>
  </Image>
  <SizeMode>Zoom</SizeMode>
</MyObject>
```

{% endcode %}

Здесь немного изменили размер объекта, а в тэге `<Image>` указали выбор файла логотипа по условию AppThemeDarkEqualCondition.

<div><figure><img src="/files/Q0qMrJz9bsOYkDOXxG0o" alt="" width="311"><figcaption></figcaption></figure> <figure><img src="/files/e5tZyUQRBhI63NkmnJau" alt="" width="311"><figcaption></figcaption></figure></div>

Для выбора пользователя создайте объект с именем UserComboBox типа [ComboBox](https://wfsys.gitbook.io/workflow-mobile-forms-syntax/workflow_mobile_forms/objects/combobox), сделав отступ от LogoPictureBox в 54 единицы и привязав ширину и координату Left к аналогичным значениям LogoPictureBox. В атрибуте `Show` тэга [`<NullValue>`](https://wfsys.gitbook.io/workflow-mobile-forms-syntax/workflow_mobile_forms/objects/combobox#null_value) укажите значение False - нам не нужно добавлять в список пустое значение, которое будет отображаться, если значение в списке не выбрано. Вместо этого будем использовать текст-подсказку из тэга [`<Text>`](https://wfsys.gitbook.io/workflow-mobile-forms-syntax/workflow_mobile_forms/objects/combobox#text), в котором укажите текст "Выберите пользователя". В тэге [`<ValueList>`](https://wfsys.gitbook.io/workflow-mobile-forms-syntax/workflow_mobile_forms/objects/combobox#value_list) укажите **UserPrimaryGetDataConnection**.

В тэге `<ForeColor>` выпадающего списка будем использовать цвет в зависимости от выбранного оформления системы. Для темной темы нужно использовать светлый цвет (WhiteSmoke - уже есть на форме), а для светлой темы - темный цвет (MostlyBlack - так же есть на форме). В тэге `<BackColor>` так же будем отслеживать оформление системы: для темной темы будем использовать MostlyBlack, а для светлой темы - WhiteSmoke.

Добавьте условие UserComboBoxIsNullCondition, которое будет проверять наличие значения в UserComboBox.

Для ввода пароля добавьте объект с именем PasswordTextBox типа [TextBox](https://wfsys.gitbook.io/workflow-mobile-forms-syntax/workflow_mobile_forms/objects/textbox), задав отступ от UserComboBox в 8 единиц, а ширину и координату Left равными соответствующим свойствам списка. В тэге [`<TipText>`](https://wfsys.gitbook.io/workflow-mobile-forms-syntax/workflow_mobile_forms/objects/textbox#tip_text) задается текст-подсказка, который будет отображаться в поле, если значение объекта Null, - укажите "Пароль". А в тэге [`<TipTextColor>`](https://wfsys.gitbook.io/workflow-mobile-forms-syntax/workflow_mobile_forms/objects/textbox#tip_text_color) задается цвет текста-подсказки. Его также будем выбирать в зависимости от оформления системы: для темной темы будем использовать DarkGray, а для светлой темы - LightGray. Для тэгов `<ForeColor>` и `<BackColor>`  задаем значения такие же, как указывали для объекта UserComboBox.

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

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

{% code title="TemplateLogin.xml" %}

```xml
<MyObject Name="ShowPasswordVariable" Type="Variable" Assembly="SimpleControls">
  <Value>True</Value>
</MyObject>
```

{% endcode %}

В тэге [`<Password>`](https://wfsys.gitbook.io/workflow-mobile-forms-syntax/workflow_mobile_forms/objects/textbox#password) текстового поля PasswordTextBox укажем новый объект.

Для переключения режима пароля создадим объект ShowPasswordPictureBox, который на экране будет представлен в виде классической иконки глаза:

{% code title="TemplateLogin.xml" %}

```xml
<MyObject Name="ShowPasswordPictureBox" Type="PictureBox" Assembly="BaseControls">
  <Top>
    <Object Name="PasswordTextBox">
      <Property Name="Top" />
    </Object>
  </Top>
  <Right>
    <Calculate>
      <Expression>{0} - 10</Expression>
      <Items>
        <Item>
          <Object Name="PasswordTextBox">
            <Property Name="Right" />
          </Object>
        </Item>
      </Items>
    </Calculate>
  </Right>
  <Height>
    <Object Name="PasswordTextBox">
      <Property Name="Height" />
    </Object>
  </Height>
  <Width>25</Width>
  <Image>
    <Switch>
      <Case>
        <When>
          <Object Name="ShowPasswordVariable" />
        </When>
        <Then>Images\ic_eye.png</Then>
      </Case>
      <Case>Images\ic_eye_off.png</Case>
    </Switch>
  </Image>
  <SizeMode>Zoom</SizeMode>
</MyObject>
```

{% endcode %}

Обратите внимание на то, как задали значение в тэге `<Right>`: на экране объект ShowPasswordPictureBox будет отрисовываться поверх объекта PasswordTextBox.

Объекты типа PictureBox не поддерживают вызовы команд, поэтому будем отслеживать событие тапа по объекту, используя условие ClickCondition:

{% code title="TemplateLogin.xml" %}

```xml
<Condition Name="ShowPasswordPictureBoxClickCondition" Type="ClickCondition" Assembly="Conditions">
  <Object Name="ShowPasswordPictureBox" />
</Condition>
```

{% endcode %}

Для изменения значения переменной ShowPasswordVariable будем использовать команду типа [ValueSetCommand](https://wfsys.gitbook.io/workflow-mobile-forms-syntax/workflow_mobile_forms/commands/value_set_command):

{% code title="TemplateLogin.xml" %}

```xml
<Command Name="ShowPasswordVariableValueSetCommand" Type="ValueSetCommand" Assembly="Commands">
  <Object Name="ShowPasswordVariable">
    <Not>
      <Object Name="ShowPasswordVariable" />
    </Not>
  </Object>
</Command>
```

{% endcode %}

Команду будем вызывать в Execution по условию ShowPasswordPictureBoxClickCondition:

{% code title="TemplateLogin.xml" %}

```xml
<Execution>
  <ConditionExpression>
    <Condition Name="ShowPasswordPictureBoxClickCondition" />
  </ConditionExpression>
  <Commands>
    <Command Name="ShowPasswordVariableValueSetCommand" />
  </Commands>
</Execution>
```

{% endcode %}

Добавьте на экран кнопку LoginButton, сделав отступ от поля ввода пароля в 36 единиц, а левую координату и ширину кнопки привяжите к соответствующим значениям объекта PasswordTextBox. Для тэга `<BackColor>` будем использовать один цвет ColorPrimary вне зависимости от выбранной темы оформления. А цвет текста на кнопке (тэг `<ForeColor>`) будут зависеть от свойства Enabled самой кнопки. Если кнопка доступна, то цвет текста будет WhiteSmoke, иначе - DarkGray. Для задания радиуса скругления углов кнопки используется тэг [`<BorderCorner>`](https://wfsys.gitbook.io/workflow-mobile-forms-syntax/workflow_mobile_forms/objects/button#border_corner) со значением 5.

Кнопка "Войти" должна быть активна если выбран пользователь и поле пароля содержит символы:

{% code title="TemplateLogin.xml" %}

```xml
<Condition Name="MandatoryFieldsNotAllowedNestedCondition" Type="NestedCondition" Assembly="Conditions">
  <ConditionExpression>
    <Or>
      <Condition Name="UserComboBoxIsNullCondition" />
      <Condition Name="PasswordTextBoxIsNullOrEmptyCondition" />
    </Or>
  </ConditionExpression>
</Condition>
```

{% endcode %}

Запустите приложение и проверьте реализованную логику:

<div><figure><img src="/files/NWkmFGvXX2yfd2cf4bpK" alt="" width="311"><figcaption></figcaption></figure> <figure><img src="/files/bihRHzo22v2qtbMz1oNH" alt="" width="311"><figcaption></figcaption></figure></div>

Отлично! Теперь можем приступить к реализации аутентификации.

### Аутентификация <a href="#authentication" id="authentication"></a>

Для аутентификации в программе используется команда типа [LoginCommand](https://wfsys.gitbook.io/workflow-mobile-forms-syntax/workflow_mobile_forms/commands/login_command). Создадим эту команду:

{% code title="TemplateLogin.xml" %}

```xml
<Command Name="LoginCommand" Type="LoginCommand" Assembly="Commands">
  <UserName>
    <DataConnection SourceDataConnection="UserSecondaryGetDataConnection">
      <Fields>
        <Field Name="UserName" />
      </Fields>
    </DataConnection>
  </UserName>
  <Password>
    <Object Name="PasswordTextBox" />
  </Password>
</Command>
```

{% endcode %}

В команде используется UserSecondaryGetDataConnection типа [SecondaryGetDataConnection](https://wfsys.gitbook.io/workflow-mobile-forms-syntax/workflow_mobile_forms/dataconnections/secondary_dc), который фильтрует список по идентификатору из UserComboBox и возвращает данные выбранного пользователя.

При вызове команда сама вычислит хеш строки пароля и отправит на сервер логин пользователя и хеш пароля в зашифрованном виде.

Создадим условия проверки результата выполнения команды авторизации:

{% code title="TemplateLogin.xml" %}

```xml
<Condition Name="LoginCommandOkEqualCondition" Type="EqualCondition" Assembly="Conditions">
  <AlwaysChange Value="True" />
  <Items>
    <Item>
      <Command Name="LoginCommand" />
    </Item>
    <Item>Ok</Item>
  </Items>
</Condition>

<Condition Name="LoginCommandFailEqualCondition" Type="EqualCondition" Assembly="Conditions">
  <AlwaysChange Value="True" />
  <Items>
    <Item>
      <Command Name="LoginCommand" />
    </Item>
    <Item>Fail</Item>
  </Items>
</Condition>
```

{% endcode %}

Создадим команду вывода сообщения об ошибке аутентификации:

{% code title="TemplateLogin.xml" %}

```xml
<Command Name="LoginFailedMessageBoxCommand" Type="MessageBoxCommand" Assembly="Commands">
  <Caption>Ошибка авторизации.</Caption>
  <Text>Вы ввели неверный пароль. Пожалуйста, попробуйте еще раз.</Text>
  <Buttons Type="Ok" />
</Command>
```

{% endcode %}

Создадим необходимые Execution для обработки результатов выполнения команды авторизации:

{% code title="TemplateLogin.xml" %}

```xml
<Execution>
  <ConditionExpression>
    <Condition Name="LoginCommandOkEqualCondition" />
  </ConditionExpression>
  <Commands>
    <Command Name="MainFormShowCommand" />
  </Commands>
</Execution>

<Execution>
  <ConditionExpression>
    <Condition Name="LoginCommandFailEqualCondition" />
  </ConditionExpression>
  <Commands>
    <Command Name="LoginFailedMessageBoxCommand" />
  </Commands>
</Execution>
```

{% endcode %}

Где команда MainFormShowCommand открывает главный экран приложения:

```xml
<Command Name="MainFormShowCommand" Type="FormShowCommand" Assembly="Commands">
  <Xml Type="Path">TemplateMainForm.xml</Xml>
  <Show Type="None" />
</Command>
```

Создайте пустую форму, которую будем использовать в следующем уроке для построения главного экрана мобильного приложения.

Для создания формы можете использовать паттерн из архива:

{% file src="/files/RNUAs1aDVXLj8EuAXp3l" %}

В папке **\Template\Projects\1. Template\Patterns** создайте две вложенные папки **Desktop** и **Mobile**. В первую перенесите все существующие паттерны (если такие есть), а во вторую распакуйте архив с шаблонами из урока. Как подключить шаблоны к проекту описано в [статье](https://wfsys.gitbook.io/wt-knowledge-base/workflow-xml-editor/patterns). Если в редакторе остался проект для desktop-приложения, скорректируйте путь до его шаблонов с учетом изменения расположения файлов паттернов.

### Использование подключения <a href="#using-connectivity" id="using-connectivity"></a>

Мобильное WT-приложение предполагает подключение к сети во время использования, чтобы иметь возможность обмениваться данными с сервером. Если по каким либо причинам подключение отсутствует, то необходимо об этом уведомить пользователя. Для проверки доступа к Интернету используется универсальное значение [`<Info>`](https://wfsys.gitbook.io/workflow-mobile-forms-syntax/workflow_mobile_forms/values/info) со значение ConnectivityIsConnected в атрибуте `Type`.

{% code title="TemplateLogin.xml" %}

```xml
<Execution>
  <ConditionExpression>
    <Not>
      <Info Type="ConnectivityIsConnected" />
    </Not>
  </ConditionExpression>
  <Commands>
    <Command Name="InternetIssueMessageBoxCommand" />
  </Commands>
</Execution>
```

{% endcode %}

Где команда InternetIssueMessageBoxCommand имеет вид:

{% code title="TemplateLogin.xml" %}

```xml
<Command Name="InternetIssueMessageBoxCommand" Type="MessageBoxCommand" Assembly="Commands">
  <Caption>Отсутствует интернет-соединение</Caption>
  <Text>Попробуйте воспользоваться другой сетью - мобильной или Wi-Fi</Text>
  <Buttons Type="Ok" />
</Command>
```

{% endcode %}

### Обновление данных

Если на форме настроек изменили адрес сервера, то мобильное приложение настроится на новый сервер, но данные на экране входа не обновятся самостоятельно. Для их обновления создадим команду **UserPrimaryDataConnectionRefreshCommand** типа [DataConnectionRefreshCommand](https://wfsys.gitbook.io/workflow-mobile-forms-syntax/workflow_mobile_forms/commands/dc_refresh_command) для обновления UserPrimaryGetDataConnection. Помимо обновления списка пользователей, необходимо сбрасывать выбранное значение в выпадающем списке UserComboBox и введенные символы в поле PasswordTextBox.

{% code title="TemplateLogin.xml" %}

```xml
<Command Name="UserPrimaryDataConnectionRefreshCommand" Type="DataConnectionRefreshCommand" Assembly="Commands">
  <DataConnections>
    <DataConnection Name="UserPrimaryGetDataConnection" />
  </DataConnections>
</Command>

<Command Name="UserComboBoxResetValueSetCommand" Type="ValueSetCommand" Assembly="Commands">
  <Object Name="UserComboBox" />
</Command>

<Command Name="PasswordTextBoxResetValueSetCommand" Type="ValueSetCommand" Assembly="Commands">
  <Object Name="PasswordTextBox" />
</Command>
```

{% endcode %}

Объединим новые команды в одну:

{% code title="TemplateLogin.xml" %}

```xml
<Command Name="RefreshSequentialCommand" Type="SequentialCommand" Assembly="Commands">
  <Commands>
    <Command Name="UserComboBoxResetValueSetCommand" />
    <Command Name="PasswordTextBoxResetValueSetCommand" />
    <Command Name="UserPrimaryDataConnectionRefreshCommand" />
  </Commands>
</Command>
```

{% endcode %}

В файле формы уже описан Execution, который проверяет результат выполнения команды SettingsFormShowCommand и вызывает команду LogoPictureBoxRestartValueSetCommand на изменения изображения в объекте LogoPictureBox.

Замените имя вызываемой команды на RefreshSequentialCommand:

{% code title="TemplateLogin.xml" %}

```xml
<Execution>
  <ConditionExpression>
    <Command Name="SettingsFormShowCommand" Parameter="Updated" />
  </ConditionExpression>
  <Commands>
    <Command Name="RefreshSequentialCommand" />
  </Commands>
</Execution>
```

{% endcode %}

Удалите из файла описание команды LogoPictureBoxRestartValueSetCommand - она нам больше не пригодится.

## Итоги <a href="#results" id="results"></a>

На уроке мы рассмотрели процесс аутентификации пользователя, .

## Ответы <a href="#answer" id="answer"></a>

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

{% file src="/files/fJn1c2RgQQzx8lQB3bHT" %}

{% hint style="warning" %}
Архив *не содержит* xml-файлы форм десктопного приложения.
{% endhint %}
