Signing works
This commit is contained in:
parent
d272fa90b4
commit
da2a89010c
19 changed files with 348 additions and 100 deletions
|
@ -50,6 +50,8 @@ 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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,6 +105,22 @@ 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"`
|
||||||
|
@ -112,6 +130,7 @@ 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
|
||||||
|
@ -178,6 +197,9 @@ 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 {
|
||||||
|
|
|
@ -44,3 +44,6 @@
|
||||||
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
6
go.mod
|
@ -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.0
|
git.mstar.dev/mstar/goutils v1.12.1
|
||||||
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,6 +24,7 @@ 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
|
||||||
|
@ -32,6 +33,7 @@ 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
|
||||||
|
@ -47,6 +49,7 @@ 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
|
||||||
|
@ -83,7 +86,6 @@ 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
7
go.sum
|
@ -33,6 +33,8 @@ 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=
|
||||||
|
@ -57,6 +59,8 @@ 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=
|
||||||
|
@ -150,6 +154,8 @@ 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=
|
||||||
|
@ -392,6 +398,7 @@ 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=
|
||||||
|
|
2
main.go
2
main.go
|
@ -160,6 +160,6 @@ func newServer() {
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
log.Info().Msg("Starting public server")
|
log.Info().Msg("Starting public server")
|
||||||
public := webpublic.New(":8080")
|
public := webpublic.New(":8080", &defaultDuck)
|
||||||
public.Start()
|
public.Start()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
package shared
|
|
49
shared/signing.go
Normal file
49
shared/signing.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
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,14 +40,16 @@ 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.PublicKey = field.NewBytes(tableName, "public_key")
|
_user.PublicKeyRsa = field.NewBytes(tableName, "public_key_rsa")
|
||||||
|
_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.PrivateKey = field.NewBytes(tableName, "private_key")
|
_user.PrivateKeyRsa = field.NewBytes(tableName, "private_key_rsa")
|
||||||
|
_user.PrivateKeyEd = field.NewBytes(tableName, "private_key_ed")
|
||||||
_user.RemoteInfo = userHasOneRemoteInfo{
|
_user.RemoteInfo = userHasOneRemoteInfo{
|
||||||
db: db.Session(&gorm.Session{}),
|
db: db.Session(&gorm.Session{}),
|
||||||
|
|
||||||
|
@ -353,14 +355,16 @@ type user struct {
|
||||||
BackgroundId field.Field
|
BackgroundId field.Field
|
||||||
BannerId field.Field
|
BannerId field.Field
|
||||||
Indexable field.Bool
|
Indexable field.Bool
|
||||||
PublicKey field.Bytes
|
PublicKeyRsa 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
|
||||||
PrivateKey field.Bytes
|
PrivateKeyRsa field.Bytes
|
||||||
|
PrivateKeyEd field.Bytes
|
||||||
RemoteInfo userHasOneRemoteInfo
|
RemoteInfo userHasOneRemoteInfo
|
||||||
|
|
||||||
InfoFields userHasManyInfoFields
|
InfoFields userHasManyInfoFields
|
||||||
|
@ -413,14 +417,16 @@ 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.PublicKey = field.NewBytes(table, "public_key")
|
u.PublicKeyRsa = field.NewBytes(table, "public_key_rsa")
|
||||||
|
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.PrivateKey = field.NewBytes(table, "private_key")
|
u.PrivateKeyRsa = field.NewBytes(table, "private_key_rsa")
|
||||||
|
u.PrivateKeyEd = field.NewBytes(table, "private_key_ed")
|
||||||
|
|
||||||
u.fillFieldMap()
|
u.fillFieldMap()
|
||||||
|
|
||||||
|
@ -437,7 +443,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, 33)
|
u.fieldMap = make(map[string]field.Expr, 35)
|
||||||
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
|
||||||
|
@ -451,14 +457,16 @@ 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"] = u.PublicKey
|
u.fieldMap["public_key_rsa"] = u.PublicKeyRsa
|
||||||
|
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"] = u.PrivateKey
|
u.fieldMap["private_key_rsa"] = u.PrivateKeyRsa
|
||||||
|
u.fieldMap["private_key_ed"] = u.PrivateKeyEd
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,8 @@ 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
|
||||||
PublicKey []byte ` json:"public_key"` // The public key of the account
|
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
|
||||||
// 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"`
|
||||||
|
@ -66,7 +67,8 @@ 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
|
||||||
PrivateKey []byte `json:"-"`
|
PrivateKeyRsa []byte `json:"-"`
|
||||||
|
PrivateKeyEd []byte `json:"-"`
|
||||||
|
|
||||||
// ---- "Remote" linked values
|
// ---- "Remote" linked values
|
||||||
InfoFields []UserInfoField `json:"-"`
|
InfoFields []UserInfoField `json:"-"`
|
||||||
|
|
|
@ -2,8 +2,6 @@ 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"
|
||||||
|
@ -11,6 +9,7 @@ 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"
|
||||||
)
|
)
|
||||||
|
@ -27,7 +26,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)
|
user, err := insertUser(server, duck)
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
@ -95,7 +94,10 @@ func insertServer(duck *models.MediaMetadata) (*models.RemoteServer, error) {
|
||||||
return &server, nil
|
return &server, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func insertUser(server *models.RemoteServer) (*models.User, error) {
|
func insertUser(
|
||||||
|
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
|
||||||
|
@ -103,16 +105,14 @@ func insertUser(server *models.RemoteServer) (*models.User, error) {
|
||||||
if err != gorm.ErrRecordNotFound {
|
if err != gorm.ErrRecordNotFound {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// publicKey, privateKey, err := ed25519.GenerateKey(nil)
|
publicEdKeyBytes, privateEdKeyBytes, err := shared.GenerateKeypair(true)
|
||||||
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err = privateKey.Validate(); err != nil {
|
publicRsaKeyBytes, privateRsaKeyBytes, err := shared.GenerateKeypair(false)
|
||||||
|
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,15 +129,17 @@ func insertUser(server *models.RemoteServer) (*models.User, error) {
|
||||||
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: nil,
|
Icon: duckMedia,
|
||||||
IconId: sql.NullString{Valid: false},
|
IconId: sql.NullString{Valid: true, String: duckMedia.ID},
|
||||||
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,
|
||||||
PublicKey: publicKeyBytes,
|
PublicKeyEd: publicEdKeyBytes,
|
||||||
PrivateKey: privateKeyBytes,
|
PrivateKeyEd: privateEdKeyBytes,
|
||||||
|
PublicKeyRsa: publicRsaKeyBytes,
|
||||||
|
PrivateKeyRsa: privateRsaKeyBytes,
|
||||||
Verified: true,
|
Verified: true,
|
||||||
FinishedRegistration: true,
|
FinishedRegistration: true,
|
||||||
PasskeyId: pkeyId,
|
PasskeyId: pkeyId,
|
||||||
|
|
|
@ -4,10 +4,9 @@ import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
httputils "git.mstar.dev/mstar/goutils/http"
|
webutils "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"
|
||||||
|
@ -27,15 +26,28 @@ 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 {
|
||||||
httputils.HttpErr(w, 0, "json decode failed", http.StatusBadRequest)
|
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",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
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) {
|
||||||
httputils.HttpErr(w, 0, "no user with that name", http.StatusNotFound)
|
webutils.ProblemDetailsStatusOnly(w, 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
|
||||||
}
|
}
|
||||||
|
@ -69,13 +81,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)
|
||||||
httputils.HttpErr(w, 0, "failed to get user", http.StatusInternalServerError)
|
webutils.ProblemDetailsStatusOnly(w, 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")
|
||||||
httputils.HttpErr(w, 0, "failed to get notes", http.StatusInternalServerError)
|
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
publicNotes := sliceutils.Map(notes, func(t models.Note) webshared.Note {
|
publicNotes := sliceutils.Map(notes, func(t models.Note) webshared.Note {
|
||||||
|
@ -83,11 +95,5 @@ func notesFrom(w http.ResponseWriter, r *http.Request) {
|
||||||
n.FromModel(&t)
|
n.FromModel(&t)
|
||||||
return n
|
return n
|
||||||
})
|
})
|
||||||
jsonNotes, err := json.Marshal(publicNotes)
|
webutils.SendJson(w, 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,19 +2,18 @@ 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"
|
||||||
|
|
||||||
httputils "git.mstar.dev/mstar/goutils/http"
|
webutils "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"
|
||||||
|
@ -27,25 +26,27 @@ 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 {
|
||||||
httputils.HttpErr(w, 0, "page is not a number", http.StatusBadRequest)
|
webutils.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 {
|
||||||
httputils.HttpErr(w, 0, "failed to get users", http.StatusInternalServerError)
|
webutils.ProblemDetails(
|
||||||
|
w,
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
"/errors/db-failure",
|
||||||
|
"database failure",
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
marshalled, err := json.Marshal(sliceutils.Map(users, func(t models.User) webshared.User {
|
webutils.SendJson(w, 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) {
|
||||||
|
@ -61,19 +62,43 @@ 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 {
|
||||||
httputils.HttpErr(w, 0, "decode failed", http.StatusBadRequest)
|
webutils.ProblemDetails(
|
||||||
|
w,
|
||||||
|
http.StatusBadRequest,
|
||||||
|
"/errors/bad-request-data",
|
||||||
|
"bad request data",
|
||||||
|
nil,
|
||||||
|
map[string]any{
|
||||||
|
"sample": Inbound{
|
||||||
|
Username: "bob",
|
||||||
|
Displayname: "Bob Bobbington",
|
||||||
|
Description: "Bobbing Bobs bop to Bobs bobbing beats",
|
||||||
|
Birthday: other.IntoPointer(time.Now()),
|
||||||
|
Location: nil,
|
||||||
|
IsBot: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// publicKey, privateKey, err := ed25519.GenerateKey(nil)
|
publicKeyEdBytes, privateKeyEdBytes, err := shared.GenerateKeypair(true)
|
||||||
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
if err != nil {
|
||||||
privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)
|
log.Error().Err(err).Msg("Failed to generate and marshal public key")
|
||||||
publicKeyBytes := x509.MarshalPKCS1PublicKey(&privateKey.PublicKey)
|
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
publicKeyRsaBytes, privateKeyRsaBytes, err := shared.GenerateKeypair(false)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Failed to generate and marshal public key")
|
||||||
|
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
pkeyId := make([]byte, 64)
|
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")
|
||||||
httputils.HttpErr(w, 0, "failed to generate passkey id", http.StatusInternalServerError)
|
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,8 +109,10 @@ func createLocalUser(w http.ResponseWriter, r *http.Request) {
|
||||||
u.Description,
|
u.Description,
|
||||||
u.IsBot,
|
u.IsBot,
|
||||||
u.ServerId,
|
u.ServerId,
|
||||||
u.PrivateKey,
|
u.PrivateKeyEd,
|
||||||
u.PublicKey,
|
u.PublicKeyEd,
|
||||||
|
u.PrivateKeyRsa,
|
||||||
|
u.PublicKeyRsa,
|
||||||
u.PasskeyId,
|
u.PasskeyId,
|
||||||
)
|
)
|
||||||
if data.Birthday != nil {
|
if data.Birthday != nil {
|
||||||
|
@ -100,8 +127,10 @@ 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
|
||||||
PublicKey: publicKeyBytes,
|
PublicKeyRsa: publicKeyRsaBytes,
|
||||||
PrivateKey: privateKeyBytes,
|
PublicKeyEd: publicKeyEdBytes,
|
||||||
|
PrivateKeyRsa: privateKeyRsaBytes,
|
||||||
|
PrivateKeyEd: privateKeyEdBytes,
|
||||||
PasskeyId: pkeyId,
|
PasskeyId: pkeyId,
|
||||||
}
|
}
|
||||||
if data.Birthday != nil {
|
if data.Birthday != nil {
|
||||||
|
@ -112,7 +141,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")
|
||||||
httputils.HttpErr(w, 0, "db failure", http.StatusInternalServerError)
|
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ 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")
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,13 +3,17 @@ 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/log"
|
"github.com/rs/zerolog/hlog"
|
||||||
|
|
||||||
|
"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{
|
||||||
|
@ -23,17 +27,30 @@ 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"`
|
||||||
// FIXME: Public key stuff is borken. Focus on fixing
|
PublicKey OutboundKey `json:"publicKey"`
|
||||||
// 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"`
|
||||||
}
|
}
|
||||||
|
log := hlog.FromRequest(r)
|
||||||
userId := r.PathValue("id")
|
userId := r.PathValue("id")
|
||||||
user, err := dbgen.User.Where(dbgen.User.ID.Eq(userId)).First()
|
user, err := dbgen.User.Where(dbgen.User.ID.Eq(userId)).
|
||||||
|
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) {
|
||||||
|
@ -43,20 +60,47 @@ 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.DisplayName,
|
PreferredUsername: user.Username,
|
||||||
Inbox: apUrl + "/inbox",
|
Inbox: apUrl + "/inbox",
|
||||||
// PublicKey: OutboundKey{
|
PublicKey: OutboundKey{
|
||||||
// Id: apUrl + "#main-key",
|
Id: apUrl + "#main-key",
|
||||||
// Owner: apUrl,
|
Owner: apUrl,
|
||||||
// Pem: keyBytesToPem(user.PublicKey),
|
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,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
encoded, err := json.Marshal(data)
|
encoded, err := json.Marshal(data)
|
||||||
|
@ -64,6 +108,13 @@ 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.
|
||||||
|
|
|
@ -10,6 +10,7 @@ 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) {
|
||||||
|
|
|
@ -40,7 +40,7 @@ type Server struct {
|
||||||
server *http.Server
|
server *http.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(addr string) *Server {
|
func New(addr string, duckImg *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,6 +51,11 @@ func New(addr 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,
|
||||||
|
|
|
@ -4,6 +4,7 @@ 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"
|
||||||
)
|
)
|
||||||
|
@ -97,7 +98,11 @@ 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
|
||||||
u.PublicKey = append(u.PublicKey, m.PublicKey...)
|
if config.GlobalConfig.Experimental.UseEd25519Keys {
|
||||||
|
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
|
||||||
|
|
16
web/shared/linstromUrlType.go
Normal file
16
web/shared/linstromUrlType.go
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
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)
|
||||||
|
}
|
40
web/shared/signing.go
Normal file
40
web/shared/signing.go
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
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