trixie/registration.go
Elara6331 2b23f0caef
Some checks failed
ci/woodpecker/tag/manifest unknown status
ci/woodpecker/tag/build/1 Pipeline failed
ci/woodpecker/tag/build/2 Pipeline failed
Initial Commit
2025-05-16 16:39:55 +02:00

135 lines
3.1 KiB
Go

package main
import (
"bytes"
"crypto/hmac"
"crypto/sha1"
"encoding/hex"
"encoding/json"
"errors"
"log/slog"
"net/http"
"net/url"
"os"
)
// regEndpoint is the endpoint for shared secret registration
const regEndpoint = "/_synapse/admin/v1/register"
// RegistrationReq represents the JSON body of a registration request
type RegistrationReq struct {
Nonce string `json:"nonce"`
Username string `json:"username"`
DisplayName string `json:"displayname"`
Password string `json:"password"`
Admin bool `json:"admin"`
MAC string `json:"mac"`
}
// registerUser registers the given user with the matrix server
func registerUser(username, displayName, password string, u url.URL) error {
u.Path = regEndpoint
req, err := generateRegRequest(username, displayName, password, u)
if err != nil {
return err
}
data, err := json.Marshal(req)
if err != nil {
return err
}
log.Info("Issuing registration request",
slog.String("username", req.Username),
slog.String("nonce", req.Nonce),
slog.String("mac", req.MAC),
)
res, err := http.Post(u.String(), "application/json", bytes.NewReader(data))
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != 200 {
var errRes struct {
Message string `json:"message"`
ErrCode string `json:"errcode"`
Error string `json:"error"`
}
err = json.NewDecoder(res.Body).Decode(&errRes)
if err != nil || (errRes.Message == "" && errRes.Error == "") {
return errors.New("http: " + res.Status)
} else if errRes.Error != "" {
return errors.New(u.Host + ": " + errRes.ErrCode + ": " + errRes.Error)
} else {
return errors.New(u.Host + ": " + errRes.Message)
}
}
return nil
}
// generateRegRequest generates the JSON struct that will serve as the body of a use registration request
func generateRegRequest(username, displayName, password string, u url.URL) (*RegistrationReq, error) {
secret := os.Getenv("SHARED_SECRET")
nonce, err := getNonce(u)
if err != nil {
return nil, err
}
mac, err := calculateMAC(secret, username, password, nonce)
if err != nil {
return nil, err
}
return &RegistrationReq{
nonce,
username,
displayName,
password,
false,
mac,
}, nil
}
// getNonce requests and returns a nonce from the matrix server
func getNonce(u url.URL) (string, error) {
u.Path = regEndpoint
res, err := http.Get(u.String())
if err != nil {
return "", err
}
defer res.Body.Close()
if res.StatusCode != 200 {
return "", errors.New("http: " + res.Status)
}
var resp struct {
Nonce string `json:"nonce"`
}
err = json.NewDecoder(res.Body).Decode(&resp)
if err != nil {
return "", err
}
return resp.Nonce, nil
}
// calculateMAC calculates a returns an HMAC-SHA1 for a new non-admin user
func calculateMAC(secret, username, password, nonce string) (string, error) {
h := hmac.New(sha1.New, []byte(secret))
b := &bytes.Buffer{}
b.WriteString(nonce)
b.WriteByte(0)
b.WriteString(username)
b.WriteByte(0)
b.WriteString(password)
b.WriteByte(0)
b.WriteString("notadmin")
b.WriteTo(h)
return hex.EncodeToString(h.Sum(nil)), nil
}