Skip to content

Commit c198cfc

Browse files
author
Guru
committed
feat: telegram handler customauth
1 parent 8fece1e commit c198cfc

File tree

5 files changed

+152
-1
lines changed

5 files changed

+152
-1
lines changed

src/handlers/HandlerFactory.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import JwtHandler from "./JwtHandler";
77
import MockLoginHandler from "./MockLoginHandler";
88
import PasskeysHandler from "./PasskeysHandler";
99
import PasswordlessHandler from "./PasswordlessHandler";
10+
import TelegramHandler from "./TelegramHandler";
1011
import TwitchHandler from "./TwitchHandler";
1112
import Web3AuthPasswordlessHandler from "./Web3AuthPasswordlessHandler";
1213

@@ -19,6 +20,8 @@ const createHandler = (params: CreateHandlerParams): ILoginHandler => {
1920
switch (typeOfLogin) {
2021
case LOGIN.GOOGLE:
2122
return new GoogleHandler(params);
23+
case LOGIN.TELEGRAM:
24+
return new TelegramHandler(params);
2225
case LOGIN.FACEBOOK:
2326
return new FacebookHandler(params);
2427
case LOGIN.TWITCH:

src/handlers/TelegramHandler.ts

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import deepmerge from "deepmerge";
2+
3+
import { UX_MODE } from "../utils/enums";
4+
import { broadcastChannelOptions, getTimeout } from "../utils/helpers";
5+
import log from "../utils/loglevel";
6+
import PopupHandler from "../utils/PopupHandler";
7+
import AbstractLoginHandler from "./AbstractLoginHandler";
8+
import { CreateHandlerParams, LoginWindowResponse, TorusVerifierResponse } from "./interfaces";
9+
10+
type PopupResponse = {
11+
result: {
12+
auth_date: number;
13+
first_name: string;
14+
hash: string;
15+
id: number;
16+
last_name: string;
17+
photo_url: string;
18+
username: string;
19+
};
20+
origin: string;
21+
event: string;
22+
};
23+
24+
export default class TelegramHandler extends AbstractLoginHandler {
25+
private readonly RESPONSE_TYPE: string = "token";
26+
27+
private readonly SCOPE: string = "profile";
28+
29+
private readonly PROMPT: string = "select_account";
30+
31+
constructor(params: CreateHandlerParams) {
32+
super(params);
33+
this.setFinalUrl();
34+
}
35+
36+
setFinalUrl(): void {
37+
const finalUrl = new URL("https://oauth.telegram.org/auth");
38+
const clonedParams = JSON.parse(JSON.stringify(this.params.jwtParams || {}));
39+
clonedParams.origin = `${this.params.redirect_uri}?state=${this.state}&nonce=${this.nonce}`;
40+
41+
const finalJwtParams = deepmerge(
42+
{
43+
state: this.state,
44+
response_type: this.RESPONSE_TYPE,
45+
bot_id: this.params.clientId,
46+
prompt: this.PROMPT,
47+
redirect_uri: `${this.params.redirect_uri}?state=${this.state}&nonce=${this.nonce}`,
48+
scope: this.SCOPE,
49+
nonce: this.nonce,
50+
},
51+
clonedParams
52+
);
53+
Object.keys(finalJwtParams).forEach((key: string) => {
54+
const localKey = key as keyof typeof finalJwtParams;
55+
if (finalJwtParams[localKey]) finalUrl.searchParams.append(localKey, finalJwtParams[localKey]);
56+
});
57+
this.finalURL = finalUrl;
58+
}
59+
60+
objectToAuthDataMap(tgAuthenticationResulr: string) {
61+
return JSON.parse(atob(tgAuthenticationResulr)) as { first_name: string; last_name: string; photo_url: string; username: string };
62+
}
63+
64+
async getUserInfo(params: LoginWindowResponse): Promise<TorusVerifierResponse> {
65+
const { tgAuthResult } = params;
66+
const userInfo = this.objectToAuthDataMap(tgAuthResult);
67+
const { photo_url: profileImage = "", username = "", first_name = "", last_name = "" } = userInfo;
68+
return {
69+
email: "", // Telegram does not provide email
70+
name: `${first_name} ${last_name}`,
71+
profileImage,
72+
verifier: this.params.verifier,
73+
verifierId: username.toLowerCase(),
74+
typeOfLogin: this.params.typeOfLogin,
75+
};
76+
}
77+
78+
async handleLoginWindow(params: { locationReplaceOnRedirect?: boolean; popupFeatures?: string }): Promise<LoginWindowResponse> {
79+
const verifierWindow = new PopupHandler({ url: this.finalURL, features: params.popupFeatures, timeout: getTimeout(this.params.typeOfLogin) });
80+
if (this.params.uxMode === UX_MODE.REDIRECT) {
81+
verifierWindow.redirect(params.locationReplaceOnRedirect);
82+
} else {
83+
const { BroadcastChannel } = await import("@toruslabs/broadcast-channel");
84+
return new Promise<LoginWindowResponse>((resolve, reject) => {
85+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
86+
let bc: any;
87+
const handleData = async (ev: string) => {
88+
try {
89+
const { event, origin, result, ...rest } = (JSON.parse(ev) as PopupResponse) || {};
90+
// 1. Parse URL
91+
const parsedUrl = new URL(origin);
92+
// 2. Get state param
93+
const stateParam = parsedUrl.searchParams.get("state");
94+
95+
if (event && event === "auth_result") {
96+
if (!this.params.redirectToOpener && bc) await bc.postMessage({ success: true });
97+
// properly resolve the data
98+
resolve({
99+
accessToken: "",
100+
tgAuthResult: btoa(JSON.stringify(result)) || "",
101+
...rest,
102+
// State has to be last here otherwise it will be overwritten
103+
state: atob(stateParam) as unknown as { [key: string]: string },
104+
});
105+
}
106+
} catch (error) {
107+
log.error(error);
108+
reject(error);
109+
}
110+
};
111+
112+
if (!this.params.redirectToOpener) {
113+
bc = new BroadcastChannel<{
114+
error: string;
115+
data: PopupResponse;
116+
}>(`redirect_channel_${this.nonce}`, broadcastChannelOptions);
117+
bc.addEventListener("message", async (ev: string) => {
118+
await handleData(ev);
119+
bc.close();
120+
verifierWindow.close();
121+
});
122+
}
123+
const postMessageEventHandler = async (postMessageEvent: MessageEvent) => {
124+
if (!postMessageEvent.data) return;
125+
const ev = postMessageEvent.data;
126+
window.removeEventListener("message", postMessageEventHandler);
127+
handleData(ev);
128+
verifierWindow.close();
129+
};
130+
window.addEventListener("message", postMessageEventHandler);
131+
try {
132+
verifierWindow.open();
133+
} catch (error) {
134+
log.error(error);
135+
reject(error);
136+
return;
137+
}
138+
verifierWindow.once("close", () => {
139+
if (bc) bc.close();
140+
reject(new Error("user closed popup"));
141+
});
142+
});
143+
}
144+
return null;
145+
}
146+
}

src/handlers/interfaces.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export interface TorusSubVerifierInfo {
5858
export interface LoginWindowResponse {
5959
accessToken: string;
6060
idToken?: string;
61+
tgAuthResult?: string;
6162
ref?: string;
6263
extraParams?: string;
6364
extraParamsPassed?: string;

src/login.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ class CustomAuth {
187187
verifier,
188188
userInfo.verifierId,
189189
{ verifier_id: userInfo.verifierId },
190-
loginParams.idToken || loginParams.accessToken,
190+
loginParams.idToken || loginParams.accessToken || loginParams.tgAuthResult,
191191
userInfo.extraVerifierParams
192192
);
193193
return {

src/utils/enums.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export const LOGIN = {
22
GOOGLE: "google",
3+
TELEGRAM: "telegram",
34
FACEBOOK: "facebook",
45
REDDIT: "reddit",
56
DISCORD: "discord",

0 commit comments

Comments
 (0)