Implement logging out from the bridge

But there is no API for logging out of KakaoTalk
This commit is contained in:
Andrew Ferrazzutti 2022-03-06 03:14:59 -05:00
parent db41292be7
commit bb3d7057b3
3 changed files with 100 additions and 35 deletions

View File

@ -24,7 +24,7 @@ from mautrix.util.signed_token import sign_token
from ..kt.client import Client as KakaoTalkClient
from ..kt.client.errors import DeviceVerificationRequired, IncorrectPasscode, IncorrectPassword, CommandException
#from .. import puppet as pu
from .. import puppet as pu
from .typehint import CommandEvent
SECTION_AUTH = HelpSection("Authentication", 10, "")
@ -53,7 +53,7 @@ try_again_or_cancel = "Try again, or say `$cmdprefix+sp cancel` to give up."
help_args="[_email_]",
)
async def login(evt: CommandEvent) -> None:
if evt.sender.client:
if await evt.sender.is_logged_in():
await evt.reply("You're already logged in")
return
@ -156,10 +156,43 @@ async def _handle_login_failure(evt: CommandEvent, e: Exception) -> None:
await evt.reply(f"{message}: {e}")
@command_handler(needs_auth=True, help_section=SECTION_AUTH, help_text="Log out of KakaoTalk")
@command_handler(
needs_auth=True,
help_section=SECTION_AUTH,
help_text="Log out of KakaoTalk (and optionally change your virtual device ID for next login)",
help_args="[--reset-device]",
)
async def logout(evt: CommandEvent) -> None:
#puppet = await pu.Puppet.get_by_ktid(evt.sender.ktid)
await evt.sender.logout()
#if puppet.is_real_user:
# await puppet.switch_mxid(None, None)
await evt.reply("Successfully logged out")
if len(evt.args) >= 1:
if evt.args[0] == "--reset-device":
reset_device = True
else:
await evt.reply("**Usage:** `$cmdprefix+sp logout [--reset-device]`")
return
else:
reset_device = False
puppet = await pu.Puppet.get_by_ktid(evt.sender.ktid)
await evt.sender.logout(reset_device=reset_device)
if puppet.is_real_user:
await puppet.switch_mxid(None, None)
message = "Successfully logged out"
if reset_device:
message += (
", and your next login will use a different device ID.\n\n"
"The old device must be manually de-registered from the KakaoTalk app."
)
await evt.reply(message)
@command_handler(needs_auth=False, help_section=SECTION_AUTH, help_text="Change your virtual device ID for next login")
async def reset_device(evt: CommandEvent) -> None:
if await evt.sender.is_logged_in():
await evt.reply("This command requires you to be logged out.")
else:
await evt.sender.logout(reset_device=True)
await evt.reply(
"Your next login will use a different device ID.\n\n"
"The old device must be manually de-registered from the KakaoTalk app."
)

View File

@ -172,7 +172,7 @@ class User(DBUser, BaseUser):
@property
def has_state(self) -> bool:
return self.uuid and self.ktid and self.access_token and self.refresh_token
return bool(self.uuid and self.ktid and self.access_token and self.refresh_token)
# region Database getters
@ -233,10 +233,13 @@ class User(DBUser, BaseUser):
async def get_uuid(self, force: bool = False) -> str:
if self.uuid is None or force:
self.uuid = await Client.generate_uuid(await self.get_all_uuids())
self.uuid = await self._generate_uuid()
await self.save()
return self.uuid
async def _generate_uuid(self) -> str:
return await Client.generate_uuid(await self.get_all_uuids())
# endregion
@property
@ -369,8 +372,7 @@ class User(DBUser, BaseUser):
finally:
self._is_refreshing = False
async def logout(self, remove_ktid: bool = True) -> bool:
# TODO Remove tokens too?
async def logout(self, *, remove_ktid: bool = True, reset_device: bool = False) -> bool:
ok = True
self.stop_listen()
if self.has_state:
@ -385,12 +387,15 @@ class User(DBUser, BaseUser):
await self.client.stop()
self.client = None
if remove_ktid:
if self.ktid:
#await UserPortal.delete_all(self.ktid)
del self.by_ktid[self.ktid]
self.ktid = None
self.uuid = None
if self.ktid and remove_ktid:
#await UserPortal.delete_all(self.ktid)
del self.by_ktid[self.ktid]
self.ktid = None
if reset_device:
self.uuid = await self._generate_uuid()
self.access_token = None
self.refresh_token = None
await self.save()
return ok
@ -583,7 +588,7 @@ class User(DBUser, BaseUser):
self.log.info("TODO: stop_listen")
async def on_logged_in(self, oauth_credential: OAuthCredential) -> None:
self.log.debug(f"Successfully logged in as {oauth_credential.uuid}")
self.log.debug(f"Successfully logged in as {oauth_credential.userId}")
self.oauth_credential = oauth_credential
self.client = Client(self, log=self.log.getChild("ktclient"))
await self.save()

View File

@ -28,6 +28,7 @@ import {
class UserClient {
static #initializing = false
#talkClient = new TalkClient()
get talkClient() { return this.#talkClient }
@ -37,16 +38,28 @@ class UserClient {
get serviceClient() { return this.#serviceClient }
/**
* @param {string} mxid The ID of the associated Matrix user
* @param {OAuthCredential} credential The tokens that API calls may use
* DO NOT CONSTRUCT DIRECTLY. Callers should use {@link UserClient#create} instead.
* @param {string} mxid
* @param {OAuthCredential} credential
*/
constructor(mxid, credential) {
if (!UserClient.#initializing) {
throw new Error("Private constructor")
}
UserClient.#initializing = false
this.mxid = mxid
this.credential = credential
}
/**
* @param {string} mxid The ID of the associated Matrix user
* @param {OAuthCredential} credential The tokens that API calls may use
*/
static async create(mxid, credential) {
this.#initializing = true
const userClient = new UserClient(mxid, credential)
userClient.#serviceClient = await ServiceApiClient.create(this.credential)
return userClient
}
@ -194,15 +207,26 @@ export default class PeerClient {
/**
* Checked lookup of a UserClient for a given mxid.
* @param {string} mxid
* @returns {UserClient}
*/
#getUser(mxid) {
/** @type {UserClient} */
const userClient = this.userClients.get(mxid)
if (userClient === undefined) {
throw new Error(`Could not find user ${mxid}`)
}
return userClient
}
/**
* Get the service client for the specified user ID, or create
* and return a new service client if no user ID is provided.
* @param {string} mxid
* @param {OAuthCredential} oauth_credential
*/
async #getServiceClient(mxid, oauth_credential) {
return this.userClients.get(mxid)?.serviceClient ||
await ServiceApiClient.create(oauth_credential)
}
/**
* @param {Object} req
@ -270,9 +294,7 @@ export default class PeerClient {
* @param {OAuthCredential} req.oauth_credential
*/
getOwnProfile = async (req) => {
const serviceClient =
this.userClients.get(req.mxid)?.serviceClient ||
await ServiceApiClient.create(req.oauth_credential)
const serviceClient = await this.#getServiceClient(req.mxid, req.oauth_credential)
return await serviceClient.requestMyProfile()
}
@ -283,10 +305,8 @@ export default class PeerClient {
* @param {Long} req.user_id
*/
getProfile = async (req) => {
const serviceClient =
this.userClients.get(mxid)?.serviceClient ||
await ServiceApiClient.create(req.oauth_credential)
return await serviceClient.requestProfile(user_id)
const serviceClient = await this.#getServiceClient(req.mxid, req.oauth_credential)
return await serviceClient.requestProfile(req.user_id)
}
/**
@ -310,6 +330,16 @@ export default class PeerClient {
})
}
/**
* @param {Object} req
* @param {string} req.mxid
*/
handleStop = async (req) => {
this.#getUser(req.mxid).close()
this.userClients.delete(req.mxid)
return this.#voidCommandResult
}
#makeCommandResult(result) {
return {
success: true,
@ -318,12 +348,9 @@ export default class PeerClient {
}
}
/**
* @param {Object} req
* @param {string} req.mxid
*/
handleStop = async (req) => {
this.#getUser(req.mxid).close()
#voidCommandResult = {
success: true,
status: 0,
}
handleUnknownCommand = () => {