diff --git a/apps/condo/domains/user/constants/common.js b/apps/condo/domains/user/constants/common.js index a84371fa649..eee2467a633 100644 --- a/apps/condo/domains/user/constants/common.js +++ b/apps/condo/domains/user/constants/common.js @@ -7,7 +7,8 @@ const USER_TYPES = [STAFF, RESIDENT, SERVICE] const APPLE_ID_IDP_TYPE = 'apple_id' const SBER_ID_IDP_TYPE = 'sber_id' const SBBOL_IDP_TYPE = 'sbbol' -const IDP_TYPES = [APPLE_ID_IDP_TYPE, SBER_ID_IDP_TYPE, SBBOL_IDP_TYPE] +const TELEGRAM_IDP_TYPE = 'telegram_auth' +const IDP_TYPES = [TELEGRAM_IDP_TYPE, APPLE_ID_IDP_TYPE, SBER_ID_IDP_TYPE, SBBOL_IDP_TYPE] const MIN_PASSWORD_LENGTH = 8 const MAX_PASSWORD_LENGTH = 128 @@ -59,6 +60,7 @@ module.exports = { APPLE_ID_IDP_TYPE, SBER_ID_IDP_TYPE, SBBOL_IDP_TYPE, + TELEGRAM_IDP_TYPE, IDP_TYPES, SBER_ID_SESSION_KEY, APPLE_ID_SESSION_KEY, diff --git a/apps/condo/domains/user/integration/telegram/BotController.js b/apps/condo/domains/user/integration/telegram/BotController.js index 3673da6f487..c05f76b1565 100644 --- a/apps/condo/domains/user/integration/telegram/BotController.js +++ b/apps/condo/domains/user/integration/telegram/BotController.js @@ -1,107 +1,103 @@ -const connectRedis = require('connect-redis') -const cookieSignature = require('cookie-signature') -const session = require('express-session') const { get } = require('lodash') const TelegramBot = require('node-telegram-bot-api') -const uidSafe = require('uid-safe').sync const conf = require('@open-condo/config') const { getRedisClient } = require('@open-condo/keystone/redis') const { getSchemaCtx } = require('@open-condo/keystone/schema') -const { setSession } = require('@open-condo/keystone/session') const { i18n } = require('@open-condo/locales/loader') -const { syncUser } = require('./sync/syncUser') +const { + TELEGRAM_AUTH_REDIS_START, + TELEGRAM_AUTH_REDIS_PENDING, + TELEGRAM_AUTH_REDIS_TOKEN, +} = require('@condo/domains/user/integration/telegram/constants') +const { syncUser } = require('@condo/domains/user/integration/telegram/sync/syncUser') +const { startAuthedSession } = require('@condo/domains/user/integration/telegram/utils') const TELEGRAM_AUTH_BOT_TOKEN = process.env.TELEGRAM_AUTH_BOT_TOKEN const redisClient = getRedisClient() -const RedisStore = connectRedis(session) -const sessionStore = new RedisStore({ client: redisClient }) - -async function startAuthedSession (userId) { - const id = uidSafe(24) - const payload = { - sessionId: id, - keystoneListKey: 'User', - keystoneItemId: userId, - } - await setSession(sessionStore, payload) - return cookieSignature.sign(id, conf.COOKIE_SECRET) -} - -function escapeMarkdownV2 (text) { - return text.replace(/([_*[\]()~`>#+=|{}.!/:;"'^$%&])/g, '\\$1') -} class TelegramAuthBotController { - #bot = null + #bot constructor () { this.#bot = new TelegramBot(TELEGRAM_AUTH_BOT_TOKEN, { polling: true }) } init () { - this.#bot.onText(/\/start (.+)/, async (message, match) => { - const chatId = get(message, 'chat.id', null) - const locale = get(message, 'from.language_code', conf.DEFAULT_LOCALE) - const startKey = match[1] - - if (!chatId || !startKey) return - - await redisClient.set(`telegram-auth:pending:${chatId}`, startKey, 'EX', 300) - const shareButtonText = i18n('telegram.auth.start.shareButton', { locale }) - const responseMessageText = i18n('telegram.auth.start.response', { locale }) - const options = { - parse_mode: 'MarkdownV2', - reply_markup: { - keyboard: [ - [ - { - text: shareButtonText, - request_contact: true, - }, - ], - ], - one_time_keyboard: true, - resize_keyboard: true, - }, - } + this.#bot.onText(/\/start (.+)/, this.#handleStart.bind(this)) + this.#bot.on('contact', this.#handleContact.bind(this)) + } + + async #handleStart (message, match) { + const chatId = get(message, 'chat.id') + const locale = get(message, 'from.language_code', conf.DEFAULT_LOCALE) + const startKey = match[1] + + if (!chatId || !startKey) return - this.#bot.sendMessage(chatId, escapeMarkdownV2(responseMessageText), options) + await redisClient.set(`${TELEGRAM_AUTH_REDIS_PENDING}${chatId}`, startKey, 'EX', 300) + + this.#sendMessage(chatId, 'telegram.auth.start.response', locale, { + reply_markup: { + keyboard: [[{ text: i18n('telegram.auth.start.shareButton', { locale }), request_contact: true }]], + one_time_keyboard: true, + resize_keyboard: true, + }, }) + } - this.#bot.on('contact', async (message) => { - const chatId = get(message, 'chat.id', null) - const fromId = get(message, 'from.id', null) - const contact = get(message, 'contact', null) - const locale = get(message, 'from.language_code', conf.DEFAULT_LOCALE) + async #handleContact (message) { + const chatId = get(message, 'chat.id') + const fromId = get(message, 'from.id') + const contact = get(message, 'contact') + const locale = get(message, 'from.language_code', conf.DEFAULT_LOCALE) - if (!chatId || !contact) return + if (!chatId || !contact) return - const { phone_number: phoneNumber, first_name: firstName, user_id: userId } = contact + const { phone_number: phoneNumber, first_name: firstName, user_id: userId } = contact - if (fromId !== userId) { - const wrongContactText = i18n('telegram.auth.contact.wrongContact', { locale }) - return this.#bot.sendMessage(chatId, escapeMarkdownV2(wrongContactText), { parse_mode: 'MarkdownV2' }) - } + if (fromId !== userId) { + return this.#sendMessage(chatId, 'telegram.auth.contact.wrongContact', locale) + } + + const startKey = await redisClient.get(`${TELEGRAM_AUTH_REDIS_PENDING}${chatId}`) + await redisClient.del(`${TELEGRAM_AUTH_REDIS_PENDING}${chatId}`) + + if (!startKey) { + return this.#sendMessage(chatId, 'telegram.auth.contact.error', locale) + } + + await this.#authenticateUser(chatId, startKey, { phoneNumber, firstName, userId }, locale) + } - const startKey = await redisClient.get(`telegram-auth:pending:${chatId}`) - if (!startKey) { - const notFoundStartKeyText = i18n('telegram.auth.contact.notFoundStartkey', { locale }) - return this.#bot.sendMessage(chatId, escapeMarkdownV2(notFoundStartKeyText), { parse_mode: 'MarkdownV2' }) + async #authenticateUser (chatId, startKey, userInfo, locale) { + let uniqueKey, userType + try { + const storedValue = await redisClient.get(`${TELEGRAM_AUTH_REDIS_START}${startKey}`) + + if (!storedValue) { + return this.#sendMessage(chatId, 'telegram.auth.contact.error', locale) } + ({ uniqueKey, userType } = JSON.parse(storedValue)) + await redisClient.del(`${TELEGRAM_AUTH_REDIS_START}${startKey}`) + const { keystone: context } = getSchemaCtx('User') + const { id } = await syncUser({ context, userInfo, userType }) + const token = await startAuthedSession(id, context._sessionManager._sessionStore) - const { id } = await syncUser({ context, userInfo: { phoneNumber, firstName }, userType: 'telegram' }) - - const token = await startAuthedSession(id) - const uniqueKey = await redisClient.get(`telegram:start:${startKey}`) + await redisClient.set(`${TELEGRAM_AUTH_REDIS_TOKEN}${uniqueKey}`, token, 'EX', 300) - await redisClient.set(`telegram:auth:${uniqueKey}`, token, 'EX', 300) - const completeText = i18n('telegram.auth.contact.complete', { locale }) - this.#bot.sendMessage(chatId, escapeMarkdownV2(completeText), { parse_mode: 'MarkdownV2' }) - }) + this.#sendMessage(chatId, 'telegram.auth.contact.complete', locale) + } catch (error) { + this.#sendMessage(chatId, 'telegram.auth.contact.error', locale) + } + } + + #sendMessage (chatId, translationKey, locale, options = {}) { + const messageText = i18n(translationKey, { locale }) + this.#bot.sendMessage(chatId, messageText, options) } } diff --git a/apps/condo/domains/user/integration/telegram/constants.js b/apps/condo/domains/user/integration/telegram/constants.js index bc958b2265f..8f12405f9d3 100644 --- a/apps/condo/domains/user/integration/telegram/constants.js +++ b/apps/condo/domains/user/integration/telegram/constants.js @@ -1,9 +1,16 @@ -const STATUS_PENDING = 'pending' -const STATUS_SUCCESS = 'success' -const STATUS_ERROR = 'error' +const TELEGRAM_AUTH_STATUS_PENDING = 'pending' +const TELEGRAM_AUTH_STATUS_SUCCESS = 'success' +const TELEGRAM_AUTH_STATUS_ERROR = 'error' + +const TELEGRAM_AUTH_REDIS_START = 'telegram:start:' +const TELEGRAM_AUTH_REDIS_TOKEN = 'telegram:token:' +const TELEGRAM_AUTH_REDIS_PENDING = 'telegram:pending:' module.exports = { - STATUS_PENDING, - STATUS_SUCCESS, - STATUS_ERROR, + TELEGRAM_AUTH_STATUS_PENDING, + TELEGRAM_AUTH_STATUS_SUCCESS, + TELEGRAM_AUTH_STATUS_ERROR, + TELEGRAM_AUTH_REDIS_START, + TELEGRAM_AUTH_REDIS_TOKEN, + TELEGRAM_AUTH_REDIS_PENDING, } \ No newline at end of file diff --git a/apps/condo/domains/user/integration/telegram/routes.js b/apps/condo/domains/user/integration/telegram/routes.js index 58ec0db7ed5..b4cd2843f75 100644 --- a/apps/condo/domains/user/integration/telegram/routes.js +++ b/apps/condo/domains/user/integration/telegram/routes.js @@ -3,9 +3,15 @@ const { v4: uuid } = require('uuid') const { getLogger } = require('@open-condo/keystone/logging') const { getRedisClient } = require('@open-condo/keystone/redis') -const TelegramAuthBotController = require('./BotController') -const { STATUS_SUCCESS, STATUS_ERROR, STATUS_PENDING } = require('./constants') -const { getUniqueKey } = require('./utils') +const TelegramAuthBotController = require('@condo/domains/user/integration/telegram/BotController') +const { + TELEGRAM_AUTH_STATUS_PENDING, + TELEGRAM_AUTH_STATUS_ERROR, + TELEGRAM_AUTH_STATUS_SUCCESS, + TELEGRAM_AUTH_REDIS_START, + TELEGRAM_AUTH_REDIS_TOKEN, +} = require('@condo/domains/user/integration/telegram/constants') +const { getUniqueKey, getUserType } = require('@condo/domains/user/integration/telegram/utils') const TELEGRAM_AUTH_BOT_URL = process.env.TELEGRAM_AUTH_BOT_URL @@ -20,50 +26,47 @@ class TelegramAuthRoutes { } async startAuth (req, res, next) { - const reqId = req.id - try { + const userType = getUserType(req) const startKey = uuid() const uniqueKey = getUniqueKey() const startLink = `${TELEGRAM_AUTH_BOT_URL}?start=${startKey}` - await redisClient.set(`telegram:start:${startKey}`, uniqueKey, 'EX', 300) - await redisClient.set(`telegram:auth:${uniqueKey}`, STATUS_PENDING, 'EX', 300) + await Promise.all([ + redisClient.set(`${TELEGRAM_AUTH_REDIS_START}${startKey}`, JSON.stringify({ uniqueKey, userType }), 'EX', 300), + redisClient.set(`${TELEGRAM_AUTH_REDIS_TOKEN}${uniqueKey}`, TELEGRAM_AUTH_STATUS_PENDING, 'EX', 300), + ]) - return res.send({ startLink, uniqueKey }) + res.send({ startLink, uniqueKey }) } catch (error) { - logger.error({ msg: 'Telegram auth start error', reqId, error }) - return next(error) + logger.error({ msg: 'Telegram auth start error', reqId: req.id, error }) + next(error) } } async getAuthStatus (req, res, next) { - const reqId = req.id - try { const { uniqueKey } = req.body - if (!uniqueKey) { - return res.status(400).send({ status: 'error', message: 'Missing uniqueKey' }) + return res.status(400).json({ status: 'error', message: 'Missing uniqueKey' }) } - const token = await redisClient.get(`telegram:auth:${uniqueKey}`) - + const token = await redisClient.get(`${TELEGRAM_AUTH_REDIS_TOKEN}${uniqueKey}`) if (token === null) { - return res.status(400).send({ status: STATUS_ERROR, message: 'uniqueKey is expired' }) - } else if (token === STATUS_PENDING) { - return res.send({ status: STATUS_PENDING }) - } else { - await redisClient.del(`telegram:auth:${uniqueKey}`) - return res.send({ token, status: STATUS_SUCCESS }) + return res.status(400).json({ status: TELEGRAM_AUTH_STATUS_ERROR, message: 'uniqueKey is expired' }) } + + if (token === TELEGRAM_AUTH_STATUS_PENDING) { + return res.json({ status: TELEGRAM_AUTH_STATUS_PENDING }) + } + + await redisClient.del(`${TELEGRAM_AUTH_REDIS_TOKEN}${uniqueKey}`) + res.json({ token, status: TELEGRAM_AUTH_STATUS_SUCCESS }) } catch (error) { - logger.error({ msg: 'Telegram auth status error', reqId, error }) - return next(error) + logger.error({ msg: 'Telegram auth status error', reqId: req.id, error }) + next(error) } } } -module.exports = { - TelegramAuthRoutes, -} +module.exports = { TelegramAuthRoutes } diff --git a/apps/condo/domains/user/integration/telegram/sync/syncUser.js b/apps/condo/domains/user/integration/telegram/sync/syncUser.js index f92f4c43659..c33a1e8566b 100644 --- a/apps/condo/domains/user/integration/telegram/sync/syncUser.js +++ b/apps/condo/domains/user/integration/telegram/sync/syncUser.js @@ -15,7 +15,7 @@ const linkUser = async (context, user, userInfo) => { dv, sender, user: { connect: { id: user.id } }, - identityId: userInfo.sub, + identityId: String(userInfo.userId), identityType: TELEGRAM_IDP_TYPE, meta: userInfo, }) @@ -45,7 +45,7 @@ const registerUser = async (context, userInfo, userType) => { const syncUser = async ({ context, userInfo, userType }) => { const userIdentities = await UserExternalIdentity.getAll(context, { identityType: TELEGRAM_IDP_TYPE, - identityId: userInfo.sub, + identityId: String(userInfo.userId), deletedAt: null, }, 'id user { id }') diff --git a/apps/condo/domains/user/integration/telegram/sync/syncUser.spec.js b/apps/condo/domains/user/integration/telegram/sync/syncUser.spec.js index f331901b6f5..2177b763127 100644 --- a/apps/condo/domains/user/integration/telegram/sync/syncUser.spec.js +++ b/apps/condo/domains/user/integration/telegram/sync/syncUser.spec.js @@ -8,7 +8,7 @@ const { faker } = require('@faker-js/faker') const { setFakeClientMode } = require('@open-condo/keystone/test.utils') const { makeClientWithRegisteredOrganization } = require('@condo/domains/organization/utils/testSchema/Organization') -const { SBER_ID_IDP_TYPE, RESIDENT } = require('@condo/domains/user/constants/common') +const { TELEGRAM_IDP_TYPE, RESIDENT } = require('@condo/domains/user/constants/common') const { User, UserExternalIdentity: UserExternalIdentityApi, @@ -18,14 +18,7 @@ const { syncUser } = require('./syncUser') const { keystone } = index -const mockUserInfo = (identityId, phone) => ({ - id: identityId, - issuer: 'testIssuer', - email: faker.internet.email(), - phoneNumber: phone || faker.phone.number('+792########'), -}) - -describe('syncUser from SberId', () => { +describe('syncUser', () => { let context setFakeClientMode(index) @@ -33,15 +26,15 @@ describe('syncUser from SberId', () => { context = await keystone.createContext({ skipAccessControl: true }) }) - it('should create user', async () => { - const identityId = faker.datatype.uuid() - const userInfo = mockUserInfo(identityId) + test('Should create user', async () => { + const userInfo = { + userId: faker.random.numeric(10), + firstName: faker.name.firstName(), + phoneNumber: faker.phone.number('+792########'), + } - // act const { id } = await syncUser({ context, userInfo, userType: RESIDENT }) - // assertions - // assert created user expect(id).toBeDefined() const [ checkUser ] = await User.getAll(context, { id }, @@ -50,73 +43,68 @@ describe('syncUser from SberId', () => { expect(checkUser).toBeDefined() expect(checkUser).toHaveProperty('id') expect(checkUser).toHaveProperty('name') - expect(checkUser).toHaveProperty('email') expect(checkUser).toHaveProperty('phone') expect(checkUser.type).toEqual(RESIDENT) expect(checkUser.isPhoneVerified).toBeTruthy() - expect(checkUser.isEmailVerified).toBeTruthy() - // assert user external identity const [ checkedIdentity ] = await UserExternalIdentityApi.getAll(context, { - identityId, - identityType: SBER_ID_IDP_TYPE, + identityId: userInfo.userId, + identityType: TELEGRAM_IDP_TYPE, }, 'id user { id } identityId identityType') expect(checkedIdentity).toBeDefined() expect(checkedIdentity).toHaveProperty('user') expect(checkedIdentity.user.id).toEqual(checkUser.id) - expect(checkedIdentity.identityId).toEqual(identityId) - expect(checkedIdentity.identityType).toEqual(SBER_ID_IDP_TYPE) + expect(checkedIdentity.identityId).toEqual(userInfo.userId) + expect(checkedIdentity.identityType).toEqual(TELEGRAM_IDP_TYPE) }) - it('should create user external identity', async () => { - const identityId = faker.datatype.uuid() + test('Should create user external identity', async () => { const { userAttrs: { phone: existingUserPhone }, user: existingUser } = await makeClientWithRegisteredOrganization() - const userInfo = mockUserInfo(identityId, existingUserPhone) + const userInfo = { + userId: faker.random.numeric(10), + firstName: faker.name.firstName(), + phoneNumber: existingUserPhone, + } - // act const { id } = await syncUser({ context, userInfo, userType: existingUser.type }) - // assertions - // assert id of user expect(id).toEqual(existingUser.id) - // assert user external identity const [ checkedIdentity ] = await UserExternalIdentityApi.getAll(context, { - identityId, - identityType: SBER_ID_IDP_TYPE, + identityId: userInfo.userId, + identityType: TELEGRAM_IDP_TYPE, }, 'user { id } identityId identityType') expect(checkedIdentity).toBeDefined() expect(checkedIdentity).toHaveProperty('user') expect(checkedIdentity.user.id).toEqual(id) - expect(checkedIdentity.identityId).toEqual(identityId) - expect(checkedIdentity.identityType).toEqual(SBER_ID_IDP_TYPE) + expect(checkedIdentity.identityId).toEqual(userInfo.userId) + expect(checkedIdentity.identityType).toEqual(TELEGRAM_IDP_TYPE) }) - it('should return user id', async () => { - const identityId = faker.datatype.uuid() + test('Should return user id', async () => { const { userAttrs: { phone: existingUserPhone }, user: existingUser } = await makeClientWithRegisteredOrganization() - const userInfo = mockUserInfo(identityId, existingUserPhone) + const userInfo = { + userId: faker.random.numeric(10), + firstName: faker.name.firstName(), + phoneNumber: existingUserPhone, + } await UserExternalIdentityApi.create(context, { dv: 1, sender: { dv: 1, fingerprint: faker.datatype.uuid() }, user: { connect: { id: existingUser.id } }, - identityId: userInfo.id, - identityType: SBER_ID_IDP_TYPE, + identityId: userInfo.userId, + identityType: TELEGRAM_IDP_TYPE, meta: userInfo, }) - // act const { id } = await syncUser({ context, userInfo, userType: existingUser.type }) - // assertions - // assert id of user expect(id).toEqual(existingUser.id) - // assert count of external identities const identities = await UserExternalIdentityApi.getAll(context, { user: { id }, - identityType: SBER_ID_IDP_TYPE, + identityType: TELEGRAM_IDP_TYPE, }) expect(identities).toHaveLength(1) diff --git a/apps/condo/domains/user/integration/telegram/utils/index.js b/apps/condo/domains/user/integration/telegram/utils/index.js index c51c172d5c4..287f2f79aeb 100644 --- a/apps/condo/domains/user/integration/telegram/utils/index.js +++ b/apps/condo/domains/user/integration/telegram/utils/index.js @@ -1,7 +1,11 @@ const { getUniqueKey, + startAuthedSession, + getUserType, } = require('./params') module.exports = { getUniqueKey, + startAuthedSession, + getUserType, } \ No newline at end of file diff --git a/apps/condo/domains/user/integration/telegram/utils/params.js b/apps/condo/domains/user/integration/telegram/utils/params.js index 20a7bf26bb8..7a945bb8474 100644 --- a/apps/condo/domains/user/integration/telegram/utils/params.js +++ b/apps/condo/domains/user/integration/telegram/utils/params.js @@ -1,10 +1,46 @@ const crypto = require('crypto') -const getUniqueKey = () => { +const cookieSignature = require('cookie-signature') +const { get, isNil } = require('lodash') +const uidSafe = require('uid-safe').sync + +const conf = require('@open-condo/config') +const { setSession } = require('@open-condo/keystone/session') + +const { RESIDENT, USER_TYPES } = require('@condo/domains/user/constants/common') + + +function getUserType (req) { + let userType = RESIDENT + const userTypeQP = get(req, 'query.userType') + + if (!isNil(userTypeQP)) { + userType = userTypeQP + } + + if (!USER_TYPES.includes(userType)) throw new Error('userType is incorrect') + + return userType +} + +function getUniqueKey (){ const randomPart = crypto.randomBytes(32).toString('hex') return crypto.createHmac('sha256', 'secret').update(randomPart).digest('hex') } +async function startAuthedSession (userId, sessionStore) { + const id = uidSafe(24) + const payload = { + sessionId: id, + keystoneListKey: 'User', + keystoneItemId: userId, + } + await setSession(sessionStore, payload) + return cookieSignature.sign(id, conf.COOKIE_SECRET) +} + module.exports = { getUniqueKey, + startAuthedSession, + getUserType, } \ No newline at end of file diff --git a/apps/condo/lang/en/en.json b/apps/condo/lang/en/en.json index f19a9912dbf..6868efb0d9a 100644 --- a/apps/condo/lang/en/en.json +++ b/apps/condo/lang/en/en.json @@ -3211,7 +3211,7 @@ "trial.title": "Free period of 14 days", "telegram.auth.start.shareButton": "Send contact", "telegram.auth.start.response": "Please send your contact", - "telegram.auth.contact.wrongContact": "Please send *your* contact", - "telegram.auth.contact.notFoundStartkey": "Error: authorization key not found. Please start the authorization process from the beginning", - "telegram.auth.contact.complete": "✅ Authorization completed successfully! Return to the app" + "telegram.auth.contact.wrongContact": "Please send your contact", + "telegram.auth.contact.complete": "✅ Authorization completed successfully! Return to the app", + "telegram.auth.contact.error": "❌ An error occurred during authentication. Please start the authentication process from the beginning" } diff --git a/apps/condo/lang/es/es.json b/apps/condo/lang/es/es.json index 3cb05ca7c46..3b20b048451 100644 --- a/apps/condo/lang/es/es.json +++ b/apps/condo/lang/es/es.json @@ -3211,7 +3211,7 @@ "trial.title": "Periodo gratuito de 14 días", "telegram.auth.start.shareButton": "Enviar contacto", "telegram.auth.start.response": "Por favor, envía tu contacto", - "telegram.auth.contact.wrongContact": "Por favor, envía *tu* contacto", - "telegram.auth.contact.notFoundStartkey": "Error: clave de autorización no encontrada. Por favor, comienza el proceso de autorización desde el principio", - "telegram.auth.contact.complete": "✅ ¡Autorización completada con éxito! Regresa a la aplicación" + "telegram.auth.contact.wrongContact": "Por favor, envía tu contacto", + "telegram.auth.contact.complete": "✅ ¡Autorización completada con éxito! Regresa a la aplicación", + "telegram.auth.contact.error": "❌ Ocurrió un error durante la autenticación. Por favor, inicia el proceso de autenticación desde el principio" } diff --git a/apps/condo/lang/ru/ru.json b/apps/condo/lang/ru/ru.json index c83f3033c47..2479af5531d 100644 --- a/apps/condo/lang/ru/ru.json +++ b/apps/condo/lang/ru/ru.json @@ -3211,7 +3211,7 @@ "trial.title": "Бесплатный период 14 дней", "telegram.auth.start.shareButton": "Отправить контакт", "telegram.auth.start.response": "Пожалуйста, отправьте свой контакт", - "telegram.auth.contact.wrongContact": "Пожалуйста, отправьте *ваш* контакт", - "telegram.auth.contact.notFoundStartkey": "Ошибка: ключ авторизации не найден. Начните авторизацию с самого начала", - "telegram.auth.contact.complete": "✅ Авторизация успешно завершена! Возвращайтесь в приложение" + "telegram.auth.contact.wrongContact": "Пожалуйста, отправьте ваш контакт", + "telegram.auth.contact.complete": "✅ Авторизация успешно завершена! Возвращайтесь в приложение", + "telegram.auth.contact.error": "❌ Произошла ошибка при авторизации. Начните авторизацию с самого начала" }