Signing works

This commit is contained in:
Melody Becker 2025-04-10 16:40:06 +02:00
parent d272fa90b4
commit da2a89010c
Signed by: mstar
SSH key fingerprint: SHA256:9VAo09aaVNTWKzPW7Hq2LW+ox9OdwmTSHRoD4mlz1yI
19 changed files with 348 additions and 100 deletions

View file

@ -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 {

View file

@ -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
View file

@ -5,7 +5,7 @@ go 1.23.0
toolchain go1.23.7 toolchain go1.23.7
require ( require (
git.mstar.dev/mstar/goutils v1.12.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
View file

@ -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=

View file

@ -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()
} }

View file

@ -1 +0,0 @@
package shared

49
shared/signing.go Normal file
View 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
}
}

View file

@ -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
} }

View file

@ -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:"-"`

View file

@ -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,

View file

@ -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))
} }

View file

@ -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)
} }
} }

View file

@ -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")
}) })

View file

@ -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.

View file

@ -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) {

View file

@ -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,

View file

@ -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

View 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
View 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()
}