# Урок 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 %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://wfsys.gitbook.io/wt-practice-mobile/main/lesson_authentication.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
