2022-05-24 10:08:02 +00:00
|
|
|
import nodecrypto from "crypto"
|
2022-05-24 16:55:21 +00:00
|
|
|
import { ParsedUrlQuery } from "querystring"
|
|
|
|
import * as QueryString from "./querystring"
|
2022-05-24 10:08:02 +00:00
|
|
|
|
|
|
|
/**
|
2022-05-24 16:55:21 +00:00
|
|
|
* Serializable Telegram user data without any technical information.
|
2022-05-24 10:08:02 +00:00
|
|
|
*/
|
2022-05-24 16:55:21 +00:00
|
|
|
export interface UserData {
|
2022-05-24 10:08:02 +00:00
|
|
|
id: number
|
|
|
|
first_name: string
|
2022-05-24 16:55:21 +00:00
|
|
|
last_name?: string
|
|
|
|
username?: string
|
|
|
|
photo_url?: string
|
|
|
|
lang?: string
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Serializable Telegram login data with technical information.
|
|
|
|
*
|
|
|
|
* Can be turned in a {@link LoginResponse} for additional methods.
|
|
|
|
*/
|
|
|
|
export interface LoginData extends UserData {
|
|
|
|
auth_date: number
|
|
|
|
hash: string
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-05-25 15:50:31 +00:00
|
|
|
/**
|
|
|
|
* Get the default Telegram representation of a username.
|
|
|
|
*
|
|
|
|
* @param u
|
|
|
|
* @returns
|
|
|
|
*/
|
|
|
|
export function getTelegramName(u: UserData) {
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-05-24 16:55:21 +00:00
|
|
|
/**
|
|
|
|
* Create a {@link LoginData} object from a {@link ParsedUrlQuery}.
|
|
|
|
*
|
|
|
|
* @param queryObj The source object.
|
|
|
|
* @returns The created object.
|
|
|
|
*/
|
|
|
|
export function queryStringToLoginData(queryObj: ParsedUrlQuery): LoginData {
|
|
|
|
return {
|
|
|
|
id: parseInt(QueryString.getSingle(queryObj, "id")),
|
|
|
|
first_name: QueryString.getSingle(queryObj, "first_name"),
|
|
|
|
last_name: QueryString.getSingle(queryObj, "last_name"),
|
|
|
|
username: QueryString.getSingle(queryObj, "username"),
|
|
|
|
photo_url: QueryString.getSingle(queryObj, "photo_url"),
|
|
|
|
lang: QueryString.getSingle(queryObj, "lang"),
|
|
|
|
auth_date: parseInt(QueryString.getSingle(queryObj, "auth_date")),
|
|
|
|
hash: QueryString.getSingle(queryObj, "hash"),
|
|
|
|
}
|
2022-05-24 10:08:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The response sent by Telegram after a login.
|
|
|
|
*/
|
2022-05-24 16:55:21 +00:00
|
|
|
export class LoginResponse implements LoginData {
|
2022-05-24 10:08:02 +00:00
|
|
|
id: number
|
|
|
|
first_name: string
|
|
|
|
last_name?: string
|
|
|
|
username?: string
|
|
|
|
photo_url?: string
|
|
|
|
auth_date: number
|
|
|
|
hash: string
|
|
|
|
lang?: string
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Construct a new {@link LoginResponse} from a query string object as returned by Next.js.
|
|
|
|
*
|
|
|
|
* @param queryObj The query string object, from `context.query`.
|
|
|
|
*/
|
2022-05-24 16:55:21 +00:00
|
|
|
constructor(ld: LoginData) {
|
|
|
|
this.id = ld.id
|
|
|
|
this.first_name = ld.first_name
|
|
|
|
this.last_name = ld.last_name
|
|
|
|
this.username = ld.username
|
|
|
|
this.photo_url = ld.photo_url
|
|
|
|
this.auth_date = ld.auth_date
|
|
|
|
this.hash = ld.hash
|
|
|
|
this.lang = ld.lang
|
2022-05-24 10:08:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stringify a {@link LoginResponse} in [the format required to verify a Telegram Login](https://core.telegram.org/widgets/login#checking-authorization).
|
|
|
|
*
|
|
|
|
* @param data The data to encode.
|
|
|
|
* @returns The stringified data.
|
|
|
|
*/
|
|
|
|
stringify(): string {
|
|
|
|
const string = Object.entries(this)
|
|
|
|
.filter(([key, _]) => key !== "hash")
|
|
|
|
.filter(([_, value]) => value !== undefined)
|
|
|
|
.map(([key, value]) => `${key}=${value}`)
|
|
|
|
.sort()
|
|
|
|
.join("\n")
|
|
|
|
return string
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if the `auth_date` of the response is recent: it must be in the past, but within `maxSeconds` from the current date.
|
|
|
|
*
|
2022-05-24 16:55:21 +00:00
|
|
|
* @param maxSeconds The maximum number of milliseconds that may pass after authentication for the response to be considered valid; defaults to `864_000_000`, 1 day.
|
2022-05-24 10:08:02 +00:00
|
|
|
* @returns `true` if the response can be considered recent, `false` otherwise.
|
|
|
|
*/
|
2022-05-24 16:55:21 +00:00
|
|
|
isRecent(maxSeconds: number = 864_000_000): boolean {
|
2022-05-24 10:08:02 +00:00
|
|
|
const diff = new Date().getTime() - new Date(this.auth_date * 1000).getTime()
|
|
|
|
return 0 < diff && diff <= maxSeconds
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calculate the "`hash`" of a Telegram Login using [this procedure](https://core.telegram.org/widgets/login#checking-authorization).
|
|
|
|
*
|
2022-05-24 16:55:21 +00:00
|
|
|
* _Only works on Node.js, due to usage of the `crypto` module._
|
|
|
|
*
|
2022-05-24 10:08:02 +00:00
|
|
|
* @param token The bot token used to validate the signature.
|
|
|
|
* @returns The calculated value of the `hash` {@link LoginResponse} parameter.
|
|
|
|
*/
|
2022-05-24 16:55:21 +00:00
|
|
|
hmac(token: string): string {
|
|
|
|
const hash = nodecrypto.createHash("sha256")
|
|
|
|
hash.update(token)
|
|
|
|
const hmac = nodecrypto.createHmac("sha256", hash.digest())
|
2022-05-24 10:08:02 +00:00
|
|
|
hmac.update(this.stringify())
|
|
|
|
return hmac.digest("hex")
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Validate a Telegram Login using [this procedure](https://core.telegram.org/widgets/login#checking-authorization).
|
|
|
|
*
|
2022-05-24 16:55:21 +00:00
|
|
|
* _Only works on Node.js, due to usage of the `crypto` module._
|
|
|
|
*
|
2022-05-24 10:08:02 +00:00
|
|
|
* @param token The bot token used to validate the signature.
|
|
|
|
* @returns `true` if the validation is successful, `false` otherwise.
|
|
|
|
*/
|
|
|
|
isValid(token: string): boolean {
|
|
|
|
const client = this.hmac(token)
|
|
|
|
const server = this.hash
|
|
|
|
return client === server
|
|
|
|
}
|
|
|
|
}
|