Skip to content

Commit

Permalink
feat: telegram handler customauth
Browse files Browse the repository at this point in the history
  • Loading branch information
guru-web3 committed Jan 20, 2025
1 parent 8fece1e commit c198cfc
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 1 deletion.
3 changes: 3 additions & 0 deletions src/handlers/HandlerFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import JwtHandler from "./JwtHandler";
import MockLoginHandler from "./MockLoginHandler";
import PasskeysHandler from "./PasskeysHandler";
import PasswordlessHandler from "./PasswordlessHandler";
import TelegramHandler from "./TelegramHandler";
import TwitchHandler from "./TwitchHandler";
import Web3AuthPasswordlessHandler from "./Web3AuthPasswordlessHandler";

Expand All @@ -19,6 +20,8 @@ const createHandler = (params: CreateHandlerParams): ILoginHandler => {
switch (typeOfLogin) {
case LOGIN.GOOGLE:
return new GoogleHandler(params);
case LOGIN.TELEGRAM:
return new TelegramHandler(params);
case LOGIN.FACEBOOK:
return new FacebookHandler(params);
case LOGIN.TWITCH:
Expand Down
146 changes: 146 additions & 0 deletions src/handlers/TelegramHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import deepmerge from "deepmerge";

import { UX_MODE } from "../utils/enums";
import { broadcastChannelOptions, getTimeout } from "../utils/helpers";
import log from "../utils/loglevel";
import PopupHandler from "../utils/PopupHandler";
import AbstractLoginHandler from "./AbstractLoginHandler";
import { CreateHandlerParams, LoginWindowResponse, TorusVerifierResponse } from "./interfaces";

type PopupResponse = {
result: {
auth_date: number;
first_name: string;
hash: string;
id: number;
last_name: string;
photo_url: string;
username: string;
};
origin: string;
event: string;
};

export default class TelegramHandler extends AbstractLoginHandler {
private readonly RESPONSE_TYPE: string = "token";

private readonly SCOPE: string = "profile";

private readonly PROMPT: string = "select_account";

constructor(params: CreateHandlerParams) {
super(params);
this.setFinalUrl();
}

setFinalUrl(): void {
const finalUrl = new URL("https://oauth.telegram.org/auth");
const clonedParams = JSON.parse(JSON.stringify(this.params.jwtParams || {}));
clonedParams.origin = `${this.params.redirect_uri}?state=${this.state}&nonce=${this.nonce}`;

const finalJwtParams = deepmerge(
{
state: this.state,
response_type: this.RESPONSE_TYPE,
bot_id: this.params.clientId,
prompt: this.PROMPT,
redirect_uri: `${this.params.redirect_uri}?state=${this.state}&nonce=${this.nonce}`,
scope: this.SCOPE,
nonce: this.nonce,
},
clonedParams
);
Object.keys(finalJwtParams).forEach((key: string) => {
const localKey = key as keyof typeof finalJwtParams;
if (finalJwtParams[localKey]) finalUrl.searchParams.append(localKey, finalJwtParams[localKey]);
});
this.finalURL = finalUrl;
}

objectToAuthDataMap(tgAuthenticationResulr: string) {
return JSON.parse(atob(tgAuthenticationResulr)) as { first_name: string; last_name: string; photo_url: string; username: string };
}

async getUserInfo(params: LoginWindowResponse): Promise<TorusVerifierResponse> {
const { tgAuthResult } = params;
const userInfo = this.objectToAuthDataMap(tgAuthResult);
const { photo_url: profileImage = "", username = "", first_name = "", last_name = "" } = userInfo;
return {
email: "", // Telegram does not provide email
name: `${first_name} ${last_name}`,
profileImage,
verifier: this.params.verifier,
verifierId: username.toLowerCase(),
typeOfLogin: this.params.typeOfLogin,
};
}

async handleLoginWindow(params: { locationReplaceOnRedirect?: boolean; popupFeatures?: string }): Promise<LoginWindowResponse> {
const verifierWindow = new PopupHandler({ url: this.finalURL, features: params.popupFeatures, timeout: getTimeout(this.params.typeOfLogin) });
if (this.params.uxMode === UX_MODE.REDIRECT) {
verifierWindow.redirect(params.locationReplaceOnRedirect);
} else {
const { BroadcastChannel } = await import("@toruslabs/broadcast-channel");
return new Promise<LoginWindowResponse>((resolve, reject) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let bc: any;
const handleData = async (ev: string) => {
try {
const { event, origin, result, ...rest } = (JSON.parse(ev) as PopupResponse) || {};
// 1. Parse URL
const parsedUrl = new URL(origin);
// 2. Get state param
const stateParam = parsedUrl.searchParams.get("state");

if (event && event === "auth_result") {
if (!this.params.redirectToOpener && bc) await bc.postMessage({ success: true });
// properly resolve the data
resolve({
accessToken: "",
tgAuthResult: btoa(JSON.stringify(result)) || "",
...rest,
// State has to be last here otherwise it will be overwritten
state: atob(stateParam) as unknown as { [key: string]: string },
});
}
} catch (error) {
log.error(error);
reject(error);
}
};

if (!this.params.redirectToOpener) {
bc = new BroadcastChannel<{
error: string;
data: PopupResponse;
}>(`redirect_channel_${this.nonce}`, broadcastChannelOptions);
bc.addEventListener("message", async (ev: string) => {
await handleData(ev);
bc.close();
verifierWindow.close();
});
}
const postMessageEventHandler = async (postMessageEvent: MessageEvent) => {
if (!postMessageEvent.data) return;
const ev = postMessageEvent.data;
window.removeEventListener("message", postMessageEventHandler);
handleData(ev);
verifierWindow.close();
};
window.addEventListener("message", postMessageEventHandler);
try {
verifierWindow.open();
} catch (error) {
log.error(error);
reject(error);
return;
}
verifierWindow.once("close", () => {
if (bc) bc.close();
reject(new Error("user closed popup"));
});
});
}
return null;
}
}
1 change: 1 addition & 0 deletions src/handlers/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export interface TorusSubVerifierInfo {
export interface LoginWindowResponse {
accessToken: string;
idToken?: string;
tgAuthResult?: string;
ref?: string;
extraParams?: string;
extraParamsPassed?: string;
Expand Down
2 changes: 1 addition & 1 deletion src/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ class CustomAuth {
verifier,
userInfo.verifierId,
{ verifier_id: userInfo.verifierId },
loginParams.idToken || loginParams.accessToken,
loginParams.idToken || loginParams.accessToken || loginParams.tgAuthResult,
userInfo.extraVerifierParams
);
return {
Expand Down
1 change: 1 addition & 0 deletions src/utils/enums.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export const LOGIN = {
GOOGLE: "google",
TELEGRAM: "telegram",
FACEBOOK: "facebook",
REDDIT: "reddit",
DISCORD: "discord",
Expand Down

0 comments on commit c198cfc

Please sign in to comment.