Compare commits
No commits in common. "f2616c041b80facb159432d2c8c7e0f004676d55" and "d272fa90b45762df9ab89467d90d3e59a75e3672" have entirely different histories.
f2616c041b
...
d272fa90b4
22 changed files with 99 additions and 777 deletions
|
@ -50,9 +50,7 @@ type ConfigAdmin struct {
|
|||
// The password has to be supplied in the `password` GET form value for all requests
|
||||
// to /profiling/*
|
||||
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"`
|
||||
}
|
||||
|
||||
type ConfigStorage struct {
|
||||
|
@ -105,32 +103,15 @@ type ConfigSelf struct {
|
|||
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 {
|
||||
General ConfigGeneral `toml:"general"`
|
||||
SSL ConfigSSL `toml:"ssl"`
|
||||
Admin ConfigAdmin `toml:"admin"`
|
||||
Webauthn ConfigWebAuthn `toml:"webauthn"`
|
||||
Storage ConfigStorage `toml:"storage"`
|
||||
Mail ConfigMail `toml:"mail"`
|
||||
Self ConfigSelf `toml:"self"`
|
||||
S3 ConfigS3 `toml:"s3"`
|
||||
Experimental ConfigExperimental `toml:"experimental"`
|
||||
General ConfigGeneral `toml:"general"`
|
||||
SSL ConfigSSL `toml:"ssl"`
|
||||
Admin ConfigAdmin `toml:"admin"`
|
||||
Webauthn ConfigWebAuthn `toml:"webauthn"`
|
||||
Storage ConfigStorage `toml:"storage"`
|
||||
Mail ConfigMail `toml:"mail"`
|
||||
Self ConfigSelf `toml:"self"`
|
||||
S3 ConfigS3 `toml:"s3"`
|
||||
}
|
||||
|
||||
var GlobalConfig Config
|
||||
|
@ -197,9 +178,6 @@ var defaultConfig Config = Config{
|
|||
Endpoint: "http://localhost:3900",
|
||||
UseSSL: false,
|
||||
},
|
||||
Experimental: ConfigExperimental{
|
||||
UseEd25519Keys: false,
|
||||
},
|
||||
}
|
||||
|
||||
func (gc *ConfigGeneral) GetFullDomain() string {
|
||||
|
|
|
@ -44,6 +44,3 @@
|
|||
endpoint = "http://localhost:3900"
|
||||
use_ssl = false
|
||||
bucket_name = "linstrom-bucket"
|
||||
|
||||
[experimental]
|
||||
use_ed25519_keys = false
|
||||
|
|
6
go.mod
6
go.mod
|
@ -5,7 +5,7 @@ go 1.23.0
|
|||
toolchain go1.23.7
|
||||
|
||||
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/dgraph-io/ristretto v0.2.0
|
||||
github.com/eko/gocache/lib/v4 v4.1.6
|
||||
|
@ -24,7 +24,6 @@ require (
|
|||
github.com/rs/zerolog v1.33.0
|
||||
golang.org/x/crypto v0.36.0
|
||||
golang.org/x/image v0.20.0
|
||||
golang.org/x/sys v0.32.0
|
||||
gorm.io/driver/postgres v1.5.7
|
||||
gorm.io/gen v0.3.26
|
||||
gorm.io/gorm v1.25.12
|
||||
|
@ -33,7 +32,6 @@ require (
|
|||
|
||||
require (
|
||||
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
|
||||
github.com/beorn7/perks v1.0.1 // 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/goccy/go-json v0.10.3 // 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/protobuf v1.5.4 // 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/net v0.30.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/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
|
|
7
go.sum
7
go.sum
|
@ -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=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
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/go.mod h1:cVCXcMGdYk8pySulIYSqMuvGG6lYEkibM5pPy97gylQ=
|
||||
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.12.0 h1:d88hLS8KnLUCI+8aWBR6228M43hxHdJpj8WuSqm4LAM=
|
||||
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 v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
||||
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/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
||||
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/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=
|
||||
|
@ -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/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-20200801110659-972c09e46d76/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw=
|
||||
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=
|
||||
|
|
2
main.go
2
main.go
|
@ -160,6 +160,6 @@ func newServer() {
|
|||
}()
|
||||
}
|
||||
log.Info().Msg("Starting public server")
|
||||
public := webpublic.New(":8080", &defaultDuck)
|
||||
public := webpublic.New(":8080")
|
||||
public.Start()
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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
1
shared/rsaKey.go
Normal file
|
@ -0,0 +1 @@
|
|||
package shared
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -40,16 +40,14 @@ func newUser(db *gorm.DB, opts ...gen.DOOption) user {
|
|||
_user.BackgroundId = field.NewField(tableName, "background_id")
|
||||
_user.BannerId = field.NewField(tableName, "banner_id")
|
||||
_user.Indexable = field.NewBool(tableName, "indexable")
|
||||
_user.PublicKeyRsa = field.NewBytes(tableName, "public_key_rsa")
|
||||
_user.PublicKeyEd = field.NewBytes(tableName, "public_key_ed")
|
||||
_user.PublicKey = field.NewBytes(tableName, "public_key")
|
||||
_user.RestrictedFollow = field.NewBool(tableName, "restricted_follow")
|
||||
_user.Location = field.NewField(tableName, "location")
|
||||
_user.Birthday = field.NewField(tableName, "birthday")
|
||||
_user.Verified = field.NewBool(tableName, "verified")
|
||||
_user.PasskeyId = field.NewBytes(tableName, "passkey_id")
|
||||
_user.FinishedRegistration = field.NewBool(tableName, "finished_registration")
|
||||
_user.PrivateKeyRsa = field.NewBytes(tableName, "private_key_rsa")
|
||||
_user.PrivateKeyEd = field.NewBytes(tableName, "private_key_ed")
|
||||
_user.PrivateKey = field.NewBytes(tableName, "private_key")
|
||||
_user.RemoteInfo = userHasOneRemoteInfo{
|
||||
db: db.Session(&gorm.Session{}),
|
||||
|
||||
|
@ -355,16 +353,14 @@ type user struct {
|
|||
BackgroundId field.Field
|
||||
BannerId field.Field
|
||||
Indexable field.Bool
|
||||
PublicKeyRsa field.Bytes
|
||||
PublicKeyEd field.Bytes
|
||||
PublicKey field.Bytes
|
||||
RestrictedFollow field.Bool
|
||||
Location field.Field
|
||||
Birthday field.Field
|
||||
Verified field.Bool
|
||||
PasskeyId field.Bytes
|
||||
FinishedRegistration field.Bool
|
||||
PrivateKeyRsa field.Bytes
|
||||
PrivateKeyEd field.Bytes
|
||||
PrivateKey field.Bytes
|
||||
RemoteInfo userHasOneRemoteInfo
|
||||
|
||||
InfoFields userHasManyInfoFields
|
||||
|
@ -417,16 +413,14 @@ func (u *user) updateTableName(table string) *user {
|
|||
u.BackgroundId = field.NewField(table, "background_id")
|
||||
u.BannerId = field.NewField(table, "banner_id")
|
||||
u.Indexable = field.NewBool(table, "indexable")
|
||||
u.PublicKeyRsa = field.NewBytes(table, "public_key_rsa")
|
||||
u.PublicKeyEd = field.NewBytes(table, "public_key_ed")
|
||||
u.PublicKey = field.NewBytes(table, "public_key")
|
||||
u.RestrictedFollow = field.NewBool(table, "restricted_follow")
|
||||
u.Location = field.NewField(table, "location")
|
||||
u.Birthday = field.NewField(table, "birthday")
|
||||
u.Verified = field.NewBool(table, "verified")
|
||||
u.PasskeyId = field.NewBytes(table, "passkey_id")
|
||||
u.FinishedRegistration = field.NewBool(table, "finished_registration")
|
||||
u.PrivateKeyRsa = field.NewBytes(table, "private_key_rsa")
|
||||
u.PrivateKeyEd = field.NewBytes(table, "private_key_ed")
|
||||
u.PrivateKey = field.NewBytes(table, "private_key")
|
||||
|
||||
u.fillFieldMap()
|
||||
|
||||
|
@ -443,7 +437,7 @@ func (u *user) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
|
|||
}
|
||||
|
||||
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["username"] = u.Username
|
||||
u.fieldMap["created_at"] = u.CreatedAt
|
||||
|
@ -457,16 +451,14 @@ func (u *user) fillFieldMap() {
|
|||
u.fieldMap["background_id"] = u.BackgroundId
|
||||
u.fieldMap["banner_id"] = u.BannerId
|
||||
u.fieldMap["indexable"] = u.Indexable
|
||||
u.fieldMap["public_key_rsa"] = u.PublicKeyRsa
|
||||
u.fieldMap["public_key_ed"] = u.PublicKeyEd
|
||||
u.fieldMap["public_key"] = u.PublicKey
|
||||
u.fieldMap["restricted_follow"] = u.RestrictedFollow
|
||||
u.fieldMap["location"] = u.Location
|
||||
u.fieldMap["birthday"] = u.Birthday
|
||||
u.fieldMap["verified"] = u.Verified
|
||||
u.fieldMap["passkey_id"] = u.PasskeyId
|
||||
u.fieldMap["finished_registration"] = u.FinishedRegistration
|
||||
u.fieldMap["private_key_rsa"] = u.PrivateKeyRsa
|
||||
u.fieldMap["private_key_ed"] = u.PrivateKeyEd
|
||||
u.fieldMap["private_key"] = u.PrivateKey
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -47,10 +47,9 @@ type User struct {
|
|||
Background *MediaMetadata ` json:"-"`
|
||||
BackgroundId sql.NullString ` json:"background_id"` // ID of a media file used as background image
|
||||
Banner *MediaMetadata ` json:"-"`
|
||||
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
|
||||
PublicKeyRsa []byte ` json:"public_key_rsa"` // The public RSA key of the account
|
||||
PublicKeyEd []byte ` json:"public_key_ed"` // The public Ed25519 key of the account
|
||||
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
|
||||
PublicKey []byte ` json:"public_key"` // The public key of the account
|
||||
// Whether this account restricts following
|
||||
// If true, the owner must approve of a follow request first
|
||||
RestrictedFollow bool ` json:"restricted_follow"`
|
||||
|
@ -67,8 +66,7 @@ type User struct {
|
|||
// saved space is worth
|
||||
PasskeyId []byte `json:"-"`
|
||||
FinishedRegistration bool `json:"-"` // Whether this account has completed registration yet
|
||||
PrivateKeyRsa []byte `json:"-"`
|
||||
PrivateKeyEd []byte `json:"-"`
|
||||
PrivateKey []byte `json:"-"`
|
||||
|
||||
// ---- "Remote" linked values
|
||||
InfoFields []UserInfoField `json:"-"`
|
||||
|
|
|
@ -2,6 +2,8 @@ package storage
|
|||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"database/sql"
|
||||
|
||||
"git.mstar.dev/mstar/goutils/other"
|
||||
|
@ -9,7 +11,6 @@ import (
|
|||
"gorm.io/gorm"
|
||||
|
||||
"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/models"
|
||||
)
|
||||
|
@ -26,7 +27,7 @@ func InsertSelf() error {
|
|||
if err != nil {
|
||||
return other.Error("storage", "failed to save/update self server", err)
|
||||
}
|
||||
user, err := insertUser(server, duck)
|
||||
user, err := insertUser(server)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
func insertUser(
|
||||
server *models.RemoteServer,
|
||||
duckMedia *models.MediaMetadata,
|
||||
) (*models.User, error) {
|
||||
func insertUser(server *models.RemoteServer) (*models.User, error) {
|
||||
dbUser, err := dbgen.User.GetByUsername("linstrom")
|
||||
if err == nil {
|
||||
return dbUser, nil
|
||||
|
@ -105,14 +103,16 @@ func insertUser(
|
|||
if err != gorm.ErrRecordNotFound {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
publicRsaKeyBytes, privateRsaKeyBytes, err := shared.GenerateKeypair(false)
|
||||
if err != nil {
|
||||
if err = privateKey.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)
|
||||
publicKeyBytes := x509.MarshalPKCS1PublicKey(&privateKey.PublicKey)
|
||||
pkeyId := make([]byte, 64)
|
||||
_, err = rand.Read(pkeyId)
|
||||
if err != nil {
|
||||
|
@ -129,17 +129,15 @@ func insertUser(
|
|||
DisplayName: config.GlobalConfig.Self.ServerActorDisplayName,
|
||||
Description: "The default linstrom server user",
|
||||
IsBot: true,
|
||||
Icon: duckMedia,
|
||||
IconId: sql.NullString{Valid: true, String: duckMedia.ID},
|
||||
Icon: nil,
|
||||
IconId: sql.NullString{Valid: false},
|
||||
Background: nil,
|
||||
BackgroundId: sql.NullString{Valid: false},
|
||||
Banner: nil,
|
||||
BannerId: sql.NullString{Valid: false},
|
||||
Indexable: false,
|
||||
PublicKeyEd: publicEdKeyBytes,
|
||||
PrivateKeyEd: privateEdKeyBytes,
|
||||
PublicKeyRsa: publicRsaKeyBytes,
|
||||
PrivateKeyRsa: privateRsaKeyBytes,
|
||||
PublicKey: publicKeyBytes,
|
||||
PrivateKey: privateKeyBytes,
|
||||
Verified: true,
|
||||
FinishedRegistration: true,
|
||||
PasskeyId: pkeyId,
|
||||
|
|
|
@ -4,9 +4,10 @@ import (
|
|||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
webutils "git.mstar.dev/mstar/goutils/http"
|
||||
httputils "git.mstar.dev/mstar/goutils/http"
|
||||
"git.mstar.dev/mstar/goutils/sliceutils"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gorm.io/gorm"
|
||||
|
@ -26,28 +27,15 @@ func postAs(w http.ResponseWriter, r *http.Request) {
|
|||
data := Inbound{}
|
||||
err := dec.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",
|
||||
Content: "Heya there, this is sample data",
|
||||
},
|
||||
},
|
||||
)
|
||||
httputils.HttpErr(w, 0, "json decode failed", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
user, err := dbgen.User.GetByUsername(data.Username)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusNotFound)
|
||||
httputils.HttpErr(w, 0, "no user with that name", http.StatusNotFound)
|
||||
} else {
|
||||
log.Error().Err(err).Str("name", data.Username).Msg("Failed to find user")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -81,13 +69,13 @@ func notesFrom(w http.ResponseWriter, r *http.Request) {
|
|||
if err != nil {
|
||||
log.Error().Err(err).Str("name", username).Msg("Failed to get user")
|
||||
storage.HandleReconnectError(err)
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
httputils.HttpErr(w, 0, "failed to get user", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
notes, err := dbgen.Note.GetNotesPaged(user.ID, 0, uint8(models.NOTE_TARGET_PUBLIC))
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
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)
|
||||
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))
|
||||
}
|
||||
|
|
|
@ -2,18 +2,19 @@ package webdebug
|
|||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
webutils "git.mstar.dev/mstar/goutils/http"
|
||||
"git.mstar.dev/mstar/goutils/other"
|
||||
httputils "git.mstar.dev/mstar/goutils/http"
|
||||
"git.mstar.dev/mstar/goutils/sliceutils"
|
||||
"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/models"
|
||||
webshared "git.mstar.dev/mstar/linstrom/web/shared"
|
||||
|
@ -26,27 +27,25 @@ func getNonDeletedUsers(w http.ResponseWriter, r *http.Request) {
|
|||
var err error
|
||||
page, err = strconv.Atoi(pageStr)
|
||||
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
|
||||
}
|
||||
}
|
||||
users, err := dbgen.User.GetPagedAllNonDeleted(uint(page))
|
||||
if err != nil {
|
||||
webutils.ProblemDetails(
|
||||
w,
|
||||
http.StatusInternalServerError,
|
||||
"/errors/db-failure",
|
||||
"database failure",
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
httputils.HttpErr(w, 0, "failed to get users", http.StatusInternalServerError)
|
||||
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.FromModel(&t)
|
||||
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) {
|
||||
|
@ -62,43 +61,19 @@ func createLocalUser(w http.ResponseWriter, r *http.Request) {
|
|||
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,
|
||||
},
|
||||
},
|
||||
)
|
||||
httputils.HttpErr(w, 0, "decode failed", http.StatusBadRequest)
|
||||
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
|
||||
}
|
||||
// publicKey, privateKey, err := ed25519.GenerateKey(nil)
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)
|
||||
publicKeyBytes := x509.MarshalPKCS1PublicKey(&privateKey.PublicKey)
|
||||
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)
|
||||
httputils.HttpErr(w, 0, "failed to generate passkey id", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -109,10 +84,8 @@ func createLocalUser(w http.ResponseWriter, r *http.Request) {
|
|||
u.Description,
|
||||
u.IsBot,
|
||||
u.ServerId,
|
||||
u.PrivateKeyEd,
|
||||
u.PublicKeyEd,
|
||||
u.PrivateKeyRsa,
|
||||
u.PublicKeyRsa,
|
||||
u.PrivateKey,
|
||||
u.PublicKey,
|
||||
u.PasskeyId,
|
||||
)
|
||||
if data.Birthday != nil {
|
||||
|
@ -122,16 +95,14 @@ func createLocalUser(w http.ResponseWriter, r *http.Request) {
|
|||
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,
|
||||
Username: data.Username,
|
||||
DisplayName: data.Displayname,
|
||||
Description: data.Description,
|
||||
IsBot: data.IsBot,
|
||||
ServerId: 1, // Hardcoded, Self is always first ID
|
||||
PublicKey: publicKeyBytes,
|
||||
PrivateKey: privateKeyBytes,
|
||||
PasskeyId: pkeyId,
|
||||
}
|
||||
if data.Birthday != nil {
|
||||
user.Birthday = sql.NullTime{Valid: true, Time: *data.Birthday}
|
||||
|
@ -141,7 +112,7 @@ func createLocalUser(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
if err = u.Create(&user); err != nil {
|
||||
log.Error().Err(err).Msg("failed to create new local user")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
httputils.HttpErr(w, 0, "db failure", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
func BuildActivitypubRouter() http.Handler {
|
||||
router := http.NewServeMux()
|
||||
router.HandleFunc("/user/{id}", users)
|
||||
router.HandleFunc("/user/{id}/inbox", userInbox)
|
||||
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, "in ap")
|
||||
})
|
||||
|
|
|
@ -3,17 +3,13 @@ package activitypub
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
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/dbgen"
|
||||
webshared "git.mstar.dev/mstar/linstrom/web/shared"
|
||||
)
|
||||
|
||||
var baseLdContext = []any{
|
||||
|
@ -27,30 +23,17 @@ func users(w http.ResponseWriter, r *http.Request) {
|
|||
Owner string `json:"owner"`
|
||||
Pem string `json:"publicKeyPem"`
|
||||
}
|
||||
type OutboundMedia struct {
|
||||
Type string `json:"type"`
|
||||
Url string `json:"url"`
|
||||
MediaType string `json:"mediaType"`
|
||||
}
|
||||
type Outbound struct {
|
||||
Context []any `json:"@context"`
|
||||
Id string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
PreferredUsername string `json:"preferredUsername"`
|
||||
Inbox string `json:"inbox"`
|
||||
PublicKey OutboundKey `json:"publicKey"`
|
||||
Published time.Time `json:"published"`
|
||||
DisplayName string `json:"name"`
|
||||
Description *string `json:"summary,omitempty"`
|
||||
PublicUrl string `json:"url"`
|
||||
Icon *OutboundMedia `json:"icon,omitempty"`
|
||||
Banner *OutboundMedia `json:"image,omitempty"`
|
||||
Context []any `json:"@context"`
|
||||
Id string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
PreferredUsername string `json:"preferredUsername"`
|
||||
Inbox string `json:"inbox"`
|
||||
// FIXME: Public key stuff is borken. Focus on fixing
|
||||
// PublicKey OutboundKey `json:"publicKey"`
|
||||
}
|
||||
log := hlog.FromRequest(r)
|
||||
userId := r.PathValue("id")
|
||||
user, err := dbgen.User.Where(dbgen.User.ID.Eq(userId)).
|
||||
Preload(dbgen.User.Icon).Preload(dbgen.User.Banner).
|
||||
First()
|
||||
user, err := dbgen.User.Where(dbgen.User.ID.Eq(userId)).First()
|
||||
if err != nil {
|
||||
webutils.ProblemDetails(w, 500, "/errors/db-failure", "internal database failure", nil, nil)
|
||||
if storage.HandleReconnectError(err) {
|
||||
|
@ -60,47 +43,20 @@ func users(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
return
|
||||
}
|
||||
// fmt.Println(x509.ParsePKCS1PublicKey(user.PublicKey))
|
||||
|
||||
apUrl := userIdToApUrl(user.ID)
|
||||
var keyBytes string
|
||||
if config.GlobalConfig.Experimental.UseEd25519Keys {
|
||||
keyBytes = keyBytesToPem(user.PublicKeyEd)
|
||||
} else {
|
||||
keyBytes = keyBytesToPem(user.PublicKeyRsa)
|
||||
}
|
||||
data := Outbound{
|
||||
Context: baseLdContext,
|
||||
Id: apUrl,
|
||||
Type: "Person",
|
||||
PreferredUsername: user.Username,
|
||||
PreferredUsername: user.DisplayName,
|
||||
Inbox: apUrl + "/inbox",
|
||||
PublicKey: OutboundKey{
|
||||
Id: apUrl + "#main-key",
|
||||
Owner: apUrl,
|
||||
Pem: keyBytes,
|
||||
},
|
||||
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,
|
||||
}
|
||||
// PublicKey: OutboundKey{
|
||||
// Id: apUrl + "#main-key",
|
||||
// Owner: apUrl,
|
||||
// Pem: keyBytesToPem(user.PublicKey),
|
||||
// },
|
||||
}
|
||||
|
||||
encoded, err := json.Marshal(data)
|
||||
|
@ -108,13 +64,6 @@ func users(w http.ResponseWriter, r *http.Request) {
|
|||
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
|
||||
Fuck you.
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
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>\"",
|
||||
"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) {
|
||||
|
|
|
@ -40,7 +40,7 @@ type Server struct {
|
|||
server *http.Server
|
||||
}
|
||||
|
||||
func New(addr string, duckImg *string) *Server {
|
||||
func New(addr string) *Server {
|
||||
handler := http.NewServeMux()
|
||||
handler.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
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 /nodeinfo/2.1", api.Nodeinfo)
|
||||
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{
|
||||
Handler: webutils.ChainMiddlewares(handler, webutils.LoggingMiddleware),
|
||||
Addr: addr,
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"slices"
|
||||
"time"
|
||||
|
||||
"git.mstar.dev/mstar/linstrom/config"
|
||||
"git.mstar.dev/mstar/linstrom/shared"
|
||||
"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.Indexable = m.Indexable
|
||||
if config.GlobalConfig.Experimental.UseEd25519Keys {
|
||||
u.PublicKey = append(u.PublicKey, m.PublicKeyEd...)
|
||||
} else {
|
||||
u.PublicKey = append(u.PublicKey, m.PublicKeyRsa...)
|
||||
}
|
||||
u.PublicKey = append(u.PublicKey, m.PublicKey...)
|
||||
u.RestrictedFollow = m.RestrictedFollow
|
||||
if m.Location.Valid {
|
||||
u.Location = &m.Location.String
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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()
|
||||
}
|
Loading…
Reference in a new issue