Skip to content

Commit

Permalink
feat(condo): DOMA-11012 some refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
vovaaxeapolla committed Feb 11, 2025
1 parent b421482 commit 22deb7c
Show file tree
Hide file tree
Showing 11 changed files with 198 additions and 162 deletions.
4 changes: 3 additions & 1 deletion apps/condo/domains/user/constants/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
142 changes: 69 additions & 73 deletions apps/condo/domains/user/integration/telegram/BotController.js
Original file line number Diff line number Diff line change
@@ -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)
}
}

Expand Down
19 changes: 13 additions & 6 deletions apps/condo/domains/user/integration/telegram/constants.js
Original file line number Diff line number Diff line change
@@ -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,
}
57 changes: 30 additions & 27 deletions apps/condo/domains/user/integration/telegram/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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 }
4 changes: 2 additions & 2 deletions apps/condo/domains/user/integration/telegram/sync/syncUser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
Expand Down Expand Up @@ -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 }')

Expand Down
Loading

0 comments on commit 22deb7c

Please sign in to comment.