api: rework discover & login

This commit is contained in:
saces 2026-04-29 00:35:12 +02:00
parent 5d02e28a51
commit 5c29242109
6 changed files with 82 additions and 92 deletions

View file

@ -4,31 +4,33 @@ package mxapi
import ( import (
"context" "context"
"encoding/json"
"maunium.net/go/mautrix" "maunium.net/go/mautrix"
"maunium.net/go/mautrix/id" "maunium.net/go/mautrix/id"
) )
func Discover(mxid string) (string, error) { type DiscoverInfo struct {
Homeserver string `json:"homeserver"`
UserID id.UserID `json:"user_id"`
LoginName string `json:"login_name"`
}
localpart, hs, err := id.UserID(mxid).ParseAndValidateRelaxed() // Discover tries to guess the homeserver from the given matrix id
func Discover(userID id.UserID) (di *DiscoverInfo, err error) {
var tmp_di DiscoverInfo
tmp_di.LoginName, tmp_di.Homeserver, err = userID.ParseAndValidateRelaxed()
if err != nil { if err != nil {
return "", err return
} }
wk, err := mautrix.DiscoverClientAPI(context.Background(), hs) wk, err := mautrix.DiscoverClientAPI(context.Background(), tmp_di.Homeserver)
if err != nil { if err != nil {
return "", err return
} }
if wk != nil { if wk != nil {
hs = wk.Homeserver.BaseURL tmp_di.Homeserver = wk.Homeserver.BaseURL
} }
tmp_di.UserID = userID
out, err := json.Marshal(map[string]string{"mxid": mxid, "homeserver": hs, "loginname": localpart}) di = &tmp_di
if err != nil { return
return "", err
}
return string(out), nil
} }

View file

@ -4,8 +4,6 @@ package mxapi
import ( import (
"context" "context"
"crypto/rand"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"os" "os"
@ -15,56 +13,47 @@ import (
"maunium.net/go/mautrix/id" "maunium.net/go/mautrix/id"
) )
type login_data struct { type LoginInfo struct {
Homeserver string `json:"homeserver"` DiscoverInfo DiscoverInfo `json:"discover_info"`
Mxid string `json:"mxid"` Password string `json:"password"`
Loginname string `json:"loginname"` DeviceID id.DeviceID `json:"deviceid"`
Password string `json:"password"` DeviceName string `json:"devicename"`
DeviceID string `json:"deviceid"` MXPassFile string `json:"mxpassfile"`
DeviceName string `json:"devicename"`
MXPassFile string `json:"mxpassfile"`
MakeMasterKey bool `json:"make_master_key"`
MakeRecoveryKey bool `json:"make_recovery_key"`
} }
func Login(data string) (string, error) { func Login(li *LoginInfo) (string, error) {
var ld login_data
err := json.Unmarshal([]byte(data), &ld)
if err != nil {
return "", err
}
if ld.MXPassFile != "" { if li.MXPassFile != "" {
if _, err := os.Stat(ld.MXPassFile); err == nil { if _, err := os.Stat(li.MXPassFile); err == nil {
return "", fmt.Errorf("mxpassfile '%s' already exists", ld.MXPassFile) return "", fmt.Errorf("mxpassfile '%s' already exists", li.MXPassFile)
} else if !errors.Is(err, os.ErrNotExist) { } else if !errors.Is(err, os.ErrNotExist) {
return "", fmt.Errorf("error while checking mxpassfile: %v", err) return "", fmt.Errorf("error while checking mxpassfile: %v", err)
} }
} }
mauclient, err := mautrix.NewClient(ld.Homeserver, id.UserID(ld.Mxid), "") mauclient, err := mautrix.NewClient(li.DiscoverInfo.Homeserver, li.DiscoverInfo.UserID, "")
if err != nil { if err != nil {
return "", err return "", err
} }
now := time.Now() now := time.Now()
if ld.DeviceID == "" { if li.DeviceID == "" {
ld.DeviceID = fmt.Sprintf("libmxclient-%d", now.Unix()) li.DeviceID = id.DeviceID(fmt.Sprintf("libmxclient-%d", now.Unix()))
} }
if ld.DeviceName == "" { if li.DeviceName == "" {
ld.DeviceName = fmt.Sprintf("libmxclient-%s", now.Format(time.RFC3339)) li.DeviceName = fmt.Sprintf("libmxclient-%s", now.Format(time.RFC3339))
} }
resp, err := mauclient.Login(context.Background(), &mautrix.ReqLogin{ resp, err := mauclient.Login(context.Background(), &mautrix.ReqLogin{
Type: "m.login.password", Type: "m.login.password",
Identifier: mautrix.UserIdentifier{ Identifier: mautrix.UserIdentifier{
Type: "m.id.user", Type: "m.id.user",
User: ld.Loginname, User: li.DiscoverInfo.LoginName,
}, },
Password: ld.Password, Password: li.Password,
DeviceID: id.DeviceID(ld.DeviceID), DeviceID: li.DeviceID,
InitialDeviceDisplayName: ld.DeviceName, InitialDeviceDisplayName: li.DeviceName,
StoreCredentials: false, StoreCredentials: false,
StoreHomeserverURL: false, StoreHomeserverURL: false,
RefreshToken: false, RefreshToken: false,
@ -73,26 +62,14 @@ func Login(data string) (string, error) {
return "", err return "", err
} }
res := fmt.Sprintf("%s | %s | %s | %s\n", ld.Homeserver, ld.Loginname, id.UserID(ld.Mxid).Homeserver(), resp.AccessToken) res := fmt.Sprintf("%s | %s | %s | %s\n", li.DiscoverInfo.Homeserver, li.DiscoverInfo.LoginName, li.DiscoverInfo.UserID.Homeserver(), resp.AccessToken)
if ld.MakeMasterKey { if li.MXPassFile != "" {
masterkey := make([]byte, 32) err := os.WriteFile(li.MXPassFile, []byte(res), 0600)
rand.Read(masterkey)
res = fmt.Sprintf("%smaster | | | %x\n", res, masterkey)
}
if ld.MakeRecoveryKey {
recoverykey := make([]byte, 32)
rand.Read(recoverykey)
res = fmt.Sprintf("%srecovery | | | %x\n", res, recoverykey)
}
if ld.MXPassFile != "" {
err := os.WriteFile(ld.MXPassFile, []byte(res), 0600)
if err != nil { if err != nil {
return "", fmt.Errorf("unable to write file: %w", err) return "", fmt.Errorf("unable to write file: %w", err)
} }
return "SUCCESS.", nil return "", nil
} }
return res, nil return res, nil

View file

@ -48,6 +48,12 @@ var apiCanceled = errors.New("canceled by api call")
type conversion helpers with some basic validation type conversion helpers with some basic validation
*/ */
func c2UserID(userid *C.char) (id.UserID, error) {
userID := id.UserID(C.GoString(userid))
_, _, err := userID.ParseAndValidateRelaxed()
return userID, err
}
func c2RoomID(roomid *C.char) (id.RoomID, error) { func c2RoomID(roomid *C.char) (id.RoomID, error) {
rid := C.GoString(roomid) rid := C.GoString(roomid)
if rid == "" || rid[0] != '!' { if rid == "" || rid[0] != '!' {
@ -283,19 +289,31 @@ func apiv0_deinitialize() C.int {
} }
//export apiv0_discover //export apiv0_discover
func apiv0_discover(mxid *C.char) *C.char { func apiv0_discover(userid *C.char) *C.char {
result, err := mxapi.Discover(C.GoString(mxid)) userID, err := c2UserID(userid)
if err != nil {
returnErr(err)
}
result, err := mxapi.Discover(userID)
if err != nil { if err != nil {
return C.CString(fmt.Sprintf("ERR: %v", err)) return C.CString(fmt.Sprintf("ERR: %v", err))
} }
return C.CString(result) return returnJSON(result, err)
} }
//export apiv0_login //export apiv0_login
func apiv0_login(data *C.char) *C.char { func apiv0_login(login_info *C.char) *C.char {
result, err := mxapi.Login(C.GoString(data)) var loginInfo mxapi.LoginInfo
err := json.Unmarshal([]byte(C.GoString(login_info)), &loginInfo)
if err != nil { if err != nil {
return C.CString(fmt.Sprintf("ERR: %v", err)) return returnErr(err)
}
result, err := mxapi.Login(&loginInfo)
if err != nil {
return returnErr(err)
}
if result == "" {
returnErr(nil)
} }
return C.CString(result) return C.CString(result)
} }

View file

@ -1,6 +1,6 @@
# Copyright (C) 2026 saces@c-base.org # Copyright (C) 2026 saces@c-base.org
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
import datetime from datetime import datetime
import getpass import getpass
import os import os
import time import time
@ -39,22 +39,22 @@ def catch_exception(func=None, *, handle):
def smalsetup(mxid, mxpassfile): def smalsetup(mxid, mxpassfile):
"""Utility for creating smalbot mxpass files""" """Utility for creating smalbot mxpass files"""
create_mxpass = len(mxpassfile.strip()) > 0 now = int(time.time())
if create_mxpass: if len(mxpassfile.strip()) > 0:
if os.path.exists(mxpassfile): if os.path.exists(mxpassfile):
raise click.ClickException(f"file {mxpassfile} exists.") raise click.ClickException(f"file {mxpassfile} exists.")
result_dict = ApiV0.Discover(mxid) discover_info = ApiV0.Discover(mxid)
result_dict["password"] = getpass.getpass(prompt="Password: ") login_info = {
result_dict["make_master_key"] = True "discover_info": discover_info,
result_dict["make_recovery_key"] = True "deviceid": f"smalbot-{now}",
"devicename": f"smalbot-{datetime.fromtimestamp(now)}",
"mxpassfile": mxpassfile,
"password": getpass.getpass(prompt="Password: "),
}
now = int(time.time()) ApiV0.Login(login_info)
result_dict["deviceid"] = f"smalbot-{now}"
result_dict["devicename"] = f"smalbot-{datetime.datetime.fromtimestamp(now)}"
ApiV0.Login(result_dict, ".mxpass") click.echo("login created. start your bot now or run e2eesetup.")
click.echo("login created. start your bot now.")

View file

@ -50,8 +50,8 @@ ffibuilder.cdef(
extern char* cliv0_generic_request(char* hs, char* access_token, char* method, char* path, char* data); extern char* cliv0_generic_request(char* hs, char* access_token, char* method, char* path, char* data);
extern int apiv0_initialize(); extern int apiv0_initialize();
extern int apiv0_deinitialize(); extern int apiv0_deinitialize();
extern char* apiv0_discover(char* mxid); extern char* apiv0_discover(char* user_id);
extern char* apiv0_login(char* data); extern char* apiv0_login(char* login_info);
extern char* apiv0_createclient(char* storage_path, char* hs, char* mxid, char* accessToken); extern char* apiv0_createclient(char* storage_path, char* hs, char* mxid, char* accessToken);
extern char* apiv0_createclient_pass(char* mxpassfile, char* storage_path, char* hs, char* localpart, char* domain); extern char* apiv0_createclient_pass(char* mxpassfile, char* storage_path, char* hs, char* localpart, char* domain);
extern char* apiv0_set_on_event_handler(int cid, on_event_handler_ptr ptr, void* pobj); extern char* apiv0_set_on_event_handler(int cid, on_event_handler_ptr ptr, void* pobj);

View file

@ -155,14 +155,7 @@ class ApiV0:
return CheckApiResult(res) return CheckApiResult(res)
@staticmethod @staticmethod
def Login(data: dict, mxpassfile: str): def Login(login_info: dict):
withpass = False res = ApiV0Api.login(login_info)
if mxpassfile is not None and len(mxpassfile.strip()) > 0: CheckApiErrorOnly(res)
data["mxpassfile"] = mxpassfile return res
withpass = True
res = ApiV0Api.login(data)
if withpass:
CheckApiError(res)
else:
CheckApiErrorOnly(res)
return res