linstrom/web/debug/users.go
mstar 415cd89792
All checks were successful
/ docker (push) Successful in 4m23s
Debug proxy, duck as fs
- Add proxy endpoint for proxying a message to a target's inbox
- Change duck embed to fs based to fix mk not understanding it
2025-04-23 15:11:46 +02:00

286 lines
7.6 KiB
Go

package webdebug
import (
"crypto/rand"
"database/sql"
"encoding/json"
"encoding/pem"
"fmt"
"io"
"net/http"
"strconv"
"time"
webutils "git.mstar.dev/mstar/goutils/http"
"git.mstar.dev/mstar/goutils/other"
"git.mstar.dev/mstar/goutils/sliceutils"
"github.com/rs/zerolog/hlog"
"git.mstar.dev/mstar/linstrom/activitypub"
"git.mstar.dev/mstar/linstrom/shared"
"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
"git.mstar.dev/mstar/linstrom/storage-new/models"
webshared "git.mstar.dev/mstar/linstrom/web/shared"
)
func getNonDeletedUsers(w http.ResponseWriter, r *http.Request) {
log := hlog.FromRequest(r)
pageStr := r.FormValue("page")
page := 0
if pageStr != "" {
var err error
page, err = strconv.Atoi(pageStr)
if err != nil {
webutils.ProblemDetails(
w,
http.StatusBadRequest,
"/errors/bad-page",
"bad page number",
other.IntoPointer("page number must be an uint"),
nil,
)
return
}
if page < 0 {
webutils.ProblemDetails(
w,
http.StatusBadRequest,
"/errors/bad-page",
"bad page number",
other.IntoPointer("page number must be >= 0"),
nil,
)
return
}
}
users, err := dbgen.User.GetPagedAllNonDeleted(uint(page))
if err != nil {
log.Error().Err(err).Int("page", page).Msg("Failed to get non-deleted users")
webutils.ProblemDetails(
w,
http.StatusInternalServerError,
"/errors/db-failure",
"database failure",
nil,
nil,
)
return
}
webutils.SendJson(w, sliceutils.Map(users, func(t models.User) webshared.User {
u := webshared.User{}
u.FromModel(&t)
return u
}))
}
func createLocalUser(w http.ResponseWriter, r *http.Request) {
type Inbound struct {
Username string `json:"username"`
Displayname string `json:"displayname"`
Description string `json:"description"`
Birthday *time.Time `json:"birthday"`
Location *string `json:"location"`
IsBot bool `json:"is_bot"`
}
log := hlog.FromRequest(r)
jsonDecoder := json.NewDecoder(r.Body)
data := Inbound{}
err := jsonDecoder.Decode(&data)
if err != nil {
webutils.ProblemDetails(
w,
http.StatusBadRequest,
"/errors/bad-request-data",
"bad request data",
nil,
map[string]any{
"sample": Inbound{
Username: "bob",
Displayname: "Bob Bobbington",
Description: "Bobbing Bobs bop to Bobs bobbing beats",
Birthday: other.IntoPointer(time.Now()),
Location: nil,
IsBot: false,
},
},
)
return
}
publicKeyEdBytes, privateKeyEdBytes, err := shared.GenerateKeypair(true)
if err != nil {
log.Error().Err(err).Msg("Failed to generate and marshal public key")
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
return
}
publicKeyRsaBytes, privateKeyRsaBytes, err := shared.GenerateKeypair(false)
if err != nil {
log.Error().Err(err).Msg("Failed to generate and marshal public key")
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
return
}
pkeyId := make([]byte, 64)
_, err = rand.Read(pkeyId)
if err != nil {
log.Error().Err(err).Msg("Failed to generate passkey id")
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
return
}
u := dbgen.User
query := u.Select(
u.Username,
u.DisplayName,
u.Description,
u.IsBot,
u.ServerId,
u.PrivateKeyEd,
u.PublicKeyEd,
u.PrivateKeyRsa,
u.PublicKeyRsa,
u.PasskeyId,
)
if data.Birthday != nil {
query = query.Select(u.Birthday)
}
if data.Location != nil {
query = query.Select(u.Location)
}
user := models.User{
Username: data.Username,
DisplayName: data.Displayname,
Description: data.Description,
IsBot: data.IsBot,
ServerId: 1, // Hardcoded, Self is always first ID
PublicKeyRsa: publicKeyRsaBytes,
PublicKeyEd: publicKeyEdBytes,
PrivateKeyRsa: privateKeyRsaBytes,
PrivateKeyEd: privateKeyEdBytes,
PasskeyId: pkeyId,
}
if data.Birthday != nil {
user.Birthday = sql.NullString{Valid: true, String: data.Birthday.Format("2006-Jan-02")}
// user.Birthday = sql.NullTime{Valid: true, Time: *data.Birthday}
}
if data.Location != nil {
user.Location = sql.NullString{Valid: true, String: *data.Location}
}
if err = query.Create(&user); err != nil {
log.Error().Err(err).Msg("failed to create new local user")
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
}
}
func deleteUser(w http.ResponseWriter, r *http.Request) {
id := r.FormValue("id")
dbgen.User.Where(dbgen.User.ID.Eq(id)).Delete()
w.WriteHeader(http.StatusOK)
}
func returnKeypair(w http.ResponseWriter, r *http.Request) {
id := r.FormValue("id")
user, err := dbgen.User.Where(dbgen.User.ID.Eq(id)).First()
if err != nil {
return
}
err = shared.SanityCheckX509dRsaKeys(user.PublicKeyRsa, user.PrivateKeyRsa)
if err != nil {
hlog.FromRequest(r).Error().Err(err).Msg("Sanity check failed")
}
privKeyBlock := pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: user.PrivateKeyRsa,
}
if err != nil {
hlog.FromRequest(r).Error().Err(err).Msg("Sanity check failed")
}
privKeyPem := pem.EncodeToMemory(&privKeyBlock)
pubKeyPen := []byte(shared.KeyBytesToPem(user.PublicKeyRsa, false))
err = shared.SanityCheckPemdRsaKeys(pubKeyPen, privKeyPem)
if err != nil {
hlog.FromRequest(r).Error().Err(err).Msg("Pem Sanity check failed")
}
fmt.Fprintf(w, "%s\n\n%s", privKeyPem, pubKeyPen)
}
func issueUserImport(w http.ResponseWriter, r *http.Request) {
target := r.FormValue("target")
_, err := activitypub.ImportRemoteAccountByHandle(target)
if err != nil {
hlog.FromRequest(r).Info().Err(err).Msg("Err from import request")
}
}
func proxyMessageToTarget(w http.ResponseWriter, r *http.Request) {
type Inbound struct {
From string `json:"from"`
Target string `json:"target"`
Message any `json:"message"`
}
log := hlog.FromRequest(r)
data := Inbound{}
dec := json.NewDecoder(r.Body)
err := dec.Decode(&data)
if err != nil {
log.Warn().Err(err).Msg("Failed to decode json body")
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
return
}
log.Debug().Any("data", data).Msg("Received message")
user, err := dbgen.User.GetByUsername(data.From)
if err != nil {
log.Error().Err(err).Msg("Failed to get user from storage")
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
return
}
targetId, err := activitypub.ImportRemoteAccountByHandle(data.Target)
if err != nil {
log.Error().Err(err).Msg("Failed to import target user")
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
return
}
target, err := dbgen.User.Where(dbgen.User.ID.Eq(targetId)).
Preload(dbgen.User.RemoteInfo).
First()
if err != nil {
log.Error().Err(err).Msg("Failed to get target user from db")
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
return
}
outBody, err := json.Marshal(data.Message)
if err != nil {
log.Error().Err(err).Msg("Failed to marshal out data")
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
return
}
log.Debug().Bytes("request-body", outBody).Msg("Body of proxied request")
response, err := webshared.RequestSignedCavage(
"POST",
target.RemoteInfo.InboxLink,
outBody,
user,
)
if err != nil {
log.Error().Err(err).Msg("Request failed")
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
return
}
defer response.Body.Close()
respBody, _ := io.ReadAll(response.Body)
log.Debug().
Int("status-code", response.StatusCode).
Bytes("body", respBody).
Msg("Response from message")
}
func kickoffFollow(w http.ResponseWriter, r *http.Request) {
type Inbound struct {
From string `json:"from"`
To string `json:"to"`
}
var data Inbound
dec := json.NewDecoder(r.Body)
dec.Decode(&data)
}