Add some synapse admin API wrappers

This commit is contained in:
Tulir Asokan 2023-05-20 14:12:48 +03:00
commit 44006bd068
3 changed files with 191 additions and 0 deletions

18
synapseadmin/client.go Normal file
View file

@ -0,0 +1,18 @@
// Copyright (c) 2023 Tulir Asokan
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package synapseadmin
import (
"maunium.net/go/mautrix"
)
// Client is a wrapper for the mautrix.Client struct that includes methods for accessing the Synapse admin API.
//
// https://matrix-org.github.io/synapse/latest/usage/administration/admin_api/index.html
type Client struct {
*mautrix.Client
}

112
synapseadmin/register.go Normal file
View file

@ -0,0 +1,112 @@
// Copyright (c) 2023 Tulir Asokan
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package synapseadmin
import (
"context"
"crypto/hmac"
"crypto/sha1"
"encoding/hex"
"fmt"
"net/http"
"maunium.net/go/mautrix"
)
type respGetRegisterNonce struct {
Nonce string `json:"nonce"`
}
// ReqSharedSecretRegister is the request content for Client.SharedSecretRegister.
type ReqSharedSecretRegister struct {
// The username to register. Required.
Username string `json:"username"`
// The new password for the user. Required.
Password string `json:"password"`
// Initial displayname for the user. By default, the server will use the username as the displayname.
Displayname string `json:"displayname,omitempty"`
// The type of user to register. Defaults to empty (normal user).
// Officially allowed values are "support" or "bot".
//
// Currently, the only effect is that synapse skips consent requirements for those two user types,
// other than that the user type does absolutely nothing.
UserType string `json:"user_type,omitempty"`
// Whether the created user should be a server admin.
Admin bool `json:"admin"`
// Disable generating a new device along with the registration.
// If true, the access_token and device_id fields in the response will be empty.
InhibitLogin bool `json:"inhibit_login,omitempty"`
// A single-use nonce from GetRegisterNonce. This is automatically filled by Client.SharedSecretRegister if left empty.
Nonce string `json:"nonce"`
// The checksum for the request. This is automatically generated by Client.SharedSecretRegister using Sign.
SHA1Checksum string `json:"mac"`
}
func (req *ReqSharedSecretRegister) Sign(secret string) string {
signer := hmac.New(sha1.New, []byte(secret))
signer.Write([]byte(req.Nonce))
signer.Write([]byte{0})
signer.Write([]byte(req.Username))
signer.Write([]byte{0})
signer.Write([]byte(req.Password))
signer.Write([]byte{0})
if req.Admin {
signer.Write([]byte("admin"))
} else {
signer.Write([]byte("notadmin"))
}
if req.UserType != "" {
signer.Write([]byte{0})
signer.Write([]byte(req.UserType))
}
return hex.EncodeToString(signer.Sum(nil))
}
// GetRegisterNonce gets a nonce that can be used for SharedSecretRegister.
//
// This does not need to be called manually as SharedSecretRegister will automatically call this if no nonce is provided.
func (cli *Client) GetRegisterNonce(ctx context.Context) (string, error) {
var resp respGetRegisterNonce
_, err := cli.MakeFullRequest(mautrix.FullRequest{
Method: http.MethodGet,
URL: cli.BuildURL(mautrix.SynapseAdminURLPath{"v1", "register"}),
ResponseJSON: &resp,
Context: ctx,
})
if err != nil {
return "", err
}
return resp.Nonce, nil
}
// SharedSecretRegister creates a new account using a shared secret.
//
// https://matrix-org.github.io/synapse/latest/admin_api/register_api.html
func (cli *Client) SharedSecretRegister(ctx context.Context, sharedSecret string, req ReqSharedSecretRegister) (*mautrix.RespRegister, error) {
var err error
if req.Nonce == "" {
req.Nonce, err = cli.GetRegisterNonce(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get nonce: %w", err)
}
}
req.SHA1Checksum = req.Sign(sharedSecret)
var resp mautrix.RespRegister
_, err = cli.MakeFullRequest(mautrix.FullRequest{
Method: http.MethodPost,
URL: cli.BuildURL(mautrix.SynapseAdminURLPath{"v1", "register"}),
RequestJSON: req,
ResponseJSON: &resp,
Context: ctx,
})
if err != nil {
return nil, err
}
return &resp, nil
}

61
synapseadmin/userapi.go Normal file
View file

@ -0,0 +1,61 @@
// Copyright (c) 2023 Tulir Asokan
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package synapseadmin
import (
"context"
"fmt"
"net/http"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/id"
)
// ReqResetPassword is the request content for Client.ResetPassword.
type ReqResetPassword struct {
// The user whose password to reset.
UserID id.UserID `json:"-"`
// The new password for the user. Required.
NewPassword string `json:"new_password"`
// Whether all the user's existing devices should be logged out after the password change.
LogoutDevices bool `json:"logout_devices"`
}
// ResetPassword changes the password of another user using
//
// https://matrix-org.github.io/synapse/latest/admin_api/user_admin_api.html#reset-password
func (cli *Client) ResetPassword(ctx context.Context, req ReqResetPassword) error {
reqURL := cli.BuildURL(mautrix.SynapseAdminURLPath{"v1", "reset_password", req.UserID})
_, err := cli.MakeFullRequest(mautrix.FullRequest{
Method: http.MethodPost,
URL: reqURL,
RequestJSON: &req,
Context: ctx,
})
return err
}
// UsernameAvailable checks if a username is valid and available for registration on the server using the admin API.
//
// The response format is the same as mautrix.Client.RegisterAvailable,
// but it works even if registration is disabled on the server.
//
// https://matrix-org.github.io/synapse/latest/admin_api/user_admin_api.html#check-username-availability
func (cli *Client) UsernameAvailable(ctx context.Context, username string) (resp *mautrix.RespRegisterAvailable, err error) {
u := cli.BuildURLWithQuery(mautrix.SynapseAdminURLPath{"v1", "username_available"}, map[string]string{"username": username})
_, err = cli.MakeFullRequest(mautrix.FullRequest{
Method: http.MethodGet,
URL: u,
ResponseJSON: &resp,
Context: ctx,
})
if err == nil && !resp.Available {
err = fmt.Errorf(`request returned OK status without "available": true`)
}
return
}