Compare commits

..

No commits in common. "f2616c041b80facb159432d2c8c7e0f004676d55" and "d272fa90b45762df9ab89467d90d3e59a75e3672" have entirely different histories.

22 changed files with 99 additions and 777 deletions

View file

@ -50,8 +50,6 @@ type ConfigAdmin struct {
// The password has to be supplied in the `password` GET form value for all requests // The password has to be supplied in the `password` GET form value for all requests
// to /profiling/* // to /profiling/*
ProfilingPassword string `toml:"profiling_password"` ProfilingPassword string `toml:"profiling_password"`
// Allow registration on the server
// If disabled, user must be manually created (currently via the debug server)
AllowRegistration bool `toml:"allow_registration"` AllowRegistration bool `toml:"allow_registration"`
} }
@ -105,22 +103,6 @@ type ConfigSelf struct {
ServerDisplayName string `toml:"server_display_name"` ServerDisplayName string `toml:"server_display_name"`
} }
// Contains experimental features that could be good to have
// but are either in an unstable implementation state
// or maybe not widely supported by other implementations
//
// All features controlled by this config section
// **MUST BE**
// disabled by default
type ConfigExperimental struct {
// Use ED25519 key pairs instead of RSA 2048
// ED25519 keys are shorter and safer, but might not be supported by other
// implementations
// Both are created and stored for each local user. If this flag is enabled,
// Linstrom shares the ED25519 key on request, otherwise the RSA key
UseEd25519Keys bool `toml:"use_ed25519_keys"`
}
type Config struct { type Config struct {
General ConfigGeneral `toml:"general"` General ConfigGeneral `toml:"general"`
SSL ConfigSSL `toml:"ssl"` SSL ConfigSSL `toml:"ssl"`
@ -130,7 +112,6 @@ type Config struct {
Mail ConfigMail `toml:"mail"` Mail ConfigMail `toml:"mail"`
Self ConfigSelf `toml:"self"` Self ConfigSelf `toml:"self"`
S3 ConfigS3 `toml:"s3"` S3 ConfigS3 `toml:"s3"`
Experimental ConfigExperimental `toml:"experimental"`
} }
var GlobalConfig Config var GlobalConfig Config
@ -197,9 +178,6 @@ var defaultConfig Config = Config{
Endpoint: "http://localhost:3900", Endpoint: "http://localhost:3900",
UseSSL: false, UseSSL: false,
}, },
Experimental: ConfigExperimental{
UseEd25519Keys: false,
},
} }
func (gc *ConfigGeneral) GetFullDomain() string { func (gc *ConfigGeneral) GetFullDomain() string {

View file

@ -44,6 +44,3 @@
endpoint = "http://localhost:3900" endpoint = "http://localhost:3900"
use_ssl = false use_ssl = false
bucket_name = "linstrom-bucket" bucket_name = "linstrom-bucket"
[experimental]
use_ed25519_keys = false

6
go.mod
View file

@ -5,7 +5,7 @@ go 1.23.0
toolchain go1.23.7 toolchain go1.23.7
require ( require (
git.mstar.dev/mstar/goutils v1.12.1 git.mstar.dev/mstar/goutils v1.12.0
github.com/BurntSushi/toml v1.4.0 github.com/BurntSushi/toml v1.4.0
github.com/dgraph-io/ristretto v0.2.0 github.com/dgraph-io/ristretto v0.2.0
github.com/eko/gocache/lib/v4 v4.1.6 github.com/eko/gocache/lib/v4 v4.1.6
@ -24,7 +24,6 @@ require (
github.com/rs/zerolog v1.33.0 github.com/rs/zerolog v1.33.0
golang.org/x/crypto v0.36.0 golang.org/x/crypto v0.36.0
golang.org/x/image v0.20.0 golang.org/x/image v0.20.0
golang.org/x/sys v0.32.0
gorm.io/driver/postgres v1.5.7 gorm.io/driver/postgres v1.5.7
gorm.io/gen v0.3.26 gorm.io/gen v0.3.26
gorm.io/gorm v1.25.12 gorm.io/gorm v1.25.12
@ -33,7 +32,6 @@ require (
require ( require (
filippo.io/edwards25519 v1.1.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect
git.mstar.dev/mstar/canvas v0.13.1 // indirect
git.mstar.dev/mstar/goap v1.2.5 // indirect git.mstar.dev/mstar/goap v1.2.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
@ -49,7 +47,6 @@ require (
github.com/go-webauthn/x v0.1.14 // indirect github.com/go-webauthn/x v0.1.14 // indirect
github.com/goccy/go-json v0.10.3 // indirect github.com/goccy/go-json v0.10.3 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/golang/mock v1.6.0 // indirect github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect github.com/golang/protobuf v1.5.4 // indirect
github.com/google/go-tpm v0.9.1 // indirect github.com/google/go-tpm v0.9.1 // indirect
@ -86,6 +83,7 @@ require (
golang.org/x/mod v0.17.0 // indirect golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.30.0 // indirect golang.org/x/net v0.30.0 // indirect
golang.org/x/sync v0.12.0 // indirect golang.org/x/sync v0.12.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/text v0.23.0 // indirect golang.org/x/text v0.23.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
google.golang.org/protobuf v1.33.0 // indirect google.golang.org/protobuf v1.33.0 // indirect

7
go.sum
View file

@ -33,8 +33,6 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
git.mstar.dev/mstar/canvas v0.13.1 h1:+oJRv3O+1vDOqMQFXfV6r+o2JiZGBARadlWXOrK6WUo=
git.mstar.dev/mstar/canvas v0.13.1/go.mod h1:CzLWCvOvHXsLbwU9l8WBL/RU5VAorgJ9+Ald5yhWoMs=
git.mstar.dev/mstar/goap v0.0.0-20250407153813-45fa095a1597 h1:KUdA5J1ArvD7X4z8ttE41xaCo+Hv/vWB8scoT+HIpOE= git.mstar.dev/mstar/goap v0.0.0-20250407153813-45fa095a1597 h1:KUdA5J1ArvD7X4z8ttE41xaCo+Hv/vWB8scoT+HIpOE=
git.mstar.dev/mstar/goap v0.0.0-20250407153813-45fa095a1597/go.mod h1:cVCXcMGdYk8pySulIYSqMuvGG6lYEkibM5pPy97gylQ= git.mstar.dev/mstar/goap v0.0.0-20250407153813-45fa095a1597/go.mod h1:cVCXcMGdYk8pySulIYSqMuvGG6lYEkibM5pPy97gylQ=
git.mstar.dev/mstar/goap v1.2.0 h1:jiWgU7zria+uMEq40lyvrNqofmw4Voe+sRboxxcnbZE= git.mstar.dev/mstar/goap v1.2.0 h1:jiWgU7zria+uMEq40lyvrNqofmw4Voe+sRboxxcnbZE=
@ -59,8 +57,6 @@ git.mstar.dev/mstar/goutils v1.11.1 h1:G21MjZzQDnpC7h+ZkfITbqX+jBQtqZ4FB7rj4K6id
git.mstar.dev/mstar/goutils v1.11.1/go.mod h1:juxY0eZEMnA95fedRp2LVXvUBgEjz66nE8SEdGKcxMA= git.mstar.dev/mstar/goutils v1.11.1/go.mod h1:juxY0eZEMnA95fedRp2LVXvUBgEjz66nE8SEdGKcxMA=
git.mstar.dev/mstar/goutils v1.12.0 h1:d88hLS8KnLUCI+8aWBR6228M43hxHdJpj8WuSqm4LAM= git.mstar.dev/mstar/goutils v1.12.0 h1:d88hLS8KnLUCI+8aWBR6228M43hxHdJpj8WuSqm4LAM=
git.mstar.dev/mstar/goutils v1.12.0/go.mod h1:juxY0eZEMnA95fedRp2LVXvUBgEjz66nE8SEdGKcxMA= git.mstar.dev/mstar/goutils v1.12.0/go.mod h1:juxY0eZEMnA95fedRp2LVXvUBgEjz66nE8SEdGKcxMA=
git.mstar.dev/mstar/goutils v1.12.1 h1:HZKKzMNfx7JKSUi5s8SwwUFEqEX6xvkM6NMf+Pht+lo=
git.mstar.dev/mstar/goutils v1.12.1/go.mod h1:juxY0eZEMnA95fedRp2LVXvUBgEjz66nE8SEdGKcxMA=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
@ -154,8 +150,6 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0kt
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -398,7 +392,6 @@ golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20200801110659-972c09e46d76/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw= golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw=
golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM= golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=

View file

@ -160,6 +160,6 @@ func newServer() {
}() }()
} }
log.Info().Msg("Starting public server") log.Info().Msg("Starting public server")
public := webpublic.New(":8080", &defaultDuck) public := webpublic.New(":8080")
public.Start() public.Start()
} }

View file

@ -1,100 +0,0 @@
package identicon
import (
"git.mstar.dev/mstar/canvas"
mathutils "git.mstar.dev/mstar/goutils/math"
)
type vec2 struct {
X, Y float64
}
type block struct {
canvas *canvas.Canvas
blockType int
primary string
secondary string
hash int
pos vec2
cellSize int
margin int
scale float64
}
func (b *block) Offset() {
b.canvas.Save()
b.canvas.Translate(0.6*b.scale, -0.6*b.scale)
}
func (b *block) ResetOffset() {
b.canvas.Restore()
}
func (b *block) MakePath(hash int, offset int) {
mod := mathutils.Abs(hash+offset) % 4
switch mod {
case 0:
// Top
b.canvas.BeginPath()
b.canvas.MoveTo(float64(b.pos.X), float64(b.pos.Y))
b.canvas.LineTo(b.pos.X+float64(int(b.cellSize)), float64(b.pos.Y))
b.canvas.LineTo(b.pos.X+float64(int(b.cellSize)), b.pos.Y+float64(int(b.cellSize)))
b.canvas.ClosePath()
case 1:
// Right
b.canvas.BeginPath()
b.canvas.MoveTo(b.pos.X+float64(int(b.cellSize)), float64(b.pos.Y))
b.canvas.LineTo(b.pos.X+float64(int(b.cellSize)), b.pos.Y+float64(int(b.cellSize)))
b.canvas.LineTo(float64(b.pos.X), b.pos.Y+float64(int(b.cellSize)))
b.canvas.ClosePath()
case 2:
// Bottom
b.canvas.BeginPath()
b.canvas.MoveTo(float64(b.pos.X), float64(b.pos.Y))
b.canvas.LineTo(float64(b.pos.X), b.pos.Y+float64(int(b.cellSize)))
b.canvas.LineTo(b.pos.X+float64(int(b.cellSize)), b.pos.Y+float64(int(b.cellSize)))
b.canvas.ClosePath()
case 3:
// Left
b.canvas.BeginPath()
b.canvas.MoveTo(float64(b.pos.X), float64(b.pos.Y))
b.canvas.LineTo(b.pos.X+float64(int(b.cellSize)), float64(b.pos.Y))
b.canvas.LineTo(float64(b.pos.X), b.pos.Y+float64(int(b.cellSize)))
b.canvas.ClosePath()
default:
// Top
b.canvas.BeginPath()
b.canvas.MoveTo(float64(b.pos.X), float64(b.pos.Y))
b.canvas.LineTo(b.pos.X+float64(int(b.cellSize)), float64(b.pos.Y))
b.canvas.LineTo(b.pos.X+float64(int(b.cellSize)), b.pos.Y+float64(int(b.cellSize)))
b.canvas.ClosePath()
}
}
func (b *block) Draw() {
b.Offset()
switch b.blockType {
case 1:
b.MakePath(b.hash, b.hash%3)
b.canvas.SetFillStyle(b.primary)
b.canvas.Fill()
b.MakePath(b.hash, b.hash%5)
b.canvas.SetFillStyle(b.secondary)
b.canvas.Fill()
case 2:
b.MakePath(b.hash, b.hash%4)
b.canvas.SetFillStyle(b.secondary)
b.canvas.Fill()
b.MakePath(b.hash, b.hash%3)
b.canvas.SetFillStyle(b.primary)
b.canvas.Fill()
default:
b.MakePath(b.hash, b.hash%7)
b.canvas.SetFillStyle(b.secondary)
b.canvas.Fill()
b.MakePath(b.hash, b.hash%8)
b.canvas.SetFillStyle(b.primary)
b.canvas.Fill()
}
b.ResetOffset()
}

View file

@ -1,218 +0,0 @@
package identicon
import (
"errors"
"fmt"
"image"
"math"
"strings"
"git.mstar.dev/mstar/canvas"
"git.mstar.dev/mstar/canvas/backend/softwarebackend"
mathutils "git.mstar.dev/mstar/goutils/math"
"git.mstar.dev/mstar/goutils/sliceutils"
)
type identicon struct {
canvas *canvas.Canvas
primary string
secondary string
margin int
scale float64
cellSize int
hash int
blocks []block
shape shape
hasStroke bool
strokeWeight int
strokeColor string
compositeOperation string
palette []string
}
const canvasSize = 500
var (
palette = []string{
"#F44336", "#E91E63", "#9C27B0", "#673AB7", "#3F51B5", "#2196F3",
"#03A9F4", "#00BCD4", "#009688", "#4CAF50", "#8BC34A", "#CDDC39",
"#FFEB3B", "#FFC107", "#FF9800", "#FF5722", "#795548", "#607D8B",
}
validOperations = []string{
"source-over", "source-in", "source-out", "source-atop", "destination-over",
"destination-in", "destination-out", "destination-atop", "lighter", "copy",
"xor", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge",
"color-burn", "hard-light", "soft-light", "difference", "exclusion", "hue",
"saturation", "color", "luminosity",
}
)
// Deprecated: Doesn't work
func GenerateIdenticon(text string) *image.RGBA {
backend := softwarebackend.New(canvasSize, canvasSize)
canvas := canvas.New(backend)
i := identicon{
canvas: canvas,
hash: hash(text),
}
i.Init()
i.Draw()
tmp := *backend.Image
return &tmp
}
func hash(text string) int {
runes := []rune(text)
return sliceutils.Compact(
sliceutils.Map(runes, func(t rune) int { return int(t) }),
func(acc, next int) int {
acc = ((acc << 5) - acc) + next
return acc & acc
},
)
}
func (i *identicon) SetUsername(username string) {
i.hash = hash(username)
}
func (i *identicon) SetPalette(pal []string) error {
if len(pal) < 2 {
return errors.New("palette to small")
}
i.palette = pal
return nil
}
func (i *identicon) SetCompositeOperation(newOp string) error {
newOp = strings.ToLower(newOp)
if !sliceutils.Contains(validOperations, newOp) {
return errors.New("invalid operation")
}
i.compositeOperation = newOp
return nil
}
func (i *identicon) Draw() {
i.Init()
i.canvas.Save()
i.canvas.Translate(float64(canvasSize/2), float64(canvasSize/2))
i.canvas.Rotate(-math.Pi / 4)
i.canvas.Translate(float64(canvasSize/2), float64(canvasSize/2))
i.GenerateBlocks()
i.DrawBlocks()
if i.hasStroke {
i.DrawOutline()
}
i.shape = shape{
canvas: i.canvas,
hash: i.hash,
primary: i.primary,
secondary: i.secondary,
pos: vec2{
X: float64(i.margin*int(i.scale)) + 1.5*float64(i.cellSize),
Y: float64(i.margin)*i.scale + 0.5*float64(i.cellSize),
},
scale: i.scale,
cellSize: i.cellSize,
strokeColor: i.strokeColor,
}
i.shape.Draw(i.hasStroke, i.strokeWeight)
i.canvas.Restore()
}
func (i *identicon) Init() {
i.blocks = []block{}
i.shape = shape{}
i.primary = palette[mathutils.Abs(i.hash%len(palette))]
i.secondary = palette[mathutils.Abs(hash(fmt.Sprint(i.hash))%len(palette))]
// i.canvas.SetGlobalCompositeOperation("source-over")
i.canvas.ClearRect(0, 0, canvasSize, canvasSize)
// i.canvas.SetGlobalCompositeOperation(i.compositeOperation)
}
func (i *identicon) Offset() {
i.canvas.Save()
i.canvas.Translate(0.6*i.scale, -0.6*i.scale)
}
func (i *identicon) ResetOffset() {
i.canvas.Restore()
}
func (i *identicon) GenerateBlocks() {
i.blocks = append(i.blocks, block{
canvas: i.canvas,
blockType: 1,
primary: i.primary,
secondary: i.secondary,
hash: i.hash,
pos: vec2{float64(i.margin) * i.scale, float64(i.margin) * i.scale},
cellSize: i.cellSize,
margin: i.margin,
scale: i.scale,
}, block{
canvas: i.canvas,
blockType: 2,
primary: i.primary,
secondary: i.primary,
hash: i.hash,
pos: vec2{float64(i.margin) * i.scale, float64(i.canvas.Height()) / 2},
cellSize: i.cellSize,
margin: i.margin,
scale: i.scale,
}, block{
canvas: i.canvas,
blockType: 3,
primary: i.primary,
secondary: i.secondary,
hash: i.hash,
pos: vec2{float64(i.canvas.Width()) / 2, float64(i.canvas.Height()) / 2},
cellSize: i.cellSize,
margin: i.margin,
scale: i.scale,
},
)
}
func (i *identicon) DrawOutline() {
i.Offset()
// i.canvas.SetGlobalCompositeOperation("source-over")
i.canvas.BeginPath()
i.canvas.MoveTo(float64(i.margin)*i.scale, float64(i.margin)*i.scale)
i.canvas.LineTo(float64(i.margin)*i.scale, float64(canvasSize)-float64(i.margin)*i.scale)
i.canvas.LineTo(
float64(i.canvas.Width())-float64(i.margin)*i.scale,
float64(i.canvas.Height())-float64(i.margin)*i.scale,
)
i.canvas.LineTo(
float64(i.canvas.Width())-float64(i.margin)*i.scale,
float64(i.canvas.Height())/2,
)
i.canvas.LineTo(float64(i.canvas.Width())/2, float64(i.canvas.Height())/2)
i.canvas.LineTo(float64(i.canvas.Width())/2, float64(i.margin)*i.scale)
i.canvas.ClosePath()
i.canvas.SetStrokeStyle(i.strokeColor)
i.canvas.SetLineWidth(i.scale * (float64(i.strokeWeight) / float64(i.canvas.Width())))
i.canvas.SetLineJoin(canvas.Round)
i.canvas.SetLineCap(canvas.Round)
i.canvas.Stroke()
i.canvas.BeginPath()
i.canvas.MoveTo(float64(i.canvas.Width())/2, float64(i.canvas.Height())/2)
i.canvas.LineTo(float64(i.margin)*i.scale, float64(i.canvas.Height())/2)
i.canvas.MoveTo(float64(i.canvas.Width())/2, float64(i.canvas.Height())/2)
i.canvas.LineTo(
float64(i.canvas.Width())/2,
float64(i.canvas.Height())-(float64(i.margin)*i.scale),
)
i.canvas.Stroke()
i.ResetOffset()
}
func (i *identicon) DrawBlocks() {
for _, b := range i.blocks {
b.Draw()
}
}

View file

@ -1,112 +0,0 @@
package identicon
import (
"math"
"git.mstar.dev/mstar/canvas"
mathutils "git.mstar.dev/mstar/goutils/math"
)
type shape struct {
canvas *canvas.Canvas
hash int
primary string
secondary string
pos vec2
scale float64
cellSize int
strokeColor string
}
func (s *shape) GetColor() string {
if mathutils.Abs(s.hash) == 0 {
return s.primary
} else {
return s.secondary
}
}
func (s *shape) MakePath() {
mod := mathutils.Abs(s.hash+1) % 4
switch mod {
case 0:
// square
s.canvas.BeginPath()
s.canvas.MoveTo(float64(s.pos.X), float64(s.pos.Y))
s.canvas.LineTo(float64(s.pos.X+float64(s.cellSize/2)), float64(s.pos.Y))
s.canvas.LineTo(
float64(s.pos.X+float64(s.cellSize/2)),
float64(s.pos.Y-float64(s.cellSize/2)),
)
s.canvas.LineTo(float64(s.pos.X), float64(s.pos.Y-float64(s.cellSize/2)))
s.canvas.ClosePath()
case 1:
// circle
s.canvas.BeginPath()
s.canvas.Arc(
float64(s.pos.X)+float64(s.cellSize)/math.Pi-5,
float64(s.pos.Y)-float64(s.cellSize)/math.Pi+5,
float64(s.cellSize)/3,
0,
math.Pi*2,
true,
)
case 2:
// triangle
s.canvas.BeginPath()
s.canvas.MoveTo(float64(s.pos.X), float64(s.pos.Y))
s.canvas.LineTo(float64(s.pos.X)+float64(s.cellSize)*0.65, float64(s.pos.Y))
s.canvas.LineTo(float64(s.pos.X), float64(s.pos.Y)-float64(s.cellSize)*0.65)
s.canvas.ClosePath()
case 3:
// oval
s.canvas.BeginPath()
s.canvas.MoveTo(
float64(s.pos.X)-float64(s.cellSize)*0.2,
float64(s.pos.Y)+float64(s.cellSize)*0.2,
)
s.canvas.QuadraticCurveTo(
float64(s.pos.X)+float64(s.cellSize)*0.4,
float64(s.pos.Y),
float64(s.pos.X)+float64(s.cellSize)*0.5,
float64(s.pos.Y)-float64(s.cellSize)*0.5,
)
s.canvas.MoveTo(
float64(s.pos.X)+float64(s.cellSize)*0.5,
float64(s.pos.Y)-float64(s.cellSize)*0.5,
)
s.canvas.QuadraticCurveTo(
float64(s.pos.X),
float64(s.pos.Y)-float64(s.cellSize)*0.4,
float64(s.pos.X)-float64(s.cellSize)*0.2,
float64(s.pos.Y)+float64(s.cellSize)*0.2,
)
default:
// square
s.canvas.BeginPath()
s.canvas.MoveTo(float64(s.pos.X), float64(s.pos.Y))
s.canvas.LineTo(float64(s.pos.X+float64(s.cellSize/2)), float64(s.pos.Y))
s.canvas.LineTo(
float64(s.pos.X+float64(s.cellSize/2)),
float64(s.pos.Y-float64(s.cellSize/2)),
)
s.canvas.LineTo(float64(s.pos.X), float64(s.pos.Y-float64(s.cellSize/2)))
s.canvas.ClosePath()
}
}
func (s *shape) Draw(hasStroke bool, strokeWeight int) {
color := s.GetColor()
// THIS SHIT ISN'T IMPLEMENTED! I hope it will work regardless
// s.canvas.SetGlobalCompositeOperation("source-over")
s.MakePath()
s.canvas.SetFillStyle(color)
s.canvas.SetStrokeStyle(s.strokeColor)
s.canvas.SetLineWidth(s.scale * (4 / 5 * float64(strokeWeight)) / float64(canvasSize))
s.canvas.SetLineJoin(canvas.Round)
s.canvas.SetLineCap(canvas.Round)
s.canvas.Fill()
if hasStroke {
s.canvas.Stroke()
}
}

1
shared/rsaKey.go Normal file
View file

@ -0,0 +1 @@
package shared

View file

@ -1,49 +0,0 @@
package shared
import (
"crypto"
"crypto/ed25519"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
)
func GenerateKeypair(useEd bool) (publicKey []byte, privateKey []byte, err error) {
if useEd {
publicKey, privateKey, err := ed25519.GenerateKey(nil)
if err != nil {
return nil, nil, err
}
publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey)
return publicKeyBytes, privateKey, nil
} else {
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, err
}
privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)
publicKeyBytes, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)
if err != nil {
return nil, nil, err
}
return publicKeyBytes, privateKeyBytes, nil
}
}
func Sign(toSign string, keyBytes []byte, keyIsRsa bool) ([]byte, error) {
if keyIsRsa {
key, err := x509.ParsePKCS1PrivateKey(keyBytes)
if err != nil {
return nil, err
}
hash := sha256.Sum256([]byte(toSign))
signed, err := key.Sign(rand.Reader, hash[:], crypto.SHA256)
return signed, err
} else {
key := ed25519.PrivateKey(keyBytes)
signed, err := key.Sign(rand.Reader, []byte(toSign), crypto.SHA256)
return signed, err
}
}

View file

@ -40,16 +40,14 @@ func newUser(db *gorm.DB, opts ...gen.DOOption) user {
_user.BackgroundId = field.NewField(tableName, "background_id") _user.BackgroundId = field.NewField(tableName, "background_id")
_user.BannerId = field.NewField(tableName, "banner_id") _user.BannerId = field.NewField(tableName, "banner_id")
_user.Indexable = field.NewBool(tableName, "indexable") _user.Indexable = field.NewBool(tableName, "indexable")
_user.PublicKeyRsa = field.NewBytes(tableName, "public_key_rsa") _user.PublicKey = field.NewBytes(tableName, "public_key")
_user.PublicKeyEd = field.NewBytes(tableName, "public_key_ed")
_user.RestrictedFollow = field.NewBool(tableName, "restricted_follow") _user.RestrictedFollow = field.NewBool(tableName, "restricted_follow")
_user.Location = field.NewField(tableName, "location") _user.Location = field.NewField(tableName, "location")
_user.Birthday = field.NewField(tableName, "birthday") _user.Birthday = field.NewField(tableName, "birthday")
_user.Verified = field.NewBool(tableName, "verified") _user.Verified = field.NewBool(tableName, "verified")
_user.PasskeyId = field.NewBytes(tableName, "passkey_id") _user.PasskeyId = field.NewBytes(tableName, "passkey_id")
_user.FinishedRegistration = field.NewBool(tableName, "finished_registration") _user.FinishedRegistration = field.NewBool(tableName, "finished_registration")
_user.PrivateKeyRsa = field.NewBytes(tableName, "private_key_rsa") _user.PrivateKey = field.NewBytes(tableName, "private_key")
_user.PrivateKeyEd = field.NewBytes(tableName, "private_key_ed")
_user.RemoteInfo = userHasOneRemoteInfo{ _user.RemoteInfo = userHasOneRemoteInfo{
db: db.Session(&gorm.Session{}), db: db.Session(&gorm.Session{}),
@ -355,16 +353,14 @@ type user struct {
BackgroundId field.Field BackgroundId field.Field
BannerId field.Field BannerId field.Field
Indexable field.Bool Indexable field.Bool
PublicKeyRsa field.Bytes PublicKey field.Bytes
PublicKeyEd field.Bytes
RestrictedFollow field.Bool RestrictedFollow field.Bool
Location field.Field Location field.Field
Birthday field.Field Birthday field.Field
Verified field.Bool Verified field.Bool
PasskeyId field.Bytes PasskeyId field.Bytes
FinishedRegistration field.Bool FinishedRegistration field.Bool
PrivateKeyRsa field.Bytes PrivateKey field.Bytes
PrivateKeyEd field.Bytes
RemoteInfo userHasOneRemoteInfo RemoteInfo userHasOneRemoteInfo
InfoFields userHasManyInfoFields InfoFields userHasManyInfoFields
@ -417,16 +413,14 @@ func (u *user) updateTableName(table string) *user {
u.BackgroundId = field.NewField(table, "background_id") u.BackgroundId = field.NewField(table, "background_id")
u.BannerId = field.NewField(table, "banner_id") u.BannerId = field.NewField(table, "banner_id")
u.Indexable = field.NewBool(table, "indexable") u.Indexable = field.NewBool(table, "indexable")
u.PublicKeyRsa = field.NewBytes(table, "public_key_rsa") u.PublicKey = field.NewBytes(table, "public_key")
u.PublicKeyEd = field.NewBytes(table, "public_key_ed")
u.RestrictedFollow = field.NewBool(table, "restricted_follow") u.RestrictedFollow = field.NewBool(table, "restricted_follow")
u.Location = field.NewField(table, "location") u.Location = field.NewField(table, "location")
u.Birthday = field.NewField(table, "birthday") u.Birthday = field.NewField(table, "birthday")
u.Verified = field.NewBool(table, "verified") u.Verified = field.NewBool(table, "verified")
u.PasskeyId = field.NewBytes(table, "passkey_id") u.PasskeyId = field.NewBytes(table, "passkey_id")
u.FinishedRegistration = field.NewBool(table, "finished_registration") u.FinishedRegistration = field.NewBool(table, "finished_registration")
u.PrivateKeyRsa = field.NewBytes(table, "private_key_rsa") u.PrivateKey = field.NewBytes(table, "private_key")
u.PrivateKeyEd = field.NewBytes(table, "private_key_ed")
u.fillFieldMap() u.fillFieldMap()
@ -443,7 +437,7 @@ func (u *user) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
} }
func (u *user) fillFieldMap() { func (u *user) fillFieldMap() {
u.fieldMap = make(map[string]field.Expr, 35) u.fieldMap = make(map[string]field.Expr, 33)
u.fieldMap["id"] = u.ID u.fieldMap["id"] = u.ID
u.fieldMap["username"] = u.Username u.fieldMap["username"] = u.Username
u.fieldMap["created_at"] = u.CreatedAt u.fieldMap["created_at"] = u.CreatedAt
@ -457,16 +451,14 @@ func (u *user) fillFieldMap() {
u.fieldMap["background_id"] = u.BackgroundId u.fieldMap["background_id"] = u.BackgroundId
u.fieldMap["banner_id"] = u.BannerId u.fieldMap["banner_id"] = u.BannerId
u.fieldMap["indexable"] = u.Indexable u.fieldMap["indexable"] = u.Indexable
u.fieldMap["public_key_rsa"] = u.PublicKeyRsa u.fieldMap["public_key"] = u.PublicKey
u.fieldMap["public_key_ed"] = u.PublicKeyEd
u.fieldMap["restricted_follow"] = u.RestrictedFollow u.fieldMap["restricted_follow"] = u.RestrictedFollow
u.fieldMap["location"] = u.Location u.fieldMap["location"] = u.Location
u.fieldMap["birthday"] = u.Birthday u.fieldMap["birthday"] = u.Birthday
u.fieldMap["verified"] = u.Verified u.fieldMap["verified"] = u.Verified
u.fieldMap["passkey_id"] = u.PasskeyId u.fieldMap["passkey_id"] = u.PasskeyId
u.fieldMap["finished_registration"] = u.FinishedRegistration u.fieldMap["finished_registration"] = u.FinishedRegistration
u.fieldMap["private_key_rsa"] = u.PrivateKeyRsa u.fieldMap["private_key"] = u.PrivateKey
u.fieldMap["private_key_ed"] = u.PrivateKeyEd
} }

View file

@ -49,8 +49,7 @@ type User struct {
Banner *MediaMetadata ` json:"-"` Banner *MediaMetadata ` json:"-"`
BannerId sql.NullString ` json:"banner_id"` // ID of a media file used as banner BannerId sql.NullString ` json:"banner_id"` // ID of a media file used as banner
Indexable bool ` json:"indexable"` // Whether this account can be found by crawlers Indexable bool ` json:"indexable"` // Whether this account can be found by crawlers
PublicKeyRsa []byte ` json:"public_key_rsa"` // The public RSA key of the account PublicKey []byte ` json:"public_key"` // The public key of the account
PublicKeyEd []byte ` json:"public_key_ed"` // The public Ed25519 key of the account
// Whether this account restricts following // Whether this account restricts following
// If true, the owner must approve of a follow request first // If true, the owner must approve of a follow request first
RestrictedFollow bool ` json:"restricted_follow"` RestrictedFollow bool ` json:"restricted_follow"`
@ -67,8 +66,7 @@ type User struct {
// saved space is worth // saved space is worth
PasskeyId []byte `json:"-"` PasskeyId []byte `json:"-"`
FinishedRegistration bool `json:"-"` // Whether this account has completed registration yet FinishedRegistration bool `json:"-"` // Whether this account has completed registration yet
PrivateKeyRsa []byte `json:"-"` PrivateKey []byte `json:"-"`
PrivateKeyEd []byte `json:"-"`
// ---- "Remote" linked values // ---- "Remote" linked values
InfoFields []UserInfoField `json:"-"` InfoFields []UserInfoField `json:"-"`

View file

@ -2,6 +2,8 @@ package storage
import ( import (
"crypto/rand" "crypto/rand"
"crypto/rsa"
"crypto/x509"
"database/sql" "database/sql"
"git.mstar.dev/mstar/goutils/other" "git.mstar.dev/mstar/goutils/other"
@ -9,7 +11,6 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
"git.mstar.dev/mstar/linstrom/config" "git.mstar.dev/mstar/linstrom/config"
"git.mstar.dev/mstar/linstrom/shared"
"git.mstar.dev/mstar/linstrom/storage-new/dbgen" "git.mstar.dev/mstar/linstrom/storage-new/dbgen"
"git.mstar.dev/mstar/linstrom/storage-new/models" "git.mstar.dev/mstar/linstrom/storage-new/models"
) )
@ -26,7 +27,7 @@ func InsertSelf() error {
if err != nil { if err != nil {
return other.Error("storage", "failed to save/update self server", err) return other.Error("storage", "failed to save/update self server", err)
} }
user, err := insertUser(server, duck) user, err := insertUser(server)
if err != nil { if err != nil {
return other.Error("storage", "failed to save/update self user", err) return other.Error("storage", "failed to save/update self user", err)
} }
@ -94,10 +95,7 @@ func insertServer(duck *models.MediaMetadata) (*models.RemoteServer, error) {
return &server, nil return &server, nil
} }
func insertUser( func insertUser(server *models.RemoteServer) (*models.User, error) {
server *models.RemoteServer,
duckMedia *models.MediaMetadata,
) (*models.User, error) {
dbUser, err := dbgen.User.GetByUsername("linstrom") dbUser, err := dbgen.User.GetByUsername("linstrom")
if err == nil { if err == nil {
return dbUser, nil return dbUser, nil
@ -105,14 +103,16 @@ func insertUser(
if err != gorm.ErrRecordNotFound { if err != gorm.ErrRecordNotFound {
return nil, err return nil, err
} }
publicEdKeyBytes, privateEdKeyBytes, err := shared.GenerateKeypair(true) // publicKey, privateKey, err := ed25519.GenerateKey(nil)
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil { if err != nil {
return nil, err return nil, err
} }
publicRsaKeyBytes, privateRsaKeyBytes, err := shared.GenerateKeypair(false) if err = privateKey.Validate(); err != nil {
if err != nil {
return nil, err return nil, err
} }
privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)
publicKeyBytes := x509.MarshalPKCS1PublicKey(&privateKey.PublicKey)
pkeyId := make([]byte, 64) pkeyId := make([]byte, 64)
_, err = rand.Read(pkeyId) _, err = rand.Read(pkeyId)
if err != nil { if err != nil {
@ -129,17 +129,15 @@ func insertUser(
DisplayName: config.GlobalConfig.Self.ServerActorDisplayName, DisplayName: config.GlobalConfig.Self.ServerActorDisplayName,
Description: "The default linstrom server user", Description: "The default linstrom server user",
IsBot: true, IsBot: true,
Icon: duckMedia, Icon: nil,
IconId: sql.NullString{Valid: true, String: duckMedia.ID}, IconId: sql.NullString{Valid: false},
Background: nil, Background: nil,
BackgroundId: sql.NullString{Valid: false}, BackgroundId: sql.NullString{Valid: false},
Banner: nil, Banner: nil,
BannerId: sql.NullString{Valid: false}, BannerId: sql.NullString{Valid: false},
Indexable: false, Indexable: false,
PublicKeyEd: publicEdKeyBytes, PublicKey: publicKeyBytes,
PrivateKeyEd: privateEdKeyBytes, PrivateKey: privateKeyBytes,
PublicKeyRsa: publicRsaKeyBytes,
PrivateKeyRsa: privateRsaKeyBytes,
Verified: true, Verified: true,
FinishedRegistration: true, FinishedRegistration: true,
PasskeyId: pkeyId, PasskeyId: pkeyId,

View file

@ -4,9 +4,10 @@ import (
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"net/http" "net/http"
webutils "git.mstar.dev/mstar/goutils/http" httputils "git.mstar.dev/mstar/goutils/http"
"git.mstar.dev/mstar/goutils/sliceutils" "git.mstar.dev/mstar/goutils/sliceutils"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"gorm.io/gorm" "gorm.io/gorm"
@ -26,28 +27,15 @@ func postAs(w http.ResponseWriter, r *http.Request) {
data := Inbound{} data := Inbound{}
err := dec.Decode(&data) err := dec.Decode(&data)
if err != nil { if err != nil {
webutils.ProblemDetails( httputils.HttpErr(w, 0, "json decode failed", http.StatusBadRequest)
w,
http.StatusBadRequest,
"/errors/bad-request-data",
"bad request data",
nil,
map[string]any{
"sample": Inbound{
Username: "bob",
Content: "Heya there, this is sample data",
},
},
)
return return
} }
user, err := dbgen.User.GetByUsername(data.Username) user, err := dbgen.User.GetByUsername(data.Username)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
webutils.ProblemDetailsStatusOnly(w, http.StatusNotFound) httputils.HttpErr(w, 0, "no user with that name", http.StatusNotFound)
} else { } else {
log.Error().Err(err).Str("name", data.Username).Msg("Failed to find user") log.Error().Err(err).Str("name", data.Username).Msg("Failed to find user")
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
} }
return return
} }
@ -81,13 +69,13 @@ func notesFrom(w http.ResponseWriter, r *http.Request) {
if err != nil { if err != nil {
log.Error().Err(err).Str("name", username).Msg("Failed to get user") log.Error().Err(err).Str("name", username).Msg("Failed to get user")
storage.HandleReconnectError(err) storage.HandleReconnectError(err)
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError) httputils.HttpErr(w, 0, "failed to get user", http.StatusInternalServerError)
return return
} }
notes, err := dbgen.Note.GetNotesPaged(user.ID, 0, uint8(models.NOTE_TARGET_PUBLIC)) notes, err := dbgen.Note.GetNotesPaged(user.ID, 0, uint8(models.NOTE_TARGET_PUBLIC))
if err != nil { if err != nil {
log.Error().Err(err).Str("name", username).Msg("Failed to get notes") log.Error().Err(err).Str("name", username).Msg("Failed to get notes")
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError) httputils.HttpErr(w, 0, "failed to get notes", http.StatusInternalServerError)
return return
} }
publicNotes := sliceutils.Map(notes, func(t models.Note) webshared.Note { publicNotes := sliceutils.Map(notes, func(t models.Note) webshared.Note {
@ -95,5 +83,11 @@ func notesFrom(w http.ResponseWriter, r *http.Request) {
n.FromModel(&t) n.FromModel(&t)
return n return n
}) })
webutils.SendJson(w, publicNotes) jsonNotes, err := json.Marshal(publicNotes)
if err != nil {
log.Error().Err(err).Msg("Failed to marshal notes")
httputils.HttpErr(w, 0, "failed to marshal", http.StatusInternalServerError)
return
}
fmt.Fprint(w, string(jsonNotes))
} }

View file

@ -2,18 +2,19 @@ package webdebug
import ( import (
"crypto/rand" "crypto/rand"
"crypto/rsa"
"crypto/x509"
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"fmt"
"net/http" "net/http"
"strconv" "strconv"
"time" "time"
webutils "git.mstar.dev/mstar/goutils/http" httputils "git.mstar.dev/mstar/goutils/http"
"git.mstar.dev/mstar/goutils/other"
"git.mstar.dev/mstar/goutils/sliceutils" "git.mstar.dev/mstar/goutils/sliceutils"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"git.mstar.dev/mstar/linstrom/shared"
"git.mstar.dev/mstar/linstrom/storage-new/dbgen" "git.mstar.dev/mstar/linstrom/storage-new/dbgen"
"git.mstar.dev/mstar/linstrom/storage-new/models" "git.mstar.dev/mstar/linstrom/storage-new/models"
webshared "git.mstar.dev/mstar/linstrom/web/shared" webshared "git.mstar.dev/mstar/linstrom/web/shared"
@ -26,27 +27,25 @@ func getNonDeletedUsers(w http.ResponseWriter, r *http.Request) {
var err error var err error
page, err = strconv.Atoi(pageStr) page, err = strconv.Atoi(pageStr)
if err != nil { if err != nil {
webutils.HttpErr(w, 0, "page is not a number", http.StatusBadRequest) httputils.HttpErr(w, 0, "page is not a number", http.StatusBadRequest)
return return
} }
} }
users, err := dbgen.User.GetPagedAllNonDeleted(uint(page)) users, err := dbgen.User.GetPagedAllNonDeleted(uint(page))
if err != nil { if err != nil {
webutils.ProblemDetails( httputils.HttpErr(w, 0, "failed to get users", http.StatusInternalServerError)
w,
http.StatusInternalServerError,
"/errors/db-failure",
"database failure",
nil,
nil,
)
return return
} }
webutils.SendJson(w, sliceutils.Map(users, func(t models.User) webshared.User { marshalled, err := json.Marshal(sliceutils.Map(users, func(t models.User) webshared.User {
u := webshared.User{} u := webshared.User{}
u.FromModel(&t) u.FromModel(&t)
return u return u
})) }))
if err != nil {
httputils.HttpErr(w, 0, "failed to marshal users", http.StatusInternalServerError)
return
}
fmt.Fprint(w, string(marshalled))
} }
func createLocalUser(w http.ResponseWriter, r *http.Request) { func createLocalUser(w http.ResponseWriter, r *http.Request) {
@ -62,43 +61,19 @@ func createLocalUser(w http.ResponseWriter, r *http.Request) {
data := Inbound{} data := Inbound{}
err := jsonDecoder.Decode(&data) err := jsonDecoder.Decode(&data)
if err != nil { if err != nil {
webutils.ProblemDetails( httputils.HttpErr(w, 0, "decode failed", http.StatusBadRequest)
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 return
} }
publicKeyEdBytes, privateKeyEdBytes, err := shared.GenerateKeypair(true) // publicKey, privateKey, err := ed25519.GenerateKey(nil)
if err != nil { privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
log.Error().Err(err).Msg("Failed to generate and marshal public key") privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError) publicKeyBytes := x509.MarshalPKCS1PublicKey(&privateKey.PublicKey)
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) pkeyId := make([]byte, 64)
_, err = rand.Read(pkeyId) _, err = rand.Read(pkeyId)
if err != nil { if err != nil {
log.Error().Err(err).Msg("Failed to generate passkey id") log.Error().Err(err).Msg("Failed to generate passkey id")
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError) httputils.HttpErr(w, 0, "failed to generate passkey id", http.StatusInternalServerError)
return return
} }
@ -109,10 +84,8 @@ func createLocalUser(w http.ResponseWriter, r *http.Request) {
u.Description, u.Description,
u.IsBot, u.IsBot,
u.ServerId, u.ServerId,
u.PrivateKeyEd, u.PrivateKey,
u.PublicKeyEd, u.PublicKey,
u.PrivateKeyRsa,
u.PublicKeyRsa,
u.PasskeyId, u.PasskeyId,
) )
if data.Birthday != nil { if data.Birthday != nil {
@ -127,10 +100,8 @@ func createLocalUser(w http.ResponseWriter, r *http.Request) {
Description: data.Description, Description: data.Description,
IsBot: data.IsBot, IsBot: data.IsBot,
ServerId: 1, // Hardcoded, Self is always first ID ServerId: 1, // Hardcoded, Self is always first ID
PublicKeyRsa: publicKeyRsaBytes, PublicKey: publicKeyBytes,
PublicKeyEd: publicKeyEdBytes, PrivateKey: privateKeyBytes,
PrivateKeyRsa: privateKeyRsaBytes,
PrivateKeyEd: privateKeyEdBytes,
PasskeyId: pkeyId, PasskeyId: pkeyId,
} }
if data.Birthday != nil { if data.Birthday != nil {
@ -141,7 +112,7 @@ func createLocalUser(w http.ResponseWriter, r *http.Request) {
} }
if err = u.Create(&user); err != nil { if err = u.Create(&user); err != nil {
log.Error().Err(err).Msg("failed to create new local user") log.Error().Err(err).Msg("failed to create new local user")
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError) httputils.HttpErr(w, 0, "db failure", http.StatusInternalServerError)
} }
} }

View file

@ -8,7 +8,6 @@ import (
func BuildActivitypubRouter() http.Handler { func BuildActivitypubRouter() http.Handler {
router := http.NewServeMux() router := http.NewServeMux()
router.HandleFunc("/user/{id}", users) router.HandleFunc("/user/{id}", users)
router.HandleFunc("/user/{id}/inbox", userInbox)
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "in ap") fmt.Fprint(w, "in ap")
}) })

View file

@ -3,17 +3,13 @@ package activitypub
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"net/http" "net/http"
"time"
webutils "git.mstar.dev/mstar/goutils/http" webutils "git.mstar.dev/mstar/goutils/http"
"github.com/rs/zerolog/hlog" "github.com/rs/zerolog/log"
"git.mstar.dev/mstar/linstrom/config"
"git.mstar.dev/mstar/linstrom/storage-new" "git.mstar.dev/mstar/linstrom/storage-new"
"git.mstar.dev/mstar/linstrom/storage-new/dbgen" "git.mstar.dev/mstar/linstrom/storage-new/dbgen"
webshared "git.mstar.dev/mstar/linstrom/web/shared"
) )
var baseLdContext = []any{ var baseLdContext = []any{
@ -27,30 +23,17 @@ func users(w http.ResponseWriter, r *http.Request) {
Owner string `json:"owner"` Owner string `json:"owner"`
Pem string `json:"publicKeyPem"` Pem string `json:"publicKeyPem"`
} }
type OutboundMedia struct {
Type string `json:"type"`
Url string `json:"url"`
MediaType string `json:"mediaType"`
}
type Outbound struct { type Outbound struct {
Context []any `json:"@context"` Context []any `json:"@context"`
Id string `json:"id"` Id string `json:"id"`
Type string `json:"type"` Type string `json:"type"`
PreferredUsername string `json:"preferredUsername"` PreferredUsername string `json:"preferredUsername"`
Inbox string `json:"inbox"` Inbox string `json:"inbox"`
PublicKey OutboundKey `json:"publicKey"` // FIXME: Public key stuff is borken. Focus on fixing
Published time.Time `json:"published"` // PublicKey OutboundKey `json:"publicKey"`
DisplayName string `json:"name"`
Description *string `json:"summary,omitempty"`
PublicUrl string `json:"url"`
Icon *OutboundMedia `json:"icon,omitempty"`
Banner *OutboundMedia `json:"image,omitempty"`
} }
log := hlog.FromRequest(r)
userId := r.PathValue("id") userId := r.PathValue("id")
user, err := dbgen.User.Where(dbgen.User.ID.Eq(userId)). user, err := dbgen.User.Where(dbgen.User.ID.Eq(userId)).First()
Preload(dbgen.User.Icon).Preload(dbgen.User.Banner).
First()
if err != nil { if err != nil {
webutils.ProblemDetails(w, 500, "/errors/db-failure", "internal database failure", nil, nil) webutils.ProblemDetails(w, 500, "/errors/db-failure", "internal database failure", nil, nil)
if storage.HandleReconnectError(err) { if storage.HandleReconnectError(err) {
@ -60,47 +43,20 @@ func users(w http.ResponseWriter, r *http.Request) {
} }
return return
} }
// fmt.Println(x509.ParsePKCS1PublicKey(user.PublicKey))
apUrl := userIdToApUrl(user.ID) apUrl := userIdToApUrl(user.ID)
var keyBytes string
if config.GlobalConfig.Experimental.UseEd25519Keys {
keyBytes = keyBytesToPem(user.PublicKeyEd)
} else {
keyBytes = keyBytesToPem(user.PublicKeyRsa)
}
data := Outbound{ data := Outbound{
Context: baseLdContext, Context: baseLdContext,
Id: apUrl, Id: apUrl,
Type: "Person", Type: "Person",
PreferredUsername: user.Username, PreferredUsername: user.DisplayName,
Inbox: apUrl + "/inbox", Inbox: apUrl + "/inbox",
PublicKey: OutboundKey{ // PublicKey: OutboundKey{
Id: apUrl + "#main-key", // Id: apUrl + "#main-key",
Owner: apUrl, // Owner: apUrl,
Pem: keyBytes, // Pem: keyBytesToPem(user.PublicKey),
}, // },
Published: user.CreatedAt,
DisplayName: user.DisplayName,
PublicUrl: config.GlobalConfig.General.GetFullPublicUrl() + "/user/" + user.Username,
}
if user.Description != "" {
data.Description = &user.Description
}
if user.Icon != nil {
log.Debug().Msg("icon found")
data.Icon = &OutboundMedia{
Type: "Image",
Url: webshared.EnsurePublicUrl(user.Icon.Location),
MediaType: user.Icon.Type,
}
}
if user.Banner != nil {
log.Debug().Msg("icon banner")
data.Banner = &OutboundMedia{
Type: "Image",
Url: webshared.EnsurePublicUrl(user.Banner.Location),
MediaType: user.Banner.Type,
}
} }
encoded, err := json.Marshal(data) encoded, err := json.Marshal(data)
@ -108,13 +64,6 @@ func users(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, string(encoded)) fmt.Fprint(w, string(encoded))
} }
func userInbox(w http.ResponseWriter, r *http.Request) {
log := hlog.FromRequest(r)
userId := r.PathValue("id")
data, err := io.ReadAll(r.Body)
log.Info().Err(err).Str("userId", userId).Bytes("body", data).Msg("Inbox message")
}
/* /*
Fine. You win JsonLD. I can't get you to work properly. I'll just treat you like normal json then Fine. You win JsonLD. I can't get you to work properly. I'll just treat you like normal json then
Fuck you. Fuck you.

View file

@ -10,7 +10,6 @@ import (
var errorDescriptions = map[string]string{ var errorDescriptions = map[string]string{
"webfinger-bad-resource": "The given format for the \"resource\" url parameter was missing or invalid. It must follow the form \"acct:<username>@<domain>\"", "webfinger-bad-resource": "The given format for the \"resource\" url parameter was missing or invalid. It must follow the form \"acct:<username>@<domain>\"",
"db-failure": "The database query for this request failed for an undisclosed reason. This is often caused by bad input data conflicting with existing information. Try to submit different data or wait for some time", "db-failure": "The database query for this request failed for an undisclosed reason. This is often caused by bad input data conflicting with existing information. Try to submit different data or wait for some time",
"bad-request-data": "The data provided in the request doesn't match the requirements, see problem details' detail field for more information",
} }
func errorTypeHandler(w http.ResponseWriter, r *http.Request) { func errorTypeHandler(w http.ResponseWriter, r *http.Request) {

View file

@ -40,7 +40,7 @@ type Server struct {
server *http.Server server *http.Server
} }
func New(addr string, duckImg *string) *Server { func New(addr string) *Server {
handler := http.NewServeMux() handler := http.NewServeMux()
handler.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { handler.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(500) w.WriteHeader(500)
@ -51,11 +51,6 @@ func New(addr string, duckImg *string) *Server {
handler.HandleFunc("GET /.well-known/nodeinfo", api.WellKnownNodeinfo) handler.HandleFunc("GET /.well-known/nodeinfo", api.WellKnownNodeinfo)
handler.HandleFunc("GET /nodeinfo/2.1", api.Nodeinfo) handler.HandleFunc("GET /nodeinfo/2.1", api.Nodeinfo)
handler.HandleFunc("GET /errors/{name}", errorTypeHandler) handler.HandleFunc("GET /errors/{name}", errorTypeHandler)
handler.HandleFunc("GET /default-image", func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "image/web")
w.Header().Add("Content-Disposition", "attachment; filename=\"duck.webp\"")
fmt.Fprint(w, *duckImg)
})
server := http.Server{ server := http.Server{
Handler: webutils.ChainMiddlewares(handler, webutils.LoggingMiddleware), Handler: webutils.ChainMiddlewares(handler, webutils.LoggingMiddleware),
Addr: addr, Addr: addr,

View file

@ -4,7 +4,6 @@ import (
"slices" "slices"
"time" "time"
"git.mstar.dev/mstar/linstrom/config"
"git.mstar.dev/mstar/linstrom/shared" "git.mstar.dev/mstar/linstrom/shared"
"git.mstar.dev/mstar/linstrom/storage-new/models" "git.mstar.dev/mstar/linstrom/storage-new/models"
) )
@ -98,11 +97,7 @@ func (u *User) FromModel(m *models.User) {
u.BannerId = &m.IconId.String u.BannerId = &m.IconId.String
} }
u.Indexable = m.Indexable u.Indexable = m.Indexable
if config.GlobalConfig.Experimental.UseEd25519Keys { u.PublicKey = append(u.PublicKey, m.PublicKey...)
u.PublicKey = append(u.PublicKey, m.PublicKeyEd...)
} else {
u.PublicKey = append(u.PublicKey, m.PublicKeyRsa...)
}
u.RestrictedFollow = m.RestrictedFollow u.RestrictedFollow = m.RestrictedFollow
if m.Location.Valid { if m.Location.Valid {
u.Location = &m.Location.String u.Location = &m.Location.String

View file

@ -1,16 +0,0 @@
package webshared
import "strings"
// TODO: Define linstrom uri type
var hardcodedUrls = map[string]string{
"default-media": "/default-image",
}
func EnsurePublicUrl(rawUrl string) string {
if !strings.HasPrefix(rawUrl, "linstrom://") {
return rawUrl
}
return strings.Replace(rawUrl, "linstrom://", "/", 1)
}

View file

@ -1,40 +0,0 @@
package webshared
import (
"strings"
"git.mstar.dev/mstar/linstrom/shared"
)
func CreateSignatureRSA(
method string,
target string,
headers map[string]string,
privateKeyBytes []byte,
) (string, error) {
message := genPreSignatureString(method, target, headers)
signed, err := shared.Sign(message, privateKeyBytes, true)
return string(signed), err
}
func CreateSignatureED(
method string,
target string,
headers map[string]string,
privateKeyBytes []byte,
) (string, error) {
message := genPreSignatureString(method, target, headers)
signed, err := shared.Sign(message, privateKeyBytes, false)
return string(signed), err
}
func genPreSignatureString(method, target string, headers map[string]string) string {
dataBuilder := strings.Builder{}
dataBuilder.WriteString("(request-target) ")
dataBuilder.WriteString(strings.ToLower(method) + " ")
dataBuilder.WriteString(target + "\n")
for k, v := range headers {
dataBuilder.WriteString(k + ": " + v + "\n")
}
return dataBuilder.String()
}