import { Controller , ForbiddenException , Get , Header , Inject , Query , Redirect , } from '@nestjs/common' ; import { ConfigType } from '@nestjs/config' ; import { createHash , createHmac , timingSafeEqual } from 'crypto' ; import { BotService } from 'src/bot/bot.service' ; import { TokenService } from 'src/token/token.service' ; import botConfig from '../config/bot.config' ; import httpConfig from '../config/http.config' ; @ Controller ( 'user/auth' ) export class AuthController { private botToken : string ; private uiUrl : string ; private baseUrl : string ; constructor ( @ Inject ( httpConfig . KEY ) httpCfg , @ Inject ( botConfig . KEY ) botCfg , private tokenService , private botService ) { this . botToken = botCfg . token ; this . uiUrl = httpCfg . uiUrl ; this . baseUrl = `${ httpCfg . baseUrl }${ httpCfg . globalPrefix }` ; } @ Get ( 'viaTelegram' ) @ Header ( 'Cache-Control' , 'no-cache, no-store' ) @ Redirect ( '/' , 302 ) public async authCallback ( @ Query ( 'chatId' ) chatId , @ Query ( 'id' ) userId , @ Query ( 'first_name' ) firstName , @ Query ( 'last_name' ) lastName , @ Query ( 'username' ) username , @ Query ( 'photo_url' ) photoUrl , @ Query ( 'auth_date' ) authDate , @ Query ( 'hash' ) hash ) { const telegramLogin = { id : userId , first_name : firstName , last_name : lastName , username , photo_url : photoUrl , auth_date : authDate , } ; await this . verifyHash ( telegramLogin , hash ) ; const numberUserId = Number ( userId ) ; if ( ! ( await this . botService . checkIfUserIsMember ( numberUserId , chatId ) ) ) { throw new ForbiddenException ( 'User is not a member of this chat' ) ; } const token = this . tokenService . sign ( { userId : numberUserId , chatId } ) ; const url = new URL ( `${ this . uiUrl }/index.html` ) ; url . searchParams . append ( 'tas_server' , this . baseUrl ) ; url . searchParams . append ( 'tas_indexName' , chatId ) ; url . searchParams . append ( 'tas_authKey' , token ) ; return { url : url . toString ( ) } ; } private async verifyHash ( loginObject , inputHash ) { const authDate = loginObject . auth_date ; if ( ! authDate ) { throw new ForbiddenException ( 'No auth_date in login' ) ; } if ( Math . abs ( Number ( authDate ) * 1000 - Date . now ( ) ) > 5 * 60 * 1000 ) { throw new ForbiddenException ( 'Invalid auth date' ) ; } const keys = Object . keys ( loginObject ) . sort ( ) ; const dataCheckString = keys . reduce ( ( acc , key ) => loginObject [ key ] ? `${ acc }${ key }=${ loginObject [ key ] }\n` : acc , '' ) . trimEnd ( ) ; const sha = createHash ( 'sha256' ) . update ( this . botToken ) . digest ( ) ; const hmac = createHmac ( 'sha256' , sha ) ; hmac . update ( dataCheckString ) ; if ( ! timingSafeEqual ( hmac . digest ( ) , Buffer . from ( inputHash , 'hex' ) ) ) { throw new ForbiddenException ( 'Invalid hash' ) ; } } }