diff --git a/matrix_appservice_kakaotalk/__main__.py b/matrix_appservice_kakaotalk/__main__.py index d708bc9..d3e7e74 100644 --- a/matrix_appservice_kakaotalk/__main__.py +++ b/matrix_appservice_kakaotalk/__main__.py @@ -28,7 +28,7 @@ from .puppet import Puppet from .user import User from .kt.client import Client as KakaoTalkClient from .version import linkified_version, version -from .web import PublicBridgeWebsite +#from .web import PublicBridgeWebsite from . import commands as _ @@ -46,7 +46,7 @@ class KakaoTalkBridge(Bridge): config: Config matrix: MatrixHandler - public_website: PublicBridgeWebsite | None + #public_website: PublicBridgeWebsite | None def prepare_config(self)->None: super().prepare_config() @@ -55,9 +55,9 @@ class KakaoTalkBridge(Bridge): super().prepare_db() init_db(self.db) + """ TODO Implement web login def prepare_bridge(self) -> None: super().prepare_bridge() - """ TODO Implement web login if self.config["appservice.public.enabled"]: secret = self.config["appservice.public.shared_secret"] self.public_website = PublicBridgeWebsite(loop=self.loop, shared_secret=secret) @@ -66,8 +66,7 @@ class KakaoTalkBridge(Bridge): ) else: self.public_website = None - """ - self.public_website = None + """ def prepare_stop(self) -> None: self.log.debug("Stopping RPC connection") @@ -87,8 +86,10 @@ class KakaoTalkBridge(Bridge): if self.config["bridge.resend_bridge_info"]: self.add_startup_actions(self.resend_bridge_info()) await super().start() + """ TODO Implement web login if self.public_website: self.public_website.ready_wait.set_result(None) + """ async def resend_bridge_info(self) -> None: self.config["bridge.resend_bridge_info"] = False diff --git a/matrix_appservice_kakaotalk/commands/auth.py b/matrix_appservice_kakaotalk/commands/auth.py index dc38edf..1909859 100644 --- a/matrix_appservice_kakaotalk/commands/auth.py +++ b/matrix_appservice_kakaotalk/commands/auth.py @@ -13,13 +13,13 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -import time +#import time -from yarl import URL +#from yarl import URL from mautrix.bridge.commands import HelpSection, command_handler from mautrix.errors import MForbidden -from mautrix.util.signed_token import sign_token +#from mautrix.util.signed_token import sign_token from ..kt.client import Client as KakaoTalkClient from ..kt.client.errors import DeviceVerificationRequired, IncorrectPasscode, IncorrectPassword, CommandException @@ -29,6 +29,7 @@ from .typehint import CommandEvent SECTION_AUTH = HelpSection("Authentication", 10, "") +""" TODO Implement web login web_unsupported = ( "This instance of the KakaoTalk bridge does not support the web-based login interface" ) @@ -40,6 +41,7 @@ forced_web_login = ( "This instance of the KakaoTalk bridge does not allow in-Matrix login. " "Please use [the web-based login interface]({url})." ) +""" send_password = "Please send your password here to log in" missing_email = "Please use `$cmdprefix+sp login ` to log in here" try_again_or_cancel = "Try again, or say `$cmdprefix+sp cancel` to give up." @@ -67,6 +69,7 @@ async def login(evt: CommandEvent) -> None: "email": evt.args[0], } + """ TODO Implement web login if evt.bridge.public_website: external_url = URL(evt.config["appservice.public.external"]) token = sign_token( @@ -87,6 +90,11 @@ async def login(evt: CommandEvent) -> None: await evt.reply(f"{missing_email}. {web_unsupported}.") else: await evt.reply(f"{send_password}. {web_unsupported}.") + """ + if not email: + await evt.reply(f"{missing_email}.") + else: + await evt.reply(f"{send_password}.") async def enter_password(evt: CommandEvent) -> None: diff --git a/matrix_appservice_kakaotalk/config.py b/matrix_appservice_kakaotalk/config.py index fe2c6c2..7493f1c 100644 --- a/matrix_appservice_kakaotalk/config.py +++ b/matrix_appservice_kakaotalk/config.py @@ -35,11 +35,13 @@ class Config(BaseBridgeConfig): return [ *super().forbidden_defaults, ForbiddenDefault("appservice.database", "postgres://username:password@hostname/db"), - ForbiddenDefault( - "appservice.public.external", - "https://example.com/public", - condition="appservice.public.enabled", - ), + # TODO + #ForbiddenDefault( + # "appservice.public.external", + # "https://example.com/public", + # condition="appservice.public.enabled", + #), + # ForbiddenDefault("bridge.permissions", ForbiddenKey("example.com")), ] @@ -50,6 +52,7 @@ class Config(BaseBridgeConfig): copy("homeserver.asmux") + """ TODO copy("appservice.public.enabled") copy("appservice.public.prefix") copy("appservice.public.external") @@ -59,6 +62,7 @@ class Config(BaseBridgeConfig): copy("appservice.public.shared_secret") copy("appservice.public.allow_matrix_login") copy("appservice.public.segment_key") + """ copy("metrics.enabled") copy("metrics.listen_port") diff --git a/matrix_appservice_kakaotalk/example-config.yaml b/matrix_appservice_kakaotalk/example-config.yaml index 10f4dfd..f0332d8 100644 --- a/matrix_appservice_kakaotalk/example-config.yaml +++ b/matrix_appservice_kakaotalk/example-config.yaml @@ -49,26 +49,6 @@ appservice: min_size: 5 max_size: 10 - # Public part of web server for out-of-Matrix interaction with the bridge. - # TODO Implement web login - public: - # Whether or not the public-facing endpoints should be enabled. - enabled: false - # The prefix to use in the public-facing endpoints. - prefix: /public - # The base URL where the public-facing endpoints are available. The prefix is not added - # implicitly. - external: https://example.com/public - # Shared secret for integration managers such as mautrix-manager. - # If set to "generate", a random string will be generated on the next startup. - # If null, integration manager access to the API will not be possible. - shared_secret: generate - # Allow logging in within Matrix. If false, users can only log in using the web interface. - allow_matrix_login: true - # Segment API key to enable analytics tracking for web server endpoints. Set to null to disable. - # Currently the only events are login start, success and fail. - segment_key: null - # The unique ID of this appservice. id: kakaotalk # Username of the appservice bot. diff --git a/matrix_appservice_kakaotalk/web/__init__.py b/matrix_appservice_kakaotalk/web/__init__.py deleted file mode 100644 index 57c6f1f..0000000 --- a/matrix_appservice_kakaotalk/web/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .public import PublicBridgeWebsite diff --git a/matrix_appservice_kakaotalk/web/public.py b/matrix_appservice_kakaotalk/web/public.py deleted file mode 100644 index a910dff..0000000 --- a/matrix_appservice_kakaotalk/web/public.py +++ /dev/null @@ -1,380 +0,0 @@ -# matrix-appservice-kakaotalk - A Matrix-KakaoTalk puppeting bridge. -# Copyright (C) 2022 Tulir Asokan, Andrew Ferrazzutti -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -from __future__ import annotations - -import asyncio -import json -import logging -import random -import string -import time - -from aiohttp import web -import pkg_resources - -from mautrix.types import UserID -from mautrix.util.signed_token import verify_token - -from .. import puppet as pu, user as u - - -class InvalidTokenError(Exception): - pass - - -class PublicBridgeWebsite: - log: logging.Logger = logging.getLogger("mau.web.public") - app: web.Application - secret_key: str - shared_secret: str - ready_wait: asyncio.Future | None - - def __init__(self, shared_secret: str, loop: asyncio.AbstractEventLoop) -> None: - self.app = web.Application() - self.ready_wait = loop.create_future() - self.secret_key = "".join(random.choices(string.ascii_lowercase + string.digits, k=64)) - self.shared_secret = shared_secret - for path in ( - "whoami", - "login", - "login/prepare", - "login/2fa", - "login/check_approved", - "login/approved", - "logout", - "disconnect", - "reconnect", - "refresh", - ): - self.app.router.add_options(f"/api/{path}", self.login_options) - self.app.router.add_get("/api/whoami", self.status) - self.app.router.add_post("/api/login/prepare", self.login_prepare) - self.app.router.add_post("/api/login", self.login) - self.app.router.add_post("/api/login/2fa", self.login_2fa) - self.app.router.add_get("/api/login/check_approved", self.login_check_approved) - self.app.router.add_post("/api/login/approved", self.login_approved) - self.app.router.add_post("/api/logout", self.logout) - self.app.router.add_post("/api/disconnect", self.disconnect) - self.app.router.add_post("/api/reconnect", self.reconnect) - self.app.router.add_post("/api/refresh", self.refresh) - self.app.router.add_static( - "/", pkg_resources.resource_filename("matrix_appservice_kakaotalk.web", "static/") - ) - - def verify_token(self, token: str) -> UserID: - token = verify_token(self.secret_key, token) - if token: - if token.get("expiry", 0) < int(time.time()): - raise InvalidTokenError("Access token has expired") - return UserID(token.get("mxid")) - raise InvalidTokenError("Access token is invalid") - - @property - def _acao_headers(self) -> dict[str, str]: - return { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Headers": "Authorization, Content-Type", - "Access-Control-Allow-Methods": "GET, POST, OPTIONS", - } - - @property - def _headers(self) -> dict[str, str]: - return { - **self._acao_headers, - "Content-Type": "application/json", - } - - async def login_options(self, _: web.Request) -> web.Response: - return web.Response(status=200, headers=self._headers) - - async def check_token(self, request: web.Request) -> u.User | None: - if self.ready_wait: - await self.ready_wait - self.ready_wait = None - try: - token = request.headers["Authorization"] - token = token[len("Bearer ") :] - except KeyError: - raise web.HTTPBadRequest( - text='{"error": "Missing Authorization header"}', headers=self._headers - ) - except IndexError: - raise web.HTTPBadRequest( - text='{"error": "Malformed Authorization header"}', - headers=self._headers, - ) - if self.shared_secret and token == self.shared_secret: - try: - user_id = request.query["user_id"] - except KeyError: - raise web.HTTPBadRequest( - text='{"error": "Missing user_id query param"}', - headers=self._headers, - ) - else: - try: - user_id = self.verify_token(token) - except InvalidTokenError as e: - raise web.HTTPForbidden( - text=json.dumps( - {"error": f"{e}, please request a new one from the bridge bot"} - ), - headers=self._headers, - ) - - user = await u.User.get_by_mxid(user_id) - return user - - async def status(self, request: web.Request) -> web.Response: - user = await self.check_token(request) - data = { - "permissions": user.permission_level, - "mxid": user.mxid, - "kakaotalk": None, - } - if user.client: - try: - info = await user.get_own_info() - except Exception: - # TODO do something? - self.log.warning( - "Exception while getting self from status endpoint", exc_info=True - ) - else: - data["kakaotalk"] = info.serialize() - data["kakaotalk"]["connected"] = user.is_connected - data["kakaotalk"][ - "device_displayname" - ] = f"{user.state.device.manufacturer} {user.state.device.name}" - return web.json_response(data, headers=self._acao_headers) - - async def login_prepare(self, request: web.Request) -> web.Response: - self.log.info("TODO") - """ - user = await self.check_token(request) - state = AndroidState() - state.generate(user.mxid) - api = AndroidAPI(state, log=user.log.getChild("login-api")) - user.command_status = { - "action": "Login", - "state": state, - "api": api, - } - try: - await api.mobile_config_sessionless() - except Exception as e: - self.log.exception( - f"Failed to get mobile_config_sessionless to prepare login for {user.mxid}" - ) - return web.json_response({"error": str(e)}, headers=self._acao_headers, status=500) - return web.json_response( - { - "status": "login", - "password_encryption_key_id": state.session.password_encryption_key_id, - "password_encryption_pubkey": state.session.password_encryption_pubkey, - }, - headers=self._acao_headers, - ) - """ - - async def login(self, request: web.Request) -> web.Response: - self.log.info("TODO") - """ - user = await self.check_token(request) - - try: - data = await request.json() - except json.JSONDecodeError: - raise web.HTTPBadRequest(text='{"error": "Malformed JSON"}', headers=self._headers) - - try: - email = data["email"] - except KeyError: - raise web.HTTPBadRequest(text='{"error": "Missing email"}', headers=self._headers) - try: - password = data["password"] - encrypted_password = None - except KeyError: - try: - encrypted_password = data["encrypted_password"] - password = None - except KeyError: - raise web.HTTPBadRequest( - text='{"error": "Missing password"}', headers=self._headers - ) - - if encrypted_password: - if not user.command_status or user.command_status["action"] != "Login": - raise web.HTTPBadRequest( - text='{"error": "No login in progress"}', headers=self._headers - ) - state: AndroidState = user.command_status["state"] - api: AndroidAPI = user.command_status["api"] - else: - state = AndroidState() - state.generate(user.mxid) - api = AndroidAPI(state, log=user.log.getChild("login-api")) - await api.mobile_config_sessionless() - - try: - self.log.debug(f"Logging in as {email} for {user.mxid}") - resp = await api.login(email, password=password, encrypted_password=encrypted_password) - self.log.debug(f"Got successful login response with UID {resp.uid} for {user.mxid}") - await user.on_logged_in(state) - return web.json_response({"status": "logged-in"}, headers=self._acao_headers) - except TwoFactorRequired as e: - self.log.debug( - f"Got 2-factor auth required login error with UID {e.uid} for {user.mxid}" - ) - user.command_status = { - "action": "Login", - "state": state, - "api": api, - } - return web.json_response( - { - "status": "two-factor", - "error": e.data, - }, - headers=self._acao_headers, - ) - except OAuthException as e: - self.log.debug(f"Got OAuthException {e} for {user.mxid}") - return web.json_response({"error": str(e)}, headers=self._acao_headers, status=401) - """ - - async def login_2fa(self, request: web.Request) -> web.Response: - self.log.info("TODO") - """ - user = await self.check_token(request) - - if not user.command_status or user.command_status["action"] != "Login": - raise web.HTTPBadRequest( - text='{"error": "No login in progress"}', headers=self._headers - ) - - try: - data = await request.json() - except json.JSONDecodeError: - raise web.HTTPBadRequest(text='{"error": "Malformed JSON"}', headers=self._headers) - - try: - email = data["email"] - code = data["code"] - except KeyError as e: - raise web.HTTPBadRequest( - text=json.dumps({"error": f"Missing key {e}"}), headers=self._headers - ) - - state: AndroidState = user.command_status["state"] - api: AndroidAPI = user.command_status["api"] - try: - self.log.debug(f"Sending 2-factor auth code for {user.mxid}") - resp = await api.login_2fa(email, code) - self.log.debug( - f"Got successful login response with UID {resp.uid} for {user.mxid}" - " after 2fa login" - ) - await user.on_logged_in(state) - return web.json_response({"status": "logged-in"}, headers=self._acao_headers) - except IncorrectPassword: - self.log.debug(f"Got incorrect 2fa code error for {user.mxid}") - return web.json_response( - { - "error": "Incorrect two-factor authentication code", - "status": "incorrect-code", - }, - headers=self._acao_headers, - status=401, - ) - except OAuthException as e: - self.log.debug(f"Got OAuthException {e} for {user.mxid} in 2fa stage") - return web.json_response({"error": str(e)}, headers=self._acao_headers, status=401) - """ - - async def login_approved(self, request: web.Request) -> web.Response: - self.log.info("TODO") - """ - user = await self.check_token(request) - - if not user.command_status or user.command_status["action"] != "Login": - raise web.HTTPBadRequest( - text='{"error": "No login in progress"}', headers=self._headers - ) - - state: AndroidState = user.command_status["state"] - api: AndroidAPI = user.command_status["api"] - try: - self.log.debug(f"Trying to log in after approval for {user.mxid}") - resp = await api.login_approved() - self.log.debug( - f"Got successful login response with UID {resp.uid} for {user.mxid}" - " after approval login" - ) - await user.on_logged_in(state) - return web.json_response({"status": "logged-in"}, headers=self._acao_headers) - except OAuthException as e: - self.log.debug(f"Got OAuthException {e} for {user.mxid} in checkpoint login stage") - return web.json_response({"error": str(e)}, headers=self._acao_headers, status=401) - """ - - async def login_check_approved(self, request: web.Request) -> web.Response: - self.log.info("TODO") - """ - user = await self.check_token(request) - - if not user.command_status or user.command_status["action"] != "Login": - raise web.HTTPBadRequest( - text='{"error": "No login in progress"}', headers=self._headers - ) - - api: AndroidAPI = user.command_status["api"] - approved = await api.check_approved_machine() - return web.json_response({"approved": approved}, headers=self._acao_headers) - """ - - async def logout(self, request: web.Request) -> web.Response: - user = await self.check_token(request) - - puppet = await pu.Puppet.get_by_ktid(user.ktid) - await user.logout() - if puppet.is_real_user: - await puppet.switch_mxid(None, None) - return web.json_response({}, headers=self._acao_headers) - - async def disconnect(self, request: web.Request) -> web.Response: - user = await self.check_token(request) - if not user.is_connected: - raise web.HTTPBadRequest( - text='{"error": "User is not connected"}', headers=self._headers - ) - user.mqtt.disconnect() - await user.listen_task - return web.json_response({}, headers=self._acao_headers) - - async def reconnect(self, request: web.Request) -> web.Response: - user = await self.check_token(request) - if user.is_connected: - raise web.HTTPConflict( - text='{"error": "User is already connected"}', headers=self._headers - ) - user.start_listen() - return web.json_response({}, headers=self._acao_headers) - - async def refresh(self, request: web.Request) -> web.Response: - user = await self.check_token(request) - await user.refresh() - return web.json_response({}, headers=self._acao_headers) diff --git a/matrix_appservice_kakaotalk/web/static/lib/asn1hex-1.1.min.js b/matrix_appservice_kakaotalk/web/static/lib/asn1hex-1.1.min.js deleted file mode 100644 index ced8b9e..0000000 --- a/matrix_appservice_kakaotalk/web/static/lib/asn1hex-1.1.min.js +++ /dev/null @@ -1,7 +0,0 @@ -// asn1hex-1.2.8.js (c) 2012-2020 Kenji Urushima | kjur.github.com/jsrsasign/license - -import BigInteger from "./jsbn.min.js" - -var ASN1HEX=new function(){};ASN1HEX.getLblen=function(t,n){if("8"!=t.substr(n+2,1))return 1;var r=parseInt(t.substr(n+3,1));return 0==r?-1:0=e)break}return g},ASN1HEX.getNthChildIdx=function(t,n,r){return ASN1HEX.getChildIdx(t,n)[r]},ASN1HEX.getIdxbyList=function(t,n,r,e){var i,u,g=ASN1HEX;return 0==r.length?void 0!==e&&t.substr(n,2)!==e?-1:n:(i=r.shift())>=(u=g.getChildIdx(t,n)).length?-1:g.getIdxbyList(t,u[i],r,e)},ASN1HEX.getIdxbyListEx=function(t,n,r,e){var i,u,g=ASN1HEX;if(0==r.length)return void 0!==e&&t.substr(n,2)!==e?-1:n;i=r.shift(),u=g.getChildIdx(t,n);for(var s=0,o=0;o=t.length?null:i.getTLV(t,u)},ASN1HEX.getTLVbyListEx=function(t,n,r,e){var i=ASN1HEX,u=i.getIdxbyListEx(t,n,r,e);return-1==u?null:i.getTLV(t,u)},ASN1HEX.getVbyList=function(t,n,r,e,i){var u,g,s=ASN1HEX;return-1==(u=s.getIdxbyList(t,n,r,e))?null:u>=t.length?null:(g=s.getV(t,u),!0===i&&(g=g.substr(2)),g)},ASN1HEX.getVbyListEx=function(t,n,r,e,i){var u,g,s=ASN1HEX;return-1==(u=s.getIdxbyListEx(t,n,r,e))?null:(g=s.getV(t,u),"03"==t.substr(u,2)&&!1!==i&&(g=g.substr(2)),g)},ASN1HEX.getInt=function(t,n,r){null==r&&(r=-1);try{var e=t.substr(n,2);if("02"!=e&&"03"!=e)return r;var i=ASN1HEX.getV(t,n);return"02"==e?parseInt(i,16):bitstrtoint(i)}catch(t){return r}},ASN1HEX.getOID=function(t,n,r){null==r&&(r=null);try{if("06"!=t.substr(n,2))return r;var e=ASN1HEX.getV(t,n);return hextooid(e)}catch(t){return r}},ASN1HEX.getOIDName=function(t,n,r){null==r&&(r=null);try{var e=ASN1HEX.getOID(t,n,r);if(e==r)return r;var i=KJUR.asn1.x509.OID.oid2name(e);return""==i?e:i}catch(t){return r}},ASN1HEX.getString=function(t,n,r){null==r&&(r=null);try{var e=ASN1HEX.getV(t,n);return hextorstr(e)}catch(t){return r}},ASN1HEX.hextooidstr=function(t){var n=function(t,n){return t.length>=n?t:new Array(n-t.length+1).join("0")+t},r=[],e=t.substr(0,2),i=parseInt(e,16);r[0]=new String(Math.floor(i/40)),r[1]=new String(i%40);for(var u=t.substr(2),g=[],s=0;s0&&(l=l+"."+o.join(".")),l},ASN1HEX.dump=function(t,n,r,e){var i=ASN1HEX,u=i.getV,g=i.dump,s=i.getChildIdx,o=t;t instanceof KJUR.asn1.ASN1Object&&(o=t.getEncodedHex());var a=function(t,n){return t.length<=2*n?t:t.substr(0,n)+"..(total "+t.length/2+"bytes).."+t.substr(t.length-n,n)};void 0===n&&(n={ommit_long_octet:32}),void 0===r&&(r=0),void 0===e&&(e="");var l,f=n.ommit_long_octet;if("01"==(l=o.substr(r,2)))return"00"==(E=u(o,r))?e+"BOOLEAN FALSE\n":e+"BOOLEAN TRUE\n";if("02"==l)return e+"INTEGER "+a(E=u(o,r),f)+"\n";if("03"==l){var E=u(o,r);if(i.isASN1HEX(E.substr(2))){var h=e+"BITSTRING, encapsulates\n";return h+=g(E.substr(2),n,0,e+" ")}return e+"BITSTRING "+a(E,f)+"\n"}if("04"==l){E=u(o,r);if(i.isASN1HEX(E)){h=e+"OCTETSTRING, encapsulates\n";return h+=g(E,n,0,e+" ")}return e+"OCTETSTRING "+a(E,f)+"\n"}if("05"==l)return e+"NULL\n";if("06"==l){var S=u(o,r),N=KJUR.asn1.ASN1Util.oidHexToInt(S),A=KJUR.asn1.x509.OID.oid2name(N),b=N.replace(/\./g," ");return""!=A?e+"ObjectIdentifier "+A+" ("+b+")\n":e+"ObjectIdentifier ("+b+")\n"}if("0a"==l)return e+"ENUMERATED "+parseInt(u(o,r))+"\n";if("0c"==l)return e+"UTF8String '"+hextoutf8(u(o,r))+"'\n";if("13"==l)return e+"PrintableString '"+hextoutf8(u(o,r))+"'\n";if("14"==l)return e+"TeletexString '"+hextoutf8(u(o,r))+"'\n";if("16"==l)return e+"IA5String '"+hextoutf8(u(o,r))+"'\n";if("17"==l)return e+"UTCTime "+hextoutf8(u(o,r))+"\n";if("18"==l)return e+"GeneralizedTime "+hextoutf8(u(o,r))+"\n";if("1a"==l)return e+"VisualString '"+hextoutf8(u(o,r))+"'\n";if("1e"==l)return e+"BMPString '"+hextoutf8(u(o,r))+"'\n";if("30"==l){if("3000"==o.substr(r,4))return e+"SEQUENCE {}\n";h=e+"SEQUENCE\n";var c=n;if((2==(v=s(o,r)).length||3==v.length)&&"06"==o.substr(v[0],2)&&"04"==o.substr(v[v.length-1],2)){A=i.oidname(u(o,v[0]));var x=JSON.parse(JSON.stringify(n));x.x509ExtName=A,c=x}for(var H=0;H31)&&(128==(192&r)&&(31&r)==e))}catch(t){return!1}},ASN1HEX.isASN1HEX=function(t){var n=ASN1HEX;if(t.length%2==1)return!1;var r=n.getVblen(t,0),e=t.substr(0,2),i=n.getL(t,0);return t.length-e.length-i.length==2*r},ASN1HEX.checkStrictDER=function(t,n,r,e,i){var u=ASN1HEX;if(void 0===r){if("string"!=typeof t)throw new Error("not hex string");if(t=t.toLowerCase(),!KJUR.lang.String.isHex(t))throw new Error("not hex string");r=t.length,i=(e=t.length/2)<128?1:Math.ceil(e.toString(16))+1}if(u.getL(t,n).length>2*i)throw new Error("L of TLV too long: idx="+n);var g=u.getVblen(t,n);if(g>e)throw new Error("value of L too long than hex: idx="+n);var s=u.getTLV(t,n),o=s.length-2-u.getL(t,n).length;if(o!==2*g)throw new Error("V string length and L's value not the same:"+o+"/"+2*g);if(0===n&&t.length!=s.length)throw new Error("total length and TLV length unmatch:"+t.length+"!="+s.length);var a=t.substr(n,2);if("02"===a){var l=u.getVidx(t,n);if("00"==t.substr(l,2)&&t.charCodeAt(l+2)<56)throw new Error("not least zeros for DER INTEGER")}if(32&parseInt(a,16)){for(var f=u.getVblen(t,n),E=0,h=u.getChildIdx(t,n),S=0;S=5&&((e||!n&&5===r)&&(h.push(r,0,e,s),r=6),n&&(h.push(r,n,0,s),r=6)),e=""},a=0;a"===t?(r=1,e=""):e=t+e[0]:u?t===u?u="":e+=t:'"'===t||"'"===t?u=t:">"===t?(p(),r=1):r&&("="===t?(r=5,s=e,e=""):"/"===t&&(r<5||">"===n[a][l+1])?(p(),3===r&&(h=h[0]),r=h,(h=h[0]).push(2,0,r),r=0):" "===t||"\t"===t||"\n"===t||"\r"===t?(p(),r=2):e+=t),3===r&&"!--"===e&&(r=4,h=h[0])}return p(),h}(s)),r),arguments,[])).length>1?r:r[0]} diff --git a/matrix_appservice_kakaotalk/web/static/lib/jsbn.min.js b/matrix_appservice_kakaotalk/web/static/lib/jsbn.min.js deleted file mode 100644 index 776f93c..0000000 --- a/matrix_appservice_kakaotalk/web/static/lib/jsbn.min.js +++ /dev/null @@ -1,9 +0,0 @@ -// From http://www-cs-students.stanford.edu/~tjw/jsbn/jsbn.js -// Also includes bnIntValue from http://www-cs-students.stanford.edu/~tjw/jsbn/jsbn2.js - -// Copyright (c) 2005 Tom Wu -// http://www-cs-students.stanford.edu/~tjw/jsbn/LICENSE - -var dbits,canary=0xdeadbeefcafe,j_lm=15715070==(16777215&canary);function BigInteger(t,i,r){null!=t&&("number"==typeof t?this.fromNumber(t,i,r):null==i&&"string"!=typeof t?this.fromString(t,256):this.fromString(t,i))}function nbi(){return new BigInteger(null)}function am1(t,i,r,o,n,e){for(;--e>=0;){var s=i*this[t++]+r[o]+n;n=Math.floor(s/67108864),r[o++]=67108863&s}return n}function am2(t,i,r,o,n,e){for(var s=32767&i,h=i>>15;--e>=0;){var p=32767&this[t],a=this[t++]>>15,f=h*p+a*s;n=((p=s*p+((32767&f)<<15)+r[o]+(1073741823&n))>>>30)+(f>>>15)+h*a+(n>>>30),r[o++]=1073741823&p}return n}function am3(t,i,r,o,n,e){for(var s=16383&i,h=i>>14;--e>=0;){var p=16383&this[t],a=this[t++]>>14,f=h*p+a*s;n=((p=s*p+((16383&f)<<14)+r[o]+n)>>28)+(f>>14)+h*a,r[o++]=268435455&p}return n}j_lm&&"Microsoft Internet Explorer"==navigator.appName?(BigInteger.prototype.am=am2,dbits=30):j_lm&&"Netscape"!=navigator.appName?(BigInteger.prototype.am=am1,dbits=26):(BigInteger.prototype.am=am3,dbits=28),BigInteger.prototype.DB=dbits,BigInteger.prototype.DM=(1<=0;--i)t[i]=this[i];t.t=this.t,t.s=this.s}function bnpFromInt(t){this.t=1,this.s=t<0?-1:0,t>0?this[0]=t:t<-1?this[0]=t+this.DV:this.t=0}function nbv(t){var i=nbi();return i.fromInt(t),i}function bnpFromString(t,i){var r;if(16==i)r=4;else if(8==i)r=3;else if(256==i)r=8;else if(2==i)r=1;else if(32==i)r=5;else{if(4!=i)return void this.fromRadix(t,i);r=2}this.t=0,this.s=0;for(var o=t.length,n=!1,e=0;--o>=0;){var s=8==r?255&t[o]:intAt(t,o);s<0?"-"==t.charAt(o)&&(n=!0):(n=!1,0==e?this[this.t++]=s:e+r>this.DB?(this[this.t-1]|=(s&(1<>this.DB-e):this[this.t-1]|=s<=this.DB&&(e-=this.DB))}8==r&&0!=(128&t[0])&&(this.s=-1,e>0&&(this[this.t-1]|=(1<0&&this[this.t-1]==t;)--this.t}function bnToString(t){if(this.s<0)return"-"+this.negate().toString(t);var i;if(16==t)i=4;else if(8==t)i=3;else if(2==t)i=1;else if(32==t)i=5;else{if(4!=t)return this.toRadix(t);i=2}var r,o=(1<0)for(h>h)>0&&(n=!0,e=int2char(r));s>=0;)h>(h+=this.DB-i)):(r=this[s]>>(h-=i)&o,h<=0&&(h+=this.DB,--s)),r>0&&(n=!0),n&&(e+=int2char(r));return n?e:"0"}function bnNegate(){var t=nbi();return BigInteger.ZERO.subTo(this,t),t}function bnAbs(){return this.s<0?this.negate():this}function bnCompareTo(t){var i=this.s-t.s;if(0!=i)return i;var r=this.t;if(0!=(i=r-t.t))return this.s<0?-i:i;for(;--r>=0;)if(0!=(i=this[r]-t[r]))return i;return 0}function nbits(t){var i,r=1;return 0!=(i=t>>>16)&&(t=i,r+=16),0!=(i=t>>8)&&(t=i,r+=8),0!=(i=t>>4)&&(t=i,r+=4),0!=(i=t>>2)&&(t=i,r+=2),0!=(i=t>>1)&&(t=i,r+=1),r}function bnBitLength(){return this.t<=0?0:this.DB*(this.t-1)+nbits(this[this.t-1]^this.s&this.DM)}function bnpDLShiftTo(t,i){var r;for(r=this.t-1;r>=0;--r)i[r+t]=this[r];for(r=t-1;r>=0;--r)i[r]=0;i.t=this.t+t,i.s=this.s}function bnpDRShiftTo(t,i){for(var r=t;r=0;--r)i[r+s+1]=this[r]>>n|h,h=(this[r]&e)<=0;--r)i[r]=0;i[s]=h,i.t=this.t+s+1,i.s=this.s,i.clamp()}function bnpRShiftTo(t,i){i.s=this.s;var r=Math.floor(t/this.DB);if(r>=this.t)i.t=0;else{var o=t%this.DB,n=this.DB-o,e=(1<>o;for(var s=r+1;s>o;o>0&&(i[this.t-r-1]|=(this.s&e)<>=this.DB;if(t.t>=this.DB;o+=this.s}else{for(o+=this.s;r>=this.DB;o-=t.s}i.s=o<0?-1:0,o<-1?i[r++]=this.DV+o:o>0&&(i[r++]=o),i.t=r,i.clamp()}function bnpMultiplyTo(t,i){var r=this.abs(),o=t.abs(),n=r.t;for(i.t=n+o.t;--n>=0;)i[n]=0;for(n=0;n=0;)t[r]=0;for(r=0;r=i.DV&&(t[r+i.t]-=i.DV,t[r+i.t+1]=1)}t.t>0&&(t[t.t-1]+=i.am(r,i[r],t,2*r,0,1)),t.s=0,t.clamp()}function bnpDivRemTo(t,i,r){var o=t.abs();if(!(o.t<=0)){var n=this.abs();if(n.t0?(o.lShiftTo(p,e),n.lShiftTo(p,r)):(o.copyTo(e),n.copyTo(r));var a=e.t,f=e[a-1];if(0!=f){var u=f*(1<1?e[a-2]>>this.F2:0),g=this.FV/u,m=(1<=0&&(r[r.t++]=1,r.subTo(l,r)),BigInteger.ONE.dlShiftTo(a,l),l.subTo(e,e);e.t=0;){var B=r[--v]==f?this.DM:Math.floor(r[v]*g+(r[v-1]+c)*m);if((r[v]+=e.am(0,B,r,b,0,a))0&&r.rShiftTo(p,r),s<0&&BigInteger.ZERO.subTo(r,r)}}}function bnMod(t){var i=nbi();return this.abs().divRemTo(t,null,i),this.s<0&&i.compareTo(BigInteger.ZERO)>0&&t.subTo(i,i),i}function Classic(t){this.m=t}function cConvert(t){return t.s<0||t.compareTo(this.m)>=0?t.mod(this.m):t}function cRevert(t){return t}function cReduce(t){t.divRemTo(this.m,null,t)}function cMulTo(t,i,r){t.multiplyTo(i,r),this.reduce(r)}function cSqrTo(t,i){t.squareTo(i),this.reduce(i)}function bnpInvDigit(){if(this.t<1)return 0;var t=this[0];if(0==(1&t))return 0;var i=3&t;return(i=(i=(i=(i=i*(2-(15&t)*i)&15)*(2-(255&t)*i)&255)*(2-((65535&t)*i&65535))&65535)*(2-t*i%this.DV)%this.DV)>0?this.DV-i:-i}function Montgomery(t){this.m=t,this.mp=t.invDigit(),this.mpl=32767&this.mp,this.mph=this.mp>>15,this.um=(1<0&&this.m.subTo(i,i),i}function montRevert(t){var i=nbi();return t.copyTo(i),this.reduce(i),i}function montReduce(t){for(;t.t<=this.mt2;)t[t.t++]=0;for(var i=0;i>15)*this.mpl&this.um)<<15)&t.DM;for(t[r=i+this.m.t]+=this.m.am(0,o,t,i,0,this.m.t);t[r]>=t.DV;)t[r]-=t.DV,t[++r]++}t.clamp(),t.drShiftTo(this.m.t,t),t.compareTo(this.m)>=0&&t.subTo(this.m,t)}function montSqrTo(t,i){t.squareTo(i),this.reduce(i)}function montMulTo(t,i,r){t.multiplyTo(i,r),this.reduce(r)}function bnpIsEven(){return 0==(this.t>0?1&this[0]:this.s)}function bnpExp(t,i){if(t>4294967295||t<1)return BigInteger.ONE;var r=nbi(),o=nbi(),n=i.convert(this),e=nbits(t)-1;for(n.copyTo(r);--e>=0;)if(i.sqrTo(r,o),(t&1<0)i.mulTo(o,n,r);else{var s=r;r=o,o=s}return i.revert(r)}function bnModPowInt(t,i){var r;return r=t<256||i.isEven()?new Classic(i):new Montgomery(i),this.exp(t,r)}function bnIntValue(){if(this.s<0){if(1==this.t)return this[0]-this.DV;if(0==this.t)return-1}else{if(1==this.t)return this[0];if(0==this.t)return 0}return(this[1]&(1<<32-this.DB)-1)<code{border-radius:0;display:block;padding:1rem 1.5rem;white-space:pre}hr{border:0;border-top:.1rem solid #f4f5f6;margin:3rem 0}input:not([type]),input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=url],input[type=week],select,textarea{-webkit-appearance:none;background-color:transparent;border:.1rem solid #d1d1d1;border-radius:.4rem;box-shadow:none;box-sizing:inherit;height:3.8rem;padding:.6rem 1rem .7rem;width:100%}input:not([type]):focus,input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=url]:focus,input[type=week]:focus,select:focus,textarea:focus{border-color:#9b4dca;outline:0}select{background:url('data:image/svg+xml;utf8,') center right no-repeat;padding-right:3rem}select:focus{background-image:url('data:image/svg+xml;utf8,')}select[multiple]{background:0 0;height:auto}textarea{min-height:6.5rem}label,legend{display:block;font-size:1.6rem;font-weight:700;margin-bottom:.5rem}fieldset{border-width:0;padding:0}input[type=checkbox],input[type=radio]{display:inline}.label-inline{display:inline-block;font-weight:400;margin-left:.5rem}.container{margin:0 auto;max-width:112rem;padding:0 2rem;position:relative;width:100%}.row{display:flex;flex-direction:column;padding:0;width:100%}.row.row-no-padding{padding:0}.row.row-no-padding>.column{padding:0}.row.row-wrap{flex-wrap:wrap}.row.row-top{align-items:flex-start}.row.row-bottom{align-items:flex-end}.row.row-center{align-items:center}.row.row-stretch{align-items:stretch}.row.row-baseline{align-items:baseline}.row .column{display:block;flex:1 1 auto;margin-left:0;max-width:100%;width:100%}.row .column.column-offset-10{margin-left:10%}.row .column.column-offset-20{margin-left:20%}.row .column.column-offset-25{margin-left:25%}.row .column.column-offset-33,.row .column.column-offset-34{margin-left:33.3333%}.row .column.column-offset-40{margin-left:40%}.row .column.column-offset-50{margin-left:50%}.row .column.column-offset-60{margin-left:60%}.row .column.column-offset-66,.row .column.column-offset-67{margin-left:66.6666%}.row .column.column-offset-75{margin-left:75%}.row .column.column-offset-80{margin-left:80%}.row .column.column-offset-90{margin-left:90%}.row .column.column-10{flex:0 0 10%;max-width:10%}.row .column.column-20{flex:0 0 20%;max-width:20%}.row .column.column-25{flex:0 0 25%;max-width:25%}.row .column.column-33,.row .column.column-34{flex:0 0 33.3333%;max-width:33.3333%}.row .column.column-40{flex:0 0 40%;max-width:40%}.row .column.column-50{flex:0 0 50%;max-width:50%}.row .column.column-60{flex:0 0 60%;max-width:60%}.row .column.column-66,.row .column.column-67{flex:0 0 66.6666%;max-width:66.6666%}.row .column.column-75{flex:0 0 75%;max-width:75%}.row .column.column-80{flex:0 0 80%;max-width:80%}.row .column.column-90{flex:0 0 90%;max-width:90%}.row .column .column-top{align-self:flex-start}.row .column .column-bottom{align-self:flex-end}.row .column .column-center{align-self:center}@media (min-width:40rem){.row{flex-direction:row;margin-left:-1rem;width:calc(100% + 2rem)}.row .column{margin-bottom:inherit;padding:0 1rem}}a{color:#9b4dca;text-decoration:none}a:focus,a:hover{color:#606c76}dl,ol,ul{list-style:none;margin-top:0;padding-left:0}dl dl,dl ol,dl ul,ol dl,ol ol,ol ul,ul dl,ul ol,ul ul{font-size:90%;margin:1.5rem 0 1.5rem 3rem}ol{list-style:decimal inside}ul{list-style:circle inside}.button,button,dd,dt,li{margin-bottom:1rem}fieldset,input,select,textarea{margin-bottom:1.5rem}blockquote,dl,figure,form,ol,p,pre,table,ul{margin-bottom:2.5rem}table{border-spacing:0;display:block;overflow-x:auto;text-align:left;width:100%}td,th{border-bottom:.1rem solid #e1e1e1;padding:1.2rem 1.5rem}td:first-child,th:first-child{padding-left:0}td:last-child,th:last-child{padding-right:0}@media (min-width:40rem){table{display:table;overflow-x:initial}}b,strong{font-weight:700}p{margin-top:0}h1,h2,h3,h4,h5,h6{font-weight:300;letter-spacing:-.1rem;margin-bottom:2rem;margin-top:0}h1{font-size:4.6rem;line-height:1.2}h2{font-size:3.6rem;line-height:1.25}h3{font-size:2.8rem;line-height:1.3}h4{font-size:2.2rem;letter-spacing:-.08rem;line-height:1.35}h5{font-size:1.8rem;letter-spacing:-.05rem;line-height:1.5}h6{font-size:1.6rem;letter-spacing:0;line-height:1.4}img{max-width:100%}.clearfix:after{clear:both;content:' ';display:table}.float-left{float:left}.float-right{float:right} diff --git a/matrix_appservice_kakaotalk/web/static/lib/normalize-8.0.1.min.css b/matrix_appservice_kakaotalk/web/static/lib/normalize-8.0.1.min.css deleted file mode 100644 index b52daa7..0000000 --- a/matrix_appservice_kakaotalk/web/static/lib/normalize-8.0.1.min.css +++ /dev/null @@ -1,2 +0,0 @@ -/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ -html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none} diff --git a/matrix_appservice_kakaotalk/web/static/lib/preact-10.5.12.min.js b/matrix_appservice_kakaotalk/web/static/lib/preact-10.5.12.min.js deleted file mode 100644 index b4ff741..0000000 --- a/matrix_appservice_kakaotalk/web/static/lib/preact-10.5.12.min.js +++ /dev/null @@ -1 +0,0 @@ -var n,l,u,i,t,r,o={},f=[],e=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;function c(e,n){for(var t in n)e[t]=n[t];return e}function s(e){var n=e.parentNode;n&&n.removeChild(e)}function a(e,n,t){var _,l,o,r=arguments,u={};for(o in n)"key"==o?_=n[o]:"ref"==o?l=n[o]:u[o]=n[o];if(arguments.length>3)for(t=[t],o=3;o0?v(m.type,m.props,m.key,null,m.__v):m)){if(m.__=t,m.__b=t.__b+1,null===(h=P[p])||h&&m.key==h.key&&m.type===h.type)P[p]=void 0;else for(a=0;a3)for(t=[t],o=3;o>8&255,rng_pool[rng_pptr++]^=t>>16&255,rng_pool[rng_pptr++]^=t>>24&255,rng_pptr>=rng_psize&&(rng_pptr-=rng_psize)}function rng_seed_time(){rng_seed_int((new Date).getTime())}if(null==rng_pool){var t;if(rng_pool=new Array,rng_pptr=0,window.crypto&&window.crypto.getRandomValues){var ua=new Uint8Array(32);for(window.crypto.getRandomValues(ua),t=0;t<32;++t)rng_pool[rng_pptr++]=ua[t]}if("Netscape"==navigator.appName&&navigator.appVersion<"5"&&window.crypto){var z=window.crypto.random(32);for(t=0;t>>8,rng_pool[rng_pptr++]=255&t;rng_pptr=0,rng_seed_time()}function rng_get_byte(){if(null==rng_state){for(rng_seed_time(),(rng_state=prng_newstate()).init(rng_pool),rng_pptr=0;rng_pptr=0&&t>0;)e[--t]=n[r--];e[--t]=0;for(var l=new SecureRandom,i=new Array;t>2;){for(i[0]=0;0==i[0];)l.nextBytes(i);e[--t]=i[0]}return e[--t]=2,e[--t]=0,new BigInteger(e)}function RSAKey(){this.n=null,this.e=0,this.d=null,this.p=null,this.q=null,this.dmp1=null,this.dmq1=null,this.coeff=null}function RSASetPublic(n,t){null!=n&&null!=t&&n.length>0&&t.length>0?(this.n=parseBigInt(n,16),this.e=parseInt(t,16)):alert("Invalid RSA public key")}function RSADoPublic(n){return n.modPowInt(this.e,this.n)}function RSAEncrypt(n){var t=pkcs1pad2(n,this.n.bitLength()+7>>3);if(null==t)return null;var e=this.doPublic(t);if(null==e)return null;var r=e.toString(16);return 0==(1&r.length)?r:"0"+r}RSAKey.prototype.doPublic=RSADoPublic,RSAKey.prototype.setPublic=RSASetPublic,RSAKey.prototype.encrypt=RSAEncrypt; - -export default RSAKey diff --git a/matrix_appservice_kakaotalk/web/static/lib/spinner.css b/matrix_appservice_kakaotalk/web/static/lib/spinner.css deleted file mode 100644 index ca0e335..0000000 --- a/matrix_appservice_kakaotalk/web/static/lib/spinner.css +++ /dev/null @@ -1,80 +0,0 @@ -.loader { - color: #9b4dca; - font-size: 90px; - text-indent: -9999em; - overflow: hidden; - width: 1em; - height: 1em; - border-radius: 50%; - margin: 72px auto; - position: relative; - -webkit-transform: translateZ(0); - -ms-transform: translateZ(0); - transform: translateZ(0); - -webkit-animation: load6 1.7s infinite ease, round 1.7s infinite ease; - animation: load6 1.7s infinite ease, round 1.7s infinite ease; -} -@-webkit-keyframes load6 { - 0% { - box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em; - } - 5%, - 95% { - box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em; - } - 10%, - 59% { - box-shadow: 0 -0.83em 0 -0.4em, -0.087em -0.825em 0 -0.42em, -0.173em -0.812em 0 -0.44em, -0.256em -0.789em 0 -0.46em, -0.297em -0.775em 0 -0.477em; - } - 20% { - box-shadow: 0 -0.83em 0 -0.4em, -0.338em -0.758em 0 -0.42em, -0.555em -0.617em 0 -0.44em, -0.671em -0.488em 0 -0.46em, -0.749em -0.34em 0 -0.477em; - } - 38% { - box-shadow: 0 -0.83em 0 -0.4em, -0.377em -0.74em 0 -0.42em, -0.645em -0.522em 0 -0.44em, -0.775em -0.297em 0 -0.46em, -0.82em -0.09em 0 -0.477em; - } - 100% { - box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em; - } -} -@keyframes load6 { - 0% { - box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em; - } - 5%, - 95% { - box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em; - } - 10%, - 59% { - box-shadow: 0 -0.83em 0 -0.4em, -0.087em -0.825em 0 -0.42em, -0.173em -0.812em 0 -0.44em, -0.256em -0.789em 0 -0.46em, -0.297em -0.775em 0 -0.477em; - } - 20% { - box-shadow: 0 -0.83em 0 -0.4em, -0.338em -0.758em 0 -0.42em, -0.555em -0.617em 0 -0.44em, -0.671em -0.488em 0 -0.46em, -0.749em -0.34em 0 -0.477em; - } - 38% { - box-shadow: 0 -0.83em 0 -0.4em, -0.377em -0.74em 0 -0.42em, -0.645em -0.522em 0 -0.44em, -0.775em -0.297em 0 -0.46em, -0.82em -0.09em 0 -0.477em; - } - 100% { - box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em; - } -} -@-webkit-keyframes round { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); - } -} -@keyframes round { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); - } -} diff --git a/matrix_appservice_kakaotalk/web/static/login.html b/matrix_appservice_kakaotalk/web/static/login.html deleted file mode 100644 index 0a9b902..0000000 --- a/matrix_appservice_kakaotalk/web/static/login.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - matrix-appservice-kakaotalk login - - - - - - - - - - - - - - - - - - - - - diff --git a/matrix_appservice_kakaotalk/web/static/login/api.js b/matrix_appservice_kakaotalk/web/static/login/api.js deleted file mode 100644 index ee9ad19..0000000 --- a/matrix_appservice_kakaotalk/web/static/login/api.js +++ /dev/null @@ -1,62 +0,0 @@ -// matrix-appservice-kakaotalk - A Matrix-KakaoTalk puppeting bridge. -// Copyright (C) 2021 Tulir Asokan -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . -import encryptPassword from "./crypto.js" - -const apiToken = location.hash.slice(1) -const headers = { Authorization: `Bearer ${apiToken}` } -const jsonHeaders = { ...headers, "Content-Type": "application/json" } -const fetchParams = { headers } - -export async function whoami() { - const resp = await fetch("api/whoami", fetchParams) - return await resp.json() -} - -export async function prepareLogin() { - const resp = await fetch("api/login/prepare", { ...fetchParams, method: "POST" }) - return await resp.json() -} - -export async function login(pubkey, keyID, email, password) { - const resp = await fetch("api/login", { - method: "POST", - body: JSON.stringify({ - email, - encrypted_password: await encryptPassword(pubkey, keyID, password), - }), - headers: jsonHeaders, - }) - return await resp.json() -} - -export async function login2FA(email, code) { - const resp = await fetch("api/login/2fa", { - method: "POST", - body: JSON.stringify({ email, code }), - headers: jsonHeaders, - }) - return await resp.json() -} - -export async function loginApproved() { - const resp = await fetch("api/login/approved", { method: "POST", headers }) - return await resp.json() -} - -export async function wasLoginApproved() { - const resp = await fetch("api/login/check_approved", fetchParams) - return (await resp.json()).approved -} diff --git a/matrix_appservice_kakaotalk/web/static/login/app.js b/matrix_appservice_kakaotalk/web/static/login/app.js deleted file mode 100644 index 69afb55..0000000 --- a/matrix_appservice_kakaotalk/web/static/login/app.js +++ /dev/null @@ -1,200 +0,0 @@ -// matrix-appservice-kakaotalk - A Matrix-KakaoTalk puppeting bridge. -// Copyright (C) 2021 Tulir Asokan -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . -import { h, Component, render } from "../lib/preact-10.5.12.min.js" -import htm from "../lib/htm-3.0.4.min.js" -import * as api from "./api.js" - -const html = htm.bind(h) - -class App extends Component { - constructor(props) { - super(props) - this.approveCheckInterval = null - this.state = { - loading: true, - submitting: false, - error: null, - mxid: null, - facebook: null, - status: "pre-login", - pubkey: null, - keyID: null, - email: "", - password: "", - twoFactorCode: "", - twoFactorInfo: {}, - } - } - - async componentDidMount() { - const { error, mxid, facebook } = await api.whoami() - if (error) { - this.setState({ error, loading: false }) - } else { - this.setState({ mxid, facebook, loading: false }) - } - } - - checkLoginApproved = async () => { - if (!await api.wasLoginApproved()) { - return - } - clearInterval(this.approveCheckInterval) - this.approveCheckInterval = null - const resp = await api.loginApproved() - if (resp.status === "logged-in") { - this.setState({ status: resp.status }) - } - } - - submitNoDefault = evt => { - evt.preventDefault() - this.submit() - } - - async submit() { - if (this.approveCheckInterval) { - clearInterval(this.approveCheckInterval) - this.approveCheckInterval = null - } - this.setState({ submitting: true }) - let resp - switch (this.state.status) { - case "pre-login": - resp = await api.prepareLogin() - break - case "login": - resp = await api.login(this.state.pubkey, this.state.keyID, - this.state.email, this.state.password) - break - case "two-factor": - resp = await api.login2FA(this.state.email, this.state.twoFactorCode) - break - } - const stateUpdate = { submitting: false } - if (typeof resp.error === "string") { - stateUpdate.error = resp.error - } else { - stateUpdate.status = resp.status - } - if (resp.password_encryption_key_id) { - stateUpdate.pubkey = resp.password_encryption_pubkey - stateUpdate.keyID = resp.password_encryption_key_id - } - if (resp.status === "two-factor") { - this.approveCheckInterval = setInterval(this.checkLoginApproved, 5000) - stateUpdate.twoFactorInfo = resp.error - } else if (resp.status === "logged-in") { - api.whoami().then(({ facebook }) => this.setState({ facebook })) - } - this.setState(stateUpdate) - } - - fieldChange = evt => { - this.setState({ [evt.target.id]: evt.target.value }) - } - - renderFields() { - switch (this.state.status) { - case "pre-login": - return null - case "login": - return html` - - - - - ` - case "two-factor": - return html` -

${this.state.twoFactorInfo.error_user_msg}

- - - - - ` - } - } - - submitButtonText() { - switch (this.state.status) { - case "pre-login": - return "Start" - case "login": - case "two-factor": - return "Sign in" - } - } - - renderContent() { - if (this.state.loading) { - return html` -
Loading...
- ` - } else if (this.state.status === "logged-in") { - if (this.state.facebook) { - return html` - Successfully logged in as ${this.state.facebook.name}. The bridge will appear - as ${this.state.facebook.device_displayname} in Facebook security settings. - ` - } - return html` - Successfully logged in - ` - } else if (this.state.facebook) { - return html` - You're already logged in as ${this.state.facebook.name}. The bridge appears - as ${this.state.facebook.device_displayname} in Facebook security settings. - ` - } - return html` - ${this.state.error && html` -
${this.state.error}
- `} -
-
- - - ${this.renderFields()} - -
-
- ` - } - - render() { - return html` -
-

matrix-appservice-kakaotalk login

- ${this.renderContent()} -
- ` - } -} - - -render(html` - <${App}/> -`, document.body) diff --git a/matrix_appservice_kakaotalk/web/static/login/crypto.js b/matrix_appservice_kakaotalk/web/static/login/crypto.js deleted file mode 100644 index 6b0262d..0000000 --- a/matrix_appservice_kakaotalk/web/static/login/crypto.js +++ /dev/null @@ -1,112 +0,0 @@ -// matrix-appservice-kakaotalk - A Matrix-KakaoTalk puppeting bridge. -// Copyright (C) 2021 Tulir Asokan -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -// We have to use this pure-js RSA implementation because SubtleCrypto dropped PKCS#1 v1.5 support. -import RSAKey from "../lib/rsa.min.js" -import ASN1HEX from "../lib/asn1hex-1.1.min.js" - -function pemToHex(pem) { - // Strip pem header - pem = pem.replace("-----BEGIN PUBLIC KEY-----", "") - pem = pem.replace("-----END PUBLIC KEY-----", "") - - // Convert base64 to hex - const raw = atob(pem) - let result = "" - for (let i = 0; i < raw.length; i++) { - const hex = raw.charCodeAt(i).toString(16) - result += (hex.length === 2 ? hex : "0" + hex) - } - return result.toLowerCase() -} - -function getKey(pem) { - const keyHex = pemToHex(pem) - if (ASN1HEX.isASN1HEX(keyHex) === false) { - throw new Error("key is not ASN.1 hex string") - } else if (ASN1HEX.getVbyList(keyHex, 0, [0, 0], "06") !== "2a864886f70d010101") { - throw new Error("not PKCS8 RSA key") - } else if (ASN1HEX.getTLVbyListEx(keyHex, 0, [0, 0]) !== "06092a864886f70d010101") { - throw new Error("not PKCS8 RSA public key") - } - - const p5hex = ASN1HEX.getTLVbyListEx(keyHex, 0, [1, 0]) - if (ASN1HEX.isASN1HEX(p5hex) === false) { - throw new Error("keyHex is not ASN.1 hex string") - } - - const aIdx = ASN1HEX.getChildIdx(p5hex, 0) - if (aIdx.length !== 2 || p5hex.substr(aIdx[0], 2) !== "02" || p5hex.substr(aIdx[1], 2) !== "02") { - throw new Error("wrong hex for PKCS#5 public key") - } - - const hN = ASN1HEX.getV(p5hex, aIdx[0]) - const hE = ASN1HEX.getV(p5hex, aIdx[1]) - const key = new RSAKey() - key.setPublic(hN, hE) - return key -} - -// encryptPassword encrypts a login password using AES-256-GCM, then encrypts the AES key -// for Facebook's RSA-2048 key using PKCS#1 v1.5 padding. -// -// See https://github.com/mautrix/facebook/blob/v0.3.0/maufbapi/http/login.py#L164-L192 -// for the Python implementation of the same encryption protocol. -async function encryptPassword(pubkey, keyID, password) { - // Key and IV for AES encryption - const aesKey = await crypto.subtle.generateKey({ - name: "AES-GCM", - length: 256, - }, true, ["encrypt", "decrypt"]) - const aesIV = crypto.getRandomValues(new Uint8Array(12)) - // Get the actual bytes of the AES key - const aesKeyBytes = await crypto.subtle.exportKey("raw", aesKey) - - // Encrypt AES key with Facebook's RSA public key. - const rsaKey = getKey(pubkey) - const encryptedAESKeyHex = rsaKey.encrypt(new Uint8Array(aesKeyBytes)) - const encryptedAESKey = new Uint8Array(encryptedAESKeyHex.match(/[0-9A-Fa-f]{2}/g).map(h => parseInt(h, 16))) - - const encoder = new TextEncoder() - const time = Math.floor(Date.now() / 1000) - // Encrypt the password. The result includes the ciphertext and AES MAC auth tag. - const encryptedPasswordBuffer = await crypto.subtle.encrypt({ - name: "AES-GCM", - iv: aesIV, - // Add the current time to the additional authenticated data (AAD) section - additionalData: encoder.encode(time.toString()), - tagLength: 128, - }, aesKey, encoder.encode(password)) - // SubtleCrypto returns the auth tag and ciphertext in the wrong order, - // so we have to flip them around. - const authTag = new Uint8Array(encryptedPasswordBuffer.slice(-16)) - const encryptedPassword = new Uint8Array(encryptedPasswordBuffer.slice(0, -16)) - - const payload = new Uint8Array(2 + aesIV.byteLength + 2 + encryptedAESKey.byteLength + authTag.byteLength + encryptedPassword.byteLength) - // 1 is presumably the version - payload[0] = 1 - payload[1] = keyID - payload.set(aesIV, 2) - // Length of the encrypted AES key as a little-endian 16-bit int - payload[aesIV.byteLength + 2] = encryptedAESKey.byteLength & (1 << 8) - payload[aesIV.byteLength + 3] = encryptedAESKey.byteLength >> 8 - payload.set(encryptedAESKey, 4 + aesIV.byteLength) - payload.set(authTag, 4 + aesIV.byteLength + encryptedAESKey.byteLength) - payload.set(encryptedPassword, 4 + aesIV.byteLength + encryptedAESKey.byteLength + authTag.byteLength) - return `#PWD_MSGR:1:${time}:${btoa(String.fromCharCode(...payload))}` -} - -export default encryptPassword diff --git a/matrix_appservice_kakaotalk/web/static/login/index.css b/matrix_appservice_kakaotalk/web/static/login/index.css deleted file mode 100644 index efef17d..0000000 --- a/matrix_appservice_kakaotalk/web/static/login/index.css +++ /dev/null @@ -1,10 +0,0 @@ -.error { - background-color: darkred !important; - border-color: darkred !important; - opacity: 1 !important; -} - -main { - max-width: 50rem; - margin: 2rem auto 0; -}