This commit is contained in:
parent
b6f12b7acf
commit
8f8ad3035a
33 changed files with 166 additions and 111 deletions
|
@ -13,10 +13,13 @@ import (
|
||||||
"github.com/go-webauthn/webauthn/webauthn"
|
"github.com/go-webauthn/webauthn/webauthn"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// An Authenticator is used for authenticating user requests against the server
|
||||||
type Authenticator struct {
|
type Authenticator struct {
|
||||||
webauthn *webauthn.WebAuthn
|
webauthn *webauthn.WebAuthn
|
||||||
recentlyUsedTotpTokens map[string]time.Time
|
recentlyUsedTotpTokens map[string]time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The next state of a login process
|
||||||
type LoginNextState uint8
|
type LoginNextState uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
// Len of salt for passwords in bytes
|
// Len of salt for passwords in bytes
|
||||||
const saltLen = 32
|
const saltLen = 32
|
||||||
|
|
||||||
|
// Generate a random salt with the given nr of bytes
|
||||||
func generateSalt(length int) ([]byte, error) {
|
func generateSalt(length int) ([]byte, error) {
|
||||||
salt := make([]byte, length)
|
salt := make([]byte, length)
|
||||||
if _, err := rand.Read(salt); err != nil {
|
if _, err := rand.Read(salt); err != nil {
|
||||||
|
@ -26,18 +27,19 @@ func generateSalt(length int) ([]byte, error) {
|
||||||
return salt, nil
|
return salt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func hashPassword(password string) ([]byte, error) {
|
// Hash a password with salt
|
||||||
|
func hashPassword(password string) (hash []byte, err error) {
|
||||||
salt, err := generateSalt(saltLen)
|
salt, err := generateSalt(saltLen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
hash := argon2.IDKey([]byte(password), salt, 1, 64*1024, 4, 32)
|
hash = argon2.IDKey([]byte(password), salt, 1, 64*1024, 4, 32)
|
||||||
hash = append(hash, salt...)
|
hash = append(hash, salt...)
|
||||||
// return bcrypt.GenerateFromPassword([]byte(password), 14)
|
// return bcrypt.GenerateFromPassword([]byte(password), 14)
|
||||||
return hash, nil
|
return hash, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compare a raw password against a hash + salt
|
// Check wether a password matches a hash
|
||||||
func comparePassword(password string, hash []byte) bool {
|
func comparePassword(password string, hash []byte) bool {
|
||||||
// Hash is actually hash(password)+salt
|
// Hash is actually hash(password)+salt
|
||||||
salt := hash[len(hash)-saltLen:]
|
salt := hash[len(hash)-saltLen:]
|
||||||
|
@ -47,6 +49,7 @@ func comparePassword(password string, hash []byte) bool {
|
||||||
|
|
||||||
// Copied and adjusted from: https://bruinsslot.jp/post/golang-crypto/
|
// Copied and adjusted from: https://bruinsslot.jp/post/golang-crypto/
|
||||||
|
|
||||||
|
// Encrypt some data using a key
|
||||||
func Encrypt(key, data []byte) ([]byte, error) {
|
func Encrypt(key, data []byte) ([]byte, error) {
|
||||||
key, salt, err := deriveKey(key, nil)
|
key, salt, err := deriveKey(key, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -75,6 +78,7 @@ func Encrypt(key, data []byte) ([]byte, error) {
|
||||||
return ciphertext, nil
|
return ciphertext, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Decrypt some data using a key
|
||||||
func Decrypt(key, data []byte) ([]byte, error) {
|
func Decrypt(key, data []byte) ([]byte, error) {
|
||||||
salt, data := data[len(data)-32:], data[:len(data)-32]
|
salt, data := data[len(data)-32:], data[:len(data)-32]
|
||||||
|
|
||||||
|
@ -103,6 +107,8 @@ func Decrypt(key, data []byte) ([]byte, error) {
|
||||||
return plaintext, nil
|
return plaintext, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Derive a key from a password and optionally salt.
|
||||||
|
// Returns the hash and salt used
|
||||||
func deriveKey(password, salt []byte) ([]byte, []byte, error) {
|
func deriveKey(password, salt []byte) ([]byte, []byte, error) {
|
||||||
if salt == nil {
|
if salt == nil {
|
||||||
salt = make([]byte, 32)
|
salt = make([]byte, 32)
|
||||||
|
@ -115,12 +121,16 @@ func deriveKey(password, salt []byte) ([]byte, []byte, error) {
|
||||||
return key, salt, nil
|
return key, salt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate the expiration timestamp from the call of the function
|
||||||
func calcAccessExpirationTimestamp() time.Time {
|
func calcAccessExpirationTimestamp() time.Time {
|
||||||
// For now, the default expiration is one month after creation
|
// For now, the default expiration is one month after creation
|
||||||
// though "never" might also be a good option
|
// though "never" might also be a good option
|
||||||
return time.Now().Add(time.Hour * 24 * 30)
|
return time.Now().Add(time.Hour * 24 * 30)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert a list of authentication methods into a [LoginNextState] bitflag.
|
||||||
|
// [isStart] determines whether to allow authentication methods that start
|
||||||
|
// a login process or complete one
|
||||||
func ConvertNewStorageAuthMethodsToLoginState(
|
func ConvertNewStorageAuthMethodsToLoginState(
|
||||||
methods []models.AuthenticationMethodType,
|
methods []models.AuthenticationMethodType,
|
||||||
isStart bool,
|
isStart bool,
|
||||||
|
@ -141,6 +151,7 @@ func ConvertNewStorageAuthMethodsToLoginState(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Translate one [models.AuthenticationMethodType] to one [LoginNextState]
|
||||||
func oneStorageAuthToLoginState(in models.AuthenticationMethodType) LoginNextState {
|
func oneStorageAuthToLoginState(in models.AuthenticationMethodType) LoginNextState {
|
||||||
switch in {
|
switch in {
|
||||||
case models.AuthMethodGAuth:
|
case models.AuthMethodGAuth:
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
const totpUnverifiedSuffix = "-NOT_VERIFIED"
|
const totpUnverifiedSuffix = "-NOT_VERIFIED"
|
||||||
const totpTokenNoLongerRecentlyUsed = time.Second * 90
|
const totpTokenNoLongerRecentlyUsed = time.Second * 90
|
||||||
|
|
||||||
|
// Perform a 2nd factor totp based login
|
||||||
func (a *Authenticator) PerformTotpLogin(
|
func (a *Authenticator) PerformTotpLogin(
|
||||||
username string,
|
username string,
|
||||||
sessionId uint64,
|
sessionId uint64,
|
||||||
|
@ -119,6 +120,8 @@ func (a *Authenticator) PerformTotpLogin(
|
||||||
return LoginNextSucess, token.Token, nil
|
return LoginNextSucess, token.Token, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a new totp key for a user.
|
||||||
|
// The key is marked as not verified until it is sucessfully used once
|
||||||
func (a *Authenticator) StartTotpRegistration(
|
func (a *Authenticator) StartTotpRegistration(
|
||||||
username string,
|
username string,
|
||||||
tokenName string,
|
tokenName string,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Tool for generating helper functions for storage.Role structs inside of the storage package
|
Tool for generating helper functions for [new-storage.Role] structs inside of the storage package
|
||||||
It generates the following functions:
|
It generates the following functions:
|
||||||
- CollapseRolesIntoOne: Collapse a list of roles into one singular role. Each value will be set to the
|
- CollapseRolesIntoOne: Collapse a list of roles into one singular role. Each value will be set to the
|
||||||
value of the role with the highest priority
|
value of the role with the highest priority
|
||||||
|
@ -116,7 +116,12 @@ func main() {
|
||||||
|
|
||||||
// Build role collapse function
|
// Build role collapse function
|
||||||
outBuilder.WriteString(
|
outBuilder.WriteString(
|
||||||
`func CollapseRolesIntoOne(roles ...models.Role) models.Role {
|
`// CollapseRolesIntoOne takes a list of roles and collapses them down into one.
|
||||||
|
// It ensures to follow the priority of each role.
|
||||||
|
// All results will use [models.DefaultUserRole] as the baseline.
|
||||||
|
// The resulting role will have each entry filled with the value of the highest priority.
|
||||||
|
// If multiple roles have the same priority, the order in which they are applied is not stable.
|
||||||
|
func CollapseRolesIntoOne(roles ...models.Role) models.Role {
|
||||||
startingRole := RoleDeepCopy(models.DefaultUserRole)
|
startingRole := RoleDeepCopy(models.DefaultUserRole)
|
||||||
slices.SortFunc(roles, func(a, b models.Role) int { return int(int64(a.Priority)-int64(b.Priority)) })
|
slices.SortFunc(roles, func(a, b models.Role) int { return int(int64(a.Priority)-int64(b.Priority)) })
|
||||||
for _, role := range roles {
|
for _, role := range roles {
|
||||||
|
@ -143,7 +148,12 @@ func main() {
|
||||||
`)
|
`)
|
||||||
|
|
||||||
// Then build the deep copy function
|
// Then build the deep copy function
|
||||||
outBuilder.WriteString("\nfunc RoleDeepCopy(o models.Role) models.Role {\n")
|
outBuilder.WriteString(`
|
||||||
|
// RoleDeepCopy performs a deep copy of a given role.
|
||||||
|
// Each element will point to a newly stored value.
|
||||||
|
// The new and old role will contain identical information.
|
||||||
|
func RoleDeepCopy(o models.Role) models.Role {
|
||||||
|
`)
|
||||||
outBuilder.WriteString(` n := models.Role{}
|
outBuilder.WriteString(` n := models.Role{}
|
||||||
n.Model = o.Model
|
n.Model = o.Model
|
||||||
n.Name = o.Name
|
n.Name = o.Name
|
||||||
|
@ -165,7 +175,10 @@ func main() {
|
||||||
outBuilder.WriteString(" return n\n}\n\n")
|
outBuilder.WriteString(" return n\n}\n\n")
|
||||||
|
|
||||||
// Build compare function
|
// Build compare function
|
||||||
outBuilder.WriteString("func CompareRoles(a, b *models.Role) bool {\n")
|
outBuilder.WriteString(`// Compare two roles for equality.
|
||||||
|
// If a permission is nil in one of the roles, that permission is ignored.
|
||||||
|
func CompareRoles(a, b *models.Role) bool {
|
||||||
|
`)
|
||||||
outBuilder.WriteString(" return ")
|
outBuilder.WriteString(" return ")
|
||||||
lastName, lastType := "", ""
|
lastName, lastType := "", ""
|
||||||
for valName, valType := range nameTypeMap {
|
for valName, valType := range nameTypeMap {
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
/*
|
||||||
|
migrate-new is a helper script for auto-migrating Linstrom's
|
||||||
|
database layout into a database defined in the given config
|
||||||
|
*/
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
/*
|
||||||
|
model-gen generates the gorm-gen interface for interacting
|
||||||
|
with the database. It does not perform migrations on the database.
|
||||||
|
*/
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"git.mstar.dev/mstar/linstrom/storage-new/models"
|
"git.mstar.dev/mstar/linstrom/storage-new/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Auto-migrate all tables and types used
|
||||||
func Migrate(db *gorm.DB) error {
|
func Migrate(db *gorm.DB) error {
|
||||||
if err := createAccountAuthMethodType(db); err != nil {
|
if err := createAccountAuthMethodType(db); err != nil {
|
||||||
return other.Error("storage", "Failed to create Auth Method type", err)
|
return other.Error("storage", "Failed to create Auth Method type", err)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
// Just a list of all models stored in the database
|
// A list of all models stored in the database
|
||||||
var AllTypes = []any{
|
var AllTypes = []any{
|
||||||
&Emote{},
|
&Emote{},
|
||||||
&Feed{},
|
&Feed{},
|
||||||
|
|
|
@ -2,15 +2,17 @@ package models
|
||||||
|
|
||||||
import "gorm.io/gorm"
|
import "gorm.io/gorm"
|
||||||
|
|
||||||
// An emote is effectively an assignment of a name and server
|
// Emotes are combinations of a name, the server it originated from
|
||||||
|
// and the media for it
|
||||||
|
//
|
||||||
|
// TODO: Include the case of unicode icons being used as emote
|
||||||
type Emote struct {
|
type Emote struct {
|
||||||
gorm.Model
|
gorm.Model // Standard gorm model for id and timestamps
|
||||||
// Media used for this emote
|
Metadata MediaMetadata // The media used by this emote
|
||||||
Metadata MediaMetadata // `gorm:"foreignKey:MetadataId"`
|
MetadataId string // Id of the media information, primarily for gorm
|
||||||
MetadataId string
|
|
||||||
// Name of the emote. Also the text for using it in a message (ex. :bob:)
|
// Name of the emote. Also the text for using it in a message (ex. :bob:)
|
||||||
Name string
|
Name string
|
||||||
// Server the emote is from
|
|
||||||
Server RemoteServer // `gorm:"foreignKey:ServerId;references:ID"`
|
Server RemoteServer // Server the emote is from
|
||||||
ServerId uint
|
ServerId uint // Id of the server
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,26 +6,27 @@ import (
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A feed is the initial entry point for all inbound Activitypub events.
|
// A feed is the initial entry point for inbound Activitypub events.
|
||||||
// However, its primary and only user-facing use case is to be a collection
|
// However, its primary and only user-facing use case is to be a collection
|
||||||
// of inbound messages, nothing else.
|
// of inbound messages, nothing else.
|
||||||
//
|
//
|
||||||
// Thus, the flow for inbound events is the following:
|
// Feeds are split into two groups, default and non-default.
|
||||||
// If the event is a note:
|
// Default feeds are feeds automatically created for each user, where their normal
|
||||||
|
// timeline lives in. Additionally, they also relay inbound non-note events,
|
||||||
|
// such as likes/reactions, boosts or follow requests, to their owner.
|
||||||
|
// Default feeds also act using their owner's username (others would ge a follow request from
|
||||||
|
// `username@host`).
|
||||||
//
|
//
|
||||||
// Add it to the receiving feed. If it's a reply and the feed is a default
|
// Non-default feeds, in comparison, are explicitly created by users and can be shared
|
||||||
// create a notification for the owner
|
// between them. Thus, they also only accept note events, dropping everything else.
|
||||||
//
|
// They also are explicitly labeled as such when issuing follow requests (ex `somename-feed@host`)
|
||||||
// If it's an event:
|
|
||||||
//
|
|
||||||
// If the feed is not a default feed for a user, discard the event
|
|
||||||
// If it is the default feed for a user, create a notification for the owner
|
|
||||||
type Feed struct {
|
type Feed struct {
|
||||||
gorm.Model
|
gorm.Model
|
||||||
|
// The name of the feed. Will be equal to the owner username if a default feed
|
||||||
Name string
|
Name string
|
||||||
Owner User
|
Owner User // The owner of the feed
|
||||||
OwnerId string
|
OwnerId string // Id of the owner
|
||||||
IsDefault bool // Whether the feed is the default one for the user
|
IsDefault bool // Whether the feed is the default one for the user
|
||||||
// If a feed is the default one for a user, use that user's public key.
|
// If a feed is the default one for a user, use that user's public key.
|
||||||
// Otherwise, use its own key
|
// Otherwise, use its own key
|
||||||
PublicKey sql.NullString
|
PublicKey sql.NullString
|
||||||
|
|
|
@ -6,9 +6,10 @@ import (
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Metadata for describing some media
|
// MediaMetadata contains metadata about some media file.
|
||||||
// Media is, at least for Linstrom, always stored on a separate server,
|
// These files are never stored directly by Linstrom.
|
||||||
// be that the remote server it originated from or an s3 bucket
|
// Instead, they are either stored on the remote server they originated from
|
||||||
|
// or an s3 bucket if uploaded to Linstrom.
|
||||||
type MediaMetadata struct {
|
type MediaMetadata struct {
|
||||||
ID string `gorm:"primarykey;type:uuid;default:gen_random_uuid()"` // The unique ID of this media file
|
ID string `gorm:"primarykey;type:uuid;default:gen_random_uuid()"` // The unique ID of this media file
|
||||||
CreatedAt time.Time // When this entry was created
|
CreatedAt time.Time // When this entry was created
|
||||||
|
@ -17,7 +18,7 @@ type MediaMetadata struct {
|
||||||
// Soft delete means that this entry still exists in the db, but gorm won't include it anymore unless specifically told to
|
// Soft delete means that this entry still exists in the db, but gorm won't include it anymore unless specifically told to
|
||||||
// If not null, this entry is marked as deleted
|
// If not null, this entry is marked as deleted
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||||
OwnedBy string // Account id this media belongs to
|
OwnedById string // Account id this media belongs to
|
||||||
Remote bool // whether the attachment is a remote one
|
Remote bool // whether the attachment is a remote one
|
||||||
// Where the media is stored. Url
|
// Where the media is stored. Url
|
||||||
Location string
|
Location string
|
||||||
|
|
|
@ -6,14 +6,7 @@ import (
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// User created content, containing some message, maybe attachments,
|
// A note describes some user generated text content.
|
||||||
// tags, pings or other extra things
|
|
||||||
//
|
|
||||||
// Data defined in extra structs (links are included at the bottom):
|
|
||||||
// - Attachments: models.NoteToAttachment
|
|
||||||
// - Emotes: models.NoteToEmote
|
|
||||||
// - Pings: models.NoteToPing
|
|
||||||
// - Tags: models.NoteTag
|
|
||||||
type Note struct {
|
type Note struct {
|
||||||
ID string `gorm:"primarykey;type:uuid;default:gen_random_uuid()"` // Make ID a string (uuid) for other implementations
|
ID string `gorm:"primarykey;type:uuid;default:gen_random_uuid()"` // Make ID a string (uuid) for other implementations
|
||||||
CreatedAt time.Time // When this entry was created
|
CreatedAt time.Time // When this entry was created
|
||||||
|
@ -22,9 +15,9 @@ type Note struct {
|
||||||
// Soft delete means that this entry still exists in the db, but gorm won't include it anymore unless specifically told to
|
// Soft delete means that this entry still exists in the db, but gorm won't include it anymore unless specifically told to
|
||||||
// If not null, this entry is marked as deleted
|
// If not null, this entry is marked as deleted
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||||
Creator User
|
Creator User // The user that created this note
|
||||||
CreatorId string
|
CreatorId string // Id of the creator user
|
||||||
Remote bool // Whether the note is originally a remote one and just "cached"
|
Remote bool // Whether the note is originally a remote one and just "cached"
|
||||||
// Raw content of the note. So without additional formatting applied
|
// Raw content of the note. So without additional formatting applied
|
||||||
// Might already have formatting applied beforehand from the origin server
|
// Might already have formatting applied beforehand from the origin server
|
||||||
RawContent string
|
RawContent string
|
||||||
|
@ -34,8 +27,8 @@ type Note struct {
|
||||||
AccessLevel NoteAccessLevel // Where to send this message to (public, home, followers, dm)
|
AccessLevel NoteAccessLevel // Where to send this message to (public, home, followers, dm)
|
||||||
OriginServer string // Url of the origin server. Also the primary key for those
|
OriginServer string // Url of the origin server. Also the primary key for those
|
||||||
|
|
||||||
AttachmentRelations []NoteToAttachment `gorm:"foreignKey:NoteId"`
|
AttachmentRelations []NoteToAttachment `gorm:"foreignKey:NoteId"` // Attachments added on to this note
|
||||||
EmoteRelations []NoteToEmote `gorm:"foreignKey:NoteId"`
|
EmoteRelations []NoteToEmote `gorm:"foreignKey:NoteId"` // Emotes used in this note
|
||||||
PingRelations []NoteToPing `gorm:"foreignKey:NoteId"`
|
PingRelations []NoteToPing `gorm:"foreignKey:NoteId"` // Pings/mentions this note performs
|
||||||
Tags []NoteTag `gorm:"foreignKey:NoteId"`
|
Tags []NoteTag `gorm:"foreignKey:NoteId"` // Tags this note contains
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,8 @@ package models
|
||||||
// A binding of one note to one media attachment
|
// A binding of one note to one media attachment
|
||||||
type NoteToAttachment struct {
|
type NoteToAttachment struct {
|
||||||
ID uint64 `gorm:"primarykey"`
|
ID uint64 `gorm:"primarykey"`
|
||||||
Note Note
|
Note Note // The note being bound
|
||||||
NoteId string
|
NoteId string
|
||||||
Attachment MediaMetadata
|
Attachment MediaMetadata // The media being bound to
|
||||||
AttachmentId string
|
AttachmentId string
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,8 @@ package models
|
||||||
// A binding of one note to one emote
|
// A binding of one note to one emote
|
||||||
type NoteToEmote struct {
|
type NoteToEmote struct {
|
||||||
ID uint64 `gorm:"primarykey"`
|
ID uint64 `gorm:"primarykey"`
|
||||||
Note Note
|
Note Note // The note being bound
|
||||||
NoteId string
|
NoteId string
|
||||||
Emote Emote
|
Emote Emote // The emote being included
|
||||||
EmoteId string
|
EmoteId string
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,11 +17,13 @@ import "time"
|
||||||
// Also need to store the boosts a user has performed somewhere
|
// Also need to store the boosts a user has performed somewhere
|
||||||
// Maybe adjust Reaction? Though a separate table might be a better option
|
// Maybe adjust Reaction? Though a separate table might be a better option
|
||||||
|
|
||||||
// Assigns a note to a feed
|
// Assigns a note to a feed.
|
||||||
|
// Multiple notes may be assigned to multiple feeds, but each feed may contain
|
||||||
|
// one note at most once.
|
||||||
type NoteToFeed struct {
|
type NoteToFeed struct {
|
||||||
ID uint64 `gorm:"primarykey"`
|
ID uint64 `gorm:"primarykey"`
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
Note Note
|
Note Note // The note being assigned
|
||||||
NoteId string
|
NoteId string
|
||||||
// Feed Feed
|
// Feed Feed
|
||||||
// FeedId uint64
|
// FeedId uint64
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
// A binding of one note to one mentioned account
|
// A binding of one note to one mentioned account.
|
||||||
|
// A note may ping multiple users, but each ping wil be stored
|
||||||
|
// at most once
|
||||||
type NoteToPing struct {
|
type NoteToPing struct {
|
||||||
ID uint64 `gorm:"primarykey"`
|
ID uint64 `gorm:"primarykey"`
|
||||||
Note Note
|
Note Note // The note mentioning an account
|
||||||
NoteId string
|
NoteId string
|
||||||
PingTarget User
|
PingTarget User // The account being mentioned
|
||||||
PingTargetId string
|
PingTargetId string
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
// A binding of one note to one string (hash)tag
|
// A binding of one note to one string (hash)tag.
|
||||||
|
// A note may contain multiple tags, but each tag will be stored at most
|
||||||
|
// once per note
|
||||||
type NoteTag struct {
|
type NoteTag struct {
|
||||||
ID uint64 `gorm:"primarykey"`
|
ID uint64 `gorm:"primarykey"`
|
||||||
Note Note
|
Note Note // The note containing a tag
|
||||||
NoteId string
|
NoteId string
|
||||||
Tag string
|
Tag string // The tag contained
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ package models
|
||||||
|
|
||||||
import "gorm.io/gorm"
|
import "gorm.io/gorm"
|
||||||
|
|
||||||
// A binding of one note to one account reacting to it with one emote
|
// A Reaction is a user liking a note using an emote
|
||||||
type Reaction struct {
|
type Reaction struct {
|
||||||
gorm.Model
|
gorm.Model
|
||||||
Note Note
|
Note Note
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
import "gorm.io/gorm"
|
import (
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
// RemoteServer describes an ActivityPub server
|
// RemoteServer describes an ActivityPub server
|
||||||
// This includes self too
|
// This includes self too
|
||||||
|
@ -9,7 +13,7 @@ type RemoteServer struct {
|
||||||
ServerType ServerSoftwareType // What software the server is running. Useful for formatting
|
ServerType ServerSoftwareType // What software the server is running. Useful for formatting
|
||||||
Domain string // `gorm:"primaryKey"` // Domain the server exists under. Additional primary key
|
Domain string // `gorm:"primaryKey"` // Domain the server exists under. Additional primary key
|
||||||
Name string // What the server wants to be known as (usually same as url)
|
Name string // What the server wants to be known as (usually same as url)
|
||||||
Icon MediaMetadata
|
Icon *MediaMetadata // The icon used by the server. May be empty
|
||||||
IconId string // ID of a media file
|
IconId sql.NullString // ID of a media file
|
||||||
IsSelf bool // Whether this server is yours truly
|
IsSelf bool // Whether this server is yours truly
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,18 +8,19 @@ import (
|
||||||
type ServerSoftwareType string
|
type ServerSoftwareType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Includes forks like glitch-soc, etc
|
// Mastodon and forks (glitch-soc, etc)
|
||||||
ServerSoftwareMastodon = ServerSoftwareType("Mastodon")
|
ServerSoftwareMastodon = ServerSoftwareType("Mastodon")
|
||||||
// Includes forks like Ice Shrimp, Sharkey, Cutiekey, etc
|
// Misskey and forks (Ice Shrimp, Sharkey, Cutiekey, etc)
|
||||||
ServerSoftwareMisskey = ServerSoftwareType("Misskey")
|
ServerSoftwareMisskey = ServerSoftwareType("Misskey")
|
||||||
// Includes Akkoma
|
// Plemora and Akkoma
|
||||||
ServerSoftwarePlemora = ServerSoftwareType("Plemora")
|
ServerSoftwarePlemora = ServerSoftwareType("Plemora")
|
||||||
// Wafrn is a new entry
|
// Wafrn with no known forks
|
||||||
ServerSoftwareWafrn = ServerSoftwareType("Wafrn")
|
ServerSoftwareWafrn = ServerSoftwareType("Wafrn")
|
||||||
// And of course, yours truly
|
// Linstrom with no known forks
|
||||||
ServerSoftwareLinstrom = ServerSoftwareType("Linstrom")
|
ServerSoftwareLinstrom = ServerSoftwareType("Linstrom")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// A list of all known server software systems
|
||||||
var AllServerSoftwareTypes = []ServerSoftwareType{
|
var AllServerSoftwareTypes = []ServerSoftwareType{
|
||||||
ServerSoftwareMastodon,
|
ServerSoftwareMastodon,
|
||||||
ServerSoftwareMisskey,
|
ServerSoftwareMisskey,
|
||||||
|
|
|
@ -2,21 +2,19 @@ package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gorm.io/gen"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// AccessToken maps a unique token to one account.
|
||||||
|
// Access to server resource may only happen with a valid access token.
|
||||||
|
// Access tokens are granted by [auth-new/auth.Authenticator].
|
||||||
|
// Each account may have multiple access tokens at any time
|
||||||
type AccessToken struct {
|
type AccessToken struct {
|
||||||
User User
|
User User // The account the token belongs to
|
||||||
UserId string
|
UserId string
|
||||||
Token string `gorm:"primarykey;type:uuid;default:gen_random_uuid()"`
|
// The token itself is a uuid value
|
||||||
Name string // Token name will be empty if autogenerated with sucessful login
|
Token string `gorm:"primarykey;type:uuid;default:gen_random_uuid()"`
|
||||||
|
Name string // Token name will be empty if autogenerated with sucessful login
|
||||||
// Every token expires, even if set to "not expire". If set to "not expire", it just expires
|
// Every token expires, even if set to "not expire". If set to "not expire", it just expires
|
||||||
// at a point in the future this server should never reach
|
// at a point in the future this server should never reach
|
||||||
ExpiresAt time.Time `gorm:"default:TIMESTAMP WITH TIME ZONE '9999-12-30 23:59:59+00'"`
|
ExpiresAt time.Time `gorm:"default:TIMESTAMP WITH TIME ZONE '9999-12-30 23:59:59+00'"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type IAccessToken interface {
|
|
||||||
// INSERT INTO @@table (user_id, token, name, {{if expiresAt != nil}}, )
|
|
||||||
NewToken(user *User, name string, expiresAt *time.Time) (gen.T, error)
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,10 +2,10 @@ package models
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
// A token used during the login process
|
// A LoginProcessToken contains a token used during the login process.
|
||||||
// Each user may only have at most one login process active at the same time
|
// Login tokens are used during login to identify and track the login process
|
||||||
// Technically, that could be used to permanently block someone from logging in
|
// if said process involves multiple steps (2fa and passkey)..
|
||||||
// by starting a new login process every time the target has just started one
|
// Each user may have multiple active login processes at the same time.
|
||||||
type LoginProcessToken struct {
|
type LoginProcessToken struct {
|
||||||
ID uint64 `gorm:"primarykey"`
|
ID uint64 `gorm:"primarykey"`
|
||||||
User User
|
User User
|
||||||
|
|
|
@ -7,11 +7,11 @@ import (
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A user describes an account for creating content.
|
// A user describes an account for creating content and events.
|
||||||
// This may be controlled by either a human or some external script
|
// This may be controlled by either a human or some external script
|
||||||
//
|
//
|
||||||
// Data stored externally:
|
// Data stored externally:
|
||||||
// - Feed connections (which note belongs in the feed of this user, for what reason)
|
// - Feed connections (which note belongs in the feed of this user, for what reason), see [NoteToFeed]
|
||||||
type User struct {
|
type User struct {
|
||||||
// ID is a uuid for this account
|
// ID is a uuid for this account
|
||||||
//
|
//
|
||||||
|
|
|
@ -6,13 +6,15 @@ import "database/sql/driver"
|
||||||
type AuthenticationMethodType string
|
type AuthenticationMethodType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
AuthMethodPassword AuthenticationMethodType = "password"
|
AuthMethodPassword AuthenticationMethodType = "password" // Password based authentication
|
||||||
AuthMethodGAuth AuthenticationMethodType = "g-auth" // Google Authenticator / totp
|
AuthMethodGAuth AuthenticationMethodType = "g-auth" // Totp based 2nd factor
|
||||||
AuthMethodMail AuthenticationMethodType = "mail"
|
AuthMethodMail AuthenticationMethodType = "mail" // Mail based 2nd factor. Unused
|
||||||
AuthMethodPasskey2fa AuthenticationMethodType = "passkey-2fa" // Passkey used as 2fa factor
|
AuthMethodPasskey2fa AuthenticationMethodType = "passkey-2fa" // Passkey based 2nd factor. Unused
|
||||||
AuthMethodPasskey AuthenticationMethodType = "passkey" // Passkey as only auth key
|
AuthMethodPasskey AuthenticationMethodType = "passkey" // Passkey as only auth key
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// A list of all known authentication methods.
|
||||||
|
// Known != supported
|
||||||
var AllAuthMethods = []AuthenticationMethodType{
|
var AllAuthMethods = []AuthenticationMethodType{
|
||||||
AuthMethodPassword, AuthMethodGAuth, AuthMethodMail, AuthMethodPasskey, AuthMethodPasskey2fa,
|
AuthMethodPassword, AuthMethodGAuth, AuthMethodMail, AuthMethodPasskey, AuthMethodPasskey2fa,
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,12 +10,12 @@ import (
|
||||||
type BeingType string
|
type BeingType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
BEING_HUMAN = BeingType("human")
|
BEING_HUMAN = BeingType("human") // Is a human
|
||||||
BEING_CAT = BeingType("cat")
|
BEING_CAT = BeingType("cat") // Is a cat
|
||||||
BEING_FOX = BeingType("fox")
|
BEING_FOX = BeingType("fox") // Is a fox
|
||||||
BEING_DOG = BeingType("dog")
|
BEING_DOG = BeingType("dog") // Is a dog
|
||||||
BEING_ROBOT = BeingType("robot")
|
BEING_ROBOT = BeingType("robot") // Is a robot
|
||||||
BEING_DOLL = BeingType("doll")
|
BEING_DOLL = BeingType("doll") // Is a doll
|
||||||
)
|
)
|
||||||
|
|
||||||
var AllBeings = []BeingType{BEING_HUMAN, BEING_CAT, BEING_FOX, BEING_DOG, BEING_ROBOT, BEING_DOLL}
|
var AllBeings = []BeingType{BEING_HUMAN, BEING_CAT, BEING_FOX, BEING_DOG, BEING_ROBOT, BEING_DOLL}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
// Defines an account to be a being of the set type
|
// Defines an user to be a being of the set type
|
||||||
// Multiple are possible for combination
|
// Each user may have multiple mappings
|
||||||
|
//
|
||||||
|
// TODO: Decide whether Being here could be changed to an open string instead
|
||||||
type UserToBeing struct {
|
type UserToBeing struct {
|
||||||
ID uint64 `gorm:"primarykey"`
|
ID uint64 `gorm:"primarykey"`
|
||||||
User User
|
User User
|
||||||
|
|
|
@ -6,7 +6,8 @@ import (
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// One key-value field attached to an account
|
// A UserInfoField describes one custom key-value information field.
|
||||||
|
// Each user may have none, one or more of these fields.
|
||||||
// If the value is an uri, the server may attempt to verify ownership
|
// If the value is an uri, the server may attempt to verify ownership
|
||||||
// over that uri by checking the content for a `rel="me"` anchor
|
// over that uri by checking the content for a `rel="me"` anchor
|
||||||
// linking back to the account the field is attached to
|
// linking back to the account the field is attached to
|
||||||
|
|
|
@ -6,14 +6,14 @@ import "database/sql/driver"
|
||||||
type RelationType string
|
type RelationType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
RelationFollow RelationType = "follow"
|
RelationFollow RelationType = "follow" // X follows Y
|
||||||
RelationMute RelationType = "mute"
|
RelationMute RelationType = "mute" // X has Y muted (X doesn't see Y, but Y still X)
|
||||||
RelationNoBoosts RelationType = "no-boosts"
|
RelationNoBoosts RelationType = "no-boosts" // X has Ys boosts muted
|
||||||
RelationBlock RelationType = "block"
|
RelationBlock RelationType = "block" // X has Y blocked (X doesn't see Y and Y doesn't see X)
|
||||||
RelationPreventFollow RelationType = "prevent-follow"
|
RelationPreventFollow RelationType = "prevent-follow" // X blocks Y from following (Y can still see X)
|
||||||
)
|
)
|
||||||
|
|
||||||
// var AllBeings = []BeingType{BEING_HUMAN, BEING_CAT, BEING_FOX, BEING_DOG, BEING_ROBOT, BEING_DOLL}
|
// List of all relation types known
|
||||||
var AllRelations = []RelationType{
|
var AllRelations = []RelationType{
|
||||||
RelationFollow,
|
RelationFollow,
|
||||||
RelationMute,
|
RelationMute,
|
||||||
|
|
|
@ -2,7 +2,7 @@ package models
|
||||||
|
|
||||||
import "gorm.io/gorm"
|
import "gorm.io/gorm"
|
||||||
|
|
||||||
// "Cached" extra data for accounts, in case they are remote
|
// UserRemoteLinks contains cached links for remote users
|
||||||
type UserRemoteLinks struct {
|
type UserRemoteLinks struct {
|
||||||
// ---- Section: gorm
|
// ---- Section: gorm
|
||||||
// Sets this struct up as a value that an Account may have
|
// Sets this struct up as a value that an Account may have
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
// A (hash)tag appearing on an account's profile description
|
// A (hash)tag appearing on an account's profile description
|
||||||
// Accounts may have multiple tags, but each tag may only be stored once at most
|
// Users may have multiple tags, but each user to tag relation may only
|
||||||
|
// appear at most once
|
||||||
type UserToTag struct {
|
type UserToTag struct {
|
||||||
ID uint64 `gorm:"primarykey"`
|
ID uint64 `gorm:"primarykey"`
|
||||||
User User
|
User User
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
|
// Adds one pronoun to a user.
|
||||||
|
// Each user may have zero, one or more pronouns
|
||||||
|
// but each user to pronoun relation may appear at most once
|
||||||
type UserToPronoun struct {
|
type UserToPronoun struct {
|
||||||
ID uint64 `gorm:"primarykey"`
|
ID uint64 `gorm:"primarykey"`
|
||||||
User User
|
User User
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
// A link of one account to one role
|
// A link of one account to one role
|
||||||
// There may be multiple of these links per user and per role
|
// Each user may have one or more roles
|
||||||
// But a role may only be linked at most once to the same user
|
// (every user has the default role and their personal one)
|
||||||
|
// but each user to role combination may appear at most once
|
||||||
type UserToRole struct {
|
type UserToRole struct {
|
||||||
ID uint64 `gorm:"primarykey"`
|
ID uint64 `gorm:"primarykey"`
|
||||||
User User
|
User User
|
||||||
|
|
|
@ -5,9 +5,9 @@ package models
|
||||||
// each describing a different aspect
|
// each describing a different aspect
|
||||||
type UserToUserRelation struct {
|
type UserToUserRelation struct {
|
||||||
ID uint64 `gorm:"primarykey"`
|
ID uint64 `gorm:"primarykey"`
|
||||||
User User
|
User User // The user X described in [RelationType]
|
||||||
UserId string
|
UserId string
|
||||||
TargetUser User
|
TargetUser User // The user Y described in [RelationType]
|
||||||
TargetUserId string
|
TargetUserId string
|
||||||
Relation RelationType `gorm:"type:relation_type"`
|
Relation RelationType `gorm:"type:relation_type"`
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue